usb: serial: baseband: Restructure open and close
Vinayak Pane [Tue, 20 Mar 2012 01:18:09 +0000 (18:18 -0700)]
This commit reorganizes the usb chr driver for
 (1) Application does not have to rmmod & insmod
 (2) Application recovery mechanism to restart download
 (3) Change memory allocation policy to accommodate in low-mem
 situations.
 (4) Avoid kernel panic when module is not removed

Bug 947621
Bug 956211

Reviewed-on: http://git-master/r/91373
(cherry picked from commit bb5a148979a92191e0dfb4d97d4942f877f18309)

Change-Id: I2679d1d5f94cfe6e7dc98df0026f64cab703fe5c
Signed-off-by: Vinayak Pane <vpane@nvidia.com>
Reviewed-on: http://git-master/r/96334
Reviewed-by: Automatic_Commit_Validation_User
Reviewed-by: Steve Lin <stlin@nvidia.com>

drivers/usb/serial/baseband_usb_chr.c
drivers/usb/serial/baseband_usb_chr.h

index 6d691a4..cad33d5 100644 (file)
@@ -3,7 +3,7 @@
  *
  * USB character driver to communicate with baseband modems.
  *
- * Copyright (c) 2011, NVIDIA Corporation.
+ * Copyright (c) 2012, NVIDIA Corporation.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -33,8 +33,9 @@
 #include <linux/errno.h>
 #include <linux/usb.h>
 #include <linux/workqueue.h>
-#include <asm/ioctls.h>
 #include <linux/uaccess.h>
+#include <linux/vmalloc.h>
+#include <asm/ioctls.h>
 #include "baseband_usb_chr.h"
 
 MODULE_LICENSE("GPL");
@@ -51,7 +52,9 @@ module_param(baseband_usb_chr_intf, ulong, 0644);
 MODULE_PARM_DESC(baseband_usb_chr_intf, "baseband (usb chr) - USB interface");
 
 static struct baseband_usb *baseband_usb_chr;
+static struct usb_interface *probe_usb_intf;
 static bool usb_device_connection;
+static struct workqueue_struct *chr_ipc_wq;
 
 static atomic_t g_rx_count = ATOMIC_INIT(0);
 
