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: 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; }; /** 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_ */ diff --git a/subsys/usb/host/CMakeLists.txt b/subsys/usb/host/CMakeLists.txt index 72976f4aeea8e..97ac4e8d35825 100644 --- a/subsys/usb/host/CMakeLists.txt +++ b/subsys/usb/host/CMakeLists.txt @@ -14,6 +14,10 @@ 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_shell.c @@ -24,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_ */ 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_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); 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