Merge branch '2023-07-14-expo-initial-config-editor'

To quote the author:
This series provides a means to edit board configuration in U-Boot in a
graphical manner. It supports multiple menu items and allows moving
between them and selecting items. The configuration is defined in a data
format so that code is not needed in most cases. This allows the board
configuration to be provided in the devicetree.

This is still at an early stage, since it only supports menus. Numeric
values are not supported. Most importantly it does not yet support
loading or saving the configuration selected by the user.

To try it out you can use something like:

    ./tools/expo.py -e test/boot/files/expo_layout.dts \
        -l test/boot/files/expo_layout.dts -o cedit.dtb
    ./u-boot -Tl -c "cedit load hostfs - cedit.dtb; cedit run"

Use the arrow keys to move between menus, enter to open a menu, escape
to exit.

Various minor fixes and improvements are provided in this series:
- Update STB TrueType library to latest
- Support clearing part of the video display
- Support multiple livetrees loaded at runtime
- Support loading and allocating a file
- Support proper measuring of text in expo
- Support simple themes for expo
This commit is contained in:
Tom Rini 2023-07-14 13:26:42 -04:00
commit b3bbad816e
49 changed files with 5161 additions and 615 deletions

View file

@ -6,6 +6,7 @@
#include <errno.h>
#include <unistd.h>
#include <stdbool.h>
#include <sysreset.h>
#include <linux/input.h>
#include <SDL2/SDL.h>
#include <asm/state.h>
@ -81,7 +82,7 @@ static void sandbox_sdl_poll_events(void)
switch (event.type) {
case SDL_QUIT:
puts("LCD window closed - quitting\n");
reset_cpu();
sysreset_walk(SYSRESET_POWER_OFF);
break;
}
}

View 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";
};
};
};

View file

@ -16,6 +16,12 @@
stdout-path = "/serial";
};
cedit-theme {
font-size = <30>;
menu-inset = <3>;
menuitem-gap-y = <1>;
};
alarm_wdt: alarm-wdt {
compatible = "sandbox,alarm-wdt";
timeout-sec = <5>;

View file

@ -96,6 +96,8 @@
theme {
font-size = <30>;
menu-inset = <3>;
menuitem-gap-y = <1>;
};
/*
@ -139,6 +141,15 @@
};
};
cedit: cedit {
};
cedit-theme {
font-size = <30>;
menu-inset = <3>;
menuitem-gap-y = <1>;
};
fuzzing-engine {
compatible = "sandbox,fuzzing-engine";
};
@ -1828,3 +1839,5 @@
#ifdef CONFIG_SANDBOX_VPL
#include "sandbox_vpl.dtsi"
#endif
#include "cedit.dtsi"

View file

@ -1630,4 +1630,18 @@ config SAVE_PREV_BL_INITRAMFS_START_ADDR
If no initramfs was provided by previous bootloader, no env variables
will be created.
menu "Configuration editor"
config CEDIT
bool "Configuration editor"
depends on BOOTSTD
help
Provides a way to deal with board configuration and present it to
the user for adjustment.
This is intended to provide both graphical and text-based user
interfaces, but only graphical is support at present.
endmenu # Configuration editor
endmenu # Booting

View file

@ -33,6 +33,7 @@ ifdef CONFIG_$(SPL_TPL_)BOOTSTD_FULL
obj-$(CONFIG_CMD_BOOTEFI_BOOTMGR) += bootmeth_efi_mgr.o
obj-$(CONFIG_$(SPL_TPL_)EXPO) += bootflow_menu.o
obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += bootflow_menu.o
obj-$(CONFIG_$(SPL_TPL_)CEDIT) += cedit.o
endif
obj-$(CONFIG_$(SPL_TPL_)OF_LIBFDT) += image-fdt.o
@ -50,7 +51,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

View file

@ -124,6 +124,10 @@ int bootflow_menu_new(struct expo **expp)
priv->num_bootflows++;
}
ret = scene_arrange(scn);
if (ret)
return log_msg_ret("arr", ret);
*expp = exp;
return 0;
@ -205,7 +209,7 @@ int bootflow_menu_run(struct bootstd_priv *std, bool text_mode,
return log_msg_ret("scn", ret);
if (text_mode)
exp_set_text_mode(exp, text_mode);
expo_set_text_mode(exp, text_mode);
done = false;
do {

View file

@ -301,32 +301,6 @@ int bootmeth_try_file(struct bootflow *bflow, struct blk_desc *desc,
return 0;
}
static int alloc_file(const char *fname, uint size, void **bufp)
{
loff_t bytes_read;
ulong addr;
char *buf;
int ret;
buf = malloc(size + 1);
if (!buf)
return log_msg_ret("buf", -ENOMEM);
addr = map_to_sysmem(buf);
ret = fs_read(fname, addr, 0, size, &bytes_read);
if (ret) {
free(buf);
return log_msg_ret("read", ret);
}
if (size != bytes_read)
return log_msg_ret("bread", -EIO);
buf[size] = '\0';
*bufp = buf;
return 0;
}
int bootmeth_alloc_file(struct bootflow *bflow, uint size_limit, uint align)
{
void *buf;
@ -338,7 +312,7 @@ int bootmeth_alloc_file(struct bootflow *bflow, uint size_limit, uint align)
if (size > size_limit)
return log_msg_ret("chk", -E2BIG);
ret = alloc_file(bflow->fname, bflow->size, &buf);
ret = fs_read_alloc(bflow->fname, bflow->size, align, &buf);
if (ret)
return log_msg_ret("all", ret);
@ -374,7 +348,7 @@ int bootmeth_alloc_other(struct bootflow *bflow, const char *fname,
if (ret)
return log_msg_ret("fs", ret);
ret = alloc_file(path, size, &buf);
ret = fs_read_alloc(path, size, 0, &buf);
if (ret)
return log_msg_ret("all", ret);

163
boot/cedit.c Normal file
View file

@ -0,0 +1,163 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Implementation of configuration editor
*
* Copyright 2023 Google LLC
* Written by Simon Glass <sjg@chromium.org>
*/
#include <common.h>
#include <cli.h>
#include <dm.h>
#include <expo.h>
#include <menu.h>
#include <video.h>
#include <linux/delay.h>
#include "scene_internal.h"
int cedit_arange(struct expo *exp, struct video_priv *vpriv, uint scene_id)
{
struct scene_obj_txt *txt;
struct scene_obj *obj;
struct scene *scn;
int y;
scn = expo_lookup_scene_id(exp, scene_id);
if (!scn)
return log_msg_ret("scn", -ENOENT);
txt = scene_obj_find_by_name(scn, "prompt");
if (txt)
scene_obj_set_pos(scn, txt->obj.id, 0, vpriv->ysize - 50);
txt = scene_obj_find_by_name(scn, "title");
if (txt)
scene_obj_set_pos(scn, txt->obj.id, 200, 10);
y = 100;
list_for_each_entry(obj, &scn->obj_head, sibling) {
if (obj->type == SCENEOBJT_MENU) {
scene_obj_set_pos(scn, obj->id, 50, y);
scene_menu_arrange(scn, (struct scene_obj_menu *)obj);
y += 50;
}
}
return 0;
}
int cedit_run(struct expo *exp)
{
struct cli_ch_state s_cch, *cch = &s_cch;
struct video_priv *vid_priv;
uint scene_id;
struct udevice *dev;
struct scene *scn;
bool done;
int ret;
cli_ch_init(cch);
/* For now we only support a video console */
ret = uclass_first_device_err(UCLASS_VIDEO, &dev);
if (ret)
return log_msg_ret("vid", ret);
ret = expo_set_display(exp, dev);
if (ret)
return log_msg_ret("dis", ret);
ret = expo_first_scene_id(exp);
if (ret < 0)
return log_msg_ret("scn", ret);
scene_id = ret;
ret = expo_set_scene_id(exp, scene_id);
if (ret)
return log_msg_ret("sid", ret);
exp->popup = true;
/* This is not supported for now */
if (0)
expo_set_text_mode(exp, true);
vid_priv = dev_get_uclass_priv(dev);
scn = expo_lookup_scene_id(exp, scene_id);
scene_highlight_first(scn);
cedit_arange(exp, vid_priv, scene_id);
ret = expo_calc_dims(exp);
if (ret)
return log_msg_ret("dim", ret);
done = false;
do {
struct expo_action act;
int ichar, key;
ret = expo_render(exp);
if (ret)
break;
ichar = cli_ch_process(cch, 0);
if (!ichar) {
while (!ichar && !tstc()) {
schedule();
mdelay(2);
ichar = cli_ch_process(cch, -ETIMEDOUT);
}
if (!ichar) {
ichar = getchar();
ichar = cli_ch_process(cch, ichar);
}
}
key = 0;
if (ichar) {
key = bootmenu_conv_key(ichar);
if (key == BKEY_NONE)
key = ichar;
}
if (!key)
continue;
ret = expo_send_key(exp, key);
if (ret)
break;
ret = expo_action_get(exp, &act);
if (!ret) {
switch (act.type) {
case EXPOACT_POINT_OBJ:
scene_set_highlight_id(scn, act.select.id);
cedit_arange(exp, vid_priv, scene_id);
break;
case EXPOACT_OPEN:
scene_set_open(scn, act.select.id, true);
cedit_arange(exp, vid_priv, scene_id);
break;
case EXPOACT_CLOSE:
scene_set_open(scn, act.select.id, false);
cedit_arange(exp, vid_priv, scene_id);
break;
case EXPOACT_SELECT:
scene_set_open(scn, scn->highlight_id, false);
cedit_arange(exp, vid_priv, scene_id);
break;
case EXPOACT_QUIT:
log_debug("quitting\n");
done = true;
break;
default:
break;
}
}
} while (!done);
if (ret)
return log_msg_ret("end", ret);
return 0;
}

View file

@ -6,6 +6,8 @@
* Written by Simon Glass <sjg@chromium.org>
*/
#define LOG_CATEGORY LOGC_EXPO
#include <common.h>
#include <dm.h>
#include <expo.h>
@ -54,6 +56,22 @@ void expo_destroy(struct expo *exp)
free(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)
exp->next_id = id + 1;
return id;
}
void expo_set_dynamic_start(struct expo *exp, uint dyn_start)
{
exp->next_id = dyn_start;
}
int expo_str(struct expo *exp, const char *name, uint id, const char *str)
{
struct expo_string *estr;
@ -83,12 +101,45 @@ const char *expo_get_str(struct expo *exp, uint id)
int expo_set_display(struct expo *exp, struct udevice *dev)
{
struct udevice *cons;
int ret;
ret = device_find_first_child_by_uclass(dev, UCLASS_VIDEO_CONSOLE,
&cons);
if (ret)
return log_msg_ret("con", ret);
exp->display = dev;
exp->cons = cons;
return 0;
}
void exp_set_text_mode(struct expo *exp, bool text_mode)
int expo_calc_dims(struct expo *exp)
{
struct scene *scn;
int ret;
if (!exp->cons)
return log_msg_ret("dim", -ENOTSUPP);
list_for_each_entry(scn, &exp->scene_head, sibling) {
/*
* Do the menus last so that all the menus' text objects
* are dimensioned
*/
ret = scene_calc_dims(scn, false);
if (ret)
return log_msg_ret("scn", ret);
ret = scene_calc_dims(scn, true);
if (ret)
return log_msg_ret("scn", ret);
}
return 0;
}
void expo_set_text_mode(struct expo *exp, bool text_mode)
{
exp->text_mode = text_mode;
}
@ -107,13 +158,33 @@ struct scene *expo_lookup_scene_id(struct expo *exp, uint scene_id)
int expo_set_scene_id(struct expo *exp, uint scene_id)
{
if (!expo_lookup_scene_id(exp, scene_id))
struct scene *scn;
int ret;
scn = expo_lookup_scene_id(exp, scene_id);
if (!scn)
return log_msg_ret("id", -ENOENT);
ret = scene_arrange(scn);
if (ret)
return log_msg_ret("arr", ret);
exp->scene_id = scene_id;
return 0;
}
int expo_first_scene_id(struct expo *exp)
{
struct scene *scn;
if (list_empty(&exp->scene_head))
return -ENOENT;
scn = list_first_entry(&exp->scene_head, struct scene, sibling);
return scn->id;
}
int expo_render(struct expo *exp)
{
struct udevice *dev = exp->display;
@ -156,6 +227,11 @@ int expo_send_key(struct expo *exp, int key)
ret = scene_send_key(scn, key, &exp->action);
if (ret)
return log_msg_ret("key", ret);
/* arrange it to get any changes */
ret = scene_arrange(scn);
if (ret)
return log_msg_ret("arr", ret);
}
return scn ? 0 : -ECHILD;
@ -168,3 +244,25 @@ int expo_action_get(struct expo *exp, struct expo_action *act)
return act->type == EXPOACT_NONE ? -EAGAIN : 0;
}
int expo_apply_theme(struct expo *exp, ofnode node)
{
struct scene *scn;
struct expo_theme *theme = &exp->theme;
int ret;
log_debug("Applying theme %s\n", ofnode_get_name(node));
memset(theme, '\0', sizeof(struct expo_theme));
ofnode_read_u32(node, "font-size", &theme->font_size);
ofnode_read_u32(node, "menu-inset", &theme->menu_inset);
ofnode_read_u32(node, "menuitem-gap-y", &theme->menuitem_gap_y);
list_for_each_entry(scn, &exp->scene_head, sibling) {
ret = scene_apply_theme(scn, theme);
if (ret)
return log_msg_ret("app", ret);
}
return 0;
}

401
boot/expo_build.c Normal file
View file

@ -0,0 +1,401 @@
// 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);
if (_DEBUG)
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;
}

View file

