VIM3/3L: add 4G modem EM06 support
authorNick Xie <nick@khadas.com>
Tue, 28 Apr 2020 08:45:20 +0000 (16:45 +0800)
committerNick Xie <nick@khadas.com>
Tue, 28 Apr 2020 08:51:16 +0000 (16:51 +0800)
Signed-off-by: Nick Xie <nick@khadas.com>
drivers/net/usb/GobiUSBNet.c [new file with mode: 0644]
drivers/net/usb/Makefile
drivers/net/usb/QMI.c [new file with mode: 0644]
drivers/net/usb/QMI.h [new file with mode: 0644]
drivers/net/usb/QMIDevice.c [new file with mode: 0644]
drivers/net/usb/QMIDevice.h [new file with mode: 0644]
drivers/net/usb/Structs.h [new file with mode: 0644]
drivers/usb/serial/option.c
drivers/usb/serial/usb_wwan.c

diff --git a/drivers/net/usb/GobiUSBNet.c b/drivers/net/usb/GobiUSBNet.c
new file mode 100644 (file)
index 0000000..5baec11
--- /dev/null
@@ -0,0 +1,2364 @@
+/*===========================================================================
+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_Linux&Android_GobiNet_Driver_V1.6.1"
+#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);
+}
+
+#if 1 
+static DEVICE_ATTR(qmap_mode, S_IRUGO, qmap_mode_show, NULL);
+
+static ssize_t qmap_size_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->qmap_size);
+}
+
+static DEVICE_ATTR(qmap_size, S_IRUGO, qmap_size_show, NULL);
+#else
+
+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);
+        }
+    }
+
+    return count;
+}
+
+static DEVICE_ATTR(qmap_mode, S_IWUSR | S_IRUGO, qmap_mode_show, qmap_mode_store);
+#endif
+
+static ssize_t link_state_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 * pQmapDev = (sGobiUSBNet *)pDev->data[0];
+
+       return snprintf(buf, PAGE_SIZE, "0x%x\n",  pQmapDev->link_state);
+}
+
+static ssize_t link_state_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 * pQmapDev = (sGobiUSBNet *)pDev->data[0];
+       unsigned qmap_mode = pQmapDev->m_qmap_mode;
+       unsigned link_state = 0;
+       unsigned old_link = pQmapDev->link_state;
+
+       link_state = simple_strtoul(buf, NULL, 0);
+       if (qmap_mode == 1)
+               pQmapDev->link_state = !!link_state;
+       else if (qmap_mode > 1) {
+               if (0 < link_state && link_state <= qmap_mode)
+                       pQmapDev->link_state |= (1 << (link_state - 1));
+               else if (0x80 < link_state && link_state <= (0x80 + qmap_mode))
+                       pQmapDev->link_state &= ~(1 << ((link_state&0xF) - 1));
+       }
+
+       if (old_link != pQmapDev->link_state)
+               dev_info(dev, "link_state 0x%x -> 0x%x\n", old_link, pQmapDev->link_state);
+
+       return count;
+}
+
+static DEVICE_ATTR(link_state, S_IWUSR | S_IRUGO, link_state_show, link_state_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,
+       &dev_attr_qmap_size.attr,
+       &dev_attr_link_state.attr,
+#endif
+       NULL,
+};
+
+static struct attribute_group gobinet_sysfs_attr_group = {
+       .attrs = gobinet_sysfs_attrs,
+};
+
+#if defined(QUECTEL_WWAN_QMAP) && defined(CONFIG_ANDROID)
+static int qmap_ndo_do_ioctl(struct net_device *dev,struct ifreq *ifr, int cmd) {
+       int rc = -EOPNOTSUPP;
+       uint link_state = 0;
+
+       switch (cmd) {
+       case 0x89F1: //SIOCDEVPRIVATE
+               rc = copy_from_user(&link_state, ifr->ifr_ifru.ifru_data, sizeof(link_state));
+               if (!rc) {
+                       char buf[32];
+                       snprintf(buf, sizeof(buf), "%u", link_state);
+                       link_state_store(&dev->dev, NULL, buf, strlen(buf));
+               }
+       break;
+
+       default:
+       break;
+       }
+
+       return rc;
+}
+#endif
+
+#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;
+}
+#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,27 ))   
+static int GobiNetResetResume( struct usb_interface * pIntf )
+{
+   INFO( "device do not support reset_resume\n" );
+   pIntf->needs_binding = 1;
+   
+   return -EOPNOTSUPP;
+}
+#endif
+#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 (unlikely(!pGobiDev->link_state)) {
+           dev_info(&dev->net->dev, "link_state 0x%x, drop skb, len = %u\n", pGobiDev->link_state, skb->len);
+           goto drop_skb;
+        }
+
+        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
+    }
+#if defined(QUECTEL_WWAN_QMAP)    
+drop_skb:
+#endif
+#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
+#if defined(QUECTEL_WWAN_QMAP) && defined(FLAG_RX_ASSEMBLE)
+       .flags         = FLAG_ETHER | FLAG_POINTTOPOINT | FLAG_RX_ASSEMBLE, //usb0
+#else
+       .flags         = FLAG_ETHER | FLAG_POINTTOPOINT, //usb0
+#endif
+#else
+#if defined(QUECTEL_WWAN_QMAP) && defined(FLAG_RX_ASSEMBLE)
+       .flags         = FLAG_ETHER | FLAG_RX_ASSEMBLE,
+#else
+       .flags         = FLAG_ETHER,
+#endif
+#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 (MDM9215)
+    GOBI_FIXED_INTF( 0x2c7c, 0x0125 ), // Quectel EC20 (MDM9X07)/EC25/EG25
+    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;
+
+#if defined(QUECTEL_WWAN_QMAP) && defined(CONFIG_ANDROID)
+   pNetDevOps->ndo_do_ioctl = qmap_ndo_do_ioctl;
+#endif
+
+   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 defined(QUECTEL_WWAN_QMAP)
+      if (pGobiDev->mbRawIPMode) {
+         pGobiDev->m_qmap_mode = qmap_mode;
+         if (pGobiDev->m_qmap_mode == 0) {
+               unsigned idProduct = le16_to_cpu(pDev->udev->descriptor.idProduct);
+               if (idProduct == 0x0800)
+                       pGobiDev->m_qmap_mode = 1;
+         }              
+      }
+#endif
+      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
+
+   // 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,27 ))   
+   .reset_resume = GobiNetResetResume,
+#endif   
+#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" );
+
index 37fb46aee341a77bf734ea55999fd70c9b8eac3a..06ecb45e12ba0eb498898394fa16ca5dadcd8d77 100644 (file)
@@ -39,3 +39,6 @@ obj-$(CONFIG_USB_VL600)               += lg-vl600.o
 obj-$(CONFIG_USB_NET_QMI_WWAN) += qmi_wwan.o
 obj-$(CONFIG_USB_NET_CDC_MBIM) += cdc_mbim.o
 obj-$(CONFIG_USB_NET_CH9200)   += ch9200.o
+
+obj-y += GobiNet.o
+GobiNet-objs := GobiUSBNet.o QMIDevice.o QMI.o
diff --git a/drivers/net/usb/QMI.c b/drivers/net/usb/QMI.c
new file mode 100644 (file)
index 0000000..909cd13
--- /dev/null
@@ -0,0 +1,1507 @@
+#ifdef __QUEC_INCLUDE_QMI_C__
+/*===========================================================================
+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
+      QMIWDASetDataFormatReq
+      QMICTLSetDataFormatReq
+      QMICTLSyncReq
+      
+   Parse data from QMI responses
+      QMICTLGetClientIDResp
+      QMICTLReleaseClientIDResp
+      QMIWDSEventResp
+      QMIDMSGetMEIDResp
+      QMIWDASetDataFormatResp
+      QMICTLSyncResp
+
+Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of Code Aurora Forum nor
+      the names of its contributors may be used to endorse or promote
+      products derived from this software without specific prior written
+      permission.
+
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+===========================================================================*/
+
+//---------------------------------------------------------------------------
+// Include Files
+//---------------------------------------------------------------------------
+#include <asm/unaligned.h>
+#include <linux/kernel.h>
+#include "Structs.h"
+#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
+===========================================================================*/
+static 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
+===========================================================================*/
+static 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
+===========================================================================*/
+static 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
+===========================================================================*/
+static 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
+===========================================================================*/
+static 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
+===========================================================================*/
+static 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
+===========================================================================*/
+static u16 QMIDMSGetMEIDReqSize( void )
+{
+   return sizeof( sQMUX ) + 7;
+}
+
+struct QMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS
+{
+   u8  TLVType;
+   u16 TLVLength;
+   u8  QOSSetting;
+} __packed;
+
+struct QMIWDS_ADMIN_SET_DATA_FORMAT_TLV
+{
+   u8  TLVType;
+   u16 TLVLength;
+   u32  Value;
+} __packed;
+
+struct QMIWDS_ENDPOINT_TLV
+{
+   u8  TLVType;
+   u16 TLVLength;
+   u32  ep_type;
+   u32  iface_id;
+} __packed;
+
+struct QMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG
+{
+    u8  CtlFlags;      // 0: single QMUX Msg; 1:
+    u16 TransactionId;
+    u16 Type;
+    u16 Length;
+    struct QMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS QosDataFormatTlv;
+    struct QMIWDS_ADMIN_SET_DATA_FORMAT_TLV UnderlyingLinkLayerProtocolTlv;
+    struct QMIWDS_ADMIN_SET_DATA_FORMAT_TLV UplinkDataAggregationProtocolTlv;
+    struct QMIWDS_ADMIN_SET_DATA_FORMAT_TLV DownlinkDataAggregationProtocolTlv;
+    struct QMIWDS_ADMIN_SET_DATA_FORMAT_TLV DownlinkDataAggregationMaxDatagramsTlv;
+    struct QMIWDS_ADMIN_SET_DATA_FORMAT_TLV DownlinkDataAggregationMaxSizeTlv;
+    struct QMIWDS_ENDPOINT_TLV epTlv;
+    //struct QMIWDS_ADMIN_SET_DATA_FORMAT_TLV  dl_minimum_padding;
+} __packed;
+
+/*===========================================================================
+METHOD:
+   QMIWDASetDataFormatReqSize (Public Method)
+
+DESCRIPTION:
+   Get size of buffer needed for QMUX + QMIWDASetDataFormatReq
+
+RETURN VALUE:
+   u16 - size of buffer
+===========================================================================*/
+static u16 QMIWDASetDataFormatReqSize( int qmap_mode )
+{
+if (qmap_mode)
+   return sizeof( sQMUX ) + sizeof(struct QMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG);
+else
+   return sizeof( sQMUX ) + 18;
+}
+
+/*===========================================================================
+METHOD:
+   QMICTLSyncReqSize (Public Method)
+
+DESCRIPTION:
+   Get size of buffer needed for QMUX + QMICTLSyncReq
+RETURN VALUE:
+   u16 - size of buffer
+===========================================================================*/
+static u16  QMICTLSyncReqSize( void )
+{
+   return sizeof( sQMUX ) + 6; 
+}
+
+/*=========================================================================*/
+// 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
+===========================================================================*/
+static 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
+   ||  le16_to_cpu(get_unaligned(&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
+===========================================================================*/
+static 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;
+   put_unaligned(cpu_to_le16(buffSize - 1), &pQMUXHeader->mLength);
+   //DBG("pQMUXHeader->mLength = 0x%x, buffSize - 1 = 0x%x\n",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 buffer 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
+===========================================================================*/
+static int 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 = le16_to_cpu( get_unaligned(((u16 *)(pQMIMessage + pos + 1) )) );
+      if (*(u8 *)(pQMIMessage + pos) == type)
+      {
+         if (bufferLen < tlvSize)
+         {
+            return -ENOMEM;
+         }
+                
+         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
+===========================================================================*/
+static 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 le16_to_cpu( get_unaligned(&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
+===========================================================================*/
+static int GetQMIMessageID(
+   void *   pQMIMessage,
+   u16      messageLen )
+{
+   if (messageLen < 2)
+   {
+      return -ENODATA;
+   }
+   else
+   {
+      return le16_to_cpu( get_unaligned((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
+===========================================================================*/
+static 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
+   put_unaligned(cpu_to_le16(0x0022), (u16 *)(pBuffer + sizeof( sQMUX ) + 2));
+   // Size of TLV's
+   put_unaligned(cpu_to_le16(0x0004), (u16 *)(pBuffer + sizeof( sQMUX ) + 4));
+      // QMI Service Type
+      *(u8 *)(pBuffer + sizeof( sQMUX ) + 6)  = 0x01;
+      // Size
+   put_unaligned(cpu_to_le16(0x0001), (u16 *)(pBuffer + sizeof( sQMUX ) + 7));
+      // 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
+===========================================================================*/
+static int QMICTLReleaseClientIDReq(
+   void *   pBuffer,
+   u16      buffSize,
+   u8       transactionID,
+   u16      clientID )
+{
+   if (pBuffer == 0 || buffSize < QMICTLReleaseClientIDReqSize() )
+   {
+      return -ENOMEM;
+   }
+
+   DBG(  "buffSize: 0x%x, transactionID: 0x%x, clientID: 0x%x,\n",
+         buffSize, transactionID, clientID );
+
+   // QMI CTL RELEASE CLIENT ID REQ
+   // Request
+   *(u8 *)(pBuffer + sizeof( sQMUX ))  = 0x00;
+   // Transaction ID
+   *(u8 *)(pBuffer + sizeof( sQMUX ) + 1 ) = transactionID;
+   // Message ID
+   put_unaligned( cpu_to_le16(0x0023), (u16 *)(pBuffer + sizeof( sQMUX ) + 2) );
+   // Size of TLV's
+   put_unaligned( cpu_to_le16(0x0005), (u16 *)(pBuffer + sizeof( sQMUX ) + 4) );
+      // Release client ID
+      *(u8 *)(pBuffer + sizeof( sQMUX ) + 6)  = 0x01;
+      // Size
+   put_unaligned( cpu_to_le16(0x0002), (u16 *)(pBuffer + sizeof( sQMUX ) + 7));
+      // QMI svs type / Client ID
+   put_unaligned(cpu_to_le16(clientID), (u16 *)(pBuffer + sizeof( sQMUX ) + 9));
+      
+   // 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
+===========================================================================*/
+static int QMICTLReadyReq(
+   void *   pBuffer,
+   u16      buffSize,
+   u8       transactionID )
+{
+   if (pBuffer == 0 || buffSize < QMICTLReadyReqSize() )
+   {
+      return -ENOMEM;
+   }
+
+   DBG("buffSize: 0x%x, transactionID: 0x%x\n", buffSize, transactionID);
+
+   // QMI CTL GET VERSION INFO REQ
+   // Request
+   *(u8 *)(pBuffer + sizeof( sQMUX ))  = 0x00;
+   // Transaction ID
+   *(u8 *)(pBuffer + sizeof( sQMUX ) + 1) = transactionID;
+   // Message ID
+   put_unaligned( cpu_to_le16(0x0021), (u16 *)(pBuffer + sizeof( sQMUX ) + 2) );
+   // Size of TLV's
+   put_unaligned( cpu_to_le16(0x0000), (u16 *)(pBuffer + sizeof( sQMUX ) + 4) );
+
+   // 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
+===========================================================================*/
+static 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
+   put_unaligned( cpu_to_le16(transactionID), (u16 *)(pBuffer + sizeof( sQMUX ) + 1));
+   // Message ID
+   put_unaligned( cpu_to_le16(0x0001), (u16 *)(pBuffer + sizeof( sQMUX ) + 3));
+   // Size of TLV's
+   put_unaligned(cpu_to_le16(0x0008), (u16 *)(pBuffer + sizeof( sQMUX ) + 5));
+      // Report channel rate TLV
+      *(u8 *)(pBuffer + sizeof( sQMUX ) + 7)  = 0x11;
+      // Size
+   put_unaligned( cpu_to_le16(0x0005), (u16 *)(pBuffer + sizeof( sQMUX ) + 8));
+      // Stats period
+      *(u8 *)(pBuffer + sizeof( sQMUX ) + 10)  = 0x01;
+      // Stats mask
+   put_unaligned( cpu_to_le32(0x000000ff), (u32 *)(pBuffer + sizeof( sQMUX ) + 11) );
+
+   // 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
+===========================================================================*/
+static 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
+   put_unaligned(cpu_to_le16(transactionID), (u16 *)(pBuffer + sizeof( sQMUX ) + 1));
+   // Message ID
+   put_unaligned(cpu_to_le16(0x0022), (u16 *)(pBuffer + sizeof( sQMUX ) + 3));
+   // Size of TLV's
+   put_unaligned(cpu_to_le16(0x0000), (u16 *)(pBuffer + sizeof( sQMUX ) + 5));
+
+   // success
+   return sizeof( sQMUX ) + 7;
+}
+
+#if 0
+static u16 QMIWDSSetQMUXBindMuxDataPortSize( void )
+{
+   return sizeof( sQMUX ) + 29;
+}
+
+static u16 QMIWDSSetQMUXBindMuxDataPortReq(
+   void *   pBuffer,
+   u16      buffSize,
+   u8 MuxId,
+   u16      transactionID )
+{
+   if (pBuffer == 0 || buffSize < QMIWDSSetQMUXBindMuxDataPortSize() )
+   {
+      return -ENOMEM;
+   }
+
+   // QMI WDS Set QMUX Bind Mux Data Port REQ
+   // Request
+   *(u8 *)(pBuffer + sizeof( sQMUX ))  = 0x00;
+   // Transaction ID
+   put_unaligned(cpu_to_le16(transactionID), (u16 *)(pBuffer + sizeof( sQMUX ) + 1));
+   // Message ID
+   put_unaligned(cpu_to_le16(0x00a2), (u16 *)(pBuffer + sizeof( sQMUX ) + 3));
+   // Size of TLV's
+   put_unaligned(cpu_to_le16(0x0016), (u16 *)(pBuffer + sizeof( sQMUX ) + 5));
+
+   *(u8 *)(pBuffer + sizeof( sQMUX ) +  7) = 0x10;
+   put_unaligned(cpu_to_le16(0x08), (u16 *)(pBuffer + sizeof( sQMUX ) + 8));
+   put_unaligned(cpu_to_le32(0x02), (u32 *)(pBuffer + sizeof( sQMUX ) + 10)); // ep_type
+   put_unaligned(cpu_to_le32(0x04), (u32 *)(pBuffer + sizeof( sQMUX ) + 14)); // iface_id
+   
+   *(u8 *)(pBuffer + sizeof( sQMUX ) +  18) = 0x11;
+   put_unaligned(cpu_to_le16(0x01), (u16 *)(pBuffer + sizeof( sQMUX ) + 19));
+   *(u8 *)(pBuffer + sizeof( sQMUX ) +  21) = MuxId;  // MuxId
+   
+   *(u8 *)(pBuffer + sizeof( sQMUX ) +  22) = 0x13;
+   put_unaligned(cpu_to_le16(0x04), (u16 *)(pBuffer + sizeof( sQMUX ) + 23));
+   put_unaligned(cpu_to_le32(0x01), (u32 *)(pBuffer + sizeof( sQMUX ) + 25));
+       
+   // success
+   return sizeof( sQMUX ) + 29;
+}
+#endif
+
+/*===========================================================================
+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
+===========================================================================*/
+static 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
+   put_unaligned( cpu_to_le16(transactionID), (u16 *)(pBuffer + sizeof( sQMUX ) + 1) );
+   // Message ID
+   put_unaligned( cpu_to_le16(0x0025), (u16 *)(pBuffer + sizeof( sQMUX ) + 3) );
+   // Size of TLV's
+   put_unaligned( cpu_to_le16(0x0000), (u16 *)(pBuffer + sizeof( sQMUX ) + 5));
+
+   // success
+   return sizeof( sQMUX ) + 7;
+}
+
+/*===========================================================================
+METHOD:
+   QMIWDASetDataFormatReq (Public Method)
+
+DESCRIPTION:
+   Fill buffer with QMI WDA Set Data Format 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
+===========================================================================*/
+static int QMIWDASetDataFormatReq(
+   void *   pBuffer,
+   u16      buffSize,
+   bool     bRawIPMode, int qmap_mode, u32 rx_size,
+   u16      transactionID )
+{
+if (qmap_mode) {
+    struct QMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG *pMUXMsg = (struct QMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG *)(pBuffer + sizeof( sQMUX ));
+
+    pMUXMsg->CtlFlags = 0x00;
+    put_unaligned( cpu_to_le16(transactionID), &pMUXMsg->TransactionId);
+    put_unaligned( cpu_to_le16(0x0020), &pMUXMsg->Type);
+    put_unaligned( cpu_to_le16(sizeof( struct QMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG) - 7), &pMUXMsg->Length);
+
+ //Indicates whether the Quality of Service(QOS) data format is used by the client.
+    pMUXMsg->QosDataFormatTlv.TLVType = 0x10;
+    pMUXMsg->QosDataFormatTlv.TLVLength = cpu_to_le16(0x0001);
+    pMUXMsg->QosDataFormatTlv.QOSSetting = 0; /* no-QOS header */
+//Underlying Link Layer Protocol
+    pMUXMsg->UnderlyingLinkLayerProtocolTlv.TLVType = 0x11; 
+    pMUXMsg->UnderlyingLinkLayerProtocolTlv.TLVLength = cpu_to_le16(4);
+    pMUXMsg->UnderlyingLinkLayerProtocolTlv.Value = cpu_to_le32(0x02);     /* Set Ethernet  mode */
+//Uplink (UL) data aggregation protocol to be used for uplink data transfer.
+    pMUXMsg->UplinkDataAggregationProtocolTlv.TLVType = 0x12; 
+    pMUXMsg->UplinkDataAggregationProtocolTlv.TLVLength = cpu_to_le16(4);
+    pMUXMsg->UplinkDataAggregationProtocolTlv.Value = cpu_to_le32(0x05); //UL QMAP is enabled
+//Downlink (DL) data aggregation protocol to be used for downlink data transfer
+    pMUXMsg->DownlinkDataAggregationProtocolTlv.TLVType = 0x13; 
+    pMUXMsg->DownlinkDataAggregationProtocolTlv.TLVLength = cpu_to_le16(4);
+    pMUXMsg->DownlinkDataAggregationProtocolTlv.Value = cpu_to_le32(0x05); //UL QMAP is enabled
+//Maximum number of datagrams in a single aggregated packet on downlink
+    pMUXMsg->DownlinkDataAggregationMaxDatagramsTlv.TLVType = 0x15; 
+    pMUXMsg->DownlinkDataAggregationMaxDatagramsTlv.TLVLength = cpu_to_le16(4);
+    pMUXMsg->DownlinkDataAggregationMaxDatagramsTlv.Value = cpu_to_le32(rx_size/1024);
+//Maximum size in bytes of a single aggregated packet allowed on downlink
+    pMUXMsg->DownlinkDataAggregationMaxSizeTlv.TLVType = 0x16; 
+    pMUXMsg->DownlinkDataAggregationMaxSizeTlv.TLVLength = cpu_to_le16(4);
+    pMUXMsg->DownlinkDataAggregationMaxSizeTlv.Value = cpu_to_le32(rx_size);
+//Peripheral End Point ID
+    pMUXMsg->epTlv.TLVType = 0x17; 
+    pMUXMsg->epTlv.TLVLength = cpu_to_le16(8);
+    pMUXMsg->epTlv.ep_type = cpu_to_le32(0x02); // DATA_EP_TYPE_BAM_DMUX
+    pMUXMsg->epTlv.iface_id = cpu_to_le32(0x04); 
+
+#if 0
+//Specifies the minimum padding bytes to be added in between aggregated downlink QMAP packets.
+    pMUXMsg->dl_minimum_padding.TLVType = 0x19; 
+    pMUXMsg->dl_minimum_padding.TLVLength = cpu_to_le16(4);
+    pMUXMsg->dl_minimum_padding.Value = cpu_to_le32(0);
+#endif
+
+}
+else {
+   if (pBuffer == 0 || buffSize < QMIWDASetDataFormatReqSize(qmap_mode) )
+   {
+      return -ENOMEM;
+   }
+
+   // QMI WDA SET DATA FORMAT REQ
+   // Request
+   *(u8 *)(pBuffer + sizeof( sQMUX ))  = 0x00;
+
+   // Transaction ID
+   put_unaligned( cpu_to_le16(transactionID), (u16 *)(pBuffer + sizeof( sQMUX ) + 1) );
+
+   // Message ID
+   put_unaligned( cpu_to_le16(0x0020), (u16 *)(pBuffer + sizeof( sQMUX ) + 3) );
+
+   // Size of TLV's
+   put_unaligned( cpu_to_le16(0x000b), (u16 *)(pBuffer + sizeof( sQMUX ) + 5));
+
+   /* TLVType QOS Data Format 1 byte  */
+   *(u8 *)(pBuffer + sizeof( sQMUX ) +  7) = 0x10; // type data format
+
+   /* TLVLength  2 bytes - see spec */
+   put_unaligned( cpu_to_le16(0x0001), (u16 *)(pBuffer + sizeof( sQMUX ) + 8)); 
+
+   /* DataFormat: 0-default; 1-QoS hdr present 2 bytes */
+#ifdef QOS_MODE
+   *(u8 *)(pBuffer + sizeof( sQMUX ) + 10) = 1; /* QOS header */
+#else
+   *(u8 *)(pBuffer + sizeof( sQMUX ) + 10) = 0; /* no-QOS header */
+#endif
+
+   /* TLVType Link-Layer Protocol  (Optional) 1 byte */
+   *(u8 *)(pBuffer + sizeof( sQMUX ) + 11) = 0x11;
+
+   /* TLVLength 2 bytes */
+   put_unaligned( cpu_to_le16(0x0004), (u16 *)(pBuffer + sizeof( sQMUX ) + 12));
+
+   /* LinkProt: 0x1 - ETH; 0x2 - rawIP  4 bytes */
+if (bRawIPMode) { //#ifdef DATA_MODE_RP
+   /* Set RawIP mode */
+   put_unaligned( cpu_to_le32(0x00000002), (u32 *)(pBuffer + sizeof( sQMUX ) + 14));
+   DBG("Request RawIP Data Format\n");
+} else { //#else
+   /* Set Ethernet  mode */
+   put_unaligned( cpu_to_le32(0x00000001), (u32 *)(pBuffer + sizeof( sQMUX ) + 14));
+   DBG("Request Ethernet Data Format\n");
+} //#endif
+
+}
+
+   // success
+   return QMIWDASetDataFormatReqSize(qmap_mode);
+}
+
+#if 0
+static int QMIWDASetDataQmapReq(
+               void *   pBuffer,
+               u16      buffSize,
+               u16      transactionID )
+{
+    // QMI WDA SET DATA FORMAT REQ
+    // Request
+    *(u8 *)(pBuffer + sizeof( sQMUX ))  = 0x00;
+
+    // Transaction ID
+    put_unaligned( cpu_to_le16(transactionID), (u16 *)(pBuffer + sizeof( sQMUX ) + 1) );
+
+    // Message ID
+    put_unaligned( cpu_to_le16(0x002B), (u16 *)(pBuffer + sizeof( sQMUX ) + 3) );
+
+    // Size of TLV's
+    put_unaligned( cpu_to_le16(0x0004), (u16 *)(pBuffer + sizeof( sQMUX ) + 5));
+
+    /* TLVType QMAP In-Band Flow Control 1 byte  */
+    *(u8 *)(pBuffer + sizeof( sQMUX ) +  7) = 0x10;
+    put_unaligned( cpu_to_le16(0x0001), (u16 *)(pBuffer + sizeof( sQMUX ) + 8)); 
+    *(u8 *)(pBuffer + sizeof( sQMUX ) +  10) = 0x01;
+
+    // success
+    return ( sizeof( sQMUX ) + 11);
+}
+#endif
+
+#if 0
+/*===========================================================================
+METHOD:
+   QMICTLSetDataFormatReqSize (Public Method)
+
+DESCRIPTION:
+   Get size of buffer needed for QMUX + QMICTLSetDataFormatReq
+RETURN VALUE:
+   u16 - size of buffer
+===========================================================================*/
+static u16  QMICTLSetDataFormatReqSize( void )
+{
+   return sizeof( sQMUX ) + 15; 
+}
+
+/*===========================================================================
+METHOD:
+   QMICTLSetDataFormatReq (Public Method)
+
+DESCRIPTION:
+   Fill buffer with QMI CTL Set Data Format 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
+===========================================================================*/
+static int QMICTLSetDataFormatReq(
+   void *   pBuffer,
+   u16      buffSize,
+   u8       transactionID )
+{
+   if (pBuffer == 0 || buffSize < QMICTLSetDataFormatReqSize() )
+   {
+      return -ENOMEM;
+   }
+
+   /* QMI CTL Set Data Format Request */
+   /* Request */
+   *(u8 *)(pBuffer + sizeof( sQMUX ))  = 0x00; // QMICTL_FLAG_REQUEST
+   
+   /* Transaction ID 1 byte */
+   *(u8 *)(pBuffer + sizeof( sQMUX ) + 1) = transactionID; /* 1 byte as in spec */
+
+   /* QMICTLType  2 bytes */
+   put_unaligned( cpu_to_le16(0x0026), (u16 *)(pBuffer + sizeof( sQMUX ) + 2));
+
+   /* Length  2 bytes  of 2 TLVs  each - see spec */
+   put_unaligned( cpu_to_le16(0x0009), (u16 *)(pBuffer + sizeof( sQMUX ) + 4));
+
+   /* TLVType Data Format (Mandatory)  1 byte  */
+   *(u8 *)(pBuffer + sizeof( sQMUX ) +  6) = 0x01; // type data format
+
+   /* TLVLength  2 bytes - see spec */
+   put_unaligned( cpu_to_le16(0x0001), (u16 *)(pBuffer + sizeof( sQMUX ) + 7)); 
+
+   /* DataFormat: 0-default; 1-QoS hdr present 2 bytes */
+#ifdef QOS_MODE
+   *(u8 *)(pBuffer + sizeof( sQMUX ) + 9) = 1; /* QOS header */
+#else
+   *(u8 *)(pBuffer + sizeof( sQMUX ) + 9) = 0; /* no-QOS header */
+#endif
+
+    /* TLVType Link-Layer Protocol  (Optional) 1 byte */
+    *(u8 *)(pBuffer + sizeof( sQMUX ) + 10) = TLV_TYPE_LINK_PROTO;
+
+    /* TLVLength 2 bytes */
+    put_unaligned( cpu_to_le16(0x0002), (u16 *)(pBuffer + sizeof( sQMUX ) + 11));
+
+   /* LinkProt: 0x1 - ETH; 0x2 - rawIP  2 bytes */
+#ifdef DATA_MODE_RP
+   /* Set RawIP mode */
+   put_unaligned( cpu_to_le16(0x0002), (u16 *)(pBuffer + sizeof( sQMUX ) + 13));
+   DBG("Request RawIP Data Format\n");
+#else
+   /* Set Ethernet  mode */
+   put_unaligned( cpu_to_le16(0x0001), (u16 *)(pBuffer + sizeof( sQMUX ) + 13));
+   DBG("Request Ethernet Data Format\n");
+#endif
+
+   /* success */
+   return sizeof( sQMUX ) + 15;
+
+}
+#endif
+
+/*===========================================================================
+METHOD:
+   QMICTLSyncReq (Public Method)
+
+DESCRIPTION:
+   Fill buffer with QMI CTL Sync 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
+===========================================================================*/
+static int QMICTLSyncReq(
+   void *   pBuffer,
+   u16      buffSize,
+   u16      transactionID )
+{
+   if (pBuffer == 0 || buffSize < QMICTLSyncReqSize() )
+   {
+      return -ENOMEM;
+   }
+
+   // Request
+   *(u8 *)(pBuffer + sizeof( sQMUX ))  = 0x00;
+   // Transaction ID
+   *(u8 *)(pBuffer + sizeof( sQMUX ) + 1) = transactionID;
+   // Message ID
+   put_unaligned( cpu_to_le16(0x0027), (u16 *)(pBuffer + sizeof( sQMUX ) + 2) );
+   // Size of TLV's
+   put_unaligned( cpu_to_le16(0x0000), (u16 *)(pBuffer + sizeof( sQMUX ) + 4) );
+
+  // success
+  return sizeof( sQMUX ) + 6;
+}
+
+/*=========================================================================*/
+// 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
+===========================================================================*/
+static 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
+===========================================================================*/
+static 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
+===========================================================================*/
+static 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 );
+      put_unaligned( le32_to_cpu(*pTXOk), pTXOk);
+      GetTLV( pBuffer, buffSize, 0x11, (void*)pRXOk, 4 );
+      put_unaligned( le32_to_cpu(*pRXOk), pRXOk);
+      GetTLV( pBuffer, buffSize, 0x12, (void*)pTXErr, 4 );
+      put_unaligned( le32_to_cpu(*pTXErr), pTXErr);
+      GetTLV( pBuffer, buffSize, 0x13, (void*)pRXErr, 4 );
+      put_unaligned( le32_to_cpu(*pRXErr), pRXErr);
+      GetTLV( pBuffer, buffSize, 0x14, (void*)pTXOfl, 4 );
+      put_unaligned( le32_to_cpu(*pTXOfl), pTXOfl);
+      GetTLV( pBuffer, buffSize, 0x15, (void*)pRXOfl, 4 );
+      put_unaligned( le32_to_cpu(*pRXOfl), pRXOfl);
+      GetTLV( pBuffer, buffSize, 0x19, (void*)pTXBytesOk, 8 );
+      put_unaligned( le64_to_cpu(*pTXBytesOk), pTXBytesOk);
+      GetTLV( pBuffer, buffSize, 0x1A, (void*)pRXBytesOk, 8 );
+      put_unaligned( le64_to_cpu(*pRXBytesOk), pRXBytesOk);
+   }
+   // 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
+===========================================================================*/
+static 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;
+}
+
+/*===========================================================================
+METHOD:
+   QMIWDASetDataFormatResp (Public Method)
+
+DESCRIPTION:
+   Parse the QMI WDA Set Data Format Response
+
+PARAMETERS
+   pBuffer         [ I ] - Buffer to be parsed
+   buffSize        [ I ] - Size of pBuffer
+
+RETURN VALUE:
+   int - 0 for success
+         Negative errno for error
+===========================================================================*/
+static int QMIWDASetDataFormatResp(
+   void *   pBuffer,
+   u16      buffSize, bool bRawIPMode, int *qmap_enabled, int *rx_size, int *tx_size)
+{
+
+   int result;
+
+   u8 pktLinkProtocol[4];
+
+   // Ignore QMUX and SDU
+   // QMI SDU is 3 bytes
+   u8 offset = sizeof( sQMUX ) + 3;
+
+   if (pBuffer == 0 || buffSize < offset)
+   {
+      return -ENOMEM;
+   }
+
+   pBuffer = pBuffer + offset;
+   buffSize -= offset;
+
+   result = GetQMIMessageID( pBuffer, buffSize );
+   if (result != 0x20)
+   {
+      return -EFAULT;
+   }
+
+   /* Check response message result TLV */
+   result = ValidQMIMessage( pBuffer, buffSize );
+   if (result != 0)
+   {
+      DBG("EFAULT: Data Format Mode Bad Response\n"); 
+//      return -EFAULT;
+      return 0;
+   }
+
+   /* Check response message link protocol */
+   result = GetTLV( pBuffer, buffSize, 0x11,
+                     &pktLinkProtocol[0], 4);
+   if (result != 4)
+   {
+      DBG("EFAULT: Wrong TLV format\n"); 
+      return 0;
+   }
+
+if (bRawIPMode) { ////#ifdef DATA_MODE_RP
+   if (pktLinkProtocol[0] != 2)
+   {
+      DBG("EFAULT: Data Format Cannot be set to RawIP Mode\n"); 
+      return pktLinkProtocol[0];
+   }
+   DBG("Data Format Set to RawIP\n");
+} else { ////#else
+   if (pktLinkProtocol[0] != 1)
+   {
+      DBG("EFAULT: Data Format Cannot be set to Ethernet Mode\n"); 
+      return pktLinkProtocol[0];
+   }
+   DBG("Data Format Set to Ethernet Mode \n");
+} //#endif
+
+    GetTLV( pBuffer, buffSize, 0x12, qmap_enabled, 4);
+    if (le32_to_cpu(*qmap_enabled) == 5)
+        GetTLV( pBuffer, buffSize, 0x13, qmap_enabled, 4);
+
+    GetTLV( pBuffer, buffSize, 0x16, rx_size, 4);
+    GetTLV( pBuffer, buffSize, 0x18, tx_size, 4);
+
+   return pktLinkProtocol[0];
+}
+
+/*===========================================================================
+METHOD:
+   QMICTLSyncResp (Public Method)
+
+DESCRIPTION:
+   Validate the QMI CTL Sync Response
+
+PARAMETERS
+   pBuffer         [ I ] - Buffer to be parsed
+   buffSize        [ I ] - Size of pBuffer
+
+RETURN VALUE:
+   int - 0 for success
+         Negative errno for error
+===========================================================================*/
+static int QMICTLSyncResp(
+   void *pBuffer,
+   u16  buffSize )
+{
+   int result;
+
+   // Ignore QMUX (2 bytes for QMI CTL) and SDU
+   u8 offset = sizeof( sQMUX ) + 2;
+
+   if (pBuffer == 0 || buffSize < offset)
+   {
+      return -ENOMEM;
+   }
+
+   pBuffer = pBuffer + offset;
+   buffSize -= offset;
+
+   result = GetQMIMessageID( pBuffer, buffSize );
+   if (result != 0x27)
+   {
+      return -EFAULT;
+   }
+
+   result = ValidQMIMessage( pBuffer, buffSize );
+
+   return result;
+}
+#endif
diff --git a/drivers/net/usb/QMI.h b/drivers/net/usb/QMI.h
new file mode 100644 (file)
index 0000000..4508be4
--- /dev/null
@@ -0,0 +1,337 @@
+/*===========================================================================
+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
+      QMICTLSyncReqSize
+
+   Fill Buffers with QMI requests
+      QMICTLGetClientIDReq
+      QMICTLReleaseClientIDReq
+      QMICTLReadyReq
+      QMIWDSSetEventReportReq
+      QMIWDSGetPKGSRVCStatusReq
+      QMIDMSGetMEIDReq
+      QMICTLSetDataFormatReq
+      QMICTLSyncReq
+      
+   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.
+
+
+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
+/*=========================================================================*/
+
+extern int quec_debug;
+// DBG macro
+#define DBG( format, arg... ) do { \
+   if (quec_debug == 1)\
+   { \
+      printk( KERN_INFO "GobiNet::%s " format, __FUNCTION__, ## arg ); \
+   } }while(0)
+
+#if 0
+#define VDBG( format, arg... ) do { \
+   if (debug == 1)\
+   { \
+      printk( KERN_INFO "GobiNet::%s " format, __FUNCTION__, ## arg ); \
+   } } while(0)
+#else
+#define VDBG( format, arg... ) do { } while(0)
+#endif
+
+#define INFO( format, arg... ) do { \
+      printk( KERN_INFO "GobiNet::%s " format, __FUNCTION__, ## arg ); \
+   }while(0)
+
+// QMI Service Types
+#define QMICTL 0
+#define QMIWDS 1
+#define QMIDMS 2
+#define QMINAS 3
+#define QMIUIM 11
+#define QMIWDA 0x1A
+
+#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
+#ifndef ENOMSG
+#define ENOMSG    42
+#endif
+#define ENODATA   61
+
+#define TLV_TYPE_LINK_PROTO 0x10
+
+/*=========================================================================*/
+// 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;
+
+#if 0
+/*=========================================================================*/
+// 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 buffer of a specified TLV from a QMI message
+int 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 );
+
+u16 QMIWDSSetQMUXBindMuxDataPortSize( void );
+
+// Get size of buffer needed for QMUX + QMIDMSGetMEIDReq
+u16 QMIDMSGetMEIDReqSize( void );
+
+// Get size of buffer needed for QMUX + QMIWDASetDataFormatReq
+u16 QMIWDASetDataFormatReqSize( int qmap_mode );
+
+// Get size of buffer needed for QMUX + QMICTLSyncReq
+u16 QMICTLSyncReqSize( 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 );
+
+u16 QMIWDSSetQMUXBindMuxDataPortReq(
+   void *   pBuffer,
+   u16      buffSize,
+   u8 MuxId,
+   u16      transactionID );
+
+// Fill buffer with QMI DMS Get Serial Numbers Request
+int QMIDMSGetMEIDReq(
+   void *   pBuffer,
+   u16      buffSize,
+   u16      transactionID );
+
+// Fill buffer with QMI WDA Set Data Format Request
+int QMIWDASetDataFormatReq(
+   void *   pBuffer,
+   u16      buffSize,
+   bool     bRawIPMode, int qmap_mode, u32 rx_size,
+   u16      transactionID );
+
+#if 0
+int QMIWDASetDataQmapReq(
+               void *   pBuffer,
+               u16      buffSize,
+               u16      transactionID );
+#endif
+
+int QMICTLSyncReq(
+   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 );
+
+// Parse the QMI DMS Get Serial Numbers Resp
+int QMIWDASetDataFormatResp(
+   void *   pBuffer,
+   u16      buffSize, bool bRawIPMode, int *qmap_enabled, int *rx_size, int *tx_size);
+
+// Pasre the QMI CTL Sync Response
+int QMICTLSyncResp(
+   void *pBuffer,
+   u16  buffSize );
+#endif
diff --git a/drivers/net/usb/QMIDevice.c b/drivers/net/usb/QMIDevice.c
new file mode 100644 (file)
index 0000000..c71da31
--- /dev/null
@@ -0,0 +1,4210 @@
+/*===========================================================================
+FILE:
+   QMIDevice.c
+
+DESCRIPTION:
+   Functions related to the QMI interface device
+   
+FUNCTIONS:
+   Generic functions
+      IsDeviceValid
+      PrintHex
+      GobiSetDownReason
+      GobiClearDownReason
+      GobiTestDownReason
+
+   Driver level asynchronous read functions
+      ResubmitIntURB
+      ReadCallback
+      IntCallback
+      StartRead
+      KillRead
+
+   Internal read/write functions
+      ReadAsync
+      UpSem
+      ReadSync
+      WriteSyncCallback
+      WriteSync
+
+   Internal memory management functions
+      GetClientID
+      ReleaseClientID
+      FindClientMem
+      AddToReadMemList
+      PopFromReadMemList
+      AddToNotifyList
+      NotifyAndPopNotifyList
+      AddToURBList
+      PopFromURBList
+
+   Internal userspace wrapper functions
+      UserspaceunlockedIOCTL
+
+   Userspace wrappers
+      UserspaceOpen
+      UserspaceIOCTL
+      UserspaceClose
+      UserspaceRead
+      UserspaceWrite
+      UserspacePoll
+
+   Initializer and destructor
+      RegisterQMIDevice
+      DeregisterQMIDevice
+
+   Driver level client management
+      QMIReady
+      QMIWDSCallback
+      SetupQMIWDSCallback
+      QMIDMSGetMEID
+
+Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of Code Aurora Forum nor
+      the names of its contributors may be used to endorse or promote
+      products derived from this software without specific prior written
+      permission.
+
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+===========================================================================*/
+
+//---------------------------------------------------------------------------
+// Include Files
+//---------------------------------------------------------------------------
+#include <asm/unaligned.h>
+#include <linux/module.h>
+#include <linux/usb/cdc.h>
+#include <linux/usb.h>
+
+//-----------------------------------------------------------------------------
+// Definitions
+//-----------------------------------------------------------------------------
+
+#define __QUEC_INCLUDE_QMI_C__
+#include "QMI.c"
+#define __QUECTEL_INTER__
+#include "QMIDevice.h"
+
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,22 ))
+static int s_interval;
+#endif
+
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,14 ))
+#include <linux/devfs_fs_kernel.h>
+static char devfs_name[32];
+static int device_create(struct class *class,  struct device *parent, dev_t devt, const char *fmt, ...)
+{
+   va_list vargs;
+   struct class_device *class_dev;
+   int err;
+
+   va_start(vargs, fmt);
+   vsnprintf(devfs_name, sizeof(devfs_name), fmt, vargs);
+   va_end(vargs);
+
+   class_dev = class_device_create(class, devt, parent, "%s", devfs_name);
+   if (IS_ERR(class_dev)) {
+      err = PTR_ERR(class_dev);
+      goto out;
+   }
+
+   err = devfs_mk_cdev(devt, S_IFCHR|S_IRUSR|S_IWUSR|S_IRGRP, devfs_name);
+   if (err) {
+      class_device_destroy(class, devt);
+      goto out;
+   }
+
+   return 0;
+   
+out:
+   return err;   
+}
+
+static void device_destroy(struct class *class, dev_t devt)
+{
+   class_device_destroy(class, devt);
+   devfs_remove(devfs_name);
+}
+#endif
+
+#ifdef CONFIG_PM
+// Prototype to GobiNetSuspend function
+int QuecGobiNetSuspend(
+   struct usb_interface *     pIntf,
+   pm_message_t               powerEvent );
+#endif /* CONFIG_PM */
+
+// IOCTL to generate a client ID for this service type
+#define IOCTL_QMI_GET_SERVICE_FILE 0x8BE0 + 1
+
+// IOCTL to get the VIDPID of the device
+#define IOCTL_QMI_GET_DEVICE_VIDPID 0x8BE0 + 2
+
+// IOCTL to get the MEID of the device
+#define IOCTL_QMI_GET_DEVICE_MEID 0x8BE0 + 3
+
+#define IOCTL_QMI_RELEASE_SERVICE_FILE_IOCTL  (0x8BE0 + 4)
+
+// CDC GET_ENCAPSULATED_RESPONSE packet
+#define CDC_GET_ENCAPSULATED_RESPONSE_LE 0x01A1ll
+#define CDC_GET_ENCAPSULATED_RESPONSE_BE 0xA101000000000000ll
+/* The following masks filter the common part of the encapsulated response
+ * packet value for Gobi and QMI devices, ie. ignore usb interface number
+ */
+#define CDC_RSP_MASK_BE 0xFFFFFFFF00FFFFFFll
+#define CDC_RSP_MASK_LE 0xFFFFFFE0FFFFFFFFll
+
+static const int i = 1;
+#define is_bigendian() ( (*(char*)&i) == 0 )
+#define CDC_GET_ENCAPSULATED_RESPONSE(pcdcrsp, pmask)\
+{\
+   *pcdcrsp  = is_bigendian() ? CDC_GET_ENCAPSULATED_RESPONSE_BE \
+                          : CDC_GET_ENCAPSULATED_RESPONSE_LE ; \
+   *pmask = is_bigendian() ? CDC_RSP_MASK_BE \
+                           : CDC_RSP_MASK_LE; \
+}
+
+// CDC CONNECTION_SPEED_CHANGE indication packet
+#define CDC_CONNECTION_SPEED_CHANGE_LE 0x2AA1ll
+#define CDC_CONNECTION_SPEED_CHANGE_BE 0xA12A000000000000ll
+/* The following masks filter the common part of the connection speed change
+ * packet value for Gobi and QMI devices
+ */
+#define CDC_CONNSPD_MASK_BE 0xFFFFFFFFFFFF7FFFll
+#define CDC_CONNSPD_MASK_LE 0XFFF7FFFFFFFFFFFFll
+#define CDC_GET_CONNECTION_SPEED_CHANGE(pcdccscp, pmask)\
+{\
+   *pcdccscp  = is_bigendian() ? CDC_CONNECTION_SPEED_CHANGE_BE \
+                          : CDC_CONNECTION_SPEED_CHANGE_LE ; \
+   *pmask = is_bigendian() ? CDC_CONNSPD_MASK_BE \
+                           : CDC_CONNSPD_MASK_LE; \
+}
+
+#define SET_CONTROL_LINE_STATE_REQUEST_TYPE        0x21
+#define SET_CONTROL_LINE_STATE_REQUEST             0x22
+#define CONTROL_DTR                     0x01
+#define CONTROL_RTS                     0x02
+
+/*=========================================================================*/
+// UserspaceQMIFops
+//    QMI device's userspace file operations
+/*=========================================================================*/
+static struct file_operations UserspaceQMIFops = 
+{
+   .owner     = THIS_MODULE,
+   .read      = UserspaceRead,
+   .write     = UserspaceWrite,
+#ifdef CONFIG_COMPAT
+   .compat_ioctl = UserspaceunlockedIOCTL,
+#endif
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 2,6,36 ))
+   .unlocked_ioctl = UserspaceunlockedIOCTL,
+#else
+   .ioctl     = UserspaceIOCTL,
+#endif
+   .open      = UserspaceOpen,
+#ifdef quectel_no_for_each_process
+   .release      = UserspaceClose,
+#else
+   .flush     = UserspaceClose,
+#endif
+   .poll      = UserspacePoll,
+};
+
+/*=========================================================================*/
+// Generic functions
+/*=========================================================================*/
+static u8 QMIXactionIDGet( sGobiUSBNet *pDev)
+{
+   u8 transactionID;
+
+   if( 0 == (transactionID = atomic_add_return( 1, &pDev->mQMIDev.mQMICTLTransactionID)) )
+   {
+      transactionID = atomic_add_return( 1, &pDev->mQMIDev.mQMICTLTransactionID );
+   }
+   
+   return transactionID;
+}
+
+static struct usb_endpoint_descriptor *GetEndpoint(
+    struct usb_interface *pintf,
+    int type,
+    int dir )
+{
+   int i;
+   struct usb_host_interface *iface = pintf->cur_altsetting;
+   struct usb_endpoint_descriptor *pendp;
+
+   for( i = 0; i < iface->desc.bNumEndpoints; i++)
+   {
+      pendp = &iface->endpoint[i].desc;
+      if( ((pendp->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == dir)
+          &&
+          (usb_endpoint_type(pendp) == type) )
+      {
+         return pendp;
+      }
+   }
+
+   return NULL;
+}
+
+/*===========================================================================
+METHOD:
+   IsDeviceValid (Public Method)
+
+DESCRIPTION:
+   Basic test to see if device memory is valid
+
+PARAMETERS:
+   pDev     [ I ] - Device specific memory
+
+RETURN VALUE:
+   bool
+===========================================================================*/
+static 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 QuecPrintHex(
+   void *      pBuffer,
+   u16         bufSize )
+{
+   char * pPrintBuf;
+   u16 pos;
+   int status;
+   
+   if (quec_debug != 1)
+   {
+       return;
+   }
+
+   pPrintBuf = kmalloc( bufSize * 3 + 1, GFP_ATOMIC );
+   if (pPrintBuf == NULL)
+   {
+      DBG( "Unable to allocate buffer\n" );
+      return;
+   }
+   memset( pPrintBuf, 0 , bufSize * 3 + 1 );
+   
+   for (pos = 0; pos < bufSize; pos++)
+   {
+      status = snprintf( (pPrintBuf + (pos * 3)), 
+                         4, 
+                         "%02X ", 
+                         *(u8 *)(pBuffer + pos) );
+      if (status != 3)
+      {
+         DBG( "snprintf error %d\n", status );
+         kfree( pPrintBuf );
+         return;
+      }
+   }
+   
+   DBG( "   : %s\n", pPrintBuf );
+
+   kfree( pPrintBuf );
+   pPrintBuf = NULL;
+   return;   
+}
+
+/*===========================================================================
+METHOD:
+   GobiSetDownReason (Public Method)
+
+DESCRIPTION:
+   Sets mDownReason and turns carrier off
+
+PARAMETERS
+   pDev     [ I ] - Device specific memory
+   reason   [ I ] - Reason device is down
+
+RETURN VALUE:
+   None
+===========================================================================*/
+void QuecGobiSetDownReason(
+   sGobiUSBNet *    pDev,
+   u8                 reason )
+{
+   DBG("%s reason=%d, mDownReason=%x\n", __func__, reason, (unsigned)pDev->mDownReason);
+   
+#ifdef QUECTEL_WWAN_QMAP
+   if (reason == NO_NDIS_CONNECTION)
+      return;
+#endif
+   
+   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 QuecGobiClearDownReason(
+   sGobiUSBNet *    pDev,
+   u8                 reason )
+{
+   clear_bit( reason, &pDev->mDownReason );
+   
+   DBG("%s reason=%d, mDownReason=%x\n", __func__, reason, (unsigned)pDev->mDownReason);
+#if 0 //(LINUX_VERSION_CODE >= KERNEL_VERSION( 3,11,0 ))
+    netif_carrier_on( pDev->mpNetDev->net );
+#else
+   if (pDev->mDownReason == 0)
+   {
+      netif_carrier_on( pDev->mpNetDev->net );
+   }
+#endif
+}
+
+/*===========================================================================
+METHOD:
+   GobiTestDownReason (Public Method)
+
+DESCRIPTION:
+   Test mDownReason and returns whether reason is set
+
+PARAMETERS
+   pDev     [ I ] - Device specific memory
+   reason   [ I ] - Reason device is down
+
+RETURN VALUE:
+   bool
+===========================================================================*/
+bool QuecGobiTestDownReason(
+   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
+===========================================================================*/
+static int ResubmitIntURB( struct urb * pIntURB )
+{
+   int status;
+   int interval;
+
+   // Sanity test
+   if ( (pIntURB == NULL)
+   ||   (pIntURB->dev == NULL) )
+   {
+      return -EINVAL;
+   }
+   // Interval needs reset after every URB completion
+#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,22 ))
+    interval = max((int)(pIntURB->ep->desc.bInterval),
+                  (pIntURB->dev->speed == USB_SPEED_HIGH) ? 7 : 3);
+#else
+    interval = s_interval;
+#endif
+
+   // Reschedule interrupt URB
+   usb_fill_int_urb( pIntURB,
+                     pIntURB->dev,
+                     pIntURB->pipe,
+                     pIntURB->transfer_buffer,
+                     pIntURB->transfer_buffer_length,
+                     pIntURB->complete,
+                     pIntURB->context,
+                     interval );
+   status = usb_submit_urb( pIntURB, GFP_ATOMIC );
+   if (status != 0)
+   {
+      DBG( "Error re-submitting Int URB %d\n", status );
+   }
+
+   return status;
+}
+
+/*===========================================================================
+METHOD:
+   ReadCallback (Public Method)
+
+DESCRIPTION:
+   Put the data in storage and notify anyone waiting for data
+
+PARAMETERS
+   pReadURB       [ I ] - URB this callback is run for
+
+RETURN VALUE:
+   None
+===========================================================================*/
+#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,18 ))
+static void ReadCallback( struct urb * pReadURB )
+#else
+static void ReadCallback(struct urb *pReadURB, struct pt_regs *regs)
+#endif
+{
+   int result;
+   u16 clientID;
+   sClientMemList * pClientMem;
+   void * pData;
+   void * pDataCopy;
+   u16 dataSize;
+   sGobiUSBNet * pDev;
+   unsigned long flags;
+   u16 transactionID;
+
+   if (pReadURB == NULL)
+   {
+      DBG( "bad read URB\n" );
+      return;
+   }
+   
+   pDev = pReadURB->context;
+   if (IsDeviceValid( pDev ) == false)
+   {
+      DBG( "Invalid device!\n" );
+      return;
+   }   
+
+#ifdef READ_QMI_URB_ERROR
+   del_timer(&pDev->mQMIDev.mReadUrbTimer);
+   if ((pReadURB->status == -ECONNRESET) && (pReadURB->actual_length > 0))
+      pReadURB->status = 0;
+#endif
+
+   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 );
+
+#ifdef READ_QMI_URB_ERROR
+   if (dataSize < (le16_to_cpu(get_unaligned((u16*)(pData + 1))) + 1)) {
+      dataSize = (le16_to_cpu(get_unaligned((u16*)(pData + 1))) + 1);
+      memset(pReadURB->transfer_buffer + pReadURB->actual_length, 0x00, dataSize - pReadURB->actual_length);
+      INFO( "Read %d / %d bytes\n", pReadURB->actual_length, dataSize);
+   }
+#endif
+
+   result = ParseQMUX( &clientID,
+                       pData,
+                       dataSize );
+   if (result < 0)
+   {
+      DBG( "Read error parsing QMUX %d\n", result );
+
+      // Resubmit the interrupt URB
+      ResubmitIntURB( pDev->mQMIDev.mpIntURB );
+
+      return;
+   }
+   
+   // Grab transaction ID
+
+   // Data large enough?
+   if (dataSize < result + 3)
+   {
+      DBG( "Data buffer too small to parse\n" );
+
+      // Resubmit the interrupt URB
+      ResubmitIntURB( pDev->mQMIDev.mpIntURB );
+
+      return;
+   }
+   
+   // Transaction ID size is 1 for QMICTL, 2 for others
+   if (clientID == QMICTL)
+   {
+      transactionID = *(u8*)(pData + result + 1);
+   }
+   else
+   {
+      transactionID = le16_to_cpu( get_unaligned((u16*)(pData + result + 1)) );
+   }
+   
+   // Critical section
+   spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
+
+   // Find memory storage for this service and Client ID
+   // Not using FindClientMem because it can't handle broadcasts
+   pClientMem = pDev->mQMIDev.mpClientMemList;
+
+   while (pClientMem != NULL)
+   {
+      if (pClientMem->mClientID == clientID 
+      ||  (pClientMem->mClientID | 0xff00) == clientID)
+      {
+         // Make copy of pData
+         pDataCopy = kmalloc( dataSize, GFP_ATOMIC );
+         if (pDataCopy == NULL)
+         {
+            DBG( "Error allocating client data memory\n" );
+
+            // End critical section
+            spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
+
+            // Resubmit the interrupt URB
+            ResubmitIntURB( pDev->mQMIDev.mpIntURB );
+
+            return;             
+         }
+
+         memcpy( pDataCopy, pData, dataSize );
+
+         if (AddToReadMemList( pDev,
+                               pClientMem->mClientID,
+                               transactionID,
+                               pDataCopy,
+                               dataSize ) == false)
+         {
+            DBG( "Error allocating pReadMemListEntry "
+                 "read will be discarded\n" );
+            kfree( pDataCopy );
+            
+            // End critical section
+            spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
+
+            // Resubmit the interrupt URB
+            ResubmitIntURB( pDev->mQMIDev.mpIntURB );
+
+            return;
+         }
+
+         // Success
+         VDBG( "Creating new readListEntry for client 0x%04X, TID %x\n",
+              clientID,
+              transactionID );
+
+         // Notify this client data exists
+         NotifyAndPopNotifyList( pDev,
+                                 pClientMem->mClientID,
+                                 transactionID );
+
+         // Possibly notify poll() that data exists
+         wake_up_interruptible_sync( &pClientMem->mWaitQueue );
+
+         // Not a broadcast
+         if (clientID >> 8 != 0xff)
+         {
+            break;
+         }
+      }
+      
+      // Next element
+      pClientMem = pClientMem->mpNext;
+   }
+   
+   // End critical section
+   spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
+   
+   // Resubmit the interrupt URB
+   ResubmitIntURB( pDev->mQMIDev.mpIntURB );
+}
+
+/*===========================================================================
+METHOD:
+   IntCallback (Public Method)
+
+DESCRIPTION:
+   Data is available, fire off a read URB
+
+PARAMETERS
+   pIntURB       [ I ] - URB this callback is run for
+
+RETURN VALUE:
+   None
+===========================================================================*/
+#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,18 ))
+static void IntCallback( struct urb * pIntURB )
+{
+#else
+static void IntCallback(struct urb *pIntURB, struct pt_regs *regs)
+{
+#endif
+   int status;
+   struct usb_cdc_notification *dr;
+   
+   sGobiUSBNet * pDev = (sGobiUSBNet *)pIntURB->context;
+   dr = (struct usb_cdc_notification *)pDev->mQMIDev.mpIntBuffer;
+
+   if (IsDeviceValid( pDev ) == false)
+   {
+      DBG( "Invalid device!\n" );
+      return;
+   }
+
+   // Verify this was a normal interrupt
+   if (pIntURB->status != 0)
+   {
+        DBG( "IntCallback: Int status = %d\n", pIntURB->status );
+      
+      // Ignore EOVERFLOW errors
+      if (pIntURB->status != -EOVERFLOW)
+      {
+         // Read 'thread' dies here
+         return;
+      }
+   }
+   else
+   {
+      //TODO cast transfer_buffer to struct usb_cdc_notification
+      
+      VDBG( "IntCallback: Encapsulated Response = 0x%llx\n",
+          (*(u64*)pIntURB->transfer_buffer));
+
+     switch (dr->bNotificationType) {
+      case USB_CDC_NOTIFY_RESPONSE_AVAILABLE: //0x01
+         {
+          // 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 );
+          #ifdef READ_QMI_URB_ERROR
+          mod_timer( &pDev->mQMIDev.mReadUrbTimer, jiffies + msecs_to_jiffies(300) );
+          #endif
+          status = usb_submit_urb( pDev->mQMIDev.mpReadURB, GFP_ATOMIC );
+          if (status != 0)
+          {
+            DBG("Error submitting Read URB %d\n", status);
+            // Resubmit the interrupt urb
+            ResubmitIntURB(pIntURB);
+            return;
+          }
+
+           // Int URB will be resubmitted during ReadCallback
+           return; 
+         }
+      case USB_CDC_NOTIFY_SPEED_CHANGE:   //0x2a
+         {
+             DBG( "IntCallback: Connection Speed Change = 0x%llx\n",
+              (*(u64*)pIntURB->transfer_buffer));
+
+           // if upstream or downstream is 0, stop traffic.  Otherwise resume it
+           if ((*(u32*)(pIntURB->transfer_buffer + 8) == 0)
+           ||  (*(u32*)(pIntURB->transfer_buffer + 12) == 0))
+           {
+              GobiSetDownReason( pDev, CDC_CONNECTION_SPEED );
+              DBG( "traffic stopping due to CONNECTION_SPEED_CHANGE\n" );
+           }
+           else
+           {
+              GobiClearDownReason( pDev, CDC_CONNECTION_SPEED );
+              DBG( "resuming traffic due to CONNECTION_SPEED_CHANGE\n" );
+           }
+         }
+      default:
+         {
+             DBG( "ignoring invalid interrupt in packet\n" );
+             PrintHex( pIntURB->transfer_buffer, pIntURB->actual_length );
+         }
+      }
+      
+        // Resubmit the interrupt urb
+      ResubmitIntURB( pIntURB );
+
+      return;
+   }
+}
+
+#ifdef READ_QMI_URB_ERROR
+static void ReadUrbTimerFunc( struct urb * pReadURB )
+{
+  int result;
+
+  INFO( "%s called (%ld).\n", __func__, jiffies );
+
+  if ((pReadURB != NULL) && (pReadURB->status == -EINPROGRESS))
+  {
+     // Asynchronously unlink URB. On success, -EINPROGRESS will be returned, 
+     // URB status will be set to -ECONNRESET, and ReadCallback() executed
+     result = usb_unlink_urb( pReadURB );
+     INFO( "%s called usb_unlink_urb, result = %d\n", __func__, result);
+  }
+}
+#endif
+
+/*===========================================================================
+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 QuecStartRead( sGobiUSBNet * pDev )
+{
+   int interval;
+   struct usb_endpoint_descriptor *pendp;
+
+   if (IsDeviceValid( pDev ) == false)
+   {
+      DBG( "Invalid device!\n" );
+      return -ENXIO;
+   }
+
+   // Allocate URB buffers
+   pDev->mQMIDev.mpReadURB = usb_alloc_urb( 0, GFP_KERNEL );
+   if (pDev->mQMIDev.mpReadURB == NULL)
+   {
+      DBG( "Error allocating read urb\n" );
+      return -ENOMEM;
+   }
+
+#ifdef READ_QMI_URB_ERROR
+   setup_timer( &pDev->mQMIDev.mReadUrbTimer, (void*)ReadUrbTimerFunc, (unsigned long)pDev->mQMIDev.mpReadURB );
+#endif
+
+   pDev->mQMIDev.mpIntURB = usb_alloc_urb( 0, GFP_KERNEL );
+   if (pDev->mQMIDev.mpIntURB == NULL)
+   {
+      DBG( "Error allocating int urb\n" );
+      usb_free_urb( pDev->mQMIDev.mpReadURB );
+      pDev->mQMIDev.mpReadURB = NULL;
+      return -ENOMEM;
+   }
+
+   // Create data buffers
+   pDev->mQMIDev.mpReadBuffer = kmalloc( DEFAULT_READ_URB_LENGTH, GFP_KERNEL );
+   if (pDev->mQMIDev.mpReadBuffer == NULL)
+   {
+      DBG( "Error allocating read buffer\n" );
+      usb_free_urb( pDev->mQMIDev.mpIntURB );
+      pDev->mQMIDev.mpIntURB = NULL;
+      usb_free_urb( pDev->mQMIDev.mpReadURB );
+      pDev->mQMIDev.mpReadURB = NULL;
+      return -ENOMEM;
+   }
+   
+   pDev->mQMIDev.mpIntBuffer = kmalloc( 64, GFP_KERNEL );
+   if (pDev->mQMIDev.mpIntBuffer == NULL)
+   {
+      DBG( "Error allocating int buffer\n" );
+      kfree( pDev->mQMIDev.mpReadBuffer );
+      pDev->mQMIDev.mpReadBuffer = NULL;
+      usb_free_urb( pDev->mQMIDev.mpIntURB );
+      pDev->mQMIDev.mpIntURB = NULL;
+      usb_free_urb( pDev->mQMIDev.mpReadURB );
+      pDev->mQMIDev.mpReadURB = NULL;
+      return -ENOMEM;
+   }      
+   
+   pDev->mQMIDev.mpReadSetupPacket = kmalloc( sizeof( sURBSetupPacket ), 
+                                              GFP_KERNEL );
+   if (pDev->mQMIDev.mpReadSetupPacket == NULL)
+   {
+      DBG( "Error allocating setup packet buffer\n" );
+      kfree( pDev->mQMIDev.mpIntBuffer );
+      pDev->mQMIDev.mpIntBuffer = NULL;
+      kfree( pDev->mQMIDev.mpReadBuffer );
+      pDev->mQMIDev.mpReadBuffer = NULL;
+      usb_free_urb( pDev->mQMIDev.mpIntURB );
+      pDev->mQMIDev.mpIntURB = NULL;
+      usb_free_urb( pDev->mQMIDev.mpReadURB );
+      pDev->mQMIDev.mpReadURB = NULL;
+      return -ENOMEM;
+   }
+
+   // CDC Get Encapsulated Response packet
+   pDev->mQMIDev.mpReadSetupPacket->mRequestType = 0xA1;
+   pDev->mQMIDev.mpReadSetupPacket->mRequestCode = 1;
+   pDev->mQMIDev.mpReadSetupPacket->mValue = 0;
+   pDev->mQMIDev.mpReadSetupPacket->mIndex =
+      cpu_to_le16(pDev->mpIntf->cur_altsetting->desc.bInterfaceNumber);  /* interface number */
+   pDev->mQMIDev.mpReadSetupPacket->mLength = cpu_to_le16(DEFAULT_READ_URB_LENGTH);
+
+   pendp = GetEndpoint(pDev->mpIntf, USB_ENDPOINT_XFER_INT, USB_DIR_IN);
+   if (pendp == NULL)
+   {
+      DBG( "Invalid interrupt endpoint!\n" );
+      kfree(pDev->mQMIDev.mpReadSetupPacket);
+      pDev->mQMIDev.mpReadSetupPacket = NULL;
+      kfree( pDev->mQMIDev.mpIntBuffer );
+      pDev->mQMIDev.mpIntBuffer = NULL;
+      kfree( pDev->mQMIDev.mpReadBuffer );
+      pDev->mQMIDev.mpReadBuffer = NULL;
+      usb_free_urb( pDev->mQMIDev.mpIntURB );
+      pDev->mQMIDev.mpIntURB = NULL;
+      usb_free_urb( pDev->mQMIDev.mpReadURB );
+      pDev->mQMIDev.mpReadURB = NULL;
+      return -ENXIO;
+   }
+
+   // Interval needs reset after every URB completion
+   interval = max((int)(pendp->bInterval),
+                  (pDev->mpNetDev->udev->speed == USB_SPEED_HIGH) ? 7 : 3);
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,22 ))
+    s_interval = interval;
+#endif
+   
+   // Schedule interrupt URB
+   usb_fill_int_urb( pDev->mQMIDev.mpIntURB,
+                     pDev->mpNetDev->udev,
+                     /* QMI interrupt endpoint for the following
+                      * interface configuration: DM, NMEA, MDM, NET
+                      */
+                     usb_rcvintpipe( pDev->mpNetDev->udev,
+                                     pendp->bEndpointAddress),
+                     pDev->mQMIDev.mpIntBuffer,
+                     min((int)le16_to_cpu(pendp->wMaxPacketSize), 64),
+                     IntCallback,
+                     pDev,
+                     interval );
+   return usb_submit_urb( pDev->mQMIDev.mpIntURB, GFP_KERNEL );
+}
+
+/*===========================================================================
+METHOD:
+   KillRead (Public Method)
+
+DESCRIPTION:
+   Kill continuous read "thread"
+   
+PARAMETERS:
+   pDev     [ I ] - Device specific memory
+
+RETURN VALUE:
+   None
+===========================================================================*/
+void QuecKillRead( 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
+===========================================================================*/
+static 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
+===========================================================================*/
+#define QUEC_SEM_MAGIC 0x12345678
+struct QuecSem {
+    struct semaphore readSem;
+    int magic;
+};
+
+static void UpSem( 
+   sGobiUSBNet * pDev,
+   u16             clientID,
+   void *          pData )
+{
+   struct QuecSem *pSem = (struct QuecSem *)pData;
+
+   VDBG( "0x%04X\n", clientID );
+
+   if (pSem->magic == QUEC_SEM_MAGIC)        
+      up( &(pSem->readSem) );
+   else
+       kfree(pSem);
+   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
+===========================================================================*/
+static int ReadSync(
+   sGobiUSBNet *    pDev,
+   void **            ppOutBuffer,
+   u16                clientID,
+   u16                transactionID )
+{
+   int result;
+   sClientMemList * pClientMem;
+   sNotifyList ** ppNotifyList, * pDelNotifyListEntry;
+   struct QuecSem 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.readSem, 0 );
+      readSem.magic = QUEC_SEM_MAGIC;
+
+      // 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.readSem );
+         if (result == -EINTR) {
+                 result = down_timeout(&readSem.readSem, msecs_to_jiffies(200));
+         }
+      if (result != 0)
+      {
+         DBG( "Down Timeout %d\n", result );
+
+         // readSem will fall out of scope, 
+         // remove from notify list so it's not referenced
+         spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
+         ppNotifyList = &(pClientMem->mpReadNotifyList);
+         pDelNotifyListEntry = NULL;
+
+         // Find and delete matching entry
+         while (*ppNotifyList != NULL)
+         {
+            if ((*ppNotifyList)->mpData == &readSem)
+            {
+               pDelNotifyListEntry = *ppNotifyList;
+               *ppNotifyList = (*ppNotifyList)->mpNext;
+               kfree( pDelNotifyListEntry );
+               break;
+            }
+
+            // Next
+            ppNotifyList = &(*ppNotifyList)->mpNext;
+         }
+
+         spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
+         return -EINTR;
+      }
+      
+      // Verify device is still valid
+      if (IsDeviceValid( pDev ) == false)
+      {
+         DBG( "Invalid device!\n" );
+         return -ENXIO;
+      }
+      
+      // Restart critical section and continue loop
+      spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
+   }
+   
+   // End Critical section
+   spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
+
+   // Success
+   *ppOutBuffer = pData;
+
+   return dataSize;
+}
+
+/*===========================================================================
+METHOD:
+   WriteSyncCallback (Public Method)
+
+DESCRIPTION:
+   Write callback
+
+PARAMETERS
+   pWriteURB       [ I ] - URB this callback is run for
+
+RETURN VALUE:
+   None
+===========================================================================*/
+#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,18 ))
+static void WriteSyncCallback( struct urb * pWriteURB )
+#else
+static void WriteSyncCallback(struct urb *pWriteURB, struct pt_regs *regs)
+#endif
+{
+   if (pWriteURB == NULL)
+   {
+      DBG( "null urb\n" );
+      return;
+   }
+
+   DBG( "Write status/size %d/%d\n", 
+        pWriteURB->status, 
+        pWriteURB->actual_length );
+
+   // Notify that write has completed by up()-ing semeaphore
+   up( (struct semaphore * )pWriteURB->context );
+   
+   return;
+}
+
+/*===========================================================================
+METHOD:
+   WriteSync (Public Method)
+
+DESCRIPTION:
+   Start synchronous write
+
+PARAMETERS:
+   pDev                 [ I ] - Device specific memory
+   pWriteBuffer         [ I ] - Data to be written
+   writeBufferSize      [ I ] - Size of data to be written
+   clientID             [ I ] - Client ID of requester
+
+RETURN VALUE:
+   int - write size (includes QMUX)
+         negative errno for failure
+===========================================================================*/
+static 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 = kmalloc(sizeof(sURBSetupPacket *), GFP_KERNEL);
+   writeSetup->mRequestType = 0x21;
+   writeSetup->mRequestCode = 0;
+   writeSetup->mValue = 0;
+   writeSetup->mIndex = cpu_to_le16(pDev->mpIntf->cur_altsetting->desc.bInterfaceNumber);
+   writeSetup->mLength = cpu_to_le16(writeBufferSize);
+
+   // Create URB   
+   usb_fill_control_urb( pWriteURB,
+                         pDev->mpNetDev->udev,
+                         usb_sndctrlpipe( pDev->mpNetDev->udev, 0 ),
+                         (unsigned char *)writeSetup,
+                         (void*)pWriteBuffer,
+                         writeBufferSize,
+                         NULL,
+                         pDev );
+
+   DBG( "Actual Write:\n" );
+   PrintHex( pWriteBuffer, writeBufferSize );
+
+   sema_init( &writeSem, 0 );
+   
+   pWriteURB->complete = WriteSyncCallback;
+   pWriteURB->context = &writeSem;
+
+   // Wake device
+   result = usb_autopm_get_interface( pDev->mpIntf );
+   if (result < 0)
+   {
+      DBG( "unable to resume interface: %d\n", result );
+      
+      // Likely caused by device going from autosuspend -> full suspend
+      if (result == -EPERM)
+      {
+#ifdef CONFIG_PM
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,33 ))
+#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,18 ))
+         pDev->mpNetDev->udev->auto_pm = 0;
+#endif
+#endif
+         QuecGobiNetSuspend( pDev->mpIntf, PMSG_SUSPEND );
+#endif /* CONFIG_PM */
+      }
+      usb_free_urb( pWriteURB );
+      kfree(writeSetup);
+
+      return result;
+   }
+
+   // Critical section
+   spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
+
+   if (AddToURBList( pDev, clientID, pWriteURB ) == false)
+   {
+      usb_free_urb( pWriteURB );
+      kfree(writeSetup);
+
+      // End critical section
+      spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );   
+      usb_autopm_put_interface( pDev->mpIntf );
+      return -EINVAL;
+   }
+
+   spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
+   result = usb_submit_urb( pWriteURB, GFP_KERNEL );
+   spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
+
+   if (result < 0)
+   {
+      DBG( "submit URB error %d\n", result );
+      
+      // Get URB back so we can destroy it
+      if (PopFromURBList( pDev, clientID ) != pWriteURB)
+      {
+         // This shouldn't happen
+         DBG( "Didn't get write URB back\n" );
+      }
+
+      usb_free_urb( pWriteURB );
+      kfree(writeSetup);
+
+      // 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 (1 != 0) //(interruptible != 0)
+   {
+      // Allow user interrupts
+      result = down_interruptible( &writeSem );
+         if (result == -EINTR) {
+                 result = down_timeout(&writeSem, msecs_to_jiffies(200));
+         }
+   }
+   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 );
+      kfree(writeSetup);
+      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 );
+      kfree(writeSetup);
+      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 );
+   kfree(writeSetup);
+
+   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
+===========================================================================*/
+static 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 = QMIXactionIDGet( pDev );
+
+      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 );
+
+     /* Upon return from QMICTLGetClientIDResp, clientID
+      * low address contains the Service Number (SN), and
+      * clientID high address contains Client Number (CN)
+      * For the ReadCallback to function correctly,we swap
+      * the SN and CN on a Big Endian architecture.
+      */
+      clientID = le16_to_cpu(clientID);
+
+      kfree( pReadBuffer );
+
+      if (result < 0)
+      {
+         return result;
+      }
+   }
+   else
+   {
+      // QMI CTL will always have client ID 0
+      clientID = 0;
+   }
+
+   // Critical section
+   spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
+
+   // Verify client is not already allocated
+   if (FindClientMem( pDev, clientID ) != NULL)
+   {
+      DBG( "Client memory already exists\n" );
+
+      // End Critical section
+      spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
+      return -ETOOMANYREFS;
+   }
+
+   // Go to last entry in client mem list
+   ppClientMem = &pDev->mQMIDev.mpClientMemList;
+   while (*ppClientMem != NULL)
+   {
+      ppClientMem = &(*ppClientMem)->mpNext;
+   }
+   
+   // Create locations for read to place data into
+   *ppClientMem = kmalloc( sizeof( sClientMemList ), GFP_ATOMIC );
+   if (*ppClientMem == NULL)
+   {
+      DBG( "Error allocating read list\n" );
+
+      // End critical section
+      spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
+      return -ENOMEM;
+   }
+      
+   (*ppClientMem)->mClientID = clientID;
+   (*ppClientMem)->mpList = NULL;
+   (*ppClientMem)->mpReadNotifyList = NULL;
+   (*ppClientMem)->mpURBList = NULL;
+   (*ppClientMem)->mpNext = NULL;
+
+   // Initialize workqueue for poll()
+   init_waitqueue_head( &(*ppClientMem)->mWaitQueue );
+
+   // End Critical section
+   spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
+   
+   return (int)( (*ppClientMem)->mClientID );
+}
+
+/*===========================================================================
+METHOD:
+   ReleaseClientID (Public Method)
+
+DESCRIPTION:
+   Release QMI client and free memory
+
+PARAMETERS:
+   pDev           [ I ] - Device specific memory
+   clientID       [ I ] - Requester's client ID
+
+RETURN VALUE:
+   None
+===========================================================================*/
+static 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 && pDev->mpNetDev->udev->state)
+   {
+      // 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 = QMIXactionIDGet( pDev );
+
+         result = QMICTLReleaseClientIDReq( pWriteBuffer, 
+                                            writeBufferSize,
+                                            transactionID,
+                                            clientID );
+         if (result < 0)
+         {
+            kfree( pWriteBuffer );
+            DBG( "error %d filling req buffer\n", result );
+         }
+         else
+         {
+            result = WriteSync( pDev,
+                                pWriteBuffer,
+                                writeBufferSize,
+                                QMICTL );
+            kfree( pWriteBuffer );
+
+            if (result < 0)
+            {
+               DBG( "bad write status %d\n", result );
+            }
+            else
+            {
+               result = ReadSync( pDev,
+                                  &pReadBuffer,
+                                  QMICTL,
+                                  transactionID );
+               if (result < 0)
+               {
+                  DBG( "bad read status %d\n", result );
+               }
+               else
+               {
+                  readBufferSize = result;
+
+                  result = QMICTLReleaseClientIDResp( pReadBuffer,
+                                                      readBufferSize );
+                  kfree( pReadBuffer );
+
+                  if (result < 0)
+                  {
+                     DBG( "error %d parsing response\n", result );
+                  }
+               }
+            }
+         }
+      }
+   }
+
+   // Cleaning up client memory
+   
+   // Critical section
+   spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
+
+   // Can't use FindClientMem, I need to keep pointer of previous
+   ppDelClientMem = &pDev->mQMIDev.mpClientMemList;
+   while (*ppDelClientMem != NULL)
+   {
+      if ((*ppDelClientMem)->mClientID == clientID)
+      {
+         pNextClientMem = (*ppDelClientMem)->mpNext;
+
+         // Notify all clients
+         while (NotifyAndPopNotifyList( pDev,
+                                        clientID,
+                                        0 ) == true );         
+
+         // Kill and free all URB's
+         pDelURB = PopFromURBList( pDev, clientID );
+         while (pDelURB != NULL)
+         {
+            usb_kill_urb( pDelURB );
+            usb_free_urb( pDelURB );
+            pDelURB = PopFromURBList( pDev, clientID );
+         }
+
+         // Free any unread data
+         while (PopFromReadMemList( pDev, 
+                                    clientID,
+                                    0,
+                                    &pDelData,
+                                    &dataSize ) == true )
+         {
+            kfree( pDelData );
+         }
+
+         // Delete client Mem
+         if (!waitqueue_active( &(*ppDelClientMem)->mWaitQueue))
+            kfree( *ppDelClientMem );
+         else
+            INFO("memory leak!\n");
+
+         // Overwrite the pointer that was to this client mem
+         *ppDelClientMem = pNextClientMem;
+      }
+      else
+      {
+         // I now point to (a pointer of ((the node I was at)'s mpNext))
+         ppDelClientMem = &(*ppDelClientMem)->mpNext;
+      }
+   }
+   
+   // End Critical section
+   spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
+
+   return;
+}
+
+/*===========================================================================
+METHOD:
+   FindClientMem (Public Method)
+
+DESCRIPTION:
+   Find this client's memory
+
+   Caller MUST have lock on mClientMemLock
+
+PARAMETERS:
+   pDev           [ I ] - Device specific memory
+   clientID       [ I ] - Requester's client ID
+
+RETURN VALUE:
+   sClientMemList - Pointer to requested sClientMemList for success
+                    NULL for error
+===========================================================================*/
+static sClientMemList * FindClientMem( 
+   sGobiUSBNet *      pDev,
+   u16              clientID )
+{
+   sClientMemList * pClientMem;
+   
+   if (IsDeviceValid( pDev ) == false)
+   {
+      DBG( "Invalid device\n" );
+      return NULL;
+   }
+   
+#ifdef CONFIG_SMP
+   // Verify Lock
+   if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0)
+   {
+      DBG( "unlocked\n" );
+      BUG();
+   }
+#endif
+   
+   pClientMem = pDev->mQMIDev.mpClientMemList;
+   while (pClientMem != NULL)
+   {
+      if (pClientMem->mClientID == clientID)
+      {
+         // Success
+         VDBG("Found client's 0x%x memory\n", clientID);
+         return pClientMem;
+      }
+      
+      pClientMem = pClientMem->mpNext;
+   }
+
+   DBG( "Could not find client mem 0x%04X\n", clientID );
+   return NULL;
+}
+
+/*===========================================================================
+METHOD:
+   AddToReadMemList (Public Method)
+
+DESCRIPTION:
+   Add Data to this client's ReadMem list
+   
+   Caller MUST have lock on mClientMemLock
+
+PARAMETERS:
+   pDev           [ I ] - Device specific memory
+   clientID       [ I ] - Requester's client ID
+   transactionID  [ I ] - Transaction ID or 0 for any
+   pData          [ I ] - Data to add
+   dataSize       [ I ] - Size of data to add
+
+RETURN VALUE:
+   bool
+===========================================================================*/
+static 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
+===========================================================================*/
+static bool PopFromReadMemList( 
+   sGobiUSBNet *      pDev,
+   u16                  clientID,
+   u16                  transactionID,
+   void **              ppData,
+   u16 *                pDataSize )
+{
+   sClientMemList * pClientMem;
+   sReadMemList * pDelReadMemList, ** ppReadMemList;
+
+#ifdef CONFIG_SMP
+   // Verify Lock
+   if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0)
+   {
+      DBG( "unlocked\n" );
+      BUG();
+   }
+#endif
+
+   // Get this client's memory location
+   pClientMem = FindClientMem( pDev, clientID );
+   if (pClientMem == NULL)
+   {
+      DBG( "Could not find this client's memory 0x%04X\n",
+           clientID );
+
+      return false;
+   }
+   
+   ppReadMemList = &(pClientMem->mpList);
+   pDelReadMemList = NULL;
+   
+   // Find first message that matches this transaction ID
+   while (*ppReadMemList != NULL)
+   {
+      // Do we care about transaction ID?
+      if (transactionID == 0
+      ||  transactionID == (*ppReadMemList)->mTransactionID )
+      {
+         pDelReadMemList = *ppReadMemList;
+         VDBG(  "*ppReadMemList = 0x%p pDelReadMemList = 0x%p\n",
+               *ppReadMemList, pDelReadMemList );
+         break;
+      }
+      
+      VDBG( "skipping 0x%04X data TID = %x\n", clientID, (*ppReadMemList)->mTransactionID );
+      
+      // Next
+      ppReadMemList = &(*ppReadMemList)->mpNext;
+   }
+   VDBG(  "*ppReadMemList = 0x%p pDelReadMemList = 0x%p\n",
+         *ppReadMemList, pDelReadMemList );
+   if (pDelReadMemList != NULL)
+   {
+      *ppReadMemList = (*ppReadMemList)->mpNext;
+      
+      // Copy to output
+      *ppData = pDelReadMemList->mpData;
+      *pDataSize = pDelReadMemList->mDataSize;
+      VDBG(  "*ppData = 0x%p pDataSize = %u\n",
+            *ppData, *pDataSize );
+      
+      // Free memory
+      kfree( pDelReadMemList );
+      
+      return true;
+   }
+   else
+   {
+      DBG( "No read memory to pop, Client 0x%04X, TID = %x\n", 
+           clientID, 
+           transactionID );
+      return false;
+   }
+}
+
+/*===========================================================================
+METHOD:
+   AddToNotifyList (Public Method)
+
+DESCRIPTION:
+   Add Notify entry to this client's notify List
+   
+   Caller MUST have lock on mClientMemLock
+
+PARAMETERS:
+   pDev              [ I ] - Device specific memory
+   clientID          [ I ] - Requester's client ID
+   transactionID     [ I ] - Transaction ID or 0 for any
+   pNotifyFunct      [ I ] - Callback function to be run when data is available
+   pData             [ I ] - Data buffer that willl be passed (unmodified) 
+                             to callback
+
+RETURN VALUE:
+   bool
+===========================================================================*/
+static 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
+===========================================================================*/
+static 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
+===========================================================================*/
+static 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
+===========================================================================*/
+static 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;
+   }
+}
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 3,19,0 ))
+#ifndef f_dentry
+#define f_dentry f_path.dentry
+#endif
+#endif
+
+/*=========================================================================*/
+// Internal userspace wrappers
+/*=========================================================================*/
+
+/*===========================================================================
+METHOD:
+   UserspaceunlockedIOCTL (Public Method)
+
+DESCRIPTION:
+   Internal wrapper for Userspace IOCTL interface
+
+PARAMETERS
+   pFilp        [ I ] - userspace file descriptor
+   cmd          [ I ] - IOCTL command
+   arg          [ I ] - IOCTL argument
+
+RETURN VALUE:
+   long - 0 for success
+         Negative errno for failure
+===========================================================================*/
+static long UserspaceunlockedIOCTL(
+   struct file *     pFilp,
+   unsigned int      cmd, 
+   unsigned long     arg )
+{
+   int result;
+   u32 devVIDPID;
+
+   sQMIFilpStorage * pFilpData = (sQMIFilpStorage *)pFilp->private_data;
+
+   if (pFilpData == NULL)
+   {
+      DBG( "Bad file data\n" );
+      return -EBADF;
+   }
+   
+   if (IsDeviceValid( pFilpData->mpDev ) == false)
+   {
+      DBG( "Invalid device! Updating f_ops\n" );
+      pFilp->f_op = pFilp->f_dentry->d_inode->i_fop;
+      return -ENXIO;
+   }
+
+   switch (cmd)
+   {
+      case IOCTL_QMI_GET_SERVICE_FILE:    
+         DBG( "Setting up QMI for service %lu\n", arg );
+         if ((u8)arg == 0)
+         {
+            DBG( "Cannot use QMICTL from userspace\n" );
+            return -EINVAL;
+         }
+
+         // Connection is already setup
+         if (pFilpData->mClientID != (u16)-1)
+         {
+            DBG( "Close the current connection before opening a new one\n" );
+            return -EBADR;
+         }
+         
+         result = GetClientID( pFilpData->mpDev, (u8)arg );
+// it seems QMIWDA only allow one client, if the last quectel-CM donot realese it (killed by SIGKILL).
+// can force release it at here
+#if 1
+         if (result < 0 && (u8)arg == QMIWDA)
+         {
+             ReleaseClientID( pFilpData->mpDev, QMIWDA | (1 << 8) );
+             result = GetClientID( pFilpData->mpDev, (u8)arg );
+         }
+#endif
+         if (result < 0)
+         {
+            return result;
+         }
+         pFilpData->mClientID = (u16)result;
+         DBG("pFilpData->mClientID = 0x%x\n", pFilpData->mClientID );
+         return 0;
+         break;
+
+
+      case IOCTL_QMI_GET_DEVICE_VIDPID:
+         if (arg == 0)
+         {
+            DBG( "Bad VIDPID buffer\n" );
+            return -EINVAL;
+         }
+         
+         // Extra verification
+         if (pFilpData->mpDev->mpNetDev == 0)
+         {
+            DBG( "Bad mpNetDev\n" );
+            return -ENOMEM;
+         }
+         if (pFilpData->mpDev->mpNetDev->udev == 0)
+         {
+            DBG( "Bad udev\n" );
+            return -ENOMEM;
+         }
+
+         devVIDPID = ((le16_to_cpu( pFilpData->mpDev->mpNetDev->udev->descriptor.idVendor ) << 16)
+                     + le16_to_cpu( pFilpData->mpDev->mpNetDev->udev->descriptor.idProduct ) );
+
+         result = copy_to_user( (unsigned int *)arg, &devVIDPID, 4 );
+         if (result != 0)
+         {
+            DBG( "Copy to userspace failure %d\n", result );
+         }
+
+         return result;
+                 
+         break;
+
+      case IOCTL_QMI_GET_DEVICE_MEID:
+         if (arg == 0)
+         {
+            DBG( "Bad MEID buffer\n" );
+            return -EINVAL;
+         }
+         
+         result = copy_to_user( (unsigned int *)arg, &pFilpData->mpDev->mMEID[0], 14 );
+         if (result != 0)
+         {
+            DBG( "Copy to userspace failure %d\n", result );
+         }
+
+         return result;
+                 
+         break;
+         
+      default:
+         return -EBADRQC;       
+   }
+}
+
+/*=========================================================================*/
+// Userspace wrappers
+/*=========================================================================*/
+
+/*===========================================================================
+METHOD:
+   UserspaceOpen (Public Method)
+
+DESCRIPTION:
+   Userspace open
+      IOCTL must be called before reads or writes
+
+PARAMETERS
+   pInode       [ I ] - kernel file descriptor
+   pFilp        [ I ] - userspace file descriptor
+
+RETURN VALUE:
+   int - 0 for success
+         Negative errno for failure
+===========================================================================*/
+static int UserspaceOpen(
+   struct inode *         pInode,
+   struct file *          pFilp )
+{
+   sQMIFilpStorage * pFilpData;
+
+   // Optain device pointer from pInode
+   sQMIDev * pQMIDev = container_of( pInode->i_cdev,
+                                     sQMIDev,
+                                     mCdev );
+   sGobiUSBNet * pDev = container_of( pQMIDev,
+                                    sGobiUSBNet,
+                                    mQMIDev );
+
+   if (pDev->mbMdm9x07)
+   {
+      atomic_inc(&pDev->refcount);
+      if (!pDev->mbQMIReady) {
+         if (wait_for_completion_interruptible_timeout(&pDev->mQMIReadyCompletion, 15*HZ) <= 0) {
+            if (atomic_dec_and_test(&pDev->refcount)) {
+               kfree( pDev );
+            }
+            return -ETIMEDOUT;
+         }
+      }
+      atomic_dec(&pDev->refcount);
+   }
+
+   if (IsDeviceValid( pDev ) == false)
+   {
+      DBG( "Invalid device\n" );
+      return -ENXIO;
+   }
+
+   // Setup data in pFilp->private_data
+   pFilp->private_data = kmalloc( sizeof( sQMIFilpStorage ), GFP_KERNEL );
+   if (pFilp->private_data == NULL)
+   {
+      DBG( "Mem error\n" );
+      return -ENOMEM;
+   }
+
+   pFilpData = (sQMIFilpStorage *)pFilp->private_data;
+   pFilpData->mClientID = (u16)-1;
+   pFilpData->mpDev = pDev;
+   atomic_inc(&pFilpData->mpDev->refcount);
+
+   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
+===========================================================================*/
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,36 ))
+static int UserspaceIOCTL(
+   struct inode *    pUnusedInode,
+   struct file *     pFilp,
+   unsigned int      cmd,
+   unsigned long     arg ) 
+{
+   // call the internal wrapper function
+   return (int)UserspaceunlockedIOCTL( pFilp, cmd, arg );  
+}
+#endif
+
+#ifdef quectel_no_for_each_process
+static int UserspaceClose(
+   struct inode *         pInode,
+   struct file *          pFilp )
+{
+   sQMIFilpStorage * pFilpData = (sQMIFilpStorage *)pFilp->private_data;
+
+   if (pFilpData == NULL)
+   {
+      DBG( "bad file data\n" );
+      return -EBADF;
+   }
+
+   atomic_dec(&pFilpData->mpDev->refcount);
+   
+   if (IsDeviceValid( pFilpData->mpDev ) == false)
+   {
+      return -ENXIO;
+   }
+   
+   DBG( "0x%04X\n", pFilpData->mClientID );
+   
+   // Disable pFilpData so they can't keep sending read or write 
+   //    should this function hang
+   // Note: memory pointer is still saved in pFilpData to be deleted later
+   pFilp->private_data = NULL;
+
+   if (pFilpData->mClientID != (u16)-1)
+   {
+      if (pFilpData->mpDev->mbDeregisterQMIDevice)
+         pFilpData->mClientID = (u16)-1; //DeregisterQMIDevice() will release this ClientID
+      else
+      ReleaseClientID( pFilpData->mpDev,
+                       pFilpData->mClientID );
+   }
+      
+   kfree( pFilpData );
+   return 0;
+}
+#else
+/*===========================================================================
+METHOD:
+   UserspaceClose (Public Method)
+
+DESCRIPTION:
+   Userspace close
+      Release client ID and free memory
+
+PARAMETERS
+   pFilp           [ I ] - userspace file descriptor
+   unusedFileTable [ I ] - (unused) file table
+
+RETURN VALUE:
+   int - 0 for success
+         Negative errno for failure
+===========================================================================*/
+#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,14 ))
+int UserspaceClose( 
+   struct file *       pFilp,
+   fl_owner_t          unusedFileTable )
+#else
+int UserspaceClose( struct file *       pFilp )
+#endif
+{
+   sQMIFilpStorage * pFilpData = (sQMIFilpStorage *)pFilp->private_data;
+   struct task_struct * pEachTask;
+   struct fdtable * pFDT;
+   int count = 0;
+   int used = 0;
+   unsigned long flags;
+
+   if (pFilpData == NULL)
+   {
+      DBG( "bad file data\n" );
+      return -EBADF;
+   }
+
+   // Fallthough.  If f_count == 1 no need to do more checks
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,24 ))
+   if (atomic_read( &pFilp->f_count ) != 1)
+#else
+   if (atomic_long_read( &pFilp->f_count ) != 1)
+#endif
+   {
+      rcu_read_lock();
+      for_each_process( pEachTask )
+      {
+         task_lock(pEachTask);
+         if (pEachTask == NULL || pEachTask->files == NULL)
+         {
+            // Some tasks may not have files (e.g. Xsession)
+            task_unlock(pEachTask);
+            continue;
+         }
+         spin_lock_irqsave( &pEachTask->files->file_lock, flags );
+         task_unlock(pEachTask); //kernel/exit.c:do_exit() -> fs/file.c:exit_files()
+         pFDT = files_fdtable( pEachTask->files );
+         for (count = 0; count < pFDT->max_fds; count++)
+         {
+            // Before this function was called, this file was removed
+            // from our task's file table so if we find it in a file
+            // table then it is being used by another task
+            if (pFDT->fd[count] == pFilp)
+            {
+               used++;
+               break;
+            }
+         }
+         spin_unlock_irqrestore( &pEachTask->files->file_lock, flags );
+      }
+      rcu_read_unlock();
+      
+      if (used > 0)
+      {
+         DBG( "not closing, as this FD is open by %d other process\n", used );
+         return 0;
+      }
+   }
+
+   if (IsDeviceValid( pFilpData->mpDev ) == false)
+   {
+      DBG( "Invalid device! Updating f_ops\n" );
+      pFilp->f_op = pFilp->f_dentry->d_inode->i_fop;
+      return -ENXIO;
+   }
+   
+   DBG( "0x%04X\n", pFilpData->mClientID );
+   
+   // Disable pFilpData so they can't keep sending read or write 
+   //    should this function hang
+   // Note: memory pointer is still saved in pFilpData to be deleted later
+   pFilp->private_data = NULL;
+
+   if (pFilpData->mClientID != (u16)-1)
+   {
+      if (pFilpData->mpDev->mbDeregisterQMIDevice)
+         pFilpData->mClientID = (u16)-1; //DeregisterQMIDevice() will release this ClientID
+      else
+      ReleaseClientID( pFilpData->mpDev,
+                       pFilpData->mClientID );
+   }
+   atomic_dec(&pFilpData->mpDev->refcount);
+      
+   kfree( pFilpData );
+   return 0;
+}
+#endif
+
+/*===========================================================================
+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
+===========================================================================*/
+static ssize_t UserspaceRead( 
+   struct file *          pFilp,
+   char __user *          pBuf, 
+   size_t                 size,
+   loff_t *               pUnusedFpos )
+{
+   int result;
+   void * pReadData = NULL;
+   void * pSmallReadData;
+   sQMIFilpStorage * pFilpData = (sQMIFilpStorage *)pFilp->private_data;
+
+   if (pFilpData == NULL)
+   {
+      DBG( "Bad file data\n" );
+      return -EBADF;
+   }
+
+   if (IsDeviceValid( pFilpData->mpDev ) == false)
+   {
+      DBG( "Invalid device! Updating f_ops\n" );
+      pFilp->f_op = pFilp->f_dentry->d_inode->i_fop;
+      return -ENXIO;
+   }
+   
+   if (pFilpData->mClientID == (u16)-1)
+   {
+      DBG( "Client ID must be set before reading 0x%04X\n",
+           pFilpData->mClientID );
+      return -EBADR;
+   }
+   
+   // Perform synchronous read
+   result = ReadSync( pFilpData->mpDev,
+                      &pReadData,
+                      pFilpData->mClientID,
+                      0 );
+   if (result <= 0)
+   {
+      return result;
+   }
+   
+   // Discard QMUX header
+   result -= QMUXHeaderSize();
+   pSmallReadData = pReadData + QMUXHeaderSize();
+
+   if (result > size)
+   {
+      DBG( "Read data is too large for amount user has requested\n" );
+      kfree( pReadData );
+      return -EOVERFLOW;
+   }
+
+   DBG(  "pBuf = 0x%p pSmallReadData = 0x%p, result = %d",
+         pBuf, pSmallReadData, result );
+
+   if (copy_to_user( pBuf, pSmallReadData, result ) != 0)
+   {
+      DBG( "Error copying read data to user\n" );
+      result = -EFAULT;
+   }
+   
+   // Reader is responsible for freeing read buffer
+   kfree( pReadData );
+   
+   return result;
+}
+
+/*===========================================================================
+METHOD:
+   UserspaceWrite (Public Method)
+
+DESCRIPTION:
+   Userspace write (synchronous)
+
+PARAMETERS
+   pFilp           [ I ] - userspace file descriptor
+   pBuf            [ I ] - write buffer
+   size            [ I ] - size of write buffer
+   pUnusedFpos     [ I ] - (unused) file position
+
+RETURN VALUE:
+   ssize_t - Number of bytes read for success
+             Negative errno for failure
+===========================================================================*/
+static ssize_t UserspaceWrite(
+   struct file *        pFilp, 
+   const char __user *  pBuf, 
+   size_t               size,
+   loff_t *             pUnusedFpos )
+{
+   int status;
+   void * pWriteBuffer;
+   sQMIFilpStorage * pFilpData = (sQMIFilpStorage *)pFilp->private_data;
+
+   if (pFilpData == NULL)
+   {
+      DBG( "Bad file data\n" );
+      return -EBADF;
+   }
+
+   if (IsDeviceValid( pFilpData->mpDev ) == false)
+   {
+      DBG( "Invalid device! Updating f_ops\n" );
+      pFilp->f_op = pFilp->f_dentry->d_inode->i_fop;
+      return -ENXIO;
+   }
+
+   if (pFilpData->mClientID == (u16)-1)
+   {
+      DBG( "Client ID must be set before writing 0x%04X\n",
+           pFilpData->mClientID );
+      return -EBADR;
+   }
+   
+   // Copy data from user to kernel space
+   pWriteBuffer = kmalloc( size + QMUXHeaderSize(), GFP_KERNEL );
+   if (pWriteBuffer == NULL)
+   {
+      return -ENOMEM;
+   }
+   status = copy_from_user( pWriteBuffer + QMUXHeaderSize(), pBuf, size );
+   if (status != 0)
+   {
+      DBG( "Unable to copy data from userspace %d\n", status );
+      kfree( pWriteBuffer );
+      return status;
+   }
+
+   status = WriteSync( pFilpData->mpDev,
+                       pWriteBuffer, 
+                       size + QMUXHeaderSize(),
+                       pFilpData->mClientID );
+
+   kfree( pWriteBuffer );
+   
+   // On success, return requested size, not full QMI reqest size
+   if (status == size + QMUXHeaderSize())
+   {
+      return size;
+   }
+   else
+   {
+      return status;
+   }
+}
+
+/*===========================================================================
+METHOD:
+   UserspacePoll (Public Method)
+
+DESCRIPTION:
+   Used to determine if read/write operations are possible without blocking
+
+PARAMETERS
+   pFilp              [ I ] - userspace file descriptor
+   pPollTable         [I/O] - Wait object to notify the kernel when data 
+                              is ready
+
+RETURN VALUE:
+   unsigned int - bitmask of what operations can be done immediately
+===========================================================================*/
+static unsigned int UserspacePoll(
+   struct file *                  pFilp,
+   struct poll_table_struct *     pPollTable )
+{
+   sQMIFilpStorage * pFilpData = (sQMIFilpStorage *)pFilp->private_data;
+   sClientMemList * pClientMem;
+   unsigned long flags;
+
+   // Always ready to write
+   unsigned long status = POLLOUT | POLLWRNORM;
+
+   if (pFilpData == NULL)
+   {
+      DBG( "Bad file data\n" );
+      return POLLERR;
+   }
+
+   if (IsDeviceValid( pFilpData->mpDev ) == false)
+   {
+      DBG( "Invalid device! Updating f_ops\n" );
+      pFilp->f_op = pFilp->f_dentry->d_inode->i_fop;
+      return POLLERR;
+   }
+
+   if (pFilpData->mpDev->mbDeregisterQMIDevice)
+   {
+      DBG( "DeregisterQMIDevice ing\n" );
+      return POLLHUP | POLLERR;
+   }
+
+   if (pFilpData->mClientID == (u16)-1)
+   {
+      DBG( "Client ID must be set before polling 0x%04X\n",
+           pFilpData->mClientID );
+      return POLLERR;
+   }
+
+   // Critical section
+   spin_lock_irqsave( &pFilpData->mpDev->mQMIDev.mClientMemLock, flags );
+
+   // Get this client's memory location
+   pClientMem = FindClientMem( pFilpData->mpDev, 
+                               pFilpData->mClientID );
+   if (pClientMem == NULL)
+   {
+      DBG( "Could not find this client's memory 0x%04X\n",
+           pFilpData->mClientID );
+
+      spin_unlock_irqrestore( &pFilpData->mpDev->mQMIDev.mClientMemLock, 
+                              flags );
+      return POLLERR;
+   }
+   
+   poll_wait( pFilp, &pClientMem->mWaitQueue, pPollTable );
+
+   if (pClientMem->mpList != NULL)
+   {
+      status |= POLLIN | POLLRDNORM;
+   }
+
+   // End critical section
+   spin_unlock_irqrestore( &pFilpData->mpDev->mQMIDev.mClientMemLock, flags );
+
+   // Always ready to write 
+   return (status | POLLOUT | POLLWRNORM);
+}
+
+/*=========================================================================*/
+// Initializer and destructor
+/*=========================================================================*/
+static int QMICTLSyncProc(sGobiUSBNet *pDev)
+{
+   void *pWriteBuffer;
+   void *pReadBuffer;
+   int result;
+   u16 writeBufferSize;
+   u16 readBufferSize;
+   u8 transactionID;
+   unsigned long flags;
+
+   if (IsDeviceValid( pDev ) == false)
+   {
+      DBG( "Invalid device\n" );
+      return -EFAULT;
+   }
+
+   writeBufferSize= QMICTLSyncReqSize();
+   pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
+   if (pWriteBuffer == NULL)
+   {
+      return -ENOMEM;
+   }
+
+   transactionID = QMIXactionIDGet(pDev);
+
+   /* send a QMI_CTL_SYNC_REQ (0x0027) */
+   result = QMICTLSyncReq( pWriteBuffer,
+                           writeBufferSize,
+                           transactionID );
+   if (result < 0)
+   {
+      kfree( pWriteBuffer );
+      return result;
+   }
+
+   result = WriteSync( pDev,
+                       pWriteBuffer,
+                       writeBufferSize,
+                       QMICTL );
+
+   if (result < 0)
+   {
+      kfree( pWriteBuffer );
+      return result;
+   }
+
+   // QMI CTL Sync Response
+   result = ReadSync( pDev,
+                      &pReadBuffer,
+                      QMICTL,
+                      transactionID );
+   if (result < 0)
+   {
+      return result;
+   }
+
+   result = QMICTLSyncResp( pReadBuffer,
+                            (u16)result );
+
+   kfree( pReadBuffer );
+
+   if (result < 0) /* need to re-sync */
+   {
+      DBG( "sync response error code %d\n", result );
+      /* start timer and wait for the response */
+      /* process response */
+      return result;
+   }
+
+#if 1 //free these ununsed qmi response, or when these transactionID re-used, they will be regarded as qmi response of the qmi request that have same transactionID
+   // Enter critical section
+   spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
+
+   // Free any unread data
+   while (PopFromReadMemList( pDev, QMICTL, 0, &pReadBuffer, &readBufferSize) == true) {       
+       kfree( pReadBuffer ); 
+   }
+   
+   // End critical section
+   spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );    
+#endif
+  
+   // Success
+   return 0;
+}
+
+static int qmi_sync_thread(void *data) {
+    sGobiUSBNet * pDev = (sGobiUSBNet *)data;
+    int result = 0;
+
+#if 1  
+   // Device is not ready for QMI connections right away
+   //   Wait up to 30 seconds before failing
+   if (QMIReady( pDev, 30000 ) == false)
+   {
+      DBG( "Device unresponsive to QMI\n" );
+      goto __qmi_sync_finished;
+   }
+
+   // Initiate QMI CTL Sync Procedure
+   DBG( "Sending QMI CTL Sync Request\n" );
+   result = QMICTLSyncProc(pDev);
+   if (result != 0)
+   {
+      DBG( "QMI CTL Sync Procedure Error\n" );
+      goto __qmi_sync_finished;
+   }
+   else
+   {
+      DBG( "QMI CTL Sync Procedure Successful\n" );
+   }
+
+#if defined(QUECTEL_WWAN_QMAP)
+if (pDev->m_qmap_mode) {
+   // Setup Data Format
+   result = QMIWDASetDataFormat (pDev, pDev->m_qmap_mode, &pDev->qmap_size);
+   if (result != 0)
+   {
+      goto __qmi_sync_finished;
+   }
+   pDev->mpNetDev->rx_urb_size = pDev->qmap_size;
+}
+#endif
+
+   // Setup WDS callback
+   result = SetupQMIWDSCallback( pDev );
+   if (result != 0)
+   {
+      goto __qmi_sync_finished;
+   }
+
+   // Fill MEID for device
+   result = QMIDMSGetMEID( pDev );
+   if (result != 0)
+   {
+      goto __qmi_sync_finished;
+   }
+#endif
+
+__qmi_sync_finished:
+   pDev->mbQMIReady = true;
+   complete_all(&pDev->mQMIReadyCompletion);
+   if (atomic_dec_and_test(&pDev->refcount)) {
+      kfree( pDev );
+   }
+   return result;
+}
+
+/*===========================================================================
+METHOD:
+   RegisterQMIDevice (Public Method)
+
+DESCRIPTION:
+   QMI Device initialization function
+
+PARAMETERS:
+   pDev     [ I ] - Device specific memory
+   
+RETURN VALUE:
+   int - 0 for success
+         Negative errno for failure
+===========================================================================*/
+int RegisterQMIDevice( sGobiUSBNet * pDev )
+{
+   int result;
+   int GobiQMIIndex = 0;
+   dev_t devno; 
+   char * pDevName;
+
+   if (pDev->mQMIDev.mbCdevIsInitialized == true)
+   {
+      // Should never happen, but always better to check
+      DBG( "device already exists\n" );
+      return -EEXIST;
+   }
+   pDev->mbQMIValid = true;
+   pDev->mbDeregisterQMIDevice = false;
+
+   // Set up for QMICTL
+   //    (does not send QMI message, just sets up memory)
+   result = GetClientID( pDev, QMICTL );
+   if (result != 0)
+   {
+      pDev->mbQMIValid = false;
+      return result;
+   }
+   atomic_set( &pDev->mQMIDev.mQMICTLTransactionID, 1 );
+
+   // Start Async reading
+   result = StartRead( pDev );
+   if (result != 0)
+   {
+      pDev->mbQMIValid = false;
+      return result;
+   }
+
+   if (pDev->mbMdm9x07)
+   {
+      usb_control_msg( pDev->mpNetDev->udev,
+                            usb_sndctrlpipe( pDev->mpNetDev->udev, 0 ),
+                             SET_CONTROL_LINE_STATE_REQUEST,
+                             SET_CONTROL_LINE_STATE_REQUEST_TYPE,
+                             CONTROL_DTR,
+                             /* USB interface number to receive control message */
+                             pDev->mpIntf->cur_altsetting->desc.bInterfaceNumber,
+                             NULL,
+                             0,
+                             100 );
+   }
+
+    //for EC21&25, must wait about 15 seconds to wait QMI ready. it is too long for driver probe(will block other drivers probe).
+   if (pDev->mbMdm9x07)
+   {
+      struct task_struct *qmi_sync_task;
+      atomic_inc(&pDev->refcount);
+      init_completion(&pDev->mQMIReadyCompletion);
+      pDev->mbQMIReady = false;
+      qmi_sync_task = kthread_run(qmi_sync_thread, (void *)pDev, "qmi_sync/%d", pDev->mpNetDev->udev->devnum);
+       if (IS_ERR(qmi_sync_task)) {
+         atomic_dec(&pDev->refcount);
+         DBG( "Create qmi_sync_thread fail\n" );
+         return PTR_ERR(qmi_sync_task);
+      }
+      goto __register_chardev_qccmi;
+   }
+   
+   // Device is not ready for QMI connections right away
+   //   Wait up to 30 seconds before failing
+   if (QMIReady( pDev, 30000 ) == false)
+   {
+      DBG( "Device unresponsive to QMI\n" );
+      return -ETIMEDOUT;
+   }
+
+   // Initiate QMI CTL Sync Procedure
+   DBG( "Sending QMI CTL Sync Request\n" );
+   result = QMICTLSyncProc(pDev);
+   if (result != 0)
+   {
+      DBG( "QMI CTL Sync Procedure Error\n" );
+      return result;
+   }
+   else
+   {
+      DBG( "QMI CTL Sync Procedure Successful\n" );
+   }
+
+   // Setup Data Format
+#if defined(QUECTEL_WWAN_QMAP)   
+   result = QMIWDASetDataFormat (pDev, pDev->m_qmap_mode, NULL);
+#else
+   result = QMIWDASetDataFormat (pDev, 0, NULL);
+#endif
+   if (result != 0)
+   {
+       return result;
+   }
+
+   // Setup WDS callback
+   result = SetupQMIWDSCallback( pDev );
+   if (result != 0)
+   {
+         return result;
+   }
+
+   // Fill MEID for device
+   result = QMIDMSGetMEID( pDev );
+   if (result != 0)
+   {
+      return result;
+   }
+
+__register_chardev_qccmi:
+   // allocate and fill devno with numbers
+   result = alloc_chrdev_region( &devno, 0, 1, "qcqmi" );
+   if (result < 0)
+   {
+         return result;
+   }
+
+   // Create cdev
+   cdev_init( &pDev->mQMIDev.mCdev, &UserspaceQMIFops );
+   pDev->mQMIDev.mCdev.owner = THIS_MODULE;
+   pDev->mQMIDev.mCdev.ops = &UserspaceQMIFops;
+   pDev->mQMIDev.mbCdevIsInitialized = true;
+
+   result = cdev_add( &pDev->mQMIDev.mCdev, devno, 1 );
+   if (result != 0)
+   {
+      DBG( "error adding cdev\n" );
+      return result;
+   }
+
+   // Match interface number (usb# or eth#)
+   if (!!(pDevName = strstr( pDev->mpNetDev->net->name, "eth" ))) {
+       pDevName += strlen( "eth" );
+   } else if (!!(pDevName = strstr( pDev->mpNetDev->net->name, "usb" ))) {
+       pDevName += strlen( "usb" );
+#if 1 //openWRT like use ppp# or lte#
+   } else if (!!(pDevName = strstr( pDev->mpNetDev->net->name, "ppp" ))) {
+       pDevName += strlen( "ppp" );
+   } else if (!!(pDevName = strstr( pDev->mpNetDev->net->name, "lte" ))) {
+       pDevName += strlen( "lte" );
+#endif
+   } else {
+      DBG( "Bad net name: %s\n", pDev->mpNetDev->net->name );
+      return -ENXIO;
+   }
+   GobiQMIIndex = simple_strtoul( pDevName, NULL, 10 );
+   if (GobiQMIIndex < 0)
+   {
+      DBG( "Bad minor number\n" );
+      return -ENXIO;
+   }
+
+   // Always print this output
+   printk( KERN_INFO "creating qcqmi%d\n",
+           GobiQMIIndex );
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 2,6,27 ))
+   // kernel 2.6.27 added a new fourth parameter to device_create
+   //    void * drvdata : the data to be added to the device for callbacks
+   device_create( pDev->mQMIDev.mpDevClass,
+                  &pDev->mpIntf->dev, 
+                  devno,
+                  NULL,
+                  "qcqmi%d", 
+                  GobiQMIIndex );
+#else
+   device_create( pDev->mQMIDev.mpDevClass,
+                  &pDev->mpIntf->dev, 
+                  devno,
+                  "qcqmi%d", 
+                  GobiQMIIndex );
+#endif
+   
+   pDev->mQMIDev.mDevNum = devno;
+
+   // Success
+   return 0;
+}
+
+/*===========================================================================
+METHOD:
+   DeregisterQMIDevice (Public Method)
+
+DESCRIPTION:
+   QMI Device cleanup function
+   
+   NOTE: When this function is run the device is no longer valid
+
+PARAMETERS:
+   pDev     [ I ] - Device specific memory
+
+RETURN VALUE:
+   None
+===========================================================================*/
+void DeregisterQMIDevice( sGobiUSBNet * pDev )
+{
+#ifndef quectel_no_for_each_process
+   struct inode * pOpenInode;
+   struct list_head * pInodeList;
+   struct task_struct * pEachTask;
+   struct fdtable * pFDT;
+   struct file * pFilp;
+   int count = 0;
+#endif
+   unsigned long flags;
+   int tries;
+   int result;
+
+   // Should never happen, but check anyway
+   if (IsDeviceValid( pDev ) == false)
+   {
+      DBG( "wrong device\n" );
+      return;
+   }
+
+   pDev->mbDeregisterQMIDevice = true;
+
+   // Release all clients
+   spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
+   while (pDev->mQMIDev.mpClientMemList != NULL)
+   {
+      u16 mClientID = pDev->mQMIDev.mpClientMemList->mClientID;
+      if (waitqueue_active(&pDev->mQMIDev.mpClientMemList->mWaitQueue)) {
+         DBG("WaitQueue 0x%04X\n", mClientID);
+         wake_up_interruptible_sync( &pDev->mQMIDev.mpClientMemList->mWaitQueue );
+         spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );      
+         msleep(10);
+         spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
+         continue;
+      }
+
+      DBG( "release 0x%04X\n", pDev->mQMIDev.mpClientMemList->mClientID );
+   
+      spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
+      ReleaseClientID( pDev, mClientID );
+      // NOTE: pDev->mQMIDev.mpClientMemList will 
+      //       be updated in ReleaseClientID()
+      spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags );
+   }
+   spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags );
+
+   // Stop all reads
+   KillRead( pDev );
+
+   pDev->mbQMIValid = false;
+
+   if (pDev->mQMIDev.mbCdevIsInitialized == false)
+   {
+      return;
+   }
+
+#ifndef quectel_no_for_each_process
+   // Find each open file handle, and manually close it
+   
+   // Generally there will only be only one inode, but more are possible
+   list_for_each( pInodeList, &pDev->mQMIDev.mCdev.list )
+   {
+      // Get the inode
+      pOpenInode = container_of( pInodeList, struct inode, i_devices );
+      if (pOpenInode != NULL && (IS_ERR( pOpenInode ) == false))
+      {
+         // Look for this inode in each task
+
+         rcu_read_lock();
+         for_each_process( pEachTask )
+         {
+            task_lock(pEachTask);
+            if (pEachTask == NULL || pEachTask->files == NULL)
+            {
+               // Some tasks may not have files (e.g. Xsession)
+               task_unlock(pEachTask);
+               continue;
+            }
+            // For each file this task has open, check if it's referencing
+            // our inode.
+            spin_lock_irqsave( &pEachTask->files->file_lock, flags );
+            task_unlock(pEachTask);  //kernel/exit.c:do_exit() -> fs/file.c:exit_files()
+            pFDT = files_fdtable( pEachTask->files );
+            for (count = 0; count < pFDT->max_fds; count++)
+            {
+               pFilp = pFDT->fd[count];
+               if (pFilp != NULL &&  pFilp->f_dentry != NULL)
+               {
+                  if (pFilp->f_dentry->d_inode == pOpenInode)
+                  {
+                     // Close this file handle
+                     rcu_assign_pointer( pFDT->fd[count], NULL );                     
+                     spin_unlock_irqrestore( &pEachTask->files->file_lock, flags );
+                     
+                     DBG( "forcing close of open file handle\n" );
+                     filp_close( pFilp, pEachTask->files );
+
+                     spin_lock_irqsave( &pEachTask->files->file_lock, flags );
+                  }
+               }
+            }
+            spin_unlock_irqrestore( &pEachTask->files->file_lock, flags );
+         }
+         rcu_read_unlock();
+      }
+   }
+#endif
+
+if (pDev->mpNetDev->udev->state) {
+   // Send SetControlLineState request (USB_CDC)
+   result = usb_control_msg( pDev->mpNetDev->udev,
+                             usb_sndctrlpipe( pDev->mpNetDev->udev, 0 ),
+                             SET_CONTROL_LINE_STATE_REQUEST,
+                             SET_CONTROL_LINE_STATE_REQUEST_TYPE,
+                             0, // DTR not present
+                             /* USB interface number to receive control message */
+                             pDev->mpIntf->cur_altsetting->desc.bInterfaceNumber,
+                             NULL,
+                             0,
+                             100 );
+   if (result < 0)
+   {
+      DBG( "Bad SetControlLineState status %d\n", result );
+   }
+}
+
+   // Remove device (so no more calls can be made by users)
+   if (IS_ERR( pDev->mQMIDev.mpDevClass ) == false)
+   {
+      device_destroy( pDev->mQMIDev.mpDevClass, 
+                      pDev->mQMIDev.mDevNum );   
+   }
+
+   // Hold onto cdev memory location until everyone is through using it.
+   // Timeout after 30 seconds (10 ms interval).  Timeout should never happen,
+   // but exists to prevent an infinate loop just in case.
+   for (tries = 0; tries < 30 * 100; tries++)
+   {
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 4,11,0 ))
+      int ref = atomic_read( &pDev->mQMIDev.mCdev.kobj.kref.refcount );
+#else
+      int ref = kref_read( &pDev->mQMIDev.mCdev.kobj.kref );
+#endif
+      if (ref > 1)
+      {
+         DBG( "cdev in use by %d tasks\n", ref - 1 ); 
+         if (tries > 10)
+            INFO( "cdev in use by %d tasks\n", ref - 1 ); 
+         msleep( 10 );
+      }
+      else
+      {
+         break;
+      }
+   }
+
+   cdev_del( &pDev->mQMIDev.mCdev );
+   
+   unregister_chrdev_region( pDev->mQMIDev.mDevNum, 1 );
+
+   return;
+}
+
+/*=========================================================================*/
+// Driver level client management
+/*=========================================================================*/
+
+/*===========================================================================
+METHOD:
+   QMIReady (Public Method)
+
+DESCRIPTION:
+   Send QMI CTL GET VERSION INFO REQ and SET DATA FORMAT REQ
+   Wait for response or timeout
+
+PARAMETERS:
+   pDev     [ I ] - Device specific memory
+   timeout  [ I ] - Milliseconds to wait for response
+
+RETURN VALUE:
+   bool
+===========================================================================*/
+static bool QMIReady(
+   sGobiUSBNet *    pDev,
+   u16                timeout )
+{
+   int result;
+   void * pWriteBuffer;
+   u16 writeBufferSize;
+   void * pReadBuffer;
+   u16 readBufferSize;
+   u16 curTime;
+   unsigned long flags;
+   u8 transactionID;
+   u16 interval = 2000;
+   
+   if (IsDeviceValid( pDev ) == false)
+   {
+      DBG( "Invalid device\n" );
+      return false;
+   }
+
+   writeBufferSize = QMICTLReadyReqSize();
+   pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
+   if (pWriteBuffer == NULL)
+   {
+      return false;
+   }
+
+   // An implimentation of down_timeout has not been agreed on,
+   //    so it's been added and removed from the kernel several times.
+   //    We're just going to ignore it and poll the semaphore.
+
+   // Send a write every 1000 ms and see if we get a response
+   for (curTime = 0; curTime < timeout; curTime += interval)
+   {
+      // Start read
+      struct QuecSem *readSem = kmalloc(sizeof(struct QuecSem ), GFP_KERNEL);
+      readSem->magic = QUEC_SEM_MAGIC;
+      sema_init( &readSem->readSem, 0 );
+   
+      transactionID = QMIXactionIDGet( pDev );
+
+      result = ReadAsync( pDev, QMICTL, transactionID, UpSem, readSem );
+      if (result != 0)
+      {
+         kfree( pWriteBuffer );
+         return false;
+      }
+
+      // Fill buffer
+      result = QMICTLReadyReq( pWriteBuffer, 
+                               writeBufferSize,
+                               transactionID );
+      if (result < 0)
+      {
+         kfree( pWriteBuffer );
+         return false;
+      }
+
+      // Disregard status.  On errors, just try again
+      WriteSync( pDev,
+                 pWriteBuffer,
+                 writeBufferSize,
+                 QMICTL );
+
+#if 1
+      if (down_timeout( &readSem->readSem, msecs_to_jiffies(interval) ) == 0)
+#else
+      msleep( interval );
+      if (down_trylock( &readSem->readSem ) == 0)
+#endif
+      {
+         kfree(readSem);
+         // 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
+      {
+         readSem->magic = 0;
+         // 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
+===========================================================================*/
+static void QMIWDSCallback(
+   sGobiUSBNet *    pDev,
+   u16                clientID,
+   void *             pData )
+{
+   bool bRet;
+   int result;
+   void * pReadBuffer;
+   u16 readBufferSize;
+
+#if 0
+#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
+#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
+   {
+#if 0 //usbbet.c will do this job
+      // 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;
+      }
+#endif
+
+      if (bReconfigure == true)
+      {
+         DBG( "Net device link reset\n" );
+         GobiSetDownReason( pDev, NO_NDIS_CONNECTION );
+         GobiClearDownReason( pDev, NO_NDIS_CONNECTION );
+      }
+      else
+      {
+         if (bLinkState == true)
+         {
+            if (GobiTestDownReason( pDev, NO_NDIS_CONNECTION )) {
+                DBG( "Net device link is connected\n" );
+                GobiClearDownReason( pDev, NO_NDIS_CONNECTION );
+            }
+         }
+         else
+         {
+            if (!GobiTestDownReason( pDev, NO_NDIS_CONNECTION )) {
+                DBG( "Net device link is disconnected\n" );
+                GobiSetDownReason( pDev, NO_NDIS_CONNECTION );
+            }
+         }
+      }
+   }
+
+   kfree( pReadBuffer );
+
+   // Setup next read
+   result = ReadAsync( pDev,
+                       clientID,
+                       0,
+                       QMIWDSCallback,
+                       pData );
+   if (result != 0)
+   {
+      DBG( "unable to setup next async read\n" );
+   }
+
+   return;
+}
+
+/*===========================================================================
+METHOD:
+   SetupQMIWDSCallback (Public Method)
+
+DESCRIPTION:
+   Request client and fire off reqests and start async read for 
+   QMI WDS callback
+
+PARAMETERS:
+   pDev     [ I ] - Device specific memory
+
+RETURN VALUE:
+   int - 0 for success
+         Negative errno for failure
+===========================================================================*/
+static 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;
+
+#if 0 // add for "AT$QCRMCALL=1,1", be careful: donot enable these codes if use quectel-CM, or cannot obtain IP by udhcpc
+    if (pDev->mbMdm9x07)
+    {
+       void * pReadBuffer;
+       u16 readBufferSize;
+
+        writeBufferSize = QMIWDSSetQMUXBindMuxDataPortSize();
+        pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
+        if (pWriteBuffer == NULL)
+        {
+            return -ENOMEM;
+        }
+   
+        result = QMIWDSSetQMUXBindMuxDataPortReq( pWriteBuffer, 
+                                         writeBufferSize,
+                                         0x81,
+                                         3 );
+        if (result < 0)
+        {
+            kfree( pWriteBuffer );
+            return result;
+        }
+
+       result = WriteSync( pDev,
+                           pWriteBuffer,
+                           writeBufferSize,
+                           WDSClientID );
+        kfree( pWriteBuffer );
+
+        if (result < 0)
+        {
+            return result;
+        }
+       
+        result = ReadSync( pDev,
+                          &pReadBuffer,
+                          WDSClientID,
+                          3 );
+        if (result < 0)
+        {
+            return result;
+        }
+        readBufferSize = result;
+
+        kfree( pReadBuffer );
+   }
+#endif
+
+   // QMI WDS Set Event Report
+   writeBufferSize = QMIWDSSetEventReportReqSize();
+   pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
+   if (pWriteBuffer == NULL)
+   {
+      return -ENOMEM;
+   }
+   
+   result = QMIWDSSetEventReportReq( pWriteBuffer, 
+                                     writeBufferSize,
+                                     1 );
+   if (result < 0)
+   {
+      kfree( pWriteBuffer );
+      return result;
+   }
+
+   result = WriteSync( pDev,
+                       pWriteBuffer,
+                       writeBufferSize,
+                       WDSClientID );
+   kfree( pWriteBuffer );
+
+   if (result < 0)
+   {
+      return result;
+   }
+
+   // QMI WDS Get PKG SRVC Status
+   writeBufferSize = QMIWDSGetPKGSRVCStatusReqSize();
+   pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
+   if (pWriteBuffer == NULL)
+   {
+      return -ENOMEM;
+   }
+
+   result = QMIWDSGetPKGSRVCStatusReq( pWriteBuffer, 
+                                       writeBufferSize,
+                                       2 );
+   if (result < 0)
+   {
+      kfree( pWriteBuffer );
+      return result;
+   }
+   
+   result = WriteSync( pDev,
+                       pWriteBuffer,
+                       writeBufferSize,
+                       WDSClientID );
+   kfree( pWriteBuffer );
+
+   if (result < 0)
+   {
+      return result;
+   }
+
+   // Setup asnyc read callback
+   result = ReadAsync( pDev,
+                       WDSClientID,
+                       0,
+                       QMIWDSCallback,
+                       NULL );
+   if (result != 0)
+   {
+      DBG( "unable to setup async read\n" );
+      return result;
+   }
+
+   // Send SetControlLineState request (USB_CDC)
+   //   Required for Autoconnect
+   result = usb_control_msg( pDev->mpNetDev->udev,
+                             usb_sndctrlpipe( pDev->mpNetDev->udev, 0 ),
+                             SET_CONTROL_LINE_STATE_REQUEST,
+                             SET_CONTROL_LINE_STATE_REQUEST_TYPE,
+                             CONTROL_DTR,
+                             /* USB interface number to receive control message */
+                             pDev->mpIntf->cur_altsetting->desc.bInterfaceNumber,
+                             NULL,
+                             0,
+                             100 );
+   if (result < 0)
+   {
+      DBG( "Bad SetControlLineState status %d\n", result );
+      return result;
+   }
+
+   return 0;
+}
+
+/*===========================================================================
+METHOD:
+   QMIDMSGetMEID (Public Method)
+
+DESCRIPTION:
+   Register DMS client
+   send MEID req and parse response
+   Release DMS client
+
+PARAMETERS:
+   pDev     [ I ] - Device specific memory
+
+RETURN VALUE:
+   None
+===========================================================================*/
+static int QMIDMSGetMEID( sGobiUSBNet * pDev )
+{
+   int result;
+   void * pWriteBuffer;
+   u16 writeBufferSize;
+   void * pReadBuffer;
+   u16 readBufferSize;
+   u16 DMSClientID;
+
+   if (IsDeviceValid( pDev ) == false)
+   {
+      DBG( "Invalid device\n" );
+      return -EFAULT;
+   }
+
+   result = GetClientID( pDev, QMIDMS );
+   if (result < 0)
+   {
+      return result;
+   }
+   DMSClientID = result;
+
+   // QMI DMS Get Serial numbers Req
+   writeBufferSize = QMIDMSGetMEIDReqSize();
+   pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
+   if (pWriteBuffer == NULL)
+   {
+      return -ENOMEM;
+   }
+
+   result = QMIDMSGetMEIDReq( pWriteBuffer, 
+                              writeBufferSize,
+                              1 );
+   if (result < 0)
+   {
+      kfree( pWriteBuffer );
+      return result;
+   }
+
+   result = WriteSync( pDev,
+                       pWriteBuffer,
+                       writeBufferSize,
+                       DMSClientID );
+   kfree( pWriteBuffer );
+
+   if (result < 0)
+   {
+      return result;
+   }
+
+   // QMI DMS Get Serial numbers Resp
+   result = ReadSync( pDev,
+                      &pReadBuffer,
+                      DMSClientID,
+                      1 );
+   if (result < 0)
+   {
+      return result;
+   }
+   readBufferSize = result;
+
+   result = QMIDMSGetMEIDResp( pReadBuffer,
+                               readBufferSize,
+                               &pDev->mMEID[0],
+                               14 );
+   kfree( pReadBuffer );
+
+   if (result < 0)
+   {
+      DBG( "bad get MEID resp\n" );
+      
+      // Non fatal error, device did not return any MEID
+      //    Fill with 0's
+      memset( &pDev->mMEID[0], '0', 14 );
+   }
+
+   ReleaseClientID( pDev, DMSClientID );
+
+   // Success
+   return 0;
+}
+
+/*===========================================================================
+METHOD:
+   QMIWDASetDataFormat (Public Method)
+
+DESCRIPTION:
+   Register WDA client
+   send Data format request and parse response
+   Release WDA client
+
+PARAMETERS:
+   pDev     [ I ] - Device specific memory
+
+RETURN VALUE:
+   None
+===========================================================================*/
+static int QMIWDASetDataFormat( sGobiUSBNet * pDev, int qmap_mode, int *rx_urb_size )
+{
+   int result;
+   void * pWriteBuffer;
+   u16 writeBufferSize;
+   void * pReadBuffer;
+   u16 readBufferSize;
+   u16 WDAClientID;
+   u16 idProduct = le16_to_cpu(pDev->mpNetDev->udev->descriptor.idProduct);
+
+   DBG("\n");
+
+   if (IsDeviceValid( pDev ) == false)
+   {
+      DBG( "Invalid device\n" );
+      return -EFAULT;
+   }
+
+   result = GetClientID( pDev, QMIWDA );
+   if (result < 0)
+   {
+      return result;
+   }
+   WDAClientID = result;
+
+   // QMI WDA Set Data Format Request
+   writeBufferSize = QMIWDASetDataFormatReqSize(qmap_mode);
+   pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL );
+   if (pWriteBuffer == NULL)
+   {
+      return -ENOMEM;
+   }
+
+   result = QMIWDASetDataFormatReq( pWriteBuffer,
+                              writeBufferSize, pDev->mbRawIPMode,
+                              qmap_mode, (idProduct == 0x0800) ? (16*1024) : (32*1024), //SDX24&SDX55 support 32KB //if set as 32K, x55 will rx error pkt
+                              1 );
+   
+   if (result < 0)
+   {
+      kfree( pWriteBuffer );
+      return result;
+   }
+
+   result = WriteSync( pDev,
+                       pWriteBuffer,
+                       writeBufferSize,
+                       WDAClientID );
+   kfree( pWriteBuffer );
+
+   if (result < 0)
+   {
+      return result;
+   }
+
+   // QMI DMS Get Serial numbers Resp
+   result = ReadSync( pDev,
+                      &pReadBuffer,
+                      WDAClientID,
+                      1 );
+   if (result < 0)
+   {
+      return result;
+   }
+   readBufferSize = result;
+
+if (qmap_mode && rx_urb_size) {
+   int qmap_enabled = 0, rx_size = 0, tx_size = 0;
+   result = QMIWDASetDataFormatResp( pReadBuffer,
+                                     readBufferSize, pDev->mbRawIPMode, &qmap_enabled, &rx_size, &tx_size);
+    INFO( "qmap settings qmap_enabled=%d, rx_size=%d, tx_size=%d\n",
+        le32_to_cpu(qmap_enabled), le32_to_cpu(rx_size), le32_to_cpu(tx_size));
+
+    if (le32_to_cpu(qmap_enabled) == 5) {
+        *rx_urb_size = le32_to_cpu(rx_size);
+    } else {
+        *rx_urb_size = 0;
+        result = -EFAULT;
+    }
+} else {
+   int qmap_enabled = 0, rx_size = 0, tx_size = 0;
+   result = QMIWDASetDataFormatResp( pReadBuffer,
+                                     readBufferSize, pDev->mbRawIPMode, &qmap_enabled, &rx_size, &tx_size);
+}
+
+   kfree( pReadBuffer );
+
+   if (result < 0)
+   {
+      DBG( "Data Format Cannot be set\n" );
+   }
+
+   ReleaseClientID( pDev, WDAClientID );
+
+   // Success
+    return 0;
+}
+
+int QuecQMIWDASetDataFormat( sGobiUSBNet * pDev, int qmap_mode, int *rx_urb_size ) {
+       return QMIWDASetDataFormat(pDev, qmap_mode, rx_urb_size);
+}
diff --git a/drivers/net/usb/QMIDevice.h b/drivers/net/usb/QMIDevice.h
new file mode 100644 (file)
index 0000000..71af0af
--- /dev/null
@@ -0,0 +1,368 @@
+/*===========================================================================
+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
+
+   Internal userspace wrapper functions
+      UserspaceunlockedIOCTL
+
+   Userspace wrappers
+      UserspaceOpen
+      UserspaceIOCTL
+      UserspaceClose
+      UserspaceRead
+      UserspaceWrite
+      UserspacePoll
+
+   Initializer and destructor
+      RegisterQMIDevice
+      DeregisterQMIDevice
+
+   Driver level client management
+      QMIReady
+      QMIWDSCallback
+      SetupQMIWDSCallback
+      QMIDMSGetMEID
+
+Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of Code Aurora Forum nor
+      the names of its contributors may be used to endorse or promote
+      products derived from this software without specific prior written
+      permission.
+
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+===========================================================================*/
+
+//---------------------------------------------------------------------------
+// Pragmas
+//---------------------------------------------------------------------------
+#pragma once
+
+//---------------------------------------------------------------------------
+// Include Files
+//---------------------------------------------------------------------------
+#include "Structs.h"
+#include "QMI.h"
+
+/*=========================================================================*/
+// Generic functions
+/*=========================================================================*/
+
+#ifdef __QUECTEL_INTER__
+
+// Basic test to see if device memory is valid
+static bool IsDeviceValid( sGobiUSBNet * pDev );
+
+/*=========================================================================*/
+// Driver level asynchronous read functions
+/*=========================================================================*/
+
+// Resubmit interrupt URB, re-using same values
+static int ResubmitIntURB( struct urb * pIntURB );
+
+// Read callback
+//    Put the data in storage and notify anyone waiting for data
+#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,18 ))
+static void ReadCallback( struct urb * pReadURB );
+#else
+static void ReadCallback(struct urb *pReadURB, struct pt_regs *regs);
+#endif
+
+// Inturrupt callback
+//    Data is available, start a read URB
+#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,18 ))
+static void IntCallback( struct urb * pIntURB );
+#else
+static void IntCallback(struct urb *pIntURB, struct pt_regs *regs);
+#endif
+
+/*=========================================================================*/
+// Internal read/write functions
+/*=========================================================================*/
+
+// Start asynchronous read
+//     Reading client's data store, not device
+static int ReadAsync(
+   sGobiUSBNet *    pDev,
+   u16                clientID,
+   u16                transactionID,
+   void               (*pCallback)(sGobiUSBNet *, u16, void *),
+   void *             pData );
+
+// Notification function for synchronous read
+static void UpSem( 
+   sGobiUSBNet *    pDev,
+   u16                clientID,
+   void *             pData );
+
+// Start synchronous read
+//     Reading client's data store, not device
+static int ReadSync(
+   sGobiUSBNet *    pDev,
+   void **            ppOutBuffer,
+   u16                clientID,
+   u16                transactionID );
+
+// Write callback
+#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,18 ))
+static void WriteSyncCallback( struct urb * pWriteURB );
+#else
+static void WriteSyncCallback(struct urb *pWriteURB, struct pt_regs *regs);
+#endif
+
+// Start synchronous write
+static int WriteSync(
+   sGobiUSBNet *    pDev,
+   char *             pInWriteBuffer,
+   int                size,
+   u16                clientID );
+
+/*=========================================================================*/
+// Internal memory management functions
+/*=========================================================================*/
+
+// Create client and allocate memory
+static int GetClientID( 
+   sGobiUSBNet *      pDev,
+   u8                   serviceType );
+
+// Release client and free memory
+static void ReleaseClientID(
+   sGobiUSBNet *      pDev,
+   u16                  clientID );
+
+// Find this client's memory
+static sClientMemList * FindClientMem(
+   sGobiUSBNet *      pDev,
+   u16                  clientID );
+
+// Add Data to this client's ReadMem list
+static 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.
+static bool PopFromReadMemList( 
+   sGobiUSBNet *      pDev,
+   u16                  clientID,
+   u16                  transactionID,
+   void **              ppData,
+   u16 *                pDataSize );
+
+// Add Notify entry to this client's notify List
+static 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
+static bool NotifyAndPopNotifyList( 
+   sGobiUSBNet *      pDev,
+   u16                  clientID,
+   u16                  transactionID );
+
+// Add URB to this client's URB list
+static bool AddToURBList( 
+   sGobiUSBNet *      pDev,
+   u16                  clientID,
+   struct urb *         pURB );
+
+// Remove URB from this client's URB list
+static struct urb * PopFromURBList( 
+   sGobiUSBNet *      pDev,
+   u16                  clientID );
+
+/*=========================================================================*/
+// Internal userspace wrappers
+/*=========================================================================*/
+
+// Userspace unlocked ioctl
+static long UserspaceunlockedIOCTL(
+   struct file *     pFilp,
+   unsigned int      cmd,
+   unsigned long     arg );
+
+/*=========================================================================*/
+// Userspace wrappers
+/*=========================================================================*/
+
+// Userspace open
+static int UserspaceOpen( 
+   struct inode *   pInode, 
+   struct file *    pFilp );
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,36 ))
+// Userspace ioctl
+static int UserspaceIOCTL( 
+   struct inode *    pUnusedInode, 
+   struct file *     pFilp,
+   unsigned int      cmd, 
+   unsigned long     arg );
+#endif
+
+// Userspace close
+#define quectel_no_for_each_process
+#ifdef quectel_no_for_each_process
+static int UserspaceClose( 
+   struct inode *   pInode, 
+   struct file *    pFilp );
+#else
+#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,14 ))
+static int UserspaceClose( 
+   struct file *       pFilp,
+   fl_owner_t          unusedFileTable );
+#else
+static int UserspaceClose( struct file *       pFilp );
+#endif
+#endif
+
+// Userspace read (synchronous)
+static ssize_t UserspaceRead( 
+   struct file *        pFilp,
+   char __user *        pBuf, 
+   size_t               size,
+   loff_t *             pUnusedFpos );
+
+// Userspace write (synchronous)
+static ssize_t UserspaceWrite(
+   struct file *        pFilp, 
+   const char __user *  pBuf, 
+   size_t               size,
+   loff_t *             pUnusedFpos );
+
+static unsigned int UserspacePoll(
+   struct file *                  pFilp,
+   struct poll_table_struct *     pPollTable );
+
+/*=========================================================================*/
+// Driver level client management
+/*=========================================================================*/
+
+// Check if QMI is ready for use
+static bool QMIReady(
+   sGobiUSBNet *    pDev,
+   u16                timeout );
+
+// QMI WDS callback function
+static void QMIWDSCallback(
+   sGobiUSBNet *    pDev,
+   u16                clientID,
+   void *             pData );
+
+// Fire off reqests and start async read for QMI WDS callback
+static int SetupQMIWDSCallback( sGobiUSBNet * pDev );
+
+// Register client, send req and parse MEID response, release client
+static int QMIDMSGetMEID( sGobiUSBNet * pDev );
+
+// Register client, send req and parse Data format response, release client
+static int QMIWDASetDataFormat( sGobiUSBNet * pDev, int qmap_mode, int *rx_urb_size );
+#endif
+
+// Print Hex data, for debug purposes
+void QuecPrintHex(
+   void *         pBuffer,
+   u16            bufSize );
+
+// Sets mDownReason and turns carrier off
+void QuecGobiSetDownReason(
+   sGobiUSBNet *    pDev,
+   u8                 reason );
+
+// Clear mDownReason and may turn carrier on
+void QuecGobiClearDownReason(
+   sGobiUSBNet *    pDev,
+   u8                 reason );
+
+// Tests mDownReason and returns whether reason is set
+bool QuecGobiTestDownReason(
+   sGobiUSBNet *    pDev,
+   u8                 reason );
+
+// Start continuous read "thread"
+ int QuecStartRead( sGobiUSBNet * pDev );
+
+// Kill continuous read "thread"
+ void QuecKillRead( sGobiUSBNet * pDev );
+
+/*=========================================================================*/
+// Initializer and destructor
+/*=========================================================================*/
+
+// QMI Device initialization function
+int QuecRegisterQMIDevice( sGobiUSBNet * pDev );
+
+// QMI Device cleanup function
+void QuecDeregisterQMIDevice( sGobiUSBNet * pDev );
+
+int QuecQMIWDASetDataFormat( sGobiUSBNet * pDev, int qmap_mode, int *rx_urb_size );
+
+#define PrintHex QuecPrintHex
+#define GobiSetDownReason QuecGobiSetDownReason
+#define GobiClearDownReason QuecGobiClearDownReason
+#define GobiTestDownReason QuecGobiTestDownReason
+#define StartRead QuecStartRead
+#define KillRead QuecKillRead
+#define RegisterQMIDevice QuecRegisterQMIDevice
+#define DeregisterQMIDevice QuecDeregisterQMIDevice
diff --git a/drivers/net/usb/Structs.h b/drivers/net/usb/Structs.h
new file mode 100644 (file)
index 0000000..ad8a947
--- /dev/null
@@ -0,0 +1,460 @@
+/*===========================================================================
+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.
+
+
+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>
+#include <linux/poll.h>
+#include <linux/completion.h>
+
+#define QUECTEL_WWAN_QMAP 8
+#ifdef QUECTEL_WWAN_QMAP
+#define QUECTEL_QMAP_MUX_ID 0x81
+#endif
+
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,21 ))
+static inline void skb_reset_mac_header(struct sk_buff *skb)
+{
+    skb->mac.raw = skb->data;
+}
+#endif
+
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,22 ))
+#define bool      u8
+#ifndef URB_FREE_BUFFER
+#define URB_FREE_BUFFER_BY_SELF //usb_free_urb will not free, should free by self
+#define URB_FREE_BUFFER                0x0100  /* Free transfer buffer with the URB */
+#endif
+
+/**
+ * usb_endpoint_type - get the endpoint's transfer type
+ * @epd: endpoint to be checked
+ *
+ * Returns one of USB_ENDPOINT_XFER_{CONTROL, ISOC, BULK, INT} according
+ * to @epd's transfer type.
+ */
+static inline int usb_endpoint_type(const struct usb_endpoint_descriptor *epd)
+{
+       return epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK;
+}
+#endif
+
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,18 ))
+/**
+ * usb_endpoint_dir_in - check if the endpoint has IN direction
+ * @epd: endpoint to be checked
+ *
+ * Returns true if the endpoint is of type IN, otherwise it returns false.
+ */
+static inline int usb_endpoint_dir_in(const struct usb_endpoint_descriptor *epd)
+{
+       return ((epd->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN);
+}
+
+/**
+ * usb_endpoint_dir_out - check if the endpoint has OUT direction
+ * @epd: endpoint to be checked
+ *
+ * Returns true if the endpoint is of type OUT, otherwise it returns false.
+ */
+static inline int usb_endpoint_dir_out(
+                               const struct usb_endpoint_descriptor *epd)
+{
+       return ((epd->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT);
+}
+
+/**
+ * usb_endpoint_xfer_int - check if the endpoint has interrupt transfer type
+ * @epd: endpoint to be checked
+ *
+ * Returns true if the endpoint is of type interrupt, otherwise it returns
+ * false.
+ */
+static inline int usb_endpoint_xfer_int(
+                               const struct usb_endpoint_descriptor *epd)
+{
+       return ((epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) ==
+               USB_ENDPOINT_XFER_INT);
+}
+
+static inline int usb_autopm_set_interface(struct usb_interface *intf)
+{ return 0; }
+
+static inline int usb_autopm_get_interface(struct usb_interface *intf)
+{ return 0; }
+
+static inline int usb_autopm_get_interface_async(struct usb_interface *intf)
+{ return 0; }
+
+static inline void usb_autopm_put_interface(struct usb_interface *intf)
+{ }
+static inline void usb_autopm_put_interface_async(struct usb_interface *intf)
+{ }
+static inline void usb_autopm_enable(struct usb_interface *intf)
+{ }
+static inline void usb_autopm_disable(struct usb_interface *intf)
+{ }
+static inline void usb_mark_last_busy(struct usb_device *udev)
+{ }
+#endif
+
+#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
+
+// 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;
+
+   /* Wait queue object for poll() */
+   wait_queue_head_t    mWaitQueue;
+
+} 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
+
+#ifdef CONFIG_PM
+#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 ))
+/*=========================================================================*/
+// 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;
+
+   /* Length of the URB list */
+   atomic_t                   mURBListLen;
+   
+   /* 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;
+#endif
+#endif /* CONFIG_PM */
+
+/*=========================================================================*/
+// 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;
+
+//#define READ_QMI_URB_ERROR
+#ifdef READ_QMI_URB_ERROR
+   struct timer_list mReadUrbTimer;
+#endif
+
+   /* 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
+{
+    atomic_t refcount;
+
+   /* Net device structure */
+   struct usbnet *        mpNetDev;
+#ifdef QUECTEL_WWAN_QMAP
+   unsigned link_state;
+   int m_qmap_mode;
+   int qmap_size;
+   struct net_device   *mpQmapNetDev[QUECTEL_WWAN_QMAP];
+#ifdef CONFIG_BRIDGE
+   int m_qmap_bridge_mode[QUECTEL_WWAN_QMAP];
+#endif
+#endif
+
+#if 1 //def DATA_MODE_RP
+    bool                   mbMdm9x07;  
+    bool                   mbMdm9x06;  //for BG96
+   /* QMI "device" work in IP Mode or ETH Mode */
+   bool                   mbRawIPMode;
+#ifdef CONFIG_BRIDGE
+   int m_bridge_mode;
+   uint m_bridge_ipv4;
+   unsigned char     mHostMAC[6];
+#endif
+   int m_qcrmcall_mode;
+#endif
+
+   struct completion mQMIReadyCompletion;
+   bool                   mbQMIReady;
+
+   /* 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;
+
+   bool                   mbDeregisterQMIDevice;
+
+   /* QMI "device" memory */
+   sQMIDev                mQMIDev;
+
+   /* Device MEID */
+   char                   mMEID[14];
+   
+#ifdef CONFIG_PM
+   #if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 ))   
+   /* AutoPM thread */
+   sAutoPM                mAutoPM;
+#endif
+#endif /* CONFIG_PM */
+} 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;
+
index 9337eef00c61b74dd0ed30464c11f6545958279b..a8c622b21015bc49a235349251744305688f5a2e 100644 (file)
@@ -2035,6 +2035,7 @@ static struct usb_serial_driver option_1port_device = {
 #ifdef CONFIG_PM
        .suspend           = usb_wwan_suspend,
        .resume            = usb_wwan_resume,
+       .reset_resume = usb_wwan_resume,
 #endif
 };
 
