blob: 1a9d50f7ba49075c4baed57cb4e4a0913ea6b4ec [file] [log] [blame]
/*
* Copyright (c) 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/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>
#define MAX_INST_NAME_LEN 40
#define IPC_BRIDGE_MAX_READ_SZ (8 * 1024)
#define IPC_BRIDGE_MAX_WRITE_SZ (8 * 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 = 0xFF,
.bInterfaceSubClass = 0xFF,
.bInterfaceProtocol = 0x30,
};
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)) {
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;
}
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) {
pr_err("%s: Request timed out\n", __func__);
ret = -ETIMEDOUT;
goto fail;
}
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;
return;
}
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;
ipc_dev = kzalloc(sizeof(*ipc_dev), GFP_KERNEL);
if (!ipc_dev)
return -ENOMEM;
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->ctxt);
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;
ret = usb_function_register(&ipcusb_func);
if (ret)
pr_err("%s: failed to register ipc %d\n", __func__, ret);
fipc_debugfs_init();
return ret;
}
static void __exit ipc_exit(void)
{
fipc_debugfs_remove();
usb_function_unregister(&ipcusb_func);
}
module_init(ipc_init);
module_exit(ipc_exit);
MODULE_DESCRIPTION("IPC function driver");
MODULE_LICENSE("GPL v2");