@ -6,26 +6,19 @@
* Written by Simon Glass <sjg@chromium.org>
*/
#define LOG_CATEGORY LOGC_EXPO
#include <common.h>
#include <dm.h>
#include <expo.h>
#include <malloc.h>
#include <mapmem.h>
#include <menu.h>
#include <video.h>
#include <video_console.h>
#include <linux/input.h>
#include "scene_internal.h"
uint resolve_id(struct expo *exp, uint id)
{
if (!id)
id = exp->next_id++;
else if (id >= exp->next_id)
exp->next_id = id + 1;
return id;
}
int scene_new(struct expo *exp, const char *name, uint id, struct scene **scnp)
{
struct scene *scn;
@ -65,16 +58,12 @@ void scene_destroy(struct scene *scn)
scene_obj_destroy(obj);
free(scn->name);
free(scn->title);
free(scn);
}
int scene_title_set(struct scene *scn, const char *title)
int scene_title_set(struct scene *scn, uint id)
{
free(scn->title);
scn->title = strdup(title);
if (!scn->title)
return log_msg_ret("tit", -ENOMEM);
scn->title_id = id;
return 0;
}
@ -103,6 +92,18 @@ void *scene_obj_find(struct scene *scn, uint id, enum scene_obj_t type)
return NULL;
}
void *scene_obj_find_by_name(struct scene *scn, const char *name)
{
struct scene_obj *obj;
list_for_each_entry(obj, &scn->obj_head, sibling) {
if (!strcmp(name, obj->name))
return obj;
}
return NULL;
}
int scene_obj_add(struct scene *scn, const char *name, uint id,
enum scene_obj_t type, uint size, struct scene_obj **objp)
{
@ -213,22 +214,46 @@ int scene_obj_set_pos(struct scene *scn, uint id, int x, int y)
obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
if (!obj)
return log_msg_ret("find", -ENOENT);
obj->x = x;
obj->y = y;
if (obj->type == SCENEOBJT_MENU)
scene_menu_arrange(scn, (struct scene_obj_menu *)obj);
obj->dim.x = x;
obj->dim.y = y;
return 0;
}
int scene_obj_set_hide(struct scene *scn, uint id, bool hide)
int scene_obj_set_size(struct scene *scn, uint id, int w, int h)
{
struct scene_obj *obj;
obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
if (!obj)
return log_msg_ret("find", -ENOENT);
obj->hide = hide;
obj->dim.w = w;
obj->dim.h = h;
return 0;
}
int scene_obj_set_hide(struct scene *scn, uint id, bool hide)
{
int ret;
ret = scene_obj_flag_clrset(scn, id, SCENEOF_HIDE,
hide ? SCENEOF_HIDE : 0);
if (ret)
return log_msg_ret("flg", ret);
return 0;
}
int scene_obj_flag_clrset(struct scene *scn, uint id, uint clr, uint set)
{
struct scene_obj *obj;
obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
if (!obj)
return log_msg_ret("find", -ENOENT);
obj->flags &= ~clr;
obj->flags |= set;
return 0;
}
@ -258,16 +283,30 @@ int scene_obj_get_hw(struct scene *scn, uint id, int *widthp)
case SCENEOBJT_TEXT: {
struct scene_obj_txt *txt = (struct scene_obj_txt *)obj;
struct expo *exp = scn->expo;
struct vidconsole_bbox bbox;
const char *str;
int len, ret;
str = expo_get_str(exp, txt->str_id);
if (!str)
return log_msg_ret("str", -ENOENT);
len = strlen(str);
/* if there is no console, make it up */
if (!exp->cons) {
if (widthp)
*widthp = 8 * len;
return 16;
}
ret = vidconsole_measure(scn->expo->cons, txt->font_name,
txt->font_size, str, &bbox);
if (ret)
return log_msg_ret("mea", ret);
if (widthp)
*widthp = 16; /* fake value for now */
if (txt->font_size)
return txt->font_size;
if (exp->display)
return video_default_font_height(exp->display);
*widthp = bbox.x1;
/* use a sensible default */
return 16;
return bbox.y1;
}
}
@ -282,18 +321,13 @@ static int scene_obj_render(struct scene_obj *obj, bool text_mode)
{
struct scene *scn = obj->scene;
struct expo *exp = scn->expo;
struct udevice *cons, *dev = exp->display;
const struct expo_theme *theme = &exp->theme;
struct udevice *dev = exp->display;
struct udevice *cons = text_mode ? NULL : exp->cons;
int x, y, ret;
cons = NULL;
if (!text_mode) {
ret = device_find_first_child_by_uclass(dev,
UCLASS_VIDEO_CONSOLE,
&cons);
}
x = obj->x;
y = obj->y;
x = obj->dim.x;
y = obj->dim.y;
switch (obj->type) {
case SCENEOBJT_NONE:
@ -325,14 +359,45 @@ static int scene_obj_render(struct scene_obj *obj, bool text_mode)
}
if (ret && ret != -ENOSYS)
return log_msg_ret("font", ret);
vidconsole_set_cursor_pos(cons, x, y);
str = expo_get_str(exp, txt->str_id);
if (str)
if (str) {
struct video_priv *vid_priv;
struct vidconsole_colour old;
enum colour_idx fore, back;
if (CONFIG_IS_ENABLED(SYS_WHITE_ON_BLACK)) {
fore = VID_BLACK;
back = VID_WHITE;
} else {
fore = VID_LIGHT_GRAY;
back = VID_BLACK;
}
vid_priv = dev_get_uclass_priv(dev);
if (obj->flags & SCENEOF_POINT) {
vidconsole_push_colour(cons, fore, back, &old);
video_fill_part(dev, x - theme->menu_inset, y,
x + obj->dim.w,
y + obj->dim.h,
vid_priv->colour_bg);
}
vidconsole_set_cursor_pos(cons, x, y);
vidconsole_put_string(cons, str);
if (obj->flags & SCENEOF_POINT)
vidconsole_pop_colour(cons, &old);
}
break;
}
case SCENEOBJT_MENU: {
struct scene_obj_menu *menu = (struct scene_obj_menu *)obj;
if (exp->popup && (obj->flags & SCENEOF_OPEN)) {
if (!cons)
return -ENOTSUPP;
/* draw a background behind the menu items */
scene_menu_render(menu);
}
/*
* With a vidconsole, the text and item pointer are rendered as
* normal objects so we don't need to do anything here. The menu
@ -371,6 +436,30 @@ int scene_arrange(struct scene *scn)
return 0;
}
int scene_render_deps(struct scene *scn, uint id)
{
struct scene_obj *obj;
int ret;
if (!id)
return 0;
obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
if (!obj)
return log_msg_ret("obj", -ENOENT);
if (!(obj->flags & SCENEOF_HIDE)) {
ret = scene_obj_render(obj, false);
if (ret && ret != -ENOTSUPP)
return log_msg_ret("ren", ret);
if (obj->type == SCENEOBJT_MENU)
scene_menu_render_deps(scn,
(struct scene_obj_menu *)obj);
}
return 0;
}
int scene_render(struct scene *scn)
{
struct expo *exp = scn->expo;
@ -378,21 +467,107 @@ int scene_render(struct scene *scn)
int ret;
list_for_each_entry(obj, &scn->obj_head, sibling) {
if (!obj->hide) {
if (!(obj->flags & SCENEOF_HIDE)) {
ret = scene_obj_render(obj, exp->text_mode);
if (ret && ret != -ENOTSUPP)
return log_msg_ret("ren", ret);
}
}
/* render any highlighted object on top of the others */
if (scn->highlight_id && !exp->text_mode) {
ret = scene_render_deps(scn, scn->highlight_id);
if (ret && ret != -ENOTSUPP)
return log_msg_ret("dep", ret);
}
return 0;
}
/**
* send_key_obj() - Handle a keypress for moving between objects
*
* @scn: Scene to receive the key
* @key: Key to send (KEYCODE_UP)
* @event: Returns resulting event from this keypress
* Returns: 0 if OK, -ve on error
*/
static void send_key_obj(struct scene *scn, struct scene_obj *obj, int key,
struct expo_action *event)
{
switch (key) {
case BKEY_UP:
while (obj != list_first_entry(&scn->obj_head, struct scene_obj,
sibling)) {
obj = list_entry(obj->sibling.prev,
struct scene_obj, sibling);
if (obj->type == SCENEOBJT_MENU) {
event->type = EXPOACT_POINT_OBJ;
event->select.id = obj->id;
log_debug("up to obj %d\n", event->select.id);
break;
}
}
break;
case BKEY_DOWN:
while (!list_is_last(&obj->sibling, &scn->obj_head)) {
obj = list_entry(obj->sibling.next, struct scene_obj,
sibling);
if (obj->type == SCENEOBJT_MENU) {
event->type = EXPOACT_POINT_OBJ;
event->select.id = obj->id;
log_debug("down to obj %d\n", event->select.id);
break;
}
}
break;
case BKEY_SELECT:
if (obj->type == SCENEOBJT_MENU) {
event->type = EXPOACT_OPEN;
event->select.id = obj->id;
log_debug("open obj %d\n", event->select.id);
}
break;
case BKEY_QUIT:
event->type = EXPOACT_QUIT;
log_debug("obj quit\n");
break;
}
}
int scene_send_key(struct scene *scn, int key, struct expo_action *event)
{
struct scene_obj_menu *menu;
struct scene_obj *obj;
int ret;
event->type = EXPOACT_NONE;
/*
* In 'popup' mode, arrow keys move betwen objects, unless a menu is
* opened
*/
if (scn->expo->popup) {
obj = NULL;
if (scn->highlight_id) {
obj = scene_obj_find(scn, scn->highlight_id,
SCENEOBJT_NONE);
}
if (!obj)
return 0;
if (!(obj->flags & SCENEOF_OPEN)) {
send_key_obj(scn, obj, key, event);
return 0;
}
menu = (struct scene_obj_menu *)obj,
ret = scene_menu_send_key(scn, menu, key, event);
if (ret)
return log_msg_ret("key", ret);
return 0;
}
list_for_each_entry(obj, &scn->obj_head, sibling) {
if (obj->type == SCENEOBJT_MENU) {
struct scene_obj_menu *menu;
@ -401,14 +576,108 @@ int scene_send_key(struct scene *scn, int key, struct expo_action *event)
ret = scene_menu_send_key(scn, menu, key, event);
if (ret)
return log_msg_ret("key", ret);
/* only allow one menu */
ret = scene_menu_arrange(scn, menu);
if (ret)
return log_msg_ret("arr", ret);
break;
}
}
return 0;
}
int scene_calc_dims(struct scene *scn, bool do_menus)
{
struct scene_obj *obj;
int ret;
list_for_each_entry(obj, &scn->obj_head, sibling) {
switch (obj->type) {
case SCENEOBJT_NONE:
case SCENEOBJT_TEXT:
case SCENEOBJT_IMAGE: {
int width;
if (!do_menus) {
ret = scene_obj_get_hw(scn, obj->id, &width);
if (ret < 0)
return log_msg_ret("get", ret);
obj->dim.w = width;
obj->dim.h = ret;
}
break;
}
case SCENEOBJT_MENU: {
struct scene_obj_menu *menu;
if (do_menus) {
menu = (struct scene_obj_menu *)obj;
ret = scene_menu_calc_dims(menu);
if (ret)
return log_msg_ret("men", ret);
}
break;
}
}
}
return 0;
}
int scene_apply_theme(struct scene *scn, struct expo_theme *theme)
{
struct scene_obj *obj;
int ret;
/* Avoid error-checking optional items */
scene_txt_set_font(scn, scn->title_id, NULL, theme->font_size);
list_for_each_entry(obj, &scn->obj_head, sibling) {
switch (obj->type) {
case SCENEOBJT_NONE:
case SCENEOBJT_IMAGE:
case SCENEOBJT_MENU:
break;
case SCENEOBJT_TEXT:
scene_txt_set_font(scn, obj->id, NULL,
theme->font_size);
break;
}
}
ret = scene_arrange(scn);
if (ret)
return log_msg_ret("arr", ret);
return 0;
}
void scene_set_highlight_id(struct scene *scn, uint id)
{
scn->highlight_id = id;
}
void scene_highlight_first(struct scene *scn)
{
struct scene_obj *obj;
list_for_each_entry(obj, &scn->obj_head, sibling) {
switch (obj->type) {
case SCENEOBJT_MENU:
scene_set_highlight_id(scn, obj->id);
return;
default:
break;
}
}
}
int scene_set_open(struct scene *scn, uint id, bool open)
{
int ret;
ret = scene_obj_flag_clrset(scn, id, SCENEOF_OPEN,
open ? SCENEOF_OPEN : 0);
if (ret)
return log_msg_ret("flg", ret);
return 0;
}

View file

