Skip to content

Commit 4617ffc

Browse files
Josuah DemangeonAidenHu
andcommitted
usb: host: introduce usbh_class with init/remove functions
Add functions to probe/remove all classes as part of a new usbh_class.c and a matching usbh_class.h. These functions are called from the function usbh_init_device_intl() in usbh_core.c to initialize every class upon connection of a device. Every class driver provide filters to match the interfaces of the device. Co-authored-by: Aiden Hu <[email protected]> Signed-off-by: Josuah Demangeon <[email protected]>
1 parent a071c98 commit 4617ffc

File tree

8 files changed

+690
-22
lines changed

8 files changed

+690
-22
lines changed

include/zephyr/usb/usbh.h

Lines changed: 68 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/*
2-
* Copyright (c) 2022 Nordic Semiconductor ASA
2+
* Copyright (c) 2025 Nordic Semiconductor ASA
3+
* Copyright 2025 NXP
34
*
45
* SPDX-License-Identifier: Apache-2.0
56
*/
@@ -17,6 +18,7 @@
1718
#include <stdint.h>
1819
#include <zephyr/device.h>
1920
#include <zephyr/net_buf.h>
21+
#include <zephyr/sys/util.h>
2022
#include <zephyr/sys/dlist.h>
2123
#include <zephyr/sys/bitarray.h>
2224
#include <zephyr/drivers/usb/uhc.h>
@@ -72,20 +74,26 @@ struct usbh_context {
7274
.addr_ba = &ba_##device_name, \
7375
}
7476

