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 <hang.h>
16#include <malloc.h>
17#include <time.h>
18#include <watchdog.h>
19#include <asm/global_data.h>
20
21DECLARE_GLOBAL_DATA_PTR;
22
23static const char erase_seq[] = "\b \b";	/* erase sequence */
24static const char   tab_seq[] = "        ";	/* used to expand TABs */
25
26char console_buffer[CONFIG_SYS_CBSIZE + 1];	/* console I/O buffer	*/
27
28static 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_SPL_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
84static int hist_max;
85static int hist_add_idx;
86static int hist_cur = -1;
87static unsigned hist_num;
88
89#ifndef CONFIG_CMD_HISTORY_USE_CALLOC
90static char hist_data[HIST_MAX][HIST_SIZE + 1];
91#endif
92static char *hist_list[HIST_MAX];
93
94#define add_idx_minus_one() ((hist_add_idx == 0) ? hist_max : hist_add_idx-1)
95
96static 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
104static 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
130static 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
143static 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
165static 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
186void 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
229static 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
262static 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
271int 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 - 1;
336			     base >= 0 && buf[base] == ' ';)
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
442void 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
456static 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
511static inline int hist_init(void)
512{
513	return 0;
514}
515
516static 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
526int 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 */
546static 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
646int 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