// SPDX-License-Identifier: GPL-2.0 /* * USB Raw Gadget driver. * See Documentation/usb/raw-gadget.rst for more details. * * Copyright (c) 2020 Google, Inc. * Author: Andrey Konovalov <andreyknvl@gmail.com>
*/
struct raw_event_queue { /* See the comment in raw_event_queue_fetch() for locking details. */
spinlock_t lock; struct semaphore sema; struct usb_raw_event *events[RAW_EVENT_QUEUE_SIZE]; int size;
};
/* * This function can be called concurrently. We first check that * there's at least one event queued by decrementing the semaphore, * and then take the lock to protect queue struct fields.
*/
ret = down_interruptible(&queue->sema); if (ret) return ERR_PTR(ret);
spin_lock_irqsave(&queue->lock, flags); /* * queue->size must have the same value as queue->sema counter (before * the down_interruptible() call above), so this check is a fail-safe.
*/ if (WARN_ON(!queue->size)) {
spin_unlock_irqrestore(&queue->lock, flags); return ERR_PTR(-ENODEV);
}
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;
}
staticvoid 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;
}
static u8 get_ep_addr(constchar *name)
{ /* If the endpoint has fixed function (named as e.g. "ep12out-bulk"), * parse the endpoint address from its name. We deliberately use * deprecated simple_strtoul() function here, as the number isn't * followed by '\0' nor '\n'.
*/ if (isdigit(name[2])) return simple_strtoul(&name[2], NULL, 10); /* Otherwise the endpoint is configurable (named as e.g. "ep-a"). */ return USB_RAW_EP_ADDR_ANY;
}
staticint gadget_setup(struct usb_gadget *gadget, conststruct usb_ctrlrequest *ctrl)
{ int ret = 0; struct raw_dev *dev = get_gadget_data(gadget); unsignedlong flags;
spin_lock_irqsave(&dev->lock, flags); if (dev->state != STATE_DEV_RUNNING) {
dev_err(&gadget->dev, "ignoring, device is not running\n");
ret = -ENODEV; goto out_unlock;
} if (dev->ep0_in_pending || dev->ep0_out_pending) {
dev_dbg(&gadget->dev, "stalling, request already pending\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)
dev_err(&gadget->dev, "failed to queue control event\n"); goto out;
out_unlock:
spin_unlock_irqrestore(&dev->lock, flags);
out: if (ret == 0 && ctrl->wLength == 0) { /* * Return USB_GADGET_DELAYED_STATUS as a workaround to stop * some UDC drivers (e.g. dwc3) from automatically proceeding * with the status stage for 0-length transfers. * Should be removed once all UDC drivers are fixed to always * delay the status stage until a response is queued to EP0.
*/ return USB_GADGET_DELAYED_STATUS;
} return ret;
}
if (unregister) {
ret = usb_gadget_unregister_driver(&dev->driver); if (ret != 0)
dev_err(dev->dev, "usb_gadget_unregister_driver() failed with %d\n",
ret); /* Matches kref_get() in raw_ioctl_run(). */
kref_put(&dev->count, dev_free);
}
staticint raw_ioctl_init(struct raw_dev *dev, unsignedlong value)
{ int ret = 0; int driver_id_number; struct usb_raw_init arg; char *udc_driver_name; char *udc_device_name; char *driver_driver_name; unsignedlong flags;
if (copy_from_user(&arg, (void __user *)value, sizeof(arg))) return -EFAULT;
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;
}
driver_id_number = ida_alloc(&driver_id_numbers, GFP_KERNEL); if (driver_id_number < 0) return driver_id_number;
driver_driver_name = kmalloc(DRIVER_DRIVER_NAME_LENGTH_MAX, GFP_KERNEL); if (!driver_driver_name) {
ret = -ENOMEM; goto out_free_driver_id_number;
}
snprintf(driver_driver_name, DRIVER_DRIVER_NAME_LENGTH_MAX,
DRIVER_NAME ".%d", driver_id_number);
udc_driver_name = kmalloc(UDC_NAME_LENGTH_MAX, GFP_KERNEL); if (!udc_driver_name) {
ret = -ENOMEM; goto out_free_driver_driver_name;
}
ret = strscpy(udc_driver_name, &arg.driver_name[0],
UDC_NAME_LENGTH_MAX); if (ret < 0) goto out_free_udc_driver_name;
ret = 0;
udc_device_name = kmalloc(UDC_NAME_LENGTH_MAX, GFP_KERNEL); if (!udc_device_name) {
ret = -ENOMEM; goto out_free_udc_driver_name;
}
ret = strscpy(udc_device_name, &arg.device_name[0],
UDC_NAME_LENGTH_MAX); if (ret < 0) goto out_free_udc_device_name;
ret = 0;
spin_lock_irqsave(&dev->lock, flags); if (dev->state != STATE_DEV_OPENED) {
dev_dbg(dev->dev, "fail, device is not opened\n");
ret = -EINVAL; goto out_unlock;
}
dev->udc_name = udc_driver_name;
if (copy_from_user(io, ptr, sizeof(*io))) return ERR_PTR(-EFAULT); if (io->ep >= USB_RAW_EPS_NUM_MAX) return ERR_PTR(-EINVAL); if (!usb_raw_io_flags_valid(io->flags)) 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;
}
staticint raw_process_ep0_io(struct raw_dev *dev, struct usb_raw_ep_io *io, void *data, bool in)
{ int ret = 0; unsignedlong flags;
spin_lock_irqsave(&dev->lock, flags); if (dev->state != STATE_DEV_RUNNING) {
dev_dbg(dev->dev, "fail, device is not running\n");
ret = -EINVAL; goto out_unlock;
} if (!dev->gadget) {
dev_dbg(dev->dev, "fail, gadget is not bound\n");
ret = -EBUSY; goto out_unlock;
} if (dev->ep0_urb_queued) {
dev_dbg(&dev->gadget->dev, "fail, urb already queued\n");
ret = -EBUSY; goto out_unlock;
} if ((in && !dev->ep0_in_pending) ||
(!in && !dev->ep0_out_pending)) {
dev_dbg(&dev->gadget->dev, "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_unlock;
} if (WARN_ON(!in && dev->ep0_in_pending)) {
ret = -ENODEV;
dev->state = STATE_DEV_FAILED; goto out_unlock;
}
staticint raw_ioctl_ep0_write(struct raw_dev *dev, unsignedlong 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;
}
staticint raw_ioctl_ep0_read(struct raw_dev *dev, unsignedlong value)
{ int ret = 0; void *data; struct usb_raw_ep_io io; unsignedint 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) goto free;
length = min_t(unsignedint, io.length, ret); if (copy_to_user((void __user *)(value + sizeof(io)), data, length))
ret = -EFAULT; else
ret = length;
free:
kfree(data); return ret;
}
staticint raw_ioctl_ep0_stall(struct raw_dev *dev, unsignedlong value)
{ int ret = 0; unsignedlong flags;
if (value) return -EINVAL;
spin_lock_irqsave(&dev->lock, flags); if (dev->state != STATE_DEV_RUNNING) {
dev_dbg(dev->dev, "fail, device is not running\n");
ret = -EINVAL; goto out_unlock;
} if (!dev->gadget) {
dev_dbg(dev->dev, "fail, gadget is not bound\n");
ret = -EBUSY; goto out_unlock;
} if (dev->ep0_urb_queued) {
dev_dbg(&dev->gadget->dev, "fail, urb already queued\n");
ret = -EBUSY; goto out_unlock;
} if (!dev->ep0_in_pending && !dev->ep0_out_pending) {
dev_dbg(&dev->gadget->dev, "fail, no request pending\n");
ret = -EBUSY; goto out_unlock;
}
ret = usb_ep_set_halt(dev->gadget->ep0); if (ret < 0)
dev_err(&dev->gadget->dev, "fail, usb_ep_set_halt returned %d\n", ret);
if (dev->ep0_in_pending)
dev->ep0_in_pending = false; else
dev->ep0_out_pending = false;
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) {
dev_dbg(dev->dev, "fail, bad endpoint maxpacket\n");
kfree(desc); return -EINVAL;
}
spin_lock_irqsave(&dev->lock, flags); if (dev->state != STATE_DEV_RUNNING) {
dev_dbg(dev->dev, "fail, device is not running\n");
ret = -EINVAL; goto out_free;
} if (!dev->gadget) {
dev_dbg(dev->dev, "fail, gadget is not bound\n");
ret = -EBUSY; goto out_free;
}
for (i = 0; i < dev->eps_num; i++) {
ep = &dev->eps[i]; if (ep->addr != usb_endpoint_num(desc) &&
ep->addr != USB_RAW_EP_ADDR_ANY) continue; if (!usb_gadget_ep_match_desc(dev->gadget, ep->ep, desc, NULL)) continue;
ep_props_matched = true; if (ep->state != STATE_EP_DISABLED) continue;
ep->ep->desc = desc;
ret = usb_ep_enable(ep->ep); if (ret < 0) {
dev_err(&dev->gadget->dev, "fail, usb_ep_enable returned %d\n", ret); goto out_free;
}
ep->req = usb_ep_alloc_request(ep->ep, GFP_ATOMIC); if (!ep->req) {
dev_err(&dev->gadget->dev, "fail, usb_ep_alloc_request failed\n");
usb_ep_disable(ep->ep);
ret = -ENOMEM; goto out_free;
}
ep->state = STATE_EP_ENABLED;
ep->ep->driver_data = ep;
ret = i; goto out_unlock;
}
if (!ep_props_matched) {
dev_dbg(&dev->gadget->dev, "fail, bad endpoint descriptor\n");
ret = -EINVAL;
} else {
dev_dbg(&dev->gadget->dev, "fail, no endpoints available\n");
ret = -EBUSY;
}
staticint raw_ioctl_ep_disable(struct raw_dev *dev, unsignedlong value)
{ int ret = 0, i = value; unsignedlong flags;
spin_lock_irqsave(&dev->lock, flags); if (dev->state != STATE_DEV_RUNNING) {
dev_dbg(dev->dev, "fail, device is not running\n");
ret = -EINVAL; goto out_unlock;
} if (!dev->gadget) {
dev_dbg(dev->dev, "fail, gadget is not bound\n");
ret = -EBUSY; goto out_unlock;
} if (i < 0 || i >= dev->eps_num) {
dev_dbg(dev->dev, "fail, invalid endpoint\n");
ret = -EBUSY; goto out_unlock;
} if (dev->eps[i].state == STATE_EP_DISABLED) {
dev_dbg(&dev->gadget->dev, "fail, endpoint is not enabled\n");
ret = -EINVAL; goto out_unlock;
} if (dev->eps[i].disabling) {
dev_dbg(&dev->gadget->dev, "fail, disable already in progress\n");
ret = -EINVAL; goto out_unlock;
} if (dev->eps[i].urb_queued) {
dev_dbg(&dev->gadget->dev, "fail, waiting for urb completion\n");
ret = -EINVAL; goto out_unlock;
}
dev->eps[i].disabling = true;
spin_unlock_irqrestore(&dev->lock, flags);
staticint raw_ioctl_ep_set_clear_halt_wedge(struct raw_dev *dev, unsignedlong value, bool set, bool halt)
{ int ret = 0, i = value; unsignedlong flags;
spin_lock_irqsave(&dev->lock, flags); if (dev->state != STATE_DEV_RUNNING) {
dev_dbg(dev->dev, "fail, device is not running\n");
ret = -EINVAL; goto out_unlock;
} if (!dev->gadget) {
dev_dbg(dev->dev, "fail, gadget is not bound\n");
ret = -EBUSY; goto out_unlock;
} if (i < 0 || i >= dev->eps_num) {
dev_dbg(dev->dev, "fail, invalid endpoint\n");
ret = -EBUSY; goto out_unlock;
} if (dev->eps[i].state == STATE_EP_DISABLED) {
dev_dbg(&dev->gadget->dev, "fail, endpoint is not enabled\n");
ret = -EINVAL; goto out_unlock;
} if (dev->eps[i].disabling) {
dev_dbg(&dev->gadget->dev, "fail, disable is in progress\n");
ret = -EINVAL; goto out_unlock;
} if (dev->eps[i].urb_queued) {
dev_dbg(&dev->gadget->dev, "fail, waiting for urb completion\n");
ret = -EINVAL; goto out_unlock;
} if (usb_endpoint_xfer_isoc(dev->eps[i].ep->desc)) {
dev_dbg(&dev->gadget->dev, "fail, can't halt/wedge ISO endpoint\n");
ret = -EINVAL; goto out_unlock;
}
if (set && halt) {
ret = usb_ep_set_halt(dev->eps[i].ep); if (ret < 0)
dev_err(&dev->gadget->dev, "fail, usb_ep_set_halt returned %d\n", ret);
} elseif (!set && halt) {
ret = usb_ep_clear_halt(dev->eps[i].ep); if (ret < 0)
dev_err(&dev->gadget->dev, "fail, usb_ep_clear_halt returned %d\n", ret);
} elseif (set && !halt) {
ret = usb_ep_set_wedge(dev->eps[i].ep); if (ret < 0)
dev_err(&dev->gadget->dev, "fail, usb_ep_set_wedge returned %d\n", ret);
}
spin_lock_irqsave(&dev->lock, flags); if (dev->state != STATE_DEV_RUNNING) {
dev_dbg(dev->dev, "fail, device is not running\n");
ret = -EINVAL; goto out_unlock;
} if (!dev->gadget) {
dev_dbg(dev->dev, "fail, gadget is not bound\n");
ret = -EBUSY; goto out_unlock;
} if (io->ep >= dev->eps_num) {
dev_dbg(&dev->gadget->dev, "fail, invalid endpoint\n");
ret = -EINVAL; goto out_unlock;
}
ep = &dev->eps[io->ep]; if (ep->state != STATE_EP_ENABLED) {
dev_dbg(&dev->gadget->dev, "fail, endpoint is not enabled\n");
ret = -EBUSY; goto out_unlock;
} if (ep->disabling) {
dev_dbg(&dev->gadget->dev, "fail, endpoint is already being disabled\n");
ret = -EBUSY; goto out_unlock;
} if (ep->urb_queued) {
dev_dbg(&dev->gadget->dev, "fail, urb already queued\n");
ret = -EBUSY; goto out_unlock;
} if (in != usb_endpoint_dir_in(ep->ep->desc)) {
dev_dbg(&dev->gadget->dev, "fail, wrong direction\n");
ret = -EINVAL; goto out_unlock;
}
staticint raw_ioctl_ep_write(struct raw_dev *dev, unsignedlong 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;
}
staticint raw_ioctl_ep_read(struct raw_dev *dev, unsignedlong value)
{ int ret = 0; char *data; struct usb_raw_ep_io io; unsignedint 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) goto free;
length = min_t(unsignedint, io.length, ret); if (copy_to_user((void __user *)(value + sizeof(io)), data, length))
ret = -EFAULT; else
ret = length;
free:
kfree(data); return ret;
}
staticint raw_ioctl_configure(struct raw_dev *dev, unsignedlong value)
{ int ret = 0; unsignedlong flags;
if (value) return -EINVAL;
spin_lock_irqsave(&dev->lock, flags); if (dev->state != STATE_DEV_RUNNING) {
dev_dbg(dev->dev, "fail, device is not running\n");
ret = -EINVAL; goto out_unlock;
} if (!dev->gadget) {
dev_dbg(dev->dev, "fail, gadget is not bound\n");
ret = -EBUSY; goto out_unlock;
}
usb_gadget_set_state(dev->gadget, USB_STATE_CONFIGURED);
staticint raw_ioctl_vbus_draw(struct raw_dev *dev, unsignedlong value)
{ int ret = 0; unsignedlong flags;
spin_lock_irqsave(&dev->lock, flags); if (dev->state != STATE_DEV_RUNNING) {
dev_dbg(dev->dev, "fail, device is not running\n");
ret = -EINVAL; goto out_unlock;
} if (!dev->gadget) {
dev_dbg(dev->dev, "fail, gadget is not bound\n");
ret = -EBUSY; goto out_unlock;
}
usb_gadget_vbus_draw(dev->gadget, 2 * value);
staticint raw_ioctl_eps_info(struct raw_dev *dev, unsignedlong value)
{ int ret = 0, i; unsignedlong flags; struct usb_raw_eps_info *info; struct raw_ep *ep;
info = kzalloc(sizeof(*info), GFP_KERNEL); if (!info) {
ret = -ENOMEM; goto out;
}
spin_lock_irqsave(&dev->lock, flags); if (dev->state != STATE_DEV_RUNNING) {
dev_dbg(dev->dev, "fail, device is not running\n");
ret = -EINVAL;
spin_unlock_irqrestore(&dev->lock, flags); goto out_free;
} if (!dev->gadget) {
dev_dbg(dev->dev, "fail, gadget is not bound\n");
ret = -EBUSY;
spin_unlock_irqrestore(&dev->lock, flags); goto out_free;
}
for (i = 0; i < dev->eps_num; i++) {
ep = &dev->eps[i];
strscpy(&info->eps[i].name[0], ep->ep->name,
USB_RAW_EP_NAME_MAX);
info->eps[i].addr = ep->addr;
fill_ep_caps(&ep->ep->caps, &info->eps[i].caps);
fill_ep_limits(ep->ep, &info->eps[i].limits);
}
ret = dev->eps_num;
spin_unlock_irqrestore(&dev->lock, flags);
if (copy_to_user((void __user *)value, info, sizeof(*info)))
ret = -EFAULT;
out_free:
kfree(info);
out: return ret;
}
staticlong raw_ioctl(struct file *fd, unsignedint cmd, unsignedlong 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; case USB_RAW_IOCTL_EPS_INFO:
ret = raw_ioctl_eps_info(dev, value); break; case USB_RAW_IOCTL_EP0_STALL:
ret = raw_ioctl_ep0_stall(dev, value); break; case USB_RAW_IOCTL_EP_SET_HALT:
ret = raw_ioctl_ep_set_clear_halt_wedge(
dev, value, true, true); break; case USB_RAW_IOCTL_EP_CLEAR_HALT:
ret = raw_ioctl_ep_set_clear_halt_wedge(
dev, value, false, true); break; case USB_RAW_IOCTL_EP_SET_WEDGE:
ret = raw_ioctl_ep_set_clear_halt_wedge(
dev, value, true, false); break; default:
ret = -EINVAL;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.