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