707 lines
16 KiB
C
707 lines
16 KiB
C
/* 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");
|