From patchwork Tue Feb 7 16:29:34 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Guennadi Liakhovetski X-Patchwork-Id: 39253 X-Patchwork-Delegate: laurent.pinchart@ideasonboard.com Received: from mail.tu-berlin.de ([130.149.7.33]) by www.linuxtv.org with esmtp (Exim 4.84_2) (envelope-from ) id 1cb8eN-00024h-Li; Tue, 07 Feb 2017 16:29:51 +0000 X-tubIT-Incoming-IP: 209.132.180.67 Received: from vger.kernel.org ([209.132.180.67]) by mail.tu-berlin.de (exim-4.84_2/mailfrontend-6) with esmtp id 1cb8eK-0006gS-5w; Tue, 07 Feb 2017 17:29:51 +0100 Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1754912AbdBGQ3q (ORCPT + 1 other); Tue, 7 Feb 2017 11:29:46 -0500 Received: from mout.gmx.net ([212.227.17.21]:53535 "EHLO mout.gmx.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1754249AbdBGQ3o (ORCPT ); Tue, 7 Feb 2017 11:29:44 -0500 Received: from axis700.grange ([81.173.166.100]) by mail.gmx.com (mrgmx102 [212.227.17.168]) with ESMTPSA (Nemesis) id 0MELdk-1cdIMe0rxQ-00FWiN for ; Tue, 07 Feb 2017 17:29:40 +0100 Received: from 200r.grange (200r.grange [192.168.1.16]) by axis700.grange (Postfix) with ESMTP id 40F0A8B114 for ; Tue, 7 Feb 2017 17:29:37 +0100 (CET) Received: from lyakh (uid 1000) (envelope-from g.liakhovetski@gmx.de) id 802039 by 200r.grange (DragonFly Mail Agent v0.9); Tue, 07 Feb 2017 17:29:37 +0100 From: Guennadi Liakhovetski To: linux-media@vger.kernel.org Cc: Laurent Pinchart , Hans Verkuil Subject: [PATCH v2 2/4] uvcvideo: send a control event when a Control Change interrupt arrives Date: Tue, 7 Feb 2017 17:29:34 +0100 Message-Id: <1486484976-17365-3-git-send-email-guennadi.liakhovetski@intel.com> X-Mailer: git-send-email 1.9.3 In-Reply-To: <1486484976-17365-1-git-send-email-guennadi.liakhovetski@intel.com> References: <1486484976-17365-1-git-send-email-guennadi.liakhovetski@intel.com> X-Provags-ID: V03:K0:OkE8bj/IqXizMZmYP1/eNckxzJbqcUnqFEsNZZ+KsAbgD97Gk1Y UInAP/lwY2scA16X9fPYRJIhEgkIKKAujs40YJKMG85+bU6XlAbUY91oLcZF0V+LvzFXNBH axzZ0nmNrnT8rRDmu2mxOIlJNeK3a3jAisGlNJTFsKpgc5tt+vKmegEfprvVy3E4H2+6A0B tSFILJzrbHNvdb6UgujIQ== X-UI-Out-Filterresults: notjunk:1; V01:K0:yRygfC5IkBY=:IGZCdL68f1+D+C3JsyekzD sGM2nqNuIxK6bpo25LtnZN3RAhtCmh8/UQ61iB4cdRIC4cKMJVp9ZSzs+gttKvBvALVTNK5SM 6/IxTdFuMTIqjigIsoUsrIGG3oNc/C7kIfinlgziDEMUUrM9CKoRMkDp8kTcimsojGxSnN7K7 4nfzbuxeSdWW7y6QcV6bfLIgtEBNAdhqShU1PZKWyx+YL9iuEp/eelB+hKf/SbhSomz4fVHer sniMdI2FxVfi11AQijFL3bndBGwXiufa4MNsxnE7BcgZ3x/R+FXS6f0SIZ4k4oHkuBVGa9Ygb TORZNaBUTJF+Z93G6JyggXEj+DIHC0p0iNcewsQNvHyj5ewVvcGATTlGncDekLBMNbinDTR6x 42dAwZdBGmUlixWThLVhRPDKb3Z7p/f0+ymwpKI5+gRU0bmiODdZd8u07EWkX8pT71Z0Y31Sm qeVModM0MZgAgM1AdTGpK2kRRXvTydhd53/W6w8Rr/v3z7coB6EFXSy6Ra3dyZDj6A18L4Xn/ dKpAwf6LogZHGwqMMuygTCIwUpTuQJSyR9nmDisyj9wMxiL7JyUTFECWWok5LsbFt26mpziAz NMmzKh/REIs7nSjPo3svpeZdAB/0qyh4VF6yKa8p9BQvnVl81/7xqOwc7D1PjFZBO33Fwx+ih DgB9/C455kWsbN6ptOX43aOFw6tkpytynCX3KxF6RdzcD1VuhY3myMXUzDqq88mQOv6Ww3i94 Sf5VXIq7gKh78fqqB8OUsY0wbVe9l7VqKz3mSv7spqOLStTrwS4cnG06YA61R++QAN+fdKAIr S9YQQvtFTu9Fi2vhZqw9MY292GI412U7aIaEjw1aLXMwr4w0nD46YhyBzxyflbvwUZ2UOH6dT jjFlg38gphjyUswS9dJoao/6dcoMQ0u7vBTLsjxPro9I6B13zrzubL3bpXlVBKYFdTtRRiQCS guvqrdp9DQ/HS5KKHMD2ZBU2BwSf9Zjv9QZ/E6a70+8v0xxFuHA8U0h2YydOVsSPLz8k92B/Y +BPtHMURr2fJyyB6HDVfVPQEGrJ2j5TOqdlkg4FnTD6L Sender: linux-media-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org X-PMX-Version: 6.0.0.2142326, Antispam-Engine: 2.7.2.2107409, Antispam-Data: 2017.2.7.162115 X-PMX-Spam: Gauge=IIIIIIII, Probability=8%, Report=' MULTIPLE_RCPTS 0.1, HTML_00_01 0.05, HTML_00_10 0.05, BODY_SIZE_10000_PLUS 0, IN_REP_TO 0, LEGITIMATE_SIGNS 0, MSG_THREAD 0, MULTIPLE_REAL_RCPTS 0, NO_URI_HTTPS 0, REFERENCES 0, __ANY_URI 0, __CC_NAME 0, __CC_NAME_DIFF_FROM_ACC 0, __CC_REAL_NAMES 0, __HAS_CC_HDR 0, __HAS_FROM 0, __HAS_LIST_ID 0, __HAS_MSGID 0, __HAS_X_MAILER 0, __HAS_X_MAILING_LIST 0, __IN_REP_TO 0, __LEGIT_LIST_HEADER 0, __MIME_TEXT_ONLY 0, __MIME_TEXT_P 0, __MIME_TEXT_P1 0, __MULTIPLE_RCPTS_CC_X2 0, __NO_HTML_TAG_RAW 0, __REFERENCES 0, __SANE_MSGID 0, __STOCK_PHRASE_7 0, __SUBJ_ALPHA_END 0, __TO_MALFORMED_2 0, __TO_NO_NAME 0, __URI_NO_WWW 0, __URI_NS ' UVC defines a method of handling asynchronous controls, which sends a USB packet over the interrupt pipe. This patch implements support for such packets by sending a control event to the user. Since this can involve USB traffic and, therefore, scheduling, this has to be done in a work queue. Signed-off-by: Guennadi Liakhovetski --- drivers/media/usb/uvc/uvc_ctrl.c | 149 +++++++++++++++++++++++++++++++++---- drivers/media/usb/uvc/uvc_status.c | 112 +++++++++++++++++++++++++--- drivers/media/usb/uvc/uvc_v4l2.c | 4 +- drivers/media/usb/uvc/uvcvideo.h | 14 +++- include/uapi/linux/uvcvideo.h | 2 + 5 files changed, 253 insertions(+), 28 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c index 6e33bd0..98f2fed 100644 --- a/drivers/media/usb/uvc/uvc_ctrl.c +++ b/drivers/media/usb/uvc/uvc_ctrl.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -1277,16 +1278,112 @@ static void uvc_ctrl_send_event(struct uvc_fh *handle, } } -static void uvc_ctrl_send_slave_event(struct uvc_fh *handle, - struct uvc_control *master, u32 slave_id, - const struct v4l2_ext_control *xctrls, unsigned int xctrls_count) +static void __uvc_ctrl_send_slave_event(struct uvc_fh *handle, + struct uvc_control *master, u32 slave_id) { struct uvc_control_mapping *mapping = NULL; struct uvc_control *ctrl = NULL; u32 changes = V4L2_EVENT_CTRL_CH_FLAGS; - unsigned int i; s32 val = 0; + __uvc_find_control(master->entity, slave_id, &mapping, &ctrl, 0); + if (ctrl == NULL) + return; + + if (__uvc_ctrl_get(handle->chain, ctrl, mapping, &val) == 0) + changes |= V4L2_EVENT_CTRL_CH_VALUE; + + uvc_ctrl_send_event(handle, ctrl, mapping, val, changes); +} + +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; + struct uvc_control_mapping *mapping; + struct uvc_control *ctrl; + struct uvc_fh *handle; + __u8 *data; + unsigned int i; + + spin_lock_irq(&w->lock); + data = w->data; + w->data = NULL; + ctrl = w->ctrl; + handle = ctrl->handle; + ctrl->handle = NULL; + spin_unlock_irq(&w->lock); + + if (mutex_lock_interruptible(&handle->chain->ctrl_mutex)) + goto free; + + list_for_each_entry(mapping, &ctrl->info.mappings, list) { + s32 value; + + mapping->get(mapping, UVC_GET_CUR, data, &value, sizeof(value)); + + for (i = 0; i < ARRAY_SIZE(mapping->slave_ids); ++i) { + if (!mapping->slave_ids[i]) + break; + + __uvc_ctrl_send_slave_event(handle, ctrl, + mapping->slave_ids[i]); + } + + if (mapping->v4l2_type == V4L2_CTRL_TYPE_MENU) { + struct uvc_menu_info *menu = mapping->menu_info; + unsigned int i; + + for (i = 0; i < mapping->menu_count; ++i, ++menu) + if (menu->value == value) { + value = i; + break; + } + } + + uvc_ctrl_send_event(handle, ctrl, mapping, value, + V4L2_EVENT_CTRL_CH_VALUE); + } + + mutex_unlock(&handle->chain->ctrl_mutex); + +free: + kfree(data); +} + +void uvc_ctrl_status_event(struct uvc_device *dev, struct uvc_control *ctrl, + __u8 *data, size_t len) +{ + struct uvc_ctrl_work *w = &dev->async_ctrl; + + if (list_empty(&ctrl->info.mappings)) + return; + + if (!ctrl->handle) + /* This is an auto-update, they are unsupported */ + return; + + spin_lock(&w->lock); + if (w->data) + /* A previous event work hasn't run yet, we lose 1 event */ + kfree(w->data); + + w->data = kmalloc(len, GFP_ATOMIC); + if (w->data) { + memcpy(w->data, data, len); + w->ctrl = ctrl; + schedule_work(&w->work); + } + spin_unlock(&w->lock); +} + +static void uvc_ctrl_send_slave_event(struct uvc_fh *handle, + struct uvc_control *master, u32 slave_id, + const struct v4l2_ext_control *xctrls, unsigned int xctrls_count) +{ + unsigned int i; + /* * We can skip sending an event for the slave if the slave * is being modified in the same transaction. @@ -1296,14 +1393,7 @@ static void uvc_ctrl_send_slave_event(struct uvc_fh *handle, return; } - __uvc_find_control(master->entity, slave_id, &mapping, &ctrl, 0); - if (ctrl == NULL) - return; - - if (__uvc_ctrl_get(handle->chain, ctrl, mapping, &val) == 0) - changes |= V4L2_EVENT_CTRL_CH_VALUE; - - uvc_ctrl_send_event(handle, ctrl, mapping, val, changes); + __uvc_ctrl_send_slave_event(handle, master, slave_id); } static void uvc_ctrl_send_events(struct uvc_fh *handle, @@ -1318,6 +1408,10 @@ static void uvc_ctrl_send_events(struct uvc_fh *handle, for (i = 0; i < xctrls_count; ++i) { ctrl = uvc_find_control(handle->chain, xctrls[i].id, &mapping); + if (ctrl->info.flags & UVC_CTRL_FLAG_ASYNCHRONOUS) + /* Notification will be sent from an Interrupt event */ + continue; + for (j = 0; j < ARRAY_SIZE(mapping->slave_ids); ++j) { if (!mapping->slave_ids[j]) break; @@ -1513,9 +1607,10 @@ int uvc_ctrl_get(struct uvc_video_chain *chain, return __uvc_ctrl_get(chain, ctrl, mapping, &xctrl->value); } -int uvc_ctrl_set(struct uvc_video_chain *chain, +int uvc_ctrl_set(struct uvc_fh *handle, struct v4l2_ext_control *xctrl) { + struct uvc_video_chain *chain = handle->chain; struct uvc_control *ctrl; struct uvc_control_mapping *mapping; s32 value; @@ -1529,6 +1624,18 @@ int uvc_ctrl_set(struct uvc_video_chain *chain, return -EINVAL; if (!(ctrl->info.flags & UVC_CTRL_FLAG_SET_CUR)) return -EACCES; + if (ctrl->info.flags & UVC_CTRL_FLAG_ASYNCHRONOUS) { + if (ctrl->handle) + /* + * Actually we could send the control and let the camera + * issue a STALL, but we have to check here anyway. + * Besides we cannot process a new instance of the same + * asynchronous control, while the previous one is still + * active. + */ + return -EBUSY; + ctrl->handle = handle; + } /* Clamp out of range values. */ switch (mapping->v4l2_type) { @@ -1721,7 +1828,9 @@ static int uvc_ctrl_fill_xu_info(struct uvc_device *dev, | (data[0] & UVC_CONTROL_CAP_SET ? UVC_CTRL_FLAG_SET_CUR : 0) | (data[0] & UVC_CONTROL_CAP_AUTOUPDATE ? - UVC_CTRL_FLAG_AUTO_UPDATE : 0); + UVC_CTRL_FLAG_AUTO_UPDATE : 0) + | (data[0] & UVC_CONTROL_CAP_ASYNCHRONOUS ? + UVC_CTRL_FLAG_ASYNCHRONOUS : 0); uvc_ctrl_fixup_xu_info(dev, ctrl, info); @@ -2169,6 +2278,13 @@ static void uvc_ctrl_init_ctrl(struct uvc_device *dev, struct uvc_control *ctrl) if (!ctrl->initialized) return; + /* Temporarily abuse DATA_CURRENT buffer to avoid 1 byte allocation */ + if (!uvc_query_ctrl(dev, UVC_GET_INFO, ctrl->entity->id, + dev->intfnum, info->selector, + uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT), 1) && + uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT)[0] & 0x10) + ctrl->info.flags |= UVC_CTRL_FLAG_ASYNCHRONOUS; + for (; mapping < mend; ++mapping) { if (uvc_entity_match_guid(ctrl->entity, mapping->entity) && ctrl->info.selector == mapping->selector) @@ -2184,6 +2300,9 @@ int uvc_ctrl_init_device(struct uvc_device *dev) struct uvc_entity *entity; unsigned int i; + spin_lock_init(&dev->async_ctrl.lock); + INIT_WORK(&dev->async_ctrl.work, uvc_ctrl_status_event_work); + /* Walk the entities list and instantiate controls */ list_for_each_entry(entity, &dev->entities, list) { struct uvc_control *ctrl; @@ -2252,6 +2371,8 @@ void uvc_ctrl_cleanup_device(struct uvc_device *dev) struct uvc_entity *entity; unsigned int i; + cancel_work_sync(&dev->async_ctrl.work); + /* Free controls and control mappings for all entities. */ list_for_each_entry(entity, &dev->entities, list) { for (i = 0; i < entity->ncontrols; ++i) { diff --git a/drivers/media/usb/uvc/uvc_status.c b/drivers/media/usb/uvc/uvc_status.c index f552ab9..1d8f776 100644 --- a/drivers/media/usb/uvc/uvc_status.c +++ b/drivers/media/usb/uvc/uvc_status.c @@ -78,7 +78,24 @@ static void uvc_input_report_key(struct uvc_device *dev, unsigned int code, /* -------------------------------------------------------------------------- * Status interrupt endpoint */ -static void uvc_event_streaming(struct uvc_device *dev, __u8 *data, int len) +struct uvc_streaming_status { + __u8 bStatusType; + __u8 bOriginator; + __u8 bEvent; + __u8 bValue[]; +} __packed; + +struct uvc_control_status { + __u8 bStatusType; + __u8 bOriginator; + __u8 bEvent; + __u8 bSelector; + __u8 bAttribute; + __u8 bValue[]; +} __packed; + +static void uvc_event_streaming(struct uvc_device *dev, + struct uvc_streaming_status *status, int len) { if (len < 3) { uvc_trace(UVC_TRACE_STATUS, "Invalid streaming status event " @@ -86,30 +103,101 @@ static void uvc_event_streaming(struct uvc_device *dev, __u8 *data, int len) return; } - if (data[2] == 0) { + if (status->bEvent == 0) { if (len < 4) 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]); + status->bOriginator, + status->bValue[0] ? "pressed" : "released", len); + uvc_input_report_key(dev, KEY_CAMERA, status->bValue[0]); } else { uvc_trace(UVC_TRACE_STATUS, "Stream %u error event %02x %02x " - "len %d.\n", data[1], data[2], data[3], len); + "len %d.\n", status->bOriginator, status->bEvent, + status->bValue[0], 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_ctrl(struct uvc_entity *entity, + __u8 selector) +{ + struct uvc_control *ctrl; + unsigned int i; + + 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, + struct uvc_control_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) { + ctrl = uvc_event_entity_ctrl(entity, + status->bSelector); + /* + * Some buggy cameras send asynchronous Control + * Change events for control, other than the + * ones, that had been changed, even though the + * AutoUpdate flag isn't set for the control. + */ + if (ctrl && (!ctrl->handle || + ctrl->handle->chain == chain)) + return ctrl; + } + } + } + + return NULL; +} + +static void uvc_event_control(struct uvc_device *dev, + struct uvc_control_status *status, int len) { - char *attrs[3] = { "value", "info", "failure" }; + struct uvc_control *ctrl; + char *attrs[] = { "value", "info", "failure", "min", "max" }; - if (len < 6 || data[2] != 0 || data[4] > 2) { + if (len < 6 || status->bEvent != 0 || + status->bAttribute >= ARRAY_SIZE(attrs)) { uvc_trace(UVC_TRACE_STATUS, "Invalid control status event " "received.\n"); return; } uvc_trace(UVC_TRACE_STATUS, "Control %u/%u %s change len %d.\n", - data[1], data[3], attrs[data[4]], len); + status->bOriginator, status->bSelector, + attrs[status->bAttribute], len); + + /* Find the control. */ + ctrl = uvc_event_find_ctrl(dev, status); + if (!ctrl) + return; + + switch (status->bAttribute) { + case UVC_CTRL_VALUE_CHANGE: + case UVC_CTRL_INFO_CHANGE: + uvc_ctrl_status_event(dev, ctrl, status->bValue, len - + offsetof(struct uvc_control_status, bValue)); + break; + case UVC_CTRL_FAILURE_CHANGE: + case UVC_CTRL_MIN_CHANGE: + case UVC_CTRL_MAX_CHANGE: + break; + } } static void uvc_status_complete(struct urb *urb) @@ -138,11 +226,13 @@ static void uvc_status_complete(struct urb *urb) if (len > 0) { switch (dev->status[0] & 0x0f) { case UVC_STATUS_TYPE_CONTROL: - uvc_event_control(dev, dev->status, len); + uvc_event_control(dev, + (struct uvc_control_status *)dev->status, len); break; case UVC_STATUS_TYPE_STREAMING: - uvc_event_streaming(dev, dev->status, len); + uvc_event_streaming(dev, + (struct uvc_streaming_status *)dev->status, len); break; default: diff --git a/drivers/media/usb/uvc/uvc_v4l2.c b/drivers/media/usb/uvc/uvc_v4l2.c index 3e7e283..06be5f6 100644 --- a/drivers/media/usb/uvc/uvc_v4l2.c +++ b/drivers/media/usb/uvc/uvc_v4l2.c @@ -970,7 +970,7 @@ static int uvc_ioctl_s_ctrl(struct file *file, void *fh, if (ret < 0) return ret; - ret = uvc_ctrl_set(chain, &xctrl); + ret = uvc_ctrl_set(handle, &xctrl); if (ret < 0) { uvc_ctrl_rollback(handle); return ret; @@ -1045,7 +1045,7 @@ static int uvc_ioctl_s_try_ext_ctrls(struct uvc_fh *handle, return ret; for (i = 0; i < ctrls->count; ++ctrl, ++i) { - ret = uvc_ctrl_set(chain, ctrl); + ret = uvc_ctrl_set(handle, ctrl); if (ret < 0) { uvc_ctrl_rollback(handle); ctrls->error_idx = commit ? ctrls->count : i; diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h index 47a42f6..2ef135a 100644 --- a/drivers/media/usb/uvc/uvcvideo.h +++ b/drivers/media/usb/uvc/uvcvideo.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -241,6 +242,8 @@ struct uvc_control { initialized:1; __u8 *uvc_data; + + struct uvc_fh *handle; /* Used for asynchronous event delivery */ }; struct uvc_format_desc { @@ -574,6 +577,13 @@ struct uvc_device { __u8 *status; struct input_dev *input; char input_phys[64]; + + struct uvc_ctrl_work { + struct work_struct work; + struct uvc_control *ctrl; + spinlock_t lock; + void *data; + } async_ctrl; }; enum uvc_handle_state { @@ -720,6 +730,8 @@ extern int uvc_ctrl_add_mapping(struct uvc_video_chain *chain, extern int uvc_ctrl_init_device(struct uvc_device *dev); extern void uvc_ctrl_cleanup_device(struct uvc_device *dev); extern int uvc_ctrl_restore_values(struct uvc_device *dev); +extern void uvc_ctrl_status_event(struct uvc_device *dev, + struct uvc_control *ctrl, __u8 *data, size_t len); extern int uvc_ctrl_begin(struct uvc_video_chain *chain); extern int __uvc_ctrl_commit(struct uvc_fh *handle, int rollback, @@ -738,7 +750,7 @@ static inline int uvc_ctrl_rollback(struct uvc_fh *handle) extern int uvc_ctrl_get(struct uvc_video_chain *chain, struct v4l2_ext_control *xctrl); -extern int uvc_ctrl_set(struct uvc_video_chain *chain, +extern int uvc_ctrl_set(struct uvc_fh *handle, struct v4l2_ext_control *xctrl); extern int uvc_xu_ctrl_query(struct uvc_video_chain *chain, diff --git a/include/uapi/linux/uvcvideo.h b/include/uapi/linux/uvcvideo.h index 3b08186..420883a 100644 --- a/include/uapi/linux/uvcvideo.h +++ b/include/uapi/linux/uvcvideo.h @@ -27,6 +27,8 @@ #define UVC_CTRL_FLAG_RESTORE (1 << 6) /* Control can be updated by the camera. */ #define UVC_CTRL_FLAG_AUTO_UPDATE (1 << 7) +/* Control supports asynchronous reporting */ +#define UVC_CTRL_FLAG_ASYNCHRONOUS (1 << 8) #define UVC_CTRL_FLAG_GET_RANGE \ (UVC_CTRL_FLAG_GET_CUR | UVC_CTRL_FLAG_GET_MIN | \