1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
19037: sys/usb, pkg/tinyusb: move USB board reset from highlevel STDIO to CDC ACM r=dylad a=gschorcht

### Contribution description

The USB board reset function `usb_board_reset_coding_cb` can be used on any CDC-ACM interface, even if the CDC ACM interface is not used as high-level STDIO. Therefore, this PR provides the following changes:

- The call of the board reset function `usb_board_reset_coding_cb` from USBUS stack has been moved from the STDIO CDC ACM implementation to the CDC ACM implementation and is thus a feature of any USBUS CDC ACM interface which does not necessarily have to be used as highlevel STDIO.

- The call of the board reset function `usb_board_reset_coding_cb` from tinyUSB stack been moved from module `tinyusb_stdio_cdc_acm` to module `tinyusb_contrib` and is compiled in if the `tinyusb_class_cdc` module is used together the `tinyusb_device` module. Thus, it is now a feature of the tinyUSB CDC ACM interface, which does not necessarily have to be used as highlevel STDIO.
 
- The `usb_board_reset` module defines the `usb_board_reset_in_bootloader` function as a weak symbol to be used when reset in bootloader if no real implementation of this function is compiled in and the `riotboot_reset` module is not used. It only prints an error message that the reset in bootloader is not supported. This is necessary if the module `usb_board_reset` is used to be able to restart the board with an application via a USB CDC ACM interface, but the board's bootloader does not support the reset in bootloader feature.

- A test application has been added that either uses the highlevel STDIO `stdio_acm_cdc` or creates a CDC-ACM interface to enable board resets via USB. If the `usbus_dfu` module is used, it also initializes the DFU interface to be able to work together with the `riotboot_dfu` bootloader.

### Testing procedure

1. Use a board with a bootloader that supports the reset in bootloader via USB, but don't use the highlevel STDIO to check that it works with `usbus_cdc_acm`, for example:
   ```python
   USEMODULE=stdio_uart BOARD=arduino-mkr1000 make -C tests/usb_board_reset flash
   ```
   After reset in application with command
   ```python
   stty -F /dev/ttyACM0 raw ispeed 600 ospeed 600 cs8 -cstopb ignpar eol 255 eof 255
   ```
   command `dmesg` should give an output like the following with RIOT's test VID/PID:
   ```python
   dmesg
   [1745182.057403] usb 1-4.1.2: new full-speed USB device number 69 using xhci_hcd
   [1745182.160386] usb 1-4.1.2: New USB device found, idVendor=1209, idProduct=7d01, bcdDevice= 1.00
   [1745182.160390] usb 1-4.1.2: New USB device strings: Mfr=3, Product=2, SerialNumber=4
   [1745182.160392] usb 1-4.1.2: Product: arduino-mkr1000
   [1745182.160393] usb 1-4.1.2: Manufacturer: RIOT-os.org
   [1745182.160395] usb 1-4.1.2: SerialNumber: 6B6C2CA5229020D8
   [1745182.170982] cdc_acm 1-4.1.2:1.0: ttyACM0: USB ACM device
   ```
   After reset in bootloader with command
   ```python
   stty -F /dev/ttyACM0 raw ispeed 1200 ospeed 1200 cs8 -cstopb ignpar eol 255 eof 255
   ```
   command `dmesg` should give an output like the following with vendor VID/PID:
   ```python
   [1746220.443792] usb 1-4.1.2: new full-speed USB device number 70 using xhci_hcd
   [1746220.544705] usb 1-4.1.2: New USB device found, idVendor=2341, idProduct=024e, bcdDevice= 2.00
   [1746220.544708] usb 1-4.1.2: New USB device strings: Mfr=0, Product=0, SerialNumber=0
   [1746220.553471] cdc_acm 1-4.1.2:1.0: ttyACM0: USB ACM device
   ```
   
2. Test the same as in 1., but this time use the highlevel STDIO to check that there is no regression and it still works with `stdio_cdc_acm`, for example:
   ```python
   BOARD=arduino-mkr1000 make -C tests/usb_board_reset flash
   ```
   
