]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
USB: cdc-acm: Add quirks for Yoga Book 9 14IAH10 INGENIC touchscreen
authorDave Carey <carvsdriver@gmail.com>
Thu, 2 Apr 2026 18:29:50 +0000 (14:29 -0400)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Tue, 7 Apr 2026 11:47:37 +0000 (13:47 +0200)
The Lenovo Yoga Book 9 14IAH10 (83KJ) has a composite USB device
(17EF:6161) that controls both touchscreens via a CDC ACM interface.
Interface 0 is a standard CDC ACM control interface, but interface 1
(the data interface) incorrectly declares vendor-specific class (0xFF)
instead of USB_CLASS_CDC_DATA. cdc-acm rejects the device at probe with
-EINVAL, leaving interface 0 unbound and EP 0x82 never polled.

With no consumer polling EP 0x82, the firmware's watchdog fires every
~20 seconds and resets the USB bus, producing a continuous disconnect/
reconnect loop that prevents the touchscreens from ever initialising.

Add two new quirk flags:

VENDOR_CLASS_DATA_IFACE: Bypasses the bInterfaceClass check in
acm_probe() that would otherwise reject the vendor-class data
interface with -EINVAL.

ALWAYS_POLL_CTRL: Submits the notification URB at probe() rather than
waiting for a TTY open. This keeps EP 0x82 polled at all times,
permanently suppressing the firmware watchdog. The URB is resubmitted
after port_shutdown() and on system resume. SET_CONTROL_LINE_STATE
(DTR|RTS) is sent at probe and after port_shutdown() to complete
firmware handshake.

Note: the firmware performs exactly 4 USB connect/disconnect cycles
(~19 s each) on every cold boot before stabilising. This is a fixed
firmware property; touch is available ~75-80 s after power-on.

Signed-off-by: Dave Carey <carvsdriver@gmail.com>
Cc: stable <stable@kernel.org>
Tested-by: Dave Carey <carvsdriver@gmail.com>
Acked-by: Oliver Neukum <oneukum@suse.com>
Link: https://patch.msgid.link/20260402182950.389016-1-carvsdriver@gmail.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/class/cdc-acm.c

index cf3c3eede1a51f08d160f51423a0c3708a92185e..54059e4fc6ed72dc2dcff35fb59fc9c45ef13bde 100644 (file)
@@ -114,6 +114,8 @@ static int acm_ctrl_msg(struct acm *acm, int request, int value,
        int retval;
 
        retval = usb_autopm_get_interface(acm->control);
+#define VENDOR_CLASS_DATA_IFACE                BIT(9)  /* data interface uses vendor-specific class */
+#define ALWAYS_POLL_CTRL               BIT(10) /* keep ctrl URB active even without an open TTY */
        if (retval)
                return retval;
 
@@ -710,12 +712,14 @@ static int acm_port_activate(struct tty_port *port, struct tty_struct *tty)
        set_bit(TTY_NO_WRITE_SPLIT, &tty->flags);
        acm->control->needs_remote_wakeup = 1;
 
-       acm->ctrlurb->dev = acm->dev;
-       retval = usb_submit_urb(acm->ctrlurb, GFP_KERNEL);
-       if (retval) {
-               dev_err(&acm->control->dev,
-                       "%s - usb_submit_urb(ctrl irq) failed\n", __func__);
-               goto error_submit_urb;
+       if (!(acm->quirks & ALWAYS_POLL_CTRL)) {
+               acm->ctrlurb->dev = acm->dev;
+               retval = usb_submit_urb(acm->ctrlurb, GFP_KERNEL);
+               if (retval) {
+                       dev_err(&acm->control->dev,
+                               "%s - usb_submit_urb(ctrl irq) failed\n", __func__);
+                       goto error_submit_urb;
+               }
        }
 
        acm_tty_set_termios(tty, NULL);
@@ -788,6 +792,14 @@ static void acm_port_shutdown(struct tty_port *port)
 
        acm_unpoison_urbs(acm);
 
+       if (acm->quirks & ALWAYS_POLL_CTRL) {
+               acm->ctrlurb->dev = acm->dev;
+               if (usb_submit_urb(acm->ctrlurb, GFP_KERNEL))
+                       dev_dbg(&acm->control->dev,
+                               "ctrl polling restart failed after port close\n");
+               /* port_shutdown() cleared DTR/RTS; restore them */
+               acm_set_control(acm, USB_CDC_CTRL_DTR | USB_CDC_CTRL_RTS);
+       }
 }
 
 static void acm_tty_cleanup(struct tty_struct *tty)
@@ -1328,6 +1340,9 @@ skip_normal_probe:
                        dev_dbg(&intf->dev,
                                "Your device has switched interfaces.\n");
                        swap(control_interface, data_interface);
+               } else if (quirks & VENDOR_CLASS_DATA_IFACE) {
+                       dev_dbg(&intf->dev,
+                               "Vendor-specific data interface class, continuing.\n");
                } else {
                        return -EINVAL;
                }
