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