3. Use a board that supports `riotboot_dfu` but doesn't use the highlevel STDIO and flash the `riotboot_dfu` bootloader, for example:
   ```python
   BOARD=stm32f429i-disc1 make -C bootloaders/riotboot_dfu flash term
   ```
   Once the bootloader is flashed, command `dfu-util --list` should give something like the following:
   ```python
   Found DFU: [1209:7d02] ver=0100, devnum=14, cfg=1, intf=0, path="1-2", alt=1, name="RIOT-OS Slot 1", serial="6591620BCB270283"
   Found DFU: [1209:7d02] ver=0100, devnum=14, cfg=1, intf=0, path="1-2", alt=0, name="RIOT-OS Slot 0", serial="6591620BCB270283"
   ```
   If the output gives only
   ```python
   Found Runtime: [1209:7d00] ver=0100, devnum=123, cfg=1, intf=0, path="1-2", alt=0, name="RIOT-OS bootloader", serial="6591620BCB270283"
   ```
   an application is already running in DFU Runtime mode. Use `dfu-util -e` to restart it in bootloader DFU mode.
   Then flash the test application, for example:
   ```python
   FEATURES_REQUIRED=riotboot USEMODULE='usbus_dfu riotboot_reset' \
   BOARD=stm32f429i-disc1 make -C tests/usbus_board_reset PROGRAMMER=dfu-util riotboot/flash-slot0
   ```
   Once the test application is flashed, command `dfu-util --list` should give:
   ```python
   Found Runtime: [1209:7d00] ver=0100, devnum=123, cfg=1, intf=0, path="1-2", alt=0, name="RIOT-OS bootloader", serial="6591620BCB270283"
   ```
   Now, use command
   ```python
   stty -F /dev/ttyACM1 raw ispeed 600 ospeed 600 cs8 -cstopb ignpar eol 255 eof 255
   ``` 
   to restart the board in application. Command `dfu-util --list` should give again the following:
   ```python
   Found Runtime: [1209:7d00] ver=0100, devnum=123, cfg=1, intf=0, path="1-2", alt=0, name="RIOT-OS bootloader", serial="6591620BCB270283"
   ```
   That is, the application is running in DFU Runtime mode. Then use command
   ```python
   stty -F /dev/ttyACM1 raw ispeed 1200 ospeed 1200 cs8 -cstopb ignpar eol 255 eof 255
   ```
   to restart the board in bootloader DFU mode. Command  `dfu-util --list` should now give the following:
   ```python
   Found DFU: [1209:7d02] ver=0100, devnum=50, cfg=1, intf=0, path="1-2", alt=1, name="RIOT-OS Slot 1", serial="7D156425A950A8EB"
   Found DFU: [1209:7d02] ver=0100, devnum=50, cfg=1, intf=0, path="1-2", alt=0, name="RIOT-OS Slot 0", serial="7D156425A950A8EB"
   ```
   That is, the bootloader is in DFU mode and another application can be flash.
   
4. After a hard reset of the board under 3., try the commands `reboot` and `bootloader`. 

5. To check the same for tinyUSB, use the existing tinyUSB application with a CDC ACM interface and add module `usb_board_reset`, for example:
   ```python
   USEMODULE=usb_board_reset BOARD=stm32f429i-disc1 make -C tests/pkg_tinyusb_cdc_msc flash term
   ```
   After flashing, it should be possible to restart the application with command:
   ```python
   stty -F /dev/ttyACM1 raw ispeed 600 ospeed 600 cs8 -cstopb ignpar eol 255 eof 255
   ```
   When using command
   ```python
   stty -F /dev/ttyACM1 raw ispeed 1200 ospeed 1200 cs8 -cstopb ignpar eol 255 eof 255
   ```
   the following error message should be shown in terminal
   ```python
   [cdc-acm] reset in bootloader is not supported
   ```
   
### Issues/PRs references


Co-authored-by: Gunar Schorcht <gunar@schorcht.net>
This commit is contained in:
bors[bot] 2023-01-03 19:36:16 +00:00 committed by GitHub
commit e51d8285f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 200 additions and 36 deletions

