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