Linux Input/HID development
 help / color / mirror / Atom feed
* [PATCH 02/11] selftests: hid: import hid-tools hid-core tests
From: Benjamin Tissoires @ 2023-02-17 16:17 UTC (permalink / raw)
  To: Jiri Kosina, Shuah Khan
  Cc: linux-input, linux-kselftest, linux-kernel, Benjamin Tissoires,
	Peter Hutterer
In-Reply-To: <20230217-import-hid-tools-tests-v1-0-d1c48590d0ee@redhat.com>

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.

I haven't imported all of hid-tools, the python module, but only the
tests related to the kernel. We can rely on pip to fetch the latest
hid-tools release, and then run the tests directly from the tree.

This should now be easier to request tests when something is not behaving
properly in the HID subsystem.

[0] https://gitlab.freedesktop.org/libevdev/hid-tools

Cc: Peter Hutterer <peter.hutterer@who-t.net>
Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
---
 tools/testing/selftests/hid/Makefile               |   2 +
 tools/testing/selftests/hid/hid-core.sh            |   7 +
 tools/testing/selftests/hid/run-hid-tools-tests.sh |  28 ++
 tools/testing/selftests/hid/tests/__init__.py      |   2 +
 tools/testing/selftests/hid/tests/base.py          | 345 +++++++++++++++++++++
 tools/testing/selftests/hid/tests/conftest.py      |  81 +++++
 tools/testing/selftests/hid/tests/test_hid_core.py | 154 +++++++++
 tools/testing/selftests/hid/vmtest.sh              |   2 +-
 8 files changed, 620 insertions(+), 1 deletion(-)

diff --git a/tools/testing/selftests/hid/Makefile b/tools/testing/selftests/hid/Makefile
index 83e8f87d643a..bdcb36d80c8c 100644
--- a/tools/testing/selftests/hid/Makefile
+++ b/tools/testing/selftests/hid/Makefile
@@ -5,6 +5,8 @@ include ../../../build/Build.include
 include ../../../scripts/Makefile.arch
 include ../../../scripts/Makefile.include
 
+TEST_PROGS := hid-core.sh
+
 CXX ?= $(CROSS_COMPILE)g++
 
 HOSTPKG_CONFIG := pkg-config
diff --git a/tools/testing/selftests/hid/hid-core.sh b/tools/testing/selftests/hid/hid-core.sh
new file mode 100755
index 000000000000..5bbabc12c34f
--- /dev/null
+++ b/tools/testing/selftests/hid/hid-core.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Runs tests for the HID subsystem
+
+export TARGET=test_hid_core.py
+
+bash ./run-hid-tools-tests.sh
diff --git a/tools/testing/selftests/hid/run-hid-tools-tests.sh b/tools/testing/selftests/hid/run-hid-tools-tests.sh
new file mode 100755
index 000000000000..bdae8464da86
--- /dev/null
+++ b/tools/testing/selftests/hid/run-hid-tools-tests.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Runs tests for the HID subsystem
+
+if ! command -v python3 > /dev/null 2>&1; then
+	echo "hid-tools: [SKIP] python3 not installed"
+	exit 77
+fi
+
+if ! python3 -c "import pytest" > /dev/null 2>&1; then
+	echo "hid: [SKIP/ pytest module not installed"
+	exit 77
+fi
+
+if ! python3 -c "import pytest_tap" > /dev/null 2>&1; then
+	echo "hid: [SKIP/ pytest_tap module not installed"
+	exit 77
+fi
+
+if ! python3 -c "import hidtools" > /dev/null 2>&1; then
+	echo "hid: [SKIP/ hid-tools module not installed"
+	exit 77
+fi
+
+TARGET=${TARGET:=.}
+
+echo TAP version 13
+python3 -u -m pytest $PYTEST_XDIST ./tests/$TARGET --tap-stream --udevd
diff --git a/tools/testing/selftests/hid/tests/__init__.py b/tools/testing/selftests/hid/tests/__init__.py
new file mode 100644
index 000000000000..c940e9275252
--- /dev/null
+++ b/tools/testing/selftests/hid/tests/__init__.py
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0
+# Just to make sphinx-apidoc document this directory
diff --git a/tools/testing/selftests/hid/tests/base.py b/tools/testing/selftests/hid/tests/base.py
new file mode 100644
index 000000000000..1305cfc9646e
--- /dev/null
+++ b/tools/testing/selftests/hid/tests/base.py
@@ -0,0 +1,345 @@
+#!/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2017 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+# Copyright (c) 2017 Red Hat, Inc.
+
+import libevdev
+import os
+import pytest
+import time
+
+import logging
+
+from hidtools.device.base_device import BaseDevice, EvdevMatch, SysfsFile
+from pathlib import Path
+from typing import Final
+
+logger = logging.getLogger("hidtools.test.base")
+
+# application to matches
+application_matches: Final = {
+    # pyright: ignore
+    "Accelerometer": EvdevMatch(
+        req_properties=[
+            libevdev.INPUT_PROP_ACCELEROMETER,
+        ]
+    ),
+    "Game Pad": EvdevMatch(  # in systemd, this is a lot more complex, but that will do
+        requires=[
+            libevdev.EV_ABS.ABS_X,
+            libevdev.EV_ABS.ABS_Y,
+            libevdev.EV_ABS.ABS_RX,
+            libevdev.EV_ABS.ABS_RY,
+            libevdev.EV_KEY.BTN_START,
+        ],
+        excl_properties=[
+            libevdev.INPUT_PROP_ACCELEROMETER,
+        ],
+    ),
+    "Joystick": EvdevMatch(  # in systemd, this is a lot more complex, but that will do
+        requires=[
+            libevdev.EV_ABS.ABS_RX,
+            libevdev.EV_ABS.ABS_RY,
+            libevdev.EV_KEY.BTN_START,
+        ],
+        excl_properties=[
+            libevdev.INPUT_PROP_ACCELEROMETER,
+        ],
+    ),
+    "Key": EvdevMatch(
+        requires=[
+            libevdev.EV_KEY.KEY_A,
+        ],
+        excl_properties=[
+            libevdev.INPUT_PROP_ACCELEROMETER,
+            libevdev.INPUT_PROP_DIRECT,
+            libevdev.INPUT_PROP_POINTER,
+        ],
+    ),
+    "Mouse": EvdevMatch(
+        requires=[
+            libevdev.EV_REL.REL_X,
+            libevdev.EV_REL.REL_Y,
+            libevdev.EV_KEY.BTN_LEFT,
+        ],
+        excl_properties=[
+            libevdev.INPUT_PROP_ACCELEROMETER,
+        ],
+    ),
+    "Pad": EvdevMatch(
+        requires=[
+            libevdev.EV_KEY.BTN_0,
+        ],
+        excludes=[
+            libevdev.EV_KEY.BTN_TOOL_PEN,
+            libevdev.EV_KEY.BTN_TOUCH,
+            libevdev.EV_ABS.ABS_DISTANCE,
+        ],
+        excl_properties=[
+            libevdev.INPUT_PROP_ACCELEROMETER,
+        ],
+    ),
+    "Pen": EvdevMatch(
+        requires=[
+            libevdev.EV_KEY.BTN_STYLUS,
+            libevdev.EV_ABS.ABS_X,
+            libevdev.EV_ABS.ABS_Y,
+        ],
+        excl_properties=[
+            libevdev.INPUT_PROP_ACCELEROMETER,
+        ],
+    ),
+    "Stylus": EvdevMatch(
+        requires=[
+            libevdev.EV_KEY.BTN_STYLUS,
+            libevdev.EV_ABS.ABS_X,
+            libevdev.EV_ABS.ABS_Y,
+        ],
+        excl_properties=[
+            libevdev.INPUT_PROP_ACCELEROMETER,
+        ],
+    ),
+    "Touch Pad": EvdevMatch(
+        requires=[
+            libevdev.EV_KEY.BTN_LEFT,
+            libevdev.EV_ABS.ABS_X,
+            libevdev.EV_ABS.ABS_Y,
+        ],
+        excludes=[libevdev.EV_KEY.BTN_TOOL_PEN, libevdev.EV_KEY.BTN_STYLUS],
+        req_properties=[
+            libevdev.INPUT_PROP_POINTER,
+        ],
+        excl_properties=[
+            libevdev.INPUT_PROP_ACCELEROMETER,
+        ],
+    ),
+    "Touch Screen": EvdevMatch(
+        requires=[
+            libevdev.EV_KEY.BTN_TOUCH,
+            libevdev.EV_ABS.ABS_X,
+            libevdev.EV_ABS.ABS_Y,
+        ],
+        excludes=[libevdev.EV_KEY.BTN_TOOL_PEN, libevdev.EV_KEY.BTN_STYLUS],
+        req_properties=[
+            libevdev.INPUT_PROP_DIRECT,
+        ],
+        excl_properties=[
+            libevdev.INPUT_PROP_ACCELEROMETER,
+        ],
+    ),
+}
+
+
+class UHIDTestDevice(BaseDevice):
+    def __init__(self, name, application, rdesc_str=None, rdesc=None, input_info=None):
+        super().__init__(name, application, rdesc_str, rdesc, input_info)
+        self.application_matches = application_matches
+        if name is None:
+            name = f"uhid test {self.__class__.__name__}"
+        if not name.startswith("uhid test "):
+            name = "uhid test " + self.name
+        self.name = name
+
+
+class BaseTestCase:
+    class TestUhid(object):
+        syn_event = libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT)  # type: ignore
+        key_event = libevdev.InputEvent(libevdev.EV_KEY)  # type: ignore
+        abs_event = libevdev.InputEvent(libevdev.EV_ABS)  # type: ignore
+        rel_event = libevdev.InputEvent(libevdev.EV_REL)  # type: ignore
+        msc_event = libevdev.InputEvent(libevdev.EV_MSC.MSC_SCAN)  # type: ignore
+
+        # List of kernel modules to load before starting the test
+        # if any module is not available (not compiled), the test will skip.
+        # Each element is a tuple '(kernel driver name, kernel module)',
+        # for example ("playstation", "hid-playstation")
+        kernel_modules = []
+
+        def assertInputEventsIn(self, expected_events, effective_events):
+            effective_events = effective_events.copy()
+            for ev in expected_events:
+                assert ev in effective_events
+                effective_events.remove(ev)
+            return effective_events
+
+        def assertInputEvents(self, expected_events, effective_events):
+            remaining = self.assertInputEventsIn(expected_events, effective_events)
+            assert remaining == []
+
+        @classmethod
+        def debug_reports(cls, reports, uhdev=None, events=None):
+            data = [" ".join([f"{v:02x}" for v in r]) for r in reports]
+
+            if uhdev is not None:
+                human_data = [
+                    uhdev.parsed_rdesc.format_report(r, split_lines=True)
+                    for r in reports
+                ]
+                try:
+                    human_data = [
+                        f'\n\t       {" " * h.index("/")}'.join(h.split("\n"))
+                        for h in human_data
+                    ]
+                except ValueError:
+                    # '/' not found: not a numbered report
+                    human_data = ["\n\t      ".join(h.split("\n")) for h in human_data]
+                data = [f"{d}\n\t ====> {h}" for d, h in zip(data, human_data)]
+
+            reports = data
+
+            if len(reports) == 1:
+                print("sending 1 report:")
+            else:
+                print(f"sending {len(reports)} reports:")
+            for report in reports:
+                print("\t", report)
+
+            if events is not None:
+                print("events received:", events)
+
+        def create_device(self):
+            raise Exception("please reimplement me in subclasses")
+
+        def _load_kernel_module(self, kernel_driver, kernel_module):
+            sysfs_path = Path("/sys/bus/hid/drivers")
+            if kernel_driver is not None:
+                sysfs_path /= kernel_driver
+            else:
+                # special case for when testing all available modules:
+                # we don't know beforehand the name of the module from modinfo
+                sysfs_path = Path("/sys/module") / kernel_module.replace("-", "_")
+            if not sysfs_path.exists():
+                import subprocess
+
+                ret = subprocess.run(["/usr/sbin/modprobe", kernel_module])
+                if ret.returncode != 0:
+                    pytest.skip(
+                        f"module {kernel_module} could not be loaded, skipping the test"
+                    )
+
+        @pytest.fixture()
+        def load_kernel_module(self):
+            for kernel_driver, kernel_module in self.kernel_modules:
+                self._load_kernel_module(kernel_driver, kernel_module)
+            yield
+
+        @pytest.fixture()
+        def new_uhdev(self, load_kernel_module):
+            return self.create_device()
+
+        def assertName(self, uhdev):
+            evdev = uhdev.get_evdev()
+            assert uhdev.name in evdev.name
+
+        @pytest.fixture(autouse=True)
+        def context(self, new_uhdev, request):
+            try:
+                with HIDTestUdevRule.instance():
+                    with new_uhdev as self.uhdev:
+                        skip_cond = request.node.get_closest_marker("skip_if_uhdev")
+                        if skip_cond:
+                            test, message, *rest = skip_cond.args
+
+                            if test(self.uhdev):
+                                pytest.skip(message)
+
+                        self.uhdev.create_kernel_device()
+                        now = time.time()
+                        while not self.uhdev.is_ready() and time.time() - now < 5:
+                            self.uhdev.dispatch(1)
+                        if self.uhdev.get_evdev() is None:
+                            logger.warning(
+                                f"available list of input nodes: (default application is '{self.uhdev.application}')"
+                            )
+                            logger.warning(self.uhdev.input_nodes)
+                        yield
+                        self.uhdev = None
+            except PermissionError:
+                pytest.skip("Insufficient permissions, run me as root")
+
+        @pytest.fixture(autouse=True)
+        def check_taint(self):
+            # we are abusing SysfsFile here, it's in /proc, but meh
+            taint_file = SysfsFile("/proc/sys/kernel/tainted")
+            taint = taint_file.int_value
+
+            yield
+
+            assert taint_file.int_value == taint
+
+        def test_creation(self):
+            """Make sure the device gets processed by the kernel and creates
+            the expected application input node.
+
+            If this fail, there is something wrong in the device report
+            descriptors."""
+            uhdev = self.uhdev
+            assert uhdev is not None
+            assert uhdev.get_evdev() is not None
+            self.assertName(uhdev)
+            assert len(uhdev.next_sync_events()) == 0
+            assert uhdev.get_evdev() is not None
+
+
+class HIDTestUdevRule(object):
+    _instance = None
+    """
+    A context-manager compatible class that sets up our udev rules file and
+    deletes it on context exit.
+
+    This class is tailored to our test setup: it only sets up the udev rule
+    on the **second** context and it cleans it up again on the last context
+    removed. This matches the expected pytest setup: we enter a context for
+    the session once, then once for each test (the first of which will
+    trigger the udev rule) and once the last test exited and the session
+    exited, we clean up after ourselves.
+    """
+
+    def __init__(self):
+        self.refs = 0
+        self.rulesfile = None
+
+    def __enter__(self):
+        self.refs += 1
+        if self.refs == 2 and self.rulesfile is None:
+            self.create_udev_rule()
+            self.reload_udev_rules()
+
+    def __exit__(self, exc_type, exc_value, traceback):
+        self.refs -= 1
+        if self.refs == 0 and self.rulesfile:
+            os.remove(self.rulesfile.name)
+            self.reload_udev_rules()
+
+    def reload_udev_rules(self):
+        import subprocess
+
+        subprocess.run("udevadm control --reload-rules".split())
+        subprocess.run("systemd-hwdb update".split())
+
+    def create_udev_rule(self):
+        import tempfile
+
+        os.makedirs("/run/udev/rules.d", exist_ok=True)
+        with tempfile.NamedTemporaryFile(
+            prefix="91-uhid-test-device-REMOVEME-",
+            suffix=".rules",
+            mode="w+",
+            dir="/run/udev/rules.d",
+            delete=False,
+        ) as f:
+            f.write(
+                'KERNELS=="*input*", ATTRS{name}=="*uhid test *", ENV{LIBINPUT_IGNORE_DEVICE}="1"\n'
+            )
+            f.write(
+                'KERNELS=="*input*", ATTRS{name}=="*uhid test * System Multi Axis", ENV{ID_INPUT_TOUCHSCREEN}="", ENV{ID_INPUT_SYSTEM_MULTIAXIS}="1"\n'
+            )
+            self.rulesfile = f
+
+    @classmethod
+    def instance(cls):
+        if not cls._instance:
+            cls._instance = HIDTestUdevRule()
+        return cls._instance
diff --git a/tools/testing/selftests/hid/tests/conftest.py b/tools/testing/selftests/hid/tests/conftest.py
new file mode 100644
index 000000000000..1361ec981db6
--- /dev/null
+++ b/tools/testing/selftests/hid/tests/conftest.py
@@ -0,0 +1,81 @@
+#!/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2017 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+# Copyright (c) 2017 Red Hat, Inc.
+
+import platform
+import pytest
+import re
+import resource
+import subprocess
+from .base import HIDTestUdevRule
+from pathlib import Path
+
+
+# See the comment in HIDTestUdevRule, this doesn't set up but it will clean
+# up once the last test exited.
+@pytest.fixture(autouse=True, scope="session")
+def udev_rules_session_setup():
+    with HIDTestUdevRule.instance():
+        yield
+
+
+@pytest.fixture(autouse=True, scope="session")
+def setup_rlimit():
+    resource.setrlimit(resource.RLIMIT_CORE, (0, 0))
+
+
+@pytest.fixture(autouse=True, scope="session")
+def start_udevd(pytestconfig):
+    if pytestconfig.getoption("udevd"):
+        import subprocess
+
+        with subprocess.Popen("/usr/lib/systemd/systemd-udevd") as proc:
+            yield
+            proc.kill()
+    else:
+        yield
+
+
+def pytest_configure(config):
+    config.addinivalue_line(
+        "markers",
+        "skip_if_uhdev(condition, message): mark test to skip if the condition on the uhdev device is met",
+    )
+
+
+# Generate the list of modules and modaliases
+# for the tests that need to be parametrized with those
+def pytest_generate_tests(metafunc):
+    if "usbVidPid" in metafunc.fixturenames:
+        modules = (
+            Path("/lib/modules/")
+            / platform.uname().release
+            / "kernel"
+            / "drivers"
+            / "hid"
+        )
+
+        modalias_re = re.compile(r"alias:\s+hid:b0003g.*v([0-9a-fA-F]+)p([0-9a-fA-F]+)")
+
+        params = []
+        ids = []
+        for module in modules.glob("*.ko"):
+            p = subprocess.run(
+                ["modinfo", module], capture_output=True, check=True, encoding="utf-8"
+            )
+            for line in p.stdout.split("\n"):
+                m = modalias_re.match(line)
+                if m is not None:
+                    vid, pid = m.groups()
+                    vid = int(vid, 16)
+                    pid = int(pid, 16)
+                    params.append([module.name.replace(".ko", ""), vid, pid])
+                    ids.append(f"{module.name} {vid:04x}:{pid:04x}")
+        metafunc.parametrize("usbVidPid", params, ids=ids)
+
+
+def pytest_addoption(parser):
+    parser.addoption("--udevd", action="store_true", default=False)
diff --git a/tools/testing/selftests/hid/tests/test_hid_core.py b/tools/testing/selftests/hid/tests/test_hid_core.py
new file mode 100644
index 000000000000..9a7fe40020d2
--- /dev/null
+++ b/tools/testing/selftests/hid/tests/test_hid_core.py
@@ -0,0 +1,154 @@
+#!/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2017 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+# Copyright (c) 2017 Red Hat, Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+# This is for generic devices
+
+from . import base
+import logging
+
+logger = logging.getLogger("hidtools.test.hid")
+
+
+class TestCollectionOverflow(base.BaseTestCase.TestUhid):
+    """
+    Test class to test re-allocation of the HID collection stack in
+    hid-core.c.
+    """
+
+    def create_device(self):
+        # fmt: off
+        report_descriptor = [
+            0x05, 0x01,         # .Usage Page (Generic Desktop)
+            0x09, 0x02,         # .Usage (Mouse)
+            0xa1, 0x01,         # .Collection (Application)
+            0x09, 0x02,         # ..Usage (Mouse)
+            0xa1, 0x02,         # ..Collection (Logical)
+            0x09, 0x01,         # ...Usage (Pointer)
+            0xa1, 0x00,         # ...Collection (Physical)
+            0x05, 0x09,         # ....Usage Page (Button)
+            0x19, 0x01,         # ....Usage Minimum (1)
+            0x29, 0x03,         # ....Usage Maximum (3)
+            0x15, 0x00,         # ....Logical Minimum (0)
+            0x25, 0x01,         # ....Logical Maximum (1)
+            0x75, 0x01,         # ....Report Size (1)
+            0x95, 0x03,         # ....Report Count (3)
+            0x81, 0x02,         # ....Input (Data,Var,Abs)
+            0x75, 0x05,         # ....Report Size (5)
+            0x95, 0x01,         # ....Report Count (1)
+            0x81, 0x03,         # ....Input (Cnst,Var,Abs)
+            0xa1, 0x02,         # ....Collection (Logical)
+            0x09, 0x01,         # .....Usage (Pointer)
+            0xa1, 0x02,         # ....Collection (Logical)
+            0x09, 0x01,         # .....Usage (Pointer)
+            0xa1, 0x02,         # ....Collection (Logical)
+            0x09, 0x01,         # .....Usage (Pointer)
+            0xa1, 0x02,         # ....Collection (Logical)
+            0x09, 0x01,         # .....Usage (Pointer)
+            0xa1, 0x02,         # ....Collection (Logical)
+            0x09, 0x01,         # .....Usage (Pointer)
+            0xa1, 0x02,         # ....Collection (Logical)
+            0x09, 0x01,         # .....Usage (Pointer)
+            0xa1, 0x02,         # ....Collection (Logical)
+            0x09, 0x01,         # .....Usage (Pointer)
+            0xa1, 0x02,         # ....Collection (Logical)
+            0x09, 0x01,         # .....Usage (Pointer)
+            0xa1, 0x02,         # ....Collection (Logical)
+            0x09, 0x01,         # .....Usage (Pointer)
+            0xa1, 0x02,         # ....Collection (Logical)
+            0x09, 0x01,         # .....Usage (Pointer)
+            0xa1, 0x02,         # ....Collection (Logical)
+            0x09, 0x01,         # .....Usage (Pointer)
+            0xa1, 0x02,         # ....Collection (Logical)
+            0x09, 0x01,         # .....Usage (Pointer)
+            0xa1, 0x02,         # ....Collection (Logical)
+            0x09, 0x01,         # .....Usage (Pointer)
+            0xa1, 0x02,         # ....Collection (Logical)
+            0x09, 0x01,         # .....Usage (Pointer)
+            0xa1, 0x02,         # ....Collection (Logical)
+            0x09, 0x01,         # .....Usage (Pointer)
+            0xa1, 0x02,         # ....Collection (Logical)
+            0x09, 0x01,         # .....Usage (Pointer)
+            0xa1, 0x02,         # ....Collection (Logical)
+            0x09, 0x01,         # .....Usage (Pointer)
+            0x05, 0x01,         # .....Usage Page (Generic Desktop)
+            0x09, 0x30,         # .....Usage (X)
+            0x09, 0x31,         # .....Usage (Y)
+            0x15, 0x81,         # .....Logical Minimum (-127)
+            0x25, 0x7f,         # .....Logical Maximum (127)
+            0x75, 0x08,         # .....Report Size (8)
+            0x95, 0x02,         # .....Report Count (2)
+            0x81, 0x06,         # .....Input (Data,Var,Rel)
+            0xa1, 0x02,         # ...Collection (Logical)
+            0x85, 0x12,         # ....Report ID (18)
+            0x09, 0x48,         # ....Usage (Resolution Multiplier)
+            0x95, 0x01,         # ....Report Count (1)
+            0x75, 0x02,         # ....Report Size (2)
+            0x15, 0x00,         # ....Logical Minimum (0)
+            0x25, 0x01,         # ....Logical Maximum (1)
+            0x35, 0x01,         # ....Physical Minimum (1)
+            0x45, 0x0c,         # ....Physical Maximum (12)
+            0xb1, 0x02,         # ....Feature (Data,Var,Abs)
+            0x85, 0x1a,         # ....Report ID (26)
+            0x09, 0x38,         # ....Usage (Wheel)
+            0x35, 0x00,         # ....Physical Minimum (0)
+            0x45, 0x00,         # ....Physical Maximum (0)
+            0x95, 0x01,         # ....Report Count (1)
+            0x75, 0x10,         # ....Report Size (16)
+            0x16, 0x01, 0x80,   # ....Logical Minimum (-32767)
+            0x26, 0xff, 0x7f,   # ....Logical Maximum (32767)
+            0x81, 0x06,         # ....Input (Data,Var,Rel)
+            0xc0,               # ...End Collection
+            0xc0,               # ...End Collection
+            0xc0,               # ...End Collection
+            0xc0,               # ...End Collection
+            0xc0,               # ...End Collection
+            0xc0,               # ...End Collection
+            0xc0,               # ...End Collection
+            0xc0,               # ...End Collection
+            0xc0,               # ...End Collection
+            0xc0,               # ...End Collection
+            0xc0,               # ...End Collection
+            0xc0,               # ...End Collection
+            0xc0,               # ...End Collection
+            0xc0,               # ...End Collection
+            0xc0,               # ...End Collection
+            0xc0,               # ...End Collection
+            0xc0,               # ...End Collection
+            0xc0,               # ...End Collection
+            0xc0,               # ...End Collection
+            0xc0,               # ..End Collection
+            0xc0,               # .End Collection
+        ]
+        # fmt: on
+        return base.UHIDTestDevice(
+            name=None, rdesc=report_descriptor, application="Mouse"
+        )
+
+    def test_rdesc(self):
+        """
+        This test can only check for negatives. If the kernel crashes, you
+        know why. If this test passes, either the bug isn't present or just
+        didn't get triggered. No way to know.
+
+        For an explanation, see kernel patch
+            HID: core: replace the collection tree pointers with indices
+        """
+        pass
diff --git a/tools/testing/selftests/hid/vmtest.sh b/tools/testing/selftests/hid/vmtest.sh
index 6346b0620dba..681b906b4853 100755
--- a/tools/testing/selftests/hid/vmtest.sh
+++ b/tools/testing/selftests/hid/vmtest.sh
@@ -27,7 +27,7 @@ EXIT_STATUS_FILE="${LOG_FILE_BASE}.exit_status"
 CONTAINER_IMAGE="registry.freedesktop.org/libevdev/hid-tools/fedora/37:2023-02-17.1"
 
 TARGETS="${TARGETS:=$(basename ${SCRIPT_DIR})}"
