diff --git a/Documentation/drivers/cfr.md b/Documentation/drivers/cfr.md new file mode 100644 index 0000000000..ade6d2b14a --- /dev/null +++ b/Documentation/drivers/cfr.md @@ -0,0 +1,297 @@ +# CFR - coreboot form representation + +This documents the API exposed by coreboot to be consumed by +loaded OS image or payload. + +## Problem Statement + +As per coreboot design there's no UI present to change firmware +related options like "Hyper-Theading Enable". There's no way of +knowing what options are supported, if they are supported in the +current configuration and what they do. + +The `USE_OPTION_TABLE` Kconfig allows to integrate a list of +mainboard specific options into coreboot tables when the option +API is using the *CMOS NVRAM*. It has no meaning if another option +API is being used. + +## Design Proposal + +Propose a new coreboot table that is independent from the option +backend. The coreboot table is generated from coreboot ramstage +code. + +Every possible boot option is described by its name, the user +visible name, a help text, a default value and status flags. +All strings are in ASCII. + +The boot options are grouped into forms, where each form hold +one ore more options. Boot options that are not used in the current +boot flow, and are never reachable should be marked as hidden. +Dependecies between options can be defined in the code and should +be evaluated by the CFR parser/UI. + +A boot option can be one of the following types: +- boolean +- number +- enum +- string + +All of the information is *Position Independent Data*. That is, it is +safe to relocate any of the information without its meaning/correctness +changing. + +CFR records form a tree structure. Every record starts with a `tag` +and a `size` field as generic header: + +```C +struct __packed lb_cfr_header { + uint32_t tag; + uint32_t size; +}; +``` + +The size of a record includes the size of its own fields plus the size of all +child records. A record can have none or multiple child records. +The record `tag` must be known by the parser to parse the record and its +sub records. If it is not known to the parser it can simply skip it by +jumping `size` bytes forward. + +The coreboot table containing the CFR tree has the tag `LB_TAG_CFR`. + +The public API can be found in +`src/commonlib/include/commonlib/cfr.h` and +`src/commonlib/include/commonlib/coreboot_tables.h`. + +## Implementation design +### Tags +Tags identify the structure defined in `src/commonlib/include/commonlib/cfr.h`. +Every struct might be immideatly followed by additional structs (so called +sub nodes), having their own tag and size field. The sum of all sub nodes size +fields plus the size of the struct itself equals the size field. + +* CFR_TAG_OPTION_FORM + + Used in `struct lb_cfr_option_form` to describe a group of options. Every + sub node is one option that should be displayed in the order found in + memory. + + Allowed sub nodes: + - `CFR_TAG_OPTION_ENUM` + - `CFR_TAG_OPTION_NUMBER` + - `CFR_TAG_OPTION_BOOL` + - `CFR_TAG_OPTION_VARCHAR` + - `CFR_TAG_OPTION_FORM` + - `CFR_TAG_OPTION_COMMENT` + - `CFR_TAG_VARCHAR_UI_NAME` + + Required sub nodes: + - `CFR_TAG_VARCHAR_UI_NAME` + +* CFR_TAG_ENUM_VALUE + + Used in `struct lb_cfr_enum_value` to describe a numeric value to be + used in a parent `CFR_TAG_OPTION_ENUM`. + + Allowed sub nodes: + - `CFR_TAG_VARCHAR_UI_NAME` + + Required sub nodes: + - `CFR_TAG_VARCHAR_UI_NAME` + +* CFR_TAG_OPTION_ENUM + + Used in `struct lb_cfr_numeric_option` to describe a numeric variable with + a predefined selection of possible values in the referenced variable. + + Allowed sub nodes: + - `CFR_TAG_VARCHAR_OPT_NAME` + - `CFR_TAG_VARCHAR_UI_NAME` + - `CFR_TAG_ENUM_VALUE` + - `CFR_TAG_VARCHAR_UI_HELPTEXT` + + Required sub nodes: + - `CFR_TAG_VARCHAR_OPT_NAME` + - `CFR_TAG_VARCHAR_UI_NAME` + - `CFR_TAG_ENUM_VALUE` + +* CFR_TAG_OPTION_NUMBER + + Used in `struct lb_cfr_numeric_option` to describe a numeric variable with + any possible value in the referenced variable. + + Allowed sub nodes: + - `CFR_TAG_VARCHAR_OPT_NAME` + - `CFR_TAG_VARCHAR_UI_NAME` + - `CFR_TAG_VARCHAR_UI_HELPTEXT` + + Required sub nodes: + - `CFR_TAG_VARCHAR_OPT_NAME` + - `CFR_TAG_VARCHAR_UI_NAME` + +* CFR_TAG_OPTION_BOOL + + Used in `struct lb_cfr_numeric_option` to describe a numeric variable with + the possible values [0, 1] in the referenced variable. + + Allowed sub nodes: + - `CFR_TAG_VARCHAR_OPT_NAME` + - `CFR_TAG_VARCHAR_UI_NAME` + - `CFR_TAG_VARCHAR_UI_HELPTEXT` + + Required sub nodes: + - `CFR_TAG_VARCHAR_OPT_NAME` + - `CFR_TAG_VARCHAR_UI_NAME` + +* CFR_TAG_OPTION_VARCHAR + + Used in `struct lb_cfr_varchar_option` to describe an ASCII string + stored in the referenced variable. + + *Example:* Linux kernel cmdline. + + Allowed sub nodes: + - `CFR_TAG_VARCHAR_DEF_VALUE` + - `CFR_TAG_VARCHAR_OPT_NAME` + - `CFR_TAG_VARCHAR_UI_NAME` + - `CFR_TAG_VARCHAR_UI_HELPTEXT` + + Required sub nodes: + - `CFR_TAG_VARCHAR_DEF_VALUE` + - `CFR_TAG_VARCHAR_OPT_NAME` + - `CFR_TAG_VARCHAR_UI_NAME` + +* CFR_TAG_OPTION_COMMENT + + Used in `struct lb_cfr_option_comment` to describe an ASCII string visible + to the user, but doesn't reference a variable. Informal use only. + + Allowed sub nodes: + - `CFR_TAG_VARCHAR_UI_NAME` + - `CFR_TAG_VARCHAR_UI_HELPTEXT` + + Required sub nodes: + - `CFR_TAG_VARCHAR_UI_NAME` + +* CFR_TAG_VARCHAR_OPT_NAME + + Used in `struct lb_cfr_varbinary` to describe the option name used by + coreboot's code. It thus must match what is used in code by + `get_uint_option()`. + + Is not user visible. + +* CFR_TAG_VARCHAR_UI_NAME + + Used in `struct lb_cfr_varbinary` + + User visible name of the option. + +* CFR_TAG_VARCHAR_UI_HELPTEXT + + Used in `struct lb_cfr_varbinary` + + Optional user visible description what is changed by this option. + +* CFR_TAG_VARCHAR_DEF_VALUE + + Used in `struct lb_cfr_varbinary` + + Default value in case the variable is not present. + +### Flags + +The optional flags describe the visibilty of the option and the +effect on the non-volatile variable. + +* `CFR_OPTFLAG_READONLY` + + Prevents writes to the variable. + +* `CFR_OPTFLAG_GRAYOUT` + + Implies `READONLY`. The option is visible, but cannot be modified + because one of the dependencies are not given. However there's a + possibility to enable the option by changing runtime configuration. + + *For example:* Setting SATA mode, but SATA is globally disabled. + +* `CFR_OPTFLAG_SUPPRESS` + + Runtime code sets this flag to indicate that the option has no effect + and is never reachable, not even by changing runtime configuration. + This option is never shown in the UI. + +* `CFR_OPTFLAG_VOLATILE` + + Implies `READONLY`. + The option is not backed by a non-volatile variable. This is useful + to display the current state of a specific component, a dependency or + a serial number. This information could be passed in a new coreboot + table, but it not useful other than to be shown at this spot in the + UI. + +* `CFR_OPTFLAG_RUNTIME` + + The option is allowed to be changed by a post payload entity. On UEFI + this sets the `EFI_VARIABLE_RUNTIME_ACCESS` attribute. + It is out of scope of this specification how non runtime variables + are protected after the payload has hand over control. + +### Example + +To display a boolean option with the label `Boolean`, that default value +is `true`, on a form called `test`, that modifies the variable `First` +the following structure will be generated: + +``` +struct lb_cfr_option_form { + uint32_t tag; = CFR_TAG_OPTION_FORM + uint32_t size; = sizeof(struct lb_cfr_option_form) + + sizeof(struct lb_cfr_varbinary) + + strlen(name) + 1 + 3 + + sizeof(struct lb_cfr_numeric_option) + + sizeof(struct lb_cfr_varbinary) + + strlen(optname) + 1 + 2 + + sizeof(struct lb_cfr_varbinary) + + strlen(uiname) + 1 = 120 + uint64_t object_id; = 1 + uint64_t dependency_id; = 0 + uint32_t flags; = 0 +} + struct lb_cfr_varbinary { + uint32_t tag; = CFR_TAG_VARCHAR_UI_NAME + uint32_t size; = sizeof(struct lb_cfr_varbinary) + + strlen(name) + 1 + 3 = 20 + uint32_t data_length; = strlen(name) + 1 + }; + char name[5]; = "test" + char padding[3]; + struct lb_cfr_numeric_option { + uint32_t tag; = CFR_TAG_OPTION_BOOL + uint32_t size; = sizeof(struct lb_cfr_numeric_option) + + sizeof(struct lb_cfr_varbinary) + + strlen(optname) + 1 + 2 + + sizeof(struct lb_cfr_varbinary) + + strlen(uiname) + 1 = 72 + uint64_t object_id; = 2 + uint64_t dependency_id; = 0 + uint32_t flags; = 0 + uint32_t default_value; = true + }; + struct lb_cfr_varbinary { + uint32_t tag; = CFR_TAG_VARCHAR_OPT_NAME + uint32_t size; = sizeof(struct lb_cfr_varbinary) + + strlen(optname) + 1 + 2 = 20 + uint32_t data_length; = strlen(optname) + 1 = 6 + }; + char optname[6]; = "First" + char padding[2]; + struct lb_cfr_varbinary { + uint32_t tag; = CFR_TAG_VARCHAR_UI_NAME + uint32_t size; = sizeof(struct lb_cfr_varbinary) + + strlen(uiname) + 1 = 20 + uint32_t data_length; = strlen(uiname) + 1 = 8 + }; + char uiname[8]; = "Boolean" +``` \ No newline at end of file diff --git a/Documentation/drivers/cfr_internal.md b/Documentation/drivers/cfr_internal.md new file mode 100644 index 0000000000..95fef66eee --- /dev/null +++ b/Documentation/drivers/cfr_internal.md @@ -0,0 +1,114 @@ +# CFR - coreboot form representation - Internals + +This documents the API internally used by coreboot to be used +by ramstage code. + +Please read [CFR](cfr.md) first. + +## Enabling CFR support + +Users should select `DRIVERS_OPTION_CFR` in Kconfig to enable CFR +support. Mainboards should select `DRIVERS_OPTION_CFR_ENABLED` to +enable `DRIVERS_OPTION_CFR` by default. + +## Using CFR + +When CFR support is enabled there are two possibilites to generate +the records: + +- mainboard specific code +- automatic collection + +In both cases you have to add `C` structs in ramstage to describe the +option and group them together into a form. CFR objects should reside +on the heap as they can be modified to match the current boot flow. + +### Updating CFR options + +The CFR options should be updated before tables are written. +You can use a callback, using the `WITH_CALLBACK()` macro, to update +single or multiple options when the CFR table is written into the +coreboot table. + +**Example:** Updates the option serial_number with the contents from the +EMI eeprom. + +``` +static void update_serial(const struct sm_object *obj, struct sm_object *new) +{ + new->sm_varchar.default_value = get_emi_eeprom_vpd()->serial_number; +} + +static const struct sm_object serial_number = SM_DECLARE_VARCHAR({ + .flags = CFR_OPTFLAG_READONLY | CFR_OPTFLAG_VOLATILE, + .opt_name = "serial_number", + .ui_name = "Serial Number", +}, WITH_CALLBACK(update_serial)); +``` + +### Dependencies in CFR options + +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. + +**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. +When the user changes "sata_enable" to 1 or it was 1 then the option +`sata_disable_port0` should be visible. + +``` +static struct sm_object sata_enable = SM_DECLARE_BOOL({ + .flags = CFR_OPTFLAG_RUNTIME, + .opt_name = "sata_enable", + .ui_name = "Enable SATA controller", + .ui_helptext = NULL, + .default_value = true, +}); + +static struct sm_object sata_disable_port0 = SM_DECLARE_BOOL({ + .flags = CFR_OPTFLAG_RUNTIME, + .opt_name = "sata_disable_port0", + .ui_name = "Disable SATA port #0", + .ui_helptext = NULL, + .default_value = false, +}, WITH_DEP(&sata_enable)); +``` + +### Providing mainboard custom options + +A mainboard that uses CFR can provide a list of custom options +be overwriting the weak `void mb_cfr_setup_menu(struct lb_cfr *cfr_root);` +function in ramstage. + +### Automatic CFR collection + +CFR forms that have the `__cfr_form` attribute are automatically collected +and inserted into the coreboot table. + +## Example + +The following CFR form `southbridge` will be automatically added to the +coreboot table and it will have a single option called `Enable NMI` that +allows the variable `nmi` to be changed to *0* or *1*. + +**Example:** +```C +static struct sm_object nmi = SM_DECLARE_BOOL({ + .flags = CFR_OPTFLAG_RUNTIME, + .opt_name = "nmi", + .ui_name = "Enable NMI", + .ui_helptext = NULL, + .default_value = false, +}); + +static const __cfr_form struct sm_obj_form southbridge = { + .flags = 0, + .ui_name = "Southbridge", + .obj_list = (const struct sm_object *[]) { + &nmi, + NULL + }, +}; +``` \ No newline at end of file diff --git a/Documentation/drivers/index.md b/Documentation/drivers/index.md index efbc186e75..45c491bbaa 100644 --- a/Documentation/drivers/index.md +++ b/Documentation/drivers/index.md @@ -18,6 +18,8 @@ Some of the drivers currently available include: ```{toctree} :maxdepth: 1 +CFR +CFR use within coreboot Intel DPTF IPMI KCS SMMSTORE diff --git a/src/commonlib/include/commonlib/cfr.h b/src/commonlib/include/commonlib/cfr.h new file mode 100644 index 0000000000..22058e96f9 --- /dev/null +++ b/src/commonlib/include/commonlib/cfr.h @@ -0,0 +1,196 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ + +#ifndef COMMONLIB_OPTION_CFR_H +#define COMMONLIB_OPTION_CFR_H + +#include + +/* + * PUBLIC API + * + * The CFR table information is for conveying available boot + * option of the firmware to the loaded OS image or payload. + * Primarily this is expected to be information that is user + * visible. + * + * The boot options are grouped into forms, where each form hold + * one ore more options. + * Boot options that are not used in the current boot flow, but + * are reachable under certain conditions should be marked as hidden. + * + * A boot option can be one of the following types: + * - boolean + * - number + * - enum + * - string + * + * Each boot option has an UI name and UI help text that is free to + * choose. All strings are in ASCII. + * + * All of the information should be Position Independent Data. + * That is it should be safe to relocated any of the information + * without it's meaning/correctness changing. For table that + * can reasonably be used on multiple architectures the data + * size should be fixed. This should ease the transition between + * 32 bit and 64 bit architectures etc. + * + * CFR records form a tree structure. The size of a record includes + * the size of its own fields plus the size of all children records. + * CFR tags can appear multiple times except for `LB_TAG_CFR` which + * is used for the root record. + * + * The following structures have comments that describe the supported + * children records. These comments cannot be replaced with code! The + * structures are variable-length, so the offsets won't be valid most + * of the time. Besides, the implementation uses `sizeof()` to obtain + * the size of the "record header" (the fixed-length members); adding + * the children structures as struct members will increase the length + * returned by `sizeof()`, which complicates things for zero reason. + * + */ + +enum cfr_tags { + CFR_TAG_OPTION_FORM = 1, + CFR_TAG_ENUM_VALUE = 2, + CFR_TAG_OPTION_ENUM = 3, + CFR_TAG_OPTION_NUMBER = 4, + CFR_TAG_OPTION_BOOL = 5, + CFR_TAG_OPTION_VARCHAR = 6, + CFR_TAG_VARCHAR_OPT_NAME = 7, + CFR_TAG_VARCHAR_UI_NAME = 8, + CFR_TAG_VARCHAR_UI_HELPTEXT = 9, + CFR_TAG_VARCHAR_DEF_VALUE = 10, + CFR_TAG_OPTION_COMMENT = 11, +}; + +/* + * The optional flags describe the visibilty of the option and the + * effect on the non-volatile variable. + * CFR_OPTFLAG_READONLY: + * Prevents writes to the variable. + * CFR_OPTFLAG_GRAYOUT: + * Implies READONLY. The option is visible, but cannot be modified + * because one of the dependencies are not given. However there's a + * possibility to enable the option by changing runtime configuration. + * + * For example: Setting SATA mode, but SATA is globally disabled. + * CFR_OPTFLAG_SUPPRESS: + * Runtime code sets this flag to indicate that the option has no effect + * and is never reachable, not even by changing runtime configuration. + * This option is never shown in the UI. + * CFR_OPTFLAG_VOLATILE: + * Implies READONLY. + * The option is not backed by a non-volatile variable. This is useful + * to display the current state of a specific component, a dependency or + * a serial number. This information could be passed in a new coreboot + * table, but it not useful other than to be shown at this spot in the + * UI. + * CFR_OPTFLAG_RUNTIME: + * The option is allowed to be changed by a post payload entity. On UEFI + * this sets the EFI_VARIABLE_RUNTIME_ACCESS attribute. + * It is out of scope of this specification how non runtime variables + * are protected after the payload has hand over control. + */ +enum cfr_option_flags { + CFR_OPTFLAG_READONLY = 1 << 0, + CFR_OPTFLAG_GRAYOUT = 1 << 1, + CFR_OPTFLAG_SUPPRESS = 1 << 2, + CFR_OPTFLAG_VOLATILE = 1 << 3, + CFR_OPTFLAG_RUNTIME = 1 << 4, +}; + +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 + */ + uint32_t size; /* Length of the entire structure */ + uint32_t data_length; /* Length of data, including NULL terminator for strings */ +}; + +struct __packed lb_cfr_enum_value { + uint32_t tag; /* CFR_TAG_ENUM_VALUE */ + uint32_t size; + uint32_t value; + /* + * struct lb_cfr_varbinary ui_name + */ +}; + +/* Supports multiple option types: ENUM, NUMBER, BOOL */ +struct __packed lb_cfr_numeric_option { + uint32_t tag; /* + * CFR_TAG_OPTION_ENUM, CFR_TAG_OPTION_NUMBER or + * CFR_TAG_OPTION_BOOL + */ + uint32_t size; + uint64_t object_id; /* Uniqueue ID */ + uint64_t dependency_id; /* Grayout if value of lb_cfr_numeric_option with given ID is 0. + * Ignore if field is 0. + */ + uint32_t flags; /* enum cfr_option_flags */ + uint32_t default_value; + /* + * struct lb_cfr_varbinary opt_name + * struct lb_cfr_varbinary ui_name + * struct lb_cfr_varbinary ui_helptext (Optional) + * struct lb_cfr_enum_value enum_values[] + */ +}; + +struct __packed lb_cfr_varchar_option { + uint32_t tag; /* CFR_TAG_OPTION_VARCHAR */ + uint32_t size; + uint64_t object_id; /* Uniqueue ID */ + uint64_t dependency_id; /* Grayout if value of lb_cfr_numeric_option with given ID is 0. + * Ignore if field is 0. + */ + uint32_t flags; /* enum cfr_option_flags */ + /* + * struct lb_cfr_varbinary default_value + * struct lb_cfr_varbinary opt_name + * struct lb_cfr_varbinary ui_name + * struct lb_cfr_varbinary ui_helptext (Optional) + */ +}; + +/* + * A CFR option comment is roughly equivalent to a Kconfig comment. + * Option comments are *NOT* string options (see CFR_OPTION_VARCHAR + * instead) but they're considered an option for simplicity's sake. + */ +struct __packed lb_cfr_option_comment { + uint32_t tag; /* CFR_TAG_OPTION_COMMENT */ + uint32_t size; + uint64_t object_id; /* Uniqueue ID */ + uint64_t dependency_id; /* Grayout if value of lb_cfr_numeric_option with given ID is 0. + * Ignore if field is 0. + */ + uint32_t flags; /* enum cfr_option_flags */ + /* + * struct lb_cfr_varbinary ui_name + * struct lb_cfr_varbinary ui_helptext (Optional) + */ +}; + +/* CFR forms are considered options as they can be nested inside other forms */ +struct __packed lb_cfr_option_form { + uint32_t tag; /* CFR_TAG_OPTION_FORM */ + uint32_t size; + uint64_t object_id; /* Uniqueue ID */ + uint64_t dependency_id; /* Grayout if value of lb_cfr_numeric_option with given ID is 0. + * Ignore if field is 0. + */ + uint32_t flags; /* enum cfr_option_flags */ + /* + * struct lb_cfr_varbinary ui_name + * struct lb_cfr_varchar_option options[] + */ +}; + +struct __packed lb_cfr_header { + uint32_t tag; + uint32_t size; +}; + +#endif /* DRIVERS_OPTION_CFR_H */ diff --git a/src/commonlib/include/commonlib/coreboot_tables.h b/src/commonlib/include/commonlib/coreboot_tables.h index 7cbc7da478..def48b7797 100644 --- a/src/commonlib/include/commonlib/coreboot_tables.h +++ b/src/commonlib/include/commonlib/coreboot_tables.h @@ -89,6 +89,7 @@ enum { LB_TAG_PCIE = 0x0044, LB_TAG_EFI_FW_INFO = 0x0045, LB_TAG_CAPSULE = 0x0046, + LB_TAG_CFR_ROOT = 0x0047, /* The following options are CMOS-related */ LB_TAG_CMOS_OPTION_TABLE = 0x00c8, LB_TAG_OPTION = 0x00c9, @@ -598,5 +599,11 @@ struct lb_efi_fw_info { uint32_t lowest_supported_version; /* Lowest allowed version for downgrades */ uint32_t fw_size; /* Size of firmware in bytes */ } __packed; +struct lb_cfr { + uint32_t tag; + uint32_t size; + uint32_t checksum; /* Checksum of the variable payload. */ + /* struct lb_cfr_option_form forms[] */ +}; #endif diff --git a/src/drivers/option/Kconfig b/src/drivers/option/Kconfig new file mode 100644 index 0000000000..6c63bb49b0 --- /dev/null +++ b/src/drivers/option/Kconfig @@ -0,0 +1,6 @@ +config DRIVERS_OPTION_CFR_ENABLED + def_bool n + +config DRIVERS_OPTION_CFR + bool "Support generating a CFR list of options" + default y if DRIVERS_OPTION_CFR_ENABLED diff --git a/src/drivers/option/Makefile.mk b/src/drivers/option/Makefile.mk new file mode 100644 index 0000000000..7ca439db78 --- /dev/null +++ b/src/drivers/option/Makefile.mk @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only + +ramstage-$(CONFIG_DRIVERS_OPTION_CFR) += cfr.c diff --git a/src/drivers/option/cfr.c b/src/drivers/option/cfr.c new file mode 100644 index 0000000000..b323b4318a --- /dev/null +++ b/src/drivers/option/cfr.c @@ -0,0 +1,330 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static uint32_t cfr_record_size(const char *startp, const char *endp) +{ + const uintptr_t start = (uintptr_t)startp; + const uintptr_t end = (uintptr_t)endp; + + if (start > end || end - start > UINT32_MAX) { + /* + * Should never be reached unless something went really + * wrong. Record size can never be negative, and things + * would break long before record length exceeds 4 GiB. + */ + die("%s: bad record size (start = %" PRIxPTR ", end = %" PRIxPTR ")", + __func__, start, end); + } + return (uint32_t)(end - start); +} + +static uint32_t write_cfr_varchar(char *current, const char *string, uint32_t tag) +{ + uint8_t *data; + size_t padding; + + ASSERT(string); + if (!string) + return 0; + + struct lb_cfr_varbinary *cfr_str = (struct lb_cfr_varbinary *)current; + cfr_str->tag = tag; + cfr_str->data_length = strlen(string) + 1; + char *data = current + sizeof(*cfr_str); + memcpy(data, string, cfr_str->data_length); + + /* Make sure that every TAG/SIZE field is always aligned to LB_ENTRY_ALIGN */ + cfr_str->size = ALIGN_UP(sizeof(*cfr_str) + cfr_str->data_length, LB_ENTRY_ALIGN); + + return cfr_str->size; +} + +static uint32_t sm_write_string_default_value(char *current, const char *string) +{ + return write_cfr_varchar(current, string ? string : "", CFR_TAG_VARCHAR_DEF_VALUE); +} + +static uint32_t sm_write_opt_name(char *current, const char *string) +{ + return write_cfr_varchar(current, string, CFR_TAG_VARCHAR_OPT_NAME); +} + +static uint32_t sm_write_ui_name(char *current, const char *string) +{ + return write_cfr_varchar(current, string, CFR_TAG_VARCHAR_UI_NAME); +} + +static uint32_t sm_write_ui_helptext(char *current, const char *string) +{ + /* UI Helptext is optional, return if nothing to display */ + if (!string || !strlen(string)) + return 0; + + return write_cfr_varchar(current, string, CFR_TAG_VARCHAR_UI_HELPTEXT); +} + +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; + enum_val->tag = CFR_TAG_ENUM_VALUE; + enum_val->value = e->value; + enum_val->size = sizeof(*enum_val); + + current += enum_val->size; + current += sm_write_ui_name(current, e->ui_name); + + enum_val->size = cfr_record_size((char *)enum_val, current); + return enum_val->size; +} + +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) +{ + struct lb_cfr_numeric_option *option = (struct lb_cfr_numeric_option *)current; + size_t len; + + option->tag = tag; + option->object_id = object_id; + option->dependency_id = dep_id; + option->flags = flags; + if (option->flags & (CFR_OPTFLAG_GRAYOUT | CFR_OPTFLAG_VOLATILE)) + option->flags |= CFR_OPTFLAG_READONLY; + option->default_value = default_value; + option->size = sizeof(*option); + + current += option->size; + len = sm_write_opt_name(current, opt_name); + if (!len) + return 0; + current += len; + len = sm_write_ui_name(current, ui_name); + if (!len) + return 0; + current += len; + current += sm_write_ui_helptext(current, ui_helptext); + + if (option->tag == CFR_TAG_OPTION_ENUM && values) { + for (const struct sm_enum_value *e = values; e->ui_name; e++) { + current += sm_write_enum_value(current, e); + } + } + + option->size = cfr_record_size((char *)option, current); + return option->size; +} + +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) + +{ + 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); +} + +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) + +{ + 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); +} + +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) + +{ + 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); +} + +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) + +{ + struct lb_cfr_varchar_option *option = (struct lb_cfr_varchar_option *)current; + size_t len; + + option->tag = CFR_TAG_OPTION_VARCHAR; + option->object_id = object_id; + option->dependency_id = dep_id; + option->flags = sm_varchar->flags; + if (option->flags & (CFR_OPTFLAG_GRAYOUT | CFR_OPTFLAG_VOLATILE)) + option->flags |= CFR_OPTFLAG_READONLY; + option->size = sizeof(*option); + + current += option->size; + current += sm_write_string_default_value(current, sm_varchar->default_value); + len = sm_write_opt_name(current, sm_varchar->opt_name); + if (!len) + return 0; + current += len; + len = sm_write_ui_name(current, sm_varchar->ui_name); + if (!len) + return 0; + current += len; + current += sm_write_ui_helptext(current, sm_varchar->ui_helptext); + + 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) +{ + struct lb_cfr_option_comment *comment = (struct lb_cfr_option_comment *)current; + size_t len; + + comment->tag = CFR_TAG_OPTION_COMMENT; + comment->object_id = object_id; + comment->dependency_id = dep_id; + comment->flags = sm_comment->flags; + if (comment->flags & (CFR_OPTFLAG_GRAYOUT | CFR_OPTFLAG_VOLATILE)) + comment->flags |= CFR_OPTFLAG_READONLY; + comment->size = sizeof(*comment); + + current += comment->size; + len = sm_write_ui_name(current, sm_comment->ui_name); + if (!len) + return 0; + current += len; + current += sm_write_ui_helptext(current, sm_comment->ui_helptext); + + comment->size = cfr_record_size((char *)comment, current); + return comment->size; +} + +static uint64_t sm_gen_obj_id(void *ptr) +{ + uintptr_t id = (uintptr_t)ptr; + /* Convert pointer to unique ID */ + return id ^ 0xffffcafecafecafe; +} + +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) +{ + struct lb_cfr_option_form *form = (struct lb_cfr_option_form *)current; + size_t len; + size_t i = 0; + + form->tag = CFR_TAG_OPTION_FORM; + form->object_id = object_id; + form->dependency_id = dep_id; + form->flags = sm_form->flags; + if (form->flags & (CFR_OPTFLAG_GRAYOUT | CFR_OPTFLAG_VOLATILE)) + form->flags |= CFR_OPTFLAG_READONLY; + form->size = sizeof(*form); + + current += form->size; + len = sm_write_ui_name(current, sm_form->ui_name); + if (!len) + return 0; + current += len; + + while (sm_form->obj_list[i]) + current += sm_write_object(current, sm_form->obj_list[i++]); + + form->size = cfr_record_size((char *)form, current); + return form->size; +} + +static uint32_t sm_write_object(char *current, const struct sm_object *sm_obj) +{ + uint64_t dep_id, obj_id; + struct sm_object sm_obj_copy; + assert(sm_obj); + + /* Assign uniqueue ID */ + obj_id = sm_gen_obj_id((void *)sm_obj); + + /* Set dependency ID */ + dep_id = 0; + if (sm_obj->dep) { + assert(sm_obj->dep->kind == SM_OBJ_BOOL); + if (sm_obj->dep->kind == SM_OBJ_BOOL) + dep_id = sm_gen_obj_id((void *)sm_obj->dep); + } + + /* Invoke callback to update fields */ + if (sm_obj->ctor) { + memcpy(&sm_obj_copy, sm_obj, sizeof(*sm_obj)); + sm_obj->ctor(sm_obj, &sm_obj_copy); + + assert(sm_obj->kind == sm_obj_copy.kind); + sm_obj = (const struct sm_object *)&sm_obj_copy; + } + + switch (sm_obj->kind) { + case SM_OBJ_NONE: + return 0; + case SM_OBJ_ENUM: + return sm_write_opt_enum(current, &sm_obj->sm_enum, obj_id, + dep_id); + case SM_OBJ_NUMBER: + return sm_write_opt_number(current, &sm_obj->sm_number, obj_id, + dep_id); + case SM_OBJ_BOOL: + return sm_write_opt_bool(current, &sm_obj->sm_bool, obj_id, + dep_id); + case SM_OBJ_VARCHAR: + return sm_write_opt_varchar(current, &sm_obj->sm_varchar, obj_id, + dep_id); + case SM_OBJ_COMMENT: + return sm_write_opt_comment(current, &sm_obj->sm_comment, obj_id, + dep_id); + case SM_OBJ_FORM: + return sm_write_form(current, (struct sm_obj_form *)&sm_obj->sm_form, obj_id, dep_id); + default: + BUG(); + printk(BIOS_ERR, "Unknown setup menu object kind %u, ignoring\n", sm_obj->kind); + return 0; + } +} + +void cfr_write_setup_menu(struct lb_cfr *cfr_root, struct sm_obj_form *sm_root[]) +{ + void *current = cfr_root; + struct sm_obj_form *obj; + size_t i = 0; + + ASSERT(cfr_root); + if (!cfr_root) + return; + + cfr_root->tag = LB_TAG_CFR_ROOT; + cfr_root->size = sizeof(*cfr_root); + + current += cfr_root->size; + while (sm_root && sm_root[i]) + current += sm_write_form(current, sm_root[i++], 0, 0); + + /* + * Add generic forms. + */ + for (obj = &_cfr_forms[0]; obj != &_ecfr_forms[0]; obj++) + current += sm_write_form(current, obj, 0, 0); + + cfr_root->size = cfr_record_size((char *)cfr_root, current); + + cfr_root->checksum = CRC(cfr_root + 1, cfr_root->size - sizeof(*cfr_root), crc32_byte); + + printk(BIOS_DEBUG, "CFR: Written %u bytes of CFR structures at %p, with CRC32 0x%08x\n", + cfr_root->size, cfr_root, cfr_root->checksum); +} diff --git a/src/drivers/option/cfr_frontend.h b/src/drivers/option/cfr_frontend.h new file mode 100644 index 0000000000..2161853f01 --- /dev/null +++ b/src/drivers/option/cfr_frontend.h @@ -0,0 +1,119 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef DRIVERS_OPTION_CFR_H +#define DRIVERS_OPTION_CFR_H + +#include +#include +#include + +/* Front-end */ +struct sm_enum_value { + const char *ui_name; + uint32_t value; +}; + +#define SM_ENUM_VALUE_END ((struct sm_enum_value) {0}) + +struct sm_obj_enum { + uint32_t flags; /* enum cfr_option_flags */ + const char *opt_name; + const char *ui_name; + const char *ui_helptext; + uint32_t default_value; + const struct sm_enum_value *values; +}; + +struct sm_obj_number { + uint32_t flags; /* enum cfr_option_flags */ + const char *opt_name; + const char *ui_name; + const char *ui_helptext; + uint32_t default_value; +}; + +struct sm_obj_bool { + uint32_t flags; /* enum cfr_option_flags */ + const char *opt_name; + const char *ui_name; + const char *ui_helptext; + bool default_value; +}; + +struct sm_obj_varchar { + uint32_t flags; /* enum cfr_option_flags */ + const char *opt_name; + const char *ui_name; + const char *ui_helptext; + const char *default_value; +}; + +struct sm_obj_comment { + uint32_t flags; /* enum cfr_option_flags */ + const char *ui_name; + const char *ui_helptext; +}; + +struct sm_object; + +struct sm_obj_form { + uint32_t flags; /* enum cfr_option_flags */ + const char *ui_name; + const struct sm_object **obj_list; /* NULL terminated */ +}; + +enum sm_object_kind { + SM_OBJ_NONE = 0, + SM_OBJ_ENUM, + SM_OBJ_NUMBER, + SM_OBJ_BOOL, + SM_OBJ_VARCHAR, + SM_OBJ_COMMENT, + SM_OBJ_FORM, +}; + +struct sm_object { + enum sm_object_kind kind; + const struct sm_object *dep; + void (*ctor)(const struct sm_object *obj, struct sm_object *new); /* Called on object creation */ + union { + struct sm_obj_enum sm_enum; + struct sm_obj_number sm_number; + struct sm_obj_bool sm_bool; + struct sm_obj_varchar sm_varchar; + struct sm_obj_comment sm_comment; + struct sm_obj_form sm_form; + }; +}; + +/* sm_object helpers with type checking */ +#define SM_DECLARE_ENUM(...) { .kind = SM_OBJ_ENUM, .dep = NULL, \ + .ctor = NULL, .sm_enum = __VA_ARGS__ } +#define SM_DECLARE_NUMBER(...) { .kind = SM_OBJ_NUMBER, .dep = NULL, \ + .ctor = NULL, .sm_number = __VA_ARGS__ } +#define SM_DECLARE_BOOL(...) { .kind = SM_OBJ_BOOL, .dep = NULL, \ + .ctor = NULL, .sm_bool = __VA_ARGS__ } +#define SM_DECLARE_VARCHAR(...) { .kind = SM_OBJ_VARCHAR, .dep = NULL, \ + .ctor = NULL, .sm_varchar = __VA_ARGS__ } +#define SM_DECLARE_COMMENT(...) { .kind = SM_OBJ_COMMENT, .dep = NULL, \ + .ctor = NULL, .sm_comment = __VA_ARGS__ } +#define SM_DECLARE_FORM(...) { .kind = SM_OBJ_FORM, .dep = NULL, \ + .ctor = NULL, .sm_form = __VA_ARGS__ } + +#define WITH_CALLBACK(c) .ctor = (c) +#define WITH_DEP(d) .dep = (d) + +void cfr_write_setup_menu(struct lb_cfr *cfr_root, struct sm_obj_form *sm_root[]); + +#if ENV_RAMSTAGE +#define __cfr_form __attribute__((used, __section__(".rodata.cfr_forms"))) +#else +#define __cfr_form __maybe_unused +#endif + +/** start of compile time generated cfr form array */ +extern struct sm_obj_form _cfr_forms[]; +/** end of compile time generated cfr form array */ +extern struct sm_obj_form _ecfr_forms[]; + +#endif /* DRIVERS_OPTION_CFR_H */ diff --git a/src/include/boot/coreboot_tables.h b/src/include/boot/coreboot_tables.h index 090889c6c9..9c258c687e 100644 --- a/src/include/boot/coreboot_tables.h +++ b/src/include/boot/coreboot_tables.h @@ -53,4 +53,7 @@ struct lb_record *lb_new_record(struct lb_header *header); /* Add VBOOT VBNV offsets. */ void lb_table_add_vbnv_cmos(struct lb_header *header); +/* Define this in mainboard.c to add board specific CFR entries */ +void mb_cfr_setup_menu(struct lb_cfr *cfr_root); + #endif /* COREBOOT_TABLES_H */ diff --git a/src/lib/coreboot_table.c b/src/lib/coreboot_table.c index e954edb822..3902c88eff 100644 --- a/src/lib/coreboot_table.c +++ b/src/lib/coreboot_table.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -346,6 +347,19 @@ static struct lb_board_config *lb_board_config(struct lb_header *header) return config; } +void __weak mb_cfr_setup_menu(struct lb_cfr *cfr_root) +{ + cfr_write_setup_menu(cfr_root, NULL); +} + +static void lb_cfr_setup_menu(struct lb_header *header) +{ + char *current = (char *)lb_new_record(header); + struct lb_cfr *cfr_root = (struct lb_cfr *)current; + + mb_cfr_setup_menu(cfr_root); +} + #if CONFIG(USE_OPTION_TABLE) static struct cmos_checksum *lb_cmos_checksum(struct lb_header *header) { @@ -491,6 +505,10 @@ static uintptr_t write_coreboot_table(uintptr_t rom_table_end) } #endif + /* Generate CFR entry for setup menus */ + if (CONFIG(DRIVERS_OPTION_CFR)) + lb_cfr_setup_menu(head); + /* Serialize resource map into mem table types (LB_MEM_*) */ bootmem_write_memory_table(lb_memory(head)); diff --git a/src/lib/program.ld b/src/lib/program.ld index b3ddc0b762..93bfb069ac 100644 --- a/src/lib/program.ld +++ b/src/lib/program.ld @@ -57,6 +57,11 @@ KEEP(*(.rodata.cpu_driver)); _ecpu_drivers = .; RECORD_SIZE(cpu_drivers) + . = ALIGN(ARCH_POINTER_ALIGN_SIZE); + _cfr_forms = .; + KEEP(*(.rodata.cfr_forms)); + _ecfr_forms = .; + RECORD_SIZE(cfr_forms) #endif . = ALIGN(ARCH_POINTER_ALIGN_SIZE);