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