Merge branch '2023-08-25-add-persistent-config-editor-via-expo' into next

To quote the author:
So far cedit does not support reading and writing the configuration.
This series add several features related to this:

First, it adds support for using a file on a filesystem. This is in
FDT format and provides enough information to reset the cedit back to
the saved settings.

Second, it adds support for using the U-Boot environment. Since the
environment is generally saved across reboots, this feature provides an
easy way of storing the state on most boards. The variables all have a
'c.' prefix to avoid confusion with other variables.

Finally it adds support for using CMOS RAM. This is commonly used on x86
devices to store BIOS settings. The expo schema provides information on
the register layout.

Some other minor tweaks and improvements are included along the way.
This commit is contained in:
Tom Rini 2023-08-25 17:52:59 -04:00
commit 05763b71d2
25 changed files with 1679 additions and 146 deletions

View file

@ -16,12 +16,6 @@
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>;
@ -36,6 +30,12 @@
bootstd {
compatible = "u-boot,boot-std";
filename-prefixes = "./";
cedit-theme {
font-size = <30>;
menu-inset = <3>;
menuitem-gap-y = <1>;
};
};
buttons {

View file

@ -100,6 +100,12 @@
menuitem-gap-y = <1>;
};
cedit-theme {
font-size = <30>;
menu-inset = <3>;
menuitem-gap-y = <1>;
};
/*
* This is used for the VBE OS-request tests. A FAT filesystem
* created in a partition with the VBE information appearing
@ -144,12 +150,6 @@
cedit: cedit {
};
cedit-theme {
font-size = <30>;
menu-inset = <3>;
menuitem-gap-y = <1>;
};
fuzzing-engine {
compatible = "sandbox,fuzzing-engine";
};

View file

@ -6,15 +6,50 @@
* Written by Simon Glass <sjg@chromium.org>
*/
#define LOG_CATEGORY LOGC_EXPO
#include <common.h>
#include <abuf.h>
#include <cedit.h>
#include <cli.h>
#include <dm.h>
#include <env.h>
#include <expo.h>
#include <malloc.h>
#include <menu.h>
#include <rtc.h>
#include <video.h>
#include <linux/delay.h>
#include "scene_internal.h"
enum {
CMOS_MAX_BITS = 2048,
CMOS_MAX_BYTES = CMOS_MAX_BITS / 8,
};
#define CMOS_BYTE(bit) ((bit) / 8)
#define CMOS_BIT(bit) ((bit) % 8)
/**
* struct cedit_iter_priv - private data for cedit operations
*
* @buf: Buffer to use when writing settings to the devicetree
* @node: Node to read from when reading settings from devicetree
* @verbose: true to show writing to environment variables
* @mask: Mask bits for the CMOS RAM. If a bit is set the byte containing it
* will be written
* @value: Value bits for CMOS RAM. This is the actual value written
* @dev: RTC device to write to
*/
struct cedit_iter_priv {
struct abuf *buf;
ofnode node;
bool verbose;
u8 *mask;
u8 *value;
struct udevice *dev;
};
int cedit_arange(struct expo *exp, struct video_priv *vpriv, uint scene_id)
{
struct scene_obj_txt *txt;
@ -46,18 +81,15 @@ int cedit_arange(struct expo *exp, struct video_priv *vpriv, uint scene_id)
return 0;
}
int cedit_run(struct expo *exp)
int cedit_prepare(struct expo *exp, struct video_priv **vid_privp,
struct scene **scnp)
{
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;
uint scene_id;
int ret;
cli_ch_init(cch);
/* For now we only support a video console */
ret = uclass_first_device_err(UCLASS_VIDEO, &dev);
if (ret)
@ -92,6 +124,27 @@ int cedit_run(struct expo *exp)
if (ret)
return log_msg_ret("dim", ret);
*vid_privp = vid_priv;
*scnp = scn;
return scene_id;
}
int cedit_run(struct expo *exp)
{
struct cli_ch_state s_cch, *cch = &s_cch;
struct video_priv *vid_priv;
uint scene_id;
struct scene *scn;
bool done;
int ret;
cli_ch_init(cch);
ret = cedit_prepare(exp, &vid_priv, &scn);
if (ret < 0)
return log_msg_ret("prep", ret);
scene_id = ret;
done = false;
do {
struct expo_action act;
@ -161,3 +214,507 @@ int cedit_run(struct expo *exp)
return 0;
}
static int check_space(int ret, struct abuf *buf)
{
if (ret == -FDT_ERR_NOSPACE) {
if (!abuf_realloc_inc(buf, CEDIT_SIZE_INC))
return log_msg_ret("spc", -ENOMEM);
ret = fdt_resize(abuf_data(buf), abuf_data(buf),
abuf_size(buf));
if (ret)
return log_msg_ret("res", -EFAULT);
}
return 0;
}
static int get_cur_menuitem_text(const struct scene_obj_menu *menu,
const char **strp)
{
struct scene *scn = menu->obj.scene;
const struct scene_menitem *mi;
const struct scene_obj_txt *txt;
const char *str;
mi = scene_menuitem_find(menu, menu->cur_item_id);
if (!mi)
return log_msg_ret("mi", -ENOENT);
txt = scene_obj_find(scn, mi->label_id, SCENEOBJT_TEXT);
if (!txt)
return log_msg_ret("txt", -ENOENT);
str = expo_get_str(scn->expo, txt->str_id);
if (!str)
return log_msg_ret("str", -ENOENT);
*strp = str;
return 0;
}
static int h_write_settings(struct scene_obj *obj, void *vpriv)
{
struct cedit_iter_priv *priv = vpriv;
struct abuf *buf = priv->buf;
switch (obj->type) {
case SCENEOBJT_NONE:
case SCENEOBJT_IMAGE:
case SCENEOBJT_TEXT:
break;
case SCENEOBJT_MENU: {
const struct scene_obj_menu *menu;
const char *str;
char name[80];
int ret, i;
menu = (struct scene_obj_menu *)obj;
ret = -EAGAIN;
for (i = 0; ret && i < 2; i++) {
ret = fdt_property_u32(abuf_data(buf), obj->name,
menu->cur_item_id);
if (!i) {
ret = check_space(ret, buf);
if (ret)
return log_msg_ret("res", -ENOMEM);
}
}
/* this should not happen */
if (ret)
return log_msg_ret("wrt", -EFAULT);
ret = get_cur_menuitem_text(menu, &str);
if (ret)
return log_msg_ret("mis", ret);
snprintf(name, sizeof(name), "%s-str", obj->name);
ret = -EAGAIN;
for (i = 0; ret && i < 2; i++) {
ret = fdt_property_string(abuf_data(buf), name, str);
if (!i) {
ret = check_space(ret, buf);
if (ret)
return log_msg_ret("rs2", -ENOMEM);
}
}
/* this should not happen */
if (ret)
return log_msg_ret("wr2", -EFAULT);
break;
}
}
return 0;
}
int cedit_write_settings(struct expo *exp, struct abuf *buf)
{
struct cedit_iter_priv priv;
void *fdt;
int ret;
abuf_init(buf);
if (!abuf_realloc(buf, CEDIT_SIZE_INC))
return log_msg_ret("buf", -ENOMEM);
fdt = abuf_data(buf);
ret = fdt_create(fdt, abuf_size(buf));
if (!ret)
ret = fdt_finish_reservemap(fdt);
if (!ret)
ret = fdt_begin_node(fdt, "");
if (!ret)
ret = fdt_begin_node(fdt, CEDIT_NODE_NAME);
if (ret) {
log_debug("Failed to start FDT (err=%d)\n", ret);
return log_msg_ret("sta", -EINVAL);
}
/* write out the items */
priv.buf = buf;
ret = expo_iter_scene_objs(exp, h_write_settings, &priv);
if (ret) {
log_debug("Failed to write settings (err=%d)\n", ret);
return log_msg_ret("set", ret);
}
ret = fdt_end_node(fdt);
if (!ret)
ret = fdt_end_node(fdt);
if (!ret)
ret = fdt_finish(fdt);
if (ret) {
log_debug("Failed to finish FDT (err=%d)\n", ret);
return log_msg_ret("fin", -EINVAL);
}
return 0;
}
static int h_read_settings(struct scene_obj *obj, void *vpriv)
{
struct cedit_iter_priv *priv = vpriv;
ofnode node = priv->node;
switch (obj->type) {
case SCENEOBJT_NONE:
case SCENEOBJT_IMAGE:
case SCENEOBJT_TEXT:
break;
case SCENEOBJT_MENU: {
struct scene_obj_menu *menu;
uint val;
if (ofnode_read_u32(node, obj->name, &val))
return log_msg_ret("rd", -ENOENT);
menu = (struct scene_obj_menu *)obj;
menu->cur_item_id = val;
break;
}
}
return 0;
}
int cedit_read_settings(struct expo *exp, oftree tree)
{
struct cedit_iter_priv priv;
ofnode root, node;
int ret;
root = oftree_root(tree);
if (!ofnode_valid(root))
return log_msg_ret("roo", -ENOENT);
node = ofnode_find_subnode(root, CEDIT_NODE_NAME);
if (!ofnode_valid(node))
return log_msg_ret("pat", -ENOENT);
/* read in the items */
priv.node = node;
ret = expo_iter_scene_objs(exp, h_read_settings, &priv);
if (ret) {
log_debug("Failed to read settings (err=%d)\n", ret);
return log_msg_ret("set", ret);
}
return 0;
}
static int h_write_settings_env(struct scene_obj *obj, void *vpriv)
{
const struct scene_obj_menu *menu;
struct cedit_iter_priv *priv = vpriv;
char name[80], var[60];
const char *str;
int val, ret;
if (obj->type != SCENEOBJT_MENU)
return 0;
menu = (struct scene_obj_menu *)obj;
val = menu->cur_item_id;
snprintf(var, sizeof(var), "c.%s", obj->name);
if (priv->verbose)
printf("%s=%d\n", var, val);
ret = env_set_ulong(var, val);
if (ret)
return log_msg_ret("set", ret);
ret = get_cur_menuitem_text(menu, &str);
if (ret)
return log_msg_ret("mis", ret);
snprintf(name, sizeof(name), "c.%s-str", obj->name);
if (priv->verbose)
printf("%s=%s\n", name, str);
ret = env_set(name, str);
if (ret)
return log_msg_ret("st2", ret);
return 0;
}
int cedit_write_settings_env(struct expo *exp, bool verbose)
{
struct cedit_iter_priv priv;
int ret;
/* write out the items */
priv.verbose = verbose;
ret = expo_iter_scene_objs(exp, h_write_settings_env, &priv);
if (ret) {
log_debug("Failed to write settings to env (err=%d)\n", ret);
return log_msg_ret("set", ret);
}
return 0;
}
static int h_read_settings_env(struct scene_obj *obj, void *vpriv)
{
struct cedit_iter_priv *priv = vpriv;
struct scene_obj_menu *menu;
char var[60];
int val;
if (obj->type != SCENEOBJT_MENU)
return 0;
menu = (struct scene_obj_menu *)obj;
val = menu->cur_item_id;
snprintf(var, sizeof(var), "c.%s", obj->name);
val = env_get_ulong(var, 10, 0);
if (priv->verbose)
printf("%s=%d\n", var, val);
if (!val)
return log_msg_ret("get", -ENOENT);
/*
* note that no validation is done here, to make sure the ID is valid
* and actually points to a menu item
*/
menu->cur_item_id = val;
return 0;
}
int cedit_read_settings_env(struct expo *exp, bool verbose)
{
struct cedit_iter_priv priv;
int ret;
/* write out the items */
priv.verbose = verbose;
ret = expo_iter_scene_objs(exp, h_read_settings_env, &priv);
if (ret) {
log_debug("Failed to read settings from env (err=%d)\n", ret);
return log_msg_ret("set", ret);
}
return 0;
}
/**
* get_cur_menuitem_seq() - Get the sequence number of a menu's current item
*
* Enumerates the items of a menu (0, 1, 2) and returns the sequence number of
* the currently selected item. If the first item is selected, this returns 0;
* if the second, 1; etc.
*
* @menu: Menu to check
* Return: Sequence number on success, else -ve error value
*/
static int get_cur_menuitem_seq(const struct scene_obj_menu *menu)
{
const struct scene_menitem *mi;
int seq, found;
seq = 0;
found = -1;
list_for_each_entry(mi, &menu->item_head, sibling) {
if (mi->id == menu->cur_item_id) {
found = seq;
break;
}
seq++;
}
if (found == -1)
return log_msg_ret("nf", -ENOENT);
return found;
}
static int h_write_settings_cmos(struct scene_obj *obj, void *vpriv)
{
const struct scene_obj_menu *menu;
struct cedit_iter_priv *priv = vpriv;
int val, ret;
uint i, seq;
if (obj->type != SCENEOBJT_MENU)
return 0;
menu = (struct scene_obj_menu *)obj;
val = menu->cur_item_id;
ret = get_cur_menuitem_seq(menu);
if (ret < 0)
return log_msg_ret("cur", ret);
seq = ret;
log_debug("%s: seq=%d\n", menu->obj.name, seq);
/* figure out where to place this item */
if (!obj->bit_length)
return log_msg_ret("len", -EINVAL);
if (obj->start_bit + obj->bit_length > CMOS_MAX_BITS)
return log_msg_ret("bit", -E2BIG);
for (i = 0; i < obj->bit_length; i++, seq >>= 1) {
uint bitnum = obj->start_bit + i;
priv->mask[CMOS_BYTE(bitnum)] |= 1 << CMOS_BIT(bitnum);
if (seq & 1)
priv->value[CMOS_BYTE(bitnum)] |= BIT(CMOS_BIT(bitnum));
log_debug("bit %x %x %x\n", bitnum,
priv->mask[CMOS_BYTE(bitnum)],
priv->value[CMOS_BYTE(bitnum)]);
}
return 0;
}
int cedit_write_settings_cmos(struct expo *exp, struct udevice *dev,
bool verbose)
{
struct cedit_iter_priv priv;
int ret, i, count, first, last;
/* write out the items */
priv.mask = calloc(1, CMOS_MAX_BYTES);
if (!priv.mask)
return log_msg_ret("mas", -ENOMEM);
priv.value = calloc(1, CMOS_MAX_BYTES);
if (!priv.value) {
free(priv.mask);
return log_msg_ret("val", -ENOMEM);
}
ret = expo_iter_scene_objs(exp, h_write_settings_cmos, &priv);
if (ret) {
log_debug("Failed to write CMOS (err=%d)\n", ret);
ret = log_msg_ret("set", ret);
goto done;
}
/* write the data to the RTC */
first = CMOS_MAX_BYTES;
last = -1;
for (i = 0, count = 0; i < CMOS_MAX_BYTES; i++) {
if (priv.mask[i]) {
log_debug("Write byte %x: %x\n", i, priv.value[i]);
ret = rtc_write8(dev, i, priv.value[i]);
if (ret) {
ret = log_msg_ret("wri", ret);
goto done;
}
count++;
first = min(first, i);
last = max(last, i);
}
}
if (verbose) {
printf("Write %d bytes from offset %x to %x\n", count, first,
last);
}
done:
free(priv.mask);
free(priv.value);
return ret;
}
static int h_read_settings_cmos(struct scene_obj *obj, void *vpriv)
{
struct cedit_iter_priv *priv = vpriv;
const struct scene_menitem *mi;
struct scene_obj_menu *menu;
int val, ret;
uint i;
if (obj->type != SCENEOBJT_MENU)
return 0;
menu = (struct scene_obj_menu *)obj;
/* figure out where to place this item */
if (!obj->bit_length)
return log_msg_ret("len", -EINVAL);
if (obj->start_bit + obj->bit_length > CMOS_MAX_BITS)
return log_msg_ret("bit", -E2BIG);
val = 0;
for (i = 0; i < obj->bit_length; i++) {
uint bitnum = obj->start_bit + i;
uint offset = CMOS_BYTE(bitnum);
/* read the byte if not already read */
if (!priv->mask[offset]) {
ret = rtc_read8(priv->dev, offset);
if (ret < 0)
return log_msg_ret("rea", ret);
priv->value[offset] = ret;
/* mark it as read */
priv->mask[offset] = 0xff;
}
if (priv->value[offset] & BIT(CMOS_BIT(bitnum)))
val |= BIT(i);
log_debug("bit %x %x\n", bitnum, val);
}
/* update the current item */
mi = scene_menuitem_find_seq(menu, val);
if (!mi)
return log_msg_ret("seq", -ENOENT);
menu->cur_item_id = mi->id;
log_debug("Update menu %d cur_item_id %d\n", menu->obj.id, mi->id);
return 0;
}
int cedit_read_settings_cmos(struct expo *exp, struct udevice *dev,
bool verbose)
{
struct cedit_iter_priv priv;
int ret, i, count, first, last;
/* read in the items */
priv.mask = calloc(1, CMOS_MAX_BYTES);
if (!priv.mask)
return log_msg_ret("mas", -ENOMEM);
priv.value = calloc(1, CMOS_MAX_BYTES);
if (!priv.value) {
free(priv.mask);
return log_msg_ret("val", -ENOMEM);
}
priv.dev = dev;
ret = expo_iter_scene_objs(exp, h_read_settings_cmos, &priv);
if (ret) {
log_debug("Failed to read CMOS (err=%d)\n", ret);
ret = log_msg_ret("set", ret);
goto done;
}
/* read the data to the RTC */
first = CMOS_MAX_BYTES;
last = -1;
for (i = 0, count = 0; i < CMOS_MAX_BYTES; i++) {
if (priv.mask[i]) {
log_debug("Read byte %x: %x\n", i, priv.value[i]);
count++;
first = min(first, i);
last = max(last, i);
}
}
if (verbose) {
printf("Read %d bytes from offset %x to %x\n", count, first,
last);
}
done:
free(priv.mask);
free(priv.value);
return ret;
}

View file

@ -266,3 +266,18 @@ int expo_apply_theme(struct expo *exp, ofnode node)
return 0;
}
int expo_iter_scene_objs(struct expo *exp, expo_scene_obj_iterator iter,
void *priv)
{
struct scene *scn;
int ret;
list_for_each_entry(scn, &exp->scene_head, sibling) {
ret = scene_iter_objs(scn, iter, priv);
if (ret)
return log_msg_ret("wr", ret);
}
return 0;
}

View file

@ -214,22 +214,21 @@ static void list_strings(struct build_info *info)
* @info: Build information
* @node: Node containing the menu description
* @scn: Scene to add the menu to
* @id: ID for the menu
* @objp: Returns the object pointer
* 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)
static int menu_build(struct build_info *info, ofnode node, struct scene *scn,
uint id, struct scene_obj **objp)
{
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)
@ -275,12 +274,13 @@ static int menu_build(struct build_info *info, ofnode node, struct scene *scn)
if (ret < 0)
return log_msg_ret("mi", ret);
}
*objp = &menu->obj;
return 0;
}
/**
* menu_build() - Build an expo object and add it to a scene
* obj_build() - Build an expo object and add it to a scene
*
* See doc/developer/expo.rst for a description of the format
*
@ -292,8 +292,9 @@ static int menu_build(struct build_info *info, ofnode node, struct scene *scn)
*/
static int obj_build(struct build_info *info, ofnode node, struct scene *scn)
{
struct scene_obj *obj;
const char *type;
u32 id;
u32 id, val;
int ret;
log_debug("- object %s\n", ofnode_get_name(node));
@ -306,12 +307,17 @@ static int obj_build(struct build_info *info, ofnode node, struct scene *scn)
return log_msg_ret("typ", -EINVAL);
if (!strcmp("menu", type))
ret = menu_build(info, node, scn);
ret = menu_build(info, node, scn, id, &obj);
else
ret = -EINVAL;
if (ret)
return log_msg_ret("bld", ret);
if (!ofnode_read_u32(node, "start-bit", &val))
obj->start_bit = val;
if (!ofnode_read_u32(node, "bit-length", &val))
obj->bit_length = val;
return 0;
}

