Skip to content

Commit 1b463d0

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 99175f8 commit 1b463d0

File tree

8 files changed

+717
-22
lines changed

8 files changed

+717
-22
lines changed

include/zephyr/usb/usbh.h

Lines changed: 71 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
*/
@@ -127,10 +135,62 @@ struct usbh_class_data {
127135
};
128136

129137
/**
138+
* @cond INTERNAL_HIDDEN
139+
*
140+
* Internal state of an USB class. Not corresponding to an USB protocol state,
141+
* but instead software life cycle.
130142
*/
131-
#define USBH_DEFINE_CLASS(name) \
132-
static STRUCT_SECTION_ITERABLE(usbh_class_data, name)
143+
enum usbh_class_state {
144+
/** The class is available to be associated to an USB device function. */
145+
USBH_CLASS_STATE_IDLE,
146+
/** The class got bound to an USB function of a particular device on the bus. */
147+
USBH_CLASS_STATE_BOUND,
148+
/** The class failed to initialize and cannot be used. */
149+
USBH_CLASS_STATE_ERROR,
150+
};
151+
/* @endcond */
133152

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

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

0 commit comments

Comments
 (0)