blob: 143ef8413326e079bebe0649433ed0178bc08dad [file] [log] [blame]
Simon Glass0a2f6a32023-01-06 08:52:40 -06001// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Provide a menu of available bootflows and related options
4 *
5 * Copyright 2022 Google LLC
6 * Written by Simon Glass <sjg@chromium.org>
7 */
8
9#define LOG_CATEGORY UCLASS_BOOTSTD
10
Simon Glass0a2f6a32023-01-06 08:52:40 -060011#include <bootflow.h>
12#include <bootstd.h>
13#include <cli.h>
14#include <dm.h>
15#include <expo.h>
16#include <malloc.h>
17#include <menu.h>
18#include <video_console.h>
19#include <watchdog.h>
20#include <linux/delay.h>
21#include "bootflow_internal.h"
22
23/**
24 * struct menu_priv - information about the menu
25 *
26 * @num_bootflows: Number of bootflows in the menu
27 */
28struct menu_priv {
29 int num_bootflows;
30};
31
32int bootflow_menu_new(struct expo **expp)
33{
34 struct udevice *last_bootdev;
35 struct scene_obj_menu *menu;
36 struct menu_priv *priv;
37 struct bootflow *bflow;
38 struct scene *scn;
39 struct expo *exp;
40 void *logo;
41 int ret, i;
42
43 priv = calloc(1, sizeof(*priv));
44 if (!priv)
45 return log_msg_ret("prv", -ENOMEM);
46
47 ret = expo_new("bootflows", priv, &exp);
48 if (ret)
49 return log_msg_ret("exp", ret);
50
51 ret = scene_new(exp, "main", MAIN, &scn);
52 if (ret < 0)
53 return log_msg_ret("scn", ret);
54
55 ret |= scene_txt_str(scn, "prompt", OBJ_PROMPT, STR_PROMPT,
56 "UP and DOWN to choose, ENTER to select", NULL);
57
58 ret = scene_menu(scn, "main", OBJ_MENU, &menu);
59 ret |= scene_obj_set_pos(scn, OBJ_MENU, MARGIN_LEFT, 100);
60 ret |= scene_txt_str(scn, "title", OBJ_MENU_TITLE, STR_MENU_TITLE,
61 "U-Boot - Boot Menu", NULL);
62 ret |= scene_menu_set_title(scn, OBJ_MENU, OBJ_PROMPT);
63
64 logo = video_get_u_boot_logo();
65 if (logo) {
66 ret |= scene_img(scn, "ulogo", OBJ_U_BOOT_LOGO, logo, NULL);
67 ret |= scene_obj_set_pos(scn, OBJ_U_BOOT_LOGO, -4, 4);
68 }
69
70 ret |= scene_txt_str(scn, "cur_item", OBJ_POINTER, STR_POINTER, ">",
71 NULL);
72 ret |= scene_menu_set_pointer(scn, OBJ_MENU, OBJ_POINTER);
73 if (ret < 0)
74 return log_msg_ret("new", -EINVAL);
75
76 last_bootdev = NULL;
77 for (ret = bootflow_first_glob(&bflow), i = 0; !ret && i < 36;
78 ret = bootflow_next_glob(&bflow), i++) {
79 char str[2], *label, *key;
80 uint preview_id;
81 bool add_gap;
82
83 if (bflow->state != BOOTFLOWST_READY)
84 continue;
85
86 *str = i < 10 ? '0' + i : 'A' + i - 10;
87 str[1] = '\0';
88 key = strdup(str);
89 if (!key)
90 return log_msg_ret("key", -ENOMEM);
91 label = strdup(dev_get_parent(bflow->dev)->name);
92 if (!label) {
93 free(key);
94 return log_msg_ret("nam", -ENOMEM);
95 }
96
97 add_gap = last_bootdev != bflow->dev;
98 last_bootdev = bflow->dev;
99
100 ret = expo_str(exp, "prompt", STR_POINTER, ">");
101 ret |= scene_txt_str(scn, "label", ITEM_LABEL + i,
102 STR_LABEL + i, label, NULL);
103 ret |= scene_txt_str(scn, "desc", ITEM_DESC + i, STR_DESC + i,
104 bflow->os_name ? bflow->os_name :
105 bflow->name, NULL);
106 ret |= scene_txt_str(scn, "key", ITEM_KEY + i, STR_KEY + i, key,
107 NULL);
108 preview_id = 0;
109 if (bflow->logo) {
110 preview_id = ITEM_PREVIEW + i;
111 ret |= scene_img(scn, "preview", preview_id,
112 bflow->logo, NULL);
113 }
114 ret |= scene_menuitem(scn, OBJ_MENU, "item", ITEM + i,
115 ITEM_KEY + i, ITEM_LABEL + i,
116 ITEM_DESC + i, preview_id,
117 add_gap ? SCENEMIF_GAP_BEFORE : 0,
118 NULL);
119
120 if (ret < 0)
121 return log_msg_ret("itm", -EINVAL);
Simon Glass0a2f6a32023-01-06 08:52:40 -0600122 priv->num_bootflows++;
123 }
124
Simon Glassd7e32a82023-06-01 10:22:35 -0600125 ret = scene_arrange(scn);
126 if (ret)
127 return log_msg_ret("arr", ret);
128
Simon Glass0a2f6a32023-01-06 08:52:40 -0600129 *expp = exp;
130
131 return 0;
132}
133
Simon Glassd92bcc42023-01-06 08:52:42 -0600134int bootflow_menu_apply_theme(struct expo *exp, ofnode node)
135{
136 struct menu_priv *priv = exp->priv;
137 struct scene *scn;
138 u32 font_size;
139 int ret;
140
141 log_debug("Applying theme %s\n", ofnode_get_name(node));
142 scn = expo_lookup_scene_id(exp, MAIN);
143 if (!scn)
144 return log_msg_ret("scn", -ENOENT);
145
146 /* Avoid error-checking optional items */
147 if (!ofnode_read_u32(node, "font-size", &font_size)) {
148 int i;
149
150 log_debug("font size %d\n", font_size);
151 scene_txt_set_font(scn, OBJ_PROMPT, NULL, font_size);
152 scene_txt_set_font(scn, OBJ_POINTER, NULL, font_size);
153 for (i = 0; i < priv->num_bootflows; i++) {
154 ret = scene_txt_set_font(scn, ITEM_DESC + i, NULL,
155 font_size);
156 if (ret)
157 return log_msg_ret("des", ret);
158 scene_txt_set_font(scn, ITEM_KEY + i, NULL, font_size);
159 scene_txt_set_font(scn, ITEM_LABEL + i, NULL,
160 font_size);
161 }
162 }
163
164 ret = scene_arrange(scn);
165 if (ret)
166 return log_msg_ret("arr", ret);
167
168 return 0;
169}
170
Simon Glass0a2f6a32023-01-06 08:52:40 -0600171int bootflow_menu_run(struct bootstd_priv *std, bool text_mode,
172 struct bootflow **bflowp)
173{
174 struct cli_ch_state s_cch, *cch = &s_cch;
175 struct bootflow *sel_bflow;
176 struct udevice *dev;
177 struct expo *exp;
178 uint sel_id;
179 bool done;
180 int ret;
181
182 cli_ch_init(cch);
183
184 sel_bflow = NULL;
185 *bflowp = NULL;
186
187 ret = bootflow_menu_new(&exp);
188 if (ret)
189 return log_msg_ret("exp", ret);
190
Simon Glassd92bcc42023-01-06 08:52:42 -0600191 if (ofnode_valid(std->theme)) {
192 ret = bootflow_menu_apply_theme(exp, std->theme);
193 if (ret)
194 return log_msg_ret("thm", ret);
195 }
196
Simon Glass0a2f6a32023-01-06 08:52:40 -0600197 /* For now we only support a video console */
198 ret = uclass_first_device_err(UCLASS_VIDEO, &dev);
199 if (ret)
200 return log_msg_ret("vid", ret);
201 ret = expo_set_display(exp, dev);
202 if (ret)
203 return log_msg_ret("dis", ret);
204
205 ret = expo_set_scene_id(exp, MAIN);
206 if (ret)
207 return log_msg_ret("scn", ret);
208
209 if (text_mode)
Simon Glassb2c40342023-06-01 10:22:37 -0600210 expo_set_text_mode(exp, text_mode);
Simon Glass0a2f6a32023-01-06 08:52:40 -0600211
212 done = false;
213 do {
214 struct expo_action act;
215 int ichar, key;
216
217 ret = expo_render(exp);
218 if (ret)
219 break;
220
221 ichar = cli_ch_process(cch, 0);
222 if (!ichar) {
223 while (!ichar && !tstc()) {
224 schedule();
225 mdelay(2);
226 ichar = cli_ch_process(cch, -ETIMEDOUT);
227 }
228 if (!ichar) {
229 ichar = getchar();
230 ichar = cli_ch_process(cch, ichar);
231 }
232 }
233
234 key = 0;
235 if (ichar) {
236 key = bootmenu_conv_key(ichar);
237 if (key == BKEY_NONE)
238 key = ichar;
239 }
240 if (!key)
241 continue;
242
243 ret = expo_send_key(exp, key);
244 if (ret)
245 break;
246
247 ret = expo_action_get(exp, &act);
248 if (!ret) {
249 switch (act.type) {
250 case EXPOACT_SELECT:
251 sel_id = act.select.id;
252 done = true;
253 break;
254 case EXPOACT_QUIT:
255 done = true;
256 break;
257 default:
258 break;
259 }
260 }
261 } while (!done);
262
263 if (ret)
264 return log_msg_ret("end", ret);
265
266 if (sel_id) {
267 struct bootflow *bflow;
268 int i;
269
270 for (ret = bootflow_first_glob(&bflow), i = 0; !ret && i < 36;
271 ret = bootflow_next_glob(&bflow), i++) {
272 if (i == sel_id - ITEM) {
273 sel_bflow = bflow;
274 break;
275 }
276 }
277 }
278
279 expo_destroy(exp);
280
281 if (!sel_bflow)
282 return -EAGAIN;
283 *bflowp = sel_bflow;
284
285 return 0;
286}