View File

@ -6,7 +6,7 @@
config MODULE_BOARDS_COMMON_SAMDX1-ARDUINO-BOOTLOADER
bool
default y if MODULE_STDIO_CDC_ACM
default y if MODULE_USBUS_CDC_ACM
imply MODULE_USB_BOARD_RESET
depends on TEST_KCONFIG
help

View File

@ -1,7 +1,7 @@
include $(RIOTBOARD)/common/makefiles/stdio_cdc_acm.dep.mk
# USB Board reset only works if CDC ACM is used
ifneq (,$(filter stdio_cdc_acm,$(USEMODULE)))
ifneq (,$(filter usbus_cdc_acm,$(USEMODULE)))
USEMODULE += boards_common_samdx1-arduino-bootloader
USEMODULE += usb_board_reset
endif

View File

@ -32,11 +32,6 @@
#include "vfs.h"
#endif
#ifdef MODULE_USB_BOARD_RESET
#include "usb_board_reset_internal.h"
#include "class/cdc/cdc.h"
#endif
static mutex_t data_lock = MUTEX_INIT_LOCKED;
void stdio_init(void)
@ -81,21 +76,3 @@ void tud_cdc_rx_cb(uint8_t itf)
mutex_unlock(&data_lock);
}
#ifdef MODULE_USB_BOARD_RESET
void tud_cdc_line_coding_cb(uint8_t itf, cdc_line_coding_t const* p_line_coding)
{
(void)itf;
assert(p_line_coding != NULL);
/* The first parameter is the USBUS CDC ACM device, but this is
* not used in `usb_board_reset_coding_cb`. Therefore we can simply
* reuse this callback function in tinyUSB without any problems. */
usb_board_reset_coding_cb(NULL,
p_line_coding->bit_rate,
p_line_coding->data_bits,
p_line_coding->parity,
p_line_coding->stop_bits);
}
#endif

View File

@ -0,0 +1,31 @@
/*
* Copyright (C) 2022 Gunar Schorcht
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
#if MODULE_TINYUSB_DEVICE && MODULE_TINYUSB_CLASS_CDC && MODULE_USB_BOARD_RESET
#include "usb_board_reset_internal.h"
#include "class/cdc/cdc.h"
void tud_cdc_line_coding_cb(uint8_t itf, cdc_line_coding_t const* p_line_coding)
{
(void)itf;
assert(p_line_coding != NULL);
/* The first parameter is the USBUS CDC ACM device, but this is
* not used in `usb_board_reset_coding_cb`. Therefore we can simply
* reuse this callback function in tinyUSB without any problems. */
usb_board_reset_coding_cb(NULL,
p_line_coding->bit_rate,
p_line_coding->data_bits,
p_line_coding->parity,
p_line_coding->stop_bits);
}
#else
typedef int dont_be_pedantic;
#endif

View File

