expo: cedit: Support writing settings to CMOS RAM

Add a command to write cedit settings to CMOS RAM so that it can be
preserved across a reboot. This uses a simple bit-encoding, where each
field has a 'bit position' and a 'bit length' in the schema.

Signed-off-by: Simon Glass <sjg@chromium.org>
This commit is contained in:
Simon Glass 2023-08-14 16:40:37 -06:00 committed by Tom Rini
parent bcf2b7202e
commit eb6c71b562
9 changed files with 266 additions and 3 deletions

View file

@ -15,22 +15,37 @@
#include <dm.h> #include <dm.h>
#include <env.h> #include <env.h>
#include <expo.h> #include <expo.h>
#include <malloc.h>
#include <menu.h> #include <menu.h>
#include <rtc.h>
#include <video.h> #include <video.h>
#include <linux/delay.h> #include <linux/delay.h>
#include "scene_internal.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 * struct cedit_iter_priv - private data for cedit operations
* *
* @buf: Buffer to use when writing settings to the devicetree * @buf: Buffer to use when writing settings to the devicetree
* @node: Node to read from when reading settings from devicetree * @node: Node to read from when reading settings from devicetree
* @verbose: true to show writing to environment variables * @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
*/ */
struct cedit_iter_priv { struct cedit_iter_priv {
struct abuf *buf; struct abuf *buf;
ofnode node; ofnode node;
bool verbose; bool verbose;
u8 *mask;
u8 *value;
}; };
int cedit_arange(struct expo *exp, struct video_priv *vpriv, uint scene_id) int cedit_arange(struct expo *exp, struct video_priv *vpriv, uint scene_id)
@ -445,7 +460,7 @@ static int h_read_settings_env(struct scene_obj *obj, void *vpriv)
struct cedit_iter_priv *priv = vpriv; struct cedit_iter_priv *priv = vpriv;
struct scene_obj_menu *menu; struct scene_obj_menu *menu;
char var[60]; char var[60];
int val, ret; int val;
if (obj->type != SCENEOBJT_MENU) if (obj->type != SCENEOBJT_MENU)
return 0; return 0;
@ -484,3 +499,123 @@ int cedit_read_settings_env(struct expo *exp, bool verbose)
return 0; 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;
}

View file