@ -23,7 +23,7 @@ struct scene *expo_lookup_scene_id(struct expo *exp, uint scene_id);
*
* @exp: Expo to use
* @id: ID to use, or 0 to auto-allocate one
* @return: Either @id, or the auto-allocated ID
* Returns: Either @id, or the auto-allocated ID
*/
uint resolve_id(struct expo *exp, uint id);
@ -36,9 +36,18 @@ uint resolve_id(struct expo *exp, uint id);
* @scn: Scene to search
* @id: ID of object to find
* @type: Type of the object, or SCENEOBJT_NONE to match any type
* Returns: Object found, or NULL if not found
*/
void *scene_obj_find(struct scene *scn, uint id, enum scene_obj_t type);
/**
* scene_obj_find_by_name() - Find an object in a scene by name
*
* @scn: Scene to search
* @name: Name to search for
*/
void *scene_obj_find_by_name(struct scene *scn, const char *name);
/**
* scene_obj_add() - Add a new object to a scene
*
@ -53,6 +62,28 @@ void *scene_obj_find(struct scene *scn, uint id, enum scene_obj_t type);
int scene_obj_add(struct scene *scn, const char *name, uint id,
enum scene_obj_t type, uint size, struct scene_obj **objp);
/**
* scene_obj_flag_clrset() - Adjust object flags
*
* @scn: Scene to update
* @id: ID of object to update
* @clr: Bits to clear in the object's flags
* @set: Bits to set in the object's flags
* Returns 0 if OK, -ENOENT if the object was not found
*/
int scene_obj_flag_clrset(struct scene *scn, uint id, uint clr, uint set);
/**
* scene_calc_dims() - Calculate the dimensions of the scene objects
*
* Updates the width and height of all objects based on their contents
*
* @scn: Scene to update
* @do_menus: true to calculate only menus, false to calculate everything else
* Returns 0 if OK, -ENOTSUPP if there is no graphical console
*/
int scene_calc_dims(struct scene *scn, bool do_menus);
/**
* scene_menu_arrange() - Set the position of things in the menu
*
@ -62,9 +93,19 @@ int scene_obj_add(struct scene *scn, const char *name, uint id,
*
* @scn: Scene to update
* @menu: Menu to process
* Returns: 0 if OK, -ve on error
*/
int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu);
/**
* scene_apply_theme() - Apply a theme to a scene
*
* @scn: Scene to update
* @theme: Theme to apply
* Returns: 0 if OK, -ve on error
*/
int scene_apply_theme(struct scene *scn, struct expo_theme *theme);
/**
* scene_menu_send_key() - Send a key to a menu for processing
*
@ -72,7 +113,7 @@ int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu);
* @menu: Menu to use
* @key: Key code to send (KEY_...)
* @event: Place to put any event which is generated by the key
* @return 0 if OK, -ENOTTY if there is no current menu item, other -ve on other
* Returns: 0 if OK, -ENOTTY if there is no current menu item, other -ve on other
* error
*/
int scene_menu_send_key(struct scene *scn, struct scene_obj_menu *menu, int key,
@ -89,7 +130,7 @@ void scene_menu_destroy(struct scene_obj_menu *menu);
* scene_menu_display() - Display a menu as text
*
* @menu: Menu to display
* @return 0 if OK, -ENOENT if @id is invalid
* Returns: 0 if OK, -ENOENT if @id is invalid
*/
int scene_menu_display(struct scene_obj_menu *menu);
@ -120,4 +161,41 @@ int scene_render(struct scene *scn);
*/
int scene_send_key(struct scene *scn, int key, struct expo_action *event);
/**
* scene_menu_render() - Render the background behind a menu
*
* @menu: Menu to render
*/
void scene_menu_render(struct scene_obj_menu *menu);
/**
* scene_render_deps() - Render an object and its dependencies
*
* @scn: Scene to render
* @id: Object ID to render (or 0 for none)
* Returns: 0 if OK, -ve on error
*/
int scene_render_deps(struct scene *scn, uint id);
/**
* scene_menu_render_deps() - Render a menu and its dependencies
*
* Renders the menu and all of its attached objects
*
* @scn: Scene to render
* @menu: Menu render
* Returns: 0 if OK, -ve on error
*/
int scene_menu_render_deps(struct scene *scn, struct scene_obj_menu *menu);
/**
* scene_menu_calc_dims() - Calculate the dimensions of a menu
*
* Updates the width and height of the menu based on its contents
*
* @menu: Menu to update
* Returns 0 if OK, -ENOTSUPP if there is no graphical console
*/
int scene_menu_calc_dims(struct scene_obj_menu *menu);
#endif /* __SCENE_INTERNAL_H */

View file

@ -6,7 +6,7 @@
* Written by Simon Glass <sjg@chromium.org>
*/
#define LOG_CATEGORY LOGC_BOOT
#define LOG_CATEGORY LOGC_EXPO
#include <common.h>
#include <dm.h>
@ -33,6 +33,58 @@ void scene_menu_destroy(struct scene_obj_menu *menu)
scene_menuitem_destroy(item);
}
static struct scene_menitem *scene_menuitem_find(struct scene_obj_menu *menu,
int id)
{
struct scene_menitem *item;
list_for_each_entry(item, &menu->item_head, sibling) {
if (item->id == id)
return item;
}
return NULL;
}
/**
* update_pointers() - Update the pointer object and handle highlights
*
* @menu: Menu to update
* @id: ID of menu item to select/deselect
* @point: true if @id is being selected, false if it is being deselected
*/
static int update_pointers(struct scene_obj_menu *menu, uint id, bool point)
{
struct scene *scn = menu->obj.scene;
const bool stack = scn->expo->popup;
const struct scene_menitem *item;
int ret;
item = scene_menuitem_find(menu, id);
if (!item)
return log_msg_ret("itm", -ENOENT);
/* adjust the pointer object to point to the selected item */
if (menu->pointer_id && item && point) {
struct scene_obj *label;
label = scene_obj_find(scn, item->label_id, SCENEOBJT_NONE);
ret = scene_obj_set_pos(scn, menu->pointer_id,
menu->obj.dim.x + 200, label->dim.y);
if (ret < 0)
return log_msg_ret("ptr", ret);
}
if (stack) {
point &= scn->highlight_id == menu->obj.id;
scene_obj_flag_clrset(scn, item->label_id, SCENEOF_POINT,
point ? SCENEOF_POINT : 0);
}
return 0;
}
/**
* menu_point_to_item() - Point to a particular menu item
*
@ -40,18 +92,115 @@ void scene_menu_destroy(struct scene_obj_menu *menu)
*/
static void menu_point_to_item(struct scene_obj_menu *menu, uint item_id)
{
if (menu->cur_item_id)
update_pointers(menu, menu->cur_item_id, false);
menu->cur_item_id = item_id;
update_pointers(menu, item_id, true);
}
static int scene_bbox_union(struct scene *scn, uint id, int inset,
struct vidconsole_bbox *bbox)
{
struct scene_obj *obj;
if (!id)
return 0;
obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
if (!obj)
return log_msg_ret("obj", -ENOENT);
if (bbox->valid) {
bbox->x0 = min(bbox->x0, obj->dim.x - inset);
bbox->y0 = min(bbox->y0, obj->dim.y);
bbox->x1 = max(bbox->x1, obj->dim.x + obj->dim.w + inset);
bbox->y1 = max(bbox->y1, obj->dim.y + obj->dim.h);
} else {
bbox->x0 = obj->dim.x - inset;
bbox->y0 = obj->dim.y;
bbox->x1 = obj->dim.x + obj->dim.w + inset;
bbox->y1 = obj->dim.y + obj->dim.h;
bbox->valid = true;
}
return 0;
}
/**
* scene_menu_calc_bbox() - Calculate bounding boxes for the menu
*
* @menu: Menu to process
* @bbox: Returns bounding box of menu including prompts
* @label_bbox: Returns bounding box of labels
*/
static void scene_menu_calc_bbox(struct scene_obj_menu *menu,
struct vidconsole_bbox *bbox,
struct vidconsole_bbox *label_bbox)
{
const struct expo_theme *theme = &menu->obj.scene->expo->theme;
const struct scene_menitem *item;
bbox->valid = false;
scene_bbox_union(menu->obj.scene, menu->title_id, 0, bbox);
label_bbox->valid = false;
list_for_each_entry(item, &menu->item_head, sibling) {
scene_bbox_union(menu->obj.scene, item->label_id,
theme->menu_inset, bbox);
scene_bbox_union(menu->obj.scene, item->key_id, 0, bbox);
scene_bbox_union(menu->obj.scene, item->desc_id, 0, bbox);
scene_bbox_union(menu->obj.scene, item->preview_id, 0, bbox);
/* Get the bounding box of all labels */
scene_bbox_union(menu->obj.scene, item->label_id,
theme->menu_inset, label_bbox);
}
/*
* subtract the final menuitem's gap to keep the insert the same top
* and bottom
*/
label_bbox->y1 -= theme->menuitem_gap_y;
}
int scene_menu_calc_dims(struct scene_obj_menu *menu)
{
struct vidconsole_bbox bbox, label_bbox;
const struct scene_menitem *item;
scene_menu_calc_bbox(menu, &bbox, &label_bbox);
/* Make all labels the same size */
if (label_bbox.valid) {
list_for_each_entry(item, &menu->item_head, sibling) {
scene_obj_set_size(menu->obj.scene, item->label_id,
label_bbox.x1 - label_bbox.x0,
label_bbox.y1 - label_bbox.y0);
}
}
if (bbox.valid) {
menu->obj.dim.w = bbox.x1 - bbox.x0;
menu->obj.dim.h = bbox.y1 - bbox.y0;
}
return 0;
}
int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu)
{
const bool open = menu->obj.flags & SCENEOF_OPEN;
struct expo *exp = scn->expo;
const bool stack = exp->popup;
const struct expo_theme *theme = &exp->theme;
struct scene_menitem *item;
int y, cur_y;
uint sel_id;
int x, y;
int ret;
y = menu->obj.y;
x = menu->obj.dim.x;
y = menu->obj.dim.y;
if (menu->title_id) {
ret = scene_obj_set_pos(scn, menu->title_id, menu->obj.x, y);
ret = scene_obj_set_pos(scn, menu->title_id, menu->obj.dim.x, y);
if (ret < 0)
return log_msg_ret("tit", ret);
@ -59,7 +208,10 @@ int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu)
if (ret < 0)
return log_msg_ret("hei", ret);
y += ret * 2;
if (stack)
x += 200;
else
y += ret * 2;
}
/*
@ -68,11 +220,12 @@ int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu)
* small. This can be updated once text measuring is supported in
* vidconsole
*/
cur_y = -1;
sel_id = menu->cur_item_id;
list_for_each_entry(item, &menu->item_head, sibling) {
bool selected;
int height;
ret = scene_obj_get_hw(scn, item->desc_id, NULL);
ret = scene_obj_get_hw(scn, item->label_id, NULL);
if (ret < 0)
return log_msg_ret("get", ret);
height = ret;
@ -81,32 +234,33 @@ int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu)
y += height;
/* select an item if not done already */
if (!menu->cur_item_id)
menu_point_to_item(menu, item->id);
if (!sel_id)
sel_id = item->id;
selected = sel_id == item->id;
/*
* Put the label on the left, then leave a space for the
* pointer, then the key and the description
*/
if (item->label_id) {
ret = scene_obj_set_pos(scn, item->label_id, menu->obj.x,
y);
ret = scene_obj_set_pos(scn, item->label_id,
x + theme->menu_inset, y);
if (ret < 0)
return log_msg_ret("nam", ret);
scene_obj_set_hide(scn, item->label_id,
stack && !open && !selected);
if (item->key_id) {
ret = scene_obj_set_pos(scn, item->key_id, x + 230, y);
if (ret < 0)
return log_msg_ret("nam", ret);
return log_msg_ret("key", ret);
}
ret = scene_obj_set_pos(scn, item->key_id, menu->obj.x + 230,
y);
if (ret < 0)
return log_msg_ret("key", ret);
ret = scene_obj_set_pos(scn, item->desc_id, menu->obj.x + 280,
y);
if (ret < 0)
return log_msg_ret("des", ret);
if (menu->cur_item_id == item->id)
cur_y = y;
if (item->desc_id) {
ret = scene_obj_set_pos(scn, item->desc_id, x + 280, y);
if (ret < 0)
return log_msg_ret("des", ret);
}
if (item->preview_id) {
bool hide;
@ -125,19 +279,12 @@ int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu)
return log_msg_ret("hid", ret);
}
y += height;
if (!stack || open)
y += height + theme->menuitem_gap_y;
}
if (menu->pointer_id && cur_y != -1) {
/*
* put the pointer to the right of and level with the item it
* points to
*/
ret = scene_obj_set_pos(scn, menu->pointer_id,
menu->obj.x + 200, cur_y);
if (ret < 0)
return log_msg_ret("ptr", ret);
}
if (sel_id)
menu_point_to_item(menu, sel_id);
return 0;
}
@ -158,10 +305,6 @@ int scene_menu(struct scene *scn, const char *name, uint id,
*menup = menu;
INIT_LIST_HEAD(&menu->item_head);
ret = scene_menu_arrange(scn, menu);
if (ret)
return log_msg_ret("pos", ret);
return menu->obj.id;
}
@ -191,6 +334,7 @@ static struct scene_menitem *scene_menu_find_key(struct scene *scn,
int scene_menu_send_key(struct scene *scn, struct scene_obj_menu *menu, int key,
struct expo_action *event)
{
const bool open = menu->obj.flags & SCENEOF_OPEN;
struct scene_menitem *item, *cur, *key_item;
cur = NULL;
@ -215,7 +359,7 @@ int scene_menu_send_key(struct scene *scn, struct scene_obj_menu *menu, int key,
struct scene_menitem, sibling)) {
item = list_entry(item->sibling.prev,
struct scene_menitem, sibling);
event->type = EXPOACT_POINT;
event->type = EXPOACT_POINT_ITEM;
event->select.id = item->id;
log_debug("up to item %d\n", event->select.id);
}
@ -224,7 +368,7 @@ int scene_menu_send_key(struct scene *scn, struct scene_obj_menu *menu, int key,
if (!list_is_last(&item->sibling, &menu->item_head)) {
item = list_entry(item->sibling.next,
struct scene_menitem, sibling);
event->type = EXPOACT_POINT;
event->type = EXPOACT_POINT_ITEM;
event->select.id = item->id;
log_debug("down to item %d\n", event->select.id);
}
@ -235,8 +379,13 @@ int scene_menu_send_key(struct scene *scn, struct scene_obj_menu *menu, int key,
log_debug("select item %d\n", event->select.id);
break;
case BKEY_QUIT:
event->type = EXPOACT_QUIT;
log_debug("quit\n");
if (scn->expo->popup && open) {
event->type = EXPOACT_CLOSE;
event->select.id = menu->obj.id;
} else {
event->type = EXPOACT_QUIT;
log_debug("menu quit\n");
}
break;
case '0'...'9':
key_item = scene_menu_find_key(scn, menu, key);
@ -258,14 +407,13 @@ int scene_menuitem(struct scene *scn, uint menu_id, const char *name, uint id,
{
struct scene_obj_menu *menu;
struct scene_menitem *item;
int ret;
menu = scene_obj_find(scn, menu_id, SCENEOBJT_MENU);
if (!menu)
return log_msg_ret("find", -ENOENT);
/* Check that the text ID is valid */
if (!scene_obj_find(scn, desc_id, SCENEOBJT_TEXT))
if (!scene_obj_find(scn, label_id, SCENEOBJT_TEXT))
return log_msg_ret("txt", -EINVAL);
item = calloc(1, sizeof(struct scene_obj_menu));
@ -285,10 +433,6 @@ int scene_menuitem(struct scene *scn, uint menu_id, const char *name, uint id,
item->flags = flags;
list_add_tail(&item->sibling, &menu->item_head);
ret = scene_menu_arrange(scn, menu);
if (ret)
return log_msg_ret("pos", ret);
if (itemp)
*itemp = item;
@ -388,3 +532,49 @@ int scene_menu_display(struct scene_obj_menu *menu)
return -ENOTSUPP;
}
void scene_menu_render(struct scene_obj_menu *menu)
{
struct expo *exp = menu->obj.scene->expo;
const struct expo_theme *theme = &exp->theme;
struct vidconsole_bbox bbox, label_bbox;
struct udevice *dev = exp->display;
struct video_priv *vid_priv;
struct udevice *cons = exp->cons;
struct vidconsole_colour old;
enum colour_idx fore, back;
if (CONFIG_IS_ENABLED(SYS_WHITE_ON_BLACK)) {
fore = VID_BLACK;
back = VID_WHITE;
} else {
fore = VID_LIGHT_GRAY;
back = VID_BLACK;
}
scene_menu_calc_bbox(menu, &bbox, &label_bbox);
vidconsole_push_colour(cons, fore, back, &old);
vid_priv = dev_get_uclass_priv(dev);
video_fill_part(dev, label_bbox.x0 - theme->menu_inset,
label_bbox.y0 - theme->menu_inset,
label_bbox.x1, label_bbox.y1 + theme->menu_inset,
vid_priv->colour_fg);
vidconsole_pop_colour(cons, &old);
}
int scene_menu_render_deps(struct scene *scn, struct scene_obj_menu *menu)
{
struct scene_menitem *item;
scene_render_deps(scn, menu->title_id);
scene_render_deps(scn, menu->cur_item_id);
scene_render_deps(scn, menu->pointer_id);
list_for_each_entry(item, &menu->item_head, sibling) {
scene_render_deps(scn, item->key_id);
scene_render_deps(scn, item->label_id);
scene_render_deps(scn, item->desc_id);
}
return 0;
}

View file

@ -428,6 +428,15 @@ config CMD_ABOOTIMG
See doc/android/boot-image.rst for details.
config CMD_CEDIT
bool "cedit - Configuration editor"
depends on CEDIT
default y
help
Provides a command to allow editing of board configuration and
providing a UI for the user to adjust settings. Subcommands allow
loading and saving of configuration as well as showing an editor.
config CMD_ELF
bool "bootelf, bootvx"
default y

View file

@ -43,6 +43,7 @@ obj-$(CONFIG_CMD_BUTTON) += button.o
obj-$(CONFIG_CMD_CAT) += cat.o
obj-$(CONFIG_CMD_CACHE) += cache.o
obj-$(CONFIG_CMD_CBFS) += cbfs.o
obj-$(CONFIG_CMD_CEDIT) += cedit.o
obj-$(CONFIG_CMD_CLK) += clk.o
obj-$(CONFIG_CMD_CLS) += cls.o
obj-$(CONFIG_CMD_CONFIG) += config.o

View file

@ -17,8 +17,8 @@ static int do_cat(struct cmd_tbl *cmdtp, int flag, int argc,
char *dev;
char *file;
char *buffer;
phys_addr_t addr;
loff_t file_size;
ulong file_size;
int ret;
if (argc < 4)
return CMD_RET_USAGE;
@ -27,40 +27,27 @@ static int do_cat(struct cmd_tbl *cmdtp, int flag, int argc,
dev = argv[2];
file = argv[3];
// check file exists
if (fs_set_blk_dev(ifname, dev, FS_TYPE_ANY))
return CMD_RET_FAILURE;
ret = fs_load_alloc(ifname, dev, file, 0, 0, (void **)&buffer,
&file_size);
if (!fs_exists(file)) {
// check file exists
switch (ret) {
case 0:
break;
case -ENOMEDIUM:
return CMD_RET_FAILURE;
case -ENOENT:
log_err("File does not exist: ifname=%s dev=%s file=%s\n", ifname, dev, file);
return CMD_RET_FAILURE;
}
// get file size
if (fs_set_blk_dev(ifname, dev, FS_TYPE_ANY))
case -E2BIG:
log_err("File is too large: ifname=%s dev=%s file=%s\n", ifname, dev, file);
return CMD_RET_FAILURE;
if (fs_size(file, &file_size)) {
log_err("Cannot read file size: ifname=%s dev=%s file=%s\n", ifname, dev, file);
case -ENOMEM:
log_err("Not enough memory: ifname=%s dev=%s file=%s\n", ifname, dev, file);
return CMD_RET_FAILURE;
}
// allocate memory for file content
buffer = calloc(sizeof(char), file_size + 1);
if (!buffer) {
log_err("Out of memory\n");
return CMD_RET_FAILURE;
}
// map pointer to system memory
addr = map_to_sysmem(buffer);
// read file to memory
if (fs_set_blk_dev(ifname, dev, FS_TYPE_ANY))
return CMD_RET_FAILURE;
if (fs_read(file, addr, 0, 0, &file_size)) {
log_err("Cannot read file: ifname=%s dev=%s file=%s\n", ifname, dev, file);
default:
case -EIO:
log_err("File-read failed: ifname=%s dev=%s file=%s\n", ifname, dev, file);
return CMD_RET_FAILURE;
}

93
cmd/cedit.c Normal file
View file

@ -0,0 +1,93 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* 'cedit' command
*
* Copyright 2023 Google LLC
* Written by Simon Glass <sjg@chromium.org>
*/
#include <common.h>
#include <command.h>
#include <expo.h>
#include <fs.h>
#include <dm/ofnode.h>
#include <linux/sizes.h>
struct expo *cur_exp;
static int do_cedit_load(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
const char *fname;
struct expo *exp;
oftree tree;
ulong size;
void *buf;
int ret;
if (argc < 4)
return CMD_RET_USAGE;
fname = argv[3];
ret = fs_load_alloc(argv[1], argv[2], argv[3], SZ_1M, 0, &buf, &size);
if (ret) {
printf("File not found\n");
return CMD_RET_FAILURE;
}
tree = oftree_from_fdt(buf);
if (!oftree_valid(tree)) {
printf("Cannot create oftree\n");
return CMD_RET_FAILURE;
}
ret = expo_build(oftree_root(tree), &exp);
oftree_dispose(tree);
if (ret) {
printf("Failed to build expo: %dE\n", ret);
return CMD_RET_FAILURE;
}
cur_exp = exp;
return 0;
}
static int do_cedit_run(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
ofnode node;
int ret;
if (!cur_exp) {
printf("No expo loaded\n");
return CMD_RET_FAILURE;
}
node = ofnode_path("/cedit-theme");
if (ofnode_valid(node)) {
ret = expo_apply_theme(cur_exp, node);
if (ret)
return CMD_RET_FAILURE;
} else {
log_warning("No theme found\n");
}
ret = cedit_run(cur_exp);
if (ret) {
log_err("Failed (err=%dE)\n", ret);
return CMD_RET_FAILURE;
}
return 0;
}
#ifdef CONFIG_SYS_LONGHELP
static char cedit_help_text[] =
"load <interface> <dev[:part]> <filename> - load config editor\n"
"cedit run - run config editor";
#endif /* CONFIG_SYS_LONGHELP */
U_BOOT_CMD_WITH_SUBCMDS(cedit, "Configuration editor", cedit_help_text,
U_BOOT_SUBCMD_MKENT(load, 5, 1, do_cedit_load),
U_BOOT_SUBCMD_MKENT(run, 1, 1, do_cedit_run),
);

View file

@ -31,6 +31,7 @@ static const char *const log_cat_name[] = {
"boot",
"event",
"fs",
"expo",
};
_Static_assert(ARRAY_SIZE(log_cat_name) == LOGC_COUNT - LOGC_NONE,

View file

@ -30,6 +30,7 @@ CONFIG_AUTOBOOT_STOP_STR_ENABLE=y
CONFIG_AUTOBOOT_STOP_STR_CRYPT="$5$rounds=640000$HrpE65IkB8CM5nCL$BKT3QdF98Bo8fJpTr9tjZLZQyzqPASBY20xuK5Rent9"
CONFIG_IMAGE_PRE_LOAD=y
CONFIG_IMAGE_PRE_LOAD_SIG=y
CONFIG_CEDIT=y
CONFIG_CONSOLE_RECORD=y
CONFIG_CONSOLE_RECORD_OUT_SIZE=0x6000
CONFIG_PRE_CONSOLE_BUFFER=y

View file

@ -85,6 +85,9 @@ or even the IDs of objects. Programmatic creation of many items in a loop can be
handled by allocating space in the enum for a maximum number of items, then
adding the loop count to the enum values to obtain unique IDs.
Where dynamic IDs are need, use expo_set_dynamic_start() to set the start value,
so that they are allocated above the starting (enum) IDs.
All text strings are stored in a structure attached to the expo, referenced by
a text ID. This makes it easier at some point to implement multiple languages or
to support Unicode strings.
@ -97,10 +100,13 @@ objects first, then create the menu item, passing in the relevant IDs.
Creating an expo
----------------
To create an expo, use `expo_new()` followed by `scene_new()` to create a scene.
Then add objects to the scene, using functions like `scene_txt_str()` and
`scene_menu()`. For every menu item, add text and image objects, then create
the menu item with `scene_menuitem()`, referring to those objects.
To create an expo programmatically, use `expo_new()` followed by `scene_new()`
to create a scene. Then add objects to the scene, using functions like
`scene_txt_str()` and `scene_menu()`. For every menu item, add text and image
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
------
@ -152,8 +158,287 @@ such as scanning devices for more bootflows.
Themes
------
Expo does not itself support themes. The bootflow_menu implement supposed a
basic theme, applying font sizes to the various text objects in the expo.
Expo supports simple themes, for setting the font size, for example. Use the
expo_apply_theme() function to load a theme, passing a node with the required
properties:
font-size
Font size to use for all text (type: u32)
menu-inset
Number of pixels to inset the menu on the sides and top (type: u32)
menuitem-gap-y
Number of pixels between menu items
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:
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. The :doc:`../usage/cmd/cedit` can be used to load a configuration
and create an expo from it.
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
-----------------
@ -166,12 +451,10 @@ Future ideas
Some ideas for future work:
- Default menu item and a timeout
- Higher-level / automatic / more flexible layout of objects
- Image formats other than BMP
- Use of ANSI sequences to control a serial terminal
- Colour selection
- Better support for handling lots of settings, e.g. with multiple menus and
radio/option widgets
- Support for more widgets, e.g. text, numeric, radio/option
- Mouse support
- Integrate Nuklear, NxWidgets or some other library for a richer UI
- Optimise rendering by only updating the display with changes since last render
@ -179,10 +462,10 @@ Some ideas for future work:
- Add a Kconfig option to drop the names to save code / data space
- Add a Kconfig option to disable vidconsole support to save code / data space
- Support both graphical and text menus at the same time on different devices
- Implement proper measurement of object bounding boxes, to permit more exact
layout. This would tidy up the layout when Truetype is not used
- Support unicode
- Support curses for proper serial-terminal menus
- Add support for large menus which need to scroll
- Add support for reading and writing configuration settings with cedit
.. Simon Glass <sjg@chromium.org>
.. 7-Oct-22

31
doc/usage/cmd/cedit.rst Normal file
View file

@ -0,0 +1,31 @@
.. SPDX-License-Identifier: GPL-2.0+:
cedit command
=============
Synopis
-------
::
cedit load <interface> <dev[:part]> <filename>
cedit run
Description
-----------
The *cedit* command is used to load a configuration-editor description and allow
the user to interact with it.
It makes use of the expo subsystem.
The description is in the form of a devicetree file, as documented at
:ref:`expo_format`.
Example
-------
::
=> cedit load hostfs - fred.dtb
=> cedit run

View file

@ -39,6 +39,7 @@ Shell commands
cmd/bootz
cmd/cat
cmd/cbsysinfo
cmd/cedit
cmd/cls
cmd/cmp
cmd/coninfo

View file

@ -12,6 +12,7 @@
#include <fdt_support.h>
#include <log.h>
#include <malloc.h>
#include <of_live.h>
#include <linux/libfdt.h>
#include <dm/of_access.h>
#include <dm/of_addr.h>
@ -51,6 +52,20 @@ static oftree oftree_ensure(void *fdt)
oftree tree;
int i;
if (of_live_active()) {
struct device_node *root;
int ret;
ret = unflatten_device_tree(fdt, &root);
if (ret) {
log_err("Failed to create live tree: err=%d\n", ret);
return oftree_null();
}
tree = oftree_from_np(root);
return tree;
}
if (gd->flags & GD_FLG_RELOC) {
i = oftree_find(fdt);
if (i == -1) {
@ -67,7 +82,7 @@ static oftree oftree_ensure(void *fdt)
}
} else {
if (fdt != gd->fdt_blob) {
log_debug("Cannot only access control FDT before relocation\n");
log_debug("Only the control FDT can be accessed before relocation\n");
return oftree_null();
}
}
@ -77,6 +92,12 @@ static oftree oftree_ensure(void *fdt)
return tree;
}
void oftree_dispose(oftree tree)
{
if (of_live_active())
of_live_free(tree.np);
}
void *ofnode_lookup_fdt(ofnode node)
{
if (gd->flags & GD_FLG_RELOC) {
@ -133,6 +154,10 @@ oftree oftree_from_fdt(void *fdt)
if (CONFIG_IS_ENABLED(OFNODE_MULTI_TREE))
return oftree_ensure(fdt);
#ifdef OF_CHECKS
if (of_live_active())
return oftree_null();
#endif
tree.fdt = fdt;
return tree;

View file

@ -1474,7 +1474,7 @@ static int gpio_post_bind(struct udevice *dev)
}
#endif
if (CONFIG_IS_ENABLED(GPIO_HOG)) {
if (CONFIG_IS_ENABLED(GPIO_HOG) && dev_has_ofnode(dev)) {
struct udevice *child;
ofnode node;

View file

@ -62,10 +62,43 @@ static double tt_sqrt(double value)
return lo;
}
static double tt_fmod(double x, double y)
{
double rem;
if (y == 0.0)
return 0.0;
rem = x - (x / y) * y;
return rem;
}
/* dummy implementation */
static double tt_pow(double x, double y)
{
return 0;
}
/* dummy implementation */
static double tt_cos(double val)
{
return 0;
}
/* dummy implementation */
static double tt_acos(double val)
{
return 0;
}
#define STBTT_ifloor tt_floor
#define STBTT_iceil tt_ceil
#define STBTT_fabs tt_fabs
#define STBTT_sqrt tt_sqrt
#define STBTT_pow tt_pow
#define STBTT_fmod tt_fmod
#define STBTT_cos tt_cos
#define STBTT_acos tt_acos
#define STBTT_malloc(size, u) ((void)(u), malloc(size))
#define STBTT_free(size, u) ((void)(u), free(size))
#define STBTT_assert(x)
@ -154,33 +187,33 @@ static int console_truetype_set_row(struct udevice *dev, uint row, int clr)
end = line + met->font_size * vid_priv->line_length;
switch (vid_priv->bpix) {
#ifdef CONFIG_VIDEO_BPP8
case VIDEO_BPP8: {
u8 *dst;
for (dst = line; dst < (u8 *)end; ++dst)
*dst = clr;
if (IS_ENABLED(CONFIG_VIDEO_BPP8)) {
for (dst = line; dst < (u8 *)end; ++dst)
*dst = clr;
}
break;
}
#endif
#ifdef CONFIG_VIDEO_BPP16
case VIDEO_BPP16: {
u16 *dst = line;
for (dst = line; dst < (u16 *)end; ++dst)
*dst = clr;
if (IS_ENABLED(CONFIG_VIDEO_BPP16)) {
for (dst = line; dst < (u16 *)end; ++dst)
*dst = clr;
}
break;
}
#endif
#ifdef CONFIG_VIDEO_BPP32
case VIDEO_BPP32: {
u32 *dst = line;
for (dst = line; dst < (u32 *)end; ++dst)
*dst = clr;
if (IS_ENABLED(CONFIG_VIDEO_BPP32)) {
for (dst = line; dst < (u32 *)end; ++dst)
*dst = clr;
}
break;
}
#endif
default:
return -ENOSYS;
}
@ -256,7 +289,7 @@ static int console_truetype_putc_xy(struct udevice *dev, uint x, uint y,
*/
x_shift = xpos - (double)tt_floor(xpos);
xpos += advance * met->scale;
width_frac = (int)VID_TO_POS(xpos);
width_frac = (int)VID_TO_POS(advance * met->scale);
if (x + width_frac >= vc_priv->xsize_frac)
return -EAGAIN;
@ -317,52 +350,52 @@ static int console_truetype_putc_xy(struct udevice *dev, uint x, uint y,
end = dst;
}
break;
#ifdef CONFIG_VIDEO_BPP16
case VIDEO_BPP16: {
uint16_t *dst = (uint16_t *)line + xoff;
int i;
for (i = 0; i < width; i++) {
int val = *bits;
int out;
if (IS_ENABLED(CONFIG_VIDEO_BPP16)) {
for (i = 0; i < width; i++) {
int val = *bits;
int out;
if (vid_priv->colour_bg)
val = 255 - val;
out = val >> 3 |
(val >> 2) << 5 |
(val >> 3) << 11;
if (vid_priv->colour_fg)
*dst++ |= out;
else
*dst++ &= out;
bits++;
if (vid_priv->colour_bg)
val = 255 - val;
out = val >> 3 |
(val >> 2) << 5 |
(val >> 3) << 11;
if (vid_priv->colour_fg)
*dst++ |= out;
else
*dst++ &= out;
bits++;
}
end = dst;
}
end = dst;
break;
}
#endif
#ifdef CONFIG_VIDEO_BPP32
case VIDEO_BPP32: {
u32 *dst = (u32 *)line + xoff;
int i;
for (i = 0; i < width; i++) {
int val = *bits;
int out;
if (IS_ENABLED(CONFIG_VIDEO_BPP32)) {
for (i = 0; i < width; i++) {
int val = *bits;
int out;
if (vid_priv->colour_bg)
val = 255 - val;
out = val | val << 8 | val << 16;
if (vid_priv->colour_fg)
*dst++ |= out;
else
*dst++ &= out;
bits++;
if (vid_priv->colour_bg)
val = 255 - val;
out = val | val << 8 | val << 16;
if (vid_priv->colour_fg)
*dst++ |= out;
else
*dst++ &= out;
bits++;
}
end = dst;
}
end = dst;
break;
}
#endif
default:
free(data);
return -ENOSYS;
@ -378,72 +411,6 @@ static int console_truetype_putc_xy(struct udevice *dev, uint x, uint y,
return width_frac;
}
/**
* console_truetype_erase() - Erase a character
*
* This is used for backspace. We erase a square of the display within the
* given bounds.
*
* @dev: Device to update
* @xstart: X start position in pixels from the left
* @ystart: Y start position in pixels from the top
* @xend: X end position in pixels from the left
* @yend: Y end position in pixels from the top
* @clr: Value to write
* Return: 0 if OK, -ENOSYS if the display depth is not supported
*/
static int console_truetype_erase(struct udevice *dev, int xstart, int ystart,
int xend, int yend, int clr)
{
struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent);
void *start, *line;
int pixels = xend - xstart;
int row, i, ret;
start = vid_priv->fb + ystart * vid_priv->line_length;
start += xstart * VNBYTES(vid_priv->bpix);
line = start;
for (row = ystart; row < yend; row++) {
switch (vid_priv->bpix) {
#ifdef CONFIG_VIDEO_BPP8
case VIDEO_BPP8: {
uint8_t *dst = line;
for (i = 0; i < pixels; i++)
*dst++ = clr;
break;
}
#endif
#ifdef CONFIG_VIDEO_BPP16
case VIDEO_BPP16: {
uint16_t *dst = line;
for (i = 0; i < pixels; i++)
*dst++ = clr;
break;
}
#endif
#ifdef CONFIG_VIDEO_BPP32
case VIDEO_BPP32: {
uint32_t *dst = line;
for (i = 0; i < pixels; i++)
*dst++ = clr;
break;
}
#endif
default:
return -ENOSYS;
}
line += vid_priv->line_length;
}
ret = vidconsole_sync_copy(dev, start, line);
if (ret)
return ret;
return 0;
}
/**
* console_truetype_backspace() - Handle a backspace operation
*
@ -482,9 +449,9 @@ static int console_truetype_backspace(struct udevice *dev)
else
xend = vid_priv->xsize;
console_truetype_erase(dev, VID_TO_PIXEL(pos->xpos_frac), pos->ypos,
xend, pos->ypos + vc_priv->y_charsize,
vid_priv->colour_bg);
video_fill_part(vid_dev, VID_TO_PIXEL(pos->xpos_frac), pos->ypos,
xend, pos->ypos + vc_priv->y_charsize,
vid_priv->colour_bg);
/* Move the cursor back to where it was when we pushed this record */
vc_priv->xcur_frac = pos->xpos_frac;
@ -680,8 +647,8 @@ static void select_metrics(struct udevice *dev, struct console_tt_metrics *met)
vc_priv->tab_width_frac = VID_TO_POS(met->font_size) * 8 / 2;
}
static int truetype_select_font(struct udevice *dev, const char *name,
uint size)
static int get_metrics(struct udevice *dev, const char *name, uint size,
struct console_tt_metrics **metp)
{
struct console_tt_priv *priv = dev_get_priv(dev);
struct console_tt_metrics *met;
@ -719,11 +686,70 @@ static int truetype_select_font(struct udevice *dev, const char *name,
met = priv->metrics;
}
*metp = met;
return 0;
}
static int truetype_select_font(struct udevice *dev, const char *name,
uint size)
{
struct console_tt_metrics *met;
int ret;
ret = get_metrics(dev, name, size, &met);
if (ret)
return log_msg_ret("sel", ret);
select_metrics(dev, met);
return 0;
}
int truetype_measure(struct udevice *dev, const char *name, uint size,
const char *text, struct vidconsole_bbox *bbox)
{
struct console_tt_metrics *met;
stbtt_fontinfo *font;
int lsb, advance;
const char *s;
int width;
int last;
int ret;
ret = get_metrics(dev, name, size, &met);
if (ret)
return log_msg_ret("sel", ret);
bbox->valid = false;
if (!*text)
return 0;
font = &met->font;
width = 0;
for (last = 0, s = text; *s; s++) {
int ch = *s;
/* Used kerning to fine-tune the position of this character */
if (last)
width += stbtt_GetCodepointKernAdvance(font, last, ch);
/* First get some basic metrics about this character */
stbtt_GetCodepointHMetrics(font, ch, &advance, &lsb);
width += advance;
last = ch;
}
bbox->valid = true;
bbox->x0 = 0;
bbox->y0 = 0;
bbox->x1 = tt_ceil((double)width * met->scale);
bbox->y1 = met->font_size;
return 0;
}
const char *console_truetype_get_font_size(struct udevice *dev, uint *sizep)
{
struct console_tt_priv *priv = dev_get_priv(dev);
@ -775,6 +801,7 @@ struct vidconsole_ops console_truetype_ops = {
.get_font = console_truetype_get_font,
.get_font_size = console_truetype_get_font_size,
.select_font = truetype_select_font,
.measure = truetype_measure,
};
U_BOOT_DRIVER(vidconsole_truetype) = {

File diff suppressed because it is too large Load diff

View file

@ -596,6 +596,48 @@ int vidconsole_select_font(struct udevice *dev, const char *name, uint size)
return ops->select_font(dev, name, size);
}
int vidconsole_measure(struct udevice *dev, const char *name, uint size,
const char *text, struct vidconsole_bbox *bbox)
{
struct vidconsole_priv *priv = dev_get_uclass_priv(dev);
struct vidconsole_ops *ops = vidconsole_get_ops(dev);
int ret;
if (ops->select_font) {
ret = ops->measure(dev, name, size, text, bbox);
if (ret != -ENOSYS)
return ret;
}
bbox->valid = true;
bbox->x0 = 0;
bbox->y0 = 0;
bbox->x1 = priv->x_charsize * strlen(text);
bbox->y1 = priv->y_charsize;
return 0;
}
void vidconsole_push_colour(struct udevice *dev, enum colour_idx fg,
enum colour_idx bg, struct vidconsole_colour *old)
{
struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent);
old->colour_fg = vid_priv->colour_fg;
old->colour_bg = vid_priv->colour_bg;
vid_priv->colour_fg = video_index_to_colour(vid_priv, fg);
vid_priv->colour_bg = video_index_to_colour(vid_priv, bg);
}
void vidconsole_pop_colour(struct udevice *dev, struct vidconsole_colour *old)
{
struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent);
vid_priv->colour_fg = old->colour_fg;
vid_priv->colour_bg = old->colour_bg;
}
/* Set up the number of rows and colours (rotated drivers override this) */
static int vidconsole_pre_probe(struct udevice *dev)
{

View file

@ -142,6 +142,58 @@ int video_reserve(ulong *addrp)
return 0;
}
int video_fill_part(struct udevice *dev, int xstart, int ystart, int xend,
int yend, u32 colour)
{
struct video_priv *priv = dev_get_uclass_priv(dev);
void *start, *line;
int pixels = xend - xstart;
int row, i, ret;
start = priv->fb + ystart * priv->line_length;
start += xstart * VNBYTES(priv->bpix);
line = start;
for (row = ystart; row < yend; row++) {
switch (priv->bpix) {
case VIDEO_BPP8: {
u8 *dst = line;
if (IS_ENABLED(CONFIG_VIDEO_BPP8)) {
for (i = 0; i < pixels; i++)
*dst++ = colour;
}
break;
}
case VIDEO_BPP16: {
u16 *dst = line;
if (IS_ENABLED(CONFIG_VIDEO_BPP16)) {
for (i = 0; i < pixels; i++)
*dst++ = colour;
}
break;
}
case VIDEO_BPP32: {
u32 *dst = line;
if (IS_ENABLED(CONFIG_VIDEO_BPP32)) {
for (i = 0; i < pixels; i++)
*dst++ = colour;
}
break;
}
default:
return -ENOSYS;
}
line += priv->line_length;
}
ret = video_sync_copy(dev, start, line);
if (ret)
return ret;
return 0;
}
int video_fill(struct udevice *dev, u32 colour)
{
struct video_priv *priv = dev_get_uclass_priv(dev);
@ -208,7 +260,7 @@ static const struct vid_rgb colours[VID_COLOUR_COUNT] = {
{ 0xff, 0xff, 0xff }, /* white */
};
u32 video_index_to_colour(struct video_priv *priv, unsigned int idx)
u32 video_index_to_colour(struct video_priv *priv, enum colour_idx idx)
{
switch (priv->bpix) {
case VIDEO_BPP16:

58
fs/fs.c
View file

@ -13,6 +13,7 @@
#include <env.h>
#include <lmb.h>
#include <log.h>
#include <malloc.h>
#include <mapmem.h>
#include <part.h>
#include <ext4fs.h>
@ -26,6 +27,7 @@
#include <asm/io.h>
#include <div64.h>
#include <linux/math64.h>
#include <linux/sizes.h>
#include <efi_loader.h>
#include <squashfs.h>
#include <erofs.h>
@ -1008,3 +1010,59 @@ int do_fs_types(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[])
puts("\n");
return CMD_RET_SUCCESS;
}
int fs_read_alloc(const char *fname, ulong size, uint align, void **bufp)
{
loff_t bytes_read;
ulong addr;
char *buf;
int ret;
buf = memalign(align, size + 1);
if (!buf)
return log_msg_ret("buf", -ENOMEM);
addr = map_to_sysmem(buf);
ret = fs_read(fname, addr, 0, size, &bytes_read);
if (ret) {
free(buf);
return log_msg_ret("read", ret);
}
if (size != bytes_read)
return log_msg_ret("bread", -EIO);
buf[size] = '\0';
*bufp = buf;
return 0;
}
int fs_load_alloc(const char *ifname, const char *dev_part_str,
const char *fname, ulong max_size, ulong align, void **bufp,
ulong *sizep)
{
loff_t size;
void *buf;
int ret;
if (fs_set_blk_dev(ifname, dev_part_str, FS_TYPE_ANY))
return log_msg_ret("set", -ENOMEDIUM);
ret = fs_size(fname, &size);
if (ret)
return log_msg_ret("sz", -ENOENT);
if (size >= (max_size ?: SZ_1G))
return log_msg_ret("sz", -E2BIG);
if (fs_set_blk_dev(ifname, dev_part_str, FS_TYPE_ANY))
return log_msg_ret("set", -ENOMEDIUM);
ret = fs_read_alloc(fname, size, align, &buf);
if (ret)
return log_msg_ret("al", ret);
*sizep = size;
*bufp = buf;
return 0;
}

View file

@ -63,6 +63,8 @@ struct device_node {
struct device_node *sibling;
};
#define BAD_OF_ROOT 0xdead11e3
#define OF_MAX_PHANDLE_ARGS 16
/**

View file

@ -352,6 +352,16 @@ static inline oftree oftree_from_np(struct device_node *root)
return tree;
}
/**
* oftree_dispose() - Dispose of an oftree
*
* This can be used to dispose of a tree that has been created (other than
* the control FDT which must not be disposed)
*
* @tree: Tree to dispose
*/
void oftree_dispose(oftree tree);
/**
* ofnode_name_eq() - Check if the node name is equivalent to a given name
* ignoring the unit address

View file

@ -7,22 +7,31 @@
#ifndef __SCENE_H
#define __SCENE_H
#include <dm/ofnode_decl.h>
#include <linux/list.h>
struct udevice;
struct video_priv;
/**
* enum expoact_type - types of actions reported by the expo
*
* @EXPOACT_NONE: no action
* @EXPOACT_POINT: menu item was highlighted (@id indicates which)
* @EXPOACT_POINT_OBJ: object was highlighted (@id indicates which)
* @EXPOACT_POINT_ITEM: menu item was highlighted (@id indicates which)
* @EXPOACT_SELECT: menu item was selected (@id indicates which)
* @EXPOACT_OPEN: menu was opened, so an item can be selected (@id indicates
* which menu object)
* @EXPOACT_CLOSE: menu was closed (@id indicates which menu object)
* @EXPOACT_QUIT: request to exit the menu
*/
enum expoact_type {
EXPOACT_NONE,
EXPOACT_POINT,
EXPOACT_POINT_OBJ,
EXPOACT_POINT_ITEM,
EXPOACT_SELECT,
EXPOACT_OPEN,
EXPOACT_CLOSE,
EXPOACT_QUIT,
};
@ -30,7 +39,7 @@ enum expoact_type {
* struct expo_action - an action report by the expo
*
* @type: Action type (EXPOACT_NONE if there is no action)
* @select: Used for EXPOACT_POINT and EXPOACT_SELECT
* @select: Used for EXPOACT_POINT_ITEM and EXPOACT_SELECT
* @id: ID number of the object affected.
*/
struct expo_action {
@ -42,6 +51,19 @@ struct expo_action {
};
};
/**
* struct expo_theme - theme for the expo
*
* @font_size: Default font size for all text
* @menu_inset: Inset width (on each side and top/bottom) for menu items
* @menuitem_gap_y: Gap between menu items in pixels
*/
struct expo_theme {
u32 font_size;
u32 menu_inset;
u32 menuitem_gap_y;
};
/**
* struct expo - information about an expo
*
@ -50,23 +72,29 @@ struct expo_action {
*
* @name: Name of the expo (allocated)
* @display: Display to use (`UCLASS_VIDEO`), or NULL to use text mode
* @cons: Console to use (`UCLASS_VIDEO_CONSOLE`), or NULL to use text mode
* @scene_id: Current scene ID (0 if none)
* @next_id: Next ID number to use, for automatic allocation
* @action: Action selected by user. At present only one is supported, with the
* type set to EXPOACT_NONE if there is no action
* @text_mode: true to use text mode for the menu (no vidconsole)
* @popup: true to use popup menus, instead of showing all items
* @priv: Private data for the controller
* @theme: Information about fonts styles, etc.
* @scene_head: List of scenes
* @str_head: list of strings
*/
struct expo {
char *name;
struct udevice *display;
struct udevice *cons;
uint scene_id;
uint next_id;
struct expo_action action;
bool text_mode;
bool popup;
void *priv;
struct expo_theme theme;
struct list_head scene_head;
struct list_head str_head;
};
@ -92,7 +120,8 @@ struct expo_string {
* @expo: Expo this scene is part of
* @name: Name of the scene (allocated)
* @id: ID number of the scene
* @title: Title of the scene (allocated)
* @title_id: String ID of title of the scene (allocated)
* @highlight_id: ID of highlighted object, if any
* @sibling: Node to link this scene to its siblings
* @obj_head: List of objects in the scene
*/
@ -100,7 +129,8 @@ struct scene {
struct expo *expo;
char *name;
uint id;
char *title;
uint title_id;
uint highlight_id;
struct list_head sibling;
struct list_head obj_head;
};
@ -120,6 +150,35 @@ enum scene_obj_t {
SCENEOBJT_MENU,
};
/**
* struct scene_dim - Dimensions of an object
*
* @x: x position, in pixels from left side
* @y: y position, in pixels from top
* @w: width, in pixels
* @h: height, in pixels
*/
struct scene_dim {
int x;
int y;
int w;
int h;
};
/**
* enum scene_obj_flags_t - flags for objects
*
* @SCENEOF_HIDE: object should be hidden
* @SCENEOF_POINT: object should be highlighted
* @SCENEOF_OPEN: object should be opened (e.g. menu is opened so that an option
* can be selected)
*/
enum scene_obj_flags_t {
SCENEOF_HIDE = 1 << 0,
SCENEOF_POINT = 1 << 1,
SCENEOF_OPEN = 1 << 2,
};
/**
* struct scene_obj - information about an object in a scene
*
@ -127,9 +186,8 @@ enum scene_obj_t {
* @name: Name of the object (allocated)
* @id: ID number of the object
* @type: Type of this object
* @x: x position, in pixels from left side
* @y: y position, in pixels from top
* @hide: true if the object should be hidden
* @dim: Dimensions for this object
* @flags: Flags for this object
* @sibling: Node to link this object to its siblings
*/
struct scene_obj {
@ -137,9 +195,8 @@ struct scene_obj {
char *name;
uint id;
enum scene_obj_t type;
int x;
int y;
bool hide;
struct scene_dim dim;
int flags;
struct list_head sibling;
};
@ -255,6 +312,25 @@ int expo_new(const char *name, void *priv, struct expo **expp);
*/
void expo_destroy(struct expo *exp);
/**
* expo_set_dynamic_start() - Set the start of the 'dynamic' IDs
*
* It is common for a set of 'static' IDs to be used to refer to objects in the
* expo. These typically use an enum so that they are defined in sequential
* order.
*
* Dynamic IDs (for objects not in the enum) are intended to be used for
* objects to which the code does not need to refer. These are ideally located
* above the static IDs.
*
* Use this function to set the start of the dynamic range, making sure that the
* value is higher than all the statically allocated IDs.
*
* @exp: Expo to update
* @dyn_start: Start ID that expo should use for dynamic allocation
*/
void expo_set_dynamic_start(struct expo *exp, uint dyn_start);
/**
* expo_str() - add a new string to an expo
*
@ -284,6 +360,16 @@ const char *expo_get_str(struct expo *exp, uint id);
*/
int expo_set_display(struct expo *exp, struct udevice *dev);
/**
* expo_calc_dims() - Calculate the dimensions of the objects
*
* Updates the width and height of all objects based on their contents
*
* @exp: Expo to update
* Returns 0 if OK, -ENOTSUPP if there is no graphical console
*/
int expo_calc_dims(struct expo *exp);
/**
* expo_set_scene_id() - Set the current scene ID
*
@ -293,6 +379,14 @@ int expo_set_display(struct expo *exp, struct udevice *dev);
*/
int expo_set_scene_id(struct expo *exp, uint scene_id);
/**
* expo_first_scene_id() - Get the ID of the first scene
*
* @exp: Expo to check
* Returns: Scene ID of first scene, or -ENOENT if there are no scenes
*/
int expo_first_scene_id(struct expo *exp);
/**
* expo_render() - render the expo on the display / console
*
@ -304,12 +398,12 @@ int expo_set_scene_id(struct expo *exp, uint scene_id);
int expo_render(struct expo *exp);
/**
* exp_set_text_mode() - Controls whether the expo renders in text mode
* expo_set_text_mode() - Controls whether the expo renders in text mode
*
* @exp: Expo to update
* @text_mode: true to use text mode, false to use the console
*/
void exp_set_text_mode(struct expo *exp, bool text_mode);
void expo_set_text_mode(struct expo *exp, bool text_mode);
/**
* scene_new() - create a new scene in a expo
@ -334,14 +428,44 @@ int scene_new(struct expo *exp, const char *name, uint id, struct scene **scnp);
*/
struct scene *expo_lookup_scene_id(struct expo *exp, uint scene_id);
/**
* scene_highlight_first() - Highlight the first item in a scene
*
* This highlights the first item, so that the user can see that it is pointed
* to
*
* @scn: Scene to update
*/
void scene_highlight_first(struct scene *scn);
/**
* scene_set_highlight_id() - Set the object which is highlighted
*
* Sets a new object to highlight in the scene
*
* @scn: Scene to update
* @id: ID of object to highlight
*/
void scene_set_highlight_id(struct scene *scn, uint id);
/**
* scene_set_open() - Set whether an item is open or not
*
* @scn: Scene to update
* @id: ID of object to update
* @open: true to open the object, false to close it
* Returns: 0 if OK, -ENOENT if @id is invalid
*/
int scene_set_open(struct scene *scn, uint id, bool open);
/**
* scene_title_set() - set the scene title
*
* @scn: Scene to update
* @title: Title to set, NULL if none (this is allocated by this call)
* Returns: 0 if OK, -ENOMEM if out of memory
* @title_id: Title ID to set
* Returns: 0 if OK
*/
int scene_title_set(struct scene *scn, const char *title);
int scene_title_set(struct scene *scn, uint title_id);
/**
* scene_obj_count() - Count the number of objects in a scene
@ -425,6 +549,17 @@ int scene_txt_set_font(struct scene *scn, uint id, const char *font_name,
*/
int scene_obj_set_pos(struct scene *scn, uint id, int x, int y);
/**
* scene_obj_set_size() - Set the size of an object
*
* @scn: Scene to update
* @id: ID of object to update
* @w: width in pixels
* @h: height in pixels
* Returns: 0 if OK, -ENOENT if @id is invalid
*/
int scene_obj_set_size(struct scene *scn, uint id, int w, int h);
/**
* scene_obj_set_hide() - Set whether an object is hidden
*
@ -519,4 +654,46 @@ int expo_send_key(struct expo *exp, int key);
*/
int expo_action_get(struct expo *exp, struct expo_action *act);
/**
* expo_apply_theme() - Apply a theme to an expo
*
* @exp: Expo to update
* @node: Node containing the theme
*/
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);
/**
* cedit_arange() - Arrange objects in a configuration-editor scene
*
* @exp: Expo to update
* @vid_priv: Private info of the video device
* @scene_id: scene ID to arrange
* Returns: 0 if OK, -ve on error
*/
int cedit_arange(struct expo *exp, struct video_priv *vid_priv, uint scene_id);
/**
* cedit_run() - Run a configuration editor
*
* This accepts input until the user quits with Escape
*
* @exp: Expo to use
* Returns: 0 if OK, -ve on error
*/
int cedit_run(struct expo *exp);
#endif /*__SCENE_H */

View file

@ -300,4 +300,42 @@ int do_fs_type(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]);
*/
int do_fs_types(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[]);
/**
* fs_read_alloc() - Allocate space for a file and read it
*
* You must call fs_set_blk_dev() or a similar function before calling this,
* since that sets up the block device to use.
*
* The file is terminated with a nul character
*
* @fname: Filename to read
* @size: Size of file to read (must be correct!)
* @align: Alignment to use for memory allocation (0 for default)
* @bufp: On success, returns the allocated buffer with the nul-terminated file
* in it
* Return: 0 if OK, -ENOMEM if out of memory, -EIO if read failed
*/
int fs_read_alloc(const char *fname, ulong size, uint align, void **bufp);
/**
* fs_load_alloc() - Load a file into allocated space
*
* The file is terminated with a nul character
*
* @ifname: Interface name to read from (e.g. "mmc")
* @dev_part_str: Device and partition string (e.g. "1:2")
* @fname: Filename to read
* @max_size: Maximum allowed size for the file (use 0 for 1GB)
* @align: Alignment to use for memory allocation (0 for default)
* @bufp: On success, returns the allocated buffer with the nul-terminated file
* in it
* @sizep: On success, returns the size of the file
* Return: 0 if OK, -ENOMEM if out of memory, -ENOENT if the file does not
* exist, -ENOMEDIUM if the device does not exist, -E2BIG if the file is too
* large (greater than @max_size), -EIO if read failed
*/
int fs_load_alloc(const char *ifname, const char *dev_part_str,
const char *fname, ulong max_size, ulong align, void **bufp,
ulong *sizep);
#endif /* _FS_H */

View file

@ -102,6 +102,8 @@ enum log_category_t {
LOGC_EVENT,
/** @LOGC_FS: Related to filesystems */
LOGC_FS,
/** @LOGC_EXPO: Related to expo handling */
LOGC_EXPO,
/** @LOGC_COUNT: Number of log categories */
LOGC_COUNT,
/** @LOGC_END: Sentinel value for lists of log categories */

View file

@ -36,4 +36,14 @@ int of_live_build(const void *fdt_blob, struct device_node **rootp);
*/
int unflatten_device_tree(const void *blob, struct device_node **mynodes);
/**
* of_live_free() - Dispose of a livetree
*
* This frees memory used by the tree, after which @root becomes invalid and
* cannot be used
*
* @root: Tree to dispose
*/
void of_live_free(struct device_node *root);
#endif

29
include/test/cedit-test.h Normal file
View 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

View file

@ -130,7 +130,7 @@ int ut_check_console_dump(struct unit_test_state *uts, int total_bytes);
\
if (!(cond)) { \
ut_fail(uts, __FILE__, __LINE__, __func__, #cond); \
__ret = CMD_RET_FAILURE; \
return CMD_RET_FAILURE; \
} \
__ret; \
})
@ -142,7 +142,7 @@ int ut_check_console_dump(struct unit_test_state *uts, int total_bytes);
if (!(cond)) { \
ut_failf(uts, __FILE__, __LINE__, __func__, #cond, \
fmt, ##args); \
__ret = CMD_RET_FAILURE; \
return CMD_RET_FAILURE; \
} \
__ret; \
})
@ -157,7 +157,7 @@ int ut_check_console_dump(struct unit_test_state *uts, int total_bytes);
#expr1 " == " #expr2, \
"Expected %#x (%d), got %#x (%d)", \
_val1, _val1, _val2, _val2); \
__ret = CMD_RET_FAILURE; \
return CMD_RET_FAILURE; \
} \
__ret; \
})
@ -175,7 +175,7 @@ int ut_check_console_dump(struct unit_test_state *uts, int total_bytes);
(unsigned long long)_val1, \
(unsigned long long)_val2, \
(unsigned long long)_val2); \
__ret = CMD_RET_FAILURE; \
return CMD_RET_FAILURE; \
} \
__ret; \
})
@ -189,7 +189,7 @@ int ut_check_console_dump(struct unit_test_state *uts, int total_bytes);
ut_failf(uts, __FILE__, __LINE__, __func__, \
#expr1 " = " #expr2, \
"Expected \"%s\", got \"%s\"", _val1, _val2); \
__ret = CMD_RET_FAILURE; \
return CMD_RET_FAILURE; \
} \
__ret; \
})
@ -208,7 +208,7 @@ int ut_check_console_dump(struct unit_test_state *uts, int total_bytes);
#expr1 " = " #expr2, \
"Expected \"%.*s\", got \"%.*s\"", \
_len, _val1, _len, _val2); \
__ret = CMD_RET_FAILURE; \
return CMD_RET_FAILURE; \
} \
__ret; \
})
@ -228,7 +228,7 @@ int ut_check_console_dump(struct unit_test_state *uts, int total_bytes);
#expr1 " = " #expr2, \
"Expected \"%s\", got \"%s\"", \
__buf1, __buf2); \
__ret = CMD_RET_FAILURE; \
return CMD_RET_FAILURE; \
} \
__ret; \
})
@ -242,7 +242,7 @@ int ut_check_console_dump(struct unit_test_state *uts, int total_bytes);
ut_failf(uts, __FILE__, __LINE__, __func__, \
#expr1 " = " #expr2, \
"Expected %p, got %p", _val1, _val2); \
__ret = CMD_RET_FAILURE; \
return CMD_RET_FAILURE; \
} \
__ret; \
})
@ -257,7 +257,7 @@ int ut_check_console_dump(struct unit_test_state *uts, int total_bytes);
ut_failf(uts, __FILE__, __LINE__, __func__, \
#expr1 " = " #expr2, \
"Expected %lx, got %lx", _val1, _val2); \
__ret = CMD_RET_FAILURE; \
return CMD_RET_FAILURE; \
} \
__ret; \
})
@ -271,7 +271,7 @@ int ut_check_console_dump(struct unit_test_state *uts, int total_bytes);
ut_failf(uts, __FILE__, __LINE__, __func__, \
#expr " != NULL", \
"Expected NULL, got %p", _val); \
__ret = CMD_RET_FAILURE; \
return CMD_RET_FAILURE; \
} \
__ret; \
})
@ -285,7 +285,7 @@ int ut_check_console_dump(struct unit_test_state *uts, int total_bytes);
ut_failf(uts, __FILE__, __LINE__, __func__, \
#expr " = NULL", \
"Expected non-null, got NULL"); \
__ret = CMD_RET_FAILURE; \
return CMD_RET_FAILURE; \
} \
__ret; \
})
@ -300,7 +300,7 @@ int ut_check_console_dump(struct unit_test_state *uts, int total_bytes);
#expr " = NULL", \
"Expected pointer, got error %ld", \
PTR_ERR(_val)); \
__ret = CMD_RET_FAILURE; \
return CMD_RET_FAILURE; \
} \
__ret; \
})
@ -316,7 +316,7 @@ int ut_check_console_dump(struct unit_test_state *uts, int total_bytes);
ut_failf(uts, __FILE__, __LINE__, __func__, \
"console", "\nExpected '%s',\n got '%s'", \
uts->expect_str, uts->actual_str); \
__ret = CMD_RET_FAILURE; \
return CMD_RET_FAILURE; \
} \
__ret; \
})
@ -329,7 +329,7 @@ int ut_check_console_dump(struct unit_test_state *uts, int total_bytes);
ut_failf(uts, __FILE__, __LINE__, __func__, \
"console", "\nExpected '%s',\n got '%s'", \
uts->expect_str, uts->actual_str); \
__ret = CMD_RET_FAILURE; \
return CMD_RET_FAILURE; \
} \
__ret; \
})
@ -341,7 +341,7 @@ int ut_check_console_dump(struct unit_test_state *uts, int total_bytes);
if (ut_check_skipline(uts)) { \
ut_failf(uts, __FILE__, __LINE__, __func__, \
"console", "\nExpected a line, got end"); \
__ret = CMD_RET_FAILURE; \
return CMD_RET_FAILURE; \
} \
__ret; \
})
@ -354,7 +354,7 @@ int ut_check_console_dump(struct unit_test_state *uts, int total_bytes);
ut_failf(uts, __FILE__, __LINE__, __func__, \
"console", "\nExpected '%s',\n got to '%s'", \
uts->expect_str, uts->actual_str); \
__ret = CMD_RET_FAILURE; \
return CMD_RET_FAILURE; \
} \
__ret; \
})
@ -367,7 +367,7 @@ int ut_check_console_dump(struct unit_test_state *uts, int total_bytes);
ut_failf(uts, __FILE__, __LINE__, __func__, \
"console", "Expected no more output, got '%s'",\
uts->actual_str); \
__ret = CMD_RET_FAILURE; \
return CMD_RET_FAILURE; \
} \
__ret; \
})
@ -381,7 +381,7 @@ int ut_check_console_dump(struct unit_test_state *uts, int total_bytes);
"console", \
"Expected dump of length %x bytes, got '%s'", \
total_bytes, uts->actual_str); \
__ret = CMD_RET_FAILURE; \
return CMD_RET_FAILURE; \
} \
__ret; \
})

