net: usb: raw-ip: Fix autopm / system suspend issues.
Michael Hsu [Fri, 24 Feb 2012 05:18:10 +0000 (21:18 -0800)]
Add system suspend count and avoid urb activity during
system suspend.  Use async autopm to avoid deadlock with
system suspend.  Do not allocate rx urb's constantly -
allocate once upon init, free rx urb upon exit.

Bug 929408, 952748, 957354

Change-Id: I4ea050fc881528cf44d2039d42891e21c9df8c4e
Signed-off-by: Michael Hsu <mhsu@nvidia.com>
(cherry picked from commit 8bd7322127ccf6727d949f4bc1b2a4eac4b6814e)
Reviewed-on: http://git-master/r/95166
Reviewed-by: Vinayak Pane <vpane@nvidia.com>
Reviewed-by: Automatic_Commit_Validation_User
Reviewed-by: Bharat Nihalani <bnihalani@nvidia.com>

drivers/net/usb/raw_ip_net.c

index b8cc5fd..1b3b89b 100644 (file)
@@ -67,8 +67,13 @@ module_param(usb_net_raw_ip_tx_debug, ulong, 0644);
 MODULE_PARM_DESC(usb_net_raw_ip_tx_debug, "usb net (raw-ip) - tx debug");
 
 struct baseband_usb {
+       /* semaphore between disconnect/suspend/resume */
+       struct semaphore sem;
+       /* instance */
        int baseband_index;
+       /* network statistics */
        struct net_device_stats stats;
+       /* usb context */
        struct {
                struct usb_driver *driver;
                struct usb_device *device;
@@ -87,8 +92,11 @@ struct baseband_usb {
                struct workqueue_struct *tx_workqueue;
                struct work_struct tx_work;
        } usb;
+       /* re-usable rx urb */
        struct urb *urb_r;
        void *buff;
+       /* suspend count */
+       int susp_count;
 };
 
 static struct baseband_usb *baseband_usb_net[MAX_INTFS] = { 0, 0, 0};
@@ -158,6 +166,11 @@ static void baseband_usb_driver_disconnect(struct usb_interface *intf)
                                baseband_usb_net[i]->usb.interface, intf);
                        continue;
                }
+               /* acquire semaphore */
+               if (down_interruptible(&baseband_usb_net[i]->sem)) {
+                       pr_err("%s: cannot acquire semaphore\n", __func__);
+                       continue;
+               }
                /* kill usb tx */
                while ((urb = usb_get_from_anchor(&baseband_usb_net[i]->
                        usb.tx_urb_deferred)) != (struct urb *) 0) {
@@ -192,6 +205,8 @@ static void baseband_usb_driver_disconnect(struct usb_interface *intf)
                /* mark interface as disconnected */
                baseband_usb_net[i]->usb.interface
                        = (struct usb_interface *) 0;
+               /* release semaphore */
+               up(&baseband_usb_net[i]->sem);
        }
 
 }
@@ -200,10 +215,14 @@ static void baseband_usb_driver_disconnect(struct usb_interface *intf)
 static int baseband_usb_driver_suspend(struct usb_interface *intf,
        pm_message_t message)
 {
-       int i;
+       int i, susp_count;
 
        pr_debug("%s intf %p\n", __func__, intf);
 
+       pr_debug("%s: cnt %d intf=%p &intf->dev=%p kobj=%s\n",
+                       __func__, atomic_read(&intf->dev.power.usage_count),
+                       intf, &intf->dev, kobject_name(&intf->dev.kobj));
+
        for (i = 0; i < max_intfs; i++) {
                pr_debug("[%d]\n", i);
                if (!baseband_usb_net[i])
@@ -213,13 +232,47 @@ static int baseband_usb_driver_suspend(struct usb_interface *intf,
                                baseband_usb_net[i]->usb.interface, intf);
                        continue;
                }
+               /* increment suspend count */
+               susp_count = (baseband_usb_net[i]->susp_count)++;
+               if (susp_count > 0) {
+                       pr_debug("%s: susp_count %d > 0 (already suspended)\n",
+                               __func__, susp_count);
+                       continue;
+               }
+               if (susp_count < 0) {
+                       pr_debug("%s: susp_count %d < 0 (ILLEGAL VALUE)\n",
+                               __func__, susp_count);
+                       baseband_usb_net[i]->susp_count = 0;
+                       continue;
+               }
+               pr_debug("%s: susp_count = %d (suspending...)\n",
+                       __func__, susp_count);
+               /* acquire semaphore */
+               if (down_interruptible(&baseband_usb_net[i]->sem)) {
+                       pr_err("%s: cannot acquire semaphore\n", __func__);
+                       continue;
+               }
                /* kill usb rx */
                if (!baseband_usb_net[i]->usb.rx_urb) {
                        pr_debug("rx_usb already killed\n");
+                       up(&baseband_usb_net[i]->sem);
                        continue;
                }
+               pr_debug("%s: kill rx_urb {\n",__func__);
                usb_kill_urb(baseband_usb_net[i]->usb.rx_urb);
+               pr_debug("%s: kill rx_urb }\n",__func__);
                baseband_usb_net[i]->usb.rx_urb = (struct urb *) 0;
+               /* cancel tx urb work (will restart after resume) */
+               if (!baseband_usb_net[i]->usb.tx_workqueue) {
+                       pr_err("%s: !tx_workqueue\n", __func__);
+                       up(&baseband_usb_net[i]->sem);
+                       continue;
+               }
+               pr_debug("%s: cancel_work_sync {\n",__func__);
+               cancel_work_sync(&baseband_usb_net[i]->usb.tx_work);
+               pr_debug("%s: cancel_work_sync }\n",__func__);
+               /* release semaphore */
+               up(&baseband_usb_net[i]->sem);
        }
 
        return 0;
