/* Copyright (c) 2011-2014, 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 enum ipc_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 ctl_pkt { u32 len; void *buf; struct list_head list; }; struct ipc_bridge { struct usb_device *udev; struct usb_interface *intf; struct urb *inturb; struct urb *readurb; struct urb *writeurb; struct usb_ctrlrequest *in_ctlreq; struct usb_ctrlrequest *out_ctlreq; void *readbuf; void *intbuf; spinlock_t lock; struct list_head rx_list; enum ipc_bridge_rx_state rx_state; struct platform_device *pdev; struct mutex open_mutex; struct mutex read_mutex; struct mutex write_mutex; bool opened; struct completion write_done; int write_result; wait_queue_head_t read_wait_q; unsigned int snd_encap_cmd; unsigned int get_encap_resp; unsigned int susp_fail_cnt; }; #define IPC_BRIDGE_MAX_READ_SZ (8 * 1024) #define IPC_BRIDGE_MAX_WRITE_SZ (8 * 1024) static struct ipc_bridge *__ipc_bridge_dev; static int ipc_bridge_submit_inturb(struct ipc_bridge *dev, gfp_t gfp_flags) { int ret; unsigned long flags; ret = usb_submit_urb(dev->inturb, gfp_flags); if (ret < 0 && ret != -EPERM) dev_err(&dev->intf->dev, "int urb submit err %d\n", ret); spin_lock_irqsave(&dev->lock, flags); if (ret) dev->rx_state = RX_IDLE; else dev->rx_state = RX_WAIT; spin_unlock_irqrestore(&dev->lock, flags); return ret; } static void ipc_bridge_write_cb(struct urb *urb) { struct ipc_bridge *dev = urb->context; usb_autopm_put_interface_async(dev->intf); if (urb->dev->state == USB_STATE_NOTATTACHED) dev->write_result = -ENODEV; else if (urb->status < 0) dev->write_result = urb->status; else dev->write_result = urb->actual_length; complete(&dev->write_done); } static void ipc_bridge_read_cb(struct urb *urb) { struct ipc_bridge *dev = urb->context; bool resubmit = true; struct ctl_pkt *pkt; unsigned long flags; usb_autopm_put_interface_async(dev->intf); if (urb->dev->state == USB_STATE_NOTATTACHED) { wake_up(&dev->read_wait_q); return; } switch (urb->status) { case 0: break; case -ENOENT: case -ESHUTDOWN: case -ECONNRESET: case -EPROTO: case -EPIPE: resubmit = false; goto done; case -EOVERFLOW: default: goto done; } if (!urb->actual_length) goto done; pkt = kmalloc(sizeof(*pkt), GFP_ATOMIC); if (!pkt) { dev_err(&dev->intf->dev, "fail to allocate pkt\n"); resubmit = false; goto done; } pkt->len = urb->actual_length; pkt->buf = kmalloc(pkt->len, GFP_ATOMIC); if (!pkt->buf) { kfree(pkt); dev_err(&dev->intf->dev, "fail to allocate pkt buffer\n"); resubmit = false; goto done; } memcpy(pkt->buf, urb->transfer_buffer, pkt->len); spin_lock_irqsave(&dev->lock, flags); list_add_tail(&pkt->list, &dev->rx_list); spin_unlock_irqrestore(&dev->lock, flags); wake_up(&dev->read_wait_q); done: if (resubmit) { ipc_bridge_submit_inturb(dev, GFP_ATOMIC); } else { spin_lock_irqsave(&dev->lock, flags); dev->rx_state = RX_IDLE; spin_unlock_irqrestore(&dev->lock, flags); } } static void ipc_bridge_int_cb(struct urb *urb) { struct ipc_bridge *dev = urb->context; struct usb_cdc_notification *ctl; int status; unsigned long flags; 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: case -ENOENT: break; case -ESHUTDOWN: case -ECONNRESET: case -EPROTO: case -EPIPE: return; case -EOVERFLOW: default: ipc_bridge_submit_inturb(dev, GFP_ATOMIC); return; } if (!urb->actual_length) return; ctl = urb->transfer_buffer; switch (ctl->bNotificationType) { case USB_CDC_NOTIFY_RESPONSE_AVAILABLE: spin_lock_irqsave(&dev->lock, flags); dev->rx_state = RX_BUSY; spin_unlock_irqrestore(&dev->lock, flags); usb_fill_control_urb(dev->readurb, dev->udev, usb_rcvctrlpipe(dev->udev, 0), (unsigned char *)dev->in_ctlreq, dev->readbuf, IPC_BRIDGE_MAX_READ_SZ, ipc_bridge_read_cb, dev); status = usb_submit_urb(dev->readurb, GFP_ATOMIC); if (status) { dev_err(&dev->intf->dev, "read urb submit err %d\n", status); goto resubmit_int_urb; } dev->get_encap_resp++; /* Tell runtime pm core that we are busy */ usb_autopm_get_interface_async(dev->intf); return; default: dev_err(&dev->intf->dev, "unknown data on int ep\n"); } resubmit_int_urb: ipc_bridge_submit_inturb(dev, GFP_ATOMIC); } static int ipc_bridge_open(struct platform_device *pdev) { struct ipc_bridge *dev = __ipc_bridge_dev; if (dev->pdev != pdev) return -EINVAL; mutex_lock(&dev->open_mutex); if (dev->opened) { mutex_unlock(&dev->open_mutex); dev_dbg(&dev->intf->dev, "bridge already opened\n"); return -EBUSY; } dev->opened = true; mutex_unlock(&dev->open_mutex); return 0; } static int ipc_bridge_rx_list_empty(struct ipc_bridge *dev) { int ret; unsigned long flags; spin_lock_irqsave(&dev->lock, flags); ret = list_empty(&dev->rx_list); spin_unlock_irqrestore(&dev->lock, flags); return ret; } static int ipc_bridge_read(struct platform_device *pdev, char *buf, unsigned int count) { struct ipc_bridge *dev = __ipc_bridge_dev; struct ctl_pkt *pkt; int ret; unsigned long flags; if (dev->pdev != pdev) return -EINVAL; if (!dev->opened) return -EPERM; if (count > IPC_BRIDGE_MAX_READ_SZ) return -ENOSPC; mutex_lock(&dev->read_mutex); wait_event(dev->read_wait_q, (!ipc_bridge_rx_list_empty(dev) || (dev->udev->state == USB_STATE_NOTATTACHED))); if (dev->udev->state == USB_STATE_NOTATTACHED) { ret = -ENODEV; goto done; } spin_lock_irqsave(&dev->lock, flags); pkt = list_first_entry(&dev->rx_list, struct ctl_pkt, list); if (pkt->len > count) { spin_unlock_irqrestore(&dev->lock, flags); dev_err(&dev->intf->dev, "large RX packet\n"); ret = -ENOSPC; goto done; } list_del(&pkt->list); spin_unlock_irqrestore(&dev->lock, flags); memcpy(buf, pkt->buf, pkt->len); ret = pkt->len; kfree(pkt->buf); kfree(pkt); done: mutex_unlock(&dev->read_mutex); return ret; } static int ipc_bridge_write(struct platform_device *pdev, char *buf, unsigned int count) { struct ipc_bridge *dev = __ipc_bridge_dev; int ret; if (dev->pdev != pdev) return -EINVAL; if (!dev->opened) return -EPERM; if (count > IPC_BRIDGE_MAX_WRITE_SZ) return -EINVAL; mutex_lock(&dev->write_mutex); dev->out_ctlreq->wLength = cpu_to_le16(count); usb_fill_control_urb(dev->writeurb, dev->udev, usb_sndctrlpipe(dev->udev, 0), (unsigned char *)dev->out_ctlreq, (void *)buf, count, ipc_bridge_write_cb, dev); ret = usb_autopm_get_interface(dev->intf); if (ret < 0) { dev_err(&dev->intf->dev, "write auto pm fail %d\n", ret); goto done; } ret = usb_submit_urb(dev->writeurb, GFP_KERNEL); if (ret < 0) { dev_err(&dev->intf->dev, "write urb submit err %d\n", ret); usb_autopm_put_interface_async(dev->intf); goto done; } dev->snd_encap_cmd++; wait_for_completion(&dev->write_done); ret = dev->write_result; done: mutex_unlock(&dev->write_mutex); return ret; } static void ipc_bridge_close(struct platform_device *pdev) { struct ipc_bridge *dev = __ipc_bridge_dev; WARN_ON(dev->pdev != pdev); WARN_ON(dev->udev->state != USB_STATE_NOTATTACHED); mutex_lock(&dev->open_mutex); if (!dev->opened) { mutex_unlock(&dev->open_mutex); dev_dbg(&dev->intf->dev, "bridge not opened\n"); return; } dev->opened = false; mutex_unlock(&dev->open_mutex); } static const struct ipc_bridge_platform_data ipc_bridge_pdata = { .max_read_size = IPC_BRIDGE_MAX_READ_SZ, .max_write_size = IPC_BRIDGE_MAX_WRITE_SZ, .open = ipc_bridge_open, .read = ipc_bridge_read, .write = ipc_bridge_write, .close = ipc_bridge_close, }; static int ipc_bridge_suspend(struct usb_interface *intf, pm_message_t message) { struct ipc_bridge *dev = usb_get_intfdata(intf); unsigned long flags; int ret = 0; spin_lock_irqsave(&dev->lock, flags); if (dev->rx_state == RX_BUSY) { dev->susp_fail_cnt++; ret = -EBUSY; goto done; } spin_unlock_irqrestore(&dev->lock, flags); usb_kill_urb(dev->inturb); spin_lock_irqsave(&dev->lock, flags); if (dev->rx_state != RX_IDLE) { dev->susp_fail_cnt++; ret = -EBUSY; goto done; } done: spin_unlock_irqrestore(&dev->lock, flags); return ret; } static int ipc_bridge_resume(struct usb_interface *intf) { struct ipc_bridge *dev = usb_get_intfdata(intf); return ipc_bridge_submit_inturb(dev, GFP_KERNEL); } #define DEBUG_BUF_SIZE 512 static ssize_t ipc_bridge_read_stats(struct file *file, char __user *ubuf, size_t count, loff_t *ppos) { struct ipc_bridge *dev = __ipc_bridge_dev; char *buf; int ret; buf = kzalloc(DEBUG_BUF_SIZE, GFP_KERNEL); if (!buf) return -ENOMEM; ret = scnprintf(buf, DEBUG_BUF_SIZE, "ch opened: %d\n" "encap cmd sent: %u\n" "encap resp recvd: %u\n" "suspend fail cnt: %u\n", dev->opened, dev->snd_encap_cmd, dev->get_encap_resp, dev->susp_fail_cnt ); ret = simple_read_from_buffer(ubuf, count, ppos, buf, ret); kfree(buf); return ret; } const struct file_operations ipc_stats_ops = { .read = ipc_bridge_read_stats, }; static struct dentry *dir; static void ipc_bridge_debugfs_init(void) { struct dentry *dfile; dir = debugfs_create_dir("ipc_bridge", 0); if (IS_ERR_OR_NULL(dir)) return; dfile = debugfs_create_file("status", 0444, dir, 0, &ipc_stats_ops); if (IS_ERR_OR_NULL(dfile)) debugfs_remove(dir); } static void ipc_bridge_debugfs_cleanup(void) { debugfs_remove_recursive(dir); } static int ipc_bridge_probe(struct usb_interface *intf, const struct usb_device_id *id) { struct ipc_bridge *dev; struct usb_device *udev = interface_to_usbdev(intf); struct usb_host_interface *intf_desc; struct usb_endpoint_descriptor *ep; u16 wMaxPacketSize; int ret; intf_desc = intf->cur_altsetting; if (intf_desc->desc.bNumEndpoints != 1 || !usb_endpoint_is_int_in( &intf_desc->endpoint[0].desc)) { dev_err(&intf->dev, "driver expects only 1 int ep\n"); return -ENODEV; } dev = kzalloc(sizeof(*dev), GFP_KERNEL); if (!dev) { dev_err(&intf->dev, "fail to allocate dev\n"); return -ENOMEM; } __ipc_bridge_dev = dev; dev->inturb = usb_alloc_urb(0, GFP_KERNEL); if (!dev->inturb) { dev_err(&intf->dev, "fail to allocate int urb\n"); ret = -ENOMEM; goto free_dev; } ep = &intf->cur_altsetting->endpoint[0].desc; wMaxPacketSize = le16_to_cpu(ep->wMaxPacketSize); dev->intbuf = kmalloc(wMaxPacketSize, GFP_KERNEL); if (!dev->intbuf) { dev_err(&intf->dev, "%s: error allocating int buffer\n", __func__); ret = -ENOMEM; goto free_inturb; } usb_fill_int_urb(dev->inturb, udev, usb_rcvintpipe(udev, ep->bEndpointAddress), dev->intbuf, wMaxPacketSize, ipc_bridge_int_cb, dev, ep->bInterval); dev->in_ctlreq = kmalloc(sizeof(*dev->in_ctlreq), GFP_KERNEL); if (!dev->in_ctlreq) { dev_err(&intf->dev, "error allocating IN control req\n"); ret = -ENOMEM; goto free_intbuf; } 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 = intf->cur_altsetting->desc.bInterfaceNumber; dev->in_ctlreq->wLength = cpu_to_le16(IPC_BRIDGE_MAX_READ_SZ); dev->readurb = usb_alloc_urb(0, GFP_KERNEL); if (!dev->readurb) { dev_err(&intf->dev, "fail to allocate read urb\n"); ret = -ENOMEM; goto free_in_ctlreq; } dev->readbuf = kmalloc(IPC_BRIDGE_MAX_READ_SZ, GFP_KERNEL); if (!dev->readbuf) { dev_err(&intf->dev, "fail to allocate read buffer\n"); ret = -ENOMEM; goto free_readurb; } dev->out_ctlreq = kmalloc(sizeof(*dev->out_ctlreq), GFP_KERNEL); if (!dev->out_ctlreq) { dev_err(&intf->dev, "error allocating OUT control req\n"); ret = -ENOMEM; goto free_readbuf; } dev->out_ctlreq->bRequestType = (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE); dev->out_ctlreq->bRequest = USB_CDC_SEND_ENCAPSULATED_COMMAND; dev->out_ctlreq->wValue = 0; dev->out_ctlreq->wIndex = intf->cur_altsetting->desc.bInterfaceNumber; dev->writeurb = usb_alloc_urb(0, GFP_KERNEL); if (!dev->writeurb) { dev_err(&intf->dev, "fail to allocate write urb\n"); ret = -ENOMEM; goto free_out_ctlreq; } dev->udev = usb_get_dev(interface_to_usbdev(intf)); dev->intf = intf; spin_lock_init(&dev->lock); init_completion(&dev->write_done); init_waitqueue_head(&dev->read_wait_q); INIT_LIST_HEAD(&dev->rx_list); mutex_init(&dev->open_mutex); mutex_init(&dev->read_mutex); mutex_init(&dev->write_mutex); usb_set_intfdata(intf, dev); usb_enable_autosuspend(udev); dev->pdev = platform_device_alloc("ipc_bridge", -1); if (!dev->pdev) { dev_err(&intf->dev, "fail to allocate pdev\n"); ret = -ENOMEM; goto destroy_mutex; } ret = platform_device_add_data(dev->pdev, &ipc_bridge_pdata, sizeof(struct ipc_bridge_platform_data)); if (ret) { dev_err(&intf->dev, "fail to add pdata\n"); goto put_pdev; } ret = platform_device_add(dev->pdev); if (ret) { dev_err(&intf->dev, "fail to add pdev\n"); goto put_pdev; } ret = ipc_bridge_submit_inturb(dev, GFP_KERNEL); if (ret) { dev_err(&intf->dev, "fail to start reading\n"); goto del_pdev; } ipc_bridge_debugfs_init(); return 0; del_pdev: platform_device_del(dev->pdev); put_pdev: platform_device_put(dev->pdev); destroy_mutex: usb_disable_autosuspend(udev); mutex_destroy(&dev->write_mutex); mutex_destroy(&dev->read_mutex); mutex_destroy(&dev->open_mutex); usb_put_dev(dev->udev); usb_free_urb(dev->writeurb); free_out_ctlreq: kfree(dev->out_ctlreq); free_readbuf: kfree(dev->readbuf); free_readurb: usb_free_urb(dev->readurb); free_in_ctlreq: kfree(dev->in_ctlreq); free_intbuf: kfree(dev->intbuf); free_inturb: usb_free_urb(dev->inturb); free_dev: kfree(dev); __ipc_bridge_dev = NULL; return ret; } static void ipc_bridge_disconnect(struct usb_interface *intf) { struct ipc_bridge *dev = usb_get_intfdata(intf); struct ctl_pkt *pkt; unsigned long flags; ipc_bridge_debugfs_cleanup(); usb_kill_urb(dev->writeurb); usb_kill_urb(dev->inturb); usb_kill_urb(dev->readurb); /* * The readurb may not be active at the time of * unlink. Wake up the reader explicitly before * unregistering the platform device. */ wake_up(&dev->read_wait_q); platform_device_unregister(dev->pdev); WARN_ON(dev->opened); spin_lock_irqsave(&dev->lock, flags); while (!list_empty(&dev->rx_list)) { pkt = list_first_entry(&dev->rx_list, struct ctl_pkt, list); list_del(&pkt->list); kfree(pkt->buf); kfree(pkt); } spin_unlock_irqrestore(&dev->lock, flags); mutex_destroy(&dev->open_mutex); mutex_destroy(&dev->read_mutex); mutex_destroy(&dev->write_mutex); usb_free_urb(dev->writeurb); kfree(dev->out_ctlreq); kfree(dev->readbuf); usb_free_urb(dev->readurb); kfree(dev->in_ctlreq); kfree(dev->intbuf); usb_free_urb(dev->inturb); usb_put_dev(dev->udev); kfree(dev); __ipc_bridge_dev = NULL; } static const struct usb_device_id ipc_bridge_ids[] = { { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x908A, 7) }, { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x908E, 9) }, { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x909D, 5) }, { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x909E, 7) }, { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x90A0, 7) }, { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x90A4, 9) }, {} /* terminating entry */ }; MODULE_DEVICE_TABLE(usb, ipc_bridge_ids); static struct usb_driver ipc_bridge_driver = { .name = "ipc_bridge", .probe = ipc_bridge_probe, .disconnect = ipc_bridge_disconnect, .suspend = ipc_bridge_suspend, .resume = ipc_bridge_resume, .reset_resume = ipc_bridge_resume, .id_table = ipc_bridge_ids, .supports_autosuspend = 1, }; module_usb_driver(ipc_bridge_driver); MODULE_DESCRIPTION("USB IPC bridge driver"); MODULE_LICENSE("GPL v2");