Re: Gobi 3000

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Am Donnerstag, 17. März 2011, 15:12:48 schrieb Greg KH:
> On Thu, Mar 17, 2011 at 08:36:14AM +0100, Oliver Neukum wrote:
> > Hi,
> > 
> > is anybody else here looking at the Gobi 3000 driver?
> > I'd like to get it included, but right now it has some grave errors,
> > e.g. happily following the null pointer if kmalloc fails.
> 
> Someone from google posted here that they were looking at it, you should
> look in the archives and ask them.

Thanks.

> Do you have a pointer to the source for it anywhere?

Yes, I do. It is:
https://www.codeaurora.org/patches/quic/gobi/

If you want to take a look I am attaching csets that add it to the
latest kernel and begins fixing bugs.
The code looks like written by someone ready to take orders to not
take prisoners.

	Regards
		Oliver
From 710f2c756f212eccce9933f3269f22800b13dd6f Mon Sep 17 00:00:00 2001
From: Oliver Neukum <oliver@xxxxxxxxxx>
Date: Thu, 17 Mar 2011 17:48:39 +0100
Subject: [PATCH 1/4] Gobi3000:add upstream stuff

---
 drivers/net/usb/GobiUSBNet.c    | 1150 ++++++++++++++
 drivers/net/usb/QMI.c           |  991 ++++++++++++
 drivers/net/usb/QMI.h           |  289 ++++
 drivers/net/usb/QMIDevice.c     | 3273 +++++++++++++++++++++++++++++++++++++++
 drivers/net/usb/QMIDevice.h     |  339 ++++
 drivers/net/usb/Structs.h       |  359 +++++
 drivers/usb/serial/GobiSerial.c |  754 +++++++++
 7 files changed, 7155 insertions(+), 0 deletions(-)
 create mode 100644 drivers/net/usb/GobiUSBNet.c
 create mode 100644 drivers/net/usb/QMI.c
 create mode 100644 drivers/net/usb/QMI.h
 create mode 100644 drivers/net/usb/QMIDevice.c
 create mode 100644 drivers/net/usb/QMIDevice.h
 create mode 100644 drivers/net/usb/Structs.h
 create mode 100644 drivers/usb/serial/GobiSerial.c

