diff options
Diffstat (limited to 'drivers/usb/gadget/function')
52 files changed, 8016 insertions, 1360 deletions
diff --git a/drivers/usb/gadget/function/Makefile b/drivers/usb/gadget/function/Makefile index 5d3a6cf02218..7ce1637276f0 100644 --- a/drivers/usb/gadget/function/Makefile +++ b/drivers/usb/gadget/function/Makefile @@ -41,9 +41,15 @@ obj-$(CONFIG_USB_F_UAC1_LEGACY) += usb_f_uac1_legacy.o usb_f_uac2-y := f_uac2.o obj-$(CONFIG_USB_F_UAC2) += usb_f_uac2.o usb_f_uvc-y := f_uvc.o uvc_queue.o uvc_v4l2.o uvc_video.o uvc_configfs.o +ifneq ($(CONFIG_TRACING),) + CFLAGS_uvc_trace.o := -I$(src) + usb_f_uvc-y += uvc_trace.o +endif obj-$(CONFIG_USB_F_UVC) += usb_f_uvc.o usb_f_midi-y := f_midi.o obj-$(CONFIG_USB_F_MIDI) += usb_f_midi.o +usb_f_midi2-y := f_midi2.o +obj-$(CONFIG_USB_F_MIDI2) += usb_f_midi2.o usb_f_hid-y := f_hid.o obj-$(CONFIG_USB_F_HID) += usb_f_hid.o usb_f_printer-y := f_printer.o diff --git a/drivers/usb/gadget/function/f_acm.c b/drivers/usb/gadget/function/f_acm.c index cb523f118f04..106046e17c4e 100644 --- a/drivers/usb/gadget/function/f_acm.c +++ b/drivers/usb/gadget/function/f_acm.c @@ -11,12 +11,15 @@ /* #define VERBOSE_DEBUG */ +#include <linux/cleanup.h> #include <linux/slab.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/device.h> #include <linux/err.h> +#include <linux/usb/gadget.h> + #include "u_serial.h" @@ -41,6 +44,7 @@ struct f_acm { struct gserial port; u8 ctrl_id, data_id; u8 port_num; + u8 bInterfaceProtocol; u8 pending; @@ -89,7 +93,7 @@ acm_iad_descriptor = { .bInterfaceCount = 2, // control + data .bFunctionClass = USB_CLASS_COMM, .bFunctionSubClass = USB_CDC_SUBCLASS_ACM, - .bFunctionProtocol = USB_CDC_ACM_PROTO_AT_V25TER, + /* .bFunctionProtocol = DYNAMIC */ /* .iFunction = DYNAMIC */ }; @@ -101,7 +105,7 @@ static struct usb_interface_descriptor acm_control_interface_desc = { .bNumEndpoints = 1, .bInterfaceClass = USB_CLASS_COMM, .bInterfaceSubClass = USB_CDC_SUBCLASS_ACM, - .bInterfaceProtocol = USB_CDC_ACM_PROTO_AT_V25TER, + /* .bInterfaceProtocol = DYNAMIC */ /* .iInterface = DYNAMIC */ }; @@ -612,6 +616,7 @@ acm_bind(struct usb_configuration *c, struct usb_function *f) struct usb_string *us; int status; struct usb_ep *ep; + struct usb_request *request __free(free_usb_request) = NULL; /* REVISIT might want instance-specific strings to help * distinguish instances ... @@ -629,7 +634,7 @@ acm_bind(struct usb_configuration *c, struct usb_function *f) /* allocate instance-specific interface IDs, and patch descriptors */ status = usb_interface_id(c, f); if (status < 0) - goto fail; + return status; acm->ctrl_id = status; acm_iad_descriptor.bFirstInterface = status; @@ -638,40 +643,41 @@ acm_bind(struct usb_configuration *c, struct usb_function *f) status = usb_interface_id(c, f); if (status < 0) - goto fail; + return status; acm->data_id = status; acm_data_interface_desc.bInterfaceNumber = status; acm_union_desc.bSlaveInterface0 = status; acm_call_mgmt_descriptor.bDataInterface = status; - status = -ENODEV; - /* allocate instance-specific endpoints */ ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_in_desc); if (!ep) - goto fail; + return -ENODEV; acm->port.in = ep; ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_out_desc); if (!ep) - goto fail; + return -ENODEV; acm->port.out = ep; ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_notify_desc); if (!ep) - goto fail; + return -ENODEV; acm->notify = ep; + acm_iad_descriptor.bFunctionProtocol = acm->bInterfaceProtocol; + acm_control_interface_desc.bInterfaceProtocol = acm->bInterfaceProtocol; + /* allocate notification */ - acm->notify_req = gs_alloc_req(ep, - sizeof(struct usb_cdc_notification) + 2, - GFP_KERNEL); - if (!acm->notify_req) - goto fail; + request = gs_alloc_req(ep, + sizeof(struct usb_cdc_notification) + 2, + GFP_KERNEL); + if (!request) + return -ENODEV; - acm->notify_req->complete = acm_cdc_notify_complete; - acm->notify_req->context = acm; + request->complete = acm_cdc_notify_complete; + request->context = acm; /* support all relevant hardware speeds... we expect that when * hardware is dual speed, all bulk-capable endpoints work at @@ -688,24 +694,16 @@ acm_bind(struct usb_configuration *c, struct usb_function *f) status = usb_assign_descriptors(f, acm_fs_function, acm_hs_function, acm_ss_function, acm_ss_function); if (status) - goto fail; + return status; + + acm->notify_req = no_free_ptr(request); dev_dbg(&cdev->gadget->dev, - "acm ttyGS%d: %s speed IN/%s OUT/%s NOTIFY/%s\n", + "acm ttyGS%d: IN/%s OUT/%s NOTIFY/%s\n", acm->port_num, - gadget_is_superspeed(c->cdev->gadget) ? "super" : - gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", acm->port.in->name, acm->port.out->name, acm->notify->name); return 0; - -fail: - if (acm->notify_req) - gs_free_req(acm->notify, acm->notify_req); - - ERROR(cdev, "%s/%p: can't bind, err %d\n", f->name, f, status); - - return status; } static void acm_unbind(struct usb_configuration *c, struct usb_function *f) @@ -721,8 +719,14 @@ static void acm_unbind(struct usb_configuration *c, struct usb_function *f) static void acm_free_func(struct usb_function *f) { struct f_acm *acm = func_to_acm(f); + struct f_serial_opts *opts; + + opts = container_of(f->fi, struct f_serial_opts, func_inst); kfree(acm); + mutex_lock(&opts->lock); + opts->instances--; + mutex_unlock(&opts->lock); } static void acm_resume(struct usb_function *f) @@ -763,7 +767,11 @@ static struct usb_function *acm_alloc_func(struct usb_function_instance *fi) acm->port.func.disable = acm_disable; opts = container_of(fi, struct f_serial_opts, func_inst); + mutex_lock(&opts->lock); acm->port_num = opts->port_num; + acm->bInterfaceProtocol = opts->protocol; + opts->instances++; + mutex_unlock(&opts->lock); acm->port.func.unbind = acm_unbind; acm->port.func.free_func = acm_free_func; acm->port.func.resume = acm_resume; @@ -814,11 +822,42 @@ static ssize_t f_acm_port_num_show(struct config_item *item, char *page) CONFIGFS_ATTR_RO(f_acm_, port_num); +static ssize_t f_acm_protocol_show(struct config_item *item, char *page) +{ + return sprintf(page, "%u\n", to_f_serial_opts(item)->protocol); +} + +static ssize_t f_acm_protocol_store(struct config_item *item, + const char *page, size_t count) +{ + struct f_serial_opts *opts = to_f_serial_opts(item); + int ret; + + mutex_lock(&opts->lock); + + if (opts->instances) { + ret = -EBUSY; + goto out; + } + + ret = kstrtou8(page, 0, &opts->protocol); + if (ret) + goto out; + ret = count; + +out: + mutex_unlock(&opts->lock); + return ret; +} + +CONFIGFS_ATTR(f_acm_, protocol); + static struct configfs_attribute *acm_attrs[] = { #ifdef CONFIG_U_SERIAL_CONSOLE &f_acm_attr_console, #endif &f_acm_attr_port_num, + &f_acm_attr_protocol, NULL, }; @@ -834,6 +873,7 @@ static void acm_free_instance(struct usb_function_instance *fi) opts = container_of(fi, struct f_serial_opts, func_inst); gserial_free_line(opts->port_num); + mutex_destroy(&opts->lock); kfree(opts); } @@ -845,7 +885,9 @@ static struct usb_function_instance *acm_alloc_instance(void) opts = kzalloc(sizeof(*opts), GFP_KERNEL); if (!opts) return ERR_PTR(-ENOMEM); + opts->protocol = USB_CDC_ACM_PROTO_AT_V25TER; opts->func_inst.free_func_inst = acm_free_instance; + mutex_init(&opts->lock); ret = gserial_alloc_line(&opts->port_num); if (ret) { kfree(opts); @@ -856,4 +898,5 @@ static struct usb_function_instance *acm_alloc_instance(void) return &opts->func_inst; } DECLARE_USB_FUNCTION_INIT(acm, acm_alloc_instance, acm_alloc_func); +MODULE_DESCRIPTION("USB CDC serial (ACM) function driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/function/f_ecm.c b/drivers/usb/gadget/function/f_ecm.c index a7ab30e603e2..675d2bc538a4 100644 --- a/drivers/usb/gadget/function/f_ecm.c +++ b/drivers/usb/gadget/function/f_ecm.c @@ -8,11 +8,15 @@ /* #define VERBOSE_DEBUG */ +#include <linux/cleanup.h> #include <linux/slab.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/device.h> #include <linux/etherdevice.h> +#include <linux/string_choices.h> + +#include <linux/usb/gadget.h> #include "u_ether.h" #include "u_ether_configfs.h" @@ -65,17 +69,6 @@ static inline struct f_ecm *func_to_ecm(struct usb_function *f) return container_of(f, struct f_ecm, port.func); } -/* peak (theoretical) bulk transfer rate in bits-per-second */ -static inline unsigned ecm_bitrate(struct usb_gadget *g) -{ - if (gadget_is_superspeed(g) && g->speed == USB_SPEED_SUPER) - return 13 * 1024 * 8 * 1000 * 8; - else if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH) - return 13 * 512 * 8 * 1000 * 8; - else - return 19 * 64 * 1 * 1000 * 8; -} - /*-------------------------------------------------------------------------*/ /* @@ -398,8 +391,7 @@ static void ecm_do_notify(struct f_ecm *ecm) event->wLength = 0; req->length = sizeof *event; - DBG(cdev, "notify connect %s\n", - ecm->is_open ? "true" : "false"); + DBG(cdev, "notify connect %s\n", str_true_false(ecm->is_open)); ecm->notify_state = ECM_NOTIFY_SPEED; break; @@ -411,10 +403,10 @@ static void ecm_do_notify(struct f_ecm *ecm) /* SPEED_CHANGE data is up/down speeds in bits/sec */ data = req->buf + sizeof *event; - data[0] = cpu_to_le32(ecm_bitrate(cdev->gadget)); + data[0] = cpu_to_le32(gether_bitrate(cdev->gadget)); data[1] = data[0]; - DBG(cdev, "notify speed %d\n", ecm_bitrate(cdev->gadget)); + DBG(cdev, "notify speed %d\n", gether_bitrate(cdev->gadget)); ecm->notify_state = ECM_NOTIFY_NONE; break; } @@ -689,6 +681,7 @@ ecm_bind(struct usb_configuration *c, struct usb_function *f) struct usb_ep *ep; struct f_ecm_opts *ecm_opts; + struct usb_request *request __free(free_usb_request) = NULL; if (!can_support_ecm(cdev->gadget)) return -EINVAL; @@ -722,7 +715,7 @@ ecm_bind(struct usb_configuration *c, struct usb_function *f) /* allocate instance-specific interface IDs */ status = usb_interface_id(c, f); if (status < 0) - goto fail; + return status; ecm->ctrl_id = status; ecm_iad_descriptor.bFirstInterface = status; @@ -731,24 +724,22 @@ ecm_bind(struct usb_configuration *c, struct usb_function *f) status = usb_interface_id(c, f); if (status < 0) - goto fail; + return status; ecm->data_id = status; ecm_data_nop_intf.bInterfaceNumber = status; ecm_data_intf.bInterfaceNumber = status; ecm_union_desc.bSlaveInterface0 = status; - status = -ENODEV; - /* allocate instance-specific endpoints */ ep = usb_ep_autoconfig(cdev->gadget, &fs_ecm_in_desc); if (!ep) - goto fail; + return -ENODEV; ecm->port.in_ep = ep; ep = usb_ep_autoconfig(cdev->gadget, &fs_ecm_out_desc); if (!ep) - goto fail; + return -ENODEV; ecm->port.out_ep = ep; /* NOTE: a status/notification endpoint is *OPTIONAL* but we @@ -757,20 +748,18 @@ ecm_bind(struct usb_configuration *c, struct usb_function *f) */ ep = usb_ep_autoconfig(cdev->gadget, &fs_ecm_notify_desc); if (!ep) - goto fail; + return -ENODEV; ecm->notify = ep; - status = -ENOMEM; - /* allocate notification request and buffer */ - ecm->notify_req = usb_ep_alloc_request(ep, GFP_KERNEL); - if (!ecm->notify_req) - goto fail; - ecm->notify_req->buf = kmalloc(ECM_STATUS_BYTECOUNT, GFP_KERNEL); - if (!ecm->notify_req->buf) - goto fail; - ecm->notify_req->context = ecm; - ecm->notify_req->complete = ecm_notify_complete; + request = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!request) + return -ENOMEM; + request->buf = kmalloc(ECM_STATUS_BYTECOUNT, GFP_KERNEL); + if (!request->buf) + return -ENOMEM; + request->context = ecm; + request->complete = ecm_notify_complete; /* support all relevant hardware speeds... we expect that when * hardware is dual speed, all bulk-capable endpoints work at @@ -789,7 +778,7 @@ ecm_bind(struct usb_configuration *c, struct usb_function *f) status = usb_assign_descriptors(f, ecm_fs_function, ecm_hs_function, ecm_ss_function, ecm_ss_function); if (status) - goto fail; + return status; /* NOTE: all that is done without knowing or caring about * the network link ... which is unavailable to this code @@ -799,22 +788,12 @@ ecm_bind(struct usb_configuration *c, struct usb_function *f) ecm->port.open = ecm_open; ecm->port.close = ecm_close; - DBG(cdev, "CDC Ethernet: %s speed IN/%s OUT/%s NOTIFY/%s\n", - gadget_is_superspeed(c->cdev->gadget) ? "super" : - gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + ecm->notify_req = no_free_ptr(request); + + DBG(cdev, "CDC Ethernet: IN/%s OUT/%s NOTIFY/%s\n", ecm->port.in_ep->name, ecm->port.out_ep->name, ecm->notify->name); return 0; - -fail: - if (ecm->notify_req) { - kfree(ecm->notify_req->buf); - usb_ep_free_request(ecm->notify, ecm->notify_req); - } - - ERROR(cdev, "%s: can't bind, err %d\n", f->name, status); - - return status; } static inline struct f_ecm_opts *to_f_ecm_opts(struct config_item *item) @@ -885,6 +864,32 @@ static struct usb_function_instance *ecm_alloc_inst(void) return &opts->func_inst; } +static void ecm_suspend(struct usb_function *f) +{ + struct f_ecm *ecm = func_to_ecm(f); + struct usb_composite_dev *cdev = ecm->port.func.config->cdev; + + DBG(cdev, "ECM Suspend\n"); + + gether_suspend(&ecm->port); +} + +static void ecm_resume(struct usb_function *f) +{ + struct f_ecm *ecm = func_to_ecm(f); + struct usb_composite_dev *cdev = ecm->port.func.config->cdev; + + DBG(cdev, "ECM Resume\n"); + + gether_resume(&ecm->port); +} + +static int ecm_get_status(struct usb_function *f) +{ + return (f->func_wakeup_armed ? USB_INTRF_STAT_FUNC_RW : 0) | + USB_INTRF_STAT_FUNC_RW_CAP; +} + static void ecm_free(struct usb_function *f) { struct f_ecm *ecm; @@ -952,10 +957,14 @@ static struct usb_function *ecm_alloc(struct usb_function_instance *fi) ecm->port.func.setup = ecm_setup; ecm->port.func.disable = ecm_disable; ecm->port.func.free_func = ecm_free; + ecm->port.func.suspend = ecm_suspend; + ecm->port.func.get_status = ecm_get_status; + ecm->port.func.resume = ecm_resume; return &ecm->port.func; } DECLARE_USB_FUNCTION_INIT(ecm, ecm_alloc_inst, ecm_alloc); +MODULE_DESCRIPTION("USB CDC Ethernet (ECM) link function driver"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("David Brownell"); diff --git a/drivers/usb/gadget/function/f_eem.c b/drivers/usb/gadget/function/f_eem.c index 5d38f29bda72..edbbadad6138 100644 --- a/drivers/usb/gadget/function/f_eem.c +++ b/drivers/usb/gadget/function/f_eem.c @@ -311,9 +311,7 @@ static int eem_bind(struct usb_configuration *c, struct usb_function *f) if (status) goto fail; - DBG(cdev, "CDC Ethernet (EEM): %s speed IN/%s OUT/%s\n", - gadget_is_superspeed(c->cdev->gadget) ? "super" : - gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + DBG(cdev, "CDC Ethernet (EEM): IN/%s OUT/%s\n", eem->port.in_ep->name, eem->port.out_ep->name); return 0; @@ -479,8 +477,13 @@ static int eem_unwrap(struct gether *port, req->complete = eem_cmd_complete; req->zero = 1; req->context = ctx; - if (usb_ep_queue(port->in_ep, req, GFP_ATOMIC)) + if (usb_ep_queue(port->in_ep, req, GFP_ATOMIC)) { DBG(cdev, "echo response queue fail\n"); + kfree(ctx); + kfree(req->buf); + usb_ep_free_request(ep, req); + dev_kfree_skb_any(skb2); + } break; case 1: /* echo response */ @@ -676,5 +679,6 @@ static struct usb_function *eem_alloc(struct usb_function_instance *fi) } DECLARE_USB_FUNCTION_INIT(eem, eem_alloc_inst, eem_alloc); +MODULE_DESCRIPTION("USB CDC Ethernet (EEM) link function driver"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("David Brownell"); diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index 523a961b910b..05c6750702b6 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -15,6 +15,9 @@ /* #define VERBOSE_DEBUG */ #include <linux/blkdev.h> +#include <linux/dma-buf.h> +#include <linux/dma-fence.h> +#include <linux/dma-resv.h> #include <linux/pagemap.h> #include <linux/export.h> #include <linux/fs_parser.h> @@ -25,11 +28,12 @@ #include <linux/sched/signal.h> #include <linux/uio.h> #include <linux/vmalloc.h> -#include <asm/unaligned.h> +#include <linux/unaligned.h> #include <linux/usb/ccid.h> #include <linux/usb/composite.h> #include <linux/usb/functionfs.h> +#include <linux/usb/func_utils.h> #include <linux/aio.h> #include <linux/kthread.h> @@ -37,11 +41,15 @@ #include <linux/eventfd.h> #include "u_fs.h" -#include "u_f.h" #include "u_os_desc.h" #include "configfs.h" #define FUNCTIONFS_MAGIC 0xa647361 /* Chosen by a honest dice roll ;) */ +#define MAX_ALT_SETTINGS 2 /* Allow up to 2 alt settings to be set. */ + +#define DMABUF_ENQUEUE_TIMEOUT_MS 5000 + +MODULE_IMPORT_NS("DMA_BUF"); /* Reference counter handling */ static void ffs_data_get(struct ffs_data *ffs); @@ -75,6 +83,7 @@ struct ffs_function { short *interfaces_nums; struct usb_function function; + int cur_alt[MAX_CONFIG_INTERFACES]; }; @@ -98,6 +107,7 @@ static int __must_check ffs_func_eps_enable(struct ffs_function *func); static int ffs_func_bind(struct usb_configuration *, struct usb_function *); static int ffs_func_set_alt(struct usb_function *, unsigned, unsigned); +static int ffs_func_get_alt(struct usb_function *f, unsigned int intf); static void ffs_func_disable(struct usb_function *); static int ffs_func_setup(struct usb_function *, const struct usb_ctrlrequest *); @@ -124,6 +134,25 @@ struct ffs_ep { u8 num; }; +struct ffs_dmabuf_priv { + struct list_head entry; + struct kref ref; + struct ffs_data *ffs; + struct dma_buf_attachment *attach; + struct sg_table *sgt; + enum dma_data_direction dir; + spinlock_t lock; + u64 context; + struct usb_request *req; /* P: ffs->eps_lock */ + struct usb_ep *ep; /* P: ffs->eps_lock */ +}; + +struct ffs_dma_fence { + struct dma_fence base; + struct ffs_dmabuf_priv *priv; + struct work_struct work; +}; + struct ffs_epfile { /* Protects ep->ep and ep->req. */ struct mutex mutex; @@ -131,8 +160,6 @@ struct ffs_epfile { struct ffs_data *ffs; struct ffs_ep *ep; /* P: ffs->eps_lock */ - struct dentry *dentry; - /* * Buffer for holding data from partial reads which may happen since * we’re rounding user read requests to a multiple of a max packet size. @@ -197,12 +224,17 @@ struct ffs_epfile { unsigned char isoc; /* P: ffs->eps_lock */ unsigned char _pad; + + /* Protects dmabufs */ + struct mutex dmabufs_mutex; + struct list_head dmabufs; /* P: dmabufs_mutex */ + atomic_t seqno; }; struct ffs_buffer { size_t length; char *data; - char storage[]; + char storage[] __counted_by(length); }; /* ffs_io_data structure ***************************************************/ @@ -237,11 +269,11 @@ struct ffs_desc_helper { }; static int __must_check ffs_epfiles_create(struct ffs_data *ffs); -static void ffs_epfiles_destroy(struct ffs_epfile *epfiles, unsigned count); +static void ffs_epfiles_destroy(struct super_block *sb, + struct ffs_epfile *epfiles, unsigned count); -static struct dentry * -ffs_sb_create_file(struct super_block *sb, const char *name, void *data, - const struct file_operations *fops); +static int ffs_sb_create_file(struct super_block *sb, const char *name, + void *data, const struct file_operations *fops); /* Devices management *******************************************************/ @@ -279,8 +311,10 @@ static int __ffs_ep0_queue_wait(struct ffs_data *ffs, char *data, size_t len) struct usb_request *req = ffs->ep0req; int ret; - if (!req) + if (!req) { + spin_unlock_irq(&ffs->ev.waitq.lock); return -EINVAL; + } req->zero = len < le16_to_cpu(ffs->ev.setup.wLength); @@ -333,8 +367,6 @@ static ssize_t ffs_ep0_write(struct file *file, const char __user *buf, ssize_t ret; char *data; - ENTER(); - /* Fast check if setup was canceled */ if (ffs_setup_state_clear_cancelled(ffs) == FFS_SETUP_CANCELLED) return -EIDRM; @@ -422,7 +454,7 @@ static ssize_t ffs_ep0_write(struct file *file, const char __user *buf, } /* FFS_SETUP_PENDING and not stall */ - len = min(len, (size_t)le16_to_cpu(ffs->ev.setup.wLength)); + len = min_t(size_t, len, le16_to_cpu(ffs->ev.setup.wLength)); spin_unlock_irq(&ffs->ev.waitq.lock); @@ -510,8 +542,6 @@ static ssize_t ffs_ep0_read(struct file *file, char __user *buf, size_t n; int ret; - ENTER(); - /* Fast check if setup was canceled */ if (ffs_setup_state_clear_cancelled(ffs) == FFS_SETUP_CANCELLED) return -EIDRM; @@ -558,7 +588,7 @@ static ssize_t ffs_ep0_read(struct file *file, char __user *buf, /* unlocks spinlock */ return __ffs_ep0_read_events(ffs, buf, - min(n, (size_t)ffs->ev.count)); + min_t(size_t, n, ffs->ev.count)); case FFS_SETUP_PENDING: if (ffs->ev.setup.bRequestType & USB_DIR_IN) { @@ -567,7 +597,7 @@ static ssize_t ffs_ep0_read(struct file *file, char __user *buf, goto done_mutex; } - len = min(len, (size_t)le16_to_cpu(ffs->ev.setup.wLength)); + len = min_t(size_t, len, le16_to_cpu(ffs->ev.setup.wLength)); spin_unlock_irq(&ffs->ev.waitq.lock); @@ -608,15 +638,22 @@ done_mutex: static int ffs_ep0_open(struct inode *inode, struct file *file) { - struct ffs_data *ffs = inode->i_private; + struct ffs_data *ffs = inode->i_sb->s_fs_info; + int ret; - ENTER(); + /* Acquire mutex */ + ret = ffs_mutex_lock(&ffs->mutex, file->f_flags & O_NONBLOCK); + if (ret < 0) + return ret; - if (ffs->state == FFS_CLOSING) + ffs_data_opened(ffs); + if (ffs->state == FFS_CLOSING) { + ffs_data_closed(ffs); + mutex_unlock(&ffs->mutex); return -EBUSY; - + } + mutex_unlock(&ffs->mutex); file->private_data = ffs; - ffs_data_opened(ffs); return stream_open(inode, file); } @@ -625,8 +662,6 @@ static int ffs_ep0_release(struct inode *inode, struct file *file) { struct ffs_data *ffs = file->private_data; - ENTER(); - ffs_data_closed(ffs); return 0; @@ -638,8 +673,6 @@ static long ffs_ep0_ioctl(struct file *file, unsigned code, unsigned long value) struct usb_gadget *gadget = ffs->gadget; long ret; - ENTER(); - if (code == FUNCTIONFS_INTERFACE_REVMAP) { struct ffs_function *func = ffs->func; ret = func ? ffs_func_revmap_intf(func, value) : -ENODEV; @@ -696,7 +729,6 @@ static __poll_t ffs_ep0_poll(struct file *file, poll_table *wait) } static const struct file_operations ffs_ep0_operations = { - .llseek = no_llseek, .open = ffs_ep0_open, .write = ffs_ep0_write, @@ -713,7 +745,6 @@ static void ffs_epfile_io_complete(struct usb_ep *_ep, struct usb_request *req) { struct ffs_io_data *io_data = req->context; - ENTER(); if (req->status) io_data->status = req->status; else @@ -828,8 +859,7 @@ static void ffs_user_copy_worker(struct work_struct *work) { struct ffs_io_data *io_data = container_of(work, struct ffs_io_data, work); - int ret = io_data->req->status ? io_data->req->status : - io_data->req->actual; + int ret = io_data->status; bool kiocb_has_eventfd = io_data->kiocb->ki_flags & IOCB_EVENTFD; if (io_data->read && ret > 0) { @@ -841,7 +871,7 @@ static void ffs_user_copy_worker(struct work_struct *work) io_data->kiocb->ki_complete(io_data->kiocb, ret); if (io_data->ffs->ffs_eventfd && !kiocb_has_eventfd) - eventfd_signal(io_data->ffs->ffs_eventfd, 1); + eventfd_signal(io_data->ffs->ffs_eventfd); usb_ep_free_request(io_data->ep, io_data->req); @@ -857,7 +887,7 @@ static void ffs_epfile_async_io_complete(struct usb_ep *_ep, struct ffs_io_data *io_data = req->context; struct ffs_data *ffs = io_data->ffs; - ENTER(); + io_data->status = req->status ? req->status : req->actual; INIT_WORK(&io_data->work, ffs_user_copy_worker); queue_work(ffs->io_completion_wq, &io_data->work); @@ -945,31 +975,44 @@ static ssize_t __ffs_epfile_read_data(struct ffs_epfile *epfile, return ret; } -static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) +static struct ffs_ep *ffs_epfile_wait_ep(struct file *file) { struct ffs_epfile *epfile = file->private_data; - struct usb_request *req; struct ffs_ep *ep; - char *data = NULL; - ssize_t ret, data_len = -EINVAL; - int halt; - - /* Are we still active? */ - if (WARN_ON(epfile->ffs->state != FFS_ACTIVE)) - return -ENODEV; + int ret; /* Wait for endpoint to be enabled */ ep = epfile->ep; if (!ep) { if (file->f_flags & O_NONBLOCK) - return -EAGAIN; + return ERR_PTR(-EAGAIN); ret = wait_event_interruptible( epfile->ffs->wait, (ep = epfile->ep)); if (ret) - return -EINTR; + return ERR_PTR(-EINTR); } + return ep; +} + +static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) +{ + struct ffs_epfile *epfile = file->private_data; + struct usb_request *req; + struct ffs_ep *ep; + char *data = NULL; + ssize_t ret, data_len = -EINVAL; + int halt; + + /* Are we still active? */ + if (WARN_ON(epfile->ffs->state != FFS_ACTIVE)) + return -ENODEV; + + ep = ffs_epfile_wait_ep(file); + if (IS_ERR(ep)) + return PTR_ERR(ep); + /* Do we halt? */ halt = (!io_data->read == !epfile->in); if (halt && epfile->isoc) @@ -1157,37 +1200,46 @@ error: static int ffs_epfile_open(struct inode *inode, struct file *file) { - struct ffs_epfile *epfile = inode->i_private; + struct ffs_data *ffs = inode->i_sb->s_fs_info; + struct ffs_epfile *epfile; + int ret; - ENTER(); + /* Acquire mutex */ + ret = ffs_mutex_lock(&ffs->mutex, file->f_flags & O_NONBLOCK); + if (ret < 0) + return ret; - if (WARN_ON(epfile->ffs->state != FFS_ACTIVE)) + if (!atomic_inc_not_zero(&ffs->opened)) { + mutex_unlock(&ffs->mutex); return -ENODEV; + } + /* + * we want the state to be FFS_ACTIVE; FFS_ACTIVE alone is + * not enough, though - we might have been through FFS_CLOSING + * and back to FFS_ACTIVE, with our file already removed. + */ + epfile = smp_load_acquire(&inode->i_private); + if (unlikely(ffs->state != FFS_ACTIVE || !epfile)) { + mutex_unlock(&ffs->mutex); + ffs_data_closed(ffs); + return -ENODEV; + } + mutex_unlock(&ffs->mutex); file->private_data = epfile; - ffs_data_opened(epfile->ffs); - return stream_open(inode, file); } static int ffs_aio_cancel(struct kiocb *kiocb) { struct ffs_io_data *io_data = kiocb->private; - struct ffs_epfile *epfile = kiocb->ki_filp->private_data; - unsigned long flags; int value; - ENTER(); - - spin_lock_irqsave(&epfile->ffs->eps_lock, flags); - if (io_data && io_data->ep && io_data->req) value = usb_ep_dequeue(io_data->ep, io_data->req); else value = -EINVAL; - spin_unlock_irqrestore(&epfile->ffs->eps_lock, flags); - return value; } @@ -1196,8 +1248,6 @@ static ssize_t ffs_epfile_write_iter(struct kiocb *kiocb, struct iov_iter *from) struct ffs_io_data io_data, *p = &io_data; ssize_t res; - ENTER(); - if (!is_sync_kiocb(kiocb)) { p = kzalloc(sizeof(io_data), GFP_KERNEL); if (!p) @@ -1233,8 +1283,6 @@ static ssize_t ffs_epfile_read_iter(struct kiocb *kiocb, struct iov_iter *to) struct ffs_io_data io_data, *p = &io_data; ssize_t res; - ENTER(); - if (!is_sync_kiocb(kiocb)) { p = kzalloc(sizeof(io_data), GFP_KERNEL); if (!p) @@ -1249,7 +1297,7 @@ static ssize_t ffs_epfile_read_iter(struct kiocb *kiocb, struct iov_iter *to) p->kiocb = kiocb; if (p->aio) { p->to_free = dup_iter(&p->data, to, GFP_KERNEL); - if (!p->to_free) { + if (!iter_is_ubuf(&p->data) && !p->to_free) { kfree(p); return -ENOMEM; } @@ -1277,12 +1325,56 @@ static ssize_t ffs_epfile_read_iter(struct kiocb *kiocb, struct iov_iter *to) return res; } +static void ffs_dmabuf_release(struct kref *ref) +{ + struct ffs_dmabuf_priv *priv = container_of(ref, struct ffs_dmabuf_priv, ref); + struct dma_buf_attachment *attach = priv->attach; + struct dma_buf *dmabuf = attach->dmabuf; + + pr_vdebug("FFS DMABUF release\n"); + dma_buf_unmap_attachment_unlocked(attach, priv->sgt, priv->dir); + + dma_buf_detach(attach->dmabuf, attach); + dma_buf_put(dmabuf); + kfree(priv); +} + +static void ffs_dmabuf_get(struct dma_buf_attachment *attach) +{ + struct ffs_dmabuf_priv *priv = attach->importer_priv; + + kref_get(&priv->ref); +} + +static void ffs_dmabuf_put(struct dma_buf_attachment *attach) +{ + struct ffs_dmabuf_priv *priv = attach->importer_priv; + + kref_put(&priv->ref, ffs_dmabuf_release); +} + static int ffs_epfile_release(struct inode *inode, struct file *file) { - struct ffs_epfile *epfile = inode->i_private; + struct ffs_epfile *epfile = file->private_data; + struct ffs_dmabuf_priv *priv, *tmp; + struct ffs_data *ffs = epfile->ffs; + + mutex_lock(&epfile->dmabufs_mutex); - ENTER(); + /* Close all attached DMABUFs */ + list_for_each_entry_safe(priv, tmp, &epfile->dmabufs, entry) { + /* Cancel any pending transfer */ + spin_lock_irq(&ffs->eps_lock); + if (priv->ep && priv->req) + usb_ep_dequeue(priv->ep, priv->req); + spin_unlock_irq(&ffs->eps_lock); + + list_del(&priv->entry); + ffs_dmabuf_put(priv->attach); + } + + mutex_unlock(&epfile->dmabufs_mutex); __ffs_epfile_read_buffer_free(epfile); ffs_data_closed(epfile->ffs); @@ -1290,6 +1382,357 @@ ffs_epfile_release(struct inode *inode, struct file *file) return 0; } +static void ffs_dmabuf_cleanup(struct work_struct *work) +{ + struct ffs_dma_fence *dma_fence = + container_of(work, struct ffs_dma_fence, work); + struct ffs_dmabuf_priv *priv = dma_fence->priv; + struct dma_buf_attachment *attach = priv->attach; + struct dma_fence *fence = &dma_fence->base; + + ffs_dmabuf_put(attach); + dma_fence_put(fence); +} + +static void ffs_dmabuf_signal_done(struct ffs_dma_fence *dma_fence, int ret) +{ + struct ffs_dmabuf_priv *priv = dma_fence->priv; + struct dma_fence *fence = &dma_fence->base; + bool cookie = dma_fence_begin_signalling(); + + dma_fence_get(fence); + fence->error = ret; + dma_fence_signal(fence); + dma_fence_end_signalling(cookie); + + /* + * The fence will be unref'd in ffs_dmabuf_cleanup. + * It can't be done here, as the unref functions might try to lock + * the resv object, which would deadlock. + */ + INIT_WORK(&dma_fence->work, ffs_dmabuf_cleanup); + queue_work(priv->ffs->io_completion_wq, &dma_fence->work); +} + +static void ffs_epfile_dmabuf_io_complete(struct usb_ep *ep, + struct usb_request *req) +{ + pr_vdebug("FFS: DMABUF transfer complete, status=%d\n", req->status); + ffs_dmabuf_signal_done(req->context, req->status); + usb_ep_free_request(ep, req); +} + +static const char *ffs_dmabuf_get_driver_name(struct dma_fence *fence) +{ + return "functionfs"; +} + +static const char *ffs_dmabuf_get_timeline_name(struct dma_fence *fence) +{ + return ""; +} + +static void ffs_dmabuf_fence_release(struct dma_fence *fence) +{ + struct ffs_dma_fence *dma_fence = + container_of(fence, struct ffs_dma_fence, base); + + kfree(dma_fence); +} + +static const struct dma_fence_ops ffs_dmabuf_fence_ops = { + .get_driver_name = ffs_dmabuf_get_driver_name, + .get_timeline_name = ffs_dmabuf_get_timeline_name, + .release = ffs_dmabuf_fence_release, +}; + +static int ffs_dma_resv_lock(struct dma_buf *dmabuf, bool nonblock) +{ + if (!nonblock) + return dma_resv_lock_interruptible(dmabuf->resv, NULL); + + if (!dma_resv_trylock(dmabuf->resv)) + return -EBUSY; + + return 0; +} + +static struct dma_buf_attachment * +ffs_dmabuf_find_attachment(struct ffs_epfile *epfile, struct dma_buf *dmabuf) +{ + struct device *dev = epfile->ffs->gadget->dev.parent; + struct dma_buf_attachment *attach = NULL; + struct ffs_dmabuf_priv *priv; + + mutex_lock(&epfile->dmabufs_mutex); + + list_for_each_entry(priv, &epfile->dmabufs, entry) { + if (priv->attach->dev == dev + && priv->attach->dmabuf == dmabuf) { + attach = priv->attach; + break; + } + } + + if (attach) + ffs_dmabuf_get(attach); + + mutex_unlock(&epfile->dmabufs_mutex); + + return attach ?: ERR_PTR(-EPERM); +} + +static int ffs_dmabuf_attach(struct file *file, int fd) +{ + bool nonblock = file->f_flags & O_NONBLOCK; + struct ffs_epfile *epfile = file->private_data; + struct usb_gadget *gadget = epfile->ffs->gadget; + struct dma_buf_attachment *attach; + struct ffs_dmabuf_priv *priv; + enum dma_data_direction dir; + struct sg_table *sg_table; + struct dma_buf *dmabuf; + int err; + + if (!gadget || !gadget->sg_supported) + return -EPERM; + + dmabuf = dma_buf_get(fd); + if (IS_ERR(dmabuf)) + return PTR_ERR(dmabuf); + + attach = dma_buf_attach(dmabuf, gadget->dev.parent); + if (IS_ERR(attach)) { + err = PTR_ERR(attach); + goto err_dmabuf_put; + } + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) { + err = -ENOMEM; + goto err_dmabuf_detach; + } + + dir = epfile->in ? DMA_FROM_DEVICE : DMA_TO_DEVICE; + + err = ffs_dma_resv_lock(dmabuf, nonblock); + if (err) + goto err_free_priv; + + sg_table = dma_buf_map_attachment(attach, dir); + dma_resv_unlock(dmabuf->resv); + + if (IS_ERR(sg_table)) { + err = PTR_ERR(sg_table); + goto err_free_priv; + } + + attach->importer_priv = priv; + + priv->sgt = sg_table; + priv->dir = dir; + priv->ffs = epfile->ffs; + priv->attach = attach; + spin_lock_init(&priv->lock); + kref_init(&priv->ref); + priv->context = dma_fence_context_alloc(1); + + mutex_lock(&epfile->dmabufs_mutex); + list_add(&priv->entry, &epfile->dmabufs); + mutex_unlock(&epfile->dmabufs_mutex); + + return 0; + +err_free_priv: + kfree(priv); +err_dmabuf_detach: + dma_buf_detach(dmabuf, attach); +err_dmabuf_put: + dma_buf_put(dmabuf); + + return err; +} + +static int ffs_dmabuf_detach(struct file *file, int fd) +{ + struct ffs_epfile *epfile = file->private_data; + struct ffs_data *ffs = epfile->ffs; + struct device *dev = ffs->gadget->dev.parent; + struct ffs_dmabuf_priv *priv, *tmp; + struct dma_buf *dmabuf; + int ret = -EPERM; + + dmabuf = dma_buf_get(fd); + if (IS_ERR(dmabuf)) + return PTR_ERR(dmabuf); + + mutex_lock(&epfile->dmabufs_mutex); + + list_for_each_entry_safe(priv, tmp, &epfile->dmabufs, entry) { + if (priv->attach->dev == dev + && priv->attach->dmabuf == dmabuf) { + /* Cancel any pending transfer */ + spin_lock_irq(&ffs->eps_lock); + if (priv->ep && priv->req) + usb_ep_dequeue(priv->ep, priv->req); + spin_unlock_irq(&ffs->eps_lock); + + list_del(&priv->entry); + + /* Unref the reference from ffs_dmabuf_attach() */ + ffs_dmabuf_put(priv->attach); + ret = 0; + break; + } + } + + mutex_unlock(&epfile->dmabufs_mutex); + dma_buf_put(dmabuf); + + return ret; +} + +static int ffs_dmabuf_transfer(struct file *file, + const struct usb_ffs_dmabuf_transfer_req *req) +{ + bool nonblock = file->f_flags & O_NONBLOCK; + struct ffs_epfile *epfile = file->private_data; + struct dma_buf_attachment *attach; + struct ffs_dmabuf_priv *priv; + struct ffs_dma_fence *fence; + struct usb_request *usb_req; + enum dma_resv_usage resv_dir; + struct dma_buf *dmabuf; + unsigned long timeout; + struct ffs_ep *ep; + bool cookie; + u32 seqno; + long retl; + int ret; + + if (req->flags & ~USB_FFS_DMABUF_TRANSFER_MASK) + return -EINVAL; + + dmabuf = dma_buf_get(req->fd); + if (IS_ERR(dmabuf)) + return PTR_ERR(dmabuf); + + if (req->length > dmabuf->size || req->length == 0) { + ret = -EINVAL; + goto err_dmabuf_put; + } + + attach = ffs_dmabuf_find_attachment(epfile, dmabuf); + if (IS_ERR(attach)) { + ret = PTR_ERR(attach); + goto err_dmabuf_put; + } + + priv = attach->importer_priv; + + ep = ffs_epfile_wait_ep(file); + if (IS_ERR(ep)) { + ret = PTR_ERR(ep); + goto err_attachment_put; + } + + ret = ffs_dma_resv_lock(dmabuf, nonblock); + if (ret) + goto err_attachment_put; + + /* Make sure we don't have writers */ + timeout = nonblock ? 0 : msecs_to_jiffies(DMABUF_ENQUEUE_TIMEOUT_MS); + retl = dma_resv_wait_timeout(dmabuf->resv, + dma_resv_usage_rw(epfile->in), + true, timeout); + if (retl == 0) + retl = -EBUSY; + if (retl < 0) { + ret = (int)retl; + goto err_resv_unlock; + } + + ret = dma_resv_reserve_fences(dmabuf->resv, 1); + if (ret) + goto err_resv_unlock; + + fence = kmalloc(sizeof(*fence), GFP_KERNEL); + if (!fence) { + ret = -ENOMEM; + goto err_resv_unlock; + } + + fence->priv = priv; + + spin_lock_irq(&epfile->ffs->eps_lock); + + /* In the meantime, endpoint got disabled or changed. */ + if (epfile->ep != ep) { + ret = -ESHUTDOWN; + goto err_fence_put; + } + + usb_req = usb_ep_alloc_request(ep->ep, GFP_ATOMIC); + if (!usb_req) { + ret = -ENOMEM; + goto err_fence_put; + } + + /* + * usb_ep_queue() guarantees that all transfers are processed in the + * order they are enqueued, so we can use a simple incrementing + * sequence number for the dma_fence. + */ + seqno = atomic_add_return(1, &epfile->seqno); + + dma_fence_init(&fence->base, &ffs_dmabuf_fence_ops, + &priv->lock, priv->context, seqno); + + resv_dir = epfile->in ? DMA_RESV_USAGE_WRITE : DMA_RESV_USAGE_READ; + + dma_resv_add_fence(dmabuf->resv, &fence->base, resv_dir); + dma_resv_unlock(dmabuf->resv); + + /* Now that the dma_fence is in place, queue the transfer. */ + + usb_req->length = req->length; + usb_req->buf = NULL; + usb_req->sg = priv->sgt->sgl; + usb_req->num_sgs = sg_nents_for_len(priv->sgt->sgl, req->length); + usb_req->sg_was_mapped = true; + usb_req->context = fence; + usb_req->complete = ffs_epfile_dmabuf_io_complete; + + cookie = dma_fence_begin_signalling(); + ret = usb_ep_queue(ep->ep, usb_req, GFP_ATOMIC); + dma_fence_end_signalling(cookie); + if (!ret) { + priv->req = usb_req; + priv->ep = ep->ep; + } else { + pr_warn("FFS: Failed to queue DMABUF: %d\n", ret); + ffs_dmabuf_signal_done(fence, ret); + usb_ep_free_request(ep->ep, usb_req); + } + + spin_unlock_irq(&epfile->ffs->eps_lock); + dma_buf_put(dmabuf); + + return ret; + +err_fence_put: + spin_unlock_irq(&epfile->ffs->eps_lock); + dma_fence_put(&fence->base); +err_resv_unlock: + dma_resv_unlock(dmabuf->resv); +err_attachment_put: + ffs_dmabuf_put(attach); +err_dmabuf_put: + dma_buf_put(dmabuf); + + return ret; +} + static long ffs_epfile_ioctl(struct file *file, unsigned code, unsigned long value) { @@ -1297,22 +1740,51 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code, struct ffs_ep *ep; int ret; - ENTER(); - if (WARN_ON(epfile->ffs->state != FFS_ACTIVE)) return -ENODEV; - /* Wait for endpoint to be enabled */ - ep = epfile->ep; - if (!ep) { - if (file->f_flags & O_NONBLOCK) - return -EAGAIN; + switch (code) { + case FUNCTIONFS_DMABUF_ATTACH: + { + int fd; - ret = wait_event_interruptible( - epfile->ffs->wait, (ep = epfile->ep)); - if (ret) - return -EINTR; + if (copy_from_user(&fd, (void __user *)value, sizeof(fd))) { + ret = -EFAULT; + break; + } + + return ffs_dmabuf_attach(file, fd); } + case FUNCTIONFS_DMABUF_DETACH: + { + int fd; + + if (copy_from_user(&fd, (void __user *)value, sizeof(fd))) { + ret = -EFAULT; + break; + } + + return ffs_dmabuf_detach(file, fd); + } + case FUNCTIONFS_DMABUF_TRANSFER: + { + struct usb_ffs_dmabuf_transfer_req req; + + if (copy_from_user(&req, (void __user *)value, sizeof(req))) { + ret = -EFAULT; + break; + } + + return ffs_dmabuf_transfer(file, &req); + } + default: + break; + } + + /* Wait for endpoint to be enabled */ + ep = ffs_epfile_wait_ep(file); + if (IS_ERR(ep)) + return PTR_ERR(ep); spin_lock_irq(&epfile->ffs->eps_lock); @@ -1371,7 +1843,6 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code, } static const struct file_operations ffs_epfile_operations = { - .llseek = no_llseek, .open = ffs_epfile_open, .write_iter = ffs_epfile_write_iter, @@ -1397,20 +1868,17 @@ ffs_sb_make_inode(struct super_block *sb, void *data, { struct inode *inode; - ENTER(); - inode = new_inode(sb); if (inode) { - struct timespec64 ts = current_time(inode); + struct timespec64 ts = inode_set_ctime_current(inode); inode->i_ino = get_next_ino(); inode->i_mode = perms->mode; inode->i_uid = perms->uid; inode->i_gid = perms->gid; - inode->i_atime = ts; - inode->i_mtime = ts; - inode->i_ctime = ts; + inode_set_atime_to_ts(inode, ts); + inode_set_mtime_to_ts(inode, ts); inode->i_private = data; if (fops) inode->i_fop = fops; @@ -1422,34 +1890,32 @@ ffs_sb_make_inode(struct super_block *sb, void *data, } /* Create "regular" file */ -static struct dentry *ffs_sb_create_file(struct super_block *sb, - const char *name, void *data, - const struct file_operations *fops) +static int ffs_sb_create_file(struct super_block *sb, const char *name, + void *data, const struct file_operations *fops) { struct ffs_data *ffs = sb->s_fs_info; struct dentry *dentry; struct inode *inode; - ENTER(); - - dentry = d_alloc_name(sb->s_root, name); - if (!dentry) - return NULL; - inode = ffs_sb_make_inode(sb, data, fops, NULL, &ffs->file_perms); - if (!inode) { - dput(dentry); - return NULL; + if (!inode) + return -ENOMEM; + dentry = simple_start_creating(sb->s_root, name); + if (IS_ERR(dentry)) { + iput(inode); + return PTR_ERR(dentry); } - d_add(dentry, inode); - return dentry; + d_make_persistent(dentry, inode); + + simple_done_creating(dentry); + return 0; } /* Super block */ static const struct super_operations ffs_sb_operations = { .statfs = simple_statfs, - .drop_inode = generic_delete_inode, + .drop_inode = inode_just_drop, }; struct ffs_sb_fill_data { @@ -1466,8 +1932,6 @@ static int ffs_sb_fill(struct super_block *sb, struct fs_context *fc) struct inode *inode; struct ffs_data *ffs = data->ffs_data; - ENTER(); - ffs->sb = sb; data->ffs_data = NULL; sb->s_fs_info = ffs; @@ -1488,10 +1952,7 @@ static int ffs_sb_fill(struct super_block *sb, struct fs_context *fc) return -ENOMEM; /* EP0 file */ - if (!ffs_sb_create_file(sb, "ep0", ffs, &ffs_ep0_operations)) - return -ENOMEM; - - return 0; + return ffs_sb_create_file(sb, "ep0", ffs, &ffs_ep0_operations); } enum { @@ -1519,8 +1980,6 @@ static int ffs_fs_parse_param(struct fs_context *fc, struct fs_parameter *param) struct fs_parse_result result; int opt; - ENTER(); - opt = fs_parse(fc, ffs_fs_fs_parameters, param, &result); if (opt < 0) return opt; @@ -1570,8 +2029,6 @@ static int ffs_fs_get_tree(struct fs_context *fc) struct ffs_data *ffs; int ret; - ENTER(); - if (!fc->source) return invalf(fc, "No source specified"); @@ -1635,14 +2092,21 @@ static int ffs_fs_init_fs_context(struct fs_context *fc) return 0; } +static void ffs_data_reset(struct ffs_data *ffs); + static void ffs_fs_kill_sb(struct super_block *sb) { - ENTER(); - - kill_litter_super(sb); - if (sb->s_fs_info) - ffs_data_closed(sb->s_fs_info); + kill_anon_super(sb); + if (sb->s_fs_info) { + struct ffs_data *ffs = sb->s_fs_info; + ffs->state = FFS_CLOSING; + ffs_data_reset(ffs); + // no configfs accesses from that point on, + // so no further schedule_work() is possible + cancel_work_sync(&ffs->reset_work); + ffs_data_put(ffs); + } } static struct file_system_type ffs_fs_type = { @@ -1661,8 +2125,6 @@ static int functionfs_init(void) { int ret; - ENTER(); - ret = register_filesystem(&ffs_fs_type); if (!ret) pr_info("file system registered\n"); @@ -1674,8 +2136,6 @@ static int functionfs_init(void) static void functionfs_cleanup(void) { - ENTER(); - pr_info("unloading\n"); unregister_filesystem(&ffs_fs_type); } @@ -1684,20 +2144,14 @@ static void functionfs_cleanup(void) /* ffs_data and ffs_function construction and destruction code **************/ static void ffs_data_clear(struct ffs_data *ffs); -static void ffs_data_reset(struct ffs_data *ffs); static void ffs_data_get(struct ffs_data *ffs) { - ENTER(); - refcount_inc(&ffs->ref); } static void ffs_data_opened(struct ffs_data *ffs) { - ENTER(); - - refcount_inc(&ffs->ref); if (atomic_add_return(1, &ffs->opened) == 1 && ffs->state == FFS_DEACTIVATED) { ffs->state = FFS_CLOSING; @@ -1707,8 +2161,6 @@ static void ffs_data_opened(struct ffs_data *ffs) static void ffs_data_put(struct ffs_data *ffs) { - ENTER(); - if (refcount_dec_and_test(&ffs->ref)) { pr_info("%s(): freeing\n", __func__); ffs_data_clear(ffs); @@ -1724,13 +2176,11 @@ static void ffs_data_put(struct ffs_data *ffs) static void ffs_data_closed(struct ffs_data *ffs) { - struct ffs_epfile *epfiles; - unsigned long flags; - - ENTER(); - if (atomic_dec_and_test(&ffs->opened)) { if (ffs->no_disconnect) { + struct ffs_epfile *epfiles; + unsigned long flags; + ffs->state = FFS_DEACTIVATED; spin_lock_irqsave(&ffs->eps_lock, flags); epfiles = ffs->epfiles; @@ -1739,7 +2189,7 @@ static void ffs_data_closed(struct ffs_data *ffs) flags); if (epfiles) - ffs_epfiles_destroy(epfiles, + ffs_epfiles_destroy(ffs->sb, epfiles, ffs->eps_count); if (ffs->setup_state == FFS_SETUP_PENDING) @@ -1749,12 +2199,6 @@ static void ffs_data_closed(struct ffs_data *ffs) ffs_data_reset(ffs); } } - if (atomic_read(&ffs->opened) < 0) { - ffs->state = FFS_CLOSING; - ffs_data_reset(ffs); - } - - ffs_data_put(ffs); } static struct ffs_data *ffs_data_new(const char *dev_name) @@ -1763,8 +2207,6 @@ static struct ffs_data *ffs_data_new(const char *dev_name) if (!ffs) return NULL; - ENTER(); - ffs->io_completion_wq = alloc_ordered_workqueue("%s", 0, dev_name); if (!ffs->io_completion_wq) { kfree(ffs); @@ -1791,8 +2233,6 @@ static void ffs_data_clear(struct ffs_data *ffs) struct ffs_epfile *epfiles; unsigned long flags; - ENTER(); - ffs_closed(ffs); BUG_ON(ffs->gadget); @@ -1808,7 +2248,7 @@ static void ffs_data_clear(struct ffs_data *ffs) * copy of epfile will save us from use-after-free. */ if (epfiles) { - ffs_epfiles_destroy(epfiles, ffs->eps_count); + ffs_epfiles_destroy(ffs->sb, epfiles, ffs->eps_count); ffs->epfiles = NULL; } @@ -1824,8 +2264,6 @@ static void ffs_data_clear(struct ffs_data *ffs) static void ffs_data_reset(struct ffs_data *ffs) { - ENTER(); - ffs_data_clear(ffs); ffs->raw_descs_data = NULL; @@ -1859,9 +2297,7 @@ static int functionfs_bind(struct ffs_data *ffs, struct usb_composite_dev *cdev) struct usb_gadget_strings **lang; int first_id; - ENTER(); - - if (WARN_ON(ffs->state != FFS_ACTIVE + if ((ffs->state != FFS_ACTIVE || test_and_set_bit(FFS_FL_BOUND, &ffs->flags))) return -EBADFD; @@ -1892,8 +2328,6 @@ static int functionfs_bind(struct ffs_data *ffs, struct usb_composite_dev *cdev) static void functionfs_unbind(struct ffs_data *ffs) { - ENTER(); - if (!WARN_ON(!ffs->gadget)) { /* dequeue before freeing ep0req */ usb_ep_dequeue(ffs->gadget->ep0, ffs->ep0req); @@ -1911,8 +2345,7 @@ static int ffs_epfiles_create(struct ffs_data *ffs) { struct ffs_epfile *epfile, *epfiles; unsigned i, count; - - ENTER(); + int err; count = ffs->eps_count; epfiles = kcalloc(count, sizeof(*epfiles), GFP_KERNEL); @@ -1923,16 +2356,17 @@ static int ffs_epfiles_create(struct ffs_data *ffs) for (i = 1; i <= count; ++i, ++epfile) { epfile->ffs = ffs; mutex_init(&epfile->mutex); + mutex_init(&epfile->dmabufs_mutex); + INIT_LIST_HEAD(&epfile->dmabufs); if (ffs->user_flags & FUNCTIONFS_VIRTUAL_ADDR) sprintf(epfile->name, "ep%02x", ffs->eps_addrmap[i]); else sprintf(epfile->name, "ep%u", i); - epfile->dentry = ffs_sb_create_file(ffs->sb, epfile->name, - epfile, - &ffs_epfile_operations); - if (!epfile->dentry) { - ffs_epfiles_destroy(epfiles, i - 1); - return -ENOMEM; + err = ffs_sb_create_file(ffs->sb, epfile->name, + epfile, &ffs_epfile_operations); + if (err) { + ffs_epfiles_destroy(ffs->sb, epfiles, i - 1); + return err; } } @@ -1940,19 +2374,20 @@ static int ffs_epfiles_create(struct ffs_data *ffs) return 0; } -static void ffs_epfiles_destroy(struct ffs_epfile *epfiles, unsigned count) +static void clear_one(struct dentry *dentry) { - struct ffs_epfile *epfile = epfiles; + smp_store_release(&dentry->d_inode->i_private, NULL); +} - ENTER(); +static void ffs_epfiles_destroy(struct super_block *sb, + struct ffs_epfile *epfiles, unsigned count) +{ + struct ffs_epfile *epfile = epfiles; + struct dentry *root = sb->s_root; for (; count; --count, ++epfile) { BUG_ON(mutex_is_locked(&epfile->mutex)); - if (epfile->dentry) { - d_delete(epfile->dentry); - dput(epfile->dentry); - epfile->dentry = NULL; - } + simple_remove_by_name(root, epfile->name, clear_one); } kfree(epfiles); @@ -1998,7 +2433,12 @@ static int ffs_func_eps_enable(struct ffs_function *func) ep = func->eps; epfile = ffs->epfiles; count = ffs->eps_count; - while(count--) { + if (!epfile) { + ret = -ENOMEM; + goto done; + } + + while (count--) { ep->ep->driver_data = ep; ret = config_ep_by_speed(func->gadget, &func->function, ep->ep); @@ -2022,6 +2462,7 @@ static int ffs_func_eps_enable(struct ffs_function *func) } wake_up_interruptible(&ffs->wait); +done: spin_unlock_irqrestore(&func->ffs->eps_lock, flags); return ret; @@ -2056,14 +2497,12 @@ typedef int (*ffs_os_desc_callback)(enum ffs_os_desc_type entity, static int __must_check ffs_do_single_desc(char *data, unsigned len, ffs_entity_callback entity, - void *priv, int *current_class) + void *priv, int *current_class, int *current_subclass) { struct usb_descriptor_header *_ds = (void *)data; u8 length; int ret; - ENTER(); - /* At least two bytes are required: length and type */ if (len < 2) { pr_vdebug("descriptor too short\n"); @@ -2115,6 +2554,7 @@ static int __must_check ffs_do_single_desc(char *data, unsigned len, if (ds->iInterface) __entity(STRING, ds->iInterface); *current_class = ds->bInterfaceClass; + *current_subclass = ds->bInterfaceSubClass; } break; @@ -2139,6 +2579,12 @@ static int __must_check ffs_do_single_desc(char *data, unsigned len, if (length != sizeof(struct ccid_descriptor)) goto inv_length; break; + } else if (*current_class == USB_CLASS_APP_SPEC && + *current_subclass == USB_SUBCLASS_DFU) { + pr_vdebug("dfu functional descriptor\n"); + if (length != sizeof(struct usb_dfu_functional_descriptor)) + goto inv_length; + break; } else { pr_vdebug("unknown descriptor: %d for class %d\n", _ds->bDescriptorType, *current_class); @@ -2201,8 +2647,7 @@ static int __must_check ffs_do_descs(unsigned count, char *data, unsigned len, const unsigned _len = len; unsigned long num = 0; int current_class = -1; - - ENTER(); + int current_subclass = -1; for (;;) { int ret; @@ -2222,7 +2667,7 @@ static int __must_check ffs_do_descs(unsigned count, char *data, unsigned len, return _len - len; ret = ffs_do_single_desc(data, len, entity, priv, - ¤t_class); + ¤t_class, ¤t_subclass); if (ret < 0) { pr_debug("%s returns %d\n", __func__, ret); return ret; @@ -2241,8 +2686,6 @@ static int __ffs_data_do_entity(enum ffs_entity_type type, struct ffs_desc_helper *helper = priv; struct usb_endpoint_descriptor *d; - ENTER(); - switch (type) { case FFS_DESCRIPTOR: break; @@ -2290,8 +2733,11 @@ static int __ffs_do_os_desc_header(enum ffs_os_desc_type *next_type, u16 bcd_version = le16_to_cpu(desc->bcdVersion); u16 w_index = le16_to_cpu(desc->wIndex); - if (bcd_version != 1) { - pr_vdebug("unsupported os descriptors version: %d", + if (bcd_version == 0x1) { + pr_warn("bcdVersion must be 0x0100, stored in Little Endian order. " + "Userspace driver should be fixed, accepting 0x0001 for compatibility.\n"); + } else if (bcd_version != 0x100) { + pr_vdebug("unsupported os descriptors version: 0x%x\n", bcd_version); return -EINVAL; } @@ -2324,8 +2770,6 @@ static int __must_check ffs_do_single_os_desc(char *data, unsigned len, int ret; const unsigned _len = len; - ENTER(); - /* loop over all ext compat/ext prop descriptors */ while (feature_count--) { ret = entity(type, h, data, len, priv); @@ -2347,8 +2791,6 @@ static int __must_check ffs_do_os_descs(unsigned count, const unsigned _len = len; unsigned long num = 0; - ENTER(); - for (num = 0; num < count; ++num) { int ret; enum ffs_os_desc_type type; @@ -2411,8 +2853,6 @@ static int __ffs_data_do_os_desc(enum ffs_os_desc_type type, struct ffs_data *ffs = priv; u8 length; - ENTER(); - switch (type) { case FFS_OS_DESC_EXT_COMPAT: { struct usb_ext_compat_desc *d = data; @@ -2488,8 +2928,6 @@ static int __ffs_data_got_descs(struct ffs_data *ffs, int ret = -EINVAL, i; struct ffs_desc_helper helper; - ENTER(); - if (get_unaligned_le32(data + 4) != len) goto error; @@ -2620,8 +3058,6 @@ static int __ffs_data_got_strings(struct ffs_data *ffs, const char *data = _data; struct usb_string *s; - ENTER(); - if (len < 16 || get_unaligned_le32(data) != FUNCTIONFS_STRINGS_MAGIC || get_unaligned_le32(data + 4) != len) @@ -2813,7 +3249,7 @@ static void __ffs_event_add(struct ffs_data *ffs, ffs->ev.types[ffs->ev.count++] = type; wake_up_locked(&ffs->ev.waitq); if (ffs->ffs_eventfd) - eventfd_signal(ffs->ffs_eventfd, 1); + eventfd_signal(ffs->ffs_eventfd); } static void ffs_event_add(struct ffs_data *ffs, @@ -2880,7 +3316,7 @@ static int __ffs_func_bind_do_descs(enum ffs_entity_type type, u8 *valuep, if (ffs_ep->descs[ep_desc_id]) { pr_err("two %sspeed descriptors for EP %d\n", speed_names[ep_desc_id], - ds->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); + usb_endpoint_num(ds)); return -EINVAL; } ffs_ep->descs[ep_desc_id] = ds; @@ -3006,9 +3442,8 @@ static int __ffs_func_bind_do_os_desc(enum ffs_os_desc_type type, t = &func->function.os_desc_table[desc->bFirstInterfaceNumber]; t->if_id = func->interfaces_nums[desc->bFirstInterfaceNumber]; - memcpy(t->os_desc->ext_compat_id, &desc->CompatibleID, - ARRAY_SIZE(desc->CompatibleID) + - ARRAY_SIZE(desc->SubCompatibleID)); + memcpy(t->os_desc->ext_compat_id, &desc->IDs, + sizeof_field(struct usb_ext_compat_desc, IDs)); length = sizeof(*desc); } break; @@ -3080,8 +3515,6 @@ static inline struct f_fs_opts *ffs_do_functionfs_bind(struct usb_function *f, struct ffs_data *ffs_data; int ret; - ENTER(); - /* * Legacy gadget triggers binding in functionfs_ready_callback, * which already uses locking; taking the same lock here would @@ -3158,8 +3591,6 @@ static int _ffs_func_bind(struct usb_configuration *c, vla_item_with_sz(d, char, raw_descs, ffs->raw_descs_length); char *vlabuf; - ENTER(); - /* Has descriptors only for speeds gadget does not support */ if (!(full | high | super)) return -ENOTSUPP; @@ -3311,6 +3742,15 @@ static void ffs_reset_work(struct work_struct *work) ffs_data_reset(ffs); } +static int ffs_func_get_alt(struct usb_function *f, + unsigned int interface) +{ + struct ffs_function *func = ffs_func_from_usb(f); + int intf = ffs_func_revmap_intf(func, interface); + + return (intf < 0) ? intf : func->cur_alt[interface]; +} + static int ffs_func_set_alt(struct usb_function *f, unsigned interface, unsigned alt) { @@ -3318,11 +3758,12 @@ static int ffs_func_set_alt(struct usb_function *f, struct ffs_data *ffs = func->ffs; int ret = 0, intf; - if (alt != (unsigned)-1) { - intf = ffs_func_revmap_intf(func, interface); - if (intf < 0) - return intf; - } + if (alt > MAX_ALT_SETTINGS) + return -EINVAL; + + intf = ffs_func_revmap_intf(func, interface); + if (intf < 0) + return intf; if (ffs->func) ffs_func_eps_disable(ffs->func); @@ -3337,22 +3778,34 @@ static int ffs_func_set_alt(struct usb_function *f, if (ffs->state != FFS_ACTIVE) return -ENODEV; - if (alt == (unsigned)-1) { - ffs->func = NULL; - ffs_event_add(ffs, FUNCTIONFS_DISABLE); - return 0; - } - ffs->func = func; ret = ffs_func_eps_enable(func); - if (ret >= 0) + if (ret >= 0) { ffs_event_add(ffs, FUNCTIONFS_ENABLE); + func->cur_alt[interface] = alt; + } return ret; } static void ffs_func_disable(struct usb_function *f) { - ffs_func_set_alt(f, 0, (unsigned)-1); + struct ffs_function *func = ffs_func_from_usb(f); + struct ffs_data *ffs = func->ffs; + + if (ffs->func) + ffs_func_eps_disable(ffs->func); + + if (ffs->state == FFS_DEACTIVATED) { + ffs->state = FFS_CLOSING; + INIT_WORK(&ffs->reset_work, ffs_reset_work); + schedule_work(&ffs->reset_work); + return; + } + + if (ffs->state == FFS_ACTIVE) { + ffs->func = NULL; + ffs_event_add(ffs, FUNCTIONFS_DISABLE); + } } static int ffs_func_setup(struct usb_function *f, @@ -3363,8 +3816,6 @@ static int ffs_func_setup(struct usb_function *f, unsigned long flags; int ret; - ENTER(); - pr_vdebug("creq->bRequestType = %02x\n", creq->bRequestType); pr_vdebug("creq->bRequest = %02x\n", creq->bRequest); pr_vdebug("creq->wValue = %04x\n", le16_to_cpu(creq->wValue)); @@ -3412,7 +3863,7 @@ static int ffs_func_setup(struct usb_function *f, __ffs_event_add(ffs, FUNCTIONFS_SETUP); spin_unlock_irqrestore(&ffs->ev.waitq.lock, flags); - return creq->wLength == 0 ? USB_GADGET_DELAYED_STATUS : 0; + return ffs->ev.setup.wLength == 0 ? USB_GADGET_DELAYED_STATUS : 0; } static bool ffs_func_req_match(struct usb_function *f, @@ -3439,13 +3890,11 @@ static bool ffs_func_req_match(struct usb_function *f, static void ffs_func_suspend(struct usb_function *f) { - ENTER(); ffs_event_add(ffs_func_from_usb(f)->ffs, FUNCTIONFS_SUSPEND); } static void ffs_func_resume(struct usb_function *f) { - ENTER(); ffs_event_add(ffs_func_from_usb(f)->ffs, FUNCTIONFS_RESUME); } @@ -3529,6 +3978,25 @@ static inline struct f_fs_opts *to_ffs_opts(struct config_item *item) func_inst.group); } +static ssize_t f_fs_opts_ready_show(struct config_item *item, char *page) +{ + struct f_fs_opts *opts = to_ffs_opts(item); + int ready; + + ffs_dev_lock(); + ready = opts->dev->desc_ready; + ffs_dev_unlock(); + + return sprintf(page, "%d\n", ready); +} + +CONFIGFS_ATTR_RO(f_fs_opts_, ready); + +static struct configfs_attribute *ffs_attrs[] = { + &f_fs_opts_attr_ready, + NULL, +}; + static void ffs_attr_release(struct config_item *item) { struct f_fs_opts *opts = to_ffs_opts(item); @@ -3542,6 +4010,7 @@ static struct configfs_item_operations ffs_item_ops = { static const struct config_item_type ffs_func_type = { .ct_item_ops = &ffs_item_ops, + .ct_attrs = ffs_attrs, .ct_owner = THIS_MODULE, }; @@ -3609,7 +4078,6 @@ static void ffs_func_unbind(struct usb_configuration *c, unsigned count = ffs->eps_count; unsigned long flags; - ENTER(); if (ffs->func == func) { ffs_func_eps_disable(func); ffs->func = NULL; @@ -3618,6 +4086,7 @@ static void ffs_func_unbind(struct usb_configuration *c, /* Drain any pending AIO completions */ drain_workqueue(ffs->io_completion_wq); + ffs_event_add(ffs, FUNCTIONFS_UNBIND); if (!--opts->refcnt) functionfs_unbind(ffs); @@ -3642,15 +4111,12 @@ static void ffs_func_unbind(struct usb_configuration *c, func->function.ssp_descriptors = NULL; func->interfaces_nums = NULL; - ffs_event_add(ffs, FUNCTIONFS_UNBIND); } static struct usb_function *ffs_alloc(struct usb_function_instance *fi) { struct ffs_function *func; - ENTER(); - func = kzalloc(sizeof(*func), GFP_KERNEL); if (!func) return ERR_PTR(-ENOMEM); @@ -3660,6 +4126,7 @@ static struct usb_function *ffs_alloc(struct usb_function_instance *fi) func->function.bind = ffs_func_bind; func->function.unbind = ffs_func_unbind; func->function.set_alt = ffs_func_set_alt; + func->function.get_alt = ffs_func_get_alt; func->function.disable = ffs_func_disable; func->function.setup = ffs_func_setup; func->function.req_match = ffs_func_req_match; @@ -3751,7 +4218,6 @@ static int ffs_acquire_dev(const char *dev_name, struct ffs_data *ffs_data) int ret = 0; struct ffs_dev *ffs_dev; - ENTER(); ffs_dev_lock(); ffs_dev = _ffs_find_dev(dev_name); @@ -3774,7 +4240,6 @@ static int ffs_acquire_dev(const char *dev_name, struct ffs_data *ffs_data) static void ffs_release_dev(struct ffs_dev *ffs_dev) { - ENTER(); ffs_dev_lock(); if (ffs_dev && ffs_dev->mounted) { @@ -3796,7 +4261,6 @@ static int ffs_ready(struct ffs_data *ffs) struct ffs_dev *ffs_obj; int ret = 0; - ENTER(); ffs_dev_lock(); ffs_obj = ffs->private_data; @@ -3829,7 +4293,6 @@ static void ffs_closed(struct ffs_data *ffs) struct f_fs_opts *opts; struct config_item *ci; - ENTER(); ffs_dev_lock(); ffs_obj = ffs->private_data; @@ -3888,5 +4351,6 @@ static char *ffs_prepare_buffer(const char __user *buf, size_t len) } DECLARE_USB_FUNCTION_INIT(ffs, ffs_alloc_inst, ffs_alloc); +MODULE_DESCRIPTION("user mode file system API for USB composite function controllers"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Michal Nazarewicz"); diff --git a/drivers/usb/gadget/function/f_hid.c b/drivers/usb/gadget/function/f_hid.c index a8da3b4a2855..3ddfd4f66f0b 100644 --- a/drivers/usb/gadget/function/f_hid.c +++ b/drivers/usb/gadget/function/f_hid.c @@ -15,18 +15,35 @@ #include <linux/uaccess.h> #include <linux/wait.h> #include <linux/sched.h> +#include <linux/workqueue.h> +#include <linux/usb/func_utils.h> #include <linux/usb/g_hid.h> +#include <uapi/linux/usb/g_hid.h> -#include "u_f.h" #include "u_hid.h" #define HIDG_MINORS 4 +/* + * Most operating systems seem to allow for 5000ms timeout, we will allow + * userspace half that time to respond before we return an empty report. + */ +#define GET_REPORT_TIMEOUT_MS 2500 + static int major, minors; -static struct class *hidg_class; + +static const struct class hidg_class = { + .name = "hidg", +}; + static DEFINE_IDA(hidg_ida); static DEFINE_MUTEX(hidg_ida_lock); /* protects access to hidg_ida */ +struct report_entry { + struct usb_hidg_report report_data; + struct list_head node; +}; + /*-------------------------------------------------------------------------*/ /* HID gadget struct */ @@ -45,6 +62,9 @@ struct f_hidg { unsigned short report_desc_length; char *report_desc; unsigned short report_length; + unsigned char interval; + bool interval_user_set; + /* * use_out_ep - if true, the OUT Endpoint (interrupt out method) * will be used to receive reports from the host @@ -58,6 +78,7 @@ struct f_hidg { /* recv report */ spinlock_t read_spinlock; wait_queue_head_t read_queue; + bool disabled; /* recv report - interrupt out only (use_out_ep == 1) */ struct list_head completed_out_req; unsigned int qlen; @@ -71,6 +92,19 @@ struct f_hidg { wait_queue_head_t write_queue; struct usb_request *req; + /* get report */ + struct usb_request *get_req; + struct usb_hidg_report get_report; + bool get_report_returned; + int get_report_req_report_id; + int get_report_req_report_length; + spinlock_t get_report_spinlock; + wait_queue_head_t get_queue; /* Waiting for userspace response */ + wait_queue_head_t get_id_queue; /* Get ID came in */ + struct work_struct work; + struct workqueue_struct *workqueue; + struct list_head report_list; + struct device dev; struct cdev cdev; struct usb_function func; @@ -88,6 +122,7 @@ static void hidg_release(struct device *dev) { struct f_hidg *hidg = container_of(dev, struct f_hidg, dev); + kfree(hidg->report_desc); kfree(hidg->set_report_buf); kfree(hidg); } @@ -113,8 +148,8 @@ static struct hid_descriptor hidg_desc = { .bcdHID = cpu_to_le16(0x0101), .bCountryCode = 0x00, .bNumDescriptors = 0x1, - /*.desc[0].bDescriptorType = DYNAMIC */ - /*.desc[0].wDescriptorLenght = DYNAMIC */ + /*.rpt_desc.bDescriptorType = DYNAMIC */ + /*.rpt_desc.wDescriptorLength = DYNAMIC */ }; /* Super-Speed Support */ @@ -125,10 +160,7 @@ static struct usb_endpoint_descriptor hidg_ss_in_ep_desc = { .bEndpointAddress = USB_DIR_IN, .bmAttributes = USB_ENDPOINT_XFER_INT, /*.wMaxPacketSize = DYNAMIC */ - .bInterval = 4, /* FIXME: Add this field in the - * HID gadget configuration? - * (struct hidg_func_descriptor) - */ + /*.bInterval = DYNAMIC */ }; static struct usb_ss_ep_comp_descriptor hidg_ss_in_comp_desc = { @@ -146,10 +178,7 @@ static struct usb_endpoint_descriptor hidg_ss_out_ep_desc = { .bEndpointAddress = USB_DIR_OUT, .bmAttributes = USB_ENDPOINT_XFER_INT, /*.wMaxPacketSize = DYNAMIC */ - .bInterval = 4, /* FIXME: Add this field in the - * HID gadget configuration? - * (struct hidg_func_descriptor) - */ + /*.bInterval = DYNAMIC */ }; static struct usb_ss_ep_comp_descriptor hidg_ss_out_comp_desc = { @@ -187,10 +216,7 @@ static struct usb_endpoint_descriptor hidg_hs_in_ep_desc = { .bEndpointAddress = USB_DIR_IN, .bmAttributes = USB_ENDPOINT_XFER_INT, /*.wMaxPacketSize = DYNAMIC */ - .bInterval = 4, /* FIXME: Add this field in the - * HID gadget configuration? - * (struct hidg_func_descriptor) - */ + /* .bInterval = DYNAMIC */ }; static struct usb_endpoint_descriptor hidg_hs_out_ep_desc = { @@ -199,10 +225,7 @@ static struct usb_endpoint_descriptor hidg_hs_out_ep_desc = { .bEndpointAddress = USB_DIR_OUT, .bmAttributes = USB_ENDPOINT_XFER_INT, /*.wMaxPacketSize = DYNAMIC */ - .bInterval = 4, /* FIXME: Add this field in the - * HID gadget configuration? - * (struct hidg_func_descriptor) - */ + /*.bInterval = DYNAMIC */ }; static struct usb_descriptor_header *hidg_hs_descriptors_intout[] = { @@ -228,10 +251,7 @@ static struct usb_endpoint_descriptor hidg_fs_in_ep_desc = { .bEndpointAddress = USB_DIR_IN, .bmAttributes = USB_ENDPOINT_XFER_INT, /*.wMaxPacketSize = DYNAMIC */ - .bInterval = 10, /* FIXME: Add this field in the - * HID gadget configuration? - * (struct hidg_func_descriptor) - */ + /*.bInterval = DYNAMIC */ }; static struct usb_endpoint_descriptor hidg_fs_out_ep_desc = { @@ -240,10 +260,7 @@ static struct usb_endpoint_descriptor hidg_fs_out_ep_desc = { .bEndpointAddress = USB_DIR_OUT, .bmAttributes = USB_ENDPOINT_XFER_INT, /*.wMaxPacketSize = DYNAMIC */ - .bInterval = 10, /* FIXME: Add this field in the - * HID gadget configuration? - * (struct hidg_func_descriptor) - */ + /*.bInterval = DYNAMIC */ }; static struct usb_descriptor_header *hidg_fs_descriptors_intout[] = { @@ -298,7 +315,7 @@ static ssize_t f_hidg_intout_read(struct file *file, char __user *buffer, spin_lock_irqsave(&hidg->read_spinlock, flags); -#define READ_COND_INTOUT (!list_empty(&hidg->completed_out_req)) +#define READ_COND_INTOUT (!list_empty(&hidg->completed_out_req) || hidg->disabled) /* wait for at least one buffer to complete */ while (!READ_COND_INTOUT) { @@ -312,6 +329,11 @@ static ssize_t f_hidg_intout_read(struct file *file, char __user *buffer, spin_lock_irqsave(&hidg->read_spinlock, flags); } + if (hidg->disabled) { + spin_unlock_irqrestore(&hidg->read_spinlock, flags); + return -ESHUTDOWN; + } + /* pick the first one */ list = list_first_entry(&hidg->completed_out_req, struct f_hidg_req_list, list); @@ -356,7 +378,7 @@ static ssize_t f_hidg_intout_read(struct file *file, char __user *buffer, return count; } -#define READ_COND_SSREPORT (hidg->set_report_buf != NULL) +#define READ_COND_SSREPORT (hidg->set_report_buf != NULL || hidg->disabled) static ssize_t f_hidg_ssreport_read(struct file *file, char __user *buffer, size_t count, loff_t *ptr) @@ -489,7 +511,7 @@ try_again: } req->status = 0; - req->zero = 0; + req->zero = 1; req->length = count; req->complete = f_hidg_req_complete; req->context = hidg; @@ -519,6 +541,174 @@ release_write_pending: return status; } +static struct report_entry *f_hidg_search_for_report(struct f_hidg *hidg, u8 report_id) +{ + struct list_head *ptr; + struct report_entry *entry; + + list_for_each(ptr, &hidg->report_list) { + entry = list_entry(ptr, struct report_entry, node); + if (entry->report_data.report_id == report_id) + return entry; + } + + return NULL; +} + +static void get_report_workqueue_handler(struct work_struct *work) +{ + struct f_hidg *hidg = container_of(work, struct f_hidg, work); + struct usb_composite_dev *cdev = hidg->func.config->cdev; + struct usb_request *req; + struct report_entry *ptr; + unsigned long flags; + + int status = 0; + + spin_lock_irqsave(&hidg->get_report_spinlock, flags); + req = hidg->get_req; + if (!req) { + spin_unlock_irqrestore(&hidg->get_report_spinlock, flags); + return; + } + + req->zero = 0; + req->length = min_t(unsigned int, min_t(unsigned int, hidg->get_report_req_report_length, + hidg->report_length), + MAX_REPORT_LENGTH); + + /* Check if there is a response available for immediate response */ + ptr = f_hidg_search_for_report(hidg, hidg->get_report_req_report_id); + if (ptr && !ptr->report_data.userspace_req) { + /* Report exists in list and it is to be used for immediate response */ + req->buf = ptr->report_data.data; + status = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + hidg->get_report_returned = true; + spin_unlock_irqrestore(&hidg->get_report_spinlock, flags); + } else { + /* + * Report does not exist in list or should not be immediately sent + * i.e. give userspace time to respond + */ + hidg->get_report_returned = false; + spin_unlock_irqrestore(&hidg->get_report_spinlock, flags); + wake_up(&hidg->get_id_queue); +#define GET_REPORT_COND (!hidg->get_report_returned) + /* Wait until userspace has responded or timeout */ + status = wait_event_interruptible_timeout(hidg->get_queue, !GET_REPORT_COND, + msecs_to_jiffies(GET_REPORT_TIMEOUT_MS)); + spin_lock_irqsave(&hidg->get_report_spinlock, flags); + req = hidg->get_req; + if (!req) { + spin_unlock_irqrestore(&hidg->get_report_spinlock, flags); + return; + } + if (status == 0 && !hidg->get_report_returned) { + /* GET_REPORT request was not serviced by userspace within timeout period */ + VDBG(cdev, "get_report : userspace timeout.\n"); + hidg->get_report_returned = true; + } + + /* Search again for report ID in list and respond to GET_REPORT request */ + ptr = f_hidg_search_for_report(hidg, hidg->get_report_req_report_id); + if (ptr) { + /* + * Either get an updated response just serviced by userspace + * or send the latest response in the list + */ + req->buf = ptr->report_data.data; + } else { + /* If there are no prevoiusly sent reports send empty report */ + req->buf = hidg->get_report.data; + memset(req->buf, 0x0, req->length); + } + + status = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + spin_unlock_irqrestore(&hidg->get_report_spinlock, flags); + } + + if (status < 0) + VDBG(cdev, "usb_ep_queue error on ep0 responding to GET_REPORT\n"); +} + +static int f_hidg_get_report_id(struct file *file, __u8 __user *buffer) +{ + struct f_hidg *hidg = file->private_data; + int ret = 0; + + ret = put_user(hidg->get_report_req_report_id, buffer); + + return ret; +} + +static int f_hidg_get_report(struct file *file, struct usb_hidg_report __user *buffer) +{ + struct f_hidg *hidg = file->private_data; + struct usb_composite_dev *cdev = hidg->func.config->cdev; + unsigned long flags; + struct report_entry *entry; + struct report_entry *ptr; + __u8 report_id; + + entry = kmalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) + return -ENOMEM; + + if (copy_from_user(&entry->report_data, buffer, + sizeof(struct usb_hidg_report))) { + ERROR(cdev, "copy_from_user error\n"); + kfree(entry); + return -EINVAL; + } + + report_id = entry->report_data.report_id; + + spin_lock_irqsave(&hidg->get_report_spinlock, flags); + ptr = f_hidg_search_for_report(hidg, report_id); + + if (ptr) { + /* Report already exists in list - update it */ + if (copy_from_user(&ptr->report_data, buffer, + sizeof(struct usb_hidg_report))) { + spin_unlock_irqrestore(&hidg->get_report_spinlock, flags); + ERROR(cdev, "copy_from_user error\n"); + kfree(entry); + return -EINVAL; + } + kfree(entry); + } else { + /* Report does not exist in list - add it */ + list_add_tail(&entry->node, &hidg->report_list); + } + + /* If there is no response pending then do nothing further */ + if (hidg->get_report_returned) { + spin_unlock_irqrestore(&hidg->get_report_spinlock, flags); + return 0; + } + + /* If this userspace response serves the current pending report */ + if (hidg->get_report_req_report_id == report_id) { + hidg->get_report_returned = true; + wake_up(&hidg->get_queue); + } + + spin_unlock_irqrestore(&hidg->get_report_spinlock, flags); + return 0; +} + +static long f_hidg_ioctl(struct file *file, unsigned int code, unsigned long arg) +{ + switch (code) { + case GADGET_HID_READ_GET_REPORT_ID: + return f_hidg_get_report_id(file, (__u8 __user *)arg); + case GADGET_HID_WRITE_GET_REPORT: + return f_hidg_get_report(file, (struct usb_hidg_report __user *)arg); + default: + return -ENOTTY; + } +} + static __poll_t f_hidg_poll(struct file *file, poll_table *wait) { struct f_hidg *hidg = file->private_data; @@ -526,6 +716,8 @@ static __poll_t f_hidg_poll(struct file *file, poll_table *wait) poll_wait(file, &hidg->read_queue, wait); poll_wait(file, &hidg->write_queue, wait); + poll_wait(file, &hidg->get_queue, wait); + poll_wait(file, &hidg->get_id_queue, wait); if (WRITE_COND) ret |= EPOLLOUT | EPOLLWRNORM; @@ -538,12 +730,16 @@ static __poll_t f_hidg_poll(struct file *file, poll_table *wait) ret |= EPOLLIN | EPOLLRDNORM; } + if (GET_REPORT_COND) + ret |= EPOLLPRI; + return ret; } #undef WRITE_COND #undef READ_COND_SSREPORT #undef READ_COND_INTOUT +#undef GET_REPORT_COND static int f_hidg_release(struct inode *inode, struct file *fd) { @@ -636,6 +832,10 @@ static void hidg_ssreport_complete(struct usb_ep *ep, struct usb_request *req) wake_up(&hidg->read_queue); } +static void hidg_get_report_complete(struct usb_ep *ep, struct usb_request *req) +{ +} + static int hidg_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) { @@ -644,6 +844,7 @@ static int hidg_setup(struct usb_function *f, struct usb_request *req = cdev->req; int status = 0; __u16 value, length; + unsigned long flags; value = __le16_to_cpu(ctrl->wValue); length = __le16_to_cpu(ctrl->wLength); @@ -655,14 +856,20 @@ static int hidg_setup(struct usb_function *f, switch ((ctrl->bRequestType << 8) | ctrl->bRequest) { case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8 | HID_REQ_GET_REPORT): - VDBG(cdev, "get_report\n"); + VDBG(cdev, "get_report | wLength=%d\n", ctrl->wLength); - /* send an empty report */ - length = min_t(unsigned, length, hidg->report_length); - memset(req->buf, 0x0, length); + /* + * Update GET_REPORT ID so that an ioctl can be used to determine what + * GET_REPORT the request was actually for. + */ + spin_lock_irqsave(&hidg->get_report_spinlock, flags); + hidg->get_report_req_report_id = value & 0xff; + hidg->get_report_req_report_length = length; + spin_unlock_irqrestore(&hidg->get_report_spinlock, flags); - goto respond; - break; + queue_work(hidg->workqueue, &hidg->work); + + return status; case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8 | HID_REQ_GET_PROTOCOL): @@ -723,8 +930,8 @@ static int hidg_setup(struct usb_function *f, struct hid_descriptor hidg_desc_copy = hidg_desc; VDBG(cdev, "USB_REQ_GET_DESCRIPTOR: HID\n"); - hidg_desc_copy.desc[0].bDescriptorType = HID_DT_REPORT; - hidg_desc_copy.desc[0].wDescriptorLength = + hidg_desc_copy.rpt_desc.bDescriptorType = HID_DT_REPORT; + hidg_desc_copy.rpt_desc.wDescriptorLength = cpu_to_le16(hidg->report_desc_length); length = min_t(unsigned short, length, @@ -760,7 +967,7 @@ stall: return -EOPNOTSUPP; respond: - req->zero = 0; + req->zero = 1; req->length = length; status = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); if (status < 0) @@ -788,6 +995,19 @@ static void hidg_disable(struct usb_function *f) spin_unlock_irqrestore(&hidg->read_spinlock, flags); } + spin_lock_irqsave(&hidg->get_report_spinlock, flags); + if (!hidg->get_report_returned) { + usb_ep_free_request(f->config->cdev->gadget->ep0, hidg->get_req); + hidg->get_req = NULL; + hidg->get_report_returned = true; + } + spin_unlock_irqrestore(&hidg->get_report_spinlock, flags); + + spin_lock_irqsave(&hidg->read_spinlock, flags); + hidg->disabled = true; + spin_unlock_irqrestore(&hidg->read_spinlock, flags); + wake_up(&hidg->read_queue); + spin_lock_irqsave(&hidg->write_spinlock, flags); if (!hidg->write_pending) { free_ep_req(hidg->in_ep, hidg->req); @@ -873,6 +1093,10 @@ static int hidg_set_alt(struct usb_function *f, unsigned intf, unsigned alt) } } + spin_lock_irqsave(&hidg->read_spinlock, flags); + hidg->disabled = false; + spin_unlock_irqrestore(&hidg->read_spinlock, flags); + if (hidg->in_ep != NULL) { spin_lock_irqsave(&hidg->write_spinlock, flags); hidg->req = req_in; @@ -897,6 +1121,14 @@ fail: return status; } +#ifdef CONFIG_COMPAT +static long f_hidg_compat_ioctl(struct file *file, unsigned int code, + unsigned long value) +{ + return f_hidg_ioctl(file, code, value); +} +#endif + static const struct file_operations f_hidg_fops = { .owner = THIS_MODULE, .open = f_hidg_open, @@ -904,6 +1136,10 @@ static const struct file_operations f_hidg_fops = { .write = f_hidg_write, .read = f_hidg_read, .poll = f_hidg_poll, + .unlocked_ioctl = f_hidg_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = f_hidg_compat_ioctl, +#endif .llseek = noop_llseek, }; @@ -914,6 +1150,15 @@ static int hidg_bind(struct usb_configuration *c, struct usb_function *f) struct usb_string *us; int status; + hidg->get_req = usb_ep_alloc_request(c->cdev->gadget->ep0, GFP_ATOMIC); + if (!hidg->get_req) + return -ENOMEM; + + hidg->get_req->zero = 0; + hidg->get_req->complete = hidg_get_report_complete; + hidg->get_req->context = hidg; + hidg->get_report_returned = true; + /* maybe allocate device-global string IDs, and patch descriptors */ us = usb_gstrings_attach(c->cdev, ct_func_strings, ARRAY_SIZE(ct_func_string_defs)); @@ -957,6 +1202,16 @@ static int hidg_bind(struct usb_configuration *c, struct usb_function *f) hidg_hs_in_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length); hidg_fs_in_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length); hidg_ss_out_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length); + + /* IN endpoints: FS default=10ms, HS default=4µ-frame; user override if set */ + if (!hidg->interval_user_set) { + hidg_fs_in_ep_desc.bInterval = 10; + hidg_hs_in_ep_desc.bInterval = 4; + } else { + hidg_fs_in_ep_desc.bInterval = hidg->interval; + hidg_hs_in_ep_desc.bInterval = hidg->interval; + } + hidg_ss_out_comp_desc.wBytesPerInterval = cpu_to_le16(hidg->report_length); hidg_hs_out_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length); @@ -965,8 +1220,8 @@ static int hidg_bind(struct usb_configuration *c, struct usb_function *f) * We can use hidg_desc struct here but we should not relay * that its content won't change after returning from this function. */ - hidg_desc.desc[0].bDescriptorType = HID_DT_REPORT; - hidg_desc.desc[0].wDescriptorLength = + hidg_desc.rpt_desc.bDescriptorType = HID_DT_REPORT; + hidg_desc.rpt_desc.wDescriptorLength = cpu_to_le16(hidg->report_desc_length); hidg_hs_in_ep_desc.bEndpointAddress = @@ -979,19 +1234,27 @@ static int hidg_bind(struct usb_configuration *c, struct usb_function *f) hidg_ss_out_ep_desc.bEndpointAddress = hidg_fs_out_ep_desc.bEndpointAddress; - if (hidg->use_out_ep) + if (hidg->use_out_ep) { + /* OUT endpoints: same defaults (FS=10, HS=4) unless user set */ + if (!hidg->interval_user_set) { + hidg_fs_out_ep_desc.bInterval = 10; + hidg_hs_out_ep_desc.bInterval = 4; + } else { + hidg_fs_out_ep_desc.bInterval = hidg->interval; + hidg_hs_out_ep_desc.bInterval = hidg->interval; + } status = usb_assign_descriptors(f, - hidg_fs_descriptors_intout, - hidg_hs_descriptors_intout, - hidg_ss_descriptors_intout, - hidg_ss_descriptors_intout); - else + hidg_fs_descriptors_intout, + hidg_hs_descriptors_intout, + hidg_ss_descriptors_intout, + hidg_ss_descriptors_intout); + } else { status = usb_assign_descriptors(f, hidg_fs_descriptors_ssreport, hidg_hs_descriptors_ssreport, hidg_ss_descriptors_ssreport, hidg_ss_descriptors_ssreport); - + } if (status) goto fail; @@ -999,17 +1262,33 @@ static int hidg_bind(struct usb_configuration *c, struct usb_function *f) hidg->write_pending = 1; hidg->req = NULL; spin_lock_init(&hidg->read_spinlock); + spin_lock_init(&hidg->get_report_spinlock); init_waitqueue_head(&hidg->write_queue); init_waitqueue_head(&hidg->read_queue); + init_waitqueue_head(&hidg->get_queue); + init_waitqueue_head(&hidg->get_id_queue); INIT_LIST_HEAD(&hidg->completed_out_req); + INIT_LIST_HEAD(&hidg->report_list); + + INIT_WORK(&hidg->work, get_report_workqueue_handler); + hidg->workqueue = alloc_workqueue("report_work", + WQ_FREEZABLE | WQ_MEM_RECLAIM | WQ_PERCPU, + 1); + + if (!hidg->workqueue) { + status = -ENOMEM; + goto fail_free_descs; + } /* create char device */ cdev_init(&hidg->cdev, &f_hidg_fops); status = cdev_device_add(&hidg->cdev, &hidg->dev); if (status) - goto fail_free_descs; + goto fail_free_all; return 0; +fail_free_all: + destroy_workqueue(hidg->workqueue); fail_free_descs: usb_free_all_descriptors(f); fail: @@ -1017,6 +1296,9 @@ fail: if (hidg->req != NULL) free_ep_req(hidg->in_ep, hidg->req); + usb_ep_free_request(c->cdev->gadget->ep0, hidg->get_req); + hidg->get_req = NULL; + return status; } @@ -1024,9 +1306,9 @@ static inline int hidg_get_minor(void) { int ret; - ret = ida_simple_get(&hidg_ida, 0, 0, GFP_KERNEL); + ret = ida_alloc(&hidg_ida, GFP_KERNEL); if (ret >= HIDG_MINORS) { - ida_simple_remove(&hidg_ida, ret); + ida_free(&hidg_ida, ret); ret = -ENODEV; } @@ -1144,6 +1426,53 @@ end: CONFIGFS_ATTR(f_hid_opts_, report_desc); +static ssize_t f_hid_opts_interval_show(struct config_item *item, char *page) +{ + struct f_hid_opts *opts = to_f_hid_opts(item); + int result; + + mutex_lock(&opts->lock); + result = sprintf(page, "%d\n", opts->interval); + mutex_unlock(&opts->lock); + + return result; +} + +static ssize_t f_hid_opts_interval_store(struct config_item *item, + const char *page, size_t len) +{ + struct f_hid_opts *opts = to_f_hid_opts(item); + int ret; + unsigned int tmp; + + mutex_lock(&opts->lock); + if (opts->refcnt) { + ret = -EBUSY; + goto end; + } + + /* parse into a wider type first */ + ret = kstrtouint(page, 0, &tmp); + if (ret) + goto end; + + /* range-check against unsigned char max */ + if (tmp > 255) { + ret = -EINVAL; + goto end; + } + + opts->interval = (unsigned char)tmp; + opts->interval_user_set = true; + ret = len; + +end: + mutex_unlock(&opts->lock); + return ret; +} + +CONFIGFS_ATTR(f_hid_opts_, interval); + static ssize_t f_hid_opts_dev_show(struct config_item *item, char *page) { struct f_hid_opts *opts = to_f_hid_opts(item); @@ -1158,6 +1487,7 @@ static struct configfs_attribute *hid_attrs[] = { &f_hid_opts_attr_protocol, &f_hid_opts_attr_no_out_endpoint, &f_hid_opts_attr_report_length, + &f_hid_opts_attr_interval, &f_hid_opts_attr_report_desc, &f_hid_opts_attr_dev, NULL, @@ -1171,7 +1501,7 @@ static const struct config_item_type hid_func_type = { static inline void hidg_put_minor(int minor) { - ida_simple_remove(&hidg_ida, minor); + ida_free(&hidg_ida, minor); } static void hidg_free_inst(struct usb_function_instance *f) @@ -1204,6 +1534,10 @@ static struct usb_function_instance *hidg_alloc_inst(void) if (!opts) return ERR_PTR(-ENOMEM); mutex_init(&opts->lock); + + opts->interval = 4; + opts->interval_user_set = false; + opts->func_inst.free_func_inst = hidg_free_inst; ret = &opts->func_inst; @@ -1251,7 +1585,7 @@ static void hidg_unbind(struct usb_configuration *c, struct usb_function *f) struct f_hidg *hidg = func_to_hidg(f); cdev_device_del(&hidg->cdev, &hidg->dev); - + destroy_workqueue(hidg->workqueue); usb_free_all_descriptors(f); } @@ -1272,7 +1606,7 @@ static struct usb_function *hidg_alloc(struct usb_function_instance *fi) device_initialize(&hidg->dev); hidg->dev.release = hidg_release; - hidg->dev.class = hidg_class; + hidg->dev.class = &hidg_class; hidg->dev.devt = MKDEV(major, opts->minor); ret = dev_set_name(&hidg->dev, "hidg%d", opts->minor); if (ret) @@ -1282,10 +1616,12 @@ static struct usb_function *hidg_alloc(struct usb_function_instance *fi) hidg->bInterfaceProtocol = opts->protocol; hidg->report_length = opts->report_length; hidg->report_desc_length = opts->report_desc_length; + hidg->interval = opts->interval; + hidg->interval_user_set = opts->interval_user_set; if (opts->report_desc) { - hidg->report_desc = devm_kmemdup(&hidg->dev, opts->report_desc, - opts->report_desc_length, - GFP_KERNEL); + hidg->report_desc = kmemdup(opts->report_desc, + opts->report_desc_length, + GFP_KERNEL); if (!hidg->report_desc) { ret = -ENOMEM; goto err_put_device; @@ -1317,6 +1653,7 @@ err_unlock: } DECLARE_USB_FUNCTION_INIT(hid, hidg_alloc_inst, hidg_alloc); +MODULE_DESCRIPTION("USB HID function driver"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Fabien Chouteau"); @@ -1325,17 +1662,13 @@ int ghid_setup(struct usb_gadget *g, int count) int status; dev_t dev; - hidg_class = class_create(THIS_MODULE, "hidg"); - if (IS_ERR(hidg_class)) { - status = PTR_ERR(hidg_class); - hidg_class = NULL; + status = class_register(&hidg_class); + if (status) return status; - } status = alloc_chrdev_region(&dev, 0, count, "hidg"); if (status) { - class_destroy(hidg_class); - hidg_class = NULL; + class_unregister(&hidg_class); return status; } @@ -1352,6 +1685,5 @@ void ghid_cleanup(void) major = minors = 0; } - class_destroy(hidg_class); - hidg_class = NULL; + class_unregister(&hidg_class); } diff --git a/drivers/usb/gadget/function/f_loopback.c b/drivers/usb/gadget/function/f_loopback.c index ae41f556eb75..49b009a7d5d7 100644 --- a/drivers/usb/gadget/function/f_loopback.c +++ b/drivers/usb/gadget/function/f_loopback.c @@ -14,9 +14,9 @@ #include <linux/module.h> #include <linux/err.h> #include <linux/usb/composite.h> +#include <linux/usb/func_utils.h> #include "g_zero.h" -#include "u_f.h" /* * LOOPBACK FUNCTION ... a testing vehicle for USB peripherals, @@ -211,9 +211,7 @@ autoconf_fail: if (ret) return ret; - DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n", - (gadget_is_superspeed(c->cdev->gadget) ? "super" : - (gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full")), + DBG(cdev, "%s: IN/%s, OUT/%s\n", f->name, loop->in_ep->name, loop->out_ep->name); return 0; } @@ -595,4 +593,5 @@ void __exit lb_modexit(void) usb_function_unregister(&Loopbackusb_func); } +MODULE_DESCRIPTION("USB peripheral loopback configuration driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/function/f_mass_storage.c b/drivers/usb/gadget/function/f_mass_storage.c index 3a30feb47073..94d478b6bcd3 100644 --- a/drivers/usb/gadget/function/f_mass_storage.c +++ b/drivers/usb/gadget/function/f_mass_storage.c @@ -188,7 +188,7 @@ #include <linux/freezer.h> #include <linux/module.h> #include <linux/uaccess.h> -#include <asm/unaligned.h> +#include <linux/unaligned.h> #include <linux/usb/ch9.h> #include <linux/usb/gadget.h> @@ -500,7 +500,7 @@ static int fsg_setup(struct usb_function *f, *(u8 *)req->buf = _fsg_common_get_max_lun(fsg->common); /* Respond with data/status */ - req->length = min((u16)1, w_length); + req->length = min_t(u16, 1, w_length); return ep0_queue(fsg->common); } @@ -545,21 +545,37 @@ static int start_transfer(struct fsg_dev *fsg, struct usb_ep *ep, static bool start_in_transfer(struct fsg_common *common, struct fsg_buffhd *bh) { + int rc; + if (!fsg_is_set(common)) return false; bh->state = BUF_STATE_SENDING; - if (start_transfer(common->fsg, common->fsg->bulk_in, bh->inreq)) + rc = start_transfer(common->fsg, common->fsg->bulk_in, bh->inreq); + if (rc) { bh->state = BUF_STATE_EMPTY; + if (rc == -ESHUTDOWN) { + common->running = 0; + return false; + } + } return true; } static bool start_out_transfer(struct fsg_common *common, struct fsg_buffhd *bh) { + int rc; + if (!fsg_is_set(common)) return false; bh->state = BUF_STATE_RECEIVING; - if (start_transfer(common->fsg, common->fsg->bulk_out, bh->outreq)) + rc = start_transfer(common->fsg, common->fsg->bulk_out, bh->outreq); + if (rc) { bh->state = BUF_STATE_FULL; + if (rc == -ESHUTDOWN) { + common->running = 0; + return false; + } + } return true; } @@ -639,7 +655,7 @@ static int do_read(struct fsg_common *common) * And don't try to read past the end of the file. */ amount = min(amount_left, FSG_BUFLEN); - amount = min((loff_t)amount, + amount = min_t(loff_t, amount, curlun->file_length - file_offset); /* Wait for the next buffer to become available */ @@ -927,7 +943,7 @@ static void invalidate_sub(struct fsg_lun *curlun) { struct file *filp = curlun->filp; struct inode *inode = file_inode(filp); - unsigned long rc; + unsigned long __maybe_unused rc; rc = invalidate_mapping_pages(inode->i_mapping, 0, -1); VLDBG(curlun, "invalidate_mapping_pages -> %ld\n", rc); @@ -989,7 +1005,7 @@ static int do_verify(struct fsg_common *common) * And don't try to read past the end of the file. */ amount = min(amount_left, FSG_BUFLEN); - amount = min((loff_t)amount, + amount = min_t(loff_t, amount, curlun->file_length - file_offset); if (amount == 0) { curlun->sense_data = @@ -2126,8 +2142,8 @@ static int do_scsi_command(struct fsg_common *common) * of Posix locks. */ case FORMAT_UNIT: - case RELEASE: - case RESERVE: + case RELEASE_6: + case RESERVE_6: case SEND_DIAGNOSTIC: default: @@ -2151,7 +2167,7 @@ unknown_cmnd: if (reply == -EINVAL) reply = 0; /* Error reply length */ if (reply >= 0 && common->data_dir == DATA_DIR_TO_HOST) { - reply = min((u32)reply, common->data_size_from_cmnd); + reply = min_t(u32, reply, common->data_size_from_cmnd); bh->inreq->length = reply; bh->state = BUF_STATE_FULL; common->residue -= reply; @@ -2857,7 +2873,7 @@ int fsg_common_create_lun(struct fsg_common *common, struct fsg_lun_config *cfg, const char **name_pfx) { struct fsg_lun *lun; - char *pathbuf, *p; + char *pathbuf = NULL, *p = "(no medium)"; int rc = -ENOMEM; if (id >= ARRAY_SIZE(common->luns)) @@ -2907,12 +2923,9 @@ int fsg_common_create_lun(struct fsg_common *common, struct fsg_lun_config *cfg, rc = fsg_lun_open(lun, cfg->filename); if (rc) goto error_lun; - } - pathbuf = kmalloc(PATH_MAX, GFP_KERNEL); - p = "(no medium)"; - if (fsg_lun_is_open(lun)) { p = "(error)"; + pathbuf = kmalloc(PATH_MAX, GFP_KERNEL); if (pathbuf) { p = file_path(lun->filp, pathbuf, PATH_MAX); if (IS_ERR(p)) @@ -2931,7 +2944,6 @@ int fsg_common_create_lun(struct fsg_common *common, struct fsg_lun_config *cfg, error_lun: if (device_is_registered(&lun->dev)) device_unregister(&lun->dev); - fsg_lun_close(lun); common->luns[id] = NULL; error_sysfs: kfree(lun); @@ -3038,7 +3050,7 @@ static int fsg_bind(struct usb_configuration *c, struct usb_function *f) if (!common->thread_task) { common->state = FSG_STATE_NORMAL; common->thread_task = - kthread_create(fsg_main_thread, common, "file-storage"); + kthread_run(fsg_main_thread, common, "file-storage"); if (IS_ERR(common->thread_task)) { ret = PTR_ERR(common->thread_task); common->thread_task = NULL; @@ -3047,7 +3059,6 @@ static int fsg_bind(struct usb_configuration *c, struct usb_function *f) } DBG(common, "I/O thread pid: %d\n", task_pid_nr(common->thread_task)); - wake_up_process(common->thread_task); } fsg->gadget = gadget; @@ -3565,6 +3576,7 @@ static struct usb_function *fsg_alloc(struct usb_function_instance *fi) } DECLARE_USB_FUNCTION_INIT(mass_storage, fsg_alloc_inst, fsg_alloc); +MODULE_DESCRIPTION("Mass Storage USB Composite Function"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Michal Nazarewicz"); diff --git a/drivers/usb/gadget/function/f_mass_storage.h b/drivers/usb/gadget/function/f_mass_storage.h index 3b8c4ce2a40a..82ecd3fedb3a 100644 --- a/drivers/usb/gadget/function/f_mass_storage.h +++ b/drivers/usb/gadget/function/f_mass_storage.h @@ -110,7 +110,7 @@ struct fsg_config { }; static inline struct fsg_opts * -fsg_opts_from_func_inst(const struct usb_function_instance *fi) +fsg_opts_from_func_inst(struct usb_function_instance *fi) { return container_of(fi, struct fsg_opts, func_inst); } diff --git a/drivers/usb/gadget/function/f_midi.c b/drivers/usb/gadget/function/f_midi.c index fddf539008a9..da82598fcef8 100644 --- a/drivers/usb/gadget/function/f_midi.c +++ b/drivers/usb/gadget/function/f_midi.c @@ -30,14 +30,15 @@ #include <sound/rawmidi.h> #include <linux/usb/ch9.h> +#include <linux/usb/func_utils.h> #include <linux/usb/gadget.h> #include <linux/usb/audio.h> #include <linux/usb/midi.h> -#include "u_f.h" #include "u_midi.h" MODULE_AUTHOR("Ben Williamson"); +MODULE_DESCRIPTION("USB MIDI class function driver"); MODULE_LICENSE("GPL v2"); static const char f_midi_shortname[] = "f_midi"; @@ -99,7 +100,7 @@ struct f_midi { unsigned int in_last_port; unsigned char free_ref; - struct gmidi_in_port in_ports_array[/* in_ports */]; + struct gmidi_in_port in_ports_array[] __counted_by(in_ports); }; static inline struct f_midi *func_to_midi(struct usb_function *f) @@ -282,7 +283,7 @@ f_midi_complete(struct usb_ep *ep, struct usb_request *req) /* Our transmit completed. See if there's more to go. * f_midi_transmit eats req, don't queue it again. */ req->length = 0; - f_midi_transmit(midi); + queue_work(system_highpri_wq, &midi->work); return; } break; @@ -818,9 +819,9 @@ static int f_midi_register_card(struct f_midi *midi) goto fail; } - strcpy(card->driver, f_midi_longname); - strcpy(card->longname, f_midi_longname); - strcpy(card->shortname, f_midi_shortname); + strscpy(card->driver, f_midi_longname); + strscpy(card->longname, f_midi_longname); + strscpy(card->shortname, f_midi_shortname); /* Set up rawmidi */ snd_component_add(card, "MIDI"); @@ -832,7 +833,7 @@ static int f_midi_register_card(struct f_midi *midi) } midi->rmidi = rmidi; midi->in_last_port = 0; - strcpy(rmidi->name, card->shortname); + strscpy(rmidi->name, card->shortname); rmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT | SNDRV_RAWMIDI_INFO_DUPLEX; @@ -906,6 +907,15 @@ static int f_midi_bind(struct usb_configuration *c, struct usb_function *f) status = -ENODEV; + /* + * Reset wMaxPacketSize with maximum packet size of FS bulk transfer before + * endpoint claim. This ensures that the wMaxPacketSize does not exceed the + * limit during bind retries where configured dwc3 TX/RX FIFO's maxpacket + * size of 512 bytes for IN/OUT endpoints in support HS speed only. + */ + bulk_in_desc.wMaxPacketSize = cpu_to_le16(64); + bulk_out_desc.wMaxPacketSize = cpu_to_le16(64); + /* allocate instance-specific endpoints */ midi->in_ep = usb_ep_autoconfig(cdev->gadget, &bulk_in_desc); if (!midi->in_ep) @@ -999,11 +1009,11 @@ static int f_midi_bind(struct usb_configuration *c, struct usb_function *f) } /* configure the endpoint descriptors ... */ - ms_out_desc.bLength = USB_DT_MS_ENDPOINT_SIZE(midi->in_ports); - ms_out_desc.bNumEmbMIDIJack = midi->in_ports; + ms_out_desc.bLength = USB_DT_MS_ENDPOINT_SIZE(midi->out_ports); + ms_out_desc.bNumEmbMIDIJack = midi->out_ports; - ms_in_desc.bLength = USB_DT_MS_ENDPOINT_SIZE(midi->out_ports); - ms_in_desc.bNumEmbMIDIJack = midi->out_ports; + ms_in_desc.bLength = USB_DT_MS_ENDPOINT_SIZE(midi->in_ports); + ms_in_desc.bNumEmbMIDIJack = midi->in_ports; /* ... and add them to the list */ endpoint_descriptor_index = i; @@ -1023,40 +1033,30 @@ static int f_midi_bind(struct usb_configuration *c, struct usb_function *f) if (!f->fs_descriptors) goto fail_f_midi; - if (gadget_is_dualspeed(c->cdev->gadget)) { - bulk_in_desc.wMaxPacketSize = cpu_to_le16(512); - bulk_out_desc.wMaxPacketSize = cpu_to_le16(512); - f->hs_descriptors = usb_copy_descriptors(midi_function); - if (!f->hs_descriptors) - goto fail_f_midi; - } + bulk_in_desc.wMaxPacketSize = cpu_to_le16(512); + bulk_out_desc.wMaxPacketSize = cpu_to_le16(512); + f->hs_descriptors = usb_copy_descriptors(midi_function); + if (!f->hs_descriptors) + goto fail_f_midi; - if (gadget_is_superspeed(c->cdev->gadget)) { - bulk_in_desc.wMaxPacketSize = cpu_to_le16(1024); - bulk_out_desc.wMaxPacketSize = cpu_to_le16(1024); - i = endpoint_descriptor_index; - midi_function[i++] = (struct usb_descriptor_header *) - &bulk_out_desc; - midi_function[i++] = (struct usb_descriptor_header *) - &bulk_out_ss_comp_desc; - midi_function[i++] = (struct usb_descriptor_header *) - &ms_out_desc; - midi_function[i++] = (struct usb_descriptor_header *) - &bulk_in_desc; - midi_function[i++] = (struct usb_descriptor_header *) - &bulk_in_ss_comp_desc; - midi_function[i++] = (struct usb_descriptor_header *) - &ms_in_desc; - f->ss_descriptors = usb_copy_descriptors(midi_function); - if (!f->ss_descriptors) - goto fail_f_midi; - - if (gadget_is_superspeed_plus(c->cdev->gadget)) { - f->ssp_descriptors = usb_copy_descriptors(midi_function); - if (!f->ssp_descriptors) - goto fail_f_midi; - } - } + bulk_in_desc.wMaxPacketSize = cpu_to_le16(1024); + bulk_out_desc.wMaxPacketSize = cpu_to_le16(1024); + i = endpoint_descriptor_index; + midi_function[i++] = (struct usb_descriptor_header *) + &bulk_out_desc; + midi_function[i++] = (struct usb_descriptor_header *) + &bulk_out_ss_comp_desc; + midi_function[i++] = (struct usb_descriptor_header *) + &ms_out_desc; + midi_function[i++] = (struct usb_descriptor_header *) + &bulk_in_desc; + midi_function[i++] = (struct usb_descriptor_header *) + &bulk_in_ss_comp_desc; + midi_function[i++] = (struct usb_descriptor_header *) + &ms_in_desc; + f->ss_descriptors = usb_copy_descriptors(midi_function); + if (!f->ss_descriptors) + goto fail_f_midi; kfree(midi_function); @@ -1187,11 +1187,11 @@ F_MIDI_OPT(out_ports, true, MAX_PORTS); static ssize_t f_midi_opts_id_show(struct config_item *item, char *page) { struct f_midi_opts *opts = to_f_midi_opts(item); - int result; + ssize_t result; mutex_lock(&opts->lock); if (opts->id) { - result = strlcpy(page, opts->id, PAGE_SIZE); + result = strscpy(page, opts->id, PAGE_SIZE); } else { page[0] = 0; result = 0; @@ -1359,6 +1359,7 @@ static struct usb_function *f_midi_alloc(struct usb_function_instance *fi) status = -ENOMEM; goto setup_fail; } + midi->in_ports = opts->in_ports; for (i = 0; i < opts->in_ports; i++) midi->in_ports_array[i].cable = i; @@ -1369,7 +1370,6 @@ static struct usb_function *f_midi_alloc(struct usb_function_instance *fi) status = -ENOMEM; goto midi_free; } - midi->in_ports = opts->in_ports; midi->out_ports = opts->out_ports; midi->index = opts->index; midi->buflen = opts->buflen; diff --git a/drivers/usb/gadget/function/f_midi2.c b/drivers/usb/gadget/function/f_midi2.c new file mode 100644 index 000000000000..de16b02d857e --- /dev/null +++ b/drivers/usb/gadget/function/f_midi2.c @@ -0,0 +1,2893 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * f_midi2.c -- USB MIDI 2.0 class function driver + */ + +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> + +#include <sound/core.h> +#include <sound/control.h> +#include <sound/ump.h> +#include <sound/ump_msg.h> +#include <sound/ump_convert.h> + +#include <linux/usb/ch9.h> +#include <linux/usb/func_utils.h> +#include <linux/usb/gadget.h> +#include <linux/usb/audio.h> +#include <linux/usb/midi-v2.h> + +#include "u_midi2.h" + +struct f_midi2; +struct f_midi2_ep; +struct f_midi2_usb_ep; + +/* Context for each USB request */ +struct f_midi2_req_ctx { + struct f_midi2_usb_ep *usb_ep; /* belonging USB EP */ + unsigned int index; /* array index: 0-31 */ + struct usb_request *req; /* assigned request */ +}; + +/* Resources for a USB Endpoint */ +struct f_midi2_usb_ep { + struct f_midi2 *card; /* belonging card */ + struct f_midi2_ep *ep; /* belonging UMP EP (optional) */ + struct usb_ep *usb_ep; /* assigned USB EP */ + void (*complete)(struct usb_ep *usb_ep, struct usb_request *req); + unsigned long free_reqs; /* bitmap for unused requests */ + unsigned int num_reqs; /* number of allocated requests */ + struct f_midi2_req_ctx *reqs; /* request context array */ +}; + +/* Resources for UMP Function Block (and USB Group Terminal Block) */ +struct f_midi2_block { + struct f_midi2_block_info info; /* FB info, copied from configfs */ + struct snd_ump_block *fb; /* assigned FB */ + unsigned int gtb_id; /* assigned GTB id */ + unsigned int string_id; /* assigned string id */ +}; + +/* Temporary buffer for altset 0 MIDI 1.0 handling */ +struct f_midi2_midi1_port { + unsigned int pending; /* pending bytes on the input buffer */ + u8 buf[32]; /* raw MIDI 1.0 byte input */ + u8 state; /* running status */ + u8 data[2]; /* rendered USB MIDI 1.0 packet data */ +}; + +/* MIDI 1.0 message states */ +enum { + STATE_INITIAL = 0, /* pseudo state */ + STATE_1PARAM, + STATE_2PARAM_1, + STATE_2PARAM_2, + STATE_SYSEX_0, + STATE_SYSEX_1, + STATE_SYSEX_2, + STATE_REAL_TIME, + STATE_FINISHED, /* pseudo state */ +}; + +/* Resources for UMP Endpoint */ +struct f_midi2_ep { + struct snd_ump_endpoint *ump; /* assigned UMP EP */ + struct f_midi2 *card; /* belonging MIDI 2.0 device */ + + struct f_midi2_ep_info info; /* UMP EP info, copied from configfs */ + unsigned int num_blks; /* number of FBs */ + struct f_midi2_block blks[SNDRV_UMP_MAX_BLOCKS]; /* UMP FBs */ + + struct f_midi2_usb_ep ep_in; /* USB MIDI EP-in */ + struct f_midi2_usb_ep ep_out; /* USB MIDI EP-out */ + + u8 in_group_to_cable[SNDRV_UMP_MAX_GROUPS]; /* map to cable; 1-based! */ +}; + +/* indices for USB strings */ +enum { + STR_IFACE = 0, + STR_GTB1 = 1, +}; + +/* 1-based GTB id to string id */ +#define gtb_to_str_id(id) (STR_GTB1 + (id) - 1) + +/* mapping from MIDI 1.0 cable to UMP group */ +struct midi1_cable_mapping { + struct f_midi2_ep *ep; + unsigned char block; + unsigned char group; +}; + +/* operation mode */ +enum { + MIDI_OP_MODE_UNSET, /* no altset set yet */ + MIDI_OP_MODE_MIDI1, /* MIDI 1.0 (altset 0) is used */ + MIDI_OP_MODE_MIDI2, /* MIDI 2.0 (altset 1) is used */ +}; + +/* Resources for MIDI 2.0 Device */ +struct f_midi2 { + struct usb_function func; + struct usb_gadget *gadget; + struct snd_card *card; + + /* MIDI 1.0 in/out USB EPs */ + struct f_midi2_usb_ep midi1_ep_in; + struct f_midi2_usb_ep midi1_ep_out; + + /* number of MIDI 1.0 I/O cables */ + unsigned int num_midi1_in; + unsigned int num_midi1_out; + + /* conversion for MIDI 1.0 EP-in */ + struct f_midi2_midi1_port midi1_port[MAX_CABLES]; + /* conversion for MIDI 1.0 EP-out */ + struct ump_cvt_to_ump midi1_ump_cvt; + /* mapping between cables and UMP groups */ + struct midi1_cable_mapping in_cable_mapping[MAX_CABLES]; + struct midi1_cable_mapping out_cable_mapping[MAX_CABLES]; + + int midi_if; /* USB MIDI interface number */ + int operation_mode; /* current operation mode */ + + spinlock_t queue_lock; + + struct f_midi2_card_info info; /* card info, copied from configfs */ + + unsigned int num_eps; + struct f_midi2_ep midi2_eps[MAX_UMP_EPS]; + + unsigned int total_blocks; /* total number of blocks of all EPs */ + struct usb_string *string_defs; + struct usb_string *strings; +}; + +#define func_to_midi2(f) container_of(f, struct f_midi2, func) + +/* convert from MIDI protocol number (1 or 2) to SNDRV_UMP_EP_INFO_PROTO_* */ +#define to_ump_protocol(v) (((v) & 3) << 8) + +/* get EP name string */ +static const char *ump_ep_name(const struct f_midi2_ep *ep) +{ + return ep->info.ep_name ? ep->info.ep_name : "MIDI 2.0 Gadget"; +} + +/* get EP product ID string */ +static const char *ump_product_id(const struct f_midi2_ep *ep) +{ + return ep->info.product_id ? ep->info.product_id : "Unique Product ID"; +} + +/* get FB name string */ +static const char *ump_fb_name(const struct f_midi2_block_info *info) +{ + return info->name ? info->name : "MIDI 2.0 Gadget I/O"; +} + +/* + * USB Descriptor Definitions + */ +/* GTB header descriptor */ +static struct usb_ms20_gr_trm_block_header_descriptor gtb_header_desc = { + .bLength = sizeof(gtb_header_desc), + .bDescriptorType = USB_DT_CS_GR_TRM_BLOCK, + .bDescriptorSubtype = USB_MS_GR_TRM_BLOCK_HEADER, + .wTotalLength = __cpu_to_le16(0x12), // to be filled +}; + +/* GTB descriptor template: most items are replaced dynamically */ +static struct usb_ms20_gr_trm_block_descriptor gtb_desc = { + .bLength = sizeof(gtb_desc), + .bDescriptorType = USB_DT_CS_GR_TRM_BLOCK, + .bDescriptorSubtype = USB_MS_GR_TRM_BLOCK, + .bGrpTrmBlkID = 0x01, + .bGrpTrmBlkType = USB_MS_GR_TRM_BLOCK_TYPE_BIDIRECTIONAL, + .nGroupTrm = 0x00, + .nNumGroupTrm = 1, + .iBlockItem = 0, + .bMIDIProtocol = USB_MS_MIDI_PROTO_1_0_64, + .wMaxInputBandwidth = 0, + .wMaxOutputBandwidth = 0, +}; + +DECLARE_USB_MIDI_OUT_JACK_DESCRIPTOR(1); +DECLARE_USB_MS_ENDPOINT_DESCRIPTOR(16); +DECLARE_UAC_AC_HEADER_DESCRIPTOR(1); +DECLARE_USB_MS20_ENDPOINT_DESCRIPTOR(32); + +#define EP_MAX_PACKET_INT 8 + +/* Audio Control Interface */ +static struct usb_interface_descriptor midi2_audio_if_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, // to be filled + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, + .bInterfaceProtocol = 0, + .iInterface = 0, +}; + +static struct uac1_ac_header_descriptor_1 midi2_audio_class_desc = { + .bLength = 0x09, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = 0x01, + .bcdADC = __cpu_to_le16(0x0100), + .wTotalLength = __cpu_to_le16(0x0009), + .bInCollection = 0x01, + .baInterfaceNr = { 0x01 }, // to be filled +}; + +/* MIDI 1.0 Streaming Interface (altset 0) */ +static struct usb_interface_descriptor midi2_midi1_if_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, // to be filled + .bAlternateSetting = 0, + .bNumEndpoints = 2, // to be filled + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_MIDISTREAMING, + .bInterfaceProtocol = 0, + .iInterface = 0, // to be filled +}; + +static struct usb_ms_header_descriptor midi2_midi1_class_desc = { + .bLength = 0x07, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = USB_MS_HEADER, + .bcdMSC = __cpu_to_le16(0x0100), + .wTotalLength = __cpu_to_le16(0x41), // to be calculated +}; + +/* MIDI 1.0 EP OUT */ +static struct usb_endpoint_descriptor midi2_midi1_ep_out_desc = { + .bLength = USB_DT_ENDPOINT_AUDIO_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT | 0, // set up dynamically + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_ss_ep_comp_descriptor midi2_midi1_ep_out_ss_comp_desc = { + .bLength = sizeof(midi2_midi1_ep_out_ss_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, +}; + +static struct usb_ms_endpoint_descriptor_16 midi2_midi1_ep_out_class_desc = { + .bLength = 0x05, // to be filled + .bDescriptorType = USB_DT_CS_ENDPOINT, + .bDescriptorSubtype = USB_MS_GENERAL, + .bNumEmbMIDIJack = 1, + .baAssocJackID = { 0x01 }, +}; + +/* MIDI 1.0 EP IN */ +static struct usb_endpoint_descriptor midi2_midi1_ep_in_desc = { + .bLength = USB_DT_ENDPOINT_AUDIO_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN | 0, // set up dynamically + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_ss_ep_comp_descriptor midi2_midi1_ep_in_ss_comp_desc = { + .bLength = sizeof(midi2_midi1_ep_in_ss_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, +}; + +static struct usb_ms_endpoint_descriptor_16 midi2_midi1_ep_in_class_desc = { + .bLength = 0x05, // to be filled + .bDescriptorType = USB_DT_CS_ENDPOINT, + .bDescriptorSubtype = USB_MS_GENERAL, + .bNumEmbMIDIJack = 1, + .baAssocJackID = { 0x03 }, +}; + +/* MIDI 2.0 Streaming Interface (altset 1) */ +static struct usb_interface_descriptor midi2_midi2_if_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, // to be filled + .bAlternateSetting = 1, + .bNumEndpoints = 2, // to be filled + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_MIDISTREAMING, + .bInterfaceProtocol = 0, + .iInterface = 0, // to be filled +}; + +static struct usb_ms_header_descriptor midi2_midi2_class_desc = { + .bLength = 0x07, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = USB_MS_HEADER, + .bcdMSC = __cpu_to_le16(0x0200), + .wTotalLength = __cpu_to_le16(0x07), +}; + +/* MIDI 2.0 EP OUT */ +static struct usb_endpoint_descriptor midi2_midi2_ep_out_desc[MAX_UMP_EPS]; + +static struct usb_ss_ep_comp_descriptor midi2_midi2_ep_out_ss_comp_desc = { + .bLength = sizeof(midi2_midi1_ep_out_ss_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, +}; + +static struct usb_ms20_endpoint_descriptor_32 midi2_midi2_ep_out_class_desc[MAX_UMP_EPS]; + +/* MIDI 2.0 EP IN */ +static struct usb_endpoint_descriptor midi2_midi2_ep_in_desc[MAX_UMP_EPS]; + +static struct usb_ss_ep_comp_descriptor midi2_midi2_ep_in_ss_comp_desc = { + .bLength = sizeof(midi2_midi2_ep_in_ss_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, +}; + +static struct usb_ms20_endpoint_descriptor_32 midi2_midi2_ep_in_class_desc[MAX_UMP_EPS]; + +/* Arrays of descriptors to be created */ +static void *midi2_audio_descs[] = { + &midi2_audio_if_desc, + &midi2_audio_class_desc, + NULL +}; + +static void *midi2_midi1_descs[] = { + &midi2_midi1_if_desc, + &midi2_midi1_class_desc, + NULL +}; + +static void *midi2_midi1_ep_out_descs[] = { + &midi2_midi1_ep_out_desc, + &midi2_midi1_ep_out_class_desc, + NULL +}; + +static void *midi2_midi1_ep_in_descs[] = { + &midi2_midi1_ep_in_desc, + &midi2_midi1_ep_in_class_desc, + NULL +}; + +static void *midi2_midi1_ep_out_ss_descs[] = { + &midi2_midi1_ep_out_desc, + &midi2_midi1_ep_out_ss_comp_desc, + &midi2_midi1_ep_out_class_desc, + NULL +}; + +static void *midi2_midi1_ep_in_ss_descs[] = { + &midi2_midi1_ep_in_desc, + &midi2_midi1_ep_in_ss_comp_desc, + &midi2_midi1_ep_in_class_desc, + NULL +}; + +static void *midi2_midi2_descs[] = { + &midi2_midi2_if_desc, + &midi2_midi2_class_desc, + NULL +}; + +/* + * USB request handling + */ + +/* get an empty request for the given EP */ +static struct usb_request *get_empty_request(struct f_midi2_usb_ep *usb_ep) +{ + struct usb_request *req = NULL; + unsigned long flags; + int index; + + spin_lock_irqsave(&usb_ep->card->queue_lock, flags); + if (!usb_ep->free_reqs) + goto unlock; + index = find_first_bit(&usb_ep->free_reqs, usb_ep->num_reqs); + if (index >= usb_ep->num_reqs) + goto unlock; + req = usb_ep->reqs[index].req; + if (!req) + goto unlock; + clear_bit(index, &usb_ep->free_reqs); + req->length = 0; + unlock: + spin_unlock_irqrestore(&usb_ep->card->queue_lock, flags); + return req; +} + +/* put the empty request back */ +static void put_empty_request(struct usb_request *req) +{ + struct f_midi2_req_ctx *ctx = req->context; + unsigned long flags; + + spin_lock_irqsave(&ctx->usb_ep->card->queue_lock, flags); + set_bit(ctx->index, &ctx->usb_ep->free_reqs); + spin_unlock_irqrestore(&ctx->usb_ep->card->queue_lock, flags); +} + +/* + * UMP v1.1 Stream message handling + */ + +/* queue a request to UMP EP; request is either queued or freed after this */ +static int queue_request_ep_raw(struct usb_request *req) +{ + struct f_midi2_req_ctx *ctx = req->context; + int err; + + req->complete = ctx->usb_ep->complete; + err = usb_ep_queue(ctx->usb_ep->usb_ep, req, GFP_ATOMIC); + if (err) { + put_empty_request(req); + return err; + } + return 0; +} + +/* queue a request with endianness conversion */ +static int queue_request_ep_in(struct usb_request *req) +{ + /* UMP packets have to be converted to little-endian */ + cpu_to_le32_array((u32 *)req->buf, req->length >> 2); + return queue_request_ep_raw(req); +} + +/* reply a UMP packet via EP-in */ +static int reply_ep_in(struct f_midi2_ep *ep, const void *buf, int len) +{ + struct f_midi2_usb_ep *usb_ep = &ep->ep_in; + struct usb_request *req; + + req = get_empty_request(usb_ep); + if (!req) + return -ENOSPC; + + req->length = len; + memcpy(req->buf, buf, len); + return queue_request_ep_in(req); +} + +/* reply a UMP stream EP info */ +static void reply_ump_stream_ep_info(struct f_midi2_ep *ep) +{ + struct snd_ump_stream_msg_ep_info rep = { + .type = UMP_MSG_TYPE_STREAM, + .status = UMP_STREAM_MSG_STATUS_EP_INFO, + .ump_version_major = 0x01, + .ump_version_minor = 0x01, + .num_function_blocks = ep->num_blks, + .static_function_block = !!ep->card->info.static_block, + .protocol = (UMP_STREAM_MSG_EP_INFO_CAP_MIDI1 | + UMP_STREAM_MSG_EP_INFO_CAP_MIDI2) >> 8, + }; + + reply_ep_in(ep, &rep, sizeof(rep)); +} + +/* reply a UMP EP device info */ +static void reply_ump_stream_ep_device(struct f_midi2_ep *ep) +{ + struct snd_ump_stream_msg_device_info rep = { + .type = UMP_MSG_TYPE_STREAM, + .status = UMP_STREAM_MSG_STATUS_DEVICE_INFO, + .manufacture_id = ep->info.manufacturer, + .family_lsb = ep->info.family & 0xff, + .family_msb = (ep->info.family >> 8) & 0xff, + .model_lsb = ep->info.model & 0xff, + .model_msb = (ep->info.model >> 8) & 0xff, + .sw_revision = ep->info.sw_revision, + }; + + reply_ep_in(ep, &rep, sizeof(rep)); +} + +#define UMP_STREAM_PKT_BYTES 16 /* UMP stream packet size = 16 bytes*/ +#define UMP_STREAM_EP_STR_OFF 2 /* offset of name string for EP info */ +#define UMP_STREAM_FB_STR_OFF 3 /* offset of name string for FB info */ + +/* Helper to replay a string */ +static void reply_ump_stream_string(struct f_midi2_ep *ep, const u8 *name, + unsigned int type, unsigned int extra, + unsigned int start_ofs) +{ + struct f_midi2_usb_ep *usb_ep = &ep->ep_in; + struct f_midi2 *midi2 = ep->card; + struct usb_request *req; + unsigned int pos; + u32 *buf; + + if (!*name) + return; + req = get_empty_request(usb_ep); + if (!req) + return; + + buf = (u32 *)req->buf; + pos = start_ofs; + for (;;) { + if (pos == start_ofs) { + memset(buf, 0, UMP_STREAM_PKT_BYTES); + buf[0] = ump_stream_compose(type, 0) | extra; + } + buf[pos / 4] |= *name++ << ((3 - (pos % 4)) * 8); + if (!*name) { + if (req->length) + buf[0] |= UMP_STREAM_MSG_FORMAT_END << 26; + req->length += UMP_STREAM_PKT_BYTES; + break; + } + if (++pos == UMP_STREAM_PKT_BYTES) { + if (!req->length) + buf[0] |= UMP_STREAM_MSG_FORMAT_START << 26; + else + buf[0] |= UMP_STREAM_MSG_FORMAT_CONTINUE << 26; + req->length += UMP_STREAM_PKT_BYTES; + if (midi2->info.req_buf_size - req->length < UMP_STREAM_PKT_BYTES) + break; + buf += 4; + pos = start_ofs; + } + } + + if (req->length) + queue_request_ep_in(req); + else + put_empty_request(req); +} + +/* Reply a UMP EP name string */ +static void reply_ump_stream_ep_name(struct f_midi2_ep *ep) +{ + reply_ump_stream_string(ep, ump_ep_name(ep), + UMP_STREAM_MSG_STATUS_EP_NAME, 0, + UMP_STREAM_EP_STR_OFF); +} + +/* Reply a UMP EP product ID string */ +static void reply_ump_stream_ep_pid(struct f_midi2_ep *ep) +{ + reply_ump_stream_string(ep, ump_product_id(ep), + UMP_STREAM_MSG_STATUS_PRODUCT_ID, 0, + UMP_STREAM_EP_STR_OFF); +} + +/* Reply a UMP EP stream config */ +static void reply_ump_stream_ep_config(struct f_midi2_ep *ep) +{ + struct snd_ump_stream_msg_stream_cfg rep = { + .type = UMP_MSG_TYPE_STREAM, + .status = UMP_STREAM_MSG_STATUS_STREAM_CFG, + }; + + if (ep->info.protocol == 2) + rep.protocol = UMP_STREAM_MSG_EP_INFO_CAP_MIDI2 >> 8; + else + rep.protocol = UMP_STREAM_MSG_EP_INFO_CAP_MIDI1 >> 8; + + reply_ep_in(ep, &rep, sizeof(rep)); +} + +/* Reply a UMP FB info */ +static void reply_ump_stream_fb_info(struct f_midi2_ep *ep, int blk) +{ + struct f_midi2_block_info *b = &ep->blks[blk].info; + struct snd_ump_stream_msg_fb_info rep = { + .type = UMP_MSG_TYPE_STREAM, + .status = UMP_STREAM_MSG_STATUS_FB_INFO, + .active = !!b->active, + .function_block_id = blk, + .ui_hint = b->ui_hint, + .midi_10 = b->is_midi1, + .direction = b->direction, + .first_group = b->first_group, + .num_groups = b->num_groups, + .midi_ci_version = b->midi_ci_version, + .sysex8_streams = b->sysex8_streams, + }; + + reply_ep_in(ep, &rep, sizeof(rep)); +} + +/* Reply a FB name string */ +static void reply_ump_stream_fb_name(struct f_midi2_ep *ep, unsigned int blk) +{ + reply_ump_stream_string(ep, ump_fb_name(&ep->blks[blk].info), + UMP_STREAM_MSG_STATUS_FB_NAME, blk << 8, + UMP_STREAM_FB_STR_OFF); +} + +/* Process a UMP Stream message */ +static void process_ump_stream_msg(struct f_midi2_ep *ep, const u32 *data) +{ + struct f_midi2 *midi2 = ep->card; + unsigned int format, status, blk; + + format = ump_stream_message_format(*data); + status = ump_stream_message_status(*data); + switch (status) { + case UMP_STREAM_MSG_STATUS_EP_DISCOVERY: + if (format) + return; // invalid + if (data[1] & UMP_STREAM_MSG_REQUEST_EP_INFO) + reply_ump_stream_ep_info(ep); + if (data[1] & UMP_STREAM_MSG_REQUEST_DEVICE_INFO) + reply_ump_stream_ep_device(ep); + if (data[1] & UMP_STREAM_MSG_REQUEST_EP_NAME) + reply_ump_stream_ep_name(ep); + if (data[1] & UMP_STREAM_MSG_REQUEST_PRODUCT_ID) + reply_ump_stream_ep_pid(ep); + if (data[1] & UMP_STREAM_MSG_REQUEST_STREAM_CFG) + reply_ump_stream_ep_config(ep); + return; + case UMP_STREAM_MSG_STATUS_STREAM_CFG_REQUEST: + if (*data & UMP_STREAM_MSG_EP_INFO_CAP_MIDI2) { + ep->info.protocol = 2; + DBG(midi2, "Switching Protocol to MIDI2\n"); + } else { + ep->info.protocol = 1; + DBG(midi2, "Switching Protocol to MIDI1\n"); + } + snd_ump_switch_protocol(ep->ump, to_ump_protocol(ep->info.protocol)); + reply_ump_stream_ep_config(ep); + return; + case UMP_STREAM_MSG_STATUS_FB_DISCOVERY: + if (format) + return; // invalid + blk = (*data >> 8) & 0xff; + if (blk == 0xff) { + /* inquiry for all blocks */ + for (blk = 0; blk < ep->num_blks; blk++) { + if (*data & UMP_STREAM_MSG_REQUEST_FB_INFO) + reply_ump_stream_fb_info(ep, blk); + if (*data & UMP_STREAM_MSG_REQUEST_FB_NAME) + reply_ump_stream_fb_name(ep, blk); + } + } else if (blk < ep->num_blks) { + /* only the specified block */ + if (*data & UMP_STREAM_MSG_REQUEST_FB_INFO) + reply_ump_stream_fb_info(ep, blk); + if (*data & UMP_STREAM_MSG_REQUEST_FB_NAME) + reply_ump_stream_fb_name(ep, blk); + } + return; + } +} + +/* Process UMP messages included in a USB request */ +static void process_ump(struct f_midi2_ep *ep, const struct usb_request *req) +{ + const u32 *data = (u32 *)req->buf; + int len = req->actual >> 2; + const u32 *in_buf = ep->ump->input_buf; + + for (; len > 0; len--, data++) { + if (snd_ump_receive_ump_val(ep->ump, *data) <= 0) + continue; + if (ump_message_type(*in_buf) == UMP_MSG_TYPE_STREAM) + process_ump_stream_msg(ep, in_buf); + } +} + +/* + * MIDI 2.0 UMP USB request handling + */ + +/* complete handler for UMP EP-out requests */ +static void f_midi2_ep_out_complete(struct usb_ep *usb_ep, + struct usb_request *req) +{ + struct f_midi2_req_ctx *ctx = req->context; + struct f_midi2_ep *ep = ctx->usb_ep->ep; + struct f_midi2 *midi2 = ep->card; + int status = req->status; + + if (status) { + DBG(midi2, "%s complete error %d: %d/%d\n", + usb_ep->name, status, req->actual, req->length); + goto error; + } + + /* convert to UMP packet in native endianness */ + le32_to_cpu_array((u32 *)req->buf, req->actual >> 2); + + if (midi2->info.process_ump) + process_ump(ep, req); + + snd_ump_receive(ep->ump, req->buf, req->actual & ~3); + + if (midi2->operation_mode != MIDI_OP_MODE_MIDI2) + goto error; + + if (queue_request_ep_raw(req)) + goto error; + return; + + error: + put_empty_request(req); +} + +/* Transmit UMP packets received from user-space to the gadget */ +static void process_ump_transmit(struct f_midi2_ep *ep) +{ + struct f_midi2_usb_ep *usb_ep = &ep->ep_in; + struct f_midi2 *midi2 = ep->card; + struct usb_request *req; + int len; + + if (!usb_ep->usb_ep->enabled) + return; + + for (;;) { + req = get_empty_request(usb_ep); + if (!req) + break; + len = snd_ump_transmit(ep->ump, (u32 *)req->buf, + midi2->info.req_buf_size); + if (len <= 0) { + put_empty_request(req); + break; + } + + req->length = len; + if (queue_request_ep_in(req) < 0) + break; + } +} + +/* Complete handler for UMP EP-in requests */ +static void f_midi2_ep_in_complete(struct usb_ep *usb_ep, + struct usb_request *req) +{ + struct f_midi2_req_ctx *ctx = req->context; + struct f_midi2_ep *ep = ctx->usb_ep->ep; + struct f_midi2 *midi2 = ep->card; + int status = req->status; + + put_empty_request(req); + + if (status) { + DBG(midi2, "%s complete error %d: %d/%d\n", + usb_ep->name, status, req->actual, req->length); + return; + } + + process_ump_transmit(ep); +} + +/* + * MIDI1 (altset 0) USB request handling + */ + +/* process one MIDI byte -- copied from f_midi.c + * + * fill the packet or request if needed + * returns true if the request became empty (queued) + */ +static bool process_midi1_byte(struct f_midi2 *midi2, u8 cable, u8 b, + struct usb_request **req_p) +{ + struct f_midi2_midi1_port *port = &midi2->midi1_port[cable]; + u8 p[4] = { cable << 4, 0, 0, 0 }; + int next_state = STATE_INITIAL; + struct usb_request *req = *req_p; + + switch (b) { + case 0xf8 ... 0xff: + /* System Real-Time Messages */ + p[0] |= 0x0f; + p[1] = b; + next_state = port->state; + port->state = STATE_REAL_TIME; + break; + + case 0xf7: + /* End of SysEx */ + switch (port->state) { + case STATE_SYSEX_0: + p[0] |= 0x05; + p[1] = 0xf7; + next_state = STATE_FINISHED; + break; + case STATE_SYSEX_1: + p[0] |= 0x06; + p[1] = port->data[0]; + p[2] = 0xf7; + next_state = STATE_FINISHED; + break; + case STATE_SYSEX_2: + p[0] |= 0x07; + p[1] = port->data[0]; + p[2] = port->data[1]; + p[3] = 0xf7; + next_state = STATE_FINISHED; + break; + default: + /* Ignore byte */ + next_state = port->state; + port->state = STATE_INITIAL; + } + break; + + case 0xf0 ... 0xf6: + /* System Common Messages */ + port->data[0] = port->data[1] = 0; + port->state = STATE_INITIAL; + switch (b) { + case 0xf0: + port->data[0] = b; + port->data[1] = 0; + next_state = STATE_SYSEX_1; + break; + case 0xf1: + case 0xf3: + port->data[0] = b; + next_state = STATE_1PARAM; + break; + case 0xf2: + port->data[0] = b; + next_state = STATE_2PARAM_1; + break; + case 0xf4: + case 0xf5: + next_state = STATE_INITIAL; + break; + case 0xf6: + p[0] |= 0x05; + p[1] = 0xf6; + next_state = STATE_FINISHED; + break; + } + break; + + case 0x80 ... 0xef: + /* + * Channel Voice Messages, Channel Mode Messages + * and Control Change Messages. + */ + port->data[0] = b; + port->data[1] = 0; + port->state = STATE_INITIAL; + if (b >= 0xc0 && b <= 0xdf) + next_state = STATE_1PARAM; + else + next_state = STATE_2PARAM_1; + break; + + case 0x00 ... 0x7f: + /* Message parameters */ + switch (port->state) { + case STATE_1PARAM: + if (port->data[0] < 0xf0) + p[0] |= port->data[0] >> 4; + else + p[0] |= 0x02; + + p[1] = port->data[0]; + p[2] = b; + /* This is to allow Running State Messages */ + next_state = STATE_1PARAM; + break; + case STATE_2PARAM_1: + port->data[1] = b; + next_state = STATE_2PARAM_2; + break; + case STATE_2PARAM_2: + if (port->data[0] < 0xf0) + p[0] |= port->data[0] >> 4; + else + p[0] |= 0x03; + + p[1] = port->data[0]; + p[2] = port->data[1]; + p[3] = b; + /* This is to allow Running State Messages */ + next_state = STATE_2PARAM_1; + break; + case STATE_SYSEX_0: + port->data[0] = b; + next_state = STATE_SYSEX_1; + break; + case STATE_SYSEX_1: + port->data[1] = b; + next_state = STATE_SYSEX_2; + break; + case STATE_SYSEX_2: + p[0] |= 0x04; + p[1] = port->data[0]; + p[2] = port->data[1]; + p[3] = b; + next_state = STATE_SYSEX_0; + break; + } + break; + } + + /* States where we have to write into the USB request */ + if (next_state == STATE_FINISHED || + port->state == STATE_SYSEX_2 || + port->state == STATE_1PARAM || + port->state == STATE_2PARAM_2 || + port->state == STATE_REAL_TIME) { + memcpy(req->buf + req->length, p, sizeof(p)); + req->length += sizeof(p); + + if (next_state == STATE_FINISHED) { + next_state = STATE_INITIAL; + port->data[0] = port->data[1] = 0; + } + + if (midi2->info.req_buf_size - req->length <= 4) { + queue_request_ep_raw(req); + *req_p = NULL; + return true; + } + } + + port->state = next_state; + return false; +} + +/* process all pending MIDI bytes in the internal buffer; + * returns true if the request gets empty + * returns false if all have been processed + */ +static bool process_midi1_pending_buf(struct f_midi2 *midi2, + struct usb_request **req_p) +{ + unsigned int cable, c; + + for (cable = 0; cable < midi2->num_midi1_in; cable++) { + struct f_midi2_midi1_port *port = &midi2->midi1_port[cable]; + + if (!port->pending) + continue; + for (c = 0; c < port->pending; c++) { + if (process_midi1_byte(midi2, cable, port->buf[c], + req_p)) { + port->pending -= c; + if (port->pending) + memmove(port->buf, port->buf + c, + port->pending); + return true; + } + } + port->pending = 0; + } + + return false; +} + +/* fill the MIDI bytes onto the temporary buffer + */ +static void fill_midi1_pending_buf(struct f_midi2 *midi2, u8 cable, u8 *buf, + unsigned int size) +{ + struct f_midi2_midi1_port *port = &midi2->midi1_port[cable]; + + if (port->pending + size > sizeof(port->buf)) + return; + memcpy(port->buf + port->pending, buf, size); + port->pending += size; +} + +/* try to process data given from the associated UMP stream */ +static void process_midi1_transmit(struct f_midi2 *midi2) +{ + struct f_midi2_usb_ep *usb_ep = &midi2->midi1_ep_in; + struct f_midi2_ep *ep = &midi2->midi2_eps[0]; + struct usb_request *req = NULL; + /* 12 is the largest outcome (4 MIDI1 cmds) for a single UMP packet */ + unsigned char outbuf[12]; + unsigned char group, cable; + int len, size; + u32 ump; + + if (!usb_ep->usb_ep || !usb_ep->usb_ep->enabled) + return; + + for (;;) { + if (!req) { + req = get_empty_request(usb_ep); + if (!req) + break; + } + + if (process_midi1_pending_buf(midi2, &req)) + continue; + + len = snd_ump_transmit(ep->ump, &ump, 4); + if (len <= 0) + break; + if (snd_ump_receive_ump_val(ep->ump, ump) <= 0) + continue; + size = snd_ump_convert_from_ump(ep->ump->input_buf, outbuf, + &group); + if (size <= 0) + continue; + cable = ep->in_group_to_cable[group]; + if (!cable) + continue; + cable--; /* to 0-base */ + fill_midi1_pending_buf(midi2, cable, outbuf, size); + } + + if (req) { + if (req->length) + queue_request_ep_raw(req); + else + put_empty_request(req); + } +} + +/* complete handler for MIDI1 EP-in requests */ +static void f_midi2_midi1_ep_in_complete(struct usb_ep *usb_ep, + struct usb_request *req) +{ + struct f_midi2_req_ctx *ctx = req->context; + struct f_midi2 *midi2 = ctx->usb_ep->card; + int status = req->status; + + put_empty_request(req); + + if (status) { + DBG(midi2, "%s complete error %d: %d/%d\n", + usb_ep->name, status, req->actual, req->length); + return; + } + + process_midi1_transmit(midi2); +} + +/* complete handler for MIDI1 EP-out requests */ +static void f_midi2_midi1_ep_out_complete(struct usb_ep *usb_ep, + struct usb_request *req) +{ + struct f_midi2_req_ctx *ctx = req->context; + struct f_midi2 *midi2 = ctx->usb_ep->card; + struct f_midi2_ep *ep; + struct ump_cvt_to_ump *cvt = &midi2->midi1_ump_cvt; + static const u8 midi1_packet_bytes[16] = { + 0, 0, 2, 3, 3, 1, 2, 3, 3, 3, 3, 3, 2, 2, 3, 1 + }; + unsigned int group, cable, bytes, c, len; + int status = req->status; + const u8 *buf = req->buf; + + if (status) { + DBG(midi2, "%s complete error %d: %d/%d\n", + usb_ep->name, status, req->actual, req->length); + goto error; + } + + len = req->actual >> 2; + for (; len; len--, buf += 4) { + cable = *buf >> 4; + ep = midi2->out_cable_mapping[cable].ep; + if (!ep) + continue; + group = midi2->out_cable_mapping[cable].group; + bytes = midi1_packet_bytes[*buf & 0x0f]; + for (c = 0; c < bytes; c++) { + snd_ump_convert_to_ump(cvt, group, + to_ump_protocol(ep->info.protocol), + buf[c + 1]); + if (cvt->ump_bytes) { + snd_ump_receive(ep->ump, cvt->ump, + cvt->ump_bytes); + cvt->ump_bytes = 0; + } + } + } + + if (midi2->operation_mode != MIDI_OP_MODE_MIDI1) + goto error; + + if (queue_request_ep_raw(req)) + goto error; + return; + + error: + put_empty_request(req); +} + +/* + * Common EP handling helpers + */ + +/* Start MIDI EP */ +static int f_midi2_start_ep(struct f_midi2_usb_ep *usb_ep, + struct usb_function *fn) +{ + int err; + + if (!usb_ep->usb_ep) + return 0; + + usb_ep_disable(usb_ep->usb_ep); + err = config_ep_by_speed(usb_ep->card->gadget, fn, usb_ep->usb_ep); + if (err) + return err; + return usb_ep_enable(usb_ep->usb_ep); +} + +/* Drop pending requests */ +static void f_midi2_drop_reqs(struct f_midi2_usb_ep *usb_ep) +{ + int i; + + if (!usb_ep->usb_ep || !usb_ep->num_reqs) + return; + + for (i = 0; i < usb_ep->num_reqs; i++) { + if (!test_bit(i, &usb_ep->free_reqs) && usb_ep->reqs[i].req) { + usb_ep_dequeue(usb_ep->usb_ep, usb_ep->reqs[i].req); + set_bit(i, &usb_ep->free_reqs); + } + } +} + +/* Allocate requests for the given EP */ +static int f_midi2_alloc_ep_reqs(struct f_midi2_usb_ep *usb_ep) +{ + struct f_midi2 *midi2 = usb_ep->card; + int i; + + if (!usb_ep->usb_ep) + return 0; + if (!usb_ep->reqs) + return -EINVAL; + + for (i = 0; i < midi2->info.num_reqs; i++) { + if (usb_ep->reqs[i].req) + continue; + usb_ep->reqs[i].req = alloc_ep_req(usb_ep->usb_ep, + midi2->info.req_buf_size); + if (!usb_ep->reqs[i].req) + return -ENOMEM; + usb_ep->reqs[i].req->context = &usb_ep->reqs[i]; + } + return 0; +} + +/* Free allocated requests */ +static void f_midi2_free_ep_reqs(struct f_midi2_usb_ep *usb_ep) +{ + struct f_midi2 *midi2 = usb_ep->card; + int i; + + for (i = 0; i < midi2->info.num_reqs; i++) { + if (!usb_ep->reqs[i].req) + continue; + free_ep_req(usb_ep->usb_ep, usb_ep->reqs[i].req); + usb_ep->reqs[i].req = NULL; + } +} + +/* Initialize EP */ +static int f_midi2_init_ep(struct f_midi2 *midi2, struct f_midi2_ep *ep, + struct f_midi2_usb_ep *usb_ep, + void *desc, + void (*complete)(struct usb_ep *usb_ep, + struct usb_request *req)) +{ + int i; + + usb_ep->card = midi2; + usb_ep->ep = ep; + usb_ep->usb_ep = usb_ep_autoconfig(midi2->gadget, desc); + if (!usb_ep->usb_ep) + return -ENODEV; + usb_ep->complete = complete; + + usb_ep->reqs = kcalloc(midi2->info.num_reqs, sizeof(*usb_ep->reqs), + GFP_KERNEL); + if (!usb_ep->reqs) + return -ENOMEM; + for (i = 0; i < midi2->info.num_reqs; i++) { + usb_ep->reqs[i].index = i; + usb_ep->reqs[i].usb_ep = usb_ep; + set_bit(i, &usb_ep->free_reqs); + usb_ep->num_reqs++; + } + + return 0; +} + +/* Free EP */ +static void f_midi2_free_ep(struct f_midi2_usb_ep *usb_ep) +{ + f_midi2_drop_reqs(usb_ep); + + f_midi2_free_ep_reqs(usb_ep); + + kfree(usb_ep->reqs); + usb_ep->num_reqs = 0; + usb_ep->free_reqs = 0; + usb_ep->reqs = NULL; +} + +/* Queue requests for EP-out at start */ +static void f_midi2_queue_out_reqs(struct f_midi2_usb_ep *usb_ep) +{ + int i, err; + + if (!usb_ep->usb_ep) + return; + + for (i = 0; i < usb_ep->num_reqs; i++) { + if (!test_bit(i, &usb_ep->free_reqs) || !usb_ep->reqs[i].req) + continue; + usb_ep->reqs[i].req->complete = usb_ep->complete; + err = usb_ep_queue(usb_ep->usb_ep, usb_ep->reqs[i].req, + GFP_ATOMIC); + if (!err) + clear_bit(i, &usb_ep->free_reqs); + } +} + +/* + * Gadget Function callbacks + */ + +/* stop both IN and OUT EPs */ +static void f_midi2_stop_eps(struct f_midi2_usb_ep *ep_in, + struct f_midi2_usb_ep *ep_out) +{ + f_midi2_drop_reqs(ep_in); + f_midi2_drop_reqs(ep_out); + f_midi2_free_ep_reqs(ep_in); + f_midi2_free_ep_reqs(ep_out); +} + +/* start/queue both IN and OUT EPs */ +static int f_midi2_start_eps(struct f_midi2_usb_ep *ep_in, + struct f_midi2_usb_ep *ep_out, + struct usb_function *fn) +{ + int err; + + err = f_midi2_start_ep(ep_in, fn); + if (err) + return err; + err = f_midi2_start_ep(ep_out, fn); + if (err) + return err; + + err = f_midi2_alloc_ep_reqs(ep_in); + if (err) + return err; + err = f_midi2_alloc_ep_reqs(ep_out); + if (err) + return err; + + f_midi2_queue_out_reqs(ep_out); + return 0; +} + +/* gadget function set_alt callback */ +static int f_midi2_set_alt(struct usb_function *fn, unsigned int intf, + unsigned int alt) +{ + struct f_midi2 *midi2 = func_to_midi2(fn); + struct f_midi2_ep *ep; + int i, op_mode, err; + + if (intf != midi2->midi_if || alt > 1) + return 0; + + if (alt == 0) + op_mode = MIDI_OP_MODE_MIDI1; + else + op_mode = MIDI_OP_MODE_MIDI2; + + if (midi2->operation_mode == op_mode) + return 0; + + midi2->operation_mode = op_mode; + + if (op_mode != MIDI_OP_MODE_MIDI1) + f_midi2_stop_eps(&midi2->midi1_ep_in, &midi2->midi1_ep_out); + + if (op_mode != MIDI_OP_MODE_MIDI2) { + for (i = 0; i < midi2->num_eps; i++) { + ep = &midi2->midi2_eps[i]; + f_midi2_stop_eps(&ep->ep_in, &ep->ep_out); + } + } + + if (op_mode == MIDI_OP_MODE_MIDI1) + return f_midi2_start_eps(&midi2->midi1_ep_in, + &midi2->midi1_ep_out, fn); + + if (op_mode == MIDI_OP_MODE_MIDI2) { + for (i = 0; i < midi2->num_eps; i++) { + ep = &midi2->midi2_eps[i]; + + err = f_midi2_start_eps(&ep->ep_in, &ep->ep_out, fn); + if (err) + return err; + } + } + + return 0; +} + +/* gadget function get_alt callback */ +static int f_midi2_get_alt(struct usb_function *fn, unsigned int intf) +{ + struct f_midi2 *midi2 = func_to_midi2(fn); + + if (intf == midi2->midi_if && + midi2->operation_mode == MIDI_OP_MODE_MIDI2) + return 1; + return 0; +} + +/* convert UMP direction to USB MIDI 2.0 direction */ +static unsigned int ump_to_usb_dir(unsigned int ump_dir) +{ + switch (ump_dir) { + case SNDRV_UMP_DIR_INPUT: + return USB_MS_GR_TRM_BLOCK_TYPE_INPUT_ONLY; + case SNDRV_UMP_DIR_OUTPUT: + return USB_MS_GR_TRM_BLOCK_TYPE_OUTPUT_ONLY; + default: + return USB_MS_GR_TRM_BLOCK_TYPE_BIDIRECTIONAL; + } +} + +/* assign GTB descriptors (for the given request) */ +static void assign_block_descriptors(struct f_midi2 *midi2, + struct usb_request *req, + int max_len) +{ + struct usb_ms20_gr_trm_block_header_descriptor header; + struct usb_ms20_gr_trm_block_descriptor *desc; + struct f_midi2_block_info *b; + struct f_midi2_ep *ep; + int i, blk, len; + char *data; + + len = sizeof(gtb_header_desc) + sizeof(gtb_desc) * midi2->total_blocks; + if (WARN_ON(len > midi2->info.req_buf_size)) + return; + + header = gtb_header_desc; + header.wTotalLength = cpu_to_le16(len); + if (max_len < len) { + len = min_t(int, len, sizeof(header)); + memcpy(req->buf, &header, len); + req->length = len; + req->zero = len < max_len; + return; + } + + memcpy(req->buf, &header, sizeof(header)); + data = req->buf + sizeof(header); + for (i = 0; i < midi2->num_eps; i++) { + ep = &midi2->midi2_eps[i]; + for (blk = 0; blk < ep->num_blks; blk++) { + b = &ep->blks[blk].info; + desc = (struct usb_ms20_gr_trm_block_descriptor *)data; + + *desc = gtb_desc; + desc->bGrpTrmBlkID = ep->blks[blk].gtb_id; + desc->bGrpTrmBlkType = ump_to_usb_dir(b->direction); + desc->nGroupTrm = b->first_group; + desc->nNumGroupTrm = b->num_groups; + desc->iBlockItem = ep->blks[blk].string_id; + + if (ep->info.protocol == 2) + desc->bMIDIProtocol = USB_MS_MIDI_PROTO_2_0; + else + desc->bMIDIProtocol = USB_MS_MIDI_PROTO_1_0_128; + + if (b->is_midi1 == 2) { + desc->wMaxInputBandwidth = cpu_to_le16(1); + desc->wMaxOutputBandwidth = cpu_to_le16(1); + } + + data += sizeof(*desc); + } + } + + req->length = len; + req->zero = len < max_len; +} + +/* gadget function setup callback: handle GTB requests */ +static int f_midi2_setup(struct usb_function *fn, + const struct usb_ctrlrequest *ctrl) +{ + struct f_midi2 *midi2 = func_to_midi2(fn); + struct usb_composite_dev *cdev = fn->config->cdev; + struct usb_request *req = cdev->req; + u16 value, length; + + if ((ctrl->bRequestType & USB_TYPE_MASK) != USB_TYPE_STANDARD || + ctrl->bRequest != USB_REQ_GET_DESCRIPTOR) + return -EOPNOTSUPP; + + value = le16_to_cpu(ctrl->wValue); + length = le16_to_cpu(ctrl->wLength); + + if ((value >> 8) != USB_DT_CS_GR_TRM_BLOCK) + return -EOPNOTSUPP; + + /* handle only altset 1 */ + if ((value & 0xff) != 1) + return -EOPNOTSUPP; + + assign_block_descriptors(midi2, req, length); + return usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); +} + +/* gadget function disable callback */ +static void f_midi2_disable(struct usb_function *fn) +{ + struct f_midi2 *midi2 = func_to_midi2(fn); + + midi2->operation_mode = MIDI_OP_MODE_UNSET; +} + +/* + * ALSA UMP ops: most of them are NOPs, only trigger for write is needed + */ +static int f_midi2_ump_open(struct snd_ump_endpoint *ump, int dir) +{ + return 0; +} + +static void f_midi2_ump_close(struct snd_ump_endpoint *ump, int dir) +{ +} + +static void f_midi2_ump_trigger(struct snd_ump_endpoint *ump, int dir, int up) +{ + struct f_midi2_ep *ep = ump->private_data; + struct f_midi2 *midi2 = ep->card; + + if (up && dir == SNDRV_RAWMIDI_STREAM_OUTPUT) { + switch (midi2->operation_mode) { + case MIDI_OP_MODE_MIDI1: + process_midi1_transmit(midi2); + break; + case MIDI_OP_MODE_MIDI2: + process_ump_transmit(ep); + break; + } + } +} + +static void f_midi2_ump_drain(struct snd_ump_endpoint *ump, int dir) +{ +} + +static const struct snd_ump_ops f_midi2_ump_ops = { + .open = f_midi2_ump_open, + .close = f_midi2_ump_close, + .trigger = f_midi2_ump_trigger, + .drain = f_midi2_ump_drain, +}; + +/* + * "Operation Mode" control element + */ +static int f_midi2_operation_mode_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = MIDI_OP_MODE_UNSET; + uinfo->value.integer.max = MIDI_OP_MODE_MIDI2; + return 0; +} + +static int f_midi2_operation_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct f_midi2 *midi2 = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = midi2->operation_mode; + return 0; +} + +static const struct snd_kcontrol_new operation_mode_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_RAWMIDI, + .name = "Operation Mode", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = f_midi2_operation_mode_info, + .get = f_midi2_operation_mode_get, +}; + +/* + * ALSA UMP instance creation / deletion + */ +static void f_midi2_free_card(struct f_midi2 *midi2) +{ + if (midi2->card) { + snd_card_free_when_closed(midi2->card); + midi2->card = NULL; + } +} + +/* use a reverse direction for the gadget host */ +static int reverse_dir(int dir) +{ + if (!dir || dir == SNDRV_UMP_DIR_BIDIRECTION) + return dir; + return (dir == SNDRV_UMP_DIR_OUTPUT) ? + SNDRV_UMP_DIR_INPUT : SNDRV_UMP_DIR_OUTPUT; +} + +static int f_midi2_create_card(struct f_midi2 *midi2) +{ + struct snd_card *card; + struct snd_ump_endpoint *ump; + struct f_midi2_ep *ep; + int i, id, blk, err; + __be32 sw; + + err = snd_card_new(&midi2->gadget->dev, -1, NULL, THIS_MODULE, 0, + &card); + if (err < 0) + return err; + midi2->card = card; + + strcpy(card->driver, "f_midi2"); + strcpy(card->shortname, "MIDI 2.0 Gadget"); + strcpy(card->longname, "MIDI 2.0 Gadget"); + + id = 0; + for (i = 0; i < midi2->num_eps; i++) { + ep = &midi2->midi2_eps[i]; + err = snd_ump_endpoint_new(card, "MIDI 2.0 Gadget", id, + 1, 1, &ump); + if (err < 0) + goto error; + id++; + + ep->ump = ump; + ump->no_process_stream = true; + ump->private_data = ep; + ump->ops = &f_midi2_ump_ops; + if (midi2->info.static_block) + ump->info.flags |= SNDRV_UMP_EP_INFO_STATIC_BLOCKS; + ump->info.protocol_caps = (ep->info.protocol_caps & 3) << 8; + ump->info.protocol = to_ump_protocol(ep->info.protocol); + ump->info.version = 0x0101; + ump->info.family_id = ep->info.family; + ump->info.model_id = ep->info.model; + ump->info.manufacturer_id = ep->info.manufacturer & 0xffffff; + sw = cpu_to_be32(ep->info.sw_revision); + memcpy(ump->info.sw_revision, &sw, 4); + + strscpy(ump->info.name, ump_ep_name(ep), + sizeof(ump->info.name)); + strscpy(ump->info.product_id, ump_product_id(ep), + sizeof(ump->info.product_id)); + strscpy(ump->core.name, ump->info.name, sizeof(ump->core.name)); + + for (blk = 0; blk < ep->num_blks; blk++) { + const struct f_midi2_block_info *b = &ep->blks[blk].info; + struct snd_ump_block *fb; + + err = snd_ump_block_new(ump, blk, + reverse_dir(b->direction), + b->first_group, b->num_groups, + &ep->blks[blk].fb); + if (err < 0) + goto error; + fb = ep->blks[blk].fb; + fb->info.active = !!b->active; + fb->info.midi_ci_version = b->midi_ci_version; + fb->info.ui_hint = reverse_dir(b->ui_hint); + fb->info.sysex8_streams = b->sysex8_streams; + if (b->is_midi1 < 2) + fb->info.flags |= b->is_midi1; + else + fb->info.flags |= SNDRV_UMP_BLOCK_IS_MIDI1 | + SNDRV_UMP_BLOCK_IS_LOWSPEED; + strscpy(fb->info.name, ump_fb_name(b), + sizeof(fb->info.name)); + } + snd_ump_update_group_attrs(ump); + } + + for (i = 0; i < midi2->num_eps; i++) { + err = snd_ump_attach_legacy_rawmidi(midi2->midi2_eps[i].ump, + "Legacy MIDI", id); + if (err < 0) + goto error; + id++; + } + + err = snd_ctl_add(card, snd_ctl_new1(&operation_mode_ctl, midi2)); + if (err < 0) + goto error; + + err = snd_card_register(card); + if (err < 0) + goto error; + + return 0; + + error: + f_midi2_free_card(midi2); + return err; +} + +/* + * Creation of USB descriptors + */ +struct f_midi2_usb_config { + struct usb_descriptor_header **list; + unsigned int size; + unsigned int alloc; + + /* MIDI 1.0 jacks */ + unsigned char jack_in, jack_out, jack_id; + struct usb_midi_in_jack_descriptor jack_ins[MAX_CABLES]; + struct usb_midi_out_jack_descriptor_1 jack_outs[MAX_CABLES]; +}; + +static int append_config(struct f_midi2_usb_config *config, void *d) +{ + unsigned int size; + void *buf; + + if (config->size + 2 >= config->alloc) { + size = config->size + 16; + buf = krealloc(config->list, size * sizeof(void *), GFP_KERNEL); + if (!buf) + return -ENOMEM; + config->list = buf; + config->alloc = size; + } + + config->list[config->size] = d; + config->size++; + config->list[config->size] = NULL; + return 0; +} + +static int append_configs(struct f_midi2_usb_config *config, void **d) +{ + int err; + + for (; *d; d++) { + err = append_config(config, *d); + if (err) + return err; + } + return 0; +} + +static int append_midi1_in_jack(struct f_midi2 *midi2, + struct f_midi2_usb_config *config, + struct midi1_cable_mapping *map, + unsigned int type) +{ + struct usb_midi_in_jack_descriptor *jack = + &config->jack_ins[config->jack_in++]; + int id = ++config->jack_id; + int err; + + jack->bLength = 0x06; + jack->bDescriptorType = USB_DT_CS_INTERFACE; + jack->bDescriptorSubtype = USB_MS_MIDI_IN_JACK; + jack->bJackType = type; + jack->bJackID = id; + /* use the corresponding block name as jack name */ + if (map->ep) + jack->iJack = map->ep->blks[map->block].string_id; + + err = append_config(config, jack); + if (err < 0) + return err; + return id; +} + +static int append_midi1_out_jack(struct f_midi2 *midi2, + struct f_midi2_usb_config *config, + struct midi1_cable_mapping *map, + unsigned int type, unsigned int source) +{ + struct usb_midi_out_jack_descriptor_1 *jack = + &config->jack_outs[config->jack_out++]; + int id = ++config->jack_id; + int err; + + jack->bLength = 0x09; + jack->bDescriptorType = USB_DT_CS_INTERFACE; + jack->bDescriptorSubtype = USB_MS_MIDI_OUT_JACK; + jack->bJackType = type; + jack->bJackID = id; + jack->bNrInputPins = 1; + jack->pins[0].baSourceID = source; + jack->pins[0].baSourcePin = 0x01; + /* use the corresponding block name as jack name */ + if (map->ep) + jack->iJack = map->ep->blks[map->block].string_id; + + err = append_config(config, jack); + if (err < 0) + return err; + return id; +} + +static int f_midi2_create_usb_configs(struct f_midi2 *midi2, + struct f_midi2_usb_config *config, + int speed) +{ + void **midi1_in_eps, **midi1_out_eps; + int i, jack, total; + int err; + + switch (speed) { + default: + case USB_SPEED_HIGH: + midi2_midi1_ep_out_desc.wMaxPacketSize = cpu_to_le16(512); + midi2_midi1_ep_in_desc.wMaxPacketSize = cpu_to_le16(512); + for (i = 0; i < midi2->num_eps; i++) { + midi2_midi2_ep_out_desc[i].wMaxPacketSize = + cpu_to_le16(512); + midi2_midi2_ep_in_desc[i].wMaxPacketSize = + cpu_to_le16(512); + } + fallthrough; + case USB_SPEED_FULL: + midi1_in_eps = midi2_midi1_ep_in_descs; + midi1_out_eps = midi2_midi1_ep_out_descs; + break; + case USB_SPEED_SUPER: + midi2_midi1_ep_out_desc.wMaxPacketSize = cpu_to_le16(1024); + midi2_midi1_ep_in_desc.wMaxPacketSize = cpu_to_le16(1024); + for (i = 0; i < midi2->num_eps; i++) { + midi2_midi2_ep_out_desc[i].wMaxPacketSize = + cpu_to_le16(1024); + midi2_midi2_ep_in_desc[i].wMaxPacketSize = + cpu_to_le16(1024); + } + midi1_in_eps = midi2_midi1_ep_in_ss_descs; + midi1_out_eps = midi2_midi1_ep_out_ss_descs; + break; + } + + err = append_configs(config, midi2_audio_descs); + if (err < 0) + return err; + + if (midi2->num_midi1_in && midi2->num_midi1_out) + midi2_midi1_if_desc.bNumEndpoints = 2; + else + midi2_midi1_if_desc.bNumEndpoints = 1; + + err = append_configs(config, midi2_midi1_descs); + if (err < 0) + return err; + + total = USB_DT_MS_HEADER_SIZE; + if (midi2->num_midi1_out) { + midi2_midi1_ep_out_class_desc.bLength = + USB_DT_MS_ENDPOINT_SIZE(midi2->num_midi1_out); + total += midi2_midi1_ep_out_class_desc.bLength; + midi2_midi1_ep_out_class_desc.bNumEmbMIDIJack = + midi2->num_midi1_out; + total += midi2->num_midi1_out * + (USB_DT_MIDI_IN_SIZE + USB_DT_MIDI_OUT_SIZE(1)); + for (i = 0; i < midi2->num_midi1_out; i++) { + jack = append_midi1_in_jack(midi2, config, + &midi2->in_cable_mapping[i], + USB_MS_EMBEDDED); + if (jack < 0) + return jack; + midi2_midi1_ep_out_class_desc.baAssocJackID[i] = jack; + jack = append_midi1_out_jack(midi2, config, + &midi2->in_cable_mapping[i], + USB_MS_EXTERNAL, jack); + if (jack < 0) + return jack; + } + } + + if (midi2->num_midi1_in) { + midi2_midi1_ep_in_class_desc.bLength = + USB_DT_MS_ENDPOINT_SIZE(midi2->num_midi1_in); + total += midi2_midi1_ep_in_class_desc.bLength; + midi2_midi1_ep_in_class_desc.bNumEmbMIDIJack = + midi2->num_midi1_in; + total += midi2->num_midi1_in * + (USB_DT_MIDI_IN_SIZE + USB_DT_MIDI_OUT_SIZE(1)); + for (i = 0; i < midi2->num_midi1_in; i++) { + jack = append_midi1_in_jack(midi2, config, + &midi2->out_cable_mapping[i], + USB_MS_EXTERNAL); + if (jack < 0) + return jack; + jack = append_midi1_out_jack(midi2, config, + &midi2->out_cable_mapping[i], + USB_MS_EMBEDDED, jack); + if (jack < 0) + return jack; + midi2_midi1_ep_in_class_desc.baAssocJackID[i] = jack; + } + } + + midi2_midi1_class_desc.wTotalLength = cpu_to_le16(total); + + if (midi2->num_midi1_out) { + err = append_configs(config, midi1_out_eps); + if (err < 0) + return err; + } + if (midi2->num_midi1_in) { + err = append_configs(config, midi1_in_eps); + if (err < 0) + return err; + } + + err = append_configs(config, midi2_midi2_descs); + if (err < 0) + return err; + + for (i = 0; i < midi2->num_eps; i++) { + err = append_config(config, &midi2_midi2_ep_out_desc[i]); + if (err < 0) + return err; + if (speed == USB_SPEED_SUPER || speed == USB_SPEED_SUPER_PLUS) { + err = append_config(config, &midi2_midi2_ep_out_ss_comp_desc); + if (err < 0) + return err; + } + err = append_config(config, &midi2_midi2_ep_out_class_desc[i]); + if (err < 0) + return err; + err = append_config(config, &midi2_midi2_ep_in_desc[i]); + if (err < 0) + return err; + if (speed == USB_SPEED_SUPER || speed == USB_SPEED_SUPER_PLUS) { + err = append_config(config, &midi2_midi2_ep_in_ss_comp_desc); + if (err < 0) + return err; + } + err = append_config(config, &midi2_midi2_ep_in_class_desc[i]); + if (err < 0) + return err; + } + + return 0; +} + +static void f_midi2_free_usb_configs(struct f_midi2_usb_config *config) +{ + kfree(config->list); + memset(config, 0, sizeof(*config)); +} + +/* as we use the static descriptors for simplicity, serialize bind call */ +static DEFINE_MUTEX(f_midi2_desc_mutex); + +/* fill MIDI2 EP class-specific descriptor */ +static void fill_midi2_class_desc(struct f_midi2_ep *ep, + struct usb_ms20_endpoint_descriptor_32 *cdesc) +{ + int blk; + + cdesc->bLength = USB_DT_MS20_ENDPOINT_SIZE(ep->num_blks); + cdesc->bDescriptorType = USB_DT_CS_ENDPOINT; + cdesc->bDescriptorSubtype = USB_MS_GENERAL_2_0; + cdesc->bNumGrpTrmBlock = ep->num_blks; + for (blk = 0; blk < ep->num_blks; blk++) + cdesc->baAssoGrpTrmBlkID[blk] = ep->blks[blk].gtb_id; +} + +/* initialize MIDI2 EP-in */ +static int f_midi2_init_midi2_ep_in(struct f_midi2 *midi2, int index) +{ + struct f_midi2_ep *ep = &midi2->midi2_eps[index]; + struct usb_endpoint_descriptor *desc = &midi2_midi2_ep_in_desc[index]; + + desc->bLength = USB_DT_ENDPOINT_SIZE; + desc->bDescriptorType = USB_DT_ENDPOINT; + desc->bEndpointAddress = USB_DIR_IN; + desc->bmAttributes = USB_ENDPOINT_XFER_INT; + desc->wMaxPacketSize = cpu_to_le16(EP_MAX_PACKET_INT); + desc->bInterval = 1; + + fill_midi2_class_desc(ep, &midi2_midi2_ep_in_class_desc[index]); + + return f_midi2_init_ep(midi2, ep, &ep->ep_in, desc, + f_midi2_ep_in_complete); +} + +/* initialize MIDI2 EP-out */ +static int f_midi2_init_midi2_ep_out(struct f_midi2 *midi2, int index) +{ + struct f_midi2_ep *ep = &midi2->midi2_eps[index]; + struct usb_endpoint_descriptor *desc = &midi2_midi2_ep_out_desc[index]; + + desc->bLength = USB_DT_ENDPOINT_SIZE; + desc->bDescriptorType = USB_DT_ENDPOINT; + desc->bEndpointAddress = USB_DIR_OUT; + desc->bmAttributes = USB_ENDPOINT_XFER_BULK; + + fill_midi2_class_desc(ep, &midi2_midi2_ep_out_class_desc[index]); + + return f_midi2_init_ep(midi2, ep, &ep->ep_out, desc, + f_midi2_ep_out_complete); +} + +/* gadget function bind callback */ +static int f_midi2_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct f_midi2 *midi2 = func_to_midi2(f); + struct f_midi2_ep *ep; + struct f_midi2_usb_config config = {}; + struct usb_gadget_strings string_fn = { + .language = 0x0409, /* en-us */ + .strings = midi2->string_defs, + }; + struct usb_gadget_strings *strings[] = { + &string_fn, + NULL, + }; + int i, blk, status; + + midi2->gadget = cdev->gadget; + midi2->operation_mode = MIDI_OP_MODE_UNSET; + + status = f_midi2_create_card(midi2); + if (status < 0) + goto fail_register; + + /* maybe allocate device-global string ID */ + midi2->strings = usb_gstrings_attach(c->cdev, strings, + midi2->total_blocks + 1); + if (IS_ERR(midi2->strings)) { + status = PTR_ERR(midi2->strings); + goto fail_string; + } + + mutex_lock(&f_midi2_desc_mutex); + midi2_midi1_if_desc.iInterface = midi2->strings[STR_IFACE].id; + midi2_midi2_if_desc.iInterface = midi2->strings[STR_IFACE].id; + for (i = 0; i < midi2->num_eps; i++) { + ep = &midi2->midi2_eps[i]; + for (blk = 0; blk < ep->num_blks; blk++) + ep->blks[blk].string_id = + midi2->strings[gtb_to_str_id(ep->blks[blk].gtb_id)].id; + } + + midi2_midi2_if_desc.bNumEndpoints = midi2->num_eps * 2; + + /* audio interface */ + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + midi2_audio_if_desc.bInterfaceNumber = status; + + /* MIDI streaming */ + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + midi2->midi_if = status; + midi2_midi1_if_desc.bInterfaceNumber = status; + midi2_midi2_if_desc.bInterfaceNumber = status; + midi2_audio_class_desc.baInterfaceNr[0] = status; + + /* allocate instance-specific endpoints */ + if (midi2->midi2_eps[0].blks[0].info.direction != SNDRV_UMP_DIR_OUTPUT) { + status = f_midi2_init_ep(midi2, NULL, &midi2->midi1_ep_in, + &midi2_midi1_ep_in_desc, + f_midi2_midi1_ep_in_complete); + if (status) + goto fail; + } + + if (midi2->midi2_eps[0].blks[0].info.direction != SNDRV_UMP_DIR_INPUT) { + status = f_midi2_init_ep(midi2, NULL, &midi2->midi1_ep_out, + &midi2_midi1_ep_out_desc, + f_midi2_midi1_ep_out_complete); + if (status) + goto fail; + } + + for (i = 0; i < midi2->num_eps; i++) { + status = f_midi2_init_midi2_ep_in(midi2, i); + if (status) + goto fail; + status = f_midi2_init_midi2_ep_out(midi2, i); + if (status) + goto fail; + } + + status = f_midi2_create_usb_configs(midi2, &config, USB_SPEED_FULL); + if (status < 0) + goto fail; + f->fs_descriptors = usb_copy_descriptors(config.list); + if (!f->fs_descriptors) { + status = -ENOMEM; + goto fail; + } + f_midi2_free_usb_configs(&config); + + status = f_midi2_create_usb_configs(midi2, &config, USB_SPEED_HIGH); + if (status < 0) + goto fail; + f->hs_descriptors = usb_copy_descriptors(config.list); + if (!f->hs_descriptors) { + status = -ENOMEM; + goto fail; + } + f_midi2_free_usb_configs(&config); + + status = f_midi2_create_usb_configs(midi2, &config, USB_SPEED_SUPER); + if (status < 0) + goto fail; + f->ss_descriptors = usb_copy_descriptors(config.list); + if (!f->ss_descriptors) { + status = -ENOMEM; + goto fail; + } + f_midi2_free_usb_configs(&config); + + mutex_unlock(&f_midi2_desc_mutex); + return 0; + +fail: + f_midi2_free_usb_configs(&config); + mutex_unlock(&f_midi2_desc_mutex); + usb_free_all_descriptors(f); +fail_string: + f_midi2_free_card(midi2); +fail_register: + ERROR(midi2, "%s: can't bind, err %d\n", f->name, status); + return status; +} + +/* gadget function unbind callback */ +static void f_midi2_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_midi2 *midi2 = func_to_midi2(f); + int i; + + f_midi2_free_card(midi2); + + f_midi2_free_ep(&midi2->midi1_ep_in); + f_midi2_free_ep(&midi2->midi1_ep_out); + for (i = 0; i < midi2->num_eps; i++) { + f_midi2_free_ep(&midi2->midi2_eps[i].ep_in); + f_midi2_free_ep(&midi2->midi2_eps[i].ep_out); + } + + usb_free_all_descriptors(f); +} + +/* + * ConfigFS interface + */ + +/* type conversion helpers */ +static inline struct f_midi2_opts *to_f_midi2_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_midi2_opts, + func_inst.group); +} + +static inline struct f_midi2_ep_opts * +to_f_midi2_ep_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_midi2_ep_opts, + group); +} + +static inline struct f_midi2_block_opts * +to_f_midi2_block_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_midi2_block_opts, + group); +} + +/* trim the string to be usable for EP and FB name strings */ +static void make_name_string(char *s) +{ + char *p; + + p = strchr(s, '\n'); + if (p) + *p = 0; + + p = s + strlen(s); + for (; p > s && isspace(*p); p--) + *p = 0; +} + +/* configfs helpers: generic show/store for unisnged int */ +static ssize_t f_midi2_opts_uint_show(struct f_midi2_opts *opts, + u32 val, const char *format, char *page) +{ + int result; + + mutex_lock(&opts->lock); + result = sprintf(page, format, val); + mutex_unlock(&opts->lock); + return result; +} + +static ssize_t f_midi2_opts_uint_store(struct f_midi2_opts *opts, + u32 *valp, u32 minval, u32 maxval, + const char *page, size_t len) +{ + int ret; + u32 val; + + mutex_lock(&opts->lock); + if (opts->refcnt) { + ret = -EBUSY; + goto end; + } + + ret = kstrtou32(page, 0, &val); + if (ret) + goto end; + if (val < minval || val > maxval) { + ret = -EINVAL; + goto end; + } + + *valp = val; + ret = len; + +end: + mutex_unlock(&opts->lock); + return ret; +} + +/* generic store for bool */ +static ssize_t f_midi2_opts_bool_store(struct f_midi2_opts *opts, + bool *valp, const char *page, size_t len) +{ + int ret; + bool val; + + mutex_lock(&opts->lock); + if (opts->refcnt) { + ret = -EBUSY; + goto end; + } + + ret = kstrtobool(page, &val); + if (ret) + goto end; + *valp = val; + ret = len; + +end: + mutex_unlock(&opts->lock); + return ret; +} + +/* generic show/store for string */ +static ssize_t f_midi2_opts_str_show(struct f_midi2_opts *opts, + const char *str, char *page) +{ + int result = 0; + + mutex_lock(&opts->lock); + if (str) + result = scnprintf(page, PAGE_SIZE, "%s\n", str); + mutex_unlock(&opts->lock); + return result; +} + +static ssize_t f_midi2_opts_str_store(struct f_midi2_opts *opts, + const char **strp, size_t maxlen, + const char *page, size_t len) +{ + char *c; + int ret; + + mutex_lock(&opts->lock); + if (opts->refcnt) { + ret = -EBUSY; + goto end; + } + + c = kstrndup(page, min(len, maxlen), GFP_KERNEL); + if (!c) { + ret = -ENOMEM; + goto end; + } + + kfree(*strp); + make_name_string(c); + *strp = c; + ret = len; + +end: + mutex_unlock(&opts->lock); + return ret; +} + +/* + * Definitions for UMP Block config + */ + +/* define an uint option for block */ +#define F_MIDI2_BLOCK_OPT(name, format, minval, maxval) \ +static ssize_t f_midi2_block_opts_##name##_show(struct config_item *item,\ + char *page) \ +{ \ + struct f_midi2_block_opts *opts = to_f_midi2_block_opts(item); \ + return f_midi2_opts_uint_show(opts->ep->opts, opts->info.name, \ + format "\n", page); \ +} \ + \ +static ssize_t f_midi2_block_opts_##name##_store(struct config_item *item,\ + const char *page, size_t len) \ +{ \ + struct f_midi2_block_opts *opts = to_f_midi2_block_opts(item); \ + return f_midi2_opts_uint_store(opts->ep->opts, &opts->info.name,\ + minval, maxval, page, len); \ +} \ + \ +CONFIGFS_ATTR(f_midi2_block_opts_, name) + +/* define a boolean option for block */ +#define F_MIDI2_BLOCK_BOOL_OPT(name) \ +static ssize_t f_midi2_block_opts_##name##_show(struct config_item *item,\ + char *page) \ +{ \ + struct f_midi2_block_opts *opts = to_f_midi2_block_opts(item); \ + return f_midi2_opts_uint_show(opts->ep->opts, opts->info.name, \ + "%u\n", page); \ +} \ + \ +static ssize_t f_midi2_block_opts_##name##_store(struct config_item *item,\ + const char *page, size_t len) \ +{ \ + struct f_midi2_block_opts *opts = to_f_midi2_block_opts(item); \ + return f_midi2_opts_bool_store(opts->ep->opts, &opts->info.name,\ + page, len); \ +} \ + \ +CONFIGFS_ATTR(f_midi2_block_opts_, name) + +F_MIDI2_BLOCK_OPT(direction, "0x%x", 1, 3); +F_MIDI2_BLOCK_OPT(first_group, "0x%x", 0, 15); +F_MIDI2_BLOCK_OPT(num_groups, "0x%x", 1, 16); +F_MIDI2_BLOCK_OPT(midi1_first_group, "0x%x", 0, 15); +F_MIDI2_BLOCK_OPT(midi1_num_groups, "0x%x", 0, 16); +F_MIDI2_BLOCK_OPT(ui_hint, "0x%x", 0, 3); +F_MIDI2_BLOCK_OPT(midi_ci_version, "%u", 0, 1); +F_MIDI2_BLOCK_OPT(sysex8_streams, "%u", 0, 255); +F_MIDI2_BLOCK_OPT(is_midi1, "%u", 0, 2); +F_MIDI2_BLOCK_BOOL_OPT(active); + +static ssize_t f_midi2_block_opts_name_show(struct config_item *item, + char *page) +{ + struct f_midi2_block_opts *opts = to_f_midi2_block_opts(item); + + return f_midi2_opts_str_show(opts->ep->opts, opts->info.name, page); +} + +static ssize_t f_midi2_block_opts_name_store(struct config_item *item, + const char *page, size_t len) +{ + struct f_midi2_block_opts *opts = to_f_midi2_block_opts(item); + + return f_midi2_opts_str_store(opts->ep->opts, &opts->info.name, 128, + page, len); +} + +CONFIGFS_ATTR(f_midi2_block_opts_, name); + +static struct configfs_attribute *f_midi2_block_attrs[] = { + &f_midi2_block_opts_attr_direction, + &f_midi2_block_opts_attr_first_group, + &f_midi2_block_opts_attr_num_groups, + &f_midi2_block_opts_attr_midi1_first_group, + &f_midi2_block_opts_attr_midi1_num_groups, + &f_midi2_block_opts_attr_ui_hint, + &f_midi2_block_opts_attr_midi_ci_version, + &f_midi2_block_opts_attr_sysex8_streams, + &f_midi2_block_opts_attr_is_midi1, + &f_midi2_block_opts_attr_active, + &f_midi2_block_opts_attr_name, + NULL, +}; + +static void f_midi2_block_opts_release(struct config_item *item) +{ + struct f_midi2_block_opts *opts = to_f_midi2_block_opts(item); + + kfree(opts->info.name); + kfree(opts); +} + +static struct configfs_item_operations f_midi2_block_item_ops = { + .release = f_midi2_block_opts_release, +}; + +static const struct config_item_type f_midi2_block_type = { + .ct_item_ops = &f_midi2_block_item_ops, + .ct_attrs = f_midi2_block_attrs, + .ct_owner = THIS_MODULE, +}; + +/* create a f_midi2_block_opts instance for the given block number */ +static int f_midi2_block_opts_create(struct f_midi2_ep_opts *ep_opts, + unsigned int blk, + struct f_midi2_block_opts **block_p) +{ + struct f_midi2_block_opts *block_opts; + int ret = 0; + + mutex_lock(&ep_opts->opts->lock); + if (ep_opts->opts->refcnt || ep_opts->blks[blk]) { + ret = -EBUSY; + goto out; + } + + block_opts = kzalloc(sizeof(*block_opts), GFP_KERNEL); + if (!block_opts) { + ret = -ENOMEM; + goto out; + } + + block_opts->ep = ep_opts; + block_opts->id = blk; + + /* set up the default values */ + block_opts->info.direction = SNDRV_UMP_DIR_BIDIRECTION; + block_opts->info.first_group = 0; + block_opts->info.num_groups = 1; + block_opts->info.ui_hint = SNDRV_UMP_BLOCK_UI_HINT_BOTH; + block_opts->info.active = 1; + + ep_opts->blks[blk] = block_opts; + *block_p = block_opts; + + out: + mutex_unlock(&ep_opts->opts->lock); + return ret; +} + +/* make_group callback for a block */ +static struct config_group * +f_midi2_opts_block_make(struct config_group *group, const char *name) +{ + struct f_midi2_ep_opts *ep_opts; + struct f_midi2_block_opts *block_opts; + unsigned int blk; + int ret; + + if (strncmp(name, "block.", 6)) + return ERR_PTR(-EINVAL); + ret = kstrtouint(name + 6, 10, &blk); + if (ret) + return ERR_PTR(ret); + + ep_opts = to_f_midi2_ep_opts(&group->cg_item); + + if (blk >= SNDRV_UMP_MAX_BLOCKS) + return ERR_PTR(-EINVAL); + if (ep_opts->blks[blk]) + return ERR_PTR(-EBUSY); + ret = f_midi2_block_opts_create(ep_opts, blk, &block_opts); + if (ret) + return ERR_PTR(ret); + + config_group_init_type_name(&block_opts->group, name, + &f_midi2_block_type); + return &block_opts->group; +} + +/* drop_item callback for a block */ +static void +f_midi2_opts_block_drop(struct config_group *group, struct config_item *item) +{ + struct f_midi2_block_opts *block_opts = to_f_midi2_block_opts(item); + + mutex_lock(&block_opts->ep->opts->lock); + block_opts->ep->blks[block_opts->id] = NULL; + mutex_unlock(&block_opts->ep->opts->lock); + config_item_put(item); +} + +/* + * Definitions for UMP Endpoint config + */ + +/* define an uint option for EP */ +#define F_MIDI2_EP_OPT(name, format, minval, maxval) \ +static ssize_t f_midi2_ep_opts_##name##_show(struct config_item *item, \ + char *page) \ +{ \ + struct f_midi2_ep_opts *opts = to_f_midi2_ep_opts(item); \ + return f_midi2_opts_uint_show(opts->opts, opts->info.name, \ + format "\n", page); \ +} \ + \ +static ssize_t f_midi2_ep_opts_##name##_store(struct config_item *item, \ + const char *page, size_t len)\ +{ \ + struct f_midi2_ep_opts *opts = to_f_midi2_ep_opts(item); \ + return f_midi2_opts_uint_store(opts->opts, &opts->info.name, \ + minval, maxval, page, len); \ +} \ + \ +CONFIGFS_ATTR(f_midi2_ep_opts_, name) + +/* define a string option for EP */ +#define F_MIDI2_EP_STR_OPT(name, maxlen) \ +static ssize_t f_midi2_ep_opts_##name##_show(struct config_item *item, \ + char *page) \ +{ \ + struct f_midi2_ep_opts *opts = to_f_midi2_ep_opts(item); \ + return f_midi2_opts_str_show(opts->opts, opts->info.name, page);\ +} \ + \ +static ssize_t f_midi2_ep_opts_##name##_store(struct config_item *item, \ + const char *page, size_t len) \ +{ \ + struct f_midi2_ep_opts *opts = to_f_midi2_ep_opts(item); \ + return f_midi2_opts_str_store(opts->opts, &opts->info.name, maxlen,\ + page, len); \ +} \ + \ +CONFIGFS_ATTR(f_midi2_ep_opts_, name) + +F_MIDI2_EP_OPT(protocol, "0x%x", 1, 2); +F_MIDI2_EP_OPT(protocol_caps, "0x%x", 1, 3); +F_MIDI2_EP_OPT(manufacturer, "0x%x", 0, 0xffffff); +F_MIDI2_EP_OPT(family, "0x%x", 0, 0xffff); +F_MIDI2_EP_OPT(model, "0x%x", 0, 0xffff); +F_MIDI2_EP_OPT(sw_revision, "0x%x", 0, 0xffffffff); +F_MIDI2_EP_STR_OPT(ep_name, 128); +F_MIDI2_EP_STR_OPT(product_id, 128); + +static struct configfs_attribute *f_midi2_ep_attrs[] = { + &f_midi2_ep_opts_attr_protocol, + &f_midi2_ep_opts_attr_protocol_caps, + &f_midi2_ep_opts_attr_ep_name, + &f_midi2_ep_opts_attr_product_id, + &f_midi2_ep_opts_attr_manufacturer, + &f_midi2_ep_opts_attr_family, + &f_midi2_ep_opts_attr_model, + &f_midi2_ep_opts_attr_sw_revision, + NULL, +}; + +static void f_midi2_ep_opts_release(struct config_item *item) +{ + struct f_midi2_ep_opts *opts = to_f_midi2_ep_opts(item); + + kfree(opts->info.ep_name); + kfree(opts->info.product_id); + kfree(opts); +} + +static struct configfs_item_operations f_midi2_ep_item_ops = { + .release = f_midi2_ep_opts_release, +}; + +static struct configfs_group_operations f_midi2_ep_group_ops = { + .make_group = f_midi2_opts_block_make, + .drop_item = f_midi2_opts_block_drop, +}; + +static const struct config_item_type f_midi2_ep_type = { + .ct_item_ops = &f_midi2_ep_item_ops, + .ct_group_ops = &f_midi2_ep_group_ops, + .ct_attrs = f_midi2_ep_attrs, + .ct_owner = THIS_MODULE, +}; + +/* create a f_midi2_ep_opts instance */ +static int f_midi2_ep_opts_create(struct f_midi2_opts *opts, + unsigned int index, + struct f_midi2_ep_opts **ep_p) +{ + struct f_midi2_ep_opts *ep_opts; + + ep_opts = kzalloc(sizeof(*ep_opts), GFP_KERNEL); + if (!ep_opts) + return -ENOMEM; + + ep_opts->opts = opts; + ep_opts->index = index; + + /* set up the default values */ + ep_opts->info.protocol = 2; + ep_opts->info.protocol_caps = 3; + + opts->eps[index] = ep_opts; + *ep_p = ep_opts; + return 0; +} + +/* make_group callback for an EP */ +static struct config_group * +f_midi2_opts_ep_make(struct config_group *group, const char *name) +{ + struct f_midi2_opts *opts; + struct f_midi2_ep_opts *ep_opts; + unsigned int index; + int ret; + + if (strncmp(name, "ep.", 3)) + return ERR_PTR(-EINVAL); + ret = kstrtouint(name + 3, 10, &index); + if (ret) + return ERR_PTR(ret); + + opts = to_f_midi2_opts(&group->cg_item); + if (index >= MAX_UMP_EPS) + return ERR_PTR(-EINVAL); + if (opts->eps[index]) + return ERR_PTR(-EBUSY); + ret = f_midi2_ep_opts_create(opts, index, &ep_opts); + if (ret) + return ERR_PTR(ret); + + config_group_init_type_name(&ep_opts->group, name, &f_midi2_ep_type); + return &ep_opts->group; +} + +/* drop_item callback for an EP */ +static void +f_midi2_opts_ep_drop(struct config_group *group, struct config_item *item) +{ + struct f_midi2_ep_opts *ep_opts = to_f_midi2_ep_opts(item); + + mutex_lock(&ep_opts->opts->lock); + ep_opts->opts->eps[ep_opts->index] = NULL; + mutex_unlock(&ep_opts->opts->lock); + config_item_put(item); +} + +/* + * Definitions for card config + */ + +/* define a bool option for card */ +#define F_MIDI2_BOOL_OPT(name) \ +static ssize_t f_midi2_opts_##name##_show(struct config_item *item, \ + char *page) \ +{ \ + struct f_midi2_opts *opts = to_f_midi2_opts(item); \ + return f_midi2_opts_uint_show(opts, opts->info.name, \ + "%u\n", page); \ +} \ + \ +static ssize_t f_midi2_opts_##name##_store(struct config_item *item, \ + const char *page, size_t len) \ +{ \ + struct f_midi2_opts *opts = to_f_midi2_opts(item); \ + return f_midi2_opts_bool_store(opts, &opts->info.name, \ + page, len); \ +} \ + \ +CONFIGFS_ATTR(f_midi2_opts_, name) + +F_MIDI2_BOOL_OPT(process_ump); +F_MIDI2_BOOL_OPT(static_block); + +static ssize_t f_midi2_opts_iface_name_show(struct config_item *item, + char *page) +{ + struct f_midi2_opts *opts = to_f_midi2_opts(item); + + return f_midi2_opts_str_show(opts, opts->info.iface_name, page); +} + +static ssize_t f_midi2_opts_iface_name_store(struct config_item *item, + const char *page, size_t len) +{ + struct f_midi2_opts *opts = to_f_midi2_opts(item); + + return f_midi2_opts_str_store(opts, &opts->info.iface_name, 128, + page, len); +} + +CONFIGFS_ATTR(f_midi2_opts_, iface_name); + +static struct configfs_attribute *f_midi2_attrs[] = { + &f_midi2_opts_attr_process_ump, + &f_midi2_opts_attr_static_block, + &f_midi2_opts_attr_iface_name, + NULL +}; + +static void f_midi2_opts_release(struct config_item *item) +{ + struct f_midi2_opts *opts = to_f_midi2_opts(item); + + usb_put_function_instance(&opts->func_inst); +} + +static struct configfs_item_operations f_midi2_item_ops = { + .release = f_midi2_opts_release, +}; + +static struct configfs_group_operations f_midi2_group_ops = { + .make_group = f_midi2_opts_ep_make, + .drop_item = f_midi2_opts_ep_drop, +}; + +static const struct config_item_type f_midi2_func_type = { + .ct_item_ops = &f_midi2_item_ops, + .ct_group_ops = &f_midi2_group_ops, + .ct_attrs = f_midi2_attrs, + .ct_owner = THIS_MODULE, +}; + +static void f_midi2_free_inst(struct usb_function_instance *f) +{ + struct f_midi2_opts *opts; + + opts = container_of(f, struct f_midi2_opts, func_inst); + + kfree(opts->info.iface_name); + kfree(opts); +} + +/* gadget alloc_inst */ +static struct usb_function_instance *f_midi2_alloc_inst(void) +{ + struct f_midi2_opts *opts; + struct f_midi2_ep_opts *ep_opts; + struct f_midi2_block_opts *block_opts; + int ret; + + opts = kzalloc(sizeof(*opts), GFP_KERNEL); + if (!opts) + return ERR_PTR(-ENOMEM); + + mutex_init(&opts->lock); + opts->func_inst.free_func_inst = f_midi2_free_inst; + opts->info.process_ump = true; + opts->info.static_block = true; + opts->info.num_reqs = 32; + opts->info.req_buf_size = 512; + + /* create the default ep */ + ret = f_midi2_ep_opts_create(opts, 0, &ep_opts); + if (ret) { + kfree(opts); + return ERR_PTR(ret); + } + + /* create the default block */ + ret = f_midi2_block_opts_create(ep_opts, 0, &block_opts); + if (ret) { + kfree(ep_opts); + kfree(opts); + return ERR_PTR(ret); + } + + /* set up the default MIDI1 (that is mandatory) */ + block_opts->info.midi1_num_groups = 1; + + config_group_init_type_name(&opts->func_inst.group, "", + &f_midi2_func_type); + + config_group_init_type_name(&ep_opts->group, "ep.0", + &f_midi2_ep_type); + configfs_add_default_group(&ep_opts->group, &opts->func_inst.group); + + config_group_init_type_name(&block_opts->group, "block.0", + &f_midi2_block_type); + configfs_add_default_group(&block_opts->group, &ep_opts->group); + + return &opts->func_inst; +} + +static void do_f_midi2_free(struct f_midi2 *midi2, struct f_midi2_opts *opts) +{ + mutex_lock(&opts->lock); + --opts->refcnt; + mutex_unlock(&opts->lock); + kfree(midi2->string_defs); + kfree(midi2); +} + +static void f_midi2_free(struct usb_function *f) +{ + do_f_midi2_free(func_to_midi2(f), + container_of(f->fi, struct f_midi2_opts, func_inst)); +} + +/* verify the parameters set up via configfs; + * return the number of EPs or a negative error + */ +static int verify_parameters(struct f_midi2_opts *opts) +{ + int i, j, num_eps, num_blks; + struct f_midi2_ep_info *ep; + struct f_midi2_block_info *bp; + + for (num_eps = 0; num_eps < MAX_UMP_EPS && opts->eps[num_eps]; + num_eps++) + ; + if (!num_eps) { + pr_err("f_midi2: No EP is defined\n"); + return -EINVAL; + } + + num_blks = 0; + for (i = 0; i < num_eps; i++) { + ep = &opts->eps[i]->info; + if (!(ep->protocol_caps & ep->protocol)) { + pr_err("f_midi2: Invalid protocol 0x%x (caps 0x%x) for EP %d\n", + ep->protocol, ep->protocol_caps, i); + return -EINVAL; + } + + for (j = 0; j < SNDRV_UMP_MAX_BLOCKS && opts->eps[i]->blks[j]; + j++, num_blks++) { + bp = &opts->eps[i]->blks[j]->info; + if (bp->first_group + bp->num_groups > SNDRV_UMP_MAX_GROUPS) { + pr_err("f_midi2: Invalid group definitions for block %d:%d\n", + i, j); + return -EINVAL; + } + + if (bp->midi1_num_groups) { + if (bp->midi1_first_group < bp->first_group || + bp->midi1_first_group + bp->midi1_num_groups > + bp->first_group + bp->num_groups) { + pr_err("f_midi2: Invalid MIDI1 group definitions for block %d:%d\n", + i, j); + return -EINVAL; + } + } + } + } + if (!num_blks) { + pr_err("f_midi2: No block is defined\n"); + return -EINVAL; + } + + return num_eps; +} + +/* fill mapping between MIDI 1.0 cable and UMP EP/group */ +static void fill_midi1_cable_mapping(struct f_midi2 *midi2, + struct f_midi2_ep *ep, + int blk) +{ + const struct f_midi2_block_info *binfo = &ep->blks[blk].info; + struct midi1_cable_mapping *map; + int i, group; + + if (!binfo->midi1_num_groups) + return; + if (binfo->direction != SNDRV_UMP_DIR_OUTPUT) { + group = binfo->midi1_first_group; + map = midi2->in_cable_mapping + midi2->num_midi1_in; + for (i = 0; i < binfo->midi1_num_groups; i++, group++, map++) { + if (midi2->num_midi1_in >= MAX_CABLES) + break; + map->ep = ep; + map->block = blk; + map->group = group; + midi2->num_midi1_in++; + /* store 1-based cable number */ + ep->in_group_to_cable[group] = midi2->num_midi1_in; + } + } + + if (binfo->direction != SNDRV_UMP_DIR_INPUT) { + group = binfo->midi1_first_group; + map = midi2->out_cable_mapping + midi2->num_midi1_out; + for (i = 0; i < binfo->midi1_num_groups; i++, group++, map++) { + if (midi2->num_midi1_out >= MAX_CABLES) + break; + map->ep = ep; + map->block = blk; + map->group = group; + midi2->num_midi1_out++; + } + } +} + +/* gadget alloc callback */ +static struct usb_function *f_midi2_alloc(struct usb_function_instance *fi) +{ + struct f_midi2 *midi2; + struct f_midi2_opts *opts; + struct f_midi2_ep *ep; + struct f_midi2_block *bp; + int i, num_eps, blk; + + midi2 = kzalloc(sizeof(*midi2), GFP_KERNEL); + if (!midi2) + return ERR_PTR(-ENOMEM); + + opts = container_of(fi, struct f_midi2_opts, func_inst); + mutex_lock(&opts->lock); + num_eps = verify_parameters(opts); + if (num_eps < 0) { + mutex_unlock(&opts->lock); + kfree(midi2); + return ERR_PTR(num_eps); + } + ++opts->refcnt; + mutex_unlock(&opts->lock); + + spin_lock_init(&midi2->queue_lock); + + midi2->func.name = "midi2_func"; + midi2->func.bind = f_midi2_bind; + midi2->func.unbind = f_midi2_unbind; + midi2->func.get_alt = f_midi2_get_alt; + midi2->func.set_alt = f_midi2_set_alt; + midi2->func.setup = f_midi2_setup; + midi2->func.disable = f_midi2_disable; + midi2->func.free_func = f_midi2_free; + + midi2->info = opts->info; + midi2->num_eps = num_eps; + + for (i = 0; i < num_eps; i++) { + ep = &midi2->midi2_eps[i]; + ep->info = opts->eps[i]->info; + ep->card = midi2; + for (blk = 0; blk < SNDRV_UMP_MAX_BLOCKS && + opts->eps[i]->blks[blk]; blk++) { + bp = &ep->blks[blk]; + ep->num_blks++; + bp->info = opts->eps[i]->blks[blk]->info; + bp->gtb_id = ++midi2->total_blocks; + } + } + + midi2->string_defs = kcalloc(midi2->total_blocks + 1, + sizeof(*midi2->string_defs), GFP_KERNEL); + if (!midi2->string_defs) { + do_f_midi2_free(midi2, opts); + return ERR_PTR(-ENOMEM); + } + + if (opts->info.iface_name && *opts->info.iface_name) + midi2->string_defs[STR_IFACE].s = opts->info.iface_name; + else + midi2->string_defs[STR_IFACE].s = ump_ep_name(&midi2->midi2_eps[0]); + + for (i = 0; i < midi2->num_eps; i++) { + ep = &midi2->midi2_eps[i]; + for (blk = 0; blk < ep->num_blks; blk++) { + bp = &ep->blks[blk]; + midi2->string_defs[gtb_to_str_id(bp->gtb_id)].s = + ump_fb_name(&bp->info); + + fill_midi1_cable_mapping(midi2, ep, blk); + } + } + + if (!midi2->num_midi1_in && !midi2->num_midi1_out) { + pr_err("f_midi2: MIDI1 definition is missing\n"); + do_f_midi2_free(midi2, opts); + return ERR_PTR(-EINVAL); + } + + return &midi2->func; +} + +DECLARE_USB_FUNCTION_INIT(midi2, f_midi2_alloc_inst, f_midi2_alloc); + +MODULE_DESCRIPTION("USB MIDI 2.0 class function driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/function/f_ncm.c b/drivers/usb/gadget/function/f_ncm.c index 424bb3b666db..0e38330271d5 100644 --- a/drivers/usb/gadget/function/f_ncm.c +++ b/drivers/usb/gadget/function/f_ncm.c @@ -11,14 +11,17 @@ * Copyright (C) 2008 Nokia Corporation */ +#include <linux/cleanup.h> #include <linux/kernel.h> #include <linux/interrupt.h> #include <linux/module.h> #include <linux/device.h> #include <linux/etherdevice.h> #include <linux/crc32.h> +#include <linux/string_choices.h> #include <linux/usb/cdc.h> +#include <linux/usb/gadget.h> #include "u_ether.h" #include "u_ether_configfs.h" @@ -80,21 +83,6 @@ static inline struct f_ncm *func_to_ncm(struct usb_function *f) return container_of(f, struct f_ncm, port.func); } -/* peak (theoretical) bulk transfer rate in bits-per-second */ -static inline unsigned ncm_bitrate(struct usb_gadget *g) -{ - if (!g) - return 0; - else if (gadget_is_superspeed(g) && g->speed >= USB_SPEED_SUPER_PLUS) - return 4250000000U; - else if (gadget_is_superspeed(g) && g->speed == USB_SPEED_SUPER) - return 3750000000U; - else if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH) - return 13 * 512 * 8 * 1000 * 8; - else - return 19 * 64 * 1 * 1000 * 8; -} - /*-------------------------------------------------------------------------*/ /* @@ -118,6 +106,16 @@ static inline unsigned ncm_bitrate(struct usb_gadget *g) /* Delay for the transmit to wait before sending an unfilled NTB frame. */ #define TX_TIMEOUT_NSECS 300000 +/* + * Although max mtu as dictated by u_ether is 15412 bytes, setting + * max_segment_size to 15426 would not be efficient. If user chooses segment + * size to be (>= 8192), then we can't aggregate more than one buffer in each + * NTB (assuming each packet coming from network layer is >= 8192 bytes) as ep + * maxpacket limit is 16384. So let max_segment_size be limited to 8000 to allow + * at least 2 packets to be aggregated reducing wastage of NTB buffer space + */ +#define MAX_DATAGRAM_SIZE 8000 + #define FORMATS_SUPPORTED (USB_CDC_NCM_NTB16_SUPPORTED | \ USB_CDC_NCM_NTB32_SUPPORTED) @@ -194,7 +192,6 @@ static struct usb_cdc_ether_desc ecm_desc = { /* this descriptor actually adds value, surprise! */ /* .iMACAddress = DYNAMIC */ .bmEthernetStatistics = cpu_to_le32(0), /* no statistics */ - .wMaxSegmentSize = cpu_to_le16(ETH_FRAME_LEN), .wNumberMCFilters = cpu_to_le16(0), .bNumberPowerFilters = 0, }; @@ -564,7 +561,7 @@ static void ncm_do_notify(struct f_ncm *ncm) req->length = sizeof *event; DBG(cdev, "notify connect %s\n", - ncm->is_open ? "true" : "false"); + str_true_false(ncm->is_open)); ncm->notify_state = NCM_NOTIFY_NONE; break; @@ -576,10 +573,10 @@ static void ncm_do_notify(struct f_ncm *ncm) /* SPEED_CHANGE data is up/down speeds in bits/sec */ data = req->buf + sizeof *event; - data[0] = cpu_to_le32(ncm_bitrate(cdev->gadget)); + data[0] = cpu_to_le32(gether_bitrate(cdev->gadget)); data[1] = data[0]; - DBG(cdev, "notify speed %u\n", ncm_bitrate(cdev->gadget)); + DBG(cdev, "notify speed %u\n", gether_bitrate(cdev->gadget)); ncm->notify_state = NCM_NOTIFY_CONNECT; break; } @@ -884,7 +881,7 @@ static int ncm_set_alt(struct usb_function *f, unsigned intf, unsigned alt) if (alt > 1) goto fail; - if (ncm->port.in_ep->enabled) { + if (ncm->netdev) { DBG(cdev, "reset ncm\n"); ncm->netdev = NULL; gether_disconnect(&ncm->port); @@ -1171,7 +1168,8 @@ static int ncm_unwrap_ntb(struct gether *port, struct sk_buff_head *list) { struct f_ncm *ncm = func_to_ncm(&port->func); - __le16 *tmp = (void *) skb->data; + unsigned char *ntb_ptr = skb->data; + __le16 *tmp; unsigned index, index2; int ndp_index; unsigned dg_len, dg_len2; @@ -1180,10 +1178,18 @@ static int ncm_unwrap_ntb(struct gether *port, struct sk_buff *skb2; int ret = -EINVAL; unsigned ntb_max = le32_to_cpu(ntb_parameters.dwNtbOutMaxSize); - unsigned frame_max = le16_to_cpu(ecm_desc.wMaxSegmentSize); + unsigned frame_max; const struct ndp_parser_opts *opts = ncm->parser_opts; unsigned crc_len = ncm->is_crc ? sizeof(uint32_t) : 0; int dgram_counter; + int to_process = skb->len; + struct f_ncm_opts *ncm_opts; + + ncm_opts = container_of(port->func.fi, struct f_ncm_opts, func_inst); + frame_max = ncm_opts->max_segment_size; + +parse_ntb: + tmp = (__le16 *)ntb_ptr; /* dwSignature */ if (get_unaligned_le32(tmp) != opts->nth_sign) { @@ -1230,7 +1236,7 @@ static int ncm_unwrap_ntb(struct gether *port, * walk through NDP * dwSignature */ - tmp = (void *)(skb->data + ndp_index); + tmp = (__le16 *)(ntb_ptr + ndp_index); if (get_unaligned_le32(tmp) != ncm->ndp_sign) { INFO(port->func.config->cdev, "Wrong NDP SIGN\n"); goto err; @@ -1287,11 +1293,11 @@ static int ncm_unwrap_ntb(struct gether *port, if (ncm->is_crc) { uint32_t crc, crc2; - crc = get_unaligned_le32(skb->data + + crc = get_unaligned_le32(ntb_ptr + index + dg_len - crc_len); crc2 = ~crc32_le(~0, - skb->data + index, + ntb_ptr + index, dg_len - crc_len); if (crc != crc2) { INFO(port->func.config->cdev, @@ -1318,7 +1324,7 @@ static int ncm_unwrap_ntb(struct gether *port, dg_len - crc_len); if (skb2 == NULL) goto err; - skb_put_data(skb2, skb->data + index, + skb_put_data(skb2, ntb_ptr + index, dg_len - crc_len); skb_queue_tail(list, skb2); @@ -1331,10 +1337,25 @@ static int ncm_unwrap_ntb(struct gether *port, } while (ndp_len > 2 * (opts->dgram_item_len * 2)); } while (ndp_index); - dev_consume_skb_any(skb); - VDBG(port->func.config->cdev, "Parsed NTB with %d frames\n", dgram_counter); + + to_process -= block_len; + + /* + * Windows NCM driver avoids USB ZLPs by adding a 1-byte + * zero pad as needed. + */ + if (to_process == 1 && + (*(unsigned char *)(ntb_ptr + block_len) == 0x00)) { + to_process--; + } else if ((to_process > 0) && (block_len != 0)) { + ntb_ptr = (unsigned char *)(ntb_ptr + block_len); + goto parse_ntb; + } + + dev_consume_skb_any(skb); + return 0; err: skb_queue_purge(list); @@ -1349,7 +1370,7 @@ static void ncm_disable(struct usb_function *f) DBG(cdev, "ncm deactivated\n"); - if (ncm->port.in_ep->enabled) { + if (ncm->netdev) { ncm->netdev = NULL; gether_disconnect(&ncm->port); } @@ -1413,46 +1434,44 @@ static int ncm_bind(struct usb_configuration *c, struct usb_function *f) struct usb_composite_dev *cdev = c->cdev; struct f_ncm *ncm = func_to_ncm(f); struct usb_string *us; - int status; + int status = 0; struct usb_ep *ep; struct f_ncm_opts *ncm_opts; + struct usb_os_desc_table *os_desc_table __free(kfree) = NULL; + struct usb_request *request __free(free_usb_request) = NULL; + if (!can_support_ecm(cdev->gadget)) return -EINVAL; ncm_opts = container_of(f->fi, struct f_ncm_opts, func_inst); if (cdev->use_os_string) { - f->os_desc_table = kzalloc(sizeof(*f->os_desc_table), - GFP_KERNEL); - if (!f->os_desc_table) + os_desc_table = kzalloc(sizeof(*os_desc_table), GFP_KERNEL); + if (!os_desc_table) return -ENOMEM; - f->os_desc_n = 1; - f->os_desc_table[0].os_desc = &ncm_opts->ncm_os_desc; } - /* - * in drivers/usb/gadget/configfs.c:configfs_composite_bind() - * configurations are bound in sequence with list_for_each_entry, - * in each configuration its functions are bound in sequence - * with list_for_each_entry, so we assume no race condition - * with regard to ncm_opts->bound access - */ + mutex_lock(&ncm_opts->lock); + gether_set_gadget(ncm_opts->net, cdev->gadget); if (!ncm_opts->bound) { - mutex_lock(&ncm_opts->lock); - gether_set_gadget(ncm_opts->net, cdev->gadget); + ncm_opts->net->mtu = (ncm_opts->max_segment_size - ETH_HLEN); status = gether_register_netdev(ncm_opts->net); - mutex_unlock(&ncm_opts->lock); - if (status) - goto fail; - ncm_opts->bound = true; } + mutex_unlock(&ncm_opts->lock); + + if (status) + return status; + + ncm_opts->bound = true; + + ncm_string_defs[1].s = ncm->ethaddr; + us = usb_gstrings_attach(cdev, ncm_strings, ARRAY_SIZE(ncm_string_defs)); - if (IS_ERR(us)) { - status = PTR_ERR(us); - goto fail; - } + if (IS_ERR(us)) + return PTR_ERR(us); + ncm_control_intf.iInterface = us[STRING_CTRL_IDX].id; ncm_data_nop_intf.iInterface = us[STRING_DATA_IDX].id; ncm_data_intf.iInterface = us[STRING_DATA_IDX].id; @@ -1462,55 +1481,49 @@ static int ncm_bind(struct usb_configuration *c, struct usb_function *f) /* allocate instance-specific interface IDs */ status = usb_interface_id(c, f); if (status < 0) - goto fail; + return status; ncm->ctrl_id = status; ncm_iad_desc.bFirstInterface = status; ncm_control_intf.bInterfaceNumber = status; ncm_union_desc.bMasterInterface0 = status; - if (cdev->use_os_string) - f->os_desc_table[0].if_id = - ncm_iad_desc.bFirstInterface; - status = usb_interface_id(c, f); if (status < 0) - goto fail; + return status; ncm->data_id = status; ncm_data_nop_intf.bInterfaceNumber = status; ncm_data_intf.bInterfaceNumber = status; ncm_union_desc.bSlaveInterface0 = status; - status = -ENODEV; + ecm_desc.wMaxSegmentSize = cpu_to_le16(ncm_opts->max_segment_size); /* allocate instance-specific endpoints */ ep = usb_ep_autoconfig(cdev->gadget, &fs_ncm_in_desc); if (!ep) - goto fail; + return -ENODEV; ncm->port.in_ep = ep; ep = usb_ep_autoconfig(cdev->gadget, &fs_ncm_out_desc); if (!ep) - goto fail; + return -ENODEV; ncm->port.out_ep = ep; ep = usb_ep_autoconfig(cdev->gadget, &fs_ncm_notify_desc); if (!ep) - goto fail; + return -ENODEV; ncm->notify = ep; - status = -ENOMEM; - /* allocate notification request and buffer */ - ncm->notify_req = usb_ep_alloc_request(ep, GFP_KERNEL); - if (!ncm->notify_req) - goto fail; - ncm->notify_req->buf = kmalloc(NCM_STATUS_BYTECOUNT, GFP_KERNEL); - if (!ncm->notify_req->buf) - goto fail; - ncm->notify_req->context = ncm; - ncm->notify_req->complete = ncm_notify_complete; + request = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!request) + return -ENOMEM; + request->buf = kmalloc(NCM_STATUS_BYTECOUNT, GFP_KERNEL); + if (!request->buf) + return -ENOMEM; + request->context = ncm; + request->complete = ncm_notify_complete; /* * support all relevant hardware speeds... we expect that when @@ -1530,7 +1543,7 @@ static int ncm_bind(struct usb_configuration *c, struct usb_function *f) status = usb_assign_descriptors(f, ncm_fs_function, ncm_hs_function, ncm_ss_function, ncm_ss_function); if (status) - goto fail; + return status; /* * NOTE: all that is done without knowing or caring about @@ -1541,28 +1554,20 @@ static int ncm_bind(struct usb_configuration *c, struct usb_function *f) ncm->port.open = ncm_open; ncm->port.close = ncm_close; - hrtimer_init(&ncm->task_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); - ncm->task_timer.function = ncm_tx_timeout; + hrtimer_setup(&ncm->task_timer, ncm_tx_timeout, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); - DBG(cdev, "CDC Network: %s speed IN/%s OUT/%s NOTIFY/%s\n", - gadget_is_superspeed(c->cdev->gadget) ? "super" : - gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + if (cdev->use_os_string) { + os_desc_table[0].os_desc = &ncm_opts->ncm_os_desc; + os_desc_table[0].if_id = ncm_iad_desc.bFirstInterface; + f->os_desc_table = no_free_ptr(os_desc_table); + f->os_desc_n = 1; + } + ncm->notify_req = no_free_ptr(request); + + DBG(cdev, "CDC Network: IN/%s OUT/%s NOTIFY/%s\n", ncm->port.in_ep->name, ncm->port.out_ep->name, ncm->notify->name); return 0; - -fail: - kfree(f->os_desc_table); - f->os_desc_n = 0; - - if (ncm->notify_req) { - kfree(ncm->notify_req->buf); - usb_ep_free_request(ncm->notify, ncm->notify_req); - } - - ERROR(cdev, "%s: can't bind, err %d\n", f->name, status); - - return status; } static inline struct f_ncm_opts *to_f_ncm_opts(struct config_item *item) @@ -1586,11 +1591,56 @@ USB_ETHERNET_CONFIGFS_ITEM_ATTR_QMULT(ncm); /* f_ncm_opts_ifname */ USB_ETHERNET_CONFIGFS_ITEM_ATTR_IFNAME(ncm); +static ssize_t ncm_opts_max_segment_size_show(struct config_item *item, + char *page) +{ + struct f_ncm_opts *opts = to_f_ncm_opts(item); + u16 segment_size; + + mutex_lock(&opts->lock); + segment_size = opts->max_segment_size; + mutex_unlock(&opts->lock); + + return sysfs_emit(page, "%u\n", segment_size); +} + +static ssize_t ncm_opts_max_segment_size_store(struct config_item *item, + const char *page, size_t len) +{ + struct f_ncm_opts *opts = to_f_ncm_opts(item); + u16 segment_size; + int ret; + + mutex_lock(&opts->lock); + if (opts->refcnt) { + ret = -EBUSY; + goto out; + } + + ret = kstrtou16(page, 0, &segment_size); + if (ret) + goto out; + + if (segment_size > MAX_DATAGRAM_SIZE) { + ret = -EINVAL; + goto out; + } + + opts->max_segment_size = segment_size; + ret = len; +out: + mutex_unlock(&opts->lock); + return ret; +} + +CONFIGFS_ATTR(ncm_opts_, max_segment_size); + static struct configfs_attribute *ncm_attrs[] = { &ncm_opts_attr_dev_addr, &ncm_opts_attr_host_addr, &ncm_opts_attr_qmult, &ncm_opts_attr_ifname, + &ncm_opts_attr_max_segment_size, NULL, }; @@ -1633,6 +1683,7 @@ static struct usb_function_instance *ncm_alloc_inst(void) kfree(opts); return ERR_CAST(net); } + opts->max_segment_size = ETH_FRAME_LEN; INIT_LIST_HEAD(&opts->ncm_os_desc.ext_prop); descs[0] = &opts->ncm_os_desc; @@ -1710,7 +1761,6 @@ static struct usb_function *ncm_alloc(struct usb_function_instance *fi) mutex_unlock(&opts->lock); return ERR_PTR(-EINVAL); } - ncm_string_defs[STRING_MAC_IDX].s = ncm->ethaddr; spin_lock_init(&ncm->lock); ncm_reset_values(ncm); @@ -1736,5 +1786,6 @@ static struct usb_function *ncm_alloc(struct usb_function_instance *fi) } DECLARE_USB_FUNCTION_INIT(ncm, ncm_alloc_inst, ncm_alloc); +MODULE_DESCRIPTION("USB CDC Network (NCM) link function driver"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Yauheni Kaliuta"); diff --git a/drivers/usb/gadget/function/f_obex.c b/drivers/usb/gadget/function/f_obex.c index ab26d84ed95e..1305e2326cdf 100644 --- a/drivers/usb/gadget/function/f_obex.c +++ b/drivers/usb/gadget/function/f_obex.c @@ -365,9 +365,8 @@ static int obex_bind(struct usb_configuration *c, struct usb_function *f) if (status) goto fail; - dev_dbg(&cdev->gadget->dev, "obex ttyGS%d: %s speed IN/%s OUT/%s\n", + dev_dbg(&cdev->gadget->dev, "obex ttyGS%d: IN/%s OUT/%s\n", obex->port_num, - gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", obex->port.in->name, obex->port.out->name); return 0; @@ -488,4 +487,5 @@ static struct usb_function *obex_alloc(struct usb_function_instance *fi) DECLARE_USB_FUNCTION_INIT(obex, obex_alloc_inst, obex_alloc); MODULE_AUTHOR("Felipe Balbi"); +MODULE_DESCRIPTION("USB CDC OBEX function driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/function/f_phonet.c b/drivers/usb/gadget/function/f_phonet.c index 0bebbdf3f213..0aa9e8224cae 100644 --- a/drivers/usb/gadget/function/f_phonet.c +++ b/drivers/usb/gadget/function/f_phonet.c @@ -729,4 +729,5 @@ void gphonet_cleanup(struct net_device *dev) DECLARE_USB_FUNCTION_INIT(phonet, phonet_alloc_inst, phonet_alloc); MODULE_AUTHOR("Rémi Denis-Courmont"); +MODULE_DESCRIPTION("USB CDC Phonet function"); MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/function/f_printer.c b/drivers/usb/gadget/function/f_printer.c index 4903d761a872..d295ade8fa67 100644 --- a/drivers/usb/gadget/function/f_printer.c +++ b/drivers/usb/gadget/function/f_printer.c @@ -37,7 +37,7 @@ #include <linux/io.h> #include <linux/irq.h> #include <linux/uaccess.h> -#include <asm/unaligned.h> +#include <linux/unaligned.h> #include <linux/usb/ch9.h> #include <linux/usb/composite.h> @@ -54,7 +54,10 @@ #define DEFAULT_Q_LEN 10 /* same as legacy g_printer gadget */ static int major, minors; -static struct class *usb_gadget_class; +static const struct class usb_gadget_class = { + .name = "usb_printer_gadget", +}; + static DEFINE_IDA(printer_ida); static DEFINE_MUTEX(printer_ida_lock); /* protects access do printer_ida */ @@ -210,6 +213,7 @@ static inline struct usb_endpoint_descriptor *ep_desc(struct usb_gadget *gadget, struct usb_endpoint_descriptor *ss) { switch (gadget->speed) { + case USB_SPEED_SUPER_PLUS: case USB_SPEED_SUPER: return ss; case USB_SPEED_HIGH: @@ -446,11 +450,8 @@ printer_read(struct file *fd, char __user *buf, size_t len, loff_t *ptr) mutex_lock(&dev->lock_printer_io); spin_lock_irqsave(&dev->lock, flags); - if (dev->interface < 0) { - spin_unlock_irqrestore(&dev->lock, flags); - mutex_unlock(&dev->lock_printer_io); - return -ENODEV; - } + if (dev->interface < 0) + goto out_disabled; /* We will use this flag later to check if a printer reset happened * after we turn interrupts back on. @@ -458,6 +459,9 @@ printer_read(struct file *fd, char __user *buf, size_t len, loff_t *ptr) dev->reset_printer = 0; setup_rx_reqs(dev); + /* this dropped the lock - need to retest */ + if (dev->interface < 0) + goto out_disabled; bytes_copied = 0; current_rx_req = dev->current_rx_req; @@ -491,6 +495,8 @@ printer_read(struct file *fd, char __user *buf, size_t len, loff_t *ptr) wait_event_interruptible(dev->rx_wait, (likely(!list_empty(&dev->rx_buffers)))); spin_lock_irqsave(&dev->lock, flags); + if (dev->interface < 0) + goto out_disabled; } /* We have data to return then copy it to the caller's buffer.*/ @@ -534,6 +540,9 @@ printer_read(struct file *fd, char __user *buf, size_t len, loff_t *ptr) return -EAGAIN; } + if (dev->interface < 0) + goto out_disabled; + /* If we not returning all the data left in this RX request * buffer then adjust the amount of data left in the buffer. * Othewise if we are done with this RX request buffer then @@ -563,6 +572,11 @@ printer_read(struct file *fd, char __user *buf, size_t len, loff_t *ptr) return bytes_copied; else return -EAGAIN; + +out_disabled: + spin_unlock_irqrestore(&dev->lock, flags); + mutex_unlock(&dev->lock_printer_io); + return -ENODEV; } static ssize_t @@ -583,11 +597,8 @@ printer_write(struct file *fd, const char __user *buf, size_t len, loff_t *ptr) mutex_lock(&dev->lock_printer_io); spin_lock_irqsave(&dev->lock, flags); - if (dev->interface < 0) { - spin_unlock_irqrestore(&dev->lock, flags); - mutex_unlock(&dev->lock_printer_io); - return -ENODEV; - } + if (dev->interface < 0) + goto out_disabled; /* Check if a printer reset happens while we have interrupts on */ dev->reset_printer = 0; @@ -610,6 +621,8 @@ printer_write(struct file *fd, const char __user *buf, size_t len, loff_t *ptr) wait_event_interruptible(dev->tx_wait, (likely(!list_empty(&dev->tx_reqs)))); spin_lock_irqsave(&dev->lock, flags); + if (dev->interface < 0) + goto out_disabled; } while (likely(!list_empty(&dev->tx_reqs)) && len) { @@ -659,6 +672,9 @@ printer_write(struct file *fd, const char __user *buf, size_t len, loff_t *ptr) return -EAGAIN; } + if (dev->interface < 0) + goto out_disabled; + list_add(&req->list, &dev->tx_reqs_active); /* here, we unlock, and only unlock, to avoid deadlock. */ @@ -671,6 +687,8 @@ printer_write(struct file *fd, const char __user *buf, size_t len, loff_t *ptr) mutex_unlock(&dev->lock_printer_io); return -EAGAIN; } + if (dev->interface < 0) + goto out_disabled; } spin_unlock_irqrestore(&dev->lock, flags); @@ -682,6 +700,11 @@ printer_write(struct file *fd, const char __user *buf, size_t len, loff_t *ptr) return bytes_copied; else return -EAGAIN; + +out_disabled: + spin_unlock_irqrestore(&dev->lock, flags); + mutex_unlock(&dev->lock_printer_io); + return -ENODEV; } static int @@ -1120,7 +1143,7 @@ autoconf_fail: /* Setup the sysfs files for the printer gadget. */ devt = MKDEV(major, dev->minor); - pdev = device_create(usb_gadget_class, NULL, devt, + pdev = device_create(&usb_gadget_class, NULL, devt, NULL, "g_printer%d", dev->minor); if (IS_ERR(pdev)) { ERROR(dev, "Failed to create device: g_printer\n"); @@ -1143,7 +1166,7 @@ autoconf_fail: return 0; fail_cdev_add: - device_destroy(usb_gadget_class, devt); + device_destroy(&usb_gadget_class, devt); fail_rx_reqs: while (!list_empty(&dev->rx_reqs)) { @@ -1211,8 +1234,8 @@ static ssize_t f_printer_opts_pnp_string_show(struct config_item *item, if (!opts->pnp_string) goto unlock; - result = strlcpy(page, opts->pnp_string, PAGE_SIZE); - if (result >= PAGE_SIZE) { + result = strscpy(page, opts->pnp_string, PAGE_SIZE); + if (result < 1) { result = PAGE_SIZE; } else if (page[result - 1] != '\n' && result + 1 < PAGE_SIZE) { page[result++] = '\n'; @@ -1309,9 +1332,9 @@ static inline int gprinter_get_minor(void) { int ret; - ret = ida_simple_get(&printer_ida, 0, 0, GFP_KERNEL); + ret = ida_alloc(&printer_ida, GFP_KERNEL); if (ret >= PRINTER_MINORS) { - ida_simple_remove(&printer_ida, ret); + ida_free(&printer_ida, ret); ret = -ENODEV; } @@ -1320,7 +1343,7 @@ static inline int gprinter_get_minor(void) static inline void gprinter_put_minor(int minor) { - ida_simple_remove(&printer_ida, minor); + ida_free(&printer_ida, minor); } static int gprinter_setup(int); @@ -1410,7 +1433,7 @@ static void printer_func_unbind(struct usb_configuration *c, dev = func_to_printer(f); - device_destroy(usb_gadget_class, MKDEV(major, dev->minor)); + device_destroy(&usb_gadget_class, MKDEV(major, dev->minor)); /* Remove Character Device */ cdev_del(&dev->printer_cdev); @@ -1504,6 +1527,7 @@ static struct usb_function *gprinter_alloc(struct usb_function_instance *fi) } DECLARE_USB_FUNCTION_INIT(printer, gprinter_alloc_inst, gprinter_alloc); +MODULE_DESCRIPTION("USB printer function driver"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Craig Nadler"); @@ -1512,19 +1536,14 @@ static int gprinter_setup(int count) int status; dev_t devt; - usb_gadget_class = class_create(THIS_MODULE, "usb_printer_gadget"); - if (IS_ERR(usb_gadget_class)) { - status = PTR_ERR(usb_gadget_class); - usb_gadget_class = NULL; - pr_err("unable to create usb_gadget class %d\n", status); + status = class_register(&usb_gadget_class); + if (status) return status; - } status = alloc_chrdev_region(&devt, 0, count, "USB printer gadget"); if (status) { pr_err("alloc_chrdev_region %d\n", status); - class_destroy(usb_gadget_class); - usb_gadget_class = NULL; + class_unregister(&usb_gadget_class); return status; } @@ -1540,6 +1559,5 @@ static void gprinter_cleanup(void) unregister_chrdev_region(MKDEV(major, 0), minors); major = minors = 0; } - class_destroy(usb_gadget_class); - usb_gadget_class = NULL; + class_unregister(&usb_gadget_class); } diff --git a/drivers/usb/gadget/function/f_rndis.c b/drivers/usb/gadget/function/f_rndis.c index ee95e8f5f9d4..7451e7cb7a85 100644 --- a/drivers/usb/gadget/function/f_rndis.c +++ b/drivers/usb/gadget/function/f_rndis.c @@ -19,6 +19,8 @@ #include <linux/atomic.h> +#include <linux/usb/gadget.h> + #include "u_ether.h" #include "u_ether_configfs.h" #include "u_rndis.h" @@ -84,19 +86,6 @@ static inline struct f_rndis *func_to_rndis(struct usb_function *f) return container_of(f, struct f_rndis, port.func); } -/* peak (theoretical) bulk transfer rate in bits-per-second */ -static unsigned int bitrate(struct usb_gadget *g) -{ - if (gadget_is_superspeed(g) && g->speed >= USB_SPEED_SUPER_PLUS) - return 4250000000U; - if (gadget_is_superspeed(g) && g->speed == USB_SPEED_SUPER) - return 3750000000U; - else if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH) - return 13 * 512 * 8 * 1000 * 8; - else - return 19 * 64 * 1 * 1000 * 8; -} - /*-------------------------------------------------------------------------*/ /* @@ -640,7 +629,7 @@ static void rndis_open(struct gether *geth) DBG(cdev, "%s\n", __func__); rndis_set_param_medium(rndis->params, RNDIS_MEDIUM_802_3, - bitrate(cdev->gadget) / 100); + gether_bitrate(cdev->gadget) / 100); rndis_signal_connect(rndis->params); } @@ -675,6 +664,8 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f) struct usb_ep *ep; struct f_rndis_opts *rndis_opts; + struct usb_os_desc_table *os_desc_table __free(kfree) = NULL; + struct usb_request *request __free(free_usb_request) = NULL; if (!can_support_rndis(c)) return -EINVAL; @@ -682,12 +673,9 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f) rndis_opts = container_of(f->fi, struct f_rndis_opts, func_inst); if (cdev->use_os_string) { - f->os_desc_table = kzalloc(sizeof(*f->os_desc_table), - GFP_KERNEL); - if (!f->os_desc_table) + os_desc_table = kzalloc(sizeof(*os_desc_table), GFP_KERNEL); + if (!os_desc_table) return -ENOMEM; - f->os_desc_n = 1; - f->os_desc_table[0].os_desc = &rndis_opts->rndis_os_desc; } rndis_iad_descriptor.bFunctionClass = rndis_opts->class; @@ -705,16 +693,14 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f) gether_set_gadget(rndis_opts->net, cdev->gadget); status = gether_register_netdev(rndis_opts->net); if (status) - goto fail; + return status; rndis_opts->bound = true; } us = usb_gstrings_attach(cdev, rndis_strings, ARRAY_SIZE(rndis_string_defs)); - if (IS_ERR(us)) { - status = PTR_ERR(us); - goto fail; - } + if (IS_ERR(us)) + return PTR_ERR(us); rndis_control_intf.iInterface = us[0].id; rndis_data_intf.iInterface = us[1].id; rndis_iad_descriptor.iFunction = us[2].id; @@ -722,36 +708,30 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f) /* allocate instance-specific interface IDs */ status = usb_interface_id(c, f); if (status < 0) - goto fail; + return status; rndis->ctrl_id = status; rndis_iad_descriptor.bFirstInterface = status; rndis_control_intf.bInterfaceNumber = status; rndis_union_desc.bMasterInterface0 = status; - if (cdev->use_os_string) - f->os_desc_table[0].if_id = - rndis_iad_descriptor.bFirstInterface; - status = usb_interface_id(c, f); if (status < 0) - goto fail; + return status; rndis->data_id = status; rndis_data_intf.bInterfaceNumber = status; rndis_union_desc.bSlaveInterface0 = status; - status = -ENODEV; - /* allocate instance-specific endpoints */ ep = usb_ep_autoconfig(cdev->gadget, &fs_in_desc); if (!ep) - goto fail; + return -ENODEV; rndis->port.in_ep = ep; ep = usb_ep_autoconfig(cdev->gadget, &fs_out_desc); if (!ep) - goto fail; + return -ENODEV; rndis->port.out_ep = ep; /* NOTE: a status/notification endpoint is, strictly speaking, @@ -760,21 +740,19 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f) */ ep = usb_ep_autoconfig(cdev->gadget, &fs_notify_desc); if (!ep) - goto fail; + return -ENODEV; rndis->notify = ep; - status = -ENOMEM; - /* allocate notification request and buffer */ - rndis->notify_req = usb_ep_alloc_request(ep, GFP_KERNEL); - if (!rndis->notify_req) - goto fail; - rndis->notify_req->buf = kmalloc(STATUS_BYTECOUNT, GFP_KERNEL); - if (!rndis->notify_req->buf) - goto fail; - rndis->notify_req->length = STATUS_BYTECOUNT; - rndis->notify_req->context = rndis; - rndis->notify_req->complete = rndis_response_complete; + request = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!request) + return -ENOMEM; + request->buf = kmalloc(STATUS_BYTECOUNT, GFP_KERNEL); + if (!request->buf) + return -ENOMEM; + request->length = STATUS_BYTECOUNT; + request->context = rndis; + request->complete = rndis_response_complete; /* support all relevant hardware speeds... we expect that when * hardware is dual speed, all bulk-capable endpoints work at @@ -791,7 +769,7 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f) status = usb_assign_descriptors(f, eth_fs_function, eth_hs_function, eth_ss_function, eth_ss_function); if (status) - goto fail; + return status; rndis->port.open = rndis_open; rndis->port.close = rndis_close; @@ -802,36 +780,28 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f) if (rndis->manufacturer && rndis->vendorID && rndis_set_param_vendor(rndis->params, rndis->vendorID, rndis->manufacturer)) { - status = -EINVAL; - goto fail_free_descs; + usb_free_all_descriptors(f); + return -EINVAL; } + if (cdev->use_os_string) { + os_desc_table[0].os_desc = &rndis_opts->rndis_os_desc; + os_desc_table[0].if_id = rndis_iad_descriptor.bFirstInterface; + f->os_desc_table = no_free_ptr(os_desc_table); + f->os_desc_n = 1; + + } + rndis->notify_req = no_free_ptr(request); + /* NOTE: all that is done without knowing or caring about * the network link ... which is unavailable to this code * until we're activated via set_alt(). */ - DBG(cdev, "RNDIS: %s speed IN/%s OUT/%s NOTIFY/%s\n", - gadget_is_superspeed(c->cdev->gadget) ? "super" : - gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + DBG(cdev, "RNDIS: IN/%s OUT/%s NOTIFY/%s\n", rndis->port.in_ep->name, rndis->port.out_ep->name, rndis->notify->name); return 0; - -fail_free_descs: - usb_free_all_descriptors(f); -fail: - kfree(f->os_desc_table); - f->os_desc_n = 0; - - if (rndis->notify_req) { - kfree(rndis->notify_req->buf); - usb_ep_free_request(rndis->notify, rndis->notify_req); - } - - ERROR(cdev, "%s: can't bind, err %d\n", f->name, status); - - return status; } void rndis_borrow_net(struct usb_function_instance *f, struct net_device *net) @@ -1028,5 +998,6 @@ static struct usb_function *rndis_alloc(struct usb_function_instance *fi) } DECLARE_USB_FUNCTION_INIT(rndis, rndis_alloc_inst, rndis_alloc); +MODULE_DESCRIPTION("RNDIS link function driver"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("David Brownell"); diff --git a/drivers/usb/gadget/function/f_serial.c b/drivers/usb/gadget/function/f_serial.c index a9480b9e312e..0f266bc067f5 100644 --- a/drivers/usb/gadget/function/f_serial.c +++ b/drivers/usb/gadget/function/f_serial.c @@ -236,10 +236,8 @@ static int gser_bind(struct usb_configuration *c, struct usb_function *f) gser_ss_function, gser_ss_function); if (status) goto fail; - dev_dbg(&cdev->gadget->dev, "generic ttyGS%d: %s speed IN/%s OUT/%s\n", + dev_dbg(&cdev->gadget->dev, "generic ttyGS%d: IN/%s OUT/%s\n", gser->port_num, - gadget_is_superspeed(c->cdev->gadget) ? "super" : - gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", gser->port.in->name, gser->port.out->name); return 0; @@ -366,6 +364,12 @@ static void gser_suspend(struct usb_function *f) gserial_suspend(&gser->port); } +static int gser_get_status(struct usb_function *f) +{ + return (f->func_wakeup_armed ? USB_INTRF_STAT_FUNC_RW : 0) | + USB_INTRF_STAT_FUNC_RW_CAP; +} + static struct usb_function *gser_alloc(struct usb_function_instance *fi) { struct f_gser *gser; @@ -389,11 +393,13 @@ static struct usb_function *gser_alloc(struct usb_function_instance *fi) gser->port.func.free_func = gser_free; gser->port.func.resume = gser_resume; gser->port.func.suspend = gser_suspend; + gser->port.func.get_status = gser_get_status; return &gser->port.func; } DECLARE_USB_FUNCTION_INIT(gser, gser_alloc_inst, gser_alloc); +MODULE_DESCRIPTION("generic USB serial function driver"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Al Borchers"); MODULE_AUTHOR("David Brownell"); diff --git a/drivers/usb/gadget/function/f_sourcesink.c b/drivers/usb/gadget/function/f_sourcesink.c index 6803cd60cc6d..ec5fd25020fd 100644 --- a/drivers/usb/gadget/function/f_sourcesink.c +++ b/drivers/usb/gadget/function/f_sourcesink.c @@ -13,10 +13,10 @@ #include <linux/device.h> #include <linux/module.h> #include <linux/usb/composite.h> +#include <linux/usb/func_utils.h> #include <linux/err.h> #include "g_zero.h" -#include "u_f.h" /* * SOURCE/SINK FUNCTION ... a primary testing vehicle for USB peripheral @@ -436,9 +436,7 @@ no_iso: if (ret) return ret; - DBG(cdev, "%s speed %s: IN/%s, OUT/%s, ISO-IN/%s, ISO-OUT/%s\n", - (gadget_is_superspeed(c->cdev->gadget) ? "super" : - (gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full")), + DBG(cdev, "%s: IN/%s, OUT/%s, ISO-IN/%s, ISO-OUT/%s\n", f->name, ss->in_ep->name, ss->out_ep->name, ss->iso_in_ep ? ss->iso_in_ep->name : "<none>", ss->iso_out_ep ? ss->iso_out_ep->name : "<none>"); @@ -1286,4 +1284,5 @@ static void __exit sslb_modexit(void) module_init(sslb_modinit); module_exit(sslb_modexit); +MODULE_DESCRIPTION("USB peripheral source/sink configuration driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/function/f_subset.c b/drivers/usb/gadget/function/f_subset.c index 51c1cae162d9..ea3fdd842462 100644 --- a/drivers/usb/gadget/function/f_subset.c +++ b/drivers/usb/gadget/function/f_subset.c @@ -367,9 +367,7 @@ geth_bind(struct usb_configuration *c, struct usb_function *f) * until we're activated via set_alt(). */ - DBG(cdev, "CDC Subset: %s speed IN/%s OUT/%s\n", - gadget_is_superspeed(c->cdev->gadget) ? "super" : - gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + DBG(cdev, "CDC Subset: IN/%s OUT/%s\n", geth->port.in_ep->name, geth->port.out_ep->name); return 0; @@ -502,5 +500,6 @@ static struct usb_function *geth_alloc(struct usb_function_instance *fi) } DECLARE_USB_FUNCTION_INIT(geth, geth_alloc_inst, geth_alloc); +MODULE_DESCRIPTION("\"CDC Subset\" Ethernet link function driver"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("David Brownell"); diff --git a/drivers/usb/gadget/function/f_tcm.c b/drivers/usb/gadget/function/f_tcm.c index 658e2e21fdd0..6e8804f04baa 100644 --- a/drivers/usb/gadget/function/f_tcm.c +++ b/drivers/usb/gadget/function/f_tcm.c @@ -12,6 +12,7 @@ #include <linux/string.h> #include <linux/configfs.h> #include <linux/ctype.h> +#include <linux/delay.h> #include <linux/usb/ch9.h> #include <linux/usb/composite.h> #include <linux/usb/gadget.h> @@ -19,7 +20,7 @@ #include <scsi/scsi_tcq.h> #include <target/target_core_base.h> #include <target/target_core_fabric.h> -#include <asm/unaligned.h> +#include <linux/unaligned.h> #include "tcm.h" #include "u_tcm.h" @@ -50,7 +51,7 @@ static int bot_enqueue_cmd_cbw(struct f_uas *fu) if (fu->flags & USBG_BOT_CMD_PEND) return 0; - ret = usb_ep_queue(fu->ep_out, fu->cmd.req, GFP_ATOMIC); + ret = usb_ep_queue(fu->ep_out, fu->cmd[0].req, GFP_ATOMIC); if (!ret) fu->flags |= USBG_BOT_CMD_PEND; return ret; @@ -62,10 +63,11 @@ static void bot_status_complete(struct usb_ep *ep, struct usb_request *req) struct f_uas *fu = cmd->fu; transport_generic_free_cmd(&cmd->se_cmd, 0); - if (req->status < 0) { - pr_err("ERR %s(%d)\n", __func__, __LINE__); + if (req->status == -ESHUTDOWN) return; - } + + if (req->status < 0) + pr_err("ERR %s(%d)\n", __func__, __LINE__); /* CSW completed, wait for next CBW */ bot_enqueue_cmd_cbw(fu); @@ -136,7 +138,7 @@ static void bot_send_bad_status(struct usbg_cmd *cmd) } req->complete = bot_err_compl; req->context = cmd; - req->buf = fu->cmd.buf; + req->buf = fu->cmd[0].buf; usb_ep_queue(ep, req, GFP_KERNEL); } else { bot_enqueue_sense_code(fu, cmd); @@ -196,6 +198,11 @@ static void bot_read_compl(struct usb_ep *ep, struct usb_request *req) if (req->status < 0) pr_err("ERR %s(%d)\n", __func__, __LINE__); + if (req->status == -ESHUTDOWN) { + transport_generic_free_cmd(&cmd->se_cmd, 0); + return; + } + bot_send_status(cmd, true); } @@ -244,11 +251,8 @@ static int usbg_prepare_w_request(struct usbg_cmd *, struct usb_request *); static int bot_send_write_request(struct usbg_cmd *cmd) { struct f_uas *fu = cmd->fu; - struct se_cmd *se_cmd = &cmd->se_cmd; - struct usb_gadget *gadget = fuas_to_gadget(fu); int ret; - init_completion(&cmd->write_complete); cmd->fu = fu; if (!cmd->data_len) { @@ -256,22 +260,6 @@ static int bot_send_write_request(struct usbg_cmd *cmd) return -EINVAL; } - if (!gadget->sg_supported) { - cmd->data_buf = kmalloc(se_cmd->data_length, GFP_KERNEL); - if (!cmd->data_buf) - return -ENOMEM; - - fu->bot_req_out->buf = cmd->data_buf; - } else { - fu->bot_req_out->buf = NULL; - fu->bot_req_out->num_sgs = se_cmd->t_data_nents; - fu->bot_req_out->sg = se_cmd->t_data_sg; - } - - fu->bot_req_out->complete = usbg_data_write_cmpl; - fu->bot_req_out->length = se_cmd->data_length; - fu->bot_req_out->context = cmd; - ret = usbg_prepare_w_request(cmd, fu->bot_req_out); if (ret) goto cleanup; @@ -279,8 +267,6 @@ static int bot_send_write_request(struct usbg_cmd *cmd) if (ret) pr_err("%s(%d)\n", __func__, __LINE__); - wait_for_completion(&cmd->write_complete); - target_execute_cmd(se_cmd); cleanup: return ret; } @@ -292,14 +278,31 @@ static void bot_cmd_complete(struct usb_ep *ep, struct usb_request *req) struct f_uas *fu = req->context; int ret; + if (req->status == -ESHUTDOWN) + return; + fu->flags &= ~USBG_BOT_CMD_PEND; - if (req->status < 0) + if (req->status < 0) { + struct usb_gadget *gadget = fuas_to_gadget(fu); + + dev_err(&gadget->dev, "BOT command req err (%d)\n", req->status); + bot_enqueue_cmd_cbw(fu); return; + } ret = bot_submit_command(fu, req->buf, req->actual); - if (ret) + if (ret) { pr_err("%s(%d): %d\n", __func__, __LINE__, ret); + if (!(fu->flags & USBG_BOT_WEDGED)) + usb_ep_set_wedge(fu->ep_in); + + fu->flags |= USBG_BOT_WEDGED; + bot_enqueue_cmd_cbw(fu); + } else if (fu->flags & USBG_BOT_WEDGED) { + fu->flags &= ~USBG_BOT_WEDGED; + usb_ep_clear_halt(fu->ep_in); + } } static int bot_prepare_reqs(struct f_uas *fu) @@ -314,8 +317,8 @@ static int bot_prepare_reqs(struct f_uas *fu) if (!fu->bot_req_out) goto err_out; - fu->cmd.req = usb_ep_alloc_request(fu->ep_out, GFP_KERNEL); - if (!fu->cmd.req) + fu->cmd[0].req = usb_ep_alloc_request(fu->ep_out, GFP_KERNEL); + if (!fu->cmd[0].req) goto err_cmd; fu->bot_status.req = usb_ep_alloc_request(fu->ep_in, GFP_KERNEL); @@ -327,27 +330,27 @@ static int bot_prepare_reqs(struct f_uas *fu) fu->bot_status.req->complete = bot_status_complete; fu->bot_status.csw.Signature = cpu_to_le32(US_BULK_CS_SIGN); - fu->cmd.buf = kmalloc(fu->ep_out->maxpacket, GFP_KERNEL); - if (!fu->cmd.buf) + fu->cmd[0].buf = kmalloc(fu->ep_out->maxpacket, GFP_KERNEL); + if (!fu->cmd[0].buf) goto err_buf; - fu->cmd.req->complete = bot_cmd_complete; - fu->cmd.req->buf = fu->cmd.buf; - fu->cmd.req->length = fu->ep_out->maxpacket; - fu->cmd.req->context = fu; + fu->cmd[0].req->complete = bot_cmd_complete; + fu->cmd[0].req->buf = fu->cmd[0].buf; + fu->cmd[0].req->length = fu->ep_out->maxpacket; + fu->cmd[0].req->context = fu; ret = bot_enqueue_cmd_cbw(fu); if (ret) goto err_queue; return 0; err_queue: - kfree(fu->cmd.buf); - fu->cmd.buf = NULL; + kfree(fu->cmd[0].buf); + fu->cmd[0].buf = NULL; err_buf: usb_ep_free_request(fu->ep_in, fu->bot_status.req); err_sts: - usb_ep_free_request(fu->ep_out, fu->cmd.req); - fu->cmd.req = NULL; + usb_ep_free_request(fu->ep_out, fu->cmd[0].req); + fu->cmd[0].req = NULL; err_cmd: usb_ep_free_request(fu->ep_out, fu->bot_req_out); fu->bot_req_out = NULL; @@ -372,16 +375,16 @@ static void bot_cleanup_old_alt(struct f_uas *fu) usb_ep_free_request(fu->ep_in, fu->bot_req_in); usb_ep_free_request(fu->ep_out, fu->bot_req_out); - usb_ep_free_request(fu->ep_out, fu->cmd.req); + usb_ep_free_request(fu->ep_out, fu->cmd[0].req); usb_ep_free_request(fu->ep_in, fu->bot_status.req); - kfree(fu->cmd.buf); + kfree(fu->cmd[0].buf); fu->bot_req_in = NULL; fu->bot_req_out = NULL; - fu->cmd.req = NULL; + fu->cmd[0].req = NULL; fu->bot_status.req = NULL; - fu->cmd.buf = NULL; + fu->cmd[0].buf = NULL; } static void bot_set_alt(struct f_uas *fu) @@ -441,14 +444,10 @@ static int usbg_bot_setup(struct usb_function *f, pr_err("No LUNs configured?\n"); return -EINVAL; } - /* - * If 4 LUNs are present we return 3 i.e. LUN 0..3 can be - * accessed. The upper limit is 0xf - */ luns--; - if (luns > 0xf) { + if (luns > US_BULK_MAX_LUN_LIMIT) { pr_info_once("Limiting the number of luns to 16\n"); - luns = 0xf; + luns = US_BULK_MAX_LUN_LIMIT; } ret_lun = cdev->req->buf; *ret_lun = luns; @@ -457,6 +456,11 @@ static int usbg_bot_setup(struct usb_function *f, case US_BULK_RESET_REQUEST: /* XXX maybe we should remove previous requests for IN + OUT */ + if (fu->flags & USBG_BOT_WEDGED) { + fu->flags &= ~USBG_BOT_WEDGED; + usb_ep_clear_halt(fu->ep_in); + } + bot_enqueue_cmd_cbw(fu); return 0; } @@ -465,6 +469,45 @@ static int usbg_bot_setup(struct usb_function *f, /* Start uas.c code */ +static int tcm_to_uasp_response(enum tcm_tmrsp_table code) +{ + switch (code) { + case TMR_FUNCTION_FAILED: + return RC_TMF_FAILED; + case TMR_FUNCTION_COMPLETE: + case TMR_TASK_DOES_NOT_EXIST: + return RC_TMF_COMPLETE; + case TMR_LUN_DOES_NOT_EXIST: + return RC_INCORRECT_LUN; + case TMR_FUNCTION_REJECTED: + case TMR_TASK_MGMT_FUNCTION_NOT_SUPPORTED: + default: + return RC_TMF_NOT_SUPPORTED; + } +} + +static unsigned char uasp_to_tcm_func(int code) +{ + switch (code) { + case TMF_ABORT_TASK: + return TMR_ABORT_TASK; + case TMF_ABORT_TASK_SET: + return TMR_ABORT_TASK_SET; + case TMF_CLEAR_TASK_SET: + return TMR_CLEAR_TASK_SET; + case TMF_LOGICAL_UNIT_RESET: + return TMR_LUN_RESET; + case TMF_CLEAR_ACA: + return TMR_CLEAR_ACA; + case TMF_I_T_NEXUS_RESET: + case TMF_QUERY_TASK: + case TMF_QUERY_TASK_SET: + case TMF_QUERY_ASYNC_EVENT: + default: + return TMR_UNKNOWN; + } +} + static void uasp_cleanup_one_stream(struct f_uas *fu, struct uas_stream *stream) { /* We have either all three allocated or none */ @@ -482,10 +525,14 @@ static void uasp_cleanup_one_stream(struct f_uas *fu, struct uas_stream *stream) static void uasp_free_cmdreq(struct f_uas *fu) { - usb_ep_free_request(fu->ep_cmd, fu->cmd.req); - kfree(fu->cmd.buf); - fu->cmd.req = NULL; - fu->cmd.buf = NULL; + int i; + + for (i = 0; i < USBG_NUM_CMDS; i++) { + usb_ep_free_request(fu->ep_cmd, fu->cmd[i].req); + kfree(fu->cmd[i].buf); + fu->cmd[i].req = NULL; + fu->cmd[i].buf = NULL; + } } static void uasp_cleanup_old_alt(struct f_uas *fu) @@ -500,7 +547,7 @@ static void uasp_cleanup_old_alt(struct f_uas *fu) usb_ep_disable(fu->ep_status); usb_ep_disable(fu->ep_cmd); - for (i = 0; i < UASP_SS_EP_COMP_NUM_STREAMS; i++) + for (i = 0; i < USBG_NUM_CMDS; i++) uasp_cleanup_one_stream(fu, &fu->stream[i]); uasp_free_cmdreq(fu); } @@ -512,7 +559,7 @@ static int uasp_prepare_r_request(struct usbg_cmd *cmd) struct se_cmd *se_cmd = &cmd->se_cmd; struct f_uas *fu = cmd->fu; struct usb_gadget *gadget = fuas_to_gadget(fu); - struct uas_stream *stream = cmd->stream; + struct uas_stream *stream = &fu->stream[se_cmd->map_tag]; if (!gadget->sg_supported) { cmd->data_buf = kmalloc(se_cmd->data_length, GFP_ATOMIC); @@ -532,6 +579,7 @@ static int uasp_prepare_r_request(struct usbg_cmd *cmd) } stream->req_in->is_last = 1; + stream->req_in->stream_id = cmd->tag; stream->req_in->complete = uasp_status_data_cmpl; stream->req_in->length = se_cmd->data_length; stream->req_in->context = cmd; @@ -544,7 +592,7 @@ static void uasp_prepare_status(struct usbg_cmd *cmd) { struct se_cmd *se_cmd = &cmd->se_cmd; struct sense_iu *iu = &cmd->sense_iu; - struct uas_stream *stream = cmd->stream; + struct uas_stream *stream = &cmd->fu->stream[se_cmd->map_tag]; cmd->state = UASP_QUEUE_COMMAND; iu->iu_id = IU_ID_STATUS; @@ -556,20 +604,76 @@ static void uasp_prepare_status(struct usbg_cmd *cmd) iu->len = cpu_to_be16(se_cmd->scsi_sense_length); iu->status = se_cmd->scsi_status; stream->req_status->is_last = 1; + stream->req_status->stream_id = cmd->tag; stream->req_status->context = cmd; stream->req_status->length = se_cmd->scsi_sense_length + 16; stream->req_status->buf = iu; stream->req_status->complete = uasp_status_data_cmpl; } +static void uasp_prepare_response(struct usbg_cmd *cmd) +{ + struct se_cmd *se_cmd = &cmd->se_cmd; + struct response_iu *rsp_iu = &cmd->response_iu; + struct uas_stream *stream = &cmd->fu->stream[se_cmd->map_tag]; + + cmd->state = UASP_QUEUE_COMMAND; + rsp_iu->iu_id = IU_ID_RESPONSE; + rsp_iu->tag = cpu_to_be16(cmd->tag); + + if (cmd->tmr_rsp != RC_RESPONSE_UNKNOWN) + rsp_iu->response_code = cmd->tmr_rsp; + else + rsp_iu->response_code = + tcm_to_uasp_response(se_cmd->se_tmr_req->response); + + /* + * The UASP driver must support all the task management functions listed + * in Table 20 of UAS-r04. To remain compliant while indicate that the + * TMR did not go through, report RC_TMF_FAILED instead of + * RC_TMF_NOT_SUPPORTED and print a warning to the user. + */ + switch (cmd->tmr_func) { + case TMF_ABORT_TASK: + case TMF_ABORT_TASK_SET: + case TMF_CLEAR_TASK_SET: + case TMF_LOGICAL_UNIT_RESET: + case TMF_CLEAR_ACA: + case TMF_I_T_NEXUS_RESET: + case TMF_QUERY_TASK: + case TMF_QUERY_TASK_SET: + case TMF_QUERY_ASYNC_EVENT: + if (rsp_iu->response_code == RC_TMF_NOT_SUPPORTED) { + struct usb_gadget *gadget = fuas_to_gadget(cmd->fu); + + dev_warn(&gadget->dev, "TMF function %d not supported\n", + cmd->tmr_func); + rsp_iu->response_code = RC_TMF_FAILED; + } + break; + default: + break; + } + + stream->req_status->is_last = 1; + stream->req_status->stream_id = cmd->tag; + stream->req_status->context = cmd; + stream->req_status->length = sizeof(struct response_iu); + stream->req_status->buf = rsp_iu; + stream->req_status->complete = uasp_status_data_cmpl; +} + +static void usbg_release_cmd(struct se_cmd *se_cmd); +static int uasp_send_tm_response(struct usbg_cmd *cmd); + static void uasp_status_data_cmpl(struct usb_ep *ep, struct usb_request *req) { struct usbg_cmd *cmd = req->context; - struct uas_stream *stream = cmd->stream; struct f_uas *fu = cmd->fu; + struct uas_stream *stream = &fu->stream[cmd->se_cmd.map_tag]; int ret; - if (req->status < 0) + if (req->status == -ESHUTDOWN) goto cleanup; switch (cmd->state) { @@ -600,8 +704,37 @@ static void uasp_status_data_cmpl(struct usb_ep *ep, struct usb_request *req) break; case UASP_QUEUE_COMMAND: - transport_generic_free_cmd(&cmd->se_cmd, 0); - usb_ep_queue(fu->ep_cmd, fu->cmd.req, GFP_ATOMIC); + /* + * Overlapped command detected and cancelled. + * So send overlapped attempted status. + */ + if (cmd->tmr_rsp == RC_OVERLAPPED_TAG && + req->status == -ECONNRESET) { + uasp_send_tm_response(cmd); + return; + } + + hash_del(&stream->node); + + /* + * If no command submitted to target core here, just free the + * bitmap index. This is for the cases where f_tcm handles + * status response instead of the target core. + */ + if (cmd->tmr_rsp != RC_OVERLAPPED_TAG && + cmd->tmr_rsp != RC_RESPONSE_UNKNOWN) { + struct se_session *se_sess; + + se_sess = fu->tpg->tpg_nexus->tvn_se_sess; + sbitmap_queue_clear(&se_sess->sess_tag_pool, + cmd->se_cmd.map_tag, + cmd->se_cmd.map_cpu); + } else { + transport_generic_free_cmd(&cmd->se_cmd, 0); + } + + usb_ep_queue(fu->ep_cmd, cmd->req, GFP_ATOMIC); + complete(&stream->cmd_completion); break; default: @@ -610,27 +743,38 @@ static void uasp_status_data_cmpl(struct usb_ep *ep, struct usb_request *req) return; cleanup: + hash_del(&stream->node); transport_generic_free_cmd(&cmd->se_cmd, 0); } static int uasp_send_status_response(struct usbg_cmd *cmd) { struct f_uas *fu = cmd->fu; - struct uas_stream *stream = cmd->stream; + struct uas_stream *stream = &fu->stream[cmd->se_cmd.map_tag]; struct sense_iu *iu = &cmd->sense_iu; iu->tag = cpu_to_be16(cmd->tag); - stream->req_status->complete = uasp_status_data_cmpl; - stream->req_status->context = cmd; cmd->fu = fu; uasp_prepare_status(cmd); return usb_ep_queue(fu->ep_status, stream->req_status, GFP_ATOMIC); } +static int uasp_send_tm_response(struct usbg_cmd *cmd) +{ + struct f_uas *fu = cmd->fu; + struct uas_stream *stream = &fu->stream[cmd->se_cmd.map_tag]; + struct response_iu *iu = &cmd->response_iu; + + iu->tag = cpu_to_be16(cmd->tag); + cmd->fu = fu; + uasp_prepare_response(cmd); + return usb_ep_queue(fu->ep_status, stream->req_status, GFP_ATOMIC); +} + static int uasp_send_read_response(struct usbg_cmd *cmd) { struct f_uas *fu = cmd->fu; - struct uas_stream *stream = cmd->stream; + struct uas_stream *stream = &fu->stream[cmd->se_cmd.map_tag]; struct sense_iu *iu = &cmd->sense_iu; int ret; @@ -674,11 +818,10 @@ static int uasp_send_write_request(struct usbg_cmd *cmd) { struct f_uas *fu = cmd->fu; struct se_cmd *se_cmd = &cmd->se_cmd; - struct uas_stream *stream = cmd->stream; + struct uas_stream *stream = &fu->stream[se_cmd->map_tag]; struct sense_iu *iu = &cmd->sense_iu; int ret; - init_completion(&cmd->write_complete); cmd->fu = fu; iu->tag = cpu_to_be16(cmd->tag); @@ -710,36 +853,31 @@ static int uasp_send_write_request(struct usbg_cmd *cmd) pr_err("%s(%d)\n", __func__, __LINE__); } - wait_for_completion(&cmd->write_complete); - target_execute_cmd(se_cmd); cleanup: return ret; } -static int usbg_submit_command(struct f_uas *, void *, unsigned int); +static int usbg_submit_command(struct f_uas *, struct usb_request *); static void uasp_cmd_complete(struct usb_ep *ep, struct usb_request *req) { struct f_uas *fu = req->context; - int ret; - if (req->status < 0) + if (req->status == -ESHUTDOWN) return; - ret = usbg_submit_command(fu, req->buf, req->actual); - /* - * Once we tune for performance enqueue the command req here again so - * we can receive a second command while we processing this one. Pay - * attention to properly sync STAUS endpoint with DATA IN + OUT so you - * don't break HS. - */ - if (!ret) + if (req->status < 0) { + usb_ep_queue(fu->ep_cmd, req, GFP_ATOMIC); return; - usb_ep_queue(fu->ep_cmd, fu->cmd.req, GFP_ATOMIC); + } + + usbg_submit_command(fu, req); } static int uasp_alloc_stream_res(struct f_uas *fu, struct uas_stream *stream) { + init_completion(&stream->cmd_completion); + stream->req_in = usb_ep_alloc_request(fu->ep_in, GFP_KERNEL); if (!stream->req_in) goto out; @@ -764,66 +902,48 @@ out: return -ENOMEM; } -static int uasp_alloc_cmd(struct f_uas *fu) +static int uasp_alloc_cmd(struct f_uas *fu, int i) { - fu->cmd.req = usb_ep_alloc_request(fu->ep_cmd, GFP_KERNEL); - if (!fu->cmd.req) + fu->cmd[i].req = usb_ep_alloc_request(fu->ep_cmd, GFP_KERNEL); + if (!fu->cmd[i].req) goto err; - fu->cmd.buf = kmalloc(fu->ep_cmd->maxpacket, GFP_KERNEL); - if (!fu->cmd.buf) + fu->cmd[i].buf = kmalloc(fu->ep_cmd->maxpacket, GFP_KERNEL); + if (!fu->cmd[i].buf) goto err_buf; - fu->cmd.req->complete = uasp_cmd_complete; - fu->cmd.req->buf = fu->cmd.buf; - fu->cmd.req->length = fu->ep_cmd->maxpacket; - fu->cmd.req->context = fu; + fu->cmd[i].req->complete = uasp_cmd_complete; + fu->cmd[i].req->buf = fu->cmd[i].buf; + fu->cmd[i].req->length = fu->ep_cmd->maxpacket; + fu->cmd[i].req->context = fu; return 0; err_buf: - usb_ep_free_request(fu->ep_cmd, fu->cmd.req); + usb_ep_free_request(fu->ep_cmd, fu->cmd[i].req); err: return -ENOMEM; } -static void uasp_setup_stream_res(struct f_uas *fu, int max_streams) -{ - int i; - - for (i = 0; i < max_streams; i++) { - struct uas_stream *s = &fu->stream[i]; - - s->req_in->stream_id = i + 1; - s->req_out->stream_id = i + 1; - s->req_status->stream_id = i + 1; - } -} - static int uasp_prepare_reqs(struct f_uas *fu) { int ret; int i; - int max_streams; - if (fu->flags & USBG_USE_STREAMS) - max_streams = UASP_SS_EP_COMP_NUM_STREAMS; - else - max_streams = 1; - - for (i = 0; i < max_streams; i++) { + for (i = 0; i < USBG_NUM_CMDS; i++) { ret = uasp_alloc_stream_res(fu, &fu->stream[i]); if (ret) goto err_cleanup; } - ret = uasp_alloc_cmd(fu); - if (ret) - goto err_free_stream; - uasp_setup_stream_res(fu, max_streams); + for (i = 0; i < USBG_NUM_CMDS; i++) { + ret = uasp_alloc_cmd(fu, i); + if (ret) + goto err_free_stream; - ret = usb_ep_queue(fu->ep_cmd, fu->cmd.req, GFP_ATOMIC); - if (ret) - goto err_free_stream; + ret = usb_ep_queue(fu->ep_cmd, fu->cmd[i].req, GFP_ATOMIC); + if (ret) + goto err_free_stream; + } return 0; @@ -914,6 +1034,8 @@ static int get_cmd_dir(const unsigned char *cdb) case READ_TOC: case READ_FORMAT_CAPACITIES: case REQUEST_SENSE: + case ATA_12: + case ATA_16: ret = DMA_FROM_DEVICE; break; @@ -957,7 +1079,18 @@ static void usbg_data_write_cmpl(struct usb_ep *ep, struct usb_request *req) struct usbg_cmd *cmd = req->context; struct se_cmd *se_cmd = &cmd->se_cmd; - if (req->status < 0) { + cmd->state = UASP_QUEUE_COMMAND; + + if (req->status == -ESHUTDOWN) { + struct uas_stream *stream = &cmd->fu->stream[se_cmd->map_tag]; + + hash_del(&stream->node); + target_put_sess_cmd(se_cmd); + transport_generic_free_cmd(&cmd->se_cmd, 0); + return; + } + + if (req->status) { pr_err("%s() state %d transfer failed\n", __func__, cmd->state); goto cleanup; } @@ -969,11 +1102,22 @@ static void usbg_data_write_cmpl(struct usb_ep *ep, struct usb_request *req) se_cmd->data_length); } - complete(&cmd->write_complete); + cmd->flags |= USBG_CMD_PENDING_DATA_WRITE; + queue_work(cmd->fu->tpg->workqueue, &cmd->work); return; cleanup: - transport_generic_free_cmd(&cmd->se_cmd, 0); + target_put_sess_cmd(se_cmd); + + /* Command was aborted due to overlapped tag */ + if (cmd->state == UASP_QUEUE_COMMAND && + cmd->tmr_rsp == RC_OVERLAPPED_TAG) { + uasp_send_tm_response(cmd); + return; + } + + transport_send_check_condition_and_sense(se_cmd, + TCM_CHECK_CONDITION_ABORT_CMD, 0); } static int usbg_prepare_w_request(struct usbg_cmd *cmd, struct usb_request *req) @@ -995,9 +1139,12 @@ static int usbg_prepare_w_request(struct usbg_cmd *cmd, struct usb_request *req) } req->is_last = 1; + req->stream_id = cmd->tag; req->complete = usbg_data_write_cmpl; req->length = se_cmd->data_length; req->context = cmd; + + cmd->state = UASP_SEND_STATUS; return 0; } @@ -1037,36 +1184,153 @@ static int usbg_send_read_response(struct se_cmd *se_cmd) return uasp_send_read_response(cmd); } -static void usbg_cmd_work(struct work_struct *work) +static void usbg_aborted_task(struct se_cmd *se_cmd); + +static void usbg_submit_tmr(struct usbg_cmd *cmd) +{ + struct se_session *se_sess; + struct se_cmd *se_cmd; + int flags = TARGET_SCF_ACK_KREF; + + se_cmd = &cmd->se_cmd; + se_sess = cmd->fu->tpg->tpg_nexus->tvn_se_sess; + + target_submit_tmr(se_cmd, se_sess, + cmd->response_iu.add_response_info, + cmd->unpacked_lun, NULL, uasp_to_tcm_func(cmd->tmr_func), + GFP_ATOMIC, cmd->tag, flags); +} + +static void usbg_submit_cmd(struct usbg_cmd *cmd) { - struct usbg_cmd *cmd = container_of(work, struct usbg_cmd, work); struct se_cmd *se_cmd; struct tcm_usbg_nexus *tv_nexus; struct usbg_tpg *tpg; int dir, flags = (TARGET_SCF_UNKNOWN_SIZE | TARGET_SCF_ACK_KREF); + /* + * Note: each command will spawn its own process, and each stage of the + * command is processed sequentially. Should this no longer be the case, + * locking is needed. + */ + if (cmd->flags & USBG_CMD_PENDING_DATA_WRITE) { + target_execute_cmd(&cmd->se_cmd); + cmd->flags &= ~USBG_CMD_PENDING_DATA_WRITE; + return; + } + se_cmd = &cmd->se_cmd; tpg = cmd->fu->tpg; tv_nexus = tpg->tpg_nexus; dir = get_cmd_dir(cmd->cmd_buf); - if (dir < 0) { - __target_init_cmd(se_cmd, - tv_nexus->tvn_se_sess->se_tpg->se_tpg_tfo, - tv_nexus->tvn_se_sess, cmd->data_len, DMA_NONE, - cmd->prio_attr, cmd->sense_iu.sense, - cmd->unpacked_lun); + if (dir < 0) goto out; - } target_submit_cmd(se_cmd, tv_nexus->tvn_se_sess, cmd->cmd_buf, cmd->sense_iu.sense, cmd->unpacked_lun, 0, cmd->prio_attr, dir, flags); + return; out: + __target_init_cmd(se_cmd, + tv_nexus->tvn_se_sess->se_tpg->se_tpg_tfo, + tv_nexus->tvn_se_sess, cmd->data_len, DMA_NONE, + cmd->prio_attr, cmd->sense_iu.sense, + cmd->unpacked_lun, NULL); transport_send_check_condition_and_sense(se_cmd, - TCM_UNSUPPORTED_SCSI_OPCODE, 1); - transport_generic_free_cmd(&cmd->se_cmd, 0); + TCM_UNSUPPORTED_SCSI_OPCODE, 0); +} + +static void usbg_cmd_work(struct work_struct *work) +{ + struct usbg_cmd *cmd = container_of(work, struct usbg_cmd, work); + + /* + * Failure is detected by f_tcm here. Skip submitting the command to the + * target core if we already know the failing response and send the usb + * response to the host directly. + */ + if (cmd->tmr_rsp != RC_RESPONSE_UNKNOWN) + goto skip; + + if (cmd->tmr_func) + usbg_submit_tmr(cmd); + else + usbg_submit_cmd(cmd); + + return; + +skip: + if (cmd->tmr_rsp == RC_OVERLAPPED_TAG) { + struct f_uas *fu = cmd->fu; + struct se_session *se_sess; + struct uas_stream *stream = NULL; + struct hlist_node *tmp; + struct usbg_cmd *active_cmd = NULL; + + se_sess = cmd->fu->tpg->tpg_nexus->tvn_se_sess; + + hash_for_each_possible_safe(fu->stream_hash, stream, tmp, node, cmd->tag) { + int i = stream - &fu->stream[0]; + + active_cmd = &((struct usbg_cmd *)se_sess->sess_cmd_map)[i]; + if (active_cmd->tag == cmd->tag) + break; + } + + /* Sanity check */ + if (!stream || (active_cmd && active_cmd->tag != cmd->tag)) { + usbg_submit_command(cmd->fu, cmd->req); + return; + } + + reinit_completion(&stream->cmd_completion); + + /* + * A UASP command consists of the command, data, and status + * stages, each operating sequentially from different endpoints. + * + * Each USB endpoint operates independently, and depending on + * hardware implementation, a completion callback for a transfer + * from one endpoint may not reflect the order of completion on + * the wire. This is particularly true for devices with + * endpoints that have independent interrupts and event buffers. + * + * The driver must still detect misbehaving hosts and respond + * with an overlap status. To reduce false overlap failures, + * allow the active and matching stream ID a brief 1ms to + * complete before responding with an overlap command failure. + * Overlap failure should be rare. + */ + wait_for_completion_timeout(&stream->cmd_completion, msecs_to_jiffies(1)); + + /* If the previous stream is completed, retry the command. */ + if (!hash_hashed(&stream->node)) { + usbg_submit_command(cmd->fu, cmd->req); + return; + } + + /* + * The command isn't submitted to the target core, so we're safe + * to remove the bitmap index from the session tag pool. + */ + sbitmap_queue_clear(&se_sess->sess_tag_pool, + cmd->se_cmd.map_tag, + cmd->se_cmd.map_cpu); + + /* + * Overlap command tag detected. Cancel any pending transfer of + * the command submitted to target core. + */ + active_cmd->tmr_rsp = RC_OVERLAPPED_TAG; + usbg_aborted_task(&active_cmd->se_cmd); + + /* Send the response after the transfer is aborted. */ + return; + } + + uasp_send_tm_response(cmd); } static struct usbg_cmd *usbg_get_cmd(struct f_uas *fu, @@ -1084,6 +1348,7 @@ static struct usbg_cmd *usbg_get_cmd(struct f_uas *fu, memset(cmd, 0, sizeof(*cmd)); cmd->se_cmd.map_tag = tag; cmd->se_cmd.map_cpu = cpu; + cmd->se_cmd.cpuid = cpu; cmd->se_cmd.tag = cmd->tag = scsi_tag; cmd->fu = fu; @@ -1092,49 +1357,81 @@ static struct usbg_cmd *usbg_get_cmd(struct f_uas *fu, static void usbg_release_cmd(struct se_cmd *); -static int usbg_submit_command(struct f_uas *fu, - void *cmdbuf, unsigned int len) +static int usbg_submit_command(struct f_uas *fu, struct usb_request *req) { - struct command_iu *cmd_iu = cmdbuf; + struct iu *iu = req->buf; struct usbg_cmd *cmd; struct usbg_tpg *tpg = fu->tpg; struct tcm_usbg_nexus *tv_nexus; + struct uas_stream *stream; + struct hlist_node *tmp; + struct command_iu *cmd_iu; u32 cmd_len; u16 scsi_tag; - if (cmd_iu->iu_id != IU_ID_COMMAND) { - pr_err("Unsupported type %d\n", cmd_iu->iu_id); - return -EINVAL; - } - tv_nexus = tpg->tpg_nexus; if (!tv_nexus) { pr_err("Missing nexus, ignoring command\n"); return -EINVAL; } - cmd_len = (cmd_iu->len & ~0x3) + 16; - if (cmd_len > USBG_MAX_CMD) - return -EINVAL; - - scsi_tag = be16_to_cpup(&cmd_iu->tag); + scsi_tag = be16_to_cpup(&iu->tag); cmd = usbg_get_cmd(fu, tv_nexus, scsi_tag); if (IS_ERR(cmd)) { pr_err("usbg_get_cmd failed\n"); return -ENOMEM; } - memcpy(cmd->cmd_buf, cmd_iu->cdb, cmd_len); - if (fu->flags & USBG_USE_STREAMS) { - if (cmd->tag > UASP_SS_EP_COMP_NUM_STREAMS) - goto err; - if (!cmd->tag) - cmd->stream = &fu->stream[0]; - else - cmd->stream = &fu->stream[cmd->tag - 1]; - } else { - cmd->stream = &fu->stream[0]; + cmd->req = req; + cmd->fu = fu; + cmd->tag = scsi_tag; + cmd->se_cmd.tag = scsi_tag; + cmd->tmr_func = 0; + cmd->tmr_rsp = RC_RESPONSE_UNKNOWN; + cmd->flags = 0; + + cmd_iu = (struct command_iu *)iu; + + /* Command and Task Management IUs share the same LUN offset */ + cmd->unpacked_lun = scsilun_to_int(&cmd_iu->lun); + + if (iu->iu_id != IU_ID_COMMAND && iu->iu_id != IU_ID_TASK_MGMT) { + cmd->tmr_rsp = RC_INVALID_INFO_UNIT; + goto skip; + } + + hash_for_each_possible_safe(fu->stream_hash, stream, tmp, node, scsi_tag) { + struct usbg_cmd *active_cmd; + struct se_session *se_sess; + int i = stream - &fu->stream[0]; + + se_sess = cmd->fu->tpg->tpg_nexus->tvn_se_sess; + active_cmd = &((struct usbg_cmd *)se_sess->sess_cmd_map)[i]; + + if (active_cmd->tag == scsi_tag) { + cmd->tmr_rsp = RC_OVERLAPPED_TAG; + goto skip; + } + } + + stream = &fu->stream[cmd->se_cmd.map_tag]; + hash_add(fu->stream_hash, &stream->node, scsi_tag); + + if (iu->iu_id == IU_ID_TASK_MGMT) { + struct task_mgmt_iu *tm_iu; + + tm_iu = (struct task_mgmt_iu *)iu; + cmd->tmr_func = tm_iu->function; + goto skip; + } + + cmd_len = (cmd_iu->len & ~0x3) + 16; + if (cmd_len > USBG_MAX_CMD) { + target_free_tag(tv_nexus->tvn_se_sess, &cmd->se_cmd); + hash_del(&stream->node); + return -EINVAL; } + memcpy(cmd->cmd_buf, cmd_iu->cdb, cmd_len); switch (cmd_iu->prio_attr & 0x7) { case UAS_HEAD_TAG: @@ -1155,15 +1452,11 @@ static int usbg_submit_command(struct f_uas *fu, break; } - cmd->unpacked_lun = scsilun_to_int(&cmd_iu->lun); - +skip: INIT_WORK(&cmd->work, usbg_cmd_work); queue_work(tpg->workqueue, &cmd->work); return 0; -err: - usbg_release_cmd(&cmd->se_cmd); - return -EINVAL; } static void bot_cmd_work(struct work_struct *work) @@ -1172,30 +1465,40 @@ static void bot_cmd_work(struct work_struct *work) struct se_cmd *se_cmd; struct tcm_usbg_nexus *tv_nexus; struct usbg_tpg *tpg; + int flags = TARGET_SCF_ACK_KREF; int dir; + /* + * Note: each command will spawn its own process, and each stage of the + * command is processed sequentially. Should this no longer be the case, + * locking is needed. + */ + if (cmd->flags & USBG_CMD_PENDING_DATA_WRITE) { + target_execute_cmd(&cmd->se_cmd); + cmd->flags &= ~USBG_CMD_PENDING_DATA_WRITE; + return; + } + se_cmd = &cmd->se_cmd; tpg = cmd->fu->tpg; tv_nexus = tpg->tpg_nexus; dir = get_cmd_dir(cmd->cmd_buf); - if (dir < 0) { - __target_init_cmd(se_cmd, - tv_nexus->tvn_se_sess->se_tpg->se_tpg_tfo, - tv_nexus->tvn_se_sess, cmd->data_len, DMA_NONE, - cmd->prio_attr, cmd->sense_iu.sense, - cmd->unpacked_lun); + if (dir < 0) goto out; - } target_submit_cmd(se_cmd, tv_nexus->tvn_se_sess, cmd->cmd_buf, cmd->sense_iu.sense, cmd->unpacked_lun, - cmd->data_len, cmd->prio_attr, dir, 0); + cmd->data_len, cmd->prio_attr, dir, flags); return; out: + __target_init_cmd(se_cmd, + tv_nexus->tvn_se_sess->se_tpg->se_tpg_tfo, + tv_nexus->tvn_se_sess, cmd->data_len, DMA_NONE, + cmd->prio_attr, cmd->sense_iu.sense, + cmd->unpacked_lun, NULL); transport_send_check_condition_and_sense(se_cmd, - TCM_UNSUPPORTED_SCSI_OPCODE, 1); - transport_generic_free_cmd(&cmd->se_cmd, 0); + TCM_UNSUPPORTED_SCSI_OPCODE, 0); } static int bot_submit_command(struct f_uas *fu, @@ -1239,6 +1542,7 @@ static int bot_submit_command(struct f_uas *fu, cmd->is_read = cbw->Flags & US_BULK_FLAG_IN ? 1 : 0; cmd->data_len = le32_to_cpu(cbw->DataTransferLength); cmd->se_cmd.tag = le32_to_cpu(cmd->bot_tag); + cmd->flags = 0; INIT_WORK(&cmd->work, bot_cmd_work); queue_work(tpg->workqueue, &cmd->work); @@ -1253,11 +1557,6 @@ static int usbg_check_true(struct se_portal_group *se_tpg) return 1; } -static int usbg_check_false(struct se_portal_group *se_tpg) -{ - return 0; -} - static char *usbg_get_fabric_wwn(struct se_portal_group *se_tpg) { struct usbg_tpg *tpg = container_of(se_tpg, @@ -1274,41 +1573,44 @@ static u16 usbg_get_tag(struct se_portal_group *se_tpg) return tpg->tport_tpgt; } -static u32 usbg_tpg_get_inst_index(struct se_portal_group *se_tpg) -{ - return 1; -} - static void usbg_release_cmd(struct se_cmd *se_cmd) { struct usbg_cmd *cmd = container_of(se_cmd, struct usbg_cmd, se_cmd); struct se_session *se_sess = se_cmd->se_sess; + cmd->tag = 0; kfree(cmd->data_buf); target_free_tag(se_sess, se_cmd); } -static u32 usbg_sess_get_index(struct se_session *se_sess) +static void usbg_queue_tm_rsp(struct se_cmd *se_cmd) { - return 0; -} + struct usbg_cmd *cmd = container_of(se_cmd, struct usbg_cmd, se_cmd); -static void usbg_set_default_node_attrs(struct se_node_acl *nacl) -{ + uasp_send_tm_response(cmd); } -static int usbg_get_cmd_state(struct se_cmd *se_cmd) +static void usbg_aborted_task(struct se_cmd *se_cmd) { - return 0; -} + struct usbg_cmd *cmd = container_of(se_cmd, struct usbg_cmd, se_cmd); + struct f_uas *fu = cmd->fu; + struct usb_gadget *gadget = fuas_to_gadget(fu); + struct uas_stream *stream = &fu->stream[se_cmd->map_tag]; + int ret = 0; -static void usbg_queue_tm_rsp(struct se_cmd *se_cmd) -{ -} + if (stream->req_out->status == -EINPROGRESS) + ret = usb_ep_dequeue(fu->ep_out, stream->req_out); + else if (stream->req_in->status == -EINPROGRESS) + ret = usb_ep_dequeue(fu->ep_in, stream->req_in); + else if (stream->req_status->status == -EINPROGRESS) + ret = usb_ep_dequeue(fu->ep_status, stream->req_status); -static void usbg_aborted_task(struct se_cmd *se_cmd) -{ + if (ret) + dev_err(&gadget->dev, "Failed to abort cmd tag %d, (%d)\n", + cmd->tag, ret); + + cmd->state = UASP_QUEUE_COMMAND; } static const char *usbg_check_wwn(const char *name) @@ -1339,14 +1641,14 @@ static struct se_portal_group *usbg_make_tpg(struct se_wwn *wwn, struct usbg_tport *tport = container_of(wwn, struct usbg_tport, tport_wwn); struct usbg_tpg *tpg; - unsigned long tpgt; + u16 tpgt; int ret; struct f_tcm_opts *opts; unsigned i; if (strstr(name, "tpgt_") != name) return ERR_PTR(-EINVAL); - if (kstrtoul(name + 5, 0, &tpgt) || tpgt > UINT_MAX) + if (kstrtou16(name + 5, 0, &tpgt)) return ERR_PTR(-EINVAL); ret = -ENODEV; mutex_lock(&tpg_instances_lock); @@ -1379,7 +1681,8 @@ static struct se_portal_group *usbg_make_tpg(struct se_wwn *wwn, goto unref_dep; mutex_init(&tpg->tpg_mutex); atomic_set(&tpg->tpg_port_count, 0); - tpg->workqueue = alloc_workqueue("tcm_usb_gadget", 0, 1); + tpg->workqueue = alloc_workqueue("tcm_usb_gadget", + WQ_UNBOUND, WQ_UNBOUND_MAX_ACTIVE); if (!tpg->workqueue) goto free_tpg; @@ -1528,8 +1831,8 @@ static ssize_t tcm_usbg_tpg_nexus_show(struct config_item *item, char *page) ret = -ENODEV; goto out; } - ret = snprintf(page, PAGE_SIZE, "%s\n", - tv_nexus->tvn_se_sess->se_node_acl->initiatorname); + ret = sysfs_emit(page, "%s\n", + tv_nexus->tvn_se_sess->se_node_acl->initiatorname); out: mutex_unlock(&tpg->tpg_mutex); return ret; @@ -1691,16 +1994,9 @@ static const struct target_core_fabric_ops usbg_ops = { .tpg_get_wwn = usbg_get_fabric_wwn, .tpg_get_tag = usbg_get_tag, .tpg_check_demo_mode = usbg_check_true, - .tpg_check_demo_mode_cache = usbg_check_false, - .tpg_check_demo_mode_write_protect = usbg_check_false, - .tpg_check_prod_mode_write_protect = usbg_check_false, - .tpg_get_inst_index = usbg_tpg_get_inst_index, .release_cmd = usbg_release_cmd, - .sess_get_index = usbg_sess_get_index, .sess_get_initiator_sid = NULL, .write_pending = usbg_send_write_request, - .set_default_node_attributes = usbg_set_default_node_attrs, - .get_cmd_state = usbg_get_cmd_state, .queue_data_in = usbg_send_read_response, .queue_status = usbg_send_status_response, .queue_tm_rsp = usbg_queue_tm_rsp, @@ -1718,6 +2014,9 @@ static const struct target_core_fabric_ops usbg_ops = { .tfc_wwn_attrs = usbg_wwn_attrs, .tfc_tpg_base_attrs = usbg_base_attrs, + + .default_submit_type = TARGET_DIRECT_SUBMIT, + .direct_submit_supp = 1, }; /* Start gadget.c code */ @@ -1774,7 +2073,7 @@ static struct usb_endpoint_descriptor uasp_ss_bi_desc = { static struct usb_ss_ep_comp_descriptor uasp_bi_ep_comp_desc = { .bLength = sizeof(uasp_bi_ep_comp_desc), .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, - .bMaxBurst = 0, + .bMaxBurst = 15, .bmAttributes = UASP_SS_EP_COMP_LOG_STREAMS, .wBytesPerInterval = 0, }; @@ -1782,7 +2081,7 @@ static struct usb_ss_ep_comp_descriptor uasp_bi_ep_comp_desc = { static struct usb_ss_ep_comp_descriptor bot_bi_ep_comp_desc = { .bLength = sizeof(bot_bi_ep_comp_desc), .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, - .bMaxBurst = 0, + .bMaxBurst = 15, }; static struct usb_endpoint_descriptor uasp_bo_desc = { @@ -1817,12 +2116,14 @@ static struct usb_endpoint_descriptor uasp_ss_bo_desc = { static struct usb_ss_ep_comp_descriptor uasp_bo_ep_comp_desc = { .bLength = sizeof(uasp_bo_ep_comp_desc), .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + .bMaxBurst = 15, .bmAttributes = UASP_SS_EP_COMP_LOG_STREAMS, }; static struct usb_ss_ep_comp_descriptor bot_bo_ep_comp_desc = { .bLength = sizeof(bot_bo_ep_comp_desc), .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + .bMaxBurst = 15, }; static struct usb_endpoint_descriptor uasp_status_desc = { @@ -1999,43 +2300,39 @@ static int tcm_bind(struct usb_configuration *c, struct usb_function *f) bot_intf_desc.bInterfaceNumber = iface; uasp_intf_desc.bInterfaceNumber = iface; fu->iface = iface; - ep = usb_ep_autoconfig_ss(gadget, &uasp_ss_bi_desc, - &uasp_bi_ep_comp_desc); + ep = usb_ep_autoconfig(gadget, &uasp_fs_bi_desc); if (!ep) goto ep_fail; fu->ep_in = ep; - ep = usb_ep_autoconfig_ss(gadget, &uasp_ss_bo_desc, - &uasp_bo_ep_comp_desc); + ep = usb_ep_autoconfig(gadget, &uasp_fs_bo_desc); if (!ep) goto ep_fail; fu->ep_out = ep; - ep = usb_ep_autoconfig_ss(gadget, &uasp_ss_status_desc, - &uasp_status_in_ep_comp_desc); + ep = usb_ep_autoconfig(gadget, &uasp_fs_status_desc); if (!ep) goto ep_fail; fu->ep_status = ep; - ep = usb_ep_autoconfig_ss(gadget, &uasp_ss_cmd_desc, - &uasp_cmd_comp_desc); + ep = usb_ep_autoconfig(gadget, &uasp_fs_cmd_desc); if (!ep) goto ep_fail; fu->ep_cmd = ep; /* Assume endpoint addresses are the same for both speeds */ - uasp_bi_desc.bEndpointAddress = uasp_ss_bi_desc.bEndpointAddress; - uasp_bo_desc.bEndpointAddress = uasp_ss_bo_desc.bEndpointAddress; + uasp_bi_desc.bEndpointAddress = uasp_fs_bi_desc.bEndpointAddress; + uasp_bo_desc.bEndpointAddress = uasp_fs_bo_desc.bEndpointAddress; uasp_status_desc.bEndpointAddress = - uasp_ss_status_desc.bEndpointAddress; - uasp_cmd_desc.bEndpointAddress = uasp_ss_cmd_desc.bEndpointAddress; + uasp_fs_status_desc.bEndpointAddress; + uasp_cmd_desc.bEndpointAddress = uasp_fs_cmd_desc.bEndpointAddress; - uasp_fs_bi_desc.bEndpointAddress = uasp_ss_bi_desc.bEndpointAddress; - uasp_fs_bo_desc.bEndpointAddress = uasp_ss_bo_desc.bEndpointAddress; - uasp_fs_status_desc.bEndpointAddress = - uasp_ss_status_desc.bEndpointAddress; - uasp_fs_cmd_desc.bEndpointAddress = uasp_ss_cmd_desc.bEndpointAddress; + uasp_ss_bi_desc.bEndpointAddress = uasp_fs_bi_desc.bEndpointAddress; + uasp_ss_bo_desc.bEndpointAddress = uasp_fs_bo_desc.bEndpointAddress; + uasp_ss_status_desc.bEndpointAddress = + uasp_fs_status_desc.bEndpointAddress; + uasp_ss_cmd_desc.bEndpointAddress = uasp_fs_cmd_desc.bEndpointAddress; ret = usb_assign_descriptors(f, uasp_fs_function_desc, uasp_hs_function_desc, uasp_ss_function_desc, @@ -2079,9 +2376,14 @@ static void tcm_delayed_set_alt(struct work_struct *wq) static int tcm_get_alt(struct usb_function *f, unsigned intf) { - if (intf == bot_intf_desc.bInterfaceNumber) + struct f_uas *fu = to_f_uas(f); + + if (fu->iface != intf) + return -EOPNOTSUPP; + + if (fu->flags & USBG_IS_BOT) return USB_G_ALT_INT_BBB; - if (intf == uasp_intf_desc.bInterfaceNumber) + else if (fu->flags & USBG_IS_UAS) return USB_G_ALT_INT_UAS; return -EOPNOTSUPP; @@ -2091,6 +2393,9 @@ static int tcm_set_alt(struct usb_function *f, unsigned intf, unsigned alt) { struct f_uas *fu = to_f_uas(f); + if (fu->iface != intf) + return -EOPNOTSUPP; + if ((alt == USB_G_ALT_INT_BBB) || (alt == USB_G_ALT_INT_UAS)) { struct guas_setup_wq *work; @@ -2299,6 +2604,8 @@ static struct usb_function *tcm_alloc(struct usb_function_instance *fi) fu->function.disable = tcm_disable; fu->function.free_func = tcm_free; fu->tpg = tpg_instances[i].tpg; + + hash_init(fu->stream_hash); mutex_unlock(&tpg_instances_lock); return &fu->function; @@ -2329,5 +2636,6 @@ static void __exit tcm_exit(void) } module_exit(tcm_exit); +MODULE_DESCRIPTION("Target based USB-Gadget"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Sebastian Andrzej Siewior"); diff --git a/drivers/usb/gadget/function/f_uac1.c b/drivers/usb/gadget/function/f_uac1.c index 6f0e1d803dc2..9da9fb4e1239 100644 --- a/drivers/usb/gadget/function/f_uac1.c +++ b/drivers/usb/gadget/function/f_uac1.c @@ -292,6 +292,77 @@ static struct usb_descriptor_header *f_audio_desc[] = { NULL, }; +/* Standard ISO OUT Endpoint Descriptor */ +static struct usb_endpoint_descriptor ss_as_out_ep_desc = { + .bLength = USB_DT_ENDPOINT_AUDIO_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_SYNC_ADAPTIVE + | USB_ENDPOINT_XFER_ISOC, + .wMaxPacketSize = cpu_to_le16(UAC1_OUT_EP_MAX_PACKET_SIZE), + .bInterval = 4, +}; + +static struct usb_ss_ep_comp_descriptor ss_as_out_ep_desc_comp = { + .bLength = sizeof(ss_as_out_ep_desc_comp), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + .bMaxBurst = 0, + .bmAttributes = 0, + /* wBytesPerInterval = DYNAMIC */ +}; + +/* Standard ISO OUT Endpoint Descriptor */ +static struct usb_endpoint_descriptor ss_as_in_ep_desc = { + .bLength = USB_DT_ENDPOINT_AUDIO_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_SYNC_ASYNC + | USB_ENDPOINT_XFER_ISOC, + .wMaxPacketSize = cpu_to_le16(UAC1_OUT_EP_MAX_PACKET_SIZE), + .bInterval = 4, +}; + +static struct usb_ss_ep_comp_descriptor ss_as_in_ep_desc_comp = { + .bLength = sizeof(ss_as_in_ep_desc_comp), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + .bMaxBurst = 0, + .bmAttributes = 0, + /* wBytesPerInterval = DYNAMIC */ +}; + +static struct usb_descriptor_header *f_audio_ss_desc[] = { + (struct usb_descriptor_header *)&ac_interface_desc, + (struct usb_descriptor_header *)&ac_header_desc, + + (struct usb_descriptor_header *)&usb_out_it_desc, + (struct usb_descriptor_header *)&io_out_ot_desc, + (struct usb_descriptor_header *)&io_in_it_desc, + (struct usb_descriptor_header *)&usb_in_ot_desc, + + (struct usb_descriptor_header *)&as_out_interface_alt_0_desc, + (struct usb_descriptor_header *)&as_out_interface_alt_1_desc, + (struct usb_descriptor_header *)&as_out_header_desc, + + (struct usb_descriptor_header *)&as_out_type_i_desc, + + //(struct usb_descriptor_header *)&as_out_ep_desc, + (struct usb_descriptor_header *)&ss_as_out_ep_desc, + (struct usb_descriptor_header *)&ss_as_out_ep_desc_comp, + (struct usb_descriptor_header *)&as_iso_out_desc, + + (struct usb_descriptor_header *)&as_in_interface_alt_0_desc, + (struct usb_descriptor_header *)&as_in_interface_alt_1_desc, + (struct usb_descriptor_header *)&as_in_header_desc, + + (struct usb_descriptor_header *)&as_in_type_i_desc, + + //(struct usb_descriptor_header *)&as_in_ep_desc, + (struct usb_descriptor_header *)&ss_as_in_ep_desc, + (struct usb_descriptor_header *)&ss_as_in_ep_desc_comp, + (struct usb_descriptor_header *)&as_iso_in_desc, + NULL, +}; + enum { STR_AC_IF, STR_USB_OUT_IT, @@ -306,24 +377,10 @@ enum { STR_AS_OUT_IF_ALT1, STR_AS_IN_IF_ALT0, STR_AS_IN_IF_ALT1, + NUM_STR_DESCRIPTORS, }; -static struct usb_string strings_uac1[] = { - /* [STR_AC_IF].s = DYNAMIC, */ - [STR_USB_OUT_IT].s = "Playback Input terminal", - [STR_USB_OUT_IT_CH_NAMES].s = "Playback Channels", - [STR_IO_OUT_OT].s = "Playback Output terminal", - [STR_IO_IN_IT].s = "Capture Input terminal", - [STR_IO_IN_IT_CH_NAMES].s = "Capture Channels", - [STR_USB_IN_OT].s = "Capture Output terminal", - [STR_FU_IN].s = "Capture Volume", - [STR_FU_OUT].s = "Playback Volume", - [STR_AS_OUT_IF_ALT0].s = "Playback Inactive", - [STR_AS_OUT_IF_ALT1].s = "Playback Active", - [STR_AS_IN_IF_ALT0].s = "Capture Inactive", - [STR_AS_IN_IF_ALT1].s = "Capture Active", - { }, -}; +static struct usb_string strings_uac1[NUM_STR_DESCRIPTORS + 1] = {}; static struct usb_gadget_strings str_uac1 = { .language = 0x0409, /* en-us */ @@ -1194,6 +1251,20 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f) strings_uac1[STR_AC_IF].s = audio_opts->function_name; + strings_uac1[STR_USB_OUT_IT].s = audio_opts->c_it_name; + strings_uac1[STR_USB_OUT_IT_CH_NAMES].s = audio_opts->c_it_ch_name; + strings_uac1[STR_IO_OUT_OT].s = audio_opts->c_ot_name; + strings_uac1[STR_FU_OUT].s = audio_opts->c_fu_vol_name; + strings_uac1[STR_AS_OUT_IF_ALT0].s = "Playback Inactive"; + strings_uac1[STR_AS_OUT_IF_ALT1].s = "Playback Active"; + + strings_uac1[STR_IO_IN_IT].s = audio_opts->p_it_name; + strings_uac1[STR_IO_IN_IT_CH_NAMES].s = audio_opts->p_it_ch_name; + strings_uac1[STR_USB_IN_OT].s = audio_opts->p_ot_name; + strings_uac1[STR_FU_IN].s = audio_opts->p_fu_vol_name; + strings_uac1[STR_AS_IN_IF_ALT0].s = "Capture Inactive"; + strings_uac1[STR_AS_IN_IF_ALT1].s = "Capture Active"; + us = usb_gstrings_attach(cdev, uac1_strings, ARRAY_SIZE(strings_uac1)); if (IS_ERR(us)) return PTR_ERR(us); @@ -1352,6 +1423,7 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f) ep = usb_ep_autoconfig(cdev->gadget, &as_out_ep_desc); if (!ep) goto err_free_fu; + ss_as_out_ep_desc.bEndpointAddress = as_out_ep_desc.bEndpointAddress; audio->out_ep = ep; audio->out_ep->desc = &as_out_ep_desc; } @@ -1360,6 +1432,7 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f) ep = usb_ep_autoconfig(cdev->gadget, &as_in_ep_desc); if (!ep) goto err_free_fu; + ss_as_in_ep_desc.bEndpointAddress = as_in_ep_desc.bEndpointAddress; audio->in_ep = ep; audio->in_ep->desc = &as_in_ep_desc; } @@ -1367,8 +1440,8 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f) setup_descriptor(audio_opts); /* copy descriptors, and track endpoint copies */ - status = usb_assign_descriptors(f, f_audio_desc, f_audio_desc, NULL, - NULL); + status = usb_assign_descriptors(f, f_audio_desc, f_audio_desc, f_audio_ss_desc, + f_audio_ss_desc); if (status) goto err_free_fu; @@ -1561,7 +1634,7 @@ static ssize_t f_uac1_opts_##name##_show(struct config_item *item, \ int result; \ \ mutex_lock(&opts->lock); \ - result = snprintf(page, sizeof(opts->name), "%s", opts->name); \ + result = sysfs_emit(page, "%s", opts->name); \ mutex_unlock(&opts->lock); \ \ return result; \ @@ -1579,7 +1652,7 @@ static ssize_t f_uac1_opts_##name##_store(struct config_item *item, \ goto end; \ } \ \ - ret = snprintf(opts->name, min(sizeof(opts->name), len), \ + ret = scnprintf(opts->name, min(sizeof(opts->name), len), \ "%s", page); \ \ end: \ @@ -1608,8 +1681,19 @@ UAC1_ATTRIBUTE(bool, c_volume_present); UAC1_ATTRIBUTE(s16, c_volume_min); UAC1_ATTRIBUTE(s16, c_volume_max); UAC1_ATTRIBUTE(s16, c_volume_res); + UAC1_ATTRIBUTE_STRING(function_name); +UAC1_ATTRIBUTE_STRING(p_it_name); +UAC1_ATTRIBUTE_STRING(p_it_ch_name); +UAC1_ATTRIBUTE_STRING(p_ot_name); +UAC1_ATTRIBUTE_STRING(p_fu_vol_name); + +UAC1_ATTRIBUTE_STRING(c_it_name); +UAC1_ATTRIBUTE_STRING(c_it_ch_name); +UAC1_ATTRIBUTE_STRING(c_ot_name); +UAC1_ATTRIBUTE_STRING(c_fu_vol_name); + static struct configfs_attribute *f_uac1_attrs[] = { &f_uac1_opts_attr_c_chmask, &f_uac1_opts_attr_c_srate, @@ -1633,6 +1717,16 @@ static struct configfs_attribute *f_uac1_attrs[] = { &f_uac1_opts_attr_function_name, + &f_uac1_opts_attr_p_it_name, + &f_uac1_opts_attr_p_it_ch_name, + &f_uac1_opts_attr_p_ot_name, + &f_uac1_opts_attr_p_fu_vol_name, + + &f_uac1_opts_attr_c_it_name, + &f_uac1_opts_attr_c_it_ch_name, + &f_uac1_opts_attr_c_ot_name, + &f_uac1_opts_attr_c_fu_vol_name, + NULL, }; @@ -1685,7 +1779,17 @@ static struct usb_function_instance *f_audio_alloc_inst(void) opts->req_number = UAC1_DEF_REQ_NUM; - snprintf(opts->function_name, sizeof(opts->function_name), "AC Interface"); + scnprintf(opts->function_name, sizeof(opts->function_name), "AC Interface"); + + scnprintf(opts->p_it_name, sizeof(opts->p_it_name), "Capture Input terminal"); + scnprintf(opts->p_it_ch_name, sizeof(opts->p_it_ch_name), "Capture Channels"); + scnprintf(opts->p_ot_name, sizeof(opts->p_ot_name), "Capture Output terminal"); + scnprintf(opts->p_fu_vol_name, sizeof(opts->p_fu_vol_name), "Capture Volume"); + + scnprintf(opts->c_it_name, sizeof(opts->c_it_name), "Playback Input terminal"); + scnprintf(opts->c_it_ch_name, sizeof(opts->c_it_ch_name), "Playback Channels"); + scnprintf(opts->c_ot_name, sizeof(opts->c_ot_name), "Playback Output terminal"); + scnprintf(opts->c_fu_vol_name, sizeof(opts->c_fu_vol_name), "Playback Volume"); return &opts->func_inst; } @@ -1750,5 +1854,6 @@ static struct usb_function *f_audio_alloc(struct usb_function_instance *fi) } DECLARE_USB_FUNCTION_INIT(uac1, f_audio_alloc_inst, f_audio_alloc); +MODULE_DESCRIPTION("USB Audio Class 1.0 Function (using u_audio API)"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Ruslan Bilovol"); diff --git a/drivers/usb/gadget/function/f_uac1_legacy.c b/drivers/usb/gadget/function/f_uac1_legacy.c index e2d7f69128a0..49cf5aae90ca 100644 --- a/drivers/usb/gadget/function/f_uac1_legacy.c +++ b/drivers/usb/gadget/function/f_uac1_legacy.c @@ -1014,5 +1014,6 @@ static struct usb_function *f_audio_alloc(struct usb_function_instance *fi) } DECLARE_USB_FUNCTION_INIT(uac1_legacy, f_audio_alloc_inst, f_audio_alloc); +MODULE_DESCRIPTION("USB Audio class function driver"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Bryan Wu"); diff --git a/drivers/usb/gadget/function/f_uac2.c b/drivers/usb/gadget/function/f_uac2.c index 08726e4c68a5..dd252ff2fb4e 100644 --- a/drivers/usb/gadget/function/f_uac2.c +++ b/drivers/usb/gadget/function/f_uac2.c @@ -95,7 +95,9 @@ enum { STR_CLKSRC_IN, STR_CLKSRC_OUT, STR_USB_IT, + STR_USB_IT_CH, STR_IO_IT, + STR_IO_IT_CH, STR_USB_OT, STR_IO_OT, STR_FU_IN, @@ -104,25 +106,10 @@ enum { STR_AS_OUT_ALT1, STR_AS_IN_ALT0, STR_AS_IN_ALT1, + NUM_STR_DESCRIPTORS, }; -static struct usb_string strings_fn[] = { - /* [STR_ASSOC].s = DYNAMIC, */ - [STR_IF_CTRL].s = "Topology Control", - [STR_CLKSRC_IN].s = "Input Clock", - [STR_CLKSRC_OUT].s = "Output Clock", - [STR_USB_IT].s = "USBH Out", - [STR_IO_IT].s = "USBD Out", - [STR_USB_OT].s = "USBH In", - [STR_IO_OT].s = "USBD In", - [STR_FU_IN].s = "Capture Volume", - [STR_FU_OUT].s = "Playback Volume", - [STR_AS_OUT_ALT0].s = "Playback Inactive", - [STR_AS_OUT_ALT1].s = "Playback Active", - [STR_AS_IN_ALT0].s = "Capture Inactive", - [STR_AS_IN_ALT1].s = "Capture Active", - { }, -}; +static struct usb_string strings_fn[NUM_STR_DESCRIPTORS + 1] = {}; static const char *const speed_names[] = { [USB_SPEED_UNKNOWN] = "UNKNOWN", @@ -212,7 +199,7 @@ static struct uac2_input_terminal_descriptor io_in_it_desc = { .bDescriptorSubtype = UAC_INPUT_TERMINAL, /* .bTerminalID = DYNAMIC */ - .wTerminalType = cpu_to_le16(UAC_INPUT_TERMINAL_MICROPHONE), + /* .wTerminalType = DYNAMIC */ .bAssocTerminal = 0, /* .bCSourceID = DYNAMIC */ .iChannelNames = 0, @@ -240,7 +227,7 @@ static struct uac2_output_terminal_descriptor io_out_ot_desc = { .bDescriptorSubtype = UAC_OUTPUT_TERMINAL, /* .bTerminalID = DYNAMIC */ - .wTerminalType = cpu_to_le16(UAC_OUTPUT_TERMINAL_SPEAKER), + /* .wTerminalType = DYNAMIC */ .bAssocTerminal = 0, /* .bSourceID = DYNAMIC */ /* .bCSourceID = DYNAMIC */ @@ -977,6 +964,9 @@ static void setup_descriptor(struct f_uac2_opts *opts) iad_desc.bInterfaceCount++; } + io_in_it_desc.wTerminalType = cpu_to_le16(opts->c_terminal_type); + io_out_ot_desc.wTerminalType = cpu_to_le16(opts->p_terminal_type); + setup_headers(opts, fs_audio_desc, USB_SPEED_FULL); setup_headers(opts, hs_audio_desc, USB_SPEED_HIGH); setup_headers(opts, ss_audio_desc, USB_SPEED_SUPER); @@ -1046,6 +1036,23 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) return ret; strings_fn[STR_ASSOC].s = uac2_opts->function_name; + strings_fn[STR_IF_CTRL].s = uac2_opts->if_ctrl_name; + strings_fn[STR_CLKSRC_IN].s = uac2_opts->clksrc_in_name; + strings_fn[STR_CLKSRC_OUT].s = uac2_opts->clksrc_out_name; + + strings_fn[STR_USB_IT].s = uac2_opts->c_it_name; + strings_fn[STR_USB_IT_CH].s = uac2_opts->c_it_ch_name; + strings_fn[STR_IO_OT].s = uac2_opts->c_ot_name; + strings_fn[STR_FU_OUT].s = uac2_opts->c_fu_vol_name; + strings_fn[STR_AS_OUT_ALT0].s = "Playback Inactive"; + strings_fn[STR_AS_OUT_ALT1].s = "Playback Active"; + + strings_fn[STR_IO_IT].s = uac2_opts->p_it_name; + strings_fn[STR_IO_IT_CH].s = uac2_opts->p_it_ch_name; + strings_fn[STR_USB_OT].s = uac2_opts->p_ot_name; + strings_fn[STR_FU_IN].s = uac2_opts->p_fu_vol_name; + strings_fn[STR_AS_IN_ALT0].s = "Capture Inactive"; + strings_fn[STR_AS_IN_ALT1].s = "Capture Active"; us = usb_gstrings_attach(cdev, fn_strings, ARRAY_SIZE(strings_fn)); if (IS_ERR(us)) @@ -1069,7 +1076,9 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) in_clk_src_desc.iClockSource = us[STR_CLKSRC_IN].id; out_clk_src_desc.iClockSource = us[STR_CLKSRC_OUT].id; usb_out_it_desc.iTerminal = us[STR_USB_IT].id; + usb_out_it_desc.iChannelNames = us[STR_USB_IT_CH].id; io_in_it_desc.iTerminal = us[STR_IO_IT].id; + io_in_it_desc.iChannelNames = us[STR_IO_IT_CH].id; usb_in_ot_desc.iTerminal = us[STR_USB_OT].id; io_out_ot_desc.iTerminal = us[STR_IO_OT].id; std_as_out_if0_desc.iInterface = us[STR_AS_OUT_ALT0].id; @@ -1142,6 +1151,7 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) } std_as_out_if0_desc.bInterfaceNumber = ret; std_as_out_if1_desc.bInterfaceNumber = ret; + std_as_out_if1_desc.bNumEndpoints = 1; uac2->as_out_intf = ret; uac2->as_out_alt = 0; @@ -1175,6 +1185,7 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) uac2->as_in_alt = 0; } + std_ac_if_desc.bNumEndpoints = 0; if (FUOUT_EN(uac2_opts) || FUIN_EN(uac2_opts)) { uac2->int_ep = usb_ep_autoconfig(gadget, &fs_ep_int_desc); if (!uac2->int_ep) { @@ -2041,7 +2052,7 @@ static ssize_t f_uac2_opts_##name##_show(struct config_item *item, \ int result; \ \ mutex_lock(&opts->lock); \ - result = snprintf(page, sizeof(opts->name), "%s", opts->name); \ + result = sysfs_emit(page, "%s", opts->name); \ mutex_unlock(&opts->lock); \ \ return result; \ @@ -2051,7 +2062,7 @@ static ssize_t f_uac2_opts_##name##_store(struct config_item *item, \ const char *page, size_t len) \ { \ struct f_uac2_opts *opts = to_f_uac2_opts(item); \ - int ret = 0; \ + int ret = len; \ \ mutex_lock(&opts->lock); \ if (opts->refcnt) { \ @@ -2059,8 +2070,11 @@ static ssize_t f_uac2_opts_##name##_store(struct config_item *item, \ goto end; \ } \ \ - ret = snprintf(opts->name, min(sizeof(opts->name), len), \ - "%s", page); \ + if (len && page[len - 1] == '\n') \ + len--; \ + \ + scnprintf(opts->name, min(sizeof(opts->name), len + 1), \ + "%s", page); \ \ end: \ mutex_unlock(&opts->lock); \ @@ -2093,6 +2107,23 @@ UAC2_ATTRIBUTE(s16, c_volume_max); UAC2_ATTRIBUTE(s16, c_volume_res); UAC2_ATTRIBUTE(u32, fb_max); UAC2_ATTRIBUTE_STRING(function_name); +UAC2_ATTRIBUTE_STRING(if_ctrl_name); +UAC2_ATTRIBUTE_STRING(clksrc_in_name); +UAC2_ATTRIBUTE_STRING(clksrc_out_name); + +UAC2_ATTRIBUTE_STRING(p_it_name); +UAC2_ATTRIBUTE_STRING(p_it_ch_name); +UAC2_ATTRIBUTE_STRING(p_ot_name); +UAC2_ATTRIBUTE_STRING(p_fu_vol_name); + +UAC2_ATTRIBUTE_STRING(c_it_name); +UAC2_ATTRIBUTE_STRING(c_it_ch_name); +UAC2_ATTRIBUTE_STRING(c_ot_name); +UAC2_ATTRIBUTE_STRING(c_fu_vol_name); + +UAC2_ATTRIBUTE(s16, p_terminal_type); +UAC2_ATTRIBUTE(s16, c_terminal_type); + static struct configfs_attribute *f_uac2_attrs[] = { &f_uac2_opts_attr_p_chmask, @@ -2120,6 +2151,22 @@ static struct configfs_attribute *f_uac2_attrs[] = { &f_uac2_opts_attr_c_volume_res, &f_uac2_opts_attr_function_name, + &f_uac2_opts_attr_if_ctrl_name, + &f_uac2_opts_attr_clksrc_in_name, + &f_uac2_opts_attr_clksrc_out_name, + + &f_uac2_opts_attr_p_it_name, + &f_uac2_opts_attr_p_it_ch_name, + &f_uac2_opts_attr_p_ot_name, + &f_uac2_opts_attr_p_fu_vol_name, + + &f_uac2_opts_attr_c_it_name, + &f_uac2_opts_attr_c_it_ch_name, + &f_uac2_opts_attr_c_ot_name, + &f_uac2_opts_attr_c_fu_vol_name, + + &f_uac2_opts_attr_p_terminal_type, + &f_uac2_opts_attr_c_terminal_type, NULL, }; @@ -2177,7 +2224,23 @@ static struct usb_function_instance *afunc_alloc_inst(void) opts->req_number = UAC2_DEF_REQ_NUM; opts->fb_max = FBACK_FAST_MAX; - snprintf(opts->function_name, sizeof(opts->function_name), "Source/Sink"); + scnprintf(opts->function_name, sizeof(opts->function_name), "Source/Sink"); + scnprintf(opts->if_ctrl_name, sizeof(opts->if_ctrl_name), "Topology Control"); + scnprintf(opts->clksrc_in_name, sizeof(opts->clksrc_in_name), "Input Clock"); + scnprintf(opts->clksrc_out_name, sizeof(opts->clksrc_out_name), "Output Clock"); + + scnprintf(opts->p_it_name, sizeof(opts->p_it_name), "USBD Out"); + scnprintf(opts->p_it_ch_name, sizeof(opts->p_it_ch_name), "Capture Channels"); + scnprintf(opts->p_ot_name, sizeof(opts->p_ot_name), "USBH In"); + scnprintf(opts->p_fu_vol_name, sizeof(opts->p_fu_vol_name), "Capture Volume"); + + scnprintf(opts->c_it_name, sizeof(opts->c_it_name), "USBH Out"); + scnprintf(opts->c_it_ch_name, sizeof(opts->c_it_ch_name), "Playback Channels"); + scnprintf(opts->c_ot_name, sizeof(opts->c_ot_name), "USBD In"); + scnprintf(opts->c_fu_vol_name, sizeof(opts->c_fu_vol_name), "Playback Volume"); + + opts->p_terminal_type = UAC2_DEF_P_TERM_TYPE; + opts->c_terminal_type = UAC2_DEF_C_TERM_TYPE; return &opts->func_inst; } @@ -2238,6 +2301,7 @@ static struct usb_function *afunc_alloc(struct usb_function_instance *fi) } DECLARE_USB_FUNCTION_INIT(uac2, afunc_alloc_inst, afunc_alloc); +MODULE_DESCRIPTION("USB Audio Class 2.0 Function"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Yadwinder Singh"); MODULE_AUTHOR("Jaswinder Singh"); diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c index 32f2c1645467..aa6ab666741a 100644 --- a/drivers/usb/gadget/function/f_uvc.c +++ b/drivers/usb/gadget/function/f_uvc.c @@ -76,14 +76,14 @@ static struct usb_interface_descriptor uvc_control_intf = { .bDescriptorType = USB_DT_INTERFACE, .bInterfaceNumber = UVC_INTF_VIDEO_CONTROL, .bAlternateSetting = 0, - .bNumEndpoints = 1, + .bNumEndpoints = 0, .bInterfaceClass = USB_CLASS_VIDEO, .bInterfaceSubClass = UVC_SC_VIDEOCONTROL, .bInterfaceProtocol = 0x00, .iInterface = 0, }; -static struct usb_endpoint_descriptor uvc_control_ep = { +static struct usb_endpoint_descriptor uvc_interrupt_ep = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = USB_DIR_IN, @@ -92,8 +92,8 @@ static struct usb_endpoint_descriptor uvc_control_ep = { .bInterval = 8, }; -static struct usb_ss_ep_comp_descriptor uvc_ss_control_comp = { - .bLength = sizeof(uvc_ss_control_comp), +static struct usb_ss_ep_comp_descriptor uvc_ss_interrupt_comp = { + .bLength = sizeof(uvc_ss_interrupt_comp), .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, /* The following 3 values can be tweaked if necessary. */ .bMaxBurst = 0, @@ -101,7 +101,7 @@ static struct usb_ss_ep_comp_descriptor uvc_ss_control_comp = { .wBytesPerInterval = cpu_to_le16(UVC_STATUS_MAX_PACKET_SIZE), }; -static struct uvc_control_endpoint_descriptor uvc_control_cs_ep = { +static struct uvc_control_endpoint_descriptor uvc_interrupt_cs_ep = { .bLength = UVC_DT_CONTROL_ENDPOINT_SIZE, .bDescriptorType = USB_DT_CS_ENDPOINT, .bDescriptorSubType = UVC_EP_INTERRUPT, @@ -263,10 +263,13 @@ uvc_function_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) return 0; } -void uvc_function_setup_continue(struct uvc_device *uvc) +void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep) { struct usb_composite_dev *cdev = uvc->func.config->cdev; + if (disable_ep && uvc->video.ep) + usb_ep_disable(uvc->video.ep); + usb_composite_setup_continue(cdev); } @@ -300,14 +303,17 @@ uvc_function_set_alt(struct usb_function *f, unsigned interface, unsigned alt) if (alt) return -EINVAL; - uvcg_info(f, "reset UVC Control\n"); - usb_ep_disable(uvc->control_ep); + if (uvc->enable_interrupt_ep) { + uvcg_info(f, "reset UVC interrupt endpoint\n"); + usb_ep_disable(uvc->interrupt_ep); - if (!uvc->control_ep->desc) - if (config_ep_by_speed(cdev->gadget, f, uvc->control_ep)) - return -EINVAL; + if (!uvc->interrupt_ep->desc) + if (config_ep_by_speed(cdev->gadget, f, + uvc->interrupt_ep)) + return -EINVAL; - usb_ep_enable(uvc->control_ep); + usb_ep_enable(uvc->interrupt_ep); + } if (uvc->state == UVC_STATE_DISCONNECTED) { memset(&v4l2_event, 0, sizeof(v4l2_event)); @@ -334,15 +340,11 @@ uvc_function_set_alt(struct usb_function *f, unsigned interface, unsigned alt) if (uvc->state != UVC_STATE_STREAMING) return 0; - if (uvc->video.ep) - usb_ep_disable(uvc->video.ep); - memset(&v4l2_event, 0, sizeof(v4l2_event)); v4l2_event.type = UVC_EVENT_STREAMOFF; v4l2_event_queue(&uvc->vdev, &v4l2_event); - uvc->state = UVC_STATE_CONNECTED; - return 0; + return USB_GADGET_DELAYED_STATUS; case 1: if (uvc->state != UVC_STATE_CONNECTED) @@ -385,7 +387,8 @@ uvc_function_disable(struct usb_function *f) uvc->state = UVC_STATE_DISCONNECTED; usb_ep_disable(uvc->video.ep); - usb_ep_disable(uvc->control_ep); + if (uvc->enable_interrupt_ep) + usb_ep_disable(uvc->interrupt_ep); } /* -------------------------------------------------------------------------- @@ -462,7 +465,7 @@ uvc_register_video(struct uvc_device *uvc) memcpy(mem, desc, (desc)->bLength); \ *(dst)++ = mem; \ mem += (desc)->bLength; \ - } while (0); + } while (0) #define UVC_COPY_DESCRIPTORS(mem, dst, src) \ do { \ @@ -474,6 +477,25 @@ uvc_register_video(struct uvc_device *uvc) } \ } while (0) +#define UVC_COPY_XU_DESCRIPTOR(mem, dst, desc) \ + do { \ + *(dst)++ = mem; \ + memcpy(mem, desc, 22); /* bLength to bNrInPins */ \ + mem += 22; \ + \ + memcpy(mem, (desc)->baSourceID, (desc)->bNrInPins); \ + mem += (desc)->bNrInPins; \ + \ + memcpy(mem, &(desc)->bControlSize, 1); \ + mem++; \ + \ + memcpy(mem, (desc)->bmControls, (desc)->bControlSize); \ + mem += (desc)->bControlSize; \ + \ + memcpy(mem, &(desc)->iExtension, 1); \ + mem++; \ + } while (0) + static struct usb_descriptor_header ** uvc_copy_descriptors(struct uvc_device *uvc, enum usb_device_speed speed) { @@ -485,6 +507,7 @@ uvc_copy_descriptors(struct uvc_device *uvc, enum usb_device_speed speed) const struct usb_descriptor_header * const *src; struct usb_descriptor_header **dst; struct usb_descriptor_header **hdr; + struct uvcg_extension *xu; unsigned int control_size; unsigned int streaming_size; unsigned int n_desc; @@ -492,6 +515,7 @@ uvc_copy_descriptors(struct uvc_device *uvc, enum usb_device_speed speed) void *mem; switch (speed) { + case USB_SPEED_SUPER_PLUS: case USB_SPEED_SUPER: uvc_control_desc = uvc->desc.ss_control; uvc_streaming_cls = uvc->desc.ss_streaming; @@ -521,9 +545,9 @@ uvc_copy_descriptors(struct uvc_device *uvc, enum usb_device_speed speed) * uvc_iad * uvc_control_intf * Class-specific UVC control descriptors - * uvc_control_ep - * uvc_control_cs_ep - * uvc_ss_control_comp (for SS only) + * uvc_interrupt_ep + * uvc_interrupt_cs_ep + * uvc_ss_interrupt_comp (for SS only) * uvc_streaming_intf_alt0 * Class-specific UVC streaming descriptors * uvc_{fs|hs}_streaming @@ -533,14 +557,18 @@ uvc_copy_descriptors(struct uvc_device *uvc, enum usb_device_speed speed) control_size = 0; streaming_size = 0; bytes = uvc_iad.bLength + uvc_control_intf.bLength - + uvc_control_ep.bLength + uvc_control_cs_ep.bLength + uvc_streaming_intf_alt0.bLength; - if (speed == USB_SPEED_SUPER) { - bytes += uvc_ss_control_comp.bLength; - n_desc = 6; - } else { - n_desc = 5; + n_desc = 3; + if (uvc->enable_interrupt_ep) { + bytes += uvc_interrupt_ep.bLength + uvc_interrupt_cs_ep.bLength; + n_desc += 2; + + if (speed == USB_SPEED_SUPER || + speed == USB_SPEED_SUPER_PLUS) { + bytes += uvc_ss_interrupt_comp.bLength; + n_desc += 1; + } } for (src = (const struct usb_descriptor_header **)uvc_control_desc; @@ -549,6 +577,13 @@ uvc_copy_descriptors(struct uvc_device *uvc, enum usb_device_speed speed) bytes += (*src)->bLength; n_desc++; } + + list_for_each_entry(xu, uvc->desc.extension_units, list) { + control_size += xu->desc.bLength; + bytes += xu->desc.bLength; + n_desc++; + } + for (src = (const struct usb_descriptor_header **)uvc_streaming_cls; *src; ++src) { streaming_size += (*src)->bLength; @@ -575,15 +610,23 @@ uvc_copy_descriptors(struct uvc_device *uvc, enum usb_device_speed speed) uvc_control_header = mem; UVC_COPY_DESCRIPTORS(mem, dst, (const struct usb_descriptor_header **)uvc_control_desc); + + list_for_each_entry(xu, uvc->desc.extension_units, list) + UVC_COPY_XU_DESCRIPTOR(mem, dst, &xu->desc); + uvc_control_header->wTotalLength = cpu_to_le16(control_size); uvc_control_header->bInCollection = 1; uvc_control_header->baInterfaceNr[0] = uvc->streaming_intf; - UVC_COPY_DESCRIPTOR(mem, dst, &uvc_control_ep); - if (speed == USB_SPEED_SUPER) - UVC_COPY_DESCRIPTOR(mem, dst, &uvc_ss_control_comp); + if (uvc->enable_interrupt_ep) { + UVC_COPY_DESCRIPTOR(mem, dst, &uvc_interrupt_ep); + if (speed == USB_SPEED_SUPER || + speed == USB_SPEED_SUPER_PLUS) + UVC_COPY_DESCRIPTOR(mem, dst, &uvc_ss_interrupt_comp); + + UVC_COPY_DESCRIPTOR(mem, dst, &uvc_interrupt_cs_ep); + } - UVC_COPY_DESCRIPTOR(mem, dst, &uvc_control_cs_ep); UVC_COPY_DESCRIPTOR(mem, dst, &uvc_streaming_intf_alt0); uvc_streaming_header = mem; @@ -603,6 +646,7 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f) { struct usb_composite_dev *cdev = c->cdev; struct uvc_device *uvc = to_uvc(f); + struct uvcg_extension *xu; struct usb_string *us; unsigned int max_packet_mult; unsigned int max_packet_size; @@ -666,13 +710,25 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f) (opts->streaming_maxburst + 1)); /* Allocate endpoints. */ - ep = usb_ep_autoconfig(cdev->gadget, &uvc_control_ep); - if (!ep) { - uvcg_info(f, "Unable to allocate control EP\n"); - goto error; + if (opts->enable_interrupt_ep) { + ep = usb_ep_autoconfig(cdev->gadget, &uvc_interrupt_ep); + if (!ep) { + uvcg_info(f, "Unable to allocate interrupt EP\n"); + goto error; + } + uvc->interrupt_ep = ep; + uvc_control_intf.bNumEndpoints = 1; } - uvc->control_ep = ep; + uvc->enable_interrupt_ep = opts->enable_interrupt_ep; + /* + * gadget_is_{super|dual}speed() API check UDC controller capitblity. It should pass down + * highest speed endpoint descriptor to UDC controller. So UDC controller driver can reserve + * enough resource at check_config(), especially mult and maxburst. So UDC driver (such as + * cdns3) can know need at least (mult + 1) * (maxburst + 1) * wMaxPacketSize internal + * memory for this uvc functions. This is the only straightforward method to resolve the UDC + * resource allocation issue in the current gadget framework. + */ if (gadget_is_superspeed(c->cdev->gadget)) ep = usb_ep_autoconfig_ss(cdev->gadget, &uvc_ss_streaming_ep, &uvc_ss_streaming_comp); @@ -691,6 +747,18 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f) uvc_hs_streaming_ep.bEndpointAddress = uvc->video.ep->address; uvc_ss_streaming_ep.bEndpointAddress = uvc->video.ep->address; + /* + * XUs can have an arbitrary string descriptor describing them. If they + * have one pick up the ID. + */ + list_for_each_entry(xu, &opts->extension_units, list) + if (xu->string_descriptor_index) + xu->desc.iExtension = cdev->usb_strings[xu->string_descriptor_index].id; + + /* + * We attach the hard-coded defaults incase the user does not provide + * any more appropriate strings through configfs. + */ uvc_en_us_strings[UVC_STRING_CONTROL_IDX].s = opts->function_name; us = usb_gstrings_attach(cdev, uvc_function_strings, ARRAY_SIZE(uvc_en_us_strings)); @@ -698,11 +766,15 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f) ret = PTR_ERR(us); goto error; } - uvc_iad.iFunction = us[UVC_STRING_CONTROL_IDX].id; - uvc_control_intf.iInterface = us[UVC_STRING_CONTROL_IDX].id; - ret = us[UVC_STRING_STREAMING_IDX].id; - uvc_streaming_intf_alt0.iInterface = ret; - uvc_streaming_intf_alt1.iInterface = ret; + + uvc_iad.iFunction = opts->iad_index ? cdev->usb_strings[opts->iad_index].id : + us[UVC_STRING_CONTROL_IDX].id; + uvc_streaming_intf_alt0.iInterface = opts->vs0_index ? + cdev->usb_strings[opts->vs0_index].id : + us[UVC_STRING_STREAMING_IDX].id; + uvc_streaming_intf_alt1.iInterface = opts->vs1_index ? + cdev->usb_strings[opts->vs1_index].id : + us[UVC_STRING_STREAMING_IDX].id; /* Allocate interface IDs. */ if ((ret = usb_interface_id(c, f)) < 0) @@ -726,21 +798,26 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f) f->fs_descriptors = NULL; goto error; } - if (gadget_is_dualspeed(cdev->gadget)) { - f->hs_descriptors = uvc_copy_descriptors(uvc, USB_SPEED_HIGH); - if (IS_ERR(f->hs_descriptors)) { - ret = PTR_ERR(f->hs_descriptors); - f->hs_descriptors = NULL; - goto error; - } + + f->hs_descriptors = uvc_copy_descriptors(uvc, USB_SPEED_HIGH); + if (IS_ERR(f->hs_descriptors)) { + ret = PTR_ERR(f->hs_descriptors); + f->hs_descriptors = NULL; + goto error; } - if (gadget_is_superspeed(c->cdev->gadget)) { - f->ss_descriptors = uvc_copy_descriptors(uvc, USB_SPEED_SUPER); - if (IS_ERR(f->ss_descriptors)) { - ret = PTR_ERR(f->ss_descriptors); - f->ss_descriptors = NULL; - goto error; - } + + f->ss_descriptors = uvc_copy_descriptors(uvc, USB_SPEED_SUPER); + if (IS_ERR(f->ss_descriptors)) { + ret = PTR_ERR(f->ss_descriptors); + f->ss_descriptors = NULL; + goto error; + } + + f->ssp_descriptors = uvc_copy_descriptors(uvc, USB_SPEED_SUPER_PLUS); + if (IS_ERR(f->ssp_descriptors)) { + ret = PTR_ERR(f->ssp_descriptors); + f->ssp_descriptors = NULL; + goto error; } /* Preallocate control endpoint request. */ @@ -803,7 +880,6 @@ static struct usb_function_instance *uvc_alloc_inst(void) struct uvc_camera_terminal_descriptor *cd; struct uvc_processing_unit_descriptor *pd; struct uvc_output_terminal_descriptor *od; - struct uvc_color_matching_descriptor *md; struct uvc_descriptor_header **ctl_cls; int ret; @@ -852,13 +928,12 @@ static struct usb_function_instance *uvc_alloc_inst(void) od->bSourceID = 2; od->iTerminal = 0; - md = &opts->uvc_color_matching; - md->bLength = UVC_DT_COLOR_MATCHING_SIZE; - md->bDescriptorType = USB_DT_CS_INTERFACE; - md->bDescriptorSubType = UVC_VS_COLORFORMAT; - md->bColorPrimaries = 1; - md->bTransferCharacteristics = 1; - md->bMatrixCoefficients = 4; + /* + * With the ability to add XUs to the UVC function graph, we need to be + * able to allocate unique unit IDs to them. The IDs are 1-based, with + * the CT, PU and OT above consuming the first 3. + */ + opts->last_unit_id = 3; /* Prepare fs control class descriptors for configfs-based gadgets */ ctl_cls = opts->uvc_fs_control_cls; @@ -880,6 +955,8 @@ static struct usb_function_instance *uvc_alloc_inst(void) opts->ss_control = (const struct uvc_descriptor_header * const *)ctl_cls; + INIT_LIST_HEAD(&opts->extension_units); + opts->streaming_interval = 1; opts->streaming_maxpacket = 1024; snprintf(opts->function_name, sizeof(opts->function_name), "UVC Camera"); @@ -898,7 +975,8 @@ static void uvc_free(struct usb_function *f) struct uvc_device *uvc = to_uvc(f); struct f_uvc_opts *opts = container_of(f->fi, struct f_uvc_opts, func_inst); - config_item_put(&uvc->header->item); + if (!opts->header) + config_item_put(&uvc->header->item); --opts->refcnt; kfree(uvc); } @@ -913,6 +991,8 @@ static void uvc_function_unbind(struct usb_configuration *c, uvcg_info(f, "%s()\n", __func__); + kthread_cancel_work_sync(&video->hw_submit); + if (video->async_wq) destroy_workqueue(video->async_wq); @@ -990,27 +1070,33 @@ static struct usb_function *uvc_alloc(struct usb_function_instance *fi) uvc->desc.hs_streaming = opts->hs_streaming; uvc->desc.ss_streaming = opts->ss_streaming; - streaming = config_group_find_item(&opts->func_inst.group, "streaming"); - if (!streaming) - goto err_config; - - header = config_group_find_item(to_config_group(streaming), "header"); - config_item_put(streaming); - if (!header) - goto err_config; - - h = config_group_find_item(to_config_group(header), "h"); - config_item_put(header); - if (!h) - goto err_config; - - uvc->header = to_uvcg_streaming_header(h); - if (!uvc->header->linked) { - mutex_unlock(&opts->lock); - kfree(uvc); - return ERR_PTR(-EBUSY); + if (opts->header) { + uvc->header = opts->header; + } else { + streaming = config_group_find_item(&opts->func_inst.group, "streaming"); + if (!streaming) + goto err_config; + + header = config_group_find_item(to_config_group(streaming), "header"); + config_item_put(streaming); + if (!header) + goto err_config; + + h = config_group_find_item(to_config_group(header), "h"); + config_item_put(header); + if (!h) + goto err_config; + + uvc->header = to_uvcg_streaming_header(h); + if (!uvc->header->linked) { + mutex_unlock(&opts->lock); + kfree(uvc); + return ERR_PTR(-EBUSY); + } } + uvc->desc.extension_units = &opts->extension_units; + ++opts->refcnt; mutex_unlock(&opts->lock); @@ -1034,5 +1120,6 @@ err_config: } DECLARE_USB_FUNCTION_INIT(uvc, uvc_alloc_inst, uvc_alloc); +MODULE_DESCRIPTION("USB Video Class Gadget driver"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Laurent Pinchart"); diff --git a/drivers/usb/gadget/function/f_uvc.h b/drivers/usb/gadget/function/f_uvc.h index 1db972d4beeb..083aef0c65c6 100644 --- a/drivers/usb/gadget/function/f_uvc.h +++ b/drivers/usb/gadget/function/f_uvc.h @@ -11,7 +11,7 @@ struct uvc_device; -void uvc_function_setup_continue(struct uvc_device *uvc); +void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep); void uvc_function_connect(struct uvc_device *uvc); diff --git a/drivers/usb/gadget/function/rndis.c b/drivers/usb/gadget/function/rndis.c index 29bf8664bf58..afd75d72412c 100644 --- a/drivers/usb/gadget/function/rndis.c +++ b/drivers/usb/gadget/function/rndis.c @@ -31,7 +31,7 @@ #include <asm/io.h> #include <asm/byteorder.h> -#include <asm/unaligned.h> +#include <linux/unaligned.h> #include "u_rndis.h" @@ -869,12 +869,12 @@ EXPORT_SYMBOL_GPL(rndis_msg_parser); static inline int rndis_get_nr(void) { - return ida_simple_get(&rndis_ida, 0, 1000, GFP_KERNEL); + return ida_alloc_max(&rndis_ida, 999, GFP_KERNEL); } static inline void rndis_put_nr(int nr) { - ida_simple_remove(&rndis_ida, nr); + ida_free(&rndis_ida, nr); } struct rndis_params *rndis_register(void (*resp_avail)(void *v), void *v) diff --git a/drivers/usb/gadget/function/storage_common.c b/drivers/usb/gadget/function/storage_common.c index 2a4163b0f6fe..75831f2c7abe 100644 --- a/drivers/usb/gadget/function/storage_common.c +++ b/drivers/usb/gadget/function/storage_common.c @@ -537,4 +537,5 @@ ssize_t fsg_store_forced_eject(struct fsg_lun *curlun, struct rw_semaphore *file } EXPORT_SYMBOL_GPL(fsg_store_forced_eject); +MODULE_DESCRIPTION("Common definitions for mass storage functionality"); MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/function/storage_common.h b/drivers/usb/gadget/function/storage_common.h index 0a544a82cbf8..11ac785d5eee 100644 --- a/drivers/usb/gadget/function/storage_common.h +++ b/drivers/usb/gadget/function/storage_common.h @@ -5,7 +5,7 @@ #include <linux/device.h> #include <linux/usb/storage.h> #include <scsi/scsi.h> -#include <asm/unaligned.h> +#include <linux/unaligned.h> #ifndef DEBUG #undef VERBOSE_DEBUG @@ -131,7 +131,7 @@ static inline bool fsg_lun_is_open(struct fsg_lun *curlun) #define FSG_BUFLEN ((u32)16384) /* Maximal number of LUNs supported in mass storage function */ -#define FSG_MAX_LUNS 16 +#define FSG_MAX_LUNS (US_BULK_MAX_LUN_LIMIT + 1) enum fsg_buffer_state { BUF_STATE_SENDING = -2, diff --git a/drivers/usb/gadget/function/tcm.h b/drivers/usb/gadget/function/tcm.h index 3cd565794ad7..009974d81d66 100644 --- a/drivers/usb/gadget/function/tcm.h +++ b/drivers/usb/gadget/function/tcm.h @@ -4,6 +4,7 @@ #include <linux/kref.h> /* #include <linux/usb/uas.h> */ +#include <linux/hashtable.h> #include <linux/usb/composite.h> #include <linux/usb/uas.h> #include <linux/usb/storage.h> @@ -13,9 +14,11 @@ #define USBG_NAMELEN 32 #define fuas_to_gadget(f) (f->function.config->cdev->gadget) -#define UASP_SS_EP_COMP_LOG_STREAMS 4 +#define UASP_SS_EP_COMP_LOG_STREAMS 5 #define UASP_SS_EP_COMP_NUM_STREAMS (1 << UASP_SS_EP_COMP_LOG_STREAMS) +#define USBG_NUM_CMDS (UASP_SS_EP_COMP_NUM_STREAMS + 1) + enum { USB_G_STR_INT_UAS = 0, USB_G_STR_INT_BBB, @@ -24,7 +27,7 @@ enum { #define USB_G_ALT_INT_BBB 0 #define USB_G_ALT_INT_UAS 1 -#define USB_G_DEFAULT_SESSION_TAGS 128 +#define USB_G_DEFAULT_SESSION_TAGS USBG_NUM_CMDS struct tcm_usbg_nexus { struct se_session *tvn_se_sess; @@ -72,15 +75,23 @@ struct usbg_cmd { struct se_cmd se_cmd; void *data_buf; /* used if no sg support available */ struct f_uas *fu; - struct completion write_complete; struct kref ref; + struct usb_request *req; + + u32 flags; +#define USBG_CMD_PENDING_DATA_WRITE BIT(0) + /* UAS only */ u16 tag; u16 prio_attr; struct sense_iu sense_iu; + struct response_iu response_iu; enum uas_state state; - struct uas_stream *stream; + + int tmr_func; + int tmr_rsp; +#define RC_RESPONSE_UNKNOWN 0xff /* BOT only */ __le32 bot_tag; @@ -93,6 +104,9 @@ struct uas_stream { struct usb_request *req_in; struct usb_request *req_out; struct usb_request *req_status; + + struct completion cmd_completion; + struct hlist_node node; }; struct usbg_cdb { @@ -116,15 +130,17 @@ struct f_uas { #define USBG_USE_STREAMS (1 << 2) #define USBG_IS_BOT (1 << 3) #define USBG_BOT_CMD_PEND (1 << 4) +#define USBG_BOT_WEDGED (1 << 5) - struct usbg_cdb cmd; + struct usbg_cdb cmd[USBG_NUM_CMDS]; struct usb_ep *ep_in; struct usb_ep *ep_out; /* UAS */ struct usb_ep *ep_status; struct usb_ep *ep_cmd; - struct uas_stream stream[UASP_SS_EP_COMP_NUM_STREAMS]; + struct uas_stream stream[USBG_NUM_CMDS]; + DECLARE_HASHTABLE(stream_hash, UASP_SS_EP_COMP_LOG_STREAMS); /* BOT */ struct bot_status bot_status; diff --git a/drivers/usb/gadget/function/u_audio.c b/drivers/usb/gadget/function/u_audio.c index c1f62e91b012..ca8dbec65f73 100644 --- a/drivers/usb/gadget/function/u_audio.c +++ b/drivers/usb/gadget/function/u_audio.c @@ -57,13 +57,13 @@ struct uac_rtd_params { /* Volume/Mute controls and their state */ int fu_id; /* Feature Unit ID */ - struct snd_kcontrol *snd_kctl_volume; - struct snd_kcontrol *snd_kctl_mute; + struct snd_ctl_elem_id snd_kctl_volume_id; + struct snd_ctl_elem_id snd_kctl_mute_id; s16 volume_min, volume_max, volume_res; s16 volume; int mute; - struct snd_kcontrol *snd_kctl_rate; /* read-only current rate */ + struct snd_ctl_elem_id snd_kctl_rate_id; /* read-only current rate */ int srate; /* selected samplerate */ int active; /* playback/capture running */ @@ -494,14 +494,13 @@ static inline void free_ep_fback(struct uac_rtd_params *prm, struct usb_ep *ep) static void set_active(struct uac_rtd_params *prm, bool active) { // notifying through the Rate ctrl - struct snd_kcontrol *kctl = prm->snd_kctl_rate; unsigned long flags; spin_lock_irqsave(&prm->lock, flags); if (prm->active != active) { prm->active = active; snd_ctl_notify(prm->uac->card, SNDRV_CTL_EVENT_MASK_VALUE, - &kctl->id); + &prm->snd_kctl_rate_id); } spin_unlock_irqrestore(&prm->lock, flags); } @@ -593,16 +592,25 @@ int u_audio_start_capture(struct g_audio *audio_dev) struct usb_ep *ep, *ep_fback; struct uac_rtd_params *prm; struct uac_params *params = &audio_dev->params; - int req_len, i; + int req_len, i, ret; prm = &uac->c_prm; dev_dbg(dev, "start capture with rate %d\n", prm->srate); ep = audio_dev->out_ep; - config_ep_by_speed(gadget, &audio_dev->func, ep); + ret = config_ep_by_speed(gadget, &audio_dev->func, ep); + if (ret < 0) { + dev_err(dev, "config_ep_by_speed for out_ep failed (%d)\n", ret); + return ret; + } + req_len = ep->maxpacket; prm->ep_enabled = true; - usb_ep_enable(ep); + ret = usb_ep_enable(ep); + if (ret < 0) { + dev_err(dev, "usb_ep_enable failed for out_ep (%d)\n", ret); + return ret; + } for (i = 0; i < params->req_number; i++) { if (!prm->reqs[i]) { @@ -630,9 +638,18 @@ int u_audio_start_capture(struct g_audio *audio_dev) return 0; /* Setup feedback endpoint */ - config_ep_by_speed(gadget, &audio_dev->func, ep_fback); + ret = config_ep_by_speed(gadget, &audio_dev->func, ep_fback); + if (ret < 0) { + dev_err(dev, "config_ep_by_speed in_ep_fback failed (%d)\n", ret); + return ret; // TODO: Clean up out_ep + } + prm->fb_ep_enabled = true; - usb_ep_enable(ep_fback); + ret = usb_ep_enable(ep_fback); + if (ret < 0) { + dev_err(dev, "usb_ep_enable failed for in_ep_fback (%d)\n", ret); + return ret; // TODO: Clean up out_ep + } req_len = ep_fback->maxpacket; req_fback = usb_ep_alloc_request(ep_fback, GFP_ATOMIC); @@ -688,13 +705,17 @@ int u_audio_start_playback(struct g_audio *audio_dev) struct uac_params *params = &audio_dev->params; unsigned int factor; const struct usb_endpoint_descriptor *ep_desc; - int req_len, i; + int req_len, i, ret; unsigned int p_pktsize; prm = &uac->p_prm; dev_dbg(dev, "start playback with rate %d\n", prm->srate); ep = audio_dev->in_ep; - config_ep_by_speed(gadget, &audio_dev->func, ep); + ret = config_ep_by_speed(gadget, &audio_dev->func, ep); + if (ret < 0) { + dev_err(dev, "config_ep_by_speed for in_ep failed (%d)\n", ret); + return ret; + } ep_desc = ep->desc; /* @@ -721,7 +742,11 @@ int u_audio_start_playback(struct g_audio *audio_dev) uac->p_residue_mil = 0; prm->ep_enabled = true; - usb_ep_enable(ep); + ret = usb_ep_enable(ep); + if (ret < 0) { + dev_err(dev, "usb_ep_enable failed for in_ep (%d)\n", ret); + return ret; + } for (i = 0; i < params->req_number; i++) { if (!prm->reqs[i]) { @@ -807,7 +832,7 @@ int u_audio_set_volume(struct g_audio *audio_dev, int playback, s16 val) if (change) snd_ctl_notify(uac->card, SNDRV_CTL_EVENT_MASK_VALUE, - &prm->snd_kctl_volume->id); + &prm->snd_kctl_volume_id); return 0; } @@ -856,7 +881,7 @@ int u_audio_set_mute(struct g_audio *audio_dev, int playback, int val) if (change) snd_ctl_notify(uac->card, SNDRV_CTL_EVENT_MASK_VALUE, - &prm->snd_kctl_mute->id); + &prm->snd_kctl_mute_id); return 0; } @@ -1115,35 +1140,35 @@ static int u_audio_rate_get(struct snd_kcontrol *kcontrol, } static struct snd_kcontrol_new u_audio_controls[] = { - [UAC_FBACK_CTRL] { + [UAC_FBACK_CTRL] = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = "Capture Pitch 1000000", .info = u_audio_pitch_info, .get = u_audio_pitch_get, .put = u_audio_pitch_put, }, - [UAC_P_PITCH_CTRL] { + [UAC_P_PITCH_CTRL] = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = "Playback Pitch 1000000", .info = u_audio_pitch_info, .get = u_audio_pitch_get, .put = u_audio_pitch_put, }, - [UAC_MUTE_CTRL] { + [UAC_MUTE_CTRL] = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "", /* will be filled later */ .info = u_audio_mute_info, .get = u_audio_mute_get, .put = u_audio_mute_put, }, - [UAC_VOLUME_CTRL] { + [UAC_VOLUME_CTRL] = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "", /* will be filled later */ .info = u_audio_volume_info, .get = u_audio_volume_get, .put = u_audio_volume_put, }, - [UAC_RATE_CTRL] { + [UAC_RATE_CTRL] = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = "", /* will be filled later */ .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, @@ -1243,7 +1268,7 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, if (err < 0) goto snd_fail; - strscpy(pcm->name, pcm_name, sizeof(pcm->name)); + strscpy(pcm->name, pcm_name); pcm->private_data = uac; uac->pcm = pcm; @@ -1257,7 +1282,7 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, if ((c_chmask && g_audio->in_ep_fback) || (p_chmask && params->p_fu.id) || (c_chmask && params->c_fu.id)) - strscpy(card->mixername, card_name, sizeof(card->driver)); + strscpy(card->mixername, card_name); if (c_chmask && g_audio->in_ep_fback) { kctl = snd_ctl_new1(&u_audio_controls[UAC_FBACK_CTRL], @@ -1331,7 +1356,7 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, err = snd_ctl_add(card, kctl); if (err < 0) goto snd_fail; - prm->snd_kctl_mute = kctl; + prm->snd_kctl_mute_id = kctl->id; prm->mute = 0; } @@ -1359,7 +1384,7 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, err = snd_ctl_add(card, kctl); if (err < 0) goto snd_fail; - prm->snd_kctl_volume = kctl; + prm->snd_kctl_volume_id = kctl->id; prm->volume = fu->volume_max; prm->volume_max = fu->volume_max; prm->volume_min = fu->volume_min; @@ -1383,12 +1408,13 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, err = snd_ctl_add(card, kctl); if (err < 0) goto snd_fail; - prm->snd_kctl_rate = kctl; + prm->snd_kctl_rate_id = kctl->id; } - strscpy(card->driver, card_name, sizeof(card->driver)); - strscpy(card->shortname, card_name, sizeof(card->shortname)); - sprintf(card->longname, "%s %i", card_name, card->dev->id); + strscpy(card->driver, card_name); + strscpy(card->shortname, card_name); + snprintf(card->longname, sizeof(card->longname), "%s %i", + card_name, card->dev->id); snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, NULL, 0, BUFF_SIZE_MAX); @@ -1420,9 +1446,11 @@ void g_audio_cleanup(struct g_audio *g_audio) return; uac = g_audio->uac; + g_audio->uac = NULL; + card = uac->card; if (card) - snd_card_free(card); + snd_card_free_when_closed(card); kfree(uac->p_prm.reqs); kfree(uac->c_prm.reqs); diff --git a/drivers/usb/gadget/function/u_ether.c b/drivers/usb/gadget/function/u_ether.c index 8f12f3f8f6ee..f58590bf5e02 100644 --- a/drivers/usb/gadget/function/u_ether.c +++ b/drivers/usb/gadget/function/u_ether.c @@ -17,6 +17,8 @@ #include <linux/etherdevice.h> #include <linux/ethtool.h> #include <linux/if_vlan.h> +#include <linux/string_helpers.h> +#include <linux/usb/composite.h> #include "u_ether.h" @@ -91,11 +93,10 @@ struct eth_dev { #define DEFAULT_QLEN 2 /* double buffering by default */ -/* for dual-speed hardware, use deeper queues at high/super speed */ +/* use deeper queues at high/super speed */ static inline int qlen(struct usb_gadget *gadget, unsigned qmult) { - if (gadget_is_dualspeed(gadget) && (gadget->speed == USB_SPEED_HIGH || - gadget->speed >= USB_SPEED_SUPER)) + if (gadget->speed == USB_SPEED_HIGH || gadget->speed >= USB_SPEED_SUPER) return qmult * DEFAULT_QLEN; else return DEFAULT_QLEN; @@ -103,41 +104,6 @@ static inline int qlen(struct usb_gadget *gadget, unsigned qmult) /*-------------------------------------------------------------------------*/ -/* REVISIT there must be a better way than having two sets - * of debug calls ... - */ - -#undef DBG -#undef VDBG -#undef ERROR -#undef INFO - -#define xprintk(d, level, fmt, args...) \ - printk(level "%s: " fmt , (d)->net->name , ## args) - -#ifdef DEBUG -#undef DEBUG -#define DBG(dev, fmt, args...) \ - xprintk(dev , KERN_DEBUG , fmt , ## args) -#else -#define DBG(dev, fmt, args...) \ - do { } while (0) -#endif /* DEBUG */ - -#ifdef VERBOSE_DEBUG -#define VDBG DBG -#else -#define VDBG(dev, fmt, args...) \ - do { } while (0) -#endif /* DEBUG */ - -#define ERROR(dev, fmt, args...) \ - xprintk(dev , KERN_ERR , fmt , ## args) -#define INFO(dev, fmt, args...) \ - xprintk(dev , KERN_INFO , fmt , ## args) - -/*-------------------------------------------------------------------------*/ - /* NETWORK DRIVER HOOKUP (to the layer above this driver) */ static void eth_get_drvinfo(struct net_device *net, struct ethtool_drvinfo *p) @@ -471,6 +437,20 @@ static inline int is_promisc(u16 cdc_filter) return cdc_filter & USB_CDC_PACKET_TYPE_PROMISCUOUS; } +static int ether_wakeup_host(struct gether *port) +{ + int ret; + struct usb_function *func = &port->func; + struct usb_gadget *gadget = func->config->cdev->gadget; + + if (func->func_suspended) + ret = usb_func_wakeup(func); + else + ret = usb_gadget_wakeup(gadget); + + return ret; +} + static netdev_tx_t eth_start_xmit(struct sk_buff *skb, struct net_device *net) { @@ -490,6 +470,15 @@ static netdev_tx_t eth_start_xmit(struct sk_buff *skb, in = NULL; cdc_filter = 0; } + + if (dev->port_usb && dev->port_usb->is_suspend) { + DBG(dev, "Port suspended. Triggering wakeup\n"); + netif_stop_queue(net); + spin_unlock_irqrestore(&dev->lock, flags); + ether_wakeup_host(dev->port_usb); + return NETDEV_TX_BUSY; + } + spin_unlock_irqrestore(&dev->lock, flags); if (!in) { @@ -729,7 +718,7 @@ static const struct net_device_ops eth_netdev_ops = { .ndo_validate_addr = eth_validate_addr, }; -static struct device_type gadget_type = { +static const struct device_type gadget_type = { .name = "gadget", }; @@ -798,6 +787,7 @@ struct eth_dev *gether_setup_name(struct usb_gadget *g, net->max_mtu = GETHER_MAX_MTU_SIZE; dev->gadget = g; + SET_NETDEV_DEV(net, &g->dev); SET_NETDEV_DEVTYPE(net, &gadget_type); status = register_netdev(net); @@ -845,13 +835,11 @@ struct net_device *gether_setup_name_default(const char *netname) snprintf(net->name, sizeof(net->name), "%s%%d", netname); eth_random_addr(dev->dev_mac); - pr_warn("using random %s ethernet address\n", "self"); /* by default we always have a random MAC address */ net->addr_assign_type = NET_ADDR_RANDOM; eth_random_addr(dev->host_mac); - pr_warn("using random %s ethernet address\n", "host"); net->netdev_ops = ð_netdev_ops; @@ -872,6 +860,8 @@ int gether_register_netdev(struct net_device *net) struct usb_gadget *g; int status; + if (!net->dev.parent) + return -EINVAL; dev = netdev_priv(net); g = dev->gadget; @@ -902,6 +892,7 @@ void gether_set_gadget(struct net_device *net, struct usb_gadget *g) dev = netdev_priv(net); dev->gadget = g; + SET_NETDEV_DEV(net, &g->dev); } EXPORT_SYMBOL_GPL(gether_set_gadget); @@ -974,6 +965,8 @@ int gether_get_host_addr_cdc(struct net_device *net, char *host_addr, int len) dev = netdev_priv(net); snprintf(host_addr, len, "%pm", dev->host_mac); + string_upper(host_addr, host_addr); + return strlen(host_addr); } EXPORT_SYMBOL_GPL(gether_get_host_addr_cdc); @@ -1039,13 +1032,52 @@ int gether_set_ifname(struct net_device *net, const char *name, int len) if (!p || p[1] != 'd' || strchr(p + 2, '%')) return -EINVAL; - strncpy(net->name, tmp, sizeof(net->name)); + strscpy(net->name, tmp); dev->ifname_set = true; return 0; } EXPORT_SYMBOL_GPL(gether_set_ifname); +void gether_suspend(struct gether *link) +{ + struct eth_dev *dev = link->ioport; + unsigned long flags; + + if (!dev) + return; + + if (atomic_read(&dev->tx_qlen)) { + /* + * There is a transfer in progress. So we trigger a remote + * wakeup to inform the host. + */ + if (!ether_wakeup_host(dev->port_usb)) + return; + } + spin_lock_irqsave(&dev->lock, flags); + link->is_suspend = true; + spin_unlock_irqrestore(&dev->lock, flags); +} +EXPORT_SYMBOL_GPL(gether_suspend); + +void gether_resume(struct gether *link) +{ + struct eth_dev *dev = link->ioport; + unsigned long flags; + + if (!dev) + return; + + if (netif_queue_stopped(dev->net)) + netif_start_queue(dev->net); + + spin_lock_irqsave(&dev->lock, flags); + link->is_suspend = false; + spin_unlock_irqrestore(&dev->lock, flags); +} +EXPORT_SYMBOL_GPL(gether_resume); + /* * gether_cleanup - remove Ethernet-over-USB device * Context: may sleep @@ -1208,9 +1240,11 @@ void gether_disconnect(struct gether *link) spin_lock(&dev->lock); dev->port_usb = NULL; + link->is_suspend = false; spin_unlock(&dev->lock); } EXPORT_SYMBOL_GPL(gether_disconnect); +MODULE_DESCRIPTION("Ethernet-over-USB link layer utilities for Gadget stack"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("David Brownell"); diff --git a/drivers/usb/gadget/function/u_ether.h b/drivers/usb/gadget/function/u_ether.h index 40144546d1b0..34be220cef77 100644 --- a/drivers/usb/gadget/function/u_ether.h +++ b/drivers/usb/gadget/function/u_ether.h @@ -79,6 +79,7 @@ struct gether { /* called on network open/close */ void (*open)(struct gether *); void (*close)(struct gether *); + bool is_suspend; }; #define DEFAULT_FILTER (USB_CDC_PACKET_TYPE_BROADCAST \ @@ -258,6 +259,9 @@ int gether_set_ifname(struct net_device *net, const char *name, int len); void gether_cleanup(struct eth_dev *dev); +void gether_suspend(struct gether *link); +void gether_resume(struct gether *link); + /* connect/disconnect is handled by individual functions */ struct net_device *gether_connect(struct gether *); void gether_disconnect(struct gether *); @@ -275,4 +279,17 @@ static inline bool can_support_ecm(struct usb_gadget *gadget) return true; } +/* peak (theoretical) bulk transfer rate in bits-per-second */ +static inline unsigned int gether_bitrate(struct usb_gadget *g) +{ + if (g->speed >= USB_SPEED_SUPER_PLUS) + return 4250000000U; + if (g->speed == USB_SPEED_SUPER) + return 3750000000U; + else if (g->speed == USB_SPEED_HIGH) + return 13 * 512 * 8 * 1000 * 8; + else + return 19 * 64 * 1 * 1000 * 8; +} + #endif /* __U_ETHER_H */ diff --git a/drivers/usb/gadget/function/u_fs.h b/drivers/usb/gadget/function/u_fs.h index f102ec23f3af..4b3365f23fd7 100644 --- a/drivers/usb/gadget/function/u_fs.h +++ b/drivers/usb/gadget/function/u_fs.h @@ -32,8 +32,6 @@ # define ffs_dump_mem(prefix, ptr, len) do { } while (0) #endif /* VERBOSE_DEBUG */ -#define ENTER() pr_vdebug("%s()\n", __func__) - struct f_fs_opts; struct ffs_dev { diff --git a/drivers/usb/gadget/function/u_hid.h b/drivers/usb/gadget/function/u_hid.h index 84bb70292855..a9ed9720caee 100644 --- a/drivers/usb/gadget/function/u_hid.h +++ b/drivers/usb/gadget/function/u_hid.h @@ -25,6 +25,8 @@ struct f_hid_opts { unsigned short report_desc_length; unsigned char *report_desc; bool report_desc_alloc; + unsigned char interval; + bool interval_user_set; /* * Protect the data form concurrent access by read/write diff --git a/drivers/usb/gadget/function/u_midi2.h b/drivers/usb/gadget/function/u_midi2.h new file mode 100644 index 000000000000..4e7adb41dfb7 --- /dev/null +++ b/drivers/usb/gadget/function/u_midi2.h @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Utility definitions for MIDI 2.0 function + */ + +#ifndef U_MIDI2_H +#define U_MIDI2_H + +#include <linux/usb/composite.h> +#include <sound/asound.h> + +struct f_midi2_opts; +struct f_midi2_ep_opts; +struct f_midi2_block_opts; + +/* UMP Function Block info */ +struct f_midi2_block_info { + unsigned int direction; /* FB direction: 1-3 */ + unsigned int first_group; /* first UMP group: 0-15 */ + unsigned int num_groups; /* number of UMP groups: 1-16 */ + unsigned int midi1_first_group; /* first UMP group for MIDI 1.0 */ + unsigned int midi1_num_groups; /* number of UMP groups for MIDI 1.0 */ + unsigned int ui_hint; /* UI-hint: 0-3 */ + unsigned int midi_ci_version; /* MIDI-CI version: 0-255 */ + unsigned int sysex8_streams; /* number of sysex8 streams: 0-255 */ + unsigned int is_midi1; /* MIDI 1.0 port: 0-2 */ + bool active; /* FB active flag: bool */ + const char *name; /* FB name */ +}; + +/* UMP Endpoint info */ +struct f_midi2_ep_info { + unsigned int protocol_caps; /* protocol capabilities: 1-3 */ + unsigned int protocol; /* default protocol: 1-2 */ + unsigned int manufacturer; /* manufacturer id: 0-0xffffff */ + unsigned int family; /* device family id: 0-0xffff */ + unsigned int model; /* device model id: 0x-0xffff */ + unsigned int sw_revision; /* software revision: 32bit */ + + const char *ep_name; /* Endpoint name */ + const char *product_id; /* Product ID */ +}; + +struct f_midi2_card_info { + bool process_ump; /* process UMP stream: bool */ + bool static_block; /* static FBs: bool */ + unsigned int req_buf_size; /* request buffer size */ + unsigned int num_reqs; /* number of requests */ + const char *iface_name; /* interface name */ +}; + +struct f_midi2_block_opts { + struct config_group group; + unsigned int id; + struct f_midi2_block_info info; + struct f_midi2_ep_opts *ep; +}; + +struct f_midi2_ep_opts { + struct config_group group; + unsigned int index; + struct f_midi2_ep_info info; + struct f_midi2_block_opts *blks[SNDRV_UMP_MAX_BLOCKS]; + struct f_midi2_opts *opts; +}; + +#define MAX_UMP_EPS 4 +#define MAX_CABLES 16 + +struct f_midi2_opts { + struct usb_function_instance func_inst; + struct mutex lock; + int refcnt; + + struct f_midi2_card_info info; + + unsigned int num_eps; + struct f_midi2_ep_opts *eps[MAX_UMP_EPS]; +}; + +#endif /* U_MIDI2_H */ diff --git a/drivers/usb/gadget/function/u_ncm.h b/drivers/usb/gadget/function/u_ncm.h index 5408854d8407..49ec095cdb4b 100644 --- a/drivers/usb/gadget/function/u_ncm.h +++ b/drivers/usb/gadget/function/u_ncm.h @@ -31,6 +31,8 @@ struct f_ncm_opts { */ struct mutex lock; int refcnt; + + u16 max_segment_size; }; #endif /* U_NCM_H */ diff --git a/drivers/usb/gadget/function/u_phonet.h b/drivers/usb/gadget/function/u_phonet.h index c53233b37192..ff62ca22c40d 100644 --- a/drivers/usb/gadget/function/u_phonet.h +++ b/drivers/usb/gadget/function/u_phonet.h @@ -20,7 +20,6 @@ struct f_phonet_opts { struct net_device *gphonet_setup_default(void); void gphonet_set_gadget(struct net_device *net, struct usb_gadget *g); int gphonet_register_netdev(struct net_device *net); -int phonet_bind_config(struct usb_configuration *c, struct net_device *dev); void gphonet_cleanup(struct net_device *dev); #endif /* __U_PHONET_H */ diff --git a/drivers/usb/gadget/function/u_serial.c b/drivers/usb/gadget/function/u_serial.c index 840626e064e1..1cce5317181a 100644 --- a/drivers/usb/gadget/function/u_serial.c +++ b/drivers/usb/gadget/function/u_serial.c @@ -21,6 +21,7 @@ #include <linux/tty.h> #include <linux/tty_flip.h> #include <linux/slab.h> +#include <linux/string_choices.h> #include <linux/export.h> #include <linux/module.h> #include <linux/console.h> @@ -28,6 +29,7 @@ #include <linux/kthread.h> #include <linux/workqueue.h> #include <linux/kfifo.h> +#include <linux/serial.h> #include "u_serial.h" @@ -82,6 +84,9 @@ #define WRITE_BUF_SIZE 8192 /* TX only */ #define GS_CONSOLE_BUF_SIZE 8192 +/* Prevents race conditions while accessing gser->ioport */ +static DEFINE_SPINLOCK(serial_port_lock); + /* console info */ struct gs_console { struct console console; @@ -123,6 +128,7 @@ struct gs_port { wait_queue_head_t close_wait; bool suspended; /* port suspended */ bool start_delayed; /* delay start when suspended */ + struct async_icount icount; /* REVISIT this state ... */ struct usb_cdc_line_coding port_line_coding; /* 8-N-1 etc */ @@ -254,6 +260,7 @@ __acquires(&port->port_lock) break; } do_tty_wake = true; + port->icount.tx += len; req->length = len; list_del(&req->list); @@ -288,8 +295,8 @@ __acquires(&port->port_lock) break; } - if (do_tty_wake && port->port.tty) - tty_wakeup(port->port.tty); + if (do_tty_wake) + tty_port_tty_wakeup(&port->port); return status; } @@ -405,6 +412,7 @@ static void gs_rx_push(struct work_struct *work) size -= n; } + port->icount.rx += size; count = tty_insert_flip_string(&port->port, packet, size); if (count) @@ -566,17 +574,31 @@ static int gs_start_io(struct gs_port *port) gs_start_tx(port); /* Unblock any pending writes into our circular buffer, in case * we didn't in gs_start_tx() */ - tty_wakeup(port->port.tty); + tty_port_tty_wakeup(&port->port); } else { - gs_free_requests(ep, head, &port->read_allocated); - gs_free_requests(port->port_usb->in, &port->write_pool, - &port->write_allocated); + /* Free reqs only if we are still connected */ + if (port->port_usb) { + gs_free_requests(ep, head, &port->read_allocated); + gs_free_requests(port->port_usb->in, &port->write_pool, + &port->write_allocated); + } status = -EIO; } return status; } +static int gserial_wakeup_host(struct gserial *gser) +{ + struct usb_function *func = &gser->func; + struct usb_gadget *gadget = func->config->cdev->gadget; + + if (func->func_suspended) + return usb_func_wakeup(func); + else + return usb_gadget_wakeup(gadget); +} + /*-------------------------------------------------------------------------*/ /* TTY Driver */ @@ -727,17 +749,30 @@ exit: spin_unlock_irq(&port->port_lock); } -static int gs_write(struct tty_struct *tty, const unsigned char *buf, int count) +static ssize_t gs_write(struct tty_struct *tty, const u8 *buf, size_t count) { struct gs_port *port = tty->driver_data; unsigned long flags; + int ret = 0; + struct gserial *gser = port->port_usb; - pr_vdebug("gs_write: ttyGS%d (%p) writing %d bytes\n", + pr_vdebug("gs_write: ttyGS%d (%p) writing %zu bytes\n", port->port_num, tty, count); spin_lock_irqsave(&port->port_lock, flags); if (count) count = kfifo_in(&port->port_write_buf, buf, count); + + if (port->suspended) { + spin_unlock_irqrestore(&port->port_lock, flags); + ret = gserial_wakeup_host(gser); + if (ret) { + pr_debug("ttyGS%d: Remote wakeup failed:%d\n", port->port_num, ret); + return count; + } + spin_lock_irqsave(&port->port_lock, flags); + } + /* treat count == 0 as flush_chars() */ if (port->port_usb) gs_start_tx(port); @@ -746,7 +781,7 @@ static int gs_write(struct tty_struct *tty, const unsigned char *buf, int count) return count; } -static int gs_put_char(struct tty_struct *tty, unsigned char ch) +static int gs_put_char(struct tty_struct *tty, u8 ch) { struct gs_port *port = tty->driver_data; unsigned long flags; @@ -766,10 +801,22 @@ static void gs_flush_chars(struct tty_struct *tty) { struct gs_port *port = tty->driver_data; unsigned long flags; + int ret = 0; + struct gserial *gser = port->port_usb; pr_vdebug("gs_flush_chars: (%d,%p)\n", port->port_num, tty); spin_lock_irqsave(&port->port_lock, flags); + if (port->suspended) { + spin_unlock_irqrestore(&port->port_lock, flags); + ret = gserial_wakeup_host(gser); + if (ret) { + pr_debug("ttyGS%d: Remote wakeup failed:%d\n", port->port_num, ret); + return; + } + spin_lock_irqsave(&port->port_lock, flags); + } + if (port->port_usb) gs_start_tx(port); spin_unlock_irqrestore(&port->port_lock, flags); @@ -844,6 +891,23 @@ static int gs_break_ctl(struct tty_struct *tty, int duration) return status; } +static int gs_get_icount(struct tty_struct *tty, + struct serial_icounter_struct *icount) +{ + struct gs_port *port = tty->driver_data; + struct async_icount cnow; + unsigned long flags; + + spin_lock_irqsave(&port->port_lock, flags); + cnow = port->icount; + spin_unlock_irqrestore(&port->port_lock, flags); + + icount->rx = cnow.rx; + icount->tx = cnow.tx; + + return 0; +} + static const struct tty_operations gs_tty_ops = { .open = gs_open, .close = gs_close, @@ -854,6 +918,7 @@ static const struct tty_operations gs_tty_ops = { .chars_in_buffer = gs_chars_in_buffer, .unthrottle = gs_unthrottle, .break_ctl = gs_break_ctl, + .get_icount = gs_get_icount, }; /*-------------------------------------------------------------------------*/ @@ -913,8 +978,11 @@ static void __gs_console_push(struct gs_console *cons) } req->length = size; + + spin_unlock_irq(&cons->lock); if (usb_ep_queue(ep, req, GFP_ATOMIC)) req->length = 0; + spin_lock_irq(&cons->lock); } static void gs_console_work(struct work_struct *work) @@ -1375,8 +1443,10 @@ void gserial_disconnect(struct gserial *gser) if (!port) return; + spin_lock_irqsave(&serial_port_lock, flags); + /* tell the TTY glue not to do I/O here any more */ - spin_lock_irqsave(&port->port_lock, flags); + spin_lock(&port->port_lock); gs_console_disconnect(port); @@ -1391,7 +1461,8 @@ void gserial_disconnect(struct gserial *gser) tty_hangup(port->port.tty); } port->suspended = false; - spin_unlock_irqrestore(&port->port_lock, flags); + spin_unlock(&port->port_lock); + spin_unlock_irqrestore(&serial_port_lock, flags); /* disable endpoints, aborting down any active I/O */ usb_ep_disable(gser->out); @@ -1414,21 +1485,48 @@ EXPORT_SYMBOL_GPL(gserial_disconnect); void gserial_suspend(struct gserial *gser) { - struct gs_port *port = gser->ioport; + struct gs_port *port; unsigned long flags; - spin_lock_irqsave(&port->port_lock, flags); + spin_lock_irqsave(&serial_port_lock, flags); + port = gser->ioport; + + if (!port) { + spin_unlock_irqrestore(&serial_port_lock, flags); + return; + } + + if (port->write_busy || port->write_started) { + /* Wakeup to host if there are ongoing transfers */ + spin_unlock_irqrestore(&serial_port_lock, flags); + if (!gserial_wakeup_host(gser)) + return; + spin_lock_irqsave(&serial_port_lock, flags); + } + + spin_lock(&port->port_lock); + spin_unlock(&serial_port_lock); port->suspended = true; + port->start_delayed = true; spin_unlock_irqrestore(&port->port_lock, flags); } EXPORT_SYMBOL_GPL(gserial_suspend); void gserial_resume(struct gserial *gser) { - struct gs_port *port = gser->ioport; + struct gs_port *port; unsigned long flags; - spin_lock_irqsave(&port->port_lock, flags); + spin_lock_irqsave(&serial_port_lock, flags); + port = gser->ioport; + + if (!port) { + spin_unlock_irqrestore(&serial_port_lock, flags); + return; + } + + spin_lock(&port->port_lock); + spin_unlock(&serial_port_lock); port->suspended = false; if (!port->start_delayed) { spin_unlock_irqrestore(&port->port_lock, flags); @@ -1488,7 +1586,7 @@ static int __init userial_init(void) pr_debug("%s: registered %d ttyGS* device%s\n", __func__, MAX_U_SERIAL_PORTS, - (MAX_U_SERIAL_PORTS == 1) ? "" : "s"); + str_plural(MAX_U_SERIAL_PORTS)); return status; fail: @@ -1505,4 +1603,5 @@ static void __exit userial_cleanup(void) } module_exit(userial_cleanup); +MODULE_DESCRIPTION("utilities for USB gadget \"serial port\"/TTY support"); MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/function/u_serial.h b/drivers/usb/gadget/function/u_serial.h index 102a7323a1fd..e1274338ea61 100644 --- a/drivers/usb/gadget/function/u_serial.h +++ b/drivers/usb/gadget/function/u_serial.h @@ -17,6 +17,10 @@ struct f_serial_opts { struct usb_function_instance func_inst; u8 port_num; + u8 protocol; + + struct mutex lock; /* protect instances */ + int instances; }; /* @@ -71,8 +75,4 @@ void gserial_disconnect(struct gserial *); void gserial_suspend(struct gserial *p); void gserial_resume(struct gserial *p); -/* functions are bound to configurations by a config or gadget driver */ -int gser_bind_config(struct usb_configuration *c, u8 port_num); -int obex_bind_config(struct usb_configuration *c, u8 port_num); - #endif /* __U_SERIAL_H */ diff --git a/drivers/usb/gadget/function/u_uac1.h b/drivers/usb/gadget/function/u_uac1.h index f7a616760e31..feb6eb76462f 100644 --- a/drivers/usb/gadget/function/u_uac1.h +++ b/drivers/usb/gadget/function/u_uac1.h @@ -52,7 +52,17 @@ struct f_uac1_opts { int req_number; unsigned bound:1; - char function_name[32]; + char function_name[USB_MAX_STRING_LEN]; + + char p_it_name[USB_MAX_STRING_LEN]; + char p_it_ch_name[USB_MAX_STRING_LEN]; + char p_ot_name[USB_MAX_STRING_LEN]; + char p_fu_vol_name[USB_MAX_STRING_LEN]; + + char c_it_name[USB_MAX_STRING_LEN]; + char c_it_ch_name[USB_MAX_STRING_LEN]; + char c_ot_name[USB_MAX_STRING_LEN]; + char c_fu_vol_name[USB_MAX_STRING_LEN]; struct mutex lock; int refcnt; diff --git a/drivers/usb/gadget/function/u_uac2.h b/drivers/usb/gadget/function/u_uac2.h index 0510c9bad58d..0df808289ded 100644 --- a/drivers/usb/gadget/function/u_uac2.h +++ b/drivers/usb/gadget/function/u_uac2.h @@ -35,6 +35,11 @@ #define UAC2_DEF_REQ_NUM 2 #define UAC2_DEF_INT_REQ_NUM 10 +#define UAC2_DEF_P_TERM_TYPE 0x301 + /* UAC_OUTPUT_TERMINAL_SPEAKER */ +#define UAC2_DEF_C_TERM_TYPE 0x201 + /* UAC_INPUT_TERMINAL_MICROPHONE*/ + struct f_uac2_opts { struct usb_function_instance func_inst; int p_chmask; @@ -63,7 +68,23 @@ struct f_uac2_opts { int fb_max; bool bound; - char function_name[32]; + char function_name[USB_MAX_STRING_LEN]; + char if_ctrl_name[USB_MAX_STRING_LEN]; + char clksrc_in_name[USB_MAX_STRING_LEN]; + char clksrc_out_name[USB_MAX_STRING_LEN]; + + char p_it_name[USB_MAX_STRING_LEN]; + char p_it_ch_name[USB_MAX_STRING_LEN]; + char p_ot_name[USB_MAX_STRING_LEN]; + char p_fu_vol_name[USB_MAX_STRING_LEN]; + + char c_it_name[USB_MAX_STRING_LEN]; + char c_it_ch_name[USB_MAX_STRING_LEN]; + char c_ot_name[USB_MAX_STRING_LEN]; + char c_fu_vol_name[USB_MAX_STRING_LEN]; + + s16 p_terminal_type; + s16 c_terminal_type; struct mutex lock; int refcnt; diff --git a/drivers/usb/gadget/function/u_uvc.h b/drivers/usb/gadget/function/u_uvc.h index 24b8681b0d6f..3ac392cbb779 100644 --- a/drivers/usb/gadget/function/u_uvc.h +++ b/drivers/usb/gadget/function/u_uvc.h @@ -28,6 +28,9 @@ struct f_uvc_opts { unsigned int control_interface; unsigned int streaming_interface; char function_name[32]; + unsigned int last_unit_id; + + bool enable_interrupt_ep; /* * Control descriptors array pointers for full-/high-speed and @@ -52,7 +55,6 @@ struct f_uvc_opts { struct uvc_camera_terminal_descriptor uvc_camera_terminal; struct uvc_processing_unit_descriptor uvc_processing; struct uvc_output_terminal_descriptor uvc_output_terminal; - struct uvc_color_matching_descriptor uvc_color_matching; /* * Control descriptors pointers arrays for full-/high-speed and @@ -65,6 +67,12 @@ struct f_uvc_opts { struct uvc_descriptor_header *uvc_ss_control_cls[5]; /* + * Control descriptors for extension units. There could be any number + * of these, including none at all. + */ + struct list_head extension_units; + + /* * Streaming descriptors for full-speed, high-speed and super-speed. * Used by configfs only, must not be touched by legacy gadgets. The * arrays are allocated at runtime as the number of descriptors isn't @@ -75,6 +83,14 @@ struct f_uvc_opts { struct uvc_descriptor_header **uvc_ss_streaming_cls; /* + * Indexes into the function's string descriptors allowing users to set + * custom descriptions rather than the hard-coded defaults. + */ + u8 iad_index; + u8 vs0_index; + u8 vs1_index; + + /* * Read/write access to configfs attributes is handled by configfs. * * This lock protects the descriptors from concurrent access by @@ -82,6 +98,12 @@ struct f_uvc_opts { */ struct mutex lock; int refcnt; + + /* + * Only for legacy gadget. Shall be NULL for configfs-composed gadgets, + * which is guaranteed by alloc_inst implementation of f_uvc doing kzalloc. + */ + struct uvcg_streaming_header *header; }; #endif /* U_UVC_H */ diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h index 40226b1f7e14..9e79cbe50715 100644 --- a/drivers/usb/gadget/function/uvc.h +++ b/drivers/usb/gadget/function/uvc.h @@ -71,6 +71,11 @@ extern unsigned int uvc_gadget_trace_param; #define UVCG_REQUEST_HEADER_LEN 12 +#define UVCG_REQ_MAX_INT_COUNT 16 +#define UVCG_REQ_MAX_ZERO_COUNT (2 * UVCG_REQ_MAX_INT_COUNT) + +#define UVCG_STREAMING_MIN_BUFFERS 2 + /* ------------------------------------------------------------------------ * Structures */ @@ -81,6 +86,7 @@ struct uvc_request { struct sg_table sgt; u8 header[UVCG_REQUEST_HEADER_LEN]; struct uvc_buffer *last_buf; + struct list_head list; }; struct uvc_video { @@ -90,20 +96,37 @@ struct uvc_video { struct work_struct pump; struct workqueue_struct *async_wq; + struct kthread_worker *kworker; + struct kthread_work hw_submit; + + atomic_t queued; + /* Frame parameters */ u8 bpp; u32 fcc; unsigned int width; unsigned int height; unsigned int imagesize; + unsigned int interval; struct mutex mutex; /* protects frame parameters */ unsigned int uvc_num_requests; + unsigned int reqs_per_frame; + /* Requests */ + bool is_enabled; /* tracks whether video stream is enabled */ unsigned int req_size; - struct uvc_request *ureq; + struct list_head ureqs; /* all uvc_requests allocated by uvc_video */ + + /* USB requests that the video pump thread can encode into */ struct list_head req_free; + + /* + * USB requests video pump thread has already encoded into. These are + * ready to be queued to the endpoint. + */ + struct list_head req_ready; spinlock_t req_lock; unsigned int req_int_count; @@ -143,12 +166,14 @@ struct uvc_device { const struct uvc_descriptor_header * const *fs_streaming; const struct uvc_descriptor_header * const *hs_streaming; const struct uvc_descriptor_header * const *ss_streaming; + struct list_head *extension_units; } desc; unsigned int control_intf; - struct usb_ep *control_ep; + struct usb_ep *interrupt_ep; struct usb_request *control_req; void *control_buf; + bool enable_interrupt_ep; unsigned int streaming_intf; @@ -171,13 +196,16 @@ struct uvc_file_handle { #define to_uvc_file_handle(handle) \ container_of(handle, struct uvc_file_handle, vfh) +static inline struct uvc_file_handle *file_to_uvc_file_handle(struct file *filp) +{ + return container_of(file_to_v4l2_fh(filp), struct uvc_file_handle, vfh); +} + /* ------------------------------------------------------------------------ * Functions */ -extern void uvc_function_setup_continue(struct uvc_device *uvc); -extern void uvc_endpoint_stream(struct uvc_device *dev); - +extern void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep); extern void uvc_function_connect(struct uvc_device *uvc); extern void uvc_function_disconnect(struct uvc_device *uvc); diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index 76cb60d13049..a4a2d3dcb0d6 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -13,6 +13,8 @@ #include "uvc_configfs.h" #include <linux/sort.h> +#include <linux/usb/uvc.h> +#include <linux/usb/video.h> /* ----------------------------------------------------------------------------- * Global Utility Structures and Macros @@ -46,6 +48,71 @@ static int uvcg_config_compare_u32(const void *l, const void *r) return li < ri ? -1 : li == ri ? 0 : 1; } +static inline int __uvcg_count_item_entries(char *buf, void *priv, unsigned int size) +{ + ++*((int *)priv); + return 0; +} + +static inline int __uvcg_fill_item_entries(char *buf, void *priv, unsigned int size) +{ + unsigned int num; + u8 **values; + int ret; + + ret = kstrtouint(buf, 0, &num); + if (ret) + return ret; + + if (num != (num & GENMASK((size * 8) - 1, 0))) + return -ERANGE; + + values = priv; + memcpy(*values, &num, size); + *values += size; + + return 0; +} + +static int __uvcg_iter_item_entries(const char *page, size_t len, + int (*fun)(char *, void *, unsigned int), + void *priv, unsigned int size) +{ + /* sign, base 2 representation, newline, terminator */ + unsigned int bufsize = 1 + size * 8 + 1 + 1; + const char *pg = page; + int i, ret = 0; + char *buf; + + if (!fun) + return -EINVAL; + + buf = kzalloc(bufsize, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + while (pg - page < len) { + i = 0; + while (i < bufsize && (pg - page < len) && + *pg != '\0' && *pg != '\n') + buf[i++] = *pg++; + if (i == bufsize) { + ret = -EINVAL; + goto out_free_buf; + } + while ((pg - page < len) && (*pg == '\0' || *pg == '\n')) + ++pg; + buf[i] = '\0'; + ret = fun(buf, priv, size); + if (ret) + goto out_free_buf; + } + +out_free_buf: + kfree(buf); + return ret; +} + struct uvcg_config_group_type { struct config_item_type type; const char *name; @@ -268,6 +335,64 @@ UVCG_DEFAULT_PROCESSING_ATTR(i_processing, iProcessing, 8); #undef UVCG_DEFAULT_PROCESSING_ATTR +static ssize_t uvcg_default_processing_bm_controls_store( + struct config_item *item, const char *page, size_t len) +{ + struct config_group *group = to_config_group(item); + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct uvc_processing_unit_descriptor *pd; + struct config_item *opts_item; + struct f_uvc_opts *opts; + u8 *bm_controls, *tmp; + unsigned int i; + int ret, n = 0; + + mutex_lock(su_mutex); + + opts_item = group->cg_item.ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + pd = &opts->uvc_processing; + + mutex_lock(&opts->lock); + if (opts->refcnt) { + ret = -EBUSY; + goto unlock; + } + + ret = __uvcg_iter_item_entries(page, len, __uvcg_count_item_entries, &n, + sizeof(u8)); + if (ret) + goto unlock; + + if (n > pd->bControlSize) { + ret = -EINVAL; + goto unlock; + } + + tmp = bm_controls = kcalloc(n, sizeof(u8), GFP_KERNEL); + if (!bm_controls) { + ret = -ENOMEM; + goto unlock; + } + + ret = __uvcg_iter_item_entries(page, len, __uvcg_fill_item_entries, &tmp, + sizeof(u8)); + if (ret) + goto free_mem; + + for (i = 0; i < n; i++) + pd->bmControls[i] = bm_controls[i]; + + ret = len; + +free_mem: + kfree(bm_controls); +unlock: + mutex_unlock(&opts->lock); + mutex_unlock(su_mutex); + return ret; +} + static ssize_t uvcg_default_processing_bm_controls_show( struct config_item *item, char *page) { @@ -297,7 +422,7 @@ static ssize_t uvcg_default_processing_bm_controls_show( return result; } -UVC_ATTR_RO(uvcg_default_processing_, bm_controls, bmControls); +UVC_ATTR(uvcg_default_processing_, bm_controls, bmControls); static struct configfs_attribute *uvcg_default_processing_attrs[] = { &uvcg_default_processing_attr_b_unit_id, @@ -379,6 +504,65 @@ UVCG_DEFAULT_CAMERA_ATTR(w_ocular_focal_length, wOcularFocalLength, #undef UVCG_DEFAULT_CAMERA_ATTR +static ssize_t uvcg_default_camera_bm_controls_store( + struct config_item *item, const char *page, size_t len) +{ + struct config_group *group = to_config_group(item); + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct uvc_camera_terminal_descriptor *cd; + struct config_item *opts_item; + struct f_uvc_opts *opts; + u8 *bm_controls, *tmp; + unsigned int i; + int ret, n = 0; + + mutex_lock(su_mutex); + + opts_item = group->cg_item.ci_parent->ci_parent->ci_parent-> + ci_parent; + opts = to_f_uvc_opts(opts_item); + cd = &opts->uvc_camera_terminal; + + mutex_lock(&opts->lock); + if (opts->refcnt) { + ret = -EBUSY; + goto unlock; + } + + ret = __uvcg_iter_item_entries(page, len, __uvcg_count_item_entries, &n, + sizeof(u8)); + if (ret) + goto unlock; + + if (n > cd->bControlSize) { + ret = -EINVAL; + goto unlock; + } + + tmp = bm_controls = kcalloc(n, sizeof(u8), GFP_KERNEL); + if (!bm_controls) { + ret = -ENOMEM; + goto unlock; + } + + ret = __uvcg_iter_item_entries(page, len, __uvcg_fill_item_entries, &tmp, + sizeof(u8)); + if (ret) + goto free_mem; + + for (i = 0; i < n; i++) + cd->bmControls[i] = bm_controls[i]; + + ret = len; + +free_mem: + kfree(bm_controls); +unlock: + mutex_unlock(&opts->lock); + mutex_unlock(su_mutex); + return ret; +} + static ssize_t uvcg_default_camera_bm_controls_show( struct config_item *item, char *page) { @@ -408,7 +592,7 @@ static ssize_t uvcg_default_camera_bm_controls_show( return result; } -UVC_ATTR_RO(uvcg_default_camera_, bm_controls, bmControls); +UVC_ATTR(uvcg_default_camera_, bm_controls, bmControls); static struct configfs_attribute *uvcg_default_camera_attrs[] = { &uvcg_default_camera_attr_b_terminal_id, @@ -483,11 +667,68 @@ UVC_ATTR_RO(uvcg_default_output_, cname, aname) UVCG_DEFAULT_OUTPUT_ATTR(b_terminal_id, bTerminalID, 8); UVCG_DEFAULT_OUTPUT_ATTR(w_terminal_type, wTerminalType, 16); UVCG_DEFAULT_OUTPUT_ATTR(b_assoc_terminal, bAssocTerminal, 8); -UVCG_DEFAULT_OUTPUT_ATTR(b_source_id, bSourceID, 8); UVCG_DEFAULT_OUTPUT_ATTR(i_terminal, iTerminal, 8); #undef UVCG_DEFAULT_OUTPUT_ATTR +static ssize_t uvcg_default_output_b_source_id_show(struct config_item *item, + char *page) +{ + struct config_group *group = to_config_group(item); + struct f_uvc_opts *opts; + struct config_item *opts_item; + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct uvc_output_terminal_descriptor *cd; + int result; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = group->cg_item.ci_parent->ci_parent-> + ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + cd = &opts->uvc_output_terminal; + + mutex_lock(&opts->lock); + result = sprintf(page, "%u\n", le8_to_cpu(cd->bSourceID)); + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + + return result; +} + +static ssize_t uvcg_default_output_b_source_id_store(struct config_item *item, + const char *page, size_t len) +{ + struct config_group *group = to_config_group(item); + struct f_uvc_opts *opts; + struct config_item *opts_item; + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct uvc_output_terminal_descriptor *cd; + int result; + u8 num; + + result = kstrtou8(page, 0, &num); + if (result) + return result; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = group->cg_item.ci_parent->ci_parent-> + ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + cd = &opts->uvc_output_terminal; + + mutex_lock(&opts->lock); + cd->bSourceID = num; + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + + return len; +} +UVC_ATTR(uvcg_default_output_, b_source_id, bSourceID); + static struct configfs_attribute *uvcg_default_output_attrs[] = { &uvcg_default_output_attr_b_terminal_id, &uvcg_default_output_attr_w_terminal_type, @@ -540,6 +781,537 @@ static const struct uvcg_config_group_type uvcg_terminal_grp_type = { }; /* ----------------------------------------------------------------------------- + * control/extensions + */ + +#define UVCG_EXTENSION_ATTR(cname, aname, ro...) \ +static ssize_t uvcg_extension_##cname##_show(struct config_item *item, \ + char *page) \ +{ \ + struct config_group *group = to_config_group(item->ci_parent); \ + struct mutex *su_mutex = &group->cg_subsys->su_mutex; \ + struct uvcg_extension *xu = to_uvcg_extension(item); \ + struct config_item *opts_item; \ + struct f_uvc_opts *opts; \ + int ret; \ + \ + mutex_lock(su_mutex); \ + \ + opts_item = item->ci_parent->ci_parent->ci_parent; \ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + ret = sprintf(page, "%u\n", xu->desc.aname); \ + mutex_unlock(&opts->lock); \ + \ + mutex_unlock(su_mutex); \ + \ + return ret; \ +} \ +UVC_ATTR##ro(uvcg_extension_, cname, aname) + +UVCG_EXTENSION_ATTR(b_length, bLength, _RO); +UVCG_EXTENSION_ATTR(b_unit_id, bUnitID, _RO); +UVCG_EXTENSION_ATTR(i_extension, iExtension, _RO); + +static ssize_t uvcg_extension_b_num_controls_store(struct config_item *item, + const char *page, size_t len) +{ + struct config_group *group = to_config_group(item->ci_parent); + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct uvcg_extension *xu = to_uvcg_extension(item); + struct config_item *opts_item; + struct f_uvc_opts *opts; + int ret; + u8 num; + + ret = kstrtou8(page, 0, &num); + if (ret) + return ret; + + mutex_lock(su_mutex); + + opts_item = item->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + xu->desc.bNumControls = num; + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + + return len; +} +UVCG_EXTENSION_ATTR(b_num_controls, bNumControls); + +/* + * In addition to storing bNrInPins, this function needs to realloc the + * memory for the baSourceID array and additionally expand bLength. + */ +static ssize_t uvcg_extension_b_nr_in_pins_store(struct config_item *item, + const char *page, size_t len) +{ + struct config_group *group = to_config_group(item->ci_parent); + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct uvcg_extension *xu = to_uvcg_extension(item); + struct config_item *opts_item; + struct f_uvc_opts *opts; + void *tmp_buf; + int ret; + u8 num; + + ret = kstrtou8(page, 0, &num); + if (ret) + return ret; + + mutex_lock(su_mutex); + + opts_item = item->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + + if (num == xu->desc.bNrInPins) { + ret = len; + goto unlock; + } + + tmp_buf = krealloc_array(xu->desc.baSourceID, num, sizeof(u8), + GFP_KERNEL | __GFP_ZERO); + if (!tmp_buf) { + ret = -ENOMEM; + goto unlock; + } + + xu->desc.baSourceID = tmp_buf; + xu->desc.bNrInPins = num; + xu->desc.bLength = UVC_DT_EXTENSION_UNIT_SIZE(xu->desc.bNrInPins, + xu->desc.bControlSize); + + ret = len; + +unlock: + mutex_unlock(&opts->lock); + mutex_unlock(su_mutex); + return ret; +} +UVCG_EXTENSION_ATTR(b_nr_in_pins, bNrInPins); + +/* + * In addition to storing bControlSize, this function needs to realloc the + * memory for the bmControls array and additionally expand bLength. + */ +static ssize_t uvcg_extension_b_control_size_store(struct config_item *item, + const char *page, size_t len) +{ + struct config_group *group = to_config_group(item->ci_parent); + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct uvcg_extension *xu = to_uvcg_extension(item); + struct config_item *opts_item; + struct f_uvc_opts *opts; + void *tmp_buf; + int ret; + u8 num; + + ret = kstrtou8(page, 0, &num); + if (ret) + return ret; + + mutex_lock(su_mutex); + + opts_item = item->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + + if (num == xu->desc.bControlSize) { + ret = len; + goto unlock; + } + + tmp_buf = krealloc_array(xu->desc.bmControls, num, sizeof(u8), + GFP_KERNEL | __GFP_ZERO); + if (!tmp_buf) { + ret = -ENOMEM; + goto unlock; + } + + xu->desc.bmControls = tmp_buf; + xu->desc.bControlSize = num; + xu->desc.bLength = UVC_DT_EXTENSION_UNIT_SIZE(xu->desc.bNrInPins, + xu->desc.bControlSize); + + ret = len; + +unlock: + mutex_unlock(&opts->lock); + mutex_unlock(su_mutex); + return ret; +} + +UVCG_EXTENSION_ATTR(b_control_size, bControlSize); + +static ssize_t uvcg_extension_guid_extension_code_show(struct config_item *item, + char *page) +{ + struct config_group *group = to_config_group(item->ci_parent); + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct uvcg_extension *xu = to_uvcg_extension(item); + struct config_item *opts_item; + struct f_uvc_opts *opts; + + mutex_lock(su_mutex); + + opts_item = item->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + memcpy(page, xu->desc.guidExtensionCode, sizeof(xu->desc.guidExtensionCode)); + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + + return sizeof(xu->desc.guidExtensionCode); +} + +static ssize_t uvcg_extension_guid_extension_code_store(struct config_item *item, + const char *page, size_t len) +{ + struct config_group *group = to_config_group(item->ci_parent); + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct uvcg_extension *xu = to_uvcg_extension(item); + struct config_item *opts_item; + struct f_uvc_opts *opts; + int ret; + + mutex_lock(su_mutex); + + opts_item = item->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + memcpy(xu->desc.guidExtensionCode, page, + min(sizeof(xu->desc.guidExtensionCode), len)); + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + + ret = sizeof(xu->desc.guidExtensionCode); + + return ret; +} + +UVC_ATTR(uvcg_extension_, guid_extension_code, guidExtensionCode); + +static ssize_t uvcg_extension_ba_source_id_show(struct config_item *item, + char *page) +{ + struct config_group *group = to_config_group(item->ci_parent); + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct uvcg_extension *xu = to_uvcg_extension(item); + struct config_item *opts_item; + struct f_uvc_opts *opts; + char *pg = page; + int ret, i; + + mutex_lock(su_mutex); + + opts_item = item->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + for (ret = 0, i = 0; i < xu->desc.bNrInPins; ++i) { + ret += sprintf(pg, "%u\n", xu->desc.baSourceID[i]); + pg = page + ret; + } + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + + return ret; +} + +static ssize_t uvcg_extension_ba_source_id_store(struct config_item *item, + const char *page, size_t len) +{ + struct config_group *group = to_config_group(item->ci_parent); + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct uvcg_extension *xu = to_uvcg_extension(item); + struct config_item *opts_item; + struct f_uvc_opts *opts; + u8 *source_ids, *iter; + int ret, n = 0; + + mutex_lock(su_mutex); + + opts_item = item->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + + ret = __uvcg_iter_item_entries(page, len, __uvcg_count_item_entries, &n, + sizeof(u8)); + if (ret) + goto unlock; + + iter = source_ids = kcalloc(n, sizeof(u8), GFP_KERNEL); + if (!source_ids) { + ret = -ENOMEM; + goto unlock; + } + + ret = __uvcg_iter_item_entries(page, len, __uvcg_fill_item_entries, &iter, + sizeof(u8)); + if (ret) { + kfree(source_ids); + goto unlock; + } + + kfree(xu->desc.baSourceID); + xu->desc.baSourceID = source_ids; + xu->desc.bNrInPins = n; + xu->desc.bLength = UVC_DT_EXTENSION_UNIT_SIZE(xu->desc.bNrInPins, + xu->desc.bControlSize); + + ret = len; + +unlock: + mutex_unlock(&opts->lock); + mutex_unlock(su_mutex); + return ret; +} +UVC_ATTR(uvcg_extension_, ba_source_id, baSourceID); + +static ssize_t uvcg_extension_bm_controls_show(struct config_item *item, + char *page) +{ + struct config_group *group = to_config_group(item->ci_parent); + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct uvcg_extension *xu = to_uvcg_extension(item); + struct config_item *opts_item; + struct f_uvc_opts *opts; + char *pg = page; + int ret, i; + + mutex_lock(su_mutex); + + opts_item = item->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + for (ret = 0, i = 0; i < xu->desc.bControlSize; ++i) { + ret += sprintf(pg, "0x%02x\n", xu->desc.bmControls[i]); + pg = page + ret; + } + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + + return ret; +} + +static ssize_t uvcg_extension_bm_controls_store(struct config_item *item, + const char *page, size_t len) +{ + struct config_group *group = to_config_group(item->ci_parent); + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct uvcg_extension *xu = to_uvcg_extension(item); + struct config_item *opts_item; + struct f_uvc_opts *opts; + u8 *bm_controls, *iter; + int ret, n = 0; + + mutex_lock(su_mutex); + + opts_item = item->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + + ret = __uvcg_iter_item_entries(page, len, __uvcg_count_item_entries, &n, + sizeof(u8)); + if (ret) + goto unlock; + + iter = bm_controls = kcalloc(n, sizeof(u8), GFP_KERNEL); + if (!bm_controls) { + ret = -ENOMEM; + goto unlock; + } + + ret = __uvcg_iter_item_entries(page, len, __uvcg_fill_item_entries, &iter, + sizeof(u8)); + if (ret) { + kfree(bm_controls); + goto unlock; + } + + kfree(xu->desc.bmControls); + xu->desc.bmControls = bm_controls; + xu->desc.bControlSize = n; + xu->desc.bLength = UVC_DT_EXTENSION_UNIT_SIZE(xu->desc.bNrInPins, + xu->desc.bControlSize); + + ret = len; + +unlock: + mutex_unlock(&opts->lock); + mutex_unlock(su_mutex); + return ret; +} + +UVC_ATTR(uvcg_extension_, bm_controls, bmControls); + +static struct configfs_attribute *uvcg_extension_attrs[] = { + &uvcg_extension_attr_b_length, + &uvcg_extension_attr_b_unit_id, + &uvcg_extension_attr_b_num_controls, + &uvcg_extension_attr_b_nr_in_pins, + &uvcg_extension_attr_b_control_size, + &uvcg_extension_attr_guid_extension_code, + &uvcg_extension_attr_ba_source_id, + &uvcg_extension_attr_bm_controls, + &uvcg_extension_attr_i_extension, + NULL, +}; + +static void uvcg_extension_release(struct config_item *item) +{ + struct uvcg_extension *xu = container_of(item, struct uvcg_extension, item); + + kfree(xu); +} + +static int uvcg_extension_allow_link(struct config_item *src, struct config_item *tgt) +{ + struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex; + struct uvcg_extension *xu = to_uvcg_extension(src); + struct config_item *gadget_item; + struct gadget_string *string; + struct config_item *strings; + int ret = 0; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + /* Validate that the target of the link is an entry in strings/<langid> */ + gadget_item = src->ci_parent->ci_parent->ci_parent->ci_parent->ci_parent; + strings = config_group_find_item(to_config_group(gadget_item), "strings"); + if (!strings || tgt->ci_parent->ci_parent != strings) { + ret = -EINVAL; + goto put_strings; + } + + string = to_gadget_string(tgt); + xu->string_descriptor_index = string->usb_string.id; + +put_strings: + config_item_put(strings); + mutex_unlock(su_mutex); + + return ret; +} + +static void uvcg_extension_drop_link(struct config_item *src, struct config_item *tgt) +{ + struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex; + struct uvcg_extension *xu = to_uvcg_extension(src); + struct config_item *opts_item; + struct f_uvc_opts *opts; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = src->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + + xu->string_descriptor_index = 0; + + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); +} + +static struct configfs_item_operations uvcg_extension_item_ops = { + .release = uvcg_extension_release, + .allow_link = uvcg_extension_allow_link, + .drop_link = uvcg_extension_drop_link, +}; + +static const struct config_item_type uvcg_extension_type = { + .ct_item_ops = &uvcg_extension_item_ops, + .ct_attrs = uvcg_extension_attrs, + .ct_owner = THIS_MODULE, +}; + +static void uvcg_extension_drop(struct config_group *group, struct config_item *item) +{ + struct uvcg_extension *xu = container_of(item, struct uvcg_extension, item); + struct config_item *opts_item; + struct f_uvc_opts *opts; + + opts_item = group->cg_item.ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + + config_item_put(item); + list_del(&xu->list); + kfree(xu->desc.baSourceID); + kfree(xu->desc.bmControls); + + mutex_unlock(&opts->lock); +} + +static struct config_item *uvcg_extension_make(struct config_group *group, const char *name) +{ + struct config_item *opts_item; + struct uvcg_extension *xu; + struct f_uvc_opts *opts; + + opts_item = group->cg_item.ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + xu = kzalloc(sizeof(*xu), GFP_KERNEL); + if (!xu) + return ERR_PTR(-ENOMEM); + + xu->desc.bLength = UVC_DT_EXTENSION_UNIT_SIZE(0, 0); + xu->desc.bDescriptorType = USB_DT_CS_INTERFACE; + xu->desc.bDescriptorSubType = UVC_VC_EXTENSION_UNIT; + xu->desc.bNumControls = 0; + xu->desc.bNrInPins = 0; + xu->desc.baSourceID = NULL; + xu->desc.bControlSize = 0; + xu->desc.bmControls = NULL; + + mutex_lock(&opts->lock); + + xu->desc.bUnitID = ++opts->last_unit_id; + + config_item_init_type_name(&xu->item, name, &uvcg_extension_type); + list_add_tail(&xu->list, &opts->extension_units); + + mutex_unlock(&opts->lock); + + return &xu->item; +} + +static struct configfs_group_operations uvcg_extensions_grp_ops = { + .make_item = uvcg_extension_make, + .drop_item = uvcg_extension_drop, +}; + +static const struct uvcg_config_group_type uvcg_extensions_grp_type = { + .type = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_group_ops = &uvcg_extensions_grp_ops, + .ct_owner = THIS_MODULE, + }, + .name = "extensions", +}; + +/* ----------------------------------------------------------------------------- * control/class/{fs|ss} */ @@ -716,8 +1488,61 @@ static ssize_t uvcg_default_control_b_interface_number_show( UVC_ATTR_RO(uvcg_default_control_, b_interface_number, bInterfaceNumber); +static ssize_t uvcg_default_control_enable_interrupt_ep_show( + struct config_item *item, char *page) +{ + struct config_group *group = to_config_group(item); + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct config_item *opts_item; + struct f_uvc_opts *opts; + int result = 0; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = item->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + result += sprintf(page, "%u\n", opts->enable_interrupt_ep); + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + + return result; +} + +static ssize_t uvcg_default_control_enable_interrupt_ep_store( + struct config_item *item, const char *page, size_t len) +{ + struct config_group *group = to_config_group(item); + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct config_item *opts_item; + struct f_uvc_opts *opts; + ssize_t ret; + u8 num; + + ret = kstrtou8(page, 0, &num); + if (ret) + return ret; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = item->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + opts->enable_interrupt_ep = num; + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + + return len; +} +UVC_ATTR(uvcg_default_control_, enable_interrupt_ep, enable_interrupt_ep); + static struct configfs_attribute *uvcg_default_control_attrs[] = { &uvcg_default_control_attr_b_interface_number, + &uvcg_default_control_attr_enable_interrupt_ep, NULL, }; @@ -733,6 +1558,7 @@ static const struct uvcg_config_group_type uvcg_control_grp_type = { &uvcg_processing_grp_type, &uvcg_terminal_grp_type, &uvcg_control_class_grp_type, + &uvcg_extensions_grp_type, NULL, }, }; @@ -740,11 +1566,107 @@ static const struct uvcg_config_group_type uvcg_control_grp_type = { /* ----------------------------------------------------------------------------- * streaming/uncompressed * streaming/mjpeg + * streaming/framebased */ static const char * const uvcg_format_names[] = { "uncompressed", "mjpeg", + "framebased", +}; + +static struct uvcg_color_matching * +uvcg_format_get_default_color_match(struct config_item *streaming) +{ + struct config_item *color_matching_item, *cm_default; + struct uvcg_color_matching *color_match; + + color_matching_item = config_group_find_item(to_config_group(streaming), + "color_matching"); + if (!color_matching_item) + return NULL; + + cm_default = config_group_find_item(to_config_group(color_matching_item), + "default"); + config_item_put(color_matching_item); + if (!cm_default) + return NULL; + + color_match = to_uvcg_color_matching(to_config_group(cm_default)); + config_item_put(cm_default); + + return color_match; +} + +static int uvcg_format_allow_link(struct config_item *src, struct config_item *tgt) +{ + struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex; + struct uvcg_color_matching *color_matching_desc; + struct config_item *streaming, *color_matching; + struct uvcg_format *fmt; + int ret = 0; + + mutex_lock(su_mutex); + + streaming = src->ci_parent->ci_parent; + color_matching = config_group_find_item(to_config_group(streaming), "color_matching"); + if (!color_matching || color_matching != tgt->ci_parent) { + ret = -EINVAL; + goto out_put_cm; + } + + fmt = to_uvcg_format(src); + + /* + * There's always a color matching descriptor associated with the format + * but without a symlink it should only ever be the default one. If it's + * not the default, there's already a symlink and we should bail out. + */ + color_matching_desc = uvcg_format_get_default_color_match(streaming); + if (fmt->color_matching != color_matching_desc) { + ret = -EBUSY; + goto out_put_cm; + } + + color_matching_desc->refcnt--; + + color_matching_desc = to_uvcg_color_matching(to_config_group(tgt)); + fmt->color_matching = color_matching_desc; + color_matching_desc->refcnt++; + +out_put_cm: + config_item_put(color_matching); + mutex_unlock(su_mutex); + + return ret; +} + +static void uvcg_format_drop_link(struct config_item *src, struct config_item *tgt) +{ + struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex; + struct uvcg_color_matching *color_matching_desc; + struct config_item *streaming; + struct uvcg_format *fmt; + + mutex_lock(su_mutex); + + color_matching_desc = to_uvcg_color_matching(to_config_group(tgt)); + color_matching_desc->refcnt--; + + streaming = src->ci_parent->ci_parent; + color_matching_desc = uvcg_format_get_default_color_match(streaming); + + fmt = to_uvcg_format(src); + fmt->color_matching = color_matching_desc; + color_matching_desc->refcnt++; + + mutex_unlock(su_mutex); +} + +static struct configfs_item_operations uvcg_format_item_operations = { + .release = uvcg_config_item_release, + .allow_link = uvcg_format_allow_link, + .drop_link = uvcg_format_drop_link, }; static ssize_t uvcg_format_bma_controls_show(struct uvcg_format *f, char *page) @@ -857,6 +1779,9 @@ static int uvcg_streaming_header_allow_link(struct config_item *src, target_fmt = container_of(to_config_group(target), struct uvcg_format, group); + if (!target_fmt) + goto out; + uvcg_format_set_indices(to_config_group(target)); format_ptr = kzalloc(sizeof(*format_ptr), GFP_KERNEL); @@ -896,6 +1821,9 @@ static void uvcg_streaming_header_drop_link(struct config_item *src, target_fmt = container_of(to_config_group(target), struct uvcg_format, group); + if (!target_fmt) + goto out; + list_for_each_entry_safe(format_ptr, tmp, &src_hdr->formats, entry) if (format_ptr->fmt == target_fmt) { list_del(&format_ptr->entry); @@ -906,6 +1834,7 @@ static void uvcg_streaming_header_drop_link(struct config_item *src, --target_fmt->linked; +out: mutex_unlock(&opts->lock); mutex_unlock(su_mutex); } @@ -1102,6 +2031,7 @@ UVCG_FRAME_ATTR(dw_min_bit_rate, dwMinBitRate, 32); UVCG_FRAME_ATTR(dw_max_bit_rate, dwMaxBitRate, 32); UVCG_FRAME_ATTR(dw_max_video_frame_buffer_size, dwMaxVideoFrameBufferSize, 32); UVCG_FRAME_ATTR(dw_default_frame_interval, dwDefaultFrameInterval, 32); +UVCG_FRAME_ATTR(dw_bytes_perline, dwBytesPerLine, 32); #undef UVCG_FRAME_ATTR @@ -1115,7 +2045,7 @@ static ssize_t uvcg_frame_dw_frame_interval_show(struct config_item *item, int result, i; char *pg = page; - mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ opts_item = frm->item.ci_parent->ci_parent->ci_parent->ci_parent; opts = to_f_uvc_opts(opts_item); @@ -1131,57 +2061,6 @@ static ssize_t uvcg_frame_dw_frame_interval_show(struct config_item *item, return result; } -static inline int __uvcg_count_frm_intrv(char *buf, void *priv) -{ - ++*((int *)priv); - return 0; -} - -static inline int __uvcg_fill_frm_intrv(char *buf, void *priv) -{ - u32 num, **interv; - int ret; - - ret = kstrtou32(buf, 0, &num); - if (ret) - return ret; - - interv = priv; - **interv = num; - ++*interv; - - return 0; -} - -static int __uvcg_iter_frm_intrv(const char *page, size_t len, - int (*fun)(char *, void *), void *priv) -{ - /* sign, base 2 representation, newline, terminator */ - char buf[1 + sizeof(u32) * 8 + 1 + 1]; - const char *pg = page; - int i, ret; - - if (!fun) - return -EINVAL; - - while (pg - page < len) { - i = 0; - while (i < sizeof(buf) && (pg - page < len) && - *pg != '\0' && *pg != '\n') - buf[i++] = *pg++; - if (i == sizeof(buf)) - return -EINVAL; - while ((pg - page < len) && (*pg == '\0' || *pg == '\n')) - ++pg; - buf[i] = '\0'; - ret = fun(buf, priv); - if (ret) - return ret; - } - - return 0; -} - static ssize_t uvcg_frame_dw_frame_interval_store(struct config_item *item, const char *page, size_t len) { @@ -1205,7 +2084,7 @@ static ssize_t uvcg_frame_dw_frame_interval_store(struct config_item *item, goto end; } - ret = __uvcg_iter_frm_intrv(page, len, __uvcg_count_frm_intrv, &n); + ret = __uvcg_iter_item_entries(page, len, __uvcg_count_item_entries, &n, sizeof(u32)); if (ret) goto end; @@ -1215,7 +2094,7 @@ static ssize_t uvcg_frame_dw_frame_interval_store(struct config_item *item, goto end; } - ret = __uvcg_iter_frm_intrv(page, len, __uvcg_fill_frm_intrv, &tmp); + ret = __uvcg_iter_item_entries(page, len, __uvcg_fill_item_entries, &tmp, sizeof(u32)); if (ret) { kfree(frm_intrv); goto end; @@ -1236,7 +2115,7 @@ end: UVC_ATTR(uvcg_frame_, dw_frame_interval, dwFrameInterval); -static struct configfs_attribute *uvcg_frame_attrs[] = { +static struct configfs_attribute *uvcg_frame_attrs1[] = { &uvcg_frame_attr_b_frame_index, &uvcg_frame_attr_bm_capabilities, &uvcg_frame_attr_w_width, @@ -1249,12 +2128,31 @@ static struct configfs_attribute *uvcg_frame_attrs[] = { NULL, }; -static const struct config_item_type uvcg_frame_type = { +static struct configfs_attribute *uvcg_frame_attrs2[] = { + &uvcg_frame_attr_b_frame_index, + &uvcg_frame_attr_bm_capabilities, + &uvcg_frame_attr_w_width, + &uvcg_frame_attr_w_height, + &uvcg_frame_attr_dw_min_bit_rate, + &uvcg_frame_attr_dw_max_bit_rate, + &uvcg_frame_attr_dw_default_frame_interval, + &uvcg_frame_attr_dw_frame_interval, + &uvcg_frame_attr_dw_bytes_perline, + NULL, +}; + +static const struct config_item_type uvcg_frame_type1 = { .ct_item_ops = &uvcg_config_item_ops, - .ct_attrs = uvcg_frame_attrs, + .ct_attrs = uvcg_frame_attrs1, .ct_owner = THIS_MODULE, }; +static const struct config_item_type uvcg_frame_type2 = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_attrs = uvcg_frame_attrs2, + .ct_owner = THIS_MODULE, +}; + static struct config_item *uvcg_frame_make(struct config_group *group, const char *name) { @@ -1276,6 +2174,7 @@ static struct config_item *uvcg_frame_make(struct config_group *group, h->frame.dw_max_bit_rate = 55296000; h->frame.dw_max_video_frame_buffer_size = 460800; h->frame.dw_default_frame_interval = 666666; + h->frame.dw_bytes_perline = 0; opts_item = group->cg_item.ci_parent->ci_parent->ci_parent; opts = to_f_uvc_opts(opts_item); @@ -1288,6 +2187,9 @@ static struct config_item *uvcg_frame_make(struct config_group *group, } else if (fmt->type == UVCG_MJPEG) { h->frame.b_descriptor_subtype = UVC_VS_FRAME_MJPEG; h->fmt_type = UVCG_MJPEG; + } else if (fmt->type == UVCG_FRAMEBASED) { + h->frame.b_descriptor_subtype = UVC_VS_FRAME_FRAME_BASED; + h->fmt_type = UVCG_FRAMEBASED; } else { mutex_unlock(&opts->lock); kfree(h); @@ -1306,7 +2208,10 @@ static struct config_item *uvcg_frame_make(struct config_group *group, ++fmt->num_frames; mutex_unlock(&opts->lock); - config_item_init_type_name(&h->item, name, &uvcg_frame_type); + if (fmt->type == UVCG_FRAMEBASED) + config_item_init_type_name(&h->item, name, &uvcg_frame_type2); + else + config_item_init_type_name(&h->item, name, &uvcg_frame_type1); return &h->item; } @@ -1346,9 +2251,6 @@ static void uvcg_format_set_indices(struct config_group *fmt) list_for_each_entry(ci, &fmt->cg_children, ci_entry) { struct uvcg_frame *frm; - if (ci->ci_type != &uvcg_frame_type) - continue; - frm = to_uvcg_frame(ci); frm->frame.b_frame_index = i++; } @@ -1392,6 +2294,8 @@ static ssize_t uvcg_uncompressed_guid_format_store(struct config_item *item, struct f_uvc_opts *opts; struct config_item *opts_item; struct mutex *su_mutex = &ch->fmt.group.cg_subsys->su_mutex; + const struct uvc_format_desc *format; + u8 tmpguidFormat[sizeof(ch->desc.guidFormat)]; int ret; mutex_lock(su_mutex); /* for navigating configfs hierarchy */ @@ -1405,7 +2309,16 @@ static ssize_t uvcg_uncompressed_guid_format_store(struct config_item *item, goto end; } - memcpy(ch->desc.guidFormat, page, + memcpy(tmpguidFormat, page, + min(sizeof(tmpguidFormat), len)); + + format = uvc_format_by_guid(tmpguidFormat); + if (!format) { + ret = -EINVAL; + goto end; + } + + memcpy(ch->desc.guidFormat, tmpguidFormat, min(sizeof(ch->desc.guidFormat), len)); ret = sizeof(ch->desc.guidFormat); @@ -1547,7 +2460,7 @@ static struct configfs_attribute *uvcg_uncompressed_attrs[] = { }; static const struct config_item_type uvcg_uncompressed_type = { - .ct_item_ops = &uvcg_config_item_ops, + .ct_item_ops = &uvcg_format_item_operations, .ct_group_ops = &uvcg_uncompressed_group_ops, .ct_attrs = uvcg_uncompressed_attrs, .ct_owner = THIS_MODULE, @@ -1560,8 +2473,15 @@ static struct config_group *uvcg_uncompressed_make(struct config_group *group, 'Y', 'U', 'Y', '2', 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 }; + struct uvcg_color_matching *color_match; + struct config_item *streaming; struct uvcg_uncompressed *h; + streaming = group->cg_item.ci_parent; + color_match = uvcg_format_get_default_color_match(streaming); + if (!color_match) + return ERR_PTR(-EINVAL); + h = kzalloc(sizeof(*h), GFP_KERNEL); if (!h) return ERR_PTR(-ENOMEM); @@ -1579,6 +2499,8 @@ static struct config_group *uvcg_uncompressed_make(struct config_group *group, INIT_LIST_HEAD(&h->fmt.frames); h->fmt.type = UVCG_UNCOMPRESSED; + h->fmt.color_matching = color_match; + color_match->refcnt++; config_group_init_type_name(&h->fmt.group, name, &uvcg_uncompressed_type); @@ -1734,7 +2656,7 @@ static struct configfs_attribute *uvcg_mjpeg_attrs[] = { }; static const struct config_item_type uvcg_mjpeg_type = { - .ct_item_ops = &uvcg_config_item_ops, + .ct_item_ops = &uvcg_format_item_operations, .ct_group_ops = &uvcg_mjpeg_group_ops, .ct_attrs = uvcg_mjpeg_attrs, .ct_owner = THIS_MODULE, @@ -1743,8 +2665,15 @@ static const struct config_item_type uvcg_mjpeg_type = { static struct config_group *uvcg_mjpeg_make(struct config_group *group, const char *name) { + struct uvcg_color_matching *color_match; + struct config_item *streaming; struct uvcg_mjpeg *h; + streaming = group->cg_item.ci_parent; + color_match = uvcg_format_get_default_color_match(streaming); + if (!color_match) + return ERR_PTR(-EINVAL); + h = kzalloc(sizeof(*h), GFP_KERNEL); if (!h) return ERR_PTR(-ENOMEM); @@ -1760,6 +2689,8 @@ static struct config_group *uvcg_mjpeg_make(struct config_group *group, INIT_LIST_HEAD(&h->fmt.frames); h->fmt.type = UVCG_MJPEG; + h->fmt.color_matching = color_match; + color_match->refcnt++; config_group_init_type_name(&h->fmt.group, name, &uvcg_mjpeg_type); @@ -1780,73 +2711,417 @@ static const struct uvcg_config_group_type uvcg_mjpeg_grp_type = { }; /* ----------------------------------------------------------------------------- + * streaming/framebased/<NAME> + */ + +static struct configfs_group_operations uvcg_framebased_group_ops = { + .make_item = uvcg_frame_make, + .drop_item = uvcg_frame_drop, +}; + +#define UVCG_FRAMEBASED_ATTR_RO(cname, aname, bits) \ +static ssize_t uvcg_framebased_##cname##_show(struct config_item *item, \ + char *page) \ +{ \ + struct uvcg_framebased *u = to_uvcg_framebased(item); \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex; \ + int result; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%u\n", le##bits##_to_cpu(u->desc.aname));\ + mutex_unlock(&opts->lock); \ + \ + mutex_unlock(su_mutex); \ + return result; \ +} \ + \ +UVC_ATTR_RO(uvcg_framebased_, cname, aname) + +#define UVCG_FRAMEBASED_ATTR(cname, aname, bits) \ +static ssize_t uvcg_framebased_##cname##_show(struct config_item *item, \ + char *page) \ +{ \ + struct uvcg_framebased *u = to_uvcg_framebased(item); \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex; \ + int result; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%u\n", le##bits##_to_cpu(u->desc.aname));\ + mutex_unlock(&opts->lock); \ + \ + mutex_unlock(su_mutex); \ + return result; \ +} \ + \ +static ssize_t \ +uvcg_framebased_##cname##_store(struct config_item *item, \ + const char *page, size_t len) \ +{ \ + struct uvcg_framebased *u = to_uvcg_framebased(item); \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex; \ + int ret; \ + u8 num; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + if (u->fmt.linked || opts->refcnt) { \ + ret = -EBUSY; \ + goto end; \ + } \ + \ + ret = kstrtou8(page, 0, &num); \ + if (ret) \ + goto end; \ + \ + if (num > 255) { \ + ret = -EINVAL; \ + goto end; \ + } \ + u->desc.aname = num; \ + ret = len; \ +end: \ + mutex_unlock(&opts->lock); \ + mutex_unlock(su_mutex); \ + return ret; \ +} \ + \ +UVC_ATTR(uvcg_framebased_, cname, aname) + +UVCG_FRAMEBASED_ATTR_RO(b_format_index, bFormatIndex, 8); +UVCG_FRAMEBASED_ATTR_RO(b_bits_per_pixel, bBitsPerPixel, 8); +UVCG_FRAMEBASED_ATTR(b_default_frame_index, bDefaultFrameIndex, 8); +UVCG_FRAMEBASED_ATTR_RO(b_aspect_ratio_x, bAspectRatioX, 8); +UVCG_FRAMEBASED_ATTR_RO(b_aspect_ratio_y, bAspectRatioY, 8); +UVCG_FRAMEBASED_ATTR_RO(bm_interface_flags, bmInterfaceFlags, 8); + +#undef UVCG_FRAMEBASED_ATTR +#undef UVCG_FRAMEBASED_ATTR_RO + +static ssize_t uvcg_framebased_guid_format_show(struct config_item *item, + char *page) +{ + struct uvcg_framebased *ch = to_uvcg_framebased(item); + struct f_uvc_opts *opts; + struct config_item *opts_item; + struct mutex *su_mutex = &ch->fmt.group.cg_subsys->su_mutex; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = ch->fmt.group.cg_item.ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + memcpy(page, ch->desc.guidFormat, sizeof(ch->desc.guidFormat)); + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + + return sizeof(ch->desc.guidFormat); +} + +static ssize_t uvcg_framebased_guid_format_store(struct config_item *item, + const char *page, size_t len) +{ + struct uvcg_framebased *ch = to_uvcg_framebased(item); + struct f_uvc_opts *opts; + struct config_item *opts_item; + struct mutex *su_mutex = &ch->fmt.group.cg_subsys->su_mutex; + int ret; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = ch->fmt.group.cg_item.ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + if (ch->fmt.linked || opts->refcnt) { + ret = -EBUSY; + goto end; + } + + memcpy(ch->desc.guidFormat, page, + min(sizeof(ch->desc.guidFormat), len)); + ret = sizeof(ch->desc.guidFormat); + +end: + mutex_unlock(&opts->lock); + mutex_unlock(su_mutex); + return ret; +} + +UVC_ATTR(uvcg_framebased_, guid_format, guidFormat); + +static inline ssize_t +uvcg_framebased_bma_controls_show(struct config_item *item, char *page) +{ + struct uvcg_framebased *u = to_uvcg_framebased(item); + + return uvcg_format_bma_controls_show(&u->fmt, page); +} + +static inline ssize_t +uvcg_framebased_bma_controls_store(struct config_item *item, + const char *page, size_t len) +{ + struct uvcg_framebased *u = to_uvcg_framebased(item); + + return uvcg_format_bma_controls_store(&u->fmt, page, len); +} + +UVC_ATTR(uvcg_framebased_, bma_controls, bmaControls); + +static struct configfs_attribute *uvcg_framebased_attrs[] = { + &uvcg_framebased_attr_b_format_index, + &uvcg_framebased_attr_b_default_frame_index, + &uvcg_framebased_attr_b_bits_per_pixel, + &uvcg_framebased_attr_b_aspect_ratio_x, + &uvcg_framebased_attr_b_aspect_ratio_y, + &uvcg_framebased_attr_bm_interface_flags, + &uvcg_framebased_attr_bma_controls, + &uvcg_framebased_attr_guid_format, + NULL, +}; + +static const struct config_item_type uvcg_framebased_type = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_group_ops = &uvcg_framebased_group_ops, + .ct_attrs = uvcg_framebased_attrs, + .ct_owner = THIS_MODULE, +}; + +static struct config_group *uvcg_framebased_make(struct config_group *group, + const char *name) +{ + static char guid[] = { /*Declear frame based as H264 format*/ + 'H', '2', '6', '4', 0x00, 0x00, 0x10, 0x00, + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 + }; + struct uvcg_color_matching *color_match; + struct config_item *streaming; + struct uvcg_framebased *h; + + streaming = group->cg_item.ci_parent; + color_match = uvcg_format_get_default_color_match(streaming); + if (!color_match) + return ERR_PTR(-EINVAL); + + h = kzalloc(sizeof(*h), GFP_KERNEL); + if (!h) + return ERR_PTR(-ENOMEM); + + h->desc.bLength = UVC_DT_FORMAT_FRAMEBASED_SIZE; + h->desc.bDescriptorType = USB_DT_CS_INTERFACE; + h->desc.bDescriptorSubType = UVC_VS_FORMAT_FRAME_BASED; + memcpy(h->desc.guidFormat, guid, sizeof(guid)); + h->desc.bBitsPerPixel = 0; + h->desc.bDefaultFrameIndex = 1; + h->desc.bAspectRatioX = 0; + h->desc.bAspectRatioY = 0; + h->desc.bmInterfaceFlags = 0; + h->desc.bCopyProtect = 0; + h->desc.bVariableSize = 1; + + INIT_LIST_HEAD(&h->fmt.frames); + h->fmt.type = UVCG_FRAMEBASED; + + h->fmt.color_matching = color_match; + color_match->refcnt++; + config_group_init_type_name(&h->fmt.group, name, + &uvcg_framebased_type); + + return &h->fmt.group; +} + +static struct configfs_group_operations uvcg_framebased_grp_ops = { + .make_group = uvcg_framebased_make, +}; + +static const struct uvcg_config_group_type uvcg_framebased_grp_type = { + .type = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_group_ops = &uvcg_framebased_grp_ops, + .ct_owner = THIS_MODULE, + }, + .name = "framebased", +}; + +/* ----------------------------------------------------------------------------- * streaming/color_matching/default */ -#define UVCG_DEFAULT_COLOR_MATCHING_ATTR(cname, aname, bits) \ -static ssize_t uvcg_default_color_matching_##cname##_show( \ +#define UVCG_COLOR_MATCHING_ATTR(cname, aname, bits) \ +static ssize_t uvcg_color_matching_##cname##_show( \ struct config_item *item, char *page) \ { \ struct config_group *group = to_config_group(item); \ + struct uvcg_color_matching *color_match = \ + to_uvcg_color_matching(group); \ struct f_uvc_opts *opts; \ struct config_item *opts_item; \ struct mutex *su_mutex = &group->cg_subsys->su_mutex; \ - struct uvc_color_matching_descriptor *cd; \ int result; \ \ mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ \ opts_item = group->cg_item.ci_parent->ci_parent->ci_parent; \ opts = to_f_uvc_opts(opts_item); \ - cd = &opts->uvc_color_matching; \ \ mutex_lock(&opts->lock); \ - result = sprintf(page, "%u\n", le##bits##_to_cpu(cd->aname)); \ + result = sprintf(page, "%u\n", \ + le##bits##_to_cpu(color_match->desc.aname)); \ mutex_unlock(&opts->lock); \ \ mutex_unlock(su_mutex); \ return result; \ } \ \ -UVC_ATTR_RO(uvcg_default_color_matching_, cname, aname) +static ssize_t uvcg_color_matching_##cname##_store( \ + struct config_item *item, const char *page, size_t len) \ +{ \ + struct config_group *group = to_config_group(item); \ + struct mutex *su_mutex = &group->cg_subsys->su_mutex; \ + struct uvcg_color_matching *color_match = \ + to_uvcg_color_matching(group); \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + int ret; \ + u##bits num; \ + \ + ret = kstrtou##bits(page, 0, &num); \ + if (ret) \ + return ret; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + if (color_match->refcnt) { \ + ret = -EBUSY; \ + goto unlock_su; \ + } \ + \ + opts_item = group->cg_item.ci_parent->ci_parent->ci_parent; \ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + \ + color_match->desc.aname = num; \ + ret = len; \ + \ + mutex_unlock(&opts->lock); \ +unlock_su: \ + mutex_unlock(su_mutex); \ + \ + return ret; \ +} \ +UVC_ATTR(uvcg_color_matching_, cname, aname) -UVCG_DEFAULT_COLOR_MATCHING_ATTR(b_color_primaries, bColorPrimaries, 8); -UVCG_DEFAULT_COLOR_MATCHING_ATTR(b_transfer_characteristics, - bTransferCharacteristics, 8); -UVCG_DEFAULT_COLOR_MATCHING_ATTR(b_matrix_coefficients, bMatrixCoefficients, 8); +UVCG_COLOR_MATCHING_ATTR(b_color_primaries, bColorPrimaries, 8); +UVCG_COLOR_MATCHING_ATTR(b_transfer_characteristics, bTransferCharacteristics, 8); +UVCG_COLOR_MATCHING_ATTR(b_matrix_coefficients, bMatrixCoefficients, 8); -#undef UVCG_DEFAULT_COLOR_MATCHING_ATTR +#undef UVCG_COLOR_MATCHING_ATTR -static struct configfs_attribute *uvcg_default_color_matching_attrs[] = { - &uvcg_default_color_matching_attr_b_color_primaries, - &uvcg_default_color_matching_attr_b_transfer_characteristics, - &uvcg_default_color_matching_attr_b_matrix_coefficients, +static struct configfs_attribute *uvcg_color_matching_attrs[] = { + &uvcg_color_matching_attr_b_color_primaries, + &uvcg_color_matching_attr_b_transfer_characteristics, + &uvcg_color_matching_attr_b_matrix_coefficients, NULL, }; -static const struct uvcg_config_group_type uvcg_default_color_matching_type = { - .type = { - .ct_item_ops = &uvcg_config_item_ops, - .ct_attrs = uvcg_default_color_matching_attrs, - .ct_owner = THIS_MODULE, - }, - .name = "default", +static void uvcg_color_matching_release(struct config_item *item) +{ + struct uvcg_color_matching *color_match = + to_uvcg_color_matching(to_config_group(item)); + + kfree(color_match); +} + +static struct configfs_item_operations uvcg_color_matching_item_ops = { + .release = uvcg_color_matching_release, +}; + +static const struct config_item_type uvcg_color_matching_type = { + .ct_item_ops = &uvcg_color_matching_item_ops, + .ct_attrs = uvcg_color_matching_attrs, + .ct_owner = THIS_MODULE, }; /* ----------------------------------------------------------------------------- * streaming/color_matching */ +static struct config_group *uvcg_color_matching_make(struct config_group *group, + const char *name) +{ + struct uvcg_color_matching *color_match; + + color_match = kzalloc(sizeof(*color_match), GFP_KERNEL); + if (!color_match) + return ERR_PTR(-ENOMEM); + + color_match->desc.bLength = UVC_DT_COLOR_MATCHING_SIZE; + color_match->desc.bDescriptorType = USB_DT_CS_INTERFACE; + color_match->desc.bDescriptorSubType = UVC_VS_COLORFORMAT; + + config_group_init_type_name(&color_match->group, name, + &uvcg_color_matching_type); + + return &color_match->group; +} + +static struct configfs_group_operations uvcg_color_matching_grp_group_ops = { + .make_group = uvcg_color_matching_make, +}; + +static int uvcg_color_matching_create_children(struct config_group *parent) +{ + struct uvcg_color_matching *color_match; + + color_match = kzalloc(sizeof(*color_match), GFP_KERNEL); + if (!color_match) + return -ENOMEM; + + color_match->desc.bLength = UVC_DT_COLOR_MATCHING_SIZE; + color_match->desc.bDescriptorType = USB_DT_CS_INTERFACE; + color_match->desc.bDescriptorSubType = UVC_VS_COLORFORMAT; + color_match->desc.bColorPrimaries = UVC_COLOR_PRIMARIES_BT_709_SRGB; + color_match->desc.bTransferCharacteristics = UVC_TRANSFER_CHARACTERISTICS_BT_709; + color_match->desc.bMatrixCoefficients = UVC_MATRIX_COEFFICIENTS_SMPTE_170M; + + config_group_init_type_name(&color_match->group, "default", + &uvcg_color_matching_type); + configfs_add_default_group(&color_match->group, parent); + + return 0; +} + static const struct uvcg_config_group_type uvcg_color_matching_grp_type = { .type = { .ct_item_ops = &uvcg_config_item_ops, + .ct_group_ops = &uvcg_color_matching_grp_group_ops, .ct_owner = THIS_MODULE, }, .name = "color_matching", - .children = (const struct uvcg_config_group_type*[]) { - &uvcg_default_color_matching_type, - NULL, - }, + .create_children = uvcg_color_matching_create_children, }; /* ----------------------------------------------------------------------------- @@ -1880,7 +3155,8 @@ static inline struct uvc_descriptor_header enum uvcg_strm_type { UVCG_HEADER = 0, UVCG_FORMAT, - UVCG_FRAME + UVCG_FRAME, + UVCG_COLOR_MATCHING, }; /* @@ -1924,12 +3200,18 @@ static int __uvcg_iter_strm_cls(struct uvcg_streaming_header *h, if (ret) return ret; grp = &f->fmt->group; + j = 0; list_for_each_entry(item, &grp->cg_children, ci_entry) { frm = to_uvcg_frame(item); ret = fun(frm, priv2, priv3, j++, UVCG_FRAME); if (ret) return ret; } + + ret = fun(f->fmt->color_matching, priv2, priv3, 0, + UVCG_COLOR_MATCHING); + if (ret) + return ret; } return ret; @@ -1972,6 +3254,11 @@ static int __uvcg_cnt_strm(void *priv1, void *priv2, void *priv3, int n, container_of(fmt, struct uvcg_mjpeg, fmt); *size += sizeof(m->desc); + } else if (fmt->type == UVCG_FRAMEBASED) { + struct uvcg_framebased *f = + container_of(fmt, struct uvcg_framebased, fmt); + + *size += sizeof(f->desc); } else { return -EINVAL; } @@ -1982,9 +3269,20 @@ static int __uvcg_cnt_strm(void *priv1, void *priv2, void *priv3, int n, int sz = sizeof(frm->dw_frame_interval); *size += sizeof(frm->frame); + /* + * framebased has duplicate member with uncompressed and + * mjpeg, so minus it + */ + *size -= sizeof(u32); *size += frm->frame.b_frame_interval_type * sz; } break; + case UVCG_COLOR_MATCHING: { + struct uvcg_color_matching *color_match = priv1; + + *size += sizeof(color_match->desc); + } + break; } ++*count; @@ -1992,6 +3290,27 @@ static int __uvcg_cnt_strm(void *priv1, void *priv2, void *priv3, int n, return 0; } +static int __uvcg_copy_framebased_desc(void *dest, struct uvcg_frame *frm, + int sz) +{ + struct uvc_frame_framebased *desc = dest; + + desc->bLength = frm->frame.b_length; + desc->bDescriptorType = frm->frame.b_descriptor_type; + desc->bDescriptorSubType = frm->frame.b_descriptor_subtype; + desc->bFrameIndex = frm->frame.b_frame_index; + desc->bmCapabilities = frm->frame.bm_capabilities; + desc->wWidth = frm->frame.w_width; + desc->wHeight = frm->frame.w_height; + desc->dwMinBitRate = frm->frame.dw_min_bit_rate; + desc->dwMaxBitRate = frm->frame.dw_max_bit_rate; + desc->dwDefaultFrameInterval = frm->frame.dw_default_frame_interval; + desc->bFrameIntervalType = frm->frame.b_frame_interval_type; + desc->dwBytesPerLine = frm->frame.dw_bytes_perline; + + return 0; +} + /* * Fill an array of streaming descriptors. * @@ -2046,6 +3365,15 @@ static int __uvcg_fill_strm(void *priv1, void *priv2, void *priv3, int n, m->desc.bNumFrameDescriptors = fmt->num_frames; memcpy(*dest, &m->desc, sizeof(m->desc)); *dest += sizeof(m->desc); + } else if (fmt->type == UVCG_FRAMEBASED) { + struct uvcg_framebased *f = + container_of(fmt, struct uvcg_framebased, + fmt); + + f->desc.bFormatIndex = n + 1; + f->desc.bNumFrameDescriptors = fmt->num_frames; + memcpy(*dest, &f->desc, sizeof(f->desc)); + *dest += sizeof(f->desc); } else { return -EINVAL; } @@ -2055,8 +3383,11 @@ static int __uvcg_fill_strm(void *priv1, void *priv2, void *priv3, int n, struct uvcg_frame *frm = priv1; struct uvc_descriptor_header *h = *dest; - sz = sizeof(frm->frame); - memcpy(*dest, &frm->frame, sz); + sz = sizeof(frm->frame) - 4; + if (frm->fmt_type != UVCG_FRAMEBASED) + memcpy(*dest, &frm->frame, sz); + else + __uvcg_copy_framebased_desc(*dest, frm, sz); *dest += sz; sz = frm->frame.b_frame_interval_type * sizeof(*frm->dw_frame_interval); @@ -2067,7 +3398,17 @@ static int __uvcg_fill_strm(void *priv1, void *priv2, void *priv3, int n, frm->frame.b_frame_interval_type); else if (frm->fmt_type == UVCG_MJPEG) h->bLength = UVC_DT_FRAME_MJPEG_SIZE( - frm->frame.b_frame_interval_type); + frm->frame.b_frame_interval_type); + else if (frm->fmt_type == UVCG_FRAMEBASED) + h->bLength = UVC_DT_FRAME_FRAMEBASED_SIZE( + frm->frame.b_frame_interval_type); + } + break; + case UVCG_COLOR_MATCHING: { + struct uvcg_color_matching *color_match = priv1; + + memcpy(*dest, &color_match->desc, sizeof(color_match->desc)); + *dest += sizeof(color_match->desc); } break; } @@ -2109,7 +3450,7 @@ static int uvcg_streaming_class_allow_link(struct config_item *src, if (ret) goto unlock; - count += 2; /* color_matching, NULL */ + count += 1; /* NULL */ *class_array = kcalloc(count, sizeof(void *), GFP_KERNEL); if (!*class_array) { ret = -ENOMEM; @@ -2136,7 +3477,6 @@ static int uvcg_streaming_class_allow_link(struct config_item *src, kfree(data_save); goto unlock; } - *cl_arr = (struct uvc_descriptor_header *)&opts->uvc_color_matching; ++target_hdr->linked; ret = 0; @@ -2280,6 +3620,7 @@ static const struct uvcg_config_group_type uvcg_streaming_grp_type = { &uvcg_streaming_header_grp_type, &uvcg_uncompressed_grp_type, &uvcg_mjpeg_grp_type, + &uvcg_framebased_grp_type, &uvcg_color_matching_grp_type, &uvcg_streaming_class_grp_type, NULL, @@ -2298,8 +3639,68 @@ static void uvc_func_item_release(struct config_item *item) usb_put_function_instance(&opts->func_inst); } +static int uvc_func_allow_link(struct config_item *src, struct config_item *tgt) +{ + struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex; + struct gadget_string *string; + struct config_item *strings; + struct f_uvc_opts *opts; + int ret = 0; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + /* Validate that the target is an entry in strings/<langid> */ + strings = config_group_find_item(to_config_group(src->ci_parent->ci_parent), + "strings"); + if (!strings || tgt->ci_parent->ci_parent != strings) { + ret = -EINVAL; + goto put_strings; + } + + string = to_gadget_string(tgt); + + opts = to_f_uvc_opts(src); + mutex_lock(&opts->lock); + + if (!strcmp(tgt->ci_name, "iad_desc")) + opts->iad_index = string->usb_string.id; + else if (!strcmp(tgt->ci_name, "vs0_desc")) + opts->vs0_index = string->usb_string.id; + else if (!strcmp(tgt->ci_name, "vs1_desc")) + opts->vs1_index = string->usb_string.id; + else + ret = -EINVAL; + + mutex_unlock(&opts->lock); + +put_strings: + config_item_put(strings); + mutex_unlock(su_mutex); + + return ret; +} + +static void uvc_func_drop_link(struct config_item *src, struct config_item *tgt) +{ + struct f_uvc_opts *opts; + + opts = to_f_uvc_opts(src); + mutex_lock(&opts->lock); + + if (!strcmp(tgt->ci_name, "iad_desc")) + opts->iad_index = 0; + else if (!strcmp(tgt->ci_name, "vs0_desc")) + opts->vs0_index = 0; + else if (!strcmp(tgt->ci_name, "vs1_desc")) + opts->vs1_index = 0; + + mutex_unlock(&opts->lock); +} + static struct configfs_item_operations uvc_func_item_ops = { .release = uvc_func_item_release, + .allow_link = uvc_func_allow_link, + .drop_link = uvc_func_drop_link, }; #define UVCG_OPTS_ATTR(cname, aname, limit) \ @@ -2361,7 +3762,7 @@ static ssize_t f_uvc_opts_string_##cname##_show(struct config_item *item,\ int result; \ \ mutex_lock(&opts->lock); \ - result = snprintf(page, sizeof(opts->aname), "%s", opts->aname);\ + result = scnprintf(page, sizeof(opts->aname), "%s", opts->aname);\ mutex_unlock(&opts->lock); \ \ return result; \ diff --git a/drivers/usb/gadget/function/uvc_configfs.h b/drivers/usb/gadget/function/uvc_configfs.h index ad2ec8c4c78c..9391614135e9 100644 --- a/drivers/usb/gadget/function/uvc_configfs.h +++ b/drivers/usb/gadget/function/uvc_configfs.h @@ -37,18 +37,29 @@ static inline struct uvcg_control_header *to_uvcg_control_header(struct config_i return container_of(item, struct uvcg_control_header, item); } +struct uvcg_color_matching { + struct config_group group; + struct uvc_color_matching_descriptor desc; + unsigned int refcnt; +}; + +#define to_uvcg_color_matching(group_ptr) \ +container_of(group_ptr, struct uvcg_color_matching, group) + enum uvcg_format_type { UVCG_UNCOMPRESSED = 0, UVCG_MJPEG, + UVCG_FRAMEBASED, }; struct uvcg_format { - struct config_group group; - enum uvcg_format_type type; - unsigned linked; - struct list_head frames; - unsigned num_frames; - __u8 bmaControls[UVCG_STREAMING_CONTROL_SIZE]; + struct config_group group; + enum uvcg_format_type type; + unsigned linked; + struct list_head frames; + unsigned num_frames; + __u8 bmaControls[UVCG_STREAMING_CONTROL_SIZE]; + struct uvcg_color_matching *color_matching; }; struct uvcg_format_ptr { @@ -63,10 +74,12 @@ static inline struct uvcg_format *to_uvcg_format(struct config_item *item) struct uvcg_streaming_header { struct config_item item; - struct uvc_input_header_descriptor desc; unsigned linked; struct list_head formats; unsigned num_fmt; + + /* Must be last --ends in a flexible-array member. */ + struct uvc_input_header_descriptor desc; }; static inline struct uvcg_streaming_header *to_uvcg_streaming_header(struct config_item *item) @@ -95,6 +108,7 @@ struct uvcg_frame { u32 dw_max_video_frame_buffer_size; u32 dw_default_frame_interval; u8 b_frame_interval_type; + u32 dw_bytes_perline; } __attribute__((packed)) frame; u32 *dw_frame_interval; }; @@ -132,6 +146,50 @@ static inline struct uvcg_mjpeg *to_uvcg_mjpeg(struct config_item *item) return container_of(to_uvcg_format(item), struct uvcg_mjpeg, fmt); } +/* ----------------------------------------------------------------------------- + * streaming/framebased/<NAME> + */ + +struct uvcg_framebased { + struct uvcg_format fmt; + struct uvc_format_framebased desc; +}; + +static inline struct uvcg_framebased *to_uvcg_framebased(struct config_item *item) +{ + return container_of(to_uvcg_format(item), struct uvcg_framebased, fmt); +} + +/* ----------------------------------------------------------------------------- + * control/extensions/<NAME> + */ + +struct uvcg_extension_unit_descriptor { + u8 bLength; + u8 bDescriptorType; + u8 bDescriptorSubType; + u8 bUnitID; + u8 guidExtensionCode[16]; + u8 bNumControls; + u8 bNrInPins; + u8 *baSourceID; + u8 bControlSize; + u8 *bmControls; + u8 iExtension; +} __packed; + +struct uvcg_extension { + struct config_item item; + struct list_head list; + u8 string_descriptor_index; + struct uvcg_extension_unit_descriptor desc; +}; + +static inline struct uvcg_extension *to_uvcg_extension(struct config_item *item) +{ + return container_of(item, struct uvcg_extension, item); +} + int uvcg_attach_configfs(struct f_uvc_opts *opts); #endif /* UVC_CONFIGFS_H */ diff --git a/drivers/usb/gadget/function/uvc_queue.c b/drivers/usb/gadget/function/uvc_queue.c index 0aa3d7e1f3cc..9a1bbd79ff5a 100644 --- a/drivers/usb/gadget/function/uvc_queue.c +++ b/drivers/usb/gadget/function/uvc_queue.c @@ -21,6 +21,7 @@ #include <media/videobuf2-vmalloc.h> #include "uvc.h" +#include "uvc_video.h" /* ------------------------------------------------------------------------ * Video buffers queue management. @@ -44,33 +45,23 @@ static int uvc_queue_setup(struct vb2_queue *vq, { struct uvc_video_queue *queue = vb2_get_drv_priv(vq); struct uvc_video *video = container_of(queue, struct uvc_video, queue); - unsigned int req_size; - unsigned int nreq; if (*nbuffers > UVC_MAX_VIDEO_BUFFERS) *nbuffers = UVC_MAX_VIDEO_BUFFERS; + if (*nbuffers < UVCG_STREAMING_MIN_BUFFERS) + *nbuffers = UVCG_STREAMING_MIN_BUFFERS; *nplanes = 1; sizes[0] = video->imagesize; - req_size = video->ep->maxpacket - * max_t(unsigned int, video->ep->maxburst, 1) - * (video->ep->mult); - - /* We divide by two, to increase the chance to run - * into fewer requests for smaller framesizes. - */ - nreq = DIV_ROUND_UP(DIV_ROUND_UP(sizes[0], 2), req_size); - nreq = clamp(nreq, 4U, 64U); - video->uvc_num_requests = nreq; - return 0; } static int uvc_buffer_prepare(struct vb2_buffer *vb) { struct uvc_video_queue *queue = vb2_get_drv_priv(vb->vb2_queue); + struct uvc_video *video = container_of(queue, struct uvc_video, queue); struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); struct uvc_buffer *buf = container_of(vbuf, struct uvc_buffer, buf); @@ -91,10 +82,15 @@ static int uvc_buffer_prepare(struct vb2_buffer *vb) buf->mem = vb2_plane_vaddr(vb, 0); } buf->length = vb2_plane_size(vb, 0); - if (vb->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + if (vb->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { buf->bytesused = 0; - else + } else { buf->bytesused = vb2_get_plane_payload(vb, 0); + buf->req_payload_size = + DIV_ROUND_UP(buf->bytesused + + (video->reqs_per_frame * UVCG_REQUEST_HEADER_LEN), + video->reqs_per_frame); + } return 0; } @@ -126,8 +122,6 @@ static const struct vb2_ops uvc_queue_qops = { .queue_setup = uvc_queue_setup, .buf_prepare = uvc_buffer_prepare, .buf_queue = uvc_buffer_queue, - .wait_prepare = vb2_ops_wait_prepare, - .wait_finish = vb2_ops_wait_finish, }; int uvcg_queue_init(struct uvc_video_queue *queue, struct device *dev, enum v4l2_buf_type type, diff --git a/drivers/usb/gadget/function/uvc_queue.h b/drivers/usb/gadget/function/uvc_queue.h index 41f87b917f6b..b54becc570a3 100644 --- a/drivers/usb/gadget/function/uvc_queue.h +++ b/drivers/usb/gadget/function/uvc_queue.h @@ -39,6 +39,8 @@ struct uvc_buffer { unsigned int offset; unsigned int length; unsigned int bytesused; + /* req_payload_size: only used with isoc */ + unsigned int req_payload_size; }; #define UVC_QUEUE_DISCONNECTED (1 << 0) diff --git a/drivers/usb/gadget/function/uvc_trace.c b/drivers/usb/gadget/function/uvc_trace.c new file mode 100644 index 000000000000..d384f6d8221a --- /dev/null +++ b/drivers/usb/gadget/function/uvc_trace.c @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * trace.c - USB UVC Gadget Trace Support + * + * Copyright (C) 2024 Pengutronix e.K. + * + * Author: Michael Grzeschik <m.grzeschik@pengutronix.de> + */ + +#define CREATE_TRACE_POINTS +#include "uvc_trace.h" diff --git a/drivers/usb/gadget/function/uvc_trace.h b/drivers/usb/gadget/function/uvc_trace.h new file mode 100644 index 000000000000..04c33cf43cc2 --- /dev/null +++ b/drivers/usb/gadget/function/uvc_trace.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * trace.h - USB UVC Gadget Trace Support + * + * Copyright (C) 2024 Pengutronix e.K. + * + * Author: Michael Grzeschik <m.grzeschik@pengutronix.de> + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM uvcg + +#if !defined(__UVCG_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define __UVCG_TRACE_H + +#include <linux/types.h> +#include <linux/tracepoint.h> +#include <linux/usb/gadget.h> +#include <asm/byteorder.h> + +DECLARE_EVENT_CLASS(uvcg_video_req, + TP_PROTO(struct usb_request *req, u32 queued), + TP_ARGS(req, queued), + TP_STRUCT__entry( + __field(struct usb_request *, req) + __field(u32, length) + __field(u32, queued) + ), + TP_fast_assign( + __entry->req = req; + __entry->length = req->length; + __entry->queued = queued; + ), + TP_printk("req %p length %u queued %u", + __entry->req, + __entry->length, + __entry->queued) +); + +DEFINE_EVENT(uvcg_video_req, uvcg_video_complete, + TP_PROTO(struct usb_request *req, u32 queued), + TP_ARGS(req, queued) +); + +DEFINE_EVENT(uvcg_video_req, uvcg_video_queue, + TP_PROTO(struct usb_request *req, u32 queued), + TP_ARGS(req, queued) +); + +#endif /* __UVCG_TRACE_H */ + +/* this part has to be here */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . + +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE uvc_trace + +#include <trace/define_trace.h> diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c index a189b08bba80..fd4b998ccd16 100644 --- a/drivers/usb/gadget/function/uvc_v4l2.c +++ b/drivers/usb/gadget/function/uvc_v4l2.c @@ -11,6 +11,7 @@ #include <linux/kernel.h> #include <linux/list.h> #include <linux/usb/g_uvc.h> +#include <linux/usb/uvc.h> #include <linux/videodev2.h> #include <linux/vmalloc.h> #include <linux/wait.h> @@ -18,7 +19,6 @@ #include <media/v4l2-dev.h> #include <media/v4l2-event.h> #include <media/v4l2-ioctl.h> -#include <media/v4l2-uvc.h> #include "f_uvc.h" #include "uvc.h" @@ -27,18 +27,27 @@ #include "uvc_v4l2.h" #include "uvc_configfs.h" -static struct uvc_format_desc *to_uvc_format(struct uvcg_format *uformat) +static const struct uvc_format_desc *to_uvc_format(struct uvcg_format *uformat) { char guid[16] = UVC_GUID_FORMAT_MJPEG; - struct uvc_format_desc *format; - struct uvcg_uncompressed *unc; + const struct uvc_format_desc *format; if (uformat->type == UVCG_UNCOMPRESSED) { + struct uvcg_uncompressed *unc; + unc = to_uvcg_uncompressed(&uformat->group.cg_item); if (!unc) return ERR_PTR(-EINVAL); memcpy(guid, unc->desc.guidFormat, sizeof(guid)); + } else if (uformat->type == UVCG_FRAMEBASED) { + struct uvcg_framebased *unc; + + unc = to_uvcg_framebased(&uformat->group.cg_item); + if (!unc) + return ERR_PTR(-EINVAL); + + memcpy(guid, unc->desc.guidFormat, sizeof(guid)); } format = uvc_format_by_guid(guid); @@ -119,7 +128,10 @@ static struct uvcg_format *find_format_by_pix(struct uvc_device *uvc, struct uvcg_format *uformat = NULL; list_for_each_entry(format, &uvc->header->formats, entry) { - struct uvc_format_desc *fmtdesc = to_uvc_format(format->fmt); + const struct uvc_format_desc *fmtdesc = to_uvc_format(format->fmt); + + if (IS_ERR(fmtdesc)) + continue; if (fmtdesc->fcc == pixelformat) { uformat = format->fmt; @@ -240,6 +252,7 @@ uvc_v4l2_try_format(struct file *file, void *fh, struct v4l2_format *fmt) struct uvc_video *video = &uvc->video; struct uvcg_format *uformat; struct uvcg_frame *uframe; + const struct uvc_format_desc *fmtdesc; u8 *fcc; if (fmt->type != video->queue.queue.type) @@ -260,12 +273,29 @@ uvc_v4l2_try_format(struct file *file, void *fh, struct v4l2_format *fmt) if (!uframe) return -EINVAL; - fmt->fmt.pix.width = uframe->frame.w_width; - fmt->fmt.pix.height = uframe->frame.w_height; + if (uformat->type == UVCG_UNCOMPRESSED) { + struct uvcg_uncompressed *u = + to_uvcg_uncompressed(&uformat->group.cg_item); + if (!u) + return 0; + + v4l2_fill_pixfmt(&fmt->fmt.pix, fmt->fmt.pix.pixelformat, + uframe->frame.w_width, uframe->frame.w_height); + + if (fmt->fmt.pix.sizeimage != (uvc_v4l2_get_bytesperline(uformat, uframe) * + uframe->frame.w_height)) + return -EINVAL; + } else { + fmt->fmt.pix.width = uframe->frame.w_width; + fmt->fmt.pix.height = uframe->frame.w_height; + fmt->fmt.pix.bytesperline = uvc_v4l2_get_bytesperline(uformat, uframe); + fmt->fmt.pix.sizeimage = uvc_get_frame_size(uformat, uframe); + fmtdesc = to_uvc_format(uformat); + if (IS_ERR(fmtdesc)) + return PTR_ERR(fmtdesc); + fmt->fmt.pix.pixelformat = fmtdesc->fcc; + } fmt->fmt.pix.field = V4L2_FIELD_NONE; - fmt->fmt.pix.bytesperline = uvc_v4l2_get_bytesperline(uformat, uframe); - fmt->fmt.pix.sizeimage = uvc_get_frame_size(uformat, uframe); - fmt->fmt.pix.pixelformat = to_uvc_format(uformat)->fcc; fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB; fmt->fmt.pix.priv = 0; @@ -293,6 +323,56 @@ uvc_v4l2_set_format(struct file *file, void *fh, struct v4l2_format *fmt) return ret; } +static int uvc_v4l2_g_parm(struct file *file, void *fh, + struct v4l2_streamparm *parm) +{ + struct video_device *vdev = video_devdata(file); + struct uvc_device *uvc = video_get_drvdata(vdev); + struct uvc_video *video = &uvc->video; + struct v4l2_fract timeperframe; + + if (!V4L2_TYPE_IS_OUTPUT(parm->type)) + return -EINVAL; + + /* Return the actual frame period. */ + timeperframe.numerator = video->interval; + timeperframe.denominator = 10000000; + v4l2_simplify_fraction(&timeperframe.numerator, + &timeperframe.denominator, 8, 333); + + uvcg_dbg(&uvc->func, "Getting frame interval of %u/%u (%u)\n", + timeperframe.numerator, timeperframe.denominator, + video->interval); + + parm->parm.output.timeperframe = timeperframe; + parm->parm.output.capability = V4L2_CAP_TIMEPERFRAME; + + return 0; +} + +static int uvc_v4l2_s_parm(struct file *file, void *fh, + struct v4l2_streamparm *parm) +{ + struct video_device *vdev = video_devdata(file); + struct uvc_device *uvc = video_get_drvdata(vdev); + struct uvc_video *video = &uvc->video; + struct v4l2_fract timeperframe; + + if (!V4L2_TYPE_IS_OUTPUT(parm->type)) + return -EINVAL; + + timeperframe = parm->parm.output.timeperframe; + + video->interval = v4l2_fraction_to_interval(timeperframe.numerator, + timeperframe.denominator); + + uvcg_dbg(&uvc->func, "Setting frame interval to %u/%u (%u)\n", + timeperframe.numerator, timeperframe.denominator, + video->interval); + + return 0; +} + static int uvc_v4l2_enum_frameintervals(struct file *file, void *fh, struct v4l2_frmivalenum *fival) @@ -364,7 +444,7 @@ uvc_v4l2_enum_format(struct file *file, void *fh, struct v4l2_fmtdesc *f) { struct video_device *vdev = video_devdata(file); struct uvc_device *uvc = video_get_drvdata(vdev); - struct uvc_format_desc *fmtdesc; + const struct uvc_format_desc *fmtdesc; struct uvcg_format *uformat; if (f->index >= uvc->header->num_fmt) @@ -374,14 +454,11 @@ uvc_v4l2_enum_format(struct file *file, void *fh, struct v4l2_fmtdesc *f) if (!uformat) return -EINVAL; - if (uformat->type != UVCG_UNCOMPRESSED) - f->flags |= V4L2_FMT_FLAG_COMPRESSED; - fmtdesc = to_uvc_format(uformat); - f->pixelformat = fmtdesc->fcc; + if (IS_ERR(fmtdesc)) + return PTR_ERR(fmtdesc); - strscpy(f->description, fmtdesc->name, sizeof(f->description)); - f->description[strlen(fmtdesc->name) - 1] = 0; + f->pixelformat = fmtdesc->fcc; return 0; } @@ -449,7 +526,7 @@ uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type) return -EINVAL; /* Enable UVC video. */ - ret = uvcg_video_enable(video, 1); + ret = uvcg_video_enable(video); if (ret < 0) return ret; @@ -457,7 +534,7 @@ uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type) * Complete the alternate setting selection setup phase now that * userspace is ready to provide video frames. */ - uvc_function_setup_continue(uvc); + uvc_function_setup_continue(uvc, 0); uvc->state = UVC_STATE_STREAMING; return 0; @@ -469,11 +546,21 @@ uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type) struct video_device *vdev = video_devdata(file); struct uvc_device *uvc = video_get_drvdata(vdev); struct uvc_video *video = &uvc->video; + int ret = 0; if (type != video->queue.queue.type) return -EINVAL; - return uvcg_video_enable(video, 0); + ret = uvcg_video_disable(video); + if (ret < 0) + return ret; + + if (uvc->state != UVC_STATE_STREAMING) + return 0; + + uvc->state = UVC_STATE_CONNECTED; + uvc_function_setup_continue(uvc, 1); + return 0; } static int @@ -506,7 +593,7 @@ uvc_v4l2_subscribe_event(struct v4l2_fh *fh, static void uvc_v4l2_disable(struct uvc_device *uvc) { uvc_function_disconnect(uvc); - uvcg_video_enable(&uvc->video, 0); + uvcg_video_disable(&uvc->video); uvcg_free_buffers(&uvc->video.queue); uvc->func_connected = false; wake_up_interruptible(&uvc->func_connected_queue); @@ -562,6 +649,8 @@ const struct v4l2_ioctl_ops uvc_v4l2_ioctl_ops = { .vidioc_dqbuf = uvc_v4l2_dqbuf, .vidioc_streamon = uvc_v4l2_streamon, .vidioc_streamoff = uvc_v4l2_streamoff, + .vidioc_s_parm = uvc_v4l2_s_parm, + .vidioc_g_parm = uvc_v4l2_g_parm, .vidioc_subscribe_event = uvc_v4l2_subscribe_event, .vidioc_unsubscribe_event = uvc_v4l2_unsubscribe_event, .vidioc_default = uvc_v4l2_ioctl_default, @@ -583,10 +672,9 @@ uvc_v4l2_open(struct file *file) return -ENOMEM; v4l2_fh_init(&handle->vfh, vdev); - v4l2_fh_add(&handle->vfh); + v4l2_fh_add(&handle->vfh, file); handle->device = &uvc->video; - file->private_data = &handle->vfh; return 0; } @@ -596,7 +684,7 @@ uvc_v4l2_release(struct file *file) { struct video_device *vdev = video_devdata(file); struct uvc_device *uvc = video_get_drvdata(vdev); - struct uvc_file_handle *handle = to_uvc_file_handle(file->private_data); + struct uvc_file_handle *handle = file_to_uvc_file_handle(file); struct uvc_video *video = handle->device; mutex_lock(&video->mutex); @@ -604,8 +692,7 @@ uvc_v4l2_release(struct file *file) uvc_v4l2_disable(uvc); mutex_unlock(&video->mutex); - file->private_data = NULL; - v4l2_fh_del(&handle->vfh); + v4l2_fh_del(&handle->vfh, file); v4l2_fh_exit(&handle->vfh); kfree(handle); @@ -653,4 +740,3 @@ const struct v4l2_file_operations uvc_v4l2_fops = { .get_unmapped_area = uvcg_v4l2_get_unmapped_area, #endif }; - diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c index dd1c6b2ca7c6..fb77b0b21790 100644 --- a/drivers/usb/gadget/function/uvc_video.c +++ b/drivers/usb/gadget/function/uvc_video.c @@ -12,13 +12,14 @@ #include <linux/usb/ch9.h> #include <linux/usb/gadget.h> #include <linux/usb/video.h> -#include <asm/unaligned.h> +#include <linux/unaligned.h> #include <media/v4l2-dev.h> #include "uvc.h" #include "uvc_queue.h" #include "uvc_video.h" +#include "uvc_trace.h" /* -------------------------------------------------------------------------- * Video codecs @@ -35,6 +36,9 @@ uvc_video_encode_header(struct uvc_video *video, struct uvc_buffer *buf, data[1] = UVC_STREAM_EOH | video->fid; + if (video->queue.flags & UVC_QUEUE_DROP_INCOMPLETE) + data[1] |= UVC_STREAM_ERR; + if (video->queue.buf_used == 0 && ts.tv_sec) { /* dwClockFrequency is 48 MHz */ u32 pts = ((u64)ts.tv_sec * USEC_PER_SEC + ts.tv_nsec / NSEC_PER_USEC) * 48; @@ -75,7 +79,7 @@ uvc_video_encode_data(struct uvc_video *video, struct uvc_buffer *buf, /* Copy video data to the USB buffer. */ mem = buf->mem + queue->buf_used; - nbytes = min((unsigned int)len, buf->bytesused - queue->buf_used); + nbytes = min_t(unsigned int, len, buf->bytesused - queue->buf_used); memcpy(data, mem, nbytes); queue->buf_used += nbytes; @@ -101,7 +105,7 @@ uvc_video_encode_bulk(struct usb_request *req, struct uvc_video *video, } /* Process video data. */ - len = min((int)(video->max_payload_size - video->payload_size), len); + len = min_t(int, video->max_payload_size - video->payload_size, len); ret = uvc_video_encode_data(video, buf, mem, len); video->payload_size += ret; @@ -133,7 +137,7 @@ uvc_video_encode_isoc_sg(struct usb_request *req, struct uvc_video *video, unsigned int pending = buf->bytesused - video->queue.buf_used; struct uvc_request *ureq = req->context; struct scatterlist *sg, *iter; - unsigned int len = video->req_size; + unsigned int len = buf->req_payload_size; unsigned int sg_left, part = 0; unsigned int i; int header_len; @@ -143,15 +147,15 @@ uvc_video_encode_isoc_sg(struct usb_request *req, struct uvc_video *video, /* Init the header. */ header_len = uvc_video_encode_header(video, buf, ureq->header, - video->req_size); + buf->req_payload_size); sg_set_buf(sg, ureq->header, header_len); len -= header_len; if (pending <= len) len = pending; - req->length = (len == pending) ? - len + header_len : video->req_size; + req->length = (len == pending) ? len + header_len : + buf->req_payload_size; /* Init the pending sgs with payload */ sg = sg_next(sg); @@ -199,7 +203,7 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video, { void *mem = req->buf; struct uvc_request *ureq = req->context; - int len = video->req_size; + int len = buf->req_payload_size; int ret; /* Add the header. */ @@ -211,7 +215,7 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video, ret = uvc_video_encode_data(video, buf, mem, len); len -= ret; - req->length = video->req_size - len; + req->length = buf->req_payload_size - len; if (buf->bytesused == video->queue.buf_used || video->queue.flags & UVC_QUEUE_DROP_INCOMPLETE) { @@ -227,6 +231,28 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video, * Request handling */ +/* + * Callers must take care to hold req_lock when this function may be called + * from multiple threads. For example, when frames are streaming to the host. + */ +static void +uvc_video_free_request(struct uvc_request *ureq, struct usb_ep *ep) +{ + sg_free_table(&ureq->sgt); + if (ureq->req && ep) { + usb_ep_free_request(ep, ureq->req); + ureq->req = NULL; + } + + kfree(ureq->req_buffer); + ureq->req_buffer = NULL; + + if (!list_empty(&ureq->list)) + list_del_init(&ureq->list); + + kfree(ureq); +} + static int uvcg_video_ep_queue(struct uvc_video *video, struct usb_request *req) { int ret; @@ -244,25 +270,98 @@ static int uvcg_video_ep_queue(struct uvc_video *video, struct usb_request *req) } } + atomic_inc(&video->queued); + + trace_uvcg_video_queue(req, atomic_read(&video->queued)); + return ret; } +/* This function must be called with video->req_lock held. */ +static int uvcg_video_usb_req_queue(struct uvc_video *video, + struct usb_request *req, bool queue_to_ep) +{ + bool is_bulk = video->max_payload_size; + struct list_head *list = NULL; + + if (!video->is_enabled) + return -ENODEV; + + if (queue_to_ep) { + struct uvc_request *ureq = req->context; + /* + * With USB3 handling more requests at a higher speed, we can't + * afford to generate an interrupt for every request. Decide to + * interrupt: + * + * - When no more requests are available in the free queue, as + * this may be our last chance to refill the endpoint's + * request queue. + * + * - When this is request is the last request for the video + * buffer, as we want to start sending the next video buffer + * ASAP in case it doesn't get started already in the next + * iteration of this loop. + * + * - Four times over the length of the requests queue (as + * indicated by video->uvc_num_requests), as a trade-off + * between latency and interrupt load. + */ + if (list_empty(&video->req_free) || ureq->last_buf || + !(video->req_int_count % + min(DIV_ROUND_UP(video->uvc_num_requests, 4), UVCG_REQ_MAX_INT_COUNT))) { + video->req_int_count = 0; + req->no_interrupt = 0; + } else { + req->no_interrupt = 1; + } + video->req_int_count++; + return uvcg_video_ep_queue(video, req); + } + /* + * If we're not queuing to the ep, for isoc we're queuing + * to the req_ready list, otherwise req_free. + */ + list = is_bulk ? &video->req_free : &video->req_ready; + list_add_tail(&req->list, list); + return 0; +} + static void uvc_video_complete(struct usb_ep *ep, struct usb_request *req) { struct uvc_request *ureq = req->context; struct uvc_video *video = ureq->video; struct uvc_video_queue *queue = &video->queue; - struct uvc_device *uvc = video->uvc; + struct uvc_buffer *last_buf; unsigned long flags; + spin_lock_irqsave(&video->req_lock, flags); + atomic_dec(&video->queued); + if (!video->is_enabled) { + /* + * When is_enabled is false, uvcg_video_disable() ensures + * that in-flight uvc_buffers are returned, so we can + * safely call free_request without worrying about + * last_buf. + */ + uvc_video_free_request(ureq, ep); + spin_unlock_irqrestore(&video->req_lock, flags); + return; + } + + last_buf = ureq->last_buf; + ureq->last_buf = NULL; + spin_unlock_irqrestore(&video->req_lock, flags); + switch (req->status) { case 0: break; case -EXDEV: uvcg_dbg(&video->uvc->func, "VS request missed xfer.\n"); - queue->flags |= UVC_QUEUE_DROP_INCOMPLETE; + if (req->length != 0) + queue->flags |= UVC_QUEUE_DROP_INCOMPLETE; break; case -ESHUTDOWN: /* disconnect from host. */ @@ -277,90 +376,218 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req) uvcg_queue_cancel(queue, 0); } - if (ureq->last_buf) { - uvcg_complete_buffer(&video->queue, ureq->last_buf); - ureq->last_buf = NULL; + if (last_buf) { + spin_lock_irqsave(&queue->irqlock, flags); + uvcg_complete_buffer(queue, last_buf); + spin_unlock_irqrestore(&queue->irqlock, flags); } spin_lock_irqsave(&video->req_lock, flags); + /* + * Video stream might have been disabled while we were + * processing the current usb_request. So make sure + * we're still streaming before queueing the usb_request + * back to req_free + */ + if (!video->is_enabled) { + uvc_video_free_request(ureq, ep); + spin_unlock_irqrestore(&video->req_lock, flags); + uvcg_queue_cancel(queue, 0); + + return; + } + list_add_tail(&req->list, &video->req_free); + /* + * Queue work to the wq as well since it is possible that a + * buffer may not have been completely encoded with the set of + * in-flight usb requests for whih the complete callbacks are + * firing. + * In that case, if we do not queue work to the worker thread, + * the buffer will never be marked as complete - and therefore + * not be returned to userpsace. As a result, + * dequeue -> queue -> dequeue flow of uvc buffers will not + * happen. Since there are is a new free request wake up the pump. + */ + queue_work(video->async_wq, &video->pump); + + trace_uvcg_video_complete(req, atomic_read(&video->queued)); + spin_unlock_irqrestore(&video->req_lock, flags); - if (uvc->state == UVC_STATE_STREAMING) - queue_work(video->async_wq, &video->pump); + kthread_queue_work(video->kworker, &video->hw_submit); } -static int -uvc_video_free_requests(struct uvc_video *video) +static void uvcg_video_hw_submit(struct kthread_work *work) { - unsigned int i; + struct uvc_video *video = container_of(work, struct uvc_video, hw_submit); + bool is_bulk = video->max_payload_size; + unsigned long flags; + struct usb_request *req; + int ret = 0; - if (video->ureq) { - for (i = 0; i < video->uvc_num_requests; ++i) { - sg_free_table(&video->ureq[i].sgt); + while (true) { + if (!video->ep->enabled) + return; + spin_lock_irqsave(&video->req_lock, flags); + /* + * Here we check whether any request is available in the ready + * list. If it is, queue it to the ep and add the current + * usb_request to the req_free list - for video_pump to fill in. + * Otherwise, just use the current usb_request to queue a 0 + * length request to the ep. Since we always add to the req_free + * list if we dequeue from the ready list, there will never + * be a situation where the req_free list is completely out of + * requests and cannot recover. + */ + if (!list_empty(&video->req_ready)) { + req = list_first_entry(&video->req_ready, + struct usb_request, list); + } else { + if (list_empty(&video->req_free) || + (atomic_read(&video->queued) > UVCG_REQ_MAX_ZERO_COUNT)) { + spin_unlock_irqrestore(&video->req_lock, flags); - if (video->ureq[i].req) { - usb_ep_free_request(video->ep, video->ureq[i].req); - video->ureq[i].req = NULL; + return; } + req = list_first_entry(&video->req_free, struct usb_request, + list); + req->length = 0; + } + list_del(&req->list); + + /* + * Queue to the endpoint. The actual queueing to ep will + * only happen on one thread - the async_wq for bulk endpoints + * and this thread for isoc endpoints. + */ + ret = uvcg_video_usb_req_queue(video, req, !is_bulk); + if (ret < 0) { + /* + * Endpoint error, but the stream is still enabled. + * Put request back in req_free for it to be cleaned + * up later. + */ + list_add_tail(&req->list, &video->req_free); + /* + * There is a new free request - wake up the pump. + */ + queue_work(video->async_wq, &video->pump); - if (video->ureq[i].req_buffer) { - kfree(video->ureq[i].req_buffer); - video->ureq[i].req_buffer = NULL; - } } - kfree(video->ureq); - video->ureq = NULL; + spin_unlock_irqrestore(&video->req_lock, flags); } +} + +static int +uvc_video_free_requests(struct uvc_video *video) +{ + struct uvc_request *ureq, *temp; + list_for_each_entry_safe(ureq, temp, &video->ureqs, list) + uvc_video_free_request(ureq, video->ep); + + INIT_LIST_HEAD(&video->ureqs); INIT_LIST_HEAD(&video->req_free); - video->req_size = 0; + INIT_LIST_HEAD(&video->req_ready); return 0; } +static void +uvc_video_prep_requests(struct uvc_video *video) +{ + struct uvc_device *uvc = container_of(video, struct uvc_device, video); + struct usb_composite_dev *cdev = uvc->func.config->cdev; + unsigned int interval_duration = video->ep->desc->bInterval * 1250; + unsigned int max_req_size, req_size, header_size; + unsigned int nreq; + + max_req_size = video->ep->maxpacket + * max_t(unsigned int, video->ep->maxburst, 1) + * (video->ep->mult); + + if (!usb_endpoint_xfer_isoc(video->ep->desc)) { + video->req_size = max_req_size; + video->reqs_per_frame = video->uvc_num_requests = + DIV_ROUND_UP(video->imagesize, max_req_size); + + return; + } + + if (cdev->gadget->speed < USB_SPEED_HIGH) + interval_duration = video->ep->desc->bInterval * 10000; + + nreq = DIV_ROUND_UP(video->interval, interval_duration); + + header_size = nreq * UVCG_REQUEST_HEADER_LEN; + + req_size = DIV_ROUND_UP(video->imagesize + header_size, nreq); + + if (req_size > max_req_size) { + /* The prepared interval length and expected buffer size + * is not possible to stream with the currently configured + * isoc bandwidth. Fallback to the maximum. + */ + req_size = max_req_size; + } + video->req_size = req_size; + + /* We need to compensate the amount of requests to be + * allocated with the maximum amount of zero length requests. + * Since it is possible that hw_submit will initially + * enqueue some zero length requests and we then will not be + * able to fully encode one frame. + */ + video->uvc_num_requests = nreq + UVCG_REQ_MAX_ZERO_COUNT; + video->reqs_per_frame = nreq; +} + static int uvc_video_alloc_requests(struct uvc_video *video) { - unsigned int req_size; + struct uvc_request *ureq; unsigned int i; int ret = -ENOMEM; - BUG_ON(video->req_size); + /* + * calculate in uvc_video_prep_requests + * - video->uvc_num_requests + * - video->req_size + */ + uvc_video_prep_requests(video); - req_size = video->ep->maxpacket - * max_t(unsigned int, video->ep->maxburst, 1) - * (video->ep->mult); + for (i = 0; i < video->uvc_num_requests; i++) { + ureq = kzalloc(sizeof(struct uvc_request), GFP_KERNEL); + if (ureq == NULL) + goto error; + + INIT_LIST_HEAD(&ureq->list); - video->ureq = kcalloc(video->uvc_num_requests, sizeof(struct uvc_request), GFP_KERNEL); - if (video->ureq == NULL) - return -ENOMEM; + list_add_tail(&ureq->list, &video->ureqs); - for (i = 0; i < video->uvc_num_requests; ++i) { - video->ureq[i].req_buffer = kmalloc(req_size, GFP_KERNEL); - if (video->ureq[i].req_buffer == NULL) + ureq->req_buffer = kmalloc(video->req_size, GFP_KERNEL); + if (ureq->req_buffer == NULL) goto error; - video->ureq[i].req = usb_ep_alloc_request(video->ep, GFP_KERNEL); - if (video->ureq[i].req == NULL) + ureq->req = usb_ep_alloc_request(video->ep, GFP_KERNEL); + if (ureq->req == NULL) goto error; - video->ureq[i].req->buf = video->ureq[i].req_buffer; - video->ureq[i].req->length = 0; - video->ureq[i].req->complete = uvc_video_complete; - video->ureq[i].req->context = &video->ureq[i]; - video->ureq[i].video = video; - video->ureq[i].last_buf = NULL; + ureq->req->buf = ureq->req_buffer; + ureq->req->length = 0; + ureq->req->complete = uvc_video_complete; + ureq->req->context = ureq; + ureq->video = video; + ureq->last_buf = NULL; - list_add_tail(&video->ureq[i].req->list, &video->req_free); + list_add_tail(&ureq->req->list, &video->req_free); /* req_size/PAGE_SIZE + 1 for overruns and + 1 for header */ - sg_alloc_table(&video->ureq[i].sgt, - DIV_ROUND_UP(req_size - UVCG_REQUEST_HEADER_LEN, + sg_alloc_table(&ureq->sgt, + DIV_ROUND_UP(video->req_size - UVCG_REQUEST_HEADER_LEN, PAGE_SIZE) + 2, GFP_KERNEL); } - video->req_size = req_size; - return 0; error: @@ -382,18 +609,23 @@ static void uvcg_video_pump(struct work_struct *work) { struct uvc_video *video = container_of(work, struct uvc_video, pump); struct uvc_video_queue *queue = &video->queue; + /* video->max_payload_size is only set when using bulk transfer */ + bool is_bulk = video->max_payload_size; struct usb_request *req = NULL; struct uvc_buffer *buf; unsigned long flags; - int ret; + int ret = 0; + + while (true) { + if (!video->ep->enabled) + return; - while (video->ep->enabled) { /* - * Retrieve the first available USB request, protected by the - * request lock. + * Check is_enabled and retrieve the first available USB + * request, protected by the request lock. */ spin_lock_irqsave(&video->req_lock, flags); - if (list_empty(&video->req_free)) { + if (!video->is_enabled || list_empty(&video->req_free)) { spin_unlock_irqrestore(&video->req_lock, flags); return; } @@ -408,58 +640,127 @@ static void uvcg_video_pump(struct work_struct *work) */ spin_lock_irqsave(&queue->irqlock, flags); buf = uvcg_queue_head(queue); - if (buf == NULL) { + if (!buf) { + /* + * Either the queue has been disconnected or no video buffer + * available for bulk transfer. Either way, stop processing + * further. + */ spin_unlock_irqrestore(&queue->irqlock, flags); break; } video->encode(req, video, buf); - /* - * With usb3 we have more requests. This will decrease the - * interrupt load to a quarter but also catches the corner - * cases, which needs to be handled. - */ - if (list_empty(&video->req_free) || - buf->state == UVC_BUF_STATE_DONE || - !(video->req_int_count % - DIV_ROUND_UP(video->uvc_num_requests, 4))) { - video->req_int_count = 0; - req->no_interrupt = 0; - } else { - req->no_interrupt = 1; - } - - /* Queue the USB request */ - ret = uvcg_video_ep_queue(video, req); spin_unlock_irqrestore(&queue->irqlock, flags); + spin_lock_irqsave(&video->req_lock, flags); + /* For bulk end points we queue from the worker thread + * since we would preferably not want to wait on requests + * to be ready, in the uvcg_video_complete() handler. + * For isoc endpoints we add the request to the ready list + * and only queue it to the endpoint from the complete handler. + */ + ret = uvcg_video_usb_req_queue(video, req, is_bulk); + spin_unlock_irqrestore(&video->req_lock, flags); + if (ret < 0) { uvcg_queue_cancel(queue, 0); break; } + } + spin_lock_irqsave(&video->req_lock, flags); + if (video->is_enabled) + list_add_tail(&req->list, &video->req_free); + else + uvc_video_free_request(req->context, video->ep); + spin_unlock_irqrestore(&video->req_lock, flags); +} + +/* + * Disable the video stream + */ +int +uvcg_video_disable(struct uvc_video *video) +{ + unsigned long flags; + struct list_head inflight_bufs; + struct usb_request *req, *temp; + struct uvc_buffer *buf, *btemp; + struct uvc_request *ureq, *utemp; + + if (video->ep == NULL) { + uvcg_info(&video->uvc->func, + "Video disable failed, device is uninitialized.\n"); + return -ENODEV; + } - /* Endpoint now owns the request */ - req = NULL; - if (buf->state != UVC_BUF_STATE_DONE) - video->req_int_count++; + INIT_LIST_HEAD(&inflight_bufs); + spin_lock_irqsave(&video->req_lock, flags); + video->is_enabled = false; + + /* + * Remove any in-flight buffers from the uvc_requests + * because we want to return them before cancelling the + * queue. This ensures that we aren't stuck waiting for + * all complete callbacks to come through before disabling + * vb2 queue. + */ + list_for_each_entry(ureq, &video->ureqs, list) { + if (ureq->last_buf) { + list_add_tail(&ureq->last_buf->queue, &inflight_bufs); + ureq->last_buf = NULL; + } } + spin_unlock_irqrestore(&video->req_lock, flags); - if (!req) - return; + cancel_work_sync(&video->pump); + uvcg_queue_cancel(&video->queue, 0); spin_lock_irqsave(&video->req_lock, flags); - list_add_tail(&req->list, &video->req_free); + /* + * Remove all uvc_requests from ureqs with list_del_init + * This lets uvc_video_free_request correctly identify + * if the uvc_request is attached to a list or not when freeing + * memory. + */ + list_for_each_entry_safe(ureq, utemp, &video->ureqs, list) + list_del_init(&ureq->list); + + list_for_each_entry_safe(req, temp, &video->req_free, list) { + list_del(&req->list); + uvc_video_free_request(req->context, video->ep); + } + + list_for_each_entry_safe(req, temp, &video->req_ready, list) { + list_del(&req->list); + uvc_video_free_request(req->context, video->ep); + } + + INIT_LIST_HEAD(&video->ureqs); + INIT_LIST_HEAD(&video->req_free); + INIT_LIST_HEAD(&video->req_ready); spin_unlock_irqrestore(&video->req_lock, flags); - return; + + /* + * Return all the video buffers before disabling the queue. + */ + spin_lock_irqsave(&video->queue.irqlock, flags); + list_for_each_entry_safe(buf, btemp, &inflight_bufs, queue) { + list_del(&buf->queue); + uvcg_complete_buffer(&video->queue, buf); + } + spin_unlock_irqrestore(&video->queue.irqlock, flags); + + uvcg_queue_enable(&video->queue, 0); + return 0; } /* - * Enable or disable the video stream. + * Enable the video stream. */ -int uvcg_video_enable(struct uvc_video *video, int enable) +int uvcg_video_enable(struct uvc_video *video) { - unsigned int i; int ret; if (video->ep == NULL) { @@ -468,18 +769,13 @@ int uvcg_video_enable(struct uvc_video *video, int enable) return -ENODEV; } - if (!enable) { - cancel_work_sync(&video->pump); - uvcg_queue_cancel(&video->queue, 0); - - for (i = 0; i < video->uvc_num_requests; ++i) - if (video->ureq && video->ureq[i].req) - usb_ep_dequeue(video->ep, video->ureq[i].req); - - uvc_video_free_requests(video); - uvcg_queue_enable(&video->queue, 0); - return 0; - } + /* + * Safe to access request related fields without req_lock because + * this is the only thread currently active, and no other + * request handling thread will become active until this function + * returns. + */ + video->is_enabled = true; if ((ret = uvcg_queue_enable(&video->queue, 1)) < 0) return ret; @@ -496,6 +792,9 @@ int uvcg_video_enable(struct uvc_video *video, int enable) video->req_int_count = 0; + atomic_set(&video->queued, 0); + + kthread_queue_work(video->kworker, &video->hw_submit); queue_work(video->async_wq, &video->pump); return ret; @@ -506,7 +805,10 @@ int uvcg_video_enable(struct uvc_video *video, int enable) */ int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc) { + video->is_enabled = false; + INIT_LIST_HEAD(&video->ureqs); INIT_LIST_HEAD(&video->req_free); + INIT_LIST_HEAD(&video->req_ready); spin_lock_init(&video->req_lock); INIT_WORK(&video->pump, uvcg_video_pump); @@ -515,16 +817,27 @@ int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc) if (!video->async_wq) return -EINVAL; + /* Allocate a kthread for asynchronous hw submit handler. */ + video->kworker = kthread_run_worker(0, "UVCG"); + if (IS_ERR(video->kworker)) { + uvcg_err(&video->uvc->func, "failed to create UVCG kworker\n"); + return PTR_ERR(video->kworker); + } + + kthread_init_work(&video->hw_submit, uvcg_video_hw_submit); + + sched_set_fifo(video->kworker->task); + video->uvc = uvc; video->fcc = V4L2_PIX_FMT_YUYV; video->bpp = 16; video->width = 320; video->height = 240; video->imagesize = 320 * 240 * 2; + video->interval = 666666; /* Initialize the video buffers queue. */ uvcg_queue_init(&video->queue, uvc->v4l2_dev.dev->parent, V4L2_BUF_TYPE_VIDEO_OUTPUT, &video->mutex); return 0; } - diff --git a/drivers/usb/gadget/function/uvc_video.h b/drivers/usb/gadget/function/uvc_video.h index 03adeefa343b..8ef6259741f1 100644 --- a/drivers/usb/gadget/function/uvc_video.h +++ b/drivers/usb/gadget/function/uvc_video.h @@ -14,7 +14,8 @@ struct uvc_video; -int uvcg_video_enable(struct uvc_video *video, int enable); +int uvcg_video_enable(struct uvc_video *video); +int uvcg_video_disable(struct uvc_video *video); int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc); |