@@ -227,10 +280,14 @@ static int baseband_usb_driver_suspend(struct usb_interface *intf,
 
 static int baseband_usb_driver_resume(struct usb_interface *intf)
 {
-       int i, err;
+       int i, err, susp_count;
 
        pr_debug("%s intf %p\n", __func__, intf);
 
+       pr_debug("%s: cnt %d intf=%p &intf->dev=%p kobj=%s\n",
+                       __func__, atomic_read(&intf->dev.power.usage_count),
+                       intf, &intf->dev, kobject_name(&intf->dev.kobj));
+
        for (i = 0; i < max_intfs; i++) {
                pr_debug("[%d]\n", i);
                if (!baseband_usb_net[i])
@@ -240,16 +297,48 @@ static int baseband_usb_driver_resume(struct usb_interface *intf)
                                baseband_usb_net[i]->usb.interface, intf);
                        continue;
                }
+               /* decrement suspend count */
+               susp_count = --(baseband_usb_net[i]->susp_count);
+               if (susp_count > 0) {
+                       pr_debug("%s: susp_count %d > 0 (not resuming yet)\n",
+                               __func__, susp_count);
+                       continue;
+               }
+               if (susp_count < 0) {
+                       pr_debug("%s: susp_count %d < 0 (ILLEGAL VALUE)\n",
+                               __func__, susp_count);
+                       baseband_usb_net[i]->susp_count = 0;
+                       continue;
+               }
+               pr_debug("%s: susp_count = %d (resuming...)\n",
+                       __func__, susp_count);
+               /* acquire semaphore */
+               if (down_interruptible(&baseband_usb_net[i]->sem)) {
+                       pr_err("%s: cannot acquire semaphore\n", __func__);
+                       continue;
+               }
                /* start usb rx */
                if (baseband_usb_net[i]->usb.rx_urb) {
                        pr_debug("rx_usb already exists\n");
+                       up(&baseband_usb_net[i]->sem);
                        continue;
                }
                err = usb_net_raw_ip_rx_urb_submit(baseband_usb_net[i]);
                if (err < 0) {
                        pr_err("submit rx failed - err %d\n", err);
+                       up(&baseband_usb_net[i]->sem);
                        continue;
                }
+               /* restart tx urb work (cancelled in suspend) */
+               if (!baseband_usb_net[i]->usb.tx_workqueue) {
+                       pr_err("%s: !tx_workqueue\n", __func__);
+                       up(&baseband_usb_net[i]->sem);
+                       continue;
+               }
+               queue_work(baseband_usb_net[i]->usb.tx_workqueue,
+                       &baseband_usb_net[i]->usb.tx_work);
+               /* release semaphore */
+               up(&baseband_usb_net[i]->sem);
        }
 
        return 0;