diff --git a/drivers/net/usb/GobiUSBNet.c b/drivers/net/usb/GobiUSBNet.c
new file mode 100644
index 0000000..797bcee
--- /dev/null
+++ b/drivers/net/usb/GobiUSBNet.c
@@ -0,0 +1,1150 @@
+/*===========================================================================
+FILE:
+   GobiUSBNet.c
+
+DESCRIPTION:
+   Qualcomm USB Network device for Gobi 3000
+   
+FUNCTIONS:
+   GobiSuspend
+   GobiResume
+   GobiNetDriverBind
+   GobiNetDriverUnbind
+   GobiUSBNetURBCallback
+   GobiUSBNetTXTimeout
+   GobiUSBNetAutoPMThread
+   GobiUSBNetStartXmit
+   GobiUSBNetOpen
+   GobiUSBNetStop
+   GobiUSBNetProbe
+   GobiUSBNetModInit
+   GobiUSBNetModExit
+
+Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of Code Aurora Forum nor
+      the names of its contributors may be used to endorse or promote
+      products derived from this software without specific prior written
+      permission.
+
+Alternatively, provided that this notice is retained in full, this software
+may be relicensed by the recipient under the terms of the GNU General Public
+License version 2 ("GPL") and only version 2, in which case the provisions of
+the GPL apply INSTEAD OF those given above.  If the recipient relicenses the
+software under the GPL, then the identification text in the MODULE_LICENSE
+macro must be changed to reflect "GPLv2" instead of "Dual BSD/GPL".  Once a
+recipient changes the license terms to the GPL, subsequent recipients shall
+not relicense under alternate licensing terms, including the BSD or dual
+BSD/GPL terms.  In addition, the following license statement immediately
+below and between the words START and END shall also then apply when this
+software is relicensed under the GPL:
+
+START
+
+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.
+
+END
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+===========================================================================*/
+
+//---------------------------------------------------------------------------
+// Include Files
+//---------------------------------------------------------------------------
+
+#include "Structs.h"
+#include "QMIDevice.h"
+#include "QMI.h"
+
+//-----------------------------------------------------------------------------
+// Definitions
+//-----------------------------------------------------------------------------
+
+// Version Information
+#define DRIVER_VERSION "1.0.40"
+#define DRIVER_AUTHOR "Qualcomm Innovation Center"
+#define DRIVER_DESC "GobiNet"
+
+// Debug flag
+int debug;
+
+// Allow user interrupts
+int interruptible = 1;
+
+// Class should be created during module init, so needs to be global
+static struct class * gpClass;
+
+/*===========================================================================
+METHOD:
+   GobiSuspend (Public Method)
+
+DESCRIPTION:
+   Stops QMI traffic while device is suspended
+
+PARAMETERS
+   pIntf          [ I ] - Pointer to interface
+   powerEvent     [ I ] - Power management event
+
+RETURN VALUE:
+   int - 0 for success
+         negative errno for failure
+===========================================================================*/
+int GobiSuspend( 
+   struct usb_interface *     pIntf,
+   pm_message_t               powerEvent )
+{
+   struct usbnet * pDev;
+   sGobiUSBNet * pGobiDev;
+   
+   if (pIntf == 0)
+   {
+      return -ENOMEM;
+   }
+   
+#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,23 ))
+   pDev = usb_get_intfdata( pIntf );
+#else
+   pDev = (struct usbnet *)pIntf->dev.platform_data;
+#endif
+
+   if (pDev == NULL || pDev->net == NULL)
+   {
+      DBG( "failed to get netdevice\n" );
+      return -ENXIO;
+   }
+   
+   pGobiDev = (sGobiUSBNet *)pDev->data[0];
+   if (pGobiDev == NULL)
+   {
+      DBG( "failed to get QMIDevice\n" );
+      return -ENXIO;
+   }
+
+   // Is this autosuspend or system suspend?
+   //    do we allow remote wakeup?
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,33 ))
+   if (pDev->udev->auto_pm == 0)
+#else
+   if ((powerEvent.event & PM_EVENT_AUTO) == 0)
+#endif
+   {
+      DBG( "device suspended to power level %d\n", 
+           powerEvent.event );
+      GobiSetDownReason( pGobiDev, DRIVER_SUSPENDED );
+   }
+   else
+   {
+      DBG( "device autosuspend\n" );
+   }
+     
+   if (powerEvent.event & PM_EVENT_SUSPEND)
+   {
+      // Stop QMI read callbacks
+      KillRead( pGobiDev );
+      pDev->udev->reset_resume = 0;
+      
+      // Store power state to avoid duplicate resumes
+      pIntf->dev.power.power_state.event = powerEvent.event;
+   }
+   else
+   {
+      // Other power modes cause QMI connection to be lost
+      pDev->udev->reset_resume = 1;
+   }
+   
+   // Run usbnet's suspend function
+   return usbnet_suspend( pIntf, powerEvent );
+}
+   
+/*===========================================================================
+METHOD:
+   GobiResume (Public Method)
+
+DESCRIPTION:
+   Resume QMI traffic or recreate QMI device
+
+PARAMETERS
+   pIntf          [ I ] - Pointer to interface
+
+RETURN VALUE:
+   int - 0 for success
+         negative errno for failure
+===========================================================================*/
+int GobiResume( struct usb_interface * pIntf )
+{
+   struct usbnet * pDev;
+   sGobiUSBNet * pGobiDev;
+   int nRet;
+   int oldPowerState;
+   
+   if (pIntf == 0)
+   {
+      return -ENOMEM;
+   }
+   
+#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,23 ))
+   pDev = usb_get_intfdata( pIntf );
+#else
+   pDev = (struct usbnet *)pIntf->dev.platform_data;
+#endif
+
+   if (pDev == NULL || pDev->net == NULL)
+   {
+      DBG( "failed to get netdevice\n" );
+      return -ENXIO;
+   }
+   
+   pGobiDev = (sGobiUSBNet *)pDev->data[0];
+   if (pGobiDev == NULL)
+   {
+      DBG( "failed to get QMIDevice\n" );
+      return -ENXIO;
+   }
+
+   oldPowerState = pIntf->dev.power.power_state.event;
+   pIntf->dev.power.power_state.event = PM_EVENT_ON;
+   DBG( "resuming from power mode %d\n", oldPowerState );
+
+   if (oldPowerState & PM_EVENT_SUSPEND)
+   {
+      // It doesn't matter if this is autoresume or system resume
+      GobiClearDownReason( pGobiDev, DRIVER_SUSPENDED );
+   
+      nRet = usbnet_resume( pIntf );
+      if (nRet != 0)
+      {
+         DBG( "usbnet_resume error %d\n", nRet );
+         return nRet;
+      }
+
+      // Restart QMI read callbacks
+      nRet = StartRead( pGobiDev );
+      if (nRet != 0)
+      {
+         DBG( "StartRead error %d\n", nRet );
+         return nRet;
+      }
+
+      // Kick Auto PM thread to process any queued URBs
+      complete( &pGobiDev->mAutoPM.mThreadDoWork );
+   }
+   else
+   {
+      DBG( "nothing to resume\n" );
+      return 0;
+   }
+   
+   return nRet;
+}
+
+/*===========================================================================
+METHOD:
+   GobiNetDriverBind (Public Method)
+
+DESCRIPTION:
+   Setup in and out pipes
+
+PARAMETERS
+   pDev           [ I ] - Pointer to usbnet device
+   pIntf          [ I ] - Pointer to interface
+
+RETURN VALUE:
+   int - 0 for success
+         Negative errno for error
+===========================================================================*/
+static int GobiNetDriverBind( 
+   struct usbnet *         pDev, 
+   struct usb_interface *  pIntf )
+{
+   int numEndpoints;
+   int endpointIndex;
+   struct usb_host_endpoint * pEndpoint = NULL;
+   struct usb_host_endpoint * pIn = NULL;
+   struct usb_host_endpoint * pOut = NULL;
+   
+   // Verify one altsetting
+   if (pIntf->num_altsetting != 1)
+   {
+      DBG( "invalid num_altsetting %u\n", pIntf->num_altsetting );
+      return -ENODEV;
+   }
+
+   // Verify correct interface (0 or 5)
+   if ( (pIntf->cur_altsetting->desc.bInterfaceNumber != 0)
+   &&   (pIntf->cur_altsetting->desc.bInterfaceNumber != 5) )
+   {
+      DBG( "invalid interface %d\n", 
+           pIntf->cur_altsetting->desc.bInterfaceNumber );
+      return -ENODEV;
+   }
+   
+   // Collect In and Out endpoints
+   numEndpoints = pIntf->cur_altsetting->desc.bNumEndpoints;
+   for (endpointIndex = 0; endpointIndex < numEndpoints; endpointIndex++)
+   {
+      pEndpoint = pIntf->cur_altsetting->endpoint + endpointIndex;
+      if (pEndpoint == NULL)
+      {
+         DBG( "invalid endpoint %u\n", endpointIndex );
+         return -ENODEV;
+      }
+      
+      if (usb_endpoint_dir_in( &pEndpoint->desc ) == true
+      &&  usb_endpoint_xfer_int( &pEndpoint->desc ) == false)
+      {
+         pIn = pEndpoint;
+      }
+      else if (usb_endpoint_dir_out( &pEndpoint->desc ) == true)
+      {
+         pOut = pEndpoint;
+      }
+   }
+   
+   if (pIn == NULL || pOut == NULL)
+   {
+      DBG( "invalid endpoints\n" );
+      return -ENODEV;
+   }
+
+   if (usb_set_interface( pDev->udev, 
+                          pIntf->cur_altsetting->desc.bInterfaceNumber,
+                          0 ) != 0)
+   {
+      DBG( "unable to set interface\n" );
+      return -ENODEV;
+   }
+
+   pDev->in = usb_rcvbulkpipe( pDev->udev,
+                   pIn->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK );
+   pDev->out = usb_sndbulkpipe( pDev->udev,
+                   pOut->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK );
+                   
+   DBG( "in %x, out %x\n", 
+        pIn->desc.bEndpointAddress, 
+        pOut->desc.bEndpointAddress );
+
+   // In later versions of the kernel, usbnet helps with this
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,23 ))
+   pIntf->dev.platform_data = (void *)pDev;
+#endif
+
+   return 0;
+}
+
+/*===========================================================================
+METHOD:
+   GobiNetDriverUnbind (Public Method)
+
+DESCRIPTION:
+   Deregisters QMI device (Registration happened in the probe function)
+
+PARAMETERS
+   pDev           [ I ] - Pointer to usbnet device
+   pIntfUnused    [ I ] - Pointer to interface
+
+RETURN VALUE:
+   None
+===========================================================================*/
+static void GobiNetDriverUnbind( 
+   struct usbnet *         pDev, 
+   struct usb_interface *  pIntf)
+{
+   sGobiUSBNet * pGobiDev = (sGobiUSBNet *)pDev->data[0];
+
+   // Should already be down, but just in case...
+   netif_carrier_off( pDev->net );
+
+   DeregisterQMIDevice( pGobiDev );
+   
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 2,6,29 ))
+   kfree( pDev->net->netdev_ops );
+   pDev->net->netdev_ops = NULL;
+#endif
+
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,23 ))
+   pIntf->dev.platform_data = NULL;
+#endif
+
+   kfree( pGobiDev );
+   pGobiDev = NULL;
+}
+
+/*===========================================================================
+METHOD:
+   GobiUSBNetURBCallback (Public Method)
+
+DESCRIPTION:
+   Write is complete, cleanup and signal that we're ready for next packet
+
+PARAMETERS
+   pURB     [ I ] - Pointer to sAutoPM struct
+
+RETURN VALUE:
+   None
+===========================================================================*/
+void GobiUSBNetURBCallback( struct urb * pURB )
+{
+   unsigned long activeURBflags;
+   sAutoPM * pAutoPM = (sAutoPM *)pURB->context;
+   if (pAutoPM == NULL)
+   {
+      // Should never happen
+      DBG( "bad context\n" );
+      return;
+   }
+
+   if (pURB->status != 0)
+   {
+      // Note that in case of an error, the behaviour is no different
+      DBG( "urb finished with error %d\n", pURB->status );
+   }
+
+   // Remove activeURB (memory to be freed later)
+   spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags );
+
+   // EAGAIN used to signify callback is done
+   pAutoPM->mpActiveURB = ERR_PTR( -EAGAIN );
+
+   spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags );
+
+   complete( &pAutoPM->mThreadDoWork );
+   
+   usb_free_urb( pURB );
+}
+
+/*===========================================================================
+METHOD:
+   GobiUSBNetTXTimeout (Public Method)
+
+DESCRIPTION:
+   Timeout declared by the net driver.  Stop all transfers
+
+PARAMETERS
+   pNet     [ I ] - Pointer to net device
+
+RETURN VALUE:
+   None
+===========================================================================*/
+void GobiUSBNetTXTimeout( struct net_device * pNet )
+{
+   struct sGobiUSBNet * pGobiDev;
+   sAutoPM * pAutoPM;
+   sURBList * pURBListEntry;
+   unsigned long activeURBflags, URBListFlags;
+   struct usbnet * pDev = netdev_priv( pNet );
+
+   if (pDev == NULL || pDev->net == NULL)
+   {
+      DBG( "failed to get usbnet device\n" );
+      return;
+   }
+   
+   pGobiDev = (sGobiUSBNet *)pDev->data[0];
+   if (pGobiDev == NULL)
+   {
+      DBG( "failed to get QMIDevice\n" );
+      return;
+   }
+   pAutoPM = &pGobiDev->mAutoPM;
+
+   DBG( "\n" );
+
+   // Stop activeURB
+   spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags );
+
+   if (pAutoPM->mpActiveURB != NULL)
+   {
+      usb_kill_urb( pAutoPM->mpActiveURB );
+   }
+
+   spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags );
+
+   // Cleanup URB List
+   spin_lock_irqsave( &pAutoPM->mURBListLock, URBListFlags );
+
+   pURBListEntry = pAutoPM->mpURBList;
+   while (pURBListEntry != NULL)
+   {
+      pAutoPM->mpURBList = pAutoPM->mpURBList->mpNext;
+      usb_free_urb( pURBListEntry->mpURB );
+      kfree( pURBListEntry );
+      pURBListEntry = pAutoPM->mpURBList;
+   }
+
+   spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags );
+
+   complete( &pAutoPM->mThreadDoWork );
+
+   return;
+}
+
+/*===========================================================================
+METHOD:
+   GobiUSBNetAutoPMThread (Public Method)
+
+DESCRIPTION:
+   Handle device Auto PM state asynchronously
+   Handle network packet transmission asynchronously
+
+PARAMETERS
+   pData     [ I ] - Pointer to sAutoPM struct
+
+RETURN VALUE:
+   int - 0 for success
+         Negative errno for error
+===========================================================================*/
+static int GobiUSBNetAutoPMThread( void * pData )
+{
+   unsigned long activeURBflags, URBListFlags;
+   sURBList * pURBListEntry;
+   int status;
+   struct usb_device * pUdev;
+   sAutoPM * pAutoPM = (sAutoPM *)pData;
+   if (pAutoPM == NULL)
+   {
+      DBG( "passed null pointer\n" );
+      return -EINVAL;
+   }
+   
+   pUdev = interface_to_usbdev( pAutoPM->mpIntf );
+
+   DBG( "traffic thread started\n" );
+
+   while (pAutoPM->mbExit == false)
+   {
+      // Wait for someone to poke us
+      wait_for_completion_interruptible( &pAutoPM->mThreadDoWork );
+
+      // Time to exit?
+      if (pAutoPM->mbExit == true)
+      {
+         // Stop activeURB
+         spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags );
+
+         if (pAutoPM->mpActiveURB != NULL)
+         {
+            usb_kill_urb( pAutoPM->mpActiveURB );
+         }
+         // Will be freed in callback function
+
+         spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags );
+
+         // Cleanup URB List
+         spin_lock_irqsave( &pAutoPM->mURBListLock, URBListFlags );
+
+         pURBListEntry = pAutoPM->mpURBList;
+         while (pURBListEntry != NULL)
+         {
+            pAutoPM->mpURBList = pAutoPM->mpURBList->mpNext;
+            usb_free_urb( pURBListEntry->mpURB );
+            kfree( pURBListEntry );
+            pURBListEntry = pAutoPM->mpURBList;
+         }
+
+         spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags );
+
+         break;
+      }
+      
+      // Is our URB active?
+      spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags );
+
+      // EAGAIN used to signify callback is done
+      if (IS_ERR( pAutoPM->mpActiveURB ) 
+      &&  PTR_ERR( pAutoPM->mpActiveURB ) == -EAGAIN )
+      {
+         pAutoPM->mpActiveURB = NULL;
+
+         // Restore IRQs so task can sleep
+         spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags );
+         
+         // URB is done, decrement the Auto PM usage count
+         usb_autopm_put_interface( pAutoPM->mpIntf );
+
+         // Lock ActiveURB again
+         spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags );
+      }
+
+      if (pAutoPM->mpActiveURB != NULL)
+      {
+         // There is already a URB active, go back to sleep
+         spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags );
+         continue;
+      }
+      
+      // Is there a URB waiting to be submitted?
+      spin_lock_irqsave( &pAutoPM->mURBListLock, URBListFlags );
+      if (pAutoPM->mpURBList == NULL)
+      {
+         // No more URBs to submit, go back to sleep
+         spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags );
+         spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags );
+         continue;
+      }
+
+      // Pop an element
+      pURBListEntry = pAutoPM->mpURBList;
+      pAutoPM->mpURBList = pAutoPM->mpURBList->mpNext;
+      spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags );
+
+      // Set ActiveURB
+      pAutoPM->mpActiveURB = pURBListEntry->mpURB;
+      spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags );
+
+      // Tell autopm core we need device woken up
+      status = usb_autopm_get_interface( pAutoPM->mpIntf );
+      if (status < 0)
+      {
+         DBG( "unable to autoresume interface: %d\n", status );
+
+         // likely caused by device going from autosuspend -> full suspend
+         if (status == -EPERM)
+         {
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,33 ))
+            pUdev->auto_pm = 0;
+#endif
+            GobiSuspend( pAutoPM->mpIntf, PMSG_SUSPEND );
+         }
+
+         // Add pURBListEntry back onto pAutoPM->mpURBList
+         spin_lock_irqsave( &pAutoPM->mURBListLock, URBListFlags );
+         pURBListEntry->mpNext = pAutoPM->mpURBList;
+         pAutoPM->mpURBList = pURBListEntry;
+         spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags );
+         
+         spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags );
+         pAutoPM->mpActiveURB = NULL;
+         spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags );
+         
+         // Go back to sleep
+         continue;
+      }
+
+      // Submit URB
+      status = usb_submit_urb( pAutoPM->mpActiveURB, GFP_KERNEL );
+      if (status < 0)
+      {
+         // Could happen for a number of reasons
+         DBG( "Failed to submit URB: %d.  Packet dropped\n", status );
+         spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags );
+         usb_free_urb( pAutoPM->mpActiveURB );
+         pAutoPM->mpActiveURB = NULL;
+         spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags );
+         usb_autopm_put_interface( pAutoPM->mpIntf );
+
+         // Loop again
+         complete( &pAutoPM->mThreadDoWork );
+      }
+      
+      kfree( pURBListEntry );
+   }   
+   
+   DBG( "traffic thread exiting\n" );
+   pAutoPM->mpThread = NULL;
+   return 0;
+}      
+
+/*===========================================================================
+METHOD:
+   GobiUSBNetStartXmit (Public Method)
+
+DESCRIPTION:
+   Convert sk_buff to usb URB and queue for transmit
+
+PARAMETERS
+   pNet     [ I ] - Pointer to net device
+
+RETURN VALUE:
+   NETDEV_TX_OK on success
+   NETDEV_TX_BUSY on error
+===========================================================================*/
+int GobiUSBNetStartXmit( 
+   struct sk_buff *     pSKB,
+   struct net_device *  pNet )
+{
+   unsigned long URBListFlags;
+   struct sGobiUSBNet * pGobiDev;
+   sAutoPM * pAutoPM;
+   sURBList * pURBListEntry, ** ppURBListEnd;
+   void * pURBData;
+   struct usbnet * pDev = netdev_priv( pNet );
+   
+   DBG( "\n" );
+   
+   if (pDev == NULL || pDev->net == NULL)
+   {
+      DBG( "failed to get usbnet device\n" );
+      return NETDEV_TX_BUSY;
+   }
+   
+   pGobiDev = (sGobiUSBNet *)pDev->data[0];
+   if (pGobiDev == NULL)
+   {
+      DBG( "failed to get QMIDevice\n" );
+      return NETDEV_TX_BUSY;
+   }
+   pAutoPM = &pGobiDev->mAutoPM;
+   
+   if (GobiTestDownReason( pGobiDev, DRIVER_SUSPENDED ) == true)
+   {
+      // Should not happen
+      DBG( "device is suspended\n" );
+      dump_stack();
+      return NETDEV_TX_BUSY;
+   }
+   
+   // Convert the sk_buff into a URB
+
+   // Allocate URBListEntry
+   pURBListEntry = kmalloc( sizeof( sURBList ), GFP_ATOMIC );
+   if (pURBListEntry == NULL)
+   {
+      DBG( "unable to allocate URBList memory\n" );
+      return NETDEV_TX_BUSY;
+   }
+   pURBListEntry->mpNext = NULL;
+
+   // Allocate URB
+   pURBListEntry->mpURB = usb_alloc_urb( 0, GFP_ATOMIC );
+   if (pURBListEntry->mpURB == NULL)
+   {
+      DBG( "unable to allocate URB\n" );
+      return NETDEV_TX_BUSY;
+   }
+
+   // Allocate URB transfer_buffer
+   pURBData = kmalloc( pSKB->len, GFP_ATOMIC );
+   if (pURBData == NULL)
+   {
+      DBG( "unable to allocate URB data\n" );
+      return NETDEV_TX_BUSY;
+   }
+   // Fill will SKB's data
+   memcpy( pURBData, pSKB->data, pSKB->len );
+
+   usb_fill_bulk_urb( pURBListEntry->mpURB,
+                      pGobiDev->mpNetDev->udev,
+                      pGobiDev->mpNetDev->out,
+                      pURBData,
+                      pSKB->len,
+                      GobiUSBNetURBCallback,
+                      pAutoPM );
+   
+   // Aquire lock on URBList
+   spin_lock_irqsave( &pAutoPM->mURBListLock, URBListFlags );
+   
+   // Add URB to end of list
+   ppURBListEnd = &pAutoPM->mpURBList;
+   while ((*ppURBListEnd) != NULL)
+   {
+      ppURBListEnd = &(*ppURBListEnd)->mpNext;
+   }
+   *ppURBListEnd = pURBListEntry;
+
+   spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags );
+
+   complete( &pAutoPM->mThreadDoWork );
+
+   // Start transfer timer
+   pNet->trans_start = jiffies;
+   // Free SKB
+   dev_kfree_skb_any( pSKB );
+
+   return NETDEV_TX_OK;
+}
+
+/*===========================================================================
+METHOD:
+   GobiUSBNetOpen (Public Method)
+
+DESCRIPTION:
+   Wrapper to usbnet_open, correctly handling autosuspend
+   Start AutoPM thread
+
+PARAMETERS
+   pNet     [ I ] - Pointer to net device
+
+RETURN VALUE:
+   int - 0 for success
+         Negative errno for error
+===========================================================================*/
+int GobiUSBNetOpen( struct net_device * pNet )
+{
+   int status = 0;
+   struct sGobiUSBNet * pGobiDev;
+   struct usbnet * pDev = netdev_priv( pNet );
+   
+   if (pDev == NULL)
+   {
+      DBG( "failed to get usbnet device\n" );
+      return -ENXIO;
+   }
+   
+   pGobiDev = (sGobiUSBNet *)pDev->data[0];
+   if (pGobiDev == NULL)
+   {
+      DBG( "failed to get QMIDevice\n" );
+      return -ENXIO;
+   }
+
+   DBG( "\n" );
+
+   // Start the AutoPM thread
+   pGobiDev->mAutoPM.mpIntf = pGobiDev->mpIntf;
+   pGobiDev->mAutoPM.mbExit = false;
+   pGobiDev->mAutoPM.mpURBList = NULL;
+   pGobiDev->mAutoPM.mpActiveURB = NULL;
+   spin_lock_init( &pGobiDev->mAutoPM.mURBListLock );
+   spin_lock_init( &pGobiDev->mAutoPM.mActiveURBLock );
+   init_completion( &pGobiDev->mAutoPM.mThreadDoWork );
+   
+   pGobiDev->mAutoPM.mpThread = kthread_run( GobiUSBNetAutoPMThread, 
+                                               &pGobiDev->mAutoPM, 
+                                               "GobiUSBNetAutoPMThread" );
+   if (IS_ERR( pGobiDev->mAutoPM.mpThread ))
+   {
+      DBG( "AutoPM thread creation error\n" );
+      return PTR_ERR( pGobiDev->mAutoPM.mpThread );
+   }
+
+   // Allow traffic
+   GobiClearDownReason( pGobiDev, NET_IFACE_STOPPED );
+
+   // Pass to usbnet_open if defined
+   if (pGobiDev->mpUSBNetOpen != NULL)
+   {
+      status = pGobiDev->mpUSBNetOpen( pNet );
+   
+      // If usbnet_open was successful enable Auto PM
+      if (status == 0)
+      {
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,33 ))
+         usb_autopm_enable( pGobiDev->mpIntf );
+#else
+         usb_autopm_put_interface( pGobiDev->mpIntf );
+#endif
+      }
+   }
+   else
+   {
+      DBG( "no USBNetOpen defined\n" );
+   }
+   
+   return status;
+}
+
+/*===========================================================================
+METHOD:
+   GobiUSBNetStop (Public Method)
+
+DESCRIPTION:
+   Wrapper to usbnet_stop, correctly handling autosuspend
+   Stop AutoPM thread
+
+PARAMETERS
+   pNet     [ I ] - Pointer to net device
+
+RETURN VALUE:
+   int - 0 for success
+         Negative errno for error
+===========================================================================*/
+int GobiUSBNetStop( struct net_device * pNet )
+{
+   struct sGobiUSBNet * pGobiDev;
+   struct usbnet * pDev = netdev_priv( pNet );
+
+   if (pDev == NULL || pDev->net == NULL)
+   {
+      DBG( "failed to get netdevice\n" );
+      return -ENXIO;
+   }
+   
+   pGobiDev = (sGobiUSBNet *)pDev->data[0];
+   if (pGobiDev == NULL)
+   {
+      DBG( "failed to get QMIDevice\n" );
+      return -ENXIO;
+   }
+
+   // Stop traffic
+   GobiSetDownReason( pGobiDev, NET_IFACE_STOPPED );
+
+   // Tell traffic thread to exit
+   pGobiDev->mAutoPM.mbExit = true;
+   complete( &pGobiDev->mAutoPM.mThreadDoWork );
+   
+   // Wait for it to exit
+   while( pGobiDev->mAutoPM.mpThread != NULL )
+   {
+      msleep( 100 );
+   }
+   DBG( "thread stopped\n" );
+
+   // Pass to usbnet_stop, if defined
+   if (pGobiDev->mpUSBNetStop != NULL)
+   {
+      return pGobiDev->mpUSBNetStop( pNet );
+   }
+   else
+   {
+      return 0;
+   }
+}
+
+/*=========================================================================*/
+// Struct driver_info
+/*=========================================================================*/
+static const struct driver_info GobiNetInfo = 
+{
+   .description   = "GobiNet Ethernet Device",
+   .flags         = FLAG_ETHER,
+   .bind          = GobiNetDriverBind,
+   .unbind        = GobiNetDriverUnbind,
+   .data          = 0,
+};
+
+/*=========================================================================*/
+// Qualcomm Gobi 3000 VID/PIDs
+/*=========================================================================*/
+static const struct usb_device_id GobiVIDPIDTable [] =
+{
+   // Gobi 3000
+   { 
+      USB_DEVICE( 0x05c6, 0x920d ),
+      .driver_info = (unsigned long)&GobiNetInfo 
+   },
+   //Terminating entry
+   { }
+};
+
+MODULE_DEVICE_TABLE( usb, GobiVIDPIDTable );
+
+/*===========================================================================
+METHOD:
+   GobiUSBNetProbe (Public Method)
+
+DESCRIPTION:
+   Run usbnet_probe
+   Setup QMI device
+
+PARAMETERS
+   pIntf        [ I ] - Pointer to interface
+   pVIDPIDs     [ I ] - Pointer to VID/PID table
+
+RETURN VALUE:
+   int - 0 for success
+         Negative errno for error
+===========================================================================*/
+int GobiUSBNetProbe( 
+   struct usb_interface *        pIntf, 
+   const struct usb_device_id *  pVIDPIDs )
+{
+   int status;
+   struct usbnet * pDev;
+   sGobiUSBNet * pGobiDev;
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 2,6,29 ))
+   struct net_device_ops * pNetDevOps;
+#endif
+
+   status = usbnet_probe( pIntf, pVIDPIDs );
+   if (status < 0)
+   {
+      DBG( "usbnet_probe failed %d\n", status );
+      return status;
+   }
+
+#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,23 ))
+   pDev = usb_get_intfdata( pIntf );
+#else
+   pDev = (struct usbnet *)pIntf->dev.platform_data;
+#endif
+
+   if (pDev == NULL || pDev->net == NULL)
+   {
+      DBG( "failed to get netdevice\n" );
+      usbnet_disconnect( pIntf );
+      return -ENXIO;
+   }
+
+   pGobiDev = kmalloc( sizeof( sGobiUSBNet ), GFP_KERNEL );
+   if (pGobiDev == NULL)
+   {
+      DBG( "falied to allocate device buffers" );
+      usbnet_disconnect( pIntf );
+      return -ENOMEM;
+   }
+   
+   pDev->data[0] = (unsigned long)pGobiDev;
+   
+   pGobiDev->mpNetDev = pDev;
+
+   // Overload PM related network functions
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 ))
+   pGobiDev->mpUSBNetOpen = pDev->net->open;
+   pDev->net->open = GobiUSBNetOpen;
+   pGobiDev->mpUSBNetStop = pDev->net->stop;
+   pDev->net->stop = GobiUSBNetStop;
+   pDev->net->hard_start_xmit = GobiUSBNetStartXmit;
+   pDev->net->tx_timeout = GobiUSBNetTXTimeout;
+#else
+   pNetDevOps = kmalloc( sizeof( struct net_device_ops ), GFP_KERNEL );
+   if (pNetDevOps == NULL)
+   {
+      DBG( "falied to allocate net device ops" );
+      usbnet_disconnect( pIntf );
+      return -ENOMEM;
+   }
+   memcpy( pNetDevOps, pDev->net->netdev_ops, sizeof( struct net_device_ops ) );
+   
+   pGobiDev->mpUSBNetOpen = pNetDevOps->ndo_open;
+   pNetDevOps->ndo_open = GobiUSBNetOpen;
+   pGobiDev->mpUSBNetStop = pNetDevOps->ndo_stop;
+   pNetDevOps->ndo_stop = GobiUSBNetStop;
+   pNetDevOps->ndo_start_xmit = GobiUSBNetStartXmit;
+   pNetDevOps->ndo_tx_timeout = GobiUSBNetTXTimeout;
+
+   pDev->net->netdev_ops = pNetDevOps;
+#endif
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,31 ))
+   memset( &(pGobiDev->mpNetDev->stats), 0, sizeof( struct net_device_stats ) );
+#else
+   memset( &(pGobiDev->mpNetDev->net->stats), 0, sizeof( struct net_device_stats ) );
+#endif
+
+   pGobiDev->mpIntf = pIntf;
+   memset( &(pGobiDev->mMEID), '0', 14 );
+   
+   DBG( "Mac Address:\n" );
+   PrintHex( &pGobiDev->mpNetDev->net->dev_addr[0], 6 );
+
+   pGobiDev->mbQMIValid = false;
+   memset( &pGobiDev->mQMIDev, 0, sizeof( sQMIDev ) );
+   pGobiDev->mQMIDev.mbCdevIsInitialized = false;
+
+   pGobiDev->mQMIDev.mpDevClass = gpClass;
+   
+   init_completion( &pGobiDev->mAutoPM.mThreadDoWork );
+   spin_lock_init( &pGobiDev->mQMIDev.mClientMemLock );
+
+   // Default to device down
+   pGobiDev->mDownReason = 0;
+   GobiSetDownReason( pGobiDev, NO_NDIS_CONNECTION );
+   GobiSetDownReason( pGobiDev, NET_IFACE_STOPPED );
+
+   // Register QMI
+   status = RegisterQMIDevice( pGobiDev );
+   if (status != 0)
+   {
+      // usbnet_disconnect() will call GobiNetDriverUnbind() which will call
+      // DeregisterQMIDevice() to clean up any partially created QMI device
+      usbnet_disconnect( pIntf );
+      return status;
+   }
+   
+   // Success
+   return 0;
+}
+
+static struct usb_driver GobiNet =
+{
+   .name       = "GobiNet",
+   .id_table   = GobiVIDPIDTable,
+   .probe      = GobiUSBNetProbe,
+   .disconnect = usbnet_disconnect,
+   .suspend    = GobiSuspend,
+   .resume     = GobiResume,
+   .supports_autosuspend = true,
+};
+
+/*===========================================================================
+METHOD:
+   GobiUSBNetModInit (Public Method)
+
+DESCRIPTION:
+   Initialize module
+   Create device class
+   Register out usb_driver struct
+
+RETURN VALUE:
+   int - 0 for success
+         Negative errno for error
+===========================================================================*/
+static int __init GobiUSBNetModInit( void )
+{
+   gpClass = class_create( THIS_MODULE, "GobiQMI" );
+   if (IS_ERR( gpClass ) == true)
+   {
+      DBG( "error at class_create %ld\n",
+           PTR_ERR( gpClass ) );
+      return -ENOMEM;
+   }
+
+   // This will be shown whenever driver is loaded
+   printk( KERN_INFO "%s: %s\n", DRIVER_DESC, DRIVER_VERSION );
+
+   return usb_register( &GobiNet );
+}
+module_init( GobiUSBNetModInit );
+
+/*===========================================================================
+METHOD:
+   GobiUSBNetModExit (Public Method)
+
+DESCRIPTION:
+   Deregister module
+   Destroy device class
+
+RETURN VALUE:
+   void
+===========================================================================*/
+static void __exit GobiUSBNetModExit( void )
+{
+   usb_deregister( &GobiNet );
+
+   class_destroy( gpClass );
+}
+module_exit( GobiUSBNetModExit );
+
+MODULE_VERSION( DRIVER_VERSION );
+MODULE_AUTHOR( DRIVER_AUTHOR );
+MODULE_DESCRIPTION( DRIVER_DESC );
+MODULE_LICENSE( "Dual BSD/GPL" );
+
+#ifdef bool
+#undef bool
+#endif
+
+module_param( debug, bool, S_IRUGO | S_IWUSR );
+MODULE_PARM_DESC( debug, "Debuging enabled or not" );
+
+module_param( interruptible, bool, S_IRUGO | S_IWUSR );
+MODULE_PARM_DESC( interruptible, "Listen for and return on user interrupt" );
diff --git a/drivers/net/usb/QMI.c b/drivers/net/usb/QMI.c
new file mode 100644
index 0000000..f02f927
--- /dev/null
+++ b/drivers/net/usb/QMI.c
@@ -0,0 +1,991 @@
+/*===========================================================================
+FILE:
+   QMI.c
+
+DESCRIPTION:
+   Qualcomm QMI driver code
+   
+FUNCTIONS:
+   Generic QMUX functions
+      ParseQMUX
+      FillQMUX
+   
+   Generic QMI functions
+      GetTLV
+      ValidQMIMessage
+      GetQMIMessageID
+
+   Fill Buffers with QMI requests
+      QMICTLGetClientIDReq
+      QMICTLReleaseClientIDReq
+      QMICTLReadyReq
+      QMIWDSSetEventReportReq
+      QMIWDSGetPKGSRVCStatusReq
+      QMIDMSGetMEIDReq
+      
+   Parse data from QMI responses
+      QMICTLGetClientIDResp
+      QMICTLReleaseClientIDResp
+      QMIWDSEventResp
+      QMIDMSGetMEIDResp
+
+Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of Code Aurora Forum nor
+      the names of its contributors may be used to endorse or promote
+      products derived from this software without specific prior written
+      permission.
+
+Alternatively, provided that this notice is retained in full, this software
+may be relicensed by the recipient under the terms of the GNU General Public
+License version 2 ("GPL") and only version 2, in which case the provisions of
+the GPL apply INSTEAD OF those given above.  If the recipient relicenses the
+software under the GPL, then the identification text in the MODULE_LICENSE
+macro must be changed to reflect "GPLv2" instead of "Dual BSD/GPL".  Once a
+recipient changes the license terms to the GPL, subsequent recipients shall
+not relicense under alternate licensing terms, including the BSD or dual
+BSD/GPL terms.  In addition, the following license statement immediately
+below and between the words START and END shall also then apply when this
+software is relicensed under the GPL:
+
+START
+
+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.
+
+END
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+===========================================================================*/
+
+//---------------------------------------------------------------------------
+// Include Files
+//---------------------------------------------------------------------------
+#include "QMI.h"
+
+/*=========================================================================*/
+// Get sizes of buffers needed by QMI requests
+/*=========================================================================*/
+
+/*===========================================================================
+METHOD:
+   QMUXHeaderSize (Public Method)
+
+DESCRIPTION:
+   Get size of buffer needed for QMUX
+ 
+RETURN VALUE:
+   u16 - size of buffer
+===========================================================================*/
+u16 QMUXHeaderSize( void )
+{
+   return sizeof( sQMUX );
+}
+
+/*===========================================================================
+METHOD:
+   QMICTLGetClientIDReqSize (Public Method)
+
+DESCRIPTION:
+   Get size of buffer needed for QMUX + QMICTLGetClientIDReq
+ 
+RETURN VALUE:
+   u16 - size of buffer
+===========================================================================*/
+u16 QMICTLGetClientIDReqSize( void )
+{
+   return sizeof( sQMUX ) + 10;
+}
+
+/*===========================================================================
+METHOD:
+   QMICTLReleaseClientIDReqSize (Public Method)
+
+DESCRIPTION:
+   Get size of buffer needed for QMUX + QMICTLReleaseClientIDReq
+ 
+RETURN VALUE:
+   u16 - size of header
+===========================================================================*/
+u16 QMICTLReleaseClientIDReqSize( void )
+{
+   return sizeof( sQMUX ) + 11;
+}
+
+/*===========================================================================
+METHOD:
+   QMICTLReadyReqSize (Public Method)
+
+DESCRIPTION:
+   Get size of buffer needed for QMUX + QMICTLReadyReq
+ 
+RETURN VALUE:
+   u16 - size of buffer
+===========================================================================*/
+u16 QMICTLReadyReqSize( void )
+{
+   return sizeof( sQMUX ) + 6;
+}
+
+/*===========================================================================
+METHOD:
+   QMIWDSSetEventReportReqSize (Public Method)
+
+DESCRIPTION:
+   Get size of buffer needed for QMUX + QMIWDSSetEventReportReq
+ 
+RETURN VALUE:
+   u16 - size of buffer
+===========================================================================*/
+u16 QMIWDSSetEventReportReqSize( void )
+{
+   return sizeof( sQMUX ) + 15;
+}
+
+/*===========================================================================
+METHOD:
+   QMIWDSGetPKGSRVCStatusReqSize (Public Method)
+
+DESCRIPTION:
+   Get size of buffer needed for QMUX + QMIWDSGetPKGSRVCStatusReq
+ 
+RETURN VALUE:
+   u16 - size of buffer
+===========================================================================*/
+u16 QMIWDSGetPKGSRVCStatusReqSize( void )
+{
+   return sizeof( sQMUX ) + 7;
+}
+
+/*===========================================================================
+METHOD:
+   QMIDMSGetMEIDReqSize (Public Method)
+
+DESCRIPTION:
+   Get size of buffer needed for QMUX + QMIDMSGetMEIDReq
+ 
+RETURN VALUE:
+   u16 - size of buffer
+===========================================================================*/
+u16 QMIDMSGetMEIDReqSize( void )
+{
+   return sizeof( sQMUX ) + 7;
+}
+
+/*=========================================================================*/
+// Generic QMUX functions
+/*=========================================================================*/
+
+/*===========================================================================
+METHOD:
+   ParseQMUX (Public Method)
+
+DESCRIPTION:
+   Remove QMUX headers from a buffer
+
+PARAMETERS
+   pClientID       [ O ] - On success, will point to Client ID
+   pBuffer         [ I ] - Full Message passed in
+   buffSize        [ I ] - Size of pBuffer
+
+RETURN VALUE:
+   int - Positive for size of QMUX header
+         Negative errno for error
+===========================================================================*/
+int ParseQMUX(
+   u16 *    pClientID,
+   void *   pBuffer,
+   u16      buffSize )
+{
+   sQMUX * pQMUXHeader;
+   
+   if (pBuffer == 0 || buffSize < 12)
+   {
+      return -ENOMEM;
+   }
+
+   // QMUX Header
+   pQMUXHeader = (sQMUX *)pBuffer;
+
+   if (pQMUXHeader->mTF != 1
+   ||  pQMUXHeader->mLength != buffSize - 1
+   ||  pQMUXHeader->mCtrlFlag != 0x80 )
+   {
+      return -EINVAL;
+   }
+
+   // Client ID   
+   *pClientID = (pQMUXHeader->mQMIClientID << 8) 
+              + pQMUXHeader->mQMIService;
+   
+   return sizeof( sQMUX );
+}
+
+/*===========================================================================
+METHOD:
+   FillQMUX (Public Method)
+
+DESCRIPTION:
+   Fill buffer with QMUX headers
+
+PARAMETERS
+   clientID        [ I ] - Client ID
+   pBuffer         [ O ] - Buffer to be filled
+   buffSize        [ I ] - Size of pBuffer (must be at least 6)
+
+RETURN VALUE:
+   int - 0 for success
+         Negative errno for error
+===========================================================================*/
+int FillQMUX(
+   u16      clientID,
+   void *   pBuffer,
+   u16      buffSize )
+{
+   sQMUX * pQMUXHeader;
+
+   if (pBuffer == 0 ||  buffSize < sizeof( sQMUX ))
+   {
+      return -ENOMEM;
+   }
+
+   // QMUX Header
+   pQMUXHeader = (sQMUX *)pBuffer;
+
+   pQMUXHeader->mTF = 1;
+   pQMUXHeader->mLength = buffSize - 1;
+   pQMUXHeader->mCtrlFlag = 0;
+
+   // Service and Client ID   
+   pQMUXHeader->mQMIService = clientID & 0xff;
+   pQMUXHeader->mQMIClientID = clientID >> 8;
+
+   return 0;
+}
+
+/*=========================================================================*/
+// Generic QMI functions
+/*=========================================================================*/
+
+/*===========================================================================
+METHOD:
+   GetTLV (Public Method)
+
+DESCRIPTION:
+   Get data bufffer of a specified TLV from a QMI message
+
+   QMI Message shall NOT include SDU
+   
+PARAMETERS
+   pQMIMessage    [ I ] - QMI Message buffer
+   messageLen     [ I ] - Size of QMI Message buffer
+   type           [ I ] - Desired Type
+   pOutDataBuf    [ O ] - Buffer to be filled with TLV
+   messageLen     [ I ] - Size of QMI Message buffer
+
+RETURN VALUE:
+   u16 - Size of TLV for success
+         Negative errno for error
+===========================================================================*/
+u16 GetTLV(
+   void *   pQMIMessage,
+   u16      messageLen,
+   u8       type,
+   void *   pOutDataBuf,
+   u16      bufferLen )
+{
+   u16 pos;
+   u16 tlvSize = 0;
+   u16 cpyCount;
+   
+   if (pQMIMessage == 0 || pOutDataBuf == 0)
+   {
+      return -ENOMEM;
+   }   
+   
+   for (pos = 4; 
+        pos + 3 < messageLen; 
+        pos += tlvSize + 3)
+   {
+      tlvSize = *(u16 *)(pQMIMessage + pos + 1);
+      if (*(u8 *)(pQMIMessage + pos) == type)
+      {
+         if (bufferLen < tlvSize)
+         {
+            return -ENOMEM;
+         }
+        
+         /* replacement memcpy
+            memcpy( pOutDataBuf,
+                    pQMIMessage + pos + 3,
+                    tlvSize ); */
+         
+         for (cpyCount = 0; cpyCount < tlvSize; cpyCount++)
+         {
+            *((char*)(pOutDataBuf + cpyCount)) = *((char*)(pQMIMessage + pos + 3 + cpyCount));
+         }
+         
+         return tlvSize;
+      }
+   }
+   
+   return -ENOMSG;
+}
+
+/*===========================================================================
+METHOD:
+   ValidQMIMessage (Public Method)
+
+DESCRIPTION:
+   Check mandatory TLV in a QMI message
+
+   QMI Message shall NOT include SDU
+
+PARAMETERS
+   pQMIMessage    [ I ] - QMI Message buffer
+   messageLen     [ I ] - Size of QMI Message buffer
+
+RETURN VALUE:
+   int - 0 for success (no error)
+         Negative errno for error
+         Positive for QMI error code
+===========================================================================*/
+int ValidQMIMessage(
+   void *   pQMIMessage,
+   u16      messageLen )
+{
+   char mandTLV[4];
+
+   if (GetTLV( pQMIMessage, messageLen, 2, &mandTLV[0], 4 ) == 4)
+   {
+      // Found TLV
+      if (*(u16 *)&mandTLV[0] != 0)
+      {
+         return *(u16 *)&mandTLV[2];
+      }
+      else
+      {
+         return 0;
+      }
+   }
+   else
+   {
+      return -ENOMSG;
+   }
+}      
+
+/*===========================================================================
+METHOD:
+   GetQMIMessageID (Public Method)
+
+DESCRIPTION:
+   Get the message ID of a QMI message
+   
+   QMI Message shall NOT include SDU
+
+PARAMETERS
+   pQMIMessage    [ I ] - QMI Message buffer
+   messageLen     [ I ] - Size of QMI Message buffer
+
+RETURN VALUE:
+   int - Positive for message ID
+         Negative errno for error
+===========================================================================*/
+int GetQMIMessageID(
+   void *   pQMIMessage,
+   u16      messageLen )
+{
+   if (messageLen < 2)
+   {
+      return -ENODATA;
+   }
+   else
+   {
+      return *(u16 *)pQMIMessage;
+   }
+}
+
+/*=========================================================================*/
+// Fill Buffers with QMI requests
+/*=========================================================================*/
+
+/*===========================================================================
+METHOD:
+   QMICTLGetClientIDReq (Public Method)
+
+DESCRIPTION:
+   Fill buffer with QMI CTL Get Client ID Request
+
+PARAMETERS
+   pBuffer         [ 0 ] - Buffer to be filled
+   buffSize        [ I ] - Size of pBuffer
+   transactionID   [ I ] - Transaction ID
+   serviceType     [ I ] - Service type requested
+
+RETURN VALUE:
+   int - Positive for resulting size of pBuffer
+         Negative errno for error
+===========================================================================*/
+int QMICTLGetClientIDReq(
+   void *   pBuffer,
+   u16      buffSize,
+   u8       transactionID,
+   u8       serviceType )
+{
+   if (pBuffer == 0 || buffSize < QMICTLGetClientIDReqSize() )
+   {
+      return -ENOMEM;
+   }
+
+   // QMI CTL GET CLIENT ID
+   // Request
+   *(u8 *)(pBuffer + sizeof( sQMUX ))= 0x00;
+   // Transaction ID
+   *(u8 *)(pBuffer + sizeof( sQMUX ) + 1) = transactionID;
+   // Message ID
+   *(u16 *)(pBuffer + sizeof( sQMUX ) + 2) = 0x0022;
+   // Size of TLV's
+   *(u16 *)(pBuffer + sizeof( sQMUX ) + 4) = 0x0004;
+      // QMI Service Type
+      *(u8 *)(pBuffer + sizeof( sQMUX ) + 6)  = 0x01;
+      // Size
+      *(u16 *)(pBuffer + sizeof( sQMUX ) + 7) = 0x0001;
+      // QMI svc type
+      *(u8 *)(pBuffer + sizeof( sQMUX ) + 9)  = serviceType;
+
+   // success
+   return sizeof( sQMUX ) + 10;
+}
+
+/*===========================================================================
+METHOD:
+   QMICTLReleaseClientIDReq (Public Method)
+
+DESCRIPTION:
+   Fill buffer with QMI CTL Release Client ID Request
+
+PARAMETERS
+   pBuffer         [ 0 ] - Buffer to be filled
+   buffSize        [ I ] - Size of pBuffer
+   transactionID   [ I ] - Transaction ID
+   clientID        [ I ] - Service type requested
+
+RETURN VALUE:
+   int - Positive for resulting size of pBuffer
+         Negative errno for error
+===========================================================================*/
+int QMICTLReleaseClientIDReq(
+   void *   pBuffer,
+   u16      buffSize,
+   u8       transactionID,
+   u16      clientID )
+{
+   if (pBuffer == 0 || buffSize < QMICTLReleaseClientIDReqSize() )
+   {
+      return -ENOMEM;
+   }
+
+   // QMI CTL RELEASE CLIENT ID REQ
+   // Request
+   *(u8 *)(pBuffer + sizeof( sQMUX ))  = 0x00;
+   // Transaction ID
+   *(u8 *)(pBuffer + sizeof( sQMUX ) + 1 ) = transactionID;
+   // Message ID
+   *(u16 *)(pBuffer + sizeof( sQMUX ) + 2) = 0x0023;
+   // Size of TLV's
+   *(u16 *)(pBuffer + sizeof( sQMUX ) + 4) = 0x0005;
+      // Release client ID
+      *(u8 *)(pBuffer + sizeof( sQMUX ) + 6)  = 0x01;
+      // Size
+      *(u16 *)(pBuffer + sizeof( sQMUX ) + 7) = 0x0002;
+      // QMI svs type / Client ID
+      *(u16 *)(pBuffer + sizeof( sQMUX ) + 9)  = clientID;
+      
+   // success
+   return sizeof( sQMUX ) + 11;
+}
+
+/*===========================================================================
+METHOD:
+   QMICTLReadyReq (Public Method)
+
+DESCRIPTION:
+   Fill buffer with QMI CTL Get Version Info Request
+
+PARAMETERS
+   pBuffer         [ 0 ] - Buffer to be filled
+   buffSize        [ I ] - Size of pBuffer
+   transactionID   [ I ] - Transaction ID
+
+RETURN VALUE:
+   int - Positive for resulting size of pBuffer
+         Negative errno for error
+===========================================================================*/
+int QMICTLReadyReq(
+   void *   pBuffer,
+   u16      buffSize,
+   u8       transactionID )
+{
+   if (pBuffer == 0 || buffSize < QMICTLReadyReqSize() )
+   {
+      return -ENOMEM;
+   }
+
+   // QMI CTL GET VERSION INFO REQ
+   // Request
+   *(u8 *)(pBuffer + sizeof( sQMUX ))  = 0x00;
+   // Transaction ID
+   *(u8 *)(pBuffer + sizeof( sQMUX ) + 1) = transactionID;
+   // Message ID
+   *(u16 *)(pBuffer + sizeof( sQMUX ) + 2) = 0x0021;
+   // Size of TLV's
+   *(u16 *)(pBuffer + sizeof( sQMUX ) + 4) = 0x0000;
+
+   // success
+   return sizeof( sQMUX ) + 6;
+}
+
+/*===========================================================================
+METHOD:
+   QMIWDSSetEventReportReq (Public Method)
+
+DESCRIPTION:
+   Fill buffer with QMI WDS Set Event Report Request
+
+PARAMETERS
+   pBuffer         [ 0 ] - Buffer to be filled
+   buffSize        [ I ] - Size of pBuffer
+   transactionID   [ I ] - Transaction ID
+
+RETURN VALUE:
+   int - Positive for resulting size of pBuffer
+         Negative errno for error
+===========================================================================*/
+int QMIWDSSetEventReportReq(
+   void *   pBuffer,
+   u16      buffSize,
+   u16      transactionID )
+{
+   if (pBuffer == 0 || buffSize < QMIWDSSetEventReportReqSize() )
+   {
+      return -ENOMEM;
+   }
+
+   // QMI WDS SET EVENT REPORT REQ
+   // Request
+   *(u8 *)(pBuffer + sizeof( sQMUX ))  = 0x00;
+   // Transaction ID
+   *(u16 *)(pBuffer + sizeof( sQMUX ) + 1) = transactionID;
+   // Message ID
+   *(u16 *)(pBuffer + sizeof( sQMUX ) + 3) = 0x0001;
+   // Size of TLV's
+   *(u16 *)(pBuffer + sizeof( sQMUX ) + 5) = 0x0008;
+      // Report channel rate TLV
+      *(u8 *)(pBuffer + sizeof( sQMUX ) + 7)  = 0x11;
+      // Size
+      *(u16 *)(pBuffer + sizeof( sQMUX ) + 8) = 0x0005;
+      // Stats period
+      *(u8 *)(pBuffer + sizeof( sQMUX ) + 10)  = 0x01;
+      // Stats mask
+      *(u32 *)(pBuffer + sizeof( sQMUX ) + 11)  = 0x000000ff;
+
+   // success
+   return sizeof( sQMUX ) + 15;
+}
+
+/*===========================================================================
+METHOD:
+   QMIWDSGetPKGSRVCStatusReq (Public Method)
+
+DESCRIPTION:
+   Fill buffer with QMI WDS Get PKG SRVC Status Request
+
+PARAMETERS
+   pBuffer         [ 0 ] - Buffer to be filled
+   buffSize        [ I ] - Size of pBuffer
+   transactionID   [ I ] - Transaction ID
+
+RETURN VALUE:
+   int - Positive for resulting size of pBuffer
+         Negative errno for error
+===========================================================================*/
+int QMIWDSGetPKGSRVCStatusReq(
+   void *   pBuffer,
+   u16      buffSize,
+   u16      transactionID )
+{
+   if (pBuffer == 0 || buffSize < QMIWDSGetPKGSRVCStatusReqSize() )
+   {
+      return -ENOMEM;
+   }
+
+   // QMI WDS Get PKG SRVC Status REQ
+   // Request
+   *(u8 *)(pBuffer + sizeof( sQMUX ))  = 0x00;
+   // Transaction ID
+   *(u16 *)(pBuffer + sizeof( sQMUX ) + 1) = transactionID;
+   // Message ID
+   *(u16 *)(pBuffer + sizeof( sQMUX ) + 3) = 0x0022;
+   // Size of TLV's
+   *(u16 *)(pBuffer + sizeof( sQMUX ) + 5) = 0x0000;
+
+   // success
+   return sizeof( sQMUX ) + 7;
+}
+
+/*===========================================================================
+METHOD:
+   QMIDMSGetMEIDReq (Public Method)
+
+DESCRIPTION:
+   Fill buffer with QMI DMS Get Serial Numbers Request
+
+PARAMETERS
+   pBuffer         [ 0 ] - Buffer to be filled
+   buffSize        [ I ] - Size of pBuffer
+   transactionID   [ I ] - Transaction ID
+
+RETURN VALUE:
+   int - Positive for resulting size of pBuffer
+         Negative errno for error
+===========================================================================*/
+int QMIDMSGetMEIDReq(
+   void *   pBuffer,
+   u16      buffSize,
+   u16      transactionID )
+{
+   if (pBuffer == 0 || buffSize < QMIDMSGetMEIDReqSize() )
+   {
+      return -ENOMEM;
+   }
+
+   // QMI DMS GET SERIAL NUMBERS REQ
+   // Request
+   *(u8 *)(pBuffer + sizeof( sQMUX ))  = 0x00;
+   // Transaction ID
+   *(u16 *)(pBuffer + sizeof( sQMUX ) + 1) = transactionID;
+   // Message ID
+   *(u16 *)(pBuffer + sizeof( sQMUX ) + 3) = 0x0025;
+   // Size of TLV's
+   *(u16 *)(pBuffer + sizeof( sQMUX ) + 5) = 0x0000;
+
+  // success
+  return sizeof( sQMUX ) + 7;
+}
+
+/*=========================================================================*/
+// Parse data from QMI responses
+/*=========================================================================*/
+
+/*===========================================================================
+METHOD:
+   QMICTLGetClientIDResp (Public Method)
+
+DESCRIPTION:
+   Parse the QMI CTL Get Client ID Resp
+
+PARAMETERS
+   pBuffer         [ I ] - Buffer to be parsed
+   buffSize        [ I ] - Size of pBuffer
+   pClientID       [ 0 ] - Recieved client ID
+
+RETURN VALUE:
+   int - 0 for success
+         Negative errno for error
+===========================================================================*/
+int QMICTLGetClientIDResp(
+   void * pBuffer,
+   u16    buffSize,
+   u16 *  pClientID )
+{
+   int result;
+   
+   // Ignore QMUX and SDU
+   //    QMI CTL SDU is 2 bytes, not 3
+   u8 offset = sizeof( sQMUX ) + 2;
+
+   if (pBuffer == 0 || buffSize < offset)
+   {
+      return -ENOMEM;
+   }
+
+   pBuffer = pBuffer + offset;
+   buffSize -= offset;
+
+   result = GetQMIMessageID( pBuffer, buffSize );
+   if (result != 0x22)
+   {
+      return -EFAULT;
+   }
+
+   result = ValidQMIMessage( pBuffer, buffSize );
+   if (result != 0)
+   {
+      return -EFAULT;
+   }
+
+   result = GetTLV( pBuffer, buffSize, 0x01, pClientID, 2 );
+   if (result != 2)
+   {
+      return -EFAULT;
+   }
+
+   return 0;
+}
+
+/*===========================================================================
+METHOD:
+   QMICTLReleaseClientIDResp (Public Method)
+
+DESCRIPTION:
+   Verify the QMI CTL Release Client ID Resp is valid
+
+PARAMETERS
+   pBuffer         [ I ] - Buffer to be parsed
+   buffSize        [ I ] - Size of pBuffer
+
+RETURN VALUE:
+   int - 0 for success
+         Negative errno for error
+===========================================================================*/
+int QMICTLReleaseClientIDResp(
+   void *   pBuffer,
+   u16      buffSize )
+{
+   int result;
+   
+   // Ignore QMUX and SDU
+   //    QMI CTL SDU is 2 bytes, not 3
+   u8 offset = sizeof( sQMUX ) + 2;
+
+   if (pBuffer == 0 || buffSize < offset)
+   {
+      return -ENOMEM;
+   }
+
+   pBuffer = pBuffer + offset;
+   buffSize -= offset;
+
+   result = GetQMIMessageID( pBuffer, buffSize );
+   if (result != 0x23)
+   {
+      return -EFAULT;
+   }
+
+   result = ValidQMIMessage( pBuffer, buffSize );
+   if (result != 0)
+   {
+      return -EFAULT;
+   }
+
+   return 0;
+}
+
+/*===========================================================================
+METHOD:
+   QMIWDSEventResp (Public Method)
+
+DESCRIPTION:
+   Parse the QMI WDS Set Event Report Resp/Indication or
+      QMI WDS Get PKG SRVC Status Resp/Indication
+
+   Return parameters will only be updated if value was received
+
+PARAMETERS
+   pBuffer         [ I ] - Buffer to be parsed
+   buffSize        [ I ] - Size of pBuffer
+   pTXOk           [ O ] - Number of transmitted packets without errors
+   pRXOk           [ O ] - Number of recieved packets without errors
+   pTXErr          [ O ] - Number of transmitted packets with framing errors
+   pRXErr          [ O ] - Number of recieved packets with framing errors
+   pTXOfl          [ O ] - Number of transmitted packets dropped due to overflow
+   pRXOfl          [ O ] - Number of recieved packets dropped due to overflow
+   pTXBytesOk      [ O ] - Number of transmitted bytes without errors
+   pRXBytesOk      [ O ] - Number of recieved bytes without errors
+   pbLinkState     [ 0 ] - Is the link active?
+   pbReconfigure   [ 0 ] - Must interface be reconfigured? (reset IP address)
+
+RETURN VALUE:
+   int - 0 for success
+         Negative errno for error
+===========================================================================*/
+int QMIWDSEventResp(
+   void *   pBuffer,
+   u16      buffSize,
+   u32 *    pTXOk,
+   u32 *    pRXOk,
+   u32 *    pTXErr,
+   u32 *    pRXErr,
+   u32 *    pTXOfl,
+   u32 *    pRXOfl,
+   u64 *    pTXBytesOk,
+   u64 *    pRXBytesOk,
+   bool *   pbLinkState,
+   bool *   pbReconfigure )
+{
+   int result;
+   u8 pktStatusRead[2];
+
+   // Ignore QMUX and SDU
+   u8 offset = sizeof( sQMUX ) + 3;
+
+   if (pBuffer == 0 
+   || buffSize < offset
+   || pTXOk == 0
+   || pRXOk == 0
+   || pTXErr == 0
+   || pRXErr == 0
+   || pTXOfl == 0
+   || pRXOfl == 0
+   || pTXBytesOk == 0
+   || pRXBytesOk == 0
+   || pbLinkState == 0
+   || pbReconfigure == 0 )
+   {
+      return -ENOMEM;
+   }
+
+   pBuffer = pBuffer + offset;
+   buffSize -= offset;
+
+   // Note: Indications.  No Mandatory TLV required
+
+   result = GetQMIMessageID( pBuffer, buffSize );
+   // QMI WDS Set Event Report Resp
+   if (result == 0x01)
+   {
+      // TLV's are not mandatory
+      GetTLV( pBuffer, buffSize, 0x10, (void*)pTXOk, 4 );
+      GetTLV( pBuffer, buffSize, 0x11, (void*)pRXOk, 4 );
+      GetTLV( pBuffer, buffSize, 0x12, (void*)pTXErr, 4 );
+      GetTLV( pBuffer, buffSize, 0x13, (void*)pRXErr, 4 );
+      GetTLV( pBuffer, buffSize, 0x14, (void*)pTXOfl, 4 );
+      GetTLV( pBuffer, buffSize, 0x15, (void*)pRXOfl, 4 );
+      GetTLV( pBuffer, buffSize, 0x19, (void*)pTXBytesOk, 8 );
+      GetTLV( pBuffer, buffSize, 0x1A, (void*)pRXBytesOk, 8 );
+   }
+   // QMI WDS Get PKG SRVC Status Resp
+   else if (result == 0x22)
+   {
+      result = GetTLV( pBuffer, buffSize, 0x01, &pktStatusRead[0], 2 );
+      // 1 or 2 bytes may be received
+      if (result >= 1)
+      {
+         if (pktStatusRead[0] == 0x02)
+         {
+            *pbLinkState = true;
+         }
+         else
+         {
+            *pbLinkState = false;
+         }
+      }
+      if (result == 2)
+      {
+         if (pktStatusRead[1] == 0x01)
+         {
+            *pbReconfigure = true;
+         }
+         else
+         {
+            *pbReconfigure = false;
+         }
+      }
+      
+      if (result < 0)
+      {
+         return result;
+      }
+   }
+   else
+   {
+      return -EFAULT;
+   }
+
+   return 0;
+}
+
+/*===========================================================================
+METHOD:
+   QMIDMSGetMEIDResp (Public Method)
+
+DESCRIPTION:
+   Parse the QMI DMS Get Serial Numbers Resp
+
+PARAMETERS
+   pBuffer         [ I ] - Buffer to be parsed
+   buffSize        [ I ] - Size of pBuffer
+   pMEID           [ O ] - Device MEID
+   meidSize        [ I ] - Size of MEID buffer (at least 14)
+
+RETURN VALUE:
+   int - 0 for success
+         Negative errno for error
+===========================================================================*/
+int QMIDMSGetMEIDResp(
+   void *   pBuffer,
+   u16      buffSize,
+   char *   pMEID,
+   int      meidSize )
+{
+   int result;
+
+   // Ignore QMUX and SDU
+   u8 offset = sizeof( sQMUX ) + 3;
+
+   if (pBuffer == 0 || buffSize < offset || meidSize < 14)
+   {
+      return -ENOMEM;
+   }
+
+   pBuffer = pBuffer + offset;
+   buffSize -= offset;
+
+   result = GetQMIMessageID( pBuffer, buffSize );
+   if (result != 0x25)
+   {
+      return -EFAULT;
+   }
+
+   result = ValidQMIMessage( pBuffer, buffSize );
+   if (result != 0)
+   {
+      return -EFAULT;
+   }
+
+   result = GetTLV( pBuffer, buffSize, 0x12, (void*)pMEID, 14 );
+   if (result != 14)
+   {
+      return -EFAULT;
+   }
+
+   return 0;
+}
+
diff --git a/drivers/net/usb/QMI.h b/drivers/net/usb/QMI.h
new file mode 100644
index 0000000..e6e63c6
--- /dev/null
+++ b/drivers/net/usb/QMI.h
@@ -0,0 +1,289 @@
+/*===========================================================================
+FILE:
+   QMI.h
+
+DESCRIPTION:
+   Qualcomm QMI driver header
+   
+FUNCTIONS:
+   Generic QMUX functions
+      ParseQMUX
+      FillQMUX
+   
+   Generic QMI functions
+      GetTLV
+      ValidQMIMessage
+      GetQMIMessageID
+
+   Get sizes of buffers needed by QMI requests
+      QMUXHeaderSize
+      QMICTLGetClientIDReqSize
+      QMICTLReleaseClientIDReqSize
+      QMICTLReadyReqSize
+      QMIWDSSetEventReportReqSize
+      QMIWDSGetPKGSRVCStatusReqSize
+      QMIDMSGetMEIDReqSize
+
+   Fill Buffers with QMI requests
+      QMICTLGetClientIDReq
+      QMICTLReleaseClientIDReq
+      QMICTLReadyReq
+      QMIWDSSetEventReportReq
+      QMIWDSGetPKGSRVCStatusReq
+      QMIDMSGetMEIDReq
+      
+   Parse data from QMI responses
+      QMICTLGetClientIDResp
+      QMICTLReleaseClientIDResp
+      QMIWDSEventResp
+      QMIDMSGetMEIDResp
+
+Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of Code Aurora Forum nor
+      the names of its contributors may be used to endorse or promote
+      products derived from this software without specific prior written
+      permission.
+
+Alternatively, provided that this notice is retained in full, this software
+may be relicensed by the recipient under the terms of the GNU General Public
+License version 2 ("GPL") and only version 2, in which case the provisions of
+the GPL apply INSTEAD OF those given above.  If the recipient relicenses the
+software under the GPL, then the identification text in the MODULE_LICENSE
+macro must be changed to reflect "GPLv2" instead of "Dual BSD/GPL".  Once a
+recipient changes the license terms to the GPL, subsequent recipients shall
+not relicense under alternate licensing terms, including the BSD or dual
+BSD/GPL terms.  In addition, the following license statement immediately
+below and between the words START and END shall also then apply when this
+software is relicensed under the GPL:
+
+START
+
+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.
+
+END
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+===========================================================================*/
+
+#pragma once
+
+/*=========================================================================*/
+// Definitions
+/*=========================================================================*/
+
+// QMI Service Types
+#define QMICTL 0
+#define QMIWDS 1
+#define QMIDMS 2
+
+#define u8        unsigned char
+#define u16       unsigned short
+#define u32       unsigned int
+#define u64       unsigned long long
+
+#define bool      u8
+#define true      1
+#define false     0
+
+#define ENOMEM    12
+#define EFAULT    14
+#define EINVAL    22
+#define ENOMSG    42
+#define ENODATA   61
+
+/*=========================================================================*/
+// Struct sQMUX
+//
+//    Structure that defines a QMUX header
+/*=========================================================================*/
+typedef struct sQMUX
+{
+   /* T\F, always 1 */
+   u8         mTF;
+
+   /* Size of message */
+   u16        mLength;
+
+   /* Control flag */
+   u8         mCtrlFlag;
+   
+   /* Service Type */
+   u8         mQMIService;
+   
+   /* Client ID */
+   u8         mQMIClientID;
+
+}__attribute__((__packed__)) sQMUX;
+
+/*=========================================================================*/
+// Generic QMUX functions
+/*=========================================================================*/
+
+// Remove QMUX headers from a buffer
+int ParseQMUX(
+   u16 *    pClientID,
+   void *   pBuffer,
+   u16      buffSize );
+
+// Fill buffer with QMUX headers
+int FillQMUX(
+   u16      clientID,
+   void *   pBuffer,
+   u16      buffSize );
+
+/*=========================================================================*/
+// Generic QMI functions
+/*=========================================================================*/
+
+// Get data bufffer of a specified TLV from a QMI message
+u16 GetTLV(
+   void *   pQMIMessage,
+   u16      messageLen,
+   u8       type,
+   void *   pOutDataBuf,
+   u16      bufferLen );
+
+// Check mandatory TLV in a QMI message
+int ValidQMIMessage(
+   void *   pQMIMessage,
+   u16      messageLen );
+
+// Get the message ID of a QMI message
+int GetQMIMessageID(
+   void *   pQMIMessage,
+   u16      messageLen );
+
+/*=========================================================================*/
+// Get sizes of buffers needed by QMI requests
+/*=========================================================================*/
+
+// Get size of buffer needed for QMUX
+u16 QMUXHeaderSize( void );
+
+// Get size of buffer needed for QMUX + QMICTLGetClientIDReq
+u16 QMICTLGetClientIDReqSize( void );
+
+// Get size of buffer needed for QMUX + QMICTLReleaseClientIDReq
+u16 QMICTLReleaseClientIDReqSize( void );
+
+// Get size of buffer needed for QMUX + QMICTLReadyReq
+u16 QMICTLReadyReqSize( void );
+
+// Get size of buffer needed for QMUX + QMIWDSSetEventReportReq
+u16 QMIWDSSetEventReportReqSize( void );
+
+// Get size of buffer needed for QMUX + QMIWDSGetPKGSRVCStatusReq
+u16 QMIWDSGetPKGSRVCStatusReqSize( void );
+
+// Get size of buffer needed for QMUX + QMIDMSGetMEIDReq
+u16 QMIDMSGetMEIDReqSize( void );
+
+/*=========================================================================*/
+// Fill Buffers with QMI requests
+/*=========================================================================*/
+
+// Fill buffer with QMI CTL Get Client ID Request
+int QMICTLGetClientIDReq(
+   void *   pBuffer,
+   u16      buffSize,
+   u8       transactionID,
+   u8       serviceType );
+
+// Fill buffer with QMI CTL Release Client ID Request
+int QMICTLReleaseClientIDReq(
+   void *   pBuffer,
+   u16      buffSize,
+   u8       transactionID,
+   u16      clientID );
+
+// Fill buffer with QMI CTL Get Version Info Request
+int QMICTLReadyReq(
+   void *   pBuffer,
+   u16      buffSize,
+   u8       transactionID );
+
+// Fill buffer with QMI WDS Set Event Report Request
+int QMIWDSSetEventReportReq(
+   void *   pBuffer,
+   u16      buffSize,
+   u16      transactionID );
+
+// Fill buffer with QMI WDS Get PKG SRVC Status Request
+int QMIWDSGetPKGSRVCStatusReq(
+   void *   pBuffer,
+   u16      buffSize,
+   u16      transactionID );
+
+// Fill buffer with QMI DMS Get Serial Numbers Request
+int QMIDMSGetMEIDReq(
+   void *   pBuffer,
+   u16      buffSize,
+   u16      transactionID );
+
+/*=========================================================================*/
+// Parse data from QMI responses
+/*=========================================================================*/
+
+// Parse the QMI CTL Get Client ID Resp
+int QMICTLGetClientIDResp(
+   void * pBuffer,
+   u16    buffSize,
+   u16 *  pClientID );
+
+// Verify the QMI CTL Release Client ID Resp is valid
+int QMICTLReleaseClientIDResp(
+   void *   pBuffer,
+   u16      buffSize );
+
+// Parse the QMI WDS Set Event Report Resp/Indication or
+//    QMI WDS Get PKG SRVC Status Resp/Indication
+int QMIWDSEventResp(
+   void *   pBuffer,
+   u16      buffSize,
+   u32 *    pTXOk,
+   u32 *    pRXOk,
+   u32 *    pTXErr,
+   u32 *    pRXErr,
+   u32 *    pTXOfl,
+   u32 *    pRXOfl,
+   u64 *    pTXBytesOk,
+   u64 *    pRXBytesOk,
+   bool *   pbLinkState,
+   bool *   pbReconfigure );
+
+// Parse the QMI DMS Get Serial Numbers Resp
+int QMIDMSGetMEIDResp(
+   void *   pBuffer,
+   u16      buffSize,
+   char *   pMEID,
+   int      meidSize );
+
diff --git a/drivers/net/usb/QMIDevice.c b/drivers/net/usb/QMIDevice.c
new file mode 100644
index 0000000..ff11456
--- /dev/null
+++ b/drivers/net/usb/QMIDevice.c
@@ -0,0 +1,3273 @@
+/*===========================================================================
+FILE:
+   QMIDevice.c
+
+DESCRIPTION:
+   Functions related to the QMI interface device
+   
+FUNCTIONS:
+   Generic functions
+      IsDeviceValid
+      PrintHex
+      GobiSetDownReason
+      GobiClearDownReason
+      GobiTestDownReason
+
+   Driver level asynchronous read functions
+      ResubmitIntURB
+      ReadCallback
+      IntCallback
+      StartRead
+      KillRead
+
+   Internal read/write functions
+      ReadAsync
+      UpSem
+      ReadSync
+      WriteSyncCallback
+      WriteSync
+
+   Internal memory management functions
+      GetClientID
+      ReleaseClientID
+      FindClientMem
+      AddToReadMemList
+      PopFromReadMemList
+      AddToNotifyList
+      NotifyAndPopNotifyList
+      AddToURBList
+      PopFromURBList
+
+   Userspace wrappers
+      UserspaceOpen
+      UserspaceIOCTL
+      UserspaceClose
+      UserspaceRead
+      UserspaceWrite
+
+   Initializer and destructor
+      RegisterQMIDevice
+      DeregisterQMIDevice
+
+   Driver level client management
+      QMIReady
+      QMIWDSCallback
+      SetupQMIWDSCallback
+      QMIDMSGetMEID
+
+Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of Code Aurora Forum nor
+      the names of its contributors may be used to endorse or promote
+      products derived from this software without specific prior written
+      permission.
+
+Alternatively, provided that this notice is retained in full, this software
+may be relicensed by the recipient under the terms of the GNU General Public
+License version 2 ("GPL") and only version 2, in which case the provisions of
+the GPL apply INSTEAD OF those given above.  If the recipient relicenses the
+software under the GPL, then the identification text in the MODULE_LICENSE
+macro must be changed to reflect "GPLv2" instead of "Dual BSD/GPL".  Once a
+recipient changes the license terms to the GPL, subsequent recipients shall
+not relicense under alternate licensing terms, including the BSD or dual
+BSD/GPL terms.  In addition, the following license statement immediately
+below and between the words START and END shall also then apply when this
+software is relicensed under the GPL:
+
+START
+
+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.
+
+END
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+===========================================================================*/
+
+//---------------------------------------------------------------------------
+// Include Files
+//---------------------------------------------------------------------------
+#include "QMIDevice.h"
+
+//-----------------------------------------------------------------------------
+// Definitions
+//-----------------------------------------------------------------------------
+
+extern int debug;
+extern int interruptible;
+
+// Prototype to GobiSuspend function
+int GobiSuspend( 
+   struct usb_interface *     pIntf,
+   pm_message_t               powerEvent );
+
+// IOCTL to generate a client ID for this service type
+#define IOCTL_QMI_GET_SERVICE_FILE 0x8BE0 + 1
+
+// IOCTL to get the VIDPID of the device
+#define IOCTL_QMI_GET_DEVICE_VIDPID 0x8BE0 + 2
+
+// IOCTL to get the MEID of the device
+#define IOCTL_QMI_GET_DEVICE_MEID 0x8BE0 + 3
+
+// CDC GET_ENCAPSULATED_RESPONSE packet
+#define CDC_GET_ENCAPSULATED_RESPONSE 0x01A1ll
+
+// CDC CONNECTION_SPEED_CHANGE indication packet
+#define CDC_CONNECTION_SPEED_CHANGE 0x08000000002AA1ll
+
+/*=========================================================================*/
+// UserspaceQMIFops
+//    QMI device's userspace file operations
+/*=========================================================================*/
+struct file_operations UserspaceQMIFops = 
+{
+   .owner     = THIS_MODULE,
+   .read      = UserspaceRead,
+   .write     = UserspaceWrite,
+   .ioctl     = UserspaceIOCTL,
+   .open      = UserspaceOpen,
+   .flush     = UserspaceClose,
+};
+
+/*=========================================================================*/
+// Generic functions
+/*=========================================================================*/
+
+/*===========================================================================
+METHOD:
+   IsDeviceValid (Public Method)
+
+DESCRIPTION:
+   Basic test to see if device memory is valid
+
+PARAMETERS:
+   pDev     [ I ] - Device specific memory
+
+RETURN VALUE:
+   bool
+===========================================================================*/
+bool IsDeviceValid( sGobiUSBNet * pDev )
+{
+   if (pDev == NULL)
+   {
+      return false;
+   }
+
+   if (pDev->mbQMIValid == false)
+   {
+      return false;
+   }
+   
+   return true;
+} 
+
+/*===========================================================================
+METHOD:
+   PrintHex (Public Method)
+
+DESCRIPTION:
+   Print Hex data, for debug purposes
+
+PARAMETERS:
+   pBuffer       [ I ] - Data buffer
+   bufSize       [ I ] - Size of data buffer
+
+RETURN VALUE:
+   None
+===========================================================================*/
+void PrintHex(
+   void *      pBuffer,
+   u16         bufSize )
+{
+   char * pPrintBuf;
+   u16 pos;
+   int status;
+   
+   pPrintBuf = kmalloc( bufSize * 3 + 1, GFP_ATOMIC );
+   if (pPrintBuf == NULL)
+   {
+      DBG( "Unable to allocate buffer\n" );
+      return;
+   }
+   memset( pPrintBuf, 0 , bufSize * 3 + 1 );
+   
+   for (pos = 0; pos < bufSize; pos++)
+   {
+      status = snprintf( (pPrintBuf + (pos * 3)), 
+                         4, 
+                         "%02X ", 
+                         *(u8 *)(pBuffer + pos) );
+      if (status != 3)
+      {
+         DBG( "snprintf error %d\n", status );
+         kfree( pPrintBuf );
+         return;
+      }
+   }
+   
+   DBG( "   : %s\n", pPrintBuf );
+
+   kfree( pPrintBuf );
+   pPrintBuf = NULL;
+   return;   
+}
+
+/*===========================================================================
+METHOD:
+   GobiSetDownReason (Public Method)
+
+DESCRIPTION:
+   Sets mDownReason and turns carrier off
+
+PARAMETERS
+   pDev     [ I ] - Device specific memory
+   reason   [ I ] - Reason device is down
+
+RETURN VALUE:
+   None
+===========================================================================*/
+void GobiSetDownReason(
+   sGobiUSBNet *    pDev,
+   u8                 reason )
+{
+   set_bit( reason, &pDev->mDownReason );
+   
+   netif_carrier_off( pDev->mpNetDev->net );
+}
+
+/*===========================================================================
+METHOD:
+   GobiClearDownReason (Public Method)
+
+DESCRIPTION:
+   Clear mDownReason and may turn carrier on
+
+PARAMETERS
+   pDev     [ I ] - Device specific memory
+   reason   [ I ] - Reason device is no longer down
+
+RETURN VALUE:
+   None
+===========================================================================*/
+void GobiClearDownReason(
+   sGobiUSBNet *    pDev,
+   u8                 reason )
+{
+   clear_bit( reason, &pDev->mDownReason );
+   
+   if (pDev->mDownReason == 0)
+   {
+      netif_carrier_on( pDev->mpNetDev->net );
+   }
+}
+
+/*===========================================================================
+METHOD:
+   GobiTestDownReason (Public Method)
+
+DESCRIPTION:
+   Test mDownReason and returns whether reason is set
+
+PARAMETERS
+   pDev     [ I ] - Device specific memory
+   reason   [ I ] - Reason device is down
+
+RETURN VALUE:
+   bool
+===========================================================================*/
+bool GobiTestDownReason(
+   sGobiUSBNet *    pDev,
+   u8                 reason )
+{
+   return test_bit( reason, &pDev->mDownReason );
+}
+
+/*=========================================================================*/
+// Driver level asynchronous read functions
+/*=========================================================================*/
+
+/*===========================================================================
+METHOD:
+   ResubmitIntURB (Public Method)
+
+DESCRIPTION:
+   Resubmit interrupt URB, re-using same values
+
+PARAMETERS
+   pIntURB       [ I ] - Interrupt URB 
+
+RETURN VALUE:
+   int - 0 for success
+         negative errno for failure
+===========================================================================*/
+int ResubmitIntURB( struct urb * pIntURB )
+{
+   int status;
+   int interval;
+
+   // Sanity test
+   if ( (pIntURB == NULL)
+   ||   (pIntURB->dev == NULL) )
+   {
+      return -EINVAL;
+   }
+ 
+   // Interval needs reset after every URB completion
+   interval = (pIntURB->dev->speed == USB_SPEED_HIGH) ? 7 : 3;
+
+   // Reschedule interrupt URB
+   usb_fill_int_urb( pIntURB,
+                     pIntURB->dev,
+                     pIntURB->pipe,
+                     pIntURB->transfer_buffer,
+                     pIntURB->transfer_buffer_length,
+                     pIntURB->complete,
+                     pIntURB->context,
+                     interval );
+   status = usb_submit_urb( pIntURB, GFP_ATOMIC );
+   if (status != 0)
+   {
+      DBG( "Error re-submitting Int URB %d\n", status );
+   }
+
+   return status;
+}
+
+/*===========================================================================
+METHOD:
+   ReadCallback (Public Method)
+
+DESCRIPTION:
+   Put the data in storage and notify anyone waiting for data
+
+PARAMETERS
+   pReadURB       [ I ] - URB this callback is run for
+
+RETURN VALUE:
+   None
+===========================================================================*/
+void ReadCallback( struct urb * pReadURB )
+{
+   int result;
+   u16 clientID;
+   sClientMemList * pClientMem;
+   void * pData;
+   void * pDataCopy;
+   u16 dataSize;
+   sGobiUSBNet * pDev;
+   unsigned long flags;
+   u16 transactionID;
+
+   if (pReadURB == NULL)
+   {
+      DBG( "bad read URB\n" );
+      return;
+   }
+   
+   pDev = pReadURB->context;
+   if (IsDeviceValid( pDev ) == false)
+   {
+      DBG( "Invalid device!\n" );
+      return;
+   }   
+
+   if (pReadURB->status != 0)
+   {
+      DBG( "Read status = %d\n", pReadURB->status );
+
+      // Resubmit the interrupt URB
+      ResubmitIntURB( pDev->mQMIDev.mpIntURB );
+
+      return;
+   }
+   DBG( "Read %d bytes\n", pReadURB->actual_length );
+   
+   pData = pReadURB->transfer_buffer;
+   dataSize = pReadURB->actual_length;
+
+   PrintHex( pData, dataSize );
+
+   result = ParseQMUX( &clientID,
+                       pData,
+                       dataSize );
+   if (result < 0)
+   {
+      DBG( "Read error parsing QMUX %d\n", result );
+
+      // Resubmit the interrupt URB
+      ResubmitIntURB( pDev->mQMIDev.mpIntURB );
+
+      return;
+   }
+   
+   // Grab transaction ID
+
+   // Data large enough?
+   if (dataSize < result + 3)
+   {
+      DBG( "Data buffer too small to parse\n" );
+
+      // Resubmit the interrupt URB
+      ResubmitIntURB( pDev->mQMIDev.mpIntURB );
+
+      return;
+   }
+   
+   // Transaction ID size is 1 for QMICTL, 2 for others
+   if (clientID == QMICTL)
+   {
+      transactionID = *(u8*)(pData + result + 1);
+   }
+   else
+   {
+      transactionID = *(u16*)(pData + result + 1);
+   }
+   
+   // Critical section
+   spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
+
+   // Find memory storage for this service and Client ID
+   // Not using FindClientMem because it can't handle broadcasts
+   pClientMem = pDev->mQMIDev.mpClientMemList;
+   while (pClientMem != NULL)
+   {
+      if (pClientMem->mClientID == clientID 
+      ||  (pClientMem->mClientID | 0xff00) == clientID)
+      {
+         // Make copy of pData
+         pDataCopy = kmalloc( dataSize, GFP_ATOMIC );
+         memcpy( pDataCopy, pData, dataSize );
+
+         if (AddToReadMemList( pDev,
+                               pClientMem->mClientID,
+                               transactionID,
+                               pDataCopy,
+                               dataSize ) == false)
+         {
+            DBG( "Error allocating pReadMemListEntry "
+                 "read will be discarded\n" );
+            kfree( pDataCopy );
+            
+            // End critical section
+            spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
+
+            // Resubmit the interrupt URB
+            ResubmitIntURB( pDev->mQMIDev.mpIntURB );
+
+            return;
+         }
+
+         // Success
+         DBG( "Creating new readListEntry for client 0x%04X, TID %x\n",
+              clientID,
+              transactionID );
+
+         // Notify this client data exists
+         NotifyAndPopNotifyList( pDev,
+                                 pClientMem->mClientID,
+                                 transactionID );
+
+         // Not a broadcast
+         if (clientID >> 8 != 0xff)
+         {
+            break;
+         }
+      }
+      
+      // Next element
+      pClientMem = pClientMem->mpNext;
+   }
+   
+   // End critical section
+   spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
+   
+   // Resubmit the interrupt URB
+   ResubmitIntURB( pDev->mQMIDev.mpIntURB );
+}
+
+/*===========================================================================
+METHOD:
+   IntCallback (Public Method)
+
+DESCRIPTION:
+   Data is available, fire off a read URB
+
+PARAMETERS
+   pIntURB       [ I ] - URB this callback is run for
+
+RETURN VALUE:
+   None
+===========================================================================*/
+void IntCallback( struct urb * pIntURB )
+{
+   int status;
+   
+   sGobiUSBNet * pDev = (sGobiUSBNet *)pIntURB->context;
+   if (IsDeviceValid( pDev ) == false)
+   {
+      DBG( "Invalid device!\n" );
+      return;
+   }
+
+   // Verify this was a normal interrupt
+   if (pIntURB->status != 0)
+   {
+      DBG( "Int status = %d\n", pIntURB->status );
+      
+      // Ignore EOVERFLOW errors
+      if (pIntURB->status != -EOVERFLOW)
+      {
+         // Read 'thread' dies here
+         return;
+      }
+   }
+   else
+   {
+      // CDC GET_ENCAPSULATED_RESPONSE
+      if ((pIntURB->actual_length == 8)
+      &&  (*(u64*)pIntURB->transfer_buffer == CDC_GET_ENCAPSULATED_RESPONSE))
+      {
+         // Time to read
+         usb_fill_control_urb( pDev->mQMIDev.mpReadURB,
+                               pDev->mpNetDev->udev,
+                               usb_rcvctrlpipe( pDev->mpNetDev->udev, 0 ),
+                               (unsigned char *)pDev->mQMIDev.mpReadSetupPacket,
+                               pDev->mQMIDev.mpReadBuffer,
+                               DEFAULT_READ_URB_LENGTH,
+                               ReadCallback,
+                               pDev );
+         status = usb_submit_urb( pDev->mQMIDev.mpReadURB, GFP_ATOMIC );
+         if (status != 0)
+         {
+            DBG( "Error submitting Read URB %d\n", status );
+         }
+
+         // Int URB will be resubmitted during ReadCallback
+         return;
+      }
+      // CDC CONNECTION_SPEED_CHANGE
+      else if ((pIntURB->actual_length == 16)
+      &&       (*(u64*)pIntURB->transfer_buffer == CDC_CONNECTION_SPEED_CHANGE))
+      {
+         // if upstream or downstream is 0, stop traffic.  Otherwise resume it
+         if ((*(u32*)(pIntURB->transfer_buffer + 8) == 0)
+         ||  (*(u32*)(pIntURB->transfer_buffer + 12) == 0))
+         {
+            GobiSetDownReason( pDev, CDC_CONNECTION_SPEED );
+            DBG( "traffic stopping due to CONNECTION_SPEED_CHANGE\n" );
+         }
+         else
+         {
+            GobiClearDownReason( pDev, CDC_CONNECTION_SPEED );
+            DBG( "resuming traffic due to CONNECTION_SPEED_CHANGE\n" );
+         }
+      }
+      else
+      {
+         DBG( "ignoring invalid interrupt in packet\n" );
+         PrintHex( pIntURB->transfer_buffer, pIntURB->actual_length );
+      }
+   }
+
+   ResubmitIntURB( pIntURB );
+
+   return;
+}
+
+/*===========================================================================
+METHOD:
+   StartRead (Public Method)
+
+DESCRIPTION:
+   Start continuous read "thread" (callback driven)
+
+   Note: In case of error, KillRead() should be run
+         to remove urbs and clean up memory.
+   
+PARAMETERS:
+   pDev     [ I ] - Device specific memory
+
+RETURN VALUE:
+   int - 0 for success
+         negative errno for failure
+===========================================================================*/
+int StartRead( sGobiUSBNet * pDev )
+{
+   int interval;
+
+   if (IsDeviceValid( pDev ) == false)
+   {
+      DBG( "Invalid device!\n" );
+      return -ENXIO;
+   }
+   
+   // Allocate URB buffers
+   pDev->mQMIDev.mpReadURB = usb_alloc_urb( 0, GFP_KERNEL );
+   if (pDev->mQMIDev.mpReadURB == NULL)
+   {
+      DBG( "Error allocating read urb\n" );
+      return -ENOMEM;
+   }
+   
+   pDev->mQMIDev.mpIntURB = usb_alloc_urb( 0, GFP_KERNEL );
+   if (pDev->mQMIDev.mpIntURB == NULL)
+   {
+      DBG( "Error allocating int urb\n" );
+      return -ENOMEM;
+   }
+
+   // Create data buffers
+   pDev->mQMIDev.mpReadBuffer = kmalloc( DEFAULT_READ_URB_LENGTH, GFP_KERNEL );
+   if (pDev->mQMIDev.mpReadBuffer == NULL)
+   {
+      DBG( "Error allocating read buffer\n" );
+      return -ENOMEM;
+   }
+   
+   pDev->mQMIDev.mpIntBuffer = kmalloc( DEFAULT_READ_URB_LENGTH, GFP_KERNEL );
+   if (pDev->mQMIDev.mpIntBuffer == NULL)
+   {
+      DBG( "Error allocating int buffer\n" );
+      return -ENOMEM;
+   }      
+   
+   pDev->mQMIDev.mpReadSetupPacket = kmalloc( sizeof( sURBSetupPacket ), 
+                                              GFP_KERNEL );
+   if (pDev->mQMIDev.mpReadSetupPacket == NULL)
+   {
+      DBG( "Error allocating setup packet buffer\n" );
+      return -ENOMEM;
+   }
+
+   // CDC Get Encapsulated Response packet
+   pDev->mQMIDev.mpReadSetupPacket->mRequestType = 0xA1;
+   pDev->mQMIDev.mpReadSetupPacket->mRequestCode = 1;
+   pDev->mQMIDev.mpReadSetupPacket->mValue = 0;
+   pDev->mQMIDev.mpReadSetupPacket->mIndex = 0;
+   pDev->mQMIDev.mpReadSetupPacket->mLength = DEFAULT_READ_URB_LENGTH;
+
+   interval = (pDev->mpNetDev->udev->speed == USB_SPEED_HIGH) ? 7 : 3;
+   
+   // Schedule interrupt URB
+   usb_fill_int_urb( pDev->mQMIDev.mpIntURB,
+                     pDev->mpNetDev->udev,
+                     usb_rcvintpipe( pDev->mpNetDev->udev, 0x81 ),
+                     pDev->mQMIDev.mpIntBuffer,
+                     DEFAULT_READ_URB_LENGTH,
+                     IntCallback,
+                     pDev,
+                     interval );
+   return usb_submit_urb( pDev->mQMIDev.mpIntURB, GFP_KERNEL );
+}
+
+/*===========================================================================
+METHOD:
+   KillRead (Public Method)
+
+DESCRIPTION:
+   Kill continuous read "thread"
+   
+PARAMETERS:
+   pDev     [ I ] - Device specific memory
+
+RETURN VALUE:
+   None
+===========================================================================*/
+void KillRead( sGobiUSBNet * pDev )
+{
+   // Stop reading
+   if (pDev->mQMIDev.mpReadURB != NULL)
+   {
+      DBG( "Killng read URB\n" );
+      usb_kill_urb( pDev->mQMIDev.mpReadURB );
+   }
+
+   if (pDev->mQMIDev.mpIntURB != NULL)
+   {
+      DBG( "Killng int URB\n" );
+      usb_kill_urb( pDev->mQMIDev.mpIntURB );
+   }
+
+   // Release buffers
+   kfree( pDev->mQMIDev.mpReadSetupPacket );
+   pDev->mQMIDev.mpReadSetupPacket = NULL;
+   kfree( pDev->mQMIDev.mpReadBuffer );
+   pDev->mQMIDev.mpReadBuffer = NULL;
+   kfree( pDev->mQMIDev.mpIntBuffer );
+   pDev->mQMIDev.mpIntBuffer = NULL;
+   
+   // Release URB's
+   usb_free_urb( pDev->mQMIDev.mpReadURB );
+   pDev->mQMIDev.mpReadURB = NULL;
+   usb_free_urb( pDev->mQMIDev.mpIntURB );
+   pDev->mQMIDev.mpIntURB = NULL;
+}
+
+/*=========================================================================*/
+// Internal read/write functions
+/*=========================================================================*/
+
+/*===========================================================================
+METHOD:
+   ReadAsync (Public Method)
+
+DESCRIPTION:
+   Start asynchronous read
+   NOTE: Reading client's data store, not device
+
+PARAMETERS:
+   pDev              [ I ] - Device specific memory
+   clientID          [ I ] - Requester's client ID
+   transactionID     [ I ] - Transaction ID or 0 for any
+   pCallback         [ I ] - Callback to be executed when data is available
+   pData             [ I ] - Data buffer that willl be passed (unmodified) 
+                             to callback
+
+RETURN VALUE:
+   int - 0 for success
+         negative errno for failure
+===========================================================================*/
+int ReadAsync(
+   sGobiUSBNet *    pDev,
+   u16                clientID,
+   u16                transactionID,
+   void               (*pCallback)(sGobiUSBNet*, u16, void *),
+   void *             pData )
+{
+   sClientMemList * pClientMem;
+   sReadMemList ** ppReadMemList;
+   
+   unsigned long flags;
+
+   if (IsDeviceValid( pDev ) == false)
+   {
+      DBG( "Invalid device!\n" );
+      return -ENXIO;
+   }
+
+   // Critical section
+   spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
+
+   // Find memory storage for this client ID
+   pClientMem = FindClientMem( pDev, clientID );
+   if (pClientMem == NULL)
+   {
+      DBG( "Could not find matching client ID 0x%04X\n",
+           clientID );
+           
+      // End critical section
+      spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
+      return -ENXIO;
+   }
+   
+   ppReadMemList = &(pClientMem->mpList);
+   
+   // Does data already exist?
+   while (*ppReadMemList != NULL)
+   {
+      // Is this element our data?
+      if (transactionID == 0 
+      ||  transactionID == (*ppReadMemList)->mTransactionID)
+      {
+         // End critical section
+         spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
+
+         // Run our own callback
+         pCallback( pDev, clientID, pData );
+         
+         return 0;
+      }
+      
+      // Next
+      ppReadMemList = &(*ppReadMemList)->mpNext;
+   }
+
+   // Data not found, add ourself to list of waiters
+   if (AddToNotifyList( pDev,
+                        clientID,
+                        transactionID, 
+                        pCallback, 
+                        pData ) == false)
+   {
+      DBG( "Unable to register for notification\n" );
+   }
+
+   // End critical section
+   spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
+
+   // Success
+   return 0;
+}
+
+/*===========================================================================
+METHOD:
+   UpSem (Public Method)
+
+DESCRIPTION:
+   Notification function for synchronous read
+
+PARAMETERS:
+   pDev              [ I ] - Device specific memory
+   clientID          [ I ] - Requester's client ID
+   pData             [ I ] - Buffer that holds semaphore to be up()-ed
+
+RETURN VALUE:
+   None
+===========================================================================*/
+void UpSem( 
+   sGobiUSBNet * pDev,
+   u16             clientID,
+   void *          pData )
+{
+   DBG( "0x%04X\n", clientID );
+        
+   up( (struct semaphore *)pData );
+   return;
+}
+
+/*===========================================================================
+METHOD:
+   ReadSync (Public Method)
+
+DESCRIPTION:
+   Start synchronous read
+   NOTE: Reading client's data store, not device
+
+PARAMETERS:
+   pDev              [ I ] - Device specific memory
+   ppOutBuffer       [I/O] - On success, will be filled with a 
+                             pointer to read buffer
+   clientID          [ I ] - Requester's client ID
+   transactionID     [ I ] - Transaction ID or 0 for any
+
+RETURN VALUE:
+   int - size of data read for success
+         negative errno for failure
+===========================================================================*/
+int ReadSync(
+   sGobiUSBNet *    pDev,
+   void **            ppOutBuffer,
+   u16                clientID,
+   u16                transactionID )
+{
+   int result;
+   sClientMemList * pClientMem;
+   sNotifyList ** ppNotifyList, * pDelNotifyListEntry;
+   struct semaphore readSem;
+   void * pData;
+   unsigned long flags;
+   u16 dataSize;
+
+   if (IsDeviceValid( pDev ) == false)
+   {
+      DBG( "Invalid device!\n" );
+      return -ENXIO;
+   }
+   
+   // Critical section
+   spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
+
+   // Find memory storage for this Client ID
+   pClientMem = FindClientMem( pDev, clientID );
+   if (pClientMem == NULL)
+   {
+      DBG( "Could not find matching client ID 0x%04X\n",
+           clientID );
+      
+      // End critical section
+      spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
+      return -ENXIO;
+   }
+   
+   // Note: in cases where read is interrupted, 
+   //    this will verify client is still valid
+   while (PopFromReadMemList( pDev,
+                              clientID,
+                              transactionID,
+                              &pData,
+                              &dataSize ) == false)
+   {
+      // Data does not yet exist, wait
+      sema_init( &readSem, 0 );
+
+      // Add ourself to list of waiters
+      if (AddToNotifyList( pDev, 
+                           clientID, 
+                           transactionID, 
+                           UpSem, 
+                           &readSem ) == false)
+      {
+         DBG( "unable to register for notification\n" );
+         spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
+         return -EFAULT;
+      }
+
+      // End critical section while we block
+      spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
+
+      // Wait for notification
+      result = down_interruptible( &readSem );
+      if (result != 0)
+      {
+         DBG( "Interrupted %d\n", result );
+
+         // readSem will fall out of scope, 
+         // remove from notify list so it's not referenced
+         spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
+         ppNotifyList = &(pClientMem->mpReadNotifyList);
+         pDelNotifyListEntry = NULL;
+
+         // Find and delete matching entry
+         while (*ppNotifyList != NULL)
+         {
+            if ((*ppNotifyList)->mpData == &readSem)
+            {
+               pDelNotifyListEntry = *ppNotifyList;
+               *ppNotifyList = (*ppNotifyList)->mpNext;
+               kfree( pDelNotifyListEntry );
+               break;
+            }
+
+            // Next
+            ppNotifyList = &(*ppNotifyList)->mpNext;
+         }
+
+         spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
+         return -EINTR;
+      }
+      
+      // Verify device is still valid
+      if (IsDeviceValid( pDev ) == false)
+      {
+         DBG( "Invalid device!\n" );
+         return -ENXIO;
+      }
+      
+      // Restart critical section and continue loop
+      spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
+   }
+   
+   // End Critical section
+   spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
+
+   // Success
+   *ppOutBuffer = pData;
+
+   return dataSize;
+}
+
+/*===========================================================================
+METHOD:
+   WriteSyncCallback (Public Method)
+
+DESCRIPTION:
+   Write callback
+
+PARAMETERS
+   pWriteURB       [ I ] - URB this callback is run for
+
+RETURN VALUE:
+   None
+===========================================================================*/
+void WriteSyncCallback( struct urb * pWriteURB )
+{
+   if (pWriteURB == NULL)
+   {
+      DBG( "null urb\n" );
+      return;
+   }
+
+   DBG( "Write status/size %d/%d\n", 
+        pWriteURB->status, 
+        pWriteURB->actual_length );
+
+   // Notify that write has completed by up()-ing semeaphore
+   up( (struct semaphore * )pWriteURB->context );
+   
+   return;
+}
+
+/*===========================================================================
+METHOD:
+   WriteSync (Public Method)
+
+DESCRIPTION:
+   Start synchronous write
+
+PARAMETERS:
+   pDev                 [ I ] - Device specific memory
+   pWriteBuffer         [ I ] - Data to be written
+   writeBufferSize      [ I ] - Size of data to be written
+   clientID             [ I ] - Client ID of requester
+
+RETURN VALUE:
+   int - write size (includes QMUX)
+         negative errno for failure
+===========================================================================*/
+int WriteSync(
+   sGobiUSBNet *          pDev,
+   char *                 pWriteBuffer,
+   int                    writeBufferSize,
+   u16                    clientID )
+{
+   int result;
+   struct semaphore writeSem;
+   struct urb * pWriteURB;
+   sURBSetupPacket writeSetup;
+   unsigned long flags;
+
+   if (IsDeviceValid( pDev ) == false)
+   {
+      DBG( "Invalid device!\n" );
+      return -ENXIO;
+   }
+
+   pWriteURB = usb_alloc_urb( 0, GFP_KERNEL );
+   if (pWriteURB == NULL)
+   {
+      DBG( "URB mem error\n" );
+      return -ENOMEM;
+   }
+
+   // Fill writeBuffer with QMUX
+   result = FillQMUX( clientID, pWriteBuffer, writeBufferSize );
+   if (result < 0)
+   {
+      usb_free_urb( pWriteURB );
+      return result;
+   }
+
+   // CDC Send Encapsulated Request packet
+   writeSetup.mRequestType = 0x21;
+   writeSetup.mRequestCode = 0;
+   writeSetup.mValue = 0;
+   writeSetup.mIndex = 0;
+   writeSetup.mLength = 0;
+   writeSetup.mLength = writeBufferSize;
+
+   // Create URB   
+   usb_fill_control_urb( pWriteURB,
+                         pDev->mpNetDev->udev,
+                         usb_sndctrlpipe( pDev->mpNetDev->udev, 0 ),
+                         (unsigned char *)&writeSetup,
+                         (void*)pWriteBuffer,
+                         writeBufferSize,
+                         NULL,
+                         pDev );
+
+   DBG( "Actual Write:\n" );
+   PrintHex( pWriteBuffer, writeBufferSize );
+
+   sema_init( &writeSem, 0 );
+   
+   pWriteURB->complete = WriteSyncCallback;
+   pWriteURB->context = &writeSem;
+   
+   // Wake device
+   result = usb_autopm_get_interface( pDev->mpIntf );
+   if (result < 0)
+   {
+      DBG( "unable to resume interface: %d\n", result );
+      
+      // Likely caused by device going from autosuspend -> full suspend
+      if (result == -EPERM)
+      {
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,33 ))
+         pDev->mpNetDev->udev->auto_pm = 0;
+#endif
+         GobiSuspend( pDev->mpIntf, PMSG_SUSPEND );
+      }
+
+      usb_free_urb( pWriteURB );
+
+      return result;
+   }
+
+   // Critical section
+   spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
+
+   if (AddToURBList( pDev, clientID, pWriteURB ) == false)
+   {
+      usb_free_urb( pWriteURB );
+
+      // End critical section
+      spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );   
+      usb_autopm_put_interface( pDev->mpIntf );
+      return -EINVAL;
+   }
+
+   result = usb_submit_urb( pWriteURB, GFP_KERNEL );
+   if (result < 0)
+   {
+      DBG( "submit URB error %d\n", result );
+      
+      // Get URB back so we can destroy it
+      if (PopFromURBList( pDev, clientID ) != pWriteURB)
+      {
+         // This shouldn't happen
+         DBG( "Didn't get write URB back\n" );
+      }
+
+      usb_free_urb( pWriteURB );
+
+      // End critical section
+      spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
+      usb_autopm_put_interface( pDev->mpIntf );
+      return result;
+   }
+   
+   // End critical section while we block
+   spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );   
+
+   // Wait for write to finish
+   if (interruptible != 0)
+   {
+      // Allow user interrupts
+      result = down_interruptible( &writeSem );
+   }
+   else
+   {
+      // Ignore user interrupts
+      result = 0;
+      down( &writeSem );
+   }
+
+   // Write is done, release device
+   usb_autopm_put_interface( pDev->mpIntf );
+
+   // Verify device is still valid
+   if (IsDeviceValid( pDev ) == false)
+   {
+      DBG( "Invalid device!\n" );
+
+      usb_free_urb( pWriteURB );
+      return -ENXIO;
+   }
+
+   // Restart critical section
+   spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
+
+   // Get URB back so we can destroy it
+   if (PopFromURBList( pDev, clientID ) != pWriteURB)
+   {
+      // This shouldn't happen
+      DBG( "Didn't get write URB back\n" );
+   
+      // End critical section
+      spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
+      usb_free_urb( pWriteURB );
+      return -EINVAL;
+   }
+
+   // End critical section
+   spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );   
+
+   if (result == 0)
+   {
+      // Write is finished
+      if (pWriteURB->status == 0)
+      {
+         // Return number of bytes that were supposed to have been written,
+         //   not size of QMI request
+         result = writeBufferSize;
+      }
+      else
+      {
+         DBG( "bad status = %d\n", pWriteURB->status );
+         
+         // Return error value
+         result = pWriteURB->status;
+      }
+   }
+   else
+   {
+      // We have been forcibly interrupted
+      DBG( "Interrupted %d !!!\n", result );
+      DBG( "Device may be in bad state and need reset !!!\n" );
+
+      // URB has not finished
+      usb_kill_urb( pWriteURB );
+   }
+
+   usb_free_urb( pWriteURB );
+
+   return result;
+}
+
+/*=========================================================================*/
+// Internal memory management functions
+/*=========================================================================*/
+
+/*===========================================================================
+METHOD:
+   GetClientID (Public Method)
+
+DESCRIPTION:
+   Request a QMI client for the input service type and initialize memory
+   structure
+
+PARAMETERS:
+   pDev           [ I ] - Device specific memory
+   serviceType    [ I ] - Desired QMI service type
+
+RETURN VALUE:
+   int - Client ID for success (positive)
+         Negative errno for error
+===========================================================================*/
+int GetClientID( 
+   sGobiUSBNet *    pDev,
+   u8                 serviceType )
+{
+   u16 clientID;
+   sClientMemList ** ppClientMem;
+   int result;
+   void * pWriteBuffer;
+   u16 writeBufferSize;
+   void * pReadBuffer;
+   u16 readBufferSize;
+   unsigned long flags;
+   u8 transactionID;
+   
+   if (IsDeviceValid( pDev ) == false)
+   {
+      DBG( "Invalid device!\n" );
+      return -ENXIO;
+   }
+
+   // Run QMI request to be asigned a Client ID
+   if (serviceType != 0)
+   {
+      writeBufferSize = QMICTLGetClientIDReqSize();
+      pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
+      if (pWriteBuffer == NULL)
+      {
+         return -ENOMEM;
+      }
+
+      transactionID = atomic_add_return( 1, &pDev->mQMIDev.mQMICTLTransactionID );
+      if (transactionID == 0)
+      {
+         atomic_add_return( 1, &pDev->mQMIDev.mQMICTLTransactionID );
+      }
+      result = QMICTLGetClientIDReq( pWriteBuffer, 
+                                     writeBufferSize,
+                                     transactionID,
+                                     serviceType );
+      if (result < 0)
+      {
+         kfree( pWriteBuffer );
+         return result;
+      }
+      
+      result = WriteSync( pDev,
+                          pWriteBuffer,
+                          writeBufferSize,
+                          QMICTL );
+      kfree( pWriteBuffer );
+
+      if (result < 0)
+      {
+         return result;
+      }
+
+      result = ReadSync( pDev,
+                         &pReadBuffer,
+                         QMICTL,
+                         transactionID );
+      if (result < 0)
+      {
+         DBG( "bad read data %d\n", result );
+         return result;
+      }
+      readBufferSize = result;
+
+      result = QMICTLGetClientIDResp( pReadBuffer,
+                                      readBufferSize,
+                                      &clientID );
+      kfree( pReadBuffer );
+
+      if (result < 0)
+      {
+         return result;
+      }
+   }
+   else
+   {
+      // QMI CTL will always have client ID 0
+      clientID = 0;
+   }
+
+   // Critical section
+   spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
+
+   // Verify client is not already allocated
+   if (FindClientMem( pDev, clientID ) != NULL)
+   {
+      DBG( "Client memory already exists\n" );
+
+      // End Critical section
+      spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
+      return -ETOOMANYREFS;
+   }
+
+   // Go to last entry in client mem list
+   ppClientMem = &pDev->mQMIDev.mpClientMemList;
+   while (*ppClientMem != NULL)
+   {
+      ppClientMem = &(*ppClientMem)->mpNext;
+   }
+   
+   // Create locations for read to place data into
+   *ppClientMem = kmalloc( sizeof( sClientMemList ), GFP_ATOMIC );
+   if (*ppClientMem == NULL)
+   {
+      DBG( "Error allocating read list\n" );
+
+      // End critical section
+      spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
+      return -ENOMEM;
+   }
+      
+   (*ppClientMem)->mClientID = clientID;
+   (*ppClientMem)->mpList = NULL;
+   (*ppClientMem)->mpReadNotifyList = NULL;
+   (*ppClientMem)->mpURBList = NULL;
+   (*ppClientMem)->mpNext = NULL;
+
+
+   // End Critical section
+   spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
+   
+   return clientID;
+}
+
+/*===========================================================================
+METHOD:
+   ReleaseClientID (Public Method)
+
+DESCRIPTION:
+   Release QMI client and free memory
+
+PARAMETERS:
+   pDev           [ I ] - Device specific memory
+   clientID       [ I ] - Requester's client ID
+
+RETURN VALUE:
+   None
+===========================================================================*/
+void ReleaseClientID(
+   sGobiUSBNet *    pDev,
+   u16                clientID )
+{
+   int result;
+   sClientMemList ** ppDelClientMem;
+   sClientMemList * pNextClientMem;
+   struct urb * pDelURB;
+   void * pDelData;
+   u16 dataSize;
+   void * pWriteBuffer;
+   u16 writeBufferSize;
+   void * pReadBuffer;
+   u16 readBufferSize;
+   unsigned long flags;
+   u8 transactionID;
+
+   // Is device is still valid?
+   if (IsDeviceValid( pDev ) == false)
+   {
+      DBG( "invalid device\n" );
+      return;
+   }
+   
+   DBG( "releasing 0x%04X\n", clientID );
+
+   // Run QMI ReleaseClientID if this isn't QMICTL   
+   if (clientID != QMICTL)
+   {
+      // Note: all errors are non fatal, as we always want to delete 
+      //    client memory in latter part of function
+      
+      writeBufferSize = QMICTLReleaseClientIDReqSize();
+      pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
+      if (pWriteBuffer == NULL)
+      {
+         DBG( "memory error\n" );
+      }
+      else
+      {
+         transactionID = atomic_add_return( 1, &pDev->mQMIDev.mQMICTLTransactionID );
+         if (transactionID == 0)
+         {
+            transactionID = atomic_add_return( 1, &pDev->mQMIDev.mQMICTLTransactionID );
+         }
+         result = QMICTLReleaseClientIDReq( pWriteBuffer, 
+                                            writeBufferSize,
+                                            transactionID,
+                                            clientID );
+         if (result < 0)
+         {
+            kfree( pWriteBuffer );
+            DBG( "error %d filling req buffer\n", result );
+         }
+         else
+         {
+            result = WriteSync( pDev,
+                                pWriteBuffer,
+                                writeBufferSize,
+                                QMICTL );
+            kfree( pWriteBuffer );
+
+            if (result < 0)
+            {
+               DBG( "bad write status %d\n", result );
+            }
+            else
+            {
+               result = ReadSync( pDev,
+                                  &pReadBuffer,
+                                  QMICTL,
+                                  transactionID );
+               if (result < 0)
+               {
+                  DBG( "bad read status %d\n", result );
+               }
+               else
+               {
+                  readBufferSize = result;
+
+                  result = QMICTLReleaseClientIDResp( pReadBuffer,
+                                                      readBufferSize );
+                  kfree( pReadBuffer );
+
+                  if (result < 0)
+                  {
+                     DBG( "error %d parsing response\n", result );
+                  }
+               }
+            }
+         }
+      }
+   }
+
+   // Cleaning up client memory
+   
+   // Critical section
+   spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
+
+   // Can't use FindClientMem, I need to keep pointer of previous
+   ppDelClientMem = &pDev->mQMIDev.mpClientMemList;
+   while (*ppDelClientMem != NULL)
+   {
+      if ((*ppDelClientMem)->mClientID == clientID)
+      {
+         pNextClientMem = (*ppDelClientMem)->mpNext;
+
+         // Notify all clients
+         while (NotifyAndPopNotifyList( pDev,
+                                        clientID,
+                                        0 ) == true );         
+
+         // Kill and free all URB's
+         pDelURB = PopFromURBList( pDev, clientID );
+         while (pDelURB != NULL)
+         {
+            usb_kill_urb( pDelURB );
+            usb_free_urb( pDelURB );
+            pDelURB = PopFromURBList( pDev, clientID );
+         }
+
+         // Free any unread data
+         while (PopFromReadMemList( pDev, 
+                                    clientID,
+                                    0,
+                                    &pDelData,
+                                    &dataSize ) == true )
+         {
+            kfree( pDelData );
+         }
+
+         // Delete client Mem
+         kfree( *ppDelClientMem );
+
+         // Overwrite the pointer that was to this client mem
+         *ppDelClientMem = pNextClientMem;
+      }
+      else
+      {
+         // I now point to (a pointer of ((the node I was at)'s mpNext))
+         ppDelClientMem = &(*ppDelClientMem)->mpNext;
+      }
+   }
+   
+   // End Critical section
+   spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
+
+   return;
+}
+
+/*===========================================================================
+METHOD:
+   FindClientMem (Public Method)
+
+DESCRIPTION:
+   Find this client's memory
+
+   Caller MUST have lock on mClientMemLock
+
+PARAMETERS:
+   pDev           [ I ] - Device specific memory
+   clientID       [ I ] - Requester's client ID
+
+RETURN VALUE:
+   sClientMemList - Pointer to requested sClientMemList for success
+                    NULL for error
+===========================================================================*/
+sClientMemList * FindClientMem( 
+   sGobiUSBNet *      pDev,
+   u16              clientID )
+{
+   sClientMemList * pClientMem;
+   
+   if (IsDeviceValid( pDev ) == false)
+   {
+      DBG( "Invalid device\n" );
+      return NULL;
+   }
+   
+#ifdef CONFIG_SMP
+   // Verify Lock
+   if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0)
+   {
+      DBG( "unlocked\n" );
+      BUG();
+   }
+#endif
+   
+   pClientMem = pDev->mQMIDev.mpClientMemList;
+   while (pClientMem != NULL)
+   {
+      if (pClientMem->mClientID == clientID)
+      {
+         // Success
+         //DBG( "Found client mem %p\n", pClientMem );
+         return pClientMem;
+      }
+      
+      pClientMem = pClientMem->mpNext;
+   }
+
+   DBG( "Could not find client mem 0x%04X\n", clientID );
+   return NULL;
+}
+
+/*===========================================================================
+METHOD:
+   AddToReadMemList (Public Method)
+
+DESCRIPTION:
+   Add Data to this client's ReadMem list
+   
+   Caller MUST have lock on mClientMemLock
+
+PARAMETERS:
+   pDev           [ I ] - Device specific memory
+   clientID       [ I ] - Requester's client ID
+   transactionID  [ I ] - Transaction ID or 0 for any
+   pData          [ I ] - Data to add
+   dataSize       [ I ] - Size of data to add
+
+RETURN VALUE:
+   bool
+===========================================================================*/
+bool AddToReadMemList( 
+   sGobiUSBNet *      pDev,
+   u16              clientID,
+   u16              transactionID,
+   void *           pData,
+   u16              dataSize )
+{
+   sClientMemList * pClientMem;
+   sReadMemList ** ppThisReadMemList;
+
+#ifdef CONFIG_SMP
+   // Verify Lock
+   if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0)
+   {
+      DBG( "unlocked\n" );
+      BUG();
+   }
+#endif
+
+   // Get this client's memory location
+   pClientMem = FindClientMem( pDev, clientID );
+   if (pClientMem == NULL)
+   {
+      DBG( "Could not find this client's memory 0x%04X\n",
+           clientID );
+
+      return false;
+   }
+
+   // Go to last ReadMemList entry
+   ppThisReadMemList = &pClientMem->mpList;
+   while (*ppThisReadMemList != NULL)
+   {
+      ppThisReadMemList = &(*ppThisReadMemList)->mpNext;
+   }
+   
+   *ppThisReadMemList = kmalloc( sizeof( sReadMemList ), GFP_ATOMIC );
+   if (*ppThisReadMemList == NULL)
+   {
+      DBG( "Mem error\n" );
+
+      return false;
+   }   
+   
+   (*ppThisReadMemList)->mpNext = NULL;
+   (*ppThisReadMemList)->mpData = pData;
+   (*ppThisReadMemList)->mDataSize = dataSize;
+   (*ppThisReadMemList)->mTransactionID = transactionID;
+   
+   return true;
+}
+
+/*===========================================================================
+METHOD:
+   PopFromReadMemList (Public Method)
+
+DESCRIPTION:
+   Remove data from this client's ReadMem list if it matches 
+   the specified transaction ID.
+   
+   Caller MUST have lock on mClientMemLock
+
+PARAMETERS:
+   pDev              [ I ] - Device specific memory
+   clientID          [ I ] - Requester's client ID
+   transactionID     [ I ] - Transaction ID or 0 for any
+   ppData            [I/O] - On success, will be filled with a 
+                             pointer to read buffer
+   pDataSize         [I/O] - On succces, will be filled with the 
+                             read buffer's size
+
+RETURN VALUE:
+   bool
+===========================================================================*/
+bool PopFromReadMemList( 
+   sGobiUSBNet *      pDev,
+   u16                  clientID,
+   u16                  transactionID,
+   void **              ppData,
+   u16 *                pDataSize )
+{
+   sClientMemList * pClientMem;
+   sReadMemList * pDelReadMemList, ** ppReadMemList;
+
+#ifdef CONFIG_SMP
+   // Verify Lock
+   if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0)
+   {
+      DBG( "unlocked\n" );
+      BUG();
+   }
+#endif
+
+   // Get this client's memory location
+   pClientMem = FindClientMem( pDev, clientID );
+   if (pClientMem == NULL)
+   {
+      DBG( "Could not find this client's memory 0x%04X\n",
+           clientID );
+
+      return false;
+   }
+   
+   ppReadMemList = &(pClientMem->mpList);
+   pDelReadMemList = NULL;
+   
+   // Find first message that matches this transaction ID
+   while (*ppReadMemList != NULL)
+   {
+      // Do we care about transaction ID?
+      if (transactionID == 0
+      ||  transactionID == (*ppReadMemList)->mTransactionID )
+      {
+         pDelReadMemList = *ppReadMemList;
+         break;
+      }
+      
+      DBG( "skipping 0x%04X data TID = %x\n",
+           clientID, 
+           (*ppReadMemList)->mTransactionID );
+      
+      // Next
+      ppReadMemList = &(*ppReadMemList)->mpNext;
+   }
+   
+   if (pDelReadMemList != NULL)
+   {
+      *ppReadMemList = (*ppReadMemList)->mpNext;
+      
+      // Copy to output
+      *ppData = pDelReadMemList->mpData;
+      *pDataSize = pDelReadMemList->mDataSize;
+      
+      // Free memory
+      kfree( pDelReadMemList );
+      
+      return true;
+   }
+   else
+   {
+      DBG( "No read memory to pop, Client 0x%04X, TID = %x\n", 
+           clientID, 
+           transactionID );
+      return false;
+   }
+}
+
+/*===========================================================================
+METHOD:
+   AddToNotifyList (Public Method)
+
+DESCRIPTION:
+   Add Notify entry to this client's notify List
+   
+   Caller MUST have lock on mClientMemLock
+
+PARAMETERS:
+   pDev              [ I ] - Device specific memory
+   clientID          [ I ] - Requester's client ID
+   transactionID     [ I ] - Transaction ID or 0 for any
+   pNotifyFunct      [ I ] - Callback function to be run when data is available
+   pData             [ I ] - Data buffer that willl be passed (unmodified) 
+                             to callback
+
+RETURN VALUE:
+   bool
+===========================================================================*/
+bool AddToNotifyList( 
+   sGobiUSBNet *      pDev,
+   u16                  clientID,
+   u16                  transactionID,
+   void                 (* pNotifyFunct)(sGobiUSBNet *, u16, void *),
+   void *               pData )
+{
+   sClientMemList * pClientMem;
+   sNotifyList ** ppThisNotifyList;
+
+#ifdef CONFIG_SMP
+   // Verify Lock
+   if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0)
+   {
+      DBG( "unlocked\n" );
+      BUG();
+   }
+#endif
+
+   // Get this client's memory location
+   pClientMem = FindClientMem( pDev, clientID );
+   if (pClientMem == NULL)
+   {
+      DBG( "Could not find this client's memory 0x%04X\n", clientID );
+      return false;
+   }
+
+   // Go to last URBList entry
+   ppThisNotifyList = &pClientMem->mpReadNotifyList;
+   while (*ppThisNotifyList != NULL)
+   {
+      ppThisNotifyList = &(*ppThisNotifyList)->mpNext;
+   }
+   
+   *ppThisNotifyList = kmalloc( sizeof( sNotifyList ), GFP_ATOMIC );
+   if (*ppThisNotifyList == NULL)
+   {
+      DBG( "Mem error\n" );
+      return false;
+   }   
+   
+   (*ppThisNotifyList)->mpNext = NULL;
+   (*ppThisNotifyList)->mpNotifyFunct = pNotifyFunct;
+   (*ppThisNotifyList)->mpData = pData;
+   (*ppThisNotifyList)->mTransactionID = transactionID;
+   
+   return true;
+}
+
+/*===========================================================================
+METHOD:
+   NotifyAndPopNotifyList (Public Method)
+
+DESCRIPTION:
+   Remove first Notify entry from this client's notify list 
+   and Run function
+   
+   Caller MUST have lock on mClientMemLock
+
+PARAMETERS:
+   pDev              [ I ] - Device specific memory
+   clientID          [ I ] - Requester's client ID
+   transactionID     [ I ] - Transaction ID or 0 for any
+
+RETURN VALUE:
+   bool
+===========================================================================*/
+bool NotifyAndPopNotifyList( 
+   sGobiUSBNet *      pDev,
+   u16                  clientID,
+   u16                  transactionID )
+{
+   sClientMemList * pClientMem;
+   sNotifyList * pDelNotifyList, ** ppNotifyList;
+
+#ifdef CONFIG_SMP
+   // Verify Lock
+   if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0)
+   {
+      DBG( "unlocked\n" );
+      BUG();
+   }
+#endif
+
+   // Get this client's memory location
+   pClientMem = FindClientMem( pDev, clientID );
+   if (pClientMem == NULL)
+   {
+      DBG( "Could not find this client's memory 0x%04X\n", clientID );
+      return false;
+   }
+
+   ppNotifyList = &(pClientMem->mpReadNotifyList);
+   pDelNotifyList = NULL;
+
+   // Remove from list
+   while (*ppNotifyList != NULL)
+   {
+      // Do we care about transaction ID?
+      if (transactionID == 0
+      ||  (*ppNotifyList)->mTransactionID == 0
+      ||  transactionID == (*ppNotifyList)->mTransactionID)
+      {
+         pDelNotifyList = *ppNotifyList;
+         break;
+      }
+      
+      DBG( "skipping data TID = %x\n", (*ppNotifyList)->mTransactionID );
+      
+      // next
+      ppNotifyList = &(*ppNotifyList)->mpNext;
+   }
+   
+   if (pDelNotifyList != NULL)
+   {
+      // Remove element
+      *ppNotifyList = (*ppNotifyList)->mpNext;
+      
+      // Run notification function
+      if (pDelNotifyList->mpNotifyFunct != NULL)
+      {
+         // Unlock for callback
+         spin_unlock( &pDev->mQMIDev.mClientMemLock );
+      
+         pDelNotifyList->mpNotifyFunct( pDev,
+                                        clientID,
+                                        pDelNotifyList->mpData );
+                                        
+         // Restore lock
+         spin_lock( &pDev->mQMIDev.mClientMemLock );
+      }
+      
+      // Delete memory
+      kfree( pDelNotifyList );
+
+      return true;
+   }
+   else
+   {
+      DBG( "no one to notify for TID %x\n", transactionID );
+      
+      return false;
+   }
+}
+
+/*===========================================================================
+METHOD:
+   AddToURBList (Public Method)
+
+DESCRIPTION:
+   Add URB to this client's URB list
+   
+   Caller MUST have lock on mClientMemLock
+
+PARAMETERS:
+   pDev              [ I ] - Device specific memory
+   clientID          [ I ] - Requester's client ID
+   pURB              [ I ] - URB to be added
+
+RETURN VALUE:
+   bool
+===========================================================================*/
+bool AddToURBList( 
+   sGobiUSBNet *      pDev,
+   u16                  clientID,
+   struct urb *     pURB )
+{
+   sClientMemList * pClientMem;
+   sURBList ** ppThisURBList;
+
+#ifdef CONFIG_SMP
+   // Verify Lock
+   if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0)
+   {
+      DBG( "unlocked\n" );
+      BUG();
+   }
+#endif
+
+   // Get this client's memory location
+   pClientMem = FindClientMem( pDev, clientID );
+   if (pClientMem == NULL)
+   {
+      DBG( "Could not find this client's memory 0x%04X\n", clientID );
+      return false;
+   }
+
+   // Go to last URBList entry
+   ppThisURBList = &pClientMem->mpURBList;
+   while (*ppThisURBList != NULL)
+   {
+      ppThisURBList = &(*ppThisURBList)->mpNext;
+   }
+   
+   *ppThisURBList = kmalloc( sizeof( sURBList ), GFP_ATOMIC );
+   if (*ppThisURBList == NULL)
+   {
+      DBG( "Mem error\n" );
+      return false;
+   }   
+   
+   (*ppThisURBList)->mpNext = NULL;
+   (*ppThisURBList)->mpURB = pURB;
+   
+   return true;
+}
+
+/*===========================================================================
+METHOD:
+   PopFromURBList (Public Method)
+
+DESCRIPTION:
+   Remove URB from this client's URB list
+   
+   Caller MUST have lock on mClientMemLock
+
+PARAMETERS:
+   pDev           [ I ] - Device specific memory
+   clientID       [ I ] - Requester's client ID
+
+RETURN VALUE:
+   struct urb - Pointer to requested client's URB
+                NULL for error
+===========================================================================*/
+struct urb * PopFromURBList( 
+   sGobiUSBNet *      pDev,
+   u16                  clientID )
+{
+   sClientMemList * pClientMem;
+   sURBList * pDelURBList;
+   struct urb * pURB;
+
+#ifdef CONFIG_SMP
+   // Verify Lock
+   if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0)
+   {
+      DBG( "unlocked\n" );
+      BUG();
+   }
+#endif
+
+   // Get this client's memory location
+   pClientMem = FindClientMem( pDev, clientID );
+   if (pClientMem == NULL)
+   {
+      DBG( "Could not find this client's memory 0x%04X\n", clientID );
+      return NULL;
+   }
+
+   // Remove from list
+   if (pClientMem->mpURBList != NULL)
+   {
+      pDelURBList = pClientMem->mpURBList;
+      pClientMem->mpURBList = pClientMem->mpURBList->mpNext;
+      
+      // Copy to output
+      pURB = pDelURBList->mpURB;
+      
+      // Delete memory
+      kfree( pDelURBList );
+
+      return pURB;
+   }
+   else
+   {
+      DBG( "No URB's to pop\n" );
+      
+      return NULL;
+   }
+}
+
+/*=========================================================================*/
+// Userspace wrappers
+/*=========================================================================*/
+
+/*===========================================================================
+METHOD:
+   UserspaceOpen (Public Method)
+
+DESCRIPTION:
+   Userspace open
+      IOCTL must be called before reads or writes
+
+PARAMETERS
+   pInode       [ I ] - kernel file descriptor
+   pFilp        [ I ] - userspace file descriptor
+
+RETURN VALUE:
+   int - 0 for success
+         Negative errno for failure
+===========================================================================*/
+int UserspaceOpen( 
+   struct inode *         pInode, 
+   struct file *          pFilp )
+{
+   sQMIFilpStorage * pFilpData;
+   
+   // Optain device pointer from pInode
+   sQMIDev * pQMIDev = container_of( pInode->i_cdev,
+                                     sQMIDev,
+                                     mCdev );
+   sGobiUSBNet * pDev = container_of( pQMIDev,
+                                    sGobiUSBNet,
+                                    mQMIDev );                                    
+
+   if (IsDeviceValid( pDev ) == false)
+   {
+      DBG( "Invalid device\n" );
+      return -ENXIO;
+   }
+
+   // Setup data in pFilp->private_data
+   pFilp->private_data = kmalloc( sizeof( sQMIFilpStorage ), GFP_KERNEL );
+   if (pFilp->private_data == NULL)
+   {
+      DBG( "Mem error\n" );
+      return -ENOMEM;
+   }
+   
+   pFilpData = (sQMIFilpStorage *)pFilp->private_data;
+   pFilpData->mClientID = (u16)-1;
+   pFilpData->mpDev = pDev;
+   
+   return 0;
+}
+
+/*===========================================================================
+METHOD:
+   UserspaceIOCTL (Public Method)
+
+DESCRIPTION:
+   Userspace IOCTL functions
+
+PARAMETERS
+   pUnusedInode [ I ] - (unused) kernel file descriptor
+   pFilp        [ I ] - userspace file descriptor
+   cmd          [ I ] - IOCTL command
+   arg          [ I ] - IOCTL argument
+
+RETURN VALUE:
+   int - 0 for success
+         Negative errno for failure
+===========================================================================*/
+int UserspaceIOCTL( 
+   struct inode *    pUnusedInode, 
+   struct file *     pFilp,
+   unsigned int      cmd, 
+   unsigned long     arg )
+{
+   int result;
+   u32 devVIDPID;
+   
+   sQMIFilpStorage * pFilpData = (sQMIFilpStorage *)pFilp->private_data;
+
+   if (pFilpData == NULL)
+   {
+      DBG( "Bad file data\n" );
+      return -EBADF;
+   }
+   
+   if (IsDeviceValid( pFilpData->mpDev ) == false)
+   {
+      DBG( "Invalid device! Updating f_ops\n" );
+      pFilp->f_op = pFilp->f_dentry->d_inode->i_fop;
+      return -ENXIO;
+   }
+
+   switch (cmd)
+   {
+      case IOCTL_QMI_GET_SERVICE_FILE:
+      
+         DBG( "Setting up QMI for service %lu\n", arg );
+         if ((u8)arg == 0)
+         {
+            DBG( "Cannot use QMICTL from userspace\n" );
+            return -EINVAL;
+         }
+
+         // Connection is already setup
+         if (pFilpData->mClientID != (u16)-1)
+         {
+            DBG( "Close the current connection before opening a new one\n" );
+            return -EBADR;
+         }
+         
+         result = GetClientID( pFilpData->mpDev, (u8)arg );
+         if (result < 0)
+         {
+            return result;
+         }
+         pFilpData->mClientID = result;
+
+         return 0;
+         break;
+
+
+      case IOCTL_QMI_GET_DEVICE_VIDPID:
+         if (arg == 0)
+         {
+            DBG( "Bad VIDPID buffer\n" );
+            return -EINVAL;
+         }
+         
+         // Extra verification
+         if (pFilpData->mpDev->mpNetDev == 0)
+         {
+            DBG( "Bad mpNetDev\n" );
+            return -ENOMEM;
+         }
+         if (pFilpData->mpDev->mpNetDev->udev == 0)
+         {
+            DBG( "Bad udev\n" );
+            return -ENOMEM;
+         }
+
+         devVIDPID = ((le16_to_cpu( pFilpData->mpDev->mpNetDev->udev->descriptor.idVendor ) << 16)
+                     + le16_to_cpu( pFilpData->mpDev->mpNetDev->udev->descriptor.idProduct ) );
+
+         result = copy_to_user( (unsigned int *)arg, &devVIDPID, 4 );
+         if (result != 0)
+         {
+            DBG( "Copy to userspace failure %d\n", result );
+         }
+
+         return result;
+                 
+         break;
+
+      case IOCTL_QMI_GET_DEVICE_MEID:
+         if (arg == 0)
+         {
+            DBG( "Bad MEID buffer\n" );
+            return -EINVAL;
+         }
+         
+         result = copy_to_user( (unsigned int *)arg, &pFilpData->mpDev->mMEID[0], 14 );
+         if (result != 0)
+         {
+            DBG( "Copy to userspace failure %d\n", result );
+         }
+
+         return result;
+                 
+         break;
+         
+      default:
+         return -EBADRQC;       
+   }
+}
+
+/*===========================================================================
+METHOD:
+   UserspaceClose (Public Method)
+
+DESCRIPTION:
+   Userspace close
+      Release client ID and free memory
+
+PARAMETERS
+   pFilp           [ I ] - userspace file descriptor
+   unusedFileTable [ I ] - (unused) file table
+
+RETURN VALUE:
+   int - 0 for success
+         Negative errno for failure
+===========================================================================*/
+int UserspaceClose(
+   struct file *       pFilp,
+   fl_owner_t          unusedFileTable )
+{
+   sQMIFilpStorage * pFilpData = (sQMIFilpStorage *)pFilp->private_data;
+   struct task_struct * pEachTask;
+   struct fdtable * pFDT;
+   int count = 0;
+   int used = 0;
+   unsigned long flags;
+
+   if (pFilpData == NULL)
+   {
+      DBG( "bad file data\n" );
+      return -EBADF;
+   }
+
+   // Fallthough.  If f_count == 1 no need to do more checks
+   if (atomic_read( &pFilp->f_count ) != 1)
+   {
+      rcu_read_lock();
+      for_each_process( pEachTask )
+      {
+         if (pEachTask == NULL || pEachTask->files == NULL)
+         {
+            // Some tasks may not have files (e.g. Xsession)
+            continue;
+         }
+         spin_lock_irqsave( &pEachTask->files->file_lock, flags );
+         pFDT = files_fdtable( pEachTask->files );
+         for (count = 0; count < pFDT->max_fds; count++)
+         {
+            // Before this function was called, this file was removed
+            // from our task's file table so if we find it in a file
+            // table then it is being used by another task
+            if (pFDT->fd[count] == pFilp)
+            {
+               used++;
+               break;
+            }
+         }
+         spin_unlock_irqrestore( &pEachTask->files->file_lock, flags );
+      }
+      rcu_read_unlock();
+      
+      if (used > 0)
+      {
+         DBG( "not closing, as this FD is open by %d other process\n", used );
+         return 0;
+      }
+   }
+
+   if (IsDeviceValid( pFilpData->mpDev ) == false)
+   {
+      DBG( "Invalid device! Updating f_ops\n" );
+      pFilp->f_op = pFilp->f_dentry->d_inode->i_fop;
+      return -ENXIO;
+   }
+   
+   DBG( "0x%04X\n", pFilpData->mClientID );
+   
+   // Disable pFilpData so they can't keep sending read or write 
+   //    should this function hang
+   // Note: memory pointer is still saved in pFilpData to be deleted later
+   pFilp->private_data = NULL;
+
+   if (pFilpData->mClientID != (u16)-1)
+   {
+      ReleaseClientID( pFilpData->mpDev,
+                       pFilpData->mClientID );
+   }
+      
+   kfree( pFilpData );
+   return 0;
+}
+
+/*===========================================================================
+METHOD:
+   UserspaceRead (Public Method)
+
+DESCRIPTION:
+   Userspace read (synchronous)
+
+PARAMETERS
+   pFilp           [ I ] - userspace file descriptor
+   pBuf            [ I ] - read buffer
+   size            [ I ] - size of read buffer
+   pUnusedFpos     [ I ] - (unused) file position
+
+RETURN VALUE:
+   ssize_t - Number of bytes read for success
+             Negative errno for failure
+===========================================================================*/
+ssize_t UserspaceRead( 
+   struct file *          pFilp,
+   char __user *          pBuf, 
+   size_t                 size,
+   loff_t *               pUnusedFpos )
+{
+   int result;
+   void * pReadData = NULL;
+   void * pSmallReadData;
+   sQMIFilpStorage * pFilpData = (sQMIFilpStorage *)pFilp->private_data;
+
+   if (pFilpData == NULL)
+   {
+      DBG( "Bad file data\n" );
+      return -EBADF;
+   }
+
+   if (IsDeviceValid( pFilpData->mpDev ) == false)
+   {
+      DBG( "Invalid device! Updating f_ops\n" );
+      pFilp->f_op = pFilp->f_dentry->d_inode->i_fop;
+      return -ENXIO;
+   }
+   
+   if (pFilpData->mClientID == (u16)-1)
+   {
+      DBG( "Client ID must be set before reading 0x%04X\n",
+           pFilpData->mClientID );
+      return -EBADR;
+   }
+   
+   // Perform synchronous read
+   result = ReadSync( pFilpData->mpDev,
+                      &pReadData,
+                      pFilpData->mClientID,
+                      0 );
+   if (result <= 0)
+   {
+      return result;
+   }
+   
+   // Discard QMUX header
+   result -= QMUXHeaderSize();
+   pSmallReadData = pReadData + QMUXHeaderSize();
+
+   if (result > size)
+   {
+      DBG( "Read data is too large for amount user has requested\n" );
+      kfree( pReadData );
+      return -EOVERFLOW;
+   }
+
+   if (copy_to_user( pBuf, pSmallReadData, result ) != 0)
+   {
+      DBG( "Error copying read data to user\n" );
+      result = -EFAULT;
+   }
+   
+   // Reader is responsible for freeing read buffer
+   kfree( pReadData );
+   
+   return result;
+}
+
+/*===========================================================================
+METHOD:
+   UserspaceWrite (Public Method)
+
+DESCRIPTION:
+   Userspace write (synchronous)
+
+PARAMETERS
+   pFilp           [ I ] - userspace file descriptor
+   pBuf            [ I ] - write buffer
+   size            [ I ] - size of write buffer
+   pUnusedFpos     [ I ] - (unused) file position
+
+RETURN VALUE:
+   ssize_t - Number of bytes read for success
+             Negative errno for failure
+===========================================================================*/
+ssize_t UserspaceWrite(
+   struct file *        pFilp, 
+   const char __user *  pBuf, 
+   size_t               size,
+   loff_t *             pUnusedFpos )
+{
+   int status;
+   void * pWriteBuffer;
+   sQMIFilpStorage * pFilpData = (sQMIFilpStorage *)pFilp->private_data;
+
+   if (pFilpData == NULL)
+   {
+      DBG( "Bad file data\n" );
+      return -EBADF;
+   }
+
+   if (IsDeviceValid( pFilpData->mpDev ) == false)
+   {
+      DBG( "Invalid device! Updating f_ops\n" );
+      pFilp->f_op = pFilp->f_dentry->d_inode->i_fop;
+      return -ENXIO;
+   }
+
+   if (pFilpData->mClientID == (u16)-1)
+   {
+      DBG( "Client ID must be set before writing 0x%04X\n",
+           pFilpData->mClientID );
+      return -EBADR;
+   }
+   
+   // Copy data from user to kernel space
+   pWriteBuffer = kmalloc( size + QMUXHeaderSize(), GFP_KERNEL );
+   if (pWriteBuffer == NULL)
+   {
+      return -ENOMEM;
+   }
+   status = copy_from_user( pWriteBuffer + QMUXHeaderSize(), pBuf, size );
+   if (status != 0)
+   {
+      DBG( "Unable to copy data from userspace %d\n", status );
+      kfree( pWriteBuffer );
+      return status;
+   }
+
+   status = WriteSync( pFilpData->mpDev,
+                       pWriteBuffer, 
+                       size + QMUXHeaderSize(),
+                       pFilpData->mClientID );
+
+   kfree( pWriteBuffer );
+   
+   // On success, return requested size, not full QMI reqest size
+   if (status == size + QMUXHeaderSize())
+   {
+      return size;
+   }
+   else
+   {
+      return status;
+   }
+}
+
+/*=========================================================================*/
+// Initializer and destructor
+/*=========================================================================*/
+
+/*===========================================================================
+METHOD:
+   RegisterQMIDevice (Public Method)
+
+DESCRIPTION:
+   QMI Device initialization function
+
+PARAMETERS:
+   pDev     [ I ] - Device specific memory
+   
+RETURN VALUE:
+   int - 0 for success
+         Negative errno for failure
+===========================================================================*/
+int RegisterQMIDevice( sGobiUSBNet * pDev )
+{
+   int result;
+   int GobiQMIIndex = 0;
+   dev_t devno; 
+   char * pDevName;
+
+   if (pDev->mQMIDev.mbCdevIsInitialized == true)
+   {
+      // Should never happen, but always better to check
+      dbg( "device already exists\n" );
+      return -EEXIST;
+   }
+ 
+   pDev->mbQMIValid = true;
+
+   // Set up for QMICTL
+   //    (does not send QMI message, just sets up memory)
+   result = GetClientID( pDev, QMICTL );
+   if (result != 0)
+   {
+      pDev->mbQMIValid = false;
+      return result;
+   }
+   atomic_set( &pDev->mQMIDev.mQMICTLTransactionID, 1 );
+
+   // Start Async reading
+   result = StartRead( pDev );
+   if (result != 0)
+   {
+      pDev->mbQMIValid = false;
+      return result;
+   }
+   
+   // Device is not ready for QMI connections right away
+   //   Wait up to 30 seconds before failing
+   if (QMIReady( pDev, 30000 ) == false)
+   {
+      DBG( "Device unresponsive to QMI\n" );
+      return -ETIMEDOUT;
+   }
+
+   // Setup WDS callback
+   result = SetupQMIWDSCallback( pDev );
+   if (result != 0)
+   {
+      return result;
+   }
+
+   // Fill MEID for device
+   result = QMIDMSGetMEID( pDev );
+   if (result != 0)
+   {
+      return result;
+   }
+
+   // allocate and fill devno with numbers
+   result = alloc_chrdev_region( &devno, 0, 1, "qcqmi" );
+   if (result < 0)
+   {
+      return result;
+   }
+
+   // Create cdev
+   cdev_init( &pDev->mQMIDev.mCdev, &UserspaceQMIFops );
+   pDev->mQMIDev.mCdev.owner = THIS_MODULE;
+   pDev->mQMIDev.mCdev.ops = &UserspaceQMIFops;
+   pDev->mQMIDev.mbCdevIsInitialized = true;
+
+   result = cdev_add( &pDev->mQMIDev.mCdev, devno, 1 );
+   if (result != 0)
+   {
+      DBG( "error adding cdev\n" );
+      return result;
+   }
+
+   // Match interface number (usb#)
+   pDevName = strstr( pDev->mpNetDev->net->name, "usb" );
+   if (pDevName == NULL)
+   {
+      DBG( "Bad net name: %s\n", pDev->mpNetDev->net->name );
+      return -ENXIO;
+   }
+   pDevName += strlen( "usb" );
+   GobiQMIIndex = simple_strtoul( pDevName, NULL, 10 );
+   if (GobiQMIIndex < 0)
+   {
+      DBG( "Bad minor number\n" );
+      return -ENXIO;
+   }
+
+   // Always print this output
+   printk( KERN_INFO "creating qcqmi%d\n",
+           GobiQMIIndex );
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 2,6,27 ))
+   // kernel 2.6.27 added a new fourth parameter to device_create
+   //    void * drvdata : the data to be added to the device for callbacks
+   device_create( pDev->mQMIDev.mpDevClass,
+                  &pDev->mpIntf->dev, 
+                  devno,
+                  NULL,
+                  "qcqmi%d", 
+                  GobiQMIIndex );
+#else
+   device_create( pDev->mQMIDev.mpDevClass,
+                  &pDev->mpIntf->dev, 
+                  devno,
+                  "qcqmi%d", 
+                  GobiQMIIndex );
+#endif
+   
+   pDev->mQMIDev.mDevNum = devno;
+   
+   // Success
+   return 0;
+}
+
+/*===========================================================================
+METHOD:
+   DeregisterQMIDevice (Public Method)
+
+DESCRIPTION:
+   QMI Device cleanup function
+   
+   NOTE: When this function is run the device is no longer valid
+
+PARAMETERS:
+   pDev     [ I ] - Device specific memory
+
+RETURN VALUE:
+   None
+===========================================================================*/
+void DeregisterQMIDevice( sGobiUSBNet * pDev )
+{
+   struct inode * pOpenInode;
+   struct list_head * pInodeList;
+   struct task_struct * pEachTask;
+   struct fdtable * pFDT;
+   struct file * pFilp;
+   unsigned long flags;
+   int count = 0;
+   int tries;
+
+   // Should never happen, but check anyway
+   if (IsDeviceValid( pDev ) == false)
+   {
+      DBG( "wrong device\n" );
+      return;
+   }
+
+   // Release all clients
+   while (pDev->mQMIDev.mpClientMemList != NULL)
+   {
+      DBG( "release 0x%04X\n", pDev->mQMIDev.mpClientMemList->mClientID );
+   
+      ReleaseClientID( pDev,
+                       pDev->mQMIDev.mpClientMemList->mClientID );
+      // NOTE: pDev->mQMIDev.mpClientMemList will 
+      //       be updated in ReleaseClientID()
+   }
+
+   // Stop all reads
+   KillRead( pDev );
+
+   pDev->mbQMIValid = false;
+
+   if (pDev->mQMIDev.mbCdevIsInitialized == false)
+   {
+      return;
+   }
+
+   // Find each open file handle, and manually close it
+   
+   // Generally there will only be only one inode, but more are possible
+   list_for_each( pInodeList, &pDev->mQMIDev.mCdev.list )
+   {
+      // Get the inode
+      pOpenInode = container_of( pInodeList, struct inode, i_devices );
+      if (pOpenInode != NULL && (IS_ERR( pOpenInode ) == false))
+      {
+         // Look for this inode in each task
+
+         rcu_read_lock();
+         for_each_process( pEachTask )
+         {
+            if (pEachTask == NULL || pEachTask->files == NULL)
+            {
+               // Some tasks may not have files (e.g. Xsession)
+               continue;
+            }
+            // For each file this task has open, check if it's referencing
+            // our inode.
+            spin_lock_irqsave( &pEachTask->files->file_lock, flags );
+            pFDT = files_fdtable( pEachTask->files );
+            for (count = 0; count < pFDT->max_fds; count++)
+            {
+               pFilp = pFDT->fd[count];
+               if (pFilp != NULL &&  pFilp->f_dentry != NULL)
+               {
+                  if (pFilp->f_dentry->d_inode == pOpenInode)
+                  {
+                     // Close this file handle
+                     rcu_assign_pointer( pFDT->fd[count], NULL );                     
+                     spin_unlock_irqrestore( &pEachTask->files->file_lock, flags );
+                     
+                     DBG( "forcing close of open file handle\n" );
+                     filp_close( pFilp, pEachTask->files );
+
+                     spin_lock_irqsave( &pEachTask->files->file_lock, flags );
+                  }
+               }
+            }
+            spin_unlock_irqrestore( &pEachTask->files->file_lock, flags );
+         }
+         rcu_read_unlock();
+      }
+   }
+
+   // Remove device (so no more calls can be made by users)
+   if (IS_ERR( pDev->mQMIDev.mpDevClass ) == false)
+   {
+      device_destroy( pDev->mQMIDev.mpDevClass, 
+                      pDev->mQMIDev.mDevNum );   
+   }
+
+   // Hold onto cdev memory location until everyone is through using it.
+   // Timeout after 30 seconds (10 ms interval).  Timeout should never happen,
+   // but exists to prevent an infinate loop just in case.
+   for (tries = 0; tries < 30 * 100; tries++)
+   {
+      int ref = atomic_read( &pDev->mQMIDev.mCdev.kobj.kref.refcount );
+      if (ref > 1)
+      {
+         DBG( "cdev in use by %d tasks\n", ref - 1 ); 
+         msleep( 10 );
+      }
+      else
+      {
+         break;
+      }
+   }
+
+   cdev_del( &pDev->mQMIDev.mCdev );
+   
+   unregister_chrdev_region( pDev->mQMIDev.mDevNum, 1 );
+
+   return;
+}
+
+/*=========================================================================*/
+// Driver level client management
+/*=========================================================================*/
+
+/*===========================================================================
+METHOD:
+   QMIReady (Public Method)
+
+DESCRIPTION:
+   Send QMI CTL GET VERSION INFO REQ
+   Wait for response or timeout
+
+PARAMETERS:
+   pDev     [ I ] - Device specific memory
+   timeout  [ I ] - Milliseconds to wait for response
+
+RETURN VALUE:
+   bool
+===========================================================================*/
+bool QMIReady(
+   sGobiUSBNet *    pDev,
+   u16                timeout )
+{
+   int result;
+   void * pWriteBuffer;
+   u16 writeBufferSize;
+   void * pReadBuffer;
+   u16 readBufferSize;
+   struct semaphore readSem;
+   u16 curTime;
+   unsigned long flags;
+   u8 transactionID;
+   
+   if (IsDeviceValid( pDev ) == false)
+   {
+      DBG( "Invalid device\n" );
+      return false;
+   }
+
+   writeBufferSize = QMICTLReadyReqSize();
+   pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
+   if (pWriteBuffer == NULL)
+   {
+      return false;
+   }
+
+   // An implimentation of down_timeout has not been agreed on,
+   //    so it's been added and removed from the kernel several times.
+   //    We're just going to ignore it and poll the semaphore.
+
+   // Send a write every 100 ms and see if we get a response
+   for (curTime = 0; curTime < timeout; curTime += 100)
+   {
+      // Start read
+      sema_init( &readSem, 0 );
+   
+      transactionID = atomic_add_return( 1, &pDev->mQMIDev.mQMICTLTransactionID );
+      if (transactionID == 0)
+      {
+         transactionID = atomic_add_return( 1, &pDev->mQMIDev.mQMICTLTransactionID );
+      }
+      result = ReadAsync( pDev, QMICTL, transactionID, UpSem, &readSem );
+      if (result != 0)
+      {
+         return false;
+      }
+
+      // Fill buffer
+      result = QMICTLReadyReq( pWriteBuffer, 
+                               writeBufferSize,
+                               transactionID );
+      if (result < 0)
+      {
+         kfree( pWriteBuffer );
+         return false;
+      }
+
+      // Disregard status.  On errors, just try again
+      WriteSync( pDev,
+                 pWriteBuffer,
+                 writeBufferSize,
+                 QMICTL );
+
+      msleep( 100 );
+      if (down_trylock( &readSem ) == 0)
+      {
+         // Enter critical section
+         spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
+
+         // Pop the read data
+         if (PopFromReadMemList( pDev,
+                                 QMICTL,
+                                 transactionID,
+                                 &pReadBuffer,
+                                 &readBufferSize ) == true)
+         {
+            // Success
+
+            // End critical section
+            spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
+         
+            // We don't care about the result
+            kfree( pReadBuffer );
+
+            break;
+         }
+         else
+         {
+            // Read mismatch/failure, unlock and continue
+            spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
+         }
+      }
+      else
+      {
+         // Enter critical section
+         spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
+         
+         // Timeout, remove the async read
+         NotifyAndPopNotifyList( pDev, QMICTL, transactionID );
+         
+         // End critical section
+         spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
+      }
+   }
+
+   kfree( pWriteBuffer );
+
+   // Did we time out?   
+   if (curTime >= timeout)
+   {
+      return false;
+   }
+   
+   DBG( "QMI Ready after %u milliseconds\n", curTime );
+
+   // Success
+   return true;
+}
+
+/*===========================================================================
+METHOD:
+   QMIWDSCallback (Public Method)
+
+DESCRIPTION:
+   QMI WDS callback function
+   Update net stats or link state
+
+PARAMETERS:
+   pDev     [ I ] - Device specific memory
+   clientID [ I ] - Client ID
+   pData    [ I ] - Callback data (unused)
+
+RETURN VALUE:
+   None
+===========================================================================*/
+void QMIWDSCallback(
+   sGobiUSBNet *    pDev,
+   u16                clientID,
+   void *             pData )
+{
+   bool bRet;
+   int result;
+   void * pReadBuffer;
+   u16 readBufferSize;
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,31 ))
+   struct net_device_stats * pStats = &(pDev->mpNetDev->stats);
+#else
+   struct net_device_stats * pStats = &(pDev->mpNetDev->net->stats);
+#endif
+
+   u32 TXOk = (u32)-1;
+   u32 RXOk = (u32)-1;
+   u32 TXErr = (u32)-1;
+   u32 RXErr = (u32)-1;
+   u32 TXOfl = (u32)-1;
+   u32 RXOfl = (u32)-1;
+   u64 TXBytesOk = (u64)-1;
+   u64 RXBytesOk = (u64)-1;
+   bool bLinkState;
+   bool bReconfigure;
+   unsigned long flags;
+   
+   if (IsDeviceValid( pDev ) == false)
+   {
+      DBG( "Invalid device\n" );
+      return;
+   }
+
+   // Critical section
+   spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
+   
+   bRet = PopFromReadMemList( pDev,
+                              clientID,
+                              0,
+                              &pReadBuffer,
+                              &readBufferSize );
+   
+   // End critical section
+   spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); 
+   
+   if (bRet == false)
+   {
+      DBG( "WDS callback failed to get data\n" );
+      return;
+   }
+   
+   // Default values
+   bLinkState = ! GobiTestDownReason( pDev, NO_NDIS_CONNECTION );
+   bReconfigure = false;
+
+   result = QMIWDSEventResp( pReadBuffer,
+                             readBufferSize,
+                             &TXOk,
+                             &RXOk,
+                             &TXErr,
+                             &RXErr,
+                             &TXOfl,
+                             &RXOfl,
+                             &TXBytesOk,
+                             &RXBytesOk,
+                             &bLinkState,
+                             &bReconfigure );
+   if (result < 0)
+   {
+      DBG( "bad WDS packet\n" );
+   }
+   else
+   {
+
+      // Fill in new values, ignore max values
+      if (TXOfl != (u32)-1)
+      {
+         pStats->tx_fifo_errors = TXOfl;
+      }
+      
+      if (RXOfl != (u32)-1)
+      {
+         pStats->rx_fifo_errors = RXOfl;
+      }
+
+      if (TXErr != (u32)-1)
+      {
+         pStats->tx_errors = TXErr;
+      }
+      
+      if (RXErr != (u32)-1)
+      {
+         pStats->rx_errors = RXErr;
+      }
+
+      if (TXOk != (u32)-1)
+      {
+         pStats->tx_packets = TXOk + pStats->tx_errors;
+      }
+      
+      if (RXOk != (u32)-1)
+      {
+         pStats->rx_packets = RXOk + pStats->rx_errors;
+      }
+
+      if (TXBytesOk != (u64)-1)
+      {
+         pStats->tx_bytes = TXBytesOk;
+      }
+      
+      if (RXBytesOk != (u64)-1)
+      {
+         pStats->rx_bytes = RXBytesOk;
+      }
+
+      if (bReconfigure == true)
+      {
+         DBG( "Net device link reset\n" );
+         GobiSetDownReason( pDev, NO_NDIS_CONNECTION );
+         GobiClearDownReason( pDev, NO_NDIS_CONNECTION );
+      }
+      else 
+      {
+         if (bLinkState == true)
+         {
+            DBG( "Net device link is connected\n" );
+            GobiClearDownReason( pDev, NO_NDIS_CONNECTION );
+         }
+         else
+         {
+            DBG( "Net device link is disconnected\n" );
+            GobiSetDownReason( pDev, NO_NDIS_CONNECTION );
+         }
+      }
+   }
+
+   kfree( pReadBuffer );
+
+   // Setup next read
+   result = ReadAsync( pDev,
+                       clientID,
+                       0,
+                       QMIWDSCallback,
+                       pData );
+   if (result != 0)
+   {
+      DBG( "unable to setup next async read\n" );
+   }
+
+   return;
+}
+
+/*===========================================================================
+METHOD:
+   SetupQMIWDSCallback (Public Method)
+
+DESCRIPTION:
+   Request client and fire off reqests and start async read for 
+   QMI WDS callback
+
+PARAMETERS:
+   pDev     [ I ] - Device specific memory
+
+RETURN VALUE:
+   int - 0 for success
+         Negative errno for failure
+===========================================================================*/
+int SetupQMIWDSCallback( sGobiUSBNet * pDev )
+{
+   int result;
+   void * pWriteBuffer;
+   u16 writeBufferSize;
+   u16 WDSClientID;
+
+   if (IsDeviceValid( pDev ) == false)
+   {
+      DBG( "Invalid device\n" );
+      return -EFAULT;
+   }
+   
+   result = GetClientID( pDev, QMIWDS );
+   if (result < 0)
+   {
+      return result;
+   }
+   WDSClientID = result;
+
+   // QMI WDS Set Event Report
+   writeBufferSize = QMIWDSSetEventReportReqSize();
+   pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
+   if (pWriteBuffer == NULL)
+   {
+      return -ENOMEM;
+   }
+   
+   result = QMIWDSSetEventReportReq( pWriteBuffer, 
+                                     writeBufferSize,
+                                     1 );
+   if (result < 0)
+   {
+      kfree( pWriteBuffer );
+      return result;
+   }
+
+   result = WriteSync( pDev,
+                       pWriteBuffer,
+                       writeBufferSize,
+                       WDSClientID );
+   kfree( pWriteBuffer );
+
+   if (result < 0)
+   {
+      return result;
+   }
+
+   // QMI WDS Get PKG SRVC Status
+   writeBufferSize = QMIWDSGetPKGSRVCStatusReqSize();
+   pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
+   if (pWriteBuffer == NULL)
+   {
+      return -ENOMEM;
+   }
+
+   result = QMIWDSGetPKGSRVCStatusReq( pWriteBuffer, 
+                                       writeBufferSize,
+                                       2 );
+   if (result < 0)
+   {
+      kfree( pWriteBuffer );
+      return result;
+   }
+   
+   result = WriteSync( pDev,
+                       pWriteBuffer,
+                       writeBufferSize,
+                       WDSClientID );
+   kfree( pWriteBuffer );
+
+   if (result < 0)
+   {
+      return result;
+   }
+
+   // Setup asnyc read callback
+   result = ReadAsync( pDev,
+                       WDSClientID,
+                       0,
+                       QMIWDSCallback,
+                       NULL );
+   if (result != 0)
+   {
+      DBG( "unable to setup async read\n" );
+      return result;
+   }
+
+   // Send SetControlLineState request (USB_CDC)
+   //   Required for Autoconnect
+   result = usb_control_msg( pDev->mpNetDev->udev,
+                             usb_sndctrlpipe( pDev->mpNetDev->udev, 0 ),
+                             0x22,
+                             0x21,
+                             1, // DTR present
+                             0,
+                             NULL,
+                             0,
+                             100 );
+   if (result < 0)
+   {
+      DBG( "Bad SetControlLineState status %d\n", result );
+      return result;
+   }
+
+   return 0;
+}
+
+/*===========================================================================
+METHOD:
+   QMIDMSGetMEID (Public Method)
+
+DESCRIPTION:
+   Register DMS client
+   send MEID req and parse response
+   Release DMS client
+
+PARAMETERS:
+   pDev     [ I ] - Device specific memory
+
+RETURN VALUE:
+   None
+===========================================================================*/
+int QMIDMSGetMEID( sGobiUSBNet * pDev )
+{
+   int result;
+   void * pWriteBuffer;
+   u16 writeBufferSize;
+   void * pReadBuffer;
+   u16 readBufferSize;
+   u16 DMSClientID;
+
+   if (IsDeviceValid( pDev ) == false)
+   {
+      DBG( "Invalid device\n" );
+      return -EFAULT;
+   }
+
+   result = GetClientID( pDev, QMIDMS );
+   if (result < 0)
+   {
+      return result;
+   }
+   DMSClientID = result;
+
+   // QMI DMS Get Serial numbers Req
+   writeBufferSize = QMIDMSGetMEIDReqSize();
+   pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
+   if (pWriteBuffer == NULL)
+   {
+      return -ENOMEM;
+   }
+
+   result = QMIDMSGetMEIDReq( pWriteBuffer, 
+                              writeBufferSize,
+                              1 );
+   if (result < 0)
+   {
+      kfree( pWriteBuffer );
+      return result;
+   }
+
+   result = WriteSync( pDev,
+                       pWriteBuffer,
+                       writeBufferSize,
+                       DMSClientID );
+   kfree( pWriteBuffer );
+
+   if (result < 0)
+   {
+      return result;
+   }
+
+   // QMI DMS Get Serial numbers Resp
+   result = ReadSync( pDev,
+                      &pReadBuffer,
+                      DMSClientID,
+                      1 );
+   if (result < 0)
+   {
+      return result;
+   }
+   readBufferSize = result;
+
+   result = QMIDMSGetMEIDResp( pReadBuffer,
+                               readBufferSize,
+                               &pDev->mMEID[0],
+                               14 );
+   kfree( pReadBuffer );
+
+   if (result < 0)
+   {
+      DBG( "bad get MEID resp\n" );
+      
+      // Non fatal error, device did not return any MEID
+      //    Fill with 0's
+      memset( &pDev->mMEID[0], '0', 14 );
+   }
+
+   ReleaseClientID( pDev, DMSClientID );
+
+   // Success
+   return 0;
+}
diff --git a/drivers/net/usb/QMIDevice.h b/drivers/net/usb/QMIDevice.h
new file mode 100644
index 0000000..176f080
--- /dev/null
+++ b/drivers/net/usb/QMIDevice.h
@@ -0,0 +1,339 @@
+/*===========================================================================
+FILE:
+   QMIDevice.h
+
+DESCRIPTION:
+   Functions related to the QMI interface device
+   
+FUNCTIONS:
+   Generic functions
+      IsDeviceValid
+      PrintHex
+      GobiSetDownReason
+      GobiClearDownReason
+      GobiTestDownReason
+
+   Driver level asynchronous read functions
+      ResubmitIntURB
+      ReadCallback
+      IntCallback
+      StartRead
+      KillRead
+
+   Internal read/write functions
+      ReadAsync
+      UpSem
+      ReadSync
+      WriteSyncCallback
+      WriteSync
+
+   Internal memory management functions
+      GetClientID
+      ReleaseClientID
+      FindClientMem
+      AddToReadMemList
+      PopFromReadMemList
+      AddToNotifyList
+      NotifyAndPopNotifyList
+      AddToURBList
+      PopFromURBList
+
+   Userspace wrappers
+      UserspaceOpen
+      UserspaceIOCTL
+      UserspaceClose
+      UserspaceRead
+      UserspaceWrite
+
+   Initializer and destructor
+      RegisterQMIDevice
+      DeregisterQMIDevice
+
+   Driver level client management
+      QMIReady
+      QMIWDSCallback
+      SetupQMIWDSCallback
+      QMIDMSGetMEID
+
+Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of Code Aurora Forum nor
+      the names of its contributors may be used to endorse or promote
+      products derived from this software without specific prior written
+      permission.
+
+Alternatively, provided that this notice is retained in full, this software
+may be relicensed by the recipient under the terms of the GNU General Public
+License version 2 ("GPL") and only version 2, in which case the provisions of
+the GPL apply INSTEAD OF those given above.  If the recipient relicenses the
+software under the GPL, then the identification text in the MODULE_LICENSE
+macro must be changed to reflect "GPLv2" instead of "Dual BSD/GPL".  Once a
+recipient changes the license terms to the GPL, subsequent recipients shall
+not relicense under alternate licensing terms, including the BSD or dual
+BSD/GPL terms.  In addition, the following license statement immediately
+below and between the words START and END shall also then apply when this
+software is relicensed under the GPL:
+
+START
+
+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.
+
+END
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+===========================================================================*/
+
+//---------------------------------------------------------------------------
+// Pragmas
+//---------------------------------------------------------------------------
+#pragma once
+
+//---------------------------------------------------------------------------
+// Include Files
+//---------------------------------------------------------------------------
+#include "Structs.h"
+#include "QMI.h"
+
+/*=========================================================================*/
+// Generic functions
+/*=========================================================================*/
+
+// Basic test to see if device memory is valid
+bool IsDeviceValid( sGobiUSBNet * pDev );
+
+// Print Hex data, for debug purposes
+void PrintHex(
+   void *         pBuffer,
+   u16            bufSize );
+
+// Sets mDownReason and turns carrier off
+void GobiSetDownReason(
+   sGobiUSBNet *    pDev,
+   u8                 reason );
+
+// Clear mDownReason and may turn carrier on
+void GobiClearDownReason(
+   sGobiUSBNet *    pDev,
+   u8                 reason );
+
+// Tests mDownReason and returns whether reason is set
+bool GobiTestDownReason(
+   sGobiUSBNet *    pDev,
+   u8                 reason );
+
+/*=========================================================================*/
+// Driver level asynchronous read functions
+/*=========================================================================*/
+
+// Resubmit interrupt URB, re-using same values
+int ResubmitIntURB( struct urb * pIntURB );
+
+// Read callback
+//    Put the data in storage and notify anyone waiting for data
+void ReadCallback( struct urb * pReadURB );
+
+// Inturrupt callback
+//    Data is available, start a read URB
+void IntCallback( struct urb * pIntURB );
+
+// Start continuous read "thread"
+int StartRead( sGobiUSBNet * pDev );
+
+// Kill continuous read "thread"
+void KillRead( sGobiUSBNet * pDev );
+
+/*=========================================================================*/
+// Internal read/write functions
+/*=========================================================================*/
+
+// Start asynchronous read
+//     Reading client's data store, not device
+int ReadAsync(
+   sGobiUSBNet *    pDev,
+   u16                clientID,
+   u16                transactionID,
+   void               (*pCallback)(sGobiUSBNet *, u16, void *),
+   void *             pData );
+
+// Notification function for synchronous read
+void UpSem( 
+   sGobiUSBNet *    pDev,
+   u16                clientID,
+   void *             pData );
+
+// Start synchronous read
+//     Reading client's data store, not device
+int ReadSync(
+   sGobiUSBNet *    pDev,
+   void **            ppOutBuffer,
+   u16                clientID,
+   u16                transactionID );
+
+// Write callback
+void WriteSyncCallback( struct urb * pWriteURB );
+
+// Start synchronous write
+int WriteSync(
+   sGobiUSBNet *    pDev,
+   char *             pInWriteBuffer,
+   int                size,
+   u16                clientID );
+
+/*=========================================================================*/
+// Internal memory management functions
+/*=========================================================================*/
+
+// Create client and allocate memory
+int GetClientID( 
+   sGobiUSBNet *      pDev,
+   u8                   serviceType );
+
+// Release client and free memory
+void ReleaseClientID(
+   sGobiUSBNet *      pDev,
+   u16                  clientID );
+
+// Find this client's memory
+sClientMemList * FindClientMem(
+   sGobiUSBNet *      pDev,
+   u16                  clientID );
+
+// Add Data to this client's ReadMem list
+bool AddToReadMemList( 
+   sGobiUSBNet *      pDev,
+   u16                  clientID,
+   u16                  transactionID,
+   void *               pData,
+   u16                  dataSize );
+
+// Remove data from this client's ReadMem list if it matches 
+// the specified transaction ID.
+bool PopFromReadMemList( 
+   sGobiUSBNet *      pDev,
+   u16                  clientID,
+   u16                  transactionID,
+   void **              ppData,
+   u16 *                pDataSize );
+
+// Add Notify entry to this client's notify List
+bool AddToNotifyList( 
+   sGobiUSBNet *      pDev,
+   u16                  clientID,
+   u16                  transactionID,
+   void                 (* pNotifyFunct)(sGobiUSBNet *, u16, void *),
+   void *               pData );
+
+// Remove first Notify entry from this client's notify list 
+//    and Run function
+bool NotifyAndPopNotifyList( 
+   sGobiUSBNet *      pDev,
+   u16                  clientID,
+   u16                  transactionID );
+
+// Add URB to this client's URB list
+bool AddToURBList( 
+   sGobiUSBNet *      pDev,
+   u16                  clientID,
+   struct urb *         pURB );
+
+// Remove URB from this client's URB list
+struct urb * PopFromURBList( 
+   sGobiUSBNet *      pDev,
+   u16                  clientID );
+
+/*=========================================================================*/
+// Userspace wrappers
+/*=========================================================================*/
+
+// Userspace open
+int UserspaceOpen( 
+   struct inode *   pInode, 
+   struct file *    pFilp );
+
+// Userspace ioctl
+int UserspaceIOCTL( 
+   struct inode *    pUnusedInode, 
+   struct file *     pFilp,
+   unsigned int      cmd, 
+   unsigned long     arg );
+
+// Userspace close
+int UserspaceClose( 
+   struct file *       pFilp,
+   fl_owner_t          unusedFileTable );
+
+// Userspace read (synchronous)
+ssize_t UserspaceRead( 
+   struct file *        pFilp,
+   char __user *        pBuf, 
+   size_t               size,
+   loff_t *             pUnusedFpos );
+
+// Userspace write (synchronous)
+ssize_t UserspaceWrite(
+   struct file *        pFilp, 
+   const char __user *  pBuf, 
+   size_t               size,
+   loff_t *             pUnusedFpos );
+
+/*=========================================================================*/
+// Initializer and destructor
+/*=========================================================================*/
+
+// QMI Device initialization function
+int RegisterQMIDevice( sGobiUSBNet * pDev );
+
+// QMI Device cleanup function
+void DeregisterQMIDevice( sGobiUSBNet * pDev );
+
+/*=========================================================================*/
+// Driver level client management
+/*=========================================================================*/
+
+// Check if QMI is ready for use
+bool QMIReady(
+   sGobiUSBNet *    pDev,
+   u16                timeout );
+
+// QMI WDS callback function
+void QMIWDSCallback(
+   sGobiUSBNet *    pDev,
+   u16                clientID,
+   void *             pData );
+
+// Fire off reqests and start async read for QMI WDS callback
+int SetupQMIWDSCallback( sGobiUSBNet * pDev );
+
+// Register client, send req and parse MEID response, release client
+int QMIDMSGetMEID( sGobiUSBNet * pDev );
+
+
+
diff --git a/drivers/net/usb/Structs.h b/drivers/net/usb/Structs.h
new file mode 100644
index 0000000..0d8dd4e
--- /dev/null
+++ b/drivers/net/usb/Structs.h
@@ -0,0 +1,359 @@
+/*===========================================================================
+FILE:
+   Structs.h
+
+DESCRIPTION:
+   Declaration of structures used by the Qualcomm Linux USB Network driver
+   
+FUNCTIONS:
+   none
+
+Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of Code Aurora Forum nor
+      the names of its contributors may be used to endorse or promote
+      products derived from this software without specific prior written
+      permission.
+
+Alternatively, provided that this notice is retained in full, this software
+may be relicensed by the recipient under the terms of the GNU General Public
+License version 2 ("GPL") and only version 2, in which case the provisions of
+the GPL apply INSTEAD OF those given above.  If the recipient relicenses the
+software under the GPL, then the identification text in the MODULE_LICENSE
+macro must be changed to reflect "GPLv2" instead of "Dual BSD/GPL".  Once a
+recipient changes the license terms to the GPL, subsequent recipients shall
+not relicense under alternate licensing terms, including the BSD or dual
+BSD/GPL terms.  In addition, the following license statement immediately
+below and between the words START and END shall also then apply when this
+software is relicensed under the GPL:
+
+START
+
+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.
+
+END
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+===========================================================================*/
+
+//---------------------------------------------------------------------------
+// Pragmas
+//---------------------------------------------------------------------------
+#pragma once
+
+//---------------------------------------------------------------------------
+// Include Files
+//---------------------------------------------------------------------------
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/mii.h>
+#include <linux/usb.h>
+#include <linux/version.h>
+#include <linux/cdev.h>
+#include <linux/kthread.h>
+
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,24 ))
+   #include "usbnet.h"
+#else
+   #include <linux/usb/usbnet.h>
+#endif
+
+#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,25 ))
+   #include <linux/fdtable.h>
+#else
+   #include <linux/file.h>
+#endif
+
+// DBG macro
+#define DBG( format, arg... ) \
+   if (debug == 1)\
+   { \
+      printk( KERN_INFO "GobiNet::%s " format, __FUNCTION__, ## arg ); \
+   } \
+
+
+// Used in recursion, defined later below
+struct sGobiUSBNet;
+
+/*=========================================================================*/
+// Struct sReadMemList
+//
+//    Structure that defines an entry in a Read Memory linked list
+/*=========================================================================*/
+typedef struct sReadMemList
+{
+   /* Data buffer */
+   void *                     mpData;
+   
+   /* Transaction ID */
+   u16                        mTransactionID;
+
+   /* Size of data buffer */
+   u16                        mDataSize;
+
+   /* Next entry in linked list */
+   struct sReadMemList *      mpNext;
+
+} sReadMemList;
+
+/*=========================================================================*/
+// Struct sNotifyList
+//
+//    Structure that defines an entry in a Notification linked list
+/*=========================================================================*/
+typedef struct sNotifyList
+{
+   /* Function to be run when data becomes available */
+   void                  (* mpNotifyFunct)(struct sGobiUSBNet *, u16, void *);
+   
+   /* Transaction ID */
+   u16                   mTransactionID;
+
+   /* Data to provide as parameter to mpNotifyFunct */
+   void *                mpData;
+   
+   /* Next entry in linked list */
+   struct sNotifyList *  mpNext;
+
+} sNotifyList;
+
+/*=========================================================================*/
+// Struct sURBList
+//
+//    Structure that defines an entry in a URB linked list
+/*=========================================================================*/
+typedef struct sURBList
+{
+   /* The current URB */
+   struct urb *       mpURB;
+
+   /* Next entry in linked list */
+   struct sURBList *  mpNext;
+
+} sURBList;
+
+/*=========================================================================*/
+// Struct sClientMemList
+//
+//    Structure that defines an entry in a Client Memory linked list
+//      Stores data specific to a Service Type and Client ID
+/*=========================================================================*/
+typedef struct sClientMemList
+{
+   /* Client ID for this Client */
+   u16                          mClientID;
+
+   /* Linked list of Read entries */
+   /*    Stores data read from device before sending to client */
+   sReadMemList *               mpList;
+   
+   /* Linked list of Notification entries */
+   /*    Stores notification functions to be run as data becomes 
+         available or the device is removed */
+   sNotifyList *                mpReadNotifyList;
+
+   /* Linked list of URB entries */
+   /*    Stores pointers to outstanding URBs which need canceled 
+         when the client is deregistered or the device is removed */
+   sURBList *                   mpURBList;
+   
+   /* Next entry in linked list */
+   struct sClientMemList *      mpNext;
+
+} sClientMemList;
+
+/*=========================================================================*/
+// Struct sURBSetupPacket
+//
+//    Structure that defines a USB Setup packet for Control URBs
+//    Taken from USB CDC specifications
+/*=========================================================================*/
+typedef struct sURBSetupPacket
+{
+   /* Request type */
+   u8    mRequestType;
+
+   /* Request code */
+   u8    mRequestCode;
+
+   /* Value */
+   u16   mValue;
+
+   /* Index */
+   u16   mIndex;
+
+   /* Length of Control URB */
+   u16   mLength;
+
+} sURBSetupPacket;
+
+// Common value for sURBSetupPacket.mLength
+#define DEFAULT_READ_URB_LENGTH 0x1000
+
+
+/*=========================================================================*/
+// Struct sAutoPM
+//
+//    Structure used to manage AutoPM thread which determines whether the
+//    device is in use or may enter autosuspend.  Also submits net 
+//    transmissions asynchronously.
+/*=========================================================================*/
+typedef struct sAutoPM
+{
+   /* Thread for atomic autopm function */
+   struct task_struct *       mpThread;
+
+   /* Signal for completion when it's time for the thread to work */
+   struct completion          mThreadDoWork;
+
+   /* Time to exit? */
+   bool                       mbExit;
+
+   /* List of URB's queued to be sent to the device */
+   sURBList *                 mpURBList;
+
+   /* URB list lock (for adding and removing elements) */
+   spinlock_t                 mURBListLock;
+   
+   /* Active URB */
+   struct urb *               mpActiveURB;
+
+   /* Active URB lock (for adding and removing elements) */
+   spinlock_t                 mActiveURBLock;
+   
+   /* Duplicate pointer to USB device interface */
+   struct usb_interface *     mpIntf;
+
+} sAutoPM;
+
+
+/*=========================================================================*/
+// Struct sQMIDev
+//
+//    Structure that defines the data for the QMI device
+/*=========================================================================*/
+typedef struct sQMIDev
+{
+   /* Device number */
+   dev_t                      mDevNum;
+
+   /* Device class */
+   struct class *             mpDevClass;
+
+   /* cdev struct */
+   struct cdev                mCdev;
+
+   /* is mCdev initialized? */
+   bool                       mbCdevIsInitialized;
+
+   /* Pointer to read URB */
+   struct urb *               mpReadURB;
+
+   /* Read setup packet */
+   sURBSetupPacket *          mpReadSetupPacket;
+
+   /* Read buffer attached to current read URB */
+   void *                     mpReadBuffer;
+   
+   /* Inturrupt URB */
+   /*    Used to asynchronously notify when read data is available */
+   struct urb *               mpIntURB;
+
+   /* Buffer used by Inturrupt URB */
+   void *                     mpIntBuffer;
+   
+   /* Pointer to memory linked list for all clients */
+   sClientMemList *           mpClientMemList;
+   
+   /* Spinlock for client Memory entries */
+   spinlock_t                 mClientMemLock;
+
+   /* Transaction ID associated with QMICTL "client" */
+   atomic_t                   mQMICTLTransactionID;
+
+} sQMIDev;
+
+/*=========================================================================*/
+// Struct sGobiUSBNet
+//
+//    Structure that defines the data associated with the Qualcomm USB device
+/*=========================================================================*/
+typedef struct sGobiUSBNet
+{
+   /* Net device structure */
+   struct usbnet *        mpNetDev;
+
+   /* Usb device interface */
+   struct usb_interface * mpIntf;
+   
+   /* Pointers to usbnet_open and usbnet_stop functions */
+   int                  (* mpUSBNetOpen)(struct net_device *);
+   int                  (* mpUSBNetStop)(struct net_device *);
+   
+   /* Reason(s) why interface is down */
+   /* Used by Gobi*DownReason */
+   unsigned long          mDownReason;
+#define NO_NDIS_CONNECTION    0
+#define CDC_CONNECTION_SPEED  1
+#define DRIVER_SUSPENDED      2
+#define NET_IFACE_STOPPED     3
+
+   /* QMI "device" status */
+   bool                   mbQMIValid;
+
+   /* QMI "device" memory */
+   sQMIDev                mQMIDev;
+
+   /* Device MEID */
+   char                   mMEID[14];
+   
+   /* AutoPM thread */
+   sAutoPM                mAutoPM;
+
+} sGobiUSBNet;
+
+/*=========================================================================*/
+// Struct sQMIFilpStorage
+//
+//    Structure that defines the storage each file handle contains
+//       Relates the file handle to a client
+/*=========================================================================*/
+typedef struct sQMIFilpStorage
+{
+   /* Client ID */
+   u16                  mClientID;
+   
+   /* Device pointer */
+   sGobiUSBNet *          mpDev;
+
+} sQMIFilpStorage;
+
+
diff --git a/drivers/usb/serial/GobiSerial.c b/drivers/usb/serial/GobiSerial.c
new file mode 100644
index 0000000..ee92269
--- /dev/null
+++ b/drivers/usb/serial/GobiSerial.c
@@ -0,0 +1,754 @@
+/*===========================================================================
+FILE: 
+   GobiSerial.c
+
+DESCRIPTION:
+   Linux Qualcomm Serial USB driver Implementation 
+
+PUBLIC DRIVER FUNCTIONS:
+   GobiProbe
+   GobiOpen
+   GobiClose
+   GobiReadBulkCallback (if kernel is less than 2.6.25)
+   GobiSuspend
+   GobiResume (if kernel is less than 2.6.24)
+
+Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of Code Aurora Forum nor
+      the names of its contributors may be used to endorse or promote
+      products derived from this software without specific prior written
+      permission.
+
+Alternatively, provided that this notice is retained in full, this software
+may be relicensed by the recipient under the terms of the GNU General Public
+License version 2 ("GPL") and only version 2, in which case the provisions of
+the GPL apply INSTEAD OF those given above.  If the recipient relicenses the
+software under the GPL, then the identification text in the MODULE_LICENSE
+macro must be changed to reflect "GPLv2" instead of "Dual BSD/GPL".  Once a
+recipient changes the license terms to the GPL, subsequent recipients shall
+not relicense under alternate licensing terms, including the BSD or dual
+BSD/GPL terms.  In addition, the following license statement immediately
+below and between the words START and END shall also then apply when this
+software is relicensed under the GPL:
+
+START
+
+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.
+
+END
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+==========================================================================*/
+//---------------------------------------------------------------------------
+// Include Files
+//---------------------------------------------------------------------------
+
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/usb.h> 
+#include <linux/usb/serial.h>
+#include <linux/version.h>
+
+//---------------------------------------------------------------------------
+// Global veriable and defination
+//---------------------------------------------------------------------------
+
+// Version Information
+#define DRIVER_VERSION "1.0.20"
+#define DRIVER_AUTHOR "Qualcomm Innovation Center"
+#define DRIVER_DESC "GobiSerial"
+
+#define NUM_BULK_EPS         1
+#define MAX_BULK_EPS         6
+
+// Debug flag
+static int debug;
+
+// Global pointer to usb_serial_generic_close function
+// This function is not exported, which is why we have to use a pointer
+// instead of just calling it.
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,26 ))
+   void (* gpClose)( 
+      struct usb_serial_port *,
+      struct file * );
+#elif (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,29 ))
+   void (* gpClose)( 
+      struct tty_struct *,
+      struct usb_serial_port *,
+      struct file * );
+#else // > 2.6.29
+   void (* gpClose)( struct usb_serial_port * );
+#endif
+
+// DBG macro
+#define DBG( format, arg... ) \
+   if (debug == 1)\
+   { \
+      printk( KERN_INFO "GobiSerial::%s " format, __FUNCTION__, ## arg ); \
+   } \
+
+/*=========================================================================*/
+// Function Prototypes
+/*=========================================================================*/
+
+// Attach to correct interfaces
+static int GobiProbe(
+   struct usb_serial * pSerial, 
+   const struct usb_device_id * pID );
+   
+// Start GPS if GPS port, run usb_serial_generic_open
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,26 ))
+   int GobiOpen(
+      struct usb_serial_port *   pPort,
+      struct file *              pFilp );
+#elif (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,31 ))
+   int GobiOpen(
+      struct tty_struct *        pTTY,
+      struct usb_serial_port *   pPort,
+      struct file *              pFilp );
+#else // > 2.6.31
+   int GobiOpen(
+      struct tty_struct *        pTTY,
+      struct usb_serial_port *   pPort );
+#endif
+
+// Stop GPS if GPS port, run usb_serial_generic_close
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,26 ))
+   void GobiClose( 
+      struct usb_serial_port *,
+      struct file * );
+#elif (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,29 ))
+   void GobiClose( 
+      struct tty_struct *,
+      struct usb_serial_port *,
+      struct file * );
+#else // > 2.6.29
+   void GobiClose( struct usb_serial_port * );
+#endif
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,25 ))
+
+// Read data from USB, push to TTY and user space
+static void GobiReadBulkCallback( struct urb * pURB );
+
+#endif
+
+// Set reset_resume flag
+int GobiSuspend( 
+   struct usb_interface *     pIntf,
+   pm_message_t               powerEvent );
+
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,23 ))
+
+// Restart URBs killed during usb_serial_suspend
+int GobiResume( struct usb_interface * pIntf );
+
+#endif
+
+/*=========================================================================*/
+// Qualcomm Gobi 3000 VID/PIDs
+/*=========================================================================*/
+static struct usb_device_id GobiVIDPIDTable[] = 
+{
+   { USB_DEVICE( 0x05c6, 0x920c ) },   // Gobi 3000 QDL device
+   { USB_DEVICE( 0x05c6, 0x920d ) },   // Gobi 3000 Composite Device
+   { }                               // Terminating entry
+};
+MODULE_DEVICE_TABLE( usb, GobiVIDPIDTable );
+
+/*=========================================================================*/
+// Struct usb_serial_driver
+// Driver structure we register with the USB core
+/*=========================================================================*/
+static struct usb_driver GobiDriver = 
+{
+   .name       = "GobiSerial",
+   .probe      = usb_serial_probe,
+   .disconnect = usb_serial_disconnect,
+   .id_table   = GobiVIDPIDTable,
+   .suspend    = GobiSuspend,
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,23 ))
+   .resume     = GobiResume,
+#else
+   .resume     = usb_serial_resume,
+#endif
+   .supports_autosuspend = true,
+};
+
+/*=========================================================================*/
+// Struct usb_serial_driver
+/*=========================================================================*/
+static struct usb_serial_driver gGobiDevice = 
+{
+   .driver = 
+   {   
+      .owner     = THIS_MODULE,
+      .name      = "GobiSerial driver",
+   },
+   .description         = "GobiSerial",
+   .id_table            = GobiVIDPIDTable,
+   .usb_driver          = &GobiDriver,
+   .num_ports           = 1,
+   .probe               = GobiProbe,
+   .open                = GobiOpen,
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,25 ))
+   .num_interrupt_in    = NUM_DONT_CARE,
+   .num_bulk_in         = 1,
+   .num_bulk_out        = 1,
+   .read_bulk_callback  = GobiReadBulkCallback,
+#endif
+};
+
+//---------------------------------------------------------------------------
+// USB serial core overridding Methods
+//---------------------------------------------------------------------------
+
+/*===========================================================================
+METHOD:
+   GobiProbe (Free Method)
+
+DESCRIPTION:
+   Attach to correct interfaces
+
+PARAMETERS:
+   pSerial    [ I ] - Serial structure 
+   pID        [ I ] - VID PID table
+
+RETURN VALUE:
+   int - negative error code on failure
+         zero on success
+===========================================================================*/
+static int GobiProbe(
+   struct usb_serial * pSerial, 
+   const struct usb_device_id * pID )
+{
+   // Assume failure
+   int nRetval = -ENODEV;
+
+   int nNumInterfaces;
+   int nInterfaceNum;
+   DBG( "\n" );
+
+   // Test parameters
+   if ( (pSerial == NULL)
+   ||   (pSerial->dev == NULL)
+   ||   (pSerial->dev->actconfig == NULL)
+   ||   (pSerial->interface == NULL)
+   ||   (pSerial->interface->cur_altsetting == NULL)
+   ||   (pSerial->type == NULL) )
+   {
+      DBG( "invalid parameter\n" );
+      return -EINVAL;
+   }
+
+   nNumInterfaces = pSerial->dev->actconfig->desc.bNumInterfaces;
+   DBG( "Num Interfaces = %d\n", nNumInterfaces );
+   nInterfaceNum = pSerial->interface->cur_altsetting->desc.bInterfaceNumber;
+   DBG( "This Interface = %d\n", nInterfaceNum );
+   
+   if (nNumInterfaces == 1)
+   {
+      // QDL mode?
+      if (nInterfaceNum == 1 || nInterfaceNum == 0) 
+      {
+         DBG( "QDL port found\n" );
+         nRetval = usb_set_interface( pSerial->dev, 
+                                      nInterfaceNum, 
+                                      0 );
+         if (nRetval < 0)
+         {
+            DBG( "Could not set interface, error %d\n", nRetval );
+         }
+      }
+      else
+      {
+         DBG( "Incorrect QDL interface number\n" );
+      }
+   }
+   else
+   {
+      // Composite mode
+      if (nInterfaceNum == 2) 
+      {
+         DBG( "Modem port found\n" );
+         nRetval = usb_set_interface( pSerial->dev, 
+                                      nInterfaceNum, 
+                                      0 );
+         if (nRetval < 0)
+         {
+            DBG( "Could not set interface, error %d\n", nRetval );
+         }
+      }
+      else if (nInterfaceNum == 3) 
+      {
+         DBG( "GPS port found\n" );
+         nRetval = usb_set_interface( pSerial->dev, 
+                                      nInterfaceNum, 
+                                      0 );
+         if (nRetval < 0)
+         {
+            DBG( "Could not set interface, error %d\n", nRetval );
+         }
+         
+         // Check for recursion
+         if (pSerial->type->close != GobiClose)
+         {
+            // Store usb_serial_generic_close in gpClose
+            gpClose = pSerial->type->close;
+            pSerial->type->close = GobiClose;
+         }
+      }
+      else
+      {
+         // Not a port we want to support at this time
+         DBG( "Unsupported interface number\n" );
+      }
+   }
+
+   return nRetval;
+}
+
+/*===========================================================================
+METHOD:
+   GobiOpen (Free Method)
+
+DESCRIPTION:
+   Start GPS if GPS port, run usb_serial_generic_open
+
+PARAMETERS:
+   pTTY    [ I ] - TTY structure (only on kernels <= 2.6.26)
+   pPort   [ I ] - USB serial port structure
+   pFilp   [ I ] - File structure (only on kernels <= 2.6.31)
+
+RETURN VALUE:
+   int - zero for success
+       - negative errno on error
+===========================================================================*/
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,26 ))
+int GobiOpen(
+   struct usb_serial_port *   pPort,
+   struct file *              pFilp )
+#elif (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,31 ))
+int GobiOpen(
+   struct tty_struct *        pTTY,
+   struct usb_serial_port *   pPort,
+   struct file *              pFilp )
+#else // > 2.6.31
+int GobiOpen(
+   struct tty_struct *        pTTY,
+   struct usb_serial_port *   pPort )
+#endif
+{
+   const char startMessage[] = "$GPS_START";
+   int nResult;
+   int bytesWrote;
+   
+   DBG( "\n" );
+   
+   // Test parameters
+   if ( (pPort == NULL)
+   ||   (pPort->serial == NULL)
+   ||   (pPort->serial->dev == NULL)
+   ||   (pPort->serial->interface == NULL)
+   ||   (pPort->serial->interface->cur_altsetting == NULL) )
+   {
+      DBG( "invalid parameter\n" );
+      return -EINVAL;
+   }
+   
+   // Is this the GPS port?
+   if (pPort->serial->interface->cur_altsetting->desc.bInterfaceNumber == 3)
+   {
+      // Send startMessage, 1s timeout
+      nResult = usb_bulk_msg( pPort->serial->dev,
+                              usb_sndbulkpipe( pPort->serial->dev,
+                                               pPort->bulk_out_endpointAddress ),
+                              (void *)&startMessage[0],
+                              sizeof( startMessage ),
+                              &bytesWrote,
+                              1000 );
+      if (nResult != 0)
+      {
+         DBG( "error %d sending startMessage\n", nResult );
+         return nResult;
+      }
+      if (bytesWrote != sizeof( startMessage ))
+      {
+         DBG( "invalid write size %d, %d\n", 
+              bytesWrote, 
+              sizeof( startMessage ) );
+         return -EIO;
+      }      
+   }
+   
+   // Pass to usb_serial_generic_open
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,26 ))
+   return usb_serial_generic_open( pPort, pFilp );
+#elif (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,31 ))
+   return usb_serial_generic_open( pTTY, pPort, pFilp );
+#else // > 2.6.31
+   return usb_serial_generic_open( pTTY, pPort );
+#endif
+}
+
+/*===========================================================================
+METHOD:
+   GobiClose (Free Method)
+
+DESCRIPTION:
+   Stop GPS if GPS port, run usb_serial_generic_close
+
+PARAMETERS:
+   pTTY    [ I ] - TTY structure (only if kernel > 2.6.26 and <= 2.6.29)
+   pPort   [ I ] - USB serial port structure
+   pFilp   [ I ] - File structure (only on kernel <= 2.6.29)
+===========================================================================*/
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,26 ))
+void GobiClose(
+   struct usb_serial_port *   pPort,
+   struct file *              pFilp )
+#elif (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,29 ))
+void GobiClose(
+   struct tty_struct *        pTTY,
+   struct usb_serial_port *   pPort,
+   struct file *              pFilp )
+#else // > 2.6.29
+void GobiClose( struct usb_serial_port * pPort )
+#endif
+{
+   const char stopMessage[] = "$GPS_STOP";
+   int nResult;
+   int bytesWrote;
+   
+   DBG( "\n" );
+   
+   // Test parameters
+   if ( (pPort == NULL)
+   ||   (pPort->serial == NULL)
+   ||   (pPort->serial->dev == NULL)
+   ||   (pPort->serial->interface == NULL)
+   ||   (pPort->serial->interface->cur_altsetting == NULL) )
+   {
+      DBG( "invalid parameter\n" );
+      return;
+   }
+   
+   // Is this the GPS port?
+   if (pPort->serial->interface->cur_altsetting->desc.bInterfaceNumber == 3)
+   {
+      // Send stopMessage, 1s timeout
+      nResult = usb_bulk_msg( pPort->serial->dev,
+                              usb_sndbulkpipe( pPort->serial->dev,
+                                               pPort->bulk_out_endpointAddress ),
+                              (void *)&stopMessage[0],
+                              sizeof( stopMessage ),
+                              &bytesWrote,
+                              1000 );
+      if (nResult != 0)
+      {
+         DBG( "error %d sending stopMessage\n", nResult );
+      }
+      if (bytesWrote != sizeof( stopMessage ))
+      {
+         DBG( "invalid write size %d, %d\n", 
+              bytesWrote, 
+              sizeof( stopMessage ) );
+      }      
+   }
+   
+   // Pass to usb_serial_generic_close
+   if (gpClose == NULL)
+   {
+      DBG( "NULL gpClose\n" );
+      return;
+   }
+   
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,26 ))
+   gpClose( pPort, pFilp );
+#elif (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,29 ))
+   gpClose( pTTY, pPort, pFilp );
+#else // > 2.6.29
+   gpClose( pPort );
+#endif
+}
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,25 ))
+
+/*===========================================================================
+METHOD:
+   GobiReadBulkCallback (Free Method)
+
+DESCRIPTION:
+   Read data from USB, push to TTY and user space
+
+PARAMETERS:
+   pURB  [ I ] - USB Request Block (urb) that called us 
+
+RETURN VALUE:
+===========================================================================*/
+static void GobiReadBulkCallback( struct urb * pURB )
+{
+   struct usb_serial_port * pPort = pURB->context;
+   struct tty_struct * pTTY = pPort->tty;
+   int nResult;
+   int nRoom = 0;
+   unsigned int pipeEP;
+   
+   DBG( "port %d\n", pPort->number );
+
+   if (pURB->status != 0) 
+   {
+      DBG( "nonzero read bulk status received: %d\n", pURB->status );
+
+      return;
+   }
+
+   usb_serial_debug_data( debug, 
+                          &pPort->dev, 
+                          __FUNCTION__, 
+                          pURB->actual_length, 
+                          pURB->transfer_buffer );
+
+   // We do no port throttling
+
+   // Push data to tty layer and user space read function
+   if (pTTY != 0 && pURB->actual_length) 
+   {
+      nRoom = tty_buffer_request_room( pTTY, pURB->actual_length );
+      DBG( "room size %d %d\n", nRoom, 512 );
+      if (nRoom != 0)
+      {
+         tty_insert_flip_string( pTTY, pURB->transfer_buffer, nRoom );
+         tty_flip_buffer_push( pTTY );
+      }
+   }
+
+   pipeEP = usb_rcvbulkpipe( pPort->serial->dev, 
+                             pPort->bulk_in_endpointAddress );
+
+   // For continuous reading
+   usb_fill_bulk_urb( pPort->read_urb, 
+                      pPort->serial->dev,
+                      pipeEP,
+                      pPort->read_urb->transfer_buffer,
+                      pPort->read_urb->transfer_buffer_length,
+                      GobiReadBulkCallback, 
+                      pPort );
+   
+   nResult = usb_submit_urb( pPort->read_urb, GFP_ATOMIC );
+   if (nResult != 0)
+   {
+      DBG( "failed resubmitting read urb, error %d\n", nResult );
+   }
+}
+
+#endif
+
+/*===========================================================================
+METHOD:
+   GobiSuspend (Public Method)
+
+DESCRIPTION:
+   Set reset_resume flag
+
+PARAMETERS
+   pIntf          [ I ] - Pointer to interface
+   powerEvent     [ I ] - Power management event
+
+RETURN VALUE:
+   int - 0 for success
+         negative errno for failure
+===========================================================================*/
+int GobiSuspend( 
+   struct usb_interface *     pIntf,
+   pm_message_t               powerEvent )
+{
+   struct usb_serial * pDev;
+   
+   if (pIntf == 0)
+   {
+      return -ENOMEM;
+   }
+   
+   pDev = usb_get_intfdata( pIntf );
+   if (pDev == NULL)
+   {
+      return -ENXIO;
+   }
+
+   // Unless this is PM_EVENT_SUSPEND, make sure device gets rescanned
+   if ((powerEvent.event & PM_EVENT_SUSPEND) == 0)
+   {
+      pDev->dev->reset_resume = 1;
+   }
+   
+   // Run usb_serial's suspend function
+   return usb_serial_suspend( pIntf, powerEvent );
+}
+
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,23 ))
+
+/*===========================================================================
+METHOD:
+   GobiResume (Free Method)
+
+DESCRIPTION:
+   Restart URBs killed during usb_serial_suspend
+
+   Fixes 2 bugs in 2.6.23 kernel
+      1. pSerial->type->resume was NULL and unchecked, caused crash.
+      2. set_to_generic_if_null was not run for resume.
+
+PARAMETERS:
+   pIntf  [ I ] - Pointer to interface
+
+RETURN VALUE:
+   int - 0 for success
+         negative errno for failure
+===========================================================================*/
+int GobiResume( struct usb_interface * pIntf )
+{
+   struct usb_serial * pSerial = usb_get_intfdata( pIntf );
+   struct usb_serial_port * pPort;
+   int portIndex, errors, nResult;
+
+   if (pSerial == NULL)
+   {
+      DBG( "no pSerial\n" );
+      return -ENOMEM;
+   }
+   if (pSerial->type == NULL)
+   {
+      DBG( "no pSerial->type\n" );
+      return ENOMEM;
+   }
+   if (pSerial->type->resume == NULL)
+   {
+      // Expected behaviour in 2.6.23, in later kernels this was handled
+      // by the usb-serial driver and usb_serial_generic_resume
+      errors = 0;
+      for (portIndex = 0; portIndex < pSerial->num_ports; portIndex++)
+      {
+         pPort = pSerial->port[portIndex];
+         if (pPort->open_count > 0 && pPort->read_urb != NULL)
+         {
+            nResult = usb_submit_urb( pPort->read_urb, GFP_NOIO );
+            if (nResult < 0)
+            {
+               // Return first error we see
+               DBG( "error %d\n", nResult );
+               return nResult;
+            }
+         }
+      }
+
+      // Success
+      return 0;
+   }
+
+   // Execution would only reach this point if user has
+   // patched version of usb-serial driver.
+   return usb_serial_resume( pIntf );
+}
+
+#endif
+
+/*===========================================================================
+METHOD:
+   GobiInit (Free Method)
+
+DESCRIPTION:
+   Register the driver and device
+
+PARAMETERS:
+
+RETURN VALUE:
+   int - negative error code on failure
+         zero on success
+===========================================================================*/
+static int __init GobiInit( void )
+{
+   int nRetval = 0;
+   gpClose = NULL;
+
+   gGobiDevice.num_ports = NUM_BULK_EPS;
+
+   // Registering driver to USB serial core layer 
+   nRetval = usb_serial_register( &gGobiDevice );
+   if (nRetval != 0)
+   {
+      return nRetval;
+   }
+
+   // Registering driver to USB core layer
+   nRetval = usb_register( &GobiDriver );
+   if (nRetval != 0) 
+   {
+      usb_serial_deregister( &gGobiDevice );
+      return nRetval;
+   }
+
+   // This will be shown whenever driver is loaded
+   printk( KERN_INFO "%s: %s\n", DRIVER_DESC, DRIVER_VERSION );
+   
+   return nRetval;
+}
+
+/*===========================================================================
+METHOD:
+   GobiExit (Free Method)
+
+DESCRIPTION:
+   Deregister the driver and device
+
+PARAMETERS:
+
+RETURN VALUE:
+===========================================================================*/
+static void __exit GobiExit( void )
+{
+   gpClose = NULL;
+   usb_deregister( &GobiDriver );
+   usb_serial_deregister( &gGobiDevice );
+}
+
+// Calling kernel module to init our driver
+module_init( GobiInit );
+module_exit( GobiExit );
+
+MODULE_VERSION( DRIVER_VERSION );
+MODULE_AUTHOR( DRIVER_AUTHOR );
+MODULE_DESCRIPTION( DRIVER_DESC );
+MODULE_LICENSE( "Dual BSD/GPL" );
+
+module_param( debug, bool, S_IRUGO | S_IWUSR );
+MODULE_PARM_DESC( debug, "Debug enabled or not" );
-- 
1.7.1

