Skip to content

Commit 104842c

Browse files
author
Josuah Demangeon
committed
tests: usb: uvc: connect USB device and host via UVB
Add a test to run the USB Video Class host support by using the existing Zephyr USB Video Class device support. This allows running implementing the host side from the device side. A draft implementation of UVC is added leveraging this test. Signed-off-by: Josuah Demangeon <[email protected]>
1 parent f178d61 commit 104842c

File tree

10 files changed

+266
-0
lines changed

10 files changed

+266
-0
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Copyright (c) 2025 Nordic Semiconductor ASA
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
description: |
5+
USB Video Class (UVC) host instance.
6+
7+
Each UVC instance added to the USB Host Controller (UHC) node will be visible
8+
as a new camera from Zephyr point of view.
9+
10+
as soon as a camera is connected to USB this device will be usable by the application as a
11+
video device, following the video API.
12+
13+
compatible: "zephyr,uvc-host"
14+
15+
include: base.yaml

subsys/usb/host/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ zephyr_library_sources_ifdef(
1818
usbh_shell.c
1919
)
2020

21+
zephyr_library_sources_ifdef(
22+
CONFIG_USBH_VIDEO_CLASS
23+
class/usbh_uvc.c
24+
)
25+
2126
zephyr_library_sources_ifdef(
2227
CONFIG_USBIP
2328
usbip.c

subsys/usb/host/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,5 +54,6 @@ config USBH_MAX_UHC_MSG
5454
Maximum number of USB host controller events that can be queued.
5555

5656
rsource "Kconfig.usbip"
57+
rsource "class/Kconfig"
5758

5859
endif # USB_HOST_STACK

subsys/usb/host/class/Kconfig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Copyright (c) 2025 Nordic Semiconductor ASA
2+
#
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
rsource "Kconfig.uvc"

subsys/usb/host/class/Kconfig.uvc

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Copyright (c) 2025 Nordic Semiconductor ASA
2+
#
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
config USBH_VIDEO_CLASS
6+
bool "USB Video Class implementation [EXPERIMENTAL]"
7+
depends on DT_HAS_ZEPHYR_UVC_HOST_ENABLED
8+
select EXPERIMENTAL
9+
help
10+
USB Host Video Class (UVC) implementation.
11+
12+
if USBH_VIDEO_CLASS
13+
14+
module = USBH_VIDEO
15+
module-str = usbh uvc
16+
default-count = 1
17+
source "subsys/logging/Kconfig.template.log_config"
18+
19+
endif # USBH_VIDEO_CLASS
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
3+
cmake_minimum_required(VERSION 3.20.0)
4+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
5+
project(uvc_uvb)
6+
7+
# This sample cannot use the common USB sample infrastructure as we do not want
8+
# to initialize the board default `zephyr_udc0` but instead an unrelated `virtual_udc0`.
9+
#include(${ZEPHYR_BASE}/samples/subsys/usb/common/common.cmake)
10+
11+
FILE(GLOB app_sources src/*.c)
12+
target_sources(app PRIVATE ${app_sources})
13+
zephyr_include_directories(${ZEPHYR_BASE}/subsys/usb/host)

tests/subsys/usb/uvc/app.overlay

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright (c) 2025 Nordic Semiconductor ASA
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
/ {
8+
virtual_uhc0: uhc-virtual {
9+
compatible = "zephyr,uhc-virtual";
10+
maximum-speed = "full-speed";
11+
12+
virtual_udc0: udc-virtual {
13+
compatible = "zephyr,udc-virtual";
14+
num-bidir-endpoints = <4>;
15+
maximum-speed = "full-speed";
16+
};
17+
};
18+
19+
uvc_device: uvc-device {
20+
compatible = "zephyr,uvc-device";
21+
};
22+
23+
uvc_host: uvc-host {
24+
compatible = "zephyr,uvc-host";
25+
};
26+
};

tests/subsys/usb/uvc/prj.conf

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
CONFIG_LOG=y
2+
CONFIG_UDC_BUF_POOL_SIZE=2048
3+
CONFIG_UDC_DRIVER_LOG_LEVEL_INF=y
4+
CONFIG_UHC_DRIVER_LOG_LEVEL_INF=y
5+
CONFIG_USBD_VIDEO_CLASS=y
6+
CONFIG_USBD_VIDEO_LOG_LEVEL_INF=y
7+
CONFIG_USBH_LOG_LEVEL_INF=y
8+
CONFIG_USBH_VIDEO_CLASS=y
9+
CONFIG_USBH_VIDEO_LOG_LEVEL_INF=y
10+
CONFIG_USB_DEVICE_STACK_NEXT=y
11+
CONFIG_USB_HOST_STACK=y
12+
CONFIG_UVB_LOG_LEVEL_INF=y
13+
CONFIG_VIDEO=y
14+
CONFIG_VIDEO_LOG_LEVEL_INF=y
15+
CONFIG_ZTEST=y

tests/subsys/usb/uvc/src/main.c

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/*
2+
* Copyright (c) 2025 Nordic Semiconductor ASA
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <zephyr/device.h>
8+
#include <zephyr/logging/log.h>
9+
#include <zephyr/usb/usbd.h>
10+
#include <zephyr/usb/usbh.h>
11+
#include <zephyr/usb/class/usbd_uvc.h>
12+
#include <zephyr/ztest.h>
13+
14+
#include "../../../../../drivers/video/video_common.h"
15+
16+
LOG_MODULE_REGISTER(app, LOG_LEVEL_INF);
17+
18+
/*
19+
* Minimal USB Host initialization.
20+
*/
21+
22+
USBH_CONTROLLER_DEFINE(app_usbh,
23+
DEVICE_DT_GET(DT_NODELABEL(virtual_uhc0)));
24+
25+
struct usbh_context *app_usbh_init_controller(void)
26+
{
27+
int err;
28+
29+
err = usbh_init(&app_usbh);
30+
if (err) {
31+
LOG_ERR("Failed to initialize host support");
32+
return NULL;
33+
}
34+
35+
return &app_usbh;
36+
}
37+
38+
USBD_DEVICE_DEFINE(app_usbd, DEVICE_DT_GET(DT_NODELABEL(virtual_udc0)), 0x2fe3, 0x9999);
39+
USBD_DESC_LANG_DEFINE(app_lang);
40+
USBD_DESC_MANUFACTURER_DEFINE(app_mfr, "Nordic");
41+
USBD_DESC_PRODUCT_DEFINE(app_product, "Virtual UVC device");
42+
USBD_DESC_CONFIG_DEFINE(fs_cfg_desc, "FS Configuration");
43+
USBD_CONFIGURATION_DEFINE(app_fs_config, 0, 200, &fs_cfg_desc);
44+
45+
/*
46+
* Minimal USB Device initialization.
47+
*/
48+
49+
struct usbd_context *app_usbd_init_device(void)
50+
{
51+
int ret;
52+
53+
ret = usbd_add_descriptor(&app_usbd, &app_lang);
54+
if (ret != 0) {
55+
LOG_ERR("Failed to initialize language descriptor (%d)", ret);
56+
return NULL;
57+
}
58+
59+
ret = usbd_add_descriptor(&app_usbd, &app_mfr);
60+
if (ret != 0) {
61+
LOG_ERR("Failed to initialize manufacturer descriptor (%d)", ret);
62+
return NULL;
63+
}
64+
65+
ret = usbd_add_descriptor(&app_usbd, &app_product);
66+
if (ret != 0) {
67+
LOG_ERR("Failed to initialize product descriptor (%d)", ret);
68+
return NULL;
69+
}
70+
71+
ret = usbd_add_configuration(&app_usbd, USBD_SPEED_FS, &app_fs_config);
72+
if (ret != 0) {
73+
LOG_ERR("Failed to add Full-Speed configuration");
74+
return NULL;
75+
}
76+
77+
ret = usbd_register_all_classes(&app_usbd, USBD_SPEED_FS, 1, NULL);
78+
if (ret != 0) {
79+
LOG_ERR("Failed to add register classes");
80+
return NULL;
81+
}
82+
83+
usbd_device_set_code_triple(&app_usbd, USBD_SPEED_FS, USB_BCC_MISCELLANEOUS, 0x02, 0x01);
84+
85+
usbd_self_powered(&app_usbd, USB_SCD_SELF_POWERED);
86+
87+
ret = usbd_init(&app_usbd);
88+
if (ret != 0) {
89+
LOG_ERR("Failed to initialize device support");
90+
return NULL;
91+
}
92+
93+
return &app_usbd;
94+
}
95+
96+
/*
97+
* Test using this host connected to this device via UVB
98+
*/
99+
100+
const struct video_format test_formats[] = {
101+
{.pixelformat = VIDEO_PIX_FMT_YUYV, .width = 640, .height = 480},
102+
{.pixelformat = VIDEO_PIX_FMT_YUYV, .width = 320, .height = 240},
103+
{.pixelformat = VIDEO_PIX_FMT_YUYV, .width = 160, .height = 120},
104+
};
105+
106+
const struct device *const uvc_dev = DEVICE_DT_GET(DT_NODELABEL(uvc_device));
107+
const struct device *const video_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_camera));
108+
109+
ZTEST(usb_uvc, test_virtual_device_virtual_host)
110+
{
111+
struct usbd_context *usbd_ctx;
112+
struct usbh_context *usbh_ctx;
113+
int ret;
114+
115+
uvc_set_video_dev(uvc_dev, video_dev);
116+
117+
for (size_t i = 0; i < ARRAY_SIZE(test_formats); i++) {
118+
struct video_format fmt = test_formats[i];
119+
120+
ret = video_estimate_fmt_size(&fmt);
121+
zassert_ok(ret);
122+
123+
ret = uvc_add_format(uvc_dev, &fmt);
124+
zassert_ok(ret);
125+
}
126+
127+
usbd_ctx = app_usbd_init_device();
128+
zassert_not_null(usbd_ctx, "USB device initialization must succeed");
129+
130+
usbh_ctx = app_usbh_init_controller();
131+
zassert_not_null(usbh_ctx, "USB host initialization must succeed");
132+
133+
ret = usbd_enable(usbd_ctx);
134+
zassert_ok(ret, "USB device error code %d must be 0", ret);
135+
136+
ret = usbh_enable(usbh_ctx);
137+
zassert_ok(ret, "USB host enable error code %d must be 0", ret);
138+
139+
k_sleep(K_MSEC(500));
140+
141+
/* TODO: test the video devices here. */
142+
143+
ret = usbh_disable(usbh_ctx);
144+
zassert_ok(ret, "USB host disable error code %d must be 0", ret);
145+
146+
ret = usbd_disable(usbd_ctx);
147+
zassert_ok(ret, "USB device disable error code %d must be 0", ret);
148+
149+
ret = usbh_shutdown(usbh_ctx);
150+
zassert_ok(ret, "USB host shutdown error code %d must be 0", ret);
151+
152+
ret = usbd_shutdown(usbd_ctx);
153+
zassert_ok(ret, "USB device shutdown error code %d must be 0", ret);
154+
}
155+
156+
ZTEST_SUITE(usb_uvc, NULL, NULL, NULL, NULL, NULL);

tests/subsys/usb/uvc/testcase.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
tests:
2+
usb.uvc:
3+
tags:
4+
- usb
5+
- video
6+
platform_allow:
7+
- native_sim/native
8+
integration_platforms:
9+
- native_sim/native
10+
extra_args:
11+
- platform:native_sim/native:SNIPPET="video-sw-generator"

0 commit comments

Comments
 (0)