3995 lines
109 KiB
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;
|
|
}
|