-DEFAULT_COMMAND="make -C tools/testing/selftests TARGETS=${TARGETS} run_tests"
+DEFAULT_COMMAND="pip3 install hid-tools; make -C tools/testing/selftests TARGETS=${TARGETS} run_tests"
 
 usage()
 {

-- 
2.39.1


^ permalink raw reply related

* [PATCH 04/11] selftests: hid: import hid-tools hid-keyboards tests
From: Benjamin Tissoires @ 2023-02-17 16:17 UTC (permalink / raw)
  To: Jiri Kosina, Shuah Khan
  Cc: linux-input, linux-kselftest, linux-kernel, Benjamin Tissoires,
	Peter Hutterer, Roderick Colenbrander, Nicolas Saenz Julienne
In-Reply-To: <20230217-import-hid-tools-tests-v1-0-d1c48590d0ee@redhat.com>

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.

[0] https://gitlab.freedesktop.org/libevdev/hid-tools

Cc: Nicolas Saenz Julienne <nsaenzjulienne@suse.de>
Cc: Peter Hutterer <peter.hutterer@who-t.net>
Cc: Roderick Colenbrander <roderick.colenbrander@sony.com>
Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
---
 tools/testing/selftests/hid/Makefile               |   1 +
 tools/testing/selftests/hid/hid-keyboard.sh        |   7 +
 tools/testing/selftests/hid/tests/test_keyboard.py | 485 +++++++++++++++++++++
 3 files changed, 493 insertions(+)

diff --git a/tools/testing/selftests/hid/Makefile b/tools/testing/selftests/hid/Makefile
index d16a22477140..181a594ffe92 100644
--- a/tools/testing/selftests/hid/Makefile
+++ b/tools/testing/selftests/hid/Makefile
@@ -7,6 +7,7 @@ include ../../../scripts/Makefile.include
 
 TEST_PROGS := hid-core.sh
 TEST_PROGS += hid-gamepad.sh
+TEST_PROGS += hid-keyboard.sh
 
 CXX ?= $(CROSS_COMPILE)g++
 
diff --git a/tools/testing/selftests/hid/hid-keyboard.sh b/tools/testing/selftests/hid/hid-keyboard.sh
new file mode 100755
index 000000000000..55368f17d1d5
--- /dev/null
+++ b/tools/testing/selftests/hid/hid-keyboard.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Runs tests for the HID subsystem
+
+export TARGET=test_keyboard.py
+
+bash ./run-hid-tools-tests.sh
diff --git a/tools/testing/selftests/hid/tests/test_keyboard.py b/tools/testing/selftests/hid/tests/test_keyboard.py
new file mode 100644
index 000000000000..b3b2bdbf63b7
--- /dev/null
+++ b/tools/testing/selftests/hid/tests/test_keyboard.py
@@ -0,0 +1,485 @@
+#!/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2018 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+# Copyright (c) 2018 Red Hat, Inc.
+#
+
+from . import base
+import hidtools.hid
+import libevdev
+import logging
+
+logger = logging.getLogger("hidtools.test.keyboard")
+
+
+class InvalidHIDCommunication(Exception):
+    pass
+
+
+class KeyboardData(object):
+    pass
+
+
+class BaseKeyboard(base.UHIDTestDevice):
+    def __init__(self, rdesc, name=None, input_info=None):
+        assert rdesc is not None
+        super().__init__(name, "Key", input_info=input_info, rdesc=rdesc)
+        self.keystates = {}
+
+    def _update_key_state(self, keys):
+        """
+        Update the internal state of keys with the new state given.
+
+        :param key: a tuple of chars for the currently pressed keys.
+        """
+        # First remove the already released keys
+        unused_keys = [k for k, v in self.keystates.items() if not v]
+        for key in unused_keys:
+            del self.keystates[key]
+
+        # self.keystates contains now the list of currently pressed keys,
+        # release them...
+        for key in self.keystates.keys():
+            self.keystates[key] = False
+
+        # ...and press those that are in parameter
+        for key in keys:
+            self.keystates[key] = True
+
+    def _create_report_data(self):
+        keyboard = KeyboardData()
+        for key, value in self.keystates.items():
+            key = key.replace(" ", "").lower()
+            setattr(keyboard, key, value)
+        return keyboard
+
+    def create_array_report(self, keys, reportID=None, application=None):
+        """
+        Return an input report for this device.
+
+        :param keys: a tuple of chars for the pressed keys. The class maintains
+            the list of currently pressed keys, so to release a key, the caller
+            needs to call again this function without the key in this tuple.
+        :param reportID: the numeric report ID for this report, if needed
+        """
+        self._update_key_state(keys)
+        reportID = reportID or self.default_reportID
+
+        keyboard = self._create_report_data()
+        return self.create_report(keyboard, reportID=reportID, application=application)
+
+    def event(self, keys, reportID=None, application=None):
+        """
+        Send an input event on the default report ID.
+
+        :param keys: a tuple of chars for the pressed keys. The class maintains
+            the list of currently pressed keys, so to release a key, the caller
+            needs to call again this function without the key in this tuple.
+        """
+        r = self.create_array_report(keys, reportID, application)
+        self.call_input_event(r)
+        return [r]
+
+
+class PlainKeyboard(BaseKeyboard):
+    # fmt: off
+    report_descriptor = [
+        0x05, 0x01,                    # Usage Page (Generic Desktop)
+        0x09, 0x06,                    # Usage (Keyboard)
+        0xa1, 0x01,                    # Collection (Application)
+        0x85, 0x01,                    # .Report ID (1)
+        0x05, 0x07,                    # .Usage Page (Keyboard)
+        0x19, 0xe0,                    # .Usage Minimum (224)
+        0x29, 0xe7,                    # .Usage Maximum (231)
+        0x15, 0x00,                    # .Logical Minimum (0)
+        0x25, 0x01,                    # .Logical Maximum (1)
+        0x75, 0x01,                    # .Report Size (1)
+        0x95, 0x08,                    # .Report Count (8)
+        0x81, 0x02,                    # .Input (Data,Var,Abs)
+        0x19, 0x00,                    # .Usage Minimum (0)
+        0x29, 0x97,                    # .Usage Maximum (151)
+        0x15, 0x00,                    # .Logical Minimum (0)
+        0x25, 0x01,                    # .Logical Maximum (1)
+        0x75, 0x01,                    # .Report Size (1)
+        0x95, 0x98,                    # .Report Count (152)
+        0x81, 0x02,                    # .Input (Data,Var,Abs)
+        0xc0,                          # End Collection
+    ]
+    # fmt: on
+
+    def __init__(self, rdesc=report_descriptor, name=None, input_info=None):
+        super().__init__(rdesc, name, input_info)
+        self.default_reportID = 1
+
+
+class ArrayKeyboard(BaseKeyboard):
+    # fmt: off
+    report_descriptor = [
+        0x05, 0x01,                    # Usage Page (Generic Desktop)
+        0x09, 0x06,                    # Usage (Keyboard)
+        0xa1, 0x01,                    # Collection (Application)
+        0x05, 0x07,                    # .Usage Page (Keyboard)
+        0x19, 0xe0,                    # .Usage Minimum (224)
+        0x29, 0xe7,                    # .Usage Maximum (231)
+        0x15, 0x00,                    # .Logical Minimum (0)
+        0x25, 0x01,                    # .Logical Maximum (1)
+        0x75, 0x01,                    # .Report Size (1)
+        0x95, 0x08,                    # .Report Count (8)
+        0x81, 0x02,                    # .Input (Data,Var,Abs)
+        0x95, 0x06,                    # .Report Count (6)
+        0x75, 0x08,                    # .Report Size (8)
+        0x15, 0x00,                    # .Logical Minimum (0)
+        0x26, 0xa4, 0x00,              # .Logical Maximum (164)
+        0x05, 0x07,                    # .Usage Page (Keyboard)
+        0x19, 0x00,                    # .Usage Minimum (0)
+        0x29, 0xa4,                    # .Usage Maximum (164)
+        0x81, 0x00,                    # .Input (Data,Arr,Abs)
+        0xc0,                          # End Collection
+    ]
+    # fmt: on
+
+    def __init__(self, rdesc=report_descriptor, name=None, input_info=None):
+        super().__init__(rdesc, name, input_info)
+
+    def _create_report_data(self):
+        data = KeyboardData()
+        array = []
+
+        hut = hidtools.hut.HUT
+
+        # strip modifiers from the array
+        for k, v in self.keystates.items():
+            # we ignore depressed keys
+            if not v:
+                continue
+
+            usage = hut[0x07].from_name[k].usage
+            if usage >= 224 and usage <= 231:
+                # modifier
+                setattr(data, k.lower(), 1)
+            else:
+                array.append(k)
+
+        # if array length is bigger than 6, report ErrorRollOver
+        if len(array) > 6:
+            array = ["ErrorRollOver"] * 6
+
+        data.keyboard = array
+        return data
+
+
+class LEDKeyboard(ArrayKeyboard):
+    # fmt: off
+    report_descriptor = [
+        0x05, 0x01,                    # Usage Page (Generic Desktop)
+        0x09, 0x06,                    # Usage (Keyboard)
+        0xa1, 0x01,                    # Collection (Application)
+        0x05, 0x07,                    # .Usage Page (Keyboard)
+        0x19, 0xe0,                    # .Usage Minimum (224)
+        0x29, 0xe7,                    # .Usage Maximum (231)
+        0x15, 0x00,                    # .Logical Minimum (0)
+        0x25, 0x01,                    # .Logical Maximum (1)
+        0x75, 0x01,                    # .Report Size (1)
+        0x95, 0x08,                    # .Report Count (8)
+        0x81, 0x02,                    # .Input (Data,Var,Abs)
+        0x95, 0x01,                    # .Report Count (1)
+        0x75, 0x08,                    # .Report Size (8)
+        0x81, 0x01,                    # .Input (Cnst,Arr,Abs)
+        0x95, 0x05,                    # .Report Count (5)
+        0x75, 0x01,                    # .Report Size (1)
+        0x05, 0x08,                    # .Usage Page (LEDs)
+        0x19, 0x01,                    # .Usage Minimum (1)
+        0x29, 0x05,                    # .Usage Maximum (5)
+        0x91, 0x02,                    # .Output (Data,Var,Abs)
+        0x95, 0x01,                    # .Report Count (1)
+        0x75, 0x03,                    # .Report Size (3)
+        0x91, 0x01,                    # .Output (Cnst,Arr,Abs)
+        0x95, 0x06,                    # .Report Count (6)
+        0x75, 0x08,                    # .Report Size (8)
+        0x15, 0x00,                    # .Logical Minimum (0)
+        0x26, 0xa4, 0x00,              # .Logical Maximum (164)
+        0x05, 0x07,                    # .Usage Page (Keyboard)
+        0x19, 0x00,                    # .Usage Minimum (0)
+        0x29, 0xa4,                    # .Usage Maximum (164)
+        0x81, 0x00,                    # .Input (Data,Arr,Abs)
+        0xc0,                          # End Collection
+    ]
+    # fmt: on
+
+    def __init__(self, rdesc=report_descriptor, name=None, input_info=None):
+        super().__init__(rdesc, name, input_info)
+
+
+# Some Primax manufactured keyboards set the Usage Page after having defined
+# some local Usages. It relies on the fact that the specification states that
+# Usages are to be concatenated with Usage Pages upon finding a Main item (see
+# 6.2.2.8). This test covers this case.
+class PrimaxKeyboard(ArrayKeyboard):
+    # fmt: off
+    report_descriptor = [
+        0x05, 0x01,                    # Usage Page (Generic Desktop)
+        0x09, 0x06,                    # Usage (Keyboard)
+        0xA1, 0x01,                    # Collection (Application)
+        0x05, 0x07,                    # .Usage Page (Keyboard)
+        0x19, 0xE0,                    # .Usage Minimum (224)
+        0x29, 0xE7,                    # .Usage Maximum (231)
+        0x15, 0x00,                    # .Logical Minimum (0)
+        0x25, 0x01,                    # .Logical Maximum (1)
+        0x75, 0x01,                    # .Report Size (1)
+        0x95, 0x08,                    # .Report Count (8)
+        0x81, 0x02,                    # .Input (Data,Var,Abs)
+        0x75, 0x08,                    # .Report Size (8)
+        0x95, 0x01,                    # .Report Count (1)
+        0x81, 0x01,                    # .Input (Data,Var,Abs)
+        0x05, 0x08,                    # .Usage Page (LEDs)
+        0x19, 0x01,                    # .Usage Minimum (1)
+        0x29, 0x03,                    # .Usage Maximum (3)
+        0x75, 0x01,                    # .Report Size (1)
+        0x95, 0x03,                    # .Report Count (3)
+        0x91, 0x02,                    # .Output (Data,Var,Abs)
+        0x95, 0x01,                    # .Report Count (1)
+        0x75, 0x05,                    # .Report Size (5)
+        0x91, 0x01,                    # .Output (Constant)
+        0x15, 0x00,                    # .Logical Minimum (0)
+        0x26, 0xFF, 0x00,              # .Logical Maximum (255)
+        0x19, 0x00,                    # .Usage Minimum (0)
+        0x2A, 0xFF, 0x00,              # .Usage Maximum (255)
+        0x05, 0x07,                    # .Usage Page (Keyboard)
+        0x75, 0x08,                    # .Report Size (8)
+        0x95, 0x06,                    # .Report Count (6)
+        0x81, 0x00,                    # .Input (Data,Arr,Abs)
+        0xC0,                          # End Collection
+    ]
+    # fmt: on
+
+    def __init__(self, rdesc=report_descriptor, name=None, input_info=None):
+        super().__init__(rdesc, name, input_info)
+
+
+class BaseTest:
+    class TestKeyboard(base.BaseTestCase.TestUhid):
+        def test_single_key(self):
+            """check for key reliability."""
+            uhdev = self.uhdev
+            evdev = uhdev.get_evdev()
+            syn_event = self.syn_event
+
+            r = uhdev.event(["a and A"])
+            expected = [syn_event]
+            expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_A, 1))
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEventsIn(expected, events)
+            assert evdev.value[libevdev.EV_KEY.KEY_A] == 1
+
+            r = uhdev.event([])
+            expected = [syn_event]
+            expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_A, 0))
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEventsIn(expected, events)
+            assert evdev.value[libevdev.EV_KEY.KEY_A] == 0
+
+        def test_two_keys(self):
+            uhdev = self.uhdev
+            evdev = uhdev.get_evdev()
+            syn_event = self.syn_event
+
+            r = uhdev.event(["a and A", "q and Q"])
+            expected = [syn_event]
+            expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_A, 1))
+            expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_Q, 1))
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEventsIn(expected, events)
+            assert evdev.value[libevdev.EV_KEY.KEY_A] == 1
+
+            r = uhdev.event([])
+            expected = [syn_event]
+            expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_A, 0))
+            expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_Q, 0))
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEventsIn(expected, events)
+            assert evdev.value[libevdev.EV_KEY.KEY_A] == 0
+            assert evdev.value[libevdev.EV_KEY.KEY_Q] == 0
+
+            r = uhdev.event(["c and C"])
+            expected = [syn_event]
+            expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_C, 1))
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEventsIn(expected, events)
+            assert evdev.value[libevdev.EV_KEY.KEY_C] == 1
+
+            r = uhdev.event(["c and C", "Spacebar"])
+            expected = [syn_event]
+            expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_SPACE, 1))
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            assert libevdev.InputEvent(libevdev.EV_KEY.KEY_C) not in events
+            self.assertInputEventsIn(expected, events)
+            assert evdev.value[libevdev.EV_KEY.KEY_C] == 1
+            assert evdev.value[libevdev.EV_KEY.KEY_SPACE] == 1
+
+            r = uhdev.event(["Spacebar"])
+            expected = [syn_event]
+            expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_C, 0))
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            assert libevdev.InputEvent(libevdev.EV_KEY.KEY_SPACE) not in events
+            self.assertInputEventsIn(expected, events)
+            assert evdev.value[libevdev.EV_KEY.KEY_C] == 0
+            assert evdev.value[libevdev.EV_KEY.KEY_SPACE] == 1
+
+            r = uhdev.event([])
+            expected = [syn_event]
+            expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_SPACE, 0))
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEventsIn(expected, events)
+            assert evdev.value[libevdev.EV_KEY.KEY_SPACE] == 0
+
+        def test_modifiers(self):
+            # ctrl-alt-del would be very nice :)
+            uhdev = self.uhdev
+            syn_event = self.syn_event
+
+            r = uhdev.event(["LeftControl", "LeftShift", "= and +"])
+            expected = [syn_event]
+            expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_LEFTCTRL, 1))
+            expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_LEFTSHIFT, 1))
+            expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_EQUAL, 1))
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEventsIn(expected, events)
+
+
+class TestPlainKeyboard(BaseTest.TestKeyboard):
+    def create_device(self):
+        return PlainKeyboard()
+
+    def test_10_keys(self):
+        uhdev = self.uhdev
+        syn_event = self.syn_event
+
+        r = uhdev.event(
+            [
+                "1 and !",
+                "2 and @",
+                "3 and #",
+                "4 and $",
+                "5 and %",
+                "6 and ^",
+                "7 and &",
+                "8 and *",
+                "9 and (",
+                "0 and )",
+            ]
+        )
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_0, 1))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_1, 1))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_2, 1))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_3, 1))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_4, 1))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_5, 1))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_6, 1))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_7, 1))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_8, 1))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_9, 1))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEventsIn(expected, events)
+
+        r = uhdev.event([])
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_0, 0))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_1, 0))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_2, 0))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_3, 0))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_4, 0))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_5, 0))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_6, 0))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_7, 0))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_8, 0))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_9, 0))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEventsIn(expected, events)
+
+
+class TestArrayKeyboard(BaseTest.TestKeyboard):
+    def create_device(self):
+        return ArrayKeyboard()
+
+    def test_10_keys(self):
+        uhdev = self.uhdev
+        syn_event = self.syn_event
+
+        r = uhdev.event(
+            [
+                "1 and !",
+                "2 and @",
+                "3 and #",
+                "4 and $",
+                "5 and %",
+                "6 and ^",
+            ]
+        )
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_1, 1))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_2, 1))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_3, 1))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_4, 1))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_5, 1))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_6, 1))
+        events = uhdev.next_sync_events()
+
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEventsIn(expected, events)
+
+        # ErrRollOver
+        r = uhdev.event(
+            [
+                "1 and !",
+                "2 and @",
+                "3 and #",
+                "4 and $",
+                "5 and %",
+                "6 and ^",
+                "7 and &",
+                "8 and *",
+                "9 and (",
+                "0 and )",
+            ]
+        )
+        events = uhdev.next_sync_events()
+
+        self.debug_reports(r, uhdev, events)
+
+        assert len(events) == 0
+
+        r = uhdev.event([])
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_1, 0))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_2, 0))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_3, 0))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_4, 0))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_5, 0))
+        expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_6, 0))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEventsIn(expected, events)
+
+
+class TestLEDKeyboard(BaseTest.TestKeyboard):
+    def create_device(self):
+        return LEDKeyboard()
+
+
+class TestPrimaxKeyboard(BaseTest.TestKeyboard):
+    def create_device(self):
+        return PrimaxKeyboard()

