diff options
Diffstat (limited to 'drivers/media/usb/uvc/uvc_status.c')
| -rw-r--r-- | drivers/media/usb/uvc/uvc_status.c | 300 |
1 files changed, 246 insertions, 54 deletions
diff --git a/drivers/media/usb/uvc/uvc_status.c b/drivers/media/usb/uvc/uvc_status.c index f552ab997956..231cfee8e7c2 100644 --- a/drivers/media/usb/uvc/uvc_status.c +++ b/drivers/media/usb/uvc/uvc_status.c @@ -1,16 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * uvc_status.c -- USB Video Class driver - Status endpoint * * Copyright (C) 2005-2009 * Laurent Pinchart (laurent.pinchart@ideasonboard.com) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * */ +#include <asm/barrier.h> #include <linux/kernel.h> #include <linux/input.h> #include <linux/slab.h> @@ -23,11 +19,34 @@ * Input device */ #ifdef CONFIG_USB_VIDEO_CLASS_INPUT_EVDEV + +static bool uvc_input_has_button(struct uvc_device *dev) +{ + struct uvc_streaming *stream; + + /* + * The device has button events if both bTriggerSupport and + * bTriggerUsage are one. Otherwise the camera button does not + * exist or is handled automatically by the camera without host + * driver or client application intervention. + */ + list_for_each_entry(stream, &dev->streams, list) { + if (stream->header.bTriggerSupport == 1 && + stream->header.bTriggerUsage == 1) + return true; + } + + return false; +} + static int uvc_input_init(struct uvc_device *dev) { struct input_dev *input; int ret; + if (!uvc_input_has_button(dev)) + return 0; + input = input_allocate_device(); if (input == NULL) return -ENOMEM; @@ -43,7 +62,8 @@ static int uvc_input_init(struct uvc_device *dev) __set_bit(EV_KEY, input->evbit); __set_bit(KEY_CAMERA, input->keybit); - if ((ret = input_register_device(input)) < 0) + ret = input_register_device(input); + if (ret < 0) goto error; dev->input = input; @@ -54,7 +74,7 @@ error: return ret; } -static void uvc_input_cleanup(struct uvc_device *dev) +static void uvc_input_unregister(struct uvc_device *dev) { if (dev->input) input_unregister_device(dev->input); @@ -71,45 +91,113 @@ static void uvc_input_report_key(struct uvc_device *dev, unsigned int code, #else #define uvc_input_init(dev) -#define uvc_input_cleanup(dev) +#define uvc_input_unregister(dev) #define uvc_input_report_key(dev, code, value) #endif /* CONFIG_USB_VIDEO_CLASS_INPUT_EVDEV */ /* -------------------------------------------------------------------------- * Status interrupt endpoint */ -static void uvc_event_streaming(struct uvc_device *dev, __u8 *data, int len) +static void uvc_event_streaming(struct uvc_device *dev, + struct uvc_status *status, int len) { - if (len < 3) { - uvc_trace(UVC_TRACE_STATUS, "Invalid streaming status event " - "received.\n"); + if (len <= offsetof(struct uvc_status, bEvent)) { + uvc_dbg(dev, STATUS, + "Invalid streaming status event received\n"); return; } - if (data[2] == 0) { - if (len < 4) + if (status->bEvent == 0) { + if (len <= offsetof(struct uvc_status, streaming)) return; - uvc_trace(UVC_TRACE_STATUS, "Button (intf %u) %s len %d\n", - data[1], data[3] ? "pressed" : "released", len); - uvc_input_report_key(dev, KEY_CAMERA, data[3]); + + uvc_dbg(dev, STATUS, "Button (intf %u) %s len %d\n", + status->bOriginator, + status->streaming.button ? "pressed" : "released", len); + uvc_input_report_key(dev, KEY_CAMERA, status->streaming.button); } else { - uvc_trace(UVC_TRACE_STATUS, "Stream %u error event %02x %02x " - "len %d.\n", data[1], data[2], data[3], len); + uvc_dbg(dev, STATUS, "Stream %u error event %02x len %d\n", + status->bOriginator, status->bEvent, len); } } -static void uvc_event_control(struct uvc_device *dev, __u8 *data, int len) +#define UVC_CTRL_VALUE_CHANGE 0 +#define UVC_CTRL_INFO_CHANGE 1 +#define UVC_CTRL_FAILURE_CHANGE 2 +#define UVC_CTRL_MIN_CHANGE 3 +#define UVC_CTRL_MAX_CHANGE 4 + +static struct uvc_control *uvc_event_entity_find_ctrl(struct uvc_entity *entity, + u8 selector) { - char *attrs[3] = { "value", "info", "failure" }; + struct uvc_control *ctrl; + unsigned int i; - if (len < 6 || data[2] != 0 || data[4] > 2) { - uvc_trace(UVC_TRACE_STATUS, "Invalid control status event " - "received.\n"); - return; + for (i = 0, ctrl = entity->controls; i < entity->ncontrols; i++, ctrl++) + if (ctrl->info.selector == selector) + return ctrl; + + return NULL; +} + +static struct uvc_control *uvc_event_find_ctrl(struct uvc_device *dev, + const struct uvc_status *status, + struct uvc_video_chain **chain) +{ + list_for_each_entry((*chain), &dev->chains, list) { + struct uvc_entity *entity; + struct uvc_control *ctrl; + + list_for_each_entry(entity, &(*chain)->entities, chain) { + if (entity->id != status->bOriginator) + continue; + + ctrl = uvc_event_entity_find_ctrl(entity, + status->control.bSelector); + if (ctrl) + return ctrl; + } + } + + return NULL; +} + +static bool uvc_event_control(struct urb *urb, + const struct uvc_status *status, int len) +{ + static const char *attrs[] = { "value", "info", "failure", "min", "max" }; + struct uvc_device *dev = urb->context; + struct uvc_video_chain *chain; + struct uvc_control *ctrl; + + if (len < 6 || status->bEvent != 0 || + status->control.bAttribute >= ARRAY_SIZE(attrs)) { + uvc_dbg(dev, STATUS, "Invalid control status event received\n"); + return false; } - uvc_trace(UVC_TRACE_STATUS, "Control %u/%u %s change len %d.\n", - data[1], data[3], attrs[data[4]], len); + uvc_dbg(dev, STATUS, "Control %u/%u %s change len %d\n", + status->bOriginator, status->control.bSelector, + attrs[status->control.bAttribute], len); + + /* Find the control. */ + ctrl = uvc_event_find_ctrl(dev, status, &chain); + if (!ctrl) + return false; + + switch (status->control.bAttribute) { + case UVC_CTRL_VALUE_CHANGE: + return uvc_ctrl_status_event_async(urb, chain, ctrl, + status->control.bValue); + + case UVC_CTRL_INFO_CHANGE: + case UVC_CTRL_FAILURE_CHANGE: + case UVC_CTRL_MIN_CHANGE: + case UVC_CTRL_MAX_CHANGE: + break; + } + + return false; } static void uvc_status_complete(struct urb *urb) @@ -124,40 +212,44 @@ static void uvc_status_complete(struct urb *urb) case -ENOENT: /* usb_kill_urb() called. */ case -ECONNRESET: /* usb_unlink_urb() called. */ case -ESHUTDOWN: /* The endpoint is being disabled. */ - case -EPROTO: /* Device is disconnected (reported by some - * host controller). */ + case -EPROTO: /* Device is disconnected (reported by some host controllers). */ return; default: - uvc_printk(KERN_WARNING, "Non-zero status (%d) in status " - "completion handler.\n", urb->status); + dev_warn(&dev->intf->dev, + "Non-zero status (%d) in status completion handler.\n", + urb->status); return; } len = urb->actual_length; if (len > 0) { - switch (dev->status[0] & 0x0f) { - case UVC_STATUS_TYPE_CONTROL: - uvc_event_control(dev, dev->status, len); + switch (dev->status->bStatusType & 0x0f) { + case UVC_STATUS_TYPE_CONTROL: { + if (uvc_event_control(urb, dev->status, len)) + /* The URB will be resubmitted in work context. */ + return; break; + } - case UVC_STATUS_TYPE_STREAMING: + case UVC_STATUS_TYPE_STREAMING: { uvc_event_streaming(dev, dev->status, len); break; + } default: - uvc_trace(UVC_TRACE_STATUS, "Unknown status event " - "type %u.\n", dev->status[0]); + uvc_dbg(dev, STATUS, "Unknown status event type %u\n", + dev->status->bStatusType); break; } } /* Resubmit the URB. */ urb->interval = dev->int_ep->desc.bInterval; - if ((ret = usb_submit_urb(urb, GFP_ATOMIC)) < 0) { - uvc_printk(KERN_ERR, "Failed to resubmit status URB (%d).\n", - ret); - } + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret < 0) + dev_err(&dev->intf->dev, + "Failed to resubmit status URB (%d).\n", ret); } int uvc_status_init(struct uvc_device *dev) @@ -166,24 +258,26 @@ int uvc_status_init(struct uvc_device *dev) unsigned int pipe; int interval; + mutex_init(&dev->status_lock); + if (ep == NULL) return 0; - uvc_input_init(dev); - - dev->status = kzalloc(UVC_MAX_STATUS_SIZE, GFP_KERNEL); - if (dev->status == NULL) + dev->status = kzalloc(sizeof(*dev->status), GFP_KERNEL); + if (!dev->status) return -ENOMEM; dev->int_urb = usb_alloc_urb(0, GFP_KERNEL); - if (dev->int_urb == NULL) { + if (!dev->int_urb) { kfree(dev->status); + dev->status = NULL; return -ENOMEM; } pipe = usb_rcvintpipe(dev->udev, ep->desc.bEndpointAddress); - /* For high-speed interrupt endpoints, the bInterval value is used as + /* + * For high-speed interrupt endpoints, the bInterval value is used as * an exponent of two. Some developers forgot about it. */ interval = ep->desc.bInterval; @@ -192,29 +286,127 @@ int uvc_status_init(struct uvc_device *dev) interval = fls(interval) - 1; usb_fill_int_urb(dev->int_urb, dev->udev, pipe, - dev->status, UVC_MAX_STATUS_SIZE, uvc_status_complete, + dev->status, sizeof(*dev->status), uvc_status_complete, dev, interval); + uvc_input_init(dev); + return 0; } +void uvc_status_unregister(struct uvc_device *dev) +{ + if (!dev->status) + return; + + uvc_status_suspend(dev); + uvc_input_unregister(dev); +} + void uvc_status_cleanup(struct uvc_device *dev) { - usb_kill_urb(dev->int_urb); usb_free_urb(dev->int_urb); kfree(dev->status); - uvc_input_cleanup(dev); } -int uvc_status_start(struct uvc_device *dev, gfp_t flags) +static int uvc_status_start(struct uvc_device *dev, gfp_t flags) { - if (dev->int_urb == NULL) + lockdep_assert_held(&dev->status_lock); + + if (!dev->int_urb) return 0; return usb_submit_urb(dev->int_urb, flags); } -void uvc_status_stop(struct uvc_device *dev) +static void uvc_status_stop(struct uvc_device *dev) { + struct uvc_ctrl_work *w = &dev->async_ctrl; + + lockdep_assert_held(&dev->status_lock); + + if (!dev->int_urb) + return; + + /* + * Prevent the asynchronous control handler from requeing the URB. The + * barrier is needed so the flush_status change is visible to other + * CPUs running the asynchronous handler before usb_kill_urb() is + * called below. + */ + smp_store_release(&dev->flush_status, true); + + /* + * Cancel any pending asynchronous work. If any status event was queued, + * process it synchronously. + */ + if (cancel_work_sync(&w->work)) + uvc_ctrl_status_event(w->chain, w->ctrl, w->data); + + /* Kill the urb. */ usb_kill_urb(dev->int_urb); + + /* + * The URB completion handler may have queued asynchronous work. This + * won't resubmit the URB as flush_status is set, but it needs to be + * cancelled before returning or it could then race with a future + * uvc_status_start() call. + */ + if (cancel_work_sync(&w->work)) + uvc_ctrl_status_event(w->chain, w->ctrl, w->data); + + /* + * From this point, there are no events on the queue and the status URB + * is dead. No events will be queued until uvc_status_start() is called. + * The barrier is needed to make sure that flush_status is visible to + * uvc_ctrl_status_event_work() when uvc_status_start() will be called + * again. + */ + smp_store_release(&dev->flush_status, false); +} + +int uvc_status_resume(struct uvc_device *dev) +{ + guard(mutex)(&dev->status_lock); + + if (dev->status_users) + return uvc_status_start(dev, GFP_NOIO); + + return 0; +} + +void uvc_status_suspend(struct uvc_device *dev) +{ + guard(mutex)(&dev->status_lock); + + if (dev->status_users) + uvc_status_stop(dev); +} + +int uvc_status_get(struct uvc_device *dev) +{ + int ret; + + guard(mutex)(&dev->status_lock); + + if (!dev->status_users) { + ret = uvc_status_start(dev, GFP_KERNEL); + if (ret) + return ret; + } + + dev->status_users++; + + return 0; +} + +void uvc_status_put(struct uvc_device *dev) +{ + guard(mutex)(&dev->status_lock); + + if (dev->status_users == 1) + uvc_status_stop(dev); + WARN_ON(!dev->status_users); + if (dev->status_users) + dev->status_users--; } |
