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