-- 
2.39.1


^ permalink raw reply related

* [PATCH 05/11] selftests: hid: import hid-tools hid-mouse tests
From: Benjamin Tissoires @ 2023-02-17 16:17 UTC (permalink / raw)
  To: Jiri Kosina, Shuah Khan
  Cc: linux-input, linux-kselftest, linux-kernel, Benjamin Tissoires,
	Peter Hutterer, Roderick Colenbrander
In-Reply-To: <20230217-import-hid-tools-tests-v1-0-d1c48590d0ee@redhat.com>

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.

[0] https://gitlab.freedesktop.org/libevdev/hid-tools

Cc: Peter Hutterer <peter.hutterer@who-t.net>
Cc: Roderick Colenbrander <roderick.colenbrander@sony.com>
Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
---
 tools/testing/selftests/hid/Makefile            |   1 +
 tools/testing/selftests/hid/hid-mouse.sh        |   7 +
 tools/testing/selftests/hid/tests/test_mouse.py | 977 ++++++++++++++++++++++++
 3 files changed, 985 insertions(+)

diff --git a/tools/testing/selftests/hid/Makefile b/tools/testing/selftests/hid/Makefile
index 181a594ffe92..00b2cc25e24c 100644
--- a/tools/testing/selftests/hid/Makefile
+++ b/tools/testing/selftests/hid/Makefile
@@ -8,6 +8,7 @@ include ../../../scripts/Makefile.include
 TEST_PROGS := hid-core.sh
 TEST_PROGS += hid-gamepad.sh
 TEST_PROGS += hid-keyboard.sh
+TEST_PROGS += hid-mouse.sh
 
 CXX ?= $(CROSS_COMPILE)g++
 
