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