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