1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Building an expo from an FDT description
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 <expo.h>
12 #include <fdtdec.h>
13 #include <log.h>
14 #include <malloc.h>
15 #include <dm/ofnode.h>
16 #include <linux/libfdt.h>
17 
18 /**
19  * struct build_info - Information to use when building
20  *
21  * @str_for_id: String for each ID in use, NULL if empty. The string is NULL
22  *	if there is nothing for this ID. Since ID 0 is never used, the first
23  *	element of this array is always NULL
24  * @str_count: Number of entries in @str_for_id
25  * @err_node: Node being processed (for error reporting)
26  * @err_prop: Property being processed (for error reporting)
27  */
28 struct build_info {
29 	const char **str_for_id;
30 	int str_count;
31 	ofnode err_node;
32 	const char *err_prop;
33 };
34 
35 /**
36  * add_txt_str - Add a string or lookup its ID, then add to expo
37  *
38  * @info: Build information
39  * @node: Node describing scene
40  * @scn: Scene to add to
41  * @find_name: Name to look for (e.g. "title"). This will find a property called
42  * "title" if it exists, else will look up the string for "title-id"
43  * Return: ID of added string, or -ve on error
44  */
add_txt_str(struct build_info * info,ofnode node,struct scene * scn,const char * find_name,uint obj_id)45 int add_txt_str(struct build_info *info, ofnode node, struct scene *scn,
46 		const char *find_name, uint obj_id)
47 {
48 	const char *text;
49 	int ret;
50 
51 	info->err_prop = find_name;
52 	text = ofnode_read_string(node, find_name);
53 	if (!text) {
54 		char name[40];
55 		u32 id;
56 
57 		snprintf(name, sizeof(name), "%s-id", find_name);
58 		ret = ofnode_read_u32(node, name, &id);
59 		if (ret)
60 			return log_msg_ret("id", -ENOENT);
61 
62 		if (id >= info->str_count)
63 			return log_msg_ret("id", -E2BIG);
64 		text = info->str_for_id[id];
65 		if (!text)
66 			return log_msg_ret("id", -EINVAL);
67 	}
68 
69 	ret = scene_txt_str(scn, find_name, obj_id, 0, text, NULL);
70 	if (ret < 0)
71 		return log_msg_ret("add", ret);
72 
73 	return ret;
74 }
75 
76 /**
77  * add_txt_str_list - Add a list string or lookup its ID, then add to expo
78  *
79  * @info: Build information
80  * @node: Node describing scene
81  * @scn: Scene to add to
82  * @find_name: Name to look for (e.g. "title"). This will find a string-list
83  * property called "title" if it exists, else will look up the string in the
84  * "title-id" string list.
85  * Return: ID of added string, or -ve on error
86  */
add_txt_str_list(struct build_info * info,ofnode node,struct scene * scn,const char * find_name,int index,uint obj_id)87 int add_txt_str_list(struct build_info *info, ofnode node, struct scene *scn,
88 		     const char *find_name, int index, uint obj_id)
89 {
90 	const char *text;
91 	int ret;
92 
93 	ret = ofnode_read_string_index(node, find_name, index, &text);
94 	if (ret) {
95 		char name[40];
96 		u32 id;
97 
98 		snprintf(name, sizeof(name), "%s-id", find_name);
99 		ret = ofnode_read_u32_index(node, name, index, &id);
100 		if (ret)
101 			return log_msg_ret("id", -ENOENT);
102 
103 		if (id >= info->str_count)
104 			return log_msg_ret("id", -E2BIG);
105 		text = info->str_for_id[id];
106 		if (!text)
107 			return log_msg_ret("id", -EINVAL);
108 	}
109 
110 	ret = scene_txt_str(scn, find_name, obj_id, 0, text, NULL);
111 	if (ret < 0)
112 		return log_msg_ret("add", ret);
113 
114 	return ret;
115 }
116 
117 /*
118  * build_element() - Handle creating a text object from a label
119  *
120  * Look up a property called @label or @label-id and create a string for it
121  */
build_element(void * ldtb,int node,const char * label)122 int build_element(void *ldtb, int node, const char *label)
123 {
124 	return 0;
125 }
126 
127 /**
128  * read_strings() - Read in the list of strings
129  *
130  * Read the strings into an ID-indexed list, so they can be used for building
131  * an expo. The strings are in a /strings node and each has its own subnode
132  * containing the ID and the string itself:
133  *
134  * example {
135  *    id = <123>;
136  *    value = "This is a test";
137  * };
138  *
139  * Future work may add support for unicode and multiple languages
140  *
141  * @info: Build information
142  * @root: Root node to read from
143  * Returns: 0 if OK, -ENOMEM if out of memory, -EINVAL if there is a format
144  * error
145  */
read_strings(struct build_info * info,ofnode root)146 static int read_strings(struct build_info *info, ofnode root)
147 {
148 	ofnode strings, node;
149 
150 	strings = ofnode_find_subnode(root, "strings");
151 	if (!ofnode_valid(strings))
152 		return log_msg_ret("str", -EINVAL);
153 
154 	ofnode_for_each_subnode(node, strings) {
155 		const char *val;
156 		int ret;
157 		u32 id;
158 
159 		info->err_node = node;
160 		ret = ofnode_read_u32(node, "id", &id);
161 		if (ret)
162 			return log_msg_ret("id", -ENOENT);
163 		val = ofnode_read_string(node, "value");
164 		if (!val)
165 			return log_msg_ret("val", -EINVAL);
166 
167 		if (id >= info->str_count) {
168 			int new_count = info->str_count + 20;
169 			void *new_arr;
170 
171 			new_arr = realloc(info->str_for_id,
172 					  new_count * sizeof(char *));
173 			if (!new_arr)
174 				return log_msg_ret("id", -ENOMEM);
175 			memset(new_arr + info->str_count, '\0',
176 			       (new_count - info->str_count) * sizeof(char *));
177 			info->str_for_id = new_arr;
178 			info->str_count = new_count;
179 		}
180 
181 		info->str_for_id[id] = val;
182 	}
183 
184 	return 0;
185 }
186 
187 /**
188  * list_strings() - List the available strings with their IDs
189  *
190  * @info: Build information
191  */
list_strings(struct build_info * info)192 static void list_strings(struct build_info *info)
193 {
194 	int i;
195 
196 	for (i = 0; i < info->str_count; i++) {
197 		if (info->str_for_id[i])
198 			printf("%3d %s\n", i, info->str_for_id[i]);
199 	}
200 }
201 
202 /**
203  * menu_build() - Build a menu and add it to a scene
204  *
205  * See doc/develop/expo.rst for a description of the format
206  *
207  * @info: Build information
208  * @node: Node containing the menu description
209  * @scn: Scene to add the menu to
210  * @id: ID for the menu
211  * @objp: Returns the object pointer
212  * Returns: 0 if OK, -ENOMEM if out of memory, -EINVAL if there is a format
213  * error, -ENOENT if there is a references to a non-existent string
214  */
menu_build(struct build_info * info,ofnode node,struct scene * scn,uint id,struct scene_obj ** objp)215 static int menu_build(struct build_info *info, ofnode node, struct scene *scn,
216 		      uint id, struct scene_obj **objp)
217 {
218 	const u32 *item_ids, *item_values;
219 	struct scene_obj_menu *menu;
220 	int ret, size, i, num_items;
221 	uint title_id, menu_id;
222 	const char *name;
223 
224 	name = ofnode_get_name(node);
225 
226 	ret = scene_menu(scn, name, id, &menu);
227 	if (ret < 0)
228 		return log_msg_ret("men", ret);
229 	menu_id = ret;
230 
231 	/* Set the title */
232 	ret = add_txt_str(info, node, scn, "title", 0);
233 	if (ret < 0)
234 		return log_msg_ret("tit", ret);
235 	title_id = ret;
236 	ret = scene_menu_set_title(scn, menu_id, title_id);
237 	if (ret)
238 		return log_msg_ret("set", ret);
239 
240 	item_ids = ofnode_read_prop(node, "item-id", &size);
241 	if (!item_ids)
242 		return log_msg_ret("itm", -EINVAL);
243 	if (!size || size % sizeof(u32))
244 		return log_msg_ret("isz", -EINVAL);
245 	num_items = size / sizeof(u32);
246 
247 	item_values = ofnode_read_prop(node, "item-value", &size);
248 	if (item_values) {
249 		if (size != num_items * sizeof(u32))
250 			return log_msg_ret("vsz", -EINVAL);
251 	}
252 
253 	for (i = 0; i < num_items; i++) {
254 		struct scene_menitem *item;
255 		uint label, key, desc;
256 
257 		ret = add_txt_str_list(info, node, scn, "item-label", i, 0);
258 		if (ret < 0 && ret != -ENOENT)
259 			return log_msg_ret("lab", ret);
260 		label = max(0, ret);
261 
262 		ret = add_txt_str_list(info, node, scn, "key-label", i, 0);
263 		if (ret < 0 && ret != -ENOENT)
264 			return log_msg_ret("key", ret);
265 		key = max(0, ret);
266 
267 		ret = add_txt_str_list(info, node, scn, "desc-label", i, 0);
268 		if (ret < 0  && ret != -ENOENT)
269 			return log_msg_ret("lab", ret);
270 		desc = max(0, ret);
271 
272 		ret = scene_menuitem(scn, menu_id, simple_xtoa(i),
273 				     fdt32_to_cpu(item_ids[i]), key, label,
274 				     desc, 0, 0, &item);
275 		if (ret < 0)
276 			return log_msg_ret("mi", ret);
277 		if (item_values)
278 			item->value = fdt32_to_cpu(item_values[i]);
279 	}
280 	*objp = &menu->obj;
281 
282 	return 0;
283 }
284 
textline_build(struct build_info * info,ofnode node,struct scene * scn,uint id,struct scene_obj ** objp)285 static int textline_build(struct build_info *info, ofnode node,
286 			  struct scene *scn, uint id, struct scene_obj **objp)
287 {
288 	struct scene_obj_textline *ted;
289 	uint ted_id, edit_id;
290 	const char *name;
291 	u32 max_chars;
292 	int ret;
293 
294 	name = ofnode_get_name(node);
295 
296 	info->err_prop = "max-chars";
297 	ret = ofnode_read_u32(node, "max-chars", &max_chars);
298 	if (ret)
299 		return log_msg_ret("max", -ENOENT);
300 
301 	ret = scene_textline(scn, name, id, max_chars, &ted);
302 	if (ret < 0)
303 		return log_msg_ret("ted", ret);
304 	ted_id = ret;
305 
306 	/* Set the title */
307 	ret = add_txt_str(info, node, scn, "title", 0);
308 	if (ret < 0)
309 		return log_msg_ret("tit", ret);
310 	ted->label_id = ret;
311 
312 	/* Setup the editor */
313 	info->err_prop = "edit-id";
314 	ret = ofnode_read_u32(node, "edit-id", &id);
315 	if (ret)
316 		return log_msg_ret("id", -ENOENT);
317 	edit_id = ret;
318 
319 	ret = scene_txt_str(scn, "edit", edit_id, 0, abuf_data(&ted->buf),
320 			    NULL);
321 	if (ret < 0)
322 		return log_msg_ret("add", ret);
323 	ted->edit_id = ret;
324 
325 	return 0;
326 }
327 
328 /**
329  * obj_build() - Build an expo object and add it to a scene
330  *
331  * See doc/develop/expo.rst for a description of the format
332  *
333  * @info: Build information
334  * @node: Node containing the object description
335  * @scn: Scene to add the object to
336  * Returns: 0 if OK, -ENOMEM if out of memory, -EINVAL if there is a format
337  * error, -ENOENT if there is a references to a non-existent string
338  */
obj_build(struct build_info * info,ofnode node,struct scene * scn)339 static int obj_build(struct build_info *info, ofnode node, struct scene *scn)
340 {
341 	struct scene_obj *obj;
342 	const char *type;
343 	u32 id, val;
344 	int ret;
345 
346 	log_debug("- object %s\n", ofnode_get_name(node));
347 	ret = ofnode_read_u32(node, "id", &id);
348 	if (ret)
349 		return log_msg_ret("id", -ENOENT);
350 
351 	type = ofnode_read_string(node, "type");
352 	if (!type)
353 		return log_msg_ret("typ", -EINVAL);
354 
355 	if (!strcmp("menu", type))
356 		ret = menu_build(info, node, scn, id, &obj);
357 	else if (!strcmp("textline", type))
358 		ret = textline_build(info, node, scn, id, &obj);
359 	else
360 		ret = -EOPNOTSUPP;
361 	if (ret)
362 		return log_msg_ret("bld", ret);
363 
364 	if (!ofnode_read_u32(node, "start-bit", &val))
365 		obj->start_bit = val;
366 	if (!ofnode_read_u32(node, "bit-length", &val))
367 		obj->bit_length = val;
368 
369 	return 0;
370 }
371 
372 /**
373  * scene_build() - Build a scene and all its objects
374  *
375  * See doc/develop/expo.rst for a description of the format
376  *
377  * @info: Build information
378  * @node: Node containing the scene description
379  * @scn: Scene to add the object to
380  * Returns: 0 if OK, -ENOMEM if out of memory, -EINVAL if there is a format
381  * error, -ENOENT if there is a references to a non-existent string
382  */
scene_build(struct build_info * info,ofnode scn_node,struct expo * exp)383 static int scene_build(struct build_info *info, ofnode scn_node,
384 		       struct expo *exp)
385 {
386 	const char *name;
387 	struct scene *scn;
388 	uint id, title_id;
389 	ofnode node;
390 	int ret;
391 
392 	info->err_node = scn_node;
393 	name = ofnode_get_name(scn_node);
394 	log_debug("Building scene %s\n", name);
395 	ret = ofnode_read_u32(scn_node, "id", &id);
396 	if (ret)
397 		return log_msg_ret("id", -ENOENT);
398 
399 	ret = scene_new(exp, name, id, &scn);
400 	if (ret < 0)
401 		return log_msg_ret("scn", ret);
402 
403 	ret = add_txt_str(info, scn_node, scn, "title", 0);
404 	if (ret < 0)
405 		return log_msg_ret("tit", ret);
406 	title_id = ret;
407 	scn->title_id = title_id;
408 
409 	ret = add_txt_str(info, scn_node, scn, "prompt", 0);
410 	if (ret < 0)
411 		return log_msg_ret("pr", ret);
412 
413 	ofnode_for_each_subnode(node, scn_node) {
414 		info->err_node = node;
415 		ret = obj_build(info, node, scn);
416 		if (ret < 0)
417 			return log_msg_ret("mit", ret);
418 	}
419 
420 	return 0;
421 }
422 
build_it(struct build_info * info,ofnode root,struct expo ** expp)423 static int build_it(struct build_info *info, ofnode root, struct expo **expp)
424 {
425 	ofnode scenes, node;
426 	struct expo *exp;
427 	u32 dyn_start;
428 	int ret;
429 
430 	ret = read_strings(info, root);
431 	if (ret)
432 		return log_msg_ret("str", ret);
433 	if (_DEBUG)
434 		list_strings(info);
435 	info->err_node = root;
436 
437 	ret = expo_new("name", NULL, &exp);
438 	if (ret)
439 		return log_msg_ret("exp", ret);
440 
441 	if (!ofnode_read_u32(root, "dynamic-start", &dyn_start))
442 		expo_set_dynamic_start(exp, dyn_start);
443 
444 	scenes = ofnode_find_subnode(root, "scenes");
445 	if (!ofnode_valid(scenes))
446 		return log_msg_ret("sno", -EINVAL);
447 
448 	ofnode_for_each_subnode(node, scenes) {
449 		ret = scene_build(info, node, exp);
450 		if (ret < 0)
451 			return log_msg_ret("scn", ret);
452 	}
453 	*expp = exp;
454 
455 	return 0;
456 }
457 
expo_build(ofnode root,struct expo ** expp)458 int expo_build(ofnode root, struct expo **expp)
459 {
460 	struct build_info info;
461 	struct expo *exp;
462 	int ret;
463 
464 	memset(&info, '\0', sizeof(info));
465 	ret = build_it(&info, root, &exp);
466 	if (ret) {
467 		char buf[120];
468 		int node_ret;
469 
470 		node_ret = ofnode_get_path(info.err_node, buf, sizeof(buf));
471 		log_warning("Build failed at node %s, property %s\n",
472 			    node_ret ? ofnode_get_name(info.err_node) : buf,
473 			    info.err_prop);
474 
475 		return log_msg_ret("bui", ret);
476 	}
477 	*expp = exp;
478 
479 	return 0;
480 }
481