blob: 1ba7b622e5061940a8848fc4b9137568ff652c49 [file] [log] [blame]
Tom Rini10e47792018-05-06 17:58:06 -04001// SPDX-License-Identifier: GPL-2.0+
Pali Rohárac91b472013-03-23 14:53:08 +00002/*
Pali Rohár10a953d2020-04-01 00:35:08 +02003 * (C) Copyright 2011-2013 Pali Rohár <pali@kernel.org>
Pali Rohárac91b472013-03-23 14:53:08 +00004 */
5
6#include <common.h>
7#include <command.h>
8#include <ansi.h>
Simon Glass0af6e2d2019-08-01 09:46:52 -06009#include <env.h>
Simon Glass0f2af882020-05-10 11:40:05 -060010#include <log.h>
Pali Rohárac91b472013-03-23 14:53:08 +000011#include <menu.h>
Pali Rohárac91b472013-03-23 14:53:08 +000012#include <watchdog.h>
13#include <malloc.h>
Simon Glassdbd79542020-05-10 11:40:11 -060014#include <linux/delay.h>
Pali Rohárac91b472013-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,
48};
49
50static char *bootmenu_getoption(unsigned short int n)
51{
Lan Yixun (dlan)981c7e02013-06-27 18:58:53 +080052 char name[MAX_ENV_SIZE];
Pali Rohárac91b472013-03-23 14:53:08 +000053
54 if (n > MAX_COUNT)
55 return NULL;
56
Lan Yixun (dlan)981c7e02013-06-27 18:58:53 +080057 sprintf(name, "bootmenu_%d", n);
Simon Glass64b723f2017-08-03 12:22:12 -060058 return env_get(name);
Pali Rohárac91b472013-03-23 14:53:08 +000059}
60
61static 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
83static 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;
Heinrich Schuchardtc4954fb2020-10-07 18:11:48 +0200102 c = getchar();
Pali Rohárac91b472013-03-23 14:53:08 +0000103
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
134static 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
Heinrich Schuchardtc4954fb2020-10-07 18:11:48 +0200144 c = getchar();
Pali Rohárac91b472013-03-23 14:53:08 +0000145
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
192static 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
235static 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
250static 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;
Frank Wunderlich6a3578e2018-10-05 11:41:59 +0200259 char *default_str;
Pali Rohárac91b472013-03-23 14:53:08 +0000260 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
Frank Wunderlich6a3578e2018-10-05 11:41:59 +0200270 default_str = env_get("bootmenu_default");
271 if (default_str)
272 menu->active = (int)simple_strtol(default_str, NULL, 10);
273
Pali Rohárac91b472013-03-23 14:53:08 +0000274 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;
Frank Wunderlich4931d962018-12-03 11:23:41 +0100357
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
Pali Rohárac91b472013-03-23 14:53:08 +0000363 return menu;
364
365cleanup:
366 bootmenu_destroy(menu);
367 return NULL;
368}
369
Thirupathaiah Annapureddyd6b9f6b2020-03-18 11:38:42 -0700370static 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
Pali Rohárac91b472013-03-23 14:53:08 +0000398static 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
Thirupathaiah Annapureddyd6b9f6b2020-03-18 11:38:42 -0700429 menu = menu_create(NULL, bootmenu->delay, 1, menu_display_statusline,
430 bootmenu_print_entry, bootmenu_choice_entry,
431 bootmenu);
Pali Rohárac91b472013-03-23 14:53:08 +0000432 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
457cleanup:
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
Simon Glassec5f71a2019-07-20 20:51:24 -0600479#ifdef CONFIG_AUTOBOOT_MENU_SHOW
Pali Rohárac91b472013-03-23 14:53:08 +0000480int menu_show(int bootdelay)
481{
482 bootmenu_show(bootdelay);
483 return -1; /* -1 - abort boot and run monitor code */
484}
485#endif
486
Simon Glassed38aef2020-05-10 11:40:03 -0600487int do_bootmenu(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
Pali Rohárac91b472013-03-23 14:53:08 +0000488{
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)
Simon Glass64b723f2017-08-03 12:22:12 -0600500 delay_str = env_get("bootmenu_delay");
Pali Rohárac91b472013-03-23 14:53:08 +0000501
502 if (delay_str)
503 delay = (int)simple_strtol(delay_str, NULL, 10);
504
505 bootmenu_show(delay);
506 return 0;
507}
508
509U_BOOT_CMD(
510 bootmenu, 2, 1, do_bootmenu,
511 "ANSI terminal bootmenu",
512 "[delay]\n"
513 " - show ANSI terminal bootmenu with autoboot delay"
514);