View file

@ -163,11 +163,11 @@ enum colour_idx {
* The caller has to guarantee that the color index is less than
* VID_COLOR_COUNT.
*
* @priv private data of the console device
* @idx color index
* @priv private data of the video device (UCLASS_VIDEO)
* @idx color index (e.g. VID_YELLOW)
* Return: color value
*/
u32 video_index_to_colour(struct video_priv *priv, unsigned int idx);
u32 video_index_to_colour(struct video_priv *priv, enum colour_idx idx);
/**
* video_reserve() - Reserve frame-buffer memory for video devices
@ -204,6 +204,22 @@ int video_clear(struct udevice *dev);
*/
int video_fill(struct udevice *dev, u32 colour);
/**
* video_fill_part() - Erase a region
*
* Erase a rectangle of the display within the given bounds.
*
* @dev: Device to update
* @xstart: X start position in pixels from the left
* @ystart: Y start position in pixels from the top
* @xend: X end position in pixels from the left
* @yend: Y end position in pixels from the top
* @colour: Value to write
* Return: 0 if OK, -ENOSYS if the display depth is not supported
*/
int video_fill_part(struct udevice *dev, int xstart, int ystart, int xend,
int yend, u32 colour);
/**
* video_sync() - Sync a device's frame buffer with its hardware
*

View file

@ -71,6 +71,38 @@ struct vidfont_info {
const char *name;
};
/**
* struct vidconsole_colour - Holds colour information
*
* @colour_fg: Foreground colour (pixel value)
* @colour_bg: Background colour (pixel value)
*/
struct vidconsole_colour {
u32 colour_fg;
u32 colour_bg;
};
/**
* struct vidconsole_bbox - Bounding box of text
*
* This describes the bounding box of something, measured in pixels. The x0/y0
* pair is inclusive; the x1/y2 pair is exclusive, meaning that it is one pixel
* beyond the extent of the object
*
* @valid: Values are valid (bounding box is known)
* @x0: left x position, in pixels from left side
* @y0: top y position, in pixels from top
* @x1: right x position + 1
* @y1: botton y position + 1
*/
struct vidconsole_bbox {
bool valid;
int x0;
int y0;
int x1;
int y1;
};
/**
* struct vidconsole_ops - Video console operations
*
@ -178,6 +210,20 @@ struct vidconsole_ops {
* Returns: 0 on success, -ENOENT if no such font
*/
int (*select_font)(struct udevice *dev, const char *name, uint size);
/**
* measure() - Measure the bounds of some text
*
* @dev: Device to adjust
* @name: Font name to use (NULL to use default)
* @size: Font size to use (0 to use default)
* @text: Text to measure
* @bbox: Returns bounding box of text, assuming it is positioned
* at 0,0
* Returns: 0 on success, -ENOENT if no such font
*/
int (*measure)(struct udevice *dev, const char *name, uint size,
const char *text, struct vidconsole_bbox *bbox);
};
/* Get a pointer to the driver operations for a video console device */
@ -204,6 +250,38 @@ int vidconsole_get_font(struct udevice *dev, int seq,
*/
int vidconsole_select_font(struct udevice *dev, const char *name, uint size);
/*
* vidconsole_measure() - Measuring the bounding box of some text
*
* @dev: Console device to use
* @name: Font name, NULL for default
* @size: Font size, ignored if @name is NULL
* @text: Text to measure
* @bbox: Returns nounding box of text
* Returns: 0 if OK, -ve on error
*/
int vidconsole_measure(struct udevice *dev, const char *name, uint size,
const char *text, struct vidconsole_bbox *bbox);
/**
* vidconsole_push_colour() - Temporarily change the font colour
*
* @dev: Device to adjust
* @fg: Foreground colour to select
* @bg: Background colour to select
* @old: Place to store the current colour, so it can be restored
*/
void vidconsole_push_colour(struct udevice *dev, enum colour_idx fg,
enum colour_idx bg, struct vidconsole_colour *old);
/**
* vidconsole_pop_colour() - Restore the original colour
*
* @dev: Device to adjust
* @old: Old colour to be restored
*/
void vidconsole_pop_colour(struct udevice *dev, struct vidconsole_colour *old);
/**
* vidconsole_putc_xy() - write a single character to a position
*

View file

@ -287,9 +287,12 @@ int unflatten_device_tree(const void *blob, struct device_node **mynodes)
debug(" size is %lx, allocating...\n", size);
/* Allocate memory for the expanded device tree */
mem = malloc(size + 4);
mem = memalign(__alignof__(struct device_node), size + 4);
memset(mem, '\0', size);
/* Set up value for dm_test_livetree_align() */
*(u32 *)mem = BAD_OF_ROOT;
*(__be32 *)(mem + size) = cpu_to_be32(0xdeadbeef);
debug(" unflattening %p...\n", mem);
@ -327,3 +330,9 @@ int of_live_build(const void *fdt_blob, struct device_node **rootp)
return ret;
}
void of_live_free(struct device_node *root)
{
/* the tree is stored as a contiguous block of memory */
free(root);
}

