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