Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1d31fbb5 authored by Gustavo Solaira's avatar Gustavo Solaira
Browse files

usb: gadget: f_ipc: Add IPC function driver for QRTR support



Add snapshot for f_ipc from msm-4.9 commit daaa935914c7
("usb: gadget: f_ipc: Use timeout when waiting for request completion").

Change-Id: I0a0750753b59c743c293ff0e979d4a86a054db5a
Signed-off-by: default avatarGustavo Solaira <gustavos@codeaurora.org>
parent 62942272
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -247,6 +247,9 @@ config USB_F_GSI
config USB_F_QDSS
	tristate

config USB_F_IPC
	tristate

# this first set of drivers all depend on bulk-capable hardware.

config USB_CONFIGFS
@@ -606,6 +609,15 @@ config USB_CONFIGFS_F_QDSS
	help
	  USB QDSS function driver to get hwtracing related data over USB.

config USB_CONFIGFS_F_IPC
	bool "USB IPC function"
	select USB_F_IPC
	depends on USB_CONFIGFS
	help
	  IPC function driver enables support for IPC messages port over USB.
	  This driver provides USB gadget access to the QRTR USB device mode
	  transport.

choice
	tristate "USB Gadget precomposed configurations"
	default USB_ETH
+2 −0
Original line number Diff line number Diff line
@@ -69,3 +69,5 @@ usb_f_gsi-y := f_gsi.o
obj-$(CONFIG_USB_F_GSI)		+= usb_f_gsi.o
usb_f_qdss-y			:= f_qdss.o u_qdss.o
obj-$(CONFIG_USB_F_QDSS)	+= usb_f_qdss.o
usb_f_ipc-y			:= f_ipc.o
obj-$(CONFIG_USB_F_IPC)		+= usb_f_ipc.o
+866 −0
Original line number Diff line number Diff line
/*
 * 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;
}

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");