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