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 struct bootmenu_entry *entry; 257 258 menu = malloc(sizeof(struct bootmenu_data)); 259 if (!menu) 260 return NULL; 261 262 menu->delay = delay; 263 menu->active = 0; 264 menu->first = NULL; 265 266 while ((option = bootmenu_getoption(i))) { 267 sep = strchr(option, '='); 268 if (!sep) { 269 printf("Invalid bootmenu entry: %s\n", option); 270 break; 271 } 272 273 entry = malloc(sizeof(struct bootmenu_entry)); 274 if (!entry) 275 goto cleanup; 276 277 len = sep-option; 278 entry->title = malloc(len + 1); 279 if (!entry->title) { 280 free(entry); 281 goto cleanup; 282 } 283 memcpy(entry->title, option, len); 284 entry->title[len] = 0; 285 286 len = strlen(sep + 1); 287 entry->command = malloc(len + 1); 288 if (!entry->command) { 289 free(entry->title); 290 free(entry); 291 goto cleanup; 292 } 293 memcpy(entry->command, sep + 1, len); 294 entry->command[len] = 0; 295 296 sprintf(entry->key, "%d", i); 297 298 entry->num = i; 299 entry->menu = menu; 300 entry->next = NULL; 301 302 if (!iter) 303 menu->first = entry; 304 else 305 iter->next = entry; 306 307 iter = entry; 308 ++i; 309 310 if (i == MAX_COUNT - 1) 311 break; 312 } 313 314 /* Add U-Boot console entry at the end */ 315 if (i <= MAX_COUNT - 1) { 316 entry = malloc(sizeof(struct bootmenu_entry)); 317 if (!entry) 318 goto cleanup; 319 320 entry->title = strdup("U-Boot console"); 321 if (!entry->title) { 322 free(entry); 323 goto cleanup; 324 } 325 326 entry->command = strdup(""); 327 if (!entry->command) { 328 free(entry->title); 329 free(entry); 330 goto cleanup; 331 } 332 333 sprintf(entry->key, "%d", i); 334 335 entry->num = i; 336 entry->menu = menu; 337 entry->next = NULL; 338 339 if (!iter) 340 menu->first = entry; 341 else 342 iter->next = entry; 343 344 iter = entry; 345 ++i; 346 } 347 348 menu->count = i; 349 return menu; 350 351 cleanup: 352 bootmenu_destroy(menu); 353 return NULL; 354 } 355 356 static void bootmenu_show(int delay) 357 { 358 int init = 0; 359 void *choice = NULL; 360 char *title = NULL; 361 char *command = NULL; 362 struct menu *menu; 363 struct bootmenu_data *bootmenu; 364 struct bootmenu_entry *iter; 365 char *option, *sep; 366 367 /* If delay is 0 do not create menu, just run first entry */ 368 if (delay == 0) { 369 option = bootmenu_getoption(0); 370 if (!option) { 371 puts("bootmenu option 0 was not found\n"); 372 return; 373 } 374 sep = strchr(option, '='); 375 if (!sep) { 376 puts("bootmenu option 0 is invalid\n"); 377 return; 378 } 379 run_command(sep+1, 0); 380 return; 381 } 382 383 bootmenu = bootmenu_create(delay); 384 if (!bootmenu) 385 return; 386 387 menu = menu_create(NULL, bootmenu->delay, 1, bootmenu_print_entry, 388 bootmenu_choice_entry, bootmenu); 389 if (!menu) { 390 bootmenu_destroy(bootmenu); 391 return; 392 } 393 394 for (iter = bootmenu->first; iter; iter = iter->next) { 395 if (!menu_item_add(menu, iter->key, iter)) 396 goto cleanup; 397 } 398 399 /* Default menu entry is always first */ 400 menu_default_set(menu, "0"); 401 402 puts(ANSI_CURSOR_HIDE); 403 puts(ANSI_CLEAR_CONSOLE); 404 printf(ANSI_CURSOR_POSITION, 1, 1); 405 406 init = 1; 407 408 if (menu_get_choice(menu, &choice)) { 409 iter = choice; 410 title = strdup(iter->title); 411 command = strdup(iter->command); 412 } 413 414 cleanup: 415 menu_destroy(menu); 416 bootmenu_destroy(bootmenu); 417 418 if (init) { 419 puts(ANSI_CURSOR_SHOW); 420 puts(ANSI_CLEAR_CONSOLE); 421 printf(ANSI_CURSOR_POSITION, 1, 1); 422 } 423 424 if (title && command) { 425 debug("Starting entry '%s'\n", title); 426 free(title); 427 run_command(command, 0); 428 free(command); 429 } 430 431 #ifdef CONFIG_POSTBOOTMENU 432 run_command(CONFIG_POSTBOOTMENU, 0); 433 #endif 434 } 435 436 void menu_display_statusline(struct menu *m) 437 { 438 struct bootmenu_entry *entry; 439 struct bootmenu_data *menu; 440 441 if (menu_default_choice(m, (void *)&entry) < 0) 442 return; 443 444 menu = entry->menu; 445 446 printf(ANSI_CURSOR_POSITION, 1, 1); 447 puts(ANSI_CLEAR_LINE); 448 printf(ANSI_CURSOR_POSITION, 2, 1); 449 puts(" *** U-Boot Boot Menu ***"); 450 puts(ANSI_CLEAR_LINE_TO_END); 451 printf(ANSI_CURSOR_POSITION, 3, 1); 452 puts(ANSI_CLEAR_LINE); 453 454 /* First 3 lines are bootmenu header + 2 empty lines between entries */ 455 printf(ANSI_CURSOR_POSITION, menu->count + 5, 1); 456 puts(ANSI_CLEAR_LINE); 457 printf(ANSI_CURSOR_POSITION, menu->count + 6, 1); 458 puts(" Press UP/DOWN to move, ENTER to select"); 459 puts(ANSI_CLEAR_LINE_TO_END); 460 printf(ANSI_CURSOR_POSITION, menu->count + 7, 1); 461 puts(ANSI_CLEAR_LINE); 462 } 463 464 #ifdef CONFIG_MENU_SHOW 465 int menu_show(int bootdelay) 466 { 467 bootmenu_show(bootdelay); 468 return -1; /* -1 - abort boot and run monitor code */ 469 } 470 #endif 471 472 int do_bootmenu(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[]) 473 { 474 char *delay_str = NULL; 475 int delay = 10; 476 477 #if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0) 478 delay = CONFIG_BOOTDELAY; 479 #endif 480 481 if (argc >= 2) 482 delay_str = argv[1]; 483 484 if (!delay_str) 485 delay_str = env_get("bootmenu_delay"); 486 487 if (delay_str) 488 delay = (int)simple_strtol(delay_str, NULL, 10); 489 490 bootmenu_show(delay); 491 return 0; 492 } 493 494 U_BOOT_CMD( 495 bootmenu, 2, 1, do_bootmenu, 496 "ANSI terminal bootmenu", 497 "[delay]\n" 498 " - show ANSI terminal bootmenu with autoboot delay" 499 ); 500