blob: d97347e1725381c366eb25085ccb0b2885564748 [file] [log] [blame]
Simon Glass61300722023-06-01 10:23:01 -06001// 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
Simon Glass61300722023-06-01 10:23:01 -060011#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
Simon Glassb3a8b0a2023-10-01 19:13:22 -060025 * @err_node: Node being processed (for error reporting)
26 * @err_prop: Property being processed (for error reporting)
Simon Glass61300722023-06-01 10:23:01 -060027 */
28struct build_info {
29 const char **str_for_id;
30 int str_count;
Simon Glassb3a8b0a2023-10-01 19:13:22 -060031 ofnode err_node;
32 const char *err_prop;
Simon Glass61300722023-06-01 10:23:01 -060033};
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 */
45int 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;
Simon Glass61300722023-06-01 10:23:01 -060049 int ret;
50
Simon Glassb3a8b0a2023-10-01 19:13:22 -060051 info->err_prop = find_name;
Simon Glass61300722023-06-01 10:23:01 -060052 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)
Simon Glassb3a8b0a2023-10-01 19:13:22 -060060 return log_msg_ret("id", -ENOENT);
Simon Glass61300722023-06-01 10:23:01 -060061
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
Simon Glass1f4bbff2024-10-14 16:32:01 -060069 ret = scene_txt_str(scn, find_name, obj_id, 0, text, NULL);
Simon Glass61300722023-06-01 10:23:01 -060070 if (ret < 0)
71 return log_msg_ret("add", ret);
Simon Glass61300722023-06-01 10:23:01 -060072
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 */
87int 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;
Simon Glass61300722023-06-01 10:23:01 -060091 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
Simon Glass1f4bbff2024-10-14 16:32:01 -0600110 ret = scene_txt_str(scn, find_name, obj_id, 0, text, NULL);
Simon Glass61300722023-06-01 10:23:01 -0600111 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 */
122int 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 */
146static 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
Simon Glassb3a8b0a2023-10-01 19:13:22 -0600159 info->err_node = node;
Simon Glass61300722023-06-01 10:23:01 -0600160 ret = ofnode_read_u32(node, "id", &id);
161 if (ret)
Simon Glassb3a8b0a2023-10-01 19:13:22 -0600162 return log_msg_ret("id", -ENOENT);
Simon Glass61300722023-06-01 10:23:01 -0600163 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 */
192static 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 *
Massimo Pegorerc8c70022023-09-09 12:32:28 +0200205 * See doc/develop/expo.rst for a description of the format
Simon Glass61300722023-06-01 10:23:01 -0600206 *
207 * @info: Build information
208 * @node: Node containing the menu description
209 * @scn: Scene to add the menu to
Simon Glass50b1e342023-08-14 16:40:24 -0600210 * @id: ID for the menu
211 * @objp: Returns the object pointer
Simon Glass61300722023-06-01 10:23:01 -0600212 * 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 */
Simon Glass50b1e342023-08-14 16:40:24 -0600215static int menu_build(struct build_info *info, ofnode node, struct scene *scn,
216 uint id, struct scene_obj **objp)
Simon Glass61300722023-06-01 10:23:01 -0600217{
Simon Glass100389f2024-10-14 16:31:58 -0600218 const u32 *item_ids, *item_values;
Simon Glass61300722023-06-01 10:23:01 -0600219 struct scene_obj_menu *menu;
Simon Glass100389f2024-10-14 16:31:58 -0600220 int ret, size, i, num_items;
Simon Glass61300722023-06-01 10:23:01 -0600221 uint title_id, menu_id;
Simon Glass61300722023-06-01 10:23:01 -0600222 const char *name;
Simon Glass61300722023-06-01 10:23:01 -0600223
224 name = ofnode_get_name(node);
Simon Glass61300722023-06-01 10:23:01 -0600225
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);
Simon Glassb3a8b0a2023-10-01 19:13:22 -0600237 if (ret)
238 return log_msg_ret("set", ret);
Simon Glass61300722023-06-01 10:23:01 -0600239
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);
Simon Glass100389f2024-10-14 16:31:58 -0600245 num_items = size / sizeof(u32);
Simon Glass61300722023-06-01 10:23:01 -0600246
Simon Glass100389f2024-10-14 16:31:58 -0600247 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++) {
Simon Glass61300722023-06-01 10:23:01 -0600254 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);
Simon Glass100389f2024-10-14 16:31:58 -0600277 if (item_values)
278 item->value = fdt32_to_cpu(item_values[i]);
Simon Glass61300722023-06-01 10:23:01 -0600279 }
Simon Glass50b1e342023-08-14 16:40:24 -0600280 *objp = &menu->obj;
Simon Glass61300722023-06-01 10:23:01 -0600281
282 return 0;
283}
284
Simon Glassc7c751b2023-10-01 19:13:38 -0600285static 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
Simon Glass61300722023-06-01 10:23:01 -0600328/**
Simon Glass50b1e342023-08-14 16:40:24 -0600329 * obj_build() - Build an expo object and add it to a scene
Simon Glass61300722023-06-01 10:23:01 -0600330 *
Massimo Pegorerc8c70022023-09-09 12:32:28 +0200331 * See doc/develop/expo.rst for a description of the format
Simon Glass61300722023-06-01 10:23:01 -0600332 *
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 */
339static int obj_build(struct build_info *info, ofnode node, struct scene *scn)
340{
Simon Glass50b1e342023-08-14 16:40:24 -0600341 struct scene_obj *obj;
Simon Glass61300722023-06-01 10:23:01 -0600342 const char *type;
Simon Glass2b91ca62023-08-14 16:40:37 -0600343 u32 id, val;
Simon Glass61300722023-06-01 10:23:01 -0600344 int ret;
345
346 log_debug("- object %s\n", ofnode_get_name(node));
347 ret = ofnode_read_u32(node, "id", &id);
348 if (ret)
Simon Glassb3a8b0a2023-10-01 19:13:22 -0600349 return log_msg_ret("id", -ENOENT);
Simon Glass61300722023-06-01 10:23:01 -0600350
351 type = ofnode_read_string(node, "type");
352 if (!type)
353 return log_msg_ret("typ", -EINVAL);
354
355 if (!strcmp("menu", type))
Simon Glass50b1e342023-08-14 16:40:24 -0600356 ret = menu_build(info, node, scn, id, &obj);
Simon Glassc7c751b2023-10-01 19:13:38 -0600357 else if (!strcmp("textline", type))
358 ret = textline_build(info, node, scn, id, &obj);
Simon Glassb3a8b0a2023-10-01 19:13:22 -0600359 else
360 ret = -EOPNOTSUPP;
Simon Glass61300722023-06-01 10:23:01 -0600361 if (ret)
362 return log_msg_ret("bld", ret);
363
Simon Glass2b91ca62023-08-14 16:40:37 -0600364 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
Simon Glass61300722023-06-01 10:23:01 -0600369 return 0;
370}
371
372/**
373 * scene_build() - Build a scene and all its objects
374 *
Massimo Pegorerc8c70022023-09-09 12:32:28 +0200375 * See doc/develop/expo.rst for a description of the format
Simon Glass61300722023-06-01 10:23:01 -0600376 *
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 */
383static 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
Simon Glassb3a8b0a2023-10-01 19:13:22 -0600392 info->err_node = scn_node;
Simon Glass61300722023-06-01 10:23:01 -0600393 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)
Simon Glassb3a8b0a2023-10-01 19:13:22 -0600397 return log_msg_ret("id", -ENOENT);
Simon Glass61300722023-06-01 10:23:01 -0600398
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;
Simon Glassf9577852024-10-14 16:32:02 -0600407 scn->title_id = title_id;
Simon Glass61300722023-06-01 10:23:01 -0600408
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) {
Simon Glassb3a8b0a2023-10-01 19:13:22 -0600414 info->err_node = node;
Simon Glass61300722023-06-01 10:23:01 -0600415 ret = obj_build(info, node, scn);
416 if (ret < 0)
417 return log_msg_ret("mit", ret);
418 }
419
420 return 0;
421}
422
Simon Glassf9577852024-10-14 16:32:02 -0600423static int build_it(struct build_info *info, ofnode root, struct expo **expp)
Simon Glass61300722023-06-01 10:23:01 -0600424{
Simon Glass61300722023-06-01 10:23:01 -0600425 ofnode scenes, node;
426 struct expo *exp;
427 u32 dyn_start;
428 int ret;
429
Simon Glassb3a8b0a2023-10-01 19:13:22 -0600430 ret = read_strings(info, root);
Simon Glass61300722023-06-01 10:23:01 -0600431 if (ret)
432 return log_msg_ret("str", ret);
Simon Glassc8925112023-06-01 10:23:02 -0600433 if (_DEBUG)
Simon Glassb3a8b0a2023-10-01 19:13:22 -0600434 list_strings(info);
435 info->err_node = root;
Simon Glass61300722023-06-01 10:23:01 -0600436
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) {
Simon Glassb3a8b0a2023-10-01 19:13:22 -0600449 ret = scene_build(info, node, exp);
Simon Glass61300722023-06-01 10:23:01 -0600450 if (ret < 0)
451 return log_msg_ret("scn", ret);
452 }
453 *expp = exp;
454
455 return 0;
456}
Simon Glassb3a8b0a2023-10-01 19:13:22 -0600457
458int 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}