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