aboutsummaryrefslogtreecommitdiff
path: root/boot
diff options
context:
space:
mode:
authorSimon Glass2023-06-01 10:23:01 -0600
committerTom Rini2023-07-14 12:54:51 -0400
commit82cafee133ee5c087449761988c096fc26a17cf6 (patch)
tree8558fb8f832174e54cc1ed454b163e3b870b36ff /boot
parent7230fdb3837ad745adff4cf129dd04e893fe0a36 (diff)
expo: Support building an expo from a description file
The only way to create an expo at present is by calling the functions to create each object. It is useful to have more data-driven approach, where the objects can be specified in a suitable file format and created from that. This makes testing easier as well. Add support for describing an expo in a devicetree node. This allows more complex tests to be set up, as well as providing an easier format for users. It also provides a better basis for the upcoming configuration editor. Signed-off-by: Simon Glass <sjg@chromium.org>
Diffstat (limited to 'boot')
-rw-r--r--boot/Makefile2
-rw-r--r--boot/expo.c1
-rw-r--r--boot/expo_build.c400
3 files changed, 402 insertions, 1 deletions
diff --git a/boot/Makefile b/boot/Makefile
index f94c31d922d..28c4e55ca65 100644
--- a/boot/Makefile
+++ b/boot/Makefile
@@ -50,7 +50,7 @@ ifdef CONFIG_SPL_BUILD
obj-$(CONFIG_SPL_LOAD_FIT) += common_fit.o
endif
-obj-$(CONFIG_$(SPL_TPL_)EXPO) += expo.o scene.o scene_menu.o
+obj-$(CONFIG_$(SPL_TPL_)EXPO) += expo.o scene.o scene_menu.o expo_build.o
obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE) += vbe.o
obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_REQUEST) += vbe_request.o
diff --git a/boot/expo.c b/boot/expo.c
index 8c6fbc0e30b..db837f7b492 100644
--- a/boot/expo.c
+++ b/boot/expo.c
@@ -58,6 +58,7 @@ void expo_destroy(struct expo *exp)
uint resolve_id(struct expo *exp, uint id)
{
+ log_debug("resolve id %d\n", id);
if (!id)
id = exp->next_id++;
else if (id >= exp->next_id)
diff --git a/boot/expo_build.c b/boot/expo_build.c
new file mode 100644
index 00000000000..7e61ab06a8d
--- /dev/null
+++ b/boot/expo_build.c
@@ -0,0 +1,400 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Building an expo from an FDT description
+ *
+ * Copyright 2022 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY LOGC_EXPO
+
+#include <common.h>
+#include <expo.h>
+#include <fdtdec.h>
+#include <log.h>
+#include <malloc.h>
+#include <dm/ofnode.h>
+#include <linux/libfdt.h>
+
+/**
+ * struct build_info - Information to use when building
+ *
+ * @str_for_id: String for each ID in use, NULL if empty. The string is NULL
+ * if there is nothing for this ID. Since ID 0 is never used, the first
+ * element of this array is always NULL
+ * @str_count: Number of entries in @str_for_id
+ */
+struct build_info {
+ const char **str_for_id;
+ int str_count;
+};
+
+/**
+ * add_txt_str - Add a string or lookup its ID, then add to expo
+ *
+ * @info: Build information
+ * @node: Node describing scene
+ * @scn: Scene to add to
+ * @find_name: Name to look for (e.g. "title"). This will find a property called
+ * "title" if it exists, else will look up the string for "title-id"
+ * Return: ID of added string, or -ve on error
+ */
+int add_txt_str(struct build_info *info, ofnode node, struct scene *scn,
+ const char *find_name, uint obj_id)
+{
+ const char *text;
+ uint str_id;
+ int ret;
+
+ text = ofnode_read_string(node, find_name);
+ if (!text) {
+ char name[40];
+ u32 id;
+
+ snprintf(name, sizeof(name), "%s-id", find_name);
+ ret = ofnode_read_u32(node, name, &id);
+ if (ret)
+ return log_msg_ret("id", -EINVAL);
+
+ if (id >= info->str_count)
+ return log_msg_ret("id", -E2BIG);
+ text = info->str_for_id[id];
+ if (!text)
+ return log_msg_ret("id", -EINVAL);
+ }
+
+ ret = expo_str(scn->expo, find_name, 0, text);
+ if (ret < 0)
+ return log_msg_ret("add", ret);
+ str_id = ret;
+
+ ret = scene_txt_str(scn, find_name, obj_id, str_id, text, NULL);
+ if (ret < 0)
+ return log_msg_ret("add", ret);
+
+ return ret;
+}
+
+/**
+ * add_txt_str_list - Add a list string or lookup its ID, then add to expo
+ *
+ * @info: Build information
+ * @node: Node describing scene
+ * @scn: Scene to add to
+ * @find_name: Name to look for (e.g. "title"). This will find a string-list
+ * property called "title" if it exists, else will look up the string in the
+ * "title-id" string list.
+ * Return: ID of added string, or -ve on error
+ */
+int add_txt_str_list(struct build_info *info, ofnode node, struct scene *scn,
+ const char *find_name, int index, uint obj_id)
+{
+ const char *text;
+ uint str_id;
+ int ret;
+
+ ret = ofnode_read_string_index(node, find_name, index, &text);
+ if (ret) {
+ char name[40];
+ u32 id;
+
+ snprintf(name, sizeof(name), "%s-id", find_name);
+ ret = ofnode_read_u32_index(node, name, index, &id);
+ if (ret)
+ return log_msg_ret("id", -ENOENT);
+
+ if (id >= info->str_count)
+ return log_msg_ret("id", -E2BIG);
+ text = info->str_for_id[id];
+ if (!text)
+ return log_msg_ret("id", -EINVAL);
+ }
+
+ ret = expo_str(scn->expo, find_name, 0, text);
+ if (ret < 0)
+ return log_msg_ret("add", ret);
+ str_id = ret;
+
+ ret = scene_txt_str(scn, find_name, obj_id, str_id, text, NULL);
+ if (ret < 0)
+ return log_msg_ret("add", ret);
+
+ return ret;
+}
+
+/*
+ * build_element() - Handle creating a text object from a label
+ *
+ * Look up a property called @label or @label-id and create a string for it
+ */
+int build_element(void *ldtb, int node, const char *label)
+{
+ return 0;
+}
+
+/**
+ * read_strings() - Read in the list of strings
+ *
+ * Read the strings into an ID-indexed list, so they can be used for building
+ * an expo. The strings are in a /strings node and each has its own subnode
+ * containing the ID and the string itself:
+ *
+ * example {
+ * id = <123>;
+ * value = "This is a test";
+ * };
+ *
+ * Future work may add support for unicode and multiple languages
+ *
+ * @info: Build information
+ * @root: Root node to read from
+ * Returns: 0 if OK, -ENOMEM if out of memory, -EINVAL if there is a format
+ * error
+ */
+static int read_strings(struct build_info *info, ofnode root)
+{
+ ofnode strings, node;
+
+ strings = ofnode_find_subnode(root, "strings");
+ if (!ofnode_valid(strings))
+ return log_msg_ret("str", -EINVAL);
+
+ ofnode_for_each_subnode(node, strings) {
+ const char *val;
+ int ret;
+ u32 id;
+
+ ret = ofnode_read_u32(node, "id", &id);
+ if (ret)
+ return log_msg_ret("id", -EINVAL);
+ val = ofnode_read_string(node, "value");
+ if (!val)
+ return log_msg_ret("val", -EINVAL);
+
+ if (id >= info->str_count) {
+ int new_count = info->str_count + 20;
+ void *new_arr;
+
+ new_arr = realloc(info->str_for_id,
+ new_count * sizeof(char *));
+ if (!new_arr)
+ return log_msg_ret("id", -ENOMEM);
+ memset(new_arr + info->str_count, '\0',
+ (new_count - info->str_count) * sizeof(char *));
+ info->str_for_id = new_arr;
+ info->str_count = new_count;
+ }
+
+ info->str_for_id[id] = val;
+ }
+
+ return 0;
+}
+
+/**
+ * list_strings() - List the available strings with their IDs
+ *
+ * @info: Build information
+ */
+static void list_strings(struct build_info *info)
+{
+ int i;
+
+ for (i = 0; i < info->str_count; i++) {
+ if (info->str_for_id[i])
+ printf("%3d %s\n", i, info->str_for_id[i]);
+ }
+}
+
+/**
+ * menu_build() - Build a menu and add it to a scene
+ *
+ * See doc/developer/expo.rst for a description of the format
+ *
+ * @info: Build information
+ * @node: Node containing the menu description
+ * @scn: Scene to add the menu to
+ * Returns: 0 if OK, -ENOMEM if out of memory, -EINVAL if there is a format
+ * error, -ENOENT if there is a references to a non-existent string
+ */
+static int menu_build(struct build_info *info, ofnode node, struct scene *scn)
+{
+ struct scene_obj_menu *menu;
+ uint title_id, menu_id;
+ const u32 *item_ids;
+ int ret, size, i;
+ const char *name;
+ u32 id;
+
+ name = ofnode_get_name(node);
+ ret = ofnode_read_u32(node, "id", &id);
+ if (ret)
+ return log_msg_ret("id", -EINVAL);
+
+ ret = scene_menu(scn, name, id, &menu);
+ if (ret < 0)
+ return log_msg_ret("men", ret);
+ menu_id = ret;
+
+ /* Set the title */
+ ret = add_txt_str(info, node, scn, "title", 0);
+ if (ret < 0)
+ return log_msg_ret("tit", ret);
+ title_id = ret;
+ ret = scene_menu_set_title(scn, menu_id, title_id);
+
+ item_ids = ofnode_read_prop(node, "item-id", &size);
+ if (!item_ids)
+ return log_msg_ret("itm", -EINVAL);
+ if (!size || size % sizeof(u32))
+ return log_msg_ret("isz", -EINVAL);
+ size /= sizeof(u32);
+
+ for (i = 0; i < size; i++) {
+ struct scene_menitem *item;
+ uint label, key, desc;
+
+ ret = add_txt_str_list(info, node, scn, "item-label", i, 0);
+ if (ret < 0 && ret != -ENOENT)
+ return log_msg_ret("lab", ret);
+ label = max(0, ret);
+
+ ret = add_txt_str_list(info, node, scn, "key-label", i, 0);
+ if (ret < 0 && ret != -ENOENT)
+ return log_msg_ret("key", ret);
+ key = max(0, ret);
+
+ ret = add_txt_str_list(info, node, scn, "desc-label", i, 0);
+ if (ret < 0 && ret != -ENOENT)
+ return log_msg_ret("lab", ret);
+ desc = max(0, ret);
+
+ ret = scene_menuitem(scn, menu_id, simple_xtoa(i),
+ fdt32_to_cpu(item_ids[i]), key, label,
+ desc, 0, 0, &item);
+ if (ret < 0)
+ return log_msg_ret("mi", ret);
+ }
+
+ return 0;
+}
+
+/**
+ * menu_build() - Build an expo object and add it to a scene
+ *
+ * See doc/developer/expo.rst for a description of the format
+ *
+ * @info: Build information
+ * @node: Node containing the object description
+ * @scn: Scene to add the object to
+ * Returns: 0 if OK, -ENOMEM if out of memory, -EINVAL if there is a format
+ * error, -ENOENT if there is a references to a non-existent string
+ */
+static int obj_build(struct build_info *info, ofnode node, struct scene *scn)
+{
+ const char *type;
+ u32 id;
+ int ret;
+
+ log_debug("- object %s\n", ofnode_get_name(node));
+ ret = ofnode_read_u32(node, "id", &id);
+ if (ret)
+ return log_msg_ret("id", -EINVAL);
+
+ type = ofnode_read_string(node, "type");
+ if (!type)
+ return log_msg_ret("typ", -EINVAL);
+
+ if (!strcmp("menu", type))
+ ret = menu_build(info, node, scn);
+ else
+ ret = -EINVAL;
+ if (ret)
+ return log_msg_ret("bld", ret);
+
+ return 0;
+}
+
+/**
+ * scene_build() - Build a scene and all its objects
+ *
+ * See doc/developer/expo.rst for a description of the format
+ *
+ * @info: Build information
+ * @node: Node containing the scene description
+ * @scn: Scene to add the object to
+ * Returns: 0 if OK, -ENOMEM if out of memory, -EINVAL if there is a format
+ * error, -ENOENT if there is a references to a non-existent string
+ */
+static int scene_build(struct build_info *info, ofnode scn_node,
+ struct expo *exp)
+{
+ const char *name;
+ struct scene *scn;
+ uint id, title_id;
+ ofnode node;
+ int ret;
+
+ name = ofnode_get_name(scn_node);
+ log_debug("Building scene %s\n", name);
+ ret = ofnode_read_u32(scn_node, "id", &id);
+ if (ret)
+ return log_msg_ret("id", -EINVAL);
+
+ ret = scene_new(exp, name, id, &scn);
+ if (ret < 0)
+ return log_msg_ret("scn", ret);
+
+ ret = add_txt_str(info, scn_node, scn, "title", 0);
+ if (ret < 0)
+ return log_msg_ret("tit", ret);
+ title_id = ret;
+ scene_title_set(scn, title_id);
+
+ ret = add_txt_str(info, scn_node, scn, "prompt", 0);
+ if (ret < 0)
+ return log_msg_ret("pr", ret);
+
+ ofnode_for_each_subnode(node, scn_node) {
+ ret = obj_build(info, node, scn);
+ if (ret < 0)
+ return log_msg_ret("mit", ret);
+ }
+
+ return 0;
+}
+
+int expo_build(ofnode root, struct expo **expp)
+{
+ struct build_info info;
+ ofnode scenes, node;
+ struct expo *exp;
+ u32 dyn_start;
+ int ret;
+
+ memset(&info, '\0', sizeof(info));
+ ret = read_strings(&info, root);
+ if (ret)
+ return log_msg_ret("str", ret);
+ list_strings(&info);
+
+ ret = expo_new("name", NULL, &exp);
+ if (ret)
+ return log_msg_ret("exp", ret);
+
+ if (!ofnode_read_u32(root, "dynamic-start", &dyn_start))
+ expo_set_dynamic_start(exp, dyn_start);
+
+ scenes = ofnode_find_subnode(root, "scenes");
+ if (!ofnode_valid(scenes))
+ return log_msg_ret("sno", -EINVAL);
+
+ ofnode_for_each_subnode(node, scenes) {
+ ret = scene_build(&info, node, exp);
+ if (ret < 0)
+ return log_msg_ret("scn", ret);
+ }
+ *expp = exp;
+
+ return 0;
+}