mirror of
https://github.com/rd-stuffs/msm-4.14.git
synced 2025-02-20 11:45:48 +08:00
Rename EVT_WAKE_UP to EVENT_REQUEST_WAKE_UP to clearly expalin that this notification is to request to wake up the remote processor. Change-Id: Icc829f3b6c6d8b0cfc2e0ff189a0152759bb6415 Signed-off-by: Shreyas K K <shrekk@codeaurora.org>
885 lines
22 KiB
C
885 lines
22 KiB
C
/*
|
|
* Copyright (c) 2018-2020, 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/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/platform_device.h>
|
|
|
|
#include <linux/usb/composite.h>
|
|
#include <linux/usb/gadget.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/usb/ipc_bridge.h>
|
|
#include <soc/qcom/sb_notification.h>
|
|
|
|
#define MAX_INST_NAME_LEN 40
|
|
|
|
#define IPC_BRIDGE_MAX_READ_SZ (24 * 1024)
|
|
#define IPC_BRIDGE_MAX_WRITE_SZ (24 * 1024)
|
|
|
|
#define IPC_WRITE_WAIT_TIMEOUT 10000
|
|
|
|
/* for configfs support */
|
|
struct ipc_opts {
|
|
struct usb_function_instance func_inst;
|
|
struct ipc_context *ctxt;
|
|
};
|
|
|
|
static inline struct ipc_opts *to_ipc_opts(struct config_item *item)
|
|
{
|
|
return container_of(to_config_group(item), struct ipc_opts,
|
|
func_inst.group);
|
|
}
|
|
|
|
static struct usb_interface_descriptor intf_desc = {
|
|
.bLength = sizeof(intf_desc),
|
|
.bDescriptorType = USB_DT_INTERFACE,
|
|
.bNumEndpoints = 2,
|
|
.bInterfaceClass = USB_CLASS_VENDOR_SPEC,
|
|
.bInterfaceSubClass = USB_SUBCLASS_VENDOR_SPEC,
|
|
.bInterfaceProtocol = 0x90,
|
|
};
|
|
|
|
static struct usb_endpoint_descriptor hs_bulk_in_desc = {
|
|
.bLength = USB_DT_ENDPOINT_SIZE,
|
|
.bDescriptorType = USB_DT_ENDPOINT,
|
|
.bEndpointAddress = USB_DIR_IN,
|
|
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
|
.wMaxPacketSize = cpu_to_le16(512),
|
|
.bInterval = 0,
|
|
};
|
|
static struct usb_endpoint_descriptor fs_bulk_in_desc = {
|
|
.bLength = USB_DT_ENDPOINT_SIZE,
|
|
.bDescriptorType = USB_DT_ENDPOINT,
|
|
.bEndpointAddress = USB_DIR_IN,
|
|
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
|
.wMaxPacketSize = cpu_to_le16(64),
|
|
.bInterval = 0,
|
|
};
|
|
|
|
static struct usb_endpoint_descriptor hs_bulk_out_desc = {
|
|
.bLength = USB_DT_ENDPOINT_SIZE,
|
|
.bDescriptorType = USB_DT_ENDPOINT,
|
|
.bEndpointAddress = USB_DIR_OUT,
|
|
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
|
.wMaxPacketSize = cpu_to_le16(512),
|
|
.bInterval = 0,
|
|
};
|
|
|
|
static struct usb_endpoint_descriptor fs_bulk_out_desc = {
|
|
.bLength = USB_DT_ENDPOINT_SIZE,
|
|
.bDescriptorType = USB_DT_ENDPOINT,
|
|
.bEndpointAddress = USB_DIR_OUT,
|
|
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
|
.wMaxPacketSize = cpu_to_le16(64),
|
|
.bInterval = 0,
|
|
};
|
|
|
|
static struct usb_endpoint_descriptor ss_bulk_in_desc = {
|
|
.bLength = USB_DT_ENDPOINT_SIZE,
|
|
.bDescriptorType = USB_DT_ENDPOINT,
|
|
.bEndpointAddress = USB_DIR_IN,
|
|
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
|
.wMaxPacketSize = cpu_to_le16(1024),
|
|
};
|
|
|
|
static struct usb_ss_ep_comp_descriptor ss_bulk_in_comp_desc = {
|
|
.bLength = sizeof(ss_bulk_in_comp_desc),
|
|
.bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
|
|
|
|
/* the following 2 values can be tweaked if necessary */
|
|
/* .bMaxBurst = 0, */
|
|
/* .bmAttributes = 0, */
|
|
};
|
|
|
|
static struct usb_endpoint_descriptor ss_bulk_out_desc = {
|
|
.bLength = USB_DT_ENDPOINT_SIZE,
|
|
.bDescriptorType = USB_DT_ENDPOINT,
|
|
.bEndpointAddress = USB_DIR_OUT,
|
|
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
|
.wMaxPacketSize = cpu_to_le16(1024),
|
|
};
|
|
|
|
static struct usb_ss_ep_comp_descriptor ss_bulk_out_comp_desc = {
|
|
.bLength = sizeof(ss_bulk_out_comp_desc),
|
|
.bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
|
|
|
|
/* the following 2 values can be tweaked if necessary */
|
|
/* .bMaxBurst = 0, */
|
|
/* .bmAttributes = 0, */
|
|
};
|
|
|
|
static struct usb_descriptor_header *fs_ipc_desc[] = {
|
|
(struct usb_descriptor_header *) &intf_desc,
|
|
(struct usb_descriptor_header *) &fs_bulk_in_desc,
|
|
(struct usb_descriptor_header *) &fs_bulk_out_desc,
|
|
NULL,
|
|
};
|
|
|
|
static struct usb_descriptor_header *hs_ipc_desc[] = {
|
|
(struct usb_descriptor_header *) &intf_desc,
|
|
(struct usb_descriptor_header *) &hs_bulk_in_desc,
|
|
(struct usb_descriptor_header *) &hs_bulk_out_desc,
|
|
NULL,
|
|
};
|
|
|
|
static struct usb_descriptor_header *ss_ipc_desc[] = {
|
|
(struct usb_descriptor_header *) &intf_desc,
|
|
(struct usb_descriptor_header *) &ss_bulk_in_desc,
|
|
(struct usb_descriptor_header *) &ss_bulk_in_comp_desc,
|
|
(struct usb_descriptor_header *) &ss_bulk_out_desc,
|
|
(struct usb_descriptor_header *) &ss_bulk_out_comp_desc,
|
|
NULL,
|
|
};
|
|
|
|
/* String descriptors */
|
|
|
|
static struct usb_string ipc_string_defs[] = {
|
|
[0].s = "IPC",
|
|
{ } /* end of list */
|
|
};
|
|
|
|
static struct usb_gadget_strings ipc_string_table = {
|
|
.language = 0x0409, /* en-us */
|
|
.strings = ipc_string_defs,
|
|
};
|
|
|
|
static struct usb_gadget_strings *ipc_strings[] = {
|
|
&ipc_string_table,
|
|
NULL,
|
|
};
|
|
|
|
enum current_state_type {
|
|
IPC_DISCONNECTED,
|
|
IPC_CONNECTED,
|
|
};
|
|
|
|
/*
|
|
* struct ipc_context - USB IPC router function driver private structure
|
|
* @function: function structure for USB interface
|
|
* @out: USB OUT endpoint struct
|
|
* @in: USB IN endpoint struct
|
|
* @in_req: USB IN endpoint request
|
|
* @out_req: USB OUT endpoint request
|
|
* @lock: Spinlock to protect structure members
|
|
* @state_wq: Waitqueue to wait on online and disconnected states
|
|
* @read_done: Denote OUT endpoint request completion
|
|
* @write_done: Denote IN endpoint request completion
|
|
* @online: If true, function is ready to send and receive data
|
|
* @connected: If true, set_alt issued by the host
|
|
* @opened: If true, IPC router platform device has opened this route
|
|
* @cdev: USB composite device struct
|
|
* @pdev: Platform device to register with IPC router
|
|
* @func_work: Work item to register pdev with IPC router and update states
|
|
* @current_state: Current status of the interface
|
|
*/
|
|
struct ipc_context {
|
|
struct usb_function function;
|
|
struct usb_ep *out;
|
|
struct usb_ep *in;
|
|
struct usb_request *in_req;
|
|
struct usb_request *out_req;
|
|
spinlock_t lock;
|
|
wait_queue_head_t state_wq;
|
|
struct completion read_done;
|
|
struct completion write_done;
|
|
unsigned int online;
|
|
unsigned int connected;
|
|
bool opened;
|
|
struct usb_composite_dev *cdev;
|
|
struct platform_device *pdev;
|
|
struct work_struct func_work;
|
|
enum current_state_type current_state;
|
|
|
|
/* pkt counters */
|
|
unsigned long bytes_to_host;
|
|
unsigned long bytes_to_mdm;
|
|
unsigned int pending_writes;
|
|
unsigned int pending_reads;
|
|
};
|
|
|
|
static struct ipc_context *ipc_dev;
|
|
|
|
static inline struct ipc_context *func_to_ipc(struct usb_function *f)
|
|
{
|
|
return container_of(f, struct ipc_context, function);
|
|
}
|
|
|
|
static void ipc_in_complete(struct usb_ep *ep, struct usb_request *req)
|
|
{
|
|
complete(&ipc_dev->write_done);
|
|
ipc_dev->bytes_to_host += req->actual;
|
|
ipc_dev->pending_writes--;
|
|
}
|
|
|
|
/*
|
|
* ipc_write() - Write IPC data from IPC router
|
|
* @pdev: IPC router USB transport platform device
|
|
* @buf: Data buffer from IPC core
|
|
* @count: Data buffer size
|
|
*
|
|
* Enqueue a request on IN endpoint of the interface corresponding to this
|
|
* channel. This function returns proper error code if the interface or data
|
|
* buffer is not configured properly. If ep_queue fails because interface is
|
|
* suspended, then it waits for interface to be online or get disconnected.
|
|
*
|
|
* This function operates asynchronously. WRITE_DONE event is notified after
|
|
* completion of IN request.
|
|
*/
|
|
static int ipc_write(struct platform_device *pdev, char *buf,
|
|
unsigned int count)
|
|
{
|
|
unsigned long flags;
|
|
struct usb_request *req;
|
|
struct usb_ep *in;
|
|
int ret;
|
|
|
|
if (!ipc_dev)
|
|
return -ENODEV;
|
|
if (ipc_dev->pdev != pdev)
|
|
return -EINVAL;
|
|
if (!ipc_dev->opened)
|
|
return -EPERM;
|
|
if (count > IPC_BRIDGE_MAX_WRITE_SZ)
|
|
return -ENOSPC;
|
|
|
|
spin_lock_irqsave(&ipc_dev->lock, flags);
|
|
in = ipc_dev->in;
|
|
req = ipc_dev->in_req;
|
|
req->buf = buf;
|
|
req->length = count;
|
|
ipc_dev->pending_writes++;
|
|
spin_unlock_irqrestore(&ipc_dev->lock, flags);
|
|
|
|
retry_write:
|
|
if (ipc_dev->current_state == IPC_DISCONNECTED) {
|
|
pr_err("%s: Interface disconnected, cannot queue req\n",
|
|
__func__);
|
|
ipc_dev->pending_writes--;
|
|
return -EINVAL;
|
|
}
|
|
|
|
reinit_completion(&ipc_dev->write_done);
|
|
|
|
if (usb_ep_queue(in, req, GFP_KERNEL)) {
|
|
/* Notify GPIO driver to wakup the host if host
|
|
* is in suspend mode.
|
|
*/
|
|
sb_notifier_call_chain(EVENT_REQUEST_WAKE_UP, NULL);
|
|
wait_event_interruptible(ipc_dev->state_wq, ipc_dev->online ||
|
|
ipc_dev->current_state == IPC_DISCONNECTED);
|
|
pr_debug("%s: Interface ready, Retry IN request\n", __func__);
|
|
goto retry_write;
|
|
}
|
|
|
|
retry_write_done:
|
|
ret = wait_for_completion_interruptible_timeout(&ipc_dev->write_done,
|
|
msecs_to_jiffies(IPC_WRITE_WAIT_TIMEOUT));
|
|
if (ret < 0) {
|
|
pr_err("%s: Interruption triggered\n", __func__);
|
|
ret = -EINTR;
|
|
goto fail;
|
|
} else if (ret == 0 && ipc_dev->online) {
|
|
pr_err("%s: Request timed out\n", __func__);
|
|
ret = -ETIMEDOUT;
|
|
goto fail;
|
|
/* Notify the GPIO driver to wakeup the host and reintialize the
|
|
* completion structure.
|
|
*/
|
|
} else if (ipc_dev->connected && !ipc_dev->online) {
|
|
sb_notifier_call_chain(EVENT_REQUEST_WAKE_UP, NULL);
|
|
reinit_completion(&ipc_dev->write_done);
|
|
goto retry_write_done;
|
|
}
|
|
|
|
return !req->status ? req->actual : req->status;
|
|
|
|
fail:
|
|
usb_ep_dequeue(in, req);
|
|
return ret;
|
|
}
|
|
|
|
static void ipc_out_complete(struct usb_ep *ep, struct usb_request *req)
|
|
{
|
|
complete(&ipc_dev->read_done);
|
|
ipc_dev->bytes_to_mdm += req->actual;
|
|
ipc_dev->pending_reads--;
|
|
}
|
|
|
|
/*
|
|
* ipc_read() - Read IPC data from USB
|
|
* @pdev: IPC router USB transport platform device
|
|
* @buf: Data buffer to be populated
|
|
* @count: Data buffer size
|
|
*
|
|
* Enqueue a request on OUT endpoint of the interface corresponding to this
|
|
* channel. This function returns proper error code if the interface or data
|
|
* buffer is not configured properly. If ep_queue fails because interface is
|
|
* suspended, then it waits for interface to be online or get disconnected.
|
|
*
|
|
* This function operates asynchronously. READ_DONE event is notified after
|
|
* completion of OUT request.
|
|
*/
|
|
static int ipc_read(struct platform_device *pdev, char *buf, unsigned int count)
|
|
{
|
|
unsigned long flags;
|
|
struct usb_request *req;
|
|
struct usb_ep *out;
|
|
|
|
if (!ipc_dev)
|
|
return -ENODEV;
|
|
if (ipc_dev->pdev != pdev)
|
|
return -EINVAL;
|
|
if (!ipc_dev->opened)
|
|
return -EPERM;
|
|
if (count > IPC_BRIDGE_MAX_READ_SZ)
|
|
return -ENOSPC;
|
|
|
|
spin_lock_irqsave(&ipc_dev->lock, flags);
|
|
out = ipc_dev->out;
|
|
req = ipc_dev->out_req;
|
|
req->buf = buf;
|
|
req->length = count;
|
|
ipc_dev->pending_reads++;
|
|
spin_unlock_irqrestore(&ipc_dev->lock, flags);
|
|
|
|
retry_read:
|
|
if (ipc_dev->current_state == IPC_DISCONNECTED) {
|
|
pr_err("%s: Interface disconnected, cannot queue req\n",
|
|
__func__);
|
|
ipc_dev->pending_reads--;
|
|
return -EINVAL;
|
|
}
|
|
|
|
reinit_completion(&ipc_dev->read_done);
|
|
|
|
if (usb_ep_queue(out, req, GFP_KERNEL)) {
|
|
wait_event_interruptible(ipc_dev->state_wq, ipc_dev->online ||
|
|
ipc_dev->current_state == IPC_DISCONNECTED);
|
|
pr_debug("%s: Interface ready, Retry OUT request\n", __func__);
|
|
goto retry_read;
|
|
}
|
|
|
|
if (unlikely(wait_for_completion_interruptible(&ipc_dev->read_done))) {
|
|
usb_ep_dequeue(out, req);
|
|
return -EINTR;
|
|
}
|
|
|
|
return !req->status ? req->actual : req->status;
|
|
}
|
|
|
|
static int ipc_open(struct platform_device *pdev)
|
|
{
|
|
unsigned long flags;
|
|
|
|
if (ipc_dev->pdev != pdev)
|
|
return -EINVAL;
|
|
|
|
pr_debug("%s: Trying to open IPC bridge\n", __func__);
|
|
spin_lock_irqsave(&ipc_dev->lock, flags);
|
|
if (ipc_dev->opened) {
|
|
spin_unlock_irqrestore(&ipc_dev->lock, flags);
|
|
pr_err("%s: Bridge already opened\n", __func__);
|
|
return -EBUSY;
|
|
}
|
|
|
|
ipc_dev->opened = true;
|
|
spin_unlock_irqrestore(&ipc_dev->lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ipc_close(struct platform_device *pdev)
|
|
{
|
|
unsigned long flags;
|
|
|
|
WARN_ON(ipc_dev->pdev != pdev);
|
|
|
|
pr_debug("%s: Trying to close IPC bridge\n", __func__);
|
|
spin_lock_irqsave(&ipc_dev->lock, flags);
|
|
if (!ipc_dev->opened) {
|
|
spin_unlock_irqrestore(&ipc_dev->lock, flags);
|
|
pr_err("%s: Bridge already closed\n", __func__);
|
|
return;
|
|
}
|
|
|
|
ipc_dev->opened = false;
|
|
spin_unlock_irqrestore(&ipc_dev->lock, flags);
|
|
}
|
|
|
|
static const struct ipc_bridge_platform_data ipc_pdata = {
|
|
.max_read_size = IPC_BRIDGE_MAX_READ_SZ,
|
|
.max_write_size = IPC_BRIDGE_MAX_WRITE_SZ,
|
|
.open = ipc_open,
|
|
.read = ipc_read,
|
|
.write = ipc_write,
|
|
.close = ipc_close,
|
|
};
|
|
|
|
static void ipc_function_work(struct work_struct *w)
|
|
{
|
|
struct ipc_context *ctxt = container_of(w, struct ipc_context,
|
|
func_work);
|
|
int ret;
|
|
|
|
switch (ctxt->current_state) {
|
|
case IPC_DISCONNECTED:
|
|
if (!ctxt->connected)
|
|
break;
|
|
|
|
ctxt->current_state = IPC_CONNECTED;
|
|
ctxt->pdev = platform_device_alloc("ipc_bridge", -1);
|
|
if (!ctxt->pdev)
|
|
goto pdev_fail;
|
|
|
|
ret = platform_device_add_data(ctxt->pdev, &ipc_pdata,
|
|
sizeof(struct ipc_bridge_platform_data));
|
|
if (ret) {
|
|
platform_device_put(ctxt->pdev);
|
|
pr_err("%s: fail to add pdata\n", __func__);
|
|
goto pdev_fail;
|
|
}
|
|
|
|
ret = platform_device_add(ctxt->pdev);
|
|
if (ret) {
|
|
platform_device_put(ctxt->pdev);
|
|
pr_err("%s: fail to add pdev\n", __func__);
|
|
goto pdev_fail;
|
|
}
|
|
break;
|
|
case IPC_CONNECTED:
|
|
if (ctxt->connected)
|
|
break;
|
|
|
|
ctxt->current_state = IPC_DISCONNECTED;
|
|
wake_up(&ctxt->state_wq);
|
|
platform_device_unregister(ctxt->pdev);
|
|
break;
|
|
default:
|
|
pr_debug("%s: Unknown current state\n", __func__);
|
|
}
|
|
|
|
return;
|
|
|
|
pdev_fail:
|
|
ctxt->current_state = IPC_DISCONNECTED;
|
|
}
|
|
|
|
static int ipc_bind(struct usb_configuration *c, struct usb_function *f)
|
|
{
|
|
struct usb_composite_dev *cdev = c->cdev;
|
|
struct ipc_context *ctxt = func_to_ipc(f);
|
|
struct usb_ep *ep;
|
|
int status;
|
|
|
|
pr_debug("%s: start binding\n", __func__);
|
|
ctxt->cdev = c->cdev;
|
|
|
|
if (ipc_string_defs[0].id == 0) {
|
|
status = usb_string_id(cdev);
|
|
if (status < 0)
|
|
return status;
|
|
ipc_string_defs[0].id = status;
|
|
}
|
|
|
|
intf_desc.bInterfaceNumber = usb_interface_id(c, f);
|
|
|
|
status = -ENODEV;
|
|
ep = usb_ep_autoconfig(cdev->gadget, &fs_bulk_in_desc);
|
|
if (!ep)
|
|
goto fail;
|
|
ctxt->in = ep;
|
|
ep->driver_data = ctxt;
|
|
|
|
ep = usb_ep_autoconfig(cdev->gadget, &fs_bulk_out_desc);
|
|
if (!ep)
|
|
goto fail;
|
|
ctxt->out = ep;
|
|
ep->driver_data = ctxt;
|
|
|
|
status = -ENOMEM;
|
|
ctxt->in_req = usb_ep_alloc_request(ctxt->in, GFP_KERNEL);
|
|
if (!ctxt->in_req)
|
|
goto fail;
|
|
|
|
ctxt->in_req->complete = ipc_in_complete;
|
|
ctxt->out_req = usb_ep_alloc_request(ctxt->out, GFP_KERNEL);
|
|
if (!ctxt->out_req)
|
|
goto fail;
|
|
|
|
ctxt->out_req->complete = ipc_out_complete;
|
|
/* copy descriptors, and track endpoint copies */
|
|
f->fs_descriptors = usb_copy_descriptors(fs_ipc_desc);
|
|
if (!f->fs_descriptors)
|
|
goto fail;
|
|
|
|
if (gadget_is_dualspeed(c->cdev->gadget)) {
|
|
hs_bulk_in_desc.bEndpointAddress =
|
|
fs_bulk_in_desc.bEndpointAddress;
|
|
hs_bulk_out_desc.bEndpointAddress =
|
|
fs_bulk_out_desc.bEndpointAddress;
|
|
|
|
/* copy descriptors, and track endpoint copies */
|
|
f->hs_descriptors = usb_copy_descriptors(hs_ipc_desc);
|
|
if (!f->hs_descriptors)
|
|
goto fail;
|
|
}
|
|
|
|
if (gadget_is_superspeed(c->cdev->gadget)) {
|
|
ss_bulk_in_desc.bEndpointAddress =
|
|
fs_bulk_in_desc.bEndpointAddress;
|
|
ss_bulk_out_desc.bEndpointAddress =
|
|
fs_bulk_out_desc.bEndpointAddress;
|
|
|
|
/* copy descriptors, and track endpoint copies */
|
|
f->ss_descriptors = usb_copy_descriptors(ss_ipc_desc);
|
|
if (!f->ss_descriptors)
|
|
goto fail;
|
|
}
|
|
|
|
return 0;
|
|
fail:
|
|
if (f->hs_descriptors)
|
|
usb_free_descriptors(f->hs_descriptors);
|
|
if (f->fs_descriptors)
|
|
usb_free_descriptors(f->fs_descriptors);
|
|
if (ctxt->out_req)
|
|
usb_ep_free_request(ctxt->out, ctxt->out_req);
|
|
if (ctxt->in_req)
|
|
usb_ep_free_request(ctxt->in, ctxt->in_req);
|
|
if (ctxt->out)
|
|
ctxt->out->driver_data = NULL;
|
|
if (ctxt->in)
|
|
ctxt->in->driver_data = NULL;
|
|
|
|
pr_err("%s: can't bind, err %d\n", __func__, status);
|
|
return status;
|
|
}
|
|
|
|
static void ipc_unbind(struct usb_configuration *c, struct usb_function *f)
|
|
{
|
|
struct ipc_context *ctxt = func_to_ipc(f);
|
|
|
|
pr_debug("%s: start unbinding\nclear_desc\n", __func__);
|
|
if (gadget_is_superspeed(c->cdev->gadget))
|
|
usb_free_descriptors(f->ss_descriptors);
|
|
if (gadget_is_dualspeed(c->cdev->gadget))
|
|
usb_free_descriptors(f->hs_descriptors);
|
|
|
|
usb_free_descriptors(f->fs_descriptors);
|
|
|
|
if (ctxt->out_req)
|
|
usb_ep_free_request(ctxt->out, ctxt->out_req);
|
|
if (ctxt->in_req)
|
|
usb_ep_free_request(ctxt->in, ctxt->in_req);
|
|
}
|
|
|
|
static int ipc_set_alt(struct usb_function *f, unsigned int intf,
|
|
unsigned int alt)
|
|
{
|
|
struct ipc_context *ctxt = func_to_ipc(f);
|
|
struct usb_composite_dev *cdev = f->config->cdev;
|
|
unsigned long flags;
|
|
int rc = 0;
|
|
|
|
pr_debug("%s: ipc_dev: %pK\n", __func__, ctxt);
|
|
if (config_ep_by_speed(cdev->gadget, f, ctxt->in) ||
|
|
config_ep_by_speed(cdev->gadget, f, ctxt->out)) {
|
|
ctxt->in->desc = NULL;
|
|
ctxt->out->desc = NULL;
|
|
return -EINVAL;
|
|
}
|
|
|
|
ctxt->in->driver_data = ctxt;
|
|
rc = usb_ep_enable(ctxt->in);
|
|
if (rc) {
|
|
ERROR(ctxt->cdev, "can't enable %s, result %d\n",
|
|
ctxt->in->name, rc);
|
|
return rc;
|
|
}
|
|
|
|
ctxt->out->driver_data = ctxt;
|
|
rc = usb_ep_enable(ctxt->out);
|
|
if (rc) {
|
|
ERROR(ctxt->cdev, "can't enable %s, result %d\n",
|
|
ctxt->out->name, rc);
|
|
usb_ep_disable(ctxt->in);
|
|
return rc;
|
|
}
|
|
|
|
spin_lock_irqsave(&ctxt->lock, flags);
|
|
ctxt->connected = 1;
|
|
ctxt->online = 1;
|
|
spin_unlock_irqrestore(&ctxt->lock, flags);
|
|
schedule_work(&ctxt->func_work);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void ipc_disable(struct usb_function *f)
|
|
{
|
|
struct ipc_context *ctxt = func_to_ipc(f);
|
|
unsigned long flags;
|
|
|
|
pr_debug("%s: Disabling\n", __func__);
|
|
spin_lock_irqsave(&ctxt->lock, flags);
|
|
ctxt->online = 0;
|
|
ctxt->connected = 0;
|
|
spin_unlock_irqrestore(&ctxt->lock, flags);
|
|
schedule_work(&ctxt->func_work);
|
|
|
|
usb_ep_disable(ctxt->in);
|
|
ctxt->in->driver_data = NULL;
|
|
|
|
usb_ep_disable(ctxt->out);
|
|
ctxt->out->driver_data = NULL;
|
|
}
|
|
|
|
static void ipc_suspend(struct usb_function *f)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&ipc_dev->lock, flags);
|
|
ipc_dev->online = 0;
|
|
spin_unlock_irqrestore(&ipc_dev->lock, flags);
|
|
}
|
|
|
|
static void ipc_resume(struct usb_function *f)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&ipc_dev->lock, flags);
|
|
ipc_dev->online = 1;
|
|
spin_unlock_irqrestore(&ipc_dev->lock, flags);
|
|
wake_up(&ipc_dev->state_wq);
|
|
}
|
|
|
|
static void ipc_free(struct usb_function *f) {}
|
|
|
|
static struct usb_function *ipc_bind_config(struct usb_function_instance *fi)
|
|
{
|
|
struct ipc_opts *opts;
|
|
struct ipc_context *ctxt;
|
|
struct usb_function *f;
|
|
|
|
opts = container_of(fi, struct ipc_opts, func_inst);
|
|
ctxt = opts->ctxt;
|
|
|
|
f = &ctxt->function;
|
|
f->name = "ipc";
|
|
f->strings = ipc_strings;
|
|
f->bind = ipc_bind;
|
|
f->unbind = ipc_unbind;
|
|
f->set_alt = ipc_set_alt;
|
|
f->disable = ipc_disable;
|
|
f->suspend = ipc_suspend;
|
|
f->resume = ipc_resume;
|
|
f->free_func = ipc_free;
|
|
|
|
pr_debug("%s: complete\n", __func__);
|
|
|
|
return f;
|
|
}
|
|
|
|
#if defined(CONFIG_DEBUG_FS)
|
|
static char ipc_debug_buffer[PAGE_SIZE];
|
|
|
|
static ssize_t debug_read_stats(struct file *file, char __user *ubuf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
char *buf = ipc_debug_buffer;
|
|
int temp = 0;
|
|
unsigned long flags;
|
|
|
|
if (!ipc_dev || !ipc_dev->in || !ipc_dev->out) {
|
|
pr_err("ipc_dev instance, or EPs not yet initialised\n");
|
|
return 0;
|
|
}
|
|
|
|
spin_lock_irqsave(&ipc_dev->lock, flags);
|
|
temp += scnprintf(buf + temp, PAGE_SIZE - temp,
|
|
"endpoints: %s, %s\n"
|
|
"bytes to host: %lu\n"
|
|
"bytes to mdm: %lu\n"
|
|
"pending writes: %u\n"
|
|
"pending reads: %u\n",
|
|
ipc_dev->in->name, ipc_dev->out->name,
|
|
ipc_dev->bytes_to_host,
|
|
ipc_dev->bytes_to_mdm,
|
|
ipc_dev->pending_writes,
|
|
ipc_dev->pending_reads);
|
|
spin_unlock_irqrestore(&ipc_dev->lock, flags);
|
|
|
|
return simple_read_from_buffer(ubuf, count, ppos, buf, temp);
|
|
}
|
|
|
|
static ssize_t debug_reset_stats(struct file *file, const char __user *buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
unsigned long flags;
|
|
|
|
if (!ipc_dev) {
|
|
pr_err("ipc_dev instance not yet initialised\n");
|
|
return count;
|
|
}
|
|
|
|
spin_lock_irqsave(&ipc_dev->lock, flags);
|
|
ipc_dev->bytes_to_host = 0;
|
|
ipc_dev->bytes_to_mdm = 0;
|
|
spin_unlock_irqrestore(&ipc_dev->lock, flags);
|
|
|
|
return count;
|
|
}
|
|
|
|
static int debug_open(struct inode *inode, struct file *file)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static const struct file_operations debug_fipc_ops = {
|
|
.open = debug_open,
|
|
.read = debug_read_stats,
|
|
.write = debug_reset_stats,
|
|
};
|
|
|
|
static struct dentry *dent_ipc;
|
|
static void fipc_debugfs_init(void)
|
|
{
|
|
struct dentry *dent_ipc_status;
|
|
|
|
dent_ipc = debugfs_create_dir("usb_ipc", NULL);
|
|
if (!dent_ipc || IS_ERR(dent_ipc))
|
|
return;
|
|
|
|
dent_ipc_status = debugfs_create_file("status", 0444, dent_ipc, NULL,
|
|
&debug_fipc_ops);
|
|
|
|
if (!dent_ipc_status || IS_ERR(dent_ipc_status)) {
|
|
debugfs_remove(dent_ipc);
|
|
dent_ipc = NULL;
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void fipc_debugfs_remove(void)
|
|
{
|
|
debugfs_remove_recursive(dent_ipc);
|
|
}
|
|
#else
|
|
static inline void fipc_debugfs_init(void) {}
|
|
static inline void fipc_debugfs_remove(void) {}
|
|
#endif
|
|
|
|
static void ipc_opts_release(struct config_item *item)
|
|
{
|
|
struct ipc_opts *opts = to_ipc_opts(item);
|
|
|
|
usb_put_function_instance(&opts->func_inst);
|
|
}
|
|
|
|
static struct configfs_item_operations ipc_item_ops = {
|
|
.release = ipc_opts_release,
|
|
};
|
|
|
|
static struct config_item_type ipc_func_type = {
|
|
.ct_item_ops = &ipc_item_ops,
|
|
.ct_owner = THIS_MODULE,
|
|
};
|
|
|
|
static int ipc_set_inst_name(struct usb_function_instance *fi,
|
|
const char *name)
|
|
{
|
|
struct ipc_opts *opts = container_of(fi, struct ipc_opts, func_inst);
|
|
int name_len;
|
|
|
|
name_len = strlen(name) + 1;
|
|
if (name_len > MAX_INST_NAME_LEN)
|
|
return -ENAMETOOLONG;
|
|
|
|
spin_lock_init(&ipc_dev->lock);
|
|
init_waitqueue_head(&ipc_dev->state_wq);
|
|
init_completion(&ipc_dev->read_done);
|
|
init_completion(&ipc_dev->write_done);
|
|
INIT_WORK(&ipc_dev->func_work, ipc_function_work);
|
|
|
|
opts->ctxt = ipc_dev;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ipc_free_inst(struct usb_function_instance *f)
|
|
{
|
|
struct ipc_opts *opts = container_of(f, struct ipc_opts, func_inst);
|
|
|
|
kfree(opts);
|
|
}
|
|
|
|
static struct usb_function_instance *ipc_alloc_inst(void)
|
|
{
|
|
struct ipc_opts *opts;
|
|
|
|
opts = kzalloc(sizeof(*opts), GFP_KERNEL);
|
|
if (!opts)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
opts->func_inst.set_inst_name = ipc_set_inst_name;
|
|
opts->func_inst.free_func_inst = ipc_free_inst;
|
|
config_group_init_type_name(&opts->func_inst.group, "",
|
|
&ipc_func_type);
|
|
|
|
return &opts->func_inst;
|
|
}
|
|
|
|
static struct usb_function *ipc_alloc(struct usb_function_instance *fi)
|
|
{
|
|
return ipc_bind_config(fi);
|
|
}
|
|
|
|
DECLARE_USB_FUNCTION(ipc, ipc_alloc_inst, ipc_alloc);
|
|
|
|
static int __init ipc_init(void)
|
|
{
|
|
int ret;
|
|
|
|
ipc_dev = kzalloc(sizeof(*ipc_dev), GFP_KERNEL);
|
|
if (!ipc_dev)
|
|
return -ENOMEM;
|
|
|
|
ret = usb_function_register(&ipcusb_func);
|
|
if (ret) {
|
|
kfree(ipc_dev);
|
|
ipc_dev = NULL;
|
|
pr_err("%s: failed to register ipc %d\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
fipc_debugfs_init();
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void __exit ipc_exit(void)
|
|
{
|
|
fipc_debugfs_remove();
|
|
usb_function_unregister(&ipcusb_func);
|
|
kfree(ipc_dev);
|
|
ipc_dev = NULL;
|
|
}
|
|
|
|
module_init(ipc_init);
|
|
module_exit(ipc_exit);
|
|
|
|
MODULE_DESCRIPTION("IPC function driver");
|
|
MODULE_LICENSE("GPL v2");
|