blob: f731d7858b2997701ec3ff022ba63937dd4ebd51 [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
28 */
29struct menu_priv {
30 int num_bootflows;
31};
32
33int bootflow_menu_new(struct expo **expp)
34{
Simon Glass0a2f6a32023-01-06 08:52:40 -060035 struct scene_obj_menu *menu;
36 struct menu_priv *priv;
Simon Glass0a2f6a32023-01-06 08:52:40 -060037 struct scene *scn;
38 struct expo *exp;
39 void *logo;
Simon Glassd9da19a2025-05-02 08:46:28 -060040 int ret;
Simon Glass0a2f6a32023-01-06 08:52:40 -060041
42 priv = calloc(1, sizeof(*priv));
43 if (!priv)
44 return log_msg_ret("prv", -ENOMEM);
45
46 ret = expo_new("bootflows", priv, &exp);
47 if (ret)
48 return log_msg_ret("exp", ret);
49
50 ret = scene_new(exp, "main", MAIN, &scn);
51 if (ret < 0)
52 return log_msg_ret("scn", ret);
53
54 ret |= scene_txt_str(scn, "prompt", OBJ_PROMPT, STR_PROMPT,
55 "UP and DOWN to choose, ENTER to select", NULL);
56
57 ret = scene_menu(scn, "main", OBJ_MENU, &menu);
58 ret |= scene_obj_set_pos(scn, OBJ_MENU, MARGIN_LEFT, 100);
59 ret |= scene_txt_str(scn, "title", OBJ_MENU_TITLE, STR_MENU_TITLE,
60 "U-Boot - Boot Menu", NULL);
61 ret |= scene_menu_set_title(scn, OBJ_MENU, OBJ_PROMPT);
62
63 logo = video_get_u_boot_logo();
64 if (logo) {
65 ret |= scene_img(scn, "ulogo", OBJ_U_BOOT_LOGO, logo, NULL);
66 ret |= scene_obj_set_pos(scn, OBJ_U_BOOT_LOGO, -4, 4);
67 }
68
69 ret |= scene_txt_str(scn, "cur_item", OBJ_POINTER, STR_POINTER, ">",
70 NULL);
71 ret |= scene_menu_set_pointer(scn, OBJ_MENU, OBJ_POINTER);
72 if (ret < 0)
73 return log_msg_ret("new", -EINVAL);
74
Simon Glassd9da19a2025-05-02 08:46:28 -060075 *expp = exp;
76
77 return 0;
78}
79
80int bootflow_menu_add_all(struct expo *exp)
81{
82 struct menu_priv *priv = exp->priv;
83 struct udevice *last_bootdev;
84 struct bootflow *bflow;
85 struct scene *scn;
86 uint scene_id;
87 int ret, i;
88
89 ret = expo_first_scene_id(exp);
90 if (ret < 0)
91 return log_msg_ret("scn", ret);
92 scene_id = ret;
93 scn = expo_lookup_scene_id(exp, scene_id);
94
Simon Glass0a2f6a32023-01-06 08:52:40 -060095 last_bootdev = NULL;
96 for (ret = bootflow_first_glob(&bflow), i = 0; !ret && i < 36;
97 ret = bootflow_next_glob(&bflow), i++) {
Quentin Schulz21a6aec2024-06-12 16:58:49 +020098 struct bootmeth_uc_plat *ucp;
Simon Glass0a2f6a32023-01-06 08:52:40 -060099 char str[2], *label, *key;
100 uint preview_id;
101 bool add_gap;
102
103 if (bflow->state != BOOTFLOWST_READY)
104 continue;
105
Quentin Schulz21a6aec2024-06-12 16:58:49 +0200106 /* No media to show for BOOTMETHF_GLOBAL bootmeths */
107 ucp = dev_get_uclass_plat(bflow->method);
108 if (ucp->flags & BOOTMETHF_GLOBAL)
109 continue;
110
Simon Glass0a2f6a32023-01-06 08:52:40 -0600111 *str = i < 10 ? '0' + i : 'A' + i - 10;
112 str[1] = '\0';
113 key = strdup(str);
114 if (!key)
115 return log_msg_ret("key", -ENOMEM);
116 label = strdup(dev_get_parent(bflow->dev)->name);
117 if (!label) {
118 free(key);
119 return log_msg_ret("nam", -ENOMEM);
120 }
121
122 add_gap = last_bootdev != bflow->dev;
123 last_bootdev = bflow->dev;
124
125 ret = expo_str(exp, "prompt", STR_POINTER, ">");
126 ret |= scene_txt_str(scn, "label", ITEM_LABEL + i,
127 STR_LABEL + i, label, NULL);
128 ret |= scene_txt_str(scn, "desc", ITEM_DESC + i, STR_DESC + i,
129 bflow->os_name ? bflow->os_name :
130 bflow->name, NULL);
131 ret |= scene_txt_str(scn, "key", ITEM_KEY + i, STR_KEY + i, key,
132 NULL);
133 preview_id = 0;
134 if (bflow->logo) {
135 preview_id = ITEM_PREVIEW + i;
136 ret |= scene_img(scn, "preview", preview_id,
137 bflow->logo, NULL);
138 }
139 ret |= scene_menuitem(scn, OBJ_MENU, "item", ITEM + i,
140 ITEM_KEY + i, ITEM_LABEL + i,
141 ITEM_DESC + i, preview_id,
142 add_gap ? SCENEMIF_GAP_BEFORE : 0,
143 NULL);
144
145 if (ret < 0)
146 return log_msg_ret("itm", -EINVAL);
Simon Glass0a2f6a32023-01-06 08:52:40 -0600147 priv->num_bootflows++;
148 }
149
Simon Glassd7e32a82023-06-01 10:22:35 -0600150 ret = scene_arrange(scn);
151 if (ret)
152 return log_msg_ret("arr", ret);
153
Simon Glass0a2f6a32023-01-06 08:52:40 -0600154 return 0;
155}
156
Simon Glassd92bcc42023-01-06 08:52:42 -0600157int bootflow_menu_apply_theme(struct expo *exp, ofnode node)
158{
159 struct menu_priv *priv = exp->priv;
160 struct scene *scn;
161 u32 font_size;
162 int ret;
163
164 log_debug("Applying theme %s\n", ofnode_get_name(node));
165 scn = expo_lookup_scene_id(exp, MAIN);
166 if (!scn)
167 return log_msg_ret("scn", -ENOENT);
168
169 /* Avoid error-checking optional items */
170 if (!ofnode_read_u32(node, "font-size", &font_size)) {
171 int i;
172
173 log_debug("font size %d\n", font_size);
174 scene_txt_set_font(scn, OBJ_PROMPT, NULL, font_size);
175 scene_txt_set_font(scn, OBJ_POINTER, NULL, font_size);
176 for (i = 0; i < priv->num_bootflows; i++) {
177 ret = scene_txt_set_font(scn, ITEM_DESC + i, NULL,
178 font_size);
179 if (ret)
180 return log_msg_ret("des", ret);
181 scene_txt_set_font(scn, ITEM_KEY + i, NULL, font_size);
182 scene_txt_set_font(scn, ITEM_LABEL + i, NULL,
183 font_size);
184 }
185 }
186
187 ret = scene_arrange(scn);
188 if (ret)
189 return log_msg_ret("arr", ret);
190
191 return 0;
192}
193
Simon Glassd7cef832025-05-02 08:46:27 -0600194int bootflow_menu_start(struct bootstd_priv *std, bool text_mode,
195 struct expo **expp)
Simon Glass0a2f6a32023-01-06 08:52:40 -0600196{
Simon Glass0a2f6a32023-01-06 08:52:40 -0600197 struct udevice *dev;
198 struct expo *exp;
Simon Glass0a2f6a32023-01-06 08:52:40 -0600199 int ret;
200
Simon Glass0a2f6a32023-01-06 08:52:40 -0600201 ret = bootflow_menu_new(&exp);
202 if (ret)
Simon Glassd9da19a2025-05-02 08:46:28 -0600203 return log_msg_ret("bmn", ret);
204 ret = bootflow_menu_add_all(exp);
205 if (ret)
206 return log_msg_ret("bma", ret);
Simon Glass0a2f6a32023-01-06 08:52:40 -0600207
Simon Glassd92bcc42023-01-06 08:52:42 -0600208 if (ofnode_valid(std->theme)) {
209 ret = bootflow_menu_apply_theme(exp, std->theme);
210 if (ret)
211 return log_msg_ret("thm", ret);
212 }
213
Simon Glass0a2f6a32023-01-06 08:52:40 -0600214 /* For now we only support a video console */
215 ret = uclass_first_device_err(UCLASS_VIDEO, &dev);
216 if (ret)
217 return log_msg_ret("vid", ret);
218 ret = expo_set_display(exp, dev);
219 if (ret)
220 return log_msg_ret("dis", ret);
221
222 ret = expo_set_scene_id(exp, MAIN);
223 if (ret)
224 return log_msg_ret("scn", ret);
225
226 if (text_mode)
Simon Glassb2c40342023-06-01 10:22:37 -0600227 expo_set_text_mode(exp, text_mode);
Simon Glass0a2f6a32023-01-06 08:52:40 -0600228
Simon Glassd7cef832025-05-02 08:46:27 -0600229 *expp = exp;
Simon Glass0a2f6a32023-01-06 08:52:40 -0600230
Simon Glassd7cef832025-05-02 08:46:27 -0600231 return 0;
232}
233
234int bootflow_menu_poll(struct expo *exp, struct bootflow **bflowp)
235{
236 struct bootflow *sel_bflow;
237 struct expo_action act;
238 int ret;
Simon Glass0a2f6a32023-01-06 08:52:40 -0600239
Simon Glassd7cef832025-05-02 08:46:27 -0600240 sel_bflow = NULL;
241 *bflowp = NULL;
242
243 ret = expo_poll(exp, &act);
244 if (ret)
245 return log_msg_ret("bmp", ret);
246
247 switch (act.type) {
248 case EXPOACT_SELECT: {
Simon Glass0a2f6a32023-01-06 08:52:40 -0600249 struct bootflow *bflow;
250 int i;
251
252 for (ret = bootflow_first_glob(&bflow), i = 0; !ret && i < 36;
253 ret = bootflow_next_glob(&bflow), i++) {
Simon Glassd7cef832025-05-02 08:46:27 -0600254 if (i == act.select.id - ITEM) {
255 *bflowp = bflow;
256 // printf("found %p\n", bflow);
257 return 0;
Simon Glass0a2f6a32023-01-06 08:52:40 -0600258 }
259 }
Simon Glassd7cef832025-05-02 08:46:27 -0600260 break;
Simon Glass0a2f6a32023-01-06 08:52:40 -0600261 }
Simon Glassd7cef832025-05-02 08:46:27 -0600262 case EXPOACT_POINT_ITEM: {
263 struct scene *scn = expo_lookup_scene_id(exp, MAIN);
Simon Glass0a2f6a32023-01-06 08:52:40 -0600264
Simon Glassd7cef832025-05-02 08:46:27 -0600265 if (!scn)
266 return log_msg_ret("bms", -ENOENT);
267 ret = scene_menu_select_item(scn, OBJ_MENU, act.select.id);
268 if (ret)
269 return log_msg_ret("bmp", ret);
270 break;
271 }
272 case EXPOACT_QUIT:
273 return -EPIPE;
274 default:
275 break;
276 }
Simon Glass0a2f6a32023-01-06 08:52:40 -0600277
Simon Glassd7cef832025-05-02 08:46:27 -0600278 return -EAGAIN;
Simon Glass0a2f6a32023-01-06 08:52:40 -0600279}