From 94d4ace88ba995ad62a583780763d67e8e5cc42f Mon Sep 17 00:00:00 2001 From: logicog Date: Tue, 20 Jan 2026 17:53:31 +0100 Subject: [PATCH 1/2] Add support for command line editing The current code for entering commands via the serial CLI is completely refactored and moved out of rtlplayground.c into a separate file cmd_edit.c putting code into BANK2. The serial ring buffer sbuf[] is now exclusively used for receiving keys, including escape sequences (DEL will generate a 4-byte escape sequence). The command is now held in cmd_buffer. This reduces XMEM use, because the serial buffer can be much smaller. Supported is only editing the current command line via backspace, del, cursor left and right. Because the code cannot distinguish escape sequences which are not yet complete (because the serial isr has not yet put all characters into the serial buffer) from the dozens of unsupported ones (F-keys/home, Page up/down, cursor up/down...) they are ignored and remain in the serial buffer until the ring-pointer overwrites them. This is standard behaviour for consoles, which will print out garbage for unsupported characters. In this implementation, the command line buffer and the visible command line in the terminal are always synced, so garbage can be removed again by ediing the line. The code makes several redundant checks in order to prevent race-conditions between the serial ISR and the comand-editing code. --- Makefile | 2 +- cmd_editor.c | 125 +++++++++++++++++++++++++++++++++++++++++++++++ cmd_editor.h | 9 ++++ cmd_parser.c | 32 ++++++------ cmd_parser.h | 2 +- machine.c | 2 +- rtl837x_common.h | 18 +++++-- rtlplayground.c | 42 +++------------- 8 files changed, 176 insertions(+), 56 deletions(-) create mode 100644 cmd_editor.c create mode 100644 cmd_editor.h diff --git a/Makefile b/Makefile index fbe636d..db568ce 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,7 @@ all: create_build_dir $(VERSION_HEADER) $(SUBDIRS) $(BUILDDIR)rtlplayground.bin create_build_dir: mkdir -p $(BUILDDIR) -SRCS = rtlplayground.c rtl837x_flash.c rtl837x_leds.c rtl837x_phy.c rtl837x_port.c cmd_parser.c html_data.c rtl837x_igmp.c rtl837x_stp.c rtl837x_pins.c dhcp.c machine.c +SRCS = rtlplayground.c rtl837x_flash.c rtl837x_leds.c rtl837x_phy.c rtl837x_port.c cmd_parser.c html_data.c rtl837x_igmp.c rtl837x_stp.c rtl837x_pins.c dhcp.c machine.c cmd_editor.c OBJS = ${SRCS:%.c=$(BUILDDIR)%.rel} OBJS += uip/$(BUILDDIR)/timer.rel uip/$(BUILDDIR)/uip-fw.rel uip/$(BUILDDIR)/uip-neighbor.rel uip/$(BUILDDIR)/uip-split.rel uip/$(BUILDDIR)/uip.rel uip/$(BUILDDIR)/uip_arp.rel uip/$(BUILDDIR)/uiplib.rel httpd/$(BUILDDIR)/httpd.rel httpd/$(BUILDDIR)/page_impl.rel diff --git a/cmd_editor.c b/cmd_editor.c new file mode 100644 index 0000000..db3c95c --- /dev/null +++ b/cmd_editor.c @@ -0,0 +1,125 @@ +#include "cmd_parser.h" +#include "machine.h" + +#pragma codeseg BANK2 +#pragma constseg BANK2 + +// Position in the serial buffer +__xdata uint8_t l; +// Properties of currently edited command line in cmd_buffer[CMD_BUF_SIZE] +__xdata uint8_t cursor; +__xdata uint8_t cmd_line_len; + + +void cmd_editor_init(void) __banked +{ + l = sbuf_ptr; // We have printed out entered characters until l + cursor = 0; + cmd_line_len = 0; + cmd_available = 0; +} + +/* + * Allows editing the current command line held in cmd_buffer[CMD_BUF_SIZE] by + * identifying new characters typed or up to 4-byte escape sequences in the + * serial buffer ring sbuf[SBUF_SIZE]. + * Upon detecting new characters or escape sequences, the cmd_buffer and the + * representation of the command line in the terminal are updated. + * To debug, the easist is to interpose a tty-interceptor between the physical + * serial device and a logical one created by interceptty: + * sudo interceptty -s 'ispeed 115200 ospeed 115200' /dev/ttyUSB0 /dev/tmpS + * picocom -b 115200 /dev/tmpS + */ +void cmd_edit(void) __banked +{ + while (l != sbuf_ptr) { + if (sbuf[l] >= ' ' && sbuf[l] < 127) { // A printable character, copy to command line + if (cmd_line_len >= CMD_BUF_SIZE) + continue; + write_char(sbuf[l]); + // Shift buffer to right + for (uint8_t i = cmd_line_len; i > cursor; i--) + cmd_buffer[i] = cmd_buffer[i-1]; + // Insert char in comand buffer + cmd_buffer[cursor++] = sbuf[l]; + cmd_line_len++; + // Print rest of line + for (uint8_t i = cursor; i < cmd_line_len; i++) + write_char(cmd_buffer[i]); + // Move backwards + for (uint8_t i = cursor; i < cmd_line_len; i++) + write_char('\010'); // BS works like cursor-left + } else if (sbuf[l] == '\033') { // ESC-Sequence + // Wait until we have at least 3 characters including the ESC character in the serial buffer + if (((sbuf_ptr + SBUF_SIZE - l) & SBUF_MASK) < 3) + continue; + if (((sbuf_ptr > l ? sbuf_ptr - l : SBUF_SIZE + sbuf_ptr - l) >= 4) + && sbuf[l] == '\033' && sbuf[(l + 1) & SBUF_MASK] == '[' && sbuf[(l + 2) & SBUF_MASK] == '3' && sbuf[(l + 3) & SBUF_MASK] == '~') { // DEL + if (cursor < cmd_line_len) { + write_char('\033'); write_char('['); write_char('1'); write_char('P'); // Delete to end of line + cmd_line_len--; + for (uint8_t i = cursor; i < cmd_line_len; i++) { + cmd_buffer[i] = cmd_buffer[i+1]; + write_char(cmd_buffer[i]); + } + for (uint8_t i = cursor; i < cmd_line_len; i++) + write_char('\010'); + } + l += 4; + l &= SBUF_MASK; + continue; + } else if (sbuf[l] == '\033' && sbuf[(l + 1) & SBUF_MASK] == '[' && sbuf[(l + 2) & SBUF_MASK] == 'D') { // + if (cursor) { + write_char('\010'); // BS works like cursor-left + cursor--; + } + l += 3; + l &= SBUF_MASK; + continue; + } else if (sbuf[l] == '\033' && sbuf[(l + 1) & SBUF_MASK] == '[' && sbuf[(l + 2) & SBUF_MASK] == 'C') { // + if (cursor < cmd_line_len) { + write_char('\033'); write_char('['); write_char('C'); + cursor++; + } + l += 3; + l &= SBUF_MASK; + continue; + } else { // An unknown or not yet complete Escape sequence: wait + continue; + } + } else if (sbuf[l] == 127) { // Backspace + if (cursor > 0) { + write_char('\010'); + for (uint8_t i = cursor; i < cmd_line_len; i++) + write_char(cmd_buffer[i]); + write_char(' '); // Overwrite end of line + // Move backwards n steps: + for (uint8_t i = cursor; i <= cmd_line_len; i++) + write_char('\010'); + cursor--; + for (uint8_t i = cursor; i <= cmd_line_len; i++) + cmd_buffer[i] = cmd_buffer[i+1]; + cmd_line_len--; + } + } + // If the command buffer is currently in use, we cannot copy to it + if (cmd_available) + break; + // Check whether return was pressed: + if (sbuf[l] == '\n' || sbuf[l] == '\r') { + write_char('\n'); + cmd_buffer[cmd_line_len] = '\0'; + write_char('>'); print_string_x(cmd_buffer); write_char('<'); + // If there is a command we print the prompt after execution + // otherwise immediately because there is nothing to execute + if (cmd_line_len) + cmd_available = 1; + else + print_string("\n> "); + cursor = 0; + cmd_line_len = 0; + } + l++; + l &= SBUF_MASK; + } +} diff --git a/cmd_editor.h b/cmd_editor.h new file mode 100644 index 0000000..cfb70a0 --- /dev/null +++ b/cmd_editor.h @@ -0,0 +1,9 @@ +#ifndef _CMD_EDITOR_H_ +#define _CMD_EDITOR_H_ + +#include + +void cmd_editor_init(void) __banked; +void cmd_edit(void) __banked; + +#endif diff --git a/cmd_parser.c b/cmd_parser.c index 51dd9bf..0b8f97a 100644 --- a/cmd_parser.c +++ b/cmd_parser.c @@ -51,17 +51,16 @@ __xdata uint8_t hexvalue[4] = { 0 }; // Buffer for writing to flash 0x1fd000, copy to 0x1fe000 -__xdata uint8_t cmd_buffer[SBUF_SIZE]; +__xdata uint8_t cmd_buffer[CMD_BUF_SIZE]; __xdata uint8_t cmd_available; -__xdata uint8_t l; __xdata uint8_t line_ptr; __xdata char is_white; __xdata char save_cmd; __xdata uint8_t ip[4]; -#define N_WORDS SBUF_SIZE +#define N_WORDS CMD_BUF_SIZE __xdata signed char cmd_words_b[N_WORDS]; __xdata uint8_t cmd_history[CMD_HISTORY_SIZE]; @@ -93,7 +92,7 @@ uint8_t cmd_compare(uint8_t start, uint8_t * __code cmd) signed char j = 0; for (i = cmd_words_b[start]; i != cmd_words_b[start + 1] && cmd_buffer[i] != ' '; i++) { - i &= SBUF_SIZE - 1; + i &= CMD_BUF_SIZE - 1; // print_byte(i); write_char(':'); print_byte(j); write_char('#'); print_string("\n"); // write_char('>'); write_char(cmd[j]); write_char('-'); write_char(cmd_buffer[i]); print_string("\n"); if (!cmd[j] && !isletter(cmd_buffer[i])) @@ -710,7 +709,7 @@ uint8_t cmd_tokenize(void) __banked is_white = 1; uint8_t word = 0; cmd_words_b[0] = -1; - while (cmd_buffer[line_ptr] && line_ptr < SBUF_SIZE - 1) { + while (cmd_buffer[line_ptr] && line_ptr < CMD_BUF_SIZE - 1) { if (is_white && cmd_buffer[line_ptr] != ' ') { is_white = 0; cmd_words_b[word++] = line_ptr; @@ -723,7 +722,7 @@ uint8_t cmd_tokenize(void) __banked return 1; } } - if (line_ptr == SBUF_SIZE - 1) + if (line_ptr == CMD_BUF_SIZE - 1) return 1; cmd_words_b[word++] = line_ptr; cmd_words_b[word++] = -1; @@ -1001,6 +1000,16 @@ void cmd_parser(void) __banked } } + +void clear_command_history(void) __banked +{ + for (cmd_history_ptr = 0; cmd_history_ptr < CMD_HISTORY_SIZE; cmd_history_ptr++) + cmd_history[cmd_history_ptr] = 0; + cmd_history_ptr = 0; + return; +} + + #define FLASH_READ_BURST_SIZE 0x100 #define PASSWORD "1234" void execute_config(void) __banked @@ -1020,7 +1029,7 @@ void execute_config(void) __banked __xdata uint8_t cfg_idx = 0; uint8_t c = 0; do { - for (uint8_t cmd_idx = 0; cmd_idx < (SBUF_SIZE - 1); cmd_idx++) { + for (uint8_t cmd_idx = 0; cmd_idx < (CMD_BUF_SIZE - 1); cmd_idx++) { c = flash_buf[cfg_idx++]; if (c == 0 || c == '\n') { cmd_buffer[cmd_idx] = '\0'; @@ -1041,13 +1050,6 @@ void execute_config(void) __banked config_done: // Start saving commands to cmd_history + clear_command_history(); save_cmd = 1; } - -void clear_command_history(void) __banked -{ - for (cmd_history_ptr = 0; cmd_history_ptr < CMD_HISTORY_SIZE; cmd_history_ptr++) - cmd_history[cmd_history_ptr] = 0; - cmd_history_ptr = 0; - return; -} \ No newline at end of file diff --git a/cmd_parser.h b/cmd_parser.h index 816d00b..dff8cf8 100644 --- a/cmd_parser.h +++ b/cmd_parser.h @@ -5,7 +5,7 @@ #include "rtl837x_common.h" -extern __xdata uint8_t cmd_buffer[SBUF_SIZE]; +extern __xdata uint8_t cmd_buffer[CMD_BUF_SIZE]; extern __xdata uint8_t cmd_available; uint8_t cmd_tokenize(void) __banked; diff --git a/machine.c b/machine.c index ae8ff9c..2b36644 100644 --- a/machine.c +++ b/machine.c @@ -75,7 +75,7 @@ void machine_custom_init(void) { } #elif defined MACHINE_KP_9000_9XH_X_EU __code const struct machine machine = { - .machine_name = "keepLink KP-9000-6XH-X-EU", + .machine_name = "keepLink KP-9000-9XH-X-EU", .isRTL8373 = 1, .min_port = 0, .max_port = 8, diff --git a/rtl837x_common.h b/rtl837x_common.h index f6c9dd7..f33a1d2 100644 --- a/rtl837x_common.h +++ b/rtl837x_common.h @@ -19,9 +19,19 @@ #define LOOKUP_MISS_DROP_9 0x00015555 #define LOOKUP_MISS_FLOOD 0x00000000 -// The serial buffer. Defines the command line size -// Must be 2^x and <= 128 -#define SBUF_SIZE 128 +/* Buffer for serial input, SBUF_SIZE must be power of 2 < 256 + * Writing to this buffer is under the sole control of the serial ISR + * Note that key-presses such as can create multiple + * keys (3 to 4) being sent via the serial line, so this must be + * sufficiently large */ +#define SBUF_SIZE 16 +#define SBUF_MASK (SBUF_SIZE - 1) + +extern __xdata volatile uint8_t sbuf_ptr; +extern __xdata uint8_t sbuf[SBUF_SIZE]; + +// Define the command buffer size, Must be 2^x and <= 128 +#define CMD_BUF_SIZE 128 // Size of the TCP Output buffer #define TCP_OUTBUF_SIZE 2500 @@ -90,6 +100,7 @@ extern __xdata struct uip_eth_addr uip_ethaddr; // Headers for calls in the common code area (HOME/BANK0) void print_string(__code char *p); +void print_string_x(__xdata char *p); void print_long(__xdata uint32_t a); void print_short(uint16_t a); void print_byte(uint8_t a); @@ -121,7 +132,6 @@ uint16_t strlen(register __code const char *s); uint16_t strlen_x(register __xdata const char *s); uint16_t strtox(register __xdata uint8_t *dst, register __code const char *s); void tcpip_output(void); -void print_string_x(__xdata char *p); uint8_t read_flash(uint8_t bank, __code uint8_t *addr); void get_random_32(void); void read_reg_timer(uint32_t * tmr); diff --git a/rtlplayground.c b/rtlplayground.c index 55b6d52..fc0eeed 100644 --- a/rtlplayground.c +++ b/rtlplayground.c @@ -16,6 +16,7 @@ #include "rtl837x_leds.h" #include "dhcp.h" #include "cmd_parser.h" +#include "cmd_editor.h" #include "uip/uipopt.h" #include "uip/uip.h" #include "uip/uip_arp.h" @@ -86,10 +87,13 @@ extern __xdata struct dhcp_state dhcp_state; #define STP_TICK_DIVIDER 3 - -// Buffer for serial input, SBUF_SIZE must be power of 2 < 256 +/* Buffer for serial input, SBUF_SIZE must be power of 2 < 256 + * Writing to this buffer is under the sole control of the serial ISR + * Note that key-presses such as can create multiple + * keys being sent via the serial line */ __xdata volatile uint8_t sbuf_ptr; __xdata uint8_t sbuf[SBUF_SIZE]; + __xdata uint8_t sfr_data[4]; extern __xdata uint8_t gpio_last_value[8]; @@ -2102,39 +2106,9 @@ void bootloader(void) set_sys_led_state(SYS_LED_ON); - // Wait for commands on serial connection - // sbuf_ptr is moved forward by serial interrupt, l is the position until we have already - // printed out the entered characters - __xdata uint8_t l = sbuf_ptr; // We have printed out entered characters until l - __xdata uint8_t line_start = sbuf_ptr; // This is where the current line starts - cmd_available = 0; + cmd_editor_init(); while (1) { - while (l != sbuf_ptr) { - // If the command buffer is currently in use, we cannot copy to it - if (cmd_available) - break; - write_char(sbuf[l]); - // Check whether there is a full line: - if (sbuf[l] == '\n' || sbuf[l] == '\r') { - write_char('\n'); - register uint8_t i = 0; - while (line_start != l) { - cmd_buffer[i++] = sbuf[line_start++]; - line_start &= (SBUF_SIZE - 1); - } - line_start++; - line_start &= (SBUF_SIZE - 1); - cmd_buffer[i] = '\0'; - // If there is a command we print the prompt after execution - // otherwise immediately because there is nothing to execute - if (i) - cmd_available = 1; - else - print_string("\n> "); - } - l++; - l &= (SBUF_SIZE - 1); - } + cmd_edit(); idle(); // Enter Idle mode until interrupt occurs } } From dfc96761b3584ca2e485b91aaa318fc5bd796201 Mon Sep 17 00:00:00 2001 From: logicog Date: Sun, 25 Jan 2026 10:36:26 +0100 Subject: [PATCH 2/2] Add support for history editing --- cmd_editor.c | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-) diff --git a/cmd_editor.c b/cmd_editor.c index db3c95c..43d0c0c 100644 --- a/cmd_editor.c +++ b/cmd_editor.c @@ -9,14 +9,20 @@ __xdata uint8_t l; // Properties of currently edited command line in cmd_buffer[CMD_BUF_SIZE] __xdata uint8_t cursor; __xdata uint8_t cmd_line_len; +__xdata uint8_t current_cmdline[CMD_BUF_SIZE]; +__xdata uint16_t history_editptr; +extern __xdata uint8_t cmd_history[CMD_HISTORY_SIZE]; +extern __xdata uint16_t cmd_history_ptr; + void cmd_editor_init(void) __banked { l = sbuf_ptr; // We have printed out entered characters until l cursor = 0; cmd_line_len = 0; cmd_available = 0; + history_editptr = 0xffff; } /* @@ -84,6 +90,82 @@ void cmd_edit(void) __banked l += 3; l &= SBUF_MASK; continue; + } else if (sbuf[l] == '\033' && sbuf[(l + 1) & SBUF_MASK] == '[' && sbuf[(l + 2) & SBUF_MASK] == 'A') { // + for (uint8_t i = 0; i < cmd_line_len; i++) + current_cmdline[i] = cmd_buffer[i]; + current_cmdline[cmd_line_len] = 0; + __xdata uint16_t p; + if (history_editptr == 0xffff) + p = (cmd_history_ptr - 2) & CMD_HISTORY_MASK; + else + p = history_editptr; + // Move cursor to beginning of line + write_char('\033'); write_char('['); itoa(cursor + 2); write_char('D'); + cursor = 0; + while (cmd_history[p] && cmd_history[p] != '\n') { + cursor++; + p--; + p &= CMD_HISTORY_MASK; + } + history_editptr = (p - 1) & CMD_HISTORY_MASK; + p = (p + 1) & CMD_HISTORY_MASK; + if (cursor) { + print_string("\033[2K> "); // Clear entire line: ^[[2K and print new prompt + for (uint8_t i = 0; i < cursor; i++) { + cmd_buffer[i] = cmd_history[p]; + write_char(cmd_buffer[i]); + p = (p+1) & CMD_HISTORY_MASK; + } + cmd_line_len = cursor; + } else { + print_string("\033[2C"); // Move 2 right to start of editing space + } + l += 3; + l &= SBUF_MASK; + continue; + } else if (sbuf[l] == '\033' && sbuf[(l + 1) & SBUF_MASK] == '[' && sbuf[(l + 2) & SBUF_MASK] == 'B') { // + if (history_editptr != 0xffff) { + __xdata uint16_t p = (history_editptr + 2) & CMD_HISTORY_MASK; + // Move cursor to beginning of line + write_char('\033'); write_char('['); itoa(cursor + 2); write_char('D'); + print_string("\033[2K> "); // Clear entire line: ^[[2K and print new prompt + uint8_t i = 0; + while (cmd_history[p] && cmd_history[p] != '\n') { + p = (p + 1) & CMD_HISTORY_MASK; + } + p = (p + 1) & CMD_HISTORY_MASK; + while (cmd_history[p] && cmd_history[p] != '\n') { + cmd_buffer[i] = cmd_history[p]; + write_char(cmd_buffer[i++]); + p = (p + 1) & CMD_HISTORY_MASK; + } + history_editptr = (p - 1) & CMD_HISTORY_MASK; + if (!i) { + if (current_cmdline[i]) { + while (current_cmdline[i]) { + cmd_buffer[i] = current_cmdline[i]; + write_char(cmd_buffer[i++]); + } + } else { + write_char('\033'); write_char('['); write_char('C'); + } + history_editptr = 0xffff; + // Move cursor right + } + cmd_line_len = i; + cursor = 0; + // Move cursor again to beginning of line + write_char('\033'); write_char('['); itoa(i); write_char('D'); + } else { + // If we are at the last entry of the history, just move the cursor to the end of the line + if (cursor < cmd_line_len) { + write_char('\033'); write_char('['); itoa(cmd_line_len - cursor); write_char('C'); + } + cursor = cmd_line_len; + } + l += 3; + l &= SBUF_MASK; + continue; } else { // An unknown or not yet complete Escape sequence: wait continue; } @@ -109,7 +191,7 @@ void cmd_edit(void) __banked if (sbuf[l] == '\n' || sbuf[l] == '\r') { write_char('\n'); cmd_buffer[cmd_line_len] = '\0'; - write_char('>'); print_string_x(cmd_buffer); write_char('<'); +// write_char('>'); print_string_x(cmd_buffer); write_char('<'); // If there is a command we print the prompt after execution // otherwise immediately because there is nothing to execute if (cmd_line_len) @@ -118,6 +200,7 @@ void cmd_edit(void) __banked print_string("\n> "); cursor = 0; cmd_line_len = 0; + history_editptr = 0xffff; } l++; l &= SBUF_MASK;