blob: 6ef7a3e564254233c9d08dd29a950f4040f707ce [file] [log] [blame]
Tom Rini10e47792018-05-06 17:58:06 -04001// SPDX-License-Identifier: GPL-2.0+
Simon Glass66b8c8b2014-04-10 20:01:26 -06002/*
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>
Simon Glass66b8c8b2014-04-10 20:01:26 -06009 */
10
11#include <common.h>
Simon Glass399ed9a2014-04-10 20:01:30 -060012#include <bootretry.h>
Simon Glass66b8c8b2014-04-10 20:01:26 -060013#include <cli.h>
Simon Glass495a5dc2019-11-14 12:57:30 -070014#include <time.h>
Simon Glass66b8c8b2014-04-10 20:01:26 -060015#include <watchdog.h>
16
17DECLARE_GLOBAL_DATA_PTR;
18
19static const char erase_seq[] = "\b \b"; /* erase sequence */
20static const char tab_seq[] = " "; /* used to expand TABs */
21
Simon Glass66b8c8b2014-04-10 20:01:26 -060022char console_buffer[CONFIG_SYS_CBSIZE + 1]; /* console I/O buffer */
23
Simon Glass66b8c8b2014-04-10 20:01:26 -060024static 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
76static int hist_max;
77static int hist_add_idx;
78static int hist_cur = -1;
79static unsigned hist_num;
80
81static char *hist_list[HIST_MAX];
82static 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
86static 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
101static 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
114static 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
136static 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
158static 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
202static 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
235static 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
245static 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) {
Simon Glass399ed9a2014-04-10 20:01:30 -0600262 if (bootretry_tstc_timeout())
263 return -2; /* timed out */
Simon Glass66b8c8b2014-04-10 20:01:26 -0600264 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
Patrick Delaunayf0017d22018-08-03 13:38:45 +0200277 /* ichar=0x0 when error occurs in U-Boot getc */
278 if (!ichar)
279 continue;
280
Simon Glass66b8c8b2014-04-10 20:01:26 -0600281 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) {
James Byrnee0aea632016-08-16 18:16:28 +0100290 enum { ESC_REJECT, ESC_SAVE, ESC_CONVERTED } act = ESC_REJECT;
291
Simon Glass66b8c8b2014-04-10 20:01:26 -0600292 if (esc_len == 1) {
James Byrnee0aea632016-08-16 18:16:28 +0100293 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;
Simon Glass66b8c8b2014-04-10 20:01:26 -0600331 }
James Byrnee0aea632016-08-16 18:16:28 +0100332 } 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 }
Simon Glass66b8c8b2014-04-10 20:01:26 -0600351 }
352
James Byrnee0aea632016-08-16 18:16:28 +0100353 switch (act) {
354 case ESC_SAVE:
355 esc_save[esc_len++] = ichar;
356 continue;
357 case ESC_REJECT:
Simon Glass66b8c8b2014-04-10 20:01:26 -0600358 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;
James Byrnee0aea632016-08-16 18:16:28 +0100363 case ESC_CONVERTED:
364 esc_len = 0;
365 break;
Simon Glass66b8c8b2014-04-10 20:01:26 -0600366 }
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] && buf[0] != CREAD_HIST_CHAR)
504 cread_add_to_hist(buf);
505 hist_cur = hist_add_idx;
506
507 return 0;
508}
509
510#endif /* CONFIG_CMDLINE_EDITING */
511
512/****************************************************************************/
513
Simon Glassbe6aafc2014-04-10 20:01:27 -0600514int cli_readline(const char *const prompt)
Simon Glass66b8c8b2014-04-10 20:01:26 -0600515{
516 /*
517 * If console_buffer isn't 0-length the user will be prompted to modify
518 * it instead of entering it from scratch as desired.
519 */
520 console_buffer[0] = '\0';
521
Simon Glassbe6aafc2014-04-10 20:01:27 -0600522 return cli_readline_into_buffer(prompt, console_buffer, 0);
Simon Glass66b8c8b2014-04-10 20:01:26 -0600523}
524
525
Simon Glassbe6aafc2014-04-10 20:01:27 -0600526int cli_readline_into_buffer(const char *const prompt, char *buffer,
527 int timeout)
Simon Glass66b8c8b2014-04-10 20:01:26 -0600528{
529 char *p = buffer;
530#ifdef CONFIG_CMDLINE_EDITING
531 unsigned int len = CONFIG_SYS_CBSIZE;
532 int rc;
533 static int initted;
534
535 /*
536 * History uses a global array which is not
537 * writable until after relocation to RAM.
538 * Revert to non-history version if still
539 * running from flash.
540 */
541 if (gd->flags & GD_FLG_RELOC) {
542 if (!initted) {
543 hist_init();
544 initted = 1;
545 }
546
547 if (prompt)
548 puts(prompt);
549
550 rc = cread_line(prompt, p, &len, timeout);
551 return rc < 0 ? rc : len;
552
553 } else {
554#endif /* CONFIG_CMDLINE_EDITING */
555 char *p_buf = p;
556 int n = 0; /* buffer index */
557 int plen = 0; /* prompt length */
558 int col; /* output column cnt */
559 char c;
560
561 /* print prompt */
562 if (prompt) {
563 plen = strlen(prompt);
564 puts(prompt);
565 }
566 col = plen;
567
568 for (;;) {
Simon Glass399ed9a2014-04-10 20:01:30 -0600569 if (bootretry_tstc_timeout())
570 return -2; /* timed out */
Simon Glass66b8c8b2014-04-10 20:01:26 -0600571 WATCHDOG_RESET(); /* Trigger watchdog, if needed */
572
Simon Glass66b8c8b2014-04-10 20:01:26 -0600573 c = getc();
574
575 /*
576 * Special character handling
577 */
578 switch (c) {
579 case '\r': /* Enter */
580 case '\n':
581 *p = '\0';
582 puts("\r\n");
583 return p - p_buf;
584
585 case '\0': /* nul */
586 continue;
587
588 case 0x03: /* ^C - break */
589 p_buf[0] = '\0'; /* discard input */
590 return -1;
591
592 case 0x15: /* ^U - erase line */
593 while (col > plen) {
594 puts(erase_seq);
595 --col;
596 }
597 p = p_buf;
598 n = 0;
599 continue;
600
601 case 0x17: /* ^W - erase word */
602 p = delete_char(p_buf, p, &col, &n, plen);
603 while ((n > 0) && (*p != ' '))
604 p = delete_char(p_buf, p, &col, &n, plen);
605 continue;
606
607 case 0x08: /* ^H - backspace */
608 case 0x7F: /* DEL - backspace */
609 p = delete_char(p_buf, p, &col, &n, plen);
610 continue;
611
612 default:
613 /*
614 * Must be a normal character then
615 */
616 if (n < CONFIG_SYS_CBSIZE-2) {
617 if (c == '\t') { /* expand TABs */
618#ifdef CONFIG_AUTO_COMPLETE
619 /*
620 * if auto completion triggered just
621 * continue
622 */
623 *p = '\0';
624 if (cmd_auto_complete(prompt,
625 console_buffer,
626 &n, &col)) {
627 p = p_buf + n; /* reset */
628 continue;
629 }
630#endif
631 puts(tab_seq + (col & 07));
632 col += 8 - (col & 07);
633 } else {
Heiko Schocher62cb1562015-06-29 09:10:46 +0200634 char __maybe_unused buf[2];
Simon Glass66b8c8b2014-04-10 20:01:26 -0600635
636 /*
637 * Echo input using puts() to force an
638 * LCD flush if we are using an LCD
639 */
640 ++col;
641 buf[0] = c;
642 buf[1] = '\0';
643 puts(buf);
644 }
645 *p++ = c;
646 ++n;
647 } else { /* Buffer full */
648 putc('\a');
649 }
650 }
651 }
652#ifdef CONFIG_CMDLINE_EDITING
653 }
654#endif
655}