Skip to content

usb: device_next: allow to initialize multiple CDC ACM instances#105141

Open
jfischer-no wants to merge 6 commits intozephyrproject-rtos:mainfrom
jfischer-no:pr-cdc-acm-serial-multiple-instances
Open

usb: device_next: allow to initialize multiple CDC ACM instances#105141
jfischer-no wants to merge 6 commits intozephyrproject-rtos:mainfrom
jfischer-no:pr-cdc-acm-serial-multiple-instances

Conversation

@jfischer-no
Copy link
Copy Markdown
Contributor

Application can use new option CDC_ACM_SERIAL_MULTIPLE_INSTANCES if different CDC ACM serial backends are required for common use cases such as logging, the shell, and specific protocols. This option also guarantees the order in which the instances will be registered and appear in the configuration descriptor. The option uses the chosen node properties to identify UART devices.

The following are currently supported, in this order: "zephyr,console", "zephyr,shell-uart", "zephyr,uart-mcumgr". A supported property may be missing, and properties may reference the same device.

@zephyrbot zephyrbot added the area: USB Universal Serial Bus label Mar 9, 2026
@zephyrbot zephyrbot requested review from josuah and tmon-nordic March 9, 2026 12:20
Copy link
Copy Markdown
Contributor

@Damian-Nordic Damian-Nordic left a comment

Choose a reason for hiding this comment

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

Nice, thanks!

