Skip to content

Commit e21942e

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 a5ac3e6 commit e21942e

19 files changed

+622
-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: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
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,
172+
test_filters, ARRAY_SIZE(test_filters));
173+
174+
ZTEST(host_class, test_class_matchiing)
175+
{
176+
struct usb_device *udev = &test_udev;
177+
const void *const desc_beg = usbh_desc_get_cfg_beg(udev);
178+
const void *const desc_end = usbh_desc_get_cfg_end(udev);
179+
const struct usb_desc_header *desc;
180+
struct usbh_class_filter info_ok = {
181+
.vid = TEST_VID,
182+
.pid = TEST_PID,
183+
};
184+
struct usbh_class_filter info_wrong_vid = {
185+
.vid = TEST_VID + 1,
186+
.pid = TEST_PID,
187+
};
188+
struct usbh_class_filter info_wrong_pid = {
189+
.vid = TEST_VID,
190+
.pid = TEST_PID + 1,
191+
};
192+
int ret;
193+
194+
zassert(usbh_class_is_matching(test_filters, ARRAY_SIZE(test_filters), &info_ok),
195+
"Filtering on valid VID:PID should match");
196+
197+
zassert(!usbh_class_is_matching(test_filters, ARRAY_SIZE(test_filters), &info_wrong_vid),
198+
"Filtering on invalid VID only should not match");
199+
200+
zassert(!usbh_class_is_matching(test_filters, ARRAY_SIZE(test_filters), &info_wrong_pid),
201+
"Filtering on invalid PID only should not match");
202+
203+
desc = usbh_desc_get_next_function(desc_beg, desc_end);
204+
zassert_not_null(desc, "There should be at least a function descriptor");
205+
206+
ret = usbh_desc_get_iface_info(desc, &info_ok, 0);
207+
zassert_ok(ret, "Expecting the class info to be found");
208+
209+
ret = usbh_desc_get_iface_info(desc, &info_wrong_pid, 0);
210+
zassert_ok(ret, "Expecting the class info to be found");
211+
212+
ret = usbh_desc_get_iface_info(desc, &info_wrong_vid, 0);
213+
zassert_ok(ret, "Expecting the class info to be found");
214+
215+
zassert(usbh_class_is_matching(test_filters, ARRAY_SIZE(test_filters), &info_ok),
216+
"Filteringon valid VID:PID and code triple should match");
217+
218+
zassert(usbh_class_is_matching(test_filters, ARRAY_SIZE(test_filters), &info_wrong_vid),
219+
"Filtering on code triple only should match");
220+
221+
zassert(usbh_class_is_matching(test_filters, ARRAY_SIZE(test_filters), &info_wrong_pid),
222+
"Filtering on code triple only should match");
223+
}
224+
225+
ZTEST(host_class, test_class_fake_device)
226+
{
227+
struct usbh_class_data *c_data = test_class.c_data;
228+
struct usb_device *udev = &test_udev;
229+
struct test_class_priv *priv = c_data->priv;
230+
int ret;
231+
232+
zassert_equal(priv->state, TEST_CLASS_PRIV_IDLE,
233+
"The class should have been initialized by usbh_init()");
234+
235+
ret = usbh_class_probe(c_data, udev, 1);
236+
zassert_equal(ret, -ENOENT, "There is no interface 1 so should be rejected");
237+
zassert_equal(priv->state, TEST_CLASS_PRIV_IDLE,
238+
"The class should not be enabled if probing failed");
239+
240+
ret = usbh_class_probe(c_data, udev, 0);
241+
zassert_ok(ret, "The interface 0 should match in this example class (%s)", strerror(-ret));
242+
zassert_equal(priv->state, TEST_CLASS_PRIV_ENABLED,
243+
"The class should be enabled if probing succeeded");
244+
245+
ret = usbh_class_suspended(c_data);
246+
zassert_ok(ret, "Susupending the class while it is running should succeed");
247+
zassert_equal(priv->state, TEST_CLASS_PRIV_SUSPENDED,
248+
"The class private state should have been updated");
249+
250+
ret = usbh_class_resumed(c_data);
251+
zassert_ok(ret, "Resuming the class after suspending should succeed");
252+
zassert_equal(priv->state, TEST_CLASS_PRIV_ENABLED,
253+
"The class private state should have been updated");
254+
255+
ret = usbh_class_removed(c_data);
256+
zassert_ok(ret, "Removing the class after probing it should succeed");
257+
zassert_equal(priv->state, TEST_CLASS_PRIV_IDLE,
258+
"The class should be back to inactive ");
259+
260+
ret = usbh_class_probe(c_data, udev, 0);
261+
zassert_ok(ret, "Probing the class again should succeed");
262+
zassert_equal(priv->state, TEST_CLASS_PRIV_ENABLED,
263+
"The class should be back to active ");
264+
}
265+
266+
static void *usb_test_enable(void)
267+
{
268+
int ret;
269+
270+
ret = usbh_init(&uhs_ctx);
271+
zassert_ok(ret, "Failed to initialize USB host (%s)", strerror(-ret));
272+
273+
ret = usbh_enable(&uhs_ctx);
274+
zassert_ok(ret, "Failed to enable USB host (%s)", strerror(-ret));
275+
276+
ret = uhc_bus_reset(uhs_ctx.dev);
277+
zassert_ok(ret, "Failed to signal bus reset (%s)", strerror(-ret));
278+
279+
ret = uhc_bus_resume(uhs_ctx.dev);
280+
zassert_ok(ret, "Failed to signal bus resume (%s)", strerror(-ret));
281+
282+
ret = uhc_sof_enable(uhs_ctx.dev);
283+
zassert_ok(ret, "Failed to enable SoF generator (%s)", strerror(-ret));
284+
285+
LOG_INF("Host controller enabled");
286+
287+
/* Allow the host time to reset the host. */
288+
k_msleep(200);
289+
290+
return NULL;
291+
}
292+
293+
static void usb_test_shutdown(void *f)
294+
{
295+
int ret;
296+
297+
ret = usbh_disable(&uhs_ctx);
298+
zassert_ok(ret, "Failed to enable host support (%s)", strerror(-ret));
299+
300+
ret = usbh_shutdown(&uhs_ctx);
301+
zassert_ok(ret, "Failed to shutdown host support (%s)", strerror(-ret));
302+
303+
LOG_INF("Host controller disabled");
304+
}
305+
306+
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)