View file

@ -79,7 +79,7 @@ int scene_obj_count(struct scene *scn)
return count;
}
void *scene_obj_find(struct scene *scn, uint id, enum scene_obj_t type)
void *scene_obj_find(const struct scene *scn, uint id, enum scene_obj_t type)
{
struct scene_obj *obj;
@ -681,3 +681,19 @@ int scene_set_open(struct scene *scn, uint id, bool open)
return 0;
}
int scene_iter_objs(struct scene *scn, expo_scene_obj_iterator iter,
void *priv)
{
struct scene_obj *obj;
list_for_each_entry(obj, &scn->obj_head, sibling) {
int ret;
ret = iter(obj, priv);
if (ret)
return log_msg_ret("itr", ret);
}
return 0;
}

View file

@ -9,6 +9,8 @@
#ifndef __SCENE_INTERNAL_H
#define __SCENE_INTERNAL_H
typedef int (*expo_scene_obj_iterator)(struct scene_obj *obj, void *priv);
/**
* expo_lookup_scene_id() - Look up a scene ID
*
@ -38,7 +40,7 @@ uint resolve_id(struct expo *exp, uint id);
* @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);
void *scene_obj_find(const struct scene *scn, uint id, enum scene_obj_t type);
/**
* scene_obj_find_by_name() - Find an object in a scene by name
@ -198,4 +200,50 @@ int scene_menu_render_deps(struct scene *scn, struct scene_obj_menu *menu);
*/
int scene_menu_calc_dims(struct scene_obj_menu *menu);
/**
* scene_iter_objs() - Iterate through all scene objects
*
* @scn: Scene to process
* @iter: Iterator to call on each object
* @priv: Private data to pass to the iterator, in addition to the object
* Return: 0 if OK, -ve on error
*/
int scene_iter_objs(struct scene *scn, expo_scene_obj_iterator iter,
void *priv);
/**
* expo_iter_scene_objects() - Iterate through all scene objects
*
* @exp: Expo to process
* @iter: Iterator to call on each object
* @priv: Private data to pass to the iterator, in addition to the object
* Return: 0 if OK, -ve on error
*/
int expo_iter_scene_objs(struct expo *exp, expo_scene_obj_iterator iter,
void *priv);
/**
* scene_menuitem_find() - Find the menu item for an ID
*
* Looks up the menu to find the item with the given ID
*
* @menu: Menu to check
* @id: ID to look for
* Return: Menu item, or NULL if not found
*/
struct scene_menitem *scene_menuitem_find(const struct scene_obj_menu *menu,
int id);
/**
* scene_menuitem_find_seq() - Find the menu item at a sequential position
*
* This numbers the items from 0 and returns the seq'th one
*
* @menu: Menu to check
* @seq: Sequence number to look for
* Return: menu item if found, else NULL
*/
struct scene_menitem *scene_menuitem_find_seq(const struct scene_obj_menu *menu,
uint seq);
#endif /* __SCENE_INTERNAL_H */

