acm:tty: prevent rx data discarded on throttled.
glei [Thu, 1 Aug 2013 04:18:54 +0000 (12:18 +0800)]
Bug 1357628

Change-Id: Ic51f72190e4a2dd9a24d3e39e18d17564134f79a
Signed-off-by: glei <glei@nvidia.com>
Reviewed-on: http://git-master/r/256626
Reviewed-by: Automatic_Commit_Validation_User
Reviewed-by: Neil Patel <neilp@nvidia.com>
GVS: Gerrit_Virtual_Submit
Reviewed-by: Steve Lin <stlin@nvidia.com>

drivers/usb/class/cdc-acm.c
drivers/usb/class/cdc-acm.h

index 3d2f842..5d2c3f9 100644 (file)
@@ -8,6 +8,7 @@
  * Copyright (c) 2004 Oliver Neukum    <oliver@neukum.name>
  * Copyright (c) 2005 David Kubicek    <dave@awk.cz>
  * Copyright (c) 2011 Johan Hovold     <jhovold@gmail.com>
+ * Copyright (c) 2013 NVIDIA CORPORATION. All rights reserved.
  *
  * USB Abstract Control Model driver for USB modems and ISDN adapters
  *
@@ -433,16 +434,39 @@ static int acm_submit_read_urbs(struct acm *acm, gfp_t mem_flags)
 
 static void acm_process_read_urb(struct acm *acm, struct urb *urb)
 {
+       struct acm_rb *rb = urb->context;
        struct tty_struct *tty;
+       unsigned long flags;
 
-       if (!urb->actual_length)
+       if (!urb->actual_length) {
+               set_bit(rb->index, &acm->read_urbs_free);
                return;
+       }
 
        tty = tty_port_tty_get(&acm->port);
-       if (!tty)
+       if (!tty) {
+               set_bit(rb->index, &acm->read_urbs_free);
                return;
+       }
+
+       rb->data = (unsigned char *)urb->transfer_buffer;
+       rb->alen = urb->actual_length;
+       spin_lock_irqsave(&acm->read_lock, flags);
+       if (!acm->int_throttled && !acm->throttled) {
+               int count = tty_insert_flip_string(tty,
+                       rb->data, rb->alen);
+               if (count != rb->alen) {
+                       acm->int_throttled = 1;
+                       rb->data += count;
+                       rb->alen -= count;
+               }
+       }
+       if (!acm->int_throttled && !acm->throttled)
+               set_bit(rb->index, &acm->read_urbs_free);
+       else
+               list_add_tail(&rb->rb_node, &acm->rb_head);
+       spin_unlock_irqrestore(&acm->read_lock, flags);
 
-       tty_insert_flip_string(tty, urb->transfer_buffer, urb->actual_length);
        tty_flip_buffer_push(tty);
 
        tty_kref_put(tty);
@@ -456,10 +480,10 @@ static void acm_read_bulk_callback(struct urb *urb)
 
        dev_vdbg(&acm->data->dev, "%s - urb %d, len %d\n", __func__,
                                        rb->index, urb->actual_length);
-       set_bit(rb->index, &acm->read_urbs_free);
 
        if (!acm->dev) {
                dev_dbg(&acm->data->dev, "%s - disconnected\n", __func__);
+               set_bit(rb->index, &acm->read_urbs_free);
                return;
        }
        usb_mark_last_busy(acm->dev);
@@ -467,14 +491,16 @@ static void acm_read_bulk_callback(struct urb *urb)
        if (urb->status && !urb->actual_length) {
                dev_dbg(&acm->data->dev, "%s - non-zero urb status: %d\n",
                                                        __func__, urb->status);
+               set_bit(rb->index, &acm->read_urbs_free);
                return;
        }
+
        acm_process_read_urb(acm, urb);
 
        /* throttle device if requested by tty */
        spin_lock_irqsave(&acm->read_lock, flags);
        acm->throttled = acm->throttle_req;
-       if (!acm->throttled && !acm->susp_count) {
+       if (!acm->int_throttled && !acm->throttled && !acm->susp_count) {
                spin_unlock_irqrestore(&acm->read_lock, flags);
                acm_submit_read_urb(acm, rb->index, GFP_ATOMIC);
        } else {
@@ -596,8 +622,15 @@ static int acm_port_activate(struct tty_port *port, struct tty_struct *tty)
         * Unthrottle device in case the TTY was closed while throttled.
         */
        spin_lock_irq(&acm->read_lock);
+       acm->int_throttled = 0;
        acm->throttled = 0;
        acm->throttle_req = 0;
+       while (!list_empty(&acm->rb_head)) {
+               struct acm_rb *rb = list_entry(acm->rb_head.next,
+                       struct acm_rb, rb_node);
+               list_del(acm->rb_head.next);
+               set_bit(rb->index, &acm->read_urbs_free);
+       }
        spin_unlock_irq(&acm->read_lock);
 
        if (acm_submit_read_urbs(acm, GFP_KERNEL))
@@ -749,13 +782,36 @@ static void acm_tty_unthrottle(struct tty_struct *tty)
        unsigned int was_throttled;
 
        spin_lock_irq(&acm->read_lock);
-       was_throttled = acm->throttled;
+       was_throttled = acm->int_throttled | acm->throttled;
+       acm->int_throttled = 0;
        acm->throttled = 0;
        acm->throttle_req = 0;
        spin_unlock_irq(&acm->read_lock);
 
-       if (was_throttled)
-               acm_submit_read_urbs(acm, GFP_KERNEL);
+       if (was_throttled) {
+               spin_lock_irq(&acm->read_lock);
+               while (!list_empty(&acm->rb_head)) {
+                       struct acm_rb *rb = list_entry(acm->rb_head.next,
+                               struct acm_rb, rb_node);
+                       int count = tty_insert_flip_string(tty,
+                               rb->data, rb->alen);
+                       if (count != rb->alen) {
+                               acm->int_throttled = 1;
+                               rb->data += count;
+                               rb->alen -= count;
+                               break;
+                       } else {
+                               list_del(acm->rb_head.next);
+                               set_bit(rb->index, &acm->read_urbs_free);
+                       }
+               }
+               spin_unlock_irq(&acm->read_lock);
+
+               tty_flip_buffer_push(tty);
+
+               if (!acm->int_throttled)
+                       acm_submit_read_urbs(acm, GFP_KERNEL);
+       }
 }
 
 static int acm_tty_break_ctl(struct tty_struct *tty, int state)
@@ -1229,6 +1285,7 @@ made_compressed_probe:
                acm->ctrl_caps &= ~USB_CDC_CAP_LINE;
        acm->ctrlsize = ctrlsize;
        acm->readsize = readsize;
+       INIT_LIST_HEAD(&acm->rb_head);
        acm->rx_buflimit = num_rx_buf;
        INIT_WORK(&acm->work, acm_softint);
        init_usb_anchor(&acm->deferred);
index f812cce..1ad5f10 100644 (file)
@@ -1,4 +1,5 @@
 /*
+ * Copyright (c) 2013 NVIDIA CORPORATION. All rights reserved.
  *
  * Includes for cdc-acm.c
  *
@@ -77,6 +78,9 @@ struct acm_rb {
        dma_addr_t              dma;
        int                     index;
        struct acm              *instance;
+       unsigned char           *data;
+       int                     alen;
+       struct list_head        rb_node;
 };
 
 struct acm {
@@ -94,6 +98,7 @@ struct acm {
        unsigned long read_urbs_free;
        struct urb *read_urbs[ACM_NR];
        struct acm_rb read_buffers[ACM_NR];
+       struct list_head rb_head;
        int rx_buflimit;
        int rx_endpoint;
        spinlock_t read_lock;
@@ -114,6 +119,7 @@ struct acm {
        unsigned int susp_count;                        /* number of suspended interfaces */
        unsigned int combined_interfaces:1;             /* control and data collapsed */
        unsigned int is_int_ep:1;                       /* interrupt endpoints contrary to spec used */
+       unsigned int int_throttled:1;                   /* internal throttled */
        unsigned int throttled:1;                       /* actually throttled */
        unsigned int throttle_req:1;                    /* throttle requested */
        unsigned int no_hangup_in_reset_resume:1;       /* do not call tty_hangup in acm_reset_resume */