On Wed, Dec 11, 2019 at 07:02:41PM +0100, Andrey Konovalov wrote: > USB Raw Gadget is a kernel module that provides a userspace interface for > the USB Gadget subsystem. Essentially it allows to emulate USB devices > from userspace. Enabled with CONFIG_USB_RAW_GADGET. Raw Gadget is > currently a strictly debugging feature and shouldn't be used in > production. > > Raw Gadget is similar to GadgetFS, but provides a more low-level and > direct access to the USB Gadget layer for the userspace. The key > differences are: > > 1. Every USB request is passed to the userspace to get a response, while > GadgetFS responds to some USB requests internally based on the provided > descriptors. However note, that the UDC driver might respond to some > requests on its own and never forward them to the Gadget layer. > > 2. GadgetFS performs some sanity checks on the provided USB descriptors, > while Raw Gadget allows you to provide arbitrary data as responses to > USB requests. > > 3. Raw Gadget provides a way to select a UDC device/driver to bind to, > while GadgetFS currently binds to the first available UDC. > > 4. Raw Gadget uses predictable endpoint names (handles) across different > UDCs (as long as UDCs have enough endpoints of each required transfer > type). > > 5. Raw Gadget has ioctl-based interface instead of a filesystem-based one. Looks good to me, only minor comments below. > Signed-off-by: Andrey Konovalov <andreyknvl@xxxxxxxxxx> > --- > Documentation/usb/index.rst | 1 + > Documentation/usb/raw-gadget.rst | 59 ++ > drivers/usb/gadget/legacy/Kconfig | 8 + > drivers/usb/gadget/legacy/Makefile | 1 + > drivers/usb/gadget/legacy/raw.c | 1070 +++++++++++++++++++++++++++ > include/uapi/linux/usb/raw_gadget.h | 167 +++++ > 6 files changed, 1306 insertions(+) > create mode 100644 Documentation/usb/raw-gadget.rst > create mode 100644 drivers/usb/gadget/legacy/raw.c > create mode 100644 include/uapi/linux/usb/raw_gadget.h > > diff --git a/Documentation/usb/index.rst b/Documentation/usb/index.rst > index e55386a4abfb..90310e2a0c1f 100644 > --- a/Documentation/usb/index.rst > +++ b/Documentation/usb/index.rst > @@ -22,6 +22,7 @@ USB support > misc_usbsevseg > mtouchusb > ohci > + raw-gadget > rio > usbip_protocol > usbmon > diff --git a/Documentation/usb/raw-gadget.rst b/Documentation/usb/raw-gadget.rst > new file mode 100644 > index 000000000000..cbedf5451ed3 > --- /dev/null > +++ b/Documentation/usb/raw-gadget.rst > @@ -0,0 +1,59 @@ > +============== > +USB Raw Gadget > +============== > + > +USB Raw Gadget is a kernel module that provides a userspace interface for > +the USB Gadget subsystem. Essentially it allows to emulate USB devices > +from userspace. Enabled with CONFIG_USB_RAW_GADGET. Raw Gadget is > +currently a strictly debugging feature and shouldn't be used in > +production, use GadgetFS instead. > + > +Comparison to GadgetFS > +~~~~~~~~~~~~~~~~~~~~~~ > + > +Raw Gadget is similar to GadgetFS, but provides a more low-level and > +direct access to the USB Gadget layer for the userspace. The key > +differences are: > + > +1. Every USB request is passed to the userspace to get a response, while > + GadgetFS responds to some USB requests internally based on the provided > + descriptors. However note, that the UDC driver might respond to some > + requests on its own and never forward them to the Gadget layer. > + > +2. GadgetFS performs some sanity checks on the provided USB descriptors, > + while Raw Gadget allows you to provide arbitrary data as responses to > + USB requests. > + > +3. Raw Gadget provides a way to select a UDC device/driver to bind to, > + while GadgetFS currently binds to the first available UDC. > + > +4. Raw Gadget uses predictable endpoint names (handles) across different > + UDCs (as long as UDCs have enough endpoints of each required transfer > + type). > + > +5. Raw Gadget has ioctl-based interface instead of a filesystem-based one. > + > +Userspace interface > +~~~~~~~~~~~~~~~~~~~ > + > +To create a Raw Gadget instance open /dev/raw-gadget. Multiple raw-gadget > +instances (bound to different UDCs) can be used at the same time. The > +interaction with the opened file happens through the ioctl() calls, see > +comments in include/uapi/linux/usb/raw_gadget.h for details. > + > +The typical usage of Raw Gadget looks like: > + > +1. Open Raw Gadget instance via /dev/raw-gadget. > +2. Initialize the instance via USB_RAW_IOCTL_INIT. > +3. Launch the instance with USB_RAW_IOCTL_RUN. > +4. In a loop issue USB_RAW_IOCTL_EVENT_FETCH calls to receive events from > + Raw Gadget and react to those depending on what kind of USB device > + needs to be emulated. > + > +Potential future improvements > +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ > + > +- Implement ioctl's for setting/clearing halt status on endpoints. > + > +- Reporting more events (suspend, resume, etc.) through > + USB_RAW_IOCTL_EVENT_FETCH. > diff --git a/drivers/usb/gadget/legacy/Kconfig b/drivers/usb/gadget/legacy/Kconfig > index 119a4e47681f..8c947edf7515 100644 > --- a/drivers/usb/gadget/legacy/Kconfig > +++ b/drivers/usb/gadget/legacy/Kconfig > @@ -489,3 +489,11 @@ config USB_G_WEBCAM > > Say "y" to link the driver statically, or "m" to build a > dynamically linked module called "g_webcam". > + > +config USB_RAW_GADGET > + tristate "USB Raw Gadget" > + help > + USB Raw Gadget is a kernel module that provides a userspace interface > + for the USB Gadget subsystem. Essentially it allows to emulate USB > + devices from userspace. See Documentation/usb/raw-gadget.rst for > + details. No help text saying what the module name would be if selected as one? > diff --git a/drivers/usb/gadget/legacy/Makefile b/drivers/usb/gadget/legacy/Makefile > index abd0c3e66a05..799feefeee58 100644 > --- a/drivers/usb/gadget/legacy/Makefile > +++ b/drivers/usb/gadget/legacy/Makefile > @@ -43,3 +43,4 @@ obj-$(CONFIG_USB_G_WEBCAM) += g_webcam.o > obj-$(CONFIG_USB_G_NCM) += g_ncm.o > obj-$(CONFIG_USB_G_ACM_MS) += g_acm_ms.o > obj-$(CONFIG_USB_GADGET_TARGET) += tcm_usb_gadget.o > +obj-$(CONFIG_USB_RAW_GADGET) += raw.o raw.ko? I think we already have a raw.ko in the kernel, shouldn't this be usb_raw_gadget.ko or something? Yeah, we do, look at drivers/char/raw.c. This needs to be changed, I can't take it as-is otherwise the builders will croak on it. > diff --git a/drivers/usb/gadget/legacy/raw.c b/drivers/usb/gadget/legacy/raw.c > new file mode 100644 > index 000000000000..fcfa2ebc103e > --- /dev/null > +++ b/drivers/usb/gadget/legacy/raw.c > @@ -0,0 +1,1070 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * USB Raw Gadget driver. > + * See Documentation/usb/raw-gadget.rst for more details. > + * > + * Andrey Konovalov <andreyknvl@xxxxxxxxx> > + */ > + > +#define pr_fmt(fmt) "raw: %s: " fmt, __func__ "raw gadget"? This a driver, why do you ever need to do a pr_* call and not a dev_* instead? > + > +#include <linux/compiler.h> > +#include <linux/debugfs.h> > +#include <linux/delay.h> > +#include <linux/kref.h> > +#include <linux/miscdevice.h> > +#include <linux/module.h> > +#include <linux/semaphore.h> > +#include <linux/sched.h> > +#include <linux/slab.h> > +#include <linux/uaccess.h> > +#include <linux/wait.h> > + > +#include <linux/usb.h> > +#include <linux/usb/ch9.h> > +#include <linux/usb/ch11.h> > +#include <linux/usb/gadget.h> > + > +#include <uapi/linux/usb/raw_gadget.h> > + > +#define DRIVER_DESC "USB Raw Gadget" > +#define DRIVER_NAME "raw-gadget" You only use this in one place. You can use it in the definition of the misc driver name also, right? > + > +MODULE_DESCRIPTION(DRIVER_DESC); > +MODULE_AUTHOR("Andrey Konovalov"); > +MODULE_LICENSE("GPL"); > + > +/*----------------------------------------------------------------------*/ > + > +#define RAW_EVENT_QUEUE_SIZE 128 > + > +struct raw_event_queue { > + spinlock_t lock; > + struct semaphore sema; Two locks for the same structure? That feels "wrong". Why is this? document the heck out of it if it is required please. > + struct usb_raw_event *events[RAW_EVENT_QUEUE_SIZE]; > + int size; > +}; > + > +static void raw_event_queue_init(struct raw_event_queue *queue) > +{ > + spin_lock_init(&queue->lock); > + sema_init(&queue->sema, 0); > + queue->size = 0; > +} > + > +static int raw_event_queue_add(struct raw_event_queue *queue, > + enum usb_raw_event_type type, size_t length, const void *data) > +{ > + unsigned long flags; > + struct usb_raw_event *event; > + > + spin_lock_irqsave(&queue->lock, flags); > + if (queue->size >= RAW_EVENT_QUEUE_SIZE) { > + pr_err("too many events\n"); What can someone do with this error? dev_err() to show exactly where it came from? > + spin_unlock_irqrestore(&queue->lock, flags); > + return -ENOMEM; > + } > + event = kmalloc(sizeof(*event) + length, GFP_ATOMIC); > + if (!event) { > + spin_unlock_irqrestore(&queue->lock, flags); > + return -ENOMEM; > + } > + event->type = type; > + event->length = length; > + if (event->length) > + memcpy(&event->data[0], data, length); > + queue->events[queue->size] = event; > + queue->size++; > + up(&queue->sema); > + spin_unlock_irqrestore(&queue->lock, flags); > + return 0; > +} > + > +static struct usb_raw_event *raw_event_queue_fetch( > + struct raw_event_queue *queue) > +{ > + unsigned long flags; > + struct usb_raw_event *event; > + > + if (down_interruptible(&queue->sema)) > + return NULL; > + spin_lock_irqsave(&queue->lock, flags); You just locked twice? > + if (WARN_ON(!queue->size)) > + return NULL; It's as if you want to trigger syzbot :) Just log it and return an error. > + event = queue->events[0]; > + queue->size--; > + memmove(&queue->events[0], &queue->events[1], > + queue->size * sizeof(queue->events[0])); > + spin_unlock_irqrestore(&queue->lock, flags); > + return event; > +} > + > +static void raw_event_queue_destroy(struct raw_event_queue *queue) > +{ > + int i; > + > + for (i = 0; i < queue->size; i++) > + kfree(queue->events[i]); > + queue->size = 0; No locking needed? > +} > + > +/*----------------------------------------------------------------------*/ > + > +struct raw_dev; > + > +#define USB_RAW_MAX_ENDPOINTS 32 > + > +enum ep_state { > + STATE_EP_DISABLED, > + STATE_EP_ENABLED, > +}; > + > +struct raw_ep { > + struct raw_dev *dev; > + enum ep_state state; > + struct usb_ep *ep; > + struct usb_request *req; > + bool urb_queued; > + bool disabling; > + ssize_t status; > +}; > + > +enum dev_state { > + STATE_DEV_INVALID = 0, > + STATE_DEV_OPENED, > + STATE_DEV_INITIALIZED, > + STATE_DEV_RUNNING, > + STATE_DEV_CLOSED, > + STATE_DEV_FAILED > +}; > + > +struct raw_dev { > + struct kref count; > + spinlock_t lock; > + > + const char *udc_name; > + struct usb_gadget_driver driver; A dev embeds a driver? Not a pointer? But you have a kref, so the reference count of this object is there, right? > + > + /* Protected by lock: */ > + enum dev_state state; > + bool gadget_registered; > + struct usb_gadget *gadget; > + struct usb_request *req; > + bool ep0_in_pending; > + bool ep0_out_pending; > + bool ep0_urb_queued; > + ssize_t ep0_status; > + struct raw_ep eps[USB_RAW_MAX_ENDPOINTS]; > + > + struct completion ep0_done; > + struct raw_event_queue queue; No pointer to your misc device? I think you need that for logging. > +}; > + > +static struct raw_dev *dev_new(void) > +{ > + struct raw_dev *dev; > + > + dev = kzalloc(sizeof(*dev), GFP_KERNEL); > + if (!dev) > + return NULL; > + /* Matches kref_put() in raw_release(). */ > + kref_init(&dev->count); > + spin_lock_init(&dev->lock); > + init_completion(&dev->ep0_done); > + raw_event_queue_init(&dev->queue); > + return dev; > +} > + > +static void dev_free(struct kref *kref) > +{ > + struct raw_dev *dev = container_of(kref, struct raw_dev, count); > + int i; > + > + kfree(dev->udc_name); > + kfree(dev->driver.udc_name); > + if (dev->req) { > + if (dev->ep0_urb_queued) > + usb_ep_dequeue(dev->gadget->ep0, dev->req); > + usb_ep_free_request(dev->gadget->ep0, dev->req); > + } > + raw_event_queue_destroy(&dev->queue); > + for (i = 0; i < USB_RAW_MAX_ENDPOINTS; i++) { > + if (dev->eps[i].state != STATE_EP_ENABLED) > + continue; > + usb_ep_disable(dev->eps[i].ep); > + usb_ep_free_request(dev->eps[i].ep, dev->eps[i].req); > + kfree(dev->eps[i].ep->desc); > + dev->eps[i].state = STATE_EP_DISABLED; > + } > + kfree(dev); > +} > + > +/*----------------------------------------------------------------------*/ > + > +static int raw_queue_event(struct raw_dev *dev, > + enum usb_raw_event_type type, size_t length, const void *data) > +{ > + int ret = 0; > + unsigned long flags; > + > + ret = raw_event_queue_add(&dev->queue, type, length, data); > + if (ret < 0) { > + spin_lock_irqsave(&dev->lock, flags); > + dev->state = STATE_DEV_FAILED; > + spin_unlock_irqrestore(&dev->lock, flags); > + } > + return ret; > +} > + > +static void gadget_ep0_complete(struct usb_ep *ep, struct usb_request *req) > +{ > + struct raw_dev *dev = req->context; > + unsigned long flags; > + > + spin_lock_irqsave(&dev->lock, flags); > + if (req->status) > + dev->ep0_status = req->status; > + else > + dev->ep0_status = req->actual; > + if (dev->ep0_in_pending) > + dev->ep0_in_pending = false; > + else > + dev->ep0_out_pending = false; > + spin_unlock_irqrestore(&dev->lock, flags); > + > + complete(&dev->ep0_done); > +} > + > +static int gadget_bind(struct usb_gadget *gadget, > + struct usb_gadget_driver *driver) > +{ > + int ret = 0; > + struct raw_dev *dev = container_of(driver, struct raw_dev, driver); > + struct usb_request *req; > + unsigned long flags; > + > + if (strcmp(gadget->name, dev->udc_name) != 0) > + return -ENODEV; > + > + set_gadget_data(gadget, dev); > + req = usb_ep_alloc_request(gadget->ep0, GFP_KERNEL); > + if (!req) { > + pr_err("usb_ep_alloc_request failed\n"); > + set_gadget_data(gadget, NULL); > + return -ENOMEM; > + } > + > + spin_lock_irqsave(&dev->lock, flags); > + dev->req = req; > + dev->req->context = dev; > + dev->req->complete = gadget_ep0_complete; > + dev->gadget = gadget; > + spin_unlock_irqrestore(&dev->lock, flags); > + > + /* Matches kref_put() in gadget_unbind(). */ > + kref_get(&dev->count); > + > + ret = raw_queue_event(dev, USB_RAW_EVENT_CONNECT, 0, NULL); > + if (ret < 0) > + pr_err("failed to queue event\n"); > + > + return ret; > +} > + > +static void gadget_unbind(struct usb_gadget *gadget) > +{ > + struct raw_dev *dev = get_gadget_data(gadget); > + unsigned long flags; > + > + spin_lock_irqsave(&dev->lock, flags); > + set_gadget_data(gadget, NULL); > + spin_unlock_irqrestore(&dev->lock, flags); > + /* Matches kref_get() in gadget_bind(). */ > + kref_put(&dev->count, dev_free); What protects the kref from being called 'put' twice on the same pointer at the same time? There should be some lock somewhere, right? > +} > + > +static int gadget_setup(struct usb_gadget *gadget, > + const struct usb_ctrlrequest *ctrl) > +{ > + int ret = 0; > + struct raw_dev *dev = get_gadget_data(gadget); > + unsigned long flags; > + > + spin_lock_irqsave(&dev->lock, flags); > + if (dev->state != STATE_DEV_RUNNING) { > + pr_err("ignoring, device is not running\n"); > + ret = -ENODEV; > + goto out_unlock; > + } > + if (dev->ep0_in_pending || dev->ep0_out_pending) { > + pr_debug("stalling, already have pending request\n"); > + ret = -EBUSY; > + goto out_unlock; > + } > + if ((ctrl->bRequestType & USB_DIR_IN) && ctrl->wLength) > + dev->ep0_in_pending = true; > + else > + dev->ep0_out_pending = true; > + spin_unlock_irqrestore(&dev->lock, flags); > + > + ret = raw_queue_event(dev, USB_RAW_EVENT_CONTROL, sizeof(*ctrl), ctrl); > + if (ret < 0) > + pr_err("failed to queue event\n"); > + goto out; > + > +out_unlock: > + spin_unlock_irqrestore(&dev->lock, flags); > +out: > + return ret; > +} > + > +/* These are currently ununsed but present in case UDC driver requires them. */ > +static void gadget_disconnect(struct usb_gadget *gadget) { } > +static void gadget_suspend(struct usb_gadget *gadget) { } > +static void gadget_resume(struct usb_gadget *gadget) { } > +static void gadget_reset(struct usb_gadget *gadget) { } > + > +/*----------------------------------------------------------------------*/ > + > +static int raw_open(struct inode *inode, struct file *fd) > +{ > + struct raw_dev *dev; > + > + dev = dev_new(); > + if (!dev) { > + pr_err("failed to created device"); You should have access to your misc device here, so dev_err()? > + return -ENOMEM; > + } > + fd->private_data = dev; > + dev->state = STATE_DEV_OPENED; > + return 0; > +} > + > +static int raw_release(struct inode *inode, struct file *fd) > +{ > + int ret = 0; > + struct raw_dev *dev = fd->private_data; > + unsigned long flags; > + bool unregister = false; > + > + spin_lock_irqsave(&dev->lock, flags); > + dev->state = STATE_DEV_CLOSED; > + if (!dev->gadget) { > + spin_unlock_irqrestore(&dev->lock, flags); > + goto out_put; > + } > + if (dev->gadget_registered) > + unregister = true; > + dev->gadget_registered = false; > + spin_unlock_irqrestore(&dev->lock, flags); > + > + if (unregister) { > + ret = usb_gadget_unregister_driver(&dev->driver); > + WARN_ON(ret != 0); you want to crash? :) > + /* Matches kref_get() in raw_ioctl_run(). */ > + kref_put(&dev->count, dev_free); > + } > + > +out_put: > + /* Matches dev_new() in raw_open(). */ > + kref_put(&dev->count, dev_free); > + return ret; > +} > + > +/*----------------------------------------------------------------------*/ > + > +static int raw_ioctl_init(struct raw_dev *dev, unsigned long value) > +{ > + int ret = 0; > + struct usb_raw_init arg; > + char *udc_driver_name; > + char *udc_device_name; > + unsigned long flags; > + > + ret = copy_from_user(&arg, (void __user *)value, sizeof(arg)); > + if (ret) > + return ret; > + > + switch (arg.speed) { > + case USB_SPEED_UNKNOWN: > + arg.speed = USB_SPEED_HIGH; > + break; > + case USB_SPEED_LOW: > + case USB_SPEED_FULL: > + case USB_SPEED_HIGH: > + case USB_SPEED_SUPER: > + break; > + default: > + return -EINVAL; > + } > + > + udc_driver_name = kmalloc(UDC_NAME_LENGTH_MAX, GFP_KERNEL); > + if (!udc_driver_name) > + return -ENOMEM; > + ret = strscpy(udc_driver_name, &arg.driver_name[0], > + UDC_NAME_LENGTH_MAX); > + if (ret < 0) { > + kfree(udc_driver_name); > + return ret; > + } > + ret = 0; > + > + udc_device_name = kmalloc(UDC_NAME_LENGTH_MAX, GFP_KERNEL); > + if (!udc_device_name) { > + kfree(udc_driver_name); > + return -ENOMEM; > + } > + ret = strscpy(udc_device_name, &arg.device_name[0], > + UDC_NAME_LENGTH_MAX); > + if (ret < 0) { > + kfree(udc_driver_name); > + kfree(udc_device_name); > + return ret; > + } > + ret = 0; > + > + spin_lock_irqsave(&dev->lock, flags); > + if (dev->state != STATE_DEV_OPENED) { > + pr_debug("fail, device is not opened\n"); dev_dbg(). > + kfree(udc_driver_name); > + kfree(udc_device_name); > + ret = -EINVAL; > + goto out_unlock; > + } > + dev->udc_name = udc_driver_name; > + > + dev->driver.function = DRIVER_DESC; > + dev->driver.max_speed = arg.speed; > + dev->driver.setup = gadget_setup; > + dev->driver.disconnect = gadget_disconnect; > + dev->driver.bind = gadget_bind; > + dev->driver.unbind = gadget_unbind; > + dev->driver.suspend = gadget_suspend; > + dev->driver.resume = gadget_resume; > + dev->driver.reset = gadget_reset; > + dev->driver.driver.name = DRIVER_NAME; > + dev->driver.udc_name = udc_device_name; > + dev->driver.match_existing_only = 1; > + > + dev->state = STATE_DEV_INITIALIZED; > + > +out_unlock: > + spin_unlock_irqrestore(&dev->lock, flags); > + return ret; > +} > + > +static int raw_ioctl_run(struct raw_dev *dev, unsigned long value) > +{ > + int ret = 0; > + unsigned long flags; > + > + if (value) > + return -EINVAL; > + > + spin_lock_irqsave(&dev->lock, flags); > + if (dev->state != STATE_DEV_INITIALIZED) { > + pr_debug("fail, device is not initialized\n"); dev_dbg() > + ret = -EINVAL; > + goto out_unlock; > + } > + spin_unlock_irqrestore(&dev->lock, flags); > + > + ret = usb_gadget_probe_driver(&dev->driver); > + > + spin_lock_irqsave(&dev->lock, flags); > + if (ret) { > + pr_err("fail, usb_gadget_probe_driver returned %d\n", ret); dev_err() same goes for everywhere, no need for any pr_* calls in this driver. > + dev->state = STATE_DEV_FAILED; > + goto out_unlock; > + } > + dev->gadget_registered = true; > + dev->state = STATE_DEV_RUNNING; > + /* Matches kref_put() in raw_release(). */ > + kref_get(&dev->count); > + > +out_unlock: > + spin_unlock_irqrestore(&dev->lock, flags); > + return ret; > +} > + > +static int raw_ioctl_event_fetch(struct raw_dev *dev, unsigned long value) > +{ > + int ret = 0; > + struct usb_raw_event arg; > + unsigned long flags; > + struct usb_raw_event *event; > + uint32_t length; > + > + ret = copy_from_user(&arg, (void __user *)value, sizeof(arg)); > + if (ret) > + return ret; > + > + spin_lock_irqsave(&dev->lock, flags); > + if (dev->state != STATE_DEV_RUNNING) { > + pr_debug("fail, device is not running\n"); > + spin_unlock_irqrestore(&dev->lock, flags); > + return -EINVAL; > + } > + if (!dev->gadget) { > + pr_debug("fail, gadget is not bound\n"); > + spin_unlock_irqrestore(&dev->lock, flags); > + return -EBUSY; > + } > + spin_unlock_irqrestore(&dev->lock, flags); > + > + event = raw_event_queue_fetch(&dev->queue); > + if (!event) { > + pr_debug("event fetching interrupted\n"); > + return -EINTR; > + } > + length = min(arg.length, event->length); > + ret = copy_to_user((void __user *)value, event, > + sizeof(*event) + length); > + return ret; > +} > + > +static void *raw_alloc_io_data(struct usb_raw_ep_io *io, void __user *ptr, > + bool get_from_user) > +{ > + int ret; > + void *data; > + > + ret = copy_from_user(io, ptr, sizeof(*io)); > + if (ret) > + return ERR_PTR(ret); > + if (io->ep >= USB_RAW_MAX_ENDPOINTS) > + return ERR_PTR(-EINVAL); > + if (!usb_raw_io_flags_valid(io->flags)) > + return ERR_PTR(-EINVAL); > + if (io->length > PAGE_SIZE) > + return ERR_PTR(-EINVAL); > + if (get_from_user) > + data = memdup_user(ptr + sizeof(*io), io->length); > + else { > + data = kmalloc(io->length, GFP_KERNEL); > + if (!data) > + data = ERR_PTR(-ENOMEM); > + } > + return data; > +} > + > +static int raw_process_ep0_io(struct raw_dev *dev, struct usb_raw_ep_io *io, > + void *data, bool in) > +{ > + int ret = 0; > + unsigned long flags; > + > + spin_lock_irqsave(&dev->lock, flags); > + if (dev->state != STATE_DEV_RUNNING) { > + pr_debug("fail, device is not running\n"); > + ret = -EINVAL; > + goto out_unlock; > + } > + if (!dev->gadget) { > + pr_debug("fail, gadget is not bound\n"); > + ret = -EBUSY; > + goto out_unlock; > + } > + if (dev->ep0_urb_queued) { > + pr_debug("fail, urb already queued\n"); > + ret = -EBUSY; > + goto out_unlock; > + } > + if ((in && !dev->ep0_in_pending) || > + (!in && !dev->ep0_out_pending)) { > + pr_debug("fail, wrong direction\n"); > + ret = -EBUSY; > + goto out_unlock; > + } > + if (WARN_ON(in && dev->ep0_out_pending)) { > + ret = -ENODEV; > + dev->state = STATE_DEV_FAILED; > + goto out_done; > + } > + if (WARN_ON(!in && dev->ep0_in_pending)) { > + ret = -ENODEV; > + dev->state = STATE_DEV_FAILED; > + goto out_done; > + } > + > + dev->req->buf = data; > + dev->req->length = io->length; > + dev->req->zero = usb_raw_io_flags_zero(io->flags); > + dev->ep0_urb_queued = true; > + spin_unlock_irqrestore(&dev->lock, flags); > + > + ret = usb_ep_queue(dev->gadget->ep0, dev->req, GFP_ATOMIC); > + if (ret) { > + pr_err("fail, usb_ep_queue returned %d\n", ret); > + spin_lock_irqsave(&dev->lock, flags); > + dev->state = STATE_DEV_FAILED; > + goto out_done; > + } > + > + ret = wait_for_completion_interruptible(&dev->ep0_done); > + if (ret) { > + pr_debug("wait interrupted\n"); > + usb_ep_dequeue(dev->gadget->ep0, dev->req); > + wait_for_completion(&dev->ep0_done); > + spin_lock_irqsave(&dev->lock, flags); > + goto out_done; > + } > + > + spin_lock_irqsave(&dev->lock, flags); > + ret = dev->ep0_status; > + > +out_done: > + dev->ep0_urb_queued = false; > +out_unlock: > + spin_unlock_irqrestore(&dev->lock, flags); > + return ret; > +} > + > +static int raw_ioctl_ep0_write(struct raw_dev *dev, unsigned long value) > +{ > + int ret = 0; > + void *data; > + struct usb_raw_ep_io io; > + > + data = raw_alloc_io_data(&io, (void __user *)value, true); > + if (IS_ERR(data)) > + return PTR_ERR(data); > + ret = raw_process_ep0_io(dev, &io, data, true); > + kfree(data); > + return ret; > +} > + > +static int raw_ioctl_ep0_read(struct raw_dev *dev, unsigned long value) > +{ > + int ret = 0; > + void *data; > + struct usb_raw_ep_io io; > + unsigned int length; > + > + data = raw_alloc_io_data(&io, (void __user *)value, false); > + if (IS_ERR(data)) > + return PTR_ERR(data); > + ret = raw_process_ep0_io(dev, &io, data, false); > + if (ret < 0) { > + kfree(data); > + return ret; > + } > + length = min(io.length, (unsigned int)ret); > + ret = copy_to_user((void __user *)(value + sizeof(io)), data, length); > + kfree(data); > + return ret; > +} > + > +static bool check_ep_caps(struct usb_ep *ep, > + struct usb_endpoint_descriptor *desc) > +{ > + switch (usb_endpoint_type(desc)) { > + case USB_ENDPOINT_XFER_ISOC: > + if (!ep->caps.type_iso) > + return false; > + break; > + case USB_ENDPOINT_XFER_BULK: > + if (!ep->caps.type_bulk) > + return false; > + break; > + case USB_ENDPOINT_XFER_INT: > + if (!ep->caps.type_int) > + return false; > + break; > + default: > + return false; > + } > + > + if (usb_endpoint_dir_in(desc) && !ep->caps.dir_in) > + return false; > + if (usb_endpoint_dir_out(desc) && !ep->caps.dir_out) > + return false; > + > + return true; > +} > + > +static int raw_ioctl_ep_enable(struct raw_dev *dev, unsigned long value) > +{ > + int ret = 0, i; > + unsigned long flags; > + struct usb_endpoint_descriptor *desc; > + struct usb_ep *ep = NULL; > + > + desc = memdup_user((void __user *)value, sizeof(*desc)); > + if (IS_ERR(desc)) > + return PTR_ERR(desc); > + > + /* > + * Endpoints with a maxpacket length of 0 can cause crashes in UDC > + * drivers. > + */ > + if (usb_endpoint_maxp(desc) == 0) { > + kfree(desc); > + return -EINVAL; > + } > + > + spin_lock_irqsave(&dev->lock, flags); > + if (dev->state != STATE_DEV_RUNNING) { > + pr_debug("fail, device is not running\n"); > + ret = -EINVAL; > + goto out_free; > + } > + if (!dev->gadget) { > + pr_debug("fail, gadget is not bound\n"); > + ret = -EBUSY; > + goto out_free; > + } > + > + for (i = 0; i < USB_RAW_MAX_ENDPOINTS; i++) { > + if (dev->eps[i].state == STATE_EP_ENABLED) > + continue; > + break; > + } > + if (i == USB_RAW_MAX_ENDPOINTS) { > + pr_debug("fail, no device endpoints available\n"); > + ret = -EBUSY; > + goto out_free; > + } > + > + gadget_for_each_ep(ep, dev->gadget) { > + if (ep->enabled) > + continue; > + if (!check_ep_caps(ep, desc)) > + continue; > + ep->desc = desc; > + ret = usb_ep_enable(ep); > + if (ret < 0) { > + pr_err("fail, usb_ep_enable returned %d\n", ret); > + goto out_free; > + } > + dev->eps[i].req = usb_ep_alloc_request(ep, GFP_ATOMIC); > + if (!dev->eps[i].req) { > + pr_err("fail, usb_ep_alloc_request failed\n"); > + usb_ep_disable(ep); > + ret = -ENOMEM; > + goto out_free; > + } > + dev->eps[i].ep = ep; > + dev->eps[i].state = STATE_EP_ENABLED; > + ep->driver_data = &dev->eps[i]; > + ret = i; > + goto out_unlock; > + } > + > + pr_debug("fail, no gadget endpoints available\n"); > + ret = -EBUSY; > + > +out_free: > + kfree(desc); > +out_unlock: > + spin_unlock_irqrestore(&dev->lock, flags); > + return ret; > +} > + > +static int raw_ioctl_ep_disable(struct raw_dev *dev, unsigned long value) > +{ > + int ret = 0, i = value; > + unsigned long flags; > + const void *desc; > + > + if (i < 0 || i >= USB_RAW_MAX_ENDPOINTS) > + return -EINVAL; > + > + spin_lock_irqsave(&dev->lock, flags); > + if (dev->state != STATE_DEV_RUNNING) { > + pr_debug("fail, device is not running\n"); > + ret = -EINVAL; > + goto out_unlock; > + } > + if (!dev->gadget) { > + pr_debug("fail, gadget is not bound\n"); > + ret = -EBUSY; > + goto out_unlock; > + } > + if (dev->eps[i].state != STATE_EP_ENABLED) { > + pr_debug("fail, endpoint is not enabled\n"); > + ret = -EINVAL; > + goto out_unlock; > + } > + if (dev->eps[i].disabling) { > + pr_debug("fail, disable already in progress\n"); > + ret = -EINVAL; > + goto out_unlock; > + } > + dev->eps[i].disabling = true; > + spin_unlock_irqrestore(&dev->lock, flags); > + > + usb_ep_disable(dev->eps[i].ep); > + > + spin_lock_irqsave(&dev->lock, flags); > + usb_ep_free_request(dev->eps[i].ep, dev->eps[i].req); > + desc = dev->eps[i].ep->desc; > + dev->eps[i].ep = NULL; > + dev->eps[i].state = STATE_EP_DISABLED; > + kfree(desc); > + dev->eps[i].disabling = false; > + > +out_unlock: > + spin_unlock_irqrestore(&dev->lock, flags); > + return ret; > +} > + > +static void gadget_ep_complete(struct usb_ep *ep, struct usb_request *req) > +{ > + struct raw_ep *r_ep = (struct raw_ep *)ep->driver_data; > + struct raw_dev *dev = r_ep->dev; > + unsigned long flags; > + > + spin_lock_irqsave(&dev->lock, flags); > + if (req->status) > + r_ep->status = req->status; > + else > + r_ep->status = req->actual; > + spin_unlock_irqrestore(&dev->lock, flags); > + > + complete((struct completion *)req->context); > +} > + > +static int raw_process_ep_io(struct raw_dev *dev, struct usb_raw_ep_io *io, > + void *data, bool in) > +{ > + int ret = 0; > + unsigned long flags; > + struct raw_ep *ep = &dev->eps[io->ep]; > + DECLARE_COMPLETION_ONSTACK(done); > + > + spin_lock_irqsave(&dev->lock, flags); > + if (dev->state != STATE_DEV_RUNNING) { > + pr_debug("fail, device is not running\n"); > + ret = -EINVAL; > + goto out_unlock; > + } > + if (!dev->gadget) { > + pr_debug("fail, gadget is not bound\n"); > + ret = -EBUSY; > + goto out_unlock; > + } > + if (ep->state != STATE_EP_ENABLED) { > + pr_debug("fail, endpoint is not enabled\n"); > + ret = -EBUSY; > + goto out_unlock; > + } > + if (ep->disabling) { > + pr_debug("fail, endpoint is being disabled\n"); > + ret = -EBUSY; > + goto out_unlock; > + } > + if (ep->urb_queued) { > + pr_debug("fail, urb already queued\n"); > + ret = -EBUSY; > + goto out_unlock; > + } > + if ((in && !ep->ep->caps.dir_in) || (!in && ep->ep->caps.dir_in)) { > + pr_debug("fail, wrong direction\n"); > + ret = -EINVAL; > + goto out_unlock; > + } > + > + ep->dev = dev; > + ep->req->context = &done; > + ep->req->complete = gadget_ep_complete; > + ep->req->buf = data; > + ep->req->length = io->length; > + ep->req->zero = usb_raw_io_flags_zero(io->flags); > + ep->urb_queued = true; > + spin_unlock_irqrestore(&dev->lock, flags); > + > + ret = usb_ep_queue(ep->ep, ep->req, GFP_ATOMIC); > + if (ret) { > + pr_err("fail, usb_ep_queue returned %d\n", ret); > + spin_lock_irqsave(&dev->lock, flags); > + dev->state = STATE_DEV_FAILED; > + goto out_done; > + } > + > + ret = wait_for_completion_interruptible(&done); > + if (ret) { > + pr_debug("wait interrupted\n"); > + usb_ep_dequeue(ep->ep, ep->req); > + wait_for_completion(&done); > + spin_lock_irqsave(&dev->lock, flags); > + goto out_done; > + } > + > + spin_lock_irqsave(&dev->lock, flags); > + ret = ep->status; > + > +out_done: > + ep->urb_queued = false; > +out_unlock: > + spin_unlock_irqrestore(&dev->lock, flags); > + return ret; > +} > + > +static int raw_ioctl_ep_write(struct raw_dev *dev, unsigned long value) > +{ > + int ret = 0; > + char *data; > + struct usb_raw_ep_io io; > + > + data = raw_alloc_io_data(&io, (void __user *)value, true); > + if (IS_ERR(data)) > + return PTR_ERR(data); > + ret = raw_process_ep_io(dev, &io, data, true); > + kfree(data); > + return ret; > +} > + > +static int raw_ioctl_ep_read(struct raw_dev *dev, unsigned long value) > +{ > + int ret = 0; > + char *data; > + struct usb_raw_ep_io io; > + unsigned int length; > + > + data = raw_alloc_io_data(&io, (void __user *)value, false); > + if (IS_ERR(data)) > + return PTR_ERR(data); > + ret = raw_process_ep_io(dev, &io, data, false); > + if (ret < 0) { > + kfree(data); > + return ret; > + } > + length = min(io.length, (unsigned int)ret); > + ret = copy_to_user((void __user *)(value + sizeof(io)), data, length); > + kfree(data); > + return ret; > +} > + > +static int raw_ioctl_configure(struct raw_dev *dev, unsigned long value) > +{ > + int ret = 0; > + unsigned long flags; > + > + if (value) > + return -EINVAL; > + spin_lock_irqsave(&dev->lock, flags); > + if (dev->state != STATE_DEV_RUNNING) { > + pr_debug("fail, device is not running\n"); > + ret = -EINVAL; > + goto out_unlock; > + } > + if (!dev->gadget) { > + pr_debug("fail, gadget is not bound\n"); > + ret = -EBUSY; > + goto out_unlock; > + } > + usb_gadget_set_state(dev->gadget, USB_STATE_CONFIGURED); > + > +out_unlock: > + spin_unlock_irqrestore(&dev->lock, flags); > + return ret; > +} > + > +static int raw_ioctl_vbus_draw(struct raw_dev *dev, unsigned long value) > +{ > + int ret = 0; > + unsigned long flags; > + > + spin_lock_irqsave(&dev->lock, flags); > + if (dev->state != STATE_DEV_RUNNING) { > + pr_debug("fail, device is not running\n"); > + ret = -EINVAL; > + goto out_unlock; > + } > + if (!dev->gadget) { > + pr_debug("fail, gadget is not bound\n"); > + ret = -EBUSY; > + goto out_unlock; > + } > + usb_gadget_vbus_draw(dev->gadget, 2 * value); > + > +out_unlock: > + spin_unlock_irqrestore(&dev->lock, flags); > + return ret; > +} > + > +static long raw_ioctl(struct file *fd, unsigned int cmd, unsigned long value) > +{ > + struct raw_dev *dev = fd->private_data; > + int ret = 0; > + > + if (!dev) > + return -EBUSY; > + > + switch (cmd) { > + case USB_RAW_IOCTL_INIT: > + ret = raw_ioctl_init(dev, value); > + break; > + case USB_RAW_IOCTL_RUN: > + ret = raw_ioctl_run(dev, value); > + break; > + case USB_RAW_IOCTL_EVENT_FETCH: > + ret = raw_ioctl_event_fetch(dev, value); > + break; > + case USB_RAW_IOCTL_EP0_WRITE: > + ret = raw_ioctl_ep0_write(dev, value); > + break; > + case USB_RAW_IOCTL_EP0_READ: > + ret = raw_ioctl_ep0_read(dev, value); > + break; > + case USB_RAW_IOCTL_EP_ENABLE: > + ret = raw_ioctl_ep_enable(dev, value); > + break; > + case USB_RAW_IOCTL_EP_DISABLE: > + ret = raw_ioctl_ep_disable(dev, value); > + break; > + case USB_RAW_IOCTL_EP_WRITE: > + ret = raw_ioctl_ep_write(dev, value); > + break; > + case USB_RAW_IOCTL_EP_READ: > + ret = raw_ioctl_ep_read(dev, value); > + break; > + case USB_RAW_IOCTL_CONFIGURE: > + ret = raw_ioctl_configure(dev, value); > + break; > + case USB_RAW_IOCTL_VBUS_DRAW: > + ret = raw_ioctl_vbus_draw(dev, value); > + break; > + default: > + ret = -EINVAL; > + } > + > + return ret; > +} > + > +/*----------------------------------------------------------------------*/ > + > +static const struct file_operations raw_fops = { > + .open = raw_open, > + .unlocked_ioctl = raw_ioctl, > + .compat_ioctl = raw_ioctl, > + .release = raw_release, > + .llseek = no_llseek, > +}; > + > +static struct miscdevice raw_device = { > + .minor = MISC_DYNAMIC_MINOR, > + .name = "raw-gadget", DRIVER_NAME? > + .fops = &raw_fops, > +}; > + > +static int __init raw_init(void) > +{ > + int rv; > + > + rv = misc_register(&raw_device); > + if (rv) { > + pr_err("failed to register raw-gadget device with %d\n", rv); > + return rv; > + } > + return 0; > +} > + > +static void __exit raw_exit(void) > +{ > + misc_deregister(&raw_device); > +} > + > +module_init(raw_init); > +module_exit(raw_exit); module_misc_device()? :) Looks good. thanks, greg k-h