]>
Commit | Line | Data |
---|---|---|
82cafee1 SG |
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 <[email protected]> | |
7 | */ | |
8 | ||
9 | #define LOG_CATEGORY LOGC_EXPO | |
10 | ||
82cafee1 SG |
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 | |
5fb9e2aa SG |
25 | * @err_node: Node being processed (for error reporting) |
26 | * @err_prop: Property being processed (for error reporting) | |
82cafee1 SG |
27 | */ |
28 | struct build_info { | |
29 | const char **str_for_id; | |
30 | int str_count; | |
5fb9e2aa SG |
31 | ofnode err_node; |
32 | const char *err_prop; | |
82cafee1 SG |
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 | */ | |
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; | |
82cafee1 SG |
49 | int ret; |
50 | ||
5fb9e2aa | 51 | info->err_prop = find_name; |
82cafee1 SG |
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) | |
5fb9e2aa | 60 | return log_msg_ret("id", -ENOENT); |
82cafee1 SG |
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 | ||
fde70750 | 69 | ret = scene_txt_str(scn, find_name, obj_id, 0, text, NULL); |
82cafee1 SG |
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 | */ | |
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; | |
82cafee1 SG |
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 | ||
fde70750 | 110 | ret = scene_txt_str(scn, find_name, obj_id, 0, text, NULL); |
82cafee1 SG |
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 | */ | |
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 | */ | |
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 | ||
5fb9e2aa | 159 | info->err_node = node; |
82cafee1 SG |
160 | ret = ofnode_read_u32(node, "id", &id); |
161 | if (ret) | |
5fb9e2aa | 162 | return log_msg_ret("id", -ENOENT); |
82cafee1 SG |
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 | */ | |
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 | * | |
a3a057f8 | 205 | * See doc/develop/expo.rst for a description of the format |
82cafee1 SG |
206 | * |
207 | * @info: Build information | |
208 | * @node: Node containing the menu description | |
209 | * @scn: Scene to add the menu to | |
431b21fd SG |
210 | * @id: ID for the menu |
211 | * @objp: Returns the object pointer | |
82cafee1 SG |
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 | */ | |
431b21fd SG |
215 | static int menu_build(struct build_info *info, ofnode node, struct scene *scn, |
216 | uint id, struct scene_obj **objp) | |
82cafee1 | 217 | { |
012e1e86 | 218 | const u32 *item_ids, *item_values; |
82cafee1 | 219 | struct scene_obj_menu *menu; |
012e1e86 | 220 | int ret, size, i, num_items; |
82cafee1 | 221 | uint title_id, menu_id; |
82cafee1 | 222 | const char *name; |
82cafee1 SG |
223 | |
224 | name = ofnode_get_name(node); | |
82cafee1 SG |
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); | |
5fb9e2aa SG |
237 | if (ret) |
238 | return log_msg_ret("set", ret); | |
82cafee1 SG |
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); | |
012e1e86 | 245 | num_items = size / sizeof(u32); |
82cafee1 | 246 | |
012e1e86 SG |
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++) { | |
82cafee1 SG |
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); | |
012e1e86 SG |
277 | if (item_values) |
278 | item->value = fdt32_to_cpu(item_values[i]); | |
82cafee1 | 279 | } |
431b21fd | 280 | *objp = &menu->obj; |
82cafee1 SG |
281 | |
282 | return 0; | |
283 | } | |
284 | ||
7318e0ef SG |
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 | ||
82cafee1 | 328 | /** |
431b21fd | 329 | * obj_build() - Build an expo object and add it to a scene |
82cafee1 | 330 | * |
a3a057f8 | 331 | * See doc/develop/expo.rst for a description of the format |
82cafee1 SG |
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 | */ | |
339 | static int obj_build(struct build_info *info, ofnode node, struct scene *scn) | |
340 | { | |
431b21fd | 341 | struct scene_obj *obj; |
82cafee1 | 342 | const char *type; |
eb6c71b5 | 343 | u32 id, val; |
82cafee1 SG |
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) | |
5fb9e2aa | 349 | return log_msg_ret("id", -ENOENT); |
82cafee1 SG |
350 | |
351 | type = ofnode_read_string(node, "type"); | |
352 | if (!type) | |
353 | return log_msg_ret("typ", -EINVAL); | |
354 | ||
355 | if (!strcmp("menu", type)) | |
431b21fd | 356 | ret = menu_build(info, node, scn, id, &obj); |
7318e0ef SG |
357 | else if (!strcmp("textline", type)) |
358 | ret = textline_build(info, node, scn, id, &obj); | |
5fb9e2aa SG |
359 | else |
360 | ret = -EOPNOTSUPP; | |
82cafee1 SG |
361 | if (ret) |
362 | return log_msg_ret("bld", ret); | |
363 | ||
eb6c71b5 SG |
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 | ||
82cafee1 SG |
369 | return 0; |
370 | } | |
371 | ||
372 | /** | |
373 | * scene_build() - Build a scene and all its objects | |
374 | * | |
a3a057f8 | 375 | * See doc/develop/expo.rst for a description of the format |
82cafee1 SG |
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 | */ | |
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 | ||
5fb9e2aa | 392 | info->err_node = scn_node; |
82cafee1 SG |
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) | |
5fb9e2aa | 397 | return log_msg_ret("id", -ENOENT); |
82cafee1 SG |
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; | |
96cbafb1 | 407 | scn->title_id = title_id; |
82cafee1 SG |
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) { | |
5fb9e2aa | 414 | info->err_node = node; |
82cafee1 SG |
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 | ||
96cbafb1 | 423 | static int build_it(struct build_info *info, ofnode root, struct expo **expp) |
82cafee1 | 424 | { |
82cafee1 SG |
425 | ofnode scenes, node; |
426 | struct expo *exp; | |
427 | u32 dyn_start; | |
428 | int ret; | |
429 | ||
5fb9e2aa | 430 | ret = read_strings(info, root); |
82cafee1 SG |
431 | if (ret) |
432 | return log_msg_ret("str", ret); | |
a0874dc4 | 433 | if (_DEBUG) |
5fb9e2aa SG |
434 | list_strings(info); |
435 | info->err_node = root; | |
82cafee1 SG |
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) { | |
5fb9e2aa | 449 | ret = scene_build(info, node, exp); |
82cafee1 SG |
450 | if (ret < 0) |
451 | return log_msg_ret("scn", ret); | |
452 | } | |
453 | *expp = exp; | |
454 | ||
455 | return 0; | |
456 | } | |
5fb9e2aa SG |
457 | |
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 | } |