@@ -2072,6 +2073,19 @@ static int option_probe(struct usb_serial *serial,
            iface_desc->bInterfaceClass != USB_CLASS_CDC_DATA)
                return -ENODEV;
 
+       if (serial->dev->descriptor.idVendor == cpu_to_le16(0x2C7C)
+               && serial->interface->cur_altsetting->desc.bInterfaceNumber >= 4)
+               return -ENODEV;
+
+       if (serial->dev->descriptor.idVendor == cpu_to_le16(0x2C7C)) {
+               pm_runtime_set_autosuspend_delay(&serial->dev->dev, 3000);
+               usb_enable_autosuspend(serial->dev);
+       }
+
+       if (serial->dev->descriptor.idVendor == cpu_to_le16(0x2C7C)) {
+               device_init_wakeup(&serial->dev->dev, 1); //usb remote wakeup
+       }
+
        /* Store the device flags so we can use them during attach. */
        usb_set_serial_data(serial, (void *)device_flags);
 
index 3dfdfc81254b827cc2e1f04feb926d0ad9666cf0..a918f300557bc2c5857923434b60f98721075e02 100644 (file)
@@ -505,6 +505,18 @@ static struct urb *usb_wwan_setup_urb(struct usb_serial_port *port,
                          usb_sndbulkpipe(serial->dev, endpoint) | dir,
                          buf, len, callback, ctx);
 
+       if (dir == USB_DIR_OUT) {
+               struct usb_device_descriptor *desc = &serial->dev->descriptor;
+               if (desc->idVendor == cpu_to_le16(0x05C6) && desc->idProduct == cpu_to_le16(0x9090))
+                       urb->transfer_flags |= URB_ZERO_PACKET;
+               if (desc->idVendor == cpu_to_le16(0x05C6) && desc->idProduct == cpu_to_le16(0x9003))
+                       urb->transfer_flags |= URB_ZERO_PACKET;
+               if (desc->idVendor == cpu_to_le16(0x05C6) && desc->idProduct == cpu_to_le16(0x9215))
+                       urb->transfer_flags |= URB_ZERO_PACKET;
+               if (desc->idVendor == cpu_to_le16(0x2C7C))
+                       urb->transfer_flags |= URB_ZERO_PACKET;
+       }
+
        return urb;
 }