aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/net/usb
diff options
context:
space:
mode:
authorcodeworkx <daniel.hillenbrand@codeworkx.de>2012-06-02 13:09:29 +0200
committercodeworkx <daniel.hillenbrand@codeworkx.de>2012-06-02 13:09:29 +0200
commitc6da2cfeb05178a11c6d062a06f8078150ee492f (patch)
treef3b4021d252c52d6463a9b3c1bb7245e399b009c /drivers/net/usb
parentc6d7c4dbff353eac7919342ae6b3299a378160a6 (diff)
downloadkernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.zip
kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.gz
kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.bz2
samsung update 1
Diffstat (limited to 'drivers/net/usb')
-rw-r--r--drivers/net/usb/Kconfig10
-rw-r--r--drivers/net/usb/Makefile3
-rw-r--r--drivers/net/usb/rmnet_usb_ctrl.c1060
-rw-r--r--drivers/net/usb/rmnet_usb_ctrl.h82
-rw-r--r--drivers/net/usb/rmnet_usb_data.c595
-rw-r--r--drivers/net/usb/usbnet.c10
6 files changed, 1757 insertions, 3 deletions
diff --git a/drivers/net/usb/Kconfig b/drivers/net/usb/Kconfig
index 84d4608..4e40468 100644
--- a/drivers/net/usb/Kconfig
+++ b/drivers/net/usb/Kconfig
@@ -458,4 +458,14 @@ config USB_VL600
http://ubuntuforums.org/showpost.php?p=10589647&postcount=17
+config MSM_RMNET_USB
+ tristate "RMNET USB Driver"
+ depends on USB_USBNET
+ help
+ Select this if you have a Qualcomm modem device connected via USB
+ supporting RMNET network interface.
+
+ To compile this driver as a module, choose M here: the module
+ will be called rmnet_usb. If unsure, choose N.
+
endmenu
diff --git a/drivers/net/usb/Makefile b/drivers/net/usb/Makefile
index c203fa2..84228c1 100644
--- a/drivers/net/usb/Makefile
+++ b/drivers/net/usb/Makefile
@@ -29,4 +29,5 @@ obj-$(CONFIG_USB_SIERRA_NET) += sierra_net.o
obj-$(CONFIG_USB_NET_CX82310_ETH) += cx82310_eth.o
obj-$(CONFIG_USB_NET_CDC_NCM) += cdc_ncm.o
obj-$(CONFIG_USB_VL600) += lg-vl600.o
-
+rmnet_usb-y := rmnet_usb_ctrl.o rmnet_usb_data.o
+obj-$(CONFIG_MSM_RMNET_USB) += rmnet_usb.o
diff --git a/drivers/net/usb/rmnet_usb_ctrl.c b/drivers/net/usb/rmnet_usb_ctrl.c
new file mode 100644
index 0000000..ebd58ab
--- /dev/null
+++ b/drivers/net/usb/rmnet_usb_ctrl.c
@@ -0,0 +1,1060 @@
+/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/uaccess.h>
+#include <linux/termios.h>
+#include <linux/ratelimit.h>
+#include <linux/debugfs.h>
+#include "rmnet_usb_ctrl.h"
+
+#define DEVICE_NAME "hsicctl"
+#define NUM_CTRL_CHANNELS 4
+#define DEFAULT_READ_URB_LENGTH 0x1000
+
+/*Output control lines.*/
+#define ACM_CTRL_DTR BIT(0)
+#define ACM_CTRL_RTS BIT(1)
+
+
+/*Input control lines.*/
+#define ACM_CTRL_DSR BIT(0)
+#define ACM_CTRL_CTS BIT(1)
+#define ACM_CTRL_RI BIT(2)
+#define ACM_CTRL_CD BIT(3)
+
+/* polling interval for Interrupt ep */
+#define HS_INTERVAL 7
+#define FS_LS_INTERVAL 3
+
+/*echo modem_wait > /sys/class/hsicctl/hsicctlx/modem_wait*/
+static ssize_t modem_wait_store(struct device *d, struct device_attribute *attr,
+ const char *buf, size_t n)
+{
+ unsigned int mdm_wait;
+ struct rmnet_ctrl_dev *dev = dev_get_drvdata(d);
+
+ if (!dev)
+ return -ENODEV;
+
+ sscanf(buf, "%u", &mdm_wait);
+
+ dev->mdm_wait_timeout = mdm_wait;
+
+ return n;
+}
+
+static ssize_t modem_wait_show(struct device *d, struct device_attribute *attr,
+ char *buf)
+{
+ struct rmnet_ctrl_dev *dev = dev_get_drvdata(d);
+
+ if (!dev)
+ return -ENODEV;
+
+ return snprintf(buf, PAGE_SIZE, "%u\n", dev->mdm_wait_timeout);
+}
+
+static DEVICE_ATTR(modem_wait, 0664, modem_wait_show, modem_wait_store);
+
+static int ctl_msg_dbg_mask;
+module_param_named(dump_ctrl_msg, ctl_msg_dbg_mask, int,
+ S_IRUGO | S_IWUSR | S_IWGRP);
+
+enum {
+ MSM_USB_CTL_DEBUG = 1U << 0,
+ MSM_USB_CTL_DUMP_BUFFER = 1U << 1,
+};
+
+#define DUMP_BUFFER(prestr, cnt, buf) \
+do { \
+ if (ctl_msg_dbg_mask & MSM_USB_CTL_DUMP_BUFFER) \
+ print_hex_dump(KERN_INFO, prestr, DUMP_PREFIX_NONE, \
+ 16, 1, buf, cnt, false); \
+} while (0)
+
+#define DBG(x...) \
+ do { \
+ if (ctl_msg_dbg_mask & MSM_USB_CTL_DEBUG) \
+ pr_info(x); \
+ } while (0)
+
+struct rmnet_ctrl_dev *ctrl_dev[NUM_CTRL_CHANNELS];
+struct class *ctrldev_classp;
+static dev_t ctrldev_num;
+
+struct ctrl_pkt {
+ size_t data_size;
+ void *data;
+};
+
+struct ctrl_pkt_list_elem {
+ struct list_head list;
+ struct ctrl_pkt cpkt;
+};
+
+static void resp_avail_cb(struct urb *);
+
+static int is_dev_connected(struct rmnet_ctrl_dev *dev)
+{
+ if (dev) {
+ mutex_lock(&dev->dev_lock);
+ if (!dev->intf) {
+ mutex_unlock(&dev->dev_lock);
+ return 0;
+ }
+ mutex_unlock(&dev->dev_lock);
+ return 1;
+ }
+ return 0;
+}
+
+static void notification_available_cb(struct urb *urb)
+{
+ int status;
+ struct usb_cdc_notification *ctrl;
+ struct usb_device *udev;
+ struct rmnet_ctrl_dev *dev = urb->context;
+
+ udev = interface_to_usbdev(dev->intf);
+
+ switch (urb->status) {
+ case 0:
+ /*success*/
+ break;
+
+ /*do not resubmit*/
+ case -ESHUTDOWN:
+ case -ENOENT:
+ case -ECONNRESET:
+ case -EPROTO:
+ return;
+ case -EPIPE:
+ pr_err_ratelimited("%s: Stall on int endpoint\n", __func__);
+ /* TBD : halt to be cleared in work */
+ return;
+
+ /*resubmit*/
+ case -EOVERFLOW:
+ pr_err_ratelimited("%s: Babble error happened\n", __func__);
+ default:
+ pr_debug_ratelimited("%s: Non zero urb status = %d\n",
+ __func__, urb->status);
+ goto resubmit_int_urb;
+ }
+
+ ctrl = urb->transfer_buffer;
+
+ switch (ctrl->bNotificationType) {
+ case USB_CDC_NOTIFY_RESPONSE_AVAILABLE:
+ dev->resp_avail_cnt++;
+ usb_fill_control_urb(dev->rcvurb, udev,
+ usb_rcvctrlpipe(udev, 0),
+ (unsigned char *)dev->in_ctlreq,
+ dev->rcvbuf,
+ DEFAULT_READ_URB_LENGTH,
+ resp_avail_cb, dev);
+
+ status = usb_submit_urb(dev->rcvurb, GFP_ATOMIC);
+ if (status) {
+ dev_err(dev->devicep,
+ "%s: Error submitting Read URB %d\n", __func__, status);
+ goto resubmit_int_urb;
+ }
+
+ if (!dev->resp_available) {
+ dev->resp_available = true;
+ wake_up(&dev->open_wait_queue);
+ }
+
+ return;
+ default:
+ dev_err(dev->devicep,
+ "%s:Command not implemented\n", __func__);
+ }
+
+resubmit_int_urb:
+ status = usb_submit_urb(urb, GFP_ATOMIC);
+ if (status)
+ dev_err(dev->devicep, "%s: Error re-submitting Int URB %d\n",
+ __func__, status);
+
+ return;
+}
+
+static void resp_avail_cb(struct urb *urb)
+{
+ struct usb_device *udev;
+ struct ctrl_pkt_list_elem *list_elem = NULL;
+ struct rmnet_ctrl_dev *dev = urb->context;
+ void *cpkt;
+ int status = 0;
+ size_t cpkt_size = 0;
+
+ udev = interface_to_usbdev(dev->intf);
+
+ switch (urb->status) {
+ case 0:
+ /*success*/
+ dev->get_encap_resp_cnt++;
+ break;
+
+ /*do not resubmit*/
+ case -ESHUTDOWN:
+ case -ENOENT:
+ case -ECONNRESET:
+ case -EPROTO:
+ return;
+
+ /*resubmit*/
+ case -EOVERFLOW:
+ pr_err_ratelimited("%s: Babble error happened\n", __func__);
+ default:
+ pr_debug_ratelimited("%s: Non zero urb status = %d\n",
+ __func__, urb->status);
+ goto resubmit_int_urb;
+ }
+
+ dev_dbg(dev->devicep, "Read %d bytes for %s\n",
+ urb->actual_length, dev->name);
+
+ cpkt = urb->transfer_buffer;
+ cpkt_size = urb->actual_length;
+
+ list_elem = kmalloc(sizeof(struct ctrl_pkt_list_elem), GFP_ATOMIC);
+ if (!list_elem) {
+ dev_err(dev->devicep, "%s: list_elem alloc failed\n", __func__);
+ return;
+ }
+ list_elem->cpkt.data = kmalloc(cpkt_size, GFP_ATOMIC);
+ if (!list_elem->cpkt.data) {
+ dev_err(dev->devicep, "%s: list_elem->data alloc failed\n",
+ __func__);
+ kfree(list_elem);
+ return;
+ }
+ memcpy(list_elem->cpkt.data, cpkt, cpkt_size);
+ list_elem->cpkt.data_size = cpkt_size;
+ spin_lock(&dev->rx_lock);
+ list_add_tail(&list_elem->list, &dev->rx_list);
+ spin_unlock(&dev->rx_lock);
+
+ wake_up(&dev->read_wait_queue);
+
+resubmit_int_urb:
+ /*re-submit int urb to check response available*/
+ status = usb_submit_urb(dev->inturb, GFP_ATOMIC);
+ if (status)
+ dev_err(dev->devicep, "%s: Error re-submitting Int URB %d\n",
+ __func__, status);
+}
+
+static int rmnet_usb_ctrl_start_rx(struct rmnet_ctrl_dev *dev)
+{
+ int retval = 0;
+
+ retval = usb_submit_urb(dev->inturb, GFP_KERNEL);
+ if (retval < 0)
+ dev_err(dev->devicep, "%s Intr submit %d\n", __func__, retval);
+
+ return retval;
+}
+
+int rmnet_usb_ctrl_stop_rx(struct rmnet_ctrl_dev *dev)
+{
+ if (!is_dev_connected(dev)) {
+ dev_dbg(dev->devicep, "%s: Ctrl device disconnected\n",
+ __func__);
+ return -ENODEV;
+ }
+
+ dev_dbg(dev->devicep, "%s\n", __func__);
+
+ usb_kill_urb(dev->rcvurb);
+ usb_kill_urb(dev->inturb);
+
+ return 0;
+}
+
+int rmnet_usb_ctrl_start(struct rmnet_ctrl_dev *dev)
+{
+ int status = 0;
+
+ mutex_lock(&dev->dev_lock);
+ if (dev->is_opened)
+ status = rmnet_usb_ctrl_start_rx(dev);
+ mutex_unlock(&dev->dev_lock);
+
+ return status;
+}
+
+static int rmnet_usb_ctrl_alloc_rx(struct rmnet_ctrl_dev *dev)
+{
+ int retval = -ENOMEM;
+
+ dev->rcvurb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!dev->rcvurb) {
+ pr_err("%s: Error allocating read urb\n", __func__);
+ goto nomem;
+ }
+
+ dev->rcvbuf = kmalloc(DEFAULT_READ_URB_LENGTH, GFP_KERNEL);
+ if (!dev->rcvbuf) {
+ pr_err("%s: Error allocating read buffer\n", __func__);
+ goto nomem;
+ }
+
+ dev->in_ctlreq = kmalloc(sizeof(*dev->in_ctlreq), GFP_KERNEL);
+ if (!dev->in_ctlreq) {
+ pr_err("%s: Error allocating setup packet buffer\n", __func__);
+ goto nomem;
+ }
+
+ return 0;
+
+nomem:
+ usb_free_urb(dev->rcvurb);
+ kfree(dev->rcvbuf);
+ kfree(dev->in_ctlreq);
+
+ return retval;
+
+}
+static int rmnet_usb_ctrl_write_cmd(struct rmnet_ctrl_dev *dev)
+{
+ struct usb_device *udev;
+
+ if (!is_dev_connected(dev))
+ return -ENODEV;
+
+ udev = interface_to_usbdev(dev->intf);
+ dev->set_ctrl_line_state_cnt++;
+ return usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
+ USB_CDC_REQ_SET_CONTROL_LINE_STATE,
+ (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE),
+ dev->cbits_tomdm,
+ dev->intf->cur_altsetting->desc.bInterfaceNumber,
+ NULL, 0, USB_CTRL_SET_TIMEOUT);
+}
+
+static void ctrl_write_callback(struct urb *urb)
+{
+ struct rmnet_ctrl_dev *dev = urb->context;
+
+ if (urb->status) {
+ dev->tx_ctrl_err_cnt++;
+ pr_debug_ratelimited("Write status/size %d/%d\n",
+ urb->status, urb->actual_length);
+ }
+
+ kfree(urb->setup_packet);
+ kfree(urb->transfer_buffer);
+ usb_free_urb(urb);
+ usb_autopm_put_interface_async(dev->intf);
+}
+
+static int rmnet_usb_ctrl_write(struct rmnet_ctrl_dev *dev, char *buf,
+ size_t size)
+{
+ int result;
+ struct urb *sndurb;
+ struct usb_ctrlrequest *out_ctlreq;
+ struct usb_device *udev;
+
+ if (!is_dev_connected(dev))
+ return -ENETRESET;
+
+ udev = interface_to_usbdev(dev->intf);
+
+ sndurb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!sndurb) {
+ dev_err(dev->devicep, "Error allocating read urb\n");
+ return -ENOMEM;
+ }
+
+ out_ctlreq = kmalloc(sizeof(*out_ctlreq), GFP_KERNEL);
+ if (!out_ctlreq) {
+ usb_free_urb(sndurb);
+ dev_err(dev->devicep, "Error allocating setup packet buffer\n");
+ return -ENOMEM;
+ }
+
+ /* CDC Send Encapsulated Request packet */
+ out_ctlreq->bRequestType = (USB_DIR_OUT | USB_TYPE_CLASS |
+ USB_RECIP_INTERFACE);
+ out_ctlreq->bRequest = USB_CDC_SEND_ENCAPSULATED_COMMAND;
+ out_ctlreq->wValue = 0;
+ out_ctlreq->wIndex = dev->intf->cur_altsetting->desc.bInterfaceNumber;
+ out_ctlreq->wLength = cpu_to_le16(size);
+
+ usb_fill_control_urb(sndurb, udev,
+ usb_sndctrlpipe(udev, 0),
+ (unsigned char *)out_ctlreq, (void *)buf, size,
+ ctrl_write_callback, dev);
+
+ result = usb_autopm_get_interface(dev->intf);
+ if (result < 0) {
+ dev_err(dev->devicep, "%s: Unable to resume interface: %d\n",
+ __func__, result);
+
+ /*
+ * Revisit: if (result == -EPERM)
+ * rmnet_usb_suspend(dev->intf, PMSG_SUSPEND);
+ */
+
+ usb_free_urb(sndurb);
+ kfree(out_ctlreq);
+ return result;
+ }
+
+ usb_anchor_urb(sndurb, &dev->tx_submitted);
+ dev->snd_encap_cmd_cnt++;
+ result = usb_submit_urb(sndurb, GFP_KERNEL);
+ if (result < 0) {
+ dev_err(dev->devicep, "%s: Submit URB error %d\n",
+ __func__, result);
+ dev->snd_encap_cmd_cnt--;
+ usb_autopm_put_interface(dev->intf);
+ usb_unanchor_urb(sndurb);
+ usb_free_urb(sndurb);
+ kfree(out_ctlreq);
+ return result;
+ }
+
+ return size;
+}
+
+static int rmnet_ctl_open(struct inode *inode, struct file *file)
+{
+ int retval = 0;
+ struct rmnet_ctrl_dev *dev =
+ container_of(inode->i_cdev, struct rmnet_ctrl_dev, cdev);
+
+ if (!dev)
+ return -ENODEV;
+
+ if (dev->is_opened)
+ goto already_opened;
+
+ctrl_open:
+ if (!is_dev_connected(dev)) {
+ dev_dbg(dev->devicep, "%s: Device not connected\n",
+ __func__);
+ return -ENODEV;
+ }
+
+ /*block open to get first response available from mdm*/
+ if (dev->mdm_wait_timeout && !dev->resp_available) {
+ retval = wait_event_interruptible_timeout(
+ dev->open_wait_queue,
+ dev->resp_available ||
+ !is_dev_connected(dev),
+ msecs_to_jiffies(dev->mdm_wait_timeout *
+ 1000));
+ if (retval == 0) {
+ dev_err(dev->devicep, "%s: Timeout opening %s\n",
+ __func__, dev->name);
+ return -ETIMEDOUT;
+ } else if (retval < 0) {
+ dev_err(dev->devicep, "%s: Error waiting for %s\n",
+ __func__, dev->name);
+ return retval;
+ }
+
+ goto ctrl_open;
+ }
+
+ if (!dev->resp_available) {
+ dev_dbg(dev->devicep, "%s: Connection timedout opening %s\n",
+ __func__, dev->name);
+ return -ETIMEDOUT;
+ }
+
+ mutex_lock(&dev->dev_lock);
+ dev->is_opened = 1;
+ mutex_unlock(&dev->dev_lock);
+
+ file->private_data = dev;
+
+already_opened:
+ DBG("%s: Open called for %s\n", __func__, dev->name);
+
+ return 0;
+}
+
+static int rmnet_ctl_release(struct inode *inode, struct file *file)
+{
+ struct ctrl_pkt_list_elem *list_elem = NULL;
+ struct rmnet_ctrl_dev *dev;
+ unsigned long flag;
+
+ dev = file->private_data;
+ if (!dev)
+ return -ENODEV;
+
+ DBG("%s Called on %s device\n", __func__, dev->name);
+
+ spin_lock_irqsave(&dev->rx_lock, flag);
+ while (!list_empty(&dev->rx_list)) {
+ list_elem = list_first_entry(
+ &dev->rx_list,
+ struct ctrl_pkt_list_elem,
+ list);
+ list_del(&list_elem->list);
+ kfree(list_elem->cpkt.data);
+ kfree(list_elem);
+ }
+ spin_unlock_irqrestore(&dev->rx_lock, flag);
+
+ mutex_lock(&dev->dev_lock);
+ dev->is_opened = 0;
+ mutex_unlock(&dev->dev_lock);
+
+ rmnet_usb_ctrl_stop_rx(dev);
+
+ if (is_dev_connected(dev))
+ usb_kill_anchored_urbs(&dev->tx_submitted);
+
+ file->private_data = NULL;
+
+ return 0;
+}
+
+static ssize_t rmnet_ctl_read(struct file *file, char __user *buf, size_t count,
+ loff_t *ppos)
+{
+ int retval = 0;
+ int bytes_to_read;
+ struct rmnet_ctrl_dev *dev;
+ struct ctrl_pkt_list_elem *list_elem = NULL;
+ unsigned long flags;
+
+ dev = file->private_data;
+ if (!dev)
+ return -ENODEV;
+
+ DBG("%s: Read from %s\n", __func__, dev->name);
+
+ctrl_read:
+ if (!is_dev_connected(dev)) {
+ dev_dbg(dev->devicep, "%s: Device not connected\n",
+ __func__);
+ return -ENETRESET;
+ }
+ spin_lock_irqsave(&dev->rx_lock, flags);
+ if (list_empty(&dev->rx_list)) {
+ spin_unlock_irqrestore(&dev->rx_lock, flags);
+
+ retval = wait_event_interruptible(dev->read_wait_queue,
+ !list_empty(&dev->rx_list) ||
+ !is_dev_connected(dev));
+ if (retval < 0)
+ return retval;
+
+ goto ctrl_read;
+ }
+
+ list_elem = list_first_entry(&dev->rx_list,
+ struct ctrl_pkt_list_elem, list);
+ bytes_to_read = (uint32_t)(list_elem->cpkt.data_size);
+ if (bytes_to_read > count) {
+ spin_unlock_irqrestore(&dev->rx_lock, flags);
+ dev_err(dev->devicep, "%s: Packet size %d > buf size %d\n",
+ __func__, bytes_to_read, count);
+ return -ENOMEM;
+ }
+ spin_unlock_irqrestore(&dev->rx_lock, flags);
+
+ if (copy_to_user(buf, list_elem->cpkt.data, bytes_to_read)) {
+ dev_err(dev->devicep,
+ "%s: copy_to_user failed for %s\n",
+ __func__, dev->name);
+ return -EFAULT;
+ }
+ spin_lock_irqsave(&dev->rx_lock, flags);
+ list_del(&list_elem->list);
+ spin_unlock_irqrestore(&dev->rx_lock, flags);
+
+ kfree(list_elem->cpkt.data);
+ kfree(list_elem);
+ DBG("%s: Returning %d bytes to %s\n", __func__, bytes_to_read,
+ dev->name);
+ DUMP_BUFFER("Read: ", bytes_to_read, buf);
+
+ return bytes_to_read;
+}
+
+static ssize_t rmnet_ctl_write(struct file *file, const char __user * buf,
+ size_t size, loff_t *pos)
+{
+ int status;
+ void *wbuf;
+ struct rmnet_ctrl_dev *dev = file->private_data;
+
+ if (!dev)
+ return -ENODEV;
+
+ if (size <= 0)
+ return -EINVAL;
+
+ if (!is_dev_connected(dev))
+ return -ENETRESET;
+
+ DBG("%s: Writing %i bytes on %s\n", __func__, size, dev->name);
+
+ wbuf = kmalloc(size , GFP_KERNEL);
+ if (!wbuf)
+ return -ENOMEM;
+
+ status = copy_from_user(wbuf , buf, size);
+ if (status) {
+ dev_err(dev->devicep,
+ "%s: Unable to copy data from userspace %d\n",
+ __func__, status);
+ kfree(wbuf);
+ return status;
+ }
+ DUMP_BUFFER("Write: ", size, buf);
+
+ status = rmnet_usb_ctrl_write(dev, wbuf, size);
+ if (status == size)
+ return size;
+
+ return status;
+}
+
+static int rmnet_ctrl_tiocmset(struct rmnet_ctrl_dev *dev, unsigned int set,
+ unsigned int clear)
+{
+ int retval;
+
+ mutex_lock(&dev->dev_lock);
+ if (set & TIOCM_DTR)
+ dev->cbits_tomdm |= ACM_CTRL_DTR;
+
+ /*
+ * TBD if (set & TIOCM_RTS)
+ * dev->cbits_tomdm |= ACM_CTRL_RTS;
+ */
+
+ if (clear & TIOCM_DTR)
+ dev->cbits_tomdm &= ~ACM_CTRL_DTR;
+
+ /*
+ * (clear & TIOCM_RTS)
+ * dev->cbits_tomdm &= ~ACM_CTRL_RTS;
+ */
+
+ mutex_unlock(&dev->dev_lock);
+
+ retval = usb_autopm_get_interface(dev->intf);
+ if (retval < 0) {
+ dev_err(dev->devicep, "%s: Unable to resume interface: %d\n",
+ __func__, retval);
+ return retval;
+ }
+
+ retval = rmnet_usb_ctrl_write_cmd(dev);
+
+ usb_autopm_put_interface(dev->intf);
+ return retval;
+}
+
+static int rmnet_ctrl_tiocmget(struct rmnet_ctrl_dev *dev)
+{
+ int ret;
+
+ mutex_lock(&dev->dev_lock);
+ ret =
+ /*
+ * TBD(dev->cbits_tolocal & ACM_CTRL_DSR ? TIOCM_DSR : 0) |
+ * (dev->cbits_tolocal & ACM_CTRL_CTS ? TIOCM_CTS : 0) |
+ */
+ (dev->cbits_tolocal & ACM_CTRL_CD ? TIOCM_CD : 0) |
+ /*
+ * TBD (dev->cbits_tolocal & ACM_CTRL_RI ? TIOCM_RI : 0) |
+ *(dev->cbits_tomdm & ACM_CTRL_RTS ? TIOCM_RTS : 0) |
+ */
+ (dev->cbits_tomdm & ACM_CTRL_DTR ? TIOCM_DTR : 0);
+ mutex_unlock(&dev->dev_lock);
+
+ return ret;
+}
+
+static long rmnet_ctrl_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ int ret;
+ struct rmnet_ctrl_dev *dev;
+
+ dev = file->private_data;
+ if (!dev)
+ return -ENODEV;
+
+ switch (cmd) {
+ case TIOCMGET:
+
+ ret = rmnet_ctrl_tiocmget(dev);
+ break;
+ case TIOCMSET:
+ ret = rmnet_ctrl_tiocmset(dev, arg, ~arg);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static const struct file_operations ctrldev_fops = {
+ .owner = THIS_MODULE,
+ .read = rmnet_ctl_read,
+ .write = rmnet_ctl_write,
+ .unlocked_ioctl = rmnet_ctrl_ioctl,
+ .open = rmnet_ctl_open,
+ .release = rmnet_ctl_release,
+};
+
+int rmnet_usb_ctrl_probe(struct usb_interface *intf,
+ struct usb_host_endpoint *int_in, struct rmnet_ctrl_dev *dev)
+{
+ u16 wMaxPacketSize;
+ struct usb_endpoint_descriptor *ep;
+ struct usb_device *udev;
+ int interval;
+ int ret = 0;
+
+ udev = interface_to_usbdev(intf);
+
+ if (!dev) {
+ pr_err("%s: Ctrl device not found\n", __func__);
+ return -ENODEV;
+ }
+ dev->int_pipe = usb_rcvintpipe(udev,
+ int_in->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);
+
+ mutex_lock(&dev->dev_lock);
+ dev->intf = intf;
+
+ /*TBD: for now just update CD status*/
+ dev->cbits_tolocal = ACM_CTRL_CD;
+
+ /*send DTR high to modem*/
+ dev->cbits_tomdm = ACM_CTRL_DTR;
+ mutex_unlock(&dev->dev_lock);
+
+ dev->resp_available = false;
+ dev->snd_encap_cmd_cnt = 0;
+ dev->get_encap_resp_cnt = 0;
+ dev->resp_avail_cnt = 0;
+ dev->tx_ctrl_err_cnt = 0;
+ dev->set_ctrl_line_state_cnt = 0;
+
+ ret = rmnet_usb_ctrl_write_cmd(dev);
+ if (ret < 0)
+ return ret;
+
+ dev->inturb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!dev->inturb) {
+ dev_err(dev->devicep, "Error allocating int urb\n");
+ return -ENOMEM;
+ }
+
+ /*use max pkt size from ep desc*/
+ ep = &dev->intf->cur_altsetting->endpoint[0].desc;
+ wMaxPacketSize = le16_to_cpu(ep->wMaxPacketSize);
+
+ dev->intbuf = kmalloc(wMaxPacketSize, GFP_KERNEL);
+ if (!dev->intbuf) {
+ usb_free_urb(dev->inturb);
+ dev_err(dev->devicep, "Error allocating int buffer\n");
+ return -ENOMEM;
+ }
+
+ dev->in_ctlreq->bRequestType =
+ (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE);
+ dev->in_ctlreq->bRequest = USB_CDC_GET_ENCAPSULATED_RESPONSE;
+ dev->in_ctlreq->wValue = 0;
+ dev->in_ctlreq->wIndex =
+ dev->intf->cur_altsetting->desc.bInterfaceNumber;
+ dev->in_ctlreq->wLength = cpu_to_le16(DEFAULT_READ_URB_LENGTH);
+
+ interval = max((int)int_in->desc.bInterval,
+ (udev->speed == USB_SPEED_HIGH) ? HS_INTERVAL
+ : FS_LS_INTERVAL);
+
+ usb_fill_int_urb(dev->inturb, udev,
+ dev->int_pipe,
+ dev->intbuf, wMaxPacketSize,
+ notification_available_cb, dev, interval);
+
+ return rmnet_usb_ctrl_start_rx(dev);
+}
+
+void rmnet_usb_ctrl_disconnect(struct rmnet_ctrl_dev *dev)
+{
+ rmnet_usb_ctrl_stop_rx(dev);
+
+ mutex_lock(&dev->dev_lock);
+
+ /*TBD: for now just update CD status*/
+ dev->cbits_tolocal = ~ACM_CTRL_CD;
+
+ dev->cbits_tomdm = ~ACM_CTRL_DTR;
+ dev->intf = NULL;
+ mutex_unlock(&dev->dev_lock);
+
+ wake_up(&dev->read_wait_queue);
+
+ usb_free_urb(dev->inturb);
+ dev->inturb = NULL;
+
+ kfree(dev->intbuf);
+ dev->intbuf = NULL;
+
+ usb_kill_anchored_urbs(&dev->tx_submitted);
+}
+
+#if defined(CONFIG_DEBUG_FS)
+#define DEBUG_BUF_SIZE 1024
+static ssize_t rmnet_usb_ctrl_read_stats(struct file *file, char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct rmnet_ctrl_dev *dev;
+ char *buf;
+ int ret;
+ int i;
+ int temp = 0;
+
+ buf = kzalloc(sizeof(char) * DEBUG_BUF_SIZE, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ for (i = 0; i < NUM_CTRL_CHANNELS; i++) {
+ dev = ctrl_dev[i];
+ if (!dev)
+ continue;
+
+ temp += scnprintf(buf + temp, DEBUG_BUF_SIZE - temp,
+ "\n#ctrl_dev: %p Name: %s#\n"
+ "snd encap cmd cnt %u\n"
+ "resp avail cnt: %u\n"
+ "get encap resp cnt: %u\n"
+ "set ctrl line state cnt: %u\n"
+ "tx_err_cnt: %u\n"
+ "cbits_tolocal: %d\n"
+ "cbits_tomdm: %d\n"
+ "mdm_wait_timeout: %u\n"
+ "dev opened: %s\n",
+ dev, dev->name,
+ dev->snd_encap_cmd_cnt,
+ dev->resp_avail_cnt,
+ dev->get_encap_resp_cnt,
+ dev->set_ctrl_line_state_cnt,
+ dev->tx_ctrl_err_cnt,
+ dev->cbits_tolocal,
+ dev->cbits_tomdm,
+ dev->mdm_wait_timeout,
+ dev->is_opened ? "OPEN" : "CLOSE");
+
+ }
+
+ ret = simple_read_from_buffer(ubuf, count, ppos, buf, temp);
+
+ kfree(buf);
+
+ return ret;
+}
+
+static ssize_t rmnet_usb_ctrl_reset_stats(struct file *file, const char __user *
+ buf, size_t count, loff_t *ppos)
+{
+ struct rmnet_ctrl_dev *dev;
+ int i;
+
+ for (i = 0; i < NUM_CTRL_CHANNELS; i++) {
+ dev = ctrl_dev[i];
+ if (!dev)
+ continue;
+
+ dev->snd_encap_cmd_cnt = 0;
+ dev->resp_avail_cnt = 0;
+ dev->get_encap_resp_cnt = 0;
+ dev->set_ctrl_line_state_cnt = 0;
+ dev->tx_ctrl_err_cnt = 0;
+ }
+ return count;
+}
+
+const struct file_operations rmnet_usb_ctrl_stats_ops = {
+ .read = rmnet_usb_ctrl_read_stats,
+ .write = rmnet_usb_ctrl_reset_stats,
+};
+
+struct dentry *usb_ctrl_dent;
+struct dentry *usb_ctrl_dfile;
+static void rmnet_usb_ctrl_debugfs_init(void)
+{
+ usb_ctrl_dent = debugfs_create_dir("rmnet_usb_ctrl", 0);
+ if (IS_ERR(usb_ctrl_dent))
+ return;
+
+ usb_ctrl_dfile = debugfs_create_file("status", 0644, usb_ctrl_dent, 0,
+ &rmnet_usb_ctrl_stats_ops);
+ if (!usb_ctrl_dfile || IS_ERR(usb_ctrl_dfile))
+ debugfs_remove(usb_ctrl_dent);
+}
+
+static void rmnet_usb_ctrl_debugfs_exit(void)
+{
+ debugfs_remove(usb_ctrl_dfile);
+ debugfs_remove(usb_ctrl_dent);
+}
+
+#else
+static void rmnet_usb_ctrl_debugfs_init(void) { }
+static void rmnet_usb_ctrl_debugfs_exit(void) { }
+#endif
+
+int rmnet_usb_ctrl_init(void)
+{
+ struct rmnet_ctrl_dev *dev;
+ int n;
+ int status;
+
+ for (n = 0; n < NUM_CTRL_CHANNELS; ++n) {
+
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev) {
+ status = -ENOMEM;
+ goto error0;
+ }
+ /*for debug purpose*/
+ snprintf(dev->name, CTRL_DEV_MAX_LEN, "hsicctl%d", n);
+
+ mutex_init(&dev->dev_lock);
+ spin_lock_init(&dev->rx_lock);
+ init_waitqueue_head(&dev->read_wait_queue);
+ init_waitqueue_head(&dev->open_wait_queue);
+ INIT_LIST_HEAD(&dev->rx_list);
+ init_usb_anchor(&dev->tx_submitted);
+
+ status = rmnet_usb_ctrl_alloc_rx(dev);
+ if (status < 0) {
+ kfree(dev);
+ goto error0;
+ }
+
+ ctrl_dev[n] = dev;
+ }
+
+ status = alloc_chrdev_region(&ctrldev_num, 0, NUM_CTRL_CHANNELS,
+ DEVICE_NAME);
+ if (IS_ERR_VALUE(status)) {
+ pr_err("ERROR:%s: alloc_chrdev_region() ret %i.\n",
+ __func__, status);
+ goto error0;
+ }
+
+ ctrldev_classp = class_create(THIS_MODULE, DEVICE_NAME);
+ if (IS_ERR(ctrldev_classp)) {
+ pr_err("ERROR:%s: class_create() ENOMEM\n", __func__);
+ status = -ENOMEM;
+ goto error1;
+ }
+ for (n = 0; n < NUM_CTRL_CHANNELS; ++n) {
+ cdev_init(&ctrl_dev[n]->cdev, &ctrldev_fops);
+ ctrl_dev[n]->cdev.owner = THIS_MODULE;
+
+ status = cdev_add(&ctrl_dev[n]->cdev, (ctrldev_num + n), 1);
+
+ if (IS_ERR_VALUE(status)) {
+ pr_err("%s: cdev_add() ret %i\n", __func__, status);
+ kfree(ctrl_dev[n]);
+ goto error2;
+ }
+
+ ctrl_dev[n]->devicep =
+ device_create(ctrldev_classp, NULL,
+ (ctrldev_num + n), NULL,
+ DEVICE_NAME "%d", n);
+
+ if (IS_ERR(ctrl_dev[n]->devicep)) {
+ pr_err("%s: device_create() ENOMEM\n", __func__);
+ status = -ENOMEM;
+ cdev_del(&ctrl_dev[n]->cdev);
+ kfree(ctrl_dev[n]);
+ goto error2;
+ }
+ /*create /sys/class/hsicctl/hsicctlx/modem_wait*/
+ status = device_create_file(ctrl_dev[n]->devicep,
+ &dev_attr_modem_wait);
+ if (status) {
+ device_destroy(ctrldev_classp,
+ MKDEV(MAJOR(ctrldev_num), n));
+ cdev_del(&ctrl_dev[n]->cdev);
+ kfree(ctrl_dev[n]);
+ goto error2;
+ }
+ dev_set_drvdata(ctrl_dev[n]->devicep, ctrl_dev[n]);
+ }
+
+ rmnet_usb_ctrl_debugfs_init();
+ pr_info("rmnet usb ctrl Initialized.\n");
+ return 0;
+
+error2:
+ while (--n >= 0) {
+ cdev_del(&ctrl_dev[n]->cdev);
+ device_destroy(ctrldev_classp,
+ MKDEV(MAJOR(ctrldev_num), n));
+ }
+
+ class_destroy(ctrldev_classp);
+ n = NUM_CTRL_CHANNELS;
+error1:
+ unregister_chrdev_region(MAJOR(ctrldev_num), NUM_CTRL_CHANNELS);
+error0:
+ while (--n >= 0)
+ kfree(ctrl_dev[n]);
+
+ return status;
+}
+
+void rmnet_usb_ctrl_exit(void)
+{
+ int i;
+
+ for (i = 0; i < NUM_CTRL_CHANNELS; ++i) {
+ if (!ctrl_dev[i])
+ return;
+
+ kfree(ctrl_dev[i]->in_ctlreq);
+ kfree(ctrl_dev[i]->rcvbuf);
+ kfree(ctrl_dev[i]->intbuf);
+ usb_free_urb(ctrl_dev[i]->rcvurb);
+ usb_free_urb(ctrl_dev[i]->inturb);
+#if defined(DEBUG)
+ device_remove_file(ctrl_dev[i]->devicep, &dev_attr_modem_wait);
+#endif
+ cdev_del(&ctrl_dev[i]->cdev);
+ kfree(ctrl_dev[i]);
+ ctrl_dev[i] = NULL;
+ device_destroy(ctrldev_classp, MKDEV(MAJOR(ctrldev_num), i));
+ }
+
+ class_destroy(ctrldev_classp);
+ unregister_chrdev_region(MAJOR(ctrldev_num), NUM_CTRL_CHANNELS);
+ rmnet_usb_ctrl_debugfs_exit();
+}
diff --git a/drivers/net/usb/rmnet_usb_ctrl.h b/drivers/net/usb/rmnet_usb_ctrl.h
new file mode 100644
index 0000000..f6e5876
--- /dev/null
+++ b/drivers/net/usb/rmnet_usb_ctrl.h
@@ -0,0 +1,82 @@
+/* Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __RMNET_USB_CTRL_H
+#define __RMNET_USB_CTRL_H
+
+#include <linux/mutex.h>
+#include <linux/usb.h>
+#include <linux/cdev.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/cdc.h>
+
+#define CTRL_DEV_MAX_LEN 10
+
+struct rmnet_ctrl_dev {
+
+ /*for debugging purpose*/
+ char name[CTRL_DEV_MAX_LEN];
+
+ struct cdev cdev;
+ struct device *devicep;
+
+ struct usb_interface *intf;
+ unsigned int int_pipe;
+ struct urb *rcvurb;
+ struct urb *inturb;
+ struct usb_anchor tx_submitted;
+ void *rcvbuf;
+ void *intbuf;
+ struct usb_ctrlrequest *in_ctlreq;
+
+ spinlock_t rx_lock;
+ struct mutex dev_lock;
+ struct list_head rx_list;
+ wait_queue_head_t read_wait_queue;
+ wait_queue_head_t open_wait_queue;
+
+ unsigned is_opened;
+
+ /*input control lines (DSR, CTS, CD, RI)*/
+ unsigned int cbits_tolocal;
+
+ /*output control lines (DTR, RTS)*/
+ unsigned int cbits_tomdm;
+
+ /*
+ * track first resp available from mdm when it boots up
+ * to avoid bigger timeout value used by qmuxd
+ */
+ bool resp_available;
+
+ unsigned int mdm_wait_timeout;
+
+ /*counters*/
+ unsigned int snd_encap_cmd_cnt;
+ unsigned int get_encap_resp_cnt;
+ unsigned int resp_avail_cnt;
+ unsigned int set_ctrl_line_state_cnt;
+ unsigned int tx_ctrl_err_cnt;
+};
+
+extern struct rmnet_ctrl_dev *ctrl_dev[];
+
+extern int rmnet_usb_ctrl_start(struct rmnet_ctrl_dev *);
+extern int rmnet_usb_ctrl_stop_rx(struct rmnet_ctrl_dev *);
+extern int rmnet_usb_ctrl_init(void);
+extern void rmnet_usb_ctrl_exit(void);
+extern int rmnet_usb_ctrl_probe(struct usb_interface *intf,
+ struct usb_host_endpoint *status,
+ struct rmnet_ctrl_dev *dev);
+extern void rmnet_usb_ctrl_disconnect(struct rmnet_ctrl_dev *);
+
+#endif /* __RMNET_USB_H*/
diff --git a/drivers/net/usb/rmnet_usb_data.c b/drivers/net/usb/rmnet_usb_data.c
new file mode 100644
index 0000000..ae3f934
--- /dev/null
+++ b/drivers/net/usb/rmnet_usb_data.c
@@ -0,0 +1,595 @@
+/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/mii.h>
+#include <linux/if_arp.h>
+#include <linux/etherdevice.h>
+#include <linux/usb.h>
+#include <linux/usb/usbnet.h>
+#include <linux/msm_rmnet.h>
+
+#include "rmnet_usb_ctrl.h"
+
+#define RMNET_DATA_LEN 2000
+#define HEADROOM_FOR_QOS 8
+
+static int data_msg_dbg_mask;
+
+enum {
+ DEBUG_MASK_LVL0 = 1U << 0,
+ DEBUG_MASK_LVL1 = 1U << 1,
+ DEBUG_MASK_LVL2 = 1U << 2,
+};
+
+#define DBG(m, x...) do { \
+ if (data_msg_dbg_mask & m) \
+ pr_info(x); \
+} while (0)
+
+/*echo dbg_mask > /sys/class/net/rmnet_usbx/dbg_mask*/
+static ssize_t dbg_mask_store(struct device *d,
+ struct device_attribute *attr,
+ const char *buf, size_t n)
+{
+ unsigned int dbg_mask;
+ struct net_device *dev = to_net_dev(d);
+ struct usbnet *unet = netdev_priv(dev);
+
+ if (!dev)
+ return -ENODEV;
+
+ sscanf(buf, "%u", &dbg_mask);
+ /*enable dbg msgs for data driver*/
+ data_msg_dbg_mask = dbg_mask;
+
+ /*set default msg level*/
+ unet->msg_enable = NETIF_MSG_DRV | NETIF_MSG_PROBE | NETIF_MSG_LINK;
+
+ /*enable netif_xxx msgs*/
+ if (dbg_mask & DEBUG_MASK_LVL0)
+ unet->msg_enable |= NETIF_MSG_IFUP | NETIF_MSG_IFDOWN;
+ if (dbg_mask & DEBUG_MASK_LVL1)
+ unet->msg_enable |= NETIF_MSG_TX_ERR | NETIF_MSG_RX_ERR
+ | NETIF_MSG_TX_QUEUED | NETIF_MSG_TX_DONE
+ | NETIF_MSG_RX_STATUS;
+
+ return n;
+}
+
+static ssize_t dbg_mask_show(struct device *d,
+ struct device_attribute *attr, char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%d\n", data_msg_dbg_mask);
+}
+
+static DEVICE_ATTR(dbg_mask, 0644, dbg_mask_show, dbg_mask_store);
+
+#define DBG0(x...) DBG(DEBUG_MASK_LVL0, x)
+#define DBG1(x...) DBG(DEBUG_MASK_LVL1, x)
+#define DBG2(x...) DBG(DEBUG_MASK_LVL2, x)
+
+static void rmnet_usb_setup(struct net_device *);
+static int rmnet_ioctl(struct net_device *, struct ifreq *, int);
+
+static int rmnet_usb_suspend(struct usb_interface *iface, pm_message_t message)
+{
+ struct usbnet *unet;
+ struct rmnet_ctrl_dev *dev;
+ int time = 0;
+ int retval = 0;
+
+ unet = usb_get_intfdata(iface);
+ if (!unet) {
+ pr_err("%s:data device not found\n", __func__);
+ retval = -ENODEV;
+ goto fail;
+ }
+
+ dev = (struct rmnet_ctrl_dev *)unet->data[1];
+ if (!dev) {
+ dev_err(&unet->udev->dev, "%s: ctrl device not found\n",
+ __func__);
+ retval = -ENODEV;
+ goto fail;
+ }
+
+ retval = usbnet_suspend(iface, message);
+ if (!retval) {
+ if (message.event & PM_EVENT_SUSPEND) {
+ time = usb_wait_anchor_empty_timeout(&dev->tx_submitted,
+ 1000);
+ if (!time)
+ usb_kill_anchored_urbs(&dev->tx_submitted);
+
+ retval = rmnet_usb_ctrl_stop_rx(dev);
+ iface->dev.power.power_state.event = message.event;
+ }
+ /* TBD : do we need to set/clear usbnet->udev->reset_resume*/
+ } else
+ dev_dbg(&unet->udev->dev,
+ "%s: device is busy can not suspend\n", __func__);
+
+fail:
+ return retval;
+}
+
+static int rmnet_usb_resume(struct usb_interface *iface)
+{
+ int retval = 0;
+ int oldstate;
+ struct usbnet *unet;
+ struct rmnet_ctrl_dev *dev;
+
+ unet = usb_get_intfdata(iface);
+ if (!unet) {
+ pr_err("%s:data device not found\n", __func__);
+ retval = -ENODEV;
+ goto fail;
+ }
+
+ dev = (struct rmnet_ctrl_dev *)unet->data[1];
+ if (!dev) {
+ dev_err(&unet->udev->dev, "%s: ctrl device not found\n",
+ __func__);
+ retval = -ENODEV;
+ goto fail;
+ }
+ oldstate = iface->dev.power.power_state.event;
+ iface->dev.power.power_state.event = PM_EVENT_ON;
+
+ retval = usbnet_resume(iface);
+ if (!retval) {
+ if (oldstate & PM_EVENT_SUSPEND)
+ retval = rmnet_usb_ctrl_start(dev);
+ }
+fail:
+ return retval;
+}
+
+static int rmnet_usb_bind(struct usbnet *usbnet, struct usb_interface *iface)
+{
+ struct usb_host_endpoint *endpoint = NULL;
+ struct usb_host_endpoint *bulk_in = NULL;
+ struct usb_host_endpoint *bulk_out = NULL;
+ struct usb_host_endpoint *int_in = NULL;
+ struct usb_device *udev;
+ int status = 0;
+ int i;
+ int numends;
+
+ udev = interface_to_usbdev(iface);
+ numends = iface->cur_altsetting->desc.bNumEndpoints;
+ for (i = 0; i < numends; i++) {
+ endpoint = iface->cur_altsetting->endpoint + i;
+ if (!endpoint) {
+ dev_err(&udev->dev, "%s: invalid endpoint %u\n",
+ __func__, i);
+ status = -EINVAL;
+ goto out;
+ }
+ if (usb_endpoint_is_bulk_in(&endpoint->desc))
+ bulk_in = endpoint;
+ else if (usb_endpoint_is_bulk_out(&endpoint->desc))
+ bulk_out = endpoint;
+ else if (usb_endpoint_is_int_in(&endpoint->desc))
+ int_in = endpoint;
+ }
+
+ if (!bulk_in || !bulk_out || !int_in) {
+ dev_err(&udev->dev, "%s: invalid endpoints\n", __func__);
+ status = -EINVAL;
+ goto out;
+ }
+ usbnet->in = usb_rcvbulkpipe(usbnet->udev,
+ bulk_in->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);
+ usbnet->out = usb_sndbulkpipe(usbnet->udev,
+ bulk_out->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);
+ usbnet->status = int_in;
+
+ /*change name of net device to rmnet_usbx here*/
+ strlcpy(usbnet->net->name, "rmnet_usb%d", IFNAMSIZ);
+
+ /*TBD: update rx_urb_size, curently set to eth frame len by usbnet*/
+out:
+ return status;
+}
+
+static struct sk_buff *rmnet_usb_tx_fixup(struct usbnet *dev,
+ struct sk_buff *skb, gfp_t flags)
+{
+ struct QMI_QOS_HDR_S *qmih;
+
+ if (test_bit(RMNET_MODE_QOS, &dev->data[0])) {
+ qmih = (struct QMI_QOS_HDR_S *)
+ skb_push(skb, sizeof(struct QMI_QOS_HDR_S));
+ qmih->version = 1;
+ qmih->flags = 0;
+ qmih->flow_id = skb->mark;
+ }
+
+ DBG1("[%s] Tx packet #%lu len=%d mark=0x%x\n",
+ dev->net->name, dev->net->stats.tx_packets, skb->len, skb->mark);
+
+ return skb;
+}
+
+static __be16 rmnet_ip_type_trans(struct sk_buff *skb,
+ struct net_device *dev)
+{
+ __be16 protocol = 0;
+
+ skb->dev = dev;
+
+ switch (skb->data[0] & 0xf0) {
+ case 0x40:
+ protocol = htons(ETH_P_IP);
+ break;
+ case 0x60:
+ protocol = htons(ETH_P_IPV6);
+ break;
+ default:
+ pr_err("[%s] rmnet_recv() L3 protocol decode error: 0x%02x",
+ dev->name, skb->data[0] & 0xf0);
+ }
+
+ return protocol;
+}
+
+static int rmnet_usb_rx_fixup(struct usbnet *dev,
+ struct sk_buff *skb)
+{
+
+ if (test_bit(RMNET_MODE_LLP_IP, &dev->data[0]))
+ skb->protocol = rmnet_ip_type_trans(skb, dev->net);
+ else /*set zero for eth mode*/
+ skb->protocol = 0;
+
+ DBG1("[%s] Rx packet #%lu len=%d\n",
+ dev->net->name, dev->net->stats.rx_packets, skb->len);
+
+ return 1;
+}
+
+static int rmnet_usb_manage_power(struct usbnet *dev, int on)
+{
+ dev->intf->needs_remote_wakeup = on;
+ return 0;
+}
+
+static int rmnet_change_mtu(struct net_device *dev, int new_mtu)
+{
+ if (0 > new_mtu || RMNET_DATA_LEN < new_mtu)
+ return -EINVAL;
+
+ DBG0("[%s] MTU change: old=%d new=%d\n", dev->name, dev->mtu, new_mtu);
+
+ dev->mtu = new_mtu;
+
+ return 0;
+}
+
+static struct net_device_stats *rmnet_get_stats(struct net_device *dev)
+{
+ return &dev->stats;
+}
+
+static const struct net_device_ops rmnet_usb_ops_ether = {
+ .ndo_open = usbnet_open,
+ .ndo_stop = usbnet_stop,
+ .ndo_start_xmit = usbnet_start_xmit,
+ .ndo_get_stats = rmnet_get_stats,
+ /*.ndo_set_multicast_list = rmnet_set_multicast_list,*/
+ .ndo_tx_timeout = usbnet_tx_timeout,
+ .ndo_do_ioctl = rmnet_ioctl,
+ .ndo_change_mtu = usbnet_change_mtu,
+ .ndo_set_mac_address = eth_mac_addr,
+ .ndo_validate_addr = eth_validate_addr,
+};
+
+static const struct net_device_ops rmnet_usb_ops_ip = {
+ .ndo_open = usbnet_open,
+ .ndo_stop = usbnet_stop,
+ .ndo_start_xmit = usbnet_start_xmit,
+ .ndo_get_stats = rmnet_get_stats,
+ /*.ndo_set_multicast_list = rmnet_set_multicast_list,*/
+ .ndo_tx_timeout = usbnet_tx_timeout,
+ .ndo_do_ioctl = rmnet_ioctl,
+ .ndo_change_mtu = rmnet_change_mtu,
+ .ndo_set_mac_address = 0,
+ .ndo_validate_addr = 0,
+};
+
+
+static int rmnet_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+ struct usbnet *unet = netdev_priv(dev);
+ u32 old_opmode;
+ int prev_mtu = dev->mtu;
+ int rc = 0;
+
+ old_opmode = unet->data[0]; /*data[0] saves operation mode*/
+ /* Process IOCTL command */
+ switch (cmd) {
+ case RMNET_IOCTL_SET_LLP_ETHERNET: /*Set Ethernet protocol*/
+ /* Perform Ethernet config only if in IP mode currently*/
+ if (test_bit(RMNET_MODE_LLP_IP, &unet->data[0])) {
+ ether_setup(dev);
+ random_ether_addr(dev->dev_addr);
+ dev->mtu = prev_mtu;
+ dev->netdev_ops = &rmnet_usb_ops_ether;
+ clear_bit(RMNET_MODE_LLP_IP, &unet->data[0]);
+ set_bit(RMNET_MODE_LLP_ETH, &unet->data[0]);
+ DBG0("[%s] rmnet_ioctl(): set Ethernet protocol mode\n",
+ dev->name);
+ }
+ break;
+
+ case RMNET_IOCTL_SET_LLP_IP: /* Set RAWIP protocol*/
+ /* Perform IP config only if in Ethernet mode currently*/
+ if (test_bit(RMNET_MODE_LLP_ETH, &unet->data[0])) {
+
+ /* Undo config done in ether_setup() */
+ dev->header_ops = 0; /* No header */
+ dev->type = ARPHRD_RAWIP;
+ dev->hard_header_len = 0;
+ dev->mtu = prev_mtu;
+ dev->addr_len = 0;
+ dev->flags &= ~(IFF_BROADCAST | IFF_MULTICAST);
+ dev->needed_headroom = HEADROOM_FOR_QOS;
+ dev->netdev_ops = &rmnet_usb_ops_ip;
+ clear_bit(RMNET_MODE_LLP_ETH, &unet->data[0]);
+ set_bit(RMNET_MODE_LLP_IP, &unet->data[0]);
+ DBG0("[%s] rmnet_ioctl(): set IP protocol mode\n",
+ dev->name);
+ }
+ break;
+
+ case RMNET_IOCTL_GET_LLP: /* Get link protocol state */
+ ifr->ifr_ifru.ifru_data = (void *)(unet->data[0]
+ & (RMNET_MODE_LLP_ETH
+ | RMNET_MODE_LLP_IP));
+ break;
+
+ case RMNET_IOCTL_SET_QOS_ENABLE: /* Set QoS header enabled*/
+ set_bit(RMNET_MODE_QOS, &unet->data[0]);
+ DBG0("[%s] rmnet_ioctl(): set QMI QOS header enable\n",
+ dev->name);
+ break;
+
+ case RMNET_IOCTL_SET_QOS_DISABLE: /* Set QoS header disabled */
+ clear_bit(RMNET_MODE_QOS, &unet->data[0]);
+ DBG0("[%s] rmnet_ioctl(): set QMI QOS header disable\n",
+ dev->name);
+ break;
+
+ case RMNET_IOCTL_GET_QOS: /* Get QoS header state */
+ ifr->ifr_ifru.ifru_data = (void *)(unet->data[0]
+ & RMNET_MODE_QOS);
+ break;
+
+ case RMNET_IOCTL_GET_OPMODE: /* Get operation mode*/
+ ifr->ifr_ifru.ifru_data = (void *)unet->data[0];
+ break;
+
+ case RMNET_IOCTL_OPEN: /* Open transport port */
+ rc = usbnet_open(dev);
+ DBG0("[%s] rmnet_ioctl(): open transport port\n", dev->name);
+ break;
+
+ case RMNET_IOCTL_CLOSE: /* Close transport port*/
+ rc = usbnet_stop(dev);
+ DBG0("[%s] rmnet_ioctl(): close transport port\n", dev->name);
+ break;
+
+ default:
+ dev_err(&unet->udev->dev, "[%s] error: "
+ "rmnet_ioct called for unsupported cmd[%d]",
+ dev->name, cmd);
+ return -EINVAL;
+ }
+
+ DBG2("[%s] %s: cmd=0x%x opmode old=0x%08x new=0x%08lx\n",
+ dev->name, __func__, cmd, old_opmode, unet->data[0]);
+
+ return rc;
+}
+
+static void rmnet_usb_setup(struct net_device *dev)
+{
+ /* Using Ethernet mode by default */
+ dev->netdev_ops = &rmnet_usb_ops_ether;
+
+ /* set this after calling ether_setup */
+ dev->mtu = RMNET_DATA_LEN;
+
+ dev->needed_headroom = HEADROOM_FOR_QOS;
+ random_ether_addr(dev->dev_addr);
+ dev->watchdog_timeo = 1000; /* 10 seconds? */
+}
+
+static int rmnet_usb_probe(struct usb_interface *iface,
+ const struct usb_device_id *prod)
+{
+ struct usbnet *unet;
+ struct usb_device *udev;
+ struct driver_info *info;
+ unsigned int iface_num;
+ static int first_rmnet_iface_num = -EINVAL;
+ int status = 0;
+
+ udev = interface_to_usbdev(iface);
+ iface_num = iface->cur_altsetting->desc.bInterfaceNumber;
+ if (iface->num_altsetting != 1) {
+ dev_err(&udev->dev, "%s invalid num_altsetting %u\n",
+ __func__, iface->num_altsetting);
+ status = -EINVAL;
+ goto out;
+ }
+
+ info = (struct driver_info *)prod->driver_info;
+ if (!test_bit(iface_num, &info->data))
+ return -ENODEV;
+
+ status = usbnet_probe(iface, prod);
+ if (status < 0) {
+ dev_err(&udev->dev, "usbnet_probe failed %d\n", status);
+ goto out;
+ }
+ unet = usb_get_intfdata(iface);
+
+ /*set rmnet operation mode to eth by default*/
+ set_bit(RMNET_MODE_LLP_ETH, &unet->data[0]);
+
+ /*update net device*/
+ rmnet_usb_setup(unet->net);
+
+ /*create /sys/class/net/rmnet_usbx/dbg_mask*/
+ status = device_create_file(&unet->net->dev, &dev_attr_dbg_mask);
+ if (status)
+ goto out;
+
+ if (first_rmnet_iface_num == -EINVAL)
+ first_rmnet_iface_num = iface_num;
+
+ /*save control device intstance */
+ unet->data[1] = (unsigned long)ctrl_dev \
+ [iface_num - first_rmnet_iface_num];
+
+ status = rmnet_usb_ctrl_probe(iface, unet->status,
+ (struct rmnet_ctrl_dev *)unet->data[1]);
+ if (status)
+ goto out;
+
+ /* allow modem to wake up suspended system */
+ device_set_wakeup_enable(&udev->dev, 1);
+out:
+ return status;
+}
+
+static void rmnet_usb_disconnect(struct usb_interface *intf)
+{
+ struct usbnet *unet;
+ struct usb_device *udev;
+ struct rmnet_ctrl_dev *dev;
+
+ udev = interface_to_usbdev(intf);
+ device_set_wakeup_enable(&udev->dev, 0);
+
+ unet = usb_get_intfdata(intf);
+ if (!unet) {
+ dev_err(&udev->dev, "%s:data device not found\n", __func__);
+ return;
+ }
+
+ dev = (struct rmnet_ctrl_dev *)unet->data[1];
+ if (!dev) {
+ dev_err(&udev->dev, "%s:ctrl device not found\n", __func__);
+ return;
+ }
+ unet->data[0] = 0;
+ unet->data[1] = 0;
+ rmnet_usb_ctrl_disconnect(dev);
+ device_remove_file(&unet->net->dev, &dev_attr_dbg_mask);
+ usbnet_disconnect(intf);
+}
+
+/*bit position represents interface number*/
+#define PID9034_IFACE_MASK 0xF0
+#define PID9048_IFACE_MASK 0x1E0
+#define PID904C_IFACE_MASK 0x1C0
+
+static const struct driver_info rmnet_info_pid9034 = {
+ .description = "RmNET net device",
+ .bind = rmnet_usb_bind,
+ .tx_fixup = rmnet_usb_tx_fixup,
+ .rx_fixup = rmnet_usb_rx_fixup,
+ .manage_power = rmnet_usb_manage_power,
+ .data = PID9034_IFACE_MASK,
+};
+
+static const struct driver_info rmnet_info_pid9048 = {
+ .description = "RmNET net device",
+ .bind = rmnet_usb_bind,
+ .tx_fixup = rmnet_usb_tx_fixup,
+ .rx_fixup = rmnet_usb_rx_fixup,
+ .manage_power = rmnet_usb_manage_power,
+ .data = PID9048_IFACE_MASK,
+};
+
+static const struct driver_info rmnet_info_pid904c = {
+ .description = "RmNET net device",
+ .bind = rmnet_usb_bind,
+ .tx_fixup = rmnet_usb_tx_fixup,
+ .rx_fixup = rmnet_usb_rx_fixup,
+ .manage_power = rmnet_usb_manage_power,
+ .data = PID904C_IFACE_MASK,
+};
+
+static const struct usb_device_id vidpids[] = {
+ {
+ USB_DEVICE(0x05c6, 0x9034), /* MDM9x15*/
+ .driver_info = (unsigned long)&rmnet_info_pid9034,
+ },
+ {
+ USB_DEVICE(0x05c6, 0x9048), /* MDM9x15*/
+ .driver_info = (unsigned long)&rmnet_info_pid9048,
+ },
+ {
+ USB_DEVICE(0x05c6, 0x904c), /* MDM9x15*/
+ .driver_info = (unsigned long)&rmnet_info_pid904c,
+ },
+
+ { }, /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, vidpids);
+
+static struct usb_driver rmnet_usb = {
+ .name = "rmnet_usb",
+ .id_table = vidpids,
+ .probe = rmnet_usb_probe,
+ .disconnect = rmnet_usb_disconnect,
+ .suspend = rmnet_usb_suspend,
+ .resume = rmnet_usb_resume,
+ .supports_autosuspend = true,
+};
+
+static int __init rmnet_usb_init(void)
+{
+ int retval;
+
+ retval = usb_register(&rmnet_usb);
+ if (retval) {
+ err("usb_register failed: %d", retval);
+ return retval;
+ }
+ /* initialize rmnet ctrl device here*/
+ retval = rmnet_usb_ctrl_init();
+ if (retval) {
+ usb_deregister(&rmnet_usb);
+ err("rmnet_usb_cmux_init failed: %d", retval);
+ return retval;
+ }
+
+ return 0;
+}
+module_init(rmnet_usb_init);
+
+static void __exit rmnet_usb_exit(void)
+{
+ rmnet_usb_ctrl_exit();
+ usb_deregister(&rmnet_usb);
+}
+module_exit(rmnet_usb_exit);
+
+MODULE_DESCRIPTION("msm rmnet usb device");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/usb/usbnet.c b/drivers/net/usb/usbnet.c
index ce395fe..d07487b 100644
--- a/drivers/net/usb/usbnet.c
+++ b/drivers/net/usb/usbnet.c
@@ -35,6 +35,7 @@
#include <linux/module.h>
#include <linux/init.h>
+#include <linux/if_arp.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/ctype.h>
@@ -231,7 +232,9 @@ void usbnet_skb_return (struct usbnet *dev, struct sk_buff *skb)
return;
}
- skb->protocol = eth_type_trans (skb, dev->net);
+ if (!skb->protocol)
+ skb->protocol = eth_type_trans(skb, dev->net);
+
dev->net->stats.rx_packets++;
dev->net->stats.rx_bytes += skb->len;
@@ -330,7 +333,9 @@ static int rx_submit (struct usbnet *dev, struct urb *urb, gfp_t flags)
usb_free_urb (urb);
return -ENOMEM;
}
- skb_reserve (skb, NET_IP_ALIGN);
+
+ if (dev->net->type != ARPHRD_RAWIP)
+ skb_reserve(skb, NET_IP_ALIGN);
entry = (struct skb_data *) skb->cb;
entry->urb = urb;
@@ -367,6 +372,7 @@ static int rx_submit (struct usbnet *dev, struct urb *urb, gfp_t flags)
tasklet_schedule (&dev->bh);
break;
case 0:
+ usb_mark_last_busy(dev->udev);
__skb_queue_tail (&dev->rxq, skb);
}
} else {