diff options
author | Bjørn Mork <bjorn@mork.no> | 2017-05-11 11:03:45 +0200 |
---|---|---|
committer | Bjørn Mork <bjorn@mork.no> | 2017-05-11 11:03:45 +0200 |
commit | 00456512bb6b3734fbb06e8d37fcb1b05bd0ee19 (patch) | |
tree | 0341b82bd5107483f245af230b557f0ac79fc73f | |
parent | 3aaa2d2259ef043248ab42676fe1aa3e083c2596 (diff) |
add GobiNet+GobiSerial packages
Signed-off-by: Bjørn Mork <bjorn@mork.no>
-rw-r--r-- | swi-drivers/Makefile | 51 | ||||
-rw-r--r-- | swi-drivers/src/GobiNet/GobiUSBNet.c | 2606 | ||||
-rw-r--r-- | swi-drivers/src/GobiNet/Makefile | 56 | ||||
-rw-r--r-- | swi-drivers/src/GobiNet/QMI.c | 1868 | ||||
-rw-r--r-- | swi-drivers/src/GobiNet/QMI.h | 456 | ||||
-rw-r--r-- | swi-drivers/src/GobiNet/QMIDevice.c | 6379 | ||||
-rw-r--r-- | swi-drivers/src/GobiNet/QMIDevice.h | 440 | ||||
-rw-r--r-- | swi-drivers/src/GobiNet/Readme.txt | 78 | ||||
-rw-r--r-- | swi-drivers/src/GobiNet/Structs.h | 476 | ||||
-rw-r--r-- | swi-drivers/src/GobiNet/gobi_usbnet.h | 51 | ||||
-rw-r--r-- | swi-drivers/src/GobiNet/usbnet_2_6_32.c | 396 | ||||
-rw-r--r-- | swi-drivers/src/GobiNet/usbnet_2_6_35.c | 400 | ||||
-rw-r--r-- | swi-drivers/src/GobiNet/usbnet_3_0_6.c | 432 | ||||
-rw-r--r-- | swi-drivers/src/GobiNet/usbnet_3_10_21.c | 457 | ||||
-rw-r--r-- | swi-drivers/src/GobiNet/usbnet_3_12_xx.c | 513 | ||||
-rw-r--r-- | swi-drivers/src/GobiNet/usbnet_4_4_xx.c | 514 | ||||
-rw-r--r-- | swi-drivers/src/GobiSerial/GobiSerial.c | 1479 | ||||
-rw-r--r-- | swi-drivers/src/GobiSerial/Makefile | 33 | ||||
-rw-r--r-- | swi-drivers/src/GobiSerial/Readme.txt | 61 |
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(ð->h_dest, &pGobiDev->mpNetDev->net->dev_addr[0], ETH_ALEN); + memcpy(ð->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(ð->h_dest, &pGobiDev->mpNetDev->net->dev_addr[0], ETH_ALEN); + memcpy(ð->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. + +------------------------------------------------------------------------------- + + + |