usb: Add network bridge host driver for dun and rmnet
This host driver will be used to communicate with modem devices
with dial up network and RMNET interfaces. This driver works as
a bridge to pass control and data packets between the modem and
peripheral usb gadget driver. Driver currently supports
modem devices (vendor ID 0x05c6) with PIDs 0x9001
Change-Id: Id85b552b39d061528a1c3c90a354d73580c9b631
Signed-off-by: Hemant Kumar <hemantk@codeaurora.org>
Signed-off-by: Jack Pham <jackp@codeaurora.org>
diff --git a/drivers/usb/misc/mdm_ctrl_bridge.c b/drivers/usb/misc/mdm_ctrl_bridge.c
new file mode 100644
index 0000000..87adf2e
--- /dev/null
+++ b/drivers/usb/misc/mdm_ctrl_bridge.c
@@ -0,0 +1,729 @@
+/* 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/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kref.h>
+#include <linux/debugfs.h>
+#include <linux/platform_device.h>
+#include <linux/uaccess.h>
+#include <linux/ratelimit.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/cdc.h>
+#include <linux/termios.h>
+#include <asm/unaligned.h>
+#include <mach/usb_bridge.h>
+
+static const char *ctrl_bridge_names[] = {
+ "dun_ctrl_hsic0",
+ "rmnet_ctrl_hsic0"
+};
+
+/* polling interval for Interrupt ep */
+#define HS_INTERVAL 7
+#define FS_LS_INTERVAL 3
+
+#define ACM_CTRL_DTR (1 << 0)
+#define DEFAULT_READ_URB_LENGTH 4096
+
+struct ctrl_bridge {
+
+ struct usb_device *udev;
+ struct usb_interface *intf;
+
+ unsigned int int_pipe;
+ struct urb *inturb;
+ void *intbuf;
+
+ struct urb *readurb;
+ void *readbuf;
+
+ struct usb_anchor tx_submitted;
+ struct usb_ctrlrequest *in_ctlreq;
+
+ struct bridge *brdg;
+ struct platform_device *pdev;
+
+ /* input control lines (DSR, CTS, CD, RI) */
+ unsigned int cbits_tohost;
+
+ /* output control lines (DTR, RTS) */
+ unsigned int cbits_tomdm;
+
+ /* counters */
+ unsigned int snd_encap_cmd;
+ unsigned int get_encap_res;
+ unsigned int resp_avail;
+ unsigned int set_ctrl_line_sts;
+ unsigned int notify_ser_state;
+
+};
+
+static struct ctrl_bridge *__dev[MAX_BRIDGE_DEVICES];
+
+/* counter used for indexing ctrl bridge devices */
+static int ch_id;
+
+unsigned int ctrl_bridge_get_cbits_tohost(unsigned int id)
+{
+ struct ctrl_bridge *dev;
+
+ if (id >= MAX_BRIDGE_DEVICES)
+ return -EINVAL;
+
+ dev = __dev[id];
+ if (!dev)
+ return -ENODEV;
+
+ return dev->cbits_tohost;
+}
+EXPORT_SYMBOL(ctrl_bridge_get_cbits_tohost);
+
+int ctrl_bridge_set_cbits(unsigned int id, unsigned int cbits)
+{
+ struct ctrl_bridge *dev;
+ struct bridge *brdg;
+ int retval;
+
+ if (id >= MAX_BRIDGE_DEVICES)
+ return -EINVAL;
+
+ dev = __dev[id];
+ if (!dev)
+ return -ENODEV;
+
+ pr_debug("%s: dev[id] =%u cbits : %u\n", __func__, id, cbits);
+
+ brdg = dev->brdg;
+ if (!brdg)
+ return -ENODEV;
+
+ dev->cbits_tomdm = cbits;
+
+ retval = ctrl_bridge_write(id, NULL, 0);
+
+ /* if DTR is high, update latest modem info to host */
+ if (brdg && (cbits & ACM_CTRL_DTR) && brdg->ops.send_cbits)
+ brdg->ops.send_cbits(brdg->ctx, dev->cbits_tohost);
+
+ return retval;
+}
+EXPORT_SYMBOL(ctrl_bridge_set_cbits);
+
+static void resp_avail_cb(struct urb *urb)
+{
+ struct ctrl_bridge *dev = urb->context;
+ struct usb_device *udev;
+ int status = 0;
+ int resubmit_urb = 1;
+ struct bridge *brdg = dev->brdg;
+
+ udev = interface_to_usbdev(dev->intf);
+ switch (urb->status) {
+ case 0:
+ /*success*/
+ dev->get_encap_res++;
+ if (brdg && brdg->ops.send_pkt)
+ brdg->ops.send_pkt(brdg->ctx, urb->transfer_buffer,
+ urb->actual_length);
+ break;
+
+ /*do not resubmit*/
+ case -ESHUTDOWN:
+ case -ENOENT:
+ case -ECONNRESET:
+ /* unplug */
+ case -EPROTO:
+ /*babble error*/
+ resubmit_urb = 0;
+ /*resubmit*/
+ case -EOVERFLOW:
+ default:
+ dev_dbg(&udev->dev, "%s: non zero urb status = %d\n",
+ __func__, urb->status);
+ }
+
+ if (resubmit_urb) {
+ /*re- submit int urb to check response available*/
+ status = usb_submit_urb(dev->inturb, GFP_ATOMIC);
+ if (status)
+ dev_err(&udev->dev,
+ "%s: Error re-submitting Int URB %d\n",
+ __func__, status);
+ }
+}
+
+static void notification_available_cb(struct urb *urb)
+{
+ int status;
+ struct usb_cdc_notification *ctrl;
+ struct usb_device *udev;
+ struct ctrl_bridge *dev = urb->context;
+ struct bridge *brdg = dev->brdg;
+ unsigned int ctrl_bits;
+ unsigned char *data;
+
+ udev = interface_to_usbdev(dev->intf);
+
+ switch (urb->status) {
+ case 0:
+ /*success*/
+ break;
+ case -ESHUTDOWN:
+ case -ENOENT:
+ case -ECONNRESET:
+ case -EPROTO:
+ /* unplug */
+ return;
+ case -EPIPE:
+ dev_err(&udev->dev, "%s: stall on int endpoint\n", __func__);
+ /* TBD : halt to be cleared in work */
+ case -EOVERFLOW:
+ default:
+ pr_debug_ratelimited("%s: non zero urb status = %d\n",
+ __func__, urb->status);
+ goto resubmit_int_urb;
+ }
+
+ ctrl = (struct usb_cdc_notification *)urb->transfer_buffer;
+ data = (unsigned char *)(ctrl + 1);
+
+ switch (ctrl->bNotificationType) {
+ case USB_CDC_NOTIFY_RESPONSE_AVAILABLE:
+ dev->resp_avail++;
+ usb_fill_control_urb(dev->readurb, udev,
+ usb_rcvctrlpipe(udev, 0),
+ (unsigned char *)dev->in_ctlreq,
+ dev->readbuf,
+ DEFAULT_READ_URB_LENGTH,
+ resp_avail_cb, dev);
+
+ status = usb_submit_urb(dev->readurb, GFP_ATOMIC);
+ if (status) {
+ dev_err(&udev->dev,
+ "%s: Error submitting Read URB %d\n",
+ __func__, status);
+ goto resubmit_int_urb;
+ }
+ return;
+ case USB_CDC_NOTIFY_NETWORK_CONNECTION:
+ dev_dbg(&udev->dev, "%s network\n", ctrl->wValue ?
+ "connected to" : "disconnected from");
+ break;
+ case USB_CDC_NOTIFY_SERIAL_STATE:
+ dev->notify_ser_state++;
+ ctrl_bits = get_unaligned_le16(data);
+ dev_dbg(&udev->dev, "serial state: %d\n", ctrl_bits);
+ dev->cbits_tohost = ctrl_bits;
+ if (brdg && brdg->ops.send_cbits)
+ brdg->ops.send_cbits(brdg->ctx, ctrl_bits);
+ break;
+ default:
+ dev_err(&udev->dev, "%s: unknown notification %d received:"
+ "index %d len %d data0 %d data1 %d",
+ __func__, ctrl->bNotificationType, ctrl->wIndex,
+ ctrl->wLength, data[0], data[1]);
+ }
+
+resubmit_int_urb:
+ status = usb_submit_urb(urb, GFP_ATOMIC);
+ if (status)
+ dev_err(&udev->dev, "%s: Error re-submitting Int URB %d\n",
+ __func__, status);
+}
+
+int ctrl_bridge_start_read(struct ctrl_bridge *dev)
+{
+ int retval = 0;
+ struct usb_device *udev;
+
+ udev = interface_to_usbdev(dev->intf);
+
+ retval = usb_autopm_get_interface_async(dev->intf);
+ if (retval < 0) {
+ dev_err(&udev->dev, "%s resumption fail\n", __func__);
+ goto done_nopm;
+ }
+
+ retval = usb_submit_urb(dev->inturb, GFP_KERNEL);
+ if (retval < 0)
+ dev_err(&udev->dev, "%s intr submit %d\n", __func__, retval);
+
+ usb_autopm_put_interface_async(dev->intf);
+done_nopm:
+ return retval;
+}
+
+static int ctrl_bridge_stop_read(struct ctrl_bridge *dev)
+{
+ if (dev->readurb) {
+ dev_dbg(&dev->udev->dev, "killing rcv urb\n");
+ usb_unlink_urb(dev->readurb);
+ }
+
+ if (dev->inturb) {
+ dev_dbg(&dev->udev->dev, "killing int urb\n");
+ usb_unlink_urb(dev->inturb);
+ }
+
+ return 0;
+}
+
+int ctrl_bridge_open(struct bridge *brdg)
+{
+ struct ctrl_bridge *dev;
+
+ if (!brdg) {
+ err("bridge is null\n");
+ return -EINVAL;
+ }
+
+ if (brdg->ch_id >= MAX_BRIDGE_DEVICES)
+ return -EINVAL;
+
+ dev = __dev[brdg->ch_id];
+ if (!dev) {
+ err("dev is null\n");
+ return -ENODEV;
+ }
+
+ dev->brdg = brdg;
+ dev->snd_encap_cmd = 0;
+ dev->get_encap_res = 0;
+ dev->resp_avail = 0;
+ dev->set_ctrl_line_sts = 0;
+ dev->notify_ser_state = 0;
+
+ return ctrl_bridge_start_read(dev);
+}
+EXPORT_SYMBOL(ctrl_bridge_open);
+
+void ctrl_bridge_close(unsigned int id)
+{
+ struct ctrl_bridge *dev;
+
+ if (id >= MAX_BRIDGE_DEVICES)
+ return;
+
+ dev = __dev[id];
+ if (!dev && !dev->brdg)
+ return;
+
+ dev_dbg(&dev->udev->dev, "%s:\n", __func__);
+
+ ctrl_bridge_set_cbits(dev->brdg->ch_id, 0);
+ usb_unlink_anchored_urbs(&dev->tx_submitted);
+ ctrl_bridge_stop_read(dev);
+
+ dev->brdg = NULL;
+}
+EXPORT_SYMBOL(ctrl_bridge_close);
+
+static void ctrl_write_callback(struct urb *urb)
+{
+
+ if (urb->status) {
+ pr_debug("Write status/size %d/%d\n",
+ urb->status, urb->actual_length);
+ }
+
+ kfree(urb->transfer_buffer);
+ kfree(urb->setup_packet);
+ usb_free_urb(urb);
+}
+
+int ctrl_bridge_write(unsigned int id, char *data, size_t size)
+{
+ int result;
+ struct urb *writeurb;
+ struct usb_ctrlrequest *out_ctlreq;
+ struct usb_device *udev;
+ struct ctrl_bridge *dev;
+
+ if (id >= MAX_BRIDGE_DEVICES) {
+ result = -EINVAL;
+ goto free_data;
+ }
+
+ dev = __dev[id];
+
+ if (!dev) {
+ result = -ENODEV;
+ goto free_data;
+ }
+
+ udev = interface_to_usbdev(dev->intf);
+
+ dev_dbg(&udev->dev, "%s:[id]:%u: write (%d bytes)\n",
+ __func__, id, size);
+
+ writeurb = usb_alloc_urb(0, GFP_ATOMIC);
+ if (!writeurb) {
+ dev_err(&udev->dev, "%s: error allocating read urb\n",
+ __func__);
+ result = -ENOMEM;
+ goto free_data;
+ }
+
+ out_ctlreq = kmalloc(sizeof(*out_ctlreq), GFP_ATOMIC);
+ if (!out_ctlreq) {
+ dev_err(&udev->dev,
+ "%s: error allocating setup packet buffer\n",
+ __func__);
+ result = -ENOMEM;
+ goto free_urb;
+ }
+
+ /* CDC Send Encapsulated Request packet */
+ out_ctlreq->bRequestType = (USB_DIR_OUT | USB_TYPE_CLASS |
+ USB_RECIP_INTERFACE);
+ if (!data && !size) {
+ out_ctlreq->bRequest = USB_CDC_REQ_SET_CONTROL_LINE_STATE;
+ out_ctlreq->wValue = dev->cbits_tomdm;
+ dev->set_ctrl_line_sts++;
+ } else {
+ out_ctlreq->bRequest = USB_CDC_SEND_ENCAPSULATED_COMMAND;
+ out_ctlreq->wValue = 0;
+ dev->snd_encap_cmd++;
+ }
+ out_ctlreq->wIndex =
+ dev->intf->cur_altsetting->desc.bInterfaceNumber;
+ out_ctlreq->wLength = cpu_to_le16(size);
+
+ usb_fill_control_urb(writeurb, udev,
+ usb_sndctrlpipe(udev, 0),
+ (unsigned char *)out_ctlreq,
+ (void *)data, size,
+ ctrl_write_callback, NULL);
+
+ result = usb_autopm_get_interface_async(dev->intf);
+ if (result < 0) {
+ dev_err(&udev->dev, "%s: unable to resume interface: %d\n",
+ __func__, result);
+
+ /*
+ * Revisit: if (result == -EPERM)
+ * bridge_suspend(dev->intf, PMSG_SUSPEND);
+ */
+
+ goto free_ctrlreq;
+ }
+
+ usb_anchor_urb(writeurb, &dev->tx_submitted);
+ result = usb_submit_urb(writeurb, GFP_ATOMIC);
+ if (result < 0) {
+ dev_err(&udev->dev, "%s: submit URB error %d\n",
+ __func__, result);
+ usb_autopm_put_interface_async(dev->intf);
+ goto unanchor_urb;
+ }
+
+ return size;
+
+unanchor_urb:
+ usb_unanchor_urb(writeurb);
+free_ctrlreq:
+ kfree(out_ctlreq);
+free_urb:
+ usb_free_urb(writeurb);
+free_data:
+ kfree(data);
+
+ return result;
+}
+EXPORT_SYMBOL(ctrl_bridge_write);
+
+int ctrl_bridge_suspend(unsigned int id)
+{
+ struct ctrl_bridge *dev;
+
+ if (id >= MAX_BRIDGE_DEVICES)
+ return -EINVAL;
+
+ dev = __dev[id];
+ if (!dev)
+ return -ENODEV;
+
+ usb_kill_anchored_urbs(&dev->tx_submitted);
+
+ return ctrl_bridge_stop_read(dev);
+}
+
+int ctrl_bridge_resume(unsigned int id)
+{
+ struct ctrl_bridge *dev;
+
+ if (id >= MAX_BRIDGE_DEVICES)
+ return -EINVAL;
+
+ dev = __dev[id];
+ if (!dev)
+ return -ENODEV;
+
+ return ctrl_bridge_start_read(dev);
+}
+
+#if defined(CONFIG_DEBUG_FS)
+#define DEBUG_BUF_SIZE 1024
+static ssize_t ctrl_bridge_read_stats(struct file *file, char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct ctrl_bridge *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 < ch_id; i++) {
+ dev = __dev[i];
+ if (!dev)
+ continue;
+
+ temp += scnprintf(buf + temp, DEBUG_BUF_SIZE - temp,
+ "\nName#%s dev %p\n"
+ "snd encap cmd cnt: %u\n"
+ "get encap res cnt: %u\n"
+ "res available cnt: %u\n"
+ "set ctrlline sts cnt: %u\n"
+ "notify ser state cnt: %u\n"
+ "cbits_tomdm: %d\n"
+ "cbits_tohost: %d\n",
+ dev->pdev->name, dev,
+ dev->snd_encap_cmd,
+ dev->get_encap_res,
+ dev->resp_avail,
+ dev->set_ctrl_line_sts,
+ dev->notify_ser_state,
+ dev->cbits_tomdm,
+ dev->cbits_tohost);
+
+ }
+
+ ret = simple_read_from_buffer(ubuf, count, ppos, buf, temp);
+
+ kfree(buf);
+
+ return ret;
+}
+
+static ssize_t ctrl_bridge_reset_stats(struct file *file,
+ const char __user *buf, size_t count, loff_t *ppos)
+{
+ struct ctrl_bridge *dev;
+ int i;
+
+ for (i = 0; i < ch_id; i++) {
+ dev = __dev[i];
+ if (!dev)
+ continue;
+
+ dev->snd_encap_cmd = 0;
+ dev->get_encap_res = 0;
+ dev->resp_avail = 0;
+ dev->set_ctrl_line_sts = 0;
+ dev->notify_ser_state = 0;
+ }
+ return count;
+}
+
+const struct file_operations ctrl_stats_ops = {
+ .read = ctrl_bridge_read_stats,
+ .write = ctrl_bridge_reset_stats,
+};
+
+struct dentry *ctrl_dent;
+struct dentry *ctrl_dfile;
+static void ctrl_bridge_debugfs_init(void)
+{
+ ctrl_dent = debugfs_create_dir("ctrl_hsic_bridge", 0);
+ if (IS_ERR(ctrl_dent))
+ return;
+
+ ctrl_dfile =
+ debugfs_create_file("status", 0644, ctrl_dent, 0,
+ &ctrl_stats_ops);
+ if (!ctrl_dfile || IS_ERR(ctrl_dfile))
+ debugfs_remove(ctrl_dent);
+}
+
+static void ctrl_bridge_debugfs_exit(void)
+{
+ debugfs_remove(ctrl_dfile);
+ debugfs_remove(ctrl_dent);
+}
+
+#else
+static void ctrl_bridge_debugfs_init(void) { }
+static void ctrl_bridge_debugfs_exit(void) { }
+#endif
+
+int
+ctrl_bridge_probe(struct usb_interface *ifc, struct usb_host_endpoint *int_in,
+ int id)
+{
+ struct ctrl_bridge *dev;
+ struct usb_device *udev;
+ struct usb_endpoint_descriptor *ep;
+ u16 wMaxPacketSize;
+ int retval = 0;
+ int interval;
+
+ udev = interface_to_usbdev(ifc);
+
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev) {
+ dev_err(&udev->dev, "%s: unable to allocate dev\n",
+ __func__);
+ return -ENOMEM;
+ }
+ dev->pdev = platform_device_alloc(ctrl_bridge_names[id], id);
+ if (!dev->pdev) {
+ dev_err(&dev->udev->dev,
+ "%s: unable to allocate platform device\n", __func__);
+ retval = -ENOMEM;
+ goto nomem;
+ }
+
+ dev->udev = udev;
+ dev->int_pipe = usb_rcvintpipe(udev,
+ int_in->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);
+ dev->intf = ifc;
+
+ init_usb_anchor(&dev->tx_submitted);
+
+ /*use max pkt size from ep desc*/
+ ep = &dev->intf->cur_altsetting->endpoint[0].desc;
+
+ dev->inturb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!dev->inturb) {
+ dev_err(&udev->dev, "%s: error allocating int urb\n", __func__);
+ retval = -ENOMEM;
+ goto pdev_del;
+ }
+
+ wMaxPacketSize = le16_to_cpu(ep->wMaxPacketSize);
+
+ dev->intbuf = kmalloc(wMaxPacketSize, GFP_KERNEL);
+ if (!dev->intbuf) {
+ dev_err(&udev->dev, "%s: error allocating int buffer\n",
+ __func__);
+ retval = -ENOMEM;
+ goto free_inturb;
+ }
+
+ interval =
+ (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);
+
+ dev->readurb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!dev->readurb) {
+ dev_err(&udev->dev, "%s: error allocating read urb\n",
+ __func__);
+ retval = -ENOMEM;
+ goto free_intbuf;
+ }
+
+ dev->readbuf = kmalloc(DEFAULT_READ_URB_LENGTH, GFP_KERNEL);
+ if (!dev->readbuf) {
+ dev_err(&udev->dev, "%s: error allocating read buffer\n",
+ __func__);
+ retval = -ENOMEM;
+ goto free_rurb;
+ }
+
+ dev->in_ctlreq = kmalloc(sizeof(*dev->in_ctlreq), GFP_KERNEL);
+ if (!dev->in_ctlreq) {
+ dev_err(&udev->dev,
+ "%s:error allocating setup packet buffer\n",
+ __func__);
+ retval = -ENOMEM;
+ goto free_rbuf;
+ }
+
+ 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);
+
+ __dev[id] = dev;
+
+ platform_device_add(dev->pdev);
+
+ ch_id++;
+
+ return retval;
+
+free_rbuf:
+ kfree(dev->readbuf);
+free_rurb:
+ usb_free_urb(dev->readurb);
+free_intbuf:
+ kfree(dev->intbuf);
+free_inturb:
+ usb_free_urb(dev->inturb);
+pdev_del:
+ platform_device_del(dev->pdev);
+nomem:
+ kfree(dev);
+
+ return retval;
+}
+
+void ctrl_bridge_disconnect(unsigned int id)
+{
+ struct ctrl_bridge *dev = __dev[id];
+
+ dev_dbg(&dev->udev->dev, "%s:\n", __func__);
+
+ kfree(dev->in_ctlreq);
+ kfree(dev->readbuf);
+ kfree(dev->intbuf);
+
+ usb_free_urb(dev->readurb);
+ usb_free_urb(dev->inturb);
+
+ platform_device_del(dev->pdev);
+ __dev[id] = NULL;
+ ch_id--;
+
+ kfree(dev);
+}
+
+static int __init ctrl_bridge_init(void)
+{
+ ctrl_bridge_debugfs_init();
+
+ return 0;
+}
+module_init(ctrl_bridge_init);
+
+static void __exit ctrl_bridge_exit(void)
+{
+ ctrl_bridge_debugfs_exit();
+}
+module_exit(ctrl_bridge_exit);
+
+MODULE_DESCRIPTION("Qualcomm modem control bridge driver");
+MODULE_LICENSE("GPL v2");