blob: 6bca17142adf2b46dca45b778e1c68db2bccd92f [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>
Quentin Schulz21a6aec2024-06-12 16:58:49 +020012#include <bootmeth.h>
Simon Glass0a2f6a32023-01-06 08:52:40 -060013#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
Simon Glass9f5a7922025-05-02 08:46:29 -060028 * @last_bootdev: bootdev of the last bootflow added to the menu, NULL if none
Simon Glass0a2f6a32023-01-06 08:52:40 -060029 */
30struct menu_priv {
31 int num_bootflows;
Simon Glass9f5a7922025-05-02 08:46:29 -060032 struct udevice *last_bootdev;
Simon Glass0a2f6a32023-01-06 08:52:40 -060033};
34
35int bootflow_menu_new(struct expo **expp)
36{
Simon Glass0a2f6a32023-01-06 08:52:40 -060037 struct scene_obj_menu *menu;
38 struct menu_priv *priv;
Simon Glass0a2f6a32023-01-06 08:52:40 -060039 struct scene *scn;
40 struct expo *exp;
Simon Glassfe805992025-05-02 08:46:54 -060041 bool use_font;
Simon Glass0a2f6a32023-01-06 08:52:40 -060042 void *logo;
Simon Glassd9da19a2025-05-02 08:46:28 -060043 int ret;
Simon Glass0a2f6a32023-01-06 08:52:40 -060044
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
Simon Glassfe805992025-05-02 08:46:54 -060057 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);
Simon Glass0a2f6a32023-01-06 08:52:40 -060061
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);
Simon Glassfe805992025-05-02 08:46:54 -060066 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);
Simon Glass0a2f6a32023-01-06 08:52:40 -060069
70 logo = video_get_u_boot_logo();
71 if (logo) {
72 ret |= scene_img(scn, "ulogo", OBJ_U_BOOT_LOGO, logo, NULL);
Simon Glassfe805992025-05-02 08:46:54 -060073 ret |= scene_obj_set_pos(scn, OBJ_U_BOOT_LOGO, 1165, 100);
Simon Glass0a2f6a32023-01-06 08:52:40 -060074 }
75
Simon Glassfe805992025-05-02 08:46:54 -060076 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
Simon Glass0a2f6a32023-01-06 08:52:40 -0600108 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
Simon Glassfe805992025-05-02 08:46:54 -0600114 exp->show_highlight = true;
115
Simon Glassd9da19a2025-05-02 08:46:28 -0600116 *expp = exp;
117
118 return 0;
119}
120
Simon Glass9f5a7922025-05-02 08:46:29 -0600121int bootflow_menu_add(struct expo *exp, struct bootflow *bflow, int seq,
122 struct scene **scnp)
Simon Glassd9da19a2025-05-02 08:46:28 -0600123{
124 struct menu_priv *priv = exp->priv;
Simon Glass9f5a7922025-05-02 08:46:29 -0600125 char str[2], *label, *key;
Simon Glasscffde572025-05-02 08:46:47 -0600126 struct udevice *media;
Simon Glassd9da19a2025-05-02 08:46:28 -0600127 struct scene *scn;
Simon Glasscffde572025-05-02 08:46:47 -0600128 const char *name;
Simon Glass9f5a7922025-05-02 08:46:29 -0600129 uint preview_id;
Simon Glassd9da19a2025-05-02 08:46:28 -0600130 uint scene_id;
Simon Glass9f5a7922025-05-02 08:46:29 -0600131 bool add_gap;
132 int ret;
Simon Glassd9da19a2025-05-02 08:46:28 -0600133
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
Simon Glass9f5a7922025-05-02 08:46:29 -0600140 *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);
Simon Glasscffde572025-05-02 08:46:47 -0600145
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
Simon Glass9f5a7922025-05-02 08:46:29 -0600153 if (!label) {
154 free(key);
155 return log_msg_ret("nam", -ENOMEM);
156 }
157
158 add_gap = priv->last_bootdev != bflow->dev;
Simon Glassdd7ff432025-05-02 08:46:48 -0600159
160 /* disable this gap for now, since it looks a little ugly */
161 add_gap = false;
Simon Glass9f5a7922025-05-02 08:46:29 -0600162 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
192int bootflow_menu_add_all(struct expo *exp)
193{
194 struct bootflow *bflow;
195 struct scene *scn;
196 int ret, i;
197
Simon Glass0a2f6a32023-01-06 08:52:40 -0600198 for (ret = bootflow_first_glob(&bflow), i = 0; !ret && i < 36;
199 ret = bootflow_next_glob(&bflow), i++) {
Quentin Schulz21a6aec2024-06-12 16:58:49 +0200200 struct bootmeth_uc_plat *ucp;
Simon Glass0a2f6a32023-01-06 08:52:40 -0600201
202 if (bflow->state != BOOTFLOWST_READY)
203 continue;
204
Quentin Schulz21a6aec2024-06-12 16:58:49 +0200205 /* 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
Simon Glass9f5a7922025-05-02 08:46:29 -0600210 ret = bootflow_menu_add(exp, bflow, i, &scn);
211 if (ret)
212 return log_msg_ret("bao", ret);
Simon Glass0a2f6a32023-01-06 08:52:40 -0600213
Simon Glass0a2f6a32023-01-06 08:52:40 -0600214 }
215
Simon Glassd7e32a82023-06-01 10:22:35 -0600216 ret = scene_arrange(scn);
217 if (ret)
218 return log_msg_ret("arr", ret);
219
Simon Glass0a2f6a32023-01-06 08:52:40 -0600220 return 0;
221}
222
Simon Glassd92bcc42023-01-06 08:52:42 -0600223int bootflow_menu_apply_theme(struct expo *exp, ofnode node)
224{
225 struct menu_priv *priv = exp->priv;
226 struct scene *scn;
227 u32 font_size;
228 int ret;
229
230 log_debug("Applying theme %s\n", ofnode_get_name(node));
231 scn = expo_lookup_scene_id(exp, MAIN);
232 if (!scn)
233 return log_msg_ret("scn", -ENOENT);
234
235 /* Avoid error-checking optional items */
236 if (!ofnode_read_u32(node, "font-size", &font_size)) {
237 int i;
238
239 log_debug("font size %d\n", font_size);
Simon Glassfe805992025-05-02 08:46:54 -0600240 scene_txt_set_font(scn, OBJ_PROMPT1A, NULL, font_size);
Simon Glassd92bcc42023-01-06 08:52:42 -0600241 scene_txt_set_font(scn, OBJ_POINTER, NULL, font_size);
242 for (i = 0; i < priv->num_bootflows; i++) {
243 ret = scene_txt_set_font(scn, ITEM_DESC + i, NULL,
244 font_size);
245 if (ret)
246 return log_msg_ret("des", ret);
247 scene_txt_set_font(scn, ITEM_KEY + i, NULL, font_size);
248 scene_txt_set_font(scn, ITEM_LABEL + i, NULL,
249 font_size);
250 }
251 }
252
253 ret = scene_arrange(scn);
254 if (ret)
255 return log_msg_ret("arr", ret);
256
257 return 0;
258}
259
Simon Glassd7cef832025-05-02 08:46:27 -0600260int bootflow_menu_start(struct bootstd_priv *std, bool text_mode,
261 struct expo **expp)
Simon Glass0a2f6a32023-01-06 08:52:40 -0600262{
Simon Glass0a2f6a32023-01-06 08:52:40 -0600263 struct udevice *dev;
264 struct expo *exp;
Simon Glass0a2f6a32023-01-06 08:52:40 -0600265 int ret;
266
Simon Glass0a2f6a32023-01-06 08:52:40 -0600267 ret = bootflow_menu_new(&exp);
268 if (ret)
Simon Glassd9da19a2025-05-02 08:46:28 -0600269 return log_msg_ret("bmn", ret);
270 ret = bootflow_menu_add_all(exp);
271 if (ret)
272 return log_msg_ret("bma", ret);
Simon Glass0a2f6a32023-01-06 08:52:40 -0600273
Simon Glassd92bcc42023-01-06 08:52:42 -0600274 if (ofnode_valid(std->theme)) {
275 ret = bootflow_menu_apply_theme(exp, std->theme);
276 if (ret)
277 return log_msg_ret("thm", ret);
278 }
279
Simon Glass0a2f6a32023-01-06 08:52:40 -0600280 /* For now we only support a video console */
281 ret = uclass_first_device_err(UCLASS_VIDEO, &dev);
282 if (ret)
283 return log_msg_ret("vid", ret);
284 ret = expo_set_display(exp, dev);
285 if (ret)
286 return log_msg_ret("dis", ret);
287
288 ret = expo_set_scene_id(exp, MAIN);
289 if (ret)
290 return log_msg_ret("scn", ret);
291
292 if (text_mode)
Simon Glassb2c40342023-06-01 10:22:37 -0600293 expo_set_text_mode(exp, text_mode);
Simon Glass0a2f6a32023-01-06 08:52:40 -0600294
Simon Glasscfb4f2c2025-05-02 08:46:42 -0600295 ret = expo_calc_dims(exp);
296 if (ret)
297 return log_msg_ret("bmd", ret);
298
Simon Glassd7cef832025-05-02 08:46:27 -0600299 *expp = exp;
Simon Glass0a2f6a32023-01-06 08:52:40 -0600300
Simon Glassd7cef832025-05-02 08:46:27 -0600301 return 0;
302}
303
Simon Glass7bbbcbf2025-05-02 08:46:55 -0600304int bootflow_menu_poll(struct expo *exp, int *seqp)
Simon Glassd7cef832025-05-02 08:46:27 -0600305{
306 struct bootflow *sel_bflow;
307 struct expo_action act;
Simon Glass7bbbcbf2025-05-02 08:46:55 -0600308 struct scene *scn;
309 int item, ret;
Simon Glass0a2f6a32023-01-06 08:52:40 -0600310
Simon Glassd7cef832025-05-02 08:46:27 -0600311 sel_bflow = NULL;
Simon Glass7bbbcbf2025-05-02 08:46:55 -0600312
313 scn = expo_lookup_scene_id(exp, exp->scene_id);
314
315 item = scene_menu_get_cur_item(scn, OBJ_MENU);
316 *seqp = item > 0 ? item - ITEM : -1;
Simon Glassd7cef832025-05-02 08:46:27 -0600317
318 ret = expo_poll(exp, &act);
319 if (ret)
320 return log_msg_ret("bmp", ret);
321
322 switch (act.type) {
Simon Glass7bbbcbf2025-05-02 08:46:55 -0600323 case EXPOACT_SELECT:
324 *seqp = act.select.id - ITEM;
Simon Glassd7cef832025-05-02 08:46:27 -0600325 break;
Simon Glassd7cef832025-05-02 08:46:27 -0600326 case EXPOACT_POINT_ITEM: {
327 struct scene *scn = expo_lookup_scene_id(exp, MAIN);
Simon Glass0a2f6a32023-01-06 08:52:40 -0600328
Simon Glassd7cef832025-05-02 08:46:27 -0600329 if (!scn)
330 return log_msg_ret("bms", -ENOENT);
331 ret = scene_menu_select_item(scn, OBJ_MENU, act.select.id);
332 if (ret)
333 return log_msg_ret("bmp", ret);
Simon Glass7bbbcbf2025-05-02 08:46:55 -0600334 return -ERESTART;
Simon Glassd7cef832025-05-02 08:46:27 -0600335 }
336 case EXPOACT_QUIT:
337 return -EPIPE;
338 default:
Simon Glass7bbbcbf2025-05-02 08:46:55 -0600339 return -EAGAIN;
Simon Glassd7cef832025-05-02 08:46:27 -0600340 }
Simon Glass0a2f6a32023-01-06 08:52:40 -0600341
Simon Glass7bbbcbf2025-05-02 08:46:55 -0600342 return 0;
Simon Glass0a2f6a32023-01-06 08:52:40 -0600343}