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