#include #include #include #include #include #include /* microVTY - small implementation of a limited feature command line interface * (C) 2019 by Harald Welte * * modelled after libosmovty (part of libosmocore.git), which in turn is a fork * of the command line interface of GNU zebra / quagga. * * microVTY is intended for very small bare-iron microcontroller. It doesn't need * any dynamic allocations/heap, and tries to stay very simplistic to conserve * resources. */ struct microvty_state { const char *prompt; char buf[MICROVTY_CMD_BUF_SIZE]; unsigned int buf_idx; const struct microvty_fn *cmd[MICROVTY_MAX_CMD]; unsigned int cmd_idx; }; static struct microvty_state g_cmds; /*! register a command with microVTY */ int microvty_register(const struct microvty_fn *cmd) { if (g_cmds.cmd_idx >= ARRAY_SIZE(g_cmds.cmd)) return -1; g_cmds.cmd[g_cmds.cmd_idx++] = cmd; return 0; } /*! built-in help command */ DEFUN(help, help_cmd, "help", "Print command reference") { unsigned int i; printf("Help:\r\n"); printf(" Command Help\r\n"); printf(" ---------------- ----\r\n"); for (i = 0; i < g_cmds.cmd_idx; i++) printf(" %-16s %s\r\n", g_cmds.cmd[i]->command, g_cmds.cmd[i]->help); } static void cmd_execute(void) { char *argv[16]; unsigned int i; unsigned int argc = 0; char *cur, *saveptr = NULL; printf("\r\n"); memset(argv, 0, sizeof(argv)); for (cur = strtok_r(g_cmds.buf, " ", &saveptr); cur; cur = strtok_r(NULL, " ", &saveptr)) { if (argc >= ARRAY_SIZE(argv)) break; argv[argc++] = cur; } for (i = 0; i < g_cmds.cmd_idx; i++) { if (!strcmp(g_cmds.cmd[i]->command, argv[0])) { g_cmds.cmd[i]->fn(argc, argv); return; } } printf("Unknown command: '%s'\r\n", argv[0]); } static void cmd_buf_reset(void) { memset(g_cmds.buf, 0, sizeof(g_cmds.buf)); g_cmds.buf_idx = 0; } static void cmd_buf_append(char c) { g_cmds.buf[g_cmds.buf_idx++] = c; } static bool cmd_buf_backspace(void) { if (g_cmds.buf_idx > 0) { g_cmds.buf_idx--; g_cmds.buf[g_cmds.buf_idx] = 0; return true; } return false; } /*! print the prompt to the console (stdout) */ void microvty_print_prompt(void) { printf(g_cmds.prompt); } #define HIST_MOD(x) ((x) % 3) /* detect the "matic" "\x02\xff\x00 XCP-CONNECT sequence OpenBLT uses */ static bool detect_xcp_connect(int c) { static char history[3]; static uint8_t hist_idx; hist_idx = HIST_MOD(hist_idx + 1); history[hist_idx] = c; if (history[hist_idx] == 0 && history[HIST_MOD(hist_idx+1)] == 0x02 && history[HIST_MOD(hist_idx+2)] == 0xff) return true; return false; } static bool microvty_process_char(int c) { /* if the bootloader entry sequence is detected, reset the system */ if (detect_xcp_connect(c)) scb_reset_system(); if (c == '\r' || c == '\n' || g_cmds.buf_idx >= sizeof(g_cmds.buf)-1) { /* skip empty commands */ if (g_cmds.buf_idx == 0) { printf("\r\n"); microvty_print_prompt(); return true; } cmd_execute(); cmd_buf_reset(); microvty_print_prompt(); return true; } else if (c == '\b' || c == '\x7f') { if (cmd_buf_backspace()) { /* VT100 weirdness: Need to print backspace, space, backspace */ printf("\b \b"); } } else if (c == 3) { /* Ctrl+C */ printf("\r\n"); microvty_print_prompt(); return true; } else if (c == '\t' || c == '\033') { /* ignore control characters */ return false; } else { /* print + append character */ putchar(c); cmd_buf_append(c); } return false; } /*! try to receive characters from the console, dispatching them */ void microvty_try_recv(void) { unsigned int i = 0; /* yield CPU after maximum of 10 received characters */ while (microvty_cb_uart_rx_not_empty() && (i < 10)) { int c = getchar(); if (c < 0) return; if (microvty_process_char(c)) return; i++; } } /* initialize microVTY. Should be called once on start-up */ void microvty_init(const char *prompt) { g_cmds.prompt = prompt; microvty_register(&help_cmd); } #ifndef CONFIG_NO_FIBRE #include static int microvty_fibre_fn(fibre_t *fibre) { PT_BEGIN_FIBRE(fibre); while (true) { /* we could use PT_WAIT_UNTIL() herre, but then we'd have to hack * a fibre_run_atomic() call into the USART2 ISR, which is ugly and * violates the separation of the stdio/bio layer and the microvty */ if (!microvty_cb_uart_rx_not_empty()) PT_YIELD(); int c = getchar(); if (c >= 0) microvty_process_char(c); } PT_END(); } fibre_t microvty_fibre = FIBRE_VAR_INIT(microvty_fibre_fn); #endif /* CONFIG_NO_FIBRE */