Skip to content

Commit 2fd3ae8

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 024edc6 commit 2fd3ae8

File tree

10 files changed

+248
-0
lines changed

10 files changed

+248
-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: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
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+
LOG_MODULE_REGISTER(app, LOG_LEVEL_INF);
15+
16+
/*
17+
* Minimal USB Host initialization.
18+
*/
19+
20+
USBH_CONTROLLER_DEFINE(app_usbh,
21+
DEVICE_DT_GET(DT_NODELABEL(virtual_uhc0)));
22+
23+
struct usbh_context *app_usbh_init_controller(void)
24+
{
25+
int err;
26+
27+
err = usbh_init(&app_usbh);
28+
if (err) {
29+
LOG_ERR("Failed to initialize host support");
30+
return NULL;
31+
}
32+
33+
return &app_usbh;
34+
}
35+
36+
USBD_DEVICE_DEFINE(app_usbd, DEVICE_DT_GET(DT_NODELABEL(virtual_udc0)), 0x2fe3, 0x9999);
37+
USBD_DESC_LANG_DEFINE(app_lang);
38+
USBD_DESC_MANUFACTURER_DEFINE(app_mfr, "Nordic");
39+
USBD_DESC_PRODUCT_DEFINE(app_product, "Virtual UVC device");
40+
USBD_DESC_CONFIG_DEFINE(fs_cfg_desc, "FS Configuration");
41+
USBD_CONFIGURATION_DEFINE(app_fs_config, 0, 200, &fs_cfg_desc);
42+
43+
/*
44+
* Minimal USB Device initialization.
45+
*/
46+
47+
struct usbd_context *app_usbd_init_device(void)
48+
{
49+
int ret;
50+
51+
ret = usbd_add_descriptor(&app_usbd, &app_lang);
52+
if (ret != 0) {
53+
LOG_ERR("Failed to initialize language descriptor (%d)", ret);
54+
return NULL;
55+
}
56+
57+
ret = usbd_add_descriptor(&app_usbd, &app_mfr);
58+
if (ret != 0) {
59+
LOG_ERR("Failed to initialize manufacturer descriptor (%d)", ret);
60+
return NULL;
61+
}
62+
63+
ret = usbd_add_descriptor(&app_usbd, &app_product);
64+
if (ret != 0) {
65+
LOG_ERR("Failed to initialize product descriptor (%d)", ret);
66+
return NULL;
67+
}
68+
69+
ret = usbd_add_configuration(&app_usbd, USBD_SPEED_FS, &app_fs_config);
70+
if (ret != 0) {
71+
LOG_ERR("Failed to add Full-Speed configuration");
72+
return NULL;
73+
}
74+
75+
ret = usbd_register_all_classes(&app_usbd, USBD_SPEED_FS, 1, NULL);
76+
if (ret != 0) {
77+
LOG_ERR("Failed to add register classes");
78+
return NULL;
79+
}
80+
81+
usbd_device_set_code_triple(&app_usbd, USBD_SPEED_FS, USB_BCC_MISCELLANEOUS, 0x02, 0x01);
82+
83+
usbd_self_powered(&app_usbd, USB_SCD_SELF_POWERED);
84+
85+
ret = usbd_init(&app_usbd);
86+
if (ret != 0) {
87+
LOG_ERR("Failed to initialize device support");
88+
return NULL;
89+
}
90+
91+
return &app_usbd;
92+
}
93+
94+
/*
95+
* Test using this host connected to this device via UVB
96+
*/
97+
98+
const struct device *const uvc_dev = DEVICE_DT_GET(DT_NODELABEL(uvc_device));
99+
const struct device *const video_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_camera));
100+
101+
ZTEST(usb_uvc, test_virtual_device_virtual_host)
102+
{
103+
struct usbd_context *usbd_ctx;
104+
struct usbh_context *usbh_ctx;
105+
int ret;
106+
107+
uvc_set_video_dev(uvc_dev, video_dev);
108+
109+
usbd_ctx = app_usbd_init_device();
110+
zassert_not_null(usbd_ctx, "USB device initialization must succeed");
111+
112+
usbh_ctx = app_usbh_init_controller();
113+
zassert_not_null(usbh_ctx, "USB host initialization must succeed");
114+
115+
ret = usbd_enable(usbd_ctx);
116+
zassert_ok(ret, "USB device error code %d must be 0", ret);
117+
118+
ret = usbh_enable(usbh_ctx);
119+
zassert_ok(ret, "USB host enable error code %d must be 0", ret);
120+
121+
k_sleep(K_MSEC(500));
122+
123+
/* TODO: test the video devices here. */
124+
125+
ret = usbh_disable(usbh_ctx);
126+
zassert_ok(ret, "USB host disable error code %d must be 0", ret);
127+
128+
ret = usbd_disable(usbd_ctx);
129+
zassert_ok(ret, "USB device disable error code %d must be 0", ret);
130+
131+
ret = usbh_shutdown(usbh_ctx);
132+
zassert_ok(ret, "USB host shutdown error code %d must be 0", ret);
133+
134+
ret = usbd_shutdown(usbd_ctx);
135+
zassert_ok(ret, "USB device shutdown error code %d must be 0", ret);
136+
}
137+
138+
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)