diff options
Diffstat (limited to 'drivers/media/usb/uvc')
-rw-r--r-- | drivers/media/usb/uvc/uvc_ctrl.c | 30 | ||||
-rw-r--r-- | drivers/media/usb/uvc/uvc_driver.c | 66 | ||||
-rw-r--r-- | drivers/media/usb/uvc/uvc_entity.c | 2 | ||||
-rw-r--r-- | drivers/media/usb/uvc/uvc_status.c | 40 | ||||
-rw-r--r-- | drivers/media/usb/uvc/uvc_v4l2.c | 20 | ||||
-rw-r--r-- | drivers/media/usb/uvc/uvc_video.c | 23 | ||||
-rw-r--r-- | drivers/media/usb/uvc/uvcvideo.h | 6 |
7 files changed, 131 insertions, 56 deletions
diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c index 36abe47997b0..e0d894c61f4b 100644 --- a/drivers/media/usb/uvc/uvc_ctrl.c +++ b/drivers/media/usb/uvc/uvc_ctrl.c @@ -6,6 +6,7 @@ * Laurent Pinchart (laurent.pinchart@ideasonboard.com) */ +#include <asm/barrier.h> #include <linux/kernel.h> #include <linux/list.h> #include <linux/module.h> @@ -1275,17 +1276,12 @@ static void uvc_ctrl_send_slave_event(struct uvc_video_chain *chain, uvc_ctrl_send_event(chain, handle, ctrl, mapping, val, changes); } -static void uvc_ctrl_status_event_work(struct work_struct *work) +void uvc_ctrl_status_event(struct uvc_video_chain *chain, + struct uvc_control *ctrl, const u8 *data) { - struct uvc_device *dev = container_of(work, struct uvc_device, - async_ctrl.work); - struct uvc_ctrl_work *w = &dev->async_ctrl; - struct uvc_video_chain *chain = w->chain; struct uvc_control_mapping *mapping; - struct uvc_control *ctrl = w->ctrl; struct uvc_fh *handle; unsigned int i; - int ret; mutex_lock(&chain->ctrl_mutex); @@ -1293,7 +1289,7 @@ static void uvc_ctrl_status_event_work(struct work_struct *work) ctrl->handle = NULL; list_for_each_entry(mapping, &ctrl->info.mappings, list) { - s32 value = __uvc_ctrl_get_value(mapping, w->data); + s32 value = __uvc_ctrl_get_value(mapping, data); /* * handle may be NULL here if the device sends auto-update @@ -1312,6 +1308,20 @@ static void uvc_ctrl_status_event_work(struct work_struct *work) } mutex_unlock(&chain->ctrl_mutex); +} + +static void uvc_ctrl_status_event_work(struct work_struct *work) +{ + struct uvc_device *dev = container_of(work, struct uvc_device, + async_ctrl.work); + struct uvc_ctrl_work *w = &dev->async_ctrl; + int ret; + + uvc_ctrl_status_event(w->chain, w->ctrl, w->data); + + /* The barrier is needed to synchronize with uvc_status_stop(). */ + if (smp_load_acquire(&dev->flush_status)) + return; /* Resubmit the URB. */ w->urb->interval = dev->int_ep->desc.bInterval; @@ -1321,8 +1331,8 @@ static void uvc_ctrl_status_event_work(struct work_struct *work) ret); } -bool uvc_ctrl_status_event(struct urb *urb, struct uvc_video_chain *chain, - struct uvc_control *ctrl, const u8 *data) +bool uvc_ctrl_status_event_async(struct urb *urb, struct uvc_video_chain *chain, + struct uvc_control *ctrl, const u8 *data) { struct uvc_device *dev = chain->dev; struct uvc_ctrl_work *w = &dev->async_ctrl; diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c index 40ca1d4e0348..0caa57a6782a 100644 --- a/drivers/media/usb/uvc/uvc_driver.c +++ b/drivers/media/usb/uvc/uvc_driver.c @@ -1060,10 +1060,8 @@ static int uvc_parse_vendor_control(struct uvc_device *dev, + n; memcpy(unit->extension.bmControls, &buffer[23+p], 2*n); - if (buffer[24+p+2*n] != 0) - usb_string(udev, buffer[24+p+2*n], unit->name, - sizeof(unit->name)); - else + if (buffer[24+p+2*n] == 0 || + usb_string(udev, buffer[24+p+2*n], unit->name, sizeof(unit->name)) < 0) sprintf(unit->name, "Extension %u", buffer[3]); list_add_tail(&unit->list, &dev->entities); @@ -1188,15 +1186,15 @@ static int uvc_parse_standard_control(struct uvc_device *dev, memcpy(term->media.bmTransportModes, &buffer[10+n], p); } - if (buffer[7] != 0) - usb_string(udev, buffer[7], term->name, - sizeof(term->name)); - else if (UVC_ENTITY_TYPE(term) == UVC_ITT_CAMERA) - sprintf(term->name, "Camera %u", buffer[3]); - else if (UVC_ENTITY_TYPE(term) == UVC_ITT_MEDIA_TRANSPORT_INPUT) - sprintf(term->name, "Media %u", buffer[3]); - else - sprintf(term->name, "Input %u", buffer[3]); + if (buffer[7] == 0 || + usb_string(udev, buffer[7], term->name, sizeof(term->name)) < 0) { + if (UVC_ENTITY_TYPE(term) == UVC_ITT_CAMERA) + sprintf(term->name, "Camera %u", buffer[3]); + if (UVC_ENTITY_TYPE(term) == UVC_ITT_MEDIA_TRANSPORT_INPUT) + sprintf(term->name, "Media %u", buffer[3]); + else + sprintf(term->name, "Input %u", buffer[3]); + } list_add_tail(&term->list, &dev->entities); break; @@ -1228,10 +1226,8 @@ static int uvc_parse_standard_control(struct uvc_device *dev, memcpy(term->baSourceID, &buffer[7], 1); - if (buffer[8] != 0) - usb_string(udev, buffer[8], term->name, - sizeof(term->name)); - else + if (buffer[8] == 0 || + usb_string(udev, buffer[8], term->name, sizeof(term->name)) < 0) sprintf(term->name, "Output %u", buffer[3]); list_add_tail(&term->list, &dev->entities); @@ -1253,10 +1249,8 @@ static int uvc_parse_standard_control(struct uvc_device *dev, memcpy(unit->baSourceID, &buffer[5], p); - if (buffer[5+p] != 0) - usb_string(udev, buffer[5+p], unit->name, - sizeof(unit->name)); - else + if (buffer[5+p] == 0 || + usb_string(udev, buffer[5+p], unit->name, sizeof(unit->name)) < 0) sprintf(unit->name, "Selector %u", buffer[3]); list_add_tail(&unit->list, &dev->entities); @@ -1286,10 +1280,8 @@ static int uvc_parse_standard_control(struct uvc_device *dev, if (dev->uvc_version >= 0x0110) unit->processing.bmVideoStandards = buffer[9+n]; - if (buffer[8+n] != 0) - usb_string(udev, buffer[8+n], unit->name, - sizeof(unit->name)); - else + if (buffer[8+n] == 0 || + usb_string(udev, buffer[8+n], unit->name, sizeof(unit->name)) < 0) sprintf(unit->name, "Processing %u", buffer[3]); list_add_tail(&unit->list, &dev->entities); @@ -1317,10 +1309,8 @@ static int uvc_parse_standard_control(struct uvc_device *dev, unit->extension.bmControls = (u8 *)unit + sizeof(*unit); memcpy(unit->extension.bmControls, &buffer[23+p], n); - if (buffer[23+p+n] != 0) - usb_string(udev, buffer[23+p+n], unit->name, - sizeof(unit->name)); - else + if (buffer[23+p+n] == 0 || + usb_string(udev, buffer[23+p+n], unit->name, sizeof(unit->name)) < 0) sprintf(unit->name, "Extension %u", buffer[3]); list_add_tail(&unit->list, &dev->entities); @@ -2472,6 +2462,24 @@ static const struct usb_device_id uvc_ids[] = { .bInterfaceSubClass = 1, .bInterfaceProtocol = 0, .driver_info = (kernel_ulong_t)&uvc_quirk_probe_minmax }, + /* Logitech, Webcam C910 */ + { .match_flags = USB_DEVICE_ID_MATCH_DEVICE + | USB_DEVICE_ID_MATCH_INT_INFO, + .idVendor = 0x046d, + .idProduct = 0x0821, + .bInterfaceClass = USB_CLASS_VIDEO, + .bInterfaceSubClass = 1, + .bInterfaceProtocol = 0, + .driver_info = UVC_INFO_QUIRK(UVC_QUIRK_WAKE_AUTOSUSPEND)}, + /* Logitech, Webcam B910 */ + { .match_flags = USB_DEVICE_ID_MATCH_DEVICE + | USB_DEVICE_ID_MATCH_INT_INFO, + .idVendor = 0x046d, + .idProduct = 0x0823, + .bInterfaceClass = USB_CLASS_VIDEO, + .bInterfaceSubClass = 1, + .bInterfaceProtocol = 0, + .driver_info = UVC_INFO_QUIRK(UVC_QUIRK_WAKE_AUTOSUSPEND)}, /* Logitech Quickcam Fusion */ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_INT_INFO, diff --git a/drivers/media/usb/uvc/uvc_entity.c b/drivers/media/usb/uvc/uvc_entity.c index ca3a9c2eec27..7c9895377118 100644 --- a/drivers/media/usb/uvc/uvc_entity.c +++ b/drivers/media/usb/uvc/uvc_entity.c @@ -37,7 +37,7 @@ static int uvc_mc_create_links(struct uvc_video_chain *chain, continue; remote = uvc_entity_by_id(chain->dev, entity->baSourceID[i]); - if (remote == NULL) + if (remote == NULL || remote->num_pads == 0) return -EINVAL; source = (UVC_ENTITY_TYPE(remote) == UVC_TT_STREAMING) diff --git a/drivers/media/usb/uvc/uvc_status.c b/drivers/media/usb/uvc/uvc_status.c index 2bdb0ff203f8..73725051cc16 100644 --- a/drivers/media/usb/uvc/uvc_status.c +++ b/drivers/media/usb/uvc/uvc_status.c @@ -6,6 +6,7 @@ * Laurent Pinchart (laurent.pinchart@ideasonboard.com) */ +#include <asm/barrier.h> #include <linux/kernel.h> #include <linux/input.h> #include <linux/slab.h> @@ -179,7 +180,8 @@ static bool uvc_event_control(struct urb *urb, switch (status->bAttribute) { case UVC_CTRL_VALUE_CHANGE: - return uvc_ctrl_status_event(urb, chain, ctrl, status->bValue); + return uvc_ctrl_status_event_async(urb, chain, ctrl, + status->bValue); case UVC_CTRL_INFO_CHANGE: case UVC_CTRL_FAILURE_CHANGE: @@ -309,5 +311,41 @@ int uvc_status_start(struct uvc_device *dev, gfp_t flags) void uvc_status_stop(struct uvc_device *dev) { + struct uvc_ctrl_work *w = &dev->async_ctrl; + + /* + * 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); } diff --git a/drivers/media/usb/uvc/uvc_v4l2.c b/drivers/media/usb/uvc/uvc_v4l2.c index 3126ee9e965c..96ef64b6a232 100644 --- a/drivers/media/usb/uvc/uvc_v4l2.c +++ b/drivers/media/usb/uvc/uvc_v4l2.c @@ -859,29 +859,31 @@ static int uvc_ioctl_enum_input(struct file *file, void *fh, struct uvc_video_chain *chain = handle->chain; const struct uvc_entity *selector = chain->selector; struct uvc_entity *iterm = NULL; + struct uvc_entity *it; u32 index = input->index; - int pin = 0; if (selector == NULL || (chain->dev->quirks & UVC_QUIRK_IGNORE_SELECTOR_UNIT)) { if (index != 0) return -EINVAL; - list_for_each_entry(iterm, &chain->entities, chain) { - if (UVC_ENTITY_IS_ITERM(iterm)) + list_for_each_entry(it, &chain->entities, chain) { + if (UVC_ENTITY_IS_ITERM(it)) { + iterm = it; break; + } } - pin = iterm->id; } else if (index < selector->bNrInPins) { - pin = selector->baSourceID[index]; - list_for_each_entry(iterm, &chain->entities, chain) { - if (!UVC_ENTITY_IS_ITERM(iterm)) + list_for_each_entry(it, &chain->entities, chain) { + if (!UVC_ENTITY_IS_ITERM(it)) continue; - if (iterm->id == pin) + if (it->id == selector->baSourceID[index]) { + iterm = it; break; + } } } - if (iterm == NULL || iterm->id != pin) + if (iterm == NULL) return -EINVAL; memset(input, 0, sizeof(*input)); diff --git a/drivers/media/usb/uvc/uvc_video.c b/drivers/media/usb/uvc/uvc_video.c index fe58723fc5ac..6dff8e2fadba 100644 --- a/drivers/media/usb/uvc/uvc_video.c +++ b/drivers/media/usb/uvc/uvc_video.c @@ -797,9 +797,9 @@ static void uvc_video_stats_decode(struct uvc_streaming *stream, unsigned int header_size; bool has_pts = false; bool has_scr = false; - u16 uninitialized_var(scr_sof); - u32 uninitialized_var(scr_stc); - u32 uninitialized_var(pts); + u16 scr_sof; + u32 scr_stc; + u32 pts; if (stream->stats.stream.nb_frames == 0 && stream->stats.frame.nb_packets == 0) @@ -1308,7 +1308,9 @@ static void uvc_video_decode_meta(struct uvc_streaming *stream, if (has_scr) memcpy(stream->clock.last_scr, scr, 6); - memcpy(&meta->length, mem, length); + meta->length = mem[0]; + meta->flags = mem[1]; + memcpy(meta->buf, &mem[2], length - 2); meta_buf->bytesused += length + sizeof(meta->ns) + sizeof(meta->sof); uvc_trace(UVC_TRACE_FRAME, @@ -1860,7 +1862,7 @@ static int uvc_video_start_transfer(struct uvc_streaming *stream, struct usb_host_endpoint *best_ep = NULL; unsigned int best_psize = UINT_MAX; unsigned int bandwidth; - unsigned int uninitialized_var(altsetting); + unsigned int altsetting; int intfnum = stream->intfnum; /* Isochronous endpoint, select the alternate setting. */ @@ -1903,6 +1905,17 @@ static int uvc_video_start_transfer(struct uvc_streaming *stream, uvc_trace(UVC_TRACE_VIDEO, "Selecting alternate setting %u " "(%u B/frame bandwidth).\n", altsetting, best_psize); + /* + * Some devices, namely the Logitech C910 and B910, are unable + * to recover from a USB autosuspend, unless the alternate + * setting of the streaming interface is toggled. + */ + if (stream->dev->quirks & UVC_QUIRK_WAKE_AUTOSUSPEND) { + usb_set_interface(stream->dev->udev, intfnum, + altsetting); + usb_set_interface(stream->dev->udev, intfnum, 0); + } + ret = usb_set_interface(stream->dev->udev, intfnum, altsetting); if (ret < 0) return ret; diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h index 5f137400bebd..9f5b9601eadc 100644 --- a/drivers/media/usb/uvc/uvcvideo.h +++ b/drivers/media/usb/uvc/uvcvideo.h @@ -199,6 +199,7 @@ #define UVC_QUIRK_RESTORE_CTRLS_ON_INIT 0x00000400 #define UVC_QUIRK_FORCE_Y8 0x00000800 #define UVC_QUIRK_FORCE_BPP 0x00001000 +#define UVC_QUIRK_WAKE_AUTOSUSPEND 0x00002000 /* Format flags */ #define UVC_FMT_FLAG_COMPRESSED 0x00000001 @@ -663,6 +664,7 @@ struct uvc_device { /* Status Interrupt Endpoint */ struct usb_host_endpoint *int_ep; struct urb *int_urb; + bool flush_status; u8 *status; struct input_dev *input; char input_phys[64]; @@ -832,7 +834,9 @@ int uvc_ctrl_add_mapping(struct uvc_video_chain *chain, int uvc_ctrl_init_device(struct uvc_device *dev); void uvc_ctrl_cleanup_device(struct uvc_device *dev); int uvc_ctrl_restore_values(struct uvc_device *dev); -bool uvc_ctrl_status_event(struct urb *urb, struct uvc_video_chain *chain, +bool uvc_ctrl_status_event_async(struct urb *urb, struct uvc_video_chain *chain, + struct uvc_control *ctrl, const u8 *data); +void uvc_ctrl_status_event(struct uvc_video_chain *chain, struct uvc_control *ctrl, const u8 *data); int uvc_ctrl_begin(struct uvc_video_chain *chain); |