aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDeepa Dinamani <deepadinamani@codeaurora.org>2010-09-13 19:20:35 (GMT)
committerDeepa Dinamani <deepadinamani@codeaurora.org>2010-09-22 16:43:17 (GMT)
commitb5135a880f8942f990e8c2e383f7f876beacc55b (patch)
treed3f29859553976f558159cfe12168a26af21a98f
parentccebe9c2c61289099801957472cac0b14dae2f1c (diff)
staging: gobi: Add Qualcomm Gobi Chipset code.
The driver exposes a usb network interface and a character device interface. Change-Id: I9f6ff30675b505cde11fbcae7d577432cb7e256a Signed-off-by: Deepa Dinamani <deepadinamani@codeaurora.org>
-rw-r--r--drivers/staging/Kconfig2
-rw-r--r--drivers/staging/Makefile1
-rw-r--r--drivers/staging/gobi/Kconfig5
-rwxr-xr-xdrivers/staging/gobi/QCUSBNet2k/Makefile2
-rw-r--r--drivers/staging/gobi/QCUSBNet2k/QCUSBNet.c1227
-rw-r--r--drivers/staging/gobi/QCUSBNet2k/QMI.c954
-rw-r--r--drivers/staging/gobi/QCUSBNet2k/QMI.h251
-rw-r--r--drivers/staging/gobi/QCUSBNet2k/QMIDevice.c3129
-rw-r--r--drivers/staging/gobi/QCUSBNet2k/QMIDevice.h297
-rw-r--r--drivers/staging/gobi/QCUSBNet2k/Structs.h318
10 files changed, 6186 insertions, 0 deletions
diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig
index fb3c15a..d238a17 100644
--- a/drivers/staging/Kconfig
+++ b/drivers/staging/Kconfig
@@ -147,5 +147,7 @@ source "drivers/staging/xgifb/Kconfig"
source "drivers/staging/mrst-touchscreen/Kconfig"
+source "drivers/staging/gobi/Kconfig"
+
endif # !STAGING_EXCLUDE_BUILD
endif # STAGING
diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile
index 4baec4c..6189417 100644
--- a/drivers/staging/Makefile
+++ b/drivers/staging/Makefile
@@ -55,3 +55,4 @@ obj-$(CONFIG_ADIS16255) += adis16255/
obj-$(CONFIG_FB_XGI) += xgifb/
obj-$(CONFIG_TOUCHSCREEN_MRSTOUCH) += mrst-touchscreen/
obj-$(CONFIG_MSM_STAGING) += msm/
+obj-$(CONFIG_GOBI_USBNET) += gobi/QCUSBNet2k/
diff --git a/drivers/staging/gobi/Kconfig b/drivers/staging/gobi/Kconfig
new file mode 100644
index 0000000..99d3b8d
--- /dev/null
+++ b/drivers/staging/gobi/Kconfig
@@ -0,0 +1,5 @@
+config GOBI_USBNET
+ tristate "Qualcomm GOBI2k and QCQMI support"
+ help
+ This module adds network device support for GOBI2k 3G radios.
+~
diff --git a/drivers/staging/gobi/QCUSBNet2k/Makefile b/drivers/staging/gobi/QCUSBNet2k/Makefile
new file mode 100755
index 0000000..66c1590
--- /dev/null
+++ b/drivers/staging/gobi/QCUSBNet2k/Makefile
@@ -0,0 +1,2 @@
+obj-$(CONFIG_GOBI_USBNET) += QCUSBNet2k.o
+QCUSBNet2k-objs += QCUSBNet.o QMIDevice.o QMI.o
diff --git a/drivers/staging/gobi/QCUSBNet2k/QCUSBNet.c b/drivers/staging/gobi/QCUSBNet2k/QCUSBNet.c
new file mode 100644
index 0000000..e7f72e7
--- /dev/null
+++ b/drivers/staging/gobi/QCUSBNet2k/QCUSBNet.c
@@ -0,0 +1,1227 @@
+/*===========================================================================
+FILE:
+ QCUSBNet.c
+
+DESCRIPTION:
+ Qualcomm USB Network device for Gobi 2000
+
+FUNCTIONS:
+ QCSuspend
+ QCResume
+ QCNetDriverBind
+ QCNetDriverUnbind
+ QCUSBNetURBCallback
+ QCUSBNetTXTimeout
+ QCUSBNetAutoPMThread
+ QCUSBNetStartXmit
+ QCUSBNetOpen
+ QCUSBNetStop
+ QCUSBNetProbe
+ QCUSBNetModInit
+ QCUSBNetModExit
+
+Copyright (c) 2010, Code Aurora Forum. All rights reserved.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2 and
+only version 2 as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301, USA.
+
+===========================================================================*/
+
+//---------------------------------------------------------------------------
+// Include Files
+//---------------------------------------------------------------------------
+
+#include "Structs.h"
+#include "QMIDevice.h"
+#include "QMI.h"
+
+//-----------------------------------------------------------------------------
+// Definitions
+//-----------------------------------------------------------------------------
+
+// Version Information
+#define DRIVER_VERSION "1.0.110"
+#define DRIVER_DESC "QCUSBNet2k"
+
+// Debug flag
+int debug;
+
+// Class should be created during module init, so needs to be global
+static struct class * gpClass;
+
+/*===========================================================================
+METHOD:
+ QCSuspend (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 QCSuspend(
+ struct usb_interface * pIntf,
+ pm_message_t powerEvent )
+{
+ struct usbnet * pDev;
+ sQCUSBNet * pQCDev;
+
+ 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;
+ }
+
+ pQCDev = (sQCUSBNet *)pDev->data[0];
+ if (pQCDev == 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 );
+ QSetDownReason( pQCDev, DRIVER_SUSPENDED );
+ }
+ else
+ {
+ DBG( "device autosuspend\n" );
+ }
+
+ if (powerEvent.event & PM_EVENT_SUSPEND)
+ {
+ // Stop QMI read callbacks
+ KillRead( pQCDev );
+ 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:
+ QCResume (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 QCResume( struct usb_interface * pIntf )
+{
+ struct usbnet * pDev;
+ sQCUSBNet * pQCDev;
+ 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;
+ }
+
+ pQCDev = (sQCUSBNet *)pDev->data[0];
+ if (pQCDev == 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
+ QClearDownReason( pQCDev, DRIVER_SUSPENDED );
+
+ nRet = usbnet_resume( pIntf );
+ if (nRet != 0)
+ {
+ DBG( "usbnet_resume error %d\n", nRet );
+ return nRet;
+ }
+
+ // Restart QMI read callbacks
+ nRet = StartRead( pQCDev );
+ if (nRet != 0)
+ {
+ DBG( "StartRead error %d\n", nRet );
+ return nRet;
+ }
+
+ // Kick Auto PM thread to process any queued URBs
+ up( &pQCDev->mAutoPM.mThreadDoWork );
+ }
+ else
+ {
+ DBG( "nothing to resume\n" );
+ return 0;
+ }
+
+ return nRet;
+}
+
+/*===========================================================================
+METHOD:
+ QCNetDriverBind (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 QCNetDriverBind(
+ 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 -EINVAL;
+ }
+
+ // Verify correct interface (0)
+ if (pIntf->cur_altsetting->desc.bInterfaceNumber != 0)
+ {
+ DBG( "invalid interface %d\n",
+ pIntf->cur_altsetting->desc.bInterfaceNumber );
+ return -EINVAL;
+ }
+
+ // 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 -EINVAL;
+ }
+
+ 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 -EINVAL;
+ }
+
+ if (usb_set_interface( pDev->udev,
+ pIntf->cur_altsetting->desc.bInterfaceNumber,
+ 0 ) != 0)
+ {
+ DBG( "unable to set interface\n" );
+ return -EINVAL;
+ }
+
+ 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:
+ QCNetDriverUnbind (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 QCNetDriverUnbind(
+ struct usbnet * pDev,
+ struct usb_interface * pIntf)
+{
+ sQCUSBNet * pQCDev = (sQCUSBNet *)pDev->data[0];
+
+ // Should already be down, but just in case...
+ netif_carrier_off( pDev->net );
+
+ DeregisterQMIDevice( pQCDev );
+
+#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( pQCDev );
+ pQCDev = NULL;
+}
+
+/*===========================================================================
+METHOD:
+ QCUSBNetURBCallback (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 QCUSBNetURBCallback( 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 );
+
+ up( &pAutoPM->mThreadDoWork );
+
+ usb_free_urb( pURB );
+}
+
+/*===========================================================================
+METHOD:
+ QCUSBNetTXTimeout (Public Method)
+
+DESCRIPTION:
+ Timeout declared by the net driver. Stop all transfers
+
+PARAMETERS
+ pNet [ I ] - Pointer to net device
+
+RETURN VALUE:
+ None
+===========================================================================*/
+void QCUSBNetTXTimeout( struct net_device * pNet )
+{
+ struct sQCUSBNet * pQCDev;
+ 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;
+ }
+
+ pQCDev = (sQCUSBNet *)pDev->data[0];
+ if (pQCDev == NULL)
+ {
+ DBG( "failed to get QMIDevice\n" );
+ return;
+ }
+ pAutoPM = &pQCDev->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 );
+
+ up( &pAutoPM->mThreadDoWork );
+
+ return;
+}
+
+/*===========================================================================
+METHOD:
+ QCUSBNetAutoPMThread (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 QCUSBNetAutoPMThread( 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
+ down( &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
+ QCSuspend( 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
+ up( &pAutoPM->mThreadDoWork );
+ }
+
+ kfree( pURBListEntry );
+ }
+
+ DBG( "traffic thread exiting\n" );
+ pAutoPM->mpThread = NULL;
+ return 0;
+}
+
+/*===========================================================================
+METHOD:
+ QCUSBNetStartXmit (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 QCUSBNetStartXmit(
+ struct sk_buff * pSKB,
+ struct net_device * pNet )
+{
+ unsigned long URBListFlags;
+ struct sQCUSBNet * pQCDev;
+ 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;
+ }
+
+ pQCDev = (sQCUSBNet *)pDev->data[0];
+ if (pQCDev == NULL)
+ {
+ DBG( "failed to get QMIDevice\n" );
+ return NETDEV_TX_BUSY;
+ }
+ pAutoPM = &pQCDev->mAutoPM;
+
+ if (QTestDownReason( pQCDev, 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,
+ pQCDev->mpNetDev->udev,
+ pQCDev->mpNetDev->out,
+ pURBData,
+ pSKB->len,
+ QCUSBNetURBCallback,
+ 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 );
+
+ up( &pAutoPM->mThreadDoWork );
+
+ // Start transfer timer
+ pNet->trans_start = jiffies;
+ // Free SKB
+ dev_kfree_skb_any( pSKB );
+
+ return NETDEV_TX_OK;
+}
+
+/*===========================================================================
+METHOD:
+ QCUSBNetOpen (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 QCUSBNetOpen( struct net_device * pNet )
+{
+ int status = 0;
+ struct sQCUSBNet * pQCDev;
+ struct usbnet * pDev = netdev_priv( pNet );
+
+ if (pDev == NULL)
+ {
+ DBG( "failed to get usbnet device\n" );
+ return -ENXIO;
+ }
+
+ pQCDev = (sQCUSBNet *)pDev->data[0];
+ if (pQCDev == NULL)
+ {
+ DBG( "failed to get QMIDevice\n" );
+ return -ENXIO;
+ }
+
+ DBG( "\n" );
+
+ // Start the AutoPM thread
+ pQCDev->mAutoPM.mpIntf = pQCDev->mpIntf;
+ pQCDev->mAutoPM.mbExit = false;
+ pQCDev->mAutoPM.mpURBList = NULL;
+ pQCDev->mAutoPM.mpActiveURB = NULL;
+ spin_lock_init( &pQCDev->mAutoPM.mURBListLock );
+ spin_lock_init( &pQCDev->mAutoPM.mActiveURBLock );
+ sema_init( &pQCDev->mAutoPM.mThreadDoWork, 0 );
+
+ pQCDev->mAutoPM.mpThread = kthread_run( QCUSBNetAutoPMThread,
+ &pQCDev->mAutoPM,
+ "QCUSBNetAutoPMThread" );
+ if (IS_ERR( pQCDev->mAutoPM.mpThread ))
+ {
+ DBG( "AutoPM thread creation error\n" );
+ return PTR_ERR( pQCDev->mAutoPM.mpThread );
+ }
+
+ // Allow traffic
+ QClearDownReason( pQCDev, NET_IFACE_STOPPED );
+
+ // Pass to usbnet_open if defined
+ if (pQCDev->mpUSBNetOpen != NULL)
+ {
+ status = pQCDev->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( pQCDev->mpIntf );
+#else
+ usb_autopm_put_interface( pQCDev->mpIntf );
+#endif
+ }
+ }
+ else
+ {
+ DBG( "no USBNetOpen defined\n" );
+ }
+
+ return status;
+}
+
+/*===========================================================================
+METHOD:
+ QCUSBNetStop (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 QCUSBNetStop( struct net_device * pNet )
+{
+ struct sQCUSBNet * pQCDev;
+ struct usbnet * pDev = netdev_priv( pNet );
+
+ if (pDev == NULL || pDev->net == NULL)
+ {
+ DBG( "failed to get netdevice\n" );
+ return -ENXIO;
+ }
+
+ pQCDev = (sQCUSBNet *)pDev->data[0];
+ if (pQCDev == NULL)
+ {
+ DBG( "failed to get QMIDevice\n" );
+ return -ENXIO;
+ }
+
+ // Stop traffic
+ QSetDownReason( pQCDev, NET_IFACE_STOPPED );
+
+ // Tell traffic thread to exit
+ pQCDev->mAutoPM.mbExit = true;
+ up( &pQCDev->mAutoPM.mThreadDoWork );
+
+ // Wait for it to exit
+ while( pQCDev->mAutoPM.mpThread != NULL )
+ {
+ msleep( 100 );
+ }
+ DBG( "thread stopped\n" );
+
+ // Pass to usbnet_stop, if defined
+ if (pQCDev->mpUSBNetStop != NULL)
+ {
+ return pQCDev->mpUSBNetStop( pNet );
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+/*=========================================================================*/
+// Struct driver_info
+/*=========================================================================*/
+static const struct driver_info QCNetInfo =
+{
+ .description = "QCUSBNet Ethernet Device",
+ .flags = FLAG_ETHER,
+ .bind = QCNetDriverBind,
+ .unbind = QCNetDriverUnbind,
+ .data = 0,
+};
+
+/*=========================================================================*/
+// Qualcomm Gobi 2000 VID/PIDs
+/*=========================================================================*/
+static const struct usb_device_id QCVIDPIDTable [] =
+{
+ // Acer Gobi 2000
+ {
+ USB_DEVICE( 0x05c6, 0x9215 ),
+ .driver_info = (unsigned long)&QCNetInfo
+ },
+ // Asus Gobi 2000
+ {
+ USB_DEVICE( 0x05c6, 0x9265 ),
+ .driver_info = (unsigned long)&QCNetInfo
+ },
+ // CMOTech Gobi 2000
+ {
+ USB_DEVICE( 0x16d8, 0x8002 ),
+ .driver_info = (unsigned long)&QCNetInfo
+ },
+ // Dell Gobi 2000
+ {
+ USB_DEVICE( 0x413c, 0x8186 ),
+ .driver_info = (unsigned long)&QCNetInfo
+ },
+ // Entourage Gobi 2000
+ {
+ USB_DEVICE( 0x1410, 0xa010 ),
+ .driver_info = (unsigned long)&QCNetInfo
+ },
+ // Entourage Gobi 2000
+ {
+ USB_DEVICE( 0x1410, 0xa011 ),
+ .driver_info = (unsigned long)&QCNetInfo
+ },
+ // Entourage Gobi 2000
+ {
+ USB_DEVICE( 0x1410, 0xa012 ),
+ .driver_info = (unsigned long)&QCNetInfo
+ },
+ // Entourage Gobi 2000
+ {
+ USB_DEVICE( 0x1410, 0xa013 ),
+ .driver_info = (unsigned long)&QCNetInfo
+ },
+ // HP Gobi 2000
+ {
+ USB_DEVICE( 0x03f0, 0x251d ),
+ .driver_info = (unsigned long)&QCNetInfo
+ },
+ // Lenovo Gobi 2000
+ {
+ USB_DEVICE( 0x05c6, 0x9205 ),
+ .driver_info = (unsigned long)&QCNetInfo
+ },
+ // Panasonic Gobi 2000
+ {
+ USB_DEVICE( 0x04da, 0x250f ),
+ .driver_info = (unsigned long)&QCNetInfo
+ },
+ // Samsung Gobi 2000
+ {
+ USB_DEVICE( 0x05c6, 0x9245 ),
+ .driver_info = (unsigned long)&QCNetInfo
+ },
+ // Sierra Wireless Gobi 2000
+ {
+ USB_DEVICE( 0x1199, 0x9001 ),
+ .driver_info = (unsigned long)&QCNetInfo
+ },
+ // Sierra Wireless Gobi 2000
+ {
+ USB_DEVICE( 0x1199, 0x9002 ),
+ .driver_info = (unsigned long)&QCNetInfo
+ },
+ // Sierra Wireless Gobi 2000
+ {
+ USB_DEVICE( 0x1199, 0x9003 ),
+ .driver_info = (unsigned long)&QCNetInfo
+ },
+ // Sierra Wireless Gobi 2000
+ {
+ USB_DEVICE( 0x1199, 0x9004 ),
+ .driver_info = (unsigned long)&QCNetInfo
+ },
+ // Sierra Wireless Gobi 2000
+ {
+ USB_DEVICE( 0x1199, 0x9005 ),
+ .driver_info = (unsigned long)&QCNetInfo
+ },
+ // Sierra Wireless Gobi 2000
+ {
+ USB_DEVICE( 0x1199, 0x9006 ),
+ .driver_info = (unsigned long)&QCNetInfo
+ },
+ // Sierra Wireless Gobi 2000
+ {
+ USB_DEVICE( 0x1199, 0x9007 ),
+ .driver_info = (unsigned long)&QCNetInfo
+ },
+ // Sierra Wireless Gobi 2000
+ {
+ USB_DEVICE( 0x1199, 0x9008 ),
+ .driver_info = (unsigned long)&QCNetInfo
+ },
+ // Sierra Wireless Gobi 2000
+ {
+ USB_DEVICE( 0x1199, 0x9009 ),
+ .driver_info = (unsigned long)&QCNetInfo
+ },
+ // Sierra Wireless Gobi 2000
+ {
+ USB_DEVICE( 0x1199, 0x900a ),
+ .driver_info = (unsigned long)&QCNetInfo
+ },
+ // Sony Gobi 2000
+ {
+ USB_DEVICE( 0x05c6, 0x9225 ),
+ .driver_info = (unsigned long)&QCNetInfo
+ },
+ // Top Global Gobi 2000
+ {
+ USB_DEVICE( 0x05c6, 0x9235 ),
+ .driver_info = (unsigned long)&QCNetInfo
+ },
+ // iRex Technologies Gobi 2000
+ {
+ USB_DEVICE( 0x05c6, 0x9275 ),
+ .driver_info = (unsigned long)&QCNetInfo
+ },
+ // Generic Gobi 2000
+ {
+ USB_DEVICE( 0x5c6, 0x920B ),
+ .driver_info = (unsigned long)&QCNetInfo
+ },
+
+ //Terminating entry
+ { }
+};
+
+MODULE_DEVICE_TABLE( usb, QCVIDPIDTable );
+
+/*===========================================================================
+METHOD:
+ QCUSBNetProbe (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 QCUSBNetProbe(
+ struct usb_interface * pIntf,
+ const struct usb_device_id * pVIDPIDs )
+{
+ int status;
+ struct usbnet * pDev;
+ sQCUSBNet * pQCDev;
+#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" );
+ return -ENXIO;
+ }
+
+ pQCDev = kmalloc( sizeof( sQCUSBNet ), GFP_KERNEL );
+ if (pQCDev == NULL)
+ {
+ DBG( "falied to allocate device buffers" );
+ return -ENOMEM;
+ }
+
+ pDev->data[0] = (unsigned long)pQCDev;
+
+ pQCDev->mpNetDev = pDev;
+
+ // Overload PM related network functions
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 ))
+ pQCDev->mpUSBNetOpen = pDev->net->open;
+ pDev->net->open = QCUSBNetOpen;
+ pQCDev->mpUSBNetStop = pDev->net->stop;
+ pDev->net->stop = QCUSBNetStop;
+ pDev->net->hard_start_xmit = QCUSBNetStartXmit;
+ pDev->net->tx_timeout = QCUSBNetTXTimeout;
+#else
+ pNetDevOps = kmalloc( sizeof( struct net_device_ops ), GFP_KERNEL );
+ if (pNetDevOps == NULL)
+ {
+ DBG( "falied to allocate net device ops" );
+ return -ENOMEM;
+ }
+ memcpy( pNetDevOps, pDev->net->netdev_ops, sizeof( struct net_device_ops ) );
+
+ pQCDev->mpUSBNetOpen = pNetDevOps->ndo_open;
+ pNetDevOps->ndo_open = QCUSBNetOpen;
+ pQCDev->mpUSBNetStop = pNetDevOps->ndo_stop;
+ pNetDevOps->ndo_stop = QCUSBNetStop;
+ pNetDevOps->ndo_start_xmit = QCUSBNetStartXmit;
+ pNetDevOps->ndo_tx_timeout = QCUSBNetTXTimeout;
+
+ pDev->net->netdev_ops = pNetDevOps;
+#endif
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,31 ))
+ memset( &(pQCDev->mpNetDev->stats), 0, sizeof( struct net_device_stats ) );
+#else
+ memset( &(pQCDev->mpNetDev->net->stats), 0, sizeof( struct net_device_stats ) );
+#endif
+
+ pQCDev->mpIntf = pIntf;
+ memset( &(pQCDev->mMEID), '0', 14 );
+
+ DBG( "Mac Address:\n" );
+ PrintHex( &pQCDev->mpNetDev->net->dev_addr[0], 6 );
+
+ pQCDev->mbQMIValid = false;
+ memset( &pQCDev->mQMIDev, 0, sizeof( sQMIDev ) );
+
+ pQCDev->mQMIDev.mpDevClass = gpClass;
+
+ sema_init( &pQCDev->mAutoPM.mThreadDoWork, 0 );
+ spin_lock_init( &pQCDev->mQMIDev.mClientMemLock );
+
+ // Default to device down
+ pQCDev->mDownReason = 0;
+ QSetDownReason( pQCDev, NO_NDIS_CONNECTION );
+ QSetDownReason( pQCDev, NET_IFACE_STOPPED );
+
+ // Register QMI
+ status = RegisterQMIDevice( pQCDev );
+ if (status != 0)
+ {
+ // Clean up
+ DeregisterQMIDevice( pQCDev );
+ return status;
+ }
+
+ // Success
+ return status;
+}
+
+EXPORT_SYMBOL_GPL( QCUSBNetProbe );
+
+static struct usb_driver QCUSBNet =
+{
+ .name = "QCUSBNet2k",
+ .id_table = QCVIDPIDTable,
+ .probe = QCUSBNetProbe,
+ .disconnect = usbnet_disconnect,
+ .suspend = QCSuspend,
+ .resume = QCResume,
+ .supports_autosuspend = true,
+};
+
+/*===========================================================================
+METHOD:
+ QCUSBNetModInit (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 QCUSBNetModInit( void )
+{
+ gpClass = class_create( THIS_MODULE, "QCQMI" );
+ 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( &QCUSBNet );
+}
+module_init( QCUSBNetModInit );
+
+/*===========================================================================
+METHOD:
+ QCUSBNetModExit (Public Method)
+
+DESCRIPTION:
+ Deregister module
+ Destroy device class
+
+RETURN VALUE:
+ void
+===========================================================================*/
+static void __exit QCUSBNetModExit( void )
+{
+ usb_deregister( &QCUSBNet );
+
+ class_destroy( gpClass );
+}
+module_exit( QCUSBNetModExit );
+
+#ifdef bool
+#undef bool
+#endif
+
+MODULE_VERSION( DRIVER_VERSION );
+MODULE_DESCRIPTION( DRIVER_DESC );
+MODULE_LICENSE( "GPL v2" );
+
+module_param( debug, bool, S_IRUGO | S_IWUSR );
+MODULE_PARM_DESC( debug, "Debuging enabled or not" );
+
diff --git a/drivers/staging/gobi/QCUSBNet2k/QMI.c b/drivers/staging/gobi/QCUSBNet2k/QMI.c
new file mode 100644
index 0000000..fe7eebe
--- /dev/null
+++ b/drivers/staging/gobi/QCUSBNet2k/QMI.c
@@ -0,0 +1,954 @@
+/*===========================================================================
+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) 2010, Code Aurora Forum. All rights reserved.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2 and
+only version 2 as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301, USA.
+
+===========================================================================*/
+
+//---------------------------------------------------------------------------
+// Include 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/staging/gobi/QCUSBNet2k/QMI.h b/drivers/staging/gobi/QCUSBNet2k/QMI.h
new file mode 100644
index 0000000..4da1285
--- /dev/null
+++ b/drivers/staging/gobi/QCUSBNet2k/QMI.h
@@ -0,0 +1,251 @@
+/*===========================================================================
+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) 2010, Code Aurora Forum. All rights reserved.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2 and
+only version 2 as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301, USA.
+
+===========================================================================*/
+
+#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/staging/gobi/QCUSBNet2k/QMIDevice.c b/drivers/staging/gobi/QCUSBNet2k/QMIDevice.c
new file mode 100644
index 0000000..668328c
--- /dev/null
+++ b/drivers/staging/gobi/QCUSBNet2k/QMIDevice.c
@@ -0,0 +1,3129 @@
+/*===========================================================================
+FILE:
+ QMIDevice.c
+
+DESCRIPTION:
+ Functions related to the QMI interface device
+
+FUNCTIONS:
+ Generic functions
+ IsDeviceValid
+ PrintHex
+ QSetDownReason
+ QClearDownReason
+ QTestDownReason
+
+ Driver level asynchronous read functions
+ 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) 2010, Code Aurora Forum. All rights reserved.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2 and
+only version 2 as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301, USA.
+
+===========================================================================*/
+
+//---------------------------------------------------------------------------
+// Include Files
+//---------------------------------------------------------------------------
+#include "QMIDevice.h"
+
+//-----------------------------------------------------------------------------
+// Definitions
+//-----------------------------------------------------------------------------
+
+extern int debug;
+
+// Prototype to QCSuspend function
+int QCSuspend(
+ 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( sQCUSBNet * 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 );
+ return;
+ }
+ }
+
+ DBG( " : %s\n", pPrintBuf );
+
+ kfree( pPrintBuf );
+ pPrintBuf = NULL;
+ return;
+}
+
+/*===========================================================================
+METHOD:
+ QSetDownReason (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 QSetDownReason(
+ sQCUSBNet * pDev,
+ u8 reason )
+{
+ set_bit( reason, &pDev->mDownReason );
+
+ netif_carrier_off( pDev->mpNetDev->net );
+}
+
+/*===========================================================================
+METHOD:
+ QClearDownReason (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 QClearDownReason(
+ sQCUSBNet * pDev,
+ u8 reason )
+{
+ clear_bit( reason, &pDev->mDownReason );
+
+ if (pDev->mDownReason == 0)
+ {
+ netif_carrier_on( pDev->mpNetDev->net );
+ }
+}
+
+/*===========================================================================
+METHOD:
+ QTestDownReason (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 QTestDownReason(
+ sQCUSBNet * pDev,
+ u8 reason )
+{
+ return test_bit( reason, &pDev->mDownReason );
+}
+
+/*=========================================================================*/
+// Driver level asynchronous read functions
+/*=========================================================================*/
+
+/*===========================================================================
+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;
+ sQCUSBNet * 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 );
+ 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 );
+ return;
+ }
+
+ // Grab transaction ID
+
+ // Data large enough?
+ if (dataSize < result + 3)
+ {
+ DBG( "Data buffer too small to parse\n" );
+ 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 );
+ 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 );
+}
+
+/*===========================================================================
+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;
+ int interval;
+
+ sQCUSBNet * pDev = (sQCUSBNet *)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 );
+ 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))
+ {
+ QSetDownReason( pDev, CDC_CONNECTION_SPEED );
+ DBG( "traffic stopping due to CONNECTION_SPEED_CHANGE\n" );
+ }
+ else
+ {
+ QClearDownReason( 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 );
+ }
+ }
+
+ interval = (pDev->mpNetDev->udev->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;
+}
+
+/*===========================================================================
+METHOD:
+ StartRead (Public Method)
+
+DESCRIPTION:
+ Start continuous read "thread" (callback driven)
+
+PARAMETERS:
+ pDev [ I ] - Device specific memory
+
+RETURN VALUE:
+ int - 0 for success
+ negative errno for failure
+===========================================================================*/
+int StartRead( sQCUSBNet * 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( sQCUSBNet * 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(
+ sQCUSBNet * pDev,
+ u16 clientID,
+ u16 transactionID,
+ void (*pCallback)(sQCUSBNet*, 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(
+ sQCUSBNet * 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(
+ sQCUSBNet * 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(
+ sQCUSBNet * 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
+ QCSuspend( pDev->mpIntf, PMSG_SUSPEND );
+ }
+
+ 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
+ result = down_interruptible( &writeSem );
+
+ // Verify device is still valid
+ if (IsDeviceValid( pDev ) == false)
+ {
+ DBG( "Invalid device!\n" );
+ return -ENXIO;
+ }
+
+ // Write is done, release device
+ usb_autopm_put_interface( pDev->mpIntf );
+
+ // 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 );
+ 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:
+ Construct object/load file into memory
+
+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(
+ sQCUSBNet * 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(
+ sQCUSBNet * 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(
+ sQCUSBNet * 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(
+ sQCUSBNet * 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(
+ sQCUSBNet * 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(
+ sQCUSBNet * pDev,
+ u16 clientID,
+ u16 transactionID,
+ void (* pNotifyFunct)(sQCUSBNet *, 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(
+ sQCUSBNet * 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(
+ sQCUSBNet * 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(
+ sQCUSBNet * 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 );
+ sQCUSBNet * pDev = container_of( pQMIDev,
+ sQCUSBNet,
+ 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\n" );
+ }
+
+ 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\n" );
+ }
+
+ 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 list_head * pTasks;
+ 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)
+ {
+ // "group_leader" points to the main process' task, which resides in
+ // the global "tasks" list.
+ list_for_each( pTasks, &current->group_leader->tasks )
+ {
+ pEachTask = container_of( pTasks, struct task_struct, tasks );
+ 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 );
+ }
+
+ 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( sQCUSBNet * pDev )
+{
+ int result;
+ int QCQMIIndex = 0;
+ dev_t devno;
+ char * pDevName;
+
+ 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)
+ {
+ pDev->mbQMIValid = false;
+ return result;
+ }
+
+ // Fill MEID for device
+ result = QMIDMSGetMEID( pDev );
+ if (result != 0)
+ {
+ pDev->mbQMIValid = false;
+ 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;
+
+ 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");
+ QCQMIIndex = simple_strtoul( pDevName, NULL, 10 );
+ if(QCQMIIndex < 0 )
+ {
+ DBG( "Bad minor number\n" );
+ return -ENXIO;
+ }
+
+ // Always print this output
+ printk( KERN_INFO "creating qcqmi%d\n",
+ QCQMIIndex );
+#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,
+ NULL,
+ devno,
+ NULL,
+ "qcqmi%d",
+ QCQMIIndex );
+#else
+ device_create( pDev->mQMIDev.mpDevClass,
+ NULL,
+ devno,
+ "qcqmi%d",
+ QCQMIIndex );
+#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( sQCUSBNet * pDev )
+{
+ struct inode * pOpenInode;
+ struct list_head * pInodeList;
+ struct list_head * pTasks;
+ struct task_struct * pEachTask;
+ struct fdtable * pFDT;
+ struct file * pFilp;
+ unsigned long flags;
+ int count = 0;
+
+ // 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;
+
+ // 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
+
+ // "group_leader" points to the main process' task, which resides in
+ // the global "tasks" list.
+ list_for_each( pTasks, &current->group_leader->tasks )
+ {
+ pEachTask = container_of( pTasks, struct task_struct, tasks );
+ 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 );
+ }
+ }
+ }
+
+ // 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 );
+ }
+ 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(
+ sQCUSBNet * 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 -EFAULT;
+ }
+
+ writeBufferSize = QMICTLReadyReqSize();
+ pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
+ if (pWriteBuffer == NULL)
+ {
+ return -ENOMEM;
+ }
+
+ // 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
+ {
+ // 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 );
+
+ // TODO: 3580 and newer firmware does not require this delay
+ msleep( 5000 );
+
+ // 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(
+ sQCUSBNet * 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 = ! QTestDownReason( 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" );
+ QSetDownReason( pDev, NO_NDIS_CONNECTION );
+ QClearDownReason( pDev, NO_NDIS_CONNECTION );
+ }
+ else
+ {
+ if (bLinkState == true)
+ {
+ DBG( "Net device link is connected\n" );
+ QClearDownReason( pDev, NO_NDIS_CONNECTION );
+ }
+ else
+ {
+ DBG( "Net device link is disconnected\n" );
+ QSetDownReason( 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( sQCUSBNet * 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( sQCUSBNet * 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/staging/gobi/QCUSBNet2k/QMIDevice.h b/drivers/staging/gobi/QCUSBNet2k/QMIDevice.h
new file mode 100644
index 0000000..6fb9c47
--- /dev/null
+++ b/drivers/staging/gobi/QCUSBNet2k/QMIDevice.h
@@ -0,0 +1,297 @@
+/*===========================================================================
+FILE:
+ QMIDevice.h
+
+DESCRIPTION:
+ Functions related to the QMI interface device
+
+FUNCTIONS:
+ Generic functions
+ IsDeviceValid
+ PrintHex
+ QSetDownReason
+ QClearDownReason
+ QTestDownReason
+
+ Driver level asynchronous read functions
+ 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) 2010, Code Aurora Forum. All rights reserved.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2 and
+only version 2 as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301, USA.
+
+===========================================================================*/
+
+//---------------------------------------------------------------------------
+// Pragmas
+//---------------------------------------------------------------------------
+#pragma once
+
+//---------------------------------------------------------------------------
+// Include Files
+//---------------------------------------------------------------------------
+#include "Structs.h"
+#include "QMI.h"
+
+/*=========================================================================*/
+// Generic functions
+/*=========================================================================*/
+
+// Basic test to see if device memory is valid
+bool IsDeviceValid( sQCUSBNet * pDev );
+
+// Print Hex data, for debug purposes
+void PrintHex(
+ void * pBuffer,
+ u16 bufSize );
+
+// Sets mDownReason and turns carrier off
+void QSetDownReason(
+ sQCUSBNet * pDev,
+ u8 reason );
+
+// Clear mDownReason and may turn carrier on
+void QClearDownReason(
+ sQCUSBNet * pDev,
+ u8 reason );
+
+// Tests mDownReason and returns whether reason is set
+bool QTestDownReason(
+ sQCUSBNet * pDev,
+ u8 reason );
+
+/*=========================================================================*/
+// Driver level asynchronous read functions
+/*=========================================================================*/
+
+// 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( sQCUSBNet * pDev );
+
+// Kill continuous read "thread"
+void KillRead( sQCUSBNet * pDev );
+
+/*=========================================================================*/
+// Internal read/write functions
+/*=========================================================================*/
+
+// Start asynchronous read
+// Reading client's data store, not device
+int ReadAsync(
+ sQCUSBNet * pDev,
+ u16 clientID,
+ u16 transactionID,
+ void (*pCallback)(sQCUSBNet *, u16, void *),
+ void * pData );
+
+// Notification function for synchronous read
+void UpSem(
+ sQCUSBNet * pDev,
+ u16 clientID,
+ void * pData );
+
+// Start synchronous read
+// Reading client's data store, not device
+int ReadSync(
+ sQCUSBNet * pDev,
+ void ** ppOutBuffer,
+ u16 clientID,
+ u16 transactionID );
+
+// Write callback
+void WriteSyncCallback( struct urb * pWriteURB );
+
+// Start synchronous write
+int WriteSync(
+ sQCUSBNet * pDev,
+ char * pInWriteBuffer,
+ int size,
+ u16 clientID );
+
+/*=========================================================================*/
+// Internal memory management functions
+/*=========================================================================*/
+
+// Create client and allocate memory
+int GetClientID(
+ sQCUSBNet * pDev,
+ u8 serviceType );
+
+// Release client and free memory
+void ReleaseClientID(
+ sQCUSBNet * pDev,
+ u16 clientID );
+
+// Find this client's memory
+sClientMemList * FindClientMem(
+ sQCUSBNet * pDev,
+ u16 clientID );
+
+// Add Data to this client's ReadMem list
+bool AddToReadMemList(
+ sQCUSBNet * 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(
+ sQCUSBNet * pDev,
+ u16 clientID,
+ u16 transactionID,
+ void ** ppData,
+ u16 * pDataSize );
+
+// Add Notify entry to this client's notify List
+bool AddToNotifyList(
+ sQCUSBNet * pDev,
+ u16 clientID,
+ u16 transactionID,
+ void (* pNotifyFunct)(sQCUSBNet *, u16, void *),
+ void * pData );
+
+// Remove first Notify entry from this client's notify list
+// and Run function
+bool NotifyAndPopNotifyList(
+ sQCUSBNet * pDev,
+ u16 clientID,
+ u16 transactionID );
+
+// Add URB to this client's URB list
+bool AddToURBList(
+ sQCUSBNet * pDev,
+ u16 clientID,
+ struct urb * pURB );
+
+// Remove URB from this client's URB list
+struct urb * PopFromURBList(
+ sQCUSBNet * 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( sQCUSBNet * pDev );
+
+// QMI Device cleanup function
+void DeregisterQMIDevice( sQCUSBNet * pDev );
+
+/*=========================================================================*/
+// Driver level client management
+/*=========================================================================*/
+
+// Check if QMI is ready for use
+bool QMIReady(
+ sQCUSBNet * pDev,
+ u16 timeout );
+
+// QMI WDS callback function
+void QMIWDSCallback(
+ sQCUSBNet * pDev,
+ u16 clientID,
+ void * pData );
+
+// Fire off reqests and start async read for QMI WDS callback
+int SetupQMIWDSCallback( sQCUSBNet * pDev );
+
+// Register client, send req and parse MEID response, release client
+int QMIDMSGetMEID( sQCUSBNet * pDev );
+
+
+
diff --git a/drivers/staging/gobi/QCUSBNet2k/Structs.h b/drivers/staging/gobi/QCUSBNet2k/Structs.h
new file mode 100644
index 0000000..07e3193
--- /dev/null
+++ b/drivers/staging/gobi/QCUSBNet2k/Structs.h
@@ -0,0 +1,318 @@
+/*===========================================================================
+FILE:
+ Structs.h
+
+DESCRIPTION:
+ Declaration of structures used by the Qualcomm Linux USB Network driver
+
+FUNCTIONS:
+ none
+
+Copyright (c) 2010, Code Aurora Forum. All rights reserved.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2 and
+only version 2 as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301, USA.
+
+===========================================================================*/
+
+//---------------------------------------------------------------------------
+// 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 "QCUSBNet2k::%s " format, __FUNCTION__, ## arg ); \
+ } \
+
+
+// Used in recursion, defined later below
+struct sQCUSBNet;
+
+/*=========================================================================*/
+// 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 sQCUSBNet *, 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;
+
+ /* Up this semaphore when it's time for the thread to work */
+ struct semaphore 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;
+
+ /* 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 sQCUSBNet
+//
+// Structure that defines the data associated with the Qualcomm USB device
+/*=========================================================================*/
+typedef struct sQCUSBNet
+{
+ /* 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 Q*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;
+
+} sQCUSBNet;
+
+/*=========================================================================*/
+// 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 */
+ sQCUSBNet * mpDev;
+
+} sQMIFilpStorage;
+
+