Skip to content

Commit a4ee40b

Browse files
committed
selftests: hid: import hid-tools hid-sony and hid-playstation tests
These tests have been developed in the hid-tools[0] tree for a while. Now that we have a proper selftests/hid kernel entry and that the tests are more reliable, it is time to directly include those in the kernel tree. The code is taken from [1] to fix a change in v6.3. [0] https://gitlab.freedesktop.org/libevdev/hid-tools Link: https://gitlab.freedesktop.org/libevdev/hid-tools/-/merge_requests/143 [1] Cc: Roderick Colenbrander <[email protected]> Cc: Jose Torreguitar <[email protected]> Signed-off-by: Roderick Colenbrander <[email protected]> Signed-off-by: Benjamin Tissoires <[email protected]>
1 parent ff3b222 commit a4ee40b

File tree

4 files changed

+355
-0
lines changed

4 files changed

+355
-0
lines changed

tools/testing/selftests/hid/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ TEST_PROGS += hid-ite.sh
1212
TEST_PROGS += hid-keyboard.sh
1313
TEST_PROGS += hid-mouse.sh
1414
TEST_PROGS += hid-multitouch.sh
15+
TEST_PROGS += hid-sony.sh
1516
TEST_PROGS += hid-tablet.sh
1617
TEST_PROGS += hid-wacom.sh
1718

