Files
u-boot/boot/cedit.c
Simon Glass ef117db9c3 expo: Store mouse pointer and size in expo
Store the mouse pointer image and its dimensions in the expo structure
when mouse support is enabled. This avoids repeatedly looking up the
image and calculating its size.

Use struct vid_size to store the mouse pointer dimensions.

Signed-off-by: Simon Glass <sjg@chromium.org>
2025-10-06 10:03:29 -06:00

926 lines
21 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* Implementation of configuration editor
*
* Copyright 2023 Google LLC
* Written by Simon Glass <sjg@chromium.org>
*/
#define LOG_CATEGORY LOGC_EXPO
#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"
#include <u-boot/schedule.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 expo_theme *theme = &exp->theme;
struct expo_arrange_info arr;
struct scene_obj_txt *txt;
struct scene_obj *obj;
struct scene *scn;
int y, ret;
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);
memset(&arr, '\0', sizeof(arr));
ret = scene_calc_arrange(scn, &arr);
if (ret < 0)
return log_msg_ret("arr", ret);
y = 100;
list_for_each_entry(obj, &scn->obj_head, sibling) {
bool add_gap = true;
switch (obj->type) {
case SCENEOBJT_NONE:
case SCENEOBJT_IMAGE:
case SCENEOBJT_TEXT:
case SCENEOBJT_BOX:
case SCENEOBJT_TEXTEDIT:
add_gap = false;
break;
case SCENEOBJT_MENU:
scene_obj_set_pos(scn, obj->id, 50, y);
scene_menu_arrange(scn, &arr,
(struct scene_obj_menu *)obj);
y += obj->dims.y;
break;
case SCENEOBJT_TEXTLINE:
scene_obj_set_pos(scn, obj->id, 50, y);
scene_textline_arrange(scn, &arr,
(struct scene_obj_textline *)obj);
y += obj->dims.y;
break;
}
if (add_gap)
y += theme->menuitem_gap_y;
}
ret = scene_arrange(scn);
if (ret)
return log_msg_ret("cac", ret);
return 0;
}
int cedit_prepare(struct expo *exp, struct udevice *vid_dev,
struct scene **scnp)
{
struct udevice *dev = vid_dev;
struct video_priv *vid_priv;
struct scene *scn;
uint scene_id;
int ret;
/* For now we only support a video console */
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;
exp->show_highlight = 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);
ret = expo_calc_dims(exp);
if (ret)
return log_msg_ret("dim", ret);
cedit_arange(exp, vid_priv, scene_id);
*scnp = scn;
return scene_id;
}
int cedit_do_action(struct expo *exp, struct scene *scn,
struct video_priv *vid_priv, struct expo_action *act)
{
int ret;
switch (act->type) {
case EXPOACT_NONE:
return -EAGAIN;
case EXPOACT_POINT_ITEM:
ret = scene_menu_select_item(scn, scn->highlight_id,
act->select.id);
if (ret)
return log_msg_ret("cdp", ret);
break;
case EXPOACT_POINT_OBJ:
scene_set_highlight_id(scn, act->select.id);
cedit_arange(exp, vid_priv, scn->id);
break;
case EXPOACT_OPEN:
scene_set_open(scn, act->select.id, true);
cedit_arange(exp, vid_priv, scn->id);
switch (scn->highlight_id) {
case EXPOID_SAVE:
exp->done = true;
exp->save = true;
break;
case EXPOID_DISCARD:
exp->done = true;
break;
}
break;
case EXPOACT_CLOSE:
scene_set_open(scn, act->select.id, false);
cedit_arange(exp, vid_priv, scn->id);
break;
case EXPOACT_POINT_OPEN:
scene_set_highlight_id(scn, act->select.id);
scene_set_open(scn, act->select.id, true);
cedit_arange(exp, vid_priv, scn->id);
break;
case EXPOACT_POINT_CLOSE:
/* Point to the item (select it in the menu) */
ret = scene_menu_select_item(scn, scn->highlight_id,
act->select.id);
if (ret)
return log_msg_ret("pco", ret);
/* Close the menu */
scene_set_open(scn, scn->highlight_id, false);
cedit_arange(exp, vid_priv, scn->id);
break;
case EXPOACT_REPOINT_OPEN:
scene_set_open(scn, act->select.prev_id, false);
scene_set_highlight_id(scn, act->select.id);
scene_set_open(scn, act->select.id, true);
cedit_arange(exp, vid_priv, scn->id);
break;
case EXPOACT_SELECT:
scene_set_open(scn, scn->highlight_id, false);
cedit_arange(exp, vid_priv, scn->id);
break;
case EXPOACT_QUIT:
log_debug("quitting\n");
exp->done = true;
break;
}
return 0;
}
int cedit_run(struct expo *exp)
{
struct video_priv *vid_priv;
struct udevice *dev;
struct scene *scn;
uint scene_id;
int ret;
ret = uclass_first_device_err(UCLASS_VIDEO, &dev);
if (ret)
return log_msg_ret("vid", ret);
vid_priv = dev_get_uclass_priv(dev);
ret = cedit_prepare(exp, dev, &scn);
if (ret < 0)
return log_msg_ret("prep", ret);
scene_id = ret;
expo_set_mouse_enable(exp, true);
expo_enter_mode(exp);
exp->done = false;
exp->save = false;
do {
struct expo_action act;
ret = expo_render(exp);
if (ret) {
expo_exit_mode(exp);
return log_msg_ret("cer", ret);
}
ret = expo_poll(exp, &act);
if (!ret) {
cedit_do_action(exp, scn, vid_priv, &act);
} else if (ret != -EAGAIN) {
expo_exit_mode(exp);
return log_msg_ret("cep", ret);
}
} while (!exp->done);
if (ret)
return log_msg_ret("end", ret);
if (!exp->save)
return -EACCES;
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;
}
/**
* get_cur_menuitem_text() - Get the text of the currently selected item
*
* Looks up the object for the current item, finds text object for it and looks
* up the string for that text
*
* @menu: Menu to look at
* @strp: Returns a pointer to the next
* Return: 0 if OK, -ENOENT if something was not found
*/
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->gen.str_id);
if (!str)
return log_msg_ret("str", -ENOENT);
*strp = str;
return 0;
}
/**
* get_cur_menuitem_val() - Get the value of a menu's current item
*
* Obtains the value of the current item in the menu. If no value, then
* 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
* @valp: Returns current-item value / sequence number
* Return: 0 on success, else -ve error value
*/
static int get_cur_menuitem_val(const struct scene_obj_menu *menu, int *valp)
{
const struct scene_menitem *mi;
int seq;
seq = 0;
list_for_each_entry(mi, &menu->item_head, sibling) {
if (mi->id == menu->cur_item_id) {
*valp = mi->value == INT_MAX ? seq : mi->value;
return 0;
}
seq++;
}
return log_msg_ret("nf", -ENOENT);
}
/**
* write_dt_string() - Write a string to the devicetree, expanding if needed
*
* If this fails, it tries again after expanding the devicetree a little
*
* @buf: Buffer containing the devicetree
* @name: Property name to use
* @str: String value
* Return: 0 if OK, -EFAULT if something went horribly wrong
*/
static int write_dt_string(struct abuf *buf, const char *name, const char *str)
{
int ret, i;
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("str", -EFAULT);
return 0;
}
/**
* write_dt_u32() - Write an int to the devicetree, expanding if needed
*
* If this fails, it tries again after expanding the devicetree a little
*
* @buf: Buffer containing the devicetree
* @name: Property name to use
* @lva: Integer value
* Return: 0 if OK, -EFAULT if something went horribly wrong
*/
static int write_dt_u32(struct abuf *buf, const char *name, uint val)
{
int ret, i;
/* write the text of the current item */
ret = -EAGAIN;
for (i = 0; ret && i < 2; i++) {
ret = fdt_property_u32(abuf_data(buf), name, val);
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("str", -EFAULT);
return 0;
}
static int h_write_settings(struct scene_obj *obj, void *vpriv)
{
struct cedit_iter_priv *priv = vpriv;
struct abuf *buf = priv->buf;
int ret;
switch (obj->type) {
case SCENEOBJT_NONE:
case SCENEOBJT_IMAGE:
case SCENEOBJT_TEXT:
case SCENEOBJT_BOX:
case SCENEOBJT_TEXTEDIT:
break;
case SCENEOBJT_TEXTLINE: {
const struct scene_obj_textline *tline;
tline = (struct scene_obj_textline *)obj;
ret = write_dt_string(buf, obj->name, abuf_data(&tline->buf));
if (ret)
return log_msg_ret("wr2", ret);
break;
}
case SCENEOBJT_MENU: {
const struct scene_obj_menu *menu;
const char *str;
char name[80];
int val;
/* write the ID of the current item */
menu = (struct scene_obj_menu *)obj;
ret = write_dt_u32(buf, obj->name, menu->cur_item_id);
if (ret)
return log_msg_ret("wrt", ret);
snprintf(name, sizeof(name), "%s-value", obj->name);
ret = get_cur_menuitem_val(menu, &val);
if (ret < 0)
return log_msg_ret("cur", ret);
ret = write_dt_u32(buf, name, val);
if (ret)
return log_msg_ret("wr2", ret);
ret = get_cur_menuitem_text(menu, &str);
if (ret)
return log_msg_ret("mis", ret);
/* write the text of the current item */
snprintf(name, sizeof(name), "%s-str", obj->name);
ret = write_dt_string(buf, name, str);
if (ret)
return log_msg_ret("wr2", ret);
break;
}
}
return 0;
}
int cedit_write_settings(struct expo *exp, struct abuf *buf)
{
struct cedit_iter_priv priv;
void *fdt;
int ret;
if (!abuf_init_size(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:
case SCENEOBJT_BOX:
case SCENEOBJT_TEXTEDIT:
break;
case SCENEOBJT_TEXTLINE: {
const struct scene_obj_textline *tline;
const char *val;
int len;
tline = (struct scene_obj_textline *)obj;
val = ofnode_read_prop(node, obj->name, &len);
if (len >= tline->max_chars)
return log_msg_ret("str", -ENOSPC);
strcpy(abuf_data(&tline->buf), val);
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->id < EXPOID_BASE_ID)
return 0;
snprintf(var, sizeof(var), "c.%s", obj->name);
switch (obj->type) {
case SCENEOBJT_NONE:
case SCENEOBJT_IMAGE:
case SCENEOBJT_TEXT:
case SCENEOBJT_BOX:
case SCENEOBJT_TEXTEDIT:
break;
case SCENEOBJT_MENU:
menu = (struct scene_obj_menu *)obj;
val = menu->cur_item_id;
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);
ret = get_cur_menuitem_val(menu, &val);
if (ret < 0)
return log_msg_ret("cur", ret);
snprintf(name, sizeof(name), "c.%s-value", obj->name);
if (priv->verbose)
printf("%s=%d\n", name, val);
break;
case SCENEOBJT_TEXTLINE: {
const struct scene_obj_textline *tline;
tline = (struct scene_obj_textline *)obj;
str = abuf_data(&tline->buf);
ret = env_set(var, str);
if (ret)
return log_msg_ret("set", ret);
if (priv->verbose)
printf("%s=%s\n", var, str);
break;
}
}
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->id < EXPOID_BASE_ID)
return 0;
snprintf(var, sizeof(var), "c.%s", obj->name);
switch (obj->type) {
case SCENEOBJT_NONE:
case SCENEOBJT_IMAGE:
case SCENEOBJT_TEXT:
case SCENEOBJT_BOX:
case SCENEOBJT_TEXTEDIT:
break;
case SCENEOBJT_MENU:
menu = (struct scene_obj_menu *)obj;
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;
break;
case SCENEOBJT_TEXTLINE: {
const struct scene_obj_textline *tline;
const char *value;
tline = (struct scene_obj_textline *)obj;
value = env_get(var);
if (value && strlen(value) >= tline->max_chars)
return log_msg_ret("str", -ENOSPC);
if (!value)
value = "";
if (priv->verbose)
printf("%s=%s\n", var, value);
strcpy(abuf_data(&tline->buf), value);
break;
}
}
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;
}
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;
if (obj->type != SCENEOBJT_MENU || obj->id < EXPOID_BASE_ID)
return 0;
menu = (struct scene_obj_menu *)obj;
val = menu->cur_item_id;
ret = get_cur_menuitem_val(menu, &val);
if (ret < 0)
return log_msg_ret("cur", ret);
log_debug("%s: val=%d\n", menu->obj.name, val);
/* 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++, val >>= 1) {
uint bitnum = obj->start_bit + i;
priv->mask[CMOS_BYTE(bitnum)] |= 1 << CMOS_BIT(bitnum);
if (val & 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 */
log_debug("Writing CMOS\n");
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 || obj->id < EXPOID_BASE_ID)
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 */
log_debug("look for menuitem value %d in menu %d\n", val, menu->obj.id);
mi = scene_menuitem_find_val(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;
}
/* indicate what bytes were read from 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;
}