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                        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
 474int 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
 486int 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