/*=========================================================================== FILE: GobiUSBNet.c DESCRIPTION: Qualcomm USB Network device for Gobi 3000 FUNCTIONS: GobiNetSuspend GobiNetResume GobiNetDriverBind GobiNetDriverUnbind GobiUSBNetURBCallback GobiUSBNetTXTimeout GobiUSBNetAutoPMThread GobiUSBNetStartXmit GobiUSBNetOpen GobiUSBNetStop GobiUSBNetProbe GobiUSBNetModInit GobiUSBNetModExit Copyright (c) 2011, Code Aurora Forum. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Code Aurora Forum nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ===========================================================================*/ //--------------------------------------------------------------------------- // Include Files //--------------------------------------------------------------------------- #include "Structs.h" #include "QMIDevice.h" #include "QMI.h" #include #include #include //----------------------------------------------------------------------------- // Definitions //----------------------------------------------------------------------------- // Version Information #define DRIVER_VERSION "GobiNet_SR01A02V14" #define DRIVER_AUTHOR "Qualcomm Innovation Center" #define DRIVER_DESC "GobiNet" // Debug flag int debug = 0; // Allow user interrupts int interruptible = 1; // Number of IP packets which may be queued up for transmit int txQueueLength = 100; // Class should be created during module init, so needs to be global static struct class * gpClass; #ifdef CONFIG_PM /*=========================================================================== METHOD: GobiNetSuspend (Public Method) DESCRIPTION: Stops QMI traffic while device is suspended PARAMETERS pIntf [ I ] - Pointer to interface powerEvent [ I ] - Power management event RETURN VALUE: int - 0 for success negative errno for failure ===========================================================================*/ int GobiNetSuspend( struct usb_interface * pIntf, pm_message_t powerEvent ) { struct usbnet * pDev; sGobiUSBNet * pGobiDev; 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 (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,14 )) if (pDev->udev->auto_pm == 0) #else if (1) #endif #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 ); #if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,22 )) pDev->udev->reset_resume = 0; #endif // 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 #if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,22 )) pDev->udev->reset_resume = 1; #endif } // Run usbnet's suspend function return usbnet_suspend( pIntf, powerEvent ); } /*=========================================================================== METHOD: GobiNetResume (Public Method) DESCRIPTION: Resume QMI traffic or recreate QMI device PARAMETERS pIntf [ I ] - Pointer to interface RETURN VALUE: int - 0 for success negative errno for failure ===========================================================================*/ int GobiNetResume( struct usb_interface * pIntf ) { struct usbnet * pDev; sGobiUSBNet * pGobiDev; int nRet; 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; } #ifdef CONFIG_PM #if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 )) // Kick Auto PM thread to process any queued URBs complete( &pGobiDev->mAutoPM.mThreadDoWork ); #endif #endif /* CONFIG_PM */ } else { DBG( "nothing to resume\n" ); return 0; } return nRet; } #endif /* CONFIG_PM */ /*=========================================================================== 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 (4 for UC20) if ( pIntf->cur_altsetting->desc.bInterfaceNumber != 4 ) { 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 ); #if 1 //def DATA_MODE_RP /* make MAC addr easily distinguishable from an IP header */ if ((pDev->net->dev_addr[0] & 0xd0) == 0x40) { /*clear this bit wil make usbnet apdater named as usbX(instead if ethX)*/ pDev->net->dev_addr[0] |= 0x02; /* set local assignment bit */ pDev->net->dev_addr[0] &= 0xbf; /* clear "IP" bit */ } #endif 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 #if (LINUX_VERSION_CODE >= KERNEL_VERSION( 2,6,19 )) pIntf->needs_remote_wakeup = 0; #endif if (atomic_dec_and_test(&pGobiDev->refcount)) kfree( pGobiDev ); else DBG("memory leak!\n"); } #if 1 //def DATA_MODE_RP /*=========================================================================== METHOD: GobiNetDriverTxFixup (Public Method) DESCRIPTION: Handling data format mode on transmit path PARAMETERS pDev [ I ] - Pointer to usbnet device pSKB [ I ] - Pointer to transmit packet buffer flags [ I ] - os flags RETURN VALUE: None ===========================================================================*/ struct sk_buff *GobiNetDriverTxFixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags) { sGobiUSBNet * pGobiDev = (sGobiUSBNet *)dev->data[0]; if (!pGobiDev->mbRawIPMode) return skb; // Skip Ethernet header from message if (skb_pull(skb, ETH_HLEN)) { return skb; } else { dev_err(&dev->intf->dev, "Packet Dropped "); } // Filter the packet out, release it dev_kfree_skb_any(skb); return NULL; } /*=========================================================================== METHOD: GobiNetDriverRxFixup (Public Method) DESCRIPTION: Handling data format mode on receive path PARAMETERS pDev [ I ] - Pointer to usbnet device pSKB [ I ] - Pointer to received packet buffer RETURN VALUE: None ===========================================================================*/ static int GobiNetDriverRxFixup(struct usbnet *dev, struct sk_buff *skb) { __be16 proto; sGobiUSBNet * pGobiDev = (sGobiUSBNet *)dev->data[0]; if (!pGobiDev->mbRawIPMode) return 1; /* This check is no longer done by usbnet */ if (skb->len < dev->net->hard_header_len) return 0; switch (skb->data[0] & 0xf0) { case 0x40: proto = htons(ETH_P_IP); break; case 0x60: proto = htons(ETH_P_IPV6); break; case 0x00: if (is_multicast_ether_addr(skb->data)) return 1; /* possibly bogus destination - rewrite just in case */ skb_reset_mac_header(skb); goto fix_dest; default: /* pass along other packets without modifications */ return 1; } if (skb_headroom(skb) < ETH_HLEN && pskb_expand_head(skb, ETH_HLEN, 0, GFP_ATOMIC)) { DBG("%s: couldn't pskb_expand_head\n", __func__); return 0; } skb_push(skb, ETH_HLEN); skb_reset_mac_header(skb); eth_hdr(skb)->h_proto = proto; memset(eth_hdr(skb)->h_source, 0, ETH_ALEN); fix_dest: memcpy(eth_hdr(skb)->h_dest, dev->net->dev_addr, ETH_ALEN); return 1; } #endif #if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 )) #ifdef CONFIG_PM /*=========================================================================== METHOD: GobiUSBNetURBCallback (Public Method) DESCRIPTION: Write is complete, cleanup and signal that we're ready for next packet PARAMETERS pURB [ I ] - Pointer to sAutoPM struct RETURN VALUE: None ===========================================================================*/ #if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,14 )) void GobiUSBNetURBCallback( struct urb * pURB ) #else void GobiUSBNetURBCallback(struct urb *pURB, struct pt_regs *regs) #endif { 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 ); struct urb * pURB; if (pDev == NULL || pDev->net == NULL) { DBG( "failed to get usbnet device\n" ); return; } pGobiDev = (sGobiUSBNet *)pDev->data[0]; if (pGobiDev == NULL) { DBG( "failed to get QMIDevice\n" ); return; } pAutoPM = &pGobiDev->mAutoPM; DBG( "\n" ); // Grab a pointer to active URB spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags ); pURB = pAutoPM->mpActiveURB; spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags ); // Stop active URB if (pURB != NULL) { usb_kill_urb( pURB ); } // Cleanup URB List spin_lock_irqsave( &pAutoPM->mURBListLock, URBListFlags ); pURBListEntry = pAutoPM->mpURBList; while (pURBListEntry != NULL) { pAutoPM->mpURBList = pAutoPM->mpURBList->mpNext; atomic_dec( &pAutoPM->mURBListLen ); usb_free_urb( pURBListEntry->mpURB ); kfree( pURBListEntry ); pURBListEntry = pAutoPM->mpURBList; } spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags ); complete( &pAutoPM->mThreadDoWork ); return; } /*=========================================================================== METHOD: GobiUSBNetAutoPMThread (Public Method) DESCRIPTION: Handle device Auto PM state asynchronously Handle network packet transmission asynchronously PARAMETERS pData [ I ] - Pointer to sAutoPM struct RETURN VALUE: int - 0 for success Negative errno for error ===========================================================================*/ static int GobiUSBNetAutoPMThread( void * pData ) { unsigned long activeURBflags, URBListFlags; sURBList * pURBListEntry; int status; struct usb_device * pUdev; sAutoPM * pAutoPM = (sAutoPM *)pData; struct urb * pURB; if (pAutoPM == NULL) { DBG( "passed null pointer\n" ); return -EINVAL; } pUdev = interface_to_usbdev( pAutoPM->mpIntf ); DBG( "traffic thread started\n" ); while (pAutoPM->mbExit == false) { // Wait for someone to poke us wait_for_completion_interruptible( &pAutoPM->mThreadDoWork ); // Time to exit? if (pAutoPM->mbExit == true) { // Stop activeURB spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags ); pURB = pAutoPM->mpActiveURB; spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags ); // EAGAIN used to signify callback is done if (IS_ERR( pAutoPM->mpActiveURB ) && PTR_ERR( pAutoPM->mpActiveURB ) == -EAGAIN ) { pURB = NULL; } if (pURB != NULL) { usb_kill_urb( pURB ); } // Will be freed in callback function // Cleanup URB List spin_lock_irqsave( &pAutoPM->mURBListLock, URBListFlags ); pURBListEntry = pAutoPM->mpURBList; while (pURBListEntry != NULL) { pAutoPM->mpURBList = pAutoPM->mpURBList->mpNext; atomic_dec( &pAutoPM->mURBListLen ); usb_free_urb( pURBListEntry->mpURB ); kfree( pURBListEntry ); pURBListEntry = pAutoPM->mpURBList; } spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags ); break; } // Is our URB active? spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags ); // EAGAIN used to signify callback is done if (IS_ERR( pAutoPM->mpActiveURB ) && PTR_ERR( pAutoPM->mpActiveURB ) == -EAGAIN ) { pAutoPM->mpActiveURB = NULL; // Restore IRQs so task can sleep spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags ); // URB is done, decrement the Auto PM usage count usb_autopm_put_interface( pAutoPM->mpIntf ); // Lock ActiveURB again spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags ); } if (pAutoPM->mpActiveURB != NULL) { // There is already a URB active, go back to sleep spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags ); continue; } // Is there a URB waiting to be submitted? spin_lock_irqsave( &pAutoPM->mURBListLock, URBListFlags ); if (pAutoPM->mpURBList == NULL) { // No more URBs to submit, go back to sleep spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags ); spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags ); continue; } // Pop an element pURBListEntry = pAutoPM->mpURBList; pAutoPM->mpURBList = pAutoPM->mpURBList->mpNext; atomic_dec( &pAutoPM->mURBListLen ); spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags ); // Set ActiveURB pAutoPM->mpActiveURB = pURBListEntry->mpURB; spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags ); // Tell autopm core we need device woken up status = usb_autopm_get_interface( pAutoPM->mpIntf ); if (status < 0) { DBG( "unable to autoresume interface: %d\n", status ); // likely caused by device going from autosuspend -> full suspend if (status == -EPERM) { #if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,33 )) #if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,14 )) pUdev->auto_pm = 0; #else pUdev = pUdev; #endif #endif GobiNetSuspend( pAutoPM->mpIntf, PMSG_SUSPEND ); } // Add pURBListEntry back onto pAutoPM->mpURBList spin_lock_irqsave( &pAutoPM->mURBListLock, URBListFlags ); pURBListEntry->mpNext = pAutoPM->mpURBList; pAutoPM->mpURBList = pURBListEntry; atomic_inc( &pAutoPM->mURBListLen ); spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags ); spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags ); pAutoPM->mpActiveURB = NULL; spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags ); // Go back to sleep continue; } // Submit URB status = usb_submit_urb( pAutoPM->mpActiveURB, GFP_KERNEL ); if (status < 0) { // Could happen for a number of reasons DBG( "Failed to submit URB: %d. Packet dropped\n", status ); spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags ); usb_free_urb( pAutoPM->mpActiveURB ); pAutoPM->mpActiveURB = NULL; spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags ); usb_autopm_put_interface( pAutoPM->mpIntf ); // Loop again complete( &pAutoPM->mThreadDoWork ); } kfree( pURBListEntry ); } DBG( "traffic thread exiting\n" ); pAutoPM->mpThread = NULL; return 0; } /*=========================================================================== 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( NULL == pSKB ) { DBG( "Buffer is NULL \n" ); return NETDEV_TX_BUSY; } if (GobiTestDownReason( pGobiDev, DRIVER_SUSPENDED ) == true) { // Should not happen DBG( "device is suspended\n" ); dump_stack(); return NETDEV_TX_BUSY; } if (GobiTestDownReason( pGobiDev, NO_NDIS_CONNECTION )) { //netif_carrier_off( pGobiDev->mpNetDev->net ); //DBG( "device is disconnected\n" ); //dump_stack(); return NETDEV_TX_BUSY; } // Convert the sk_buff into a URB // Check if buffer is full if ( atomic_read( &pAutoPM->mURBListLen ) >= txQueueLength) { DBG( "not scheduling request, buffer is full\n" ); return NETDEV_TX_BUSY; } // 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" ); // release all memory allocated by now if (pURBListEntry) kfree( pURBListEntry ); return NETDEV_TX_BUSY; } #if 1 //def DATA_MODE_RP GobiNetDriverTxFixup(pNet, pSKB, GFP_ATOMIC); #endif // Allocate URB transfer_buffer pURBData = kmalloc( pSKB->len, GFP_ATOMIC ); if (pURBData == NULL) { DBG( "unable to allocate URB data\n" ); // release all memory allocated by now if (pURBListEntry) { usb_free_urb( pURBListEntry->mpURB ); kfree( pURBListEntry ); } return NETDEV_TX_BUSY; } // Fill with SKB's data memcpy( pURBData, pSKB->data, pSKB->len ); usb_fill_bulk_urb( pURBListEntry->mpURB, pGobiDev->mpNetDev->udev, pGobiDev->mpNetDev->out, pURBData, pSKB->len, GobiUSBNetURBCallback, pAutoPM ); /* Handle the need to send a zero length packet and release the * transfer buffer */ pURBListEntry->mpURB->transfer_flags |= (URB_ZERO_PACKET | URB_FREE_BUFFER); // Aquire lock on URBList spin_lock_irqsave( &pAutoPM->mURBListLock, URBListFlags ); // Add URB to end of list ppURBListEnd = &pAutoPM->mpURBList; while ((*ppURBListEnd) != NULL) { ppURBListEnd = &(*ppURBListEnd)->mpNext; } *ppURBListEnd = pURBListEntry; atomic_inc( &pAutoPM->mURBListLen ); spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags ); complete( &pAutoPM->mThreadDoWork ); // Start transfer timer pNet->trans_start = jiffies; // Free SKB if (pSKB) dev_kfree_skb_any( pSKB ); return NETDEV_TX_OK; } #endif static int (*local_usbnet_start_xmit) (struct sk_buff *skb, struct net_device *net); #endif static int GobiUSBNetStartXmit2( struct sk_buff *pSKB, struct net_device *pNet ){ struct sGobiUSBNet * pGobiDev; 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; } if( NULL == pSKB ) { DBG( "Buffer is NULL \n" ); return NETDEV_TX_BUSY; } if (GobiTestDownReason( pGobiDev, DRIVER_SUSPENDED ) == true) { // Should not happen DBG( "device is suspended\n" ); dump_stack(); return NETDEV_TX_BUSY; } if (GobiTestDownReason( pGobiDev, NO_NDIS_CONNECTION )) { //netif_carrier_off( pGobiDev->mpNetDev->net ); //DBG( "device is disconnected\n" ); //dump_stack(); return NETDEV_TX_BUSY; } #if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 )) return local_usbnet_start_xmit(pSKB, pNet); #else return usbnet_start_xmit(pSKB, pNet); #endif } /*=========================================================================== METHOD: GobiUSBNetOpen (Public Method) DESCRIPTION: Wrapper to usbnet_open, correctly handling autosuspend Start AutoPM thread (if CONFIG_PM is defined) PARAMETERS pNet [ I ] - Pointer to net device RETURN VALUE: int - 0 for success Negative errno for error ===========================================================================*/ int GobiUSBNetOpen( struct net_device * pNet ) { int status = 0; struct sGobiUSBNet * pGobiDev; 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" ); #ifdef CONFIG_PM #if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 )) // Start the AutoPM thread pGobiDev->mAutoPM.mpIntf = pGobiDev->mpIntf; pGobiDev->mAutoPM.mbExit = false; pGobiDev->mAutoPM.mpURBList = NULL; pGobiDev->mAutoPM.mpActiveURB = NULL; spin_lock_init( &pGobiDev->mAutoPM.mURBListLock ); spin_lock_init( &pGobiDev->mAutoPM.mActiveURBLock ); atomic_set( &pGobiDev->mAutoPM.mURBListLen, 0 ); init_completion( &pGobiDev->mAutoPM.mThreadDoWork ); pGobiDev->mAutoPM.mpThread = kthread_run( GobiUSBNetAutoPMThread, &pGobiDev->mAutoPM, "GobiUSBNetAutoPMThread" ); if (IS_ERR( pGobiDev->mAutoPM.mpThread )) { DBG( "AutoPM thread creation error\n" ); return PTR_ERR( pGobiDev->mAutoPM.mpThread ); } #endif #endif /* CONFIG_PM */ // Allow traffic GobiClearDownReason( pGobiDev, NET_IFACE_STOPPED ); // Pass to usbnet_open if defined if (pGobiDev->mpUSBNetOpen != NULL) { status = pGobiDev->mpUSBNetOpen( pNet ); #ifdef CONFIG_PM // If usbnet_open was successful enable Auto PM if (status == 0) { #if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,33 )) usb_autopm_enable( pGobiDev->mpIntf ); #else usb_autopm_put_interface( pGobiDev->mpIntf ); #endif } #endif /* CONFIG_PM */ } else { DBG( "no USBNetOpen defined\n" ); } return status; } /*=========================================================================== METHOD: GobiUSBNetStop (Public Method) DESCRIPTION: Wrapper to usbnet_stop, correctly handling autosuspend Stop AutoPM thread (if CONFIG_PM is defined) PARAMETERS pNet [ I ] - Pointer to net device RETURN VALUE: int - 0 for success Negative errno for error ===========================================================================*/ int GobiUSBNetStop( struct net_device * pNet ) { struct sGobiUSBNet * pGobiDev; struct usbnet * pDev = netdev_priv( pNet ); if (pDev == NULL || pDev->net == NULL) { DBG( "failed to get netdevice\n" ); return -ENXIO; } pGobiDev = (sGobiUSBNet *)pDev->data[0]; if (pGobiDev == NULL) { DBG( "failed to get QMIDevice\n" ); return -ENXIO; } // Stop traffic GobiSetDownReason( pGobiDev, NET_IFACE_STOPPED ); #ifdef CONFIG_PM #if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 )) // Tell traffic thread to exit pGobiDev->mAutoPM.mbExit = true; complete( &pGobiDev->mAutoPM.mThreadDoWork ); // Wait for it to exit while( pGobiDev->mAutoPM.mpThread != NULL ) { msleep( 100 ); } DBG( "thread stopped\n" ); #endif #endif /* CONFIG_PM */ // Pass to usbnet_stop, if defined if (pGobiDev->mpUSBNetStop != NULL) { return pGobiDev->mpUSBNetStop( pNet ); } else { return 0; } } /*=========================================================================*/ // Struct driver_info /*=========================================================================*/ static const struct driver_info GobiNetInfo = { .description = "GobiNet Ethernet Device", #ifdef CONFIG_ANDROID .flags = FLAG_ETHER | FLAG_POINTTOPOINT, #else .flags = FLAG_ETHER, #endif .bind = GobiNetDriverBind, .unbind = GobiNetDriverUnbind, #if 1 //def DATA_MODE_RP .rx_fixup = GobiNetDriverRxFixup, .tx_fixup = GobiNetDriverTxFixup, #endif .data = 0, }; /*=========================================================================*/ // Qualcomm Gobi 3000 VID/PIDs /*=========================================================================*/ static const struct usb_device_id GobiVIDPIDTable [] = { // Quectel UC20 { USB_DEVICE( 0x05c6, 0x9003 ), .driver_info = (unsigned long)&GobiNetInfo }, // Quectel EC20 { USB_DEVICE( 0x05c6, 0x9215 ), .driver_info = (unsigned long)&GobiNetInfo }, // Quectel EC25 { USB_DEVICE( 0x2c7c, 0x0125 ), .driver_info = (unsigned long)&GobiNetInfo }, // Quectel EC21 { USB_DEVICE( 0x2c7c, 0x0121 ), .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,19 )) pIntf->needs_remote_wakeup = 1; #endif #if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,23 )) pDev = usb_get_intfdata( pIntf ); #else pDev = (struct usbnet *)pIntf->dev.platform_data; #endif if (pDev == NULL || pDev->net == NULL) { DBG( "failed to get netdevice\n" ); usbnet_disconnect( pIntf ); return -ENXIO; } pGobiDev = kzalloc( sizeof( sGobiUSBNet ), GFP_KERNEL ); if (pGobiDev == NULL) { DBG( "falied to allocate device buffers" ); usbnet_disconnect( pIntf ); return -ENOMEM; } atomic_set(&pGobiDev->refcount, 1); pDev->data[0] = (unsigned long)pGobiDev; pGobiDev->mpNetDev = pDev; // Clearing endpoint halt is a magic handshake that brings // the device out of low power (airplane) mode usb_clear_halt( pGobiDev->mpNetDev->udev, pDev->out ); // Overload PM related network functions #if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 )) pGobiDev->mpUSBNetOpen = pDev->net->open; pDev->net->open = GobiUSBNetOpen; pGobiDev->mpUSBNetStop = pDev->net->stop; pDev->net->stop = GobiUSBNetStop; #if defined(CONFIG_PM) && (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,14 )) pDev->net->hard_start_xmit = GobiUSBNetStartXmit; pDev->net->tx_timeout = GobiUSBNetTXTimeout; #else //quectel donot send dhcp request before ndis connect for uc20 local_usbnet_start_xmit = pDev->net->hard_start_xmit; pDev->net->hard_start_xmit = GobiUSBNetStartXmit2; #endif #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; #if 1 //quectel donot send dhcp request before ndis connect for uc20 pNetDevOps->ndo_start_xmit = GobiUSBNetStartXmit2; #else pNetDevOps->ndo_start_xmit = usbnet_start_xmit; #endif pNetDevOps->ndo_tx_timeout = usbnet_tx_timeout; 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; #ifdef CONFIG_PM #if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 )) init_completion( &pGobiDev->mAutoPM.mThreadDoWork ); #endif #endif /* CONFIG_PM */ spin_lock_init( &pGobiDev->mQMIDev.mClientMemLock ); // Default to device down pGobiDev->mDownReason = 0; //#if (LINUX_VERSION_CODE < KERNEL_VERSION( 3,11,0 )) GobiSetDownReason( pGobiDev, NO_NDIS_CONNECTION ); GobiSetDownReason( pGobiDev, NET_IFACE_STOPPED ); //#endif // 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, #ifdef CONFIG_PM .suspend = GobiNetSuspend, .resume = GobiNetResume, #if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,14 )) .supports_autosuspend = true, #endif #else .suspend = NULL, .resume = NULL, #if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,14 )) .supports_autosuspend = false, #endif #endif /* CONFIG_PM */ }; /*=========================================================================== METHOD: GobiUSBNetModInit (Public Method) DESCRIPTION: Initialize module Create device class Register out usb_driver struct RETURN VALUE: int - 0 for success Negative errno for error ===========================================================================*/ 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, int, S_IRUGO | S_IWUSR ); MODULE_PARM_DESC( debug, "Debuging enabled or not" ); module_param( interruptible, int, S_IRUGO | S_IWUSR ); MODULE_PARM_DESC( interruptible, "Listen for and return on user interrupt" ); module_param( txQueueLength, int, S_IRUGO | S_IWUSR ); MODULE_PARM_DESC( txQueueLength, "Number of IP packets which may be queued up for transmit" );