Home | History | Annotate | Download | only in common
      1 // SPDX-License-Identifier: GPL-2.0+
      2 /*
      3  * (C) Copyright 2000
      4  * Wolfgang Denk, DENX Software Engineering, wd (at) denx.de.
      5  *
      6  * Add to readline cmdline-editing by
      7  * (C) Copyright 2005
      8  * JinHua Luo, GuangDong Linux Center, <luo.jinhua (at) gd-linux.com>
      9  */
     10 
     11 #include <common.h>
     12 #include <bootretry.h>
     13 #include <cli.h>
     14 #include <watchdog.h>
     15 
     16 DECLARE_GLOBAL_DATA_PTR;
     17 
     18 static const char erase_seq[] = "\b \b";	/* erase sequence */
     19 static const char   tab_seq[] = "        ";	/* used to expand TABs */
     20 
     21 char console_buffer[CONFIG_SYS_CBSIZE + 1];	/* console I/O buffer	*/
     22 
     23 static char *delete_char (char *buffer, char *p, int *colp, int *np, int plen)
     24 {
     25 	char *s;
     26 
     27 	if (*np == 0)
     28 		return p;
     29 
     30 	if (*(--p) == '\t') {		/* will retype the whole line */
     31 		while (*colp > plen) {
     32 			puts(erase_seq);
     33 			(*colp)--;
     34 		}
     35 		for (s = buffer; s < p; ++s) {
     36 			if (*s == '\t') {
     37 				puts(tab_seq + ((*colp) & 07));
     38 				*colp += 8 - ((*colp) & 07);
     39 			} else {
     40 				++(*colp);
     41 				putc(*s);
     42 			}
     43 		}
     44 	} else {
     45 		puts(erase_seq);
     46 		(*colp)--;
     47 	}
     48 	(*np)--;
     49 
     50 	return p;
     51 }
     52 
     53 #ifdef CONFIG_CMDLINE_EDITING
     54 
     55 /*
     56  * cmdline-editing related codes from vivi.
     57  * Author: Janghoon Lyu <nandy (at) mizi.com>
     58  */
     59 
     60 #define putnstr(str, n)	printf("%.*s", (int)n, str)
     61 
     62 #define CTL_CH(c)		((c) - 'a' + 1)
     63 #define CTL_BACKSPACE		('\b')
     64 #define DEL			((char)255)
     65 #define DEL7			((char)127)
     66 #define CREAD_HIST_CHAR		('!')
     67 
     68 #define getcmd_putch(ch)	putc(ch)
     69 #define getcmd_getch()		getc()
     70 #define getcmd_cbeep()		getcmd_putch('\a')
     71 
     72 #define HIST_MAX		20
     73 #define HIST_SIZE		CONFIG_SYS_CBSIZE
     74 
     75 static int hist_max;
     76 static int hist_add_idx;
     77 static int hist_cur = -1;
     78 static unsigned hist_num;
     79 
     80 static char *hist_list[HIST_MAX];
     81 static char hist_lines[HIST_MAX][HIST_SIZE + 1];	/* Save room for NULL */
     82 
     83 #define add_idx_minus_one() ((hist_add_idx == 0) ? hist_max : hist_add_idx-1)
     84 
     85 static void hist_init(void)
     86 {
     87 	int i;
     88 
     89 	hist_max = 0;
     90 	hist_add_idx = 0;
     91 	hist_cur = -1;
     92 	hist_num = 0;
     93 
     94 	for (i = 0; i < HIST_MAX; i++) {
     95 		hist_list[i] = hist_lines[i];
     96 		hist_list[i][0] = '\0';
     97 	}
     98 }
     99 
    100 static void cread_add_to_hist(char *line)
    101 {
    102 	strcpy(hist_list[hist_add_idx], line);
    103 
    104 	if (++hist_add_idx >= HIST_MAX)
    105 		hist_add_idx = 0;
    106 
    107 	if (hist_add_idx > hist_max)
    108 		hist_max = hist_add_idx;
    109 
    110 	hist_num++;
    111 }
    112 
    113 static char *hist_prev(void)
    114 {
    115 	char *ret;
    116 	int old_cur;
    117 
    118 	if (hist_cur < 0)
    119 		return NULL;
    120 
    121 	old_cur = hist_cur;
    122 	if (--hist_cur < 0)
    123 		hist_cur = hist_max;
    124 
    125 	if (hist_cur == hist_add_idx) {
    126 		hist_cur = old_cur;
    127 		ret = NULL;
    128 	} else {
    129 		ret = hist_list[hist_cur];
    130 	}
    131 
    132 	return ret;
    133 }
    134 
    135 static char *hist_next(void)
    136 {
    137 	char *ret;
    138 
    139 	if (hist_cur < 0)
    140 		return NULL;
    141 
    142 	if (hist_cur == hist_add_idx)
    143 		return NULL;
    144 
    145 	if (++hist_cur > hist_max)
    146 		hist_cur = 0;
    147 
    148 	if (hist_cur == hist_add_idx)
    149 		ret = "";
    150 	else
    151 		ret = hist_list[hist_cur];
    152 
    153 	return ret;
    154 }
    155 
    156 #ifndef CONFIG_CMDLINE_EDITING
    157 static void cread_print_hist_list(void)
    158 {
    159 	int i;
    160 	unsigned long n;
    161 
    162 	n = hist_num - hist_max;
    163 
    164 	i = hist_add_idx + 1;
    165 	while (1) {
    166 		if (i > hist_max)
    167 			i = 0;
    168 		if (i == hist_add_idx)
    169 			break;
    170 		printf("%s\n", hist_list[i]);
    171 		n++;
    172 		i++;
    173 	}
    174 }
    175 #endif /* CONFIG_CMDLINE_EDITING */
    176 
    177 #define BEGINNING_OF_LINE() {			\
    178 	while (num) {				\
    179 		getcmd_putch(CTL_BACKSPACE);	\
    180 		num--;				\
    181 	}					\
    182 }
    183 
    184 #define ERASE_TO_EOL() {				\
    185 	if (num < eol_num) {				\
    186 		printf("%*s", (int)(eol_num - num), ""); \
    187 		do {					\
    188 			getcmd_putch(CTL_BACKSPACE);	\
    189 		} while (--eol_num > num);		\
    190 	}						\
    191 }
    192 
    193 #define REFRESH_TO_EOL() {			\
    194 	if (num < eol_num) {			\
    195 		wlen = eol_num - num;		\
    196 		putnstr(buf + num, wlen);	\
    197 		num = eol_num;			\
    198 	}					\
    199 }
    200 
    201 static void cread_add_char(char ichar, int insert, unsigned long *num,
    202 	       unsigned long *eol_num, char *buf, unsigned long len)
    203 {
    204 	unsigned long wlen;
    205 
    206 	/* room ??? */
    207 	if (insert || *num == *eol_num) {
    208 		if (*eol_num > len - 1) {
    209 			getcmd_cbeep();
    210 			return;
    211 		}
    212 		(*eol_num)++;
    213 	}
    214 
    215 	if (insert) {
    216 		wlen = *eol_num - *num;
    217 		if (wlen > 1)
    218 			memmove(&buf[*num+1], &buf[*num], wlen-1);
    219 
    220 		buf[*num] = ichar;
    221 		putnstr(buf + *num, wlen);
    222 		(*num)++;
    223 		while (--wlen)
    224 			getcmd_putch(CTL_BACKSPACE);
    225 	} else {
    226 		/* echo the character */
    227 		wlen = 1;
    228 		buf[*num] = ichar;
    229 		putnstr(buf + *num, wlen);
    230 		(*num)++;
    231 	}
    232 }
    233 
    234 static void cread_add_str(char *str, int strsize, int insert,
    235 			  unsigned long *num, unsigned long *eol_num,
    236 			  char *buf, unsigned long len)
    237 {
    238 	while (strsize--) {
    239 		cread_add_char(*str, insert, num, eol_num, buf, len);
    240 		str++;
    241 	}
    242 }
    243 
    244 static int cread_line(const char *const prompt, char *buf, unsigned int *len,
    245 		int timeout)
    246 {
    247 	unsigned long num = 0;
    248 	unsigned long eol_num = 0;
    249 	unsigned long wlen;
    250 	char ichar;
    251 	int insert = 1;
    252 	int esc_len = 0;
    253 	char esc_save[8];
    254 	int init_len = strlen(buf);
    255 	int first = 1;
    256 
    257 	if (init_len)
    258 		cread_add_str(buf, init_len, 1, &num, &eol_num, buf, *len);
    259 
    260 	while (1) {
    261 		if (bootretry_tstc_timeout())
    262 			return -2;	/* timed out */
    263 		if (first && timeout) {
    264 			uint64_t etime = endtick(timeout);
    265 
    266 			while (!tstc()) {	/* while no incoming data */
    267 				if (get_ticks() >= etime)
    268 					return -2;	/* timed out */
    269 				WATCHDOG_RESET();
    270 			}
    271 			first = 0;
    272 		}
    273 
    274 		ichar = getcmd_getch();
    275 
    276 		if ((ichar == '\n') || (ichar == '\r')) {
    277 			putc('\n');
    278 			break;
    279 		}
    280 
    281 		/*
    282 		 * handle standard linux xterm esc sequences for arrow key, etc.
    283 		 */
    284 		if (esc_len != 0) {
    285 			enum { ESC_REJECT, ESC_SAVE, ESC_CONVERTED } act = ESC_REJECT;
    286 
    287 			if (esc_len == 1) {
    288 				if (ichar == '[' || ichar == 'O')
    289 					act = ESC_SAVE;
    290 			} else if (esc_len == 2) {
    291 				switch (ichar) {
    292 				case 'D':	/* <- key */
    293 					ichar = CTL_CH('b');
    294 					act = ESC_CONVERTED;
    295 					break;	/* pass off to ^B handler */
    296 				case 'C':	/* -> key */
    297 					ichar = CTL_CH('f');
    298 					act = ESC_CONVERTED;
    299 					break;	/* pass off to ^F handler */
    300 				case 'H':	/* Home key */
    301 					ichar = CTL_CH('a');
    302 					act = ESC_CONVERTED;
    303 					break;	/* pass off to ^A handler */
    304 				case 'F':	/* End key */
    305 					ichar = CTL_CH('e');
    306 					act = ESC_CONVERTED;
    307 					break;	/* pass off to ^E handler */
    308 				case 'A':	/* up arrow */
    309 					ichar = CTL_CH('p');
    310 					act = ESC_CONVERTED;
    311 					break;	/* pass off to ^P handler */
    312 				case 'B':	/* down arrow */
    313 					ichar = CTL_CH('n');
    314 					act = ESC_CONVERTED;
    315 					break;	/* pass off to ^N handler */
    316 				case '1':
    317 				case '3':
    318 				case '4':
    319 				case '7':
    320 				case '8':
    321 					if (esc_save[1] == '[') {
    322 						/* see if next character is ~ */
    323 						act = ESC_SAVE;
    324 					}
    325 					break;
    326 				}
    327 			} else if (esc_len == 3) {
    328 				if (ichar == '~') {
    329 					switch (esc_save[2]) {
    330 					case '3':	/* Delete key */
    331 						ichar = CTL_CH('d');
    332 						act = ESC_CONVERTED;
    333 						break;	/* pass to ^D handler */
    334 					case '1':	/* Home key */
    335 					case '7':
    336 						ichar = CTL_CH('a');
    337 						act = ESC_CONVERTED;
    338 						break;	/* pass to ^A handler */
    339 					case '4':	/* End key */
    340 					case '8':
    341 						ichar = CTL_CH('e');
    342 						act = ESC_CONVERTED;
    343 						break;	/* pass to ^E handler */
    344 					}
    345 				}
    346 			}
    347 
    348 			switch (act) {
    349 			case ESC_SAVE:
    350 				esc_save[esc_len++] = ichar;
    351 				continue;
    352 			case ESC_REJECT:
    353 				esc_save[esc_len++] = ichar;
    354 				cread_add_str(esc_save, esc_len, insert,
    355 					      &num, &eol_num, buf, *len);
    356 				esc_len = 0;
    357 				continue;
    358 			case ESC_CONVERTED:
    359 				esc_len = 0;
    360 				break;
    361 			}
    362 		}
    363 
    364 		switch (ichar) {
    365 		case 0x1b:
    366 			if (esc_len == 0) {
    367 				esc_save[esc_len] = ichar;
    368 				esc_len = 1;
    369 			} else {
    370 				puts("impossible condition #876\n");
    371 				esc_len = 0;
    372 			}
    373 			break;
    374 
    375 		case CTL_CH('a'):
    376 			BEGINNING_OF_LINE();
    377 			break;
    378 		case CTL_CH('c'):	/* ^C - break */
    379 			*buf = '\0';	/* discard input */
    380 			return -1;
    381 		case CTL_CH('f'):
    382 			if (num < eol_num) {
    383 				getcmd_putch(buf[num]);
    384 				num++;
    385 			}
    386 			break;
    387 		case CTL_CH('b'):
    388 			if (num) {
    389 				getcmd_putch(CTL_BACKSPACE);
    390 				num--;
    391 			}
    392 			break;
    393 		case CTL_CH('d'):
    394 			if (num < eol_num) {
    395 				wlen = eol_num - num - 1;
    396 				if (wlen) {
    397 					memmove(&buf[num], &buf[num+1], wlen);
    398 					putnstr(buf + num, wlen);
    399 				}
    400 
    401 				getcmd_putch(' ');
    402 				do {
    403 					getcmd_putch(CTL_BACKSPACE);
    404 				} while (wlen--);
    405 				eol_num--;
    406 			}
    407 			break;
    408 		case CTL_CH('k'):
    409 			ERASE_TO_EOL();
    410 			break;
    411 		case CTL_CH('e'):
    412 			REFRESH_TO_EOL();
    413 			break;
    414 		case CTL_CH('o'):
    415 			insert = !insert;
    416 			break;
    417 		case CTL_CH('x'):
    418 		case CTL_CH('u'):
    419 			BEGINNING_OF_LINE();
    420 			ERASE_TO_EOL();
    421 			break;
    422 		case DEL:
    423 		case DEL7:
    424 		case 8:
    425 			if (num) {
    426 				wlen = eol_num - num;
    427 				num--;
    428 				memmove(&buf[num], &buf[num+1], wlen);
    429 				getcmd_putch(CTL_BACKSPACE);
    430 				putnstr(buf + num, wlen);
    431 				getcmd_putch(' ');
    432 				do {
    433 					getcmd_putch(CTL_BACKSPACE);
    434 				} while (wlen--);
    435 				eol_num--;
    436 			}
    437 			break;
    438 		case CTL_CH('p'):
    439 		case CTL_CH('n'):
    440 		{
    441 			char *hline;
    442 
    443 			esc_len = 0;
    444 
    445 			if (ichar == CTL_CH('p'))
    446 				hline = hist_prev();
    447 			else
    448 				hline = hist_next();
    449 
    450 			if (!hline) {
    451 				getcmd_cbeep();
    452 				continue;
    453 			}
    454 
    455 			/* nuke the current line */
    456 			/* first, go home */
    457 			BEGINNING_OF_LINE();
    458 
    459 			/* erase to end of line */
    460 			ERASE_TO_EOL();
    461 
    462 			/* copy new line into place and display */
    463 			strcpy(buf, hline);
    464 			eol_num = strlen(buf);
    465 			REFRESH_TO_EOL();
    466 			continue;
    467 		}
    468 #ifdef CONFIG_AUTO_COMPLETE
    469 		case '\t': {
    470 			int num2, col;
    471 
    472 			/* do not autocomplete when in the middle */
    473 			if (num < eol_num) {
    474 				getcmd_cbeep();
    475 				break;
    476 			}
    477 
    478 			buf[num] = '\0';
    479 			col = strlen(prompt) + eol_num;
    480 			num2 = num;
    481 			if (cmd_auto_complete(prompt, buf, &num2, &col)) {
    482 				col = num2 - num;
    483 				num += col;
    484 				eol_num += col;
    485 			}
    486 			break;
    487 		}
    488 #endif
    489 		default:
    490 			cread_add_char(ichar, insert, &num, &eol_num, buf,
    491 				       *len);
    492 			break;
    493 		}
    494 	}
    495 	*len = eol_num;
    496 	buf[eol_num] = '\0';	/* lose the newline */
    497 
    498 	if (buf[0] && buf[0] != CREAD_HIST_CHAR)
    499 		cread_add_to_hist(buf);
    500 	hist_cur = hist_add_idx;
    501 
    502 	return 0;
    503 }
    504 
    505 #endif /* CONFIG_CMDLINE_EDITING */
    506 
    507 /****************************************************************************/
    508 
    509 int cli_readline(const char *const prompt)
    510 {
    511 	/*
    512 	 * If console_buffer isn't 0-length the user will be prompted to modify
    513 	 * it instead of entering it from scratch as desired.
    514 	 */
    515 	console_buffer[0] = '\0';
    516 
    517 	return cli_readline_into_buffer(prompt, console_buffer, 0);
    518 }
    519 
    520 
    521 int cli_readline_into_buffer(const char *const prompt, char *buffer,
    522 			     int timeout)
    523 {
    524 	char *p = buffer;
    525 #ifdef CONFIG_CMDLINE_EDITING
    526 	unsigned int len = CONFIG_SYS_CBSIZE;
    527 	int rc;
    528 	static int initted;
    529 
    530 	/*
    531 	 * History uses a global array which is not
    532 	 * writable until after relocation to RAM.
    533 	 * Revert to non-history version if still
    534 	 * running from flash.
    535 	 */
    536 	if (gd->flags & GD_FLG_RELOC) {
    537 		if (!initted) {
    538 			hist_init();
    539 			initted = 1;
    540 		}
    541 
    542 		if (prompt)
    543 			puts(prompt);
    544 
    545 		rc = cread_line(prompt, p, &len, timeout);
    546 		return rc < 0 ? rc : len;
    547 
    548 	} else {
    549 #endif	/* CONFIG_CMDLINE_EDITING */
    550 	char *p_buf = p;
    551 	int	n = 0;				/* buffer index		*/
    552 	int	plen = 0;			/* prompt length	*/
    553 	int	col;				/* output column cnt	*/
    554 	char	c;
    555 
    556 	/* print prompt */
    557 	if (prompt) {
    558 		plen = strlen(prompt);
    559 		puts(prompt);
    560 	}
    561 	col = plen;
    562 
    563 	for (;;) {
    564 		if (bootretry_tstc_timeout())
    565 			return -2;	/* timed out */
    566 		WATCHDOG_RESET();	/* Trigger watchdog, if needed */
    567 
    568 #ifdef CONFIG_SHOW_ACTIVITY
    569 		while (!tstc()) {
    570 			show_activity(0);
    571 			WATCHDOG_RESET();
    572 		}
    573 #endif
    574 		c = getc();
    575 
    576 		/*
    577 		 * Special character handling
    578 		 */
    579 		switch (c) {
    580 		case '\r':			/* Enter		*/
    581 		case '\n':
    582 			*p = '\0';
    583 			puts("\r\n");
    584 			return p - p_buf;
    585 
    586 		case '\0':			/* nul			*/
    587 			continue;
    588 
    589 		case 0x03:			/* ^C - break		*/
    590 			p_buf[0] = '\0';	/* discard input */
    591 			return -1;
    592 
    593 		case 0x15:			/* ^U - erase line	*/
    594 			while (col > plen) {
    595 				puts(erase_seq);
    596 				--col;
    597 			}
    598 			p = p_buf;
    599 			n = 0;
    600 			continue;
    601 
    602 		case 0x17:			/* ^W - erase word	*/
    603 			p = delete_char(p_buf, p, &col, &n, plen);
    604 			while ((n > 0) && (*p != ' '))
    605 				p = delete_char(p_buf, p, &col, &n, plen);
    606 			continue;
    607 
    608 		case 0x08:			/* ^H  - backspace	*/
    609 		case 0x7F:			/* DEL - backspace	*/
    610 			p = delete_char(p_buf, p, &col, &n, plen);
    611 			continue;
    612 
    613 		default:
    614 			/*
    615 			 * Must be a normal character then
    616 			 */
    617 			if (n < CONFIG_SYS_CBSIZE-2) {
    618 				if (c == '\t') {	/* expand TABs */
    619 #ifdef CONFIG_AUTO_COMPLETE
    620 					/*
    621 					 * if auto completion triggered just
    622 					 * continue
    623 					 */
    624 					*p = '\0';
    625 					if (cmd_auto_complete(prompt,
    626 							      console_buffer,
    627 							      &n, &col)) {
    628 						p = p_buf + n;	/* reset */
    629 						continue;
    630 					}
    631 #endif
    632 					puts(tab_seq + (col & 07));
    633 					col += 8 - (col & 07);
    634 				} else {
    635 					char __maybe_unused buf[2];
    636 
    637 					/*
    638 					 * Echo input using puts() to force an
    639 					 * LCD flush if we are using an LCD
    640 					 */
    641 					++col;
    642 					buf[0] = c;
    643 					buf[1] = '\0';
    644 					puts(buf);
    645 				}
    646 				*p++ = c;
    647 				++n;
    648 			} else {			/* Buffer full */
    649 				putc('\a');
    650 			}
    651 		}
    652 	}
    653 #ifdef CONFIG_CMDLINE_EDITING
    654 	}
    655 #endif
    656 }
    657