net: usb: Add support for embedded rmnet usb host driver

Embedded rmnet usb host driver will be used to communicate with modem
devices having rmnet interface. This driver works as pass through layer
for control and data path communication. Driver currently supports device
with product id: 0x9034 and vendor id : 0x05c6.

Change-Id: Ie037af915f2650828420a351bd3fe503a505eaee
Signed-off-by: Hemant Kumar <hemantk@codeaurora.org>
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 @@
 
 	  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_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..b693b18
--- /dev/null
+++ b/drivers/net/usb/rmnet_usb_ctrl.c
@@ -0,0 +1,1071 @@
+/* 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.
+ */
+
+#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, 0666, 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_autopm_get_interface(dev->intf);
+	if (retval < 0) {
+		dev_err(dev->devicep, "%s Resumption fail\n", __func__);
+		goto done_nopm;
+	}
+
+	retval = usb_submit_urb(dev->inturb, GFP_KERNEL);
+	if (retval < 0)
+		dev_err(dev->devicep, "%s Intr submit %d\n", __func__, retval);
+
+	usb_autopm_put_interface(dev->intf);
+
+done_nopm:
+	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)
+{
+	int			retval = 0;
+	struct usb_device	*udev;
+
+	if (!is_dev_connected(dev))
+		return -ENODEV;
+
+	udev = interface_to_usbdev(dev->intf);
+	retval = usb_autopm_get_interface(dev->intf);
+	if (retval < 0) {
+		dev_err(dev->devicep, "%s: Unable to resume interface: %d\n",
+			__func__, retval);
+
+		/*
+		* Revisit if (retval == -EPERM)
+		*		rmnet_usb_suspend(dev->intf, PMSG_SUSPEND);
+		*/
+
+		return retval;
+	}
+	dev->set_ctrl_line_state_cnt++;
+	retval = 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);
+	usb_autopm_put_interface(dev->intf);
+
+	return retval;
+}
+
+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_async(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_async(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_err(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)
+{
+	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);
+
+	return rmnet_usb_ctrl_write_cmd(dev);
+}
+
+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;
+
+	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);
+
+	ret = rmnet_usb_ctrl_write_cmd(dev);
+	if (ret < 0)
+		return ret;
+
+	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);
+
+	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..6df586d
--- /dev/null
+++ b/drivers/net/usb/rmnet_usb_data.c
@@ -0,0 +1,564 @@
+/* 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.
+ */
+
+#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
+
+#define FIRST_RMNET_USB_INTERFACE	4
+#define NUM_EMBEDDED_RMNET_IFACE	4
+
+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_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;
+	int			iface_num;
+	int			last_rmnet_iface_num;
+	int			status = -ENODEV;
+
+	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;
+	}
+
+	last_rmnet_iface_num = (FIRST_RMNET_USB_INTERFACE +
+		 NUM_EMBEDDED_RMNET_IFACE - 1);
+
+	if (iface_num >= FIRST_RMNET_USB_INTERFACE &&
+		iface_num <= last_rmnet_iface_num) {
+		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;
+
+		/*save control device intstance */
+		unet->data[1] = (unsigned long)ctrl_dev	\
+		[iface_num - FIRST_RMNET_USB_INTERFACE];
+
+		status = rmnet_usb_ctrl_probe(iface, unet->status,
+			(struct rmnet_ctrl_dev *)unet->data[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;
+	int			iface_num;
+	int			last_rmnet_iface_num;
+
+	udev = interface_to_usbdev(intf);
+	iface_num = intf->cur_altsetting->desc.bInterfaceNumber;
+
+	last_rmnet_iface_num = (FIRST_RMNET_USB_INTERFACE +
+			 NUM_EMBEDDED_RMNET_IFACE - 1);
+
+	if (iface_num >= FIRST_RMNET_USB_INTERFACE &&
+		iface_num <= last_rmnet_iface_num) {
+		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);
+	}
+}
+
+static const struct driver_info rmnet_info = {
+	.description   = "RmNET net device",
+	.bind          = rmnet_usb_bind,
+	.tx_fixup      = rmnet_usb_tx_fixup,
+	.rx_fixup      = rmnet_usb_rx_fixup,
+	.data          = 0,
+};
+
+static const struct usb_device_id vidpids[] = {
+	{
+		USB_DEVICE(0x05c6, 0x9034),	/* MDM9x15*/
+		.driver_info = (unsigned long)&rmnet_info,
+	},
+	{ },
+};
+
+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..dce8d6d 100644
--- a/drivers/net/usb/usbnet.c
+++ b/drivers/net/usb/usbnet.c
@@ -231,6 +231,9 @@
 		return;
 	}
 
+	if (!skb->protocol)
+		skb->protocol = eth_type_trans(skb, dev->net);
+
 	skb->protocol = eth_type_trans (skb, dev->net);
 	dev->net->stats.rx_packets++;
 	dev->net->stats.rx_bytes += skb->len;