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