diff --git a/tools/testing/selftests/hid/hid-mouse.sh b/tools/testing/selftests/hid/hid-mouse.sh
new file mode 100755
index 000000000000..7b4ad4f646f7
--- /dev/null
+++ b/tools/testing/selftests/hid/hid-mouse.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Runs tests for the HID subsystem
+
+export TARGET=test_mouse.py
+
+bash ./run-hid-tools-tests.sh
diff --git a/tools/testing/selftests/hid/tests/test_mouse.py b/tools/testing/selftests/hid/tests/test_mouse.py
new file mode 100644
index 000000000000..fd2ba62e783a
--- /dev/null
+++ b/tools/testing/selftests/hid/tests/test_mouse.py
@@ -0,0 +1,977 @@
+#!/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2017 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+# Copyright (c) 2017 Red Hat, Inc.
+#
+
+from . import base
+import hidtools.hid
+from hidtools.util import BusType
+import libevdev
+import logging
+import pytest
+
+logger = logging.getLogger("hidtools.test.mouse")
+
+# workaround https://gitlab.freedesktop.org/libevdev/python-libevdev/issues/6
+try:
+    libevdev.EV_REL.REL_WHEEL_HI_RES
+except AttributeError:
+    libevdev.EV_REL.REL_WHEEL_HI_RES = libevdev.EV_REL.REL_0B
+    libevdev.EV_REL.REL_HWHEEL_HI_RES = libevdev.EV_REL.REL_0C
+
+
+class InvalidHIDCommunication(Exception):
+    pass
+
+
+class MouseData(object):
+    pass
+
+
+class BaseMouse(base.UHIDTestDevice):
+    def __init__(self, rdesc, name=None, input_info=None):
+        assert rdesc is not None
+        super().__init__(name, "Mouse", input_info=input_info, rdesc=rdesc)
+        self.left = False
+        self.right = False
+        self.middle = False
+
+    def create_report(self, x, y, buttons=None, wheels=None, reportID=None):
+        """
+        Return an input report for this device.
+
+        :param x: relative x
+        :param y: relative y
+        :param buttons: a (l, r, m) tuple of bools for the button states,
+            where ``None`` is "leave unchanged"
+        :param wheels: a single value for the vertical wheel or a (vertical, horizontal) tuple for
+            the two wheels
+        :param reportID: the numeric report ID for this report, if needed
+        """
+        if buttons is not None:
+            l, r, m = buttons
+            if l is not None:
+                self.left = l
+            if r is not None:
+                self.right = r
+            if m is not None:
+                self.middle = m
+        left = self.left
+        right = self.right
+        middle = self.middle
+        # Note: the BaseMouse doesn't actually have a wheel but the
+        # create_report magic only fills in those fields exist, so let's
+        # make this generic here.
+        wheel, acpan = 0, 0
+        if wheels is not None:
+            if isinstance(wheels, tuple):
+                wheel = wheels[0]
+                acpan = wheels[1]
+            else:
+                wheel = wheels
+
+        reportID = reportID or self.default_reportID
+
+        mouse = MouseData()
+        mouse.b1 = int(left)
+        mouse.b2 = int(right)
+        mouse.b3 = int(middle)
+        mouse.x = x
+        mouse.y = y
+        mouse.wheel = wheel
+        mouse.acpan = acpan
+        return super().create_report(mouse, reportID=reportID)
+
+    def event(self, x, y, buttons=None, wheels=None):
+        """
+        Send an input event on the default report ID.
+
+        :param x: relative x
+        :param y: relative y
+        :param buttons: a (l, r, m) tuple of bools for the button states,
+            where ``None`` is "leave unchanged"
+        :param wheels: a single value for the vertical wheel or a (vertical, horizontal) tuple for
+            the two wheels
+        """
+        r = self.create_report(x, y, buttons, wheels)
+        self.call_input_event(r)
+        return [r]
+
+
+class ButtonMouse(BaseMouse):
+    # fmt: off
+    report_descriptor = [
+        0x05, 0x01,  # .Usage Page (Generic Desktop)        0
+        0x09, 0x02,  # .Usage (Mouse)                       2
+        0xa1, 0x01,  # .Collection (Application)            4
+        0x09, 0x02,  # ..Usage (Mouse)                      6
+        0xa1, 0x02,  # ..Collection (Logical)               8
+        0x09, 0x01,  # ...Usage (Pointer)                   10
+        0xa1, 0x00,  # ...Collection (Physical)             12
+        0x05, 0x09,  # ....Usage Page (Button)              14
+        0x19, 0x01,  # ....Usage Minimum (1)                16
+        0x29, 0x03,  # ....Usage Maximum (3)                18
+        0x15, 0x00,  # ....Logical Minimum (0)              20
+        0x25, 0x01,  # ....Logical Maximum (1)              22
+        0x75, 0x01,  # ....Report Size (1)                  24
+        0x95, 0x03,  # ....Report Count (3)                 26
+        0x81, 0x02,  # ....Input (Data,Var,Abs)             28
+        0x75, 0x05,  # ....Report Size (5)                  30
+        0x95, 0x01,  # ....Report Count (1)                 32
+        0x81, 0x03,  # ....Input (Cnst,Var,Abs)             34
+        0x05, 0x01,  # ....Usage Page (Generic Desktop)     36
+        0x09, 0x30,  # ....Usage (X)                        38
+        0x09, 0x31,  # ....Usage (Y)                        40
+        0x15, 0x81,  # ....Logical Minimum (-127)           42
+        0x25, 0x7f,  # ....Logical Maximum (127)            44
+        0x75, 0x08,  # ....Report Size (8)                  46
+        0x95, 0x02,  # ....Report Count (2)                 48
+        0x81, 0x06,  # ....Input (Data,Var,Rel)             50
+        0xc0,        # ...End Collection                    52
+        0xc0,        # ..End Collection                     53
+        0xc0,        # .End Collection                      54
+    ]
+    # fmt: on
+
+    def __init__(self, rdesc=report_descriptor, name=None, input_info=None):
+        super().__init__(rdesc, name, input_info)
+
+    def fake_report(self, x, y, buttons):
+        if buttons is not None:
+            left, right, middle = buttons
+            if left is None:
+                left = self.left
+            if right is None:
+                right = self.right
+            if middle is None:
+                middle = self.middle
+        else:
+            left = self.left
+            right = self.right
+            middle = self.middle
+
+        button_mask = sum(1 << i for i, b in enumerate([left, right, middle]) if b)
+        x = max(-127, min(127, x))
+        y = max(-127, min(127, y))
+        x = hidtools.util.to_twos_comp(x, 8)
+        y = hidtools.util.to_twos_comp(y, 8)
+        return [button_mask, x, y]
+
+
+class WheelMouse(ButtonMouse):
+    # fmt: off
+    report_descriptor = [
+        0x05, 0x01,  # Usage Page (Generic Desktop)        0
+        0x09, 0x02,  # Usage (Mouse)                       2
+        0xa1, 0x01,  # Collection (Application)            4
+        0x05, 0x09,  # .Usage Page (Button)                6
+        0x19, 0x01,  # .Usage Minimum (1)                  8
+        0x29, 0x03,  # .Usage Maximum (3)                  10
+        0x15, 0x00,  # .Logical Minimum (0)                12
+        0x25, 0x01,  # .Logical Maximum (1)                14
+        0x95, 0x03,  # .Report Count (3)                   16
+        0x75, 0x01,  # .Report Size (1)                    18
+        0x81, 0x02,  # .Input (Data,Var,Abs)               20
+        0x95, 0x01,  # .Report Count (1)                   22
+        0x75, 0x05,  # .Report Size (5)                    24
+        0x81, 0x03,  # .Input (Cnst,Var,Abs)               26
+        0x05, 0x01,  # .Usage Page (Generic Desktop)       28
+        0x09, 0x01,  # .Usage (Pointer)                    30
+        0xa1, 0x00,  # .Collection (Physical)              32
+        0x09, 0x30,  # ..Usage (X)                         34
+        0x09, 0x31,  # ..Usage (Y)                         36
+        0x15, 0x81,  # ..Logical Minimum (-127)            38
+        0x25, 0x7f,  # ..Logical Maximum (127)             40
+        0x75, 0x08,  # ..Report Size (8)                   42
+        0x95, 0x02,  # ..Report Count (2)                  44
+        0x81, 0x06,  # ..Input (Data,Var,Rel)              46
+        0xc0,        # .End Collection                     48
+        0x09, 0x38,  # .Usage (Wheel)                      49
+        0x15, 0x81,  # .Logical Minimum (-127)             51
+        0x25, 0x7f,  # .Logical Maximum (127)              53
+        0x75, 0x08,  # .Report Size (8)                    55
+        0x95, 0x01,  # .Report Count (1)                   57
+        0x81, 0x06,  # .Input (Data,Var,Rel)               59
+        0xc0,        # End Collection                      61
+    ]
+    # fmt: on
+
+    def __init__(self, rdesc=report_descriptor, name=None, input_info=None):
+        super().__init__(rdesc, name, input_info)
+        self.wheel_multiplier = 1
+
+
+class TwoWheelMouse(WheelMouse):
+    # fmt: off
+    report_descriptor = [
+        0x05, 0x01,        # Usage Page (Generic Desktop)        0
+        0x09, 0x02,        # Usage (Mouse)                       2
+        0xa1, 0x01,        # Collection (Application)            4
+        0x09, 0x01,        # .Usage (Pointer)                    6
+        0xa1, 0x00,        # .Collection (Physical)              8
+        0x05, 0x09,        # ..Usage Page (Button)               10
+        0x19, 0x01,        # ..Usage Minimum (1)                 12
+        0x29, 0x10,        # ..Usage Maximum (16)                14
+        0x15, 0x00,        # ..Logical Minimum (0)               16
+        0x25, 0x01,        # ..Logical Maximum (1)               18
+        0x95, 0x10,        # ..Report Count (16)                 20
+        0x75, 0x01,        # ..Report Size (1)                   22
+        0x81, 0x02,        # ..Input (Data,Var,Abs)              24
+        0x05, 0x01,        # ..Usage Page (Generic Desktop)      26
+        0x16, 0x01, 0x80,  # ..Logical Minimum (-32767)          28
+        0x26, 0xff, 0x7f,  # ..Logical Maximum (32767)           31
+        0x75, 0x10,        # ..Report Size (16)                  34
+        0x95, 0x02,        # ..Report Count (2)                  36
+        0x09, 0x30,        # ..Usage (X)                         38
+        0x09, 0x31,        # ..Usage (Y)                         40
+        0x81, 0x06,        # ..Input (Data,Var,Rel)              42
+        0x15, 0x81,        # ..Logical Minimum (-127)            44
+        0x25, 0x7f,        # ..Logical Maximum (127)             46
+        0x75, 0x08,        # ..Report Size (8)                   48
+        0x95, 0x01,        # ..Report Count (1)                  50
+        0x09, 0x38,        # ..Usage (Wheel)                     52
+        0x81, 0x06,        # ..Input (Data,Var,Rel)              54
+        0x05, 0x0c,        # ..Usage Page (Consumer Devices)     56
+        0x0a, 0x38, 0x02,  # ..Usage (AC Pan)                    58
+        0x95, 0x01,        # ..Report Count (1)                  61
+        0x81, 0x06,        # ..Input (Data,Var,Rel)              63
+        0xc0,              # .End Collection                     65
+        0xc0,              # End Collection                      66
+    ]
+    # fmt: on
+
+    def __init__(self, rdesc=report_descriptor, name=None, input_info=None):
+        super().__init__(rdesc, name, input_info)
+        self.hwheel_multiplier = 1
+
+
+class MIDongleMIWirelessMouse(TwoWheelMouse):
+    # fmt: off
+    report_descriptor = [
+        0x05, 0x01,         # Usage Page (Generic Desktop)
+        0x09, 0x02,         # Usage (Mouse)
+        0xa1, 0x01,         # Collection (Application)
+        0x85, 0x01,         # .Report ID (1)
+        0x09, 0x01,         # .Usage (Pointer)
+        0xa1, 0x00,         # .Collection (Physical)
+        0x95, 0x05,         # ..Report Count (5)
+        0x75, 0x01,         # ..Report Size (1)
+        0x05, 0x09,         # ..Usage Page (Button)
+        0x19, 0x01,         # ..Usage Minimum (1)
+        0x29, 0x05,         # ..Usage Maximum (5)
+        0x15, 0x00,         # ..Logical Minimum (0)
+        0x25, 0x01,         # ..Logical Maximum (1)
+        0x81, 0x02,         # ..Input (Data,Var,Abs)
+        0x95, 0x01,         # ..Report Count (1)
+        0x75, 0x03,         # ..Report Size (3)
+        0x81, 0x01,         # ..Input (Cnst,Arr,Abs)
+        0x75, 0x08,         # ..Report Size (8)
+        0x95, 0x01,         # ..Report Count (1)
+        0x05, 0x01,         # ..Usage Page (Generic Desktop)
+        0x09, 0x38,         # ..Usage (Wheel)
+        0x15, 0x81,         # ..Logical Minimum (-127)
+        0x25, 0x7f,         # ..Logical Maximum (127)
+        0x81, 0x06,         # ..Input (Data,Var,Rel)
+        0x05, 0x0c,         # ..Usage Page (Consumer Devices)
+        0x0a, 0x38, 0x02,   # ..Usage (AC Pan)
+        0x95, 0x01,         # ..Report Count (1)
+        0x81, 0x06,         # ..Input (Data,Var,Rel)
+        0xc0,               # .End Collection
+        0x85, 0x02,         # .Report ID (2)
+        0x09, 0x01,         # .Usage (Consumer Control)
+        0xa1, 0x00,         # .Collection (Physical)
+        0x75, 0x0c,         # ..Report Size (12)
+        0x95, 0x02,         # ..Report Count (2)
+        0x05, 0x01,         # ..Usage Page (Generic Desktop)
+        0x09, 0x30,         # ..Usage (X)
+        0x09, 0x31,         # ..Usage (Y)
+        0x16, 0x01, 0xf8,   # ..Logical Minimum (-2047)
+        0x26, 0xff, 0x07,   # ..Logical Maximum (2047)
+        0x81, 0x06,         # ..Input (Data,Var,Rel)
+        0xc0,               # .End Collection
+        0xc0,               # End Collection
+        0x05, 0x0c,         # Usage Page (Consumer Devices)
+        0x09, 0x01,         # Usage (Consumer Control)
+        0xa1, 0x01,         # Collection (Application)
+        0x85, 0x03,         # .Report ID (3)
+        0x15, 0x00,         # .Logical Minimum (0)
+        0x25, 0x01,         # .Logical Maximum (1)
+        0x75, 0x01,         # .Report Size (1)
+        0x95, 0x01,         # .Report Count (1)
+        0x09, 0xcd,         # .Usage (Play/Pause)
+        0x81, 0x06,         # .Input (Data,Var,Rel)
+        0x0a, 0x83, 0x01,   # .Usage (AL Consumer Control Config)
+        0x81, 0x06,         # .Input (Data,Var,Rel)
+        0x09, 0xb5,         # .Usage (Scan Next Track)
+        0x81, 0x06,         # .Input (Data,Var,Rel)
+        0x09, 0xb6,         # .Usage (Scan Previous Track)
+        0x81, 0x06,         # .Input (Data,Var,Rel)
+        0x09, 0xea,         # .Usage (Volume Down)
+        0x81, 0x06,         # .Input (Data,Var,Rel)
+        0x09, 0xe9,         # .Usage (Volume Up)
+        0x81, 0x06,         # .Input (Data,Var,Rel)
+        0x0a, 0x25, 0x02,   # .Usage (AC Forward)
+        0x81, 0x06,         # .Input (Data,Var,Rel)
+        0x0a, 0x24, 0x02,   # .Usage (AC Back)
+        0x81, 0x06,         # .Input (Data,Var,Rel)
+        0xc0,               # End Collection
+    ]
+    # fmt: on
+    device_input_info = (BusType.USB, 0x2717, 0x003B)
+    device_name = "uhid test MI Dongle MI Wireless Mouse"
+
+    def __init__(
+        self, rdesc=report_descriptor, name=device_name, input_info=device_input_info
+    ):
+        super().__init__(rdesc, name, input_info)
+
+    def event(self, x, y, buttons=None, wheels=None):
+        # this mouse spreads the relative pointer and the mouse buttons
+        # onto 2 distinct reports
+        rs = []
+        r = self.create_report(x, y, buttons, wheels, reportID=1)
+        self.call_input_event(r)
+        rs.append(r)
+        r = self.create_report(x, y, buttons, reportID=2)
+        self.call_input_event(r)
+        rs.append(r)
+        return rs
+
+
+class ResolutionMultiplierMouse(TwoWheelMouse):
+    # fmt: off
+    report_descriptor = [
+        0x05, 0x01,        # Usage Page (Generic Desktop)        83
+        0x09, 0x02,        # Usage (Mouse)                       85
+        0xa1, 0x01,        # Collection (Application)            87
+        0x05, 0x01,        # .Usage Page (Generic Desktop)       89
+        0x09, 0x02,        # .Usage (Mouse)                      91
+        0xa1, 0x02,        # .Collection (Logical)               93
+        0x85, 0x11,        # ..Report ID (17)                    95
+        0x09, 0x01,        # ..Usage (Pointer)                   97
+        0xa1, 0x00,        # ..Collection (Physical)             99
+        0x05, 0x09,        # ...Usage Page (Button)              101
+        0x19, 0x01,        # ...Usage Minimum (1)                103
+        0x29, 0x03,        # ...Usage Maximum (3)                105
+        0x95, 0x03,        # ...Report Count (3)                 107
+        0x75, 0x01,        # ...Report Size (1)                  109
+        0x25, 0x01,        # ...Logical Maximum (1)              111
+        0x81, 0x02,        # ...Input (Data,Var,Abs)             113
+        0x95, 0x01,        # ...Report Count (1)                 115
+        0x81, 0x01,        # ...Input (Cnst,Arr,Abs)             117
+        0x09, 0x05,        # ...Usage (Vendor Usage 0x05)        119
+        0x81, 0x02,        # ...Input (Data,Var,Abs)             121
+        0x95, 0x03,        # ...Report Count (3)                 123
+        0x81, 0x01,        # ...Input (Cnst,Arr,Abs)             125
+        0x05, 0x01,        # ...Usage Page (Generic Desktop)     127
+        0x09, 0x30,        # ...Usage (X)                        129
+        0x09, 0x31,        # ...Usage (Y)                        131
+        0x95, 0x02,        # ...Report Count (2)                 133
+        0x75, 0x08,        # ...Report Size (8)                  135
+        0x15, 0x81,        # ...Logical Minimum (-127)           137
+        0x25, 0x7f,        # ...Logical Maximum (127)            139
+        0x81, 0x06,        # ...Input (Data,Var,Rel)             141
+        0xa1, 0x02,        # ...Collection (Logical)             143
+        0x85, 0x12,        # ....Report ID (18)                  145
+        0x09, 0x48,        # ....Usage (Resolution Multiplier)   147
+        0x95, 0x01,        # ....Report Count (1)                149
+        0x75, 0x02,        # ....Report Size (2)                 151
+        0x15, 0x00,        # ....Logical Minimum (0)             153
+        0x25, 0x01,        # ....Logical Maximum (1)             155
+        0x35, 0x01,        # ....Physical Minimum (1)            157
+        0x45, 0x04,        # ....Physical Maximum (4)            159
+        0xb1, 0x02,        # ....Feature (Data,Var,Abs)          161
+        0x35, 0x00,        # ....Physical Minimum (0)            163
+        0x45, 0x00,        # ....Physical Maximum (0)            165
+        0x75, 0x06,        # ....Report Size (6)                 167
+        0xb1, 0x01,        # ....Feature (Cnst,Arr,Abs)          169
+        0x85, 0x11,        # ....Report ID (17)                  171
+        0x09, 0x38,        # ....Usage (Wheel)                   173
+        0x15, 0x81,        # ....Logical Minimum (-127)          175
+        0x25, 0x7f,        # ....Logical Maximum (127)           177
+        0x75, 0x08,        # ....Report Size (8)                 179
+        0x81, 0x06,        # ....Input (Data,Var,Rel)            181
+        0xc0,              # ...End Collection                   183
+        0x05, 0x0c,        # ...Usage Page (Consumer Devices)    184
+        0x75, 0x08,        # ...Report Size (8)                  186
+        0x0a, 0x38, 0x02,  # ...Usage (AC Pan)                   188
+        0x81, 0x06,        # ...Input (Data,Var,Rel)             191
+        0xc0,              # ..End Collection                    193
+        0xc0,              # .End Collection                     194
+        0xc0,              # End Collection                      195
+    ]
+    # fmt: on
+
+    def __init__(self, rdesc=report_descriptor, name=None, input_info=None):
+        super().__init__(rdesc, name, input_info)
+        self.default_reportID = 0x11
+
+        # Feature Report 12, multiplier Feature value must be set to 0b01,
+        # i.e. 1. We should extract that from the descriptor instead
+        # of hardcoding it here, but meanwhile this will do.
+        self.set_feature_report = [0x12, 0x1]
+
+    def set_report(self, req, rnum, rtype, data):
+        if rtype != self.UHID_FEATURE_REPORT:
+            raise InvalidHIDCommunication(f"Unexpected report type: {rtype}")
+        if rnum != 0x12:
+            raise InvalidHIDCommunication(f"Unexpected report number: {rnum}")
+
+        if data != self.set_feature_report:
+            raise InvalidHIDCommunication(
+                f"Unexpected data: {data}, expected {self.set_feature_report}"
+            )
+
+        self.wheel_multiplier = 4
+
+        return 0
+
+
+class BadResolutionMultiplierMouse(ResolutionMultiplierMouse):
+    def set_report(self, req, rnum, rtype, data):
+        super().set_report(req, rnum, rtype, data)
+
+        self.wheel_multiplier = 1
+        self.hwheel_multiplier = 1
+        return 32  # EPIPE
+
+
+class ResolutionMultiplierHWheelMouse(TwoWheelMouse):
+    # fmt: off
+    report_descriptor = [
+        0x05, 0x01,         # Usage Page (Generic Desktop)        0
+        0x09, 0x02,         # Usage (Mouse)                       2
+        0xa1, 0x01,         # Collection (Application)            4
+        0x05, 0x01,         # .Usage Page (Generic Desktop)       6
+        0x09, 0x02,         # .Usage (Mouse)                      8
+        0xa1, 0x02,         # .Collection (Logical)               10
+        0x85, 0x1a,         # ..Report ID (26)                    12
+        0x09, 0x01,         # ..Usage (Pointer)                   14
+        0xa1, 0x00,         # ..Collection (Physical)             16
+        0x05, 0x09,         # ...Usage Page (Button)              18
+        0x19, 0x01,         # ...Usage Minimum (1)                20
+        0x29, 0x05,         # ...Usage Maximum (5)                22
+        0x95, 0x05,         # ...Report Count (5)                 24
+        0x75, 0x01,         # ...Report Size (1)                  26
+        0x15, 0x00,         # ...Logical Minimum (0)              28
+        0x25, 0x01,         # ...Logical Maximum (1)              30
+        0x81, 0x02,         # ...Input (Data,Var,Abs)             32
+        0x75, 0x03,         # ...Report Size (3)                  34
+        0x95, 0x01,         # ...Report Count (1)                 36
+        0x81, 0x01,         # ...Input (Cnst,Arr,Abs)             38
+        0x05, 0x01,         # ...Usage Page (Generic Desktop)     40
+        0x09, 0x30,         # ...Usage (X)                        42
+        0x09, 0x31,         # ...Usage (Y)                        44
+        0x95, 0x02,         # ...Report Count (2)                 46
+        0x75, 0x10,         # ...Report Size (16)                 48
+        0x16, 0x01, 0x80,   # ...Logical Minimum (-32767)         50
+        0x26, 0xff, 0x7f,   # ...Logical Maximum (32767)          53
+        0x81, 0x06,         # ...Input (Data,Var,Rel)             56
+        0xa1, 0x02,         # ...Collection (Logical)             58
+        0x85, 0x12,         # ....Report ID (18)                  60
+        0x09, 0x48,         # ....Usage (Resolution Multiplier)   62
+        0x95, 0x01,         # ....Report Count (1)                64
+        0x75, 0x02,         # ....Report Size (2)                 66
+        0x15, 0x00,         # ....Logical Minimum (0)             68
+        0x25, 0x01,         # ....Logical Maximum (1)             70
+        0x35, 0x01,         # ....Physical Minimum (1)            72
+        0x45, 0x0c,         # ....Physical Maximum (12)           74
+        0xb1, 0x02,         # ....Feature (Data,Var,Abs)          76
+        0x85, 0x1a,         # ....Report ID (26)                  78
+        0x09, 0x38,         # ....Usage (Wheel)                   80
+        0x35, 0x00,         # ....Physical Minimum (0)            82
+        0x45, 0x00,         # ....Physical Maximum (0)            84
+        0x95, 0x01,         # ....Report Count (1)                86
+        0x75, 0x10,         # ....Report Size (16)                88
+        0x16, 0x01, 0x80,   # ....Logical Minimum (-32767)        90
+        0x26, 0xff, 0x7f,   # ....Logical Maximum (32767)         93
+        0x81, 0x06,         # ....Input (Data,Var,Rel)            96
+        0xc0,               # ...End Collection                   98
+        0xa1, 0x02,         # ...Collection (Logical)             99
+        0x85, 0x12,         # ....Report ID (18)                  101
+        0x09, 0x48,         # ....Usage (Resolution Multiplier)   103
+        0x75, 0x02,         # ....Report Size (2)                 105
+        0x15, 0x00,         # ....Logical Minimum (0)             107
+        0x25, 0x01,         # ....Logical Maximum (1)             109
+        0x35, 0x01,         # ....Physical Minimum (1)            111
+        0x45, 0x0c,         # ....Physical Maximum (12)           113
+        0xb1, 0x02,         # ....Feature (Data,Var,Abs)          115
+        0x35, 0x00,         # ....Physical Minimum (0)            117
+        0x45, 0x00,         # ....Physical Maximum (0)            119
+        0x75, 0x04,         # ....Report Size (4)                 121
+        0xb1, 0x01,         # ....Feature (Cnst,Arr,Abs)          123
+        0x85, 0x1a,         # ....Report ID (26)                  125
+        0x05, 0x0c,         # ....Usage Page (Consumer Devices)   127
+        0x95, 0x01,         # ....Report Count (1)                129
+        0x75, 0x10,         # ....Report Size (16)                131
+        0x16, 0x01, 0x80,   # ....Logical Minimum (-32767)        133
+        0x26, 0xff, 0x7f,   # ....Logical Maximum (32767)         136
+        0x0a, 0x38, 0x02,   # ....Usage (AC Pan)                  139
+        0x81, 0x06,         # ....Input (Data,Var,Rel)            142
+        0xc0,               # ...End Collection                   144
+        0xc0,               # ..End Collection                    145
+        0xc0,               # .End Collection                     146
+        0xc0,               # End Collection                      147
+    ]
+    # fmt: on
+
+    def __init__(self, rdesc=report_descriptor, name=None, input_info=None):
+        super().__init__(rdesc, name, input_info)
+        self.default_reportID = 0x1A
+
+        # Feature Report 12, multiplier Feature value must be set to 0b0101,
+        # i.e. 5. We should extract that from the descriptor instead
+        # of hardcoding it here, but meanwhile this will do.
+        self.set_feature_report = [0x12, 0x5]
+
+    def set_report(self, req, rnum, rtype, data):
+        super().set_report(req, rnum, rtype, data)
+
+        self.wheel_multiplier = 12
+        self.hwheel_multiplier = 12
+
+        return 0
+
+
+class BaseTest:
+    class TestMouse(base.BaseTestCase.TestUhid):
+        def test_buttons(self):
+            """check for button reliability."""
+            uhdev = self.uhdev
+            evdev = uhdev.get_evdev()
+            syn_event = self.syn_event
+
+            r = uhdev.event(0, 0, (None, True, None))
+            expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_RIGHT, 1)
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEventsIn((syn_event, expected_event), events)
+            assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 1
+
+            r = uhdev.event(0, 0, (None, False, None))
+            expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_RIGHT, 0)
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEventsIn((syn_event, expected_event), events)
+            assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 0
+
+            r = uhdev.event(0, 0, (None, None, True))
+            expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_MIDDLE, 1)
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEventsIn((syn_event, expected_event), events)
+            assert evdev.value[libevdev.EV_KEY.BTN_MIDDLE] == 1
+
+            r = uhdev.event(0, 0, (None, None, False))
+            expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_MIDDLE, 0)
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEventsIn((syn_event, expected_event), events)
+            assert evdev.value[libevdev.EV_KEY.BTN_MIDDLE] == 0
+
+            r = uhdev.event(0, 0, (True, None, None))
+            expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 1)
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEventsIn((syn_event, expected_event), events)
+            assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 1
+
+            r = uhdev.event(0, 0, (False, None, None))
+            expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 0)
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEventsIn((syn_event, expected_event), events)
+            assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 0
+
+            r = uhdev.event(0, 0, (True, True, None))
+            expected_event0 = libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 1)
+            expected_event1 = libevdev.InputEvent(libevdev.EV_KEY.BTN_RIGHT, 1)
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEventsIn(
+                (syn_event, expected_event0, expected_event1), events
+            )
+            assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 1
+            assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 1
+
+            r = uhdev.event(0, 0, (False, None, None))
+            expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 0)
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEventsIn((syn_event, expected_event), events)
+            assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 1
+            assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 0
+
+            r = uhdev.event(0, 0, (None, False, None))
+            expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_RIGHT, 0)
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEventsIn((syn_event, expected_event), events)
+            assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 0
+            assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 0
+
+        def test_relative(self):
+            """Check for relative events."""
+            uhdev = self.uhdev
+
+            syn_event = self.syn_event
+
+            r = uhdev.event(0, -1)
+            expected_event = libevdev.InputEvent(libevdev.EV_REL.REL_Y, -1)
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEvents((syn_event, expected_event), events)
+
+            r = uhdev.event(1, 0)
+            expected_event = libevdev.InputEvent(libevdev.EV_REL.REL_X, 1)
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEvents((syn_event, expected_event), events)
+
+            r = uhdev.event(-1, 2)
+            expected_event0 = libevdev.InputEvent(libevdev.EV_REL.REL_X, -1)
+            expected_event1 = libevdev.InputEvent(libevdev.EV_REL.REL_Y, 2)
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEvents(
+                (syn_event, expected_event0, expected_event1), events
+            )
+
+
+class TestSimpleMouse(BaseTest.TestMouse):
+    def create_device(self):
+        return ButtonMouse()
+
+    def test_rdesc(self):
+        """Check that the testsuite actually manages to format the
+        reports according to the report descriptors.
+        No kernel device is used here"""
+        uhdev = self.uhdev
+
+        event = (0, 0, (None, None, None))
+        assert uhdev.fake_report(*event) == uhdev.create_report(*event)
+
+        event = (0, 0, (None, True, None))
+        assert uhdev.fake_report(*event) == uhdev.create_report(*event)
+
+        event = (0, 0, (True, True, None))
+        assert uhdev.fake_report(*event) == uhdev.create_report(*event)
+
+        event = (0, 0, (False, False, False))
+        assert uhdev.fake_report(*event) == uhdev.create_report(*event)
+
+        event = (1, 0, (True, False, True))
+        assert uhdev.fake_report(*event) == uhdev.create_report(*event)
+
+        event = (-1, 0, (True, False, True))
+        assert uhdev.fake_report(*event) == uhdev.create_report(*event)
+
+        event = (-5, 5, (True, False, True))
+        assert uhdev.fake_report(*event) == uhdev.create_report(*event)
+
+        event = (-127, 127, (True, False, True))
+        assert uhdev.fake_report(*event) == uhdev.create_report(*event)
+
+        event = (0, -128, (True, False, True))
+        with pytest.raises(hidtools.hid.RangeError):
+            uhdev.create_report(*event)
+
+
+class TestWheelMouse(BaseTest.TestMouse):
+    def create_device(self):
+        return WheelMouse()
+
+    def is_wheel_highres(self, uhdev):
+        evdev = uhdev.get_evdev()
+        assert evdev.has(libevdev.EV_REL.REL_WHEEL)
+        return evdev.has(libevdev.EV_REL.REL_WHEEL_HI_RES)
+
+    def test_wheel(self):
+        uhdev = self.uhdev
+
+        # check if the kernel is high res wheel compatible
+        high_res_wheel = self.is_wheel_highres(uhdev)
+
+        syn_event = self.syn_event
+        # The Resolution Multiplier is applied to the HID reports, so we
+        # need to pre-multiply too.
+        mult = uhdev.wheel_multiplier
+
+        r = uhdev.event(0, 0, wheels=1 * mult)
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL, 1))
+        if high_res_wheel:
+            expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, 120))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEvents(expected, events)
+
+        r = uhdev.event(0, 0, wheels=-1 * mult)
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL, -1))
+        if high_res_wheel:
+            expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, -120))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEvents(expected, events)
+
+        r = uhdev.event(-1, 2, wheels=3 * mult)
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_X, -1))
+        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_Y, 2))
+        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL, 3))
+        if high_res_wheel:
+            expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, 360))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEvents(expected, events)
+
+
+class TestTwoWheelMouse(TestWheelMouse):
+    def create_device(self):
+        return TwoWheelMouse()
+
+    def is_hwheel_highres(self, uhdev):
+        evdev = uhdev.get_evdev()
+        assert evdev.has(libevdev.EV_REL.REL_HWHEEL)
+        return evdev.has(libevdev.EV_REL.REL_HWHEEL_HI_RES)
+
+    def test_ac_pan(self):
+        uhdev = self.uhdev
+
+        # check if the kernel is high res wheel compatible
+        high_res_wheel = self.is_wheel_highres(uhdev)
+        high_res_hwheel = self.is_hwheel_highres(uhdev)
+        assert high_res_wheel == high_res_hwheel
+
+        syn_event = self.syn_event
+        # The Resolution Multiplier is applied to the HID reports, so we
+        # need to pre-multiply too.
+        hmult = uhdev.hwheel_multiplier
+        vmult = uhdev.wheel_multiplier
+
+        r = uhdev.event(0, 0, wheels=(0, 1 * hmult))
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL, 1))
+        if high_res_hwheel:
+            expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, 120))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEvents(expected, events)
+
+        r = uhdev.event(0, 0, wheels=(0, -1 * hmult))
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL, -1))
+        if high_res_hwheel:
+            expected.append(
+                libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, -120)
+            )
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEvents(expected, events)
+
+        r = uhdev.event(-1, 2, wheels=(0, 3 * hmult))
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_X, -1))
+        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_Y, 2))
+        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL, 3))
+        if high_res_hwheel:
+            expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, 360))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEvents(expected, events)
+
+        r = uhdev.event(-1, 2, wheels=(-3 * vmult, 4 * hmult))
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_X, -1))
+        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_Y, 2))
+        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL, -3))
+        if high_res_wheel:
+            expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, -360))
+        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL, 4))
+        if high_res_wheel:
+            expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, 480))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEvents(expected, events)
+
+
+class TestResolutionMultiplierMouse(TestTwoWheelMouse):
+    def create_device(self):
+        return ResolutionMultiplierMouse()
+
+    def is_wheel_highres(self, uhdev):
+        high_res = super().is_wheel_highres(uhdev)
+
+        if not high_res:
+            # the kernel doesn't seem to support the high res wheel mice,
+            # make sure we haven't triggered the feature
+            assert uhdev.wheel_multiplier == 1
+
+        return high_res
+
+    def test_resolution_multiplier_wheel(self):
+        uhdev = self.uhdev
+
+        if not self.is_wheel_highres(uhdev):
+            pytest.skip("Kernel not compatible, we can not trigger the conditions")
+
+        assert uhdev.wheel_multiplier > 1
+        assert 120 % uhdev.wheel_multiplier == 0
+
+    def test_wheel_with_multiplier(self):
+        uhdev = self.uhdev
+
+        if not self.is_wheel_highres(uhdev):
+            pytest.skip("Kernel not compatible, we can not trigger the conditions")
+
+        assert uhdev.wheel_multiplier > 1
+
+        syn_event = self.syn_event
+        mult = uhdev.wheel_multiplier
+
+        r = uhdev.event(0, 0, wheels=1)
+        expected = [syn_event]
+        expected.append(
+            libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, 120 / mult)
+        )
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEvents(expected, events)
+
+        r = uhdev.event(0, 0, wheels=-1)
+        expected = [syn_event]
+        expected.append(
+            libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, -120 / mult)
+        )
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEvents(expected, events)
+
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_X, 1))
+        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_Y, -2))
+        expected.append(
+            libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, 120 / mult)
+        )
+
+        for _ in range(mult - 1):
+            r = uhdev.event(1, -2, wheels=1)
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEvents(expected, events)
+
+        r = uhdev.event(1, -2, wheels=1)
+        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL, 1))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEvents(expected, events)
+
+
+class TestBadResolutionMultiplierMouse(TestTwoWheelMouse):
+    def create_device(self):
+        return BadResolutionMultiplierMouse()
+
+    def is_wheel_highres(self, uhdev):
+        high_res = super().is_wheel_highres(uhdev)
+
+        assert uhdev.wheel_multiplier == 1
+
+        return high_res
+
+    def test_resolution_multiplier_wheel(self):
+        uhdev = self.uhdev
+
+        assert uhdev.wheel_multiplier == 1
+
+
+class TestResolutionMultiplierHWheelMouse(TestResolutionMultiplierMouse):
+    def create_device(self):
+        return ResolutionMultiplierHWheelMouse()
+
+    def is_hwheel_highres(self, uhdev):
+        high_res = super().is_hwheel_highres(uhdev)
+
+        if not high_res:
+            # the kernel doesn't seem to support the high res wheel mice,
+            # make sure we haven't triggered the feature
+            assert uhdev.hwheel_multiplier == 1
+
+        return high_res
+
+    def test_resolution_multiplier_ac_pan(self):
+        uhdev = self.uhdev
+
+        if not self.is_hwheel_highres(uhdev):
+            pytest.skip("Kernel not compatible, we can not trigger the conditions")
+
+        assert uhdev.hwheel_multiplier > 1
+        assert 120 % uhdev.hwheel_multiplier == 0
+
+    def test_ac_pan_with_multiplier(self):
+        uhdev = self.uhdev
+
+        if not self.is_hwheel_highres(uhdev):
+            pytest.skip("Kernel not compatible, we can not trigger the conditions")
+
+        assert uhdev.hwheel_multiplier > 1
+
+        syn_event = self.syn_event
+        hmult = uhdev.hwheel_multiplier
+
+        r = uhdev.event(0, 0, wheels=(0, 1))
+        expected = [syn_event]
+        expected.append(
+            libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, 120 / hmult)
+        )
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEvents(expected, events)
+
+        r = uhdev.event(0, 0, wheels=(0, -1))
+        expected = [syn_event]
+        expected.append(
+            libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, -120 / hmult)
+        )
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEvents(expected, events)
+
+        expected = [syn_event]
+        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_X, 1))
+        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_Y, -2))
+        expected.append(
+            libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, 120 / hmult)
+        )
+
+        for _ in range(hmult - 1):
+            r = uhdev.event(1, -2, wheels=(0, 1))
+            events = uhdev.next_sync_events()
+            self.debug_reports(r, uhdev, events)
+            self.assertInputEvents(expected, events)
+
+        r = uhdev.event(1, -2, wheels=(0, 1))
+        expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL, 1))
+        events = uhdev.next_sync_events()
+        self.debug_reports(r, uhdev, events)
+        self.assertInputEvents(expected, events)
+
+
+class TestMiMouse(TestWheelMouse):
+    def create_device(self):
+        return MIDongleMIWirelessMouse()
+
+    def assertInputEvents(self, expected_events, effective_events):
+        # Buttons and x/y are spread over two HID reports, so we can get two
+        # event frames for this device.
+        remaining = self.assertInputEventsIn(expected_events, effective_events)
+        try:
+            remaining.remove(libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT, 0))
+        except ValueError:
+            # If there's no SYN_REPORT in the list, continue and let the
+            # assert below print out the real error
+            pass
+        assert remaining == []

