* [PATCH] HID: multitouch: Fix Yoga Book 9 14IAH10 touchscreen misclassification
@ 2026-04-02 18:29 Dave Carey
2026-04-03 13:02 ` Benjamin Tissoires
2026-04-13 12:58 ` [PATCH v2] " Dave Carey
0 siblings, 2 replies; 4+ messages in thread
From: Dave Carey @ 2026-04-02 18:29 UTC (permalink / raw)
To: jikos; +Cc: bentiss, linux-input, linux-kernel, Dave Carey
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.
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.
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.
Signed-off-by: Dave Carey <carvsdriver@gmail.com>
Tested-by: Dave Carey <carvsdriver@gmail.com>
---
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
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH] HID: multitouch: Fix Yoga Book 9 14IAH10 touchscreen misclassification
2026-04-02 18:29 [PATCH] HID: multitouch: Fix Yoga Book 9 14IAH10 touchscreen misclassification Dave Carey
@ 2026-04-03 13:02 ` Benjamin Tissoires
[not found] ` <CALPvROSB4y0UsPvF5-ZS=_rGmj1NgM6QvBAbHO13bkgpAwQSyA@mail.gmail.com>
2026-04-13 12:58 ` [PATCH v2] " Dave Carey
1 sibling, 1 reply; 4+ messages in thread
From: Benjamin Tissoires @ 2026-04-03 13:02 UTC (permalink / raw)
To: Dave Carey; +Cc: jikos, linux-input, linux-kernel
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@gmail.com>
> Tested-by: Dave Carey <carvsdriver@gmail.com>
> ---
> 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
>
>
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH] HID: multitouch: Fix Yoga Book 9 14IAH10 touchscreen misclassification
[not found] ` <CALPvROSB4y0UsPvF5-ZS=_rGmj1NgM6QvBAbHO13bkgpAwQSyA@mail.gmail.com>
@ 2026-04-03 16:51 ` Benjamin Tissoires
0 siblings, 0 replies; 4+ messages in thread
From: Benjamin Tissoires @ 2026-04-03 16:51 UTC (permalink / raw)
To: Dave Carey; +Cc: Benjamin Tissoires, jikos, linux-input, linux-kernel
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@gmail.com> 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@kernel.org> 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@gmail.com>
>> > Tested-by: Dave Carey <carvsdriver@gmail.com>
>> > ---
>> > 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
>> >
>> >
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH v2] HID: multitouch: Fix Yoga Book 9 14IAH10 touchscreen misclassification
2026-04-02 18:29 [PATCH] HID: multitouch: Fix Yoga Book 9 14IAH10 touchscreen misclassification Dave Carey
2026-04-03 13:02 ` Benjamin Tissoires
@ 2026-04-13 12:58 ` Dave Carey
1 sibling, 0 replies; 4+ messages in thread
From: Dave Carey @ 2026-04-13 12:58 UTC (permalink / raw)
To: linux-input; +Cc: jikos, bentiss, Dave Carey
The Lenovo Yoga Book 9 14IAH10 (83KJ) (17EF:6161) firmware includes a
HID_DG_TOUCHPAD application collection designed for the Windows inbox HID
driver's Win8 PTP touchpad mode. On Linux the HID_DG_TOUCHSCREEN
collections provide the correct direct-touch interface. The presence of
the touchpad collection causes hid-multitouch to misclassify the
touchscreen nodes as indirect buttonpads, leaving them non-functional.
Within the touchpad collection:
- HID_UP_BUTTON usages trigger the touchscreen-with-buttons heuristic
that sets INPUT_MT_POINTER on the touchscreen applications.
- The HID_DG_TOUCHPAD application itself sets INPUT_MT_POINTER via
mt_allocate_application(), propagating to all touchscreen nodes.
- A HID_DG_BUTTONTYPE feature (report 0x51) returns MT_BUTTONTYPE_CLICKPAD,
setting td->is_buttonpad = true for the entire device.
Additionally, the firmware resets if any USB control request arrives while
the CDC-ACM interface is initialising (~1.18 s after enumeration).
The Win8 compliance blob (0xff00:0xc5) and Contact Count Max feature
reports in the touchscreen collections trigger GET_REPORT calls at probe
that hit this window. Surface Switch (0x57) and Button Switch (0x58)
feature reports are sent by mt_set_modes() on every input-device open and
close, repeatedly hitting this window throughout device lifetime.
The firmware also leaves a persistent ghost contact in its contact buffer
(contact ID 2, fixed coordinates, tip always asserted) on every enumeration.
This ghost occupies a multitouch slot and prevents KWin from seeing a clean
finger-lift, causing stuck touch state. The ghost is cleared when Input
Mode is set via HID_REQ_SET_REPORT at probe.
Fix using a report descriptor fixup in mt_report_fixup() and a class
definition update:
1. Remove the entire HID_DG_TOUCHPAD application collection. Parsing
HID short items from its header to the matching End Collection and
closing the gap with memmove eliminates all three BUTTONPAD
heuristics and the feature reports within the collection.
2. Neutralize the Win8 compliance blob feature reports remaining in the
touchscreen collections by changing Usage Page 0xff00 to 0x0f00,
preventing the case 0xff0000c5 branch in mt_feature_mapping() from
issuing GET_REPORT.
3. Neutralize the Contact Count Max feature reports by changing usage
0x55 to 0x00; set maxcontacts = 10 in the class definition so the
driver uses the correct contact limit without querying the device.
4. Neutralize Surface Switch (0x57) and Button Switch (0x58) feature
report usages in the Device Configuration collection so mt_set_modes()
does not issue HID_REQ_SET_REPORT for these on every input-device
open/close. Input Mode (0x52) is intentionally left intact: the single
HID_REQ_SET_REPORT at probe flushes the firmware's contact buffer and
clears the persistent ghost contact. By probe time the cdc-acm driver
has already satisfied the CDC-ACM init watchdog (~130 ms), so this
request arrives safely after the reset window has closed.
5. Add MT_QUIRK_NOT_SEEN_MEANS_UP to the MT_CLS_YOGABOOK9I class so that
contacts not present in a frame are released via INPUT_MT_DROP_UNUSED,
preventing stale multitouch slots from lingering if the firmware omits
a contact from a report.
Signed-off-by: Dave Carey <carvsdriver@gmail.com>
Tested-by: Dave Carey <carvsdriver@gmail.com>
---
Re: Benjamin's question about the Windows driver — the device uses
Windows' generic inbox drivers: usbser.sys binds interface 0 (CDC ACM)
and the generic HID class driver handles the HID interfaces. The
HID_DG_TOUCHPAD collection is in the descriptor for Windows' PTP inbox
touchpad path, not for a custom driver. On Windows the system routes
input through the touchpad application; on Linux hid-multitouch sees
both the touchscreen and touchpad applications and gets confused by the
touchpad one. The descriptor fixup removes it from Linux's view.
Changes in v2:
- Replace per-callsite MT_QUIRK_YOGABOOK9I guards with a single
mt_yogabook9_fixup() function called from mt_report_fixup().
- Drop the HID_DG_TOUCHPAD application collection entirely via memmove,
eliminating all three BUTTONPAD heuristics at source rather than
suppressing their effects at each callsite.
- Neutralize Win8 compliance blob GET_REPORT triggers by changing
Usage Page 0xff00 to 0x0f00 in the descriptor.
- Neutralize Contact Count Max GET_REPORT trigger (usage 0x55 -> 0x00);
set maxcontacts = 10 in the class definition.
- Neutralize Surface Switch (0x57) and Button Switch (0x58) SET_REPORT
triggers; retain Input Mode (0x52) so the single probe-time SET_REPORT
flushes the firmware contact buffer and clears a persistent ghost contact.
- Add MT_QUIRK_NOT_SEEN_MEANS_UP to the MT_CLS_YOGABOOK9I class.
drivers/hid/hid-multitouch.c | 146 ++++++++++++++++++++++++++++++++++-
1 file changed, 145 insertions(+), 1 deletion(-)
diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c
index e82a3c4e5..ec04dbafb 100644
--- a/drivers/hid/hid-multitouch.c
+++ b/drivers/hid/hid-multitouch.c
@@ -443,11 +443,13 @@ static const struct mt_class mt_classes[] = {
MT_QUIRK_CONTACT_CNT_ACCURATE,
},
{ .name = MT_CLS_YOGABOOK9I,
- .quirks = MT_QUIRK_ALWAYS_VALID |
+ .quirks = MT_QUIRK_NOT_SEEN_MEANS_UP |
+ MT_QUIRK_ALWAYS_VALID |
MT_QUIRK_FORCE_MULTI_INPUT |
MT_QUIRK_SEPARATE_APP_REPORT |
MT_QUIRK_HOVERING |
MT_QUIRK_YOGABOOK9I,
+ .maxcontacts = 10,
.export_all_inputs = true
},
{ .name = MT_CLS_EGALAX_P80H84,
@@ -1566,6 +1568,144 @@ static int mt_event(struct hid_device *hid, struct hid_field *field,
return 0;
}
+/*
+ * Yoga Book 9 14IAH10 descriptor fixup.
+ *
+ * The device includes a HID_DG_TOUCHPAD application collection designed for
+ * the Windows inbox HID driver's Win8 PTP touchpad mode. On Linux we want
+ * only the HID_DG_TOUCHSCREEN collections. The touchpad collection (and the
+ * HID_DG_BUTTONTYPE and Win8 compliance blob features it contains) must be
+ * removed so hid-multitouch does not misclassify the touchscreen nodes as
+ * indirect buttonpads.
+ *
+ * The firmware also resets if any USB control request is received while the
+ * CDC-ACM interface is initialising (~1.18 s after enumeration). Dropping
+ * the Win8 blob and Contact Count Max feature reports prevents the
+ * GET_REPORT calls that hid-multitouch issues at probe.
+ */
+static void mt_yogabook9_fixup(struct hid_device *hdev, __u8 *rdesc,
+ unsigned int *size)
+{
+ /* Usage Page (Digitizer), Usage (Touch Pad), Collection (Application) */
+ static const __u8 tp_app_hdr[] = { 0x05, 0x0d, 0x09, 0x05, 0xa1, 0x01 };
+ /* Vendor Usage Page 0xff00 (Win8 compliance blob header) */
+ static const __u8 win8_page[] = { 0x06, 0x00, 0xff };
+ /* Usage (Contact Count Max = 0x55) */
+ static const __u8 ccmax_usage[] = { 0x09, 0x55 };
+ unsigned int i;
+
+ /*
+ * Step 1: find and remove the Touch Pad application collection.
+ * Walk HID short items from the collection header to its matching
+ * End Collection, then close the gap with memmove.
+ */
+ for (i = 0; i + sizeof(tp_app_hdr) <= *size; i++) {
+ if (memcmp(rdesc + i, tp_app_hdr, sizeof(tp_app_hdr)) == 0) {
+ __u8 *start = rdesc + i;
+ __u8 *coll_end = NULL;
+ __u8 *p = start;
+ unsigned int drop;
+ int depth = 0;
+
+ while (p < rdesc + *size) {
+ __u8 b = *p;
+ int ds = b & 3;
+ int item_len;
+
+ if (b == 0xfe) { /* long item */
+ if (p + 2 >= rdesc + *size)
+ break;
+ item_len = p[1] + 3;
+ } else {
+ item_len = (ds == 3) ? 5 : ds + 1;
+ }
+ if (p + item_len > rdesc + *size)
+ break;
+
+ if ((b & 0xfc) == 0xa0)
+ depth++; /* Collection */
+ else if (b == 0xc0) {
+ depth--; /* End Collection */
+ if (depth == 0) {
+ coll_end = p;
+ break;
+ }
+ }
+ p += item_len;
+ }
+
+ if (!coll_end) {
+ hid_err(hdev,
+ "Yoga Book 9: Touch Pad End Collection not found\n");
+ break;
+ }
+
+ drop = coll_end - start + 1;
+ memmove(start, coll_end + 1, rdesc + *size - coll_end - 1);
+ *size -= drop;
+ hid_dbg(hdev,
+ "Yoga Book 9: dropped Touch Pad collection (%u bytes)\n",
+ drop);
+ break;
+ }
+ }
+
+ /*
+ * Step 2: neutralize Win8 compliance blob feature reports remaining
+ * in the touchscreen collections. Change Usage Page 0xff00 to 0x0f00
+ * so the case 0xff0000c5 branch in mt_feature_mapping() is not reached
+ * and no GET_REPORT is issued.
+ */
+ for (i = 0; i + sizeof(win8_page) <= *size; i++) {
+ if (memcmp(rdesc + i, win8_page, sizeof(win8_page)) == 0) {
+ rdesc[i + 2] = 0x0f; /* 0xff00 -> 0x0f00 */
+ hid_dbg(hdev,
+ "Yoga Book 9: neutralized Win8 blob at offset %u\n",
+ i);
+ }
+ }
+
+ /*
+ * Step 3: neutralize Contact Count Max feature reports. Change usage
+ * 0x55 (HID_DG_CONTACTMAX) to 0x00 so mt_feature_mapping() does not
+ * issue GET_REPORT. The class maxcontacts field provides the value.
+ */
+ for (i = 0; i + sizeof(ccmax_usage) <= *size; i++) {
+ if (memcmp(rdesc + i, ccmax_usage, sizeof(ccmax_usage)) == 0) {
+ rdesc[i + 1] = 0x00;
+ hid_dbg(hdev,
+ "Yoga Book 9: neutralized ContactMax at offset %u\n",
+ i);
+ }
+ }
+
+ /*
+ * Step 4: neutralize Surface Switch (0x57) and Button Switch (0x58)
+ * feature report usages in the Device Configuration collection.
+ * mt_set_modes() issues HID_REQ_SET_REPORT for these on every
+ * input-device open/close; those repeated control requests hit the
+ * firmware's CDC-ACM init window and trigger resets.
+ *
+ * Input Mode (0x52) is intentionally left intact. mt_set_modes()
+ * sends it once at probe to set the device into touchscreen mode,
+ * which flushes the firmware's contact buffer and clears a persistent
+ * ghost contact (cid 2, fixed coordinates) that otherwise appears on
+ * every enumeration. By probe time cdc_acm has already satisfied the
+ * CDC-ACM init watchdog (~130 ms), so the single SET_REPORT for Input
+ * Mode arrives safely after the reset window has closed.
+ */
+ for (i = 0; i + 2 <= *size; i++) {
+ if (rdesc[i] == 0x09 &&
+ (rdesc[i + 1] == 0x57 ||
+ rdesc[i + 1] == 0x58)) {
+ hid_dbg(hdev,
+ "Yoga Book 9: neutralized set-modes usage 0x%02x at offset %u\n",
+ rdesc[i + 1], i);
+ rdesc[i + 1] = 0x00;
+ }
+ }
+}
+
static const __u8 *mt_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *size)
{
@@ -1595,6 +1735,10 @@ got: %x\n",
}
}
+ if (hdev->vendor == USB_VENDOR_ID_LENOVO &&
+ hdev->product == USB_DEVICE_ID_LENOVO_YOGABOOK9I)
+ mt_yogabook9_fixup(hdev, rdesc, size);
+
return rdesc;
}
base-commit: 705c735d0ef7701cf9ded290545345a8c9b8bd7e
--
2.53.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-04-13 12:58 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-02 18:29 [PATCH] HID: multitouch: Fix Yoga Book 9 14IAH10 touchscreen misclassification Dave Carey
2026-04-03 13:02 ` Benjamin Tissoires
[not found] ` <CALPvROSB4y0UsPvF5-ZS=_rGmj1NgM6QvBAbHO13bkgpAwQSyA@mail.gmail.com>
2026-04-03 16:51 ` Benjamin Tissoires
2026-04-13 12:58 ` [PATCH v2] " Dave Carey
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox