Skip to content

Commit a8d02cb

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 cb94f8e commit a8d02cb

19 files changed

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

0 commit comments

Comments
 (0)