Merge branch '2022-01-16-bootstd-updates'
[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                 }
288
289                 ichar = cli_ch_process(cch, ichar);
290
291                 /* ichar=0x0 when error occurs in U-Boot getc */
292                 if (!ichar)
293                         continue;
294
295                 if (ichar == '\n') {
296                         putc('\n');
297                         break;
298                 }
299
300                 switch (ichar) {
301                 case CTL_CH('a'):
302                         BEGINNING_OF_LINE();
303                         break;
304                 case CTL_CH('c'):       /* ^C - break */
305                         *buf = '\0';    /* discard input */
306                         return -1;
307                 case CTL_CH('f'):
308                         if (num < eol_num) {
309                                 getcmd_putch(buf[num]);
310                                 num++;
311                         }
312                         break;
313                 case CTL_CH('b'):
314                         if (num) {
315                                 getcmd_putch(CTL_BACKSPACE);
316                                 num--;
317                         }
318                         break;
319                 case CTL_CH('d'):
320                         if (num < eol_num) {
321                                 wlen = eol_num - num - 1;
322                                 if (wlen) {
323                                         memmove(&buf[num], &buf[num+1], wlen);
324                                         putnstr(buf + num, wlen);
325                                 }
326
327                                 getcmd_putch(' ');
328                                 do {
329                                         getcmd_putch(CTL_BACKSPACE);
330                                 } while (wlen--);
331                                 eol_num--;
332                         }
333                         break;
334                 case CTL_CH('k'):
335                         ERASE_TO_EOL();
336                         break;
337                 case CTL_CH('e'):
338                         REFRESH_TO_EOL();
339                         break;
340                 case CTL_CH('o'):
341                         insert = !insert;
342                         break;
343                 case CTL_CH('x'):
344                 case CTL_CH('u'):
345                         BEGINNING_OF_LINE();
346                         ERASE_TO_EOL();
347                         break;
348                 case DEL:
349                 case DEL7:
350                 case 8:
351                         if (num) {
352                                 wlen = eol_num - num;
353                                 num--;
354                                 memmove(&buf[num], &buf[num+1], wlen);
355                                 getcmd_putch(CTL_BACKSPACE);
356                                 putnstr(buf + num, wlen);
357                                 getcmd_putch(' ');
358                                 do {
359                                         getcmd_putch(CTL_BACKSPACE);
360                                 } while (wlen--);
361                                 eol_num--;
362                         }
363                         break;
364                 case CTL_CH('p'):
365                 case CTL_CH('n'):
366                 {
367                         char *hline;
368
369                         if (ichar == CTL_CH('p'))
370                                 hline = hist_prev();
371                         else
372                                 hline = hist_next();
373
374                         if (!hline) {
375                                 getcmd_cbeep();
376                                 continue;
377                         }
378
379                         /* nuke the current line */
380                         /* first, go home */
381                         BEGINNING_OF_LINE();
382
383                         /* erase to end of line */
384                         ERASE_TO_EOL();
385
386                         /* copy new line into place and display */
387                         strcpy(buf, hline);
388                         eol_num = strlen(buf);
389                         REFRESH_TO_EOL();
390                         continue;
391                 }
392 #ifdef CONFIG_AUTO_COMPLETE
393                 case '\t': {
394                         int num2, col;
395
396                         /* do not autocomplete when in the middle */
397                         if (num < eol_num) {
398                                 getcmd_cbeep();
399                                 break;
400                         }
401
402                         buf[num] = '\0';
403                         col = strlen(prompt) + eol_num;
404                         num2 = num;
405                         if (cmd_auto_complete(prompt, buf, &num2, &col)) {
406                                 col = num2 - num;
407                                 num += col;
408                                 eol_num += col;
409                         }
410                         break;
411                 }
412 #endif
413                 default:
414                         cread_add_char(ichar, insert, &num, &eol_num, buf,
415                                        *len);
416                         break;
417                 }
418         }
419         *len = eol_num;
420         buf[eol_num] = '\0';    /* lose the newline */
421
422         if (buf[0] && buf[0] != CREAD_HIST_CHAR)
423                 cread_add_to_hist(buf);
424         hist_cur = hist_add_idx;
425
426         return 0;
427 }
428
429 #endif /* CONFIG_CMDLINE_EDITING */
430
431 /****************************************************************************/
432
433 int cli_readline(const char *const prompt)
434 {
435         /*
436          * If console_buffer isn't 0-length the user will be prompted to modify
437          * it instead of entering it from scratch as desired.
438          */
439         console_buffer[0] = '\0';
440
441         return cli_readline_into_buffer(prompt, console_buffer, 0);
442 }
443
444
445 int cli_readline_into_buffer(const char *const prompt, char *buffer,
446                              int timeout)
447 {
448         char *p = buffer;
449 #ifdef CONFIG_CMDLINE_EDITING
450         unsigned int len = CONFIG_SYS_CBSIZE;
451         int rc;
452         static int initted;
453
454         /*
455          * History uses a global array which is not
456          * writable until after relocation to RAM.
457          * Revert to non-history version if still
458          * running from flash.
459          */
460         if (gd->flags & GD_FLG_RELOC) {
461                 if (!initted) {
462                         hist_init();
463                         initted = 1;
464                 }
465
466                 if (prompt)
467                         puts(prompt);
468
469                 rc = cread_line(prompt, p, &len, timeout);
470                 return rc < 0 ? rc : len;
471
472         } else {
473 #endif  /* CONFIG_CMDLINE_EDITING */
474         char *p_buf = p;
475         int     n = 0;                          /* buffer index         */
476         int     plen = 0;                       /* prompt length        */
477         int     col;                            /* output column cnt    */
478         char    c;
479
480         /* print prompt */
481         if (prompt) {
482                 plen = strlen(prompt);
483                 puts(prompt);
484         }
485         col = plen;
486
487         for (;;) {
488                 if (bootretry_tstc_timeout())
489                         return -2;      /* timed out */
490                 schedule();     /* Trigger watchdog, if needed */
491
492                 c = getchar();
493
494                 /*
495                  * Special character handling
496                  */
497                 switch (c) {
498                 case '\r':                      /* Enter                */
499                 case '\n':
500                         *p = '\0';
501                         puts("\r\n");
502                         return p - p_buf;
503
504                 case '\0':                      /* nul                  */
505                         continue;
506
507                 case 0x03:                      /* ^C - break           */
508                         p_buf[0] = '\0';        /* discard input */
509                         return -1;
510
511                 case 0x15:                      /* ^U - erase line      */
512                         while (col > plen) {
513                                 puts(erase_seq);
514                                 --col;
515                         }
516                         p = p_buf;
517                         n = 0;
518                         continue;
519
520                 case 0x17:                      /* ^W - erase word      */
521                         p = delete_char(p_buf, p, &col, &n, plen);
522                         while ((n > 0) && (*p != ' '))
523                                 p = delete_char(p_buf, p, &col, &n, plen);
524                         continue;
525
526                 case 0x08:                      /* ^H  - backspace      */
527                 case 0x7F:                      /* DEL - backspace      */
528                         p = delete_char(p_buf, p, &col, &n, plen);
529                         continue;
530
531                 default:
532                         /*
533                          * Must be a normal character then
534                          */
535                         if (n < CONFIG_SYS_CBSIZE-2) {
536                                 if (c == '\t') {        /* expand TABs */
537 #ifdef CONFIG_AUTO_COMPLETE
538                                         /*
539                                          * if auto completion triggered just
540                                          * continue
541                                          */
542                                         *p = '\0';
543                                         if (cmd_auto_complete(prompt,
544                                                               console_buffer,
545                                                               &n, &col)) {
546                                                 p = p_buf + n;  /* reset */
547                                                 continue;
548                                         }
549 #endif
550                                         puts(tab_seq + (col & 07));
551                                         col += 8 - (col & 07);
552                                 } else {
553                                         char __maybe_unused buf[2];
554
555                                         /*
556                                          * Echo input using puts() to force an
557                                          * LCD flush if we are using an LCD
558                                          */
559                                         ++col;
560                                         buf[0] = c;
561                                         buf[1] = '\0';
562                                         puts(buf);
563                                 }
564                                 *p++ = c;
565                                 ++n;
566                         } else {                        /* Buffer full */
567                                 putc('\a');
568                         }
569                 }
570         }
571 #ifdef CONFIG_CMDLINE_EDITING
572         }
573 #endif
574 }