From ac1ce3211c3985ebf77829466a3d302ae3d2d4bf Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Fri, 8 Aug 2025 14:13:16 +0000 Subject: [PATCH 1/8] usb: host: add host status to the host context Add a "struct usbh_status" that contains a bitmask of flags to keep track of the global state of the host context, like done for the device_next implementation. Signed-off-by: Josuah Demangeon --- include/zephyr/usb/usbh.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/include/zephyr/usb/usbh.h b/include/zephyr/usb/usbh.h index e2e6f3074000e..b6526af828bc9 100644 --- a/include/zephyr/usb/usbh.h +++ b/include/zephyr/usb/usbh.h @@ -33,6 +33,16 @@ extern "C" { * @{ */ +/** + * USB host support status + */ +struct usbh_status { + /** USB host support is initialized */ + unsigned int initialized : 1; + /** USB host support is enabled */ + unsigned int enabled : 1; +}; + /** * USB host support runtime context */ @@ -43,6 +53,8 @@ struct usbh_context { struct k_mutex mutex; /** Pointer to UHC device struct */ const struct device *dev; + /** Status of the USB host support */ + struct usbh_status status; /** USB device list */ sys_dlist_t udevs; /** USB root device */ From 55fc6504fbc71bb602c5acdd7002f2fc24f777f4 Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Thu, 23 Oct 2025 20:08:48 +0000 Subject: [PATCH 2/8] usb: host: add copyright notice to linker script Add missing copyright notice for the linker script to help with check_compliance.py. Signed-off-by: Josuah Demangeon --- subsys/usb/host/usbh_data.ld | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/subsys/usb/host/usbh_data.ld b/subsys/usb/host/usbh_data.ld index 4363154f29474..c2778d534d2fa 100644 --- a/subsys/usb/host/usbh_data.ld +++ b/subsys/usb/host/usbh_data.ld @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: Copyright The Zephyr Project Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + #include ITERABLE_SECTION_RAM(usbh_context, Z_LINK_ITERABLE_SUBALIGN) From 9bc3cd573cab21ea8e2097581ad1b902f1c852cf Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Thu, 14 Aug 2025 14:45:40 +0000 Subject: [PATCH 3/8] usb: host: class: implement more of the class/data struct fields Add a "struct usbh_class_api" for the host implementation, and move all the function poitners to it. Add more fields to "struct usbh_class_data". Signed-off-by: Josuah Demangeon --- include/zephyr/usb/usbh.h | 51 ++++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/include/zephyr/usb/usbh.h b/include/zephyr/usb/usbh.h index b6526af828bc9..86c50e1a47db7 100644 --- a/include/zephyr/usb/usbh.h +++ b/include/zephyr/usb/usbh.h @@ -84,28 +84,45 @@ struct usbh_code_triple { uint8_t proto; }; +struct usbh_class_data; + /** - * @brief USB host class data and class instance API + * @brief USB host class instance API + */ +struct usbh_class_api { + /** Initialization of the class implementation */ + int (*init)(struct usbh_class_data *const c_data); + /** Request completion event handler */ + int (*request)(struct usbh_class_data *const c_data, + struct uhc_transfer *const xfer, int err); + /** Device connected handler */ + int (*connected)(struct usbh_class_data *const c_data, + void *const desc_start_addr, + void *const desc_end_addr); + /** Device removed handler */ + int (*removed)(struct usbh_class_data *const c_data); + /** Bus remote wakeup handler */ + int (*rwup)(struct usbh_class_data *const c_data); + /** Bus suspended handler */ + int (*suspended)(struct usbh_class_data *const c_data); + /** Bus resumed handler */ + int (*resumed)(struct usbh_class_data *const c_data); +}; + +/** + * @brief USB host class instance data */ struct usbh_class_data { + /** Name of the USB host class instance */ + const char *name; + /** Pointer to USB host stack context structure */ + struct usbh_context *uhs_ctx; /** Class code supported by this instance */ struct usbh_code_triple code; - - /** Initialization of the class implementation */ - /* int (*init)(struct usbh_context *const uhs_ctx); */ - /** Request completion event handler */ - int (*request)(struct usbh_context *const uhs_ctx, - struct uhc_transfer *const xfer, int err); - /** Device connected handler */ - int (*connected)(struct usbh_context *const uhs_ctx); - /** Device removed handler */ - int (*removed)(struct usbh_context *const uhs_ctx); - /** Bus remote wakeup handler */ - int (*rwup)(struct usbh_context *const uhs_ctx); - /** Bus suspended handler */ - int (*suspended)(struct usbh_context *const uhs_ctx); - /** Bus resumed handler */ - int (*resumed)(struct usbh_context *const uhs_ctx); + /** Pointer to host support class API */ + struct usbh_class_api *api; + /** Pointer to private data */ + void *priv; }; /** From eb872341de185f4c30103a2ca249f2049ef75186 Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Fri, 15 Aug 2025 16:10:46 +0000 Subject: [PATCH 4/8] usb: host: introduce wrappers to access the class function pointers Add API wrappers around the function pointers in struct usbh_class_api, while also documenting the USB host class internal API. Signed-off-by: Josuah Demangeon --- include/zephyr/usb/usbh.h | 27 +++--- subsys/usb/host/usbh_class_api.h | 146 +++++++++++++++++++++++++++++++ 2 files changed, 160 insertions(+), 13 deletions(-) create mode 100644 subsys/usb/host/usbh_class_api.h diff --git a/include/zephyr/usb/usbh.h b/include/zephyr/usb/usbh.h index 86c50e1a47db7..7461e1f7e533e 100644 --- a/include/zephyr/usb/usbh.h +++ b/include/zephyr/usb/usbh.h @@ -90,19 +90,18 @@ struct usbh_class_data; * @brief USB host class instance API */ struct usbh_class_api { - /** Initialization of the class implementation */ - int (*init)(struct usbh_class_data *const c_data); + /** Host init handler, before any device is connected */ + int (*init)(struct usbh_class_data *const c_data, + struct usbh_context *const uhs_ctx); /** Request completion event handler */ - int (*request)(struct usbh_class_data *const c_data, - struct uhc_transfer *const xfer, int err); - /** Device connected handler */ - int (*connected)(struct usbh_class_data *const c_data, - void *const desc_start_addr, - void *const desc_end_addr); - /** Device removed handler */ + int (*completion_cb)(struct usbh_class_data *const c_data, + struct uhc_transfer *const xfer); + /** Device connection handler */ + int (*probe)(struct usbh_class_data *const c_data, + struct usb_device *const udev, + const uint8_t iface); + /** Device removal handler */ int (*removed)(struct usbh_class_data *const c_data); - /** Bus remote wakeup handler */ - int (*rwup)(struct usbh_class_data *const c_data); /** Bus suspended handler */ int (*suspended)(struct usbh_class_data *const c_data); /** Bus resumed handler */ @@ -117,8 +116,10 @@ struct usbh_class_data { const char *name; /** Pointer to USB host stack context structure */ struct usbh_context *uhs_ctx; - /** Class code supported by this instance */ - struct usbh_code_triple code; + /** Pointer to USB device this class is used for */ + struct usb_device *udev; + /** Interface number for which this class matched */ + uint8_t iface; /** Pointer to host support class API */ struct usbh_class_api *api; /** Pointer to private data */ diff --git a/subsys/usb/host/usbh_class_api.h b/subsys/usb/host/usbh_class_api.h new file mode 100644 index 0000000000000..4aeabfd3d801a --- /dev/null +++ b/subsys/usb/host/usbh_class_api.h @@ -0,0 +1,146 @@ +/* + * SPDX-FileCopyrightText: Copyright Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief USB host stack class instances API + * + * This file contains the USB host stack class instances API. + */ + +#ifndef ZEPHYR_INCLUDE_USBH_CLASS_API_H +#define ZEPHYR_INCLUDE_USBH_CLASS_API_H + +#include + +/** + * @brief Initialization of the class implementation + * + * This is called for each instance during the initialization phase, + * for every registered class. + * It can be used to initialize underlying systems. + * + * @param[in] c_data Pointer to USB host class data + * @param[in] uhs_ctx USB host context to assign to this class + * @return 0 on success, negative error code on failure. + */ +static inline int usbh_class_init(struct usbh_class_data *const c_data, + struct usbh_context *const uhs_ctx) +{ + const struct usbh_class_api *api = c_data->api; + + if (api->init != NULL) { + return api->init(c_data, uhs_ctx); + } + + return -ENOTSUP; +} + +/** + * @brief Request completion event handler + * + * Called upon completion of a request made by the host to this class. + * + * @param[in] c_data Pointer to USB host class data + * @param[in] xfer Completed transfer + * @return 0 on success, negative error code on failure. + */ +static inline int usbh_class_completion_cb(struct usbh_class_data *const c_data, + struct uhc_transfer *const xfer) +{ + const struct usbh_class_api *api = c_data->api; + + if (api->completion_cb != NULL) { + return api->completion_cb(c_data, xfer); + } + + return -ENOTSUP; +} + +/** + * @brief Device initialization handler + * + * Called when a device is connected to the bus for every device. + * + * @param[in] c_data Pointer to USB host class data + * @param[in] udev USB device connected + * @param[in] iface The @c bInterfaceNumber or @c bFirstInterface of this class + * @return 0 on success, negative error code on failure. + * @return -ENOTSUP if the class is not matching + */ +static inline int usbh_class_probe(struct usbh_class_data *const c_data, + struct usb_device *const udev, + const uint8_t iface) +{ + const struct usbh_class_api *api = c_data->api; + + if (api->probe != NULL) { + return api->probe(c_data, udev, iface); + } + + return -ENOTSUP; +} + +/** + * @brief Device removed handler + * + * Called when the device is removed from the bus + * and it matches the class filters of this instance. + * + * @param[in] c_data Pointer to USB host class data + * @return 0 on success, negative error code on failure. + */ +static inline int usbh_class_removed(struct usbh_class_data *const c_data) +{ + const struct usbh_class_api *api = c_data->api; + + if (api->removed != NULL) { + return api->removed(c_data); + } + + return -ENOTSUP; +} + +/** + * @brief Bus suspended handler + * + * Called when the host has suspended the bus. + * It can be used to suspend underlying systems. + * + * @param[in] c_data Pointer to USB host class data + * @return 0 on success, negative error code on failure. + */ +static inline int usbh_class_suspended(struct usbh_class_data *const c_data) +{ + const struct usbh_class_api *api = c_data->api; + + if (api->suspended != NULL) { + return api->suspended(c_data); + } + + return -ENOTSUP; +} + +/** + * @brief Bus resumed handler + * + * Called when the host resumes its activity on the bus. + * It can be used to wake-up underlying systems. + * + * @param[in] c_data Pointer to USB host class data + * @return 0 on success, negative error code on failure. + */ +static inline int usbh_class_resumed(struct usbh_class_data *const c_data) +{ + const struct usbh_class_api *api = c_data->api; + + if (api->resumed != NULL) { + return api->resumed(c_data); + } + + return -ENOTSUP; +} + +#endif /* ZEPHYR_INCLUDE_USBD_CLASS_API_H */ From ff0493fffbf603f21ece270e13f7169921d6fa3d Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Fri, 15 Aug 2025 19:04:59 +0000 Subject: [PATCH 5/8] 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 Signed-off-by: Josuah Demangeon --- include/zephyr/usb/usbh.h | 78 ++++++++++-- subsys/usb/host/CMakeLists.txt | 4 +- subsys/usb/host/usbh_class.c | 197 ++++++++++++++++++++++++++++++ subsys/usb/host/usbh_class.h | 78 ++++++++++++ subsys/usb/host/usbh_core.c | 17 ++- subsys/usb/host/usbh_data.ld | 2 +- subsys/usb/host/usbh_desc.c | 215 +++++++++++++++++++++++++++++++++ subsys/usb/host/usbh_desc.h | 149 +++++++++++++++++++++++ 8 files changed, 718 insertions(+), 22 deletions(-) create mode 100644 subsys/usb/host/usbh_class.c create mode 100644 subsys/usb/host/usbh_class.h create mode 100644 subsys/usb/host/usbh_desc.c create mode 100644 subsys/usb/host/usbh_desc.h diff --git a/include/zephyr/usb/usbh.h b/include/zephyr/usb/usbh.h index 7461e1f7e533e..fc2f23ce10805 100644 --- a/include/zephyr/usb/usbh.h +++ b/include/zephyr/usb/usbh.h @@ -1,5 +1,6 @@ /* - * Copyright (c) 2022 Nordic Semiconductor ASA + * Copyright (c) 2025 Nordic Semiconductor ASA + * Copyright 2025 NXP * * SPDX-License-Identifier: Apache-2.0 */ @@ -17,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -72,20 +74,26 @@ struct usbh_context { .addr_ba = &ba_##device_name, \ } +struct usbh_class_data; + /** - * @brief USB Class Code triple + * @brief Information about a device, which is relevant for matching a particular class. */ -struct usbh_code_triple { - /** Device Class Code */ - uint8_t dclass; - /** Class Subclass Code */ +struct usbh_class_filter { + /** Vendor ID */ + uint16_t vid; + /** Product ID */ + uint16_t pid; + /** Class Code */ + uint8_t class; + /** Subclass Code */ uint8_t sub; - /** Class Protocol Code */ + /** Protocol Code */ uint8_t proto; + /** Flags that tell which field to match */ + uint8_t flags; }; -struct usbh_class_data; - /** * @brief USB host class instance API */ @@ -127,10 +135,58 @@ struct usbh_class_data { }; /** + * @cond INTERNAL_HIDDEN + * + * Internal state of an USB class. Not corresponding to an USB protocol state, + * but instead software life cycle. */ -#define USBH_DEFINE_CLASS(name) \ - static STRUCT_SECTION_ITERABLE(usbh_class_data, name) +enum usbh_class_state { + /** The class is available to be associated to an USB device function. */ + USBH_CLASS_STATE_IDLE, + /** The class got bound to an USB function of a particular device on the bus. */ + USBH_CLASS_STATE_BOUND, + /** The class failed to initialize and cannot be used. */ + USBH_CLASS_STATE_ERROR, +}; +/* @endcond */ +/** + * @cond INTERNAL_HIDDEN + * + * Variables used by the USB host stack but not exposed to the class + * through the class API. + */ +struct usbh_class_node { + /** Class information exposed to host class implementations (drivers). */ + struct usbh_class_data *const c_data; + /** Filter rules to match this USB host class instance against a device class **/ + struct usbh_class_filter *filters; + /** State of the USB class instance */ + enum usbh_class_state state; +}; +/* @endcond */ + +/** + * @brief Define USB host support class data + * + * Macro defines class (function) data, as well as corresponding node + * structures used internally by the stack. + * + * @param[in] class_name Class name + * @param[in] class_api Pointer to struct usbh_class_api + * @param[in] class_priv Class private data + * @param[in] filt Array of @ref usbh_class_filter to match this class or NULL to match everything + */ +#define USBH_DEFINE_CLASS(class_name, class_api, class_priv, filt) \ + static struct usbh_class_data UTIL_CAT(class_data_, class_name) = { \ + .name = STRINGIFY(class_name), \ + .api = class_api, \ + .priv = class_priv, \ + }; \ + static STRUCT_SECTION_ITERABLE(usbh_class_node, class_name) = { \ + .c_data = &UTIL_CAT(class_data_, class_name), \ + .filters = filt, \ + }; /** * @brief Initialize the USB host support; diff --git a/subsys/usb/host/CMakeLists.txt b/subsys/usb/host/CMakeLists.txt index 4e8f7b9d28dc8..cafa3162b3c94 100644 --- a/subsys/usb/host/CMakeLists.txt +++ b/subsys/usb/host/CMakeLists.txt @@ -5,9 +5,11 @@ zephyr_library() zephyr_library_include_directories(${CMAKE_CURRENT_SOURCE_DIR}) zephyr_library_sources( + usbh_api.c usbh_ch9.c + usbh_class.c usbh_core.c - usbh_api.c + usbh_desc.c usbh_device.c ) diff --git a/subsys/usb/host/usbh_class.c b/subsys/usb/host/usbh_class.c new file mode 100644 index 0000000000000..7b26fb2dbc381 --- /dev/null +++ b/subsys/usb/host/usbh_class.c @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * Copyright 2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "usbh_class.h" +#include "usbh_class_api.h" +#include "usbh_desc.h" + +LOG_MODULE_REGISTER(usbh_class, CONFIG_USBH_LOG_LEVEL); + +void usbh_class_init_all(struct usbh_context *const uhs_ctx) +{ + int ret; + + STRUCT_SECTION_FOREACH(usbh_class_node, c_node) { + struct usbh_class_data *const c_data = c_node->c_data; + + if (c_node->state != USBH_CLASS_STATE_IDLE) { + LOG_DBG("Skipping '%s' in state %u", c_data->name, c_node->state); + continue; + } + + ret = usbh_class_init(c_data, uhs_ctx); + if (ret != 0) { + LOG_WRN("Failed to initialize class %s (%d)", c_data->name, ret); + c_node->state = USBH_CLASS_STATE_ERROR; + } + } +} + +void usbh_class_remove_all(struct usb_device *const udev) +{ + int ret; + + k_mutex_lock(&udev->mutex, K_FOREVER); + + STRUCT_SECTION_FOREACH(usbh_class_node, c_node) { + struct usbh_class_data *const c_data = c_node->c_data; + + if (c_data->udev == udev) { + ret = usbh_class_removed(c_data); + if (ret != 0) { + LOG_ERR("Failed to handle device removal for each class (%d)", ret); + c_node->state = USBH_CLASS_STATE_ERROR; + continue; + } + + /* The class instance is now free to bind to a new device */ + c_data->udev = NULL; + c_node->state = USBH_CLASS_STATE_IDLE; + } + } + + k_mutex_unlock(&udev->mutex); +} + +/* + * Probe an USB device function against all available classes of the system. + * + * Try to match a class from the global list of all system classes, using their filter rules + * and return status to tell if a class matches or not. + * + * The first matched class will stop the loop, and the status will be updated so that classes + * are only matched for a single USB function at a time. + * + * USB functions will only have one class matching, and calling usbh_class_probe_function() + * multiple times consequently has no effect. + */ +static void usbh_class_probe_function(struct usb_device *const udev, + struct usbh_class_filter *const info, const uint8_t iface) +{ + int ret; + + /* Assumes that udev->mutex is locked */ + + /* First check if any interface is already bound to this */ + STRUCT_SECTION_FOREACH(usbh_class_node, c_node) { + struct usbh_class_data *const c_data = c_node->c_data; + + if (c_node->state == USBH_CLASS_STATE_BOUND && + c_data->udev == udev && c_data->iface == iface) { + LOG_DBG("Interface %u bound to '%s', skipping", iface, c_data->name); + return; + } + } + + /* Then try to match this function against all interfaces */ + STRUCT_SECTION_FOREACH(usbh_class_node, c_node) { + struct usbh_class_data *const c_data = c_node->c_data; + + if (c_node->state != USBH_CLASS_STATE_IDLE) { + LOG_DBG("Class %s already matched, skipping", c_data->name); + continue; + } + + if (!usbh_class_is_matching(c_node->filters, info)) { + LOG_DBG("Class %s not matching interface %u", c_data->name, iface); + continue; + } + + ret = usbh_class_probe(c_data, udev, iface); + if (ret == -ENOTSUP) { + LOG_DBG("Class %s not supporting this device, skipping", c_data->name); + continue; + } + + LOG_INF("Class '%s' matches interface %u", c_data->name, iface); + c_node->state = USBH_CLASS_STATE_BOUND; + c_data->udev = udev; + c_data->iface = iface; + break; + } +} + +void usbh_class_probe_device(struct usb_device *const udev) +{ + const void *desc_end; + const struct usb_desc_header *desc; + struct usbh_class_filter info; + uint8_t iface; + int ret; + + k_mutex_lock(&udev->mutex, K_FOREVER); + + desc = usbh_desc_get_cfg_beg(udev); + desc_end = usbh_desc_get_cfg_end(udev); + info.vid = udev->dev_desc.idVendor; + info.pid = udev->dev_desc.idProduct; + + while (true) { + desc = usbh_desc_get_next_function(desc, desc_end); + if (desc == NULL) { + break; + } + + ret = usbh_desc_get_iface_info(desc, &info, &iface); + if (ret < 0) { + LOG_ERR("Failed to collect class codes for matching interface %u", iface); + continue; + } + + usbh_class_probe_function(udev, &info, iface); + } + + k_mutex_unlock(&udev->mutex); +} + +bool usbh_class_is_matching(struct usbh_class_filter *const filters, + struct usbh_class_filter *const info) +{ + /* Make empty filter set match everything (use class_api->probe() only) */ + if (filters[0].flags == 0) { + return true; + } + + /* Try to find a rule that matches completely */ + for (size_t i = 0; filters[i].flags != 0; i++) { + const struct usbh_class_filter *filt = &filters[i]; + + if ((filt->flags & USBH_CLASS_MATCH_VID) && + info->vid != filt->vid) { + continue; + } + + if ((filt->flags & USBH_CLASS_MATCH_PID) && + info->pid != filt->pid) { + continue; + } + + if (filt->flags & USBH_CLASS_MATCH_CLASS && + info->class != filt->class) { + continue; + } + + if ((filt->flags & USBH_CLASS_MATCH_SUB) && + info->sub != filt->sub) { + continue; + } + + if ((filt->flags & USBH_CLASS_MATCH_PROTO) && + info->proto != filt->proto) { + continue; + } + + /* All the selected filters did match */ + return true; + } + + /* At the end of the filter table and still no match */ + return false; +} diff --git a/subsys/usb/host/usbh_class.h b/subsys/usb/host/usbh_class.h new file mode 100644 index 0000000000000..091b15525be51 --- /dev/null +++ b/subsys/usb/host/usbh_class.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * Copyright 2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_USBH_CLASS_H +#define ZEPHYR_INCLUDE_USBH_CLASS_H + +#include + +/** Match a device's vendor ID */ +#define USBH_CLASS_MATCH_VID BIT(1) + +/** Match a device's product ID */ +#define USBH_CLASS_MATCH_PID BIT(2) + +/** Match a class code */ +#define USBH_CLASS_MATCH_CLASS BIT(3) + +/** Match a subclass code */ +#define USBH_CLASS_MATCH_SUB BIT(4) + +/** Match a protocol code */ +#define USBH_CLASS_MATCH_PROTO BIT(5) + +/** Match a code triple */ +#define USBH_CLASS_MATCH_CODE_TRIPLE \ + (USBH_CLASS_MATCH_CLASS | USBH_CLASS_MATCH_SUB | USBH_CLASS_MATCH_PROTO) + +/** + * @brief Match an USB host class (a driver) against a device descriptor. + * + * An empty filter set matches everything. + * This can be used to only rely on @c class_api->probe() return value. + * + * @param[in] filters Array of filter rules to match + * @param[in] device_info Device information filled by this function + * + * @retval true if the USB Device descriptor matches at least one rule. + */ +bool usbh_class_is_matching(struct usbh_class_filter *const filters, + struct usbh_class_filter *const device_info); + +/** + * @brief Initialize every class instantiated on the system + * + * @param[in] uhs_ctx USB Host context to pass to the class. + * + * @retval 0 on success or negative error code on failure + */ +void usbh_class_init_all(struct usbh_context *const uhs_ctx); + +/** + * @brief Probe an USB device function against all available classes. + * + * Try to match a class from the global list of all system classes using their filter rules + * and return status to update the state of each matched class. + * + * The first matched class + * + * @param[in] udev USB device to probe. + * + * @retval 0 on success or negative error code on failure + */ +void usbh_class_probe_device(struct usb_device *const udev); + +/** + * @brief Call the device removal handler for every class configured with it + * + * @param[in] udev USB device that got removed. + * + * @retval 0 on success or negative error code on failure + */ +void usbh_class_remove_all(struct usb_device *const udev); + +#endif /* ZEPHYR_INCLUDE_USBH_CLASS_H */ diff --git a/subsys/usb/host/usbh_core.c b/subsys/usb/host/usbh_core.c index 1b654b5041133..1b30f2079b20d 100644 --- a/subsys/usb/host/usbh_core.c +++ b/subsys/usb/host/usbh_core.c @@ -10,9 +10,12 @@ #include #include #include +#include -#include "usbh_internal.h" +#include "usbh_class.h" +#include "usbh_class_api.h" #include "usbh_device.h" +#include "usbh_internal.h" #include LOG_MODULE_REGISTER(uhs, CONFIG_USBH_LOG_LEVEL); @@ -46,7 +49,6 @@ static int usbh_event_carrier(const struct device *dev, static void dev_connected_handler(struct usbh_context *const ctx, const struct uhc_event *const event) { - LOG_DBG("Device connected event"); if (ctx->root != NULL) { LOG_ERR("Device already connected"); @@ -71,11 +73,14 @@ static void dev_connected_handler(struct usbh_context *const ctx, if (usbh_device_init(ctx->root)) { LOG_ERR("Failed to reset new USB device"); } + + usbh_class_probe_device(ctx->root); } static void dev_removed_handler(struct usbh_context *const ctx) { if (ctx->root != NULL) { + usbh_class_remove_all(ctx->root); usbh_device_free(ctx->root); ctx->root = NULL; LOG_DBG("Device removed"); @@ -194,13 +199,7 @@ int usbh_init_device_intl(struct usbh_context *const uhs_ctx) sys_dlist_init(&uhs_ctx->udevs); - STRUCT_SECTION_FOREACH(usbh_class_data, cdata) { - /* - * For now, we have not implemented any class drivers, - * so just keep it as placeholder. - */ - break; - } + usbh_class_init_all(uhs_ctx); return 0; } diff --git a/subsys/usb/host/usbh_data.ld b/subsys/usb/host/usbh_data.ld index c2778d534d2fa..98f470a080436 100644 --- a/subsys/usb/host/usbh_data.ld +++ b/subsys/usb/host/usbh_data.ld @@ -6,4 +6,4 @@ #include ITERABLE_SECTION_RAM(usbh_context, Z_LINK_ITERABLE_SUBALIGN) -ITERABLE_SECTION_RAM(usbh_class_data, Z_LINK_ITERABLE_SUBALIGN) +ITERABLE_SECTION_RAM(usbh_class_node, Z_LINK_ITERABLE_SUBALIGN) diff --git a/subsys/usb/host/usbh_desc.c b/subsys/usb/host/usbh_desc.c new file mode 100644 index 0000000000000..0105c9d8e9bf8 --- /dev/null +++ b/subsys/usb/host/usbh_desc.c @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * Copyright NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "usbh_class.h" +#include "usbh_desc.h" + +LOG_MODULE_REGISTER(usbh_desc, CONFIG_USBH_LOG_LEVEL); + +bool usbh_desc_is_valid(const void *const desc_ptr, const void *const desc_end, + const size_t expected_size, const uint8_t type) +{ + const struct usb_desc_header *desc = desc_ptr; + + /* Block invalid input */ + if (desc == NULL || desc_end == NULL || expected_size < sizeof(struct usb_desc_header)) { + return false; + } + + /* Avoid out of order pointers */ + if ((uint8_t *)desc > (uint8_t *)desc_end) { + return false; + } + + /* Avoid too short descriptors */ + if ((uint8_t *)desc + expected_size > (uint8_t *)desc_end) { + return false; + } + + /* Avoid too short bLength field */ + if (desc->bLength < expected_size) { + return false; + } + + /* Expect the correct type */ + if (type != 0 && type != desc->bDescriptorType) { + return false; + } + + return true; +} + +bool usbh_desc_is_valid_interface(const void *const desc, const void *const desc_end) +{ + return usbh_desc_is_valid(desc, desc_end, sizeof(struct usb_if_descriptor), + USB_DESC_INTERFACE); +} + +bool usbh_desc_is_valid_association(const void *const desc, const void *const desc_end) +{ + return usbh_desc_is_valid(desc, desc_end, sizeof(struct usb_association_descriptor), + USB_DESC_INTERFACE_ASSOC); +} + +bool usbh_desc_is_valid_configuration(const void *const desc, const void *const desc_end) +{ + return usbh_desc_is_valid(desc, desc_end, sizeof(struct usb_cfg_descriptor), + USB_DESC_CONFIGURATION); +} + +const void *usbh_desc_get_next(const void *const desc_beg, const void *const desc_end) +{ + const struct usb_desc_header *const desc = desc_beg; + const struct usb_desc_header *next; + + if (!usbh_desc_is_valid(desc, desc_end, sizeof(const struct usb_desc_header), 0)) { + return NULL; + } + + next = (const struct usb_desc_header *)((uint8_t *)desc + desc->bLength); + + if (!usbh_desc_is_valid(next, desc_end, sizeof(const struct usb_desc_header), 0)) { + return NULL; + } + + return next; +} + +const void *usbh_desc_get_next_alt_setting(const void *const desc_beg, const void *const desc_end) +{ + const struct usb_desc_header *desc = desc_beg; + + /* Expect desc to already be pointing at an interface descriptor */ + if (desc_beg == NULL || desc_end == NULL || desc->bDescriptorType != USB_DESC_INTERFACE) { + return NULL; + } + + /* Skip the current interface descriptor */ + desc = usbh_desc_get_next(desc, desc_end); + + /* Seek to the nextAlternate Setting for this interface */ + for (; desc != NULL; desc = usbh_desc_get_next(desc, desc_end)) { + if (desc->bDescriptorType == USB_DESC_INTERFACE) { + /* Non-zero Alternate Setting */ + if (usbh_desc_is_valid_interface(desc, desc_end) && + ((struct usb_if_descriptor *)desc)->bAlternateSetting != 0) { + return desc; + } + + /* Do not continue to the next interface */ + return NULL; + } + } + + return NULL; +} + +const void *usbh_desc_get_by_iface(const void *const desc_beg, const void *const desc_end, + const uint8_t iface) +{ + const struct usb_desc_header *desc; + + if (desc_beg == NULL || desc_end == NULL) { + return NULL; + } + + for (desc = desc_beg; desc != NULL; desc = usbh_desc_get_next(desc, desc_end)) { + if (usbh_desc_is_valid_interface(desc, desc_end) && + ((struct usb_if_descriptor *)desc)->bInterfaceNumber == iface) { + return desc; + } + if (usbh_desc_is_valid_association(desc, desc_end) && + ((struct usb_association_descriptor *)desc)->bFirstInterface == iface) { + return desc; + } + } + + return NULL; +} + +const void *usbh_desc_get_cfg_beg(const struct usb_device *const udev) +{ + return udev->cfg_desc; +} + +const void *usbh_desc_get_cfg_end(const struct usb_device *const udev) +{ + const struct usb_cfg_descriptor *const cfg_desc = udev->cfg_desc; + + __ASSERT_NO_MSG(cfg_desc != NULL); + + /* Relies on wTotalLength being validated in usbh_device_set_configuration() */ + return (uint8_t *)cfg_desc + cfg_desc->wTotalLength; +} + +int usbh_desc_get_iface_info(const struct usb_desc_header *const desc, + struct usbh_class_filter *const iface_code, uint8_t *const iface_num) +{ + if (desc->bDescriptorType == USB_DESC_INTERFACE_ASSOC) { + const struct usb_association_descriptor *iad_desc = + (const struct usb_association_descriptor *)desc; + + iface_code->class = iad_desc->bFunctionClass; + iface_code->sub = iad_desc->bFunctionSubClass; + iface_code->proto = iad_desc->bFunctionProtocol; + if (iface_num != NULL) { + *iface_num = iad_desc->bFirstInterface; + } + return 0; + } + + if (desc->bDescriptorType == USB_DESC_INTERFACE) { + const struct usb_if_descriptor *if_desc = + (const struct usb_if_descriptor *)desc; + + iface_code->class = if_desc->bInterfaceClass; + iface_code->sub = if_desc->bInterfaceSubClass; + iface_code->proto = if_desc->bInterfaceProtocol; + if (iface_num != NULL) { + *iface_num = if_desc->bInterfaceNumber; + } + return 0; + } + + return -EINVAL; +} + +const void *usbh_desc_get_next_function(const void *const desc_beg, const void *const desc_end) +{ + const struct usb_desc_header *desc = desc_beg; + uint8_t skip_num = 1; + + if (desc_beg == NULL || desc_end == NULL) { + return NULL; + } + + /* Skip all interfaces the Association descriptor contains */ + if (usbh_desc_is_valid_association(desc, desc_end)) { + skip_num = ((struct usb_association_descriptor *)desc)->bInterfaceCount; + } + + while (desc != NULL && skip_num > 0) { + /* If already on an Interface Association or Interface, this will skip it */ + desc = usbh_desc_get_next(desc, desc_end); + + /* Association descriptor: this is always a new function */ + if (usbh_desc_is_valid_association(desc, desc_end)) { + return desc; + } + + /* Only count the first Alternate Setting of an Interface */ + if (usbh_desc_is_valid_interface(desc, desc_end) && + ((struct usb_if_descriptor *)desc)->bAlternateSetting == 0) { + skip_num--; + } + } + + return desc; +} diff --git a/subsys/usb/host/usbh_desc.h b/subsys/usb/host/usbh_desc.h new file mode 100644 index 0000000000000..1e270fcabbbfa --- /dev/null +++ b/subsys/usb/host/usbh_desc.h @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Descriptor matching and searching utilities + * + * This file contains utilities to browse USB descriptors returned by a device + * according to the USB Specification 2.0 Chapter 9. + */ + +#ifndef ZEPHYR_INCLUDE_USBH_DESC_H +#define ZEPHYR_INCLUDE_USBH_DESC_H + +#include + +#include + +/** + * @brief Get the next descriptor in an array of descriptors. + * + * @param[in] desc_beg Pointer to the beginning of the descriptor array; to search. May be NULL. + * @param[in] desc_end Pointer after the end of the descriptor array to search + * + * @return A pointer to the first descriptor matching + */ +const void *usbh_desc_get_next(const void *const desc_beg, const void *const desc_end); + +/** + * @brief Search a descriptor matching the interace number wanted. + * + * The descriptors are going to be scanned until either an interface association + * @c bFirstInterface field or an interface @c bInterfaceNumber field match. + * + * The descriptors following it can be browsed using @ref usbh_desc_get_next + * + * @param[in] desc_beg Pointer to the beginning of the descriptor array; to search. May be NULL. + * @param[in] desc_end Pointer after the end of the descriptor array to search + * @param[in] iface The interface number to search + * + * @return A pointer to the first descriptor matching + */ +const void *usbh_desc_get_by_iface(const void *const desc_beg, const void *const desc_end, + const uint8_t iface); + +/** + * @brief Get the start of a device's configuration descriptor. + * + * @param[in] udev The device holding the configuration descriptor + * + * @return A pointer to the beginning of the descriptor + */ +const void *usbh_desc_get_cfg_beg(const struct usb_device *const udev); + +/** + * @brief Get the end of a device's configuration descriptor. + * + * @param[in] udev The device holding the configuration descriptor + * + * @return A pointer to the beginning of the descriptor + */ +const void *usbh_desc_get_cfg_end(const struct usb_device *const udev); + +/** + * @brief Extract information from an interface or interface association descriptors + * + * @param[in] desc The descriptor to use + * @param[out] iface_code Device information filled by this function + * @param[out] iface_num Pointer filled with the interface number, or NULL + * + * @return 0 on success or negative error code on failure. + */ +int usbh_desc_get_iface_info(const struct usb_desc_header *desc, + struct usbh_class_filter *const iface_code, + uint8_t *const iface_num); + +/** + * @brief Checks that the pointed descriptor is not truncated. + * + * @param[in] desc The descriptor to validate + * @param[in] desc_end Pointer after the end of the descriptor array to search. + * @param[in] expected_size The size of the descriptor. + * @param[in] expected_type The type of the descriptor, 0 for any. + * + * @return true if the descriptor size is valid + */ +const bool usbh_desc_is_valid(const void *const desc, const void *const desc_end, + const size_t expected_size, const uint8_t expected_type); + +/** + * @brief Checks that the pointed descriptor is a valid interface descriptor. + * + * @param[in] desc The descriptor to validate + * @param[in] desc_end Pointer after the end of the descriptor array to search. + * + * @return true if the descriptor size is the expected type and size + */ +bool usbh_desc_is_valid_interface(const void *const desc, const void *const desc_end); + +/** + * @brief Checks that the pointed descriptor is a valid interface descriptor. + * + * @param[in] desc The descriptor to validate + * @param[in] desc_end Pointer after the end of the descriptor array to search. + * + * @return true if the descriptor size is the expected type and size + */ +bool usbh_desc_is_valid_association(const void *const desc, const void *const desc_end); + +/** + * @brief Get the next function in the descriptor list. + * + * This returns the interface or interface association of the next USB function, if found. + * This can be used to walk through the list of USB functions to associate a class to each. + * + * If @p desc_beg is an interface association descriptor, it will return a pointer to the interface + * after all those related to the association descriptor. + * + * If @p desc_beg is an interface descriptor, it will skip all the interface alternate settings + * and return a pointer to the next interface with bAlternateSetting of 0. + * + * If @p desc_beg is another type of descriptors, it will seek to a matching descriptor type and + * return it, without skipping to the next one after it. + * + * @param[in] desc_beg Pointer to the beginning of the descriptor array; to search. May be NULL. + * @param[in] desc_end Pointer after the end of the descriptor array to search. + * + * @return Pointer to the next matching descriptor or NULL. + */ +const void *usbh_desc_get_next_function(const void *const desc_beg, const void *const desc_end); + +/** + * @brief Get the next alternate setting in the current interface. + * + * The @p desc_beg descriptor is expected to point at an interface descriptor, and the descriptor + * returned will be different, with bAlternateSetting > 0 and same bInterfaceNumber, or NULL if + * none was found or on invalid descriptor. + * + * @param[in] desc_beg Pointer to the beginning of the descriptor array; to search. May be NULL. + * @param[in] desc_end Pointer after the end of the descriptor array to search. + * + * @return Pointer to the next matching descriptor or NULL. + */ +const void *usbh_desc_get_next_alt_setting(const void *const desc_beg, const void *const desc_end); + +#endif /* ZEPHYR_INCLUDE_USBH_DESC_H */ From c930b72c262952a6a908e121a9419b4eecefbdd7 Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Fri, 25 Jul 2025 12:09:06 +0000 Subject: [PATCH 6/8] usb: uvc: move the header definition to include/ Move the UVC header with all the definitions from the UVC standard to share it between USB host and device class implementation. Signed-off-by: Josuah Demangeon --- subsys/usb/CMakeLists.txt | 1 + subsys/usb/common/CMakeLists.txt | 4 ++++ .../class/usbd_uvc.h => common/include/usb_uvc.h} | 0 subsys/usb/device_next/class/usbd_uvc.c | 3 ++- 4 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 subsys/usb/common/CMakeLists.txt rename subsys/usb/{device_next/class/usbd_uvc.h => common/include/usb_uvc.h} (100%) diff --git a/subsys/usb/CMakeLists.txt b/subsys/usb/CMakeLists.txt index 6b968a4bf894e..b8367352d06de 100644 --- a/subsys/usb/CMakeLists.txt +++ b/subsys/usb/CMakeLists.txt @@ -5,3 +5,4 @@ add_subdirectory_ifdef(CONFIG_USB_DEVICE_STACK device) add_subdirectory_ifdef(CONFIG_USB_DEVICE_STACK_NEXT device_next) add_subdirectory_ifdef(CONFIG_USB_HOST_STACK host) add_subdirectory_ifdef(CONFIG_USBC_STACK usb_c) +add_subdirectory(common) diff --git a/subsys/usb/common/CMakeLists.txt b/subsys/usb/common/CMakeLists.txt new file mode 100644 index 0000000000000..1dfd227bd5081 --- /dev/null +++ b/subsys/usb/common/CMakeLists.txt @@ -0,0 +1,4 @@ +# Copyright (c) 2025 Nordic Semiconductor +# SPDX-License-Identifier: Apache-2.0 + +zephyr_include_directories(include) diff --git a/subsys/usb/device_next/class/usbd_uvc.h b/subsys/usb/common/include/usb_uvc.h similarity index 100% rename from subsys/usb/device_next/class/usbd_uvc.h rename to subsys/usb/common/include/usb_uvc.h diff --git a/subsys/usb/device_next/class/usbd_uvc.c b/subsys/usb/device_next/class/usbd_uvc.c index 2b80084079e37..169e4f30bcb0a 100644 --- a/subsys/usb/device_next/class/usbd_uvc.c +++ b/subsys/usb/device_next/class/usbd_uvc.c @@ -25,7 +25,8 @@ #include #include -#include "usbd_uvc.h" +#include "usb_uvc.h" + #include "../../../drivers/video/video_ctrls.h" #include "../../../drivers/video/video_device.h" From e43dd89f1b30a32e8d241237210a93e7d4526799 Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Thu, 11 Sep 2025 19:32:25 +0000 Subject: [PATCH 7/8] 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 --- .../subsys/usb/host/class_api/CMakeLists.txt | 12 + .../usb/host/class_api/boards/native_sim.conf | 1 + .../host/class_api/boards/native_sim.overlay | 19 ++ .../host/class_api/boards/native_sim_64.conf | 1 + .../class_api/boards/native_sim_64.overlay | 7 + .../class_api/boards/qemu_cortex_m3.overlay | 17 + tests/subsys/usb/host/class_api/prj.conf | 8 + tests/subsys/usb/host/class_api/src/main.c | 305 ++++++++++++++++++ tests/subsys/usb/host/class_api/testcase.yaml | 17 + .../usb/host/common/include/test_descriptor.h | 86 +++++ tests/subsys/usb/host/desc/CMakeLists.txt | 12 + .../usb/host/desc/boards/native_sim.conf | 1 + .../usb/host/desc/boards/native_sim.overlay | 19 ++ .../usb/host/desc/boards/native_sim_64.conf | 1 + .../host/desc/boards/native_sim_64.overlay | 7 + .../host/desc/boards/qemu_cortex_m3.overlay | 17 + tests/subsys/usb/host/desc/prj.conf | 8 + tests/subsys/usb/host/desc/src/main.c | 74 +++++ tests/subsys/usb/host/desc/testcase.yaml | 9 + 19 files changed, 621 insertions(+) create mode 100644 tests/subsys/usb/host/class_api/CMakeLists.txt create mode 100644 tests/subsys/usb/host/class_api/boards/native_sim.conf create mode 100644 tests/subsys/usb/host/class_api/boards/native_sim.overlay create mode 100644 tests/subsys/usb/host/class_api/boards/native_sim_64.conf create mode 100644 tests/subsys/usb/host/class_api/boards/native_sim_64.overlay create mode 100644 tests/subsys/usb/host/class_api/boards/qemu_cortex_m3.overlay create mode 100644 tests/subsys/usb/host/class_api/prj.conf create mode 100644 tests/subsys/usb/host/class_api/src/main.c create mode 100644 tests/subsys/usb/host/class_api/testcase.yaml create mode 100644 tests/subsys/usb/host/common/include/test_descriptor.h create mode 100644 tests/subsys/usb/host/desc/CMakeLists.txt create mode 100644 tests/subsys/usb/host/desc/boards/native_sim.conf create mode 100644 tests/subsys/usb/host/desc/boards/native_sim.overlay create mode 100644 tests/subsys/usb/host/desc/boards/native_sim_64.conf create mode 100644 tests/subsys/usb/host/desc/boards/native_sim_64.overlay create mode 100644 tests/subsys/usb/host/desc/boards/qemu_cortex_m3.overlay create mode 100644 tests/subsys/usb/host/desc/prj.conf create mode 100644 tests/subsys/usb/host/desc/src/main.c create mode 100644 tests/subsys/usb/host/desc/testcase.yaml diff --git a/tests/subsys/usb/host/class_api/CMakeLists.txt b/tests/subsys/usb/host/class_api/CMakeLists.txt new file mode 100644 index 0000000000000..d99885afb1d47 --- /dev/null +++ b/tests/subsys/usb/host/class_api/CMakeLists.txt @@ -0,0 +1,12 @@ +# Copyright (c) 2025 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(test_usb_host) + +target_include_directories(app PRIVATE ${ZEPHYR_BASE}/subsys/usb/host) +target_include_directories(app PRIVATE ${ZEPHYR_BASE}/tests/subsys/usb/host/common/include) + +target_sources(app PRIVATE src/main.c) diff --git a/tests/subsys/usb/host/class_api/boards/native_sim.conf b/tests/subsys/usb/host/class_api/boards/native_sim.conf new file mode 100644 index 0000000000000..68e22b2de6a0c --- /dev/null +++ b/tests/subsys/usb/host/class_api/boards/native_sim.conf @@ -0,0 +1 @@ +CONFIG_SYS_CLOCK_TICKS_PER_SEC=1000000 diff --git a/tests/subsys/usb/host/class_api/boards/native_sim.overlay b/tests/subsys/usb/host/class_api/boards/native_sim.overlay new file mode 100644 index 0000000000000..d352f555c8c89 --- /dev/null +++ b/tests/subsys/usb/host/class_api/boards/native_sim.overlay @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/delete-node/ &zephyr_udc0; + +/ { + zephyr_uhc0: uhc_vrt0 { + compatible = "zephyr,uhc-virtual"; + + zephyr_udc0: udc_vrt0 { + compatible = "zephyr,udc-virtual"; + num-bidir-endpoints = <8>; + maximum-speed = "high-speed"; + }; + }; +}; diff --git a/tests/subsys/usb/host/class_api/boards/native_sim_64.conf b/tests/subsys/usb/host/class_api/boards/native_sim_64.conf new file mode 100644 index 0000000000000..68e22b2de6a0c --- /dev/null +++ b/tests/subsys/usb/host/class_api/boards/native_sim_64.conf @@ -0,0 +1 @@ +CONFIG_SYS_CLOCK_TICKS_PER_SEC=1000000 diff --git a/tests/subsys/usb/host/class_api/boards/native_sim_64.overlay b/tests/subsys/usb/host/class_api/boards/native_sim_64.overlay new file mode 100644 index 0000000000000..e3ea1991fbdfe --- /dev/null +++ b/tests/subsys/usb/host/class_api/boards/native_sim_64.overlay @@ -0,0 +1,7 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "native_sim.overlay" diff --git a/tests/subsys/usb/host/class_api/boards/qemu_cortex_m3.overlay b/tests/subsys/usb/host/class_api/boards/qemu_cortex_m3.overlay new file mode 100644 index 0000000000000..4dd05c0fe9975 --- /dev/null +++ b/tests/subsys/usb/host/class_api/boards/qemu_cortex_m3.overlay @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + zephyr_uhc0: uhc_vrt0 { + compatible = "zephyr,uhc-virtual"; + + zephyr_udc0: udc_vrt0 { + compatible = "zephyr,udc-virtual"; + num-bidir-endpoints = <8>; + maximum-speed = "high-speed"; + }; + }; +}; diff --git a/tests/subsys/usb/host/class_api/prj.conf b/tests/subsys/usb/host/class_api/prj.conf new file mode 100644 index 0000000000000..74f0aae233c58 --- /dev/null +++ b/tests/subsys/usb/host/class_api/prj.conf @@ -0,0 +1,8 @@ +# Copyright (c) 2025 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_LOG=y +CONFIG_ZTEST=y + +CONFIG_USB_HOST_STACK=y +CONFIG_UHC_DRIVER=y diff --git a/tests/subsys/usb/host/class_api/src/main.c b/tests/subsys/usb/host/class_api/src/main.c new file mode 100644 index 0000000000000..e107d25c17f37 --- /dev/null +++ b/tests/subsys/usb/host/class_api/src/main.c @@ -0,0 +1,305 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include "usbh_ch9.h" +#include "usbh_class_api.h" +#include "usbh_desc.h" +#include "usbh_host.h" +#include "usbh_class.h" + +#include "test_descriptor.h" + +#include +LOG_MODULE_REGISTER(usb_test, LOG_LEVEL_DBG); + +USBH_CONTROLLER_DEFINE(uhs_ctx, DEVICE_DT_GET(DT_NODELABEL(zephyr_uhc0))); + +#define TEST_VID 0x2FE3 +#define TEST_PID 0x0002 + +static const uint8_t test_hub_descriptor[] = { + TEST_HUB_DESCRIPTOR +}; + +struct usb_device test_udev = { + .cfg_desc = (void *)test_hub_descriptor, +}; + +/* Private class data, here just an integer but usually a custom struct. */ +struct test_class_priv { + enum { + /* Test value stored before the class is initialized */ + TEST_CLASS_PRIV_INACTIVE, + /* Test value stored after the class is initialized */ + TEST_CLASS_PRIV_IDLE, + /* Test value stored after the class is probed */ + TEST_CLASS_PRIV_ENABLED, + /* Test value stored after the class is initialized */ + TEST_CLASS_PRIV_INITIALIZED, + /* Test value stored after the class is suspended */ + TEST_CLASS_PRIV_SUSPENDED, + } state; +}; + +static struct test_class_priv test_class_priv = { + .state = TEST_CLASS_PRIV_INACTIVE, +}; + +static int test_class_init(struct usbh_class_data *const c_data, + struct usbh_context *const uhs_ctx) +{ + struct test_class_priv *priv = c_data->priv; + + LOG_DBG("initializing %p, priv value 0x%x", c_data, *priv); + + zassert_equal(priv->state, TEST_CLASS_PRIV_INACTIVE, + "Class should be initialized only once"); + + priv->state = TEST_CLASS_PRIV_IDLE; + + return 0; +} + +static int test_class_completion_cb(struct usbh_class_data *const c_data, + struct uhc_transfer *const xfer) +{ + struct test_class_priv *priv = c_data->priv; + + LOG_DBG("completion callback for %p, transfer %p", c_data, xfer); + + zassert_equal(priv->state, TEST_CLASS_PRIV_ENABLED); + + return -ENOTSUP; +} + +static int test_class_probe(struct usbh_class_data *const c_data, + struct usb_device *const udev, + const uint8_t iface) +{ + struct test_class_priv *priv = c_data->priv; + const void *const desc_beg = test_hub_descriptor; + const void *const desc_end = test_hub_descriptor + sizeof(test_hub_descriptor); + const struct usb_desc_header *desc = desc_beg; + const struct usb_if_descriptor *if_desc; + + zassert_equal(priv->state, TEST_CLASS_PRIV_IDLE); + + desc = usbh_desc_get_by_iface(desc, desc_end, iface); + if (desc == NULL) { + return -ENOENT; + } + + if (desc->bDescriptorType != USB_DESC_INTERFACE) { + return -ENOTSUP; + } + + if_desc = (struct usb_if_descriptor *)desc; + if (if_desc->bInterfaceClass != USB_HUB_CLASSCODE || + if_desc->bInterfaceSubClass != 0x00) { + return -ENOTSUP; + } + + priv->state = TEST_CLASS_PRIV_ENABLED; + + return 0; +} + +static int test_class_removed(struct usbh_class_data *const c_data) +{ + struct test_class_priv *priv = c_data->priv; + + zassert_equal(priv->state, TEST_CLASS_PRIV_ENABLED); + + priv->state = TEST_CLASS_PRIV_IDLE; + + return 0; +} + +static int test_class_suspended(struct usbh_class_data *const c_data) +{ + struct test_class_priv *priv = c_data->priv; + + zassert_equal(priv->state, TEST_CLASS_PRIV_ENABLED); + + priv->state = TEST_CLASS_PRIV_SUSPENDED; + + return 0; +} + +static int test_class_resumed(struct usbh_class_data *const c_data) +{ + struct test_class_priv *priv = c_data->priv; + + zassert_equal(priv->state, TEST_CLASS_PRIV_SUSPENDED); + + priv->state = TEST_CLASS_PRIV_ENABLED; + + return 0; +} + +static struct usbh_class_api test_class_api = { + .init = &test_class_init, + .completion_cb = &test_class_completion_cb, + .probe = &test_class_probe, + .removed = &test_class_removed, + .suspended = &test_class_suspended, + .resumed = &test_class_resumed, +}; + +struct usbh_class_filter test_filters[] = { + { + .class = 9, + .sub = 0, + .proto = 1, + .flags = USBH_CLASS_MATCH_CODE_TRIPLE, + }, + { + .vid = TEST_VID, + .pid = TEST_PID, + .flags = USBH_CLASS_MATCH_VID | USBH_CLASS_MATCH_PID, + }, +}; + +/* Define a class used in the tests */ +USBH_DEFINE_CLASS(test_class, &test_class_api, &test_class_priv, test_filters); + +ZTEST(host_class, test_class_matchiing) +{ + struct usb_device *udev = &test_udev; + const void *const desc_beg = usbh_desc_get_cfg_beg(udev); + const void *const desc_end = usbh_desc_get_cfg_end(udev); + const struct usb_desc_header *desc; + struct usbh_class_filter info_ok = { + .vid = TEST_VID, + .pid = TEST_PID, + }; + struct usbh_class_filter info_wrong_vid = { + .vid = TEST_VID + 1, + .pid = TEST_PID, + }; + struct usbh_class_filter info_wrong_pid = { + .vid = TEST_VID, + .pid = TEST_PID + 1, + }; + int ret; + + zassert(usbh_class_is_matching(test_filters, &info_ok), + "Filtering on valid VID:PID should match"); + + zassert(!usbh_class_is_matching(test_filters, &info_wrong_vid), + "Filtering on invalid VID only should not match"); + + zassert(!usbh_class_is_matching(test_filters, &info_wrong_pid), + "Filtering on invalid PID only should not match"); + + desc = usbh_desc_get_next_function(desc_beg, desc_end); + zassert_not_null(desc, "There should be at least a function descriptor"); + + ret = usbh_desc_get_iface_info(desc, &info_ok, 0); + zassert_ok(ret, "Expecting the class info to be found"); + + ret = usbh_desc_get_iface_info(desc, &info_wrong_pid, 0); + zassert_ok(ret, "Expecting the class info to be found"); + + ret = usbh_desc_get_iface_info(desc, &info_wrong_vid, 0); + zassert_ok(ret, "Expecting the class info to be found"); + + zassert(usbh_class_is_matching(test_filters, &info_ok), + "Filteringon valid VID:PID and code triple should match"); + + zassert(usbh_class_is_matching(test_filters, &info_wrong_vid), + "Filtering on code triple only should match"); + + zassert(usbh_class_is_matching(test_filters, &info_wrong_pid), + "Filtering on code triple only should match"); +} + +ZTEST(host_class, test_class_fake_device) +{ + struct usbh_class_data *c_data = test_class.c_data; + struct usb_device *udev = &test_udev; + struct test_class_priv *priv = c_data->priv; + int ret; + + zassert_equal(priv->state, TEST_CLASS_PRIV_IDLE, + "The class should have been initialized by usbh_init()"); + + ret = usbh_class_probe(c_data, udev, 1); + zassert_equal(ret, -ENOENT, "There is no interface 1 so should be rejected"); + zassert_equal(priv->state, TEST_CLASS_PRIV_IDLE, + "The class should not be enabled if probing failed"); + + ret = usbh_class_probe(c_data, udev, 0); + zassert_ok(ret, "The interface 0 should match in this example class (%s)", strerror(-ret)); + zassert_equal(priv->state, TEST_CLASS_PRIV_ENABLED, + "The class should be enabled if probing succeeded"); + + ret = usbh_class_suspended(c_data); + zassert_ok(ret, "Susupending the class while it is running should succeed"); + zassert_equal(priv->state, TEST_CLASS_PRIV_SUSPENDED, + "The class private state should have been updated"); + + ret = usbh_class_resumed(c_data); + zassert_ok(ret, "Resuming the class after suspending should succeed"); + zassert_equal(priv->state, TEST_CLASS_PRIV_ENABLED, + "The class private state should have been updated"); + + ret = usbh_class_removed(c_data); + zassert_ok(ret, "Removing the class after probing it should succeed"); + zassert_equal(priv->state, TEST_CLASS_PRIV_IDLE, + "The class should be back to inactive "); + + ret = usbh_class_probe(c_data, udev, 0); + zassert_ok(ret, "Probing the class again should succeed"); + zassert_equal(priv->state, TEST_CLASS_PRIV_ENABLED, + "The class should be back to active "); +} + +static void *usb_test_enable(void) +{ + int ret; + + ret = usbh_init(&uhs_ctx); + zassert_ok(ret, "Failed to initialize USB host (%s)", strerror(-ret)); + + ret = usbh_enable(&uhs_ctx); + zassert_ok(ret, "Failed to enable USB host (%s)", strerror(-ret)); + + ret = uhc_bus_reset(uhs_ctx.dev); + zassert_ok(ret, "Failed to signal bus reset (%s)", strerror(-ret)); + + ret = uhc_bus_resume(uhs_ctx.dev); + zassert_ok(ret, "Failed to signal bus resume (%s)", strerror(-ret)); + + ret = uhc_sof_enable(uhs_ctx.dev); + zassert_ok(ret, "Failed to enable SoF generator (%s)", strerror(-ret)); + + LOG_INF("Host controller enabled"); + + /* Allow the host time to reset the host. */ + k_msleep(200); + + return NULL; +} + +static void usb_test_shutdown(void *f) +{ + int ret; + + ret = usbh_disable(&uhs_ctx); + zassert_ok(ret, "Failed to enable host support (%s)", strerror(-ret)); + + ret = usbh_shutdown(&uhs_ctx); + zassert_ok(ret, "Failed to shutdown host support (%s)", strerror(-ret)); + + LOG_INF("Host controller disabled"); +} + +ZTEST_SUITE(host_class, NULL, usb_test_enable, NULL, NULL, usb_test_shutdown); diff --git a/tests/subsys/usb/host/class_api/testcase.yaml b/tests/subsys/usb/host/class_api/testcase.yaml new file mode 100644 index 0000000000000..3ff951fc47a9f --- /dev/null +++ b/tests/subsys/usb/host/class_api/testcase.yaml @@ -0,0 +1,17 @@ +tests: + usb.host.class_api: + platform_allow: + - native_sim + - native_sim/native/64 + - qemu_cortex_m3 + integration_platforms: + - native_sim + tags: usb + usb.host.class_api.build_all: + platform_allow: + - native_sim + - native_sim/native/64 + integration_platforms: + - native_sim + tags: usb + build_only: true diff --git a/tests/subsys/usb/host/common/include/test_descriptor.h b/tests/subsys/usb/host/common/include/test_descriptor.h new file mode 100644 index 0000000000000..02b5cfdab36e8 --- /dev/null +++ b/tests/subsys/usb/host/common/include/test_descriptor.h @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_TEST_USB_HOST_SAMPLE_DESC_H_ +#define ZEPHYR_TEST_USB_HOST_SAMPLE_DESC_H_ + +#include +#include + +#define _LE16(n) ((n) & 0xff), ((n) >> 8) + +/* bInterfaceClass of these descriptors */ +#define USB_HUB_CLASSCODE 0x09 + +/* + * Obtained with lsusb then verified against the + * USB 2.0 standard's sample HUB descriptor + */ + +#define TEST_HUB_DEVICE_DESCRIPTOR \ + 18, /* bLength */ \ + 1, /* bDescriptorType */ \ + _LE16(0x0200), /* bcdUSB */ \ + 0x09, /* bDeviceClass */ \ + 0x00, /* bDeviceSubClass */ \ + 0x02, /* bDeviceProtocol */ \ + 64, /* bMaxPacketSize0 */ \ + _LE16(0x0bda), /* idVendor */ \ + _LE16(0x5411), /* idProduct */ \ + _LE16(0x0001), /* bcdDevice */ \ + 0, /* iManufacturer */ \ + 0, /* iProduct */ \ + 0, /* iSerial */ \ + 1, /* bNumConfigurations */ + +#define TEST_HUB_CONFIG_DESCRIPTOR \ + 9, /* bLength */ \ + 2, /* bDescriptorType */ \ + _LE16(0x0029), /* wTotalLength */ \ + 1, /* bNumInterfaces */ \ + 1, /* bConfigurationValue */ \ + 0, /* iConfiguration */ \ + 0xe0, /* bmAttributes */ \ + 0, /* MaxPower */ + +#define TEST_HUB_INTERFACE_ALT0_DESCRIPTOR \ + 9, /* bLength */ \ + 4, /* bDescriptorType */ \ + 0, /* bInterfaceNumber */ \ + 0, /* bAlternateSetting */ \ + 1, /* bNumEndpoints */ \ + 9, /* bInterfaceClass */ \ + 0, /* bInterfaceSubClass */ \ + 1, /* bInterfaceProtocol */ \ + 0, /* iInterface */ + +#define TEST_HUB_INTERFACE_ALT1_DESCRIPTOR \ + 9, /* bLength */ \ + 4, /* bDescriptorType */ \ + 0, /* bInterfaceNumber */ \ + 1, /* bAlternateSetting */ \ + 1, /* bNumEndpoints */ \ + 9, /* bInterfaceClass */ \ + 0, /* bInterfaceSubClass */ \ + 2, /* bInterfaceProtocol */ \ + 0, /* iInterface */ + +#define TEST_HUB_ENDPOINT_DESCRIPTOR \ + 7, /* bLength */ \ + 5, /* bDescriptorType */ \ + 0x81, /* bEndpointAddress */ \ + 0x03, /* bmAttributes */ \ + _LE16(1), /* wMaxPacketSize */ \ + 12, /* bInterval */ + +#define TEST_HUB_DESCRIPTOR \ + TEST_HUB_CONFIG_DESCRIPTOR \ + TEST_HUB_INTERFACE_ALT0_DESCRIPTOR \ + TEST_HUB_ENDPOINT_DESCRIPTOR \ + TEST_HUB_INTERFACE_ALT1_DESCRIPTOR \ + TEST_HUB_ENDPOINT_DESCRIPTOR + +#endif /* ZEPHYR_TEST_USB_HOST_SAMPLE_DESC_H_ */ diff --git a/tests/subsys/usb/host/desc/CMakeLists.txt b/tests/subsys/usb/host/desc/CMakeLists.txt new file mode 100644 index 0000000000000..d99885afb1d47 --- /dev/null +++ b/tests/subsys/usb/host/desc/CMakeLists.txt @@ -0,0 +1,12 @@ +# Copyright (c) 2025 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(test_usb_host) + +target_include_directories(app PRIVATE ${ZEPHYR_BASE}/subsys/usb/host) +target_include_directories(app PRIVATE ${ZEPHYR_BASE}/tests/subsys/usb/host/common/include) + +target_sources(app PRIVATE src/main.c) diff --git a/tests/subsys/usb/host/desc/boards/native_sim.conf b/tests/subsys/usb/host/desc/boards/native_sim.conf new file mode 100644 index 0000000000000..68e22b2de6a0c --- /dev/null +++ b/tests/subsys/usb/host/desc/boards/native_sim.conf @@ -0,0 +1 @@ +CONFIG_SYS_CLOCK_TICKS_PER_SEC=1000000 diff --git a/tests/subsys/usb/host/desc/boards/native_sim.overlay b/tests/subsys/usb/host/desc/boards/native_sim.overlay new file mode 100644 index 0000000000000..d352f555c8c89 --- /dev/null +++ b/tests/subsys/usb/host/desc/boards/native_sim.overlay @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/delete-node/ &zephyr_udc0; + +/ { + zephyr_uhc0: uhc_vrt0 { + compatible = "zephyr,uhc-virtual"; + + zephyr_udc0: udc_vrt0 { + compatible = "zephyr,udc-virtual"; + num-bidir-endpoints = <8>; + maximum-speed = "high-speed"; + }; + }; +}; diff --git a/tests/subsys/usb/host/desc/boards/native_sim_64.conf b/tests/subsys/usb/host/desc/boards/native_sim_64.conf new file mode 100644 index 0000000000000..68e22b2de6a0c --- /dev/null +++ b/tests/subsys/usb/host/desc/boards/native_sim_64.conf @@ -0,0 +1 @@ +CONFIG_SYS_CLOCK_TICKS_PER_SEC=1000000 diff --git a/tests/subsys/usb/host/desc/boards/native_sim_64.overlay b/tests/subsys/usb/host/desc/boards/native_sim_64.overlay new file mode 100644 index 0000000000000..e3ea1991fbdfe --- /dev/null +++ b/tests/subsys/usb/host/desc/boards/native_sim_64.overlay @@ -0,0 +1,7 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "native_sim.overlay" diff --git a/tests/subsys/usb/host/desc/boards/qemu_cortex_m3.overlay b/tests/subsys/usb/host/desc/boards/qemu_cortex_m3.overlay new file mode 100644 index 0000000000000..4dd05c0fe9975 --- /dev/null +++ b/tests/subsys/usb/host/desc/boards/qemu_cortex_m3.overlay @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + zephyr_uhc0: uhc_vrt0 { + compatible = "zephyr,uhc-virtual"; + + zephyr_udc0: udc_vrt0 { + compatible = "zephyr,udc-virtual"; + num-bidir-endpoints = <8>; + maximum-speed = "high-speed"; + }; + }; +}; diff --git a/tests/subsys/usb/host/desc/prj.conf b/tests/subsys/usb/host/desc/prj.conf new file mode 100644 index 0000000000000..74f0aae233c58 --- /dev/null +++ b/tests/subsys/usb/host/desc/prj.conf @@ -0,0 +1,8 @@ +# Copyright (c) 2025 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_LOG=y +CONFIG_ZTEST=y + +CONFIG_USB_HOST_STACK=y +CONFIG_UHC_DRIVER=y diff --git a/tests/subsys/usb/host/desc/src/main.c b/tests/subsys/usb/host/desc/src/main.c new file mode 100644 index 0000000000000..ee2b6ad47c88e --- /dev/null +++ b/tests/subsys/usb/host/desc/src/main.c @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include "usbh_desc.h" + +#include "test_descriptor.h" + +static const uint8_t test_hub_descriptor[] = {TEST_HUB_DESCRIPTOR}; + +static const uint8_t test_hub_config_descriptor[] = {TEST_HUB_CONFIG_DESCRIPTOR}; +static const uint8_t test_hub_interface_alt0_descriptor[] = {TEST_HUB_INTERFACE_ALT0_DESCRIPTOR}; +static const uint8_t test_hub_interface_alt1_descriptor[] = {TEST_HUB_INTERFACE_ALT1_DESCRIPTOR}; +static const uint8_t test_hub_endpoint_descriptor[] = {TEST_HUB_ENDPOINT_DESCRIPTOR}; + +ZTEST(host_desc, test_desc_get_by_type) +{ + const uint8_t *const start_addr = test_hub_descriptor; + const uint8_t *const end_addr = test_hub_descriptor + sizeof(test_hub_descriptor); + const struct usb_desc_header *desc; + + /* From "test_descriptor.h": + * #0 TEST_HUB_CONFIG_DESCRIPTOR + * #1 TEST_HUB_INTERFACE_ALT0_DESCRIPTOR + * #2 TEST_HUB_ENDPOINT_DESCRIPTOR + * #3 TEST_HUB_INTERFACE_ALT1_DESCRIPTOR + * #4 TEST_HUB_ENDPOINT_DESCRIPTOR + */ + desc = (const struct usb_desc_header *)start_addr; + + /* desc at #0 TEST_HUB_CONFIG_DESCRIPTOR */ + + zassert_mem_equal(desc, test_hub_config_descriptor, + sizeof(test_hub_config_descriptor), + "needs to be able to reach the config descriptor"); + + /* desc at #0 TEST_HUB_CONFIG_DESCRIPTOR */ + + desc = usbh_desc_get_next(desc, end_addr); + zassert_not_null(desc); + zassert_mem_equal(desc, test_hub_interface_alt0_descriptor, + sizeof(test_hub_interface_alt0_descriptor), + "needs to return the first match (interface 0 alt 1) only"); + + /* desc at #1 TEST_HUB_INTERFACE_ALT0_DESCRIPTOR */ + + desc = usbh_desc_get_next_alt_setting(desc, end_addr); + zassert_not_null(desc); + zassert_mem_equal(desc, test_hub_interface_alt1_descriptor, + sizeof(test_hub_interface_alt1_descriptor), + "needs to return the interface 0 alt 1 descriptor"); + + /* desc at #3 TEST_HUB_INTERFACE_ALT1_DESCRIPTOR */ + + desc = usbh_desc_get_next(desc, end_addr); + zassert_not_null(desc); + zassert_mem_equal(desc, test_hub_endpoint_descriptor, + sizeof(test_hub_endpoint_descriptor), + "needs to return the last descriptor (endpoint)"); + + /* desc at #4 TEST_HUB_ENDPOINT_DESCRIPTOR */ + + desc = usbh_desc_get_next(desc, end_addr); + zassert_is_null(desc, + "needs to return NULL when running past the end"); +} + +ZTEST_SUITE(host_desc, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/subsys/usb/host/desc/testcase.yaml b/tests/subsys/usb/host/desc/testcase.yaml new file mode 100644 index 0000000000000..3eef037a273dc --- /dev/null +++ b/tests/subsys/usb/host/desc/testcase.yaml @@ -0,0 +1,9 @@ +tests: + usb.host.desc: + platform_allow: + - native_sim + - native_sim/native/64 + - qemu_cortex_m3 + integration_platforms: + - native_sim + tags: usb From 148a28354e2a585adbd76fed79c4865994b39fdd Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Mon, 8 Dec 2025 22:01:25 +0000 Subject: [PATCH 8/8] usb: host: class: support multiple controllers Only bind to a controller after probing. Wait that a class is matching and assigned to a device to assign a host controller to a class. This allows a class to be used by any of the controller in place. Signed-off-by: Josuah Demangeon --- include/zephyr/usb/usbh.h | 3 +-- subsys/usb/host/usbh_class.c | 4 ++-- subsys/usb/host/usbh_class.h | 4 +--- subsys/usb/host/usbh_class_api.h | 6 ++---- subsys/usb/host/usbh_core.c | 4 ++-- tests/subsys/usb/host/class_api/src/main.c | 3 +-- 6 files changed, 9 insertions(+), 15 deletions(-) diff --git a/include/zephyr/usb/usbh.h b/include/zephyr/usb/usbh.h index fc2f23ce10805..d602fe93ed1f3 100644 --- a/include/zephyr/usb/usbh.h +++ b/include/zephyr/usb/usbh.h @@ -99,8 +99,7 @@ struct usbh_class_filter { */ struct usbh_class_api { /** Host init handler, before any device is connected */ - int (*init)(struct usbh_class_data *const c_data, - struct usbh_context *const uhs_ctx); + int (*init)(struct usbh_class_data *const c_data); /** Request completion event handler */ int (*completion_cb)(struct usbh_class_data *const c_data, struct uhc_transfer *const xfer); diff --git a/subsys/usb/host/usbh_class.c b/subsys/usb/host/usbh_class.c index 7b26fb2dbc381..1a816481ccd77 100644 --- a/subsys/usb/host/usbh_class.c +++ b/subsys/usb/host/usbh_class.c @@ -14,7 +14,7 @@ LOG_MODULE_REGISTER(usbh_class, CONFIG_USBH_LOG_LEVEL); -void usbh_class_init_all(struct usbh_context *const uhs_ctx) +void usbh_class_init_all(void) { int ret; @@ -26,7 +26,7 @@ void usbh_class_init_all(struct usbh_context *const uhs_ctx) continue; } - ret = usbh_class_init(c_data, uhs_ctx); + ret = usbh_class_init(c_data); if (ret != 0) { LOG_WRN("Failed to initialize class %s (%d)", c_data->name, ret); c_node->state = USBH_CLASS_STATE_ERROR; diff --git a/subsys/usb/host/usbh_class.h b/subsys/usb/host/usbh_class.h index 091b15525be51..39d75dcc3d2ab 100644 --- a/subsys/usb/host/usbh_class.h +++ b/subsys/usb/host/usbh_class.h @@ -46,11 +46,9 @@ bool usbh_class_is_matching(struct usbh_class_filter *const filters, /** * @brief Initialize every class instantiated on the system * - * @param[in] uhs_ctx USB Host context to pass to the class. - * * @retval 0 on success or negative error code on failure */ -void usbh_class_init_all(struct usbh_context *const uhs_ctx); +void usbh_class_init_all(void); /** * @brief Probe an USB device function against all available classes. diff --git a/subsys/usb/host/usbh_class_api.h b/subsys/usb/host/usbh_class_api.h index 4aeabfd3d801a..671ac27721a34 100644 --- a/subsys/usb/host/usbh_class_api.h +++ b/subsys/usb/host/usbh_class_api.h @@ -23,16 +23,14 @@ * It can be used to initialize underlying systems. * * @param[in] c_data Pointer to USB host class data - * @param[in] uhs_ctx USB host context to assign to this class * @return 0 on success, negative error code on failure. */ -static inline int usbh_class_init(struct usbh_class_data *const c_data, - struct usbh_context *const uhs_ctx) +static inline int usbh_class_init(struct usbh_class_data *const c_data) { const struct usbh_class_api *api = c_data->api; if (api->init != NULL) { - return api->init(c_data, uhs_ctx); + return api->init(c_data); } return -ENOTSUP; diff --git a/subsys/usb/host/usbh_core.c b/subsys/usb/host/usbh_core.c index 1b30f2079b20d..e09ca96ac8d20 100644 --- a/subsys/usb/host/usbh_core.c +++ b/subsys/usb/host/usbh_core.c @@ -199,8 +199,6 @@ int usbh_init_device_intl(struct usbh_context *const uhs_ctx) sys_dlist_init(&uhs_ctx->udevs); - usbh_class_init_all(uhs_ctx); - return 0; } @@ -222,6 +220,8 @@ static int uhs_pre_init(void) k_thread_name_set(&usbh_thread_data, "usbh_bus"); + usbh_class_init_all(); + return 0; } diff --git a/tests/subsys/usb/host/class_api/src/main.c b/tests/subsys/usb/host/class_api/src/main.c index e107d25c17f37..1eb15eb193706 100644 --- a/tests/subsys/usb/host/class_api/src/main.c +++ b/tests/subsys/usb/host/class_api/src/main.c @@ -52,8 +52,7 @@ static struct test_class_priv test_class_priv = { .state = TEST_CLASS_PRIV_INACTIVE, }; -static int test_class_init(struct usbh_class_data *const c_data, - struct usbh_context *const uhs_ctx) +static int test_class_init(struct usbh_class_data *const c_data) { struct test_class_priv *priv = c_data->priv;