From 19da139b00caa50904c0094ec676fea6dce8430a Mon Sep 17 00:00:00 2001
From: Oliver Neukum <oliver@xxxxxxxxxx>
Date: Fri, 18 Mar 2011 10:53:13 +0100
Subject: [PATCH 2/4] Gobi3000: adjust to newest kernel

---
 drivers/net/usb/Kconfig     |    5 +++++
 drivers/net/usb/Makefile    |    3 ++-
 drivers/net/usb/QMIDevice.c |    8 +++-----
 drivers/net/usb/QMIDevice.h |    3 +--
 drivers/usb/serial/Kconfig  |    9 +++++++++
 drivers/usb/serial/Makefile |    1 +
 6 files changed, 21 insertions(+), 8 deletions(-)

diff --git a/drivers/net/usb/Kconfig b/drivers/net/usb/Kconfig
index 6f600cc..8b6d078 100644
--- a/drivers/net/usb/Kconfig
+++ b/drivers/net/usb/Kconfig
@@ -215,6 +215,11 @@ config USB_NET_CDC_NCM
 	    * ST-Ericsson M343 HSPA Mobile Broadband Modem (reference design)
 	    * Ericsson F5521gw Mobile Broadband Module
 
+config USB_NET_GOBI3000
+	tristate "Gobi 3000 support"
+	help
+	  This driver supports the Gobi3000 3G module
+
 config USB_NET_DM9601
 	tristate "Davicom DM9601 based USB 1.1 10/100 ethernet devices"
 	depends on USB_USBNET
