/* Copyright (c) 2011-2013, The Linux Foundation. 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 /* 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) enum ctrl_bridge_rx_state { RX_IDLE, /* inturb is not queued */ RX_WAIT, /* inturb is queued and waiting for data */ RX_BUSY, /* inturb is completed. processing RX */ }; struct ctrl_bridge { struct usb_device *udev; struct usb_interface *intf; char *name; 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; spinlock_t lock; enum ctrl_bridge_rx_state rx_state; /* 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]; static int get_ctrl_bridge_chid(char *xport_name) { struct ctrl_bridge *dev; int i; for (i = 0; i < MAX_BRIDGE_DEVICES; i++) { dev = __dev[i]; if (!strncmp(dev->name, xport_name, BRIDGE_NAME_MAX_LEN)) return i; } return -ENODEV; } 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 int ctrl_bridge_start_read(struct ctrl_bridge *dev, gfp_t gfp_flags) { int retval = 0; unsigned long flags; if (!dev->inturb) { dev_err(&dev->intf->dev, "%s: inturb is NULL\n", __func__); return -ENODEV; } retval = usb_submit_urb(dev->inturb, gfp_flags); if (retval < 0 && retval != -EPERM) { dev_err(&dev->intf->dev, "%s error submitting int urb %d\n", __func__, retval); } spin_lock_irqsave(&dev->lock, flags); if (retval) dev->rx_state = RX_IDLE; else dev->rx_state = RX_WAIT; spin_unlock_irqrestore(&dev->lock, flags); return retval; } static void resp_avail_cb(struct urb *urb) { struct ctrl_bridge *dev = urb->context; int resubmit_urb = 1; struct bridge *brdg = dev->brdg; unsigned long flags; /*usb device disconnect*/ if (urb->dev->state == USB_STATE_NOTATTACHED) return; 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(&dev->intf->dev, "%s: non zero urb status = %d\n", __func__, urb->status); } if (resubmit_urb) { /*re- submit int urb to check response available*/ ctrl_bridge_start_read(dev, GFP_ATOMIC); } else { spin_lock_irqsave(&dev->lock, flags); dev->rx_state = RX_IDLE; spin_unlock_irqrestore(&dev->lock, flags); } usb_autopm_put_interface_async(dev->intf); } static void notification_available_cb(struct urb *urb) { int status; struct usb_cdc_notification *ctrl; struct ctrl_bridge *dev = urb->context; struct bridge *brdg = dev->brdg; unsigned int ctrl_bits; unsigned char *data; unsigned long flags; /*usb device disconnect*/ if (urb->dev->state == USB_STATE_NOTATTACHED) return; spin_lock_irqsave(&dev->lock, flags); dev->rx_state = RX_IDLE; spin_unlock_irqrestore(&dev->lock, flags); switch (urb->status) { case 0: /*success*/ break; case -ESHUTDOWN: case -ENOENT: case -ECONNRESET: case -EPROTO: /* unplug */ return; case -EPIPE: dev_err(&dev->intf->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: spin_lock_irqsave(&dev->lock, flags); dev->rx_state = RX_BUSY; spin_unlock_irqrestore(&dev->lock, flags); dev->resp_avail++; usb_autopm_get_interface_no_resume(dev->intf); usb_fill_control_urb(dev->readurb, dev->udev, usb_rcvctrlpipe(dev->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(&dev->intf->dev, "%s: Error submitting Read URB %d\n", __func__, status); usb_autopm_put_interface_async(dev->intf); goto resubmit_int_urb; } return; case USB_CDC_NOTIFY_NETWORK_CONNECTION: dev_dbg(&dev->intf->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(&dev->intf->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(&dev->intf->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: ctrl_bridge_start_read(dev, GFP_ATOMIC); } int ctrl_bridge_open(struct bridge *brdg) { struct ctrl_bridge *dev; int ch_id; if (!brdg) { err("bridge is null\n"); return -EINVAL; } ch_id = get_ctrl_bridge_chid(brdg->name); if (ch_id < 0 || ch_id >= MAX_BRIDGE_DEVICES) { err("%s: %s dev not found\n", __func__, brdg->name); return ch_id; } brdg->ch_id = ch_id; dev = __dev[ch_id]; 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; if (brdg->ops.send_cbits) brdg->ops.send_cbits(brdg->ctx, dev->cbits_tohost); return 0; } 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->intf->dev, "%s:\n", __func__); ctrl_bridge_set_cbits(dev->brdg->ch_id, 0); 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); /* if we are here after device disconnect * usb_unbind_interface() takes care of * residual pm_autopm_get_interface_* calls */ if (urb->dev->state != USB_STATE_NOTATTACHED) 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 ctrl_bridge *dev; unsigned long flags; if (id >= MAX_BRIDGE_DEVICES) { result = -EINVAL; goto free_data; } dev = __dev[id]; if (!dev) { result = -ENODEV; goto free_data; } dev_dbg(&dev->intf->dev, "%s:[id]:%u: write (%d bytes)\n", __func__, id, size); writeurb = usb_alloc_urb(0, GFP_ATOMIC); if (!writeurb) { dev_err(&dev->intf->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(&dev->intf->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, dev->udev, usb_sndctrlpipe(dev->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_dbg(&dev->intf->dev, "%s: unable to resume interface: %d\n", __func__, result); /* * Revisit: if (result == -EPERM) * bridge_suspend(dev->intf, PMSG_SUSPEND); */ goto free_ctrlreq; } spin_lock_irqsave(&dev->lock, flags); if (test_bit(SUSPENDED, &dev->flags)) { usb_anchor_urb(writeurb, &dev->tx_deferred); spin_unlock_irqrestore(&dev->lock, flags); goto deferred; } usb_anchor_urb(writeurb, &dev->tx_submitted); spin_unlock_irqrestore(&dev->lock, flags); result = usb_submit_urb(writeurb, GFP_ATOMIC); if (result < 0) { dev_err(&dev->intf->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; unsigned long flags; if (id >= MAX_BRIDGE_DEVICES) return -EINVAL; dev = __dev[id]; if (!dev) return -ENODEV; spin_lock_irqsave(&dev->lock, flags); if (!usb_anchor_empty(&dev->tx_submitted) || dev->rx_state == RX_BUSY) { spin_unlock_irqrestore(&dev->lock, flags); return -EBUSY; } spin_unlock_irqrestore(&dev->lock, flags); usb_kill_urb(dev->inturb); spin_lock_irqsave(&dev->lock, flags); if (dev->rx_state != RX_IDLE) { spin_unlock_irqrestore(&dev->lock, flags); return -EBUSY; } if (!usb_anchor_empty(&dev->tx_submitted)) { spin_unlock_irqrestore(&dev->lock, flags); ctrl_bridge_start_read(dev, GFP_KERNEL); return -EBUSY; } set_bit(SUSPENDED, &dev->flags); spin_unlock_irqrestore(&dev->lock, flags); return 0; } int ctrl_bridge_resume(unsigned int id) { struct ctrl_bridge *dev; struct urb *urb; unsigned long flags; int ret; if (id >= MAX_BRIDGE_DEVICES) return -EINVAL; dev = __dev[id]; if (!dev) return -ENODEV; if (!test_bit(SUSPENDED, &dev->flags)) return 0; spin_lock_irqsave(&dev->lock, flags); /* submit pending write requests */ while ((urb = usb_get_from_anchor(&dev->tx_deferred))) { spin_unlock_irqrestore(&dev->lock, flags); /* * usb_get_from_anchor() does not drop the * ref count incremented by the usb_anchro_urb() * called in Tx submission path. Let us do it. */ usb_put_urb(urb); 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); } spin_lock_irqsave(&dev->lock, flags); } clear_bit(SUSPENDED, &dev->flags); spin_unlock_irqrestore(&dev->lock, flags); return ctrl_bridge_start_read(dev, GFP_KERNEL); } #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 < MAX_BRIDGE_DEVICES; 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->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 < MAX_BRIDGE_DEVICES; 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, char *name, 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 = __dev[id]; if (!dev) { pr_err("%s:device not found\n", __func__); return -ENODEV; } dev->name = name; dev->pdev = platform_device_alloc(name, -1); if (!dev->pdev) { retval = -ENOMEM; dev_err(&ifc->dev, "%s: unable to allocate platform device\n", __func__); goto free_name; } dev->flags = 0; dev->udev = udev; dev->int_pipe = usb_rcvintpipe(udev, int_in->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); dev->intf = ifc; /*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(&ifc->dev, "%s: error allocating int urb\n", __func__); retval = -ENOMEM; goto pdev_put; } wMaxPacketSize = le16_to_cpu(ep->wMaxPacketSize); dev->intbuf = kmalloc(wMaxPacketSize, GFP_KERNEL); if (!dev->intbuf) { dev_err(&ifc->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(&ifc->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(&ifc->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(&ifc->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); retval = platform_device_add(dev->pdev); if (retval) { dev_err(&ifc->dev, "%s:fail to add pdev\n", __func__); goto free_ctrlreq; } retval = ctrl_bridge_start_read(dev, GFP_KERNEL); if (retval) { dev_err(&ifc->dev, "%s:fail to start reading\n", __func__); goto pdev_del; } return 0; pdev_del: platform_device_del(dev->pdev); free_ctrlreq: kfree(dev->in_ctlreq); 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_put: platform_device_put(dev->pdev); free_name: dev->name = "none"; return retval; } void ctrl_bridge_disconnect(unsigned int id) { struct ctrl_bridge *dev = __dev[id]; dev_dbg(&dev->intf->dev, "%s:\n", __func__); /*set device name to none to get correct channel id * at the time of bridge open */ dev->name = "none"; platform_device_unregister(dev->pdev); usb_scuttle_anchored_urbs(&dev->tx_deferred); usb_kill_anchored_urbs(&dev->tx_submitted); usb_kill_urb(dev->inturb); usb_kill_urb(dev->readurb); kfree(dev->in_ctlreq); kfree(dev->readbuf); kfree(dev->intbuf); usb_free_urb(dev->readurb); usb_free_urb(dev->inturb); } int ctrl_bridge_init(void) { struct ctrl_bridge *dev; int i; int retval = 0; for (i = 0; i < MAX_BRIDGE_DEVICES; i++) { dev = kzalloc(sizeof(*dev), GFP_KERNEL); if (!dev) { pr_err("%s: unable to allocate dev\n", __func__); retval = -ENOMEM; goto error; } /*transport name will be set during probe*/ dev->name = "none"; spin_lock_init(&dev->lock); init_usb_anchor(&dev->tx_submitted); init_usb_anchor(&dev->tx_deferred); __dev[i] = dev; } ctrl_bridge_debugfs_init(); return 0; error: while (--i >= 0) { kfree(__dev[i]); __dev[i] = NULL; } return retval; } void ctrl_bridge_exit(void) { int i; ctrl_bridge_debugfs_exit(); for (i = 0; i < MAX_BRIDGE_DEVICES; i++) { kfree(__dev[i]); __dev[i] = NULL; } }