Skip to content

Commit c960560

Browse files
author
Josuah Demangeon
committed
tests: usb: host: test basic API functionality
Add tests making sure the USB Host class APIs introduced build and run as expected. Signed-off-by: Josuah Demangeon <[email protected]>
1 parent 474238e commit c960560

19 files changed

+594
-0
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Copyright (c) 2025 Nordic Semiconductor ASA
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
cmake_minimum_required(VERSION 3.20.0)
5+
6+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
7+
project(test_usb_host)
8+
9+
target_include_directories(app PRIVATE ${ZEPHYR_BASE}/subsys/usb/host)
10+
target_include_directories(app PRIVATE ${ZEPHYR_BASE}/tests/subsys/usb/host/common/include)
11+
12+
target_sources(app PRIVATE src/main.c)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
CONFIG_SYS_CLOCK_TICKS_PER_SEC=1000000
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* Copyright (c) 2023 Nordic Semiconductor ASA
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
/delete-node/ &zephyr_udc0;
8+
9+
/ {
10+
zephyr_uhc0: uhc_vrt0 {
11+
compatible = "zephyr,uhc-virtual";
12+
13+
zephyr_udc0: udc_vrt0 {
14+
compatible = "zephyr,udc-virtual";
15+
num-bidir-endpoints = <8>;
16+
maximum-speed = "high-speed";
17+
};
18+
};
19+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
CONFIG_SYS_CLOCK_TICKS_PER_SEC=1000000
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/*
2+
* Copyright (c) 2025 Nordic Semiconductor ASA
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include "native_sim.overlay"
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*
2+
* Copyright (c) 2023 Nordic Semiconductor ASA
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
/ {
8+
zephyr_uhc0: uhc_vrt0 {
9+
compatible = "zephyr,uhc-virtual";
10+
11+
zephyr_udc0: udc_vrt0 {
12+
compatible = "zephyr,udc-virtual";
13+
num-bidir-endpoints = <8>;
14+
maximum-speed = "high-speed";
15+
};
16+
};
17+
};
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Copyright (c) 2025 Nordic Semiconductor ASA
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
CONFIG_LOG=y
5+
CONFIG_ZTEST=y
6+
7+
CONFIG_USB_HOST_STACK=y
8+
CONFIG_UHC_DRIVER=y
Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
/*
2+
* Copyright (c) 2025 Nordic Semiconductor ASA
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <zephyr/ztest.h>
8+
#include <zephyr/usb/usbh.h>
9+
#include <zephyr/usb/usbh.h>
10+
11+
#include "usbh_ch9.h"
12+
#include "usbh_class_api.h"
13+
#include "usbh_desc.h"
14+
#include "usbh_host.h"
15+
#include "usbh_class.h"
16+
17+
#include "test_descriptor.h"
18+
19+
#include <zephyr/logging/log.h>
20+
LOG_MODULE_REGISTER(usb_test, LOG_LEVEL_DBG);
21+
22+
USBH_CONTROLLER_DEFINE(uhs_ctx, DEVICE_DT_GET(DT_NODELABEL(zephyr_uhc0)));
23+
24+
#define TEST_VID 0x2FE3
25+
#define TEST_PID 0x0002
26+
27+
static const uint8_t test_hub_descriptor[] = {
28+
TEST_HUB_DESCRIPTOR
29+
};
30+
31+
struct usb_device test_udev = {
32+
.cfg_desc = (const void *)test_hub_descriptor,
33+
};
34+
35+
/* Private class data, here just an integer but usually a custom struct. */
36+
struct test_class_priv {
37+
enum {
38+
/* Test value stored before the class is initialized */
39+
TEST_CLASS_PRIV_INACTIVE,
40+
/* Test value stored after the class is initialized */
41+
TEST_CLASS_PRIV_IDLE,
42+
/* Test value stored after the class is probed */
43+
TEST_CLASS_PRIV_ENABLED,
44+
/* Test value stored after the class is initialized */
45+
TEST_CLASS_PRIV_INITIALIZED,
46+
} state;
47+
};
48+
49+
static struct test_class_priv test_class_priv = {
50+
.state = TEST_CLASS_PRIV_INACTIVE,
51+
};
52+
53+
static int test_class_init(struct usbh_class_data *const c_data,
54+
struct usbh_context *const uhs_ctx)
55+
{
56+
struct test_class_priv *priv = c_data->priv;
57+
58+
LOG_DBG("initializing %p, priv value 0x%x", c_data, *priv);
59+
60+
zassert_equal(priv->state, TEST_CLASS_PRIV_INACTIVE,
61+
"Class should be initialized only once");
62+
63+
priv->state = TEST_CLASS_PRIV_IDLE;
64+
65+
return 0;
66+
}
67+
68+
static int test_class_completion_cb(struct usbh_class_data *const c_data,
69+
struct uhc_transfer *const xfer)
70+
{
71+
struct test_class_priv *priv = c_data->priv;
72+
73+
LOG_DBG("completion callback for %p, transfer %p", c_data, xfer);
74+
75+
zassert_equal(priv->state, TEST_CLASS_PRIV_ENABLED);
76+
77+
return -ENOTSUP;
78+
}
79+
80+
static int test_class_probe(struct usbh_class_data *const c_data,
81+
struct usb_device *const udev,
82+
const uint8_t iface)
83+
{
84+
struct test_class_priv *priv = c_data->priv;
85+
const struct usb_desc_header *desc = (const struct usb_desc_header *)test_hub_descriptor;
86+
const void *const desc_end = test_hub_descriptor + sizeof(test_hub_descriptor);
87+
const struct usb_if_descriptor *if_desc;
88+
89+
zassert_equal(priv->state, TEST_CLASS_PRIV_IDLE);
90+
91+
desc = usbh_desc_get_by_iface(desc, desc_end, iface);
92+
if (desc == NULL) {
93+
return -ENOENT;
94+
}
95+
96+
if (desc->bDescriptorType != USB_DESC_INTERFACE) {
97+
return -ENOTSUP;
98+
}
99+
100+
if_desc = (const struct usb_if_descriptor *)desc;
101+
if (if_desc->bInterfaceClass != USB_HUB_CLASSCODE ||
102+
if_desc->bInterfaceSubClass != 0x00) {
103+
return -ENOTSUP;
104+
}
105+
106+
priv->state = TEST_CLASS_PRIV_ENABLED;
107+
108+
return 0;
109+
}
110+
111+
static int test_class_removed(struct usbh_class_data *const c_data)
112+
{
113+
struct test_class_priv *priv = c_data->priv;
114+
115+
zassert_equal(priv->state, TEST_CLASS_PRIV_ENABLED);
116+
117+
priv->state = TEST_CLASS_PRIV_IDLE;
118+
119+
return 0;
120+
}
121+
122+
static struct usbh_class_api test_class_api = {
123+
.init = &test_class_init,
124+
.completion_cb = &test_class_completion_cb,
125+
.probe = &test_class_probe,
126+
.removed = &test_class_removed,
127+
};
128+
129+
struct usbh_class_filter test_filters[] = {
130+
{
131+
.class = 9,
132+
.sub = 0,
133+
.proto = 1,
134+
.flags = USBH_CLASS_MATCH_CODE_TRIPLE,
135+
},
136+
{
137+
.vid = TEST_VID,
138+
.pid = TEST_PID,
139+
.flags = USBH_CLASS_MATCH_VID_PID,
140+
},
141+
{0},
142+
};
143+
144+
struct usbh_class_filter test_empty_filters[] = {
145+
{0},
146+
};
147+
148+
/* Define a class used in the tests */
149+
USBH_DEFINE_CLASS(test_class, &test_class_api, &test_class_priv, test_filters);
150+
151+
ZTEST(host_class, test_class_matchiing)
152+
{
153+
struct usb_device *udev = &test_udev;
154+
const struct usb_desc_header *desc = usbh_desc_get_cfg(udev);
155+
const void *const desc_end = usbh_desc_get_cfg_end(udev);
156+
struct usbh_class_filter info_ok = {
157+
.vid = TEST_VID,
158+
.pid = TEST_PID,
159+
};
160+
struct usbh_class_filter info_wrong_vid = {
161+
.vid = TEST_VID + 1,
162+
.pid = TEST_PID,
163+
};
164+
struct usbh_class_filter info_wrong_pid = {
165+
.vid = TEST_VID,
166+
.pid = TEST_PID + 1,
167+
};
168+
int ret;
169+
170+
zassert(usbh_class_is_matching(test_filters, &info_ok),
171+
"Filtering on valid VID:PID should match");
172+
173+
zassert(usbh_class_is_matching(NULL, &info_ok),
174+
"Filtering on NULL rules should match");
175+
176+
zassert(!usbh_class_is_matching(test_empty_filters, &info_ok),
177+
"Filtering on NULL rules should not match");
178+
179+
zassert(!usbh_class_is_matching(test_filters, &info_wrong_vid),
180+
"Filtering on invalid VID only should not match");
181+
182+
zassert(!usbh_class_is_matching(test_filters, &info_wrong_pid),
183+
"Filtering on invalid PID only should not match");
184+
185+
desc = usbh_desc_get_next_function(desc, desc_end);
186+
zassert_not_null(desc, "There should be at least a function descriptor");
187+
188+
ret = usbh_desc_get_iface_info(desc, &info_ok, 0);
189+
zassert_ok(ret, "Expecting the class info to be found");
190+
191+
ret = usbh_desc_get_iface_info(desc, &info_wrong_pid, 0);
192+
zassert_ok(ret, "Expecting the class info to be found");
193+
194+
ret = usbh_desc_get_iface_info(desc, &info_wrong_vid, 0);
195+
zassert_ok(ret, "Expecting the class info to be found");
196+
197+
zassert(usbh_class_is_matching(test_filters, &info_ok),
198+
"Filteringon valid VID:PID and code triple should match");
199+
200+
zassert(usbh_class_is_matching(test_filters, &info_wrong_vid),
201+
"Filtering on code triple only should match");
202+
203+
zassert(usbh_class_is_matching(test_filters, &info_wrong_pid),
204+
"Filtering on code triple only should match");
205+
}
206+
207+
ZTEST(host_class, test_class_fake_device)
208+
{
209+
struct usbh_class_data *c_data = test_class.c_data;
210+
struct usb_device *udev = &test_udev;
211+
struct test_class_priv *priv = c_data->priv;
212+
int ret;
213+
214+
zassert_equal(priv->state, TEST_CLASS_PRIV_IDLE,
215+
"The class should have been initialized by usbh_init()");
216+
217+
ret = usbh_class_probe(c_data, udev, 1);
218+
zassert_equal(ret, -ENOENT, "There is no interface 1 so should be rejected");
219+
zassert_equal(priv->state, TEST_CLASS_PRIV_IDLE,
220+
"The class should not be enabled if probing failed");
221+
222+
ret = usbh_class_probe(c_data, udev, 0);
223+
zassert_ok(ret, "The interface 0 should match in this example class (%s)", strerror(-ret));
224+
zassert_equal(priv->state, TEST_CLASS_PRIV_ENABLED,
225+
"The class should be enabled if probing succeeded");
226+
227+
ret = usbh_class_removed(c_data);
228+
zassert_ok(ret, "Removing the class after probing it should succeed");
229+
zassert_equal(priv->state, TEST_CLASS_PRIV_IDLE,
230+
"The class should be back to inactive ");
231+
232+
ret = usbh_class_probe(c_data, udev, 0);
233+
zassert_ok(ret, "Probing the class again should succeed");
234+
zassert_equal(priv->state, TEST_CLASS_PRIV_ENABLED,
235+
"The class should be back to active ");
236+
}
237+
238+
static void *usb_test_enable(void)
239+
{
240+
int ret;
241+
242+
ret = usbh_init(&uhs_ctx);
243+
zassert_ok(ret, "Failed to initialize USB host (%s)", strerror(-ret));
244+
245+
ret = usbh_enable(&uhs_ctx);
246+
zassert_ok(ret, "Failed to enable USB host (%s)", strerror(-ret));
247+
248+
ret = uhc_bus_reset(uhs_ctx.dev);
249+
zassert_ok(ret, "Failed to signal bus reset (%s)", strerror(-ret));
250+
251+
ret = uhc_bus_resume(uhs_ctx.dev);
252+
zassert_ok(ret, "Failed to signal bus resume (%s)", strerror(-ret));
253+
254+
ret = uhc_sof_enable(uhs_ctx.dev);
255+
zassert_ok(ret, "Failed to enable SoF generator (%s)", strerror(-ret));
256+
257+
LOG_INF("Host controller enabled");
258+
259+
/* Allow the host time to reset the host. */
260+
k_msleep(200);
261+
262+
return NULL;
263+
}
264+
265+
static void usb_test_shutdown(void *f)
266+
{
267+
int ret;
268+
269+
ret = usbh_disable(&uhs_ctx);
270+
zassert_ok(ret, "Failed to enable host support (%s)", strerror(-ret));
271+
272+
ret = usbh_shutdown(&uhs_ctx);
273+
zassert_ok(ret, "Failed to shutdown host support (%s)", strerror(-ret));
274+
275+
LOG_INF("Host controller disabled");
276+
}
277+
278+
ZTEST_SUITE(host_class, NULL, usb_test_enable, NULL, NULL, usb_test_shutdown);
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
tests:
2+
usb.host.class_api:
3+
platform_allow:
4+
- native_sim
5+
- native_sim/native/64
6+
- qemu_cortex_m3
7+
integration_platforms:
8+
- native_sim
9+
tags: usb
10+
usb.host.class_api.build_all:
11+
platform_allow:
12+
- native_sim
13+
- native_sim/native/64
14+
integration_platforms:
15+
- native_sim
16+
tags: usb
17+
build_only: true

0 commit comments

Comments
 (0)