-
Notifications
You must be signed in to change notification settings - Fork 8.4k
USB Host: integrate class API [2: helpers] #94590
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
e57477d to
b3b4bc4
Compare
e672ee8 to
d04bbfb
Compare
d04bbfb to
7073da8
Compare
| /** Pointer to host support class API */ | ||
| struct usbh_class_api *api; | ||
| /** Pointer to private data */ | ||
| void *priv; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I dug out the branch where I started working on the CDC ACM driver, cleaned it up a bit, and pushed it to my Zephyr repository, https://github.com/jfischer-no/zephyr/commits/wip-usbh-class-driver/, please take a look and reuse what makes sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I still have these checks missing, left for the class to implement them:
if_desc = (void *)udev->ifaces[i].dhp;
if (if_desc->bInterfaceClass == code->dclass &&
if_desc->bInterfaceSubClass == code->sub &&
if_desc->bInterfaceProtocol == code->proto) {But that is something that I can add. Is it possible for a standard device to only expect interface or interface-association to be used for class selection?
I really need to check the standards again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I imported everything into the current PR (except the CDC ACM implementation itself).
subsys/usb/host/usbh_desc.c
Outdated
| struct usb_desc_header *usbh_desc_get_by_type(const uint8_t *const start_addr, | ||
| const uint8_t *const end_addr, | ||
| uint32_t type_mask) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need some simple function driver code to test it, IMO jfischer-no@28f064b is too ugly for that, maybe you or me can sand down it to loopback.c skeleton (to be counterpart of device_next/class/loopback.c), just checking for the testusb bulk interface and testusb sample VID/PID.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should help downstream PRs a lot.
Should that be using UVB connecting the device and host class together? I tried it on a WIP branch and can try to collect bits from various branches and integrate unit tests into API2 that way.
Is there another way I should test the host functionality than UVB?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or this can also be some manually crafted descriptor, it seems a different concern than testing everything end-to-end, and easier to reproduce bugs. I will try this thank you.
f095c02 to
3e111d0
Compare
d9c0620 to
e21942e
Compare
|
Force-push:
|
subsys/usb/host/usbh_data.ld
Outdated
| @@ -1,3 +1,9 @@ | |||
| /* | |||
| * Copyright 2025 Nordic Semiconductor ASA | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should use just
* SPDX-FileCopyrightText: Copyright The Zephyr Project Contributors
for this type of files.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Understood. Done.
subsys/usb/host/usbh_class_api.h
Outdated
| @@ -0,0 +1,147 @@ | |||
| /* | |||
| * Copyright (c) 2025 Nordic Semiconductor ASA | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SPDX-FileCopyrightText: Copyright Nordic Semiconductor ASA
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Applied, thank you.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I applied to all files as a stand-alone commit. Not sure if that's what to be done...
include/zephyr/usb/usbh.h
Outdated
| struct usbh_class_filter *filters; | ||
| /** Number of filters in the array */ | ||
| size_t num_filters; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should filters array be NULL pointer terminated instead of using num_filters?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this was the original version but was modified based on feedback of an earlier review.
I do not remember where exactly.
subsys/usb/host/usbh_class.c
Outdated
| k_mutex_unlock(&udev->mutex); | ||
| } | ||
|
|
||
| bool usbh_class_is_matching(struct usbh_class_filter *const filters, size_t num_filters, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bool usbh_class_is_matching(struct usbh_class_filter *const filters,
const size_t num_filters,There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed num_filters in favor of a NULL terminator.
subsys/usb/host/usbh_class.c
Outdated
| * multiple times consequently has no effect. | ||
| */ | ||
| static void usbh_class_probe_function(struct usb_device *const udev, | ||
| struct usbh_class_filter *const info, uint8_t iface) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const uint8_t iface)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I forgot this one... done
subsys/usb/host/usbh_desc.c
Outdated
| return usbh_desc_is_valid(desc, desc_end, sizeof(struct usb_if_descriptor)) && | ||
| ((struct usb_desc_header *)desc)->bDescriptorType == USB_DESC_INTERFACE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest writing it in more detail.
bool usbh_desc_is_valid_interface(const void *const desc, const void *const desc_end)
{
struct usb_desc_header *const head = desc;
if (head->bDescriptorType != USB_DESC_INTERFACE)
return false;
}
return usbh_desc_is_valid(desc, desc_end, sizeof(struct usb_if_descriptor));
}btw, why not pass type parameter to usbh_desc_is_valid()? Like
const bool usbh_desc_is_valid(const void *const desc_ptr, const void *const desc_end,
size_t expected_size, const uint8_t type)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have added the type to usbh_desc_is_valid() and kept the usbh_desc_is_valid_interface() to reduce boilerplate as these functions are frequently used across the source and classes.
subsys/usb/host/usbh_class.c
Outdated
| } | ||
|
|
||
| /* Try to find a rule that matches completely */ | ||
| for (int i = 0; i < num_filters; i++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for (size_t i = 0; i < num_filters; i++) { !
size_t can be very annoying.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
15feead to
e43dd89
Compare
e43dd89 to
bd931ba
Compare
subsys/usb/host/usbh_class_api.h
Outdated
| /** | ||
| * @brief Device initialization handler | ||
| * | ||
| * Called when a device is connected to the bus for every device. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does "for every device" mean here? Isn't it supposed to be called for every interface?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is definitely confusing... This might probably need to be "for every USB function" instead. Updated!
subsys/usb/host/usbh_class_api.h
Outdated
| return api->suspended(c_data); | ||
| } | ||
|
|
||
| return -ENOTSUP; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is suspended really mandatory?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would permit to propagate the action to the subsystem associated.
It does not seem used by downstream PRs and can be introduced in a dedicated PR if needed.
Removed. Thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't mean to remove the suspend callback, but rather to allow it to be optional, i.e. to not return -ENOTSUP, but just 0 if there is no callback.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I remember now, it was rwup which needed to go away! Restored suspend()/resume() and applied return 0, thanks
| void usbh_class_init_all(struct usbh_context *const uhs_ctx) | ||
| { | ||
| int ret; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is going without mutex safe here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assumed it would just because it is called in usbh_init() which calls usbh_host_lock()/usbh_host_unlock().
But this goes through several internal APIs that are expoed in .h, so from API point of view, maybe it is better to lock again here?
Mutexes support recursion anyway so it should work...
subsys/usb/host/usbh_class.c
Outdated
| struct usbh_class_filter *const info) | ||
| { | ||
| /* Make empty filter set match everything (use class_api->probe() only) */ | ||
| if (filters[0].flags == 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
USBH_DEFINE_CLASS documentation mentions that NULL matches everything, not a filter with flags set to 0. Therefore it should be more like if (filters == NULL).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is a better way to make a distinction... It also makes it possible to write classes that match nothing, which is safer and easier to debug if i.e. every rule is commented out by #ifdef CONFIG_....
include/zephyr/usb/usbh.h
Outdated
| * @param[in] class_name Class name | ||
| * @param[in] class_api Pointer to struct usbh_class_api | ||
| * @param[in] class_priv Class private data | ||
| * @param[in] filt Array of @ref usbh_class_filter to match this class or NULL to match everything |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not specified here that the array is expected to end with entry that has flags set to 0.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the consistency check.
subsys/usb/host/usbh_class.h
Outdated
| /** Match a device's vendor ID */ | ||
| #define USBH_CLASS_MATCH_VID BIT(1) | ||
|
|
||
| /** Match a device's product ID */ | ||
| #define USBH_CLASS_MATCH_PID BIT(2) | ||
|
|
||
| /** Match a class code */ | ||
| #define USBH_CLASS_MATCH_CLASS BIT(3) | ||
|
|
||
| /** Match a subclass code */ | ||
| #define USBH_CLASS_MATCH_SUB BIT(4) | ||
|
|
||
| /** Match a protocol code */ | ||
| #define USBH_CLASS_MATCH_PROTO BIT(5) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The individual criteria seem a bit odd. Matching on code triple is fine, but on individual fields seems questionable.
Matching on VID alone is very questionable, while matching on PID alone sounds outright wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, reduced to only USBH_CLASS_MATCH_VID_PID and USBH_CLASS_MATCH_CODE_TRIPLET.
subsys/usb/host/usbh_desc.h
Outdated
| * | ||
| * @return A pointer to the beginning of the descriptor | ||
| */ | ||
| const void *usbh_desc_get_cfg_beg(const struct usb_device *const udev); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not usbh_desc_get_cfg?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just for symmetry with usbh_desc_get_cfg_end and to remind users they need to call both in combination for safety purpose. However it is enforced by all APIs...
Removed the _beg suffix.
subsys/usb/host/usbh_desc.h
Outdated
| * | ||
| * @param[in] udev The device holding the configuration descriptor | ||
| * | ||
| * @return A pointer to the beginning of the descriptor |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copy&paste error. THis function is supposed to return the end.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My bad...
subsys/usb/host/usbh_desc.h
Outdated
| bool usbh_desc_is_valid_interface(const void *const desc, const void *const desc_end); | ||
|
|
||
| /** | ||
| * @brief Checks that the pointed descriptor is a valid interface descriptor. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copy&pate error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My bad...
subsys/usb/host/usbh_desc.h
Outdated
| * If @p desc_beg is another type of descriptors, it will seek to a matching descriptor type and | ||
| * return it, without skipping to the next one after it. | ||
| * | ||
| * @param[in] desc_beg Pointer to the beginning of the descriptor array; to search. May be NULL. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A bit strange name. I would go with just desc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Applying here like for usbh_desc_get_cfg.
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]>
099d7d7 to
13bcf60
Compare
1e45a69 to
5bd97c9
Compare
|
Force-push:
|
5bd97c9 to
3c6b1ab
Compare
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]>
3c6b1ab to
c995d9b
Compare
|



Main thread:
Downstream:
It contains all the commits of the previous PR, and in addition:
struct usbh_class_dataandusbh_class_apiThis also contains functions imported from #94085 by @AidenHu.
So far, this was only tested with this command to make sure it builds and run, but not in hardware yet:
The PR #94085 got then completely rebased on top of this, available at
josuah:enable_usb_host_video_class_api2, which is meant as a drop-in replacement for #94085.It was also built (but not run) with this command:
All tests are done on top of main...josuah:zephyr:pr_usb_host_class_api2