Re: [PATCH] HID: multitouch: Fix Yoga Book 9 14IAH10 touchscreen misclassification

From: Benjamin Tissoires

Date: Fri Apr 03 2026 - 13:04:05 EST


Hi Dave,

[not sure why your message doesn't appear on lore, so using the
@redhat.com email.]

On Fri, Apr 3, 2026 at 5:17 PM Dave Carey <carvsdriver@xxxxxxxxx> wrote:
>
> Benjamin - thanks for the response, I'll admit that I'm not the smartest person in the room here.

I wouldn't say that. Given the research you showed in the repo below,
you are definitely smart :)

> My approach was trivially simple, it works in Windows flawlessly so I knew there had to be a way to make it work in Linux.

OK, so that's a hint we are not doing something correctly.

> I essentially ran USB log captures in Windows, then banged my face against the keyboard on the linux side until I could figure out what was going on there and implement the same behavior with the firmware. All of my research is in my repo here ...
>
> https://bitbucket.org/carvsdriver/lenovoyoga9ibook/src/main/README_Touch.md

Could you add a hid-recorder output when doing a simple touch on one
of the 2 panels? (pip3 install hid-tools).

I'm curious to understand the touchpad emulation part. As I read it,
does the device emit the event on both the touchscreen and the
emulated one simultaneously?

I guess you don't happen to have a compatible stylus? It would be
interesting to look at the events from a pen to understand the button
logic.

>
> Happy to get your input and take a swing at implementing this differently.

It looks like there are multiple problems with the touchscreens, and I
need the big picture. The nice thing is you already wrote a HID-BPF
program for it, so why not contribute it upstream to udev-hid-bpf.
I'll eventually push it into the kernel as well and that would help
people who did not updated their kernel yet.

Cheers,
Benjamin

