net: usb: Add MUX support in embedded rmnet driver
Due to limited number of HSIC endpoints, to support
more than existing number of control and data channels,
driver needs to MUX all the data channels as well as
the control channels into one HSIC interface. MUX
support is enabled on PID 0x9075. MUX header is added
to IP/control packet based on the channel id while
transmitting to mdm device. Similarly, while receiving
driver removes and parses the MUX header and based on
channel id it forwards IP/control packet to appropriate
network interface/control channel.
Change-Id: If37c672347ab47be6e02d4efd33a70c46486b5f1
Signed-off-by: Hemant Kumar <hemantk@codeaurora.org>
diff --git a/drivers/net/usb/rmnet_usb_ctrl.h b/drivers/net/usb/rmnet_usb.h
similarity index 71%
rename from drivers/net/usb/rmnet_usb_ctrl.h
rename to drivers/net/usb/rmnet_usb.h
index 57ea8ba..b895969 100644
--- a/drivers/net/usb/rmnet_usb_ctrl.h
+++ b/drivers/net/usb/rmnet_usb.h
@@ -10,8 +10,8 @@
* GNU General Public License for more details.
*/
-#ifndef __RMNET_USB_CTRL_H
-#define __RMNET_USB_CTRL_H
+#ifndef __RMNET_USB_H
+#define __RMNET_USB_H
#include <linux/mutex.h>
#include <linux/usb.h>
@@ -20,8 +20,36 @@
#include <linux/usb/cdc.h>
#define MAX_RMNET_DEVS 4
+#define MAX_RMNET_INSTS_PER_DEV 17
+#define TOTAL_RMNET_DEV_COUNT (MAX_RMNET_DEVS * MAX_RMNET_INSTS_PER_DEV)
+
#define CTRL_DEV_MAX_LEN 10
+#define RMNET_CTRL_DEV_OPEN 0
+#define RMNET_CTRL_DEV_READY 1
+#define RMNET_CTRL_DEV_MUX_EN 2
+
+/*MUX header bit masks*/
+#define MUX_CTRL_MASK 0x1
+#define MUX_PAD_SHIFT 0x2
+
+/*max padding bytes for n byte alignment*/
+#define MAX_PAD_BYTES(n) (n-1)
+
+/*
+ *MUX Header Format
+ *BIT 0 : Mux type 0: Data, 1: control
+ *BIT 1: Reserved
+ *BIT 2-7: Pad bytes
+ *BIT 8-15: Mux ID
+ *BIT 16-31: PACKET_LEN_WITH_PADDING (Bytes)
+ */
+struct mux_hdr {
+ __u8 padding_info;
+ __u8 mux_id;
+ __le16 pkt_len_w_padding;
+} __packed;
+
struct rmnet_ctrl_dev {
/*for debugging purpose*/
@@ -29,6 +57,10 @@
struct cdev cdev;
struct device *devicep;
+ unsigned ch_id;
+
+ /*to identify the usb device*/
+ unsigned id;
struct usb_interface *intf;
unsigned int int_pipe;
@@ -49,15 +81,7 @@
struct workqueue_struct *wq;
struct work_struct get_encap_work;
- unsigned is_opened;
-
- bool is_connected;
-
- /*
- * track first resp available from mdm when it boots up
- * to avoid bigger timeout value used by qmuxd
- */
- bool resp_available;
+ unsigned long status;
bool claimed;
@@ -78,6 +102,8 @@
unsigned int zlp_cnt;
};
+extern struct workqueue_struct *usbnet_wq;
+
extern int rmnet_usb_ctrl_start_rx(struct rmnet_ctrl_dev *);
extern int rmnet_usb_ctrl_suspend(struct rmnet_ctrl_dev *dev);
extern int rmnet_usb_ctrl_init(int num_devs, int insts_per_dev);
@@ -85,7 +111,8 @@
extern int rmnet_usb_ctrl_probe(struct usb_interface *intf,
struct usb_host_endpoint *int_in,
unsigned long rmnet_devnum,
- struct rmnet_ctrl_dev **ctrldev);
+ unsigned long *data);
extern void rmnet_usb_ctrl_disconnect(struct rmnet_ctrl_dev *);
+extern void rmnet_usb_ctrl_cleanup(struct rmnet_ctrl_dev *dev);
#endif /* __RMNET_USB_H*/
diff --git a/drivers/net/usb/rmnet_usb_ctrl.c b/drivers/net/usb/rmnet_usb_ctrl.c
index cea56a7..42c95a6 100644
--- a/drivers/net/usb/rmnet_usb_ctrl.c
+++ b/drivers/net/usb/rmnet_usb_ctrl.c
@@ -19,7 +19,7 @@
#include <linux/poll.h>
#include <linux/ratelimit.h>
#include <linux/debugfs.h>
-#include "rmnet_usb_ctrl.h"
+#include "rmnet_usb.h"
static char *rmnet_dev_names[MAX_RMNET_DEVS] = {"hsicctl"};
module_param_array(rmnet_dev_names, charp, NULL, S_IRUGO | S_IWUSR);
@@ -106,6 +106,7 @@
struct ctrl_pkt {
size_t data_size;
void *data;
+ void *ctxt;
};
struct ctrl_pkt_list_elem {
@@ -115,18 +116,56 @@
static void resp_avail_cb(struct urb *);
-static int is_dev_connected(struct rmnet_ctrl_dev *dev)
+static int rmnet_usb_ctrl_dmux(struct ctrl_pkt_list_elem *clist)
{
- if (dev) {
- mutex_lock(&dev->dev_lock);
- if (!dev->is_connected) {
- mutex_unlock(&dev->dev_lock);
- return 0;
- }
- mutex_unlock(&dev->dev_lock);
- return 1;
+ struct mux_hdr *hdr;
+ size_t pad_len;
+ size_t total_len;
+ unsigned int mux_id;
+
+ hdr = (struct mux_hdr *)clist->cpkt.data;
+ pad_len = hdr->padding_info >> MUX_PAD_SHIFT;
+ if (pad_len > MAX_PAD_BYTES(4)) {
+ pr_err_ratelimited("%s: Invalid pad len %d\n", __func__,
+ pad_len);
+ return -EINVAL;
}
- return 0;
+
+ mux_id = hdr->mux_id;
+ if (!mux_id || mux_id > insts_per_dev) {
+ pr_err_ratelimited("%s: Invalid mux id %d\n", __func__, mux_id);
+ return -EINVAL;
+ }
+
+ total_len = le16_to_cpu(hdr->pkt_len_w_padding);
+ if (!total_len || !(total_len - pad_len)) {
+ pr_err_ratelimited("%s: Invalid pkt length %d\n", __func__,
+ total_len);
+ return -EINVAL;
+ }
+
+ clist->cpkt.data_size = total_len - pad_len;
+
+ return mux_id - 1;
+}
+
+static void rmnet_usb_ctrl_mux(unsigned int id, struct ctrl_pkt *cpkt)
+{
+ struct mux_hdr *hdr;
+ size_t len;
+ size_t pad_len = 0;
+
+ hdr = (struct mux_hdr *)cpkt->data;
+ hdr->mux_id = id + 1;
+ len = cpkt->data_size - sizeof(struct mux_hdr) - MAX_PAD_BYTES(4);
+
+ /*add padding if len is not 4 byte aligned*/
+ pad_len = ALIGN(len, 4) - len;
+
+ hdr->pkt_len_w_padding = cpu_to_le16(len + pad_len);
+ hdr->padding_info = (pad_len << MUX_PAD_SHIFT) | MUX_CTRL_MASK;
+
+ cpkt->data_size = sizeof(struct mux_hdr) + hdr->pkt_len_w_padding;
}
static void get_encap_work(struct work_struct *w)
@@ -136,6 +175,9 @@
container_of(w, struct rmnet_ctrl_dev, get_encap_work);
int status;
+ if (!test_bit(RMNET_CTRL_DEV_READY, &dev->status))
+ return;
+
udev = interface_to_usbdev(dev->intf);
status = usb_autopm_get_interface(dev->intf);
@@ -225,11 +267,6 @@
usb_mark_last_busy(udev);
queue_work(dev->wq, &dev->get_encap_work);
- if (!dev->resp_available) {
- dev->resp_available = true;
- wake_up(&dev->open_wait_queue);
- }
-
return;
default:
dev_err(dev->devicep,
@@ -252,9 +289,9 @@
{
struct usb_device *udev;
struct ctrl_pkt_list_elem *list_elem = NULL;
- struct rmnet_ctrl_dev *dev = urb->context;
+ struct rmnet_ctrl_dev *rx_dev, *dev = urb->context;
void *cpkt;
- int status = 0;
+ int ch_id, status = 0;
size_t cpkt_size = 0;
udev = interface_to_usbdev(dev->intf);
@@ -264,7 +301,6 @@
switch (urb->status) {
case 0:
/*success*/
- dev->get_encap_resp_cnt++;
break;
/*do not resubmit*/
@@ -309,11 +345,27 @@
}
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);
+ rx_dev = dev;
+
+ if (test_bit(RMNET_CTRL_DEV_MUX_EN, &dev->status)) {
+ ch_id = rmnet_usb_ctrl_dmux(list_elem);
+ if (ch_id < 0) {
+ kfree(list_elem->cpkt.data);
+ kfree(list_elem);
+ goto resubmit_int_urb;
+ }
+
+ rx_dev = &ctrl_devs[dev->id][ch_id];
+ }
+
+ rx_dev->get_encap_resp_cnt++;
+
+ spin_lock(&rx_dev->rx_lock);
+ list_add_tail(&list_elem->list, &rx_dev->rx_list);
+ spin_unlock(&rx_dev->rx_lock);
+
+ wake_up(&rx_dev->read_wait_queue);
resubmit_int_urb:
/*check if it is already submitted in resume*/
@@ -378,7 +430,7 @@
{
struct usb_device *udev;
- if (!is_dev_connected(dev))
+ if (!test_bit(RMNET_CTRL_DEV_READY, &dev->status))
return -ENODEV;
udev = interface_to_usbdev(dev->intf);
@@ -393,7 +445,8 @@
static void ctrl_write_callback(struct urb *urb)
{
- struct rmnet_ctrl_dev *dev = urb->context;
+ struct ctrl_pkt *cpkt = urb->context;
+ struct rmnet_ctrl_dev *dev = cpkt->ctxt;
if (urb->status) {
dev->tx_ctrl_err_cnt++;
@@ -404,18 +457,19 @@
kfree(urb->setup_packet);
kfree(urb->transfer_buffer);
usb_free_urb(urb);
+ kfree(cpkt);
usb_autopm_put_interface_async(dev->intf);
}
-static int rmnet_usb_ctrl_write(struct rmnet_ctrl_dev *dev, char *buf,
- size_t size)
+static int rmnet_usb_ctrl_write(struct rmnet_ctrl_dev *dev,
+ struct ctrl_pkt *cpkt, size_t size)
{
int result;
struct urb *sndurb;
struct usb_ctrlrequest *out_ctlreq;
struct usb_device *udev;
- if (!is_dev_connected(dev))
+ if (!test_bit(RMNET_CTRL_DEV_READY, &dev->status))
return -ENETRESET;
udev = interface_to_usbdev(dev->intf);
@@ -439,12 +493,12 @@
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);
+ out_ctlreq->wLength = cpu_to_le16(cpkt->data_size);
usb_fill_control_urb(sndurb, udev,
usb_sndctrlpipe(udev, 0),
- (unsigned char *)out_ctlreq, (void *)buf, size,
- ctrl_write_callback, dev);
+ (unsigned char *)out_ctlreq, (void *)cpkt->data,
+ cpkt->data_size, ctrl_write_callback, cpkt);
result = usb_autopm_get_interface(dev->intf);
if (result < 0) {
@@ -487,16 +541,15 @@
if (!dev)
return -ENODEV;
- if (dev->is_opened)
+ if (test_bit(RMNET_CTRL_DEV_OPEN, &dev->status))
goto already_opened;
- /*block open to get first response available from mdm*/
- if (dev->mdm_wait_timeout && !dev->resp_available) {
+ if (dev->mdm_wait_timeout &&
+ !test_bit(RMNET_CTRL_DEV_READY, &dev->status)) {
retval = wait_event_interruptible_timeout(
- dev->open_wait_queue,
- dev->resp_available,
- msecs_to_jiffies(dev->mdm_wait_timeout *
- 1000));
+ dev->open_wait_queue,
+ test_bit(RMNET_CTRL_DEV_READY, &dev->status),
+ msecs_to_jiffies(dev->mdm_wait_timeout * 1000));
if (retval == 0) {
dev_err(dev->devicep, "%s: Timeout opening %s\n",
__func__, dev->name);
@@ -508,15 +561,13 @@
}
}
- if (!dev->resp_available) {
+ if (!test_bit(RMNET_CTRL_DEV_READY, &dev->status)) {
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);
+ set_bit(RMNET_CTRL_DEV_OPEN, &dev->status);
file->private_data = dev;
@@ -551,9 +602,7 @@
}
spin_unlock_irqrestore(&dev->rx_lock, flag);
- mutex_lock(&dev->dev_lock);
- dev->is_opened = 0;
- mutex_unlock(&dev->dev_lock);
+ clear_bit(RMNET_CTRL_DEV_OPEN, &dev->status);
time = usb_wait_anchor_empty_timeout(&dev->tx_submitted,
UNLINK_TIMEOUT_MS);
@@ -575,7 +624,7 @@
return POLLERR;
poll_wait(file, &dev->read_wait_queue, wait);
- if (!is_dev_connected(dev)) {
+ if (!test_bit(RMNET_CTRL_DEV_READY, &dev->status)) {
dev_dbg(dev->devicep, "%s: Device not connected\n",
__func__);
return POLLERR;
@@ -592,6 +641,7 @@
{
int retval = 0;
int bytes_to_read;
+ unsigned int hdr_len = 0;
struct rmnet_ctrl_dev *dev;
struct ctrl_pkt_list_elem *list_elem = NULL;
unsigned long flags;
@@ -603,7 +653,7 @@
DBG("%s: Read from %s\n", __func__, dev->name);
ctrl_read:
- if (!is_dev_connected(dev)) {
+ if (!test_bit(RMNET_CTRL_DEV_READY, &dev->status)) {
dev_dbg(dev->devicep, "%s: Device not connected\n",
__func__);
return -ENETRESET;
@@ -613,8 +663,8 @@
spin_unlock_irqrestore(&dev->rx_lock, flags);
retval = wait_event_interruptible(dev->read_wait_queue,
- !list_empty(&dev->rx_list) ||
- !is_dev_connected(dev));
+ !list_empty(&dev->rx_list) ||
+ !test_bit(RMNET_CTRL_DEV_READY, &dev->status));
if (retval < 0)
return retval;
@@ -632,7 +682,10 @@
}
spin_unlock_irqrestore(&dev->rx_lock, flags);
- if (copy_to_user(buf, list_elem->cpkt.data, bytes_to_read)) {
+ if (test_bit(RMNET_CTRL_DEV_MUX_EN, &dev->status))
+ hdr_len = sizeof(struct mux_hdr);
+
+ if (copy_to_user(buf, list_elem->cpkt.data + hdr_len, bytes_to_read)) {
dev_err(dev->devicep,
"%s: copy_to_user failed for %s\n",
__func__, dev->name);
@@ -655,7 +708,10 @@
size_t size, loff_t *pos)
{
int status;
+ size_t total_len;
void *wbuf;
+ void *actual_data;
+ struct ctrl_pkt *cpkt;
struct rmnet_ctrl_dev *dev = file->private_data;
if (!dev)
@@ -664,26 +720,46 @@
if (size <= 0)
return -EINVAL;
- if (!is_dev_connected(dev))
+ if (!test_bit(RMNET_CTRL_DEV_READY, &dev->status))
return -ENETRESET;
DBG("%s: Writing %i bytes on %s\n", __func__, size, dev->name);
- wbuf = kmalloc(size , GFP_KERNEL);
+ total_len = size;
+
+ if (test_bit(RMNET_CTRL_DEV_MUX_EN, &dev->status))
+ total_len += sizeof(struct mux_hdr) + MAX_PAD_BYTES(4);
+
+ wbuf = kmalloc(total_len , GFP_KERNEL);
if (!wbuf)
return -ENOMEM;
- status = copy_from_user(wbuf , buf, size);
+ cpkt = kmalloc(sizeof(struct ctrl_pkt), GFP_KERNEL);
+ if (!cpkt) {
+ kfree(wbuf);
+ return -ENOMEM;
+ }
+ actual_data = cpkt->data = wbuf;
+ cpkt->data_size = total_len;
+ cpkt->ctxt = dev;
+
+ if (test_bit(RMNET_CTRL_DEV_MUX_EN, &dev->status)) {
+ actual_data = wbuf + sizeof(struct mux_hdr);
+ rmnet_usb_ctrl_mux(dev->ch_id, cpkt);
+ }
+
+ status = copy_from_user(actual_data, buf, size);
if (status) {
dev_err(dev->devicep,
"%s: Unable to copy data from userspace %d\n",
__func__, status);
kfree(wbuf);
+ kfree(cpkt);
return status;
}
DUMP_BUFFER("Write: ", size, buf);
- status = rmnet_usb_ctrl_write(dev, wbuf, size);
+ status = rmnet_usb_ctrl_write(dev, cpkt, size);
if (status == size)
return size;
@@ -783,10 +859,18 @@
.poll = rmnet_ctl_poll,
};
+void rmnet_usb_ctrl_cleanup(struct rmnet_ctrl_dev *dev)
+{
+ if (dev) {
+ usb_free_urb(dev->inturb);
+ kfree(dev->intbuf);
+ }
+}
+
int rmnet_usb_ctrl_probe(struct usb_interface *intf,
struct usb_host_endpoint *int_in,
unsigned long rmnet_devnum,
- struct rmnet_ctrl_dev **ctrldev)
+ unsigned long *data)
{
struct rmnet_ctrl_dev *dev = NULL;
u16 wMaxPacketSize;
@@ -811,34 +895,16 @@
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;
+ dev->id = rmnet_devnum;
- /*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 = 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);
- if (ret < 0)
- return ret;
-
- dev->set_ctrl_line_state_cnt++;
-
dev->inturb = usb_alloc_urb(0, GFP_KERNEL);
if (!dev->inturb) {
dev_err(dev->devicep, "Error allocating int urb\n");
@@ -875,11 +941,23 @@
usb_mark_last_busy(udev);
ret = rmnet_usb_ctrl_start_rx(dev);
- if (!ret)
- dev->is_connected = true;
+ if (ret) {
+ usb_free_urb(dev->inturb);
+ kfree(dev->intbuf);
+ return ret;
+ }
dev->claimed = true;
- *ctrldev = dev;
+
+ /*mux info is passed to data parameter*/
+ if (*data)
+ set_bit(RMNET_CTRL_DEV_MUX_EN, &dev->status);
+
+ *data = (unsigned long)dev;
+
+ set_bit(RMNET_CTRL_DEV_READY, &dev->status);
+ wake_up(&dev->open_wait_queue);
+
return 0;
}
@@ -887,11 +965,12 @@
{
dev->claimed = false;
+ clear_bit(RMNET_CTRL_DEV_READY, &dev->status);
+
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->is_connected = false;
mutex_unlock(&dev->dev_lock);
wake_up(&dev->read_wait_queue);
@@ -938,7 +1017,9 @@
"mdm_wait_timeout: %u\n"
"zlp_cnt: %u\n"
"get_encap_failure_cnt %u\n"
- "dev opened: %s\n",
+ "RMNET_CTRL_DEV_MUX_EN: %d\n"
+ "RMNET_CTRL_DEV_OPEN: %d\n"
+ "RMNET_CTRL_DEV_READY: %d\n",
dev, dev->name,
dev->snd_encap_cmd_cnt,
dev->resp_avail_cnt,
@@ -950,7 +1031,12 @@
dev->mdm_wait_timeout,
dev->zlp_cnt,
dev->get_encap_failure_cnt,
- dev->is_opened ? "OPEN" : "CLOSE");
+ test_bit(RMNET_CTRL_DEV_MUX_EN,
+ &dev->status),
+ test_bit(RMNET_CTRL_DEV_OPEN,
+ &dev->status),
+ test_bit(RMNET_CTRL_DEV_READY,
+ &dev->status));
}
}
@@ -1059,6 +1145,8 @@
return -ENOMEM;
}
+ dev->ch_id = n;
+
mutex_init(&dev->dev_lock);
spin_lock_init(&dev->rx_lock);
init_waitqueue_head(&dev->read_wait_queue);
diff --git a/drivers/net/usb/rmnet_usb_data.c b/drivers/net/usb/rmnet_usb_data.c
index c5861f7..5f0d342 100644
--- a/drivers/net/usb/rmnet_usb_data.c
+++ b/drivers/net/usb/rmnet_usb_data.c
@@ -17,13 +17,36 @@
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/usb.h>
+#include <linux/ratelimit.h>
#include <linux/usb/usbnet.h>
#include <linux/msm_rmnet.h>
-#include "rmnet_usb_ctrl.h"
+#include "rmnet_usb.h"
#define RMNET_DATA_LEN 2000
-#define HEADROOM_FOR_QOS 8
+#define RMNET_HEADROOM_W_MUX (sizeof(struct mux_hdr) + \
+ sizeof(struct QMI_QOS_HDR_S))
+#define RMNET_HEADROOM sizeof(struct QMI_QOS_HDR_S)
+#define RMNET_TAILROOM MAX_PAD_BYTES(4);
+
+static unsigned int no_rmnet_devs = 1;
+module_param(no_rmnet_devs, uint, S_IRUGO | S_IWUSR);
+
+unsigned int no_rmnet_insts_per_dev = 4;
+module_param(no_rmnet_insts_per_dev, uint, S_IRUGO | S_IWUSR);
+
+/*
+ * To support mux on multiple devices, bit position represents device
+ * and value represnts if mux is enabled or disabled.
+ * e.g. bit 0: mdm over HSIC, bit1: mdm over hsusb
+ */
+static unsigned long mux_enabled;
+module_param(mux_enabled, ulong, S_IRUGO | S_IWUSR);
+
+static unsigned int no_fwd_rmnet_links;
+module_param(no_fwd_rmnet_links, uint, S_IRUGO | S_IWUSR);
+
+struct usbnet *unet_list[TOTAL_RMNET_DEV_COUNT];
/* net device name prefixes, indexed by driver_info->data */
static const char * const rmnet_names[] = {
@@ -31,6 +54,11 @@
"rmnet2_usb%d",
};
+/* net device reverse link name prefixes, indexed by driver_info->data */
+static const char * const rev_rmnet_names[] = {
+ "rev_rmnet_usb%d",
+ "rev_rmnet2_usb%d",
+};
static int data_msg_dbg_mask;
enum {
@@ -86,12 +114,6 @@
#define DBG1(x...) DBG(DEBUG_MASK_LVL1, x)
#define DBG2(x...) DBG(DEBUG_MASK_LVL2, x)
-static unsigned int no_rmnet_devs = 1;
-module_param(no_rmnet_devs, uint, S_IRUGO | S_IWUSR);
-
-static unsigned int no_rmnet_insts_per_dev = 4;
-module_param(no_rmnet_insts_per_dev, uint, S_IRUGO | S_IWUSR);
-
static int rmnet_data_start(void);
static bool rmnet_data_init;
@@ -120,43 +142,82 @@
module_param_cb(rmnet_data_init, &rmnet_init_ops, &rmnet_data_init,
S_IRUGO | S_IWUSR);
-static void rmnet_usb_setup(struct net_device *);
+static void rmnet_usb_setup(struct net_device *, int mux_enabled);
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 usbnet *unet = usb_get_intfdata(iface);
struct rmnet_ctrl_dev *dev;
+ int i, n, rdev_cnt, unet_id;
+ int retval = 0;
- unet = usb_get_intfdata(iface);
+ rdev_cnt = unet->data[4] ? no_rmnet_insts_per_dev : 1;
- dev = (struct rmnet_ctrl_dev *)unet->data[1];
+ for (n = 0; n < rdev_cnt; n++) {
+ unet_id = n + unet->driver_info->data * no_rmnet_insts_per_dev;
+ unet =
+ unet->data[4] ? unet_list[unet_id] : usb_get_intfdata(iface);
- if (work_busy(&dev->get_encap_work))
- return -EBUSY;
+ dev = (struct rmnet_ctrl_dev *)unet->data[1];
+ spin_lock_irq(&unet->txq.lock);
+ if (work_busy(&dev->get_encap_work) || unet->txq.qlen) {
+ spin_unlock_irq(&unet->txq.lock);
+ retval = -EBUSY;
+ goto abort_suspend;
+ }
- if (usbnet_suspend(iface, message))
- return -EBUSY;
+ set_bit(EVENT_DEV_ASLEEP, &unet->flags);
+ spin_unlock_irq(&unet->txq.lock);
+ }
- usb_kill_anchored_urbs(&dev->rx_submitted);
+ for (n = 0; n < rdev_cnt; n++) {
+ unet_id = n + unet->driver_info->data * no_rmnet_insts_per_dev;
+ unet =
+ unet->data[4] ? unet_list[unet_id] : usb_get_intfdata(iface);
+
+ dev = (struct rmnet_ctrl_dev *)unet->data[1];
+ usb_kill_anchored_urbs(&dev->rx_submitted);
+ netif_device_detach(unet->net);
+ usbnet_terminate_urbs(unet);
+ netif_device_attach(unet->net);
+ }
return 0;
+
+abort_suspend:
+ for (i = 0; i < n; i++) {
+ unet_id = i + unet->driver_info->data * no_rmnet_insts_per_dev;
+ unet =
+ unet->data[4] ? unet_list[unet_id] : usb_get_intfdata(iface);
+ spin_lock_irq(&unet->txq.lock);
+ clear_bit(EVENT_DEV_ASLEEP, &unet->flags);
+ spin_unlock_irq(&unet->txq.lock);
+ }
+ return retval;
}
static int rmnet_usb_resume(struct usb_interface *iface)
{
- int retval = 0;
- struct usbnet *unet;
+ struct usbnet *unet = usb_get_intfdata(iface);
struct rmnet_ctrl_dev *dev;
+ int n, rdev_cnt, unet_id;
- unet = usb_get_intfdata(iface);
+ rdev_cnt = unet->data[4] ? no_rmnet_insts_per_dev : 1;
- dev = (struct rmnet_ctrl_dev *)unet->data[1];
+ for (n = 0; n < rdev_cnt; n++) {
+ unet_id = n + unet->driver_info->data * no_rmnet_insts_per_dev;
+ unet =
+ unet->data[4] ? unet_list[unet_id] : usb_get_intfdata(iface);
- usbnet_resume(iface);
- retval = rmnet_usb_ctrl_start_rx(dev);
+ dev = (struct rmnet_ctrl_dev *)unet->data[1];
+ rmnet_usb_ctrl_start_rx(dev);
+ usb_set_intfdata(iface, unet);
+ unet->suspend_count = 1;
+ usbnet_resume(iface);
+ }
- return retval;
+ return 0;
}
static int rmnet_usb_bind(struct usbnet *usbnet, struct usb_interface *iface)
@@ -169,6 +230,9 @@
int status = 0;
int i;
int numends;
+ bool mux;
+
+ mux = test_bit(info->data, &mux_enabled);
numends = iface->cur_altsetting->desc.bNumEndpoints;
for (i = 0; i < numends; i++) {
@@ -199,13 +263,71 @@
usbnet->status = int_in;
/*change name of net device to rmnet_usbx here*/
- strlcpy(usbnet->net->name, rmnet_names[info->data], IFNAMSIZ);
+ if (mux && (info->in > no_fwd_rmnet_links))
+ strlcpy(usbnet->net->name, rev_rmnet_names[info->data],
+ IFNAMSIZ);
+ else
+ strlcpy(usbnet->net->name, rmnet_names[info->data],
+ IFNAMSIZ);
- /*TBD: update rx_urb_size, curently set to eth frame len by usbnet*/
+ if (mux)
+ usbnet->rx_urb_size = usbnet->hard_mtu + sizeof(struct mux_hdr)
+ + MAX_PAD_BYTES(4);
out:
return status;
}
+static int rmnet_usb_data_dmux(struct sk_buff *skb, struct urb *rx_urb)
+{
+ struct mux_hdr *hdr;
+ size_t pad_len;
+ size_t total_len;
+ unsigned int mux_id;
+
+ hdr = (struct mux_hdr *)skb->data;
+ mux_id = hdr->mux_id;
+ if (!mux_id || mux_id > no_rmnet_insts_per_dev) {
+ pr_err_ratelimited("%s: Invalid data channel id %u.\n",
+ __func__, mux_id);
+ return -EINVAL;
+ }
+
+ pad_len = hdr->padding_info >> MUX_PAD_SHIFT;
+ if (pad_len > MAX_PAD_BYTES(4)) {
+ pr_err_ratelimited("%s: Invalid pad len %d\n",
+ __func__, pad_len);
+ return -EINVAL;
+ }
+
+ total_len = le16_to_cpu(hdr->pkt_len_w_padding);
+ if (!total_len || !(total_len - pad_len)) {
+ pr_err_ratelimited("%s: Invalid pkt length %d\n", __func__,
+ total_len);
+ return -EINVAL;
+ }
+
+ skb->data = (unsigned char *)(hdr + 1);
+ rx_urb->actual_length = total_len - pad_len;
+
+ return mux_id - 1;
+}
+
+static void rmnet_usb_data_mux(struct sk_buff *skb, unsigned int id)
+{
+ struct mux_hdr *hdr;
+ size_t len;
+
+ hdr = (struct mux_hdr *)skb_push(skb, sizeof(struct mux_hdr));
+ hdr->mux_id = id + 1;
+ len = skb->len - sizeof(struct mux_hdr);
+
+ /*add padding if len is not 4 byte aligned*/
+ skb_put(skb, ALIGN(len, 4) - len);
+
+ hdr->pkt_len_w_padding = cpu_to_le16(skb->len - sizeof(struct mux_hdr));
+ hdr->padding_info = (ALIGN(len, 4) - len) << MUX_PAD_SHIFT;
+}
+
static struct sk_buff *rmnet_usb_tx_fixup(struct usbnet *dev,
struct sk_buff *skb, gfp_t flags)
{
@@ -219,6 +341,9 @@
qmih->flow_id = skb->mark;
}
+ if (dev->data[4])
+ rmnet_usb_data_mux(skb, dev->data[3]);
+
DBG1("[%s] Tx packet #%lu len=%d mark=0x%x\n",
dev->net->name, dev->net->stats.tx_packets, skb->len, skb->mark);
@@ -247,10 +372,35 @@
return protocol;
}
-static int rmnet_usb_rx_fixup(struct usbnet *dev,
- struct sk_buff *skb)
+static void rmnet_usb_rx_complete(struct urb *rx_urb)
{
+ struct sk_buff *skb = (struct sk_buff *) rx_urb->context;
+ struct skb_data *entry = (struct skb_data *) skb->cb;
+ struct usbnet *dev = entry->dev;
+ unsigned int unet_offset;
+ unsigned int unet_id;
+ int mux_id;
+ unet_offset = dev->driver_info->data * no_rmnet_insts_per_dev;
+
+ if (!rx_urb->status && dev->data[4]) {
+ mux_id = rmnet_usb_data_dmux(skb, rx_urb);
+ if (mux_id < 0) {
+ /*resubmit urb and free skb in rx_complete*/
+ rx_urb->status = -EINVAL;
+ } else {
+ /*map urb to actual network iface based on mux id*/
+ unet_id = unet_offset + mux_id;
+ skb->dev = unet_list[unet_id]->net;
+ entry->dev = unet_list[unet_id];
+ }
+ }
+
+ rx_complete(rx_urb);
+}
+
+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*/
@@ -311,7 +461,6 @@
.ndo_validate_addr = 0,
};
-
static int rmnet_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
{
struct usbnet *unet = netdev_priv(dev);
@@ -347,7 +496,6 @@
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]);
@@ -406,7 +554,7 @@
return rc;
}
-static void rmnet_usb_setup(struct net_device *dev)
+static void rmnet_usb_setup(struct net_device *dev, int mux_enabled)
{
/* Using Ethernet mode by default */
dev->netdev_ops = &rmnet_usb_ops_ether;
@@ -414,7 +562,15 @@
/* set this after calling ether_setup */
dev->mtu = RMNET_DATA_LEN;
- dev->needed_headroom = HEADROOM_FOR_QOS;
+ if (mux_enabled) {
+ dev->needed_headroom = RMNET_HEADROOM_W_MUX;
+
+ /*max pad bytes for 4 byte alignment*/
+ dev->needed_tailroom = RMNET_TAILROOM;
+ } else {
+ dev->needed_headroom = RMNET_HEADROOM;
+ }
+
random_ether_addr(dev->dev_addr);
dev->watchdog_timeo = 1000; /* 10 seconds? */
}
@@ -444,7 +600,6 @@
seq_printf(s, "tx errors: %lu\n", unet->net->stats.tx_errors);
seq_printf(s, "tx packets: %lu\n", unet->net->stats.tx_packets);
seq_printf(s, "tx bytes: %lu\n", unet->net->stats.tx_bytes);
- seq_printf(s, "suspend count: %d\n", unet->suspend_count);
seq_printf(s, "EVENT_DEV_OPEN: %d\n",
test_bit(EVENT_DEV_OPEN, &unet->flags));
seq_printf(s, "EVENT_TX_HALT: %d\n",
@@ -499,8 +654,10 @@
{
struct dentry *root = (struct dentry *)unet->data[2];
- debugfs_remove_recursive(root);
- unet->data[2] = 0;
+ if (root) {
+ debugfs_remove_recursive(root);
+ unet->data[2] = 0;
+ }
}
static int rmnet_usb_probe(struct usb_interface *iface,
@@ -510,6 +667,10 @@
struct driver_info *info = (struct driver_info *)prod->driver_info;
struct usb_device *udev;
int status = 0;
+ unsigned int i, unet_id, rdev_cnt, n = 0;
+ bool mux;
+
+ udev = interface_to_usbdev(iface);
if (iface->num_altsetting != 1) {
dev_err(&iface->dev, "%s invalid num_altsetting %u\n",
@@ -518,34 +679,63 @@
goto out;
}
- status = usbnet_probe(iface, prod);
- if (status < 0) {
- dev_err(&iface->dev, "usbnet_probe failed %d\n", status);
- goto out;
+ mux = test_bit(info->data, &mux_enabled);
+ rdev_cnt = mux ? no_rmnet_insts_per_dev : 1;
+ info->in = 0;
+
+ for (n = 0; n < rdev_cnt; n++) {
+
+ /* Use this filed to increment device count this will be
+ * used by bind to determin the forward link and reverse
+ * link network interface names.
+ */
+ info->in++;
+ status = usbnet_probe(iface, prod);
+ if (status < 0) {
+ dev_err(&iface->dev, "usbnet_probe failed %d\n",
+ status);
+ goto out;
+ }
+
+ unet_id = n + info->data * no_rmnet_insts_per_dev;
+
+ unet_list[unet_id] = unet = usb_get_intfdata(iface);
+
+ /*store mux id for later access*/
+ unet->data[3] = n;
+
+ /*save mux info for control and usbnet devices*/
+ unet->data[1] = unet->data[4] = mux;
+
+ /*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, mux);
+
+ /*create /sys/class/net/rmnet_usbx/dbg_mask*/
+ status = device_create_file(&unet->net->dev,
+ &dev_attr_dbg_mask);
+ if (status) {
+ free_netdev(unet->net);
+ usb_put_dev(udev);
+ goto out;
+ }
+
+ status = rmnet_usb_ctrl_probe(iface, unet->status, info->data,
+ &unet->data[1]);
+ if (status) {
+ device_remove_file(&unet->net->dev, &dev_attr_dbg_mask);
+ free_netdev(unet->net);
+ usb_put_dev(udev);
+ goto out;
+ }
+
+ status = rmnet_usb_data_debugfs_init(unet);
+ if (status)
+ dev_dbg(&iface->dev,
+ "mode debugfs file is not available\n");
}
- 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;
-
- status = rmnet_usb_ctrl_probe(iface, unet->status, info->data,
- (struct rmnet_ctrl_dev **)&unet->data[1]);
- if (status)
- goto out;
-
- status = rmnet_usb_data_debugfs_init(unet);
- if (status)
- dev_dbg(&iface->dev, "mode debugfs file is not available\n");
-
- udev = unet->udev;
usb_enable_autosuspend(udev);
@@ -559,52 +749,67 @@
pm_runtime_set_autosuspend_delay(&udev->parent->dev, 200);
}
+ return 0;
+
out:
+ for (i = 0; i < n; i++) {
+ unet_id = i + info->data * no_rmnet_insts_per_dev;
+ rmnet_usb_ctrl_cleanup(
+ (struct rmnet_ctrl_dev *)unet_list[unet_id]->data[1]);
+ device_remove_file(&unet_list[unet_id]->net->dev,
+ &dev_attr_dbg_mask);
+ free_netdev(unet_list[unet_id]->net);
+ usb_put_dev(udev);
+ }
+
return status;
}
static void rmnet_usb_disconnect(struct usb_interface *intf)
{
- struct usbnet *unet;
+ struct usbnet *unet = usb_get_intfdata(intf);
struct rmnet_ctrl_dev *dev;
+ unsigned int n, rdev_cnt, unet_id;
- unet = usb_get_intfdata(intf);
- if (!unet) {
- dev_err(&intf->dev, "%s:data device not found\n", __func__);
- return;
- }
+ rdev_cnt = unet->data[4] ? no_rmnet_insts_per_dev : 1;
device_set_wakeup_enable(&unet->udev->dev, 0);
- rmnet_usb_data_debugfs_cleanup(unet);
- dev = (struct rmnet_ctrl_dev *)unet->data[1];
- if (!dev) {
- dev_err(&intf->dev, "%s:ctrl device not found\n", __func__);
- return;
+ for (n = 0; n < rdev_cnt; n++) {
+ unet_id = n + unet->driver_info->data * no_rmnet_insts_per_dev;
+ unet =
+ unet->data[4] ? unet_list[unet_id] : usb_get_intfdata(intf);
+ device_remove_file(&unet->net->dev, &dev_attr_dbg_mask);
+
+ dev = (struct rmnet_ctrl_dev *)unet->data[1];
+ rmnet_usb_ctrl_disconnect(dev);
+ unet->data[0] = 0;
+ unet->data[1] = 0;
+ rmnet_usb_data_debugfs_cleanup(unet);
+ usb_set_intfdata(intf, unet);
+ usbnet_disconnect(intf);
+ unet_list[unet_id] = NULL;
}
- 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 = {
+static struct driver_info rmnet_info = {
.description = "RmNET net device",
.flags = FLAG_SEND_ZLP,
.bind = rmnet_usb_bind,
.tx_fixup = rmnet_usb_tx_fixup,
.rx_fixup = rmnet_usb_rx_fixup,
+ .rx_complete = rmnet_usb_rx_complete,
.manage_power = rmnet_usb_manage_power,
.data = 0,
};
-static const struct driver_info rmnet_usb_info = {
+static struct driver_info rmnet_usb_info = {
.description = "RmNET net device",
.flags = FLAG_SEND_ZLP,
.bind = rmnet_usb_bind,
.tx_fixup = rmnet_usb_tx_fixup,
.rx_fixup = rmnet_usb_rx_fixup,
+ .rx_complete = rmnet_usb_rx_complete,
.manage_power = rmnet_usb_manage_power,
.data = 1,
};
@@ -643,6 +848,9 @@
{ USB_DEVICE_INTERFACE_NUMBER(0x05c6, 0x904c, 8),
.driver_info = (unsigned long)&rmnet_info,
},
+ { USB_DEVICE_INTERFACE_NUMBER(0x05c6, 0x9075, 6), /*mux over hsic mdm*/
+ .driver_info = (unsigned long)&rmnet_info,
+ },
{ USB_DEVICE_INTERFACE_NUMBER(0x05c6, 0x9079, 5),
.driver_info = (unsigned long)&rmnet_usb_info,
},
diff --git a/drivers/net/usb/usbnet.c b/drivers/net/usb/usbnet.c
index e9130f6..d7c5df1 100644
--- a/drivers/net/usb/usbnet.c
+++ b/drivers/net/usb/usbnet.c
@@ -87,7 +87,7 @@
static const char driver_name [] = "usbnet";
-static struct workqueue_struct *usbnet_wq;
+struct workqueue_struct *usbnet_wq;
static DECLARE_WAIT_QUEUE_HEAD(unlink_wakeup);
@@ -344,12 +344,11 @@
/*-------------------------------------------------------------------------*/
-static void rx_complete (struct urb *urb);
-
static int rx_submit (struct usbnet *dev, struct urb *urb, gfp_t flags)
{
struct sk_buff *skb;
struct skb_data *entry;
+ usb_complete_t complete_fn;
int retval = 0;
unsigned long lockflags;
size_t size = dev->rx_urb_size;
@@ -370,8 +369,13 @@
entry->dev = dev;
entry->length = 0;
+ if (dev->driver_info->rx_complete)
+ complete_fn = dev->driver_info->rx_complete;
+ else
+ complete_fn = rx_complete;
+
usb_fill_bulk_urb (urb, dev->udev, dev->in,
- skb->data, size, rx_complete, skb);
+ skb->data, size, complete_fn, skb);
spin_lock_irqsave (&dev->rxq.lock, lockflags);
@@ -445,7 +449,7 @@
/*-------------------------------------------------------------------------*/
-static void rx_complete (struct urb *urb)
+void rx_complete(struct urb *urb)
{
struct sk_buff *skb = (struct sk_buff *) urb->context;
struct skb_data *entry = (struct skb_data *) skb->cb;
@@ -531,6 +535,7 @@
}
netif_dbg(dev, rx_err, dev->net, "no read resubmitted\n");
}
+EXPORT_SYMBOL_GPL(rx_complete);
static void intr_complete (struct urb *urb)
{
@@ -666,7 +671,7 @@
/*-------------------------------------------------------------------------*/
// precondition: never called in_interrupt
-static void usbnet_terminate_urbs(struct usbnet *dev)
+void usbnet_terminate_urbs(struct usbnet *dev)
{
DECLARE_WAITQUEUE(wait, current);
int temp;
@@ -691,6 +696,7 @@
dev->wait = NULL;
remove_wait_queue(&unlink_wakeup, &wait);
}
+EXPORT_SYMBOL_GPL(usbnet_terminate_urbs);
int usbnet_stop (struct net_device *net)
{