1 /*
2 * SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <stdint.h>
8 #include <stdio.h>
9 #include <fcntl.h>
10 #include <sys/cdefs.h>
11 #include "sdkconfig.h"
12 #include "esp_err.h"
13 #include "esp_log.h"
14 #include "esp_console.h"
15 #include "esp_vfs_dev.h"
16 #include "esp_vfs_cdcacm.h"
17 #include "esp_vfs_usb_serial_jtag.h"
18 #include "freertos/FreeRTOS.h"
19 #include "freertos/task.h"
20 #include "driver/uart.h"
21 #include "driver/usb_serial_jtag.h"
22 #include "linenoise/linenoise.h"
23
24 static const char *TAG = "console.repl";
25
26 #define CONSOLE_PROMPT_MAX_LEN (32)
27 #define CONSOLE_PATH_MAX_LEN (ESP_VFS_PATH_MAX)
28
29 typedef enum {
30 CONSOLE_REPL_STATE_DEINIT,
31 CONSOLE_REPL_STATE_INIT,
32 CONSOLE_REPL_STATE_START,
33 } repl_state_t;
34
35 typedef struct {
36 esp_console_repl_t repl_core; // base class
37 char prompt[CONSOLE_PROMPT_MAX_LEN]; // Prompt to be printed before each line
38 repl_state_t state;
39 const char *history_save_path;
40 TaskHandle_t task_hdl; // REPL task handle
41 size_t max_cmdline_length; // Maximum length of a command line. If 0, default value will be used.
42 } esp_console_repl_com_t;
43
44 typedef struct {
45 esp_console_repl_com_t repl_com; // base class
46 int uart_channel; // uart channel number
47 } esp_console_repl_universal_t;
48
49 static void esp_console_repl_task(void *args);
50 static esp_err_t esp_console_repl_uart_delete(esp_console_repl_t *repl);
51 static esp_err_t esp_console_repl_usb_cdc_delete(esp_console_repl_t *repl);
52 #if CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG
53 static esp_err_t esp_console_repl_usb_serial_jtag_delete(esp_console_repl_t *repl);
54 #endif //CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG
55 static esp_err_t esp_console_common_init(size_t max_cmdline_length, esp_console_repl_com_t *repl_com);
56 static esp_err_t esp_console_setup_prompt(const char *prompt, esp_console_repl_com_t *repl_com);
57 static esp_err_t esp_console_setup_history(const char *history_path, uint32_t max_history_len, esp_console_repl_com_t *repl_com);
58
esp_console_new_repl_usb_cdc(const esp_console_dev_usb_cdc_config_t * dev_config,const esp_console_repl_config_t * repl_config,esp_console_repl_t ** ret_repl)59 esp_err_t esp_console_new_repl_usb_cdc(const esp_console_dev_usb_cdc_config_t *dev_config, const esp_console_repl_config_t *repl_config, esp_console_repl_t **ret_repl)
60 {
61 esp_err_t ret = ESP_OK;
62 esp_console_repl_universal_t *cdc_repl = NULL;
63 if (!repl_config | !dev_config | !ret_repl) {
64 ret = ESP_ERR_INVALID_ARG;
65 goto _exit;
66 }
67 // allocate memory for console REPL context
68 cdc_repl = calloc(1, sizeof(esp_console_repl_universal_t));
69 if (!cdc_repl) {
70 ret = ESP_ERR_NO_MEM;
71 goto _exit;
72 }
73
74 /* Minicom, screen, idf_monitor send CR when ENTER key is pressed */
75 esp_vfs_dev_cdcacm_set_rx_line_endings(ESP_LINE_ENDINGS_CR);
76 /* Move the caret to the beginning of the next line on '\n' */
77 esp_vfs_dev_cdcacm_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF);
78
79 /* Enable non-blocking mode on stdin and stdout */
80 fcntl(fileno(stdout), F_SETFL, 0);
81 fcntl(fileno(stdin), F_SETFL, 0);
82
83 // initialize console, common part
84 ret = esp_console_common_init(repl_config->max_cmdline_length, &cdc_repl->repl_com);
85 if (ret != ESP_OK) {
86 goto _exit;
87 }
88
89 // setup history
90 ret = esp_console_setup_history(repl_config->history_save_path, repl_config->max_history_len, &cdc_repl->repl_com);
91 if (ret != ESP_OK) {
92 goto _exit;
93 }
94
95 // setup prompt
96 esp_console_setup_prompt(repl_config->prompt, &cdc_repl->repl_com);
97
98 /* Fill the structure here as it will be used directly by the created task. */
99 cdc_repl->uart_channel = CONFIG_ESP_CONSOLE_UART_NUM;
100 cdc_repl->repl_com.state = CONSOLE_REPL_STATE_INIT;
101 cdc_repl->repl_com.repl_core.del = esp_console_repl_usb_cdc_delete;
102
103 /* spawn a single thread to run REPL */
104 if (xTaskCreate(esp_console_repl_task, "console_repl", repl_config->task_stack_size,
105 cdc_repl, repl_config->task_priority, &cdc_repl->repl_com.task_hdl) != pdTRUE) {
106 ret = ESP_FAIL;
107 goto _exit;
108 }
109
110 *ret_repl = &cdc_repl->repl_com.repl_core;
111 return ESP_OK;
112 _exit:
113 if (cdc_repl) {
114 esp_console_deinit();
115 free(cdc_repl);
116 }
117 if (ret_repl) {
118 *ret_repl = NULL;
119 }
120 return ret;
121 }
122
123 #if CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG
esp_console_new_repl_usb_serial_jtag(const esp_console_dev_usb_serial_jtag_config_t * dev_config,const esp_console_repl_config_t * repl_config,esp_console_repl_t ** ret_repl)124 esp_err_t esp_console_new_repl_usb_serial_jtag(const esp_console_dev_usb_serial_jtag_config_t *dev_config, const esp_console_repl_config_t *repl_config, esp_console_repl_t **ret_repl)
125 {
126 esp_console_repl_universal_t *usb_serial_jtag_repl = NULL;
127 if (!repl_config | !dev_config | !ret_repl) {
128 return ESP_ERR_INVALID_ARG;
129 }
130
131 esp_err_t ret = ESP_OK;
132 // allocate memory for console REPL context
133 usb_serial_jtag_repl = calloc(1, sizeof(esp_console_repl_universal_t));
134 if (!usb_serial_jtag_repl) {
135 ret = ESP_ERR_NO_MEM;
136 goto _exit;
137 }
138
139 /* Disable buffering on stdin */
140 setvbuf(stdin, NULL, _IONBF, 0);
141
142 /* Minicom, screen, idf_monitor send CR when ENTER key is pressed */
143 esp_vfs_dev_usb_serial_jtag_set_rx_line_endings(ESP_LINE_ENDINGS_CR);
144 /* Move the caret to the beginning of the next line on '\n' */
145 esp_vfs_dev_usb_serial_jtag_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF);
146
147 /* Enable non-blocking mode on stdin and stdout */
148 fcntl(fileno(stdout), F_SETFL, 0);
149 fcntl(fileno(stdin), F_SETFL, 0);
150
151 usb_serial_jtag_driver_config_t usb_serial_jtag_config = USB_SERIAL_JTAG_DRIVER_CONFIG_DEFAULT();
152
153 /* Install USB-SERIAL-JTAG driver for interrupt-driven reads and writes */
154 ret = usb_serial_jtag_driver_install(&usb_serial_jtag_config);
155 if (ret != ESP_OK) {
156 goto _exit;
157 }
158
159 // initialize console, common part
160 ret = esp_console_common_init(repl_config->max_cmdline_length, &usb_serial_jtag_repl->repl_com);
161 if (ret != ESP_OK) {
162 goto _exit;
163 }
164
165 /* Tell vfs to use usb-serial-jtag driver */
166 esp_vfs_usb_serial_jtag_use_driver();
167
168 // setup history
169 ret = esp_console_setup_history(repl_config->history_save_path, repl_config->max_history_len, &usb_serial_jtag_repl->repl_com);
170 if (ret != ESP_OK) {
171 goto _exit;
172 }
173
174 // setup prompt
175 esp_console_setup_prompt(repl_config->prompt, &usb_serial_jtag_repl->repl_com);
176
177 /* spawn a single thread to run REPL */
178 if (xTaskCreate(esp_console_repl_task, "console_repl", repl_config->task_stack_size,
179 &usb_serial_jtag_repl->repl_com, repl_config->task_priority, &usb_serial_jtag_repl->repl_com.task_hdl) != pdTRUE) {
180 ret = ESP_FAIL;
181 goto _exit;
182 }
183
184 usb_serial_jtag_repl->uart_channel = CONFIG_ESP_CONSOLE_UART_NUM;
185 usb_serial_jtag_repl->repl_com.state = CONSOLE_REPL_STATE_INIT;
186 usb_serial_jtag_repl->repl_com.repl_core.del = esp_console_repl_usb_serial_jtag_delete;
187 *ret_repl = &usb_serial_jtag_repl->repl_com.repl_core;
188 return ESP_OK;
189 _exit:
190 if (usb_serial_jtag_repl) {
191 esp_console_deinit();
192 free(usb_serial_jtag_repl);
193 }
194 if (ret_repl) {
195 *ret_repl = NULL;
196 }
197 return ret;
198 }
199 #endif // CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG
200
esp_console_new_repl_uart(const esp_console_dev_uart_config_t * dev_config,const esp_console_repl_config_t * repl_config,esp_console_repl_t ** ret_repl)201 esp_err_t esp_console_new_repl_uart(const esp_console_dev_uart_config_t *dev_config, const esp_console_repl_config_t *repl_config, esp_console_repl_t **ret_repl)
202 {
203 esp_err_t ret = ESP_OK;
204 esp_console_repl_universal_t *uart_repl = NULL;
205 if (!repl_config | !dev_config | !ret_repl) {
206 ret = ESP_ERR_INVALID_ARG;
207 goto _exit;
208 }
209 // allocate memory for console REPL context
210 uart_repl = calloc(1, sizeof(esp_console_repl_universal_t));
211 if (!uart_repl) {
212 ret = ESP_ERR_NO_MEM;
213 goto _exit;
214 }
215
216 /* Drain stdout before reconfiguring it */
217 fflush(stdout);
218 fsync(fileno(stdout));
219
220 /* Minicom, screen, idf_monitor send CR when ENTER key is pressed */
221 esp_vfs_dev_uart_port_set_rx_line_endings(dev_config->channel, ESP_LINE_ENDINGS_CR);
222 /* Move the caret to the beginning of the next line on '\n' */
223 esp_vfs_dev_uart_port_set_tx_line_endings(dev_config->channel, ESP_LINE_ENDINGS_CRLF);
224
225 /* Configure UART. Note that REF_TICK/XTAL is used so that the baud rate remains
226 * correct while APB frequency is changing in light sleep mode.
227 */
228 const uart_config_t uart_config = {
229 .baud_rate = dev_config->baud_rate,
230 .data_bits = UART_DATA_8_BITS,
231 .parity = UART_PARITY_DISABLE,
232 .stop_bits = UART_STOP_BITS_1,
233 #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2
234 .source_clk = UART_SCLK_REF_TICK,
235 #else
236 .source_clk = UART_SCLK_XTAL,
237 #endif
238 };
239
240 uart_param_config(dev_config->channel, &uart_config);
241 uart_set_pin(dev_config->channel, dev_config->tx_gpio_num, dev_config->rx_gpio_num, -1, -1);
242
243 /* Install UART driver for interrupt-driven reads and writes */
244 ret = uart_driver_install(dev_config->channel, 256, 0, 0, NULL, 0);
245 if (ret != ESP_OK) {
246 goto _exit;
247 }
248
249 /* Tell VFS to use UART driver */
250 esp_vfs_dev_uart_use_driver(dev_config->channel);
251
252 // initialize console, common part
253 ret = esp_console_common_init(repl_config->max_cmdline_length, &uart_repl->repl_com);
254 if (ret != ESP_OK) {
255 goto _exit;
256 }
257
258 // setup history
259 ret = esp_console_setup_history(repl_config->history_save_path, repl_config->max_history_len, &uart_repl->repl_com);
260 if (ret != ESP_OK) {
261 goto _exit;
262 }
263
264 // setup prompt
265 esp_console_setup_prompt(repl_config->prompt, &uart_repl->repl_com);
266
267 /* Fill the structure here as it will be used directly by the created task. */
268 uart_repl->uart_channel = dev_config->channel;
269 uart_repl->repl_com.state = CONSOLE_REPL_STATE_INIT;
270 uart_repl->repl_com.repl_core.del = esp_console_repl_uart_delete;
271
272 /* Spawn a single thread to run REPL, we need to pass `uart_repl` to it as
273 * it also requires the uart channel. */
274 if (xTaskCreate(esp_console_repl_task, "console_repl", repl_config->task_stack_size,
275 uart_repl, repl_config->task_priority, &uart_repl->repl_com.task_hdl) != pdTRUE) {
276 ret = ESP_FAIL;
277 goto _exit;
278 }
279
280 *ret_repl = &uart_repl->repl_com.repl_core;
281 return ESP_OK;
282 _exit:
283 if (uart_repl) {
284 esp_console_deinit();
285 uart_driver_delete(dev_config->channel);
286 free(uart_repl);
287 }
288 if (ret_repl) {
289 *ret_repl = NULL;
290 }
291 return ret;
292 }
293
esp_console_start_repl(esp_console_repl_t * repl)294 esp_err_t esp_console_start_repl(esp_console_repl_t *repl)
295 {
296 esp_err_t ret = ESP_OK;
297 esp_console_repl_com_t *repl_com = __containerof(repl, esp_console_repl_com_t, repl_core);
298 // check if already initialized
299 if (repl_com->state != CONSOLE_REPL_STATE_INIT) {
300 ret = ESP_ERR_INVALID_STATE;
301 goto _exit;
302 }
303
304 repl_com->state = CONSOLE_REPL_STATE_START;
305 xTaskNotifyGive(repl_com->task_hdl);
306 return ESP_OK;
307 _exit:
308 return ret;
309 }
310
esp_console_setup_prompt(const char * prompt,esp_console_repl_com_t * repl_com)311 static esp_err_t esp_console_setup_prompt(const char *prompt, esp_console_repl_com_t *repl_com)
312 {
313 /* set command line prompt */
314 const char *prompt_temp = "esp>";
315 if (prompt) {
316 prompt_temp = prompt;
317 }
318 snprintf(repl_com->prompt, CONSOLE_PROMPT_MAX_LEN - 1, LOG_COLOR_I "%s " LOG_RESET_COLOR, prompt_temp);
319
320 /* Figure out if the terminal supports escape sequences */
321 int probe_status = linenoiseProbe();
322 if (probe_status) {
323 /* zero indicates success */
324 linenoiseSetDumbMode(1);
325 #if CONFIG_LOG_COLORS
326 /* Since the terminal doesn't support escape sequences,
327 * don't use color codes in the s_prompt.
328 */
329 snprintf(repl_com->prompt, CONSOLE_PROMPT_MAX_LEN - 1, "%s ", prompt_temp);
330 #endif //CONFIG_LOG_COLORS
331 }
332
333 return ESP_OK;
334 }
335
esp_console_setup_history(const char * history_path,uint32_t max_history_len,esp_console_repl_com_t * repl_com)336 static esp_err_t esp_console_setup_history(const char *history_path, uint32_t max_history_len, esp_console_repl_com_t *repl_com)
337 {
338 esp_err_t ret = ESP_OK;
339
340 repl_com->history_save_path = history_path;
341 if (history_path) {
342 /* Load command history from filesystem */
343 linenoiseHistoryLoad(history_path);
344 }
345
346 /* Set command history size */
347 if (linenoiseHistorySetMaxLen(max_history_len) != 1) {
348 ESP_LOGE(TAG, "set max history length to %d failed", max_history_len);
349 ret = ESP_FAIL;
350 goto _exit;
351 }
352 return ESP_OK;
353 _exit:
354 return ret;
355 }
356
esp_console_common_init(size_t max_cmdline_length,esp_console_repl_com_t * repl_com)357 static esp_err_t esp_console_common_init(size_t max_cmdline_length, esp_console_repl_com_t *repl_com)
358 {
359 esp_err_t ret = ESP_OK;
360 /* Initialize the console */
361 esp_console_config_t console_config = ESP_CONSOLE_CONFIG_DEFAULT();
362 repl_com->max_cmdline_length = console_config.max_cmdline_length;
363 /* Replace the default command line length if passed as a parameter */
364 if (max_cmdline_length != 0) {
365 console_config.max_cmdline_length = max_cmdline_length;
366 repl_com->max_cmdline_length = max_cmdline_length;
367 }
368
369 #if CONFIG_LOG_COLORS
370 console_config.hint_color = atoi(LOG_COLOR_CYAN);
371 #endif
372 ret = esp_console_init(&console_config);
373 if (ret != ESP_OK) {
374 goto _exit;
375 }
376
377 ret = esp_console_register_help_command();
378 if (ret != ESP_OK) {
379 goto _exit;
380 }
381
382 /* Configure linenoise line completion library */
383 /* Enable multiline editing. If not set, long commands will scroll within single line */
384 linenoiseSetMultiLine(1);
385
386 /* Tell linenoise where to get command completions and hints */
387 linenoiseSetCompletionCallback(&esp_console_get_completion);
388 linenoiseSetHintsCallback((linenoiseHintsCallback *)&esp_console_get_hint);
389
390 return ESP_OK;
391 _exit:
392 return ret;
393 }
394
esp_console_repl_uart_delete(esp_console_repl_t * repl)395 static esp_err_t esp_console_repl_uart_delete(esp_console_repl_t *repl)
396 {
397 esp_err_t ret = ESP_OK;
398 esp_console_repl_com_t *repl_com = __containerof(repl, esp_console_repl_com_t, repl_core);
399 esp_console_repl_universal_t *uart_repl = __containerof(repl_com, esp_console_repl_universal_t, repl_com);
400 // check if already de-initialized
401 if (repl_com->state == CONSOLE_REPL_STATE_DEINIT) {
402 ESP_LOGE(TAG, "already de-initialized");
403 ret = ESP_ERR_INVALID_STATE;
404 goto _exit;
405 }
406 repl_com->state = CONSOLE_REPL_STATE_DEINIT;
407 esp_console_deinit();
408 esp_vfs_dev_uart_use_nonblocking(uart_repl->uart_channel);
409 uart_driver_delete(uart_repl->uart_channel);
410 free(uart_repl);
411 _exit:
412 return ret;
413 }
414
esp_console_repl_usb_cdc_delete(esp_console_repl_t * repl)415 static esp_err_t esp_console_repl_usb_cdc_delete(esp_console_repl_t *repl)
416 {
417 esp_err_t ret = ESP_OK;
418 esp_console_repl_com_t *repl_com = __containerof(repl, esp_console_repl_com_t, repl_core);
419 esp_console_repl_universal_t *cdc_repl = __containerof(repl_com, esp_console_repl_universal_t, repl_com);
420 // check if already de-initialized
421 if (repl_com->state == CONSOLE_REPL_STATE_DEINIT) {
422 ESP_LOGE(TAG, "already de-initialized");
423 ret = ESP_ERR_INVALID_STATE;
424 goto _exit;
425 }
426 repl_com->state = CONSOLE_REPL_STATE_DEINIT;
427 esp_console_deinit();
428 free(cdc_repl);
429 _exit:
430 return ret;
431 }
432
433 #if CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG
esp_console_repl_usb_serial_jtag_delete(esp_console_repl_t * repl)434 static esp_err_t esp_console_repl_usb_serial_jtag_delete(esp_console_repl_t *repl)
435 {
436 esp_err_t ret = ESP_OK;
437 esp_console_repl_com_t *repl_com = __containerof(repl, esp_console_repl_com_t, repl_core);
438 esp_console_repl_universal_t *usb_serial_jtag_repl = __containerof(repl_com, esp_console_repl_universal_t, repl_com);
439 // check if already de-initialized
440 if (repl_com->state == CONSOLE_REPL_STATE_DEINIT) {
441 ESP_LOGE(TAG, "already de-initialized");
442 ret = ESP_ERR_INVALID_STATE;
443 goto _exit;
444 }
445 repl_com->state = CONSOLE_REPL_STATE_DEINIT;
446 esp_console_deinit();
447 esp_vfs_usb_serial_jtag_use_nonblocking();
448 usb_serial_jtag_driver_uninstall();
449 free(usb_serial_jtag_repl);
450 _exit:
451 return ret;
452 }
453 #endif // CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG
454
esp_console_repl_task(void * args)455 static void esp_console_repl_task(void *args)
456 {
457 esp_console_repl_universal_t *repl_conf = (esp_console_repl_universal_t *) args;
458 esp_console_repl_com_t *repl_com = &repl_conf->repl_com;
459 const int uart_channel = repl_conf->uart_channel;
460
461 /* Waiting for task notify. This happens when `esp_console_start_repl()`
462 * function is called. */
463 ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
464
465 /* Change standard input and output of the task if the requested UART is
466 * NOT the default one. This block will replace stdin, stdout and stderr.
467 */
468 if (uart_channel != CONFIG_ESP_CONSOLE_UART_NUM) {
469 char path[CONSOLE_PATH_MAX_LEN] = { 0 };
470 snprintf(path, CONSOLE_PATH_MAX_LEN, "/dev/uart/%d", uart_channel);
471
472 stdin = fopen(path, "r");
473 stdout = fopen(path, "w");
474 stderr = stdout;
475 }
476
477 /* Disable buffering on stdin of the current task.
478 * If the console is ran on a different UART than the default one,
479 * buffering shall only be disabled for the current one. */
480 setvbuf(stdin, NULL, _IONBF, 0);
481
482 /* This message shall be printed here and not earlier as the stdout
483 * has just been set above. */
484 printf("\r\n"
485 "Type 'help' to get the list of commands.\r\n"
486 "Use UP/DOWN arrows to navigate through command history.\r\n"
487 "Press TAB when typing command name to auto-complete.\r\n");
488
489 if (linenoiseIsDumbMode()) {
490 printf("\r\n"
491 "Your terminal application does not support escape sequences.\n\n"
492 "Line editing and history features are disabled.\n\n"
493 "On Windows, try using Putty instead.\r\n");
494 }
495
496 linenoiseSetMaxLineLen(repl_com->max_cmdline_length);
497 while (repl_com->state == CONSOLE_REPL_STATE_START) {
498 char *line = linenoise(repl_com->prompt);
499 if (line == NULL) {
500 ESP_LOGD(TAG, "empty line");
501 /* Ignore empty lines */
502 continue;
503 }
504 /* Add the command to the history */
505 linenoiseHistoryAdd(line);
506 /* Save command history to filesystem */
507 if (repl_com->history_save_path) {
508 linenoiseHistorySave(repl_com->history_save_path);
509 }
510
511 /* Try to run the command */
512 int ret;
513 esp_err_t err = esp_console_run(line, &ret);
514 if (err == ESP_ERR_NOT_FOUND) {
515 printf("Unrecognized command\n");
516 } else if (err == ESP_ERR_INVALID_ARG) {
517 // command was empty
518 } else if (err == ESP_OK && ret != ESP_OK) {
519 printf("Command returned non-zero error code: 0x%x (%s)\n", ret, esp_err_to_name(ret));
520 } else if (err != ESP_OK) {
521 printf("Internal error: %s\n", esp_err_to_name(err));
522 }
523 /* linenoise allocates line buffer on the heap, so need to free it */
524 linenoiseFree(line);
525 }
526 ESP_LOGD(TAG, "The End");
527 vTaskDelete(NULL);
528 }
529