-- 
2.39.1


^ permalink raw reply related

* [PATCH 01/11] selftests: hid: make vmtest rely on make
From: Benjamin Tissoires @ 2023-02-17 16:17 UTC (permalink / raw)
  To: Jiri Kosina, Shuah Khan
  Cc: linux-input, linux-kselftest, linux-kernel, Benjamin Tissoires
In-Reply-To: <20230217-import-hid-tools-tests-v1-0-d1c48590d0ee@redhat.com>

Having a default binary is simple enough, but this also means that
we need to keep the targets in sync as we are adding them in the Makefile.

So instead of doing that manual work, make vmtest.sh generic enough to
actually be capable of running 'make -C tools/testing/selftests/hid'.

The new image we use has make installed, which the base fedora image
doesn't.

Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
---
 tools/testing/selftests/hid/vmtest.sh | 25 +++++++++++++++----------
 1 file changed, 15 insertions(+), 10 deletions(-)

diff --git a/tools/testing/selftests/hid/vmtest.sh b/tools/testing/selftests/hid/vmtest.sh
index 90f34150f257..6346b0620dba 100755
--- a/tools/testing/selftests/hid/vmtest.sh
+++ b/tools/testing/selftests/hid/vmtest.sh
@@ -16,7 +16,6 @@ x86_64)
 	exit 1
 	;;
 esac
-DEFAULT_COMMAND="./hid_bpf"
 SCRIPT_DIR="$(dirname $(realpath $0))"
 OUTPUT_DIR="$SCRIPT_DIR/results"
 KCONFIG_REL_PATHS=("${SCRIPT_DIR}/config" "${SCRIPT_DIR}/config.common" "${SCRIPT_DIR}/config.${ARCH}")
@@ -25,7 +24,10 @@ NUM_COMPILE_JOBS="$(nproc)"
 LOG_FILE_BASE="$(date +"hid_selftests.%Y-%m-%d_%H-%M-%S")"
 LOG_FILE="${LOG_FILE_BASE}.log"
 EXIT_STATUS_FILE="${LOG_FILE_BASE}.exit_status"