View file

@ -5,6 +5,7 @@
*/
#include <common.h>
#include <command.h>
#include <dm.h>
#include <expo.h>
#include <menu.h>
@ -13,6 +14,7 @@
#include <test/suites.h>
#include <test/ut.h>
#include "bootstd_common.h"
#include <test/cedit-test.h>
#include "../../boot/scene_internal.h"
enum {
@ -28,6 +30,8 @@ enum {
OBJ_MENU_TITLE,
/* strings */
STR_SCENE_TITLE,
STR_TEXT,
STR_TEXT2,
STR_MENU_TITLE,
@ -120,7 +124,7 @@ static int expo_scene(struct unit_test_state *uts)
struct expo *exp;
ulong start_mem;
char name[100];
int id;
int id, title_id;
start_mem = ut_check_free();
@ -141,21 +145,20 @@ static int expo_scene(struct unit_test_state *uts)
ut_asserteq_str(SCENE_NAME1, scn->name);
/* Set the title */
strcpy(name, SCENE_TITLE);
ut_assertok(scene_title_set(scn, name));
*name = '\0';
ut_assertnonnull(scn->title);
ut_asserteq_str(SCENE_TITLE, scn->title);
title_id = expo_str(exp, "title", STR_SCENE_TITLE, SCENE_TITLE);
ut_assert(title_id >= 0);
/* Use an allocated ID */
/* Use an allocated ID - this will be allocated after the title str */
scn = NULL;
id = scene_new(exp, SCENE_NAME2, 0, &scn);
ut_assertnonnull(scn);
ut_asserteq(SCENE2, id);
ut_asserteq(SCENE2 + 1, exp->next_id);
ut_assertok(scene_title_set(scn, title_id));
ut_asserteq(STR_SCENE_TITLE + 1, id);
ut_asserteq(STR_SCENE_TITLE + 2, exp->next_id);
ut_asserteq_ptr(exp, scn->expo);
ut_asserteq_str(SCENE_NAME2, scn->name);
ut_asserteq(title_id, scn->title_id);
expo_destroy(exp);
@ -225,7 +228,7 @@ static int expo_object(struct unit_test_state *uts)
}
BOOTSTD_TEST(expo_object, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
/* Check setting object attributes */
/* Check setting object attributes and using themes */
static int expo_object_attr(struct unit_test_state *uts)
{
struct scene_obj_menu *menu;
@ -235,6 +238,7 @@ static int expo_object_attr(struct unit_test_state *uts)
struct expo *exp;
ulong start_mem;
char name[100];
ofnode node;
char *data;
int id;
@ -249,8 +253,8 @@ static int expo_object_attr(struct unit_test_state *uts)
ut_assert(id > 0);
ut_assertok(scene_obj_set_pos(scn, OBJ_LOGO, 123, 456));
ut_asserteq(123, img->obj.x);
ut_asserteq(456, img->obj.y);
ut_asserteq(123, img->obj.dim.x);
ut_asserteq(456, img->obj.dim.y);
ut_asserteq(-ENOENT, scene_obj_set_pos(scn, OBJ_TEXT2, 0, 0));
@ -272,6 +276,11 @@ static int expo_object_attr(struct unit_test_state *uts)
ut_asserteq(-ENOENT, scene_menu_set_title(scn, OBJ_TEXT2, OBJ_TEXT));
ut_asserteq(-EINVAL, scene_menu_set_title(scn, OBJ_MENU, OBJ_TEXT2));
node = ofnode_path("/bootstd/theme");
ut_assert(ofnode_valid(node));
ut_assertok(expo_apply_theme(exp, node));
ut_asserteq(30, txt->font_size);
expo_destroy(exp);
ut_assertok(ut_check_delta(start_mem));
@ -306,8 +315,8 @@ static int expo_object_menu(struct unit_test_state *uts)
ut_asserteq(0, menu->pointer_id);
ut_assertok(scene_obj_set_pos(scn, OBJ_MENU, 50, 400));
ut_asserteq(50, menu->obj.x);
ut_asserteq(400, menu->obj.y);
ut_asserteq(50, menu->obj.dim.x);
ut_asserteq(400, menu->obj.dim.y);
id = scene_txt_str(scn, "title", OBJ_MENU_TITLE, STR_MENU_TITLE,
"Main Menu", &tit);
@ -347,29 +356,31 @@ static int expo_object_menu(struct unit_test_state *uts)
ut_asserteq(desc_id, item->desc_id);
ut_asserteq(preview_id, item->preview_id);
/* adding an item should cause the first item to become current */
ut_assertok(scene_arrange(scn));
/* arranging the scene should cause the first item to become current */
ut_asserteq(id, menu->cur_item_id);
/* the title should be at the top */
ut_asserteq(menu->obj.x, tit->obj.x);
ut_asserteq(menu->obj.y, tit->obj.y);
ut_asserteq(menu->obj.dim.x, tit->obj.dim.x);
ut_asserteq(menu->obj.dim.y, tit->obj.dim.y);
/* the first item should be next */
ut_asserteq(menu->obj.x, name1->obj.x);
ut_asserteq(menu->obj.y + 32, name1->obj.y);
ut_asserteq(menu->obj.dim.x, name1->obj.dim.x);
ut_asserteq(menu->obj.dim.y + 32, name1->obj.dim.y);
ut_asserteq(menu->obj.x + 230, key1->obj.x);
ut_asserteq(menu->obj.y + 32, key1->obj.y);
ut_asserteq(menu->obj.dim.x + 230, key1->obj.dim.x);
ut_asserteq(menu->obj.dim.y + 32, key1->obj.dim.y);
ut_asserteq(menu->obj.x + 200, ptr->obj.x);
ut_asserteq(menu->obj.y + 32, ptr->obj.y);
ut_asserteq(menu->obj.dim.x + 200, ptr->obj.dim.x);
ut_asserteq(menu->obj.dim.y + 32, ptr->obj.dim.y);
ut_asserteq(menu->obj.x + 280, desc1->obj.x);
ut_asserteq(menu->obj.y + 32, desc1->obj.y);
ut_asserteq(menu->obj.dim.x + 280, desc1->obj.dim.x);
ut_asserteq(menu->obj.dim.y + 32, desc1->obj.dim.y);
ut_asserteq(-4, prev1->obj.x);
ut_asserteq(menu->obj.y + 32, prev1->obj.y);
ut_asserteq(false, prev1->obj.hide);
ut_asserteq(-4, prev1->obj.dim.x);
ut_asserteq(menu->obj.dim.y + 32, prev1->obj.dim.y);
ut_asserteq(true, prev1->obj.flags & SCENEOF_HIDE);
expo_destroy(exp);
@ -470,6 +481,48 @@ static int expo_render_image(struct unit_test_state *uts)
/* render without a scene */
ut_asserteq(-ECHILD, expo_render(exp));
ut_assertok(expo_calc_dims(exp));
ut_assertok(scene_arrange(scn));
/* check dimensions of text */
obj = scene_obj_find(scn, OBJ_TEXT, SCENEOBJT_NONE);
ut_assertnonnull(obj);
ut_asserteq(400, obj->dim.x);
ut_asserteq(100, obj->dim.y);
ut_asserteq(126, obj->dim.w);
ut_asserteq(40, obj->dim.h);
/* check dimensions of image */
obj = scene_obj_find(scn, OBJ_LOGO, SCENEOBJT_NONE);
ut_assertnonnull(obj);
ut_asserteq(50, obj->dim.x);
ut_asserteq(20, obj->dim.y);
ut_asserteq(160, obj->dim.w);
ut_asserteq(160, obj->dim.h);
/* check dimensions of menu labels - both should be the same width */
obj = scene_obj_find(scn, ITEM1_LABEL, SCENEOBJT_NONE);
ut_assertnonnull(obj);
ut_asserteq(50, obj->dim.x);
ut_asserteq(436, obj->dim.y);
ut_asserteq(29, obj->dim.w);
ut_asserteq(18, obj->dim.h);
obj = scene_obj_find(scn, ITEM2_LABEL, SCENEOBJT_NONE);
ut_assertnonnull(obj);
ut_asserteq(50, obj->dim.x);
ut_asserteq(454, obj->dim.y);
ut_asserteq(29, obj->dim.w);
ut_asserteq(18, obj->dim.h);
/* check dimensions of menu */
obj = scene_obj_find(scn, OBJ_MENU, SCENEOBJT_NONE);
ut_assertnonnull(obj);
ut_asserteq(50, obj->dim.x);
ut_asserteq(400, obj->dim.y);
ut_asserteq(160, obj->dim.w);
ut_asserteq(160, obj->dim.h);
/* render it */
expo_set_scene_id(exp, SCENE1);
ut_assertok(expo_render(exp));
@ -479,16 +532,16 @@ static int expo_render_image(struct unit_test_state *uts)
ut_assertok(expo_action_get(exp, &act));
ut_asserteq(EXPOACT_POINT, act.type);
ut_asserteq(EXPOACT_POINT_ITEM, act.type);
ut_asserteq(ITEM2, act.select.id);
ut_assertok(expo_render(exp));
/* make sure only the preview for the second item is shown */
obj = scene_obj_find(scn, ITEM1_PREVIEW, SCENEOBJT_NONE);
ut_asserteq(true, obj->hide);
ut_asserteq(true, obj->flags & SCENEOF_HIDE);
obj = scene_obj_find(scn, ITEM2_PREVIEW, SCENEOBJT_NONE);
ut_asserteq(false, obj->hide);
ut_asserteq(false, obj->flags & SCENEOF_HIDE);
/* select it */
ut_assertok(expo_send_key(exp, BKEY_SELECT));
@ -504,7 +557,7 @@ static int expo_render_image(struct unit_test_state *uts)
ut_assert_console_end();
/* now try in text mode */
exp_set_text_mode(exp, true);
expo_set_text_mode(exp, true);
ut_assertok(expo_render(exp));
ut_assert_nextline("U-Boot : Boot Menu");
@ -519,7 +572,7 @@ static int expo_render_image(struct unit_test_state *uts)
ut_assertok(expo_action_get(exp, &act));
ut_asserteq(EXPOACT_POINT, act.type);
ut_asserteq(EXPOACT_POINT_ITEM, act.type);
ut_asserteq(ITEM1, act.select.id);
ut_assertok(expo_render(exp));
@ -537,3 +590,125 @@ static int expo_render_image(struct unit_test_state *uts)
return 0;
}
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);
/* Check the cedit command */
static int expo_cedit(struct unit_test_state *uts)
{
extern struct expo *cur_exp;
struct scene_obj_menu *menu;
struct scene_obj_txt *txt;
struct expo *exp;
struct scene *scn;
if (!IS_ENABLED(CONFIG_CMD_CEDIT))
return -EAGAIN;
ut_assertok(run_command("cedit load hostfs - cedit.dtb", 0));
console_record_reset_enable();
/*
* ^N Move down to second menu
* ^M Open menu
* ^N Move down to second item
* ^M Select item
* \e Quit
*/
console_in_puts("\x0e\x0d\x0e\x0d\e");
ut_assertok(run_command("cedit run", 0));
exp = cur_exp;
scn = expo_lookup_scene_id(exp, exp->scene_id);
ut_assertnonnull(scn);
menu = scene_obj_find(scn, scn->highlight_id, SCENEOBJT_NONE);
ut_assertnonnull(menu);
txt = scene_obj_find(scn, menu->title_id, SCENEOBJT_NONE);
ut_assertnonnull(txt);
ut_asserteq_str("AC Power", expo_get_str(exp, txt->str_id));
ut_asserteq(ID_AC_ON, menu->cur_item_id);
return 0;
}
BOOTSTD_TEST(expo_cedit, UT_TESTF_DM | UT_TESTF_SCAN_FDT);

View file

@ -0,0 +1,84 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Sample expo screen layout
*/
/dts-v1/;
/*
enum {
ZERO,
ID_PROMPT,
ID_SCENE1,
ID_SCENE1_TITLE,
ID_CPU_SPEED,
ID_CPU_SPEED_TITLE,
ID_CPU_SPEED_1,
ID_CPU_SPEED_2,
ID_CPU_SPEED_3,
ID_POWER_LOSS,
ID_AC_OFF,
ID_AC_ON,
ID_AC_MEMORY,
ID_DYNAMIC_START,
};
*/
/ {
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";
};
};
};

