uboot/cmd/bootmenu.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * (C) Copyright 2011-2013 Pali Rohár <pali@kernel.org>
   4 */
   5
   6#include <common.h>
   7#include <command.h>
   8#include <ansi.h>
   9#include <env.h>
  10#include <log.h>
  11#include <menu.h>
  12#include <watchdog.h>
  13#include <malloc.h>
  14#include <linux/delay.h>
  15#include <linux/string.h>
  16
  17/* maximum bootmenu entries */
  18#define MAX_COUNT       99
  19
  20/* maximal size of bootmenu env
  21 *  9 = strlen("bootmenu_")
  22 *  2 = strlen(MAX_COUNT)
  23 *  1 = NULL term
  24 */
  25#define MAX_ENV_SIZE    (9 + 2 + 1)
  26
  27struct bootmenu_entry {
  28        unsigned short int num;         /* unique number 0 .. MAX_COUNT */
  29        char key[3];                    /* key identifier of number */
  30        char *title;                    /* title of entry */
  31        char *command;                  /* hush command of entry */
  32        struct bootmenu_data *menu;     /* this bootmenu */
  33        struct bootmenu_entry *next;    /* next menu entry (num+1) */
  34};
  35
  36struct bootmenu_data {
  37        int delay;                      /* delay for autoboot */
  38        int active;                     /* active menu entry */
  39        int count;                      /* total count of menu entries */
  40        struct bootmenu_entry *first;   /* first menu entry */
  41};
  42
  43enum bootmenu_key {
  44        KEY_NONE = 0,
  45        KEY_UP,
  46        KEY_DOWN,
  47        KEY_SELECT,
  48        KEY_QUIT,
  49};
  50
  51static char *bootmenu_getoption(unsigned short int n)
  52{
  53        char name[MAX_ENV_SIZE];
  54
  55        if (n > MAX_COUNT)
  56                return NULL;
  57
  58        sprintf(name, "bootmenu_%d", n);
  59        return env_get(name);
  60}
  61
  62static void bootmenu_print_entry(void *data)
  63{
  64        struct bootmenu_entry *entry = data;
  65        int reverse = (entry->menu->active == entry->num);
  66
  67        /*
  68         * Move cursor to line where the entry will be drown (entry->num)
  69         * First 3 lines contain bootmenu header + 1 empty line
  70         */
  71        printf(ANSI_CURSOR_POSITION, entry->num + 4, 1);
  72
  73        puts("     ");
  74
  75        if (reverse)
  76                puts(ANSI_COLOR_REVERSE);
  77
  78        puts(entry->title);
  79
  80        if (reverse)
  81                puts(ANSI_COLOR_RESET);
  82}
  83
  84static void bootmenu_autoboot_loop(struct bootmenu_data *menu,
  85                                enum bootmenu_key *key, int *esc)
  86{
  87        int i, c;
  88
  89        if (menu->delay > 0) {
  90                printf(ANSI_CURSOR_POSITION, menu->count + 5, 1);
  91                printf("  Hit any key to stop autoboot: %2d ", menu->delay);
  92        }
  93
  94        while (menu->delay > 0) {
  95                for (i = 0; i < 100; ++i) {
  96                        if (!tstc()) {
  97                                WATCHDOG_RESET();
  98                                mdelay(10);
  99                                continue;
 100                        }
 101
 102                        menu->delay = -1;
 103                        c = getchar();
 104
 105                        switch (c) {
 106                        case '\e':
 107                                *esc = 1;
 108                                *key = KEY_NONE;
 109                                break;
 110                        case '\r':
 111                                *key = KEY_SELECT;
 112                                break;
 113                        case 0x3: /* ^C */
 114                                *key = KEY_QUIT;
 115                                break;
 116                        default:
 117                                *key = KEY_NONE;
 118                                break;
 119                        }
 120
 121                        break;
 122                }
 123
 124                if (menu->delay < 0)
 125                        break;
 126
 127                --menu->delay;
 128                printf("\b\b\b%2d ", menu->delay);
 129        }
 130
 131        printf(ANSI_CURSOR_POSITION, menu->count + 5, 1);
 132        puts(ANSI_CLEAR_LINE);
 133
 134        if (menu->delay == 0)
 135                *key = KEY_SELECT;
 136}
 137
 138static void bootmenu_loop(struct bootmenu_data *menu,
 139                enum bootmenu_key *key, int *esc)
 140{
 141        int c;
 142
 143        if (*esc == 1) {
 144                if (tstc()) {
 145                        c = getchar();
 146                } else {
 147                        WATCHDOG_RESET();
 148                        mdelay(10);
 149                        if (tstc())
 150                                c = getchar();
 151                        else
 152                                c = '\e';
 153                }
 154        } else {
 155                while (!tstc()) {
 156                        WATCHDOG_RESET();
 157                        mdelay(10);
 158                }
 159                c = getchar();
 160        }
 161
 162        switch (*esc) {
 163        case 0:
 164                /* First char of ANSI escape sequence '\e' */
 165                if (c == '\e') {
 166                        *esc = 1;
 167                        *key = KEY_NONE;
 168                }
 169                break;
 170        case 1:
 171                /* Second char of ANSI '[' */
 172                if (c == '[') {
 173                        *esc = 2;
 174                        *key = KEY_NONE;
 175                } else {
 176                /* Alone ESC key was pressed */
 177                        *key = KEY_QUIT;
 178                        *esc = (c == '\e') ? 1 : 0;
 179                }
 180                break;
 181        case 2:
 182        case 3:
 183                /* Third char of ANSI (number '1') - optional */
 184                if (*esc == 2 && c == '1') {
 185                        *esc = 3;
 186                        *key = KEY_NONE;
 187                        break;
 188                }
 189
 190                *esc = 0;
 191
 192                /* ANSI 'A' - key up was pressed */
 193                if (c == 'A')
 194                        *key = KEY_UP;
 195                /* ANSI 'B' - key down was pressed */
 196                else if (c == 'B')
 197                        *key = KEY_DOWN;
 198                /* other key was pressed */
 199                else
 200                        *key = KEY_NONE;
 201
 202                break;
 203        }
 204
 205        /* enter key was pressed */
 206        if (c == '\r')
 207                *key = KEY_SELECT;
 208
 209        /* ^C was pressed */
 210        if (c == 0x3)
 211                *key = KEY_QUIT;
 212}
 213
 214static char *bootmenu_choice_entry(void *data)
 215{
 216        struct bootmenu_data *menu = data;
 217        struct bootmenu_entry *iter;
 218        enum bootmenu_key key = KEY_NONE;
 219        int esc = 0;
 220        int i;
 221
 222        while (1) {
 223                if (menu->delay >= 0) {
 224                        /* Autoboot was not stopped */
 225                        bootmenu_autoboot_loop(menu, &key, &esc);
 226                } else {
 227                        /* Some key was pressed, so autoboot was stopped */
 228                        bootmenu_loop(menu, &key, &esc);
 229                }
 230
 231                switch (key) {
 232                case KEY_UP:
 233                        if (menu->active > 0)
 234                                --menu->active;
 235                        /* no menu key selected, regenerate menu */
 236                        return NULL;
 237                case KEY_DOWN:
 238                        if (menu->active < menu->count - 1)
 239                                ++menu->active;
 240                        /* no menu key selected, regenerate menu */
 241                        return NULL;
 242                case KEY_SELECT:
 243                        iter = menu->first;
 244                        for (i = 0; i < menu->active; ++i)
 245                                iter = iter->next;
 246                        return iter->key;
 247                case KEY_QUIT:
 248                        /* Quit by choosing the last entry - U-Boot console */
 249                        iter = menu->first;
 250                        while (iter->next)
 251                                iter = iter->next;
 252                        return iter->key;
 253                default:
 254                        break;
 255                }
 256        }
 257
 258        /* never happens */
 259        debug("bootmenu: this should not happen");
 260        return NULL;
 261}
 262
 263static void bootmenu_destroy(struct bootmenu_data *menu)
 264{
 265        struct bootmenu_entry *iter = menu->first;
 266        struct bootmenu_entry *next;
 267
 268        while (iter) {
 269                next = iter->next;
 270                free(iter->title);
 271                free(iter->command);
 272                free(iter);
 273                iter = next;
 274        }
 275        free(menu);
 276}
 277
 278static struct bootmenu_data *bootmenu_create(int delay)
 279{
 280        unsigned short int i = 0;
 281        const char *option;
 282        struct bootmenu_data *menu;
 283        struct bootmenu_entry *iter = NULL;
 284
 285        int len;
 286        char *sep;
 287        char *default_str;
 288        struct bootmenu_entry *entry;
 289
 290        menu = malloc(sizeof(struct bootmenu_data));
 291        if (!menu)
 292                return NULL;
 293
 294        menu->delay = delay;
 295        menu->active = 0;
 296        menu->first = NULL;
 297
 298        default_str = env_get("bootmenu_default");
 299        if (default_str)
 300                menu->active = (int)simple_strtol(default_str, NULL, 10);
 301
 302        while ((option = bootmenu_getoption(i))) {
 303                sep = strchr(option, '=');
 304                if (!sep) {
 305                        printf("Invalid bootmenu entry: %s\n", option);
 306                        break;
 307                }
 308
 309                entry = malloc(sizeof(struct bootmenu_entry));
 310                if (!entry)
 311                        goto cleanup;
 312
 313                len = sep-option;
 314                entry->title = malloc(len + 1);
 315                if (!entry->title) {
 316                        free(entry);
 317                        goto cleanup;
 318                }
 319                memcpy(entry->title, option, len);
 320                entry->title[len] = 0;
 321
 322                len = strlen(sep + 1);
 323                entry->command = malloc(len + 1);
 324                if (!entry->command) {
 325                        free(entry->title);
 326                        free(entry);
 327                        goto cleanup;
 328                }
 329                memcpy(entry->command, sep + 1, len);
 330                entry->command[len] = 0;
 331
 332                sprintf(entry->key, "%d", i);
 333
 334                entry->num = i;
 335                entry->menu = menu;
 336                entry->next = NULL;
 337
 338                if (!iter)
 339                        menu->first = entry;
 340                else
 341                        iter->next = entry;
 342
 343                iter = entry;
 344                ++i;
 345
 346                if (i == MAX_COUNT - 1)
 347                        break;
 348        }
 349
 350        /* Add U-Boot console entry at the end */
 351        if (i <= MAX_COUNT - 1) {
 352                entry = malloc(sizeof(struct bootmenu_entry));
 353                if (!entry)
 354                        goto cleanup;
 355
 356                entry->title = strdup("U-Boot console");
 357                if (!entry->title) {
 358                        free(entry);
 359                        goto cleanup;
 360                }
 361
 362                entry->command = strdup("");
 363                if (!entry->command) {
 364                        free(entry->title);
 365                        free(entry);
 366                        goto cleanup;
 367                }
 368
 369                sprintf(entry->key, "%d", i);
 370
 371                entry->num = i;
 372                entry->menu = menu;
 373                entry->next = NULL;
 374
 375                if (!iter)
 376                        menu->first = entry;
 377                else
 378                        iter->next = entry;
 379
 380                iter = entry;
 381                ++i;
 382        }
 383
 384        menu->count = i;
 385
 386        if ((menu->active >= menu->count)||(menu->active < 0)) { //ensure active menuitem is inside menu
 387                printf("active menuitem (%d) is outside menu (0..%d)\n",menu->active,menu->count-1);
 388                menu->active=0;
 389        }
 390
 391        return menu;
 392
 393cleanup:
 394        bootmenu_destroy(menu);
 395        return NULL;
 396}
 397
 398static void menu_display_statusline(struct menu *m)
 399{
 400        struct bootmenu_entry *entry;
 401        struct bootmenu_data *menu;
 402
 403        if (menu_default_choice(m, (void *)&entry) < 0)
 404                return;
 405
 406        menu = entry->menu;
 407
 408        printf(ANSI_CURSOR_POSITION, 1, 1);
 409        puts(ANSI_CLEAR_LINE);
 410        printf(ANSI_CURSOR_POSITION, 2, 1);
 411        puts("  *** U-Boot Boot Menu ***");
 412        puts(ANSI_CLEAR_LINE_TO_END);
 413        printf(ANSI_CURSOR_POSITION, 3, 1);
 414        puts(ANSI_CLEAR_LINE);
 415
 416        /* First 3 lines are bootmenu header + 2 empty lines between entries */
 417        printf(ANSI_CURSOR_POSITION, menu->count + 5, 1);
 418        puts(ANSI_CLEAR_LINE);
 419        printf(ANSI_CURSOR_POSITION, menu->count + 6, 1);
 420        puts("  Press UP/DOWN to move, ENTER to select, ESC/CTRL+C to quit");
 421        puts(ANSI_CLEAR_LINE_TO_END);
 422        printf(ANSI_CURSOR_POSITION, menu->count + 7, 1);
 423        puts(ANSI_CLEAR_LINE);
 424}
 425
 426static void bootmenu_show(int delay)
 427{
 428        int init = 0;
 429        void *choice = NULL;
 430        char *title = NULL;
 431        char *command = NULL;
 432        struct menu *menu;
 433        struct bootmenu_data *bootmenu;
 434        struct bootmenu_entry *iter;
 435        char *option, *sep;
 436
 437        /* If delay is 0 do not create menu, just run first entry */
 438        if (delay == 0) {
 439                option = bootmenu_getoption(0);
 440                if (!option) {
 441                        puts("bootmenu option 0 was not found\n");
 442                        return;
 443                }
 444                sep = strchr(option, '=');
 445                if (!sep) {
 446                        puts("bootmenu option 0 is invalid\n");
 447                        return;
 448                }
 449                run_command(sep+1, 0);
 450                return;
 451        }
 452
 453        bootmenu = bootmenu_create(delay);
 454        if (!bootmenu)
 455                return;
 456
 457        menu = menu_create(NULL, bootmenu->delay, 1, menu_display_statusline,
 458                           bootmenu_print_entry, bootmenu_choice_entry,
 459                           bootmenu);
 460        if (!menu) {
 461                bootmenu_destroy(bootmenu);
 462                return;
 463        }
 464
 465        for (iter = bootmenu->first; iter; iter = iter->next) {
 466                if (!menu_item_add(menu, iter->key, iter))
 467                        goto cleanup;
 468        }
 469
 470        /* Default menu entry is always first */
 471        menu_default_set(menu, "0");
 472
 473        puts(ANSI_CURSOR_HIDE);
 474        puts(ANSI_CLEAR_CONSOLE);
 475        printf(ANSI_CURSOR_POSITION, 1, 1);
 476
 477        init = 1;
 478
 479        if (menu_get_choice(menu, &choice)) {
 480                iter = choice;
 481                title = strdup(iter->title);
 482                command = strdup(iter->command);
 483        }
 484
 485cleanup:
 486        menu_destroy(menu);
 487        bootmenu_destroy(bootmenu);
 488
 489        if (init) {
 490                puts(ANSI_CURSOR_SHOW);
 491                puts(ANSI_CLEAR_CONSOLE);
 492                printf(ANSI_CURSOR_POSITION, 1, 1);
 493        }
 494
 495        if (title && command) {
 496                debug("Starting entry '%s'\n", title);
 497                free(title);
 498                run_command(command, 0);
 499                free(command);
 500        }
 501
 502#ifdef CONFIG_POSTBOOTMENU
 503        run_command(CONFIG_POSTBOOTMENU, 0);
 504#endif
 505}
 506
 507#ifdef CONFIG_AUTOBOOT_MENU_SHOW
 508int menu_show(int bootdelay)
 509{
 510        bootmenu_show(bootdelay);
 511        return -1; /* -1 - abort boot and run monitor code */
 512}
 513#endif
 514
 515int do_bootmenu(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
 516{
 517        char *delay_str = NULL;
 518        int delay = 10;
 519
 520#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
 521        delay = CONFIG_BOOTDELAY;
 522#endif
 523
 524        if (argc >= 2)
 525                delay_str = argv[1];
 526
 527        if (!delay_str)
 528                delay_str = env_get("bootmenu_delay");
 529
 530        if (delay_str)
 531                delay = (int)simple_strtol(delay_str, NULL, 10);
 532
 533        bootmenu_show(delay);
 534        return 0;
 535}
 536
 537U_BOOT_CMD(
 538        bootmenu, 2, 1, do_bootmenu,
 539        "ANSI terminal bootmenu",
 540        "[delay]\n"
 541        "    - show ANSI terminal bootmenu with autoboot delay"
 542);
 543