eg25-g/kernel/kernel-4.9-spiri/drivers/net/usb/QMIDevice.c

3995 lines
109 KiB
C

/*===========================================================================
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 "QMIDevice.h"
#include <linux/module.h>
//-----------------------------------------------------------------------------
// Definitions
//-----------------------------------------------------------------------------
extern int debug;
extern int interruptible;
#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];
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;
}
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 GobiNetSuspend(
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
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
/*=========================================================================*/
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,
};
/*=========================================================================*/
// 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;
}
/*===========================================================================
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;
}
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 );
DBG("%s reason=%d, mDownReason=%x\n", __func__, reason, (unsigned)pDev->mDownReason);
netif_carrier_off( pDev->mpNetDev->net );
}
/*===========================================================================
METHOD:
GobiClearDownReason (Public Method)
DESCRIPTION:
Clear mDownReason and may turn carrier on
PARAMETERS
pDev [ I ] - Device specific memory
reason [ I ] - Reason device is no longer down
RETURN VALUE:
None
===========================================================================*/
void GobiClearDownReason(
sGobiUSBNet * pDev,
u8 reason )
{
clear_bit( reason, &pDev->mDownReason );
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 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
#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,14 ))
void ReadCallback( struct urb * pReadURB )
#else
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;
}
if (pReadURB->status != 0)
{
DBG( "Read status = %d\n", pReadURB->status );
// Resubmit the interrupt URB
ResubmitIntURB( pDev->mQMIDev.mpIntURB );
return;
}
DBG( "Read %d bytes\n", pReadURB->actual_length );
pData = pReadURB->transfer_buffer;
dataSize = pReadURB->actual_length;
PrintHex( pData, dataSize );
result = ParseQMUX( &clientID,
pData,
dataSize );
if (result < 0)
{
DBG( "Read error parsing QMUX %d\n", result );
// Resubmit the interrupt URB
ResubmitIntURB( pDev->mQMIDev.mpIntURB );
return;
}
// Grab transaction ID
// Data large enough?
if (dataSize < result + 3)
{
DBG( "Data buffer too small to parse\n" );
// Resubmit the interrupt URB
ResubmitIntURB( pDev->mQMIDev.mpIntURB );
return;
}
// Transaction ID size is 1 for QMICTL, 2 for others
if (clientID == QMICTL)
{
transactionID = *(u8*)(pData + result + 1);
}
else
{
transactionID = 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,14 ))
void IntCallback( struct urb * pIntURB )
{
#else
void IntCallback(struct urb *pIntURB, struct pt_regs *regs)
{
#endif
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;
}
}
else
{
//TODO cast transfer_buffer to struct usb_cdc_notification
// CDC GET_ENCAPSULATED_RESPONSE
CDC_GET_ENCAPSULATED_RESPONSE(&CDCEncResp, &CDCEncRespMask)
VDBG( "IntCallback: Encapsulated Response = 0x%llx\n",
(*(u64*)pIntURB->transfer_buffer));
if ((pIntURB->actual_length == 8)
&& ((*(u64*)pIntURB->transfer_buffer & CDCEncRespMask) == CDCEncResp ) )
{
// Time to read
usb_fill_control_urb( pDev->mQMIDev.mpReadURB,
pDev->mpNetDev->udev,
usb_rcvctrlpipe( pDev->mpNetDev->udev, 0 ),
(unsigned char *)pDev->mQMIDev.mpReadSetupPacket,
pDev->mQMIDev.mpReadBuffer,
DEFAULT_READ_URB_LENGTH,
ReadCallback,
pDev );
status = usb_submit_urb( pDev->mQMIDev.mpReadURB, GFP_ATOMIC );
if (status != 0)
{
DBG( "Error submitting Read URB %d\n", status );
// 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 );
}
}
// 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( 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 KillRead( sGobiUSBNet * pDev )
{
// Stop reading
if (pDev->mQMIDev.mpReadURB != NULL)
{
DBG( "Killng read URB\n" );
usb_kill_urb( pDev->mQMIDev.mpReadURB );
}
if (pDev->mQMIDev.mpIntURB != NULL)
{
DBG( "Killng int URB\n" );
usb_kill_urb( pDev->mQMIDev.mpIntURB );
}
// Release buffers
kfree( pDev->mQMIDev.mpReadSetupPacket );
pDev->mQMIDev.mpReadSetupPacket = NULL;
kfree( pDev->mQMIDev.mpReadBuffer );
pDev->mQMIDev.mpReadBuffer = NULL;
kfree( pDev->mQMIDev.mpIntBuffer );
pDev->mQMIDev.mpIntBuffer = NULL;
// Release URB's
usb_free_urb( pDev->mQMIDev.mpReadURB );
pDev->mQMIDev.mpReadURB = NULL;
usb_free_urb( pDev->mQMIDev.mpIntURB );
pDev->mQMIDev.mpIntURB = NULL;
}
/*=========================================================================*/
// Internal read/write functions
/*=========================================================================*/
/*===========================================================================
METHOD:
ReadAsync (Public Method)
DESCRIPTION:
Start asynchronous read
NOTE: Reading client's data store, not device
PARAMETERS:
pDev [ I ] - Device specific memory
clientID [ I ] - Requester's client ID
transactionID [ I ] - Transaction ID or 0 for any
pCallback [ I ] - Callback to be executed when data is available
pData [ I ] - Data buffer that willl be passed (unmodified)
to callback
RETURN VALUE:
int - 0 for success
negative errno for failure
===========================================================================*/
int ReadAsync(
sGobiUSBNet * pDev,
u16 clientID,
u16 transactionID,
void (*pCallback)(sGobiUSBNet*, u16, void *),
void * pData )
{
sClientMemList * pClientMem;
sReadMemList ** ppReadMemList;
unsigned long flags;
if (IsDeviceValid( pDev ) == false)
{
DBG( "Invalid device!\n" );
return -ENXIO;
}
// Critical section
spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
// Find memory storage for this client ID
pClientMem = FindClientMem( pDev, clientID );
if (pClientMem == NULL)
{
DBG( "Could not find matching client ID 0x%04X\n",
clientID );
// End critical section
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
return -ENXIO;
}
ppReadMemList = &(pClientMem->mpList);
// Does data already exist?
while (*ppReadMemList != NULL)
{
// Is this element our data?
if (transactionID == 0
|| transactionID == (*ppReadMemList)->mTransactionID)
{
// End critical section
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
// Run our own callback
pCallback( pDev, clientID, pData );
return 0;
}
// Next
ppReadMemList = &(*ppReadMemList)->mpNext;
}
// Data not found, add ourself to list of waiters
if (AddToNotifyList( pDev,
clientID,
transactionID,
pCallback,
pData ) == false)
{
DBG( "Unable to register for notification\n" );
}
// End critical section
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
// Success
return 0;
}
/*===========================================================================
METHOD:
UpSem (Public Method)
DESCRIPTION:
Notification function for synchronous read
PARAMETERS:
pDev [ I ] - Device specific memory
clientID [ I ] - Requester's client ID
pData [ I ] - Buffer that holds semaphore to be up()-ed
RETURN VALUE:
None
===========================================================================*/
void UpSem(
sGobiUSBNet * pDev,
u16 clientID,
void * pData )
{
VDBG( "0x%04X\n", clientID );
up( (struct semaphore *)pData );
return;
}
/*===========================================================================
METHOD:
ReadSync (Public Method)
DESCRIPTION:
Start synchronous read
NOTE: Reading client's data store, not device
PARAMETERS:
pDev [ I ] - Device specific memory
ppOutBuffer [I/O] - On success, will be filled with a
pointer to read buffer
clientID [ I ] - Requester's client ID
transactionID [ I ] - Transaction ID or 0 for any
RETURN VALUE:
int - size of data read for success
negative errno for failure
===========================================================================*/
int ReadSync(
sGobiUSBNet * pDev,
void ** ppOutBuffer,
u16 clientID,
u16 transactionID )
{
int result;
sClientMemList * pClientMem;
sNotifyList ** ppNotifyList, * pDelNotifyListEntry;
struct semaphore readSem;
void * pData;
unsigned long flags;
u16 dataSize;
if (IsDeviceValid( pDev ) == false)
{
DBG( "Invalid device!\n" );
return -ENXIO;
}
// Critical section
spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
// Find memory storage for this Client ID
pClientMem = FindClientMem( pDev, clientID );
if (pClientMem == NULL)
{
DBG( "Could not find matching client ID 0x%04X\n",
clientID );
// End critical section
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
return -ENXIO;
}
// Note: in cases where read is interrupted,
// this will verify client is still valid
while (PopFromReadMemList( pDev,
clientID,
transactionID,
&pData,
&dataSize ) == false)
{
// Data does not yet exist, wait
sema_init( &readSem, 0 );
// Add ourself to list of waiters
if (AddToNotifyList( pDev,
clientID,
transactionID,
UpSem,
&readSem ) == false)
{
DBG( "unable to register for notification\n" );
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
return -EFAULT;
}
// End critical section while we block
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
// Wait for notification
result = down_interruptible( &readSem );
if (result != 0)
{
DBG( "Interrupted %d\n", result );
// readSem will fall out of scope,
// remove from notify list so it's not referenced
spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
ppNotifyList = &(pClientMem->mpReadNotifyList);
pDelNotifyListEntry = NULL;
// Find and delete matching entry
while (*ppNotifyList != NULL)
{
if ((*ppNotifyList)->mpData == &readSem)
{
pDelNotifyListEntry = *ppNotifyList;
*ppNotifyList = (*ppNotifyList)->mpNext;
kfree( pDelNotifyListEntry );
break;
}
// Next
ppNotifyList = &(*ppNotifyList)->mpNext;
}
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
return -EINTR;
}
// Verify device is still valid
if (IsDeviceValid( pDev ) == false)
{
DBG( "Invalid device!\n" );
return -ENXIO;
}
// Restart critical section and continue loop
spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
}
// End Critical section
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
// Success
*ppOutBuffer = pData;
return dataSize;
}
/*===========================================================================
METHOD:
WriteSyncCallback (Public Method)
DESCRIPTION:
Write callback
PARAMETERS
pWriteURB [ I ] - URB this callback is run for
RETURN VALUE:
None
===========================================================================*/
#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,14 ))
void WriteSyncCallback( struct urb * pWriteURB )
#else
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
===========================================================================*/
int WriteSync(
sGobiUSBNet * pDev,
char * pWriteBuffer,
int writeBufferSize,
u16 clientID )
{
int result;
struct semaphore writeSem;
struct urb * pWriteURB;
sURBSetupPacket writeSetup;
unsigned long flags;
if (IsDeviceValid( pDev ) == false)
{
DBG( "Invalid device!\n" );
return -ENXIO;
}
pWriteURB = usb_alloc_urb( 0, GFP_KERNEL );
if (pWriteURB == NULL)
{
DBG( "URB mem error\n" );
return -ENOMEM;
}
// Fill writeBuffer with QMUX
result = FillQMUX( clientID, pWriteBuffer, writeBufferSize );
if (result < 0)
{
usb_free_urb( pWriteURB );
return result;
}
// CDC Send Encapsulated Request packet
writeSetup.mRequestType = 0x21;
writeSetup.mRequestCode = 0;
writeSetup.mValue = 0;
writeSetup.mIndex = 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,14 ))
pDev->mpNetDev->udev->auto_pm = 0;
#endif
#endif
GobiNetSuspend( pDev->mpIntf, PMSG_SUSPEND );
#endif /* CONFIG_PM */
}
usb_free_urb( pWriteURB );
return result;
}
// Critical section
spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
if (AddToURBList( pDev, clientID, pWriteURB ) == false)
{
usb_free_urb( pWriteURB );
// End critical section
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
usb_autopm_put_interface( pDev->mpIntf );
return -EINVAL;
}
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 );
// End critical section
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
usb_autopm_put_interface( pDev->mpIntf );
return result;
}
// End critical section while we block
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
// Wait for write to finish
if (interruptible != 0)
{
// Allow user interrupts
result = down_interruptible( &writeSem );
}
else
{
// Ignore user interrupts
result = 0;
down( &writeSem );
}
// Write is done, release device
usb_autopm_put_interface( pDev->mpIntf );
// Verify device is still valid
if (IsDeviceValid( pDev ) == false)
{
DBG( "Invalid device!\n" );
usb_free_urb( pWriteURB );
return -ENXIO;
}
// Restart critical section
spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
// Get URB back so we can destroy it
if (PopFromURBList( pDev, clientID ) != pWriteURB)
{
// This shouldn't happen
DBG( "Didn't get write URB back\n" );
// End critical section
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
usb_free_urb( pWriteURB );
return -EINVAL;
}
// End critical section
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
if (result == 0)
{
// Write is finished
if (pWriteURB->status == 0)
{
// Return number of bytes that were supposed to have been written,
// not size of QMI request
result = writeBufferSize;
}
else
{
DBG( "bad status = %d\n", pWriteURB->status );
// Return error value
result = pWriteURB->status;
}
}
else
{
// We have been forcibly interrupted
DBG( "Interrupted %d !!!\n", result );
DBG( "Device may be in bad state and need reset !!!\n" );
// URB has not finished
usb_kill_urb( pWriteURB );
}
usb_free_urb( pWriteURB );
return result;
}
/*=========================================================================*/
// Internal memory management functions
/*=========================================================================*/
/*===========================================================================
METHOD:
GetClientID (Public Method)
DESCRIPTION:
Request a QMI client for the input service type and initialize memory
structure
PARAMETERS:
pDev [ I ] - Device specific memory
serviceType [ I ] - Desired QMI service type
RETURN VALUE:
int - Client ID for success (positive)
Negative errno for error
===========================================================================*/
int GetClientID(
sGobiUSBNet * pDev,
u8 serviceType )
{
u16 clientID;
sClientMemList ** ppClientMem;
int result;
void * pWriteBuffer;
u16 writeBufferSize;
void * pReadBuffer;
u16 readBufferSize;
unsigned long flags;
u8 transactionID;
if (IsDeviceValid( pDev ) == false)
{
DBG( "Invalid device!\n" );
return -ENXIO;
}
// Run QMI request to be asigned a Client ID
if (serviceType != 0)
{
writeBufferSize = QMICTLGetClientIDReqSize();
pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
if (pWriteBuffer == NULL)
{
return -ENOMEM;
}
/* transactionID cannot be 0 */
transactionID = atomic_add_return( 1, &pDev->mQMIDev.mQMICTLTransactionID );
if (transactionID == 0)
{
transactionID = atomic_add_return( 1, &pDev->mQMIDev.mQMICTLTransactionID );
}
if (transactionID != 0)
{
result = QMICTLGetClientIDReq( pWriteBuffer,
writeBufferSize,
transactionID,
serviceType );
if (result < 0)
{
kfree( pWriteBuffer );
return result;
}
}
else
{
DBG( "Invalid transaction ID!\n" );
return EINVAL;
}
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
===========================================================================*/
void ReleaseClientID(
sGobiUSBNet * pDev,
u16 clientID )
{
int result;
sClientMemList ** ppDelClientMem;
sClientMemList * pNextClientMem;
struct urb * pDelURB;
void * pDelData;
u16 dataSize;
void * pWriteBuffer;
u16 writeBufferSize;
void * pReadBuffer;
u16 readBufferSize;
unsigned long flags;
u8 transactionID;
// Is device is still valid?
if (IsDeviceValid( pDev ) == false)
{
DBG( "invalid device\n" );
return;
}
DBG( "releasing 0x%04X\n", clientID );
// Run QMI ReleaseClientID if this isn't QMICTL
if (clientID != QMICTL)
{
// Note: all errors are non fatal, as we always want to delete
// client memory in latter part of function
writeBufferSize = QMICTLReleaseClientIDReqSize();
pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
if (pWriteBuffer == NULL)
{
DBG( "memory error\n" );
}
else
{
transactionID = atomic_add_return( 1, &pDev->mQMIDev.mQMICTLTransactionID );
if (transactionID == 0)
{
transactionID = atomic_add_return( 1, &pDev->mQMIDev.mQMICTLTransactionID );
}
result = QMICTLReleaseClientIDReq( pWriteBuffer,
writeBufferSize,
transactionID,
clientID );
if (result < 0)
{
kfree( pWriteBuffer );
DBG( "error %d filling req buffer\n", result );
}
else
{
result = WriteSync( pDev,
pWriteBuffer,
writeBufferSize,
QMICTL );
kfree( pWriteBuffer );
if (result < 0)
{
DBG( "bad write status %d\n", result );
}
else
{
result = ReadSync( pDev,
&pReadBuffer,
QMICTL,
transactionID );
if (result < 0)
{
DBG( "bad read status %d\n", result );
}
else
{
readBufferSize = result;
result = QMICTLReleaseClientIDResp( pReadBuffer,
readBufferSize );
kfree( pReadBuffer );
if (result < 0)
{
DBG( "error %d parsing response\n", result );
}
}
}
}
}
}
// Cleaning up client memory
// Critical section
spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
// Can't use FindClientMem, I need to keep pointer of previous
ppDelClientMem = &pDev->mQMIDev.mpClientMemList;
while (*ppDelClientMem != NULL)
{
if ((*ppDelClientMem)->mClientID == clientID)
{
pNextClientMem = (*ppDelClientMem)->mpNext;
// Notify all clients
while (NotifyAndPopNotifyList( pDev,
clientID,
0 ) == true );
// Kill and free all URB's
pDelURB = PopFromURBList( pDev, clientID );
while (pDelURB != NULL)
{
usb_kill_urb( pDelURB );
usb_free_urb( pDelURB );
pDelURB = PopFromURBList( pDev, clientID );
}
// Free any unread data
while (PopFromReadMemList( pDev,
clientID,
0,
&pDelData,
&dataSize ) == true )
{
kfree( pDelData );
}
// Delete client Mem
if (!waitqueue_active( &(*ppDelClientMem)->mWaitQueue))
kfree( *ppDelClientMem );
else
DBG("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
===========================================================================*/
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
===========================================================================*/
bool AddToReadMemList(
sGobiUSBNet * pDev,
u16 clientID,
u16 transactionID,
void * pData,
u16 dataSize )
{
sClientMemList * pClientMem;
sReadMemList ** ppThisReadMemList;
#ifdef CONFIG_SMP
// Verify Lock
if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0)
{
DBG( "unlocked\n" );
BUG();
}
#endif
// Get this client's memory location
pClientMem = FindClientMem( pDev, clientID );
if (pClientMem == NULL)
{
DBG( "Could not find this client's memory 0x%04X\n",
clientID );
return false;
}
// Go to last ReadMemList entry
ppThisReadMemList = &pClientMem->mpList;
while (*ppThisReadMemList != NULL)
{
ppThisReadMemList = &(*ppThisReadMemList)->mpNext;
}
*ppThisReadMemList = kmalloc( sizeof( sReadMemList ), GFP_ATOMIC );
if (*ppThisReadMemList == NULL)
{
DBG( "Mem error\n" );
return false;
}
(*ppThisReadMemList)->mpNext = NULL;
(*ppThisReadMemList)->mpData = pData;
(*ppThisReadMemList)->mDataSize = dataSize;
(*ppThisReadMemList)->mTransactionID = transactionID;
return true;
}
/*===========================================================================
METHOD:
PopFromReadMemList (Public Method)
DESCRIPTION:
Remove data from this client's ReadMem list if it matches
the specified transaction ID.
Caller MUST have lock on mClientMemLock
PARAMETERS:
pDev [ I ] - Device specific memory
clientID [ I ] - Requester's client ID
transactionID [ I ] - Transaction ID or 0 for any
ppData [I/O] - On success, will be filled with a
pointer to read buffer
pDataSize [I/O] - On succces, will be filled with the
read buffer's size
RETURN VALUE:
bool
===========================================================================*/
bool PopFromReadMemList(
sGobiUSBNet * pDev,
u16 clientID,
u16 transactionID,
void ** ppData,
u16 * pDataSize )
{
sClientMemList * pClientMem;
sReadMemList * pDelReadMemList, ** ppReadMemList;
#ifdef CONFIG_SMP
// Verify Lock
if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0)
{
DBG( "unlocked\n" );
BUG();
}
#endif
// Get this client's memory location
pClientMem = FindClientMem( pDev, clientID );
if (pClientMem == NULL)
{
DBG( "Could not find this client's memory 0x%04X\n",
clientID );
return false;
}
ppReadMemList = &(pClientMem->mpList);
pDelReadMemList = NULL;
// Find first message that matches this transaction ID
while (*ppReadMemList != NULL)
{
// Do we care about transaction ID?
if (transactionID == 0
|| transactionID == (*ppReadMemList)->mTransactionID )
{
pDelReadMemList = *ppReadMemList;
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
===========================================================================*/
bool AddToNotifyList(
sGobiUSBNet * pDev,
u16 clientID,
u16 transactionID,
void (* pNotifyFunct)(sGobiUSBNet *, u16, void *),
void * pData )
{
sClientMemList * pClientMem;
sNotifyList ** ppThisNotifyList;
#ifdef CONFIG_SMP
// Verify Lock
if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0)
{
DBG( "unlocked\n" );
BUG();
}
#endif
// Get this client's memory location
pClientMem = FindClientMem( pDev, clientID );
if (pClientMem == NULL)
{
DBG( "Could not find this client's memory 0x%04X\n", clientID );
return false;
}
// Go to last URBList entry
ppThisNotifyList = &pClientMem->mpReadNotifyList;
while (*ppThisNotifyList != NULL)
{
ppThisNotifyList = &(*ppThisNotifyList)->mpNext;
}
*ppThisNotifyList = kmalloc( sizeof( sNotifyList ), GFP_ATOMIC );
if (*ppThisNotifyList == NULL)
{
DBG( "Mem error\n" );
return false;
}
(*ppThisNotifyList)->mpNext = NULL;
(*ppThisNotifyList)->mpNotifyFunct = pNotifyFunct;
(*ppThisNotifyList)->mpData = pData;
(*ppThisNotifyList)->mTransactionID = transactionID;
return true;
}
/*===========================================================================
METHOD:
NotifyAndPopNotifyList (Public Method)
DESCRIPTION:
Remove first Notify entry from this client's notify list
and Run function
Caller MUST have lock on mClientMemLock
PARAMETERS:
pDev [ I ] - Device specific memory
clientID [ I ] - Requester's client ID
transactionID [ I ] - Transaction ID or 0 for any
RETURN VALUE:
bool
===========================================================================*/
bool NotifyAndPopNotifyList(
sGobiUSBNet * pDev,
u16 clientID,
u16 transactionID )
{
sClientMemList * pClientMem;
sNotifyList * pDelNotifyList, ** ppNotifyList;
#ifdef CONFIG_SMP
// Verify Lock
if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0)
{
DBG( "unlocked\n" );
BUG();
}
#endif
// Get this client's memory location
pClientMem = FindClientMem( pDev, clientID );
if (pClientMem == NULL)
{
DBG( "Could not find this client's memory 0x%04X\n", clientID );
return false;
}
ppNotifyList = &(pClientMem->mpReadNotifyList);
pDelNotifyList = NULL;
// Remove from list
while (*ppNotifyList != NULL)
{
// Do we care about transaction ID?
if (transactionID == 0
|| (*ppNotifyList)->mTransactionID == 0
|| transactionID == (*ppNotifyList)->mTransactionID)
{
pDelNotifyList = *ppNotifyList;
break;
}
DBG( "skipping data TID = %x\n", (*ppNotifyList)->mTransactionID );
// next
ppNotifyList = &(*ppNotifyList)->mpNext;
}
if (pDelNotifyList != NULL)
{
// Remove element
*ppNotifyList = (*ppNotifyList)->mpNext;
// Run notification function
if (pDelNotifyList->mpNotifyFunct != NULL)
{
// Unlock for callback
spin_unlock( &pDev->mQMIDev.mClientMemLock );
pDelNotifyList->mpNotifyFunct( pDev,
clientID,
pDelNotifyList->mpData );
// Restore lock
spin_lock( &pDev->mQMIDev.mClientMemLock );
}
// Delete memory
kfree( pDelNotifyList );
return true;
}
else
{
DBG( "no one to notify for TID %x\n", transactionID );
return false;
}
}
/*===========================================================================
METHOD:
AddToURBList (Public Method)
DESCRIPTION:
Add URB to this client's URB list
Caller MUST have lock on mClientMemLock
PARAMETERS:
pDev [ I ] - Device specific memory
clientID [ I ] - Requester's client ID
pURB [ I ] - URB to be added
RETURN VALUE:
bool
===========================================================================*/
bool AddToURBList(
sGobiUSBNet * pDev,
u16 clientID,
struct urb * pURB )
{
sClientMemList * pClientMem;
sURBList ** ppThisURBList;
#ifdef CONFIG_SMP
// Verify Lock
if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0)
{
DBG( "unlocked\n" );
BUG();
}
#endif
// Get this client's memory location
pClientMem = FindClientMem( pDev, clientID );
if (pClientMem == NULL)
{
DBG( "Could not find this client's memory 0x%04X\n", clientID );
return false;
}
// Go to last URBList entry
ppThisURBList = &pClientMem->mpURBList;
while (*ppThisURBList != NULL)
{
ppThisURBList = &(*ppThisURBList)->mpNext;
}
*ppThisURBList = kmalloc( sizeof( sURBList ), GFP_ATOMIC );
if (*ppThisURBList == NULL)
{
DBG( "Mem error\n" );
return false;
}
(*ppThisURBList)->mpNext = NULL;
(*ppThisURBList)->mpURB = pURB;
return true;
}
/*===========================================================================
METHOD:
PopFromURBList (Public Method)
DESCRIPTION:
Remove URB from this client's URB list
Caller MUST have lock on mClientMemLock
PARAMETERS:
pDev [ I ] - Device specific memory
clientID [ I ] - Requester's client ID
RETURN VALUE:
struct urb - Pointer to requested client's URB
NULL for error
===========================================================================*/
struct urb * PopFromURBList(
sGobiUSBNet * pDev,
u16 clientID )
{
sClientMemList * pClientMem;
sURBList * pDelURBList;
struct urb * pURB;
#ifdef CONFIG_SMP
// Verify Lock
if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0)
{
DBG( "unlocked\n" );
BUG();
}
#endif
// Get this client's memory location
pClientMem = FindClientMem( pDev, clientID );
if (pClientMem == NULL)
{
DBG( "Could not find this client's memory 0x%04X\n", clientID );
return NULL;
}
// Remove from list
if (pClientMem->mpURBList != NULL)
{
pDelURBList = pClientMem->mpURBList;
pClientMem->mpURBList = pClientMem->mpURBList->mpNext;
// Copy to output
pURB = pDelURBList->mpURB;
// Delete memory
kfree( pDelURBList );
return pURB;
}
else
{
DBG( "No URB's to pop\n" );
return NULL;
}
}
#ifndef f_dentry
#define f_dentry f_path.dentry
#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
===========================================================================*/
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
===========================================================================*/
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->mpNetDev->udev->descriptor.idVendor == cpu_to_le16(0x2c7c))
{
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;
atomic_inc(&pDev->refcount);
pFilpData->mpDev = pDev;
return 0;
}
/*===========================================================================
METHOD:
UserspaceIOCTL (Public Method)
DESCRIPTION:
Userspace IOCTL functions
PARAMETERS
pUnusedInode [ I ] - (unused) kernel file descriptor
pFilp [ I ] - userspace file descriptor
cmd [ I ] - IOCTL command
arg [ I ] - IOCTL argument
RETURN VALUE:
int - 0 for success
Negative errno for failure
===========================================================================*/
int UserspaceIOCTL(
struct inode * pUnusedInode,
struct file * pFilp,
unsigned int cmd,
unsigned long arg )
{
// 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
===========================================================================*/
#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 = -1; //DeregisterQMIDevice() will release this ClientID
else
ReleaseClientID( pFilpData->mpDev,
pFilpData->mClientID );
}
atomic_dec(&pFilpData->mpDev->refcount);
kfree( pFilpData );
return 0;
}
/*===========================================================================
METHOD:
UserspaceRead (Public Method)
DESCRIPTION:
Userspace read (synchronous)
PARAMETERS
pFilp [ I ] - userspace file descriptor
pBuf [ I ] - read buffer
size [ I ] - size of read buffer
pUnusedFpos [ I ] - (unused) file position
RETURN VALUE:
ssize_t - Number of bytes read for success
Negative errno for failure
===========================================================================*/
ssize_t UserspaceRead(
struct file * pFilp,
char __user * pBuf,
size_t size,
loff_t * pUnusedFpos )
{
int result;
void * pReadData = NULL;
void * pSmallReadData;
sQMIFilpStorage * pFilpData = (sQMIFilpStorage *)pFilp->private_data;
if (pFilpData == NULL)
{
DBG( "Bad file data\n" );
return -EBADF;
}
if (IsDeviceValid( pFilpData->mpDev ) == false)
{
DBG( "Invalid device! Updating f_ops\n" );
pFilp->f_op = pFilp->f_dentry->d_inode->i_fop;
return -ENXIO;
}
if (pFilpData->mClientID == (u16)-1)
{
DBG( "Client ID must be set before reading 0x%04X\n",
pFilpData->mClientID );
return -EBADR;
}
// Perform synchronous read
result = ReadSync( pFilpData->mpDev,
&pReadData,
pFilpData->mClientID,
0 );
if (result <= 0)
{
return result;
}
// Discard QMUX header
result -= QMUXHeaderSize();
pSmallReadData = pReadData + QMUXHeaderSize();
if (result > size)
{
DBG( "Read data is too large for amount user has requested\n" );
kfree( pReadData );
return -EOVERFLOW;
}
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
===========================================================================*/
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
===========================================================================*/
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
/*=========================================================================*/
int QMICTLSyncProc(sGobiUSBNet *pDev)
{
void *pWriteBuffer;
void *pReadBuffer;
int result;
u16 writeBufferSize;
u8 transactionID;
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;
}
// 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" );
}
// Setup Data Format
result = QMIWDASetDataFormat (pDev);
if (result != 0)
{
goto __qmi_sync_finished;
}
// 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->mpNetDev->udev->descriptor.idVendor == cpu_to_le16(0x2c7c))
{
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->mpNetDev->udev->descriptor.idVendor == cpu_to_le16(0x2c7c))
{
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);
}
}
else
{
// 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);
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;
}
}
// 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" );
} 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 )
{
struct inode * pOpenInode;
struct list_head * pInodeList;
struct task_struct * pEachTask;
struct fdtable * pFDT;
struct file * pFilp;
unsigned long flags;
int count = 0;
int tries;
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;
}
// 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();
}
}
// 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++)
{
int ref = atomic_read( &pDev->mQMIDev.mCdev.kobj.kref.refcount );
if (ref > 1)
{
DBG( "cdev in use by %d tasks\n", ref - 1 );
msleep( 10 );
}
else
{
break;
}
}
cdev_del( &pDev->mQMIDev.mCdev );
unregister_chrdev_region( pDev->mQMIDev.mDevNum, 1 );
return;
}
/*=========================================================================*/
// Driver level client management
/*=========================================================================*/
/*===========================================================================
METHOD:
QMIReady (Public Method)
DESCRIPTION:
Send QMI CTL GET VERSION INFO REQ 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
===========================================================================*/
bool QMIReady(
sGobiUSBNet * pDev,
u16 timeout )
{
int result;
void * pWriteBuffer;
u16 writeBufferSize;
void * pReadBuffer;
u16 readBufferSize;
struct semaphore readSem;
u16 curTime;
unsigned long flags;
u8 transactionID;
if (IsDeviceValid( pDev ) == false)
{
DBG( "Invalid device\n" );
return false;
}
writeBufferSize = QMICTLReadyReqSize();
pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
if (pWriteBuffer == NULL)
{
return false;
}
// An implimentation of down_timeout has not been agreed on,
// so it's been added and removed from the kernel several times.
// We're just going to ignore it and poll the semaphore.
// Send a write every 1000 ms and see if we get a response
for (curTime = 0; curTime < timeout; curTime += 1000)
{
// Start read
sema_init( &readSem, 0 );
transactionID = atomic_add_return( 1, &pDev->mQMIDev.mQMICTLTransactionID );
if (transactionID == 0)
{
transactionID = atomic_add_return( 1, &pDev->mQMIDev.mQMICTLTransactionID );
}
result = ReadAsync( pDev, QMICTL, transactionID, UpSem, &readSem );
if (result != 0)
{
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 );
msleep( 1000 );
if (down_trylock( &readSem ) == 0)
{
// Enter critical section
spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
// Pop the read data
if (PopFromReadMemList( pDev,
QMICTL,
transactionID,
&pReadBuffer,
&readBufferSize ) == true)
{
// Success
// End critical section
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
// We don't care about the result
kfree( pReadBuffer );
break;
}
else
{
// Read mismatch/failure, unlock and continue
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
}
}
else
{
// Enter critical section
spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
// Timeout, remove the async read
NotifyAndPopNotifyList( pDev, QMICTL, transactionID );
// End critical section
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
}
}
kfree( pWriteBuffer );
// Did we time out?
if (curTime >= timeout)
{
return false;
}
DBG( "QMI Ready after %u milliseconds\n", curTime );
// Success
return true;
}
/*===========================================================================
METHOD:
QMIWDSCallback (Public Method)
DESCRIPTION:
QMI WDS callback function
Update net stats or link state
PARAMETERS:
pDev [ I ] - Device specific memory
clientID [ I ] - Client ID
pData [ I ] - Callback data (unused)
RETURN VALUE:
None
===========================================================================*/
void QMIWDSCallback(
sGobiUSBNet * pDev,
u16 clientID,
void * pData )
{
bool bRet;
int result;
void * pReadBuffer;
u16 readBufferSize;
#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,31 ))
struct net_device_stats * pStats = &(pDev->mpNetDev->stats);
#else
struct net_device_stats * pStats = &(pDev->mpNetDev->net->stats);
#endif
u32 TXOk = (u32)-1;
u32 RXOk = (u32)-1;
u32 TXErr = (u32)-1;
u32 RXErr = (u32)-1;
u32 TXOfl = (u32)-1;
u32 RXOfl = (u32)-1;
u64 TXBytesOk = (u64)-1;
u64 RXBytesOk = (u64)-1;
bool bLinkState;
bool bReconfigure;
unsigned long flags;
if (IsDeviceValid( pDev ) == false)
{
DBG( "Invalid device\n" );
return;
}
// Critical section
spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
bRet = PopFromReadMemList( pDev,
clientID,
0,
&pReadBuffer,
&readBufferSize );
// End critical section
spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
if (bRet == false)
{
DBG( "WDS callback failed to get data\n" );
return;
}
// Default values
bLinkState = ! GobiTestDownReason( pDev, NO_NDIS_CONNECTION );
bReconfigure = false;
result = QMIWDSEventResp( pReadBuffer,
readBufferSize,
&TXOk,
&RXOk,
&TXErr,
&RXErr,
&TXOfl,
&RXOfl,
&TXBytesOk,
&RXBytesOk,
&bLinkState,
&bReconfigure );
if (result < 0)
{
DBG( "bad WDS packet\n" );
}
else
{
// Fill in new values, ignore max values
if (TXOfl != (u32)-1)
{
pStats->tx_fifo_errors = TXOfl;
}
if (RXOfl != (u32)-1)
{
pStats->rx_fifo_errors = RXOfl;
}
if (TXErr != (u32)-1)
{
pStats->tx_errors = TXErr;
}
if (RXErr != (u32)-1)
{
pStats->rx_errors = RXErr;
}
if (TXOk != (u32)-1)
{
pStats->tx_packets = TXOk + pStats->tx_errors;
}
if (RXOk != (u32)-1)
{
pStats->rx_packets = RXOk + pStats->rx_errors;
}
if (TXBytesOk != (u64)-1)
{
pStats->tx_bytes = TXBytesOk;
}
if (RXBytesOk != (u64)-1)
{
pStats->rx_bytes = RXBytesOk;
}
if (bReconfigure == true)
{
DBG( "Net device link reset\n" );
GobiSetDownReason( pDev, NO_NDIS_CONNECTION );
GobiClearDownReason( pDev, NO_NDIS_CONNECTION );
}
else
{
if (bLinkState == true)
{
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
===========================================================================*/
int SetupQMIWDSCallback( sGobiUSBNet * pDev )
{
int result;
void * pWriteBuffer;
u16 writeBufferSize;
u16 WDSClientID;
if (IsDeviceValid( pDev ) == false)
{
DBG( "Invalid device\n" );
return -EFAULT;
}
result = GetClientID( pDev, QMIWDS );
if (result < 0)
{
return result;
}
WDSClientID = result;
// QMI WDS Set Event Report
writeBufferSize = QMIWDSSetEventReportReqSize();
pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
if (pWriteBuffer == NULL)
{
return -ENOMEM;
}
result = QMIWDSSetEventReportReq( pWriteBuffer,
writeBufferSize,
1 );
if (result < 0)
{
kfree( pWriteBuffer );
return result;
}
result = WriteSync( pDev,
pWriteBuffer,
writeBufferSize,
WDSClientID );
kfree( pWriteBuffer );
if (result < 0)
{
return result;
}
// QMI WDS Get PKG SRVC Status
writeBufferSize = QMIWDSGetPKGSRVCStatusReqSize();
pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
if (pWriteBuffer == NULL)
{
return -ENOMEM;
}
result = QMIWDSGetPKGSRVCStatusReq( pWriteBuffer,
writeBufferSize,
2 );
if (result < 0)
{
kfree( pWriteBuffer );
return result;
}
result = WriteSync( pDev,
pWriteBuffer,
writeBufferSize,
WDSClientID );
kfree( pWriteBuffer );
if (result < 0)
{
return result;
}
// Setup asnyc read callback
result = ReadAsync( pDev,
WDSClientID,
0,
QMIWDSCallback,
NULL );
if (result != 0)
{
DBG( "unable to setup async read\n" );
return result;
}
// Send SetControlLineState request (USB_CDC)
// Required for Autoconnect
result = usb_control_msg( pDev->mpNetDev->udev,
usb_sndctrlpipe( pDev->mpNetDev->udev, 0 ),
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
===========================================================================*/
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
===========================================================================*/
int QMIWDASetDataFormat( sGobiUSBNet * pDev )
{
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();
pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
if (pWriteBuffer == NULL)
{
return -ENOMEM;
}
result = QMIWDASetDataFormatReq( pWriteBuffer,
writeBufferSize,
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;
result = QMIWDASetDataFormatResp( pReadBuffer,
readBufferSize );
kfree( pReadBuffer );
#if 1 //def DATA_MODE_RP
pDev->mbRawIPMode = (result == 2);
if (pDev->mbRawIPMode) {
pDev->mpNetDev->net->flags |= IFF_NOARP;
}
#endif
if (result < 0)
{
DBG( "Data Format Cannot be set\n" );
}
ReleaseClientID( pDev, WDAClientID );
// Success
return 0;
}