>
> -Dave
>
> On Fri, Apr 3, 2026 at 9:03 AM Benjamin Tissoires <bentiss@xxxxxxxxxx> wrote:
>>
>> Hi Dave,
>>
>> On Apr 02 2026, Dave Carey wrote:
>> > The Lenovo Yoga Book 9 14IAH10 (83KJ) uses a composite USB HID device
>> > (17EF:6161) where three descriptor quirks combine to cause hid-multitouch
>> > to incorrectly set INPUT_PROP_BUTTONPAD on both touchscreen nodes, making
>> > libinput treat them as indirect clickpads rather than direct touchscreens.
>> >
>> > Quirk 1: The HID_DG_TOUCHSCREEN application collection contains
>> > HID_UP_BUTTON usages (stylus barrel buttons). The generic heuristic in
>> > mt_touch_input_mapping() treats any touchscreen-with-buttons as a
>> > touchpad, setting INPUT_MT_POINTER.
>> >
>> > Quirk 2: A HID_DG_TOUCHPAD collection ("Emulated Touchpad") sets
>> > INPUT_MT_POINTER unconditionally in mt_allocate_application().
>> >
>> > Quirk 3: The HID_DG_BUTTONTYPE feature report (0x51) returns
>> > MT_BUTTONTYPE_CLICKPAD, directly setting td->is_buttonpad = true.
>> >
>> > These combine to produce INPUT_PROP_BUTTONPAD on the touchscreen input
>> > nodes. libinput treats the devices as indirect clickpads and suppresses
>> > direct touch events, leaving the touchscreens non-functional under
>> > KDE/Wayland.
>>
>> This looks like a completely borked report descriptor. Out of curiosity,
>> do you know if there is a specific Windows driver for it or if it's
>> using the plain generic driver there.
>>
>> The reasoning is that if it's using the generic win driver, we are
>> probably doing something wrong, and we need to fix it in a more generic
>> way.
>>
>> >
>> > Additionally, the firmware resets if any USB control request is received
>> > during the CDC ACM initialization window. The existing GET_REPORT call
>> > in mt_check_input_mode() during probe triggers this reset.
>>
>> Ouch, even better :(
>>
>> >
>> > Fix by extending MT_QUIRK_YOGABOOK9I (already defined for the earlier
>> > Yoga Book 9i) to guard all three BUTTONPAD heuristics and skip the
>> > HID_DG_BUTTONTYPE GET_REPORT during probe for this device.
>>
>> Really not a big fan of the approach taken here: We are sprinkling the
>> code with special quirks for one particular device and that makes
>> everything worse.
>>
>> I would much prefer a report descriptor fixup where:
>> - we drop the HID_UP_BUTTON
>> - we drop the HID_DG_TOUCHPAD collection entirely
>> - we drop the HID_DG_BUTTONTYPE feature entirely
>> - we drop the Win8 blob feature as well to prevent queries during
>> initialization.
>>
>> For ease of development I would recomend working with a separate HID-BPF
>> program instead of a in-kernel fix, but we already have a .report_fixup
>> here, so I wouldn't mind having the fix here as well.
>>
>> Cheers,
>> Benjamin
>>
>> >
>> > Signed-off-by: Dave Carey <carvsdriver@xxxxxxxxx>
>> > Tested-by: Dave Carey <carvsdriver@xxxxxxxxx>
>> > ---
>> > drivers/hid/hid-multitouch.c | 34 +++++++++++++++++++++++++++-------
>> > 1 file changed, 27 insertions(+), 7 deletions(-)
>> >
>> > diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c
>> > index e82a3c4e5..1bef32b1d 100644
>> > --- a/drivers/hid/hid-multitouch.c
>> > +++ b/drivers/hid/hid-multitouch.c
>> > @@ -549,7 +549,14 @@ static void mt_feature_mapping(struct hid_device *hdev,
>> >
>> > switch (usage->hid) {
>> > case HID_DG_CONTACTMAX:
>> > - mt_get_feature(hdev, field->report);
>> > + /*
>> > + * Yoga Book 9: skip GET_REPORT during probe; the firmware
>> > + * resets if it receives any control request before the init
>> > + * Output report is sent (within ~1.18s of USB enumeration).
>> > + * Logical maximum from the descriptor is used as the fallback.
>> > + */
>> > + if (!(td->mtclass.quirks & MT_QUIRK_YOGABOOK9I))
>> > + mt_get_feature(hdev, field->report);
>> >
>> > td->maxcontacts = field->value[0];
>> > if (!td->maxcontacts &&
>> > @@ -566,6 +573,10 @@ static void mt_feature_mapping(struct hid_device *hdev,
>> > break;
>> > }
>> >
>> > + /* Yoga Book 9 reports Clickpad but is a direct touchscreen */
>> > + if (td->mtclass.quirks & MT_QUIRK_YOGABOOK9I)
>> > + break;
>> > +
>> > mt_get_feature(hdev, field->report);
>> > switch (field->value[usage->usage_index]) {
>> > case MT_BUTTONTYPE_CLICKPAD:
>> > @@ -579,7 +590,9 @@ static void mt_feature_mapping(struct hid_device *hdev,
>> > break;
>> > case 0xff0000c5:
>> > /* Retrieve the Win8 blob once to enable some devices */
>> > - if (usage->usage_index == 0)
>> > + /* Yoga Book 9: skip; firmware resets before init if queried */
>> > + if (usage->usage_index == 0 &&
>> > + !(td->mtclass.quirks & MT_QUIRK_YOGABOOK9I))
>> > mt_get_feature(hdev, field->report);
>> > break;
>> > }
>> > @@ -644,8 +657,11 @@ static struct mt_application *mt_allocate_application(struct mt_device *td,
>> >
>> > /*
>> > * Model touchscreens providing buttons as touchpads.
>> > + * Yoga Book 9 has an emulated touchpad but its touch surfaces
>> > + * are direct screens, not indirect pointers.
>> > */
>> > - if (application == HID_DG_TOUCHPAD) {
>> > + if (application == HID_DG_TOUCHPAD &&
>> > + !(td->mtclass.quirks & MT_QUIRK_YOGABOOK9I)) {
>> > mt_application->mt_flags |= INPUT_MT_POINTER;
>> > td->inputmode_value = MT_INPUTMODE_TOUCHPAD;
>> > }
>> > @@ -802,11 +818,15 @@ static int mt_touch_input_mapping(struct hid_device *hdev, struct hid_input *hi,
>> >
>> > /*
>> > * Model touchscreens providing buttons as touchpads.
>> > + * Skip for Yoga Book 9 which has stylus buttons inside
>> > + * touchscreen collections, not physical touchpad buttons.
>> > */
>> > if (field->application == HID_DG_TOUCHSCREEN &&
>> > (usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON) {
>> > - app->mt_flags |= INPUT_MT_POINTER;
>> > - td->inputmode_value = MT_INPUTMODE_TOUCHPAD;
>> > + if (!(app->quirks & MT_QUIRK_YOGABOOK9I)) {
>> > + app->mt_flags |= INPUT_MT_POINTER;
>> > + td->inputmode_value = MT_INPUTMODE_TOUCHPAD;
>> > + }
>> > }
>> >
>> > /* count the buttons on touchpads */
>> > @@ -1420,7 +1440,6 @@ static int mt_touch_input_configured(struct hid_device *hdev,
>> > */
>> > if (cls->quirks & MT_QUIRK_APPLE_TOUCHBAR)
>> > app->mt_flags |= INPUT_MT_DIRECT;
>> > -
>> > if (cls->is_indirect)
>> > app->mt_flags |= INPUT_MT_POINTER;
>> >
>> > @@ -1432,7 +1451,8 @@ static int mt_touch_input_configured(struct hid_device *hdev,
>> >
>> > /* check for clickpads */
>> > if ((app->mt_flags & INPUT_MT_POINTER) &&
>> > - (app->buttons_count == 1))
>> > + (app->buttons_count == 1) &&
>> > + !(app->quirks & MT_QUIRK_YOGABOOK9I))
>> > td->is_buttonpad = true;
>> >
>> > if (td->is_buttonpad)
>> > --
>> > 2.53.0
>> >
>> >