diff --git a/drivers/usb/misc/diag_ipc_bridge.c b/drivers/usb/misc/diag_ipc_bridge.c index d246b760edd3..b9ced8d0062d 100644 --- a/drivers/usb/misc/diag_ipc_bridge.c +++ b/drivers/usb/misc/diag_ipc_bridge.c @@ -26,13 +26,22 @@ #include #include #include +#include #define DRIVER_DESC "USB host diag bridge driver" #define DRIVER_VERSION "1.0" -#define MAX_DIAG_BRIDGE_DEVS 2 +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; @@ -42,6 +51,13 @@ struct diag_bridge { 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; @@ -58,13 +74,13 @@ struct diag_bridge { unsigned pending_writes; unsigned drop_count; }; -struct diag_bridge *__dev[MAX_DIAG_BRIDGE_DEVS]; +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_DIAG_BRIDGE_DEVS) { + if (id < 0 || id >= MAX_BRIDGE_DEVS) { pr_err("Invalid device ID"); return -ENODEV; } @@ -80,17 +96,25 @@ int diag_bridge_open(int id, struct diag_bridge_ops *ops) return -EALREADY; } + mutex_lock(&dev->ifc_mutex); + if (dev->opened) { + mutex_unlock(&dev->ifc_mutex); + pr_err("Bridge already opened"); + return -EBUSY; + } + + dev->opened = true; + mutex_unlock(&dev->ifc_mutex); + dev->ops = ops; dev->err = 0; - if (!id) { #ifdef CONFIG_PM_RUNTIME - dev->default_autosusp_delay = - dev->udev->dev.power.autosuspend_delay; + dev->default_autosusp_delay = + dev->udev->dev.power.autosuspend_delay; #endif - pm_runtime_set_autosuspend_delay(&dev->udev->dev, - AUTOSUSP_DELAY_WITH_USB); - } + pm_runtime_set_autosuspend_delay(&dev->udev->dev, + AUTOSUSP_DELAY_WITH_USB); kref_get(&dev->kref); @@ -98,6 +122,14 @@ int diag_bridge_open(int id, struct diag_bridge_ops *ops) } 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); @@ -112,7 +144,7 @@ void diag_bridge_close(int id) { struct diag_bridge *dev; - if (id < 0 || id >= MAX_DIAG_BRIDGE_DEVS) { + if (id < 0 || id >= MAX_BRIDGE_DEVS) { pr_err("Invalid device ID"); return; } @@ -123,26 +155,40 @@ void diag_bridge_close(int id) return; } - if (!dev->ops) { + if (id == DIAG_BRIDGE && !dev->ops) { pr_err("can't close bridge that was not open"); return; } + mutex_lock(&dev->ifc_mutex); + if (!dev->opened) { + mutex_unlock(&dev->ifc_mutex); + pr_err("Bridge not opened"); + 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; - - if (!id) { - pm_runtime_set_autosuspend_delay(&dev->udev->dev, - dev->default_autosusp_delay); - } + 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; @@ -155,11 +201,21 @@ static void diag_bridge_read_cb(struct urb *urb) if (urb->status == -EPROTO) dev->err = urb->status; - if (cbs && cbs->read_complete_cb) + 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--; @@ -173,7 +229,7 @@ int diag_bridge_read(int id, char *data, int size) struct diag_bridge *dev; int ret; - if (id < 0 || id >= MAX_DIAG_BRIDGE_DEVS) { + if (id < 0 || id >= MAX_BRIDGE_DEVS) { pr_err("Invalid device ID"); return -ENODEV; } @@ -186,13 +242,13 @@ int diag_bridge_read(int id, char *data, int size) return -ENODEV; } - mutex_lock(&dev->ifc_mutex); + mutex_lock(&dev->read_mutex); if (!dev->ifc) { ret = -ENODEV; goto error; } - if (!dev->ops) { + if (id == DIAG_BRIDGE && !dev->ops) { pr_err("bridge is not open"); ret = -ENODEV; goto error; @@ -206,7 +262,7 @@ int diag_bridge_read(int id, char *data, int size) } /* if there was a previous unrecoverable error, just quit */ - if (dev->err) { + if (id == DIAG_BRIDGE && dev->err) { ret = -ENODEV; goto error; } @@ -245,19 +301,40 @@ int diag_bridge_read(int id, char *data, int size) dev->pending_reads--; usb_unanchor_urb(urb); } + + if (id == IPC_BRIDGE) { + wait_for_completion(&dev->read_done); + ret = dev->read_result; + } + usb_autopm_put_interface(dev->ifc); free_error: usb_free_urb(urb); put_error: - if (ret) /* otherwise this is done in the completion handler */ + if (ret < 0) /* otherwise this is done in the completion handler */ kref_put(&dev->kref, diag_bridge_delete); error: - mutex_unlock(&dev->ifc_mutex); + 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; @@ -271,11 +348,21 @@ static void diag_bridge_write_cb(struct urb *urb) if (urb->status == -EPROTO) dev->err = urb->status; - if (cbs && cbs->write_complete_cb) + 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--; @@ -289,7 +376,7 @@ int diag_bridge_write(int id, char *data, int size) struct diag_bridge *dev; int ret; - if (id < 0 || id >= MAX_DIAG_BRIDGE_DEVS) { + if (id < 0 || id >= MAX_BRIDGE_DEVS) { pr_err("Invalid device ID"); return -ENODEV; } @@ -302,13 +389,13 @@ int diag_bridge_write(int id, char *data, int size) return -ENODEV; } - mutex_lock(&dev->ifc_mutex); + mutex_lock(&dev->write_mutex); if (!dev->ifc) { ret = -ENODEV; goto error; } - if (!dev->ops) { + if (id == DIAG_BRIDGE && !dev->ops) { pr_err("bridge is not open"); ret = -ENODEV; goto error; @@ -321,7 +408,7 @@ int diag_bridge_write(int id, char *data, int size) } /* if there was a previous unrecoverable error, just quit */ - if (dev->err) { + if (id == DIAG_BRIDGE && dev->err) { ret = -ENODEV; goto error; } @@ -357,17 +444,35 @@ int diag_bridge_write(int id, char *data, int size) goto free_error; } + if (id == IPC_BRIDGE) { + wait_for_completion(&dev->write_done); + ret = dev->write_result; + } + free_error: usb_free_urb(urb); put_error: - if (ret) /* otherwise this is done in the completion handler */ + if (ret < 0) /* otherwise this is done in the completion handler */ kref_put(&dev->kref, diag_bridge_delete); error: - mutex_unlock(&dev->ifc_mutex); + 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, @@ -380,7 +485,7 @@ static ssize_t diag_read_stats(struct file *file, char __user *ubuf, if (!buf) return -ENOMEM; - for (i = 0; i < MAX_DIAG_BRIDGE_DEVS; i++) { + for (i = 0; i < MAX_BRIDGE_DEVS; i++) { struct diag_bridge *dev = __dev[i]; if (!dev) @@ -411,7 +516,7 @@ static ssize_t diag_reset_stats(struct file *file, const char __user *buf, { int i; - for (i = 0; i < MAX_DIAG_BRIDGE_DEVS; i++) { + for (i = 0; i < MAX_BRIDGE_DEVS; i++) { struct diag_bridge *dev = __dev[i]; if (dev) { @@ -454,6 +559,15 @@ 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) { @@ -465,7 +579,7 @@ diag_bridge_probe(struct usb_interface *ifc, const struct usb_device_id *id) pr_debug("id:%lu", id->driver_info); devid = id->driver_info & 0xFF; - if (devid < 0 || devid >= MAX_DIAG_BRIDGE_DEVS) + if (devid < 0 || devid >= MAX_BRIDGE_DEVS) return -ENODEV; /* already probed? */ @@ -485,6 +599,10 @@ diag_bridge_probe(struct usb_interface *ifc, const struct usb_device_id *id) dev->ifc = 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; @@ -510,19 +628,47 @@ diag_bridge_probe(struct usb_interface *ifc, const struct usb_device_id *id) usb_set_intfdata(ifc, dev); diag_bridge_debugfs_init(); - dev->pdev = platform_device_register_simple("diag_bridge", devid, - NULL, 0); - if (IS_ERR(dev->pdev)) { - pr_err("unable to allocate platform device"); - ret = PTR_ERR(dev->pdev); - goto error; + if (devid == DIAG_BRIDGE) { + dev->pdev = platform_device_register_simple("diag_bridge", + devid, NULL, 0); + if (IS_ERR(dev->pdev)) { + pr_err("unable to allocate platform device"); + ret = PTR_ERR(dev->pdev); + goto error; + } + } else { + dev->pdev = platform_device_alloc("ipc_bridge", -1); + if (!dev->pdev) { + pr_err("unable to allocate platform device"); + ret = -ENOMEM; + goto error; + } + + ret = platform_device_add_data(dev->pdev, &ipc_bridge_pdata, + sizeof(struct ipc_bridge_platform_data)); + if (ret) { + pr_err("fail to add pdata"); + goto put_pdev; + } + + ret = platform_device_add(dev->pdev); + if (ret) { + pr_err("fail to add pdev"); + 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); @@ -536,12 +682,15 @@ static void diag_bridge_disconnect(struct usb_interface *ifc) dev_dbg(&dev->ifc->dev, "%s:\n", __func__); platform_device_unregister(dev->pdev); + diag_bridge_debugfs_cleanup(); mutex_lock(&dev->ifc_mutex); dev->ifc = NULL; mutex_unlock(&dev->ifc_mutex); - diag_bridge_debugfs_cleanup(); - kref_put(&dev->kref, diag_bridge_delete); usb_set_intfdata(ifc, NULL); + mutex_destroy(&dev->write_mutex); + mutex_destroy(&dev->read_mutex); + mutex_destroy(&dev->ifc_mutex); + kref_put(&dev->kref, diag_bridge_delete); } static int diag_bridge_suspend(struct usb_interface *ifc, pm_message_t message) @@ -557,10 +706,10 @@ static int diag_bridge_suspend(struct usb_interface *ifc, pm_message_t message) "%s: diag veto'd suspend\n", __func__); return ret; } - - usb_kill_anchored_urbs(&dev->submitted); } + usb_kill_anchored_urbs(&dev->submitted); + return ret; } diff --git a/include/linux/usb/ipc_bridge.h b/include/linux/usb/ipc_bridge.h new file mode 100644 index 000000000000..a0e12d6f9af5 --- /dev/null +++ b/include/linux/usb/ipc_bridge.h @@ -0,0 +1,65 @@ +/* Copyright (c) 2013 The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __MSM_IPC_BRIDGE_H__ +#define __MSM_IPC_BRIDGE_H__ + +#include + +/* + * The IPC bridge driver adds a IPC bridge platform device when the + * underlying transport is ready. The IPC transport driver acts as a + * platform driver for this device. The platform data is populated by + * IPC bridge driver to facilitate I/O. The callback functions are + * passed in platform data to avoid export functions. This would allow + * different bridge drivers to exist in the kernel. The IPC bridge driver + * removes the platform device when the underly transport is no longer + * available. It typically happens during shutdown and remote processor's + * subsystem restart. + */ + +/** + * struct ipc_bridge_platform_data - platform device data for IPC + * transport driver. + * @max_read_size: The maximum possible read size. + * @max_write_size: The maximum possible write size. + * @open: The open must be called before starting I/O. The IPC bridge + * driver use the platform device pointer to identify the + * underlying transport channel. The IPC bridge driver may + * notify that remote processor that it is ready to receive + * data. Returns 0 upon success and appropriate error code + * upon failure. + * @read: The read is done synchronously and should be called from process + * context. Returns the number of bytes read from remote + * processor or error code upon failure. The IPC transport + * driver may pass the buffer of max_read_size length if the + * available data size is not known in advance. + * @write: The write is done synchronously and should be called from process + * context. The IPC bridge driver uses the same buffer for DMA + * to avoid additional memcpy. So it must be physically contiguous. + * Returns the number of bytes written or error code upon failure. + * @close: The close must be called when the IPC bridge platform device + * is removed. The IPC transport driver may call close when + * it is no longer required to communicate with remote processor. + */ +struct ipc_bridge_platform_data { + unsigned int max_read_size; + unsigned int max_write_size; + int (*open)(struct platform_device *pdev); + int (*read)(struct platform_device *pdev, char *buf, + unsigned int count); + int (*write)(struct platform_device *pdev, char *buf, + unsigned int count); + void (*close)(struct platform_device *pdev); +}; + +#endif