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