From a4ee40b6ac281125c3b7aa3e42401b271c50c7bc Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Fri, 17 Feb 2023 13:56:04 +0100 Subject: 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 Cc: Jose Torreguitar Signed-off-by: Roderick Colenbrander Signed-off-by: Benjamin Tissoires --- tools/testing/selftests/hid/Makefile | 1 + tools/testing/selftests/hid/config | 5 + tools/testing/selftests/hid/hid-sony.sh | 7 + tools/testing/selftests/hid/tests/test_sony.py | 342 +++++++++++++++++++++++++ 4 files changed, 355 insertions(+) create mode 100755 tools/testing/selftests/hid/hid-sony.sh create mode 100644 tools/testing/selftests/hid/tests/test_sony.py (limited to 'tools/testing') diff --git a/tools/testing/selftests/hid/Makefile b/tools/testing/selftests/hid/Makefile index 3ca696c44aab..dcea4f1e9369 100644 --- a/tools/testing/selftests/hid/Makefile +++ b/tools/testing/selftests/hid/Makefile @@ -12,6 +12,7 @@ TEST_PROGS += hid-ite.sh TEST_PROGS += hid-keyboard.sh TEST_PROGS += hid-mouse.sh TEST_PROGS += hid-multitouch.sh +TEST_PROGS += hid-sony.sh TEST_PROGS += hid-tablet.sh TEST_PROGS += hid-wacom.sh diff --git a/tools/testing/selftests/hid/config b/tools/testing/selftests/hid/config index d4e3e0374ddd..4f425178b56f 100644 --- a/tools/testing/selftests/hid/config +++ b/tools/testing/selftests/hid/config @@ -20,9 +20,14 @@ CONFIG_HID=y CONFIG_HID_BPF=y CONFIG_INPUT_EVDEV=y CONFIG_UHID=y +CONFIG_LEDS_CLASS_MULTICOLOR=y CONFIG_USB=y CONFIG_USB_HID=y CONFIG_HID_APPLE=y CONFIG_HID_ITE=y CONFIG_HID_MULTITOUCH=y +CONFIG_HID_PLAYSTATION=y +CONFIG_PLAYSTATION_FF=y +CONFIG_HID_SONY=y +CONFIG_SONY_FF=y CONFIG_HID_WACOM=y diff --git a/tools/testing/selftests/hid/hid-sony.sh b/tools/testing/selftests/hid/hid-sony.sh new file mode 100755 index 000000000000..c863c442686e --- /dev/null +++ b/tools/testing/selftests/hid/hid-sony.sh @@ -0,0 +1,7 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# Runs tests for the HID subsystem + +export TARGET=test_sony.py + +bash ./run-hid-tools-tests.sh diff --git a/tools/testing/selftests/hid/tests/test_sony.py b/tools/testing/selftests/hid/tests/test_sony.py new file mode 100644 index 000000000000..7e52c28e59c5 --- /dev/null +++ b/tools/testing/selftests/hid/tests/test_sony.py @@ -0,0 +1,342 @@ +#!/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2020 Benjamin Tissoires +# Copyright (c) 2020 Red Hat, Inc. +# + +from .base import application_matches +from .test_gamepad import BaseTest +from hidtools.device.sony_gamepad import ( + PS3Controller, + PS4ControllerBluetooth, + PS4ControllerUSB, + PS5ControllerBluetooth, + PS5ControllerUSB, + PSTouchPoint, +) +from hidtools.util import BusType + +import libevdev +import logging +import pytest + +logger = logging.getLogger("hidtools.test.sony") + +PS3_MODULE = ("sony", "hid_sony") +PS4_MODULE = ("playstation", "hid_playstation") +PS5_MODULE = ("playstation", "hid_playstation") + + +class SonyBaseTest: + class SonyTest(BaseTest.TestGamepad): + pass + + class SonyPS4ControllerTest(SonyTest): + kernel_modules = [PS4_MODULE] + + def test_accelerometer(self): + uhdev = self.uhdev + evdev = uhdev.get_evdev("Accelerometer") + + for x in range(-32000, 32000, 4000): + r = uhdev.event(accel=(x, None, None)) + events = uhdev.next_sync_events("Accelerometer") + self.debug_reports(r, uhdev, events) + + assert libevdev.InputEvent(libevdev.EV_ABS.ABS_X) in events + value = evdev.value[libevdev.EV_ABS.ABS_X] + # Check against range due to small loss in precision due + # to inverse calibration, followed by calibration by hid-sony. + assert x - 1 <= value <= x + 1 + + for y in range(-32000, 32000, 4000): + r = uhdev.event(accel=(None, y, None)) + events = uhdev.next_sync_events("Accelerometer") + self.debug_reports(r, uhdev, events) + + assert libevdev.InputEvent(libevdev.EV_ABS.ABS_Y) in events + value = evdev.value[libevdev.EV_ABS.ABS_Y] + assert y - 1 <= value <= y + 1 + + for z in range(-32000, 32000, 4000): + r = uhdev.event(accel=(None, None, z)) + events = uhdev.next_sync_events("Accelerometer") + self.debug_reports(r, uhdev, events) + + assert libevdev.InputEvent(libevdev.EV_ABS.ABS_Z) in events + value = evdev.value[libevdev.EV_ABS.ABS_Z] + assert z - 1 <= value <= z + 1 + + def test_gyroscope(self): + uhdev = self.uhdev + evdev = uhdev.get_evdev("Accelerometer") + + for rx in range(-2000000, 2000000, 200000): + r = uhdev.event(gyro=(rx, None, None)) + events = uhdev.next_sync_events("Accelerometer") + self.debug_reports(r, uhdev, events) + + assert libevdev.InputEvent(libevdev.EV_ABS.ABS_RX) in events + value = evdev.value[libevdev.EV_ABS.ABS_RX] + # Sensor internal value is 16-bit, but calibrated is 22-bit, so + # 6-bit (64) difference, so allow a range of +/- 64. + assert rx - 64 <= value <= rx + 64 + + for ry in range(-2000000, 2000000, 200000): + r = uhdev.event(gyro=(None, ry, None)) + events = uhdev.next_sync_events("Accelerometer") + self.debug_reports(r, uhdev, events) + + assert libevdev.InputEvent(libevdev.EV_ABS.ABS_RY) in events + value = evdev.value[libevdev.EV_ABS.ABS_RY] + assert ry - 64 <= value <= ry + 64 + + for rz in range(-2000000, 2000000, 200000): + r = uhdev.event(gyro=(None, None, rz)) + events = uhdev.next_sync_events("Accelerometer") + self.debug_reports(r, uhdev, events) + + assert libevdev.InputEvent(libevdev.EV_ABS.ABS_RZ) in events + value = evdev.value[libevdev.EV_ABS.ABS_RZ] + assert rz - 64 <= value <= rz + 64 + + def test_battery(self): + uhdev = self.uhdev + + assert uhdev.power_supply_class is not None + + # DS4 capacity levels are in increments of 10. + # Battery is never below 5%. + for i in range(5, 105, 10): + uhdev.battery.capacity = i + uhdev.event() + assert uhdev.power_supply_class.capacity == i + + # Discharging tests only make sense for BlueTooth. + if uhdev.bus == BusType.BLUETOOTH: + uhdev.battery.cable_connected = False + uhdev.battery.capacity = 45 + uhdev.event() + assert uhdev.power_supply_class.status == "Discharging" + + uhdev.battery.cable_connected = True + uhdev.battery.capacity = 5 + uhdev.event() + assert uhdev.power_supply_class.status == "Charging" + + uhdev.battery.capacity = 100 + uhdev.event() + assert uhdev.power_supply_class.status == "Charging" + + uhdev.battery.full = True + uhdev.event() + assert uhdev.power_supply_class.status == "Full" + + def test_mt_single_touch(self): + """send a single touch in the first slot of the device, + and release it.""" + uhdev = self.uhdev + evdev = uhdev.get_evdev("Touch Pad") + + t0 = PSTouchPoint(1, 50, 100) + r = uhdev.event(touch=[t0]) + events = uhdev.next_sync_events("Touch Pad") + self.debug_reports(r, uhdev, events) + + assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events + assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0 + assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50 + assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100 + + t0.tipswitch = False + r = uhdev.event(touch=[t0]) + events = uhdev.next_sync_events("Touch Pad") + self.debug_reports(r, uhdev, events) + assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 0) in events + assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1 + + def test_mt_dual_touch(self): + """Send 2 touches in the first 2 slots. + Make sure the kernel sees this as a dual touch. + Release and check + + Note: PTP will send here BTN_DOUBLETAP emulation""" + uhdev = self.uhdev + evdev = uhdev.get_evdev("Touch Pad") + + t0 = PSTouchPoint(1, 50, 100) + t1 = PSTouchPoint(2, 150, 200) + + r = uhdev.event(touch=[t0]) + events = uhdev.next_sync_events("Touch Pad") + self.debug_reports(r, uhdev, events) + + assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events + assert evdev.value[libevdev.EV_KEY.BTN_TOUCH] == 1 + assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0 + assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50 + assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100 + assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1 + + r = uhdev.event(touch=[t0, t1]) + events = uhdev.next_sync_events("Touch Pad") + self.debug_reports(r, uhdev, events) + assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH) not in events + assert evdev.value[libevdev.EV_KEY.BTN_TOUCH] == 1 + assert ( + libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_X, 5) not in events + ) + assert ( + libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_Y, 10) not in events + ) + assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0 + assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50 + assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100 + assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 1 + assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_POSITION_X] == 150 + assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 200 + + t0.tipswitch = False + r = uhdev.event(touch=[t0, t1]) + events = uhdev.next_sync_events("Touch Pad") + self.debug_reports(r, uhdev, events) + assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1 + assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 1 + assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_X) not in events + assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_Y) not in events + + t1.tipswitch = False + r = uhdev.event(touch=[t1]) + + events = uhdev.next_sync_events("Touch Pad") + self.debug_reports(r, uhdev, events) + assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1 + assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1 + + +class TestPS3Controller(SonyBaseTest.SonyTest): + kernel_modules = [PS3_MODULE] + + def create_device(self): + controller = PS3Controller() + controller.application_matches = application_matches + return controller + + @pytest.fixture(autouse=True) + def start_controller(self): + # emulate a 'PS' button press to tell the kernel we are ready to accept events + self.assert_button(17) + + # drain any remaining udev events + while self.uhdev.dispatch(10): + pass + + def test_led(self): + for k, v in self.uhdev.led_classes.items(): + # the kernel might have set a LED for us + logger.info(f"{k}: {v.brightness}") + + idx = int(k[-1]) - 1 + assert self.uhdev.hw_leds.get_led(idx)[0] == bool(v.brightness) + + v.brightness = 0 + self.uhdev.dispatch(10) + assert self.uhdev.hw_leds.get_led(idx)[0] is False + + v.brightness = v.max_brightness + self.uhdev.dispatch(10) + assert self.uhdev.hw_leds.get_led(idx)[0] + + +class CalibratedPS4Controller(object): + # DS4 reports uncalibrated sensor data. Calibration coefficients + # can be retrieved using a feature report (0x2 USB / 0x5 BT). + # The values below are the processed calibration values for the + # DS4s matching the feature reports of PS4ControllerBluetooth/USB + # as dumped from hid-sony 'ds4_get_calibration_data'. + # + # Note we duplicate those values here in case the kernel changes them + # so we can have tests passing even if hid-tools doesn't have the + # correct values. + accelerometer_calibration_data = { + "x": {"bias": -73, "numer": 16384, "denom": 16472}, + "y": {"bias": -352, "numer": 16384, "denom": 16344}, + "z": {"bias": 81, "numer": 16384, "denom": 16319}, + } + gyroscope_calibration_data = { + "x": {"bias": 0, "numer": 1105920, "denom": 17827}, + "y": {"bias": 0, "numer": 1105920, "denom": 17777}, + "z": {"bias": 0, "numer": 1105920, "denom": 17748}, + } + + +class CalibratedPS4ControllerBluetooth(CalibratedPS4Controller, PS4ControllerBluetooth): + pass + + +class TestPS4ControllerBluetooth(SonyBaseTest.SonyPS4ControllerTest): + def create_device(self): + controller = CalibratedPS4ControllerBluetooth() + controller.application_matches = application_matches + return controller + + +class CalibratedPS4ControllerUSB(CalibratedPS4Controller, PS4ControllerUSB): + pass + + +class TestPS4ControllerUSB(SonyBaseTest.SonyPS4ControllerTest): + def create_device(self): + controller = CalibratedPS4ControllerUSB() + controller.application_matches = application_matches + return controller + + +class CalibratedPS5Controller(object): + # DualSense reports uncalibrated sensor data. Calibration coefficients + # can be retrieved using feature report 0x09. + # The values below are the processed calibration values for the + # DualSene matching the feature reports of PS5ControllerBluetooth/USB + # as dumped from hid-playstation 'dualsense_get_calibration_data'. + # + # Note we duplicate those values here in case the kernel changes them + # so we can have tests passing even if hid-tools doesn't have the + # correct values. + accelerometer_calibration_data = { + "x": {"bias": 0, "numer": 16384, "denom": 16374}, + "y": {"bias": -114, "numer": 16384, "denom": 16362}, + "z": {"bias": 2, "numer": 16384, "denom": 16395}, + } + gyroscope_calibration_data = { + "x": {"bias": 0, "numer": 1105920, "denom": 17727}, + "y": {"bias": 0, "numer": 1105920, "denom": 17728}, + "z": {"bias": 0, "numer": 1105920, "denom": 17769}, + } + + +class CalibratedPS5ControllerBluetooth(CalibratedPS5Controller, PS5ControllerBluetooth): + pass + + +class TestPS5ControllerBluetooth(SonyBaseTest.SonyPS4ControllerTest): + kernel_modules = [PS5_MODULE] + + def create_device(self): + controller = CalibratedPS5ControllerBluetooth() + controller.application_matches = application_matches + return controller + + +class CalibratedPS5ControllerUSB(CalibratedPS5Controller, PS5ControllerUSB): + pass + + +class TestPS5ControllerUSB(SonyBaseTest.SonyPS4ControllerTest): + kernel_modules = [PS5_MODULE] + + def create_device(self): + controller = CalibratedPS5ControllerUSB() + controller.application_matches = application_matches + return controller -- cgit v1.2.3