1 /*
2 * SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <stdio.h>
8 #include <string.h>
9 #include <assert.h>
10 #include <sys/param.h>
11 #include "sdkconfig.h"
12 #include "freertos/FreeRTOS.h"
13 #include "freertos/task.h"
14 #include "freertos/semphr.h"
15 #include "esp_system.h"
16 #include "esp_log.h"
17 #include "esp_timer.h"
18 #include "esp_check.h"
19 #include "esp_intr_alloc.h"
20 #include "esp_private/usb_console.h"
21 #include "esp_private/system_internal.h"
22 #include "esp_private/startup_internal.h"
23 #include "soc/periph_defs.h"
24 #include "soc/rtc_cntl_reg.h"
25 #include "soc/usb_struct.h"
26 #include "soc/usb_reg.h"
27 #include "hal/soc_hal.h"
28 #include "esp_rom_uart.h"
29 #include "esp_rom_sys.h"
30 #include "esp_rom_caps.h"
31 #ifdef CONFIG_IDF_TARGET_ESP32S2
32 #include "esp32s2/rom/usb/usb_dc.h"
33 #include "esp32s2/rom/usb/cdc_acm.h"
34 #include "esp32s2/rom/usb/usb_dfu.h"
35 #include "esp32s2/rom/usb/usb_device.h"
36 #include "esp32s2/rom/usb/usb_os_glue.h"
37 #include "esp32s2/rom/usb/usb_persist.h"
38 #include "esp32s2/rom/usb/chip_usb_dw_wrapper.h"
39 #elif CONFIG_IDF_TARGET_ESP32S3
40 #include "esp32s3/rom/usb/usb_dc.h"
41 #include "esp32s3/rom/usb/cdc_acm.h"
42 #include "esp32s3/rom/usb/usb_dfu.h"
43 #include "esp32s3/rom/usb/usb_device.h"
44 #include "esp32s3/rom/usb/usb_os_glue.h"
45 #include "esp32s3/rom/usb/usb_persist.h"
46 #include "esp32s3/rom/usb/chip_usb_dw_wrapper.h"
47 #endif
48
49 #define CDC_WORK_BUF_SIZE (ESP_ROM_CDC_ACM_WORK_BUF_MIN + CONFIG_ESP_CONSOLE_USB_CDC_RX_BUF_SIZE)
50
51 typedef enum {
52 REBOOT_NONE,
53 REBOOT_NORMAL,
54 REBOOT_BOOTLOADER,
55 REBOOT_BOOTLOADER_DFU,
56 } reboot_type_t;
57
58
59 static reboot_type_t s_queue_reboot = REBOOT_NONE;
60 static int s_prev_rts_state;
61 static intr_handle_t s_usb_int_handle;
62 static cdc_acm_device *s_cdc_acm_device;
63 static char s_usb_tx_buf[ACM_BYTES_PER_TX];
64 static size_t s_usb_tx_buf_pos;
65 static uint8_t cdcmem[CDC_WORK_BUF_SIZE];
66 static esp_usb_console_cb_t s_rx_cb;
67 static esp_usb_console_cb_t s_tx_cb;
68 static void *s_cb_arg;
69 static esp_timer_handle_t s_restart_timer;
70
71 static const char* TAG = "usb_console";
72
73 /* This lock is used for two purposes:
74 * - To protect functions which write something to USB, e.g. esp_usb_console_write_buf.
75 * This is necessary since these functions may be called by esp_rom_printf, so the calls
76 * may preempt each other or happen concurrently.
77 * (The calls coming from regular 'printf', i.e. via VFS layer, are already protected
78 * by a mutex in the VFS driver.)
79 * - To implement "osglue" functions of the USB stack. These normally require interrupts
80 * to be disabled. However on multi-core chips a critical section is necessary.
81 */
82 static portMUX_TYPE s_lock = portMUX_INITIALIZER_UNLOCKED;
83 #ifdef CONFIG_ESP_CONSOLE_USB_CDC_SUPPORT_ETS_PRINTF
84 void esp_usb_console_write_char(char c);
85 #define ISR_FLAG ESP_INTR_FLAG_IRAM
86 #else
87 #define ISR_FLAG 0
88 #endif // CONFIG_ESP_CONSOLE_USB_CDC_SUPPORT_ETS_PRINTF
89
90
91 /* Optional write lock routines; used only if esp_rom_printf output via CDC is enabled */
92 static inline void write_lock_acquire(void);
93 static inline void write_lock_release(void);
94
95
96 /* Other forward declarations */
97 void esp_usb_console_before_restart(void);
98
99 /* Called by ROM to disable the interrupts
100 * Non-static to allow placement into IRAM by ldgen.
101 */
esp_usb_console_osglue_dis_int(void)102 void esp_usb_console_osglue_dis_int(void)
103 {
104 portENTER_CRITICAL_SAFE(&s_lock);
105 }
106
107 /* Called by ROM to enable the interrupts
108 * Non-static to allow placement into IRAM by ldgen.
109 */
esp_usb_console_osglue_ena_int(void)110 void esp_usb_console_osglue_ena_int(void)
111 {
112 portEXIT_CRITICAL_SAFE(&s_lock);
113 }
114
115 /* Delay function called by ROM USB driver.
116 * Non-static to allow placement into IRAM by ldgen.
117 */
esp_usb_console_osglue_wait_proc(int delay_us)118 int esp_usb_console_osglue_wait_proc(int delay_us)
119 {
120 if (xTaskGetSchedulerState() != taskSCHEDULER_RUNNING ||
121 !xPortCanYield()) {
122 esp_rom_delay_us(delay_us);
123 return delay_us;
124 }
125 if (delay_us == 0) {
126 /* We should effectively yield */
127 vPortYield();
128 return 1;
129 } else {
130 /* Just delay */
131 int ticks = MAX(delay_us / (portTICK_PERIOD_MS * 1000), 1);
132 vTaskDelay(ticks);
133 return ticks * portTICK_PERIOD_MS * 1000;
134 }
135 }
136
137 /* Called by ROM CDC ACM driver from interrupt context./
138 * Non-static to allow placement into IRAM by ldgen.
139 */
esp_usb_console_cdc_acm_cb(cdc_acm_device * dev,int status)140 void esp_usb_console_cdc_acm_cb(cdc_acm_device *dev, int status)
141 {
142 if (status == USB_DC_RESET || status == USB_DC_CONNECTED) {
143 s_prev_rts_state = 0;
144 } else if (status == ACM_STATUS_LINESTATE_CHANGED) {
145 uint32_t rts, dtr;
146 cdc_acm_line_ctrl_get(dev, LINE_CTRL_RTS, &rts);
147 cdc_acm_line_ctrl_get(dev, LINE_CTRL_DTR, &dtr);
148 if (!rts && s_prev_rts_state) {
149 if (dtr) {
150 s_queue_reboot = REBOOT_BOOTLOADER;
151 } else {
152 s_queue_reboot = REBOOT_NORMAL;
153 }
154 }
155 s_prev_rts_state = rts;
156 } else if (status == ACM_STATUS_RX && s_rx_cb) {
157 (*s_rx_cb)(s_cb_arg);
158 } else if (status == ACM_STATUS_TX && s_tx_cb) {
159 (*s_tx_cb)(s_cb_arg);
160 }
161 }
162
163 /* Non-static to allow placement into IRAM by ldgen. */
esp_usb_console_dfu_detach_cb(int timeout)164 void esp_usb_console_dfu_detach_cb(int timeout)
165 {
166 s_queue_reboot = REBOOT_BOOTLOADER_DFU;
167 }
168
169 /* USB interrupt handler, forward the call to the ROM driver.
170 * Non-static to allow placement into IRAM by ldgen.
171 */
esp_usb_console_interrupt(void * arg)172 void esp_usb_console_interrupt(void *arg)
173 {
174 usb_dc_check_poll_for_interrupts();
175 /* Restart can be requested from esp_usb_console_cdc_acm_cb or esp_usb_console_dfu_detach_cb */
176 if (s_queue_reboot != REBOOT_NONE) {
177 /* We can't call esp_restart here directly, since this function is called from an ISR.
178 * Instead, start an esp_timer and call esp_restart from the callback.
179 */
180 esp_err_t err = ESP_FAIL;
181 if (s_restart_timer) {
182 /* In case the timer is already running, stop it. No error check since this will fail if
183 * the timer is not running.
184 */
185 esp_timer_stop(s_restart_timer);
186 /* Start the timer again. 50ms seems to be not too long for the user to notice, but
187 * enough for the USB console output to be flushed.
188 */
189 const int restart_timeout_us = 50 * 1000;
190 err = esp_timer_start_once(s_restart_timer, restart_timeout_us);
191 }
192 if (err != ESP_OK) {
193 /* Can't schedule a restart for some reason? Call the "no-OS" restart function directly. */
194 esp_usb_console_before_restart();
195 esp_restart_noos();
196 }
197 }
198 }
199
200 /* Called as esp_timer callback when the restart timeout expires.
201 * Non-static to allow placement into IRAM by ldgen.
202 */
esp_usb_console_on_restart_timeout(void * arg)203 void esp_usb_console_on_restart_timeout(void *arg)
204 {
205 esp_restart();
206 }
207
208 /* Call the USB interrupt handler while any interrupts are pending,
209 * but not more than a few times.
210 * Non-static to allow placement into IRAM by ldgen.
211 */
esp_usb_console_poll_interrupts(void)212 void esp_usb_console_poll_interrupts(void)
213 {
214 const int max_poll_count = 10;
215 for (int i = 0; (USB0.gintsts & USB0.gintmsk) != 0 && i < max_poll_count; i++) {
216 usb_dc_check_poll_for_interrupts();
217 }
218 }
219
220 /* This function gets registered as a restart handler.
221 * Prepares USB peripheral for restart and sets up persistence.
222 * Non-static to allow placement into IRAM by ldgen.
223 */
esp_usb_console_before_restart(void)224 void esp_usb_console_before_restart(void)
225 {
226 esp_usb_console_poll_interrupts();
227 usb_dc_prepare_persist();
228 if (s_queue_reboot == REBOOT_BOOTLOADER) {
229 chip_usb_set_persist_flags(USBDC_PERSIST_ENA);
230 REG_WRITE(RTC_CNTL_OPTION1_REG, RTC_CNTL_FORCE_DOWNLOAD_BOOT);
231 } else if (s_queue_reboot == REBOOT_BOOTLOADER_DFU) {
232 chip_usb_set_persist_flags(USBDC_BOOT_DFU);
233 REG_WRITE(RTC_CNTL_OPTION1_REG, RTC_CNTL_FORCE_DOWNLOAD_BOOT);
234 } else {
235 chip_usb_set_persist_flags(USBDC_PERSIST_ENA);
236 esp_usb_console_poll_interrupts();
237 }
238 }
239
240 /* Reset some static state in ROM, which survives when going from the
241 * 2nd stage bootloader into the app. This cleans some variables which
242 * indicates that the driver is already initialized, allowing us to
243 * initialize it again, in the app.
244 */
esp_usb_console_rom_cleanup(void)245 static void esp_usb_console_rom_cleanup(void)
246 {
247 usb_dev_deinit();
248 usb_dw_ctrl_deinit();
249 uart_acm_dev = NULL;
250 }
251
esp_usb_console_init(void)252 esp_err_t esp_usb_console_init(void)
253 {
254 esp_err_t err;
255 err = esp_register_shutdown_handler(esp_usb_console_before_restart);
256 if (err != ESP_OK) {
257 return err;
258 }
259
260 esp_usb_console_rom_cleanup();
261
262 /* Install OS hooks */
263 rom_usb_osglue.int_dis_proc = esp_usb_console_osglue_dis_int;
264 rom_usb_osglue.int_ena_proc = esp_usb_console_osglue_ena_int;
265 rom_usb_osglue.wait_proc = esp_usb_console_osglue_wait_proc;
266
267 /* Install interrupt.
268 * In case of ESP_CONSOLE_USB_CDC_SUPPORT_ETS_PRINTF:
269 * Note that this the interrupt handler has to be placed into IRAM because
270 * the interrupt handler can also be called in polling mode, when
271 * interrupts are disabled, and a write to USB is performed with cache disabled.
272 * Since the handler function is in IRAM, we can register the interrupt as IRAM capable.
273 * It is not because we actually need the interrupt to work with cache disabled!
274 */
275 err = esp_intr_alloc(ETS_USB_INTR_SOURCE, ISR_FLAG | ESP_INTR_FLAG_INTRDISABLED,
276 esp_usb_console_interrupt, NULL, &s_usb_int_handle);
277 if (err != ESP_OK) {
278 esp_unregister_shutdown_handler(esp_usb_console_before_restart);
279 return err;
280 }
281
282 /* Initialize USB / CDC */
283 s_cdc_acm_device = cdc_acm_init(cdcmem, CDC_WORK_BUF_SIZE);
284 usb_dc_check_poll_for_interrupts();
285
286 /* Set callback for handling DTR/RTS lines and TX/RX events */
287 cdc_acm_irq_callback_set(s_cdc_acm_device, esp_usb_console_cdc_acm_cb);
288 cdc_acm_irq_state_enable(s_cdc_acm_device);
289
290 /* Set callback for handling DFU detach */
291 usb_dfu_set_detach_cb(esp_usb_console_dfu_detach_cb);
292
293 /* Enable interrupts on USB peripheral side */
294 USB0.gahbcfg |= USB_GLBLLNTRMSK_M;
295
296 /* Enable the interrupt handler */
297 esp_intr_enable(s_usb_int_handle);
298
299 #ifdef CONFIG_ESP_CONSOLE_USB_CDC_SUPPORT_ETS_PRINTF
300 /* Install esp_rom_printf handler */
301 esp_rom_uart_set_as_console(ESP_ROM_USB_OTG_NUM);
302 esp_rom_install_channel_putc(1, &esp_usb_console_write_char);
303 #endif // CONFIG_ESP_CONSOLE_USB_CDC_SUPPORT_ETS_PRINTF
304
305 return ESP_OK;
306 }
307
308 /* This function runs as part of the startup code to initialize the restart timer.
309 * This is not done as part of esp_usb_console_init since that function is called
310 * too early, before esp_timer is fully initialized.
311 * This gets called a bit later in the process when we can already register a timer.
312 */
313 ESP_SYSTEM_INIT_FN(esp_usb_console_init_restart_timer, BIT(0), 220)
314 {
315 esp_timer_create_args_t timer_create_args = {
316 .callback = &esp_usb_console_on_restart_timeout,
317 .name = "usb_console_restart"
318 };
319 ESP_RETURN_ON_ERROR(esp_timer_create(&timer_create_args, &s_restart_timer), TAG, "failed to create the restart timer");
320 return ESP_OK;
321 }
322
323 /* Non-static to allow placement into IRAM by ldgen.
324 * Must be called with the write lock held.
325 */
esp_usb_console_flush_internal(size_t last_write_size)326 ssize_t esp_usb_console_flush_internal(size_t last_write_size)
327 {
328 if (s_usb_tx_buf_pos == 0) {
329 return 0;
330 }
331 assert(s_usb_tx_buf_pos >= last_write_size);
332 ssize_t ret;
333 size_t tx_buf_pos_before = s_usb_tx_buf_pos - last_write_size;
334 size_t sent = cdc_acm_fifo_fill(s_cdc_acm_device, (const uint8_t*) s_usb_tx_buf, s_usb_tx_buf_pos);
335 if (sent == last_write_size) {
336 /* everything was sent */
337 ret = last_write_size;
338 s_usb_tx_buf_pos = 0;
339 } else if (sent == 0) {
340 /* nothing was sent, roll back to the original state */
341 ret = 0;
342 s_usb_tx_buf_pos = tx_buf_pos_before;
343 } else {
344 /* Some data was sent, but not all of the buffer.
345 * We can still tell the caller that all the new data
346 * was "sent" since it is in the buffer now.
347 */
348 ret = last_write_size;
349 memmove(s_usb_tx_buf, s_usb_tx_buf + sent, s_usb_tx_buf_pos - sent);
350 s_usb_tx_buf_pos = s_usb_tx_buf_pos - sent;
351 }
352 return ret;
353 }
354
esp_usb_console_flush(void)355 ssize_t esp_usb_console_flush(void)
356 {
357 if (s_cdc_acm_device == NULL) {
358 return -1;
359 }
360 write_lock_acquire();
361 int ret = esp_usb_console_flush_internal(0);
362 write_lock_release();
363 return ret;
364 }
365
esp_usb_console_write_buf(const char * buf,size_t size)366 ssize_t esp_usb_console_write_buf(const char* buf, size_t size)
367 {
368 if (s_cdc_acm_device == NULL) {
369 return -1;
370 }
371 if (size == 0) {
372 return 0;
373 }
374 write_lock_acquire();
375 ssize_t tx_buf_available = ACM_BYTES_PER_TX - s_usb_tx_buf_pos;
376 ssize_t will_write = MIN(size, tx_buf_available);
377 memcpy(s_usb_tx_buf + s_usb_tx_buf_pos, buf, will_write);
378 s_usb_tx_buf_pos += will_write;
379
380 ssize_t ret;
381 if (s_usb_tx_buf_pos == ACM_BYTES_PER_TX || buf[size - 1] == '\n') {
382 /* Buffer is full, or a newline is found.
383 * For binary streams, we probably shouldn't do line buffering,
384 * but text streams are likely going to be the most common case.
385 */
386 ret = esp_usb_console_flush_internal(will_write);
387 } else {
388 /* nothing sent out yet, but all the new data is in the buffer now */
389 ret = will_write;
390 }
391 write_lock_release();
392 return ret;
393 }
394
esp_usb_console_read_buf(char * buf,size_t buf_size)395 ssize_t esp_usb_console_read_buf(char *buf, size_t buf_size)
396 {
397 if (s_cdc_acm_device == NULL) {
398 return -1;
399 }
400 if (esp_usb_console_available_for_read() == 0) {
401 return 0;
402 }
403 int bytes_read = cdc_acm_fifo_read(s_cdc_acm_device, (uint8_t*) buf, buf_size);
404 return bytes_read;
405 }
406
esp_usb_console_set_cb(esp_usb_console_cb_t rx_cb,esp_usb_console_cb_t tx_cb,void * arg)407 esp_err_t esp_usb_console_set_cb(esp_usb_console_cb_t rx_cb, esp_usb_console_cb_t tx_cb, void *arg)
408 {
409 if (s_cdc_acm_device == NULL) {
410 return ESP_ERR_INVALID_STATE;
411 }
412 s_rx_cb = rx_cb;
413 if (s_rx_cb) {
414 cdc_acm_irq_rx_enable(s_cdc_acm_device);
415 } else {
416 cdc_acm_irq_rx_disable(s_cdc_acm_device);
417 }
418 s_tx_cb = tx_cb;
419 if (s_tx_cb) {
420 cdc_acm_irq_tx_enable(s_cdc_acm_device);
421 } else {
422 cdc_acm_irq_tx_disable(s_cdc_acm_device);
423 }
424 s_cb_arg = arg;
425 return ESP_OK;
426 }
427
esp_usb_console_available_for_read(void)428 ssize_t esp_usb_console_available_for_read(void)
429 {
430 if (s_cdc_acm_device == NULL) {
431 return -1;
432 }
433 return cdc_acm_rx_fifo_cnt(s_cdc_acm_device);
434 }
435
esp_usb_console_write_available(void)436 bool esp_usb_console_write_available(void)
437 {
438 if (s_cdc_acm_device == NULL) {
439 return false;
440 }
441 return cdc_acm_irq_tx_ready(s_cdc_acm_device) != 0;
442 }
443
444
445 #ifdef CONFIG_ESP_CONSOLE_USB_CDC_SUPPORT_ETS_PRINTF
446 /* Used as an output function by esp_rom_printf.
447 * The LF->CRLF replacement logic replicates the one in esp_rom_uart_putc.
448 * Not static to allow placement into IRAM by ldgen.
449 */
esp_usb_console_write_char(char c)450 void esp_usb_console_write_char(char c)
451 {
452 char cr = '\r';
453 char lf = '\n';
454
455 if (c == lf) {
456 esp_usb_console_write_buf(&cr, 1);
457 esp_usb_console_write_buf(&lf, 1);
458 } else if (c == '\r') {
459 } else {
460 esp_usb_console_write_buf(&c, 1);
461 }
462 }
write_lock_acquire(void)463 static inline void write_lock_acquire(void)
464 {
465 portENTER_CRITICAL_SAFE(&s_lock);
466 }
write_lock_release(void)467 static inline void write_lock_release(void)
468 {
469 portEXIT_CRITICAL_SAFE(&s_lock);
470 }
471
472 #else // CONFIG_ESP_CONSOLE_USB_CDC_SUPPORT_ETS_PRINTF
473
write_lock_acquire(void)474 static inline void write_lock_acquire(void)
475 {
476 }
477
write_lock_release(void)478 static inline void write_lock_release(void)
479 {
480 }
481 #endif // CONFIG_ESP_CONSOLE_USB_CDC_SUPPORT_ETS_PRINTF
482