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