/* * 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 #include #include #include #include #include #include #include #include #include #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");