This patch adding ANSI terminal bootmenu command. It is extension to generic menu which provide output for ANSI terminals.
Signed-off-by: Pali Rohár <pali.ro...@gmail.com> --- Changes in v3: - Do not use hardcoded numbers, added MAX_COUNT and MAX_ENV_SIZE - Use unsigned short int for menu number - Use enum bootmenu_key for key selection - Separate loop code from function bootmenu_choice_entry to bootmenu_loop and bootmenu_autoboot_loop - Updated README, added example - Use switches, added braces, fix style problems Changes in v2: - Added commit message - Removed bootmenu from include/config_cmd_all.h - Moved ANSI escape codes from include/common.h to include/ansi.h - Fixed style and indentation problems - Use mdelay instead udelay - Removed autoboot delay message when some key is pressed common/Makefile | 1 + common/cmd_bootmenu.c | 515 +++++++++++++++++++++++++++++++++++++++++++++++++ doc/README.bootmenu | 86 +++++++++ include/ansi.h | 42 ++++ 4 files changed, 644 insertions(+) create mode 100644 common/cmd_bootmenu.c create mode 100644 doc/README.bootmenu create mode 100644 include/ansi.h diff --git a/common/Makefile b/common/Makefile index 54fcc81..0bd82a9 100644 --- a/common/Makefile +++ b/common/Makefile @@ -71,6 +71,7 @@ COBJS-$(CONFIG_CMD_SOURCE) += cmd_source.o COBJS-$(CONFIG_CMD_BDI) += cmd_bdinfo.o COBJS-$(CONFIG_CMD_BEDBUG) += bedbug.o cmd_bedbug.o COBJS-$(CONFIG_CMD_BMP) += cmd_bmp.o +COBJS-$(CONFIG_CMD_BOOTMENU) += cmd_bootmenu.o COBJS-$(CONFIG_CMD_BOOTLDR) += cmd_bootldr.o COBJS-$(CONFIG_CMD_BOOTSTAGE) += cmd_bootstage.o COBJS-$(CONFIG_CMD_CACHE) += cmd_cache.o diff --git a/common/cmd_bootmenu.c b/common/cmd_bootmenu.c new file mode 100644 index 0000000..b4787ee --- /dev/null +++ b/common/cmd_bootmenu.c @@ -0,0 +1,515 @@ +/* + * (C) Copyright 2011-2013 Pali Rohár <pali.ro...@gmail.com> + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <common.h> +#include <command.h> +#include <ansi.h> +#include <menu.h> +#include <hush.h> +#include <watchdog.h> +#include <malloc.h> +#include <linux/string.h> + +/* maximum bootmenu entries */ +#define MAX_COUNT 99 + +/* maximal size of bootmenu env + * 9 = strlen("bootmenu_") + * 2 = strlen(MAX_COUNT) + * 1 = NULL term + */ +#define MAX_ENV_SIZE (9+2+1) + +struct bootmenu_entry { + unsigned short int num; /* unique number 0 .. MAX_COUNT */ + char key[3]; /* key identifier of number */ + char *title; /* title of entry */ + char *command; /* hush command of entry */ + struct bootmenu_data *menu; /* this bootmenu */ + struct bootmenu_entry *next; /* next menu entry (num+1) */ +}; + +struct bootmenu_data { + int delay; /* delay for autoboot */ + int active; /* active menu entry */ + int count; /* total count of menu entries */ + struct bootmenu_entry *first; /* first menu entry */ +}; + +enum bootmenu_key { + KEY_NONE = 0, + KEY_UP, + KEY_DOWN, + KEY_SELECT, +}; + +static char *bootmenu_getoption(unsigned short int n) +{ + char name[MAX_ENV_SIZE] = "bootmenu_"; + + if (n > MAX_COUNT) + return NULL; + + sprintf(name+9, "%d", n); + return getenv(name); +} + +static void bootmenu_print_entry(void *data) +{ + struct bootmenu_entry *entry = data; + int reverse = (entry->menu->active == entry->num); + + /* + * Move cursor to line where entry will be drown (entry->num) + * First 3 lines contains Bootmenu header + 1 empty line + */ + printf(ANSI_CURSOR_POSITION, entry->num + 4, 1); + + if (reverse) + puts(ANSI_COLOR_REVERSE); + + puts(" "); + puts(entry->title); + puts(ANSI_CLEAR_LINE_TO_END); + + if (reverse) + puts(ANSI_COLOR_RESET); +} + +static void bootmenu_autoboot_loop(struct bootmenu_data *menu, + enum bootmenu_key *key, int *esc) +{ + int i, c; + + if (menu->delay > 0) { + printf(ANSI_CURSOR_POSITION, menu->count+5, 1); + printf(" Hit any key to stop autoboot: %2d ", + menu->delay); + } + + while (menu->delay > 0) { + + for (i = 0; i < 100; ++i) { + + if (!tstc()) { + WATCHDOG_RESET(); + mdelay(10); + continue; + } + + menu->delay = -1; + c = getc(); + + switch (c) { + case '\e': + *esc = 1; + *key = KEY_NONE; + break; + case '\r': + *key = KEY_SELECT; + break; + default: + *key = KEY_NONE; + break; + } + + break; + + } + + if (menu->delay < 0) + break; + + --menu->delay; + printf("\b\b\b%2d ", menu->delay); + + } + + printf(ANSI_CURSOR_POSITION, menu->count+5, 1); + puts(ANSI_CLEAR_LINE); + + if (menu->delay == 0) + *key = KEY_SELECT; +} + +static void bootmenu_loop(struct bootmenu_data *menu, + enum bootmenu_key *key, int *esc) +{ + int c; + + while (!tstc()) { + WATCHDOG_RESET(); + mdelay(10); + } + + c = getc(); + + switch (*esc) { + + case 0: + /* First char of ANSI escape sequence '\e' */ + if (c == '\e') { + *esc = 1; + *key = KEY_NONE; + } + break; + + case 1: + /* Second char of ANSI '[' */ + if (c == '[') { + *esc = 2; + *key = KEY_NONE; + } else + *esc = 0; + break; + + case 2: + case 3: + /* Third char of ANSI (number '1') - optional */ + if (*esc == 2 && c == '1') { + *esc = 3; + *key = KEY_NONE; + break; + } + + *esc = 0; + + /* ANSI 'A' - key up was pressed */ + if (c == 'A') + *key = KEY_UP; + /* ANSI 'B' - key down was pressed */ + else if (c == 'B') + *key = KEY_DOWN; + /* other key was pressed */ + else + *key = KEY_NONE; + + break; + + } + + /* enter key was pressed */ + if (c == '\r') + *key = KEY_SELECT; +} + +static char *bootmenu_choice_entry(void *data) +{ + struct bootmenu_data *menu = data; + struct bootmenu_entry *iter; + enum bootmenu_key key = KEY_NONE; + int esc = 0; + int i; + + while (1) { + + /* Autoboot was not stopped */ + if (menu->delay >= 0) { + bootmenu_autoboot_loop(menu, &key, &esc); + /* Some key was pressed, so autoboot was stopped */ + } else { + bootmenu_loop(menu, &key, &esc); + } + + switch (key) { + case KEY_UP: + if (menu->active > 0) + --menu->active; + return ""; /* invalid menu entry, regenerate menu */ + case KEY_DOWN: + if (menu->active < menu->count-1) + ++menu->active; + return ""; /* invalid menu entry, regenerate menu */ + case KEY_SELECT: + iter = menu->first; + for (i = 0; i < menu->active; ++i) + iter = iter->next; + return iter->key; + default: + break; + } + + } + + /* never happends */ + debug("bootmenu: this should not happen"); + return NULL; +} + +static void bootmenu_destroy(struct bootmenu_data *menu) +{ + struct bootmenu_entry *iter = menu->first; + struct bootmenu_entry *next; + while (iter) { + next = iter->next; + free(iter->title); + free(iter->command); + free(iter); + iter = next; + } + free(menu); +} + +static struct bootmenu_data *bootmenu_create(int delay) +{ + unsigned short int i = 0; + const char *option; + struct bootmenu_data *menu; + struct bootmenu_entry *iter = NULL; + + int len; + char *sep; + struct bootmenu_entry *entry; + + menu = malloc(sizeof(struct bootmenu_data)); + if (!menu) + return NULL; + + menu->delay = delay; + menu->active = 0; + menu->first = NULL; + + while ((option = bootmenu_getoption(i))) { + + sep = strchr(option, '='); + if (!sep) + break; + + entry = malloc(sizeof(struct bootmenu_entry)); + if (!entry) + goto cleanup; + + len = sep-option; + entry->title = malloc(len+1); + if (!entry->title) { + free(entry); + goto cleanup; + } + memcpy(entry->title, option, len); + entry->title[len] = 0; + + len = strlen(sep+1); + entry->command = malloc(len+1); + if (!entry->command) { + free(entry->title); + free(entry); + goto cleanup; + } + memcpy(entry->command, sep+1, len); + entry->command[len] = 0; + + sprintf(entry->key, "%d", i); + + entry->num = i; + entry->menu = menu; + entry->next = NULL; + + if (!iter) + menu->first = entry; + else + iter->next = entry; + + iter = entry; + ++i; + + if (i > MAX_COUNT) + break; + + } + + /* Add U-Boot console entry at the end */ + if (i <= MAX_COUNT) { + entry = malloc(sizeof(struct bootmenu_entry)); + if (!entry) + goto cleanup; + + entry->title = strdup("U-Boot console"); + if (!entry->title) { + free(entry); + goto cleanup; + } + + entry->command = strdup(""); + if (!entry->command) { + free(entry->title); + free(entry); + goto cleanup; + } + + entry->num = i; + entry->menu = menu; + entry->next = NULL; + + if (!iter) + menu->first = entry; + else + iter->next = entry; + + iter = entry; + ++i; + + } + + menu->count = i; + return menu; + +cleanup: + bootmenu_destroy(menu); + return NULL; +} + +static void bootmenu_show(int delay) +{ + int init = 0; + void *choice = NULL; + char *title = NULL; + char *command = NULL; + struct menu *menu; + struct bootmenu_data *bootmenu; + struct bootmenu_entry *iter; + char *option, *sep; + + /* If delay is 0 do not create menu, just run first entry */ + if (delay == 0) { + option = bootmenu_getoption(0); + if (!option) { + printf("bootmenu option 0 was not found\n"); + return; + } + sep = strchr(option, '='); + if (!sep) { + printf("bootmenu option 0 is invalid\n"); + return; + } + run_command(sep+1, 0); + return; + } + + bootmenu = bootmenu_create(delay); + if (!bootmenu) + return; + + menu = menu_create(NULL, bootmenu->delay, (bootmenu->delay >= 0), + bootmenu_print_entry, bootmenu_choice_entry, bootmenu); + if (!menu) + return; + + for (iter = bootmenu->first; iter; iter = iter->next) { + if (!menu_item_add(menu, iter->key, iter)) + goto cleanup; + } + + /* Default menu entry is always first */ + menu_default_set(menu, "0"); + + puts(ANSI_CURSOR_HIDE); + puts(ANSI_CLEAR_CONSOLE); + printf(ANSI_CURSOR_POSITION, 1, 1); + + init = 1; + + if (menu_get_choice(menu, &choice)) { + iter = choice; + title = strdup(iter->title); + command = strdup(iter->command); + } + +cleanup: + menu_destroy(menu); + bootmenu_destroy(bootmenu); + + if (init) { + puts(ANSI_CURSOR_SHOW); + puts(ANSI_CLEAR_CONSOLE); + printf(ANSI_CURSOR_POSITION, 1, 1); + } + + if (title && command) { + printf("Starting entry '%s'\n", title); + free(title); + run_command(command, 0); + free(command); + } + +#ifdef CONFIG_POSTBOOTMENU + run_command(CONFIG_POSTBOOTMENU, 0); +#endif +} + +void menu_display_statusline(void *data) +{ + struct bootmenu_data *menu = data; + + printf(ANSI_CURSOR_POSITION, 1, 1); + puts(ANSI_CLEAR_LINE); + printf(ANSI_CURSOR_POSITION, 2, 1); + puts(" *** U-Boot BOOT MENU ***"); + puts(ANSI_CLEAR_LINE_TO_END); + printf(ANSI_CURSOR_POSITION, 3, 1); + puts(ANSI_CLEAR_LINE); + + /* First 3 lines are bootmenu header + 2 empty lines between entries */ + printf(ANSI_CURSOR_POSITION, menu->count + 5, 1); + puts(ANSI_CLEAR_LINE); + printf(ANSI_CURSOR_POSITION, menu->count + 6, 1); + puts(" Press UP/DOWN to move, ENTER to select"); + puts(ANSI_CLEAR_LINE_TO_END); + printf(ANSI_CURSOR_POSITION, menu->count + 7, 1); + puts(ANSI_CLEAR_LINE); +} + +#ifdef CONFIG_MENU_SHOW +int menu_show(int bootdelay) +{ + bootmenu_show(bootdelay); + return -1; /* -1 - abort boot and run monitor code */ +} +#endif + +int do_bootmenu(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[]) +{ + char *delay_str = NULL; + int delay = 10; + +#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0) + delay = CONFIG_BOOTDELAY; +#endif + + if (argc >= 2) + delay_str = argv[1]; + + if (!delay_str) + delay_str = getenv("bootmenu_delay"); + + if (delay_str) + delay = (int)simple_strtol(delay_str, NULL, 10); + + bootmenu_show(delay); + return 0; +} + +U_BOOT_CMD( + bootmenu, 2, 1, do_bootmenu, + "ANSI terminal bootmenu", + "[delay]\n" + " - show ANSI terminal bootmenu with autoboot delay" +); diff --git a/doc/README.bootmenu b/doc/README.bootmenu new file mode 100644 index 0000000..71a9aa6 --- /dev/null +++ b/doc/README.bootmenu @@ -0,0 +1,86 @@ +/* + * (C) Copyright 2011-2012 Pali Rohár <pali.ro...@gmail.com> + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +This is ANSI terminal bootmenu command. It is extension to generic menu, +which provide output for ANSI terminals. + +Configuration is done via env variables bootmenu_delay and bootmenu_<num>: + + bootmenu_delay=<delay> + bootmenu_<num>="<title>=<commands>" + + (title and commands are separated by first char '=') + + <delay> is delay in seconds of autobooting first entry + <num> is boot menu entry, starting from zero + <title> is text shown in boot screen + <commands> are commands which will be executed when menu entry is selected + +First argument of bootmenu command is delay and override env bootmenu_delay +If env bootmenu_delay or bootmenu arg is not specified, default delay is +CONFIG_BOOTDELAY. If delay is 0, no entry will be shown on screen and first +will be called. If delay is less then 0, bootmenu will be shown and autoboot +will be disabled. Bootmenu always add menu entry "U-Boot console" at end of +all entries. + + +Example using: + + setenv bootmenu_0 Boot 1. kernel=bootm 0x82000000 # Set first menu entry + setenv bootmenu_1 Boot 2. kernel=bootm 0x83000000 # Set second menu entry + setenv bootmenu_2 Reset board=reset # Set third menu entry + setenv bootmenu_3 U-Boot boot order=boot # Set fourth menu entry + setenv bootmenu_4 # Empty string is end of all bootmenu entries + bootmenu 20 # Run bootmenu with autoboot delay 20s + + +This example will be rendered as: + +-------------------------------------------- +| | +| *** U-Boot BOOT MENU *** | +| | +|#####Boot 1. kernel#######################| +| Boot 2. kernel | +| Reset board | +| U-Boot boot order | +| U-Boot console | +| | +| Hit any key to stop autoboot: 20 | +| Press UP/DOWN to move, ENTER to select | +| | +-------------------------------------------- + +(selected line is highlighted - has inverted background and text colors) + + +To enable ANSI bootmenu command add these definitions to board code: + + #define CONFIG_CMD_BOOTMENU + #define CONFIG_MENU + + +To run ANSI bootmenu at startup add these additional definitions: + + #define CONFIG_AUTOBOOT_KEYED + #define CONFIG_BOOTDELAY 30 + #define CONFIG_MENU_SHOW diff --git a/include/ansi.h b/include/ansi.h new file mode 100644 index 0000000..0e40b1d --- /dev/null +++ b/include/ansi.h @@ -0,0 +1,42 @@ +/* + * (C) Copyright 2012 + * Pali Rohár <pali.ro...@gmail.com> + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +/* + * ANSI terminal + */ + +#define ANSI_CURSOR_UP "\e[%dA" +#define ANSI_CURSOR_DOWN "\e[%dB" +#define ANSI_CURSOR_FORWARD "\e[%dC" +#define ANSI_CURSOR_BACK "\e[%dD" +#define ANSI_CURSOR_NEXTLINE "\e[%dE" +#define ANSI_CURSOR_PREVIOUSLINE "\e[%dF" +#define ANSI_CURSOR_COLUMN "\e[%dG" +#define ANSI_CURSOR_POSITION "\e[%d;%dH" +#define ANSI_CURSOR_SHOW "\e[?25h" +#define ANSI_CURSOR_HIDE "\e[?25l" +#define ANSI_CLEAR_CONSOLE "\e[2J" +#define ANSI_CLEAR_LINE_TO_END "\e[0K" +#define ANSI_CLEAR_LINE "\e[2K" +#define ANSI_COLOR_RESET "\e[0m" +#define ANSI_COLOR_REVERSE "\e[7m" -- 1.7.10.4 _______________________________________________ U-Boot mailing list U-Boot@lists.denx.de http://lists.denx.de/mailman/listinfo/u-boot