-CONTAINER_IMAGE="registry.fedoraproject.org/fedora:36"
+CONTAINER_IMAGE="registry.freedesktop.org/libevdev/hid-tools/fedora/37:2023-02-17.1"
+
+TARGETS="${TARGETS:=$(basename ${SCRIPT_DIR})}"
+DEFAULT_COMMAND="make -C tools/testing/selftests TARGETS=${TARGETS} run_tests"
 
 usage()
 {
@@ -33,9 +35,9 @@ usage()
 Usage: $0 [-i] [-s] [-d <output_dir>] -- [<command>]
 
 <command> is the command you would normally run when you are in
-tools/testing/selftests/bpf. e.g:
+the source kernel direcory. e.g:
 
-	$0 -- ./hid_bpf
+	$0 -- ./tools/testing/selftests/hid/hid_bpf
 
 If no command is specified and a debug shell (-s) is not requested,
 "${DEFAULT_COMMAND}" will be run by default.
@@ -43,11 +45,11 @@ If no command is specified and a debug shell (-s) is not requested,
 If you build your kernel using KBUILD_OUTPUT= or O= options, these
 can be passed as environment variables to the script:
 
-  O=<kernel_build_path> $0 -- ./hid_bpf
+  O=<kernel_build_path> $0 -- ./tools/testing/selftests/hid/hid_bpf
 
 or
 
-  KBUILD_OUTPUT=<kernel_build_path> $0 -- ./hid_bpf
+  KBUILD_OUTPUT=<kernel_build_path> $0 -- ./tools/testing/selftests/hid/hid_bpf
 
 Options:
 
@@ -91,11 +93,14 @@ update_selftests()
 
 run_vm()
 {
-	local b2c="$1"
-	local kernel_bzimage="$2"
-	local command="$3"
+	local run_dir="$1"
+	local b2c="$2"
+	local kernel_bzimage="$3"
+	local command="$4"
 	local post_command=""
 
+	cd "${run_dir}"
+
 	if ! which "${QEMU_BINARY}" &> /dev/null; then
 		cat <<EOF
 Could not find ${QEMU_BINARY}
@@ -273,7 +278,7 @@ main()
 	fi
 
 	update_selftests "${kernel_checkout}" "${make_command}"
-	run_vm $b2c "${kernel_bzimage}" "${command}"
+	run_vm "${kernel_checkout}" $b2c "${kernel_bzimage}" "${command}"
 	if [[ "${debug_shell}" != "yes" ]]; then
 		echo "Logs saved in ${OUTPUT_DIR}/${LOG_FILE}"
 	fi

-- 
2.39.1


^ permalink raw reply related

* [PATCH 00/11] selftests: hid: import the tests from hid-tools
From: Benjamin Tissoires @ 2023-02-17 16:17 UTC (permalink / raw)
  To: Jiri Kosina, Shuah Khan
  Cc: linux-input, linux-kselftest, linux-kernel, Benjamin Tissoires,
	Peter Hutterer, Candle Sun, Jose Torreguitar,
	Roderick Colenbrander, Silvan Jegen, Kai-Heng Feng,
	наб, Blaž Hrastnik, Jason Gerecke,
	Nicolas Saenz Julienne

I have been running hid-tools for a while, but it was in its own
separate repository for multiple reasons. And the past few weeks
I finally managed to make the kernel tests in that repo in a
state where we can merge them in the kernel tree directly:

- the tests run in ~2 to 3 minutes
- the tests are way more reliable than previously
- the tests are mostly self-contained now (to the exception
  of the Sony ones)

To be able to run the tests we need to use the latest release
of hid-tools, as this project still keeps the HID parsing logic
and is capable of generating the HID events.

The series also ensures we can run the tests with vmtest.sh,
allowing for a quick development and test in the tree itself.

This should allow us to require tests to be added to a series
when we see fit and keep them alive properly instead of having
to deal with 2 repositories.

In Cc are all of the people who participated in the elaboration
of those tests, so please send back a signed-off-by for each
commit you are part of.

This series applies on top of the for-6.3/hid-bpf branch, which
is the one that added the tools/testing/selftests/hid directory.
Given that this is unlikely this series will make the cut for
6.3, we might just consider this series to be based on top of
the future 6.3-rc1.

Cheers,
Benjamin

Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
---
Benjamin Tissoires (11):
      selftests: hid: make vmtest rely on make
      selftests: hid: import hid-tools hid-core tests
      selftests: hid: import hid-tools hid-gamepad tests
      selftests: hid: import hid-tools hid-keyboards tests
      selftests: hid: import hid-tools hid-mouse tests
      selftests: hid: import hid-tools hid-multitouch and hid-tablets tests
      selftests: hid: import hid-tools wacom tests
      selftests: hid: import hid-tools hid-apple tests
      selftests: hid: import hid-tools hid-ite tests
      selftests: hid: import hid-tools hid-sony and hid-playstation tests
      selftests: hid: import hid-tools usb-crash tests

 tools/testing/selftests/hid/Makefile               |   12 +
 tools/testing/selftests/hid/config                 |   11 +
 tools/testing/selftests/hid/hid-apple.sh           |    7 +
 tools/testing/selftests/hid/hid-core.sh            |    7 +
 tools/testing/selftests/hid/hid-gamepad.sh         |    7 +
 tools/testing/selftests/hid/hid-ite.sh             |    7 +
 tools/testing/selftests/hid/hid-keyboard.sh        |    7 +
 tools/testing/selftests/hid/hid-mouse.sh           |    7 +
 tools/testing/selftests/hid/hid-multitouch.sh      |    7 +
 tools/testing/selftests/hid/hid-sony.sh            |    7 +
 tools/testing/selftests/hid/hid-tablet.sh          |    7 +
 tools/testing/selftests/hid/hid-usb_crash.sh       |    7 +
 tools/testing/selftests/hid/hid-wacom.sh           |    7 +
 tools/testing/selftests/hid/run-hid-tools-tests.sh |   28 +
 tools/testing/selftests/hid/settings               |    3 +
 tools/testing/selftests/hid/tests/__init__.py      |    2 +
 tools/testing/selftests/hid/tests/base.py          |  345 ++++
 tools/testing/selftests/hid/tests/conftest.py      |   81 +
 .../selftests/hid/tests/descriptors_wacom.py       | 1360 +++++++++++++
 .../selftests/hid/tests/test_apple_keyboard.py     |  440 +++++
 tools/testing/selftests/hid/tests/test_gamepad.py  |  209 ++
 tools/testing/selftests/hid/tests/test_hid_core.py |  154 ++
 .../selftests/hid/tests/test_ite_keyboard.py       |  166 ++
 tools/testing/selftests/hid/tests/test_keyboard.py |  485 +++++
 tools/testing/selftests/hid/tests/test_mouse.py    |  977 +++++++++
 .../testing/selftests/hid/tests/test_multitouch.py | 2088 ++++++++++++++++++++
 tools/testing/selftests/hid/tests/test_sony.py     |  282 +++
 tools/testing/selftests/hid/tests/test_tablet.py   |  872 ++++++++
 .../testing/selftests/hid/tests/test_usb_crash.py  |  103 +
 .../selftests/hid/tests/test_wacom_generic.py      |  844 ++++++++
 tools/testing/selftests/hid/vmtest.sh              |   25 +-
 31 files changed, 8554 insertions(+), 10 deletions(-)
---
base-commit: 2f7f4efb9411770b4ad99eb314d6418e980248b4
change-id: 20230217-import-hid-tools-tests-dc0cd4f3c8a8

Best regards,
-- 
Benjamin Tissoires <benjamin.tissoires@redhat.com>


^ permalink raw reply

* [PATCH] Input: touchscreen - Add new Novatek NVT-ts driver
From: Hans de Goede @ 2023-02-17 15:07 UTC (permalink / raw)
  To: Dmitry Torokhov; +Cc: Hans de Goede, linux-input

Add a new driver for the Novatek i2c touchscreen controller as found
on the Acer Iconia One 7 B1-750 tablet. Unfortunately the touchscreen
controller model-number is unknown. Even with the tablet opened up it
is impossible to read the model-number.

Android calls this a "NVT-ts" touchscreen, but that may apply to other
Novatek controller models too.

This appears to be the same controller as the one supported by
https://github.com/advx9600/android/blob/master/touchscreen/NVTtouch_Android4.0/NVTtouch.c
but unfortunately that does not give us a model-number either.

Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
 MAINTAINERS                                |   6 +
 drivers/input/touchscreen/Kconfig          |  10 +
 drivers/input/touchscreen/Makefile         |   1 +
 drivers/input/touchscreen/novatek-nvt-ts.c | 288 +++++++++++++++++++++
 4 files changed, 305 insertions(+)
 create mode 100644 drivers/input/touchscreen/novatek-nvt-ts.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 60c0ded06e3f..0c051a973e6b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14835,6 +14835,12 @@ T:	git git://git.kernel.org/pub/scm/linux/kernel/git/wtarreau/nolibc.git
 F:	tools/include/nolibc/
 F:	tools/testing/selftests/nolibc/
 
+NOVATEK NVT-TS I2C TOUCHSCREEN DRIVER
+M:	Hans de Goede <hdegoede@redhat.com>
+L:	linux-input@vger.kernel.org
+S:	Maintained
+F:	drivers/input/touchscreen/novatek-nvt-ts.c
+
 NSDEPS
 M:	Matthias Maennich <maennich@google.com>
 S:	Maintained
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 68d99a112e14..59ca8bfe9a95 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -666,6 +666,16 @@ config TOUCHSCREEN_MTOUCH
 	  To compile this driver as a module, choose M here: the
 	  module will be called mtouch.
 
+config TOUCHSCREEN_NOVATEK_NVT_TS
+	tristate "Novatek NVT-ts touchscreen support"
+	depends on I2C
+	help
+	  Say Y here if you have a Novatek NVT-ts touchscreen.
+	  If unsure, say N.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called novatek-nvt-ts.
+
 config TOUCHSCREEN_IMAGIS
 	tristate "Imagis touchscreen support"
 	depends on I2C
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index 4968c370479a..41654239f89c 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -67,6 +67,7 @@ obj-$(CONFIG_TOUCHSCREEN_MMS114)	+= mms114.o
 obj-$(CONFIG_TOUCHSCREEN_MSG2638)	+= msg2638.o
 obj-$(CONFIG_TOUCHSCREEN_MTOUCH)	+= mtouch.o
 obj-$(CONFIG_TOUCHSCREEN_MK712)		+= mk712.o
+obj-$(CONFIG_TOUCHSCREEN_NOVATEK_NVT_TS)	+= novatek-nvt-ts.o
 obj-$(CONFIG_TOUCHSCREEN_HP600)		+= hp680_ts_input.o
 obj-$(CONFIG_TOUCHSCREEN_HP7XX)		+= jornada720_ts.o
 obj-$(CONFIG_TOUCHSCREEN_IPAQ_MICRO)	+= ipaq-micro-ts.o
diff --git a/drivers/input/touchscreen/novatek-nvt-ts.c b/drivers/input/touchscreen/novatek-nvt-ts.c
new file mode 100644
index 000000000000..2464c758ca14
--- /dev/null
+++ b/drivers/input/touchscreen/novatek-nvt-ts.c
@@ -0,0 +1,288 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for Novatek i2c touchscreen controller as found on
+ * the Acer Iconia One 7 B1-750 tablet. The Touchscreen controller
+ * model-number is unknown. Android calls this a "NVT-ts" touchscreen,
+ * but that may apply to other Novatek controller models too.
+ *
+ * Copyright (c) 2023 Hans de Goede <hdegoede@redhat.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/input/touchscreen.h>
+#include <linux/module.h>
+
+#include <asm/unaligned.h>
+
+#define NVT_TS_TOUCH_START		0x00
+#define NVT_TS_TOUCH_SIZE		6
+
+#define NVT_TS_PARAMETERS_START		0x78
+/* These are offsets from NVT_TS_PARAMETERS_START */
+#define NVT_TS_PARAMS_WIDTH		0x04
+#define NVT_TS_PARAMS_HEIGHT		0x06
+#define NVT_TS_PARAMS_MAX_TOUCH		0x09
+#define NVT_TS_PARAMS_MAX_BUTTONS	0x0a
+#define NVT_TS_PARAMS_IRQ_TYPE		0x0b
+#define NVT_TS_PARAMS_WAKE_TYPE		0x0c
+#define NVT_TS_PARAMS_CHIP_ID		0x0e
+#define NVT_TS_PARAMS_SIZE		0x0f
+
+#define NVT_TS_SUPPORTED_WAKE_TYPE	0x05
+#define NVT_TS_SUPPORTED_CHIP_ID	0x05
+
+#define NVT_TS_MAX_TOUCHES		10
+#define NVT_TS_MAX_SIZE			4096
+
+#define NVT_TS_TOUCH_NEW		1
+#define NVT_TS_TOUCH_UPDATE		2
+#define NVT_TS_TOUCH_RELEASE		3
+
+static const int nvt_ts_irq_type[4] = {
+	IRQF_TRIGGER_RISING,
+	IRQF_TRIGGER_FALLING,
+	IRQF_TRIGGER_LOW,
+	IRQF_TRIGGER_HIGH
+};
+
+struct nvt_ts_data {
+	struct i2c_client *client;
+	struct input_dev *input;
+	struct gpio_desc *reset_gpio;
+	struct touchscreen_properties prop;
+	int max_touches;
+	u8 buf[NVT_TS_TOUCH_SIZE * NVT_TS_MAX_TOUCHES];
+};
+
+static int nvt_ts_read_data(struct i2c_client *client, u8 reg, u8 *data, int count)
+{
+	struct i2c_msg msg[2] = {
+		{
+			.addr = client->addr,
+			.len = 1,
+			.buf = &reg
+		},
+		{
+			.addr = client->addr,
+			.flags = I2C_M_RD,
+			.len = count,
+			.buf = data,
+		}
+	};
+	int ret;
+
+	ret = i2c_transfer(client->adapter, msg, 2);
+	if (ret != 2) {
+		dev_err(&client->dev, "Error reading from 0x%02x: %d\n", reg, ret);
+		return (ret < 0) ? ret : -EIO;
+	}
+
+	return 0;
+}
+
+static irqreturn_t nvt_ts_irq(int irq, void *dev_id)
+{
+	struct nvt_ts_data *data = dev_id;
+	struct device *dev = &data->client->dev;
+	int i, ret, slot, x, y;
+	bool active;
+	u8 *touch;
+
+	ret = nvt_ts_read_data(data->client, NVT_TS_TOUCH_START, data->buf,
+			       data->max_touches * NVT_TS_TOUCH_SIZE);
+	if (ret)
+		return IRQ_HANDLED;
+
+	for (i = 0; i < data->max_touches; i++) {
+		touch = &data->buf[i * NVT_TS_TOUCH_SIZE];
+
+		/* 0xff means no touch */
+		if (touch[0] == 0xff)
+			continue;
+
+		slot = touch[0] >> 3;
+		if (slot < 1 || slot > data->max_touches) {
+			dev_warn(dev, "slot %d out of range, ignoring\n", slot);
+			continue;
+		}
+
+		switch (touch[0] & 7) {
+		case NVT_TS_TOUCH_NEW:
+		case NVT_TS_TOUCH_UPDATE:
+			active = true;
+			break;
+		case NVT_TS_TOUCH_RELEASE:
+			active = false;
+			break;
+		default:
+			dev_warn(dev, "slot %d unknown state %d\n", slot, touch[0] & 7);
+			continue;
+		}
+
+		slot--;
+		x = (touch[1] << 4) | (touch[3] >> 4);
+		y = (touch[2] << 4) | (touch[3] & 0x0f);
+
+		input_mt_slot(data->input, slot);
+		input_mt_report_slot_state(data->input, MT_TOOL_FINGER, active);
+		touchscreen_report_pos(data->input, &data->prop, x, y, true);
+	}
+
+	input_mt_sync_frame(data->input);
+	input_sync(data->input);
+
+	return IRQ_HANDLED;
+}
+
+static int nvt_ts_start(struct input_dev *dev)
+{
+	struct nvt_ts_data *data = input_get_drvdata(dev);
+
+	enable_irq(data->client->irq);
+	gpiod_set_value_cansleep(data->reset_gpio, 0);
+
+	return 0;
+}
+
+static void nvt_ts_stop(struct input_dev *dev)
+{
+	struct nvt_ts_data *data = input_get_drvdata(dev);
+
+	disable_irq(data->client->irq);
+	gpiod_set_value_cansleep(data->reset_gpio, 1);
+}
+
+static int nvt_ts_suspend(struct device *dev)
+{
+	struct nvt_ts_data *data = i2c_get_clientdata(to_i2c_client(dev));
+
+	mutex_lock(&data->input->mutex);
+	if (input_device_enabled(data->input))
+		nvt_ts_stop(data->input);
+	mutex_unlock(&data->input->mutex);
+
+	return 0;
+}
+
+static int nvt_ts_resume(struct device *dev)
+{
+	struct nvt_ts_data *data = i2c_get_clientdata(to_i2c_client(dev));
+
+	mutex_lock(&data->input->mutex);
+	if (input_device_enabled(data->input))
+		nvt_ts_start(data->input);
+	mutex_unlock(&data->input->mutex);
+
+	return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(nvt_ts_pm_ops, nvt_ts_suspend, nvt_ts_resume);
+
+static int nvt_ts_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	int ret, width, height, irq_type;
+	struct nvt_ts_data *data;
+	struct input_dev *input;
+
+	if (!client->irq) {
+		dev_err(dev, "Error no irq specified\n");
+		return -EINVAL;
+	}
+
+	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->client = client;
+	i2c_set_clientdata(client, data);
+
+	data->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
+	if (IS_ERR(data->reset_gpio))
+		return dev_err_probe(dev, PTR_ERR(data->reset_gpio), "requesting reset GPIO\n");
+
+	/* Wait for controller to come out of reset before params read */
+	msleep(100);
+	ret = nvt_ts_read_data(data->client, NVT_TS_PARAMETERS_START, data->buf,
+			       NVT_TS_PARAMS_SIZE);
+	gpiod_set_value_cansleep(data->reset_gpio, 1); /* Put back in reset */
+	if (ret)
+		return ret;
+
+	width  = get_unaligned_be16(&data->buf[NVT_TS_PARAMS_WIDTH]);
+	height = get_unaligned_be16(&data->buf[NVT_TS_PARAMS_HEIGHT]);
+	data->max_touches = data->buf[NVT_TS_PARAMS_MAX_TOUCH];
+	irq_type = data->buf[NVT_TS_PARAMS_IRQ_TYPE];
+
+	if (width > NVT_TS_MAX_SIZE || height >= NVT_TS_MAX_SIZE ||
+	    data->max_touches > NVT_TS_MAX_TOUCHES ||
+	    irq_type >= ARRAY_SIZE(nvt_ts_irq_type) ||
+	    data->buf[NVT_TS_PARAMS_WAKE_TYPE] != NVT_TS_SUPPORTED_WAKE_TYPE ||
+	    data->buf[NVT_TS_PARAMS_CHIP_ID] != NVT_TS_SUPPORTED_CHIP_ID) {
+		dev_err(dev, "Unsupported touchscreen parameters: %*ph\n",
+			NVT_TS_PARAMS_SIZE, data->buf);
+		return -EIO;
+	}
+
+	dev_info(dev, "Detected %dx%d touchscreen with %d max touches\n",
+		 width, height, data->max_touches);
+
+	if (data->buf[NVT_TS_PARAMS_MAX_BUTTONS])
+		dev_warn(dev, "Touchscreen buttons are not supported\n");
+
+	input = devm_input_allocate_device(dev);
+	if (!input)
+		return -ENOMEM;
+
+	input->name = client->name;
+	input->id.bustype = BUS_I2C;
+	input->open = nvt_ts_start;
+	input->close = nvt_ts_stop;
+	input->dev.parent = dev;
+
+	input_set_abs_params(input, ABS_MT_POSITION_X, 0, width - 1, 0, 0);
+	input_set_abs_params(input, ABS_MT_POSITION_Y, 0, height - 1, 0, 0);
+	touchscreen_parse_properties(input, true, &data->prop);
+
+	ret = input_mt_init_slots(input, data->max_touches,
+				  INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
+	if (ret)
+		return ret;
+
+	data->input = input;
+	input_set_drvdata(input, data);
+
+	ret = devm_request_threaded_irq(dev, client->irq, NULL, nvt_ts_irq,
+					IRQF_ONESHOT | IRQF_NO_AUTOEN | nvt_ts_irq_type[irq_type],
+					client->name, data);
+	if (ret)
+		return dev_err_probe(dev, ret, "requesting irq\n");
+
+	return input_register_device(input);
+}
+
+static const struct i2c_device_id nvt_ts_i2c_id[] = {
+	{ "NVT-ts" },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, nvt_ts_i2c_id);
+
+static struct i2c_driver nvt_ts_driver = {
+	.driver = {
+		.name	= "novatek-nvt-ts",
+		.pm	= &nvt_ts_pm_ops,
+	},
+	.probe_new = nvt_ts_probe,
+	.id_table = nvt_ts_i2c_id,
+};
+
+module_i2c_driver(nvt_ts_driver);
+
+MODULE_DESCRIPTION("Novatek NVT-ts touchscreen driver");
+MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
+MODULE_LICENSE("GPL");
-- 
2.39.1


^ permalink raw reply related

* Re: [PATCH v3 2/2] HID: hid-apple-magic-backlight: Add driver for keyboard backlight on internal Magic Keyboards
From: Thomas Weißschuh @ 2023-02-17 14:13 UTC (permalink / raw)
  To: Orlando Chamberlain
  Cc: linux-doc, linux-input, Jonathan Corbet, Jiri Kosina,
	Benjamin Tissoires, linux-kernel, Pavel Machek, Aditya Garg,
	Aun-Ali Zaidi, Kerem Karabay, Andy Shevchenko
In-Reply-To: <20230217102319.3419-3-orlandoch.dev@gmail.com>

On Fri, Feb 17, 2023 at 09:23:20PM +1100, Orlando Chamberlain wrote:
> This driver adds support for the keyboard backlight on Intel T2 Macs
> with internal Magic Keyboards (MacBookPro16,x and MacBookAir9,1)

Reviewed-by: Thomas Weißschuh <linux@weissschuh.net>

One other tiny nitpick below, if it goes to v4.

> Co-developed-by: Kerem Karabay <kekrby@gmail.com>
> Signed-off-by: Kerem Karabay <kekrby@gmail.com>
> Signed-off-by: Orlando Chamberlain <orlandoch.dev@gmail.com>
> ---
> v2->v3:
> - remove unneeded inclusion
> - use s32 for report value type
> - remove unneeded null check
> - don't set drvdata as its never used
> - prepend "hid-" to module name
> v1->v2:
> - drop unneeded remove function
> - combine set functions
> - add missing header inclusions
> - avoid char as argument in favour of u8
> - handful of style/formatting fixes
> - use standard led name ":white:kbd_backlight"
> - rename USAGE_MAGIC_BL to HID_USAGE_MAGIC_BL
>  MAINTAINERS                             |   6 ++
>  drivers/hid/Kconfig                     |  13 +++
>  drivers/hid/Makefile                    |   1 +
>  drivers/hid/hid-apple-magic-backlight.c | 122 ++++++++++++++++++++++++
>  4 files changed, 142 insertions(+)
>  create mode 100644 drivers/hid/hid-apple-magic-backlight.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index fb1471cb5ed3..3319f0c3ed1e 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -9201,6 +9201,12 @@ F:	include/linux/pm.h
>  F:	include/linux/suspend.h
>  F:	kernel/power/
>  
> +HID APPLE MAGIC BACKLIGHT DRIVER
> +M:	Orlando Chamberlain <orlandoch.dev@gmail.com>
> +L:	linux-input@vger.kernel.org
> +S:	Maintained
> +F:	drivers/hid/apple-magic-backlight.c
> +
>  HID CORE LAYER
>  M:	Jiri Kosina <jikos@kernel.org>
>  M:	Benjamin Tissoires <benjamin.tissoires@redhat.com>
> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
> index e2a5d30c8895..fe489632bfd9 100644
> --- a/drivers/hid/Kconfig
> +++ b/drivers/hid/Kconfig
> @@ -130,6 +130,19 @@ config HID_APPLE
>  	Say Y here if you want support for keyboards of	Apple iBooks, PowerBooks,
>  	MacBooks, MacBook Pros and Apple Aluminum.
>  
> +config HID_APPLE_MAGIC_BACKLIGHT
> +	tristate "Apple Magic Keyboard Backlight"
> +	depends on USB_HID
> +	depends on LEDS_CLASS
> +	depends on NEW_LEDS
> +	help
> +	Say Y here if you want support for the keyboard backlight on Macs with
> +	the magic keyboard (MacBookPro16,x and MacBookAir9,1). Note that this
> +	driver is not for external magic keyboards.
> +
> +	To compile this driver as a module, choose M here: the
> +	module will be called hid-apple-magic-backlight.
> +
>  config HID_APPLEIR
>  	tristate "Apple infrared receiver"
>  	depends on (USB_HID)
> diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
> index e8014c1a2f8b..dc8df002bc86 100644
> --- a/drivers/hid/Makefile
> +++ b/drivers/hid/Makefile
> @@ -26,6 +26,7 @@ obj-$(CONFIG_HID_ACCUTOUCH)	+= hid-accutouch.o
>  obj-$(CONFIG_HID_ALPS)		+= hid-alps.o
>  obj-$(CONFIG_HID_ACRUX)		+= hid-axff.o
>  obj-$(CONFIG_HID_APPLE)		+= hid-apple.o
> +obj-$(CONFIG_HID_APPLE_MAGIC_BACKLIGHT)	+= hid-apple-magic-backlight.o
>  obj-$(CONFIG_HID_APPLEIR)	+= hid-appleir.o
>  obj-$(CONFIG_HID_CREATIVE_SB0540)	+= hid-creative-sb0540.o
>  obj-$(CONFIG_HID_ASUS)		+= hid-asus.o
> diff --git a/drivers/hid/hid-apple-magic-backlight.c b/drivers/hid/hid-apple-magic-backlight.c
> new file mode 100644
> index 000000000000..210af2849e78
> --- /dev/null
> +++ b/drivers/hid/hid-apple-magic-backlight.c
> @@ -0,0 +1,122 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Apple Magic Keyboard Backlight Driver
> + *
> + * For Intel Macs with internal Magic Keyboard (MacBookPro16,1-4 and MacBookAir9,1)
> + *
> + * Copyright (c) 2022 Kerem Karabay <kekrby@gmail.com>
> + * Copyright (c) 2023 Orlando Chamberlain <orlandoch.dev@gmail.com>
> + */
> +
> +#include <linux/hid.h>
> +#include <linux/leds.h>
> +#include <linux/device.h>
> +#include <linux/errno.h>
> +#include <dt-bindings/leds/common.h>
> +
> +#include "hid-ids.h"
> +
> +#define HID_USAGE_MAGIC_BL	0xff00000f
> +
> +#define APPLE_MAGIC_REPORT_ID_POWER 3
> +#define APPLE_MAGIC_REPORT_ID_BRIGHTNESS 1
> +
> +struct apple_magic_backlight {
> +	struct led_classdev cdev;
> +	struct hid_device *hdev;

hdev is not used anymore.

> +	struct hid_report *brightness;
> +	struct hid_report *power;
> +};
> +
> +static void apple_magic_backlight_report_set(struct hid_report *rep, s32 value, u8 rate)
> +{
> +	rep->field[0]->value[0] = value;
> +	rep->field[1]->value[0] = 0x5e; /* Mimic Windows */
> +	rep->field[1]->value[0] |= rate << 8;
> +
> +	hid_hw_request(rep->device, rep, HID_REQ_SET_REPORT);
> +}
> +
> +static void apple_magic_backlight_set(struct apple_magic_backlight *backlight,
> +				     int brightness, char rate)
> +{
> +	apple_magic_backlight_report_set(backlight->power, brightness ? 1 : 0, rate);
> +	if (brightness)
> +		apple_magic_backlight_report_set(backlight->brightness, brightness, rate);
> +}
> +
> +static int apple_magic_backlight_led_set(struct led_classdev *led_cdev,
> +					 enum led_brightness brightness)
> +{
> +	struct apple_magic_backlight *backlight = container_of(led_cdev,
> +			struct apple_magic_backlight, cdev);
> +
> +	apple_magic_backlight_set(backlight, brightness, 1);
> +	return 0;
> +}
> +
> +static int apple_magic_backlight_probe(struct hid_device *hdev,
> +				       const struct hid_device_id *id)
> +{
> +	struct apple_magic_backlight *backlight;
> +	int rc;
> +
> +	rc = hid_parse(hdev);
> +	if (rc)
> +		return rc;
> +
> +	/*
> +	 * Ensure this usb endpoint is for the keyboard backlight, not touchbar
> +	 * backlight.
> +	 */
> +	if (hdev->collection[0].usage != HID_USAGE_MAGIC_BL)
> +		return -ENODEV;
> +
> +	backlight = devm_kzalloc(&hdev->dev, sizeof(*backlight), GFP_KERNEL);
> +	if (!backlight)
> +		return -ENOMEM;
> +
> +	rc = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
> +	if (rc)
> +		return rc;
> +
> +	backlight->brightness = hid_register_report(hdev, HID_FEATURE_REPORT,
> +			APPLE_MAGIC_REPORT_ID_BRIGHTNESS, 0);
> +	backlight->power = hid_register_report(hdev, HID_FEATURE_REPORT,
> +			APPLE_MAGIC_REPORT_ID_POWER, 0);
> +
> +	if (!backlight->brightness || !backlight->power) {
> +		rc = -ENODEV;
> +		goto hw_stop;
> +	}
> +
> +	backlight->hdev = hdev;
> +	backlight->cdev.name = ":white:" LED_FUNCTION_KBD_BACKLIGHT;
> +	backlight->cdev.max_brightness = backlight->brightness->field[0]->logical_maximum;
> +	backlight->cdev.brightness_set_blocking = apple_magic_backlight_led_set;
> +
> +	apple_magic_backlight_set(backlight, 0, 0);
> +
> +	return devm_led_classdev_register(&hdev->dev, &backlight->cdev);
> +
> +hw_stop:
> +	hid_hw_stop(hdev);
> +	return rc;
> +}
> +
> +static const struct hid_device_id apple_magic_backlight_hid_ids[] = {
> +	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_TOUCHBAR_BACKLIGHT) },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(hid, apple_magic_backlight_hid_ids);
> +
> +static struct hid_driver apple_magic_backlight_hid_driver = {
> +	.name = "hid-apple-magic-backlight",
> +	.id_table = apple_magic_backlight_hid_ids,
> +	.probe = apple_magic_backlight_probe,
> +};
> +module_hid_driver(apple_magic_backlight_hid_driver);
> +
> +MODULE_DESCRIPTION("MacBook Magic Keyboard Backlight");
> +MODULE_AUTHOR("Orlando Chamberlain <orlandoch.dev@gmail.com>");
> +MODULE_LICENSE("GPL");
> -- 
> 2.39.1
> 

^ permalink raw reply

* Re: [PATCH v3 2/2] HID: hid-apple-magic-backlight: Add driver for keyboard backlight on internal Magic Keyboards
From: Andy Shevchenko @ 2023-02-17 10:43 UTC (permalink / raw)
  To: Orlando Chamberlain
  Cc: linux-doc, linux-input, Jonathan Corbet, Jiri Kosina,
	Benjamin Tissoires, linux-kernel, Pavel Machek, Aditya Garg,
	Aun-Ali Zaidi, Kerem Karabay, Andy Shevchenko,
	Thomas Weißschuh
In-Reply-To: <20230217102319.3419-3-orlandoch.dev@gmail.com>

On Fri, Feb 17, 2023 at 12:24 PM Orlando Chamberlain
<orlandoch.dev@gmail.com> wrote:
>
> This driver adds support for the keyboard backlight on Intel T2 Macs
> with internal Magic Keyboards (MacBookPro16,x and MacBookAir9,1)

LGTM,
Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>

> Co-developed-by: Kerem Karabay <kekrby@gmail.com>
> Signed-off-by: Kerem Karabay <kekrby@gmail.com>
> Signed-off-by: Orlando Chamberlain <orlandoch.dev@gmail.com>
> ---
> v2->v3:
> - remove unneeded inclusion
> - use s32 for report value type
> - remove unneeded null check
> - don't set drvdata as its never used
> - prepend "hid-" to module name
> v1->v2:
> - drop unneeded remove function
> - combine set functions
> - add missing header inclusions
> - avoid char as argument in favour of u8
> - handful of style/formatting fixes
> - use standard led name ":white:kbd_backlight"
> - rename USAGE_MAGIC_BL to HID_USAGE_MAGIC_BL
>  MAINTAINERS                             |   6 ++
>  drivers/hid/Kconfig                     |  13 +++
>  drivers/hid/Makefile                    |   1 +
>  drivers/hid/hid-apple-magic-backlight.c | 122 ++++++++++++++++++++++++
>  4 files changed, 142 insertions(+)
>  create mode 100644 drivers/hid/hid-apple-magic-backlight.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index fb1471cb5ed3..3319f0c3ed1e 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -9201,6 +9201,12 @@ F:       include/linux/pm.h
>  F:     include/linux/suspend.h
>  F:     kernel/power/
>
> +HID APPLE MAGIC BACKLIGHT DRIVER
> +M:     Orlando Chamberlain <orlandoch.dev@gmail.com>
> +L:     linux-input@vger.kernel.org
> +S:     Maintained
> +F:     drivers/hid/apple-magic-backlight.c
> +
>  HID CORE LAYER
>  M:     Jiri Kosina <jikos@kernel.org>
>  M:     Benjamin Tissoires <benjamin.tissoires@redhat.com>
> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
> index e2a5d30c8895..fe489632bfd9 100644
> --- a/drivers/hid/Kconfig
> +++ b/drivers/hid/Kconfig
> @@ -130,6 +130,19 @@ config HID_APPLE
>         Say Y here if you want support for keyboards of Apple iBooks, PowerBooks,
>         MacBooks, MacBook Pros and Apple Aluminum.
>
> +config HID_APPLE_MAGIC_BACKLIGHT
> +       tristate "Apple Magic Keyboard Backlight"
> +       depends on USB_HID
> +       depends on LEDS_CLASS
> +       depends on NEW_LEDS
> +       help
> +       Say Y here if you want support for the keyboard backlight on Macs with
> +       the magic keyboard (MacBookPro16,x and MacBookAir9,1). Note that this
> +       driver is not for external magic keyboards.
> +
> +       To compile this driver as a module, choose M here: the
> +       module will be called hid-apple-magic-backlight.
> +
>  config HID_APPLEIR
>         tristate "Apple infrared receiver"
>         depends on (USB_HID)
> diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
> index e8014c1a2f8b..dc8df002bc86 100644
> --- a/drivers/hid/Makefile
> +++ b/drivers/hid/Makefile
> @@ -26,6 +26,7 @@ obj-$(CONFIG_HID_ACCUTOUCH)   += hid-accutouch.o
>  obj-$(CONFIG_HID_ALPS)         += hid-alps.o
>  obj-$(CONFIG_HID_ACRUX)                += hid-axff.o
>  obj-$(CONFIG_HID_APPLE)                += hid-apple.o
> +obj-$(CONFIG_HID_APPLE_MAGIC_BACKLIGHT)        += hid-apple-magic-backlight.o
>  obj-$(CONFIG_HID_APPLEIR)      += hid-appleir.o
>  obj-$(CONFIG_HID_CREATIVE_SB0540)      += hid-creative-sb0540.o
>  obj-$(CONFIG_HID_ASUS)         += hid-asus.o
> diff --git a/drivers/hid/hid-apple-magic-backlight.c b/drivers/hid/hid-apple-magic-backlight.c
> new file mode 100644
> index 000000000000..210af2849e78
> --- /dev/null
> +++ b/drivers/hid/hid-apple-magic-backlight.c
> @@ -0,0 +1,122 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Apple Magic Keyboard Backlight Driver
> + *
> + * For Intel Macs with internal Magic Keyboard (MacBookPro16,1-4 and MacBookAir9,1)
> + *
> + * Copyright (c) 2022 Kerem Karabay <kekrby@gmail.com>
> + * Copyright (c) 2023 Orlando Chamberlain <orlandoch.dev@gmail.com>
> + */
> +
> +#include <linux/hid.h>
> +#include <linux/leds.h>
> +#include <linux/device.h>
> +#include <linux/errno.h>
> +#include <dt-bindings/leds/common.h>
> +
> +#include "hid-ids.h"
> +
> +#define HID_USAGE_MAGIC_BL     0xff00000f
> +
> +#define APPLE_MAGIC_REPORT_ID_POWER 3
> +#define APPLE_MAGIC_REPORT_ID_BRIGHTNESS 1
> +
> +struct apple_magic_backlight {
> +       struct led_classdev cdev;
> +       struct hid_device *hdev;
> +       struct hid_report *brightness;
> +       struct hid_report *power;
> +};
> +
> +static void apple_magic_backlight_report_set(struct hid_report *rep, s32 value, u8 rate)
> +{
> +       rep->field[0]->value[0] = value;
> +       rep->field[1]->value[0] = 0x5e; /* Mimic Windows */
> +       rep->field[1]->value[0] |= rate << 8;
> +
> +       hid_hw_request(rep->device, rep, HID_REQ_SET_REPORT);
> +}
> +
> +static void apple_magic_backlight_set(struct apple_magic_backlight *backlight,
> +                                    int brightness, char rate)
> +{
> +       apple_magic_backlight_report_set(backlight->power, brightness ? 1 : 0, rate);
> +       if (brightness)
> +               apple_magic_backlight_report_set(backlight->brightness, brightness, rate);
> +}
> +
> +static int apple_magic_backlight_led_set(struct led_classdev *led_cdev,
> +                                        enum led_brightness brightness)
> +{
> +       struct apple_magic_backlight *backlight = container_of(led_cdev,
> +                       struct apple_magic_backlight, cdev);
> +
> +       apple_magic_backlight_set(backlight, brightness, 1);
> +       return 0;
> +}
> +
> +static int apple_magic_backlight_probe(struct hid_device *hdev,
> +                                      const struct hid_device_id *id)
> +{
> +       struct apple_magic_backlight *backlight;
> +       int rc;
> +
> +       rc = hid_parse(hdev);
> +       if (rc)
> +               return rc;
> +
> +       /*
> +        * Ensure this usb endpoint is for the keyboard backlight, not touchbar
> +        * backlight.
> +        */
> +       if (hdev->collection[0].usage != HID_USAGE_MAGIC_BL)
> +               return -ENODEV;
> +
> +       backlight = devm_kzalloc(&hdev->dev, sizeof(*backlight), GFP_KERNEL);
> +       if (!backlight)
> +               return -ENOMEM;
> +
> +       rc = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
> +       if (rc)
> +               return rc;
> +
> +       backlight->brightness = hid_register_report(hdev, HID_FEATURE_REPORT,
> +                       APPLE_MAGIC_REPORT_ID_BRIGHTNESS, 0);
> +       backlight->power = hid_register_report(hdev, HID_FEATURE_REPORT,
> +                       APPLE_MAGIC_REPORT_ID_POWER, 0);
> +
> +       if (!backlight->brightness || !backlight->power) {
> +               rc = -ENODEV;
> +               goto hw_stop;
> +       }
> +
> +       backlight->hdev = hdev;
> +       backlight->cdev.name = ":white:" LED_FUNCTION_KBD_BACKLIGHT;
> +       backlight->cdev.max_brightness = backlight->brightness->field[0]->logical_maximum;
> +       backlight->cdev.brightness_set_blocking = apple_magic_backlight_led_set;
> +
> +       apple_magic_backlight_set(backlight, 0, 0);
> +
> +       return devm_led_classdev_register(&hdev->dev, &backlight->cdev);
> +
> +hw_stop:
> +       hid_hw_stop(hdev);
> +       return rc;
> +}
> +
> +static const struct hid_device_id apple_magic_backlight_hid_ids[] = {
> +       { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_TOUCHBAR_BACKLIGHT) },
> +       { }
> +};
> +MODULE_DEVICE_TABLE(hid, apple_magic_backlight_hid_ids);
> +
> +static struct hid_driver apple_magic_backlight_hid_driver = {
> +       .name = "hid-apple-magic-backlight",
> +       .id_table = apple_magic_backlight_hid_ids,
> +       .probe = apple_magic_backlight_probe,
> +};
> +module_hid_driver(apple_magic_backlight_hid_driver);
> +
> +MODULE_DESCRIPTION("MacBook Magic Keyboard Backlight");
> +MODULE_AUTHOR("Orlando Chamberlain <orlandoch.dev@gmail.com>");
> +MODULE_LICENSE("GPL");
> --
> 2.39.1
>


-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply

* [PATCH v3 2/2] HID: hid-apple-magic-backlight: Add driver for keyboard backlight on internal Magic Keyboards
From: Orlando Chamberlain @ 2023-02-17 10:23 UTC (permalink / raw)
  To: linux-doc, linux-input
  Cc: Jonathan Corbet, Jiri Kosina, Benjamin Tissoires, linux-kernel,
	Pavel Machek, Aditya Garg, Aun-Ali Zaidi, Kerem Karabay,
	Andy Shevchenko, Thomas Weißschuh, Orlando Chamberlain
In-Reply-To: <20230217102319.3419-1-orlandoch.dev@gmail.com>

This driver adds support for the keyboard backlight on Intel T2 Macs
with internal Magic Keyboards (MacBookPro16,x and MacBookAir9,1)

Co-developed-by: Kerem Karabay <kekrby@gmail.com>
Signed-off-by: Kerem Karabay <kekrby@gmail.com>
Signed-off-by: Orlando Chamberlain <orlandoch.dev@gmail.com>
---
v2->v3:
- remove unneeded inclusion
- use s32 for report value type
- remove unneeded null check
- don't set drvdata as its never used
- prepend "hid-" to module name
v1->v2:
- drop unneeded remove function
- combine set functions
- add missing header inclusions
- avoid char as argument in favour of u8
- handful of style/formatting fixes
- use standard led name ":white:kbd_backlight"
- rename USAGE_MAGIC_BL to HID_USAGE_MAGIC_BL
 MAINTAINERS                             |   6 ++
 drivers/hid/Kconfig                     |  13 +++
 drivers/hid/Makefile                    |   1 +
 drivers/hid/hid-apple-magic-backlight.c | 122 ++++++++++++++++++++++++
 4 files changed, 142 insertions(+)
 create mode 100644 drivers/hid/hid-apple-magic-backlight.c

diff --git a/MAINTAINERS b/MAINTAINERS
index fb1471cb5ed3..3319f0c3ed1e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9201,6 +9201,12 @@ F:	include/linux/pm.h
 F:	include/linux/suspend.h
 F:	kernel/power/
 
+HID APPLE MAGIC BACKLIGHT DRIVER
+M:	Orlando Chamberlain <orlandoch.dev@gmail.com>
+L:	linux-input@vger.kernel.org
+S:	Maintained
+F:	drivers/hid/apple-magic-backlight.c
+
 HID CORE LAYER
 M:	Jiri Kosina <jikos@kernel.org>
 M:	Benjamin Tissoires <benjamin.tissoires@redhat.com>
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index e2a5d30c8895..fe489632bfd9 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -130,6 +130,19 @@ config HID_APPLE
 	Say Y here if you want support for keyboards of	Apple iBooks, PowerBooks,
 	MacBooks, MacBook Pros and Apple Aluminum.
 
+config HID_APPLE_MAGIC_BACKLIGHT
+	tristate "Apple Magic Keyboard Backlight"
+	depends on USB_HID
+	depends on LEDS_CLASS
+	depends on NEW_LEDS
+	help
+	Say Y here if you want support for the keyboard backlight on Macs with
+	the magic keyboard (MacBookPro16,x and MacBookAir9,1). Note that this
+	driver is not for external magic keyboards.
+
+	To compile this driver as a module, choose M here: the
+	module will be called hid-apple-magic-backlight.
+
 config HID_APPLEIR
 	tristate "Apple infrared receiver"
 	depends on (USB_HID)
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index e8014c1a2f8b..dc8df002bc86 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -26,6 +26,7 @@ obj-$(CONFIG_HID_ACCUTOUCH)	+= hid-accutouch.o
 obj-$(CONFIG_HID_ALPS)		+= hid-alps.o
 obj-$(CONFIG_HID_ACRUX)		+= hid-axff.o
 obj-$(CONFIG_HID_APPLE)		+= hid-apple.o
+obj-$(CONFIG_HID_APPLE_MAGIC_BACKLIGHT)	+= hid-apple-magic-backlight.o
 obj-$(CONFIG_HID_APPLEIR)	+= hid-appleir.o
 obj-$(CONFIG_HID_CREATIVE_SB0540)	+= hid-creative-sb0540.o
 obj-$(CONFIG_HID_ASUS)		+= hid-asus.o
diff --git a/drivers/hid/hid-apple-magic-backlight.c b/drivers/hid/hid-apple-magic-backlight.c
new file mode 100644
index 000000000000..210af2849e78
--- /dev/null
+++ b/drivers/hid/hid-apple-magic-backlight.c
@@ -0,0 +1,122 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Apple Magic Keyboard Backlight Driver
+ *
+ * For Intel Macs with internal Magic Keyboard (MacBookPro16,1-4 and MacBookAir9,1)
+ *
+ * Copyright (c) 2022 Kerem Karabay <kekrby@gmail.com>
+ * Copyright (c) 2023 Orlando Chamberlain <orlandoch.dev@gmail.com>
+ */
+
+#include <linux/hid.h>
+#include <linux/leds.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <dt-bindings/leds/common.h>
+
+#include "hid-ids.h"
+
+#define HID_USAGE_MAGIC_BL	0xff00000f
+
+#define APPLE_MAGIC_REPORT_ID_POWER 3
+#define APPLE_MAGIC_REPORT_ID_BRIGHTNESS 1
+
+struct apple_magic_backlight {
+	struct led_classdev cdev;
+	struct hid_device *hdev;
+	struct hid_report *brightness;
+	struct hid_report *power;
+};
+
+static void apple_magic_backlight_report_set(struct hid_report *rep, s32 value, u8 rate)
+{
+	rep->field[0]->value[0] = value;
+	rep->field[1]->value[0] = 0x5e; /* Mimic Windows */
+	rep->field[1]->value[0] |= rate << 8;
+
+	hid_hw_request(rep->device, rep, HID_REQ_SET_REPORT);
+}
+
+static void apple_magic_backlight_set(struct apple_magic_backlight *backlight,
+				     int brightness, char rate)
+{
+	apple_magic_backlight_report_set(backlight->power, brightness ? 1 : 0, rate);
+	if (brightness)
+		apple_magic_backlight_report_set(backlight->brightness, brightness, rate);
+}
+
+static int apple_magic_backlight_led_set(struct led_classdev *led_cdev,
+					 enum led_brightness brightness)
+{
+	struct apple_magic_backlight *backlight = container_of(led_cdev,
+			struct apple_magic_backlight, cdev);
+
+	apple_magic_backlight_set(backlight, brightness, 1);
+	return 0;
+}
+
+static int apple_magic_backlight_probe(struct hid_device *hdev,
+				       const struct hid_device_id *id)
+{
+	struct apple_magic_backlight *backlight;
+	int rc;
+
+	rc = hid_parse(hdev);
+	if (rc)
+		return rc;
+
+	/*
+	 * Ensure this usb endpoint is for the keyboard backlight, not touchbar
+	 * backlight.
+	 */
+	if (hdev->collection[0].usage != HID_USAGE_MAGIC_BL)
+		return -ENODEV;
+
+	backlight = devm_kzalloc(&hdev->dev, sizeof(*backlight), GFP_KERNEL);
+	if (!backlight)
+		return -ENOMEM;
+
+	rc = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+	if (rc)
+		return rc;
+
+	backlight->brightness = hid_register_report(hdev, HID_FEATURE_REPORT,
+			APPLE_MAGIC_REPORT_ID_BRIGHTNESS, 0);
+	backlight->power = hid_register_report(hdev, HID_FEATURE_REPORT,
+			APPLE_MAGIC_REPORT_ID_POWER, 0);
+
+	if (!backlight->brightness || !backlight->power) {
+		rc = -ENODEV;
+		goto hw_stop;
+	}
+
+	backlight->hdev = hdev;
+	backlight->cdev.name = ":white:" LED_FUNCTION_KBD_BACKLIGHT;
+	backlight->cdev.max_brightness = backlight->brightness->field[0]->logical_maximum;
+	backlight->cdev.brightness_set_blocking = apple_magic_backlight_led_set;
+
+	apple_magic_backlight_set(backlight, 0, 0);
+
+	return devm_led_classdev_register(&hdev->dev, &backlight->cdev);
+
+hw_stop:
+	hid_hw_stop(hdev);
+	return rc;
+}
+
+static const struct hid_device_id apple_magic_backlight_hid_ids[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_TOUCHBAR_BACKLIGHT) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, apple_magic_backlight_hid_ids);
+
+static struct hid_driver apple_magic_backlight_hid_driver = {
+	.name = "hid-apple-magic-backlight",
+	.id_table = apple_magic_backlight_hid_ids,
+	.probe = apple_magic_backlight_probe,
+};
+module_hid_driver(apple_magic_backlight_hid_driver);
+
+MODULE_DESCRIPTION("MacBook Magic Keyboard Backlight");
+MODULE_AUTHOR("Orlando Chamberlain <orlandoch.dev@gmail.com>");
+MODULE_LICENSE("GPL");
-- 
2.39.1


