summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBjørn Mork <bjorn@mork.no>2017-05-11 11:03:45 +0200
committerBjørn Mork <bjorn@mork.no>2017-05-11 11:03:45 +0200
commit00456512bb6b3734fbb06e8d37fcb1b05bd0ee19 (patch)
tree0341b82bd5107483f245af230b557f0ac79fc73f
parent3aaa2d2259ef043248ab42676fe1aa3e083c2596 (diff)
add GobiNet+GobiSerial packages
Signed-off-by: Bjørn Mork <bjorn@mork.no>
-rw-r--r--swi-drivers/Makefile51
-rw-r--r--swi-drivers/src/GobiNet/GobiUSBNet.c2606
-rw-r--r--swi-drivers/src/GobiNet/Makefile56
-rw-r--r--swi-drivers/src/GobiNet/QMI.c1868
-rw-r--r--swi-drivers/src/GobiNet/QMI.h456
-rw-r--r--swi-drivers/src/GobiNet/QMIDevice.c6379
-rw-r--r--swi-drivers/src/GobiNet/QMIDevice.h440
-rw-r--r--swi-drivers/src/GobiNet/Readme.txt78
-rw-r--r--swi-drivers/src/GobiNet/Structs.h476
-rw-r--r--swi-drivers/src/GobiNet/gobi_usbnet.h51
-rw-r--r--swi-drivers/src/GobiNet/usbnet_2_6_32.c396
-rw-r--r--swi-drivers/src/GobiNet/usbnet_2_6_35.c400
-rw-r--r--swi-drivers/src/GobiNet/usbnet_3_0_6.c432
-rw-r--r--swi-drivers/src/GobiNet/usbnet_3_10_21.c457
-rw-r--r--swi-drivers/src/GobiNet/usbnet_3_12_xx.c513
-rw-r--r--swi-drivers/src/GobiNet/usbnet_4_4_xx.c514
-rw-r--r--swi-drivers/src/GobiSerial/GobiSerial.c1479
-rw-r--r--swi-drivers/src/GobiSerial/Makefile33
-rw-r--r--swi-drivers/src/GobiSerial/Readme.txt61
19 files changed, 16746 insertions, 0 deletions
diff --git a/swi-drivers/Makefile b/swi-drivers/Makefile
new file mode 100644
index 0000000..1c38d11
--- /dev/null
+++ b/swi-drivers/Makefile
@@ -0,0 +1,51 @@
+#
+# Copyright (C) 2017 Bjørn Mork <bjorn@mork.no>
+#
+# This is free software, licensed under the GNU General Public License v2.
+# See /LICENSE for more information.
+#
+
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=swi-drivers
+PKG_VERSION:=0.01
+PKG_RELEASE:=1
+PKG_LICENSE:=GPL-2.0
+
+PKG_MAINTAINER:=Bjørn Mork <bjorn@mork.no>
+PKG_BUILD_PARALLEL:=1
+
+include $(INCLUDE_DIR)/kernel.mk
+include $(INCLUDE_DIR)/package.mk
+
+define KernelPackage/GobiNet
+ TITLE:=GobiNet
+ SUBMENU:=Other modules
+ FILES:=$(PKG_BUILD_DIR)/GobiNet/GobiNet.ko
+ AUTOLOAD:=$(call AutoLoad,50,GobiNet)
+endef
+
+define KernelPackage/GobiNet/install
+endef
+
+define KernelPackage/GobiSerial
+ TITLE:=GobiSerial
+ SUBMENU:=Other modules
+ FILES:=$(PKG_BUILD_DIR)/GobiSerial/GobiSerial.ko
+ AUTOLOAD:=$(call AutoLoad,50,GobiSerial)
+endef
+
+define KernelPackage/GobiSerial/install
+endef
+
+define Build/Compile
+ +$(MAKE) $(PKG_JOBS) -C "$(LINUX_DIR)" \
+ ARCH="$(LINUX_KARCH)" \
+ CROSS_COMPILE="$(TARGET_CROSS)" \
+ SUBDIRS="$(PKG_BUILD_DIR)/GobiNet $(PKG_BUILD_DIR)/GobiSerial" \
+ NOSTDINC_FLAGS="$(NOSTDINC_FLAGS)" \
+ modules
+endef
+
+$(eval $(call KernelPackage,GobiNet))
+$(eval $(call KernelPackage,GobiSerial))
diff --git a/swi-drivers/src/GobiNet/GobiUSBNet.c b/swi-drivers/src/GobiNet/GobiUSBNet.c
new file mode 100644
index 0000000..33d05f2
--- /dev/null
+++ b/swi-drivers/src/GobiNet/GobiUSBNet.c
@@ -0,0 +1,2606 @@
+/*===========================================================================
+FILE:
+ GobiUSBNet.c
+
+DESCRIPTION:
+ Qualcomm USB Network device for Gobi 3000
+
+FUNCTIONS:
+ GobiNetSuspend
+ GobiNetResume
+ GobiNetDriverBind
+ GobiNetDriverUnbind
+ GobiUSBNetURBCallback
+ GobiUSBNetTXTimeout
+ GobiUSBNetAutoPMThread
+ GobiUSBNetStartXmit
+ GobiUSBNetOpen
+ GobiUSBNetStop
+ GobiUSBNetProbe
+ GobiUSBNetModInit
+ GobiUSBNetModExit
+
+Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of Code Aurora Forum nor
+ the names of its contributors may be used to endorse or promote
+ products derived from this software without specific prior written
+ permission.
+
+Alternatively, provided that this notice is retained in full, this software
+may be relicensed by the recipient under the terms of the GNU General Public
+License version 2 ("GPL") and only version 2, in which case the provisions of
+the GPL apply INSTEAD OF those given above. If the recipient relicenses the
+software under the GPL, then the identification text in the MODULE_LICENSE
+macro must be changed to reflect "GPLv2" instead of "Dual BSD/GPL". Once a
+recipient changes the license terms to the GPL, subsequent recipients shall
+not relicense under alternate licensing terms, including the BSD or dual
+BSD/GPL terms. In addition, the following license statement immediately
+below and between the words START and END shall also then apply when this
+software is relicensed under the GPL:
+
+START
+
+This program is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License version 2 and only version 2 as
+published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+details.
+
+You should have received a copy of the GNU General Public License along with
+this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+END
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+===========================================================================*/
+/* ===========================================================================
+Reference http://www.spinics.net/lists/linux-usb/msg56457.html
+USB/xhci: Enable remote wakeup for USB3 devices
+===========================================================================*/
+//---------------------------------------------------------------------------
+// Include Files
+//---------------------------------------------------------------------------
+
+#include "Structs.h"
+#include "QMIDevice.h"
+#include "QMI.h"
+#include "gobi_usbnet.h"
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/module.h>
+#include <net/ip.h>
+#ifdef CONFIG_MEMCG
+#define MEMCG_NOT_FIX
+#ifdef MEMCG_NOT_FIX
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 3,13,0 ) &&\
+ LINUX_VERSION_CODE < KERNEL_VERSION( 4,4,0 ))
+#warning "Remove memcontrol.h task_in_memcg_oom warning : replace 'return p->memcg_oom.memcg;' to 'return p->memcg_oom.memcg==NULL ? 0 : 1;' in function task_in_memcg_oom"
+#warning "Commnet '#define MEMCG_NOT_FIX' above after fix applied."
+#elif (LINUX_VERSION_CODE >= KERNEL_VERSION( 4,4,0 ) )
+#warning "Remove memcontrol.h task_in_memcg_oom warning : replace 'return p->memcg_in_oom;' to 'return p->memcg_in_oom==NULL ? 0 : 1;' in function task_in_memcg_oom"
+#warning "Commnet '#define MEMCG_NOT_FIX' above after fix applied."
+#endif
+#endif
+#endif
+#include <asm/siginfo.h> //siginfo
+#include <linux/rcupdate.h> //rcu_read_lock
+#include <linux/sched.h> //find_task_by_pid_type
+#include <linux/irq.h>
+#include <net/sch_generic.h>
+
+#ifdef CONFIG_IPV6
+static inline __u8 ipv6_tclass2(const struct ipv6hdr *iph)
+{
+ return (ntohl(*(__be32 *)iph) >> 20) & 0xff;
+}
+#endif
+
+#define BIT_9X15 (31)
+//-----------------------------------------------------------------------------
+// Probe one device at the time when set to "1"
+//-----------------------------------------------------------------------------
+#define _PROBE_LOCK_ 0
+//-----------------------------------------------------------------------------
+// Definitions
+//-----------------------------------------------------------------------------
+
+// Version Information
+#define DRIVER_VERSION "2017-04-05/SWI_2.42"
+#define DRIVER_AUTHOR "Qualcomm Innovation Center"
+#define DRIVER_DESC "GobiNet"
+#define QOS_HDR_LEN (6)
+#define IPV6HDR_PAYLOAD_UPPER (4)
+#define IPV6HDR_PAYLOAD_LOWER (5)
+#define IPV6HDR_LENGTH (40) // ipv6 header length
+#define IPV4HDR_TOT_UPPER (2)
+#define IPV4HDR_TOT_LOWER (3)
+#define MAC48_MULTICAST_ID (0x33)
+#define GOBI_MAX_SINGLE_PACKET_SIZE 2048
+
+// Debug flag
+int debug;
+int qos_debug;
+int iModuleExit=0;
+/*
+ * enable/disable TE flow control
+ */
+int iTEEnable=0;
+/*
+ * Is RAW IP RAW
+ */
+#ifndef DATA_MODE_RP
+int iRAWIPEnable=0;
+#else
+int iRAWIPEnable=1;
+#endif
+
+#ifdef TX_URB_MONITOR
+
+ /*
+ * URB monitor requires that Sierra override some functions to get
+ * URB information.
+ * TX_XMIT_SIERRA indicates the default Linux functions that were over-ridden
+ * TX_URB_MONITOR indicates the changes made for URB monitor to work
+ *
+ */
+ #ifndef TX_XMIT_SIERRA
+ #define TX_XMIT_SIERRA
+ #endif
+
+/* Current URB monitor implementation is supported on kernel versions
+ * between 2.6.31 and 2.6.32. This is because the default "usbnet_start_xmit" function
+ * is over-ridden by Sierra to provide this functionality. Post kernel 2.6.32,
+ * there is a change in "usbnet_start_xmit" which is not handled
+ * by Sierra Gobinet driver
+ */
+//TO TEST DIFFERENT KERNEL VERSIONS
+//#undef LINUX_VERSION_CODE
+//#define LINUX_VERSION_CODE KERNEL_VERSION( 3,13,1 )
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,31 ) ||\
+ (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,32 ) &&\
+ LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,35 )) ||\
+ (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,35 ) &&\
+ LINUX_VERSION_CODE < KERNEL_VERSION( 3,0,6 )) ||\
+ (LINUX_VERSION_CODE > KERNEL_VERSION( 3,0,6 ) &&\
+ LINUX_VERSION_CODE < KERNEL_VERSION( 3,10,1 )) ||\
+ (LINUX_VERSION_CODE > KERNEL_VERSION( 3,11,0 ) &&\
+ LINUX_VERSION_CODE < KERNEL_VERSION( 3,12,0)) ||\
+ (LINUX_VERSION_CODE > KERNEL_VERSION( 3,12,0 ) &&\
+ LINUX_VERSION_CODE < KERNEL_VERSION( 4,4,0)) ||\
+ (LINUX_VERSION_CODE > KERNEL_VERSION( 4,4,0 ) &&\
+ LINUX_VERSION_CODE < KERNEL_VERSION( 4,4,2)) ||\
+ LINUX_VERSION_CODE >= KERNEL_VERSION( 4,5,0 ) )
+#error "URB_MONITOR is NOT supported on this kernel version"
+#endif
+#endif //TX_URB_MONITOR
+
+// Allow user interrupts
+int interruptible = 1;
+
+// Number of IP packets which may be queued up for transmit
+int txQueueLength = 100;
+
+// Class should be created during module init, so needs to be global
+static struct class * gpClass;
+struct semaphore taskLoading;
+
+/**************************************************/
+void StopTask(sGobiUSBNet *pDev);
+bool isModuleUnload(sGobiUSBNet *pDev);
+int FixEthFrame(struct usbnet *dev, struct sk_buff *skb, int isIpv4);
+void ResetEthHeader(struct usbnet *dev, struct sk_buff *skb, int isIpv4);
+int GobiUSBNetOpen( struct net_device * pNet );
+int GobiUSBNetStop( struct net_device * pNet );
+static int GobiNetDriverLteRxFixup(struct usbnet *dev, struct sk_buff *skb);
+void SendWeakupControlMsg(
+ struct usb_interface * pIntf,
+ int oldPowerState);
+
+void ClearTaskID(bool bForceMode,sGobiUSBNet *pDev)
+{
+ int i=0;
+ int iTaskID = 0;
+
+ if(pDev==NULL)
+ {
+ DBG("%s : %d\n",__FUNCTION__,__LINE__);
+ return ;
+ }
+ iTaskID = pDev->iTaskID;
+ if(iTaskID==-1)
+ {
+ DBG("%s : %d\n",__FUNCTION__,__LINE__);
+ return ;
+ }
+
+ while(!down_trylock( &(pDev->taskIDSem) ))
+ {
+ i++;
+ if(i>MAX_RETRY_TASK_LOCK_TIME)
+ {
+ printk("ClearTaskID Get TaskID Timeout");
+ if(bForceMode)
+ {
+ if(pDev)
+ pDev->iTaskID=-1;
+ else
+ {
+ DBG("%s %d\n",__FUNCTION__,__LINE__);
+ }
+ }
+ return ;
+ }
+ wait_ms(MAX_RETRY_TASK_MSLEEP_TIME);
+ if(signal_pending(current))
+ {
+ break;
+ }
+
+ if(pDev==NULL)
+ {
+ return;
+ }
+ }
+ set_current_state(TASK_RUNNING);
+ DBG("%s iTaskID(%d)\n",__FUNCTION__,iTaskID);
+ if(pDev)
+ pDev->iTaskID=-1;
+ if(pDev)
+ up(&(pDev->taskIDSem));
+ }
+
+
+void StopTask(sGobiUSBNet *pDev)
+{
+ int i =0;
+ if(pDev==NULL)
+ {
+ return ;
+ }
+ while(!down_trylock( &(pDev->taskIDSem) ))
+ {
+ i++;
+ if(i>MAX_RETRY_TASK_LOCK_TIME)
+ {
+ DBG("StopTask Get TaskID Timeout");
+ break;
+ }
+ set_current_state(TASK_INTERRUPTIBLE);
+ wait_ms(MAX_RETRY_TASK_MSLEEP_TIME);
+ if(signal_pending(current))
+ {
+ set_current_state(TASK_RUNNING);
+ return ;
+ }
+ if(pDev==NULL)
+ {
+ set_current_state(TASK_RUNNING);
+ return ;
+ }
+ }
+ set_current_state(TASK_RUNNING);
+ if(pDev==NULL)
+ {
+ return ;
+ }
+ else
+ {
+ if(pDev->iTaskID>0)
+ {
+ if(pDev)
+ {
+ if(pDev->task)
+ kthread_stop(pDev->task);
+ }
+ else
+ {
+ return ;
+ }
+ if(pDev)
+ {
+ pDev->task = NULL;
+ pDev->iTaskID = -1;
+ }
+ else
+ {
+ return ;
+ }
+ }
+ }
+ if(pDev)
+ up(&pDev->taskIDSem);
+ return ;
+}
+
+#define _PROBE_LOCK_ 0
+int thread_function(void *data)
+{
+
+ int status=0;
+ sGobiUSBNet * pGobiDev = (sGobiUSBNet*)data;
+ char szQMIBusName[64]={0};
+ struct usb_device *dev = NULL;
+
+ #if _PROBE_LOCK_
+ while(!down_trylock( &taskLoading ))
+ {
+ if((kthread_should_stop())||
+ signal_pending(current))
+ {
+ pGobiDev->task = NULL;
+ pGobiDev->iTaskID = -1;
+ pGobiDev->mbQMIValid = false;
+ return -1;
+ }
+ i++;
+ if( (i>5000) || isModuleUnload(pGobiDev))
+ {
+ DBG("Get TaskID Timeout");
+ pGobiDev->iTaskID = -1;
+ return 0;
+ }
+ if((i%1000) ==999)
+ DBG("Waiting...\n");
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (signal_pending(current))
+ {
+ return -1;
+ }
+ wait_ms(10);
+
+ if((kthread_should_stop())||
+ signal_pending(current))
+ {
+ pGobiDev->task = NULL;
+ pGobiDev->iTaskID = -1;
+ pGobiDev->mbQMIValid = false;
+ return -1;
+ }
+ }
+ set_current_state(TASK_RUNNING);
+ #endif
+ dev = interface_to_usbdev(pGobiDev->mUsb_Interface);
+ snprintf(szQMIBusName,63,"qcqmi%d-%d-%s:%d.%d",
+ (int)pGobiDev->mQMIDev.qcqmi,
+ dev->bus->busnum, dev->devpath,
+ dev->actconfig->desc.bConfigurationValue,
+ pGobiDev->mUsb_Interface->cur_altsetting->desc.bInterfaceNumber);
+ pGobiDev->mQMIDev.mpClientMemList = NULL;
+ pGobiDev->iNetLinkStatus = eNetDeviceLink_Disconnected;
+ DBG("Handle qcqmi(%s), task: %d\n",szQMIBusName,pGobiDev->iTaskID);
+ status = RegisterQMIDevice( pGobiDev, pGobiDev->mIs9x15);
+ if (status != 0)
+ {
+ if(pGobiDev)
+ {
+ ClearTaskID(false,pGobiDev);
+ if(pGobiDev)
+ {
+ DBG("Finish qcqmi(%s) task: %d %d\n",szQMIBusName,pGobiDev->iTaskID,status);
+ pGobiDev->task = NULL;
+ pGobiDev->iTaskID = -1;
+ pGobiDev->mbQMIValid = false;
+ }
+ }
+ }
+ else
+ {
+ if(pGobiDev->iDataMode==eDataMode_RAWIP)
+ {
+ pGobiDev->mpNetDev->net->hard_header_len = 0;
+ pGobiDev->mpNetDev->net->flags |= IFF_NOARP;
+ #if (LINUX_VERSION_CODE >= KERNEL_VERSION( 4,4,0 ))
+ pGobiDev->mpNetDev->net->flags |= IFF_NOARP | IFF_MULTICAST;
+ /* recalculate buffers after changing hard_header_len */
+ usbnet_change_mtu(pGobiDev->mpNetDev->net, pGobiDev->mpNetDev->net->mtu);
+ pGobiDev->mpNetDev->rx_urb_size = GOBI_MAX_SINGLE_PACKET_SIZE;
+ usbnet_update_max_qlen(pGobiDev->mpNetDev);
+ #endif
+
+ printk(KERN_INFO "RawIP mode\n" );
+ }
+ else
+ {
+ printk(KERN_INFO "Ethernet mode\n" );
+ }
+ }
+ if(pGobiDev)
+ {
+ ClearTaskID(false,pGobiDev);
+ if(pGobiDev)
+ {
+ DBG("Finish qcqmi(%s) task: %d %d\n",szQMIBusName,pGobiDev->iTaskID,status);
+ pGobiDev->task = NULL;
+ pGobiDev->iTaskID = -1;
+ }
+ }
+ if (status != 0)
+ {
+ // usbnet_disconnect() will call GobiNetDriverUnbind() which will call
+ // DeregisterQMIDevice() to clean up any partially created QMI device
+ if(pGobiDev->mbUnload >= eStatUnloading)
+ {
+ DBG("Device Disconnecting...\n");
+ return 0;
+ }
+ if((kthread_should_stop())||
+ signal_pending(current))
+ {
+ return 0;
+ }
+ if(pGobiDev)
+ {
+ usbnet_disconnect( pGobiDev->mUsb_Interface);
+ }
+ if(pGobiDev->mbUnload >= eStatUnloading)
+ {
+ DBG("Device Disconnecting...\n");
+ return 0;
+ }
+ if((kthread_should_stop())||
+ signal_pending(current))
+ {
+ return 0;
+ }
+ if(-ETIMEDOUT==status)
+ {
+ struct usb_device *udev;
+ udev = interface_to_usbdev(pGobiDev->mUsb_Interface);
+ DBG("RESET USB DEVICE...\n");
+ usb_reset_device(udev);
+ }
+ }
+ #if _PROBE_LOCK_
+ up(&taskLoading);
+ #endif
+
+ return 0;
+
+}
+
+/**************************************************/
+#ifdef CONFIG_PM
+bool bIsSuspend(sGobiUSBNet *pGobiDev)
+{
+ bool rc = false;
+ unsigned long flags = 0;
+ spin_lock_irqsave(&pGobiDev->sSuspendLock,flags);
+ rc = pGobiDev->bSuspend;
+ spin_unlock_irqrestore(&pGobiDev->sSuspendLock,flags);
+ return rc;
+}
+void SetCurrentSuspendStat(sGobiUSBNet *pGobiDev,bool bSuspend)
+{
+ unsigned long flags = 0;
+ spin_lock_irqsave(&pGobiDev->sSuspendLock,flags);
+ pGobiDev->bSuspend = bSuspend;
+ spin_unlock_irqrestore(&pGobiDev->sSuspendLock,flags);
+}
+
+/*===========================================================================
+METHOD:
+ GobiNetSuspend (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 GobiNetSuspend(
+ struct usb_interface * pIntf,
+ pm_message_t powerEvent )
+{
+ struct usbnet * pDev;
+ sGobiUSBNet * pGobiDev;
+ int nRet = 0;
+
+ if (pIntf == 0)
+ {
+ return -ENOMEM;
+ }
+
+#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,23 ))
+ pDev = usb_get_intfdata( pIntf );
+#else
+ pDev = (struct usbnet *)pIntf->dev.platform_data;
+#endif
+
+ if (pDev == NULL || pDev->net == NULL)
+ {
+ DBG( "failed to get netdevice\n" );
+ return -ENXIO;
+ }
+
+ pGobiDev = (sGobiUSBNet *)pDev->data[0];
+ if (pGobiDev == NULL)
+ {
+ DBG( "failed to get QMIDevice\n" );
+ return -ENXIO;
+ }
+
+ #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( "ConfigPowerSaveSettings\n" );
+ if(ConfigPowerSaveSettings(pGobiDev,QMIWDS,QMI_WDS_GET_PKT_SRVC_STATUS_IND)<0)
+ printk(KERN_ERR"Config Power Save Setting error 1\n");
+ if(ConfigPowerSaveSettings(pGobiDev,QMINAS,QMI_NAS_SERVING_SYSTEM_IND)<0)
+ printk(KERN_ERR"Config Power Save Setting error 2\n");
+ if(ConfigPowerSaveSettings(pGobiDev,QMIWMS,QMI_WMS_EVENT_REPORT_IND)<0)
+ printk(KERN_ERR"Config Power Save Setting error 3\n");
+ if(ConfigPowerSaveSettings(pGobiDev,QMIVOICE,QMI_VOICE_ALL_CALL_STATUS_IND)<0)
+ printk(KERN_ERR"Config Power Save Setting error 4\n");
+ }
+
+ if(SetPowerSaveMode(pGobiDev,1)<0)
+ {
+ printk(KERN_ERR"Suspend Set Power Save Mode error\n");
+ }
+ else
+ {
+ DBG( "Set Power Save Mode 1\n" );
+ }
+
+ SetCurrentSuspendStat(pGobiDev,1);
+
+ KillRead(pGobiDev);
+
+ // Is this autosuspend or system suspend?
+ // do we allow remote wakeup?
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,33 ))
+ if (pDev->udev->auto_pm == 0)
+#else
+ if ((powerEvent.event & PM_EVENT_AUTO) == 0)
+#endif
+ {
+ DBG( "device suspended to power level %d\n",
+ powerEvent.event );
+ GobiSetDownReason( pGobiDev, DRIVER_SUSPENDED );
+ }
+ else
+ {
+ DBG( "device autosuspend\n" );
+ }
+
+ if (powerEvent.event & PM_EVENT_SUSPEND)
+ {
+ // Stop QMI read callbacks
+ pDev->udev->reset_resume = 0;
+ DBG("suspend event = 0x%04x\n", powerEvent.event );
+ // 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 = 0;
+ }
+
+ #if defined(USB_INTRF_FUNC_SUSPEND) && defined(USB_INTRF_FUNC_SUSPEND_RW)
+ /* send control message to resume from suspend mode for all interface.
+ This is required by modem on USB3.0 selective suspend */
+ if ( pDev->udev->speed >= USB_SPEED_SUPER )
+ {
+ nRet = usb_control_msg(pDev->udev, usb_sndctrlpipe(pDev->udev, 0),
+ USB_REQ_SET_FEATURE, USB_RECIP_INTERFACE,
+ USB_INTRF_FUNC_SUSPEND,
+ USB_INTRF_FUNC_SUSPEND_RW | USB_INTRF_FUNC_SUSPEND_LP |
+ pIntf->cur_altsetting->desc.bInterfaceNumber, /* two bytes in this field, suspend option(1 byte) | interface number(1 byte) */
+ NULL, 0, USB_CTRL_SET_TIMEOUT);
+ if (nRet != 0)
+ {
+ DBG("[line:%d] send usb_control_msg failed!nRet = %d\n", __LINE__, nRet);
+ }
+ }
+ #endif
+ //USB/xhci: Enable remote wakeup for USB3 devices
+ nRet = usb_control_msg(pDev->udev, usb_sndctrlpipe(pDev->udev, 0),
+ USB_REQ_SET_FEATURE, USB_RECIP_DEVICE,
+ USB_DEVICE_REMOTE_WAKEUP,
+ 0, //Don't care about which interface
+ NULL,
+ 0,
+ USB_CTRL_SET_TIMEOUT);
+ if (nRet != 0)
+ {
+ DBG("[line:%d] send usb_control_msg failed!nRet = %d\n", __LINE__, nRet);
+ }
+ // Run usbnet's suspend function so that the kernel spin lock counter keeps balance
+ return usbnet_suspend( pIntf, powerEvent );
+}
+
+/*===========================================================================
+METHOD:
+ GobiNetResume (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 GobiNetResume( struct usb_interface * pIntf )
+{
+ struct usbnet * pDev;
+ sGobiUSBNet * pGobiDev;
+ int nRet= 0;
+ int oldPowerState;
+
+ if (pIntf == 0)
+ {
+ return -ENOMEM;
+ }
+
+#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,23 ))
+ pDev = usb_get_intfdata( pIntf );
+#else
+ pDev = (struct usbnet *)pIntf->dev.platform_data;
+#endif
+
+ if (pDev == NULL || pDev->net == NULL)
+ {
+ DBG( "failed to get netdevice\n" );
+ return -ENXIO;
+ }
+
+ pGobiDev = (sGobiUSBNet *)pDev->data[0];
+ if (pGobiDev == NULL)
+ {
+ DBG( "failed to get QMIDevice\n" );
+ return -ENXIO;
+ }
+
+ oldPowerState = pIntf->dev.power.power_state.event;
+ pIntf->dev.power.power_state.event = PM_EVENT_ON;
+ DBG( "resuming from power mode 0x%04x\n", oldPowerState );
+
+ // It doesn't matter if this is autoresume or system resume
+ GobiClearDownReason( pGobiDev, DRIVER_SUSPENDED );
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 ))
+ // Kick Auto PM thread to process any queued URBs
+ complete( &pGobiDev->mAutoPM.mThreadDoWork );
+#endif
+
+
+ SendWeakupControlMsg(pIntf,oldPowerState);
+ /* Run usbnet's resume function so that the kernel spin lock counter keeps balance */
+ nRet = usbnet_resume( pIntf );
+ if (nRet != 0)
+ {
+ DBG("[line:%d] usbnet_resume failed!nRet = %d\n", __LINE__, nRet);
+ }
+ SetCurrentSuspendStat(pGobiDev,0);
+ if(pGobiDev->mbUnload < eStatUnloading)
+ {
+ StartRead(pGobiDev);
+
+ if(SetPowerSaveMode(pGobiDev,0)<0)
+ {
+ printk(KERN_ERR" Resume Set Power Save Mode error\n");
+ }
+ else
+ {
+ DBG( "Set Power Save Mode 0\n" );
+ }
+ }
+ return nRet;
+}
+
+void GobiNetReset(struct usb_interface * pIntf)
+{
+ sGobiUSBNet * pGobiDev;
+ struct usbnet * pDev;
+ DBG("reset suspend");
+ #if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,23 ))
+ pDev = usb_get_intfdata( pIntf );
+ #else
+ pDev = (struct usbnet *)pIntf->dev.platform_data;
+ #endif
+ pGobiDev = (sGobiUSBNet *)pDev->data[0];
+ if (pGobiDev == NULL)
+ {
+ DBG( "failed to get QMIDevice\n" );
+ return;
+ }
+ //DeregisterQMIDevice(pGobiDev);
+ usbnet_disconnect(pIntf);
+}
+
+int GobiNetResetResume( struct usb_interface * pIntf )
+{
+ DBG("reset resume suspend\n");
+ if(pIntf->cur_altsetting->desc.bInterfaceNumber ==8)
+ {
+ struct usb_device *udev;
+ printk("Reset Device\n");
+ udev = interface_to_usbdev(pIntf);
+ usb_reset_device(udev);
+ }
+ return 0;
+}
+
+#endif /* CONFIG_PM */
+
+void UsbAutopmGetInterface(struct usb_interface * intf)
+{
+ #if (LINUX_VERSION_CODE < KERNEL_VERSION( 3,0,0 ))
+ usb_autopm_get_interface_async(intf);
+ #else
+ usb_autopm_get_interface_no_resume(intf);
+ #endif
+}
+
+/* very simplistic detection of IPv4 or IPv6 headers */
+static bool possibly_iphdr(const char *data)
+{
+ return (data[0] & 0xd0) == 0x40;
+}
+
+/*===========================================================================
+METHOD:
+ GobiNetDriverBind (Public Method)
+
+DESCRIPTION:
+ Setup in and out pipes
+
+PARAMETERS
+ pDev [ I ] - Pointer to usbnet device
+ pIntf [ I ] - Pointer to interface
+
+RETURN VALUE:
+ int - 0 for success
+ Negative errno for error
+===========================================================================*/
+static int GobiNetDriverBind(
+ struct usbnet * pDev,
+ struct usb_interface * pIntf )
+{
+ int numEndpoints;
+ int endpointIndex;
+ struct usb_host_endpoint * pEndpoint = NULL;
+ struct usb_host_endpoint * pIn = NULL;
+ struct usb_host_endpoint * pOut = NULL;
+
+ // Verify one altsetting
+ if (pIntf->num_altsetting != 1)
+ {
+ DBG( "invalid num_altsetting %u\n", pIntf->num_altsetting );
+ return -ENODEV;
+ }
+
+ /* We only accept certain interfaces */
+ if (pIntf->cur_altsetting->desc.bInterfaceClass != USB_CLASS_VENDOR_SPEC )
+ {
+ DBG( "Ignoring non vendor class interface #%d\n",
+ pIntf->cur_altsetting->desc.bInterfaceNumber );
+ return -ENODEV;
+ }
+ else if (pDev->driver_info->data &&
+ !test_bit(pIntf->cur_altsetting->desc.bInterfaceNumber, &pDev->driver_info->data)) {
+ DBG( "invalid interface %d\n",
+ pIntf->cur_altsetting->desc.bInterfaceNumber );
+ return -ENODEV;
+ }
+
+ // Collect In and Out endpoints
+ numEndpoints = pIntf->cur_altsetting->desc.bNumEndpoints;
+ for (endpointIndex = 0; endpointIndex < numEndpoints; endpointIndex++)
+ {
+ pEndpoint = pIntf->cur_altsetting->endpoint + endpointIndex;
+ if (pEndpoint == NULL)
+ {
+ DBG( "invalid endpoint %u\n", endpointIndex );
+ return -ENODEV;
+ }
+
+ if (usb_endpoint_dir_in( &pEndpoint->desc ) == true
+ && usb_endpoint_xfer_int( &pEndpoint->desc ) == false)
+ {
+ pIn = pEndpoint;
+ }
+ else if (usb_endpoint_dir_out( &pEndpoint->desc ) == true)
+ {
+ pOut = pEndpoint;
+ }
+ }
+
+ if (pIn == NULL || pOut == NULL)
+ {
+ DBG( "invalid endpoints\n" );
+ return -ENODEV;
+ }
+
+ if (usb_set_interface( pDev->udev,
+ pIntf->cur_altsetting->desc.bInterfaceNumber,
+ 0 ) != 0)
+ {
+ DBG( "unable to set interface\n" );
+ return -ENODEV;
+ }
+
+ pDev->in = usb_rcvbulkpipe( pDev->udev,
+ pIn->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK );
+ pDev->out = usb_sndbulkpipe( pDev->udev,
+ pOut->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK );
+
+ DBG( "in %x, out %x\n",
+ pIn->desc.bEndpointAddress,
+ pOut->desc.bEndpointAddress );
+
+ // In later versions of the kernel, usbnet helps with this
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,23 ))
+ pIntf->dev.platform_data = (void *)pDev;
+#endif
+
+ /* make MAC addr easily distinguishable from an IP header */
+ if (possibly_iphdr(pDev->net->dev_addr)) {
+ pDev->net->dev_addr[0] |= 0x02; /* set local assignment bit */
+ pDev->net->dev_addr[0] &= 0xbf; /* clear "IP" bit */
+ }
+
+ return 0;
+}
+
+/*===========================================================================
+METHOD:
+ GobiNetDriverUnbind (Public Method)
+
+DESCRIPTION:
+ Deregisters QMI device (Registration happened in the probe function)
+
+PARAMETERS
+ pDev [ I ] - Pointer to usbnet device
+ pIntfUnused [ I ] - Pointer to interface
+
+RETURN VALUE:
+ None
+===========================================================================*/
+static void GobiNetDriverUnbind(
+ struct usbnet * pDev,
+ struct usb_interface * pIntf)
+{
+ sGobiUSBNet * pGobiDev = (sGobiUSBNet *)pDev->data[0];
+ int counter = 0;
+ if(pGobiDev == NULL)
+ {
+ return ;
+ }
+ // Should already be down, but just in case...
+ netif_stop_queue(pDev->net);
+ netif_carrier_off( pDev->net );
+ #if LINUX_VERSION_CODE >= KERNEL_VERSION( 3,0,0 )
+ dev_deactivate(pDev->net);
+ #endif
+ while(pGobiDev->iTaskID>=0)
+ {
+ DBG("GobiNetDriverUnbind Probe not finish\n");
+ pGobiDev->mbUnload = eStatUnloading;
+ set_current_state(TASK_INTERRUPTIBLE);
+ wait_ms(100);
+ if( signal_pending(current))
+ {
+ break;
+ }
+ if(counter++>10)
+ break;
+ }
+ set_current_state(TASK_RUNNING);
+ if(pGobiDev->iTaskID>=0)
+ {
+ char szQMIBusName[64]={0};
+ struct usb_device *dev = interface_to_usbdev(pGobiDev->mUsb_Interface);
+ snprintf(szQMIBusName,63,"qcqmi%d-%d-%s:%d.%d",
+ (int)pGobiDev->mQMIDev.qcqmi,
+ dev->bus->busnum, dev->devpath,
+ dev->actconfig->desc.bConfigurationValue,
+ pGobiDev->mUsb_Interface->cur_altsetting->desc.bInterfaceNumber);
+ pGobiDev->mbUnload = eStatUnloading;
+ DBG("GobiNetDriverUnbind Probe not finish %s\n",szQMIBusName);
+ if(pGobiDev->iTaskID>=0)
+ {
+ if(pGobiDev->iTaskID>0)
+ {
+ StopTask(pGobiDev);
+ set_current_state(TASK_RUNNING);
+ wait_ms(500);
+ //gobi_flush_work();
+ }
+ pGobiDev->iTaskID = -1;
+ }
+
+
+ }
+ DeregisterQMIDevice( pGobiDev );
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 2,6,29 ))
+ kfree( pDev->net->netdev_ops );
+ pDev->net->netdev_ops = NULL;
+#endif
+
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,23 ))
+ pIntf->dev.platform_data = NULL;
+#endif
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 2,6,19 ))
+ pIntf->needs_remote_wakeup = 0;
+#endif
+
+ kfree( pGobiDev );
+ pGobiDev = NULL;
+}
+
+/*===========================================================================
+METHOD:
+ GobiNetDriverTxFixup (Public Method)
+
+DESCRIPTION:
+ Handling data format mode on transmit path
+
+PARAMETERS
+ pDev [ I ] - Pointer to usbnet device
+ pSKB [ I ] - Pointer to transmit packet buffer
+ flags [ I ] - os flags
+
+RETURN VALUE:
+ None
+===========================================================================*/
+struct sk_buff *GobiNetDriverTxFixup(
+ struct usbnet *pDev,
+ struct sk_buff *pSKB,
+ gfp_t flags)
+{
+ struct sGobiUSBNet * pGobiDev;
+ DBG( "\n" );
+ pGobiDev = (sGobiUSBNet *)pDev->data[0];
+ if (pGobiDev == NULL)
+ {
+ DBG( "failed to get QMIDevice\n" );
+ return NULL;
+ }
+ #ifdef CONFIG_PM
+ if(bIsSuspend(pGobiDev))
+ {
+ DBG("Suspended\n");
+ UsbAutopmGetInterface( pGobiDev->mpIntf );
+ usb_autopm_put_interface( pGobiDev->mpIntf );
+ }
+ #else
+ DBG( "\n" );
+ #endif
+ if(pGobiDev->iNetLinkStatus!=eNetDeviceLink_Connected)
+ {
+ DBG( "Dropped Packet : Not Connected\n" );
+ dev_kfree_skb_any(pSKB);
+ return NULL;
+ }
+ if(pGobiDev->iDataMode != eDataMode_RAWIP)
+ {
+ return pSKB;
+ }
+ if (pSKB->len > ETH_HLEN)
+ {
+
+ // Skip Ethernet header from message
+ if (skb_pull(pSKB, ETH_HLEN))
+ {
+ DBG( "For sending to device modified: ");
+ skb_reset_mac_header(pSKB);
+ skb_reset_network_header(pSKB);
+ skb_reset_transport_header(pSKB);
+ PrintHex (pSKB->data, pSKB->len);
+ return pSKB;
+ }
+ else
+ {
+ DBG( "Packet Dropped ");
+ }
+ }
+ else
+ {
+ DBG( "Packet Dropped Length");
+ }
+
+ // Filter the packet out, release it
+ dev_kfree_skb_any(pSKB);
+ return NULL;
+}
+
+/*===========================================================================
+METHOD:
+ GobiNetDriverRxFixup (Public Method)
+
+DESCRIPTION:
+ Handling data format mode on receive path
+
+PARAMETERS
+ pDev [ I ] - Pointer to usbnet device
+ pSKB [ I ] - Pointer to received packet buffer
+
+RETURN VALUE:
+ None
+===========================================================================*/
+static int GobiNetDriverRxFixup(
+ struct usbnet *pDev,
+ struct sk_buff *pSKB )
+{
+ sGobiUSBNet *pGobiDev;
+ struct ethhdr *pEth;
+ struct iphdr *pIp;
+
+ DBG( "\n" );
+
+ /* This check is no longer done by usbnet after 3.13*/
+ if (pSKB->len < pDev->net->hard_header_len)
+ {
+ printk( "Packet Dropped \n" );
+ return 0;
+ }
+ pGobiDev = (sGobiUSBNet *)pDev->data[0];
+ if (pGobiDev == NULL)
+ {
+ DBG( "failed to get Device\n" );
+ return -ENXIO;
+ }
+ #ifdef CONFIG_PM
+ if(bIsSuspend(pGobiDev))
+ {
+ DBG("Suspended\n");
+ UsbAutopmGetInterface( pGobiDev->mpIntf );
+ usb_autopm_put_interface( pGobiDev->mpIntf );
+ }
+ #endif
+ if(pGobiDev->iNetLinkStatus!=eNetDeviceLink_Connected)
+ {
+ DBG( "Dropped Packet : Not Connected\n" );
+ return 0;
+ }
+
+ DBG( "RX From Device: ");
+ PrintHex (pSKB->data, pSKB->len);
+
+ if(pSKB->truesize < ETH_HLEN+pSKB->len)
+ {
+ DBG( "DROP PACKET %d < %d\n",pSKB->truesize,ETH_HLEN+pSKB->len );
+ return 0;
+ }
+ /* Copy data section to a temporary buffer */
+ if (pSKB->head +ETH_HLEN > pSKB->data )
+ {
+ memmove(pSKB->data+ETH_HLEN,pSKB->data, pSKB->len);
+ pSKB->len = pSKB->len + ETH_HLEN;
+ pSKB->tail = pSKB->tail + ETH_HLEN;
+ }
+ else
+ {
+ DBG("pSKB->head :0x%p, pSKB->data 0x%p\n",pSKB->head , pSKB->data);
+ skb_push(pSKB,ETH_HLEN);
+ }
+
+ if (((*(u8 *)(pSKB->data + ETH_HLEN)) & 0xF0) == 0x40)
+ {
+ ResetEthHeader(pDev,pSKB,1);
+ return 1;
+ }
+ else if(((*(u8 *)(pSKB->data + ETH_HLEN)) & 0xF0) == 0x60)
+ {
+ ResetEthHeader(pDev,pSKB,0);
+ return 1;
+ }
+ else
+ {
+ skb_reset_mac_header(pSKB);
+ skb_reset_network_header(pSKB);
+ skb_reset_transport_header(pSKB);
+ }
+
+ pSKB->dev = pDev->net;
+
+ /* If the packet is IPv4 then add corresponding Ethernet header */
+ if (((*(u8 *)(pSKB->data + ETH_HLEN)) & 0xF0) == 0x40)
+ {
+ /* IPV4 packet */
+ memcpy(pSKB->data, pGobiDev->eth_hdr_tmpl_ipv4, ETH_HLEN);
+ pSKB->protocol = cpu_to_be16(ETH_P_IP);
+ DBG( "IPv4 header added: ");
+ }
+ else if (((*(u8 *)(pSKB->data + ETH_HLEN)) & 0xF0) == 0x60)
+ {
+ memcpy(pSKB->data, pGobiDev->eth_hdr_tmpl_ipv6, ETH_HLEN);
+ pSKB->protocol = cpu_to_be16(ETH_P_IPV6);
+ DBG( "IPv6 header added: ");
+ }
+
+ pIp = (struct iphdr *)((char *)pSKB->data + ETH_HLEN);
+ if(pIp->version == 6)
+ {
+ pEth = (struct ethhdr *)pSKB->data;
+ pEth->h_proto = cpu_to_be16(ETH_P_IPV6);
+ }
+
+ else if(pIp->version == 4)
+ {
+ pEth = (struct ethhdr *)pSKB->data;
+ pEth->h_proto = cpu_to_be16(ETH_P_IP);
+ }
+
+ PrintHex (pSKB->data, pSKB->len + ETH_HLEN);
+
+ return 1;
+}
+
+#ifdef CONFIG_PM
+/*===========================================================================
+METHOD:
+ GobiUSBNetURBCallback (Public Method)
+
+DESCRIPTION:
+ Write is complete, cleanup and signal that we're ready for next packet
+
+PARAMETERS
+ pURB [ I ] - Pointer to sAutoPM struct
+
+RETURN VALUE:
+ None
+===========================================================================*/
+void GobiUSBNetURBCallback( struct urb * pURB )
+{
+ unsigned long activeURBflags;
+ sAutoPM * pAutoPM = (sAutoPM *)pURB->context;
+ if (pAutoPM == NULL)
+ {
+ // Should never happen
+ DBG( "bad context\n" );
+ return;
+ }
+
+ if (pURB->status != 0)
+ {
+ // Note that in case of an error, the behaviour is no different
+ DBG( "urb finished with error %d\n", pURB->status );
+ }
+
+ // Remove activeURB (memory to be freed later)
+ spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags );
+
+ // EAGAIN used to signify callback is done
+ pAutoPM->mpActiveURB = ERR_PTR( -EAGAIN );
+
+ spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags );
+
+ complete( &pAutoPM->mThreadDoWork );
+
+ usb_free_urb( pURB );
+}
+
+/*===========================================================================
+METHOD:
+ GobiUSBNetTXTimeout (Public Method)
+
+DESCRIPTION:
+ Timeout declared by the net driver. Stop all transfers
+
+PARAMETERS
+ pNet [ I ] - Pointer to net device
+
+RETURN VALUE:
+ None
+===========================================================================*/
+void GobiUSBNetTXTimeout( struct net_device * pNet )
+{
+ struct sGobiUSBNet * pGobiDev;
+ sAutoPM * pAutoPM;
+ sURBList * pURBListEntry;
+ unsigned long activeURBflags, URBListFlags;
+ struct usbnet * pDev = NULL;
+ struct urb * pURB;
+
+ if(pNet==NULL)
+ {
+ DBG( "GobiUSBNetTXTimeout failed to get Net\n" );
+ return;
+ }
+ pDev = netdev_priv( pNet );
+ if (pDev == NULL || pDev->net == NULL)
+ {
+ DBG( "failed to get usbnet device\n" );
+ return;
+ }
+
+ pGobiDev = (sGobiUSBNet *)pDev->data[0];
+ if (pGobiDev == NULL)
+ {
+ DBG( "failed to get QMIDevice\n" );
+ return;
+ }
+ pAutoPM = &pGobiDev->mAutoPM;
+
+ DBG( "\n" );
+
+ // Grab a pointer to active URB
+ spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags );
+ pURB = pAutoPM->mpActiveURB;
+ spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags );
+ // Stop active URB
+ if (pURB != NULL)
+ {
+ usb_kill_urb( pURB );
+ }
+
+ // Cleanup URB List
+ spin_lock_irqsave( &pAutoPM->mURBListLock, URBListFlags );
+
+ pURBListEntry = pAutoPM->mpURBList;
+ while (pURBListEntry != NULL)
+ {
+ pAutoPM->mpURBList = pAutoPM->mpURBList->mpNext;
+ atomic_dec( &pAutoPM->mURBListLen );
+ usb_free_urb( pURBListEntry->mpURB );
+ kfree( pURBListEntry );
+ pURBListEntry = pAutoPM->mpURBList;
+ }
+
+ spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags );
+
+ complete( &pAutoPM->mThreadDoWork );
+
+ return;
+}
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 ))
+/*===========================================================================
+METHOD:
+ GobiUSBNetAutoPMThread (Public Method)
+
+DESCRIPTION:
+ Handle device Auto PM state asynchronously
+ Handle network packet transmission asynchronously
+
+PARAMETERS
+ pData [ I ] - Pointer to sAutoPM struct
+
+RETURN VALUE:
+ int - 0 for success
+ Negative errno for error
+===========================================================================*/
+static int GobiUSBNetAutoPMThread( void * pData )
+{
+ unsigned long activeURBflags, URBListFlags;
+ sURBList * pURBListEntry;
+ int status;
+ struct usb_device * pUdev;
+ sAutoPM * pAutoPM = (sAutoPM *)pData;
+ struct urb * pURB;
+
+ if (pAutoPM == NULL)
+ {
+ DBG( "passed null pointer\n" );
+ return -EINVAL;
+ }
+
+ pUdev = interface_to_usbdev( pAutoPM->mpIntf );
+
+ DBG( "traffic thread started\n" );
+
+ while (pAutoPM->mbExit == false)
+ {
+ // Wait for someone to poke us
+ wait_for_completion_interruptible( &pAutoPM->mThreadDoWork );
+
+ // Time to exit?
+ if (pAutoPM->mbExit == true)
+ {
+ // Stop activeURB
+ spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags );
+ pURB = pAutoPM->mpActiveURB;
+ spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags );
+
+ // EAGAIN used to signify callback is done
+ if (IS_ERR( pAutoPM->mpActiveURB )
+ && PTR_ERR( pAutoPM->mpActiveURB ) == -EAGAIN )
+ {
+ pURB = NULL;
+ }
+
+ if (pURB != NULL)
+ {
+ usb_kill_urb( pURB );
+ }
+ // Will be freed in callback function
+
+ // Cleanup URB List
+ spin_lock_irqsave( &pAutoPM->mURBListLock, URBListFlags );
+
+ pURBListEntry = pAutoPM->mpURBList;
+ while (pURBListEntry != NULL)
+ {
+ pAutoPM->mpURBList = pAutoPM->mpURBList->mpNext;
+ atomic_dec( &pAutoPM->mURBListLen );
+ 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;
+ atomic_dec( &pAutoPM->mURBListLen );
+ 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
+ GobiNetSuspend( pAutoPM->mpIntf, PMSG_SUSPEND );
+ }
+
+ // Add pURBListEntry back onto pAutoPM->mpURBList
+ spin_lock_irqsave( &pAutoPM->mURBListLock, URBListFlags );
+ pURBListEntry->mpNext = pAutoPM->mpURBList;
+ pAutoPM->mpURBList = pURBListEntry;
+ atomic_inc( &pAutoPM->mURBListLen );
+ spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags );
+
+ spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags );
+ pAutoPM->mpActiveURB = NULL;
+ spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags );
+
+ // Go back to sleep
+ continue;
+ }
+
+ // Submit URB
+ status = usb_submit_urb( pAutoPM->mpActiveURB, GFP_KERNEL );
+ if (status < 0)
+ {
+ // Could happen for a number of reasons
+ DBG( "Failed to submit URB: %d. Packet dropped\n", status );
+ spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags );
+ usb_free_urb( pAutoPM->mpActiveURB );
+ pAutoPM->mpActiveURB = NULL;
+ spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags );
+ usb_autopm_put_interface( pAutoPM->mpIntf );
+
+ // Loop again
+ complete( &pAutoPM->mThreadDoWork );
+ }
+
+ kfree( pURBListEntry );
+ }
+
+ DBG( "traffic thread exiting\n" );
+ pAutoPM->mpThread = NULL;
+ return 0;
+}
+#endif //#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 ))
+/*===========================================================================
+METHOD:
+ GobiUSBNetStartXmit (Public Method)
+
+DESCRIPTION:
+ Convert sk_buff to usb URB and queue for transmit
+
+PARAMETERS
+ pNet [ I ] - Pointer to net device
+
+RETURN VALUE:
+ NETDEV_TX_OK on success
+ NETDEV_TX_BUSY on error
+===========================================================================*/
+int GobiUSBNetStartXmit(
+ struct sk_buff * pSKB,
+ struct net_device * pNet )
+{
+ unsigned long URBListFlags;
+ struct sGobiUSBNet * pGobiDev;
+ sAutoPM * pAutoPM;
+ sURBList * pURBListEntry, ** ppURBListEnd;
+ void * pURBData;
+ struct usbnet * pDev = NULL;
+ struct driver_info *info;
+
+ DBG( "\n" );
+ if(pNet==NULL)
+ {
+ DBG( "GobiUSBNetStartXmit failed to get Net\n" );
+ return -ENXIO;
+ }
+ pDev = netdev_priv( pNet );
+ if (pDev == NULL || pDev->net == NULL)
+ {
+ DBG( "failed to get usbnet device\n" );
+ return NETDEV_TX_BUSY;
+ }
+
+ pGobiDev = (sGobiUSBNet *)pDev->data[0];
+ if (pGobiDev == NULL)
+ {
+ DBG( "failed to get QMIDevice\n" );
+ return NETDEV_TX_BUSY;
+ }
+ /* send out the packet when data connection status is connected */
+ if ( pGobiDev->bLinkState == false)
+ {
+ return NET_XMIT_DROP;
+ }
+ pAutoPM = &pGobiDev->mAutoPM;
+
+ if( NULL == pSKB )
+ {
+ DBG( "Buffer is NULL \n" );
+ return NETDEV_TX_BUSY;
+ }
+
+ if (GobiTestDownReason( pGobiDev, DRIVER_SUSPENDED ) == true)
+ {
+ // Should not happen
+ DBG( "device is suspended\n" );
+ dump_stack();
+ return NETDEV_TX_BUSY;
+ }
+
+ // Convert the sk_buff into a URB
+
+ // Check if buffer is full
+ pGobiDev->tx_qlen = atomic_read( &pAutoPM->mURBListLen );
+ if ( pGobiDev->tx_qlen >= txQueueLength)
+ {
+ DBG( "not scheduling request, buffer is full\n" );
+ return NETDEV_TX_BUSY;
+ }
+
+ if(pGobiDev->iDataMode==eDataMode_RAWIP)
+ {
+ info = pDev->driver_info;
+ if (info->tx_fixup)
+ {
+ pSKB = info->tx_fixup( pDev, pSKB, GFP_ATOMIC);
+ if (pSKB == NULL)
+ {
+ DBG( "unable to tx_fixup skb\n" );
+ return NETDEV_TX_BUSY;
+ }
+ }
+ }
+ // Allocate URBListEntry
+ pURBListEntry = kmalloc( sizeof( sURBList ), GFP_ATOMIC );
+ if (pURBListEntry == NULL)
+ {
+ DBG( "unable to allocate URBList memory\n" );
+ if (pSKB)
+ dev_kfree_skb_any ( pSKB );
+ 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" );
+ // release all memory allocated by now
+ if (pURBListEntry)
+ kfree( pURBListEntry );
+ if (pSKB)
+ dev_kfree_skb_any ( pSKB );
+ return NETDEV_TX_BUSY;
+ }
+ // Allocate URB transfer_buffer
+ pURBData = kmalloc( pSKB->len, GFP_ATOMIC );
+ if (pURBData == NULL)
+ {
+ DBG( "unable to allocate URB data\n" );
+ // release all memory allocated by now
+ if (pURBListEntry)
+ {
+ usb_free_urb(pURBListEntry->mpURB);
+ kfree( pURBListEntry );
+ }
+ if (pSKB)
+ dev_kfree_skb_any ( pSKB );
+ return NETDEV_TX_BUSY;
+
+ }
+ // Fill with SKB's data
+ memcpy( pURBData, pSKB->data, pSKB->len );
+
+ usb_fill_bulk_urb( pURBListEntry->mpURB,
+ pGobiDev->mpNetDev->udev,
+ pGobiDev->mpNetDev->out,
+ pURBData,
+ pSKB->len,
+ GobiUSBNetURBCallback,
+ pAutoPM );
+
+ /* Handle the need to send a zero length packet and release the
+ * transfer buffer
+ */
+ pURBListEntry->mpURB->transfer_flags |= (URB_ZERO_PACKET | URB_FREE_BUFFER);
+
+ // 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;
+ atomic_inc( &pAutoPM->mURBListLen );
+
+ spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags );
+
+ complete( &pAutoPM->mThreadDoWork );
+
+ // Free SKB
+ if (pSKB)
+ dev_kfree_skb_any ( pSKB );
+
+ return NETDEV_TX_OK;
+}
+#endif /* CONFIG_PM */
+
+/*===========================================================================
+METHOD:
+ GobiUSBNetOpen (Public Method)
+
+DESCRIPTION:
+ Wrapper to usbnet_open, correctly handling autosuspend
+ Start AutoPM thread (if CONFIG_PM is defined)
+
+PARAMETERS
+ pNet [ I ] - Pointer to net device
+
+RETURN VALUE:
+ int - 0 for success
+ Negative errno for error
+===========================================================================*/
+int GobiUSBNetOpen( struct net_device * pNet )
+{
+ int status = 0;
+ struct sGobiUSBNet * pGobiDev = NULL;
+ struct usbnet * pDev = NULL;
+ if(pNet==NULL)
+ {
+ DBG( "GobiUSBNetOpen failed to get Net device\n" );
+ return -ENXIO;
+ }
+ pDev = netdev_priv( pNet );
+
+ if (pDev == NULL)
+ {
+ DBG( "failed to get usbnet device\n" );
+ return -ENXIO;
+ }
+
+ pGobiDev = (sGobiUSBNet *)pDev->data[0];
+ if (pGobiDev == NULL)
+ {
+ DBG( "failed to get QMIDevice\n" );
+ return -ENXIO;
+ }
+
+ DBG( "\n" );
+
+#ifdef CONFIG_PM
+ #if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 ))
+ // Start the AutoPM thread
+ pGobiDev->mAutoPM.mpIntf = pGobiDev->mpIntf;
+ pGobiDev->mAutoPM.mbExit = false;
+ pGobiDev->mAutoPM.mpURBList = NULL;
+ pGobiDev->mAutoPM.mpActiveURB = NULL;
+ spin_lock_init( &pGobiDev->mAutoPM.mURBListLock );
+ spin_lock_init( &pGobiDev->mAutoPM.mActiveURBLock );
+ atomic_set( &pGobiDev->mAutoPM.mURBListLen, 0 );
+ init_completion( &pGobiDev->mAutoPM.mThreadDoWork );
+
+ pGobiDev->mAutoPM.mpThread = kthread_run( GobiUSBNetAutoPMThread,
+ &pGobiDev->mAutoPM,
+ "GobiUSBNetAutoPMThread" );
+ if (IS_ERR( pGobiDev->mAutoPM.mpThread ))
+ {
+ DBG( "AutoPM thread creation error\n" );
+ return PTR_ERR( pGobiDev->mAutoPM.mpThread );
+ }
+ #endif
+#endif /* CONFIG_PM */
+
+ if(pGobiDev->iNetLinkStatus==eNetDeviceLink_Connected)
+ {
+ // Allow traffic
+ GobiClearDownReason( pGobiDev, NET_IFACE_STOPPED );
+ }
+ else
+ {
+ GobiSetDownReason( pGobiDev, NO_NDIS_CONNECTION );
+ }
+
+ // Pass to usbnet_open if defined
+ if (pGobiDev->mpUSBNetOpen != NULL)
+ {
+ status = pGobiDev->mpUSBNetOpen( pNet );
+#ifdef CONFIG_PM
+ // If usbnet_open was successful enable Auto PM
+ if (status == 0)
+ {
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,33 ))
+ usb_autopm_enable( pGobiDev->mpIntf );
+#else
+ usb_autopm_put_interface( pGobiDev->mpIntf );
+#endif
+ }
+#endif /* CONFIG_PM */
+ }
+ else
+ {
+ DBG( "no USBNetOpen defined\n" );
+ }
+
+ return status;
+}
+
+/*===========================================================================
+METHOD:
+ GobiUSBNetStop (Public Method)
+
+DESCRIPTION:
+ Wrapper to usbnet_stop, correctly handling autosuspend
+ Stop AutoPM thread (if CONFIG_PM is defined)
+
+PARAMETERS
+ pNet [ I ] - Pointer to net device
+
+RETURN VALUE:
+ int - 0 for success
+ Negative errno for error
+===========================================================================*/
+int GobiUSBNetStop( struct net_device * pNet )
+{
+ struct sGobiUSBNet * pGobiDev;
+ struct usbnet * pDev = NULL;
+
+ if (pNet == NULL)
+ {
+ DBG( "GobiUSBNetStop failed to get Net\n" );
+ return -ENXIO;
+ }
+ pDev = netdev_priv( pNet );
+
+ if (pDev == NULL || pDev->net == NULL)
+ {
+ DBG( "failed to get netdevice\n" );
+ return -ENXIO;
+ }
+
+ pGobiDev = (sGobiUSBNet *)pDev->data[0];
+ if (pGobiDev == NULL)
+ {
+ DBG( "failed to get QMIDevice\n" );
+ return -ENXIO;
+ }
+
+ // Stop traffic
+ GobiSetDownReason( pGobiDev, NET_IFACE_STOPPED );
+
+#ifdef CONFIG_PM
+ #if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 ))
+ // Tell traffic thread to exit
+ pGobiDev->mAutoPM.mbExit = true;
+ complete( &pGobiDev->mAutoPM.mThreadDoWork );
+
+ // Wait for it to exit
+ while( pGobiDev->mAutoPM.mpThread != NULL )
+ {
+ set_current_state(TASK_INTERRUPTIBLE);
+ wait_ms(100);
+ if( (signal_pending(current))
+ {
+ break;
+ }
+ }
+ set_current_state(TASK_RUNNING);
+ DBG( "thread stopped\n" );
+ #endif
+#endif /* CONFIG_PM */
+
+ // Pass to usbnet_stop, if defined
+ if (pGobiDev->mpUSBNetStop != NULL)
+ {
+ return pGobiDev->mpUSBNetStop( pNet );
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+/* reset the Ethernet header with correct destionation address and ip protocol */
+void ResetEthHeader(struct usbnet *dev, struct sk_buff *skb, int isIpv4)
+{
+ __be16 ip_type;
+
+ skb_reset_mac_header(skb);
+ skb_reset_network_header(skb);
+ skb_reset_transport_header(skb);
+ /* replace the correct destination address */
+ memset(eth_hdr(skb)->h_source, 0, ETH_ALEN);
+ memcpy(eth_hdr(skb)->h_dest, dev->net->dev_addr, ETH_ALEN);
+ ip_type = isIpv4 == 1 ? ETH_P_IP : ETH_P_IPV6;
+ eth_hdr(skb)->h_proto = cpu_to_be16(ip_type);
+
+ PrintHex (skb->data, skb->len);
+}
+
+/* check the packet if the Etherenet header is corrupted or not, if yes,
+ * correct the Ethernet header with replacing destination address and ip protocol.
+ * if no, do nothing
+ */
+int FixEthFrame(struct usbnet *dev, struct sk_buff *skb, int isIpv4)
+{
+ __be16 proto;
+ u16 total_len, payload_len;
+
+ /* All MAC-48 multicast identifiers prefixed "33-33", do not overwrite the MAC address if it is not corrupted */
+ if ((skb->data[0] == MAC48_MULTICAST_ID) && (skb->data[1] == MAC48_MULTICAST_ID))
+ {
+ proto = ((skb->data[ETH_HLEN-2] << 8) & 0xff) |(skb->data[ETH_HLEN-1]);
+ /* check the IP type field, if it is correct, we can consider this is not a corrupted packet */
+ if (proto == skb->protocol)
+ {
+ DBG( "multicast MAC address: destination matched, pass through ");
+ /* correct packet, pass through */
+ return 1;
+ }
+ else
+ {
+ DBG( "multicast MAC address: destination mismatched, IPV%s header modified:", isIpv4 == 1 ? "4":"6");
+ ResetEthHeader(dev, skb, isIpv4);
+ return 1;
+ }
+ }
+ else if (memcmp(&skb->data[0], &dev->net->dev_addr[0], ETH_ALEN) == 0)
+ {
+ /* MAC address is correct, no need to overwrite, pass through */
+ DBG( "correct packet, pass through ");
+ return 1;
+ }
+ else
+ {
+ if (isIpv4)
+ {
+ /* ipv4 */
+ total_len = ((skb->data[ETH_HLEN+IPV4HDR_TOT_UPPER] << 8) & 0xff) | (skb->data[ETH_HLEN+IPV4HDR_TOT_LOWER]);
+ DBG( "ipv4 header: total length = %d\n", total_len);
+ /* total length includes IP header and payload, hence it plus Ethernet header length should be equal to
+ the skb buffer length if the Etherent header is presented in the skb buffer*/
+ if (skb->len >= (total_len+ETH_HLEN))
+ {
+ DBG( "IPv4 header modified: ");
+ ResetEthHeader(dev, skb, isIpv4);
+ return 1;
+ }
+ }
+ else
+ {
+ /* ipv6 */
+ payload_len = ((skb->data[ETH_HLEN+IPV6HDR_PAYLOAD_UPPER] << 8) & 0xff) | (skb->data[ETH_HLEN+IPV6HDR_PAYLOAD_LOWER]);
+ DBG( "ipv6 header: payload length = %d\n", payload_len);
+ /* for IPV6, the playload length does not include ipv6 header */
+ if (skb->len >= (payload_len+ETH_HLEN+IPV6HDR_LENGTH))
+ {
+ DBG( "IPv6 header modified: ");
+ ResetEthHeader(dev, skb, isIpv4);
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+/* Make up an ethernet header if the packet doesn't have one.
+ *
+ * A firmware bug common among several devices cause them to send raw
+ * IP packets under some circumstances. There is no way for the
+ * driver/host to know when this will happen. And even when the bug
+ * hits, some packets will still arrive with an intact header.
+ *
+ * The supported devices are only capably of sending IPv4, IPv6 and
+ * ARP packets on a point-to-point link. Any packet with an ethernet
+ * header will have either our address or a broadcast/multicast
+ * address as destination. ARP packets will always have a header.
+ *
+ * This means that this function will reliably add the appropriate
+ * header iff necessary, provided our hardware address does not start
+ * with 4 or 6.
+ *
+ * Another common firmware bug results in all packets being addressed
+ * to 00:a0:c6:00:00:00 despite the host address being different.
+ * This function will also fixup such packets.
+ */
+static int GobiNetDriverLteRxFixup(struct usbnet *dev, struct sk_buff *skb)
+{
+ __be16 proto;
+ struct sGobiUSBNet * pGobiDev;
+ pGobiDev = (sGobiUSBNet *)dev->data[0];
+ if (pGobiDev == NULL)
+ {
+ DBG( "failed to get QMIDevice\n" );
+ return 0;
+ }
+ #ifdef CONFIG_PM
+ if(bIsSuspend(pGobiDev))
+ {
+ DBG("Suspended\n");
+ UsbAutopmGetInterface( pGobiDev->mpIntf );
+ usb_autopm_put_interface( pGobiDev->mpIntf );
+ }
+ #endif
+ if(pGobiDev->iNetLinkStatus!=eNetDeviceLink_Connected)
+ {
+ DBG( "Dropped Packet : Not Connected\n" );
+ return 0;
+ }
+ if(pGobiDev->iDataMode==eDataMode_RAWIP)
+ {
+ return GobiNetDriverRxFixup(dev,skb);
+ }
+ if (skb->len < dev->net->hard_header_len)
+ {
+ printk( "Packet Dropped \n" );
+ return 0;
+ }
+ DBG( "From Modem: ");
+ PrintHex (skb->data, skb->len);
+
+ /* special handling for corrupted Ethernet header packet if any */
+ if ((skb->data[ETH_HLEN] & 0xF0) == 0x40)
+ {
+ /* check if need to correct the IPV4 Ethernet header or not */
+ if (FixEthFrame(dev, skb, 1))
+ {
+ /* pass through */
+ return 1;
+ }
+ }
+ else if ((skb->data[ETH_HLEN] & 0xF0) == 0x60)
+ {
+ /* check if need to correct the IPV6 Ethernet header or not */
+ if (FixEthFrame(dev, skb, 0))
+ {
+ /* pass through */
+ return 1;
+ }
+ }
+
+ /* usbnet rx_complete guarantees that skb->len is at least
+ * hard_header_len, so we can inspect the dest address without
+ * checking skb->len
+ */
+ switch (skb->data[0] & 0xf0) {
+ case 0x40:
+ proto = htons(ETH_P_IP);
+ break;
+ case 0x60:
+ proto = htons(ETH_P_IPV6);
+ break;
+ case 0x00:
+ if (is_multicast_ether_addr(skb->data))
+ return 1;
+ /* possibly bogus destination - rewrite just in case */
+ skb_reset_mac_header(skb);
+ skb_reset_network_header(skb);
+ skb_reset_transport_header(skb);
+ goto fix_dest;
+ default:
+ {
+ /* pass along other packets without modifications */
+ return 1;
+ }
+ }
+ if (skb_headroom(skb) < ETH_HLEN)
+ return 0;
+ skb_push(skb, ETH_HLEN);
+ skb_reset_mac_header(skb);
+ skb_reset_network_header(skb);
+ skb_reset_transport_header(skb);
+ eth_hdr(skb)->h_proto = proto;
+ memset(eth_hdr(skb)->h_source, 0, ETH_ALEN);
+fix_dest:
+ memcpy(eth_hdr(skb)->h_dest, dev->net->dev_addr, ETH_ALEN);
+ DBG( "To IP Stack: ");
+ PrintHex (skb->data, skb->len);
+ return 1;
+}
+
+/*=========================================================================*/
+// Struct driver_info
+/*=========================================================================*/
+static const struct driver_info GobiNetInfo_qmi = {
+ .description = "QmiNet Ethernet Device",
+ .flags = FLAG_ETHER,
+ .bind = GobiNetDriverBind,
+ .unbind = GobiNetDriverUnbind,
+//FIXME refactor below fixup handling at cases below
+ .rx_fixup = GobiNetDriverLteRxFixup,
+ .tx_fixup = GobiNetDriverTxFixup,
+ .data = BIT(8) | BIT(19) |
+ BIT(10), /* MDM9x15 PDNs */
+#ifdef CONFIG_PM
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 3,8,0 ))
+ .manage_power = usbnet_manage_power,
+#endif
+#endif
+};
+
+static const struct driver_info GobiNetInfo_gobi = {
+ .description = "GobiNet Ethernet Device",
+ .flags = FLAG_ETHER,
+ .bind = GobiNetDriverBind,
+ .unbind = GobiNetDriverUnbind,
+ .rx_fixup = GobiNetDriverLteRxFixup,
+ .tx_fixup = GobiNetDriverTxFixup,
+ .data = BIT(0) | BIT(5),
+#ifdef CONFIG_PM
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 3,8,0 ))
+ .manage_power = usbnet_manage_power,
+#endif
+#endif
+};
+
+static const struct driver_info GobiNetInfo_9x15 = {
+ .description = "GobiNet Ethernet Device",
+ .flags = FLAG_ETHER,
+ .bind = GobiNetDriverBind,
+ .unbind = GobiNetDriverUnbind,
+ .rx_fixup = GobiNetDriverLteRxFixup,
+ .tx_fixup = GobiNetDriverTxFixup,
+ .data = BIT(8) | BIT(10) | BIT(BIT_9X15),
+#ifdef CONFIG_PM
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 3,8,0 ))
+ .manage_power = usbnet_manage_power,
+#endif
+#endif
+};
+
+
+#define QMI_G3K_DEVICE(vend, prod) \
+ USB_DEVICE(vend, prod), \
+ .driver_info = (unsigned long)&GobiNetInfo_gobi
+
+#define QMI_9X15_DEVICE(vend, prod) \
+ USB_DEVICE(vend, prod), \
+ .driver_info = (unsigned long)&GobiNetInfo_9x15
+
+/*=========================================================================*/
+// Qualcomm Gobi 3000 VID/PIDs
+/*=========================================================================*/
+static const struct usb_device_id GobiVIDPIDTable [] =
+{
+ // Sierra Wireless MC7750 QMI Device VID/PID
+ {
+ USB_DEVICE( 0x1199, 0x68a2 ),
+ .driver_info = (unsigned long)&GobiNetInfo_qmi,
+ },
+
+ // Gobi 3000
+ {QMI_G3K_DEVICE(0x05c6, 0x920d)},
+ {QMI_G3K_DEVICE(0x1199, 0x9011)},
+ {QMI_G3K_DEVICE(0x1199, 0x9013)},
+ {QMI_G3K_DEVICE(0x1199, 0x9015)},
+ {QMI_G3K_DEVICE(0x1199, 0x9019)},
+ {QMI_G3K_DEVICE(0x03f0, 0x371d)},
+ // 9x15
+ {QMI_9X15_DEVICE(0x1199, 0x9071)}, /* consider 9x30 same as 9x15 at the moment, change it later if needed */
+ {QMI_9X15_DEVICE(0x1199, 0x68C0)},
+ {QMI_9X15_DEVICE(0x1199, 0x9041)},
+ {QMI_9X15_DEVICE(0x1199, 0x9051)},
+ {QMI_9X15_DEVICE(0x1199, 0x9053)},
+ {QMI_9X15_DEVICE(0x1199, 0x9054)},
+ {QMI_9X15_DEVICE(0x1199, 0x9055)},
+ {QMI_9X15_DEVICE(0x1199, 0x9056)},
+ {QMI_9X15_DEVICE(0x1199, 0x9061)},
+
+ //9x30
+ {QMI_9X15_DEVICE(0x1199, 0x9070)},
+
+ //AR759x
+ {QMI_9X15_DEVICE(0x1199, 0x9100)},
+
+ //AR758x
+ {QMI_9X15_DEVICE(0x1199, 0x9102)},
+
+ //AR758x
+ {QMI_9X15_DEVICE(0x1199, 0x9110)},
+ //Terminating entry
+ { }
+};
+
+MODULE_DEVICE_TABLE( usb, GobiVIDPIDTable );
+/*===========================================================================
+METHOD:
+ PrintCurrentUSBSpeed (Public Method)
+
+DESCRIPTION:
+ Print Current USB Speed
+
+PARAMETERS
+ pDev [ I ] - Pointer to usbnet
+
+RETURN VALUE:
+ NULL
+===========================================================================*/
+
+void PrintCurrentUSBSpeed(struct usbnet * pDev)
+{
+ enum usb_device_speed {
+ USB_SPEED_UNKNOWN = 0, /* enumerating */
+ USB_SPEED_LOW, USB_SPEED_FULL, /* usb 1.1 */
+ USB_SPEED_HIGH, /* usb 2.0 */
+ USB_SPEED_WIRELESS, /* wireless (usb 2.5) */
+ USB_SPEED_SUPER, /* usb 3.0 */
+ };
+ switch(pDev->udev->speed)
+ {
+ case USB_SPEED_LOW:
+ printk("USB Speed : USB 1.0 SPEED LOW\n");
+ break;
+ case USB_SPEED_FULL:
+ printk("USB Speed : USB 1.0 SPEED FULL\n");
+ break;
+ case USB_SPEED_HIGH:
+ printk("USB Speed : USB 2.0\n");
+ break;
+ case USB_SPEED_WIRELESS:
+ printk("USB Speed : USB 2.5\n");
+ break;
+ case USB_SPEED_SUPER:
+ printk("USB Speed : USB 3.0\n");
+ break;
+ case USB_SPEED_UNKNOWN:
+ default:
+ printk("USB Speed : USB SPEED UNKNOWN\n");
+ break;
+ }
+}
+
+
+/*===========================================================================
+METHOD:
+ GobiUSBNetProbe (Public Method)
+
+DESCRIPTION:
+ Run usbnet_probe
+ Setup QMI device
+
+PARAMETERS
+ pIntf [ I ] - Pointer to interface
+ pVIDPIDs [ I ] - Pointer to VID/PID table
+
+RETURN VALUE:
+ int - 0 for success
+ Negative errno for error
+===========================================================================*/
+int GobiUSBNetProbe(
+ struct usb_interface * pIntf,
+ const struct usb_device_id * pVIDPIDs )
+{
+ int is9x15 = 0;
+ unsigned char ifacenum;
+ int status;
+ struct usbnet * pDev;
+ sGobiUSBNet * pGobiDev;
+ struct ethhdr *eth;
+
+#if 0
+ /* There exists a race condition in the firmware that sometimes results
+ * in the absence of Ethernet framing of packets received from the device.
+ * Therefore, a firmware work-around currently hard-codes the MAC address
+ * to ensure valid Ethernet frames are sent to the host. We therefore
+ * hard-code the network device MAC address to comply with the firmware
+ */
+ const char default_addr[6] = {0x00, 0xa0, 0xc6, 0x00, 0x00, 0x00};
+#endif
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 2,6,29 ))
+ struct net_device_ops * pNetDevOps;
+#endif
+
+ ifacenum = pIntf->cur_altsetting->desc.bInterfaceNumber;
+
+ 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,19 ))
+ pIntf->needs_remote_wakeup = 1;
+#endif
+
+#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,23 ))
+ pDev = usb_get_intfdata( pIntf );
+#else
+ pDev = (struct usbnet *)pIntf->dev.platform_data;
+#endif
+
+ if (pDev == NULL || pDev->net == NULL)
+ {
+ DBG( "failed to get netdevice\n" );
+ usbnet_disconnect( pIntf );
+ return -ENXIO;
+ }
+
+ pGobiDev = kzalloc( sizeof( sGobiUSBNet ), GFP_KERNEL );
+ if (pGobiDev == NULL)
+ {
+ DBG( "falied to allocate device buffers" );
+ usbnet_disconnect( pIntf );
+ return -ENOMEM;
+ }
+
+ pGobiDev->WDSClientID = (u16)-1;
+ pGobiDev->iDataMode = eDataMode_Ethernet;
+ pDev->data[0] = (unsigned long)pGobiDev;
+
+ pGobiDev->mpNetDev = pDev;
+
+ // Clearing endpoint halt is a magic handshake that brings
+ // the device out of low power (airplane) mode
+ // NOTE: FCC verification should be done before this, if required
+ usb_clear_halt( pGobiDev->mpNetDev->udev, pDev->out );
+
+ // Overload PM related network functions
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 ))
+ pGobiDev->mpUSBNetOpen = pDev->net->open;
+ pDev->net->open = GobiUSBNetOpen;
+ pGobiDev->mpUSBNetStop = pDev->net->stop;
+ pDev->net->stop = GobiUSBNetStop;
+ pDev->net->hard_start_xmit = GobiUSBNetStartXmit;
+ pDev->net->tx_timeout = GobiUSBNetTXTimeout;
+#else
+ pNetDevOps = kmalloc( sizeof( struct net_device_ops ), GFP_KERNEL );
+ if (pNetDevOps == NULL)
+ {
+ DBG( "falied to allocate net device ops" );
+ usbnet_disconnect( pIntf );
+ return -ENOMEM;
+ }
+ memcpy( pNetDevOps, pDev->net->netdev_ops, sizeof( struct net_device_ops ) );
+
+ pGobiDev->mpUSBNetOpen = pNetDevOps->ndo_open;
+ pNetDevOps->ndo_open = GobiUSBNetOpen;
+ pGobiDev->mpUSBNetStop = pNetDevOps->ndo_stop;
+ pNetDevOps->ndo_stop = GobiUSBNetStop;
+#ifdef TX_XMIT_SIERRA
+#if (LINUX_VERSION_CODE == KERNEL_VERSION( 2,6,31 ) ||\
+ LINUX_VERSION_CODE == KERNEL_VERSION( 2,6,32 ))
+ pNetDevOps->ndo_start_xmit = gobi_usbnet_start_xmit_2_6_32;
+ pNetDevOps->ndo_tx_timeout = gobi_usbnet_tx_timeout_2_6_32;
+#elif (LINUX_VERSION_CODE == KERNEL_VERSION( 2,6,35 ))
+ pNetDevOps->ndo_start_xmit = gobi_usbnet_start_xmit_2_6_35;
+ pNetDevOps->ndo_tx_timeout = gobi_usbnet_tx_timeout_2_6_35;
+#elif (LINUX_VERSION_CODE == KERNEL_VERSION( 3,0,6 ))
+ pNetDevOps->ndo_start_xmit = gobi_usbnet_start_xmit_3_0_6;
+ pNetDevOps->ndo_tx_timeout = gobi_usbnet_tx_timeout_3_0_6;
+#elif (LINUX_VERSION_CODE >= KERNEL_VERSION( 3,10,1 ) &&\
+ LINUX_VERSION_CODE <= KERNEL_VERSION( 3,10,39 ))
+ pNetDevOps->ndo_start_xmit = gobi_usbnet_start_xmit_3_10_21;
+ pNetDevOps->ndo_tx_timeout = gobi_usbnet_tx_timeout_3_10_21;
+
+#elif (LINUX_VERSION_CODE >= KERNEL_VERSION( 3,12,0 ) &&\
+ LINUX_VERSION_CODE < KERNEL_VERSION( 3,13,0 ))
+ pNetDevOps->ndo_start_xmit = gobi_usbnet_start_xmit_3_12_xx;
+ pNetDevOps->ndo_tx_timeout = gobi_usbnet_tx_timeout_3_12_xx;
+#elif (LINUX_VERSION_CODE >= KERNEL_VERSION( 4,4,0 ) &&\
+ LINUX_VERSION_CODE < KERNEL_VERSION( 4,5,0 ))
+ pNetDevOps->ndo_start_xmit = gobi_usbnet_start_xmit_4_4_xx;
+ pNetDevOps->ndo_tx_timeout = gobi_usbnet_tx_timeout_4_4_xx;
+#endif /* #if (LINUX_VERSION_CODE == KERNEL_VERSION( 2,6,31 ) */
+#else
+ pNetDevOps->ndo_start_xmit = usbnet_start_xmit;
+ pNetDevOps->ndo_tx_timeout = usbnet_tx_timeout;
+#endif /* TX_XMIT_SIERRA */
+
+ pDev->net->netdev_ops = pNetDevOps;
+#endif
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,31 ))
+ memset( &(pGobiDev->mpNetDev->stats), 0, sizeof( struct net_device_stats ) );
+#else
+ memset( &(pGobiDev->mpNetDev->net->stats), 0, sizeof( struct net_device_stats ) );
+#endif
+
+ pGobiDev->mpIntf = pIntf;
+ memset( &(pGobiDev->mMEID), '0', MAX_DEVICE_MEID_SIZE );
+
+ /* change MAC addr to include, ifacenum, and to be unique */
+ pGobiDev->mpNetDev->net->dev_addr[ETH_ALEN-1] = ifacenum;
+
+ DBG( "Mac Address:\n" );
+ PrintHex( &pGobiDev->mpNetDev->net->dev_addr[0], 6 );
+#if 0 /* interfers with multiple interface support and no longer appears to be necessary */
+ /* Hard-code the host MAC address to comply with the firmware workaround */
+ memcpy(&pGobiDev->mpNetDev->net->dev_addr[0], &default_addr[0], 6);
+ DBG( "Default Mac Address:\n" );
+ PrintHex( &pGobiDev->mpNetDev->net->dev_addr[0], 6 );
+#endif
+ /* Create ethernet header for IPv4 packets */
+ eth = (struct ethhdr *)pGobiDev->eth_hdr_tmpl_ipv4;
+ memcpy(&eth->h_dest, &pGobiDev->mpNetDev->net->dev_addr[0], ETH_ALEN);
+ memcpy(&eth->h_source, &pGobiDev->mpNetDev->net->dev_addr[0], ETH_ALEN);
+ eth->h_proto = cpu_to_be16(ETH_P_IP);
+
+ /* Create ethernet header for IPv6 packets */
+ eth = (struct ethhdr *)pGobiDev->eth_hdr_tmpl_ipv6;
+ memcpy(&eth->h_dest, &pGobiDev->mpNetDev->net->dev_addr[0], ETH_ALEN);
+ memcpy(&eth->h_source, &pGobiDev->mpNetDev->net->dev_addr[0], ETH_ALEN);
+ eth->h_proto = cpu_to_be16(ETH_P_IPV6);
+
+ pGobiDev->mbQMIValid = false;
+ memset( &pGobiDev->mQMIDev, 0, sizeof( sQMIDev ) );
+ pGobiDev->mQMIDev.mbCdevIsInitialized = false;
+ pGobiDev->mQMIDev.iInterfaceNumber = pIntf->cur_altsetting->desc.bInterfaceNumber;
+ pGobiDev->mQMIDev.mpDevClass = gpClass;
+
+#ifdef CONFIG_PM
+ init_completion( &pGobiDev->mAutoPM.mThreadDoWork );
+ spin_lock_init(&pGobiDev->sSuspendLock);
+ SetCurrentSuspendStat(pGobiDev, false);
+#endif /* CONFIG_PM */
+ spin_lock_init( &pGobiDev->mQMIDev.mClientMemLock );
+
+ // Default to device down
+ pGobiDev->mDownReason = 0;
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 3,11,0 ))
+ GobiSetDownReason( pGobiDev, NO_NDIS_CONNECTION );
+ GobiSetDownReason( pGobiDev, NET_IFACE_STOPPED );
+#else
+ GobiSetDownReason( pGobiDev, NO_NDIS_CONNECTION );
+#endif
+
+ // Register QMI
+ if (pDev->driver_info->data &&
+ test_bit(BIT_9X15, &pDev->driver_info->data)) {
+ is9x15 = 1;
+ }
+ sema_init( &(pGobiDev->taskIDSem), SEMI_INIT_DEFAULT_VALUE );
+ pGobiDev->task=NULL;
+ ClearTaskID(true,pGobiDev);
+ pGobiDev->mIs9x15= is9x15;
+ pGobiDev->mUsb_Interface = pIntf;
+ pGobiDev->iTaskID = 0;
+ if(pGobiDev->iTaskID>=0)
+ {
+ pGobiDev->task = kthread_run(&thread_function,(void *)pGobiDev,"GobiNetThread:%d Port:%d,Intf:%d",pGobiDev->iTaskID,
+ pDev->udev->portnum,pIntf->cur_altsetting->desc.bInterfaceNumber);
+ DBG(KERN_INFO"GobiNet Thread : %s %d:%d\n",pGobiDev->task->comm,pDev->udev->portnum,pIntf->cur_altsetting->desc.bInterfaceNumber);
+ }
+ else
+ {
+ DBG(KERN_INFO"GobiNet Thread : Error\n");
+ }
+
+ PrintCurrentUSBSpeed(pDev);
+ // Success
+ return 0;
+}
+
+
+void GobiUSBDisconnect(struct usb_interface *pIntf)
+{
+ sGobiUSBNet * pGobiDev;
+ struct usbnet * pDev;
+ DBG("GobiUSBDisconnect\n");
+ #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)
+ {
+ DBG( "failed to get interface\n" );
+ return ;
+ }
+ else
+ {
+ pGobiDev = (sGobiUSBNet *)pDev->data[0];
+ if (pGobiDev == NULL)
+ {
+ DBG( "failed to get QMIDevice\n" );
+ return;
+ }
+ pGobiDev->mbUnload = eStatUnloading;
+ }
+ usbnet_disconnect(pIntf);
+}
+
+static struct usb_driver GobiNet =
+{
+ .name = "GobiNet",
+ .id_table = GobiVIDPIDTable,
+ .probe = GobiUSBNetProbe,
+ .disconnect = GobiUSBDisconnect,
+#ifdef CONFIG_PM
+ .suspend = GobiNetSuspend,
+ .resume = GobiNetResume,
+ .supports_autosuspend = true,
+ .reset_resume = GobiNetResetResume,
+#else
+ .suspend = NULL,
+ .resume = NULL,
+ .supports_autosuspend = false,
+#endif /* CONFIG_PM */
+};
+
+/*===========================================================================
+METHOD:
+ GobiUSBNetModInit (Public Method)
+
+DESCRIPTION:
+ Initialize module
+ Create device class
+ Register out usb_driver struct
+
+RETURN VALUE:
+ int - 0 for success
+ Negative errno for error
+===========================================================================*/
+bool isModuleUnload(sGobiUSBNet * pDev)
+{
+ if(iModuleExit)
+ return true;
+ if(pDev!=NULL)
+ {
+ if(pDev->mbUnload != eStatRegister)
+ return true;
+ }
+ return false;
+}
+static int __init GobiUSBNetModInit( void )
+{
+ int i;
+ iModuleExit = 0;
+ gpClass = class_create( THIS_MODULE, "GobiQMI" );
+ if (IS_ERR( gpClass ) == true)
+ {
+ DBG( "error at class_create %ld\n",
+ PTR_ERR( gpClass ) );
+ return -ENOMEM;
+ }
+
+ // This will be shown whenever driver is loaded
+ printk( KERN_INFO "%s: %s\n", DRIVER_DESC, DRIVER_VERSION );
+#ifdef TX_URB_MONITOR
+ printk( KERN_INFO "with TX_URB_MONITOR defined\n");
+#endif
+
+ for(i=0;i<MAX_QCQMI;i++)
+ qcqmi_table[i] = 0;
+ #if _PROBE_LOCK_
+ sema_init( &taskLoading, SEMI_INIT_DEFAULT_VALUE );
+ up(&taskLoading);
+ #endif
+ return usb_register( &GobiNet );
+}
+module_init( GobiUSBNetModInit );
+
+/*===========================================================================
+METHOD:
+ GobiUSBNetModExit (Public Method)
+
+DESCRIPTION:
+ Deregister module
+ Destroy device class
+
+RETURN VALUE:
+ void
+===========================================================================*/
+static void __exit GobiUSBNetModExit( void )
+{
+ iModuleExit = 1;
+ usb_deregister( &GobiNet );
+
+ class_destroy( gpClass );
+}
+
+
+/*===========================================================================
+METHOD:
+ SendWeakupControlMsg (Private Method)
+
+DESCRIPTION:
+ Send Devie Weak Up Message
+
+PARAMETERS
+ pIntf [ I ] - Pointer to interface
+ oldPowerState [ I ] - Old Power State
+
+RETURN VALUE:
+ NULL
+===========================================================================*/
+void SendWeakupControlMsg(
+ struct usb_interface * pIntf,
+ int oldPowerState)
+{
+ struct usbnet * pDev;
+ sGobiUSBNet * pGobiDev;
+ int nRet= 0;
+ if (pIntf == 0)
+ {
+ return ;
+ }
+ DBG("\n");
+#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 ;
+ }
+
+ pGobiDev = (sGobiUSBNet *)pDev->data[0];
+ if (pGobiDev == NULL)
+ {
+ DBG( "failed to get QMIDevice\n" );
+ return ;
+ }
+ #if defined(USB_INTRF_FUNC_SUSPEND) && defined(USB_INTRF_FUNC_SUSPEND_RW)
+ if ( pDev->udev->speed >= USB_SPEED_SUPER )
+ {
+ nRet = usb_control_msg(pDev->udev, usb_sndctrlpipe(pDev->udev, 0),
+ USB_REQ_SET_FEATURE, USB_RECIP_INTERFACE,
+ USB_INTRF_FUNC_SUSPEND,
+ pIntf->cur_altsetting->desc.bInterfaceNumber, /* two bytes in this field, suspend option(1 byte) | interface number(1 byte) */
+ NULL, 0, USB_CTRL_SET_TIMEOUT);
+ if (nRet != 0)
+ {
+ DBG("[line:%d] send usb_control_msg failed!nRet = %d\n", __LINE__, nRet);
+ }
+ }
+#endif
+ //USB/xhci: Enable remote wakeup for USB3 devices
+ nRet = usb_control_msg(pDev->udev, usb_sndctrlpipe(pDev->udev, 0),
+ USB_REQ_CLEAR_FEATURE,
+ USB_RECIP_DEVICE,
+ USB_DEVICE_REMOTE_WAKEUP,
+ 0,//Don't care about which interface
+ NULL,
+ 0,
+ USB_CTRL_SET_TIMEOUT);
+ if (nRet != 0)
+ {
+ DBG("[line:%d] send usb_control_msg failed!nRet = %d\n", __LINE__, nRet);
+ }
+ // 9x30(EM74xx) needs this when resume
+ nRet = usb_control_msg( pDev->udev,
+ usb_sndctrlpipe( pDev->udev, 0 ),
+ SET_CONTROL_LINE_STATE_REQUEST,
+ SET_CONTROL_LINE_STATE_REQUEST_TYPE,
+ CONTROL_DTR,
+ pIntf->cur_altsetting->desc.bInterfaceNumber,
+ NULL, 0, USB_CTRL_SET_TIMEOUT);
+ if (nRet != 0)
+ {
+ DBG( "fail at sending DTR during resume %d\n", nRet );
+ }
+
+
+}
+
+module_exit( GobiUSBNetModExit );
+
+MODULE_VERSION( DRIVER_VERSION );
+MODULE_AUTHOR( DRIVER_AUTHOR );
+MODULE_DESCRIPTION( DRIVER_DESC );
+MODULE_LICENSE( "Dual BSD/GPL" );
+
+#ifdef bool
+#undef bool
+#endif
+
+module_param( debug, int, S_IRUGO | S_IWUSR );
+MODULE_PARM_DESC( debug, "Debuging enabled or not" );
+module_param( qos_debug, int, S_IRUGO | S_IWUSR );
+MODULE_PARM_DESC( qos_debug, "QoS Debuging enabled or not" );
+
+module_param( interruptible, int, S_IRUGO | S_IWUSR );
+MODULE_PARM_DESC( interruptible, "Listen for and return on user interrupt" );
+module_param( txQueueLength, int, S_IRUGO | S_IWUSR );
+MODULE_PARM_DESC( txQueueLength,
+ "Number of IP packets which may be queued up for transmit" );
+module_param( iTEEnable, int, S_IRUGO | S_IWUSR );
+MODULE_PARM_DESC( iTEEnable, "TE Flow Control enabled or not" );
+module_param( iRAWIPEnable, int, S_IRUGO | S_IWUSR );
+MODULE_PARM_DESC( iRAWIPEnable, "RAWIP enabled or not" );
diff --git a/swi-drivers/src/GobiNet/Makefile b/swi-drivers/src/GobiNet/Makefile
new file mode 100644
index 0000000..b113769
--- /dev/null
+++ b/swi-drivers/src/GobiNet/Makefile
@@ -0,0 +1,56 @@
+obj-m := GobiNet.o
+GobiNet-objs := GobiUSBNet.o QMIDevice.o QMI.o usbnet_2_6_32.o usbnet_3_0_6.o \
+ usbnet_2_6_35.o usbnet_3_10_21.o usbnet_3_12_xx.o usbnet_4_4_xx.o
+KDIR := /lib/modules/$(shell uname -r)/build
+PWD := $(shell pwd)
+OUTPUTDIR=/lib/modules/`uname -r`/kernel/drivers/net/usb/
+#KBUILD_CFLAGS += -DQOS_SIMULATE
+#KBUILD_CFLAGS += -DTX_XMIT_SIERRA -DTX_URB_MONITOR
+ifdef TX_URB_MONITOR
+ifeq ($(TX_URB_MONITOR), 1)
+KBUILD_CFLAGS += -DTX_XMIT_SIERRA -DTX_URB_MONITOR
+else
+TX_URB_MONITOR:=0
+endif
+else
+TX_URB_MONITOR:=0
+endif
+ifeq ($(TX_URB_MONITOR), 1)
+ ccflags-y:=-DTX_URB_MONITOR
+endif
+
+RAWIP := 0
+ifeq ($(RAWIP), 1)
+ ccflags-y:=-DDATA_MODE_RP
+endif
+
+PI_KDIR := ~/k/linux-rpi-3.6.y
+PI_CCPREFIX=~/toolchain/rpi/tools-master/arm-bcm2708/arm-bcm2708-linux-gnueabi/bin/arm-bcm2708-linux-gnueabi-
+
+OW_KDIR := ~/openwrt/trunk/build_dir/target-mips_r2_uClibc-0.9.33.2/linux-ar71xx_generic/linux-3.8.11
+OW_CCPREFIX=~/openwrt/trunk/staging_dir/toolchain-mips_r2_gcc-4.6-linaro_uClibc-0.9.33.2/bin/mips-openwrt-linux-
+
+MARVELL_KDIR := ~/toolchain/qmi_mxwell/kernel-2.6.31
+MARVELL_CCPREFIX := ~/toolchain/qmi_mxwell/toolchain/bin/arm-none-linux-gnueabi-
+
+all:
+ $(MAKE) -C $(KDIR) M=$(PWD) modules
+
+marvell:
+ $(MAKE) ARCH=arm CROSS_COMPILE=${MARVELL_CCPREFIX} -C $(MARVELL_KDIR) M=$(PWD) modules
+
+pi:
+ $(MAKE) ARCH=arm CROSS_COMPILE=${PI_CCPREFIX} -C $(PI_KDIR) M=$(PWD) modules
+
+ow:
+ $(MAKE) ARCH=mips CROSS_COMPILE=${OW_CCPREFIX} -C $(OW_KDIR) M=$(PWD) modules
+
+install: all
+ mkdir -p $(OUTPUTDIR)
+ cp -f GobiNet.ko $(OUTPUTDIR)
+ depmod
+
+clean:
+ rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions Module.* modules.order
+
+
diff --git a/swi-drivers/src/GobiNet/QMI.c b/swi-drivers/src/GobiNet/QMI.c
new file mode 100644
index 0000000..78be393
--- /dev/null
+++ b/swi-drivers/src/GobiNet/QMI.c
@@ -0,0 +1,1868 @@
+/*===========================================================================
+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
+ QMIDMSSWISetFCCAuthReq
+ QMIWDASetDataFormatReq
+ QMICTLSetDataFormatReq
+ QMICTLSyncReq
+
+ Parse data from QMI responses
+ QMICTLGetClientIDResp
+ QMICTLReleaseClientIDResp
+ QMIWDSEventResp
+ QMIDMSGetMEIDResp
+ QMIWDASetDataFormatResp
+ QMICTLSetDataFormatResp
+ QMICTLSyncResp
+
+Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of Code Aurora Forum nor
+ the names of its contributors may be used to endorse or promote
+ products derived from this software without specific prior written
+ permission.
+
+Alternatively, provided that this notice is retained in full, this software
+may be relicensed by the recipient under the terms of the GNU General Public
+License version 2 ("GPL") and only version 2, in which case the provisions of
+the GPL apply INSTEAD OF those given above. If the recipient relicenses the
+software under the GPL, then the identification text in the MODULE_LICENSE
+macro must be changed to reflect "GPLv2" instead of "Dual BSD/GPL". Once a
+recipient changes the license terms to the GPL, subsequent recipients shall
+not relicense under alternate licensing terms, including the BSD or dual
+BSD/GPL terms. In addition, the following license statement immediately
+below and between the words START and END shall also then apply when this
+software is relicensed under the GPL:
+
+START
+
+This program is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License version 2 and only version 2 as
+published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+details.
+
+You should have received a copy of the GNU General Public License along with
+this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+END
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+===========================================================================*/
+
+//---------------------------------------------------------------------------
+// Include Files
+//---------------------------------------------------------------------------
+#include <asm/unaligned.h>
+#include <linux/kernel.h>
+#include "Structs.h"
+#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;
+}
+
+/*===========================================================================
+METHOD:
+ QMIDMSSWISetFCCAuthReqSize (Public Method)
+
+DESCRIPTION:
+ Get size of buffer needed for QMUX + QMIDMSSWISetFCCAuthReq
+
+RETURN VALUE:
+ u16 - size of buffer
+===========================================================================*/
+u16 QMIDMSSWISetFCCAuthReqSize( void )
+{
+ return sizeof( sQMUX ) + 7;
+}
+
+/*===========================================================================
+METHOD:
+ QMIWDASetDataFormatReqSize (Public Method)
+
+DESCRIPTION:
+ Get size of buffer needed for QMUX + QMIWDASetDataFormatReq
+
+PARAMETERS
+ te_flow_control [ I ] - TE Flow Control Flag
+
+RETURN VALUE:
+ u16 - size of buffer
+===========================================================================*/
+u16 QMIWDASetDataFormatReqSize( bool te_flow_control )
+{
+ if(te_flow_control)
+ {
+ return sizeof( sQMUX ) + 29; /* TE_FLOW_CONTROL */
+ }
+ else
+ {
+ return sizeof( sQMUX ) + 25;
+ }
+}
+
+/*===========================================================================
+METHOD:
+ QMICTLSetDataFormatReqSize (Public Method)
+
+DESCRIPTION:
+ Get size of buffer needed for QMUX + QMICTLSetDataFormatReq
+
+RETURN VALUE:
+ u16 - size of buffer
+===========================================================================*/
+u16 QMICTLSetDataFormatReqSize( void )
+{
+ return sizeof( sQMUX ) + 15;
+}
+
+/*===========================================================================
+METHOD:
+ QMICTLSyncReqSize (Public Method)
+
+DESCRIPTION:
+ Get size of buffer needed for QMUX + QMICTLSyncReq
+
+RETURN VALUE:
+ u16 - size of buffer
+===========================================================================*/
+u16 QMICTLSyncReqSize( void )
+{
+ return sizeof( sQMUX ) + 6;
+}
+
+/*=========================================================================*/
+// 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
+ || le16_to_cpu(get_unaligned(&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;
+ put_unaligned(cpu_to_le16(buffSize - 1), &pQMUXHeader->mLength);
+ DBG("pQMUXHeader->mLength = 0x%x, buffSize - 1 = 0x%x\n",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 buffer 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 = le16_to_cpu( get_unaligned(((u16 *)(pQMIMessage + pos + 1) )) );
+ if (*(u8 *)(pQMIMessage + pos) == type)
+ {
+ if (bufferLen < tlvSize)
+ {
+ return -ENOMEM;
+ }
+
+ 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 le16_to_cpu( get_unaligned(&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 le16_to_cpu( get_unaligned((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
+ put_unaligned(cpu_to_le16(0x0022), (u16 *)(pBuffer + sizeof( sQMUX ) + 2));
+ // Size of TLV's
+ put_unaligned(cpu_to_le16(0x0004), (u16 *)(pBuffer + sizeof( sQMUX ) + 4));
+ // QMI Service Type
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 6) = 0x01;
+ // Size
+ put_unaligned(cpu_to_le16(0x0001), (u16 *)(pBuffer + sizeof( sQMUX ) + 7));
+ // 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;
+ }
+
+ DBG( "buffSize: 0x%x, transactionID: 0x%x, clientID: 0x%x,\n",
+ buffSize, transactionID, clientID );
+
+ // QMI CTL RELEASE CLIENT ID REQ
+ // Request
+ *(u8 *)(pBuffer + sizeof( sQMUX )) = 0x00;
+ // Transaction ID
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 1 ) = transactionID;
+ // Message ID
+ put_unaligned( cpu_to_le16(0x0023), (u16 *)(pBuffer + sizeof( sQMUX ) + 2) );
+ // Size of TLV's
+ put_unaligned( cpu_to_le16(0x0005), (u16 *)(pBuffer + sizeof( sQMUX ) + 4) );
+ // Release client ID
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 6) = 0x01;
+ // Size
+ put_unaligned( cpu_to_le16(0x0002), (u16 *)(pBuffer + sizeof( sQMUX ) + 7));
+ // QMI svs type / Client ID
+ put_unaligned(cpu_to_le16(clientID), (u16 *)(pBuffer + sizeof( sQMUX ) + 9));
+
+ // 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;
+ }
+
+ DBG("buffSize: 0x%x, transactionID: 0x%x\n", buffSize, transactionID);
+
+ // QMI CTL GET VERSION INFO REQ
+ // Request
+ *(u8 *)(pBuffer + sizeof( sQMUX )) = 0x00;
+ // Transaction ID
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 1) = transactionID;
+ // Message ID
+ put_unaligned( cpu_to_le16(0x0021), (u16 *)(pBuffer + sizeof( sQMUX ) + 2) );
+ // Size of TLV's
+ put_unaligned( cpu_to_le16(0x0000), (u16 *)(pBuffer + sizeof( sQMUX ) + 4) );
+
+ // 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
+ put_unaligned( cpu_to_le16(transactionID), (u16 *)(pBuffer + sizeof( sQMUX ) + 1));
+ // Message ID
+ put_unaligned( cpu_to_le16(0x0001), (u16 *)(pBuffer + sizeof( sQMUX ) + 3));
+ // Size of TLV's
+ put_unaligned(cpu_to_le16(0x0008), (u16 *)(pBuffer + sizeof( sQMUX ) + 5));
+ // Report channel rate TLV
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 7) = 0x11;
+ // Size
+ put_unaligned( cpu_to_le16(0x0005), (u16 *)(pBuffer + sizeof( sQMUX ) + 8));
+ // Stats period
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 10) = 0x01;
+ // Stats mask
+ put_unaligned( cpu_to_le32(0x000000ff), (u32 *)(pBuffer + sizeof( sQMUX ) + 11) );
+
+ // 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
+ put_unaligned(cpu_to_le16(transactionID), (u16 *)(pBuffer + sizeof( sQMUX ) + 1));
+ // Message ID
+ put_unaligned(cpu_to_le16(0x0022), (u16 *)(pBuffer + sizeof( sQMUX ) + 3));
+ // Size of TLV's
+ put_unaligned(cpu_to_le16(0x0000), (u16 *)(pBuffer + sizeof( sQMUX ) + 5));
+
+ // 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
+ put_unaligned( cpu_to_le16(transactionID), (u16 *)(pBuffer + sizeof( sQMUX ) + 1) );
+ // Message ID
+ put_unaligned( cpu_to_le16(0x0025), (u16 *)(pBuffer + sizeof( sQMUX ) + 3) );
+ // Size of TLV's
+ put_unaligned( cpu_to_le16(0x0000), (u16 *)(pBuffer + sizeof( sQMUX ) + 5));
+
+ // success
+ return sizeof( sQMUX ) + 7;
+}
+
+
+/*===========================================================================
+METHOD:
+ QMIDMSSWISetFCCAuthReq (Public Method)
+
+DESCRIPTION:
+ Fill buffer with QMI DMS Get FCC Authentication 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 QMIDMSSWISetFCCAuthReq(
+ void * pBuffer,
+ u16 buffSize,
+ u16 transactionID )
+{
+ if (pBuffer == 0 || buffSize < QMIDMSSWISetFCCAuthReqSize() )
+ {
+ return -ENOMEM;
+ }
+
+ // QMI DMS SET FCC AUTH REQ
+ // Request
+ *(u8 *)(pBuffer + sizeof( sQMUX )) = 0x00;
+ // Transaction ID
+ put_unaligned( cpu_to_le16(transactionID), (u16 *)(pBuffer + sizeof( sQMUX ) + 1) );
+ // Message ID
+ put_unaligned( cpu_to_le16(0x555F), (u16 *)(pBuffer + sizeof( sQMUX ) + 3) );
+ // Size of TLV's
+ put_unaligned( cpu_to_le16(0x0000), (u16 *)(pBuffer + sizeof( sQMUX ) + 5));
+
+ // success
+ return QMIDMSSWISetFCCAuthReqSize();
+}
+
+/*===========================================================================
+METHOD:
+ QMIWDASetDataFormatReq (Public Method)
+
+DESCRIPTION:
+ Fill buffer with QMI WDA Set Data Format Request
+
+PARAMETERS
+ pBuffer [ 0 ] - Buffer to be filled
+ buffSize [ I ] - Size of pBuffer
+ transactionID [ I ] - Transaction ID
+ te_flow_control [ I ] - TE Flow Control Flag
+
+RETURN VALUE:
+ int - Positive for resulting size of pBuffer
+ Negative errno for error
+===========================================================================*/
+int QMIWDASetDataFormatReq(
+ void * pBuffer,
+ u16 buffSize,
+ u16 transactionID,
+ bool te_flow_control,
+ int iDataMode)
+{
+ if (pBuffer == 0 || buffSize < QMIWDASetDataFormatReqSize(te_flow_control) )
+ {
+ return -ENOMEM;
+ }
+
+ // QMI WDA SET DATA FORMAT REQ
+ // Request
+ *(u8 *)(pBuffer + sizeof( sQMUX )) = 0x00;
+
+ // Transaction ID
+ put_unaligned( cpu_to_le16(transactionID), (u16 *)(pBuffer + sizeof( sQMUX ) + 1) );
+
+ // Message ID
+ put_unaligned( cpu_to_le16(0x0020), (u16 *)(pBuffer + sizeof( sQMUX ) + 3) );
+
+ // Size of TLV's
+ if(te_flow_control)
+ {
+ /* TE_FLOW_CONTROL */
+ put_unaligned( cpu_to_le16(0x0016), (u16 *)(pBuffer + sizeof( sQMUX ) + 5));
+ }
+ else
+ {
+ put_unaligned( cpu_to_le16(0x0012), (u16 *)(pBuffer + sizeof( sQMUX ) + 5));
+ }
+
+
+ /* TLVType QOS Data Format 1 byte */
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 7) = 0x10; // type data format
+
+ /* TLVLength 2 bytes - see spec */
+ put_unaligned( cpu_to_le16(0x0001), (u16 *)(pBuffer + sizeof( sQMUX ) + 8));
+
+ /* DataFormat: 0-default; 1-QoS hdr present 2 bytes */
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 10) = 0; /* no-QOS header */
+
+ /* TLVType Link-Layer Protocol (Optional) 1 byte */
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 11) = 0x11;
+
+ /* TLVLength 2 bytes */
+ put_unaligned( cpu_to_le16(0x0004), (u16 *)(pBuffer + sizeof( sQMUX ) + 12));
+
+ /* LinkProt: 0x1 - ETH; 0x2 - rawIP 4 bytes */
+ if(iDataMode==eDataMode_RAWIP)
+ {
+ /* Set RawIP mode */
+ put_unaligned( cpu_to_le32(0x00000002), (u32 *)(pBuffer + sizeof( sQMUX ) + 14));
+ DBG("Request RawIP Data Format\n");
+ }
+ else
+ {
+ /* Set Ethernet mode */
+ put_unaligned( cpu_to_le32(0x00000001), (u32 *)(pBuffer + sizeof( sQMUX ) + 14));
+ DBG("Request Ethernet Data Format\n");
+ }
+
+ /* TLVType Uplink Data Aggression Protocol - 1 byte */
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 18) = 0x13;
+
+ /* TLVLength 2 bytes */
+ put_unaligned( cpu_to_le16(0x0004), (u16 *)(pBuffer + sizeof( sQMUX ) + 19));
+
+ /* TLV Data */
+ put_unaligned( cpu_to_le32(0x00000000), (u32 *)(pBuffer + sizeof( sQMUX ) + 21));
+
+ if(te_flow_control)
+ {
+ /* TLVType Flow Control - 1 byte */
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 25) = 0x1A;
+
+ /* TLVLength 2 bytes */
+ put_unaligned( cpu_to_le16(0x0001), (u16 *)(pBuffer + sizeof( sQMUX ) + 26));
+
+ /* Flow Control: 0 - not done by TE; 1 - done by TE 1 byte */
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 28) = 1; /* flow control done by TE */
+
+ } /* TE_FLOW_CONTROL */
+
+ // success
+ return QMIWDASetDataFormatReqSize(te_flow_control);
+}
+
+
+
+/*===========================================================================
+METHOD:
+ QMICTLSetDataFormatReq (Public Method)
+
+DESCRIPTION:
+ Fill buffer with QMI CTL Set Data Format 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 QMICTLSetDataFormatReq(
+ void * pBuffer,
+ u16 buffSize,
+ u8 transactionID ,
+ int iDataMode)
+{
+ if (pBuffer == 0 || buffSize < QMICTLSetDataFormatReqSize() )
+ {
+ return -ENOMEM;
+ }
+
+ /* QMI CTL Set Data Format Request */
+ /* Request */
+ *(u8 *)(pBuffer + sizeof( sQMUX )) = 0x00; // QMICTL_FLAG_REQUEST
+
+ /* Transaction ID 1 byte */
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 1) = transactionID; /* 1 byte as in spec */
+
+ /* QMICTLType 2 bytes */
+ put_unaligned( cpu_to_le16(0x0026), (u16 *)(pBuffer + sizeof( sQMUX ) + 2));
+
+ /* Length 2 bytes of 2 TLVs each - see spec */
+ put_unaligned( cpu_to_le16(0x0009), (u16 *)(pBuffer + sizeof( sQMUX ) + 4));
+
+ /* TLVType Data Format (Mandatory) 1 byte */
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 6) = 0x01; // type data format
+
+ /* TLVLength 2 bytes - see spec */
+ put_unaligned( cpu_to_le16(0x0001), (u16 *)(pBuffer + sizeof( sQMUX ) + 7));
+
+ /* DataFormat: 0-default; 1-QoS hdr present 2 bytes */
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 9) = 0; /* no-QOS header */
+
+ /* TLVType Link-Layer Protocol (Optional) 1 byte */
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 10) = TLV_TYPE_LINK_PROTO;
+
+ /* TLVLength 2 bytes */
+ put_unaligned( cpu_to_le16(0x0002), (u16 *)(pBuffer + sizeof( sQMUX ) + 11));
+
+ /* LinkProt: 0x1 - ETH; 0x2 - rawIP 2 bytes */
+ if(iDataMode==eDataMode_RAWIP)
+ {
+ /* Set RawIP mode */
+ put_unaligned( cpu_to_le16(0x0002), (u16 *)(pBuffer + sizeof( sQMUX ) + 13));
+ DBG("Request RawIP Data Format\n");
+ }
+ else
+ {
+ /* Set Ethernet mode */
+ put_unaligned( cpu_to_le16(0x0001), (u16 *)(pBuffer + sizeof( sQMUX ) + 13));
+ DBG("Request Ethernet Data Format\n");
+ }
+
+ /* success */
+ return sizeof( sQMUX ) + 15;
+
+}
+
+/*===========================================================================
+METHOD:
+ QMICTLSyncReq (Public Method)
+
+DESCRIPTION:
+ Fill buffer with QMI CTL Sync 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 QMICTLSyncReq(
+ void *pBuffer,
+ u16 buffSize,
+ u16 transactionID )
+{
+ if (pBuffer == 0 || buffSize < QMICTLSyncReqSize() )
+ {
+ return -ENOMEM;
+ }
+
+ // Request
+ *(u8 *)(pBuffer + sizeof( sQMUX )) = 0x00;
+ // Transaction ID
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 1) = transactionID;
+ // Message ID
+ put_unaligned( cpu_to_le16(0x0027), (u16 *)(pBuffer + sizeof( sQMUX ) + 2) );
+ // Size of TLV's
+ put_unaligned( cpu_to_le16(0x0000), (u16 *)(pBuffer + sizeof( sQMUX ) + 4) );
+
+ // success
+ return sizeof( sQMUX ) + 6;
+}
+
+/*=========================================================================*/
+// 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 );
+ put_unaligned( le32_to_cpu(*pTXOk), pTXOk);
+ GetTLV( pBuffer, buffSize, 0x11, (void*)pRXOk, 4 );
+ put_unaligned( le32_to_cpu(*pRXOk), pRXOk);
+ GetTLV( pBuffer, buffSize, 0x12, (void*)pTXErr, 4 );
+ put_unaligned( le32_to_cpu(*pTXErr), pTXErr);
+ GetTLV( pBuffer, buffSize, 0x13, (void*)pRXErr, 4 );
+ put_unaligned( le32_to_cpu(*pRXErr), pRXErr);
+ GetTLV( pBuffer, buffSize, 0x14, (void*)pTXOfl, 4 );
+ put_unaligned( le32_to_cpu(*pTXOfl), pTXOfl);
+ GetTLV( pBuffer, buffSize, 0x15, (void*)pRXOfl, 4 );
+ put_unaligned( le32_to_cpu(*pRXOfl), pRXOfl);
+ GetTLV( pBuffer, buffSize, 0x19, (void*)pTXBytesOk, 8 );
+ put_unaligned( le64_to_cpu(*pTXBytesOk), pTXBytesOk);
+ GetTLV( pBuffer, buffSize, 0x1A, (void*)pRXBytesOk, 8 );
+ put_unaligned( le64_to_cpu(*pRXBytesOk), pRXBytesOk);
+ }
+ // 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;
+}
+
+int QMIQOSEventResp(
+ sGobiUSBNet * pDev,
+ void * pBuffer,
+ u16 buffSize)
+{
+ int result;
+
+ // Ignore QMUX and SDU
+ u8 offset = sizeof( sQMUX ) + 3;
+
+ if (pBuffer == 0
+ || buffSize < offset)
+ {
+ return -ENOMEM;
+ }
+
+ pBuffer = pBuffer + offset;
+ buffSize -= offset;
+
+ result = GetQMIMessageID( pBuffer, buffSize );
+ if (result == QOS_NET_SUPPORT)
+ {
+ u16 tlv_rtn;
+ u8 supported = (u8)-1;
+ tlv_rtn = GetTLV( pBuffer, buffSize, 0x01, (void*)&supported, 1 );
+ QDBG(" %d\n", tlv_rtn);
+
+ if (supported != (u8)-1)
+ {
+ QDBG("supported %d", supported);
+ }
+ }
+ else if (result == QOS_STATUS)
+ {
+ int j;
+ sQosFlow flow;
+ u16 tlv_rtn;
+
+ tlv_rtn = GetTLV( pBuffer, buffSize, 0x01, (void*)&flow, 6 );
+ put_unaligned( le32_to_cpu(flow.id), &flow.id);
+ QDBG("tlv_rtn %d\n", tlv_rtn);
+ QDBG("flow.id 0x%x\n", flow.id);
+ QDBG("flow.status 0x%x\n", flow.status);
+ QDBG("flow.event 0x%x\n", flow.event);
+
+ for(j=0;j<MAX_MAP;j++)
+ {
+ //TODO this only update flow status when mapped
+ //share we always update even when user has not assign a map for this qos id
+ if (pDev->maps.table[j].qosId== flow.id)
+ {
+ pDev->maps.table[j].state = flow.status;
+ }
+ }
+
+ }
+ else
+ {
+ QDBG("unhandled indication 0x%x\n", result);
+ 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;
+}
+
+/*===========================================================================
+METHOD:
+ QMIWDASetDataFormatResp (Public Method)
+
+DESCRIPTION:
+ Parse the QMI WDA Set Data Format Response
+
+PARAMETERS
+ pBuffer [ I ] - Buffer to be parsed
+ buffSize [ I ] - Size of pBuffer
+ iDataMode [ I ] - Data Mode
+RETURN VALUE:
+ int - 0 for success
+ Negative errno for error
+===========================================================================*/
+int QMIWDASetDataFormatResp(
+ void * pBuffer,
+ u16 buffSize,
+ int iDataMode)
+{
+
+ int result;
+
+ u8 pktLinkProtocol[4];
+
+ // Ignore QMUX and SDU
+ // QMI SDU is 3 bytes
+ u8 offset = sizeof( sQMUX ) + 3;
+
+ if (pBuffer == 0 || buffSize < offset)
+ {
+ return -ENOMEM;
+ }
+
+ pBuffer = pBuffer + offset;
+ buffSize -= offset;
+
+ result = GetQMIMessageID( pBuffer, buffSize );
+ if (result != 0x20)
+ {
+ return -EFAULT;
+ }
+
+ /* Check response message result TLV */
+ result = ValidQMIMessage( pBuffer, buffSize );
+ if (result != 0)
+ {
+ DBG("EFAULT: Data Format Mode Bad Response\n");
+// return -EFAULT;
+ return 0;
+ }
+
+ /* Check response message link protocol */
+ result = GetTLV( pBuffer, buffSize, 0x11,
+ &pktLinkProtocol[0], 4);
+ if (result != 4)
+ {
+ DBG("EFAULT: Wrong TLV format\n");
+ return 0;
+
+ }
+
+ if(iDataMode==eDataMode_RAWIP)
+ {
+ if (pktLinkProtocol[0] != 2)
+ {
+ DBG("EFAULT: Data Format Cannot be set to RawIP Mode\n");
+ return -EFAULT;
+ }
+ DBG("Data Format Set to RawIP\n");
+ }
+ else
+ {
+ if (pktLinkProtocol[0] != 1)
+ {
+ DBG("EFAULT: Data Format Cannot be set to Ethernet Mode\n");
+ return -EFAULT;
+ }
+ DBG("Data Format Set to Ethernet Mode \n");
+ }
+
+ return 0;
+}
+
+/*===========================================================================
+METHOD:
+ QMICTLSetDataFormatResp (Public Method)
+
+DESCRIPTION:
+ Parse the QMI CTL Set Data Format Response
+
+PARAMETERS
+ pBuffer [ I ] - Buffer to be parsed
+ buffSize [ I ] - Size of pBuffer
+ iDataMode [ I ] - Data Mode
+RETURN VALUE:
+ int - 0 for success
+ Negative errno for error
+===========================================================================*/
+int QMICTLSetDataFormatResp(
+ void * pBuffer,
+ u16 buffSize,
+ int iDataMode)
+{
+
+ int result;
+
+ u8 pktLinkProtocol[2];
+
+ // 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 != 0x26)
+ {
+ return -EFAULT;
+ }
+
+ /* Check response message result TLV */
+ result = ValidQMIMessage( pBuffer, buffSize );
+ if (result != 0)
+ {
+ DBG("EFAULT: Data Format Mode Bad Response\n");
+ return -EFAULT;
+ }
+
+ /* Check response message link protocol */
+ result = GetTLV( pBuffer, buffSize, TLV_TYPE_LINK_PROTO,
+ &pktLinkProtocol[0], 2);
+ if (result != 2)
+ {
+ DBG("EFAULT: Wrong TLV format\n");
+ return -EFAULT;
+ }
+
+ if(iDataMode==eDataMode_RAWIP)
+ {
+ if (pktLinkProtocol[0] != 2)
+ {
+ DBG("EFAULT: Data Format Cannot be set to RawIP Mode\n");
+ return -EFAULT;
+ }
+ DBG("Data Format Set to RawIP\n");
+ }
+ else
+ {
+ if (pktLinkProtocol[0] != 1)
+ {
+ DBG("EFAULT: Data Format Cannot be set to Ethernet Mode\n");
+ return -EFAULT;
+ }
+ DBG("Data Format Set to Ethernet Mode \n");
+ }
+
+ return 0;
+}
+
+
+/*===========================================================================
+METHOD:
+ QMICTLSyncResp (Public Method)
+
+DESCRIPTION:
+ Validate the QMI CTL Sync Response
+
+PARAMETERS
+ pBuffer [ I ] - Buffer to be parsed
+ buffSize [ I ] - Size of pBuffer
+
+RETURN VALUE:
+ int - 0 for success
+ Negative errno for error
+===========================================================================*/
+int QMICTLSyncResp(
+ void *pBuffer,
+ u16 buffSize )
+{
+ int result;
+
+ // Ignore QMUX (2 bytes for QMI CTL) and SDU
+ u8 offset = sizeof( sQMUX ) + 2;
+
+ if (pBuffer == 0 || buffSize < offset)
+ {
+ return -ENOMEM;
+ }
+
+ pBuffer = pBuffer + offset;
+ buffSize -= offset;
+
+ result = GetQMIMessageID( pBuffer, buffSize );
+ if (result != 0x27)
+ {
+ return -EFAULT;
+ }
+
+ result = ValidQMIMessage( pBuffer, buffSize );
+
+ return result;
+}
+
+/*===========================================================================
+METHOD:
+ QMICTLSetPowerSaveModeReqSize (Public Method)
+
+DESCRIPTION:
+ Get size of buffer needed for QMUX + QMICTLSetPowerSaveModeReq
+
+RETURN VALUE:
+ u16 - size of buffer
+===========================================================================*/
+u16 QMICTLSetPowerSaveModeReqSize( void )
+{
+ return sizeof( sQMUX ) + 13;
+}
+
+/*===========================================================================
+METHOD:
+ QMICTLSetPowerSaveModeReq (Public Method)
+
+DESCRIPTION:
+ Fill buffer with QMI CTL Set Power Save Mode 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 QMICTLSetPowerSaveModeReq(
+ void * pBuffer,
+ u16 buffSize,
+ u8 transactionID,
+ u8 mode)
+{
+ if (pBuffer == 0 || buffSize < QMICTLSetPowerSaveModeReqSize() )
+ {
+ return -ENOMEM;
+ }
+
+ /* QMI CTL Set Power Save Mode Request */
+ /* Request */
+ *(u8 *)(pBuffer + sizeof( sQMUX )) = 0x00; // QMICTL_FLAG_REQUEST
+
+ /* Transaction ID 1 byte */
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 1) = transactionID; /* 1 byte as in spec */
+
+ /* Message ID 2 bytes */
+ put_unaligned( cpu_to_le16(0x002A), (u16 *)(pBuffer + sizeof( sQMUX ) + 2));
+
+ /* Length 2 bytes of 1 TLV = 7 bytes */
+ put_unaligned( cpu_to_le16(0x0007), (u16 *)(pBuffer + sizeof( sQMUX ) + 4));
+
+ /* TLVType Power save state 1 byte */
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 6) = 0x01;
+
+ /* TLVLength 2 bytes - see spec */
+ put_unaligned( cpu_to_le16(0x0004), (u16 *)(pBuffer + sizeof( sQMUX ) + 7));
+
+ /* pwrsave_state 4 byptes */
+ //*(u8 *)(pBuffer + sizeof( sQMUX ) + 9) = mode;
+
+ /* pwrsave_state 4 byptes */
+ put_unaligned( cpu_to_le32(mode), (u32 *)(pBuffer + sizeof( sQMUX ) + 9) );
+
+ /* success */
+ return sizeof( sQMUX ) + 13;
+
+}
+
+/*===========================================================================
+METHOD:
+ QMICTLSetPowerSaveModeResp (Public Method)
+
+DESCRIPTION:
+ Parse the QMI CTL Set Power Save Mode Response
+
+PARAMETERS
+ pBuffer [ I ] - Buffer to be parsed
+ buffSize [ I ] - Size of pBuffer
+
+RETURN VALUE:
+ int - 0 for success
+ Negative errno for error
+===========================================================================*/
+int QMICTLSetPowerSaveModeResp(
+ 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 != 0x2A)
+ {
+ return -EFAULT;
+ }
+
+ /* Check response message result TLV */
+ result = ValidQMIMessage( pBuffer, buffSize );
+ if (result != 0)
+ {
+ DBG("EFAULT: Set Power Save Mode Bad Response\n");
+ return -EFAULT;
+ }
+ return 0;
+}
+
+/*===========================================================================
+METHOD:
+ QMICTLConfigPowerSaveSettingsReqSize (Public Method)
+
+DESCRIPTION:
+ Get size of buffer needed for QMUX + QMICTLConfigPowerSaveSettingsReq
+
+RETURN VALUE:
+ u16 - size of buffer
+===========================================================================*/
+u16 QMICTLConfigPowerSaveSettingsReqSize( void )
+{
+ return sizeof( sQMUX ) + 19;
+}
+
+
+/*===========================================================================
+METHOD:
+ QMICTLConfigPowerSaveSettingsReq (Public Method)
+
+DESCRIPTION:
+ Fill buffer with QMI CTL Config Power Save Settings 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 QMICTLConfigPowerSaveSettingsReq(
+ void * pBuffer,
+ u16 buffSize,
+ u8 transactionID,
+ u8 service,
+ u8 indication)
+{
+ if (pBuffer == 0 || buffSize < QMICTLConfigPowerSaveSettingsReqSize() )
+ {
+ return -ENOMEM;
+ }
+
+ /* QMI CTL Set Power Save Mode Request */
+ /* Request */
+ *(u8 *)(pBuffer + sizeof( sQMUX )) = 0x00; // QMICTL_FLAG_REQUEST
+
+ /* Transaction ID 1 byte */
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 1) = transactionID; /* 1 byte as in spec */
+
+ /* Message ID 2 bytes */
+ put_unaligned( cpu_to_le16(0x0029), (u16 *)(pBuffer + sizeof( sQMUX ) + 2));
+
+ /* Length 2 bytes of 2 TLVs = 13 bytes */
+ put_unaligned( cpu_to_le16(0x000D), (u16 *)(pBuffer + sizeof( sQMUX ) + 4));
+
+ /* TLVType Power save state 1 byte */
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 6) = 0x01;
+
+ /* TLVLength 2 bytes - see spec */
+ put_unaligned( cpu_to_le16(0x0005), (u16 *)(pBuffer + sizeof( sQMUX ) + 7));
+
+ /* pwrsave_state 4 byptes */
+ put_unaligned( cpu_to_le32(0x00000001), (u32 *)(pBuffer + sizeof( sQMUX ) + 9) );
+
+ /* qmi_service 1 byptes */
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 13) = service;
+
+ /* TLVType Permitted Indication set 1 byte */
+ *(u8 *)(pBuffer + sizeof( sQMUX ) + 14) = 0x11;
+
+ /* TLVLength 2 bytes*/
+ put_unaligned( cpu_to_le16(0x0002), (u16 *)(pBuffer + sizeof( sQMUX ) + 15));
+
+ /* indication_set 2 bytes*/
+ put_unaligned( cpu_to_le16(indication), (u16 *)(pBuffer + sizeof( sQMUX ) + 17));
+
+ /* success */
+ return sizeof( sQMUX ) + 19;
+
+}
+
+/*===========================================================================
+METHOD:
+ QMICTLConfigPowerSaveSettingsResp (Public Method)
+
+DESCRIPTION:
+ Parse the QMI CTL Config Power Save Settings Request
+
+PARAMETERS
+ pBuffer [ I ] - Buffer to be parsed
+ buffSize [ I ] - Size of pBuffer
+
+RETURN VALUE:
+ int - 0 for success
+ Negative errno for error
+===========================================================================*/
+int QMICTLConfigPowerSaveSettingsResp(
+ 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 != 0x29)
+ {
+ return -EFAULT;
+ }
+
+ /* Check response message result TLV */
+ result = ValidQMIMessage( pBuffer, buffSize );
+ if (result != 0)
+ {
+ DBG("EFAULT: Config Power Save Settings Request\n");
+ return -EFAULT;
+ }
+ return 0;
+}
diff --git a/swi-drivers/src/GobiNet/QMI.h b/swi-drivers/src/GobiNet/QMI.h
new file mode 100644
index 0000000..d645485
--- /dev/null
+++ b/swi-drivers/src/GobiNet/QMI.h
@@ -0,0 +1,456 @@
+/*===========================================================================
+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
+ QMICTLSyncReqSize
+
+ Fill Buffers with QMI requests
+ QMICTLGetClientIDReq
+ QMICTLReleaseClientIDReq
+ QMICTLReadyReq
+ QMIWDSSetEventReportReq
+ QMIWDSGetPKGSRVCStatusReq
+ QMIDMSGetMEIDReq
+ QMICTLSetDataFormatReq
+ QMICTLSyncReq
+
+ Parse data from QMI responses
+ QMICTLGetClientIDResp
+ QMICTLReleaseClientIDResp
+ QMIWDSEventResp
+ QMIDMSGetMEIDResp
+ QMICTLSetDataFormatResp
+
+Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of Code Aurora Forum nor
+ the names of its contributors may be used to endorse or promote
+ products derived from this software without specific prior written
+ permission.
+
+Alternatively, provided that this notice is retained in full, this software
+may be relicensed by the recipient under the terms of the GNU General Public
+License version 2 ("GPL") and only version 2, in which case the provisions of
+the GPL apply INSTEAD OF those given above. If the recipient relicenses the
+software under the GPL, then the identification text in the MODULE_LICENSE
+macro must be changed to reflect "GPLv2" instead of "Dual BSD/GPL". Once a
+recipient changes the license terms to the GPL, subsequent recipients shall
+not relicense under alternate licensing terms, including the BSD or dual
+BSD/GPL terms. In addition, the following license statement immediately
+below and between the words START and END shall also then apply when this
+software is relicensed under the GPL:
+
+START
+
+This program is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License version 2 and only version 2 as
+published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+details.
+
+You should have received a copy of the GNU General Public License along with
+this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+END
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+===========================================================================*/
+
+#pragma once
+
+/*=========================================================================*/
+// Definitions
+/*=========================================================================*/
+
+extern int debug;
+extern int qos_debug;
+// DBG macro
+#define DBG( format, arg... )\
+if(debug == 1)\
+{\
+ printk( KERN_INFO "GobiNet::%s " format, __FUNCTION__, ## arg ); \
+}
+#define QDBG( format, arg... )\
+if(qos_debug == 1)\
+{\
+ printk( KERN_INFO "GobiNet[QoS]::%s " format, __FUNCTION__, ## arg );\
+}
+
+/* The following definition is disabled (commented out) by default.
+ * When uncommented it enables raw IP data format mode of operation */
+/*#define DATA_MODE_RP*/
+
+#ifdef QOS_MODE
+#ifdef DATA_MODE_RP
+#error "defining both QOS_MODE & DATA_MODE_RP is not supported"
+#endif
+#endif
+
+// QMI Service Types
+#define QMICTL 0
+#define QMIWDS 1
+#define QMIDMS 2
+#define QMIQOS 4
+#define QMIWDA 0x1A
+#define QMINAS 3
+#define QMIWMS 5
+#define QMIVOICE 9
+
+#define QMI_WMS_EVENT_REPORT_IND 0x01
+#define QMI_NAS_SERVING_SYSTEM_IND 0x24
+#define QMI_VOICE_ALL_CALL_STATUS_IND 0x2E
+#define QMI_WDS_GET_PKT_SRVC_STATUS_IND 0x22
+
+#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
+
+#define TLV_TYPE_LINK_PROTO 0x10
+
+#define QOS_STATUS (0x26)
+#define QOS_NET_SUPPORT (0x27)
+
+// throttle rx/tx briefly after some faults, so khubd might disconnect()
+// us (it polls at HZ/4 usually) before we report too many false errors.
+#define THROTTLE_JIFFIES (HZ/8)
+#define RX_MAX_QUEUE_MEMORY (60 * 1518)
+#define TX_QLEN(dev) (((dev)->udev->speed == USB_SPEED_HIGH) ? \
+ (RX_MAX_QUEUE_MEMORY/(dev)->hard_mtu) : 4)
+
+#define SET_CONTROL_LINE_STATE_REQUEST_TYPE \
+ (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE)
+#define SET_CONTROL_LINE_STATE_REQUEST 0x22
+#define CONTROL_DTR 0x01
+#define CONTROL_RTS 0x02
+
+#define MAX_TASK_ID 16
+
+//Register State
+enum {
+ eStatRegister=0,
+ eStatUnloading,
+ eStatUnloaded
+};
+
+/* The following definition is disabled (commented out) by default.
+ * When uncommented it enables a feature that provides 'feedback' to an
+ * application about allocated and freed resources on transmit path provided
+ * CONFIG_PM is enabled in the kernel*/
+/*#define TX_URB_MONITOR*/
+
+/*=========================================================================*/
+// 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;
+
+typedef struct
+{
+ u32 id;
+ u8 status;
+ u8 event;
+}__attribute__((__packed__)) sQosFlow;
+
+/*=========================================================================*/
+// 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 buffer 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 );
+
+// Get size of buffer needed for QMUX + QMIDMSSWISetFCCAuthReq
+u16 QMIDMSSWISetFCCAuthReqSize( void );
+
+// Get size of buffer needed for QMUX + QMIWDASetDataFormatReq
+u16 QMIWDASetDataFormatReqSize( bool te_flow_control );
+
+// Get size of buffer needed for QMUX + QMICTLSetDataFormatReq
+u16 QMICTLSetDataFormatReqSize( void );
+
+// Get size of buffer needed for QMUX + QMICTLSyncReq
+u16 QMICTLSyncReqSize( 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 );
+
+// Fill buffer with QMI DMS Set FCC Authentication Request
+int QMIDMSSWISetFCCAuthReq(
+ void * pBuffer,
+ u16 buffSize,
+ u16 transactionID );
+
+// Fill buffer with QMI WDA Set Data Format Request
+int QMIWDASetDataFormatReq(
+ void * pBuffer,
+ u16 buffSize,
+ u16 transactionID,
+ bool te_flow_control,
+ int iDataMode);
+
+// Fill buffer with QMI CTL Set Data Format Request
+int QMICTLSetDataFormatReq(
+ void * pBuffer,
+ u16 buffSize,
+ u8 transactionID ,
+ int iDataMode);
+
+int QMICTLSyncReq(
+ 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 );
+
+int QMIQOSEventResp(
+ sGobiUSBNet * pDev,
+ void * pBuffer,
+ u16 buffSize);
+
+// Parse the QMI DMS Get Serial Numbers Resp
+int QMIDMSGetMEIDResp(
+ void * pBuffer,
+ u16 buffSize,
+ char * pMEID,
+ int meidSize );
+
+// Parse the QMI DMS Get Serial Numbers Resp
+int QMIWDASetDataFormatResp(
+ void * pBuffer,
+ u16 buffSize,
+ int iDataMode);
+
+// Parse the QMI Set Data Format Resp
+int QMICTLSetDataFormatResp(
+ void * pBuffer,
+ u16 buffSize,
+ int iDataMode);
+
+// Pasre the QMI CTL Sync Response
+int QMICTLSyncResp(
+ void *pBuffer,
+ u16 buffSize );
+
+// Get size of buffer needed for QMUX + QMICTLSetPowerSaveModeReq
+u16 QMICTLSetPowerSaveModeReqSize( void );
+
+// Fill buffer with QMI CTL Set Power Save Mode Request
+int QMICTLSetPowerSaveModeReq(
+ void * pBuffer,
+ u16 buffSize,
+ u8 transactionID,
+ u8 mode);
+
+// Parse the QMI Set Power Save Mode Resp
+int QMICTLSetPowerSaveModeResp(
+ void * pBuffer,
+ u16 buffSize );
+
+int QMICTLConfigPowerSaveSettingsReq(
+ void * pBuffer,
+ u16 buffSize,
+ u8 transactionID,
+ u8 service,
+ u8 indication);
+
+// Get size of buffer needed for QMUX + QMICTLPowerSaveSettingsReq
+u16 QMICTLConfigPowerSaveSettingsReqSize( void );
+
+// Parse the QMI Config Power Save Settings Resp
+int QMICTLConfigPowerSaveSettingsResp(
+ void * pBuffer,
+ u16 buffSize );
diff --git a/swi-drivers/src/GobiNet/QMIDevice.c b/swi-drivers/src/GobiNet/QMIDevice.c
new file mode 100644
index 0000000..4e3542d
--- /dev/null
+++ b/swi-drivers/src/GobiNet/QMIDevice.c
@@ -0,0 +1,6379 @@
+/*===========================================================================
+FILE:
+ QMIDevice.c
+
+DESCRIPTION:
+ Functions related to the QMI interface device
+
+FUNCTIONS:
+ Generic functions
+ IsDeviceValid
+ PrintHex
+ GobiSetDownReason
+ GobiClearDownReason
+ GobiTestDownReason
+
+ Driver level asynchronous read functions
+ ResubmitIntURB
+ ReadCallback
+ IntCallback
+ StartRead
+ KillRead
+
+ Internal read/write functions
+ ReadAsync
+ UpSem
+ ReadSync
+ WriteSync
+
+ Internal memory management functions
+ GetClientID
+ ReleaseClientID
+ FindClientMem
+ AddToReadMemList
+ PopFromReadMemList
+ AddToNotifyList
+ NotifyAndPopNotifyList
+
+ Internal userspace wrapper functions
+ UserspaceunlockedIOCTL
+
+ Userspace wrappers
+ UserspaceOpen
+ UserspaceIOCTL
+ UserspaceClose
+ UserspaceRead
+ UserspaceWrite
+ UserspacePoll
+
+ Initializer and destructor
+ RegisterQMIDevice
+ DeregisterQMIDevice
+
+ Driver level client management
+ QMIReady
+ QMIWDSCallback
+ SetupQMIWDSCallback
+ QMIDMSGetMEID
+
+Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of Code Aurora Forum nor
+ the names of its contributors may be used to endorse or promote
+ products derived from this software without specific prior written
+ permission.
+
+Alternatively, provided that this notice is retained in full, this software
+may be relicensed by the recipient under the terms of the GNU General Public
+License version 2 ("GPL") and only version 2, in which case the provisions of
+the GPL apply INSTEAD OF those given above. If the recipient relicenses the
+software under the GPL, then the identification text in the MODULE_LICENSE
+macro must be changed to reflect "GPLv2" instead of "Dual BSD/GPL". Once a
+recipient changes the license terms to the GPL, subsequent recipients shall
+not relicense under alternate licensing terms, including the BSD or dual
+BSD/GPL terms. In addition, the following license statement immediately
+below and between the words START and END shall also then apply when this
+software is relicensed under the GPL:
+
+START
+
+This program is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License version 2 and only version 2 as
+published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+details.
+
+You should have received a copy of the GNU General Public License along with
+this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+END
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+===========================================================================*/
+
+//---------------------------------------------------------------------------
+// Include Files
+//---------------------------------------------------------------------------
+#include <asm/unaligned.h>
+#include "QMIDevice.h"
+#include <linux/module.h>
+#include <linux/proc_fs.h> // for the proc filesystem
+#include <linux/device.h>
+#include <linux/file.h>
+#if (LINUX_VERSION_CODE == KERNEL_VERSION( 2,6,35 ))
+#warning "Fix compilation error 'include/linux/compat.h:233: error: in /usr/src/linux-headers-2.6.35-22-generic/include/linux/compat.h, line 233, the variable name of second parameter, replace *u32 to *u"
+#endif
+#include <linux/usbdevice_fs.h>
+
+//-----------------------------------------------------------------------------
+// Definitions
+//-----------------------------------------------------------------------------
+
+extern int debug;
+extern int is9x15;
+extern int interruptible;
+extern int iTEEnable;
+const bool clientmemdebug = 0;
+enum {
+ eNotifyListEmpty=-1,
+ eNotifyListNotFound=0,
+ eNotifyListFound=1,
+};
+
+#define SEND_ENCAPSULATED_COMMAND (0)
+#define GET_ENCAPSULATED_RESPONSE (1)
+#define USB_WRITE_TIMEOUT 500 // must be less than AM timeout
+#define USB_WRITE_RETRY (2)
+#define USB_READ_TIMEOUT (500)
+#define MAX_RETRY 5
+#define ENABLE_MAX_RETRY_LOCK_MSLEEP_TIME 10
+/* initially all zero */
+int qcqmi_table[MAX_QCQMI];
+
+extern bool isModuleUnload(sGobiUSBNet *pDev);
+extern int iRAWIPEnable;
+
+#define CLIENT_READMEM_SNAPSHOT(clientID, pdev)\
+ if( debug == 1 && clientmemdebug )\
+ {\
+ sClientMemList *pclnt;\
+ sReadMemList *plist;\
+ pclnt = FindClientMem((pdev), (clientID));\
+ plist = pclnt->mpList;\
+ if( (pdev) != NULL){\
+ while(plist != NULL)\
+ {\
+ DBG( "clientID 0x%x, mDataSize = %u, mpData = 0x%p, mTransactionID = %u, \
+ mpNext = 0x%p\n", (clientID), plist->mDataSize, plist->mpData, \
+ plist->mTransactionID, plist->mpNext ) \
+ /* advance to next entry */\
+ plist = plist->mpNext;\
+ }\
+ }\
+ }
+
+#ifdef CONFIG_PM
+// Prototype to GobiNetSuspend function
+int GobiNetSuspend(
+ struct usb_interface * pIntf,
+ pm_message_t powerEvent );
+#endif /* CONFIG_PM */
+
+int weakup_inode_process(struct file *pFilp,struct task_struct * pTask);
+// 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
+
+#define IOCTL_QMI_RELEASE_SERVICE_FILE_IOCTL (0x8BE0 + 4)
+
+#define IOCTL_QMI_ADD_MAPPING 0x8BE0 + 5
+#define IOCTL_QMI_DEL_MAPPING 0x8BE0 + 6
+#define IOCTL_QMI_CLR_MAPPING 0x8BE0 + 7
+
+#define IOCTL_QMI_QOS_SIMULATE 0x8BE0 + 8
+#define IOCTL_QMI_GET_TX_Q_LEN 0x8BE0 + 9
+
+#define IOCTL_QMI_EDIT_MAPPING 0x8BE0 + 10
+#define IOCTL_QMI_READ_MAPPING 0x8BE0 + 11
+#define IOCTL_QMI_DUMP_MAPPING 0x8BE0 + 12
+#define IOCTL_QMI_GET_USBNET_STATS 0x8BE0 + 13
+#define IOCTL_QMI_SET_DEVICE_MTU 0x8BE0 + 14
+
+// CDC GET_ENCAPSULATED_RESPONSE packet
+#define CDC_GET_ENCAPSULATED_RESPONSE_LE 0x01A1ll
+#define CDC_GET_ENCAPSULATED_RESPONSE_BE 0xA101000000000000ll
+/* The following masks filter the common part of the encapsulated response
+ * packet value for Gobi and QMI devices, ie. ignore usb interface number
+ */
+#define CDC_RSP_MASK_BE 0xFFFFFFFF00FFFFFFll
+#define CDC_RSP_MASK_LE 0xFFFFFFE0FFFFFFFFll
+
+const int i = 1;
+#define is_bigendian() ( (*(char*)&i) == 0 )
+#define CDC_GET_ENCAPSULATED_RESPONSE(pcdcrsp, pmask)\
+{\
+ *pcdcrsp = is_bigendian() ? CDC_GET_ENCAPSULATED_RESPONSE_BE \
+ : CDC_GET_ENCAPSULATED_RESPONSE_LE ; \
+ *pmask = is_bigendian() ? CDC_RSP_MASK_BE \
+ : CDC_RSP_MASK_LE; \
+}
+
+// CDC CONNECTION_SPEED_CHANGE indication packet
+#define CDC_CONNECTION_SPEED_CHANGE_LE 0x2AA1ll
+#define CDC_CONNECTION_SPEED_CHANGE_BE 0xA12A000000000000ll
+/* The following masks filter the common part of the connection speed change
+ * packet value for Gobi and QMI devices
+ */
+#define CDC_CONNSPD_MASK_BE 0xFFFFFFFFFFFF7FFFll
+#define CDC_CONNSPD_MASK_LE 0XFFF7FFFFFFFFFFFFll
+#define CDC_GET_CONNECTION_SPEED_CHANGE(pcdccscp, pmask)\
+{\
+ *pcdccscp = is_bigendian() ? CDC_CONNECTION_SPEED_CHANGE_BE \
+ : CDC_CONNECTION_SPEED_CHANGE_LE ; \
+ *pmask = is_bigendian() ? CDC_CONNSPD_MASK_BE \
+ : CDC_CONNSPD_MASK_LE; \
+}
+
+#define SPIN_LOCK_DEBUG 0
+
+/*=========================================================================*/
+// UserspaceQMIFops
+// QMI device's userspace file operations
+/*=========================================================================*/
+static const struct file_operations UserspaceQMIFops =
+{
+ .owner = THIS_MODULE,
+ .read = UserspaceRead,
+ .write = UserspaceWrite,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = UserspaceunlockedIOCTL,
+#endif
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 2,6,36 ))
+ .unlocked_ioctl = UserspaceunlockedIOCTL,
+#else
+ .ioctl = UserspaceIOCTL,
+#endif
+ .open = UserspaceOpen,
+ .flush = UserspaceClose,
+ .poll = UserspacePoll,
+ .release = UserspaceRelease,
+ .lock = UserSpaceLock
+};
+
+void RemoveProcessFile(sGobiUSBNet * pDev);
+void RemoveCdev(sGobiUSBNet * pDev);
+
+int isPreempt(void)
+{
+ return in_atomic();
+}
+
+void GobiSyncRcu(void)
+{
+ if(isPreempt()!=0)
+ {
+ printk("preempt_disable");
+ preempt_disable();
+ }
+ smp_wmb();
+ smp_rmb();
+ smp_mb();
+}
+/*=========================================================================*/
+// Generic functions
+/*=========================================================================*/
+u8 QMIXactionIDGet( sGobiUSBNet *pDev)
+{
+ u8 transactionID;
+
+ if( 0 == (transactionID = atomic_add_return( 1, &pDev->mQMIDev.mQMICTLTransactionID)) )
+ {
+ transactionID = atomic_add_return( 1, &pDev->mQMIDev.mQMICTLTransactionID );
+ }
+
+ return transactionID;
+}
+
+static struct usb_endpoint_descriptor *GetEndpoint(
+ struct usb_interface *pintf,
+ int type,
+ int dir )
+{
+ int i;
+ struct usb_host_interface *iface = pintf->cur_altsetting;
+ struct usb_endpoint_descriptor *pendp;
+
+ for( i = 0; i < iface->desc.bNumEndpoints; i++)
+ {
+ pendp = &iface->endpoint[i].desc;
+ if( ((pendp->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == dir)
+ &&
+ (usb_endpoint_type(pendp) == type) )
+ {
+ return pendp;
+ }
+ }
+
+ return NULL;
+}
+
+int ForceFilpClose(struct file *pFilp)
+{
+ int iRet = -1;
+ if (file_count(pFilp)>0)
+ {
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 3,9,0 ))
+ if(file_inode(pFilp)!=NULL)
+ {
+ iRet = filp_close(pFilp, NULL);
+ }
+ else
+ {
+ printk("NULL Inode\n");
+ }
+
+#else
+ iRet = filp_close(pFilp, NULL);
+#endif
+ }
+ GobiSyncRcu();
+ return iRet;
+}
+
+int LocalClientMemLockSpinIsLock( sGobiUSBNet * pDev)
+{
+ if(pDev!=NULL)
+ {
+ return spin_is_locked(&pDev->mQMIDev.mClientMemLock);
+ }
+ return 0;
+}
+
+unsigned long LocalClientMemLockSpinLockIRQSave( sGobiUSBNet * pDev, int line)
+{
+ #if SPIN_LOCK_DEBUG
+ printk("%s :%d\n",__FUNCTION__,line);
+ #endif
+
+ if(pDev!=NULL)
+ {
+ unsigned long flags = 0;
+ spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags);
+ smp_wmb();
+ #if SPIN_LOCK_DEBUG
+ printk("%s :%d Locked\n",__FUNCTION__,line);
+ #endif
+ pDev->mQMIDev.mFlag = flags;
+ return flags;
+ }
+ return 0;
+}
+
+int LocalClientMemUnLockSpinLockIRQRestore( sGobiUSBNet * pDev, unsigned long ulFlags, int line)
+{
+ if(pDev!=NULL)
+ {
+ unsigned long flags = pDev->mQMIDev.mFlag;
+ if(LocalClientMemLockSpinIsLock(pDev)==0)
+ {
+ #if SPIN_LOCK_DEBUG
+ printk("%s :%d Not Locked\n",__FUNCTION__,line);
+ #endif
+ return 0;
+ }
+ #if SPIN_LOCK_DEBUG
+ printk("%s %d :%d\n",__FUNCTION__,__LINE__,line);
+ #endif
+ spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
+ }
+ else
+ {
+ #if SPIN_LOCK_DEBUG
+ printk("%s %d :%d\n",__FUNCTION__,__LINE__,line);
+ #endif
+ local_irq_restore(ulFlags);
+ }
+ smp_mb();
+ return 0;
+}
+
+/*===========================================================================
+METHOD:
+ IsDeviceValid (Public Method)
+
+DESCRIPTION:
+ Basic test to see if device memory is valid
+
+PARAMETERS:
+ pDev [ I ] - Device specific memory
+
+RETURN VALUE:
+ bool
+===========================================================================*/
+bool IsDeviceValid( sGobiUSBNet * pDev )
+{
+ if (pDev == NULL)
+ {
+ return false;
+ }
+
+ if (pDev->mbQMIValid == false)
+ {
+ return false;
+ }
+
+ return true;
+}
+
+/*===========================================================================
+METHOD:
+ PrintHex (Public Method)
+
+DESCRIPTION:
+ Print Hex data, for debug purposes
+
+PARAMETERS:
+ pBuffer [ I ] - Data buffer
+ bufSize [ I ] - Size of data buffer
+
+RETURN VALUE:
+ None
+===========================================================================*/
+void PrintHex(
+ void * pBuffer,
+ u16 bufSize )
+{
+ char * pPrintBuf;
+ u16 pos;
+ int status;
+
+ if (debug != 1)
+ {
+ return;
+ }
+ if(bufSize==(u16)(-1))
+ {
+ DBG( "No Data\n" );
+ }
+ pPrintBuf = kmalloc( bufSize * 3 + 1, GFP_ATOMIC );
+ if (pPrintBuf == NULL)
+ {
+ DBG( "Unable to allocate buffer\n" );
+ return;
+ }
+ memset( pPrintBuf, 0 , bufSize * 3 + 1 );
+
+ for (pos = 0; pos < bufSize; pos++)
+ {
+ status = snprintf( (pPrintBuf + (pos * 3)),
+ 4,
+ "%02X ",
+ *(u8 *)(pBuffer + pos) );
+ if (status != 3)
+ {
+ DBG( "snprintf error %d\n", status );
+ kfree( pPrintBuf );
+ return;
+ }
+ }
+
+ DBG( " : %s\n", pPrintBuf );
+
+ kfree( pPrintBuf );
+ pPrintBuf = NULL;
+ return;
+}
+
+/*===========================================================================
+METHOD:
+ GobiSetDownReason (Public Method)
+
+DESCRIPTION:
+ Sets mDownReason and turns carrier off
+
+PARAMETERS
+ pDev [ I ] - Device specific memory
+ reason [ I ] - Reason device is down
+
+RETURN VALUE:
+ None
+===========================================================================*/
+void GobiSetDownReason(
+ sGobiUSBNet * pDev,
+ u8 reason )
+{
+ set_bit( reason, &pDev->mDownReason );
+ if(reason==NO_NDIS_CONNECTION)
+ {
+ pDev->iNetLinkStatus = eNetDeviceLink_Disconnected;
+ }
+ netif_carrier_off( pDev->mpNetDev->net );
+}
+
+/*===========================================================================
+METHOD:
+ GobiClearDownReason (Public Method)
+
+DESCRIPTION:
+ Clear mDownReason and may turn carrier on
+
+PARAMETERS
+ pDev [ I ] - Device specific memory
+ reason [ I ] - Reason device is no longer down
+
+RETURN VALUE:
+ None
+===========================================================================*/
+void GobiClearDownReason(
+ sGobiUSBNet * pDev,
+ u8 reason )
+{
+ clear_bit( reason, &pDev->mDownReason );
+ if(reason==NO_NDIS_CONNECTION)
+ {
+ pDev->iNetLinkStatus = eNetDeviceLink_Connected;
+ }
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 3,11,0 ))
+ netif_carrier_on( pDev->mpNetDev->net );
+#else
+ if (pDev->mDownReason == 0)
+ {
+ netif_carrier_on( pDev->mpNetDev->net );
+ }
+#endif
+}
+
+/*===========================================================================
+METHOD:
+ GobiTestDownReason (Public Method)
+
+DESCRIPTION:
+ Test mDownReason and returns whether reason is set
+
+PARAMETERS
+ pDev [ I ] - Device specific memory
+ reason [ I ] - Reason device is down
+
+RETURN VALUE:
+ bool
+===========================================================================*/
+bool GobiTestDownReason(
+ sGobiUSBNet * pDev,
+ u8 reason )
+{
+ return test_bit( reason, &pDev->mDownReason );
+}
+
+/*=========================================================================*/
+// Driver level asynchronous read functions
+/*=========================================================================*/
+
+/*===========================================================================
+METHOD:
+ ResubmitIntURB (Public Method)
+
+DESCRIPTION:
+ Resubmit interrupt URB, re-using same values
+
+PARAMETERS
+ pIntURB [ I ] - Interrupt URB
+
+RETURN VALUE:
+ int - 0 for success
+ negative errno for failure
+===========================================================================*/
+int ResubmitIntURB( struct urb * pIntURB )
+{
+ int status;
+ int interval;
+
+ // Sanity test
+ if ( (pIntURB == NULL)
+ || (pIntURB->dev == NULL) )
+ {
+ return -EINVAL;
+ }
+
+ // Interval needs reset after every URB completion
+ // QC suggestion, 4ms per poll:
+ // bInterval 6 = 2^5 = 32 frames = 4 ms per poll
+ interval = (pIntURB->dev->speed == USB_SPEED_HIGH) ?
+ 6 : max((int)(pIntURB->ep->desc.bInterval), 3);
+
+ // Reschedule interrupt URB
+ usb_fill_int_urb( pIntURB,
+ pIntURB->dev,
+ pIntURB->pipe,
+ pIntURB->transfer_buffer,
+ pIntURB->transfer_buffer_length,
+ pIntURB->complete,
+ pIntURB->context,
+ interval );
+ status = usb_submit_urb( pIntURB, GFP_ATOMIC );
+ if (status != 0)
+ {
+ DBG( "Error re-submitting Int URB %d\n", status );
+ }
+
+ return status;
+}
+
+/*===========================================================================
+METHOD:
+ ReadCallback (Public Method)
+
+DESCRIPTION:
+ Put the data in storage and notify anyone waiting for data
+
+PARAMETERS
+ pReadURB [ I ] - URB this callback is run for
+
+RETURN VALUE:
+ None
+===========================================================================*/
+void ReadCallback( struct urb * pReadURB )
+{
+ int result;
+ u16 clientID;
+ u16 RefclientID = 0;
+ sClientMemList * pClientMem;
+ void * pData = NULL;
+ void * pDataCopy = NULL;
+ u16 dataSize;
+ sGobiUSBNet * pDev;
+ unsigned long flags = 0;
+ u16 transactionID;
+ int iResult = 0;
+
+ if (pReadURB == NULL)
+ {
+ DBG( "bad read URB\n" );
+ return;
+ }
+
+ pDev = pReadURB->context;
+ if (IsDeviceValid( pDev ) == false)
+ {
+ DBG( "Invalid device!\n" );
+ return;
+ }
+
+ del_timer(&pDev->read_tmr);
+
+ if (pReadURB->status != 0)
+ {
+ DBG( "Read status = %d\n", pReadURB->status );
+ if ((pReadURB->status == -ECONNRESET) && (pReadURB->actual_length > 0))
+ {
+ pDev->readTimeoutCnt++;
+ // Read URB unlinked after receiving data, send data to client
+ DBG( "Read URB timeout/kill after recv data\n" );
+ printk(KERN_WARNING "Read URB timeout/kill, recv data len (%d), cnt (%d)\n",
+ pReadURB->actual_length, pDev->readTimeoutCnt);
+ }
+ else
+ {
+ // Resubmit the interrupt URB
+ if (IsDeviceValid( pDev ) == false)
+ {
+ DBG( "Invalid device!\n" );
+ return;
+ }
+ ResubmitIntURB( pDev->mQMIDev.mpIntURB );
+ return;
+ }
+ }
+ DBG( "Read %d bytes\n", pReadURB->actual_length );
+
+ pData = pReadURB->transfer_buffer;
+ dataSize = pReadURB->actual_length;
+
+ PrintHex( pData, dataSize );
+
+ result = ParseQMUX( &clientID,
+ pData,
+ dataSize );
+ if (result < 0)
+ {
+ DBG( "Read error parsing QMUX %d\n", result );
+ if (IsDeviceValid( pDev ) == false)
+ {
+ DBG( "Invalid device!\n" );
+ return;
+ }
+ // Resubmit the interrupt URB
+ ResubmitIntURB( pDev->mQMIDev.mpIntURB );
+
+ return;
+ }
+
+ // Grab transaction ID
+
+ // Data large enough?
+ if (dataSize < result + 3)
+ {
+ DBG( "Data buffer too small to parse\n" );
+ if (IsDeviceValid( pDev ) == false)
+ {
+ DBG( "Invalid device!\n" );
+ return;
+ }
+ // Resubmit the interrupt URB
+ ResubmitIntURB( pDev->mQMIDev.mpIntURB );
+
+ return;
+ }
+
+ // Transaction ID size is 1 for QMICTL, 2 for others
+ if (clientID == QMICTL)
+ {
+ transactionID = *(u8*)(pData + result + 1);
+ }
+ else
+ {
+ transactionID = le16_to_cpu( get_unaligned((u16*)(pData + result + 1)) );
+ }
+
+ // Critical section
+ flags = LocalClientMemLockSpinLockIRQSave( pDev , __LINE__ );
+
+ // Find memory storage for this service and Client ID
+ // Not using FindClientMem because it can't handle broadcasts
+ pClientMem = pDev->mQMIDev.mpClientMemList;
+ RefclientID = pClientMem->mClientID ;
+ while (pClientMem != NULL)
+ {
+ if (pClientMem->mClientID == clientID
+ || (pClientMem->mClientID | 0xff00) == clientID)
+ {
+ // Make copy of pData
+ pDataCopy = kmalloc( dataSize, GFP_ATOMIC );
+ if (pDataCopy == NULL)
+ {
+ DBG( "Error allocating client data memory\n" );
+
+ // End critical section
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+ if (IsDeviceValid( pDev ) == false)
+ {
+ DBG( "Invalid device!\n" );
+ return;
+ }
+ // Resubmit the interrupt URB
+ ResubmitIntURB( pDev->mQMIDev.mpIntURB );
+
+ return;
+ }
+
+ 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
+ if (IsDeviceValid( pDev ) == false)
+ {
+ DBG( "Invalid device!\n" );
+ if(pDev)
+ {
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+ }
+ else
+ {
+ LocalClientMemUnLockSpinLockIRQRestore(pDev,flags,__LINE__);
+ }
+ return;
+ }
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+ // Resubmit the interrupt URB
+ ResubmitIntURB( pDev->mQMIDev.mpIntURB );
+
+ return;
+ }
+
+ // Success
+ CLIENT_READMEM_SNAPSHOT(clientID, pDev);
+ DBG( "Creating new readListEntry for client 0x%04X, TID 0x%x\n",
+ clientID,
+ transactionID );
+
+ // Notify this client data exists
+ iResult = NotifyAndPopNotifyList( pDev,
+ pClientMem->mClientID,
+ transactionID );
+ if (iResult==eNotifyListFound)
+ {
+ DBG("%s:%d Found ClientID:0x%x , TID:0x%x\n",__FUNCTION__,__LINE__,pClientMem->mClientID,transactionID);
+ }
+ else if (iResult==eNotifyListEmpty)
+ {
+ DBG("%s:%d Empty ClientID:0x%x , TID:0x%x\n",__FUNCTION__,__LINE__,pClientMem->mClientID,transactionID);
+ }
+ else
+ {
+ DBG("%s:%d Not Found ClientID:0x%x , TID:0x%x\n",__FUNCTION__,__LINE__,pClientMem->mClientID,transactionID);
+ if (IsDeviceValid( pDev ) == false)
+ {
+ DBG( "Invalid device!\n" );
+ LocalClientMemUnLockSpinLockIRQRestore(pDev,flags,__LINE__);
+ return;
+ }
+ if(pDev->mbUnload >= eStatUnloading)
+ {
+ DBG( "Unload:%s\n", __FUNCTION__);
+ LocalClientMemUnLockSpinLockIRQRestore(pDev,flags,__LINE__);
+ return ;
+ }
+ }
+ // Possibly notify poll() that data exists
+ wake_up_interruptible( &pClientMem->mWaitQueue );
+ // Not a broadcast
+ if (clientID >> 8 != 0xff)
+ {
+ break;
+ }
+ }
+
+ // Next element
+ pClientMem = pClientMem->mpNext;
+ }
+
+ // End critical section
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+ if (IsDeviceValid( pDev ) == false)
+ {
+ DBG( "Invalid device!\n" );
+ return;
+ }
+ // Resubmit the interrupt URB
+ ResubmitIntURB( pDev->mQMIDev.mpIntURB );
+}
+
+void read_tmr_cb( struct urb * pReadURB )
+{
+ int result;
+
+ DBG( "%s called (%ld).\n", __func__, jiffies );
+
+ if ((pReadURB != NULL) && (pReadURB->status == -EINPROGRESS))
+ {
+ // Asynchronously unlink URB. On success, -EINPROGRESS will be returned,
+ // URB status will be set to -ECONNRESET, and ReadCallback() executed
+ result = usb_unlink_urb( pReadURB );
+ DBG( "%s called usb_unlink_urb, result = %d\n", __func__, result);
+ }
+}
+
+/*===========================================================================
+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;
+ u64 CDCEncResp;
+ u64 CDCEncRespMask;
+
+ sGobiUSBNet * pDev = (sGobiUSBNet *)pIntURB->context;
+ if (IsDeviceValid( pDev ) == false)
+ {
+ DBG( "Invalid device!\n" );
+ return;
+ }
+
+ // Verify this was a normal interrupt
+ if (pIntURB->status != 0)
+ {
+ DBG( "IntCallback: Int status = %d\n", pIntURB->status );
+
+ // Ignore EOVERFLOW errors
+ if (pIntURB->status != -EOVERFLOW)
+ {
+ // Read 'thread' dies here
+ return;
+ }
+ if(pIntURB->status<0)
+ {
+ return;
+ }
+ }
+ else
+ {
+ //TODO cast transfer_buffer to struct usb_cdc_notification
+
+ // CDC GET_ENCAPSULATED_RESPONSE
+ CDC_GET_ENCAPSULATED_RESPONSE(&CDCEncResp, &CDCEncRespMask)
+
+ DBG( "IntCallback: Encapsulated Response = 0x%llx\n",
+ (*(u64*)pIntURB->transfer_buffer));
+
+ //AR7554RD returned interrupt buffer not matching expected mask
+ //thus, length check only
+ if (pIntURB->actual_length == 8)
+
+ {
+ // 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 );
+ setup_timer( &pDev->read_tmr, (void*)read_tmr_cb, (unsigned long)pDev->mQMIDev.mpReadURB );
+ mod_timer( &pDev->read_tmr, jiffies + msecs_to_jiffies(USB_READ_TIMEOUT) );
+ status = usb_submit_urb( pDev->mQMIDev.mpReadURB, GFP_ATOMIC );
+ if (status != 0)
+ {
+ DBG( "Error submitting Read URB %d\n", status );
+ if (IsDeviceValid( pDev ) == false)
+ {
+ DBG( "Invalid device!\n" );
+ return;
+ }
+ // Resubmit the interrupt urb
+ ResubmitIntURB( pIntURB );
+ return;
+ }
+
+ // Int URB will be resubmitted during ReadCallback
+ return;
+ }
+ // CDC CONNECTION_SPEED_CHANGE
+ else if ((pIntURB->actual_length == 16)
+ && (CDC_GET_CONNECTION_SPEED_CHANGE(&CDCEncResp, &CDCEncRespMask))
+ && ((*(u64*)pIntURB->transfer_buffer & CDCEncRespMask) == CDCEncResp ) )
+ {
+ DBG( "IntCallback: Connection Speed Change = 0x%llx\n",
+ (*(u64*)pIntURB->transfer_buffer));
+
+ // if upstream or downstream is 0, stop traffic. Otherwise resume it
+ if ((*(u32*)(pIntURB->transfer_buffer + 8) == 0)
+ || (*(u32*)(pIntURB->transfer_buffer + 12) == 0))
+ {
+ GobiSetDownReason( pDev, CDC_CONNECTION_SPEED );
+ DBG( "traffic stopping due to CONNECTION_SPEED_CHANGE\n" );
+ }
+ else
+ {
+ GobiClearDownReason( pDev, CDC_CONNECTION_SPEED );
+ DBG( "resuming traffic due to CONNECTION_SPEED_CHANGE\n" );
+ }
+ }
+ else
+ {
+ DBG( "ignoring invalid interrupt in packet\n" );
+ PrintHex( pIntURB->transfer_buffer, pIntURB->actual_length );
+ }
+ }
+ if (IsDeviceValid( pDev ) == false)
+ {
+ DBG( "Invalid device!\n" );
+ return;
+ }
+ // Resubmit the interrupt urb
+ ResubmitIntURB( pIntURB );
+
+ return;
+}
+
+/*===========================================================================
+METHOD:
+ StartRead (Public Method)
+
+DESCRIPTION:
+ Start continuous read "thread" (callback driven)
+
+ Note: In case of error, KillRead() should be run
+ to remove urbs and clean up memory.
+
+PARAMETERS:
+ pDev [ I ] - Device specific memory
+
+RETURN VALUE:
+ int - 0 for success
+ negative errno for failure
+===========================================================================*/
+int StartRead( sGobiUSBNet * pDev )
+{
+ int interval;
+ struct usb_endpoint_descriptor *pendp;
+
+ 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" );
+ usb_free_urb( pDev->mQMIDev.mpReadURB );
+ pDev->mQMIDev.mpReadURB = NULL;
+ 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" );
+ usb_free_urb( pDev->mQMIDev.mpIntURB );
+ pDev->mQMIDev.mpIntURB = NULL;
+ usb_free_urb( pDev->mQMIDev.mpReadURB );
+ pDev->mQMIDev.mpReadURB = NULL;
+ return -ENOMEM;
+ }
+
+ pDev->mQMIDev.mpIntBuffer = kmalloc( DEFAULT_READ_URB_LENGTH, GFP_KERNEL );
+ if (pDev->mQMIDev.mpIntBuffer == NULL)
+ {
+ DBG( "Error allocating int buffer\n" );
+ kfree( pDev->mQMIDev.mpReadBuffer );
+ pDev->mQMIDev.mpReadBuffer = NULL;
+ usb_free_urb( pDev->mQMIDev.mpIntURB );
+ pDev->mQMIDev.mpIntURB = NULL;
+ usb_free_urb( pDev->mQMIDev.mpReadURB );
+ pDev->mQMIDev.mpReadURB = NULL;
+ return -ENOMEM;
+ }
+
+ pDev->mQMIDev.mpReadSetupPacket = kmalloc( sizeof( sURBSetupPacket ),
+ GFP_KERNEL );
+ if (pDev->mQMIDev.mpReadSetupPacket == NULL)
+ {
+ DBG( "Error allocating setup packet buffer\n" );
+ kfree( pDev->mQMIDev.mpIntBuffer );
+ pDev->mQMIDev.mpIntBuffer = NULL;
+ kfree( pDev->mQMIDev.mpReadBuffer );
+ pDev->mQMIDev.mpReadBuffer = NULL;
+ usb_free_urb( pDev->mQMIDev.mpIntURB );
+ pDev->mQMIDev.mpIntURB = NULL;
+ usb_free_urb( pDev->mQMIDev.mpReadURB );
+ pDev->mQMIDev.mpReadURB = NULL;
+ return -ENOMEM;
+ }
+
+ // CDC Get Encapsulated Response packet
+ pDev->mQMIDev.mpReadSetupPacket->mRequestType =
+ USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ pDev->mQMIDev.mpReadSetupPacket->mRequestCode = GET_ENCAPSULATED_RESPONSE;
+ pDev->mQMIDev.mpReadSetupPacket->mValue = 0;
+ pDev->mQMIDev.mpReadSetupPacket->mIndex =
+ cpu_to_le16(pDev->mpIntf->cur_altsetting->desc.bInterfaceNumber); /* interface number */
+ pDev->mQMIDev.mpReadSetupPacket->mLength = cpu_to_le16(DEFAULT_READ_URB_LENGTH);
+
+ pendp = GetEndpoint(pDev->mpIntf, USB_ENDPOINT_XFER_INT, USB_DIR_IN);
+ if (pendp == NULL)
+ {
+ DBG( "Invalid interrupt endpoint!\n" );
+ kfree(pDev->mQMIDev.mpReadSetupPacket);
+ pDev->mQMIDev.mpReadSetupPacket = NULL;
+ kfree( pDev->mQMIDev.mpIntBuffer );
+ pDev->mQMIDev.mpIntBuffer = NULL;
+ kfree( pDev->mQMIDev.mpReadBuffer );
+ pDev->mQMIDev.mpReadBuffer = NULL;
+ usb_free_urb( pDev->mQMIDev.mpIntURB );
+ pDev->mQMIDev.mpIntURB = NULL;
+ usb_free_urb( pDev->mQMIDev.mpReadURB );
+ pDev->mQMIDev.mpReadURB = NULL;
+ return -ENXIO;
+ }
+
+ // Interval needs reset after every URB completion
+ interval = (pDev->mpNetDev->udev->speed == USB_SPEED_HIGH) ?
+ 6 : max((int)(pendp->bInterval), 3);
+
+ // Schedule interrupt URB
+ if (IsDeviceValid( pDev ) == false)
+ {
+ DBG( "Invalid device!\n" );
+ return -ENXIO;
+ }
+ usb_fill_int_urb( pDev->mQMIDev.mpIntURB,
+ pDev->mpNetDev->udev,
+ /* QMI interrupt endpoint for the following
+ * interface configuration: DM, NMEA, MDM, NET
+ */
+ usb_rcvintpipe( pDev->mpNetDev->udev,
+ pendp->bEndpointAddress),
+ pDev->mQMIDev.mpIntBuffer,
+ le16_to_cpu(pendp->wMaxPacketSize),
+ IntCallback,
+ pDev,
+ interval );
+ if (IsDeviceValid( pDev ) == false)
+ {
+ DBG( "Invalid device!\n" );
+ return -ENXIO;
+ }
+ return usb_submit_urb( pDev->mQMIDev.mpIntURB, GFP_ATOMIC );
+}
+
+/*===========================================================================
+METHOD:
+ KillRead (Public Method)
+
+DESCRIPTION:
+ Kill continuous read "thread"
+
+PARAMETERS:
+ pDev [ I ] - Device specific memory
+
+RETURN VALUE:
+ None
+===========================================================================*/
+void KillRead( sGobiUSBNet * pDev )
+{
+
+ if(pDev ==NULL)
+ {
+ DBG( "pDev NULL\n" );
+ return ;
+ }
+
+ // 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;
+}
+
+/*===========================================================================
+METHOD:
+ InitSemID (Public Method)
+
+DESCRIPTION:
+ Initialize Read Sync tasks semaphore
+
+PARAMETERS:
+ pDev [ I ] - Device specific memory
+
+RETURN VALUE:
+ None
+===========================================================================*/
+
+void InitSemID(sGobiUSBNet * pDev)
+{
+ int i = 0;
+ if(pDev==NULL)
+ {
+ DBG("%s NULL\n",__FUNCTION__);
+ return ;
+ }
+ sema_init( &(pDev->ReadsyncSem), SEMI_INIT_DEFAULT_VALUE );
+ up(&(pDev->ReadsyncSem));
+
+ for(i=0;i<MAX_READ_SYNC_TASK_ID;i++)
+ {
+ pDev->iReasSyncTaskID[i]=-__LINE__;
+ sema_init( &(pDev->readSem[i]), SEMI_INIT_DEFAULT_VALUE );
+ }
+ up(&(pDev->ReadsyncSem));
+}
+
+/*===========================================================================
+METHOD:
+ StopSemID (Public Method)
+
+DESCRIPTION:
+ Release all Read Sync tasks semaphore(s)
+
+PARAMETERS:
+ pDev [ I ] - Device specific memory
+
+RETURN VALUE:
+ None
+===========================================================================*/
+void StopSemID(sGobiUSBNet *pDev)
+{
+ int i = 0;
+ if(pDev==NULL)
+ {
+ DBG("%s NULL\n",__FUNCTION__);
+ return ;
+ }
+
+ while(!down_trylock( &(pDev->ReadsyncSem) ))
+ {
+ if(pDev->iTaskID>0)
+ if(signal_pending(current))
+ {
+ return ;
+ }
+ i++;
+ if(i>MAX_RETRY_LOCK_NUMBER)
+ {
+ DBG("%s Get ReadSyncID Timeout\n",__FUNCTION__);
+ return ;
+ }
+
+ set_current_state(TASK_INTERRUPTIBLE);
+ wait_ms(MAX_RETRY_LOCK_MSLEEP_TIME);
+ if(signal_pending(current))
+ {
+ set_current_state(TASK_RUNNING);
+ return ;
+ }
+
+ if(pDev==NULL)
+ {
+ set_current_state(TASK_RUNNING);
+ return ;
+ }
+ }
+ set_current_state(TASK_RUNNING);
+ for(i=0;i<MAX_READ_SYNC_TASK_ID;i++)
+ {
+ if(pDev->iReasSyncTaskID[i]>0)
+ {
+ up(&(pDev->readSem[i]));
+ pDev->iReasSyncTaskID[i]=-__LINE__;
+ }
+ }
+ up(&(pDev->ReadsyncSem));
+ DBG("%s DONE\n",__FUNCTION__);
+}
+
+/*===========================================================================
+METHOD:
+ iGetSemID (Public Method)
+
+DESCRIPTION:
+ Get free read sync semaphore slot.
+
+PARAMETERS:
+ pDev [ I ] - Device specific memory
+
+RETURN VALUE:
+ Free semaphore slot.
+===========================================================================*/
+int iGetSemID(sGobiUSBNet *pDev,int line)
+{
+ int i = 0;
+ int Ret = -1;
+
+
+ while(!down_trylock( &(pDev->ReadsyncSem) ))
+ {
+ if(pDev->iTaskID>0)
+ if(kthread_should_stop() || signal_pending(current))
+ {
+ return -1;
+ }
+ i++;
+ if(i>MAX_RETRY_LOCK_NUMBER)
+ {
+ DBG("%s Get ReadSyncID Timeout\n",__FUNCTION__);
+ return -1;
+ }
+ set_current_state(TASK_INTERRUPTIBLE);
+ wait_ms(MAX_RETRY_LOCK_MSLEEP_TIME);
+ if(signal_pending(current))
+ {
+ set_current_state(TASK_RUNNING);
+ return -1;
+ }
+
+ }
+ set_current_state(TASK_RUNNING);
+ for(i=0;i<MAX_READ_SYNC_TASK_ID;i++)
+ {
+ DBG("%s : iReasSyncTaskID[%d]:%d ret:%d\n",__FUNCTION__,i,pDev->iReasSyncTaskID[i],Ret);
+ if(pDev->iReasSyncTaskID[i]<0)
+ {
+ pDev->iReasSyncTaskID[i]=line;
+ Ret = i;
+ break;
+ }
+ }
+ up(&(pDev->ReadsyncSem));
+ return Ret;
+}
+
+/*=========================================================================*/
+// Internal read/write functions
+/*=========================================================================*/
+
+/*===========================================================================
+METHOD:
+ ReadAsync (Public Method)
+
+DESCRIPTION:
+ Start asynchronous read
+ NOTE: Reading client's data store, not device
+
+PARAMETERS:
+ pDev [ I ] - Device specific memory
+ clientID [ I ] - Requester's client ID
+ transactionID [ I ] - Transaction ID or 0 for any
+ pCallback [ I ] - Callback to be executed when data is available
+ pData [ I ] - Data buffer that willl be passed (unmodified)
+ to callback
+
+RETURN VALUE:
+ int - 0 for success
+ negative errno for failure
+===========================================================================*/
+int ReadAsync(
+ sGobiUSBNet * pDev,
+ u16 clientID,
+ u16 transactionID,
+ void (*pCallback)(sGobiUSBNet*, u16, void *),
+ void * pData )
+{
+ sClientMemList * pClientMem = NULL;
+ sReadMemList ** ppReadMemList = NULL;
+ unsigned long flags;
+ if (IsDeviceValid( pDev ) == false)
+ {
+ DBG( "Invalid device!\n" );
+ return -ENXIO;
+ }
+
+ // Critical section
+ flags = LocalClientMemLockSpinLockIRQSave( pDev , __LINE__);
+
+ // 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
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+ 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
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+
+ // 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
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+
+ // Success
+ return 0;
+}
+
+/*===========================================================================
+METHOD:
+ UpSem (Public Method)
+
+DESCRIPTION:
+ Notification function for synchronous read
+
+PARAMETERS:
+ pDev [ I ] - Device specific memory
+ clientID [ I ] - Requester's client ID
+ pData [ I ] - Buffer that holds semaphore to be up()-ed
+
+RETURN VALUE:
+ None
+===========================================================================*/
+void UpSem(
+ sGobiUSBNet * pDev,
+ u16 clientID,
+ void * pData )
+{
+ DBG( "0x%04X\n", clientID );
+ if(pData!=NULL)
+ {
+ up( (struct semaphore *)pData );
+ }
+ return;
+}
+
+/*===========================================================================
+METHOD:
+ ReadSync (Public Method)
+
+DESCRIPTION:
+ Start synchronous read
+ NOTE: Reading client's data store, not device
+
+PARAMETERS:
+ pDev [ I ] - Device specific memory
+ ppOutBuffer [I/O] - On success, will be filled with a
+ pointer to read buffer
+ clientID [ I ] - Requester's client ID
+ transactionID [ I ] - Transaction ID or 0 for any
+
+RETURN VALUE:
+ int - size of data read for success
+ negative errno for failure
+===========================================================================*/
+int ReadSync(
+ sGobiUSBNet * pDev,
+ void ** ppOutBuffer,
+ u16 clientID,
+ u16 transactionID,
+ int *iID,
+ struct semaphore *pReadSem,
+ int *iIsClosing)
+{
+ int result;
+ sClientMemList * pClientMem;
+ sNotifyList ** ppNotifyList, * pDelNotifyListEntry;
+ void * pData = NULL;
+ u16 dataSize;
+ struct semaphore *pLocalreadSem = NULL;
+ unsigned long flags;
+ DBG("\n");
+ if(pReadSem==NULL)
+ if(*iID<0)
+ {
+ DBG( "Could not find matching SemID\n");
+ return -ENXIO;
+ }
+
+ if (IsDeviceValid( pDev ) == false)
+ {
+ DBG( "Invalid device!\n" );
+ return -ENXIO;
+ }
+ if (pDev->mbUnload >= eStatUnloading)
+ {
+ DBG( "unloaded\n" );
+ return -EFAULT;
+ }
+
+
+ if(pReadSem==NULL)
+ {
+ pLocalreadSem = &(pDev->readSem[*iID]);
+ }
+ else
+ {
+ pLocalreadSem = pReadSem;
+ }
+
+ // Critical section
+ flags = LocalClientMemLockSpinLockIRQSave( pDev , __LINE__);
+
+ // 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
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+ 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
+
+ if(pDev->mbUnload >= eStatUnloading)
+ {
+ DBG("Unloading");
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+ return -ENXIO;
+ }
+ // Add ourself to list of waiters
+ if (AddToNotifyList( pDev,
+ clientID,
+ transactionID,
+ UpSem,
+ pLocalreadSem ) == false)
+ {
+ DBG( "unable to register for notification\n" );
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+ return -EFAULT;
+ }
+
+ // End critical section while we block
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+
+ // Wait for notification
+ if (signal_pending(current))
+ {
+ return -ERESTARTSYS;
+ }
+ result = down_interruptible( pLocalreadSem);
+ DBG("result:%d , CID:0x%04x\n",result,clientID);
+ if (IsDeviceValid( pDev ) == false)
+ {
+ DBG( "Invalid device!\n" );
+ return -EFAULT;
+ }
+ if (pDev->mbUnload > eStatUnloading)
+ {
+ DBG( "unloaded\n" );
+ if(pReadSem==NULL)
+ pDev->iReasSyncTaskID[*iID] = -__LINE__;
+ else
+ *iID = -__LINE__;
+ return -EFAULT;
+ }
+ if(iIsClosing!=NULL)
+ {
+ if(*iIsClosing>0)
+ {
+ DBG( "filp is closing\n" );
+ if(pReadSem==NULL)
+ pDev->iReasSyncTaskID[*iID] = -__LINE__;
+ else
+ *iID = -__LINE__;
+ flags = LocalClientMemLockSpinLockIRQSave( pDev , __LINE__);
+ RemoveAndPopNotifyList(pDev,clientID,transactionID,eClearAndReleaseCID);
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+ return result;
+ }
+ }
+ if (result < 0)
+ {
+ if(pDev)
+ {
+ if(pReadSem==NULL)
+ pDev->iReasSyncTaskID[*iID] = -__LINE__;
+ else
+ *iID = -__LINE__;
+ }
+ flags = LocalClientMemLockSpinLockIRQSave( pDev , __LINE__);
+ RemoveAndPopNotifyList(pDev,clientID,transactionID,eClearCID);
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+ return result;//-EFAULT;EINTR resume error
+ }
+ if(*iID<-1)
+ {
+ DBG( "%s:%d Interrupted %d iID:%d\n",__FUNCTION__,__LINE__, result,*iID );
+ return -EFAULT;
+ }
+ if (signal_pending(current))
+ {
+ return -ERESTARTSYS;
+ }
+ if (result != 0)
+ {
+ DBG( "Interrupted %d\n", result );
+
+ // readSem will fall out of scope,
+ // remove from notify list so it's not referenced
+ if(pDev==NULL)
+ {
+ return -EFAULT;
+ }
+ if(pDev->iIsClosing)
+ {
+ DBG( "Closing device!\n" );
+ if(pDev)
+ {
+ if(pReadSem==NULL)
+ pDev->iReasSyncTaskID[*iID] = -__LINE__;
+ else
+ *iID = -__LINE__;
+ }
+ return -EFAULT;
+ }
+ if(pDev->mbUnload >= eStatUnloading)
+ {
+ DBG("Unloading");
+ if(pReadSem==NULL)
+ pDev->iReasSyncTaskID[*iID] = -__LINE__;
+ else
+ *iID = -__LINE__;
+ return -ENXIO;
+ }
+ flags = LocalClientMemLockSpinLockIRQSave( pDev , __LINE__);
+ if(pDev)
+ {
+ flags = pDev->mQMIDev.mFlag;
+ }
+ ppNotifyList = &(pClientMem->mpReadNotifyList);
+ pDelNotifyListEntry = NULL;
+
+ // Find and delete matching entry
+ while (*ppNotifyList != NULL)
+ {
+ if (IsDeviceValid( pDev ) == false)
+ {
+ DBG( "Invalid device!\n" );
+ /* SWI_START */
+ /* workaround to leave the system in cleaner state:
+ * must enable interrupts and enable pre-emption.
+ * TBD what the correct action should be when the device is gone.
+ */
+ LocalClientMemUnLockSpinLockIRQRestore(pDev,flags,__LINE__);
+ //preempt_enable();
+ if(pReadSem==NULL)
+ pDev->iReasSyncTaskID[*iID] = -__LINE__;
+ else
+ *iID = -__LINE__;
+ /* SWI_STOP */
+ return -EFAULT;
+ }
+ if(*iID<0)
+ {
+ DBG( "Invalid device!\n" );
+ /* SWI_START */
+ /* must restore irq, pre-emption, locks before returning */
+ LocalClientMemUnLockSpinLockIRQRestore(pDev,flags,__LINE__);
+ if(pReadSem==NULL)
+ {
+ if(pDev)
+ pDev->iReasSyncTaskID[*iID] = -__LINE__;
+ }
+ else
+ *iID = -__LINE__;
+ /* SWI_STOP */
+ return -EFAULT;
+ }
+ if(pDev==NULL)
+ {
+ DBG( "Invalid device!\n" );
+ LocalClientMemUnLockSpinLockIRQRestore(pDev,flags,__LINE__);
+ return -EFAULT;
+ }
+ if (pDev->mbUnload > eStatUnloading)
+ {
+ DBG( "UNLOADING!\n" );
+ LocalClientMemUnLockSpinLockIRQRestore(pDev,flags,__LINE__);
+ if(pReadSem==NULL)
+ pDev->iReasSyncTaskID[*iID] = -__LINE__;
+ else
+ *iID = -__LINE__;
+ return -EFAULT;
+ }
+ if(ppNotifyList==NULL)
+ {
+ DBG( "UNLOADING!\n" );
+ LocalClientMemUnLockSpinLockIRQRestore(pDev,flags,__LINE__);
+ if(pReadSem==NULL)
+ pDev->iReasSyncTaskID[*iID] = -__LINE__;
+ else
+ *iID = -__LINE__;
+ return -EFAULT;
+ }
+ if ((*ppNotifyList)->mpData ==
+ pLocalreadSem)
+ {
+ pDelNotifyListEntry = *ppNotifyList;
+ *ppNotifyList = (*ppNotifyList)->mpNext;
+ kfree( pDelNotifyListEntry );
+ break;
+ }
+
+ // Next
+ ppNotifyList = &(*ppNotifyList)->mpNext;
+ }
+
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+ if(pDev)
+ {
+ if(pReadSem==NULL)
+ pDev->iReasSyncTaskID[*iID] = -__LINE__;
+ else
+ *iID = -__LINE__;
+ }
+ return -EINTR;
+ }
+
+ // Verify device is still valid
+ if (IsDeviceValid( pDev ) == false)
+ {
+ DBG( "Invalid device!\n" );
+ if(pDev)
+ {
+ if(pReadSem==NULL)
+ pDev->iReasSyncTaskID[*iID] = -__LINE__;
+ else
+ *iID = -__LINE__;
+ }
+ return -ENXIO;
+ }
+
+ // Restart critical section and continue loop
+ flags = LocalClientMemLockSpinLockIRQSave( pDev , __LINE__);
+ }
+
+ // End Critical section
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+
+ // Success
+ *ppOutBuffer = pData;
+ if(pDev)
+ {
+ if(pReadSem==NULL)
+ pDev->iReasSyncTaskID[*iID] = -__LINE__;
+ else
+ *iID = -__LINE__;
+ }
+ return dataSize;
+}
+
+/*===========================================================================
+METHOD:
+ WriteSync (Public Method)
+
+DESCRIPTION:
+ Start synchronous write
+
+PARAMETERS:
+ pDev [ I ] - Device specific memory
+ pWriteBuffer [ I ] - Data to be written
+ writeBufferSize [ I ] - Size of data to be written
+ clientID [ I ] - Client ID of requester
+
+RETURN VALUE:
+ int - write size (includes QMUX)
+ negative errno for failure
+===========================================================================*/
+int WriteSync(
+ sGobiUSBNet * pDev,
+ char * pWriteBuffer,
+ int writeBufferSize,
+ u16 clientID )
+{
+ int i;
+ int result;
+ int iLockRetry =0;
+
+ if (IsDeviceValid( pDev ) == false)
+ {
+ DBG( "Invalid device!\n" );
+ return -ENXIO;
+ }
+
+ if (pDev->mbUnload >= eStatUnloading)
+ {
+ DBG( "Unloading device!\n" );
+ return -ENXIO;
+ }
+ // Fill writeBuffer with QMUX
+ result = FillQMUX( clientID, pWriteBuffer, writeBufferSize );
+ if (result < 0)
+ {
+ return result;
+ }
+
+ // 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)
+ {
+#ifdef CONFIG_PM
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,33 ))
+ pDev->mpNetDev->udev->auto_pm = 0;
+#endif
+ GobiNetSuspend( pDev->mpIntf, PMSG_SUSPEND );
+#endif /* CONFIG_PM */
+ }
+ return result;
+ }
+
+ DBG( "Actual Write:\n" );
+ PrintHex( pWriteBuffer, writeBufferSize );
+
+ // Write Control URB, protect with read semaphore to track in-flight USB control writes in case of disconnect
+ for(i=0;i<USB_WRITE_RETRY;i++)
+ {
+
+ if(isModuleUnload(pDev))
+ {
+ DBG( "unloaded\n" );
+ return -EFAULT;
+ }
+ pDev->iShutdown_read_sem= __LINE__;
+ if(signal_pending(current))
+ {
+ return -ERESTARTSYS;
+ }
+
+ iLockRetry = 0;
+ while(down_read_trylock(&(pDev->shutdown_rwsem))!=1)
+ {
+ wait_ms(5);
+ if(iLockRetry++>100)
+ {
+ DBG("down_read_trylock timeout");
+ return -EFAULT;
+ }
+ if(pDev==NULL)
+ {
+ DBG( "NULL\n" );
+ return -EFAULT;
+ }
+ if (pDev->mbUnload >= eStatUnloading)
+ {
+ DBG( "unloaded\n" );
+ return -EFAULT;
+ }
+ }
+ smp_mb();
+ result = usb_control_msg( pDev->mpNetDev->udev, usb_sndctrlpipe( pDev->mpNetDev->udev, 0 ),
+ SEND_ENCAPSULATED_COMMAND,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ 0, pDev->mpIntf->cur_altsetting->desc.bInterfaceNumber,
+ (void*)pWriteBuffer, writeBufferSize,
+ USB_WRITE_TIMEOUT );
+ if(signal_pending(current))
+ {
+ return -ERESTARTSYS;
+ }
+ if(pDev==NULL)
+ {
+ return -EFAULT;
+ }
+ up_read(&pDev->shutdown_rwsem);
+ pDev->iShutdown_read_sem=- __LINE__;
+
+ if (pDev->mbUnload >= eStatUnloading)
+ {
+ DBG( "unloaded\n" );
+ return -EFAULT;
+ }
+
+ if (signal_pending(current))
+ {
+ return -ERESTARTSYS;
+ }
+
+ if (result < 0)
+ {
+ printk(KERN_WARNING "usb_control_msg failed (%d)", result);
+ }
+ // Control write transfer may occasionally timeout with certain HCIs, attempt a second time before reporting an error
+ if (result == -ETIMEDOUT)
+ {
+ pDev->writeTimeoutCnt++;
+ printk(KERN_WARNING "Write URB timeout, cnt(%d)\n", pDev->writeTimeoutCnt);
+ }
+ else if(result < 0 )
+ {
+ DBG( "%s no device!\n" ,__FUNCTION__);
+ return result;
+ }
+ else
+ {
+ break;
+ }
+ if (IsDeviceValid( pDev ) == false)
+ {
+ DBG( "%s Invalid device!\n" ,__FUNCTION__);
+ return -ENXIO;
+ }
+ if (pDev->mbUnload > eStatUnloading)
+ {
+ DBG( "unloaded\n" );
+ return -EFAULT;
+ }
+ }
+
+ // Write is done, release device
+ usb_autopm_put_interface( pDev->mpIntf );
+
+
+ return result;
+}
+
+/*=========================================================================*/
+// Internal memory management functions
+/*=========================================================================*/
+
+/*===========================================================================
+METHOD:
+ GetClientID (Public Method)
+
+DESCRIPTION:
+ Request a QMI client for the input service type and initialize memory
+ structure
+
+PARAMETERS:
+ pDev [ I ] - Device specific memory
+ serviceType [ I ] - Desired QMI service type
+
+RETURN VALUE:
+ int - Client ID for success (positive)
+ Negative errno for error
+===========================================================================*/
+int GetClientID(
+ sGobiUSBNet * pDev,
+ u8 serviceType ,
+ struct semaphore *pReadSem)
+{
+ u16 clientID;
+ sClientMemList ** ppClientMem;
+ int result;
+ void * pWriteBuffer;
+ u16 writeBufferSize;
+ void * pReadBuffer;
+ u16 readBufferSize;
+ u8 transactionID;
+ unsigned long flags;
+ struct semaphore readSem;
+
+ if (IsDeviceValid( pDev ) == false)
+ {
+ DBG( "Invalid device!\n" );
+ return -ENXIO;
+ }
+ sema_init( &readSem, SEMI_INIT_DEFAULT_VALUE );
+ // 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 cannot be 0 */
+ transactionID = QMIXactionIDGet(pDev);
+ if (transactionID != 0)
+ {
+ result = QMICTLGetClientIDReq( pWriteBuffer,
+ writeBufferSize,
+ transactionID,
+ serviceType );
+ if (result < 0)
+ {
+ kfree( pWriteBuffer );
+ return result;
+ }
+ }
+ else
+ {
+ kfree( pWriteBuffer );
+ DBG( "Invalid transaction ID!\n" );
+ return EINVAL;
+ }
+
+ result = ReadAsync( pDev, QMICTL, transactionID, UpSem, &readSem );
+ if (result < 0)
+ {
+ DBG( "ReadAsync Error!\n" );
+ return result;
+ }
+ result = WriteSync( pDev,
+ pWriteBuffer,
+ writeBufferSize,
+ QMICTL );
+ kfree( pWriteBuffer );
+ wait_ms(QMI_CONTROL_MSG_DELAY_MS);
+ if (result < 0)
+ {
+ // Timeout, remove the async read
+ DBG( "Timeout!\n" );
+ ReleaseNotifyList( pDev, QMICTL, transactionID );
+ return result;
+ }
+ if (down_trylock( &readSem ) == 0)
+ {
+ // Enter critical section
+ flags = LocalClientMemLockSpinLockIRQSave( pDev , __LINE__);
+
+ // Pop the read data
+ if (PopFromReadMemList( pDev,
+ QMICTL,
+ transactionID,
+ &pReadBuffer,
+ &readBufferSize ) == true)
+ {
+ // Success
+ DBG( "Success!\n" );
+ // End critical section
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+ result = QMICTLGetClientIDResp( pReadBuffer,
+ readBufferSize,
+ &clientID );
+ // We don't care about the result
+ if(pReadBuffer)
+ kfree( pReadBuffer );
+ pReadBuffer=NULL;
+ }
+ else
+ {
+ // Read mismatch/failure, unlock and continue
+ DBG( "Read mismatch/failure, unlock and continue!\n" );
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+ result = -1;
+ return result;
+ }
+ }
+ else
+ {
+ // Timeout, remove the async read
+ ReleaseNotifyList( pDev, QMICTL, transactionID );
+ result = -1;
+ return result;
+ }
+ /* Upon return from QMICTLGetClientIDResp, clientID
+ * low address contains the Service Number (SN), and
+ * clientID high address contains Client Number (CN)
+ * For the ReadCallback to function correctly,we swap
+ * the SN and CN on a Big Endian architecture.
+ */
+ clientID = le16_to_cpu(clientID);
+
+ if (result < 0)
+ {
+ return result;
+ }
+ }
+ else
+ {
+ // QMI CTL will always have client ID 0
+ clientID = 0;
+ }
+
+ // Critical section
+ flags = LocalClientMemLockSpinLockIRQSave( pDev , __LINE__);
+
+ // Verify client is not already allocated
+ if (FindClientMem( pDev, clientID ) != NULL)
+ {
+ DBG( "Client memory already exists\n" );
+
+ // End Critical section
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+ 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
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+ return -ENOMEM;
+ }
+
+ (*ppClientMem)->mClientID = clientID;
+ (*ppClientMem)->mpList = NULL;
+ (*ppClientMem)->mpReadNotifyList = NULL;
+ (*ppClientMem)->mpURBList = NULL;
+ (*ppClientMem)->mpNext = NULL;
+
+ // Initialize workqueue for poll()
+ init_waitqueue_head( &(*ppClientMem)->mWaitQueue );
+
+ // End Critical section
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+
+ return (int)( (*ppClientMem)->mClientID );
+}
+
+/*===========================================================================
+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
+===========================================================================*/
+bool ReleaseClientID(
+ sGobiUSBNet * pDev,
+ u16 clientID)
+{
+ int result;
+ sClientMemList ** ppDelClientMem;
+ sClientMemList * pNextClientMem;
+ void * pDelData = NULL;
+ u16 dataSize;
+ void * pWriteBuffer;
+ u16 writeBufferSize;
+ void * pReadBuffer = NULL;
+ u16 readBufferSize;
+ u8 transactionID;
+ unsigned long flags;
+ bool bReturn = true;
+ // Is device is still valid?
+ DBG("clientID:0x%x\n",clientID);
+ if (pDev->mbUnload > eStatUnloaded)
+ {
+ DBG( "unloaded\n" );
+ return false;
+ }
+
+ // 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" );
+ return false;
+ }
+ else
+ {
+ transactionID = QMIXactionIDGet(pDev);
+ result = QMICTLReleaseClientIDReq( pWriteBuffer,
+ writeBufferSize,
+ transactionID,
+ clientID );
+ if (result < 0)
+ {
+ kfree( pWriteBuffer );
+ DBG( "error %d filling req buffer\n", result );
+ }
+ else
+ {
+ struct semaphore readSem;
+ sema_init( &readSem, SEMI_INIT_DEFAULT_VALUE );
+ result = ReadAsync( pDev, QMICTL, transactionID, UpSem, &readSem );
+ if(result == 0)
+ {
+ result = WriteSync( pDev,
+ pWriteBuffer,
+ writeBufferSize,
+ QMICTL );
+ kfree( pWriteBuffer );
+ wait_ms(QMI_CONTROL_MSG_DELAY_MS);
+ if (down_trylock( &readSem ) == 0)
+ {
+ // Enter critical section
+ flags = LocalClientMemLockSpinLockIRQSave( pDev , __LINE__);
+ // Pop the read data
+ if (PopFromReadMemList( pDev,
+ QMICTL,
+ transactionID,
+ &pReadBuffer,
+ &readBufferSize ) == true)
+ {
+ // End critical section
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+ result = QMICTLReleaseClientIDResp(pReadBuffer,
+ readBufferSize);
+ if (result < 0)
+ {
+ DBG( "error %d parsing response\n", result );
+ }
+ // We don't care about the result
+ if(pReadBuffer)
+ kfree( pReadBuffer );
+ }
+ else
+ {
+ // Read mismatch/failure, unlock and continue
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+ }
+ }
+ else
+ {
+ DBG( "Lock Timeout\n" );
+ // Timeout, remove the async read
+ ReleaseNotifyList( pDev, QMICTL, transactionID );
+ }
+ }
+ }
+ }
+ }
+
+ // Cleaning up client memory
+
+ // Critical section
+ flags = LocalClientMemLockSpinLockIRQSave( pDev , __LINE__);
+
+ // 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 ) == eNotifyListFound );
+ // Free any unread data
+ while (PopFromReadMemList( pDev,
+ clientID,
+ 0,
+ &pDelData,
+ &dataSize ) == true )
+ {
+ kfree( pDelData );
+ pDelData = NULL;
+ }
+ DBG("Delete client Mem\r\n");
+ if(*ppDelClientMem!=NULL)
+ {
+ // Delete client Mem
+ kfree( *ppDelClientMem );
+ }
+ else
+ {
+ bReturn = false;
+ }
+ *ppDelClientMem = NULL;
+ DBG("Prepare Next Delete client Mem\r\n");
+ // 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))
+ if(*ppDelClientMem==NULL)
+ {
+ DBG("ppDelClientMem NULL %d\r\n",__LINE__);
+ break;
+ }
+ ppDelClientMem = &(*ppDelClientMem)->mpNext;
+ }
+ }
+
+ // End Critical section
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+ return bReturn;
+}
+
+/*===========================================================================
+METHOD:
+ FindClientMem (Public Method)
+
+DESCRIPTION:
+ Find this client's memory
+
+ Caller MUST have lock on mClientMemLock
+
+PARAMETERS:
+ pDev [ I ] - Device specific memory
+ clientID [ I ] - Requester's client ID
+
+RETURN VALUE:
+ sClientMemList - Pointer to requested sClientMemList for success
+ NULL for error
+===========================================================================*/
+sClientMemList * FindClientMem(
+ sGobiUSBNet * pDev,
+ u16 clientID )
+{
+ sClientMemList * pClientMem;
+
+ if (IsDeviceValid( pDev ) == false)
+ {
+ DBG( "Invalid device\n" );
+ return NULL;
+ }
+
+#ifdef CONFIG_SMP
+ // Verify Lock
+ if (LocalClientMemLockSpinIsLock( pDev ) == 0)
+ {
+ DBG( "unlocked\n" );
+ BUG();
+ }
+#endif
+
+ pClientMem = pDev->mQMIDev.mpClientMemList;
+ while (pClientMem != NULL)
+ {
+ if (pClientMem->mClientID == clientID)
+ {
+ // Success
+ DBG("Found client's 0x%x memory\n", clientID);
+ return pClientMem;
+ }
+
+ pClientMem = pClientMem->mpNext;
+ }
+
+ DBG( "Could not find client mem 0x%04X\n", clientID );
+ return NULL;
+}
+
+/*===========================================================================
+METHOD:
+ AddToReadMemList (Public Method)
+
+DESCRIPTION:
+ Add Data to this client's ReadMem list
+
+ Caller MUST have lock on mClientMemLock
+
+PARAMETERS:
+ pDev [ I ] - Device specific memory
+ clientID [ I ] - Requester's client ID
+ transactionID [ I ] - Transaction ID or 0 for any
+ pData [ I ] - Data to add
+ dataSize [ I ] - Size of data to add
+
+RETURN VALUE:
+ bool
+===========================================================================*/
+bool AddToReadMemList(
+ sGobiUSBNet * pDev,
+ u16 clientID,
+ u16 transactionID,
+ void * pData,
+ u16 dataSize )
+{
+ sClientMemList * pClientMem;
+ sReadMemList ** ppThisReadMemList;
+
+#ifdef CONFIG_SMP
+ // Verify Lock
+ if (LocalClientMemLockSpinIsLock( pDev ) == 0)
+ {
+ DBG( "unlocked\n" );
+ BUG();
+ }
+#endif
+
+ // Get this client's memory location
+ pClientMem = FindClientMem( pDev, clientID );
+ if (pClientMem == NULL)
+ {
+ DBG( "Could not find this client's memory 0x%04X\n",
+ clientID );
+
+ return false;
+ }
+
+ // Go to last ReadMemList entry
+ ppThisReadMemList = &pClientMem->mpList;
+ while (*ppThisReadMemList != NULL)
+ {
+ ppThisReadMemList = &(*ppThisReadMemList)->mpNext;
+ }
+
+ *ppThisReadMemList = kmalloc( sizeof( sReadMemList ), GFP_ATOMIC );
+ if (*ppThisReadMemList == NULL)
+ {
+ DBG( "Mem error\n" );
+
+ return false;
+ }
+
+ (*ppThisReadMemList)->mpNext = NULL;
+ (*ppThisReadMemList)->mpData = pData;
+ (*ppThisReadMemList)->mDataSize = dataSize;
+ (*ppThisReadMemList)->mTransactionID = transactionID;
+
+ return true;
+}
+
+/*===========================================================================
+METHOD:
+ PopFromReadMemList (Public Method)
+
+DESCRIPTION:
+ Remove data from this client's ReadMem list if it matches
+ the specified transaction ID.
+
+ Caller MUST have lock on mClientMemLock
+
+PARAMETERS:
+ pDev [ I ] - Device specific memory
+ clientID [ I ] - Requester's client ID
+ transactionID [ I ] - Transaction ID or 0 for any
+ ppData [I/O] - On success, will be filled with a
+ pointer to read buffer
+ pDataSize [I/O] - On succces, will be filled with the
+ read buffer's size
+
+RETURN VALUE:
+ bool
+===========================================================================*/
+bool PopFromReadMemList(
+ sGobiUSBNet * pDev,
+ u16 clientID,
+ u16 transactionID,
+ void ** ppData,
+ u16 * pDataSize )
+{
+ sClientMemList * pClientMem;
+ sReadMemList * pDelReadMemList, ** ppReadMemList;
+ DBG("");
+#ifdef CONFIG_SMP
+ // Verify Lock
+ if (LocalClientMemLockSpinIsLock( pDev ) == 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
+ CLIENT_READMEM_SNAPSHOT(clientID, pDev);
+ while (*ppReadMemList != NULL)
+ {
+ // Do we care about transaction ID?
+ if (transactionID == 0
+ || transactionID == (*ppReadMemList)->mTransactionID )
+ {
+ pDelReadMemList = *ppReadMemList;
+ DBG( "*ppReadMemList = 0x%p pDelReadMemList = 0x%p\n",
+ *ppReadMemList, pDelReadMemList );
+ break;
+ }
+
+ DBG( "skipping 0x%04X data TID = %x\n", clientID, (*ppReadMemList)->mTransactionID );
+
+ // Next
+ ppReadMemList = &(*ppReadMemList)->mpNext;
+ }
+ DBG( "*ppReadMemList = 0x%p pDelReadMemList = 0x%p\n",
+ *ppReadMemList, pDelReadMemList );
+ if (pDelReadMemList != NULL)
+ {
+ if(*ppReadMemList==NULL)
+ {
+ DBG("%d\r\n",__LINE__);
+ return false;
+ }
+ *ppReadMemList = (*ppReadMemList)->mpNext;
+
+ // Copy to output
+ *ppData = pDelReadMemList->mpData;
+ *pDataSize = pDelReadMemList->mDataSize;
+ DBG( "*ppData = 0x%p pDataSize = %u\n",
+ *ppData, *pDataSize );
+
+ // Free memory
+ kfree( pDelReadMemList );
+
+ return true;
+ }
+ else
+ {
+ DBG( "No read memory to pop, Client 0x%04X, TID = 0x%x\n",
+ clientID,
+ transactionID );
+ return false;
+ }
+}
+
+/*===========================================================================
+METHOD:
+ AddToNotifyList (Public Method)
+
+DESCRIPTION:
+ Add Notify entry to this client's notify List
+
+ Caller MUST have lock on mClientMemLock
+
+PARAMETERS:
+ pDev [ I ] - Device specific memory
+ clientID [ I ] - Requester's client ID
+ transactionID [ I ] - Transaction ID or 0 for any
+ pNotifyFunct [ I ] - Callback function to be run when data is available
+ pData [ I ] - Data buffer that willl be passed (unmodified)
+ to callback
+
+RETURN VALUE:
+ bool
+===========================================================================*/
+bool AddToNotifyList(
+ sGobiUSBNet * pDev,
+ u16 clientID,
+ u16 transactionID,
+ void (* pNotifyFunct)(sGobiUSBNet *, u16, void *),
+ void * pData )
+{
+ sClientMemList * pClientMem;
+ sNotifyList ** ppThisNotifyList;
+ DBG("ClientID:0x%x, TID:0x%x\n",clientID,transactionID);
+ if(pDev==NULL)
+ {
+ DBG("NULL");
+ return eNotifyListEmpty;
+ }
+#ifdef CONFIG_SMP
+ // Verify Lock
+ if (LocalClientMemLockSpinIsLock( pDev ) == 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
+===========================================================================*/
+int NotifyAndPopNotifyList(
+ sGobiUSBNet * pDev,
+ u16 clientID,
+ u16 transactionID )
+{
+ sClientMemList * pClientMem;
+ sNotifyList * pDelNotifyList, ** ppNotifyList;
+
+ if(pDev==NULL)
+ {
+ DBG("NULL");
+ return eNotifyListEmpty;
+ }
+#ifdef CONFIG_SMP
+ // Verify Lock
+ if (LocalClientMemLockSpinIsLock( pDev ) == 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 eNotifyListEmpty;
+ }
+
+ ppNotifyList = &(pClientMem->mpReadNotifyList);
+ pDelNotifyList = NULL;
+
+ // Remove from list
+ CLIENT_READMEM_SNAPSHOT(clientID,pDev);
+ 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
+ if(pDev)
+ LocalClientMemUnLockSpinLockIRQRestore(pDev,pDev->mQMIDev.mFlag,__LINE__);
+
+ if((clientID==QMICTL)&&(pDev->mbUnload>=eStatUnloading))
+ {
+
+ }
+ else
+ {
+ pDelNotifyList->mpNotifyFunct( pDev,
+ clientID,
+ pDelNotifyList->mpData );
+ }
+ // Restore lock
+ LocalClientMemLockSpinLockIRQSave(pDev,__LINE__);
+ }
+
+ // Delete memory
+ kfree( pDelNotifyList );
+ return eNotifyListFound;
+ }
+ else
+ {
+ DBG( "no one to notify for Client:0x%x, TID 0x%x\n",clientID, transactionID );
+ return eNotifyListNotFound;
+ }
+}
+
+/*=========================================================================*/
+// Internal userspace wrappers
+/*=========================================================================*/
+
+/*===========================================================================
+METHOD:
+ UserspaceunlockedIOCTL (Public Method)
+
+DESCRIPTION:
+ Internal wrapper for Userspace IOCTL interface
+
+PARAMETERS
+ pFilp [ I ] - userspace file descriptor
+ cmd [ I ] - IOCTL command
+ arg [ I ] - IOCTL argument
+
+RETURN VALUE:
+ long - 0 for success
+ Negative errno for failure
+===========================================================================*/
+long UserspaceunlockedIOCTL(
+ struct file * pFilp,
+ unsigned int cmd,
+ unsigned long arg )
+{
+ int j;
+ int result;
+ u32 devVIDPID;
+
+ sQMIFilpStorage * pFilpData = (sQMIFilpStorage *)pFilp->private_data;
+
+ if (pFilpData == NULL)
+ {
+ DBG( "Bad file data\n" );
+ return -EBADF;
+ }
+#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,32 ))
+ if(cmd==USBDEVFS_RESET)
+ {
+ DBG( "RESET 0\n" );
+ return 0;
+ }
+#endif
+ if (IsDeviceValid( pFilpData->mpDev ) == false)
+ {
+ DBG( "Invalid device! Updating f_ops\n" );
+ return -ENXIO;
+ }
+
+ if(pFilpData->mpDev->mbUnload)
+ {
+ DBG( "Unload:%s\n", __FUNCTION__);
+ return -ENXIO;
+ }
+
+ if(pFilpData->mDeviceInvalid==1)
+ {
+ DBG( "Clsoing.." );
+ return -ENXIO;
+ }
+ if(pFilpData->iIsClosing==1)
+ {
+ DBG( "Invalid device! Updating f_ops\n" );
+ return -ENXIO;
+
+ }
+ if(pFilpData->mpDev->iIsClosing==1)
+ {
+ DBG( "Device Clsoing.." );
+ 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;
+ }
+
+ pFilpData->iSemID = __LINE__;
+ result = GetClientID( pFilpData->mpDev, (u8)arg ,&(pFilpData->mReadSem));
+ pFilpData->iReadSyncResult = 0;
+ pFilpData->iSemID = -__LINE__;
+
+ if (result < 0)
+ {
+ pFilpData->mDeviceInvalid = 1;
+ return result;
+ }
+ pFilpData->mClientID = (u16)result;
+ DBG("pFilpData->mClientID = 0x%x\n", pFilpData->mClientID );
+ return 0;
+ break;
+
+
+ case IOCTL_QMI_GET_DEVICE_VIDPID:
+ if (arg == 0)
+ {
+ DBG( "Bad VIDPID buffer\n" );
+ return -EINVAL;
+ }
+
+ // Extra verification
+ if (pFilpData->mpDev->mpNetDev == 0)
+ {
+ DBG( "Bad mpNetDev\n" );
+ return -ENOMEM;
+ }
+ if (pFilpData->mpDev->mpNetDev->udev == 0)
+ {
+ DBG( "Bad udev\n" );
+ return -ENOMEM;
+ }
+
+ devVIDPID = ((le16_to_cpu( pFilpData->mpDev->mpNetDev->udev->descriptor.idVendor ) << 16)
+ + le16_to_cpu( pFilpData->mpDev->mpNetDev->udev->descriptor.idProduct ) );
+
+ result = copy_to_user( (unsigned int *)arg, &devVIDPID, 4 );
+ if (result != 0)
+ {
+ DBG( "Copy to userspace failure %d\n", result );
+ }
+
+ return result;
+
+ break;
+
+ case IOCTL_QMI_GET_DEVICE_MEID:
+ if (arg == 0)
+ {
+ DBG( "Bad MEID buffer\n" );
+ return -EINVAL;
+ }
+ result = copy_to_user( (unsigned int *)arg, &pFilpData->mpDev->mMEID[0], MAX_DEVICE_MEID_SIZE);
+ if (result != 0)
+ {
+ DBG( "Copy to userspace failure %d\n", result );
+ }
+
+ return result;
+
+ break;
+
+ case IOCTL_QMI_ADD_MAPPING:
+ {
+ sGobiUSBNet * pDev = pFilpData->mpDev;
+ sMapping *pmap = (sMapping*) arg;
+
+ DBG( "add mapping\n" );
+ if (arg == 0)
+ {
+ DBG( "null pointer\n" );
+ return -EINVAL;
+ }
+ DBG( "dscp, qos_id: 0x%x, 0x%x\n", pmap->dscp, pmap->qosId );
+
+ if ((MAX_DSCP_ID < pmap->dscp) && (UNIQUE_DSCP_ID != pmap->dscp))
+ {
+ DBG( "Invalid DSCP value\n" );
+ return -EINVAL;
+ }
+
+ //check for existing map
+ for (j=0;j<MAX_MAP;j++)
+ {
+ if (pDev->maps.table[j].dscp == pmap->dscp)
+ {
+ DBG("mapping already exists at slot #%d\n", j);
+ return -EINVAL;
+ }
+ }
+
+ //check if this is a request to redirect all IP traffic to default bearer
+ if (UNIQUE_DSCP_ID == pmap->dscp)
+ {
+ DBG("set slot (%d) to indicate IP packet redirection is needed\n", MAX_MAP-1);
+ pDev->maps.table[MAX_MAP-1].dscp = UNIQUE_DSCP_ID;
+ pDev->maps.table[MAX_MAP-1].qosId = pmap->qosId;
+ pDev->maps.count++;
+ return 0;
+ }
+
+ //find free slot to hold new mapping
+ for(j=0;j<MAX_MAP-1;j++)
+ {
+ if (pDev->maps.table[j].dscp == 0xff)
+ {
+ pDev->maps.table[j].dscp = pmap->dscp;
+ pDev->maps.table[j].qosId = pmap->qosId;
+ pDev->maps.count++;
+ return 0;
+ }
+ }
+
+ DBG("no free mapping slot\n");
+ return -ENOMEM;
+ }
+ break;
+
+ case IOCTL_QMI_EDIT_MAPPING:
+ {
+ sGobiUSBNet * pDev = pFilpData->mpDev;
+
+ sMapping *pmap = (sMapping*) arg;
+ DBG( "edit mapping\n" );
+ if (arg == 0)
+ {
+ DBG( "null pointer\n" );
+ return -EINVAL;
+ }
+ DBG( "dscp, qos_id: 0x%x, 0x%x\n", pmap->dscp, pmap->qosId );
+
+ if ((MAX_DSCP_ID < pmap->dscp) && (UNIQUE_DSCP_ID != pmap->dscp))
+ {
+ DBG( "Invalid DSCP value\n" );
+ return -EINVAL;
+ }
+
+ for(j=0;j<MAX_MAP;j++)
+ {
+ if (pDev->maps.table[j].dscp == pmap->dscp)
+ {
+ pDev->maps.table[j].qosId = pmap->qosId;
+ return 0;
+ }
+ }
+
+ DBG("no matching tos for edit mapping\n");
+ return -ENOMEM;
+ }
+ break;
+
+ case IOCTL_QMI_READ_MAPPING:
+ {
+ sGobiUSBNet * pDev = pFilpData->mpDev;
+
+ sMapping *pmap = (sMapping*) arg;
+ DBG( "read mapping\n" );
+ if (arg == 0)
+ {
+ DBG( "null pointer\n" );
+ return -EINVAL;
+ }
+
+ if ((MAX_DSCP_ID < pmap->dscp) && (UNIQUE_DSCP_ID != pmap->dscp))
+ {
+ DBG( "Invalid DSCP value\n" );
+ return -EINVAL;
+ }
+
+ for(j=0;j<MAX_MAP;j++)
+ {
+ if (pDev->maps.table[j].dscp == pmap->dscp)
+ {
+ pmap->qosId = pDev->maps.table[j].qosId;
+ DBG( "dscp, qos_id: 0x%x, 0x%x\n", pmap->dscp, pmap->qosId );
+
+ result = copy_to_user( (unsigned int *)arg, &pDev->maps.table[j], sizeof(sMapping));
+ if (result != 0)
+ {
+ DBG( "Copy to userspace failure %d\n", result );
+ }
+
+ return result;
+ }
+ }
+
+ DBG("no matching tos for read mapping\n");
+ return -ENOMEM;
+ }
+ break;
+
+ case IOCTL_QMI_DEL_MAPPING:
+ {
+ sGobiUSBNet * pDev = pFilpData->mpDev;
+ sMapping *pmap = (sMapping*) arg;
+ DBG( "Delete mapping\n" );
+ if (arg == 0)
+ {
+ DBG( "null pointer\n" );
+ return -EINVAL;
+ }
+ DBG( "DSCP 0x%x\n", pmap->dscp );
+
+ if ((MAX_DSCP_ID < pmap->dscp) && (UNIQUE_DSCP_ID != pmap->dscp))
+ {
+ DBG( "Invalid DSCP value\n" );
+ return -EINVAL;
+ }
+
+ for(j=0;j<MAX_MAP;j++)
+ {
+ if (pDev->maps.table[j].dscp == pmap->dscp)
+ {
+ // delete mapping table entry
+ memset(&pDev->maps.table[j], 0xff, sizeof(pDev->maps.table[0]));
+ if (pDev->maps.count) pDev->maps.count--;
+ return 0;
+ }
+ }
+
+ DBG("no matching mapping slot\n");
+ return -ENOMEM;
+ }
+ break;
+
+ case IOCTL_QMI_CLR_MAPPING:
+ {
+ sGobiUSBNet * pDev = pFilpData->mpDev;
+ DBG( "Clear mapping\n" );
+ memset(pDev->maps.table, 0xff, sizeof(pDev->maps.table));
+ pDev->maps.count = 0;
+ return 0;
+ }
+ break;
+
+#ifdef QOS_SIMULATE
+ case IOCTL_QMI_QOS_SIMULATE:
+ {
+ int result;
+ u8 supported = (u8)-1;
+ DBG( "simulate indication\n" );
+ u8 qos_support_ind[] = {
+ 0x01,0x15,0x00,0x80,0x04,0xFF,0x04,0x00,0x00,
+ 0x27,0x00,0x09,0x00,0x01,0x01,0x00,0x01,0x10,0x02,0x00,0x01,0x80
+ };
+ u8 qos_flow_activate_ind[] = {
+ 0x01,0x15,0x00,0x80,0x04,0xFF,0x04,0x00,0x00,
+ 0x26,0x00,0x09,0x00,0x01,0x06,0x00,0xDD,0xCC,0xBB,0xAA,0x01,0x01
+ };
+ u8 qos_flow_suspend_ind[] = {
+ 0x01,0x15,0x00,0x80,0x04,0xFF,0x04,0x00,0x00,
+ 0x26,0x00,0x09,0x00,0x01,0x06,0x00,0xDD,0xCC,0xBB,0xAA,0x02,0x02
+ };
+ u8 qos_flow_gone_ind[] = {
+ 0x01,0x15,0x00,0x80,0x04,0xFF,0x04,0x00,0x00,
+ 0x26,0x00,0x09,0x00,0x01,0x06,0x00,0xDD,0xCC,0xBB,0xAA,0x03,0x03
+ };
+ result = QMIQOSEventResp( qos_support_ind,
+ sizeof(qos_support_ind));
+ result = QMIQOSEventResp( qos_flow_activate_ind,
+ sizeof(qos_flow_activate_ind));
+ result = QMIQOSEventResp( qos_flow_suspend_ind,
+ sizeof(qos_flow_suspend_ind));
+ result = QMIQOSEventResp( qos_flow_gone_ind,
+ sizeof(qos_flow_gone_ind));
+ return 0;
+ }
+ break;
+#endif
+
+ case IOCTL_QMI_GET_TX_Q_LEN:
+ {
+
+ sGobiUSBNet * pDev = pFilpData->mpDev;
+
+ if (arg == 0)
+ {
+ DBG( "Bad Tx Queue 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;
+ }
+
+ result = copy_to_user( (unsigned int *)arg, &pDev->tx_qlen, sizeof(pDev->tx_qlen) );
+ if (result != 0)
+ {
+ DBG( "Copy to userspace failure %d\n", result );
+ }
+
+ return result;
+ }
+
+ break;
+
+ case IOCTL_QMI_DUMP_MAPPING:
+ {
+ sGobiUSBNet * pDev = pFilpData->mpDev;
+
+ DBG( "dump mapping\n" );
+ if (arg == 0)
+ {
+ DBG( "null pointer\n" );
+ return -EINVAL;
+ }
+
+ result = copy_to_user( (unsigned int *)arg, &pDev->maps.table[0], sizeof(pDev->maps.table));
+ if (result != 0)
+ {
+ DBG( "Copy to userspace failure %d\n", result );
+ }
+ return result;
+ }
+
+ case IOCTL_QMI_GET_USBNET_STATS:
+ {
+ sGobiUSBNet * pDev = pFilpData->mpDev;
+ struct net_device_stats * pStats = &(pDev->mpNetDev->net->stats);
+ sNetStats netStats;
+
+ if (arg == 0)
+ {
+ DBG( "Bad usbnet statistic buffer\n" );
+ return -EINVAL;
+ }
+
+ // Extra verification
+ if (pFilpData->mpDev->mpNetDev == 0)
+ {
+ DBG( "Bad mpNetDev\n" );
+ return -ENOMEM;
+ }
+
+ /* copy the value from struct net_device_stats to struct sNetStats */
+ netStats.rx_packets = pStats->rx_packets;
+ netStats.tx_packets = pStats->tx_packets;
+ netStats.rx_bytes = pStats->rx_bytes;
+ netStats.tx_bytes = pStats->tx_bytes;
+ netStats.rx_errors = pStats->rx_errors;
+ netStats.tx_errors = pStats->tx_errors;
+ netStats.rx_overflows = pStats->rx_fifo_errors;
+ netStats.tx_overflows = pStats->tx_fifo_errors;
+
+ result = copy_to_user( (unsigned int *)arg, &netStats, sizeof(sNetStats) );
+ if (result != 0)
+ {
+ DBG( "Copy to userspace failure %d\n", result );
+ }
+
+ return result;
+ }
+
+ break;
+ case IOCTL_QMI_SET_DEVICE_MTU:
+ {
+ sGobiUSBNet *pDev = pFilpData->mpDev;
+ // struct usbnet * pNet = netdev_priv( pDev->mpNetDev->net );
+ int iArgp = (int)arg;
+ if (iArgp <= 0)
+ {
+ DBG( "Bad MTU buffer\n" );
+ return -EINVAL;
+ }
+ DBG( "new mtu :%d ,qcqmi:%d\n",iArgp,(int)pDev->mQMIDev.qcqmi );
+ pDev->mtu = iArgp;
+ usbnet_change_mtu(pDev->mpNetDev->net ,pDev->mtu);
+ }
+ return 0;
+ default:
+ return -EBADRQC;
+ }
+}
+
+/*=========================================================================*/
+// 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;
+ sQMIDev * pQMIDev = NULL;
+ sGobiUSBNet * pDev = NULL;
+ DBG( "\n" );
+ // Optain device pointer from pInode
+ if(signal_pending(current))
+ {
+ return -ERESTARTSYS;
+ }
+ pQMIDev = container_of( pInode->i_cdev,
+ sQMIDev,
+ mCdev );
+ pDev = container_of( pQMIDev,
+ sGobiUSBNet,
+ mQMIDev );
+ if(signal_pending(current))
+ {
+ return -ERESTARTSYS;
+ }
+ pFilp->private_data = NULL;
+
+ if (IsDeviceValid( pDev ) == false)
+ {
+ printk( KERN_INFO "Invalid device\n" );
+ return -ENXIO;
+ }
+ if(pDev->mbUnload)
+ {
+ printk( KERN_INFO "Unload:%s\n", __FUNCTION__);
+ return -ENXIO;
+ }
+ if(pDev->iIsClosing)
+ {
+ printk( KERN_INFO "Unload:%s\n", __FUNCTION__);
+ return -ENXIO;
+ }
+ // Setup data in pFilp->private_data
+ pFilp->private_data = kmalloc( sizeof( sQMIFilpStorage ), GFP_KERNEL );
+ if (pFilp->private_data == NULL)
+ {
+ printk( KERN_INFO "Mem error\n" );
+ return -ENOMEM;
+ }
+
+ pFilpData = (sQMIFilpStorage *)pFilp->private_data;
+ pFilpData->mClientID = (u16)-1;
+ pFilpData->mDeviceInvalid = 0;
+ pFilpData->mpDev = pDev;
+ pFilpData->iSemID = -1;
+ pFilpData->iIsClosing = 0;
+ pFilpData->iReadSyncResult = -1;
+ sema_init(&pFilpData->mReadSem , SEMI_INIT_DEFAULT_VALUE );
+ 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 )
+{
+ if(signal_pending(current))
+ {
+ return -ERESTARTSYS;
+ }
+ // call the internal wrapper function
+ return (int)UserspaceunlockedIOCTL( pFilp, cmd, arg );
+}
+
+/*===========================================================================
+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 = NULL;
+ DBG( "\n" );
+
+ if(pFilp ==NULL)
+ {
+ printk( KERN_INFO "bad file data\n" );
+ return -EBADF;
+ }
+
+ pFilpData = (sQMIFilpStorage *)pFilp->private_data;
+ if (pFilpData == NULL)
+ {
+ printk( KERN_INFO "bad file data\n" );
+ return 0;
+ }
+ if (IsDeviceValid( pFilpData->mpDev ) == false)
+ {
+ printk( KERN_INFO "%s Invalid device! Updating f_ops\n",__FUNCTION__ );
+ }
+
+ pFilpData->iIsClosing = 1;
+ pFilpData->mDeviceInvalid = 1;
+ GobiSyncRcu();
+ if(pFilpData->iSemID > 0)
+ {
+ int iRetry = 0;
+ int iReturn = 0;
+ int iLockCount = 0;
+ if(pFilpData->mpDev->mbUnload)
+ {
+ iReturn = -EAGAIN;
+ }
+
+ do
+ {
+ GobiSyncRcu();
+ if(LocalClientMemLockSpinIsLock(pFilpData->mpDev)!=0)
+ {
+ if(iLockCount++ > 5)
+ {
+ unsigned long flags = pFilpData->mpDev->mQMIDev.mFlag;
+ printk("Force Unlock!");
+ LocalClientMemUnLockSpinLockIRQRestore(pFilpData->mpDev,flags,__LINE__);
+ }
+ else
+ {
+ gobi_flush_work();
+ continue;
+ }
+ }
+ iLockCount = 0;
+ if((pFilpData==NULL) || (pFilp==NULL))
+ {
+ iReturn = 0;
+ break;
+ }
+ #if (LINUX_VERSION_CODE >= KERNEL_VERSION( 2,6,33 ))
+ if(do_raw_spin_trylock(&(pFilpData->mReadSem.lock))==0)
+ {
+ DBG("NOT locked");
+ do_raw_spin_unlock(&(pFilpData->mReadSem.lock));
+ if(!down_trylock(&(pFilpData->mReadSem)))
+ {
+ DBG("NOT locked");
+ }
+ up(&(pFilpData->mReadSem));
+ }
+ #else
+ if(!down_trylock(&(pFilpData->mReadSem)))
+ {
+ DBG("NOT locked");
+ }
+ up(&(pFilpData->mReadSem));
+ #endif
+
+ weakup_inode_process(pFilp,NULL);
+ gobi_flush_work();
+ if((pFilpData==NULL) || (pFilp==NULL))
+ {
+ iReturn = 0;
+ break;
+ }
+ if(iRetry++>10)
+ {
+ iReturn = -EAGAIN;
+ printk("Timeout!");
+ break;
+ }
+ }while(pFilpData->iSemID > 0);
+ GobiSyncRcu();
+ }
+
+ if(pFilpData->mpDev->mbUnload)
+ {
+ return 0;
+ }
+
+ if (pFilpData->mpDev->mbUnload > eStatUnloading)
+ {
+ kfree( pFilp->private_data );
+ pFilp->private_data = NULL;
+ return -ENXIO;
+ }
+
+ DBG( "0x%04X\n", pFilpData->mClientID );
+
+ if (pFilpData->mClientID != (u16)-1)
+ {
+ pFilpData->iSemID = __LINE__;
+ if( (pFilpData->iReadSyncResult>=0) &&
+ (pFilpData->mpDev->mbUnload < eStatUnloading))
+ {
+ ReleaseClientID( pFilpData->mpDev,
+ pFilpData->mClientID);
+ }
+ else
+ {
+ unsigned long flags;
+ flags = LocalClientMemLockSpinLockIRQSave( pFilpData->mpDev , __LINE__);
+ RemoveAndPopNotifyList(pFilpData->mpDev,
+ pFilpData->mClientID,0,eClearAndReleaseCID);
+ LocalClientMemUnLockSpinLockIRQRestore ( pFilpData->mpDev ,flags,__LINE__);
+
+ }
+ pFilpData->iSemID = -__LINE__;
+ pFilpData->mClientID = (u16)-1;
+ }
+
+ kfree( pFilp->private_data );
+
+ // 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;
+ GobiSyncRcu();
+ 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 = -1;
+ void * pReadData = NULL;
+ void * pSmallReadData = NULL;
+ sQMIFilpStorage * pFilpData = NULL;
+ DBG("\n");
+ if(pFilp==NULL)
+ {
+ return -EBADF;
+ }
+ pFilpData = (sQMIFilpStorage *)pFilp->private_data;
+ if (pFilpData == NULL)
+ {
+ DBG( "Bad file data\n" );
+ return -EBADF;
+ }
+
+ if(signal_pending(current))
+ {
+ return -ERESTARTSYS;
+ }
+
+ if (IsDeviceValid( pFilpData->mpDev ) == false)
+ {
+ DBG( "Invalid device! Updating f_ops\n" );
+ return -ENXIO;
+ }
+ if(pFilpData->mpDev->mbUnload)
+ {
+ DBG( "Unload:%s\n", __FUNCTION__);
+ return -ENXIO;
+ }
+
+ if(pFilpData->mDeviceInvalid)
+ {
+ DBG( "mDeviceInvalid\n");
+ return -ENXIO;
+ }
+
+ if (pFilpData->mClientID == (u16)-1)
+ {
+ DBG( "Client ID must be set before reading 0x%04X\n",
+ pFilpData->mClientID );
+ return -EBADR;
+ }
+ if(pFilpData->iIsClosing==1)
+ {
+ DBG( "filep Clsoing.." );
+ return -ENXIO;
+ }
+
+ if(pFilpData->mpDev->iIsClosing==1)
+ {
+ DBG( "Device Clsoing.." );
+ return -ENXIO;
+ }
+
+ pFilpData->iSemID = __LINE__;
+ // Perform synchronous read
+ result = ReadSync( pFilpData->mpDev,
+ &pReadData,
+ pFilpData->mClientID,
+ 0,
+ &(pFilpData->iSemID),&(pFilpData->mReadSem),&(pFilpData->iIsClosing));
+ if(pFilp==NULL)
+ {
+ DBG("%s pFilp NULL\n",__FUNCTION__);
+ return -ENXIO;
+ }
+ if(pFilpData==NULL)
+ {
+ return -ENXIO;
+ }
+ pFilpData->iSemID = -__LINE__;
+ pFilpData->iReadSyncResult = result;
+ GobiSyncRcu();
+ if(result<0)
+ {
+ DBG("Read Error!CID:0x%04x\n",pFilpData->mClientID);
+ }
+ else
+ {
+ PrintHex((char*)&pReadData,result);
+ }
+ if(pFilp==NULL)
+ {
+ DBG("%s pFilp NULL\n",__FUNCTION__);
+ return -ENXIO;
+ }
+ if(pFilpData->mpDev==NULL)
+ {
+ DBG("%s pFilp NULL\n",__FUNCTION__);
+ return -ENXIO;
+ }
+ if((pFilpData->mpDev->mbUnload)||(pFilpData->iIsClosing))
+ {
+ return -ENXIO;
+ }
+ if (result <= 0)
+ {
+ #ifdef CONFIG_PM
+ if(bIsSuspend(pFilpData->mpDev))
+ {
+ DBG("SUSPEND\n");
+ }
+ #endif
+ if(result == -EINTR)
+ {
+ DBG("RETRY\n");
+ }
+ 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" );
+ if(pReadData)
+ kfree( pReadData );
+ return -EOVERFLOW;
+ }
+
+ DBG( "pBuf = 0x%p pSmallReadData = 0x%p, result = %d",
+ pBuf, pSmallReadData, result );
+
+ if (copy_to_user( pBuf, pSmallReadData, result ) != 0)
+ {
+ DBG( "Error copying read data to user\n" );
+ result = -EFAULT;
+ }
+ pSmallReadData = NULL;
+ // 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(signal_pending(current))
+ {
+ return -ERESTARTSYS;
+ }
+ if (IsDeviceValid( pFilpData->mpDev ) == false)
+ {
+ DBG( "Invalid device! Updating f_ops\n" );
+ return -ENXIO;
+ }
+ if(pFilpData->mpDev->mbUnload)
+ {
+ DBG( "Unload:%s\n", __FUNCTION__);
+ return -ENXIO;
+ }
+
+ if(pFilpData->mDeviceInvalid)
+ {
+ DBG( "mDeviceInvalid\n");
+ return -ENXIO;
+ }
+
+ if (pFilpData->mClientID == (u16)-1)
+ {
+ DBG( "Client ID must be set before writing 0x%04X\n",
+ pFilpData->mClientID );
+ return -EBADR;
+ }
+ if(pFilpData->iIsClosing==1)
+ {
+ DBG( "Filep Clsoing.." );
+ return -ENXIO;
+ }
+ if(pFilpData->mpDev->iIsClosing==1)
+ {
+ DBG( "Device Clsoing.." );
+ return -ENXIO;
+ }
+
+ // 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
+ {
+ pFilpData->mDeviceInvalid = 1;
+ if(status<0)
+ {
+ pFilpData->iIsClosing=1;
+ return -ENXIO;
+ }
+ return status;
+ }
+}
+
+/*===========================================================================
+METHOD:
+ UserspacePoll (Public Method)
+
+DESCRIPTION:
+ Used to determine if read/write operations are possible without blocking
+
+PARAMETERS
+ pFilp [ I ] - userspace file descriptor
+ pPollTable [I/O] - Wait object to notify the kernel when data
+ is ready
+
+RETURN VALUE:
+ unsigned int - bitmask of what operations can be done immediately
+===========================================================================*/
+unsigned int UserspacePoll(
+ struct file * pFilp,
+ struct poll_table_struct * pPollTable )
+{
+ sQMIFilpStorage * pFilpData = (sQMIFilpStorage *)pFilp->private_data;
+ sClientMemList * pClientMem;
+ unsigned long flags;
+
+ // Always ready to write
+ unsigned int status = POLLOUT | POLLWRNORM;
+
+ if (pFilpData == NULL)
+ {
+ DBG( "Bad file data\n" );
+ return POLLERR;
+ }
+
+ if (IsDeviceValid( pFilpData->mpDev ) == false)
+ {
+ DBG( "Invalid device! Updating f_ops\n" );
+ return POLLERR;
+ }
+
+ if (pFilpData->mClientID == (u16)-1)
+ {
+ DBG( "Client ID must be set before polling 0x%04X\n",
+ pFilpData->mClientID );
+ return POLLERR;
+ }
+
+ // Critical section
+ flags = LocalClientMemLockSpinLockIRQSave( pFilpData->mpDev , __LINE__);
+
+ // Get this client's memory location
+ pClientMem = FindClientMem( pFilpData->mpDev,
+ pFilpData->mClientID );
+ if (pClientMem == NULL)
+ {
+ DBG( "Could not find this client's memory 0x%04X\n",
+ pFilpData->mClientID );
+
+ LocalClientMemUnLockSpinLockIRQRestore ( pFilpData->mpDev ,flags,__LINE__);
+ return POLLERR;
+ }
+
+ poll_wait( pFilp, &pClientMem->mWaitQueue, pPollTable );
+
+ if (pClientMem->mpList != NULL)
+ {
+ status |= POLLIN | POLLRDNORM;
+ }
+
+ // End critical section
+ LocalClientMemUnLockSpinLockIRQRestore ( pFilpData->mpDev ,flags,__LINE__);
+
+ // Always ready to write
+ return (status | POLLOUT | POLLWRNORM);
+}
+
+
+int weakup_inode_process(struct file *pFilp,struct task_struct * pTask)
+{
+ struct task_struct *pEachTask=NULL;
+ if(pFilp==NULL)
+ return 0;
+
+ if(pTask)
+ {
+ if(pTask->state != TASK_STOPPED)
+ {
+ wake_up_process(pTask);
+ return 0;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+ for_each_process( pEachTask )
+ {
+ int count = 0;
+ struct fdtable * pFDT;
+ if (pEachTask == NULL || pEachTask->files == NULL)
+ {
+ // Some tasks may not have files (e.g. Xsession)
+ continue;
+ }
+ pFDT = files_fdtable( pEachTask->files );
+ for (count = 0; count < pFDT->max_fds; count++)
+ {
+ if (pFDT->fd[count] == pFilp)
+ {
+ if(pEachTask->state != TASK_STOPPED)
+ {
+ wake_up_process(pEachTask);
+ }
+ count = pFDT->max_fds;
+ }
+ }
+ }
+ return 0;
+}
+
+/*===========================================================================
+METHOD:
+ UserSpaceLock (Public Method)
+
+DESCRIPTION:
+ Used to determine if read/write operations are possible without blocking
+
+PARAMETERS
+ pFilp [ I ] - The file to apply the lock to
+ cmd [ I ] - type of locking operation (F_SETLK, F_GETLK, etc.)
+ fl [I/O] - The lock to be applied
+
+RETURN VALUE:
+ unsigned int - bitmask of what operations can be done immediately
+===========================================================================*/
+int UserSpaceLock(struct file *filp, int cmd, struct file_lock *fl)
+{
+ if((filp!=NULL) && (fl!=NULL))
+ {
+ #if (LINUX_VERSION_CODE >= KERNEL_VERSION( 3,9,0 ))
+ if(file_inode(filp)!=NULL)
+ {
+ return posix_lock_file(filp, fl, NULL);
+ }
+ #else
+ return posix_lock_file(filp, fl, NULL);
+ #endif
+ }
+ return -ENOLCK;
+}
+
+/*===========================================================================
+METHOD:
+ UserspaceRelease (Public Method)
+
+DESCRIPTION:
+ Used to determine if read/write operations are possible without blocking
+
+PARAMETERS
+ pInode [I/O] - userspace file descriptor
+ pFilp [I/O] - userspace file descriptor
+
+RETURN VALUE:
+ unsigned int - bitmask of what operations can be done immediately
+===========================================================================*/
+int UserspaceRelease(struct inode *pInode, struct file *pFilp)
+{
+ sQMIFilpStorage * pFilpData = NULL;
+
+ if(pFilp==NULL)
+ {
+ return 0;
+ }
+
+ pFilpData = (sQMIFilpStorage *)pFilp->private_data;
+ if(pFilpData!=NULL)
+ {
+ pFilpData->iIsClosing = 1;
+ pFilpData->mDeviceInvalid = 1;
+ if(pFilpData->iSemID > 0)
+ {
+ int iRetry = 0;
+ int iLockCount =0;
+ do
+ {
+ GobiSyncRcu();
+ if(LocalClientMemLockSpinIsLock(pFilpData->mpDev)!=0)
+ {
+ if(iLockCount++ > 5)
+ {
+ unsigned long flags = pFilpData->mpDev->mQMIDev.mFlag;
+ printk("Force Unlock!\n");
+ LocalClientMemUnLockSpinLockIRQRestore(pFilpData->mpDev,flags,__LINE__);
+ }
+ else
+ {
+ gobi_flush_work();
+ continue;
+ }
+ }
+ iLockCount = 0;
+ #if (LINUX_VERSION_CODE >= KERNEL_VERSION( 2,6,33 ))
+ if(do_raw_spin_trylock(&(pFilpData->mReadSem.lock))==0)
+ {
+ DBG("NOT locked");
+ do_raw_spin_unlock(&(pFilpData->mReadSem.lock));
+ if(!down_trylock(&(pFilpData->mReadSem)))
+ {
+ DBG("NOT locked");
+ }
+ up(&(pFilpData->mReadSem));
+ }
+ #else
+ if(!down_trylock(&(pFilpData->mReadSem)))
+ {
+ DBG("NOT locked");
+ }
+ up(&(pFilpData->mReadSem));
+ #endif
+ weakup_inode_process(pFilp,NULL);
+ gobi_flush_work();
+ GobiSyncRcu();
+ if((pFilpData==NULL) || (pFilp==NULL))
+ {
+ break;
+ }
+ if(iRetry++>5)
+ {
+ break;
+ }
+ }while(pFilpData->iSemID > 0);
+ return 0;
+ }
+ if(pFilp)
+ if(pFilp->private_data)
+ {
+ kfree(pFilp->private_data);
+ pFilp->private_data = NULL;
+ }
+ }
+ GobiSyncRcu();
+ return 0;
+}
+/*=========================================================================*/
+// Initializer and destructor
+/*=========================================================================*/
+int QMICTLSyncProc(sGobiUSBNet *pDev)
+{
+ void *pWriteBuffer;
+ void *pReadBuffer;
+ int result;
+ u16 writeBufferSize;
+ u8 transactionID;
+ struct semaphore readSem;
+ unsigned long flags;
+ u16 readBufferSize;
+
+ if (IsDeviceValid( pDev ) == false)
+ {
+ DBG( "Invalid device\n" );
+ return -EFAULT;
+ }
+ sema_init( &readSem, SEMI_INIT_DEFAULT_VALUE );
+ writeBufferSize= QMICTLSyncReqSize();
+ pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
+ if (pWriteBuffer == NULL)
+ {
+ return -ENOMEM;
+ }
+
+ transactionID = QMIXactionIDGet(pDev);
+
+ /* send a QMI_CTL_SYNC_REQ (0x0027) */
+ result = QMICTLSyncReq( pWriteBuffer,
+ writeBufferSize,
+ transactionID );
+ if (result < 0)
+ {
+ kfree( pWriteBuffer );
+ return result;
+ }
+
+ result = ReadAsync( pDev, QMICTL, transactionID, UpSem, &readSem );
+ if(result == 0)
+ {
+ result = WriteSync( pDev,
+ pWriteBuffer,
+ writeBufferSize,
+ QMICTL );
+ kfree( pWriteBuffer );
+ }
+ wait_ms(QMI_CONTROL_MSG_DELAY_MS);
+ if (down_trylock( &readSem ) == 0)
+ {
+ // Enter critical section
+ flags = LocalClientMemLockSpinLockIRQSave( pDev , __LINE__);
+ // Pop the read data
+ if (PopFromReadMemList( pDev,
+ QMICTL,
+ transactionID,
+ &pReadBuffer,
+ &readBufferSize ) == true)
+ {
+ // End critical section
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+ result = QMICTLSyncResp(pReadBuffer,
+ readBufferSize);
+ // We don't care about the result
+ if(pReadBuffer)
+ kfree( pReadBuffer );
+ }
+ else
+ {
+ // Read mismatch/failure, unlock and continue
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+ }
+ }
+ else
+ {
+ ReleaseNotifyList(pDev,QMICTL,transactionID);
+ result = -1;
+ }
+
+ if (result < 0) /* need to re-sync */
+ {
+ DBG( "sync response error code %d\n", result );
+ /* start timer and wait for the response */
+ /* process response */
+ return result;
+ }
+
+ // Success
+ return 0;
+}
+
+static int
+qmi_show(struct seq_file *m, void *v)
+{
+ sGobiUSBNet * pDev = (sGobiUSBNet*) m->private;
+ seq_printf(m, "readTimeoutCnt %d\n", pDev->readTimeoutCnt);
+ seq_printf(m, "writeTimeoutCnt %d\n", pDev->writeTimeoutCnt);
+ return 0;
+}
+
+static int
+qmi_open(struct inode *inode, struct file *file)
+{
+ char *data;
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 3,10,0 ))
+ data=PDE_DATA(inode);
+#else
+ data=PDE(inode)->data;
+#endif
+
+ return single_open(file, qmi_show, data);
+}
+
+static const struct file_operations proc_fops = {
+ .owner = THIS_MODULE,
+ .open = qmi_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+/*===========================================================================
+METHOD:
+ RegisterQMIDevice (Public Method)
+
+DESCRIPTION:
+ QMI Device initialization function
+
+PARAMETERS:
+ pDev [ I ] - Device specific memory
+ is9x15 [ I ]
+
+RETURN VALUE:
+ int - 0 for success
+ Negative errno for failure
+===========================================================================*/
+int RegisterQMIDevice( sGobiUSBNet * pDev, int is9x15 )
+{
+ char qcqmi_dev_name[10];
+ int i;
+ int result;
+ dev_t devno;
+ pDev->mQMIDev.proc_file = NULL;
+ if (pDev->mQMIDev.mbCdevIsInitialized == true)
+ {
+ // Should never happen, but always better to check
+ DBG( "device already exists\n" );
+ return -EEXIST;
+ }
+
+ pDev->mbQMIValid = true;
+ pDev->mbUnload = eStatRegister;
+ pDev->readTimeoutCnt = 0;
+ pDev->writeTimeoutCnt = 0;
+ pDev->mtu = 0;
+ pDev->iShutdown_write_sem = -1;
+ pDev->iShutdown_read_sem = -1;
+ init_rwsem(&pDev->shutdown_rwsem);
+ InitSemID(pDev);
+
+ i=0;
+ do
+ {
+ // Set up for QMICTL
+ // (does not send QMI message, just sets up memory)
+ if(kthread_should_stop())
+ {
+ return -1;
+ }
+ if(signal_pending(current))
+ {
+ return -1;
+ }
+
+ result = GetClientID( pDev, QMICTL ,NULL);
+
+ if(kthread_should_stop())
+ {
+ return -1;
+ }
+ if(signal_pending(current))
+ {
+ return -1;
+ }
+ if(pDev->mbUnload != eStatRegister)
+ {
+ return result;
+ }
+ if (result != 0)
+ {
+ if(i++>MAX_RETRY)
+ {
+ pDev->mbQMIValid = false;
+ return result;
+ }
+ }
+ }while(result!=0);
+ atomic_set( &pDev->mQMIDev.mQMICTLTransactionID, 1 );
+
+ // Start Async reading
+ result = StartRead( pDev );
+ if (result != 0)
+ {
+ pDev->mbQMIValid = false;
+ return result;
+ }
+
+ // Send SetControlLineState request (USB_CDC)
+ // Required for Autoconnect and 9x30 to wake up
+ result = usb_control_msg( pDev->mpNetDev->udev,
+ usb_sndctrlpipe( pDev->mpNetDev->udev, 0 ),
+ SET_CONTROL_LINE_STATE_REQUEST,
+ SET_CONTROL_LINE_STATE_REQUEST_TYPE,
+ CONTROL_DTR,
+ /* USB interface number to receive control message */
+ pDev->mpIntf->cur_altsetting->desc.bInterfaceNumber,
+ NULL,
+ 0,
+ 100 );
+ if (result < 0)
+ {
+ DBG( "Bad SetControlLineState status %d\n", result );
+ return result;
+ }
+
+
+ // Device is not ready for QMI connections right away
+ // Wait up to 30 seconds before failing
+ result = QMIReady( pDev, 30000 );
+ if(result==-1)
+ {
+ pDev->mbUnload = eStatUnloading;
+ return -ETIMEDOUT;
+ }
+ else if (result == false)
+ {
+ DBG( "Device unresponsive to QMI\n" );
+ return -ETIMEDOUT;
+ }
+ if(pDev->mbUnload != eStatRegister)
+ {
+ return -ETIMEDOUT;
+ }
+ // Initiate QMI CTL Sync Procedure
+ DBG( "Sending QMI CTL Sync Request\n" );
+ i=0;
+ do
+ {
+ result = QMICTLSyncProc(pDev);
+ if(isModuleUnload(pDev))
+ {
+ return -EFAULT;;
+ }
+ if (result != 0)
+ {
+ if(i++>MAX_RETRY)
+ {
+ DBG( "QMI CTL Sync Procedure Error\n" );
+ return result;
+ }
+ }
+ else
+ {
+ DBG( "QMI CTL Sync Procedure Successful\n" );
+ }
+ }while(result!=0);
+ // Setup Data Format
+ if (is9x15)
+ {
+ i=0;
+ if(iRAWIPEnable==0)
+ {
+ pDev->iDataMode = eDataMode_Ethernet;
+ }
+ else
+ {
+ pDev->iDataMode = eDataMode_RAWIP;
+ }
+
+ do
+ {
+ if(iTEEnable==1)//TE_FLOW_CONTROL
+ {
+ result = QMIWDASetDataFormat (pDev, true);
+ }
+ else
+ {
+ result = QMIWDASetDataFormat (pDev, false);
+ }
+ if(isModuleUnload(pDev))
+ {
+ return -EFAULT;;
+ }
+ if(i++>MAX_RETRY)
+ {
+ if(pDev->iDataMode==eDataMode_Ethernet)
+ {
+ pDev->iDataMode=eDataMode_RAWIP;
+ i = 0;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }while(result!=0);
+ if(result != 0)
+ {
+ if(iTEEnable==1)//TE_FLOW_CONTROL
+ {
+ result = QMIWDASetDataFormat (pDev, false);
+ if(result != 0)
+ {
+ printk(KERN_INFO "Set Data Format Fail\n");
+ }
+ else
+ {
+ printk(KERN_INFO "TE Flow Control disabled\n");
+ }
+ }
+ }
+ else
+ {
+ if(iTEEnable==1)//TE_FLOW_CONTROL
+ {
+ printk(KERN_INFO "TE Flow Control Enabled\n");
+ }
+ else
+ {
+ printk(KERN_INFO "TE Flow Control disabled\n");
+ }
+ }
+ }
+ else
+ {
+ pDev->iDataMode = eDataMode_Ethernet;
+ result = QMICTLSetDataFormat (pDev);
+ if(result!=0)
+ {
+ pDev->iDataMode = eDataMode_RAWIP;
+ result = QMICTLSetDataFormat (pDev);
+ }
+ }
+
+ if (result != 0)
+ {
+ return result;
+ }
+
+ i=0;
+ do
+ {
+ // Setup WDS callback
+ result = SetupQMIWDSCallback( pDev );
+ if(isModuleUnload(pDev))
+ {
+ return -EFAULT;
+ }
+ if (result != 0)
+ {
+ if(i++>MAX_RETRY)
+ {
+ return result;
+ }
+ }
+ }while(result!=0);
+
+ if (is9x15)
+ {
+ // Set FCC Authentication
+ i=0;
+ do
+ {
+ result = QMIDMSSWISetFCCAuth( pDev );
+ if(pDev->mbUnload != eStatRegister)
+ {
+ return -EFAULT;;
+ }
+ if (result != 0)
+ {
+ if(i++>MAX_RETRY)
+ {
+ return result;
+ }
+ }
+ }while(result!=0);
+ }
+ // Fill MEID for device
+ i=0;
+ do
+ {
+ result = QMIDMSGetMEID( pDev );
+ if(pDev->mbUnload != eStatRegister)
+ {
+ return -EFAULT;;
+ }
+ if (result != 0)
+ {
+ if(i++>MAX_RETRY)
+ {
+ return result;
+ }
+ }
+ }while(result!=0);
+ // allocate and fill devno with numbers
+ result = alloc_chrdev_region( &devno, 0, 1, "qcqmi" );
+ if (result < 0)
+ {
+ return result;
+ }
+ for(i=0;i<MAX_QCQMI;i++)
+ {
+ if (qcqmi_table[i] == 0)
+ break;
+ }
+
+ if (i == MAX_QCQMI)
+ {
+ printk(KERN_WARNING "no free entry available at qcqmi_table array\n");
+ return -ENOMEM;
+ }
+ qcqmi_table[i] = 1;
+ pDev->mQMIDev.qcqmi = i;
+
+ // Always print this output
+ printk( KERN_INFO "creating qcqmi%d\n",
+ pDev->mQMIDev.qcqmi );
+
+ // Create cdev
+ cdev_init( &pDev->mQMIDev.mCdev, &UserspaceQMIFops );
+ pDev->mQMIDev.mCdev.owner = THIS_MODULE;
+ pDev->mQMIDev.mCdev.ops = &UserspaceQMIFops;
+ pDev->mQMIDev.mbCdevIsInitialized = true;
+
+ result = cdev_add( &pDev->mQMIDev.mCdev, devno, 1 );
+ if (result != 0)
+ {
+ DBG( "error adding cdev\n" );
+ return result;
+ }
+
+#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
+ pDev->dev = device_create( pDev->mQMIDev.mpDevClass,
+ &pDev->mpIntf->dev,
+ devno,
+ NULL,
+ "qcqmi%d",
+ pDev->mQMIDev.qcqmi );
+#else
+ pDev->dev = device_create( pDev->mQMIDev.mpDevClass,
+ &pDev->mpIntf->dev,
+ devno,
+ "qcqmi%d",
+ pDev->mQMIDev.qcqmi );
+#endif
+
+ pDev->mQMIDev.mDevNum = devno;
+
+ memset(pDev->maps.table, 0xff, sizeof(pDev->maps.table));
+ pDev->maps.count = 0;
+
+ sprintf(qcqmi_dev_name, "qcqmi%d", (int)pDev->mQMIDev.qcqmi);
+ pDev->mQMIDev.proc_file = proc_create_data(qcqmi_dev_name, 0, NULL, &proc_fops, pDev);
+
+ if (!pDev->mQMIDev.proc_file) {
+ return -ENOMEM;
+ }
+
+ // Success
+ return 0;
+}
+
+/*===========================================================================
+METHOD:
+ CloseFileInode (Public Method)
+
+DESCRIPTION:
+ Close File Inode
+
+PARAMETERS:
+ pDev [ I ] - Device specific memory
+
+RETURN VALUE:
+ None
+===========================================================================*/
+void CloseFileInode(sGobiUSBNet * pDev)
+{
+ struct inode * pOpenInode=NULL;
+ struct task_struct * pEachTask = NULL;
+ struct fdtable * pFDT;
+ struct file * pFilp;
+ int count = 0;
+ DBG("\n");
+ if(pDev==NULL)
+ {
+ return ;
+ }
+ GobiSyncRcu();
+ if(!list_empty_careful(&pDev->mQMIDev.mCdev.list))
+ if(!list_empty(&pDev->mQMIDev.mCdev.list))
+ {
+ list_for_each_entry(pOpenInode,&pDev->mQMIDev.mCdev.list,i_devices)
+ {
+ // Get the inode
+ if (pOpenInode != NULL && (IS_ERR( pOpenInode ) == false))
+ {
+ // Look for this inode in each task
+ for_each_process( pEachTask )
+ {
+ int max_fds = 0;
+ 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.
+ pFDT = files_fdtable( pEachTask->files );
+ max_fds = pFDT->max_fds;
+ if(pFDT)
+ {
+ for (count = 0; count < max_fds; count++)
+ {
+ if(pFDT==NULL)
+ {
+ break;
+ }
+ pFilp = pFDT->fd[count];
+
+ #if (LINUX_VERSION_CODE >= KERNEL_VERSION( 3,19,0 ))
+ if (pFilp != NULL && pFilp->f_path.dentry != NULL)
+ #else
+ if (pFilp != NULL && pFilp->f_dentry != NULL)
+ #endif
+ {
+ #if (LINUX_VERSION_CODE >= KERNEL_VERSION( 3,19,0 ))
+ if (pFilp->f_path.dentry->d_inode == pOpenInode)
+ #else
+ if (pFilp->f_dentry->d_inode == pOpenInode)
+ #endif
+ {
+ //int ret = 0;
+ //int retry = 0;
+ int iFilpOpen =0;
+ int reffrom = 0;
+ int reffrom2 = 0;
+ sQMIFilpStorage * pFilpData = NULL;
+ printk( KERN_INFO "forcing close of opened file handle\n" );
+ GobiSyncRcu();
+ reffrom = atomic_read( &pDev->mQMIDev.mCdev.kobj.kref.refcount );
+ pFilpData = (sQMIFilpStorage *)pFilp->private_data;
+ if(pFilpData!=NULL)
+ {
+ if((pFilpData->iSemID > 0)&&(pFilpData->iIsClosing==0))
+ {
+ int iRetry = 0;
+ iFilpOpen = 1;
+ do
+ {
+ GobiSyncRcu();
+ if(!down_trylock(&(pFilpData->mReadSem)))
+ {
+ DBG("NOT locked");
+ }
+ up(&(pFilpData->mReadSem));
+ weakup_inode_process(pFilp,pEachTask);
+ gobi_flush_work();
+ if(pFilp==NULL)
+ {
+ break;
+ }
+ if(pFilp->private_data==NULL)
+ {
+ break;
+ }
+ if(reffrom>atomic_read( &pDev->mQMIDev.mCdev.kobj.kref.refcount))
+ {
+ break;
+ }else if (atomic_read( &pDev->mQMIDev.mCdev.kobj.kref.refcount)<2)
+ {
+ break;
+ }
+ if(iRetry++>5)
+ {
+ int ret = 0;
+ printk( KERN_INFO "Retry Max\n");
+ if (file_count(pFilp)>0)
+ {
+ ret = ForceFilpClose( pFilp );
+ if(isPreempt()==0)
+ rcu_assign_pointer( pFDT->fd[count], NULL );
+ gobi_flush_work();
+ }
+ else
+ {
+ if(isPreempt()==0)
+ rcu_assign_pointer( pFDT->fd[count], NULL );
+ GobiSyncRcu();
+ }
+ break;
+ }
+ }while(pFilpData->iSemID > 0);
+ }
+ else
+ {
+ GobiSyncRcu();
+ weakup_inode_process(pFilp,pEachTask);
+ gobi_flush_work();
+ }
+ }
+ GobiSyncRcu();
+ reffrom2 = atomic_read( &pDev->mQMIDev.mCdev.kobj.kref.refcount );
+ if((reffrom2>=reffrom)&&(iFilpOpen==0))
+ if((pFilp!=NULL)&&(reffrom2>1))
+ {
+ int ret = 0;
+ if (file_count(pFilp)>0)
+ {
+ if(pFilpData)
+ {
+ if(pFilpData->iIsClosing==0)
+ {
+ ret = ForceFilpClose( pFilp);
+ if(isPreempt()==0)
+ rcu_assign_pointer( pFDT->fd[count], NULL );
+ }
+ }
+ else
+ {
+ ret = ForceFilpClose(pFilp);
+ if(isPreempt()==0)
+ rcu_assign_pointer( pFDT->fd[count], NULL );
+ }
+ gobi_flush_work();
+ GobiSyncRcu();
+ }
+ else
+ {
+ if(isPreempt()==0)
+ rcu_assign_pointer( pFDT->fd[count], NULL );
+ GobiSyncRcu();
+ }
+ }
+ }
+
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ gobi_flush_work();
+ return ;
+}
+
+/*===========================================================================
+METHOD:
+ gobi_flush_work (Public Method)
+
+DESCRIPTION:
+ sync memory
+
+PARAMETERS:
+ None
+
+RETURN VALUE:
+ None
+===========================================================================*/
+void gobi_flush_work(void)
+{
+ GobiSyncRcu();
+ if(isPreempt()==0)
+ wait_ms(500);
+ GobiSyncRcu();
+ return ;
+}
+
+/*===========================================================================
+METHOD:
+ DeregisterQMIDevice (Public Method)
+
+DESCRIPTION:
+ QMI Device cleanup function
+
+ NOTE: When this function is run the device is no longer valid
+
+PARAMETERS:
+ pDev [ I ] - Device specific memory
+
+RETURN VALUE:
+ None
+===========================================================================*/
+void DeregisterQMIDevice( sGobiUSBNet * pDev )
+{
+ int tries = 0;
+ int result = -1;
+ int i = 0;
+ if(isPreempt()!=0)
+ {
+ printk("preempt_disable");
+ preempt_disable();
+ }
+ pDev->mbUnload = eStatUnloading;
+
+ // Should never happen, but check anyway
+ if (IsDeviceValid( pDev ) == false)
+ {
+ DBG( "wrong device\n" );
+ pDev->iNetLinkStatus = eNetDeviceLink_Disconnected;
+ RemoveProcessFile(pDev);
+ RemoveCdev(pDev);
+ KillRead( pDev );
+ // Send SetControlLineState request (USB_CDC)
+ result = usb_control_msg( pDev->mpNetDev->udev,
+ usb_sndctrlpipe( pDev->mpNetDev->udev, 0 ),
+ SET_CONTROL_LINE_STATE_REQUEST,
+ SET_CONTROL_LINE_STATE_REQUEST_TYPE,
+ 0, // DTR not present
+ /* USB interface number to receive control message */
+ pDev->mpIntf->cur_altsetting->desc.bInterfaceNumber,
+ NULL,
+ 0,
+ 100 );
+ pDev->mbUnload = eStatUnloaded;
+ StopSemID(pDev);
+ gobi_flush_work();
+ return;
+ }
+ pDev->iNetLinkStatus = eNetDeviceLink_Disconnected;
+ RemoveProcessFile(pDev);
+
+ tries = 0;
+ pDev->mQMIDev.mCdev.ops = NULL;
+ while(LocalClientMemLockSpinIsLock(pDev)!=0)
+ {
+ gobi_flush_work();
+ if((tries%10)==0)
+ {
+ printk("Spinlocked\n");
+ }
+ if(200> tries++)
+ {
+ break;
+ }
+ }
+
+ // Stop all reads
+ KillRead( pDev );
+
+ if(pDev->WDSClientID!=(u16)-1)
+ ReleaseClientID( pDev, pDev->WDSClientID );
+
+ StopSemID(pDev);
+ gobi_flush_work();
+ // Release all clients
+ while (pDev->mQMIDev.mpClientMemList != NULL)
+ {
+ DBG( "release 0x%04X\n", pDev->mQMIDev.mpClientMemList->mClientID );
+ if(pDev->mQMIDev.mpClientMemList->mClientID==QMICTL)
+ {
+ unsigned long flags = LocalClientMemLockSpinLockIRQSave( pDev , __LINE__);
+ // Timeout, remove the async read
+ RemoveAndPopNotifyList(pDev,QMICTL,0,eClearCID);
+ // End critical section
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+ break;
+ }
+ else
+ {
+
+ if (ReleaseClientID(pDev,
+ pDev->mQMIDev.mpClientMemList->mClientID) == false)
+ break;
+ // NOTE: pDev->mQMIDev.mpClientMemList will
+ // be updated in ReleaseClientID()
+ }
+ gobi_flush_work();
+ }
+ tries = 0;
+ do
+ {
+ int ref = 0;
+ ref = atomic_read( &pDev->mQMIDev.mCdev.kobj.kref.refcount );
+ DBG("%s:%d tries:%d ref:%d\n",__FUNCTION__,__LINE__,tries,ref);
+ if (ref > 1)
+ {
+ CloseFileInode(pDev);
+ gobi_flush_work();
+ }
+ else
+ {
+ break;
+ }
+ }while(20> tries++);
+
+ gobi_flush_work();
+
+
+ pDev->mbQMIValid = false;
+
+ if (pDev->mQMIDev.mbCdevIsInitialized == false)
+ {
+ pDev->mbUnload = eStatUnloaded;
+ return;
+ }
+
+ // Find each open file handle, and manually close it
+
+ // Generally there will only be only one inode, but more are possible
+
+ if(pDev->iShutdown_write_sem>0)
+ {
+ up_write(&pDev->shutdown_rwsem);
+ }
+
+ if(pDev->iShutdown_read_sem>0)
+ {
+ up_read(&pDev->shutdown_rwsem);
+ }
+ i =0;
+ while(!down_trylock( &(pDev->ReadsyncSem) ))
+ {
+ i++;
+ if(i>MAX_RETRY_LOCK_NUMBER)
+ {
+ break;
+ }
+ set_current_state(TASK_INTERRUPTIBLE);
+ wait_ms(MAX_RETRY_LOCK_MSLEEP_TIME);
+ if(signal_pending(current))
+ {
+ break;
+ }
+ if(pDev==NULL)
+ {
+ return ;
+ }
+ }
+ up(&(pDev->ReadsyncSem));
+ set_current_state(TASK_RUNNING);
+ // Send SetControlLineState request (USB_CDC)
+ result = usb_control_msg( pDev->mpNetDev->udev,
+ usb_sndctrlpipe( pDev->mpNetDev->udev, 0 ),
+ SET_CONTROL_LINE_STATE_REQUEST,
+ SET_CONTROL_LINE_STATE_REQUEST_TYPE,
+ 0, // DTR not present
+ /* USB interface number to receive control message */
+ pDev->mpIntf->cur_altsetting->desc.bInterfaceNumber,
+ NULL,
+ 0,
+ 100 );
+ if (result < 0)
+ {
+ DBG( "Bad SetControlLineState status %d\n", result );
+ }
+
+ // 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 );
+ }
+
+ qcqmi_table[pDev->mQMIDev.qcqmi] = 0;
+
+ // Hold onto cdev memory location until everyone is through using it.
+ // Timeout after 30 seconds (10 ms interval). Timeout should never happen,
+ // but exists to prevent an infinate loop just in case.
+
+ for (tries = 0; tries < 60; tries++)
+ {
+ int ref = atomic_read( &pDev->mQMIDev.mCdev.kobj.kref.refcount );
+ if (ref > 1)
+ {
+ printk( KERN_WARNING "cdev in use by %d tasks\n", ref - 1 );
+ CloseFileInode(pDev);
+ wait_ms(500);
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ cdev_del( &pDev->mQMIDev.mCdev );
+
+ unregister_chrdev_region( pDev->mQMIDev.mDevNum, 1 );
+ pDev->mbUnload = eStatUnloaded;
+ return;
+}
+
+/*=========================================================================*/
+// Driver level client management
+/*=========================================================================*/
+
+/*===========================================================================
+METHOD:
+ QMIReady (Public Method)
+
+DESCRIPTION:
+ Send QMI CTL GET VERSION INFO REQ and SET DATA FORMAT REQ
+ Wait for response or timeout
+
+PARAMETERS:
+ pDev [ I ] - Device specific memory
+ timeout [ I ] - Milliseconds to wait for response
+
+RETURN VALUE:
+ int
+===========================================================================*/
+int QMIReady(
+ sGobiUSBNet * pDev,
+ u16 timeout )
+{
+ int result;
+ void * pWriteBuffer;
+ u16 writeBufferSize;
+ void * pReadBuffer;
+ u16 readBufferSize;
+ struct semaphore readSem;
+ u16 curTime;
+ u8 transactionID;
+ unsigned long flags;
+
+ if (IsDeviceValid( pDev ) == false)
+ {
+ DBG( "Invalid device\n" );
+ return false;
+ }
+
+ writeBufferSize = QMICTLReadyReqSize();
+ pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
+ if (pWriteBuffer == NULL)
+ {
+ return false;
+ }
+
+ // An implimentation of down_timeout has not been agreed on,
+ // so it's been added and removed from the kernel several times.
+ // We're just going to ignore it and poll the semaphore.
+
+ sema_init( &readSem, SEMI_INIT_DEFAULT_VALUE );
+ // Send a write every 1000 ms and see if we get a response
+ for (curTime = 0; curTime < timeout; curTime += 1000)
+ {
+ if(kthread_should_stop())
+ {
+ set_current_state(TASK_RUNNING);
+ return -1;
+ }
+ if(signal_pending(current))
+ {
+ set_current_state(TASK_RUNNING);
+ return -1;
+ }
+ // Start read
+ set_current_state(TASK_INTERRUPTIBLE);
+ transactionID =QMIXactionIDGet(pDev);
+
+ result = ReadAsync( pDev, QMICTL, transactionID, UpSem, &readSem );
+ if (result != 0)
+ {
+ kfree( pWriteBuffer );
+ 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 );
+ if(kthread_should_stop())
+ {
+ set_current_state(TASK_RUNNING);
+ return -1;
+ }
+ if(signal_pending(current))
+ {
+ set_current_state(TASK_RUNNING);
+ return -1;
+ }
+
+ if(curTime>=0)
+ {
+ int iScaleCount = 0;
+ for(iScaleCount=0;iScaleCount<100;iScaleCount++)
+ {
+ if( (kthread_should_stop()) ||
+ signal_pending(current))
+ {
+ if(pWriteBuffer)
+ kfree(pWriteBuffer);
+ set_current_state(TASK_RUNNING);
+ return -1;
+ }
+ wait_ms(10);//msleep( 10 );
+ if( (kthread_should_stop()) ||
+ signal_pending(current))
+ {
+ if(pWriteBuffer)
+ kfree(pWriteBuffer);
+ set_current_state(TASK_RUNNING);
+ return -1;
+ }
+ if(isModuleUnload(pDev))
+ {
+ if(pWriteBuffer)
+ kfree(pWriteBuffer);
+ set_current_state(TASK_RUNNING);
+ return -1;
+ }
+ }
+
+ }
+
+ if (down_trylock( &readSem ) == 0)
+ {
+ // Enter critical section
+ flags = LocalClientMemLockSpinLockIRQSave( pDev , __LINE__);
+
+ // Pop the read data
+ if (PopFromReadMemList( pDev,
+ QMICTL,
+ transactionID,
+ &pReadBuffer,
+ &readBufferSize ) == true)
+ {
+ // Success
+
+ // End critical section
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+
+ // We don't care about the result
+ if(pReadBuffer)
+ kfree( pReadBuffer );
+
+ break;
+ }
+ else
+ {
+ // Read mismatch/failure, unlock and continue
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+ }
+ }
+ else
+ {
+ if(pDev->mbUnload < eStatUnloading)
+ {
+ // Enter critical section
+ flags = LocalClientMemLockSpinLockIRQSave( pDev , __LINE__);
+ // Timeout, remove the async read
+ RemoveAndPopNotifyList(pDev,QMICTL,0,eClearCID);
+ // End critical section
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+ }
+
+ }
+ }
+ kfree( pWriteBuffer );
+ flags = LocalClientMemLockSpinLockIRQSave( pDev , __LINE__);
+ RemoveAndPopNotifyList(pDev,QMICTL,0,eClearCID);
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+ // Did we time out?
+ if (curTime >= timeout)
+ {
+ return false;
+ }
+ set_current_state(TASK_RUNNING);
+
+ DBG( "QMI Ready after %u milliseconds\n", curTime );
+ if(SetPowerSaveMode(pDev,0)<0)
+ {
+ DBG("Set Power Save Mode error\n");
+ }
+ // Success
+ return true;
+}
+
+/*===========================================================================
+METHOD:
+ QMIWDSCallback (Public Method)
+
+DESCRIPTION:
+ QMI WDS callback function
+ Update net stats or link state
+
+PARAMETERS:
+ pDev [ I ] - Device specific memory
+ clientID [ I ] - Client ID
+ pData [ I ] - Callback data (unused)
+
+RETURN VALUE:
+ None
+===========================================================================*/
+void QMIWDSCallback(
+ sGobiUSBNet * pDev,
+ u16 clientID,
+ void * pData )
+{
+ bool bRet;
+ int result;
+ void * pReadBuffer=NULL;
+ u16 readBufferSize;
+ 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 bReconfigure;
+ unsigned long flags;
+
+ if (IsDeviceValid( pDev ) == false)
+ {
+ DBG( "Invalid device\n" );
+ return;
+ }
+
+ // Critical section
+ flags = LocalClientMemLockSpinLockIRQSave( pDev , __LINE__);
+
+ bRet = PopFromReadMemList( pDev,
+ clientID,
+ 0,
+ &pReadBuffer,
+ &readBufferSize );
+
+ // End critical section
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+
+ if (bRet == false)
+ {
+ DBG( "WDS callback failed to get data\n" );
+ if(pReadBuffer)
+ kfree( pReadBuffer );
+ pReadBuffer = NULL;
+ return;
+ }
+
+ // Default values
+ pDev->bLinkState = ! GobiTestDownReason( pDev, NO_NDIS_CONNECTION );
+ bReconfigure = false;
+
+ result = QMIWDSEventResp( pReadBuffer,
+ readBufferSize,
+ &TXOk,
+ &RXOk,
+ &TXErr,
+ &RXErr,
+ &TXOfl,
+ &RXOfl,
+ &TXBytesOk,
+ &RXBytesOk,
+ (u8*)&pDev->bLinkState,
+ &bReconfigure );
+ if (result < 0)
+ {
+ DBG( "bad WDS packet\n" );
+ }
+ else
+ {
+ if (bReconfigure == true)
+ {
+ DBG( "Net device link reset\n" );
+ GobiSetDownReason( pDev, NO_NDIS_CONNECTION );
+ GobiClearDownReason( pDev, NO_NDIS_CONNECTION );
+ }
+ else
+ {
+ if (pDev->bLinkState == true)
+ {
+ DBG( "Net device link is connected\n" );
+ GobiClearDownReason( pDev, NO_NDIS_CONNECTION );
+ }
+ else
+ {
+ DBG( "Net device link is disconnected\n" );
+ GobiSetDownReason( pDev, NO_NDIS_CONNECTION );
+ }
+ }
+ }
+ if(pReadBuffer)
+ kfree( pReadBuffer );
+ pReadBuffer = NULL;
+
+ // Setup next read
+ result = ReadAsync( pDev,
+ clientID,
+ 0,
+ QMIWDSCallback,
+ pData );
+ if (result != 0)
+ {
+ DBG( "unable to setup next async read\n" );
+ }
+
+ return;
+}
+
+void QMIQOSCallback(
+ sGobiUSBNet * pDev,
+ u16 clientID,
+ void * pData )
+{
+ bool bRet;
+ int result;
+ void * pReadBuffer;
+ u16 readBufferSize;
+ unsigned long flags;
+ if (IsDeviceValid( pDev ) == false)
+ {
+ QDBG( "Invalid device\n" );
+ return;
+ }
+
+ // Critical section
+ flags = LocalClientMemLockSpinLockIRQSave( pDev , __LINE__);
+
+ bRet = PopFromReadMemList( pDev,
+ clientID,
+ 0,
+ &pReadBuffer,
+ &readBufferSize );
+
+ // End critical section
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+
+ if (bRet == false)
+ {
+ QDBG( "QOS callback failed to get data\n" );
+ return;
+ }
+
+ result = QMIQOSEventResp(pDev, pReadBuffer, readBufferSize);
+
+ if (result < 0)
+ {
+ QDBG( "bad QOS packet\n" );
+ }
+ if(pReadBuffer)
+ kfree( pReadBuffer );
+
+ // Setup next read
+ result = ReadAsync( pDev,
+ clientID,
+ 0,
+ QMIQOSCallback,
+ pData );
+ if (result != 0)
+ {
+ QDBG( "unable to setup next async read\n" );
+ }
+
+ return;
+}
+
+/*===========================================================================
+METHOD:
+ SetupQMIWDSCallback (Public Method)
+
+DESCRIPTION:
+ Request client and fire off reqests and start async read for
+ QMI WDS callback
+
+PARAMETERS:
+ pDev [ I ] - Device specific memory
+
+RETURN VALUE:
+ int - 0 for success
+ Negative errno for failure
+===========================================================================*/
+int SetupQMIWDSCallback( sGobiUSBNet * pDev )
+{
+ int result;
+ void * pWriteBuffer;
+ u16 writeBufferSize;
+ u16 WDSClientID;
+
+ if (IsDeviceValid( pDev ) == false)
+ {
+ DBG( "Invalid device\n" );
+ return -EFAULT;
+ }
+
+ result = GetClientID( pDev, QMIWDS,NULL );
+ if (result < 0)
+ {
+ return result;
+ }
+ pDev->WDSClientID = 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;
+ }
+
+ return 0;
+}
+
+/*===========================================================================
+METHOD:
+ QMIDMSSWISetFCCAuth (Public Method)
+
+DESCRIPTION:
+ Register DMS client
+ send FCC Authentication req and parse response
+ Release DMS client
+
+PARAMETERS:
+ pDev [ I ] - Device specific memory
+
+RETURN VALUE:
+ None
+===========================================================================*/
+int QMIDMSSWISetFCCAuth( sGobiUSBNet * pDev )
+{
+ int result;
+ void * pWriteBuffer;
+ u16 writeBufferSize;
+ void * pReadBuffer;
+ u16 readBufferSize;
+ u16 DMSClientID;
+ struct semaphore readSem;
+ unsigned long flags;
+ DBG("\n");
+
+ if (IsDeviceValid( pDev ) == false)
+ {
+ DBG( "Invalid device\n" );
+ return -EFAULT;
+ }
+ sema_init( &readSem, SEMI_INIT_DEFAULT_VALUE );
+
+ result = GetClientID( pDev, QMIDMS ,NULL);
+ if (result < 0)
+ {
+ return result;
+ }
+ DMSClientID = result;
+
+ // QMI DMS Get Serial numbers Req
+ writeBufferSize = QMIDMSSWISetFCCAuthReqSize();
+ pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
+ if (pWriteBuffer == NULL)
+ {
+ return -ENOMEM;
+ }
+
+ result = QMIDMSSWISetFCCAuthReq( pWriteBuffer,
+ writeBufferSize,
+ 1 );
+ if (result < 0)
+ {
+ kfree( pWriteBuffer );
+ return result;
+ }
+
+ result = ReadAsync( pDev, DMSClientID, 1, UpSem, &readSem );
+ if(result == 0)
+ {
+ result = WriteSync( pDev,
+ pWriteBuffer,
+ writeBufferSize,
+ DMSClientID );
+ kfree( pWriteBuffer );
+ }
+ wait_ms(QMI_CONTROL_MSG_DELAY_MS);
+ if (down_trylock( &readSem ) == 0)
+ {
+ // Enter critical section
+ flags = LocalClientMemLockSpinLockIRQSave( pDev , __LINE__);
+ // Pop the read data
+ if (PopFromReadMemList( pDev,
+ DMSClientID,
+ 1,
+ &pReadBuffer,
+ &readBufferSize ) == true)
+ {
+ // End critical section
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+ result = 0;
+ // We don't care about the result
+ if(pReadBuffer)
+ kfree( pReadBuffer );
+ }
+ else
+ {
+ // Read mismatch/failure, unlock and continue
+ DBG( "Read mismatch/failure, unlock and continue\n" );
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+ }
+ }
+ else
+ {
+ DBG( "Timeout\n" );
+ ReleaseNotifyList(pDev,DMSClientID,1);
+ result = -1;
+ }
+
+ if (result < 0)
+ {
+ // Non fatal error, device did not return FCC Auth response
+ DBG( "Bad FCC Auth resp\n" );
+ }
+ ReleaseClientID( pDev, DMSClientID );
+
+ // Success
+ return 0;
+}
+
+/*===========================================================================
+METHOD:
+ QMIDMSGetMEID (Public Method)
+
+DESCRIPTION:
+ Register DMS client
+ send MEID req and parse response
+ Release DMS client
+
+PARAMETERS:
+ pDev [ I ] - Device specific memory
+
+RETURN VALUE:
+ None
+===========================================================================*/
+int QMIDMSGetMEID( sGobiUSBNet * pDev )
+{
+ int result;
+ void * pWriteBuffer;
+ u16 writeBufferSize;
+ void * pReadBuffer;
+ u16 readBufferSize;
+ u16 DMSClientID;
+ unsigned long flags;
+ struct semaphore readSem;
+
+ if (IsDeviceValid( pDev ) == false)
+ {
+ DBG( "Invalid device\n" );
+ return -EFAULT;
+ }
+
+ sema_init( &readSem, SEMI_INIT_DEFAULT_VALUE );
+ result = GetClientID( pDev, QMIDMS ,NULL);
+ 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 = ReadAsync( pDev, DMSClientID, 1, UpSem, &readSem );
+ if(result == 0)
+ {
+ result = WriteSync( pDev,
+ pWriteBuffer,
+ writeBufferSize,
+ DMSClientID );
+ kfree( pWriteBuffer );
+ }
+ wait_ms(QMI_CONTROL_MSG_DELAY_MS);
+ if (down_trylock( &readSem ) == 0)
+ {
+ // Enter critical section
+ flags = LocalClientMemLockSpinLockIRQSave( pDev , __LINE__);
+ // Pop the read data
+ if (PopFromReadMemList( pDev,
+ DMSClientID,
+ 1,
+ &pReadBuffer,
+ &readBufferSize ) == true)
+ {
+ // End critical section
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+ result = QMIDMSGetMEIDResp( pReadBuffer,
+ readBufferSize,
+ &pDev->mMEID[0],
+ 14 );
+ // We don't care about the result
+ if(pReadBuffer)
+ kfree( pReadBuffer );
+ }
+ else
+ {
+ // Read mismatch/failure, unlock and continue
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+ }
+ }
+ else
+ {
+ ReleaseNotifyList(pDev,DMSClientID,1);
+ result = -1;
+ }
+
+ 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 );
+
+ // always return Success as MEID is only available on CDMA devices only
+ return 0;
+}
+
+/*===========================================================================
+METHOD:
+ QMICTLSetDataFormat (Public Method)
+
+DESCRIPTION:
+ send Data format request and parse response
+
+PARAMETERS:
+ pDev [ I ] - Device specific memory
+
+RETURN VALUE:
+ None
+===========================================================================*/
+int QMICTLSetDataFormat( sGobiUSBNet * pDev )
+{
+ u8 transactionID;
+ struct semaphore readSem;
+ int result;
+ void * pWriteBuffer;
+ u16 writeBufferSize;
+ void * pReadBuffer;
+ u16 readBufferSize;
+ unsigned long flags;
+
+ DBG("\n");
+
+ // Send SET DATA FORMAT REQ
+ writeBufferSize = QMICTLSetDataFormatReqSize();
+
+ pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
+ if (pWriteBuffer == NULL)
+ {
+ return -ENOMEM;
+ }
+
+ // Start read
+ sema_init( &readSem, SEMI_INIT_DEFAULT_VALUE );
+
+ transactionID = QMIXactionIDGet(pDev);
+
+ // Fill buffer
+ result = QMICTLSetDataFormatReq( pWriteBuffer,
+ writeBufferSize,
+ transactionID ,
+ pDev->iDataMode);
+
+ if (result < 0)
+ {
+ kfree( pWriteBuffer );
+ return result;
+ }
+
+ DBG("Sending QMI Set Data Format Request, TransactionID: 0x%x\n", transactionID );
+
+ WriteSync( pDev,
+ pWriteBuffer,
+ writeBufferSize,
+ QMICTL );
+
+ //msleep( 100 );
+ wait_ms(QMI_CONTROL_MSG_DELAY_MS);
+ if (down_trylock( &readSem ) == 0)
+ {
+ // Enter critical section
+ flags = LocalClientMemLockSpinLockIRQSave( pDev , __LINE__);
+
+ // Pop the read data
+ if (PopFromReadMemList( pDev,
+ QMICTL,
+ transactionID,
+ &pReadBuffer,
+ &readBufferSize ) == true)
+ {
+ // Success
+ PrintHex(pReadBuffer, readBufferSize);
+
+ // End critical section
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+
+ // We care about the result: call Response function
+ result = QMICTLSetDataFormatResp( pReadBuffer, readBufferSize,pDev->iDataMode);
+ if(pReadBuffer)
+ kfree( pReadBuffer );
+
+ if (result != 0)
+ {
+ DBG( "Device cannot set requested data format\n" );
+ if(pWriteBuffer)
+ kfree( pWriteBuffer );
+ return result;
+ }
+ }
+ }
+ else
+ {
+ // Timeout, remove the async read
+ ReleaseNotifyList( pDev, QMICTL, transactionID );
+ }
+
+ kfree( pWriteBuffer );
+
+ return 0;
+}
+
+/*===========================================================================
+METHOD:
+ QMIWDASetDataFormat (Public Method)
+
+DESCRIPTION:
+ Register WDA client
+ send Data format request and parse response
+ Release WDA client
+
+PARAMETERS:
+ pDev [ I ] - Device specific memory
+ te_flow_control [ I ] - TE Flow Control Flag
+
+RETURN VALUE:
+ None
+===========================================================================*/
+int QMIWDASetDataFormat( sGobiUSBNet * pDev, bool te_flow_control )
+{
+ int result;
+ void * pWriteBuffer;
+ u16 writeBufferSize;
+ void * pReadBuffer;
+ u16 readBufferSize;
+ u16 WDAClientID;
+
+ struct semaphore readSem;
+
+ DBG("\n");
+
+ if (IsDeviceValid( pDev ) == false)
+ {
+ DBG( "Invalid device\n" );
+ return -EFAULT;
+ }
+ sema_init( &readSem, SEMI_INIT_DEFAULT_VALUE );
+ result = GetClientID( pDev, QMIWDA ,NULL);
+ if (result < 0)
+ {
+ return result;
+ }
+ WDAClientID = result;
+
+ // QMI WDA Set Data Format Request
+ writeBufferSize = QMIWDASetDataFormatReqSize(te_flow_control);
+ pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
+ if (pWriteBuffer == NULL)
+ {
+ return -ENOMEM;
+ }
+
+ result = QMIWDASetDataFormatReq( pWriteBuffer,
+ writeBufferSize,
+ 1,
+ te_flow_control,
+ pDev->iDataMode);
+ if (result < 0)
+ {
+ kfree( pWriteBuffer );
+ return result;
+ }
+
+ result = ReadAsync( pDev, WDAClientID, 1, UpSem, &readSem );
+ if(result == 0)
+ {
+ result = WriteSync( pDev,
+ pWriteBuffer,
+ writeBufferSize,
+ WDAClientID );
+ kfree( pWriteBuffer );
+ }
+ if (result < 0)
+ {
+ return result;
+ }
+ wait_ms(QMI_CONTROL_MSG_DELAY_MS);
+ if (down_trylock( &readSem ) == 0)
+ {
+ // Enter critical section
+ unsigned long flags;
+ flags = LocalClientMemLockSpinLockIRQSave( pDev , __LINE__);
+ // Pop the read data
+ if (PopFromReadMemList( pDev,
+ WDAClientID,
+ 1,
+ &pReadBuffer,
+ &readBufferSize ) == true)
+ {
+ // End critical section
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+ result = QMIWDASetDataFormatResp(pReadBuffer,
+ readBufferSize,
+ pDev->iDataMode);
+ // We don't care about the result
+ if(pReadBuffer)
+ kfree( pReadBuffer );
+ }
+ else
+ {
+ // Read mismatch/failure, unlock and continue
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+ }
+ }
+ else
+ {
+ result = -1;
+ ReleaseNotifyList(pDev,WDAClientID,1);
+ }
+
+ if (result < 0)
+ {
+ DBG( "Data Format Cannot be set\n" );
+ }
+ ReleaseClientID( pDev, WDAClientID );
+
+ // Success
+ return result;
+}
+
+void wait_ms(unsigned int ms) {
+ if(in_atomic())
+ {
+ DBG("preempt_ensabled\n");
+ return ;
+ }
+ if (!in_interrupt()) {
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(1 + ms * HZ / 1000);
+ set_current_state(TASK_RUNNING);
+ }
+ else
+ {
+ mdelay(ms);
+ }
+}
+
+/*===========================================================================
+METHOD:
+ RemoveAndPopNotifyList (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
+===========================================================================*/
+int RemoveAndPopNotifyList(
+ sGobiUSBNet * pDev,
+ u16 clientID,
+ u16 transactionID ,
+ int iClearClientID)
+{
+ sClientMemList * pClientMem;
+ sNotifyList * pDelNotifyList, ** ppNotifyList;
+ sClientMemList ** ppDelClientMem;
+ sClientMemList * pNextClientMem;
+
+ if(pDev==NULL)
+ {
+ DBG("NULL");
+ return eNotifyListEmpty;
+ }
+#ifdef CONFIG_SMP
+ // Verify Lock
+ if (LocalClientMemLockSpinIsLock( pDev ) == 0)
+ {
+ DBG( "unlocked\n" );
+ BUG();
+ }
+#endif
+ do
+ {
+ // 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 eNotifyListEmpty;
+ }
+
+ ppNotifyList = &(pClientMem->mpReadNotifyList);
+ pDelNotifyList = NULL;
+ // Remove from list
+ CLIENT_READMEM_SNAPSHOT(clientID,pDev);
+ while (*ppNotifyList != NULL)
+ {
+ // Do we care about transaction ID?
+ if (transactionID == 0
+ || (*ppNotifyList)->mTransactionID == 0
+ || transactionID == (*ppNotifyList)->mTransactionID)
+ {
+ pDelNotifyList = *ppNotifyList;
+ DBG( "Remove Notify TID = %x\n", (*ppNotifyList)->mTransactionID );
+ break;
+ }
+
+ DBG( "skipping data TID = %x\n", (*ppNotifyList)->mTransactionID );
+
+ // next
+ ppNotifyList = &(*ppNotifyList)->mpNext;
+ }
+ if (pDelNotifyList != NULL)
+ {
+ // Remove element
+ *ppNotifyList = (*ppNotifyList)->mpNext;
+
+ // Delete memory
+ kfree( pDelNotifyList );
+ }
+ else
+ {
+ void *pFreeData = NULL;
+ u16 FreeDataSize;
+ //Remove From memory List
+ while(PopFromReadMemList( pDev,
+ clientID,
+ transactionID,
+ &pFreeData,
+ &FreeDataSize ) == true )
+ {
+ DBG( "Remove Mem ClientID: 0x%x, data TID = 0x%x\n", clientID,transactionID);
+ kfree( pFreeData );
+ pFreeData = NULL;
+ }
+ DBG( "no one to notify for TID 0x%x\n", transactionID );
+ break;//return eNotifyListEmpty;
+ }
+ }while(ppNotifyList!=NULL);
+
+ if((clientID==QMICTL) || (iClearClientID==eClearCID))
+ {
+ return eNotifyListEmpty;
+ }
+
+ ppDelClientMem = &pDev->mQMIDev.mpClientMemList;
+ while (*ppDelClientMem != NULL)
+ {
+ if ((*ppDelClientMem)->mClientID == clientID)
+ {
+ pNextClientMem = (*ppDelClientMem)->mpNext;
+ kfree( *ppDelClientMem );
+ *ppDelClientMem = NULL;
+
+ // 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))
+ if(*ppDelClientMem==NULL)
+ {
+ DBG("ppDelClientMem NULL %d\r\n",__LINE__);
+ break;
+ }
+ ppDelClientMem = &(*ppDelClientMem)->mpNext;
+ }
+ }
+
+ return eNotifyListEmpty;
+}
+
+/*===========================================================================
+METHOD:
+ SetPowerSaveMode (Public Method)
+
+DESCRIPTION:
+ Set mode in power save mode
+
+PARAMETERS:
+ pDev [ I ] - Device specific memory
+ mode [ I ] - power save mode, 0:wakeup ; 1:suspend
+
+RETURN VALUE:
+ int - 0 for success
+ Negative errno for failure
+===========================================================================*/
+int SetPowerSaveMode(sGobiUSBNet *pDev,u8 mode)
+{
+ int result;
+ void * pWriteBuffer;
+ u16 writeBufferSize;
+ void * pReadBuffer;
+ u16 readBufferSize;
+ u8 transactionID;
+ unsigned long flags;
+ struct semaphore readSem;
+
+ if (IsDeviceValid( pDev ) == false)
+ {
+ printk(KERN_ERR "Invalid device!\n" );
+ return -ENXIO;
+ }
+
+ sema_init( &readSem, SEMI_INIT_DEFAULT_VALUE );
+ writeBufferSize = QMICTLSetPowerSaveModeReqSize();
+ pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
+ if (pWriteBuffer == NULL)
+ {
+ return -ENOMEM;
+ }
+
+ transactionID = QMIXactionIDGet(pDev);
+ result = ReadAsync( pDev, QMICTL, transactionID, UpSem, &readSem );
+
+ result = QMICTLSetPowerSaveModeReq(pWriteBuffer,
+ writeBufferSize,
+ transactionID,
+ mode );
+ if (result < 0)
+ {
+ kfree( pWriteBuffer );
+ return result;
+ }
+ result = WriteSyncNoResume( pDev,
+ pWriteBuffer,
+ writeBufferSize,
+ QMICTL );
+ kfree( pWriteBuffer );
+
+ if (result < 0)
+ {
+ DBG( "bad write data %d\n", result );
+ return result;
+ }
+ wait_ms(QMI_CONTROL_MSG_DELAY_MS);
+ if (down_trylock( &readSem ) == 0)
+ {
+ // Enter critical section
+ flags = LocalClientMemLockSpinLockIRQSave( pDev , __LINE__);
+
+ // Pop the read data
+ if (PopFromReadMemList( pDev,
+ QMICTL,
+ transactionID,
+ &pReadBuffer,
+ &readBufferSize ) == true)
+ {
+ // Success
+
+ // End critical section
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+ result = QMICTLSetPowerSaveModeResp(pReadBuffer,
+ readBufferSize);
+
+ // We don't care about the result
+ if(pReadBuffer)
+ kfree( pReadBuffer );
+ return result;
+ }
+ else
+ {
+ // Read mismatch/failure, unlock and continue
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+ }
+ }
+ else
+ {
+ // Timeout, remove the async read
+ ReleaseNotifyList( pDev, QMICTL, transactionID );
+ result = -1;
+ }
+ return result;
+
+}
+
+/*==========================================================================
+METHOD:
+ WriteSyncNoResume (Public Method)
+
+DESCRIPTION:
+ Start synchronous write without resume device
+
+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 WriteSyncNoResume(
+ sGobiUSBNet * pDev,
+ char * pWriteBuffer,
+ int writeBufferSize,
+ u16 clientID )
+{
+ int i;
+ int result;
+ int iLockRetry =0;
+ DBG("\n");
+ if (IsDeviceValid( pDev ) == false)
+ {
+ DBG( "Invalid device!\n" );
+ return -ENXIO;
+ }
+
+ if (pDev->mbUnload >= eStatUnloading)
+ {
+ DBG( "Unloading device!\n" );
+ return -ENXIO;
+ }
+ // Fill writeBuffer with QMUX
+ result = FillQMUX( clientID, pWriteBuffer, writeBufferSize );
+ if (result < 0)
+ {
+ return result;
+ }
+
+ // Wake device
+ #ifdef CONFIG_PM
+ UsbAutopmGetInterface( pDev->mpIntf );
+ #else
+ usb_autopm_get_interface( pDev->mpIntf );
+ #endif
+
+ DBG( "Actual Write:\n" );
+ PrintHex( pWriteBuffer, writeBufferSize );
+
+ // Write Control URB, protect with read semaphore to track in-flight USB control writes in case of disconnect
+ for(i=0;i<USB_WRITE_RETRY;i++)
+ {
+
+ if(isModuleUnload(pDev))
+ {
+ DBG( "unloaded\n" );
+ return -EFAULT;
+ }
+ pDev->iShutdown_read_sem= __LINE__;
+ if(signal_pending(current))
+ {
+ return -ERESTARTSYS;
+ }
+
+ iLockRetry = 0;
+ while(down_read_trylock(&(pDev->shutdown_rwsem))!=1)
+ {
+ wait_ms(5);
+ if(iLockRetry++>100)
+ {
+ DBG("down_read_trylock timeout");
+ return -EFAULT;
+ }
+ if(pDev==NULL)
+ {
+ DBG( "NULL\n" );
+ return -EFAULT;
+ }
+ if (pDev->mbUnload >= eStatUnloading)
+ {
+ DBG( "unloaded\n" );
+ return -EFAULT;
+ }
+ }
+ smp_mb();
+ result = usb_control_msg( pDev->mpNetDev->udev, usb_sndctrlpipe( pDev->mpNetDev->udev, 0 ),
+ SEND_ENCAPSULATED_COMMAND,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ 0, pDev->mpIntf->cur_altsetting->desc.bInterfaceNumber,
+ (void*)pWriteBuffer, writeBufferSize,
+ USB_WRITE_TIMEOUT );
+ if(signal_pending(current))
+ {
+ return -ERESTARTSYS;
+ }
+ if(pDev==NULL)
+ {
+ return -EFAULT;
+ }
+ up_read(&pDev->shutdown_rwsem);
+ pDev->iShutdown_read_sem=- __LINE__;
+
+ if (pDev->mbUnload >= eStatUnloading)
+ {
+ DBG( "unloaded\n" );
+ return -EFAULT;
+ }
+
+ if (signal_pending(current))
+ {
+ return -ERESTARTSYS;
+ }
+
+ if (result < 0)
+ {
+ printk(KERN_WARNING "usb_control_msg failed (%d)", result);
+ }
+ // Control write transfer may occasionally timeout with certain HCIs, attempt a second time before reporting an error
+ if (result == -ETIMEDOUT)
+ {
+ pDev->writeTimeoutCnt++;
+ printk(KERN_WARNING "Write URB timeout, cnt(%d)\n", pDev->writeTimeoutCnt);
+ }
+ else if(result < 0 )
+ {
+ DBG( "%s no device!\n" ,__FUNCTION__);
+ return result;
+ }
+ else
+ {
+ break;
+ }
+ if (IsDeviceValid( pDev ) == false)
+ {
+ DBG( "%s Invalid device!\n" ,__FUNCTION__);
+ return -ENXIO;
+ }
+ if (pDev->mbUnload > eStatUnloading)
+ {
+ DBG( "unloaded\n" );
+ return -EFAULT;
+ }
+ }
+
+ // Write is done, release device
+ usb_autopm_put_interface( pDev->mpIntf );
+ return result;
+}
+
+int ReleaseNotifyList(sGobiUSBNet *pDev,u16 clientID,u8 transactionID)
+{
+ unsigned long flags;
+ flags = LocalClientMemLockSpinLockIRQSave( pDev , __LINE__);
+ // Timeout, remove the async read
+ NotifyAndPopNotifyList( pDev, clientID, transactionID );
+ // End critical section
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+/*===========================================================================
+METHOD:
+ ConfigPowerSaveSettings (Public Method)
+
+DESCRIPTION:
+ Set modem power save mode config
+
+PARAMETERS:
+ pDev [ I ] - Device specific memory
+ service [ I ] - QMI service number
+ indication[ I ] - QMI indication number
+
+RETURN VALUE:
+ int - 0 for success
+ Negative errno for failure
+===========================================================================*/
+int ConfigPowerSaveSettings(sGobiUSBNet *pDev, u8 service, u8 indication)
+{
+ int result;
+ void * pWriteBuffer;
+ u16 writeBufferSize;
+ void * pReadBuffer;
+ u16 readBufferSize;
+ u8 transactionID;
+ unsigned long flags;
+ struct semaphore readSem;
+
+ if (IsDeviceValid(pDev) == false)
+ {
+ DBG( "Invalid device!\n" );
+ return -ENXIO;
+ }
+ sema_init( &readSem, SEMI_INIT_DEFAULT_VALUE );
+
+ writeBufferSize = QMICTLConfigPowerSaveSettingsReqSize();
+ pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
+ if (pWriteBuffer == NULL)
+ {
+ return -ENOMEM;
+ }
+
+ transactionID = QMIXactionIDGet(pDev);
+ result = ReadAsync( pDev, QMICTL, transactionID, UpSem, &readSem );
+ result = QMICTLConfigPowerSaveSettingsReq(pWriteBuffer,
+ writeBufferSize,
+ transactionID,
+ service,
+ indication);
+ if (result < 0)
+ {
+ kfree( pWriteBuffer );
+ return result;
+ }
+
+ result = WriteSyncNoResume( pDev,
+ pWriteBuffer,
+ writeBufferSize,
+ QMICTL );
+ kfree( pWriteBuffer );
+
+ if (result < 0)
+ {
+ DBG( "bad write data %d\n", result );
+ return result;
+ }
+ wait_ms(QMI_CONTROL_MSG_DELAY_MS);
+ if (down_trylock( &readSem ) == 0)
+ {
+ // Enter critical section
+ flags = LocalClientMemLockSpinLockIRQSave( pDev , __LINE__);
+
+ // Pop the read data
+ if (PopFromReadMemList( pDev,
+ QMICTL,
+ transactionID,
+ &pReadBuffer,
+ &readBufferSize ) == true)
+ {
+ // Success
+
+ // End critical section
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+ result = QMICTLConfigPowerSaveSettingsResp(pReadBuffer,
+ readBufferSize);
+
+ // We don't care about the result
+ if(pReadBuffer)
+ kfree( pReadBuffer );
+ return result;
+ }
+ else
+ {
+ // Read mismatch/failure, unlock and continue
+ LocalClientMemUnLockSpinLockIRQRestore ( pDev ,flags,__LINE__);
+ }
+ }
+ else
+ {
+ // Timeout, remove the async read
+ ReleaseNotifyList( pDev, QMICTL, transactionID );
+ result = -1;
+ }
+ return result;
+}
+#endif
+
+void RemoveProcessFile(sGobiUSBNet *pDev)
+{
+ char qcqmi_dev_name[10]={0};
+ if(pDev->mQMIDev.proc_file != NULL)
+ {
+ sprintf(qcqmi_dev_name, "qcqmi%d", (int)pDev->mQMIDev.qcqmi);
+ remove_proc_entry(qcqmi_dev_name, NULL);
+ pDev->mQMIDev.proc_file = NULL;
+ DBG("remove:%s",qcqmi_dev_name);
+ }
+ return;
+}
+
+void RemoveCdev(sGobiUSBNet * pDev)
+{
+ if(pDev->mQMIDev.mbCdevIsInitialized==true)
+ {
+ pDev->mQMIDev.mbCdevIsInitialized=false;
+ 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 );
+ }
+ }
+}
diff --git a/swi-drivers/src/GobiNet/QMIDevice.h b/swi-drivers/src/GobiNet/QMIDevice.h
new file mode 100644
index 0000000..f07e1af
--- /dev/null
+++ b/swi-drivers/src/GobiNet/QMIDevice.h
@@ -0,0 +1,440 @@
+/*===========================================================================
+FILE:
+ QMIDevice.h
+
+DESCRIPTION:
+ Functions related to the QMI interface device
+
+FUNCTIONS:
+ Generic functions
+ IsDeviceValid
+ PrintHex
+ GobiSetDownReason
+ GobiClearDownReason
+ GobiTestDownReason
+
+ Driver level asynchronous read functions
+ ResubmitIntURB
+ ReadCallback
+ IntCallback
+ StartRead
+ KillRead
+
+ Internal read/write functions
+ ReadAsync
+ UpSem
+ ReadSync
+ WriteSyncCallback
+ WriteSync
+
+ Internal memory management functions
+ GetClientID
+ ReleaseClientID
+ FindClientMem
+ AddToReadMemList
+ PopFromReadMemList
+ AddToNotifyList
+ NotifyAndPopNotifyList
+ AddToURBList
+ PopFromURBList
+
+ Internal userspace wrapper functions
+ UserspaceunlockedIOCTL
+
+ Userspace wrappers
+ UserspaceOpen
+ UserspaceIOCTL
+ UserspaceClose
+ UserspaceRead
+ UserspaceWrite
+ UserspacePoll
+
+ Initializer and destructor
+ RegisterQMIDevice
+ DeregisterQMIDevice
+
+ Driver level client management
+ QMIReady
+ QMIWDSCallback
+ SetupQMIWDSCallback
+ QMIDMSGetMEID
+ QMIDMSSWISetFCCAuth
+
+Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of Code Aurora Forum nor
+ the names of its contributors may be used to endorse or promote
+ products derived from this software without specific prior written
+ permission.
+
+Alternatively, provided that this notice is retained in full, this software
+may be relicensed by the recipient under the terms of the GNU General Public
+License version 2 ("GPL") and only version 2, in which case the provisions of
+the GPL apply INSTEAD OF those given above. If the recipient relicenses the
+software under the GPL, then the identification text in the MODULE_LICENSE
+macro must be changed to reflect "GPLv2" instead of "Dual BSD/GPL". Once a
+recipient changes the license terms to the GPL, subsequent recipients shall
+not relicense under alternate licensing terms, including the BSD or dual
+BSD/GPL terms. In addition, the following license statement immediately
+below and between the words START and END shall also then apply when this
+software is relicensed under the GPL:
+
+START
+
+This program is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License version 2 and only version 2 as
+published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+details.
+
+You should have received a copy of the GNU General Public License along with
+this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+END
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+===========================================================================*/
+
+//---------------------------------------------------------------------------
+// Pragmas
+//---------------------------------------------------------------------------
+#pragma once
+
+//---------------------------------------------------------------------------
+// Include Files
+//---------------------------------------------------------------------------
+#include "Structs.h"
+#include "QMI.h"
+
+#define MAX_QCQMI 255
+#define SEMI_INIT_DEFAULT_VALUE 0
+#define QMI_CONTROL_MSG_DELAY_MS 100
+
+extern int qcqmi_table[MAX_QCQMI];
+
+//Register State
+enum {
+ eClearCID=0,
+ eClearAndReleaseCID=1
+};
+
+/*=========================================================================*/
+// Generic functions
+/*=========================================================================*/
+
+// Basic test to see if device memory is valid
+bool IsDeviceValid( sGobiUSBNet * pDev );
+
+#ifdef CONFIG_PM
+bool bIsSuspend(sGobiUSBNet *pGobiDev);
+#endif
+
+void UsbAutopmGetInterface(struct usb_interface * intf);
+
+// Print Hex data, for debug purposes
+void PrintHex(
+ void * pBuffer,
+ u16 bufSize );
+
+// Sets mDownReason and turns carrier off
+void GobiSetDownReason(
+ sGobiUSBNet * pDev,
+ u8 reason );
+
+// Clear mDownReason and may turn carrier on
+void GobiClearDownReason(
+ sGobiUSBNet * pDev,
+ u8 reason );
+
+// Tests mDownReason and returns whether reason is set
+bool GobiTestDownReason(
+ sGobiUSBNet * pDev,
+ u8 reason );
+
+/*=========================================================================*/
+// Driver level asynchronous read functions
+/*=========================================================================*/
+
+// Resubmit interrupt URB, re-using same values
+int ResubmitIntURB( struct urb * pIntURB );
+
+// Read callback
+// Put the data in storage and notify anyone waiting for data
+void ReadCallback( struct urb * pReadURB );
+
+// Inturrupt callback
+// Data is available, start a read URB
+void IntCallback( struct urb * pIntURB );
+
+// Start continuous read "thread"
+int StartRead( sGobiUSBNet * pDev );
+
+// Kill continuous read "thread"
+void KillRead( sGobiUSBNet * pDev );
+
+/*=========================================================================*/
+// Internal read/write functions
+/*=========================================================================*/
+
+// Start asynchronous read
+// Reading client's data store, not device
+int ReadAsync(
+ sGobiUSBNet * pDev,
+ u16 clientID,
+ u16 transactionID,
+ void (*pCallback)(sGobiUSBNet *, u16, void *),
+ void * pData );
+
+// Notification function for synchronous read
+void UpSem(
+ sGobiUSBNet * pDev,
+ u16 clientID,
+ void * pData );
+
+// Start synchronous read
+// Reading client's data store, not device
+int ReadSync(
+ sGobiUSBNet * pDev,
+ void ** ppOutBuffer,
+ u16 clientID,
+ u16 transactionID,
+ int *iID,
+ struct semaphore *pReadSem,
+ int *iIsClosing);
+
+// Write callback
+void WriteSyncCallback( struct urb * pWriteURB );
+
+// Start synchronous write
+int WriteSync(
+ sGobiUSBNet * pDev,
+ char * pInWriteBuffer,
+ int size,
+ u16 clientID );
+
+// Start synchronous write without resume device
+int WriteSyncNoResume(
+ sGobiUSBNet * pDev,
+ char * pInWriteBuffer,
+ int size,
+ u16 clientID );
+
+/*=========================================================================*/
+// Internal memory management functions
+/*=========================================================================*/
+
+// Create client and allocate memory
+int GetClientID(
+ sGobiUSBNet * pDev,
+ u8 serviceType,
+ struct semaphore *pReadSem);
+
+// Release client and free memory
+bool ReleaseClientID(
+ sGobiUSBNet * pDev,
+ u16 clientID);
+
+// Find this client's memory
+sClientMemList * FindClientMem(
+ sGobiUSBNet * pDev,
+ u16 clientID );
+
+// Add Data to this client's ReadMem list
+bool AddToReadMemList(
+ sGobiUSBNet * pDev,
+ u16 clientID,
+ u16 transactionID,
+ void * pData,
+ u16 dataSize );
+
+// Remove data from this client's ReadMem list if it matches
+// the specified transaction ID.
+bool PopFromReadMemList(
+ sGobiUSBNet * pDev,
+ u16 clientID,
+ u16 transactionID,
+ void ** ppData,
+ u16 * pDataSize );
+
+// Add Notify entry to this client's notify List
+bool AddToNotifyList(
+ sGobiUSBNet * pDev,
+ u16 clientID,
+ u16 transactionID,
+ void (* pNotifyFunct)(sGobiUSBNet *, u16, void *),
+ void * pData );
+
+int RemoveAndPopNotifyList(
+ sGobiUSBNet * pDev,
+ u16 clientID,
+ u16 transactionID ,
+ int iClearCID);
+
+// Remove first Notify entry from this client's notify list
+// and Run function
+int NotifyAndPopNotifyList(
+ sGobiUSBNet * pDev,
+ u16 clientID,
+ u16 transactionID );
+
+// Add URB to this client's URB list
+bool AddToURBList(
+ sGobiUSBNet * pDev,
+ u16 clientID,
+ struct urb * pURB );
+
+// Remove URB from this client's URB list
+struct urb * PopFromURBList(
+ sGobiUSBNet * pDev,
+ u16 clientID );
+
+/*=========================================================================*/
+// Internal userspace wrappers
+/*=========================================================================*/
+
+// Userspace unlocked ioctl
+long UserspaceunlockedIOCTL(
+ struct file * pFilp,
+ unsigned int cmd,
+ unsigned long arg );
+
+/*=========================================================================*/
+// 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 );
+
+unsigned int UserspacePoll(
+ struct file * pFilp,
+ struct poll_table_struct * pPollTable );
+
+/*=========================================================================*/
+// Initializer and destructor
+/*=========================================================================*/
+
+// QMI Device initialization function
+int RegisterQMIDevice( sGobiUSBNet * pDev, int is9x15 );
+
+// QMI Device cleanup function
+void DeregisterQMIDevice( sGobiUSBNet * pDev );
+
+/*=========================================================================*/
+// Driver level client management
+/*=========================================================================*/
+
+// Check if QMI is ready for use
+int QMIReady(
+ sGobiUSBNet * pDev,
+ u16 timeout );
+
+// QMI WDS callback function
+void QMIWDSCallback(
+ sGobiUSBNet * pDev,
+ u16 clientID,
+ void * pData );
+
+// Fire off reqests and start async read for QMI WDS callback
+int SetupQMIWDSCallback( sGobiUSBNet * pDev );
+
+int SetupQMIQOSCallback( sGobiUSBNet * pDev );
+
+// Register client, send req and parse MEID response, release client
+int QMIDMSGetMEID( sGobiUSBNet * pDev );
+
+// Register client, send req and parse FCC Authentication response, release client
+int QMIDMSSWISetFCCAuth( sGobiUSBNet * pDev );
+
+// Register client, send req and parse Data format response, release client
+int QMIWDASetDataFormat( sGobiUSBNet * pDev, bool te_flow_control );
+
+// send req and parse Data format response
+int QMICTLSetDataFormat( sGobiUSBNet * pDev );
+
+// Initialize Read Sync tasks semaphore
+void InitSemID(sGobiUSBNet * pDev);
+
+//Release all Read Sync tasks semaphore(s)
+void StopSemID(sGobiUSBNet * pDev);
+
+//Query semaphore slot ID
+int iGetSemID(sGobiUSBNet *pDev,int line);
+
+/***************************************************************************/
+// wait_ms
+/**************************************************************************/
+void wait_ms(unsigned int ms) ;
+
+// Userspace Release (synchronous)
+int UserspaceRelease(struct inode *inode, struct file *file);
+
+// Userspace Lock (synchronous)
+int UserSpaceLock(struct file *filp, int cmd, struct file_lock *fl);
+
+// sync memory
+void gobi_flush_work(void);
+
+// Close Opened File Inode
+void CloseFileInode(sGobiUSBNet * pDev);
+
+// Set modem in specific power save mode
+int SetPowerSaveMode(sGobiUSBNet *pDev,u8 mode);
+
+// config modem qmi wakeup filter
+int ConfigPowerSaveSettings(sGobiUSBNet *pDev, u8 service, u8 indication);
+
+// Get TID
+u8 QMIXactionIDGet( sGobiUSBNet *pDev);
+
+// Release Specific Client ID Nofitication From Memory List
+int ReleaseNotifyList(sGobiUSBNet *pDev,u16 clientID,u8 transactionID);
+
diff --git a/swi-drivers/src/GobiNet/Readme.txt b/swi-drivers/src/GobiNet/Readme.txt
new file mode 100644
index 0000000..28412fa
--- /dev/null
+++ b/swi-drivers/src/GobiNet/Readme.txt
@@ -0,0 +1,78 @@
+Gobi3000 network driver 2011-07-29-1026
+
+This readme covers important information concerning
+the Gobi Net driver.
+
+Table of Contents
+
+1. What's new in this release
+2. Known issues
+3. Known platform issues
+
+
+-------------------------------------------------------------------------------
+
+1. WHAT'S NEW
+
+This Release (Gobi3000 network driver 2011-07-29-1026)
+a. Signal the device to leave low power mode on enumeration
+b. Add "txQueueLength" parameter, which will set the Tx Queue Length
+c. Send SetControlLineState message during driver/device removal
+d. Change to new date-based versioning scheme
+
+Prior Release (Gobi3000 network driver 1.0.60) 06/29/2011
+a. Add UserspacePoll() function, to support select()
+b. Fix possible deadlock on GobiUSBNetTXTimeout()
+c. Fix memory leak on data transmission
+
+Prior Release (Gobi3000 network driver 1.0.50) 05/18/2011
+a. Add support for kernels up to 2.6.38
+b. Add support for dynamic interface binding
+
+Prior Release (Gobi3000 network driver 1.0.40) 02/28/2011
+a. In cases of QMI read errors, discard the error and continue reading.
+b. Add "interruptible" parameter, which may be disabled for debugging purposes.
+
+Prior Release (Gobi3000 network driver 1.0.30) 01/05/2011
+a. Fix rare kernel PANIC if a process terminates while file handle close
+ or device removal is in progress.
+
+Prior Release (Gobi3000 network driver 1.0.20) 11/01/2010
+a. Fix possible kernel WARNING if device removed before QCWWANDisconnect().
+b. Fix multiple memory leaks in error cases.
+
+Prior Release (Gobi3000 network driver 1.0.10) 09/17/2010
+a. Initial release
+
+-------------------------------------------------------------------------------
+
+2. KNOWN ISSUES
+
+No known issues.
+
+-------------------------------------------------------------------------------
+
+3. KNOWN PLATFORM ISSUES
+
+a. Enabling autosuspend:
+ Autosuspend is supported by the Gobi3000 module and its drivers,
+ but by default it is not enabled by the open source kernel. As such,
+ the Gobi3000 module will not enter autosuspend unless the
+ user specifically turns on autosuspend with the command:
+ echo auto > /sys/bus/usb/devices/.../power/level
+b. Ksoftirq using 100% CPU:
+ There is a known issue with the open source usbnet driver that can
+ result in infinite software interrupts. The fix for this is to test
+ (in the usbnet_bh() function) if the usb_device can submit URBs before
+ attempting to submit the response URB buffers.
+c. NetworkManager does not recognize connection after resume:
+ After resuming from sleep/hibernate, NetworkManager may not recognize new
+ network connections by the Gobi device. This is a system issue not specific
+ to the Gobi device, which may result in dhcp not being run and the default
+ route not being updated. One way to fix this is to simply restart the
+ NetworkManager service.
+
+-------------------------------------------------------------------------------
+
+
+
diff --git a/swi-drivers/src/GobiNet/Structs.h b/swi-drivers/src/GobiNet/Structs.h
new file mode 100644
index 0000000..5bab04f
--- /dev/null
+++ b/swi-drivers/src/GobiNet/Structs.h
@@ -0,0 +1,476 @@
+/*===========================================================================
+FILE:
+ Structs.h
+
+DESCRIPTION:
+ Declaration of structures used by the Qualcomm Linux USB Network driver
+
+FUNCTIONS:
+ none
+
+Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of Code Aurora Forum nor
+ the names of its contributors may be used to endorse or promote
+ products derived from this software without specific prior written
+ permission.
+
+Alternatively, provided that this notice is retained in full, this software
+may be relicensed by the recipient under the terms of the GNU General Public
+License version 2 ("GPL") and only version 2, in which case the provisions of
+the GPL apply INSTEAD OF those given above. If the recipient relicenses the
+software under the GPL, then the identification text in the MODULE_LICENSE
+macro must be changed to reflect "GPLv2" instead of "Dual BSD/GPL". Once a
+recipient changes the license terms to the GPL, subsequent recipients shall
+not relicense under alternate licensing terms, including the BSD or dual
+BSD/GPL terms. In addition, the following license statement immediately
+below and between the words START and END shall also then apply when this
+software is relicensed under the GPL:
+
+START
+
+This program is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License version 2 and only version 2 as
+published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+details.
+
+You should have received a copy of the GNU General Public License along with
+this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+END
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+===========================================================================*/
+
+//---------------------------------------------------------------------------
+// Pragmas
+//---------------------------------------------------------------------------
+#pragma once
+
+//---------------------------------------------------------------------------
+// Include Files
+//---------------------------------------------------------------------------
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/mii.h>
+#include <linux/usb.h>
+#include <linux/version.h>
+#include <linux/cdev.h>
+#include <linux/kthread.h>
+#include <linux/poll.h>
+#include <linux/timer.h>
+#include <linux/proc_fs.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
+
+#include <linux/semaphore.h>
+
+#define MAX_MAP (9)
+#define MAX_DSCP_ID 0x3F
+#define UNIQUE_DSCP_ID 0x40
+
+#define MAX_READ_SYNC_TASK_ID 255
+#define MAX_RETRY_LOCK_NUMBER 10
+#define MAX_RETRY_LOCK_MSLEEP_TIME 10
+#define MAX_RETRY_TASK_LOCK_TIME 10
+#define MAX_RETRY_TASK_MSLEEP_TIME 5
+#define MAX_DEVICE_MEID_SIZE 14
+
+// Used in recursion, defined later below
+struct sGobiUSBNet;
+
+/*=========================================================================*/
+// Struct sReadMemList
+//
+// Structure that defines an entry in a Read Memory linked list
+/*=========================================================================*/
+typedef struct sReadMemList
+{
+ /* Data buffer */
+ void * mpData;
+
+ /* Transaction ID */
+ u16 mTransactionID;
+
+ /* Size of data buffer */
+ u16 mDataSize;
+
+ /* Next entry in linked list */
+ struct sReadMemList * mpNext;
+
+} sReadMemList;
+
+/*=========================================================================*/
+// Struct sNotifyList
+//
+// Structure that defines an entry in a Notification linked list
+/*=========================================================================*/
+typedef struct sNotifyList
+{
+ /* Function to be run when data becomes available */
+ void (* mpNotifyFunct)(struct sGobiUSBNet *, u16, void *);
+
+ /* Transaction ID */
+ u16 mTransactionID;
+
+ /* Data to provide as parameter to mpNotifyFunct */
+ void * mpData;
+
+ /* Next entry in linked list */
+ struct sNotifyList * mpNext;
+
+} sNotifyList;
+
+/*=========================================================================*/
+// Struct sURBList
+//
+// Structure that defines an entry in a URB linked list
+/*=========================================================================*/
+typedef struct sURBList
+{
+ /* The current URB */
+ struct urb * mpURB;
+
+ /* Next entry in linked list */
+ struct sURBList * mpNext;
+
+} sURBList;
+
+/*=========================================================================*/
+// Struct sClientMemList
+//
+// Structure that defines an entry in a Client Memory linked list
+// Stores data specific to a Service Type and Client ID
+/*=========================================================================*/
+typedef struct sClientMemList
+{
+ /* Client ID for this Client */
+ u16 mClientID;
+
+ /* Linked list of Read entries */
+ /* Stores data read from device before sending to client */
+ sReadMemList * mpList;
+
+ /* Linked list of Notification entries */
+ /* Stores notification functions to be run as data becomes
+ available or the device is removed */
+ sNotifyList * mpReadNotifyList;
+
+ /* Linked list of URB entries */
+ /* Stores pointers to outstanding URBs which need canceled
+ when the client is deregistered or the device is removed */
+ sURBList * mpURBList;
+
+ /* Next entry in linked list */
+ struct sClientMemList * mpNext;
+
+ /* Wait queue object for poll() */
+ wait_queue_head_t mWaitQueue;
+
+} sClientMemList;
+
+/*=========================================================================*/
+// Struct sURBSetupPacket
+//
+// Structure that defines a USB Setup packet for Control URBs
+// Taken from USB CDC specifications
+/*=========================================================================*/
+typedef struct sURBSetupPacket
+{
+ /* Request type */
+ u8 mRequestType;
+
+ /* Request code */
+ u8 mRequestCode;
+
+ /* Value */
+ u16 mValue;
+
+ /* Index */
+ u16 mIndex;
+
+ /* Length of Control URB */
+ u16 mLength;
+
+} sURBSetupPacket;
+
+// Common value for sURBSetupPacket.mLength
+#define DEFAULT_READ_URB_LENGTH 0x1000
+
+
+/*=========================================================================*/
+// Struct sAutoPM
+//
+// Structure used to manage AutoPM thread which determines whether the
+// device is in use or may enter autosuspend. Also submits net
+// transmissions asynchronously.
+/*=========================================================================*/
+typedef struct sAutoPM
+{
+ /* Thread for atomic autopm function */
+ struct task_struct * mpThread;
+
+ /* Signal for completion when it's time for the thread to work */
+ struct completion mThreadDoWork;
+
+ /* Time to exit? */
+ bool mbExit;
+
+ /* List of URB's queued to be sent to the device */
+ sURBList * mpURBList;
+
+ /* URB list lock (for adding and removing elements) */
+ spinlock_t mURBListLock;
+
+ /* Length of the URB list */
+ atomic_t mURBListLen;
+
+ /* Active URB */
+ struct urb * mpActiveURB;
+
+ /* Active URB lock (for adding and removing elements) */
+ spinlock_t mActiveURBLock;
+
+ /* Duplicate pointer to USB device interface */
+ struct usb_interface * mpIntf;
+
+} sAutoPM;
+
+
+/*=========================================================================*/
+// Struct sQMIDev
+//
+// Structure that defines the data for the QMI device
+/*=========================================================================*/
+typedef struct sQMIDev
+{
+ /* Device number */
+ dev_t mDevNum;
+
+ /* Device class */
+ struct class * mpDevClass;
+
+ /* cdev struct */
+ struct cdev mCdev;
+
+ /* is mCdev initialized? */
+ bool mbCdevIsInitialized;
+
+ /* Pointer to read URB */
+ struct urb * mpReadURB;
+
+ /* Read setup packet */
+ sURBSetupPacket * mpReadSetupPacket;
+
+ /* Read buffer attached to current read URB */
+ void * mpReadBuffer;
+
+ /* Inturrupt URB */
+ /* Used to asynchronously notify when read data is available */
+ struct urb * mpIntURB;
+
+ /* Buffer used by Inturrupt URB */
+ void * mpIntBuffer;
+
+ /* Pointer to memory linked list for all clients */
+ sClientMemList * mpClientMemList;
+
+ /* Spinlock for client Memory entries */
+ spinlock_t mClientMemLock;
+ unsigned long mFlag;
+ /* semaphore for Notify */
+ struct semaphore mNotifyMemLock;
+
+ /* Transaction ID associated with QMICTL "client" */
+ atomic_t mQMICTLTransactionID;
+
+ unsigned char qcqmi;
+
+ int iInterfaceNumber;
+ struct proc_dir_entry * proc_file;
+} sQMIDev;
+
+enum qos_flow_state {
+ FLOW_ACTIVATED = 0x01,
+ FLOW_SUSPENDED = 0x02,
+ FLOW_DELETED = 0x03,
+ FLOW_MODIFIED,
+ FLOW_ENABLED,
+ FLOW_DISABLED,
+ FLOW_INVALID = 0xff
+};
+
+typedef struct {
+ u8 dscp;
+ u32 qosId;
+ u8 state;
+} sMapping;
+
+typedef struct {
+ u8 count;
+ sMapping table[MAX_MAP];
+} sMappingTable;
+
+typedef struct {
+ u32 rx_packets;
+ u32 tx_packets;
+ u64 rx_bytes;
+ u64 tx_bytes;
+ u32 rx_errors;
+ u32 tx_errors;
+ u32 rx_overflows;
+ u32 tx_overflows;
+} sNetStats;
+
+enum{
+ eDataMode_Unknown=-1,
+ eDataMode_Ethernet,
+ eDataMode_RAWIP,
+};
+
+enum{
+ eNetDeviceLink_Unknown=-1,
+ eNetDeviceLink_Disconnected,
+ eNetDeviceLink_Connected,
+};
+
+/*=========================================================================*/
+// Struct sGobiUSBNet
+//
+// Structure that defines the data associated with the Qualcomm USB device
+/*=========================================================================*/
+typedef struct sGobiUSBNet
+{
+ /* Net device structure */
+ struct usbnet * mpNetDev;
+
+ /* Usb device interface */
+ struct usb_interface * mpIntf;
+
+ /* Pointers to usbnet_open and usbnet_stop functions */
+ int (* mpUSBNetOpen)(struct net_device *);
+ int (* mpUSBNetStop)(struct net_device *);
+
+ /* Reason(s) why interface is down */
+ /* Used by Gobi*DownReason */
+ unsigned long mDownReason;
+#define NO_NDIS_CONNECTION 0
+#define CDC_CONNECTION_SPEED 1
+#define DRIVER_SUSPENDED 2
+#define NET_IFACE_STOPPED 3
+
+ /* QMI "device" status */
+ bool mbQMIValid;
+ int mbUnload;
+
+ /* QMI "device" memory */
+ sQMIDev mQMIDev;
+
+ /* Device MEID */
+ char mMEID[MAX_DEVICE_MEID_SIZE];
+
+ /* AutoPM thread */
+ sAutoPM mAutoPM;
+
+ /* Ethernet header templates */
+ /* IPv4 */
+ u8 eth_hdr_tmpl_ipv4[ETH_HLEN];
+ /* IPv6 */
+ u8 eth_hdr_tmpl_ipv6[ETH_HLEN];
+
+ u32 tx_qlen;
+
+ sMappingTable maps;
+
+ /*
+ * Read write semaphore so that ReleaseClientID() waits until WriteSync() exits to handle
+ * below limitation
+ * If a thread in your driver uses this call, make sure your disconnect()
+ * method can wait for it to complete. Since you don't have a handle on the
+ * URB used, you can't cancel the request.
+ */
+ struct rw_semaphore shutdown_rwsem;
+ int iShutdown_read_sem;
+ int iShutdown_write_sem;
+
+ struct timer_list read_tmr;
+ u16 readTimeoutCnt;
+ u16 writeTimeoutCnt;
+
+ bool bLinkState;
+ u16 mtu;
+ #ifdef CONFIG_PM
+ bool bSuspend;
+ spinlock_t sSuspendLock;
+ #endif
+ bool mIs9x15;
+ struct usb_interface *mUsb_Interface;
+ int iTaskID;
+ struct task_struct *task;
+
+ int iReasSyncTaskID[MAX_READ_SYNC_TASK_ID];
+ struct semaphore readSem[MAX_READ_SYNC_TASK_ID];
+ struct semaphore ReadsyncSem;
+
+ struct semaphore taskIDSem;
+ int iIsClosing;
+ struct device *qcqmidev;
+ struct device *dev;
+ u16 WDSClientID;
+ int iNetLinkStatus;
+ int iDataMode;
+} sGobiUSBNet;
+
+/*=========================================================================*/
+// Struct sQMIFilpStorage
+//
+// Structure that defines the storage each file handle contains
+// Relates the file handle to a client
+/*=========================================================================*/
+typedef struct sQMIFilpStorage
+{
+ /* Client ID */
+ u16 mClientID;
+ int mDeviceInvalid;
+ /* Device pointer */
+ sGobiUSBNet * mpDev;
+ int iSemID ;
+ struct semaphore mReadSem;
+ int iReleaseSemID ;
+ struct semaphore mReleasedSem;
+ int iIsClosing;
+ int iReadSyncResult;
+} sQMIFilpStorage;
+
diff --git a/swi-drivers/src/GobiNet/gobi_usbnet.h b/swi-drivers/src/GobiNet/gobi_usbnet.h
new file mode 100644
index 0000000..b8f6253
--- /dev/null
+++ b/swi-drivers/src/GobiNet/gobi_usbnet.h
@@ -0,0 +1,51 @@
+/*===========================================================================
+FILE:
+ gobi_usbnet.h
+
+DESCRIPTION:
+ header for specific usbnet_tx_timeout and usbnet_start_xmit
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+===========================================================================*/
+
+#if (LINUX_VERSION_CODE == KERNEL_VERSION( 2,6,31 ) ||\
+ LINUX_VERSION_CODE == KERNEL_VERSION( 2,6,32 ))
+void gobi_usbnet_tx_timeout_2_6_32 (struct net_device *net);
+int gobi_usbnet_start_xmit_2_6_32 (struct sk_buff *skb, struct net_device *net);
+#elif (LINUX_VERSION_CODE == KERNEL_VERSION( 2,6,35 ))
+void gobi_usbnet_tx_timeout_2_6_35 (struct net_device *net);
+netdev_tx_t gobi_usbnet_start_xmit_2_6_35 (struct sk_buff *skb,
+ struct net_device *net);
+#elif (LINUX_VERSION_CODE == KERNEL_VERSION( 3,0,6 ))
+void gobi_usbnet_tx_timeout_3_0_6 (struct net_device *net);
+netdev_tx_t gobi_usbnet_start_xmit_3_0_6 (struct sk_buff *skb,
+ struct net_device *net);
+#elif (LINUX_VERSION_CODE >= KERNEL_VERSION( 3,10,1 ) &&\
+ LINUX_VERSION_CODE <= KERNEL_VERSION( 3,10,39 ))
+void gobi_usbnet_tx_timeout_3_10_21 (struct net_device *net);
+netdev_tx_t gobi_usbnet_start_xmit_3_10_21 (struct sk_buff *skb,
+ struct net_device *net);
+#elif (LINUX_VERSION_CODE >= KERNEL_VERSION( 3,12,0 ) &&\
+ LINUX_VERSION_CODE < KERNEL_VERSION( 3,13,0 ))
+void gobi_usbnet_tx_timeout_3_12_xx(struct net_device *net);
+netdev_tx_t gobi_usbnet_start_xmit_3_12_xx (struct sk_buff *skb,
+ struct net_device *net);
+#elif (LINUX_VERSION_CODE >= KERNEL_VERSION( 4,4,0 ) &&\
+ LINUX_VERSION_CODE < KERNEL_VERSION( 4,5,0 ))
+void gobi_usbnet_tx_timeout_4_4_xx(struct net_device *net);
+netdev_tx_t gobi_usbnet_start_xmit_4_4_xx(struct sk_buff *skb,
+ struct net_device *net);
+
+#else
+#endif
+
diff --git a/swi-drivers/src/GobiNet/usbnet_2_6_32.c b/swi-drivers/src/GobiNet/usbnet_2_6_32.c
new file mode 100644
index 0000000..b4fd193
--- /dev/null
+++ b/swi-drivers/src/GobiNet/usbnet_2_6_32.c
@@ -0,0 +1,396 @@
+/*===========================================================================
+FILE:
+ usbnet_2_6_32.c
+
+DESCRIPTION:
+ The default "usbnet_start_xmit" function is over-ridden by Sierra to provide the URB_Monitor
+ for Linux kernel 2.6.32
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+===========================================================================*/
+
+//---------------------------------------------------------------------------
+// Include Files
+//---------------------------------------------------------------------------
+
+#include "Structs.h"
+#include "QMIDevice.h"
+#include "QMI.h"
+#include "gobi_usbnet.h"
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/module.h>
+#include <net/ip.h>
+
+#include <asm/siginfo.h> //siginfo
+#include <linux/rcupdate.h> //rcu_read_lock
+#include <linux/sched.h> //find_task_by_pid_type
+
+#if (LINUX_VERSION_CODE == KERNEL_VERSION( 2,6,31 ) ||\
+ LINUX_VERSION_CODE == KERNEL_VERSION( 2,6,32 ))
+void (*URB_monitor) (bool,unsigned char);
+EXPORT_SYMBOL(URB_monitor);
+#if 0
+/*
+ * Dummy function to test URB back-pressure. In actual implementation,
+ * customer will implement this function
+ */
+// Define PDN interfaced as per specific Sierra module PTS. The below are for MC73xx modules
+#define PDN1_INTERFACE 8
+#define PDN2_INTERFACE 10
+#define BACK_PRESSURE_WATERMARK 10
+static unsigned short urb_count_pdn1 = 0;
+static unsigned short urb_count_pdn2 = 0;
+void URB_monitor (bool isUpCount,unsigned char interface)
+{
+/* printk(KERN_WARNING "[%s] isUpCount %d, intf %d", \
+ __func__, isUpCount, interface);*/
+ if (isUpCount)
+ {
+ if (interface == PDN1_INTERFACE)
+ {
+ urb_count_pdn1++;
+ }
+ else if (interface == PDN2_INTERFACE)
+ {
+ urb_count_pdn2++;
+ }
+ else
+ {
+ // unknown interface. ignore or log as needed
+ }
+ }
+ else
+ {
+ if (interface == PDN1_INTERFACE)
+ {
+ urb_count_pdn1--;
+ }
+ else if (interface == PDN2_INTERFACE)
+ {
+ urb_count_pdn2--;
+ }
+ else
+ {
+ // unknown interface. ignore or log as needed
+ }
+ }
+ if (BACK_PRESSURE_WATERMARK <= urb_count_pdn1)
+ {
+ // Back pressure on PDN1
+ printk(KERN_WARNING "[%s] Backpressure %d on PDN1", \
+ __func__, urb_count_pdn1);
+ }
+ if (BACK_PRESSURE_WATERMARK <= urb_count_pdn2)
+ {
+ // Back pressure on PDN2
+ printk(KERN_WARNING "[%s] Backpressure %d on PDN2", \
+ __func__, urb_count_pdn2);
+ }
+}
+#endif // 0 End of dummy function
+
+// Get the USB interface from a usbnet pointer. The function return 0 on success, -1 on error
+__always_inline static int get_usb_interface_from_device (struct usbnet *dev, unsigned char *pb_usb_interface)
+{
+ int iRet = -1; // set to error by default
+
+ if ((NULL!=dev) && (NULL!=pb_usb_interface))
+ {
+ if ((NULL != dev->intf) &&
+ (NULL != dev->intf->cur_altsetting))
+ {
+ *pb_usb_interface = dev->intf->cur_altsetting->desc.bInterfaceNumber;
+ iRet = 0;
+ } // (NULL != dev->intf) && (NULL != dev->intf->cur_altsetting)
+ } //(NULL != pURB) && (NULL!=pb_usb_interface)
+ return iRet;
+}
+
+// Get the USB interface from a URB. The function return 0 on success, -1 on error
+__always_inline static int get_usb_interface (struct urb * pURB, unsigned char *pb_usb_interface)
+{
+ int iRet = -1; // set to error by default
+ struct sk_buff *skb = NULL;
+ struct skb_data *entry = NULL;
+
+ if ((NULL!=pURB) && (NULL!=pb_usb_interface))
+ {
+ skb = (struct sk_buff *) pURB->context;
+ if (NULL != skb)
+ {
+ entry = (struct skb_data *) skb->cb;
+ if (NULL != entry)
+ {
+ iRet = get_usb_interface_from_device (entry->dev, pb_usb_interface);
+ }
+ } // (NULL != skb)
+ } // (NULL != pURB) && (NULL!=pb_usb_interface)
+ return iRet;
+}
+
+// unlink pending rx/tx; completion handlers do all other cleanup
+static int unlink_urbs (struct usbnet *dev, struct sk_buff_head *q)
+{
+ unsigned long flags;
+ struct sk_buff *skb, *skbnext;
+ int count = 0;
+
+ spin_lock_irqsave (&q->lock, flags);
+ skb_queue_walk_safe(q, skb, skbnext) {
+ struct skb_data *entry;
+ struct urb *urb;
+ int retval;
+
+ entry = (struct skb_data *) skb->cb;
+ urb = entry->urb;
+
+ /*
+ * Get reference count of the URB to avoid it to be
+ * freed during usb_unlink_urb, which may trigger
+ * use-after-free problem inside usb_unlink_urb since
+ * usb_unlink_urb is always racing with .complete
+ * handler(include defer_bh).
+ */
+ usb_get_urb(urb);
+ spin_unlock_irqrestore(&q->lock, flags);
+ // during some PM-driven resume scenarios,
+ // these (async) unlinks complete immediately
+ retval = usb_unlink_urb (urb);
+ if (retval != -EINPROGRESS && retval != 0)
+ devdbg (dev, "unlink urb err, %d", retval);
+ else
+ count++;
+ usb_put_urb(urb);
+ spin_lock_irqsave(&q->lock, flags);
+ }
+ spin_unlock_irqrestore (&q->lock, flags);
+ return count;
+}
+
+void gobi_usbnet_tx_timeout_2_6_32 (struct net_device *net)
+{
+ struct usbnet *dev = netdev_priv(net);
+#ifdef TX_URB_MONITOR
+ int count = 0;
+ int iRet = -1;
+ unsigned char b_usb_if_num = 0;
+ // Get the USB interface
+ iRet = get_usb_interface_from_device (dev, &b_usb_if_num);
+
+ count = unlink_urbs (dev, &dev->txq);
+ tasklet_schedule (&dev->bh);
+
+ if ((URB_monitor) && (0==iRet))
+ {
+ while (count)
+ {
+ URB_monitor(false, b_usb_if_num);
+ count--;
+ }
+ }
+#else // TX_URB_MONITOR
+ unlink_urbs (dev, &dev->txq);
+ tasklet_schedule (&dev->bh);
+#endif // TX_URB_MONITOR
+ // FIXME: device recovery -- reset?
+}
+
+/* some LK 2.4 HCDs oopsed if we freed or resubmitted urbs from
+ * completion callbacks. 2.5 should have fixed those bugs...
+ */
+
+static void defer_bh(struct usbnet *dev, struct sk_buff *skb, struct sk_buff_head *list)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&list->lock, flags);
+ __skb_unlink(skb, list);
+ spin_unlock(&list->lock);
+ spin_lock(&dev->done.lock);
+ __skb_queue_tail(&dev->done, skb);
+ if (dev->done.qlen == 1)
+ tasklet_schedule(&dev->bh);
+ spin_unlock_irqrestore(&dev->done.lock, flags);
+}
+
+static void tx_complete (struct urb *urb)
+{
+ struct sk_buff *skb = (struct sk_buff *) urb->context;
+ struct skb_data *entry = (struct skb_data *) skb->cb;
+ struct usbnet *dev = entry->dev;
+
+#ifdef TX_URB_MONITOR
+ unsigned char b_usb_if_num = 0;
+ int iRet = get_usb_interface(urb, &b_usb_if_num);
+#endif //#ifdef TX_URB_MONITOR
+
+ if (urb->status == 0)
+ {
+ dev->net->stats.tx_packets++;
+ dev->net->stats.tx_bytes += entry->length;
+ }
+ else
+ {
+ dev->net->stats.tx_errors++;
+ switch (urb->status)
+ {
+ case -EPIPE:
+ usbnet_defer_kevent (dev, EVENT_TX_HALT);
+ break;
+
+ /* software-driven interface shutdown */
+ case -ECONNRESET: // async unlink
+ case -ESHUTDOWN: // hardware gone
+ break;
+
+ // like rx, tx gets controller i/o faults during khubd delays
+ // and so it uses the same throttling mechanism.
+ case -EPROTO:
+ case -ETIME:
+ case -EILSEQ:
+ if (!timer_pending (&dev->delay)) {
+ mod_timer (&dev->delay,
+ jiffies + THROTTLE_JIFFIES);
+ if (netif_msg_link (dev))
+ devdbg (dev, "tx throttle %d",
+ urb->status);
+ }
+ netif_stop_queue (dev->net);
+ break;
+ default:
+ if (netif_msg_tx_err (dev))
+ devdbg (dev, "tx err %d", entry->urb->status);
+ break;
+ }
+ }
+
+ entry->state = tx_done;
+ defer_bh(dev, skb, &dev->txq);
+
+#ifdef TX_URB_MONITOR
+ if ((URB_monitor) && (0==iRet))
+ {
+ URB_monitor(false, b_usb_if_num);
+ }
+#endif //#ifdef TX_URB_MONITOR
+
+}
+
+int gobi_usbnet_start_xmit_2_6_32 (struct sk_buff *skb, struct net_device *net)
+{
+ struct usbnet *dev = netdev_priv(net);
+ int length;
+ struct urb *urb = NULL;
+ struct skb_data *entry;
+ struct driver_info *info = dev->driver_info;
+ unsigned long flags;
+ int retval;
+#ifdef TX_URB_MONITOR
+ unsigned char b_usb_if_num = 0;
+ int iRet = -1;
+#endif //#ifdef TX_URB_MONITOR
+ // some devices want funky USB-level framing, for
+ // win32 driver (usually) and/or hardware quirks
+ if (info->tx_fixup)
+ {
+ skb = info->tx_fixup (dev, skb, GFP_ATOMIC);
+ if (!skb)
+ {
+ if (netif_msg_tx_err (dev))
+ devdbg (dev, "can't tx_fixup skb");
+ goto drop;
+ }
+ }
+ length = skb->len;
+
+ if (!(urb = usb_alloc_urb (0, GFP_ATOMIC)))
+ {
+ if (netif_msg_tx_err (dev))
+ devdbg (dev, "no urb");
+ goto drop;
+ }
+
+ entry = (struct skb_data *) skb->cb;
+ entry->urb = urb;
+ entry->dev = dev;
+ entry->state = tx_start;
+ entry->length = length;
+
+ usb_fill_bulk_urb (urb, dev->udev, dev->out,
+ skb->data, skb->len, tx_complete, skb);
+
+ /* don't assume the hardware handles USB_ZERO_PACKET
+ * NOTE: strictly conforming cdc-ether devices should expect
+ * the ZLP here, but ignore the one-byte packet.
+ */
+ if (!(info->flags & FLAG_SEND_ZLP) && (length % dev->maxpacket) == 0)
+ {
+ urb->transfer_buffer_length++;
+ if (skb_tailroom(skb))
+ {
+ skb->data[skb->len] = 0;
+ __skb_put(skb, 1);
+ }
+ }
+ spin_lock_irqsave (&dev->txq.lock, flags);
+#ifdef TX_URB_MONITOR
+ iRet = get_usb_interface(urb, &b_usb_if_num);
+#endif //#ifdef TX_URB_MONITOR
+ switch ((retval = usb_submit_urb (urb, GFP_ATOMIC)))
+ {
+ case -EPIPE:
+ netif_stop_queue (net);
+ usbnet_defer_kevent (dev, EVENT_TX_HALT);
+ break;
+ default:
+ if (netif_msg_tx_err (dev))
+ devdbg (dev, "tx: submit urb err %d", retval);
+ break;
+ case 0:
+ net->trans_start = jiffies;
+ __skb_queue_tail (&dev->txq, skb);
+ if (dev->txq.qlen >= TX_QLEN (dev))
+ netif_stop_queue (net);
+ }
+#ifdef TX_URB_MONITOR
+ /*
+ * Call URB_monitor() with true as the URB has been successfully
+ * submitted to the txq.
+ */
+ if ((URB_monitor) && (0==iRet) && (0==retval))
+ {
+ URB_monitor(true, b_usb_if_num);
+ }
+#endif //#ifdef TX_URB_MONITOR
+ spin_unlock_irqrestore (&dev->txq.lock, flags);
+ if (retval)
+ {
+ if (netif_msg_tx_err (dev))
+ devdbg (dev, "drop, code %d", retval);
+ drop:
+ retval = NET_XMIT_SUCCESS;
+ dev->net->stats.tx_dropped++;
+ if (skb)
+ dev_kfree_skb_any (skb);
+ usb_free_urb (urb);
+ }
+ else if (netif_msg_tx_queued (dev))
+ {
+ devdbg (dev, "> tx, len %d, type 0x%x",
+ length, skb->protocol);
+ }
+ return NETDEV_TX_OK;
+}
+#endif /* LINUX_VERSION_CODE == KERNEL_VERSION( 2,6,31 ) */
+
diff --git a/swi-drivers/src/GobiNet/usbnet_2_6_35.c b/swi-drivers/src/GobiNet/usbnet_2_6_35.c
new file mode 100644
index 0000000..a772d4d
--- /dev/null
+++ b/swi-drivers/src/GobiNet/usbnet_2_6_35.c
@@ -0,0 +1,400 @@
+/*===========================================================================
+FILE:
+ usbnet_2_6_35.c
+
+DESCRIPTION:
+ The default "usbnet_start_xmit" function is over-ridden by Sierra to provide the URB_Monitor
+ for Linux kernel 2.6.35
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+===========================================================================*/
+
+//---------------------------------------------------------------------------
+// Include Files
+//---------------------------------------------------------------------------
+
+#include "Structs.h"
+#include "QMIDevice.h"
+#include "QMI.h"
+#include "gobi_usbnet.h"
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/module.h>
+#include <net/ip.h>
+
+#include <asm/siginfo.h> //siginfo
+#include <linux/rcupdate.h> //rcu_read_lock
+#include <linux/sched.h> //find_task_by_pid_type
+
+#if (LINUX_VERSION_CODE == KERNEL_VERSION( 2,6,35 ))
+void (*URB_monitor) (bool,unsigned char);
+EXPORT_SYMBOL(URB_monitor);
+#if 0
+/*
+ * Dummy function to test URB back-pressure. In actual implementation,
+ * customer will implement this function
+ */
+// Define PDN interfaced as per specific Sierra module PTS. The below are for MC73xx modules
+#define PDN1_INTERFACE 8
+#define PDN2_INTERFACE 10
+#define BACK_PRESSURE_WATERMARK 10
+static unsigned short urb_count_pdn1 = 0;
+static unsigned short urb_count_pdn2 = 0;
+void URB_monitor (bool isUpCount,unsigned char interface)
+{
+/* printk(KERN_WARNING "[%s] isUpCount %d, intf %d", \
+ __func__, isUpCount, interface);*/
+ if (isUpCount)
+ {
+ if (interface == PDN1_INTERFACE)
+ {
+ urb_count_pdn1++;
+ }
+ else if (interface == PDN2_INTERFACE)
+ {
+ urb_count_pdn2++;
+ }
+ else
+ {
+ // unknown interface. ignore or log as needed
+ }
+ }
+ else
+ {
+ if (interface == PDN1_INTERFACE)
+ {
+ urb_count_pdn1--;
+ }
+ else if (interface == PDN2_INTERFACE)
+ {
+ urb_count_pdn2--;
+ }
+ else
+ {
+ // unknown interface. ignore or log as needed
+ }
+ }
+ if (BACK_PRESSURE_WATERMARK <= urb_count_pdn1)
+ {
+ // Back pressure on PDN1
+ printk(KERN_WARNING "[%s] Backpressure %d on PDN1", \
+ __func__, urb_count_pdn1);
+ }
+ if (BACK_PRESSURE_WATERMARK <= urb_count_pdn2)
+ {
+ // Back pressure on PDN2
+ printk(KERN_WARNING "[%s] Backpressure %d on PDN2", \
+ __func__, urb_count_pdn2);
+ }
+}
+#endif // 0 End of dummy function
+
+// Get the USB interface from a usbnet pointer. The function return 0 on success, -1 on error
+__always_inline static int get_usb_interface_from_device (struct usbnet *dev, unsigned char *pb_usb_interface)
+{
+ int iRet = -1; // set to error by default
+
+ if ((NULL!=dev) && (NULL!=pb_usb_interface))
+ {
+ if ((NULL != dev->intf) &&
+ (NULL != dev->intf->cur_altsetting))
+ {
+ *pb_usb_interface = dev->intf->cur_altsetting->desc.bInterfaceNumber;
+ iRet = 0;
+ } // (NULL != dev->intf) && (NULL != dev->intf->cur_altsetting)
+ } //(NULL != pURB) && (NULL!=pb_usb_interface)
+ return iRet;
+}
+
+// Get the USB interface from a URB. The function return 0 on success, -1 on error
+__always_inline static int get_usb_interface (struct urb * pURB, unsigned char *pb_usb_interface)
+{
+ int iRet = -1; // set to error by default
+ struct sk_buff *skb = NULL;
+ struct skb_data *entry = NULL;
+
+ if ((NULL!=pURB) && (NULL!=pb_usb_interface))
+ {
+ skb = (struct sk_buff *) pURB->context;
+ if (NULL != skb)
+ {
+ entry = (struct skb_data *) skb->cb;
+ if (NULL != entry)
+ {
+ iRet = get_usb_interface_from_device (entry->dev, pb_usb_interface);
+ }
+ } // (NULL != skb)
+ } // (NULL != pURB) && (NULL!=pb_usb_interface)
+ return iRet;
+}
+
+// unlink pending rx/tx; completion handlers do all other cleanup
+
+static int unlink_urbs (struct usbnet *dev, struct sk_buff_head *q)
+{
+ unsigned long flags;
+ struct sk_buff *skb, *skbnext;
+ int count = 0;
+
+ spin_lock_irqsave (&q->lock, flags);
+ skb_queue_walk_safe(q, skb, skbnext) {
+ struct skb_data *entry;
+ struct urb *urb;
+ int retval;
+
+ entry = (struct skb_data *) skb->cb;
+ urb = entry->urb;
+
+ // during some PM-driven resume scenarios,
+ // these (async) unlinks complete immediately
+ retval = usb_unlink_urb (urb);
+ if (retval != -EINPROGRESS && retval != 0)
+ netdev_dbg(dev->net, "unlink urb err, %d\n", retval);
+ else
+ count++;
+ }
+ spin_unlock_irqrestore (&q->lock, flags);
+ return count;
+}
+
+void gobi_usbnet_tx_timeout_2_6_35(struct net_device *net)
+{
+ struct usbnet *dev = netdev_priv(net);
+#ifdef TX_URB_MONITOR
+ int count = 0;
+ int iRet = -1;
+ unsigned char b_usb_if_num = 0;
+ // Get the USB interface
+ iRet = get_usb_interface_from_device (dev, &b_usb_if_num);
+
+ count = unlink_urbs (dev, &dev->txq);
+ tasklet_schedule (&dev->bh);
+
+ if ((URB_monitor) && (0==iRet))
+ {
+ while (count)
+ {
+ URB_monitor(false, b_usb_if_num);
+ count--;
+ }
+ }
+#else // TX_URB_MONITOR
+ unlink_urbs (dev, &dev->txq);
+ tasklet_schedule (&dev->bh);
+#endif // TX_URB_MONITOR
+ // FIXME: device recovery -- reset?
+}
+
+/* some LK 2.4 HCDs oopsed if we freed or resubmitted urbs from
+ * completion callbacks. 2.5 should have fixed those bugs...
+ */
+
+static void defer_bh(struct usbnet *dev, struct sk_buff *skb, struct sk_buff_head *list)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&list->lock, flags);
+ __skb_unlink(skb, list);
+ spin_unlock(&list->lock);
+ spin_lock(&dev->done.lock);
+ __skb_queue_tail(&dev->done, skb);
+ if (dev->done.qlen == 1)
+ tasklet_schedule(&dev->bh);
+ spin_unlock_irqrestore(&dev->done.lock, flags);
+}
+
+static void tx_complete (struct urb *urb)
+{
+ struct sk_buff *skb = (struct sk_buff *) urb->context;
+ struct skb_data *entry = (struct skb_data *) skb->cb;
+ struct usbnet *dev = entry->dev;
+
+#ifdef TX_URB_MONITOR
+ unsigned char b_usb_if_num = 0;
+ int iRet = get_usb_interface(urb, &b_usb_if_num);
+#endif //#ifdef TX_URB_MONITOR
+
+ if (urb->status == 0) {
+ dev->net->stats.tx_packets++;
+ dev->net->stats.tx_bytes += entry->length;
+ } else {
+ dev->net->stats.tx_errors++;
+
+ switch (urb->status) {
+ case -EPIPE:
+ usbnet_defer_kevent (dev, EVENT_TX_HALT);
+ break;
+
+ /* software-driven interface shutdown */
+ case -ECONNRESET: // async unlink
+ case -ESHUTDOWN: // hardware gone
+ break;
+
+ // like rx, tx gets controller i/o faults during khubd delays
+ // and so it uses the same throttling mechanism.
+ case -EPROTO:
+ case -ETIME:
+ case -EILSEQ:
+ usb_mark_last_busy(dev->udev);
+ if (!timer_pending (&dev->delay)) {
+ mod_timer (&dev->delay,
+ jiffies + THROTTLE_JIFFIES);
+ netif_dbg(dev, link, dev->net,
+ "tx throttle %d\n", urb->status);
+ }
+ netif_stop_queue (dev->net);
+ break;
+ default:
+ netif_dbg(dev, tx_err, dev->net,
+ "tx err %d\n", entry->urb->status);
+ break;
+ }
+ }
+
+ usb_autopm_put_interface_async(dev->intf);
+ urb->dev = NULL;
+ entry->state = tx_done;
+ defer_bh(dev, skb, &dev->txq);
+
+#ifdef TX_URB_MONITOR
+ if ((URB_monitor) && (0==iRet))
+ {
+ URB_monitor(false, b_usb_if_num);
+ }
+#endif //#ifdef TX_URB_MONITOR
+
+}
+
+int gobi_usbnet_start_xmit_2_6_35 (struct sk_buff *skb, struct net_device *net)
+{
+ struct usbnet *dev = netdev_priv(net);
+ int length;
+ struct urb *urb = NULL;
+ struct skb_data *entry;
+ struct driver_info *info = dev->driver_info;
+ unsigned long flags;
+ int retval;
+#ifdef TX_URB_MONITOR
+ unsigned char b_usb_if_num = 0;
+ int iRet = -1;
+#endif //#ifdef TX_URB_MONITOR
+ // some devices want funky USB-level framing, for
+ // win32 driver (usually) and/or hardware quirks
+ if (info->tx_fixup) {
+ skb = info->tx_fixup (dev, skb, GFP_ATOMIC);
+ if (!skb) {
+ netif_dbg(dev, tx_err, dev->net, "can't tx_fixup skb\n");
+ goto drop;
+ }
+ }
+ length = skb->len;
+
+ if (!(urb = usb_alloc_urb (0, GFP_ATOMIC))) {
+ netif_dbg(dev, tx_err, dev->net, "no urb\n");
+ goto drop;
+ }
+
+ entry = (struct skb_data *) skb->cb;
+ entry->urb = urb;
+ entry->dev = dev;
+ entry->state = tx_start;
+ entry->length = length;
+
+ usb_fill_bulk_urb (urb, dev->udev, dev->out,
+ skb->data, skb->len, tx_complete, skb);
+
+ /* don't assume the hardware handles USB_ZERO_PACKET
+ * NOTE: strictly conforming cdc-ether devices should expect
+ * the ZLP here, but ignore the one-byte packet.
+ */
+ if (length % dev->maxpacket == 0) {
+ if (!(info->flags & FLAG_SEND_ZLP)) {
+ urb->transfer_buffer_length++;
+ if (skb_tailroom(skb)) {
+ skb->data[skb->len] = 0;
+ __skb_put(skb, 1);
+ }
+ } else
+ urb->transfer_flags |= URB_ZERO_PACKET;
+ }
+
+ spin_lock_irqsave(&dev->txq.lock, flags);
+ retval = usb_autopm_get_interface_async(dev->intf);
+ if (retval < 0) {
+ spin_unlock_irqrestore(&dev->txq.lock, flags);
+ goto drop;
+ }
+
+#ifdef CONFIG_PM
+ /* if this triggers the device is still a sleep */
+ if (test_bit(EVENT_DEV_ASLEEP, &dev->flags)) {
+ /* transmission will be done in resume */
+ usb_anchor_urb(urb, &dev->deferred);
+ /* no use to process more packets */
+ netif_stop_queue(net);
+ spin_unlock_irqrestore(&dev->txq.lock, flags);
+ netdev_dbg(dev->net, "Delaying transmission for resumption\n");
+ goto deferred;
+ }
+#endif
+
+#ifdef TX_URB_MONITOR
+ iRet = get_usb_interface(urb, &b_usb_if_num);
+#endif //#ifdef TX_URB_MONITOR
+ switch ((retval = usb_submit_urb (urb, GFP_ATOMIC))) {
+ case -EPIPE:
+ netif_stop_queue (net);
+ usbnet_defer_kevent (dev, EVENT_TX_HALT);
+ usb_autopm_put_interface_async(dev->intf);
+ break;
+ default:
+ usb_autopm_put_interface_async(dev->intf);
+ netif_dbg(dev, tx_err, dev->net,
+ "tx: submit urb err %d\n", retval);
+ break;
+ case 0:
+ net->trans_start = jiffies;
+ __skb_queue_tail (&dev->txq, skb);
+ if (dev->txq.qlen >= TX_QLEN (dev))
+ netif_stop_queue (net);
+ }
+#ifdef TX_URB_MONITOR
+ /*
+ * Call URB_monitor() with true as the URB has been successfully
+ * submitted to the txq.
+ */
+ if ((URB_monitor) && (0==iRet) && (0==retval))
+ {
+ URB_monitor(true, b_usb_if_num);
+ }
+#endif //#ifdef TX_URB_MONITOR
+
+ spin_unlock_irqrestore (&dev->txq.lock, flags);
+
+ if (retval) {
+ netif_dbg(dev, tx_err, dev->net, "drop, code %d\n", retval);
+drop:
+ dev->net->stats.tx_dropped++;
+ if (skb)
+ dev_kfree_skb_any (skb);
+ usb_free_urb (urb);
+ } else
+ netif_dbg(dev, tx_queued, dev->net,
+ "> tx, len %d, type 0x%x\n", length, skb->protocol);
+#ifdef CONFIG_PM
+deferred:
+#endif
+ return NETDEV_TX_OK;
+}
+#endif /* #if (LINUX_VERSION_CODE == KERNEL_VERSION( 2,6,35 )) */
+
diff --git a/swi-drivers/src/GobiNet/usbnet_3_0_6.c b/swi-drivers/src/GobiNet/usbnet_3_0_6.c
new file mode 100644
index 0000000..8f1c7f4
--- /dev/null
+++ b/swi-drivers/src/GobiNet/usbnet_3_0_6.c
@@ -0,0 +1,432 @@
+/*===========================================================================
+FILE:
+ usbnet_3_0_6.c
+
+DESCRIPTION:
+ The default "usbnet_start_xmit" function is over-ridden by Sierra to provide the URB_Monitor
+ for Linux kernel 3.0.6
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+===========================================================================*/
+
+//---------------------------------------------------------------------------
+// Include Files
+//---------------------------------------------------------------------------
+
+#include "Structs.h"
+#include "QMIDevice.h"
+#include "QMI.h"
+#include "gobi_usbnet.h"
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/module.h>
+#include <net/ip.h>
+
+#include <asm/siginfo.h> //siginfo
+#include <linux/rcupdate.h> //rcu_read_lock
+#include <linux/sched.h> //find_task_by_pid_type
+
+#if (LINUX_VERSION_CODE == KERNEL_VERSION( 3,0,6 ))
+void (*URB_monitor) (bool,unsigned char);
+EXPORT_SYMBOL(URB_monitor);
+#if 0
+/*
+ * Dummy function to test URB back-pressure. In actual implementation,
+ * customer will implement this function
+ */
+// Define PDN interfaced as per specific Sierra module PTS. The below are for MC73xx modules
+#define PDN1_INTERFACE 8
+#define PDN2_INTERFACE 10
+#define BACK_PRESSURE_WATERMARK 10
+static unsigned short urb_count_pdn1 = 0;
+static unsigned short urb_count_pdn2 = 0;
+void URB_monitor (bool isUpCount,unsigned char interface)
+{
+/* printk(KERN_WARNING "[%s] isUpCount %d, intf %d", \
+ __func__, isUpCount, interface);*/
+ if (isUpCount)
+ {
+ if (interface == PDN1_INTERFACE)
+ {
+ urb_count_pdn1++;
+ }
+ else if (interface == PDN2_INTERFACE)
+ {
+ urb_count_pdn2++;
+ }
+ else
+ {
+ // unknown interface. ignore or log as needed
+ }
+ }
+ else
+ {
+ if (interface == PDN1_INTERFACE)
+ {
+ urb_count_pdn1--;
+ }
+ else if (interface == PDN2_INTERFACE)
+ {
+ urb_count_pdn2--;
+ }
+ else
+ {
+ // unknown interface. ignore or log as needed
+ }
+ }
+ if (BACK_PRESSURE_WATERMARK <= urb_count_pdn1)
+ {
+ // Back pressure on PDN1
+ printk(KERN_WARNING "[%s] Backpressure %d on PDN1", \
+ __func__, urb_count_pdn1);
+ }
+ if (BACK_PRESSURE_WATERMARK <= urb_count_pdn2)
+ {
+ // Back pressure on PDN2
+ printk(KERN_WARNING "[%s] Backpressure %d on PDN2", \
+ __func__, urb_count_pdn2);
+ }
+}
+#endif // 0 End of dummy function
+
+// Get the USB interface from a usbnet pointer. The function return 0 on success, -1 on error
+__always_inline static int get_usb_interface_from_device (struct usbnet *dev, unsigned char *pb_usb_interface)
+{
+ int iRet = -1; // set to error by default
+
+ if ((NULL!=dev) && (NULL!=pb_usb_interface))
+ {
+ if ((NULL != dev->intf) &&
+ (NULL != dev->intf->cur_altsetting))
+ {
+ *pb_usb_interface = dev->intf->cur_altsetting->desc.bInterfaceNumber;
+ iRet = 0;
+ } // (NULL != dev->intf) && (NULL != dev->intf->cur_altsetting)
+ } //(NULL != pURB) && (NULL!=pb_usb_interface)
+ return iRet;
+}
+
+// Get the USB interface from a URB. The function return 0 on success, -1 on error
+__always_inline static int get_usb_interface (struct urb * pURB, unsigned char *pb_usb_interface)
+{
+ int iRet = -1; // set to error by default
+ struct sk_buff *skb = NULL;
+ struct skb_data *entry = NULL;
+
+ if ((NULL!=pURB) && (NULL!=pb_usb_interface))
+ {
+ skb = (struct sk_buff *) pURB->context;
+ if (NULL != skb)
+ {
+ entry = (struct skb_data *) skb->cb;
+ if (NULL != entry)
+ {
+ iRet = get_usb_interface_from_device (entry->dev, pb_usb_interface);
+ }
+ } // (NULL != skb)
+ } // (NULL != pURB) && (NULL!=pb_usb_interface)
+ return iRet;
+}
+
+// unlink pending rx/tx; completion handlers do all other cleanup
+static int unlink_urbs (struct usbnet *dev, struct sk_buff_head *q)
+{
+ unsigned long flags;
+ struct sk_buff *skb, *skbnext;
+ int count = 0;
+
+ spin_lock_irqsave (&q->lock, flags);
+ skb_queue_walk_safe(q, skb, skbnext) {
+ struct skb_data *entry;
+ struct urb *urb;
+ int retval;
+
+ entry = (struct skb_data *) skb->cb;
+ urb = entry->urb;
+
+ // during some PM-driven resume scenarios,
+ // these (async) unlinks complete immediately
+ retval = usb_unlink_urb (urb);
+ if (retval != -EINPROGRESS && retval != 0)
+ netdev_dbg(dev->net, "unlink urb err, %d\n", retval);
+ else
+ count++;
+ }
+ spin_unlock_irqrestore (&q->lock, flags);
+ return count;
+}
+
+void gobi_usbnet_tx_timeout_3_0_6 (struct net_device *net)
+{
+ struct usbnet *dev = netdev_priv(net);
+#ifdef TX_URB_MONITOR
+ int count = 0;
+ int iRet = -1;
+ unsigned char b_usb_if_num = 0;
+ // Get the USB interface
+ iRet = get_usb_interface_from_device (dev, &b_usb_if_num);
+
+ count = unlink_urbs (dev, &dev->txq);
+ tasklet_schedule (&dev->bh);
+
+ if ((URB_monitor) && (0==iRet))
+ {
+ while (count)
+ {
+ URB_monitor(false, b_usb_if_num);
+ count--;
+ }
+ }
+#else // TX_URB_MONITOR
+ unlink_urbs (dev, &dev->txq);
+ tasklet_schedule (&dev->bh);
+#endif // TX_URB_MONITOR
+ // FIXME: device recovery -- reset?
+}
+
+/* some LK 2.4 HCDs oopsed if we freed or resubmitted urbs from
+ * completion callbacks. 2.5 should have fixed those bugs...
+ */
+
+static void defer_bh(struct usbnet *dev, struct sk_buff *skb, struct sk_buff_head *list)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&list->lock, flags);
+ __skb_unlink(skb, list);
+ spin_unlock(&list->lock);
+ spin_lock(&dev->done.lock);
+ __skb_queue_tail(&dev->done, skb);
+ if (dev->done.qlen == 1)
+ tasklet_schedule(&dev->bh);
+ spin_unlock_irqrestore(&dev->done.lock, flags);
+}
+
+static void tx_complete (struct urb *urb)
+{
+ struct sk_buff *skb = (struct sk_buff *) urb->context;
+ struct skb_data *entry = (struct skb_data *) skb->cb;
+ struct usbnet *dev = entry->dev;
+
+#ifdef TX_URB_MONITOR
+ unsigned char b_usb_if_num = 0;
+ int iRet = get_usb_interface(urb, &b_usb_if_num);
+#endif //#ifdef TX_URB_MONITOR
+
+ if (urb->status == 0)
+ {
+ if (!(dev->driver_info->flags & FLAG_MULTI_PACKET))
+ dev->net->stats.tx_packets++;
+ dev->net->stats.tx_bytes += entry->length;
+ }
+ else
+ {
+ dev->net->stats.tx_errors++;
+ switch (urb->status)
+ {
+ case -EPIPE:
+ usbnet_defer_kevent (dev, EVENT_TX_HALT);
+ break;
+
+ /* software-driven interface shutdown */
+ case -ECONNRESET: // async unlink
+ case -ESHUTDOWN: // hardware gone
+ break;
+
+ // like rx, tx gets controller i/o faults during khubd delays
+ // and so it uses the same throttling mechanism.
+ case -EPROTO:
+ case -ETIME:
+ case -EILSEQ:
+ if (!timer_pending (&dev->delay)) {
+ mod_timer (&dev->delay,
+ jiffies + THROTTLE_JIFFIES);
+ if (netif_msg_link (dev))
+ #if (LINUX_VERSION_CODE != KERNEL_VERSION( 3,0,6 ))
+ devdbg (dev, "tx throttle %d",
+ urb->status);
+ #else
+ netif_dbg(dev, link, dev->net,
+ "tx throttle %d\n", urb->status);
+ #endif
+ }
+ netif_stop_queue (dev->net);
+ break;
+ default:
+ if (netif_msg_tx_err (dev))
+ #if (LINUX_VERSION_CODE != KERNEL_VERSION( 3,0,6 ))
+ devdbg (dev, "tx err %d", entry->urb->status);
+ #else
+ netif_dbg(dev, tx_err, dev->net,
+ "tx err %d\n", entry->urb->status);
+ #endif
+ break;
+ }
+ }
+
+ usb_autopm_put_interface_async(dev->intf);
+ urb->dev = NULL;
+ entry->state = tx_done;
+ defer_bh(dev, skb, &dev->txq);
+
+#ifdef TX_URB_MONITOR
+ if ((URB_monitor) && (0==iRet))
+ {
+ URB_monitor(false, b_usb_if_num);
+ }
+#endif //#ifdef TX_URB_MONITOR
+
+}
+
+netdev_tx_t gobi_usbnet_start_xmit_3_0_6 (struct sk_buff *skb,
+ struct net_device *net)
+{
+ struct usbnet *dev = netdev_priv(net);
+ int length;
+ struct urb *urb = NULL;
+ struct skb_data *entry;
+ struct driver_info *info = dev->driver_info;
+ unsigned long flags;
+ int retval;
+#ifdef TX_URB_MONITOR
+ unsigned char b_usb_if_num = 0;
+ int iRet = -1;
+#endif //#ifdef TX_URB_MONITOR
+
+ // some devices want funky USB-level framing, for
+ // win32 driver (usually) and/or hardware quirks
+ if (info->tx_fixup) {
+ skb = info->tx_fixup (dev, skb, GFP_ATOMIC);
+ if (!skb) {
+ if (netif_msg_tx_err(dev)) {
+ netif_dbg(dev, tx_err, dev->net, "can't tx_fixup skb\n");
+ goto drop;
+ } else {
+ /* cdc_ncm collected packet; waits for more */
+ goto not_drop;
+ }
+ }
+ }
+ length = skb->len;
+
+ if (!(urb = usb_alloc_urb (0, GFP_ATOMIC))) {
+ netif_dbg(dev, tx_err, dev->net, "no urb\n");
+ goto drop;
+ }
+
+ entry = (struct skb_data *) skb->cb;
+ entry->urb = urb;
+ entry->dev = dev;
+ entry->state = tx_start;
+ entry->length = length;
+
+ usb_fill_bulk_urb (urb, dev->udev, dev->out,
+ skb->data, skb->len, tx_complete, skb);
+
+ /* don't assume the hardware handles USB_ZERO_PACKET
+ * NOTE: strictly conforming cdc-ether devices should expect
+ * the ZLP here, but ignore the one-byte packet.
+ * NOTE2: CDC NCM specification is different from CDC ECM when
+ * handling ZLP/short packets, so cdc_ncm driver will make short
+ * packet itself if needed.
+ */
+ if (length % dev->maxpacket == 0) {
+ if (!(info->flags & FLAG_SEND_ZLP)) {
+ if (!(info->flags & FLAG_MULTI_PACKET)) {
+ urb->transfer_buffer_length++;
+ if (skb_tailroom(skb)) {
+ skb->data[skb->len] = 0;
+ __skb_put(skb, 1);
+ }
+ }
+ } else
+ urb->transfer_flags |= URB_ZERO_PACKET;
+ }
+
+ spin_lock_irqsave(&dev->txq.lock, flags);
+ retval = usb_autopm_get_interface_async(dev->intf);
+ if (retval < 0) {
+ spin_unlock_irqrestore(&dev->txq.lock, flags);
+ goto drop;
+ }
+
+#ifdef CONFIG_PM
+ /* if this triggers the device is still a sleep */
+ if (test_bit(EVENT_DEV_ASLEEP, &dev->flags)) {
+ /* transmission will be done in resume */
+ usb_anchor_urb(urb, &dev->deferred);
+ /* no use to process more packets */
+ netif_stop_queue(net);
+ spin_unlock_irqrestore(&dev->txq.lock, flags);
+ netdev_dbg(dev->net, "Delaying transmission for resumption\n");
+ goto deferred;
+ }
+#endif
+
+#ifdef TX_URB_MONITOR
+ iRet = get_usb_interface(urb, &b_usb_if_num);
+#endif //#ifdef TX_URB_MONITOR
+
+ switch ((retval = usb_submit_urb (urb, GFP_ATOMIC))) {
+ case -EPIPE:
+ netif_stop_queue (net);
+ usbnet_defer_kevent (dev, EVENT_TX_HALT);
+ usb_autopm_put_interface_async(dev->intf);
+ break;
+ default:
+ usb_autopm_put_interface_async(dev->intf);
+ netif_dbg(dev, tx_err, dev->net,
+ "tx: submit urb err %d\n", retval);
+ break;
+ case 0:
+ net->trans_start = jiffies;
+ __skb_queue_tail (&dev->txq, skb);
+ if (dev->txq.qlen >= TX_QLEN (dev))
+ netif_stop_queue (net);
+ }
+#ifdef TX_URB_MONITOR
+ /*
+ * This can be called from here or from inside the
+ * case 0 in the above switch. There will be one less
+ * condition to check
+ */
+ /*
+ * Call URB_monitor() with true as the URB has been successfully
+ * submitted to the txq.
+ */
+ if ((URB_monitor) && (0==iRet) && (0==retval))
+ {
+ URB_monitor(true, b_usb_if_num);
+ }
+#endif //#ifdef TX_URB_MONITOR
+
+ spin_unlock_irqrestore (&dev->txq.lock, flags);
+
+ if (retval) {
+ netif_dbg(dev, tx_err, dev->net, "drop, code %d\n", retval);
+drop:
+ dev->net->stats.tx_dropped++;
+not_drop:
+ if (skb)
+ dev_kfree_skb_any (skb);
+ usb_free_urb (urb);
+ } else
+ netif_dbg(dev, tx_queued, dev->net,
+ "> tx, len %d, type 0x%x\n", length, skb->protocol);
+#ifdef CONFIG_PM
+deferred:
+#endif
+ return NETDEV_TX_OK;
+}
+#endif /* #if (LINUX_VERSION_CODE == KERNEL_VERSION( 3,0,6 )) */
+
diff --git a/swi-drivers/src/GobiNet/usbnet_3_10_21.c b/swi-drivers/src/GobiNet/usbnet_3_10_21.c
new file mode 100644
index 0000000..8671ff7
--- /dev/null
+++ b/swi-drivers/src/GobiNet/usbnet_3_10_21.c
@@ -0,0 +1,457 @@
+/*===========================================================================
+FILE:
+ usbnet_3_10_21.c
+
+DESCRIPTION:
+ The default "usbnet_start_xmit" function is over-ridden by Sierra to provide the URB_Monitor
+ for Linux kernel 3.10.1 to 3.10.39
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+===========================================================================*/
+
+//---------------------------------------------------------------------------
+// Include Files
+//---------------------------------------------------------------------------
+
+#include "Structs.h"
+#include "QMIDevice.h"
+#include "QMI.h"
+#include "gobi_usbnet.h"
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/module.h>
+#include <net/ip.h>
+
+#include <asm/siginfo.h> //siginfo
+#include <linux/rcupdate.h> //rcu_read_lock
+#include <linux/sched.h> //find_task_by_pid_type
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 3,10,1 ) &&\
+ LINUX_VERSION_CODE <= KERNEL_VERSION( 3,10,39))
+void (*URB_monitor) (bool,unsigned char);
+EXPORT_SYMBOL(URB_monitor);
+#if 0
+/*
+ * Dummy function to test URB back-pressure. In actual implementation,
+ * customer will implement this function
+ */
+// Define PDN interfaced as per specific Sierra module PTS. The below are for MC73xx modules
+#define PDN1_INTERFACE 8
+#define PDN2_INTERFACE 10
+#define BACK_PRESSURE_WATERMARK 10
+static unsigned short urb_count_pdn1 = 0;
+static unsigned short urb_count_pdn2 = 0;
+void URB_monitor (bool isUpCount,unsigned char interface)
+{
+/* printk(KERN_WARNING "[%s] isUpCount %d, intf %d", \
+ __func__, isUpCount, interface);*/
+ if (isUpCount)
+ {
+ if (interface == PDN1_INTERFACE)
+ {
+ urb_count_pdn1++;
+ }
+ else if (interface == PDN2_INTERFACE)
+ {
+ urb_count_pdn2++;
+ }
+ else
+ {
+ // unknown interface. ignore or log as needed
+ }
+ }
+ else
+ {
+ if (interface == PDN1_INTERFACE)
+ {
+ urb_count_pdn1--;
+ }
+ else if (interface == PDN2_INTERFACE)
+ {
+ urb_count_pdn2--;
+ }
+ else
+ {
+ // unknown interface. ignore or log as needed
+ }
+ }
+ if (BACK_PRESSURE_WATERMARK <= urb_count_pdn1)
+ {
+ // Back pressure on PDN1
+ printk(KERN_WARNING "[%s] Backpressure %d on PDN1", \
+ __func__, urb_count_pdn1);
+ }
+ if (BACK_PRESSURE_WATERMARK <= urb_count_pdn2)
+ {
+ // Back pressure on PDN2
+ printk(KERN_WARNING "[%s] Backpressure %d on PDN2", \
+ __func__, urb_count_pdn2);
+ }
+}
+#endif // 0 End of dummy function
+
+// Get the USB interface from a usbnet pointer. The function return 0 on success, -1 on error
+__always_inline static int get_usb_interface_from_device (struct usbnet *dev, unsigned char *pb_usb_interface)
+{
+ int iRet = -1; // set to error by default
+
+ if ((NULL!=dev) && (NULL!=pb_usb_interface))
+ {
+ if ((NULL != dev->intf) &&
+ (NULL != dev->intf->cur_altsetting))
+ {
+ *pb_usb_interface = dev->intf->cur_altsetting->desc.bInterfaceNumber;
+ iRet = 0;
+ } // (NULL != dev->intf) && (NULL != dev->intf->cur_altsetting)
+ } //(NULL != pURB) && (NULL!=pb_usb_interface)
+ return iRet;
+}
+
+// Get the USB interface from a URB. The function return 0 on success, -1 on error
+__always_inline static int get_usb_interface (struct urb * pURB, unsigned char *pb_usb_interface)
+{
+ int iRet = -1; // set to error by default
+ struct sk_buff *skb = NULL;
+ struct skb_data *entry = NULL;
+
+ if ((NULL!=pURB) && (NULL!=pb_usb_interface))
+ {
+ skb = (struct sk_buff *) pURB->context;
+ if (NULL != skb)
+ {
+ entry = (struct skb_data *) skb->cb;
+ if (NULL != entry)
+ {
+ iRet = get_usb_interface_from_device (entry->dev, pb_usb_interface);
+ }
+ } // (NULL != skb)
+ } // (NULL != pURB) && (NULL!=pb_usb_interface)
+ return iRet;
+}
+
+// unlink pending rx/tx; completion handlers do all other cleanup
+
+static int unlink_urbs (struct usbnet *dev, struct sk_buff_head *q)
+{
+ unsigned long flags;
+ struct sk_buff *skb;
+ int count = 0;
+
+ spin_lock_irqsave (&q->lock, flags);
+ while (!skb_queue_empty(q)) {
+ struct skb_data *entry;
+ struct urb *urb;
+ int retval;
+
+ skb_queue_walk(q, skb) {
+ entry = (struct skb_data *) skb->cb;
+ if (entry->state != unlink_start)
+ goto found;
+ }
+ break;
+found:
+ entry->state = unlink_start;
+ urb = entry->urb;
+
+ /*
+ * Get reference count of the URB to avoid it to be
+ * freed during usb_unlink_urb, which may trigger
+ * use-after-free problem inside usb_unlink_urb since
+ * usb_unlink_urb is always racing with .complete
+ * handler(include defer_bh).
+ */
+ usb_get_urb(urb);
+ spin_unlock_irqrestore(&q->lock, flags);
+ // during some PM-driven resume scenarios,
+ // these (async) unlinks complete immediately
+ retval = usb_unlink_urb (urb);
+ if (retval != -EINPROGRESS && retval != 0)
+ netdev_dbg(dev->net, "unlink urb err, %d\n", retval);
+ else
+ count++;
+ usb_put_urb(urb);
+ spin_lock_irqsave(&q->lock, flags);
+ }
+ spin_unlock_irqrestore (&q->lock, flags);
+ return count;
+}
+
+
+void gobi_usbnet_tx_timeout_3_10_21 (struct net_device *net)
+{
+ struct usbnet *dev = netdev_priv(net);
+#ifdef TX_URB_MONITOR
+ int count = 0;
+ int iRet = -1;
+ unsigned char b_usb_if_num = 0;
+ // Get the USB interface
+ iRet = get_usb_interface_from_device (dev, &b_usb_if_num);
+
+ count = unlink_urbs (dev, &dev->txq);
+ tasklet_schedule (&dev->bh);
+
+ if ((URB_monitor) && (0==iRet))
+ {
+ while (count)
+ {
+ URB_monitor(false, b_usb_if_num);
+ count--;
+ }
+ }
+#else // TX_URB_MONITOR
+ unlink_urbs (dev, &dev->txq);
+ tasklet_schedule (&dev->bh);
+#endif // TX_URB_MONITOR
+ // FIXME: device recovery -- reset?
+}
+
+/* some LK 2.4 HCDs oopsed if we freed or resubmitted urbs from
+ * completion callbacks. 2.5 should have fixed those bugs...
+ */
+
+static enum skb_state defer_bh(struct usbnet *dev, struct sk_buff *skb,
+ struct sk_buff_head *list, enum skb_state state)
+{
+ unsigned long flags;
+ enum skb_state old_state;
+ struct skb_data *entry = (struct skb_data *) skb->cb;
+
+ spin_lock_irqsave(&list->lock, flags);
+ old_state = entry->state;
+ entry->state = state;
+ __skb_unlink(skb, list);
+ spin_unlock(&list->lock);
+ spin_lock(&dev->done.lock);
+ __skb_queue_tail(&dev->done, skb);
+ if (dev->done.qlen == 1)
+ tasklet_schedule(&dev->bh);
+ spin_unlock_irqrestore(&dev->done.lock, flags);
+ return old_state;
+}
+
+static void tx_complete (struct urb *urb)
+{
+ struct sk_buff *skb = (struct sk_buff *) urb->context;
+ struct skb_data *entry = (struct skb_data *) skb->cb;
+ struct usbnet *dev = entry->dev;
+
+#ifdef TX_URB_MONITOR
+ unsigned char b_usb_if_num = 0;
+ int iRet = get_usb_interface(urb, &b_usb_if_num);
+#endif //#ifdef TX_URB_MONITOR
+
+ if (urb->status == 0) {
+ if (!(dev->driver_info->flags & FLAG_MULTI_PACKET))
+ dev->net->stats.tx_packets++;
+ dev->net->stats.tx_bytes += entry->length;
+ } else {
+ dev->net->stats.tx_errors++;
+
+ switch (urb->status) {
+ case -EPIPE:
+ usbnet_defer_kevent (dev, EVENT_TX_HALT);
+ break;
+
+ /* software-driven interface shutdown */
+ case -ECONNRESET: // async unlink
+ case -ESHUTDOWN: // hardware gone
+ break;
+
+ // like rx, tx gets controller i/o faults during khubd delays
+ // and so it uses the same throttling mechanism.
+ case -EPROTO:
+ case -ETIME:
+ case -EILSEQ:
+ usb_mark_last_busy(dev->udev);
+ if (!timer_pending (&dev->delay)) {
+ mod_timer (&dev->delay,
+ jiffies + THROTTLE_JIFFIES);
+ netif_dbg(dev, link, dev->net,
+ "tx throttle %d\n", urb->status);
+ }
+ netif_stop_queue (dev->net);
+ break;
+ default:
+ netif_dbg(dev, tx_err, dev->net,
+ "tx err %d\n", entry->urb->status);
+ break;
+ }
+ }
+
+ usb_autopm_put_interface_async(dev->intf);
+ (void) defer_bh(dev, skb, &dev->txq, tx_done);
+
+#ifdef TX_URB_MONITOR
+ if ((URB_monitor) && (0==iRet))
+ {
+ URB_monitor(false, b_usb_if_num);
+ }
+#endif //#ifdef TX_URB_MONITOR
+
+}
+
+/* The caller must hold list->lock */
+static void __usbnet_queue_skb(struct sk_buff_head *list,
+ struct sk_buff *newsk, enum skb_state state)
+{
+ struct skb_data *entry = (struct skb_data *) newsk->cb;
+
+ __skb_queue_tail(list, newsk);
+ entry->state = state;
+}
+
+netdev_tx_t gobi_usbnet_start_xmit_3_10_21 (struct sk_buff *skb,
+ struct net_device *net)
+{
+ struct usbnet *dev = netdev_priv(net);
+ int length;
+ struct urb *urb = NULL;
+ struct skb_data *entry;
+ struct driver_info *info = dev->driver_info;
+ unsigned long flags;
+ int retval;
+#ifdef TX_URB_MONITOR
+ unsigned char b_usb_if_num = 0;
+ int iRet = -1;
+#endif //#ifdef TX_URB_MONITOR
+
+ if (skb)
+ skb_tx_timestamp(skb);
+
+ // some devices want funky USB-level framing, for
+ // win32 driver (usually) and/or hardware quirks
+ if (info->tx_fixup) {
+ skb = info->tx_fixup (dev, skb, GFP_ATOMIC);
+ if (!skb) {
+ /* packet collected; minidriver waiting for more */
+ if (info->flags & FLAG_MULTI_PACKET)
+ goto not_drop;
+ netif_dbg(dev, tx_err, dev->net, "can't tx_fixup skb\n");
+ goto drop;
+ }
+ }
+ length = skb->len;
+
+ if (!(urb = usb_alloc_urb (0, GFP_ATOMIC))) {
+ netif_dbg(dev, tx_err, dev->net, "no urb\n");
+ goto drop;
+ }
+
+ entry = (struct skb_data *) skb->cb;
+ entry->urb = urb;
+ entry->dev = dev;
+ entry->length = length;
+
+ usb_fill_bulk_urb (urb, dev->udev, dev->out,
+ skb->data, skb->len, tx_complete, skb);
+
+ /* don't assume the hardware handles USB_ZERO_PACKET
+ * NOTE: strictly conforming cdc-ether devices should expect
+ * the ZLP here, but ignore the one-byte packet.
+ * NOTE2: CDC NCM specification is different from CDC ECM when
+ * handling ZLP/short packets, so cdc_ncm driver will make short
+ * packet itself if needed.
+ */
+ if (length % dev->maxpacket == 0) {
+ if (!(info->flags & FLAG_SEND_ZLP)) {
+ if (!(info->flags & FLAG_MULTI_PACKET)) {
+ urb->transfer_buffer_length++;
+ if (skb_tailroom(skb)) {
+ skb->data[skb->len] = 0;
+ __skb_put(skb, 1);
+ }
+ }
+ } else
+ urb->transfer_flags |= URB_ZERO_PACKET;
+ }
+
+ spin_lock_irqsave(&dev->txq.lock, flags);
+ retval = usb_autopm_get_interface_async(dev->intf);
+ if (retval < 0) {
+ spin_unlock_irqrestore(&dev->txq.lock, flags);
+ goto drop;
+ }
+
+#ifdef CONFIG_PM
+ /* if this triggers the device is still a sleep */
+ if (test_bit(EVENT_DEV_ASLEEP, &dev->flags)) {
+ /* transmission will be done in resume */
+ usb_anchor_urb(urb, &dev->deferred);
+ /* no use to process more packets */
+ netif_stop_queue(net);
+ usb_put_urb(urb);
+ spin_unlock_irqrestore(&dev->txq.lock, flags);
+ netdev_dbg(dev->net, "Delaying transmission for resumption\n");
+ goto deferred;
+ }
+#endif
+
+#ifdef TX_URB_MONITOR
+ iRet = get_usb_interface(urb, &b_usb_if_num);
+#endif //#ifdef TX_URB_MONITOR
+
+ switch ((retval = usb_submit_urb (urb, GFP_ATOMIC))) {
+ case -EPIPE:
+ netif_stop_queue (net);
+ usbnet_defer_kevent (dev, EVENT_TX_HALT);
+ usb_autopm_put_interface_async(dev->intf);
+ break;
+ default:
+ usb_autopm_put_interface_async(dev->intf);
+ netif_dbg(dev, tx_err, dev->net,
+ "tx: submit urb err %d\n", retval);
+ break;
+ case 0:
+ net->trans_start = jiffies;
+ __usbnet_queue_skb(&dev->txq, skb, tx_start);
+ if (dev->txq.qlen >= TX_QLEN (dev))
+ netif_stop_queue (net);
+ }
+
+#ifdef TX_URB_MONITOR
+ /*
+ * This can be called from here or from inside the
+ * case 0 in the above switch. There will be one less
+ * condition to check
+ */
+ /*
+ * Call URB_monitor() with true as the URB has been successfully
+ * submitted to the txq.
+ */
+ if ((URB_monitor) && (0==iRet) && (0==retval))
+ {
+ URB_monitor(true, b_usb_if_num);
+ }
+#endif //#ifdef TX_URB_MONITOR
+
+ spin_unlock_irqrestore (&dev->txq.lock, flags);
+
+ if (retval) {
+ netif_dbg(dev, tx_err, dev->net, "drop, code %d\n", retval);
+drop:
+ dev->net->stats.tx_dropped++;
+not_drop:
+ if (skb)
+ dev_kfree_skb_any (skb);
+ usb_free_urb (urb);
+ } else
+ netif_dbg(dev, tx_queued, dev->net,
+ "> tx, len %d, type 0x%x\n", length, skb->protocol);
+#ifdef CONFIG_PM
+deferred:
+#endif
+ return NETDEV_TX_OK;
+}
+#endif /* #if (LINUX_VERSION_CODE >= KERNEL_VERSION( 3,10,1 ) &&\
+ LINUX_VERSION_CODE <= KERNEL_VERSION( 3,10,39))*/
+
diff --git a/swi-drivers/src/GobiNet/usbnet_3_12_xx.c b/swi-drivers/src/GobiNet/usbnet_3_12_xx.c
new file mode 100644
index 0000000..0210517
--- /dev/null
+++ b/swi-drivers/src/GobiNet/usbnet_3_12_xx.c
@@ -0,0 +1,513 @@
+/*===========================================================================
+FILE:
+ usbnet_3_12_xx.c
+
+DESCRIPTION:
+ The default "usbnet_start_xmit" function is over-ridden by Sierra to provide the URB_Monitor
+ for Linux kernel 3.12.0 to 3.12.xx
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+===========================================================================*/
+
+//---------------------------------------------------------------------------
+// Include Files
+//---------------------------------------------------------------------------
+
+#include "Structs.h"
+#include "QMIDevice.h"
+#include "QMI.h"
+#include "gobi_usbnet.h"
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/module.h>
+#include <net/ip.h>
+
+#include <asm/siginfo.h> //siginfo
+#include <linux/rcupdate.h> //rcu_read_lock
+#include <linux/sched.h> //find_task_by_pid_type
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 3,12,0 ) &&\
+ LINUX_VERSION_CODE < KERNEL_VERSION( 3,13,00))
+#if 1
+void (*URB_monitor) (bool,unsigned char);
+EXPORT_SYMBOL(URB_monitor);
+#else
+/*
+ * Dummy function to test URB back-pressure. In actual implementation,
+ * customer will implement this function
+ */
+// Define PDN interfaced as per specific Sierra module PTS. The below are for MC73xx modules
+#define PDN1_INTERFACE 8
+#define PDN2_INTERFACE 10
+#define BACK_PRESSURE_WATERMARK 1//10
+static unsigned short urb_count_pdn1 = 0;
+static unsigned short urb_count_pdn2 = 0;
+void URB_monitor (bool isUpCount,unsigned char interface)
+{
+/* printk(KERN_WARNING "[%s] isUpCount %d, intf %d", \
+ __func__, isUpCount, interface);*/
+ if (isUpCount)
+ {
+ if (interface == PDN1_INTERFACE)
+ {
+ urb_count_pdn1++;
+ }
+ else if (interface == PDN2_INTERFACE)
+ {
+ urb_count_pdn2++;
+ }
+ else
+ {
+ // unknown interface. ignore or log as needed
+ }
+ }
+ else
+ {
+ if (interface == PDN1_INTERFACE)
+ {
+ urb_count_pdn1--;
+ }
+ else if (interface == PDN2_INTERFACE)
+ {
+ urb_count_pdn2--;
+ }
+ else
+ {
+ // unknown interface. ignore or log as needed
+ }
+ }
+ if (BACK_PRESSURE_WATERMARK <= urb_count_pdn1)
+ {
+ // Back pressure on PDN1
+ printk(KERN_WARNING "[%s] Backpressure %d on PDN1", \
+ __func__, urb_count_pdn1);
+ }
+ if (BACK_PRESSURE_WATERMARK <= urb_count_pdn2)
+ {
+ // Back pressure on PDN2
+ printk(KERN_WARNING "[%s] Backpressure %d on PDN2", \
+ __func__, urb_count_pdn2);
+ }
+}
+
+
+#endif // 0 End of dummy function
+
+// Get the USB interface from a usbnet pointer. The function return 0 on success, -1 on error
+__always_inline static int get_usb_interface_from_device (struct usbnet *dev, unsigned char *pb_usb_interface)
+{
+ int iRet = -1; // set to error by default
+
+ if ((NULL!=dev) && (NULL!=pb_usb_interface))
+ {
+ if ((NULL != dev->intf) &&
+ (NULL != dev->intf->cur_altsetting))
+ {
+ *pb_usb_interface = dev->intf->cur_altsetting->desc.bInterfaceNumber;
+ iRet = 0;
+ } // (NULL != dev->intf) && (NULL != dev->intf->cur_altsetting)
+ } //(NULL != pURB) && (NULL!=pb_usb_interface)
+ return iRet;
+}
+
+// Get the USB interface from a URB. The function return 0 on success, -1 on error
+__always_inline static int get_usb_interface (struct urb * pURB, unsigned char *pb_usb_interface)
+{
+ int iRet = -1; // set to error by default
+ struct sk_buff *skb = NULL;
+ struct skb_data *entry = NULL;
+
+ if ((NULL!=pURB) && (NULL!=pb_usb_interface))
+ {
+ skb = (struct sk_buff *) pURB->context;
+ if (NULL != skb)
+ {
+ entry = (struct skb_data *) skb->cb;
+ if (NULL != entry)
+ {
+ iRet = get_usb_interface_from_device (entry->dev, pb_usb_interface);
+ }
+ } // (NULL != skb)
+ } // (NULL != pURB) && (NULL!=pb_usb_interface)
+ return iRet;
+}
+
+// unlink pending rx/tx; completion handlers do all other cleanup
+
+static int unlink_urbs (struct usbnet *dev, struct sk_buff_head *q)
+{
+ unsigned long flags;
+ struct sk_buff *skb;
+ int count = 0;
+
+ spin_lock_irqsave (&q->lock, flags);
+ while (!skb_queue_empty(q)) {
+ struct skb_data *entry;
+ struct urb *urb;
+ int retval;
+
+ skb_queue_walk(q, skb) {
+ entry = (struct skb_data *) skb->cb;
+ if (entry->state != unlink_start)
+ goto found;
+ }
+ break;
+found:
+ entry->state = unlink_start;
+ urb = entry->urb;
+
+ /*
+ * Get reference count of the URB to avoid it to be
+ * freed during usb_unlink_urb, which may trigger
+ * use-after-free problem inside usb_unlink_urb since
+ * usb_unlink_urb is always racing with .complete
+ * handler(include defer_bh).
+ */
+ usb_get_urb(urb);
+ spin_unlock_irqrestore(&q->lock, flags);
+ // during some PM-driven resume scenarios,
+ // these (async) unlinks complete immediately
+ retval = usb_unlink_urb (urb);
+ if (retval != -EINPROGRESS && retval != 0)
+ netdev_dbg(dev->net, "unlink urb err, %d\n", retval);
+ else
+ count++;
+ usb_put_urb(urb);
+ spin_lock_irqsave(&q->lock, flags);
+ }
+ spin_unlock_irqrestore (&q->lock, flags);
+ return count;
+}
+
+
+void gobi_usbnet_tx_timeout_3_12_xx (struct net_device *net)
+{
+ struct usbnet *dev = netdev_priv(net);
+#ifdef TX_URB_MONITOR
+ int count = 0;
+ int iRet = -1;
+ unsigned char b_usb_if_num = 0;
+ // Get the USB interface
+ iRet = get_usb_interface_from_device (dev, &b_usb_if_num);
+
+ count = unlink_urbs (dev, &dev->txq);
+ tasklet_schedule (&dev->bh);
+
+ if ((URB_monitor) && (0==iRet))
+ {
+ while (count)
+ {
+ URB_monitor(false, b_usb_if_num);
+ count--;
+ }
+ }
+#else // TX_URB_MONITOR
+ unlink_urbs (dev, &dev->txq);
+ tasklet_schedule (&dev->bh);
+#endif // TX_URB_MONITOR
+ // FIXME: device recovery -- reset?
+}
+
+/* some LK 2.4 HCDs oopsed if we freed or resubmitted urbs from
+ * completion callbacks. 2.5 should have fixed those bugs...
+ */
+
+static enum skb_state defer_bh(struct usbnet *dev, struct sk_buff *skb,
+ struct sk_buff_head *list, enum skb_state state)
+{
+ unsigned long flags;
+ enum skb_state old_state;
+ struct skb_data *entry = (struct skb_data *) skb->cb;
+
+ spin_lock_irqsave(&list->lock, flags);
+ old_state = entry->state;
+ entry->state = state;
+ __skb_unlink(skb, list);
+ spin_unlock(&list->lock);
+ spin_lock(&dev->done.lock);
+ __skb_queue_tail(&dev->done, skb);
+ if (dev->done.qlen == 1)
+ tasklet_schedule(&dev->bh);
+ spin_unlock_irqrestore(&dev->done.lock, flags);
+ return old_state;
+}
+
+static void tx_complete (struct urb *urb)
+{
+ struct sk_buff *skb = (struct sk_buff *) urb->context;
+ struct skb_data *entry = (struct skb_data *) skb->cb;
+ struct usbnet *dev = entry->dev;
+
+#ifdef TX_URB_MONITOR
+ unsigned char b_usb_if_num = 0;
+ int iRet = get_usb_interface(urb, &b_usb_if_num);
+#endif //#ifdef TX_URB_MONITOR
+
+ if (urb->status == 0) {
+ if (!(dev->driver_info->flags & FLAG_MULTI_PACKET))
+ dev->net->stats.tx_packets++;
+ dev->net->stats.tx_bytes += entry->length;
+ } else {
+ dev->net->stats.tx_errors++;
+
+ switch (urb->status) {
+ case -EPIPE:
+ usbnet_defer_kevent (dev, EVENT_TX_HALT);
+ break;
+
+ /* software-driven interface shutdown */
+ case -ECONNRESET: // async unlink
+ case -ESHUTDOWN: // hardware gone
+ break;
+
+ // like rx, tx gets controller i/o faults during khubd delays
+ // and so it uses the same throttling mechanism.
+ case -EPROTO:
+ case -ETIME:
+ case -EILSEQ:
+ usb_mark_last_busy(dev->udev);
+ if (!timer_pending (&dev->delay)) {
+ mod_timer (&dev->delay,
+ jiffies + THROTTLE_JIFFIES);
+ netif_dbg(dev, link, dev->net,
+ "tx throttle %d\n", urb->status);
+ }
+ netif_stop_queue (dev->net);
+ break;
+ default:
+ netif_dbg(dev, tx_err, dev->net,
+ "tx err %d\n", entry->urb->status);
+ break;
+ }
+ }
+
+ usb_autopm_put_interface_async(dev->intf);
+ (void) defer_bh(dev, skb, &dev->txq, tx_done);
+
+#ifdef TX_URB_MONITOR
+ if ((URB_monitor) && (0==iRet))
+ {
+ URB_monitor(false, b_usb_if_num);
+ }
+#endif //#ifdef TX_URB_MONITOR
+
+}
+
+static int build_dma_sg(const struct sk_buff *skb, struct urb *urb)
+{
+ unsigned num_sgs, total_len = 0;
+ int i, s = 0;
+
+ num_sgs = skb_shinfo(skb)->nr_frags + 1;
+ if (num_sgs == 1)
+ return 0;
+
+ /* reserve one for zero packet */
+ urb->sg = kmalloc((num_sgs + 1) * sizeof(struct scatterlist),
+ GFP_ATOMIC);
+ if (!urb->sg)
+ return -ENOMEM;
+
+ urb->num_sgs = num_sgs;
+ sg_init_table(urb->sg, urb->num_sgs);
+
+ sg_set_buf(&urb->sg[s++], skb->data, skb_headlen(skb));
+ total_len += skb_headlen(skb);
+
+ for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
+ struct skb_frag_struct *f = &skb_shinfo(skb)->frags[i];
+
+ total_len += skb_frag_size(f);
+ sg_set_page(&urb->sg[i + s], f->page.p, f->size,
+ f->page_offset);
+ }
+ urb->transfer_buffer_length = total_len;
+
+ return 1;
+}
+
+
+/* The caller must hold list->lock */
+static void __usbnet_queue_skb(struct sk_buff_head *list,
+ struct sk_buff *newsk, enum skb_state state)
+{
+ struct skb_data *entry = (struct skb_data *) newsk->cb;
+
+ __skb_queue_tail(list, newsk);
+ entry->state = state;
+}
+
+netdev_tx_t gobi_usbnet_start_xmit_3_12_xx (struct sk_buff *skb,
+ struct net_device *net)
+{
+ struct usbnet *dev = netdev_priv(net);
+ int length;
+ struct urb *urb = NULL;
+ struct skb_data *entry;
+ struct driver_info *info = dev->driver_info;
+ unsigned long flags;
+ int retval;
+#ifdef TX_URB_MONITOR
+unsigned char b_usb_if_num = 0;
+int iRet = -1;
+#endif //#ifdef TX_URB_MONITOR
+
+ if (skb)
+ skb_tx_timestamp(skb);
+
+ // some devices want funky USB-level framing, for
+ // win32 driver (usually) and/or hardware quirks
+ if (info->tx_fixup) {
+ skb = info->tx_fixup (dev, skb, GFP_ATOMIC);
+ if (!skb) {
+ /* packet collected; minidriver waiting for more */
+ if (info->flags & FLAG_MULTI_PACKET)
+ goto not_drop;
+ netif_dbg(dev, tx_err, dev->net, "can't tx_fixup skb\n");
+ goto drop;
+ }
+ }
+
+ if (!(urb = usb_alloc_urb (0, GFP_ATOMIC))) {
+ netif_dbg(dev, tx_err, dev->net, "no urb\n");
+ goto drop;
+ }
+
+ entry = (struct skb_data *) skb->cb;
+ entry->urb = urb;
+ entry->dev = dev;
+
+ usb_fill_bulk_urb (urb, dev->udev, dev->out,
+ skb->data, skb->len, tx_complete, skb);
+
+ if (dev->can_dma_sg) {
+ if (build_dma_sg(skb, urb) < 0)
+ goto drop;
+ }
+ length = urb->transfer_buffer_length;
+
+ /* don't assume the hardware handles USB_ZERO_PACKET
+ * NOTE: strictly conforming cdc-ether devices should expect
+ * the ZLP here, but ignore the one-byte packet.
+ * NOTE2: CDC NCM specification is different from CDC ECM when
+ * handling ZLP/short packets, so cdc_ncm driver will make short
+ * packet itself if needed.
+ */
+ if (length % dev->maxpacket == 0) {
+ if (!(info->flags & FLAG_SEND_ZLP)) {
+ if (!(info->flags & FLAG_MULTI_PACKET)) {
+ length++;
+ if (skb_tailroom(skb) && !urb->num_sgs) {
+ skb->data[skb->len] = 0;
+ __skb_put(skb, 1);
+ } else if (urb->num_sgs)
+ sg_set_buf(&urb->sg[urb->num_sgs++],
+ dev->padding_pkt, 1);
+ }
+ } else
+ urb->transfer_flags |= URB_ZERO_PACKET;
+ }
+ entry->length = urb->transfer_buffer_length = length;
+
+ spin_lock_irqsave(&dev->txq.lock, flags);
+ retval = usb_autopm_get_interface_async(dev->intf);
+ if (retval < 0) {
+ spin_unlock_irqrestore(&dev->txq.lock, flags);
+ goto drop;
+ }
+
+#ifdef CONFIG_PM
+ /* if this triggers the device is still a sleep */
+ if (test_bit(EVENT_DEV_ASLEEP, &dev->flags))
+ {
+ /* transmission will be done in resume */
+ usb_anchor_urb(urb, &dev->deferred);
+ /* no use to process more packets */
+ netif_stop_queue(net);
+ usb_put_urb(urb);
+ spin_unlock_irqrestore(&dev->txq.lock, flags);
+ netdev_dbg(dev->net, "Delaying transmission for resumption\n");
+ goto deferred;
+ }
+#endif
+ #ifdef TX_URB_MONITOR
+ iRet = get_usb_interface(urb, &b_usb_if_num);
+ #endif //#ifdef TX_URB_MONITOR
+ switch ((retval = usb_submit_urb (urb, GFP_ATOMIC)))
+ {
+ case -EPIPE:
+ netif_stop_queue (net);
+ usbnet_defer_kevent (dev, EVENT_TX_HALT);
+ usb_autopm_put_interface_async(dev->intf);
+ break;
+ default:
+ usb_autopm_put_interface_async(dev->intf);
+ netif_dbg(dev, tx_err, dev->net,
+ "tx: submit urb err %d\n", retval);
+ break;
+ case 0:
+ net->trans_start = jiffies;
+ __usbnet_queue_skb(&dev->txq, skb, tx_start);
+ if (dev->txq.qlen >= TX_QLEN (dev))
+ netif_stop_queue (net);
+ }
+
+#ifdef TX_URB_MONITOR
+/*
+ * This can be called from here or from inside the
+ * case 0 in the above switch. There will be one less
+ * condition to check
+ */
+/*
+ * Call URB_monitor() with true as the URB has been successfully
+ * submitted to the txq.
+ */
+if ((0==iRet) && (0==retval))//(URB_monitor) &&
+{
+ URB_monitor(true, b_usb_if_num);
+}
+#endif //#ifdef TX_URB_MONITOR
+
+ spin_unlock_irqrestore (&dev->txq.lock, flags);
+
+ if (retval) {
+ netif_dbg(dev, tx_err, dev->net, "drop, code %d\n", retval);
+drop:
+ dev->net->stats.tx_dropped++;
+not_drop:
+ if (skb)
+ dev_kfree_skb_any (skb);
+ if (urb)
+ {
+ kfree(urb->sg);
+ usb_free_urb(urb);
+ }
+ } else
+ netif_dbg(dev, tx_queued, dev->net,
+ "> tx, len %d, type 0x%x\n", length, skb->protocol);
+#ifdef CONFIG_PM
+deferred:
+#endif
+ return NETDEV_TX_OK;
+}
+
+#endif
+/*********
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 3,12,0 ) &&\
+ LINUX_VERSION_CODE < KERNEL_VERSION( 3,13,00))
+
+***********/
+
+
+
+
diff --git a/swi-drivers/src/GobiNet/usbnet_4_4_xx.c b/swi-drivers/src/GobiNet/usbnet_4_4_xx.c
new file mode 100644
index 0000000..abb4ea9
--- /dev/null
+++ b/swi-drivers/src/GobiNet/usbnet_4_4_xx.c
@@ -0,0 +1,514 @@
+/*===========================================================================
+FILE:
+ usbnet_4_4_xx.c
+
+DESCRIPTION:
+ The default "usbnet_start_xmit" function is over-ridden by Sierra to provide the URB_Monitor
+ for Linux kernel 4.4.0 to 4.4.xx
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+===========================================================================*/
+
+#include <linux/version.h>
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 4,4,0 ) &&\
+ LINUX_VERSION_CODE < KERNEL_VERSION( 4,5,0))
+
+//---------------------------------------------------------------------------
+// Include Files
+//---------------------------------------------------------------------------
+
+#include "Structs.h"
+#include "QMIDevice.h"
+#include "QMI.h"
+#include "gobi_usbnet.h"
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/module.h>
+#include <net/ip.h>
+
+#include <asm/siginfo.h> //siginfo
+#include <linux/rcupdate.h> //rcu_read_lock
+#include <linux/sched.h> //find_task_by_pid_type
+#if 1
+void (*URB_monitor) (bool,unsigned char);
+EXPORT_SYMBOL(URB_monitor);
+#else
+/*
+ * Dummy function to test URB back-pressure. In actual implementation,
+ * customer will implement this function
+ */
+// Define PDN interfaced as per specific Sierra module PTS. The below are for MC73xx modules
+#define PDN1_INTERFACE 8
+#define PDN2_INTERFACE 10
+#define BACK_PRESSURE_WATERMARK 1//10
+static unsigned short urb_count_pdn1 = 0;
+static unsigned short urb_count_pdn2 = 0;
+void URB_monitor (bool isUpCount,unsigned char interface)
+{
+/* printk(KERN_WARNING "[%s] isUpCount %d, intf %d", \
+ __func__, isUpCount, interface);*/
+ if (isUpCount)
+ {
+ if (interface == PDN1_INTERFACE)
+ {
+ urb_count_pdn1++;
+ }
+ else if (interface == PDN2_INTERFACE)
+ {
+ urb_count_pdn2++;
+ }
+ else
+ {
+ // unknown interface. ignore or log as needed
+ }
+ }
+ else
+ {
+ if (interface == PDN1_INTERFACE)
+ {
+ urb_count_pdn1--;
+ }
+ else if (interface == PDN2_INTERFACE)
+ {
+ urb_count_pdn2--;
+ }
+ else
+ {
+ // unknown interface. ignore or log as needed
+ }
+ }
+ if (BACK_PRESSURE_WATERMARK <= urb_count_pdn1)
+ {
+ // Back pressure on PDN1
+ printk(KERN_WARNING "[%s] Backpressure %d on PDN1", \
+ __func__, urb_count_pdn1);
+ }
+ if (BACK_PRESSURE_WATERMARK <= urb_count_pdn2)
+ {
+ // Back pressure on PDN2
+ printk(KERN_WARNING "[%s] Backpressure %d on PDN2", \
+ __func__, urb_count_pdn2);
+ }
+}
+
+
+#endif // 0 End of dummy function
+
+// Get the USB interface from a usbnet pointer. The function return 0 on success, -1 on error
+__always_inline static int get_usb_interface_from_device (struct usbnet *dev, unsigned char *pb_usb_interface)
+{
+ int iRet = -1; // set to error by default
+
+ if ((NULL!=dev) && (NULL!=pb_usb_interface))
+ {
+ if ((NULL != dev->intf) &&
+ (NULL != dev->intf->cur_altsetting))
+ {
+ *pb_usb_interface = dev->intf->cur_altsetting->desc.bInterfaceNumber;
+ iRet = 0;
+ } // (NULL != dev->intf) && (NULL != dev->intf->cur_altsetting)
+ } //(NULL != pURB) && (NULL!=pb_usb_interface)
+ return iRet;
+}
+
+// Get the USB interface from a URB. The function return 0 on success, -1 on error
+__always_inline static int get_usb_interface (struct urb * pURB, unsigned char *pb_usb_interface)
+{
+ int iRet = -1; // set to error by default
+ struct sk_buff *skb = NULL;
+ struct skb_data *entry = NULL;
+
+ if ((NULL!=pURB) && (NULL!=pb_usb_interface))
+ {
+ skb = (struct sk_buff *) pURB->context;
+ if (NULL != skb)
+ {
+ entry = (struct skb_data *) skb->cb;
+ if (NULL != entry)
+ {
+ iRet = get_usb_interface_from_device (entry->dev, pb_usb_interface);
+ }
+ } // (NULL != skb)
+ } // (NULL != pURB) && (NULL!=pb_usb_interface)
+ return iRet;
+}
+
+// unlink pending rx/tx; completion handlers do all other cleanup
+
+static int unlink_urbs (struct usbnet *dev, struct sk_buff_head *q)
+{
+ unsigned long flags;
+ struct sk_buff *skb;
+ int count = 0;
+
+ spin_lock_irqsave (&q->lock, flags);
+ while (!skb_queue_empty(q)) {
+ struct skb_data *entry;
+ struct urb *urb;
+ int retval;
+
+ skb_queue_walk(q, skb) {
+ entry = (struct skb_data *) skb->cb;
+ if (entry->state != unlink_start)
+ goto found;
+ }
+ break;
+found:
+ entry->state = unlink_start;
+ urb = entry->urb;
+
+ /*
+ * Get reference count of the URB to avoid it to be
+ * freed during usb_unlink_urb, which may trigger
+ * use-after-free problem inside usb_unlink_urb since
+ * usb_unlink_urb is always racing with .complete
+ * handler(include defer_bh).
+ */
+ usb_get_urb(urb);
+ spin_unlock_irqrestore(&q->lock, flags);
+ // during some PM-driven resume scenarios,
+ // these (async) unlinks complete immediately
+ retval = usb_unlink_urb (urb);
+ if (retval != -EINPROGRESS && retval != 0)
+ netdev_dbg(dev->net, "unlink urb err, %d\n", retval);
+ else
+ count++;
+ usb_put_urb(urb);
+ spin_lock_irqsave(&q->lock, flags);
+ }
+ spin_unlock_irqrestore (&q->lock, flags);
+ return count;
+}
+
+
+void gobi_usbnet_tx_timeout_4_4_xx (struct net_device *net)
+{
+ struct usbnet *dev = netdev_priv(net);
+#ifdef TX_URB_MONITOR
+ int count = 0;
+ int iRet = -1;
+ unsigned char b_usb_if_num = 0;
+ // Get the USB interface
+ iRet = get_usb_interface_from_device (dev, &b_usb_if_num);
+
+ count = unlink_urbs (dev, &dev->txq);
+ tasklet_schedule (&dev->bh);
+
+ if ((URB_monitor) && (0==iRet))
+ {
+ while (count)
+ {
+ URB_monitor(false, b_usb_if_num);
+ count--;
+ }
+ }
+#else // TX_URB_MONITOR
+ unlink_urbs (dev, &dev->txq);
+ tasklet_schedule (&dev->bh);
+#endif // TX_URB_MONITOR
+ // FIXME: device recovery -- reset?
+}
+
+/* some LK 2.4 HCDs oopsed if we freed or resubmitted urbs from
+ * completion callbacks. 2.5 should have fixed those bugs...
+ */
+
+static enum skb_state defer_bh(struct usbnet *dev, struct sk_buff *skb,
+ struct sk_buff_head *list, enum skb_state state)
+{
+ unsigned long flags;
+ enum skb_state old_state;
+ struct skb_data *entry = (struct skb_data *) skb->cb;
+
+ spin_lock_irqsave(&list->lock, flags);
+ old_state = entry->state;
+ entry->state = state;
+ __skb_unlink(skb, list);
+ spin_unlock(&list->lock);
+ spin_lock(&dev->done.lock);
+ __skb_queue_tail(&dev->done, skb);
+ if (dev->done.qlen == 1)
+ tasklet_schedule(&dev->bh);
+ spin_unlock_irqrestore(&dev->done.lock, flags);
+ return old_state;
+}
+
+static void tx_complete (struct urb *urb)
+{
+ struct sk_buff *skb = (struct sk_buff *) urb->context;
+ struct skb_data *entry = (struct skb_data *) skb->cb;
+ struct usbnet *dev = entry->dev;
+
+#ifdef TX_URB_MONITOR
+ unsigned char b_usb_if_num = 0;
+ int iRet = get_usb_interface(urb, &b_usb_if_num);
+#endif //#ifdef TX_URB_MONITOR
+
+ if (urb->status == 0) {
+ if (!(dev->driver_info->flags & FLAG_MULTI_PACKET))
+ dev->net->stats.tx_packets++;
+ dev->net->stats.tx_bytes += entry->length;
+ } else {
+ dev->net->stats.tx_errors++;
+
+ switch (urb->status) {
+ case -EPIPE:
+ usbnet_defer_kevent (dev, EVENT_TX_HALT);
+ break;
+
+ /* software-driven interface shutdown */
+ case -ECONNRESET: // async unlink
+ case -ESHUTDOWN: // hardware gone
+ break;
+
+ // like rx, tx gets controller i/o faults during khubd delays
+ // and so it uses the same throttling mechanism.
+ case -EPROTO:
+ case -ETIME:
+ case -EILSEQ:
+ usb_mark_last_busy(dev->udev);
+ if (!timer_pending (&dev->delay)) {
+ mod_timer (&dev->delay,
+ jiffies + THROTTLE_JIFFIES);
+ netif_dbg(dev, link, dev->net,
+ "tx throttle %d\n", urb->status);
+ }
+ netif_stop_queue (dev->net);
+ break;
+ default:
+ netif_dbg(dev, tx_err, dev->net,
+ "tx err %d\n", entry->urb->status);
+ break;
+ }
+ }
+
+ usb_autopm_put_interface_async(dev->intf);
+ (void) defer_bh(dev, skb, &dev->txq, tx_done);
+
+#ifdef TX_URB_MONITOR
+ if ((URB_monitor) && (0==iRet))
+ {
+ URB_monitor(false, b_usb_if_num);
+ }
+#endif //#ifdef TX_URB_MONITOR
+
+}
+
+static int build_dma_sg(const struct sk_buff *skb, struct urb *urb)
+{
+ unsigned num_sgs, total_len = 0;
+ int i, s = 0;
+
+ num_sgs = skb_shinfo(skb)->nr_frags + 1;
+ if (num_sgs == 1)
+ return 0;
+
+ /* reserve one for zero packet */
+ urb->sg = kmalloc((num_sgs + 1) * sizeof(struct scatterlist),
+ GFP_ATOMIC);
+ if (!urb->sg)
+ return -ENOMEM;
+
+ urb->num_sgs = num_sgs;
+ sg_init_table(urb->sg, urb->num_sgs);
+
+ sg_set_buf(&urb->sg[s++], skb->data, skb_headlen(skb));
+ total_len += skb_headlen(skb);
+
+ for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
+ struct skb_frag_struct *f = &skb_shinfo(skb)->frags[i];
+
+ total_len += skb_frag_size(f);
+ sg_set_page(&urb->sg[i + s], f->page.p, f->size,
+ f->page_offset);
+ }
+ urb->transfer_buffer_length = total_len;
+
+ return 1;
+}
+
+
+/* The caller must hold list->lock */
+static void __usbnet_queue_skb(struct sk_buff_head *list,
+ struct sk_buff *newsk, enum skb_state state)
+{
+ struct skb_data *entry = (struct skb_data *) newsk->cb;
+
+ __skb_queue_tail(list, newsk);
+ entry->state = state;
+}
+
+netdev_tx_t gobi_usbnet_start_xmit_4_4_xx (struct sk_buff *skb,
+ struct net_device *net)
+{
+ struct usbnet *dev = netdev_priv(net);
+ int length;
+ struct urb *urb = NULL;
+ struct skb_data *entry;
+ struct driver_info *info = dev->driver_info;
+ unsigned long flags;
+ int retval;
+#ifdef TX_URB_MONITOR
+unsigned char b_usb_if_num = 0;
+int iRet = -1;
+#endif //#ifdef TX_URB_MONITOR
+
+ if (skb)
+ skb_tx_timestamp(skb);
+
+ // some devices want funky USB-level framing, for
+ // win32 driver (usually) and/or hardware quirks
+ if (info->tx_fixup) {
+ skb = info->tx_fixup (dev, skb, GFP_ATOMIC);
+ if (!skb) {
+ /* packet collected; minidriver waiting for more */
+ if (info->flags & FLAG_MULTI_PACKET)
+ goto not_drop;
+ netif_dbg(dev, tx_err, dev->net, "can't tx_fixup skb\n");
+ goto drop;
+ }
+ }
+
+ if (!(urb = usb_alloc_urb (0, GFP_ATOMIC))) {
+ netif_dbg(dev, tx_err, dev->net, "no urb\n");
+ goto drop;
+ }
+
+ entry = (struct skb_data *) skb->cb;
+ entry->urb = urb;
+ entry->dev = dev;
+
+ usb_fill_bulk_urb (urb, dev->udev, dev->out,
+ skb->data, skb->len, tx_complete, skb);
+
+ if (dev->can_dma_sg) {
+ if (build_dma_sg(skb, urb) < 0)
+ goto drop;
+ }
+ length = urb->transfer_buffer_length;
+
+ /* don't assume the hardware handles USB_ZERO_PACKET
+ * NOTE: strictly conforming cdc-ether devices should expect
+ * the ZLP here, but ignore the one-byte packet.
+ * NOTE2: CDC NCM specification is different from CDC ECM when
+ * handling ZLP/short packets, so cdc_ncm driver will make short
+ * packet itself if needed.
+ */
+ if (length % dev->maxpacket == 0) {
+ if (!(info->flags & FLAG_SEND_ZLP)) {
+ if (!(info->flags & FLAG_MULTI_PACKET)) {
+ length++;
+ if (skb_tailroom(skb) && !urb->num_sgs) {
+ skb->data[skb->len] = 0;
+ __skb_put(skb, 1);
+ } else if (urb->num_sgs)
+ sg_set_buf(&urb->sg[urb->num_sgs++],
+ dev->padding_pkt, 1);
+ }
+ } else
+ urb->transfer_flags |= URB_ZERO_PACKET;
+ }
+ entry->length = urb->transfer_buffer_length = length;
+
+ spin_lock_irqsave(&dev->txq.lock, flags);
+ retval = usb_autopm_get_interface_async(dev->intf);
+ if (retval < 0) {
+ spin_unlock_irqrestore(&dev->txq.lock, flags);
+ goto drop;
+ }
+
+#ifdef CONFIG_PM
+ /* if this triggers the device is still a sleep */
+ if (test_bit(EVENT_DEV_ASLEEP, &dev->flags))
+ {
+ /* transmission will be done in resume */
+ usb_anchor_urb(urb, &dev->deferred);
+ /* no use to process more packets */
+ netif_stop_queue(net);
+ usb_put_urb(urb);
+ spin_unlock_irqrestore(&dev->txq.lock, flags);
+ netdev_dbg(dev->net, "Delaying transmission for resumption\n");
+ goto deferred;
+ }
+#endif
+ #ifdef TX_URB_MONITOR
+ iRet = get_usb_interface(urb, &b_usb_if_num);
+ #endif //#ifdef TX_URB_MONITOR
+ switch ((retval = usb_submit_urb (urb, GFP_ATOMIC)))
+ {
+ case -EPIPE:
+ netif_stop_queue (net);
+ usbnet_defer_kevent (dev, EVENT_TX_HALT);
+ usb_autopm_put_interface_async(dev->intf);
+ break;
+ default:
+ usb_autopm_put_interface_async(dev->intf);
+ netif_dbg(dev, tx_err, dev->net,
+ "tx: submit urb err %d\n", retval);
+ break;
+ case 0:
+ net->trans_start = jiffies;
+ __usbnet_queue_skb(&dev->txq, skb, tx_start);
+ if (dev->txq.qlen >= TX_QLEN (dev))
+ netif_stop_queue (net);
+ }
+
+#ifdef TX_URB_MONITOR
+/*
+ * This can be called from here or from inside the
+ * case 0 in the above switch. There will be one less
+ * condition to check
+ */
+/*
+ * Call URB_monitor() with true as the URB has been successfully
+ * submitted to the txq.
+ */
+if ((0==iRet) && (0==retval))//(URB_monitor) &&
+{
+ URB_monitor(true, b_usb_if_num);
+}
+#endif //#ifdef TX_URB_MONITOR
+
+ spin_unlock_irqrestore (&dev->txq.lock, flags);
+
+ if (retval) {
+ netif_dbg(dev, tx_err, dev->net, "drop, code %d\n", retval);
+drop:
+ dev->net->stats.tx_dropped++;
+not_drop:
+ if (skb)
+ dev_kfree_skb_any (skb);
+ if (urb)
+ {
+ kfree(urb->sg);
+ usb_free_urb(urb);
+ }
+ } else
+ netif_dbg(dev, tx_queued, dev->net,
+ "> tx, len %d, type 0x%x\n", length, skb->protocol);
+#ifdef CONFIG_PM
+deferred:
+#endif
+ return NETDEV_TX_OK;
+}
+
+#endif
+/*********
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 4,4,0 ) &&\
+ LINUX_VERSION_CODE < KERNEL_VERSION( 4,5,0))
+
+***********/
+
+
+
+
diff --git a/swi-drivers/src/GobiSerial/GobiSerial.c b/swi-drivers/src/GobiSerial/GobiSerial.c
new file mode 100644
index 0000000..b5b560e
--- /dev/null
+++ b/swi-drivers/src/GobiSerial/GobiSerial.c
@@ -0,0 +1,1479 @@
+/*===========================================================================
+FILE:
+ GobiSerial.c
+
+DESCRIPTION:
+ Linux Qualcomm Serial USB driver Implementation
+
+PUBLIC DRIVER FUNCTIONS:
+ GobiProbe
+ GobiOpen
+ GobiClose
+ GobiReadBulkCallback
+ GobiSerialSuspend
+ GobiSerialResume (if kernel is less than 2.6.24)
+
+Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of Code Aurora Forum nor
+ the names of its contributors may be used to endorse or promote
+ products derived from this software without specific prior written
+ permission.
+
+Alternatively, provided that this notice is retained in full, this software
+may be relicensed by the recipient under the terms of the GNU General Public
+License version 2 ("GPL") and only version 2, in which case the provisions of
+the GPL apply INSTEAD OF those given above. If the recipient relicenses the
+software under the GPL, then the identification text in the MODULE_LICENSE
+macro must be changed to reflect "GPLv2" instead of "Dual BSD/GPL". Once a
+recipient changes the license terms to the GPL, subsequent recipients shall
+not relicense under alternate licensing terms, including the BSD or dual
+BSD/GPL terms. In addition, the following license statement immediately
+below and between the words START and END shall also then apply when this
+software is relicensed under the GPL:
+
+START
+
+This program is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License version 2 and only version 2 as
+published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+details.
+
+You should have received a copy of the GNU General Public License along with
+this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+END
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+==========================================================================*/
+//---------------------------------------------------------------------------
+// Include Files
+//---------------------------------------------------------------------------
+
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/serial.h>
+
+/* Determine if we are in a particular kernel series */
+#define KERNEL_SERIES(x, y) \
+ ((LINUX_VERSION_CODE >> 8) == (KERNEL_VERSION( x,y,0 ) >> 8))
+
+//---------------------------------------------------------------------------
+// Global variables and definitions
+//---------------------------------------------------------------------------
+
+// Version Information
+#define DRIVER_VERSION "2017-04-05/SWI_2.28"
+#define DRIVER_AUTHOR "Qualcomm Innovation Center"
+#define DRIVER_DESC "GobiSerial"
+
+#define NUM_BULK_EPS 1
+#define MAX_BULK_EPS 6
+
+#define SWIMS_USB_REQUEST_SetHostPower 0x09
+#define SET_CONTROL_LINE_STATE_REQUEST_TYPE 0x21
+#define SET_CONTROL_LINE_STATE_REQUEST 0x22
+#define CONTROL_DTR 0x01
+#define CONTROL_RTS 0x02
+
+
+// Debug flag
+static int debug;
+// flow control flag
+static int flow_control = 1;
+// allow port open to success even when GPS control message failed
+static int ignore_gps_start_error = 1;
+#define OPEN_GPS_DELAY_IN_SECOND 10
+// Open GPS port delay
+static int delay_open_gps_port = OPEN_GPS_DELAY_IN_SECOND;
+// Number of serial interfaces
+static int nNumInterfaces;
+
+// Global pointer to usb_serial_generic_close function
+// This function is not exported, which is why we have to use a pointer
+// instead of just calling it.
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,26 ))
+ void (* gpClose)(
+ struct usb_serial_port *,
+ struct file * );
+#elif (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,30 ))
+ void (* gpClose)(
+ struct tty_struct *,
+ struct usb_serial_port *,
+ struct file * );
+#else // > 2.6.30
+ void (* gpClose)( struct usb_serial_port * );
+#endif
+
+// DBG macro
+#define DBG( format, arg... ) \
+ if (debug == 1)\
+ { \
+ printk( KERN_INFO "GobiSerial::%s " format, __FUNCTION__, ## arg ); \
+ } \
+
+struct gobi_serial_intf_private {
+ spinlock_t susp_lock;
+ unsigned int suspended:1;
+};
+
+/*=========================================================================*/
+// Function Prototypes
+/*=========================================================================*/
+
+// Attach to correct interfaces
+static int GobiProbe(
+ struct usb_serial * pSerial,
+ const struct usb_device_id * pID );
+
+// Start GPS if GPS port, run usb_serial_generic_open
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,26 ))
+ int GobiOpen(
+ struct usb_serial_port * pPort,
+ struct file * pFilp );
+#elif (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,31 ))
+ int GobiOpen(
+ struct tty_struct * pTTY,
+ struct usb_serial_port * pPort,
+ struct file * pFilp );
+#else // > 2.6.31
+ int GobiOpen(
+ struct tty_struct * pTTY,
+ struct usb_serial_port * pPort );
+#endif
+
+// Stop GPS if GPS port, run usb_serial_generic_close
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,26 ))
+ void GobiClose(
+ struct usb_serial_port *,
+ struct file * );
+#elif (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,30 ))
+ void GobiClose(
+ struct tty_struct *,
+ struct usb_serial_port *,
+ struct file * );
+#else // > 2.6.30
+ void GobiClose( struct usb_serial_port * );
+#endif
+
+// Read data from USB, push to TTY and user space
+static void GobiReadBulkCallback( struct urb * pURB );
+
+// Set reset_resume flag
+int GobiSerialSuspend(
+ struct usb_interface * pIntf,
+ pm_message_t powerEvent );
+int GobiUSBSerialSuspend(struct usb_serial *serial, pm_message_t message);
+int GobiUSBSerialResume(struct usb_serial *serial);
+int GobiUSBSerialResetResume(struct usb_serial *serial);
+void GobiUSBSerialDisconnect(struct usb_serial * serial);
+
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,23 ))
+
+// Restart URBs killed during usb_serial_suspend
+int GobiSerialResume( struct usb_interface * pIntf );
+
+#endif
+void stop_read_write_urbs(struct usb_serial *serial);
+
+
+#define MDM9X15_DEVICE(vend, prod) \
+ USB_DEVICE(vend, prod), \
+ .driver_info = BIT(1) | BIT(8) | BIT(10) | BIT(11)
+
+#define G3K_DEVICE(vend, prod) \
+ USB_DEVICE(vend, prod), \
+ .driver_info = BIT(0)
+
+#if ((KERNEL_SERIES( 3,4 ) && LINUX_VERSION_CODE >= KERNEL_VERSION( 3,4,34 )) || \
+ (KERNEL_SERIES( 3,2 ) && LINUX_VERSION_CODE >= KERNEL_VERSION( 3,2,0 )) || \
+ (KERNEL_SERIES( 3,5 ) && LINUX_VERSION_CODE >= KERNEL_VERSION( 3,5,0 )) || \
+ (KERNEL_SERIES( 3,7 ) && LINUX_VERSION_CODE >= KERNEL_VERSION( 3,7,10 )) || \
+ (KERNEL_SERIES( 3,8 ) && LINUX_VERSION_CODE >= KERNEL_VERSION( 3,8,1) ) || \
+ (LINUX_VERSION_CODE >= KERNEL_VERSION( 3,8,1) ))
+/* workaround for upstream commit b2ca699076573c94fee9a73cb0d8645383b602a0 */
+#warning "Assuming disc_mutex is locked external to the module"
+static inline void Gobi_lock_disc_mutex(struct usb_serial *serial) {
+ if (LINUX_VERSION_CODE < KERNEL_VERSION( 3,12,0) )
+ {
+ WARN_ON(!mutex_is_locked(&serial->disc_mutex));
+ }
+}
+static inline void Gobi_unlock_disc_mutex(struct usb_serial *serial) {}
+#else
+/* use the legacy method of locking disc_mutex in this driver */
+#warning "Using legacy method of locking disc_mutex"
+static inline void Gobi_lock_disc_mutex(struct usb_serial *serial) {
+ mutex_lock(&serial->disc_mutex);
+}
+static inline void Gobi_unlock_disc_mutex(struct usb_serial *serial) {
+ mutex_unlock(&serial->disc_mutex);
+}
+#endif
+
+/*=========================================================================*/
+// Qualcomm Gobi 3000 VID/PIDs
+/*=========================================================================*/
+static struct usb_device_id GobiVIDPIDTable[] =
+{
+ { USB_DEVICE(0x05c6, 0x920c) }, // Gobi 3000 QDL
+ { USB_DEVICE(0x05c6, 0x920d) }, // Gobi 3000 Composite
+ /* Sierra Wireless QMI VID/PID */
+ { USB_DEVICE(0x1199, 0x68A2),
+ .driver_info = BIT(8) | BIT(19) | BIT(20) |
+ BIT(10) | BIT(11) /* in case a MDM9x15 switched to 0x68a2 */
+ },
+ /* Sierra Wireless QMI MC78/WP7/AR7 */
+ { USB_DEVICE(0x1199, 0x68C0),
+ /* blacklist the interface */
+ .driver_info = BIT(1) | BIT(5) | BIT(6) | BIT(8) | BIT(10) | BIT(11) | BIT(12) | BIT(13)
+ },
+
+ /* Sierra Wireless QMI MC74xx/EM74xx */
+ { USB_DEVICE(0x1199, 0x9071),
+ /* blacklist the interface */
+ .driver_info = BIT(1) | BIT(4) | BIT(5) | BIT(6) | BIT(8) | BIT(10) | BIT(11) | BIT(12) | BIT(13)
+ },
+
+ /* Sierra Wireless QMI MC74xx/EM74xx */
+ { USB_DEVICE(0x1199, 0x9070),
+ /* blacklist the interface */
+ .driver_info = BIT(1) | BIT(4) | BIT(5) | BIT(6) | BIT(8) | BIT(10) | BIT(11) | BIT(12) | BIT(13)
+ },
+
+ /* Sierra Wireless QMI AR759x */
+ { USB_DEVICE(0x1199, 0x9100),
+ /* blacklist the interface */
+ .driver_info = BIT(1) | BIT(5) | BIT(6) | BIT(8) | BIT(10) | BIT(11) | BIT(12) | BIT(13)
+ },
+
+ /* Sierra Wireless QMI AR758x */
+ { USB_DEVICE(0x1199, 0x9102),
+ /* blacklist the interface */
+ .driver_info = BIT(1) | BIT(5) | BIT(6) | BIT(8) | BIT(10) | BIT(11) | BIT(12) | BIT(13)
+ },
+
+ /* Sierra Wireless QMI AR759x */
+ { USB_DEVICE(0x1199, 0x9110),
+ /* blacklist the interface */
+ .driver_info = BIT(1) | BIT(5) | BIT(6) | BIT(8) | BIT(10) | BIT(11) | BIT(12) | BIT(13)
+ },
+
+ {G3K_DEVICE(0x1199, 0x9010)},
+ {G3K_DEVICE(0x1199, 0x9011)},
+ {G3K_DEVICE(0x1199, 0x9012)},
+ {G3K_DEVICE(0x1199, 0x9013)},
+ {G3K_DEVICE(0x1199, 0x9014)},
+ {G3K_DEVICE(0x1199, 0x9015)},
+ {G3K_DEVICE(0x1199, 0x9018)},
+ {G3K_DEVICE(0x1199, 0x9019)},
+ {G3K_DEVICE(0x03F0, 0x361D)},
+ {G3K_DEVICE(0x03F0, 0x371D)},
+
+ {MDM9X15_DEVICE(0x1199, 0x9040)},
+ {MDM9X15_DEVICE(0x1199, 0x9041)},
+ {MDM9X15_DEVICE(0x1199, 0x9051)},
+ {MDM9X15_DEVICE(0x1199, 0x9053)},
+ {MDM9X15_DEVICE(0x1199, 0x9054)},
+ {MDM9X15_DEVICE(0x1199, 0x9055)},
+ {MDM9X15_DEVICE(0x1199, 0x9056)},
+ {MDM9X15_DEVICE(0x1199, 0x9060)},
+ {MDM9X15_DEVICE(0x1199, 0x9061)},
+
+ { } // Terminating entry
+};
+MODULE_DEVICE_TABLE( usb, GobiVIDPIDTable );
+
+enum {
+ eSendUnknown=-1,
+ eSendStart=0,
+ eSendEnd=1,
+};
+/* per port private data */
+struct sierra_port_private {
+ /* Settings for the port */
+ int rts_state; /* Handshaking pins (outputs) */
+ int dtr_state;
+ int isClosing;
+ int iGPSStartState;
+ unsigned long ulExpires;
+};
+
+int gobi_usb_serial_generic_resume(struct usb_interface *intf)
+{
+ struct usb_serial *serial = usb_get_intfdata(intf);
+ return usb_serial_generic_resume(serial);
+}
+
+/*=========================================================================*/
+// Struct usb_serial_driver
+// Driver structure we register with the USB core
+/*=========================================================================*/
+static struct usb_driver GobiDriver =
+{
+ .name = "GobiSerial",
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 3,5,0 ))
+ .probe = usb_serial_probe,
+ .disconnect = usb_serial_disconnect,
+#endif
+ .id_table = GobiVIDPIDTable,
+#ifdef CONFIG_PM
+ .suspend = usb_serial_suspend,
+ .resume = gobi_usb_serial_generic_resume,
+ .reset_resume = gobi_usb_serial_generic_resume,
+ .supports_autosuspend = true,
+#else
+ .suspend = NULL,
+ .resume = NULL,
+ .supports_autosuspend = false,
+#endif
+};
+
+static int Gobi_calc_interface(struct usb_serial *serial)
+{
+ int interface;
+ struct usb_interface *p_interface;
+ struct usb_host_interface *p_host_interface;
+ dev_dbg(&serial->dev->dev, "%s\n", __func__);
+
+ /* Get the interface structure pointer from the serial struct */
+ p_interface = serial->interface;
+
+ /* Get a pointer to the host interface structure */
+ p_host_interface = p_interface->cur_altsetting;
+
+ /* read the interface descriptor for this active altsetting
+ * to find out the interface number we are on
+ */
+ interface = p_host_interface->desc.bInterfaceNumber;
+
+ return interface;
+}
+
+static int Gobi_send_setup(struct usb_serial_port *port)
+{
+ struct usb_serial *serial = port->serial;
+ struct sierra_port_private *portdata;
+ __u16 interface = 0;
+ int val = 0;
+ int retval;
+
+ dev_dbg(&port->dev, "%s\n", __func__);
+
+ portdata = usb_get_serial_port_data(port);
+
+ if (portdata->dtr_state)
+ val |= CONTROL_DTR;
+ if (portdata->rts_state)
+ val |= CONTROL_RTS;
+
+ /* obtain interface for usb control message below */
+ if (serial->num_ports == 1) {
+ interface = Gobi_calc_interface(serial);
+ }
+ else {
+ dev_err(&port->dev,
+ "flow control is not supported for %d serial port\n",
+ serial->num_ports);
+ return -ENODEV;
+ }
+
+ retval = usb_autopm_get_interface(serial->interface);
+ if (retval < 0)
+ {
+ return retval;
+ }
+
+ retval = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0),
+ SET_CONTROL_LINE_STATE_REQUEST,
+ SET_CONTROL_LINE_STATE_REQUEST_TYPE,
+ val, interface, NULL, 0, USB_CTRL_SET_TIMEOUT);
+ usb_autopm_put_interface(serial->interface);
+
+ return retval;
+}
+
+static void Gobi_dtr_rts(struct usb_serial_port *port, int on)
+{
+ struct usb_serial *serial = port->serial;
+ struct sierra_port_private *portdata;
+
+ portdata = usb_get_serial_port_data(port);
+ portdata->rts_state = on;
+ portdata->dtr_state = on;
+
+ /* only send down the usb control message if enabled */
+ if (serial->dev && flow_control) {
+ Gobi_lock_disc_mutex(serial);
+ if (!serial->disconnected)
+ {
+ Gobi_send_setup(port);
+ }
+ Gobi_unlock_disc_mutex(serial);
+ }
+}
+
+/*
+ * swiState
+ * 0x0000 Host device is awake
+ * 0x0001 Host device is suspended
+ */
+static int set_power_state(struct usb_device *udev, __u16 swiState)
+{
+ int result;
+ dev_dbg(&udev->dev, "%s\n", __func__);
+ result = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ SWIMS_USB_REQUEST_SetHostPower, /* __u8 request */
+ USB_TYPE_VENDOR, /* __u8 request type */
+ swiState, /* __u16 value */
+ 0, /* __u16 index */
+ NULL, /* void *data */
+ 0, /* __u16 size */
+ USB_CTRL_SET_TIMEOUT); /* int timeout */
+ return result;
+}
+
+static int Gobi_startup(struct usb_serial *serial)
+{
+ struct usb_serial_port *port = NULL;
+ struct sierra_port_private *portdata = NULL;
+ int i;
+
+ dev_dbg(&serial->dev->dev, "%s\n", __func__);
+
+ if (serial->num_ports) {
+ /* Note: One big piece of memory is allocated for all ports
+ * private data in one shot. This memory is split into equal
+ * pieces for each port.
+ */
+ portdata = (struct sierra_port_private *)kzalloc
+ (sizeof(*portdata) * serial->num_ports, GFP_KERNEL);
+ if (!portdata) {
+ dev_dbg(&serial->dev->dev, "%s: No memory!\n", __func__);
+ return -ENOMEM;
+ }
+ }
+
+ /* Now setup per port private data */
+ for (i = 0; i < serial->num_ports; i++, portdata++) {
+ struct sierra_port_private *privatedata;
+ privatedata = portdata;
+ privatedata->iGPSStartState = eSendUnknown;
+ privatedata->ulExpires = jiffies + msecs_to_jiffies(delay_open_gps_port*1000);
+ port = serial->port[i];
+
+ /* Set the port private data pointer */
+ usb_set_serial_port_data(port, portdata);
+ }
+
+ return 0;
+}
+
+static void Gobi_release(struct usb_serial *serial)
+{
+ int i;
+ struct usb_serial_port *port;
+ struct sierra_intf_private *intfdata = serial->private;
+
+ dev_dbg(&serial->dev->dev, "%s\n", __func__);
+
+ stop_read_write_urbs(serial);
+
+ if (serial->num_ports > 0) {
+ port = serial->port[0];
+ if (port)
+ {
+ /* Note: The entire piece of memory that was allocated
+ * in the startup routine can be released by passing
+ * a pointer to the beginning of the piece.
+ * This address corresponds to the address of the chunk
+ * that was given to port 0.
+ */
+ kfree(usb_get_serial_port_data(port));
+ }
+ }
+
+ for (i = 0; i < serial->num_ports; ++i) {
+ port = serial->port[i];
+ if (!port)
+ {
+ continue;
+ }
+ usb_set_serial_port_data(port, NULL);
+ }
+ kfree(intfdata);
+}
+
+/*=========================================================================*/
+// Struct usb_serial_driver
+/*=========================================================================*/
+static struct usb_serial_driver gGobiDevice =
+{
+ .driver =
+ {
+ .owner = THIS_MODULE,
+ .name = "GobiSerial driver",
+ },
+ .description = "GobiSerial",
+ .id_table = GobiVIDPIDTable,
+ .usb_driver = &GobiDriver,
+ .num_ports = NUM_BULK_EPS,
+ .probe = GobiProbe,
+ .open = GobiOpen,
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,25 ))
+ .num_interrupt_in = NUM_DONT_CARE,
+ .num_bulk_in = 1,
+ .num_bulk_out = 1,
+#endif
+
+ /* TODO PowerPC RD1020DB kernel 3.0 support
+ */
+ /* register read_bulk_callback in order to resume upon -EPROTO
+ */
+ .read_bulk_callback = GobiReadBulkCallback,
+ .dtr_rts = Gobi_dtr_rts,
+ .attach = Gobi_startup,
+ .release = Gobi_release,
+ .disconnect = GobiUSBSerialDisconnect,
+#ifdef CONFIG_PM
+ .suspend = GobiUSBSerialSuspend,
+ .resume = GobiUSBSerialResume,
+ #if (LINUX_VERSION_CODE >= KERNEL_VERSION( 3,5,0 ))
+ .reset_resume = GobiUSBSerialResetResume,
+ #endif
+ #endif
+};
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 3,4,0 ))
+static struct usb_serial_driver * const serial_drivers[] = {
+ &gGobiDevice, NULL
+};
+#endif
+
+//---------------------------------------------------------------------------
+// USB serial core overridding Methods
+//---------------------------------------------------------------------------
+/*===========================================================================
+METHOD:
+ GobiProbe (Free Method)
+
+DESCRIPTION:
+ Attach to correct interfaces
+
+PARAMETERS:
+ pSerial [ I ] - Serial structure
+ pID [ I ] - VID PID table
+
+RETURN VALUE:
+ int - negative error code on failure
+ zero on success
+===========================================================================*/
+static int GobiProbe(
+ struct usb_serial * pSerial,
+ const struct usb_device_id * pID )
+{
+ // Assume failure
+ int nRetval = -ENODEV;
+ int nInterfaceNum;
+ struct usb_host_endpoint * pEndpoint;
+ int endpointIndex;
+ int numEndpoints;
+ struct gobi_serial_intf_private *intfdata;
+
+ DBG( "\n" );
+
+ // Test parameters
+ if ( (pSerial == NULL)
+ || (pSerial->dev == NULL)
+ || (pSerial->dev->actconfig == NULL)
+ || (pSerial->interface == NULL)
+ || (pSerial->interface->cur_altsetting == NULL)
+ || (pSerial->type == NULL) )
+ {
+ DBG( "invalid parameter\n" );
+ return -EINVAL;
+ }
+
+
+ intfdata = kzalloc(sizeof(*intfdata), GFP_KERNEL);
+ if (!intfdata)
+ return -ENOMEM;
+
+ spin_lock_init(&intfdata->susp_lock);
+
+ usb_set_serial_data(pSerial, intfdata);
+
+ nNumInterfaces = pSerial->dev->actconfig->desc.bNumInterfaces;
+ DBG( "Num Interfaces = %d\n", nNumInterfaces );
+ nInterfaceNum = pSerial->interface->cur_altsetting->desc.bInterfaceNumber;
+ DBG( "This Interface = %d\n", nInterfaceNum );
+ #ifdef CONFIG_PM
+ pSerial->dev->reset_resume = 0;
+ #endif
+
+ if (nNumInterfaces == 1)
+ {
+ // QDL mode?
+ if ((nInterfaceNum == 0) || (nInterfaceNum == 1))
+ {
+ DBG( "QDL port found\n" );
+ nRetval = usb_set_interface( pSerial->dev,
+ nInterfaceNum,
+ 0 );
+ if (nRetval < 0)
+ {
+ DBG( "Could not set interface, error %d\n", nRetval );
+ }
+ }
+ else
+ {
+ DBG( "Incorrect QDL interface number\n" );
+ }
+ }
+ else if (nNumInterfaces > 1)
+ {
+ /* Composite mode */
+ if (pSerial->interface->cur_altsetting->desc.bInterfaceClass != USB_CLASS_VENDOR_SPEC )
+ {
+ DBG( "Ignoring non vendor class interface #%d\n", nInterfaceNum );
+ return -ENODEV;
+ }
+ else if (pID->driver_info &&
+ test_bit(nInterfaceNum, &pID->driver_info)) {
+ DBG( "Ignoring blacklisted interface #%d\n", nInterfaceNum );
+ return -ENODEV;
+ }
+ else
+ {
+ nRetval = usb_set_interface( pSerial->dev,
+ nInterfaceNum,
+ 0 );
+ if (nRetval < 0)
+ {
+ DBG( "Could not set interface, error %d\n", nRetval );
+ }
+
+ // Check for recursion
+ if (pSerial->type->close != GobiClose)
+ {
+ // Store usb_serial_generic_close in gpClose
+ gpClose = pSerial->type->close;
+ pSerial->type->close = GobiClose;
+ }
+ }
+ }
+ if (nRetval == 0 && nNumInterfaces > 1 )
+ {
+ // Clearing endpoint halt is a magic handshake that brings
+ // the device out of low power (airplane) mode
+ // NOTE: FCC verification should be done before this, if required
+ numEndpoints = pSerial->interface->cur_altsetting
+ ->desc.bInterfaceNumber;
+
+ for (endpointIndex = 0; endpointIndex < numEndpoints; endpointIndex++)
+ {
+ pEndpoint = pSerial->interface->cur_altsetting->endpoint
+ + endpointIndex;
+
+ if (pEndpoint != NULL
+ && usb_endpoint_dir_out( &pEndpoint->desc ) == true)
+ {
+ int pipe = usb_sndbulkpipe( pSerial->dev,
+ pEndpoint->desc.bEndpointAddress );
+ nRetval = usb_clear_halt( pSerial->dev, pipe );
+
+ // Should only be one
+ break;
+ }
+ }
+ }
+
+ return nRetval;
+}
+
+/*===========================================================================
+METHOD:
+ IsGPSPort (Free Method)
+
+DESCRIPTION:
+ Determines whether the interface is GPS port
+
+PARAMETERS:
+ pPort [ I ] - USB serial port structure
+
+RETURN VALUE:
+ bool- true if this is a GPS port
+ - false otherwise
+===========================================================================*/
+bool IsGPSPort(struct usb_serial_port * pPort )
+{
+ DBG( "Product=0x%x, Interface=0x%x\n",
+ cpu_to_le16(pPort->serial->dev->descriptor.idProduct),
+ pPort->serial->interface->cur_altsetting->desc.bInterfaceNumber);
+
+ switch (cpu_to_le16(pPort->serial->dev->descriptor.idProduct))
+ {
+ case 0x68A2: /* Sierra Wireless QMI */
+ case 0x68C0:
+ case 0x9041:
+ case 0x9071:
+ case 0x9070:
+ case 0x9100:
+ case 0x9102:
+ case 0x9110:
+ if (pPort->serial->interface->cur_altsetting->desc.bInterfaceNumber == 2)
+ return true;
+ break;
+
+ case 0x9011: /* Sierra Wireless G3K */
+ case 0x9013: /* Sierra Wireless G3K */
+ case 0x9015: /* Sierra Wireless G3K */
+ case 0x9019: /* Sierra Wireless G3K */
+ case 0x371D: /* G3K */
+ if (pPort->serial->interface->cur_altsetting->desc.bInterfaceNumber == 3)
+ return true;
+ break;
+
+ default:
+ return false;
+ break;
+ }
+ return false;
+}
+
+/*===========================================================================
+METHOD:
+ GobiOpen (Free Method)
+
+DESCRIPTION:
+ Start GPS if GPS port, run usb_serial_generic_open
+
+PARAMETERS:
+ pTTY [ I ] - TTY structure (only on kernels <= 2.6.26)
+ pPort [ I ] - USB serial port structure
+ pFilp [ I ] - File structure (only on kernels <= 2.6.31)
+
+RETURN VALUE:
+ int - zero for success
+ - negative errno on error
+===========================================================================*/
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,26 ))
+int GobiOpen(
+ struct usb_serial_port * pPort,
+ struct file * pFilp )
+#elif (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,31 ))
+int GobiOpen(
+ struct tty_struct * pTTY,
+ struct usb_serial_port * pPort,
+ struct file * pFilp )
+#else // > 2.6.31
+int GobiOpen(
+ struct tty_struct * pTTY,
+ struct usb_serial_port * pPort )
+#endif
+{
+ struct sierra_port_private *portdata;
+ const char startMessage[] = "$GPS_START";
+ int nResult;
+ int bytesWrote;
+
+ DBG( "\n" );
+
+ portdata = usb_get_serial_port_data(pPort);
+ portdata->isClosing = 0;
+
+ // Test parameters
+ if ( (pPort == NULL)
+ || (pPort->serial == NULL)
+ || (pPort->serial->dev == NULL)
+ || (pPort->serial->interface == NULL)
+ || (pPort->serial->interface->cur_altsetting == NULL) )
+ {
+ DBG( "invalid parameter\n" );
+ return -EINVAL;
+ }
+
+ // Is this the GPS port?
+ if ((IsGPSPort(pPort)) == true)
+ {
+ int count = 0;
+ if(portdata->iGPSStartState == eSendUnknown)
+ {
+ if(jiffies<portdata->ulExpires)
+ {
+ unsigned long diff = jiffies_to_msecs(portdata->ulExpires-jiffies);
+ DBG("DELAY %lu msec\n",diff);
+ if(diff > 0)
+ msleep(diff);
+ DBG("DELAY FINISH\n");
+ }
+ else
+ {
+ DBG("NON DELAY OPEN!\n" );
+ }
+ }
+ portdata->iGPSStartState = eSendStart;
+ DBG( "GPS Port detected! send GPS_START!\n" );
+ // Send startMessage, USB_CTRL_SET_TIMEOUT timeout
+ do
+ {
+ nResult = usb_bulk_msg( pPort->serial->dev,
+ usb_sndbulkpipe( pPort->serial->dev,
+ pPort->bulk_out_endpointAddress ),
+ (void *)&startMessage[0],
+ sizeof( startMessage ),
+ &bytesWrote,
+ USB_CTRL_SET_TIMEOUT );
+ if(nResult!=0)
+ {
+ if(count++>6)
+ {
+ printk( KERN_INFO "Send GPS_START Timeout!\n" );
+ break;
+ }
+ }
+ }while(-ETIMEDOUT==nResult);
+ DBG( "send GPS_START done\n");
+ if (nResult != 0)
+ {
+ DBG( "error %d sending startMessage\n", nResult );
+ if (!ignore_gps_start_error)
+ {
+ return nResult;
+ }
+ }
+ if (bytesWrote != sizeof( startMessage ))
+ {
+ DBG( "invalid write size %d, %lu\n",
+ bytesWrote,
+ (unsigned long)sizeof( startMessage ) );
+ if (!ignore_gps_start_error)
+ {
+ return -EIO;
+ }
+ }
+ portdata->iGPSStartState = eSendEnd;
+ }
+
+ // Clear endpoint halt condition
+ if( nNumInterfaces > 1 )
+ {
+ nResult = usb_clear_halt(pPort->serial->dev,
+ usb_sndbulkpipe(pPort->serial->dev,
+ pPort->bulk_in_endpointAddress) | USB_DIR_IN );
+ if (nResult != 0)
+ {
+ DBG( "usb_clear_halt return value = %d\n", nResult );
+ }
+ }
+
+ /* host device is awake */
+ set_power_state(pPort->serial->dev, 0x0000);
+
+ // Pass to usb_serial_generic_open
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,26 ))
+ return usb_serial_generic_open( pPort, pFilp );
+#elif (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,31 ))
+ return usb_serial_generic_open( pTTY, pPort, pFilp );
+#else // > 2.6.31
+ return usb_serial_generic_open( pTTY, pPort );
+#endif
+}
+
+/*===========================================================================
+METHOD:
+ GobiClose (Free Method)
+
+DESCRIPTION:
+ Stop GPS if GPS port, run usb_serial_generic_close
+
+PARAMETERS:
+ pTTY [ I ] - TTY structure (only if kernel > 2.6.26 and <= 2.6.29)
+ pPort [ I ] - USB serial port structure
+ pFilp [ I ] - File structure (only on kernel <= 2.6.30)
+===========================================================================*/
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,26 ))
+void GobiClose(
+ struct usb_serial_port * pPort,
+ struct file * pFilp )
+#elif (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,30 ))
+void GobiClose(
+ struct tty_struct * pTTY,
+ struct usb_serial_port * pPort,
+ struct file * pFilp )
+#else // > 2.6.30
+void GobiClose( struct usb_serial_port * pPort )
+#endif
+{
+ struct sierra_port_private *portdata;
+ const char stopMessage[] = "$GPS_STOP";
+ int nResult;
+ int bytesWrote;
+
+ DBG( "\n" );
+
+ portdata = usb_get_serial_port_data(pPort);
+ portdata->isClosing = 1;
+
+ // Test parameters
+ if ( (pPort == NULL)
+ || (pPort->serial == NULL)
+ || (pPort->serial->dev == NULL)
+ || (pPort->serial->interface == NULL)
+ || (pPort->serial->interface->cur_altsetting == NULL) )
+ {
+ DBG( "invalid parameter\n" );
+ return;
+ }
+
+ // Is this the GPS port?
+ if ((IsGPSPort(pPort)) == true)
+ {
+ DBG( "GPS Port detected! send GPS_STOP!\n" );
+ // Send stopMessage, 1s timeout
+ nResult = usb_bulk_msg( pPort->serial->dev,
+ usb_sndbulkpipe( pPort->serial->dev,
+ pPort->bulk_out_endpointAddress ),
+ (void *)&stopMessage[0],
+ sizeof( stopMessage ),
+ &bytesWrote,
+ 100 );
+ if (nResult != 0)
+ {
+ DBG( "error %d sending stopMessage\n", nResult );
+ }
+ if (bytesWrote != sizeof( stopMessage ))
+ {
+ DBG( "invalid write size %d, %lu\n",
+ bytesWrote,
+ (unsigned long)sizeof( stopMessage ) );
+ }
+ }
+
+ // Pass to usb_serial_generic_close
+ if (gpClose == NULL)
+ {
+ DBG( "NULL gpClose\n" );
+ return;
+ }
+
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,26 ))
+ gpClose( pPort, pFilp );
+#elif (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,30 ))
+ gpClose( pTTY, pPort, pFilp );
+#else // > 2.6.30
+ gpClose( pPort );
+#endif
+}
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,35 ))
+/* Push data to tty layer and resubmit the bulk read URB */
+static void flush_and_resubmit_read_urb(struct usb_serial_port *port)
+{
+ struct urb *urb = port->read_urb;
+ struct tty_struct *tty = tty_port_tty_get(&port->port);
+ char *ch = (char *)urb->transfer_buffer;
+ int i;
+
+ if (!tty || urb->status)
+ {
+ tty_kref_put(tty);
+ goto done;
+ }
+
+ /* The per character mucking around with sysrq path it too slow for
+ stuff like 3G modems, so shortcircuit it in the 99.9999999% of cases
+ where the USB serial is not a console anyway */
+ if (!port->console || !port->sysrq)
+ tty_insert_flip_string(tty, ch, urb->actual_length);
+ else {
+ /* Push data to tty */
+ for (i = 0; i < urb->actual_length; i++, ch++) {
+ if (!usb_serial_handle_sysrq_char(tty, port, *ch))
+ tty_insert_flip_char(tty, *ch, TTY_NORMAL);
+ }
+ }
+ tty_flip_buffer_push(tty);
+ tty_kref_put(tty);
+done:
+ usb_serial_generic_resubmit_read_urb(port, GFP_ATOMIC);
+}
+#endif //#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,35 ))
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 3,3,0 ))
+static int usb_serial_generic_submit_read_urb(struct usb_serial_port *port,
+ int index, gfp_t mem_flags)
+{
+ int res;
+
+ if (!test_and_clear_bit(index, &port->read_urbs_free))
+ return 0;
+
+ dev_dbg(&port->dev, "%s - urb %d\n", __func__, index);
+
+ res = usb_submit_urb(port->read_urbs[index], mem_flags);
+ if (res) {
+ if (res != -EPERM) {
+ dev_err(&port->dev,
+ "%s - usb_submit_urb failed: %d\n",
+ __func__, res);
+ }
+ set_bit(index, &port->read_urbs_free);
+ return res;
+ }
+
+ return 0;
+}
+#endif //#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 3,3,0 ))
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,25 ))
+
+/*===========================================================================
+METHOD:
+ GobiReadBulkCallback (Free Method)
+
+DESCRIPTION:
+ Read data from USB, push to TTY and user space
+
+PARAMETERS:
+ pURB [ I ] - USB Request Block (urb) that called us
+
+RETURN VALUE:
+===========================================================================*/
+static void GobiReadBulkCallback( struct urb * pURB )
+{
+ struct sierra_port_private *portdata;
+ struct usb_serial_port * pPort = pURB->context;
+ struct tty_struct * pTTY = pPort->tty;
+ int nResult;
+ int nRoom = 0;
+ unsigned int pipeEP;
+ int status = urb->status;
+
+ DBG( "port %d\n", pPort->number );
+
+ portdata = usb_get_serial_port_data(pPort);
+ if (portdata->isClosing)
+ {
+ /* ignore bulk callback when port is closing */
+ return;
+ }
+
+ if (status != 0)
+ {
+ if (status == -ESHUTDOWN || status == -ENOENT || status == -ENODEV) {
+ {
+ return;
+ }
+ DBG("nonzero read bulk status received: %d\n", pURB->status );
+ }
+
+ usb_serial_debug_data( debug,
+ &pPort->dev,
+ __FUNCTION__,
+ pURB->actual_length,
+ pURB->transfer_buffer );
+
+ // We do no port throttling
+
+ // Push data to tty layer and user space read function
+ if ( (pTTY != 0) && (status == 0) && (pURB->actual_length) )
+ {
+ nRoom = tty_buffer_request_room( pTTY, pURB->actual_length );
+ DBG( "room size %d %d\n", nRoom, 512 );
+ if (nRoom != 0)
+ {
+ tty_insert_flip_string( pTTY, pURB->transfer_buffer, nRoom );
+ tty_flip_buffer_push( pTTY );
+ }
+ }
+
+ pipeEP = usb_rcvbulkpipe( pPort->serial->dev,
+ pPort->bulk_in_endpointAddress );
+
+ // For continuous reading
+ usb_fill_bulk_urb( pPort->read_urb,
+ pPort->serial->dev,
+ pipeEP,
+ pPort->read_urb->transfer_buffer,
+ pPort->read_urb->transfer_buffer_length,
+ GobiReadBulkCallback,
+ pPort );
+
+ nResult = usb_submit_urb( pPort->read_urb, GFP_ATOMIC );
+ if (nResult != 0)
+ {
+ DBG( "failed resubmitting read urb, error %d\n", nResult );
+ }
+}
+#else
+void GobiReadBulkCallback(struct urb *urb)
+{
+ struct sierra_port_private *portdata;
+ struct usb_serial_port *port = urb->context;
+ unsigned char *data = urb->transfer_buffer;
+ unsigned long flags;
+ int status = urb->status;
+ int i;
+
+ #if (LINUX_VERSION_CODE >= KERNEL_VERSION( 3,3,0 ))
+ for (i = 0; i < ARRAY_SIZE(port->read_urbs); ++i) {
+ if (urb == port->read_urbs[i])
+ break;
+ }
+ if (i < ARRAY_SIZE(port->read_urbs))
+ set_bit(i, &port->read_urbs_free);
+ #endif
+
+ /* only ignore bulk callback after the bit of read_urbs_free was set,
+ so that the port can be accessed later on */
+ portdata = usb_get_serial_port_data(port);
+ if (portdata->isClosing)
+ {
+ /* ignore bulk callback when port is closing */
+ return;
+ }
+
+ dev_dbg(&port->dev, "%s - urb %d, len %d\n", __func__, i,
+ urb->actual_length);
+
+ if (urb->status) {
+ if (status == -ESHUTDOWN || status == -ENOENT || status == -ENODEV) {
+ return;
+ }
+ DBG("%s - non-zero urb status: %d\n",
+ __func__, urb->status);
+ }
+ else
+ {
+ #if (LINUX_VERSION_CODE >= KERNEL_VERSION( 3,7,0 ))
+ usb_serial_debug_data(&port->dev, __func__, urb->actual_length, data);
+ #else
+ usb_serial_debug_data(debug, &port->dev, __func__, urb->actual_length, data);
+ #endif
+
+ #if (LINUX_VERSION_CODE >= KERNEL_VERSION( 2,6,35 ))
+ port->serial->type->process_read_urb(urb);
+ #endif
+ }
+
+ /* Throttle the device if requested by tty */
+ spin_lock_irqsave(&port->lock, flags);
+ port->throttled = port->throttle_req;
+ if (!port->throttled)
+ {
+ spin_unlock_irqrestore(&port->lock, flags);
+ #if (LINUX_VERSION_CODE >= KERNEL_VERSION( 3,3,0 ))
+ if (i < ARRAY_SIZE(port->read_urbs))
+ usb_serial_generic_submit_read_urb(port, i, GFP_ATOMIC);
+ #elif (LINUX_VERSION_CODE >= KERNEL_VERSION( 2,6,35 ))
+ usb_serial_generic_submit_read_urb(port, GFP_ATOMIC);
+ #else
+ flush_and_resubmit_read_urb(port);
+ #endif
+ }
+ else
+ {
+ spin_unlock_irqrestore(&port->lock, flags);
+ }
+}
+
+#endif //#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,25 ))
+
+#ifdef CONFIG_PM
+/*===========================================================================
+METHOD:
+ GobiSerialSuspend (Public Method)
+
+DESCRIPTION:
+ Set reset_resume flag
+
+PARAMETERS
+ pIntf [ I ] - Pointer to interface
+ powerEvent [ I ] - Power management event
+
+RETURN VALUE:
+ int - 0 for success
+ negative errno for failure
+===========================================================================*/
+int GobiSerialSuspend(
+ struct usb_interface * pIntf,
+ pm_message_t powerEvent )
+{
+ struct usb_serial * pDev;
+ DBG( "\n" );
+ if (pIntf == 0)
+ {
+ return -ENOMEM;
+ }
+
+ pDev = usb_get_intfdata( pIntf );
+ if (pDev == NULL)
+ {
+ return -ENXIO;
+ }
+
+ // Unless this is PM_EVENT_SUSPEND, make sure device gets rescanned
+ if ((powerEvent.event & PM_EVENT_SUSPEND) == 0)
+ {
+ pDev->dev->reset_resume = 1;
+ }
+
+ // Run usb_serial's suspend function
+ return usb_serial_suspend( pIntf, powerEvent );
+}
+
+
+#endif /* CONFIG_PM*/
+
+
+static void gobi_stop_rx_urbs(struct usb_serial_port *port)
+{
+ usb_kill_urb(port->interrupt_in_urb);
+}
+
+void stop_read_write_urbs(struct usb_serial *serial)
+{
+ int i;
+ struct usb_serial_port *port;
+
+ /* Stop reading/writing urbs */
+ for (i = 0; i < serial->num_ports; ++i) {
+ port = serial->port[i];
+ gobi_stop_rx_urbs(port);
+ }
+}
+
+
+
+#ifdef CONFIG_PM
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,23 ))
+
+/*===========================================================================
+METHOD:
+ GobiSerialResume (Free Method)
+
+DESCRIPTION:
+ Restart URBs killed during usb_serial_suspend
+
+ Fixes 2 bugs in 2.6.23 kernel
+ 1. pSerial->type->resume was NULL and unchecked, caused crash.
+ 2. set_to_generic_if_null was not run for resume.
+
+PARAMETERS:
+ pIntf [ I ] - Pointer to interface
+
+RETURN VALUE:
+ int - 0 for success
+ negative errno for failure
+===========================================================================*/
+int GobiSerialResume( struct usb_interface * pIntf )
+{
+ struct usb_serial * pSerial = usb_get_intfdata( pIntf );
+ struct usb_serial_port * pPort;
+ int portIndex, errors, nResult;
+
+ if (pSerial == NULL)
+ {
+ DBG( "no pSerial\n" );
+ return -ENOMEM;
+ }
+ if (pSerial->type == NULL)
+ {
+ DBG( "no pSerial->type\n" );
+ return ENOMEM;
+ }
+ if (pSerial->type->resume == NULL)
+ {
+ // Expected behaviour in 2.6.23, in later kernels this was handled
+ // by the usb-serial driver and usb_serial_generic_resume
+ errors = 0;
+ for (portIndex = 0; portIndex < pSerial->num_ports; portIndex++)
+ {
+ pPort = pSerial->port[portIndex];
+ if (pPort->open_count > 0 && pPort->read_urb != NULL)
+ {
+ nResult = usb_submit_urb( pPort->read_urb, GFP_NOIO );
+ if (nResult < 0)
+ {
+ // Return first error we see
+ DBG( "error %d\n", nResult );
+ return nResult;
+ }
+ }
+ }
+
+ // Success
+ return 0;
+ }
+
+ // Execution would only reach this point if user has
+ // patched version of usb-serial driver.
+ return usb_serial_resume( pIntf );
+}
+
+#endif
+#endif /* CONFIG_PM*/
+
+#ifdef CONFIG_PM
+int GobiUSBSerialResetResume(struct usb_serial *serial)
+{
+ DBG( "\n" );
+ return GobiUSBSerialResume(serial);
+}
+
+int GobiUSBSerialResume(struct usb_serial *serial)
+{
+ struct gobi_serial_intf_private *intfdata = usb_get_serial_data(serial);
+ DBG( "\n" );
+ spin_lock_irq(&intfdata->susp_lock);
+ intfdata->suspended = 0;
+ spin_unlock_irq(&intfdata->susp_lock);
+
+ return 0;
+}
+
+int GobiUSBSerialSuspend(struct usb_serial *pDev, pm_message_t powerEvent)
+{
+ struct gobi_serial_intf_private *intfdata = usb_get_serial_data(pDev);
+ DBG( "\n" );
+ spin_lock_irq(&intfdata->susp_lock);
+ intfdata->suspended = 1;
+ spin_unlock_irq(&intfdata->susp_lock);
+ stop_read_write_urbs(pDev);
+ return 0;
+}
+#endif
+
+void GobiUSBSerialDisconnect(struct usb_serial *serial)
+{
+ DBG( "\n" );
+ stop_read_write_urbs(serial);
+ return ;
+}
+
+/*===========================================================================
+METHOD:
+ GobiInit (Free Method)
+
+DESCRIPTION:
+ Register the driver and device
+
+PARAMETERS:
+
+RETURN VALUE:
+ int - negative error code on failure
+ zero on success
+===========================================================================*/
+static int __init GobiInit( void )
+{
+ int nRetval = 0;
+ gpClose = NULL;
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 3,4,0 ))
+ // Registering driver to USB serial core layer
+ nRetval = usb_serial_register( &gGobiDevice );
+ if (nRetval != 0)
+ {
+ return nRetval;
+ }
+
+ // Registering driver to USB core layer
+ nRetval = usb_register( &GobiDriver );
+ if (nRetval != 0)
+ {
+ usb_serial_deregister( &gGobiDevice );
+ return nRetval;
+ }
+
+ // This will be shown whenever driver is loaded
+ printk( KERN_INFO "%s: %s\n", DRIVER_DESC, DRIVER_VERSION );
+
+#else
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 3,5,0 ))
+ nRetval = usb_serial_register_drivers(serial_drivers, KBUILD_MODNAME, GobiVIDPIDTable);
+#else
+ nRetval = usb_serial_register_drivers(&GobiDriver, serial_drivers);
+#endif
+ if (nRetval == 0)
+ {
+ printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_VERSION ":"
+ DRIVER_DESC "\n");
+ }
+#endif
+
+
+ return nRetval;
+}
+
+/*===========================================================================
+METHOD:
+ GobiExit (Free Method)
+
+DESCRIPTION:
+ Deregister the driver and device
+
+PARAMETERS:
+
+RETURN VALUE:
+===========================================================================*/
+static void __exit GobiExit( void )
+{
+ gpClose = NULL;
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 3,4,0 ))
+ usb_deregister( &GobiDriver );
+ usb_serial_deregister( &gGobiDevice );
+#else
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 3,5,0 ))
+ usb_serial_deregister_drivers(serial_drivers);
+#else
+ usb_serial_deregister_drivers(&GobiDriver, serial_drivers);
+#endif
+#endif
+}
+
+// Calling kernel module to init our driver
+module_init( GobiInit );
+module_exit( GobiExit );
+
+MODULE_VERSION( DRIVER_VERSION );
+MODULE_AUTHOR( DRIVER_AUTHOR );
+MODULE_DESCRIPTION( DRIVER_DESC );
+MODULE_LICENSE( "Dual BSD/GPL" );
+
+module_param( debug, int, S_IRUGO | S_IWUSR );
+MODULE_PARM_DESC( debug, "Debug enabled or not" );
+module_param( flow_control, int, S_IRUGO | S_IWUSR );
+MODULE_PARM_DESC( flow_control, "flow control enabled or not" );
+module_param( ignore_gps_start_error, int, S_IRUGO | S_IWUSR );
+MODULE_PARM_DESC( ignore_gps_start_error,
+ "allow port open to success even when GPS control message failed");
+module_param( delay_open_gps_port, int, S_IRUGO | S_IWUSR );
+MODULE_PARM_DESC( delay_open_gps_port, "Delay Open GPS Port, after device ready" );
diff --git a/swi-drivers/src/GobiSerial/Makefile b/swi-drivers/src/GobiSerial/Makefile
new file mode 100644
index 0000000..e7ddf67
--- /dev/null
+++ b/swi-drivers/src/GobiSerial/Makefile
@@ -0,0 +1,33 @@
+obj-m := GobiSerial.o
+KDIR := /lib/modules/$(shell uname -r)/build
+PWD := $(shell pwd)
+OUTPUTDIR=/lib/modules/`uname -r`/kernel/drivers/usb/serial/
+
+PI_KDIR := ~/k/linux-rpi-3.6.y
+PI_CCPREFIX=~/toolchain/rpi/tools-master/arm-bcm2708/arm-bcm2708-linux-gnueabi/bin/arm-bcm2708-linux-gnueabi-
+
+OW_KDIR := ~/openwrt/trunk/build_dir/target-mips_r2_uClibc-0.9.33.2/linux-ar71xx_generic/linux-3.8.11
+OW_CCPREFIX=~/openwrt/trunk/staging_dir/toolchain-mips_r2_gcc-4.6-linaro_uClibc-0.9.33.2/bin/mips-openwrt-linux-
+
+MARVELL_KDIR := ~/toolchain/qmi_mxwell/kernel-2.6.31
+MARVELL_CCPREFIX := ~/toolchain/qmi_mxwell/toolchain/bin/arm-none-linux-gnueabi-
+
+all: clean
+ $(MAKE) -C $(KDIR) M=$(PWD) modules
+
+marvell:
+ $(MAKE) ARCH=arm CROSS_COMPILE=${MARVELL_CCPREFIX} -C $(MARVELL_KDIR) M=$(PWD) modules
+
+pi:
+ $(MAKE) ARCH=arm CROSS_COMPILE=${PI_CCPREFIX} -C $(PI_KDIR) M=$(PWD) modules
+
+ow:
+ $(MAKE) ARCH=mips CROSS_COMPILE=${OW_CCPREFIX} -C $(OW_KDIR) M=$(PWD) modules
+
+install: all
+ mkdir -p $(OUTPUTDIR)
+ cp -f GobiSerial.ko $(OUTPUTDIR)
+ depmod
+
+clean:
+ rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions Module.* modules.order
diff --git a/swi-drivers/src/GobiSerial/Readme.txt b/swi-drivers/src/GobiSerial/Readme.txt
new file mode 100644
index 0000000..ed7f48e
--- /dev/null
+++ b/swi-drivers/src/GobiSerial/Readme.txt
@@ -0,0 +1,61 @@
+Gobi3000 Serial driver 2011-07-29-1026
+
+This readme covers important information concerning
+the Gobi Serial driver.
+
+Table of Contents
+
+1. What's new in this release
+2. Known issues
+3. Known platform issues
+
+
+-------------------------------------------------------------------------------
+
+1. WHAT'S NEW
+
+This Release (Gobi3000 Serial driver 2011-07-29-1026)
+a. Signal the device to leave low power mode on enumeration
+b. Change to new date-based versioning scheme
+
+Prior Release (Gobi3000 Serial driver 1.0.30) 05/18/2011
+a. Fix typo affecting 2.6.30 kernels
+
+Prior Release (Gobi3000 Serial driver 1.0.20) 01/05/2011
+a. Add support for QDL port on interface 0 (enumeration changed in
+ firmware 1575 and later).
+
+Prior Release (Gobi3000 Serial driver 1.0.10) 09/17/2010
+a. Initial release
+
+-------------------------------------------------------------------------------
+
+2. KNOWN ISSUES
+
+No known issues.
+
+-------------------------------------------------------------------------------
+
+3. KNOWN PLATFORM ISSUES
+
+a. If a user attempts to obtain a Simple IP address the device may become
+ unresponsive to AT commands and will need to be restarted. This may be
+ a result of a problem in one or more of the following open source products:
+ 1. pppd daemon
+ 2. ppp protocol stack
+ 3. USB driver
+ In any case, this is not an issue that can be addressed by any software
+ provided by Qualcomm.
+b. There is a bug in the open source ehci-hcd driver on kernel 2.6.32
+ and newer caused by commit 403dbd36739e344d2d25f56ebbe342248487bd48.
+ This change in the ehci-hcd driver made it so Intel USB hubs are trusted
+ to provide hardware interrupts, however some devices do not do so
+ consistently. This has been observed as a 2 second delay or no URB
+ callback at all, resulting in QDL timeouts or a hang in the n_tty_write()
+ function. This can be corrected by reverting the original commit or
+ specifying need_io_watchdog = 1 for this USB hub.
+
+-------------------------------------------------------------------------------
+
+
+