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