View file

@ -33,8 +33,8 @@ 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 *scene_menuitem_find(const struct scene_obj_menu *menu,
int id)
{
struct scene_menitem *item;
@ -46,6 +46,22 @@ static struct scene_menitem *scene_menuitem_find(struct scene_obj_menu *menu,
return NULL;
}
struct scene_menitem *scene_menuitem_find_seq(const struct scene_obj_menu *menu,
uint seq)
{
struct scene_menitem *item;
uint i;
i = 0;
list_for_each_entry(item, &menu->item_head, sibling) {
if (i == seq)
return item;
i++;
}
return NULL;
}
/**
* update_pointers() - Update the pointer object and handle highlights
*

View file

@ -7,14 +7,29 @@
*/
#include <common.h>
#include <abuf.h>
#include <cedit.h>
#include <command.h>
#include <dm.h>
#include <expo.h>
#include <fs.h>
#include <malloc.h>
#include <mapmem.h>
#include <dm/ofnode.h>
#include <linux/sizes.h>
struct expo *cur_exp;
static int check_cur_expo(void)
{
if (!cur_exp) {
printf("No expo loaded\n");
return -ENOENT;
}
return 0;
}
static int do_cedit_load(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
@ -53,18 +68,192 @@ static int do_cedit_load(struct cmd_tbl *cmdtp, int flag, int argc,
return 0;
}
static int do_cedit_write_fdt(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
const char *fname;
struct abuf buf;
loff_t bytes;
int ret;
if (argc < 4)
return CMD_RET_USAGE;
fname = argv[3];
if (check_cur_expo())
return CMD_RET_FAILURE;
ret = cedit_write_settings(cur_exp, &buf);
if (ret) {
printf("Failed to write settings: %dE\n", ret);
return CMD_RET_FAILURE;
}
if (fs_set_blk_dev(argv[1], argv[2], FS_TYPE_ANY))
return CMD_RET_FAILURE;
ret = fs_write(fname, map_to_sysmem(abuf_data(&buf)), 0,
abuf_size(&buf), &bytes);
if (ret)
return CMD_RET_FAILURE;
return 0;
}
static int do_cedit_read_fdt(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
const char *fname;
void *buf;
oftree tree;
ulong size;
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)) {
free(buf);
printf("Cannot create oftree\n");
return CMD_RET_FAILURE;
}
ret = cedit_read_settings(cur_exp, tree);
oftree_dispose(tree);
free(buf);
if (ret) {
printf("Failed to read settings: %dE\n", ret);
return CMD_RET_FAILURE;
}
return 0;
}
static int do_cedit_write_env(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
bool verbose;
int ret;
if (check_cur_expo())
return CMD_RET_FAILURE;
verbose = argc > 1 && !strcmp(argv[1], "-v");
ret = cedit_write_settings_env(cur_exp, verbose);
if (ret) {
printf("Failed to write settings to environment: %dE\n", ret);
return CMD_RET_FAILURE;
}
return 0;
}
static int do_cedit_read_env(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
bool verbose;
int ret;
if (check_cur_expo())
return CMD_RET_FAILURE;
verbose = argc > 1 && !strcmp(argv[1], "-v");
ret = cedit_read_settings_env(cur_exp, verbose);
if (ret) {
printf("Failed to read settings from environment: %dE\n", ret);
return CMD_RET_FAILURE;
}
return 0;
}
static int do_cedit_write_cmos(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
struct udevice *dev;
bool verbose = false;
int ret;
if (check_cur_expo())
return CMD_RET_FAILURE;
if (argc > 1 && !strcmp(argv[1], "-v")) {
verbose = true;
argc--;
argv++;
}
if (argc > 1)
ret = uclass_get_device_by_name(UCLASS_RTC, argv[1], &dev);
else
ret = uclass_first_device_err(UCLASS_RTC, &dev);
if (ret) {
printf("Failed to get RTC device: %dE\n", ret);
return CMD_RET_FAILURE;
}
if (cedit_write_settings_cmos(cur_exp, dev, verbose)) {
printf("Failed to write settings to CMOS\n");
return CMD_RET_FAILURE;
}
return 0;
}
static int do_cedit_read_cmos(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
struct udevice *dev;
bool verbose = false;
int ret;
if (check_cur_expo())
return CMD_RET_FAILURE;
if (argc > 1 && !strcmp(argv[1], "-v")) {
verbose = true;
argc--;
argv++;
}
if (argc > 1)
ret = uclass_get_device_by_name(UCLASS_RTC, argv[1], &dev);
else
ret = uclass_first_device_err(UCLASS_RTC, &dev);
if (ret) {
printf("Failed to get RTC device: %dE\n", ret);
return CMD_RET_FAILURE;
}
ret = cedit_read_settings_cmos(cur_exp, dev, verbose);
if (ret) {
printf("Failed to read settings from CMOS: %dE\n", ret);
return CMD_RET_FAILURE;
}
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");
if (check_cur_expo())
return CMD_RET_FAILURE;
}
node = ofnode_path("/cedit-theme");
node = ofnode_path("/bootstd/cedit-theme");
if (ofnode_valid(node)) {
ret = expo_apply_theme(cur_exp, node);
if (ret)
@ -84,10 +273,22 @@ static int do_cedit_run(struct cmd_tbl *cmdtp, int flag, int argc,
#ifdef CONFIG_SYS_LONGHELP
static char cedit_help_text[] =
"load <interface> <dev[:part]> <filename> - load config editor\n"
"cedit read_fdt <i/f> <dev[:part]> <filename> - read settings\n"
"cedit write_fdt <i/f> <dev[:part]> <filename> - write settings\n"
"cedit read_env [-v] - read settings from env vars\n"
"cedit write_env [-v] - write settings to env vars\n"
"cedit read_cmos [-v] [dev] - read settings from CMOS RAM\n"
"cedit write_cmos [-v] [dev] - write settings to CMOS RAM\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(read_fdt, 5, 1, do_cedit_read_fdt),
U_BOOT_SUBCMD_MKENT(write_fdt, 5, 1, do_cedit_write_fdt),
U_BOOT_SUBCMD_MKENT(read_env, 2, 1, do_cedit_read_env),
U_BOOT_SUBCMD_MKENT(write_env, 2, 1, do_cedit_write_env),
U_BOOT_SUBCMD_MKENT(read_cmos, 2, 1, do_cedit_read_cmos),
U_BOOT_SUBCMD_MKENT(write_cmos, 2, 1, do_cedit_write_cmos),
U_BOOT_SUBCMD_MKENT(run, 1, 1, do_cedit_run),
);

169
doc/develop/cedit.rst Normal file
View file

@ -0,0 +1,169 @@
.. SPDX-License-Identifier: GPL-2.0+
Configuration Editor
====================
Introduction
------------
U-Boot provides a configuration editor which allows settings to be changed in
a GUI or text environment.
This feature is still in development and has a number of limitations. For
example, cedit only supports menu items (there is no numeric or text entry),
provides no support for colour text and does not support scrolling. Still it is
possible to use it for simple applications.
Overview
--------
The configuration editor makes use of :doc:`expo` to build a description of the
configuration screens and allow user to interact with it.
To create a single-scene cedit for your application:
#. Design the scene, i.e. the objects that need to be present and what their
possible values are
#. Enter this in .dts format
#. Create a header file containing the IDs
#. Run the 'expo.py' tool to generate a .dtb file containing the layout, which
can be used by U-Boot
#. Use the :doc:`../usage/cmd/cedit` to create the cedit, read the settings,
present the cedit to the user and save the settings afterwards.
Each of these is described in a separate section. See :ref:`expo_example` for
an example file.
Design a scene
--------------
Using a piece of paper or a drawing tool, lay out the objects you want in your
scene. Typically you will use the default layout engine, which simply puts items
one after the other from top to bottom. So use a single column and show the
prompt and value for each object.
For menu items, show one of the values, but keep in mind what else you need.
Create an expo-format file
--------------------------
The description is in the form of a devicetree file, as documented at
:ref:`expo_format`. Since everything in an expo has an ID number (an integer
greater than 1) the description is written terms of these IDs. They each have
an enum value. which is typically taken care of by the `expo.py` tool.
The expo should have a `scenes` node with a named scene as a subnode. Within the
scene, add properties for the scene, then a subnode for each object in the
scene.
All object nodes require an `id` value and a `type` property. Other properties
depend on the type. For example, a menu has a `title` and an `item-label` list
proving the text for the menu items, as well as an `item-id` list providing the
ID of each menu item, so it can be selected.
Text properties may have two variants. For example `title` specifies the title
of a menu, but you can instead use `title-id` to specify the string ID to use as
the title. String are defined in a separate area, common to the whole expo,
which contains a subnode for each string. Within that subnode are the ID and the
`value` (i.e. the text). For now only English is supported, but in future it may
be possible to append a language identifier to provide other values (e.g.
'value-es' for Spanish).
Create an ID header-file
------------------------
Expo needs to know the integer value to use for every ID referenced in your
expo-format file. For example, if you have defined a `cpu-speed` node with an
id of `ID_CPU_SPEED`, then Expo needs to know the value of `ID_CPU_SPEED`.
When you write C code to use the expo, you may need to know the IDs. For
example, to find which value the user selected in `cpu-speed` menu, you must
use the `ID_CPU_SPEED` ID. The ID is the only way to refer to anything in Expo.
Since we need a shared set of IDs, it is best to have a header file containing
them. Expo supports doing this with an enum, where every ID is listed in the
enum::
enum {
ZERO,
ID_PROMPT,
ID_SCENE1,
ID_SCENE1_TITLE,
...
};
The C compiler can parse this directly. The `expo.py` tool parses it for expo.
Create a header file containing every ID mentioned in your expo. Try to group
related things together.
Build the expo layout
---------------------
Use the `expo.py` tool to build a .dtb for your expo::
./tools/expo.py -e expo_ids.h -l expo_layout.dts -o expo.dtb
This uses the enum in the provided header file to get the ID numbers, grabs
the `.dts` file, inserts the ID numbers and then uses the devicetree compiler to
build a `.dtb` file.
If you get an error::
Devicetree compiler error:
Error: <stdin>:9.19-20 syntax error
FATAL ERROR: Unable to parse input tree
that means that something is wrong with your syntax, or perhaps you have an ID
in the `.dts` file that is not mentioned in your enum. Check both files and try
again.
Use the command interface
-------------------------
See the :doc:`../usage/cmd/cedit` command for information on available commands.
Typically you will use `cedit load` to load the `.dtb` file and `cedit run` to
let the user interact with it.
Multiple scenes
---------------
Expo supports multiple scenes but has no pre-determined way of moving between
them. You could use selection of a menu item as a signal to change the scene,
but this is not currently implemented in the cedit code (see `cedit_run()`).
Themes
------
The configuration editor uses simple expo themes. The theme is read from
`/bootstd/cedit-theme` in the devicetree.
Reading and writing settings
----------------------------
Cedit provides several options for persistent settings:
- Writing an FDT file to a filesystem
- Writing to U-Boot's environment variables, which are then typically stored in
a persistent manner
- Writing to CMOS RAM registers (common on x86 machines)
For now, reading and writing settings is not automatic. See the
:doc:`../usage/cmd/cedit` for how to do this on the command line or in a
script.

View file

@ -317,6 +317,18 @@ id
Specifies the ID of the object. This is used when referring to the object.
Where CMOS RAM is used for reading and writing settings, the following
additional properties are required:
start-bit
Specifies the first bit in the CMOS RAM to use for this setting. For a RAM
with 0x100 bytes, there are 0x800 bit locations. For example, register 0x80
holds bits 0x400 to 0x407.
bit-length
Specifies the number of CMOS RAM bits to use for this setting. The bits
extend from `start-bit` to `start-bit + bit-length - 1`. Note that the bits
must be contiguous.
Menu nodes have the following additional properties:
@ -358,6 +370,9 @@ 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_example:
Expo format example
~~~~~~~~~~~~~~~~~~~
@ -367,22 +382,27 @@ strings are provided inline in the nodes where they are used.
::
#define ID_PROMPT 1
#define ID_SCENE1 2
#define ID_SCENE1_TITLE 3
/* this comment is parsed by the expo.py tool to insert the values below
#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
enum {
ZERO,
ID_PROMPT,
ID_SCENE1,
ID_SCENE1_TITLE,
#define ID_POWER_LOSS 9
#define ID_AC_OFF 10
#define ID_AC_ON 11
#define ID_AC_MEMORY 12
ID_CPU_SPEED,
ID_CPU_SPEED_TITLE,
ID_CPU_SPEED_1,
ID_CPU_SPEED_2,
ID_CPU_SPEED_3,
#define ID_DYNAMIC_START 13
ID_POWER_LOSS,
ID_AC_OFF,
ID_AC_ON,
ID_AC_MEMORY,
ID_DYNAMIC_START,
*/
&cedit {
dynamic-start = <ID_DYNAMIC_START>;
@ -465,7 +485,7 @@ Some ideas for future work:
- 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
- Update expo.py tool to check for overlapping names and CMOS locations
.. Simon Glass <sjg@chromium.org>
.. 7-Oct-22

View file

@ -38,6 +38,7 @@ Implementation
driver-model/index
environment
expo
cedit
event
global_data
logging

View file

@ -10,6 +10,11 @@ Synopis
cedit load <interface> <dev[:part]> <filename>
cedit run
cedit write_fdt <dev[:part]> <filename>
cedit read_fdt <dev[:part]> <filename>
cedit write_env [-v]
cedit read_env [-v]
cedit write_cmos [-v] [dev]
Description
-----------
@ -22,6 +27,69 @@ It makes use of the expo subsystem.
The description is in the form of a devicetree file, as documented at
:ref:`expo_format`.
See :doc:`../../develop/cedit` for information about the configuration editor.
cedit load
~~~~~~~~~~
Loads a configuration-editor description from a file. It creates a new cedit
structure ready for use. Initially no settings are read, so default values are
used for each object.
cedit run
~~~~~~~~~
Runs the default configuration-editor event loop. This is very simple, just
accepting character input and moving through the objects under user control.
The implementation is at `cedit_run()`.
cedit write_fdt
~~~~~~~~~~~~~~~
Writes the current user settings to a devicetree file. For each menu item the
selected ID and its text string are written.
cedit read_fdt
~~~~~~~~~~~~~~
Reads the user settings from a devicetree file and updates the cedit with those
settings.
cedit read_env
~~~~~~~~~~~~~~
Reads the settings from the environment variables. For each menu item `<name>`,
cedit looks for a variable called `c.<name>` with the ID of the selected menu
item.
The `-v` flag enables verbose mode, where each variable is printed after it is
read.
cedit write_env
~~~~~~~~~~~~~~~
Writes the settings to environment variables. For each menu item the selected
ID and its text string are written, similar to:
setenv c.<name> <selected_id>
setenv c.<name>-str <selected_id's text string>
The `-v` flag enables verbose mode, where each variable is printed before it is
set.
cedit write_cmos
~~~~~~~~~~~~~~~~
Writes the settings to locations in the CMOS RAM. The locations used are
specified by the schema. See `expo_format_`.
The `-v` flag enables verbose mode, which shows which CMOS locations were
updated.
Normally the first RTC device is used to hold the data. You can specify a
different device by name using the `dev` parameter.
Example
-------
@ -29,3 +97,52 @@ Example
=> cedit load hostfs - fred.dtb
=> cedit run
=> cedit write_fdt hostfs - settings.dtb
That results in::
/ {
cedit-values {
cpu-speed = <0x00000006>;
cpu-speed-str = "2 GHz";
power-loss = <0x0000000a>;
power-loss-str = "Always Off";
};
}
=> cedit read_fdt hostfs - settings.dtb
This shows settings being stored in the environment::
=> cedit write_env -v
c.cpu-speed=7
c.cpu-speed-str=2.5 GHz
c.power-loss=12
c.power-loss-str=Memory
=> print
...
c.cpu-speed=6
c.cpu-speed-str=2 GHz
c.power-loss=10
c.power-loss-str=Always Off
...
=> cedit read_env -v
c.cpu-speed=7
c.power-loss=12
This shows writing to CMOS RAM. Notice that the bytes at 80 and 84 change::
=> rtc read 80 8
00000080: 00 00 00 00 00 2f 2a 08 ...../*.
=> cedit write_cmos -v
Write 2 bytes from offset 80 to 84
=> rtc read 80 8
00000080: 01 00 00 00 08 2f 2a 08 ...../*.
=> cedit read_cmos -v
Read 2 bytes from offset 80 to 84
Here is an example with the device specified::
=> cedit write_cmos rtc@43
=>

View file

@ -90,6 +90,15 @@ void abuf_map_sysmem(struct abuf *abuf, ulong addr, size_t size);
*/
bool abuf_realloc(struct abuf *abuf, size_t new_size);
/**
* abuf_realloc_inc() - Increment abuf size by a given amount
*
* @abuf: abuf to adjust
* @inc: Size incrmement to use (the buffer size will be increased by this much)
* Return: true if OK, false if out of memory
*/
bool abuf_realloc_inc(struct abuf *abuf, size_t inc);
/**
* abuf_uninit_move() - Return the allocated contents and uninit the abuf
*

125
include/cedit.h Normal file
View file

@ -0,0 +1,125 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Copyright 2023 Google LLC
* Written by Simon Glass <sjg@chromium.org>
*/
#ifndef __CEDIT_H
#define __CEDIT_H
#include <dm/ofnode_decl.h>
struct abuf;
struct expo;
struct scene;
struct video_priv;
enum {
/* size increment for writing FDT */
CEDIT_SIZE_INC = 1024,
};
/* Name of the cedit node in the devicetree */
#define CEDIT_NODE_NAME "cedit-values"
extern struct expo *cur_exp;
/**
* 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);
/**
* cedit_prepare() - Prepare to run a cedit
*
* Set up the video device, select the first scene and highlight the first item.
* This ensures that all menus have a selected item.
*
* @exp: Expo to use
* @vid_privp: Set to private data for the video device
* @scnp: Set to the first scene
* Return: scene ID of first scene if OK, -ve on error
*/
int cedit_prepare(struct expo *exp, struct video_priv **vid_privp,
struct scene **scnp);
/**
* cedit_write_settings() - Write settings in FDT format
*
* Sets up an FDT with the settings
*
* @exp: Expo to write settings from
* @buf: Returns abuf containing the settings FDT (inited by this function)
* Return: 0 if OK, -ve on error
*/
int cedit_write_settings(struct expo *exp, struct abuf *buf);
/**
* cedit_read_settings() - Read settings in FDT format
*
* Read an FDT with the settings
*
* @exp: Expo to read settings into
* @tree: Tree to read from
* Return: 0 if OK, -ve on error
*/
int cedit_read_settings(struct expo *exp, oftree tree);
/**
* cedit_write_settings_env() - Write settings to envrionment variables
*
* @exp: Expo to write settings from
* @verbose: true to print each var as it is set
* Return: 0 if OK, -ve on error
*/
int cedit_write_settings_env(struct expo *exp, bool verbose);
/*
* cedit_read_settings_env() - Read settings from the environment
*
* @exp: Expo to read settings into
* @verbose: true to print each var before it is read
*/
int cedit_read_settings_env(struct expo *exp, bool verbose);
/**
* cedit_write_settings_cmos() - Write settings to CMOS RAM
*
* Write settings to the defined places in CMOS RAM
*
* @exp: Expo to write settings from
* @dev: UCLASS_RTC device containing space for this information
* Returns 0 if OK, -ve on error
* @verbose: true to print a summary at the end
*/
int cedit_write_settings_cmos(struct expo *exp, struct udevice *dev,
bool verbose);
/**
* cedit_read_settings_cmos() - Read settings from CMOS RAM
*
* Read settings from the defined places in CMO RAM
*
* @exp: Expo to read settings into
* @dev: RTC device to read settings from
* @verbose: true to print a summary at the end
*/
int cedit_read_settings_cmos(struct expo *exp, struct udevice *dev,
bool verbose);
#endif /* __CEDIT_H */

View file

@ -4,14 +4,13 @@
* Written by Simon Glass <sjg@chromium.org>
*/
#ifndef __SCENE_H
#define __SCENE_H
#ifndef __EXPO_H
#define __EXPO_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
@ -188,6 +187,8 @@ enum scene_obj_flags_t {
* @type: Type of this object
* @dim: Dimensions for this object
* @flags: Flags for this object
* @bit_length: Number of bits used for this object in CMOS RAM
* @start_bit: Start bit to use for this object in CMOS RAM
* @sibling: Node to link this object to its siblings
*/
struct scene_obj {
@ -196,7 +197,9 @@ struct scene_obj {
uint id;
enum scene_obj_t type;
struct scene_dim dim;
int flags;
u8 flags;
u8 bit_length;
u16 start_bit;
struct list_head sibling;
};
@ -676,24 +679,4 @@ int expo_apply_theme(struct expo *exp, ofnode node);
*/
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 */
#endif /*__EXPO_H */

View file

@ -82,6 +82,11 @@ bool abuf_realloc(struct abuf *abuf, size_t new_size)
}
}
bool abuf_realloc_inc(struct abuf *abuf, size_t inc)
{
return abuf_realloc(abuf, abuf->size + inc);
}
void *abuf_uninit_move(struct abuf *abuf, size_t *sizep)
{
void *ptr;

View file

@ -6,6 +6,7 @@ obj-$(CONFIG_BOOTSTD) += bootdev.o bootstd_common.o bootflow.o bootmeth.o
obj-$(CONFIG_FIT) += image.o
obj-$(CONFIG_EXPO) += expo.o
obj-$(CONFIG_CEDIT) += cedit.o
ifdef CONFIG_OF_LIVE
obj-$(CONFIG_BOOTMETH_VBE_SIMPLE) += vbe_simple.o

198
test/boot/cedit.c Normal file
View file

@ -0,0 +1,198 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2023 Google LLC
* Written by Simon Glass <sjg@chromium.org>
*/
#include <common.h>
#include <cedit.h>
#include <env.h>
#include <expo.h>
#include <mapmem.h>
#include <dm/ofnode.h>
#include <test/ut.h>
#include "bootstd_common.h"
#include <test/cedit-test.h>
#include "../../boot/scene_internal.h"
/* Check the cedit command */
static int cedit_base(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;
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(cedit_base, 0);
/* Check the cedit write_fdt and read_fdt commands */
static int cedit_fdt(struct unit_test_state *uts)
{
struct video_priv *vid_priv;
extern struct expo *cur_exp;
struct scene_obj_menu *menu;
ulong addr = 0x1000;
struct ofprop prop;
struct scene *scn;
oftree tree;
ofnode node;
void *fdt;
int i;
console_record_reset_enable();
ut_assertok(run_command("cedit load hostfs - cedit.dtb", 0));
ut_asserteq(ID_SCENE1, cedit_prepare(cur_exp, &vid_priv, &scn));
/* get a menu to fiddle with */
menu = scene_obj_find(scn, ID_CPU_SPEED, SCENEOBJT_MENU);
ut_assertnonnull(menu);
menu->cur_item_id = ID_CPU_SPEED_2;
ut_assertok(run_command("cedit write_fdt hostfs - settings.dtb", 0));
ut_assertok(run_commandf("load hostfs - %lx settings.dtb", addr));
ut_assert_nextlinen("1024 bytes read");
fdt = map_sysmem(addr, 1024);
tree = oftree_from_fdt(fdt);
node = ofnode_find_subnode(oftree_root(tree), CEDIT_NODE_NAME);
ut_asserteq(ID_CPU_SPEED_2,
ofnode_read_u32_default(node, "cpu-speed", 0));
ut_asserteq_str("2.5 GHz", ofnode_read_string(node, "cpu-speed-str"));
ut_assert(ofnode_valid(node));
/* There should only be 4 properties */
for (i = 0, ofnode_first_property(node, &prop); ofprop_valid(&prop);
i++, ofnode_next_property(&prop))
;
ut_asserteq(4, i);
ut_assert_console_end();
/* reset the expo */
menu->cur_item_id = ID_CPU_SPEED_1;
/* load in the settings and make sure they update */
ut_assertok(run_command("cedit read_fdt hostfs - settings.dtb", 0));
ut_asserteq(ID_CPU_SPEED_2, menu->cur_item_id);
ut_assertnonnull(menu);
ut_assert_console_end();
return 0;
}
BOOTSTD_TEST(cedit_fdt, 0);
/* Check the cedit write_env and read_env commands */
static int cedit_env(struct unit_test_state *uts)
{
struct video_priv *vid_priv;
extern struct expo *cur_exp;
struct scene_obj_menu *menu;
struct scene *scn;
console_record_reset_enable();
ut_assertok(run_command("cedit load hostfs - cedit.dtb", 0));
ut_asserteq(ID_SCENE1, cedit_prepare(cur_exp, &vid_priv, &scn));
/* get a menu to fiddle with */
menu = scene_obj_find(scn, ID_CPU_SPEED, SCENEOBJT_MENU);
ut_assertnonnull(menu);
menu->cur_item_id = ID_CPU_SPEED_2;
ut_assertok(run_command("cedit write_env -v", 0));
ut_assert_nextlinen("c.cpu-speed=7");
ut_assert_nextlinen("c.cpu-speed-str=2.5 GHz");
ut_assert_nextlinen("c.power-loss=10");
ut_assert_nextlinen("c.power-loss-str=Always Off");
ut_assert_console_end();
ut_asserteq(7, env_get_ulong("c.cpu-speed", 10, 0));
ut_asserteq_str("2.5 GHz", env_get("c.cpu-speed-str"));
/* reset the expo */
menu->cur_item_id = ID_CPU_SPEED_1;
ut_assertok(run_command("cedit read_env -v", 0));
ut_assert_nextlinen("c.cpu-speed=7");
ut_assert_nextlinen("c.power-loss=10");
ut_assert_console_end();
ut_asserteq(ID_CPU_SPEED_2, menu->cur_item_id);
return 0;
}
BOOTSTD_TEST(cedit_env, 0);
/* Check the cedit write_cmos and read_cmos commands */
static int cedit_cmos(struct unit_test_state *uts)
{
struct scene_obj_menu *menu, *menu2;
struct video_priv *vid_priv;
extern struct expo *cur_exp;
struct scene *scn;
console_record_reset_enable();
ut_assertok(run_command("cedit load hostfs - cedit.dtb", 0));
ut_asserteq(ID_SCENE1, cedit_prepare(cur_exp, &vid_priv, &scn));
/* get the menus to fiddle with */
menu = scene_obj_find(scn, ID_CPU_SPEED, SCENEOBJT_MENU);
ut_assertnonnull(menu);
menu->cur_item_id = ID_CPU_SPEED_2;
menu2 = scene_obj_find(scn, ID_POWER_LOSS, SCENEOBJT_MENU);
ut_assertnonnull(menu2);
menu2->cur_item_id = ID_AC_MEMORY;
ut_assertok(run_command("cedit write_cmos -v", 0));
ut_assert_nextlinen("Write 2 bytes from offset 80 to 84");
ut_assert_console_end();
/* reset the expo */
menu->cur_item_id = ID_CPU_SPEED_1;
menu2->cur_item_id = ID_AC_OFF;
ut_assertok(run_command("cedit read_cmos -v", 0));
ut_assert_nextlinen("Read 2 bytes from offset 80 to 84");
ut_assert_console_end();
ut_asserteq(ID_CPU_SPEED_2, menu->cur_item_id);
ut_asserteq(ID_AC_MEMORY, menu2->cur_item_id);
return 0;
}
BOOTSTD_TEST(cedit_cmos, 0);

View file

@ -289,6 +289,33 @@ static int expo_object_attr(struct unit_test_state *uts)
}
BOOTSTD_TEST(expo_object_attr, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
/**
* struct test_iter_priv - private data for expo-iterator test
*
* @count: number of scene objects
* @menu_count: number of menus
* @fail_at: item ID at which to return an error
*/
struct test_iter_priv {
int count;
int menu_count;
int fail_at;
};
int h_test_iter(struct scene_obj *obj, void *vpriv)
{
struct test_iter_priv *priv = vpriv;
if (priv->fail_at == obj->id)
return -EINVAL;
priv->count++;
if (obj->type == SCENEOBJT_MENU)
priv->menu_count++;
return 0;
}
/* Check creating a scene with a menu */
static int expo_object_menu(struct unit_test_state *uts)
{
@ -296,6 +323,7 @@ static int expo_object_menu(struct unit_test_state *uts)
struct scene_menitem *item;
int id, label_id, desc_id, key_id, pointer_id, preview_id;
struct scene_obj_txt *ptr, *name1, *desc1, *key1, *tit, *prev1;
struct test_iter_priv priv;
struct scene *scn;
struct expo *exp;
ulong start_mem;
@ -382,6 +410,23 @@ static int expo_object_menu(struct unit_test_state *uts)
ut_asserteq(menu->obj.dim.y + 32, prev1->obj.dim.y);
ut_asserteq(true, prev1->obj.flags & SCENEOF_HIDE);
/* check iterating through scene items */
memset(&priv, '\0', sizeof(priv));
ut_assertok(expo_iter_scene_objs(exp, h_test_iter, &priv));
ut_asserteq(7, priv.count);
ut_asserteq(1, priv.menu_count);
/* check the iterator failing part way through iteration */
memset(&priv, '\0', sizeof(priv));
priv.fail_at = key_id;
ut_asserteq(-EINVAL, expo_iter_scene_objs(exp, h_test_iter, &priv));
/* 2 items (preview_id and the menuitem) are after key_id, 7 - 2 = 5 */
ut_asserteq(5, priv.count);
/* menu is first, so is still processed */
ut_asserteq(1, priv.menu_count);
expo_destroy(exp);
ut_assertok(ut_check_delta(start_mem));
@ -669,46 +714,3 @@ static int expo_test_build(struct unit_test_state *uts)
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,25 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Sample expo screen layout (ID numbers)
*/
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,
};

View file

@ -5,28 +5,7 @@
/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,
};
*/
/* see expo_ids.h for the IDs */
/ {
dynamic-start = <ID_DYNAMIC_START>;
@ -59,6 +38,9 @@ enum {
/* IDs for the menu items */
item-id = <ID_CPU_SPEED_1 ID_CPU_SPEED_2
ID_CPU_SPEED_3>;
start-bit = <0x400>;
bit-length = <2>;
};
power-loss {
@ -70,6 +52,8 @@ enum {
"Memory";
item-id = <ID_AC_OFF ID_AC_ON ID_AC_MEMORY>;
start-bit = <0x422>;
bit-length = <2>;
};
};
};

View file

@ -155,6 +155,31 @@ static int lib_test_abuf_realloc_size(struct unit_test_state *uts)
}
LIB_TEST(lib_test_abuf_realloc_size, 0);
/* Test abuf_realloc_inc() */
static int lib_test_abuf_realloc_inc(struct unit_test_state *uts)
{
struct abuf buf;
ulong start;
start = ut_check_free();
abuf_init(&buf);
ut_asserteq(0, buf.size);
ut_asserteq(false, buf.alloced);
abuf_realloc_inc(&buf, 20);
ut_asserteq(20, buf.size);
ut_asserteq(true, buf.alloced);
abuf_uninit(&buf);
/* Check for memory leaks */
ut_assertok(ut_check_delta(start));
return 0;
}
LIB_TEST(lib_test_abuf_realloc_inc, 0);
/* Test handling of buffers that are too large */
static int lib_test_abuf_large(struct unit_test_state *uts)
{

View file

@ -285,10 +285,12 @@ label Fedora-Workstation-armhfp-31-1.9 (5.3.7-301.fc31.armv7hl)
def setup_cedit_file(cons):
infname = os.path.join(cons.config.source_dir,
'test/boot/files/expo_layout.dts')
inhname = os.path.join(cons.config.source_dir,
'test/boot/files/expo_ids.h')
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}')
cons, f'{expo_tool} -e {inhname} -l {infname} -o {outfname}')
@pytest.mark.buildconfigspec('ut_dm')

View file

@ -69,7 +69,10 @@ def calc_ids(fname):
def run_expo(args):
"""Run the expo program"""
ids = calc_ids(args.enum_fname)
fname = args.enum_fname or args.layout
ids = calc_ids(fname)
if not ids:
print(f"Warning: No enum ID values found in file '{fname}'")
indata = tools.read_file(args.layout)
@ -88,10 +91,10 @@ def run_expo(args):
with open('/tmp/asc', 'wb') as outf:
outf.write(data)
proc = subprocess.run('dtc', input=data, capture_output=True, check=True)
proc = subprocess.run('dtc', input=data, capture_output=True)
edtb = proc.stdout
if proc.stderr:
print(proc.stderr)
print(f"Devicetree compiler error:\n{proc.stderr.decode('utf-8')}")
return 1
tools.write_file(args.outfile, edtb)
return 0
@ -109,11 +112,13 @@ def parse_args(argv):
args is a list of string arguments
"""
parser = argparse.ArgumentParser()
parser.add_argument('-D', '--debug', action='store_true',
help='Enable full debug traceback')
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='.dts or C file containing enum declaration for expo items')
parser.add_argument('-l', '--layout', type=str, required=True,
help='Devicetree file source .dts for expo layout (and perhaps enums)')
parser.add_argument('-o', '--outfile', type=str, required=True,
help='Filename to write expo layout dtb')
return parser.parse_args(argv)
@ -122,6 +127,9 @@ def start_expo():
"""Start the expo program"""
args = parse_args(sys.argv[1:])
if not args.debug:
sys.tracebacklimit = 0
ret_code = run_expo(args)
sys.exit(ret_code)