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