Skip to content

Commit 1a6f1ff

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 7255ac3 commit 1a6f1ff

File tree

8 files changed

+722
-22
lines changed

8 files changed

+722
-22
lines changed

include/zephyr/usb/usbh.h

Lines changed: 70 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
*/
@@ -72,20 +73,26 @@ struct usbh_context {
7273
.addr_ba = &ba_##device_name, \
7374
}
7475

76+
struct usbh_class_data;
77+
7578
/**
76-
* @brief USB Class Code triple
79+
* @brief Information about a device, which is relevant for matching a particular class.
7780
*/
78-
struct usbh_code_triple {
79-
/** Device Class Code */
80-
uint8_t dclass;
81-
/** Class Subclass Code */
81+
struct usbh_class_filter {
82+
/** Vendor ID */
83+
uint16_t vid;
84+
/** Product ID */
85+
uint16_t pid;
86+
/** Class Code */
87+
uint8_t class;
88+
/** Subclass Code */
8289
uint8_t sub;
83-
/** Class Protocol Code */
90+
/** Protocol Code */
8491
uint8_t proto;
92+
/** Flags that tell which field to match */
93+
uint8_t flags;
8594
};
8695

87-
struct usbh_class_data;
88-
8996
/**
9097
* @brief USB host class instance API
9198
*/
@@ -127,10 +134,62 @@ struct usbh_class_data {
127134
};
128135

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

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

135194
/**
136195
* @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: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
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+
STRUCT_SECTION_FOREACH(usbh_class_node, c_node) {
42+
struct usbh_class_data *const c_data = c_node->c_data;
43+
44+
k_mutex_lock(&udev->mutex, K_FOREVER);
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+
k_mutex_unlock(&udev->mutex);
60+
}
61+
}
62+
63+
void usbh_class_probe_function(struct usb_device *const udev,
64+
struct usbh_class_filter *const info, uint8_t iface)
65+
{
66+
int ret;
67+
68+
k_mutex_lock(&udev->mutex, K_FOREVER);
69+
70+
/* First check if any interface is already bound to this */
71+
STRUCT_SECTION_FOREACH(usbh_class_node, c_node) {
72+
struct usbh_class_data *const c_data = c_node->c_data;
73+
74+
if (c_node->state == USBH_CLASS_STATE_BOUND &&
75+
c_data->udev == udev && c_data->iface == iface) {
76+
LOG_DBG("Interface %u bound to '%s', skipping", iface, c_data->name);
77+
return;
78+
}
79+
}
80+
81+
/* Then try to match this function against all interfaces */
82+
STRUCT_SECTION_FOREACH(usbh_class_node, c_node) {
83+
struct usbh_class_data *const c_data = c_node->c_data;
84+
85+
if (c_node->state != USBH_CLASS_STATE_IDLE) {
86+
LOG_DBG("Class %s already matched, skipping", c_data->name);
87+
continue;
88+
}
89+
90+
if (!usbh_class_is_matching(c_node->filters, c_node->num_filters, info)) {
91+
LOG_DBG("Class %s not matching interface %u", c_data->name, iface);
92+
continue;
93+
}
94+
95+
ret = usbh_class_probe(c_data, udev, iface);
96+
if (ret == -ENOTSUP) {
97+
LOG_DBG("Class %s not supporting this device, skipping", c_data->name);
98+
continue;
99+
}
100+
101+
LOG_INF("Class '%s' matches interface %u", c_data->name, iface);
102+
c_node->state = USBH_CLASS_STATE_BOUND;
103+
c_data->udev = udev;
104+
c_data->iface = iface;
105+
break;
106+
}
107+
108+
k_mutex_unlock(&udev->mutex);
109+
}
110+
111+
void usbh_class_probe_device(struct usb_device *const udev)
112+
{
113+
const void *const desc_beg = usbh_desc_get_cfg_beg(udev);
114+
const void *const desc_end = usbh_desc_get_cfg_end(udev);
115+
const struct usb_desc_header *desc = desc_beg;
116+
struct usbh_class_filter info = {
117+
.vid = udev->dev_desc.idVendor,
118+
.pid = udev->dev_desc.idProduct,
119+
};
120+
uint8_t iface;
121+
int ret;
122+
123+
while (true) {
124+
desc = usbh_desc_get_next_function(desc, desc_end);
125+
if (desc == NULL) {
126+
break;
127+
}
128+
129+
ret = usbh_desc_get_iface_info(desc, &info, &iface);
130+
if (ret < 0) {
131+
LOG_ERR("Failed to collect class codes for matching interface %u", iface);
132+
continue;
133+
}
134+
135+
usbh_class_probe_function(udev, &info, iface);
136+
}
137+
}
138+
139+
bool usbh_class_is_matching(struct usbh_class_filter *const filters, size_t num_filters,
140+
struct usbh_class_filter *const info)
141+
{
142+
/* Make empty filter set match everything (use class_api->probe() only) */
143+
if (num_filters == 0) {
144+
return true;
145+
}
146+
147+
/* Try to find a rule that matches completely */
148+
for (int i = 0; i < num_filters; i++) {
149+
const struct usbh_class_filter *filt = &filters[i];
150+
151+
if ((filt->flags & USBH_CLASS_MATCH_VID) &&
152+
info->vid != filt->vid) {
153+
continue;
154+
}
155+
156+
if ((filt->flags & USBH_CLASS_MATCH_PID) &&
157+
info->pid != filt->pid) {
158+
continue;
159+
}
160+
161+
if (filt->flags & USBH_CLASS_MATCH_CLASS &&
162+
info->class != filt->class) {
163+
continue;
164+
}
165+
166+
if ((filt->flags & USBH_CLASS_MATCH_SUB) &&
167+
info->sub != filt->sub) {
168+
continue;
169+
}
170+
171+
if ((filt->flags & USBH_CLASS_MATCH_PROTO) &&
172+
info->proto != filt->proto) {
173+
continue;
174+
}
175+
176+
/* All the selected filters did match */
177+
return true;
178+
}
179+
180+
/* At the end of the filter table and still no match */
181+
return false;
182+
}

0 commit comments

Comments
 (0)