nt9856x/code/driver/source/net/GobiNet/GobiUSBNet.c

2264 lines
66 KiB
C

/*===========================================================================
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 <linux/module.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/kernel.h>
#include <linux/ethtool.h>
#include <net/arp.h>
#include <net/ip.h>
#include <net/ipv6.h>
#include "Structs.h"
#include "QMIDevice.h"
#include "QMI.h"
//-----------------------------------------------------------------------------
// Definitions
//-----------------------------------------------------------------------------
// Version Information
//add new module or new feature, increase major version. fix bug, increase minor version
#define DRIVER_VERSION "Quectel_WCDMA&LTE_Linux&Android_GobiNet_Driver_V1.5.0"
#define DRIVER_AUTHOR "Qualcomm Innovation Center"
#define DRIVER_DESC "GobiNet"
// Debug flag
int quec_debug = 0;
// Allow user interrupts
//int interruptible = 1;
// Number of IP packets which may be queued up for transmit
static int txQueueLength = 100;
// Class should be created during module init, so needs to be global
static struct class * gpClass;
static const unsigned char ec20_mac[ETH_ALEN] = {0x02, 0x50, 0xf3, 0x00, 0x00, 0x00};
//static const u8 broadcast_addr[ETH_ALEN] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
//setup data call by "AT$QCRMCALL=1,1"
static uint __read_mostly qcrmcall_mode = 0;
module_param( qcrmcall_mode, uint, S_IRUGO | S_IWUSR );
static struct sk_buff * ether_to_ip_fixup(struct net_device *dev, struct sk_buff *skb) {
const struct ethhdr *ehdr;
skb_reset_mac_header(skb);
ehdr = eth_hdr(skb);
if (ehdr->h_proto == htons(ETH_P_IP)) {
if (unlikely(skb->len <= (sizeof(struct ethhdr) + sizeof(struct iphdr)))) {
goto drop_skb;
}
}
else if (ehdr->h_proto == htons(ETH_P_IPV6)) {
if (unlikely(skb->len <= (sizeof(struct ethhdr) + sizeof(struct ipv6hdr)))) {
goto drop_skb;
}
}
else {
DBG("%s skb h_proto is %04x\n", dev->name, ntohs(ehdr->h_proto));
goto drop_skb;
}
if (unlikely(skb_pull(skb, ETH_HLEN)))
return skb;
drop_skb:
return NULL;
}
//#define QUECTEL_REMOVE_TX_ZLP
#define USB_CDC_SET_REMOVE_TX_ZLP_COMMAND 0x5D
//#define QUECTEL_WWAN_MULTI_PACKAGES
#ifdef QUECTEL_WWAN_MULTI_PACKAGES
static uint __read_mostly rx_packets = 10;
module_param( rx_packets, uint, S_IRUGO | S_IWUSR );
#define USB_CDC_SET_MULTI_PACKAGE_COMMAND (0x5C)
#define QUEC_NET_MSG_SPEC (0x80)
#define QUEC_NET_MSG_ID_IP_DATA (0x00)
struct multi_package_config {
__le32 enable;
__le32 package_max_len;
__le32 package_max_count_in_queue;
__le32 timeout;
} __packed;
struct quec_net_package_header {
unsigned char msg_spec;
unsigned char msg_id;
unsigned short payload_len;
unsigned char reserve[16];
} __packed;
#endif
#ifdef CONFIG_BRIDGE
static int __read_mostly bridge_mode = 0;
module_param( bridge_mode, int, S_IRUGO | S_IWUSR );
static int bridge_arp_reply(sGobiUSBNet * pGobiDev, struct sk_buff *skb) {
struct net_device *dev = pGobiDev->mpNetDev->net;
struct arphdr *parp;
u8 *arpptr, *sha;
u8 sip[4], tip[4], ipv4[4];
struct sk_buff *reply = NULL;
ipv4[0] = (pGobiDev->m_bridge_ipv4 >> 24) & 0xFF;
ipv4[1] = (pGobiDev->m_bridge_ipv4 >> 16) & 0xFF;
ipv4[2] = (pGobiDev->m_bridge_ipv4 >> 8) & 0xFF;
ipv4[3] = (pGobiDev->m_bridge_ipv4 >> 0) & 0xFF;
parp = arp_hdr(skb);
if (parp->ar_hrd == htons(ARPHRD_ETHER) && parp->ar_pro == htons(ETH_P_IP)
&& parp->ar_op == htons(ARPOP_REQUEST) && parp->ar_hln == 6 && parp->ar_pln == 4) {
arpptr = (u8 *)parp + sizeof(struct arphdr);
sha = arpptr;
arpptr += dev->addr_len; /* sha */
memcpy(sip, arpptr, sizeof(sip));
arpptr += sizeof(sip);
arpptr += dev->addr_len; /* tha */
memcpy(tip, arpptr, sizeof(tip));
DBG("sip = %d.%d.%d.%d, tip=%d.%d.%d.%d, ipv4=%d.%d.%d.%d\n",
sip[0], sip[1], sip[2], sip[3], tip[0], tip[1], tip[2], tip[3], ipv4[0], ipv4[1], ipv4[2], ipv4[3]);
if (tip[0] == ipv4[0] && tip[1] == ipv4[1] && tip[2] == ipv4[2] && tip[3] != ipv4[3])
reply = arp_create(ARPOP_REPLY, ETH_P_ARP, *((__be32 *)sip), dev, *((__be32 *)tip), sha, ec20_mac, sha);
if (reply) {
skb_reset_mac_header(reply);
__skb_pull(reply, skb_network_offset(reply));
reply->ip_summed = CHECKSUM_UNNECESSARY;
reply->pkt_type = PACKET_HOST;
netif_rx_ni(reply);
}
return 1;
}
return 0;
}
static ssize_t bridge_mode_show(struct device *dev, struct device_attribute *attr, char *buf) {
struct net_device *pNet = to_net_dev(dev);
struct usbnet * pDev = netdev_priv( pNet );
sGobiUSBNet * pGobiDev = (sGobiUSBNet *)pDev->data[0];
return snprintf(buf, PAGE_SIZE, "%d\n", pGobiDev->m_bridge_mode);
}
static ssize_t bridge_mode_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) {
struct net_device *pNet = to_net_dev(dev);
struct usbnet * pDev = netdev_priv( pNet );
sGobiUSBNet * pGobiDev = (sGobiUSBNet *)pDev->data[0];
if (!GobiTestDownReason( pGobiDev, NET_IFACE_STOPPED )) {
INFO("please ifconfig %s down\n", pNet->name);
return -EPERM;
}
pGobiDev->m_bridge_mode = !!simple_strtoul(buf, NULL, 10);
return count;
}
static ssize_t bridge_ipv4_show(struct device *dev, struct device_attribute *attr, char *buf) {
struct net_device *pNet = to_net_dev(dev);
struct usbnet * pDev = netdev_priv( pNet );
sGobiUSBNet * pGobiDev = (sGobiUSBNet *)pDev->data[0];
unsigned char ipv4[4];
ipv4[0] = (pGobiDev->m_bridge_ipv4 >> 24) & 0xFF;
ipv4[1] = (pGobiDev->m_bridge_ipv4 >> 16) & 0xFF;
ipv4[2] = (pGobiDev->m_bridge_ipv4 >> 8) & 0xFF;
ipv4[3] = (pGobiDev->m_bridge_ipv4 >> 0) & 0xFF;
return snprintf(buf, PAGE_SIZE, "%d.%d.%d.%d\n", ipv4[0], ipv4[1], ipv4[2], ipv4[3]);
}
static ssize_t bridge_ipv4_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) {
struct net_device *pNet = to_net_dev(dev);
struct usbnet * pDev = netdev_priv( pNet );
sGobiUSBNet * pGobiDev = (sGobiUSBNet *)pDev->data[0];
pGobiDev->m_bridge_ipv4 = simple_strtoul(buf, NULL, 16);
return count;
}
static DEVICE_ATTR(bridge_mode, S_IWUSR | S_IRUGO, bridge_mode_show, bridge_mode_store);
static DEVICE_ATTR(bridge_ipv4, S_IWUSR | S_IRUGO, bridge_ipv4_show, bridge_ipv4_store);
#endif
#ifdef QUECTEL_WWAN_QMAP
/*
Quectel_WCDMA&LTE_Linux_USB_Driver_User_Guide_V1.9.pdf
5.6. Test QMAP on GobiNet or QMI WWAN
0 - no QMAP
1 - QMAP (Aggregation protocol)
X - QMAP (Multiplexing and Aggregation protocol)
*/
static uint __read_mostly qmap_mode = 0;
module_param( qmap_mode, uint, S_IRUGO | S_IWUSR );
struct qmap_hdr {
u8 cd_rsvd_pad;
u8 mux_id;
u16 pkt_len;
} __packed;
struct qmap_priv {
struct net_device *real_dev;
u8 offset_id;
};
static int qmap_open(struct net_device *dev)
{
struct qmap_priv *priv = netdev_priv(dev);
struct net_device *real_dev = priv->real_dev;
if (!(priv->real_dev->flags & IFF_UP))
return -ENETDOWN;
if (netif_carrier_ok(real_dev))
netif_carrier_on(dev);
return 0;
}
static int qmap_stop(struct net_device *pNet)
{
netif_carrier_off(pNet);
return 0;
}
static int qmap_start_xmit(struct sk_buff *skb, struct net_device *pNet)
{
int err;
struct qmap_priv *priv = netdev_priv(pNet);
unsigned int len;
struct qmap_hdr *hdr;
if (ether_to_ip_fixup(pNet, skb) == NULL) {
dev_kfree_skb_any (skb);
return NETDEV_TX_OK;
}
len = skb->len;
hdr = (struct qmap_hdr *)skb_push(skb, sizeof(struct qmap_hdr));
hdr->cd_rsvd_pad = 0;
hdr->mux_id = QUECTEL_QMAP_MUX_ID + priv->offset_id;
hdr->pkt_len = cpu_to_be16(len);
skb->dev = priv->real_dev;
err = dev_queue_xmit(skb);
#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,14 ))
if (err == NET_XMIT_SUCCESS) {
pNet->stats.tx_packets++;
pNet->stats.tx_bytes += skb->len;
} else {
pNet->stats.tx_errors++;
}
#endif
return err;
}
#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 ))
#else
static const struct net_device_ops qmap_netdev_ops = {
.ndo_open = qmap_open,
.ndo_stop = qmap_stop,
.ndo_start_xmit = qmap_start_xmit,
};
#endif
static int qmap_register_device(sGobiUSBNet * pDev, u8 offset_id)
{
struct net_device *real_dev = pDev->mpNetDev->net;
struct net_device *qmap_net;
struct qmap_priv *priv;
int err;
qmap_net = alloc_etherdev(sizeof(*priv));
if (!qmap_net)
return -ENOBUFS;
SET_NETDEV_DEV(qmap_net, &real_dev->dev);
priv = netdev_priv(qmap_net);
priv->offset_id = offset_id;
priv->real_dev = real_dev;
sprintf(qmap_net->name, "%s.%d", real_dev->name, offset_id + 1);
#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 ))
qmap_net->open = qmap_open;
qmap_net->stop = qmap_stop;
qmap_net->hard_start_xmit = qmap_start_xmit;
#else
qmap_net->netdev_ops = &qmap_netdev_ops;
#endif
memcpy (qmap_net->dev_addr, real_dev->dev_addr, ETH_ALEN);
err = register_netdev(qmap_net);
if (err < 0)
goto out_free_newdev;
netif_device_attach (qmap_net);
pDev->mpQmapNetDev[offset_id] = qmap_net;
qmap_net->flags |= IFF_NOARP;
INFO("%s\n", qmap_net->name);
return 0;
out_free_newdev:
free_netdev(qmap_net);
return err;
}
static void qmap_unregister_device(sGobiUSBNet * pDev, u8 offset_id) {
struct net_device *net = pDev->mpQmapNetDev[offset_id];
if (net != NULL) {
netif_carrier_off( net );
unregister_netdev (net);
free_netdev(net);
}
}
static ssize_t qmap_mode_show(struct device *dev, struct device_attribute *attr, char *buf) {
struct net_device *pNet = to_net_dev(dev);
struct usbnet * pDev = netdev_priv( pNet );
sGobiUSBNet * pGobiDev = (sGobiUSBNet *)pDev->data[0];
return snprintf(buf, PAGE_SIZE, "%d\n", pGobiDev->m_qmap_mode);
}
static ssize_t qmap_mode_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) {
struct net_device *pNet = to_net_dev(dev);
struct usbnet * pDev = netdev_priv( pNet );
sGobiUSBNet * pGobiDev = (sGobiUSBNet *)pDev->data[0];
int err;
unsigned int qmap_mode;
int rx_urb_size = 4096;
if (!GobiTestDownReason( pGobiDev, NET_IFACE_STOPPED )) {
INFO("please ifconfig %s down\n", pNet->name);
return -EPERM;
}
if (!pGobiDev->mbQMIReady) {
INFO("please wait qmi ready\n");
return -EBUSY;
}
qmap_mode = simple_strtoul(buf, NULL, 10);
if (pGobiDev->m_qcrmcall_mode) {
INFO("AT$QCRMCALL MODE had enabled\n");
return -EINVAL;
}
if (qmap_mode <= 0 || qmap_mode > QUECTEL_WWAN_QMAP) {
INFO("qmap_mode = %d is Invalid argument, shoule be 1 ~ %d\n", qmap_mode, QUECTEL_WWAN_QMAP);
return -EINVAL;
}
if (pGobiDev->m_qmap_mode) {
INFO("qmap_mode aleary set to %d, do not allow re-set again\n", pGobiDev->m_qmap_mode);
return -EPERM;
}
// Setup Data Format
err = QuecQMIWDASetDataFormat (pGobiDev,
qmap_mode,
&rx_urb_size);
if (err != 0)
{
return err;
}
pDev->rx_urb_size = rx_urb_size;
pGobiDev->m_qmap_mode = qmap_mode;
if (pGobiDev->m_qmap_mode > 1) {
unsigned i;
for (i = 0; i < pGobiDev->m_qmap_mode; i++) {
qmap_register_device(pGobiDev, i);
}
}
#ifdef FLAG_RX_ASSEMBLE
if (pGobiDev->m_qmap_mode)
pDev->driver_info->flags |= FLAG_RX_ASSEMBLE;
#endif
return count;
}
static DEVICE_ATTR(qmap_mode, S_IWUSR | S_IRUGO, qmap_mode_show, qmap_mode_store);
#endif
static struct attribute *gobinet_sysfs_attrs[] = {
#ifdef CONFIG_BRIDGE
&dev_attr_bridge_mode.attr,
&dev_attr_bridge_ipv4.attr,
#endif
#ifdef QUECTEL_WWAN_QMAP
&dev_attr_qmap_mode.attr,
#endif
NULL,
};
static struct attribute_group gobinet_sysfs_attr_group = {
.attrs = gobinet_sysfs_attrs,
};
#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
===========================================================================*/
static 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,18 ))
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
if (pGobiDev->m_qcrmcall_mode) {
} else {
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 );
}
int QuecGobiNetSuspend(struct usb_interface *pIntf, pm_message_t powerEvent ) {
return GobiNetSuspend(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
===========================================================================*/
static 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
if (pGobiDev->m_qcrmcall_mode) {
nRet = 0;
} else {
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 ( !test_bit(pIntf->cur_altsetting->desc.bInterfaceNumber, &pDev->driver_info->data))
{
DBG( "invalid interface %d\n",
pIntf->cur_altsetting->desc.bInterfaceNumber );
return -ENODEV;
}
if ( pIntf->cur_altsetting->desc.bInterfaceClass != 0xff)
{
struct usb_interface_descriptor *desc = &pIntf->cur_altsetting->desc;
const char *qcfg_usbnet = "UNKNOW";
if (desc->bInterfaceClass == 2 && desc->bInterfaceSubClass == 0x0e) {
qcfg_usbnet = "MBIM";
} else if (desc->bInterfaceClass == 2 && desc->bInterfaceSubClass == 0x06) {
qcfg_usbnet = "ECM";
} else if (desc->bInterfaceClass == 0xe0 && desc->bInterfaceSubClass == 1 && desc->bInterfaceProtocol == 3) {
qcfg_usbnet = "RNDIS";
}
INFO( "usbnet is %s not NDIS/RMNET!\n", qcfg_usbnet);
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 defined(QUECTEL_WWAN_MULTI_PACKAGES)
if (rx_packets && pDev->udev->descriptor.idVendor == cpu_to_le16(0x2C7C)) {
struct multi_package_config rx_config = {
.enable = cpu_to_le32(1),
.package_max_len = cpu_to_le32((1500 + sizeof(struct quec_net_package_header)) * rx_packets),
.package_max_count_in_queue = cpu_to_le32(rx_packets),
.timeout = cpu_to_le32(10*1000), //10ms
};
int ret = 0;
ret = usb_control_msg(
interface_to_usbdev(pIntf),
usb_sndctrlpipe(interface_to_usbdev(pIntf), 0),
USB_CDC_SET_MULTI_PACKAGE_COMMAND,
0x21, //USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE
1,
pIntf->cur_altsetting->desc.bInterfaceNumber,
&rx_config, sizeof(rx_config), 100);
DBG( "Quectel EC21&EC25 rx_packets=%d, ret=%d\n", rx_packets, ret);
if (ret == sizeof(rx_config)) {
pDev->rx_urb_size = le32_to_cpu(rx_config.package_max_len);
} else {
rx_packets = 0;
}
}
#endif
#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
if (qcrmcall_mode == 0 && pDev->net->sysfs_groups[0] == NULL && gobinet_sysfs_attr_group.attrs[0] != NULL) {
#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,32)) //see commit 0c509a6c9393b27a8c5a01acd4a72616206cfc24
pDev->net->sysfs_groups[1] = &gobinet_sysfs_attr_group; //see netdev_register_sysfs()
#else
pDev->net->sysfs_groups[0] = &gobinet_sysfs_attr_group;
#endif
}
if (!pDev->rx_urb_size) {
//to advoid module report mtu 1460, but rx 1500 bytes IP packets, and cause the customer's system crash
//next setting can make usbnet.c:usbnet_change_mtu() do not modify rx_urb_size according to mtu
pDev->rx_urb_size = ETH_DATA_LEN + ETH_HLEN + 6;
}
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 );
if (pGobiDev->m_qcrmcall_mode) {
} else {
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
INFO("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
===========================================================================*/
static struct sk_buff *GobiNetDriverTxFixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags)
{
sGobiUSBNet * pGobiDev = (sGobiUSBNet *)dev->data[0];
if (!pGobiDev) {
DBG( "failed to get QMIDevice\n" );
dev_kfree_skb_any(skb);
return NULL;
}
if (!pGobiDev->mbRawIPMode)
return skb;
#ifdef QUECTEL_WWAN_QMAP
if (pGobiDev->m_qmap_mode) {
struct qmap_hdr *qhdr;
if (pGobiDev->m_qmap_mode > 1) {
qhdr = (struct qmap_hdr *)skb->data;
if (qhdr->cd_rsvd_pad != 0) {
goto drop_skb;
}
if ((qhdr->mux_id&0xF0) != 0x80) {
goto drop_skb;
}
} else {
if (ether_to_ip_fixup(dev->net, skb) == NULL)
goto drop_skb;
qhdr = (struct qmap_hdr *)skb_push(skb, sizeof(struct qmap_hdr));
qhdr->cd_rsvd_pad = 0;
qhdr->mux_id = QUECTEL_QMAP_MUX_ID;
qhdr->pkt_len = cpu_to_be16(skb->len - sizeof(struct qmap_hdr));
}
return skb;
}
#endif
#ifdef CONFIG_BRIDGE
if (pGobiDev->m_bridge_mode) {
struct ethhdr *ehdr;
const struct iphdr *iph;
if (unlikely(skb->len <= ETH_ALEN))
goto drop_skb;
skb_reset_mac_header(skb);
ehdr = eth_hdr(skb);
//quec_debug = 1;
// DBG("ethhdr: ");
// PrintHex(ehdr, sizeof(struct ethhdr));
if (ehdr->h_proto == htons(ETH_P_ARP)) {
bridge_arp_reply(pGobiDev, skb);
goto drop_skb;
}
iph = ip_hdr(skb);
//DBG("iphdr: ");
//PrintHex((void *)iph, sizeof(struct iphdr));
// 1 0.000000000 0.0.0.0 255.255.255.255 DHCP 362 DHCP Request - Transaction ID 0xe7643ad7
if (ehdr->h_proto == htons(ETH_P_IP) && iph->protocol == IPPROTO_UDP && iph->saddr == 0x00000000 && iph->daddr == 0xFFFFFFFF) {
//DBG("udphdr: ");
//PrintHex(udp_hdr(skb), sizeof(struct udphdr));
//if (udp_hdr(skb)->dest == htons(67)) //DHCP Request
{
int save_debug = quec_debug;
memcpy(pGobiDev->mHostMAC, ehdr->h_source, ETH_ALEN);
INFO("PC Mac Address: ");
quec_debug=1;PrintHex(pGobiDev->mHostMAC, ETH_ALEN);quec_debug=save_debug;
}
}
#if 0
//Ethernet II, Src: DellInc_de:14:09 (f8:b1:56:de:14:09), Dst: IPv4mcast_7f:ff:fa (01:00:5e:7f:ff:fa)
//126 85.213727000 10.184.164.175 239.255.255.250 SSDP 175 M-SEARCH * HTTP/1.1
//Ethernet II, Src: DellInc_de:14:09 (f8:b1:56:de:14:09), Dst: IPv6mcast_16 (33:33:00:00:00:16)
//160 110.305488000 fe80::6819:38ad:fcdc:2444 ff02::16 ICMPv6 90 Multicast Listener Report Message v2
if (memcmp(ehdr->h_dest, ec20_mac, ETH_ALEN) && memcmp(ehdr->h_dest, broadcast_addr, ETH_ALEN)) {
DBG("Drop h_dest: ");
PrintHex(ehdr, sizeof(struct ethhdr));
dev_kfree_skb_any(skb);
return NULL;
}
#endif
if (memcmp(ehdr->h_source, pGobiDev->mHostMAC, ETH_ALEN)) {
DBG("Drop h_source: ");
PrintHex(ehdr, sizeof(struct ethhdr));
goto drop_skb;
}
//quec_debug = 0;
}
#endif
// Skip Ethernet header from message
if (likely(ether_to_ip_fixup(dev->net, skb))) {
return skb;
} else {
#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,22 ))
dev_err(&dev->intf->dev, "Packet Dropped ");
#elif (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,18 ))
dev_err(dev->net->dev.parent, "Packet Dropped ");
#else
INFO("Packet Dropped ");
#endif
}
drop_skb:
#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,24 )) && defined(CONFIG_X86_32)
INFO("dev_kfree_skb_any() will make kernel panic on CentOS!\n");
quec_debug=1;PrintHex(skb->data, 32);quec_debug=0;
#else
// Filter the packet out, release it
dev_kfree_skb_any(skb);
#endif
return NULL;
}
#if defined(QUECTEL_WWAN_MULTI_PACKAGES)
static int GobiNetDriverRxPktsFixup(struct usbnet *dev, struct sk_buff *skb)
{
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;
if (!rx_packets) {
return GobiNetDriverRxFixup(dev, skb);
}
while (likely(skb->len)) {
struct sk_buff* new_skb;
struct quec_net_package_header package_header;
if (skb->len < sizeof(package_header))
return 0;
memcpy(&package_header, skb->data, sizeof(package_header));
package_header.payload_len = be16_to_cpu(package_header.payload_len);
if (package_header.msg_spec != QUEC_NET_MSG_SPEC || package_header.msg_id != QUEC_NET_MSG_ID_IP_DATA)
return 0;
if (skb->len < (package_header.payload_len + sizeof(package_header)))
return 0;
skb_pull(skb, sizeof(package_header));
if (skb->len == package_header.payload_len)
return GobiNetDriverRxFixup(dev, skb);
new_skb = skb_clone(skb, GFP_ATOMIC);
if (new_skb) {
skb_trim(new_skb, package_header.payload_len);
if (GobiNetDriverRxFixup(dev, new_skb))
usbnet_skb_return(dev, new_skb);
else
return 0;
}
skb_pull(skb, package_header.payload_len);
}
return 0;
}
#else
#ifdef QUECTEL_WWAN_QMAP
static int GobiNetDriverRxQmapFixup(struct usbnet *dev, struct sk_buff *skb)
{
sGobiUSBNet * pGobiDev = (sGobiUSBNet *)dev->data[0];
static int debug_len = 0;
int debug_pkts = 0;
int update_len = skb->len;
while (skb->len > sizeof(struct qmap_hdr)) {
struct qmap_hdr *qhdr = (struct qmap_hdr *)skb->data;
struct net_device *qmap_net;
struct sk_buff *qmap_skb;
__be16 proto;
int pkt_len;
u8 offset_id = 0;
int err;
unsigned len = (be16_to_cpu(qhdr->pkt_len) + sizeof(struct qmap_hdr));
#if 0
quec_debug = 1;
DBG("rx: %d\n", skb->len);
PrintHex(skb->data, 16);
quec_debug = 0;
#endif
if (skb->len < (be16_to_cpu(qhdr->pkt_len) + sizeof(struct qmap_hdr))) {
INFO("drop qmap unknow pkt, len=%d, pkt_len=%d\n", skb->len, be16_to_cpu(qhdr->pkt_len));
quec_debug = 1;
PrintHex(skb->data, 16);
quec_debug = 0;
goto out;
}
debug_pkts++;
if (qhdr->cd_rsvd_pad & 0x80) {
INFO("drop qmap command packet %x\n", qhdr->cd_rsvd_pad);
goto skip_pkt;;
}
offset_id = qhdr->mux_id - QUECTEL_QMAP_MUX_ID;
if (offset_id >= pGobiDev->m_qmap_mode) {
INFO("drop qmap unknow mux_id %x\n", qhdr->mux_id);
goto skip_pkt;
}
if (pGobiDev->m_qmap_mode > 1) {
qmap_net = pGobiDev->mpQmapNetDev[offset_id];
} else {
qmap_net = dev->net;
}
if (qmap_net == NULL) {
INFO("drop qmap unknow mux_id %x\n", qhdr->mux_id);
goto skip_pkt;
}
switch (skb->data[sizeof(struct qmap_hdr)] & 0xf0) {
case 0x40:
proto = htons(ETH_P_IP);
break;
case 0x60:
proto = htons(ETH_P_IPV6);
break;
default:
goto skip_pkt;
}
pkt_len = be16_to_cpu(qhdr->pkt_len) - (qhdr->cd_rsvd_pad&0x3F);
qmap_skb = netdev_alloc_skb(qmap_net, ETH_HLEN + pkt_len);
skb_reset_mac_header(qmap_skb);
memcpy(eth_hdr(qmap_skb)->h_source, ec20_mac, ETH_ALEN);
memcpy(eth_hdr(qmap_skb)->h_dest, qmap_net->dev_addr, ETH_ALEN);
eth_hdr(qmap_skb)->h_proto = proto;
memcpy(skb_put(qmap_skb, ETH_HLEN + pkt_len) + ETH_HLEN, skb->data + sizeof(struct qmap_hdr), pkt_len);
if (pGobiDev->m_qmap_mode > 1) {
qmap_skb->protocol = eth_type_trans (qmap_skb, qmap_net);
memset(qmap_skb->cb, 0, sizeof(struct skb_data));
err = netif_rx(qmap_skb);
#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,14 ))
if (err == NET_RX_SUCCESS) {
qmap_net->stats.rx_packets++;
qmap_net->stats.rx_bytes += qmap_skb->len;
} else {
qmap_net->stats.rx_errors++;
}
#endif
} else {
usbnet_skb_return(dev, qmap_skb);
}
skip_pkt:
skb_pull(skb, len);
}
out:
if (update_len > debug_len) {
debug_len = update_len;
INFO("rx_pkts=%d, rx_len=%d\n", debug_pkts, debug_len);
}
return 0;
}
#endif
/*===========================================================================
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;
#ifdef QUECTEL_WWAN_QMAP
if (pGobiDev->m_qmap_mode) {
return GobiNetDriverRxQmapFixup(dev, skb);
}
#endif
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;
memcpy(eth_hdr(skb)->h_source, ec20_mac, ETH_ALEN);
fix_dest:
#ifdef CONFIG_BRIDGE
if (pGobiDev->m_bridge_mode) {
memcpy(eth_hdr(skb)->h_dest, pGobiDev->mHostMAC, ETH_ALEN);
//memcpy(eth_hdr(skb)->h_dest, broadcast_addr, ETH_ALEN);
} else
#endif
memcpy(eth_hdr(skb)->h_dest, dev->net->dev_addr, ETH_ALEN);
#ifdef CONFIG_BRIDGE
#if 0
if (pGobiDev->m_bridge_mode) {
struct ethhdr *ehdr = eth_hdr(skb);
quec_debug = 1;
DBG(": ");
PrintHex(ehdr, sizeof(struct ethhdr));
quec_debug = 0;
}
#endif
#endif
return 1;
}
#endif
#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,18 ))
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 );
#ifdef URB_FREE_BUFFER_BY_SELF
if (pURB->transfer_flags & URB_FREE_BUFFER)
kfree(pURB->transfer_buffer);
#endif
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,18 ))
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 ))
{
// 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(pDev, 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 ))
{
// 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
===========================================================================*/
static 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
===========================================================================*/
static 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 struct driver_info GobiNetInfo =
{
.description = "GobiNet Ethernet Device",
#ifdef CONFIG_ANDROID
.flags = FLAG_ETHER | FLAG_POINTTOPOINT, //usb0
#else
.flags = FLAG_ETHER,
#endif
.bind = GobiNetDriverBind,
.unbind = GobiNetDriverUnbind,
#if 1 //def DATA_MODE_RP
#if defined(QUECTEL_WWAN_MULTI_PACKAGES)
.rx_fixup = GobiNetDriverRxPktsFixup,
#else
.rx_fixup = GobiNetDriverRxFixup,
#endif
.tx_fixup = GobiNetDriverTxFixup,
#endif
.data = (1 << 4),
};
/*=========================================================================*/
// Qualcomm Gobi 3000 VID/PIDs
/*=========================================================================*/
#define GOBI_FIXED_INTF(vend, prod) \
{ \
USB_DEVICE( vend, prod ), \
.driver_info = (unsigned long)&GobiNetInfo, \
}
static const struct usb_device_id QuecGobiVIDPIDTable [] =
{
GOBI_FIXED_INTF( 0x05c6, 0x9003 ), // Quectel UC20
GOBI_FIXED_INTF( 0x05c6, 0x9215 ), // Quectel EC20
GOBI_FIXED_INTF( 0x2c7c, 0x0125 ), // Quectel EC25
GOBI_FIXED_INTF( 0x2c7c, 0x0121 ), // Quectel EC21
GOBI_FIXED_INTF( 0x2c7c, 0x0306 ), // Quectel EP06
GOBI_FIXED_INTF( 0x2c7c, 0x0435 ), // Quectel AG35
GOBI_FIXED_INTF( 0x2c7c, 0x0296 ), // Quectel BG96
GOBI_FIXED_INTF( 0x2c7c, 0x0191 ), // Quectel EG91
GOBI_FIXED_INTF( 0x2c7c, 0x0195 ), // Quectel EG95
GOBI_FIXED_INTF( 0x2c7c, 0x0512 ), // Quectel EG12/EP12/EM12/EG16/EG18,SDx20
GOBI_FIXED_INTF( 0x2c7c, 0x0620 ), // Quectel EG20,SDx24
GOBI_FIXED_INTF( 0x2c7c, 0x0800 ), // Quectel RG500Q,RM500Q,RM510Q,SDX55
//Terminating entry
{ }
};
MODULE_DEVICE_TABLE( usb, QuecGobiVIDPIDTable );
/*===========================================================================
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
===========================================================================*/
static 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
pGobiDev->mbMdm9x07 |= (pDev->udev->descriptor.idVendor == cpu_to_le16(0x2c7c));
pGobiDev->mbMdm9x06 |= (pDev->udev->descriptor.idVendor == cpu_to_le16(0x2c7c) && pDev->udev->descriptor.idProduct == cpu_to_le16(0x0296));
pGobiDev->mbRawIPMode = pGobiDev->mbMdm9x07;
if ( pGobiDev->mbRawIPMode)
pGobiDev->mpNetDev->net->flags |= IFF_NOARP;
#ifdef CONFIG_BRIDGE
memcpy(pGobiDev->mHostMAC, pDev->net->dev_addr, 6);
pGobiDev->m_bridge_mode = bridge_mode;
#endif
#ifdef QUECTEL_REMOVE_TX_ZLP
{
struct remove_tx_zlp_config {
__le32 enable;
} __packed;
struct remove_tx_zlp_config cfg;
cfg.enable = cpu_to_le32(1); //1-enable 0-disable
usb_control_msg(
interface_to_usbdev(pIntf),
usb_sndctrlpipe(interface_to_usbdev(pIntf), 0),
USB_CDC_SET_REMOVE_TX_ZLP_COMMAND,
0x21, //USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE
0,
pIntf->cur_altsetting->desc.bInterfaceNumber,
&cfg, sizeof(cfg), 100);
}
#endif
pGobiDev->m_qcrmcall_mode = qcrmcall_mode;
if (pGobiDev->m_qcrmcall_mode) {
INFO("AT$QCRMCALL MODE!");
GobiClearDownReason( pGobiDev, NO_NDIS_CONNECTION );
usb_control_msg(
interface_to_usbdev(pIntf),
usb_sndctrlpipe(interface_to_usbdev(pIntf), 0),
0x22, //USB_CDC_REQ_SET_CONTROL_LINE_STATE
0x21, //USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE
1, //active CDC DTR
pIntf->cur_altsetting->desc.bInterfaceNumber,
NULL, 0, 100);
status = 0;
} else {
if (pGobiDev->mbRawIPMode) {
pGobiDev->m_qmap_mode = qmap_mode;
}
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;
}
#if defined(QUECTEL_WWAN_QMAP)
if (pGobiDev->m_qmap_mode > 1) {
unsigned i;
for (i = 0; i < pGobiDev->m_qmap_mode; i++) {
qmap_register_device(pGobiDev, i);
}
}
#endif
#ifdef FLAG_RX_ASSEMBLE
if (pGobiDev->m_qmap_mode)
pDev->driver_info->flags |= FLAG_RX_ASSEMBLE;
#endif
// Success
return 0;
}
static void GobiUSBNetDisconnect (struct usb_interface *intf) {
#if defined(QUECTEL_WWAN_QMAP)
struct usbnet *pDev = usb_get_intfdata(intf);
sGobiUSBNet * pGobiDev = (sGobiUSBNet *)pDev->data[0];
unsigned i;
for (i = 0; i < pGobiDev->m_qmap_mode; i++) {
qmap_unregister_device(pGobiDev, i);
}
#endif
usbnet_disconnect(intf);
}
static struct usb_driver GobiNet =
{
.name = "GobiNet",
.id_table = QuecGobiVIDPIDTable,
.probe = GobiUSBNetProbe,
.disconnect = GobiUSBNetDisconnect,
#ifdef CONFIG_PM
.suspend = GobiNetSuspend,
.resume = GobiNetResume,
#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,18 ))
.supports_autosuspend = true,
#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_named( debug, quec_debug, int, S_IRUGO | S_IWUSR );
MODULE_PARM_DESC( debug, "Debuging enabled or not" );
//module_param_named( interruptible, Quecinterruptible, 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" );