@@ -119,7 +122,7 @@ static size_t peek_ipc_tx_bufsiz(struct baseband_ipc *ipc,
 
        /* check input */
        if (!ipc) {
-               pr_err("!ipc\n");
+               pr_err("%s: !ipc\n", __func__);
                return 0;
        }
 
@@ -159,7 +162,7 @@ static size_t get_ipc_tx_buf(struct baseband_ipc *ipc,
 
        /* check input */
        if (!ipc || !buf) {
-               pr_err("!ipc || !buf\n");
+               pr_err("%s: !ipc || !buf\n", __func__);
                return 0;
        }
        if (!bufsiz)
@@ -221,12 +224,13 @@ static size_t put_ipc_rx_buf(struct baseband_ipc *ipc,
 {
        struct baseband_ipc_buf *ipc_buf, *ipc_buf_next;
        size_t rx_bufsiz;
+       int ret;
 
        pr_debug("put_ipc_rx_buf\n");
 
        /* check input */
        if (!ipc || !buf) {
-               pr_err("!ipc || !buf\n");
+               pr_err("%s: !ipc || !buf\n", __func__);
                return 0;
        }
        if (!bufsiz)
@@ -277,8 +281,13 @@ retry:
 
        /* wait for rx free buffer available */
        if (!rx_bufsiz) {
-               if (wait_event_interruptible(ipc->rx_free.wait,
-                       !list_empty(&ipc->rx_free.buf))) {
+               ret = wait_event_interruptible_timeout(ipc->rx_free.wait,
+                       !list_empty(&ipc->rx_free.buf), HZ*2);
+               if (ret == 0) {
+                       pr_err("%s timeout occured no wait\n", __func__);
+                       return -ETIMEDOUT;
+               }
+               if (ret == -ERESTARTSYS) {
                        pr_err("put_ipc_rx_buf - "
                                "interrupted wait\n");
                        return -ERESTARTSYS;
@@ -299,8 +308,8 @@ static ssize_t baseband_ipc_file_read(struct baseband_ipc *ipc,
        pr_debug("baseband_ipc_file_read\n");
 
        /* check input */
-       if (!ipc) {
-               pr_err("!ipc\n");
+       if (!ipc || !buf) {
+               pr_err("%s: !ipc || !buf\n", __func__);
                return -EIO;
        }
 
@@ -384,11 +393,19 @@ static ssize_t baseband_ipc_file_write(struct baseband_ipc *ipc,
        pr_debug("baseband_ipc_file_write\n");
 
        /* check input */
-       if (!ipc) {
-               pr_err("!ipc\n");
+       if (!ipc || !buf) {
+               pr_err("%s: !ipc || !buf\n", __func__);
                return -EIO;
        }
 
+       /* do not accept write if previous tx not finished */
+       if (peek_ipc_tx_bufsiz(ipc, USB_CHR_TX_BUFSIZ) != 0) {
+               pr_info("%s: not accepting write of %u bytes"
+                       " - previous tx not finished\n",
+                       __func__, count);
+               return 0;
+       }
+
        /* acquire tx buffer semaphores */
 retry:
        if (down_interruptible(&ipc->buf_sem)) {
@@ -473,12 +490,10 @@ static void baseband_ipc_close(struct baseband_ipc *ipc)
        if (!ipc)
                return;
 
-       /* destroy work queue */
+       /* cancel work queue */
        if (ipc->workqueue) {
                pr_debug("destroy workqueue {\n");
                cancel_work_sync(&ipc->work);
-               destroy_workqueue(ipc->workqueue);
-               ipc->workqueue = (struct workqueue_struct *) 0;
                pr_debug("destroy workqueue }\n");
        }
        memset(&ipc->work, 0, sizeof(ipc->work));
@@ -496,26 +511,26 @@ static void baseband_ipc_close(struct baseband_ipc *ipc)
        ipc->ipc_rx = (unsigned char *) 0;
        list_for_each_entry_safe(ipc_buf, ipc_buf_next, &ipc->tx_free.buf, list)
        {
-               kfree(ipc_buf);
+               vfree(ipc_buf);
        }
        list_for_each_entry_safe(ipc_buf, ipc_buf_next, &ipc->rx_free.buf, list)
        {
-               kfree(ipc_buf);
+               vfree(ipc_buf);
        }
        list_for_each_entry_safe(ipc_buf, ipc_buf_next, &ipc->tx.buf, list)
        {
-               kfree(ipc_buf);
+               vfree(ipc_buf);
        }
        list_for_each_entry_safe(ipc_buf, ipc_buf_next, &ipc->rx.buf, list)
        {
-               kfree(ipc_buf);
+               vfree(ipc_buf);
        }
 
        /* destroy semaphores */
        memset(&ipc->buf_sem, 0, sizeof(ipc->buf_sem));
 
        /* free baseband ipc structure */
-       kfree(ipc);
+       vfree(ipc);
 
        pr_debug("baseband_ipc_close }\n");
 }
@@ -531,10 +546,11 @@ static struct baseband_ipc *baseband_ipc_open(work_func_t work_func,
        pr_debug("baseband_ipc_open {\n");
 
        /* allocate baseband ipc structure */
-       ipc = kzalloc(sizeof(struct baseband_ipc), GFP_KERNEL);
+       ipc = vmalloc(sizeof(struct baseband_ipc));
        if (!ipc)
                return (struct baseband_ipc *) 0;
 
+       memset(ipc, 0 , sizeof(struct baseband_ipc));
        /* create semaphores */
        sema_init(&ipc->buf_sem, 1);
 
@@ -545,7 +561,7 @@ static struct baseband_ipc *baseband_ipc_open(work_func_t work_func,
        INIT_LIST_HEAD(&ipc->tx_free.buf);
        for (i = 0; i < BASEBAND_IPC_NUM_RX_BUF; i++) {
                ipc_buf = (struct baseband_ipc_buf *)
-                       kzalloc(sizeof(struct baseband_ipc_buf), GFP_KERNEL);
+                       vmalloc(sizeof(struct baseband_ipc_buf));
                if (!ipc_buf) {
                        pr_err("cannot allocate baseband ipc rx buffer #%d\n",
                                i);
@@ -558,7 +574,7 @@ static struct baseband_ipc *baseband_ipc_open(work_func_t work_func,
        }
        for (i = 0; i < BASEBAND_IPC_NUM_TX_BUF; i++) {
                ipc_buf = (struct baseband_ipc_buf *)
-                       kzalloc(sizeof(struct baseband_ipc_buf), GFP_KERNEL);
+                       vmalloc(sizeof(struct baseband_ipc_buf));
                if (!ipc_buf) {
                        pr_err("cannot allocate baseband ipc tx buffer #%d\n",
                                i);
@@ -569,8 +585,18 @@ static struct baseband_ipc *baseband_ipc_open(work_func_t work_func,
                        ipc_buf);
                list_add_tail(&ipc_buf->list, &ipc->tx_free.buf);
        }
-       ipc->ipc_rx = (unsigned char *) 0;
-       ipc->ipc_tx = (unsigned char *) 0;
+       ipc->ipc_rx = kmalloc(USB_CHR_RX_BUFSIZ, GFP_KERNEL);
+       if (!ipc->ipc_rx) {
+               pr_err("baseband_ipc_open - "
+                       "cannot allocate ipc->ipc_rx\n");
+               goto error_exit;
+       }
+       ipc->ipc_tx = kmalloc(USB_CHR_TX_BUFSIZ, GFP_KERNEL);
+       if (!ipc->ipc_tx) {
+               pr_err("baseband_ipc_open - "
+                       "cannot allocate ipc->ipc_tx\n");
+               goto error_exit;
+       }
 
        /* create wait queues */
        init_waitqueue_head(&ipc->rx.wait);
@@ -578,11 +604,11 @@ static struct baseband_ipc *baseband_ipc_open(work_func_t work_func,
        init_waitqueue_head(&ipc->rx_free.wait);
        init_waitqueue_head(&ipc->tx_free.wait);
 
-       /* create work queue */
-       ipc->workqueue = create_singlethread_workqueue
-               ("baseband_usb_chr_ipc_workqueue");
-       if (!ipc->workqueue) {
-               pr_err("cannot create workqueue\n");
+       /* init work queue */
+       if (chr_ipc_wq)
+               ipc->workqueue = chr_ipc_wq;
+       else {
+               pr_err("%s: no workqueue found\n", __func__);
                goto error_exit;
        }
        if (work_func)
@@ -608,6 +634,28 @@ static void baseband_usb_chr_rx_urb_comp(struct urb *urb)
 
        pr_debug("baseband_usb_chr_rx_urb_comp { urb %p\n", urb);
 
+       /* check input */
+       if (!usb) {
+               pr_err("%s: !usb\n", __func__);
+               return;
+       }
+       if (!usb->ipc) {
+               pr_err("%s: !usb->ipc\n", __func__);
+               return;
+       }
+       if (!usb->ipc->workqueue) {
+               pr_err("%s: !usb->ipc->rx_work\n", __func__);
+               return;
+       }
+
+       switch (urb->status) {
+       case -ENOENT:
+       case -ESHUTDOWN:
+       case -EPROTO:
+               pr_info("%s: link down\n", __func__);
+               return;
+       }
+
        /* queue rx urb completion work */
        queue_work(usb->ipc->workqueue, &usb->ipc->rx_work);
 
@@ -622,29 +670,19 @@ static int baseband_usb_chr_rx_urb_submit(struct baseband_usb *usb)
 
        pr_debug("baseband_usb_chr_rx_urb_submit { usb %p\n", usb);
 
+       /* check input */
        if (!usb_device_connection) {
-               pr_err("!!no usb device conenction!!!!!\n");
+               pr_err("%s: no usb device connection\n", __func__);
                return -1;
        }
-
-       /* check input */
-       if (usb->usb.rx_urb) {
-               pr_err("previous urb still active\n");
+       if (!usb->usb.rx_urb) {
+               pr_err("%s: no rx urb!\n", __func__);
                return -1;
        }
 
-       /* allocate rx urb */
-       urb = usb_alloc_urb(0, GFP_ATOMIC);
-       if (!urb) {
-               pr_err("usb_alloc_urb() failed\n");
-               return -ENOMEM;
-       }
-       buf = kzalloc(USB_CHR_RX_BUFSIZ, GFP_ATOMIC);
-       if (!buf) {
-               pr_err("usb buffer kzalloc() failed\n");
-               usb_free_urb(urb);
-               return -ENOMEM;
-       }
+       /* fill rx urb */
+       urb = usb->usb.rx_urb;
+       buf = usb->usb.rx_urb->transfer_buffer;
        usb_fill_bulk_urb(urb, usb->usb.device, usb->usb.pipe.bulk.in,
                buf, USB_CHR_RX_BUFSIZ,
                baseband_usb_chr_rx_urb_comp,
@@ -653,12 +691,9 @@ static int baseband_usb_chr_rx_urb_submit(struct baseband_usb *usb)
 
        /* submit rx urb */
        usb->usb.rx_urb = urb;
-       err = usb_submit_urb(urb, GFP_ATOMIC);
+       err = usb_submit_urb(urb, GFP_KERNEL);
        if (err < 0) {
                pr_err("usb_submit_urb() failed - err %d\n", err);
-               usb->usb.rx_urb = (struct urb *) 0;
-               kfree(urb->transfer_buffer);
-               usb_free_urb(urb);
                return err;
        }
 
@@ -674,12 +709,22 @@ static void baseband_usb_chr_rx_urb_comp_work(struct work_struct *work)
 
        pr_debug("baseband_usb_chr_rx_urb_comp_work { work %p\n", work);
 
+       if (usb_device_connection == false) {
+               /* device is closing or disconnect - nothing to read */
+               pr_info("%s: device is disconnected\n", __func__);
+               return;
+       }
        /* put rx urb data in rx buffer */
        if (urb->actual_length) {
                pr_debug("baseband_usb_chr_rx_urb_comp_work - "
                        "urb->actual_length %d\n", urb->actual_length);
                len = put_ipc_rx_buf(usb->ipc,
                        urb->transfer_buffer, urb->actual_length);
+               if (len == -ETIMEDOUT) {
+                       /* device closed */
+                       pr_info("%s: device closed\n", __func__);
+                       return;
+               }
                baseband_ipc_dump("baseband_usb_chr_rx_urb_comp_work"
                        " - rx buf ", 0,
                        urb->transfer_buffer, len > 16 ? 16 : len);
@@ -692,12 +737,6 @@ static void baseband_usb_chr_rx_urb_comp_work(struct work_struct *work)
                atomic_add(len, &g_rx_count);
        }
 
-       /* free rx urb */
-       kfree(urb->transfer_buffer);
-       urb->transfer_buffer = (void *) 0;
-       usb_free_urb(urb);
-       usb->usb.rx_urb = (struct urb *) 0;
-
        /* submit next rx urb */
        baseband_usb_chr_rx_urb_submit(usb);
 
@@ -748,8 +787,6 @@ static void find_usb_pipe(struct baseband_usb *usb)
 static int baseband_usb_driver_probe(struct usb_interface *intf,
        const struct usb_device_id *id)
 {
-       int err;
-
        pr_debug("%s(%d) { intf %p id %p\n", __func__, __LINE__, intf, id);
 
        pr_debug("intf->cur_altsetting->desc.bInterfaceNumber %02x\n",
@@ -775,38 +812,9 @@ static int baseband_usb_driver_probe(struct usb_interface *intf,
        }
 
        /* usb interface match */
-       baseband_usb_chr->usb.device = interface_to_usbdev(intf);
-       baseband_usb_chr->usb.interface = intf;
-       find_usb_pipe(baseband_usb_chr);
-       baseband_usb_chr->usb.rx_urb = (struct urb *) 0;
-       baseband_usb_chr->usb.tx_urb = (struct urb *) 0;
-       pr_debug("baseband_usb_chr->usb.driver->name %s\n",
-               baseband_usb_chr->usb.driver->name);
-       pr_debug("baseband_usb_chr->usb.device %p\n",
-               baseband_usb_chr->usb.device);
-       pr_debug("baseband_usb_chr->usb.interface %p\n",
-               baseband_usb_chr->usb.interface);
-       pr_debug("baseband_usb_chr->usb.pipe.isoch.in %x\n",
-               baseband_usb_chr->usb.pipe.isoch.in);
-       pr_debug("baseband_usb_chr->usb.pipe.isoch.out %x\n",
-               baseband_usb_chr->usb.pipe.isoch.out);
-       pr_debug("baseband_usb_chr->usb.pipe.bulk.in %x\n",
-               baseband_usb_chr->usb.pipe.bulk.in);
-       pr_debug("baseband_usb_chr->usb.pipe.bulk.out %x\n",
-               baseband_usb_chr->usb.pipe.bulk.out);
-       pr_debug("baseband_usb_chr->usb.pipe.interrupt.in %x\n",
-               baseband_usb_chr->usb.pipe.interrupt.in);
-       pr_debug("baseband_usb_chr->usb.pipe.interrupt.out %x\n",
-               baseband_usb_chr->usb.pipe.interrupt.out);
+       probe_usb_intf = intf;
        usb_device_connection = true;
 
-       /* start usb rx */
-       err = baseband_usb_chr_rx_urb_submit(baseband_usb_chr);
-       if (err < 0) {
-               pr_err("submit rx failed - err %d\n", err);
-               return -ENODEV;
-       }
-
        pr_debug("%s(%d) }\n", __func__, __LINE__);
        return 0;
 }
@@ -815,17 +823,25 @@ static void baseband_usb_driver_disconnect(struct usb_interface *intf)
 {
        struct usb_device *usb_dev = interface_to_usbdev(intf);
        pr_debug("%s(%d) { intf %p\n", __func__, __LINE__, intf);
-       pr_debug("%s(%d) }\n", __func__, __LINE__);
+
+       if (!baseband_usb_chr) {
+               pr_err("%s: no baseband_usb_chr\n", __func__);
+               return;
+       }
+
        if (baseband_usb_chr->usb.interface != intf) {
                pr_info("%s(%d) -ENODEV\n", __func__, __LINE__);
                return;
        }
        if (baseband_usb_chr->usb.device == usb_dev) {
                pr_info("%s: Matching usb device: Flush workqueue\n", __func__);
-               flush_workqueue(baseband_usb_chr->ipc->workqueue);
+               /* flush queued ipc transaction work */
+               if (baseband_usb_chr && baseband_usb_chr->ipc
+                       && baseband_usb_chr->ipc->workqueue)
+                       flush_workqueue(baseband_usb_chr->ipc->workqueue);
                usb_device_connection = false;
        }
-
+       pr_debug("%s(%d) }\n", __func__, __LINE__);
 }
 
 static char baseband_usb_driver_name[32];
@@ -864,23 +880,15 @@ static void baseband_usb_chr_work(struct work_struct *work)
                queue_work(usb->ipc->workqueue, &usb->ipc->work);
                return;
        }
-
-       /* allocate buffers on first transaction (will be freed on close) */
        if (!usb->ipc->ipc_rx) {
-               usb->ipc->ipc_rx = kzalloc(USB_CHR_RX_BUFSIZ, GFP_KERNEL);
-               if (!usb->ipc->ipc_rx) {
-                       pr_err("baseband_usb_chr_work - "
-                               "cannot allocate usb->ipc->ipc_rx\n");
-                       return;
-               }
+               pr_err("baseband_usb_chr_work - "
+                       "null usb->ipc->ipc_rx\n");
+               return;
        }
        if (!usb->ipc->ipc_tx) {
-               usb->ipc->ipc_tx = kzalloc(USB_CHR_TX_BUFSIZ, GFP_KERNEL);
-               if (!usb->ipc->ipc_tx) {
-                       pr_err("baseband_usb_chr_work - "
-                               "cannot allocate usb->ipc->ipc_tx\n");
-                       return;
-               }
+               pr_err("baseband_usb_chr_work - "
+                       "null usb->ipc->ipc_tx\n");
+               return;
        }
 
        /* usb transaction loop */
@@ -906,81 +914,6 @@ static void baseband_usb_chr_work(struct work_struct *work)
        pr_debug("baseband_usb_chr_work }\n");
 }
 
-/* usb character file operations */
-
-static int baseband_usb_chr_open(struct inode *inode, struct file *file)
-{
-       pr_debug("baseband_usb_chr_open\n");
-       return 0;
-}
-
-static int baseband_usb_chr_release(struct inode *inode, struct file *file)
-{
-       pr_debug("baseband_usb_chr_release\n");
-       return 0;
-}
-
-static ssize_t baseband_usb_chr_read(struct file *file, char *buf,
-       size_t count, loff_t *pos)
-{
-       ssize_t ret;
-
-       pr_debug("baseband_usb_chr_read\n");
-
-       ret = baseband_ipc_file_read(baseband_usb_chr->ipc,
-               file, buf, count, pos);
-       if (ret > 0) {
-               /* decrement count of available rx bytes */
-               int val = atomic_read(&g_rx_count);
-               pr_debug("baseband_usb_chr_read - read %d unread %d\n",
-                       ret, val - ret);
-               atomic_sub(ret, &g_rx_count);
-       }
-       return ret;
-}
-
-static ssize_t baseband_usb_chr_write(struct file *file, const char *buf,
-       size_t count, loff_t *pos)
-{
-       pr_debug("baseband_usb_chr_write\n");
-       return baseband_ipc_file_write(baseband_usb_chr->ipc,
-               file, buf, count, pos);
-}
-
-static long baseband_usb_chr_ioctl(struct file *file, unsigned int cmd,
-       unsigned long arg)
-{
-       pr_debug("baseband_usb_chr_ioctl\n");
-       switch (cmd) {
-       case TCFLSH:
-               pr_debug("TCFLSH\n");
-               /* flush queued ipc transaction work */
-               flush_workqueue(baseband_usb_chr->ipc->workqueue);
-               return 0;
-       case FIONREAD:
-               pr_debug("FIONREAD\n");
-               /* return count of available rx bytes */
-               {
-                       int __user *p = (int __user *) arg;
-                       int val = atomic_read(&g_rx_count);
-                       if (put_user(val, p))
-                               break;
-               }
-               return 0;
-       default:
-               pr_err("unsupported ioctl cmd %x\n", cmd);
-       }
-       return -ENODEV;
-}
-
-static const struct file_operations baseband_usb_chr_fops = {
-       .open = baseband_usb_chr_open,
-       .release = baseband_usb_chr_release,
-       .read = baseband_usb_chr_read,
-       .write = baseband_usb_chr_write,
-       .unlocked_ioctl = baseband_usb_chr_ioctl,
-};
-
 /* usb device driver functions */
 
 static void baseband_usb_close(struct baseband_usb *usb)
@@ -991,6 +924,23 @@ static void baseband_usb_close(struct baseband_usb *usb)
        if (!usb)
                return;
 
+       /* free re-usable rx urb + rx urb transfer buffer */
+       if (usb->usb.rx_urb) {
+               pr_debug("%s: free rx urb\n", __func__);
+               usb_kill_urb(usb->usb.rx_urb);
+               if (usb->usb.rx_urb->transfer_buffer) {
+                       pr_debug("%s: free rx urb transfer buffer\n", __func__);
+                       kfree(usb->usb.rx_urb->transfer_buffer);
+                       usb->usb.rx_urb->transfer_buffer = (void *) 0;
+               }
+       }
+
+       if (usb->ipc) {
+               usb_device_connection = false;
+               flush_work_sync(&usb->ipc->work);
+               flush_work_sync(&usb->ipc->rx_work);
+       }
+
        /* close usb driver */
        if (usb->usb.driver) {
                pr_debug("close usb driver {\n");
@@ -1002,11 +952,13 @@ static void baseband_usb_close(struct baseband_usb *usb)
        /* close baseband ipc */
        if (usb->ipc) {
                baseband_ipc_close(usb->ipc);
-               usb->ipc = (struct baseband_ipc *) 0;
+               usb_free_urb(usb->usb.rx_urb);
+               usb->usb.rx_urb = NULL;
+               usb->ipc = NULL;
        }
 
        /* free baseband usb structure */
-       kfree(usb);
+       vfree(usb);
 
        pr_debug("baseband_usb_close }\n");
 }
@@ -1019,15 +971,17 @@ static struct baseband_usb *baseband_usb_open(unsigned int vid,
        work_func_t tx_work_func)
 {
        struct baseband_usb *usb;
-       int err;
+       int err, i;
+       struct urb *urb;
+       void *buf;
 
        pr_debug("baseband_usb_open {\n");
 
        /* allocate baseband usb structure */
-       usb = kzalloc(sizeof(struct baseband_usb), GFP_KERNEL);
+       usb = vmalloc(sizeof(struct baseband_usb));
        if (!usb)
                return (struct baseband_usb *) 0;
-       baseband_usb_chr = usb;
+       memset(usb, 0, sizeof(struct baseband_usb));
 
        /* open baseband ipc */
        usb->ipc = baseband_ipc_open(work_func,
@@ -1039,6 +993,7 @@ static struct baseband_usb *baseband_usb_open(unsigned int vid,
        }
 
        /* open usb driver */
+       probe_usb_intf = (struct usb_interface *) 0;
        sprintf(baseband_usb_driver_name,
                "baseband_usb_%x_%x_%x",
                vid, pid, intf);
@@ -1053,6 +1008,65 @@ static struct baseband_usb *baseband_usb_open(unsigned int vid,
                goto error_exit;
        }
 
+       /* wait for probe */
+       pr_info("%s: waiting for usb probe...\n", __func__);
+       for (i = 0; i < 5 * 10; i++) {
+               if (probe_usb_intf && usb_device_connection)
+                       break;
+               msleep(100);
+       }
+       if (!probe_usb_intf || !usb_device_connection) {
+               pr_info("%s: probe timed out!\n", __func__);
+               goto error_exit;
+       }
+
+       /* get probed usb device information */
+       usb->usb.device = interface_to_usbdev(probe_usb_intf);
+       usb->usb.interface = probe_usb_intf;
+       find_usb_pipe(usb);
+       usb->usb.rx_urb = (struct urb *) 0;
+       usb->usb.tx_urb = (struct urb *) 0;
+       pr_debug("usb->usb.driver->name %s\n",
+               usb->usb.driver->name);
+       pr_debug("usb->usb.device %p\n",
+               usb->usb.device);
+       pr_debug("usb->usb.interface %p\n",
+               usb->usb.interface);
+       pr_debug("usb->usb.pipe.isoch.in %x\n",
+               usb->usb.pipe.isoch.in);
+       pr_debug("usb->usb.pipe.isoch.out %x\n",
+               usb->usb.pipe.isoch.out);
+       pr_debug("usb->usb.pipe.bulk.in %x\n",
+               usb->usb.pipe.bulk.in);
+       pr_debug("usb->usb.pipe.bulk.out %x\n",
+               usb->usb.pipe.bulk.out);
+       pr_debug("usb->usb.pipe.interrupt.in %x\n",
+               usb->usb.pipe.interrupt.in);
+       pr_debug("usb->usb.pipe.interrupt.out %x\n",
+               usb->usb.pipe.interrupt.out);
+
+       /* allocate re-usable rx urb + rx urb transfer buffer */
+       urb = usb_alloc_urb(0, GFP_KERNEL);
+       if (!urb) {
+               pr_err("usb_alloc_urb() failed\n");
+               goto error_exit;
+       }
+       buf = kmalloc(USB_CHR_RX_BUFSIZ, GFP_KERNEL);
+       if (!buf) {
+               pr_err("%s: usb buffer kmalloc() failed\n", __func__);
+               usb_free_urb(urb);
+               goto error_exit;
+       }
+       urb->transfer_buffer = buf;
+       usb->usb.rx_urb = urb;
+
+       /* start usb rx */
+       err = baseband_usb_chr_rx_urb_submit(usb);
+       if (err < 0) {
+               pr_err("submit rx failed - err %d\n", err);
+               goto error_exit;
+       }
+
        pr_debug("baseband_usb_open }\n");
        return usb;
 
@@ -1062,27 +1076,143 @@ error_exit:
        return (struct baseband_usb *) 0;
 }
 
-/* module init / exit functions */
 
-static int baseband_usb_chr_init(void)
+/* usb character file operations */
+
+static int baseband_usb_chr_open(struct inode *inode, struct file *file)
 {
-       int err;
+       pr_debug("baseband_usb_chr_open {\n");
 
-       pr_debug("baseband_usb_chr_init {\n");
+       if (baseband_usb_chr) {
+               pr_err("%s: device is already open\n", __func__);
+               /* application uses two fd opens for download*/
+               baseband_usb_chr->ref++;
+               return 0;
+       }
 
        /* open baseband usb */
-       baseband_usb_chr = baseband_usb_open
-               (baseband_usb_chr_vid,
-                       baseband_usb_chr_pid,
-                       baseband_usb_chr_intf,
-                       baseband_usb_chr_work,
-                       baseband_usb_chr_rx_urb_comp_work,
-                       (work_func_t) 0);
+       baseband_usb_chr = baseband_usb_open(baseband_usb_chr_vid,
+                               baseband_usb_chr_pid,
+                               baseband_usb_chr_intf,
+                               baseband_usb_chr_work,
+                               baseband_usb_chr_rx_urb_comp_work,
+                               (work_func_t) 0);
        if (!baseband_usb_chr) {
                pr_err("cannot open baseband usb chr\n");
-               err = -1;
-               goto err1;
+               return -ENODEV;
+       }
+       baseband_usb_chr->ref++;
+
+       if (!try_module_get(THIS_MODULE))
+               return -ENODEV;
+
+       pr_debug("baseband_usb_chr_open }\n");
+       return 0;
+}
+
+static int baseband_usb_chr_release(struct inode *inode, struct file *file)
+{
+       pr_debug("baseband_usb_chr_release\n");
+       pr_info("baseband_usb_chr_release {\n");
+
+       if (baseband_usb_chr) {
+               baseband_usb_chr->ref--;
+               if (baseband_usb_chr->ref)
+                       return 0;
+
+               /* close baseband usb */
+               baseband_usb_close(baseband_usb_chr);
+               baseband_usb_chr = (struct baseband_usb *) 0;
+       }
+
+       module_put(THIS_MODULE);
+       pr_info("baseband_usb_chr_release }\n");
+
+       return 0;
+}
+
+static ssize_t baseband_usb_chr_read(struct file *file, char *buf,
+       size_t count, loff_t *pos)
+{
+       ssize_t ret;
+
+       pr_debug("baseband_usb_chr_read\n");
+
+       if (!baseband_usb_chr || !baseband_usb_chr->ipc) {
+               pr_err("%s: -ENODEV\n", __func__);
+               return -ENODEV;
+       }
+       ret = baseband_ipc_file_read(baseband_usb_chr->ipc,
+               file, buf, count, pos);
+       if (ret > 0) {
+               /* decrement count of available rx bytes */
+               int val = atomic_read(&g_rx_count);
+               pr_debug("baseband_usb_chr_read - read %d unread %d\n",
+                       ret, val - ret);
+               atomic_sub(ret, &g_rx_count);
+       }
+       return ret;
+}
+
+static ssize_t baseband_usb_chr_write(struct file *file, const char *buf,
+       size_t count, loff_t *pos)
+{
+       pr_debug("baseband_usb_chr_write\n");
+       if (!baseband_usb_chr || !baseband_usb_chr->ipc) {
+               pr_err("%s: -ENODEV\n", __func__);
+               return -ENODEV;
+       }
+       return baseband_ipc_file_write(baseband_usb_chr->ipc,
+               file, buf, count, pos);
+}
+
+static long baseband_usb_chr_ioctl(struct file *file, unsigned int cmd,
+       unsigned long arg)
+{
+       pr_debug("baseband_usb_chr_ioctl\n");
+       switch (cmd) {
+       case TCFLSH:
+               pr_debug("TCFLSH\n");
+               /* flush queued ipc transaction work */
+               if (!baseband_usb_chr || !baseband_usb_chr->ipc
+                       || !baseband_usb_chr->ipc->workqueue) {
+                       pr_err("%s: no workqueue!\n", __func__);
+                       return -ENODEV;
+               }
+               flush_workqueue(baseband_usb_chr->ipc->workqueue);
+               return 0;
+       case FIONREAD:
+               pr_debug("FIONREAD\n");
+               /* return count of available rx bytes */
+               {
+                       int __user *p = (int __user *) arg;
+                       int val = atomic_read(&g_rx_count);
+                       if (put_user(val, p))
+                               break;
+               }
+               return 0;
+       default:
+               pr_err("unsupported ioctl cmd %x\n", cmd);
+               return 0;
        }
+       return -ENODEV;
+}
+
+static const struct file_operations baseband_usb_chr_fops = {
+       .open = baseband_usb_chr_open,
+       .release = baseband_usb_chr_release,
+       .read = baseband_usb_chr_read,
+       .write = baseband_usb_chr_write,
+       .unlocked_ioctl = baseband_usb_chr_ioctl,
+};
+
+/* module init / exit functions */
+
+static int baseband_usb_chr_init(void)
+{
+       int err;
+
+       pr_debug("baseband_usb_chr_init {\n");
 
        /* register character device */
        err = register_chrdev(BASEBAND_USB_CHR_DEV_MAJOR,
@@ -1090,16 +1220,22 @@ static int baseband_usb_chr_init(void)
                &baseband_usb_chr_fops);
        if (err < 0) {
                pr_err("cannot register character device - %d\n", err);
-               goto err2;
+               return err;
        }
        pr_debug("registered baseband usb character device - major %d\n",
                BASEBAND_USB_CHR_DEV_MAJOR);
 
+       /* create workqueue thread */
+       chr_ipc_wq = create_singlethread_workqueue("baseband_chr_wq");
+       if (chr_ipc_wq == NULL) {
+               pr_err("cannot create workqueue\n");
+               unregister_chrdev(BASEBAND_USB_CHR_DEV_MAJOR,
+                       BASEBAND_USB_CHR_DEV_NAME);
+               return -ENODEV;
+       }
+
        pr_debug("baseband_usb_chr_init }\n");
        return 0;
-err2:  baseband_usb_close(baseband_usb_chr);
-       baseband_usb_chr = (struct baseband_usb *) 0;
-err1:  return err;
 }
 
 static void baseband_usb_chr_exit(void)
@@ -1110,12 +1246,10 @@ static void baseband_usb_chr_exit(void)
        unregister_chrdev(BASEBAND_USB_CHR_DEV_MAJOR,
                BASEBAND_USB_CHR_DEV_NAME);
 
-       /* close baseband usb */
-       if (baseband_usb_chr) {
-               baseband_usb_close(baseband_usb_chr);
-               baseband_usb_chr = (struct baseband_usb *) 0;
+       if (chr_ipc_wq) {
+               destroy_workqueue(chr_ipc_wq);
+               chr_ipc_wq = NULL;
        }
-
        pr_debug("baseband_usb_chr_exit }\n");
 }
 
index 7935e79..e7553f7 100644 (file)
 #define BASEBAND_USB_CHR_DEV_MAJOR             66
 
 #ifndef USB_CHR_RX_BUFSIZ
-#define USB_CHR_RX_BUFSIZ                      (128*1024)
+#define USB_CHR_RX_BUFSIZ                      (32*1024)
 #endif  /* USB_CHR_RX_BUFSIZ */
 
 #ifndef USB_CHR_TX_BUFSIZ
-#define USB_CHR_TX_BUFSIZ                      (128*1024)
+#define USB_CHR_TX_BUFSIZ                      (32*1024)
 #endif  /* USB_CHR_TX_BUFSIZ */
 
 #ifndef USB_CHR_TIMEOUT
 #endif  /* USB_CHR_TIMEOUT */
 
 #ifndef BASEBAND_IPC_NUM_RX_BUF
-#define BASEBAND_IPC_NUM_RX_BUF                        32
+#define BASEBAND_IPC_NUM_RX_BUF                        1
 #endif  /* BASEBAND_IPC_NUM_RX_BUF */
 
 #ifndef BASEBAND_IPC_NUM_TX_BUF
-#define BASEBAND_IPC_NUM_TX_BUF                        16
+#define BASEBAND_IPC_NUM_TX_BUF                        1
 #endif  /* BASEBAND_IPC_NUM_TX_BUF */
 
 #ifndef BASEBAND_IPC_BUFSIZ
@@ -85,6 +85,7 @@ struct baseband_ipc_buf {
 
 struct baseband_usb {
        struct baseband_ipc *ipc;
+       unsigned int ref;
        struct {
                struct usb_driver *driver;
                struct usb_device *device;