@ -29,6 +29,8 @@
#include "usb/usbus/cdc/acm.h"
#include "usb/usbus/control.h"
#include "usb_board_reset_internal.h"
#define ENABLE_DEBUG 0
#include "debug.h"
@ -259,7 +261,7 @@ static int _control_handler(usbus_t *usbus, usbus_handler_t *handler,
usbus_cdcacm_device_t *cdcacm = (usbus_cdcacm_device_t*)handler;
switch (setup->request) {
case USB_CDC_MGNT_REQUEST_SET_LINE_CODING:
if (!(cdcacm->coding_cb)) {
if (!(cdcacm->coding_cb) && !IS_USED(MODULE_USB_BOARD_RESET)) {
/* Line coding not supported, return STALL */
DEBUG("CDCACM: line coding not supported\n");
return -1;
@ -279,6 +281,12 @@ static int _control_handler(usbus_t *usbus, usbus_handler_t *handler,
sizeof(usb_req_cdcacm_coding_t), len);
return -1;
}
if (IS_USED(MODULE_USB_BOARD_RESET)) {
/* call board reset function first if reset is received */
usb_board_reset_coding_cb(cdcacm, coding->baud,
coding->databits, coding->parity,
coding->format);
}
if (cdcacm->coding_cb) {
DEBUG("Setting line coding to baud rate %" PRIu32 ", "
"%u data bits, parity value %u, stop bit value %u\n",

View File

@ -34,10 +34,6 @@
#include "vfs.h"
#endif
#ifdef MODULE_USB_BOARD_RESET
#include "usb_board_reset_internal.h"
#endif
static usbus_cdcacm_device_t cdcacm;
static uint8_t _cdc_tx_buf_mem[CONFIG_USBUS_CDC_ACM_STDIO_BUF_SIZE];
static uint8_t _cdc_rx_buf_mem[CONFIG_USBUS_CDC_ACM_STDIO_BUF_SIZE];
@ -91,7 +87,4 @@ void usb_cdc_acm_stdio_init(usbus_t *usbus)
{
usbus_cdc_acm_init(usbus, &cdcacm, _cdc_acm_rx_pipe, NULL,
_cdc_tx_buf_mem, sizeof(_cdc_tx_buf_mem));
#ifdef MODULE_USB_BOARD_RESET
usbus_cdc_acm_set_coding_cb(&cdcacm, usb_board_reset_coding_cb);
#endif
}

View File

@ -8,4 +8,4 @@
config MODULE_USB_BOARD_RESET
bool "Trigger a board reset via USB CDC ACM"
depends on TEST_KCONFIG
depends on MODULE_USBUS_CDC_ACM || MODULE_STDIO_TINYUSB_CDC_ACM
depends on MODULE_USBUS_CDC_ACM || (MODULE_TINYUSB_DEVICE && MODULE_TINYUSB_CLASS_CDC)

View File

@ -47,11 +47,11 @@ int usb_board_reset_coding_cb(usbus_cdcacm_device_t *cdcacm,
(void)stop;
switch (baud) {
case RESET_IN_BOOTLOADER_TRIGGER_BAUDRATE:
LOG_DEBUG("[cdc-acm-stdio] reset in bootloader");
LOG_DEBUG("[cdc-acm] reset in bootloader\n");
usb_board_reset_in_bootloader();
break;
case RESET_IN_APPLICATION_TRIGGER_BAUDRATE:
LOG_DEBUG("[cdc-acm-stdio] reset in application");
LOG_DEBUG("[cdc-acm] reset in application\n");
usb_board_reset_in_application();
break;
default:
@ -62,6 +62,22 @@ int usb_board_reset_coding_cb(usbus_cdcacm_device_t *cdcacm,
return 0;
}
#ifndef MODULE_RIOTBOOT_RESET
/*
* Definition of a function as weak symbol for reset in bootloader which
* prints an error message if no real implementation is compiled in and
* the `riotboot_reset` module is not used. This is required if the module
* `usb_board_reset` is used to restart the board with an application via
* an USB CDC ACM interface, but the board's bootloader does not support
* a reset in the bootloader.
*/
__attribute__((weak))
void usb_board_reset_in_bootloader(void)
{
LOG_ERROR("[cdc-acm] reset in bootloader is not supported\n");
}
#endif
void usb_board_reset_in_application(void)
{
pm_reboot();

View File

@ -0,0 +1,17 @@
include ../Makefile.tests_common
FEATURES_REQUIRED += periph_usbdev
USEMODULE += app_metadata
USEMODULE += ps
USEMODULE += shell
USEMODULE += shell_cmds_default
USEMODULE += usb_board_reset
USEMODULE += usbus_cdc_acm
DISABLE_MODULE += auto_init_usbus
USB_VID ?= $(USB_VID_TESTING)
USB_PID ?= $(USB_PID_TESTING)
include $(RIOTBASE)/Makefile.include

View File

@ -0,0 +1,48 @@
# Overview
This test represents a simple shell application that can be used to test
the USB board reset function (module `usb_board_reset`) via the USB CDC ACM
interface.
It can be used to reset the board to restart either the application or the
bootloader if supported using the `usb_board_reset` module.
The test application requires that the board provides USB peripherals
(feature `periph_usbdev`).
# Usage
Once the test application is flashed, the board should be detected. The output
of command
```
dmesg
```
should look like the following and should show the USB DC ACM interface:
```
[1745182.057403] usb 1-4.1.2: new full-speed USB device number 69 using xhci_hcd
[1745182.160386] usb 1-4.1.2: New USB device found, idVendor=1209, idProduct=7d01, bcdDevice= 1.00
[1745182.160390] usb 1-4.1.2: New USB device strings: Mfr=3, Product=2, SerialNumber=4
[1745182.160392] usb 1-4.1.2: Product: arduino-mkr1000
[1745182.160393] usb 1-4.1.2: Manufacturer: RIOT-os.org
[1745182.160395] usb 1-4.1.2: SerialNumber: 6B6C2CA5229020D8
[1745182.170982] cdc_acm 1-4.1.2:1.0: ttyACM0: USB ACM device
```
*Note*: The interface `ttyACM0` could be different depending on which other
USB CDC ACM devices are already in use.
For boards that use the USB CDC ACM interface as STDIO (module `stdio_cdc_acm`),
this interface is used for the test application. Otherwise the test application
creates a simple USB CDC ACM interface without any functionality except the
USB board reset function.
When the USB CDC ACM interface is initialized, the `stty` command can be used
to reset the board, for example:
```
stty -F /dev/ttyACM0 raw ispeed 600 ospeed 600 cs8 -cstopb ignpar eol 255 eof 255
```
should reset the board and restart the application, while
```
stty -F /dev/ttyACM0 raw ispeed 1200 ospeed 1200 cs8 -cstopb ignpar eol 255 eof 255
```
should reset the board and start the bootloader. The latter requires that the
bootloader supports this.

View File

@ -0,0 +1,9 @@
CONFIG_MODULE_APP_METADATA=y
CONFIG_MODULE_PS=y
CONFIG_MODULE_SHELL=y
CONFIG_MODULE_SHELL_CMDS_DEFAULT=y
CONFIG_MODULE_USB_BOARD_RESET=y
CONFIG_MODULE_USBUS=y
CONFIG_MODULE_USBUS_CDC_ACM=y
CONFIG_MODULE_AUTO_INIT_USBUS=n

View File

@ -0,0 +1,65 @@
/*
* Copyright (C) 2022 Gunar Schorcht
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @file
* @brief Simple shell application to test the USB board reset function
*
* @author Gunar Schorcht <gunar@schorcht.net>
*
*/
#include <stdio.h>
#include <string.h>
#include "shell.h"
#include "usb/usbus.h"
#include "usb/usbus/cdc/acm.h"
#ifdef MODULE_USBUS_DFU
#include "usb/usbus/dfu.h"
static usbus_dfu_device_t _dfu;
#endif
static char line_buf[SHELL_DEFAULT_BUFSIZE];
static usbus_t _usbus;
static char _stack[USBUS_STACKSIZE];
static void _init(void)
{
usbus_init(&_usbus, usbdev_get_ctx(0));
#ifdef MODULE_STDIO_CDC_ACM
/* if stdio_cdc_acm is used, initialize it */
void usb_cdc_acm_stdio_init(usbus_t *_usbus);
usb_cdc_acm_stdio_init(&_usbus);
#else
/* otherwise create a device CDC ACM and initialize it */
static usbus_cdcacm_device_t _cdcacm;
/* buffer is required in usbus_cdc_acm_init, use only a single character */
static uint8_t _cdcacm_buf;
usbus_cdc_acm_init(&_usbus, &_cdcacm, NULL, NULL, &_cdcacm_buf, 1);
#endif
#ifdef MODULE_USBUS_DFU
usbus_dfu_init(&_usbus, &_dfu, USB_DFU_PROTOCOL_RUNTIME_MODE);
#endif
usbus_create(_stack, USBUS_STACKSIZE, USBUS_PRIO, USBUS_TNAME, &_usbus);
}
int main(void)
{
_init();
shell_run(NULL, line_buf, ARRAY_SIZE(line_buf));
return 0;
}