diff --git a/drivers/net/usb/Makefile b/drivers/net/usb/Makefile
index cac1703..e09083a 100644
--- a/drivers/net/usb/Makefile
+++ b/drivers/net/usb/Makefile
@@ -1,6 +1,7 @@
 #
 # Makefile for USB Network drivers
 #
+GobiNet-objs := GobiUSBNet.o QMIDevice.o QMI.o
 
 obj-$(CONFIG_USB_CATC)		+= catc.o
 obj-$(CONFIG_USB_KAWETH)	+= kaweth.o
@@ -27,4 +28,4 @@ obj-$(CONFIG_USB_IPHETH)	+= ipheth.o
 obj-$(CONFIG_USB_SIERRA_NET)	+= sierra_net.o
 obj-$(CONFIG_USB_NET_CX82310_ETH)	+= cx82310_eth.o
 obj-$(CONFIG_USB_NET_CDC_NCM)	+= cdc_ncm.o
-
+obj-$(CONFIG_USB_NET_GOBI3000)	+= GobiNet.o
diff --git a/drivers/net/usb/QMIDevice.c b/drivers/net/usb/QMIDevice.c
index ff11456..f406374 100644
--- a/drivers/net/usb/QMIDevice.c
+++ b/drivers/net/usb/QMIDevice.c
@@ -152,7 +152,7 @@ struct file_operations UserspaceQMIFops =
    .owner     = THIS_MODULE,
    .read      = UserspaceRead,
    .write     = UserspaceWrite,
