Am Freitag, 18. März 2011, 21:12:59 schrieb Greg KH: > But, I wouldn't mind that going into the staging tree for .40 so that > people can clean it up there, want to do that? Greg, I doubt I can sign up on that stuff. I haven't written it. Regards Oliver
From b41bd7026f043771fa9b3181e0d5add57c565d8c Mon Sep 17 00:00:00 2001 From: Oliver Neukum <oliver@xxxxxxxxxx> Date: Thu, 17 Mar 2011 17:48:39 +0100 Subject: [PATCH 1/5] Gobi3000:add upstream stuff --- drivers/net/usb/GobiUSBNet.c | 1150 ++++++++++++++ drivers/net/usb/QMI.c | 991 ++++++++++++ drivers/net/usb/QMI.h | 289 ++++ drivers/net/usb/QMIDevice.c | 3273 +++++++++++++++++++++++++++++++++++++++ drivers/net/usb/QMIDevice.h | 339 ++++ drivers/net/usb/Structs.h | 359 +++++ drivers/usb/serial/GobiSerial.c | 754 +++++++++ 7 files changed, 7155 insertions(+), 0 deletions(-) create mode 100644 drivers/net/usb/GobiUSBNet.c create mode 100644 drivers/net/usb/QMI.c create mode 100644 drivers/net/usb/QMI.h create mode 100644 drivers/net/usb/QMIDevice.c create mode 100644 drivers/net/usb/QMIDevice.h create mode 100644 drivers/net/usb/Structs.h create mode 100644 drivers/usb/serial/GobiSerial.c diff --git a/drivers/net/usb/GobiUSBNet.c b/drivers/net/usb/GobiUSBNet.c new file mode 100644 index 0000000..797bcee --- /dev/null +++ b/drivers/net/usb/GobiUSBNet.c @@ -0,0 +1,1150 @@ +/*=========================================================================== +FILE: + GobiUSBNet.c + +DESCRIPTION: + Qualcomm USB Network device for Gobi 3000 + +FUNCTIONS: + GobiSuspend + GobiResume + GobiNetDriverBind + GobiNetDriverUnbind + GobiUSBNetURBCallback + GobiUSBNetTXTimeout + GobiUSBNetAutoPMThread + GobiUSBNetStartXmit + GobiUSBNetOpen + GobiUSBNetStop + GobiUSBNetProbe + GobiUSBNetModInit + GobiUSBNetModExit + +Copyright (c) 2011, Code Aurora Forum. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Code Aurora Forum nor + the names of its contributors may be used to endorse or promote + products derived from this software without specific prior written + permission. + +Alternatively, provided that this notice is retained in full, this software +may be relicensed by the recipient under the terms of the GNU General Public +License version 2 ("GPL") and only version 2, in which case the provisions of +the GPL apply INSTEAD OF those given above. If the recipient relicenses the +software under the GPL, then the identification text in the MODULE_LICENSE +macro must be changed to reflect "GPLv2" instead of "Dual BSD/GPL". Once a +recipient changes the license terms to the GPL, subsequent recipients shall +not relicense under alternate licensing terms, including the BSD or dual +BSD/GPL terms. In addition, the following license statement immediately +below and between the words START and END shall also then apply when this +software is relicensed under the GPL: + +START + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License version 2 and only version 2 as +published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +END + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +===========================================================================*/ + +//--------------------------------------------------------------------------- +// Include Files +//--------------------------------------------------------------------------- + +#include "Structs.h" +#include "QMIDevice.h" +#include "QMI.h" + +//----------------------------------------------------------------------------- +// Definitions +//----------------------------------------------------------------------------- + +// Version Information +#define DRIVER_VERSION "1.0.40" +#define DRIVER_AUTHOR "Qualcomm Innovation Center" +#define DRIVER_DESC "GobiNet" + +// Debug flag +int debug; + +// Allow user interrupts +int interruptible = 1; + +// Class should be created during module init, so needs to be global +static struct class * gpClass; + +/*=========================================================================== +METHOD: + GobiSuspend (Public Method) + +DESCRIPTION: + Stops QMI traffic while device is suspended + +PARAMETERS + pIntf [ I ] - Pointer to interface + powerEvent [ I ] - Power management event + +RETURN VALUE: + int - 0 for success + negative errno for failure +===========================================================================*/ +int GobiSuspend( + struct usb_interface * pIntf, + pm_message_t powerEvent ) +{ + struct usbnet * pDev; + sGobiUSBNet * pGobiDev; + + if (pIntf == 0) + { + return -ENOMEM; + } + +#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,23 )) + pDev = usb_get_intfdata( pIntf ); +#else + pDev = (struct usbnet *)pIntf->dev.platform_data; +#endif + + if (pDev == NULL || pDev->net == NULL) + { + DBG( "failed to get netdevice\n" ); + return -ENXIO; + } + + pGobiDev = (sGobiUSBNet *)pDev->data[0]; + if (pGobiDev == NULL) + { + DBG( "failed to get QMIDevice\n" ); + return -ENXIO; + } + + // Is this autosuspend or system suspend? + // do we allow remote wakeup? +#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,33 )) + if (pDev->udev->auto_pm == 0) +#else + if ((powerEvent.event & PM_EVENT_AUTO) == 0) +#endif + { + DBG( "device suspended to power level %d\n", + powerEvent.event ); + GobiSetDownReason( pGobiDev, DRIVER_SUSPENDED ); + } + else + { + DBG( "device autosuspend\n" ); + } + + if (powerEvent.event & PM_EVENT_SUSPEND) + { + // Stop QMI read callbacks + KillRead( pGobiDev ); + pDev->udev->reset_resume = 0; + + // Store power state to avoid duplicate resumes + pIntf->dev.power.power_state.event = powerEvent.event; + } + else + { + // Other power modes cause QMI connection to be lost + pDev->udev->reset_resume = 1; + } + + // Run usbnet's suspend function + return usbnet_suspend( pIntf, powerEvent ); +} + +/*=========================================================================== +METHOD: + GobiResume (Public Method) + +DESCRIPTION: + Resume QMI traffic or recreate QMI device + +PARAMETERS + pIntf [ I ] - Pointer to interface + +RETURN VALUE: + int - 0 for success + negative errno for failure +===========================================================================*/ +int GobiResume( struct usb_interface * pIntf ) +{ + struct usbnet * pDev; + sGobiUSBNet * pGobiDev; + int nRet; + int oldPowerState; + + if (pIntf == 0) + { + return -ENOMEM; + } + +#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,23 )) + pDev = usb_get_intfdata( pIntf ); +#else + pDev = (struct usbnet *)pIntf->dev.platform_data; +#endif + + if (pDev == NULL || pDev->net == NULL) + { + DBG( "failed to get netdevice\n" ); + return -ENXIO; + } + + pGobiDev = (sGobiUSBNet *)pDev->data[0]; + if (pGobiDev == NULL) + { + DBG( "failed to get QMIDevice\n" ); + return -ENXIO; + } + + oldPowerState = pIntf->dev.power.power_state.event; + pIntf->dev.power.power_state.event = PM_EVENT_ON; + DBG( "resuming from power mode %d\n", oldPowerState ); + + if (oldPowerState & PM_EVENT_SUSPEND) + { + // It doesn't matter if this is autoresume or system resume + GobiClearDownReason( pGobiDev, DRIVER_SUSPENDED ); + + nRet = usbnet_resume( pIntf ); + if (nRet != 0) + { + DBG( "usbnet_resume error %d\n", nRet ); + return nRet; + } + + // Restart QMI read callbacks + nRet = StartRead( pGobiDev ); + if (nRet != 0) + { + DBG( "StartRead error %d\n", nRet ); + return nRet; + } + + // Kick Auto PM thread to process any queued URBs + complete( &pGobiDev->mAutoPM.mThreadDoWork ); + } + else + { + DBG( "nothing to resume\n" ); + return 0; + } + + return nRet; +} + +/*=========================================================================== +METHOD: + GobiNetDriverBind (Public Method) + +DESCRIPTION: + Setup in and out pipes + +PARAMETERS + pDev [ I ] - Pointer to usbnet device + pIntf [ I ] - Pointer to interface + +RETURN VALUE: + int - 0 for success + Negative errno for error +===========================================================================*/ +static int GobiNetDriverBind( + struct usbnet * pDev, + struct usb_interface * pIntf ) +{ + int numEndpoints; + int endpointIndex; + struct usb_host_endpoint * pEndpoint = NULL; + struct usb_host_endpoint * pIn = NULL; + struct usb_host_endpoint * pOut = NULL; + + // Verify one altsetting + if (pIntf->num_altsetting != 1) + { + DBG( "invalid num_altsetting %u\n", pIntf->num_altsetting ); + return -ENODEV; + } + + // Verify correct interface (0 or 5) + if ( (pIntf->cur_altsetting->desc.bInterfaceNumber != 0) + && (pIntf->cur_altsetting->desc.bInterfaceNumber != 5) ) + { + DBG( "invalid interface %d\n", + pIntf->cur_altsetting->desc.bInterfaceNumber ); + return -ENODEV; + } + + // Collect In and Out endpoints + numEndpoints = pIntf->cur_altsetting->desc.bNumEndpoints; + for (endpointIndex = 0; endpointIndex < numEndpoints; endpointIndex++) + { + pEndpoint = pIntf->cur_altsetting->endpoint + endpointIndex; + if (pEndpoint == NULL) + { + DBG( "invalid endpoint %u\n", endpointIndex ); + return -ENODEV; + } + + if (usb_endpoint_dir_in( &pEndpoint->desc ) == true + && usb_endpoint_xfer_int( &pEndpoint->desc ) == false) + { + pIn = pEndpoint; + } + else if (usb_endpoint_dir_out( &pEndpoint->desc ) == true) + { + pOut = pEndpoint; + } + } + + if (pIn == NULL || pOut == NULL) + { + DBG( "invalid endpoints\n" ); + return -ENODEV; + } + + if (usb_set_interface( pDev->udev, + pIntf->cur_altsetting->desc.bInterfaceNumber, + 0 ) != 0) + { + DBG( "unable to set interface\n" ); + return -ENODEV; + } + + pDev->in = usb_rcvbulkpipe( pDev->udev, + pIn->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK ); + pDev->out = usb_sndbulkpipe( pDev->udev, + pOut->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK ); + + DBG( "in %x, out %x\n", + pIn->desc.bEndpointAddress, + pOut->desc.bEndpointAddress ); + + // In later versions of the kernel, usbnet helps with this +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,23 )) + pIntf->dev.platform_data = (void *)pDev; +#endif + + return 0; +} + +/*=========================================================================== +METHOD: + GobiNetDriverUnbind (Public Method) + +DESCRIPTION: + Deregisters QMI device (Registration happened in the probe function) + +PARAMETERS + pDev [ I ] - Pointer to usbnet device + pIntfUnused [ I ] - Pointer to interface + +RETURN VALUE: + None +===========================================================================*/ +static void GobiNetDriverUnbind( + struct usbnet * pDev, + struct usb_interface * pIntf) +{ + sGobiUSBNet * pGobiDev = (sGobiUSBNet *)pDev->data[0]; + + // Should already be down, but just in case... + netif_carrier_off( pDev->net ); + + DeregisterQMIDevice( pGobiDev ); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 2,6,29 )) + kfree( pDev->net->netdev_ops ); + pDev->net->netdev_ops = NULL; +#endif + +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,23 )) + pIntf->dev.platform_data = NULL; +#endif + + kfree( pGobiDev ); + pGobiDev = NULL; +} + +/*=========================================================================== +METHOD: + GobiUSBNetURBCallback (Public Method) + +DESCRIPTION: + Write is complete, cleanup and signal that we're ready for next packet + +PARAMETERS + pURB [ I ] - Pointer to sAutoPM struct + +RETURN VALUE: + None +===========================================================================*/ +void GobiUSBNetURBCallback( struct urb * pURB ) +{ + unsigned long activeURBflags; + sAutoPM * pAutoPM = (sAutoPM *)pURB->context; + if (pAutoPM == NULL) + { + // Should never happen + DBG( "bad context\n" ); + return; + } + + if (pURB->status != 0) + { + // Note that in case of an error, the behaviour is no different + DBG( "urb finished with error %d\n", pURB->status ); + } + + // Remove activeURB (memory to be freed later) + spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags ); + + // EAGAIN used to signify callback is done + pAutoPM->mpActiveURB = ERR_PTR( -EAGAIN ); + + spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags ); + + complete( &pAutoPM->mThreadDoWork ); + + usb_free_urb( pURB ); +} + +/*=========================================================================== +METHOD: + GobiUSBNetTXTimeout (Public Method) + +DESCRIPTION: + Timeout declared by the net driver. Stop all transfers + +PARAMETERS + pNet [ I ] - Pointer to net device + +RETURN VALUE: + None +===========================================================================*/ +void GobiUSBNetTXTimeout( struct net_device * pNet ) +{ + struct sGobiUSBNet * pGobiDev; + sAutoPM * pAutoPM; + sURBList * pURBListEntry; + unsigned long activeURBflags, URBListFlags; + struct usbnet * pDev = netdev_priv( pNet ); + + if (pDev == NULL || pDev->net == NULL) + { + DBG( "failed to get usbnet device\n" ); + return; + } + + pGobiDev = (sGobiUSBNet *)pDev->data[0]; + if (pGobiDev == NULL) + { + DBG( "failed to get QMIDevice\n" ); + return; + } + pAutoPM = &pGobiDev->mAutoPM; + + DBG( "\n" ); + + // Stop activeURB + spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags ); + + if (pAutoPM->mpActiveURB != NULL) + { + usb_kill_urb( pAutoPM->mpActiveURB ); + } + + spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags ); + + // Cleanup URB List + spin_lock_irqsave( &pAutoPM->mURBListLock, URBListFlags ); + + pURBListEntry = pAutoPM->mpURBList; + while (pURBListEntry != NULL) + { + pAutoPM->mpURBList = pAutoPM->mpURBList->mpNext; + usb_free_urb( pURBListEntry->mpURB ); + kfree( pURBListEntry ); + pURBListEntry = pAutoPM->mpURBList; + } + + spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags ); + + complete( &pAutoPM->mThreadDoWork ); + + return; +} + +/*=========================================================================== +METHOD: + GobiUSBNetAutoPMThread (Public Method) + +DESCRIPTION: + Handle device Auto PM state asynchronously + Handle network packet transmission asynchronously + +PARAMETERS + pData [ I ] - Pointer to sAutoPM struct + +RETURN VALUE: + int - 0 for success + Negative errno for error +===========================================================================*/ +static int GobiUSBNetAutoPMThread( void * pData ) +{ + unsigned long activeURBflags, URBListFlags; + sURBList * pURBListEntry; + int status; + struct usb_device * pUdev; + sAutoPM * pAutoPM = (sAutoPM *)pData; + if (pAutoPM == NULL) + { + DBG( "passed null pointer\n" ); + return -EINVAL; + } + + pUdev = interface_to_usbdev( pAutoPM->mpIntf ); + + DBG( "traffic thread started\n" ); + + while (pAutoPM->mbExit == false) + { + // Wait for someone to poke us + wait_for_completion_interruptible( &pAutoPM->mThreadDoWork ); + + // Time to exit? + if (pAutoPM->mbExit == true) + { + // Stop activeURB + spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags ); + + if (pAutoPM->mpActiveURB != NULL) + { + usb_kill_urb( pAutoPM->mpActiveURB ); + } + // Will be freed in callback function + + spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags ); + + // Cleanup URB List + spin_lock_irqsave( &pAutoPM->mURBListLock, URBListFlags ); + + pURBListEntry = pAutoPM->mpURBList; + while (pURBListEntry != NULL) + { + pAutoPM->mpURBList = pAutoPM->mpURBList->mpNext; + usb_free_urb( pURBListEntry->mpURB ); + kfree( pURBListEntry ); + pURBListEntry = pAutoPM->mpURBList; + } + + spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags ); + + break; + } + + // Is our URB active? + spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags ); + + // EAGAIN used to signify callback is done + if (IS_ERR( pAutoPM->mpActiveURB ) + && PTR_ERR( pAutoPM->mpActiveURB ) == -EAGAIN ) + { + pAutoPM->mpActiveURB = NULL; + + // Restore IRQs so task can sleep + spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags ); + + // URB is done, decrement the Auto PM usage count + usb_autopm_put_interface( pAutoPM->mpIntf ); + + // Lock ActiveURB again + spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags ); + } + + if (pAutoPM->mpActiveURB != NULL) + { + // There is already a URB active, go back to sleep + spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags ); + continue; + } + + // Is there a URB waiting to be submitted? + spin_lock_irqsave( &pAutoPM->mURBListLock, URBListFlags ); + if (pAutoPM->mpURBList == NULL) + { + // No more URBs to submit, go back to sleep + spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags ); + spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags ); + continue; + } + + // Pop an element + pURBListEntry = pAutoPM->mpURBList; + pAutoPM->mpURBList = pAutoPM->mpURBList->mpNext; + spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags ); + + // Set ActiveURB + pAutoPM->mpActiveURB = pURBListEntry->mpURB; + spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags ); + + // Tell autopm core we need device woken up + status = usb_autopm_get_interface( pAutoPM->mpIntf ); + if (status < 0) + { + DBG( "unable to autoresume interface: %d\n", status ); + + // likely caused by device going from autosuspend -> full suspend + if (status == -EPERM) + { +#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,33 )) + pUdev->auto_pm = 0; +#endif + GobiSuspend( pAutoPM->mpIntf, PMSG_SUSPEND ); + } + + // Add pURBListEntry back onto pAutoPM->mpURBList + spin_lock_irqsave( &pAutoPM->mURBListLock, URBListFlags ); + pURBListEntry->mpNext = pAutoPM->mpURBList; + pAutoPM->mpURBList = pURBListEntry; + spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags ); + + spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags ); + pAutoPM->mpActiveURB = NULL; + spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags ); + + // Go back to sleep + continue; + } + + // Submit URB + status = usb_submit_urb( pAutoPM->mpActiveURB, GFP_KERNEL ); + if (status < 0) + { + // Could happen for a number of reasons + DBG( "Failed to submit URB: %d. Packet dropped\n", status ); + spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags ); + usb_free_urb( pAutoPM->mpActiveURB ); + pAutoPM->mpActiveURB = NULL; + spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags ); + usb_autopm_put_interface( pAutoPM->mpIntf ); + + // Loop again + complete( &pAutoPM->mThreadDoWork ); + } + + kfree( pURBListEntry ); + } + + DBG( "traffic thread exiting\n" ); + pAutoPM->mpThread = NULL; + return 0; +} + +/*=========================================================================== +METHOD: + GobiUSBNetStartXmit (Public Method) + +DESCRIPTION: + Convert sk_buff to usb URB and queue for transmit + +PARAMETERS + pNet [ I ] - Pointer to net device + +RETURN VALUE: + NETDEV_TX_OK on success + NETDEV_TX_BUSY on error +===========================================================================*/ +int GobiUSBNetStartXmit( + struct sk_buff * pSKB, + struct net_device * pNet ) +{ + unsigned long URBListFlags; + struct sGobiUSBNet * pGobiDev; + sAutoPM * pAutoPM; + sURBList * pURBListEntry, ** ppURBListEnd; + void * pURBData; + struct usbnet * pDev = netdev_priv( pNet ); + + DBG( "\n" ); + + if (pDev == NULL || pDev->net == NULL) + { + DBG( "failed to get usbnet device\n" ); + return NETDEV_TX_BUSY; + } + + pGobiDev = (sGobiUSBNet *)pDev->data[0]; + if (pGobiDev == NULL) + { + DBG( "failed to get QMIDevice\n" ); + return NETDEV_TX_BUSY; + } + pAutoPM = &pGobiDev->mAutoPM; + + if (GobiTestDownReason( pGobiDev, DRIVER_SUSPENDED ) == true) + { + // Should not happen + DBG( "device is suspended\n" ); + dump_stack(); + return NETDEV_TX_BUSY; + } + + // Convert the sk_buff into a URB + + // Allocate URBListEntry + pURBListEntry = kmalloc( sizeof( sURBList ), GFP_ATOMIC ); + if (pURBListEntry == NULL) + { + DBG( "unable to allocate URBList memory\n" ); + return NETDEV_TX_BUSY; + } + pURBListEntry->mpNext = NULL; + + // Allocate URB + pURBListEntry->mpURB = usb_alloc_urb( 0, GFP_ATOMIC ); + if (pURBListEntry->mpURB == NULL) + { + DBG( "unable to allocate URB\n" ); + return NETDEV_TX_BUSY; + } + + // Allocate URB transfer_buffer + pURBData = kmalloc( pSKB->len, GFP_ATOMIC ); + if (pURBData == NULL) + { + DBG( "unable to allocate URB data\n" ); + return NETDEV_TX_BUSY; + } + // Fill will SKB's data + memcpy( pURBData, pSKB->data, pSKB->len ); + + usb_fill_bulk_urb( pURBListEntry->mpURB, + pGobiDev->mpNetDev->udev, + pGobiDev->mpNetDev->out, + pURBData, + pSKB->len, + GobiUSBNetURBCallback, + pAutoPM ); + + // Aquire lock on URBList + spin_lock_irqsave( &pAutoPM->mURBListLock, URBListFlags ); + + // Add URB to end of list + ppURBListEnd = &pAutoPM->mpURBList; + while ((*ppURBListEnd) != NULL) + { + ppURBListEnd = &(*ppURBListEnd)->mpNext; + } + *ppURBListEnd = pURBListEntry; + + spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags ); + + complete( &pAutoPM->mThreadDoWork ); + + // Start transfer timer + pNet->trans_start = jiffies; + // Free SKB + dev_kfree_skb_any( pSKB ); + + return NETDEV_TX_OK; +} + +/*=========================================================================== +METHOD: + GobiUSBNetOpen (Public Method) + +DESCRIPTION: + Wrapper to usbnet_open, correctly handling autosuspend + Start AutoPM thread + +PARAMETERS + pNet [ I ] - Pointer to net device + +RETURN VALUE: + int - 0 for success + Negative errno for error +===========================================================================*/ +int GobiUSBNetOpen( struct net_device * pNet ) +{ + int status = 0; + struct sGobiUSBNet * pGobiDev; + struct usbnet * pDev = netdev_priv( pNet ); + + if (pDev == NULL) + { + DBG( "failed to get usbnet device\n" ); + return -ENXIO; + } + + pGobiDev = (sGobiUSBNet *)pDev->data[0]; + if (pGobiDev == NULL) + { + DBG( "failed to get QMIDevice\n" ); + return -ENXIO; + } + + DBG( "\n" ); + + // Start the AutoPM thread + pGobiDev->mAutoPM.mpIntf = pGobiDev->mpIntf; + pGobiDev->mAutoPM.mbExit = false; + pGobiDev->mAutoPM.mpURBList = NULL; + pGobiDev->mAutoPM.mpActiveURB = NULL; + spin_lock_init( &pGobiDev->mAutoPM.mURBListLock ); + spin_lock_init( &pGobiDev->mAutoPM.mActiveURBLock ); + init_completion( &pGobiDev->mAutoPM.mThreadDoWork ); + + pGobiDev->mAutoPM.mpThread = kthread_run( GobiUSBNetAutoPMThread, + &pGobiDev->mAutoPM, + "GobiUSBNetAutoPMThread" ); + if (IS_ERR( pGobiDev->mAutoPM.mpThread )) + { + DBG( "AutoPM thread creation error\n" ); + return PTR_ERR( pGobiDev->mAutoPM.mpThread ); + } + + // Allow traffic + GobiClearDownReason( pGobiDev, NET_IFACE_STOPPED ); + + // Pass to usbnet_open if defined + if (pGobiDev->mpUSBNetOpen != NULL) + { + status = pGobiDev->mpUSBNetOpen( pNet ); + + // If usbnet_open was successful enable Auto PM + if (status == 0) + { +#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,33 )) + usb_autopm_enable( pGobiDev->mpIntf ); +#else + usb_autopm_put_interface( pGobiDev->mpIntf ); +#endif + } + } + else + { + DBG( "no USBNetOpen defined\n" ); + } + + return status; +} + +/*=========================================================================== +METHOD: + GobiUSBNetStop (Public Method) + +DESCRIPTION: + Wrapper to usbnet_stop, correctly handling autosuspend + Stop AutoPM thread + +PARAMETERS + pNet [ I ] - Pointer to net device + +RETURN VALUE: + int - 0 for success + Negative errno for error +===========================================================================*/ +int GobiUSBNetStop( struct net_device * pNet ) +{ + struct sGobiUSBNet * pGobiDev; + struct usbnet * pDev = netdev_priv( pNet ); + + if (pDev == NULL || pDev->net == NULL) + { + DBG( "failed to get netdevice\n" ); + return -ENXIO; + } + + pGobiDev = (sGobiUSBNet *)pDev->data[0]; + if (pGobiDev == NULL) + { + DBG( "failed to get QMIDevice\n" ); + return -ENXIO; + } + + // Stop traffic + GobiSetDownReason( pGobiDev, NET_IFACE_STOPPED ); + + // Tell traffic thread to exit + pGobiDev->mAutoPM.mbExit = true; + complete( &pGobiDev->mAutoPM.mThreadDoWork ); + + // Wait for it to exit + while( pGobiDev->mAutoPM.mpThread != NULL ) + { + msleep( 100 ); + } + DBG( "thread stopped\n" ); + + // Pass to usbnet_stop, if defined + if (pGobiDev->mpUSBNetStop != NULL) + { + return pGobiDev->mpUSBNetStop( pNet ); + } + else + { + return 0; + } +} + +/*=========================================================================*/ +// Struct driver_info +/*=========================================================================*/ +static const struct driver_info GobiNetInfo = +{ + .description = "GobiNet Ethernet Device", + .flags = FLAG_ETHER, + .bind = GobiNetDriverBind, + .unbind = GobiNetDriverUnbind, + .data = 0, +}; + +/*=========================================================================*/ +// Qualcomm Gobi 3000 VID/PIDs +/*=========================================================================*/ +static const struct usb_device_id GobiVIDPIDTable [] = +{ + // Gobi 3000 + { + USB_DEVICE( 0x05c6, 0x920d ), + .driver_info = (unsigned long)&GobiNetInfo + }, + //Terminating entry + { } +}; + +MODULE_DEVICE_TABLE( usb, GobiVIDPIDTable ); + +/*=========================================================================== +METHOD: + GobiUSBNetProbe (Public Method) + +DESCRIPTION: + Run usbnet_probe + Setup QMI device + +PARAMETERS + pIntf [ I ] - Pointer to interface + pVIDPIDs [ I ] - Pointer to VID/PID table + +RETURN VALUE: + int - 0 for success + Negative errno for error +===========================================================================*/ +int GobiUSBNetProbe( + struct usb_interface * pIntf, + const struct usb_device_id * pVIDPIDs ) +{ + int status; + struct usbnet * pDev; + sGobiUSBNet * pGobiDev; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 2,6,29 )) + struct net_device_ops * pNetDevOps; +#endif + + status = usbnet_probe( pIntf, pVIDPIDs ); + if (status < 0) + { + DBG( "usbnet_probe failed %d\n", status ); + return status; + } + +#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,23 )) + pDev = usb_get_intfdata( pIntf ); +#else + pDev = (struct usbnet *)pIntf->dev.platform_data; +#endif + + if (pDev == NULL || pDev->net == NULL) + { + DBG( "failed to get netdevice\n" ); + usbnet_disconnect( pIntf ); + return -ENXIO; + } + + pGobiDev = kmalloc( sizeof( sGobiUSBNet ), GFP_KERNEL ); + if (pGobiDev == NULL) + { + DBG( "falied to allocate device buffers" ); + usbnet_disconnect( pIntf ); + return -ENOMEM; + } + + pDev->data[0] = (unsigned long)pGobiDev; + + pGobiDev->mpNetDev = pDev; + + // Overload PM related network functions +#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 )) + pGobiDev->mpUSBNetOpen = pDev->net->open; + pDev->net->open = GobiUSBNetOpen; + pGobiDev->mpUSBNetStop = pDev->net->stop; + pDev->net->stop = GobiUSBNetStop; + pDev->net->hard_start_xmit = GobiUSBNetStartXmit; + pDev->net->tx_timeout = GobiUSBNetTXTimeout; +#else + pNetDevOps = kmalloc( sizeof( struct net_device_ops ), GFP_KERNEL ); + if (pNetDevOps == NULL) + { + DBG( "falied to allocate net device ops" ); + usbnet_disconnect( pIntf ); + return -ENOMEM; + } + memcpy( pNetDevOps, pDev->net->netdev_ops, sizeof( struct net_device_ops ) ); + + pGobiDev->mpUSBNetOpen = pNetDevOps->ndo_open; + pNetDevOps->ndo_open = GobiUSBNetOpen; + pGobiDev->mpUSBNetStop = pNetDevOps->ndo_stop; + pNetDevOps->ndo_stop = GobiUSBNetStop; + pNetDevOps->ndo_start_xmit = GobiUSBNetStartXmit; + pNetDevOps->ndo_tx_timeout = GobiUSBNetTXTimeout; + + pDev->net->netdev_ops = pNetDevOps; +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,31 )) + memset( &(pGobiDev->mpNetDev->stats), 0, sizeof( struct net_device_stats ) ); +#else + memset( &(pGobiDev->mpNetDev->net->stats), 0, sizeof( struct net_device_stats ) ); +#endif + + pGobiDev->mpIntf = pIntf; + memset( &(pGobiDev->mMEID), '0', 14 ); + + DBG( "Mac Address:\n" ); + PrintHex( &pGobiDev->mpNetDev->net->dev_addr[0], 6 ); + + pGobiDev->mbQMIValid = false; + memset( &pGobiDev->mQMIDev, 0, sizeof( sQMIDev ) ); + pGobiDev->mQMIDev.mbCdevIsInitialized = false; + + pGobiDev->mQMIDev.mpDevClass = gpClass; + + init_completion( &pGobiDev->mAutoPM.mThreadDoWork ); + spin_lock_init( &pGobiDev->mQMIDev.mClientMemLock ); + + // Default to device down + pGobiDev->mDownReason = 0; + GobiSetDownReason( pGobiDev, NO_NDIS_CONNECTION ); + GobiSetDownReason( pGobiDev, NET_IFACE_STOPPED ); + + // Register QMI + status = RegisterQMIDevice( pGobiDev ); + if (status != 0) + { + // usbnet_disconnect() will call GobiNetDriverUnbind() which will call + // DeregisterQMIDevice() to clean up any partially created QMI device + usbnet_disconnect( pIntf ); + return status; + } + + // Success + return 0; +} + +static struct usb_driver GobiNet = +{ + .name = "GobiNet", + .id_table = GobiVIDPIDTable, + .probe = GobiUSBNetProbe, + .disconnect = usbnet_disconnect, + .suspend = GobiSuspend, + .resume = GobiResume, + .supports_autosuspend = true, +}; + +/*=========================================================================== +METHOD: + GobiUSBNetModInit (Public Method) + +DESCRIPTION: + Initialize module + Create device class + Register out usb_driver struct + +RETURN VALUE: + int - 0 for success + Negative errno for error +===========================================================================*/ +static int __init GobiUSBNetModInit( void ) +{ + gpClass = class_create( THIS_MODULE, "GobiQMI" ); + if (IS_ERR( gpClass ) == true) + { + DBG( "error at class_create %ld\n", + PTR_ERR( gpClass ) ); + return -ENOMEM; + } + + // This will be shown whenever driver is loaded + printk( KERN_INFO "%s: %s\n", DRIVER_DESC, DRIVER_VERSION ); + + return usb_register( &GobiNet ); +} +module_init( GobiUSBNetModInit ); + +/*=========================================================================== +METHOD: + GobiUSBNetModExit (Public Method) + +DESCRIPTION: + Deregister module + Destroy device class + +RETURN VALUE: + void +===========================================================================*/ +static void __exit GobiUSBNetModExit( void ) +{ + usb_deregister( &GobiNet ); + + class_destroy( gpClass ); +} +module_exit( GobiUSBNetModExit ); + +MODULE_VERSION( DRIVER_VERSION ); +MODULE_AUTHOR( DRIVER_AUTHOR ); +MODULE_DESCRIPTION( DRIVER_DESC ); +MODULE_LICENSE( "Dual BSD/GPL" ); + +#ifdef bool +#undef bool +#endif + +module_param( debug, bool, S_IRUGO | S_IWUSR ); +MODULE_PARM_DESC( debug, "Debuging enabled or not" ); + +module_param( interruptible, bool, S_IRUGO | S_IWUSR ); +MODULE_PARM_DESC( interruptible, "Listen for and return on user interrupt" ); diff --git a/drivers/net/usb/QMI.c b/drivers/net/usb/QMI.c new file mode 100644 index 0000000..f02f927 --- /dev/null +++ b/drivers/net/usb/QMI.c @@ -0,0 +1,991 @@ +/*=========================================================================== +FILE: + QMI.c + +DESCRIPTION: + Qualcomm QMI driver code + +FUNCTIONS: + Generic QMUX functions + ParseQMUX + FillQMUX + + Generic QMI functions + GetTLV + ValidQMIMessage + GetQMIMessageID + + Fill Buffers with QMI requests + QMICTLGetClientIDReq + QMICTLReleaseClientIDReq + QMICTLReadyReq + QMIWDSSetEventReportReq + QMIWDSGetPKGSRVCStatusReq + QMIDMSGetMEIDReq + + Parse data from QMI responses + QMICTLGetClientIDResp + QMICTLReleaseClientIDResp + QMIWDSEventResp + QMIDMSGetMEIDResp + +Copyright (c) 2011, Code Aurora Forum. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Code Aurora Forum nor + the names of its contributors may be used to endorse or promote + products derived from this software without specific prior written + permission. + +Alternatively, provided that this notice is retained in full, this software +may be relicensed by the recipient under the terms of the GNU General Public +License version 2 ("GPL") and only version 2, in which case the provisions of +the GPL apply INSTEAD OF those given above. If the recipient relicenses the +software under the GPL, then the identification text in the MODULE_LICENSE +macro must be changed to reflect "GPLv2" instead of "Dual BSD/GPL". Once a +recipient changes the license terms to the GPL, subsequent recipients shall +not relicense under alternate licensing terms, including the BSD or dual +BSD/GPL terms. In addition, the following license statement immediately +below and between the words START and END shall also then apply when this +software is relicensed under the GPL: + +START + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License version 2 and only version 2 as +published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +END + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +===========================================================================*/ + +//--------------------------------------------------------------------------- +// Include Files +//--------------------------------------------------------------------------- +#include "QMI.h" + +/*=========================================================================*/ +// Get sizes of buffers needed by QMI requests +/*=========================================================================*/ + +/*=========================================================================== +METHOD: + QMUXHeaderSize (Public Method) + +DESCRIPTION: + Get size of buffer needed for QMUX + +RETURN VALUE: + u16 - size of buffer +===========================================================================*/ +u16 QMUXHeaderSize( void ) +{ + return sizeof( sQMUX ); +} + +/*=========================================================================== +METHOD: + QMICTLGetClientIDReqSize (Public Method) + +DESCRIPTION: + Get size of buffer needed for QMUX + QMICTLGetClientIDReq + +RETURN VALUE: + u16 - size of buffer +===========================================================================*/ +u16 QMICTLGetClientIDReqSize( void ) +{ + return sizeof( sQMUX ) + 10; +} + +/*=========================================================================== +METHOD: + QMICTLReleaseClientIDReqSize (Public Method) + +DESCRIPTION: + Get size of buffer needed for QMUX + QMICTLReleaseClientIDReq + +RETURN VALUE: + u16 - size of header +===========================================================================*/ +u16 QMICTLReleaseClientIDReqSize( void ) +{ + return sizeof( sQMUX ) + 11; +} + +/*=========================================================================== +METHOD: + QMICTLReadyReqSize (Public Method) + +DESCRIPTION: + Get size of buffer needed for QMUX + QMICTLReadyReq + +RETURN VALUE: + u16 - size of buffer +===========================================================================*/ +u16 QMICTLReadyReqSize( void ) +{ + return sizeof( sQMUX ) + 6; +} + +/*=========================================================================== +METHOD: + QMIWDSSetEventReportReqSize (Public Method) + +DESCRIPTION: + Get size of buffer needed for QMUX + QMIWDSSetEventReportReq + +RETURN VALUE: + u16 - size of buffer +===========================================================================*/ +u16 QMIWDSSetEventReportReqSize( void ) +{ + return sizeof( sQMUX ) + 15; +} + +/*=========================================================================== +METHOD: + QMIWDSGetPKGSRVCStatusReqSize (Public Method) + +DESCRIPTION: + Get size of buffer needed for QMUX + QMIWDSGetPKGSRVCStatusReq + +RETURN VALUE: + u16 - size of buffer +===========================================================================*/ +u16 QMIWDSGetPKGSRVCStatusReqSize( void ) +{ + return sizeof( sQMUX ) + 7; +} + +/*=========================================================================== +METHOD: + QMIDMSGetMEIDReqSize (Public Method) + +DESCRIPTION: + Get size of buffer needed for QMUX + QMIDMSGetMEIDReq + +RETURN VALUE: + u16 - size of buffer +===========================================================================*/ +u16 QMIDMSGetMEIDReqSize( void ) +{ + return sizeof( sQMUX ) + 7; +} + +/*=========================================================================*/ +// Generic QMUX functions +/*=========================================================================*/ + +/*=========================================================================== +METHOD: + ParseQMUX (Public Method) + +DESCRIPTION: + Remove QMUX headers from a buffer + +PARAMETERS + pClientID [ O ] - On success, will point to Client ID + pBuffer [ I ] - Full Message passed in + buffSize [ I ] - Size of pBuffer + +RETURN VALUE: + int - Positive for size of QMUX header + Negative errno for error +===========================================================================*/ +int ParseQMUX( + u16 * pClientID, + void * pBuffer, + u16 buffSize ) +{ + sQMUX * pQMUXHeader; + + if (pBuffer == 0 || buffSize < 12) + { + return -ENOMEM; + } + + // QMUX Header + pQMUXHeader = (sQMUX *)pBuffer; + + if (pQMUXHeader->mTF != 1 + || pQMUXHeader->mLength != buffSize - 1 + || pQMUXHeader->mCtrlFlag != 0x80 ) + { + return -EINVAL; + } + + // Client ID + *pClientID = (pQMUXHeader->mQMIClientID << 8) + + pQMUXHeader->mQMIService; + + return sizeof( sQMUX ); +} + +/*=========================================================================== +METHOD: + FillQMUX (Public Method) + +DESCRIPTION: + Fill buffer with QMUX headers + +PARAMETERS + clientID [ I ] - Client ID + pBuffer [ O ] - Buffer to be filled + buffSize [ I ] - Size of pBuffer (must be at least 6) + +RETURN VALUE: + int - 0 for success + Negative errno for error +===========================================================================*/ +int FillQMUX( + u16 clientID, + void * pBuffer, + u16 buffSize ) +{ + sQMUX * pQMUXHeader; + + if (pBuffer == 0 || buffSize < sizeof( sQMUX )) + { + return -ENOMEM; + } + + // QMUX Header + pQMUXHeader = (sQMUX *)pBuffer; + + pQMUXHeader->mTF = 1; + pQMUXHeader->mLength = buffSize - 1; + pQMUXHeader->mCtrlFlag = 0; + + // Service and Client ID + pQMUXHeader->mQMIService = clientID & 0xff; + pQMUXHeader->mQMIClientID = clientID >> 8; + + return 0; +} + +/*=========================================================================*/ +// Generic QMI functions +/*=========================================================================*/ + +/*=========================================================================== +METHOD: + GetTLV (Public Method) + +DESCRIPTION: + Get data bufffer of a specified TLV from a QMI message + + QMI Message shall NOT include SDU + +PARAMETERS + pQMIMessage [ I ] - QMI Message buffer + messageLen [ I ] - Size of QMI Message buffer + type [ I ] - Desired Type + pOutDataBuf [ O ] - Buffer to be filled with TLV + messageLen [ I ] - Size of QMI Message buffer + +RETURN VALUE: + u16 - Size of TLV for success + Negative errno for error +===========================================================================*/ +u16 GetTLV( + void * pQMIMessage, + u16 messageLen, + u8 type, + void * pOutDataBuf, + u16 bufferLen ) +{ + u16 pos; + u16 tlvSize = 0; + u16 cpyCount; + + if (pQMIMessage == 0 || pOutDataBuf == 0) + { + return -ENOMEM; + } + + for (pos = 4; + pos + 3 < messageLen; + pos += tlvSize + 3) + { + tlvSize = *(u16 *)(pQMIMessage + pos + 1); + if (*(u8 *)(pQMIMessage + pos) == type) + { + if (bufferLen < tlvSize) + { + return -ENOMEM; + } + + /* replacement memcpy + memcpy( pOutDataBuf, + pQMIMessage + pos + 3, + tlvSize ); */ + + for (cpyCount = 0; cpyCount < tlvSize; cpyCount++) + { + *((char*)(pOutDataBuf + cpyCount)) = *((char*)(pQMIMessage + pos + 3 + cpyCount)); + } + + return tlvSize; + } + } + + return -ENOMSG; +} + +/*=========================================================================== +METHOD: + ValidQMIMessage (Public Method) + +DESCRIPTION: + Check mandatory TLV in a QMI message + + QMI Message shall NOT include SDU + +PARAMETERS + pQMIMessage [ I ] - QMI Message buffer + messageLen [ I ] - Size of QMI Message buffer + +RETURN VALUE: + int - 0 for success (no error) + Negative errno for error + Positive for QMI error code +===========================================================================*/ +int ValidQMIMessage( + void * pQMIMessage, + u16 messageLen ) +{ + char mandTLV[4]; + + if (GetTLV( pQMIMessage, messageLen, 2, &mandTLV[0], 4 ) == 4) + { + // Found TLV + if (*(u16 *)&mandTLV[0] != 0) + { + return *(u16 *)&mandTLV[2]; + } + else + { + return 0; + } + } + else + { + return -ENOMSG; + } +} + +/*=========================================================================== +METHOD: + GetQMIMessageID (Public Method) + +DESCRIPTION: + Get the message ID of a QMI message + + QMI Message shall NOT include SDU + +PARAMETERS + pQMIMessage [ I ] - QMI Message buffer + messageLen [ I ] - Size of QMI Message buffer + +RETURN VALUE: + int - Positive for message ID + Negative errno for error +===========================================================================*/ +int GetQMIMessageID( + void * pQMIMessage, + u16 messageLen ) +{ + if (messageLen < 2) + { + return -ENODATA; + } + else + { + return *(u16 *)pQMIMessage; + } +} + +/*=========================================================================*/ +// Fill Buffers with QMI requests +/*=========================================================================*/ + +/*=========================================================================== +METHOD: + QMICTLGetClientIDReq (Public Method) + +DESCRIPTION: + Fill buffer with QMI CTL Get Client ID Request + +PARAMETERS + pBuffer [ 0 ] - Buffer to be filled + buffSize [ I ] - Size of pBuffer + transactionID [ I ] - Transaction ID + serviceType [ I ] - Service type requested + +RETURN VALUE: + int - Positive for resulting size of pBuffer + Negative errno for error +===========================================================================*/ +int QMICTLGetClientIDReq( + void * pBuffer, + u16 buffSize, + u8 transactionID, + u8 serviceType ) +{ + if (pBuffer == 0 || buffSize < QMICTLGetClientIDReqSize() ) + { + return -ENOMEM; + } + + // QMI CTL GET CLIENT ID + // Request + *(u8 *)(pBuffer + sizeof( sQMUX ))= 0x00; + // Transaction ID + *(u8 *)(pBuffer + sizeof( sQMUX ) + 1) = transactionID; + // Message ID + *(u16 *)(pBuffer + sizeof( sQMUX ) + 2) = 0x0022; + // Size of TLV's + *(u16 *)(pBuffer + sizeof( sQMUX ) + 4) = 0x0004; + // QMI Service Type + *(u8 *)(pBuffer + sizeof( sQMUX ) + 6) = 0x01; + // Size + *(u16 *)(pBuffer + sizeof( sQMUX ) + 7) = 0x0001; + // QMI svc type + *(u8 *)(pBuffer + sizeof( sQMUX ) + 9) = serviceType; + + // success + return sizeof( sQMUX ) + 10; +} + +/*=========================================================================== +METHOD: + QMICTLReleaseClientIDReq (Public Method) + +DESCRIPTION: + Fill buffer with QMI CTL Release Client ID Request + +PARAMETERS + pBuffer [ 0 ] - Buffer to be filled + buffSize [ I ] - Size of pBuffer + transactionID [ I ] - Transaction ID + clientID [ I ] - Service type requested + +RETURN VALUE: + int - Positive for resulting size of pBuffer + Negative errno for error +===========================================================================*/ +int QMICTLReleaseClientIDReq( + void * pBuffer, + u16 buffSize, + u8 transactionID, + u16 clientID ) +{ + if (pBuffer == 0 || buffSize < QMICTLReleaseClientIDReqSize() ) + { + return -ENOMEM; + } + + // QMI CTL RELEASE CLIENT ID REQ + // Request + *(u8 *)(pBuffer + sizeof( sQMUX )) = 0x00; + // Transaction ID + *(u8 *)(pBuffer + sizeof( sQMUX ) + 1 ) = transactionID; + // Message ID + *(u16 *)(pBuffer + sizeof( sQMUX ) + 2) = 0x0023; + // Size of TLV's + *(u16 *)(pBuffer + sizeof( sQMUX ) + 4) = 0x0005; + // Release client ID + *(u8 *)(pBuffer + sizeof( sQMUX ) + 6) = 0x01; + // Size + *(u16 *)(pBuffer + sizeof( sQMUX ) + 7) = 0x0002; + // QMI svs type / Client ID + *(u16 *)(pBuffer + sizeof( sQMUX ) + 9) = clientID; + + // success + return sizeof( sQMUX ) + 11; +} + +/*=========================================================================== +METHOD: + QMICTLReadyReq (Public Method) + +DESCRIPTION: + Fill buffer with QMI CTL Get Version Info Request + +PARAMETERS + pBuffer [ 0 ] - Buffer to be filled + buffSize [ I ] - Size of pBuffer + transactionID [ I ] - Transaction ID + +RETURN VALUE: + int - Positive for resulting size of pBuffer + Negative errno for error +===========================================================================*/ +int QMICTLReadyReq( + void * pBuffer, + u16 buffSize, + u8 transactionID ) +{ + if (pBuffer == 0 || buffSize < QMICTLReadyReqSize() ) + { + return -ENOMEM; + } + + // QMI CTL GET VERSION INFO REQ + // Request + *(u8 *)(pBuffer + sizeof( sQMUX )) = 0x00; + // Transaction ID + *(u8 *)(pBuffer + sizeof( sQMUX ) + 1) = transactionID; + // Message ID + *(u16 *)(pBuffer + sizeof( sQMUX ) + 2) = 0x0021; + // Size of TLV's + *(u16 *)(pBuffer + sizeof( sQMUX ) + 4) = 0x0000; + + // success + return sizeof( sQMUX ) + 6; +} + +/*=========================================================================== +METHOD: + QMIWDSSetEventReportReq (Public Method) + +DESCRIPTION: + Fill buffer with QMI WDS Set Event Report Request + +PARAMETERS + pBuffer [ 0 ] - Buffer to be filled + buffSize [ I ] - Size of pBuffer + transactionID [ I ] - Transaction ID + +RETURN VALUE: + int - Positive for resulting size of pBuffer + Negative errno for error +===========================================================================*/ +int QMIWDSSetEventReportReq( + void * pBuffer, + u16 buffSize, + u16 transactionID ) +{ + if (pBuffer == 0 || buffSize < QMIWDSSetEventReportReqSize() ) + { + return -ENOMEM; + } + + // QMI WDS SET EVENT REPORT REQ + // Request + *(u8 *)(pBuffer + sizeof( sQMUX )) = 0x00; + // Transaction ID + *(u16 *)(pBuffer + sizeof( sQMUX ) + 1) = transactionID; + // Message ID + *(u16 *)(pBuffer + sizeof( sQMUX ) + 3) = 0x0001; + // Size of TLV's + *(u16 *)(pBuffer + sizeof( sQMUX ) + 5) = 0x0008; + // Report channel rate TLV + *(u8 *)(pBuffer + sizeof( sQMUX ) + 7) = 0x11; + // Size + *(u16 *)(pBuffer + sizeof( sQMUX ) + 8) = 0x0005; + // Stats period + *(u8 *)(pBuffer + sizeof( sQMUX ) + 10) = 0x01; + // Stats mask + *(u32 *)(pBuffer + sizeof( sQMUX ) + 11) = 0x000000ff; + + // success + return sizeof( sQMUX ) + 15; +} + +/*=========================================================================== +METHOD: + QMIWDSGetPKGSRVCStatusReq (Public Method) + +DESCRIPTION: + Fill buffer with QMI WDS Get PKG SRVC Status Request + +PARAMETERS + pBuffer [ 0 ] - Buffer to be filled + buffSize [ I ] - Size of pBuffer + transactionID [ I ] - Transaction ID + +RETURN VALUE: + int - Positive for resulting size of pBuffer + Negative errno for error +===========================================================================*/ +int QMIWDSGetPKGSRVCStatusReq( + void * pBuffer, + u16 buffSize, + u16 transactionID ) +{ + if (pBuffer == 0 || buffSize < QMIWDSGetPKGSRVCStatusReqSize() ) + { + return -ENOMEM; + } + + // QMI WDS Get PKG SRVC Status REQ + // Request + *(u8 *)(pBuffer + sizeof( sQMUX )) = 0x00; + // Transaction ID + *(u16 *)(pBuffer + sizeof( sQMUX ) + 1) = transactionID; + // Message ID + *(u16 *)(pBuffer + sizeof( sQMUX ) + 3) = 0x0022; + // Size of TLV's + *(u16 *)(pBuffer + sizeof( sQMUX ) + 5) = 0x0000; + + // success + return sizeof( sQMUX ) + 7; +} + +/*=========================================================================== +METHOD: + QMIDMSGetMEIDReq (Public Method) + +DESCRIPTION: + Fill buffer with QMI DMS Get Serial Numbers Request + +PARAMETERS + pBuffer [ 0 ] - Buffer to be filled + buffSize [ I ] - Size of pBuffer + transactionID [ I ] - Transaction ID + +RETURN VALUE: + int - Positive for resulting size of pBuffer + Negative errno for error +===========================================================================*/ +int QMIDMSGetMEIDReq( + void * pBuffer, + u16 buffSize, + u16 transactionID ) +{ + if (pBuffer == 0 || buffSize < QMIDMSGetMEIDReqSize() ) + { + return -ENOMEM; + } + + // QMI DMS GET SERIAL NUMBERS REQ + // Request + *(u8 *)(pBuffer + sizeof( sQMUX )) = 0x00; + // Transaction ID + *(u16 *)(pBuffer + sizeof( sQMUX ) + 1) = transactionID; + // Message ID + *(u16 *)(pBuffer + sizeof( sQMUX ) + 3) = 0x0025; + // Size of TLV's + *(u16 *)(pBuffer + sizeof( sQMUX ) + 5) = 0x0000; + + // success + return sizeof( sQMUX ) + 7; +} + +/*=========================================================================*/ +// Parse data from QMI responses +/*=========================================================================*/ + +/*=========================================================================== +METHOD: + QMICTLGetClientIDResp (Public Method) + +DESCRIPTION: + Parse the QMI CTL Get Client ID Resp + +PARAMETERS + pBuffer [ I ] - Buffer to be parsed + buffSize [ I ] - Size of pBuffer + pClientID [ 0 ] - Recieved client ID + +RETURN VALUE: + int - 0 for success + Negative errno for error +===========================================================================*/ +int QMICTLGetClientIDResp( + void * pBuffer, + u16 buffSize, + u16 * pClientID ) +{ + int result; + + // Ignore QMUX and SDU + // QMI CTL SDU is 2 bytes, not 3 + u8 offset = sizeof( sQMUX ) + 2; + + if (pBuffer == 0 || buffSize < offset) + { + return -ENOMEM; + } + + pBuffer = pBuffer + offset; + buffSize -= offset; + + result = GetQMIMessageID( pBuffer, buffSize ); + if (result != 0x22) + { + return -EFAULT; + } + + result = ValidQMIMessage( pBuffer, buffSize ); + if (result != 0) + { + return -EFAULT; + } + + result = GetTLV( pBuffer, buffSize, 0x01, pClientID, 2 ); + if (result != 2) + { + return -EFAULT; + } + + return 0; +} + +/*=========================================================================== +METHOD: + QMICTLReleaseClientIDResp (Public Method) + +DESCRIPTION: + Verify the QMI CTL Release Client ID Resp is valid + +PARAMETERS + pBuffer [ I ] - Buffer to be parsed + buffSize [ I ] - Size of pBuffer + +RETURN VALUE: + int - 0 for success + Negative errno for error +===========================================================================*/ +int QMICTLReleaseClientIDResp( + void * pBuffer, + u16 buffSize ) +{ + int result; + + // Ignore QMUX and SDU + // QMI CTL SDU is 2 bytes, not 3 + u8 offset = sizeof( sQMUX ) + 2; + + if (pBuffer == 0 || buffSize < offset) + { + return -ENOMEM; + } + + pBuffer = pBuffer + offset; + buffSize -= offset; + + result = GetQMIMessageID( pBuffer, buffSize ); + if (result != 0x23) + { + return -EFAULT; + } + + result = ValidQMIMessage( pBuffer, buffSize ); + if (result != 0) + { + return -EFAULT; + } + + return 0; +} + +/*=========================================================================== +METHOD: + QMIWDSEventResp (Public Method) + +DESCRIPTION: + Parse the QMI WDS Set Event Report Resp/Indication or + QMI WDS Get PKG SRVC Status Resp/Indication + + Return parameters will only be updated if value was received + +PARAMETERS + pBuffer [ I ] - Buffer to be parsed + buffSize [ I ] - Size of pBuffer + pTXOk [ O ] - Number of transmitted packets without errors + pRXOk [ O ] - Number of recieved packets without errors + pTXErr [ O ] - Number of transmitted packets with framing errors + pRXErr [ O ] - Number of recieved packets with framing errors + pTXOfl [ O ] - Number of transmitted packets dropped due to overflow + pRXOfl [ O ] - Number of recieved packets dropped due to overflow + pTXBytesOk [ O ] - Number of transmitted bytes without errors + pRXBytesOk [ O ] - Number of recieved bytes without errors + pbLinkState [ 0 ] - Is the link active? + pbReconfigure [ 0 ] - Must interface be reconfigured? (reset IP address) + +RETURN VALUE: + int - 0 for success + Negative errno for error +===========================================================================*/ +int QMIWDSEventResp( + void * pBuffer, + u16 buffSize, + u32 * pTXOk, + u32 * pRXOk, + u32 * pTXErr, + u32 * pRXErr, + u32 * pTXOfl, + u32 * pRXOfl, + u64 * pTXBytesOk, + u64 * pRXBytesOk, + bool * pbLinkState, + bool * pbReconfigure ) +{ + int result; + u8 pktStatusRead[2]; + + // Ignore QMUX and SDU + u8 offset = sizeof( sQMUX ) + 3; + + if (pBuffer == 0 + || buffSize < offset + || pTXOk == 0 + || pRXOk == 0 + || pTXErr == 0 + || pRXErr == 0 + || pTXOfl == 0 + || pRXOfl == 0 + || pTXBytesOk == 0 + || pRXBytesOk == 0 + || pbLinkState == 0 + || pbReconfigure == 0 ) + { + return -ENOMEM; + } + + pBuffer = pBuffer + offset; + buffSize -= offset; + + // Note: Indications. No Mandatory TLV required + + result = GetQMIMessageID( pBuffer, buffSize ); + // QMI WDS Set Event Report Resp + if (result == 0x01) + { + // TLV's are not mandatory + GetTLV( pBuffer, buffSize, 0x10, (void*)pTXOk, 4 ); + GetTLV( pBuffer, buffSize, 0x11, (void*)pRXOk, 4 ); + GetTLV( pBuffer, buffSize, 0x12, (void*)pTXErr, 4 ); + GetTLV( pBuffer, buffSize, 0x13, (void*)pRXErr, 4 ); + GetTLV( pBuffer, buffSize, 0x14, (void*)pTXOfl, 4 ); + GetTLV( pBuffer, buffSize, 0x15, (void*)pRXOfl, 4 ); + GetTLV( pBuffer, buffSize, 0x19, (void*)pTXBytesOk, 8 ); + GetTLV( pBuffer, buffSize, 0x1A, (void*)pRXBytesOk, 8 ); + } + // QMI WDS Get PKG SRVC Status Resp + else if (result == 0x22) + { + result = GetTLV( pBuffer, buffSize, 0x01, &pktStatusRead[0], 2 ); + // 1 or 2 bytes may be received + if (result >= 1) + { + if (pktStatusRead[0] == 0x02) + { + *pbLinkState = true; + } + else + { + *pbLinkState = false; + } + } + if (result == 2) + { + if (pktStatusRead[1] == 0x01) + { + *pbReconfigure = true; + } + else + { + *pbReconfigure = false; + } + } + + if (result < 0) + { + return result; + } + } + else + { + return -EFAULT; + } + + return 0; +} + +/*=========================================================================== +METHOD: + QMIDMSGetMEIDResp (Public Method) + +DESCRIPTION: + Parse the QMI DMS Get Serial Numbers Resp + +PARAMETERS + pBuffer [ I ] - Buffer to be parsed + buffSize [ I ] - Size of pBuffer + pMEID [ O ] - Device MEID + meidSize [ I ] - Size of MEID buffer (at least 14) + +RETURN VALUE: + int - 0 for success + Negative errno for error +===========================================================================*/ +int QMIDMSGetMEIDResp( + void * pBuffer, + u16 buffSize, + char * pMEID, + int meidSize ) +{ + int result; + + // Ignore QMUX and SDU + u8 offset = sizeof( sQMUX ) + 3; + + if (pBuffer == 0 || buffSize < offset || meidSize < 14) + { + return -ENOMEM; + } + + pBuffer = pBuffer + offset; + buffSize -= offset; + + result = GetQMIMessageID( pBuffer, buffSize ); + if (result != 0x25) + { + return -EFAULT; + } + + result = ValidQMIMessage( pBuffer, buffSize ); + if (result != 0) + { + return -EFAULT; + } + + result = GetTLV( pBuffer, buffSize, 0x12, (void*)pMEID, 14 ); + if (result != 14) + { + return -EFAULT; + } + + return 0; +} + diff --git a/drivers/net/usb/QMI.h b/drivers/net/usb/QMI.h new file mode 100644 index 0000000..e6e63c6 --- /dev/null +++ b/drivers/net/usb/QMI.h @@ -0,0 +1,289 @@ +/*=========================================================================== +FILE: + QMI.h + +DESCRIPTION: + Qualcomm QMI driver header + +FUNCTIONS: + Generic QMUX functions + ParseQMUX + FillQMUX + + Generic QMI functions + GetTLV + ValidQMIMessage + GetQMIMessageID + + Get sizes of buffers needed by QMI requests + QMUXHeaderSize + QMICTLGetClientIDReqSize + QMICTLReleaseClientIDReqSize + QMICTLReadyReqSize + QMIWDSSetEventReportReqSize + QMIWDSGetPKGSRVCStatusReqSize + QMIDMSGetMEIDReqSize + + Fill Buffers with QMI requests + QMICTLGetClientIDReq + QMICTLReleaseClientIDReq + QMICTLReadyReq + QMIWDSSetEventReportReq + QMIWDSGetPKGSRVCStatusReq + QMIDMSGetMEIDReq + + Parse data from QMI responses + QMICTLGetClientIDResp + QMICTLReleaseClientIDResp + QMIWDSEventResp + QMIDMSGetMEIDResp + +Copyright (c) 2011, Code Aurora Forum. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Code Aurora Forum nor + the names of its contributors may be used to endorse or promote + products derived from this software without specific prior written + permission. + +Alternatively, provided that this notice is retained in full, this software +may be relicensed by the recipient under the terms of the GNU General Public +License version 2 ("GPL") and only version 2, in which case the provisions of +the GPL apply INSTEAD OF those given above. If the recipient relicenses the +software under the GPL, then the identification text in the MODULE_LICENSE +macro must be changed to reflect "GPLv2" instead of "Dual BSD/GPL". Once a +recipient changes the license terms to the GPL, subsequent recipients shall +not relicense under alternate licensing terms, including the BSD or dual +BSD/GPL terms. In addition, the following license statement immediately +below and between the words START and END shall also then apply when this +software is relicensed under the GPL: + +START + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License version 2 and only version 2 as +published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +END + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +===========================================================================*/ + +#pragma once + +/*=========================================================================*/ +// Definitions +/*=========================================================================*/ + +// QMI Service Types +#define QMICTL 0 +#define QMIWDS 1 +#define QMIDMS 2 + +#define u8 unsigned char +#define u16 unsigned short +#define u32 unsigned int +#define u64 unsigned long long + +#define bool u8 +#define true 1 +#define false 0 + +#define ENOMEM 12 +#define EFAULT 14 +#define EINVAL 22 +#define ENOMSG 42 +#define ENODATA 61 + +/*=========================================================================*/ +// Struct sQMUX +// +// Structure that defines a QMUX header +/*=========================================================================*/ +typedef struct sQMUX +{ + /* T\F, always 1 */ + u8 mTF; + + /* Size of message */ + u16 mLength; + + /* Control flag */ + u8 mCtrlFlag; + + /* Service Type */ + u8 mQMIService; + + /* Client ID */ + u8 mQMIClientID; + +}__attribute__((__packed__)) sQMUX; + +/*=========================================================================*/ +// Generic QMUX functions +/*=========================================================================*/ + +// Remove QMUX headers from a buffer +int ParseQMUX( + u16 * pClientID, + void * pBuffer, + u16 buffSize ); + +// Fill buffer with QMUX headers +int FillQMUX( + u16 clientID, + void * pBuffer, + u16 buffSize ); + +/*=========================================================================*/ +// Generic QMI functions +/*=========================================================================*/ + +// Get data bufffer of a specified TLV from a QMI message +u16 GetTLV( + void * pQMIMessage, + u16 messageLen, + u8 type, + void * pOutDataBuf, + u16 bufferLen ); + +// Check mandatory TLV in a QMI message +int ValidQMIMessage( + void * pQMIMessage, + u16 messageLen ); + +// Get the message ID of a QMI message +int GetQMIMessageID( + void * pQMIMessage, + u16 messageLen ); + +/*=========================================================================*/ +// Get sizes of buffers needed by QMI requests +/*=========================================================================*/ + +// Get size of buffer needed for QMUX +u16 QMUXHeaderSize( void ); + +// Get size of buffer needed for QMUX + QMICTLGetClientIDReq +u16 QMICTLGetClientIDReqSize( void ); + +// Get size of buffer needed for QMUX + QMICTLReleaseClientIDReq +u16 QMICTLReleaseClientIDReqSize( void ); + +// Get size of buffer needed for QMUX + QMICTLReadyReq +u16 QMICTLReadyReqSize( void ); + +// Get size of buffer needed for QMUX + QMIWDSSetEventReportReq +u16 QMIWDSSetEventReportReqSize( void ); + +// Get size of buffer needed for QMUX + QMIWDSGetPKGSRVCStatusReq +u16 QMIWDSGetPKGSRVCStatusReqSize( void ); + +// Get size of buffer needed for QMUX + QMIDMSGetMEIDReq +u16 QMIDMSGetMEIDReqSize( void ); + +/*=========================================================================*/ +// Fill Buffers with QMI requests +/*=========================================================================*/ + +// Fill buffer with QMI CTL Get Client ID Request +int QMICTLGetClientIDReq( + void * pBuffer, + u16 buffSize, + u8 transactionID, + u8 serviceType ); + +// Fill buffer with QMI CTL Release Client ID Request +int QMICTLReleaseClientIDReq( + void * pBuffer, + u16 buffSize, + u8 transactionID, + u16 clientID ); + +// Fill buffer with QMI CTL Get Version Info Request +int QMICTLReadyReq( + void * pBuffer, + u16 buffSize, + u8 transactionID ); + +// Fill buffer with QMI WDS Set Event Report Request +int QMIWDSSetEventReportReq( + void * pBuffer, + u16 buffSize, + u16 transactionID ); + +// Fill buffer with QMI WDS Get PKG SRVC Status Request +int QMIWDSGetPKGSRVCStatusReq( + void * pBuffer, + u16 buffSize, + u16 transactionID ); + +// Fill buffer with QMI DMS Get Serial Numbers Request +int QMIDMSGetMEIDReq( + void * pBuffer, + u16 buffSize, + u16 transactionID ); + +/*=========================================================================*/ +// Parse data from QMI responses +/*=========================================================================*/ + +// Parse the QMI CTL Get Client ID Resp +int QMICTLGetClientIDResp( + void * pBuffer, + u16 buffSize, + u16 * pClientID ); + +// Verify the QMI CTL Release Client ID Resp is valid +int QMICTLReleaseClientIDResp( + void * pBuffer, + u16 buffSize ); + +// Parse the QMI WDS Set Event Report Resp/Indication or +// QMI WDS Get PKG SRVC Status Resp/Indication +int QMIWDSEventResp( + void * pBuffer, + u16 buffSize, + u32 * pTXOk, + u32 * pRXOk, + u32 * pTXErr, + u32 * pRXErr, + u32 * pTXOfl, + u32 * pRXOfl, + u64 * pTXBytesOk, + u64 * pRXBytesOk, + bool * pbLinkState, + bool * pbReconfigure ); + +// Parse the QMI DMS Get Serial Numbers Resp +int QMIDMSGetMEIDResp( + void * pBuffer, + u16 buffSize, + char * pMEID, + int meidSize ); + diff --git a/drivers/net/usb/QMIDevice.c b/drivers/net/usb/QMIDevice.c new file mode 100644 index 0000000..ff11456 --- /dev/null +++ b/drivers/net/usb/QMIDevice.c @@ -0,0 +1,3273 @@ +/*=========================================================================== +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 + + Userspace wrappers + UserspaceOpen + UserspaceIOCTL + UserspaceClose + UserspaceRead + UserspaceWrite + + Initializer and destructor + RegisterQMIDevice + DeregisterQMIDevice + + Driver level client management + QMIReady + QMIWDSCallback + SetupQMIWDSCallback + QMIDMSGetMEID + +Copyright (c) 2011, Code Aurora Forum. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Code Aurora Forum nor + the names of its contributors may be used to endorse or promote + products derived from this software without specific prior written + permission. + +Alternatively, provided that this notice is retained in full, this software +may be relicensed by the recipient under the terms of the GNU General Public +License version 2 ("GPL") and only version 2, in which case the provisions of +the GPL apply INSTEAD OF those given above. If the recipient relicenses the +software under the GPL, then the identification text in the MODULE_LICENSE +macro must be changed to reflect "GPLv2" instead of "Dual BSD/GPL". Once a +recipient changes the license terms to the GPL, subsequent recipients shall +not relicense under alternate licensing terms, including the BSD or dual +BSD/GPL terms. In addition, the following license statement immediately +below and between the words START and END shall also then apply when this +software is relicensed under the GPL: + +START + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License version 2 and only version 2 as +published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +END + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +===========================================================================*/ + +//--------------------------------------------------------------------------- +// Include Files +//--------------------------------------------------------------------------- +#include "QMIDevice.h" + +//----------------------------------------------------------------------------- +// Definitions +//----------------------------------------------------------------------------- + +extern int debug; +extern int interruptible; + +// Prototype to GobiSuspend function +int GobiSuspend( + struct usb_interface * pIntf, + pm_message_t powerEvent ); + +// 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 + +// CDC GET_ENCAPSULATED_RESPONSE packet +#define CDC_GET_ENCAPSULATED_RESPONSE 0x01A1ll + +// CDC CONNECTION_SPEED_CHANGE indication packet +#define CDC_CONNECTION_SPEED_CHANGE 0x08000000002AA1ll + +/*=========================================================================*/ +// UserspaceQMIFops +// QMI device's userspace file operations +/*=========================================================================*/ +struct file_operations UserspaceQMIFops = +{ + .owner = THIS_MODULE, + .read = UserspaceRead, + .write = UserspaceWrite, + .ioctl = UserspaceIOCTL, + .open = UserspaceOpen, + .flush = UserspaceClose, +}; + +/*=========================================================================*/ +// Generic functions +/*=========================================================================*/ + +/*=========================================================================== +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; + + 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 ); + + netif_carrier_off( pDev->mpNetDev->net ); +} + +/*=========================================================================== +METHOD: + GobiClearDownReason (Public Method) + +DESCRIPTION: + Clear mDownReason and may turn carrier on + +PARAMETERS + pDev [ I ] - Device specific memory + reason [ I ] - Reason device is no longer down + +RETURN VALUE: + None +===========================================================================*/ +void GobiClearDownReason( + sGobiUSBNet * pDev, + u8 reason ) +{ + clear_bit( reason, &pDev->mDownReason ); + + if (pDev->mDownReason == 0) + { + netif_carrier_on( pDev->mpNetDev->net ); + } +} + +/*=========================================================================== +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 + interval = (pIntURB->dev->speed == USB_SPEED_HIGH) ? 7 : 3; + + // Reschedule interrupt URB + usb_fill_int_urb( pIntURB, + pIntURB->dev, + pIntURB->pipe, + pIntURB->transfer_buffer, + pIntURB->transfer_buffer_length, + pIntURB->complete, + pIntURB->context, + interval ); + status = usb_submit_urb( pIntURB, GFP_ATOMIC ); + if (status != 0) + { + DBG( "Error re-submitting Int URB %d\n", status ); + } + + return status; +} + +/*=========================================================================== +METHOD: + ReadCallback (Public Method) + +DESCRIPTION: + Put the data in storage and notify anyone waiting for data + +PARAMETERS + pReadURB [ I ] - URB this callback is run for + +RETURN VALUE: + None +===========================================================================*/ +void ReadCallback( struct urb * pReadURB ) +{ + int result; + u16 clientID; + 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 = *(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 ); + 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 + DBG( "Creating new readListEntry for client 0x%04X, TID %x\n", + clientID, + transactionID ); + + // Notify this client data exists + NotifyAndPopNotifyList( pDev, + pClientMem->mClientID, + transactionID ); + + // 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 +===========================================================================*/ +void IntCallback( struct urb * pIntURB ) +{ + int status; + + 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( "Int status = %d\n", pIntURB->status ); + + // Ignore EOVERFLOW errors + if (pIntURB->status != -EOVERFLOW) + { + // Read 'thread' dies here + return; + } + } + else + { + // CDC GET_ENCAPSULATED_RESPONSE + if ((pIntURB->actual_length == 8) + && (*(u64*)pIntURB->transfer_buffer == CDC_GET_ENCAPSULATED_RESPONSE)) + { + // 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 ); + } + + // Int URB will be resubmitted during ReadCallback + return; + } + // CDC CONNECTION_SPEED_CHANGE + else if ((pIntURB->actual_length == 16) + && (*(u64*)pIntURB->transfer_buffer == CDC_CONNECTION_SPEED_CHANGE)) + { + // 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 ); + } + } + + 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; + + 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" ); + 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" ); + return -ENOMEM; + } + + pDev->mQMIDev.mpIntBuffer = kmalloc( DEFAULT_READ_URB_LENGTH, GFP_KERNEL ); + if (pDev->mQMIDev.mpIntBuffer == NULL) + { + DBG( "Error allocating int buffer\n" ); + return -ENOMEM; + } + + pDev->mQMIDev.mpReadSetupPacket = kmalloc( sizeof( sURBSetupPacket ), + GFP_KERNEL ); + if (pDev->mQMIDev.mpReadSetupPacket == NULL) + { + DBG( "Error allocating setup packet buffer\n" ); + 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 = 0; + pDev->mQMIDev.mpReadSetupPacket->mLength = DEFAULT_READ_URB_LENGTH; + + interval = (pDev->mpNetDev->udev->speed == USB_SPEED_HIGH) ? 7 : 3; + + // Schedule interrupt URB + usb_fill_int_urb( pDev->mQMIDev.mpIntURB, + pDev->mpNetDev->udev, + usb_rcvintpipe( pDev->mpNetDev->udev, 0x81 ), + pDev->mQMIDev.mpIntBuffer, + DEFAULT_READ_URB_LENGTH, + 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 ) +{ + DBG( "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 +===========================================================================*/ +void WriteSyncCallback( struct urb * pWriteURB ) +{ + 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 = 0; + writeSetup.mLength = 0; + writeSetup.mLength = 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) + { +#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,33 )) + pDev->mpNetDev->udev->auto_pm = 0; +#endif + GobiSuspend( pDev->mpIntf, PMSG_SUSPEND ); + } + + 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; + } + + result = usb_submit_urb( pWriteURB, GFP_KERNEL ); + 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 = atomic_add_return( 1, &pDev->mQMIDev.mQMICTLTransactionID ); + if (transactionID == 0) + { + atomic_add_return( 1, &pDev->mQMIDev.mQMICTLTransactionID ); + } + result = QMICTLGetClientIDReq( pWriteBuffer, + writeBufferSize, + transactionID, + serviceType ); + if (result < 0) + { + kfree( pWriteBuffer ); + return result; + } + + result = WriteSync( pDev, + pWriteBuffer, + writeBufferSize, + QMICTL ); + kfree( pWriteBuffer ); + + if (result < 0) + { + return result; + } + + result = ReadSync( pDev, + &pReadBuffer, + QMICTL, + transactionID ); + if (result < 0) + { + DBG( "bad read data %d\n", result ); + return result; + } + readBufferSize = result; + + result = QMICTLGetClientIDResp( pReadBuffer, + readBufferSize, + &clientID ); + 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; + + + // End Critical section + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + + return clientID; +} + +/*=========================================================================== +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 + kfree( *ppDelClientMem ); + + // 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 + //DBG( "Found client mem %p\n", pClientMem ); + 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; + break; + } + + DBG( "skipping 0x%04X data TID = %x\n", + clientID, + (*ppReadMemList)->mTransactionID ); + + // Next + ppReadMemList = &(*ppReadMemList)->mpNext; + } + + if (pDelReadMemList != NULL) + { + *ppReadMemList = (*ppReadMemList)->mpNext; + + // Copy to output + *ppData = pDelReadMemList->mpData; + *pDataSize = pDelReadMemList->mDataSize; + + // 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; + } +} + +/*=========================================================================*/ +// 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 (IsDeviceValid( pDev ) == false) + { + DBG( "Invalid device\n" ); + return -ENXIO; + } + + // Setup data in pFilp->private_data + pFilp->private_data = kmalloc( sizeof( sQMIFilpStorage ), GFP_KERNEL ); + if (pFilp->private_data == NULL) + { + DBG( "Mem error\n" ); + return -ENOMEM; + } + + pFilpData = (sQMIFilpStorage *)pFilp->private_data; + pFilpData->mClientID = (u16)-1; + pFilpData->mpDev = pDev; + + 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 ) +{ + 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 ); + if (result < 0) + { + return result; + } + pFilpData->mClientID = result; + + 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; + } +} + +/*=========================================================================== +METHOD: + UserspaceClose (Public Method) + +DESCRIPTION: + Userspace close + Release client ID and free memory + +PARAMETERS + pFilp [ I ] - userspace file descriptor + unusedFileTable [ I ] - (unused) file table + +RETURN VALUE: + int - 0 for success + Negative errno for failure +===========================================================================*/ +int UserspaceClose( + struct file * pFilp, + fl_owner_t unusedFileTable ) +{ + sQMIFilpStorage * pFilpData = (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 (atomic_read( &pFilp->f_count ) != 1) + { + rcu_read_lock(); + for_each_process( pEachTask ) + { + if (pEachTask == NULL || pEachTask->files == NULL) + { + // Some tasks may not have files (e.g. Xsession) + continue; + } + spin_lock_irqsave( &pEachTask->files->file_lock, flags ); + 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) + { + ReleaseClientID( pFilpData->mpDev, + pFilpData->mClientID ); + } + + 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; + } + + 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; + } +} + +/*=========================================================================*/ +// Initializer and destructor +/*=========================================================================*/ + +/*=========================================================================== +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; + + // 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; + } + + // 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; + } + + // 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#) + pDevName = strstr( pDev->mpNetDev->net->name, "usb" ); + if (pDevName == NULL) + { + DBG( "Bad net name: %s\n", pDev->mpNetDev->net->name ); + return -ENXIO; + } + pDevName += strlen( "usb" ); + 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; + + // Should never happen, but check anyway + if (IsDeviceValid( pDev ) == false) + { + DBG( "wrong device\n" ); + return; + } + + // Release all clients + while (pDev->mQMIDev.mpClientMemList != NULL) + { + DBG( "release 0x%04X\n", pDev->mQMIDev.mpClientMemList->mClientID ); + + ReleaseClientID( pDev, + pDev->mQMIDev.mpClientMemList->mClientID ); + // NOTE: pDev->mQMIDev.mpClientMemList will + // be updated in ReleaseClientID() + } + + // 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 ) + { + if (pEachTask == NULL || pEachTask->files == NULL) + { + // Some tasks may not have files (e.g. Xsession) + continue; + } + // For each file this task has open, check if it's referencing + // our inode. + spin_lock_irqsave( &pEachTask->files->file_lock, flags ); + 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(); + } + } + + // 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 + 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 100 ms and see if we get a response + for (curTime = 0; curTime < timeout; curTime += 100) + { + // 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) + { + 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( 100 ); + 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) + { + DBG( "Net device link is connected\n" ); + GobiClearDownReason( pDev, NO_NDIS_CONNECTION ); + } + else + { + DBG( "Net device link is disconnected\n" ); + GobiSetDownReason( pDev, NO_NDIS_CONNECTION ); + } + } + } + + 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 ), + 0x22, + 0x21, + 1, // DTR present + 0, + 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; +} diff --git a/drivers/net/usb/QMIDevice.h b/drivers/net/usb/QMIDevice.h new file mode 100644 index 0000000..176f080 --- /dev/null +++ b/drivers/net/usb/QMIDevice.h @@ -0,0 +1,339 @@ +/*=========================================================================== +FILE: + QMIDevice.h + +DESCRIPTION: + Functions related to the QMI interface device + +FUNCTIONS: + Generic functions + IsDeviceValid + PrintHex + GobiSetDownReason + GobiClearDownReason + GobiTestDownReason + + Driver level asynchronous read functions + ResubmitIntURB + ReadCallback + IntCallback + StartRead + KillRead + + Internal read/write functions + ReadAsync + UpSem + ReadSync + WriteSyncCallback + WriteSync + + Internal memory management functions + GetClientID + ReleaseClientID + FindClientMem + AddToReadMemList + PopFromReadMemList + AddToNotifyList + NotifyAndPopNotifyList + AddToURBList + PopFromURBList + + Userspace wrappers + UserspaceOpen + UserspaceIOCTL + UserspaceClose + UserspaceRead + UserspaceWrite + + Initializer and destructor + RegisterQMIDevice + DeregisterQMIDevice + + Driver level client management + QMIReady + QMIWDSCallback + SetupQMIWDSCallback + QMIDMSGetMEID + +Copyright (c) 2011, Code Aurora Forum. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Code Aurora Forum nor + the names of its contributors may be used to endorse or promote + products derived from this software without specific prior written + permission. + +Alternatively, provided that this notice is retained in full, this software +may be relicensed by the recipient under the terms of the GNU General Public +License version 2 ("GPL") and only version 2, in which case the provisions of +the GPL apply INSTEAD OF those given above. If the recipient relicenses the +software under the GPL, then the identification text in the MODULE_LICENSE +macro must be changed to reflect "GPLv2" instead of "Dual BSD/GPL". Once a +recipient changes the license terms to the GPL, subsequent recipients shall +not relicense under alternate licensing terms, including the BSD or dual +BSD/GPL terms. In addition, the following license statement immediately +below and between the words START and END shall also then apply when this +software is relicensed under the GPL: + +START + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License version 2 and only version 2 as +published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +END + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +===========================================================================*/ + +//--------------------------------------------------------------------------- +// Pragmas +//--------------------------------------------------------------------------- +#pragma once + +//--------------------------------------------------------------------------- +// Include Files +//--------------------------------------------------------------------------- +#include "Structs.h" +#include "QMI.h" + +/*=========================================================================*/ +// Generic functions +/*=========================================================================*/ + +// Basic test to see if device memory is valid +bool IsDeviceValid( sGobiUSBNet * pDev ); + +// Print Hex data, for debug purposes +void PrintHex( + void * pBuffer, + u16 bufSize ); + +// Sets mDownReason and turns carrier off +void GobiSetDownReason( + sGobiUSBNet * pDev, + u8 reason ); + +// Clear mDownReason and may turn carrier on +void GobiClearDownReason( + sGobiUSBNet * pDev, + u8 reason ); + +// Tests mDownReason and returns whether reason is set +bool GobiTestDownReason( + sGobiUSBNet * pDev, + u8 reason ); + +/*=========================================================================*/ +// Driver level asynchronous read functions +/*=========================================================================*/ + +// Resubmit interrupt URB, re-using same values +int ResubmitIntURB( struct urb * pIntURB ); + +// Read callback +// Put the data in storage and notify anyone waiting for data +void ReadCallback( struct urb * pReadURB ); + +// Inturrupt callback +// Data is available, start a read URB +void IntCallback( struct urb * pIntURB ); + +// Start continuous read "thread" +int StartRead( sGobiUSBNet * pDev ); + +// Kill continuous read "thread" +void KillRead( sGobiUSBNet * pDev ); + +/*=========================================================================*/ +// Internal read/write functions +/*=========================================================================*/ + +// Start asynchronous read +// Reading client's data store, not device +int ReadAsync( + sGobiUSBNet * pDev, + u16 clientID, + u16 transactionID, + void (*pCallback)(sGobiUSBNet *, u16, void *), + void * pData ); + +// Notification function for synchronous read +void UpSem( + sGobiUSBNet * pDev, + u16 clientID, + void * pData ); + +// Start synchronous read +// Reading client's data store, not device +int ReadSync( + sGobiUSBNet * pDev, + void ** ppOutBuffer, + u16 clientID, + u16 transactionID ); + +// Write callback +void WriteSyncCallback( struct urb * pWriteURB ); + +// Start synchronous write +int WriteSync( + sGobiUSBNet * pDev, + char * pInWriteBuffer, + int size, + u16 clientID ); + +/*=========================================================================*/ +// Internal memory management functions +/*=========================================================================*/ + +// Create client and allocate memory +int GetClientID( + sGobiUSBNet * pDev, + u8 serviceType ); + +// Release client and free memory +void ReleaseClientID( + sGobiUSBNet * pDev, + u16 clientID ); + +// Find this client's memory +sClientMemList * FindClientMem( + sGobiUSBNet * pDev, + u16 clientID ); + +// Add Data to this client's ReadMem list +bool AddToReadMemList( + sGobiUSBNet * pDev, + u16 clientID, + u16 transactionID, + void * pData, + u16 dataSize ); + +// Remove data from this client's ReadMem list if it matches +// the specified transaction ID. +bool PopFromReadMemList( + sGobiUSBNet * pDev, + u16 clientID, + u16 transactionID, + void ** ppData, + u16 * pDataSize ); + +// Add Notify entry to this client's notify List +bool AddToNotifyList( + sGobiUSBNet * pDev, + u16 clientID, + u16 transactionID, + void (* pNotifyFunct)(sGobiUSBNet *, u16, void *), + void * pData ); + +// Remove first Notify entry from this client's notify list +// and Run function +bool NotifyAndPopNotifyList( + sGobiUSBNet * pDev, + u16 clientID, + u16 transactionID ); + +// Add URB to this client's URB list +bool AddToURBList( + sGobiUSBNet * pDev, + u16 clientID, + struct urb * pURB ); + +// Remove URB from this client's URB list +struct urb * PopFromURBList( + sGobiUSBNet * pDev, + u16 clientID ); + +/*=========================================================================*/ +// Userspace wrappers +/*=========================================================================*/ + +// Userspace open +int UserspaceOpen( + struct inode * pInode, + struct file * pFilp ); + +// Userspace ioctl +int UserspaceIOCTL( + struct inode * pUnusedInode, + struct file * pFilp, + unsigned int cmd, + unsigned long arg ); + +// Userspace close +int UserspaceClose( + struct file * pFilp, + fl_owner_t unusedFileTable ); + +// Userspace read (synchronous) +ssize_t UserspaceRead( + struct file * pFilp, + char __user * pBuf, + size_t size, + loff_t * pUnusedFpos ); + +// Userspace write (synchronous) +ssize_t UserspaceWrite( + struct file * pFilp, + const char __user * pBuf, + size_t size, + loff_t * pUnusedFpos ); + +/*=========================================================================*/ +// Initializer and destructor +/*=========================================================================*/ + +// QMI Device initialization function +int RegisterQMIDevice( sGobiUSBNet * pDev ); + +// QMI Device cleanup function +void DeregisterQMIDevice( sGobiUSBNet * pDev ); + +/*=========================================================================*/ +// Driver level client management +/*=========================================================================*/ + +// Check if QMI is ready for use +bool QMIReady( + sGobiUSBNet * pDev, + u16 timeout ); + +// QMI WDS callback function +void QMIWDSCallback( + sGobiUSBNet * pDev, + u16 clientID, + void * pData ); + +// Fire off reqests and start async read for QMI WDS callback +int SetupQMIWDSCallback( sGobiUSBNet * pDev ); + +// Register client, send req and parse MEID response, release client +int QMIDMSGetMEID( sGobiUSBNet * pDev ); + + + diff --git a/drivers/net/usb/Structs.h b/drivers/net/usb/Structs.h new file mode 100644 index 0000000..0d8dd4e --- /dev/null +++ b/drivers/net/usb/Structs.h @@ -0,0 +1,359 @@ +/*=========================================================================== +FILE: + Structs.h + +DESCRIPTION: + Declaration of structures used by the Qualcomm Linux USB Network driver + +FUNCTIONS: + none + +Copyright (c) 2011, Code Aurora Forum. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Code Aurora Forum nor + the names of its contributors may be used to endorse or promote + products derived from this software without specific prior written + permission. + +Alternatively, provided that this notice is retained in full, this software +may be relicensed by the recipient under the terms of the GNU General Public +License version 2 ("GPL") and only version 2, in which case the provisions of +the GPL apply INSTEAD OF those given above. If the recipient relicenses the +software under the GPL, then the identification text in the MODULE_LICENSE +macro must be changed to reflect "GPLv2" instead of "Dual BSD/GPL". Once a +recipient changes the license terms to the GPL, subsequent recipients shall +not relicense under alternate licensing terms, including the BSD or dual +BSD/GPL terms. In addition, the following license statement immediately +below and between the words START and END shall also then apply when this +software is relicensed under the GPL: + +START + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License version 2 and only version 2 as +published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +END + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +===========================================================================*/ + +//--------------------------------------------------------------------------- +// Pragmas +//--------------------------------------------------------------------------- +#pragma once + +//--------------------------------------------------------------------------- +// Include Files +//--------------------------------------------------------------------------- +#include <linux/etherdevice.h> +#include <linux/ethtool.h> +#include <linux/mii.h> +#include <linux/usb.h> +#include <linux/version.h> +#include <linux/cdev.h> +#include <linux/kthread.h> + +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,24 )) + #include "usbnet.h" +#else + #include <linux/usb/usbnet.h> +#endif + +#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,25 )) + #include <linux/fdtable.h> +#else + #include <linux/file.h> +#endif + +// DBG macro +#define DBG( format, arg... ) \ + if (debug == 1)\ + { \ + printk( KERN_INFO "GobiNet::%s " format, __FUNCTION__, ## arg ); \ + } \ + + +// Used in recursion, defined later below +struct sGobiUSBNet; + +/*=========================================================================*/ +// Struct sReadMemList +// +// Structure that defines an entry in a Read Memory linked list +/*=========================================================================*/ +typedef struct sReadMemList +{ + /* Data buffer */ + void * mpData; + + /* Transaction ID */ + u16 mTransactionID; + + /* Size of data buffer */ + u16 mDataSize; + + /* Next entry in linked list */ + struct sReadMemList * mpNext; + +} sReadMemList; + +/*=========================================================================*/ +// Struct sNotifyList +// +// Structure that defines an entry in a Notification linked list +/*=========================================================================*/ +typedef struct sNotifyList +{ + /* Function to be run when data becomes available */ + void (* mpNotifyFunct)(struct sGobiUSBNet *, u16, void *); + + /* Transaction ID */ + u16 mTransactionID; + + /* Data to provide as parameter to mpNotifyFunct */ + void * mpData; + + /* Next entry in linked list */ + struct sNotifyList * mpNext; + +} sNotifyList; + +/*=========================================================================*/ +// Struct sURBList +// +// Structure that defines an entry in a URB linked list +/*=========================================================================*/ +typedef struct sURBList +{ + /* The current URB */ + struct urb * mpURB; + + /* Next entry in linked list */ + struct sURBList * mpNext; + +} sURBList; + +/*=========================================================================*/ +// Struct sClientMemList +// +// Structure that defines an entry in a Client Memory linked list +// Stores data specific to a Service Type and Client ID +/*=========================================================================*/ +typedef struct sClientMemList +{ + /* Client ID for this Client */ + u16 mClientID; + + /* Linked list of Read entries */ + /* Stores data read from device before sending to client */ + sReadMemList * mpList; + + /* Linked list of Notification entries */ + /* Stores notification functions to be run as data becomes + available or the device is removed */ + sNotifyList * mpReadNotifyList; + + /* Linked list of URB entries */ + /* Stores pointers to outstanding URBs which need canceled + when the client is deregistered or the device is removed */ + sURBList * mpURBList; + + /* Next entry in linked list */ + struct sClientMemList * mpNext; + +} sClientMemList; + +/*=========================================================================*/ +// Struct sURBSetupPacket +// +// Structure that defines a USB Setup packet for Control URBs +// Taken from USB CDC specifications +/*=========================================================================*/ +typedef struct sURBSetupPacket +{ + /* Request type */ + u8 mRequestType; + + /* Request code */ + u8 mRequestCode; + + /* Value */ + u16 mValue; + + /* Index */ + u16 mIndex; + + /* Length of Control URB */ + u16 mLength; + +} sURBSetupPacket; + +// Common value for sURBSetupPacket.mLength +#define DEFAULT_READ_URB_LENGTH 0x1000 + + +/*=========================================================================*/ +// Struct sAutoPM +// +// Structure used to manage AutoPM thread which determines whether the +// device is in use or may enter autosuspend. Also submits net +// transmissions asynchronously. +/*=========================================================================*/ +typedef struct sAutoPM +{ + /* Thread for atomic autopm function */ + struct task_struct * mpThread; + + /* Signal for completion when it's time for the thread to work */ + struct completion mThreadDoWork; + + /* Time to exit? */ + bool mbExit; + + /* List of URB's queued to be sent to the device */ + sURBList * mpURBList; + + /* URB list lock (for adding and removing elements) */ + spinlock_t mURBListLock; + + /* Active URB */ + struct urb * mpActiveURB; + + /* Active URB lock (for adding and removing elements) */ + spinlock_t mActiveURBLock; + + /* Duplicate pointer to USB device interface */ + struct usb_interface * mpIntf; + +} sAutoPM; + + +/*=========================================================================*/ +// Struct sQMIDev +// +// Structure that defines the data for the QMI device +/*=========================================================================*/ +typedef struct sQMIDev +{ + /* Device number */ + dev_t mDevNum; + + /* Device class */ + struct class * mpDevClass; + + /* cdev struct */ + struct cdev mCdev; + + /* is mCdev initialized? */ + bool mbCdevIsInitialized; + + /* Pointer to read URB */ + struct urb * mpReadURB; + + /* Read setup packet */ + sURBSetupPacket * mpReadSetupPacket; + + /* Read buffer attached to current read URB */ + void * mpReadBuffer; + + /* Inturrupt URB */ + /* Used to asynchronously notify when read data is available */ + struct urb * mpIntURB; + + /* Buffer used by Inturrupt URB */ + void * mpIntBuffer; + + /* Pointer to memory linked list for all clients */ + sClientMemList * mpClientMemList; + + /* Spinlock for client Memory entries */ + spinlock_t mClientMemLock; + + /* Transaction ID associated with QMICTL "client" */ + atomic_t mQMICTLTransactionID; + +} sQMIDev; + +/*=========================================================================*/ +// Struct sGobiUSBNet +// +// Structure that defines the data associated with the Qualcomm USB device +/*=========================================================================*/ +typedef struct sGobiUSBNet +{ + /* Net device structure */ + struct usbnet * mpNetDev; + + /* Usb device interface */ + struct usb_interface * mpIntf; + + /* Pointers to usbnet_open and usbnet_stop functions */ + int (* mpUSBNetOpen)(struct net_device *); + int (* mpUSBNetStop)(struct net_device *); + + /* Reason(s) why interface is down */ + /* Used by Gobi*DownReason */ + unsigned long mDownReason; +#define NO_NDIS_CONNECTION 0 +#define CDC_CONNECTION_SPEED 1 +#define DRIVER_SUSPENDED 2 +#define NET_IFACE_STOPPED 3 + + /* QMI "device" status */ + bool mbQMIValid; + + /* QMI "device" memory */ + sQMIDev mQMIDev; + + /* Device MEID */ + char mMEID[14]; + + /* AutoPM thread */ + sAutoPM mAutoPM; + +} sGobiUSBNet; + +/*=========================================================================*/ +// Struct sQMIFilpStorage +// +// Structure that defines the storage each file handle contains +// Relates the file handle to a client +/*=========================================================================*/ +typedef struct sQMIFilpStorage +{ + /* Client ID */ + u16 mClientID; + + /* Device pointer */ + sGobiUSBNet * mpDev; + +} sQMIFilpStorage; + + diff --git a/drivers/usb/serial/GobiSerial.c b/drivers/usb/serial/GobiSerial.c new file mode 100644 index 0000000..ee92269 --- /dev/null +++ b/drivers/usb/serial/GobiSerial.c @@ -0,0 +1,754 @@ +/*=========================================================================== +FILE: + GobiSerial.c + +DESCRIPTION: + Linux Qualcomm Serial USB driver Implementation + +PUBLIC DRIVER FUNCTIONS: + GobiProbe + GobiOpen + GobiClose + GobiReadBulkCallback (if kernel is less than 2.6.25) + GobiSuspend + GobiResume (if kernel is less than 2.6.24) + +Copyright (c) 2011, Code Aurora Forum. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Code Aurora Forum nor + the names of its contributors may be used to endorse or promote + products derived from this software without specific prior written + permission. + +Alternatively, provided that this notice is retained in full, this software +may be relicensed by the recipient under the terms of the GNU General Public +License version 2 ("GPL") and only version 2, in which case the provisions of +the GPL apply INSTEAD OF those given above. If the recipient relicenses the +software under the GPL, then the identification text in the MODULE_LICENSE +macro must be changed to reflect "GPLv2" instead of "Dual BSD/GPL". Once a +recipient changes the license terms to the GPL, subsequent recipients shall +not relicense under alternate licensing terms, including the BSD or dual +BSD/GPL terms. In addition, the following license statement immediately +below and between the words START and END shall also then apply when this +software is relicensed under the GPL: + +START + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License version 2 and only version 2 as +published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +END + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +==========================================================================*/ +//--------------------------------------------------------------------------- +// Include Files +//--------------------------------------------------------------------------- + +#include <linux/tty.h> +#include <linux/tty_flip.h> +#include <linux/usb.h> +#include <linux/usb/serial.h> +#include <linux/version.h> + +//--------------------------------------------------------------------------- +// Global veriable and defination +//--------------------------------------------------------------------------- + +// Version Information +#define DRIVER_VERSION "1.0.20" +#define DRIVER_AUTHOR "Qualcomm Innovation Center" +#define DRIVER_DESC "GobiSerial" + +#define NUM_BULK_EPS 1 +#define MAX_BULK_EPS 6 + +// Debug flag +static int debug; + +// Global pointer to usb_serial_generic_close function +// This function is not exported, which is why we have to use a pointer +// instead of just calling it. +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,26 )) + void (* gpClose)( + struct usb_serial_port *, + struct file * ); +#elif (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,29 )) + void (* gpClose)( + struct tty_struct *, + struct usb_serial_port *, + struct file * ); +#else // > 2.6.29 + void (* gpClose)( struct usb_serial_port * ); +#endif + +// DBG macro +#define DBG( format, arg... ) \ + if (debug == 1)\ + { \ + printk( KERN_INFO "GobiSerial::%s " format, __FUNCTION__, ## arg ); \ + } \ + +/*=========================================================================*/ +// Function Prototypes +/*=========================================================================*/ + +// Attach to correct interfaces +static int GobiProbe( + struct usb_serial * pSerial, + const struct usb_device_id * pID ); + +// Start GPS if GPS port, run usb_serial_generic_open +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,26 )) + int GobiOpen( + struct usb_serial_port * pPort, + struct file * pFilp ); +#elif (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,31 )) + int GobiOpen( + struct tty_struct * pTTY, + struct usb_serial_port * pPort, + struct file * pFilp ); +#else // > 2.6.31 + int GobiOpen( + struct tty_struct * pTTY, + struct usb_serial_port * pPort ); +#endif + +// Stop GPS if GPS port, run usb_serial_generic_close +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,26 )) + void GobiClose( + struct usb_serial_port *, + struct file * ); +#elif (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,29 )) + void GobiClose( + struct tty_struct *, + struct usb_serial_port *, + struct file * ); +#else // > 2.6.29 + void GobiClose( struct usb_serial_port * ); +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,25 )) + +// Read data from USB, push to TTY and user space +static void GobiReadBulkCallback( struct urb * pURB ); + +#endif + +// Set reset_resume flag +int GobiSuspend( + struct usb_interface * pIntf, + pm_message_t powerEvent ); + +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,23 )) + +// Restart URBs killed during usb_serial_suspend +int GobiResume( struct usb_interface * pIntf ); + +#endif + +/*=========================================================================*/ +// Qualcomm Gobi 3000 VID/PIDs +/*=========================================================================*/ +static struct usb_device_id GobiVIDPIDTable[] = +{ + { USB_DEVICE( 0x05c6, 0x920c ) }, // Gobi 3000 QDL device + { USB_DEVICE( 0x05c6, 0x920d ) }, // Gobi 3000 Composite Device + { } // Terminating entry +}; +MODULE_DEVICE_TABLE( usb, GobiVIDPIDTable ); + +/*=========================================================================*/ +// Struct usb_serial_driver +// Driver structure we register with the USB core +/*=========================================================================*/ +static struct usb_driver GobiDriver = +{ + .name = "GobiSerial", + .probe = usb_serial_probe, + .disconnect = usb_serial_disconnect, + .id_table = GobiVIDPIDTable, + .suspend = GobiSuspend, +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,23 )) + .resume = GobiResume, +#else + .resume = usb_serial_resume, +#endif + .supports_autosuspend = true, +}; + +/*=========================================================================*/ +// Struct usb_serial_driver +/*=========================================================================*/ +static struct usb_serial_driver gGobiDevice = +{ + .driver = + { + .owner = THIS_MODULE, + .name = "GobiSerial driver", + }, + .description = "GobiSerial", + .id_table = GobiVIDPIDTable, + .usb_driver = &GobiDriver, + .num_ports = 1, + .probe = GobiProbe, + .open = GobiOpen, +#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,25 )) + .num_interrupt_in = NUM_DONT_CARE, + .num_bulk_in = 1, + .num_bulk_out = 1, + .read_bulk_callback = GobiReadBulkCallback, +#endif +}; + +//--------------------------------------------------------------------------- +// USB serial core overridding Methods +//--------------------------------------------------------------------------- + +/*=========================================================================== +METHOD: + GobiProbe (Free Method) + +DESCRIPTION: + Attach to correct interfaces + +PARAMETERS: + pSerial [ I ] - Serial structure + pID [ I ] - VID PID table + +RETURN VALUE: + int - negative error code on failure + zero on success +===========================================================================*/ +static int GobiProbe( + struct usb_serial * pSerial, + const struct usb_device_id * pID ) +{ + // Assume failure + int nRetval = -ENODEV; + + int nNumInterfaces; + int nInterfaceNum; + DBG( "\n" ); + + // Test parameters + if ( (pSerial == NULL) + || (pSerial->dev == NULL) + || (pSerial->dev->actconfig == NULL) + || (pSerial->interface == NULL) + || (pSerial->interface->cur_altsetting == NULL) + || (pSerial->type == NULL) ) + { + DBG( "invalid parameter\n" ); + return -EINVAL; + } + + nNumInterfaces = pSerial->dev->actconfig->desc.bNumInterfaces; + DBG( "Num Interfaces = %d\n", nNumInterfaces ); + nInterfaceNum = pSerial->interface->cur_altsetting->desc.bInterfaceNumber; + DBG( "This Interface = %d\n", nInterfaceNum ); + + if (nNumInterfaces == 1) + { + // QDL mode? + if (nInterfaceNum == 1 || nInterfaceNum == 0) + { + DBG( "QDL port found\n" ); + nRetval = usb_set_interface( pSerial->dev, + nInterfaceNum, + 0 ); + if (nRetval < 0) + { + DBG( "Could not set interface, error %d\n", nRetval ); + } + } + else + { + DBG( "Incorrect QDL interface number\n" ); + } + } + else + { + // Composite mode + if (nInterfaceNum == 2) + { + DBG( "Modem port found\n" ); + nRetval = usb_set_interface( pSerial->dev, + nInterfaceNum, + 0 ); + if (nRetval < 0) + { + DBG( "Could not set interface, error %d\n", nRetval ); + } + } + else if (nInterfaceNum == 3) + { + DBG( "GPS port found\n" ); + nRetval = usb_set_interface( pSerial->dev, + nInterfaceNum, + 0 ); + if (nRetval < 0) + { + DBG( "Could not set interface, error %d\n", nRetval ); + } + + // Check for recursion + if (pSerial->type->close != GobiClose) + { + // Store usb_serial_generic_close in gpClose + gpClose = pSerial->type->close; + pSerial->type->close = GobiClose; + } + } + else + { + // Not a port we want to support at this time + DBG( "Unsupported interface number\n" ); + } + } + + return nRetval; +} + +/*=========================================================================== +METHOD: + GobiOpen (Free Method) + +DESCRIPTION: + Start GPS if GPS port, run usb_serial_generic_open + +PARAMETERS: + pTTY [ I ] - TTY structure (only on kernels <= 2.6.26) + pPort [ I ] - USB serial port structure + pFilp [ I ] - File structure (only on kernels <= 2.6.31) + +RETURN VALUE: + int - zero for success + - negative errno on error +===========================================================================*/ +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,26 )) +int GobiOpen( + struct usb_serial_port * pPort, + struct file * pFilp ) +#elif (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,31 )) +int GobiOpen( + struct tty_struct * pTTY, + struct usb_serial_port * pPort, + struct file * pFilp ) +#else // > 2.6.31 +int GobiOpen( + struct tty_struct * pTTY, + struct usb_serial_port * pPort ) +#endif +{ + const char startMessage[] = "$GPS_START"; + int nResult; + int bytesWrote; + + DBG( "\n" ); + + // Test parameters + if ( (pPort == NULL) + || (pPort->serial == NULL) + || (pPort->serial->dev == NULL) + || (pPort->serial->interface == NULL) + || (pPort->serial->interface->cur_altsetting == NULL) ) + { + DBG( "invalid parameter\n" ); + return -EINVAL; + } + + // Is this the GPS port? + if (pPort->serial->interface->cur_altsetting->desc.bInterfaceNumber == 3) + { + // Send startMessage, 1s timeout + nResult = usb_bulk_msg( pPort->serial->dev, + usb_sndbulkpipe( pPort->serial->dev, + pPort->bulk_out_endpointAddress ), + (void *)&startMessage[0], + sizeof( startMessage ), + &bytesWrote, + 1000 ); + if (nResult != 0) + { + DBG( "error %d sending startMessage\n", nResult ); + return nResult; + } + if (bytesWrote != sizeof( startMessage )) + { + DBG( "invalid write size %d, %d\n", + bytesWrote, + sizeof( startMessage ) ); + return -EIO; + } + } + + // Pass to usb_serial_generic_open +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,26 )) + return usb_serial_generic_open( pPort, pFilp ); +#elif (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,31 )) + return usb_serial_generic_open( pTTY, pPort, pFilp ); +#else // > 2.6.31 + return usb_serial_generic_open( pTTY, pPort ); +#endif +} + +/*=========================================================================== +METHOD: + GobiClose (Free Method) + +DESCRIPTION: + Stop GPS if GPS port, run usb_serial_generic_close + +PARAMETERS: + pTTY [ I ] - TTY structure (only if kernel > 2.6.26 and <= 2.6.29) + pPort [ I ] - USB serial port structure + pFilp [ I ] - File structure (only on kernel <= 2.6.29) +===========================================================================*/ +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,26 )) +void GobiClose( + struct usb_serial_port * pPort, + struct file * pFilp ) +#elif (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,29 )) +void GobiClose( + struct tty_struct * pTTY, + struct usb_serial_port * pPort, + struct file * pFilp ) +#else // > 2.6.29 +void GobiClose( struct usb_serial_port * pPort ) +#endif +{ + const char stopMessage[] = "$GPS_STOP"; + int nResult; + int bytesWrote; + + DBG( "\n" ); + + // Test parameters + if ( (pPort == NULL) + || (pPort->serial == NULL) + || (pPort->serial->dev == NULL) + || (pPort->serial->interface == NULL) + || (pPort->serial->interface->cur_altsetting == NULL) ) + { + DBG( "invalid parameter\n" ); + return; + } + + // Is this the GPS port? + if (pPort->serial->interface->cur_altsetting->desc.bInterfaceNumber == 3) + { + // Send stopMessage, 1s timeout + nResult = usb_bulk_msg( pPort->serial->dev, + usb_sndbulkpipe( pPort->serial->dev, + pPort->bulk_out_endpointAddress ), + (void *)&stopMessage[0], + sizeof( stopMessage ), + &bytesWrote, + 1000 ); + if (nResult != 0) + { + DBG( "error %d sending stopMessage\n", nResult ); + } + if (bytesWrote != sizeof( stopMessage )) + { + DBG( "invalid write size %d, %d\n", + bytesWrote, + sizeof( stopMessage ) ); + } + } + + // Pass to usb_serial_generic_close + if (gpClose == NULL) + { + DBG( "NULL gpClose\n" ); + return; + } + +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,26 )) + gpClose( pPort, pFilp ); +#elif (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,29 )) + gpClose( pTTY, pPort, pFilp ); +#else // > 2.6.29 + gpClose( pPort ); +#endif +} + +#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,25 )) + +/*=========================================================================== +METHOD: + GobiReadBulkCallback (Free Method) + +DESCRIPTION: + Read data from USB, push to TTY and user space + +PARAMETERS: + pURB [ I ] - USB Request Block (urb) that called us + +RETURN VALUE: +===========================================================================*/ +static void GobiReadBulkCallback( struct urb * pURB ) +{ + struct usb_serial_port * pPort = pURB->context; + struct tty_struct * pTTY = pPort->tty; + int nResult; + int nRoom = 0; + unsigned int pipeEP; + + DBG( "port %d\n", pPort->number ); + + if (pURB->status != 0) + { + DBG( "nonzero read bulk status received: %d\n", pURB->status ); + + return; + } + + usb_serial_debug_data( debug, + &pPort->dev, + __FUNCTION__, + pURB->actual_length, + pURB->transfer_buffer ); + + // We do no port throttling + + // Push data to tty layer and user space read function + if (pTTY != 0 && pURB->actual_length) + { + nRoom = tty_buffer_request_room( pTTY, pURB->actual_length ); + DBG( "room size %d %d\n", nRoom, 512 ); + if (nRoom != 0) + { + tty_insert_flip_string( pTTY, pURB->transfer_buffer, nRoom ); + tty_flip_buffer_push( pTTY ); + } + } + + pipeEP = usb_rcvbulkpipe( pPort->serial->dev, + pPort->bulk_in_endpointAddress ); + + // For continuous reading + usb_fill_bulk_urb( pPort->read_urb, + pPort->serial->dev, + pipeEP, + pPort->read_urb->transfer_buffer, + pPort->read_urb->transfer_buffer_length, + GobiReadBulkCallback, + pPort ); + + nResult = usb_submit_urb( pPort->read_urb, GFP_ATOMIC ); + if (nResult != 0) + { + DBG( "failed resubmitting read urb, error %d\n", nResult ); + } +} + +#endif + +/*=========================================================================== +METHOD: + GobiSuspend (Public Method) + +DESCRIPTION: + Set reset_resume flag + +PARAMETERS + pIntf [ I ] - Pointer to interface + powerEvent [ I ] - Power management event + +RETURN VALUE: + int - 0 for success + negative errno for failure +===========================================================================*/ +int GobiSuspend( + struct usb_interface * pIntf, + pm_message_t powerEvent ) +{ + struct usb_serial * pDev; + + if (pIntf == 0) + { + return -ENOMEM; + } + + pDev = usb_get_intfdata( pIntf ); + if (pDev == NULL) + { + return -ENXIO; + } + + // Unless this is PM_EVENT_SUSPEND, make sure device gets rescanned + if ((powerEvent.event & PM_EVENT_SUSPEND) == 0) + { + pDev->dev->reset_resume = 1; + } + + // Run usb_serial's suspend function + return usb_serial_suspend( pIntf, powerEvent ); +} + +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,23 )) + +/*=========================================================================== +METHOD: + GobiResume (Free Method) + +DESCRIPTION: + Restart URBs killed during usb_serial_suspend + + Fixes 2 bugs in 2.6.23 kernel + 1. pSerial->type->resume was NULL and unchecked, caused crash. + 2. set_to_generic_if_null was not run for resume. + +PARAMETERS: + pIntf [ I ] - Pointer to interface + +RETURN VALUE: + int - 0 for success + negative errno for failure +===========================================================================*/ +int GobiResume( struct usb_interface * pIntf ) +{ + struct usb_serial * pSerial = usb_get_intfdata( pIntf ); + struct usb_serial_port * pPort; + int portIndex, errors, nResult; + + if (pSerial == NULL) + { + DBG( "no pSerial\n" ); + return -ENOMEM; + } + if (pSerial->type == NULL) + { + DBG( "no pSerial->type\n" ); + return ENOMEM; + } + if (pSerial->type->resume == NULL) + { + // Expected behaviour in 2.6.23, in later kernels this was handled + // by the usb-serial driver and usb_serial_generic_resume + errors = 0; + for (portIndex = 0; portIndex < pSerial->num_ports; portIndex++) + { + pPort = pSerial->port[portIndex]; + if (pPort->open_count > 0 && pPort->read_urb != NULL) + { + nResult = usb_submit_urb( pPort->read_urb, GFP_NOIO ); + if (nResult < 0) + { + // Return first error we see + DBG( "error %d\n", nResult ); + return nResult; + } + } + } + + // Success + return 0; + } + + // Execution would only reach this point if user has + // patched version of usb-serial driver. + return usb_serial_resume( pIntf ); +} + +#endif + +/*=========================================================================== +METHOD: + GobiInit (Free Method) + +DESCRIPTION: + Register the driver and device + +PARAMETERS: + +RETURN VALUE: + int - negative error code on failure + zero on success +===========================================================================*/ +static int __init GobiInit( void ) +{ + int nRetval = 0; + gpClose = NULL; + + gGobiDevice.num_ports = NUM_BULK_EPS; + + // Registering driver to USB serial core layer + nRetval = usb_serial_register( &gGobiDevice ); + if (nRetval != 0) + { + return nRetval; + } + + // Registering driver to USB core layer + nRetval = usb_register( &GobiDriver ); + if (nRetval != 0) + { + usb_serial_deregister( &gGobiDevice ); + return nRetval; + } + + // This will be shown whenever driver is loaded + printk( KERN_INFO "%s: %s\n", DRIVER_DESC, DRIVER_VERSION ); + + return nRetval; +} + +/*=========================================================================== +METHOD: + GobiExit (Free Method) + +DESCRIPTION: + Deregister the driver and device + +PARAMETERS: + +RETURN VALUE: +===========================================================================*/ +static void __exit GobiExit( void ) +{ + gpClose = NULL; + usb_deregister( &GobiDriver ); + usb_serial_deregister( &gGobiDevice ); +} + +// Calling kernel module to init our driver +module_init( GobiInit ); +module_exit( GobiExit ); + +MODULE_VERSION( DRIVER_VERSION ); +MODULE_AUTHOR( DRIVER_AUTHOR ); +MODULE_DESCRIPTION( DRIVER_DESC ); +MODULE_LICENSE( "Dual BSD/GPL" ); + +module_param( debug, bool, S_IRUGO | S_IWUSR ); +MODULE_PARM_DESC( debug, "Debug enabled or not" ); -- 1.7.1
From 53b730f4705fe5cce06a48bb5099eb09c9810554 Mon Sep 17 00:00:00 2001 From: Oliver Neukum <oliver@xxxxxxxxxx> Date: Fri, 18 Mar 2011 10:53:13 +0100 Subject: [PATCH 2/5] Gobi3000: adjust to newest kernel --- drivers/net/usb/Kconfig | 5 +++++ drivers/net/usb/Makefile | 3 ++- drivers/net/usb/QMIDevice.c | 8 +++----- drivers/net/usb/QMIDevice.h | 3 +-- drivers/usb/serial/Kconfig | 9 +++++++++ drivers/usb/serial/Makefile | 1 + 6 files changed, 21 insertions(+), 8 deletions(-) diff --git a/drivers/net/usb/Kconfig b/drivers/net/usb/Kconfig index 6f600cc..8b6d078 100644 --- a/drivers/net/usb/Kconfig +++ b/drivers/net/usb/Kconfig @@ -215,6 +215,11 @@ config USB_NET_CDC_NCM * ST-Ericsson M343 HSPA Mobile Broadband Modem (reference design) * Ericsson F5521gw Mobile Broadband Module +config USB_NET_GOBI3000 + tristate "Gobi 3000 support" + help + This driver supports the Gobi3000 3G module + config USB_NET_DM9601 tristate "Davicom DM9601 based USB 1.1 10/100 ethernet devices" depends on USB_USBNET diff --git a/drivers/net/usb/Makefile b/drivers/net/usb/Makefile index cac1703..e09083a 100644 --- a/drivers/net/usb/Makefile +++ b/drivers/net/usb/Makefile @@ -1,6 +1,7 @@ # # Makefile for USB Network drivers # +GobiNet-objs := GobiUSBNet.o QMIDevice.o QMI.o obj-$(CONFIG_USB_CATC) += catc.o obj-$(CONFIG_USB_KAWETH) += kaweth.o @@ -27,4 +28,4 @@ obj-$(CONFIG_USB_IPHETH) += ipheth.o obj-$(CONFIG_USB_SIERRA_NET) += sierra_net.o obj-$(CONFIG_USB_NET_CX82310_ETH) += cx82310_eth.o obj-$(CONFIG_USB_NET_CDC_NCM) += cdc_ncm.o - +obj-$(CONFIG_USB_NET_GOBI3000) += GobiNet.o diff --git a/drivers/net/usb/QMIDevice.c b/drivers/net/usb/QMIDevice.c index ff11456..f406374 100644 --- a/drivers/net/usb/QMIDevice.c +++ b/drivers/net/usb/QMIDevice.c @@ -152,7 +152,7 @@ struct file_operations UserspaceQMIFops = .owner = THIS_MODULE, .read = UserspaceRead, .write = UserspaceWrite, - .ioctl = UserspaceIOCTL, + .unlocked_ioctl = UserspaceIOCTL, .open = UserspaceOpen, .flush = UserspaceClose, }; @@ -2108,7 +2108,6 @@ DESCRIPTION: Userspace IOCTL functions PARAMETERS - pUnusedInode [ I ] - (unused) kernel file descriptor pFilp [ I ] - userspace file descriptor cmd [ I ] - IOCTL command arg [ I ] - IOCTL argument @@ -2117,8 +2116,7 @@ RETURN VALUE: int - 0 for success Negative errno for failure ===========================================================================*/ -int UserspaceIOCTL( - struct inode * pUnusedInode, +long UserspaceIOCTL( struct file * pFilp, unsigned int cmd, unsigned long arg ) @@ -2258,7 +2256,7 @@ int UserspaceClose( } // Fallthough. If f_count == 1 no need to do more checks - if (atomic_read( &pFilp->f_count ) != 1) + if ( atomic_long_read(&pFilp->f_count) != 1) { rcu_read_lock(); for_each_process( pEachTask ) diff --git a/drivers/net/usb/QMIDevice.h b/drivers/net/usb/QMIDevice.h index 176f080..67b262b 100644 --- a/drivers/net/usb/QMIDevice.h +++ b/drivers/net/usb/QMIDevice.h @@ -279,8 +279,7 @@ int UserspaceOpen( struct file * pFilp ); // Userspace ioctl -int UserspaceIOCTL( - struct inode * pUnusedInode, +long UserspaceIOCTL( struct file * pFilp, unsigned int cmd, unsigned long arg ); diff --git a/drivers/usb/serial/Kconfig b/drivers/usb/serial/Kconfig index c2b2976..ba6b96d 100644 --- a/drivers/usb/serial/Kconfig +++ b/drivers/usb/serial/Kconfig @@ -660,6 +660,15 @@ config USB_SERIAL_SSU100 To compile this driver as a module, choose M here: the module will be called ssu100. +config CONFIG_USB_SERIAL_GOBI3000 + tristate "Support for GPS on Gobi 3000 devices" + help + Say y if you want to use the GPS reciever on the Gobi3000 + module. + + To compile this driver as a module, choose M here: the + module will be called GobiSerial. + config USB_SERIAL_DEBUG tristate "USB Debugging Device" help diff --git a/drivers/usb/serial/Makefile b/drivers/usb/serial/Makefile index 9a2117f..9ecdd79 100644 --- a/drivers/usb/serial/Makefile +++ b/drivers/usb/serial/Makefile @@ -26,6 +26,7 @@ obj-$(CONFIG_USB_SERIAL_EMPEG) += empeg.o obj-$(CONFIG_USB_SERIAL_FTDI_SIO) += ftdi_sio.o obj-$(CONFIG_USB_SERIAL_FUNSOFT) += funsoft.o obj-$(CONFIG_USB_SERIAL_GARMIN) += garmin_gps.o +obj-$(CONFIG_USB_SERIAL_GOBI3000) += GobiSerial.o obj-$(CONFIG_USB_SERIAL_HP4X) += hp4x.o obj-$(CONFIG_USB_SERIAL_IPAQ) += ipaq.o obj-$(CONFIG_USB_SERIAL_IPW) += ipw.o -- 1.7.1
From b177faa1553ac6c4ec5d403de56924ff8d7207d6 Mon Sep 17 00:00:00 2001 From: Oliver Neukum <oliver@xxxxxxxxxx> Date: Fri, 18 Mar 2011 14:13:45 +0100 Subject: [PATCH 3/5] Gobi3000: endianness, allocation checks, error returns - memory allocations must always be checked - endianness must be handled if data is going over the wire - handle copy_to/from_user correctly --- drivers/net/usb/QMIDevice.c | 25 ++++++++++++++----------- 1 files changed, 14 insertions(+), 11 deletions(-) diff --git a/drivers/net/usb/QMIDevice.c b/drivers/net/usb/QMIDevice.c index f406374..9317477 100644 --- a/drivers/net/usb/QMIDevice.c +++ b/drivers/net/usb/QMIDevice.c @@ -448,7 +448,7 @@ void ReadCallback( struct urb * pReadURB ) } else { - transactionID = *(u16*)(pData + result + 1); + transactionID = le16_to_cpu(get_unaligned((u16*)(pData + result + 1))); } // Critical section @@ -464,6 +464,8 @@ void ReadCallback( struct urb * pReadURB ) { // Make copy of pData pDataCopy = kmalloc( dataSize, GFP_ATOMIC ); + if (!pDataCopy) + goto skip_no_memory; memcpy( pDataCopy, pData, dataSize ); if (AddToReadMemList( pDev, @@ -472,10 +474,11 @@ void ReadCallback( struct urb * pReadURB ) pDataCopy, dataSize ) == false) { + kfree( pDataCopy ); + +skip_no_memory: DBG( "Error allocating pReadMemListEntry " "read will be discarded\n" ); - kfree( pDataCopy ); - // End critical section spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); @@ -553,7 +556,7 @@ void IntCallback( struct urb * pIntURB ) { // CDC GET_ENCAPSULATED_RESPONSE if ((pIntURB->actual_length == 8) - && (*(u64*)pIntURB->transfer_buffer == CDC_GET_ENCAPSULATED_RESPONSE)) + && (*(u64*)pIntURB->transfer_buffer == cpu_to_le64(CDC_GET_ENCAPSULATED_RESPONSE))) { // Time to read usb_fill_control_urb( pDev->mQMIDev.mpReadURB, @@ -575,7 +578,7 @@ void IntCallback( struct urb * pIntURB ) } // CDC CONNECTION_SPEED_CHANGE else if ((pIntURB->actual_length == 16) - && (*(u64*)pIntURB->transfer_buffer == CDC_CONNECTION_SPEED_CHANGE)) + && (*(u64*)pIntURB->transfer_buffer == cpu_to_le64(CDC_CONNECTION_SPEED_CHANGE))) { // if upstream or downstream is 0, stop traffic. Otherwise resume it if ((*(u32*)(pIntURB->transfer_buffer + 8) == 0) @@ -1124,7 +1127,7 @@ int WriteSync( return -EINVAL; } - result = usb_submit_urb( pWriteURB, GFP_KERNEL ); + result = usb_submit_urb( pWriteURB, GFP_ATOMIC ); if (result < 0) { DBG( "submit URB error %d\n", result ); @@ -2094,7 +2097,7 @@ int UserspaceOpen( } pFilpData = (sQMIFilpStorage *)pFilp->private_data; - pFilpData->mClientID = (u16)-1; + pFilpData->mClientID = (u16)0xffff; pFilpData->mpDev = pDev; return 0; @@ -2151,7 +2154,7 @@ long UserspaceIOCTL( } // Connection is already setup - if (pFilpData->mClientID != (u16)-1) + if (pFilpData->mClientID != (u16)0xffff) { DBG( "Close the current connection before opening a new one\n" ); return -EBADR; @@ -2213,7 +2216,7 @@ long UserspaceIOCTL( DBG( "Copy to userspace failure %d\n", result ); } - return result; + return result ? -EFAULT : 0; break; @@ -2304,7 +2307,7 @@ int UserspaceClose( // Note: memory pointer is still saved in pFilpData to be deleted later pFilp->private_data = NULL; - if (pFilpData->mClientID != (u16)-1) + if (pFilpData->mClientID != (u16)0xffff) { ReleaseClientID( pFilpData->mpDev, pFilpData->mClientID ); @@ -2453,7 +2456,7 @@ ssize_t UserspaceWrite( { DBG( "Unable to copy data from userspace %d\n", status ); kfree( pWriteBuffer ); - return status; + return status ? -EFAULT : 0; } status = WriteSync( pFilpData->mpDev, -- 1.7.1
From c7b45dacd56bb64df643cc32e8330d8aa82c0caa Mon Sep 17 00:00:00 2001 From: Oliver Neukum <oliver@xxxxxxxxxx> Date: Fri, 18 Mar 2011 15:11:33 +0100 Subject: [PATCH 4/5] Gobi3000: memory leaks fix memory leaks in hard_xmit path --- drivers/net/usb/GobiUSBNet.c | 3 +++ 1 files changed, 3 insertions(+), 0 deletions(-) diff --git a/drivers/net/usb/GobiUSBNet.c b/drivers/net/usb/GobiUSBNet.c index 797bcee..d09c76d 100644 --- a/drivers/net/usb/GobiUSBNet.c +++ b/drivers/net/usb/GobiUSBNet.c @@ -736,6 +736,7 @@ int GobiUSBNetStartXmit( pURBListEntry->mpURB = usb_alloc_urb( 0, GFP_ATOMIC ); if (pURBListEntry->mpURB == NULL) { + kfree(pURBListEntry); DBG( "unable to allocate URB\n" ); return NETDEV_TX_BUSY; } @@ -744,6 +745,8 @@ int GobiUSBNetStartXmit( pURBData = kmalloc( pSKB->len, GFP_ATOMIC ); if (pURBData == NULL) { + usb_free_urb(pURBListEntry->mpURB); + kfree(pURBListEntry); DBG( "unable to allocate URB data\n" ); return NETDEV_TX_BUSY; } -- 1.7.1
From 1d06e473d86a1901738020c70176fcdce01edd1c Mon Sep 17 00:00:00 2001 From: Oliver Neukum <oliver@xxxxxxxxxx> Date: Sat, 19 Mar 2011 00:31:44 +0100 Subject: [PATCH 5/5] Gobi 3000: deadlocks fixed - no usb_kill_urb() with a spinlock held - simplified locking - GFP_NOIO in hard_xmit (cifs, nfs deadlock) --- drivers/net/usb/GobiUSBNet.c | 62 +++++++++++++++++++++++------------------- 1 files changed, 34 insertions(+), 28 deletions(-) diff --git a/drivers/net/usb/GobiUSBNet.c b/drivers/net/usb/GobiUSBNet.c index d09c76d..0738ac2 100644 --- a/drivers/net/usb/GobiUSBNet.c +++ b/drivers/net/usb/GobiUSBNet.c @@ -458,8 +458,9 @@ void GobiUSBNetTXTimeout( struct net_device * pNet ) struct sGobiUSBNet * pGobiDev; sAutoPM * pAutoPM; sURBList * pURBListEntry; - unsigned long activeURBflags, URBListFlags; + unsigned long flags; struct usbnet * pDev = netdev_priv( pNet ); + int res; if (pDev == NULL || pDev->net == NULL) { @@ -478,17 +479,22 @@ void GobiUSBNetTXTimeout( struct net_device * pNet ) DBG( "\n" ); // Stop activeURB - spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags ); + spin_lock_irqsave( &pAutoPM->mActiveURBLock, flags ); if (pAutoPM->mpActiveURB != NULL) { - usb_kill_urb( pAutoPM->mpActiveURB ); + res = usb_unlink_urb( pAutoPM->mpActiveURB ); } - spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags ); + spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, flags ); + + + /* in this case it already finished */ + if (res != -EINPROGRESS) + return; // Cleanup URB List - spin_lock_irqsave( &pAutoPM->mURBListLock, URBListFlags ); + spin_lock_irqsave( &pAutoPM->mURBListLock, flags ); pURBListEntry = pAutoPM->mpURBList; while (pURBListEntry != NULL) @@ -499,7 +505,7 @@ void GobiUSBNetTXTimeout( struct net_device * pNet ) pURBListEntry = pAutoPM->mpURBList; } - spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags ); + spin_unlock_irqrestore( &pAutoPM->mURBListLock, flags ); complete( &pAutoPM->mThreadDoWork ); @@ -523,7 +529,7 @@ RETURN VALUE: ===========================================================================*/ static int GobiUSBNetAutoPMThread( void * pData ) { - unsigned long activeURBflags, URBListFlags; + unsigned long flags; sURBList * pURBListEntry; int status; struct usb_device * pUdev; @@ -547,18 +553,18 @@ static int GobiUSBNetAutoPMThread( void * pData ) if (pAutoPM->mbExit == true) { // Stop activeURB - spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags ); + spin_lock_irqsave( &pAutoPM->mActiveURBLock, flags ); if (pAutoPM->mpActiveURB != NULL) { - usb_kill_urb( pAutoPM->mpActiveURB ); + usb_unlink_urb( pAutoPM->mpActiveURB ); } // Will be freed in callback function - spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags ); + spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, flags ); // Cleanup URB List - spin_lock_irqsave( &pAutoPM->mURBListLock, URBListFlags ); + spin_lock_irqsave( &pAutoPM->mURBListLock, flags ); pURBListEntry = pAutoPM->mpURBList; while (pURBListEntry != NULL) @@ -569,13 +575,13 @@ static int GobiUSBNetAutoPMThread( void * pData ) pURBListEntry = pAutoPM->mpURBList; } - spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags ); + spin_unlock_irqrestore( &pAutoPM->mURBListLock, flags ); break; } // Is our URB active? - spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags ); + spin_lock_irqsave( &pAutoPM->mActiveURBLock, flags ); // EAGAIN used to signify callback is done if (IS_ERR( pAutoPM->mpActiveURB ) @@ -584,40 +590,40 @@ static int GobiUSBNetAutoPMThread( void * pData ) pAutoPM->mpActiveURB = NULL; // Restore IRQs so task can sleep - spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags ); + spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, flags ); // URB is done, decrement the Auto PM usage count usb_autopm_put_interface( pAutoPM->mpIntf ); // Lock ActiveURB again - spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags ); + spin_lock_irqsave( &pAutoPM->mActiveURBLock, flags ); } if (pAutoPM->mpActiveURB != NULL) { // There is already a URB active, go back to sleep - spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags ); + spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, flags ); continue; } // Is there a URB waiting to be submitted? - spin_lock_irqsave( &pAutoPM->mURBListLock, URBListFlags ); + spin_lock( &pAutoPM->mURBListLock); if (pAutoPM->mpURBList == NULL) { // No more URBs to submit, go back to sleep - spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags ); - spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags ); + spin_unlock( &pAutoPM->mURBListLock); + spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, flags ); continue; } // Pop an element pURBListEntry = pAutoPM->mpURBList; pAutoPM->mpURBList = pAutoPM->mpURBList->mpNext; - spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags ); + spin_unlock( &pAutoPM->mURBListLock); // Set ActiveURB pAutoPM->mpActiveURB = pURBListEntry->mpURB; - spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags ); + spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, flags ); // Tell autopm core we need device woken up status = usb_autopm_get_interface( pAutoPM->mpIntf ); @@ -635,29 +641,29 @@ static int GobiUSBNetAutoPMThread( void * pData ) } // Add pURBListEntry back onto pAutoPM->mpURBList - spin_lock_irqsave( &pAutoPM->mURBListLock, URBListFlags ); + spin_lock_irqsave( &pAutoPM->mURBListLock, flags ); pURBListEntry->mpNext = pAutoPM->mpURBList; pAutoPM->mpURBList = pURBListEntry; - spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags ); + spin_unlock_irqrestore( &pAutoPM->mURBListLock, flags ); - spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags ); + spin_lock_irqsave( &pAutoPM->mActiveURBLock, flags ); pAutoPM->mpActiveURB = NULL; - spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags ); + spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, flags ); // Go back to sleep continue; } // Submit URB - status = usb_submit_urb( pAutoPM->mpActiveURB, GFP_KERNEL ); + status = usb_submit_urb( pAutoPM->mpActiveURB, GFP_NOIO ); if (status < 0) { // Could happen for a number of reasons DBG( "Failed to submit URB: %d. Packet dropped\n", status ); - spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags ); + spin_lock_irqsave( &pAutoPM->mActiveURBLock, flags ); usb_free_urb( pAutoPM->mpActiveURB ); pAutoPM->mpActiveURB = NULL; - spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags ); + spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, flags ); usb_autopm_put_interface( pAutoPM->mpIntf ); // Loop again -- 1.7.1