Skip to content

Conversation

@AidenHu
Copy link
Contributor

@AidenHu AidenHu commented Nov 20, 2025

Dependency:

This implementation uses all commits from #99775.

Although this is a draft PR for usb hub, I have tested the basic attachment functionality with my local environment by the host ecm case. It can work in this case: host ecm -> hub1 -> hub2 -> device ecm.

@AidenHu
Copy link
Contributor Author

AidenHu commented Nov 20, 2025

@josuah

I think this hub class implementation can be good helper to be used for testing the new host stack of 94590 for multiple devices attachment and detachment. This hub PR can grow up with your 94590. I will keep updating it for this PR based on your newest changes for host stack. Hope your any comments here.

Copy link
Contributor

@josuah josuah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is an incomplete review with a bit of feedback.

static int usbh_hub_class_request_common(struct usbh_hub_instance *hub_instance,
uint8_t request_type, uint8_t request,
uint16_t wvalue, uint16_t windex,
uint8_t *buffer, uint16_t buffer_length,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than calling memcpy(), how about passing net_buf buffers directly as argument?
It is then up to the caller to allocate the buffers (such as the wrapper functions below).

Suggested change
uint8_t *buffer, uint16_t buffer_length,
struct net_buf *const buf,

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed.

Comment on lines 37 to 57
if (buffer_length > 0) {
buf = usbh_xfer_buf_alloc(hub_instance->hub_udev, buffer_length);
if (!buf) {
LOG_ERR("Failed to allocate buffer for hub control request");
k_mutex_unlock(&hub_instance->ctrl_lock);
return -ENOMEM;
}

/* For OUT transfers, copy data to buffer */
if (!(request_type & 0x80) && buffer) {
memcpy(buf->data, buffer, buffer_length);
net_buf_add(buf, buffer_length);
}
} else {
buf = usbh_xfer_buf_alloc(hub_instance->hub_udev, 0);
if (!buf) {
LOG_ERR("Failed to allocate zero-length buffer");
k_mutex_unlock(&hub_instance->ctrl_lock);
return -ENOMEM;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be moved elsewhere in case this function is changed to accept a struct net_buf *const buf argument.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be moved elsewhere in case this function is changed to accept a struct net_buf *const buf argument.

agree, updated.

*/
static int usbh_hub_class_request_common(struct usbh_hub_instance *hub_instance,
uint8_t request_type, uint8_t request,
uint16_t wvalue, uint16_t windex,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exceptionally, for variable names that come from the USB standard, you can use CamelCase:

Suggested change
uint16_t wvalue, uint16_t windex,
uint16_t wValue, uint16_t wIndex,

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated.

Comment on lines 79 to 83
/* Call callback if provided */
if (callback_fn) {
callback_fn(callback_param, buffer,
(ret == 0) ? buf->len : 0, ret);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

usbh_hub_class_request_common() does not run asynchronously "in the background".

So someone who wish to execute something after the request is complete can do it like this:

ret = usbh_hub_class_request_common(...);
if (ret != 0) {
	return ret;
}

other_fn(...);

Then there is no need for callback?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

usbh_hub_class_request_common() does not run asynchronously "in the background".

So someone who wish to execute something after the request is complete can do it like this:

ret = usbh_hub_class_request_common(...);
if (ret != 0) {
	return ret;
}

other_fn(...);

Then there is no need for callback?
Forget to add TODO here, will check it later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove the duplicated callback.

@AidenHu
Copy link
Contributor Author

AidenHu commented Nov 21, 2025

@josuah
Thank much for the above comments. It is helpful for me. I have started the updating for all of points and some potential issues.

@AidenHu AidenHu force-pushed the usb_host_hub_class_enablement branch from 3bd611d to 35a2747 Compare November 24, 2025 11:58
@AidenHu
Copy link
Contributor Author

AidenHu commented Nov 24, 2025

@josuah
I will concentrate on checking the removing handling for multiple devices, please now skip the code reviewing for the related code of hub class. Still thank you much for any feedback.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we look at the commit subsys: usb: host: add two APIs for connect/disconnect, it looks like the connect() and disconnect() functions are practically the same.

It is possible to declare the previous functions as public with a prototype in .h to avoid duplicating the code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we look at the commit subsys: usb: host: add two APIs for connect/disconnect, it looks like the connect() and disconnect() functions are practically the same.

It is possible to declare the previous functions as public with a prototype in .h to avoid duplicating the code.

Hi @josuah
I have created another commit #100072 about this requirement. Please help to review in this thread. So you can ignore subsys: usb: host: add two APIs for connect/disconnect in current thread.

}

static void dev_connected_handler(struct usbh_context *const ctx,
const struct uhc_event *const event)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this function needs to be used outside, by the HUB, then it is possible to remove the static, give it an API name such as usbh_device_connected(), then add it to usbh_device.h, as an example.

Copy link
Contributor Author

@AidenHu AidenHu Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@josuah
Thank you for your feedback.
I am thinking about this solution. If we change this function into a public API, there will be some structural issues. dev_connected_handler is tied to the event handler running in the USB bus thread, triggered by low-level events. Also, the speed information comes from the second parameter event. Making it an API would require dropping or modifying this parameter, which means extra changes.
Instead, if adding a new API in the commit “subsys: usb: host: add two APIs for connect/disconnect”, we can reuse the same logic as the caller to reduce code duplication and keep the structure clean.
This is my opinion, hope your any comment for this topic.

Comment on lines 86 to 65
static void dev_removed_handler(struct usbh_context *const ctx)
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise, if this function needs to be used outside, by the HUB code, then it is possible to remove the static, give it an API name such as usbh_device_remove(), then add it to usbh_device.h.

@josuah
Copy link
Contributor

josuah commented Nov 24, 2025

Understood, thank you!

Here above is some short illustration of what was discussed in #99775 (comment)

@AidenHu AidenHu force-pushed the usb_host_hub_class_enablement branch 2 times, most recently from cb6a55e to edb59fb Compare November 26, 2025 07:40
Copy link
Contributor

@josuah josuah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still an incomplete review but here is a few feedback in the meantime I can gather more time for a complete review.
Thank you for your patience!

Comment on lines 146 to 159
/**
* @brief Get Hub level string (for logging)
*/
static inline const char *usbh_hub_mgr_get_level_str(uint8_t hub_level)
{
switch (hub_level) {
case 0: return "ROOT";
case 1: return "L1";
case 2: return "L2";
case 3: return "L3";
case 4: return "L4";
default: return "Lx";
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not seems to be used anywhere, and id easy to replace with L%d, where "ROOT" becomes "L0", which is probably suitable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, deleted it.

USBH_DEFINE_CLASS(UTIL_CAT(usbh_hub_class_, i), &usbh_hub_class_api, \
NULL, hub_filters, ARRAY_SIZE(hub_filters))

LISTIFY(CONFIG_USBH_HUB_MAX_LEVELS, USBH_DEFINE_HUB_CLASS, (;), _)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, there is one USB class instance per HUB, but a tree like this:

L0              <- The single root HUB
| \
|  \
|   \
|    \
L1    L1        <- All the level 1 HUBs
|\    |\
| \   | \
L2 L2 L2 L2     <- All the level 2 HUBs
|  |  |  |
D  D  D  D      <- 1 device per HUB in this example

Then CONFIG_USBH_HUB_MAX_LEVELS would need to be configured to at least 7, not to 3, as there need at least 7 HUB classes in total?

Then it's more CONFIG_USBH_HUB_MAX than CONFIG_USBH_HUB_MAX_LEVELS?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@josuah

Thank you for the comment. CONFIG_USBH_HUB_MAX_LEVELS indeed causes some misleading here. Actually CONFIG_USBH_HUB_MAX_LEVELS indicates the max level not max count. The right value should also be 5 and now its value is also 5 that is defined in Kconfig.hub_host. You can see the following picture of bus topology from usb spec, which mentioned that "five non-root hubs maximum can be supported in a communication path between the host and any device". In this case, the max hub level between host and any device is 5, such as host -> hub1 -> hub2 -> hub3 -> hub4 -> hub5 -> device but not support host -> hub1 -> hub2 -> hub3 -> hub4 -> hub5 -> hub 6 -> device.

Actually USB spec does not limit the total hub count as 7, but I think we can add another symbol CONFIG_USBH_HUB_MAX_COUNT in Kconfig.hub_host to limit the hub count. Then the code "LISTIFY(CONFIG_USBH_HUB_MAX_COUNT, USBH_DEFINE_HUB_CLASS, (;), _)" looks more reasonable. It will be set as 7 to limit the hub count, which also limits the c_node count of hub as 7. I will also add some description for this symbol to tell users that they can change its value based on the practical usage and resource limitation such as endpoint count, etc.
image


if USBH_HUB_CLASS

config USBH_HUB_MAX_LEVELS
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This Kconfig is used in the commit before it, so the commit before will fail.
Intermediate commits are expected to be compiling successfully, which helps with debugging.
Would it be possible to move the Kconfig changes to be before the C code that uses the Kconfig?

Also, you might wish to include the CMakeLists.txt in the same commit that adds the related C code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, thank you for this reminder. I will do it and I will follow this in the future.

Comment on lines 321 to 322
ret = usbh_hub_get_descriptor(&hub_mgr_data->hub_instance,
hub_mgr_data->hub_instance.hub_desc_buf, 7);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of hardcoding 7, would it be possible to use sizeof(struct usb_hub_descriptor)? This implies defining the struct usb_hub_descriptor without the DeviceRemovable field

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, updated.

uint16_t wHubCharacteristics;
uint8_t bPwrOn2PwrGood;
uint8_t bHubContrCurrent;
uint8_t DeviceRemovable[];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about keeping this field commented as a reminder it exists, but using other methods to access it (see below)?

Suggested change
uint8_t DeviceRemovable[];
/* uint8_t DeviceRemovable[]; */

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, updated the hub descriptor definition.

struct usb_hub_descriptor {
	uint8_t bDescLength;
	uint8_t bDescriptorType;
	uint8_t bNbrPorts;
	uint16_t wHubCharacteristics;
	uint8_t bPwrOn2PwrGood;
	uint8_t bHubContrCurrent;
	/** uint8_t DeviceRemovable[]; */
	/** uint8_t PortPwrCtrlMask[]; */
} __packed;

int ctrl_status;

/* Hub descriptor buffer */
uint8_t hub_desc_buf[64];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about this strategy to avoid accidental buffer overflows in case 64 is not enough, and also not use excessive memory?

Suggested change
uint8_t hub_desc_buf[64];
struct {
struct usb_hub_descriptor desc;
uint8_t DeviceRemovable[USBH_HUB_MAX_DEVICES];
} __packed hub_desc;

Copy link
Contributor Author

@AidenHu AidenHu Nov 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@josuah

For every hub, it has its own usbh_hub_instance, which means that hub_desc_buf is independent, so we can use hub_desc_buf. Meanwhile, here is actually one risk. According to the following description from USB spec :

DeviceRemovable: For a hub with up to 255 ports, the size is ceil((255 + 1) / 8) = 32 bytes because each port requires 1 bit plus 1 reserved bit, and the total is rounded up to the nearest byte.
PortPwrCtrlMask: This field has the same size as DeviceRemovable, so it is also 32 bytes for a hub with 255 ports

One hub descriptor is not more than 7+32+32 = 71 bytes, so its update in usbh_hub.h would be:

/* Maximum hub descriptor size.
 * 7 bytes (fixed) + max 32 bytes (DeviceRemovable) + max 32 bytes (PortPwrCtrlMask)
 */
#define USBH_HUB_DESC_BUF_SIZE  71

uint8_t hub_desc_buf[USBH_HUB_DESC_BUF_SIZE];
image

@AidenHu AidenHu force-pushed the usb_host_hub_class_enablement branch from edb59fb to 6836384 Compare November 28, 2025 06:22
@AidenHu
Copy link
Contributor Author

AidenHu commented Nov 28, 2025

Have updated the basement from #99775 for this PR.

@AidenHu AidenHu force-pushed the usb_host_hub_class_enablement branch from 6836384 to 6acdb74 Compare December 1, 2025 02:16
@AidenHu AidenHu force-pushed the usb_host_hub_class_enablement branch from 6acdb74 to d984e65 Compare December 8, 2025 10:21
Josuah Demangeon and others added 12 commits December 9, 2025 14:35
Add a "struct usbh_status" that contains a bitmask of flags to keep
track of the global state of the host context, like done for the
device_next implementation.

Signed-off-by: Josuah Demangeon <[email protected]>
Add missing copyright notice for the linker script to help with
check_compliance.py.

Signed-off-by: Josuah Demangeon <[email protected]>
Add a "struct usbh_class_api" for the host implementation, and move all
the function poitners to it. Add more fields to "struct usbh_class_data".

Signed-off-by: Josuah Demangeon <[email protected]>
Add API wrappers around the function pointers in struct usbh_class_api,
while also documenting the USB host class internal API.

Signed-off-by: Josuah Demangeon <[email protected]>
Add functions to probe/remove all classes as part of a new usbh_class.c
and a matching usbh_class.h. These functions are called from the function
usbh_init_device_intl() in usbh_core.c to initialize every class upon
connection of a device. Every class driver provide filters to match the
interfaces of the device.

Co-authored-by: Aiden Hu <[email protected]>
Signed-off-by: Josuah Demangeon <[email protected]>
Move the UVC header with all the definitions from the UVC standard to
share it between USB host and device class implementation.

Signed-off-by: Josuah Demangeon <[email protected]>
Add tests making sure the USB Host class APIs introduced build
and run as expected.

Signed-off-by: Josuah Demangeon <[email protected]>
Switch to the SPDX-FileCopyrightText prefix for all sources in USB Host
class.

Signed-off-by: Josuah Demangeon <[email protected]>
Add tests making sure the USB Host class APIs introduced build
and run as expected.

Signed-off-by: Josuah Demangeon <[email protected]>
Move the UVC header with all the definitions from the UVC standard to
share it between USB host and device class implementation.

Signed-off-by: Josuah Demangeon <[email protected]>
Move UVC helper functions to a file shared between UVC host and device.
The arrays are not visible anymore from either USB host or device, but
instead accessed through a front-end funciton.

Signed-off-by: Josuah Demangeon <[email protected]>
The USB control size field was wrong for UVC_PU_CONTRAST_CONTROL.
Correct it to the correct value from the standard.

Signed-off-by: Josuah Demangeon <[email protected]>
Josuah Demangeon and others added 12 commits December 10, 2025 01:39
Loop through each of the VideoStreaming and VideoControl descriptor
to parse them. This is meant as a stub for the purpose of testing the
class API.

Signed-off-by: Josuah Demangeon <[email protected]>
Add a test to run the USB Video Class host support by using
the existing Zephyr USB Video Class device support.
This allows running implementing the host side from the device side.
A draft implementation of UVC is added leveraging this test.

Signed-off-by: Josuah Demangeon <[email protected]>
The public API file <zephyr/usb/class/usbd_uvc.h> lacked an include
to <zephyr/drivers/video.h> making it fail depending on the order of
the includes.

Signed-off-by: Josuah Demangeon <[email protected]>
Modify the USB device int sequence to read the device
descriptor only after setting a valid device address.

Signed-off-by: Santhosh Charles <[email protected]>
When hub is used, need to consider about multiple
devices are attached.

Signed-off-by: Aiden Hu <[email protected]>
add usbh_device_get_root and usbh_device_is_root
function to check root device

Signed-off-by: Aiden Hu <[email protected]>
For usb xfer, set endpoint type and interval by the
selected endpoint desc.

Signed-off-by: Aiden Hu <[email protected]>
Convert xfer's interval to actual value because
mcux_ep->interval is already calculated.

Signed-off-by: Aiden Hu <[email protected]>
maxPacketSize and numberPerUframe of pipe should
be set considering additional transactions.

Signed-off-by: Aiden Hu <[email protected]>
Add two functions:
usbh_connect_device() for device connection
usbh_disconnect_device() for device disconnection

These functions centralize the logic for device attach/detach,
including class probe and remove handling. They can be invoked
by the hub class as well as dev_connected_handler and
dev_removed_handler, improving code clarity and reuse.

Signed-off-by: Aiden Hu <[email protected]>
- Add hub_host Kconfig and source files for USB hub class
- Update CMakeLists.txt to include hub class sources

Signed-off-by: Aiden Hu <[email protected]>

some necessary change for host uvc.
Introduce USB hub class support in the USB host stack.
This includes:
- Core hub handling logic (usbh_hub.c / usbh_hub.h)
- Hub manager for port status and event handling
  (usbh_hub_mgr.c / usbh_hub_mgr.h)

Signed-off-by: Aiden Hu <[email protected]>
@AidenHu AidenHu force-pushed the usb_host_hub_class_enablement branch from d984e65 to 6b42426 Compare December 10, 2025 06:40
@sonarqubecloud
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants