Index: drivers/net/usb/gobi/qcusbnet.c |
diff --git a/drivers/net/usb/gobi/qcusbnet.c b/drivers/net/usb/gobi/qcusbnet.c |
index 146a3eb55079edba512bd906afc8370a0f27b4ff..871a7e2a5a65995ba90476d478a39f0cda130c26 100644 |
--- a/drivers/net/usb/gobi/qcusbnet.c |
+++ b/drivers/net/usb/gobi/qcusbnet.c |
@@ -68,6 +68,12 @@ struct qcusbnet *qcusbnet_get(struct qcusbnet *key) |
return NULL; |
} |
+static void wake_worker(struct worker *worker) |
+{ |
+ atomic_inc(&worker->work_count); |
+ wake_up(&worker->waitq); |
+} |
+ |
int qc_suspend(struct usb_interface *iface, pm_message_t event) |
{ |
struct usbnet *usbnet; |
@@ -150,7 +156,7 @@ static int qc_resume(struct usb_interface *iface) |
return ret; |
} |
- complete(&dev->worker.work); |
+ wake_worker(&dev->worker); |
} else { |
DBG("nothing to resume\n"); |
return 0; |
@@ -246,7 +252,7 @@ static void qcnet_urbhook(struct urb *urb) |
worker->active = ERR_PTR(-EAGAIN); |
spin_unlock_irqrestore(&worker->active_lock, flags); |
/* XXX-fix race against qcnet_stop()? */ |
- complete(&worker->work); |
+ wake_worker(worker); |
usb_free_urb(urb); |
} |
@@ -287,7 +293,18 @@ static void qcnet_txtimeout(struct net_device *netdev) |
} |
spin_unlock_irqrestore(&worker->urbs_lock, listflags); |
- complete(&worker->work); |
+ wake_worker(worker); |
+} |
+ |
+static int worker_should_wake(struct worker *worker) |
+{ |
+ if (kthread_should_stop()) |
+ return 1; |
+ /* This is safe only because we are the only place that decrements this |
+ * counter. */ |
+ if (atomic_read(&worker->work_count)) |
+ return 1; |
+ return 0; |
} |
static int qcnet_worker(void *arg) |
@@ -307,27 +324,12 @@ static int qcnet_worker(void *arg) |
DBG("traffic thread started\n"); |
- while (!worker->exit && !kthread_should_stop()) { |
- wait_for_completion_interruptible(&worker->work); |
- |
- if (worker->exit || kthread_should_stop()) { |
- spin_lock_irqsave(&worker->active_lock, activeflags); |
- if (worker->active) { |
- usb_kill_urb(worker->active); |
- } |
- spin_unlock_irqrestore(&worker->active_lock, activeflags); |
- |
- spin_lock_irqsave(&worker->urbs_lock, listflags); |
- list_for_each_safe(node, tmp, &worker->urbs) { |
- req = list_entry(node, struct urbreq, node); |
- usb_free_urb(req->urb); |
- list_del(&req->node); |
- kfree(req); |
- } |
- spin_unlock_irqrestore(&worker->urbs_lock, listflags); |
+ while (!kthread_should_stop()) { |
+ wait_event(worker->waitq, worker_should_wake(worker)); |
+ if (kthread_should_stop()) |
break; |
- } |
+ atomic_dec(&worker->work_count); |
spin_lock_irqsave(&worker->active_lock, activeflags); |
if (IS_ERR(worker->active) && PTR_ERR(worker->active) == -EAGAIN) { |
@@ -382,12 +384,27 @@ static int qcnet_worker(void *arg) |
worker->active = NULL; |
spin_unlock_irqrestore(&worker->active_lock, activeflags); |
usb_autopm_put_interface(worker->iface); |
- complete(&worker->work); |
+ wake_worker(worker); |
} |
kfree(req); |
} |
+ spin_lock_irqsave(&worker->active_lock, activeflags); |
+ if (worker->active) { |
+ usb_kill_urb(worker->active); |
+ } |
+ spin_unlock_irqrestore(&worker->active_lock, activeflags); |
+ |
+ spin_lock_irqsave(&worker->urbs_lock, listflags); |
+ list_for_each_safe(node, tmp, &worker->urbs) { |
+ req = list_entry(node, struct urbreq, node); |
+ usb_free_urb(req->urb); |
+ list_del(&req->node); |
+ kfree(req); |
+ } |
+ spin_unlock_irqrestore(&worker->urbs_lock, listflags); |
+ |
DBG("traffic thread exiting\n"); |
worker->thread = NULL; |
return 0; |
@@ -452,7 +469,7 @@ static int qcnet_startxmit(struct sk_buff *skb, struct net_device *netdev) |
list_add_tail(&req->node, &worker->urbs); |
spin_unlock_irqrestore(&worker->urbs_lock, listflags); |
- complete(&worker->work); |
+ wake_worker(worker); |
netdev->trans_start = jiffies; |
dev_kfree_skb_any(skb); |
@@ -484,9 +501,9 @@ static int qcnet_open(struct net_device *netdev) |
dev->worker.active = NULL; |
spin_lock_init(&dev->worker.urbs_lock); |
spin_lock_init(&dev->worker.active_lock); |
- init_completion(&dev->worker.work); |
+ atomic_set(&dev->worker.work_count, 0); |
+ init_waitqueue_head(&dev->worker.waitq); |
- dev->worker.exit = 0; |
dev->worker.thread = kthread_run(qcnet_worker, &dev->worker, "qcnet_worker"); |
if (IS_ERR(dev->worker.thread)) { |
DBG("AutoPM thread creation error\n"); |
@@ -523,8 +540,6 @@ int qcnet_stop(struct net_device *netdev) |
} |
qc_setdown(dev, DOWN_NET_IFACE_STOPPED); |
- dev->worker.exit = 1; |
- complete(&dev->worker.work); |
kthread_stop(dev->worker.thread); |
DBG("thread stopped\n"); |
@@ -653,7 +668,8 @@ int qcnet_probe(struct usb_interface *iface, const struct usb_device_id *vidpids |
kref_init(&dev->refcount); |
INIT_LIST_HEAD(&dev->node); |
INIT_LIST_HEAD(&dev->qmi.clients); |
- init_completion(&dev->worker.work); |
+ atomic_set(&dev->worker.work_count, 0); |
+ init_waitqueue_head(&dev->worker.waitq); |
spin_lock_init(&dev->qmi.clients_lock); |
dev->down = 0; |