-   .ioctl     = UserspaceIOCTL,
+   .unlocked_ioctl     = UserspaceIOCTL,
    .open      = UserspaceOpen,
    .flush     = UserspaceClose,
 };
@@ -2108,7 +2108,6 @@ DESCRIPTION:
    Userspace IOCTL functions
 
 PARAMETERS
-   pUnusedInode [ I ] - (unused) kernel file descriptor
    pFilp        [ I ] - userspace file descriptor
    cmd          [ I ] - IOCTL command
    arg          [ I ] - IOCTL argument
@@ -2117,8 +2116,7 @@ RETURN VALUE:
    int - 0 for success
          Negative errno for failure
 ===========================================================================*/
-int UserspaceIOCTL( 
-   struct inode *    pUnusedInode, 
+long UserspaceIOCTL(
    struct file *     pFilp,
    unsigned int      cmd, 
    unsigned long     arg )
@@ -2258,7 +2256,7 @@ int UserspaceClose(
    }
 
    // Fallthough.  If f_count == 1 no need to do more checks
-   if (atomic_read( &pFilp->f_count ) != 1)
+   if ( atomic_long_read(&pFilp->f_count) != 1)
    {
       rcu_read_lock();
       for_each_process( pEachTask )
diff --git a/drivers/net/usb/QMIDevice.h b/drivers/net/usb/QMIDevice.h
index 176f080..67b262b 100644
--- a/drivers/net/usb/QMIDevice.h
+++ b/drivers/net/usb/QMIDevice.h
@@ -279,8 +279,7 @@ int UserspaceOpen(
    struct file *    pFilp );
 
 // Userspace ioctl
-int UserspaceIOCTL( 
-   struct inode *    pUnusedInode, 
+long UserspaceIOCTL( 
    struct file *     pFilp,
    unsigned int      cmd, 
    unsigned long     arg );
diff --git a/drivers/usb/serial/Kconfig b/drivers/usb/serial/Kconfig
index c2b2976..ba6b96d 100644
--- a/drivers/usb/serial/Kconfig
+++ b/drivers/usb/serial/Kconfig
@@ -660,6 +660,15 @@ config USB_SERIAL_SSU100
 	  To compile this driver as a module, choose M here: the
 	  module will be called ssu100.
 
+config CONFIG_USB_SERIAL_GOBI3000
+	tristate "Support for GPS on Gobi 3000 devices"
+	help
+	  Say y if you want to use the GPS reciever on the Gobi3000
+	  module.
+
+          To compile this driver as a module, choose M here: the
+          module will be called GobiSerial.
+
 config USB_SERIAL_DEBUG
 	tristate "USB Debugging Device"
 	help
diff --git a/drivers/usb/serial/Makefile b/drivers/usb/serial/Makefile
index 9a2117f..9ecdd79 100644
--- a/drivers/usb/serial/Makefile
+++ b/drivers/usb/serial/Makefile
@@ -26,6 +26,7 @@ obj-$(CONFIG_USB_SERIAL_EMPEG)			+= empeg.o
 obj-$(CONFIG_USB_SERIAL_FTDI_SIO)		+= ftdi_sio.o
 obj-$(CONFIG_USB_SERIAL_FUNSOFT)		+= funsoft.o
 obj-$(CONFIG_USB_SERIAL_GARMIN)			+= garmin_gps.o
+obj-$(CONFIG_USB_SERIAL_GOBI3000)		+= GobiSerial.o
 obj-$(CONFIG_USB_SERIAL_HP4X)			+= hp4x.o
 obj-$(CONFIG_USB_SERIAL_IPAQ)			+= ipaq.o
 obj-$(CONFIG_USB_SERIAL_IPW)			+= ipw.o
-- 
1.7.1

From 415e8ea7cba831dc1a40ba10760649e5bc3e38f6 Mon Sep 17 00:00:00 2001
From: Oliver Neukum <oliver@xxxxxxxxxx>
Date: Fri, 18 Mar 2011 14:13:45 +0100
Subject: [PATCH 3/4] Gobi3000: endianness, allocation checks, error returns

- memory allocations must always be checked
- endianness must be handled if data is going over the wire
- handle copy_to/from_user correctly
---
 drivers/net/usb/QMIDevice.c |   25 ++++++++++++++-----------
 1 files changed, 14 insertions(+), 11 deletions(-)

diff --git a/drivers/net/usb/QMIDevice.c b/drivers/net/usb/QMIDevice.c
index f406374..9317477 100644
--- a/drivers/net/usb/QMIDevice.c
+++ b/drivers/net/usb/QMIDevice.c
@@ -448,7 +448,7 @@ void ReadCallback( struct urb * pReadURB )
    }
    else
    {
-      transactionID = *(u16*)(pData + result + 1);
+      transactionID = le16_to_cpu(get_unaligned((u16*)(pData + result + 1)));
    }
    
    // Critical section
@@ -464,6 +464,8 @@ void ReadCallback( struct urb * pReadURB )
       {
          // Make copy of pData
          pDataCopy = kmalloc( dataSize, GFP_ATOMIC );
+	 if (!pDataCopy)
+		 goto skip_no_memory;
          memcpy( pDataCopy, pData, dataSize );
 
          if (AddToReadMemList( pDev,
@@ -472,10 +474,11 @@ void ReadCallback( struct urb * pReadURB )
                                pDataCopy,
                                dataSize ) == false)
          {
+            kfree( pDataCopy );
+
+skip_no_memory:
             DBG( "Error allocating pReadMemListEntry "
                  "read will be discarded\n" );
-            kfree( pDataCopy );
-            
             // End critical section
             spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
 
@@ -553,7 +556,7 @@ void IntCallback( struct urb * pIntURB )
    {
       // CDC GET_ENCAPSULATED_RESPONSE
       if ((pIntURB->actual_length == 8)
-      &&  (*(u64*)pIntURB->transfer_buffer == CDC_GET_ENCAPSULATED_RESPONSE))
+      &&  (*(u64*)pIntURB->transfer_buffer == cpu_to_le64(CDC_GET_ENCAPSULATED_RESPONSE)))
       {
          // Time to read
          usb_fill_control_urb( pDev->mQMIDev.mpReadURB,
@@ -575,7 +578,7 @@ void IntCallback( struct urb * pIntURB )
       }
       // CDC CONNECTION_SPEED_CHANGE
       else if ((pIntURB->actual_length == 16)
-      &&       (*(u64*)pIntURB->transfer_buffer == CDC_CONNECTION_SPEED_CHANGE))
+      &&       (*(u64*)pIntURB->transfer_buffer == cpu_to_le64(CDC_CONNECTION_SPEED_CHANGE)))
       {
          // if upstream or downstream is 0, stop traffic.  Otherwise resume it
          if ((*(u32*)(pIntURB->transfer_buffer + 8) == 0)
@@ -1124,7 +1127,7 @@ int WriteSync(
       return -EINVAL;
    }
 
-   result = usb_submit_urb( pWriteURB, GFP_KERNEL );
+   result = usb_submit_urb( pWriteURB, GFP_ATOMIC );
    if (result < 0)
    {
       DBG( "submit URB error %d\n", result );
@@ -2094,7 +2097,7 @@ int UserspaceOpen(
    }
    
    pFilpData = (sQMIFilpStorage *)pFilp->private_data;
-   pFilpData->mClientID = (u16)-1;
+   pFilpData->mClientID = (u16)0xffff;
    pFilpData->mpDev = pDev;
    
    return 0;
@@ -2151,7 +2154,7 @@ long UserspaceIOCTL(
          }
 
          // Connection is already setup
-         if (pFilpData->mClientID != (u16)-1)
+         if (pFilpData->mClientID != (u16)0xffff)
          {
             DBG( "Close the current connection before opening a new one\n" );
             return -EBADR;
@@ -2213,7 +2216,7 @@ long UserspaceIOCTL(
             DBG( "Copy to userspace failure %d\n", result );
          }
 
-         return result;
+         return result ? -EFAULT : 0;
                  
          break;
          
@@ -2304,7 +2307,7 @@ int UserspaceClose(
    // Note: memory pointer is still saved in pFilpData to be deleted later
    pFilp->private_data = NULL;
 
-   if (pFilpData->mClientID != (u16)-1)
+   if (pFilpData->mClientID != (u16)0xffff)
    {
       ReleaseClientID( pFilpData->mpDev,
                        pFilpData->mClientID );
@@ -2453,7 +2456,7 @@ ssize_t UserspaceWrite(
    {
       DBG( "Unable to copy data from userspace %d\n", status );
       kfree( pWriteBuffer );
-      return status;
+      return status ? -EFAULT : 0;
    }
 
    status = WriteSync( pFilpData->mpDev,
-- 
1.7.1

From 6c6860f116f6132f676db7377e11cdb95d4d9a75 Mon Sep 17 00:00:00 2001
From: Oliver Neukum <oliver@xxxxxxxxxx>
Date: Fri, 18 Mar 2011 15:11:33 +0100
Subject: [PATCH 4/4] Gobi3000: memory leaks

fix memory leaks in hard_xmit path
---
 drivers/net/usb/GobiUSBNet.c |    3 +++
 1 files changed, 3 insertions(+), 0 deletions(-)

diff --git a/drivers/net/usb/GobiUSBNet.c b/drivers/net/usb/GobiUSBNet.c
index 797bcee..d09c76d 100644
--- a/drivers/net/usb/GobiUSBNet.c
+++ b/drivers/net/usb/GobiUSBNet.c
@@ -736,6 +736,7 @@ int GobiUSBNetStartXmit(
    pURBListEntry->mpURB = usb_alloc_urb( 0, GFP_ATOMIC );
    if (pURBListEntry->mpURB == NULL)
    {
+		kfree(pURBListEntry);
       DBG( "unable to allocate URB\n" );
       return NETDEV_TX_BUSY;
    }
@@ -744,6 +745,8 @@ int GobiUSBNetStartXmit(
    pURBData = kmalloc( pSKB->len, GFP_ATOMIC );
    if (pURBData == NULL)
    {
+		usb_free_urb(pURBListEntry->mpURB);
+		kfree(pURBListEntry);
       DBG( "unable to allocate URB data\n" );
       return NETDEV_TX_BUSY;
    }
-- 
1.7.1


[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux