| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| #include <assert.h> |
| #include <inttypes.h> |
| #include <ctype.h> |
| #include <stdarg.h> |
| #include <stdio.h> |
| #include <string.h> |
| |
| #include "os/mynewt.h" |
| #include "console/console.h" |
| #include "console/ticks.h" |
| #include "console_priv.h" |
| |
| /* Control characters */ |
| #define ESC 0x1b |
| #define DEL 0x7f |
| #define BS 0x08 |
| |
| /* ANSI escape sequences */ |
| #define ANSI_ESC '[' |
| #define ANSI_UP 'A' |
| #define ANSI_DOWN 'B' |
| #define ANSI_FORWARD 'C' |
| #define ANSI_BACKWARD 'D' |
| #define ANSI_END 'F' |
| #define ANSI_HOME 'H' |
| #define ANSI_DEL '~' |
| |
| #define ESC_ESC (1 << 0) |
| #define ESC_ANSI (1 << 1) |
| #define ESC_ANSI_FIRST (1 << 2) |
| #define ESC_ANSI_VAL (1 << 3) |
| #define ESC_ANSI_VAL_2 (1 << 4) |
| |
| #define CONSOLE_NLIP_PKT_START1 (6) |
| #define CONSOLE_NLIP_PKT_START2 (9) |
| #define CONSOLE_NLIP_DATA_START1 (4) |
| #define CONSOLE_NLIP_DATA_START2 (20) |
| |
| #define NLIP_PKT_START1 (1 << 0) |
| #define NLIP_PKT_START2 (1 << 1) |
| #define NLIP_DATA_START1 (1 << 2) |
| #define NLIP_DATA_START2 (1 << 3) |
| |
| /* Indicates whether the previous line of output was completed. */ |
| int console_is_midline; |
| |
| #if MYNEWT_VAL(CONSOLE_COMPAT) |
| #define CONSOLE_COMPAT_MAX_CMD_QUEUED 1 |
| static struct console_input buf[CONSOLE_COMPAT_MAX_CMD_QUEUED]; |
| static struct os_event shell_console_ev[CONSOLE_COMPAT_MAX_CMD_QUEUED]; |
| static console_rx_cb console_compat_rx_cb; /* callback that input is ready */ |
| static struct os_eventq compat_avail_queue; |
| static struct os_eventq compat_lines_queue; |
| #endif |
| |
| static int esc_state; |
| static int nlip_state; |
| static int echo = MYNEWT_VAL(CONSOLE_ECHO); |
| static unsigned int ansi_val, ansi_val_2; |
| static bool rx_stalled; |
| |
| static uint16_t cur, end; |
| static struct os_eventq avail_queue; |
| static struct os_eventq *lines_queue; |
| static completion_cb completion; |
| bool g_silence_console; |
| |
| /* |
| * Default implementation in case all consoles are disabled - we just ignore any |
| * output to console. |
| */ |
| int __attribute__((weak)) |
| console_out(int c) |
| { |
| return c; |
| } |
| |
| void |
| console_echo(int on) |
| { |
| echo = on; |
| } |
| |
| void |
| console_write(const char *str, int cnt) |
| { |
| int i; |
| |
| for (i = 0; i < cnt; i++) { |
| if (console_out((int)str[i]) == EOF) { |
| break; |
| } |
| } |
| } |
| |
| #if MYNEWT_VAL(CONSOLE_COMPAT) |
| int |
| console_read(char *str, int cnt, int *newline) |
| { |
| struct os_event *ev; |
| struct console_input *cmd; |
| size_t len; |
| |
| *newline = 0; |
| ev = os_eventq_get_no_wait(lines_queue); |
| if (!ev) { |
| return 0; |
| } |
| cmd = ev->ev_arg; |
| len = strlen(cmd->line); |
| |
| if ((cnt - 1) < len) { |
| len = cnt - 1; |
| } |
| |
| if (len > 0) { |
| memcpy(str, cmd->line, len); |
| str[len] = '\0'; |
| } else { |
| str[0] = cmd->line[0]; |
| } |
| |
| console_line_event_put(ev); |
| |
| *newline = 1; |
| return len; |
| } |
| #endif |
| |
| void |
| console_blocking_mode(void) |
| { |
| #if MYNEWT_VAL(CONSOLE_UART) |
| uart_console_blocking_mode(); |
| #endif |
| } |
| |
| void |
| console_non_blocking_mode(void) |
| { |
| #if MYNEWT_VAL(CONSOLE_UART) |
| uart_console_non_blocking_mode(); |
| #endif |
| } |
| |
| static inline void |
| cursor_forward(unsigned int count) |
| { |
| console_printf("\x1b[%uC", count); |
| } |
| |
| static inline void |
| cursor_backward(unsigned int count) |
| { |
| console_printf("\x1b[%uD", count); |
| } |
| |
| #if MYNEWT_VAL(CONSOLE_HISTORY_SIZE) > 0 |
| static inline void |
| cursor_clear_line(void) |
| { |
| console_out(ESC); |
| console_out('['); |
| console_out('K'); |
| } |
| #endif |
| |
| static inline void |
| cursor_save(void) |
| { |
| console_out(ESC); |
| console_out('['); |
| console_out('s'); |
| } |
| |
| static inline void |
| cursor_restore(void) |
| { |
| console_out(ESC); |
| console_out('['); |
| console_out('u'); |
| } |
| |
| static void |
| insert_char(char *pos, char c, uint16_t end) |
| { |
| char tmp; |
| |
| if (cur + end >= MYNEWT_VAL(CONSOLE_MAX_INPUT_LEN) - 1) { |
| return; |
| } |
| |
| if (echo) { |
| /* Echo back to console */ |
| console_out(c); |
| } |
| |
| ++cur; |
| |
| if (end == 0) { |
| *pos = c; |
| return; |
| } |
| |
| tmp = *pos; |
| *(pos++) = c; |
| |
| cursor_save(); |
| |
| while (end-- > 0) { |
| console_out(tmp); |
| c = *pos; |
| *(pos++) = tmp; |
| tmp = c; |
| } |
| |
| /* Move cursor back to right place */ |
| cursor_restore(); |
| } |
| |
| static void |
| del_char(char *pos, uint16_t end) |
| { |
| console_out('\b'); |
| |
| if (end == 0) { |
| console_out(' '); |
| console_out('\b'); |
| return; |
| } |
| |
| cursor_save(); |
| |
| while (end-- > 0) { |
| *pos = *(pos + 1); |
| console_out(*(pos++)); |
| } |
| |
| console_out(' '); |
| |
| /* Move cursor back to right place */ |
| cursor_restore(); |
| } |
| |
| #if MYNEWT_VAL(CONSOLE_HISTORY_SIZE) > 0 |
| static char console_hist_lines[ MYNEWT_VAL(CONSOLE_HISTORY_SIZE) ][ MYNEWT_VAL(CONSOLE_MAX_INPUT_LEN) ]; |
| |
| static struct console_hist { |
| uint8_t head; |
| uint8_t tail; |
| uint8_t size; |
| uint8_t curr; |
| char *lines[ MYNEWT_VAL(CONSOLE_HISTORY_SIZE) + 1 ]; |
| } console_hist; |
| |
| static void |
| console_hist_init(void) |
| { |
| struct console_hist *sh = &console_hist; |
| int i; |
| |
| memset(console_hist_lines, 0, sizeof(console_hist_lines)); |
| memset(&console_hist, 0, sizeof(console_hist)); |
| |
| sh->size = MYNEWT_VAL(CONSOLE_HISTORY_SIZE) + 1; |
| |
| for (i = 0; i < sh->size - 1; i++) { |
| sh->lines[i] = console_hist_lines[i]; |
| } |
| } |
| |
| static size_t |
| trim_whitespace(const char *str, size_t len, char *out) |
| { |
| const char *end; |
| size_t out_size; |
| |
| if (len == 0) { |
| return 0; |
| } |
| |
| /* Trim leading space */ |
| while (isspace((unsigned char)*str)) { |
| str++; |
| } |
| |
| if (*str == 0) { /* All spaces? */ |
| *out = 0; |
| return 0; |
| } |
| |
| /* Trim trailing space */ |
| end = str + strlen(str) - 1; |
| while (end > str && isspace((unsigned char)*end)) { |
| end--; |
| } |
| |
| end++; |
| |
| /* Set output size to minimum of trimmed string length and buffer size minus 1 */ |
| out_size = min(end - str, len - 1); |
| |
| /* Copy trimmed string and add null terminator */ |
| memcpy(out, str, out_size); |
| out[out_size] = 0; |
| |
| return out_size; |
| } |
| |
| static uint8_t |
| ring_buf_next(uint8_t i, uint8_t size) |
| { |
| return (uint8_t) ((i + 1) % size); |
| } |
| |
| static uint8_t |
| ring_buf_prev(uint8_t i, uint8_t size) |
| { |
| return i == 0 ? i = size - 1 : --i; |
| } |
| |
| static bool |
| console_hist_is_full(void) |
| { |
| struct console_hist *sh = &console_hist; |
| |
| return ring_buf_next(sh->head, sh->size) == sh->tail; |
| } |
| |
| static bool |
| console_hist_move_to_head(char *line) |
| { |
| struct console_hist *sh = &console_hist; |
| char *match = NULL; |
| uint8_t prev, curr; |
| |
| curr = sh->tail; |
| while (curr != sh->head) { |
| if (strcmp(sh->lines[curr], line) == 0) { |
| match = sh->lines[curr]; |
| break; |
| } |
| curr = ring_buf_next(curr, sh->size); |
| } |
| |
| if (!match) { |
| return false; |
| } |
| |
| prev = curr; |
| curr = ring_buf_next(curr, sh->size); |
| while (curr != sh->head) { |
| sh->lines[prev] = sh->lines[curr]; |
| prev = curr; |
| curr = ring_buf_next(curr, sh->size); |
| } |
| |
| sh->lines[prev] = match; |
| |
| return true; |
| } |
| |
| static void |
| console_hist_add(char *line) |
| { |
| struct console_hist *sh = &console_hist; |
| char buf[MYNEWT_VAL(CONSOLE_MAX_INPUT_LEN)]; |
| size_t len; |
| |
| /* Reset current pointer */ |
| sh->curr = sh->head; |
| |
| len = trim_whitespace(line, sizeof(buf), buf); |
| if (!len) { |
| return; |
| } |
| |
| if (console_hist_move_to_head(buf)) { |
| return; |
| } |
| |
| if (console_hist_is_full()) { |
| /* |
| * We have N buffers, but there are N+1 items in queue so one element is |
| * always empty. If queue is full we need to move buffer from oldest |
| * entry to current head and trim queue tail. |
| */ |
| assert(sh->lines[sh->head] == NULL); |
| sh->lines[sh->head] = sh->lines[sh->tail]; |
| sh->lines[sh->tail] = NULL; |
| sh->tail = ring_buf_next(sh->tail, sh->size); |
| } |
| |
| strcpy(sh->lines[sh->head], buf); |
| sh->head = ring_buf_next(sh->head, sh->size); |
| |
| /* Reset current pointer */ |
| sh->curr = sh->head; |
| } |
| |
| static void |
| console_clear_line(void) |
| { |
| if (cur) { |
| cursor_backward(cur); |
| } |
| cur = 0; |
| end = 0; |
| |
| cursor_clear_line(); |
| } |
| |
| static void |
| console_hist_move(char *line, uint8_t direction) |
| { |
| struct console_hist *sh = &console_hist; |
| char *str = NULL; |
| uint8_t limit = direction == ANSI_UP ? sh->tail : sh->head; |
| |
| /* no more history to return in this direction */ |
| if (sh->curr == limit) { |
| return; |
| } |
| |
| if (direction == ANSI_UP) { |
| sh->curr = ring_buf_prev(sh->curr, sh->size); |
| } else { |
| sh->curr = ring_buf_next(sh->curr, sh->size); |
| } |
| |
| console_clear_line(); |
| str = sh->lines[sh->curr]; |
| while (str && *str != '\0') { |
| insert_char(&line[cur], *str, end); |
| ++str; |
| } |
| } |
| #endif |
| |
| static void |
| handle_ansi(uint8_t byte, char *line) |
| { |
| if (esc_state & ESC_ANSI_FIRST) { |
| esc_state &= ~ESC_ANSI_FIRST; |
| if (!isdigit(byte)) { |
| ansi_val = 1; |
| goto ansi_cmd; |
| } |
| |
| esc_state |= ESC_ANSI_VAL; |
| ansi_val = byte - '0'; |
| ansi_val_2 = 0; |
| return; |
| } |
| |
| if (esc_state & ESC_ANSI_VAL) { |
| if (isdigit(byte)) { |
| if (esc_state & ESC_ANSI_VAL_2) { |
| ansi_val_2 *= 10; |
| ansi_val_2 += byte - '0'; |
| } else { |
| ansi_val *= 10; |
| ansi_val += byte - '0'; |
| } |
| return; |
| } |
| |
| /* Multi value sequence, e.g. Esc[Line;ColumnH */ |
| if (byte == ';' && !(esc_state & ESC_ANSI_VAL_2)) { |
| esc_state |= ESC_ANSI_VAL_2; |
| return; |
| } |
| |
| esc_state &= ~ESC_ANSI_VAL; |
| esc_state &= ~ESC_ANSI_VAL_2; |
| } |
| |
| ansi_cmd: |
| switch (byte) { |
| #if MYNEWT_VAL(CONSOLE_HISTORY_SIZE) > 0 |
| case ANSI_UP: |
| case ANSI_DOWN: |
| console_blocking_mode(); |
| console_hist_move(line, byte); |
| console_non_blocking_mode(); |
| break; |
| #endif |
| case ANSI_BACKWARD: |
| if (ansi_val > cur) { |
| break; |
| } |
| |
| end += ansi_val; |
| cur -= ansi_val; |
| cursor_backward(ansi_val); |
| break; |
| case ANSI_FORWARD: |
| if (ansi_val > end) { |
| break; |
| } |
| |
| end -= ansi_val; |
| cur += ansi_val; |
| cursor_forward(ansi_val); |
| break; |
| case ANSI_HOME: |
| if (!cur) { |
| break; |
| } |
| |
| cursor_backward(cur); |
| end += cur; |
| cur = 0; |
| break; |
| case ANSI_END: |
| if (!end) { |
| break; |
| } |
| |
| cursor_forward(end); |
| cur += end; |
| end = 0; |
| break; |
| case ANSI_DEL: |
| if (!end) { |
| break; |
| } |
| |
| cursor_forward(1); |
| del_char(&line[cur], --end); |
| break; |
| default: |
| break; |
| } |
| |
| esc_state &= ~ESC_ANSI; |
| } |
| |
| static int |
| handle_nlip(uint8_t byte) |
| { |
| if (((nlip_state & NLIP_PKT_START1) && |
| (nlip_state & NLIP_PKT_START2)) || |
| ((nlip_state & NLIP_DATA_START1) && |
| (nlip_state & NLIP_DATA_START2))) |
| { |
| return 1; |
| } |
| |
| if ((nlip_state & NLIP_PKT_START1) && |
| (byte == CONSOLE_NLIP_PKT_START2)) { |
| nlip_state |= NLIP_PKT_START2; |
| return 1; |
| } else if ((nlip_state & NLIP_DATA_START1) && |
| (byte == CONSOLE_NLIP_DATA_START2)) { |
| nlip_state |= NLIP_DATA_START2; |
| return 1; |
| } else { |
| nlip_state = 0; |
| return 0; |
| } |
| } |
| |
| static int |
| console_append_char(char *line, uint8_t byte) |
| { |
| if (cur + end >= MYNEWT_VAL(CONSOLE_MAX_INPUT_LEN) - 1) { |
| return 0; |
| } |
| |
| line[cur + end] = byte; |
| |
| if (byte == '\0') { |
| return 1; |
| } |
| |
| if (echo) { |
| /* Echo back to console */ |
| console_out(byte); |
| } |
| ++cur; |
| return 1; |
| } |
| |
| int |
| console_handle_char(uint8_t byte) |
| { |
| #if !MYNEWT_VAL(CONSOLE_INPUT) |
| return 0; |
| #endif |
| |
| static struct os_event *ev; |
| static struct console_input *input; |
| static char prev_endl = '\0'; |
| |
| if (!lines_queue) { |
| return 0; |
| } |
| |
| if (!ev) { |
| ev = os_eventq_get_no_wait(&avail_queue); |
| if (!ev) { |
| rx_stalled = true; |
| return -1; |
| } |
| input = ev->ev_arg; |
| } |
| |
| if (handle_nlip(byte)) { |
| if (byte == '\n') { |
| insert_char(&input->line[cur], byte, end); |
| input->line[cur] = '\0'; |
| cur = 0; |
| end = 0; |
| os_eventq_put(lines_queue, ev); |
| nlip_state = 0; |
| |
| #if MYNEWT_VAL(CONSOLE_COMPAT) |
| if (console_compat_rx_cb) { |
| console_compat_rx_cb(); |
| } |
| #endif |
| |
| input = NULL; |
| ev = NULL; |
| console_echo(1); |
| return 0; |
| /* Ignore characters if there's no more buffer space */ |
| } else if (byte == CONSOLE_NLIP_PKT_START2) { |
| /* Disable echo to not flood the UART */ |
| console_echo(0); |
| insert_char(&input->line[cur], CONSOLE_NLIP_PKT_START1, end); |
| } else if (byte == CONSOLE_NLIP_DATA_START2) { |
| /* Disable echo to not flood the UART */ |
| console_echo(0); |
| insert_char(&input->line[cur], CONSOLE_NLIP_DATA_START1, end); |
| } |
| |
| insert_char(&input->line[cur], byte, end); |
| |
| return 0; |
| } |
| |
| /* Handle ANSI escape mode */ |
| if (esc_state & ESC_ANSI) { |
| handle_ansi(byte, input->line); |
| return 0; |
| } |
| |
| /* Handle escape mode */ |
| if (esc_state & ESC_ESC) { |
| esc_state &= ~ESC_ESC; |
| handle_ansi(byte, input->line); |
| switch (byte) { |
| case ANSI_ESC: |
| esc_state |= ESC_ANSI; |
| esc_state |= ESC_ANSI_FIRST; |
| break; |
| default: |
| break; |
| } |
| |
| return 0; |
| } |
| |
| /* Handle special control characters */ |
| if (!isprint(byte)) { |
| handle_ansi(byte, input->line); |
| switch (byte) { |
| case CONSOLE_NLIP_PKT_START1: |
| nlip_state |= NLIP_PKT_START1; |
| break; |
| case CONSOLE_NLIP_DATA_START1: |
| nlip_state |= NLIP_DATA_START1; |
| break; |
| case DEL: |
| case BS: |
| if (cur > 0) { |
| del_char(&input->line[--cur], end); |
| } |
| break; |
| case ESC: |
| esc_state |= ESC_ESC; |
| break; |
| default: |
| insert_char(&input->line[cur], byte, end); |
| /* Falls through. */ |
| case '\r': |
| /* Falls through. */ |
| case '\n': |
| if (byte == '\n' && prev_endl == '\r') { |
| /* handle double end line chars */ |
| prev_endl = byte; |
| break; |
| } |
| prev_endl = byte; |
| input->line[cur + end] = '\0'; |
| console_out('\r'); |
| console_out('\n'); |
| cur = 0; |
| end = 0; |
| os_eventq_put(lines_queue, ev); |
| #if MYNEWT_VAL(CONSOLE_HISTORY_SIZE) > 0 |
| console_hist_add(input->line); |
| #endif |
| |
| #if MYNEWT_VAL(CONSOLE_COMPAT) |
| if (console_compat_rx_cb) { |
| console_compat_rx_cb(); |
| } |
| #endif |
| |
| input = NULL; |
| ev = NULL; |
| break; |
| case '\t': |
| if (completion && !end) { |
| #if MYNEWT_VAL(CONSOLE_UART_RX_BUF_SIZE) == 0 |
| console_blocking_mode(); |
| #endif |
| completion(input->line, console_append_char); |
| #if MYNEWT_VAL(CONSOLE_UART_RX_BUF_SIZE) == 0 |
| console_non_blocking_mode(); |
| #endif |
| } |
| break; |
| } |
| |
| return 0; |
| } |
| |
| insert_char(&input->line[cur], byte, end); |
| return 0; |
| } |
| |
| int |
| console_is_init(void) |
| { |
| #if MYNEWT_VAL(CONSOLE_UART) |
| return uart_console_is_init(); |
| #endif |
| #if MYNEWT_VAL(CONSOLE_RTT) |
| return rtt_console_is_init(); |
| #endif |
| #if MYNEWT_VAL(CONSOLE_BLE_MONITOR) |
| return ble_monitor_console_is_init(); |
| #endif |
| return 0; |
| } |
| |
| void |
| console_line_queue_set(struct os_eventq *evq) |
| { |
| lines_queue = evq; |
| } |
| |
| void |
| console_line_event_put(struct os_event *ev) |
| { |
| os_eventq_put(&avail_queue, ev); |
| |
| if (rx_stalled) { |
| rx_stalled = false; |
| console_rx_restart(); |
| } |
| } |
| |
| void |
| console_set_completion_cb(completion_cb cb) |
| { |
| completion = cb; |
| } |
| |
| #if MYNEWT_VAL(CONSOLE_COMPAT) |
| int |
| console_init(console_rx_cb rx_cb) |
| { |
| int i; |
| |
| os_eventq_init(&compat_lines_queue); |
| os_eventq_init(&compat_avail_queue); |
| console_line_queue_set(&compat_lines_queue); |
| |
| for (i = 0; i < CONSOLE_COMPAT_MAX_CMD_QUEUED; i++) { |
| shell_console_ev[i].ev_arg = &buf[i]; |
| console_line_event_put(&shell_console_ev[i]); |
| } |
| console_compat_rx_cb = rx_cb; |
| return 0; |
| } |
| #endif |
| |
| void |
| console_pkg_init(void) |
| { |
| int rc = 0; |
| |
| /* Ensure this function only gets called by sysinit. */ |
| SYSINIT_ASSERT_ACTIVE(); |
| |
| os_eventq_init(&avail_queue); |
| |
| #if MYNEWT_VAL(CONSOLE_HISTORY_SIZE) > 0 |
| console_hist_init(); |
| #endif |
| |
| #if MYNEWT_VAL(CONSOLE_UART) |
| rc = uart_console_init(); |
| #endif |
| #if MYNEWT_VAL(CONSOLE_RTT) |
| rc = rtt_console_init(); |
| #endif |
| SYSINIT_PANIC_ASSERT(rc == 0); |
| } |