^ permalink raw reply related

* [PATCH v3 1/2] Documentation: leds: standardise keyboard backlight led names
From: Orlando Chamberlain @ 2023-02-17 10:23 UTC (permalink / raw)
  To: linux-doc, linux-input
  Cc: Jonathan Corbet, Jiri Kosina, Benjamin Tissoires, linux-kernel,
	Pavel Machek, Aditya Garg, Aun-Ali Zaidi, Kerem Karabay,
	Andy Shevchenko, Thomas Weißschuh, Orlando Chamberlain
In-Reply-To: <20230217102319.3419-1-orlandoch.dev@gmail.com>

Advice use of either "input*:*:kbd_backlight" or ":*:kbd_backlight". We
don't want people using vendor or product name (e.g. "smc", "apple",
"asus") as this information is available from sysfs anyway, and it made the
folder names inconsistent.

Signed-off-by: Orlando Chamberlain <orlandoch.dev@gmail.com>
---
 Documentation/leds/well-known-leds.txt | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/Documentation/leds/well-known-leds.txt b/Documentation/leds/well-known-leds.txt
index 2160382c86be..4e5429fce4d8 100644
--- a/Documentation/leds/well-known-leds.txt
+++ b/Documentation/leds/well-known-leds.txt
@@ -44,6 +44,14 @@ Legacy: "lp5523:kb{1,2,3,4,5,6}" (Nokia N900)
 
 Frontlight/backlight of main keyboard.
 
+Good: ":*:kbd_backlight"
+Good: "input*:*:kbd_backlight"
+Legacy: "*:*:kbd_backlight"
+
+Many drivers have the vendor or product name as the first field of the led name,
+this makes names inconsistent and is redundant as that information is already in
+sysfs.
+
 Legacy: "button-backlight" (Motorola Droid 4)
 
 Some phones have touch buttons below screen; it is different from main
-- 
2.39.1


^ permalink raw reply related

* [PATCH v3 0/2] Apple Magic Keyboard Backlight
From: Orlando Chamberlain @ 2023-02-17 10:23 UTC (permalink / raw)
  To: linux-doc, linux-input
  Cc: Jonathan Corbet, Jiri Kosina, Benjamin Tissoires, linux-kernel,
	Pavel Machek, Aditya Garg, Aun-Ali Zaidi, Kerem Karabay,
	Andy Shevchenko, Thomas Weißschuh, Orlando Chamberlain

This patchseries adds support for the internal keyboard backlight of
Macs with Apple's "Magic" keyboard (MacBookPro16,* and MacBookAir9,1),
and also documents what names should be used for keyboard backlight
leds in Documentation/leds/well-known-leds.txt.

v2->v3:
- remove unneeded header inclusion
- use s32 for report value type
- remove unneeded null check
- don't set drvdata as its never used
- prepend "hid-" to module name

v1->v2:
- drop unneeded remove function
- combine set functions
- add missing header inclusions
- avoid char as argument in favour of u8
- handful of style/formatting fixes
- use standard led name ":white:kbd_backlight"
- rename USAGE_MAGIC_BL to HID_USAGE_MAGIC_BL
- New patch documenting preferred keyboard backlight names

v1: https://lore.kernel.org/linux-input/7D70F1FE-7F54-4D0A-8922-5466AA2AD364@live.com/
v2: https://lore.kernel.org/linux-input/20230216041224.4731-1-orlandoch.dev@gmail.com/

Orlando Chamberlain (2):
  Documentation: leds: standardise keyboard backlight led names
  HID: hid-apple-magic-backlight: Add driver for keyboard backlight on
    internal Magic Keyboards

 Documentation/leds/well-known-leds.txt  |   8 ++
 MAINTAINERS                             |   6 ++
 drivers/hid/Kconfig                     |  13 +++
 drivers/hid/Makefile                    |   1 +
 drivers/hid/hid-apple-magic-backlight.c | 122 ++++++++++++++++++++++++
 5 files changed, 150 insertions(+)
 create mode 100644 drivers/hid/hid-apple-magic-backlight.c

-- 
2.39.1


^ permalink raw reply

* [PATCH v2] Input: ili210x - Probe even if no resolution information
From: Marek Vasut @ 2023-02-17  2:52 UTC (permalink / raw)
  To: linux-input; +Cc: Marek Vasut, Dmitry Torokhov, Joe Hung, Luca Hsu

Probe the touch controller driver even if resolution information is not
available. This can happen e.g. in case the touch controller suffered a
failed firmware update and is stuck in bootloader mode.

Signed-off-by: Marek Vasut <marex@denx.de>
---
Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Cc: Joe Hung <joe_hung@ilitek.com>
Cc: Luca Hsu <luca_hsu@ilitek.com>
---
V2: Add dev_warn() in case resolution is invalid
---
 drivers/input/touchscreen/ili210x.c | 28 +++++++++++++++++++---------
 1 file changed, 19 insertions(+), 9 deletions(-)

diff --git a/drivers/input/touchscreen/ili210x.c b/drivers/input/touchscreen/ili210x.c
index 4897fafa4204d..d64b6d77d2e08 100644
--- a/drivers/input/touchscreen/ili210x.c
+++ b/drivers/input/touchscreen/ili210x.c
@@ -370,22 +370,33 @@ static int ili251x_firmware_update_resolution(struct device *dev)
 
 	/* The firmware update blob might have changed the resolution. */
 	error = priv->chip->read_reg(client, REG_PANEL_INFO, &rs, sizeof(rs));
-	if (error)
-		return error;
+	if (!error) {
+		resx = le16_to_cpup((__le16 *)rs);
+		resy = le16_to_cpup((__le16 *)(rs + 2));
 
-	resx = le16_to_cpup((__le16 *)rs);
-	resy = le16_to_cpup((__le16 *)(rs + 2));
+		/* The value reported by the firmware is invalid. */
+		if (!resx || resx == 0xffff || !resy || resy == 0xffff)
+			error = -EINVAL;
+	}
 
-	/* The value reported by the firmware is invalid. */
-	if (!resx || resx == 0xffff || !resy || resy == 0xffff)
-		return -EINVAL;
+	/*
+	 * In case of error, the firmware might be stuck in bootloader mode,
+	 * e.g. after a failed firmware update. Set maximum resolution, but
+	 * do not fail to probe, so the user can re-trigger the firmware
+	 * update and recover the touch controller.
+	 */
+	if (error) {
+		dev_warn(dev, "Invalid resolution reported by controller.\n");
+		resx = 16384;
+		resy = 16384;
+	}
 
 	input_abs_set_max(priv->input, ABS_X, resx - 1);
 	input_abs_set_max(priv->input, ABS_Y, resy - 1);
 	input_abs_set_max(priv->input, ABS_MT_POSITION_X, resx - 1);
 	input_abs_set_max(priv->input, ABS_MT_POSITION_Y, resy - 1);
 
-	return 0;
+	return error;
 }
 
 static ssize_t ili251x_firmware_update_firmware_version(struct device *dev)
@@ -980,7 +991,6 @@ static int ili210x_i2c_probe(struct i2c_client *client)
 	if (error) {
 		dev_err(dev, "Unable to cache firmware information, err: %d\n",
 			error);
-		return error;
 	}
 	touchscreen_parse_properties(input, true, &priv->prop);
 
-- 
2.39.1


^ permalink raw reply related

* [PATCH] Input: sun4i-lradc-keys - Use devm_platform_ioremap_resource()
From: ye.xingchen @ 2023-02-17  2:36 UTC (permalink / raw)
  To: dmitry.torokhov
  Cc: hdegoede, wens, jernej.skrabec, samuel, linux-input,
	linux-arm-kernel, linux-sunxi, linux-kernel

From: Ye Xingchen <ye.xingchen@zte.com.cn>

Convert platform_get_resource(), devm_ioremap_resource() to a single
call to Use devm_platform_ioremap_resource(), as this is exactly
what this function does.

Signed-off-by: Ye Xingchen <ye.xingchen@zte.com.cn>
---
 drivers/input/keyboard/sun4i-lradc-keys.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/drivers/input/keyboard/sun4i-lradc-keys.c b/drivers/input/keyboard/sun4i-lradc-keys.c
index 15c15c0958b0..95d927cc8b7e 100644
--- a/drivers/input/keyboard/sun4i-lradc-keys.c
+++ b/drivers/input/keyboard/sun4i-lradc-keys.c
@@ -307,8 +307,7 @@ static int sun4i_lradc_probe(struct platform_device *pdev)

 	input_set_drvdata(lradc->input, lradc);

-	lradc->base = devm_ioremap_resource(dev,
-			      platform_get_resource(pdev, IORESOURCE_MEM, 0));
+	lradc->base = devm_platform_ioremap_resource(pdev, 0);
 	if (IS_ERR(lradc->base))
 		return PTR_ERR(lradc->base);

-- 
2.25.1

^ permalink raw reply related

* Re: [PATCH] Input: ili210x - Probe even if no resolution information
From: Dmitry Torokhov @ 2023-02-16 22:44 UTC (permalink / raw)
  To: Marek Vasut; +Cc: linux-input, Joe Hung, Luca Hsu
In-Reply-To: <20220715155905.219391-1-marex@denx.de>

Hi Marek,

On Fri, Jul 15, 2022 at 05:59:05PM +0200, Marek Vasut wrote:
> Probe the touch controller driver even if resolution information is not
> available. This can happen e.g. in case the touch controller suffered a
> failed firmware update and is stuck in bootloader mode.
> 
> Signed-off-by: Marek Vasut <marex@denx.de>
> Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>
> Cc: Joe Hung <joe_hung@ilitek.com>
> Cc: Luca Hsu <luca_hsu@ilitek.com>
> ---
>  drivers/input/touchscreen/ili210x.c | 27 ++++++++++++++++++---------
>  1 file changed, 18 insertions(+), 9 deletions(-)
> 
> diff --git a/drivers/input/touchscreen/ili210x.c b/drivers/input/touchscreen/ili210x.c
> index e9bd36adbe47d..ad25081e301c6 100644
> --- a/drivers/input/touchscreen/ili210x.c
> +++ b/drivers/input/touchscreen/ili210x.c
> @@ -370,22 +370,32 @@ static int ili251x_firmware_update_resolution(struct device *dev)
>  
>  	/* The firmware update blob might have changed the resolution. */
>  	error = priv->chip->read_reg(client, REG_PANEL_INFO, &rs, sizeof(rs));
> -	if (error)
> -		return error;
> +	if (!error) {
> +		resx = le16_to_cpup((__le16 *)rs);
> +		resy = le16_to_cpup((__le16 *)(rs + 2));
>  
> -	resx = le16_to_cpup((__le16 *)rs);
> -	resy = le16_to_cpup((__le16 *)(rs + 2));
> +		/* The value reported by the firmware is invalid. */
> +		if (!resx || resx == 0xffff || !resy || resy == 0xffff)
> +			error = -EINVAL;
> +	}
>  
> -	/* The value reported by the firmware is invalid. */
> -	if (!resx || resx == 0xffff || !resy || resy == 0xffff)
> -		return -EINVAL;
> +	/*
> +	 * In case of error, the firmware might be stuck in bootloader mode,
> +	 * e.g. after a failed firmware update. Set maximum resolution, but
> +	 * do not fail to probe, so the user can re-trigger the firmware
> +	 * update and recover the touch controller.
> +	 */
> +	if (error) {

Do you want to maybe dev_warn() here to explain the weird values?

> +		resx = 16384;
> +		resy = 16384;
> +	}

