blob: 92f7e866544df1c0b033a08b1e8adbd2da504ca6 [file] [log] [blame]
/* 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 <linux/kernel.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/uaccess.h>
#include <linux/usb.h>
#include <linux/usb/ch9.h>
#include <linux/usb/cdc.h>
#include <linux/debugfs.h>
#include <mach/ipc_bridge.h>
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");