* [PATCH v2 0/3] media: uvcvideo: live pan/tilt/zoom readback on the OBSBOT Tiny 2
@ 2026-09-02 0:25 Michael Jordan
2026-09-02 0:25 ` [PATCH v2 1/3] media: uvcvideo: report AUTO_UPDATE controls as volatile Michael Jordan
` (2 more replies)
0 siblings, 3 replies; 6+ messages in thread
From: Michael Jordan @ 2026-09-02 0:25 UTC (permalink / raw)
To: Laurent Pinchart, Hans de Goede, Ricardo Ribalda
Cc: Mauro Carvalho Chehab, Hans Verkuil, linux-media, linux-kernel,
Michael Jordan
The OBSBOT Tiny 2's GET_INFO strips the AUTOUPDATE bit from its pan,
tilt and zoom controls, so uvcvideo caches them and userspace can never
observe the actuator's live state. This series reports AUTO_UPDATE
controls as volatile to userspace, generalises the existing XU flags
fixup table to all controls, and adds entries for the three affected
controls on this camera.
Changes in v2 (following Ricardo's review of v1 [1]):
- Patch 1: unchanged; picked up Ricardo's Reviewed-by.
- Patch 2: uvc_ctrl_fixup_flags() now returns bool and runs at the
start of uvc_ctrl_get_flags(), before the allocation, skipping the
GET_INFO query entirely for controls the table covers (Ricardo).
- Patch 3: as asked, I checked whether the camera's other
AUTO_UPDATE-flagged controls suffer the same bug. Two more do:
CT_PANTILT_RELATIVE and CT_ZOOM_ABSOLUTE, both verified on hardware
to change autonomously and report live values on GET_CUR; entries
added for both. Exposure, white balance and focus turned out to be
write-only on this firmware (their autos work, but GET_CUR echoes the
last SET_CUR), so they gain nothing from AUTO_UPDATE and were left
alone; details in the commit message. The commit message also no
longer claims the capability byte is the same for every control --
probing every control showed it is computed per control, just wrong
for the PTZ ones. Dropped Ricardo's Reviewed-by since the patch
changed materially.
The full lsusb -v output was posted in reply to v1's patch 3, in the
thread at [1].
[1] https://lore.kernel.org/linux-media/20260828152557.653475-1-jordan.mymail@gmail.com/
Michael Jordan (3):
media: uvcvideo: report AUTO_UPDATE controls as volatile
media: uvcvideo: generalise the XU flags fixup to all controls
media: uvcvideo: fix up missing AUTO_UPDATE on the OBSBOT Tiny 2
drivers/media/usb/uvc/uvc_ctrl.c | 122 ++++++++++++++++++++-----------
1 file changed, 81 insertions(+), 41 deletions(-)
--
2.43.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v2 1/3] media: uvcvideo: report AUTO_UPDATE controls as volatile
2026-09-02 0:25 [PATCH v2 0/3] media: uvcvideo: live pan/tilt/zoom readback on the OBSBOT Tiny 2 Michael Jordan
@ 2026-09-02 0:25 ` Michael Jordan
2026-09-02 0:25 ` [PATCH v2 2/3] media: uvcvideo: generalise the XU flags fixup to all controls Michael Jordan
2026-09-02 0:25 ` [PATCH v2 3/3] media: uvcvideo: fix up missing AUTO_UPDATE on the OBSBOT Tiny 2 Michael Jordan
2 siblings, 0 replies; 6+ messages in thread
From: Michael Jordan @ 2026-09-02 0:25 UTC (permalink / raw)
To: Laurent Pinchart, Hans de Goede, Ricardo Ribalda
Cc: Mauro Carvalho Chehab, Hans Verkuil, linux-media, linux-kernel,
Michael Jordan
A control with UVC_CTRL_FLAG_AUTO_UPDATE is one whose value the device
changes on its own: the driver never trusts its cached value for it,
re-reading the device on every VIDIOC_G_EXT_CTRLS (the rollback at the
end of the ioctl runs uvc_ctrl_commit_entity(), which clears ctrl->loaded
for these controls) and re-reading it after each write. That is exactly
what V4L2_CTRL_FLAG_VOLATILE describes to userspace, but the driver never
reported it, so applications had no way to know that the value they read
can change under them and that a fresh read is worth issuing.
Report V4L2_CTRL_FLAG_VOLATILE for AUTO_UPDATE controls. The uAPI
documents writes to a volatile control as ignored unless
V4L2_CTRL_FLAG_EXECUTE_ON_WRITE is also set, and this driver sends every
write of a writable control to the device, so report EXECUTE_ON_WRITE
alongside it whenever the control is settable.
Suggested-by: Ricardo Ribalda <ribalda@chromium.org>
Reviewed-by: Ricardo Ribalda <ribalda@chromium.org>
Signed-off-by: Michael Jordan <jordan.mymail@gmail.com>
---
drivers/media/usb/uvc/uvc_ctrl.c | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c
index 3ca108b83..aceb26310 100644
--- a/drivers/media/usb/uvc/uvc_ctrl.c
+++ b/drivers/media/usb/uvc/uvc_ctrl.c
@@ -1840,6 +1840,17 @@ static int __uvc_query_v4l2_ctrl(struct uvc_video_chain *chain,
if ((ctrl->info.flags & UVC_CTRL_FLAG_GET_MAX) &&
(ctrl->info.flags & UVC_CTRL_FLAG_GET_MIN))
v4l2_ctrl->flags |= V4L2_CTRL_FLAG_HAS_WHICH_MIN_MAX;
+ if (ctrl->info.flags & UVC_CTRL_FLAG_AUTO_UPDATE) {
+ v4l2_ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE;
+ /*
+ * Writes to a volatile control are documented to be ignored
+ * unless EXECUTE_ON_WRITE is also reported. The driver sends
+ * every write of a writable control to the device, so report
+ * the flag accordingly.
+ */
+ if (ctrl->info.flags & UVC_CTRL_FLAG_SET_CUR)
+ v4l2_ctrl->flags |= V4L2_CTRL_FLAG_EXECUTE_ON_WRITE;
+ }
if (mapping->master_id)
__uvc_find_control(ctrl->entity, mapping->master_id,
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v2 2/3] media: uvcvideo: generalise the XU flags fixup to all controls
2026-09-02 0:25 [PATCH v2 0/3] media: uvcvideo: live pan/tilt/zoom readback on the OBSBOT Tiny 2 Michael Jordan
2026-09-02 0:25 ` [PATCH v2 1/3] media: uvcvideo: report AUTO_UPDATE controls as volatile Michael Jordan
@ 2026-09-02 0:25 ` Michael Jordan
2026-09-02 6:38 ` Ricardo Ribalda
2026-09-02 0:25 ` [PATCH v2 3/3] media: uvcvideo: fix up missing AUTO_UPDATE on the OBSBOT Tiny 2 Michael Jordan
2 siblings, 1 reply; 6+ messages in thread
From: Michael Jordan @ 2026-09-02 0:25 UTC (permalink / raw)
To: Laurent Pinchart, Hans de Goede, Ricardo Ribalda
Cc: Mauro Carvalho Chehab, Hans Verkuil, linux-media, linux-kernel,
Michael Jordan
uvc_ctrl_fixup_xu_info() holds a per-device table of controls whose
GET_INFO reply is wrong, and overrides the flags for them. It only runs
from uvc_ctrl_fill_xu_info(), so it can only correct extension unit
controls, but standard controls suffer from the same class of firmware
bug: a device can report a wrong capability byte for a Camera Terminal
or Processing Unit control just as easily.
Rename it to uvc_ctrl_fixup_flags() and call it at the start of
uvc_ctrl_get_flags(), where the flags are derived from GET_INFO for
every control, standard and XU alike. The fixup replaces the flags
wholesale, so when the table covers a control there is no point in
querying a device we already know gives a wrong answer: return early
and skip the GET_INFO request altogether. The call in
uvc_ctrl_fill_xu_info() is dropped, as uvc_ctrl_get_flags() now handles
the fixup for XU controls too.
No functional change for the devices already in the table: their
entries are XU controls, matched by entity and selector before as they
are now, and their flags come from the table either way. The only
difference is one GET_INFO request no longer issued per fixed-up
control.
Suggested-by: Ricardo Ribalda <ribalda@chromium.org>
Signed-off-by: Michael Jordan <jordan.mymail@gmail.com>
---
drivers/media/usb/uvc/uvc_ctrl.c | 91 ++++++++++++++++++--------------
1 file changed, 50 insertions(+), 41 deletions(-)
diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c
index aceb26310..64c90c380 100644
--- a/drivers/media/usb/uvc/uvc_ctrl.c
+++ b/drivers/media/usb/uvc/uvc_ctrl.c
@@ -2852,6 +2852,48 @@ int uvc_ctrl_set(struct uvc_fh *handle, struct v4l2_ext_control *xctrl)
* Dynamic controls
*/
+static bool uvc_ctrl_fixup_flags(struct uvc_device *dev,
+ const struct uvc_control *ctrl,
+ struct uvc_control_info *info)
+{
+ struct uvc_ctrl_fixup {
+ struct usb_device_id id;
+ u8 entity;
+ u8 selector;
+ u8 flags;
+ };
+
+ static const struct uvc_ctrl_fixup fixups[] = {
+ { { USB_DEVICE(0x046d, 0x08c2) }, 9, 1,
+ UVC_CTRL_FLAG_GET_MIN | UVC_CTRL_FLAG_GET_MAX |
+ UVC_CTRL_FLAG_GET_DEF | UVC_CTRL_FLAG_SET_CUR |
+ UVC_CTRL_FLAG_AUTO_UPDATE },
+ { { USB_DEVICE(0x046d, 0x08cc) }, 9, 1,
+ UVC_CTRL_FLAG_GET_MIN | UVC_CTRL_FLAG_GET_MAX |
+ UVC_CTRL_FLAG_GET_DEF | UVC_CTRL_FLAG_SET_CUR |
+ UVC_CTRL_FLAG_AUTO_UPDATE },
+ { { USB_DEVICE(0x046d, 0x0994) }, 9, 1,
+ UVC_CTRL_FLAG_GET_MIN | UVC_CTRL_FLAG_GET_MAX |
+ UVC_CTRL_FLAG_GET_DEF | UVC_CTRL_FLAG_SET_CUR |
+ UVC_CTRL_FLAG_AUTO_UPDATE },
+ };
+
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(fixups); ++i) {
+ if (!usb_match_one_id(dev->intf, &fixups[i].id))
+ continue;
+
+ if (fixups[i].entity == ctrl->entity->id &&
+ fixups[i].selector == info->selector) {
+ info->flags = fixups[i].flags;
+ return true;
+ }
+ }
+
+ return false;
+}
+
/*
* Retrieve flags for a given control
*/
@@ -2862,6 +2904,14 @@ static int uvc_ctrl_get_flags(struct uvc_device *dev,
u8 *data;
int ret;
+ /*
+ * Some devices report bogus capabilities through GET_INFO. If the
+ * fixup table covers this control, take the flags from the table and
+ * skip the query altogether.
+ */
+ if (uvc_ctrl_fixup_flags(dev, ctrl, info))
+ return 0;
+
data = kmalloc(1, GFP_KERNEL);
if (data == NULL)
return -ENOMEM;
@@ -2893,45 +2943,6 @@ static int uvc_ctrl_get_flags(struct uvc_device *dev,
return ret;
}
-static void uvc_ctrl_fixup_xu_info(struct uvc_device *dev,
- const struct uvc_control *ctrl, struct uvc_control_info *info)
-{
- struct uvc_ctrl_fixup {
- struct usb_device_id id;
- u8 entity;
- u8 selector;
- u8 flags;
- };
-
- static const struct uvc_ctrl_fixup fixups[] = {
- { { USB_DEVICE(0x046d, 0x08c2) }, 9, 1,
- UVC_CTRL_FLAG_GET_MIN | UVC_CTRL_FLAG_GET_MAX |
- UVC_CTRL_FLAG_GET_DEF | UVC_CTRL_FLAG_SET_CUR |
- UVC_CTRL_FLAG_AUTO_UPDATE },
- { { USB_DEVICE(0x046d, 0x08cc) }, 9, 1,
- UVC_CTRL_FLAG_GET_MIN | UVC_CTRL_FLAG_GET_MAX |
- UVC_CTRL_FLAG_GET_DEF | UVC_CTRL_FLAG_SET_CUR |
- UVC_CTRL_FLAG_AUTO_UPDATE },
- { { USB_DEVICE(0x046d, 0x0994) }, 9, 1,
- UVC_CTRL_FLAG_GET_MIN | UVC_CTRL_FLAG_GET_MAX |
- UVC_CTRL_FLAG_GET_DEF | UVC_CTRL_FLAG_SET_CUR |
- UVC_CTRL_FLAG_AUTO_UPDATE },
- };
-
- unsigned int i;
-
- for (i = 0; i < ARRAY_SIZE(fixups); ++i) {
- if (!usb_match_one_id(dev->intf, &fixups[i].id))
- continue;
-
- if (fixups[i].entity == ctrl->entity->id &&
- fixups[i].selector == info->selector) {
- info->flags = fixups[i].flags;
- return;
- }
- }
-}
-
/*
* Query control information (size and flags) for XU controls.
*/
@@ -2972,8 +2983,6 @@ static int uvc_ctrl_fill_xu_info(struct uvc_device *dev,
goto done;
}
- uvc_ctrl_fixup_xu_info(dev, ctrl, info);
-
uvc_dbg(dev, CONTROL,
"XU control %pUl/%u queried: len %u, flags { get %u set %u auto %u }\n",
info->entity, info->selector, info->size,
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v2 3/3] media: uvcvideo: fix up missing AUTO_UPDATE on the OBSBOT Tiny 2
2026-09-02 0:25 [PATCH v2 0/3] media: uvcvideo: live pan/tilt/zoom readback on the OBSBOT Tiny 2 Michael Jordan
2026-09-02 0:25 ` [PATCH v2 1/3] media: uvcvideo: report AUTO_UPDATE controls as volatile Michael Jordan
2026-09-02 0:25 ` [PATCH v2 2/3] media: uvcvideo: generalise the XU flags fixup to all controls Michael Jordan
@ 2026-09-02 0:25 ` Michael Jordan
2026-09-02 6:34 ` Ricardo Ribalda
2 siblings, 1 reply; 6+ messages in thread
From: Michael Jordan @ 2026-09-02 0:25 UTC (permalink / raw)
To: Laurent Pinchart, Hans de Goede, Ricardo Ribalda
Cc: Mauro Carvalho Chehab, Hans Verkuil, linux-media, linux-kernel,
Michael Jordan
The OBSBOT Tiny 2 (3564:fef8) computes its GET_INFO capability byte per
control, but gets it wrong for the controls that matter most on a
motorised PTZ camera: CT_PANTILT_ABSOLUTE, CT_PANTILT_RELATIVE and
CT_ZOOM_ABSOLUTE all answer 0x03 -- GET and SET capable, with the
AUTOUPDATE bit clear. (The byte is not a constant stub: CT_ZOOM_RELATIVE
correctly reports 0x0f, CT_ROLL_ABSOLUTE reports 0x01.)
uvc_ctrl_get_flags() takes the flags from that byte, so it clears the
UVC_CTRL_FLAG_AUTO_UPDATE that the static uvc_ctrls[] entries set for
all three controls. Without AUTO_UPDATE nothing clears ctrl->loaded
after the first read, so uvcvideo serves them from its cache
indefinitely: VIDIOC_G_CTRL returns the last value the host commanded,
never the live one.
All three controls were verified on the hardware to change autonomously
and to report the live value on GET_CUR:
- pan/tilt position keeps changing for the seconds a commanded gimbal
move takes, and changes on its own under the camera's autonomous
subject tracking;
- zoom follows the subject under the camera's AI framing (observed
0-71% with the host issuing no zoom request, matching the vendor
status protocol's zoom report);
- the pan/tilt speed control reports the actual current speed during a
relative move (a commanded 80 reads back as 78, then the deceleration
ramp, then 0 once the gimbal reaches the end stop). Without
AUTO_UPDATE the cache would report the written speed forever.
Add fixup entries restoring AUTO_UPDATE, alongside the flags each
control already has in uvc_ctrls[], for these three controls. The fixup
replaces info->flags wholesale rather than OR-ing, so each entry spells
out the full flag set.
The camera's other AUTO_UPDATE-flagged controls were checked and
deliberately left alone: exposure, white balance and focus have working
autos, but their GET_CUR just echoes the last SET_CUR (the firmware
never reports the auto-chosen value), so AUTO_UPDATE would add USB
traffic for no benefit; there is no auto-hue; CT_ZOOM_RELATIVE already
reports AUTOUPDATE; CT_ROLL_ABSOLUTE is read-only and unmapped.
The vendor has been asked to fix the firmware (support ticket #8220,
2026-08-04); no fix is available at the time of writing.
lsusb -v (device descriptor and the Camera Terminal):
Bus 003 Device 006: ID 3564:fef8 Remo Tech Co., Ltd. OBSBOT Tiny 2
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 2.10
bDeviceClass 239 Miscellaneous Device
bDeviceSubClass 2 [unknown]
bDeviceProtocol 1 Interface Association
bMaxPacketSize0 64
idVendor 0x3564 Remo Tech Co., Ltd.
idProduct 0xfef8 OBSBOT Tiny 2
bcdDevice 4.09
iManufacturer 1 Remo Tech Co., Ltd.
iProduct 2 OBSBOT Tiny 2
iSerial 0
bNumConfigurations 1
[...]
VideoControl Interface Descriptor:
bLength 18
bDescriptorType 36
bDescriptorSubtype 2 (INPUT_TERMINAL)
bTerminalID 1
wTerminalType 0x0201 Camera Sensor
bAssocTerminal 0
iTerminal 0
wObjectiveFocalLengthMin 0
wObjectiveFocalLengthMax 0
wOcularFocalLength 0
bControlSize 3
bmControls 0x00023e3e
Auto-Exposure Mode
Auto-Exposure Priority
Exposure Time (Absolute)
Exposure Time (Relative)
Focus (Absolute)
Zoom (Absolute)
Zoom (Relative)
PanTilt (Absolute)
PanTilt (Relative)
Roll (Absolute)
Focus, Auto
Suggested-by: Ricardo Ribalda <ribalda@chromium.org>
Signed-off-by: Michael Jordan <jordan.mymail@gmail.com>
---
drivers/media/usb/uvc/uvc_ctrl.c | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c
index 64c90c380..74f6e8039 100644
--- a/drivers/media/usb/uvc/uvc_ctrl.c
+++ b/drivers/media/usb/uvc/uvc_ctrl.c
@@ -2876,6 +2876,26 @@ static bool uvc_ctrl_fixup_flags(struct uvc_device *dev,
UVC_CTRL_FLAG_GET_MIN | UVC_CTRL_FLAG_GET_MAX |
UVC_CTRL_FLAG_GET_DEF | UVC_CTRL_FLAG_SET_CUR |
UVC_CTRL_FLAG_AUTO_UPDATE },
+ /*
+ * OBSBOT Tiny 2: GET_INFO reports GET|SET without AUTOUPDATE
+ * for the pan/tilt and zoom controls, clearing the AUTO_UPDATE
+ * the driver's own control table sets for them. The device
+ * moves all three on its own (gimbal moves take seconds, and
+ * its autonomous subject tracking pans, tilts and zooms with
+ * no host involvement) and reports the live values on GET_CUR.
+ */
+ { { USB_DEVICE(0x3564, 0xfef8) }, 1,
+ UVC_CT_PANTILT_ABSOLUTE_CONTROL,
+ UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_RANGE |
+ UVC_CTRL_FLAG_RESTORE | UVC_CTRL_FLAG_AUTO_UPDATE },
+ { { USB_DEVICE(0x3564, 0xfef8) }, 1,
+ UVC_CT_PANTILT_RELATIVE_CONTROL,
+ UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_RANGE |
+ UVC_CTRL_FLAG_AUTO_UPDATE },
+ { { USB_DEVICE(0x3564, 0xfef8) }, 1,
+ UVC_CT_ZOOM_ABSOLUTE_CONTROL,
+ UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_RANGE |
+ UVC_CTRL_FLAG_RESTORE | UVC_CTRL_FLAG_AUTO_UPDATE },
};
unsigned int i;
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH v2 3/3] media: uvcvideo: fix up missing AUTO_UPDATE on the OBSBOT Tiny 2
2026-09-02 0:25 ` [PATCH v2 3/3] media: uvcvideo: fix up missing AUTO_UPDATE on the OBSBOT Tiny 2 Michael Jordan
@ 2026-09-02 6:34 ` Ricardo Ribalda
0 siblings, 0 replies; 6+ messages in thread
From: Ricardo Ribalda @ 2026-09-02 6:34 UTC (permalink / raw)
To: Michael Jordan
Cc: Laurent Pinchart, Hans de Goede, Mauro Carvalho Chehab,
Hans Verkuil, linux-media, linux-kernel
Hi Michael
Thanks for your patch:
On Wed, 2 Sept 2026 at 02:26, Michael Jordan <jordan.mymail@gmail.com> wrote:
>
> The OBSBOT Tiny 2 (3564:fef8) computes its GET_INFO capability byte per
> control, but gets it wrong for the controls that matter most on a
> motorised PTZ camera: CT_PANTILT_ABSOLUTE, CT_PANTILT_RELATIVE and
> CT_ZOOM_ABSOLUTE all answer 0x03 -- GET and SET capable, with the
> AUTOUPDATE bit clear. (The byte is not a constant stub: CT_ZOOM_RELATIVE
> correctly reports 0x0f, CT_ROLL_ABSOLUTE reports 0x01.)
> uvc_ctrl_get_flags() takes the flags from that byte, so it clears the
> UVC_CTRL_FLAG_AUTO_UPDATE that the static uvc_ctrls[] entries set for
> all three controls. Without AUTO_UPDATE nothing clears ctrl->loaded
> after the first read, so uvcvideo serves them from its cache
> indefinitely: VIDIOC_G_CTRL returns the last value the host commanded,
> never the live one.
>
> All three controls were verified on the hardware to change autonomously
> and to report the live value on GET_CUR:
>
> - pan/tilt position keeps changing for the seconds a commanded gimbal
> move takes, and changes on its own under the camera's autonomous
> subject tracking;
> - zoom follows the subject under the camera's AI framing (observed
> 0-71% with the host issuing no zoom request, matching the vendor
> status protocol's zoom report);
> - the pan/tilt speed control reports the actual current speed during a
> relative move (a commanded 80 reads back as 78, then the deceleration
> ramp, then 0 once the gimbal reaches the end stop). Without
> AUTO_UPDATE the cache would report the written speed forever.
>
> Add fixup entries restoring AUTO_UPDATE, alongside the flags each
> control already has in uvc_ctrls[], for these three controls. The fixup
> replaces info->flags wholesale rather than OR-ing, so each entry spells
> out the full flag set.
>
> The camera's other AUTO_UPDATE-flagged controls were checked and
> deliberately left alone: exposure, white balance and focus have working
> autos, but their GET_CUR just echoes the last SET_CUR (the firmware
> never reports the auto-chosen value), so AUTO_UPDATE would add USB
> traffic for no benefit; there is no auto-hue; CT_ZOOM_RELATIVE already
> reports AUTOUPDATE; CT_ROLL_ABSOLUTE is read-only and unmapped.
>
> The vendor has been asked to fix the firmware (support ticket #8220,
> 2026-08-04); no fix is available at the time of writing.
>
> lsusb -v (device descriptor and the Camera Terminal):
>
> Bus 003 Device 006: ID 3564:fef8 Remo Tech Co., Ltd. OBSBOT Tiny 2
> Device Descriptor:
> bLength 18
> bDescriptorType 1
> bcdUSB 2.10
> bDeviceClass 239 Miscellaneous Device
> bDeviceSubClass 2 [unknown]
> bDeviceProtocol 1 Interface Association
> bMaxPacketSize0 64
> idVendor 0x3564 Remo Tech Co., Ltd.
> idProduct 0xfef8 OBSBOT Tiny 2
> bcdDevice 4.09
> iManufacturer 1 Remo Tech Co., Ltd.
> iProduct 2 OBSBOT Tiny 2
> iSerial 0
> bNumConfigurations 1
> [...]
> VideoControl Interface Descriptor:
> bLength 18
> bDescriptorType 36
> bDescriptorSubtype 2 (INPUT_TERMINAL)
> bTerminalID 1
> wTerminalType 0x0201 Camera Sensor
> bAssocTerminal 0
> iTerminal 0
> wObjectiveFocalLengthMin 0
> wObjectiveFocalLengthMax 0
> wOcularFocalLength 0
> bControlSize 3
> bmControls 0x00023e3e
> Auto-Exposure Mode
> Auto-Exposure Priority
> Exposure Time (Absolute)
> Exposure Time (Relative)
> Focus (Absolute)
> Zoom (Absolute)
> Zoom (Relative)
> PanTilt (Absolute)
> PanTilt (Relative)
> Roll (Absolute)
> Focus, Auto
>
Reviewed-by: Ricardo Ribalda <ribalda@chromium.org>
Link: https://lore.kernel.org/linux-media/20260902002544.34798-1-jordan.mymail@gmail.com/T/#mf4b518b5bfa604a5c925d23d1653b927b811488e
> Suggested-by: Ricardo Ribalda <ribalda@chromium.org>
> Signed-off-by: Michael Jordan <jordan.mymail@gmail.com>
> ---
> drivers/media/usb/uvc/uvc_ctrl.c | 20 ++++++++++++++++++++
> 1 file changed, 20 insertions(+)
>
> diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c
> index 64c90c380..74f6e8039 100644
> --- a/drivers/media/usb/uvc/uvc_ctrl.c
> +++ b/drivers/media/usb/uvc/uvc_ctrl.c
> @@ -2876,6 +2876,26 @@ static bool uvc_ctrl_fixup_flags(struct uvc_device *dev,
> UVC_CTRL_FLAG_GET_MIN | UVC_CTRL_FLAG_GET_MAX |
> UVC_CTRL_FLAG_GET_DEF | UVC_CTRL_FLAG_SET_CUR |
> UVC_CTRL_FLAG_AUTO_UPDATE },
> + /*
> + * OBSBOT Tiny 2: GET_INFO reports GET|SET without AUTOUPDATE
> + * for the pan/tilt and zoom controls, clearing the AUTO_UPDATE
> + * the driver's own control table sets for them. The device
> + * moves all three on its own (gimbal moves take seconds, and
> + * its autonomous subject tracking pans, tilts and zooms with
> + * no host involvement) and reports the live values on GET_CUR.
> + */
> + { { USB_DEVICE(0x3564, 0xfef8) }, 1,
> + UVC_CT_PANTILT_ABSOLUTE_CONTROL,
> + UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_RANGE |
> + UVC_CTRL_FLAG_RESTORE | UVC_CTRL_FLAG_AUTO_UPDATE },
> + { { USB_DEVICE(0x3564, 0xfef8) }, 1,
> + UVC_CT_PANTILT_RELATIVE_CONTROL,
> + UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_RANGE |
> + UVC_CTRL_FLAG_AUTO_UPDATE },
> + { { USB_DEVICE(0x3564, 0xfef8) }, 1,
> + UVC_CT_ZOOM_ABSOLUTE_CONTROL,
> + UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_RANGE |
> + UVC_CTRL_FLAG_RESTORE | UVC_CTRL_FLAG_AUTO_UPDATE },
> };
>
> unsigned int i;
> --
> 2.43.0
>
--
Ricardo Ribalda
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v2 2/3] media: uvcvideo: generalise the XU flags fixup to all controls
2026-09-02 0:25 ` [PATCH v2 2/3] media: uvcvideo: generalise the XU flags fixup to all controls Michael Jordan
@ 2026-09-02 6:38 ` Ricardo Ribalda
0 siblings, 0 replies; 6+ messages in thread
From: Ricardo Ribalda @ 2026-09-02 6:38 UTC (permalink / raw)
To: Michael Jordan
Cc: Laurent Pinchart, Hans de Goede, Mauro Carvalho Chehab,
Hans Verkuil, linux-media, linux-kernel
Hi Michael
On Wed, 2 Sept 2026 at 02:25, Michael Jordan <jordan.mymail@gmail.com> wrote:
>
> uvc_ctrl_fixup_xu_info() holds a per-device table of controls whose
> GET_INFO reply is wrong, and overrides the flags for them. It only runs
> from uvc_ctrl_fill_xu_info(), so it can only correct extension unit
> controls, but standard controls suffer from the same class of firmware
> bug: a device can report a wrong capability byte for a Camera Terminal
> or Processing Unit control just as easily.
>
> Rename it to uvc_ctrl_fixup_flags() and call it at the start of
> uvc_ctrl_get_flags(), where the flags are derived from GET_INFO for
> every control, standard and XU alike. The fixup replaces the flags
> wholesale, so when the table covers a control there is no point in
> querying a device we already know gives a wrong answer: return early
> and skip the GET_INFO request altogether. The call in
> uvc_ctrl_fill_xu_info() is dropped, as uvc_ctrl_get_flags() now handles
> the fixup for XU controls too.
>
> No functional change for the devices already in the table: their
> entries are XU controls, matched by entity and selector before as they
> are now, and their flags come from the table either way. The only
> difference is one GET_INFO request no longer issued per fixed-up
> control.
>
> Suggested-by: Ricardo Ribalda <ribalda@chromium.org>
Reviewed-by: Ricardo Ribalda <ribalda@chromium.org>
> Signed-off-by: Michael Jordan <jordan.mymail@gmail.com>
> ---
> drivers/media/usb/uvc/uvc_ctrl.c | 91 ++++++++++++++++++--------------
> 1 file changed, 50 insertions(+), 41 deletions(-)
>
> diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c
> index aceb26310..64c90c380 100644
> --- a/drivers/media/usb/uvc/uvc_ctrl.c
> +++ b/drivers/media/usb/uvc/uvc_ctrl.c
> @@ -2852,6 +2852,48 @@ int uvc_ctrl_set(struct uvc_fh *handle, struct v4l2_ext_control *xctrl)
> * Dynamic controls
> */
>
> +static bool uvc_ctrl_fixup_flags(struct uvc_device *dev,
> + const struct uvc_control *ctrl,
> + struct uvc_control_info *info)
> +{
> + struct uvc_ctrl_fixup {
> + struct usb_device_id id;
> + u8 entity;
> + u8 selector;
> + u8 flags;
> + };
> +
> + static const struct uvc_ctrl_fixup fixups[] = {
> + { { USB_DEVICE(0x046d, 0x08c2) }, 9, 1,
> + UVC_CTRL_FLAG_GET_MIN | UVC_CTRL_FLAG_GET_MAX |
> + UVC_CTRL_FLAG_GET_DEF | UVC_CTRL_FLAG_SET_CUR |
> + UVC_CTRL_FLAG_AUTO_UPDATE },
> + { { USB_DEVICE(0x046d, 0x08cc) }, 9, 1,
> + UVC_CTRL_FLAG_GET_MIN | UVC_CTRL_FLAG_GET_MAX |
> + UVC_CTRL_FLAG_GET_DEF | UVC_CTRL_FLAG_SET_CUR |
> + UVC_CTRL_FLAG_AUTO_UPDATE },
> + { { USB_DEVICE(0x046d, 0x0994) }, 9, 1,
> + UVC_CTRL_FLAG_GET_MIN | UVC_CTRL_FLAG_GET_MAX |
> + UVC_CTRL_FLAG_GET_DEF | UVC_CTRL_FLAG_SET_CUR |
> + UVC_CTRL_FLAG_AUTO_UPDATE },
> + };
> +
> + unsigned int i;
> +
> + for (i = 0; i < ARRAY_SIZE(fixups); ++i) {
> + if (!usb_match_one_id(dev->intf, &fixups[i].id))
> + continue;
> +
> + if (fixups[i].entity == ctrl->entity->id &&
> + fixups[i].selector == info->selector) {
> + info->flags = fixups[i].flags;
> + return true;
> + }
> + }
> +
> + return false;
> +}
> +
> /*
> * Retrieve flags for a given control
> */
> @@ -2862,6 +2904,14 @@ static int uvc_ctrl_get_flags(struct uvc_device *dev,
> u8 *data;
> int ret;
>
> + /*
> + * Some devices report bogus capabilities through GET_INFO. If the
> + * fixup table covers this control, take the flags from the table and
> + * skip the query altogether.
> + */
> + if (uvc_ctrl_fixup_flags(dev, ctrl, info))
> + return 0;
> +
> data = kmalloc(1, GFP_KERNEL);
> if (data == NULL)
> return -ENOMEM;
> @@ -2893,45 +2943,6 @@ static int uvc_ctrl_get_flags(struct uvc_device *dev,
> return ret;
> }
>
> -static void uvc_ctrl_fixup_xu_info(struct uvc_device *dev,
> - const struct uvc_control *ctrl, struct uvc_control_info *info)
> -{
> - struct uvc_ctrl_fixup {
> - struct usb_device_id id;
> - u8 entity;
> - u8 selector;
> - u8 flags;
> - };
> -
> - static const struct uvc_ctrl_fixup fixups[] = {
> - { { USB_DEVICE(0x046d, 0x08c2) }, 9, 1,
> - UVC_CTRL_FLAG_GET_MIN | UVC_CTRL_FLAG_GET_MAX |
> - UVC_CTRL_FLAG_GET_DEF | UVC_CTRL_FLAG_SET_CUR |
> - UVC_CTRL_FLAG_AUTO_UPDATE },
> - { { USB_DEVICE(0x046d, 0x08cc) }, 9, 1,
> - UVC_CTRL_FLAG_GET_MIN | UVC_CTRL_FLAG_GET_MAX |
> - UVC_CTRL_FLAG_GET_DEF | UVC_CTRL_FLAG_SET_CUR |
> - UVC_CTRL_FLAG_AUTO_UPDATE },
> - { { USB_DEVICE(0x046d, 0x0994) }, 9, 1,
> - UVC_CTRL_FLAG_GET_MIN | UVC_CTRL_FLAG_GET_MAX |
> - UVC_CTRL_FLAG_GET_DEF | UVC_CTRL_FLAG_SET_CUR |
> - UVC_CTRL_FLAG_AUTO_UPDATE },
> - };
> -
> - unsigned int i;
> -
> - for (i = 0; i < ARRAY_SIZE(fixups); ++i) {
> - if (!usb_match_one_id(dev->intf, &fixups[i].id))
> - continue;
> -
> - if (fixups[i].entity == ctrl->entity->id &&
> - fixups[i].selector == info->selector) {
> - info->flags = fixups[i].flags;
> - return;
> - }
> - }
> -}
> -
> /*
> * Query control information (size and flags) for XU controls.
> */
> @@ -2972,8 +2983,6 @@ static int uvc_ctrl_fill_xu_info(struct uvc_device *dev,
> goto done;
> }
>
> - uvc_ctrl_fixup_xu_info(dev, ctrl, info);
> -
> uvc_dbg(dev, CONTROL,
> "XU control %pUl/%u queried: len %u, flags { get %u set %u auto %u }\n",
> info->entity, info->selector, info->size,
> --
> 2.43.0
>
--
Ricardo Ribalda
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-09-02 6:38 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-02 0:25 [PATCH v2 0/3] media: uvcvideo: live pan/tilt/zoom readback on the OBSBOT Tiny 2 Michael Jordan
2026-09-02 0:25 ` [PATCH v2 1/3] media: uvcvideo: report AUTO_UPDATE controls as volatile Michael Jordan
2026-09-02 0:25 ` [PATCH v2 2/3] media: uvcvideo: generalise the XU flags fixup to all controls Michael Jordan
2026-09-02 6:38 ` Ricardo Ribalda
2026-09-02 0:25 ` [PATCH v2 3/3] media: uvcvideo: fix up missing AUTO_UPDATE on the OBSBOT Tiny 2 Michael Jordan
2026-09-02 6:34 ` Ricardo Ribalda
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox