/* 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include static const char const *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 #define SUSPENDED BIT(0) 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_anchor tx_deferred; struct usb_ctrlrequest *in_ctlreq; struct bridge *brdg; struct platform_device *pdev; unsigned long flags; /* 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*/ usb_anchor_urb(dev->inturb, &dev->tx_submitted); status = usb_submit_urb(dev->inturb, GFP_ATOMIC); if (status) { dev_err(&udev->dev, "%s: Error re-submitting Int URB %d\n", __func__, status); usb_unanchor_urb(dev->inturb); } } } 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); usb_anchor_urb(dev->readurb, &dev->tx_submitted); status = usb_submit_urb(dev->readurb, GFP_ATOMIC); if (status) { dev_err(&udev->dev, "%s: Error submitting Read URB %d\n", __func__, status); usb_unanchor_urb(dev->readurb); 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: usb_anchor_urb(urb, &dev->tx_submitted); status = usb_submit_urb(urb, GFP_ATOMIC); if (status) { dev_err(&udev->dev, "%s: Error re-submitting Int URB %d\n", __func__, status); usb_unanchor_urb(urb); } } int ctrl_bridge_start_read(struct ctrl_bridge *dev) { int retval = 0; if (!dev->inturb) { dev_err(&dev->udev->dev, "%s: inturb is NULL\n", __func__); return -ENODEV; } if (!dev->inturb->anchor) { usb_anchor_urb(dev->inturb, &dev->tx_submitted); retval = usb_submit_urb(dev->inturb, GFP_KERNEL); if (retval < 0) { dev_err(&dev->udev->dev, "%s error submitting int urb %d\n", __func__, retval); usb_unanchor_urb(dev->inturb); } } return retval; } int ctrl_bridge_open(struct bridge *brdg) { struct ctrl_bridge *dev; int ret; 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; ret = usb_autopm_get_interface(dev->intf); if (ret < 0) { dev_err(&dev->udev->dev, "%s autopm_get fail: %d\n", __func__, ret); return ret; } ret = ctrl_bridge_start_read(dev); usb_autopm_put_interface(dev->intf); return ret; } 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); dev->brdg = NULL; } EXPORT_SYMBOL(ctrl_bridge_close); static void ctrl_write_callback(struct urb *urb) { struct ctrl_bridge *dev = urb->context; 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); usb_autopm_put_interface_async(dev->intf); } 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, dev); 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; } if (test_bit(SUSPENDED, &dev->flags)) { usb_anchor_urb(writeurb, &dev->tx_deferred); goto deferred; } 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; } deferred: 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; set_bit(SUSPENDED, &dev->flags); usb_kill_anchored_urbs(&dev->tx_submitted); return 0; } int ctrl_bridge_resume(unsigned int id) { struct ctrl_bridge *dev; struct urb *urb; if (id >= MAX_BRIDGE_DEVICES) return -EINVAL; dev = __dev[id]; if (!dev) return -ENODEV; if (!test_and_clear_bit(SUSPENDED, &dev->flags)) return 0; /* submit pending write requests */ while ((urb = usb_get_from_anchor(&dev->tx_deferred))) { int ret; usb_anchor_urb(urb, &dev->tx_submitted); ret = usb_submit_urb(urb, GFP_ATOMIC); if (ret < 0) { usb_unanchor_urb(urb); kfree(urb->setup_packet); kfree(urb->transfer_buffer); usb_free_urb(urb); usb_autopm_put_interface_async(dev->intf); } } /* if the bridge is open, resume reading */ if (dev->brdg) return ctrl_bridge_start_read(dev); return 0; } #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" "suspended: %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, test_bit(SUSPENDED, &dev->flags)); } 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); init_usb_anchor(&dev->tx_deferred); /*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__); platform_device_del(dev->pdev); kfree(dev->in_ctlreq); kfree(dev->readbuf); kfree(dev->intbuf); usb_free_urb(dev->readurb); usb_free_urb(dev->inturb); __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");