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 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 474 int cli_readline(const char *const prompt) 475 { 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 482 return cli_readline_into_buffer(prompt, console_buffer, 0); 483 } 484 485 486 int cli_readline_into_buffer(const char *const prompt, char *buffer, 487 int timeout) 488 { 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 (;;) { 529 if (bootretry_tstc_timeout()) 530 return -2; /* timed out */ 531 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 { 600 char __maybe_unused buf[2]; 601 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 } 622