blob: fe356074728b7d67a26777f5eb21101676fb7e4c [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
6#include <common.h>
7#include <command.h>
8#include <ansi.h>
Simon Glass7b51b572019-08-01 09:46:52 -06009#include <env.h>
Simon Glassf7ae49f2020-05-10 11:40:05 -060010#include <log.h>
Pali Roháre7abe912013-03-23 14:53:08 +000011#include <menu.h>
Pali Roháre7abe912013-03-23 14:53:08 +000012#include <watchdog.h>
13#include <malloc.h>
Simon Glassc05ed002020-05-10 11:40:11 -060014#include <linux/delay.h>
Pali Roháre7abe912013-03-23 14:53:08 +000015#include <linux/string.h>
16
17/* maximum bootmenu entries */
18#define MAX_COUNT 99
19
20/* maximal size of bootmenu env
21 * 9 = strlen("bootmenu_")
22 * 2 = strlen(MAX_COUNT)
23 * 1 = NULL term
24 */
25#define MAX_ENV_SIZE (9 + 2 + 1)
26
27struct bootmenu_entry {
28 unsigned short int num; /* unique number 0 .. MAX_COUNT */
29 char key[3]; /* key identifier of number */
30 char *title; /* title of entry */
31 char *command; /* hush command of entry */
32 struct bootmenu_data *menu; /* this bootmenu */
33 struct bootmenu_entry *next; /* next menu entry (num+1) */
34};
35
36struct bootmenu_data {
37 int delay; /* delay for autoboot */
38 int active; /* active menu entry */
39 int count; /* total count of menu entries */
40 struct bootmenu_entry *first; /* first menu entry */
41};
42
43enum bootmenu_key {
44 KEY_NONE = 0,
45 KEY_UP,
46 KEY_DOWN,
47 KEY_SELECT,
Pali Rohár83a287a2020-12-27 01:04:38 +010048 KEY_QUIT,
Pali Roháre7abe912013-03-23 14:53:08 +000049};
50
51static char *bootmenu_getoption(unsigned short int n)
52{
Lan Yixun (dlan)0eb33ad2013-06-27 18:58:53 +080053 char name[MAX_ENV_SIZE];
Pali Roháre7abe912013-03-23 14:53:08 +000054
55 if (n > MAX_COUNT)
56 return NULL;
57
Lan Yixun (dlan)0eb33ad2013-06-27 18:58:53 +080058 sprintf(name, "bootmenu_%d", n);
Simon Glass00caae62017-08-03 12:22:12 -060059 return env_get(name);
Pali Roháre7abe912013-03-23 14:53:08 +000060}
61
62static void bootmenu_print_entry(void *data)
63{
64 struct bootmenu_entry *entry = data;
65 int reverse = (entry->menu->active == entry->num);
66
67 /*
68 * Move cursor to line where the entry will be drown (entry->num)
69 * First 3 lines contain bootmenu header + 1 empty line
70 */
Heinrich Schuchardtd1d7ed72022-05-01 23:17:18 +020071 printf(ANSI_CURSOR_POSITION, entry->num + 4, 7);
Pali Roháre7abe912013-03-23 14:53:08 +000072
73 if (reverse)
74 puts(ANSI_COLOR_REVERSE);
75
76 puts(entry->title);
77
78 if (reverse)
79 puts(ANSI_COLOR_RESET);
80}
81
82static void bootmenu_autoboot_loop(struct bootmenu_data *menu,
83 enum bootmenu_key *key, int *esc)
84{
85 int i, c;
86
Pali Roháre7abe912013-03-23 14:53:08 +000087 while (menu->delay > 0) {
Heinrich Schuchardtd1d7ed72022-05-01 23:17:18 +020088 printf(ANSI_CURSOR_POSITION, menu->count + 5, 3);
89 printf("Hit any key to stop autoboot: %d ", menu->delay);
Pali Roháre7abe912013-03-23 14:53:08 +000090 for (i = 0; i < 100; ++i) {
91 if (!tstc()) {
92 WATCHDOG_RESET();
93 mdelay(10);
94 continue;
95 }
96
97 menu->delay = -1;
Heinrich Schuchardtc670aee2020-10-07 18:11:48 +020098 c = getchar();
Pali Roháre7abe912013-03-23 14:53:08 +000099
100 switch (c) {
101 case '\e':
102 *esc = 1;
103 *key = KEY_NONE;
104 break;
105 case '\r':
106 *key = KEY_SELECT;
107 break;
Pali Rohár83a287a2020-12-27 01:04:38 +0100108 case 0x3: /* ^C */
109 *key = KEY_QUIT;
110 break;
Pali Roháre7abe912013-03-23 14:53:08 +0000111 default:
112 *key = KEY_NONE;
113 break;
114 }
115
116 break;
117 }
118
119 if (menu->delay < 0)
120 break;
121
122 --menu->delay;
Pali Roháre7abe912013-03-23 14:53:08 +0000123 }
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
132static void bootmenu_loop(struct bootmenu_data *menu,
133 enum bootmenu_key *key, int *esc)
134{
135 int c;
136
Pali Rohár83a287a2020-12-27 01:04:38 +0100137 if (*esc == 1) {
138 if (tstc()) {
139 c = getchar();
140 } else {
141 WATCHDOG_RESET();
142 mdelay(10);
143 if (tstc())
144 c = getchar();
145 else
146 c = '\e';
147 }
148 } else {
149 while (!tstc()) {
150 WATCHDOG_RESET();
151 mdelay(10);
152 }
153 c = getchar();
Pali Roháre7abe912013-03-23 14:53:08 +0000154 }
155
Pali Roháre7abe912013-03-23 14:53:08 +0000156 switch (*esc) {
157 case 0:
158 /* First char of ANSI escape sequence '\e' */
159 if (c == '\e') {
160 *esc = 1;
161 *key = KEY_NONE;
162 }
163 break;
164 case 1:
165 /* Second char of ANSI '[' */
166 if (c == '[') {
167 *esc = 2;
168 *key = KEY_NONE;
169 } else {
Pali Rohár83a287a2020-12-27 01:04:38 +0100170 /* Alone ESC key was pressed */
171 *key = KEY_QUIT;
172 *esc = (c == '\e') ? 1 : 0;
Pali Roháre7abe912013-03-23 14:53:08 +0000173 }
174 break;
175 case 2:
176 case 3:
177 /* Third char of ANSI (number '1') - optional */
178 if (*esc == 2 && c == '1') {
179 *esc = 3;
180 *key = KEY_NONE;
181 break;
182 }
183
184 *esc = 0;
185
186 /* ANSI 'A' - key up was pressed */
187 if (c == 'A')
188 *key = KEY_UP;
189 /* ANSI 'B' - key down was pressed */
190 else if (c == 'B')
191 *key = KEY_DOWN;
192 /* other key was pressed */
193 else
194 *key = KEY_NONE;
195
196 break;
197 }
198
199 /* enter key was pressed */
200 if (c == '\r')
201 *key = KEY_SELECT;
Pali Rohár83a287a2020-12-27 01:04:38 +0100202
203 /* ^C was pressed */
204 if (c == 0x3)
205 *key = KEY_QUIT;
Pali Roháre7abe912013-03-23 14:53:08 +0000206}
207
208static char *bootmenu_choice_entry(void *data)
209{
210 struct bootmenu_data *menu = data;
211 struct bootmenu_entry *iter;
212 enum bootmenu_key key = KEY_NONE;
213 int esc = 0;
214 int i;
215
216 while (1) {
217 if (menu->delay >= 0) {
218 /* Autoboot was not stopped */
219 bootmenu_autoboot_loop(menu, &key, &esc);
220 } else {
221 /* Some key was pressed, so autoboot was stopped */
222 bootmenu_loop(menu, &key, &esc);
223 }
224
225 switch (key) {
226 case KEY_UP:
227 if (menu->active > 0)
228 --menu->active;
229 /* no menu key selected, regenerate menu */
230 return NULL;
231 case KEY_DOWN:
232 if (menu->active < menu->count - 1)
233 ++menu->active;
234 /* no menu key selected, regenerate menu */
235 return NULL;
236 case KEY_SELECT:
237 iter = menu->first;
238 for (i = 0; i < menu->active; ++i)
239 iter = iter->next;
240 return iter->key;
Pali Rohár83a287a2020-12-27 01:04:38 +0100241 case KEY_QUIT:
242 /* Quit by choosing the last entry - U-Boot console */
243 iter = menu->first;
244 while (iter->next)
245 iter = iter->next;
246 return iter->key;
Pali Roháre7abe912013-03-23 14:53:08 +0000247 default:
248 break;
249 }
250 }
251
252 /* never happens */
253 debug("bootmenu: this should not happen");
254 return NULL;
255}
256
257static void bootmenu_destroy(struct bootmenu_data *menu)
258{
259 struct bootmenu_entry *iter = menu->first;
260 struct bootmenu_entry *next;
261
262 while (iter) {
263 next = iter->next;
264 free(iter->title);
265 free(iter->command);
266 free(iter);
267 iter = next;
268 }
269 free(menu);
270}
271
272static struct bootmenu_data *bootmenu_create(int delay)
273{
274 unsigned short int i = 0;
275 const char *option;
276 struct bootmenu_data *menu;
277 struct bootmenu_entry *iter = NULL;
278
279 int len;
280 char *sep;
Frank Wunderlichf7bb20a2018-10-05 11:41:59 +0200281 char *default_str;
Pali Roháre7abe912013-03-23 14:53:08 +0000282 struct bootmenu_entry *entry;
283
284 menu = malloc(sizeof(struct bootmenu_data));
285 if (!menu)
286 return NULL;
287
288 menu->delay = delay;
289 menu->active = 0;
290 menu->first = NULL;
291
Frank Wunderlichf7bb20a2018-10-05 11:41:59 +0200292 default_str = env_get("bootmenu_default");
293 if (default_str)
294 menu->active = (int)simple_strtol(default_str, NULL, 10);
295
Pali Roháre7abe912013-03-23 14:53:08 +0000296 while ((option = bootmenu_getoption(i))) {
297 sep = strchr(option, '=');
298 if (!sep) {
299 printf("Invalid bootmenu entry: %s\n", option);
300 break;
301 }
302
303 entry = malloc(sizeof(struct bootmenu_entry));
304 if (!entry)
305 goto cleanup;
306
307 len = sep-option;
308 entry->title = malloc(len + 1);
309 if (!entry->title) {
310 free(entry);
311 goto cleanup;
312 }
313 memcpy(entry->title, option, len);
314 entry->title[len] = 0;
315
316 len = strlen(sep + 1);
317 entry->command = malloc(len + 1);
318 if (!entry->command) {
319 free(entry->title);
320 free(entry);
321 goto cleanup;
322 }
323 memcpy(entry->command, sep + 1, len);
324 entry->command[len] = 0;
325
326 sprintf(entry->key, "%d", i);
327
328 entry->num = i;
329 entry->menu = menu;
330 entry->next = NULL;
331
332 if (!iter)
333 menu->first = entry;
334 else
335 iter->next = entry;
336
337 iter = entry;
338 ++i;
339
340 if (i == MAX_COUNT - 1)
341 break;
342 }
343
344 /* Add U-Boot console entry at the end */
345 if (i <= MAX_COUNT - 1) {
346 entry = malloc(sizeof(struct bootmenu_entry));
347 if (!entry)
348 goto cleanup;
349
350 entry->title = strdup("U-Boot console");
351 if (!entry->title) {
352 free(entry);
353 goto cleanup;
354 }
355
356 entry->command = strdup("");
357 if (!entry->command) {
358 free(entry->title);
359 free(entry);
360 goto cleanup;
361 }
362
363 sprintf(entry->key, "%d", i);
364
365 entry->num = i;
366 entry->menu = menu;
367 entry->next = NULL;
368
369 if (!iter)
370 menu->first = entry;
371 else
372 iter->next = entry;
373
374 iter = entry;
375 ++i;
376 }
377
378 menu->count = i;
Frank Wunderlichbace2212018-12-03 11:23:41 +0100379
380 if ((menu->active >= menu->count)||(menu->active < 0)) { //ensure active menuitem is inside menu
381 printf("active menuitem (%d) is outside menu (0..%d)\n",menu->active,menu->count-1);
382 menu->active=0;
383 }
384
Pali Roháre7abe912013-03-23 14:53:08 +0000385 return menu;
386
387cleanup:
388 bootmenu_destroy(menu);
389 return NULL;
390}
391
Thirupathaiah Annapureddy5168d7a2020-03-18 11:38:42 -0700392static void menu_display_statusline(struct menu *m)
393{
394 struct bootmenu_entry *entry;
395 struct bootmenu_data *menu;
396
397 if (menu_default_choice(m, (void *)&entry) < 0)
398 return;
399
400 menu = entry->menu;
401
402 printf(ANSI_CURSOR_POSITION, 1, 1);
403 puts(ANSI_CLEAR_LINE);
Heinrich Schuchardtd1d7ed72022-05-01 23:17:18 +0200404 printf(ANSI_CURSOR_POSITION, 2, 3);
405 puts("*** U-Boot Boot Menu ***");
Thirupathaiah Annapureddy5168d7a2020-03-18 11:38:42 -0700406 puts(ANSI_CLEAR_LINE_TO_END);
407 printf(ANSI_CURSOR_POSITION, 3, 1);
408 puts(ANSI_CLEAR_LINE);
409
410 /* First 3 lines are bootmenu header + 2 empty lines between entries */
411 printf(ANSI_CURSOR_POSITION, menu->count + 5, 1);
412 puts(ANSI_CLEAR_LINE);
Heinrich Schuchardtd1d7ed72022-05-01 23:17:18 +0200413 printf(ANSI_CURSOR_POSITION, menu->count + 6, 3);
414 puts("Press UP/DOWN to move, ENTER to select, ESC/CTRL+C to quit");
Thirupathaiah Annapureddy5168d7a2020-03-18 11:38:42 -0700415 puts(ANSI_CLEAR_LINE_TO_END);
416 printf(ANSI_CURSOR_POSITION, menu->count + 7, 1);
417 puts(ANSI_CLEAR_LINE);
418}
419
Pali Roháre7abe912013-03-23 14:53:08 +0000420static void bootmenu_show(int delay)
421{
422 int init = 0;
423 void *choice = NULL;
424 char *title = NULL;
425 char *command = NULL;
426 struct menu *menu;
427 struct bootmenu_data *bootmenu;
428 struct bootmenu_entry *iter;
429 char *option, *sep;
430
431 /* If delay is 0 do not create menu, just run first entry */
432 if (delay == 0) {
433 option = bootmenu_getoption(0);
434 if (!option) {
435 puts("bootmenu option 0 was not found\n");
436 return;
437 }
438 sep = strchr(option, '=');
439 if (!sep) {
440 puts("bootmenu option 0 is invalid\n");
441 return;
442 }
443 run_command(sep+1, 0);
444 return;
445 }
446
447 bootmenu = bootmenu_create(delay);
448 if (!bootmenu)
449 return;
450
Thirupathaiah Annapureddy5168d7a2020-03-18 11:38:42 -0700451 menu = menu_create(NULL, bootmenu->delay, 1, menu_display_statusline,
452 bootmenu_print_entry, bootmenu_choice_entry,
453 bootmenu);
Pali Roháre7abe912013-03-23 14:53:08 +0000454 if (!menu) {
455 bootmenu_destroy(bootmenu);
456 return;
457 }
458
459 for (iter = bootmenu->first; iter; iter = iter->next) {
Masahisa Kojima990f6632022-03-24 22:54:33 +0900460 if (menu_item_add(menu, iter->key, iter) != 1)
Pali Roháre7abe912013-03-23 14:53:08 +0000461 goto cleanup;
462 }
463
464 /* Default menu entry is always first */
465 menu_default_set(menu, "0");
466
467 puts(ANSI_CURSOR_HIDE);
468 puts(ANSI_CLEAR_CONSOLE);
469 printf(ANSI_CURSOR_POSITION, 1, 1);
470
471 init = 1;
472
Masahisa Kojima990f6632022-03-24 22:54:33 +0900473 if (menu_get_choice(menu, &choice) == 1) {
Pali Roháre7abe912013-03-23 14:53:08 +0000474 iter = choice;
475 title = strdup(iter->title);
476 command = strdup(iter->command);
477 }
478
479cleanup:
480 menu_destroy(menu);
481 bootmenu_destroy(bootmenu);
482
483 if (init) {
484 puts(ANSI_CURSOR_SHOW);
485 puts(ANSI_CLEAR_CONSOLE);
486 printf(ANSI_CURSOR_POSITION, 1, 1);
487 }
488
489 if (title && command) {
490 debug("Starting entry '%s'\n", title);
491 free(title);
492 run_command(command, 0);
493 free(command);
494 }
495
496#ifdef CONFIG_POSTBOOTMENU
497 run_command(CONFIG_POSTBOOTMENU, 0);
498#endif
499}
500
Simon Glasse2313062019-07-20 20:51:24 -0600501#ifdef CONFIG_AUTOBOOT_MENU_SHOW
Pali Roháre7abe912013-03-23 14:53:08 +0000502int menu_show(int bootdelay)
503{
504 bootmenu_show(bootdelay);
505 return -1; /* -1 - abort boot and run monitor code */
506}
507#endif
508
Simon Glass09140112020-05-10 11:40:03 -0600509int do_bootmenu(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
Pali Roháre7abe912013-03-23 14:53:08 +0000510{
511 char *delay_str = NULL;
512 int delay = 10;
513
514#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
515 delay = CONFIG_BOOTDELAY;
516#endif
517
518 if (argc >= 2)
519 delay_str = argv[1];
520
521 if (!delay_str)
Simon Glass00caae62017-08-03 12:22:12 -0600522 delay_str = env_get("bootmenu_delay");
Pali Roháre7abe912013-03-23 14:53:08 +0000523
524 if (delay_str)
525 delay = (int)simple_strtol(delay_str, NULL, 10);
526
527 bootmenu_show(delay);
528 return 0;
529}
530
531U_BOOT_CMD(
532 bootmenu, 2, 1, do_bootmenu,
533 "ANSI terminal bootmenu",
534 "[delay]\n"
535 " - show ANSI terminal bootmenu with autoboot delay"
536);