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