| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481 |
- /*
- * SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD
- *
- * SPDX-License-Identifier: Apache-2.0
- */
- #include <stdio.h>
- #include <string.h>
- #include <assert.h>
- #include <sys/param.h>
- #include "sdkconfig.h"
- #include "freertos/FreeRTOS.h"
- #include "freertos/task.h"
- #include "freertos/semphr.h"
- #include "esp_system.h"
- #include "esp_log.h"
- #include "esp_timer.h"
- #include "esp_check.h"
- #include "esp_intr_alloc.h"
- #include "esp_private/usb_console.h"
- #include "esp_private/system_internal.h"
- #include "esp_private/startup_internal.h"
- #include "soc/periph_defs.h"
- #include "soc/rtc_cntl_reg.h"
- #include "soc/usb_struct.h"
- #include "soc/usb_reg.h"
- #include "hal/soc_hal.h"
- #include "esp_rom_uart.h"
- #include "esp_rom_sys.h"
- #include "esp_rom_caps.h"
- #ifdef CONFIG_IDF_TARGET_ESP32S2
- #include "esp32s2/rom/usb/usb_dc.h"
- #include "esp32s2/rom/usb/cdc_acm.h"
- #include "esp32s2/rom/usb/usb_dfu.h"
- #include "esp32s2/rom/usb/usb_device.h"
- #include "esp32s2/rom/usb/usb_os_glue.h"
- #include "esp32s2/rom/usb/usb_persist.h"
- #include "esp32s2/rom/usb/chip_usb_dw_wrapper.h"
- #elif CONFIG_IDF_TARGET_ESP32S3
- #include "esp32s3/rom/usb/usb_dc.h"
- #include "esp32s3/rom/usb/cdc_acm.h"
- #include "esp32s3/rom/usb/usb_dfu.h"
- #include "esp32s3/rom/usb/usb_device.h"
- #include "esp32s3/rom/usb/usb_os_glue.h"
- #include "esp32s3/rom/usb/usb_persist.h"
- #include "esp32s3/rom/usb/chip_usb_dw_wrapper.h"
- #endif
- #define CDC_WORK_BUF_SIZE (ESP_ROM_CDC_ACM_WORK_BUF_MIN + CONFIG_ESP_CONSOLE_USB_CDC_RX_BUF_SIZE)
- typedef enum {
- REBOOT_NONE,
- REBOOT_NORMAL,
- REBOOT_BOOTLOADER,
- REBOOT_BOOTLOADER_DFU,
- } reboot_type_t;
- static reboot_type_t s_queue_reboot = REBOOT_NONE;
- static int s_prev_rts_state;
- static intr_handle_t s_usb_int_handle;
- static cdc_acm_device *s_cdc_acm_device;
- static char s_usb_tx_buf[ACM_BYTES_PER_TX];
- static size_t s_usb_tx_buf_pos;
- static uint8_t cdcmem[CDC_WORK_BUF_SIZE];
- static esp_usb_console_cb_t s_rx_cb;
- static esp_usb_console_cb_t s_tx_cb;
- static void *s_cb_arg;
- static esp_timer_handle_t s_restart_timer;
- static const char* TAG = "usb_console";
- /* This lock is used for two purposes:
- * - To protect functions which write something to USB, e.g. esp_usb_console_write_buf.
- * This is necessary since these functions may be called by esp_rom_printf, so the calls
- * may preempt each other or happen concurrently.
- * (The calls coming from regular 'printf', i.e. via VFS layer, are already protected
- * by a mutex in the VFS driver.)
- * - To implement "osglue" functions of the USB stack. These normally require interrupts
- * to be disabled. However on multi-core chips a critical section is necessary.
- */
- static portMUX_TYPE s_lock = portMUX_INITIALIZER_UNLOCKED;
- #ifdef CONFIG_ESP_CONSOLE_USB_CDC_SUPPORT_ETS_PRINTF
- void esp_usb_console_write_char(char c);
- #define ISR_FLAG ESP_INTR_FLAG_IRAM
- #else
- #define ISR_FLAG 0
- #endif // CONFIG_ESP_CONSOLE_USB_CDC_SUPPORT_ETS_PRINTF
- /* Optional write lock routines; used only if esp_rom_printf output via CDC is enabled */
- static inline void write_lock_acquire(void);
- static inline void write_lock_release(void);
- /* Other forward declarations */
- void esp_usb_console_before_restart(void);
- /* Called by ROM to disable the interrupts
- * Non-static to allow placement into IRAM by ldgen.
- */
- void esp_usb_console_osglue_dis_int(void)
- {
- portENTER_CRITICAL_SAFE(&s_lock);
- }
- /* Called by ROM to enable the interrupts
- * Non-static to allow placement into IRAM by ldgen.
- */
- void esp_usb_console_osglue_ena_int(void)
- {
- portEXIT_CRITICAL_SAFE(&s_lock);
- }
- /* Delay function called by ROM USB driver.
- * Non-static to allow placement into IRAM by ldgen.
- */
- int esp_usb_console_osglue_wait_proc(int delay_us)
- {
- if (xTaskGetSchedulerState() != taskSCHEDULER_RUNNING ||
- !xPortCanYield()) {
- esp_rom_delay_us(delay_us);
- return delay_us;
- }
- if (delay_us == 0) {
- /* We should effectively yield */
- vPortYield();
- return 1;
- } else {
- /* Just delay */
- int ticks = MAX(delay_us / (portTICK_PERIOD_MS * 1000), 1);
- vTaskDelay(ticks);
- return ticks * portTICK_PERIOD_MS * 1000;
- }
- }
- /* Called by ROM CDC ACM driver from interrupt context./
- * Non-static to allow placement into IRAM by ldgen.
- */
- void esp_usb_console_cdc_acm_cb(cdc_acm_device *dev, int status)
- {
- if (status == USB_DC_RESET || status == USB_DC_CONNECTED) {
- s_prev_rts_state = 0;
- } else if (status == ACM_STATUS_LINESTATE_CHANGED) {
- uint32_t rts, dtr;
- cdc_acm_line_ctrl_get(dev, LINE_CTRL_RTS, &rts);
- cdc_acm_line_ctrl_get(dev, LINE_CTRL_DTR, &dtr);
- if (!rts && s_prev_rts_state) {
- if (dtr) {
- s_queue_reboot = REBOOT_BOOTLOADER;
- } else {
- s_queue_reboot = REBOOT_NORMAL;
- }
- }
- s_prev_rts_state = rts;
- } else if (status == ACM_STATUS_RX && s_rx_cb) {
- (*s_rx_cb)(s_cb_arg);
- } else if (status == ACM_STATUS_TX && s_tx_cb) {
- (*s_tx_cb)(s_cb_arg);
- }
- }
- /* Non-static to allow placement into IRAM by ldgen. */
- void esp_usb_console_dfu_detach_cb(int timeout)
- {
- s_queue_reboot = REBOOT_BOOTLOADER_DFU;
- }
- /* USB interrupt handler, forward the call to the ROM driver.
- * Non-static to allow placement into IRAM by ldgen.
- */
- void esp_usb_console_interrupt(void *arg)
- {
- usb_dc_check_poll_for_interrupts();
- /* Restart can be requested from esp_usb_console_cdc_acm_cb or esp_usb_console_dfu_detach_cb */
- if (s_queue_reboot != REBOOT_NONE) {
- /* We can't call esp_restart here directly, since this function is called from an ISR.
- * Instead, start an esp_timer and call esp_restart from the callback.
- */
- esp_err_t err = ESP_FAIL;
- if (s_restart_timer) {
- /* In case the timer is already running, stop it. No error check since this will fail if
- * the timer is not running.
- */
- esp_timer_stop(s_restart_timer);
- /* Start the timer again. 50ms seems to be not too long for the user to notice, but
- * enough for the USB console output to be flushed.
- */
- const int restart_timeout_us = 50 * 1000;
- err = esp_timer_start_once(s_restart_timer, restart_timeout_us);
- }
- if (err != ESP_OK) {
- /* Can't schedule a restart for some reason? Call the "no-OS" restart function directly. */
- esp_usb_console_before_restart();
- esp_restart_noos();
- }
- }
- }
- /* Called as esp_timer callback when the restart timeout expires.
- * Non-static to allow placement into IRAM by ldgen.
- */
- void esp_usb_console_on_restart_timeout(void *arg)
- {
- esp_restart();
- }
- /* Call the USB interrupt handler while any interrupts are pending,
- * but not more than a few times.
- * Non-static to allow placement into IRAM by ldgen.
- */
- void esp_usb_console_poll_interrupts(void)
- {
- const int max_poll_count = 10;
- for (int i = 0; (USB0.gintsts & USB0.gintmsk) != 0 && i < max_poll_count; i++) {
- usb_dc_check_poll_for_interrupts();
- }
- }
- /* This function gets registered as a restart handler.
- * Prepares USB peripheral for restart and sets up persistence.
- * Non-static to allow placement into IRAM by ldgen.
- */
- void esp_usb_console_before_restart(void)
- {
- esp_usb_console_poll_interrupts();
- usb_dc_prepare_persist();
- if (s_queue_reboot == REBOOT_BOOTLOADER) {
- chip_usb_set_persist_flags(USBDC_PERSIST_ENA);
- REG_WRITE(RTC_CNTL_OPTION1_REG, RTC_CNTL_FORCE_DOWNLOAD_BOOT);
- } else if (s_queue_reboot == REBOOT_BOOTLOADER_DFU) {
- chip_usb_set_persist_flags(USBDC_BOOT_DFU);
- REG_WRITE(RTC_CNTL_OPTION1_REG, RTC_CNTL_FORCE_DOWNLOAD_BOOT);
- } else {
- chip_usb_set_persist_flags(USBDC_PERSIST_ENA);
- esp_usb_console_poll_interrupts();
- }
- }
- /* Reset some static state in ROM, which survives when going from the
- * 2nd stage bootloader into the app. This cleans some variables which
- * indicates that the driver is already initialized, allowing us to
- * initialize it again, in the app.
- */
- static void esp_usb_console_rom_cleanup(void)
- {
- usb_dev_deinit();
- usb_dw_ctrl_deinit();
- uart_acm_dev = NULL;
- }
- esp_err_t esp_usb_console_init(void)
- {
- esp_err_t err;
- err = esp_register_shutdown_handler(esp_usb_console_before_restart);
- if (err != ESP_OK) {
- return err;
- }
- esp_usb_console_rom_cleanup();
- /* Install OS hooks */
- rom_usb_osglue.int_dis_proc = esp_usb_console_osglue_dis_int;
- rom_usb_osglue.int_ena_proc = esp_usb_console_osglue_ena_int;
- rom_usb_osglue.wait_proc = esp_usb_console_osglue_wait_proc;
- /* Install interrupt.
- * In case of ESP_CONSOLE_USB_CDC_SUPPORT_ETS_PRINTF:
- * Note that this the interrupt handler has to be placed into IRAM because
- * the interrupt handler can also be called in polling mode, when
- * interrupts are disabled, and a write to USB is performed with cache disabled.
- * Since the handler function is in IRAM, we can register the interrupt as IRAM capable.
- * It is not because we actually need the interrupt to work with cache disabled!
- */
- err = esp_intr_alloc(ETS_USB_INTR_SOURCE, ISR_FLAG | ESP_INTR_FLAG_INTRDISABLED,
- esp_usb_console_interrupt, NULL, &s_usb_int_handle);
- if (err != ESP_OK) {
- esp_unregister_shutdown_handler(esp_usb_console_before_restart);
- return err;
- }
- /* Initialize USB / CDC */
- s_cdc_acm_device = cdc_acm_init(cdcmem, CDC_WORK_BUF_SIZE);
- usb_dc_check_poll_for_interrupts();
- /* Set callback for handling DTR/RTS lines and TX/RX events */
- cdc_acm_irq_callback_set(s_cdc_acm_device, esp_usb_console_cdc_acm_cb);
- cdc_acm_irq_state_enable(s_cdc_acm_device);
- /* Set callback for handling DFU detach */
- usb_dfu_set_detach_cb(esp_usb_console_dfu_detach_cb);
- /* Enable interrupts on USB peripheral side */
- USB0.gahbcfg |= USB_GLBLLNTRMSK_M;
- /* Enable the interrupt handler */
- esp_intr_enable(s_usb_int_handle);
- #ifdef CONFIG_ESP_CONSOLE_USB_CDC_SUPPORT_ETS_PRINTF
- /* Install esp_rom_printf handler */
- esp_rom_uart_set_as_console(ESP_ROM_USB_OTG_NUM);
- esp_rom_install_channel_putc(1, &esp_usb_console_write_char);
- #endif // CONFIG_ESP_CONSOLE_USB_CDC_SUPPORT_ETS_PRINTF
- return ESP_OK;
- }
- /* This function runs as part of the startup code to initialize the restart timer.
- * This is not done as part of esp_usb_console_init since that function is called
- * too early, before esp_timer is fully initialized.
- * This gets called a bit later in the process when we can already register a timer.
- */
- ESP_SYSTEM_INIT_FN(esp_usb_console_init_restart_timer, BIT(0), 220)
- {
- esp_timer_create_args_t timer_create_args = {
- .callback = &esp_usb_console_on_restart_timeout,
- .name = "usb_console_restart"
- };
- ESP_RETURN_ON_ERROR(esp_timer_create(&timer_create_args, &s_restart_timer), TAG, "failed to create the restart timer");
- return ESP_OK;
- }
- /* Non-static to allow placement into IRAM by ldgen.
- * Must be called with the write lock held.
- */
- ssize_t esp_usb_console_flush_internal(size_t last_write_size)
- {
- if (s_usb_tx_buf_pos == 0) {
- return 0;
- }
- assert(s_usb_tx_buf_pos >= last_write_size);
- ssize_t ret;
- size_t tx_buf_pos_before = s_usb_tx_buf_pos - last_write_size;
- size_t sent = cdc_acm_fifo_fill(s_cdc_acm_device, (const uint8_t*) s_usb_tx_buf, s_usb_tx_buf_pos);
- if (sent == last_write_size) {
- /* everything was sent */
- ret = last_write_size;
- s_usb_tx_buf_pos = 0;
- } else if (sent == 0) {
- /* nothing was sent, roll back to the original state */
- ret = 0;
- s_usb_tx_buf_pos = tx_buf_pos_before;
- } else {
- /* Some data was sent, but not all of the buffer.
- * We can still tell the caller that all the new data
- * was "sent" since it is in the buffer now.
- */
- ret = last_write_size;
- memmove(s_usb_tx_buf, s_usb_tx_buf + sent, s_usb_tx_buf_pos - sent);
- s_usb_tx_buf_pos = s_usb_tx_buf_pos - sent;
- }
- return ret;
- }
- ssize_t esp_usb_console_flush(void)
- {
- if (s_cdc_acm_device == NULL) {
- return -1;
- }
- write_lock_acquire();
- int ret = esp_usb_console_flush_internal(0);
- write_lock_release();
- return ret;
- }
- ssize_t esp_usb_console_write_buf(const char* buf, size_t size)
- {
- if (s_cdc_acm_device == NULL) {
- return -1;
- }
- if (size == 0) {
- return 0;
- }
- write_lock_acquire();
- ssize_t tx_buf_available = ACM_BYTES_PER_TX - s_usb_tx_buf_pos;
- ssize_t will_write = MIN(size, tx_buf_available);
- memcpy(s_usb_tx_buf + s_usb_tx_buf_pos, buf, will_write);
- s_usb_tx_buf_pos += will_write;
- ssize_t ret;
- if (s_usb_tx_buf_pos == ACM_BYTES_PER_TX || buf[size - 1] == '\n') {
- /* Buffer is full, or a newline is found.
- * For binary streams, we probably shouldn't do line buffering,
- * but text streams are likely going to be the most common case.
- */
- ret = esp_usb_console_flush_internal(will_write);
- } else {
- /* nothing sent out yet, but all the new data is in the buffer now */
- ret = will_write;
- }
- write_lock_release();
- return ret;
- }
- ssize_t esp_usb_console_read_buf(char *buf, size_t buf_size)
- {
- if (s_cdc_acm_device == NULL) {
- return -1;
- }
- if (esp_usb_console_available_for_read() == 0) {
- return 0;
- }
- int bytes_read = cdc_acm_fifo_read(s_cdc_acm_device, (uint8_t*) buf, buf_size);
- return bytes_read;
- }
- esp_err_t esp_usb_console_set_cb(esp_usb_console_cb_t rx_cb, esp_usb_console_cb_t tx_cb, void *arg)
- {
- if (s_cdc_acm_device == NULL) {
- return ESP_ERR_INVALID_STATE;
- }
- s_rx_cb = rx_cb;
- if (s_rx_cb) {
- cdc_acm_irq_rx_enable(s_cdc_acm_device);
- } else {
- cdc_acm_irq_rx_disable(s_cdc_acm_device);
- }
- s_tx_cb = tx_cb;
- if (s_tx_cb) {
- cdc_acm_irq_tx_enable(s_cdc_acm_device);
- } else {
- cdc_acm_irq_tx_disable(s_cdc_acm_device);
- }
- s_cb_arg = arg;
- return ESP_OK;
- }
- ssize_t esp_usb_console_available_for_read(void)
- {
- if (s_cdc_acm_device == NULL) {
- return -1;
- }
- return cdc_acm_rx_fifo_cnt(s_cdc_acm_device);
- }
- bool esp_usb_console_write_available(void)
- {
- if (s_cdc_acm_device == NULL) {
- return false;
- }
- return cdc_acm_irq_tx_ready(s_cdc_acm_device) != 0;
- }
- #ifdef CONFIG_ESP_CONSOLE_USB_CDC_SUPPORT_ETS_PRINTF
- /* Used as an output function by esp_rom_printf.
- * The LF->CRLF replacement logic replicates the one in esp_rom_uart_putc.
- * Not static to allow placement into IRAM by ldgen.
- */
- void esp_usb_console_write_char(char c)
- {
- char cr = '\r';
- char lf = '\n';
- if (c == lf) {
- esp_usb_console_write_buf(&cr, 1);
- esp_usb_console_write_buf(&lf, 1);
- } else if (c == '\r') {
- } else {
- esp_usb_console_write_buf(&c, 1);
- }
- }
- static inline void write_lock_acquire(void)
- {
- portENTER_CRITICAL_SAFE(&s_lock);
- }
- static inline void write_lock_release(void)
- {
- portEXIT_CRITICAL_SAFE(&s_lock);
- }
- #else // CONFIG_ESP_CONSOLE_USB_CDC_SUPPORT_ETS_PRINTF
- static inline void write_lock_acquire(void)
- {
- }
- static inline void write_lock_release(void)
- {
- }
- #endif // CONFIG_ESP_CONSOLE_USB_CDC_SUPPORT_ETS_PRINTF
|