diff options
Diffstat (limited to 'drivers/usb/misc')
| -rw-r--r-- | drivers/usb/misc/Kconfig | 34 | ||||
| -rw-r--r-- | drivers/usb/misc/Makefile | 3 | ||||
| -rw-r--r-- | drivers/usb/misc/diag_ipc_bridge.c | 859 | ||||
| -rw-r--r-- | drivers/usb/misc/ehset.c | 304 | ||||
| -rw-r--r-- | drivers/usb/misc/ks_bridge.c | 1155 | ||||
| -rw-r--r-- | drivers/usb/misc/lvstest.c | 48 |
6 files changed, 2353 insertions, 50 deletions
diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig index c7383c41c90e..8006d75efc09 100644 --- a/drivers/usb/misc/Kconfig +++ b/drivers/usb/misc/Kconfig @@ -258,3 +258,37 @@ config USB_CHAOSKEY To compile this driver as a module, choose M here: the module will be called chaoskey. + +config USB_QTI_KS_BRIDGE + tristate "USB QTI kick start bridge" + depends on USB + help + Say Y here if you have a QTI modem device connected via USB that + will be bridged in kernel space. This driver works as a bridge to pass + boot images, ram-dumps and efs sync. + To compile this driver as a module, choose M here: the module + will be called ks_bridge. If unsure, choose N. + +config USB_QCOM_IPC_BRIDGE + tristate "USB QTI IPC bridge driver" + depends on USB + depends on USB_QCOM_DIAG_BRIDGE + help + Say Y here if you have a QTI modem device connected via USB that + will be bridged in kernel space. This driver works as a transport + layer for IPC router module that enables communication between + APPS processor and MODEM processor. This config depends on + USB_QCOM_DIAG_BRIDGE because the core USB support for the transports + of both diag and IPC messages is in the same driver. Select this + config manually if you want to compile HSIC transport IPC router. + +config USB_QCOM_DIAG_BRIDGE + tristate "USB QTI diagnostic bridge driver" + depends on USB + help + Say Y here if you have a QTI modem device connected via USB that + will be bridged in kernel space. This driver communicates with the + diagnostic interface and allows for bridging with the diag forwarding + driver. + To compile this driver as a module, choose M here: the + module will be called diag_bridge. If unsure, choose N. diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile index 0cbdd77363f2..4986df051c22 100644 --- a/drivers/usb/misc/Makefile +++ b/drivers/usb/misc/Makefile @@ -28,3 +28,6 @@ obj-$(CONFIG_USB_CHAOSKEY) += chaoskey.o obj-$(CONFIG_USB_SISUSBVGA) += sisusbvga/ obj-$(CONFIG_USB_LINK_LAYER_TEST) += lvstest.o + +obj-$(CONFIG_USB_QTI_KS_BRIDGE) += ks_bridge.o +obj-$(CONFIG_USB_QCOM_DIAG_BRIDGE) += diag_ipc_bridge.o diff --git a/drivers/usb/misc/diag_ipc_bridge.c b/drivers/usb/misc/diag_ipc_bridge.c new file mode 100644 index 000000000000..780746e8e630 --- /dev/null +++ b/drivers/usb/misc/diag_ipc_bridge.c @@ -0,0 +1,859 @@ +/* Copyright (c) 2011-2015, 2018-2019, 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/kref.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/ratelimit.h> +#include <linux/uaccess.h> +#include <linux/usb.h> +#include <linux/debugfs.h> +#include <linux/usb/diag_bridge.h> +#include <linux/usb/ipc_bridge.h> + +#define DRIVER_DESC "USB host diag bridge driver" +#define DRIVER_VERSION "1.0" + +enum { + DIAG_BRIDGE, + IPC_BRIDGE, + MAX_BRIDGE_DEVS, +}; + +#define AUTOSUSP_DELAY_WITH_USB 1000 + +#define IPC_BRIDGE_MAX_READ_SZ (8 * 1024) +#define IPC_BRIDGE_MAX_WRITE_SZ (8 * 1024) + +struct diag_bridge { + struct usb_device *udev; + struct usb_interface *ifc; + struct usb_anchor submitted; + __u8 in_epAddr; + __u8 out_epAddr; + int err; + struct kref kref; + struct mutex ifc_mutex; + struct mutex read_mutex; + struct mutex write_mutex; + bool opened; + struct completion read_done; + struct completion write_done; + int read_result; + int write_result; + struct diag_bridge_ops *ops; + struct platform_device *pdev; + unsigned default_autosusp_delay; + int id; + + /* Support INT IN instead of BULK IN */ + bool use_int_in_pipe; + unsigned int period; + + /* debugging counters */ + unsigned long bytes_to_host; + unsigned long bytes_to_mdm; + unsigned pending_reads; + unsigned pending_writes; + unsigned drop_count; +}; +struct diag_bridge *__dev[MAX_BRIDGE_DEVS]; + +int diag_bridge_open(int id, struct diag_bridge_ops *ops) +{ + struct diag_bridge *dev; + + if (id < 0 || id >= MAX_BRIDGE_DEVS) { + pr_err("%s: Invalid device ID\n", __func__); + return -ENODEV; + } + + dev = __dev[id]; + if (!dev) { + pr_err("%s: dev is null\n", __func__); + return -ENODEV; + } + + if (dev->ops) { + pr_err("%s: bridge already opened\n", __func__); + return -EALREADY; + } + + mutex_lock(&dev->ifc_mutex); + if (dev->opened) { + mutex_unlock(&dev->ifc_mutex); + pr_err("%s: Bridge already opened\n", __func__); + return -EBUSY; + } + + dev->opened = true; + mutex_unlock(&dev->ifc_mutex); + + dev_dbg(&dev->ifc->dev, "%s\n", __func__); + dev->ops = ops; + dev->err = 0; + +#ifdef CONFIG_PM_RUNTIME + dev->default_autosusp_delay = + dev->udev->dev.power.autosuspend_delay; +#endif + pm_runtime_set_autosuspend_delay(&dev->udev->dev, + AUTOSUSP_DELAY_WITH_USB); + + kref_get(&dev->kref); + + return 0; +} +EXPORT_SYMBOL(diag_bridge_open); + +static int ipc_bridge_open(struct platform_device *pdev) +{ + if (__dev[IPC_BRIDGE]->pdev != pdev) + return -EINVAL; + + return diag_bridge_open(IPC_BRIDGE, NULL); +} + +static void diag_bridge_delete(struct kref *kref) +{ + struct diag_bridge *dev = container_of(kref, struct diag_bridge, kref); + struct usb_interface *ifc = dev->ifc; + int id = dev->id; + + dev_dbg(&dev->ifc->dev, "%s\n", __func__); + usb_set_intfdata(ifc, NULL); + usb_put_intf(ifc); + usb_put_dev(dev->udev); + __dev[id] = 0; + kfree(dev); +} + +void diag_bridge_close(int id) +{ + struct diag_bridge *dev; + + if (id < 0 || id >= MAX_BRIDGE_DEVS) { + pr_err("%s: Invalid device ID\n", __func__); + return; + } + + dev = __dev[id]; + if (!dev) { + pr_err("%s: dev is null\n", __func__); + return; + } + + if (id == DIAG_BRIDGE && !dev->ops) { + pr_err("%s: can't close bridge that was not open\n", __func__); + return; + } + + mutex_lock(&dev->ifc_mutex); + if (!dev->opened) { + mutex_unlock(&dev->ifc_mutex); + pr_err("%s: Bridge not opened\n", __func__); + return; + } + + dev->opened = false; + mutex_unlock(&dev->ifc_mutex); + + dev_dbg(&dev->ifc->dev, "%s\n", __func__); + + usb_kill_anchored_urbs(&dev->submitted); + dev->ops = 0; + + pm_runtime_set_autosuspend_delay(&dev->udev->dev, + dev->default_autosusp_delay); + + kref_put(&dev->kref, diag_bridge_delete); +} +EXPORT_SYMBOL(diag_bridge_close); + +static void ipc_bridge_close(struct platform_device *pdev) +{ + WARN_ON(__dev[IPC_BRIDGE]->pdev != pdev); + WARN_ON(__dev[IPC_BRIDGE]->udev->state != USB_STATE_NOTATTACHED); + diag_bridge_close(IPC_BRIDGE); +} + +static void diag_bridge_read_cb(struct urb *urb) +{ + struct diag_bridge *dev = urb->context; + struct diag_bridge_ops *cbs = dev->ops; + + dev_dbg(&dev->ifc->dev, "%s: status:%d actual:%d\n", __func__, + urb->status, urb->actual_length); + + /* save error so that subsequent read/write returns ENODEV */ + if (urb->status == -EPROTO) + dev->err = urb->status; + + if (cbs && cbs->read_complete_cb) { + cbs->read_complete_cb(cbs->ctxt, + urb->transfer_buffer, + urb->transfer_buffer_length, + urb->status < 0 ? urb->status : urb->actual_length); + } else { + if (urb->dev->state == USB_STATE_NOTATTACHED) + dev->read_result = -ENODEV; + else if (urb->status < 0) + dev->read_result = urb->status; + else + dev->read_result = urb->actual_length; + + complete(&dev->read_done); + } + + dev->bytes_to_host += urb->actual_length; + dev->pending_reads--; + kref_put(&dev->kref, diag_bridge_delete); +} + +int diag_bridge_read(int id, char *data, int size) +{ + struct urb *urb = NULL; + unsigned int pipe; + struct diag_bridge *dev; + int ret; + + if (id < 0 || id >= MAX_BRIDGE_DEVS) { + pr_err("%s: Invalid device ID\n", __func__); + return -ENODEV; + } + + pr_debug("%s: reading %d bytes\n", __func__, size); + + dev = __dev[id]; + if (!dev) { + pr_err("%s: device is disconnected\n", __func__); + return -ENODEV; + } + + mutex_lock(&dev->read_mutex); + if (!dev->ifc) { + pr_err("%s: device is disconnected\n", __func__); + ret = -ENODEV; + goto error; + } + + if (id == DIAG_BRIDGE && !dev->ops) { + pr_err("%s: bridge is not open\n", __func__); + ret = -ENODEV; + goto error; + } + + if (!size) { + dev_err(&dev->ifc->dev, "invalid size:%d\n", size); + dev->drop_count++; + ret = -EINVAL; + goto error; + } + + /* if there was a previous unrecoverable error, just quit */ + if (id == DIAG_BRIDGE && dev->err) { + pr_err("%s: EPROTO error occurred, or device disconnected\n", + __func__); + ret = -ENODEV; + goto error; + } + + kref_get(&dev->kref); + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + dev_err(&dev->ifc->dev, "unable to allocate urb\n"); + ret = -ENOMEM; + goto put_error; + } + + ret = usb_autopm_get_interface(dev->ifc); + if (ret < 0 && ret != -EAGAIN && ret != -EACCES) { + pr_err_ratelimited("%s: read: autopm_get failed:%d\n", + __func__, ret); + goto free_error; + } + + if (dev->use_int_in_pipe) { + pipe = usb_rcvintpipe(dev->udev, dev->in_epAddr); + usb_fill_int_urb(urb, dev->udev, pipe, data, size, + diag_bridge_read_cb, dev, dev->period); + } else { + pipe = usb_rcvbulkpipe(dev->udev, dev->in_epAddr); + usb_fill_bulk_urb(urb, dev->udev, pipe, data, size, + diag_bridge_read_cb, dev); + } + + usb_anchor_urb(urb, &dev->submitted); + dev->pending_reads++; + + ret = usb_submit_urb(urb, GFP_KERNEL); + if (ret) { + pr_err_ratelimited("%s: submitting urb failed err:%d\n", + __func__, ret); + dev->pending_reads--; + usb_unanchor_urb(urb); + usb_autopm_put_interface(dev->ifc); + goto free_error; + } + + usb_autopm_put_interface(dev->ifc); + + if (id == IPC_BRIDGE) { + wait_for_completion(&dev->read_done); + ret = dev->read_result; + } + + usb_free_urb(urb); + mutex_unlock(&dev->read_mutex); + return ret; + +free_error: + usb_free_urb(urb); +put_error: + /* If URB submit successful, this is done in the completion handler */ + kref_put(&dev->kref, diag_bridge_delete); +error: + mutex_unlock(&dev->read_mutex); + return ret; +} +EXPORT_SYMBOL(diag_bridge_read); + +static int +ipc_bridge_read(struct platform_device *pdev, char *buf, unsigned int count) +{ + if (__dev[IPC_BRIDGE]->pdev != pdev) + return -EINVAL; + if (!__dev[IPC_BRIDGE]->opened) + return -EPERM; + if (count > IPC_BRIDGE_MAX_READ_SZ) + return -ENOSPC; + if (__dev[IPC_BRIDGE]->udev->state == USB_STATE_NOTATTACHED) + return -ENODEV; + + return diag_bridge_read(IPC_BRIDGE, buf, count); +} + +static void diag_bridge_write_cb(struct urb *urb) +{ + struct diag_bridge *dev = urb->context; + struct diag_bridge_ops *cbs = dev->ops; + + dev_dbg(&dev->ifc->dev, "%s\n", __func__); + + usb_autopm_put_interface_async(dev->ifc); + + /* save error so that subsequent read/write returns ENODEV */ + if (urb->status == -EPROTO) + dev->err = urb->status; + + if (cbs && cbs->write_complete_cb) { + cbs->write_complete_cb(cbs->ctxt, + urb->transfer_buffer, + urb->transfer_buffer_length, + urb->status < 0 ? urb->status : urb->actual_length); + } else { + 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); + } + + dev->bytes_to_mdm += urb->actual_length; + dev->pending_writes--; + kref_put(&dev->kref, diag_bridge_delete); +} + +int diag_bridge_write(int id, char *data, int size) +{ + struct urb *urb = NULL; + unsigned int pipe; + struct diag_bridge *dev; + int ret; + + if (id < 0 || id >= MAX_BRIDGE_DEVS) { + pr_err("%s: Invalid device ID\n", __func__); + return -ENODEV; + } + + pr_debug("%s: writing %d bytes\n", __func__, size); + + dev = __dev[id]; + if (!dev) { + pr_err("%s: device is disconnected\n", __func__); + return -ENODEV; + } + + mutex_lock(&dev->write_mutex); + if (!dev->ifc) { + pr_err("%s: device is disconnected\n", __func__); + ret = -ENODEV; + goto error; + } + + if (id == DIAG_BRIDGE && !dev->ops) { + pr_err("%s: bridge is not open\n", __func__); + ret = -ENODEV; + goto error; + } + + if (!size) { + dev_err(&dev->ifc->dev, "invalid size:%d\n", size); + ret = -EINVAL; + goto error; + } + + /* if there was a previous unrecoverable error, just quit */ + if (id == DIAG_BRIDGE && dev->err) { + pr_err("%s: EPROTO error occurred, or device disconnected\n", + __func__); + ret = -ENODEV; + goto error; + } + + kref_get(&dev->kref); + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + dev_err(&dev->ifc->dev, "unable to allocate urb\n"); + ret = -ENOMEM; + goto put_error; + } + + ret = usb_autopm_get_interface(dev->ifc); + if (ret < 0 && ret != -EAGAIN && ret != -EACCES) { + pr_err_ratelimited("%s: write: autopm_get failed:%d\n", + __func__, ret); + goto free_error; + } + + pipe = usb_sndbulkpipe(dev->udev, dev->out_epAddr); + usb_fill_bulk_urb(urb, dev->udev, pipe, data, size, + diag_bridge_write_cb, dev); + urb->transfer_flags |= URB_ZERO_PACKET; + usb_anchor_urb(urb, &dev->submitted); + dev->pending_writes++; + + ret = usb_submit_urb(urb, GFP_KERNEL); + if (ret) { + pr_err_ratelimited("%s: submitting urb failed err:%d\n", + __func__, ret); + dev->pending_writes--; + usb_unanchor_urb(urb); + usb_autopm_put_interface(dev->ifc); + goto free_error; + } + + if (id == IPC_BRIDGE) { + wait_for_completion(&dev->write_done); + ret = dev->write_result; + } + + usb_free_urb(urb); + mutex_unlock(&dev->write_mutex); + return ret; + +free_error: + usb_free_urb(urb); +put_error: + /* If URB submit successful, this is done in the completion handler */ + kref_put(&dev->kref, diag_bridge_delete); +error: + mutex_unlock(&dev->write_mutex); + return ret; +} +EXPORT_SYMBOL(diag_bridge_write); + +static int +ipc_bridge_write(struct platform_device *pdev, char *buf, unsigned int count) +{ + if (__dev[IPC_BRIDGE]->pdev != pdev) + return -EINVAL; + if (!__dev[IPC_BRIDGE]->opened) + return -EPERM; + if (count > IPC_BRIDGE_MAX_WRITE_SZ) + return -EINVAL; + + return diag_bridge_write(IPC_BRIDGE, buf, count); +} + +#if defined(CONFIG_DEBUG_FS) +#define DEBUG_BUF_SIZE 512 +static ssize_t diag_read_stats(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + char *buf; + int i, ret = 0; + + buf = kzalloc(sizeof(char) * DEBUG_BUF_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + for (i = 0; i < MAX_BRIDGE_DEVS; i++) { + struct diag_bridge *dev = __dev[i]; + + if (!dev) + continue; + + ret += scnprintf(buf, DEBUG_BUF_SIZE, + "epin:%d, epout:%d\n" + "bytes to host: %lu\n" + "bytes to mdm: %lu\n" + "pending reads: %u\n" + "pending writes: %u\n" + "drop count:%u\n" + "last error: %d\n", + dev->in_epAddr, dev->out_epAddr, + dev->bytes_to_host, dev->bytes_to_mdm, + dev->pending_reads, dev->pending_writes, + dev->drop_count, + dev->err); + } + + ret = simple_read_from_buffer(ubuf, count, ppos, buf, ret); + kfree(buf); + return ret; +} + +static ssize_t diag_reset_stats(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + int i; + + for (i = 0; i < MAX_BRIDGE_DEVS; i++) { + struct diag_bridge *dev = __dev[i]; + + if (dev) { + dev->bytes_to_host = dev->bytes_to_mdm = 0; + dev->pending_reads = dev->pending_writes = 0; + dev->drop_count = 0; + } + } + + return count; +} + +const struct file_operations diag_stats_ops = { + .read = diag_read_stats, + .write = diag_reset_stats, +}; + +static struct dentry *dent; + +static void diag_bridge_debugfs_init(void) +{ + struct dentry *dfile; + + dent = debugfs_create_dir("diag_bridge", 0); + if (IS_ERR(dent)) + return; + + dfile = debugfs_create_file("status", 0444, dent, 0, &diag_stats_ops); + if (!dfile || IS_ERR(dfile)) + debugfs_remove(dent); +} + +static void diag_bridge_debugfs_cleanup(void) +{ + debugfs_remove_recursive(dent); + dent = NULL; +} +#else +static inline void diag_bridge_debugfs_init(void) { } +static inline void diag_bridge_debugfs_cleanup(void) { } +#endif + +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 +diag_bridge_probe(struct usb_interface *ifc, const struct usb_device_id *id) +{ + struct diag_bridge *dev; + struct usb_host_interface *ifc_desc; + struct usb_endpoint_descriptor *ep_desc; + int i, devid, ret = -ENOMEM; + + pr_debug("%s: id:%lu\n", __func__, id->driver_info); + + devid = id->driver_info & 0xFF; + if (devid < 0 || devid >= MAX_BRIDGE_DEVS) { + pr_err("%s: Invalid device ID\n", __func__); + return -ENODEV; + } + + /* already probed? */ + if (__dev[devid]) { + pr_err("%s: Diag device already probed\n", __func__); + return -ENODEV; + } + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + __dev[devid] = dev; + dev->id = devid; + + dev->udev = usb_get_dev(interface_to_usbdev(ifc)); + dev->ifc = usb_get_intf(ifc); + kref_init(&dev->kref); + mutex_init(&dev->ifc_mutex); + mutex_init(&dev->read_mutex); + mutex_init(&dev->write_mutex); + init_completion(&dev->read_done); + init_completion(&dev->write_done); + init_usb_anchor(&dev->submitted); + + ifc_desc = ifc->cur_altsetting; + for (i = 0; i < ifc_desc->desc.bNumEndpoints; i++) { + ep_desc = &ifc_desc->endpoint[i].desc; + if (!dev->in_epAddr && (usb_endpoint_is_bulk_in(ep_desc) || + usb_endpoint_is_int_in(ep_desc))) { + dev->in_epAddr = ep_desc->bEndpointAddress; + if (usb_endpoint_is_int_in(ep_desc)) { + dev->use_int_in_pipe = 1; + dev->period = ep_desc->bInterval; + } + } + if (!dev->out_epAddr && usb_endpoint_is_bulk_out(ep_desc)) + dev->out_epAddr = ep_desc->bEndpointAddress; + } + + if (!(dev->in_epAddr && dev->out_epAddr)) { + pr_err("%s: could not find bulk in and bulk out endpoints\n", + __func__); + ret = -ENODEV; + goto error; + } + + usb_set_intfdata(ifc, dev); + diag_bridge_debugfs_init(); + if (devid == DIAG_BRIDGE) { + dev->pdev = platform_device_register_simple("diag_bridge", + devid, NULL, 0); + if (IS_ERR(dev->pdev)) { + pr_err("%s: unable to allocate platform device\n", + __func__); + ret = PTR_ERR(dev->pdev); + goto error; + } + } else { + dev->pdev = platform_device_alloc("ipc_bridge", -1); + if (!dev->pdev) { + pr_err("%s: unable to allocate platform device\n", + __func__); + ret = -ENOMEM; + goto error; + } + + ret = platform_device_add_data(dev->pdev, &ipc_bridge_pdata, + sizeof(struct ipc_bridge_platform_data)); + if (ret) { + pr_err("%s: fail to add pdata\n", __func__); + goto put_pdev; + } + + ret = platform_device_add(dev->pdev); + if (ret) { + pr_err("%s: fail to add pdev\n", __func__); + goto put_pdev; + } + } + + dev_dbg(&dev->ifc->dev, "%s: complete\n", __func__); + + return 0; + +put_pdev: + platform_device_put(dev->pdev); +error: + diag_bridge_debugfs_cleanup(); + mutex_destroy(&dev->write_mutex); + mutex_destroy(&dev->read_mutex); + mutex_destroy(&dev->ifc_mutex); + if (dev) + kref_put(&dev->kref, diag_bridge_delete); + + return ret; +} + +static void diag_bridge_disconnect(struct usb_interface *ifc) +{ + struct diag_bridge *dev = usb_get_intfdata(ifc); + + dev_dbg(&dev->ifc->dev, "%s\n", __func__); + + platform_device_unregister(dev->pdev); + diag_bridge_debugfs_cleanup(); + dev->err = -ENODEV; + kref_put(&dev->kref, diag_bridge_delete); +} + +static int diag_bridge_suspend(struct usb_interface *ifc, pm_message_t message) +{ + struct diag_bridge *dev = usb_get_intfdata(ifc); + struct diag_bridge_ops *cbs = dev->ops; + int ret = 0; + + if (cbs && cbs->suspend) { + ret = cbs->suspend(cbs->ctxt); + if (ret) { + dev_dbg(&dev->ifc->dev, + "%s: diag veto'd suspend\n", __func__); + return ret; + } + } + + usb_kill_anchored_urbs(&dev->submitted); + + return ret; +} + +static int diag_bridge_resume(struct usb_interface *ifc) +{ + struct diag_bridge *dev = usb_get_intfdata(ifc); + struct diag_bridge_ops *cbs = dev->ops; + + + if (cbs && cbs->resume) + cbs->resume(cbs->ctxt); + + return 0; +} + +#define DEV_ID(n) (n) + +static const struct usb_device_id diag_bridge_ids[] = { + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x9001, 0), + .driver_info = DEV_ID(0), }, + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x901D, 0), + .driver_info = DEV_ID(0), }, + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x901F, 0), + .driver_info = DEV_ID(0), }, + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x9034, 0), + .driver_info = DEV_ID(0), }, + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x9048, 0), + .driver_info = DEV_ID(0), }, + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x904C, 0), + .driver_info = DEV_ID(0), }, + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x9075, 0), + .driver_info = DEV_ID(0), }, + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x9079, 0), + .driver_info = DEV_ID(1), }, + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x908A, 0), + .driver_info = DEV_ID(0), }, + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x908E, 0), + .driver_info = DEV_ID(0), }, + /* 908E, ifc#1 refers to diag client interface */ + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x908E, 1), + .driver_info = DEV_ID(1), }, + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x909C, 0), + .driver_info = DEV_ID(0), }, + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x909D, 0), + .driver_info = DEV_ID(0), }, + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x909E, 0), + .driver_info = DEV_ID(0), }, + /* 909E, ifc#1 refers to diag client interface */ + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x909E, 1), + .driver_info = DEV_ID(1), }, + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x909F, 0), + .driver_info = DEV_ID(0), }, + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x90A0, 0), + .driver_info = DEV_ID(0), }, + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x90A4, 0), + .driver_info = DEV_ID(0), }, + /* 909E, ifc#1 refers to diag client interface */ + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x90A4, 1), + .driver_info = DEV_ID(1), }, + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x90EF, 4), + .driver_info = DEV_ID(0), }, + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x90F0, 4), + .driver_info = DEV_ID(0), }, + /* 9900, ifc#2 refers to diag client interface */ + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x9900, 2), + .driver_info = DEV_ID(0), }, + /* 9900, ifc#1 refers to IPC client interface */ + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x9900, 1), + .driver_info = DEV_ID(1), }, + /* 9901, ifc#4 refers to diag client interface */ + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x9901, 4), + .driver_info = DEV_ID(0), }, + /* 9901, ifc#3 refers to IPC client interface */ + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x9901, 3), + .driver_info = DEV_ID(1), }, + /* 9902, ifc#2 refers to diag client interface */ + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x9902, 2), + .driver_info = DEV_ID(0), }, + /* 9902, ifc#1 refers to IPC client interface */ + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x9902, 1), + .driver_info = DEV_ID(1), }, + /* 9903, ifc#4 refers to diag client interface */ + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x9903, 4), + .driver_info = DEV_ID(0), }, + /* 9903, ifc#3 refers to IPC client interface */ + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x9903, 3), + .driver_info = DEV_ID(1), }, + + {} /* terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, diag_bridge_ids); + +static struct usb_driver diag_bridge_driver = { + .name = "diag_bridge", + .probe = diag_bridge_probe, + .disconnect = diag_bridge_disconnect, + .suspend = diag_bridge_suspend, + .resume = diag_bridge_resume, + .reset_resume = diag_bridge_resume, + .id_table = diag_bridge_ids, + .supports_autosuspend = 1, +}; + +static int __init diag_bridge_init(void) +{ + int ret; + + ret = usb_register(&diag_bridge_driver); + if (ret) { + pr_err("%s: unable to register diag driver\n", __func__); + return ret; + } + + return 0; +} + +static void __exit diag_bridge_exit(void) +{ + usb_deregister(&diag_bridge_driver); +} + +module_init(diag_bridge_init); +module_exit(diag_bridge_exit); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/misc/ehset.c b/drivers/usb/misc/ehset.c index c31b4a33e6bb..0efcd485c02a 100644 --- a/drivers/usb/misc/ehset.c +++ b/drivers/usb/misc/ehset.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2013, The Linux Foundation. All rights reserved. + * Copyright (c) 2010-2013, 2018, 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 @@ -26,69 +26,89 @@ #define TEST_SINGLE_STEP_GET_DEV_DESC 0x0107 #define TEST_SINGLE_STEP_SET_FEATURE 0x0108 -static int ehset_probe(struct usb_interface *intf, - const struct usb_device_id *id) +static u8 numPorts; + +static int ehset_get_port_num(struct device *dev, const char *buf, + unsigned long *val) +{ + int ret; + + ret = kstrtoul(buf, 10, val); + if (ret < 0) { + dev_err(dev, "couldn't parse string %d\n", ret); + return ret; + } + + if (!*val || *val > numPorts) { + dev_err(dev, "Invalid port num entered\n"); + return -EINVAL; + } + + return 0; +} + +static int ehset_clear_port_feature(struct usb_device *udev, int feature, + int port1) +{ + return usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + USB_REQ_CLEAR_FEATURE, USB_RT_PORT, feature, port1, + NULL, 0, 1000); +} + +static int ehset_set_port_feature(struct usb_device *udev, int feature, + int port1, int timeout) +{ + return usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + USB_REQ_SET_FEATURE, USB_RT_PORT, feature, port1, + NULL, 0, timeout); +} + +static int ehset_set_testmode(struct device *dev, struct usb_device *child_udev, + struct usb_device *hub_udev, int test_id, int port) { - int ret = -EINVAL; - struct usb_device *dev = interface_to_usbdev(intf); - struct usb_device *hub_udev = dev->parent; struct usb_device_descriptor *buf; - u8 portnum = dev->portnum; - u16 test_pid = le16_to_cpu(dev->descriptor.idProduct); + int ret = -EINVAL; - switch (test_pid) { + switch (test_id) { case TEST_SE0_NAK_PID: - ret = usb_control_msg(hub_udev, usb_sndctrlpipe(hub_udev, 0), - USB_REQ_SET_FEATURE, USB_RT_PORT, - USB_PORT_FEAT_TEST, - (TEST_SE0_NAK << 8) | portnum, - NULL, 0, 1000); + ret = ehset_set_port_feature(hub_udev, USB_PORT_FEAT_TEST, + (TEST_SE0_NAK << 8) | port, 1000); break; case TEST_J_PID: - ret = usb_control_msg(hub_udev, usb_sndctrlpipe(hub_udev, 0), - USB_REQ_SET_FEATURE, USB_RT_PORT, - USB_PORT_FEAT_TEST, - (TEST_J << 8) | portnum, - NULL, 0, 1000); + ret = ehset_set_port_feature(hub_udev, USB_PORT_FEAT_TEST, + (TEST_J << 8) | port, 1000); break; case TEST_K_PID: - ret = usb_control_msg(hub_udev, usb_sndctrlpipe(hub_udev, 0), - USB_REQ_SET_FEATURE, USB_RT_PORT, - USB_PORT_FEAT_TEST, - (TEST_K << 8) | portnum, - NULL, 0, 1000); + ret = ehset_set_port_feature(hub_udev, USB_PORT_FEAT_TEST, + (TEST_K << 8) | port, 1000); break; case TEST_PACKET_PID: - ret = usb_control_msg(hub_udev, usb_sndctrlpipe(hub_udev, 0), - USB_REQ_SET_FEATURE, USB_RT_PORT, - USB_PORT_FEAT_TEST, - (TEST_PACKET << 8) | portnum, - NULL, 0, 1000); + ret = ehset_set_port_feature(hub_udev, USB_PORT_FEAT_TEST, + (TEST_PACKET << 8) | port, 1000); break; case TEST_HS_HOST_PORT_SUSPEND_RESUME: /* Test: wait for 15secs -> suspend -> 15secs delay -> resume */ msleep(15 * 1000); - ret = usb_control_msg(hub_udev, usb_sndctrlpipe(hub_udev, 0), - USB_REQ_SET_FEATURE, USB_RT_PORT, - USB_PORT_FEAT_SUSPEND, portnum, - NULL, 0, 1000); - if (ret < 0) + ret = ehset_set_port_feature(hub_udev, USB_PORT_FEAT_SUSPEND, + port, 1000); + if (ret) break; msleep(15 * 1000); - ret = usb_control_msg(hub_udev, usb_sndctrlpipe(hub_udev, 0), - USB_REQ_CLEAR_FEATURE, USB_RT_PORT, - USB_PORT_FEAT_SUSPEND, portnum, - NULL, 0, 1000); + ret = ehset_clear_port_feature(hub_udev, USB_PORT_FEAT_SUSPEND, + port); break; case TEST_SINGLE_STEP_GET_DEV_DESC: /* Test: wait for 15secs -> GetDescriptor request */ msleep(15 * 1000); buf = kmalloc(USB_DT_DEVICE_SIZE, GFP_KERNEL); - if (!buf) - return -ENOMEM; + if (!buf) { + ret = -ENOMEM; + break; + } - ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + ret = usb_control_msg(child_udev, + usb_rcvctrlpipe(child_udev, 0), USB_REQ_GET_DESCRIPTOR, USB_DIR_IN, USB_DT_DEVICE << 8, 0, buf, USB_DT_DEVICE_SIZE, @@ -103,28 +123,212 @@ static int ehset_probe(struct usb_interface *intf, * SetPortFeature handling can only be done inside the HCD's * hub_control callback function. */ - if (hub_udev != dev->bus->root_hub) { - dev_err(&intf->dev, "SINGLE_STEP_SET_FEATURE test only supported on root hub\n"); + if (hub_udev != child_udev->bus->root_hub) { + dev_err(dev, "SINGLE_STEP_SET_FEATURE test only supported on root hub\n"); break; } - ret = usb_control_msg(hub_udev, usb_sndctrlpipe(hub_udev, 0), - USB_REQ_SET_FEATURE, USB_RT_PORT, - USB_PORT_FEAT_TEST, - (6 << 8) | portnum, - NULL, 0, 60 * 1000); + ret = ehset_set_port_feature(hub_udev, USB_PORT_FEAT_TEST, + (6 << 8) | port, 60 * 1000); break; default: - dev_err(&intf->dev, "%s: unsupported PID: 0x%x\n", - __func__, test_pid); + dev_err(dev, "%s: unsupported test ID: 0x%x\n", + __func__, test_id); + } + + return ret; +} + +static ssize_t test_se0_nak_portnum_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_device *udev = interface_to_usbdev(intf); + unsigned long portnum; + int ret; + + ret = ehset_get_port_num(dev, buf, &portnum); + if (ret) + return ret; + + usb_lock_device(udev); + ret = ehset_set_testmode(dev, NULL, udev, TEST_SE0_NAK_PID, portnum); + usb_unlock_device(udev); + if (ret) { + dev_err(dev, "Error %d while SE0_NAK test\n", ret); + return ret; } + return count; +} +static DEVICE_ATTR_WO(test_se0_nak_portnum); + +static ssize_t test_j_portnum_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_device *udev = interface_to_usbdev(intf); + unsigned long portnum; + int ret; + + ret = ehset_get_port_num(dev, buf, &portnum); + if (ret) + return ret; + + usb_lock_device(udev); + ret = ehset_set_testmode(dev, NULL, udev, TEST_J_PID, portnum); + usb_unlock_device(udev); + if (ret) { + dev_err(dev, "Error %d while J state test\n", ret); + return ret; + } + + return count; +} +static DEVICE_ATTR_WO(test_j_portnum); + +static ssize_t test_k_portnum_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_device *udev = interface_to_usbdev(intf); + unsigned long portnum; + int ret; + + ret = ehset_get_port_num(dev, buf, &portnum); + if (ret) + return ret; + + usb_lock_device(udev); + ret = ehset_set_testmode(dev, NULL, udev, TEST_K_PID, portnum); + usb_unlock_device(udev); + if (ret) { + dev_err(dev, "Error %d while K state test\n", ret); + return ret; + } + + return count; +} +static DEVICE_ATTR_WO(test_k_portnum); + +static ssize_t test_packet_portnum_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_device *udev = interface_to_usbdev(intf); + unsigned long portnum; + int ret; + + ret = ehset_get_port_num(dev, buf, &portnum); + if (ret) + return ret; + + usb_lock_device(udev); + ret = ehset_set_testmode(dev, NULL, udev, TEST_PACKET_PID, portnum); + usb_unlock_device(udev); + if (ret) { + dev_err(dev, "Error %d while sending test packets\n", ret); + return ret; + } + + return count; +} +static DEVICE_ATTR_WO(test_packet_portnum); + +static ssize_t test_port_susp_resume_portnum_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_device *udev = interface_to_usbdev(intf); + unsigned long portnum; + int ret; + + ret = ehset_get_port_num(dev, buf, &portnum); + if (ret) + return ret; + + usb_lock_device(udev); + ret = ehset_set_testmode(dev, NULL, udev, + TEST_HS_HOST_PORT_SUSPEND_RESUME, portnum); + usb_unlock_device(udev); + if (ret) { + dev_err(dev, "Error %d while port suspend resume test\n", ret); + return ret; + } + + return count; +} +static DEVICE_ATTR_WO(test_port_susp_resume_portnum); + +static struct attribute *ehset_attributes[] = { + &dev_attr_test_se0_nak_portnum.attr, + &dev_attr_test_j_portnum.attr, + &dev_attr_test_k_portnum.attr, + &dev_attr_test_packet_portnum.attr, + &dev_attr_test_port_susp_resume_portnum.attr, + NULL +}; + +static const struct attribute_group ehset_attr_group = { + .attrs = ehset_attributes, +}; + +static int ehset_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + int ret = -EINVAL; + struct usb_device *dev = interface_to_usbdev(intf); + struct usb_device *hub_udev = dev->parent; + u8 portnum = dev->portnum; + u16 test_pid = le16_to_cpu(dev->descriptor.idProduct); + + /* + * If an external hub does not support the EHSET test fixture, then user + * can forcefully unbind the external hub from the hub driver (to which + * an external hub gets bound by default) and bind it to this driver, so + * as to send test signals on any downstream port of the hub. + */ + if (dev->descriptor.bDeviceClass == USB_CLASS_HUB) { + struct usb_hub_descriptor *descriptor; + + descriptor = kzalloc(sizeof(*descriptor), GFP_KERNEL); + if (!descriptor) + return -ENOMEM; + + ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + USB_REQ_GET_DESCRIPTOR, USB_DIR_IN | USB_RT_HUB, + USB_DT_HUB << 8, 0, descriptor, + USB_DT_HUB_NONVAR_SIZE, USB_CTRL_GET_TIMEOUT); + if (ret < 0) { + dev_err(&intf->dev, "%s: Failed to get hub desc %d\n", + __func__, ret); + kfree(descriptor); + return ret; + } + + numPorts = descriptor->bNbrPorts; + ret = sysfs_create_group(&intf->dev.kobj, &ehset_attr_group); + if (ret < 0) + dev_err(&intf->dev, "%s: Failed to create sysfs nodes %d\n", + __func__, ret); + + kfree(descriptor); + return ret; + } + + ret = ehset_set_testmode(&intf->dev, dev, hub_udev, test_pid, portnum); + return (ret < 0) ? ret : 0; } static void ehset_disconnect(struct usb_interface *intf) { + struct usb_device *dev = interface_to_usbdev(intf); + + numPorts = 0; + if (dev->descriptor.bDeviceClass == USB_CLASS_HUB) + sysfs_remove_group(&intf->dev.kobj, &ehset_attr_group); } static const struct usb_device_id ehset_id_table[] = { diff --git a/drivers/usb/misc/ks_bridge.c b/drivers/usb/misc/ks_bridge.c new file mode 100644 index 000000000000..3c8badc41fbc --- /dev/null +++ b/drivers/usb/misc/ks_bridge.c @@ -0,0 +1,1155 @@ +/* + * Copyright (c) 2012-2014, 2017-2019, 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. + */ + +/* add additional information to our printk's */ +#define pr_fmt(fmt) "%s: " fmt "\n", __func__ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/kref.h> +#include <linux/platform_device.h> +#include <linux/ratelimit.h> +#include <linux/uaccess.h> +#include <linux/usb.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/cdev.h> +#include <linux/list.h> +#include <linux/wait.h> +#include <linux/poll.h> +#include <linux/kobject.h> + +#define DRIVER_DESC "USB host ks bridge driver" + +enum bus_id { + BUS_HSIC, + BUS_USB, + BUS_UNDEF, +}; + +#define BUSNAME_LEN 20 + +static enum bus_id str_to_busid(const char *name) +{ + if (!strncasecmp("msm_hsic_host", name, BUSNAME_LEN)) + return BUS_HSIC; + if (!strncasecmp("msm_ehci_host.0", name, BUSNAME_LEN)) + return BUS_USB; + if (!strncasecmp("xhci-hcd.0.auto", name, BUSNAME_LEN) || + !strncasecmp("xhci-hcd.1.auto", name, BUSNAME_LEN)) + return BUS_USB; + + return BUS_UNDEF; +} + +struct data_pkt { + int n_read; + char *buf; + size_t len; + struct list_head list; + void *ctxt; +}; + +#define FILE_OPENED BIT(0) +#define USB_DEV_CONNECTED BIT(1) +#define NO_RX_REQS 10 +#define NO_BRIDGE_INSTANCES 4 +#define EFS_HSIC_BRIDGE_INDEX 2 +#define EFS_USB_BRIDGE_INDEX 3 +#define MAX_DATA_PKT_SIZE 16384 +#define PENDING_URB_TIMEOUT 10 + +struct ksb_dev_info { + const char *name; +}; + +struct ks_bridge { + char *name; + spinlock_t lock; + struct workqueue_struct *wq; + struct work_struct to_mdm_work; + struct work_struct start_rx_work; + struct list_head to_mdm_list; + struct list_head to_ks_list; + wait_queue_head_t ks_wait_q; + wait_queue_head_t pending_urb_wait; + atomic_t tx_pending_cnt; + atomic_t rx_pending_cnt; + + struct ksb_dev_info id_info; + + /* cdev interface */ + dev_t cdev_start_no; + struct cdev cdev; + struct class *class; + struct device *device; + + /* usb specific */ + struct usb_device *udev; + struct usb_interface *ifc; + __u8 in_epAddr; + __u8 out_epAddr; + unsigned int in_pipe; + unsigned int out_pipe; + struct usb_anchor submitted; + + unsigned long flags; + + /* to handle INT IN ep */ + unsigned int period; + +#define DBG_MSG_LEN 40 +#define DBG_MAX_MSG 500 + unsigned int dbg_idx; + rwlock_t dbg_lock; + + char (dbgbuf[DBG_MAX_MSG])[DBG_MSG_LEN]; /* buffer */ +}; + +struct ks_bridge *__ksb[NO_BRIDGE_INSTANCES]; + +/* by default debugging is enabled */ +static unsigned int enable_dbg = 1; +module_param(enable_dbg, uint, S_IRUGO | S_IWUSR); + +static void +dbg_log_event(struct ks_bridge *ksb, char *event, int d1, int d2) +{ + unsigned long flags; + unsigned long long t; + unsigned long nanosec; + + if (!enable_dbg) + return; + + write_lock_irqsave(&ksb->dbg_lock, flags); + t = cpu_clock(smp_processor_id()); + nanosec = do_div(t, 1000000000)/1000; + scnprintf(ksb->dbgbuf[ksb->dbg_idx], DBG_MSG_LEN, "%5lu.%06lu:%s:%x:%x", + (unsigned long)t, nanosec, event, d1, d2); + + ksb->dbg_idx++; + ksb->dbg_idx = ksb->dbg_idx % DBG_MAX_MSG; + write_unlock_irqrestore(&ksb->dbg_lock, flags); +} + +static +struct data_pkt *ksb_alloc_data_pkt(size_t count, gfp_t flags, void *ctxt) +{ + struct data_pkt *pkt; + + pkt = kzalloc(sizeof(struct data_pkt), flags); + if (!pkt) + return ERR_PTR(-ENOMEM); + + pkt->buf = kmalloc(count, flags); + if (!pkt->buf) { + kfree(pkt); + return ERR_PTR(-ENOMEM); + } + + pkt->len = count; + INIT_LIST_HEAD(&pkt->list); + pkt->ctxt = ctxt; + + return pkt; +} + +static void ksb_free_data_pkt(struct data_pkt *pkt) +{ + kfree(pkt->buf); + kfree(pkt); +} + + +static void +submit_one_urb(struct ks_bridge *ksb, gfp_t flags, struct data_pkt *pkt); +static ssize_t ksb_fs_read(struct file *fp, char __user *buf, + size_t count, loff_t *pos) +{ + int ret; + unsigned long flags; + struct ks_bridge *ksb = fp->private_data; + struct data_pkt *pkt = NULL; + size_t space, copied; + +read_start: + if (!test_bit(USB_DEV_CONNECTED, &ksb->flags)) + return -ENODEV; + + spin_lock_irqsave(&ksb->lock, flags); + if (list_empty(&ksb->to_ks_list)) { + spin_unlock_irqrestore(&ksb->lock, flags); + ret = wait_event_interruptible(ksb->ks_wait_q, + !list_empty(&ksb->to_ks_list) || + !test_bit(USB_DEV_CONNECTED, &ksb->flags)); + if (ret < 0) + return ret; + + goto read_start; + } + + space = count; + copied = 0; + while (!list_empty(&ksb->to_ks_list) && space && + test_bit(USB_DEV_CONNECTED, &ksb->flags)) { + size_t len; + + pkt = list_first_entry(&ksb->to_ks_list, struct data_pkt, list); + list_del_init(&pkt->list); + len = min_t(size_t, space, pkt->len - pkt->n_read); + spin_unlock_irqrestore(&ksb->lock, flags); + + ret = copy_to_user(buf + copied, pkt->buf + pkt->n_read, len); + if (ret) { + dev_err(ksb->device, + "copy_to_user failed err:%d\n", ret); + ksb_free_data_pkt(pkt); + return -EFAULT; + } + + pkt->n_read += len; + space -= len; + copied += len; + + if (pkt->n_read == pkt->len) { + /* + * re-init the packet and queue it + * for more data. + */ + pkt->n_read = 0; + pkt->len = MAX_DATA_PKT_SIZE; + submit_one_urb(ksb, GFP_KERNEL, pkt); + pkt = NULL; + } + spin_lock_irqsave(&ksb->lock, flags); + } + + /* put the partial packet back in the list */ + if (!space && pkt && pkt->n_read != pkt->len) { + if (test_bit(USB_DEV_CONNECTED, &ksb->flags)) + list_add(&pkt->list, &ksb->to_ks_list); + else + ksb_free_data_pkt(pkt); + } + spin_unlock_irqrestore(&ksb->lock, flags); + + dbg_log_event(ksb, "KS_READ", copied, 0); + + dev_dbg(ksb->device, "count:%zu space:%zu copied:%zu", count, + space, copied); + + return copied; +} + +static void ksb_tx_cb(struct urb *urb) +{ + struct data_pkt *pkt = urb->context; + struct ks_bridge *ksb = pkt->ctxt; + + dbg_log_event(ksb, "C TX_URB", urb->status, 0); + dev_dbg(&ksb->udev->dev, "status:%d", urb->status); + + if (test_bit(USB_DEV_CONNECTED, &ksb->flags)) + usb_autopm_put_interface_async(ksb->ifc); + + if (urb->status < 0) + pr_err_ratelimited("%s: urb failed with err:%d", + ksb->id_info.name, urb->status); + + ksb_free_data_pkt(pkt); + + atomic_dec(&ksb->tx_pending_cnt); + wake_up(&ksb->pending_urb_wait); +} + +static void ksb_tomdm_work(struct work_struct *w) +{ + struct ks_bridge *ksb = container_of(w, struct ks_bridge, to_mdm_work); + struct data_pkt *pkt; + unsigned long flags; + struct urb *urb; + int ret; + + spin_lock_irqsave(&ksb->lock, flags); + while (!list_empty(&ksb->to_mdm_list) + && test_bit(USB_DEV_CONNECTED, &ksb->flags)) { + pkt = list_first_entry(&ksb->to_mdm_list, + struct data_pkt, list); + list_del_init(&pkt->list); + spin_unlock_irqrestore(&ksb->lock, flags); + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + dbg_log_event(ksb, "TX_URB_MEM_FAIL", -ENOMEM, 0); + pr_err_ratelimited("%s: unable to allocate urb", + ksb->id_info.name); + ksb_free_data_pkt(pkt); + return; + } + + ret = usb_autopm_get_interface(ksb->ifc); + if (ret < 0 && ret != -EAGAIN && ret != -EACCES) { + dbg_log_event(ksb, "TX_URB_AUTOPM_FAIL", ret, 0); + pr_err_ratelimited("%s: autopm_get failed:%d", + ksb->id_info.name, ret); + usb_free_urb(urb); + ksb_free_data_pkt(pkt); + return; + } + usb_fill_bulk_urb(urb, ksb->udev, ksb->out_pipe, + pkt->buf, pkt->len, ksb_tx_cb, pkt); + usb_anchor_urb(urb, &ksb->submitted); + + dbg_log_event(ksb, "S TX_URB", pkt->len, 0); + + atomic_inc(&ksb->tx_pending_cnt); + ret = usb_submit_urb(urb, GFP_KERNEL); + if (ret) { + dev_err(&ksb->udev->dev, "out urb submission failed"); + usb_unanchor_urb(urb); + usb_free_urb(urb); + ksb_free_data_pkt(pkt); + usb_autopm_put_interface(ksb->ifc); + atomic_dec(&ksb->tx_pending_cnt); + wake_up(&ksb->pending_urb_wait); + return; + } + + usb_free_urb(urb); + + spin_lock_irqsave(&ksb->lock, flags); + } + spin_unlock_irqrestore(&ksb->lock, flags); +} + +static ssize_t ksb_fs_write(struct file *fp, const char __user *buf, + size_t count, loff_t *pos) +{ + int ret; + struct data_pkt *pkt; + unsigned long flags; + struct ks_bridge *ksb = fp->private_data; + + if (!test_bit(USB_DEV_CONNECTED, &ksb->flags)) + return -ENODEV; + + if (count > MAX_DATA_PKT_SIZE) + count = MAX_DATA_PKT_SIZE; + + pkt = ksb_alloc_data_pkt(count, GFP_KERNEL, ksb); + if (IS_ERR(pkt)) { + dev_err(ksb->device, + "unable to allocate data packet"); + return PTR_ERR(pkt); + } + + ret = copy_from_user(pkt->buf, buf, count); + if (ret) { + dev_err(ksb->device, + "copy_from_user failed: err:%d", ret); + ksb_free_data_pkt(pkt); + return ret; + } + + spin_lock_irqsave(&ksb->lock, flags); + list_add_tail(&pkt->list, &ksb->to_mdm_list); + spin_unlock_irqrestore(&ksb->lock, flags); + + queue_work(ksb->wq, &ksb->to_mdm_work); + + dbg_log_event(ksb, "KS_WRITE", count, 0); + + return count; +} + +static int ksb_fs_open(struct inode *ip, struct file *fp) +{ + struct ks_bridge *ksb = + container_of(ip->i_cdev, struct ks_bridge, cdev); + + if (IS_ERR(ksb)) { + pr_err("ksb device not found"); + return -ENODEV; + } + + dev_dbg(ksb->device, ":%s", ksb->id_info.name); + dbg_log_event(ksb, "FS-OPEN", 0, 0); + + fp->private_data = ksb; + set_bit(FILE_OPENED, &ksb->flags); + + if (test_bit(USB_DEV_CONNECTED, &ksb->flags)) + queue_work(ksb->wq, &ksb->start_rx_work); + + return 0; +} + +static unsigned int ksb_fs_poll(struct file *file, poll_table *wait) +{ + struct ks_bridge *ksb = file->private_data; + unsigned long flags; + int ret = 0; + + if (!test_bit(USB_DEV_CONNECTED, &ksb->flags)) + return POLLERR; + + poll_wait(file, &ksb->ks_wait_q, wait); + if (!test_bit(USB_DEV_CONNECTED, &ksb->flags)) + return POLLERR; + + spin_lock_irqsave(&ksb->lock, flags); + if (!list_empty(&ksb->to_ks_list)) + ret = POLLIN | POLLRDNORM; + spin_unlock_irqrestore(&ksb->lock, flags); + + return ret; +} + +static int ksb_fs_release(struct inode *ip, struct file *fp) +{ + struct ks_bridge *ksb = fp->private_data; + struct data_pkt *pkt; + unsigned long flags; + + if (test_bit(USB_DEV_CONNECTED, &ksb->flags)) + dev_dbg(ksb->device, ":%s", ksb->id_info.name); + dbg_log_event(ksb, "FS-RELEASE", 0, 0); + + usb_kill_anchored_urbs(&ksb->submitted); + + wait_event_interruptible_timeout( + ksb->pending_urb_wait, + !atomic_read(&ksb->tx_pending_cnt) && + !atomic_read(&ksb->rx_pending_cnt), + msecs_to_jiffies(PENDING_URB_TIMEOUT)); + + spin_lock_irqsave(&ksb->lock, flags); + while (!list_empty(&ksb->to_ks_list)) { + pkt = list_first_entry(&ksb->to_ks_list, + struct data_pkt, list); + list_del_init(&pkt->list); + ksb_free_data_pkt(pkt); + } + while (!list_empty(&ksb->to_mdm_list)) { + pkt = list_first_entry(&ksb->to_mdm_list, + struct data_pkt, list); + list_del_init(&pkt->list); + ksb_free_data_pkt(pkt); + } + spin_unlock_irqrestore(&ksb->lock, flags); + clear_bit(FILE_OPENED, &ksb->flags); + fp->private_data = NULL; + + return 0; +} + +static const struct file_operations ksb_fops = { + .owner = THIS_MODULE, + .read = ksb_fs_read, + .write = ksb_fs_write, + .open = ksb_fs_open, + .release = ksb_fs_release, + .poll = ksb_fs_poll, +}; + +static struct ksb_dev_info ksb_fboot_dev[] = { + { + .name = "ks_hsic_bridge", + }, + { + .name = "ks_usb_bridge", + }, +}; + +static struct ksb_dev_info ksb_efs_hsic_dev = { + .name = "efs_hsic_bridge", +}; + +static struct ksb_dev_info ksb_efs_usb_dev = { + .name = "efs_usb_bridge", +}; +static const struct usb_device_id ksb_usb_ids[] = { + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x9008, 0), + .driver_info = (unsigned long)&ksb_fboot_dev, }, + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x9025, 0), }, + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x9091, 0), }, + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x901D, 0), }, + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x901F, 0), }, + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x900E, 0), }, + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x9900, 0), }, + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x9901, 0), }, + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x9902, 3), + .driver_info = (unsigned long)&ksb_fboot_dev, }, + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x9903, 5), + .driver_info = (unsigned long)&ksb_fboot_dev, }, + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x9048, 2), + .driver_info = (unsigned long)&ksb_efs_hsic_dev, }, + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x904C, 2), + .driver_info = (unsigned long)&ksb_efs_hsic_dev, }, + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x9075, 2), + .driver_info = (unsigned long)&ksb_efs_hsic_dev, }, + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x9079, 2), + .driver_info = (unsigned long)&ksb_efs_usb_dev, }, + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x908A, 2), + .driver_info = (unsigned long)&ksb_efs_hsic_dev, }, + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x908E, 3), + .driver_info = (unsigned long)&ksb_efs_hsic_dev, }, + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x909C, 2), + .driver_info = (unsigned long)&ksb_efs_hsic_dev, }, + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x909D, 2), + .driver_info = (unsigned long)&ksb_efs_hsic_dev, }, + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x909E, 3), + .driver_info = (unsigned long)&ksb_efs_hsic_dev, }, + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x909F, 2), + .driver_info = (unsigned long)&ksb_efs_hsic_dev, }, + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x90A0, 2), + .driver_info = (unsigned long)&ksb_efs_hsic_dev, }, + { USB_DEVICE_INTERFACE_NUMBER(0x5c6, 0x90A4, 3), + .driver_info = (unsigned long)&ksb_efs_hsic_dev, }, + + {} /* terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, ksb_usb_ids); + +static void ksb_rx_cb(struct urb *urb); +static void +submit_one_urb(struct ks_bridge *ksb, gfp_t flags, struct data_pkt *pkt) +{ + struct urb *urb; + int ret; + + urb = usb_alloc_urb(0, flags); + if (!urb) { + dev_err(&ksb->udev->dev, "unable to allocate urb"); + ksb_free_data_pkt(pkt); + return; + } + + if (ksb->period) + usb_fill_int_urb(urb, ksb->udev, ksb->in_pipe, + pkt->buf, pkt->len, + ksb_rx_cb, pkt, ksb->period); + else + usb_fill_bulk_urb(urb, ksb->udev, ksb->in_pipe, + pkt->buf, pkt->len, + ksb_rx_cb, pkt); + + usb_anchor_urb(urb, &ksb->submitted); + + if (!test_bit(USB_DEV_CONNECTED, &ksb->flags)) { + usb_unanchor_urb(urb); + usb_free_urb(urb); + ksb_free_data_pkt(pkt); + return; + } + + atomic_inc(&ksb->rx_pending_cnt); + ret = usb_submit_urb(urb, flags); + if (ret) { + dev_err(&ksb->udev->dev, "in urb submission failed"); + usb_unanchor_urb(urb); + usb_free_urb(urb); + ksb_free_data_pkt(pkt); + atomic_dec(&ksb->rx_pending_cnt); + wake_up(&ksb->pending_urb_wait); + return; + } + + dbg_log_event(ksb, "S RX_URB", pkt->len, 0); + + usb_free_urb(urb); +} +static void ksb_rx_cb(struct urb *urb) +{ + struct data_pkt *pkt = urb->context; + struct ks_bridge *ksb = pkt->ctxt; + bool wakeup = true; + + dbg_log_event(ksb, "C RX_URB", urb->status, urb->actual_length); + + dev_dbg(&ksb->udev->dev, "status:%d actual:%d", urb->status, + urb->actual_length); + + /*non zero len of data received while unlinking urb*/ + if (urb->status == -ENOENT && (urb->actual_length > 0)) { + /* + * If we wakeup the reader process now, it may + * queue the URB before its reject flag gets + * cleared. + */ + wakeup = false; + goto add_to_list; + } + + if (urb->status < 0) { + if (urb->status != -ESHUTDOWN && urb->status != -ENOENT + && urb->status != -EPROTO) + pr_err_ratelimited("%s: urb failed with err:%d", + ksb->id_info.name, urb->status); + + if (!urb->actual_length) { + ksb_free_data_pkt(pkt); + goto done; + } + } + + usb_mark_last_busy(ksb->udev); + + if (urb->actual_length == 0) { + submit_one_urb(ksb, GFP_ATOMIC, pkt); + goto done; + } + +add_to_list: + spin_lock(&ksb->lock); + pkt->len = urb->actual_length; + list_add_tail(&pkt->list, &ksb->to_ks_list); + spin_unlock(&ksb->lock); + /* wake up read thread */ + if (wakeup) + wake_up(&ksb->ks_wait_q); +done: + atomic_dec(&ksb->rx_pending_cnt); + wake_up(&ksb->pending_urb_wait); +} + +static void ksb_start_rx_work(struct work_struct *w) +{ + struct ks_bridge *ksb = + container_of(w, struct ks_bridge, start_rx_work); + struct data_pkt *pkt; + struct urb *urb; + int i = 0; + int ret; + bool put = true; + + ret = usb_autopm_get_interface(ksb->ifc); + if (ret < 0) { + if (ret != -EAGAIN && ret != -EACCES) { + pr_err_ratelimited("%s: autopm_get failed:%d", + ksb->id_info.name, ret); + return; + } + put = false; + } + for (i = 0; i < NO_RX_REQS; i++) { + + if (!test_bit(USB_DEV_CONNECTED, &ksb->flags)) + break; + + pkt = ksb_alloc_data_pkt(MAX_DATA_PKT_SIZE, GFP_KERNEL, ksb); + if (IS_ERR(pkt)) { + dev_err(&ksb->udev->dev, "unable to allocate data pkt"); + break; + } + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + dev_err(&ksb->udev->dev, "unable to allocate urb"); + ksb_free_data_pkt(pkt); + break; + } + + if (ksb->period) + usb_fill_int_urb(urb, ksb->udev, ksb->in_pipe, + pkt->buf, pkt->len, + ksb_rx_cb, pkt, ksb->period); + else + usb_fill_bulk_urb(urb, ksb->udev, ksb->in_pipe, + pkt->buf, pkt->len, + ksb_rx_cb, pkt); + + usb_anchor_urb(urb, &ksb->submitted); + + dbg_log_event(ksb, "S RX_URB", pkt->len, 0); + + atomic_inc(&ksb->rx_pending_cnt); + ret = usb_submit_urb(urb, GFP_KERNEL); + if (ret) { + dev_err(&ksb->udev->dev, "in urb submission failed"); + usb_unanchor_urb(urb); + usb_free_urb(urb); + ksb_free_data_pkt(pkt); + atomic_dec(&ksb->rx_pending_cnt); + wake_up(&ksb->pending_urb_wait); + break; + } + + usb_free_urb(urb); + } + if (put) + usb_autopm_put_interface_async(ksb->ifc); +} + +static void ks_bridge_notify_status(struct kobject *kobj, + const struct usb_device_id *id) +{ + char product_info[32]; + char *envp[2] = { product_info, NULL }; + + snprintf(product_info, sizeof(product_info), "PRODUCT=%x/%x/%x", + id->idVendor, id->idProduct, id->bDeviceProtocol); + kobject_uevent_env(kobj, KOBJ_ONLINE, envp); +} + +static int +ksb_usb_probe(struct usb_interface *ifc, const struct usb_device_id *id) +{ + __u8 ifc_num, ifc_count, ksb_port_num; + struct usb_host_interface *ifc_desc; + struct usb_endpoint_descriptor *ep_desc; + int i; + struct ks_bridge *ksb; + unsigned long flags; + struct data_pkt *pkt; + struct ksb_dev_info *mdev, *fbdev; + struct usb_device *udev; + unsigned int bus_id; + int ret; + bool free_mdev = false; + + ifc_num = ifc->cur_altsetting->desc.bInterfaceNumber; + + udev = interface_to_usbdev(ifc); + ifc_count = udev->actconfig->desc.bNumInterfaces; + fbdev = mdev = (struct ksb_dev_info *)id->driver_info; + + bus_id = str_to_busid(udev->bus->bus_name); + if (bus_id == BUS_UNDEF) { + dev_err(&udev->dev, "unknown usb bus %s, probe failed\n", + udev->bus->bus_name); + return -ENODEV; + } + + switch (id->idProduct) { + case 0x900E: + case 0x9025: + case 0x9091: + case 0x901D: + case 0x901F: + /* 1-1 mapping between ksb and udev port which starts with 1 */ + ksb_port_num = udev->portnum - 1; + dev_dbg(&udev->dev, "ifc_count: %u, port_num:%u\n", ifc_count, + ksb_port_num); + if (ifc_count > 1) + return -ENODEV; + if (ksb_port_num >= NO_BRIDGE_INSTANCES) { + dev_err(&udev->dev, "port-num:%u invalid. Try first\n", + ksb_port_num); + ksb_port_num = 0; + } + ksb = __ksb[ksb_port_num]; + if (ksb->ifc) { + dev_err(&udev->dev, "port already in use\n"); + return -ENODEV; + } + mdev = kzalloc(sizeof(struct ksb_dev_info), GFP_KERNEL); + if (!mdev) + return -ENOMEM; + free_mdev = true; + mdev->name = ksb->name; + break; + case 0x9008: + case 0x9902: + case 0x9903: + ksb = __ksb[bus_id]; + mdev = &fbdev[bus_id]; + break; + case 0x9048: + case 0x904C: + case 0x9075: + case 0x908A: + case 0x908E: + case 0x90A0: + case 0x909C: + case 0x909D: + case 0x909E: + case 0x909F: + case 0x90A4: + ksb = __ksb[EFS_HSIC_BRIDGE_INDEX]; + break; + case 0x9079: + if (ifc_num != 2) + return -ENODEV; + ksb = __ksb[EFS_USB_BRIDGE_INDEX]; + break; + default: + return -ENODEV; + } + + if (!ksb) { + pr_err("ksb is not initialized"); + return -ENODEV; + } + + ksb->udev = usb_get_dev(interface_to_usbdev(ifc)); + ksb->ifc = ifc; + ifc_desc = ifc->cur_altsetting; + ksb->id_info = *mdev; + + for (i = 0; i < ifc_desc->desc.bNumEndpoints; i++) { + ep_desc = &ifc_desc->endpoint[i].desc; + + if (!ksb->in_epAddr && (usb_endpoint_is_bulk_in(ep_desc))) { + ksb->in_epAddr = ep_desc->bEndpointAddress; + ksb->period = 0; + } + + if (!ksb->in_epAddr && (usb_endpoint_is_int_in(ep_desc))) { + ksb->in_epAddr = ep_desc->bEndpointAddress; + ksb->period = ep_desc->bInterval; + } + + if (!ksb->out_epAddr && usb_endpoint_is_bulk_out(ep_desc)) + ksb->out_epAddr = ep_desc->bEndpointAddress; + } + + if (!(ksb->in_epAddr && ksb->out_epAddr)) { + dev_err(&udev->dev, + "could not find bulk in and bulk out endpoints"); + usb_put_dev(ksb->udev); + ksb->ifc = NULL; + if (free_mdev) + kfree(mdev); + return -ENODEV; + } + + ksb->in_pipe = ksb->period ? + usb_rcvintpipe(ksb->udev, ksb->in_epAddr) : + usb_rcvbulkpipe(ksb->udev, ksb->in_epAddr); + + ksb->out_pipe = usb_sndbulkpipe(ksb->udev, ksb->out_epAddr); + + usb_set_intfdata(ifc, ksb); + set_bit(USB_DEV_CONNECTED, &ksb->flags); + atomic_set(&ksb->tx_pending_cnt, 0); + atomic_set(&ksb->rx_pending_cnt, 0); + + dbg_log_event(ksb, "PID-ATT", id->idProduct, 0); + + /*free up stale buffers if any from previous disconnect*/ + spin_lock_irqsave(&ksb->lock, flags); + while (!list_empty(&ksb->to_ks_list)) { + pkt = list_first_entry(&ksb->to_ks_list, + struct data_pkt, list); + list_del_init(&pkt->list); + ksb_free_data_pkt(pkt); + } + while (!list_empty(&ksb->to_mdm_list)) { + pkt = list_first_entry(&ksb->to_mdm_list, + struct data_pkt, list); + list_del_init(&pkt->list); + ksb_free_data_pkt(pkt); + } + spin_unlock_irqrestore(&ksb->lock, flags); + + ret = alloc_chrdev_region(&ksb->cdev_start_no, 0, 1, mdev->name); + if (ret < 0) { + dbg_log_event(ksb, "chr reg failed", ret, 0); + goto fail_chrdev_region; + } + + ksb->class = class_create(THIS_MODULE, mdev->name); + if (IS_ERR(ksb->class)) { + dbg_log_event(ksb, "clscr failed", PTR_ERR(ksb->class), 0); + goto fail_class_create; + } + + cdev_init(&ksb->cdev, &ksb_fops); + ksb->cdev.owner = THIS_MODULE; + + ret = cdev_add(&ksb->cdev, ksb->cdev_start_no, 1); + if (ret < 0) { + dbg_log_event(ksb, "cdev_add failed", ret, 0); + goto fail_class_create; + } + + ksb->device = device_create(ksb->class, &udev->dev, ksb->cdev_start_no, + NULL, mdev->name); + if (IS_ERR(ksb->device)) { + dbg_log_event(ksb, "devcrfailed", PTR_ERR(ksb->device), 0); + goto fail_device_create; + } + + if (device_can_wakeup(&ksb->udev->dev)) + ifc->needs_remote_wakeup = 1; + + if (free_mdev) + kfree(mdev); + + ks_bridge_notify_status(&ksb->device->kobj, id); + dev_dbg(&udev->dev, "usb dev connected"); + + return 0; + +fail_device_create: + cdev_del(&ksb->cdev); +fail_class_create: + unregister_chrdev_region(ksb->cdev_start_no, 1); +fail_chrdev_region: + usb_set_intfdata(ifc, NULL); + clear_bit(USB_DEV_CONNECTED, &ksb->flags); + + if (free_mdev) + kfree(mdev); + + return -ENODEV; + +} + +static int ksb_usb_suspend(struct usb_interface *ifc, pm_message_t message) +{ + struct ks_bridge *ksb = usb_get_intfdata(ifc); + unsigned long flags; + + dbg_log_event(ksb, "SUSPEND", 0, 0); + + if (pm_runtime_autosuspend_expiration(&ksb->udev->dev)) { + dbg_log_event(ksb, "SUSP ABORT-TimeCheck", 0, 0); + return -EBUSY; + } + + usb_kill_anchored_urbs(&ksb->submitted); + + spin_lock_irqsave(&ksb->lock, flags); + if (!list_empty(&ksb->to_ks_list)) { + spin_unlock_irqrestore(&ksb->lock, flags); + dbg_log_event(ksb, "SUSPEND ABORT", 0, 0); + /* + * Now wakeup the reader process and queue + * Rx URBs for more data. + */ + wake_up(&ksb->ks_wait_q); + queue_work(ksb->wq, &ksb->start_rx_work); + return -EBUSY; + } + spin_unlock_irqrestore(&ksb->lock, flags); + + return 0; +} + +static int ksb_usb_resume(struct usb_interface *ifc) +{ + struct ks_bridge *ksb = usb_get_intfdata(ifc); + + dbg_log_event(ksb, "RESUME", 0, 0); + + if (test_bit(FILE_OPENED, &ksb->flags)) + queue_work(ksb->wq, &ksb->start_rx_work); + + return 0; +} + +static void ksb_usb_disconnect(struct usb_interface *ifc) +{ + struct ks_bridge *ksb = usb_get_intfdata(ifc); + unsigned long flags; + struct data_pkt *pkt; + + dbg_log_event(ksb, "PID-DETACH", 0, 0); + + clear_bit(USB_DEV_CONNECTED, &ksb->flags); + kobject_uevent(&ksb->device->kobj, KOBJ_OFFLINE); + wake_up(&ksb->ks_wait_q); + cancel_work_sync(&ksb->to_mdm_work); + cancel_work_sync(&ksb->start_rx_work); + + device_destroy(ksb->class, ksb->cdev_start_no); + cdev_del(&ksb->cdev); + class_destroy(ksb->class); + unregister_chrdev_region(ksb->cdev_start_no, 1); + + usb_kill_anchored_urbs(&ksb->submitted); + + wait_event_interruptible_timeout( + ksb->pending_urb_wait, + !atomic_read(&ksb->tx_pending_cnt) && + !atomic_read(&ksb->rx_pending_cnt), + msecs_to_jiffies(PENDING_URB_TIMEOUT)); + + spin_lock_irqsave(&ksb->lock, flags); + while (!list_empty(&ksb->to_ks_list)) { + pkt = list_first_entry(&ksb->to_ks_list, + struct data_pkt, list); + list_del_init(&pkt->list); + ksb_free_data_pkt(pkt); + } + while (!list_empty(&ksb->to_mdm_list)) { + pkt = list_first_entry(&ksb->to_mdm_list, + struct data_pkt, list); + list_del_init(&pkt->list); + ksb_free_data_pkt(pkt); + } + spin_unlock_irqrestore(&ksb->lock, flags); + + ifc->needs_remote_wakeup = 0; + usb_put_dev(ksb->udev); + ksb->ifc = NULL; + usb_set_intfdata(ifc, NULL); +} + +static struct usb_driver ksb_usb_driver = { + .name = "ks_bridge", + .probe = ksb_usb_probe, + .disconnect = ksb_usb_disconnect, + .suspend = ksb_usb_suspend, + .resume = ksb_usb_resume, + .reset_resume = ksb_usb_resume, + .id_table = ksb_usb_ids, + .supports_autosuspend = 1, +}; + +static int ksb_debug_show(struct seq_file *s, void *unused) +{ + unsigned long flags; + struct ks_bridge *ksb = s->private; + int i; + + read_lock_irqsave(&ksb->dbg_lock, flags); + for (i = 0; i < DBG_MAX_MSG; i++) { + if (i == (ksb->dbg_idx - 1)) + seq_printf(s, "-->%s\n", ksb->dbgbuf[i]); + else + seq_printf(s, "%s\n", ksb->dbgbuf[i]); + } + read_unlock_irqrestore(&ksb->dbg_lock, flags); + + return 0; +} + +static int ksb_debug_open(struct inode *ip, struct file *fp) +{ + return single_open(fp, ksb_debug_show, ip->i_private); + + return 0; +} + +static const struct file_operations dbg_fops = { + .open = ksb_debug_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static struct dentry *dbg_dir; + +static int __init ksb_init(void) +{ + struct ks_bridge *ksb; + int num_instances = 0; + int ret = 0; + int i; + + dbg_dir = debugfs_create_dir("ks_bridge", NULL); + if (IS_ERR(dbg_dir)) + pr_err("unable to create debug dir"); + + for (i = 0; i < NO_BRIDGE_INSTANCES; i++) { + ksb = kzalloc(sizeof(struct ks_bridge), GFP_KERNEL); + if (!ksb) { + pr_err("unable to allocat mem for ks_bridge"); + ret = -ENOMEM; + goto dev_free; + } + __ksb[i] = ksb; + + ksb->name = kasprintf(GFP_KERNEL, "ks_usb_bridge.%i", i); + if (!ksb->name) { + pr_info("unable to allocate name"); + kfree(ksb); + ret = -ENOMEM; + goto dev_free; + } + + spin_lock_init(&ksb->lock); + INIT_LIST_HEAD(&ksb->to_mdm_list); + INIT_LIST_HEAD(&ksb->to_ks_list); + init_waitqueue_head(&ksb->ks_wait_q); + init_waitqueue_head(&ksb->pending_urb_wait); + ksb->wq = create_singlethread_workqueue(ksb->name); + if (!ksb->wq) { + pr_err("unable to allocate workqueue"); + kfree(ksb->name); + kfree(ksb); + ret = -ENOMEM; + goto dev_free; + } + + INIT_WORK(&ksb->to_mdm_work, ksb_tomdm_work); + INIT_WORK(&ksb->start_rx_work, ksb_start_rx_work); + init_usb_anchor(&ksb->submitted); + + ksb->dbg_idx = 0; + ksb->dbg_lock = __RW_LOCK_UNLOCKED(lck); + + if (!IS_ERR(dbg_dir)) + debugfs_create_file(ksb->name, S_IRUGO, dbg_dir, + ksb, &dbg_fops); + + num_instances++; + } + + ret = usb_register(&ksb_usb_driver); + if (ret) { + pr_err("unable to register ks bridge driver"); + goto dev_free; + } + + pr_info("init done"); + + return 0; + +dev_free: + if (!IS_ERR(dbg_dir)) + debugfs_remove_recursive(dbg_dir); + + for (i = 0; i < num_instances; i++) { + ksb = __ksb[i]; + + destroy_workqueue(ksb->wq); + kfree(ksb->name); + kfree(ksb); + } + + return ret; + +} + +static void __exit ksb_exit(void) +{ + struct ks_bridge *ksb; + int i; + + if (!IS_ERR(dbg_dir)) + debugfs_remove_recursive(dbg_dir); + + usb_deregister(&ksb_usb_driver); + + for (i = 0; i < NO_BRIDGE_INSTANCES; i++) { + ksb = __ksb[i]; + + destroy_workqueue(ksb->wq); + kfree(ksb->name); + kfree(ksb); + } +} + +module_init(ksb_init); +module_exit(ksb_exit); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/misc/lvstest.c b/drivers/usb/misc/lvstest.c index bda82e63c1a9..218b26aab667 100644 --- a/drivers/usb/misc/lvstest.c +++ b/drivers/usb/misc/lvstest.c @@ -180,6 +180,28 @@ static ssize_t hot_reset_store(struct device *dev, } static DEVICE_ATTR_WO(hot_reset); +static ssize_t warm_reset_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_device *hdev = interface_to_usbdev(intf); + struct lvs_rh *lvs = usb_get_intfdata(intf); + int port; + int ret; + + if (kstrtoint(buf, 0, &port) || port < 1 || port > 255) + port = lvs->portnum; + + ret = lvs_rh_set_port_feature(hdev, port, USB_PORT_FEAT_BH_PORT_RESET); + if (ret < 0) { + dev_err(dev, "can't issue warm reset %d\n", ret); + return ret; + } + + return count; +} +static DEVICE_ATTR_WO(warm_reset); + static ssize_t u2_timeout_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { @@ -278,13 +300,39 @@ free_desc: } static DEVICE_ATTR_WO(get_dev_desc); +static ssize_t enable_compliance_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_device *hdev = interface_to_usbdev(intf); + struct lvs_rh *lvs = usb_get_intfdata(intf); + int port; + int ret; + + if (kstrtoint(buf, 0, &port) || port < 1 || port > 255) + port = lvs->portnum; + + ret = lvs_rh_set_port_feature(hdev, + port | (USB_SS_PORT_LS_COMP_MOD << 3), + USB_PORT_FEAT_LINK_STATE); + if (ret < 0) { + dev_err(dev, "can't enable compliance mode %d\n", ret); + return ret; + } + + return count; +} +static DEVICE_ATTR_WO(enable_compliance); + static struct attribute *lvs_attributes[] = { &dev_attr_get_dev_desc.attr, &dev_attr_u1_timeout.attr, &dev_attr_u2_timeout.attr, &dev_attr_hot_reset.attr, + &dev_attr_warm_reset.attr, &dev_attr_u3_entry.attr, &dev_attr_u3_exit.attr, + &dev_attr_enable_compliance.attr, NULL }; |