View file

@ -27,19 +27,25 @@ DECLARE_GLOBAL_DATA_PTR;
/* Declare a new bdinfo test */
#define BDINFO_TEST(_name, _flags) UNIT_TEST(_name, _flags, bdinfo_test)
static void bdinfo_test_num_l(struct unit_test_state *uts,
const char *name, ulong value)
static int test_num_l(struct unit_test_state *uts, const char *name,
ulong value)
{
ut_assert_nextline("%-12s= 0x%0*lx", name, 2 * (int)sizeof(value), value);
ut_assert_nextline("%-12s= 0x%0*lx", name, 2 * (int)sizeof(value),
value);
return 0;
}
static void bdinfo_test_num_ll(struct unit_test_state *uts,
const char *name, unsigned long long value)
static int test_num_ll(struct unit_test_state *uts, const char *name,
unsigned long long value)
{
ut_assert_nextline("%-12s= 0x%.*llx", name, 2 * (int)sizeof(ulong), value);
ut_assert_nextline("%-12s= 0x%.*llx", name, 2 * (int)sizeof(ulong),
value);
return 0;
}
static void test_eth(struct unit_test_state *uts)
static int test_eth(struct unit_test_state *uts)
{
const int idx = eth_get_dev_index();
uchar enetaddr[6];
@ -59,9 +65,11 @@ static void test_eth(struct unit_test_state *uts)
else
ut_assert_nextline("%-12s= %pM", name, enetaddr);
ut_assert_nextline("IP addr = %s", env_get("ipaddr"));
return 0;
}
static void test_video_info(struct unit_test_state *uts)
static int test_video_info(struct unit_test_state *uts)
{
const struct udevice *dev;
struct uclass *uc;
@ -73,22 +81,25 @@ static void test_video_info(struct unit_test_state *uts)
struct video_priv *upriv = dev_get_uclass_priv(dev);
struct video_uc_plat *plat = dev_get_uclass_plat(dev);
bdinfo_test_num_ll(uts, "FB base", (ulong)upriv->fb);
ut_assertok(test_num_ll(uts, "FB base",
(ulong)upriv->fb));
if (upriv->copy_fb) {
bdinfo_test_num_ll(uts, "FB copy",
(ulong)upriv->copy_fb);
bdinfo_test_num_l(uts, " copy size",
plat->copy_size);
ut_assertok(test_num_ll(uts, "FB copy",
(ulong)upriv->copy_fb));
ut_assertok(test_num_l(uts, " copy size",
plat->copy_size));
}
ut_assert_nextline("%-12s= %dx%dx%d", "FB size",
upriv->xsize, upriv->ysize,
1 << upriv->bpix);
}
}
return 0;
}
static void lmb_test_dump_region(struct unit_test_state *uts,
struct lmb_region *rgn, char *name)
static int lmb_test_dump_region(struct unit_test_state *uts,
struct lmb_region *rgn, char *name)
{
unsigned long long base, size, end;
enum lmb_flags flags;
@ -105,13 +116,17 @@ static void lmb_test_dump_region(struct unit_test_state *uts,
ut_assert_nextline(" %s[%d]\t[0x%llx-0x%llx], 0x%08llx bytes flags: %x",
name, i, base, end, size, flags);
}
return 0;
}
static void lmb_test_dump_all(struct unit_test_state *uts, struct lmb *lmb)
static int lmb_test_dump_all(struct unit_test_state *uts, struct lmb *lmb)
{
ut_assert_nextline("lmb_dump_all:");
lmb_test_dump_region(uts, &lmb->memory, "memory");
lmb_test_dump_region(uts, &lmb->reserved, "reserved");
return 0;
}
static int bdinfo_test_move(struct unit_test_state *uts)
@ -123,44 +138,48 @@ static int bdinfo_test_move(struct unit_test_state *uts)
ut_assertok(console_record_reset_enable());
ut_assertok(run_commandf("bdinfo"));
bdinfo_test_num_l(uts, "boot_params", 0);
ut_assertok(test_num_l(uts, "boot_params", 0));
for (i = 0; i < CONFIG_NR_DRAM_BANKS; ++i) {
if (bd->bi_dram[i].size) {
bdinfo_test_num_l(uts, "DRAM bank", i);
bdinfo_test_num_ll(uts, "-> start", bd->bi_dram[i].start);
bdinfo_test_num_ll(uts, "-> size", bd->bi_dram[i].size);
ut_assertok(test_num_l(uts, "DRAM bank", i));
ut_assertok(test_num_ll(uts, "-> start",
bd->bi_dram[i].start));
ut_assertok(test_num_ll(uts, "-> size",
bd->bi_dram[i].size));
}
}
/* CONFIG_SYS_HAS_SRAM testing not supported */
bdinfo_test_num_l(uts, "flashstart", 0);
bdinfo_test_num_l(uts, "flashsize", 0);
bdinfo_test_num_l(uts, "flashoffset", 0);
ut_assertok(test_num_l(uts, "flashstart", 0));
ut_assertok(test_num_l(uts, "flashsize", 0));
ut_assertok(test_num_l(uts, "flashoffset", 0));
ut_assert_nextline("baudrate = %lu bps",
env_get_ulong("baudrate", 10, 1234));
bdinfo_test_num_l(uts, "relocaddr", gd->relocaddr);
bdinfo_test_num_l(uts, "reloc off", gd->reloc_off);
ut_assertok(test_num_l(uts, "relocaddr", gd->relocaddr));
ut_assertok(test_num_l(uts, "reloc off", gd->reloc_off));
ut_assert_nextline("%-12s= %u-bit", "Build", (uint)sizeof(void *) * 8);
if (IS_ENABLED(CONFIG_CMD_NET))
test_eth(uts);
ut_assertok(test_eth(uts));
/*
* Make sure environment variable "fdtcontroladdr" address
* matches mapped control DT address.
*/
ut_assert(map_to_sysmem(gd->fdt_blob) == env_get_hex("fdtcontroladdr", 0x1234));
bdinfo_test_num_l(uts, "fdt_blob", (ulong)map_to_sysmem(gd->fdt_blob));
bdinfo_test_num_l(uts, "new_fdt", (ulong)map_to_sysmem(gd->new_fdt));
bdinfo_test_num_l(uts, "fdt_size", (ulong)gd->fdt_size);
ut_assertok(test_num_l(uts, "fdt_blob",
(ulong)map_to_sysmem(gd->fdt_blob)));
ut_assertok(test_num_l(uts, "new_fdt",
(ulong)map_to_sysmem(gd->new_fdt)));
ut_assertok(test_num_l(uts, "fdt_size", (ulong)gd->fdt_size));
if (IS_ENABLED(CONFIG_VIDEO))
test_video_info(uts);
/* The gd->multi_dtb_fit may not be available, hence, #if below. */
#if CONFIG_IS_ENABLED(MULTI_DTB_FIT)
bdinfo_test_num_l(uts, "multi_dtb_fit", (ulong)gd->multi_dtb_fit);
ut_assertok(test_num_l(uts, "multi_dtb_fit", (ulong)gd->multi_dtb_fit));
#endif
if (IS_ENABLED(CONFIG_LMB) && gd->fdt_blob) {

View file

@ -1240,3 +1240,48 @@ static int dm_test_ofnode_copy_props_ot(struct unit_test_state *uts)
return 0;
}
DM_TEST(dm_test_ofnode_copy_props_ot, UT_TESTF_SCAN_FDT | UT_TESTF_OTHER_FDT);
/* check that the livetree is aligned to a structure boundary */
static int dm_test_livetree_align(struct unit_test_state *uts)
{
const int align = __alignof__(struct unit_test_state);
struct device_node *node;
u32 *sentinel;
ulong start;
start = (ulong)gd_of_root();
ut_asserteq(start, ALIGN(start, align));
node = gd_of_root();
sentinel = (void *)node - sizeof(u32);
/*
* The sentinel should be overwritten with the root node. If it isn't,
* then the root node is not at the very start of the livetree memory
* area, and free(root) will fail to free the memory used by the
* livetree.
*/
ut_assert(*sentinel != BAD_OF_ROOT);
return 0;
}
DM_TEST(dm_test_livetree_align, UT_TESTF_LIVE_TREE);
/* check that it is possible to load an arbitrary livetree */
static int dm_test_livetree_ensure(struct unit_test_state *uts)
{
oftree tree;
ofnode node;
/* read from other.dtb */
ut_assertok(test_load_other_fdt(uts));
tree = oftree_from_fdt(uts->other_fdt);
ut_assert(oftree_valid(tree));
node = oftree_path(tree, "/node/subnode");
ut_assert(ofnode_valid(node));
ut_asserteq_str("sandbox-other2",
ofnode_read_string(node, "compatible"));
return 0;
}
DM_TEST(dm_test_livetree_ensure, 0);

