blob: bb33cc2a33f438cd5212ed641930cf6193913766 [file] [log] [blame]
Simon Glass82cafee2023-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
11#include <common.h>
12#include <expo.h>
13#include <fdtdec.h>
14#include <log.h>
15#include <malloc.h>
16#include <dm/ofnode.h>
17#include <linux/libfdt.h>
18
19/**
20 * struct build_info - Information to use when building
21 *
22 * @str_for_id: String for each ID in use, NULL if empty. The string is NULL
23 * if there is nothing for this ID. Since ID 0 is never used, the first
24 * element of this array is always NULL
25 * @str_count: Number of entries in @str_for_id
26 */
27struct build_info {
28 const char **str_for_id;
29 int str_count;
30};
31
32/**
33 * add_txt_str - Add a string or lookup its ID, then add to expo
34 *
35 * @info: Build information
36 * @node: Node describing scene
37 * @scn: Scene to add to
38 * @find_name: Name to look for (e.g. "title"). This will find a property called
39 * "title" if it exists, else will look up the string for "title-id"
40 * Return: ID of added string, or -ve on error
41 */
42int add_txt_str(struct build_info *info, ofnode node, struct scene *scn,
43 const char *find_name, uint obj_id)
44{
45 const char *text;
46 uint str_id;
47 int ret;
48
49 text = ofnode_read_string(node, find_name);
50 if (!text) {
51 char name[40];
52 u32 id;
53
54 snprintf(name, sizeof(name), "%s-id", find_name);
55 ret = ofnode_read_u32(node, name, &id);
56 if (ret)
57 return log_msg_ret("id", -EINVAL);
58
59 if (id >= info->str_count)
60 return log_msg_ret("id", -E2BIG);
61 text = info->str_for_id[id];
62 if (!text)
63 return log_msg_ret("id", -EINVAL);
64 }
65
66 ret = expo_str(scn->expo, find_name, 0, text);
67 if (ret < 0)
68 return log_msg_ret("add", ret);
69 str_id = ret;
70
71 ret = scene_txt_str(scn, find_name, obj_id, str_id, text, NULL);
72 if (ret < 0)
73 return log_msg_ret("add", ret);
74
75 return ret;
76}
77
78/**
79 * add_txt_str_list - Add a list string or lookup its ID, then add to expo
80 *
81 * @info: Build information
82 * @node: Node describing scene
83 * @scn: Scene to add to
84 * @find_name: Name to look for (e.g. "title"). This will find a string-list
85 * property called "title" if it exists, else will look up the string in the
86 * "title-id" string list.
87 * Return: ID of added string, or -ve on error
88 */
89int add_txt_str_list(struct build_info *info, ofnode node, struct scene *scn,
90 const char *find_name, int index, uint obj_id)
91{
92 const char *text;
93 uint str_id;
94 int ret;
95
96 ret = ofnode_read_string_index(node, find_name, index, &text);
97 if (ret) {
98 char name[40];
99 u32 id;
100
101 snprintf(name, sizeof(name), "%s-id", find_name);
102 ret = ofnode_read_u32_index(node, name, index, &id);
103 if (ret)
104 return log_msg_ret("id", -ENOENT);
105
106 if (id >= info->str_count)
107 return log_msg_ret("id", -E2BIG);
108 text = info->str_for_id[id];
109 if (!text)
110 return log_msg_ret("id", -EINVAL);
111 }
112
113 ret = expo_str(scn->expo, find_name, 0, text);
114 if (ret < 0)
115 return log_msg_ret("add", ret);
116 str_id = ret;
117
118 ret = scene_txt_str(scn, find_name, obj_id, str_id, text, NULL);
119 if (ret < 0)
120 return log_msg_ret("add", ret);
121
122 return ret;
123}
124
125/*
126 * build_element() - Handle creating a text object from a label
127 *
128 * Look up a property called @label or @label-id and create a string for it
129 */
130int build_element(void *ldtb, int node, const char *label)
131{
132 return 0;
133}
134
135/**
136 * read_strings() - Read in the list of strings
137 *
138 * Read the strings into an ID-indexed list, so they can be used for building
139 * an expo. The strings are in a /strings node and each has its own subnode
140 * containing the ID and the string itself:
141 *
142 * example {
143 * id = <123>;
144 * value = "This is a test";
145 * };
146 *
147 * Future work may add support for unicode and multiple languages
148 *
149 * @info: Build information
150 * @root: Root node to read from
151 * Returns: 0 if OK, -ENOMEM if out of memory, -EINVAL if there is a format
152 * error
153 */
154static int read_strings(struct build_info *info, ofnode root)
155{
156 ofnode strings, node;
157
158 strings = ofnode_find_subnode(root, "strings");
159 if (!ofnode_valid(strings))
160 return log_msg_ret("str", -EINVAL);
161
162 ofnode_for_each_subnode(node, strings) {
163 const char *val;
164 int ret;
165 u32 id;
166
167 ret = ofnode_read_u32(node, "id", &id);
168 if (ret)
169 return log_msg_ret("id", -EINVAL);
170 val = ofnode_read_string(node, "value");
171 if (!val)
172 return log_msg_ret("val", -EINVAL);
173
174 if (id >= info->str_count) {
175 int new_count = info->str_count + 20;
176 void *new_arr;
177
178 new_arr = realloc(info->str_for_id,
179 new_count * sizeof(char *));
180 if (!new_arr)
181 return log_msg_ret("id", -ENOMEM);
182 memset(new_arr + info->str_count, '\0',
183 (new_count - info->str_count) * sizeof(char *));
184 info->str_for_id = new_arr;
185 info->str_count = new_count;
186 }
187
188 info->str_for_id[id] = val;
189 }
190
191 return 0;
192}
193
194/**
195 * list_strings() - List the available strings with their IDs
196 *
197 * @info: Build information
198 */
199static void list_strings(struct build_info *info)
200{
201 int i;
202
203 for (i = 0; i < info->str_count; i++) {
204 if (info->str_for_id[i])
205 printf("%3d %s\n", i, info->str_for_id[i]);
206 }
207}
208
209/**
210 * menu_build() - Build a menu and add it to a scene
211 *
212 * See doc/developer/expo.rst for a description of the format
213 *
214 * @info: Build information
215 * @node: Node containing the menu description
216 * @scn: Scene to add the menu to
Simon Glass431b21f2023-08-14 16:40:24 -0600217 * @id: ID for the menu
218 * @objp: Returns the object pointer
Simon Glass82cafee2023-06-01 10:23:01 -0600219 * Returns: 0 if OK, -ENOMEM if out of memory, -EINVAL if there is a format
220 * error, -ENOENT if there is a references to a non-existent string
221 */
Simon Glass431b21f2023-08-14 16:40:24 -0600222static int menu_build(struct build_info *info, ofnode node, struct scene *scn,
223 uint id, struct scene_obj **objp)
Simon Glass82cafee2023-06-01 10:23:01 -0600224{
225 struct scene_obj_menu *menu;
226 uint title_id, menu_id;
227 const u32 *item_ids;
228 int ret, size, i;
229 const char *name;
Simon Glass82cafee2023-06-01 10:23:01 -0600230
231 name = ofnode_get_name(node);
Simon Glass82cafee2023-06-01 10:23:01 -0600232
233 ret = scene_menu(scn, name, id, &menu);
234 if (ret < 0)
235 return log_msg_ret("men", ret);
236 menu_id = ret;
237
238 /* Set the title */
239 ret = add_txt_str(info, node, scn, "title", 0);
240 if (ret < 0)
241 return log_msg_ret("tit", ret);
242 title_id = ret;
243 ret = scene_menu_set_title(scn, menu_id, title_id);
244
245 item_ids = ofnode_read_prop(node, "item-id", &size);
246 if (!item_ids)
247 return log_msg_ret("itm", -EINVAL);
248 if (!size || size % sizeof(u32))
249 return log_msg_ret("isz", -EINVAL);
250 size /= sizeof(u32);
251
252 for (i = 0; i < size; i++) {
253 struct scene_menitem *item;
254 uint label, key, desc;
255
256 ret = add_txt_str_list(info, node, scn, "item-label", i, 0);
257 if (ret < 0 && ret != -ENOENT)
258 return log_msg_ret("lab", ret);
259 label = max(0, ret);
260
261 ret = add_txt_str_list(info, node, scn, "key-label", i, 0);
262 if (ret < 0 && ret != -ENOENT)
263 return log_msg_ret("key", ret);
264 key = max(0, ret);
265
266 ret = add_txt_str_list(info, node, scn, "desc-label", i, 0);
267 if (ret < 0 && ret != -ENOENT)
268 return log_msg_ret("lab", ret);
269 desc = max(0, ret);
270
271 ret = scene_menuitem(scn, menu_id, simple_xtoa(i),
272 fdt32_to_cpu(item_ids[i]), key, label,
273 desc, 0, 0, &item);
274 if (ret < 0)
275 return log_msg_ret("mi", ret);
276 }
Simon Glass431b21f2023-08-14 16:40:24 -0600277 *objp = &menu->obj;
Simon Glass82cafee2023-06-01 10:23:01 -0600278
279 return 0;
280}
281
282/**
Simon Glass431b21f2023-08-14 16:40:24 -0600283 * obj_build() - Build an expo object and add it to a scene
Simon Glass82cafee2023-06-01 10:23:01 -0600284 *
285 * See doc/developer/expo.rst for a description of the format
286 *
287 * @info: Build information
288 * @node: Node containing the object description
289 * @scn: Scene to add the object to
290 * Returns: 0 if OK, -ENOMEM if out of memory, -EINVAL if there is a format
291 * error, -ENOENT if there is a references to a non-existent string
292 */
293static int obj_build(struct build_info *info, ofnode node, struct scene *scn)
294{
Simon Glass431b21f2023-08-14 16:40:24 -0600295 struct scene_obj *obj;
Simon Glass82cafee2023-06-01 10:23:01 -0600296 const char *type;
Simon Glasseb6c71b2023-08-14 16:40:37 -0600297 u32 id, val;
Simon Glass82cafee2023-06-01 10:23:01 -0600298 int ret;
299
300 log_debug("- object %s\n", ofnode_get_name(node));
301 ret = ofnode_read_u32(node, "id", &id);
302 if (ret)
303 return log_msg_ret("id", -EINVAL);
304
305 type = ofnode_read_string(node, "type");
306 if (!type)
307 return log_msg_ret("typ", -EINVAL);
308
309 if (!strcmp("menu", type))
Simon Glass431b21f2023-08-14 16:40:24 -0600310 ret = menu_build(info, node, scn, id, &obj);
Simon Glass82cafee2023-06-01 10:23:01 -0600311 else
312 ret = -EINVAL;
313 if (ret)
314 return log_msg_ret("bld", ret);
315
Simon Glasseb6c71b2023-08-14 16:40:37 -0600316 if (!ofnode_read_u32(node, "start-bit", &val))
317 obj->start_bit = val;
318 if (!ofnode_read_u32(node, "bit-length", &val))
319 obj->bit_length = val;
320
Simon Glass82cafee2023-06-01 10:23:01 -0600321 return 0;
322}
323
324/**
325 * scene_build() - Build a scene and all its objects
326 *
327 * See doc/developer/expo.rst for a description of the format
328 *
329 * @info: Build information
330 * @node: Node containing the scene description
331 * @scn: Scene to add the object to
332 * Returns: 0 if OK, -ENOMEM if out of memory, -EINVAL if there is a format
333 * error, -ENOENT if there is a references to a non-existent string
334 */
335static int scene_build(struct build_info *info, ofnode scn_node,
336 struct expo *exp)
337{
338 const char *name;
339 struct scene *scn;
340 uint id, title_id;
341 ofnode node;
342 int ret;
343
344 name = ofnode_get_name(scn_node);
345 log_debug("Building scene %s\n", name);
346 ret = ofnode_read_u32(scn_node, "id", &id);
347 if (ret)
348 return log_msg_ret("id", -EINVAL);
349
350 ret = scene_new(exp, name, id, &scn);
351 if (ret < 0)
352 return log_msg_ret("scn", ret);
353
354 ret = add_txt_str(info, scn_node, scn, "title", 0);
355 if (ret < 0)
356 return log_msg_ret("tit", ret);
357 title_id = ret;
358 scene_title_set(scn, title_id);
359
360 ret = add_txt_str(info, scn_node, scn, "prompt", 0);
361 if (ret < 0)
362 return log_msg_ret("pr", ret);
363
364 ofnode_for_each_subnode(node, scn_node) {
365 ret = obj_build(info, node, scn);
366 if (ret < 0)
367 return log_msg_ret("mit", ret);
368 }
369
370 return 0;
371}
372
373int expo_build(ofnode root, struct expo **expp)
374{
375 struct build_info info;
376 ofnode scenes, node;
377 struct expo *exp;
378 u32 dyn_start;
379 int ret;
380
381 memset(&info, '\0', sizeof(info));
382 ret = read_strings(&info, root);
383 if (ret)
384 return log_msg_ret("str", ret);
Simon Glassa0874dc2023-06-01 10:23:02 -0600385 if (_DEBUG)
386 list_strings(&info);
Simon Glass82cafee2023-06-01 10:23:01 -0600387
388 ret = expo_new("name", NULL, &exp);
389 if (ret)
390 return log_msg_ret("exp", ret);
391
392 if (!ofnode_read_u32(root, "dynamic-start", &dyn_start))
393 expo_set_dynamic_start(exp, dyn_start);
394
395 scenes = ofnode_find_subnode(root, "scenes");
396 if (!ofnode_valid(scenes))
397 return log_msg_ret("sno", -EINVAL);
398
399 ofnode_for_each_subnode(node, scenes) {
400 ret = scene_build(&info, node, exp);
401 if (ret < 0)
402 return log_msg_ret("scn", ret);
403 }
404 *expp = exp;
405
406 return 0;
407}