--- /dev/null
+/*===========================================================================
+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<E_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" );
+
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
--- /dev/null
+#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
--- /dev/null
+/*===========================================================================
+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
--- /dev/null
+/*===========================================================================
+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);
+}
--- /dev/null
+/*===========================================================================
+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
--- /dev/null
+/*===========================================================================
+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;
+
#ifdef CONFIG_PM
.suspend = usb_wwan_suspend,
.resume = usb_wwan_resume,
+ .reset_resume = usb_wwan_resume,
#endif
};
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);
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;
}