blob: c1476e42d95688cab1a1f9128d483027c4cfab09 [file] [log] [blame]
Simon Glass6493ccc2014-04-10 20:01:26 -06001/*
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>
Simon Glass0098e172014-04-10 20:01:30 -060013#include <bootretry.h>
Simon Glass6493ccc2014-04-10 20:01:26 -060014#include <cli.h>
15#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 Glass6493ccc2014-04-10 20:01:26 -060022char console_buffer[CONFIG_SYS_CBSIZE + 1]; /* console I/O buffer */
23
Simon Glass6493ccc2014-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 Glass0098e172014-04-10 20:01:30 -0600262 if (bootretry_tstc_timeout())
263 return -2; /* timed out */
Simon Glass6493ccc2014-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
277 if ((ichar == '\n') || (ichar == '\r')) {
278 putc('\n');
279 break;
280 }
281
282 /*
283 * handle standard linux xterm esc sequences for arrow key, etc.
284 */
285 if (esc_len != 0) {
286 if (esc_len == 1) {
287 if (ichar == '[') {
288 esc_save[esc_len] = ichar;
289 esc_len = 2;
290 } else {
291 cread_add_str(esc_save, esc_len,
292 insert, &num, &eol_num,
293 buf, *len);
294 esc_len = 0;
295 }
296 continue;
297 }
298
299 switch (ichar) {
300 case 'D': /* <- key */
301 ichar = CTL_CH('b');
302 esc_len = 0;
303 break;
304 case 'C': /* -> key */
305 ichar = CTL_CH('f');
306 esc_len = 0;
307 break; /* pass off to ^F handler */
308 case 'H': /* Home key */
309 ichar = CTL_CH('a');
310 esc_len = 0;
311 break; /* pass off to ^A handler */
312 case 'A': /* up arrow */
313 ichar = CTL_CH('p');
314 esc_len = 0;
315 break; /* pass off to ^P handler */
316 case 'B': /* down arrow */
317 ichar = CTL_CH('n');
318 esc_len = 0;
319 break; /* pass off to ^N handler */
320 default:
321 esc_save[esc_len++] = ichar;
322 cread_add_str(esc_save, esc_len, insert,
323 &num, &eol_num, buf, *len);
324 esc_len = 0;
325 continue;
326 }
327 }
328
329 switch (ichar) {
330 case 0x1b:
331 if (esc_len == 0) {
332 esc_save[esc_len] = ichar;
333 esc_len = 1;
334 } else {
335 puts("impossible condition #876\n");
336 esc_len = 0;
337 }
338 break;
339
340 case CTL_CH('a'):
341 BEGINNING_OF_LINE();
342 break;
343 case CTL_CH('c'): /* ^C - break */
344 *buf = '\0'; /* discard input */
345 return -1;
346 case CTL_CH('f'):
347 if (num < eol_num) {
348 getcmd_putch(buf[num]);
349 num++;
350 }
351 break;
352 case CTL_CH('b'):
353 if (num) {
354 getcmd_putch(CTL_BACKSPACE);
355 num--;
356 }
357 break;
358 case CTL_CH('d'):
359 if (num < eol_num) {
360 wlen = eol_num - num - 1;
361 if (wlen) {
362 memmove(&buf[num], &buf[num+1], wlen);
363 putnstr(buf + num, wlen);
364 }
365
366 getcmd_putch(' ');
367 do {
368 getcmd_putch(CTL_BACKSPACE);
369 } while (wlen--);
370 eol_num--;
371 }
372 break;
373 case CTL_CH('k'):
374 ERASE_TO_EOL();
375 break;
376 case CTL_CH('e'):
377 REFRESH_TO_EOL();
378 break;
379 case CTL_CH('o'):
380 insert = !insert;
381 break;
382 case CTL_CH('x'):
383 case CTL_CH('u'):
384 BEGINNING_OF_LINE();
385 ERASE_TO_EOL();
386 break;
387 case DEL:
388 case DEL7:
389 case 8:
390 if (num) {
391 wlen = eol_num - num;
392 num--;
393 memmove(&buf[num], &buf[num+1], wlen);
394 getcmd_putch(CTL_BACKSPACE);
395 putnstr(buf + num, wlen);
396 getcmd_putch(' ');
397 do {
398 getcmd_putch(CTL_BACKSPACE);
399 } while (wlen--);
400 eol_num--;
401 }
402 break;
403 case CTL_CH('p'):
404 case CTL_CH('n'):
405 {
406 char *hline;
407
408 esc_len = 0;
409
410 if (ichar == CTL_CH('p'))
411 hline = hist_prev();
412 else
413 hline = hist_next();
414
415 if (!hline) {
416 getcmd_cbeep();
417 continue;
418 }
419
420 /* nuke the current line */
421 /* first, go home */
422 BEGINNING_OF_LINE();
423
424 /* erase to end of line */
425 ERASE_TO_EOL();
426
427 /* copy new line into place and display */
428 strcpy(buf, hline);
429 eol_num = strlen(buf);
430 REFRESH_TO_EOL();
431 continue;
432 }
433#ifdef CONFIG_AUTO_COMPLETE
434 case '\t': {
435 int num2, col;
436
437 /* do not autocomplete when in the middle */
438 if (num < eol_num) {
439 getcmd_cbeep();
440 break;
441 }
442
443 buf[num] = '\0';
444 col = strlen(prompt) + eol_num;
445 num2 = num;
446 if (cmd_auto_complete(prompt, buf, &num2, &col)) {
447 col = num2 - num;
448 num += col;
449 eol_num += col;
450 }
451 break;
452 }
453#endif
454 default:
455 cread_add_char(ichar, insert, &num, &eol_num, buf,
456 *len);
457 break;
458 }
459 }
460 *len = eol_num;
461 buf[eol_num] = '\0'; /* lose the newline */
462
463 if (buf[0] && buf[0] != CREAD_HIST_CHAR)
464 cread_add_to_hist(buf);
465 hist_cur = hist_add_idx;
466
467 return 0;
468}
469
470#endif /* CONFIG_CMDLINE_EDITING */
471
472/****************************************************************************/
473
Simon Glasse1bf8242014-04-10 20:01:27 -0600474int cli_readline(const char *const prompt)
Simon Glass6493ccc2014-04-10 20:01:26 -0600475{
476 /*
477 * If console_buffer isn't 0-length the user will be prompted to modify
478 * it instead of entering it from scratch as desired.
479 */
480 console_buffer[0] = '\0';
481
Simon Glasse1bf8242014-04-10 20:01:27 -0600482 return cli_readline_into_buffer(prompt, console_buffer, 0);
Simon Glass6493ccc2014-04-10 20:01:26 -0600483}
484
485
Simon Glasse1bf8242014-04-10 20:01:27 -0600486int cli_readline_into_buffer(const char *const prompt, char *buffer,
487 int timeout)
Simon Glass6493ccc2014-04-10 20:01:26 -0600488{
489 char *p = buffer;
490#ifdef CONFIG_CMDLINE_EDITING
491 unsigned int len = CONFIG_SYS_CBSIZE;
492 int rc;
493 static int initted;
494
495 /*
496 * History uses a global array which is not
497 * writable until after relocation to RAM.
498 * Revert to non-history version if still
499 * running from flash.
500 */
501 if (gd->flags & GD_FLG_RELOC) {
502 if (!initted) {
503 hist_init();
504 initted = 1;
505 }
506
507 if (prompt)
508 puts(prompt);
509
510 rc = cread_line(prompt, p, &len, timeout);
511 return rc < 0 ? rc : len;
512
513 } else {
514#endif /* CONFIG_CMDLINE_EDITING */
515 char *p_buf = p;
516 int n = 0; /* buffer index */
517 int plen = 0; /* prompt length */
518 int col; /* output column cnt */
519 char c;
520
521 /* print prompt */
522 if (prompt) {
523 plen = strlen(prompt);
524 puts(prompt);
525 }
526 col = plen;
527
528 for (;;) {
Simon Glass0098e172014-04-10 20:01:30 -0600529 if (bootretry_tstc_timeout())
530 return -2; /* timed out */
Simon Glass6493ccc2014-04-10 20:01:26 -0600531 WATCHDOG_RESET(); /* Trigger watchdog, if needed */
532
533#ifdef CONFIG_SHOW_ACTIVITY
534 while (!tstc()) {
535 show_activity(0);
536 WATCHDOG_RESET();
537 }
538#endif
539 c = getc();
540
541 /*
542 * Special character handling
543 */
544 switch (c) {
545 case '\r': /* Enter */
546 case '\n':
547 *p = '\0';
548 puts("\r\n");
549 return p - p_buf;
550
551 case '\0': /* nul */
552 continue;
553
554 case 0x03: /* ^C - break */
555 p_buf[0] = '\0'; /* discard input */
556 return -1;
557
558 case 0x15: /* ^U - erase line */
559 while (col > plen) {
560 puts(erase_seq);
561 --col;
562 }
563 p = p_buf;
564 n = 0;
565 continue;
566
567 case 0x17: /* ^W - erase word */
568 p = delete_char(p_buf, p, &col, &n, plen);
569 while ((n > 0) && (*p != ' '))
570 p = delete_char(p_buf, p, &col, &n, plen);
571 continue;
572
573 case 0x08: /* ^H - backspace */
574 case 0x7F: /* DEL - backspace */
575 p = delete_char(p_buf, p, &col, &n, plen);
576 continue;
577
578 default:
579 /*
580 * Must be a normal character then
581 */
582 if (n < CONFIG_SYS_CBSIZE-2) {
583 if (c == '\t') { /* expand TABs */
584#ifdef CONFIG_AUTO_COMPLETE
585 /*
586 * if auto completion triggered just
587 * continue
588 */
589 *p = '\0';
590 if (cmd_auto_complete(prompt,
591 console_buffer,
592 &n, &col)) {
593 p = p_buf + n; /* reset */
594 continue;
595 }
596#endif
597 puts(tab_seq + (col & 07));
598 col += 8 - (col & 07);
599 } else {
Heiko Schocher80402f32015-06-29 09:10:46 +0200600 char __maybe_unused buf[2];
Simon Glass6493ccc2014-04-10 20:01:26 -0600601
602 /*
603 * Echo input using puts() to force an
604 * LCD flush if we are using an LCD
605 */
606 ++col;
607 buf[0] = c;
608 buf[1] = '\0';
609 puts(buf);
610 }
611 *p++ = c;
612 ++n;
613 } else { /* Buffer full */
614 putc('\a');
615 }
616 }
617 }
618#ifdef CONFIG_CMDLINE_EDITING
619 }
620#endif
621}