From 077c1bf346608626a5573313a041b0e86f58f0b6 Mon Sep 17 00:00:00 2001 From: Michael Hsu Date: Thu, 23 Feb 2012 21:18:10 -0800 Subject: net: usb: raw-ip: Fix autopm / system suspend issues. 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 (cherry picked from commit 8bd7322127ccf6727d949f4bc1b2a4eac4b6814e) Reviewed-on: http://git-master/r/95166 Reviewed-by: Vinayak Pane Reviewed-by: Automatic_Commit_Validation_User Reviewed-by: Bharat Nihalani --- drivers/net/usb/raw_ip_net.c | 169 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 146 insertions(+), 23 deletions(-) (limited to 'drivers') diff --git a/drivers/net/usb/raw_ip_net.c b/drivers/net/usb/raw_ip_net.c index b8cc5fd8a1bd..1b3b89b3c274 100644 --- a/drivers/net/usb/raw_ip_net.c +++ b/drivers/net/usb/raw_ip_net.c @@ -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 -- cgit v1.2.3