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