blob: 7c1abe5772cd45de6522ed78be9ad61f046e570c [file] [log] [blame]
Simon Glass02d929b2023-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
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 */
29struct menu_priv {
30 int num_bootflows;
31};
32
33int 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
Simon Glass14a86a52023-06-01 10:22:35 -0600127 ret = scene_arrange(scn);
128 if (ret)
129 return log_msg_ret("arr", ret);
130
Simon Glass02d929b2023-01-06 08:52:40 -0600131 *expp = exp;
132
133 return 0;
134}
135
Simon Glasse64c2952023-01-06 08:52:42 -0600136int bootflow_menu_apply_theme(struct expo *exp, ofnode node)
137{
138 struct menu_priv *priv = exp->priv;
139 struct scene *scn;
140 u32 font_size;
141 int ret;
142
143 log_debug("Applying theme %s\n", ofnode_get_name(node));
144 scn = expo_lookup_scene_id(exp, MAIN);
145 if (!scn)
146 return log_msg_ret("scn", -ENOENT);
147
148 /* Avoid error-checking optional items */
149 if (!ofnode_read_u32(node, "font-size", &font_size)) {
150 int i;
151
152 log_debug("font size %d\n", font_size);
153 scene_txt_set_font(scn, OBJ_PROMPT, NULL, font_size);
154 scene_txt_set_font(scn, OBJ_POINTER, NULL, font_size);
155 for (i = 0; i < priv->num_bootflows; i++) {
156 ret = scene_txt_set_font(scn, ITEM_DESC + i, NULL,
157 font_size);
158 if (ret)
159 return log_msg_ret("des", ret);
160 scene_txt_set_font(scn, ITEM_KEY + i, NULL, font_size);
161 scene_txt_set_font(scn, ITEM_LABEL + i, NULL,
162 font_size);
163 }
164 }
165
166 ret = scene_arrange(scn);
167 if (ret)
168 return log_msg_ret("arr", ret);
169
170 return 0;
171}
172
Simon Glass02d929b2023-01-06 08:52:40 -0600173int bootflow_menu_run(struct bootstd_priv *std, bool text_mode,
174 struct bootflow **bflowp)
175{
176 struct cli_ch_state s_cch, *cch = &s_cch;
177 struct bootflow *sel_bflow;
178 struct udevice *dev;
179 struct expo *exp;
180 uint sel_id;
181 bool done;
182 int ret;
183
184 cli_ch_init(cch);
185
186 sel_bflow = NULL;
187 *bflowp = NULL;
188
189 ret = bootflow_menu_new(&exp);
190 if (ret)
191 return log_msg_ret("exp", ret);
192
Simon Glasse64c2952023-01-06 08:52:42 -0600193 if (ofnode_valid(std->theme)) {
194 ret = bootflow_menu_apply_theme(exp, std->theme);
195 if (ret)
196 return log_msg_ret("thm", ret);
197 }
198
Simon Glass02d929b2023-01-06 08:52:40 -0600199 /* For now we only support a video console */
200 ret = uclass_first_device_err(UCLASS_VIDEO, &dev);
201 if (ret)
202 return log_msg_ret("vid", ret);
203 ret = expo_set_display(exp, dev);
204 if (ret)
205 return log_msg_ret("dis", ret);
206
207 ret = expo_set_scene_id(exp, MAIN);
208 if (ret)
209 return log_msg_ret("scn", ret);
210
211 if (text_mode)
Simon Glass5904d952023-06-01 10:22:37 -0600212 expo_set_text_mode(exp, text_mode);
Simon Glass02d929b2023-01-06 08:52:40 -0600213
214 done = false;
215 do {
216 struct expo_action act;
217 int ichar, key;
218
219 ret = expo_render(exp);
220 if (ret)
221 break;
222
223 ichar = cli_ch_process(cch, 0);
224 if (!ichar) {
225 while (!ichar && !tstc()) {
226 schedule();
227 mdelay(2);
228 ichar = cli_ch_process(cch, -ETIMEDOUT);
229 }
230 if (!ichar) {
231 ichar = getchar();
232 ichar = cli_ch_process(cch, ichar);
233 }
234 }
235
236 key = 0;
237 if (ichar) {
238 key = bootmenu_conv_key(ichar);
239 if (key == BKEY_NONE)
240 key = ichar;
241 }
242 if (!key)
243 continue;
244
245 ret = expo_send_key(exp, key);
246 if (ret)
247 break;
248
249 ret = expo_action_get(exp, &act);
250 if (!ret) {
251 switch (act.type) {
252 case EXPOACT_SELECT:
253 sel_id = act.select.id;
254 done = true;
255 break;
256 case EXPOACT_QUIT:
257 done = true;
258 break;
259 default:
260 break;
261 }
262 }
263 } while (!done);
264
265 if (ret)
266 return log_msg_ret("end", ret);
267
268 if (sel_id) {
269 struct bootflow *bflow;
270 int i;
271
272 for (ret = bootflow_first_glob(&bflow), i = 0; !ret && i < 36;
273 ret = bootflow_next_glob(&bflow), i++) {
274 if (i == sel_id - ITEM) {
275 sel_bflow = bflow;
276 break;
277 }
278 }
279 }
280
281 expo_destroy(exp);
282
283 if (!sel_bflow)
284 return -EAGAIN;
285 *bflowp = sel_bflow;
286
287 return 0;
288}