summaryrefslogtreecommitdiff
path: root/drivers/usb
diff options
context:
space:
mode:
authorJohan Hovold <johan@kernel.org>2026-07-08 16:31:35 +0200
committerJohan Hovold <johan@kernel.org>2026-07-13 11:32:52 +0200
commit42a97c0480f96a2977e6d51ce512adc780f1ef5d (patch)
treebd66f3d3ea889a0c6a8e0dd013da5416b9a3a326 /drivers/usb
parenta13c140cc289c0b7b3770bce5b3ad42ab35074aa (diff)
downloadlinux-next-42a97c0480f96a2977e6d51ce512adc780f1ef5d.tar.gz
linux-next-42a97c0480f96a2977e6d51ce512adc780f1ef5d.zip
USB: serial: keyspan_pda: fix data loss on receive throttling
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>
Diffstat (limited to 'drivers/usb')
-rw-r--r--drivers/usb/serial/keyspan_pda.c44
1 files changed, 35 insertions, 9 deletions
diff --git a/drivers/usb/serial/keyspan_pda.c b/drivers/usb/serial/keyspan_pda.c
index f05bcce60600..dd4cfd17f7ad 100644
--- a/drivers/usb/serial/keyspan_pda.c
+++ b/drivers/usb/serial/keyspan_pda.c
@@ -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);