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