Thanks.

-- 
Dmitry

^ permalink raw reply

* Re: [PATCH v5 3/3] HID: cp2112: Fwnode Support
From: Andy Shevchenko @ 2023-02-16 21:00 UTC (permalink / raw)
  To: Daniel Kaehn
  Cc: robh+dt, krzysztof.kozlowski+dt, jikos, benjamin.tissoires,
	bartosz.golaszewski, dmitry.torokhov, devicetree, linux-input,
	ethan.twardy
In-Reply-To: <CAP+ZCCcC6hVxMqZCR3qcRXAcUkQp_B=DF4PVy--ngLwmPY-UjQ@mail.gmail.com>

On Thu, Feb 16, 2023 at 01:02:40PM -0600, Daniel Kaehn wrote:
> On Sat, Feb 11, 2023 at 6:10 AM Andy Shevchenko
> <andriy.shevchenko@linux.intel.com> wrote:
> > On Fri, Feb 10, 2023 at 04:36:38PM -0600, Danny Kaehn wrote:

...

> > > +     dev->gc.fwnode                  = device_get_named_child_node(&hdev->dev, "gpio");
> >
> > Using like this bumps a reference count IIRC, so one need to drop it after use.
> > But please double check this.
> >
> 
> Thanks for bringing this up -- I should have explicitly called this
> out as something I was looking for feedback on, as I was unsure on
> this.
> 
> I noticed that many of the users of device_get_named_child_node didn't
> explicitly call fwnode_handle_put, and was unsure about the mechanics
> of when this is needed.
> 
> The underlying call to device_get_named_child_node for an of_node is
> of_fwnode_get_named_child_node, which does call
> for_each_available_child_of_node and returns from within the loop, so
> I _think_ you're right that the return will have its refcount
> incremented (of_get_next_available_child calls of_node_get on the next
> node, and doesn't call put until the next iteration).
> 
> However, I also noticed that many other functions in
> drivers/base/property.c contain a message like the following in their
> header comment:
> "The caller is responsible for calling fwnode_handle_put() for the
> returned node."
> and this isn't present for device_get_named_child_node, which is
> defined in that same file, which made me suspicious that this is
> somehow done elsewhere internally (although I should know better than
> to trust documenting comments :) ).

Good catch!

> I'll wait a while longer to see if someone with a better grasp than me
> on dynamic DT/firmware weighs in, otherwise, I'll assume I'll need to
> call fwnode_handle_put both on error paths in _probe as well as in
> _remove, since that appeared to be the case with the DT-specific
> of_get_child_by_name path.

Patch to update the kernel documentation has been just sent.

-- 
With Best Regards,
Andy Shevchenko



^ permalink raw reply

* Re: [PATCH v5 3/3] HID: cp2112: Fwnode Support
From: Daniel Kaehn @ 2023-02-16 19:02 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: robh+dt, krzysztof.kozlowski+dt, jikos, benjamin.tissoires,
	bartosz.golaszewski, dmitry.torokhov, devicetree, linux-input,
	ethan.twardy
In-Reply-To: <Y+eFhKw5KcPUNyXy@smile.fi.intel.com>

Hi Andy,

On Sat, Feb 11, 2023 at 6:10 AM Andy Shevchenko
<andriy.shevchenko@linux.intel.com> wrote:
>
> On Fri, Feb 10, 2023 at 04:36:38PM -0600, Danny Kaehn wrote:
> > Bind i2c and gpio interfaces to subnodes with names
> > "i2c" and "gpio" if they exist, respectively. This
> > allows the gpio and i2c controllers to be described
> > in firmware as usual. Additionally, support configuring the
> > i2c bus speed from the clock-frequency device property.
>
> Entire series (code-wise, w/o DT bindings, not an expert there) looks good to
> me, but one thing to address.
>
> ...
>
> > +     dev->gc.fwnode                  = device_get_named_child_node(&hdev->dev, "gpio");
>
> Using like this bumps a reference count IIRC, so one need to drop it after use.
> But please double check this.
>

Thanks for bringing this up -- I should have explicitly called this
out as something I was looking for feedback on, as I was unsure on
this.

I noticed that many of the users of device_get_named_child_node didn't
explicitly call fwnode_handle_put, and was unsure about the mechanics
of when this is needed.

The underlying call to device_get_named_child_node for an of_node is
of_fwnode_get_named_child_node, which does call
for_each_available_child_of_node and returns from within the loop, so
I _think_ you're right that the return will have its refcount
incremented (of_get_next_available_child calls of_node_get on the next
node, and doesn't call put until the next iteration).

However, I also noticed that many other functions in
drivers/base/property.c contain a message like the following in their
header comment:
"The caller is responsible for calling fwnode_handle_put() for the
returned node."
and this isn't present for device_get_named_child_node, which is
defined in that same file, which made me suspicious that this is
somehow done elsewhere internally (although I should know better than
to trust documenting comments :) ).

I'll wait a while longer to see if someone with a better grasp than me
on dynamic DT/firmware weighs in, otherwise, I'll assume I'll need to
call fwnode_handle_put both on error paths in _probe as well as in
_remove, since that appeared to be the case with the DT-specific
of_get_child_by_name path.

Thanks,

Danny Kaehn

> --
> With Best Regards,
> Andy Shevchenko
>
>

^ permalink raw reply

* Re: Linux 6.1 and 6.2-rc make mousewheel on Logitech G903 (046d:c091) report too many non-hires events
From: Linux regression tracking #update (Thorsten Leemhuis) @ 2023-02-16 14:53 UTC (permalink / raw)
  To: Salvatore Bonaccorso, Tobias Klausmann; +Cc: linux-input, regressions
In-Reply-To: <01fb3b37-8ba2-70f9-df62-4cf8df6bcc97@leemhuis.info>

[TLDR: This mail in primarily relevant for Linux kernel regression
tracking. See link in footer if these mails annoy you.]

On 28.01.23 17:28, Linux kernel regression tracking (Thorsten Leemhuis)
wrote:
> On 28.01.23 16:39, Salvatore Bonaccorso wrote:
>> On Wed, Jan 25, 2023 at 11:01:00PM +0100, Tobias Klausmann wrote:
>>> Hi!
>>>
>>> As it says in the subject.
>>>
>>> At some point between 6.0 and 6.1, the kernel (if HID_LOGITECH_HIDPP was
>>> m or y) started reporting a full event for every hires event on a
>>> Logitech G903. 

Just noticed that regzbot missed noticing the fix for this thread:

#regzbot fix: 690eb7dec72ae52d
#regzbot ignore-activity

Ciao, Thorsten (wearing his 'the Linux kernel's regression tracker' hat)
--
Everything you wanna know about Linux kernel regression tracking:
https://linux-regtracking.leemhuis.info/about/#tldr
That page also explains what to do if mails like this annoy you.

#regzbot ignore-activity

^ permalink raw reply

* Re: [PATCH 2/2] HID: apple-magic-backlight: Add driver for keyboard backlight on internal Magic Keyboards
From: Andy Shevchenko @ 2023-02-16 11:51 UTC (permalink / raw)
  To: Thomas Weißschuh
  Cc: Orlando Chamberlain, linux-input, Jonathan Corbet, Jiri Kosina,
	Benjamin Tissoires, linux-doc, linux-kernel, Pavel Machek,
	Aditya Garg, Aun-Ali Zaidi, Kerem Karabay, Andy Shevchenko
In-Reply-To: <20230216054105.nmtft5ma4hiuqwib@t-8ch.de>

On Thu, Feb 16, 2023 at 7:41 AM Thomas Weißschuh <thomas@t-8ch.de> wrote:
> On Thu, Feb 16, 2023 at 03:12:28PM +1100, Orlando Chamberlain wrote:

...

> Note: Only your cover letter has the "v2" prefix.
> Normally git format-patch should apply this properly to all patches when
> using --reroll-count.

There is an option -v<X>, where <X> is a version applied to all
patches in the lot.

-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply

* Re: [PATCH v4] HID: add KEY_CAMERA_FOCUS event in HID
From: Greg KH @ 2023-02-16 11:50 UTC (permalink / raw)
  To: qi feng; +Cc: jikos, benjamin.tissoires, linux-input, linux-kernel, fengqi
In-Reply-To: <CACOZ=ZUFZ7YvB+uRWthECf-xKpZkbG4XE7Lhh5gWsLFN9TY+AA@mail.gmail.com>


A: http://en.wikipedia.org/wiki/Top_post
Q: Were do I find info about this thing called top-posting?
A: Because it messes up the order in which people normally read text.
Q: Why is top-posting such a bad thing?
A: Top-posting.
Q: What is the most annoying thing in e-mail?

A: No.
Q: Should I include quotations after my reply?


http://daringfireball.net/2007/07/on_top

On Thu, Feb 16, 2023 at 07:37:51PM +0800, qi feng wrote:
> Is there something wrong with this, I can correct it

Nothing wrong at all, look at the bottom of the email where I wrote:

> Greg KH <gregkh@linuxfoundation.org> 于2023年2月16日周四 18:18写道:
> >
> > Reviewed-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>




^ permalink raw reply

* Re: [PATCH 2/2] HID: apple-magic-backlight: Add driver for keyboard backlight on internal Magic Keyboards
From: Andy Shevchenko @ 2023-02-16 11:49 UTC (permalink / raw)
  To: Orlando Chamberlain
  Cc: linux-input, Jonathan Corbet, Jiri Kosina, Benjamin Tissoires,
	linux-doc, linux-kernel, Pavel Machek, Aditya Garg, Aun-Ali Zaidi,
	Kerem Karabay, Andy Shevchenko, Thomas Weißschuh
In-Reply-To: <20230216181050.52a31247@redecorated-mbp>

On Thu, Feb 16, 2023 at 10:15 AM Orlando Chamberlain
<orlandoch.dev@gmail.com> wrote:
> On Thu, 16 Feb 2023 15:12:28 +1100
> Orlando Chamberlain <orlandoch.dev@gmail.com> wrote:

...

> >  drivers/hid/apple-magic-backlight.c | 125
>
> A general question to the hid/input folks:
>
> Is it alright that this doesn't start with "hid-"? All the other
> drivers start with that so I'm not sure if its an issue.

What makes apple so special?

> If it is, then
> I can rename it to "hid-apple-magic-backlight".

If there is a good justification of not doing that, put it into the
commit message.

-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply

* Re: [PATCH v4] HID: add KEY_CAMERA_FOCUS event in HID
From: qi feng @ 2023-02-16 11:37 UTC (permalink / raw)
  To: Greg KH; +Cc: jikos, benjamin.tissoires, linux-input, linux-kernel, fengqi
In-Reply-To: <Y+4C6srdFygrWsLr@kroah.com>

Is there something wrong with this, I can correct it

Greg KH <gregkh@linuxfoundation.org> 于2023年2月16日周四 18:18写道:
>
> On Thu, Feb 16, 2023 at 04:48:30PM +0800, Qi Feng wrote:
> > From: fengqi <fengqi@xiaomi.com>
> >
> > Our HID device need KEY_CAMERA_FOCUS event to control camera,
> > but this event is non-existent in current HID driver.
> > So we add this event in hid-input.c.
> >
> > Signed-off-by: fengqi <fengqi@xiaomi.com>
> >
> > ---
> > changes in v4:
> >
> > -add HID_UP_CAMERA in HID usage tables , Then add the mapping under HID_UP_CAMERA
> > -modify the commit log of patch
> > -Link to v3:https://lore.kernel.org/linux-input/9a85b268c7636ef2e4e3bbbe318561ba2842a591.1676536357.git.fengqi@xiaomi.com/T/#u
> > -Link to v2:https://lore.kernel.org/linux-input/CACOZ=ZU0zgRmoRu8X5bMUzUrXA9x-qoDJqrQroUs=+qKR58MQA@mail.gmail.com/T/#t
> > -Link to v1:https://lore.kernel.org/linux-input/CACOZ=ZWB3grJKn7wAZEZ0BDyN7KJF4VWUTNs-mPxeoW_oiR7=g@mail.gmail.com/T/#t
> > ---
> >  drivers/hid/hid-input.c | 10 ++++++++++
> >  include/linux/hid.h     |  1 +
> >  2 files changed, 11 insertions(+)
> >
> > diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c
> > index 77c8c49852b5..c6098ae2fac7 100644
> > --- a/drivers/hid/hid-input.c
> > +++ b/drivers/hid/hid-input.c
> > @@ -1225,6 +1225,16 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
> >                       return;
> >               }
> >               goto unknown;
> > +     case HID_UP_CAMERA:
> > +             switch (usage->hid & HID_USAGE) {
> > +             case 0x020:
> > +                     map_key_clear(KEY_CAMERA_FOCUS);        break;
> > +             case 0x021:
> > +                     map_key_clear(KEY_CAMERA);              break;
> > +             default:
> > +                     goto ignore;
> > +             }
> > +             break;
> >
> >       case HID_UP_HPVENDOR:   /* Reported on a Dutch layout HP5308 */
> >               set_bit(EV_REP, input->evbit);
> > diff --git a/include/linux/hid.h b/include/linux/hid.h
> > index 8677ae38599e..88793b77bd63 100644
> > --- a/include/linux/hid.h
> > +++ b/include/linux/hid.h
> > @@ -155,6 +155,7 @@ struct hid_item {
> >  #define HID_UP_DIGITIZER     0x000d0000
> >  #define HID_UP_PID           0x000f0000
> >  #define HID_UP_BATTERY               0x00850000
> > +#define HID_UP_CAMERA                0x00900000
> >  #define HID_UP_HPVENDOR         0xff7f0000
> >  #define HID_UP_HPVENDOR2        0xff010000
> >  #define HID_UP_MSVENDOR              0xff000000
> > --
> > 2.39.0
> >
>
> Reviewed-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

^ permalink raw reply

* Re: [PATCH] hid: bigben_probe(): validate report count
From: Benjamin Tissoires @ 2023-02-16 11:33 UTC (permalink / raw)
  To: Jiri Kosina, Hanno Zulla, Pietro Borrello
  Cc: Jiri Kosina, linux-input, linux-kernel
In-Reply-To: <20230211-bigben-oob-v1-1-d2849688594c@diag.uniroma1.it>


On Sun, 12 Feb 2023 00:01:44 +0000, Pietro Borrello wrote:
> bigben_probe() does not validate that the output report has the
> needed report values in the first field.
> A malicious device registering a report with one field and a single
> value causes an head OOB write in bigben_worker() when
> accessing report_field->value[1] to report_field->value[7].
> Use hid_validate_values() which takes care of all the needed checks.
> 
> [...]

Applied, thanks!

[1/1] hid: bigben_probe(): validate report count
      commit: b94335f899542a0da5fafc38af8edcaf90195843

Best regards,
-- 
Benjamin Tissoires <benjamin.tissoires@redhat.com>


^ permalink raw reply

* [PATCH v2] HID: mcp-2221: prevent UAF in delayed work
From: Benjamin Tissoires @ 2023-02-16 10:22 UTC (permalink / raw)
  To: Rishi Gupta, Jiri Kosina, Pietro Borrello
  Cc: linux-input, linux-kernel, Benjamin Tissoires

If the device is plugged/unplugged without giving time for mcp_init_work()
to complete, we might kick in the devm free code path and thus have
unavailable struct mcp_2221 while in delayed work.

Canceling the delayed_work item is enough to solve the issue, because
cancel_delayed_work_sync will prevent the work item to requeue itself.

Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
---
Similar to Pietro's series, we can see the pattern in hid-mcp2221,
except that this time the ledclass is not involved.

Link: https://lore.kernel.org/linux-input/20230125-hid-unregister-leds-v4-5-7860c5763c38@diag.uniroma1.it/
---
Changes in v2:
- drop the spinlock/boolean
- Link to v1: https://lore.kernel.org/r/20230215-wip-mcp2221-v1-1-d7d1da261a5c@redhat.com
---
 drivers/hid/hid-mcp2221.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/drivers/hid/hid-mcp2221.c b/drivers/hid/hid-mcp2221.c
index e61dd039354b..f74a977cf8f8 100644
--- a/drivers/hid/hid-mcp2221.c
+++ b/drivers/hid/hid-mcp2221.c
@@ -922,6 +922,9 @@ static void mcp2221_hid_unregister(void *ptr)
 /* This is needed to be sure hid_hw_stop() isn't called twice by the subsystem */
 static void mcp2221_remove(struct hid_device *hdev)
 {
+	struct mcp2221 *mcp = hid_get_drvdata(hdev);
+
+	cancel_delayed_work_sync(&mcp->init_work);
 }
 
 #if IS_REACHABLE(CONFIG_IIO)

---
base-commit: d883fd110dc17308a1506c5bf17e00ce9fe7b2a2
change-id: 20230215-wip-mcp2221-979d4115efb5

Best regards,
-- 
Benjamin Tissoires <benjamin.tissoires@redhat.com>


^ permalink raw reply related

* Re: [PATCH v4] HID: add KEY_CAMERA_FOCUS event in HID
From: Greg KH @ 2023-02-16 10:18 UTC (permalink / raw)
  To: Qi Feng; +Cc: jikos, benjamin.tissoires, linux-input, linux-kernel, fengqi
In-Reply-To: <3f8627d20de711d08b8cafe0a11481a2b9ca941e.1676537236.git.fengqi@xiaomi.com>

On Thu, Feb 16, 2023 at 04:48:30PM +0800, Qi Feng wrote:
> From: fengqi <fengqi@xiaomi.com>
> 
> Our HID device need KEY_CAMERA_FOCUS event to control camera,
> but this event is non-existent in current HID driver.
> So we add this event in hid-input.c.
> 
> Signed-off-by: fengqi <fengqi@xiaomi.com>
> 
> ---
> changes in v4:
> 
> -add HID_UP_CAMERA in HID usage tables , Then add the mapping under HID_UP_CAMERA
> -modify the commit log of patch
> -Link to v3:https://lore.kernel.org/linux-input/9a85b268c7636ef2e4e3bbbe318561ba2842a591.1676536357.git.fengqi@xiaomi.com/T/#u
> -Link to v2:https://lore.kernel.org/linux-input/CACOZ=ZU0zgRmoRu8X5bMUzUrXA9x-qoDJqrQroUs=+qKR58MQA@mail.gmail.com/T/#t
> -Link to v1:https://lore.kernel.org/linux-input/CACOZ=ZWB3grJKn7wAZEZ0BDyN7KJF4VWUTNs-mPxeoW_oiR7=g@mail.gmail.com/T/#t
> ---
>  drivers/hid/hid-input.c | 10 ++++++++++
>  include/linux/hid.h     |  1 +
>  2 files changed, 11 insertions(+)
> 
> diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c
> index 77c8c49852b5..c6098ae2fac7 100644
> --- a/drivers/hid/hid-input.c
> +++ b/drivers/hid/hid-input.c
> @@ -1225,6 +1225,16 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
>  			return;
>  		}
>  		goto unknown;
> +	case HID_UP_CAMERA:
> +		switch (usage->hid & HID_USAGE) {
> +		case 0x020:
> +			map_key_clear(KEY_CAMERA_FOCUS);	break;
> +		case 0x021:
> +			map_key_clear(KEY_CAMERA);		break;
> +		default:
> +			goto ignore;
> +		}
> +		break;
>  
>  	case HID_UP_HPVENDOR:	/* Reported on a Dutch layout HP5308 */
>  		set_bit(EV_REP, input->evbit);
> diff --git a/include/linux/hid.h b/include/linux/hid.h
> index 8677ae38599e..88793b77bd63 100644
> --- a/include/linux/hid.h
> +++ b/include/linux/hid.h
> @@ -155,6 +155,7 @@ struct hid_item {
>  #define HID_UP_DIGITIZER	0x000d0000
>  #define HID_UP_PID		0x000f0000
>  #define HID_UP_BATTERY		0x00850000
> +#define HID_UP_CAMERA		0x00900000
>  #define HID_UP_HPVENDOR         0xff7f0000
>  #define HID_UP_HPVENDOR2        0xff010000
>  #define HID_UP_MSVENDOR		0xff000000
> -- 
> 2.39.0
> 

Reviewed-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

^ permalink raw reply

* Re: [PATCH v4] HID: add KEY_CAMERA_FOCUS event in HID
From: Greg KH @ 2023-02-16 10:15 UTC (permalink / raw)
  To: qi feng; +Cc: jikos, benjamin.tissoires, linux-input, linux-kernel, fengqi
In-Reply-To: <CACOZ=ZXN7Jnug_vhWexFvxknZEwaRM9BHXEo8O20q=sEKecNLQ@mail.gmail.com>

On Thu, Feb 16, 2023 at 05:26:16PM +0800, qi feng wrote:
> loop more

????

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox