Skip to content

Conversation

@josuah
Copy link
Contributor

@josuah josuah commented Aug 17, 2025

Main thread:

Downstream:

It contains all the commits of the previous PR, and in addition:

  • Wrapper for the class API
  • Split the struct usbh_class_data and usbh_class_api
  • Add utilities to register classes as a linked list (NXP)
  • Add a filter logic to match a device class to host class instances (NXP)

This 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:

west build -t run -b native_sim samples/subsys/usb/shell/ \
-DEXTRA_CONF_FILE=virtual.conf \
-DDTC_OVERLAY_FILE=virtual.overlay \
-DEXTRA_CONF_FILE=device_and_host_prj.conf

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:

west build -b rd_rw612_bga -S video-sw-generator samples/drivers/video/capture

All tests are done on top of main...josuah:zephyr:pr_usb_host_class_api2

@josuah josuah changed the title USB Host: integrate class API [2] USB Host: integrate class API [2: helpers] Aug 17, 2025
@josuah josuah marked this pull request as ready for review August 17, 2025 21:05
@zephyrbot zephyrbot added area: USB Universal Serial Bus area: Samples Samples labels Aug 17, 2025
@josuah josuah force-pushed the pr_usb_host_class_api2 branch from e57477d to b3b4bc4 Compare September 4, 2025 13:46
@zephyrbot zephyrbot requested a review from JarmouniA September 4, 2025 13:47
@josuah josuah force-pushed the pr_usb_host_class_api2 branch 3 times, most recently from e672ee8 to d04bbfb Compare September 5, 2025 12:01
@josuah josuah force-pushed the pr_usb_host_class_api2 branch from d04bbfb to 7073da8 Compare September 5, 2025 13:27
/** Pointer to host support class API */
struct usbh_class_api *api;
/** Pointer to private data */
void *priv;
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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).

Comment on lines 10 to 12
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)
Copy link
Contributor

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.

Copy link
Contributor Author

@josuah josuah Sep 5, 2025

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?

main...josuah:zephyr:pr_sample_uvc_uvb#diff-6b7a858e2f07001df9a5d4de454923a5cf06ed38afb7fd4040bb84b45089cc34

Copy link
Contributor Author

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.

@jfischer-no jfischer-no added the Experimental Experimental features not enabled by default label Sep 5, 2025
@jfischer-no jfischer-no mentioned this pull request Sep 8, 2025
15 tasks
@josuah josuah marked this pull request as draft September 11, 2025 00:05
@josuah josuah force-pushed the pr_usb_host_class_api2 branch 4 times, most recently from f095c02 to 3e111d0 Compare September 12, 2025 20:58
@josuah josuah force-pushed the pr_usb_host_class_api2 branch from d9c0620 to e21942e Compare November 27, 2025 18:44
@josuah
Copy link
Contributor Author

josuah commented Nov 27, 2025

Force-push:

  • Rebase on latest main

@@ -1,3 +1,9 @@
/*
* Copyright 2025 Nordic Semiconductor ASA
Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Understood. Done.

@@ -0,0 +1,147 @@
/*
* Copyright (c) 2025 Nordic Semiconductor ASA
Copy link
Contributor

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Applied, thank you.

Copy link
Contributor Author

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...

Comment on lines 163 to 165
struct usbh_class_filter *filters;
/** Number of filters in the array */
size_t num_filters;
Copy link
Contributor

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?

Copy link
Contributor Author

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.

k_mutex_unlock(&udev->mutex);
}

bool usbh_class_is_matching(struct usbh_class_filter *const filters, size_t num_filters,
Copy link
Contributor

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,

Copy link
Contributor Author

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.

* 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)
Copy link
Contributor

Choose a reason for hiding this comment

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

const uint8_t iface)

Copy link
Contributor Author

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

Comment on lines 46 to 47
return usbh_desc_is_valid(desc, desc_end, sizeof(struct usb_if_descriptor)) &&
((struct usb_desc_header *)desc)->bDescriptorType == USB_DESC_INTERFACE;
Copy link
Contributor

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)

Copy link
Contributor Author

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.

}

/* Try to find a rule that matches completely */
for (int i = 0; i < num_filters; i++) {
Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@josuah josuah force-pushed the pr_usb_host_class_api2 branch 4 times, most recently from 15feead to e43dd89 Compare December 8, 2025 21:58
@josuah josuah force-pushed the pr_usb_host_class_api2 branch from e43dd89 to bd931ba Compare December 8, 2025 23:12
/**
* @brief Device initialization handler
*
* Called when a device is connected to the bus for every device.
Copy link
Contributor

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?

Copy link
Contributor Author

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!

return api->suspended(c_data);
}

return -ENOTSUP;
Copy link
Contributor

Choose a reason for hiding this comment

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

Is suspended really mandatory?

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 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.

Copy link
Contributor

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.

Copy link
Contributor Author

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;

Copy link
Contributor

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?

Copy link
Contributor Author

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...

struct usbh_class_filter *const info)
{
/* Make empty filter set match everything (use class_api->probe() only) */
if (filters[0].flags == 0) {
Copy link
Contributor

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).

Copy link
Contributor Author

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_....

* @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
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Comment on lines 13 to 25
/** 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)
Copy link
Contributor

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.

Copy link
Contributor Author

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.

*
* @return A pointer to the beginning of the descriptor
*/
const void *usbh_desc_get_cfg_beg(const struct usb_device *const udev);
Copy link
Contributor

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?

Copy link
Contributor Author

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.

*
* @param[in] udev The device holding the configuration descriptor
*
* @return A pointer to the beginning of the descriptor
Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My bad...

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.
Copy link
Contributor

Choose a reason for hiding this comment

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

Copy&pate error.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My bad...

* 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.
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Josuah Demangeon added 2 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]>
@josuah josuah force-pushed the pr_usb_host_class_api2 branch 2 times, most recently from 099d7d7 to 13bcf60 Compare December 9, 2025 16:35
@josuah josuah marked this pull request as draft December 9, 2025 17:10
@josuah josuah force-pushed the pr_usb_host_class_api2 branch 5 times, most recently from 1e45a69 to 5bd97c9 Compare December 10, 2025 01:35
@josuah
Copy link
Contributor Author

josuah commented Dec 10, 2025

Force-push:

  • Rebased on top of latest main
  • Apply review suggestions
  • Apply SonarQube suggestions
  • Extra proofreading of the documentation

@josuah josuah force-pushed the pr_usb_host_class_api2 branch from 5bd97c9 to 3c6b1ab Compare December 10, 2025 14:55
Josuah Demangeon and others added 6 commits December 10, 2025 16:57
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]>
@josuah josuah force-pushed the pr_usb_host_class_api2 branch from 3c6b1ab to c995d9b Compare December 10, 2025 17:15
@sonarqubecloud
Copy link

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

Labels

area: Samples Samples area: Tests Issues related to a particular existing or missing test area: USB Universal Serial Bus Experimental Experimental features not enabled by default

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants