diff options
author | codeworkx <daniel.hillenbrand@codeworkx.de> | 2012-06-02 13:09:29 +0200 |
---|---|---|
committer | codeworkx <daniel.hillenbrand@codeworkx.de> | 2012-06-02 13:09:29 +0200 |
commit | c6da2cfeb05178a11c6d062a06f8078150ee492f (patch) | |
tree | f3b4021d252c52d6463a9b3c1bb7245e399b009c /drivers/net/usb | |
parent | c6d7c4dbff353eac7919342ae6b3299a378160a6 (diff) | |
download | kernel_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/Kconfig | 10 | ||||
-rw-r--r-- | drivers/net/usb/Makefile | 3 | ||||
-rw-r--r-- | drivers/net/usb/rmnet_usb_ctrl.c | 1060 | ||||
-rw-r--r-- | drivers/net/usb/rmnet_usb_ctrl.h | 82 | ||||
-rw-r--r-- | drivers/net/usb/rmnet_usb_data.c | 595 | ||||
-rw-r--r-- | drivers/net/usb/usbnet.c | 10 |
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 { |