/***************************************************************************** * Boot menu: Displays boot devices and waits for user to select one * * Copyright 2017 Red Hat, Inc. * * This program and the accompanying materials * are made available under the terms of the BSD License * which accompanies this distribution, and is available at * http://www.opensource.org/licenses/bsd-license.php * * Contributors: * Thomas Huth, Red Hat Inc. - initial implementation *****************************************************************************/ #include #include #include #include #include #include #include "bootmenu.h" #define MAX_DEVS 36 /* Enough for 10 digits + 26 letters */ #define MAX_ALIAS_LEN 8 /* Maximum length of alias names */ struct bootdev { char alias[MAX_ALIAS_LEN]; char *path; }; static int nr_devs; static struct bootdev bootdevs[MAX_DEVS]; /** * Look up an alias name. * @return The NUL-terminated device tree path (should be released with free() * when it's not required anymore), or NULL if it can't be found. */ static char *find_alias(char *alias) { char *path; long len; forth_push((unsigned long)alias); forth_push(strlen(alias)); forth_eval("find-alias"); len = forth_pop(); if (!len) return NULL; path = malloc(len + 1); if (!path) { puts("Out of memory in find_alias"); return NULL; } memcpy(path, (void *)forth_pop(), len); path[len] = '\0'; return path; } static void bootmenu_populate_devs_alias(const char *alias) { int idx; for (idx = 0; idx <= 9 && nr_devs < MAX_DEVS; idx++, nr_devs++) { char *cur_alias = bootdevs[nr_devs].alias; if (idx == 0) strcpy(cur_alias, alias); else sprintf(cur_alias, "%s%i", alias, idx); bootdevs[nr_devs].path = find_alias(cur_alias); if (!bootdevs[nr_devs].path) break; } } static void bootmenu_populate_devs(void) { bootmenu_populate_devs_alias("cdrom"); bootmenu_populate_devs_alias("disk"); bootmenu_populate_devs_alias("net"); } static void bootmenu_free_devs(void) { while (nr_devs-- > 0) { free(bootdevs[nr_devs].path); bootdevs[nr_devs].path = NULL; } } static void bootmenu_show_devs(void) { int i; for (i = 0; i < nr_devs; i++) { printf("%c) %6s : %s\n", i < 9 ? '1' + i : 'a' + i - 9, bootdevs[i].alias, bootdevs[i].path); } } static bool has_key(void) { forth_eval("key?"); return forth_pop(); } static char get_key(void) { forth_eval("key"); return forth_pop(); } /* Flush pending key presses */ static void flush_keys(void) { uint32_t start; start = SLOF_GetTimer(); while (SLOF_GetTimer() - start < 10) { if (has_key()) { get_key(); start = SLOF_GetTimer(); } } } static int bootmenu_get_selection(void) { char key = 0; int sel; do { sel = -1; if (!has_key()) continue; key = get_key(); switch (key) { case '0': return -1; case '1' ... '9': sel = key - '1'; break; case 'a' ... 'z': sel = key - 'a' + 9; break; case 'A' ... 'Z': sel = key - 'A' + 9; break; default: /* Might be another escape code (F12) ... skip it */ flush_keys(); break; } } while (sel < 0 || sel >= nr_devs); return sel; } void bootmenu(void) { int sel; bootmenu_populate_devs(); if (!nr_devs) { puts("No available boot devices!"); return; } puts("\nSelect boot device (or press '0' to abort):"); bootmenu_show_devs(); if (has_key()) /* In case the user hammered on F12 */ flush_keys(); sel = bootmenu_get_selection(); if (sel < 0) { forth_push(0); } else { forth_push((unsigned long)bootdevs[sel].alias); forth_push(strlen(bootdevs[sel].alias)); } bootmenu_free_devs(); }