blob: 4707559af304222231833255c646cc979fc442c2 [file] [log] [blame]
Tom Rini83d290c2018-05-06 17:58:06 -04001// SPDX-License-Identifier: GPL-2.0+
Pali Roháre7abe912013-03-23 14:53:08 +00002/*
Pali Rohár53086652020-04-01 00:35:08 +02003 * (C) Copyright 2011-2013 Pali Rohár <pali@kernel.org>
Pali Roháre7abe912013-03-23 14:53:08 +00004 */
5
Masahisa Kojimaa3d0aa82022-04-28 17:09:41 +09006#include <charset.h>
Pali Roháre7abe912013-03-23 14:53:08 +00007#include <common.h>
8#include <command.h>
9#include <ansi.h>
Simon Glass7b51b572019-08-01 09:46:52 -060010#include <env.h>
Simon Glassf7ae49f2020-05-10 11:40:05 -060011#include <log.h>
Pali Roháre7abe912013-03-23 14:53:08 +000012#include <menu.h>
Pali Roháre7abe912013-03-23 14:53:08 +000013#include <watchdog.h>
14#include <malloc.h>
Simon Glassc05ed002020-05-10 11:40:11 -060015#include <linux/delay.h>
Pali Roháre7abe912013-03-23 14:53:08 +000016#include <linux/string.h>
17
18/* maximum bootmenu entries */
19#define MAX_COUNT 99
20
21/* maximal size of bootmenu env
22 * 9 = strlen("bootmenu_")
23 * 2 = strlen(MAX_COUNT)
24 * 1 = NULL term
25 */
26#define MAX_ENV_SIZE (9 + 2 + 1)
27
Masahisa Kojimaa3d0aa82022-04-28 17:09:41 +090028enum boot_type {
29 BOOTMENU_TYPE_NONE = 0,
30 BOOTMENU_TYPE_BOOTMENU,
31};
32
Pali Roháre7abe912013-03-23 14:53:08 +000033struct bootmenu_entry {
34 unsigned short int num; /* unique number 0 .. MAX_COUNT */
35 char key[3]; /* key identifier of number */
Masahisa Kojimaa3d0aa82022-04-28 17:09:41 +090036 u16 *title; /* title of entry */
Pali Roháre7abe912013-03-23 14:53:08 +000037 char *command; /* hush command of entry */
Masahisa Kojimaa3d0aa82022-04-28 17:09:41 +090038 enum boot_type type; /* boot type of entry */
39 u16 bootorder; /* order for each boot type */
Pali Roháre7abe912013-03-23 14:53:08 +000040 struct bootmenu_data *menu; /* this bootmenu */
41 struct bootmenu_entry *next; /* next menu entry (num+1) */
42};
43
44struct bootmenu_data {
45 int delay; /* delay for autoboot */
46 int active; /* active menu entry */
47 int count; /* total count of menu entries */
48 struct bootmenu_entry *first; /* first menu entry */
49};
50
51enum bootmenu_key {
52 KEY_NONE = 0,
53 KEY_UP,
54 KEY_DOWN,
55 KEY_SELECT,
Pali Rohár83a287a2020-12-27 01:04:38 +010056 KEY_QUIT,
Pali Roháre7abe912013-03-23 14:53:08 +000057};
58
59static char *bootmenu_getoption(unsigned short int n)
60{
Lan Yixun (dlan)0eb33ad2013-06-27 18:58:53 +080061 char name[MAX_ENV_SIZE];
Pali Roháre7abe912013-03-23 14:53:08 +000062
63 if (n > MAX_COUNT)
64 return NULL;
65
Lan Yixun (dlan)0eb33ad2013-06-27 18:58:53 +080066 sprintf(name, "bootmenu_%d", n);
Simon Glass00caae62017-08-03 12:22:12 -060067 return env_get(name);
Pali Roháre7abe912013-03-23 14:53:08 +000068}
69
70static void bootmenu_print_entry(void *data)
71{
72 struct bootmenu_entry *entry = data;
73 int reverse = (entry->menu->active == entry->num);
74
75 /*
76 * Move cursor to line where the entry will be drown (entry->num)
77 * First 3 lines contain bootmenu header + 1 empty line
78 */
Heinrich Schuchardtd1d7ed72022-05-01 23:17:18 +020079 printf(ANSI_CURSOR_POSITION, entry->num + 4, 7);
Pali Roháre7abe912013-03-23 14:53:08 +000080
81 if (reverse)
82 puts(ANSI_COLOR_REVERSE);
83
Masahisa Kojimaa3d0aa82022-04-28 17:09:41 +090084 printf("%ls", entry->title);
Pali Roháre7abe912013-03-23 14:53:08 +000085
86 if (reverse)
87 puts(ANSI_COLOR_RESET);
88}
89
90static void bootmenu_autoboot_loop(struct bootmenu_data *menu,
91 enum bootmenu_key *key, int *esc)
92{
93 int i, c;
94
Pali Roháre7abe912013-03-23 14:53:08 +000095 while (menu->delay > 0) {
Heinrich Schuchardtd1d7ed72022-05-01 23:17:18 +020096 printf(ANSI_CURSOR_POSITION, menu->count + 5, 3);
97 printf("Hit any key to stop autoboot: %d ", menu->delay);
Pali Roháre7abe912013-03-23 14:53:08 +000098 for (i = 0; i < 100; ++i) {
99 if (!tstc()) {
100 WATCHDOG_RESET();
101 mdelay(10);
102 continue;
103 }
104
105 menu->delay = -1;
Heinrich Schuchardtc670aee2020-10-07 18:11:48 +0200106 c = getchar();
Pali Roháre7abe912013-03-23 14:53:08 +0000107
108 switch (c) {
109 case '\e':
110 *esc = 1;
111 *key = KEY_NONE;
112 break;
113 case '\r':
114 *key = KEY_SELECT;
115 break;
Pali Rohár83a287a2020-12-27 01:04:38 +0100116 case 0x3: /* ^C */
117 *key = KEY_QUIT;
118 break;
Pali Roháre7abe912013-03-23 14:53:08 +0000119 default:
120 *key = KEY_NONE;
121 break;
122 }
123
124 break;
125 }
126
127 if (menu->delay < 0)
128 break;
129
130 --menu->delay;
Pali Roháre7abe912013-03-23 14:53:08 +0000131 }
132
133 printf(ANSI_CURSOR_POSITION, menu->count + 5, 1);
134 puts(ANSI_CLEAR_LINE);
135
136 if (menu->delay == 0)
137 *key = KEY_SELECT;
138}
139
140static void bootmenu_loop(struct bootmenu_data *menu,
141 enum bootmenu_key *key, int *esc)
142{
143 int c;
144
Pali Rohár83a287a2020-12-27 01:04:38 +0100145 if (*esc == 1) {
146 if (tstc()) {
147 c = getchar();
148 } else {
149 WATCHDOG_RESET();
150 mdelay(10);
151 if (tstc())
152 c = getchar();
153 else
154 c = '\e';
155 }
156 } else {
157 while (!tstc()) {
158 WATCHDOG_RESET();
159 mdelay(10);
160 }
161 c = getchar();
Pali Roháre7abe912013-03-23 14:53:08 +0000162 }
163
Pali Roháre7abe912013-03-23 14:53:08 +0000164 switch (*esc) {
165 case 0:
166 /* First char of ANSI escape sequence '\e' */
167 if (c == '\e') {
168 *esc = 1;
169 *key = KEY_NONE;
170 }
171 break;
172 case 1:
173 /* Second char of ANSI '[' */
174 if (c == '[') {
175 *esc = 2;
176 *key = KEY_NONE;
177 } else {
Pali Rohár83a287a2020-12-27 01:04:38 +0100178 /* Alone ESC key was pressed */
179 *key = KEY_QUIT;
180 *esc = (c == '\e') ? 1 : 0;
Pali Roháre7abe912013-03-23 14:53:08 +0000181 }
182 break;
183 case 2:
184 case 3:
185 /* Third char of ANSI (number '1') - optional */
186 if (*esc == 2 && c == '1') {
187 *esc = 3;
188 *key = KEY_NONE;
189 break;
190 }
191
192 *esc = 0;
193
194 /* ANSI 'A' - key up was pressed */
195 if (c == 'A')
196 *key = KEY_UP;
197 /* ANSI 'B' - key down was pressed */
198 else if (c == 'B')
199 *key = KEY_DOWN;
200 /* other key was pressed */
201 else
202 *key = KEY_NONE;
203
204 break;
205 }
206
207 /* enter key was pressed */
208 if (c == '\r')
209 *key = KEY_SELECT;
Pali Rohár83a287a2020-12-27 01:04:38 +0100210
211 /* ^C was pressed */
212 if (c == 0x3)
213 *key = KEY_QUIT;
Pali Roháre7abe912013-03-23 14:53:08 +0000214}
215
216static char *bootmenu_choice_entry(void *data)
217{
218 struct bootmenu_data *menu = data;
219 struct bootmenu_entry *iter;
220 enum bootmenu_key key = KEY_NONE;
221 int esc = 0;
222 int i;
223
224 while (1) {
225 if (menu->delay >= 0) {
226 /* Autoboot was not stopped */
227 bootmenu_autoboot_loop(menu, &key, &esc);
228 } else {
229 /* Some key was pressed, so autoboot was stopped */
230 bootmenu_loop(menu, &key, &esc);
231 }
232
233 switch (key) {
234 case KEY_UP:
235 if (menu->active > 0)
236 --menu->active;
237 /* no menu key selected, regenerate menu */
238 return NULL;
239 case KEY_DOWN:
240 if (menu->active < menu->count - 1)
241 ++menu->active;
242 /* no menu key selected, regenerate menu */
243 return NULL;
244 case KEY_SELECT:
245 iter = menu->first;
246 for (i = 0; i < menu->active; ++i)
247 iter = iter->next;
248 return iter->key;
Pali Rohár83a287a2020-12-27 01:04:38 +0100249 case KEY_QUIT:
250 /* Quit by choosing the last entry - U-Boot console */
251 iter = menu->first;
252 while (iter->next)
253 iter = iter->next;
254 return iter->key;
Pali Roháre7abe912013-03-23 14:53:08 +0000255 default:
256 break;
257 }
258 }
259
260 /* never happens */
261 debug("bootmenu: this should not happen");
262 return NULL;
263}
264
265static void bootmenu_destroy(struct bootmenu_data *menu)
266{
267 struct bootmenu_entry *iter = menu->first;
268 struct bootmenu_entry *next;
269
270 while (iter) {
271 next = iter->next;
272 free(iter->title);
273 free(iter->command);
274 free(iter);
275 iter = next;
276 }
277 free(menu);
278}
279
Masahisa Kojimaa3d0aa82022-04-28 17:09:41 +0900280/**
281 * prepare_bootmenu_entry() - generate the bootmenu_xx entries
282 *
283 * This function read the "bootmenu_x" U-Boot environment variable
284 * and generate the bootmenu entries.
285 *
286 * @menu: pointer to the bootmenu structure
287 * @current: pointer to the last bootmenu entry list
288 * @index: pointer to the index of the last bootmenu entry,
289 * the number of bootmenu entry is added by this function
290 * Return: 1 on success, negative value on error
291 */
292static int prepare_bootmenu_entry(struct bootmenu_data *menu,
293 struct bootmenu_entry **current,
294 unsigned short int *index)
Pali Roháre7abe912013-03-23 14:53:08 +0000295{
Pali Roháre7abe912013-03-23 14:53:08 +0000296 int len;
297 char *sep;
Masahisa Kojimaa3d0aa82022-04-28 17:09:41 +0900298 const char *option;
299 unsigned short int i = *index;
300 struct bootmenu_entry *entry = NULL;
301 struct bootmenu_entry *iter = *current;
Frank Wunderlichf7bb20a2018-10-05 11:41:59 +0200302
Pali Roháre7abe912013-03-23 14:53:08 +0000303 while ((option = bootmenu_getoption(i))) {
Masahisa Kojimaa3d0aa82022-04-28 17:09:41 +0900304 u16 *buf;
305
Pali Roháre7abe912013-03-23 14:53:08 +0000306 sep = strchr(option, '=');
307 if (!sep) {
308 printf("Invalid bootmenu entry: %s\n", option);
309 break;
310 }
311
312 entry = malloc(sizeof(struct bootmenu_entry));
313 if (!entry)
Masahisa Kojimaa3d0aa82022-04-28 17:09:41 +0900314 return -ENOMEM;
Pali Roháre7abe912013-03-23 14:53:08 +0000315
316 len = sep-option;
Masahisa Kojimaa3d0aa82022-04-28 17:09:41 +0900317 buf = calloc(1, (len + 1) * sizeof(u16));
318 entry->title = buf;
Pali Roháre7abe912013-03-23 14:53:08 +0000319 if (!entry->title) {
320 free(entry);
Masahisa Kojimaa3d0aa82022-04-28 17:09:41 +0900321 return -ENOMEM;
Pali Roháre7abe912013-03-23 14:53:08 +0000322 }
Masahisa Kojimaa3d0aa82022-04-28 17:09:41 +0900323 utf8_utf16_strncpy(&buf, option, len);
Pali Roháre7abe912013-03-23 14:53:08 +0000324
325 len = strlen(sep + 1);
326 entry->command = malloc(len + 1);
327 if (!entry->command) {
328 free(entry->title);
329 free(entry);
Masahisa Kojimaa3d0aa82022-04-28 17:09:41 +0900330 return -ENOMEM;
Pali Roháre7abe912013-03-23 14:53:08 +0000331 }
332 memcpy(entry->command, sep + 1, len);
333 entry->command[len] = 0;
334
335 sprintf(entry->key, "%d", i);
336
337 entry->num = i;
338 entry->menu = menu;
Masahisa Kojimaa3d0aa82022-04-28 17:09:41 +0900339 entry->type = BOOTMENU_TYPE_BOOTMENU;
340 entry->bootorder = i;
Pali Roháre7abe912013-03-23 14:53:08 +0000341 entry->next = NULL;
342
343 if (!iter)
344 menu->first = entry;
345 else
346 iter->next = entry;
347
348 iter = entry;
349 ++i;
350
351 if (i == MAX_COUNT - 1)
352 break;
353 }
354
Masahisa Kojimaa3d0aa82022-04-28 17:09:41 +0900355 *index = i;
356 *current = iter;
357
358 return 1;
359}
360
361static struct bootmenu_data *bootmenu_create(int delay)
362{
363 int ret;
364 unsigned short int i = 0;
365 struct bootmenu_data *menu;
366 struct bootmenu_entry *iter = NULL;
367 struct bootmenu_entry *entry;
368 char *default_str;
369
370 menu = malloc(sizeof(struct bootmenu_data));
371 if (!menu)
372 return NULL;
373
374 menu->delay = delay;
375 menu->active = 0;
376 menu->first = NULL;
377
378 default_str = env_get("bootmenu_default");
379 if (default_str)
380 menu->active = (int)simple_strtol(default_str, NULL, 10);
381
382 ret = prepare_bootmenu_entry(menu, &iter, &i);
383 if (ret < 0)
384 goto cleanup;
385
Pali Roháre7abe912013-03-23 14:53:08 +0000386 /* Add U-Boot console entry at the end */
387 if (i <= MAX_COUNT - 1) {
388 entry = malloc(sizeof(struct bootmenu_entry));
389 if (!entry)
390 goto cleanup;
391
Masahisa Kojimaa3d0aa82022-04-28 17:09:41 +0900392 entry->title = u16_strdup(u"U-Boot console");
Pali Roháre7abe912013-03-23 14:53:08 +0000393 if (!entry->title) {
394 free(entry);
395 goto cleanup;
396 }
397
398 entry->command = strdup("");
399 if (!entry->command) {
400 free(entry->title);
401 free(entry);
402 goto cleanup;
403 }
404
405 sprintf(entry->key, "%d", i);
406
407 entry->num = i;
408 entry->menu = menu;
Masahisa Kojimaa3d0aa82022-04-28 17:09:41 +0900409 entry->type = BOOTMENU_TYPE_NONE;
Pali Roháre7abe912013-03-23 14:53:08 +0000410 entry->next = NULL;
411
412 if (!iter)
413 menu->first = entry;
414 else
415 iter->next = entry;
416
417 iter = entry;
418 ++i;
419 }
420
421 menu->count = i;
Frank Wunderlichbace2212018-12-03 11:23:41 +0100422
423 if ((menu->active >= menu->count)||(menu->active < 0)) { //ensure active menuitem is inside menu
424 printf("active menuitem (%d) is outside menu (0..%d)\n",menu->active,menu->count-1);
425 menu->active=0;
426 }
427
Pali Roháre7abe912013-03-23 14:53:08 +0000428 return menu;
429
430cleanup:
431 bootmenu_destroy(menu);
432 return NULL;
433}
434
Thirupathaiah Annapureddy5168d7a2020-03-18 11:38:42 -0700435static void menu_display_statusline(struct menu *m)
436{
437 struct bootmenu_entry *entry;
438 struct bootmenu_data *menu;
439
440 if (menu_default_choice(m, (void *)&entry) < 0)
441 return;
442
443 menu = entry->menu;
444
445 printf(ANSI_CURSOR_POSITION, 1, 1);
446 puts(ANSI_CLEAR_LINE);
Heinrich Schuchardtd1d7ed72022-05-01 23:17:18 +0200447 printf(ANSI_CURSOR_POSITION, 2, 3);
448 puts("*** U-Boot Boot Menu ***");
Thirupathaiah Annapureddy5168d7a2020-03-18 11:38:42 -0700449 puts(ANSI_CLEAR_LINE_TO_END);
450 printf(ANSI_CURSOR_POSITION, 3, 1);
451 puts(ANSI_CLEAR_LINE);
452
453 /* First 3 lines are bootmenu header + 2 empty lines between entries */
454 printf(ANSI_CURSOR_POSITION, menu->count + 5, 1);
455 puts(ANSI_CLEAR_LINE);
Heinrich Schuchardtd1d7ed72022-05-01 23:17:18 +0200456 printf(ANSI_CURSOR_POSITION, menu->count + 6, 3);
457 puts("Press UP/DOWN to move, ENTER to select, ESC/CTRL+C to quit");
Thirupathaiah Annapureddy5168d7a2020-03-18 11:38:42 -0700458 puts(ANSI_CLEAR_LINE_TO_END);
459 printf(ANSI_CURSOR_POSITION, menu->count + 7, 1);
460 puts(ANSI_CLEAR_LINE);
461}
462
Pali Roháre7abe912013-03-23 14:53:08 +0000463static void bootmenu_show(int delay)
464{
465 int init = 0;
466 void *choice = NULL;
Masahisa Kojimaa3d0aa82022-04-28 17:09:41 +0900467 u16 *title = NULL;
Pali Roháre7abe912013-03-23 14:53:08 +0000468 char *command = NULL;
469 struct menu *menu;
470 struct bootmenu_data *bootmenu;
471 struct bootmenu_entry *iter;
472 char *option, *sep;
473
474 /* If delay is 0 do not create menu, just run first entry */
475 if (delay == 0) {
476 option = bootmenu_getoption(0);
477 if (!option) {
478 puts("bootmenu option 0 was not found\n");
479 return;
480 }
481 sep = strchr(option, '=');
482 if (!sep) {
483 puts("bootmenu option 0 is invalid\n");
484 return;
485 }
486 run_command(sep+1, 0);
487 return;
488 }
489
490 bootmenu = bootmenu_create(delay);
491 if (!bootmenu)
492 return;
493
Thirupathaiah Annapureddy5168d7a2020-03-18 11:38:42 -0700494 menu = menu_create(NULL, bootmenu->delay, 1, menu_display_statusline,
495 bootmenu_print_entry, bootmenu_choice_entry,
496 bootmenu);
Pali Roháre7abe912013-03-23 14:53:08 +0000497 if (!menu) {
498 bootmenu_destroy(bootmenu);
499 return;
500 }
501
502 for (iter = bootmenu->first; iter; iter = iter->next) {
Masahisa Kojima990f6632022-03-24 22:54:33 +0900503 if (menu_item_add(menu, iter->key, iter) != 1)
Pali Roháre7abe912013-03-23 14:53:08 +0000504 goto cleanup;
505 }
506
507 /* Default menu entry is always first */
508 menu_default_set(menu, "0");
509
510 puts(ANSI_CURSOR_HIDE);
511 puts(ANSI_CLEAR_CONSOLE);
512 printf(ANSI_CURSOR_POSITION, 1, 1);
513
514 init = 1;
515
Masahisa Kojima990f6632022-03-24 22:54:33 +0900516 if (menu_get_choice(menu, &choice) == 1) {
Pali Roháre7abe912013-03-23 14:53:08 +0000517 iter = choice;
Masahisa Kojimaa3d0aa82022-04-28 17:09:41 +0900518 title = u16_strdup(iter->title);
Pali Roháre7abe912013-03-23 14:53:08 +0000519 command = strdup(iter->command);
520 }
521
522cleanup:
523 menu_destroy(menu);
524 bootmenu_destroy(bootmenu);
525
526 if (init) {
527 puts(ANSI_CURSOR_SHOW);
528 puts(ANSI_CLEAR_CONSOLE);
529 printf(ANSI_CURSOR_POSITION, 1, 1);
530 }
531
532 if (title && command) {
Masahisa Kojimaa3d0aa82022-04-28 17:09:41 +0900533 debug("Starting entry '%ls'\n", title);
Pali Roháre7abe912013-03-23 14:53:08 +0000534 free(title);
535 run_command(command, 0);
536 free(command);
537 }
538
539#ifdef CONFIG_POSTBOOTMENU
540 run_command(CONFIG_POSTBOOTMENU, 0);
541#endif
542}
543
Simon Glasse2313062019-07-20 20:51:24 -0600544#ifdef CONFIG_AUTOBOOT_MENU_SHOW
Pali Roháre7abe912013-03-23 14:53:08 +0000545int menu_show(int bootdelay)
546{
547 bootmenu_show(bootdelay);
548 return -1; /* -1 - abort boot and run monitor code */
549}
550#endif
551
Simon Glass09140112020-05-10 11:40:03 -0600552int do_bootmenu(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
Pali Roháre7abe912013-03-23 14:53:08 +0000553{
554 char *delay_str = NULL;
555 int delay = 10;
556
557#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
558 delay = CONFIG_BOOTDELAY;
559#endif
560
561 if (argc >= 2)
562 delay_str = argv[1];
563
564 if (!delay_str)
Simon Glass00caae62017-08-03 12:22:12 -0600565 delay_str = env_get("bootmenu_delay");
Pali Roháre7abe912013-03-23 14:53:08 +0000566
567 if (delay_str)
568 delay = (int)simple_strtol(delay_str, NULL, 10);
569
570 bootmenu_show(delay);
571 return 0;
572}
573
574U_BOOT_CMD(
575 bootmenu, 2, 1, do_bootmenu,
576 "ANSI terminal bootmenu",
577 "[delay]\n"
578 " - show ANSI terminal bootmenu with autoboot delay"
579);