@@ -361,6 +450,9 @@ struct baseband_usb *baseband_usb_open(int index,
        if (!usb)
                return (struct baseband_usb *) 0;
 
+       /* create semaphores */
+       sema_init(&usb->sem, 1);
+
        /* open usb driver */
        sprintf(baseband_usb_driver_name[index],
                "baseband_usb_%x_%x_%x",
@@ -424,6 +516,9 @@ void baseband_usb_close(struct baseband_usb *usb)
                pr_debug("close usb driver }\n");
        }
 
+       /* destroy semaphores */
+       memset(&usb->sem, 0, sizeof(usb->sem));
+
        /* free baseband usb structure */
        kfree(usb);
 
@@ -485,12 +580,22 @@ static netdev_tx_t baseband_usb_netdev_start_xmit(
        }
        usb = baseband_usb_net[i];
 
+       /* autoresume if suspended */
+       if (usb->usb.interface) {
+               usb_autopm_get_interface_async(usb->usb.interface);
+       } else {
+               pr_err("%s: tx get interface error\n", __func__);
+               netif_stop_queue(dev);
+               usb->stats.tx_errors++;
+               return NETDEV_TX_BUSY;
+       }
+
        /* submit tx urb */
        err = usb_net_raw_ip_tx_urb_submit(usb, skb);
        if (err < 0) {
-               pr_err("tx urb submit error\n");
-               usb->stats.tx_errors++;
+               pr_err("%s: tx urb submit error\n", __func__);
                netif_stop_queue(dev);
+               usb->stats.tx_errors++;
                return NETDEV_TX_BUSY;
        }
 
@@ -600,14 +705,15 @@ static void usb_net_raw_ip_rx_urb_comp(struct urb *urb)
        switch (urb->status) {
        case 0:
                break;
-       case -ENOENT:
-               /* fall through */
        case -ESHUTDOWN:
                /* fall through */
-       case -EPROTO:
                pr_info("%s: rx urb %p - link shutdown %d\n",
                        __func__, urb, urb->status);
                goto err_exit;
+       case -EPROTO:
+               pr_info("%s: rx urb %p - link shutdown %d EPROTO\n",
+                       __func__, urb, urb->status);
+               goto err_exit;
        default:
                pr_info("%s: rx urb %p - status %d\n",
                        __func__, urb, urb->status);
@@ -615,7 +721,7 @@ static void usb_net_raw_ip_rx_urb_comp(struct urb *urb)
        }
 
        /* put rx urb data in rx buffer */
-       if (urb->actual_length) {
+       if (urb->actual_length > 0) {
                pr_debug("usb_net_raw_ip_rx_urb_comp - "
                        "urb->actual_length %d\n", urb->actual_length);
                /* allocate skb with space for
@@ -664,6 +770,10 @@ static void usb_net_raw_ip_rx_urb_comp(struct urb *urb)
        /* mark rx urb complete */
        usb->usb.rx_urb = (struct urb *) 0;
 
+       /* do not submit urb if interface is suspending */
+       if (urb->status == -ENOENT)
+               return;
+
        /* submit next rx urb */
        usb_net_raw_ip_rx_urb_submit(usb);
        return;
@@ -745,25 +855,28 @@ static int usb_net_raw_ip_tx_urb_submit(struct baseband_usb *usb,
                pr_err("%s: !usb\n", __func__);
                return -EINVAL;
        }
-       if (!skb) {
-               pr_err("%s: !skb\n", __func__);
-               return -EINVAL;
-       }
        if (!usb->usb.interface) {
                pr_err("usb interface disconnected - not submitting tx urb\n");
                return -EINVAL;
        }
+       if (!skb) {
+               pr_err("%s: !skb\n", __func__);
+               usb_autopm_put_interface_async(usb->usb.interface);
+               return -EINVAL;
+       }
 
        /* allocate urb */
        urb = usb_alloc_urb(0, GFP_ATOMIC);
        if (!urb) {
                pr_err("usb_alloc_urb() failed\n");
+               usb_autopm_put_interface_async(usb->usb.interface);
                return -ENOMEM;
        }
        buf = kzalloc(skb->len - 14, GFP_ATOMIC);
        if (!buf) {
                pr_err("usb buffer kzalloc() failed\n");
                usb_free_urb(urb);
+               usb_autopm_put_interface_async(usb->usb.interface);
                return -ENOMEM;
        }
        err = skb_copy_bits(skb, 14, buf, skb->len - 14);
@@ -771,6 +884,7 @@ static int usb_net_raw_ip_tx_urb_submit(struct baseband_usb *usb,
                pr_err("skb_copy_bits() failed - %d\n", err);
                kfree(buf);
                usb_free_urb(urb);
+               usb_autopm_put_interface_async(usb->usb.interface);
                return err;
        }
        usb_fill_bulk_urb(urb, usb->usb.device, usb->usb.pipe.bulk.out,
@@ -814,18 +928,23 @@ static void usb_net_raw_ip_tx_urb_work(struct work_struct *work)
                return;
        }
 
+       /* check if suspended */
+       if (usb->susp_count > 0) {
+               pr_info("%s: usb->susp_count %d > 0 (suspended)\n",
+                       __func__, usb->susp_count);
+               return;
+       }
+
        /* submit queued tx urb(s) */
        while ((urb = usb_get_from_anchor(&usb->usb.tx_urb_deferred))
                != (struct urb *) 0) {
                /* decrement count from usb_get_from_anchor() */
                usb_free_urb(urb);
-
-               /* autoresume before tx */
-               usb_mark_last_busy(usb->usb.device);
-               err = usb_autopm_get_interface(usb->usb.interface);
-               if (err < 0) {
-                       pr_err("%s: usb_autopm_get_interface(%p) failed %d\n",
-                               __func__, usb->usb.interface, err);
+               /* check if usb interface disconnected */
+               if (!usb->usb.interface) {
+                       pr_err("%s: not submitting tx urb %p"
+                               " - interface disconnected\n",
+                               __func__, urb);
                        if (urb->transfer_buffer) {
                                kfree(urb->transfer_buffer);
                                urb->transfer_buffer = (void *) 0;
@@ -834,17 +953,20 @@ static void usb_net_raw_ip_tx_urb_work(struct work_struct *work)
                        usb->stats.tx_errors++;
                        continue;
                }
+               /* autoresume before tx */
+               usb_mark_last_busy(usb->usb.device);
                /* submit tx urb */
                err = usb_submit_urb(urb, GFP_ATOMIC);
                if (err < 0) {
                        pr_err("%s: usb_submit_urb(%p) failed - err %d\n",
                                __func__, urb, err);
-                       usb_autopm_put_interface(usb->usb.interface);
+                       usb_autopm_put_interface_async(usb->usb.interface);
                        if (urb->transfer_buffer) {
                                kfree(urb->transfer_buffer);
                                urb->transfer_buffer = (void *) 0;
                        }
                        usb_free_urb(urb);
+                       usb->stats.tx_errors++;
                        continue;
                }
                /* free tx urb
@@ -882,6 +1004,7 @@ static void usb_net_raw_ip_tx_urb_comp(struct urb *urb)
        case -EPROTO:
                pr_info("%s: tx urb %p - link shutdown %d\n",
                        __func__, urb, urb->status);
+               usb_autopm_put_interface_async(usb->usb.interface);
                goto err_exit;
        default:
                pr_info("%s: tx urb %p - status %d\n",
@@ -894,6 +1017,7 @@ static void usb_net_raw_ip_tx_urb_comp(struct urb *urb)
                usb->stats.tx_packets++;
                usb->stats.tx_bytes += urb->transfer_buffer_length;
        }
+
        /* autosuspend after tx completed */
        if (!usb->usb.interface) {
                pr_err("%s: usb interface disconnected"
@@ -901,7 +1025,7 @@ static void usb_net_raw_ip_tx_urb_comp(struct urb *urb)
                        __func__);
                goto err_exit;
        }
-       usb_autopm_put_interface(usb->usb.interface);
+       usb_autopm_put_interface_async(usb->usb.interface);
 
 err_exit:
        /* free tx urb transfer buffer */
@@ -909,7 +1033,6 @@ err_exit:
                kfree(urb->transfer_buffer);
                urb->transfer_buffer = (void *) 0;
        }
-
        pr_debug("usb_net_raw_ip_tx_urb_comp }\n");
 }
 
@@ -932,6 +1055,7 @@ static int usb_net_raw_ip_init(void)
                        err = -1;
                        goto error_exit;
                }
+               init_usb_anchor(&baseband_usb_net[i]->usb.tx_urb_deferred);
                /* register network device */
                usb_net_raw_ip_dev[i] = alloc_netdev(0,
                        BASEBAND_USB_NET_DEV_NAME,
@@ -964,7 +1088,6 @@ static int usb_net_raw_ip_init(void)
                        goto error_exit;
                }
                /* start usb tx */
-               init_usb_anchor(&baseband_usb_net[i]->usb.tx_urb_deferred);
                sprintf(name, "raw_ip_tx_wq-%d",
                        baseband_usb_net[i]->baseband_index);
                baseband_usb_net[i]->usb.tx_workqueue