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