Skip to content

Commit 91382e7

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 baf901e commit 91382e7

File tree

1 file changed

+390
-0
lines changed

1 file changed

+390
-0
lines changed

subsys/usb/host/class/usbh_uvc.c

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

0 commit comments

Comments
 (0)