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 355 if ((menu->active >= menu->count)||(menu->active < 0)) { //ensure active menuitem is inside menu 356 printf("active menuitem (%d) is outside menu (0..%d)\n",menu->active,menu->count-1); 357 menu->active=0; 358 } 359 360 return menu; 361 362 cleanup: 363 bootmenu_destroy(menu); 364 return NULL; 365 } 366 367 static void bootmenu_show(int delay) 368 { 369 int init = 0; 370 void *choice = NULL; 371 char *title = NULL; 372 char *command = NULL; 373 struct menu *menu; 374 struct bootmenu_data *bootmenu; 375 struct bootmenu_entry *iter; 376 char *option, *sep; 377 378 /* If delay is 0 do not create menu, just run first entry */ 379 if (delay == 0) { 380 option = bootmenu_getoption(0); 381 if (!option) { 382 puts("bootmenu option 0 was not found\n"); 383 return; 384 } 385 sep = strchr(option, '='); 386 if (!sep) { 387 puts("bootmenu option 0 is invalid\n"); 388 return; 389 } 390 run_command(sep+1, 0); 391 return; 392 } 393 394 bootmenu = bootmenu_create(delay); 395 if (!bootmenu) 396 return; 397 398 menu = menu_create(NULL, bootmenu->delay, 1, bootmenu_print_entry, 399 bootmenu_choice_entry, bootmenu); 400 if (!menu) { 401 bootmenu_destroy(bootmenu); 402 return; 403 } 404 405 for (iter = bootmenu->first; iter; iter = iter->next) { 406 if (!menu_item_add(menu, iter->key, iter)) 407 goto cleanup; 408 } 409 410 /* Default menu entry is always first */ 411 menu_default_set(menu, "0"); 412 413 puts(ANSI_CURSOR_HIDE); 414 puts(ANSI_CLEAR_CONSOLE); 415 printf(ANSI_CURSOR_POSITION, 1, 1); 416 417 init = 1; 418 419 if (menu_get_choice(menu, &choice)) { 420 iter = choice; 421 title = strdup(iter->title); 422 command = strdup(iter->command); 423 } 424 425 cleanup: 426 menu_destroy(menu); 427 bootmenu_destroy(bootmenu); 428 429 if (init) { 430 puts(ANSI_CURSOR_SHOW); 431 puts(ANSI_CLEAR_CONSOLE); 432 printf(ANSI_CURSOR_POSITION, 1, 1); 433 } 434 435 if (title && command) { 436 debug("Starting entry '%s'\n", title); 437 free(title); 438 run_command(command, 0); 439 free(command); 440 } 441 442 #ifdef CONFIG_POSTBOOTMENU 443 run_command(CONFIG_POSTBOOTMENU, 0); 444 #endif 445 } 446 447 void menu_display_statusline(struct menu *m) 448 { 449 struct bootmenu_entry *entry; 450 struct bootmenu_data *menu; 451 452 if (menu_default_choice(m, (void *)&entry) < 0) 453 return; 454 455 menu = entry->menu; 456 457 printf(ANSI_CURSOR_POSITION, 1, 1); 458 puts(ANSI_CLEAR_LINE); 459 printf(ANSI_CURSOR_POSITION, 2, 1); 460 puts(" *** U-Boot Boot Menu ***"); 461 puts(ANSI_CLEAR_LINE_TO_END); 462 printf(ANSI_CURSOR_POSITION, 3, 1); 463 puts(ANSI_CLEAR_LINE); 464 465 /* First 3 lines are bootmenu header + 2 empty lines between entries */ 466 printf(ANSI_CURSOR_POSITION, menu->count + 5, 1); 467 puts(ANSI_CLEAR_LINE); 468 printf(ANSI_CURSOR_POSITION, menu->count + 6, 1); 469 puts(" Press UP/DOWN to move, ENTER to select"); 470 puts(ANSI_CLEAR_LINE_TO_END); 471 printf(ANSI_CURSOR_POSITION, menu->count + 7, 1); 472 puts(ANSI_CLEAR_LINE); 473 } 474 475 #ifdef CONFIG_MENU_SHOW 476 int menu_show(int bootdelay) 477 { 478 bootmenu_show(bootdelay); 479 return -1; /* -1 - abort boot and run monitor code */ 480 } 481 #endif 482 483 int do_bootmenu(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[]) 484 { 485 char *delay_str = NULL; 486 int delay = 10; 487 488 #if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0) 489 delay = CONFIG_BOOTDELAY; 490 #endif 491 492 if (argc >= 2) 493 delay_str = argv[1]; 494 495 if (!delay_str) 496 delay_str = env_get("bootmenu_delay"); 497 498 if (delay_str) 499 delay = (int)simple_strtol(delay_str, NULL, 10); 500 501 bootmenu_show(delay); 502 return 0; 503 } 504 505 U_BOOT_CMD( 506 bootmenu, 2, 1, do_bootmenu, 507 "ANSI terminal bootmenu", 508 "[delay]\n" 509 " - show ANSI terminal bootmenu with autoboot delay" 510 ); 511