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