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