Skip to content

Commit e43dd89

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 c930b72 commit e43dd89

19 files changed

+621
-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: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
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 = (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+
/* Test value stored after the class is suspended */
47+
TEST_CLASS_PRIV_SUSPENDED,
48+
} state;
49+
};
50+
51+
static struct test_class_priv test_class_priv = {
52+
.state = TEST_CLASS_PRIV_INACTIVE,
53+
};
54+
55+
static int test_class_init(struct usbh_class_data *const c_data,
56+
struct usbh_context *const uhs_ctx)
57+
{
58+
struct test_class_priv *priv = c_data->priv;
59+
60+
LOG_DBG("initializing %p, priv value 0x%x", c_data, *priv);
61+
62+
zassert_equal(priv->state, TEST_CLASS_PRIV_INACTIVE,
63+
"Class should be initialized only once");
64+
65+
priv->state = TEST_CLASS_PRIV_IDLE;
66+
67+
return 0;
68+
}
69+
70+
static int test_class_completion_cb(struct usbh_class_data *const c_data,
71+
struct uhc_transfer *const xfer)
72+
{
73+
struct test_class_priv *priv = c_data->priv;
74+
75+
LOG_DBG("completion callback for %p, transfer %p", c_data, xfer);
76+
77+
zassert_equal(priv->state, TEST_CLASS_PRIV_ENABLED);
78+
79+
return -ENOTSUP;
80+
}
81+
82+
static int test_class_probe(struct usbh_class_data *const c_data,
83+
struct usb_device *const udev,
84+
const uint8_t iface)
85+
{
86+
struct test_class_priv *priv = c_data->priv;
87+
const void *const desc_beg = test_hub_descriptor;
88+
const void *const desc_end = test_hub_descriptor + sizeof(test_hub_descriptor);
89+
const struct usb_desc_header *desc = desc_beg;
90+
const struct usb_if_descriptor *if_desc;
91+
92+
zassert_equal(priv->state, TEST_CLASS_PRIV_IDLE);
93+
94+
desc = usbh_desc_get_by_iface(desc, desc_end, iface);
95+
if (desc == NULL) {
96+
return -ENOENT;
97+
}
98+
99+
if (desc->bDescriptorType != USB_DESC_INTERFACE) {
100+
return -ENOTSUP;
101+
}
102+
103+
if_desc = (struct usb_if_descriptor *)desc;
104+
if (if_desc->bInterfaceClass != USB_HUB_CLASSCODE ||
105+
if_desc->bInterfaceSubClass != 0x00) {
106+
return -ENOTSUP;
107+
}
108+
109+
priv->state = TEST_CLASS_PRIV_ENABLED;
110+
111+
return 0;
112+
}
113+
114+
static int test_class_removed(struct usbh_class_data *const c_data)
115+
{
116+
struct test_class_priv *priv = c_data->priv;
117+
118+
zassert_equal(priv->state, TEST_CLASS_PRIV_ENABLED);
119+
120+
priv->state = TEST_CLASS_PRIV_IDLE;
121+
122+
return 0;
123+
}
124+
125+
static int test_class_suspended(struct usbh_class_data *const c_data)
126+
{
127+
struct test_class_priv *priv = c_data->priv;
128+
129+
zassert_equal(priv->state, TEST_CLASS_PRIV_ENABLED);
130+
131+
priv->state = TEST_CLASS_PRIV_SUSPENDED;
132+
133+
return 0;
134+
}
135+
136+
static int test_class_resumed(struct usbh_class_data *const c_data)
137+
{
138+
struct test_class_priv *priv = c_data->priv;
139+
140+
zassert_equal(priv->state, TEST_CLASS_PRIV_SUSPENDED);
141+
142+
priv->state = TEST_CLASS_PRIV_ENABLED;
143+
144+
return 0;
145+
}
146+
147+
static struct usbh_class_api test_class_api = {
148+
.init = &test_class_init,
149+
.completion_cb = &test_class_completion_cb,
150+
.probe = &test_class_probe,
151+
.removed = &test_class_removed,
152+
.suspended = &test_class_suspended,
153+
.resumed = &test_class_resumed,
154+
};
155+
156+
struct usbh_class_filter test_filters[] = {
157+
{
158+
.class = 9,
159+
.sub = 0,
160+
.proto = 1,
161+
.flags = USBH_CLASS_MATCH_CODE_TRIPLE,
162+
},
163+
{
164+
.vid = TEST_VID,
165+
.pid = TEST_PID,
166+
.flags = USBH_CLASS_MATCH_VID | USBH_CLASS_MATCH_PID,
167+
},
168+
};
169+
170+
/* Define a class used in the tests */
171+
USBH_DEFINE_CLASS(test_class, &test_class_api, &test_class_priv, test_filters);
172+
173+
ZTEST(host_class, test_class_matchiing)
174+
{
175+
struct usb_device *udev = &test_udev;
176+
const void *const desc_beg = usbh_desc_get_cfg_beg(udev);
177+
const void *const desc_end = usbh_desc_get_cfg_end(udev);
178+
const struct usb_desc_header *desc;
179+
struct usbh_class_filter info_ok = {
180+
.vid = TEST_VID,
181+
.pid = TEST_PID,
182+
};
183+
struct usbh_class_filter info_wrong_vid = {
184+
.vid = TEST_VID + 1,
185+
.pid = TEST_PID,
186+
};
187+
struct usbh_class_filter info_wrong_pid = {
188+
.vid = TEST_VID,
189+
.pid = TEST_PID + 1,
190+
};
191+
int ret;
192+
193+
zassert(usbh_class_is_matching(test_filters, &info_ok),
194+
"Filtering on valid VID:PID should match");
195+
196+
zassert(!usbh_class_is_matching(test_filters, &info_wrong_vid),
197+
"Filtering on invalid VID only should not match");
198+
199+
zassert(!usbh_class_is_matching(test_filters, &info_wrong_pid),
200+
"Filtering on invalid PID only should not match");
201+
202+
desc = usbh_desc_get_next_function(desc_beg, desc_end);
203+
zassert_not_null(desc, "There should be at least a function descriptor");
204+
205+
ret = usbh_desc_get_iface_info(desc, &info_ok, 0);
206+
zassert_ok(ret, "Expecting the class info to be found");
207+
208+
ret = usbh_desc_get_iface_info(desc, &info_wrong_pid, 0);
209+
zassert_ok(ret, "Expecting the class info to be found");
210+
211+
ret = usbh_desc_get_iface_info(desc, &info_wrong_vid, 0);
212+
zassert_ok(ret, "Expecting the class info to be found");
213+
214+
zassert(usbh_class_is_matching(test_filters, &info_ok),
215+
"Filteringon valid VID:PID and code triple should match");
216+
217+
zassert(usbh_class_is_matching(test_filters, &info_wrong_vid),
218+
"Filtering on code triple only should match");
219+
220+
zassert(usbh_class_is_matching(test_filters, &info_wrong_pid),
221+
"Filtering on code triple only should match");
222+
}
223+
224+
ZTEST(host_class, test_class_fake_device)
225+
{
226+
struct usbh_class_data *c_data = test_class.c_data;
227+
struct usb_device *udev = &test_udev;
228+
struct test_class_priv *priv = c_data->priv;
229+
int ret;
230+
231+
zassert_equal(priv->state, TEST_CLASS_PRIV_IDLE,
232+
"The class should have been initialized by usbh_init()");
233+
234+
ret = usbh_class_probe(c_data, udev, 1);
235+
zassert_equal(ret, -ENOENT, "There is no interface 1 so should be rejected");
236+
zassert_equal(priv->state, TEST_CLASS_PRIV_IDLE,
237+
"The class should not be enabled if probing failed");
238+
239+
ret = usbh_class_probe(c_data, udev, 0);
240+
zassert_ok(ret, "The interface 0 should match in this example class (%s)", strerror(-ret));
241+
zassert_equal(priv->state, TEST_CLASS_PRIV_ENABLED,
242+
"The class should be enabled if probing succeeded");
243+
244+
ret = usbh_class_suspended(c_data);
245+
zassert_ok(ret, "Susupending the class while it is running should succeed");
246+
zassert_equal(priv->state, TEST_CLASS_PRIV_SUSPENDED,
247+
"The class private state should have been updated");
248+
249+
ret = usbh_class_resumed(c_data);
250+
zassert_ok(ret, "Resuming the class after suspending should succeed");
251+
zassert_equal(priv->state, TEST_CLASS_PRIV_ENABLED,
252+
"The class private state should have been updated");
253+
254+
ret = usbh_class_removed(c_data);
255+
zassert_ok(ret, "Removing the class after probing it should succeed");
256+
zassert_equal(priv->state, TEST_CLASS_PRIV_IDLE,
257+
"The class should be back to inactive ");
258+
259+
ret = usbh_class_probe(c_data, udev, 0);
260+
zassert_ok(ret, "Probing the class again should succeed");
261+
zassert_equal(priv->state, TEST_CLASS_PRIV_ENABLED,
262+
"The class should be back to active ");
263+
}
264+
265+
static void *usb_test_enable(void)
266+
{
267+
int ret;
268+
269+
ret = usbh_init(&uhs_ctx);
270+
zassert_ok(ret, "Failed to initialize USB host (%s)", strerror(-ret));
271+
272+
ret = usbh_enable(&uhs_ctx);
273+
zassert_ok(ret, "Failed to enable USB host (%s)", strerror(-ret));
274+
275+
ret = uhc_bus_reset(uhs_ctx.dev);
276+
zassert_ok(ret, "Failed to signal bus reset (%s)", strerror(-ret));
277+
278+
ret = uhc_bus_resume(uhs_ctx.dev);
279+
zassert_ok(ret, "Failed to signal bus resume (%s)", strerror(-ret));
280+
281+
ret = uhc_sof_enable(uhs_ctx.dev);
282+
zassert_ok(ret, "Failed to enable SoF generator (%s)", strerror(-ret));
283+
284+
LOG_INF("Host controller enabled");
285+
286+
/* Allow the host time to reset the host. */
287+
k_msleep(200);
288+
289+
return NULL;
290+
}
291+
292+
static void usb_test_shutdown(void *f)
293+
{
294+
int ret;
295+
296+
ret = usbh_disable(&uhs_ctx);
297+
zassert_ok(ret, "Failed to enable host support (%s)", strerror(-ret));
298+
299+
ret = usbh_shutdown(&uhs_ctx);
300+
zassert_ok(ret, "Failed to shutdown host support (%s)", strerror(-ret));
301+
302+
LOG_INF("Host controller disabled");
303+
}
304+
305+
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)