Merge "usb: misc: diag_ipc_bridge: Add support for QMI messages over BULK"

This commit is contained in:
Linux Build Service Account 2018-07-02 06:08:28 -07:00 committed by Gerrit - the friendly Code Review server
commit 1e208d258e
2 changed files with 257 additions and 43 deletions

View file

@ -26,13 +26,22 @@
#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"
#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;
}

View file

@ -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 <linux/platform_device.h>
/*
* 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