View file

@ -556,7 +556,7 @@ static int dm_test_video_truetype(struct unit_test_state *uts)
ut_assertok(video_get_nologo(uts, &dev));
ut_assertok(uclass_get_device(UCLASS_VIDEO_CONSOLE, 0, &con));
vidconsole_put_string(con, test_string);
ut_asserteq(12237, compress_frame_buffer(uts, dev));
ut_asserteq(12174, compress_frame_buffer(uts, dev));
return 0;
}
@ -577,7 +577,7 @@ static int dm_test_video_truetype_scroll(struct unit_test_state *uts)
ut_assertok(video_get_nologo(uts, &dev));
ut_assertok(uclass_get_device(UCLASS_VIDEO_CONSOLE, 0, &con));
vidconsole_put_string(con, test_string);
ut_asserteq(35030, compress_frame_buffer(uts, dev));
ut_asserteq(34287, compress_frame_buffer(uts, dev));
return 0;
}
@ -598,7 +598,7 @@ static int dm_test_video_truetype_bs(struct unit_test_state *uts)
ut_assertok(video_get_nologo(uts, &dev));
ut_assertok(uclass_get_device(UCLASS_VIDEO_CONSOLE, 0, &con));
vidconsole_put_string(con, test_string);
ut_asserteq(29018, compress_frame_buffer(uts, dev));
ut_asserteq(29471, compress_frame_buffer(uts, dev));
return 0;
}

