]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
USB: serial: keyspan_pda: fix data loss on receive throttling
authorJohan Hovold <johan@kernel.org>
Wed, 8 Jul 2026 14:31:35 +0000 (16:31 +0200)
committerJohan Hovold <johan@kernel.org>
Mon, 13 Jul 2026 09:32:52 +0000 (11:32 +0200)
Killing the interrupt-in urb when the line disciple requests throttling
may lead to data loss if an ongoing transfer is cancelled.

Instead set a flag to prevent the completion handler from resubmitting
the urb until the port is unthrottled.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: stable@vger.kernel.org
Signed-off-by: Johan Hovold <johan@kernel.org>
drivers/usb/serial/keyspan_pda.c

index f05bcce6060029e52f72abf01aa6224f52b3e7fd..dd4cfd17f7adfc8583482415979b1bec783567f6 100644 (file)
@@ -33,6 +33,8 @@ struct keyspan_pda_private {
        struct work_struct      unthrottle_work;
        struct usb_serial       *serial;
        struct usb_serial_port  *port;
+       bool                    throttled;
+       bool                    throttle_req;
 };
 
 static int keyspan_pda_write_start(struct usb_serial_port *port);
@@ -148,6 +150,7 @@ static void keyspan_pda_rx_interrupt(struct urb *urb)
        int retval;
        int status = urb->status;
        struct keyspan_pda_private *priv;
+       bool throttled = false;
        unsigned long flags;
 
        priv = usb_get_serial_port_data(port);
@@ -209,16 +212,24 @@ static void keyspan_pda_rx_interrupt(struct urb *urb)
        }
 
 exit:
-       retval = usb_submit_urb(urb, GFP_ATOMIC);
-       if (retval)
-               dev_err(&port->dev,
-                       "%s - usb_submit_urb failed with result %d\n",
-                       __func__, retval);
+       spin_lock_irqsave(&port->lock, flags);
+       if (priv->throttle_req) {
+               priv->throttled = true;
+               throttled = true;
+       }
+       spin_unlock_irqrestore(&port->lock, flags);
+
+       if (!throttled) {
+               retval = usb_submit_urb(urb, GFP_ATOMIC);
+               if (retval)
+                       dev_err(&port->dev, "failed to resubmit in urb: %d\n", retval);
+       }
 }
 
 static void keyspan_pda_rx_throttle(struct tty_struct *tty)
 {
        struct usb_serial_port *port = tty->driver_data;
+       struct keyspan_pda_private *priv = usb_get_serial_port_data(port);
 
        /*
         * Stop receiving characters. We just turn off the URB request, and
@@ -228,16 +239,29 @@ static void keyspan_pda_rx_throttle(struct tty_struct *tty)
         * send an XOFF, although it might make sense to foist that off upon
         * the device too.
         */
-       usb_kill_urb(port->interrupt_in_urb);
+       spin_lock_irq(&port->lock);
+       priv->throttle_req = true;
+       spin_unlock_irq(&port->lock);
 }
 
 static void keyspan_pda_rx_unthrottle(struct tty_struct *tty)
 {
        struct usb_serial_port *port = tty->driver_data;
+       struct keyspan_pda_private *priv = usb_get_serial_port_data(port);
+       bool throttled;
+       int ret;
 
-       /* just restart the receive interrupt URB */
-       if (usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL))
-               dev_dbg(&port->dev, "usb_submit_urb(read urb) failed\n");
+       spin_lock_irq(&port->lock);
+       throttled = priv->throttled;
+       priv->throttled = false;
+       priv->throttle_req = false;
+       spin_unlock_irq(&port->lock);
+
+       if (throttled) {
+               ret = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
+               if (ret)
+                       dev_err(&port->dev, "failed to submit in urb: %d\n", ret);
+       }
 }
 
 static speed_t keyspan_pda_setbaud(struct usb_serial *serial, speed_t baud)
@@ -577,6 +601,8 @@ static int keyspan_pda_open(struct tty_struct *tty,
 
        spin_lock_irq(&port->lock);
        priv->tx_room = rc;
+       priv->throttled = false;
+       priv->throttle_req = false;
        spin_unlock_irq(&port->lock);
 
        rc = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);