From 9e0b37bc15f73a67e9007242c8751772110dfc23 Mon Sep 17 00:00:00 2001 From: Aiden Hu Date: Wed, 22 Apr 2026 11:08:42 +0800 Subject: [PATCH 1/6] include: usb: uhc: extend usb_device with hub-related information This change extends the usb_device structure to keep track of its hub relationship, including the parent hub device, hub Think Time, port number, and topology level. Signed-off-by: Aiden Hu --- include/zephyr/drivers/usb/uhc.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/zephyr/drivers/usb/uhc.h b/include/zephyr/drivers/usb/uhc.h index 799dd5ae0af48..7139a5f9d4adf 100644 --- a/include/zephyr/drivers/usb/uhc.h +++ b/include/zephyr/drivers/usb/uhc.h @@ -96,6 +96,14 @@ struct usb_device { struct usb_host_ep ep_out[16]; /** Pointers to device IN endpoints */ struct usb_host_ep ep_in[16]; + /** Pointer to the hub to which this device is connected */ + struct usb_device *hub; + /** Device's hub Think Time */ + uint16_t tt; + /** Device's hub port */ + uint8_t hub_port; + /** Device's level (root device = 0) */ + uint8_t level; }; /** From 35847cb4c877371429fdbc0f72e2ebffc79e414d Mon Sep 17 00:00:00 2001 From: Aiden Hu Date: Fri, 3 Apr 2026 11:33:06 +0800 Subject: [PATCH 2/6] usb: host: set level value for the first USB device when the first device is attached, its level should be set as 1. Signed-off-by: Aiden Hu --- subsys/usb/host/usbh_core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/subsys/usb/host/usbh_core.c b/subsys/usb/host/usbh_core.c index 5bc1f63cfe90c..541b4f69012f6 100644 --- a/subsys/usb/host/usbh_core.c +++ b/subsys/usb/host/usbh_core.c @@ -64,6 +64,7 @@ static void dev_connected_handler(struct usbh_context *const ctx, if (ctx->root == NULL) { ctx->root = udev; + udev->level = 1; } usbh_device_connect(ctx, udev); From 00ab6edb3a23a3ce465a0f46cb815dbcbdbfe00a Mon Sep 17 00:00:00 2001 From: Aiden Hu Date: Wed, 8 Apr 2026 14:19:25 +0800 Subject: [PATCH 3/6] include: usb_hub: add missing USB hub definitions This change updates usb_hub.h by adding missing USB Hub class definitions from the specification, including hub class codes, descriptor types, status and change bits, and related data structures. The goal is to make the public header more complete and easier to use for USB hub implementations. Signed-off-by: Aiden Hu --- include/zephyr/usb/class/usb_hub.h | 211 +++++++++++++++++++++++++++-- 1 file changed, 198 insertions(+), 13 deletions(-) diff --git a/include/zephyr/usb/class/usb_hub.h b/include/zephyr/usb/class/usb_hub.h index 176beb9893993..d38749900f16e 100644 --- a/include/zephyr/usb/class/usb_hub.h +++ b/include/zephyr/usb/class/usb_hub.h @@ -1,44 +1,229 @@ /* * Copyright (c) 2022 Emerson Electric Co. - * + * SPDX-FileCopyrightText: Copyright 2026 NXP * SPDX-License-Identifier: Apache-2.0 */ /** * @file - * @brief USB Hub Class device API header + * @brief USB Hub Class public header */ #ifndef ZEPHYR_INCLUDE_USB_CLASS_USB_HUB_H_ #define ZEPHYR_INCLUDE_USB_CLASS_USB_HUB_H_ -/** USB Hub Class Feature Selectors defined in spec. Table 11-17 */ +/** + * @name USB Hub Class Codes + * @{ + */ +/** Hub class code. */ +#define USB_HUB_CLASS_CODE 0x09 +/** Hub subclass code. */ +#define USB_HUB_SUBCLASS_CODE 0x00 +/** Hub protocol code. */ +#define USB_HUB_PROTOCOL_CODE 0x00 +/** @} */ + +/** + * @name USB Hub Descriptor Types + * @{ + */ +/** Hub descriptor type. */ +#define USB_HUB_DESCRIPTOR_TYPE 0x29 +/** @} */ + +/** + * @name USB Hub Class Request Codes. + * + * See Table 11-16 of the specification. + * @{ + */ +/** Get Status request. */ +#define USB_HCREQ_GET_STATUS 0x00 +/** Clear Feature request. */ +#define USB_HCREQ_CLEAR_FEATURE 0x01 +/** Set Feature request. */ +#define USB_HCREQ_SET_FEATURE 0x03 +/** Get Descriptor request. */ +#define USB_HCREQ_GET_DESCRIPTOR 0x06 +/** Set Descriptor request. */ +#define USB_HCREQ_SET_DESCRIPTOR 0x07 +/** Clear TT Buffer request. */ +#define USB_HCREQ_CLEAR_TT_BUFFER 0x08 +/** Reset TT request. */ +#define USB_HCREQ_RESET_TT 0x09 +/** Get TT State request. */ +#define USB_HCREQ_GET_TT_STATE 0x0A +/** Stop TT request. */ +#define USB_HCREQ_STOP_TT 0x0B +/** @} */ + +/** + * @name USB Hub Class Feature Selectors. + * + * See Table 11-17 of the specification. + * @{ + */ +/** Hub local power change feature selector. */ #define USB_HCFS_C_HUB_LOCAL_POWER 0x00 +/** Hub over-current change feature selector. */ #define USB_HCFS_C_HUB_OVER_CURRENT 0x01 +/** Port connection feature selector. */ #define USB_HCFS_PORT_CONNECTION 0x00 +/** Port enable feature selector. */ #define USB_HCFS_PORT_ENABLE 0x01 +/** Port suspend feature selector. */ #define USB_HCFS_PORT_SUSPEND 0x02 +/** Port over-current feature selector. */ #define USB_HCFS_PORT_OVER_CURRENT 0x03 +/** Port reset feature selector. */ #define USB_HCFS_PORT_RESET 0x04 +/** Port power feature selector. */ #define USB_HCFS_PORT_POWER 0x08 +/** Port low speed feature selector. */ #define USB_HCFS_PORT_LOW_SPEED 0x09 +/** Port connection change feature selector. */ #define USB_HCFS_C_PORT_CONNECTION 0x10 +/** Port enable change feature selector. */ #define USB_HCFS_C_PORT_ENABLE 0x11 +/** Port suspend change feature selector. */ #define USB_HCFS_C_PORT_SUSPEND 0x12 +/** Port over-current change feature selector. */ #define USB_HCFS_C_PORT_OVER_CURRENT 0x13 +/** Port reset change feature selector. */ #define USB_HCFS_C_PORT_RESET 0x14 +/** Port test feature selector. */ #define USB_HCFS_PORT_TEST 0x15 +/** Port indicator feature selector. */ #define USB_HCFS_PORT_INDICATOR 0x16 +/** @} */ -/** USB Hub Class Request Codes defined in spec. Table 11-16 */ -#define USB_HCREQ_GET_STATUS 0x00 -#define USB_HCREQ_CLEAR_FEATURE 0x01 -#define USB_HCREQ_SET_FEATURE 0x03 -#define USB_HCREQ_GET_DESCRIPTOR 0x06 -#define USB_HCREQ_SET_DESCRIPTOR 0x07 -#define USB_HCREQ_CLEAR_TT_BUFFER 0x08 -#define USB_HCREQ_RESET_TT 0x09 -#define USB_HCREQ_GET_TT_STATE 0x0A -#define USB_HCREQ_STOP_TT 0x0B +/** + * @name USB Hub Status Field, wHubStatus. + * + * See Table 11-19 of the specification. + * @{ + */ +/** Hub local power status bit. */ +#define USB_HUB_STATUS_LOCAL_POWER BIT(0) +/** Hub over-current status bit. */ +#define USB_HUB_STATUS_OVER_CURRENT BIT(1) +/** @} */ + +/** + * @name USB Hub Change Field, wHubChange. + * + * See Table 11-20 of the specification. + * @{ + */ +/** Hub local power change bit. */ +#define USB_HUB_CHANGE_LOCAL_POWER BIT(0) +/** Hub over-current change bit. */ +#define USB_HUB_CHANGE_OVER_CURRENT BIT(1) +/** @} */ + +/** + * @name USB Hub Port Status Field, wPortStatus. + * + * See Table 11-21 of the specification. + * @{ + */ +/** Port connection status bit. */ +#define USB_HUB_PORT_STATUS_CONNECTION BIT(0) +/** Port enable status bit. */ +#define USB_HUB_PORT_STATUS_ENABLE BIT(1) +/** Port suspend status bit. */ +#define USB_HUB_PORT_STATUS_SUSPEND BIT(2) +/** Port over-current status bit. */ +#define USB_HUB_PORT_STATUS_OVER_CURRENT BIT(3) +/** Port reset status bit. */ +#define USB_HUB_PORT_STATUS_RESET BIT(4) +/** Port power status bit. */ +#define USB_HUB_PORT_STATUS_POWER BIT(8) +/** Port low speed device attached bit. */ +#define USB_HUB_PORT_STATUS_LOW_SPEED BIT(9) +/** Port high speed device attached bit. */ +#define USB_HUB_PORT_STATUS_HIGH_SPEED BIT(10) +/** Port test mode bit. */ +#define USB_HUB_PORT_STATUS_TEST BIT(11) +/** Port indicator control bit. */ +#define USB_HUB_PORT_STATUS_INDICATOR BIT(12) +/** @} */ + +/** + * @name USB Hub Port Change Field, wPortChange. + * + * See Table 11-22 of the specification. + * @{ + */ +/** Port connection change bit. */ +#define USB_HUB_PORT_CHANGE_CONNECTION BIT(0) +/** Port enable change bit. */ +#define USB_HUB_PORT_CHANGE_ENABLE BIT(1) +/** Port suspend change bit. */ +#define USB_HUB_PORT_CHANGE_SUSPEND BIT(2) +/** Port over-current change bit. */ +#define USB_HUB_PORT_CHANGE_OVER_CURRENT BIT(3) +/** Port reset change bit. */ +#define USB_HUB_PORT_CHANGE_RESET BIT(4) +/** @} */ + +/** + * Maximum hub descriptor size. + * 7 bytes (fixed) + max 32 bytes (DeviceRemovable) + max 32 bytes (PortPwrCtrlMask) + */ +#define USB_HUB_DESC_BUF_SIZE 71 + +/** + * @brief Get Think Time value from wHubCharacteristics field. + * + * @param wHubCharacteristics Hub descriptor field wHubCharacteristics. + * + * @return Think time in full-speed bit times (8, 16, 24, or 32). + */ +#define USB_HUB_GET_THINK_TIME(wHubCharacteristics) \ + (((((wHubCharacteristics) & 0x0060U) >> 5) + 1) << 3) + +/** + * USB Hub Descriptor. See Table 11-13 of the specification. + */ +struct usb_hub_descriptor { + /** Descriptor length. */ + uint8_t bDescLength; + /** Descriptor type. */ + uint8_t bDescriptorType; + /** Number of downstream facing ports. */ + uint8_t bNbrPorts; + /** Hub characteristics bitmap. */ + uint16_t wHubCharacteristics; + /** Time from power-on to power-good (in 2ms intervals). */ + uint8_t bPwrOn2PwrGood; + /** Maximum current requirements of the Hub Controller (in mA). */ + uint8_t bHubContrCurrent; + /** Variable length fields follow: + * uint8_t DeviceRemovable[]; + * uint8_t PortPwrCtrlMask[]; + */ +} __packed; + +/** + * USB Hub Status. See Table 11-19 and 11-20 of the specification. + */ +struct usb_hub_status { + /** Hub status bitmap. */ + uint16_t wHubStatus; + /** Hub status change bitmap. */ + uint16_t wHubChange; +} __packed; + +/** + * USB Hub Port Status. See Table 11-21 and 11-22 of the specification. + */ +struct usb_hub_port_status { + /** Port status bitmap. */ + uint16_t wPortStatus; + /** Port status change bitmap. */ + uint16_t wPortChange; +} __packed; #endif /* ZEPHYR_INCLUDE_USB_CLASS_USB_HUB_H_ */ From 9ca28b108caa95fe44601deb1195c8db098e4fa9 Mon Sep 17 00:00:00 2001 From: Aiden Hu Date: Tue, 16 Dec 2025 14:08:20 +0800 Subject: [PATCH 4/6] drivers: usb: uhc: improve peripheral information helper This change enhances USB_HostHelperGetPeripheralInformation() so the MCUX USB host driver can correctly report hub-related topology data, including the parent hub address, port number, nearest high speed hub, HS hub port, hub think time and level. The goal is to ensure proper device identification and routing, especially when full speed or low speed devices are connected behind multi level or high speed hubs. Signed-off-by: Aiden Hu --- drivers/usb/uhc/uhc_mcux_common.c | 39 +++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/drivers/usb/uhc/uhc_mcux_common.c b/drivers/usb/uhc/uhc_mcux_common.c index fd2aea18082d9..a47f6684dd15f 100644 --- a/drivers/usb/uhc/uhc_mcux_common.c +++ b/drivers/usb/uhc/uhc_mcux_common.c @@ -178,14 +178,49 @@ usb_status_t USB_HostHelperGetPeripheralInformation(usb_device_handle deviceHand break; case kUSB_HostGetDeviceHubNumber: + if (udev->hub != NULL) { + *infoValue = udev->hub->addr; + } else { + *infoValue = 0; + } + break; + case kUSB_HostGetDevicePortNumber: + *infoValue = udev->hub_port; + break; + case kUSB_HostGetDeviceHSHubNumber: + for (; udev->hub != NULL; udev = udev->hub) { + if (udev->hub->speed == USB_SPEED_SPEED_HS) { + *infoValue = udev->hub->addr; + return kStatus_USB_Success; + } + } + *infoValue = 0; + break; + case kUSB_HostGetDeviceHSHubPort: - case kUSB_HostGetHubThinkTime: + for (; udev->hub != NULL; udev = udev->hub) { + if (udev->hub->speed == USB_SPEED_SPEED_HS) { + *infoValue = udev->hub->hub_port; + return kStatus_USB_Success; + } + } *infoValue = 0; break; + + case kUSB_HostGetHubThinkTime: { + uint16_t total_think_time = 0; + + for (; udev->hub != NULL; udev = udev->hub) { + total_think_time += udev->hub->tt; + } + *infoValue = total_think_time; + break; + } + case kUSB_HostGetDeviceLevel: - *infoValue = 1; + *infoValue = udev->level; break; case kUSB_HostGetDeviceSpeed: From 4d83ffb68d476db066f075a5755ea093659a1344 Mon Sep 17 00:00:00 2001 From: Aiden Hu Date: Sat, 25 Apr 2026 13:01:06 +0800 Subject: [PATCH 5/6] usb: host: introduce chapter 11 hub control requests Move hub control requests from usbh_ch9 to new usbh_ch11 files to align with USB 2.0 specification structure. Chapter 11 defines hub-specific requests which are distinct from the general USB device requests in Chapter 9. The new implementation provides all hub and port control requests defined in the specification, including feature control, status queries, and descriptor retrieval. This provides the foundation for the hub class driver implementation. Signed-off-by: Aiden Hu --- subsys/usb/host/CMakeLists.txt | 1 + subsys/usb/host/usbh_ch11.c | 267 +++++++++++++++++++++++++++++++++ subsys/usb/host/usbh_ch11.h | 84 +++++++++++ subsys/usb/host/usbh_ch9.c | 30 ---- subsys/usb/host/usbh_ch9.h | 6 - subsys/usb/host/usbh_shell.c | 1 + 6 files changed, 353 insertions(+), 36 deletions(-) create mode 100644 subsys/usb/host/usbh_ch11.c create mode 100644 subsys/usb/host/usbh_ch11.h diff --git a/subsys/usb/host/CMakeLists.txt b/subsys/usb/host/CMakeLists.txt index 72976f4aeea8e..ffa740574d5b6 100644 --- a/subsys/usb/host/CMakeLists.txt +++ b/subsys/usb/host/CMakeLists.txt @@ -16,6 +16,7 @@ zephyr_library_sources( zephyr_library_sources_ifdef( CONFIG_USBH_SHELL + usbh_ch11.c usbh_shell.c ) diff --git a/subsys/usb/host/usbh_ch11.c b/subsys/usb/host/usbh_ch11.c new file mode 100644 index 0000000000000..cb41d2ee690aa --- /dev/null +++ b/subsys/usb/host/usbh_ch11.c @@ -0,0 +1,267 @@ +/* + * SPDX-FileCopyrightText: Copyright 2025 - 2026 NXP + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include "usbh_device.h" +#include "usbh_ch9.h" +#include "usbh_ch11.h" + +LOG_MODULE_REGISTER(usbh_hub, CONFIG_USBH_LOG_LEVEL); + +static int usbh_req_clear_feature_hub(struct usb_device *const udev, const uint8_t feature) +{ + const uint8_t bmRequestType = USB_REQTYPE_DIR_TO_DEVICE << 7 | + USB_REQTYPE_TYPE_CLASS << 5 | + USB_REQTYPE_RECIPIENT_DEVICE; + const uint8_t bRequest = USB_HCREQ_CLEAR_FEATURE; + const uint16_t wValue = feature; + + return usbh_req_setup(udev, + bmRequestType, bRequest, wValue, 0, 0, + NULL); +} + +static int usbh_req_set_feature_port(struct usb_device *const udev, + const uint8_t port_number, const uint8_t feature) +{ + const uint8_t bmRequestType = USB_REQTYPE_DIR_TO_DEVICE << 7 | + USB_REQTYPE_TYPE_CLASS << 5 | + USB_REQTYPE_RECIPIENT_OTHER; + const uint8_t bRequest = USB_HCREQ_SET_FEATURE; + const uint16_t wValue = feature; + const uint16_t wIndex = port_number; + + return usbh_req_setup(udev, + bmRequestType, bRequest, wValue, wIndex, 0, + NULL); +} + +static int usbh_req_clear_feature_port(struct usb_device *const udev, + const uint8_t port_number, const uint8_t feature) +{ + const uint8_t bmRequestType = USB_REQTYPE_DIR_TO_DEVICE << 7 | + USB_REQTYPE_TYPE_CLASS << 5 | + USB_REQTYPE_RECIPIENT_OTHER; + const uint8_t bRequest = USB_HCREQ_CLEAR_FEATURE; + const uint16_t wValue = feature; + const uint16_t wIndex = port_number; + + return usbh_req_setup(udev, + bmRequestType, bRequest, wValue, wIndex, 0, + NULL); +} + +static int usbh_hub_get_status_common(struct usb_device *const udev, + const uint8_t recipient, const uint16_t wIndex, + uint16_t *const wStatus, uint16_t *const wChange) +{ + const uint8_t bmRequestType = USB_REQTYPE_DIR_TO_HOST << 7 | + USB_REQTYPE_TYPE_CLASS << 5 | + recipient; + const uint8_t bRequest = USB_HCREQ_GET_STATUS; + struct net_buf *buf; + int ret; + + buf = usbh_xfer_buf_alloc(udev, 4); + if (buf == NULL) { + LOG_ERR("Failed to allocate buffer for status"); + return -ENOMEM; + } + + ret = usbh_req_setup(udev, + bmRequestType, bRequest, 0, wIndex, 4, + buf); + + if ((ret != 0) || (buf->len < 4)) { + LOG_ERR("Failed to get status or insufficient data (ret=%d, len=%d)", ret, + buf ? buf->len : 0); + *wStatus = 0; + *wChange = 0; + goto cleanup; + } + + *wStatus = net_buf_pull_le16(buf); + *wChange = net_buf_pull_le16(buf); + LOG_DBG("Status: wStatus=0x%04x, wChange=0x%04x", *wStatus, *wChange); + +cleanup: + usbh_xfer_buf_free(udev, buf); + return ret; +} + +void usbh_hub_init_instance(struct usb_hub *const hub_instance, + struct usb_device *const udev) +{ + memset(hub_instance, 0, sizeof(*hub_instance)); + + hub_instance->udev = udev; +} + +void usbh_hub_cleanup_instance(struct usb_hub *hub_instance) +{ + memset(hub_instance, 0, sizeof(*hub_instance)); +} + +int usbh_req_desc_hub(struct usb_device *const udev, + uint8_t *const buffer, + const uint16_t len) +{ + const uint8_t bmRequestType = USB_REQTYPE_DIR_TO_HOST << 7 | + USB_REQTYPE_TYPE_CLASS << 5 | + USB_REQTYPE_RECIPIENT_DEVICE; + const uint8_t bRequest = USB_HCREQ_GET_DESCRIPTOR; + const uint16_t wValue = USB_HUB_DESCRIPTOR_TYPE << 8; + struct net_buf *buf; + int ret; + + buf = usbh_xfer_buf_alloc(udev, len); + if (buf == NULL) { + LOG_ERR("Failed to allocate buffer for hub descriptor"); + return -ENOMEM; + } + + ret = usbh_req_setup(udev, + bmRequestType, bRequest, wValue, 0, len, + buf); + if ((ret != 0) || (buf->len == 0)) { + LOG_ERR("Failed to get hub descriptor"); + goto cleanup; + } + + memcpy(buffer, buf->data, MIN(len, buf->len)); + +cleanup: + usbh_xfer_buf_free(udev, buf); + return ret; +} + +/* Hub features */ +int usbh_req_clear_hcfs_c_hub_local_power(struct usb_device *const udev) +{ + return usbh_req_clear_feature_hub(udev, USB_HCFS_C_HUB_LOCAL_POWER); +} + +int usbh_req_clear_hcfs_c_hub_over_current(struct usb_device *const udev) +{ + return usbh_req_clear_feature_hub(udev, USB_HCFS_C_HUB_OVER_CURRENT); +} + +/* Port features */ +int usbh_req_set_hcfs_penable(struct usb_device *const udev, + const uint8_t port) +{ + return usbh_req_set_feature_port(udev, port, USB_HCFS_PORT_ENABLE); +} + +int usbh_req_clear_hcfs_penable(struct usb_device *const udev, + const uint8_t port) +{ + return usbh_req_clear_feature_port(udev, port, USB_HCFS_PORT_ENABLE); +} + +int usbh_req_set_hcfs_psuspend(struct usb_device *const udev, + const uint8_t port) +{ + return usbh_req_set_feature_port(udev, port, USB_HCFS_PORT_SUSPEND); +} + +int usbh_req_clear_hcfs_psuspend(struct usb_device *const udev, + const uint8_t port) +{ + return usbh_req_clear_feature_port(udev, port, USB_HCFS_PORT_SUSPEND); +} + +int usbh_req_set_hcfs_prst(struct usb_device *const udev, + const uint8_t port) +{ + return usbh_req_set_feature_port(udev, port, USB_HCFS_PORT_RESET); +} + +int usbh_req_clear_hcfs_prst(struct usb_device *const udev, + const uint8_t port) +{ + return usbh_req_clear_feature_port(udev, port, USB_HCFS_PORT_RESET); +} + +int usbh_req_set_hcfs_ppwr(struct usb_device *const udev, + const uint8_t port) +{ + return usbh_req_set_feature_port(udev, port, USB_HCFS_PORT_POWER); +} + +int usbh_req_clear_hcfs_ppwr(struct usb_device *const udev, + const uint8_t port) +{ + return usbh_req_clear_feature_port(udev, port, USB_HCFS_PORT_POWER); +} + +/* Port change features */ +int usbh_req_clear_hcfs_c_pconnection(struct usb_device *const udev, + const uint8_t port) +{ + return usbh_req_clear_feature_port(udev, port, USB_HCFS_C_PORT_CONNECTION); +} + +int usbh_req_clear_hcfs_c_penable(struct usb_device *const udev, + const uint8_t port) +{ + return usbh_req_clear_feature_port(udev, port, USB_HCFS_C_PORT_ENABLE); +} + +int usbh_req_clear_hcfs_c_psuspend(struct usb_device *const udev, + const uint8_t port) +{ + return usbh_req_clear_feature_port(udev, port, USB_HCFS_C_PORT_SUSPEND); +} + +int usbh_req_clear_hcfs_c_pover_current(struct usb_device *const udev, + const uint8_t port) +{ + return usbh_req_clear_feature_port(udev, port, USB_HCFS_C_PORT_OVER_CURRENT); +} + +int usbh_req_clear_hcfs_c_preset(struct usb_device *const udev, + const uint8_t port) +{ + return usbh_req_clear_feature_port(udev, port, USB_HCFS_C_PORT_RESET); +} + +int usbh_req_set_hcfs_ptest(struct usb_device *const udev, + const uint8_t port) +{ + return usbh_req_set_feature_port(udev, port, USB_HCFS_PORT_TEST); +} + +int usbh_req_set_hcfs_pindicator(struct usb_device *const udev, + const uint8_t port) +{ + return usbh_req_set_feature_port(udev, port, USB_HCFS_PORT_INDICATOR); +} + +/* Get port status */ +int usbh_req_get_port_status(struct usb_device *const udev, + const uint8_t port_number, uint16_t *const wPortStatus, + uint16_t *const wPortChange) +{ + LOG_DBG("Querying wPortStatus and wPortChange for port %d", port_number); + + return usbh_hub_get_status_common(udev, USB_REQTYPE_RECIPIENT_OTHER, port_number, + wPortStatus, wPortChange); +} + +/* Get hub status */ +int usbh_req_get_hub_status(struct usb_device *const udev, + uint16_t *const wHubStatus, + uint16_t *const wHubChange) +{ + LOG_DBG("Querying wPortStatus and wPortChange for hub"); + + return usbh_hub_get_status_common(udev, USB_REQTYPE_RECIPIENT_DEVICE, 0, + wHubStatus, wHubChange); +} diff --git a/subsys/usb/host/usbh_ch11.h b/subsys/usb/host/usbh_ch11.h new file mode 100644 index 0000000000000..2634a5fb865fb --- /dev/null +++ b/subsys/usb/host/usbh_ch11.h @@ -0,0 +1,84 @@ +/* + * SPDX-FileCopyrightText: Copyright 2025 - 2026 NXP + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _USBH_HUB_H_ +#define _USBH_HUB_H_ + +#include +#include + +/* Hub state enumeration */ +enum usbh_hub_state { + HUB_STATE_INIT, + HUB_STATE_GET_DESCRIPTOR, + HUB_STATE_OPERATIONAL, + HUB_STATE_ERROR, +}; + +/* Port state enumeration */ +enum usbh_port_state { + PORT_STATE_POWERED_OFF, + PORT_STATE_DISCONNECTED, + PORT_STATE_DISABLED, + PORT_STATE_RESETTING, + PORT_STATE_ENABLED, + PORT_STATE_SUSPENDED, +}; + +/* Hub instance structure */ +struct usb_hub { + struct usb_device *udev; + /* Hub port number */ + uint8_t ports; + union { + struct usb_hub_descriptor hub_desc; + uint8_t hub_desc_buf[USB_HUB_DESC_BUF_SIZE]; + }; + /* Status buffers */ + struct usb_hub_status status; + struct usb_hub_port_status port_status; +}; + +void usbh_hub_init_instance(struct usb_hub *const hub_instance, + struct usb_device *const udev); + +void usbh_hub_cleanup_instance(struct usb_hub *hub_instance); + +int usbh_req_desc_hub(struct usb_device *const udev, + uint8_t *const buffer, + const uint16_t len); + +/* Hub features */ +int usbh_req_clear_hcfs_c_hub_local_power(struct usb_device *const udev); +int usbh_req_clear_hcfs_c_hub_over_current(struct usb_device *const udev); + +/* Port features */ +int usbh_req_set_hcfs_penable(struct usb_device *const udev, const uint8_t port); +int usbh_req_clear_hcfs_penable(struct usb_device *const udev, const uint8_t port); +int usbh_req_set_hcfs_psuspend(struct usb_device *const udev, const uint8_t port); +int usbh_req_clear_hcfs_psuspend(struct usb_device *const udev, const uint8_t port); +int usbh_req_set_hcfs_prst(struct usb_device *const udev, const uint8_t port); +int usbh_req_clear_hcfs_prst(struct usb_device *const udev, const uint8_t port); +int usbh_req_set_hcfs_ppwr(struct usb_device *const udev, const uint8_t port); +int usbh_req_clear_hcfs_ppwr(struct usb_device *const udev, const uint8_t port); + +/* Port change features */ +int usbh_req_clear_hcfs_c_pconnection(struct usb_device *const udev, const uint8_t port); +int usbh_req_clear_hcfs_c_penable(struct usb_device *const udev, const uint8_t port); +int usbh_req_clear_hcfs_c_psuspend(struct usb_device *const udev, const uint8_t port); +int usbh_req_clear_hcfs_c_pover_current(struct usb_device *const udev, const uint8_t port); +int usbh_req_clear_hcfs_c_preset(struct usb_device *const udev, const uint8_t port); +int usbh_req_set_hcfs_ptest(struct usb_device *const udev, const uint8_t port); +int usbh_req_set_hcfs_pindicator(struct usb_device *const udev, const uint8_t port); + +int usbh_req_get_port_status(struct usb_device *const udev, + const uint8_t port_number, uint16_t *const wPortStatus, + uint16_t *const wPortChange); + +int usbh_req_get_hub_status(struct usb_device *const udev, + uint16_t *const wHubStatus, + uint16_t *const wHubChange); + +#endif /* _USBH_HUB_H_ */ diff --git a/subsys/usb/host/usbh_ch9.c b/subsys/usb/host/usbh_ch9.c index 80783e430559b..f32b1f51e992e 100644 --- a/subsys/usb/host/usbh_ch9.c +++ b/subsys/usb/host/usbh_ch9.c @@ -281,33 +281,3 @@ int usbh_req_clear_sfs_halt(struct usb_device *const udev, const uint8_t ep) bmRequestType, bRequest, wValue, wIndex, 0, NULL); } - -int usbh_req_set_hcfs_ppwr(struct usb_device *const udev, - const uint8_t port) -{ - const uint8_t bmRequestType = USB_REQTYPE_DIR_TO_DEVICE << 7 | - USB_REQTYPE_TYPE_CLASS << 5 | - USB_REQTYPE_RECIPIENT_OTHER << 0; - const uint8_t bRequest = USB_HCREQ_SET_FEATURE; - const uint16_t wValue = USB_HCFS_PORT_POWER; - const uint16_t wIndex = port; - - return usbh_req_setup(udev, - bmRequestType, bRequest, wValue, wIndex, 0, - NULL); -} - -int usbh_req_set_hcfs_prst(struct usb_device *const udev, - const uint8_t port) -{ - const uint8_t bmRequestType = USB_REQTYPE_DIR_TO_DEVICE << 7 | - USB_REQTYPE_TYPE_CLASS << 5 | - USB_REQTYPE_RECIPIENT_OTHER << 0; - const uint8_t bRequest = USB_HCREQ_SET_FEATURE; - const uint16_t wValue = USB_HCFS_PORT_RESET; - const uint16_t wIndex = port; - - return usbh_req_setup(udev, - bmRequestType, bRequest, wValue, wIndex, 0, - NULL); -} diff --git a/subsys/usb/host/usbh_ch9.h b/subsys/usb/host/usbh_ch9.h index 19879dae6e2df..31f058b5fbf4c 100644 --- a/subsys/usb/host/usbh_ch9.h +++ b/subsys/usb/host/usbh_ch9.h @@ -62,10 +62,4 @@ int usbh_req_set_sfs_halt(struct usb_device *const udev, const uint8_t ep); int usbh_req_clear_sfs_halt(struct usb_device *const udev, const uint8_t ep); -int usbh_req_set_hcfs_ppwr(const struct usb_device *udev, - const uint8_t port); - -int usbh_req_set_hcfs_prst(const struct usb_device *udev, - const uint8_t port); - #endif /* ZEPHYR_INCLUDE_USBH_CH9_H */ diff --git a/subsys/usb/host/usbh_shell.c b/subsys/usb/host/usbh_shell.c index e2ff574bf5c66..29abf4283a47a 100644 --- a/subsys/usb/host/usbh_shell.c +++ b/subsys/usb/host/usbh_shell.c @@ -14,6 +14,7 @@ #include "usbh_device.h" #include "usbh_ch9.h" +#include "usbh_ch11.h" #include "usbh_desc.h" #include From 7f52f69c1f80b4c2cfb70ae711bf904d584c99ad Mon Sep 17 00:00:00 2001 From: Aiden Hu Date: Fri, 8 May 2026 22:40:17 +0800 Subject: [PATCH 6/6] usb: host: hub: implement usb host hub class Add usbh_hub_mgr to enable connecting USB hubs and managing downstream devices through multi-tier hub topology. The hub manager implements two state machines for hub lifecycle management. The hub state machine handles hub enumeration, descriptor retrieval, and port power configuration. The port state machine manages device connection detection, port reset sequences, device enumeration coordination, and disconnection handling. Hub status changes are monitored through interrupt endpoint with automatic transfer resubmission. The implementation supports recursive hub topology with configurable chain depth limits. When a hub is removed, all downstream devices and child hubs are recursively disconnected to maintain topology consistency. This builds upon Chapter 11 control requests to provide complete hub class driver functionality in usbh_hub_mgr.c and usbh_hub_mgr.h. Signed-off-by: Aiden Hu --- subsys/usb/host/CMakeLists.txt | 10 +- subsys/usb/host/class/Kconfig | 2 + subsys/usb/host/class/Kconfig.hub | 71 ++ subsys/usb/host/class/usbh_hub_mgr.c | 1133 ++++++++++++++++++++++++++ subsys/usb/host/class/usbh_hub_mgr.h | 69 ++ 5 files changed, 1284 insertions(+), 1 deletion(-) create mode 100644 subsys/usb/host/class/Kconfig.hub create mode 100644 subsys/usb/host/class/usbh_hub_mgr.c create mode 100644 subsys/usb/host/class/usbh_hub_mgr.h diff --git a/subsys/usb/host/CMakeLists.txt b/subsys/usb/host/CMakeLists.txt index ffa740574d5b6..97ac4e8d35825 100644 --- a/subsys/usb/host/CMakeLists.txt +++ b/subsys/usb/host/CMakeLists.txt @@ -14,9 +14,12 @@ zephyr_library_sources( usbh_class.c ) +if(CONFIG_USBH_SHELL OR CONFIG_USBH_HUB_CLASS) + zephyr_library_sources(usbh_ch11.c) +endif() + zephyr_library_sources_ifdef( CONFIG_USBH_SHELL - usbh_ch11.c usbh_shell.c ) @@ -25,6 +28,11 @@ zephyr_library_sources_ifdef( class/usbh_uvc.c ) +zephyr_library_sources_ifdef( + CONFIG_USBH_HUB_CLASS + class/usbh_hub_mgr.c +) + zephyr_library_sources_ifdef( CONFIG_USBIP usbip.c diff --git a/subsys/usb/host/class/Kconfig b/subsys/usb/host/class/Kconfig index 1a68b415273e3..bd314cd029810 100644 --- a/subsys/usb/host/class/Kconfig +++ b/subsys/usb/host/class/Kconfig @@ -1,4 +1,6 @@ # SPDX-FileCopyrightText: Copyright Nordic Semiconductor ASA +# SPDX-FileCopyrightText: Copyright 2025 - 2026 NXP # SPDX-License-Identifier: Apache-2.0 rsource "Kconfig.uvc" +rsource "Kconfig.hub" diff --git a/subsys/usb/host/class/Kconfig.hub b/subsys/usb/host/class/Kconfig.hub new file mode 100644 index 0000000000000..cf4211fd89c4f --- /dev/null +++ b/subsys/usb/host/class/Kconfig.hub @@ -0,0 +1,71 @@ +# SPDX-FileCopyrightText: Copyright 2025 - 2026 NXP +# SPDX-License-Identifier: Apache-2.0 + +config USBH_HUB_CLASS + bool "USB Host Hub Class Driver" + help + Enable USB Host Hub Class driver support. + This allows connecting USB hubs and managing downstream devices. + +if USBH_HUB_CLASS + +config USBH_HUB_INSTANCES_COUNT + int "Maximum Hub count" + default 7 + range 1 16 + help + Maximum number of hub devices supported in the entire USB topology. + +config USBH_HUB_MAX_LEVELS + int "Maximum Hub chain depth" + default 5 + range 1 5 + help + Maximum number of non-root hubs allowed in a single communication path + between the host and any device. USB specification allows up to five + non-root hubs per path, which means the deepest path can include 5 hubs. + +config USBH_HUB_PORT_RESET_TIMES + int "Hub port reset retry count" + default 2 + range 1 10 + help + Maximum number of port reset retries before giving up. + +config USBH_HUB_CONFIG_WAIT_MS + int "Hub configuration wait interval (ms)" + default 100 + range 50 1000 + help + Polling interval when waiting for hub to be configured. + Typical hub configuration completes within 100ms. + +config USBH_HUB_ENUM_RETRY_DELAY_MS + int "Enumeration retry delay (ms)" + default 500 + range 100 3000 + help + Delay before retrying device enumeration after failure. + +config USBH_HUB_CHILD_PROBE_DELAY_MS + int "Child hub probe delay (ms)" + default 50 + range 10 500 + help + Delay to wait for child hub initialization. + +config USBH_HUB_ERROR_RECOVERY_DELAY_MS + int "Error recovery delay (ms)" + default 1000 + range 500 5000 + help + Delay before retrying after serious errors. + Longer delays help avoid flooding the bus. + +module = USBH_HUB +module-str = usbh hub +default-count = 1 +source "subsys/logging/Kconfig.template.log_config" +source "subsys/usb/common/Kconfig.template.instances_count" + +endif # USBH_HUB_CLASS diff --git a/subsys/usb/host/class/usbh_hub_mgr.c b/subsys/usb/host/class/usbh_hub_mgr.c new file mode 100644 index 0000000000000..98754a1af2921 --- /dev/null +++ b/subsys/usb/host/class/usbh_hub_mgr.c @@ -0,0 +1,1133 @@ +/* + * SPDX-FileCopyrightText: Copyright 2025 - 2026 NXP + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include "usbh_class.h" +#include "usbh_device.h" +#include "usbh_desc.h" +#include "usbh_ch9.h" +#include "usbh_ch11.h" +#include "usbh_hub_mgr.h" + +LOG_MODULE_REGISTER(usbh_hub_mgr, CONFIG_USBH_HUB_LOG_LEVEL); + +static struct { + uint8_t total_hubs; + sys_slist_t hub_list; + struct k_mutex lock; + struct usbh_context *uhs_ctx; + struct usbh_hub_mgr_data *processing_hub; +} hub_mgr; + +static int hub_mgr_interrupt_in_cb(struct usb_device *const dev, + struct uhc_transfer *const xfer); + +static int hub_mgr_resubmit_interrupt_in(struct usbh_hub_mgr_data *hub_mgr_data, + struct uhc_transfer *xfer) +{ + struct net_buf *buf; + int ret; + + if (!hub_mgr_data->connected || (hub_mgr_data->int_ep == NULL) || + hub_mgr_data->being_removed) { + usbh_xfer_free(hub_mgr_data->hub_instance.udev, xfer); + return -ENODEV; + } + + /* Allocate buffer for next transfer */ + buf = usbh_xfer_buf_alloc(hub_mgr_data->hub_instance.udev, + sys_le16_to_cpu(hub_mgr_data->int_ep->wMaxPacketSize)); + if (buf == NULL) { + LOG_ERR("Failed to allocate interrupt IN buffer"); + usbh_xfer_free(hub_mgr_data->hub_instance.udev, xfer); + return -ENOMEM; + } + + xfer->buf = buf; + + ret = usbh_xfer_enqueue(hub_mgr_data->hub_instance.udev, xfer); + if (ret != 0) { + LOG_ERR("Failed to resubmit interrupt IN transfer: %d", ret); + usbh_xfer_buf_free(hub_mgr_data->hub_instance.udev, buf); + usbh_xfer_free(hub_mgr_data->hub_instance.udev, xfer); + return ret; + } + + hub_mgr_data->int_active = true; + return 0; +} + +static int hub_mgr_start_interrupt(struct usbh_hub_mgr_data *hub_mgr_data) +{ + struct uhc_transfer *xfer; + struct net_buf *buf; + int ret; + + if (!hub_mgr_data || hub_mgr_data->being_removed || hub_mgr_data->int_active) { + return -EINVAL; + } + + if (hub_mgr_data->state != HUB_STATE_OPERATIONAL) { + return -ENOENT; + } + + if (hub_mgr_data->int_ep == NULL) { + LOG_ERR("No interrupt endpoint available"); + return -ENODEV; + } + + xfer = usbh_xfer_alloc(hub_mgr_data->hub_instance.udev, + hub_mgr_data->int_ep->bEndpointAddress, + hub_mgr_interrupt_in_cb, + (void *)hub_mgr_data); + if (xfer == NULL) { + LOG_ERR("Failed to allocate interrupt transfer"); + return -ENOMEM; + } + + hub_mgr_data->interrupt_transfer = xfer; + + buf = usbh_xfer_buf_alloc(hub_mgr_data->hub_instance.udev, + sys_le16_to_cpu(hub_mgr_data->int_ep->wMaxPacketSize)); + if (buf == NULL) { + LOG_ERR("Failed to allocate interrupt buffer"); + usbh_xfer_free(hub_mgr_data->hub_instance.udev, xfer); + return -ENOMEM; + } + + xfer->buf = buf; + + ret = usbh_xfer_enqueue(hub_mgr_data->hub_instance.udev, xfer); + if (ret != 0) { + LOG_ERR("Failed to enqueue interrupt transfer: %d", ret); + usbh_xfer_buf_free(hub_mgr_data->hub_instance.udev, buf); + usbh_xfer_free(hub_mgr_data->hub_instance.udev, xfer); + return ret; + } + + hub_mgr_data->interrupt_transfer = xfer; + hub_mgr_data->int_active = true; + + return 0; +} + +static struct usbh_hub_mgr_data *const find_hub_mgr_by_udev(struct usb_device *udev) +{ + struct usbh_hub_mgr_data *hub_mgr_data; + + k_mutex_lock(&hub_mgr.lock, K_FOREVER); + + SYS_SLIST_FOR_EACH_CONTAINER(&hub_mgr.hub_list, hub_mgr_data, node) { + if (hub_mgr_data->hub_instance.udev == udev) { + k_mutex_unlock(&hub_mgr.lock); + return hub_mgr_data; + } + } + + k_mutex_unlock(&hub_mgr.lock); + + return NULL; +} + +static void hub_mgr_process_data(struct usbh_hub_mgr_data *const hub_mgr_data) +{ + struct usb_hub_status *hub_sts; + uint16_t hub_status; + uint16_t hub_change; + uint8_t port_index; + int ret; + + k_mutex_lock(&hub_mgr_data->lock, K_FOREVER); + + if (hub_mgr_data->being_removed) { + k_mutex_unlock(&hub_mgr_data->lock); + return; + } + + if (hub_mgr_data->state != HUB_STATE_OPERATIONAL) { + LOG_DBG("Hub level %d not operational yet, deferring interrupt", + hub_mgr_data->hub_instance.udev->level); + + k_mutex_unlock(&hub_mgr_data->lock); + if (!hub_mgr_data->int_active && !hub_mgr_data->being_removed) { + ret = hub_mgr_start_interrupt(hub_mgr_data); + if (ret != 0) { + LOG_ERR("Failed to start interrupt monitoring: %d", ret); + } + } + + return; + } + + for (port_index = 0; port_index <= hub_mgr_data->hub_instance.ports; ++port_index) { + if (((0x01U << (port_index & 0x07U)) & + (hub_mgr_data->int_buffer[port_index >> 3U])) == 0) { + continue; + } + + if (port_index != 0) { + if (hub_mgr.processing_hub != NULL && + hub_mgr.processing_hub != hub_mgr_data) { + continue; + } + + hub_mgr.processing_hub = hub_mgr_data; + hub_mgr_data->current_port = port_index; + + LOG_DBG("Hub level %d port %d status changed, starting processing", + hub_mgr_data->hub_instance.udev->level, port_index); + + k_mutex_unlock(&hub_mgr_data->lock); + + k_work_submit(&hub_mgr_data->port_work.work); + return; + } + + hub_sts = &hub_mgr_data->hub_instance.status; + LOG_INF("Hub level %d status changed, processing", + hub_mgr_data->hub_instance.udev->level); + ret = usbh_req_get_hub_status(hub_mgr_data->hub_instance.udev, + &hub_status, + &hub_change); + if (ret != 0) { + LOG_ERR("Failed to get hub status: %d", ret); + continue; + } + + hub_sts->wHubStatus = hub_status; + hub_sts->wHubChange = hub_change; + + LOG_DBG("Hub status: 0x%04x, change: 0x%04x", hub_sts->wHubStatus, + hub_sts->wHubChange); + + if ((hub_sts->wHubChange & USB_HUB_CHANGE_LOCAL_POWER) != 0) { + LOG_WRN("Hub local power status changed"); + ret = usbh_req_clear_hcfs_c_hub_local_power( + hub_mgr_data->hub_instance.udev); + if (ret != 0) { + LOG_ERR("Failed to clear hub local power feature: %d", ret); + } + } + + if ((hub_sts->wHubChange & USB_HUB_CHANGE_OVER_CURRENT) != 0) { + LOG_ERR("Hub over-current detected!"); + ret = usbh_req_clear_hcfs_c_hub_over_current( + hub_mgr_data->hub_instance.udev); + if (ret != 0) { + LOG_ERR("Failed to clear hub over-current feature: %d", + ret); + } + } + } + + k_mutex_unlock(&hub_mgr_data->lock); + + if (!hub_mgr_data->int_active && !hub_mgr_data->being_removed) { + ret = hub_mgr_start_interrupt(hub_mgr_data); + if (ret != 0) { + LOG_ERR("Failed to start interrupt monitoring: %d", ret); + } + } +} + +static int hub_mgr_interrupt_in_cb(struct usb_device *const dev, + struct uhc_transfer *const xfer) +{ + struct usbh_hub_mgr_data *const hub_mgr_data = (void *)xfer->priv; + struct net_buf *buf = xfer->buf; + int ret = 0; + + if (hub_mgr_data == NULL) { + goto cleanup_and_exit; + } + + k_mutex_lock(&hub_mgr_data->lock, K_FOREVER); + + if (hub_mgr_data->being_removed) { + k_mutex_unlock(&hub_mgr_data->lock); + goto cleanup_and_exit; + } + + hub_mgr_data->int_active = false; + + if (buf == NULL || buf->len == 0) { + LOG_ERR("Hub level %d interrupt transfer failed or no data", + hub_mgr_data->hub_instance.udev->level); + hub_mgr.processing_hub = NULL; + hub_mgr_data->current_port = 0; + k_mutex_unlock(&hub_mgr_data->lock); + goto resubmit; + } + + memcpy(hub_mgr_data->int_buffer, buf->data, + MIN(buf->len, sizeof(hub_mgr_data->int_buffer))); + + LOG_DBG("Hub level %d interrupt data received: length=%d", + hub_mgr_data->hub_instance.udev->level, + buf->len); + + k_mutex_unlock(&hub_mgr_data->lock); + + hub_mgr_process_data(hub_mgr_data); + + net_buf_unref(buf); + return 0; + +resubmit: + /* Resubmit transfer */ + if (hub_mgr_data->connected && hub_mgr_data->state == HUB_STATE_OPERATIONAL) { + ret = hub_mgr_resubmit_interrupt_in(hub_mgr_data, xfer); + if (ret != 0) { + LOG_ERR("Failed to resubmit interrupt transfer: %d", ret); + } + } else { + usbh_xfer_free(hub_mgr_data->hub_instance.udev, xfer); + } + return 0; + +cleanup_and_exit: + if (buf != NULL) { + net_buf_unref(buf); + } + usbh_xfer_free(dev, xfer); + return 0; +} + +static void hub_mgr_process(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct usbh_hub_mgr_data *hub_mgr_data = + CONTAINER_OF(dwork, struct usbh_hub_mgr_data, hub_work); + struct usb_device *udev; + uint16_t total_hub_desc_len = 0; + bool prime_interrupt = false; + bool process_success = false; + int ret; + + k_mutex_lock(&hub_mgr_data->lock, K_FOREVER); + + if (hub_mgr_data->being_removed) { + k_mutex_unlock(&hub_mgr_data->lock); + return; + } + + switch (hub_mgr_data->hub_status) { + case HUB_RUN_IDLE: + break; + + case HUB_RUN_INVALID: + LOG_ERR("Hub in invalid state"); + hub_mgr_data->state = HUB_STATE_ERROR; + break; + + case HUB_RUN_INIT_HUB: + LOG_DBG("Getting 7-byte hub descriptor"); + + ret = usbh_req_desc_hub(hub_mgr_data->hub_instance.udev, + hub_mgr_data->hub_instance.hub_desc_buf, + sizeof(struct usb_hub_descriptor)); + if (ret != 0) { + LOG_ERR("Failed to get hub descriptor: %d", ret); + hub_mgr_data->hub_status = HUB_RUN_INVALID; + break; + } + + udev = hub_mgr_data->hub_instance.udev; + hub_mgr_data->hub_instance.ports = hub_mgr_data->hub_instance.hub_desc.bNbrPorts; + + LOG_DBG("Hub has %d ports", hub_mgr_data->hub_instance.ports); + + /* Store hub think time */ + udev->tt = USB_HUB_GET_THINK_TIME( + sys_le16_to_cpu(hub_mgr_data->hub_instance.hub_desc.wHubCharacteristics)); + LOG_DBG("hub think time: 0x%04x", udev->tt); + + total_hub_desc_len = 7 + ((hub_mgr_data->hub_instance.ports + 7) >> 3) + 1; + /* Get full hub descriptor */ + LOG_DBG("Getting full hub descriptor (length=%d)", total_hub_desc_len); + ret = usbh_req_desc_hub(hub_mgr_data->hub_instance.udev, + hub_mgr_data->hub_instance.hub_desc_buf, + total_hub_desc_len); + if (ret != 0) { + LOG_ERR("Failed to get full hub descriptor: %d", ret); + hub_mgr_data->hub_status = HUB_RUN_INVALID; + break; + } + + /* Allocate port list if not already done */ + if (hub_mgr_data->port_list == NULL) { + hub_mgr_data->port_list = k_malloc(hub_mgr_data->hub_instance.ports * + sizeof(struct usb_hub_port)); + if (hub_mgr_data->port_list == NULL) { + LOG_ERR("Failed to allocate port list"); + hub_mgr_data->hub_status = HUB_RUN_INVALID; + break; + } + hub_mgr_data->port_index = 0; + } + + for (uint8_t i = 0; i < hub_mgr_data->hub_instance.ports; i++) { + hub_mgr_data->port_list[i].udev = NULL; + hub_mgr_data->port_list[i].reset_count = CONFIG_USBH_HUB_PORT_RESET_TIMES; + hub_mgr_data->port_list[i].status = HUB_PORT_RUN_WAIT_PORT_CHANGE; + hub_mgr_data->port_list[i].state = PORT_STATE_POWERED_OFF; + hub_mgr_data->port_list[i].num = i + 1; + hub_mgr_data->port_list[i].speed = USB_SPEED_SPEED_FS; + } + + while (hub_mgr_data->port_index < hub_mgr_data->hub_instance.ports) { + hub_mgr_data->port_index++; + + LOG_DBG("Setting port %d power", hub_mgr_data->port_index); + ret = usbh_req_set_hcfs_ppwr(hub_mgr_data->hub_instance.udev, + hub_mgr_data->port_index); + if (ret != 0) { + LOG_ERR("Failed to set port %d power: %d", + hub_mgr_data->port_index, ret); + } else { + hub_mgr_data->port_list[hub_mgr_data->port_index - 1].state = + PORT_STATE_DISCONNECTED; + } + } + + hub_mgr_data->hub_status = HUB_RUN_IDLE; + hub_mgr_data->state = HUB_STATE_OPERATIONAL; + prime_interrupt = true; + process_success = true; + break; + + default: + LOG_ERR("Unknown hub status: %d", hub_mgr_data->hub_status); + hub_mgr_data->hub_status = HUB_RUN_INVALID; + hub_mgr_data->state = HUB_STATE_ERROR; + break; + } + + k_mutex_unlock(&hub_mgr_data->lock); + + if (prime_interrupt) { + hub_mgr_data->hub_status = HUB_RUN_IDLE; + if (!hub_mgr_data->int_active && !hub_mgr_data->being_removed) { + ret = hub_mgr_start_interrupt(hub_mgr_data); + if (ret != 0) { + LOG_ERR("Failed to start interrupt monitoring: %d", ret); + } + } + } else if (!process_success && hub_mgr_data->hub_status != HUB_RUN_INVALID) { + hub_mgr_data->hub_status = HUB_RUN_INVALID; + hub_mgr_data->state = HUB_STATE_ERROR; + } +} + +static void hub_mgr_recursive_disconnect(struct usbh_hub_mgr_data *const hub_mgr_data) +{ + struct usbh_hub_mgr_data *child_hub; + struct usb_device *port_udev; + + LOG_DBG("Recursively disconnecting Hub level %d and all children", + hub_mgr_data->hub_instance.udev->level); + + k_work_cancel_delayable(&hub_mgr_data->port_work); + k_work_cancel_delayable(&hub_mgr_data->hub_work); + + k_mutex_lock(&hub_mgr_data->lock, K_FOREVER); + + hub_mgr_data->int_active = false; + + if (hub_mgr.processing_hub == hub_mgr_data) { + hub_mgr.processing_hub = NULL; + hub_mgr_data->current_port = 0; + } + + k_mutex_unlock(&hub_mgr_data->lock); + + for (uint8_t i = 0; i < hub_mgr_data->hub_instance.ports; i++) { + if (hub_mgr_data->port_list && hub_mgr_data->port_list[i].udev) { + + port_udev = hub_mgr_data->port_list[i].udev; + child_hub = find_hub_mgr_by_udev(port_udev); + if (child_hub != NULL) { + LOG_DBG("Found child Hub on port %d, recursing", i + 1); + hub_mgr_recursive_disconnect(child_hub); + + } else { + LOG_DBG("Disconnecting device on port %d", i + 1); + usbh_device_disconnect(hub_mgr_data->uhs_ctx, port_udev); + } + + hub_mgr_data->port_list[i].udev = NULL; + hub_mgr_data->port_list[i].state = PORT_STATE_POWERED_OFF; + } + } + + if (!hub_mgr_data->being_removed) { + LOG_DBG("Triggering Hub level %d removal", hub_mgr_data->hub_instance.udev->level); + usbh_device_disconnect(hub_mgr_data->uhs_ctx, hub_mgr_data->hub_instance.udev); + } +} + +static void hub_print_info(struct usbh_hub_mgr_data *const hub_mgr_data) +{ + const struct usb_device_descriptor *const dev_desc = + &hub_mgr_data->hub_instance.udev->dev_desc; + struct usb_device *udev = hub_mgr_data->hub_instance.udev; + + LOG_INF("=== USB Hub Information ==="); + LOG_INF("Hub Level: %d", udev->level); + LOG_INF("Vendor ID: 0x%04x", sys_le16_to_cpu(dev_desc->idVendor)); + LOG_INF("Product ID: 0x%04x", sys_le16_to_cpu(dev_desc->idProduct)); + LOG_INF("Device Address: %d", udev->addr); + if (udev->hub) { + LOG_INF("Parent Hub Level: %d, Port: %d", udev->hub->level, + udev->hub_port); + } else { + LOG_INF("Root Hub (no parent)"); + } + LOG_INF("==========================="); +} + +static int hub_establish_parent_child_relationship(struct usbh_hub_mgr_data *const parent_hub, + struct usbh_hub_mgr_data *const child_hub, + uint8_t port_num) +{ + if (child_hub->hub_instance.udev->level > CONFIG_USBH_HUB_MAX_LEVELS) { + LOG_ERR("Hub chain depth limit exceeded (%d > %d), removing hub", + child_hub->hub_instance.udev->level, + CONFIG_USBH_HUB_MAX_LEVELS); + + k_mutex_lock(&child_hub->lock, K_FOREVER); + child_hub->being_removed = true; + child_hub->state = HUB_STATE_ERROR; + k_mutex_unlock(&child_hub->lock); + + hub_mgr_recursive_disconnect(child_hub); + + return -ENOSPC; + } + + k_mutex_lock(&child_hub->lock, K_FOREVER); + child_hub->hub_instance.udev->hub = parent_hub->hub_instance.udev; + child_hub->hub_instance.udev->hub_port = port_num; + sys_slist_append(&parent_hub->child_hubs, &child_hub->child_node); + k_mutex_unlock(&child_hub->lock); + + hub_print_info(child_hub); + + return 0; +} + +static int enumerate_port_device(struct usbh_hub_mgr_data *hub_mgr_data, + struct usb_hub_port *port_instance, + uint8_t port_num, + struct usb_hub_port_status *port_sts, + bool *is_child_hub) +{ + struct usbh_hub_mgr_data *child_hub; + struct usb_device *udev; + + *is_child_hub = false; + + if ((port_sts->wPortStatus & USB_HUB_PORT_STATUS_HIGH_SPEED) != 0) { + port_instance->speed = USB_SPEED_SPEED_HS; + } else if ((port_sts->wPortStatus & USB_HUB_PORT_STATUS_LOW_SPEED) != 0) { + port_instance->speed = USB_SPEED_SPEED_LS; + } else { + port_instance->speed = USB_SPEED_SPEED_FS; + } + + LOG_INF("Device ready on port %d (speed: %s)", port_num, + port_instance->speed == USB_SPEED_SPEED_HS ? "HIGH" : + port_instance->speed == USB_SPEED_SPEED_LS ? "LOW" : "FULL"); + + udev = usbh_device_alloc(hub_mgr_data->uhs_ctx); + if (udev == NULL) { + LOG_ERR("Device enumeration failed for port %d", port_num); + return -ENOMEM; + } + + udev->hub_port = port_num; + udev->speed = port_instance->speed; + udev->level = hub_mgr_data->hub_instance.udev->level + 1; + udev->hub = hub_mgr_data->hub_instance.udev; + + usbh_device_connect(hub_mgr_data->uhs_ctx, udev); + LOG_DBG("Device enumeration completed for port %d, addr=%d", port_num, udev->addr); + + port_instance->udev = udev; + port_instance->status = HUB_PORT_RUN_PORT_ATTACHED; + + child_hub = find_hub_mgr_by_udev(port_instance->udev); + if (child_hub != NULL) { + port_instance->status = HUB_PORT_RUN_CHECK_CHILD_HUB; + *is_child_hub = true; + } + + return 0; +} + +static void hub_port_process(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct usbh_hub_mgr_data *const hub_mgr_data = + CONTAINER_OF(dwork, struct usbh_hub_mgr_data, port_work); + struct usb_hub_port *port_instance = NULL; + struct usb_hub_port_status *port_sts; + struct usbh_hub_mgr_data *child_hub; + struct usb_device *udev; + bool restart_interrupt = false; + bool process_complete = false; + bool is_child_hub = false; + uint16_t port_status; + uint16_t port_change; + uint8_t port_num; + int ret; + + port_num = hub_mgr_data->current_port; + if (port_num == 0 || port_num > hub_mgr_data->hub_instance.ports || + hub_mgr_data->port_list == NULL) { + LOG_ERR("Invalid port state for processing"); + goto exit_processing; + } + + port_instance = &hub_mgr_data->port_list[port_num - 1]; + + k_mutex_lock(&hub_mgr_data->lock, K_FOREVER); + + if (hub_mgr_data->being_removed) { + k_mutex_unlock(&hub_mgr_data->lock); + return; + } + + if (hub_mgr_data->hub_instance.udev == NULL || + hub_mgr_data->hub_instance.udev->state != USB_STATE_CONFIGURED) { + LOG_ERR("Hub device not ready"); + k_work_reschedule(&hub_mgr_data->port_work, K_MSEC(CONFIG_USBH_HUB_CONFIG_WAIT_MS)); + k_mutex_unlock(&hub_mgr_data->lock); + return; + } + + k_mutex_unlock(&hub_mgr_data->lock); + + LOG_DBG("Processing port %d, status=%d", port_num, port_instance->status); + + switch (port_instance->status) { + case HUB_PORT_RUN_WAIT_PORT_CHANGE: { + port_sts = &hub_mgr_data->hub_instance.port_status; + + LOG_DBG("Port %d: Getting port status", port_num); + ret = usbh_req_get_port_status(hub_mgr_data->hub_instance.udev, port_num, + &port_status, &port_change); + if (ret != 0) { + LOG_ERR("Failed to get port status: %d", ret); + goto error_recovery; + } + + port_sts->wPortStatus = port_status; + port_sts->wPortChange = port_change; + LOG_DBG("Port %d status: wPortStatus=0x%04x, wPortChange=0x%04x", port_num, + port_sts->wPortStatus, port_sts->wPortChange); + + if ((port_sts->wPortChange & USB_HUB_PORT_CHANGE_CONNECTION) != 0) { + ret = usbh_req_clear_hcfs_c_pconnection(hub_mgr_data->hub_instance.udev, + port_num); + if (ret != 0) { + LOG_ERR("Failed to clear port connection change: %d", ret); + goto error_recovery; + } + LOG_DBG("Port %d: Cleared connection change bit", port_num); + + ret = usbh_req_get_port_status(hub_mgr_data->hub_instance.udev, port_num, + &port_status, &port_change); + if (ret != 0) { + LOG_ERR("Failed to get port connection status: %d", ret); + goto error_recovery; + } + + port_sts->wPortStatus = port_status; + port_sts->wPortChange = port_change; + + if ((port_sts->wPortStatus & USB_HUB_PORT_STATUS_CONNECTION) == 0) { + port_instance->state = PORT_STATE_DISCONNECTED; + goto process_disconnection; + } + + LOG_DBG("Port %d connection confirmed, resetting", port_num); + port_instance->status = HUB_PORT_RUN_WAIT_PORT_RESET_DONE; + port_instance->state = PORT_STATE_RESETTING; + ret = usbh_req_set_hcfs_prst(hub_mgr_data->hub_instance.udev, port_num); + if (ret != 0) { + LOG_ERR("Failed to reset port: %d", ret); + goto error_recovery; + } + if (port_instance->reset_count > 0) { + port_instance->reset_count--; + } + restart_interrupt = true; + goto exit_processing; + } + + if ((port_sts->wPortStatus & USB_HUB_PORT_STATUS_CONNECTION) != 0) { + LOG_INF("Device connected to port %d, starting reset", port_num); + port_instance->status = HUB_PORT_RUN_WAIT_PORT_RESET_DONE; + port_instance->state = PORT_STATE_RESETTING; + ret = usbh_req_set_hcfs_prst(hub_mgr_data->hub_instance.udev, port_num); + if (ret != 0) { + LOG_ERR("Failed to reset port: %d", ret); + goto error_recovery; + } + if (port_instance->reset_count > 0) { + port_instance->reset_count--; + } + restart_interrupt = true; + goto exit_processing; + } + + if ((port_sts->wPortChange & USB_HUB_PORT_CHANGE_ENABLE) != 0) { + ret = usbh_req_clear_hcfs_c_penable(hub_mgr_data->hub_instance.udev, + port_num); + if (ret != 0) { + LOG_ERR("Failed to clear enable change: %d", ret); + goto error_recovery; + } + LOG_DBG("Port %d: Cleared enable change bit", port_num); + + if ((port_sts->wPortStatus & USB_HUB_PORT_STATUS_ENABLE) == 0) { + port_instance->state = PORT_STATE_DISABLED; + } + } + + if ((port_sts->wPortChange & USB_HUB_PORT_CHANGE_OVER_CURRENT) != 0) { + LOG_WRN("Port %d over-current detected", port_num); + ret = usbh_req_clear_hcfs_c_pover_current(hub_mgr_data->hub_instance.udev, + port_num); + if (ret != 0) { + LOG_ERR("Failed to clear over-current change: %d", ret); + goto error_recovery; + } + LOG_DBG("Port %d: Cleared over-current change bit", port_num); + port_instance->state = PORT_STATE_DISABLED; + } + + if ((port_sts->wPortStatus & USB_HUB_PORT_STATUS_CONNECTION) == 0) { + goto process_disconnection; + } + + process_complete = true; + goto exit_processing; + } + + case HUB_PORT_RUN_WAIT_PORT_RESET_DONE: + port_instance->status = HUB_PORT_RUN_WAIT_C_PORT_RESET; + port_instance->state = PORT_STATE_RESETTING; + restart_interrupt = true; + LOG_DBG("Port %d waiting for reset completion interrupt", port_num); + goto exit_processing; + + case HUB_PORT_RUN_WAIT_C_PORT_RESET: { + port_sts = &hub_mgr_data->hub_instance.port_status; + + ret = usbh_req_get_port_status(hub_mgr_data->hub_instance.udev, port_num, + &port_status, &port_change); + if (ret != 0) { + LOG_ERR("Failed to get port status for reset check: %d", ret); + goto error_recovery; + } + + port_sts->wPortStatus = port_status; + port_sts->wPortChange = port_change; + + if ((port_sts->wPortChange & USB_HUB_PORT_CHANGE_RESET) == 0) { + LOG_DBG("Port %d reset not completed, checking again", port_num); + k_work_reschedule(&hub_mgr_data->port_work, K_MSEC(100)); + return; + } + + ret = usbh_req_clear_hcfs_c_preset(hub_mgr_data->hub_instance.udev, port_num); + if (ret != 0) { + LOG_ERR("Failed to clear reset feature: %d", ret); + goto error_recovery; + } + LOG_DBG("Port %d: Cleared reset change bit", port_num); + + ret = usbh_req_get_port_status(hub_mgr_data->hub_instance.udev, port_num, + &port_status, &port_change); + if (ret != 0) { + LOG_ERR("Failed to get port status after reset: %d", ret); + goto error_recovery; + } + + port_sts->wPortStatus = port_status; + port_sts->wPortChange = port_change; + + if ((port_sts->wPortStatus & USB_HUB_PORT_STATUS_CONNECTION) == 0) { + port_instance->state = PORT_STATE_DISCONNECTED; + goto process_disconnection; + } + + if ((port_sts->wPortStatus & USB_HUB_PORT_STATUS_ENABLE) != 0) { + port_instance->state = PORT_STATE_ENABLED; + } else { + port_instance->state = PORT_STATE_DISABLED; + } + + ret = enumerate_port_device(hub_mgr_data, port_instance, port_num, + port_sts, &is_child_hub); + if (ret != 0) { + if (port_instance->reset_count > 0) { + port_instance->reset_count--; + LOG_WRN("Port %d enumeration failed, retrying reset (%d left)", + port_num, port_instance->reset_count); + ret = usbh_req_set_hcfs_prst(hub_mgr_data->hub_instance.udev, + port_num); + if (ret != 0) { + LOG_ERR("Failed to reset port: %d", ret); + goto error_recovery; + } + port_instance->status = HUB_PORT_RUN_WAIT_PORT_RESET_DONE; + port_instance->state = PORT_STATE_RESETTING; + restart_interrupt = true; + goto exit_processing; + } + LOG_ERR("Port %d enumeration max retries exceeded", port_num); + port_instance->status = HUB_PORT_RUN_INVALID; + port_instance->state = PORT_STATE_DISABLED; + goto exit_processing; + } + + if (is_child_hub) { + k_work_reschedule(&hub_mgr_data->port_work, + K_MSEC(CONFIG_USBH_HUB_CHILD_PROBE_DELAY_MS)); + return; + } + + process_complete = true; + hub_mgr_data->current_port = 0; + hub_mgr.processing_hub = NULL; + port_instance->reset_count = CONFIG_USBH_HUB_PORT_RESET_TIMES; + restart_interrupt = true; + goto exit_processing; + } + + case HUB_PORT_RUN_CHECK_CHILD_HUB: + child_hub = find_hub_mgr_by_udev(port_instance->udev); + if (child_hub != NULL) { + hub_establish_parent_child_relationship(hub_mgr_data, child_hub, port_num); + } + + process_complete = true; + hub_mgr_data->current_port = 0; + hub_mgr.processing_hub = NULL; + port_instance->reset_count = CONFIG_USBH_HUB_PORT_RESET_TIMES; + restart_interrupt = true; + goto exit_processing; + + default: + LOG_ERR("Unknown port status: %d", port_instance->status); + goto error_recovery; + } + +process_disconnection: + if (port_instance->udev != NULL) { + LOG_INF("Device disconnected from Hub level %d port %d", + hub_mgr_data->hub_instance.udev->level, port_num); + + udev = port_instance->udev; + port_instance->udev = NULL; + port_instance->state = PORT_STATE_DISCONNECTED; + + child_hub = find_hub_mgr_by_udev(udev); + if (child_hub != NULL) { + LOG_INF("Child Hub disconnected, triggering recursive removal"); + hub_mgr_recursive_disconnect(child_hub); + } else { + usbh_device_disconnect(hub_mgr_data->uhs_ctx, udev); + } + } + process_complete = true; + +exit_processing: + if (process_complete || + (port_instance != NULL && port_instance->status == HUB_PORT_RUN_INVALID)) { + hub_mgr_data->current_port = 0; + hub_mgr.processing_hub = NULL; + port_instance->status = HUB_PORT_RUN_WAIT_PORT_CHANGE; + port_instance->reset_count = CONFIG_USBH_HUB_PORT_RESET_TIMES; + restart_interrupt = true; + + LOG_DBG("Port %d processing completed", port_num); + } + + if (restart_interrupt && !hub_mgr_data->int_active && !hub_mgr_data->being_removed) { + ret = hub_mgr_start_interrupt(hub_mgr_data); + if (ret) { + LOG_ERR("Failed to restart interrupt monitoring: %d", ret); + } + } + + return; + +error_recovery: + if (port_instance == NULL) { + LOG_ERR("Port instance is NULL in error recovery"); + goto exit_processing; + } + + if (port_instance->reset_count > 0) { + port_instance->reset_count--; + port_instance->status = HUB_PORT_RUN_WAIT_PORT_CHANGE; + + LOG_WRN("Port %d error recovery, %d retries left", port_num, + port_instance->reset_count); + k_work_reschedule(&hub_mgr_data->port_work, + K_MSEC(CONFIG_USBH_HUB_ERROR_RECOVERY_DELAY_MS)); + } else { + LOG_ERR("Port %d max retries exceeded, disabling", port_num); + port_instance->status = HUB_PORT_RUN_INVALID; + goto exit_processing; + } +} + +static int usbh_hub_mgr_probe(struct usbh_class_data *const c_data, + struct usb_device *const udev, + const uint8_t iface) +{ + struct usbh_hub_mgr_data *hub_mgr_data; + const struct usb_desc_header *header; + const void *desc_start; + const void *desc_end; + uint8_t target_iface; + + if (hub_mgr.uhs_ctx == NULL && udev != NULL && udev->ctx != NULL) { + hub_mgr.uhs_ctx = (struct usbh_context *)udev->ctx; + } + + if (hub_mgr.total_hubs == CONFIG_USBH_HUB_INSTANCES_COUNT) { + LOG_ERR("Maximum number of hubs reached (%d)", CONFIG_USBH_HUB_INSTANCES_COUNT); + return -ENOTSUP; + } + + /* Convert device-level match to interface 0 */ + if (iface == USBH_CLASS_IFNUM_DEVICE) { + target_iface = 0; + } else { + target_iface = iface; + } + + LOG_DBG("USB HUB device probe at interface %u", target_iface); + + desc_start = usbh_desc_get_iface(udev, target_iface); + if (desc_start == NULL) { + LOG_ERR("Failed to find interface %u descriptor", iface); + return -ENOTSUP; + } + + /* Get the start of next function as the end of current function */ + desc_end = usbh_desc_get_next_function(desc_start); + + hub_mgr_data = k_malloc(sizeof(*hub_mgr_data)); + if (!hub_mgr_data) { + LOG_ERR("Failed to allocate HUB management data"); + return -ENOTSUP; + } + + memset(hub_mgr_data, 0, sizeof(*hub_mgr_data)); + + usbh_hub_init_instance(&hub_mgr_data->hub_instance, udev); + + hub_mgr_data->hub_instance.udev = udev; + hub_mgr_data->uhs_ctx = hub_mgr.uhs_ctx; + hub_mgr_data->state = HUB_STATE_INIT; + hub_mgr_data->hub_status = HUB_RUN_INIT_HUB; + hub_mgr_data->port_index = 0; + hub_mgr_data->port_list = NULL; + hub_mgr_data->being_removed = false; + hub_mgr_data->interrupt_transfer = NULL; + hub_mgr_data->int_active = false; + + sys_slist_init(&hub_mgr_data->child_hubs); + + /* Parse interrupt endpoint within the interface descriptors */ + header = (const void *)desc_start; + while (header != NULL) { + /* Stop if we've reached the next function */ + if ((desc_end != NULL) && ((void *)header >= desc_end)) { + break; + } + + if (header->bDescriptorType == USB_DESC_ENDPOINT) { + const struct usb_ep_descriptor *ep_desc = (const void *)header; + + if ((ep_desc->bEndpointAddress & USB_EP_DIR_MASK) == USB_EP_DIR_IN && + (ep_desc->bmAttributes & USB_EP_TRANSFER_TYPE_MASK) == + USB_EP_TYPE_INTERRUPT) { + hub_mgr_data->int_ep = ep_desc; + LOG_DBG("Found hub interrupt IN endpoint 0x%02x", + ep_desc->bEndpointAddress); + break; + } + } + + header = usbh_desc_get_next(header); + } + + hub_mgr_data->connected = true; + hub_mgr_data->int_active = false; + + k_mutex_init(&hub_mgr_data->lock); + k_work_init_delayable(&hub_mgr_data->hub_work, hub_mgr_process); + k_work_init_delayable(&hub_mgr_data->port_work, hub_port_process); + + /* Here only print hub information for the first hub. */ + if (0U == hub_mgr.total_hubs) { + hub_print_info(hub_mgr_data); + } + + c_data->priv = hub_mgr_data; + + k_mutex_lock(&hub_mgr.lock, K_FOREVER); + sys_slist_append(&hub_mgr.hub_list, &hub_mgr_data->node); + hub_mgr.total_hubs++; + k_mutex_unlock(&hub_mgr.lock); + + k_work_submit(&hub_mgr_data->hub_work.work); + + return 0; +} + +static int usbh_hub_mgr_removed(struct usbh_class_data *const cdata) +{ + struct usbh_hub_mgr_data *hub_mgr_data; + uint16_t vendor_id; + uint16_t product_id; + uint8_t level; + int ret; + + hub_mgr_data = cdata->priv; + if (hub_mgr_data == hub_mgr.processing_hub) { + hub_mgr.processing_hub = NULL; + } + + level = hub_mgr_data->hub_instance.udev->level; + vendor_id = sys_le16_to_cpu(hub_mgr_data->hub_instance.udev->dev_desc.idVendor); + product_id = sys_le16_to_cpu(hub_mgr_data->hub_instance.udev->dev_desc.idProduct); + + k_mutex_lock(&hub_mgr_data->lock, K_FOREVER); + hub_mgr_data->being_removed = true; + k_mutex_unlock(&hub_mgr_data->lock); + + /* Recursively disconnect all child hubs and devices */ + hub_mgr_recursive_disconnect(hub_mgr_data); + + k_work_cancel_delayable(&hub_mgr_data->hub_work); + k_work_cancel_delayable(&hub_mgr_data->port_work); + + k_mutex_lock(&hub_mgr_data->lock, K_FOREVER); + + if (hub_mgr_data->interrupt_transfer != NULL && hub_mgr_data->int_active) { + ret = usbh_xfer_dequeue(hub_mgr_data->hub_instance.udev, + hub_mgr_data->interrupt_transfer); + if (ret != 0) { + LOG_ERR("Failed to dequeue interrupt transfer: %d", ret); + } + + if (hub_mgr_data->interrupt_transfer->buf != NULL) { + usbh_xfer_buf_free(hub_mgr_data->hub_instance.udev, + hub_mgr_data->interrupt_transfer->buf); + } + usbh_xfer_free(hub_mgr_data->hub_instance.udev, hub_mgr_data->interrupt_transfer); + + hub_mgr_data->interrupt_transfer = NULL; + hub_mgr_data->int_active = false; + + LOG_DBG("Interrupt transfer cancelled"); + } + + /* Remove all connected devices */ + for (uint8_t i = 0; i < hub_mgr_data->hub_instance.ports; i++) { + if (hub_mgr_data->port_list != NULL && + hub_mgr_data->port_list[i].udev != NULL) { + hub_mgr_data->port_list[i].udev = NULL; + hub_mgr_data->port_list[i].state = PORT_STATE_POWERED_OFF; + } + } + + if (hub_mgr_data->hub_instance.udev->hub != NULL) { + struct usbh_hub_mgr_data *parent_hub_mgr; + + parent_hub_mgr = find_hub_mgr_by_udev(hub_mgr_data->hub_instance.udev->hub); + if (parent_hub_mgr != NULL) { + sys_slist_find_and_remove(&parent_hub_mgr->child_hubs, + &hub_mgr_data->child_node); + } + } + + sys_slist_find_and_remove(&hub_mgr.hub_list, &hub_mgr_data->node); + if (hub_mgr.total_hubs > 0) { + hub_mgr.total_hubs--; + } + + k_mutex_unlock(&hub_mgr.lock); + + usbh_hub_cleanup_instance(&hub_mgr_data->hub_instance); + + /* Free port list */ + if (hub_mgr_data->port_list) { + k_free(hub_mgr_data->port_list); + hub_mgr_data->port_list = NULL; + } + + LOG_INF("Hub (level %d, Vendor ID: 0x%04x, Product ID: 0x%04x) removal completed", + level, vendor_id, product_id); + + k_free(hub_mgr_data); + + return 0; +} + +/* Hub class initialization function */ +static int usbh_hub_mgr_init(struct usbh_class_data *const c_data) +{ + return 0; +} + +static struct usbh_class_filter hub_filters[] = { + { + .flags = USBH_CLASS_MATCH_CODE_TRIPLE, + .class = USB_HUB_CLASS_CODE, + .sub = USB_HUB_SUBCLASS_CODE, + .proto = 1, + }, + {0}, +}; + +/* Hub class API structure */ +static struct usbh_class_api usbh_hub_class_api = { + .init = usbh_hub_mgr_init, + .probe = usbh_hub_mgr_probe, + .removed = usbh_hub_mgr_removed, +}; + +static int hub_init(void) +{ + sys_slist_init(&hub_mgr.hub_list); + k_mutex_init(&hub_mgr.lock); + hub_mgr.total_hubs = 0; + hub_mgr.processing_hub = NULL; + hub_mgr.uhs_ctx = NULL; + + return 0; +} + +SYS_INIT(hub_init, POST_KERNEL, CONFIG_USBH_INIT_PRIO); + +#define USBH_DEFINE_HUB_CLASS(i, _) \ + USBH_DEFINE_CLASS(UTIL_CAT(usbh_hub_class_, i), &usbh_hub_class_api, NULL, hub_filters) + +LISTIFY(CONFIG_USBH_HUB_INSTANCES_COUNT, USBH_DEFINE_HUB_CLASS, (;), _) diff --git a/subsys/usb/host/class/usbh_hub_mgr.h b/subsys/usb/host/class/usbh_hub_mgr.h new file mode 100644 index 0000000000000..6625228dbb2bc --- /dev/null +++ b/subsys/usb/host/class/usbh_hub_mgr.h @@ -0,0 +1,69 @@ +/* + * Copyright 2025 - 2026 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _USBH_HUB_MGR_H_ +#define _USBH_HUB_MGR_H_ + +#include +#include "usbh_ch11.h" + +enum usbh_hub_run_status { + HUB_RUN_IDLE = 0, + HUB_RUN_INVALID, + HUB_RUN_INIT_HUB, +}; + +enum usbh_hub_port_run_status { + HUB_PORT_RUN_IDLE = 0, + HUB_PORT_RUN_INVALID, + HUB_PORT_RUN_WAIT_PORT_CHANGE, + HUB_PORT_RUN_WAIT_PORT_RESET_DONE, + HUB_PORT_RUN_WAIT_C_PORT_RESET, + HUB_PORT_RUN_PORT_ATTACHED, + HUB_PORT_RUN_CHECK_CHILD_HUB, +}; + +/* Hub port structure */ +struct usb_hub_port { + struct usb_device *udev; + uint8_t status; /* Port status */ + enum usbh_port_state state; /* Port overall state */ + uint8_t reset_count; /* Reset retry count */ + uint8_t speed; /* Device speed */ + uint8_t num; /* Port number */ +}; + +/* Hub management data structure */ +struct usbh_hub_mgr_data { + struct usbh_context *uhs_ctx; + + struct usb_hub hub_instance; + + enum usbh_hub_state state; + enum usbh_hub_run_status hub_status; + + struct usb_hub_port *port_list; + uint8_t current_port; + uint8_t port_index; + + struct k_work_delayable hub_work; + struct k_work_delayable port_work; + + const struct usb_ep_descriptor *int_ep; + struct uhc_transfer *interrupt_transfer; + uint8_t int_buffer[8]; + bool int_active; + + sys_slist_t child_hubs; + sys_snode_t child_node; + sys_snode_t node; + + struct k_mutex lock; + bool connected; + bool being_removed; +}; + +#endif /* _USBH_HUB_MGR_H_ */