| Index: drivers/net/usb/gobi/qmidevice.c | 
| diff --git a/drivers/net/usb/gobi/qmidevice.c b/drivers/net/usb/gobi/qmidevice.c | 
| new file mode 100644 | 
| index 0000000000000000000000000000000000000000..be61ddad9f65b555e4c28c746a284a1d2961af31 | 
| --- /dev/null | 
| +++ b/drivers/net/usb/gobi/qmidevice.c | 
| @@ -0,0 +1,1562 @@ | 
| +/* qmidevice.c - gobi QMI device | 
| + * Copyright (c) 2010, 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 | 
| + * only version 2 as published by the Free Software Foundation. | 
| + | 
| + * This program is distributed in the hope that it will be useful, | 
| + * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
| + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
| + * GNU General Public License for more details. | 
| + | 
| + * You should have received a copy of the GNU General Public License | 
| + * along with this program; if not, write to the Free Software | 
| + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | 
| + * 02110-1301, USA. | 
| + */ | 
| + | 
| +#include "qmidevice.h" | 
| +#include "qcusbnet.h" | 
| + | 
| +struct readreq { | 
| +	struct list_head node; | 
| +	void *data; | 
| +	u16 tid; | 
| +	u16 size; | 
| +}; | 
| + | 
| +struct notifyreq { | 
| +	struct list_head node; | 
| +	void (*func)(struct qcusbnet *, u16, void *); | 
| +	u16  tid; | 
| +	void *data; | 
| +}; | 
| + | 
| +struct client { | 
| +	struct list_head node; | 
| +	u16 cid; | 
| +	struct list_head reads; | 
| +	struct list_head notifies; | 
| +	struct list_head urbs; | 
| +}; | 
| + | 
| +struct urbsetup { | 
| +	u8 type; | 
| +	u8 code; | 
| +	u16 value; | 
| +	u16 index; | 
| +	u16 len; | 
| +}; | 
| + | 
| +struct qmihandle { | 
| +	u16 cid; | 
| +	struct qcusbnet *dev; | 
| +}; | 
| + | 
| +extern int qcusbnet_debug; | 
| +static int qcusbnet2k_fwdelay; | 
| + | 
| +static bool device_valid(struct qcusbnet *dev); | 
| +static struct client *client_bycid(struct qcusbnet *dev, u16 cid); | 
| +static bool client_addread(struct qcusbnet *dev, u16 cid, u16 tid, void *data, u16 size); | 
| +static bool client_delread(struct qcusbnet *dev, u16 cid, u16 tid, void **data, u16 *size); | 
| +static bool client_addnotify(struct qcusbnet *dev, u16 cid, u16 tid, | 
| +			     void (*hook)(struct qcusbnet *, u16 cid, void *), | 
| +			     void *data); | 
| +static bool client_notify(struct qcusbnet *dev, u16 cid, u16 tid); | 
| +static bool client_addurb(struct qcusbnet *dev, u16 cid, struct urb *urb); | 
| +static struct urb *client_delurb(struct qcusbnet *dev, u16 cid); | 
| + | 
| +static int resubmit_int_urb(struct urb *urb); | 
| + | 
| +static int devqmi_open(struct inode *inode, struct file *file); | 
| +static int devqmi_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg); | 
| +static int devqmi_release(struct inode *inode, struct file *file); | 
| +static ssize_t devqmi_read(struct file *file, char __user *buf, size_t size, | 
| +			   loff_t *pos); | 
| +static ssize_t devqmi_write(struct file *file, const char __user *buf, | 
| +			    size_t size, loff_t *pos); | 
| + | 
| +static bool qmi_ready(struct qcusbnet *dev, u16 timeout); | 
| +static void wds_callback(struct qcusbnet *dev, u16 cid, void *data); | 
| +static int setup_wds_callback(struct qcusbnet *dev); | 
| +static int qmidms_getmeid(struct qcusbnet *dev); | 
| + | 
| +#define IOCTL_QMI_GET_SERVICE_FILE      (0x8BE0 + 1) | 
| +#define IOCTL_QMI_GET_DEVICE_VIDPID     (0x8BE0 + 2) | 
| +#define IOCTL_QMI_GET_DEVICE_MEID       (0x8BE0 + 3) | 
| +#define IOCTL_QMI_CLOSE                 (0x8BE0 + 4) | 
| +#define CDC_GET_ENCAPSULATED_RESPONSE	0x01A1ll | 
| +#define CDC_CONNECTION_SPEED_CHANGE	0x08000000002AA1ll | 
| + | 
| +static const struct file_operations devqmi_fops = { | 
| +	.owner   = THIS_MODULE, | 
| +	.read    = devqmi_read, | 
| +	.write   = devqmi_write, | 
| +	.ioctl   = devqmi_ioctl, | 
| +	.open    = devqmi_open, | 
| +	.release = devqmi_release, | 
| +}; | 
| + | 
| +#ifdef CONFIG_SMP | 
| +static inline void assert_locked(struct qcusbnet *dev) | 
| +{ | 
| +	BUG_ON(!spin_is_locked(&dev->qmi.clients_lock)); | 
| +} | 
| +#else | 
| +static inline void assert_locked(struct qcusbnet *dev) | 
| +{ | 
| + | 
| +} | 
| +#endif | 
| + | 
| +static bool device_valid(struct qcusbnet *dev) | 
| +{ | 
| +	return dev && dev->valid; | 
| +} | 
| + | 
| +void qc_setdown(struct qcusbnet *dev, u8 reason) | 
| +{ | 
| +	set_bit(reason, &dev->down); | 
| +	netif_carrier_off(dev->usbnet->net); | 
| +} | 
| + | 
| +void qc_cleardown(struct qcusbnet *dev, u8 reason) | 
| +{ | 
| +	clear_bit(reason, &dev->down); | 
| +	if (!dev->down) | 
| +		netif_carrier_on(dev->usbnet->net); | 
| +} | 
| + | 
| +bool qc_isdown(struct qcusbnet *dev, u8 reason) | 
| +{ | 
| +	return test_bit(reason, &dev->down); | 
| +} | 
| + | 
| +static int resubmit_int_urb(struct urb *urb) | 
| +{ | 
| +	int status; | 
| +	int interval; | 
| +	if (!urb || !urb->dev) | 
| +		return -EINVAL; | 
| +	interval = urb->dev->speed == USB_SPEED_HIGH ? 7 : 3; | 
| +	usb_fill_int_urb(urb, urb->dev, urb->pipe, urb->transfer_buffer, | 
| +	                 urb->transfer_buffer_length, urb->complete, | 
| +	                 urb->context, interval); | 
| +	status = usb_submit_urb(urb, GFP_ATOMIC); | 
| +	if (status) | 
| +		DBG("status %d", status); | 
| +	return status; | 
| +} | 
| + | 
| +static void read_callback(struct urb *urb) | 
| +{ | 
| +	struct list_head *node; | 
| +	int result; | 
| +	u16 cid; | 
| +	struct client *client; | 
| +	void *data; | 
| +	void *copy; | 
| +	u16 size; | 
| +	struct qcusbnet *dev; | 
| +	unsigned long flags; | 
| +	u16 tid; | 
| + | 
| +	if (!urb) { | 
| +		DBG("bad read URB\n"); | 
| +		return; | 
| +	} | 
| + | 
| +	dev = urb->context; | 
| +	if (!device_valid(dev)) { | 
| +		DBG("Invalid device!\n"); | 
| +		return; | 
| +	} | 
| + | 
| +	if (urb->status) { | 
| +		DBG("Read status = %d\n", urb->status); | 
| +		resubmit_int_urb(dev->qmi.inturb); | 
| +		return; | 
| +	} | 
| + | 
| +	DBG("Read %d bytes\n", urb->actual_length); | 
| + | 
| +	data = urb->transfer_buffer; | 
| +	size = urb->actual_length; | 
| + | 
| +	if (qcusbnet_debug) | 
| +		print_hex_dump(KERN_INFO, "gobi-read: ", DUMP_PREFIX_OFFSET, | 
| +		               16, 1, data, size, true); | 
| + | 
| +	result = qmux_parse(&cid, data, size); | 
| +	if (result < 0) { | 
| +		DBG("Read error parsing QMUX %d\n", result); | 
| +		resubmit_int_urb(dev->qmi.inturb); | 
| +		return; | 
| +	} | 
| + | 
| +	if (size < result + 3) { | 
| +		DBG("Data buffer too small to parse\n"); | 
| +		resubmit_int_urb(dev->qmi.inturb); | 
| +		return; | 
| +	} | 
| + | 
| +	if (cid == QMICTL) | 
| +		tid = *(u8 *)(data + result + 1); | 
| +	else | 
| +		tid = *(u16 *)(data + result + 1); | 
| +	spin_lock_irqsave(&dev->qmi.clients_lock, flags); | 
| + | 
| +	list_for_each(node, &dev->qmi.clients) { | 
| +		client = list_entry(node, struct client, node); | 
| +		if (client->cid == cid || (client->cid | 0xff00) == cid) { | 
| +			copy = kmalloc(size, GFP_ATOMIC); | 
| +			memcpy(copy, data, size); | 
| +			if (!client_addread(dev, client->cid, tid, copy, size)) { | 
| +				DBG("Error allocating pReadMemListEntry " | 
| +					  "read will be discarded\n"); | 
| +				kfree(copy); | 
| +				spin_unlock_irqrestore(&dev->qmi.clients_lock, flags); | 
| +				resubmit_int_urb(dev->qmi.inturb); | 
| +				return; | 
| +			} | 
| + | 
| +			DBG("Creating new readListEntry for client 0x%04X, TID %x\n", | 
| +			    cid, tid); | 
| + | 
| +			client_notify(dev, client->cid, tid); | 
| + | 
| +			if (cid >> 8 != 0xff) | 
| +				break; | 
| +		} | 
| +	} | 
| + | 
| +	spin_unlock_irqrestore(&dev->qmi.clients_lock, flags); | 
| +	resubmit_int_urb(dev->qmi.inturb); | 
| +} | 
| + | 
| +static void int_callback(struct urb *urb) | 
| +{ | 
| +	int status; | 
| +	int interval; | 
| +	struct qcusbnet *dev = (struct qcusbnet *)urb->context; | 
| + | 
| +	if (!device_valid(dev)) { | 
| +		DBG("Invalid device!\n"); | 
| +		return; | 
| +	} | 
| + | 
| +	if (urb->status) { | 
| +		DBG("Int status = %d\n", urb->status); | 
| +		if (urb->status != -EOVERFLOW) | 
| +			return; | 
| +	} else { | 
| +		if ((urb->actual_length == 8) && | 
| +		    (*(u64 *)urb->transfer_buffer == CDC_GET_ENCAPSULATED_RESPONSE)) { | 
| +			usb_fill_control_urb(dev->qmi.readurb, dev->usbnet->udev, | 
| +					     usb_rcvctrlpipe(dev->usbnet->udev, 0), | 
| +					     (unsigned char *)dev->qmi.readsetup, | 
| +					     dev->qmi.readbuf, | 
| +					     DEFAULT_READ_URB_LENGTH, | 
| +					     read_callback, dev); | 
| +			status = usb_submit_urb(dev->qmi.readurb, GFP_ATOMIC); | 
| +			if (status) { | 
| +				DBG("Error submitting Read URB %d\n", status); | 
| +				return; | 
| +			} | 
| +		} else if ((urb->actual_length == 16) && | 
| +			   (*(u64 *)urb->transfer_buffer == CDC_CONNECTION_SPEED_CHANGE)) { | 
| +			/* if upstream or downstream is 0, stop traffic. | 
| +			 * Otherwise resume it */ | 
| +			if ((*(u32 *)(urb->transfer_buffer + 8) == 0) || | 
| +			    (*(u32 *)(urb->transfer_buffer + 12) == 0)) { | 
| +				qc_setdown(dev, DOWN_CDC_CONNECTION_SPEED); | 
| +				DBG("traffic stopping due to CONNECTION_SPEED_CHANGE\n"); | 
| +			} else { | 
| +				qc_cleardown(dev, DOWN_CDC_CONNECTION_SPEED); | 
| +				DBG("resuming traffic due to CONNECTION_SPEED_CHANGE\n"); | 
| +			} | 
| +		} else { | 
| +			DBG("ignoring invalid interrupt in packet\n"); | 
| +			if (qcusbnet_debug) | 
| +				print_hex_dump(KERN_INFO, "gobi-int: ", | 
| +				               DUMP_PREFIX_OFFSET, 16, 1, | 
| +				               urb->transfer_buffer, | 
| +				               urb->actual_length, true); | 
| +		} | 
| +	} | 
| + | 
| +	resubmit_int_urb(dev->qmi.inturb); | 
| +	return; | 
| +} | 
| + | 
| +int qc_startread(struct qcusbnet *dev) | 
| +{ | 
| +	int interval; | 
| + | 
| +	if (!device_valid(dev)) { | 
| +		DBG("Invalid device!\n"); | 
| +		return -ENXIO; | 
| +	} | 
| + | 
| +	dev->qmi.readurb = usb_alloc_urb(0, GFP_KERNEL); | 
| +	if (!dev->qmi.readurb) { | 
| +		DBG("Error allocating read urb\n"); | 
| +		return -ENOMEM; | 
| +	} | 
| + | 
| +	dev->qmi.inturb = usb_alloc_urb(0, GFP_KERNEL); | 
| +	if (!dev->qmi.inturb) { | 
| +		usb_free_urb(dev->qmi.readurb); | 
| +		DBG("Error allocating int urb\n"); | 
| +		return -ENOMEM; | 
| +	} | 
| + | 
| +	dev->qmi.readbuf = kmalloc(DEFAULT_READ_URB_LENGTH, GFP_KERNEL); | 
| +	if (!dev->qmi.readbuf) { | 
| +		usb_free_urb(dev->qmi.readurb); | 
| +		usb_free_urb(dev->qmi.inturb); | 
| +		DBG("Error allocating read buffer\n"); | 
| +		return -ENOMEM; | 
| +	} | 
| + | 
| +	dev->qmi.intbuf = kmalloc(DEFAULT_READ_URB_LENGTH, GFP_KERNEL); | 
| +	if (!dev->qmi.intbuf) { | 
| +		usb_free_urb(dev->qmi.readurb); | 
| +		usb_free_urb(dev->qmi.inturb); | 
| +		kfree(dev->qmi.readbuf); | 
| +		DBG("Error allocating int buffer\n"); | 
| +		return -ENOMEM; | 
| +	} | 
| + | 
| +	dev->qmi.readsetup = kmalloc(sizeof(*dev->qmi.readsetup), GFP_KERNEL); | 
| +	if (!dev->qmi.readsetup) { | 
| +		usb_free_urb(dev->qmi.readurb); | 
| +		usb_free_urb(dev->qmi.inturb); | 
| +		kfree(dev->qmi.readbuf); | 
| +		kfree(dev->qmi.intbuf); | 
| +		DBG("Error allocating setup packet buffer\n"); | 
| +		return -ENOMEM; | 
| +	} | 
| + | 
| +	dev->qmi.readsetup->type = 0xA1; | 
| +	dev->qmi.readsetup->code = 1; | 
| +	dev->qmi.readsetup->value = 0; | 
| +	dev->qmi.readsetup->index = 0; | 
| +	dev->qmi.readsetup->len = DEFAULT_READ_URB_LENGTH; | 
| + | 
| +	interval = (dev->usbnet->udev->speed == USB_SPEED_HIGH) ? 7 : 3; | 
| + | 
| +	usb_fill_int_urb(dev->qmi.inturb, dev->usbnet->udev, | 
| +			 usb_rcvintpipe(dev->usbnet->udev, 0x81), | 
| +			 dev->qmi.intbuf, DEFAULT_READ_URB_LENGTH, | 
| +			 int_callback, dev, interval); | 
| +	return usb_submit_urb(dev->qmi.inturb, GFP_KERNEL); | 
| +} | 
| + | 
| +void qc_stopread(struct qcusbnet *dev) | 
| +{ | 
| +	if (dev->qmi.readurb) { | 
| +		DBG("Killing read URB\n"); | 
| +		usb_kill_urb(dev->qmi.readurb); | 
| +	} | 
| + | 
| +	if (dev->qmi.inturb) { | 
| +		DBG("Killing int URB\n"); | 
| +		usb_kill_urb(dev->qmi.inturb); | 
| +	} | 
| + | 
| +	kfree(dev->qmi.readsetup); | 
| +	dev->qmi.readsetup = NULL; | 
| +	kfree(dev->qmi.readbuf); | 
| +	dev->qmi.readbuf = NULL; | 
| +	kfree(dev->qmi.intbuf); | 
| +	dev->qmi.intbuf = NULL; | 
| + | 
| +	usb_free_urb(dev->qmi.readurb); | 
| +	dev->qmi.readurb = NULL; | 
| +	usb_free_urb(dev->qmi.inturb); | 
| +	dev->qmi.inturb = NULL; | 
| +} | 
| + | 
| +static int read_async(struct qcusbnet *dev, u16 cid, u16 tid, | 
| +		      void (*hook)(struct qcusbnet *, u16, void *), | 
| +		      void *data) | 
| +{ | 
| +	struct list_head *node; | 
| +	struct client *client; | 
| +	struct readreq *readreq; | 
| + | 
| +	unsigned long flags; | 
| + | 
| +	if (!device_valid(dev)) { | 
| +		DBG("Invalid device!\n"); | 
| +		return -ENXIO; | 
| +	} | 
| + | 
| +	spin_lock_irqsave(&dev->qmi.clients_lock, flags); | 
| + | 
| +	client = client_bycid(dev, cid); | 
| +	if (!client) { | 
| +		DBG("Could not find matching client ID 0x%04X\n", cid); | 
| +		spin_unlock_irqrestore(&dev->qmi.clients_lock, flags); | 
| +		return -ENXIO; | 
| +	} | 
| + | 
| +	list_for_each(node, &client->reads) { | 
| +		readreq = list_entry(node, struct readreq, node); | 
| +		if (!tid || tid == readreq->tid) { | 
| +			spin_unlock_irqrestore(&dev->qmi.clients_lock, flags); | 
| +			hook(dev, cid, data); | 
| +			return 0; | 
| +		} | 
| +	} | 
| + | 
| +	if (!client_addnotify(dev, cid, tid, hook, data)) | 
| +		DBG("Unable to register for notification\n"); | 
| + | 
| +	spin_unlock_irqrestore(&dev->qmi.clients_lock, flags); | 
| +	return 0; | 
| +} | 
| + | 
| +static void upsem(struct qcusbnet *dev, u16 cid, void *data) | 
| +{ | 
| +	DBG("0x%04X\n", cid); | 
| +	up((struct semaphore *)data); | 
| +} | 
| + | 
| +static int read_sync(struct qcusbnet *dev, void **buf, u16 cid, u16 tid) | 
| +{ | 
| +	struct list_head *node; | 
| +	int result; | 
| +	struct client *client; | 
| +	struct notifyreq *notify; | 
| +	struct semaphore sem; | 
| +	void *data; | 
| +	unsigned long flags; | 
| +	u16 size; | 
| + | 
| +	if (!device_valid(dev)) { | 
| +		DBG("Invalid device!\n"); | 
| +		return -ENXIO; | 
| +	} | 
| + | 
| +	spin_lock_irqsave(&dev->qmi.clients_lock, flags); | 
| + | 
| +	client = client_bycid(dev, cid); | 
| +	if (!client) { | 
| +		DBG("Could not find matching client ID 0x%04X\n", cid); | 
| +		spin_unlock_irqrestore(&dev->qmi.clients_lock, flags); | 
| +		return -ENXIO; | 
| +	} | 
| + | 
| +	while (!client_delread(dev, cid, tid, &data, &size)) { | 
| +		sema_init(&sem, 0); | 
| +		if (!client_addnotify(dev, cid, tid, upsem, &sem)) { | 
| +			DBG("unable to register for notification\n"); | 
| +			spin_unlock_irqrestore(&dev->qmi.clients_lock, flags); | 
| +			return -EFAULT; | 
| +		} | 
| + | 
| +		spin_unlock_irqrestore(&dev->qmi.clients_lock, flags); | 
| + | 
| +		result = down_interruptible(&sem); | 
| +		if (result) { | 
| +			DBG("Interrupted %d\n", result); | 
| +			spin_lock_irqsave(&dev->qmi.clients_lock, flags); | 
| +			list_for_each(node, &client->notifies) { | 
| +				notify = list_entry(node, struct notifyreq, node); | 
| +				if (notify->data == &sem) { | 
| +					list_del(¬ify->node); | 
| +					kfree(notify); | 
| +					break; | 
| +				} | 
| +			} | 
| + | 
| +			spin_unlock_irqrestore(&dev->qmi.clients_lock, flags); | 
| +			return -EINTR; | 
| +		} | 
| + | 
| +		if (!device_valid(dev)) { | 
| +			DBG("Invalid device!\n"); | 
| +			return -ENXIO; | 
| +		} | 
| + | 
| +		spin_lock_irqsave(&dev->qmi.clients_lock, flags); | 
| +	} | 
| + | 
| +	spin_unlock_irqrestore(&dev->qmi.clients_lock, flags); | 
| +	*buf = data; | 
| +	return size; | 
| +} | 
| + | 
| +static void write_callback(struct urb *urb) | 
| +{ | 
| +	if (!urb) { | 
| +		DBG("null urb\n"); | 
| +		return; | 
| +	} | 
| + | 
| +	DBG("Write status/size %d/%d\n", urb->status, urb->actual_length); | 
| +	up((struct semaphore *)urb->context); | 
| +} | 
| + | 
| +static int write_sync(struct qcusbnet *dev, char *buf, int size, u16 cid) | 
| +{ | 
| +	int result; | 
| +	struct semaphore sem; | 
| +	struct urb *urb; | 
| +	struct urbsetup setup; | 
| +	unsigned long flags; | 
| + | 
| +	if (!device_valid(dev)) { | 
| +		DBG("Invalid device!\n"); | 
| +		return -ENXIO; | 
| +	} | 
| + | 
| +	urb = usb_alloc_urb(0, GFP_KERNEL); | 
| +	if (!urb) { | 
| +		DBG("URB mem error\n"); | 
| +		return -ENOMEM; | 
| +	} | 
| + | 
| +	result = qmux_fill(cid, buf, size); | 
| +	if (result < 0) { | 
| +		usb_free_urb(urb); | 
| +		return result; | 
| +	} | 
| + | 
| +	/* CDC Send Encapsulated Request packet */ | 
| +	setup.type = 0x21; | 
| +	setup.code = 0; | 
| +	setup.value = 0; | 
| +	setup.index = 0; | 
| +	setup.len = 0; | 
| +	setup.len = size; | 
| + | 
| +	usb_fill_control_urb(urb, dev->usbnet->udev, | 
| +			     usb_sndctrlpipe(dev->usbnet->udev, 0), | 
| +			     (unsigned char *)&setup, (void *)buf, size, | 
| +			     NULL, dev); | 
| + | 
| +	DBG("Actual Write:\n"); | 
| +	if (qcusbnet_debug) | 
| +		print_hex_dump(KERN_INFO,  "gobi-write: ", DUMP_PREFIX_OFFSET, | 
| +		               16, 1, buf, size, true); | 
| + | 
| +	sema_init(&sem, 0); | 
| + | 
| +	urb->complete = write_callback; | 
| +	urb->context = &sem; | 
| + | 
| +	result = usb_autopm_get_interface(dev->iface); | 
| +	if (result < 0) { | 
| +		DBG("unable to resume interface: %d\n", result); | 
| +		if (result == -EPERM) { | 
| +			qc_suspend(dev->iface, PMSG_SUSPEND); | 
| +		} | 
| +		return result; | 
| +	} | 
| + | 
| +	spin_lock_irqsave(&dev->qmi.clients_lock, flags); | 
| + | 
| +	if (!client_addurb(dev, cid, urb)) { | 
| +		usb_free_urb(urb); | 
| +		spin_unlock_irqrestore(&dev->qmi.clients_lock, flags); | 
| +		usb_autopm_put_interface(dev->iface); | 
| +		return -EINVAL; | 
| +	} | 
| + | 
| +	result = usb_submit_urb(urb, GFP_KERNEL); | 
| +	if (result < 0)	{ | 
| +		DBG("submit URB error %d\n", result); | 
| +		if (client_delurb(dev, cid) != urb) { | 
| +			DBG("Didn't get write URB back\n"); | 
| +		} | 
| + | 
| +		usb_free_urb(urb); | 
| + | 
| +		spin_unlock_irqrestore(&dev->qmi.clients_lock, flags); | 
| +		usb_autopm_put_interface(dev->iface); | 
| +		return result; | 
| +	} | 
| + | 
| +	spin_unlock_irqrestore(&dev->qmi.clients_lock, flags); | 
| +	result = down_interruptible(&sem); | 
| +	if (!device_valid(dev)) { | 
| +		DBG("Invalid device!\n"); | 
| +		return -ENXIO; | 
| +	} | 
| + | 
| +	usb_autopm_put_interface(dev->iface); | 
| +	spin_lock_irqsave(&dev->qmi.clients_lock, flags); | 
| +	if (client_delurb(dev, cid) != urb) { | 
| +		DBG("Didn't get write URB back\n"); | 
| +		spin_unlock_irqrestore(&dev->qmi.clients_lock, flags); | 
| +		return -EINVAL; | 
| +	} | 
| +	spin_unlock_irqrestore(&dev->qmi.clients_lock, flags); | 
| + | 
| +	if (!result) { | 
| +		if (!urb->status) { | 
| +			result = size; | 
| +		} else { | 
| +			DBG("bad status = %d\n", urb->status); | 
| +			result = urb->status; | 
| +		} | 
| +	} else { | 
| +		DBG("Interrupted %d !!!\n", result); | 
| +		DBG("Device may be in bad state and need reset !!!\n"); | 
| +		usb_kill_urb(urb); | 
| +	} | 
| + | 
| +	usb_free_urb(urb); | 
| +	return result; | 
| +} | 
| + | 
| +static int client_alloc(struct qcusbnet *dev, u8 type) | 
| +{ | 
| +	u16 cid; | 
| +	struct client *client; | 
| +	int result; | 
| +	void *wbuf; | 
| +	size_t wbufsize; | 
| +	void *rbuf; | 
| +	u16 rbufsize; | 
| +	unsigned long flags; | 
| +	u8 tid; | 
| + | 
| +	if (!device_valid(dev)) { | 
| +		DBG("Invalid device!\n"); | 
| +		return -ENXIO; | 
| +	} | 
| + | 
| +	if (type) { | 
| +		tid = atomic_add_return(1, &dev->qmi.qmitid); | 
| +		if (!tid) | 
| +			atomic_add_return(1, &dev->qmi.qmitid); | 
| +		wbuf = qmictl_new_getcid(tid, type, &wbufsize); | 
| +		if (!wbuf) | 
| +			return -ENOMEM; | 
| +		result = write_sync(dev, wbuf, wbufsize, QMICTL); | 
| +		kfree(wbuf); | 
| + | 
| +		if (result < 0) | 
| +			return result; | 
| + | 
| +		result = read_sync(dev, &rbuf, QMICTL, tid); | 
| +		if (result < 0) { | 
| +			DBG("bad read data %d\n", result); | 
| +			return result; | 
| +		} | 
| +		rbufsize = result; | 
| + | 
| +		result = qmictl_alloccid_resp(rbuf, rbufsize, &cid); | 
| +		kfree(rbuf); | 
| + | 
| +		if (result < 0) | 
| +			return result; | 
| +	} else { | 
| +		cid = 0; | 
| +	} | 
| + | 
| +	spin_lock_irqsave(&dev->qmi.clients_lock, flags); | 
| +	if (client_bycid(dev, cid)) { | 
| +		DBG("Client memory already exists\n"); | 
| +		spin_unlock_irqrestore(&dev->qmi.clients_lock, flags); | 
| +		return -ETOOMANYREFS; | 
| +	} | 
| + | 
| +	client = kmalloc(sizeof(*client), GFP_ATOMIC); | 
| +	if (!client) { | 
| +		DBG("Error allocating read list\n"); | 
| +		spin_unlock_irqrestore(&dev->qmi.clients_lock, flags); | 
| +		return -ENOMEM; | 
| +	} | 
| + | 
| +	list_add_tail(&client->node, &dev->qmi.clients); | 
| +	client->cid = cid; | 
| +	INIT_LIST_HEAD(&client->reads); | 
| +	INIT_LIST_HEAD(&client->notifies); | 
| +	INIT_LIST_HEAD(&client->urbs); | 
| +	spin_unlock_irqrestore(&dev->qmi.clients_lock, flags); | 
| +	return cid; | 
| +} | 
| + | 
| +static void client_free(struct qcusbnet *dev, u16 cid) | 
| +{ | 
| +	struct list_head *node, *tmp; | 
| +	int result; | 
| +	struct client *client; | 
| +	struct urb *urb; | 
| +	void *data; | 
| +	u16 size; | 
| +	void *wbuf; | 
| +	size_t wbufsize; | 
| +	void *rbuf; | 
| +	u16 rbufsize; | 
| +	unsigned long flags; | 
| +	u8 tid; | 
| + | 
| +	DBG("releasing 0x%04X\n", cid); | 
| + | 
| +	if (cid != QMICTL) { | 
| +		tid = atomic_add_return(1, &dev->qmi.qmitid); | 
| +		if (!tid) | 
| +			tid = atomic_add_return(1, &dev->qmi.qmitid); | 
| +		wbuf = qmictl_new_releasecid(tid, cid, &wbufsize); | 
| +		if (!wbuf) { | 
| +			DBG("memory error\n"); | 
| +		} else { | 
| +			result = write_sync(dev, wbuf, wbufsize, QMICTL); | 
| +			kfree(wbuf); | 
| + | 
| +			if (result < 0) { | 
| +				DBG("bad write status %d\n", result); | 
| +			} else { | 
| +				result = read_sync(dev, &rbuf, QMICTL, tid); | 
| +				if (result < 0) { | 
| +					DBG("bad read status %d\n", result); | 
| +				} else { | 
| +					rbufsize = result; | 
| +					result = qmictl_freecid_resp(rbuf, rbufsize); | 
| +					kfree(rbuf); | 
| +					if (result < 0) | 
| +						DBG("error %d parsing response\n", result); | 
| +				} | 
| +			} | 
| +		} | 
| +	} | 
| + | 
| +	spin_lock_irqsave(&dev->qmi.clients_lock, flags); | 
| +	list_for_each_safe(node, tmp, &dev->qmi.clients) { | 
| +		client = list_entry(node, struct client, node); | 
| +		if (client->cid == cid) { | 
| +			while (client_notify(dev, cid, 0)) { | 
| +				; | 
| +			} | 
| + | 
| +			urb = client_delurb(dev, cid); | 
| +			while (urb != NULL) { | 
| +				usb_kill_urb(urb); | 
| +				usb_free_urb(urb); | 
| +				urb = client_delurb(dev, cid); | 
| +			} | 
| + | 
| +			while (client_delread(dev, cid, 0, &data, &size)) | 
| +				kfree(data); | 
| + | 
| +			list_del(&client->node); | 
| +			kfree(client); | 
| +		} | 
| +	} | 
| +	spin_unlock_irqrestore(&dev->qmi.clients_lock, flags); | 
| +} | 
| + | 
| +struct client *client_bycid(struct qcusbnet *dev, u16 cid) | 
| +{ | 
| +	struct list_head *node; | 
| +	struct client *client; | 
| + | 
| +	if (!device_valid(dev))	{ | 
| +		DBG("Invalid device\n"); | 
| +		return NULL; | 
| +	} | 
| + | 
| +	assert_locked(dev); | 
| + | 
| +	list_for_each(node, &dev->qmi.clients) { | 
| +		client = list_entry(node, struct client, node); | 
| +		if (client->cid == cid) | 
| +			return client; | 
| +	} | 
| + | 
| +	DBG("Could not find client mem 0x%04X\n", cid); | 
| +	return NULL; | 
| +} | 
| + | 
| +static bool client_addread(struct qcusbnet *dev, u16 cid, u16 tid, void *data, | 
| +			   u16 size) | 
| +{ | 
| +	struct client *client; | 
| +	struct readreq *req; | 
| + | 
| +	assert_locked(dev); | 
| + | 
| +	client = client_bycid(dev, cid); | 
| +	if (!client) { | 
| +		DBG("Could not find this client's memory 0x%04X\n", cid); | 
| +		return false; | 
| +	} | 
| + | 
| +	req = kmalloc(sizeof(*req), GFP_ATOMIC); | 
| +	if (!req) { | 
| +		DBG("Mem error\n"); | 
| +		return false; | 
| +	} | 
| + | 
| +	req->data = data; | 
| +	req->size = size; | 
| +	req->tid = tid; | 
| + | 
| +	list_add_tail(&req->node, &client->reads); | 
| + | 
| +	return true; | 
| +} | 
| + | 
| +static bool client_delread(struct qcusbnet *dev, u16 cid, u16 tid, void **data, | 
| +			   u16 *size) | 
| +{ | 
| +	struct client *client; | 
| +	struct readreq *req; | 
| +	struct list_head *node; | 
| + | 
| +	assert_locked(dev); | 
| + | 
| +	client = client_bycid(dev, cid); | 
| +	if (!client) { | 
| +		DBG("Could not find this client's memory 0x%04X\n", cid); | 
| +		return false; | 
| +	} | 
| + | 
| +	list_for_each(node, &client->reads) { | 
| +		req = list_entry(node, struct readreq, node); | 
| +		if (!tid || tid == req->tid) { | 
| +			*data = req->data; | 
| +			*size = req->size; | 
| +			list_del(&req->node); | 
| +			kfree(req); | 
| +			return true; | 
| +		} | 
| + | 
| +		DBG("skipping 0x%04X data TID = %x\n", cid, req->tid); | 
| +	} | 
| + | 
| +	DBG("No read memory to pop, Client 0x%04X, TID = %x\n", cid, tid); | 
| +	return false; | 
| +} | 
| + | 
| +static bool client_addnotify(struct qcusbnet *dev, u16 cid, u16 tid, | 
| +			     void (*hook)(struct qcusbnet *, u16, void *), | 
| +			     void *data) | 
| +{ | 
| +	struct client *client; | 
| +	struct notifyreq *req; | 
| + | 
| +	assert_locked(dev); | 
| + | 
| +	client = client_bycid(dev, cid); | 
| +	if (!client) { | 
| +		DBG("Could not find this client's memory 0x%04X\n", cid); | 
| +		return false; | 
| +	} | 
| + | 
| +	req = kmalloc(sizeof(*req), GFP_ATOMIC); | 
| +	if (!req) { | 
| +		DBG("Mem error\n"); | 
| +		return false; | 
| +	} | 
| + | 
| +	list_add_tail(&req->node, &client->notifies); | 
| +	req->func = hook; | 
| +	req->data = data; | 
| +	req->tid = tid; | 
| + | 
| +	return true; | 
| +} | 
| + | 
| +static bool client_notify(struct qcusbnet *dev, u16 cid, u16 tid) | 
| +{ | 
| +	struct client *client; | 
| +	struct notifyreq *delnotify, *notify; | 
| +	struct list_head *node; | 
| + | 
| +	assert_locked(dev); | 
| + | 
| +	client = client_bycid(dev, cid); | 
| +	if (!client) { | 
| +		DBG("Could not find this client's memory 0x%04X\n", cid); | 
| +		return false; | 
| +	} | 
| + | 
| +	delnotify = NULL; | 
| + | 
| +	list_for_each(node, &client->notifies) { | 
| +		notify = list_entry(node, struct notifyreq, node); | 
| +		if (!tid || !notify->tid || tid == notify->tid) { | 
| +			delnotify = notify; | 
| +			break; | 
| +		} | 
| + | 
| +		DBG("skipping data TID = %x\n", notify->tid); | 
| +	} | 
| + | 
| +	if (delnotify) { | 
| +		list_del(&delnotify->node); | 
| +		if (delnotify->func) { | 
| +			spin_unlock(&dev->qmi.clients_lock); | 
| +			delnotify->func(dev, cid, delnotify->data); | 
| +			spin_lock(&dev->qmi.clients_lock); | 
| +		} | 
| +		kfree(delnotify); | 
| +		return true; | 
| +	} | 
| + | 
| +	DBG("no one to notify for TID %x\n", tid); | 
| +	return false; | 
| +} | 
| + | 
| +static bool client_addurb(struct qcusbnet *dev, u16 cid, struct urb *urb) | 
| +{ | 
| +	struct client *client; | 
| +	struct urbreq *req; | 
| + | 
| +	assert_locked(dev); | 
| + | 
| +	client = client_bycid(dev, cid); | 
| +	if (!client) { | 
| +		DBG("Could not find this client's memory 0x%04X\n", cid); | 
| +		return false; | 
| +	} | 
| + | 
| +	req = kmalloc(sizeof(*req), GFP_ATOMIC); | 
| +	if (!req) { | 
| +		DBG("Mem error\n"); | 
| +		return false; | 
| +	} | 
| + | 
| +	req->urb = urb; | 
| +	list_add_tail(&req->node, &client->urbs); | 
| + | 
| +	return true; | 
| +} | 
| + | 
| +static struct urb *client_delurb(struct qcusbnet *dev, u16 cid) | 
| +{ | 
| +	struct client *client; | 
| +	struct urbreq *req; | 
| +	struct urb *urb; | 
| + | 
| +	assert_locked(dev); | 
| + | 
| +	client = client_bycid(dev, cid); | 
| +	if (!client) { | 
| +		DBG("Could not find this client's memory 0x%04X\n", cid); | 
| +		return NULL; | 
| +	} | 
| + | 
| +	if (list_empty(&client->urbs)) { | 
| +		DBG("No URB's to pop\n"); | 
| +		return NULL; | 
| +	} | 
| + | 
| +	req = list_first_entry(&client->urbs, struct urbreq, node); | 
| +	list_del(&req->node); | 
| +	urb = req->urb; | 
| +	kfree(req); | 
| +	return urb; | 
| +} | 
| + | 
| +static int devqmi_open(struct inode *inode, struct file *file) | 
| +{ | 
| +	struct qmihandle *handle; | 
| +	struct qmidev *qmidev = container_of(inode->i_cdev, struct qmidev, cdev); | 
| +	struct qcusbnet *dev = container_of(qmidev, struct qcusbnet, qmi); | 
| + | 
| +	/* We need an extra ref on the device per fd, since we stash a ref | 
| +	 * inside the handle. If qcusbnet_get() returns NULL, that means the | 
| +	 * device has been removed from the list - no new refs for us. */ | 
| +	struct qcusbnet *ref = qcusbnet_get(dev); | 
| + | 
| +	if (!ref) | 
| +		return -ENXIO; | 
| + | 
| +	file->private_data = kmalloc(sizeof(struct qmihandle), GFP_KERNEL); | 
| +	if (!file->private_data) { | 
| +		DBG("Mem error\n"); | 
| +		return -ENOMEM; | 
| +	} | 
| + | 
| +	handle = (struct qmihandle *)file->private_data; | 
| +	handle->cid = (u16)-1; | 
| +	handle->dev = ref; | 
| + | 
| +	DBG("%p %04x", handle, handle->cid); | 
| + | 
| +	return 0; | 
| +} | 
| + | 
| +static int devqmi_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) | 
| +{ | 
| +	int result; | 
| +	u32 vidpid; | 
| + | 
| +	struct qmihandle *handle = (struct qmihandle *)file->private_data; | 
| + | 
| +	DBG("%p %04x %08x", handle, handle->cid, cmd); | 
| + | 
| +	if (!handle) { | 
| +		DBG("Bad file data\n"); | 
| +		return -EBADF; | 
| +	} | 
| + | 
| +	if (handle->dev->dying) { | 
| +		DBG("Dying device"); | 
| +		return -ENXIO; | 
| +	} | 
| + | 
| +	if (!device_valid(handle->dev)) { | 
| +		DBG("Invalid device!\n"); | 
| +		return -ENXIO; | 
| +	} | 
| + | 
| +	switch (cmd) { | 
| +	case IOCTL_QMI_GET_SERVICE_FILE: | 
| + | 
| +		DBG("Setting up QMI for service %lu\n", arg); | 
| +		if (!(u8)arg) { | 
| +			DBG("Cannot use QMICTL from userspace\n"); | 
| +			return -EINVAL; | 
| +		} | 
| + | 
| +		if (handle->cid != (u16)-1) { | 
| +			DBG("Close the current connection before opening a new one\n"); | 
| +			return -EBADR; | 
| +		} | 
| + | 
| +		result = client_alloc(handle->dev, (u8)arg); | 
| +		if (result < 0) | 
| +			return result; | 
| +		handle->cid = result; | 
| + | 
| +		return 0; | 
| +		break; | 
| + | 
| +	/* Okay, all aboard the nasty hack express. If we don't have this | 
| +	 * ioctl() (and we just rely on userspace to close() the file | 
| +	 * descriptors), if userspace has any refs left to this fd (like, say, a | 
| +	 * pending read()), then the read might hang around forever. Userspace | 
| +	 * needs a way to cause us to kick people off those waitqueues before | 
| +	 * closing the fd for good. | 
| +	 * | 
| +	 * If this driver used workqueues, the correct approach here would | 
| +	 * instead be to make the file descriptor select()able, and then just | 
| +	 * use select() instead of aio in userspace (thus allowing us to get | 
| +	 * away with one thread total and avoiding the recounting mess | 
| +	 * altogether). | 
| +	 */ | 
| +	case IOCTL_QMI_CLOSE: | 
| +		DBG("Tearing down QMI for service %lu", arg); | 
| +		if (handle->cid == (u16)-1) { | 
| +			DBG("no qmi cid"); | 
| +			return -EBADR; | 
| +		} | 
| + | 
| +		file->private_data = NULL; | 
| +		client_free(handle->dev, handle->cid); | 
| +		kfree(handle); | 
| +		return 0; | 
| +		break; | 
| + | 
| +	case IOCTL_QMI_GET_DEVICE_VIDPID: | 
| +		if (!arg) { | 
| +			DBG("Bad VIDPID buffer\n"); | 
| +			return -EINVAL; | 
| +		} | 
| + | 
| +		if (!handle->dev->usbnet) { | 
| +			DBG("Bad usbnet\n"); | 
| +			return -ENOMEM; | 
| +		} | 
| + | 
| +		if (!handle->dev->usbnet->udev) { | 
| +			DBG("Bad udev\n"); | 
| +			return -ENOMEM; | 
| +		} | 
| + | 
| +		vidpid = ((le16_to_cpu(handle->dev->usbnet->udev->descriptor.idVendor) << 16) | 
| +			  + le16_to_cpu(handle->dev->usbnet->udev->descriptor.idProduct)); | 
| + | 
| +		result = copy_to_user((unsigned int *)arg, &vidpid, 4); | 
| +		if (result) | 
| +			DBG("Copy to userspace failure\n"); | 
| + | 
| +		return result; | 
| +		break; | 
| + | 
| +	case IOCTL_QMI_GET_DEVICE_MEID: | 
| +		if (!arg) { | 
| +			DBG("Bad MEID buffer\n"); | 
| +			return -EINVAL; | 
| +		} | 
| + | 
| +		result = copy_to_user((unsigned int *)arg, &handle->dev->meid[0], 14); | 
| +		if (result) | 
| +			DBG("copy to userspace failure\n"); | 
| + | 
| +		return result; | 
| +		break; | 
| +	default: | 
| +		return -EBADRQC; | 
| +	} | 
| +} | 
| + | 
| +static int devqmi_release(struct inode *inode, struct file *file) | 
| +{ | 
| +	struct qmihandle *handle = (struct qmihandle *)file->private_data; | 
| +	if (!handle) | 
| +		return 0; | 
| +	file->private_data = NULL; | 
| +	if (handle->cid != (u16)-1) | 
| +		client_free(handle->dev, handle->cid); | 
| +	qcusbnet_put(handle->dev); | 
| +	kfree(handle); | 
| +	return 0; | 
| +} | 
| + | 
| +static ssize_t devqmi_read(struct file *file, char __user *buf, size_t size, | 
| +			   loff_t *pos) | 
| +{ | 
| +	int result; | 
| +	void *data = NULL; | 
| +	void *smalldata; | 
| +	struct qmihandle *handle = (struct qmihandle *)file->private_data; | 
| + | 
| +	if (!handle) { | 
| +		DBG("Bad file data\n"); | 
| +		return -EBADF; | 
| +	} | 
| + | 
| +	if (handle->dev->dying) { | 
| +		DBG("Dying device"); | 
| +		return -ENXIO; | 
| +	} | 
| + | 
| +	if (!device_valid(handle->dev)) { | 
| +		DBG("Invalid device!\n"); | 
| +		return -ENXIO; | 
| +	} | 
| + | 
| +	if (handle->cid == (u16)-1) { | 
| +		DBG("Client ID must be set before reading 0x%04X\n", | 
| +		    handle->cid); | 
| +		return -EBADR; | 
| +	} | 
| + | 
| +	result = read_sync(handle->dev, &data, handle->cid, 0); | 
| +	if (result <= 0) | 
| +		return result; | 
| + | 
| +	result -= qmux_size; | 
| +	smalldata = data + qmux_size; | 
| + | 
| +	if (result > size) { | 
| +		DBG("Read data is too large for amount user has requested\n"); | 
| +		kfree(data); | 
| +		return -EOVERFLOW; | 
| +	} | 
| + | 
| +	if (copy_to_user(buf, smalldata, result)) { | 
| +		DBG("Error copying read data to user\n"); | 
| +		result = -EFAULT; | 
| +	} | 
| + | 
| +	kfree(data); | 
| +	return result; | 
| +} | 
| + | 
| +static ssize_t devqmi_write(struct file *file, const char __user * buf, | 
| +			    size_t size, loff_t *pos) | 
| +{ | 
| +	int status; | 
| +	void *wbuf; | 
| +	struct qmihandle *handle = (struct qmihandle *)file->private_data; | 
| + | 
| +	if (!handle) { | 
| +		DBG("Bad file data\n"); | 
| +		return -EBADF; | 
| +	} | 
| + | 
| +	if (!device_valid(handle->dev)) { | 
| +		DBG("Invalid device! Updating f_ops\n"); | 
| +		file->f_op = file->f_dentry->d_inode->i_fop; | 
| +		return -ENXIO; | 
| +	} | 
| + | 
| +	if (handle->cid == (u16)-1) { | 
| +		DBG("Client ID must be set before writing 0x%04X\n", | 
| +			  handle->cid); | 
| +		return -EBADR; | 
| +	} | 
| + | 
| +	wbuf = kmalloc(size + qmux_size, GFP_KERNEL); | 
| +	if (!wbuf) | 
| +		return -ENOMEM; | 
| +	status = copy_from_user(wbuf + qmux_size, buf, size); | 
| +	if (status) { | 
| +		DBG("Unable to copy data from userspace %d\n", status); | 
| +		kfree(wbuf); | 
| +		return status; | 
| +	} | 
| + | 
| +	status = write_sync(handle->dev, wbuf, size + qmux_size, | 
| +			    handle->cid); | 
| + | 
| +	kfree(wbuf); | 
| +	if (status == size + qmux_size) | 
| +		return size; | 
| +	return status; | 
| +} | 
| + | 
| +int qc_register(struct qcusbnet *dev) | 
| +{ | 
| +	int result; | 
| +	int qmiidx = 0; | 
| +	dev_t devno; | 
| +	char *name; | 
| + | 
| +	dev->valid = true; | 
| +	dev->dying = false; | 
| +	result = client_alloc(dev, QMICTL); | 
| +	if (result) { | 
| +		dev->valid = false; | 
| +		return result; | 
| +	} | 
| +	atomic_set(&dev->qmi.qmitid, 1); | 
| + | 
| +	result = qc_startread(dev); | 
| +	if (result) { | 
| +		dev->valid = false; | 
| +		return result; | 
| +	} | 
| + | 
| +	if (!qmi_ready(dev, 30000)) { | 
| +		DBG("Device unresponsive to QMI\n"); | 
| +		return -ETIMEDOUT; | 
| +	} | 
| + | 
| +	result = setup_wds_callback(dev); | 
| +	if (result) { | 
| +		dev->valid = false; | 
| +		return result; | 
| +	} | 
| + | 
| +	result = qmidms_getmeid(dev); | 
| +	if (result) { | 
| +		dev->valid = false; | 
| +		return result; | 
| +	} | 
| + | 
| +	result = alloc_chrdev_region(&devno, 0, 1, "qcqmi"); | 
| +	if (result < 0) | 
| +		return result; | 
| + | 
| +	cdev_init(&dev->qmi.cdev, &devqmi_fops); | 
| +	dev->qmi.cdev.owner = THIS_MODULE; | 
| +	dev->qmi.cdev.ops = &devqmi_fops; | 
| + | 
| +	result = cdev_add(&dev->qmi.cdev, devno, 1); | 
| +	if (result) { | 
| +		DBG("error adding cdev\n"); | 
| +		return result; | 
| +	} | 
| + | 
| +	name = strstr(dev->usbnet->net->name, "usb"); | 
| +	if (!name) { | 
| +		DBG("Bad net name: %s\n", dev->usbnet->net->name); | 
| +		return -ENXIO; | 
| +	} | 
| +	name += strlen("usb"); | 
| +	qmiidx = simple_strtoul(name, NULL, 10); | 
| +	if (qmiidx < 0) { | 
| +		DBG("Bad minor number\n"); | 
| +		return -ENXIO; | 
| +	} | 
| + | 
| +	printk(KERN_INFO "creating qcqmi%d\n", qmiidx); | 
| +	device_create(dev->qmi.devclass, &dev->iface->dev, devno, NULL, "qcqmi%d", qmiidx); | 
| + | 
| +	dev->qmi.devnum = devno; | 
| +	return 0; | 
| +} | 
| + | 
| +void qc_deregister(struct qcusbnet *dev) | 
| +{ | 
| +	struct list_head *node, *tmp; | 
| +	struct client *client; | 
| + | 
| +	dev->dying = true; | 
| +	list_for_each_safe(node, tmp, &dev->qmi.clients) { | 
| +		client = list_entry(node, struct client, node); | 
| +		DBG("release 0x%04X\n", client->cid); | 
| +		client_free(dev, client->cid); | 
| +	} | 
| + | 
| +	qc_stopread(dev); | 
| +	dev->valid = false; | 
| +	if (!IS_ERR(dev->qmi.devclass)) | 
| +		device_destroy(dev->qmi.devclass, dev->qmi.devnum); | 
| +	cdev_del(&dev->qmi.cdev); | 
| +	unregister_chrdev_region(dev->qmi.devnum, 1); | 
| +} | 
| + | 
| +static bool qmi_ready(struct qcusbnet *dev, u16 timeout) | 
| +{ | 
| +	int result; | 
| +	void *wbuf; | 
| +	size_t wbufsize; | 
| +	void *rbuf; | 
| +	u16 rbufsize; | 
| +	struct semaphore sem; | 
| +	u16 now; | 
| +	unsigned long flags; | 
| +	u8 tid; | 
| + | 
| +	if (!device_valid(dev)) { | 
| +		DBG("Invalid device\n"); | 
| +		return -EFAULT; | 
| +	} | 
| + | 
| + | 
| +	for (now = 0; now < timeout; now += 100) { | 
| +		sema_init(&sem, 0); | 
| + | 
| +		tid = atomic_add_return(1, &dev->qmi.qmitid); | 
| +		if (!tid) | 
| +			tid = atomic_add_return(1, &dev->qmi.qmitid); | 
| +		kfree(wbuf); | 
| +		wbuf = qmictl_new_ready(tid, &wbufsize); | 
| +		if (!wbuf) | 
| +			return -ENOMEM; | 
| + | 
| +		result = read_async(dev, QMICTL, tid, upsem, &sem); | 
| +		if (result) { | 
| +			kfree(wbuf); | 
| +			return false; | 
| +		} | 
| + | 
| +		write_sync(dev, wbuf, wbufsize, QMICTL); | 
| + | 
| +		msleep(100); | 
| +		if (!down_trylock(&sem)) { | 
| +			spin_lock_irqsave(&dev->qmi.clients_lock, flags); | 
| +			if (client_delread(dev,	QMICTL,	tid, &rbuf, &rbufsize)) { | 
| +				spin_unlock_irqrestore(&dev->qmi.clients_lock, flags); | 
| +				kfree(rbuf); | 
| +				break; | 
| +			} else { | 
| +				spin_unlock_irqrestore(&dev->qmi.clients_lock, flags); | 
| +			} | 
| +		} else { | 
| +			spin_lock_irqsave(&dev->qmi.clients_lock, flags); | 
| +			client_notify(dev, QMICTL, tid); | 
| +			spin_unlock_irqrestore(&dev->qmi.clients_lock, flags); | 
| +		} | 
| +	} | 
| + | 
| +	kfree(wbuf); | 
| + | 
| +	if (now >= timeout) | 
| +		return false; | 
| + | 
| +	DBG("QMI Ready after %u milliseconds\n", now); | 
| + | 
| +	/* 3580 and newer doesn't need a delay; older needs 5000ms */ | 
| +	if (qcusbnet2k_fwdelay) | 
| +		msleep(qcusbnet2k_fwdelay * 1000); | 
| + | 
| +	return true; | 
| +} | 
| + | 
| +static void wds_callback(struct qcusbnet *dev, u16 cid, void *data) | 
| +{ | 
| +	bool ret; | 
| +	int result; | 
| +	void *rbuf; | 
| +	u16 rbufsize; | 
| + | 
| +	struct net_device_stats *stats = &(dev->usbnet->net->stats); | 
| + | 
| +	struct qmiwds_stats dstats = { | 
| +		.txok = (u32)-1, | 
| +		.rxok = (u32)-1, | 
| +		.txerr = (u32)-1, | 
| +		.rxerr = (u32)-1, | 
| +		.txofl = (u32)-1, | 
| +		.rxofl = (u32)-1, | 
| +		.txbytesok = (u64)-1, | 
| +		.rxbytesok = (u64)-1, | 
| +	}; | 
| +	unsigned long flags; | 
| + | 
| +	if (!device_valid(dev)) { | 
| +		DBG("Invalid device\n"); | 
| +		return; | 
| +	} | 
| + | 
| +	spin_lock_irqsave(&dev->qmi.clients_lock, flags); | 
| +	ret = client_delread(dev, cid, 0, &rbuf, &rbufsize); | 
| +	spin_unlock_irqrestore(&dev->qmi.clients_lock, flags); | 
| + | 
| +	if (!ret) { | 
| +		DBG("WDS callback failed to get data\n"); | 
| +		return; | 
| +	} | 
| + | 
| +	dstats.linkstate = !qc_isdown(dev, DOWN_NO_NDIS_CONNECTION); | 
| +	dstats.reconfigure = false; | 
| + | 
| +	result = qmiwds_event_resp(rbuf, rbufsize, &dstats); | 
| +	if (result < 0) { | 
| +		DBG("bad WDS packet\n"); | 
| +	} else { | 
| +		if (dstats.txofl != (u32)-1) | 
| +			stats->tx_fifo_errors = dstats.txofl; | 
| + | 
| +		if (dstats.rxofl != (u32)-1) | 
| +			stats->rx_fifo_errors = dstats.rxofl; | 
| + | 
| +		if (dstats.txerr != (u32)-1) | 
| +			stats->tx_errors = dstats.txerr; | 
| + | 
| +		if (dstats.rxerr != (u32)-1) | 
| +			stats->rx_errors = dstats.rxerr; | 
| + | 
| +		if (dstats.txok != (u32)-1) | 
| +			stats->tx_packets = dstats.txok + stats->tx_errors; | 
| + | 
| +		if (dstats.rxok != (u32)-1) | 
| +			stats->rx_packets = dstats.rxok + stats->rx_errors; | 
| + | 
| +		if (dstats.txbytesok != (u64)-1) | 
| +			stats->tx_bytes = dstats.txbytesok; | 
| + | 
| +		if (dstats.rxbytesok != (u64)-1) | 
| +			stats->rx_bytes = dstats.rxbytesok; | 
| + | 
| +		if (dstats.reconfigure) { | 
| +			DBG("Net device link reset\n"); | 
| +			qc_setdown(dev, DOWN_NO_NDIS_CONNECTION); | 
| +			qc_cleardown(dev, DOWN_NO_NDIS_CONNECTION); | 
| +		} else { | 
| +			if (dstats.linkstate) { | 
| +				DBG("Net device link is connected\n"); | 
| +				qc_cleardown(dev, DOWN_NO_NDIS_CONNECTION); | 
| +			} else { | 
| +				DBG("Net device link is disconnected\n"); | 
| +				qc_setdown(dev, DOWN_NO_NDIS_CONNECTION); | 
| +			} | 
| +		} | 
| +	} | 
| + | 
| +	kfree(rbuf); | 
| + | 
| +	result = read_async(dev, cid, 0, wds_callback, data); | 
| +	if (result != 0) | 
| +		DBG("unable to setup next async read\n"); | 
| +} | 
| + | 
| +static int setup_wds_callback(struct qcusbnet *dev) | 
| +{ | 
| +	int result; | 
| +	void *buf; | 
| +	size_t size; | 
| +	u16 cid; | 
| + | 
| +	if (!device_valid(dev)) { | 
| +		DBG("Invalid device\n"); | 
| +		return -EFAULT; | 
| +	} | 
| + | 
| +	result = client_alloc(dev, QMIWDS); | 
| +	if (result < 0) | 
| +		return result; | 
| +	cid = result; | 
| + | 
| +	buf = qmiwds_new_seteventreport(1, &size); | 
| +	if (!buf) | 
| +		return -ENOMEM; | 
| + | 
| +	result = write_sync(dev, buf, size, cid); | 
| +	kfree(buf); | 
| + | 
| +	if (result < 0) { | 
| +		return result; | 
| +	} | 
| + | 
| +	buf = qmiwds_new_getpkgsrvcstatus(2, &size); | 
| +	if (buf == NULL) | 
| +		return -ENOMEM; | 
| + | 
| +	result = write_sync(dev, buf, size, cid); | 
| +	kfree(buf); | 
| + | 
| +	if (result < 0) | 
| +		return result; | 
| + | 
| +	result = read_async(dev, cid, 0, wds_callback, NULL); | 
| +	if (result) { | 
| +		DBG("unable to setup async read\n"); | 
| +		return result; | 
| +	} | 
| + | 
| +	result = usb_control_msg(dev->usbnet->udev, | 
| +				 usb_sndctrlpipe(dev->usbnet->udev, 0), | 
| +				 0x22, 0x21, 1, 0, NULL, 0, 100); | 
| +	if (result < 0) { | 
| +		DBG("Bad SetControlLineState status %d\n", result); | 
| +		return result; | 
| +	} | 
| + | 
| +	return 0; | 
| +} | 
| + | 
| +static int qmidms_getmeid(struct qcusbnet *dev) | 
| +{ | 
| +	int result; | 
| +	void *wbuf; | 
| +	size_t wbufsize; | 
| +	void *rbuf; | 
| +	u16 rbufsize; | 
| +	u16 cid; | 
| + | 
| +	if (!device_valid(dev))	{ | 
| +		DBG("Invalid device\n"); | 
| +		return -EFAULT; | 
| +	} | 
| + | 
| +	result = client_alloc(dev, QMIDMS); | 
| +	if (result < 0) | 
| +		return result; | 
| +	cid = result; | 
| + | 
| +	wbuf = qmidms_new_getmeid(1, &wbufsize); | 
| +	if (!wbuf) | 
| +		return -ENOMEM; | 
| + | 
| +	result = write_sync(dev, wbuf, wbufsize, cid); | 
| +	kfree(wbuf); | 
| + | 
| +	if (result < 0) | 
| +		return result; | 
| + | 
| +	result = read_sync(dev, &rbuf, cid, 1); | 
| +	if (result < 0) | 
| +		return result; | 
| +	rbufsize = result; | 
| + | 
| +	result = qmidms_meid_resp(rbuf, rbufsize, &dev->meid[0], 14); | 
| +	kfree(rbuf); | 
| + | 
| +	if (result < 0) { | 
| +		DBG("bad get MEID resp\n"); | 
| +		memset(&dev->meid[0], '0', 14); | 
| +	} | 
| + | 
| +	client_free(dev, cid); | 
| +	return 0; | 
| +} | 
| + | 
| +module_param(qcusbnet2k_fwdelay, int, S_IRUGO | S_IWUSR); | 
| +MODULE_PARM_DESC(qcusbnet2k_fwdelay, "Delay for old firmware"); | 
|  |