diff --git a/boot/zephyr/CMakeLists.txt b/boot/zephyr/CMakeLists.txt index 969a0ca21..cab0e6fee 100644 --- a/boot/zephyr/CMakeLists.txt +++ b/boot/zephyr/CMakeLists.txt @@ -312,6 +312,10 @@ if(CONFIG_MCUBOOT_SERIAL) zephyr_sources(boot_serial_extension_zephyr_basic.c) endif() endif() + + if(CONFIG_BOOT_SERIAL_CDC_ACM) + zephyr_sources(usbd_cdc_serial.c) + endif() endif() if(NOT CONFIG_BOOT_SIGNATURE_TYPE_NONE AND NOT CONFIG_BOOT_BUILTIN_KEY AND NOT diff --git a/boot/zephyr/Kconfig.serial_recovery b/boot/zephyr/Kconfig.serial_recovery index 780ba775d..a027b2827 100644 --- a/boot/zephyr/Kconfig.serial_recovery +++ b/boot/zephyr/Kconfig.serial_recovery @@ -37,7 +37,8 @@ config BOOT_SERIAL_UART config BOOT_SERIAL_CDC_ACM bool "CDC ACM" - select USB_DEVICE_STACK + select USB_DEVICE_STACK_NEXT + select USBD_CDC_ACM_CLASS help This setting will choose CDC ACM for serial recovery unless chosen "zephyr,uart-mcumgr" is present, in which case the chosen takes @@ -46,6 +47,51 @@ config BOOT_SERIAL_CDC_ACM endchoice +if BOOT_SERIAL_CDC_ACM + +config BOOT_SERIAL_CDC_ACM_VID + hex "USB Vendor ID for CDC ACM serial recovery" + default 0x2fe3 + help + USB Vendor ID presented during serial recovery mode. + You must use your own VID for samples and applications outside of Zephyr Project. + +config BOOT_SERIAL_CDC_ACM_PID + hex "USB Product ID for CDC ACM serial recovery" + default 0x0004 + help + USB Product ID presented during serial recovery mode. + You must use your own PID for samples and applications outside of Zephyr Project. + +config BOOT_SERIAL_CDC_ACM_MANUFACTURER_STRING + string "USB manufacturer string for CDC ACM serial recovery" + default "MCUBoot" + help + USB manufacturer string descriptor for serial recovery mode. + +config BOOT_SERIAL_CDC_ACM_PRODUCT_STRING + string "USB product string for CDC ACM serial recovery" + default "CDC ACM serial recovery" + help + USB product string descriptor for serial recovery mode. + +config BOOT_SERIAL_CDC_ACM_SELF_POWERED + bool "USB self-powered attribute for CDC ACM serial recovery" + help + Set the Self-powered attribute in the USB configuration descriptor. + When enabled, the device reports that it does not draw power from + the USB bus. Only enable this if the device is powered by an + independent supply during serial recovery mode. + +config BOOT_SERIAL_CDC_ACM_MAX_POWER + int "USB bMaxPower value for CDC ACM serial recovery" + default 125 + range 0 250 + help + bMaxPower value in the configuration in 2 mA units. + +endif # BOOT_SERIAL_CDC_ACM + config MCUBOOT_SERIAL_DIRECT_IMAGE_UPLOAD bool "Allow to select image number for DFU" depends on !SINGLE_APPLICATION_SLOT diff --git a/boot/zephyr/boards/nrf52840_big.overlay b/boot/zephyr/boards/nrf52840_big.overlay index f630fd461..3f608dc8c 100644 --- a/boot/zephyr/boards/nrf52840_big.overlay +++ b/boot/zephyr/boards/nrf52840_big.overlay @@ -17,17 +17,17 @@ boot_partition: partition@0 { label = "mcuboot"; - reg = <0x000000000 0x00010000>; + reg = <0x000000000 0x00012000>; }; - slot0_partition: partition@10000 { + slot0_partition: partition@12000 { label = "image-0"; - reg = <0x000010000 0x000074000>; + reg = <0x000012000 0x000073000>; }; - slot1_partition: partition@75000 { + slot1_partition: partition@85000 { label = "image-1"; - reg = <0x00084000 0x000074000>; + reg = <0x00085000 0x000073000>; }; }; }; diff --git a/boot/zephyr/boards/nrf52840dongle_nrf52840.conf b/boot/zephyr/boards/nrf52840dongle_nrf52840.conf index d219f351d..2e75af19e 100644 --- a/boot/zephyr/boards/nrf52840dongle_nrf52840.conf +++ b/boot/zephyr/boards/nrf52840dongle_nrf52840.conf @@ -18,9 +18,4 @@ CONFIG_BOOT_SERIAL_CDC_ACM=y # Required by USB CONFIG_MULTITHREADING=y -# USB -CONFIG_USB_DEVICE_STACK=y -CONFIG_USB_DEVICE_REMOTE_WAKEUP=n -CONFIG_USB_DEVICE_PRODUCT="MCUBOOT" - CONFIG_NORDIC_QSPI_NOR=n diff --git a/boot/zephyr/boards/nrf52840dongle_nrf52840_big.overlay b/boot/zephyr/boards/nrf52840dongle_nrf52840_big.overlay new file mode 100644 index 000000000..3a511c0e5 --- /dev/null +++ b/boot/zephyr/boards/nrf52840dongle_nrf52840_big.overlay @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2026 Tolt Technologies + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/delete-node/ &boot_partition; +/delete-node/ &slot0_partition; +/delete-node/ &slot1_partition; + +&flash0 { + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + boot_partition: partition@1000 { + label = "mcuboot"; + reg = <0x00001000 0x00010000>; + }; + + slot0_partition: partition@11000 { + label = "image-0"; + reg = <0x00011000 0x00065000>; + }; + + slot1_partition: partition@76000 { + label = "image-1"; + reg = <0x00076000 0x00065000>; + }; + }; +}; diff --git a/boot/zephyr/main.c b/boot/zephyr/main.c index 0d04240d9..209c7ecbc 100644 --- a/boot/zephyr/main.c +++ b/boot/zephyr/main.c @@ -90,6 +90,18 @@ const struct boot_uart_funcs boot_funcs = { #include #endif +#ifdef CONFIG_USB_DEVICE_STACK_NEXT +#include +#endif /* CONFIG_USB_DEVICE_STACK_NEXT */ + +#ifdef CONFIG_BOOT_SERIAL_CDC_ACM +#include "usbd_cdc_serial.h" +#endif /* CONFIG_BOOT_SERIAL_CDC_ACM */ + +#if defined(CONFIG_BOOT_SERIAL_UART) && defined(CONFIG_LOG_BACKEND_UART) +#error "UART serial recovery and UART log backend cannot both be enabled" +#endif + #if CONFIG_MCUBOOT_CLEANUP_ARM_CORE #include #endif @@ -185,6 +197,21 @@ static void do_boot(struct boot_rsp *rsp) /* Disable the USB to prevent it from firing interrupts */ usb_disable(); #endif +#ifdef CONFIG_USB_DEVICE_STACK_NEXT + { + int usbd_rc; + + usbd_rc = usbd_disable(boot_usb_cdc_serial_get_context()); + + /* -EALREADY is expected on normal boot: USB was never enabled + * (lazy init -- only initialized when recovery is triggered). + * Any other error indicates a real problem. + */ + if (usbd_rc != 0 && usbd_rc != -EALREADY) { + BOOT_LOG_WRN("USB disable failed: %d", usbd_rc); + } + } +#endif #if CONFIG_MCUBOOT_CLEANUP_ARM_CORE cleanup_arm_interrupts(); /* Disable and acknowledge all interrupts */ diff --git a/boot/zephyr/sample.yaml b/boot/zephyr/sample.yaml index 5e50a44d8..7d429f896 100644 --- a/boot/zephyr/sample.yaml +++ b/boot/zephyr/sample.yaml @@ -65,7 +65,7 @@ tests: platform_allow: nrf52840dongle/nrf52840 extra_args: - EXTRA_CONF_FILE=./usb_cdc_acm_recovery.conf - - DTC_OVERLAY_FILE="./usb_cdc_acm.overlay;app.overlay" + - DTC_OVERLAY_FILE="./boards/nrf52840dongle_nrf52840_big.overlay;./usb_cdc_acm.overlay;app.overlay" extra_configs: - CONFIG_DEPRECATION_TEST=y sample.bootloader.mcuboot.usb_cdc_acm_recovery_log: diff --git a/boot/zephyr/serial_adapter.c b/boot/zephyr/serial_adapter.c index de2e5c511..961b20a5c 100644 --- a/boot/zephyr/serial_adapter.c +++ b/boot/zephyr/serial_adapter.c @@ -20,7 +20,8 @@ #include #include #include "bootutil/bootutil_log.h" -#include +#include "mcuboot_config/mcuboot_config.h" +#include #if defined(CONFIG_BOOT_SERIAL_UART) && defined(CONFIG_UART_CONSOLE) && \ (!DT_HAS_CHOSEN(zephyr_uart_mcumgr) || \ @@ -40,6 +41,10 @@ (0 or above) #endif +#if defined(CONFIG_BOOT_SERIAL_CDC_ACM) +#include "usbd_cdc_serial.h" +#endif + BOOT_LOG_MODULE_REGISTER(serial_adapter); /** @brief Console input representation @@ -213,24 +218,10 @@ boot_uart_fifo_init(void) uart_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_console)); #endif -#elif defined(CONFIG_BOOT_SERIAL_CDC_ACM) - uart_dev = DEVICE_DT_GET_ONE(zephyr_cdc_acm_uart); -#else -#error No serial recovery device selected -#endif - - if (!device_is_ready(uart_dev)) { return (-1); } -#if CONFIG_BOOT_SERIAL_CDC_ACM - int rc = usb_enable(NULL); - if (rc) { - return (-1); - } -#endif - uart_irq_callback_set(uart_dev, boot_uart_fifo_callback); /* Drain the fifo */ @@ -246,5 +237,41 @@ boot_uart_fifo_init(void) uart_irq_rx_enable(uart_dev); +#elif defined(CONFIG_BOOT_SERIAL_CDC_ACM) + + int rc; + + rc = boot_usb_cdc_serial_init(); + if (rc) { + return (-1); + } + + rc = usbd_enable(boot_usb_cdc_serial_get_context()); + if (rc) { + return (-1); + } + + /* Feed WDT while waiting for USB host to open the serial port. + * Without this, the hardware watchdog fires before the serial + * recovery loop (which feeds the WDT) is reached. */ + while (k_sem_take(&boot_cdc_acm_ready, K_MSEC(5000)) != 0) { +#if CONFIG_BOOT_WATCHDOG_FEED + MCUBOOT_WATCHDOG_FEED(); +#endif + } + + uart_dev = DEVICE_DT_GET_ONE(zephyr_cdc_acm_uart); + if (!device_is_ready(uart_dev)) { + return (-1); + } + + uart_irq_callback_set(uart_dev, boot_uart_fifo_callback); + cur = 0; + uart_irq_rx_enable(uart_dev); + +#else +#error No serial recovery device selected +#endif + return 0; } diff --git a/boot/zephyr/usb_cdc_acm_log_recovery.conf b/boot/zephyr/usb_cdc_acm_log_recovery.conf index 2312c0ece..a1e0586af 100644 --- a/boot/zephyr/usb_cdc_acm_log_recovery.conf +++ b/boot/zephyr/usb_cdc_acm_log_recovery.conf @@ -9,3 +9,7 @@ CONFIG_UART_LINE_CTRL=y # MCUBoot serial CONFIG_MCUBOOT_SERIAL=y CONFIG_BOOT_SERIAL_CDC_ACM=y +CONFIG_BOOT_SERIAL_CDC_ACM_PRODUCT_STRING="MCUBOOT" +CONFIG_USBD_MSG_DEFERRED_MODE=n +CONFIG_USBD_BOS_SUPPORT=n +CONFIG_USBD_VREQ_SUPPORT=n diff --git a/boot/zephyr/usb_cdc_acm_recovery.conf b/boot/zephyr/usb_cdc_acm_recovery.conf index 8bd340970..a853ee36f 100644 --- a/boot/zephyr/usb_cdc_acm_recovery.conf +++ b/boot/zephyr/usb_cdc_acm_recovery.conf @@ -1,3 +1,8 @@ CONFIG_MCUBOOT_SERIAL=y CONFIG_BOOT_SERIAL_CDC_ACM=y CONFIG_UART_CONSOLE=n + +CONFIG_BOOT_SERIAL_CDC_ACM_PRODUCT_STRING="MCUBOOT" +CONFIG_USBD_MSG_DEFERRED_MODE=n +CONFIG_USBD_BOS_SUPPORT=n +CONFIG_USBD_VREQ_SUPPORT=n diff --git a/boot/zephyr/usbd_cdc_serial.c b/boot/zephyr/usbd_cdc_serial.c new file mode 100644 index 000000000..32ba9af76 --- /dev/null +++ b/boot/zephyr/usbd_cdc_serial.c @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright (c) 2026 Tolt Technologies + * + * Self-contained USB CDC ACM setup for MCUboot serial recovery. + * Initialized lazily in boot_uart_fifo_init() only when recovery is needed. + */ + +#include +#include +#include + +#include "bootutil/bootutil_log.h" +#include "usbd_cdc_serial.h" + +BOOT_LOG_MODULE_DECLARE(mcuboot); + +USBD_DEVICE_DEFINE(boot_usbd, + DEVICE_DT_GET(DT_NODELABEL(zephyr_udc0)), + CONFIG_BOOT_SERIAL_CDC_ACM_VID, + CONFIG_BOOT_SERIAL_CDC_ACM_PID); + +USBD_DESC_LANG_DEFINE(boot_usbd_lang); +USBD_DESC_MANUFACTURER_DEFINE(boot_usbd_mfr, + CONFIG_BOOT_SERIAL_CDC_ACM_MANUFACTURER_STRING); +USBD_DESC_PRODUCT_DEFINE(boot_usbd_product, + CONFIG_BOOT_SERIAL_CDC_ACM_PRODUCT_STRING); +IF_ENABLED(CONFIG_HWINFO, (USBD_DESC_SERIAL_NUMBER_DEFINE(boot_usbd_sn))); + +static const uint8_t boot_usbd_attributes = + IS_ENABLED(CONFIG_BOOT_SERIAL_CDC_ACM_SELF_POWERED) + ? USB_SCD_SELF_POWERED : 0; + +USBD_DESC_CONFIG_DEFINE(boot_usbd_fs_cfg_desc, "FS Configuration"); +USBD_CONFIGURATION_DEFINE(boot_usbd_fs_config, + boot_usbd_attributes, + CONFIG_BOOT_SERIAL_CDC_ACM_MAX_POWER, + &boot_usbd_fs_cfg_desc); + +#if USBD_SUPPORTS_HIGH_SPEED +USBD_DESC_CONFIG_DEFINE(boot_usbd_hs_cfg_desc, "HS Configuration"); +USBD_CONFIGURATION_DEFINE(boot_usbd_hs_config, + boot_usbd_attributes, + CONFIG_BOOT_SERIAL_CDC_ACM_MAX_POWER, + &boot_usbd_hs_cfg_desc); +#endif + +K_SEM_DEFINE(boot_cdc_acm_ready, 0, 1); + +static void boot_usbd_msg_cb(struct usbd_context *const ctx, + const struct usbd_msg *const msg) +{ + ARG_UNUSED(ctx); + + if (msg->type == USBD_MSG_CDC_ACM_CONTROL_LINE_STATE) { + k_sem_give(&boot_cdc_acm_ready); + } +} + +static int boot_usbd_register_fs(void) +{ + int err; + + err = usbd_add_configuration(&boot_usbd, USBD_SPEED_FS, + &boot_usbd_fs_config); + if (err) { + BOOT_LOG_ERR("Failed to add FS configuration: %d", err); + return err; + } + + err = usbd_register_class(&boot_usbd, "cdc_acm_0", USBD_SPEED_FS, 1); + if (err) { + BOOT_LOG_ERR("Failed to register CDC ACM class (FS): %d", err); + return err; + } + + err = usbd_device_set_code_triple(&boot_usbd, USBD_SPEED_FS, + USB_BCC_MISCELLANEOUS, 0x02, 0x01); + if (err) { + BOOT_LOG_ERR("Failed to set code triple (FS): %d", err); + } + + return err; +} + +#if USBD_SUPPORTS_HIGH_SPEED +static int boot_usbd_register_hs(void) +{ + int err; + + err = usbd_add_configuration(&boot_usbd, USBD_SPEED_HS, + &boot_usbd_hs_config); + if (err) { + BOOT_LOG_ERR("Failed to add HS configuration: %d", err); + return err; + } + + err = usbd_register_class(&boot_usbd, "cdc_acm_0", USBD_SPEED_HS, 1); + if (err) { + BOOT_LOG_ERR("Failed to register CDC ACM class (HS): %d", err); + return err; + } + + err = usbd_device_set_code_triple(&boot_usbd, USBD_SPEED_HS, + USB_BCC_MISCELLANEOUS, 0x02, 0x01); + if (err) { + BOOT_LOG_ERR("Failed to set code triple (HS): %d", err); + } + + return err; +} +#endif + +int boot_usb_cdc_serial_init(void) +{ + int err; + + err = usbd_add_descriptor(&boot_usbd, &boot_usbd_lang); + if (err) { + BOOT_LOG_ERR("Failed to add language descriptor: %d", err); + return err; + } + + err = usbd_add_descriptor(&boot_usbd, &boot_usbd_mfr); + if (err) { + BOOT_LOG_ERR("Failed to add manufacturer descriptor: %d", err); + return err; + } + + err = usbd_add_descriptor(&boot_usbd, &boot_usbd_product); + if (err) { + BOOT_LOG_ERR("Failed to add product descriptor: %d", err); + return err; + } + + IF_ENABLED(CONFIG_HWINFO, ( + err = usbd_add_descriptor(&boot_usbd, &boot_usbd_sn); + if (err) { + BOOT_LOG_ERR("Failed to add serial number descriptor: %d", err); + return err; + } + )) + +#if USBD_SUPPORTS_HIGH_SPEED + if (usbd_caps_speed(&boot_usbd) == USBD_SPEED_HS) { + err = boot_usbd_register_hs(); + if (err) { + BOOT_LOG_ERR("Failed to register HS configuration: %d", err); + return err; + } + } +#endif + + err = boot_usbd_register_fs(); + if (err) { + BOOT_LOG_ERR("Failed to register FS configuration: %d", err); + return err; + } + + usbd_self_powered(&boot_usbd, + boot_usbd_attributes & USB_SCD_SELF_POWERED); + + err = usbd_msg_register_cb(&boot_usbd, boot_usbd_msg_cb); + if (err) { + BOOT_LOG_ERR("Failed to register message callback: %d", err); + return err; + } + + err = usbd_init(&boot_usbd); + if (err) { + BOOT_LOG_ERR("Failed to initialize USB device: %d", err); + } + + return err; +} + +struct usbd_context *boot_usb_cdc_serial_get_context(void) +{ + return &boot_usbd; +} diff --git a/boot/zephyr/usbd_cdc_serial.h b/boot/zephyr/usbd_cdc_serial.h new file mode 100644 index 000000000..6cd4c23dc --- /dev/null +++ b/boot/zephyr/usbd_cdc_serial.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* + * Copyright (c) 2026 Tolt Technologies + */ + +#ifndef BOOT_USBD_CDC_SERIAL_H +#define BOOT_USBD_CDC_SERIAL_H + +#include + +struct usbd_context; + +int boot_usb_cdc_serial_init(void); +struct usbd_context *boot_usb_cdc_serial_get_context(void); +extern struct k_sem boot_cdc_acm_ready; + +#endif /* BOOT_USBD_CDC_SERIAL_H */ diff --git a/docs/readme-zephyr.md b/docs/readme-zephyr.md index 6b12b5b06..08dfaa38f 100644 --- a/docs/readme-zephyr.md +++ b/docs/readme-zephyr.md @@ -220,6 +220,20 @@ Which interface belongs to the protocol shall be set by the devicetree-chosen no - `zephyr,console` - If a hardware serial port is used. - `zephyr,cdc-acm-uart` - If a virtual serial port is used. +When ``CONFIG_BOOT_SERIAL_CDC_ACM=y`` is selected, MCUboot manages the USB device stack +internally and only initializes it when serial recovery is actually triggered — not on every boot. +This avoids consuming hardware USB events that the application would otherwise receive. +The USB device descriptors can be configured with the following Kconfig options: + +| Option | Default | Description | +|--------|---------|-------------| +| ``CONFIG_BOOT_SERIAL_CDC_ACM_VID`` | ``0x2fe3`` | USB Vendor ID | +| ``CONFIG_BOOT_SERIAL_CDC_ACM_PID`` | ``0x0004`` | USB Product ID | +| ``CONFIG_BOOT_SERIAL_CDC_ACM_MANUFACTURER_STRING`` | ``"Zephyr Project"`` | Manufacturer string descriptor | +| ``CONFIG_BOOT_SERIAL_CDC_ACM_PRODUCT_STRING`` | ``"CDC ACM serial recovery"`` | Product string descriptor | +| ``CONFIG_BOOT_SERIAL_CDC_ACM_SELF_POWERED`` | ``n`` | Set the self-powered attribute in the USB configuration descriptor | +| ``CONFIG_BOOT_SERIAL_CDC_ACM_MAX_POWER`` | ``125`` | ``bMaxPower`` value in the USB configuration descriptor, in 2 mA units (default: 250 mA) | + ### Entering the serial recovery mode To enter the serial recovery mode, the device has to initiate rebooting, and a triggering event has to occur (for example, pressing a button).