@@ -216,6 +216,9 @@ config USB_F_PRINTER
config USB_F_TCM
tristate
+config USB_F_MUD
+ tristate
+
# this first set of drivers all depend on bulk-capable hardware.
config USB_CONFIGFS
@@ -483,6 +486,13 @@ config USB_CONFIGFS_F_TCM
Both protocols can work on USB2.0 and USB3.0.
UAS utilizes the USB 3.0 feature called streams support.
+menuconfig USB_CONFIGFS_F_MUD
+ bool "Multifunction USB Device"
+ depends on USB_CONFIGFS
+ select USB_F_MUD
+ help
+ Core support for the Multifunction USB Device.
+
choice
tristate "USB Gadget precomposed configurations"
default USB_ETH
@@ -50,3 +50,5 @@ usb_f_printer-y := f_printer.o
obj-$(CONFIG_USB_F_PRINTER) += usb_f_printer.o
usb_f_tcm-y := f_tcm.o
obj-$(CONFIG_USB_F_TCM) += usb_f_tcm.o
+usb_f_mud-y := f_mud.o mud_regmap.o
+obj-$(CONFIG_USB_F_MUD) += usb_f_mud.o
new file mode 100644
@@ -0,0 +1,913 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2020 Noralf Trønnes
+ */
+
+#include <linux/configfs.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/regmap.h>
+#include <linux/regmap_usb.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/usb/composite.h>
+#include <linux/usb/gadget.h>
+
+#include "f_mud.h"
+
+/**
+ * DOC: overview
+ *
+ * f_mud is the device side counterpart to drivers/mfd/mud.
+ * It combines the regmap and mfd cell abstraction on the host side into one cell
+ * driver on the device side: @f_mud_cell_ops. The reason for not using the
+ * regmap library here is so drivers can do compression directly with their own
+ * buffers without going through a temporary buffer.
+ */
+
+/* Temporary debugging aid */
+static unsigned int debug = 8;
+
+#define fmdebug(level, fmt, ...) \
+do { \
+ if ((level) <= debug) \
+ pr_debug(fmt, ##__VA_ARGS__); \
+} while (0)
+
+struct f_mud {
+ struct usb_function func;
+ u8 interface_id;
+ struct mud_regmap *mreg;
+
+ struct f_mud_cell **cells;
+ unsigned int num_cells;
+
+ int interrupt_interval_ms;
+
+ spinlock_t irq_lock;
+ bool irq_enabled;
+ struct usb_ep *irq_ep;
+ struct usb_request *irq_req;
+ u16 int_tag;
+ unsigned long *irq_status;
+ bool irq_queued;
+};
+
+static inline struct f_mud *func_to_f_mud(struct usb_function *f)
+{
+ return container_of(f, struct f_mud, func);
+}
+
+struct f_mud_opts {
+ struct usb_function_instance func_inst;
+ struct mutex lock;
+ int refcnt;
+
+ int interrupt_interval_ms;
+
+ struct list_head cells;
+};
+
+static inline struct f_mud_opts *ci_to_f_mud_opts(struct config_item *item)
+{
+ return container_of(to_config_group(item), struct f_mud_opts,
+ func_inst.group);
+}
+
+static DEFINE_MUTEX(f_mud_cell_ops_list_mutex);
+static LIST_HEAD(f_mud_cell_ops_list);
+
+struct f_mud_cell_ops_list_item {
+ struct list_head list;
+ const struct f_mud_cell_ops *ops;
+ unsigned int refcnt;
+};
+
+static struct f_mud_cell_ops_list_item *f_mud_cell_item_lookup(const char *name)
+{
+ struct f_mud_cell_ops_list_item *item;
+
+ list_for_each_entry(item, &f_mud_cell_ops_list, list) {
+ if (!strcmp(name, item->ops->name))
+ return item;
+ }
+
+ return NULL;
+}
+
+/**
+ * f_mud_cell_register() - Register a cell driver
+ * @ops: Cell operations structure
+ *
+ * This function registers a cell driver for use in a gadget.
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int f_mud_cell_register(const struct f_mud_cell_ops *ops)
+{
+ struct f_mud_cell_ops_list_item *item;
+ int ret = 0;
+
+ fmdebug(1, "%s: name=%s\n", __func__, ops->name);
+
+ mutex_lock(&f_mud_cell_ops_list_mutex);
+
+ item = f_mud_cell_item_lookup(ops->name);
+ if (item) {
+ pr_err("%s: '%s' is already registered\n", __func__, ops->name);
+ ret = -EEXIST;
+ goto out;
+ }
+
+ item = kzalloc(sizeof(*item), GFP_KERNEL);
+ if (!item) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ item->ops = ops;
+ INIT_LIST_HEAD(&item->list);
+ list_add(&item->list, &f_mud_cell_ops_list);
+out:
+ mutex_unlock(&f_mud_cell_ops_list_mutex);
+
+ fmdebug(1, "%s: ret=%d\n", __func__, ret);
+
+ return ret;
+}
+EXPORT_SYMBOL(f_mud_cell_register);
+
+/**
+ * f_mud_cell_unregister() - Unregister a cell driver
+ * @ops: Cell operations structure
+ *
+ * This function unregisters a cell driver.
+ */
+void f_mud_cell_unregister(const struct f_mud_cell_ops *ops)
+{
+ struct f_mud_cell_ops_list_item *item;
+
+ fmdebug(1, "%s: name=%s\n", __func__, ops->name);
+
+ mutex_lock(&f_mud_cell_ops_list_mutex);
+
+ item = f_mud_cell_item_lookup(ops->name);
+ if (item) {
+ list_del(&item->list);
+ if (item->refcnt)
+ kfree(item);
+ else
+ pr_err("%s: Can't unregister '%s' (refcnt=%u)\n", __func__, ops->name, item->refcnt);
+ } else {
+ pr_err("%s: Didn't find '%s'\n", __func__, ops->name);
+ }
+
+ mutex_unlock(&f_mud_cell_ops_list_mutex);
+}
+EXPORT_SYMBOL(f_mud_cell_unregister);
+
+static const struct f_mud_cell_ops *f_mud_cell_get(const char *name)
+{
+ const struct f_mud_cell_ops *ops = NULL;
+ struct f_mud_cell_ops_list_item *item;
+ char module_name[MODULE_NAME_LEN];
+ bool retried = false;
+
+ fmdebug(1, "%s: name=%s\n", __func__, name);
+retry:
+ mutex_lock(&f_mud_cell_ops_list_mutex);
+ item = f_mud_cell_item_lookup(name);
+ fmdebug(1, "%s: item=%px\n", __func__, item);
+ if (!item) {
+ mutex_unlock(&f_mud_cell_ops_list_mutex);
+ if (retried)
+ return NULL;
+
+ retried = true;
+ snprintf(module_name, MODULE_NAME_LEN, "usb_f_%s", name);
+ strreplace(module_name, '-', '_');
+ if (request_module(module_name))
+ return NULL;
+
+ goto retry;
+ }
+
+ if (item && try_module_get(item->ops->owner)) {
+ ops = item->ops;
+ item->refcnt++;
+ }
+
+ mutex_unlock(&f_mud_cell_ops_list_mutex);
+
+ return ops;
+}
+
+static void f_mud_cell_put(const struct f_mud_cell_ops *ops)
+{
+ struct f_mud_cell_ops_list_item *item;
+
+ fmdebug(1, "%s: name=%s\n", __func__, ops->name);
+
+ mutex_lock(&f_mud_cell_ops_list_mutex);
+ item = f_mud_cell_item_lookup(ops->name);
+ WARN_ON(!item || !item->refcnt);
+ if (item && item->refcnt) {
+ module_put(item->ops->owner);
+ item->refcnt--;
+ }
+ mutex_unlock(&f_mud_cell_ops_list_mutex);
+}
+
+#define F_MUD_DEFINE_BULK_ENDPOINT_DESCRIPTOR(name, addr, size) \
+ static struct usb_endpoint_descriptor name = { \
+ .bLength = USB_DT_ENDPOINT_SIZE, \
+ .bDescriptorType = USB_DT_ENDPOINT, \
+ .bEndpointAddress = addr, \
+ .bmAttributes = USB_ENDPOINT_XFER_BULK, \
+ .wMaxPacketSize = cpu_to_le16(size), \
+ }
+
+#define F_MUD_DEFINE_INT_ENDPOINT_DESCRIPTOR(name) \
+ static struct usb_endpoint_descriptor name = { \
+ .bLength = USB_DT_ENDPOINT_SIZE, \
+ .bDescriptorType = USB_DT_ENDPOINT, \
+ .bEndpointAddress = USB_DIR_IN, \
+ .bmAttributes = USB_ENDPOINT_XFER_INT, \
+ }
+
+static struct usb_interface_descriptor f_mud_intf = {
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ /*.bNumEndpoints = 2 or 3, */
+ .bInterfaceClass = USB_CLASS_VENDOR_SPEC,
+};
+
+F_MUD_DEFINE_BULK_ENDPOINT_DESCRIPTOR(f_mud_fs_in_desc, USB_DIR_IN, 0);
+F_MUD_DEFINE_BULK_ENDPOINT_DESCRIPTOR(f_mud_fs_out_desc, USB_DIR_OUT, 0);
+F_MUD_DEFINE_INT_ENDPOINT_DESCRIPTOR(f_mud_fs_int_desc);
+
+static struct usb_descriptor_header *f_mud_fs_function[] = {
+ (struct usb_descriptor_header *)&f_mud_intf,
+ (struct usb_descriptor_header *)&f_mud_fs_in_desc,
+ (struct usb_descriptor_header *)&f_mud_fs_out_desc,
+ NULL, /* Room for optional interrupt endpoint */
+ NULL,
+};
+
+F_MUD_DEFINE_BULK_ENDPOINT_DESCRIPTOR(f_mud_hs_in_desc, USB_DIR_IN, 512);
+F_MUD_DEFINE_BULK_ENDPOINT_DESCRIPTOR(f_mud_hs_out_desc, USB_DIR_OUT, 512);
+F_MUD_DEFINE_INT_ENDPOINT_DESCRIPTOR(f_mud_hs_int_desc);
+
+static struct usb_descriptor_header *f_mud_hs_function[] = {
+ (struct usb_descriptor_header *)&f_mud_intf,
+ (struct usb_descriptor_header *)&f_mud_hs_in_desc,
+ (struct usb_descriptor_header *)&f_mud_hs_out_desc,
+ NULL, /* Room for optional interrupt endpoint */
+ NULL,
+};
+
+F_MUD_DEFINE_BULK_ENDPOINT_DESCRIPTOR(f_mud_ss_in_desc, USB_DIR_IN, 1024);
+F_MUD_DEFINE_BULK_ENDPOINT_DESCRIPTOR(f_mud_ss_out_desc, USB_DIR_OUT, 1024);
+F_MUD_DEFINE_INT_ENDPOINT_DESCRIPTOR(f_mud_ss_int_desc);
+
+static struct usb_ss_ep_comp_descriptor f_mud_ss_bulk_comp_desc = {
+ .bLength = USB_DT_SS_EP_COMP_SIZE,
+ .bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
+};
+
+static struct usb_ss_ep_comp_descriptor f_mud_ss_int_comp_desc = {
+ .bLength = USB_DT_SS_EP_COMP_SIZE,
+ .bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
+};
+
+static struct usb_descriptor_header *f_mud_ss_function[] = {
+ (struct usb_descriptor_header *)&f_mud_intf,
+ (struct usb_descriptor_header *)&f_mud_ss_in_desc,
+ (struct usb_descriptor_header *)&f_mud_ss_bulk_comp_desc,
+ (struct usb_descriptor_header *)&f_mud_ss_out_desc,
+ (struct usb_descriptor_header *)&f_mud_ss_bulk_comp_desc,
+ NULL, /* Room for optional interrupt endpoint, otherwise terminator */
+ (struct usb_descriptor_header *)&f_mud_ss_int_comp_desc,
+ NULL,
+};
+
+static struct usb_string f_mud_string_defs[] = {
+ [0].s = "Multifunction USB device",
+ { } /* end of list */
+};
+
+static struct usb_gadget_strings f_mud_string_table = {
+ .language = 0x0409, /* en-us */
+ .strings = f_mud_string_defs,
+};
+
+static struct usb_gadget_strings *f_mud_strings[] = {
+ &f_mud_string_table,
+ NULL,
+};
+
+static void fmud_irq_req_queue(struct f_mud *fmud)
+{
+ unsigned int nlongs = DIV_ROUND_UP(fmud->num_cells, BITS_PER_LONG);
+ unsigned int nbytes = DIV_ROUND_UP(fmud->num_cells, BITS_PER_BYTE);
+ unsigned int i, ilong, ibuf = 0;
+ int ret;
+ __le16 *tag = fmud->irq_req->buf;
+ u8 *buf = fmud->irq_req->buf + sizeof(u16);
+
+ fmdebug(3, "%s: irq_status: %*pb\n", __func__, fmud->num_cells, fmud->irq_status);
+
+ *tag = cpu_to_le16(++fmud->int_tag);
+
+ for (ilong = 0; ilong < nlongs; ilong++) {
+ unsigned long val = fmud->irq_status[ilong];
+
+ fmud->irq_status[ilong] = 0;
+
+ for (i = 0; i < (BITS_PER_LONG / BITS_PER_BYTE) && ibuf < nbytes; i++, ibuf++) {
+ buf[ibuf] = val & 0xff;
+ val >>= 8;
+ }
+ }
+
+ fmdebug(3, "%s: req->buf: %*ph\n", __func__, fmud->irq_req->length, fmud->irq_req->buf);
+
+ ret = usb_ep_queue(fmud->irq_ep, fmud->irq_req, GFP_ATOMIC);
+ if (!ret)
+ fmud->irq_queued = true;
+ else
+ pr_err("%s: Failed to queue irq req, error=%d\n", __func__, ret);
+}
+
+static void fmud_irq_req_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ struct f_mud *fmud = req->context;
+ unsigned long flags;
+
+ switch (req->status) {
+ case 0:
+ break;
+ case -ECONNABORTED: /* hardware forced ep reset */
+ case -ECONNRESET: /* request dequeued */
+ case -ESHUTDOWN: /* disconnect from host */
+ fmdebug(1, "%s: abort, status=%d\n", __func__, req->status);
+ return;
+ default:
+ pr_err("%s: irq request failed, error=%d\n", __func__, req->status);
+ break;
+ }
+
+ spin_lock_irqsave(&fmud->irq_lock, flags);
+
+ fmud->irq_queued = false;
+
+ if (!bitmap_empty(fmud->irq_status, fmud->num_cells))
+ fmud_irq_req_queue(fmud);
+
+ spin_unlock_irqrestore(&fmud->irq_lock, flags);
+}
+
+/**
+ * f_mud_irq() - Send an interrupt
+ * @cell: Cell
+ *
+ * This function queues an interrupt to be sent to the host.
+ *
+ * Returns:
+ * True if there's a pending interrupt that has not been sent yet, otherwise false.
+ */
+bool f_mud_irq(struct f_mud_cell *cell)
+{
+ struct f_mud *fmud = cell->fmud;
+ unsigned long flags;
+ bool ret;
+
+ if (WARN_ON_ONCE(!fmud || !fmud->irq_enabled))
+ return false;
+
+ spin_lock_irqsave(&fmud->irq_lock, flags);
+
+ ret = test_and_set_bit(cell->index, fmud->irq_status);
+
+ if (!fmud->irq_queued)
+ fmud_irq_req_queue(fmud);
+
+ spin_unlock_irqrestore(&fmud->irq_lock, flags);
+
+ fmdebug(1, "%s: cell->index=%u was_set=%u\n", __func__, cell->index, ret);
+
+ return ret;
+}
+EXPORT_SYMBOL(f_mud_irq);
+
+static int f_mud_set_alt(struct usb_function *f, unsigned int intf, unsigned int alt)
+{
+ struct f_mud *fmud = func_to_f_mud(f);
+
+ fmdebug(1, "%s: intf=%u, alt=%u\n", __func__, intf, alt);
+
+ if (alt || intf != fmud->interface_id)
+ return -EINVAL;
+
+ if (fmud->irq_ep) {
+ struct usb_composite_dev *cdev = f->config->cdev;
+
+ if (!fmud->irq_ep->desc) {
+ if (config_ep_by_speed(cdev->gadget, f, fmud->irq_ep)) {
+ fmud->irq_ep->desc = NULL;
+ return -EINVAL;
+ }
+ }
+
+ usb_ep_disable(fmud->irq_ep);
+ usb_ep_enable(fmud->irq_ep);
+ fmud->irq_enabled = true;
+ }
+
+ return mud_regmap_set_alt(fmud->mreg, f);
+}
+
+static int f_mud_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
+{
+ struct f_mud *fmud = func_to_f_mud(f);
+
+ return mud_regmap_setup(fmud->mreg, f, ctrl);
+}
+
+static void f_mud_disable(struct usb_function *f)
+{
+ struct f_mud *fmud = func_to_f_mud(f);
+
+ fmdebug(1, "%s\n", __func__);
+
+ if (fmud->irq_ep) {
+ fmud->int_tag = 0;
+ fmud->irq_enabled = false;
+ usb_ep_disable(fmud->irq_ep);
+ }
+
+ mud_regmap_stop(fmud->mreg);
+}
+
+static void f_mud_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct f_mud *fmud = func_to_f_mud(f);
+ struct f_mud_cell *cell;
+ unsigned int i;
+
+ fmdebug(1, "%s\n", __func__);
+
+ for (i = 0; i < fmud->num_cells; i++) {
+ cell = fmud->cells[i];
+ cell->ops->unbind(cell);
+ }
+ mud_regmap_cleanup(fmud->mreg);
+ fmud->mreg = NULL;
+ usb_free_all_descriptors(f);
+ if (fmud->irq_req) {
+ kfree(fmud->irq_req->buf);
+ usb_ep_free_request(fmud->irq_ep, fmud->irq_req);
+ fmud->irq_req = NULL;
+ bitmap_free(fmud->irq_status);
+ fmud->irq_status = NULL;
+ }
+}
+
+static int f_mud_bind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct usb_composite_dev *cdev = c->cdev;
+ struct f_mud *fmud = func_to_f_mud(f);
+ struct usb_ep *in_ep, *out_ep;
+ unsigned int max_index = 0;
+ struct usb_string *us;
+ struct mud_regmap *mreg;
+ int i, ret;
+
+ fmdebug(1, "%s\n", __func__);
+
+ for (i = 0; i < fmud->num_cells; i++)
+ max_index = max(fmud->cells[i]->index, max_index);
+
+ if (fmud->num_cells != max_index + 1) {
+ pr_err("Cell indices are not continuous\n");
+ return -EINVAL;
+ }
+
+ us = usb_gstrings_attach(cdev, f_mud_strings,
+ ARRAY_SIZE(f_mud_string_defs));
+ if (IS_ERR(us))
+ return PTR_ERR(us);
+
+ f_mud_intf.iInterface = us[0].id;
+
+ ret = usb_interface_id(c, f);
+ if (ret < 0)
+ return ret;
+
+ fmud->interface_id = ret;
+ f_mud_intf.bInterfaceNumber = fmud->interface_id;
+
+ in_ep = usb_ep_autoconfig(cdev->gadget, &f_mud_fs_in_desc);
+ out_ep = usb_ep_autoconfig(cdev->gadget, &f_mud_fs_out_desc);
+ if (!in_ep || !out_ep)
+ return -ENODEV;
+
+ f_mud_hs_in_desc.bEndpointAddress = f_mud_fs_in_desc.bEndpointAddress;
+ f_mud_hs_out_desc.bEndpointAddress = f_mud_fs_out_desc.bEndpointAddress;
+
+ f_mud_ss_in_desc.bEndpointAddress = f_mud_fs_in_desc.bEndpointAddress;
+ f_mud_ss_out_desc.bEndpointAddress = f_mud_fs_out_desc.bEndpointAddress;
+
+ if (fmud->interrupt_interval_ms) {
+ unsigned int buflen = sizeof(u16) + DIV_ROUND_UP(fmud->num_cells, 8);
+ unsigned int interval_ms = fmud->interrupt_interval_ms;
+ unsigned int interval_hs_ss;
+
+ interval_hs_ss = roundup_pow_of_two(interval_ms * 8); /* 125us frames */
+ interval_hs_ss = ilog2(interval_hs_ss) + 1; /* 2^(bInterval-1) encoding */
+ interval_hs_ss = min_t(unsigned int, interval_hs_ss, 16); /* max 4096ms */
+
+ f_mud_fs_int_desc.bInterval = min_t(unsigned int, interval_ms, 255);
+ f_mud_fs_int_desc.wMaxPacketSize = cpu_to_le16(buflen);
+ f_mud_hs_int_desc.bInterval = interval_hs_ss;
+ f_mud_hs_int_desc.wMaxPacketSize = cpu_to_le16(buflen);
+ f_mud_ss_int_desc.bInterval = interval_hs_ss;
+ f_mud_ss_int_desc.wMaxPacketSize = cpu_to_le16(buflen);
+ f_mud_ss_int_comp_desc.wBytesPerInterval = cpu_to_le16(buflen);
+
+ fmud->irq_ep = usb_ep_autoconfig(cdev->gadget, &f_mud_fs_int_desc);
+ if (!fmud->irq_ep)
+ return -ENODEV;
+
+ f_mud_hs_int_desc.bEndpointAddress = f_mud_fs_int_desc.bEndpointAddress;
+ f_mud_ss_int_desc.bEndpointAddress = f_mud_fs_int_desc.bEndpointAddress;
+
+ fmud->irq_req = usb_ep_alloc_request(fmud->irq_ep, GFP_KERNEL);
+ if (!fmud->irq_req) {
+ ret = -ENOMEM;
+ goto fail_free_irq;
+ }
+
+ fmud->irq_req->complete = fmud_irq_req_complete;
+ fmud->irq_req->length = buflen;
+ fmud->irq_req->context = fmud;
+ fmud->irq_req->buf = kmalloc(buflen, GFP_KERNEL);
+ if (!fmud->irq_req->buf) {
+ ret = -ENOMEM;
+ goto fail_free_irq;
+ }
+
+ fmud->irq_status = bitmap_zalloc(fmud->num_cells, GFP_KERNEL);
+ if (!fmud->irq_status) {
+ ret = -ENOMEM;
+ goto fail_free_irq;
+ }
+
+ f_mud_intf.bNumEndpoints = 3;
+ f_mud_fs_function[3] = (struct usb_descriptor_header *)&f_mud_fs_int_desc;
+ f_mud_hs_function[3] = (struct usb_descriptor_header *)&f_mud_hs_int_desc;
+ f_mud_ss_function[5] = (struct usb_descriptor_header *)&f_mud_ss_int_desc;
+ } else {
+ f_mud_intf.bNumEndpoints = 2;
+ f_mud_fs_function[3] = NULL;
+ f_mud_hs_function[3] = NULL;
+ f_mud_ss_function[5] = NULL;
+ }
+
+ ret = usb_assign_descriptors(f, f_mud_fs_function, f_mud_hs_function,
+ f_mud_ss_function, NULL);
+ if (ret)
+ goto fail_free_irq;
+
+ for (i = 0; i < fmud->num_cells; i++) {
+ struct f_mud_cell *cell = fmud->cells[i];
+
+ ret = cell->ops->bind(cell);
+ if (ret) {
+ pr_err("%s: Failed to bind cell '%s' ret=%d",
+ __func__, cell->ops->name, ret);
+ goto fail_unbind;
+ }
+ }
+
+ mreg = mud_regmap_init(cdev, in_ep, out_ep, fmud->cells, fmud->num_cells);
+ if (IS_ERR(mreg)) {
+ ret = PTR_ERR(mreg);
+ goto fail_unbind;
+ }
+
+ fmud->mreg = mreg;
+
+ pr_info("%s: %s speed IN/%s OUT/%s\n", __func__,
+ gadget_is_superspeed(c->cdev->gadget) ? "super" :
+ gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
+ in_ep->name, out_ep->name);
+
+ return 0;
+
+fail_unbind:
+ while (--i >= 0) {
+ struct f_mud_cell *cell = fmud->cells[i];
+
+ cell->ops->unbind(cell);
+ }
+ usb_free_all_descriptors(f);
+fail_free_irq:
+ if (fmud->irq_req) {
+ kfree(fmud->irq_req->buf);
+ usb_ep_free_request(fmud->irq_ep, fmud->irq_req);
+ fmud->irq_req = NULL;
+ bitmap_free(fmud->irq_status);
+ fmud->irq_status = NULL;
+ }
+
+ return ret;
+}
+
+static void f_mud_free_func(struct usb_function *f)
+{
+ struct f_mud_opts *opts = container_of(f->fi, struct f_mud_opts, func_inst);
+ struct f_mud *fmud = func_to_f_mud(f);
+ unsigned int i;
+
+ fmdebug(1, "%s\n", __func__);
+
+ mutex_lock(&opts->lock);
+ opts->refcnt--;
+ for (i = 0; i < fmud->num_cells; i++) {
+ configfs_undepend_item(&fmud->cells[i]->group.cg_item);
+ fmud->cells[i]->fmud = NULL;
+ }
+ mutex_unlock(&opts->lock);
+
+ kfree(fmud->cells);
+ kfree(fmud);
+}
+
+static struct usb_function *f_mud_alloc_func(struct usb_function_instance *fi)
+{
+ struct f_mud_opts *opts = container_of(fi, struct f_mud_opts, func_inst);
+ unsigned int max_index = 0, interrupt_interval_ms = UINT_MAX;
+ struct usb_function *func;
+ struct f_mud_cell *cell;
+ struct f_mud *fmud;
+ int ret = 0;
+
+ fmdebug(1, "%s\n", __func__);
+
+ fmud = kzalloc(sizeof(*fmud), GFP_KERNEL);
+ if (!fmud)
+ return ERR_PTR(-ENOMEM);
+
+ spin_lock_init(&fmud->irq_lock);
+
+ mutex_lock(&opts->lock);
+
+ list_for_each_entry(cell, &opts->cells, node) {
+ max_index = max(max_index, cell->index);
+ fmud->num_cells++;
+ }
+
+ if (!fmud->num_cells) {
+ ret = -ENOENT;
+ goto unlock;
+ }
+
+ if (fmud->num_cells != max_index + 1) {
+ pr_err("Cell indices are not continuous\n");
+ ret = -EINVAL;
+ goto unlock;
+ }
+
+ fmud->cells = kcalloc(fmud->num_cells, sizeof(*fmud->cells), GFP_KERNEL);
+ if (!fmud->cells) {
+ ret = -ENOMEM;
+ goto unlock;
+ }
+
+ list_for_each_entry(cell, &opts->cells, node) {
+ /* Prevent the cell dir from being deleted */
+ ret = configfs_depend_item(cell->group.cg_subsys, &cell->group.cg_item);
+ if (ret)
+ goto unlock;
+
+ fmud->cells[cell->index] = cell;
+ cell->fmud = fmud;
+ if (cell->ops->interrupt_interval_ms)
+ interrupt_interval_ms = min(cell->ops->interrupt_interval_ms,
+ interrupt_interval_ms);
+ }
+
+ if (interrupt_interval_ms != UINT_MAX) {
+ if (opts->interrupt_interval_ms == -1)
+ fmud->interrupt_interval_ms = interrupt_interval_ms;
+ else
+ fmud->interrupt_interval_ms = opts->interrupt_interval_ms;
+ }
+
+ fmdebug(1, " interrupt_interval_ms=%d\n", fmud->interrupt_interval_ms);
+
+ opts->refcnt++;
+unlock:
+ mutex_unlock(&opts->lock);
+
+ if (ret)
+ goto error;
+
+ func = &fmud->func;
+ func->name = "f_mud";
+ func->bind = f_mud_bind;
+ func->unbind = f_mud_unbind;
+ func->set_alt = f_mud_set_alt;
+ func->setup = f_mud_setup;
+ func->disable = f_mud_disable;
+ func->free_func = f_mud_free_func;
+
+ return func;
+
+error:
+ if (fmud->cells) {
+ unsigned int i;
+
+ for (i = 0; i < fmud->num_cells; i++) {
+ cell = fmud->cells[i];
+ if (cell)
+ configfs_undepend_item(&cell->group.cg_item);
+ }
+ kfree(fmud->cells);
+ }
+ kfree(fmud);
+
+ return ERR_PTR(ret);
+}
+
+F_MUD_OPT_INT(f_mud_opts, interrupt_interval_ms, -1, INT_MAX);
+
+static struct configfs_attribute *f_mud_attrs[] = {
+ &f_mud_opts_attr_interrupt_interval_ms,
+ NULL,
+};
+
+static struct config_group *f_mud_cell_make_group(struct config_group *group,
+ const char *name)
+{
+ struct f_mud_opts *opts = ci_to_f_mud_opts(&group->cg_item);
+ char *cell_name, *cell_index_str, *buf = NULL;
+ const struct f_mud_cell_ops *ops;
+ struct f_mud_cell *cell;
+ int ret = 0;
+ u8 index;
+
+ fmdebug(1, "%s: name=%s\n", __func__, name);
+
+ mutex_lock(&opts->lock);
+ if (opts->refcnt) {
+ ret = -EBUSY;
+ goto out_unlock;
+ }
+
+ buf = kstrdup(name, GFP_KERNEL);
+ if (!buf) {
+ ret = -ENOMEM;
+ goto out_unlock;
+ }
+
+ cell_index_str = buf;
+ cell_name = strsep(&cell_index_str, ".");
+ if (!cell_index_str || !strlen(cell_index_str)) {
+ pr_err("Unable to parse CELL.INDEX for '%s'\n", name);
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+
+ ret = kstrtou8(cell_index_str, 10, &index);
+ if (ret)
+ goto out_unlock;
+
+ if (index >= REGMAP_USB_MAX_MAPS) {
+ pr_err("Cell index out of range for '%s'\n", name);
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+
+ ops = f_mud_cell_get(cell_name);
+ if (!ops) {
+ ret = -ENOENT;
+ goto out_unlock;
+ }
+
+ cell = ops->alloc();
+ if (IS_ERR(cell)) {
+ f_mud_cell_put(ops);
+ ret = PTR_ERR(cell);
+ goto out_unlock;
+ }
+
+ cell->ops = ops;
+ cell->index = index;
+ list_add(&cell->node, &opts->cells);
+out_unlock:
+ mutex_unlock(&opts->lock);
+
+ kfree(buf);
+
+ return ret ? ERR_PTR(ret) : &cell->group;
+}
+
+/**
+ * f_mud_cell_item_release() - Cell configfs item release
+ * @item: Configfs item
+ *
+ * Drivers should use this as their &configfs_item_operations.release callback
+ * in their &config_item_type on the cells &config_group.
+ */
+void f_mud_cell_item_release(struct config_item *item)
+{
+ struct f_mud_cell *cell = ci_to_f_mud_cell(item);
+ const struct f_mud_cell_ops *ops = cell->ops;
+
+ fmdebug(1, "%s: cell=%px\n", __func__, cell);
+
+ ops->free(cell);
+ f_mud_cell_put(ops);
+}
+EXPORT_SYMBOL(f_mud_cell_item_release);
+
+static void f_mud_cell_drop_item(struct config_group *group, struct config_item *item)
+{
+ struct f_mud_opts *opts = ci_to_f_mud_opts(&group->cg_item);
+ struct f_mud_cell *cell = ci_to_f_mud_cell(item);
+
+ fmdebug(1, "%s: cell=%px\n", __func__, cell);
+
+ mutex_lock(&opts->lock);
+ list_del(&cell->node);
+ mutex_unlock(&opts->lock);
+
+ config_item_put(item);
+}
+
+static struct configfs_group_operations f_mud_cell_group_ops = {
+ .make_group = f_mud_cell_make_group,
+ .drop_item = f_mud_cell_drop_item,
+};
+
+static void f_mud_attr_release(struct config_item *item)
+{
+ struct f_mud_opts *opts = ci_to_f_mud_opts(item);
+
+ fmdebug(1, "%s\n", __func__);
+
+ usb_put_function_instance(&opts->func_inst);
+}
+
+static struct configfs_item_operations f_mud_item_ops = {
+ .release = f_mud_attr_release,
+};
+
+static const struct config_item_type f_mud_func_type = {
+ .ct_item_ops = &f_mud_item_ops,
+ .ct_group_ops = &f_mud_cell_group_ops,
+ .ct_attrs = f_mud_attrs,
+ .ct_owner = THIS_MODULE,
+};
+
+static void f_mud_free_func_inst(struct usb_function_instance *f)
+{
+ struct f_mud_opts *opts = container_of(f, struct f_mud_opts, func_inst);
+
+ fmdebug(1, "%s\n", __func__);
+
+ mutex_destroy(&opts->lock);
+ kfree(opts);
+}
+
+static struct usb_function_instance *f_mud_alloc_func_inst(void)
+{
+ struct f_mud_opts *opts;
+
+ fmdebug(1, "%s\n", __func__);
+
+ opts = kzalloc(sizeof(*opts), GFP_KERNEL);
+ if (!opts)
+ return ERR_PTR(-ENOMEM);
+
+ mutex_init(&opts->lock);
+ INIT_LIST_HEAD(&opts->cells);
+ opts->func_inst.free_func_inst = f_mud_free_func_inst;
+ opts->interrupt_interval_ms = -1;
+
+ config_group_init_type_name(&opts->func_inst.group, "", &f_mud_func_type);
+
+ return &opts->func_inst;
+}
+
+DECLARE_USB_FUNCTION_INIT(f_mud, f_mud_alloc_func_inst, f_mud_alloc_func);
+
+MODULE_DESCRIPTION("Multifunction USB Device");
+MODULE_AUTHOR("Noralf Trønnes");
+MODULE_LICENSE("GPL");
new file mode 100644
@@ -0,0 +1,210 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef __LINUX_F_MUD_H
+#define __LINUX_F_MUD_H
+
+#include <linux/configfs.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+
+struct module;
+
+struct f_mud;
+struct f_mud_cell;
+
+/**
+ * struct f_mud_cell_ops - Cell driver operations
+ * @name: Name, passed on to the host as the regmap name
+ * @owner: Owner module
+ * @regval_bytes: Number of bytes in the register value (1, 2 or 4)
+ * @max_transfer_size: Maximum size of one transfer
+ * @compression: Supported compression bit mask
+ * @interrupt_interval_ms: Interrupt interval in milliseconds (optional)
+ * Setting this adds an interrupt endpoint to the interface.
+ * If more than one cell sets this then the smallest value is used.
+ * @alloc: Callback that allocates and returns an &f_mud_cell
+ * @free: Frees the allocated cell
+ * @bind: Called when the gadget is bound to the controller
+ * @unbind: Called when the gadget is unbound from the controller
+ * @enable: Called when the USB cable is plugged in (optional)
+ * @disable: Called when the USB cable is unplugged (optional)
+ * @readreg: Called when the host is writing to a register. If the host asks for
+ * compression, the cell is free to ignore it. If it does compress,
+ * then the len argument must be updated to reflect the actual buffer size.
+ * @writereg: Called when the host is reading from a register
+ *
+ * All callbacks run in process context.
+ */
+struct f_mud_cell_ops {
+ const char *name;
+ struct module *owner;
+
+ unsigned int regval_bytes;
+ unsigned int max_transfer_size;
+ u8 compression;
+ unsigned int interrupt_interval_ms;
+
+ struct f_mud_cell *(*alloc)(void);
+ void (*free)(struct f_mud_cell *cell);
+
+ int (*bind)(struct f_mud_cell *cell);
+ void (*unbind)(struct f_mud_cell *cell);
+
+ void (*enable)(struct f_mud_cell *cell);
+ void (*disable)(struct f_mud_cell *cell);
+
+ int (*readreg)(struct f_mud_cell *cell, unsigned int regnr,
+ void *buf, size_t *len, u8 compression);
+ int (*writereg)(struct f_mud_cell *cell, unsigned int regnr,
+ const void *buf, size_t len, u8 compression);
+};
+
+/**
+ * struct f_mud_cell - Cell
+ * @node: List node in the configfs entry list
+ * @index: Cell index from configfs entry
+ * @ops: Cell operations
+ * @fmud: Parent structure
+ * @group: Configfs group
+ */
+struct f_mud_cell {
+ struct list_head node;
+ unsigned int index;
+ const struct f_mud_cell_ops *ops;
+ struct f_mud *fmud;
+ struct config_group group;
+};
+
+static inline struct f_mud_cell *ci_to_f_mud_cell(struct config_item *item)
+{
+ return container_of(to_config_group(item), struct f_mud_cell, group);
+}
+
+bool f_mud_irq(struct f_mud_cell *cell);
+void f_mud_cell_item_release(struct config_item *item);
+
+int f_mud_cell_register(const struct f_mud_cell_ops *ops);
+void f_mud_cell_unregister(const struct f_mud_cell_ops *ops);
+
+#define DECLARE_F_MUD_CELL_INIT(_ops) \
+ static int __init _ops ## _mod_init(void) \
+ { \
+ return f_mud_cell_register(&_ops); \
+ } \
+ module_init(_ops ## _mod_init); \
+ static void __exit _ops ## _mod_exit(void) \
+ { \
+ f_mud_cell_unregister(&_ops); \
+ } \
+ module_exit(_ops ## _mod_exit)
+
+#define F_MUD_OPT_INT(_typ, _name, _min, _max) \
+static ssize_t _typ ## _ ## _name ## _show(struct config_item *item, \
+ char *page) \
+{ \
+ struct _typ *opts = ci_to_ ## _typ(item); \
+ ssize_t ret; \
+ \
+ mutex_lock(&opts->lock); \
+ ret = sprintf(page, "%d\n", opts->_name); \
+ mutex_unlock(&opts->lock); \
+ \
+ return ret; \
+} \
+ \
+static ssize_t _typ ## _ ## _name ## _store(struct config_item *item, \
+ const char *page, size_t len) \
+{ \
+ struct _typ *opts = ci_to_ ## _typ(item); \
+ int ret, num; \
+ \
+ mutex_lock(&opts->lock); \
+ if (opts->refcnt) { \
+ ret = -EBUSY; \
+ goto out_unlock; \
+ } \
+ \
+ ret = kstrtoint(page, 0, &num); \
+ if (ret) \
+ goto out_unlock; \
+ \
+ if (num >= (_min) || num <= (_max)) \
+ opts->_name = num; \
+ else \
+ ret = -EINVAL; \
+out_unlock: \
+ mutex_unlock(&opts->lock); \
+ \
+ return ret ? ret : len; \
+} \
+ \
+CONFIGFS_ATTR(_typ ## _, _name)
+
+#define F_MUD_OPT_STR(_typ, _name) \
+static ssize_t _typ ## _ ## _name ## _show(struct config_item *item, \
+ char *page) \
+{ \
+ struct _typ *opts = ci_to_ ## _typ(item); \
+ ssize_t ret; \
+ \
+ mutex_lock(&opts->lock); \
+ \
+ if (opts->_name) { \
+ ret = strscpy(page, opts->_name, PAGE_SIZE); \
+ } else { \
+ page[0] = '\0'; \
+ ret = 0; \
+ } \
+ \
+ mutex_unlock(&opts->lock); \
+ \
+ return ret; \
+} \
+ \
+static ssize_t _typ ## _ ## _name ## _store(struct config_item *item, \
+ const char *page, size_t len) \
+{ \
+ struct _typ *opts = ci_to_ ## _typ(item); \
+ ssize_t ret = 0; \
+ char *buf; \
+ \
+ mutex_lock(&opts->lock); \
+ if (opts->refcnt) { \
+ ret = -EBUSY; \
+ goto out_unlock; \
+ } \
+ \
+ buf = kstrndup(page, len, GFP_KERNEL); \
+ if (!buf) { \
+ ret = -ENOMEM; \
+ goto out_unlock; \
+ } \
+ \
+ kfree(opts->_name); \
+ opts->_name = buf; \
+out_unlock: \
+ mutex_unlock(&opts->lock); \
+ \
+ return ret ? ret : len; \
+} \
+ \
+CONFIGFS_ATTR(_typ ## _, _name)
+
+/* mud_regmap.c */
+struct mud_regmap;
+struct usb_composite_dev;
+struct usb_ctrlrequest;
+struct usb_ep;
+struct usb_function;
+
+void mud_regmap_stop(struct mud_regmap *mreg);
+int mud_regmap_setup(struct mud_regmap *mreg, struct usb_function *f,
+ const struct usb_ctrlrequest *ctrl);
+int mud_regmap_set_alt(struct mud_regmap *mreg, struct usb_function *f);
+
+struct mud_regmap *mud_regmap_init(struct usb_composite_dev *cdev,
+ struct usb_ep *in_ep, struct usb_ep *out_ep,
+ struct f_mud_cell **cells, unsigned int num_cells);
+void mud_regmap_cleanup(struct mud_regmap *mreg);
+
+#endif
new file mode 100644
@@ -0,0 +1,936 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2020 Noralf Trønnes
+ */
+
+#include <linux/delay.h>
+#include <linux/list.h>
+#include <linux/log2.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/spinlock.h>
+#include <linux/regmap.h>
+#include <linux/regmap_usb.h>
+#include <linux/usb/composite.h>
+#include <linux/usb/gadget.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+
+#include "f_mud.h"
+
+struct mud_regmap_transfer {
+ struct mud_regmap *mreg;
+
+ spinlock_t lock;
+ struct list_head node;
+
+ struct usb_request *header_req;
+ struct usb_request *buf_out_req;
+ struct usb_request *buf_in_req;
+ struct usb_request *status_req;
+
+ struct usb_request *current_req;
+
+ u32 tag;
+ bool in;
+ unsigned int index;
+ unsigned int regnr;
+ u32 flags;
+};
+
+struct mud_regmap {
+ struct usb_ep *in_ep;
+ struct usb_ep *out_ep;
+ struct usb_composite_dev *cdev;
+
+ struct f_mud_cell **cells;
+ unsigned int num_cells;
+
+ unsigned int max_transfer_size;
+ struct mud_regmap_transfer *transfers[2];
+
+ spinlock_t lock;
+
+ struct list_head free_transfers;
+ struct list_head pending_transfers;
+
+ struct workqueue_struct *workq;
+ struct work_struct work;
+ wait_queue_head_t waitq;
+
+ bool pending_protocol_reset;
+ bool pending_stall;
+ bool stalled;
+ bool run;
+
+ bool header_queued;
+ u8 errno;
+};
+
+/* Temporary debugging aid */
+static unsigned int debug = 0;
+
+#define udebug(level, fmt, ...) \
+do { \
+ if ((level) <= debug) \
+ printk(KERN_DEBUG fmt, ##__VA_ARGS__); \
+} while (0)
+
+#undef DBG
+#define DBG INFO
+
+static int mud_regmap_usb_ep_set_halt(struct usb_ep *ep)
+{
+ int retries = 10, ret;
+
+ while (retries-- > 0) {
+ ret = usb_ep_set_halt(ep);
+ if (ret != -EAGAIN)
+ break;
+ msleep(100);
+ }
+
+ return ret;
+}
+
+static void mud_regmap_stall(struct mud_regmap *mreg)
+{
+ struct usb_request *current_req[2] = { NULL, NULL };
+ struct mud_regmap_transfer *transfer;
+ int ret_in, ret_out, timeout;
+ unsigned int i;
+
+ udebug(0, "%s:\n", __func__);
+
+ for (i = 0; i < 2; i++) {
+ transfer = mreg->transfers[i];
+ spin_lock_irq(&transfer->lock);
+ current_req[i] = transfer->current_req;
+ spin_unlock_irq(&transfer->lock);
+ if (current_req[i]) {
+ if (current_req[i] == transfer->header_req ||
+ current_req[i] == transfer->buf_out_req)
+ usb_ep_dequeue(mreg->out_ep, current_req[i]);
+ else
+ usb_ep_dequeue(mreg->in_ep, current_req[i]);
+ }
+ }
+
+ for (timeout = 20; timeout > 0; timeout--) {
+ for (i = 0; i < 2; i++) {
+ transfer = mreg->transfers[i];
+ spin_lock_irq(&transfer->lock);
+ current_req[i] = transfer->current_req;
+ spin_unlock_irq(&transfer->lock);
+ }
+ if (!current_req[0] && !current_req[1])
+ break;
+ msleep(100);
+ }
+
+ if (!timeout)
+ pr_warn("%s: timeout waiting for transfers to complete: tr0=%u, tr1=%u\n",
+ __func__, !!current_req[0], !!current_req[1]);
+
+ ret_in = mud_regmap_usb_ep_set_halt(mreg->in_ep);
+ ret_out = mud_regmap_usb_ep_set_halt(mreg->out_ep);
+ if (ret_in || ret_out)
+ pr_err("%s: Failed to halt endpoint(s) ret_in=%d, ret_out=%d\n",
+ __func__, ret_in, ret_out);
+
+ spin_lock_irq(&mreg->lock);
+ mreg->pending_stall = false;
+ mreg->stalled = true;
+ spin_unlock_irq(&mreg->lock);
+}
+
+static void mud_regmap_queue_stall(struct mud_regmap *mreg, int error)
+{
+ unsigned long flags;
+
+ udebug(0, "%s: error=%d\n", __func__, error);
+
+ if (error < -255 || error > 0)
+ error = -EREMOTEIO;
+
+ spin_lock_irqsave(&mreg->lock, flags);
+ if (!mreg->pending_stall) {
+ mreg->errno = -error;
+ mreg->pending_stall = true;
+ wake_up(&mreg->waitq);
+ }
+ spin_unlock_irqrestore(&mreg->lock, flags);
+}
+
+static int mud_regmap_queue_header(struct mud_regmap *mreg)
+{
+ struct mud_regmap_transfer *transfer = NULL;
+ bool header_queued, run;
+ unsigned long flags;
+ int ret;
+
+ spin_lock_irqsave(&mreg->lock, flags);
+ header_queued = mreg->header_queued;
+ run = mreg->run;
+ if (!header_queued && run) {
+ transfer = list_first_entry_or_null(&mreg->free_transfers, struct mud_regmap_transfer, node);
+ if (transfer) {
+ mreg->header_queued = true;
+ list_del_init(&transfer->node);
+ }
+ }
+ spin_unlock_irqrestore(&mreg->lock, flags);
+
+ udebug(4, "%s: header_queued=%u, transfer=%px\n", __func__,
+ header_queued, transfer);
+
+ if (header_queued || !run)
+ return 0;
+
+ if (!transfer) {
+ udebug(4, "Run out of transfers\n");
+ return 0;
+ }
+
+ spin_lock_irqsave(&transfer->lock, flags);
+ ret = usb_ep_queue(mreg->out_ep, transfer->header_req, GFP_ATOMIC);
+ if (!ret)
+ transfer->current_req = transfer->header_req;
+ spin_unlock_irqrestore(&transfer->lock, flags);
+ if (ret) {
+ pr_warn("Queueing header failed, ret=%d\n", ret);
+ mud_regmap_queue_stall(mreg, ret);
+ }
+
+ return ret;
+}
+
+static void mud_regmap_queue_status(struct mud_regmap_transfer *transfer, int error)
+{
+ struct regmap_usb_status *status = transfer->status_req->buf;
+ unsigned long flags;
+ int ret;
+
+ udebug(4, "%s: tag=%u, error=%d\n", __func__, transfer->tag, error);
+
+ if (error < -255 || error > 0)
+ error = -EREMOTEIO;
+
+ spin_lock_irqsave(&transfer->lock, flags);
+ status->tag = cpu_to_le16(transfer->tag);
+ status->status = -error;
+ ret = usb_ep_queue(transfer->mreg->in_ep, transfer->status_req, GFP_ATOMIC);
+ if (!ret)
+ transfer->current_req = transfer->status_req;
+ spin_unlock_irqrestore(&transfer->lock, flags);
+ if (ret) {
+ pr_warn("Queueing status failed, ret=%d\n", ret);
+ mud_regmap_queue_stall(transfer->mreg, ret);
+ }
+}
+
+static void mud_regmap_queue_for_processing(struct mud_regmap_transfer *transfer)
+{
+ struct mud_regmap *mreg = transfer->mreg;
+ unsigned long flags;
+
+ spin_lock_irqsave(&mreg->lock, flags);
+ list_add_tail(&transfer->node, &mreg->pending_transfers);
+ spin_unlock_irqrestore(&mreg->lock, flags);
+
+ wake_up(&mreg->waitq);
+}
+
+static bool mud_regmap_check_req_status(struct usb_request *req)
+{
+ switch (req->status) {
+ case -ECONNABORTED: /* hardware forced ep reset */
+ case -ECONNRESET: /* request dequeued */
+ case -ESHUTDOWN: /* disconnect from host */
+ case -EOVERFLOW: /* buffer overrun on read means that
+ * we didn't provide a big enough
+ * buffer.
+ */
+ case -EREMOTEIO: /* short read */
+ udebug(0, "%s: bail out, status=%d\n", __func__, req->status);
+ return false;
+ }
+
+ return true;
+}
+
+static void mud_regmap_header_req_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ struct mud_regmap_transfer *transfer = req->context;
+ struct mud_regmap *mreg = transfer->mreg;
+ struct regmap_usb_header *header = req->buf;
+ unsigned int index = le16_to_cpu(header->index);
+ u32 hflags = le32_to_cpu(header->flags);
+ bool in = hflags & REGMAP_USB_HEADER_FLAG_IN;
+ u32 length = le32_to_cpu(header->length);
+ unsigned long flags;
+ bool run;
+ int ret;
+
+ udebug(4, "%s: status=%d, actual=%u, length=%u\n", __func__, req->status, req->actual, req->length);
+ udebug(4, " signature=0x%x, index=%u, tag=%u, flags=0x%x, regnr=0x%x, length=%u, in=%u\n",
+ le32_to_cpu(header->signature), le16_to_cpu(header->index), le16_to_cpu(header->tag),
+ le32_to_cpu(header->flags), le32_to_cpu(header->regnr), le32_to_cpu(header->length), in);
+
+ spin_lock_irqsave(&transfer->lock, flags);
+ transfer->current_req = NULL;
+ transfer->in = in;
+ transfer->index = index;
+ transfer->tag = le16_to_cpu(header->tag);
+ transfer->flags = hflags;
+ transfer->regnr = le32_to_cpu(header->regnr);
+ spin_unlock_irqrestore(&transfer->lock, flags);
+
+ if (!mud_regmap_check_req_status(req))
+ return;
+
+ spin_lock_irqsave(&mreg->lock, flags);
+ mreg->header_queued = false;
+ run = mreg->run;
+ spin_unlock_irqrestore(&mreg->lock, flags);
+
+ if (!run)
+ return;
+
+ if (req->status) {
+ udebug(0, "%s: Failed, status=%d\n", __func__, req->status);
+ ret = req->status;
+ goto error;
+ }
+
+ if (req->actual != req->length) {
+ udebug(0, "%s: Wrong length\n", __func__);
+ ret = -EREMOTEIO;
+ goto error;
+ }
+
+ if (le32_to_cpu(header->signature) != REGMAP_USB_HEADER_SIGNATURE) {
+ udebug(0, "%s: Wrong signature\n", __func__);
+ ret = -EINVAL;
+ goto error;
+ }
+
+ if (index >= mreg->num_cells) {
+ udebug(0, "%s: No such index %u\n", __func__, index);
+ ret = -ENOENT;
+ goto error;
+ }
+
+ /* FIXME: Temporary test code */
+ if (index == 2 && !strcmp(mreg->cells[index]->ops->name, "mud-test")) {
+ udebug(0, "%s: Test stall + reset\n", __func__);
+ ret = -ENOENT;
+ goto error;
+ }
+
+ if (length > mreg->max_transfer_size) {
+ udebug(0, "%s: Length overflow %u\n", __func__, length);
+ ret = -EOVERFLOW;
+ goto error;
+ }
+
+ if (in) {
+ transfer->buf_in_req->length = length;
+ mud_regmap_queue_for_processing(transfer);
+ mud_regmap_queue_header(mreg);
+ } else {
+ transfer->buf_out_req->length = length;
+
+ spin_lock_irqsave(&transfer->lock, flags);
+ ret = usb_ep_queue(mreg->out_ep, transfer->buf_out_req, GFP_ATOMIC);
+ if (!ret)
+ transfer->current_req = transfer->buf_out_req;
+ spin_unlock_irqrestore(&transfer->lock, flags);
+ if (ret) {
+ pr_warn("Queueing buf out failed, ret=%d\n", ret);
+ goto error;
+ }
+ }
+
+ return;
+
+error:
+ mud_regmap_queue_stall(mreg, ret);
+}
+
+static void mud_regmap_buf_out_req_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ struct mud_regmap_transfer *transfer = req->context;
+ struct mud_regmap *mreg = transfer->mreg;
+ unsigned long flags;
+ bool run;
+ int ret;
+
+ udebug(4, "%s: status=%d, actual=%u, length=%u, tag=%u\n", __func__,
+ req->status, req->actual, req->length, transfer->tag);
+
+ spin_lock_irqsave(&transfer->lock, flags);
+ transfer->current_req = NULL;
+ spin_unlock_irqrestore(&transfer->lock, flags);
+
+ if (!mud_regmap_check_req_status(req))
+ return;
+
+ spin_lock_irqsave(&mreg->lock, flags);
+ run = mreg->run;
+ spin_unlock_irqrestore(&mreg->lock, flags);
+
+ if (!run)
+ return;
+
+ if (req->status) {
+ udebug(0, "%s: Failed, status=%d\n", __func__, req->status);
+ ret = req->status;
+ goto error;
+ }
+
+ if (req->actual != req->length) {
+ udebug(0, "%s: Wrong length\n", __func__);
+ ret = -EREMOTEIO;
+ goto error;
+ }
+
+ mud_regmap_queue_for_processing(transfer);
+ mud_regmap_queue_header(mreg);
+
+ return;
+
+error:
+ mud_regmap_queue_stall(mreg, ret);
+}
+
+static void mud_regmap_buf_in_req_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ struct mud_regmap_transfer *transfer = req->context;
+ struct mud_regmap *mreg = transfer->mreg;
+ unsigned long flags;
+ bool run;
+
+ udebug(4, "%s: status=%d, actual=%u, length=%u, tag=%u\n", __func__,
+ req->status, req->actual, req->length, transfer->tag);
+
+ spin_lock_irqsave(&transfer->lock, flags);
+ transfer->current_req = NULL;
+ spin_unlock_irqrestore(&transfer->lock, flags);
+
+ if (!mud_regmap_check_req_status(req))
+ return;
+
+ spin_lock_irqsave(&mreg->lock, flags);
+ run = mreg->run;
+ spin_unlock_irqrestore(&mreg->lock, flags);
+
+ if (!run)
+ return;
+
+ if (req->actual != req->length) {
+ udebug(0, "%s: Wrong length\n", __func__);
+ mud_regmap_queue_stall(mreg, -EREMOTEIO);
+ }
+}
+
+static void mud_regmap_status_req_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ struct mud_regmap_transfer *transfer = req->context;
+ struct mud_regmap *mreg = transfer->mreg;
+ unsigned long flags;
+ bool run;
+
+ udebug(4, "%s: status=%d, actual=%u, length=%u, tag=%u\n", __func__,
+ req->status, req->actual, req->length, transfer->tag);
+
+ spin_lock_irqsave(&transfer->lock, flags);
+ transfer->current_req = NULL;
+ spin_unlock_irqrestore(&transfer->lock, flags);
+
+ if (!mud_regmap_check_req_status(req))
+ return;
+
+ spin_lock_irqsave(&mreg->lock, flags);
+ run = mreg->run;
+ list_add_tail(&transfer->node, &mreg->free_transfers);
+ spin_unlock_irqrestore(&mreg->lock, flags);
+
+ if (!run)
+ return;
+
+ if (req->actual != req->length) {
+ udebug(0, "%s: Wrong length\n", __func__);
+ mud_regmap_queue_stall(mreg, -EREMOTEIO);
+ return;
+ }
+
+ /* Make sure it's queued */
+ mud_regmap_queue_header(mreg);
+}
+
+static void mud_regmap_free_transfer(struct mud_regmap_transfer *transfer)
+{
+ if (!transfer)
+ return;
+
+ kfree(transfer->status_req->buf);
+ usb_ep_free_request(transfer->mreg->in_ep, transfer->status_req);
+
+ usb_ep_free_request(transfer->mreg->in_ep, transfer->buf_in_req);
+
+ kfree(transfer->buf_out_req->buf);
+ usb_ep_free_request(transfer->mreg->out_ep, transfer->buf_out_req);
+
+ kfree(transfer->header_req->buf);
+ usb_ep_free_request(transfer->mreg->out_ep, transfer->header_req);
+
+ kfree(transfer);
+}
+
+static struct mud_regmap_transfer *mud_regmap_alloc_transfer(struct mud_regmap *mreg)
+{
+ struct mud_regmap_transfer *transfer;
+ struct regmap_usb_header *header;
+ struct regmap_usb_status *status;
+ void *buf;
+
+ transfer = kzalloc(sizeof(*transfer), GFP_KERNEL);
+ header = kzalloc(sizeof(*header), GFP_KERNEL);
+ status = kzalloc(sizeof(*status), GFP_KERNEL);
+ buf = kmalloc(mreg->max_transfer_size, GFP_KERNEL);
+ if (!transfer || !header || !status || !buf)
+ goto free;
+
+ spin_lock_init(&transfer->lock);
+ transfer->mreg = mreg;
+
+ transfer->header_req = usb_ep_alloc_request(mreg->out_ep, GFP_KERNEL);
+ if (!transfer->header_req)
+ goto free;
+
+ transfer->header_req->context = transfer;
+ transfer->header_req->complete = mud_regmap_header_req_complete;
+ transfer->header_req->buf = header;
+ transfer->header_req->length = sizeof(*header);
+
+ transfer->buf_out_req = usb_ep_alloc_request(mreg->out_ep, GFP_KERNEL);
+ if (!transfer->buf_out_req)
+ goto free;
+
+ transfer->buf_out_req->context = transfer;
+ transfer->buf_out_req->complete = mud_regmap_buf_out_req_complete;
+ transfer->buf_out_req->buf = buf;
+
+ transfer->buf_in_req = usb_ep_alloc_request(mreg->in_ep, GFP_KERNEL);
+ if (!transfer->buf_in_req)
+ goto free;
+
+ transfer->buf_in_req->context = transfer;
+ transfer->buf_in_req->complete = mud_regmap_buf_in_req_complete;
+ transfer->buf_in_req->buf = buf;
+
+ transfer->status_req = usb_ep_alloc_request(mreg->in_ep, GFP_KERNEL);
+ if (!transfer->status_req)
+ goto free;
+
+ transfer->status_req->context = transfer;
+ transfer->status_req->complete = mud_regmap_status_req_complete;
+ transfer->status_req->buf = status;
+ transfer->status_req->length = sizeof(*status);
+ status->signature = cpu_to_le32(REGMAP_USB_STATUS_SIGNATURE);
+
+ return transfer;
+
+free:
+ if (transfer->status_req)
+ usb_ep_free_request(mreg->in_ep, transfer->status_req);
+ if (transfer->buf_in_req)
+ usb_ep_free_request(mreg->in_ep, transfer->buf_in_req);
+ if (transfer->buf_out_req)
+ usb_ep_free_request(mreg->out_ep, transfer->buf_out_req);
+ if (transfer->header_req)
+ usb_ep_free_request(mreg->out_ep, transfer->header_req);
+ kfree(buf);
+ kfree(status);
+ kfree(header);
+ kfree(transfer);
+
+ return NULL;
+}
+
+static void mud_regmap_free_transfers(struct mud_regmap *mreg)
+{
+ mud_regmap_free_transfer(mreg->transfers[0]);
+ mud_regmap_free_transfer(mreg->transfers[1]);
+}
+
+static int mud_regmap_alloc_transfers(struct mud_regmap *mreg)
+{
+retry:
+ udebug(1, "%s: max_transfer_size=%u\n", __func__,
+ mreg->max_transfer_size);
+
+ mreg->transfers[0] = mud_regmap_alloc_transfer(mreg);
+ mreg->transfers[1] = mud_regmap_alloc_transfer(mreg);
+ if (!mreg->transfers[0] || !mreg->transfers[1]) {
+ mud_regmap_free_transfers(mreg);
+ if (mreg->max_transfer_size < 512)
+ return -ENOMEM; /* No point in retrying we'll fail later anyway */
+
+ mreg->max_transfer_size /= 2;
+ goto retry;
+ }
+
+ list_add_tail(&mreg->transfers[0]->node, &mreg->free_transfers);
+ list_add_tail(&mreg->transfers[1]->node, &mreg->free_transfers);
+
+ return 0;
+}
+
+static void mud_regmap_reset_state(struct mud_regmap *mreg)
+{
+ unsigned long flags;
+
+ udebug(5, "%s:\n", __func__);
+
+ spin_lock_irqsave(&mreg->lock, flags);
+
+ mreg->pending_protocol_reset = false;
+ mreg->pending_stall = false;
+ mreg->stalled = false;
+ mreg->header_queued = false;
+ mreg->errno = 0;
+
+ INIT_LIST_HEAD(&mreg->free_transfers);
+ INIT_LIST_HEAD(&mreg->pending_transfers);
+
+ INIT_LIST_HEAD(&mreg->transfers[0]->node);
+ list_add_tail(&mreg->transfers[0]->node, &mreg->free_transfers);
+ INIT_LIST_HEAD(&mreg->transfers[1]->node);
+ list_add_tail(&mreg->transfers[1]->node, &mreg->free_transfers);
+
+ spin_unlock_irqrestore(&mreg->lock, flags);
+}
+
+static void mud_regmap_protocol_reset(struct mud_regmap *mreg)
+{
+ struct usb_composite_dev *cdev = mreg->cdev;
+ int ret;
+
+ udebug(0, "%s: IN\n", __func__);
+
+ mud_regmap_reset_state(mreg);
+
+ /* Complete the reset request and return the error */
+ ret = usb_ep_queue(cdev->gadget->ep0, cdev->req, GFP_ATOMIC);
+ if (ret < 0)
+ /* FIXME: Should we stall (again) and let the host retry? */
+ ERROR(cdev, "usb_ep_queue error on ep0 %d\n", ret);
+
+ mud_regmap_queue_header(mreg);
+
+ udebug(0, "%s: OUT\n", __func__);
+}
+
+static void mud_regmap_worker(struct work_struct *work)
+{
+ struct mud_regmap *mreg = container_of(work, struct mud_regmap, work);
+ struct mud_regmap_transfer *transfer;
+ unsigned int index, regnr;
+ struct f_mud_cell *cell;
+ bool in, stalled;
+ int ret, error;
+ size_t len;
+ u32 flags;
+
+ for (index = 0; index < mreg->num_cells; index++) {
+ cell = mreg->cells[index];
+ if (cell->ops->enable)
+ cell->ops->enable(cell);
+ }
+
+ while (mreg->run) {
+ spin_lock_irq(&mreg->lock);
+ stalled = mreg->stalled;
+ transfer = list_first_entry_or_null(&mreg->pending_transfers, struct mud_regmap_transfer, node);
+ if (transfer)
+ list_del_init(&transfer->node);
+ spin_unlock_irq(&mreg->lock);
+
+ if (mreg->pending_protocol_reset) {
+ mud_regmap_protocol_reset(mreg);
+ continue;
+ }
+
+ if (mreg->pending_stall) {
+ mud_regmap_stall(mreg);
+ continue;
+ }
+
+ if (stalled || !transfer) {
+ /* Use _interruptible to avoid triggering hung task warnings */
+ wait_event_interruptible(mreg->waitq, !mreg->run ||
+ mreg->pending_stall ||
+ mreg->pending_protocol_reset ||
+ !list_empty(&mreg->pending_transfers));
+ continue;
+ }
+
+ spin_lock_irq(&transfer->lock);
+ index = transfer->index;
+ in = transfer->in;
+ regnr = transfer->regnr;
+ flags = transfer->flags;
+ if (in)
+ len = transfer->buf_in_req->length;
+ else
+ len = transfer->buf_out_req->length;
+ spin_unlock_irq(&transfer->lock);
+
+ // FIXME: check len?
+
+ cell = mreg->cells[index];
+
+ if (in) {
+ udebug(2, "cell->ops->readreg(regnr=0x%02x, len=%zu)\n", regnr, len);
+
+ error = cell->ops->readreg(cell, regnr,
+ transfer->buf_in_req->buf, &len,
+ flags & REGMAP_USB_HEADER_FLAG_COMPRESSION_MASK);
+ if (error) {
+ udebug(2, " error=%d\n", error);
+ // FIXME: Stall or run its course to status stage? Stalling takes time...
+ }
+
+ /* In case the buffer was compressed */
+ transfer->buf_in_req->length = len;
+
+ ret = usb_ep_queue(mreg->in_ep, transfer->buf_in_req, GFP_KERNEL);
+ if (ret) {
+ pr_warn("Failed to queue buf_in_req ret=%d\n", ret);
+ mud_regmap_queue_stall(transfer->mreg, ret);
+ continue;
+ }
+
+ mud_regmap_queue_status(transfer, error);
+ } else {
+ udebug(2, "cell->ops->writereg(regnr=0x%02x, len=%zu)\n", regnr, len);
+
+ error = cell->ops->writereg(cell, regnr,
+ transfer->buf_out_req->buf, len,
+ flags & REGMAP_USB_HEADER_FLAG_COMPRESSION_MASK);
+ if (error)
+ udebug(2, " error=%d\n", error);
+
+ mud_regmap_queue_status(transfer, error);
+ }
+ }
+
+ for (index = 0; index < mreg->num_cells; index++) {
+ cell = mreg->cells[index];
+ if (cell->ops->disable)
+ cell->ops->disable(cell);
+ }
+}
+
+static int mud_regmap_start(struct mud_regmap *mreg)
+{
+ unsigned long flags;
+
+ udebug(1, "%s:\n", __func__);
+
+ mud_regmap_reset_state(mreg);
+
+ usb_ep_enable(mreg->in_ep);
+ usb_ep_enable(mreg->out_ep);
+
+ spin_lock_irqsave(&mreg->lock, flags);
+ mreg->run = true;
+ spin_unlock_irqrestore(&mreg->lock, flags);
+
+ queue_work(mreg->workq, &mreg->work);
+
+ return mud_regmap_queue_header(mreg);
+}
+
+void mud_regmap_stop(struct mud_regmap *mreg)
+{
+ unsigned long flags;
+
+ udebug(1, "%s:\n", __func__);
+
+ spin_lock_irqsave(&mreg->lock, flags);
+ mreg->run = false;
+ spin_unlock_irqrestore(&mreg->lock, flags);
+
+ wake_up(&mreg->waitq);
+
+ usb_ep_disable(mreg->in_ep);
+ usb_ep_disable(mreg->out_ep);
+}
+
+int mud_regmap_setup(struct mud_regmap *mreg, struct usb_function *f,
+ const struct usb_ctrlrequest *ctrl)
+{
+ struct usb_composite_dev *cdev = f->config->cdev;
+ u16 length = le16_to_cpu(ctrl->wLength);
+ struct usb_request *req = cdev->req;
+ int ret;
+
+ udebug(1, "%s: bRequest=0x%x, length=%u\n", __func__, ctrl->bRequest, length);
+
+ if (ctrl->bRequestType != (USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_INTERFACE))
+ return -EINVAL;
+
+ if (ctrl->bRequest == USB_REQ_GET_DESCRIPTOR) {
+ u8 type = le16_to_cpu(ctrl->wValue) >> 8;
+ u8 index = le16_to_cpu(ctrl->wValue) & 0xff;
+
+ udebug(1, " USB_REQ_GET_DESCRIPTOR: type=%u index=%u\n", type, index);
+
+ if (type == REGMAP_USB_DT_INTERFACE && index == 0) {
+ struct regmap_usb_interface_descriptor *desc = req->buf;
+
+ desc->bLength = sizeof(*desc);
+ desc->bDescriptorType = REGMAP_USB_DT_INTERFACE;
+ desc->bNumRegmaps = mreg->num_cells;
+ req->zero = 0;
+ req->length = min_t(unsigned int, length, sizeof(*desc));
+ } else if (type == REGMAP_USB_DT_MAP) {
+ struct regmap_usb_map_descriptor *desc = req->buf;
+ unsigned int max_transfer_size;
+ struct f_mud_cell *cell;
+
+ if (index >= mreg->num_cells)
+ return -ENOENT;
+
+ cell = mreg->cells[index];
+
+ desc->bLength = sizeof(*desc);
+ desc->bDescriptorType = REGMAP_USB_DT_MAP;
+ if (strscpy_pad(desc->name, cell->ops->name, 32) < 0)
+ return -EINVAL;
+ desc->bRegisterValueBits = cell->ops->regval_bytes * 8;
+ desc->bCompression = cell->ops->compression;
+ max_transfer_size = min(mreg->max_transfer_size,
+ cell->ops->max_transfer_size);
+ desc->bMaxTransferSizeOrder = ilog2(max_transfer_size);
+ req->zero = 0;
+ req->length = min_t(unsigned int, length, sizeof(*desc));
+ } else {
+ return -EINVAL;
+ }
+ } else if (ctrl->bRequest == REGMAP_USB_REQ_PROTOCOL_RESET && length == 1) {
+ unsigned long flags;
+
+ DBG(cdev, "Protocol reset request: errno=%u\n", mreg->errno);
+
+ spin_lock_irqsave(&mreg->lock, flags);
+ mreg->pending_protocol_reset = true;
+ *(u8 *)req->buf = mreg->errno;
+ mreg->errno = 0;
+ spin_unlock_irqrestore(&mreg->lock, flags);
+
+ req->zero = 0;
+ req->length = length;
+
+ wake_up(&mreg->waitq);
+
+ return USB_GADGET_DELAYED_STATUS;
+ } else {
+ return -EINVAL;
+ }
+
+ ret = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
+ if (ret < 0)
+ ERROR(cdev, "usb_ep_queue error on ep0 %d\n", ret);
+
+ return ret;
+}
+
+int mud_regmap_set_alt(struct mud_regmap *mreg, struct usb_function *f)
+{
+ struct usb_composite_dev *cdev = f->config->cdev;
+
+ DBG(cdev, "%s: reset\n", __func__);
+
+ if (!mreg->in_ep->desc || !mreg->out_ep->desc) {
+ DBG(cdev, "%s: init\n", __func__);
+ if (config_ep_by_speed(cdev->gadget, f, mreg->in_ep) ||
+ config_ep_by_speed(cdev->gadget, f, mreg->out_ep)) {
+ mreg->in_ep->desc = NULL;
+ mreg->out_ep->desc = NULL;
+ return -EINVAL;
+ }
+ }
+
+ mud_regmap_stop(mreg);
+
+ return mud_regmap_start(mreg);
+}
+
+struct mud_regmap *mud_regmap_init(struct usb_composite_dev *cdev,
+ struct usb_ep *in_ep, struct usb_ep *out_ep,
+ struct f_mud_cell **cells, unsigned int num_cells)
+{
+ size_t max_transfer_size = 0;
+ struct mud_regmap *mreg;
+ unsigned int i;
+ int ret;
+
+ for (i = 0; i < num_cells; i++) {
+ size_t cell_max = cells[i]->ops->max_transfer_size;
+
+ if (!is_power_of_2(cell_max)) {
+ pr_err("%s: Max transfer size must be a power of two: %u\n",
+ __func__, cell_max);
+ return ERR_PTR(-EINVAL);
+ }
+ max_transfer_size = max(max_transfer_size, cell_max);
+ }
+
+ mreg = kzalloc(sizeof(*mreg), GFP_KERNEL);
+ if (!mreg)
+ return ERR_PTR(-ENOMEM);
+
+ mreg->cdev = cdev;
+ mreg->in_ep = in_ep;
+ mreg->out_ep = out_ep;
+ mreg->cells = cells;
+ mreg->num_cells = num_cells;
+ mreg->max_transfer_size = max_transfer_size;
+
+ spin_lock_init(&mreg->lock);
+
+ INIT_LIST_HEAD(&mreg->free_transfers);
+ INIT_LIST_HEAD(&mreg->pending_transfers);
+
+ INIT_WORK(&mreg->work, mud_regmap_worker);
+ init_waitqueue_head(&mreg->waitq);
+
+ mreg->workq = alloc_workqueue("mud_regmap", 0, 0);
+ if (!mreg->workq) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ ret = mud_regmap_alloc_transfers(mreg);
+ if (ret)
+ goto fail;
+
+ return mreg;
+fail:
+ mud_regmap_cleanup(mreg);
+
+ return ERR_PTR(ret);
+}
+
+void mud_regmap_cleanup(struct mud_regmap *mreg)
+{
+ cancel_work_sync(&mreg->work);
+ if (mreg->workq)
+ destroy_workqueue(mreg->workq);
+ mud_regmap_free_transfers(mreg);
+ kfree(mreg);
+}
This is the gadget side of the mfd host driver. It provides a USB function that drivers can hook into providing functions like gpio and display as regmaps to the host. These drivers are configured through configfs. Signed-off-by: Noralf Trønnes <noralf@tronnes.org> --- drivers/usb/gadget/Kconfig | 10 + drivers/usb/gadget/function/Makefile | 2 + drivers/usb/gadget/function/f_mud.c | 913 ++++++++++++++++++++++ drivers/usb/gadget/function/f_mud.h | 210 +++++ drivers/usb/gadget/function/mud_regmap.c | 936 +++++++++++++++++++++++ 5 files changed, 2071 insertions(+) create mode 100644 drivers/usb/gadget/function/f_mud.c create mode 100644 drivers/usb/gadget/function/f_mud.h create mode 100644 drivers/usb/gadget/function/mud_regmap.c