77+
struct usbh_class_data;
78+
7579
/**
76-
* @brief USB Class Code triple
80+
* @brief Information about a device, which is relevant for matching a particular class.
7781
*/
78-
struct usbh_code_triple {
79-
/** Device Class Code */
80-
uint8_t dclass;
81-
/** Class Subclass Code */
82+
struct usbh_class_filter {
83+
/** Vendor ID */
84+
uint16_t vid;
85+
/** Product ID */
86+
uint16_t pid;
87+
/** Class Code */
88+
uint8_t class;
89+
/** Subclass Code */
8290
uint8_t sub;
83-
/** Class Protocol Code */
91+
/** Protocol Code */
8492
uint8_t proto;
93+
/** Flags that tell which field to match */
94+
uint8_t flags;
8595
};
8696

87-
struct usbh_class_data;
88-
8997
/**
9098
* @brief USB host class instance API
9199
*/
@@ -123,10 +131,59 @@ struct usbh_class_data {
123131
};
124132

125133
/**
134+
* @cond INTERNAL_HIDDEN
135+
*
136+
* Internal state of an USB class. Not corresponding to an USB protocol state,
137+
* but instead software life cycle.
126138
*/
127-
#define USBH_DEFINE_CLASS(name) \
128-
static STRUCT_SECTION_ITERABLE(usbh_class_data, name)
139+
enum usbh_class_state {
140+
/** The class is available to be associated to an USB device function. */
141+
USBH_CLASS_STATE_IDLE,
142+
/** The class got bound to an USB function of a particular device on the bus. */
143+
USBH_CLASS_STATE_BOUND,
144+
/** The class failed to initialize and cannot be used. */
145+
USBH_CLASS_STATE_ERROR,
146+
};
147+
/* @endcond */
129148

149+
/**
150+
* @cond INTERNAL_HIDDEN
151+
*
152+
* Variables used by the USB host stack but not exposed to the class
153+
* through the class API.
154+
*/
155+
struct usbh_class_node {
156+
/** Class information exposed to host class implementations (drivers). */
157+
struct usbh_class_data *const c_data;
158+
/** Filter rules to match this USB host class instance against a device class **/
159+
struct usbh_class_filter *filters;
160+
/** State of the USB class instance */
161+
enum usbh_class_state state;
162+
};
163+
/* @endcond */
164+
165+
/**
166+
* @brief Define USB host support class data
167+
*
168+
* Macro defines class (function) data, as well as corresponding node
169+
* structures used internally by the stack.
170+
*
171+
* @param[in] class_name Class name
172+
* @param[in] class_api Pointer to struct usbh_class_api
173+
* @param[in] class_priv Class private data
174+
* @param[in] filt Array of @ref usbh_class_filter to match this class or NULL to match everything.
175+
* When non-NULL, the it has to be terminated by an entry with @c flags set to 0.
176+
*/
177+
#define USBH_DEFINE_CLASS(class_name, class_api, class_priv, filt) \
178+
static struct usbh_class_data UTIL_CAT(class_data_, class_name) = { \
179+
.name = STRINGIFY(class_name), \
180+
.api = class_api, \
181+
.priv = class_priv, \
182+
}; \
183+
static STRUCT_SECTION_ITERABLE(usbh_class_node, class_name) = { \
184+
.c_data = &UTIL_CAT(class_data_, class_name), \
185+
.filters = filt, \
186+
};
130187

131188
/**
132189
* @brief Initialize the USB host support;

subsys/usb/host/CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ zephyr_library()
55
zephyr_library_include_directories(${CMAKE_CURRENT_SOURCE_DIR})
66

77
zephyr_library_sources(
8+
usbh_api.c
89
usbh_ch9.c
10+
usbh_class.c
911
usbh_core.c
10-
usbh_api.c
12+
usbh_desc.c
1113
usbh_device.c
1214
)
1315

subsys/usb/host/usbh_class.c

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
/*
2+
* Copyright (c) 2025 Nordic Semiconductor ASA
3+
* Copyright 2025 NXP
4+
*
5+
* SPDX-License-Identifier: Apache-2.0
6+
*/
7+
8+
#include <zephyr/usb/usbh.h>
9+
#include <zephyr/logging/log.h>
10+
11+
#include "usbh_class.h"
12+
#include "usbh_class_api.h"
13+
#include "usbh_desc.h"
14+
#include "usbh_host.h"
15+
16+
LOG_MODULE_REGISTER(usbh_class, CONFIG_USBH_LOG_LEVEL);
17+
18+
void usbh_class_init_all(struct usbh_context *const uhs_ctx)
19+
{
20+
int ret;
21+
22+
usbh_host_lock(uhs_ctx);
23+
24+
STRUCT_SECTION_FOREACH(usbh_class_node, c_node) {
25+
struct usbh_class_data *const c_data = c_node->c_data;
26+
27+
if (c_node->state != USBH_CLASS_STATE_IDLE) {
28+
LOG_DBG("Skipping '%s' in state %u", c_data->name, c_node->state);
29+
continue;
30+
}
31+
32+
ret = usbh_class_init(c_data, uhs_ctx);
33+
if (ret != 0) {
34+
LOG_WRN("Failed to initialize class %s (%d)", c_data->name, ret);
35+
c_node->state = USBH_CLASS_STATE_ERROR;
36+
}
37+
}
38+
39+
usbh_host_unlock(uhs_ctx);
40+
}
41+
42+
void usbh_class_remove_all(struct usb_device *const udev)
43+
{
44+
int ret;
45+
46+
k_mutex_lock(&udev->mutex, K_FOREVER);
47+
48+
STRUCT_SECTION_FOREACH(usbh_class_node, c_node) {
49+
struct usbh_class_data *const c_data = c_node->c_data;
50+
51+
if (c_data->udev == udev) {
52+
ret = usbh_class_removed(c_data);
53+
if (ret != 0) {
54+
LOG_ERR("Failed to handle device removal for each class (%d)", ret);
55+
c_node->state = USBH_CLASS_STATE_ERROR;
56+
continue;
57+
}
58+
59+
/* The class instance is now free to bind to a new device */
60+
c_data->udev = NULL;
61+
c_node->state = USBH_CLASS_STATE_IDLE;
62+
}
63+
}
64+
65+
k_mutex_unlock(&udev->mutex);
66+
}
67+
68+
/*
69+
* Probe an USB device function against all available classes of the system.
70+
*
71+
* Try to match a class from the global list of all system classes, using their filter rules
72+
* and return status to tell if a class matches or not.
73+
*
74+
* The first matched class will stop the loop, and the status will be updated so that classes
75+
* are only matched for a single USB function at a time.
76+
*
77+
* USB functions will only have one class matching, and calling usbh_class_probe_function()
78+
* multiple times consequently has no effect.
79+
*/
80+
static void usbh_class_probe_function(struct usb_device *const udev,
81+
struct usbh_class_filter *const info, const uint8_t iface)
82+
{
83+
int ret;
84+
85+
/* Assumes that udev->mutex is locked */
86+
87+
/* First check if any interface is already bound to this */
88+
STRUCT_SECTION_FOREACH(usbh_class_node, c_node) {
89+
struct usbh_class_data *const c_data = c_node->c_data;
90+
91+
if (c_node->state == USBH_CLASS_STATE_BOUND &&
92+
c_data->udev == udev && c_data->iface == iface) {
93+
LOG_DBG("Interface %u bound to '%s', skipping", iface, c_data->name);
94+
return;
95+
}
96+
}
97+
98+
/* Then try to match this function against all interfaces */
99+
STRUCT_SECTION_FOREACH(usbh_class_node, c_node) {
100+
struct usbh_class_data *const c_data = c_node->c_data;
101+
102+
if (c_node->state != USBH_CLASS_STATE_IDLE) {
103+
LOG_DBG("Class %s already matched, skipping", c_data->name);
104+
continue;
105+
}
106+
107+
if (!usbh_class_is_matching(c_node->filters, info)) {
108+
LOG_DBG("Class %s not matching interface %u", c_data->name, iface);
109+
continue;
110+
}
111+
112+
ret = usbh_class_probe(c_data, udev, iface);
113+
if (ret == -ENOTSUP) {
114+
LOG_DBG("Class %s not supporting this device, skipping", c_data->name);
115+
continue;
116+
}
117+
118+
LOG_INF("Class '%s' matches interface %u", c_data->name, iface);
119+
c_node->state = USBH_CLASS_STATE_BOUND;
120+
c_data->udev = udev;
121+
c_data->iface = iface;
122+
break;
123+
}
124+
}
125+
126+
void usbh_class_probe_device(struct usb_device *const udev)
127+
{
128+
const void *desc_end;
129+
const struct usb_desc_header *desc;
130+
struct usbh_class_filter info;
131+
uint8_t iface;
132+
int ret;
133+
134+
k_mutex_lock(&udev->mutex, K_FOREVER);
135+
136+
desc = usbh_desc_get_cfg(udev);
137+
desc_end = usbh_desc_get_cfg_end(udev);
138+
info.vid = udev->dev_desc.idVendor;
139+
info.pid = udev->dev_desc.idProduct;
140+
141+
while (true) {
142+
desc = usbh_desc_get_next_function(desc, desc_end);
143+
if (desc == NULL) {
144+
break;
145+
}
146+
147+
ret = usbh_desc_get_iface_info(desc, &info, &iface);
148+
if (ret < 0) {
149+
LOG_ERR("Failed to collect class codes for matching interface %u", iface);
150+
continue;
151+
}
152+
153+
usbh_class_probe_function(udev, &info, iface);
154+
}
155+
156+
k_mutex_unlock(&udev->mutex);
157+
}
158+
159+
bool usbh_class_is_matching(struct usbh_class_filter *const filters,
160+
struct usbh_class_filter *const info)
161+
{
162+
/* Make empty filter set match everything (use class_api->probe() only) */
163+
if (filters == NULL) {
164+
return true;
165+
}
166+
167+
/* Try to find a rule that matches completely */
168+
for (size_t i = 0; filters[i].flags != 0; i++) {
169+
const struct usbh_class_filter *filt = &filters[i];
170+
171+
if (filt->flags & USBH_CLASS_MATCH_VID_PID &&
172+
(info->vid != filt->vid || info->pid != filt->pid)) {
173+
continue;
174+
}
175+
176+
if (filt->flags & USBH_CLASS_MATCH_CODE_TRIPLE &&
177+
(info->class != filt->class || info->sub != filt->sub ||
178+
info->proto != filt->proto)) {
179+
continue;
180+
}
181+
182+
/* All the selected filters did match */
183+
return true;
184+
}
185+
186+
/* At the end of the filter table and still no match */
187+
return false;
188+
}

subsys/usb/host/usbh_class.h

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright (c) 2025 Nordic Semiconductor ASA
3+
* Copyright 2025 NXP
4+
*
5+
* SPDX-License-Identifier: Apache-2.0
6+
*/
7+
8+
#ifndef ZEPHYR_INCLUDE_USBH_CLASS_H
9+
#define ZEPHYR_INCLUDE_USBH_CLASS_H
10+
11+
#include <zephyr/usb/usbh.h>
12+
13+
/** Match both the device's vendor ID and product ID */
14+
#define USBH_CLASS_MATCH_VID_PID BIT(1)
15+
16+
/** Match a class/subclass/protocol code triple */
17+
#define USBH_CLASS_MATCH_CODE_TRIPLE BIT(2)
18+
19+
/**
20+
* @brief Match an USB host class (a driver) against a device descriptor.
21+
*
22+
* An empty filter set matches everything.
23+
* This can be used to only rely on @c class_api->probe() return value.
24+
*
25+
* @param[in] filters Array of filter rules to match
26+
* @param[in] device_info Device information filled by this function
27+
*
28+
* @retval true if the USB Device descriptor matches at least one rule.
29+
*/
30+
bool usbh_class_is_matching(struct usbh_class_filter *const filters,
31+
struct usbh_class_filter *const device_info);
32+
33+
/**
34+
* @brief Initialize every class instantiated on the system
35+
*
36+
* @param[in] uhs_ctx USB Host context to pass to the class.
37+
*/
38+
void usbh_class_init_all(struct usbh_context *const uhs_ctx);
39+
40+
/**
41+
* @brief Probe an USB device function against all available classes.
42+
*
43+
* Try to match a class from the global list of all system classes using their filter rules
44+
* and return status to update the state of each matched class.
45+
*
46+
* The first matched class
47+
*
48+
* @param[in] udev USB device to probe.
49+
*/
50+
void usbh_class_probe_device(struct usb_device *const udev);
51+
52+
/**
53+
* @brief Call the device removal handler for every class configured with it
54+
*
55+
* @param[in] udev USB device that got removed.
56+
*/
57+
void usbh_class_remove_all(struct usb_device *const udev);
58+
59+
#endif /* ZEPHYR_INCLUDE_USBH_CLASS_H */

0 commit comments

Comments
 (0)