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