Pull Elly's Change from R12 in to 0.11.241.B Factory Branch Manually:
CHROMIUM: gobi: Remove worker.exit hack.
The exit member was added to hack around crosbug.com/14190, but we need a proper
fix. I discussed with olofj, and we think the right thing is to use a waitqueue
and wait for there to be work available or for the thread to be asked to exit.
BUG=chromium-os:14205
TEST=Adhoc
Build, modem status, suspend/resume
http://codereview.chromium.org/6913008/
diff --git a/drivers/net/usb/gobi/qcusbnet.c b/drivers/net/usb/gobi/qcusbnet.c
index 33e26a4..871a7e2 100644
--- a/drivers/net/usb/gobi/qcusbnet.c
+++ b/drivers/net/usb/gobi/qcusbnet.c
@@ -1,5 +1,5 @@
/* qcusbnet.c - gobi network device
- * Copyright (c) 2010, Code Aurora Forum. All rights reserved.
+ * Copyright (c) 2011, Code Aurora Forum. All rights reserved.
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@@ -21,6 +21,8 @@
#include "qmi.h"
#include "qcusbnet.h"
+#include <linux/ctype.h>
+
#define DRIVER_VERSION "1.0.110+google"
#define DRIVER_AUTHOR "Qualcomm Innovation Center"
#define DRIVER_DESC "gobi"
@@ -66,6 +68,12 @@
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;
@@ -148,7 +156,7 @@
return ret;
}
- complete(&dev->worker.work);
+ wake_worker(&dev->worker);
} else {
DBG("nothing to resume\n");
return 0;
@@ -244,7 +252,7 @@
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);
}
@@ -285,7 +293,18 @@
}
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)
@@ -306,26 +325,11 @@
DBG("traffic thread started\n");
while (!kthread_should_stop()) {
- wait_for_completion_interruptible(&worker->work);
+ wait_event(worker->waitq, worker_should_wake(worker));
- if (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);
-
+ 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) {
@@ -380,12 +384,27 @@
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;
@@ -450,7 +469,7 @@
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);
@@ -482,7 +501,8 @@
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.thread = kthread_run(qcnet_worker, &dev->worker, "qcnet_worker");
if (IS_ERR(dev->worker.thread)) {
@@ -520,7 +540,6 @@
}
qc_setdown(dev, DOWN_NET_IFACE_STOPPED);
- complete(&dev->worker.work);
kthread_stop(dev->worker.thread);
DBG("thread stopped\n");
@@ -578,12 +597,24 @@
MODULE_DEVICE_TABLE(usb, qc_vidpids);
+static u8 nibble(unsigned char c)
+{
+ if (likely(isdigit(c)))
+ return c - '0';
+ c = toupper(c);
+ if (likely(isxdigit(c)))
+ return 10 + c - 'A';
+ return 0;
+}
+
int qcnet_probe(struct usb_interface *iface, const struct usb_device_id *vidpids)
{
int status;
struct usbnet *usbnet;
struct qcusbnet *dev;
struct net_device_ops *netdevops;
+ int i;
+ u8 *addr;
status = usbnet_probe(iface, vidpids);
if (status < 0) {
@@ -629,8 +660,6 @@
dev->iface = iface;
memset(&(dev->meid), '0', 14);
- DBG("Mac Address: %pM\n", dev->usbnet->net->dev_addr);
-
dev->valid = false;
memset(&dev->qmi, 0, sizeof(dev->qmi));
@@ -639,7 +668,8 @@
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;
@@ -655,6 +685,13 @@
list_add(&dev->node, &qcusbnet_list);
mutex_unlock(&qcusbnet_lock);
}
+ /* After calling qc_register, MEID is valid */
+ addr = &usbnet->net->dev_addr[0];
+ for (i = 0; i < 6; i++)
+ addr[i] = (nibble(dev->meid[i*2+2]) << 4)+
+ nibble(dev->meid[i*2+3]);
+ addr[0] &= 0xfe; /* clear multicast bit */
+ addr[0] |= 0x02; /* set local assignment bit (IEEE802) */
return status;
}
diff --git a/drivers/net/usb/gobi/structs.h b/drivers/net/usb/gobi/structs.h
index 13b3788..4c8c23e 100644
--- a/drivers/net/usb/gobi/structs.h
+++ b/drivers/net/usb/gobi/structs.h
@@ -27,6 +27,7 @@
#include <linux/cdev.h>
#include <linux/kobject.h>
#include <linux/kthread.h>
+#include <linux/wait.h>
#include <linux/usb/usbnet.h>
@@ -49,7 +50,8 @@
struct worker {
struct task_struct *thread;
- struct completion work;
+ atomic_t work_count;
+ wait_queue_head_t waitq;
struct list_head urbs;
spinlock_t urbs_lock;
struct urb *active;