samples: subsystem: usb: udc: Add multi-instance initialization#103844
samples: subsystem: usb: udc: Add multi-instance initialization#103844nandojve wants to merge 4 commits intozephyrproject-rtos:mainfrom
Conversation
Add zephyr,udc.yaml binding that can be used as a secondary compatible on any USB device controller. This enables auto-discovery of all UDC devices via DT_FOREACH_STATUS_OKAY(zephyr_udc, fn). Signed-off-by: BUDKE Gerson Fernando <gerson.budke@leica-geosystems.com>
Add support for auto-discovering USB device controllers using the zephyr,udc compatible string. This enables boards with multiple UDC devices to have them all initialized automatically. Changes to sample_usbd_init.c: - Use DT_INST_FOREACH_STATUS_OKAY(zephyr_udc) to discover UDC devices - Add fallback for legacy boards using zephyr_udc0 nodelabel without zephyr,udc compatible (backward compatibility) - Add sample_usbd_get_device_count() to get discovered device count - Add sample_usbd_get_context() to get USBD context by index - Add sample_usbd_init_all_devices() to initialize all discovered UDCs Changes to cdc_acm sample: - Update enable_usb_device_next() to init all discovered UDC devices - Enable each device that cannot detect VBUS Signed-off-by: BUDKE Gerson Fernando <gerson.budke@leica-geosystems.com>
With the multi-UDC discovery it is possible to automatically discover
all the UDC device controller and automatically instances all those.
However the CDC-ACM sample currently only support one terminal in one
instance. This refactor the CDC-ACM sample to support multiple UART
instances simultaneously - one per controller. Each instance has its
own thread that waits for DTR independently, allowing connections to
be handled without blocking each other.
Architecture:
main()
- Init all CDC-ACM instances
- Enable USB devices
- Spawn thread per instance
|
+------------------+------------------+
v v v
+----------------+ +----------------+ +----------------+
| Thread 0 | | Thread 1 | | Thread N |
| k_sem_take() | | k_sem_take() | | k_sem_take() |
| (wait for DTR) | | (wait for DTR) | | (wait for DTR) |
| | | | | | | | |
| v | | v | | v |
| setup_instance | | setup_instance | | setup_instance |
+----------------+ +----------------+ +----------------+
^ ^ ^
| | |
+------+------------------+------------------+----------+
| sample_msg_cb() - gives semaphore to correct instance |
+-------------------------------------------------------+
Changes:
- Add per-instance struct with ring buffer, semaphore, and thread
- Use DT_FOREACH_STATUS_OKAY to auto-discover CDC-ACM UART devices
- Spawn dedicated thread per instance to wait for DTR
- IRQ handler uses user_data to access per-instance data
- Each connection is handled independently
Signed-off-by: BUDKE Gerson Fernando <gerson.budke@leica-geosystems.com>
When multiple UDC devices are discovered via zephyr,udc compatible, the sample previously used shared descriptors which caused conflicts. The second device would fail with -ENOMEM/-EBUSY because descriptors can only be attached to one USBD context. This commit adds: - Per-device descriptor instantiation using DT_INST_FOREACH macros - Per-device configuration structures (sample_usbd_desc_set) - Detection of UDC devices with USB class children via devicetree - Skip class registration for UDCs without class children - Informative log messages when class registration is skipped This enables systems with multiple UDC devices to initialize correctly, with classes only registered on devices that have USB class children defined in devicetree. Signed-off-by: BUDKE Gerson Fernando <gerson.budke@leica-geosystems.com>
|
jfischer-no
left a comment
There was a problem hiding this comment.
Add support for auto-discovering USB device controllers using the
zephyr,udc compatible string. This enables boards with multiple UDC
devices to have them all initialized automatically.
In very rare cases, more than one USB controller is available on the platform. I have never seen more than two. Use cases for two USB controller in device mode are even rarer. There are no differences in setting up and initializing the second controller/USB-device, and an application does not need to be aware of anything special. We do not need a sample to demonstrate this. We do not need a more complicated CDC ACM sample just to demonstrate this one very rare use case. A sample should not abuse and depend on DT compatible just to demonstrate this rare use case. There is no benefit to the user application from that. Application developers of these very rare use case know their platforms, they do not need to iterate over whatever is there. They know the nodelabels/node-id and can just pass them to the USBD_DEVICE_DEFINE macro.
|
Hi @jfischer-no , I think this is not about rare use case, it is about use cases. Microchip is bringing the dual instance here #99620 and we need to instantiate both controllers and we need to differentiate host vs device soon with multiple controllers. There are cases that one want to use with manageable USB-HUB controller like some of these https://www.microchip.com/en-us/products/interface-and-connectivity/usb/usb-hubs which can bind multiple devices in the same board to a host using only 1 VBUS, which lead to manage the up/down the stacks correctly. This is not about a change on a sample it is about start the discussion about how to support these use cases. The infrastructure that was proposed here can be used together with the VBUS from #103840, which solve the main issue for the UDC use case and can be extended. If you see that this should be made differently it is fine but then let me know your opinion. For instance, where we should bind a floating I think postpone these discussion will only increase the pressure in future. |
|
Maybe the same way network interfaces provision their IP from Kconfig, using this possible workaround that can be used right away from the application: Whenever (and if) a "software provisioning" mechanism arrives to Zephyr, that allows such a thing to be used as a replacement for the application specific workaround. This can also be a good way to explore and report how manageable it is. Then it is also possible to set a lot of other related per-instance properties like VID:PID, interface names etc, given these are typically passed as strings in |
| /* Only register classes for UDCs that have class children */ | ||
| if (has_classes) { | ||
| err = usbd_register_all_classes(ctx, USBD_SPEED_FS, 1, blocklist); | ||
| if (err) { | ||
| LOG_ERR("Failed to register FS classes (%d)", err); | ||
| return err; | ||
| } | ||
| } else { | ||
| LOG_INF("Device %zu: Skipping FS class registration " | ||
| "(no USB class children in devicetree)", idx); | ||
| } |
There was a problem hiding this comment.
I can't see how usbd_register_all_classes() would assign the classes correctly. From the PR, I get impression that all class instances would get registered for all UDC instances that have at least 1 class in devicetree node. This would for sure fail if multiple instances were used simultaneously.
It would even fail if not used simultaneously if the UDC instances have to use different endpoint numbers (e.g. one controller supports bulk only on endpoint X, the other only on endpoint Y) because the classes descriptors are not modified in any way here (I would prefer if the class descriptors were kept in ROM and not modified at runtime, but this is out of scope here).
|
Thanks @jfischer-no, @josuah and @tmon-nordic for the review. @tmon-nordic, you're right. This is actually the @jfischer-no, I understand the concern about sample complexity and I'm happy to drop the CDC ACM changes. But I think tmon-nordic's comments highlights the real gap beyond the sample: even if a developer knows their node labels and calls This matters to make the correct adjustments: For reference, Linux's USB gadget configfs architecture solved this by scoping functions (classes) per-gadget and explicitly binding each gadget to a specific UDC controller. There is no global "register all functions" - each function is created within a gadget's namespace and explicitly linked to a configuration. It is architecturally impossible to accidentally bind a function to the wrong controller. This is essentially what we're missing in Zephyr: a way to associate class instances with a specific UDC context. The current global linker section approach works for single-UDC but has no path to multi-UDC without adding some form of device affinity. This PR goal was to expose this issues and have a point to open a more broad discussion to enable us to talk and find a good solution. What would be the best way to start with these topics in your opinion? |
It is not that the functions are randomly registered somewhere. The functions have names. Your application has full control over where to register these functions. And unregister them and move to another context. Whatever you want.
It is not that I have concerns. It is that I am absolutely against it. It does not solve a "real gap" but it introduces a huge problem. We do not assign functions to controllers, but to an USB device context.
Not sure what you mean. But please, if you think there is something wrong, file a bug report.
(2) just does not sounds like right way to do it using "single VBUS", who is bus-powered and who is self-powered.
Nice, but look Zephyr is not Linux kernel. No idea why we should do something like that. You do not have to use "register all functions"!
"global linker section" is not about "single-UDC", it is just to know what functions are there and allow them to be registered at context of your choice. So for your very unusual multi-UDC case, where multi<=2, please use usbd_register_class().
I do not see any need for "these topics". |
It has full control over where the classes are registered, but while application can refer to CDC ACM UART by respective label in devicetree I can't see a easy way to correlate |
|
This pull request has been marked as stale because it has been open (more than) 30 days with no activity. Remove the stale label or add a comment saying that you would like to have the label removed otherwise this pull request will automatically be closed in 7 days. Note, that you can always re-open a closed pull request at any time. |




This introduces a generic mechanism to instantiate multiple USB interfaces and enable/assign the classes automatically.
Problem:
Assuming that the hardware have multiple USB interfaces how we can select only the UDC instances?
Proposal:
Add a generic binding to be added in the interfaces to allow for use of FOR_EACH macros based in that bind. This allows us to use multi-vendor interfaces and group then together at build time.
Description:
This demonstrate how to use the binding in the CDC-ACM example.
The below snip add the binding in the two UDC interfaces. After that, the application can initialize looking all the
zephyr,udcdefined and active.The classes now should be added as child of the respective interfaces.
Using this approach, the user can control which interface is active and what is exposed on that interface.
The example demonstrate that no changes in the core stack are necessary to allow the binding to happen. Assuming that the USB-CORE can handle multiple instances per interface this could automate the initialization. The code was made to keep the compatibility with the
zephyr_udc0interface name and avoid double instantiation.Notes: