CFR: add dependencies based on specific option values

Implements a way for CFR options to depend on another option
being set to one or more specific values. This is achieved
by writing a list of values as a varbinary struct.

Change-Id: Iaf7965551490969052eb27c207fa524470d4dd6a
Signed-off-by: Filip Brozovic <fbrozovic@gmail.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/85987
Reviewed-by: Angel Pons <th3fanbus@gmail.com>
Reviewed-by: Matt DeVillier <matt.devillier@gmail.com>
Reviewed-by: Sean Rhodes <sean@starlabs.systems>
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
This commit is contained in:
Filip Brozovic 2025-01-14 22:42:41 +01:00 committed by Matt DeVillier
commit 17cc750e8b
4 changed files with 117 additions and 29 deletions

View file

@ -50,8 +50,9 @@ static const struct sm_object serial_number = SM_DECLARE_VARCHAR({
The CFR options can have a dependency that must be evaluated at runtime by
the OS/payload that parses the CFR record and displays the UI.
By using the `WITH_DEP()` macro you can specify another numberic option that
is checked to hide the current option.
By using the `WITH_DEP()` macro you can specify another numeric option that
is checked to hide the current option. The `WITH_DEP_VALUES()` macro allows
specifying one or more values that cause the dependent option to be displayed.
**Example:** Declares a dependency from `sata_disable_port0` to `sata_enable`.
The option `sata_disable_port0` will be hidden as long as "sata_enable" is 0.
@ -76,6 +77,37 @@ static struct sm_object sata_disable_port0 = SM_DECLARE_BOOL({
}, WITH_DEP(&sata_enable));
```
**Example:** Declares a dependency from `com1_termination` to `com1_mode`.
The option `com1_termination` will only be shown if `com1_mode` is set to RS-485.
```
#define COM_MODE_DISABLED 3
#define COM_MODE_RS232 0
#define COM_MODE_RS485 1
static struct sm_object com1_mode = SM_DECLARE_ENUM({
.flags = CFR_OPTFLAG_RUNTIME,
.opt_name = "com1_mode",
.ui_name = "COM1 Mode",
.ui_helptext = NULL,
.default_value = 1,
.values = (const struct sm_enum_value[]) {
{ "Disabled", COM_MODE_DISABLED },
{ "RS-232", COM_MODE_RS232 },
{ "RS-485", COM_MODE_RS485 },
SM_ENUM_VALUE_END },
});
static struct sm_object com1_termination = SM_DECLARE_BOOL({
.flags = CFR_OPTFLAG_RUNTIME,
.opt_name = "com1_termination",
.ui_name = "Enable COM1 termination resistors",
.ui_helptext = NULL,
.default_value = false,
}, WITH_DEP_VALUES(&com1_mode, COM_MODE_RS485));
```
### Providing mainboard custom options
A mainboard that uses CFR can provide a list of custom options

View file

@ -63,6 +63,7 @@ enum cfr_tags {
CFR_TAG_VARCHAR_UI_HELPTEXT = 9,
CFR_TAG_VARCHAR_DEF_VALUE = 10,
CFR_TAG_OPTION_COMMENT = 11,
CFR_TAG_DEP_VALUES = 12,
};
/*
@ -104,7 +105,8 @@ enum cfr_option_flags {
struct __packed lb_cfr_varbinary {
uint32_t tag; /*
* CFR_TAG_VARCHAR_OPT_NAME, CFR_TAG_VARCHAR_UI_NAME,
* CFR_TAG_VARCHAR_UI_HELPTEXT or CFR_TAG_VARCHAR_DEF_VALUE
* CFR_TAG_VARCHAR_UI_HELPTEXT, CFR_TAG_VARCHAR_DEF_VALUE
* or CFR_TAG_DEP_VALUES
*/
uint32_t size; /* Length of the entire structure */
uint32_t data_length; /* Length of data, including NULL terminator for strings */
@ -136,6 +138,7 @@ struct __packed lb_cfr_numeric_option {
* struct lb_cfr_varbinary opt_name
* struct lb_cfr_varbinary ui_name
* struct lb_cfr_varbinary ui_helptext (Optional)
* struct lb_cfr_varbinary dependency_values (Optional)
* struct lb_cfr_enum_value enum_values[]
*/
};
@ -153,6 +156,7 @@ struct __packed lb_cfr_varchar_option {
* struct lb_cfr_varbinary opt_name
* struct lb_cfr_varbinary ui_name
* struct lb_cfr_varbinary ui_helptext (Optional)
* struct lb_cfr_varbinary dependency_values (Optional)
*/
};
@ -172,6 +176,7 @@ struct __packed lb_cfr_option_comment {
/*
* struct lb_cfr_varbinary ui_name
* struct lb_cfr_varbinary ui_helptext (Optional)
* struct lb_cfr_varbinary dependency_values (Optional)
*/
};
@ -186,6 +191,7 @@ struct __packed lb_cfr_option_form {
uint32_t flags; /* enum cfr_option_flags */
/*
* struct lb_cfr_varbinary ui_name
* struct lb_cfr_varbinary dependency_values (Optional)
* struct lb_cfr_varchar_option options[]
*/
};

View file

@ -69,6 +69,25 @@ static uint32_t sm_write_ui_helptext(char *current, const char *string)
return write_cfr_varchar(current, string, CFR_TAG_VARCHAR_UI_HELPTEXT);
}
static uint32_t sm_write_dep_values(char *current,
const uint32_t *dep_values, const uint32_t num_dep_values)
{
/* Dependency values are optional */
if (!dep_values || !num_dep_values)
return 0;
struct lb_cfr_varbinary *cfr_values = (struct lb_cfr_varbinary *)current;
cfr_values->tag = CFR_TAG_DEP_VALUES;
cfr_values->data_length = sizeof(*dep_values) * num_dep_values;
char *data = current + sizeof(*cfr_values);
memcpy(data, dep_values, cfr_values->data_length);
/* Make sure that every TAG/SIZE field is always aligned to LB_ENTRY_ALIGN */
cfr_values->size = ALIGN_UP(sizeof(*cfr_values) + cfr_values->data_length, LB_ENTRY_ALIGN);
return cfr_values->size;
}
static uint32_t sm_write_enum_value(char *current, const struct sm_enum_value *e)
{
struct lb_cfr_enum_value *enum_val = (struct lb_cfr_enum_value *)current;
@ -86,7 +105,7 @@ static uint32_t sm_write_enum_value(char *current, const struct sm_enum_value *e
static uint32_t write_numeric_option(char *current, uint32_t tag, const uint64_t object_id,
const char *opt_name, const char *ui_name, const char *ui_helptext,
uint32_t flags, uint32_t default_value, const struct sm_enum_value *values,
const uint64_t dep_id)
const uint64_t dep_id, const uint32_t *dep_values, const uint32_t num_dep_values)
{
struct lb_cfr_numeric_option *option = (struct lb_cfr_numeric_option *)current;
size_t len;
@ -110,6 +129,7 @@ static uint32_t write_numeric_option(char *current, uint32_t tag, const uint64_t
return 0;
current += len;
current += sm_write_ui_helptext(current, ui_helptext);
current += sm_write_dep_values(current, dep_values, num_dep_values);
if (option->tag == CFR_TAG_OPTION_ENUM && values) {
for (const struct sm_enum_value *e = values; e->ui_name; e++) {
@ -122,35 +142,41 @@ static uint32_t write_numeric_option(char *current, uint32_t tag, const uint64_t
}
static uint32_t sm_write_opt_enum(char *current, const struct sm_obj_enum *sm_enum,
const uint64_t object_id, const uint64_t dep_id)
const uint64_t object_id, const uint64_t dep_id,
const uint32_t *dep_values, const uint32_t num_dep_values)
{
return write_numeric_option(current, CFR_TAG_OPTION_ENUM, object_id,
sm_enum->opt_name, sm_enum->ui_name, sm_enum->ui_helptext,
sm_enum->flags, sm_enum->default_value, sm_enum->values,
dep_id);
dep_id, dep_values, num_dep_values);
}
static uint32_t sm_write_opt_number(char *current, const struct sm_obj_number *sm_number,
const uint64_t object_id, const uint64_t dep_id)
const uint64_t object_id, const uint64_t dep_id,
const uint32_t *dep_values, const uint32_t num_dep_values)
{
return write_numeric_option(current, CFR_TAG_OPTION_NUMBER, object_id,
sm_number->opt_name, sm_number->ui_name, sm_number->ui_helptext,
sm_number->flags, sm_number->default_value, NULL, dep_id);
sm_number->flags, sm_number->default_value, NULL, dep_id,
dep_values, num_dep_values);
}
static uint32_t sm_write_opt_bool(char *current, const struct sm_obj_bool *sm_bool,
const uint64_t object_id, const uint64_t dep_id)
const uint64_t object_id, const uint64_t dep_id,
const uint32_t *dep_values, const uint32_t num_dep_values)
{
return write_numeric_option(current, CFR_TAG_OPTION_BOOL, object_id,
sm_bool->opt_name, sm_bool->ui_name, sm_bool->ui_helptext,
sm_bool->flags, sm_bool->default_value, NULL, dep_id);
sm_bool->flags, sm_bool->default_value, NULL, dep_id,
dep_values, num_dep_values);
}
static uint32_t sm_write_opt_varchar(char *current, const struct sm_obj_varchar *sm_varchar,
const uint64_t object_id, const uint64_t dep_id)
const uint64_t object_id, const uint64_t dep_id,
const uint32_t *dep_values, const uint32_t num_dep_values)
{
struct lb_cfr_varchar_option *option = (struct lb_cfr_varchar_option *)current;
@ -175,13 +201,15 @@ static uint32_t sm_write_opt_varchar(char *current, const struct sm_obj_varchar
return 0;
current += len;
current += sm_write_ui_helptext(current, sm_varchar->ui_helptext);
current += sm_write_dep_values(current, dep_values, num_dep_values);
option->size = cfr_record_size((char *)option, current);
return option->size;
}
static uint32_t sm_write_opt_comment(char *current, const struct sm_obj_comment *sm_comment,
const uint32_t object_id, const uint32_t dep_id)
const uint32_t object_id, const uint32_t dep_id,
const uint32_t *dep_values, const uint32_t num_dep_values)
{
struct lb_cfr_option_comment *comment = (struct lb_cfr_option_comment *)current;
size_t len;
@ -200,6 +228,7 @@ static uint32_t sm_write_opt_comment(char *current, const struct sm_obj_comment
return 0;
current += len;
current += sm_write_ui_helptext(current, sm_comment->ui_helptext);
current += sm_write_dep_values(current, dep_values, num_dep_values);
comment->size = cfr_record_size((char *)comment, current);
return comment->size;
@ -215,7 +244,8 @@ static uint64_t sm_gen_obj_id(void *ptr)
static uint32_t sm_write_object(char *current, const struct sm_object *sm_obj);
static uint32_t sm_write_form(char *current, struct sm_obj_form *sm_form,
const uint64_t object_id, const uint64_t dep_id)
const uint64_t object_id, const uint64_t dep_id,
const uint32_t *dep_values, const uint32_t num_dep_values)
{
struct lb_cfr_option_form *form = (struct lb_cfr_option_form *)current;
size_t len;
@ -234,6 +264,7 @@ static uint32_t sm_write_form(char *current, struct sm_obj_form *sm_form,
if (!len)
return 0;
current += len;
current += sm_write_dep_values(current, dep_values, num_dep_values);
while (sm_form->obj_list[i])
current += sm_write_object(current, sm_form->obj_list[i++]);
@ -245,6 +276,8 @@ static uint32_t sm_write_form(char *current, struct sm_obj_form *sm_form,
static uint32_t sm_write_object(char *current, const struct sm_object *sm_obj)
{
uint64_t dep_id, obj_id;
const uint32_t *dep_values;
uint32_t num_dep_values;
struct sm_object sm_obj_copy;
assert(sm_obj);
@ -253,10 +286,14 @@ static uint32_t sm_write_object(char *current, const struct sm_object *sm_obj)
/* Set dependency ID */
dep_id = 0;
dep_values = NULL;
num_dep_values = 0;
if (sm_obj->dep) {
assert(sm_obj->dep->kind == SM_OBJ_BOOL);
if (sm_obj->dep->kind == SM_OBJ_BOOL)
if (sm_obj->dep->kind == SM_OBJ_BOOL || sm_obj->dep->kind == SM_OBJ_ENUM) {
dep_id = sm_gen_obj_id((void *)sm_obj->dep);
dep_values = sm_obj->dep_values;
num_dep_values = sm_obj->num_dep_values;
}
}
/* Invoke callback to update fields */
@ -273,21 +310,22 @@ static uint32_t sm_write_object(char *current, const struct sm_object *sm_obj)
return 0;
case SM_OBJ_ENUM:
return sm_write_opt_enum(current, &sm_obj->sm_enum, obj_id,
dep_id);
dep_id, dep_values, num_dep_values);
case SM_OBJ_NUMBER:
return sm_write_opt_number(current, &sm_obj->sm_number, obj_id,
dep_id);
dep_id, dep_values, num_dep_values);
case SM_OBJ_BOOL:
return sm_write_opt_bool(current, &sm_obj->sm_bool, obj_id,
dep_id);
dep_id, dep_values, num_dep_values);
case SM_OBJ_VARCHAR:
return sm_write_opt_varchar(current, &sm_obj->sm_varchar, obj_id,
dep_id);
dep_id, dep_values, num_dep_values);
case SM_OBJ_COMMENT:
return sm_write_opt_comment(current, &sm_obj->sm_comment, obj_id,
dep_id);
dep_id, dep_values, num_dep_values);
case SM_OBJ_FORM:
return sm_write_form(current, (struct sm_obj_form *)&sm_obj->sm_form, obj_id, dep_id);
return sm_write_form(current, (struct sm_obj_form *)&sm_obj->sm_form, obj_id,
dep_id, dep_values, num_dep_values);
default:
BUG();
printk(BIOS_ERR, "Unknown setup menu object kind %u, ignoring\n", sm_obj->kind);
@ -311,13 +349,13 @@ void cfr_write_setup_menu(struct lb_cfr *cfr_root, struct sm_obj_form *sm_root[]
current += cfr_root->size;
while (sm_root && sm_root[i])
current += sm_write_form(current, sm_root[i++], 0, 0);
current += sm_write_form(current, sm_root[i++], 0, 0, NULL, 0);
/*
* Add generic forms.
*/
for (obj = &_cfr_forms[0]; obj != &_ecfr_forms[0]; obj++)
current += sm_write_form(current, obj, 0, 0);
current += sm_write_form(current, obj, 0, 0, NULL, 0);
cfr_root->size = cfr_record_size((char *)cfr_root, current);

View file

@ -75,6 +75,8 @@ enum sm_object_kind {
struct sm_object {
enum sm_object_kind kind;
const struct sm_object *dep;
const uint32_t *dep_values;
const uint32_t num_dep_values;
void (*ctor)(const struct sm_object *obj, struct sm_object *new); /* Called on object creation */
union {
struct sm_obj_enum sm_enum;
@ -87,21 +89,31 @@ struct sm_object {
};
/* sm_object helpers with type checking */
#define SM_DECLARE_ENUM(...) { .kind = SM_OBJ_ENUM, .dep = NULL, \
#define SM_DECLARE_ENUM(...) { .kind = SM_OBJ_ENUM, .dep = NULL, \
.dep_values = NULL, .num_dep_values = 0, \
.ctor = NULL, .sm_enum = __VA_ARGS__ }
#define SM_DECLARE_NUMBER(...) { .kind = SM_OBJ_NUMBER, .dep = NULL, \
#define SM_DECLARE_NUMBER(...) { .kind = SM_OBJ_NUMBER, .dep = NULL, \
.dep_values = NULL, .num_dep_values = 0, \
.ctor = NULL, .sm_number = __VA_ARGS__ }
#define SM_DECLARE_BOOL(...) { .kind = SM_OBJ_BOOL, .dep = NULL, \
#define SM_DECLARE_BOOL(...) { .kind = SM_OBJ_BOOL, .dep = NULL, \
.dep_values = NULL, .num_dep_values = 0, \
.ctor = NULL, .sm_bool = __VA_ARGS__ }
#define SM_DECLARE_VARCHAR(...) { .kind = SM_OBJ_VARCHAR, .dep = NULL, \
#define SM_DECLARE_VARCHAR(...) { .kind = SM_OBJ_VARCHAR, .dep = NULL, \
.dep_values = NULL, .num_dep_values = 0, \
.ctor = NULL, .sm_varchar = __VA_ARGS__ }
#define SM_DECLARE_COMMENT(...) { .kind = SM_OBJ_COMMENT, .dep = NULL, \
#define SM_DECLARE_COMMENT(...) { .kind = SM_OBJ_COMMENT, .dep = NULL, \
.dep_values = NULL, .num_dep_values = 0, \
.ctor = NULL, .sm_comment = __VA_ARGS__ }
#define SM_DECLARE_FORM(...) { .kind = SM_OBJ_FORM, .dep = NULL, \
#define SM_DECLARE_FORM(...) { .kind = SM_OBJ_FORM, .dep = NULL, \
.dep_values = NULL, .num_dep_values = 0, \
.ctor = NULL, .sm_form = __VA_ARGS__ }
#define WITH_CALLBACK(c) .ctor = (c)
#define WITH_DEP(d) .dep = (d)
#define WITH_DEP_VALUES(d, ...) \
.dep = (d), \
.dep_values = ((const uint32_t[]) { __VA_ARGS__ }), \
.num_dep_values = sizeof((uint32_t[]) { __VA_ARGS__ }) / sizeof(uint32_t)
void cfr_write_setup_menu(struct lb_cfr *cfr_root, struct sm_obj_form *sm_root[]);