Re: Gobi 3000

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

 



Am Freitag, 18. März 2011, 21:12:59 schrieb Greg KH:

> But, I wouldn't mind that going into the staging tree for .40 so that
> people can clean it up there, want to do that?

Greg,

I doubt I can sign up on that stuff. I haven't written it.

	Regards
		Oliver
From b41bd7026f043771fa9b3181e0d5add57c565d8c Mon Sep 17 00:00:00 2001
From: Oliver Neukum <oliver@xxxxxxxxxx>
Date: Thu, 17 Mar 2011 17:48:39 +0100
Subject: [PATCH 1/5] 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 53b730f4705fe5cce06a48bb5099eb09c9810554 Mon Sep 17 00:00:00 2001
From: Oliver Neukum <oliver@xxxxxxxxxx>
Date: Fri, 18 Mar 2011 10:53:13 +0100
Subject: [PATCH 2/5] 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 b177faa1553ac6c4ec5d403de56924ff8d7207d6 Mon Sep 17 00:00:00 2001
From: Oliver Neukum <oliver@xxxxxxxxxx>
Date: Fri, 18 Mar 2011 14:13:45 +0100
Subject: [PATCH 3/5] 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 c7b45dacd56bb64df643cc32e8330d8aa82c0caa Mon Sep 17 00:00:00 2001
From: Oliver Neukum <oliver@xxxxxxxxxx>
Date: Fri, 18 Mar 2011 15:11:33 +0100
Subject: [PATCH 4/5] 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

From 1d06e473d86a1901738020c70176fcdce01edd1c Mon Sep 17 00:00:00 2001
From: Oliver Neukum <oliver@xxxxxxxxxx>
Date: Sat, 19 Mar 2011 00:31:44 +0100
Subject: [PATCH 5/5] Gobi 3000: deadlocks fixed

- no usb_kill_urb() with a spinlock held
- simplified locking
- GFP_NOIO in hard_xmit (cifs, nfs deadlock)
---
 drivers/net/usb/GobiUSBNet.c |   62 +++++++++++++++++++++++-------------------
 1 files changed, 34 insertions(+), 28 deletions(-)

diff --git a/drivers/net/usb/GobiUSBNet.c b/drivers/net/usb/GobiUSBNet.c
index d09c76d..0738ac2 100644
--- a/drivers/net/usb/GobiUSBNet.c
+++ b/drivers/net/usb/GobiUSBNet.c
@@ -458,8 +458,9 @@ void GobiUSBNetTXTimeout( struct net_device * pNet )
    struct sGobiUSBNet * pGobiDev;
    sAutoPM * pAutoPM;
    sURBList * pURBListEntry;
-   unsigned long activeURBflags, URBListFlags;
+   unsigned long flags;
    struct usbnet * pDev = netdev_priv( pNet );
+   int res;
 
    if (pDev == NULL || pDev->net == NULL)
    {
@@ -478,17 +479,22 @@ void GobiUSBNetTXTimeout( struct net_device * pNet )
    DBG( "\n" );
 
    // Stop activeURB
-   spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags );
+   spin_lock_irqsave( &pAutoPM->mActiveURBLock, flags );
 
    if (pAutoPM->mpActiveURB != NULL)
    {
-      usb_kill_urb( pAutoPM->mpActiveURB );
+      res = usb_unlink_urb( pAutoPM->mpActiveURB );
    }
 
-   spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags );
+   spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, flags );
+
+
+	/* in this case it already finished */
+	if (res != -EINPROGRESS)
+		return;
 
    // Cleanup URB List
-   spin_lock_irqsave( &pAutoPM->mURBListLock, URBListFlags );
+   spin_lock_irqsave( &pAutoPM->mURBListLock, flags );
 
    pURBListEntry = pAutoPM->mpURBList;
    while (pURBListEntry != NULL)
@@ -499,7 +505,7 @@ void GobiUSBNetTXTimeout( struct net_device * pNet )
       pURBListEntry = pAutoPM->mpURBList;
    }
 
-   spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags );
+   spin_unlock_irqrestore( &pAutoPM->mURBListLock, flags );
 
    complete( &pAutoPM->mThreadDoWork );
 
