blob: 22f62eb54bc536edc77d0d8386f8d326509ad584 [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
217 * Returns: 0 if OK, -ENOMEM if out of memory, -EINVAL if there is a format
218 * error, -ENOENT if there is a references to a non-existent string
219 */
220static int menu_build(struct build_info *info, ofnode node, struct scene *scn)
221{
222 struct scene_obj_menu *menu;
223 uint title_id, menu_id;
224 const u32 *item_ids;
225 int ret, size, i;
226 const char *name;
227 u32 id;
228
229 name = ofnode_get_name(node);
230 ret = ofnode_read_u32(node, "id", &id);
231 if (ret)
232 return log_msg_ret("id", -EINVAL);
233
234 ret = scene_menu(scn, name, id, &menu);
235 if (ret < 0)
236 return log_msg_ret("men", ret);
237 menu_id = ret;
238
239 /* Set the title */
240 ret = add_txt_str(info, node, scn, "title", 0);
241 if (ret < 0)
242 return log_msg_ret("tit", ret);
243 title_id = ret;
244 ret = scene_menu_set_title(scn, menu_id, title_id);
245
246 item_ids = ofnode_read_prop(node, "item-id", &size);
247 if (!item_ids)
248 return log_msg_ret("itm", -EINVAL);
249 if (!size || size % sizeof(u32))
250 return log_msg_ret("isz", -EINVAL);
251 size /= sizeof(u32);
252
253 for (i = 0; i < size; 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 }
278
279 return 0;
280}
281
282/**
283 * menu_build() - Build an expo object and add it to a scene
284 *
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{
295 const char *type;
296 u32 id;
297 int ret;
298
299 log_debug("- object %s\n", ofnode_get_name(node));
300 ret = ofnode_read_u32(node, "id", &id);
301 if (ret)
302 return log_msg_ret("id", -EINVAL);
303
304 type = ofnode_read_string(node, "type");
305 if (!type)
306 return log_msg_ret("typ", -EINVAL);
307
308 if (!strcmp("menu", type))
309 ret = menu_build(info, node, scn);
310 else
311 ret = -EINVAL;
312 if (ret)
313 return log_msg_ret("bld", ret);
314
315 return 0;
316}
317
318/**
319 * scene_build() - Build a scene and all its objects
320 *
321 * See doc/developer/expo.rst for a description of the format
322 *
323 * @info: Build information
324 * @node: Node containing the scene description
325 * @scn: Scene to add the object to
326 * Returns: 0 if OK, -ENOMEM if out of memory, -EINVAL if there is a format
327 * error, -ENOENT if there is a references to a non-existent string
328 */
329static int scene_build(struct build_info *info, ofnode scn_node,
330 struct expo *exp)
331{
332 const char *name;
333 struct scene *scn;
334 uint id, title_id;
335 ofnode node;
336 int ret;
337
338 name = ofnode_get_name(scn_node);
339 log_debug("Building scene %s\n", name);
340 ret = ofnode_read_u32(scn_node, "id", &id);
341 if (ret)
342 return log_msg_ret("id", -EINVAL);
343
344 ret = scene_new(exp, name, id, &scn);
345 if (ret < 0)
346 return log_msg_ret("scn", ret);
347
348 ret = add_txt_str(info, scn_node, scn, "title", 0);
349 if (ret < 0)
350 return log_msg_ret("tit", ret);
351 title_id = ret;
352 scene_title_set(scn, title_id);
353
354 ret = add_txt_str(info, scn_node, scn, "prompt", 0);
355 if (ret < 0)
356 return log_msg_ret("pr", ret);
357
358 ofnode_for_each_subnode(node, scn_node) {
359 ret = obj_build(info, node, scn);
360 if (ret < 0)
361 return log_msg_ret("mit", ret);
362 }
363
364 return 0;
365}
366
367int expo_build(ofnode root, struct expo **expp)
368{
369 struct build_info info;
370 ofnode scenes, node;
371 struct expo *exp;
372 u32 dyn_start;
373 int ret;
374
375 memset(&info, '\0', sizeof(info));
376 ret = read_strings(&info, root);
377 if (ret)
378 return log_msg_ret("str", ret);
Simon Glassa0874dc2023-06-01 10:23:02 -0600379 if (_DEBUG)
380 list_strings(&info);
Simon Glass82cafee2023-06-01 10:23:01 -0600381
382 ret = expo_new("name", NULL, &exp);
383 if (ret)
384 return log_msg_ret("exp", ret);
385
386 if (!ofnode_read_u32(root, "dynamic-start", &dyn_start))
387 expo_set_dynamic_start(exp, dyn_start);
388
389 scenes = ofnode_find_subnode(root, "scenes");
390 if (!ofnode_valid(scenes))
391 return log_msg_ret("sno", -EINVAL);
392
393 ofnode_for_each_subnode(node, scenes) {
394 ret = scene_build(&info, node, exp);
395 if (ret < 0)
396 return log_msg_ret("scn", ret);
397 }
398 *expp = exp;
399
400 return 0;
401}