tools/testing/selftests/hid/config

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,14 @@ CONFIG_HID=y
2020
CONFIG_HID_BPF=y
2121
CONFIG_INPUT_EVDEV=y
2222
CONFIG_UHID=y
23+
CONFIG_LEDS_CLASS_MULTICOLOR=y
2324
CONFIG_USB=y
2425
CONFIG_USB_HID=y
2526
CONFIG_HID_APPLE=y
2627
CONFIG_HID_ITE=y
2728
CONFIG_HID_MULTITOUCH=y
29+
CONFIG_HID_PLAYSTATION=y
30+
CONFIG_PLAYSTATION_FF=y
31+
CONFIG_HID_SONY=y
32+
CONFIG_SONY_FF=y
2833
CONFIG_HID_WACOM=y
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/bin/sh
2+
# SPDX-License-Identifier: GPL-2.0
3+
# Runs tests for the HID subsystem
4+
5+
export TARGET=test_sony.py
6+
7+
bash ./run-hid-tools-tests.sh
Lines changed: 342 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,342 @@
1+
#!/bin/env python3
2+
# SPDX-License-Identifier: GPL-2.0
3+
# -*- coding: utf-8 -*-
4+
#
5+
# Copyright (c) 2020 Benjamin Tissoires <[email protected]>
6+
# Copyright (c) 2020 Red Hat, Inc.
7+
#
8+
9+
from .base import application_matches
10+
from .test_gamepad import BaseTest
11+
from hidtools.device.sony_gamepad import (
12+
PS3Controller,
13+
PS4ControllerBluetooth,
14+
PS4ControllerUSB,
15+
PS5ControllerBluetooth,
16+
PS5ControllerUSB,
17+
PSTouchPoint,
18+
)
19+
from hidtools.util import BusType
20+
21+
import libevdev
22+
import logging
23+
import pytest
24+
25+
logger = logging.getLogger("hidtools.test.sony")
26+
27+
PS3_MODULE = ("sony", "hid_sony")
28+
PS4_MODULE = ("playstation", "hid_playstation")
29+
PS5_MODULE = ("playstation", "hid_playstation")
30+
31+
32+
class SonyBaseTest:
33+
class SonyTest(BaseTest.TestGamepad):
34+
pass
35+
36+
class SonyPS4ControllerTest(SonyTest):
37+
kernel_modules = [PS4_MODULE]
38+
39+
def test_accelerometer(self):
40+
uhdev = self.uhdev
41+
evdev = uhdev.get_evdev("Accelerometer")
42+
43+
for x in range(-32000, 32000, 4000):
44+
r = uhdev.event(accel=(x, None, None))
45+
events = uhdev.next_sync_events("Accelerometer")
46+
self.debug_reports(r, uhdev, events)
47+
48+
assert libevdev.InputEvent(libevdev.EV_ABS.ABS_X) in events
49+
value = evdev.value[libevdev.EV_ABS.ABS_X]
50+
# Check against range due to small loss in precision due
51+
# to inverse calibration, followed by calibration by hid-sony.
52+
assert x - 1 <= value <= x + 1
53+
54+
for y in range(-32000, 32000, 4000):
55+
r = uhdev.event(accel=(None, y, None))
56+
events = uhdev.next_sync_events("Accelerometer")
57+
self.debug_reports(r, uhdev, events)
58+
59+
assert libevdev.InputEvent(libevdev.EV_ABS.ABS_Y) in events
60+
value = evdev.value[libevdev.EV_ABS.ABS_Y]
61+
assert y - 1 <= value <= y + 1
62+
63+
for z in range(-32000, 32000, 4000):
64+
r = uhdev.event(accel=(None, None, z))
65+
events = uhdev.next_sync_events("Accelerometer")
66+
self.debug_reports(r, uhdev, events)
67+
68+
assert libevdev.InputEvent(libevdev.EV_ABS.ABS_Z) in events
69+
value = evdev.value[libevdev.EV_ABS.ABS_Z]
70+
assert z - 1 <= value <= z + 1
71+
72+
def test_gyroscope(self):
73+
uhdev = self.uhdev
74+
evdev = uhdev.get_evdev("Accelerometer")
75+
76+
for rx in range(-2000000, 2000000, 200000):
77+
r = uhdev.event(gyro=(rx, None, None))
78+
events = uhdev.next_sync_events("Accelerometer")
79+
self.debug_reports(r, uhdev, events)
80+
81+
assert libevdev.InputEvent(libevdev.EV_ABS.ABS_RX) in events
82+
value = evdev.value[libevdev.EV_ABS.ABS_RX]
83+
# Sensor internal value is 16-bit, but calibrated is 22-bit, so
84+
# 6-bit (64) difference, so allow a range of +/- 64.
85+
assert rx - 64 <= value <= rx + 64
86+
87+
for ry in range(-2000000, 2000000, 200000):
88+
r = uhdev.event(gyro=(None, ry, None))
89+
events = uhdev.next_sync_events("Accelerometer")
90+
self.debug_reports(r, uhdev, events)
91+
92+
assert libevdev.InputEvent(libevdev.EV_ABS.ABS_RY) in events
93+
value = evdev.value[libevdev.EV_ABS.ABS_RY]
94+
assert ry - 64 <= value <= ry + 64
95+
96+
for rz in range(-2000000, 2000000, 200000):
97+
r = uhdev.event(gyro=(None, None, rz))
98+
events = uhdev.next_sync_events("Accelerometer")
99+
self.debug_reports(r, uhdev, events)
100+
101+
assert libevdev.InputEvent(libevdev.EV_ABS.ABS_RZ) in events
102+
value = evdev.value[libevdev.EV_ABS.ABS_RZ]
103+
assert rz - 64 <= value <= rz + 64
104+
105+
def test_battery(self):
106+
uhdev = self.uhdev
107+
108+
assert uhdev.power_supply_class is not None
109+
110+
# DS4 capacity levels are in increments of 10.
111+
# Battery is never below 5%.
112+
for i in range(5, 105, 10):
113+
uhdev.battery.capacity = i
114+
uhdev.event()
115+
assert uhdev.power_supply_class.capacity == i
116+
117+
# Discharging tests only make sense for BlueTooth.
118+
if uhdev.bus == BusType.BLUETOOTH:
119+
uhdev.battery.cable_connected = False
120+
uhdev.battery.capacity = 45
121+
uhdev.event()
122+
assert uhdev.power_supply_class.status == "Discharging"
123+
124+
uhdev.battery.cable_connected = True
125+
uhdev.battery.capacity = 5
126+
uhdev.event()
127+
assert uhdev.power_supply_class.status == "Charging"
128+
129+
uhdev.battery.capacity = 100
130+
uhdev.event()
131+
assert uhdev.power_supply_class.status == "Charging"
132+
133+
uhdev.battery.full = True
134+
uhdev.event()
135+
assert uhdev.power_supply_class.status == "Full"
136+
137+
def test_mt_single_touch(self):
138+
"""send a single touch in the first slot of the device,
139+
and release it."""
140+
uhdev = self.uhdev
141+
evdev = uhdev.get_evdev("Touch Pad")
142+
143+
t0 = PSTouchPoint(1, 50, 100)
144+
r = uhdev.event(touch=[t0])
145+
events = uhdev.next_sync_events("Touch Pad")
146+
self.debug_reports(r, uhdev, events)
147+
148+
assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events
149+
assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0
150+
assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50
151+
assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100
152+
153+
t0.tipswitch = False
154+
r = uhdev.event(touch=[t0])
155+
events = uhdev.next_sync_events("Touch Pad")
156+
self.debug_reports(r, uhdev, events)
157+
assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 0) in events
158+
assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
159+
160+
def test_mt_dual_touch(self):
161+
"""Send 2 touches in the first 2 slots.
162+
Make sure the kernel sees this as a dual touch.
163+
Release and check
164+
165+
Note: PTP will send here BTN_DOUBLETAP emulation"""
166+
uhdev = self.uhdev
167+
evdev = uhdev.get_evdev("Touch Pad")
168+
169+
t0 = PSTouchPoint(1, 50, 100)
170+
t1 = PSTouchPoint(2, 150, 200)
171+
172+
r = uhdev.event(touch=[t0])
173+
events = uhdev.next_sync_events("Touch Pad")
174+
self.debug_reports(r, uhdev, events)
175+
176+
assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events
177+
assert evdev.value[libevdev.EV_KEY.BTN_TOUCH] == 1
178+
assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0
179+
assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50
180+
assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100
181+
assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
182+
183+
r = uhdev.event(touch=[t0, t1])
184+
events = uhdev.next_sync_events("Touch Pad")
185+
self.debug_reports(r, uhdev, events)
186+
assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH) not in events
187+
assert evdev.value[libevdev.EV_KEY.BTN_TOUCH] == 1
188+
assert (
189+
libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_X, 5) not in events
190+
)
191+
assert (
192+
libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_Y, 10) not in events
193+
)
194+
assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0
195+
assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50
196+
assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100
197+
assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 1
198+
assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_POSITION_X] == 150
199+
assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 200
200+
201+
t0.tipswitch = False
202+
r = uhdev.event(touch=[t0, t1])
203+
events = uhdev.next_sync_events("Touch Pad")
204+
self.debug_reports(r, uhdev, events)
205+
assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
206+
assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 1
207+
assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_X) not in events
208+
assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_Y) not in events
209+
210+
t1.tipswitch = False
211+
r = uhdev.event(touch=[t1])
212+
213+
events = uhdev.next_sync_events("Touch Pad")
214+
self.debug_reports(r, uhdev, events)
215+
assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
216+
assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
217+
218+
219+
class TestPS3Controller(SonyBaseTest.SonyTest):
220+
kernel_modules = [PS3_MODULE]
221+
222+
def create_device(self):
223+
controller = PS3Controller()
224+
controller.application_matches = application_matches
225+
return controller
226+
227+
@pytest.fixture(autouse=True)
228+
def start_controller(self):
229+
# emulate a 'PS' button press to tell the kernel we are ready to accept events
230+
self.assert_button(17)
231+
232+
# drain any remaining udev events
233+
while self.uhdev.dispatch(10):
234+
pass
235+
236+
def test_led(self):
237+
for k, v in self.uhdev.led_classes.items():
238+
# the kernel might have set a LED for us
239+
logger.info(f"{k}: {v.brightness}")
240+
241+
idx = int(k[-1]) - 1
242+
assert self.uhdev.hw_leds.get_led(idx)[0] == bool(v.brightness)
243+
244+
v.brightness = 0
245+
self.uhdev.dispatch(10)
246+
assert self.uhdev.hw_leds.get_led(idx)[0] is False
247+
248+
v.brightness = v.max_brightness
249+
self.uhdev.dispatch(10)
250+
assert self.uhdev.hw_leds.get_led(idx)[0]
251+
252+
253+
class CalibratedPS4Controller(object):
254+
# DS4 reports uncalibrated sensor data. Calibration coefficients
255+
# can be retrieved using a feature report (0x2 USB / 0x5 BT).
256+
# The values below are the processed calibration values for the
257+
# DS4s matching the feature reports of PS4ControllerBluetooth/USB
258+
# as dumped from hid-sony 'ds4_get_calibration_data'.
259+
#
260+
# Note we duplicate those values here in case the kernel changes them
261+
# so we can have tests passing even if hid-tools doesn't have the
262+
# correct values.
263+
accelerometer_calibration_data = {
264+
"x": {"bias": -73, "numer": 16384, "denom": 16472},
265+
"y": {"bias": -352, "numer": 16384, "denom": 16344},
266+
"z": {"bias": 81, "numer": 16384, "denom": 16319},
267+
}
268+
gyroscope_calibration_data = {
269+
"x": {"bias": 0, "numer": 1105920, "denom": 17827},
270+
"y": {"bias": 0, "numer": 1105920, "denom": 17777},
271+
"z": {"bias": 0, "numer": 1105920, "denom": 17748},
272+
}
273+
274+
275+
class CalibratedPS4ControllerBluetooth(CalibratedPS4Controller, PS4ControllerBluetooth):
276+
pass
277+
278+
279+
class TestPS4ControllerBluetooth(SonyBaseTest.SonyPS4ControllerTest):
280+
def create_device(self):
281+
controller = CalibratedPS4ControllerBluetooth()
282+
controller.application_matches = application_matches
283+
return controller
284+
285+
286+
class CalibratedPS4ControllerUSB(CalibratedPS4Controller, PS4ControllerUSB):
287+
pass
288+
289+
290+
class TestPS4ControllerUSB(SonyBaseTest.SonyPS4ControllerTest):
291+
def create_device(self):
292+
controller = CalibratedPS4ControllerUSB()
293+
controller.application_matches = application_matches
294+
return controller
295+
296+
297+
class CalibratedPS5Controller(object):
298+
# DualSense reports uncalibrated sensor data. Calibration coefficients
299+
# can be retrieved using feature report 0x09.
300+
# The values below are the processed calibration values for the
301+
# DualSene matching the feature reports of PS5ControllerBluetooth/USB
302+
# as dumped from hid-playstation 'dualsense_get_calibration_data'.
303+
#
304+
# Note we duplicate those values here in case the kernel changes them
305+
# so we can have tests passing even if hid-tools doesn't have the
306+
# correct values.
307+
accelerometer_calibration_data = {
308+
"x": {"bias": 0, "numer": 16384, "denom": 16374},
309+
"y": {"bias": -114, "numer": 16384, "denom": 16362},
310+
"z": {"bias": 2, "numer": 16384, "denom": 16395},
311+
}
312+
gyroscope_calibration_data = {
313+
"x": {"bias": 0, "numer": 1105920, "denom": 17727},
314+
"y": {"bias": 0, "numer": 1105920, "denom": 17728},
315+
"z": {"bias": 0, "numer": 1105920, "denom": 17769},
316+
}
317+
318+
319+
class CalibratedPS5ControllerBluetooth(CalibratedPS5Controller, PS5ControllerBluetooth):
320+
pass
321+
322+
323+
class TestPS5ControllerBluetooth(SonyBaseTest.SonyPS4ControllerTest):
324+
kernel_modules = [PS5_MODULE]
325+
326+
def create_device(self):
327+
controller = CalibratedPS5ControllerBluetooth()
328+
controller.application_matches = application_matches
329+
return controller
330+
331+
332+
class CalibratedPS5ControllerUSB(CalibratedPS5Controller, PS5ControllerUSB):
333+
pass
334+
335+
336+
class TestPS5ControllerUSB(SonyBaseTest.SonyPS4ControllerTest):
337+
kernel_modules = [PS5_MODULE]
338+
339+
def create_device(self):
340+
controller = CalibratedPS5ControllerUSB()
341+
controller.application_matches = application_matches
342+
return controller

0 commit comments

Comments
 (0)