Skip to content

Commit 6b6a2d9

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 5e5ce30 commit 6b6a2d9

20 files changed

+666
-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: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright (c) 2025 Nordic Semiconductor ASA
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#ifndef ZEPHYR_TEST_USB_HOST_SAMPLE_DESC_H_
8+
#define ZEPHYR_TEST_USB_HOST_SAMPLE_DESC_H_
9+
10+
#include <stddef.h>
11+
#include <stdint.h>
12+
13+
#define _LE16(n) ((n) & 0xff), ((n) >> 8)
14+
15+
/*
16+
* Obtained with lsusb then verified against the
17+
* USB 2.0 standard's sample HUB descriptor
18+
*/
19+
20+
#define TEST_HUB_DEVICE_DESCRIPTOR \
21+
18, /* bLength */ \
22+
1, /* bDescriptorType */ \
23+
_LE16(0x0200), /* bcdUSB */ \
24+
0x09, /* bDeviceClass */ \
25+
0x00, /* bDeviceSubClass */ \
26+
0x02, /* bDeviceProtocol */ \
27+
64, /* bMaxPacketSize0 */ \
28+
_LE16(0x0bda), /* idVendor */ \
29+
_LE16(0x5411), /* idProduct */ \
30+
_LE16(0x0001), /* bcdDevice */ \
31+
0, /* iManufacturer */ \
32+
0, /* iProduct */ \
33+
0, /* iSerial */ \
34+
1, /* bNumConfigurations */
35+
36+
#define TEST_HUB_CONFIG_DESCRIPTOR \
37+
9, /* bLength */ \
38+
2, /* bDescriptorType */ \
39+
_LE16(0x0029), /* wTotalLength */ \
40+
1, /* bNumInterfaces */ \
41+
1, /* bConfigurationValue */ \
42+
0, /* iConfiguration */ \
43+
0xe0, /* bmAttributes */ \
44+
0, /* MaxPower */
45+
46+
#define TEST_HUB_INTERFACE_ALT0_DESCRIPTOR \
47+
9, /* bLength */ \
48+
4, /* bDescriptorType */ \
49+
0, /* bInterfaceNumber */ \
50+
0, /* bAlternateSetting */ \
51+
1, /* bNumEndpoints */ \
52+
9, /* bInterfaceClass */ \
53+
0, /* bInterfaceSubClass */ \
54+
1, /* bInterfaceProtocol */ \
55+
0, /* iInterface */
56+
57+
#define TEST_HUB_INTERFACE_ALT1_DESCRIPTOR \
58+
9, /* bLength */ \
59+
4, /* bDescriptorType */ \
60+
0, /* bInterfaceNumber */ \
61+
1, /* bAlternateSetting */ \
62+
1, /* bNumEndpoints */ \
63+
9, /* bInterfaceClass */ \
64+
0, /* bInterfaceSubClass */ \
65+
2, /* bInterfaceProtocol */ \
66+
0, /* iInterface */
67+
68+
#define TEST_HUB_ENDPOINT_DESCRIPTOR \
69+
7, /* bLength */ \
70+
5, /* bDescriptorType */ \
71+
0x81, /* bEndpointAddress */ \
72+
0x03, /* bmAttributes */ \
73+
_LE16(1), /* wMaxPacketSize */ \
74+
12, /* bInterval */
75+
76+
#define TEST_HUB_DESCRIPTOR \
77+
TEST_HUB_DEVICE_DESCRIPTOR \
78+
TEST_HUB_CONFIG_DESCRIPTOR \
79+
TEST_HUB_INTERFACE_ALT0_DESCRIPTOR \
80+
TEST_HUB_ENDPOINT_DESCRIPTOR \
81+
TEST_HUB_INTERFACE_ALT1_DESCRIPTOR \
82+
TEST_HUB_ENDPOINT_DESCRIPTOR
83+
84+
#endif /* ZEPHYR_TEST_USB_HOST_SAMPLE_DESC_H_ */
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: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
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+
16+
#include "test_descriptor.h"
17+
18+
#include <zephyr/logging/log.h>
19+
LOG_MODULE_REGISTER(usb_test, LOG_LEVEL_DBG);
20+
21+
USBH_CONTROLLER_DEFINE(uhs_ctx, DEVICE_DT_GET(DT_NODELABEL(zephyr_uhc0)));
22+
23+
static const uint8_t test_hub_descriptor[] = {TEST_HUB_DESCRIPTOR};
24+
struct usb_device test_udev = {};
25+
26+
/* Private class data, here just an integer but usually a custom struct. */
27+
struct test_class_priv {
28+
enum {
29+
/* Test value stored before the class is initialized */
30+
TEST_CLASS_PRIV_INACTIVE,
31+
/* Test value stored after the class is initialized */
32+
TEST_CLASS_PRIV_IDLE,
33+
/* Test value stored after the class is probed */
34+
TEST_CLASS_PRIV_ENABLED,
35+
/* Test value stored after the class is initialized */
36+
TEST_CLASS_PRIV_INITIALIZED,
37+
/* Test value stored after the class is suspended */
38+
TEST_CLASS_PRIV_SUSPENDED,
39+
} state;
40+
};
41+
42+
static struct test_class_priv test_class_priv = {
43+
.state = TEST_CLASS_PRIV_INACTIVE,
44+
};
45+
46+
static int test_class_init(struct usbh_class_data *const c_data,
47+
struct usbh_context *const uhs_ctx)
48+
{
49+
struct test_class_priv *priv = c_data->priv;
50+
51+
LOG_DBG("initializing %p, priv value 0x%x", c_data, *priv);
52+
53+
zassert_equal(priv->state, TEST_CLASS_PRIV_INACTIVE,
54+
"Class should be initialized only once");
55+
56+
priv->state = TEST_CLASS_PRIV_IDLE;
57+
58+
return 0;
59+
}
60+
61+
static int test_class_completion_cb(struct usbh_class_data *const c_data,
62+
struct usb_device *const udev,
63+
struct uhc_transfer *const xfer)
64+
{
65+
struct test_class_priv *priv = c_data->priv;
66+
67+
LOG_DBG("completion callback for %p, transfer %p", c_data, xfer);
68+
69+
zassert_equal(priv->state, TEST_CLASS_PRIV_ENABLED);
70+
71+
return -ENOTSUP;
72+
}
73+
74+
static int test_class_probe(struct usbh_class_data *const c_data,
75+
struct usb_device *const udev,
76+
const uint8_t ifnum)
77+
{
78+
struct test_class_priv *priv = c_data->priv;
79+
const void *const desc_beg = test_hub_descriptor;
80+
const void *const desc_end = test_hub_descriptor + sizeof(test_hub_descriptor);
81+
const struct usb_desc_header *desc = desc_beg;
82+
const struct usb_if_descriptor *if_desc;
83+
84+
zassert_equal(priv->state, TEST_CLASS_PRIV_IDLE);
85+
86+
desc = usbh_desc_get_by_ifnum(desc, desc_end, ifnum);
87+
if (desc == NULL) {
88+
return -ENOENT;
89+
}
90+
91+
if (desc->bDescriptorType != USB_DESC_INTERFACE) {
92+
return -ENOTSUP;
93+
}
94+
95+
if_desc = (struct usb_if_descriptor *)desc;
96+
if (if_desc->bInterfaceClass != USB_HUB_CLASSCODE ||
97+
if_desc->bInterfaceSubClass != 0x00) {
98+
return -ENOTSUP;
99+
}
100+
101+
priv->state = TEST_CLASS_PRIV_ENABLED;
102+
103+
return 0;
104+
}
105+
106+
static int test_class_removed(struct usbh_class_data *const c_data,
107+
struct usb_device *const udev)
108+
{
109+
struct test_class_priv *priv = c_data->priv;
110+
111+
zassert_equal(priv->state, TEST_CLASS_PRIV_ENABLED);
112+
113+
priv->state = TEST_CLASS_PRIV_IDLE;
114+
115+
return 0;
116+
}
117+
118+
static int test_class_suspended(struct usbh_class_data *const c_data,
119+
struct usb_device *const udev)
120+
{
121+
struct test_class_priv *priv = c_data->priv;
122+
123+
zassert_equal(priv->state, TEST_CLASS_PRIV_ENABLED);
124+
125+
priv->state = TEST_CLASS_PRIV_SUSPENDED;
126+
127+
return 0;
128+
}
129+
130+
static int test_class_resumed(struct usbh_class_data *const c_data,
131+
struct usb_device *const udev)
132+
{
133+
struct test_class_priv *priv = c_data->priv;
134+
135+
zassert_equal(priv->state, TEST_CLASS_PRIV_SUSPENDED);
136+
137+
priv->state = TEST_CLASS_PRIV_ENABLED;
138+
139+
return 0;
140+
}
141+
142+
static struct usbh_class_api test_class_api = {
143+
.init = &test_class_init,
144+
.completion_cb = &test_class_completion_cb,
145+
.probe = &test_class_probe,
146+
.removed = &test_class_removed,
147+
.suspended = &test_class_suspended,
148+
.resumed = &test_class_resumed,
149+
};
150+
151+
/* Define a class used in the tests */
152+
USBH_DEFINE_CLASS(test_class, &test_class_api, &test_class_priv);
153+
154+
ZTEST(host_class, test_class_fake_device)
155+
{
156+
struct usbh_class_data *c_data = test_class.c_data;
157+
struct usb_device *udev = &test_udev;
158+
struct test_class_priv *priv = c_data->priv;
159+
int ret;
160+
161+
zassert_equal(priv->state, TEST_CLASS_PRIV_IDLE,
162+
"The class should have been initialized by usbh_init()");
163+
164+
ret = usbh_class_probe(c_data, udev, 1);
165+
zassert_equal(ret, -ENOENT, "There is no interface 1 so should be rejected");
166+
zassert_equal(priv->state, TEST_CLASS_PRIV_IDLE,
167+
"The class should not be enabled if probing failed");
168+
169+
ret = usbh_class_probe(c_data, udev, 0);
170+
zassert_ok(ret, "The interface 0 should match in this example class (%s)", strerror(-ret));
171+
zassert_equal(priv->state, TEST_CLASS_PRIV_ENABLED,
172+
"The class should be enabled if probing succeeded");
173+
174+
ret = usbh_class_suspended(c_data, udev);
175+
zassert_ok(ret, "Susupending the class while it is running should succeed");
176+
zassert_equal(priv->state, TEST_CLASS_PRIV_SUSPENDED,
177+
"The class private state should have been updated");
178+
179+
ret = usbh_class_resumed(c_data, udev);
180+
zassert_ok(ret, "Resuming the class after suspending should succeed");
181+
zassert_equal(priv->state, TEST_CLASS_PRIV_ENABLED,
182+
"The class private state should have been updated");
183+
184+
ret = usbh_class_removed(c_data, udev);
185+
zassert_ok(ret, "Removing the class after probing it should succeed");
186+
zassert_equal(priv->state, TEST_CLASS_PRIV_IDLE,
187+
"The class should be back to inactive ");
188+
189+
ret = usbh_class_probe(c_data, udev, 0);
190+
zassert_ok(ret, "Probing the class again should succeed");
191+
zassert_equal(priv->state, TEST_CLASS_PRIV_ENABLED,
192+
"The class should be back to active ");
193+
194+
}
195+
196+
static void *usb_test_enable(void)
197+
{
198+
int ret;
199+
200+
ret = usbh_init(&uhs_ctx);
201+
zassert_ok(ret, "Failed to initialize USB host (%s)", strerror(-ret));
202+
203+
ret = usbh_enable(&uhs_ctx);
204+
zassert_ok(ret, "Failed to enable USB host (%s)", strerror(-ret));
205+
206+
ret = uhc_bus_reset(uhs_ctx.dev);
207+
zassert_ok(ret, "Failed to signal bus reset (%s)", strerror(-ret));
208+
209+
ret = uhc_bus_resume(uhs_ctx.dev);
210+
zassert_ok(ret, "Failed to signal bus resume (%s)", strerror(-ret));
211+
212+
ret = uhc_sof_enable(uhs_ctx.dev);
213+
zassert_ok(ret, "Failed to enable SoF generator (%s)", strerror(-ret));
214+
215+
LOG_INF("Host controller enabled");
216+
217+
/* Allow the host time to reset the host. */
218+
k_msleep(200);
219+
220+
return NULL;
221+
}
222+
223+
static void usb_test_shutdown(void *f)
224+
{
225+
int ret;
226+
227+
ret = usbh_disable(&uhs_ctx);
228+
zassert_ok(ret, "Failed to enable host support (%s)", strerror(-ret));
229+
230+
ret = usbh_shutdown(&uhs_ctx);
231+
zassert_ok(ret, "Failed to shutdown host support (%s)", strerror(-ret));
232+
233+
LOG_INF("Host controller disabled");
234+
}
235+
236+
ZTEST_SUITE(host_class, NULL, usb_test_enable, NULL, NULL, usb_test_shutdown);

0 commit comments

Comments
 (0)