View file

@ -282,6 +282,15 @@ label Fedora-Workstation-armhfp-31-1.9 (5.3.7-301.fc31.armv7hl)
copy_prepared_image(cons, mmc_dev, fname)
def setup_cedit_file(cons):
infname = os.path.join(cons.config.source_dir,
'test/boot/files/expo_layout.dts')
expo_tool = os.path.join(cons.config.source_dir, 'tools/expo.py')
outfname = 'cedit.dtb'
u_boot_utils.run_and_log(
cons, f'{expo_tool} -e {infname} -l {infname} -o {outfname}')
@pytest.mark.buildconfigspec('ut_dm')
def test_ut_dm_init(u_boot_console):
"""Initialize data for ut dm tests."""
@ -319,6 +328,7 @@ def test_ut_dm_init_bootstd(u_boot_console):
setup_bootflow_image(u_boot_console)
setup_bootmenu_image(u_boot_console)
setup_cedit_file(u_boot_console)
# Restart so that the new mmc1.img is picked up
u_boot_console.restart_uboot()

View file

@ -9,7 +9,7 @@ from collections import OrderedDict
import glob
try:
import importlib.resources
except ImportError:
except ImportError: # pragma: no cover
# for Python 3.6
import importlib_resources
import os

130
tools/expo.py Executable file
View file

@ -0,0 +1,130 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0+
"""
Expo utility - used for testing of expo features
Copyright 2023 Google LLC
Written by Simon Glass <sjg@chromium.org>
"""
import argparse
import collections
import io
import re
import subprocess
import sys
#from u_boot_pylib import cros_subprocess
from u_boot_pylib import tools
# Parse:
# SCENE1 = 7,
# or SCENE2,
RE_ENUM = re.compile(r'(\S*)(\s*= (\d))?,')
# Parse #define <name> "string"
RE_DEF = re.compile(r'#define (\S*)\s*"(.*)"')
def calc_ids(fname):
"""Figure out the value of the enums in a C file
Args:
fname (str): Filename to parse
Returns:
OrderedDict():
key (str): enum name
value (int or str):
Value of enum, if int
Value of #define, if string
"""
vals = collections.OrderedDict()
with open(fname, 'r', encoding='utf-8') as inf:
in_enum = False
cur_id = 0
for line in inf.readlines():
line = line.strip()
if line == 'enum {':
in_enum = True
continue
if in_enum and line == '};':
in_enum = False
if in_enum:
if not line or line.startswith('/*'):
continue
m_enum = RE_ENUM.match(line)
if m_enum.group(3):
cur_id = int(m_enum.group(3))
vals[m_enum.group(1)] = cur_id
cur_id += 1
else:
m_def = RE_DEF.match(line)
if m_def:
vals[m_def.group(1)] = tools.to_bytes(m_def.group(2))
return vals
def run_expo(args):
"""Run the expo program"""
ids = calc_ids(args.enum_fname)
indata = tools.read_file(args.layout)
outf = io.BytesIO()
for name, val in ids.items():
if isinstance(val, int):
outval = b'%d' % val
else:
outval = b'"%s"' % val
find_str = r'\b%s\b' % name
indata = re.sub(tools.to_bytes(find_str), outval, indata)
outf.write(indata)
data = outf.getvalue()
with open('/tmp/asc', 'wb') as outf:
outf.write(data)
proc = subprocess.run('dtc', input=data, capture_output=True, check=True)
edtb = proc.stdout
if proc.stderr:
print(proc.stderr)
return 1
tools.write_file(args.outfile, edtb)
return 0
def parse_args(argv):
"""Parse the command-line arguments
Args:
argv (list of str): List of string arguments
Returns:
tuple: (options, args) with the command-line options and arugments.
options provides access to the options (e.g. option.debug)
args is a list of string arguments
"""
parser = argparse.ArgumentParser()
parser.add_argument('-e', '--enum-fname', type=str,
help='C file containing enum declaration for expo items')
parser.add_argument('-l', '--layout', type=str,
help='Devicetree file source .dts for expo layout')
parser.add_argument('-o', '--outfile', type=str,
help='Filename to write expo layout dtb')
return parser.parse_args(argv)
def start_expo():
"""Start the expo program"""
args = parse_args(sys.argv[1:])
ret_code = run_expo(args)
sys.exit(ret_code)
if __name__ == "__main__":
start_expo()