nt9856x/code/driver/source/net/GobiNet/QMIDevice.c
2023-05-17 15:33:39 +08:00

4205 lines
116 KiB
C
Executable File

/*===========================================================================
FILE:
QMIDevice.c
DESCRIPTION:
Functions related to the QMI interface device
FUNCTIONS:
Generic functions
IsDeviceValid
PrintHex
GobiSetDownReason
GobiClearDownReason
GobiTestDownReason
Driver level asynchronous read functions
ResubmitIntURB
ReadCallback
IntCallback
StartRead
KillRead
Internal read/write functions
ReadAsync
UpSem
ReadSync
WriteSyncCallback
WriteSync
Internal memory management functions
GetClientID
ReleaseClientID
FindClientMem
AddToReadMemList
PopFromReadMemList
AddToNotifyList
NotifyAndPopNotifyList
AddToURBList
PopFromURBList
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.
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/module.h>
#include <linux/usb/cdc.h>
#include <linux/usb.h>
//-----------------------------------------------------------------------------
// Definitions
//-----------------------------------------------------------------------------
#define __QUEC_INCLUDE_QMI_C__
#include "QMI.c"
#define __QUECTEL_INTER__
#include "QMIDevice.h"
#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,22 ))
static int s_interval;
#endif
#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,14 ))
#include <linux/devfs_fs_kernel.h>
static char devfs_name[32];
static int device_create(struct class *class, struct device *parent, dev_t devt, const char *fmt, ...)
{
va_list vargs;
struct class_device *class_dev;
int err;
va_start(vargs, fmt);
vsnprintf(devfs_name, sizeof(devfs_name), fmt, vargs);
va_end(vargs);
class_dev = class_device_create(class, devt, parent, "%s", devfs_name);
if (IS_ERR(class_dev)) {
err = PTR_ERR(class_dev);
goto out;
}
err = devfs_mk_cdev(devt, S_IFCHR|S_IRUSR|S_IWUSR|S_IRGRP, devfs_name);
if (err) {
class_device_destroy(class, devt);
goto out;
}
return 0;
out:
return err;
}
static void device_destroy(struct class *class, dev_t devt)
{
class_device_destroy(class, devt);
devfs_remove(devfs_name);
}
#endif
#ifdef CONFIG_PM
// Prototype to GobiNetSuspend function
int QuecGobiNetSuspend(
struct usb_interface * pIntf,
pm_message_t powerEvent );
#endif /* CONFIG_PM */
// 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)
// 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
static 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 SET_CONTROL_LINE_STATE_REQUEST_TYPE 0x21
#define SET_CONTROL_LINE_STATE_REQUEST 0x22
#define CONTROL_DTR 0x01
#define CONTROL_RTS 0x02
/*=========================================================================*/
// UserspaceQMIFops
// QMI device's userspace file operations
/*=========================================================================*/
static 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,
#ifdef quectel_no_for_each_process
.release = UserspaceClose,
#else
.flush = UserspaceClose,
#endif
.poll = UserspacePoll,
};
/*=========================================================================*/
// Generic functions
/*=========================================================================*/
static 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;
}
/*===========================================================================
METHOD:
IsDeviceValid (Public Method)
DESCRIPTION:
Basic test to see if device memory is valid
PARAMETERS:
pDev [ I ] - Device specific memory
RETURN VALUE:
bool
===========================================================================*/
static 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 QuecPrintHex(
void * pBuffer,
u16 bufSize )
{
char * pPrintBuf;
u16 pos;
int status;
if (quec_debug != 1)
{
return;
}
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 QuecGobiSetDownReason(
sGobiUSBNet * pDev,
u8 reason )
{
DBG("%s reason=%d, mDownReason=%x\n", __func__, reason, (unsigned)pDev->mDownReason);
#ifdef QUECTEL_WWAN_QMAP
if (reason == NO_NDIS_CONNECTION)
return;
#endif
set_bit( reason, &pDev->mDownReason );
netif_carrier_off( pDev->mpNetDev->net );
}
/*===========================================================================
METHOD:
GobiClearDownReason (Public Method)
DESCRIPTION:
Clear mDownReason and may turn carrier on
PARAMETERS
pDev [ I ] - Device specific memory
reason [ I ] - Reason device is no longer down
RETURN VALUE:
None
===========================================================================*/
void QuecGobiClearDownReason(
sGobiUSBNet * pDev,
u8 reason )
{
clear_bit( reason, &pDev->mDownReason );
DBG("%s reason=%d, mDownReason=%x\n", __func__, reason, (unsigned)pDev->mDownReason);
#if 0 //(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 QuecGobiTestDownReason(
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
===========================================================================*/
static 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
#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,22 ))
interval = max((int)(pIntURB->ep->desc.bInterval),
(pIntURB->dev->speed == USB_SPEED_HIGH) ? 7 : 3);
#else
interval = s_interval;
#endif
// 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
===========================================================================*/
#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,18 ))
static void ReadCallback( struct urb * pReadURB )
#else
static void ReadCallback(struct urb *pReadURB, struct pt_regs *regs)
#endif
{
int result;
u16 clientID;
sClientMemList * pClientMem;
void * pData;
void * pDataCopy;
u16 dataSize;
sGobiUSBNet * pDev;
unsigned long flags;
u16 transactionID;
if (pReadURB == NULL)
{
DBG( "bad read URB\n" );
return;
}
pDev = pReadURB->context;
if (IsDeviceValid( pDev ) == false)
{
DBG( "Invalid device!\n" );
return;
}
#ifdef READ_QMI_URB_ERROR
del_timer(&pDev->mQMIDev.mReadUrbTimer);
if ((pReadURB->status == -ECONNRESET) && (pReadURB->actual_length > 0))
pReadURB->status = 0;
#endif
if (pReadURB->status != 0)
{
DBG( "Read status = %d\n", pReadURB->status );
// Resubmit the interrupt URB
ResubmitIntURB( pDev->mQMIDev.mpIntURB );
return;
}
DBG( "Read %d bytes\n", pReadURB->actual_length );
pData = pReadURB->transfer_buffer;
dataSize = pReadURB->actual_length;
PrintHex( pData, dataSize );
#ifdef READ_QMI_URB_ERROR
if (dataSize < (le16_to_cpu(get_unaligned((u16*)(pData + 1))) + 1)) {
dataSize = (le16_to_cpu(get_unaligned((u16*)(pData + 1))) + 1);
memset(pReadURB->transfer_buffer + pReadURB->actual_length, 0x00, dataSize - pReadURB->actual_length);
INFO( "Read %d / %d bytes\n", pReadURB->actual_length, dataSize);
}
#endif
result = ParseQMUX( &clientID,
pData,
dataSize );
if (result < 0)
{
DBG( "Read error parsing QMUX %d\n", result );
// Resubmit the interrupt URB
ResubmitIntURB( pDev->mQMIDev.mpIntURB );
return;
}
// Grab transaction ID
// Data large enough?
if (dataSize < result + 3)
{
DBG( "Data buffer too small to parse\n" );
// Resubmit the interrupt URB
ResubmitIntURB( pDev->mQMIDev.mpIntURB );
return;
}
// Transaction ID size is 1 for QMICTL, 2 for others
if (clientID == QMICTL)
{
transactionID = *(u8*)(pData + result + 1);
}
else
{
transactionID = le16_to_cpu( get_unaligned((u16*)(pData + result + 1)) );
}
// Critical section
spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
// Find memory storage for this service and Client ID
// Not using FindClientMem because it can't handle broadcasts
pClientMem = pDev->mQMIDev.mpClientMemList;
while (pClientMem != NULL)
{
if (pClientMem->mClientID == clientID
|| (pClientMem->mClientID | 0xff00) == clientID)
{
// Make copy of pData
pDataCopy = kmalloc( dataSize, GFP_ATOMIC );
if (pDataCopy == NULL)
{
DBG( "Error allocating client data memory\n" );
// End critical section
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
// 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
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
// Resubmit the interrupt URB
ResubmitIntURB( pDev->mQMIDev.mpIntURB );
return;
}
// Success
VDBG( "Creating new readListEntry for client 0x%04X, TID %x\n",
clientID,
transactionID );
// Notify this client data exists
NotifyAndPopNotifyList( pDev,
pClientMem->mClientID,
transactionID );
// Possibly notify poll() that data exists
wake_up_interruptible_sync( &pClientMem->mWaitQueue );
// Not a broadcast
if (clientID >> 8 != 0xff)
{
break;
}
}
// Next element
pClientMem = pClientMem->mpNext;
}
// End critical section
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
// Resubmit the interrupt URB
ResubmitIntURB( pDev->mQMIDev.mpIntURB );
}
/*===========================================================================
METHOD:
IntCallback (Public Method)
DESCRIPTION:
Data is available, fire off a read URB
PARAMETERS
pIntURB [ I ] - URB this callback is run for
RETURN VALUE:
None
===========================================================================*/
#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,18 ))
static void IntCallback( struct urb * pIntURB )
{
#else
static void IntCallback(struct urb *pIntURB, struct pt_regs *regs)
{
#endif
int status;
struct usb_cdc_notification *dr;
sGobiUSBNet * pDev = (sGobiUSBNet *)pIntURB->context;
dr = (struct usb_cdc_notification *)pDev->mQMIDev.mpIntBuffer;
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;
}
}
else
{
//TODO cast transfer_buffer to struct usb_cdc_notification
VDBG( "IntCallback: Encapsulated Response = 0x%llx\n",
(*(u64*)pIntURB->transfer_buffer));
switch (dr->bNotificationType) {
case USB_CDC_NOTIFY_RESPONSE_AVAILABLE: //0x01
{
// 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 );
#ifdef READ_QMI_URB_ERROR
mod_timer( &pDev->mQMIDev.mReadUrbTimer, jiffies + msecs_to_jiffies(300) );
#endif
status = usb_submit_urb( pDev->mQMIDev.mpReadURB, GFP_ATOMIC );
if (status != 0)
{
DBG("Error submitting Read URB %d\n", status);
// Resubmit the interrupt urb
ResubmitIntURB(pIntURB);
return;
}
// Int URB will be resubmitted during ReadCallback
return;
}
case USB_CDC_NOTIFY_SPEED_CHANGE: //0x2a
{
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" );
}
}
default:
{
DBG( "ignoring invalid interrupt in packet\n" );
PrintHex( pIntURB->transfer_buffer, pIntURB->actual_length );
}
}
// Resubmit the interrupt urb
ResubmitIntURB( pIntURB );
return;
}
}
#ifdef READ_QMI_URB_ERROR
static void ReadUrbTimerFunc( struct urb * pReadURB )
{
int result;
INFO( "%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 );
INFO( "%s called usb_unlink_urb, result = %d\n", __func__, result);
}
}
#endif
/*===========================================================================
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 QuecStartRead( 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;
}
#ifdef READ_QMI_URB_ERROR
setup_timer( &pDev->mQMIDev.mReadUrbTimer, (void*)ReadUrbTimerFunc, (unsigned long)pDev->mQMIDev.mpReadURB );
#endif
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( 64, 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 = 0xA1;
pDev->mQMIDev.mpReadSetupPacket->mRequestCode = 1;
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 = max((int)(pendp->bInterval),
(pDev->mpNetDev->udev->speed == USB_SPEED_HIGH) ? 7 : 3);
#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,22 ))
s_interval = interval;
#endif
// Schedule interrupt URB
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,
min((int)le16_to_cpu(pendp->wMaxPacketSize), 64),
IntCallback,
pDev,
interval );
return usb_submit_urb( pDev->mQMIDev.mpIntURB, GFP_KERNEL );
}
/*===========================================================================
METHOD:
KillRead (Public Method)
DESCRIPTION:
Kill continuous read "thread"
PARAMETERS:
pDev [ I ] - Device specific memory
RETURN VALUE:
None
===========================================================================*/
void QuecKillRead( sGobiUSBNet * pDev )
{
// Stop reading
if (pDev->mQMIDev.mpReadURB != NULL)
{
DBG( "Killng read URB\n" );
usb_kill_urb( pDev->mQMIDev.mpReadURB );
}
if (pDev->mQMIDev.mpIntURB != NULL)
{
DBG( "Killng int URB\n" );
usb_kill_urb( pDev->mQMIDev.mpIntURB );
}
// Release buffers
kfree( pDev->mQMIDev.mpReadSetupPacket );
pDev->mQMIDev.mpReadSetupPacket = NULL;
kfree( pDev->mQMIDev.mpReadBuffer );
pDev->mQMIDev.mpReadBuffer = NULL;
kfree( pDev->mQMIDev.mpIntBuffer );
pDev->mQMIDev.mpIntBuffer = NULL;
// Release URB's
usb_free_urb( pDev->mQMIDev.mpReadURB );
pDev->mQMIDev.mpReadURB = NULL;
usb_free_urb( pDev->mQMIDev.mpIntURB );
pDev->mQMIDev.mpIntURB = NULL;
}
/*=========================================================================*/
// Internal read/write functions
/*=========================================================================*/
/*===========================================================================
METHOD:
ReadAsync (Public Method)
DESCRIPTION:
Start asynchronous read
NOTE: Reading client's data store, not device
PARAMETERS:
pDev [ I ] - Device specific memory
clientID [ I ] - Requester's client ID
transactionID [ I ] - Transaction ID or 0 for any
pCallback [ I ] - Callback to be executed when data is available
pData [ I ] - Data buffer that willl be passed (unmodified)
to callback
RETURN VALUE:
int - 0 for success
negative errno for failure
===========================================================================*/
static int ReadAsync(
sGobiUSBNet * pDev,
u16 clientID,
u16 transactionID,
void (*pCallback)(sGobiUSBNet*, u16, void *),
void * pData )
{
sClientMemList * pClientMem;
sReadMemList ** ppReadMemList;
unsigned long flags;
if (IsDeviceValid( pDev ) == false)
{
DBG( "Invalid device!\n" );
return -ENXIO;
}
// Critical section
spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
// Find memory storage for this client ID
pClientMem = FindClientMem( pDev, clientID );
if (pClientMem == NULL)
{
DBG( "Could not find matching client ID 0x%04X\n",
clientID );
// End critical section
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
return -ENXIO;
}
ppReadMemList = &(pClientMem->mpList);
// Does data already exist?
while (*ppReadMemList != NULL)
{
// Is this element our data?
if (transactionID == 0
|| transactionID == (*ppReadMemList)->mTransactionID)
{
// End critical section
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
// Run our own callback
pCallback( pDev, clientID, pData );
return 0;
}
// Next
ppReadMemList = &(*ppReadMemList)->mpNext;
}
// Data not found, add ourself to list of waiters
if (AddToNotifyList( pDev,
clientID,
transactionID,
pCallback,
pData ) == false)
{
DBG( "Unable to register for notification\n" );
}
// End critical section
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
// Success
return 0;
}
/*===========================================================================
METHOD:
UpSem (Public Method)
DESCRIPTION:
Notification function for synchronous read
PARAMETERS:
pDev [ I ] - Device specific memory
clientID [ I ] - Requester's client ID
pData [ I ] - Buffer that holds semaphore to be up()-ed
RETURN VALUE:
None
===========================================================================*/
#define QUEC_SEM_MAGIC 0x12345678
struct QuecSem {
struct semaphore readSem;
int magic;
};
static void UpSem(
sGobiUSBNet * pDev,
u16 clientID,
void * pData )
{
struct QuecSem *pSem = (struct QuecSem *)pData;
VDBG( "0x%04X\n", clientID );
if (pSem->magic == QUEC_SEM_MAGIC)
up( &(pSem->readSem) );
else
kfree(pSem);
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
===========================================================================*/
static int ReadSync(
sGobiUSBNet * pDev,
void ** ppOutBuffer,
u16 clientID,
u16 transactionID )
{
int result;
sClientMemList * pClientMem;
sNotifyList ** ppNotifyList, * pDelNotifyListEntry;
struct QuecSem readSem;
void * pData;
unsigned long flags;
u16 dataSize;
if (IsDeviceValid( pDev ) == false)
{
DBG( "Invalid device!\n" );
return -ENXIO;
}
// Critical section
spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
// Find memory storage for this Client ID
pClientMem = FindClientMem( pDev, clientID );
if (pClientMem == NULL)
{
DBG( "Could not find matching client ID 0x%04X\n",
clientID );
// End critical section
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
return -ENXIO;
}
// Note: in cases where read is interrupted,
// this will verify client is still valid
while (PopFromReadMemList( pDev,
clientID,
transactionID,
&pData,
&dataSize ) == false)
{
// Data does not yet exist, wait
sema_init( &readSem.readSem, 0 );
readSem.magic = QUEC_SEM_MAGIC;
// Add ourself to list of waiters
if (AddToNotifyList( pDev,
clientID,
transactionID,
UpSem,
&readSem ) == false)
{
DBG( "unable to register for notification\n" );
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
return -EFAULT;
}
// End critical section while we block
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
// Wait for notification
result = down_interruptible( &readSem.readSem );
if (result == -EINTR) {
result = down_timeout(&readSem.readSem, msecs_to_jiffies(200));
}
if (result != 0)
{
DBG( "Down Timeout %d\n", result );
// readSem will fall out of scope,
// remove from notify list so it's not referenced
spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
ppNotifyList = &(pClientMem->mpReadNotifyList);
pDelNotifyListEntry = NULL;
// Find and delete matching entry
while (*ppNotifyList != NULL)
{
if ((*ppNotifyList)->mpData == &readSem)
{
pDelNotifyListEntry = *ppNotifyList;
*ppNotifyList = (*ppNotifyList)->mpNext;
kfree( pDelNotifyListEntry );
break;
}
// Next
ppNotifyList = &(*ppNotifyList)->mpNext;
}
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
return -EINTR;
}
// Verify device is still valid
if (IsDeviceValid( pDev ) == false)
{
DBG( "Invalid device!\n" );
return -ENXIO;
}
// Restart critical section and continue loop
spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
}
// End Critical section
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
// Success
*ppOutBuffer = pData;
return dataSize;
}
/*===========================================================================
METHOD:
WriteSyncCallback (Public Method)
DESCRIPTION:
Write callback
PARAMETERS
pWriteURB [ I ] - URB this callback is run for
RETURN VALUE:
None
===========================================================================*/
#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,18 ))
static void WriteSyncCallback( struct urb * pWriteURB )
#else
static void WriteSyncCallback(struct urb *pWriteURB, struct pt_regs *regs)
#endif
{
if (pWriteURB == NULL)
{
DBG( "null urb\n" );
return;
}
DBG( "Write status/size %d/%d\n",
pWriteURB->status,
pWriteURB->actual_length );
// Notify that write has completed by up()-ing semeaphore
up( (struct semaphore * )pWriteURB->context );
return;
}
/*===========================================================================
METHOD:
WriteSync (Public Method)
DESCRIPTION:
Start synchronous write
PARAMETERS:
pDev [ I ] - Device specific memory
pWriteBuffer [ I ] - Data to be written
writeBufferSize [ I ] - Size of data to be written
clientID [ I ] - Client ID of requester
RETURN VALUE:
int - write size (includes QMUX)
negative errno for failure
===========================================================================*/
static int WriteSync(
sGobiUSBNet * pDev,
char * pWriteBuffer,
int writeBufferSize,
u16 clientID )
{
int result;
struct semaphore writeSem;
struct urb * pWriteURB;
sURBSetupPacket *writeSetup;
unsigned long flags;
if (IsDeviceValid( pDev ) == false)
{
DBG( "Invalid device!\n" );
return -ENXIO;
}
pWriteURB = usb_alloc_urb( 0, GFP_KERNEL );
if (pWriteURB == NULL)
{
DBG( "URB mem error\n" );
return -ENOMEM;
}
// Fill writeBuffer with QMUX
result = FillQMUX( clientID, pWriteBuffer, writeBufferSize );
if (result < 0)
{
usb_free_urb( pWriteURB );
return result;
}
// CDC Send Encapsulated Request packet
writeSetup = kmalloc(sizeof(sURBSetupPacket *), GFP_KERNEL);
writeSetup->mRequestType = 0x21;
writeSetup->mRequestCode = 0;
writeSetup->mValue = 0;
writeSetup->mIndex = cpu_to_le16(pDev->mpIntf->cur_altsetting->desc.bInterfaceNumber);
writeSetup->mLength = cpu_to_le16(writeBufferSize);
// Create URB
usb_fill_control_urb( pWriteURB,
pDev->mpNetDev->udev,
usb_sndctrlpipe( pDev->mpNetDev->udev, 0 ),
(unsigned char *)writeSetup,
(void*)pWriteBuffer,
writeBufferSize,
NULL,
pDev );
DBG( "Actual Write:\n" );
PrintHex( pWriteBuffer, writeBufferSize );
sema_init( &writeSem, 0 );
pWriteURB->complete = WriteSyncCallback;
pWriteURB->context = &writeSem;
// Wake device
result = usb_autopm_get_interface( pDev->mpIntf );
if (result < 0)
{
DBG( "unable to resume interface: %d\n", result );
// Likely caused by device going from autosuspend -> full suspend
if (result == -EPERM)
{
#ifdef CONFIG_PM
#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,33 ))
#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,18 ))
pDev->mpNetDev->udev->auto_pm = 0;
#endif
#endif
QuecGobiNetSuspend( pDev->mpIntf, PMSG_SUSPEND );
#endif /* CONFIG_PM */
}
usb_free_urb( pWriteURB );
kfree(writeSetup);
return result;
}
// Critical section
spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
if (AddToURBList( pDev, clientID, pWriteURB ) == false)
{
usb_free_urb( pWriteURB );
kfree(writeSetup);
// End critical section
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
usb_autopm_put_interface( pDev->mpIntf );
return -EINVAL;
}
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
result = usb_submit_urb( pWriteURB, GFP_KERNEL );
spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
if (result < 0)
{
DBG( "submit URB error %d\n", result );
// Get URB back so we can destroy it
if (PopFromURBList( pDev, clientID ) != pWriteURB)
{
// This shouldn't happen
DBG( "Didn't get write URB back\n" );
}
usb_free_urb( pWriteURB );
kfree(writeSetup);
// End critical section
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
usb_autopm_put_interface( pDev->mpIntf );
return result;
}
// End critical section while we block
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
// Wait for write to finish
if (1 != 0) //(interruptible != 0)
{
// Allow user interrupts
result = down_interruptible( &writeSem );
if (result == -EINTR) {
result = down_timeout(&writeSem, msecs_to_jiffies(200));
}
}
else
{
// Ignore user interrupts
result = 0;
down( &writeSem );
}
// Write is done, release device
usb_autopm_put_interface( pDev->mpIntf );
// Verify device is still valid
if (IsDeviceValid( pDev ) == false)
{
DBG( "Invalid device!\n" );
usb_free_urb( pWriteURB );
kfree(writeSetup);
return -ENXIO;
}
// Restart critical section
spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
// Get URB back so we can destroy it
if (PopFromURBList( pDev, clientID ) != pWriteURB)
{
// This shouldn't happen
DBG( "Didn't get write URB back\n" );
// End critical section
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
usb_free_urb( pWriteURB );
kfree(writeSetup);
return -EINVAL;
}
// End critical section
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
if (result == 0)
{
// Write is finished
if (pWriteURB->status == 0)
{
// Return number of bytes that were supposed to have been written,
// not size of QMI request
result = writeBufferSize;
}
else
{
DBG( "bad status = %d\n", pWriteURB->status );
// Return error value
result = pWriteURB->status;
}
}
else
{
// We have been forcibly interrupted
DBG( "Interrupted %d !!!\n", result );
DBG( "Device may be in bad state and need reset !!!\n" );
// URB has not finished
usb_kill_urb( pWriteURB );
}
usb_free_urb( pWriteURB );
kfree(writeSetup);
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
===========================================================================*/
static int GetClientID(
sGobiUSBNet * pDev,
u8 serviceType )
{
u16 clientID;
sClientMemList ** ppClientMem;
int result;
void * pWriteBuffer;
u16 writeBufferSize;
void * pReadBuffer;
u16 readBufferSize;
unsigned long flags;
u8 transactionID;
if (IsDeviceValid( pDev ) == false)
{
DBG( "Invalid device!\n" );
return -ENXIO;
}
// Run QMI request to be asigned a Client ID
if (serviceType != 0)
{
writeBufferSize = QMICTLGetClientIDReqSize();
pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
if (pWriteBuffer == NULL)
{
return -ENOMEM;
}
transactionID = QMIXactionIDGet( pDev );
result = QMICTLGetClientIDReq( pWriteBuffer,
writeBufferSize,
transactionID,
serviceType );
if (result < 0)
{
kfree( pWriteBuffer );
return result;
}
result = WriteSync( pDev,
pWriteBuffer,
writeBufferSize,
QMICTL );
kfree( pWriteBuffer );
if (result < 0)
{
return result;
}
result = ReadSync( pDev,
&pReadBuffer,
QMICTL,
transactionID );
if (result < 0)
{
DBG( "bad read data %d\n", result );
return result;
}
readBufferSize = result;
result = QMICTLGetClientIDResp( pReadBuffer,
readBufferSize,
&clientID );
/* 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);
kfree( pReadBuffer );
if (result < 0)
{
return result;
}
}
else
{
// QMI CTL will always have client ID 0
clientID = 0;
}
// Critical section
spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
// Verify client is not already allocated
if (FindClientMem( pDev, clientID ) != NULL)
{
DBG( "Client memory already exists\n" );
// End Critical section
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
return -ETOOMANYREFS;
}
// Go to last entry in client mem list
ppClientMem = &pDev->mQMIDev.mpClientMemList;
while (*ppClientMem != NULL)
{
ppClientMem = &(*ppClientMem)->mpNext;
}
// Create locations for read to place data into
*ppClientMem = kmalloc( sizeof( sClientMemList ), GFP_ATOMIC );
if (*ppClientMem == NULL)
{
DBG( "Error allocating read list\n" );
// End critical section
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
return -ENOMEM;
}
(*ppClientMem)->mClientID = clientID;
(*ppClientMem)->mpList = NULL;
(*ppClientMem)->mpReadNotifyList = NULL;
(*ppClientMem)->mpURBList = NULL;
(*ppClientMem)->mpNext = NULL;
// Initialize workqueue for poll()
init_waitqueue_head( &(*ppClientMem)->mWaitQueue );
// End Critical section
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
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
===========================================================================*/
static void ReleaseClientID(
sGobiUSBNet * pDev,
u16 clientID )
{
int result;
sClientMemList ** ppDelClientMem;
sClientMemList * pNextClientMem;
struct urb * pDelURB;
void * pDelData;
u16 dataSize;
void * pWriteBuffer;
u16 writeBufferSize;
void * pReadBuffer;
u16 readBufferSize;
unsigned long flags;
u8 transactionID;
// Is device is still valid?
if (IsDeviceValid( pDev ) == false)
{
DBG( "invalid device\n" );
return;
}
DBG( "releasing 0x%04X\n", clientID );
// Run QMI ReleaseClientID if this isn't QMICTL
if (clientID != QMICTL && pDev->mpNetDev->udev->state)
{
// Note: all errors are non fatal, as we always want to delete
// client memory in latter part of function
writeBufferSize = QMICTLReleaseClientIDReqSize();
pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
if (pWriteBuffer == NULL)
{
DBG( "memory error\n" );
}
else
{
transactionID = QMIXactionIDGet( pDev );
result = QMICTLReleaseClientIDReq( pWriteBuffer,
writeBufferSize,
transactionID,
clientID );
if (result < 0)
{
kfree( pWriteBuffer );
DBG( "error %d filling req buffer\n", result );
}
else
{
result = WriteSync( pDev,
pWriteBuffer,
writeBufferSize,
QMICTL );
kfree( pWriteBuffer );
if (result < 0)
{
DBG( "bad write status %d\n", result );
}
else
{
result = ReadSync( pDev,
&pReadBuffer,
QMICTL,
transactionID );
if (result < 0)
{
DBG( "bad read status %d\n", result );
}
else
{
readBufferSize = result;
result = QMICTLReleaseClientIDResp( pReadBuffer,
readBufferSize );
kfree( pReadBuffer );
if (result < 0)
{
DBG( "error %d parsing response\n", result );
}
}
}
}
}
}
// Cleaning up client memory
// Critical section
spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
// Can't use FindClientMem, I need to keep pointer of previous
ppDelClientMem = &pDev->mQMIDev.mpClientMemList;
while (*ppDelClientMem != NULL)
{
if ((*ppDelClientMem)->mClientID == clientID)
{
pNextClientMem = (*ppDelClientMem)->mpNext;
// Notify all clients
while (NotifyAndPopNotifyList( pDev,
clientID,
0 ) == true );
// Kill and free all URB's
pDelURB = PopFromURBList( pDev, clientID );
while (pDelURB != NULL)
{
usb_kill_urb( pDelURB );
usb_free_urb( pDelURB );
pDelURB = PopFromURBList( pDev, clientID );
}
// Free any unread data
while (PopFromReadMemList( pDev,
clientID,
0,
&pDelData,
&dataSize ) == true )
{
kfree( pDelData );
}
// Delete client Mem
if (!waitqueue_active( &(*ppDelClientMem)->mWaitQueue))
kfree( *ppDelClientMem );
else
INFO("memory leak!\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))
ppDelClientMem = &(*ppDelClientMem)->mpNext;
}
}
// End Critical section
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
return;
}
/*===========================================================================
METHOD:
FindClientMem (Public Method)
DESCRIPTION:
Find this client's memory
Caller MUST have lock on mClientMemLock
PARAMETERS:
pDev [ I ] - Device specific memory
clientID [ I ] - Requester's client ID
RETURN VALUE:
sClientMemList - Pointer to requested sClientMemList for success
NULL for error
===========================================================================*/
static sClientMemList * FindClientMem(
sGobiUSBNet * pDev,
u16 clientID )
{
sClientMemList * pClientMem;
if (IsDeviceValid( pDev ) == false)
{
DBG( "Invalid device\n" );
return NULL;
}
#ifdef CONFIG_SMP
// Verify Lock
if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0)
{
DBG( "unlocked\n" );
BUG();
}
#endif
pClientMem = pDev->mQMIDev.mpClientMemList;
while (pClientMem != NULL)
{
if (pClientMem->mClientID == clientID)
{
// Success
VDBG("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
===========================================================================*/
static bool AddToReadMemList(
sGobiUSBNet * pDev,
u16 clientID,
u16 transactionID,
void * pData,
u16 dataSize )
{
sClientMemList * pClientMem;
sReadMemList ** ppThisReadMemList;
#ifdef CONFIG_SMP
// Verify Lock
if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0)
{
DBG( "unlocked\n" );
BUG();
}
#endif
// Get this client's memory location
pClientMem = FindClientMem( pDev, clientID );
if (pClientMem == NULL)
{
DBG( "Could not find this client's memory 0x%04X\n",
clientID );
return false;
}
// Go to last ReadMemList entry
ppThisReadMemList = &pClientMem->mpList;
while (*ppThisReadMemList != NULL)
{
ppThisReadMemList = &(*ppThisReadMemList)->mpNext;
}
*ppThisReadMemList = kmalloc( sizeof( sReadMemList ), GFP_ATOMIC );
if (*ppThisReadMemList == NULL)
{
DBG( "Mem error\n" );
return false;
}
(*ppThisReadMemList)->mpNext = NULL;
(*ppThisReadMemList)->mpData = pData;
(*ppThisReadMemList)->mDataSize = dataSize;
(*ppThisReadMemList)->mTransactionID = transactionID;
return true;
}
/*===========================================================================
METHOD:
PopFromReadMemList (Public Method)
DESCRIPTION:
Remove data from this client's ReadMem list if it matches
the specified transaction ID.
Caller MUST have lock on mClientMemLock
PARAMETERS:
pDev [ I ] - Device specific memory
clientID [ I ] - Requester's client ID
transactionID [ I ] - Transaction ID or 0 for any
ppData [I/O] - On success, will be filled with a
pointer to read buffer
pDataSize [I/O] - On succces, will be filled with the
read buffer's size
RETURN VALUE:
bool
===========================================================================*/
static bool PopFromReadMemList(
sGobiUSBNet * pDev,
u16 clientID,
u16 transactionID,
void ** ppData,
u16 * pDataSize )
{
sClientMemList * pClientMem;
sReadMemList * pDelReadMemList, ** ppReadMemList;
#ifdef CONFIG_SMP
// Verify Lock
if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0)
{
DBG( "unlocked\n" );
BUG();
}
#endif
// Get this client's memory location
pClientMem = FindClientMem( pDev, clientID );
if (pClientMem == NULL)
{
DBG( "Could not find this client's memory 0x%04X\n",
clientID );
return false;
}
ppReadMemList = &(pClientMem->mpList);
pDelReadMemList = NULL;
// Find first message that matches this transaction ID
while (*ppReadMemList != NULL)
{
// Do we care about transaction ID?
if (transactionID == 0
|| transactionID == (*ppReadMemList)->mTransactionID )
{
pDelReadMemList = *ppReadMemList;
VDBG( "*ppReadMemList = 0x%p pDelReadMemList = 0x%p\n",
*ppReadMemList, pDelReadMemList );
break;
}
VDBG( "skipping 0x%04X data TID = %x\n", clientID, (*ppReadMemList)->mTransactionID );
// Next
ppReadMemList = &(*ppReadMemList)->mpNext;
}
VDBG( "*ppReadMemList = 0x%p pDelReadMemList = 0x%p\n",
*ppReadMemList, pDelReadMemList );
if (pDelReadMemList != NULL)
{
*ppReadMemList = (*ppReadMemList)->mpNext;
// Copy to output
*ppData = pDelReadMemList->mpData;
*pDataSize = pDelReadMemList->mDataSize;
VDBG( "*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 = %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
===========================================================================*/
static bool AddToNotifyList(
sGobiUSBNet * pDev,
u16 clientID,
u16 transactionID,
void (* pNotifyFunct)(sGobiUSBNet *, u16, void *),
void * pData )
{
sClientMemList * pClientMem;
sNotifyList ** ppThisNotifyList;
#ifdef CONFIG_SMP
// Verify Lock
if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0)
{
DBG( "unlocked\n" );
BUG();
}
#endif
// Get this client's memory location
pClientMem = FindClientMem( pDev, clientID );
if (pClientMem == NULL)
{
DBG( "Could not find this client's memory 0x%04X\n", clientID );
return false;
}
// Go to last URBList entry
ppThisNotifyList = &pClientMem->mpReadNotifyList;
while (*ppThisNotifyList != NULL)
{
ppThisNotifyList = &(*ppThisNotifyList)->mpNext;
}
*ppThisNotifyList = kmalloc( sizeof( sNotifyList ), GFP_ATOMIC );
if (*ppThisNotifyList == NULL)
{
DBG( "Mem error\n" );
return false;
}
(*ppThisNotifyList)->mpNext = NULL;
(*ppThisNotifyList)->mpNotifyFunct = pNotifyFunct;
(*ppThisNotifyList)->mpData = pData;
(*ppThisNotifyList)->mTransactionID = transactionID;
return true;
}
/*===========================================================================
METHOD:
NotifyAndPopNotifyList (Public Method)
DESCRIPTION:
Remove first Notify entry from this client's notify list
and Run function
Caller MUST have lock on mClientMemLock
PARAMETERS:
pDev [ I ] - Device specific memory
clientID [ I ] - Requester's client ID
transactionID [ I ] - Transaction ID or 0 for any
RETURN VALUE:
bool
===========================================================================*/
static bool NotifyAndPopNotifyList(
sGobiUSBNet * pDev,
u16 clientID,
u16 transactionID )
{
sClientMemList * pClientMem;
sNotifyList * pDelNotifyList, ** ppNotifyList;
#ifdef CONFIG_SMP
// Verify Lock
if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0)
{
DBG( "unlocked\n" );
BUG();
}
#endif
// Get this client's memory location
pClientMem = FindClientMem( pDev, clientID );
if (pClientMem == NULL)
{
DBG( "Could not find this client's memory 0x%04X\n", clientID );
return false;
}
ppNotifyList = &(pClientMem->mpReadNotifyList);
pDelNotifyList = NULL;
// Remove from list
while (*ppNotifyList != NULL)
{
// Do we care about transaction ID?
if (transactionID == 0
|| (*ppNotifyList)->mTransactionID == 0
|| transactionID == (*ppNotifyList)->mTransactionID)
{
pDelNotifyList = *ppNotifyList;
break;
}
DBG( "skipping data TID = %x\n", (*ppNotifyList)->mTransactionID );
// next
ppNotifyList = &(*ppNotifyList)->mpNext;
}
if (pDelNotifyList != NULL)
{
// Remove element
*ppNotifyList = (*ppNotifyList)->mpNext;
// Run notification function
if (pDelNotifyList->mpNotifyFunct != NULL)
{
// Unlock for callback
spin_unlock( &pDev->mQMIDev.mClientMemLock );
pDelNotifyList->mpNotifyFunct( pDev,
clientID,
pDelNotifyList->mpData );
// Restore lock
spin_lock( &pDev->mQMIDev.mClientMemLock );
}
// Delete memory
kfree( pDelNotifyList );
return true;
}
else
{
DBG( "no one to notify for TID %x\n", transactionID );
return false;
}
}
/*===========================================================================
METHOD:
AddToURBList (Public Method)
DESCRIPTION:
Add URB to this client's URB list
Caller MUST have lock on mClientMemLock
PARAMETERS:
pDev [ I ] - Device specific memory
clientID [ I ] - Requester's client ID
pURB [ I ] - URB to be added
RETURN VALUE:
bool
===========================================================================*/
static bool AddToURBList(
sGobiUSBNet * pDev,
u16 clientID,
struct urb * pURB )
{
sClientMemList * pClientMem;
sURBList ** ppThisURBList;
#ifdef CONFIG_SMP
// Verify Lock
if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0)
{
DBG( "unlocked\n" );
BUG();
}
#endif
// Get this client's memory location
pClientMem = FindClientMem( pDev, clientID );
if (pClientMem == NULL)
{
DBG( "Could not find this client's memory 0x%04X\n", clientID );
return false;
}
// Go to last URBList entry
ppThisURBList = &pClientMem->mpURBList;
while (*ppThisURBList != NULL)
{
ppThisURBList = &(*ppThisURBList)->mpNext;
}
*ppThisURBList = kmalloc( sizeof( sURBList ), GFP_ATOMIC );
if (*ppThisURBList == NULL)
{
DBG( "Mem error\n" );
return false;
}
(*ppThisURBList)->mpNext = NULL;
(*ppThisURBList)->mpURB = pURB;
return true;
}
/*===========================================================================
METHOD:
PopFromURBList (Public Method)
DESCRIPTION:
Remove URB from this client's URB list
Caller MUST have lock on mClientMemLock
PARAMETERS:
pDev [ I ] - Device specific memory
clientID [ I ] - Requester's client ID
RETURN VALUE:
struct urb - Pointer to requested client's URB
NULL for error
===========================================================================*/
static struct urb * PopFromURBList(
sGobiUSBNet * pDev,
u16 clientID )
{
sClientMemList * pClientMem;
sURBList * pDelURBList;
struct urb * pURB;
#ifdef CONFIG_SMP
// Verify Lock
if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0)
{
DBG( "unlocked\n" );
BUG();
}
#endif
// Get this client's memory location
pClientMem = FindClientMem( pDev, clientID );
if (pClientMem == NULL)
{
DBG( "Could not find this client's memory 0x%04X\n", clientID );
return NULL;
}
// Remove from list
if (pClientMem->mpURBList != NULL)
{
pDelURBList = pClientMem->mpURBList;
pClientMem->mpURBList = pClientMem->mpURBList->mpNext;
// Copy to output
pURB = pDelURBList->mpURB;
// Delete memory
kfree( pDelURBList );
return pURB;
}
else
{
DBG( "No URB's to pop\n" );
return NULL;
}
}
#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 3,19,0 ))
#ifndef f_dentry
#define f_dentry f_path.dentry
#endif
#endif
/*=========================================================================*/
// 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
===========================================================================*/
static long UserspaceunlockedIOCTL(
struct file * pFilp,
unsigned int cmd,
unsigned long arg )
{
int result;
u32 devVIDPID;
sQMIFilpStorage * pFilpData = (sQMIFilpStorage *)pFilp->private_data;
if (pFilpData == NULL)
{
DBG( "Bad file data\n" );
return -EBADF;
}
if (IsDeviceValid( pFilpData->mpDev ) == false)
{
DBG( "Invalid device! Updating f_ops\n" );
pFilp->f_op = pFilp->f_dentry->d_inode->i_fop;
return -ENXIO;
}
switch (cmd)
{
case IOCTL_QMI_GET_SERVICE_FILE:
DBG( "Setting up QMI for service %lu\n", arg );
if ((u8)arg == 0)
{
DBG( "Cannot use QMICTL from userspace\n" );
return -EINVAL;
}
// Connection is already setup
if (pFilpData->mClientID != (u16)-1)
{
DBG( "Close the current connection before opening a new one\n" );
return -EBADR;
}
result = GetClientID( pFilpData->mpDev, (u8)arg );
// it seems QMIWDA only allow one client, if the last quectel-CM donot realese it (killed by SIGKILL).
// can force release it at here
#if 1
if (result < 0 && (u8)arg == QMIWDA)
{
ReleaseClientID( pFilpData->mpDev, QMIWDA | (1 << 8) );
result = GetClientID( pFilpData->mpDev, (u8)arg );
}
#endif
if (result < 0)
{
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], 14 );
if (result != 0)
{
DBG( "Copy to userspace failure %d\n", result );
}
return result;
break;
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
===========================================================================*/
static int UserspaceOpen(
struct inode * pInode,
struct file * pFilp )
{
sQMIFilpStorage * pFilpData;
// Optain device pointer from pInode
sQMIDev * pQMIDev = container_of( pInode->i_cdev,
sQMIDev,
mCdev );
sGobiUSBNet * pDev = container_of( pQMIDev,
sGobiUSBNet,
mQMIDev );
if (pDev->mbMdm9x07)
{
atomic_inc(&pDev->refcount);
if (!pDev->mbQMIReady) {
if (wait_for_completion_interruptible_timeout(&pDev->mQMIReadyCompletion, 15*HZ) <= 0) {
if (atomic_dec_and_test(&pDev->refcount)) {
kfree( pDev );
}
return -ETIMEDOUT;
}
}
atomic_dec(&pDev->refcount);
}
if (IsDeviceValid( pDev ) == false)
{
DBG( "Invalid device\n" );
return -ENXIO;
}
// Setup data in pFilp->private_data
pFilp->private_data = kmalloc( sizeof( sQMIFilpStorage ), GFP_KERNEL );
if (pFilp->private_data == NULL)
{
DBG( "Mem error\n" );
return -ENOMEM;
}
pFilpData = (sQMIFilpStorage *)pFilp->private_data;
pFilpData->mClientID = (u16)-1;
pFilpData->mpDev = pDev;
atomic_inc(&pFilpData->mpDev->refcount);
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
===========================================================================*/
#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,36 ))
static int UserspaceIOCTL(
struct inode * pUnusedInode,
struct file * pFilp,
unsigned int cmd,
unsigned long arg )
{
// call the internal wrapper function
return (int)UserspaceunlockedIOCTL( pFilp, cmd, arg );
}
#endif
#ifdef quectel_no_for_each_process
static int UserspaceClose(
struct inode * pInode,
struct file * pFilp )
{
sQMIFilpStorage * pFilpData = (sQMIFilpStorage *)pFilp->private_data;
if (pFilpData == NULL)
{
DBG( "bad file data\n" );
return -EBADF;
}
atomic_dec(&pFilpData->mpDev->refcount);
if (IsDeviceValid( pFilpData->mpDev ) == false)
{
return -ENXIO;
}
DBG( "0x%04X\n", pFilpData->mClientID );
// Disable pFilpData so they can't keep sending read or write
// should this function hang
// Note: memory pointer is still saved in pFilpData to be deleted later
pFilp->private_data = NULL;
if (pFilpData->mClientID != (u16)-1)
{
if (pFilpData->mpDev->mbDeregisterQMIDevice)
pFilpData->mClientID = (u16)-1; //DeregisterQMIDevice() will release this ClientID
else
ReleaseClientID( pFilpData->mpDev,
pFilpData->mClientID );
}
kfree( pFilpData );
return 0;
}
#else
/*===========================================================================
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
===========================================================================*/
#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,14 ))
int UserspaceClose(
struct file * pFilp,
fl_owner_t unusedFileTable )
#else
int UserspaceClose( struct file * pFilp )
#endif
{
sQMIFilpStorage * pFilpData = (sQMIFilpStorage *)pFilp->private_data;
struct task_struct * pEachTask;
struct fdtable * pFDT;
int count = 0;
int used = 0;
unsigned long flags;
if (pFilpData == NULL)
{
DBG( "bad file data\n" );
return -EBADF;
}
// Fallthough. If f_count == 1 no need to do more checks
#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,24 ))
if (atomic_read( &pFilp->f_count ) != 1)
#else
if (atomic_long_read( &pFilp->f_count ) != 1)
#endif
{
rcu_read_lock();
for_each_process( pEachTask )
{
task_lock(pEachTask);
if (pEachTask == NULL || pEachTask->files == NULL)
{
// Some tasks may not have files (e.g. Xsession)
task_unlock(pEachTask);
continue;
}
spin_lock_irqsave( &pEachTask->files->file_lock, flags );
task_unlock(pEachTask); //kernel/exit.c:do_exit() -> fs/file.c:exit_files()
pFDT = files_fdtable( pEachTask->files );
for (count = 0; count < pFDT->max_fds; count++)
{
// Before this function was called, this file was removed
// from our task's file table so if we find it in a file
// table then it is being used by another task
if (pFDT->fd[count] == pFilp)
{
used++;
break;
}
}
spin_unlock_irqrestore( &pEachTask->files->file_lock, flags );
}
rcu_read_unlock();
if (used > 0)
{
DBG( "not closing, as this FD is open by %d other process\n", used );
return 0;
}
}
if (IsDeviceValid( pFilpData->mpDev ) == false)
{
DBG( "Invalid device! Updating f_ops\n" );
pFilp->f_op = pFilp->f_dentry->d_inode->i_fop;
return -ENXIO;
}
DBG( "0x%04X\n", pFilpData->mClientID );
// Disable pFilpData so they can't keep sending read or write
// should this function hang
// Note: memory pointer is still saved in pFilpData to be deleted later
pFilp->private_data = NULL;
if (pFilpData->mClientID != (u16)-1)
{
if (pFilpData->mpDev->mbDeregisterQMIDevice)
pFilpData->mClientID = (u16)-1; //DeregisterQMIDevice() will release this ClientID
else
ReleaseClientID( pFilpData->mpDev,
pFilpData->mClientID );
}
atomic_dec(&pFilpData->mpDev->refcount);
kfree( pFilpData );
return 0;
}
#endif
/*===========================================================================
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
===========================================================================*/
static ssize_t UserspaceRead(
struct file * pFilp,
char __user * pBuf,
size_t size,
loff_t * pUnusedFpos )
{
int result;
void * pReadData = NULL;
void * pSmallReadData;
sQMIFilpStorage * pFilpData = (sQMIFilpStorage *)pFilp->private_data;
if (pFilpData == NULL)
{
DBG( "Bad file data\n" );
return -EBADF;
}
if (IsDeviceValid( pFilpData->mpDev ) == false)
{
DBG( "Invalid device! Updating f_ops\n" );
pFilp->f_op = pFilp->f_dentry->d_inode->i_fop;
return -ENXIO;
}
if (pFilpData->mClientID == (u16)-1)
{
DBG( "Client ID must be set before reading 0x%04X\n",
pFilpData->mClientID );
return -EBADR;
}
// Perform synchronous read
result = ReadSync( pFilpData->mpDev,
&pReadData,
pFilpData->mClientID,
0 );
if (result <= 0)
{
return result;
}
// Discard QMUX header
result -= QMUXHeaderSize();
pSmallReadData = pReadData + QMUXHeaderSize();
if (result > size)
{
DBG( "Read data is too large for amount user has requested\n" );
kfree( pReadData );
return -EOVERFLOW;
}
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;
}
// 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
===========================================================================*/
static ssize_t UserspaceWrite(
struct file * pFilp,
const char __user * pBuf,
size_t size,
loff_t * pUnusedFpos )
{
int status;
void * pWriteBuffer;
sQMIFilpStorage * pFilpData = (sQMIFilpStorage *)pFilp->private_data;
if (pFilpData == NULL)
{
DBG( "Bad file data\n" );
return -EBADF;
}
if (IsDeviceValid( pFilpData->mpDev ) == false)
{
DBG( "Invalid device! Updating f_ops\n" );
pFilp->f_op = pFilp->f_dentry->d_inode->i_fop;
return -ENXIO;
}
if (pFilpData->mClientID == (u16)-1)
{
DBG( "Client ID must be set before writing 0x%04X\n",
pFilpData->mClientID );
return -EBADR;
}
// Copy data from user to kernel space
pWriteBuffer = kmalloc( size + QMUXHeaderSize(), GFP_KERNEL );
if (pWriteBuffer == NULL)
{
return -ENOMEM;
}
status = copy_from_user( pWriteBuffer + QMUXHeaderSize(), pBuf, size );
if (status != 0)
{
DBG( "Unable to copy data from userspace %d\n", status );
kfree( pWriteBuffer );
return status;
}
status = WriteSync( pFilpData->mpDev,
pWriteBuffer,
size + QMUXHeaderSize(),
pFilpData->mClientID );
kfree( pWriteBuffer );
// On success, return requested size, not full QMI reqest size
if (status == size + QMUXHeaderSize())
{
return size;
}
else
{
return status;
}
}
/*===========================================================================
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
===========================================================================*/
static 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 long 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" );
pFilp->f_op = pFilp->f_dentry->d_inode->i_fop;
return POLLERR;
}
if (pFilpData->mpDev->mbDeregisterQMIDevice)
{
DBG( "DeregisterQMIDevice ing\n" );
return POLLHUP | POLLERR;
}
if (pFilpData->mClientID == (u16)-1)
{
DBG( "Client ID must be set before polling 0x%04X\n",
pFilpData->mClientID );
return POLLERR;
}
// Critical section
spin_lock_irqsave( &pFilpData->mpDev->mQMIDev.mClientMemLock, flags );
// 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 );
spin_unlock_irqrestore( &pFilpData->mpDev->mQMIDev.mClientMemLock,
flags );
return POLLERR;
}
poll_wait( pFilp, &pClientMem->mWaitQueue, pPollTable );
if (pClientMem->mpList != NULL)
{
status |= POLLIN | POLLRDNORM;
}
// End critical section
spin_unlock_irqrestore( &pFilpData->mpDev->mQMIDev.mClientMemLock, flags );
// Always ready to write
return (status | POLLOUT | POLLWRNORM);
}
/*=========================================================================*/
// Initializer and destructor
/*=========================================================================*/
static int QMICTLSyncProc(sGobiUSBNet *pDev)
{
void *pWriteBuffer;
void *pReadBuffer;
int result;
u16 writeBufferSize;
u16 readBufferSize;
u8 transactionID;
unsigned long flags;
if (IsDeviceValid( pDev ) == false)
{
DBG( "Invalid device\n" );
return -EFAULT;
}
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 = WriteSync( pDev,
pWriteBuffer,
writeBufferSize,
QMICTL );
if (result < 0)
{
kfree( pWriteBuffer );
return result;
}
// QMI CTL Sync Response
result = ReadSync( pDev,
&pReadBuffer,
QMICTL,
transactionID );
if (result < 0)
{
return result;
}
result = QMICTLSyncResp( pReadBuffer,
(u16)result );
kfree( pReadBuffer );
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;
}
#if 1 //free these ununsed qmi response, or when these transactionID re-used, they will be regarded as qmi response of the qmi request that have same transactionID
// Enter critical section
spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
// Free any unread data
while (PopFromReadMemList( pDev, QMICTL, 0, &pReadBuffer, &readBufferSize) == true) {
kfree( pReadBuffer );
}
// End critical section
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
#endif
// Success
return 0;
}
static int qmi_sync_thread(void *data) {
sGobiUSBNet * pDev = (sGobiUSBNet *)data;
int result = 0;
#if 1
// Device is not ready for QMI connections right away
// Wait up to 30 seconds before failing
if (QMIReady( pDev, 30000 ) == false)
{
DBG( "Device unresponsive to QMI\n" );
goto __qmi_sync_finished;
}
// Initiate QMI CTL Sync Procedure
DBG( "Sending QMI CTL Sync Request\n" );
result = QMICTLSyncProc(pDev);
if (result != 0)
{
DBG( "QMI CTL Sync Procedure Error\n" );
goto __qmi_sync_finished;
}
else
{
DBG( "QMI CTL Sync Procedure Successful\n" );
}
if (pDev->m_qmap_mode) {
// Setup Data Format
int rx_urb_size = 0;
result = QMIWDASetDataFormat (pDev, pDev->m_qmap_mode, &rx_urb_size);
if (result != 0)
{
goto __qmi_sync_finished;
}
pDev->mpNetDev->rx_urb_size = rx_urb_size;
}
// Setup WDS callback
result = SetupQMIWDSCallback( pDev );
if (result != 0)
{
goto __qmi_sync_finished;
}
// Fill MEID for device
result = QMIDMSGetMEID( pDev );
if (result != 0)
{
goto __qmi_sync_finished;
}
#endif
__qmi_sync_finished:
pDev->mbQMIReady = true;
complete_all(&pDev->mQMIReadyCompletion);
if (atomic_dec_and_test(&pDev->refcount)) {
kfree( pDev );
}
return result;
}
/*===========================================================================
METHOD:
RegisterQMIDevice (Public Method)
DESCRIPTION:
QMI Device initialization function
PARAMETERS:
pDev [ I ] - Device specific memory
RETURN VALUE:
int - 0 for success
Negative errno for failure
===========================================================================*/
int RegisterQMIDevice( sGobiUSBNet * pDev )
{
int result;
int GobiQMIIndex = 0;
dev_t devno;
char * pDevName;
if (pDev->mQMIDev.mbCdevIsInitialized == true)
{
// Should never happen, but always better to check
DBG( "device already exists\n" );
return -EEXIST;
}
pDev->mbQMIValid = true;
pDev->mbDeregisterQMIDevice = false;
// Set up for QMICTL
// (does not send QMI message, just sets up memory)
result = GetClientID( pDev, QMICTL );
if (result != 0)
{
pDev->mbQMIValid = false;
return result;
}
atomic_set( &pDev->mQMIDev.mQMICTLTransactionID, 1 );
// Start Async reading
result = StartRead( pDev );
if (result != 0)
{
pDev->mbQMIValid = false;
return result;
}
if (pDev->mbMdm9x07)
{
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 );
}
//for EC21&25, must wait about 15 seconds to wait QMI ready. it is too long for driver probe(will block other drivers probe).
if (pDev->mbMdm9x07)
{
struct task_struct *qmi_sync_task;
atomic_inc(&pDev->refcount);
init_completion(&pDev->mQMIReadyCompletion);
pDev->mbQMIReady = false;
qmi_sync_task = kthread_run(qmi_sync_thread, (void *)pDev, "qmi_sync/%d", pDev->mpNetDev->udev->devnum);
if (IS_ERR(qmi_sync_task)) {
atomic_dec(&pDev->refcount);
DBG( "Create qmi_sync_thread fail\n" );
return PTR_ERR(qmi_sync_task);
}
goto __register_chardev_qccmi;
}
// Device is not ready for QMI connections right away
// Wait up to 30 seconds before failing
if (QMIReady( pDev, 30000 ) == false)
{
DBG( "Device unresponsive to QMI\n" );
return -ETIMEDOUT;
}
// Initiate QMI CTL Sync Procedure
DBG( "Sending QMI CTL Sync Request\n" );
result = QMICTLSyncProc(pDev);
if (result != 0)
{
DBG( "QMI CTL Sync Procedure Error\n" );
return result;
}
else
{
DBG( "QMI CTL Sync Procedure Successful\n" );
}
// Setup Data Format
result = QMIWDASetDataFormat (pDev, pDev->m_qmap_mode, NULL);
if (result != 0)
{
return result;
}
// Setup WDS callback
result = SetupQMIWDSCallback( pDev );
if (result != 0)
{
return result;
}
// Fill MEID for device
result = QMIDMSGetMEID( pDev );
if (result != 0)
{
return result;
}
__register_chardev_qccmi:
// allocate and fill devno with numbers
result = alloc_chrdev_region( &devno, 0, 1, "qcqmi" );
if (result < 0)
{
return result;
}
// Create cdev
cdev_init( &pDev->mQMIDev.mCdev, &UserspaceQMIFops );
pDev->mQMIDev.mCdev.owner = THIS_MODULE;
pDev->mQMIDev.mCdev.ops = &UserspaceQMIFops;
pDev->mQMIDev.mbCdevIsInitialized = true;
result = cdev_add( &pDev->mQMIDev.mCdev, devno, 1 );
if (result != 0)
{
DBG( "error adding cdev\n" );
return result;
}
// Match interface number (usb# or eth#)
if (!!(pDevName = strstr( pDev->mpNetDev->net->name, "eth" ))) {
pDevName += strlen( "eth" );
} else if (!!(pDevName = strstr( pDev->mpNetDev->net->name, "usb" ))) {
pDevName += strlen( "usb" );
#if 1 //openWRT like use ppp# or lte#
} else if (!!(pDevName = strstr( pDev->mpNetDev->net->name, "ppp" ))) {
pDevName += strlen( "ppp" );
} else if (!!(pDevName = strstr( pDev->mpNetDev->net->name, "lte" ))) {
pDevName += strlen( "lte" );
#endif
} else {
DBG( "Bad net name: %s\n", pDev->mpNetDev->net->name );
return -ENXIO;
}
GobiQMIIndex = simple_strtoul( pDevName, NULL, 10 );
if (GobiQMIIndex < 0)
{
DBG( "Bad minor number\n" );
return -ENXIO;
}
// Always print this output
printk( KERN_INFO "creating qcqmi%d\n",
GobiQMIIndex );
#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 2,6,27 ))
// kernel 2.6.27 added a new fourth parameter to device_create
// void * drvdata : the data to be added to the device for callbacks
device_create( pDev->mQMIDev.mpDevClass,
&pDev->mpIntf->dev,
devno,
NULL,
"qcqmi%d",
GobiQMIIndex );
#else
device_create( pDev->mQMIDev.mpDevClass,
&pDev->mpIntf->dev,
devno,
"qcqmi%d",
GobiQMIIndex );
#endif
pDev->mQMIDev.mDevNum = devno;
// Success
return 0;
}
/*===========================================================================
METHOD:
DeregisterQMIDevice (Public Method)
DESCRIPTION:
QMI Device cleanup function
NOTE: When this function is run the device is no longer valid
PARAMETERS:
pDev [ I ] - Device specific memory
RETURN VALUE:
None
===========================================================================*/
void DeregisterQMIDevice( sGobiUSBNet * pDev )
{
#ifndef quectel_no_for_each_process
struct inode * pOpenInode;
struct list_head * pInodeList;
struct task_struct * pEachTask;
struct fdtable * pFDT;
struct file * pFilp;
int count = 0;
#endif
unsigned long flags;
int tries;
int result;
// Should never happen, but check anyway
if (IsDeviceValid( pDev ) == false)
{
DBG( "wrong device\n" );
return;
}
pDev->mbDeregisterQMIDevice = true;
// Release all clients
spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
while (pDev->mQMIDev.mpClientMemList != NULL)
{
u16 mClientID = pDev->mQMIDev.mpClientMemList->mClientID;
if (waitqueue_active(&pDev->mQMIDev.mpClientMemList->mWaitQueue)) {
DBG("WaitQueue 0x%04X\n", mClientID);
wake_up_interruptible_sync( &pDev->mQMIDev.mpClientMemList->mWaitQueue );
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
msleep(10);
spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
continue;
}
DBG( "release 0x%04X\n", pDev->mQMIDev.mpClientMemList->mClientID );
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
ReleaseClientID( pDev, mClientID );
// NOTE: pDev->mQMIDev.mpClientMemList will
// be updated in ReleaseClientID()
spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
}
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
// Stop all reads
KillRead( pDev );
pDev->mbQMIValid = false;
if (pDev->mQMIDev.mbCdevIsInitialized == false)
{
return;
}
#ifndef quectel_no_for_each_process
// Find each open file handle, and manually close it
// Generally there will only be only one inode, but more are possible
list_for_each( pInodeList, &pDev->mQMIDev.mCdev.list )
{
// Get the inode
pOpenInode = container_of( pInodeList, struct inode, i_devices );
if (pOpenInode != NULL && (IS_ERR( pOpenInode ) == false))
{
// Look for this inode in each task
rcu_read_lock();
for_each_process( pEachTask )
{
task_lock(pEachTask);
if (pEachTask == NULL || pEachTask->files == NULL)
{
// Some tasks may not have files (e.g. Xsession)
task_unlock(pEachTask);
continue;
}
// For each file this task has open, check if it's referencing
// our inode.
spin_lock_irqsave( &pEachTask->files->file_lock, flags );
task_unlock(pEachTask); //kernel/exit.c:do_exit() -> fs/file.c:exit_files()
pFDT = files_fdtable( pEachTask->files );
for (count = 0; count < pFDT->max_fds; count++)
{
pFilp = pFDT->fd[count];
if (pFilp != NULL && pFilp->f_dentry != NULL)
{
if (pFilp->f_dentry->d_inode == pOpenInode)
{
// Close this file handle
rcu_assign_pointer( pFDT->fd[count], NULL );
spin_unlock_irqrestore( &pEachTask->files->file_lock, flags );
DBG( "forcing close of open file handle\n" );
filp_close( pFilp, pEachTask->files );
spin_lock_irqsave( &pEachTask->files->file_lock, flags );
}
}
}
spin_unlock_irqrestore( &pEachTask->files->file_lock, flags );
}
rcu_read_unlock();
}
}
#endif
if (pDev->mpNetDev->udev->state) {
// 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 );
}
// Hold onto cdev memory location until everyone is through using it.
// Timeout after 30 seconds (10 ms interval). Timeout should never happen,
// but exists to prevent an infinate loop just in case.
for (tries = 0; tries < 30 * 100; tries++)
{
#if (LINUX_VERSION_CODE < KERNEL_VERSION( 4,11,0 ))
int ref = atomic_read( &pDev->mQMIDev.mCdev.kobj.kref.refcount );
#else
int ref = kref_read( &pDev->mQMIDev.mCdev.kobj.kref );
#endif
if (ref > 1)
{
DBG( "cdev in use by %d tasks\n", ref - 1 );
if (tries > 10)
INFO( "cdev in use by %d tasks\n", ref - 1 );
msleep( 10 );
}
else
{
break;
}
}
cdev_del( &pDev->mQMIDev.mCdev );
unregister_chrdev_region( pDev->mQMIDev.mDevNum, 1 );
return;
}
/*=========================================================================*/
// Driver level client management
/*=========================================================================*/
/*===========================================================================
METHOD:
QMIReady (Public Method)
DESCRIPTION:
Send QMI CTL GET VERSION INFO REQ 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:
bool
===========================================================================*/
static bool QMIReady(
sGobiUSBNet * pDev,
u16 timeout )
{
int result;
void * pWriteBuffer;
u16 writeBufferSize;
void * pReadBuffer;
u16 readBufferSize;
u16 curTime;
unsigned long flags;
u8 transactionID;
u16 interval = 2000;
if (IsDeviceValid( pDev ) == false)
{
DBG( "Invalid device\n" );
return false;
}
writeBufferSize = QMICTLReadyReqSize();
pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
if (pWriteBuffer == NULL)
{
return false;
}
// An implimentation of down_timeout has not been agreed on,
// so it's been added and removed from the kernel several times.
// We're just going to ignore it and poll the semaphore.
// Send a write every 1000 ms and see if we get a response
for (curTime = 0; curTime < timeout; curTime += interval)
{
// Start read
struct QuecSem *readSem = kmalloc(sizeof(struct QuecSem ), GFP_KERNEL);
readSem->magic = QUEC_SEM_MAGIC;
sema_init( &readSem->readSem, 0 );
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 1
if (down_timeout( &readSem->readSem, msecs_to_jiffies(interval) ) == 0)
#else
msleep( interval );
if (down_trylock( &readSem->readSem ) == 0)
#endif
{
kfree(readSem);
// Enter critical section
spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
// Pop the read data
if (PopFromReadMemList( pDev,
QMICTL,
transactionID,
&pReadBuffer,
&readBufferSize ) == true)
{
// Success
// End critical section
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
// We don't care about the result
kfree( pReadBuffer );
break;
}
else
{
// Read mismatch/failure, unlock and continue
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
}
}
else
{
readSem->magic = 0;
// Enter critical section
spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
// Timeout, remove the async read
NotifyAndPopNotifyList( pDev, QMICTL, transactionID );
// End critical section
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
}
}
kfree( pWriteBuffer );
// Did we time out?
if (curTime >= timeout)
{
return false;
}
DBG( "QMI Ready after %u milliseconds\n", curTime );
// Success
return true;
}
/*===========================================================================
METHOD:
QMIWDSCallback (Public Method)
DESCRIPTION:
QMI WDS callback function
Update net stats or link state
PARAMETERS:
pDev [ I ] - Device specific memory
clientID [ I ] - Client ID
pData [ I ] - Callback data (unused)
RETURN VALUE:
None
===========================================================================*/
static void QMIWDSCallback(
sGobiUSBNet * pDev,
u16 clientID,
void * pData )
{
bool bRet;
int result;
void * pReadBuffer;
u16 readBufferSize;
#if 0
#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,31 ))
struct net_device_stats * pStats = &(pDev->mpNetDev->stats);
#else
struct net_device_stats * pStats = &(pDev->mpNetDev->net->stats);
#endif
#endif
u32 TXOk = (u32)-1;
u32 RXOk = (u32)-1;
u32 TXErr = (u32)-1;
u32 RXErr = (u32)-1;
u32 TXOfl = (u32)-1;
u32 RXOfl = (u32)-1;
u64 TXBytesOk = (u64)-1;
u64 RXBytesOk = (u64)-1;
bool bLinkState;
bool bReconfigure;
unsigned long flags;
if (IsDeviceValid( pDev ) == false)
{
DBG( "Invalid device\n" );
return;
}
// Critical section
spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
bRet = PopFromReadMemList( pDev,
clientID,
0,
&pReadBuffer,
&readBufferSize );
// End critical section
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
if (bRet == false)
{
DBG( "WDS callback failed to get data\n" );
return;
}
// Default values
bLinkState = ! GobiTestDownReason( pDev, NO_NDIS_CONNECTION );
bReconfigure = false;
result = QMIWDSEventResp( pReadBuffer,
readBufferSize,
&TXOk,
&RXOk,
&TXErr,
&RXErr,
&TXOfl,
&RXOfl,
&TXBytesOk,
&RXBytesOk,
&bLinkState,
&bReconfigure );
if (result < 0)
{
DBG( "bad WDS packet\n" );
}
else
{
#if 0 //usbbet.c will do this job
// Fill in new values, ignore max values
if (TXOfl != (u32)-1)
{
pStats->tx_fifo_errors = TXOfl;
}
if (RXOfl != (u32)-1)
{
pStats->rx_fifo_errors = RXOfl;
}
if (TXErr != (u32)-1)
{
pStats->tx_errors = TXErr;
}
if (RXErr != (u32)-1)
{
pStats->rx_errors = RXErr;
}
if (TXOk != (u32)-1)
{
pStats->tx_packets = TXOk + pStats->tx_errors;
}
if (RXOk != (u32)-1)
{
pStats->rx_packets = RXOk + pStats->rx_errors;
}
if (TXBytesOk != (u64)-1)
{
pStats->tx_bytes = TXBytesOk;
}
if (RXBytesOk != (u64)-1)
{
pStats->rx_bytes = RXBytesOk;
}
#endif
if (bReconfigure == true)
{
DBG( "Net device link reset\n" );
GobiSetDownReason( pDev, NO_NDIS_CONNECTION );
GobiClearDownReason( pDev, NO_NDIS_CONNECTION );
}
else
{
if (bLinkState == true)
{
if (GobiTestDownReason( pDev, NO_NDIS_CONNECTION )) {
DBG( "Net device link is connected\n" );
GobiClearDownReason( pDev, NO_NDIS_CONNECTION );
}
}
else
{
if (!GobiTestDownReason( pDev, NO_NDIS_CONNECTION )) {
DBG( "Net device link is disconnected\n" );
GobiSetDownReason( pDev, NO_NDIS_CONNECTION );
}
}
}
}
kfree( pReadBuffer );
// Setup next read
result = ReadAsync( pDev,
clientID,
0,
QMIWDSCallback,
pData );
if (result != 0)
{
DBG( "unable to setup next async read\n" );
}
return;
}
/*===========================================================================
METHOD:
SetupQMIWDSCallback (Public Method)
DESCRIPTION:
Request client and fire off reqests and start async read for
QMI WDS callback
PARAMETERS:
pDev [ I ] - Device specific memory
RETURN VALUE:
int - 0 for success
Negative errno for failure
===========================================================================*/
static int SetupQMIWDSCallback( sGobiUSBNet * pDev )
{
int result;
void * pWriteBuffer;
u16 writeBufferSize;
u16 WDSClientID;
if (IsDeviceValid( pDev ) == false)
{
DBG( "Invalid device\n" );
return -EFAULT;
}
result = GetClientID( pDev, QMIWDS );
if (result < 0)
{
return result;
}
WDSClientID = result;
#if 0 // add for "AT$QCRMCALL=1,1", be careful: donot enable these codes if use quectel-CM, or cannot obtain IP by udhcpc
if (pDev->mbMdm9x07)
{
void * pReadBuffer;
u16 readBufferSize;
writeBufferSize = QMIWDSSetQMUXBindMuxDataPortSize();
pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
if (pWriteBuffer == NULL)
{
return -ENOMEM;
}
result = QMIWDSSetQMUXBindMuxDataPortReq( pWriteBuffer,
writeBufferSize,
0x81,
3 );
if (result < 0)
{
kfree( pWriteBuffer );
return result;
}
result = WriteSync( pDev,
pWriteBuffer,
writeBufferSize,
WDSClientID );
kfree( pWriteBuffer );
if (result < 0)
{
return result;
}
result = ReadSync( pDev,
&pReadBuffer,
WDSClientID,
3 );
if (result < 0)
{
return result;
}
readBufferSize = result;
kfree( pReadBuffer );
}
#endif
// QMI WDS Set Event Report
writeBufferSize = QMIWDSSetEventReportReqSize();
pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
if (pWriteBuffer == NULL)
{
return -ENOMEM;
}
result = QMIWDSSetEventReportReq( pWriteBuffer,
writeBufferSize,
1 );
if (result < 0)
{
kfree( pWriteBuffer );
return result;
}
result = WriteSync( pDev,
pWriteBuffer,
writeBufferSize,
WDSClientID );
kfree( pWriteBuffer );
if (result < 0)
{
return result;
}
// QMI WDS Get PKG SRVC Status
writeBufferSize = QMIWDSGetPKGSRVCStatusReqSize();
pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
if (pWriteBuffer == NULL)
{
return -ENOMEM;
}
result = QMIWDSGetPKGSRVCStatusReq( pWriteBuffer,
writeBufferSize,
2 );
if (result < 0)
{
kfree( pWriteBuffer );
return result;
}
result = WriteSync( pDev,
pWriteBuffer,
writeBufferSize,
WDSClientID );
kfree( pWriteBuffer );
if (result < 0)
{
return result;
}
// Setup asnyc read callback
result = ReadAsync( pDev,
WDSClientID,
0,
QMIWDSCallback,
NULL );
if (result != 0)
{
DBG( "unable to setup async read\n" );
return result;
}
// Send SetControlLineState request (USB_CDC)
// Required for Autoconnect
result = usb_control_msg( pDev->mpNetDev->udev,
usb_sndctrlpipe( pDev->mpNetDev->udev, 0 ),
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;
}
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
===========================================================================*/
static int QMIDMSGetMEID( sGobiUSBNet * pDev )
{
int result;
void * pWriteBuffer;
u16 writeBufferSize;
void * pReadBuffer;
u16 readBufferSize;
u16 DMSClientID;
if (IsDeviceValid( pDev ) == false)
{
DBG( "Invalid device\n" );
return -EFAULT;
}
result = GetClientID( pDev, QMIDMS );
if (result < 0)
{
return result;
}
DMSClientID = result;
// QMI DMS Get Serial numbers Req
writeBufferSize = QMIDMSGetMEIDReqSize();
pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
if (pWriteBuffer == NULL)
{
return -ENOMEM;
}
result = QMIDMSGetMEIDReq( pWriteBuffer,
writeBufferSize,
1 );
if (result < 0)
{
kfree( pWriteBuffer );
return result;
}
result = WriteSync( pDev,
pWriteBuffer,
writeBufferSize,
DMSClientID );
kfree( pWriteBuffer );
if (result < 0)
{
return result;
}
// QMI DMS Get Serial numbers Resp
result = ReadSync( pDev,
&pReadBuffer,
DMSClientID,
1 );
if (result < 0)
{
return result;
}
readBufferSize = result;
result = QMIDMSGetMEIDResp( pReadBuffer,
readBufferSize,
&pDev->mMEID[0],
14 );
kfree( pReadBuffer );
if (result < 0)
{
DBG( "bad get MEID resp\n" );
// Non fatal error, device did not return any MEID
// Fill with 0's
memset( &pDev->mMEID[0], '0', 14 );
}
ReleaseClientID( pDev, DMSClientID );
// Success
return 0;
}
/*===========================================================================
METHOD:
QMIWDASetDataFormat (Public Method)
DESCRIPTION:
Register WDA client
send Data format request and parse response
Release WDA client
PARAMETERS:
pDev [ I ] - Device specific memory
RETURN VALUE:
None
===========================================================================*/
static int QMIWDASetDataFormat( sGobiUSBNet * pDev, int qmap_mode, int *rx_urb_size )
{
int result;
void * pWriteBuffer;
u16 writeBufferSize;
void * pReadBuffer;
u16 readBufferSize;
u16 WDAClientID;
DBG("\n");
if (IsDeviceValid( pDev ) == false)
{
DBG( "Invalid device\n" );
return -EFAULT;
}
result = GetClientID( pDev, QMIWDA );
if (result < 0)
{
return result;
}
WDAClientID = result;
// QMI WDA Set Data Format Request
writeBufferSize = QMIWDASetDataFormatReqSize(qmap_mode);
pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
if (pWriteBuffer == NULL)
{
return -ENOMEM;
}
result = QMIWDASetDataFormatReq( pWriteBuffer,
writeBufferSize, pDev->mbRawIPMode,
qmap_mode, 32*1024, //SDX24&SDX55 support 32KB
1 );
if (result < 0)
{
kfree( pWriteBuffer );
return result;
}
result = WriteSync( pDev,
pWriteBuffer,
writeBufferSize,
WDAClientID );
kfree( pWriteBuffer );
if (result < 0)
{
return result;
}
// QMI DMS Get Serial numbers Resp
result = ReadSync( pDev,
&pReadBuffer,
WDAClientID,
1 );
if (result < 0)
{
return result;
}
readBufferSize = result;
if (qmap_mode && rx_urb_size) {
int qmap_enabled = 0, rx_size = 0, tx_size = 0;
result = QMIWDASetDataFormatResp( pReadBuffer,
readBufferSize, pDev->mbRawIPMode, &qmap_enabled, &rx_size, &tx_size);
INFO( "qmap settings qmap_enabled=%d, rx_size=%d, tx_size=%d\n",
le32_to_cpu(qmap_enabled), le32_to_cpu(rx_size), le32_to_cpu(tx_size));
if (le32_to_cpu(qmap_enabled) == 5) {
*rx_urb_size = le32_to_cpu(rx_size);
} else {
*rx_urb_size = 0;
result = -EFAULT;
}
} else {
int qmap_enabled = 0, rx_size = 0, tx_size = 0;
result = QMIWDASetDataFormatResp( pReadBuffer,
readBufferSize, pDev->mbRawIPMode, &qmap_enabled, &rx_size, &tx_size);
}
kfree( pReadBuffer );
if (result < 0)
{
DBG( "Data Format Cannot be set\n" );
}
ReleaseClientID( pDev, WDAClientID );
// Success
return 0;
}
int QuecQMIWDASetDataFormat( sGobiUSBNet * pDev, int qmap_mode, int *rx_urb_size ) {
return QMIWDASetDataFormat(pDev, qmap_mode, rx_urb_size);
}