1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Implementation of a expo, a collection of scenes providing menu options
4  *
5  * Copyright 2022 Google LLC
6  * Written by Simon Glass <sjg@chromium.org>
7  */
8 
9 #define LOG_CATEGORY	LOGC_EXPO
10 
11 #include <dm.h>
12 #include <expo.h>
13 #include <log.h>
14 #include <malloc.h>
15 #include <menu.h>
16 #include <video.h>
17 #include <watchdog.h>
18 #include <linux/delay.h>
19 #include "scene_internal.h"
20 
expo_new(const char * name,void * priv,struct expo ** expp)21 int expo_new(const char *name, void *priv, struct expo **expp)
22 {
23 	struct expo *exp;
24 
25 	exp = calloc(1, sizeof(struct expo));
26 	if (!exp)
27 		return log_msg_ret("expo", -ENOMEM);
28 	exp->name = strdup(name);
29 	if (!exp->name) {
30 		free(exp);
31 		return log_msg_ret("name", -ENOMEM);
32 	}
33 	exp->priv = priv;
34 	INIT_LIST_HEAD(&exp->scene_head);
35 	INIT_LIST_HEAD(&exp->str_head);
36 	exp->next_id = EXPOID_BASE_ID;
37 	cli_ch_init(&exp->cch);
38 
39 	*expp = exp;
40 
41 	return 0;
42 }
43 
estr_destroy(struct expo_string * estr)44 static void estr_destroy(struct expo_string *estr)
45 {
46 	free(estr);
47 }
48 
expo_destroy(struct expo * exp)49 void expo_destroy(struct expo *exp)
50 {
51 	struct scene *scn, *next;
52 	struct expo_string *estr, *enext;
53 
54 	list_for_each_entry_safe(scn, next, &exp->scene_head, sibling)
55 		scene_destroy(scn);
56 
57 	list_for_each_entry_safe(estr, enext, &exp->str_head, sibling)
58 		estr_destroy(estr);
59 
60 	free(exp->name);
61 	free(exp);
62 }
63 
resolve_id(struct expo * exp,uint id)64 uint resolve_id(struct expo *exp, uint id)
65 {
66 	log_debug("resolve id %d\n", id);
67 	if (!id)
68 		id = exp->next_id++;
69 	else if (id >= exp->next_id)
70 		exp->next_id = id + 1;
71 
72 	return id;
73 }
74 
expo_set_dynamic_start(struct expo * exp,uint dyn_start)75 void expo_set_dynamic_start(struct expo *exp, uint dyn_start)
76 {
77 	exp->next_id = dyn_start;
78 }
79 
expo_str(struct expo * exp,const char * name,uint id,const char * str)80 int expo_str(struct expo *exp, const char *name, uint id, const char *str)
81 {
82 	struct expo_string *estr;
83 
84 	estr = calloc(1, sizeof(struct expo_string));
85 	if (!estr)
86 		return log_msg_ret("obj", -ENOMEM);
87 
88 	estr->id = resolve_id(exp, id);
89 	abuf_init_const(&estr->buf, str, strlen(str) + 1);
90 	list_add_tail(&estr->sibling, &exp->str_head);
91 
92 	return estr->id;
93 }
94 
expo_get_str(struct expo * exp,uint id)95 const char *expo_get_str(struct expo *exp, uint id)
96 {
97 	struct expo_string *estr;
98 
99 	list_for_each_entry(estr, &exp->str_head, sibling) {
100 		if (estr->id == id)
101 			return estr->buf.data;
102 	}
103 
104 	return NULL;
105 }
106 
expo_edit_str(struct expo * exp,uint id,struct abuf * orig,struct abuf ** copyp)107 int expo_edit_str(struct expo *exp, uint id, struct abuf *orig,
108 		  struct abuf **copyp)
109 {
110 	struct expo_string *estr;
111 	struct abuf old;
112 
113 	list_for_each_entry(estr, &exp->str_head, sibling) {
114 		if (estr->id == id) {
115 			old = estr->buf;
116 			if (!abuf_copy(&old, &estr->buf))
117 				return -ENOMEM;
118 			*copyp = &estr->buf;
119 			if (orig)
120 				*orig = old;
121 			return 0;
122 		}
123 	}
124 
125 	return -ENOENT;
126 }
127 
expo_set_display(struct expo * exp,struct udevice * dev)128 int expo_set_display(struct expo *exp, struct udevice *dev)
129 {
130 	struct udevice *cons;
131 	int ret;
132 
133 	ret = device_find_first_child_by_uclass(dev, UCLASS_VIDEO_CONSOLE,
134 						&cons);
135 	if (ret)
136 		return log_msg_ret("con", ret);
137 
138 	exp->display = dev;
139 	exp->cons = cons;
140 
141 	return 0;
142 }
143 
expo_calc_dims(struct expo * exp)144 int expo_calc_dims(struct expo *exp)
145 {
146 	struct scene *scn;
147 	int ret;
148 
149 	if (!exp->cons)
150 		return log_msg_ret("dim", -ENOTSUPP);
151 
152 	list_for_each_entry(scn, &exp->scene_head, sibling) {
153 		/*
154 		 * Do the menus last so that all the menus' text objects
155 		 * are dimensioned
156 		 */
157 		ret = scene_calc_dims(scn, false);
158 		if (ret)
159 			return log_msg_ret("scn", ret);
160 		ret = scene_calc_dims(scn, true);
161 		if (ret)
162 			return log_msg_ret("scn", ret);
163 	}
164 
165 	return 0;
166 }
167 
expo_set_text_mode(struct expo * exp,bool text_mode)168 void expo_set_text_mode(struct expo *exp, bool text_mode)
169 {
170 	exp->text_mode = text_mode;
171 }
172 
expo_lookup_scene_id(struct expo * exp,uint scene_id)173 struct scene *expo_lookup_scene_id(struct expo *exp, uint scene_id)
174 {
175 	struct scene *scn;
176 
177 	list_for_each_entry(scn, &exp->scene_head, sibling) {
178 		if (scn->id == scene_id)
179 			return scn;
180 	}
181 
182 	return NULL;
183 }
184 
expo_set_scene_id(struct expo * exp,uint scene_id)185 int expo_set_scene_id(struct expo *exp, uint scene_id)
186 {
187 	struct scene *scn;
188 	int ret;
189 
190 	scn = expo_lookup_scene_id(exp, scene_id);
191 	if (!scn)
192 		return log_msg_ret("id", -ENOENT);
193 	ret = scene_arrange(scn);
194 	if (ret)
195 		return log_msg_ret("arr", ret);
196 
197 	exp->scene_id = scene_id;
198 
199 	return 0;
200 }
201 
expo_first_scene_id(struct expo * exp)202 int expo_first_scene_id(struct expo *exp)
203 {
204 	struct scene *scn;
205 
206 	if (list_empty(&exp->scene_head))
207 		return -ENOENT;
208 
209 	scn = list_first_entry(&exp->scene_head, struct scene, sibling);
210 
211 	return scn->id;
212 }
213 
expo_render(struct expo * exp)214 int expo_render(struct expo *exp)
215 {
216 	struct udevice *dev = exp->display;
217 	struct video_priv *vid_priv = dev_get_uclass_priv(dev);
218 	struct scene *scn = NULL;
219 	enum colour_idx back;
220 	u32 colour;
221 	int ret;
222 
223 	back = vid_priv->white_on_black ? VID_BLACK : VID_WHITE;
224 	colour = video_index_to_colour(vid_priv, back);
225 	ret = video_fill(dev, colour);
226 	if (ret)
227 		return log_msg_ret("fill", ret);
228 
229 	if (exp->scene_id) {
230 		scn = expo_lookup_scene_id(exp, exp->scene_id);
231 		if (!scn)
232 			return log_msg_ret("scn", -ENOENT);
233 
234 		ret = scene_render(scn);
235 		if (ret)
236 			return log_msg_ret("ren", ret);
237 	}
238 
239 	video_sync(dev, true);
240 
241 	return scn ? 0 : -ECHILD;
242 }
243 
expo_send_key(struct expo * exp,int key)244 int expo_send_key(struct expo *exp, int key)
245 {
246 	struct scene *scn = NULL;
247 
248 	if (exp->scene_id) {
249 		int ret;
250 
251 		scn = expo_lookup_scene_id(exp, exp->scene_id);
252 		if (!scn)
253 			return log_msg_ret("scn", -ENOENT);
254 
255 		ret = scene_send_key(scn, key, &exp->action);
256 		if (ret)
257 			return log_msg_ret("key", ret);
258 
259 		/* arrange it to get any changes */
260 		ret = scene_arrange(scn);
261 		if (ret)
262 			return log_msg_ret("arr", ret);
263 	}
264 
265 	return scn ? 0 : -ECHILD;
266 }
267 
expo_action_get(struct expo * exp,struct expo_action * act)268 int expo_action_get(struct expo *exp, struct expo_action *act)
269 {
270 	*act = exp->action;
271 	exp->action.type = EXPOACT_NONE;
272 
273 	return act->type == EXPOACT_NONE ? -EAGAIN : 0;
274 }
275 
expo_apply_theme(struct expo * exp,ofnode node)276 int expo_apply_theme(struct expo *exp, ofnode node)
277 {
278 	struct scene *scn;
279 	struct expo_theme *theme = &exp->theme;
280 	bool white_on_black;
281 	int ret;
282 
283 	log_debug("Applying theme %s\n", ofnode_get_name(node));
284 
285 	memset(theme, '\0', sizeof(struct expo_theme));
286 	ofnode_read_u32(node, "font-size", &theme->font_size);
287 	ofnode_read_u32(node, "menu-inset", &theme->menu_inset);
288 	ofnode_read_u32(node, "menuitem-gap-y", &theme->menuitem_gap_y);
289 	ofnode_read_u32(node, "menu-title-margin-x",
290 			&theme->menu_title_margin_x);
291 	white_on_black = ofnode_read_bool(node, "white-on-black");
292 	if (exp->display)
293 		video_set_white_on_black(exp->display, white_on_black);
294 
295 	list_for_each_entry(scn, &exp->scene_head, sibling) {
296 		ret = scene_apply_theme(scn, theme);
297 		if (ret)
298 			return log_msg_ret("app", ret);
299 	}
300 
301 	return 0;
302 }
303 
expo_iter_scene_objs(struct expo * exp,expo_scene_obj_iterator iter,void * priv)304 int expo_iter_scene_objs(struct expo *exp, expo_scene_obj_iterator iter,
305 			 void *priv)
306 {
307 	struct scene *scn;
308 	int ret;
309 
310 	list_for_each_entry(scn, &exp->scene_head, sibling) {
311 		ret = scene_iter_objs(scn, iter, priv);
312 		if (ret)
313 			return log_msg_ret("wr", ret);
314 	}
315 
316 	return 0;
317 }
318 
expo_poll(struct expo * exp,struct expo_action * act)319 int expo_poll(struct expo *exp, struct expo_action *act)
320 {
321 	int ichar, key, ret;
322 
323 	ichar = cli_ch_process(&exp->cch, 0);
324 	if (!ichar) {
325 		int i;
326 
327 		for (i = 0; i < 10 && !ichar && !tstc(); i++) {
328 			schedule();
329 			mdelay(2);
330 			ichar = cli_ch_process(&exp->cch, -ETIMEDOUT);
331 		}
332 		while (!ichar && tstc()) {
333 			ichar = getchar();
334 			ichar = cli_ch_process(&exp->cch, ichar);
335 		}
336 	}
337 
338 	key = 0;
339 	if (ichar) {
340 		key = bootmenu_conv_key(ichar);
341 		if (key == BKEY_NONE || key >= BKEY_FIRST_EXTRA)
342 			key = ichar;
343 	}
344 	if (!key)
345 		return -EAGAIN;
346 
347 	ret = expo_send_key(exp, key);
348 	if (ret)
349 		return log_msg_ret("epk", ret);
350 	ret = expo_action_get(exp, act);
351 	if (ret)
352 		return log_msg_ret("eag", ret);
353 
354 	return 0;
355 }
356