Skip to content

Commit 024edc6

Browse files
author
Josuah Demangeon
committed
usb: host: uvc: loop through every descriptor
Loop through each of the VideoStreaming and VideoControl descriptor to parse them. This is meant as a stub for the purpose of testing the class API. Signed-off-by: Josuah Demangeon <[email protected]>
1 parent d58a22b commit 024edc6

File tree

1 file changed

+391
-0
lines changed

1 file changed

+391
-0
lines changed

subsys/usb/host/class/usbh_uvc.c

Lines changed: 391 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,391 @@
1+
/*
2+
* Copyright (c) 2025 tinyVision.ai Inc.
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#define DT_DRV_COMPAT zephyr_uvc_host
8+
9+
#include <stdlib.h>
10+
11+
#include <zephyr/init.h>
12+
#include <zephyr/devicetree.h>
13+
#include <zephyr/kernel.h>
14+
#include <zephyr/sys/byteorder.h>
15+
#include <zephyr/sys/atomic.h>
16+
#include <zephyr/usb/usbh.h>
17+
#include <zephyr/usb/usb_ch9.h>
18+
#include <zephyr/drivers/usb/udc.h>
19+
#include <zephyr/drivers/video.h>
20+
#include <zephyr/drivers/video-controls.h>
21+
#include <zephyr/logging/log.h>
22+
23+
#include <zephyr/devicetree.h>
24+
#include <zephyr/sys/util.h>
25+
#include <zephyr/usb/usb_ch9.h>
26+
#include <zephyr/usb/class/usb_uvc.h>
27+
28+
#include "usbh_ch9.h"
29+
#include "usbh_class.h"
30+
#include "usbh_desc.h"
31+
#include "usbh_device.h"
32+
33+
#include "../../../drivers/video/video_ctrls.h"
34+
#include "../../../drivers/video/video_device.h"
35+
36+
LOG_MODULE_REGISTER(usbh_uvc, CONFIG_USBH_VIDEO_LOG_LEVEL);
37+
38+
struct usbh_uvc_data {
39+
int todo;
40+
};
41+
42+
const void *usbh_uvc_desc_get_vs_end(const struct usb_if_descriptor *if_desc,
43+
const void *const desc_end)
44+
{
45+
const struct uvc_stream_header_descriptor *const header_desc =
46+
usbh_desc_get_next(if_desc, desc_end);
47+
const void *vs_end;
48+
49+
if (!usbh_desc_size_is_valid(header_desc, sizeof(*header_desc), desc_end) ||
50+
header_desc->bDescriptorType != USB_DESC_CS_INTERFACE ||
51+
(header_desc->bDescriptorSubtype != UVC_VS_OUTPUT_HEADER &&
52+
header_desc->bDescriptorSubtype != UVC_VS_INPUT_HEADER)) {
53+
return NULL;
54+
}
55+
56+
vs_end = (uint8_t *)header_desc + header_desc->wTotalLength;
57+
if (vs_end > desc_end) {
58+
return NULL;
59+
}
60+
61+
return vs_end;
62+
}
63+
64+
const void *usbh_uvc_desc_get_vc_end(const struct usb_if_descriptor *if_desc,
65+
const void *const desc_end)
66+
{
67+
const struct uvc_control_header_descriptor *const header_desc =
68+
usbh_desc_get_next(if_desc, desc_end);
69+
const void *vc_end;
70+
71+
if (!usbh_desc_size_is_valid(header_desc, sizeof(*header_desc), desc_end) ||
72+
header_desc->bDescriptorType != USB_DESC_CS_INTERFACE ||
73+
header_desc->bDescriptorSubtype != UVC_VC_HEADER) {
74+
return NULL;
75+
}
76+
77+
vc_end = (uint8_t *)header_desc + header_desc->wTotalLength;
78+
if (vc_end > desc_end) {
79+
LOG_WRN("vc_end %p > desc_end %p", vc_end, desc_end);
80+
return NULL;
81+
}
82+
83+
return vc_end;
84+
}
85+
86+
static int usbh_uvc_parse_vc_desc(struct usbh_class_data *const c_data,
87+
const void *const desc_beg,
88+
const void *const desc_end)
89+
{
90+
/* Skip the interface descriptor itself */
91+
const struct usb_desc_header *desc = usbh_desc_get_next(desc_beg, desc_end);
92+
93+
for (; desc != NULL; desc = usbh_desc_get_next(desc, desc_end)) {
94+
if (desc->bDescriptorType == USB_DESC_INTERFACE ||
95+
desc->bDescriptorType == USB_DESC_INTERFACE_ASSOC ||
96+
desc->bDescriptorType == 0) {
97+
break;
98+
} else if (desc->bDescriptorType == USB_DESC_CS_INTERFACE) {
99+
const struct uvc_if_descriptor *const if_desc = (void *)desc;
100+
101+
switch (if_desc->bDescriptorSubtype) {
102+
case UVC_VC_HEADER:
103+
LOG_DBG("VideoControl interface: Header");
104+
break;
105+
case UVC_VC_OUTPUT_TERMINAL:
106+
LOG_DBG("VideoControl interface: Output Terminal");
107+
break;
108+
case UVC_VC_INPUT_TERMINAL:
109+
LOG_DBG("VideoControl interface: Input/Camera Terminal");
110+
break;
111+
case UVC_VC_SELECTOR_UNIT:
112+
LOG_DBG("VideoControl interface: Selector Unit");
113+
break;
114+
case UVC_VC_PROCESSING_UNIT:
115+
LOG_DBG("VideoControl interface: Processing Unit");
116+
break;
117+
case UVC_VC_EXTENSION_UNIT:
118+
LOG_DBG("VideoControl interface: Extension Unit");
119+
break;
120+
case UVC_VC_ENCODING_UNIT:
121+
LOG_DBG("VideoControl interface: Encoding Unit");
122+
break;
123+
default:
124+
LOG_WRN("VideoControl interface: unknown subtype %u, skipping",
125+
if_desc->bDescriptorSubtype);
126+
}
127+
} else {
128+
LOG_WRN("VideoControl descriptor: unknown type %u, skipping",
129+
desc->bDescriptorType);
130+
}
131+
}
132+
133+
return 0;
134+
}
135+
136+
static int usbh_uvc_parse_vs_desc(struct usbh_class_data *const c_data,
137+
const void *const desc_beg,
138+
const void *const desc_end)
139+
{
140+
/* Skip the interface descriptor itself */
141+
const struct usb_desc_header *desc = usbh_desc_get_next(desc_beg, desc_end);
142+
143+
for (; desc != NULL; desc = usbh_desc_get_next(desc, desc_end)) {
144+
if (desc->bDescriptorType == USB_DESC_INTERFACE ||
145+
desc->bDescriptorType == USB_DESC_INTERFACE_ASSOC ||
146+
desc->bDescriptorType == 0) {
147+
break;
148+
} else if (desc->bDescriptorType == USB_DESC_CS_INTERFACE) {
149+
const struct uvc_if_descriptor *const if_desc = (void *)desc;
150+
151+
switch (if_desc->bDescriptorSubtype) {
152+
case UVC_VS_INPUT_HEADER:
153+
LOG_DBG("VideoStreaming interface: Input header");
154+
break;
155+
case UVC_VS_OUTPUT_HEADER:
156+
LOG_DBG("VideoStreaming interface: Output header");
157+
break;
158+
case UVC_VS_FORMAT_UNCOMPRESSED:
159+
LOG_DBG("VideoStreaming interface: Uncompressed format");
160+
break;
161+
case UVC_VS_FORMAT_MJPEG:
162+
LOG_DBG("VideoStreaming interface: MJPEG format");
163+
break;
164+
case UVC_VS_FRAME_UNCOMPRESSED:
165+
LOG_DBG("VideoStreaming interface: Uncompressed Frame");
166+
break;
167+
case UVC_VS_FRAME_MJPEG:
168+
LOG_DBG("VideoStreaming interface: MJPEG Frame");
169+
break;
170+
case UVC_VS_COLORFORMAT:
171+
LOG_DBG("VideoStreaming interface: Color");
172+
break;
173+
default:
174+
LOG_DBG("VideoStreaming descriptor: unknown subtype %u, skipping",
175+
if_desc->bDescriptorSubtype);
176+
}
177+
} else if (desc->bDescriptorType == USB_DESC_ENDPOINT) {
178+
const struct usb_ep_descriptor *const ep_desc = (void *)desc;
179+
180+
LOG_DBG("VideoStreaming Endpoint", ep_desc->bEndpointAddress);
181+
} else {
182+
LOG_WRN("VideoStreaming descriptor: unknown type %u, skipping",
183+
desc->bDescriptorType);
184+
}
185+
}
186+
187+
return 0;
188+
}
189+
190+
static int usbh_uvc_probe(struct usbh_class_data *const c_data,
191+
struct usb_device *const udev, uint8_t iface)
192+
{
193+
const void *const desc_beg = usbh_desc_get_cfg_beg(udev);
194+
const void *const desc_end = usbh_desc_get_cfg_end(udev);
195+
const struct usb_association_descriptor *const iad_desc =
196+
usbh_desc_get_by_ifnum(desc_beg, desc_end, iface);
197+
const struct usb_desc_header *desc;
198+
bool has_vc_if = false;
199+
bool has_vs_if = false;
200+
int ret;
201+
202+
/* Filter the interface */
203+
204+
if (iad_desc == NULL) {
205+
LOG_ERR("failed to find interface or interface assocation number %u", iface);
206+
return -ENOSYS;
207+
}
208+
209+
if (iad_desc->bDescriptorType != USB_DESC_INTERFACE_ASSOC) {
210+
LOG_WRN("Interface %u is not a valid %s, skipping", iface, "interface assoc");
211+
return -ENOTSUP;
212+
}
213+
214+
/* Turn the descriptors into Video Interface structures */
215+
216+
desc = (struct usb_desc_header *)iad_desc;
217+
for (int i = 0; i < iad_desc->bInterfaceCount; i++) {
218+
const struct usb_if_descriptor *if_desc;
219+
220+
desc = usbh_desc_get_next(desc, desc_end);
221+
222+
if_desc = (void *)usbh_desc_get_by_type(desc, desc_end, BIT(USB_DESC_INTERFACE));
223+
if (if_desc == NULL) {
224+
LOG_ERR("Not as many interfaces (%u) as announced (%u)",
225+
i, iad_desc->bInterfaceCount);
226+
return -EBADMSG;
227+
}
228+
229+
if (if_desc->bInterfaceClass == USB_BCC_VIDEO &&
230+
if_desc->bInterfaceSubClass == UVC_SC_VIDEOCONTROL) {
231+
const void *vc_end;
232+
233+
if (has_vc_if) {
234+
LOG_WRN("Skipping extra VideoControl interface");
235+
continue;
236+
}
237+
238+
vc_end = usbh_uvc_desc_get_vc_end(if_desc, desc_end);
239+
if (vc_end == NULL) {
240+
LOG_ERR("Invalid VideoControl interface descriptor");
241+
return -EBADMSG;
242+
}
243+
244+
ret = usbh_uvc_parse_vc_desc(c_data, if_desc, vc_end);
245+
if (ret != 0) {
246+
LOG_ERR("Failed to parse VC descriptor");
247+
return ret;
248+
}
249+
250+
has_vc_if = true;
251+
}
252+
253+
if (if_desc->bInterfaceClass == USB_BCC_VIDEO &&
254+
if_desc->bInterfaceSubClass == UVC_SC_VIDEOSTREAMING) {
255+
const void *vs_end;
256+
257+
if (has_vs_if) {
258+
LOG_WRN("Skipping extra VideoStreaming interface");
259+
continue;
260+
}
261+
262+
vs_end = usbh_uvc_desc_get_vs_end(if_desc, desc_end);
263+
if (vs_end == NULL) {
264+
LOG_ERR("Invalid VideoStreaming interface descriptor");
265+
return -EBADMSG;
266+
}
267+
268+
ret = usbh_uvc_parse_vs_desc(c_data, if_desc, vs_end);
269+
if (ret != 0) {
270+
return ret;
271+
}
272+
273+
has_vs_if = true;
274+
}
275+
}
276+
277+
if (!has_vs_if) {
278+
LOG_ERR("No VideoStreaming interface found");
279+
return -EINVAL;
280+
}
281+
282+
if (!has_vc_if) {
283+
LOG_DBG("No VideoControl interface found");
284+
return -EINVAL;
285+
}
286+
287+
LOG_INF("Interface %u associated with UVC class", iface);
288+
289+
return 0;
290+
}
291+
292+
static int usbh_uvc_removed(struct usbh_class_data *const c_data)
293+
{
294+
return 0;
295+
}
296+
297+
static int usbh_uvc_init(struct usbh_class_data *const c_data,
298+
struct usbh_context *const uhs_ctx)
299+
{
300+
return 0;
301+
}
302+
303+
static int usbh_uvc_completion_cb(struct usbh_class_data *const c_data,
304+
struct uhc_transfer *const xfer)
305+
{
306+
return 0;
307+
}
308+
309+
static int usbh_uvc_suspended(struct usbh_class_data *const c_data)
310+
{
311+
return 0;
312+
}
313+
314+
static int usbh_uvc_resumed(struct usbh_class_data *const c_data)
315+
{
316+
return 0;
317+
}
318+
319+
static int usbh_uvc_preinit(const struct device *dev)
320+
{
321+
LOG_DBG("%s", dev->name);
322+
323+
return 0;
324+
}
325+
326+
static struct usbh_class_api uvc_class_api = {
327+
.init = usbh_uvc_init,
328+
.completion_cb = usbh_uvc_completion_cb,
329+
.probe = usbh_uvc_probe,
330+
.removed = usbh_uvc_removed,
331+
.suspended = usbh_uvc_suspended,
332+
.resumed = usbh_uvc_resumed,
333+
};
334+
335+
int usbh_uvc_get_caps(const struct device *const dev, struct video_caps *const caps)
336+
{
337+
return 0;
338+
}
339+
340+
int usbh_uvc_get_format(const struct device *const dev, struct video_format *const fmt)
341+
{
342+
return 0;
343+
}
344+
345+
int usbh_uvc_set_stream(const struct device *const dev, bool enable, enum video_buf_type type)
346+
{
347+
return 0;
348+
}
349+
350+
int usbh_uvc_enqueue(const struct device *const dev, struct video_buffer *const vbuf)
351+
{
352+
return 0;
353+
}
354+
355+
int usbh_uvc_dequeue(const struct device *const dev, struct video_buffer **const vbuf,
356+
k_timeout_t timeout)
357+
{
358+
return 0;
359+
}
360+
361+
static DEVICE_API(video, uvc_video_api) = {
362+
.get_caps = usbh_uvc_get_caps,
363+
.get_format = usbh_uvc_get_format,
364+
.set_stream = usbh_uvc_set_stream,
365+
.enqueue = usbh_uvc_enqueue,
366+
.dequeue = usbh_uvc_dequeue,
367+
};
368+
369+
static struct usbh_class_filter usbh_uvc_filter[] = {
370+
{
371+
.flags = USBH_CLASS_MATCH_CLASS | USBH_CLASS_MATCH_SUB,
372+
.class = USB_BCC_VIDEO,
373+
.sub = UVC_SC_VIDEO_INTERFACE_COLLECTION,
374+
},
375+
};
376+
377+
#define USBH_VIDEO_DT_DEVICE_DEFINE(n) \
378+
struct usbh_uvc_data usbh_uvc_data_##n = { \
379+
}; \
380+
\
381+
USBH_DEFINE_CLASS(uvc_c_data_##n, &uvc_class_api, \
382+
(void *)DEVICE_DT_INST_GET(n)); \
383+
\
384+
DEVICE_DT_INST_DEFINE(n, usbh_uvc_preinit, NULL, \
385+
&usbh_uvc_data_##n, NULL, \
386+
POST_KERNEL, CONFIG_VIDEO_INIT_PRIORITY, \
387+
&uvc_video_api); \
388+
\
389+
VIDEO_DEVICE_DEFINE(uvc_host_##n, DEVICE_DT_INST_GET(n), NULL);
390+
391+
DT_INST_FOREACH_STATUS_OKAY(USBH_VIDEO_DT_DEVICE_DEFINE)

0 commit comments

Comments
 (0)