* instance. If multiple chosen node properties reference the same device,
* usbd_register_class() will fail with -EBUSY.
*/
static int register_multiple(struct usbd_context *const uds_ctx,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: not sure why this function is called register_multiple if it only registers on class matching the given device (?)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Mostly because or the Kconfig option name.

STRUCT_SECTION_FOREACH_ALTERNATE(usbd_class_hs, usbd_class_node, c_nd) {
struct usbd_class_data *c_data = c_nd->c_data;

if (c_data->priv == dev) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Isn't the whole idea behind private data structures, to actually be private to class implementations? Why comparing it up here against dev is appropriate?

if (c_data->priv == dev) {
err = usbd_register_class(&cdc_acm_serial,
c_data->name, speed, 1);
if (err != 0 && err != -EBUSY) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Multiple chosen nodes pointing to the same CDC ACM instance effectively rely on -EBUSY error being returned when instance is already registered. Class already being registered is not the only case when -EBUSY is returned. Why some errors can be treated differently from others?

@jfischer-no jfischer-no force-pushed the pr-cdc-acm-serial-multiple-instances branch from 4d68431 to da1d6ad Compare March 16, 2026 15:32
@sonarqubecloud
Copy link
Copy Markdown

STRUCT_SECTION_FOREACH_ALTERNATE(usbd_class_fs, usbd_class_node, c_nd) {
struct usbd_class_data *c_data = c_nd->c_data;

if (usbd_class_get_private(c_data) == dev) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Isn't the whole idea behind private data structures, to actually be private to class implementations? Calling usbd_class_get_private() to get access to private data structure is not changing the underlying problem with the fact that outside code should be not be allowed to look into private data structures.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

So in other words, are you asking that CDC ACM class implementation expose an intermediate utility, say in usb_cdc.h, for getting the associated device?

Copy link
Copy Markdown
Contributor

@tmon-nordic tmon-nordic Mar 17, 2026

Choose a reason for hiding this comment

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

If there is a need to access some information that is only accessible via class internals, then there is a need to expose the information using a proper API.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Isn't the whole idea behind private data structures, to actually be private to class implementations? Calling usbd_class_get_private() to get access to private data structure is not changing the underlying problem with the fact that outside code should be not be allowed to look into private data structures.

private void pointers are usually used to store references to a mapped device, context in Zephyr, or some user data, not only in USB. usbd_class_get_private() is a public USBD API to get the reference. This code is located in USB device support subsystem directory. I absolutely do not see any reason to introduce yet another API to get UART device mapped to a specific CDC ACM instance.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I absolutely do not see any reason to introduce yet another API to get UART device mapped to a specific CDC ACM instance.

While I think there is no need to get UART device mapped to a specific CDC ACM instance, there is a real need to get CDC ACM instance identified based on UART device.

Exactly the same root cause (no possibility to match zephyr,cdc-acm-uart with CDC ACM class instance) was the reason behind #103844. There you were quick to reject it and state:

You do not have to use "register all functions"!

While it is true that using usbd_register_all_classes is not mandatory, there is currently no interface that would allow the user to know which class to register if they want to have some particular USB functionality that is instantiated using devicetree. Exactly the same issue applies to zephyr,cdc-acm-uart, zephyr,hid-device, zephyr,uac2, zephyr,midi2-device. All these classes currently rely on usbd_register_all_classes and the fact that in most applications there is just a single UDC controller and a single USB device context.

To solve the problem, I believe we would need to have something like USBD_DEFINE_CLASS_DT() that would do what USBD_DEFINE_CLASS() does and create a const symbol that can be used at link time to match the USB class identifier (e.g. "cdc_acm_0") that should be possible to be obtained using some macro like USBD_CLASS_GET_DT(). Having such macro, there would be no need for violating encapsulation principle and the whole iteration you have here looking for the class instance would become completely unnecessary.

if (usbd_class_get_private(c_data) == dev) {
err = usbd_register_class(&cdc_acm_serial,
c_data->name, speed, 1);
if (err != 0 && err != -EALREADY) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why not determine if there are duplicates in the uart_devs list instead of handling -EALREADY here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Why make it more complicated and "determine if there are duplicates in the uart_devs list" for no reason?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Because sanitizing the list is effectively simpler than relying on specific errors to happen.

if (err) {
LOG_ERR("Failed to register classes");
return err;
if (IS_ENABLED(CONFIG_CDC_ACM_SERIAL_MULTIPLE_INSTANCES)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is it possible to deduce the same from ARRAY_SIZE(uart_devs) > 1? Or maybe it was preferred to use Kconfig for other reasons.

If so, I wonder if only using the for() loop only would work.

I might be missing a detail of backward compatibility with the legacy stack.

@github-actions
Copy link
Copy Markdown

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.

@github-actions github-actions Bot added the Stale label Apr 21, 2026
@github-actions github-actions Bot closed this Apr 28, 2026
@jfischer-no jfischer-no reopened this Apr 28, 2026
@jfischer-no jfischer-no removed the Stale label Apr 28, 2026
@jfischer-no jfischer-no force-pushed the pr-cdc-acm-serial-multiple-instances branch from da1d6ad to 203c325 Compare May 1, 2026 00:18
Add common mapping to sample.yaml.

Signed-off-by: Johann Fischer <johann.fischer@nordicsemi.no>
…tered

Return -EALREADY if the class instance already registered, which, in
some circumstances, may be considered a non-critical error.

Signed-off-by: Johann Fischer <johann.fischer@nordicsemi.no>
The new macro has an additional parameter that takes a string
literal that can be used as a reference.

For example, to have a reference between device name that a class
instance is implementing and class name, which is used to register a
class. The device name can be passed using the DEVICE_DT_NAME() macro.

Signed-off-by: Johann Fischer <johann.fischer@nordicsemi.no>
Use USBD_DEFINE_CLASS_WITH_NAME() in CDC ACM implementation.

Signed-off-by: Johann Fischer <johann.fischer@nordicsemi.no>
Application can use new option CDC_ACM_SERIAL_MULTIPLE_INSTANCES if
different CDC ACM serial backends are required for common use cases such
as logging, the shell, and specific protocols. This option also
guarantees the order in which the instances will be registered and
appear in the configuration descriptor. The option uses the chosen node
properties to identify UART devices.

The following are currently supported, in this order:
"zephyr,console", "zephyr,shell-uart", "zephyr,uart-mcumgr",
"zephyr,bt-c2h-uart", "zephyr-ot-uart", "zephyr-bt_mon-uart".
A supported property may be missing, and properties may reference the
same device.

Signed-off-by: Johann Fischer <johann.fischer@nordicsemi.no>
@jfischer-no jfischer-no force-pushed the pr-cdc-acm-serial-multiple-instances branch from 203c325 to e7e0d1e Compare May 1, 2026 00:22
@zephyrbot zephyrbot requested a review from MarkWangChinese May 1, 2026 00:24
Remove Kconfig option CDC_ACM_SERIAL_MULTIPLE_INSTANCES.
Instead initialize multiple instances when there are multiple nodes with
compatible "zephyr,cdc-acm-usrt".

Signed-off-by: Johann Fischer <johann.fischer@nordicsemi.no>
@jfischer-no jfischer-no force-pushed the pr-cdc-acm-serial-multiple-instances branch from e7e0d1e to 740cebd Compare May 1, 2026 13:42
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented May 1, 2026

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

Labels

area: Samples Samples area: USB Universal Serial Bus

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants