Skip to content

Commit f576463

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 fe952ff commit f576463

File tree

8 files changed

+723
-22
lines changed

8 files changed

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

0 commit comments

Comments
 (0)