mirror of
https://github.com/u-boot/u-boot.git
synced 2025-04-30 08:07:59 +00:00
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>
This commit is contained in:
parent
7230fdb383
commit
82cafee133
9 changed files with 870 additions and 7 deletions
64
arch/sandbox/dts/cedit.dtsi
Normal file
64
arch/sandbox/dts/cedit.dtsi
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0+
|
||||||
|
/*
|
||||||
|
* Expo definition for the configuration editor
|
||||||
|
*
|
||||||
|
* This used for testing building an expo from a data file. This devicetree
|
||||||
|
* provides a description of the objects to be created.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <test/cedit-test.h>
|
||||||
|
|
||||||
|
&cedit {
|
||||||
|
dynamic-start = <ID_DYNAMIC_START>;
|
||||||
|
|
||||||
|
scenes {
|
||||||
|
main {
|
||||||
|
id = <ID_SCENE1>;
|
||||||
|
|
||||||
|
/* value refers to the matching id in /strings */
|
||||||
|
title-id = <ID_SCENE1_TITLE>;
|
||||||
|
|
||||||
|
/* simple string is used as it is */
|
||||||
|
prompt = "UP and DOWN to choose, ENTER to select";
|
||||||
|
|
||||||
|
/* defines a menu within the scene */
|
||||||
|
cpu-speed {
|
||||||
|
type = "menu";
|
||||||
|
id = <ID_CPU_SPEED>;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* has both string and ID. The string is ignored
|
||||||
|
* if the ID is present and points to a string
|
||||||
|
*/
|
||||||
|
title = "CPU speed";
|
||||||
|
title-id = <ID_CPU_SPEED_TITLE>;
|
||||||
|
|
||||||
|
/* menu items as simple strings */
|
||||||
|
item-label = "2 GHz", "2.5 GHz", "3 GHz";
|
||||||
|
|
||||||
|
/* IDs for the menu items */
|
||||||
|
item-id = <ID_CPU_SPEED_1 ID_CPU_SPEED_2
|
||||||
|
ID_CPU_SPEED_3>;
|
||||||
|
};
|
||||||
|
|
||||||
|
power-loss {
|
||||||
|
type = "menu";
|
||||||
|
id = <ID_POWER_LOSS>;
|
||||||
|
|
||||||
|
title = "AC Power";
|
||||||
|
item-label = "Always Off", "Always On",
|
||||||
|
"Memory";
|
||||||
|
|
||||||
|
item-id = <ID_AC_OFF ID_AC_ON ID_AC_MEMORY>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
strings {
|
||||||
|
title {
|
||||||
|
id = <ID_SCENE1_TITLE>;
|
||||||
|
value = "Test Configuration";
|
||||||
|
value-es = "configuración de prueba";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
|
@ -141,6 +141,9 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
cedit: cedit {
|
||||||
|
};
|
||||||
|
|
||||||
fuzzing-engine {
|
fuzzing-engine {
|
||||||
compatible = "sandbox,fuzzing-engine";
|
compatible = "sandbox,fuzzing-engine";
|
||||||
};
|
};
|
||||||
|
@ -1830,3 +1833,5 @@
|
||||||
#ifdef CONFIG_SANDBOX_VPL
|
#ifdef CONFIG_SANDBOX_VPL
|
||||||
#include "sandbox_vpl.dtsi"
|
#include "sandbox_vpl.dtsi"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "cedit.dtsi"
|
||||||
|
|
|
@ -50,7 +50,7 @@ ifdef CONFIG_SPL_BUILD
|
||||||
obj-$(CONFIG_SPL_LOAD_FIT) += common_fit.o
|
obj-$(CONFIG_SPL_LOAD_FIT) += common_fit.o
|
||||||
endif
|
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) += vbe.o
|
||||||
obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_REQUEST) += vbe_request.o
|
obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_REQUEST) += vbe_request.o
|
||||||
|
|
|
@ -58,6 +58,7 @@ void expo_destroy(struct expo *exp)
|
||||||
|
|
||||||
uint resolve_id(struct expo *exp, uint id)
|
uint resolve_id(struct expo *exp, uint id)
|
||||||
{
|
{
|
||||||
|
log_debug("resolve id %d\n", id);
|
||||||
if (!id)
|
if (!id)
|
||||||
id = exp->next_id++;
|
id = exp->next_id++;
|
||||||
else if (id >= exp->next_id)
|
else if (id >= exp->next_id)
|
||||||
|
|
400
boot/expo_build.c
Normal file
400
boot/expo_build.c
Normal file
|
@ -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;
|
||||||
|
}
|
|
@ -100,10 +100,13 @@ objects first, then create the menu item, passing in the relevant IDs.
|
||||||
Creating an expo
|
Creating an expo
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
To create an expo, use `expo_new()` followed by `scene_new()` to create a scene.
|
To create an expo programmatically, use `expo_new()` followed by `scene_new()`
|
||||||
Then add objects to the scene, using functions like `scene_txt_str()` and
|
to create a scene. Then add objects to the scene, using functions like
|
||||||
`scene_menu()`. For every menu item, add text and image objects, then create
|
`scene_txt_str()` and `scene_menu()`. For every menu item, add text and image
|
||||||
the menu item with `scene_menuitem()`, referring to those objects.
|
objects, then create the menu item with `scene_menuitem()`, referring to those
|
||||||
|
objects.
|
||||||
|
|
||||||
|
To create an expo using a description file, see :ref:`expo_format` below.
|
||||||
|
|
||||||
Layout
|
Layout
|
||||||
------
|
------
|
||||||
|
@ -168,6 +171,273 @@ menu-inset
|
||||||
menuitem-gap-y
|
menuitem-gap-y
|
||||||
Number of pixels between menu items
|
Number of pixels between menu items
|
||||||
|
|
||||||
|
.. _expo_format:
|
||||||
|
|
||||||
|
Pop-up mode
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Expos support two modes. The simple mode is used for selecting from a single
|
||||||
|
menu, e.g. when choosing with OS to boot. In this mode the menu items are shown
|
||||||
|
in a list (label, > pointer, key and description) and can be chosen using arrow
|
||||||
|
keys and enter::
|
||||||
|
|
||||||
|
U-Boot Boot Menu
|
||||||
|
|
||||||
|
UP and DOWN to choose, ENTER to select
|
||||||
|
|
||||||
|
mmc1 > 0 Fedora-Workstation-armhfp-31-1.9
|
||||||
|
mmc3 1 Armbian
|
||||||
|
|
||||||
|
The popup mode allows multiple menus to be present in a scene. Each is shown
|
||||||
|
just as its title and label, as with the `CPU Speed` and `AC Power` menus here::
|
||||||
|
|
||||||
|
Test Configuration
|
||||||
|
|
||||||
|
|
||||||
|
CPU Speed <2 GHz> (highlighted)
|
||||||
|
|
||||||
|
AC Power Always Off
|
||||||
|
|
||||||
|
|
||||||
|
UP and DOWN to choose, ENTER to select
|
||||||
|
|
||||||
|
|
||||||
|
Expo Format
|
||||||
|
-----------
|
||||||
|
|
||||||
|
It can be tedious to create a complex expo using code. Expo supports a
|
||||||
|
data-driven approach, where the expo description is in a devicetree file. This
|
||||||
|
makes it easier and faster to create and edit the description. An expo builder
|
||||||
|
is provided to convert this format into an expo structure.
|
||||||
|
|
||||||
|
Layout of the expo scenes is handled automatically, based on a set of simple
|
||||||
|
rules.
|
||||||
|
|
||||||
|
Top-level node
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The top-level node has the following properties:
|
||||||
|
|
||||||
|
dynamic-start
|
||||||
|
type: u32, optional
|
||||||
|
|
||||||
|
Specifies the start of the dynamically allocated objects. This results in
|
||||||
|
a call to expo_set_dynamic_start().
|
||||||
|
|
||||||
|
The top-level node has the following subnodes:
|
||||||
|
|
||||||
|
scenes
|
||||||
|
Specifies the scenes in the expo, each one being a subnode
|
||||||
|
|
||||||
|
strings
|
||||||
|
Specifies the strings in the expo, each one being a subnode
|
||||||
|
|
||||||
|
`scenes` node
|
||||||
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Contains a list of scene subnodes. The name of each subnode is passed as the
|
||||||
|
name to `scene_new()`.
|
||||||
|
|
||||||
|
`strings` node
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Contains a list of string subnodes. The name of each subnode is ignored.
|
||||||
|
|
||||||
|
`strings` subnodes
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Each subnode defines a string which can be used by scenes and objects. Each
|
||||||
|
string has an ID number which is used to refer to it.
|
||||||
|
|
||||||
|
The `strings` subnodes have the following properties:
|
||||||
|
|
||||||
|
id
|
||||||
|
type: u32, required
|
||||||
|
|
||||||
|
Specifies the ID number for the string.
|
||||||
|
|
||||||
|
value:
|
||||||
|
type: string, required
|
||||||
|
|
||||||
|
Specifies the string text. For now only a single value is supported. Future
|
||||||
|
work may add support for multiple languages by using a value for each
|
||||||
|
language.
|
||||||
|
|
||||||
|
Scene nodes (`scenes` subnodes)
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Each subnode of the `scenes` node contains a scene description.
|
||||||
|
|
||||||
|
Most properties can use either a string or a string ID. For example, a `title`
|
||||||
|
property can be used to provide the title for a menu; alternatively a `title-id`
|
||||||
|
property can provide the string ID of the title. If both are present, the
|
||||||
|
ID takes preference, except that if a string with that ID does not exist, it
|
||||||
|
falls back to using the string from the property (`title` in this example). The
|
||||||
|
description below shows these are alternative properties with the same
|
||||||
|
description.
|
||||||
|
|
||||||
|
The scene nodes have the following properties:
|
||||||
|
|
||||||
|
id
|
||||||
|
type: u32, required
|
||||||
|
|
||||||
|
Specifies the ID number for the string.
|
||||||
|
|
||||||
|
title / title-id
|
||||||
|
type: string / u32, required
|
||||||
|
|
||||||
|
Specifies the title of the scene. This is shown at the top of the scene.
|
||||||
|
|
||||||
|
prompt / prompt-id
|
||||||
|
type: string / u32, required
|
||||||
|
|
||||||
|
Specifies a prompt for the scene. This is shown at the bottom of the scene.
|
||||||
|
|
||||||
|
The scene nodes have a subnode for each object in the scene.
|
||||||
|
|
||||||
|
Object nodes
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The object-node name is used as the name of the object, e.g. when calling
|
||||||
|
`scene_menu()` to create a menu.
|
||||||
|
|
||||||
|
Object nodes have the following common properties:
|
||||||
|
|
||||||
|
type
|
||||||
|
type: string, required
|
||||||
|
|
||||||
|
Specifies the type of the object. Valid types are:
|
||||||
|
|
||||||
|
"menu"
|
||||||
|
Menu containing items which can be selected by the user
|
||||||
|
|
||||||
|
id
|
||||||
|
type: u32, required
|
||||||
|
|
||||||
|
Specifies the ID of the object. This is used when referring to the object.
|
||||||
|
|
||||||
|
|
||||||
|
Menu nodes have the following additional properties:
|
||||||
|
|
||||||
|
title / title-id
|
||||||
|
type: string / u32, required
|
||||||
|
|
||||||
|
Specifies the title of the menu. This is shown to the left of the area for
|
||||||
|
this menu.
|
||||||
|
|
||||||
|
item-id
|
||||||
|
type: u32 list, required
|
||||||
|
|
||||||
|
Specifies the ID for each menu item. These are used for checking which item
|
||||||
|
has been selected.
|
||||||
|
|
||||||
|
item-label / item-label-id
|
||||||
|
type: string list / u32 list, required
|
||||||
|
|
||||||
|
Specifies the label for each item in the menu. These are shown to the user.
|
||||||
|
In 'popup' mode these form the items in the menu.
|
||||||
|
|
||||||
|
key-label / key-label-id
|
||||||
|
type: string list / u32 list, optional
|
||||||
|
|
||||||
|
Specifies the key for each item in the menu. These are currently only
|
||||||
|
intended for use in simple mode.
|
||||||
|
|
||||||
|
desc-label / desc-label-id
|
||||||
|
type: string list / u32 list, optional
|
||||||
|
|
||||||
|
Specifies the description for each item in the menu. These are currently
|
||||||
|
only intended for use in simple mode.
|
||||||
|
|
||||||
|
|
||||||
|
Expo layout
|
||||||
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
The `expo_arrange()` function can be called to arrange the expo objects in a
|
||||||
|
suitable manner. For each scene it puts the title at the top, the prompt at the
|
||||||
|
bottom and the objects in order from top to bottom.
|
||||||
|
|
||||||
|
Expo format example
|
||||||
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This example shows an expo with a single scene consisting of two menus. The
|
||||||
|
scene title is specified using a string from the strings table, but all other
|
||||||
|
strings are provided inline in the nodes where they are used.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
#define ID_PROMPT 1
|
||||||
|
#define ID_SCENE1 2
|
||||||
|
#define ID_SCENE1_TITLE 3
|
||||||
|
|
||||||
|
#define ID_CPU_SPEED 4
|
||||||
|
#define ID_CPU_SPEED_TITLE 5
|
||||||
|
#define ID_CPU_SPEED_1 6
|
||||||
|
#define ID_CPU_SPEED_2 7
|
||||||
|
#define ID_CPU_SPEED_3 8
|
||||||
|
|
||||||
|
#define ID_POWER_LOSS 9
|
||||||
|
#define ID_AC_OFF 10
|
||||||
|
#define ID_AC_ON 11
|
||||||
|
#define ID_AC_MEMORY 12
|
||||||
|
|
||||||
|
#define ID_DYNAMIC_START 13
|
||||||
|
|
||||||
|
&cedit {
|
||||||
|
dynamic-start = <ID_DYNAMIC_START>;
|
||||||
|
|
||||||
|
scenes {
|
||||||
|
main {
|
||||||
|
id = <ID_SCENE1>;
|
||||||
|
|
||||||
|
/* value refers to the matching id in /strings */
|
||||||
|
title-id = <ID_SCENE1_TITLE>;
|
||||||
|
|
||||||
|
/* simple string is used as it is */
|
||||||
|
prompt = "UP and DOWN to choose, ENTER to select";
|
||||||
|
|
||||||
|
/* defines a menu within the scene */
|
||||||
|
cpu-speed {
|
||||||
|
type = "menu";
|
||||||
|
id = <ID_CPU_SPEED>;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* has both string and ID. The string is ignored
|
||||||
|
* if the ID is present and points to a string
|
||||||
|
*/
|
||||||
|
title = "CPU speed";
|
||||||
|
title-id = <ID_CPU_SPEED_TITLE>;
|
||||||
|
|
||||||
|
/* menu items as simple strings */
|
||||||
|
item-label = "2 GHz", "2.5 GHz", "3 GHz";
|
||||||
|
|
||||||
|
/* IDs for the menu items */
|
||||||
|
item-id = <ID_CPU_SPEED_1 ID_CPU_SPEED_2
|
||||||
|
ID_CPU_SPEED_3>;
|
||||||
|
};
|
||||||
|
|
||||||
|
power-loss {
|
||||||
|
type = "menu";
|
||||||
|
id = <ID_POWER_LOSS>;
|
||||||
|
|
||||||
|
title = "AC Power";
|
||||||
|
item-label = "Always Off", "Always On",
|
||||||
|
"Memory";
|
||||||
|
|
||||||
|
item-id = <ID_AC_OFF ID_AC_ON ID_AC_MEMORY>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
strings {
|
||||||
|
title {
|
||||||
|
id = <ID_SCENE1_TITLE>;
|
||||||
|
value = "Test Configuration";
|
||||||
|
value-es = "configuración de prueba";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
API documentation
|
API documentation
|
||||||
-----------------
|
-----------------
|
||||||
|
@ -180,11 +450,10 @@ Future ideas
|
||||||
Some ideas for future work:
|
Some ideas for future work:
|
||||||
|
|
||||||
- Default menu item and a timeout
|
- Default menu item and a timeout
|
||||||
- Higher-level / automatic / more flexible layout of objects
|
|
||||||
- Image formats other than BMP
|
- Image formats other than BMP
|
||||||
- Use of ANSI sequences to control a serial terminal
|
- Use of ANSI sequences to control a serial terminal
|
||||||
- Colour selection
|
- Colour selection
|
||||||
- Better support for handling lots of settings, e.g. with radio/option widgets
|
- Support for more widgets, e.g. text, numeric, radio/option
|
||||||
- Mouse support
|
- Mouse support
|
||||||
- Integrate Nuklear, NxWidgets or some other library for a richer UI
|
- Integrate Nuklear, NxWidgets or some other library for a richer UI
|
||||||
- Optimise rendering by only updating the display with changes since last render
|
- Optimise rendering by only updating the display with changes since last render
|
||||||
|
@ -194,6 +463,7 @@ Some ideas for future work:
|
||||||
- Support both graphical and text menus at the same time on different devices
|
- Support both graphical and text menus at the same time on different devices
|
||||||
- Support unicode
|
- Support unicode
|
||||||
- Support curses for proper serial-terminal menus
|
- Support curses for proper serial-terminal menus
|
||||||
|
- Add support for large menus which need to scroll
|
||||||
|
|
||||||
.. Simon Glass <sjg@chromium.org>
|
.. Simon Glass <sjg@chromium.org>
|
||||||
.. 7-Oct-22
|
.. 7-Oct-22
|
||||||
|
|
|
@ -653,4 +653,18 @@ int expo_action_get(struct expo *exp, struct expo_action *act);
|
||||||
*/
|
*/
|
||||||
int expo_apply_theme(struct expo *exp, ofnode node);
|
int expo_apply_theme(struct expo *exp, ofnode node);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* expo_build() - Build an expo from an FDT description
|
||||||
|
*
|
||||||
|
* Build a complete expo from a description in the provided devicetree.
|
||||||
|
*
|
||||||
|
* See doc/developer/expo.rst for a description of the format
|
||||||
|
*
|
||||||
|
* @root: Root node for expo description
|
||||||
|
* @expp: Returns the new expo
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
int expo_build(ofnode root, struct expo **expp);
|
||||||
|
|
||||||
#endif /*__SCENE_H */
|
#endif /*__SCENE_H */
|
||||||
|
|
29
include/test/cedit-test.h
Normal file
29
include/test/cedit-test.h
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||||
|
/*
|
||||||
|
* Binding shared between cedit.dtsi and test/boot/expo.c
|
||||||
|
*
|
||||||
|
* Copyright 2023 Google LLC
|
||||||
|
* Written by Simon Glass <sjg@chromium.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __cedit_test_h
|
||||||
|
#define __cedit_test_h
|
||||||
|
|
||||||
|
#define ID_PROMPT 1
|
||||||
|
#define ID_SCENE1 2
|
||||||
|
#define ID_SCENE1_TITLE 3
|
||||||
|
|
||||||
|
#define ID_CPU_SPEED 4
|
||||||
|
#define ID_CPU_SPEED_TITLE 5
|
||||||
|
#define ID_CPU_SPEED_1 6
|
||||||
|
#define ID_CPU_SPEED_2 7
|
||||||
|
#define ID_CPU_SPEED_3 8
|
||||||
|
|
||||||
|
#define ID_POWER_LOSS 9
|
||||||
|
#define ID_AC_OFF 10
|
||||||
|
#define ID_AC_ON 11
|
||||||
|
#define ID_AC_MEMORY 12
|
||||||
|
|
||||||
|
#define ID_DYNAMIC_START 13
|
||||||
|
|
||||||
|
#endif
|
|
@ -13,6 +13,7 @@
|
||||||
#include <test/suites.h>
|
#include <test/suites.h>
|
||||||
#include <test/ut.h>
|
#include <test/ut.h>
|
||||||
#include "bootstd_common.h"
|
#include "bootstd_common.h"
|
||||||
|
#include <test/cedit-test.h>
|
||||||
#include "../../boot/scene_internal.h"
|
#include "../../boot/scene_internal.h"
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
|
@ -588,3 +589,82 @@ static int expo_render_image(struct unit_test_state *uts)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
BOOTSTD_TEST(expo_render_image, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
|
BOOTSTD_TEST(expo_render_image, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
|
||||||
|
|
||||||
|
/* Check building an expo from a devicetree description */
|
||||||
|
static int expo_test_build(struct unit_test_state *uts)
|
||||||
|
{
|
||||||
|
struct scene_obj_menu *menu;
|
||||||
|
struct scene_menitem *item;
|
||||||
|
struct scene_obj_txt *txt;
|
||||||
|
struct scene_obj *obj;
|
||||||
|
struct scene *scn;
|
||||||
|
struct expo *exp;
|
||||||
|
int count;
|
||||||
|
ofnode node;
|
||||||
|
|
||||||
|
node = ofnode_path("/cedit");
|
||||||
|
ut_assert(ofnode_valid(node));
|
||||||
|
ut_assertok(expo_build(node, &exp));
|
||||||
|
|
||||||
|
ut_asserteq_str("name", exp->name);
|
||||||
|
ut_asserteq(0, exp->scene_id);
|
||||||
|
ut_asserteq(ID_DYNAMIC_START + 20, exp->next_id);
|
||||||
|
ut_asserteq(false, exp->popup);
|
||||||
|
|
||||||
|
/* check the scene */
|
||||||
|
scn = expo_lookup_scene_id(exp, ID_SCENE1);
|
||||||
|
ut_assertnonnull(scn);
|
||||||
|
ut_asserteq_str("main", scn->name);
|
||||||
|
ut_asserteq(ID_SCENE1, scn->id);
|
||||||
|
ut_asserteq(ID_DYNAMIC_START + 1, scn->title_id);
|
||||||
|
ut_asserteq(0, scn->highlight_id);
|
||||||
|
|
||||||
|
/* check the title */
|
||||||
|
txt = scene_obj_find(scn, scn->title_id, SCENEOBJT_NONE);
|
||||||
|
ut_assertnonnull(txt);
|
||||||
|
obj = &txt->obj;
|
||||||
|
ut_asserteq_ptr(scn, obj->scene);
|
||||||
|
ut_asserteq_str("title", obj->name);
|
||||||
|
ut_asserteq(scn->title_id, obj->id);
|
||||||
|
ut_asserteq(SCENEOBJT_TEXT, obj->type);
|
||||||
|
ut_asserteq(0, obj->flags);
|
||||||
|
ut_asserteq_str("Test Configuration", expo_get_str(exp, txt->str_id));
|
||||||
|
|
||||||
|
/* check the menu */
|
||||||
|
menu = scene_obj_find(scn, ID_CPU_SPEED, SCENEOBJT_NONE);
|
||||||
|
obj = &menu->obj;
|
||||||
|
ut_asserteq_ptr(scn, obj->scene);
|
||||||
|
ut_asserteq_str("cpu-speed", obj->name);
|
||||||
|
ut_asserteq(ID_CPU_SPEED, obj->id);
|
||||||
|
ut_asserteq(SCENEOBJT_MENU, obj->type);
|
||||||
|
ut_asserteq(0, obj->flags);
|
||||||
|
|
||||||
|
txt = scene_obj_find(scn, menu->title_id, SCENEOBJT_NONE);
|
||||||
|
ut_asserteq_str("CPU speed", expo_get_str(exp, txt->str_id));
|
||||||
|
|
||||||
|
ut_asserteq(0, menu->cur_item_id);
|
||||||
|
ut_asserteq(0, menu->pointer_id);
|
||||||
|
|
||||||
|
/* check the items */
|
||||||
|
item = list_first_entry(&menu->item_head, struct scene_menitem,
|
||||||
|
sibling);
|
||||||
|
ut_asserteq_str("00", item->name);
|
||||||
|
ut_asserteq(ID_CPU_SPEED_1, item->id);
|
||||||
|
ut_asserteq(0, item->key_id);
|
||||||
|
ut_asserteq(0, item->desc_id);
|
||||||
|
ut_asserteq(0, item->preview_id);
|
||||||
|
ut_asserteq(0, item->flags);
|
||||||
|
|
||||||
|
txt = scene_obj_find(scn, item->label_id, SCENEOBJT_NONE);
|
||||||
|
ut_asserteq_str("2 GHz", expo_get_str(exp, txt->str_id));
|
||||||
|
|
||||||
|
count = 0;
|
||||||
|
list_for_each_entry(item, &menu->item_head, sibling)
|
||||||
|
count++;
|
||||||
|
ut_asserteq(3, count);
|
||||||
|
|
||||||
|
expo_destroy(exp);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
BOOTSTD_TEST(expo_test_build, UT_TESTF_DM);
|
||||||
|
|
Loading…
Add table
Reference in a new issue