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