1 // 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 
11 #include <bootflow.h>
12 #include <bootmeth.h>
13 #include <bootstd.h>
14 #include <cli.h>
15 #include <dm.h>
16 #include <expo.h>
17 #include <malloc.h>
18 #include <menu.h>
19 #include <video_console.h>
20 #include <watchdog.h>
21 #include <linux/delay.h>
22 #include "bootflow_internal.h"
23 
24 /**
25  * struct menu_priv - information about the menu
26  *
27  * @num_bootflows: Number of bootflows in the menu
28  * @last_bootdev: bootdev of the last bootflow added to the menu, NULL if none
29  */
30 struct menu_priv {
31 	int num_bootflows;
32 	struct udevice *last_bootdev;
33 };
34 
bootflow_menu_new(struct expo ** expp)35 int bootflow_menu_new(struct expo **expp)
36 {
37 	struct scene_obj_menu *menu;
38 	struct menu_priv *priv;
39 	struct scene *scn;
40 	struct expo *exp;
41 	bool use_font;
42 	void *logo;
43 	int ret;
44 
45 	priv = calloc(1, sizeof(*priv));
46 	if (!priv)
47 		return log_msg_ret("prv", -ENOMEM);
48 
49 	ret = expo_new("bootflows", priv, &exp);
50 	if (ret)
51 		return log_msg_ret("exp", ret);
52 
53 	ret = scene_new(exp, "main", MAIN, &scn);
54 	if (ret < 0)
55 		return log_msg_ret("scn", ret);
56 
57 	ret = scene_box(scn, "box", OBJ_BOX, 2, NULL);
58 	if (ret < 0)
59 		return log_msg_ret("bmb", ret);
60 	ret |= scene_obj_set_bbox(scn, OBJ_BOX, 30, 90, 1366 - 30, 720);
61 
62 	ret = scene_menu(scn, "main", OBJ_MENU, &menu);
63 	ret |= scene_obj_set_pos(scn, OBJ_MENU, MARGIN_LEFT, 100);
64 	ret |= scene_txt_str(scn, "title", OBJ_MENU_TITLE, STR_MENU_TITLE,
65 			     "U-Boot - Boot Menu", NULL);
66 	ret |= scene_obj_set_bbox(scn, OBJ_MENU_TITLE, 0, 32,
67 				  SCENEOB_DISPLAY_MAX, 30);
68 	ret |= scene_obj_set_halign(scn, OBJ_MENU_TITLE, SCENEOA_CENTRE);
69 
70 	logo = video_get_u_boot_logo();
71 	if (logo) {
72 		ret |= scene_img(scn, "ulogo", OBJ_U_BOOT_LOGO, logo, NULL);
73 		ret |= scene_obj_set_pos(scn, OBJ_U_BOOT_LOGO, 1165, 100);
74 	}
75 
76 	ret |= scene_txt_str(scn, "prompt1a", OBJ_PROMPT1A, STR_PROMPT1A,
77 	     "Use the \x18 and \x19 keys to select which entry is highlighted.",
78 	     NULL);
79 	ret |= scene_txt_str(scn, "prompt1b", OBJ_PROMPT1B, STR_PROMPT1B,
80 	     "Use the UP and DOWN keys to select which entry is highlighted.",
81 	     NULL);
82 	ret |= scene_txt_str(scn, "prompt2", OBJ_PROMPT2, STR_PROMPT2,
83 	     "Press enter to boot the selected OS, 'e' to edit the commands "
84 	     "before booting or 'c' for a command-line. ESC to return to "
85 	     "previous menu", NULL);
86 	ret |= scene_txt_str(scn, "autoboot", OBJ_AUTOBOOT, STR_AUTOBOOT,
87 	     "The highlighted entry will be executed automatically in %ds.",
88 	     NULL);
89 	ret |= scene_obj_set_bbox(scn, OBJ_PROMPT1A, 0, 590,
90 				  SCENEOB_DISPLAY_MAX, 30);
91 	ret |= scene_obj_set_bbox(scn, OBJ_PROMPT1B, 0, 620,
92 				  SCENEOB_DISPLAY_MAX, 30);
93 	ret |= scene_obj_set_bbox(scn, OBJ_PROMPT2, 100, 650,
94 				  1366 - 100, 700);
95 	ret |= scene_obj_set_bbox(scn, OBJ_AUTOBOOT, 0, 720,
96 				  SCENEOB_DISPLAY_MAX, 750);
97 	ret |= scene_obj_set_halign(scn, OBJ_PROMPT1A, SCENEOA_CENTRE);
98 	ret |= scene_obj_set_halign(scn, OBJ_PROMPT1B, SCENEOA_CENTRE);
99 	ret |= scene_obj_set_halign(scn, OBJ_PROMPT2, SCENEOA_CENTRE);
100 	ret |= scene_obj_set_valign(scn, OBJ_PROMPT2, SCENEOA_CENTRE);
101 	ret |= scene_obj_set_halign(scn, OBJ_AUTOBOOT, SCENEOA_CENTRE);
102 
103 	use_font = IS_ENABLED(CONFIG_CONSOLE_TRUETYPE);
104 	scene_obj_set_hide(scn, OBJ_PROMPT1A, use_font);
105 	scene_obj_set_hide(scn, OBJ_PROMPT1B, !use_font);
106 	scene_obj_set_hide(scn, OBJ_AUTOBOOT, use_font);
107 
108 	ret |= scene_txt_str(scn, "cur_item", OBJ_POINTER, STR_POINTER, ">",
109 			     NULL);
110 	ret |= scene_menu_set_pointer(scn, OBJ_MENU, OBJ_POINTER);
111 	if (ret < 0)
112 		return log_msg_ret("new", -EINVAL);
113 
114 	exp->show_highlight = true;
115 
116 	*expp = exp;
117 
118 	return 0;
119 }
120 
bootflow_menu_add(struct expo * exp,struct bootflow * bflow,int seq,struct scene ** scnp)121 int bootflow_menu_add(struct expo *exp, struct bootflow *bflow, int seq,
122 		      struct scene **scnp)
123 {
124 	struct menu_priv *priv = exp->priv;
125 	char str[2], *label, *key;
126 	struct udevice *media;
127 	struct scene *scn;
128 	const char *name;
129 	uint preview_id;
130 	uint scene_id;
131 	bool add_gap;
132 	int ret;
133 
134 	ret = expo_first_scene_id(exp);
135 	if (ret < 0)
136 		return log_msg_ret("scn", ret);
137 	scene_id = ret;
138 	scn = expo_lookup_scene_id(exp, scene_id);
139 
140 	*str = seq < 10 ? '0' + seq : 'A' + seq - 10;
141 	str[1] = '\0';
142 	key = strdup(str);
143 	if (!key)
144 		return log_msg_ret("key", -ENOMEM);
145 
146 	media = dev_get_parent(bflow->dev);
147 	if (device_get_uclass_id(media) == UCLASS_MASS_STORAGE)
148 		name = "usb";
149 	else
150 		name = media->name;
151 	label = strdup(name);
152 
153 	if (!label) {
154 		free(key);
155 		return log_msg_ret("nam", -ENOMEM);
156 	}
157 
158 	add_gap = priv->last_bootdev != bflow->dev;
159 
160 	/* disable this gap for now, since it looks a little ugly */
161 	add_gap = false;
162 	priv->last_bootdev = bflow->dev;
163 
164 	ret = expo_str(exp, "prompt", STR_POINTER, ">");
165 	ret |= scene_txt_str(scn, "label", ITEM_LABEL + seq,
166 			      STR_LABEL + seq, label, NULL);
167 	ret |= scene_txt_str(scn, "desc", ITEM_DESC + seq, STR_DESC + seq,
168 			    bflow->os_name ? bflow->os_name :
169 			    bflow->name, NULL);
170 	ret |= scene_txt_str(scn, "key", ITEM_KEY + seq, STR_KEY + seq, key,
171 			      NULL);
172 	preview_id = 0;
173 	if (bflow->logo) {
174 		preview_id = ITEM_PREVIEW + seq;
175 		ret |= scene_img(scn, "preview", preview_id,
176 				     bflow->logo, NULL);
177 	}
178 	ret |= scene_menuitem(scn, OBJ_MENU, "item", ITEM + seq,
179 				  ITEM_KEY + seq, ITEM_LABEL + seq,
180 				  ITEM_DESC + seq, preview_id,
181 				  add_gap ? SCENEMIF_GAP_BEFORE : 0,
182 				  NULL);
183 
184 	if (ret < 0)
185 		return log_msg_ret("itm", -EINVAL);
186 	priv->num_bootflows++;
187 	*scnp = scn;
188 
189 	return 0;
190 }
191 
bootflow_menu_add_all(struct expo * exp)192 int bootflow_menu_add_all(struct expo *exp)
193 {
194 	struct bootflow *bflow;
195 	struct scene *scn;
196 	int ret, i;
197 
198 	for (ret = bootflow_first_glob(&bflow), i = 0; !ret && i < 36;
199 	     ret = bootflow_next_glob(&bflow), i++) {
200 		struct bootmeth_uc_plat *ucp;
201 
202 		if (bflow->state != BOOTFLOWST_READY)
203 			continue;
204 
205 		/* No media to show for BOOTMETHF_GLOBAL bootmeths */
206 		ucp = dev_get_uclass_plat(bflow->method);
207 		if (ucp->flags & BOOTMETHF_GLOBAL)
208 			continue;
209 
210 		ret = bootflow_menu_add(exp, bflow, i, &scn);
211 		if (ret)
212 			return log_msg_ret("bao", ret);
213 	}
214 
215 	return 0;
216 }
217 
bootflow_menu_setup(struct bootstd_priv * std,bool text_mode,struct expo ** expp)218 int bootflow_menu_setup(struct bootstd_priv *std, bool text_mode,
219 			struct expo **expp)
220 {
221 	struct udevice *dev;
222 	struct expo *exp;
223 	int ret;
224 
225 	ret = bootflow_menu_new(&exp);
226 	if (ret)
227 		return log_msg_ret("bmn", ret);
228 
229 	/* For now we only support a video console */
230 	ret = uclass_first_device_err(UCLASS_VIDEO, &dev);
231 	if (ret)
232 		return log_msg_ret("vid", ret);
233 	ret = expo_set_display(exp, dev);
234 	if (ret)
235 		return log_msg_ret("dis", ret);
236 
237 	ret = expo_set_scene_id(exp, MAIN);
238 	if (ret)
239 		return log_msg_ret("scn", ret);
240 
241 	if (text_mode)
242 		expo_set_text_mode(exp, text_mode);
243 
244 	*expp = exp;
245 
246 	return 0;
247 }
248 
bootflow_menu_start(struct bootstd_priv * std,bool text_mode,struct expo ** expp)249 int bootflow_menu_start(struct bootstd_priv *std, bool text_mode,
250 			struct expo **expp)
251 {
252 	struct scene *scn;
253 	struct expo *exp;
254 	uint scene_id;
255 	int ret;
256 
257 	ret = bootflow_menu_setup(std, text_mode, &exp);
258 	if (ret)
259 		return log_msg_ret("bmd", ret);
260 
261 	ret = bootflow_menu_add_all(exp);
262 	if (ret)
263 		return log_msg_ret("bma", ret);
264 
265 	if (ofnode_valid(std->theme)) {
266 		ret = expo_apply_theme(exp, std->theme);
267 		if (ret)
268 			return log_msg_ret("thm", ret);
269 	}
270 
271 	ret = expo_calc_dims(exp);
272 	if (ret)
273 		return log_msg_ret("bmd", ret);
274 
275 	ret = expo_first_scene_id(exp);
276 	if (ret < 0)
277 		return log_msg_ret("scn", ret);
278 	scene_id = ret;
279 	scn = expo_lookup_scene_id(exp, scene_id);
280 
281 	scene_set_highlight_id(scn, OBJ_MENU);
282 
283 	ret = scene_arrange(scn);
284 	if (ret)
285 		return log_msg_ret("arr", ret);
286 
287 	*expp = exp;
288 
289 	return 0;
290 }
291 
bootflow_menu_poll(struct expo * exp,int * seqp)292 int bootflow_menu_poll(struct expo *exp, int *seqp)
293 {
294 	struct bootflow *sel_bflow;
295 	struct expo_action act;
296 	struct scene *scn;
297 	int item, ret;
298 
299 	sel_bflow = NULL;
300 
301 	scn = expo_lookup_scene_id(exp, exp->scene_id);
302 
303 	item = scene_menu_get_cur_item(scn, OBJ_MENU);
304 	*seqp = item > 0 ? item - ITEM : -1;
305 
306 	ret = expo_poll(exp, &act);
307 	if (ret)
308 		return log_msg_ret("bmp", ret);
309 
310 	switch (act.type) {
311 	case EXPOACT_SELECT:
312 		*seqp = act.select.id - ITEM;
313 		break;
314 	case EXPOACT_POINT_ITEM: {
315 		struct scene *scn = expo_lookup_scene_id(exp, MAIN);
316 
317 		if (!scn)
318 			return log_msg_ret("bms", -ENOENT);
319 		ret = scene_menu_select_item(scn, OBJ_MENU, act.select.id);
320 		if (ret)
321 			return log_msg_ret("bmp", ret);
322 		return -ERESTART;
323 	}
324 	case EXPOACT_QUIT:
325 		return -EPIPE;
326 	default:
327 		return -EAGAIN;
328 	}
329 
330 	return 0;
331 }
332