@@ -1522,6 +1537,9 @@ skip_countries:
        acm->line.bDataBits = 8;
        acm_set_line(acm, &acm->line);
 
+       if (quirks & ALWAYS_POLL_CTRL)
+               acm_set_control(acm, USB_CDC_CTRL_DTR | USB_CDC_CTRL_RTS);
+
        if (!acm->combined_interfaces) {
                rv = usb_driver_claim_interface(&acm_driver, data_interface, acm);
                if (rv)
@@ -1543,6 +1561,13 @@ skip_countries:
 
        dev_info(&intf->dev, "ttyACM%d: USB ACM device\n", minor);
 
+       if (acm->quirks & ALWAYS_POLL_CTRL) {
+               acm->ctrlurb->dev = acm->dev;
+               if (usb_submit_urb(acm->ctrlurb, GFP_KERNEL))
+                       dev_warn(&intf->dev,
+                                "failed to start persistent ctrl polling\n");
+       }
+
        return 0;
 
 err_release_data_interface:
@@ -1669,7 +1694,7 @@ static int acm_resume(struct usb_interface *intf)
 
        acm_unpoison_urbs(acm);
 
-       if (tty_port_initialized(&acm->port)) {
+       if (tty_port_initialized(&acm->port) || (acm->quirks & ALWAYS_POLL_CTRL)) {
                rv = usb_submit_urb(acm->ctrlurb, GFP_ATOMIC);
 
                for (;;) {
@@ -2016,6 +2041,20 @@ static const struct usb_device_id acm_ids[] = {
        /* CH343 supports CAP_BRK, but doesn't advertise it */
        { USB_DEVICE(0x1a86, 0x55d3), .driver_info = MISSING_CAP_BRK, },
 
+       /*
+        * Lenovo Yoga Book 9 14IAH10 (83KJ) — INGENIC 17EF:6161 touchscreen
+        * composite device.  The CDC ACM control interface (0) uses a standard
+        * Union descriptor, but the data interface (1) is declared as vendor-
+        * specific class (0xff) with no CDC data descriptors, so cdc-acm would
+        * normally reject it.  The firmware also requires continuous polling of
+        * the notification endpoint (EP 0x82) to suppress a 20-second watchdog
+        * reset; ALWAYS_POLL_CTRL keeps the ctrlurb active even when no TTY is
+        * open.  Match only the control interface by class to avoid probing the
+        * vendor-specific data interface.
+        */
+       { USB_DEVICE_INTERFACE_CLASS(0x17ef, 0x6161, USB_CLASS_COMM),
+         .driver_info = VENDOR_CLASS_DATA_IFACE | ALWAYS_POLL_CTRL },
+
        /* control interfaces without any protocol set */
        { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM,
                USB_CDC_PROTO_NONE) },