@@ -523,7 +529,7 @@ RETURN VALUE:
 ===========================================================================*/
 static int GobiUSBNetAutoPMThread( void * pData )
 {
-   unsigned long activeURBflags, URBListFlags;
+   unsigned long flags;
    sURBList * pURBListEntry;
    int status;
    struct usb_device * pUdev;
@@ -547,18 +553,18 @@ static int GobiUSBNetAutoPMThread( void * pData )
       if (pAutoPM->mbExit == true)
       {
          // Stop activeURB
-         spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags );
+         spin_lock_irqsave( &pAutoPM->mActiveURBLock, flags );
 
          if (pAutoPM->mpActiveURB != NULL)
          {
-            usb_kill_urb( pAutoPM->mpActiveURB );
+            usb_unlink_urb( pAutoPM->mpActiveURB );
          }
          // Will be freed in callback function
 
-         spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags );
+         spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, flags );
 
          // Cleanup URB List
-         spin_lock_irqsave( &pAutoPM->mURBListLock, URBListFlags );
+         spin_lock_irqsave( &pAutoPM->mURBListLock, flags );
 
          pURBListEntry = pAutoPM->mpURBList;
          while (pURBListEntry != NULL)
@@ -569,13 +575,13 @@ static int GobiUSBNetAutoPMThread( void * pData )
             pURBListEntry = pAutoPM->mpURBList;
          }
 
-         spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags );
+         spin_unlock_irqrestore( &pAutoPM->mURBListLock, flags );
 
          break;
       }
       
       // Is our URB active?
-      spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags );
+      spin_lock_irqsave( &pAutoPM->mActiveURBLock, flags );
 
       // EAGAIN used to signify callback is done
       if (IS_ERR( pAutoPM->mpActiveURB ) 
@@ -584,40 +590,40 @@ static int GobiUSBNetAutoPMThread( void * pData )
          pAutoPM->mpActiveURB = NULL;
 
          // Restore IRQs so task can sleep
-         spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags );
+         spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, flags );
          
          // URB is done, decrement the Auto PM usage count
          usb_autopm_put_interface( pAutoPM->mpIntf );
 
          // Lock ActiveURB again
-         spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags );
+         spin_lock_irqsave( &pAutoPM->mActiveURBLock, flags );
       }
 
       if (pAutoPM->mpActiveURB != NULL)
       {
          // There is already a URB active, go back to sleep
-         spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags );
+         spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, flags );
          continue;
       }
       
       // Is there a URB waiting to be submitted?
-      spin_lock_irqsave( &pAutoPM->mURBListLock, URBListFlags );
+      spin_lock( &pAutoPM->mURBListLock);
       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 );
+         spin_unlock( &pAutoPM->mURBListLock);
+         spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, flags );
          continue;
       }
 
       // Pop an element
       pURBListEntry = pAutoPM->mpURBList;
       pAutoPM->mpURBList = pAutoPM->mpURBList->mpNext;
-      spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags );
+      spin_unlock( &pAutoPM->mURBListLock);
 
       // Set ActiveURB
       pAutoPM->mpActiveURB = pURBListEntry->mpURB;
-      spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags );
+      spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, flags );
 
       // Tell autopm core we need device woken up
       status = usb_autopm_get_interface( pAutoPM->mpIntf );
@@ -635,29 +641,29 @@ static int GobiUSBNetAutoPMThread( void * pData )
          }
 
          // Add pURBListEntry back onto pAutoPM->mpURBList
-         spin_lock_irqsave( &pAutoPM->mURBListLock, URBListFlags );
+         spin_lock_irqsave( &pAutoPM->mURBListLock, flags );
          pURBListEntry->mpNext = pAutoPM->mpURBList;
          pAutoPM->mpURBList = pURBListEntry;
-         spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags );
+         spin_unlock_irqrestore( &pAutoPM->mURBListLock, flags );
          
-         spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags );
+         spin_lock_irqsave( &pAutoPM->mActiveURBLock, flags );
          pAutoPM->mpActiveURB = NULL;
-         spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags );
+         spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, flags );
          
          // Go back to sleep
          continue;
       }
 
       // Submit URB
-      status = usb_submit_urb( pAutoPM->mpActiveURB, GFP_KERNEL );
+      status = usb_submit_urb( pAutoPM->mpActiveURB, GFP_NOIO );
       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 );
+         spin_lock_irqsave( &pAutoPM->mActiveURBLock, flags );
          usb_free_urb( pAutoPM->mpActiveURB );
          pAutoPM->mpActiveURB = NULL;
-         spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags );
+         spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, flags );
          usb_autopm_put_interface( pAutoPM->mpIntf );
 
          // Loop again
-- 
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