usb: serial: Fix handling of urbs during suspend and resume
This fix handles two race conditions:-
1) Unlinking rx URBs during interface suspend races with completion
of a rx urb, which may cause successful completion of an urb with
actual data with a status of -ECONNRESET. Avoid this race by
anchoring the urb while submission and unlink only anchored urbs
during suspend.
2) Race between write and resume with the possibility of data write
submitting urb immediately before the delayed urbs are played back
during resume. This results in data written by tty driver getting
submitted out of order to HSIC controller.
CRs-Fixed: 371929
Change-Id: I4e9c7e994d643978b9889316b899f0251be807b6
Signed-off-by: Hemant Kumar <hemantk@codeaurora.org>
diff --git a/drivers/usb/serial/usb_wwan.c b/drivers/usb/serial/usb_wwan.c
index 519af39..0c58554 100644
--- a/drivers/usb/serial/usb_wwan.c
+++ b/drivers/usb/serial/usb_wwan.c
@@ -252,10 +252,12 @@
} else {
intfdata->in_flight++;
spin_unlock_irqrestore(&intfdata->susp_lock, flags);
+ usb_anchor_urb(this_urb, &portdata->submitted);
err = usb_submit_urb(this_urb, GFP_ATOMIC);
if (err) {
dbg("usb_submit_urb %p (write bulk) failed "
"(%d)", this_urb, err);
+ usb_unanchor_urb(this_urb);
clear_bit(i, &portdata->out_busy);
spin_lock_irqsave(&intfdata->susp_lock, flags);
intfdata->in_flight--;
@@ -281,6 +283,7 @@
{
int err;
int endpoint;
+ struct usb_wwan_port_private *portdata;
struct usb_serial_port *port;
struct tty_struct *tty;
unsigned char *data = urb->transfer_buffer;
@@ -290,6 +293,7 @@
endpoint = usb_pipeendpoint(urb->pipe);
port = urb->context;
+ portdata = usb_get_serial_port_data(port);
if (status) {
dbg("%s: nonzero status: %d on endpoint %02x.",
@@ -308,8 +312,10 @@
/* Resubmit urb so we continue receiving */
if (status != -ESHUTDOWN) {
+ usb_anchor_urb(urb, &portdata->submitted);
err = usb_submit_urb(urb, GFP_ATOMIC);
if (err) {
+ usb_unanchor_urb(urb);
if (err != -EPERM) {
printk(KERN_ERR "%s: resubmit read urb failed. "
"(%d)", __func__, err);
@@ -418,8 +424,10 @@
urb = portdata->in_urbs[i];
if (!urb)
continue;
+ usb_anchor_urb(urb, &portdata->submitted);
err = usb_submit_urb(urb, GFP_KERNEL);
if (err) {
+ usb_unanchor_urb(urb);
dbg("%s: submit urb %d failed (%d) %d",
__func__, i, err, urb->transfer_buffer_length);
}
@@ -551,6 +559,7 @@
return 1;
}
init_usb_anchor(&portdata->delayed);
+ init_usb_anchor(&portdata->submitted);
for (j = 0; j < N_IN_URB; j++) {
buffer = kmalloc(IN_BUFLEN, GFP_KERNEL);
@@ -590,7 +599,7 @@
static void stop_read_write_urbs(struct usb_serial *serial)
{
- int i, j;
+ int i;
struct usb_serial_port *port;
struct usb_wwan_port_private *portdata;
@@ -598,10 +607,7 @@
for (i = 0; i < serial->num_ports; ++i) {
port = serial->port[i];
portdata = usb_get_serial_port_data(port);
- for (j = 0; j < N_IN_URB; j++)
- usb_kill_urb(portdata->in_urbs[j]);
- for (j = 0; j < N_OUT_URB; j++)
- usb_kill_urb(portdata->out_urbs[j]);
+ usb_kill_anchored_urbs(&portdata->submitted);
}
}
@@ -694,10 +700,12 @@
portdata = usb_get_serial_port_data(port);
data = port->serial->private;
while ((urb = usb_get_from_anchor(&portdata->delayed))) {
+ usb_anchor_urb(urb, &portdata->submitted);
err = usb_submit_urb(urb, GFP_ATOMIC);
if (!err) {
data->in_flight++;
} else {
+ usb_unanchor_urb(urb);
/* we have to throw away the rest */
do {
unbusy_queued_urb(urb, portdata);
@@ -736,33 +744,32 @@
spin_lock_irq(&intfdata->susp_lock);
intfdata->suspended = 0;
- spin_unlock_irq(&intfdata->susp_lock);
-
for (i = 0; i < serial->num_ports; i++) {
/* walk all ports */
port = serial->port[i];
portdata = usb_get_serial_port_data(port);
/* skip closed ports */
- spin_lock_irq(&intfdata->susp_lock);
- if (!portdata->opened) {
- spin_unlock_irq(&intfdata->susp_lock);
+ if (!portdata->opened)
continue;
- }
for (j = 0; j < N_IN_URB; j++) {
urb = portdata->in_urbs[j];
+ usb_anchor_urb(urb, &portdata->submitted);
err = usb_submit_urb(urb, GFP_ATOMIC);
if (err < 0) {
err("%s: Error %d for bulk URB %d",
__func__, err, i);
+ usb_unanchor_urb(urb);
+ intfdata->suspended = 1;
spin_unlock_irq(&intfdata->susp_lock);
goto err_out;
}
}
play_delayed(port);
- spin_unlock_irq(&intfdata->susp_lock);
}
+ spin_unlock_irq(&intfdata->susp_lock);
+
err_out:
return err;
}