blob: 6e2d83406f2096e95540ea9a23cc38f124241aa2 [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;
41 void *logo;
Simon Glassd9da19a2025-05-02 08:46:28 -060042 int ret;
Simon Glass0a2f6a32023-01-06 08:52:40 -060043
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
Simon Glassd9da19a2025-05-02 08:46:28 -060077 *expp = exp;
78
79 return 0;
80}
81
Simon Glass9f5a7922025-05-02 08:46:29 -060082int bootflow_menu_add(struct expo *exp, struct bootflow *bflow, int seq,
83 struct scene **scnp)
Simon Glassd9da19a2025-05-02 08:46:28 -060084{
85 struct menu_priv *priv = exp->priv;
Simon Glass9f5a7922025-05-02 08:46:29 -060086 char str[2], *label, *key;
Simon Glasscffde572025-05-02 08:46:47 -060087 struct udevice *media;
Simon Glassd9da19a2025-05-02 08:46:28 -060088 struct scene *scn;
Simon Glasscffde572025-05-02 08:46:47 -060089 const char *name;
Simon Glass9f5a7922025-05-02 08:46:29 -060090 uint preview_id;
Simon Glassd9da19a2025-05-02 08:46:28 -060091 uint scene_id;
Simon Glass9f5a7922025-05-02 08:46:29 -060092 bool add_gap;
93 int ret;
Simon Glassd9da19a2025-05-02 08:46:28 -060094
95 ret = expo_first_scene_id(exp);
96 if (ret < 0)
97 return log_msg_ret("scn", ret);
98 scene_id = ret;
99 scn = expo_lookup_scene_id(exp, scene_id);
100
Simon Glass9f5a7922025-05-02 08:46:29 -0600101 *str = seq < 10 ? '0' + seq : 'A' + seq - 10;
102 str[1] = '\0';
103 key = strdup(str);
104 if (!key)
105 return log_msg_ret("key", -ENOMEM);
Simon Glasscffde572025-05-02 08:46:47 -0600106
107 media = dev_get_parent(bflow->dev);
108 if (device_get_uclass_id(media) == UCLASS_MASS_STORAGE)
109 name = "usb";
110 else
111 name = media->name;
112 label = strdup(name);
113
Simon Glass9f5a7922025-05-02 08:46:29 -0600114 if (!label) {
115 free(key);
116 return log_msg_ret("nam", -ENOMEM);
117 }
118
119 add_gap = priv->last_bootdev != bflow->dev;
Simon Glassdd7ff432025-05-02 08:46:48 -0600120
121 /* disable this gap for now, since it looks a little ugly */
122 add_gap = false;
Simon Glass9f5a7922025-05-02 08:46:29 -0600123 priv->last_bootdev = bflow->dev;
124
125 ret = expo_str(exp, "prompt", STR_POINTER, ">");
126 ret |= scene_txt_str(scn, "label", ITEM_LABEL + seq,
127 STR_LABEL + seq, label, NULL);
128 ret |= scene_txt_str(scn, "desc", ITEM_DESC + seq, STR_DESC + seq,
129 bflow->os_name ? bflow->os_name :
130 bflow->name, NULL);
131 ret |= scene_txt_str(scn, "key", ITEM_KEY + seq, STR_KEY + seq, key,
132 NULL);
133 preview_id = 0;
134 if (bflow->logo) {
135 preview_id = ITEM_PREVIEW + seq;
136 ret |= scene_img(scn, "preview", preview_id,
137 bflow->logo, NULL);
138 }
139 ret |= scene_menuitem(scn, OBJ_MENU, "item", ITEM + seq,
140 ITEM_KEY + seq, ITEM_LABEL + seq,
141 ITEM_DESC + seq, preview_id,
142 add_gap ? SCENEMIF_GAP_BEFORE : 0,
143 NULL);
144
145 if (ret < 0)
146 return log_msg_ret("itm", -EINVAL);
147 priv->num_bootflows++;
148 *scnp = scn;
149
150 return 0;
151}
152
153int bootflow_menu_add_all(struct expo *exp)
154{
155 struct bootflow *bflow;
156 struct scene *scn;
157 int ret, i;
158
Simon Glass0a2f6a32023-01-06 08:52:40 -0600159 for (ret = bootflow_first_glob(&bflow), i = 0; !ret && i < 36;
160 ret = bootflow_next_glob(&bflow), i++) {
Quentin Schulz21a6aec2024-06-12 16:58:49 +0200161 struct bootmeth_uc_plat *ucp;
Simon Glass0a2f6a32023-01-06 08:52:40 -0600162
163 if (bflow->state != BOOTFLOWST_READY)
164 continue;
165
Quentin Schulz21a6aec2024-06-12 16:58:49 +0200166 /* No media to show for BOOTMETHF_GLOBAL bootmeths */
167 ucp = dev_get_uclass_plat(bflow->method);
168 if (ucp->flags & BOOTMETHF_GLOBAL)
169 continue;
170
Simon Glass9f5a7922025-05-02 08:46:29 -0600171 ret = bootflow_menu_add(exp, bflow, i, &scn);
172 if (ret)
173 return log_msg_ret("bao", ret);
Simon Glass0a2f6a32023-01-06 08:52:40 -0600174
Simon Glass0a2f6a32023-01-06 08:52:40 -0600175 }
176
Simon Glassd7e32a82023-06-01 10:22:35 -0600177 ret = scene_arrange(scn);
178 if (ret)
179 return log_msg_ret("arr", ret);
180
Simon Glass0a2f6a32023-01-06 08:52:40 -0600181 return 0;
182}
183
Simon Glassd92bcc42023-01-06 08:52:42 -0600184int bootflow_menu_apply_theme(struct expo *exp, ofnode node)
185{
186 struct menu_priv *priv = exp->priv;
187 struct scene *scn;
188 u32 font_size;
189 int ret;
190
191 log_debug("Applying theme %s\n", ofnode_get_name(node));
192 scn = expo_lookup_scene_id(exp, MAIN);
193 if (!scn)
194 return log_msg_ret("scn", -ENOENT);
195
196 /* Avoid error-checking optional items */
197 if (!ofnode_read_u32(node, "font-size", &font_size)) {
198 int i;
199
200 log_debug("font size %d\n", font_size);
201 scene_txt_set_font(scn, OBJ_PROMPT, NULL, font_size);
202 scene_txt_set_font(scn, OBJ_POINTER, NULL, font_size);
203 for (i = 0; i < priv->num_bootflows; i++) {
204 ret = scene_txt_set_font(scn, ITEM_DESC + i, NULL,
205 font_size);
206 if (ret)
207 return log_msg_ret("des", ret);
208 scene_txt_set_font(scn, ITEM_KEY + i, NULL, font_size);
209 scene_txt_set_font(scn, ITEM_LABEL + i, NULL,
210 font_size);
211 }
212 }
213
214 ret = scene_arrange(scn);
215 if (ret)
216 return log_msg_ret("arr", ret);
217
218 return 0;
219}
220
Simon Glassd7cef832025-05-02 08:46:27 -0600221int bootflow_menu_start(struct bootstd_priv *std, bool text_mode,
222 struct expo **expp)
Simon Glass0a2f6a32023-01-06 08:52:40 -0600223{
Simon Glass0a2f6a32023-01-06 08:52:40 -0600224 struct udevice *dev;
225 struct expo *exp;
Simon Glass0a2f6a32023-01-06 08:52:40 -0600226 int ret;
227
Simon Glass0a2f6a32023-01-06 08:52:40 -0600228 ret = bootflow_menu_new(&exp);
229 if (ret)
Simon Glassd9da19a2025-05-02 08:46:28 -0600230 return log_msg_ret("bmn", ret);
231 ret = bootflow_menu_add_all(exp);
232 if (ret)
233 return log_msg_ret("bma", ret);
Simon Glass0a2f6a32023-01-06 08:52:40 -0600234
Simon Glassd92bcc42023-01-06 08:52:42 -0600235 if (ofnode_valid(std->theme)) {
236 ret = bootflow_menu_apply_theme(exp, std->theme);
237 if (ret)
238 return log_msg_ret("thm", ret);
239 }
240
Simon Glass0a2f6a32023-01-06 08:52:40 -0600241 /* For now we only support a video console */
242 ret = uclass_first_device_err(UCLASS_VIDEO, &dev);
243 if (ret)
244 return log_msg_ret("vid", ret);
245 ret = expo_set_display(exp, dev);
246 if (ret)
247 return log_msg_ret("dis", ret);
248
249 ret = expo_set_scene_id(exp, MAIN);
250 if (ret)
251 return log_msg_ret("scn", ret);
252
253 if (text_mode)
Simon Glassb2c40342023-06-01 10:22:37 -0600254 expo_set_text_mode(exp, text_mode);
Simon Glass0a2f6a32023-01-06 08:52:40 -0600255
Simon Glasscfb4f2c2025-05-02 08:46:42 -0600256 ret = expo_calc_dims(exp);
257 if (ret)
258 return log_msg_ret("bmd", ret);
259
Simon Glassd7cef832025-05-02 08:46:27 -0600260 *expp = exp;
Simon Glass0a2f6a32023-01-06 08:52:40 -0600261
Simon Glassd7cef832025-05-02 08:46:27 -0600262 return 0;
263}
264
265int bootflow_menu_poll(struct expo *exp, struct bootflow **bflowp)
266{
267 struct bootflow *sel_bflow;
268 struct expo_action act;
269 int ret;
Simon Glass0a2f6a32023-01-06 08:52:40 -0600270
Simon Glassd7cef832025-05-02 08:46:27 -0600271 sel_bflow = NULL;
272 *bflowp = NULL;
273
274 ret = expo_poll(exp, &act);
275 if (ret)
276 return log_msg_ret("bmp", ret);
277
278 switch (act.type) {
279 case EXPOACT_SELECT: {
Simon Glass0a2f6a32023-01-06 08:52:40 -0600280 struct bootflow *bflow;
281 int i;
282
283 for (ret = bootflow_first_glob(&bflow), i = 0; !ret && i < 36;
284 ret = bootflow_next_glob(&bflow), i++) {
Simon Glassd7cef832025-05-02 08:46:27 -0600285 if (i == act.select.id - ITEM) {
286 *bflowp = bflow;
287 // printf("found %p\n", bflow);
288 return 0;
Simon Glass0a2f6a32023-01-06 08:52:40 -0600289 }
290 }
Simon Glassd7cef832025-05-02 08:46:27 -0600291 break;
Simon Glass0a2f6a32023-01-06 08:52:40 -0600292 }
Simon Glassd7cef832025-05-02 08:46:27 -0600293 case EXPOACT_POINT_ITEM: {
294 struct scene *scn = expo_lookup_scene_id(exp, MAIN);
Simon Glass0a2f6a32023-01-06 08:52:40 -0600295
Simon Glassd7cef832025-05-02 08:46:27 -0600296 if (!scn)
297 return log_msg_ret("bms", -ENOENT);
298 ret = scene_menu_select_item(scn, OBJ_MENU, act.select.id);
299 if (ret)
300 return log_msg_ret("bmp", ret);
301 break;
302 }
303 case EXPOACT_QUIT:
304 return -EPIPE;
305 default:
306 break;
307 }
Simon Glass0a2f6a32023-01-06 08:52:40 -0600308
Simon Glassd7cef832025-05-02 08:46:27 -0600309 return -EAGAIN;
Simon Glass0a2f6a32023-01-06 08:52:40 -0600310}