diff options
Diffstat (limited to 'drivers/media/usb/uvc')
-rw-r--r-- | drivers/media/usb/uvc/uvc_ctrl.c | 91 | ||||
-rw-r--r-- | drivers/media/usb/uvc/uvc_driver.c | 38 | ||||
-rw-r--r-- | drivers/media/usb/uvc/uvc_v4l2.c | 115 | ||||
-rw-r--r-- | drivers/media/usb/uvc/uvcvideo.h | 5 |
4 files changed, 189 insertions, 60 deletions
diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c index cbf19aa1d823..44b6513c5264 100644 --- a/drivers/media/usb/uvc/uvc_ctrl.c +++ b/drivers/media/usb/uvc/uvc_ctrl.c @@ -1812,38 +1812,49 @@ 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_set_handle(struct uvc_fh *handle, struct uvc_control *ctrl, - struct uvc_fh *new_handle) +static int uvc_ctrl_set_handle(struct uvc_fh *handle, struct uvc_control *ctrl, + struct uvc_fh *new_handle) { lockdep_assert_held(&handle->chain->ctrl_mutex); if (new_handle) { + int ret; + if (ctrl->handle) dev_warn_ratelimited(&handle->stream->dev->udev->dev, "UVC non compliance: Setting an async control with a pending operation."); if (new_handle == ctrl->handle) - return; + return 0; if (ctrl->handle) { WARN_ON(!ctrl->handle->pending_async_ctrls); if (ctrl->handle->pending_async_ctrls) ctrl->handle->pending_async_ctrls--; + ctrl->handle = new_handle; + handle->pending_async_ctrls++; + return 0; } + ret = uvc_pm_get(handle->chain->dev); + if (ret) + return ret; + ctrl->handle = new_handle; handle->pending_async_ctrls++; - return; + return 0; } /* Cannot clear the handle for a control not owned by us.*/ if (WARN_ON(ctrl->handle != handle)) - return; + return -EINVAL; ctrl->handle = NULL; if (WARN_ON(!handle->pending_async_ctrls)) - return; + return -EINVAL; handle->pending_async_ctrls--; + uvc_pm_put(handle->chain->dev); + return 0; } void uvc_ctrl_status_event(struct uvc_video_chain *chain, @@ -1943,7 +1954,9 @@ static bool uvc_ctrl_xctrls_has_control(const struct v4l2_ext_control *xctrls, } static void uvc_ctrl_send_events(struct uvc_fh *handle, - const struct v4l2_ext_control *xctrls, unsigned int xctrls_count) + struct uvc_entity *entity, + const struct v4l2_ext_control *xctrls, + unsigned int xctrls_count) { struct uvc_control_mapping *mapping; struct uvc_control *ctrl; @@ -1955,6 +1968,9 @@ static void uvc_ctrl_send_events(struct uvc_fh *handle, s32 value; ctrl = uvc_find_control(handle->chain, xctrls[i].id, &mapping); + if (ctrl->entity != entity) + continue; + if (ctrl->info.flags & UVC_CTRL_FLAG_ASYNCHRONOUS) /* Notification will be sent from an Interrupt event. */ continue; @@ -2090,15 +2106,20 @@ int uvc_ctrl_begin(struct uvc_video_chain *chain) return mutex_lock_interruptible(&chain->ctrl_mutex) ? -ERESTARTSYS : 0; } +/* + * Returns the number of uvc controls that have been correctly set, or a + * negative number if there has been an error. + */ static int uvc_ctrl_commit_entity(struct uvc_device *dev, struct uvc_fh *handle, struct uvc_entity *entity, int rollback, struct uvc_control **err_ctrl) { + unsigned int processed_ctrls = 0; struct uvc_control *ctrl; unsigned int i; - int ret; + int ret = 0; if (entity == NULL) return 0; @@ -2127,8 +2148,9 @@ static int uvc_ctrl_commit_entity(struct uvc_device *dev, dev->intfnum, ctrl->info.selector, uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT), ctrl->info.size); - else - ret = 0; + + if (!ret) + processed_ctrls++; if (rollback || ret < 0) memcpy(uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT), @@ -2137,18 +2159,25 @@ static int uvc_ctrl_commit_entity(struct uvc_device *dev, ctrl->dirty = 0; - if (ret < 0) { + if (!rollback && handle && !ret && + ctrl->info.flags & UVC_CTRL_FLAG_ASYNCHRONOUS) + ret = uvc_ctrl_set_handle(handle, ctrl, handle); + + if (ret < 0 && !rollback) { if (err_ctrl) *err_ctrl = ctrl; - return ret; + /* + * If we fail to set a control, we need to rollback + * the next ones. + */ + rollback = 1; } - - if (!rollback && handle && - ctrl->info.flags & UVC_CTRL_FLAG_ASYNCHRONOUS) - uvc_ctrl_set_handle(handle, ctrl, handle); } - return 0; + if (ret) + return ret; + + return processed_ctrls; } static int uvc_ctrl_find_ctrl_idx(struct uvc_entity *entity, @@ -2178,7 +2207,8 @@ int __uvc_ctrl_commit(struct uvc_fh *handle, int rollback, struct uvc_video_chain *chain = handle->chain; struct uvc_control *err_ctrl; struct uvc_entity *entity; - int ret = 0; + int ret_out = 0; + int ret; /* Find the control. */ list_for_each_entry(entity, &chain->entities, chain) { @@ -2189,15 +2219,23 @@ int __uvc_ctrl_commit(struct uvc_fh *handle, int rollback, ctrls->error_idx = uvc_ctrl_find_ctrl_idx(entity, ctrls, err_ctrl); - goto done; + /* + * When we fail to commit an entity, we need to + * restore the UVC_CTRL_DATA_BACKUP for all the + * controls in the other entities, otherwise our cache + * and the hardware will be out of sync. + */ + rollback = 1; + + ret_out = ret; + } else if (ret > 0 && !rollback) { + uvc_ctrl_send_events(handle, entity, + ctrls->controls, ctrls->count); } } - if (!rollback) - uvc_ctrl_send_events(handle, ctrls->controls, ctrls->count); -done: mutex_unlock(&chain->ctrl_mutex); - return ret; + return ret_out; } static int uvc_mapping_get_xctrl_compound(struct uvc_video_chain *chain, @@ -3222,6 +3260,7 @@ int uvc_ctrl_init_device(struct uvc_device *dev) void uvc_ctrl_cleanup_fh(struct uvc_fh *handle) { struct uvc_entity *entity; + int i; guard(mutex)(&handle->chain->ctrl_mutex); @@ -3236,7 +3275,11 @@ void uvc_ctrl_cleanup_fh(struct uvc_fh *handle) } } - WARN_ON(handle->pending_async_ctrls); + if (!WARN_ON(handle->pending_async_ctrls)) + return; + + for (i = 0; i < handle->pending_async_ctrls; i++) + uvc_pm_put(handle->stream->dev); } /* diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c index 107e0fafd80f..da24a655ab68 100644 --- a/drivers/media/usb/uvc/uvc_driver.c +++ b/drivers/media/usb/uvc/uvc_driver.c @@ -1299,8 +1299,13 @@ static int uvc_gpio_parse(struct uvc_device *dev) gpio_privacy = devm_gpiod_get_optional(&dev->intf->dev, "privacy", GPIOD_IN); - if (IS_ERR_OR_NULL(gpio_privacy)) - return PTR_ERR_OR_ZERO(gpio_privacy); + if (!gpio_privacy) + return 0; + + if (IS_ERR(gpio_privacy)) + return dev_err_probe(&dev->intf->dev, + PTR_ERR(gpio_privacy), + "Can't get privacy GPIO\n"); irq = gpiod_to_irq(gpio_privacy); if (irq < 0) @@ -2232,16 +2237,17 @@ static int uvc_probe(struct usb_interface *intf, #endif /* Parse the Video Class control descriptor. */ - if (uvc_parse_control(dev) < 0) { + ret = uvc_parse_control(dev); + if (ret < 0) { + ret = -ENODEV; uvc_dbg(dev, PROBE, "Unable to parse UVC descriptors\n"); goto error; } /* Parse the associated GPIOs. */ - if (uvc_gpio_parse(dev) < 0) { - uvc_dbg(dev, PROBE, "Unable to parse UVC GPIOs\n"); + ret = uvc_gpio_parse(dev); + if (ret < 0) goto error; - } dev_info(&dev->udev->dev, "Found UVC %u.%02x device %s (%04x:%04x)\n", dev->uvc_version >> 8, dev->uvc_version & 0xff, @@ -2264,24 +2270,32 @@ static int uvc_probe(struct usb_interface *intf, } /* Register the V4L2 device. */ - if (v4l2_device_register(&intf->dev, &dev->vdev) < 0) + ret = v4l2_device_register(&intf->dev, &dev->vdev); + if (ret < 0) goto error; /* Scan the device for video chains. */ - if (uvc_scan_device(dev) < 0) + if (uvc_scan_device(dev) < 0) { + ret = -ENODEV; goto error; + } /* Initialize controls. */ - if (uvc_ctrl_init_device(dev) < 0) + if (uvc_ctrl_init_device(dev) < 0) { + ret = -ENODEV; goto error; + } /* Register video device nodes. */ - if (uvc_register_chains(dev) < 0) + if (uvc_register_chains(dev) < 0) { + ret = -ENODEV; goto error; + } #ifdef CONFIG_MEDIA_CONTROLLER /* Register the media device node */ - if (media_device_register(&dev->mdev) < 0) + ret = media_device_register(&dev->mdev); + if (ret < 0) goto error; #endif /* Save our data pointer in the interface data. */ @@ -2315,7 +2329,7 @@ static int uvc_probe(struct usb_interface *intf, error: uvc_unregister_video(dev); kref_put(&dev->ref, uvc_delete); - return -ENODEV; + return ret; } static void uvc_disconnect(struct usb_interface *intf) diff --git a/drivers/media/usb/uvc/uvc_v4l2.c b/drivers/media/usb/uvc/uvc_v4l2.c index 39065db44e86..668a4e9d772c 100644 --- a/drivers/media/usb/uvc/uvc_v4l2.c +++ b/drivers/media/usb/uvc/uvc_v4l2.c @@ -26,6 +26,27 @@ #include "uvcvideo.h" +int uvc_pm_get(struct uvc_device *dev) +{ + int ret; + + ret = usb_autopm_get_interface(dev->intf); + if (ret) + return ret; + + ret = uvc_status_get(dev); + if (ret) + usb_autopm_put_interface(dev->intf); + + return ret; +} + +void uvc_pm_put(struct uvc_device *dev) +{ + uvc_status_put(dev); + usb_autopm_put_interface(dev->intf); +} + static int uvc_acquire_privileges(struct uvc_fh *handle); static int uvc_control_add_xu_mapping(struct uvc_video_chain *chain, @@ -637,28 +658,14 @@ static int uvc_v4l2_open(struct file *file) { struct uvc_streaming *stream; struct uvc_fh *handle; - int ret = 0; stream = video_drvdata(file); uvc_dbg(stream->dev, CALLS, "%s\n", __func__); - ret = usb_autopm_get_interface(stream->dev->intf); - if (ret < 0) - return ret; - /* Create the device handle. */ handle = kzalloc(sizeof(*handle), GFP_KERNEL); - if (handle == NULL) { - usb_autopm_put_interface(stream->dev->intf); + if (!handle) return -ENOMEM; - } - - ret = uvc_status_get(stream->dev); - if (ret) { - usb_autopm_put_interface(stream->dev->intf); - kfree(handle); - return ret; - } v4l2_fh_init(&handle->vfh, &stream->vdev); v4l2_fh_add(&handle->vfh); @@ -683,6 +690,9 @@ static int uvc_v4l2_release(struct file *file) if (uvc_has_privileges(handle)) uvc_queue_release(&stream->queue); + if (handle->is_streaming) + uvc_pm_put(stream->dev); + /* Release the file handle. */ uvc_dismiss_privileges(handle); v4l2_fh_del(&handle->vfh); @@ -690,9 +700,6 @@ static int uvc_v4l2_release(struct file *file) kfree(handle); file->private_data = NULL; - uvc_status_put(stream->dev); - - usb_autopm_put_interface(stream->dev->intf); return 0; } @@ -841,11 +848,23 @@ static int uvc_ioctl_streamon(struct file *file, void *fh, if (!uvc_has_privileges(handle)) return -EBUSY; - mutex_lock(&stream->mutex); + guard(mutex)(&stream->mutex); + + if (handle->is_streaming) + return 0; + ret = uvc_queue_streamon(&stream->queue, type); - mutex_unlock(&stream->mutex); + if (ret) + return ret; - return ret; + ret = uvc_pm_get(stream->dev); + if (ret) { + uvc_queue_streamoff(&stream->queue, type); + return ret; + } + handle->is_streaming = true; + + return 0; } static int uvc_ioctl_streamoff(struct file *file, void *fh, @@ -857,9 +876,13 @@ static int uvc_ioctl_streamoff(struct file *file, void *fh, if (!uvc_has_privileges(handle)) return -EBUSY; - mutex_lock(&stream->mutex); + guard(mutex)(&stream->mutex); + uvc_queue_streamoff(&stream->queue, type); - mutex_unlock(&stream->mutex); + if (handle->is_streaming) { + handle->is_streaming = false; + uvc_pm_put(stream->dev); + } return 0; } @@ -1358,9 +1381,11 @@ static int uvc_v4l2_put_xu_query(const struct uvc_xu_control_query *kp, #define UVCIOC_CTRL_MAP32 _IOWR('u', 0x20, struct uvc_xu_control_mapping32) #define UVCIOC_CTRL_QUERY32 _IOWR('u', 0x21, struct uvc_xu_control_query32) +DEFINE_FREE(uvc_pm_put, struct uvc_device *, if (_T) uvc_pm_put(_T)) static long uvc_v4l2_compat_ioctl32(struct file *file, unsigned int cmd, unsigned long arg) { + struct uvc_device *uvc_device __free(uvc_pm_put) = NULL; struct uvc_fh *handle = file->private_data; union { struct uvc_xu_control_mapping xmap; @@ -1369,6 +1394,12 @@ static long uvc_v4l2_compat_ioctl32(struct file *file, void __user *up = compat_ptr(arg); long ret; + ret = uvc_pm_get(handle->stream->dev); + if (ret) + return ret; + + uvc_device = handle->stream->dev; + switch (cmd) { case UVCIOC_CTRL_MAP32: ret = uvc_v4l2_get_xu_mapping(&karg.xmap, up); @@ -1403,6 +1434,42 @@ static long uvc_v4l2_compat_ioctl32(struct file *file, } #endif +static long uvc_v4l2_unlocked_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct uvc_fh *handle = file->private_data; + int ret; + + /* The following IOCTLs do not need to turn on the camera. */ + switch (cmd) { + case VIDIOC_CREATE_BUFS: + case VIDIOC_DQBUF: + case VIDIOC_ENUM_FMT: + case VIDIOC_ENUM_FRAMEINTERVALS: + case VIDIOC_ENUM_FRAMESIZES: + case VIDIOC_ENUMINPUT: + case VIDIOC_EXPBUF: + case VIDIOC_G_FMT: + case VIDIOC_G_PARM: + case VIDIOC_G_SELECTION: + case VIDIOC_QBUF: + case VIDIOC_QUERYCAP: + case VIDIOC_REQBUFS: + case VIDIOC_SUBSCRIBE_EVENT: + case VIDIOC_UNSUBSCRIBE_EVENT: + return video_ioctl2(file, cmd, arg); + } + + ret = uvc_pm_get(handle->stream->dev); + if (ret) + return ret; + + ret = video_ioctl2(file, cmd, arg); + + uvc_pm_put(handle->stream->dev); + return ret; +} + static ssize_t uvc_v4l2_read(struct file *file, char __user *data, size_t count, loff_t *ppos) { @@ -1487,7 +1554,7 @@ const struct v4l2_file_operations uvc_fops = { .owner = THIS_MODULE, .open = uvc_v4l2_open, .release = uvc_v4l2_release, - .unlocked_ioctl = video_ioctl2, + .unlocked_ioctl = uvc_v4l2_unlocked_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl32 = uvc_v4l2_compat_ioctl32, #endif diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h index b4ee701835fc..b9f8eb62ba1d 100644 --- a/drivers/media/usb/uvc/uvcvideo.h +++ b/drivers/media/usb/uvc/uvcvideo.h @@ -630,6 +630,7 @@ struct uvc_fh { struct uvc_streaming *stream; enum uvc_handle_state state; unsigned int pending_async_ctrls; + bool is_streaming; }; /* ------------------------------------------------------------------------ @@ -767,6 +768,10 @@ void uvc_status_suspend(struct uvc_device *dev); int uvc_status_get(struct uvc_device *dev); void uvc_status_put(struct uvc_device *dev); +/* PM */ +int uvc_pm_get(struct uvc_device *dev); +void uvc_pm_put(struct uvc_device *dev); + /* Controls */ extern const struct v4l2_subscribed_event_ops uvc_ctrl_sub_ev_ops; |