From patchwork Fri Feb 1 15:07:56 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Pali_Roh=C3=A1r?= X-Patchwork-Id: 217491 X-Patchwork-Delegate: agust@denx.de Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from theia.denx.de (theia.denx.de [85.214.87.163]) by ozlabs.org (Postfix) with ESMTP id A22022C0295 for ; Sat, 2 Feb 2013 02:15:34 +1100 (EST) Received: from localhost (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id 917924A121; Fri, 1 Feb 2013 16:15:31 +0100 (CET) X-Virus-Scanned: Debian amavisd-new at theia.denx.de Received: from theia.denx.de ([127.0.0.1]) by localhost (theia.denx.de [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id Tp78TiNwbnZK; Fri, 1 Feb 2013 16:15:31 +0100 (CET) Received: from theia.denx.de (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id AE6354A105; Fri, 1 Feb 2013 16:15:28 +0100 (CET) Received: from localhost (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id 6AB224A11D for ; Fri, 1 Feb 2013 16:15:25 +0100 (CET) X-Virus-Scanned: Debian amavisd-new at theia.denx.de Received: from theia.denx.de ([127.0.0.1]) by localhost (theia.denx.de [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id OJ7EEVezazYn for ; Fri, 1 Feb 2013 16:15:23 +0100 (CET) X-policyd-weight: NOT_IN_SBL_XBL_SPAMHAUS=-1.5 NOT_IN_SPAMCOP=-1.5 NOT_IN_BL_NJABL=-1.5 (only DNSBL check requested) Received: from mail-ea0-f180.google.com (mail-ea0-f180.google.com [209.85.215.180]) by theia.denx.de (Postfix) with ESMTPS id 373B24A0F2 for ; Fri, 1 Feb 2013 16:15:19 +0100 (CET) Received: by mail-ea0-f180.google.com with SMTP id c1so1727971eaa.11 for ; Fri, 01 Feb 2013 07:15:19 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=x-received:from:to:cc:subject:date:message-id:x-mailer:in-reply-to :references:mime-version:content-type:content-transfer-encoding; bh=6pO+zNVGyQeMOJ0pJCqfg0jJExRp2YqUm8NRiqZWAX8=; b=vt9tI1M/URl2gtuwrxbEoTFe1w49UKvRtBI6Mm0RGX2IEIJEsC+pwvvzOefEBvxoOZ xq42u4gVxiOC47KSotaSqOvjBMuSL25dRd//jRI0tfi7MCrBhoWDTQmOkQvNy8154WZw dVHf0Jhg71WIZ2DsHQHF/s9uf3Angt9AaHgOLuezLrNwKhLbkaWsYt36P5sZnyBqvq94 qNYHpk4WYcY/Jr/uJtt+MDeL+Kp5q5FRI19lCWa0Knrcp04Tu85fX+WCgfSuA1NG8ce+ bB1Qvb1VVL4ioryHxdLe7vr7dUjK2XOpeKo7eL1/L+Ua2Q1ZdTwolL2zydSSOr0ZyBS/ ZzkQ== X-Received: by 10.14.1.195 with SMTP id 43mr39994449eed.31.1359731331881; Fri, 01 Feb 2013 07:08:51 -0800 (PST) Received: from Pali-EliteBook.kolej.mff.cuni.cz (pali.kolej.mff.cuni.cz. [78.128.193.202]) by mx.google.com with ESMTPS id f49sm12938576eep.12.2013.02.01.07.08.50 (version=TLSv1.1 cipher=ECDHE-RSA-RC4-SHA bits=128/128); Fri, 01 Feb 2013 07:08:50 -0800 (PST) From: =?UTF-8?q?Pali=20Roh=C3=A1r?= To: u-boot@lists.denx.de Date: Fri, 1 Feb 2013 16:07:56 +0100 Message-Id: <1359731279-22431-3-git-send-email-pali.rohar@gmail.com> X-Mailer: git-send-email 1.7.10.4 In-Reply-To: <1359731279-22431-1-git-send-email-pali.rohar@gmail.com> References: <1338136729-3907-1-git-send-email-pali.rohar@gmail.com> <1359731279-22431-1-git-send-email-pali.rohar@gmail.com> MIME-Version: 1.0 Cc: =?UTF-8?q?Pali=20Roh=C3=A1r?= Subject: [U-Boot] [PATCH v3 2/4] New command bootmenu: ANSI terminal Boot Menu support X-BeenThere: u-boot@lists.denx.de X-Mailman-Version: 2.1.11 Precedence: list List-Id: U-Boot discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: u-boot-bounces@lists.denx.de Errors-To: u-boot-bounces@lists.denx.de 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 --- 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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +/* 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 + * + * 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_: + + bootmenu_delay= + bootmenu_="=<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.rohar@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"