@ -294,7 +294,7 @@ static int obj_build(struct build_info *info, ofnode node, struct scene *scn)
{ {
struct scene_obj *obj; struct scene_obj *obj;
const char *type; const char *type;
u32 id; u32 id, val;
int ret; int ret;
log_debug("- object %s\n", ofnode_get_name(node)); log_debug("- object %s\n", ofnode_get_name(node));
@ -313,6 +313,11 @@ static int obj_build(struct build_info *info, ofnode node, struct scene *scn)
if (ret) if (ret)
return log_msg_ret("bld", 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; return 0;
} }

View file

@ -10,6 +10,7 @@
#include <abuf.h> #include <abuf.h>
#include <cedit.h> #include <cedit.h>
#include <command.h> #include <command.h>
#include <dm.h>
#include <expo.h> #include <expo.h>
#include <fs.h> #include <fs.h>
#include <malloc.h> #include <malloc.h>
@ -176,6 +177,39 @@ static int do_cedit_read_env(struct cmd_tbl *cmdtp, int flag, int argc,
return 0; 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_run(struct cmd_tbl *cmdtp, int flag, int argc, static int do_cedit_run(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[]) char *const argv[])
{ {
@ -209,6 +243,7 @@ static char cedit_help_text[] =
"cedit write_fdt <i/f> <dev[:part]> <filename> - write settings\n" "cedit write_fdt <i/f> <dev[:part]> <filename> - write settings\n"
"cedit read_env [-v] - read settings from env vars\n" "cedit read_env [-v] - read settings from env vars\n"
"cedit write_env [-v] - write settings to env vars\n" "cedit write_env [-v] - write settings to env vars\n"
"cedit write_cmos [-v] [dev] - write settings to CMOS RAM\n"
"cedit run - run config editor"; "cedit run - run config editor";
#endif /* CONFIG_SYS_LONGHELP */ #endif /* CONFIG_SYS_LONGHELP */
@ -218,5 +253,6 @@ U_BOOT_CMD_WITH_SUBCMDS(cedit, "Configuration editor", cedit_help_text,
U_BOOT_SUBCMD_MKENT(write_fdt, 5, 1, do_cedit_write_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(read_env, 2, 1, do_cedit_read_env),
U_BOOT_SUBCMD_MKENT(write_env, 2, 1, do_cedit_write_env), U_BOOT_SUBCMD_MKENT(write_env, 2, 1, do_cedit_write_env),
U_BOOT_SUBCMD_MKENT(write_cmos, 2, 1, do_cedit_write_cmos),
U_BOOT_SUBCMD_MKENT(run, 1, 1, do_cedit_run), U_BOOT_SUBCMD_MKENT(run, 1, 1, do_cedit_run),
); );

View file

@ -317,6 +317,18 @@ id
Specifies the ID of the object. This is used when referring to the object. 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: Menu nodes have the following additional properties:
@ -474,6 +486,7 @@ Some ideas for future work:
- Support curses for proper serial-terminal menus - Support curses for proper serial-terminal menus
- Add support for large menus which need to scroll - Add support for large menus which need to scroll
- Add support for reading and writing configuration settings with cedit - 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> .. Simon Glass <sjg@chromium.org>
.. 7-Oct-22 .. 7-Oct-22

View file

@ -14,6 +14,7 @@ Synopis
cedit read_fdt <dev[:part]> <filename> cedit read_fdt <dev[:part]> <filename>
cedit write_env [-v] cedit write_env [-v]
cedit read_env [-v] cedit read_env [-v]
cedit write_cmos [-v] [dev]
Description Description
----------- -----------
@ -76,6 +77,18 @@ ID and its text string are written, similar to:
The `-v` flag enables verbose mode, where each variable is printed before it is The `-v` flag enables verbose mode, where each variable is printed before it is
set. 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 Example
------- -------
@ -117,3 +130,12 @@ This shows settings being stored in the environment::
=> cedit read_env -v => cedit read_env -v
c.cpu-speed=7 c.cpu-speed=7
c.power-loss=12 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
Write 2 bytes from offset 80 to 84
=> rtc read 80 8
00000080: 01 00 00 00 08 2f 2a 08 ...../*.

View file

@ -97,4 +97,17 @@ int cedit_write_settings_env(struct expo *exp, bool verbose);
*/ */
int cedit_read_settings_env(struct expo *exp, bool verbose); 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);
#endif /* __CEDIT_H */ #endif /* __CEDIT_H */

View file

@ -187,6 +187,8 @@ enum scene_obj_flags_t {
* @type: Type of this object * @type: Type of this object
* @dim: Dimensions for this object * @dim: Dimensions for this object
* @flags: Flags 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 * @sibling: Node to link this object to its siblings
*/ */
struct scene_obj { struct scene_obj {
@ -195,7 +197,9 @@ struct scene_obj {
uint id; uint id;
enum scene_obj_t type; enum scene_obj_t type;
struct scene_dim dim; struct scene_dim dim;
int flags; u8 flags;
u8 bit_length;
u16 start_bit;
struct list_head sibling; struct list_head sibling;
}; };

View file

@ -155,3 +155,33 @@ static int cedit_env(struct unit_test_state *uts)
return 0; return 0;
} }
BOOTSTD_TEST(cedit_env, 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();
return 0;
}
BOOTSTD_TEST(cedit_cmos, 0);

View file

@ -38,6 +38,9 @@
/* IDs for the menu items */ /* IDs for the menu items */
item-id = <ID_CPU_SPEED_1 ID_CPU_SPEED_2 item-id = <ID_CPU_SPEED_1 ID_CPU_SPEED_2
ID_CPU_SPEED_3>; ID_CPU_SPEED_3>;
start-bit = <0x400>;
bit-length = <2>;
}; };
power-loss { power-loss {
@ -49,6 +52,8 @@
"Memory"; "Memory";
item-id = <ID_AC_OFF ID_AC_ON ID_AC_MEMORY>; item-id = <ID_AC_OFF ID_AC_ON ID_AC_MEMORY>;
start-bit = <0x422>;
bit-length = <2>;
}; };
}; };
}; };