diff --git a/Documentation/lib/fw_config.md b/Documentation/lib/fw_config.md new file mode 100644 index 0000000000..63a56dcd7b --- /dev/null +++ b/Documentation/lib/fw_config.md @@ -0,0 +1,353 @@ +# Firmware Configuration Interface in coreboot + +## Motivation + +The firmware configuration interface in coreboot is designed to support a wide variety of +configuration options in that are dictated by the hardware at runtime. This allows a single +BIOS image to be used across a wide variety of devices which may have key differences but are +otherwise similar enough to use the same coreboot build target. + +The initial implementation is designed to take advantage of a bitmask returned by the Embedded +Controller on Google Chrome OS devices which allows the manufacturer to use the same firmware +image across multiple devices by selecting various options at runtime. See the Chromium OS +[Firmware Config][1] documentation for more information. + +This firmware configuration interface differs from the CMOS option interface in that this +bitmask value is not intended as a user-configurable setting as the configuration values must +match the actual hardware. In the case where a user was to swap their hardware this value +would need to be updated or overridden. + +## Device Presence + +One common example of why a firmware configuration interface is important is determining if a +device is present in the system. With some bus topologies and hardware mechanisms it is +possible to probe and enumerate this at runtime: + +- PCI is a self-discoverable bus and is very easy to handle. +- I2C devices can often be probed with a combination of bus and address. +- The use of GPIOs with external strap to ground or different voltages can be used to detect +presence of a device. + +However there are several cases where this is insufficient: + +- I2C peripherals that require different drivers but have the same bus address cannot be +uniquely identified at runtime. +- A mainboard may be designed with multiple daughter board combinations which contain devices +and configurations that cannot be detected. +- While presence detect GPIOs are a convenient way for a single device presence, they are +unable to distinguish between different devices so it can require a large number of GPIOs to +support relatively few options. + +This presence detection can impact different stages of boot: + +### ACPI + +Devices that are not present should not provide an ACPI device indicating that they are +present or the operating system may not be able to handle it correctly. + +The ACPI devices are largely driven by chips defined in the mainboard `devicetree.cb` and +the variant overridetree.cb. This means it is important to be able to specify when a device +is present or not directly in `devicetree.cb` itself. Otherwise each mainboard needs custom +code to parse the tree and disable unused devices. + +### GPIO + +GPIOs with multiple functions may need to be configured correctly depending on the attached +device. Given the wide variety of GPIO configuration possibilities it is not feasible to +specify all combinations directly in `devicetree.cb` and it is best left to code provided by +the mainboard. + +### FSP UPD + +Enabling and disabling devices may require altering FSP UPD values that are provided to the +various stages of FSP. These options are also not easy to specify multiple times for +different configurations in `devicetree.cb` and can be provided by the mainboard as code. + +## Firmware Configuration Interface + +The firmware configuration interface can be enabled by selecting `CONFIG_FW_CONFIG` and also +providing a source for the value by defining an additional Kconfig option defined below. + +If the firmware configuration interface is disabled via Kconfig then all probe attempts will +return true. + +## Firmware Configuration Value + +The 32bit value used as the firmware configuration bitmask is meant to be determined at runtime +but could also be defined at compile time if needed. + +There are two supported sources for providing this information to coreboot. + +### CBFS + +The value can be provided with a 32bit raw value in CBFS that is read by coreboot. The value +can be set at build time but also adjusted in an existing image with `cbfstool`. + +To enable this select the `CONFIG_FW_CONFIG_CBFS` option in the build configuration and add a +raw 32bit value to CBFS with the name of the current prefix at `CONFIG_FW_PREFIX/fw_config`. + +When `fw_config_probe_device()` or `fw_config_probe()` is called it will look for the specified +file in CBFS use the value it contains when matching fields and options. + +### Embedded Controller + +Google Chrome OS devices support an Embedded Controller interface for reading and writing the +firmware configuration value, along with other board-specific information. It is possible for +coreboot to read this value at boot on systems that support this feature. + +This option is selected by default for the mainboards that use it with +`CONFIG_FW_CONFIG_CHROME_EC_CBI` and it is not typically necessary to adjust the value. It is +possible by enabling the CBFS source and coreboot will look in CBFS first for a valid value +before asking the embedded controller. + +It is also possible to adjust the value in the embedded controller *(after disabling write +protection)* with the `ectool` command in a Chrome OS environment. + +For more information on the firmware configuration field on Chrome OS devices see the Chromium +documentation for [Firmware Config][1] and [Board Info][2]. + +[1]: http://chromium.googlesource.com/chromiumos/docs/+/master/design_docs/firmware_config.md +[2]: http://chromium.googlesource.com/chromiumos/docs/+/master/design_docs/cros_board_info.md + +## Firmware Configuration Table + +The firmware configuration table itself is defined in the mainboard `devicetree.cb` with +special tokens for defining fields and options. + +The table itself is enclosed in a `fw_config` token and terminated with `end` and it contains +a mix of field and option definitions. + +Each field is defined by providing the field name and the start and end bit marking the exact +location in the bitmask. Field names must be at least three characters long in order to +satisfy the sconfig parser requirements and they must be unique with non-overlapping masks. + + field [option...] end + +For single-bit fields only one number is needed: + + field [option...] end + +Each `field` definition starts a new block that can be composed of zero or more field options, +and it is terminated with `end`. + +Inside the field block the options can be defined by providing the option name and the field +value that this option represents when the bit offsets are used to apply a mask and shift. +Option names must also be at least three characters for the sconfig parser. + + option + +It is possible for there to be multiple `fw_config` blocks and for subsequent `field` blocks +to add additional `option` definitions to the existing field. These subsequent definitions +should not provide the field bitmask as it has already been defined earlier in the file and +this is just matching an existing field by name. + + field [option...] end + +This allows a baseboard to define the major fields and options in `devicetree.cb` and a board +variant to add specific options to fields in or define new fields in the unused bitmask in +`overridetree.cb`. + +It is not possible to redefine a field mask or override the value of an existing option this +way, only to add new options to a field or new fields to the table. + +### Firmware Configuration Table Example + +In this example a baseboard defines a simple boolean feature that is enabled or disabled +depending on the value of bit 0, and a field at bits 1-2 that indicates which daughter board +is attached. + +The baseboard itself defines one daughter board and the variant adds two more possibilities. +This way each variant can support multiple possible daughter boards in addition to the one +that was defined by the baseboard. + +#### devicetree.cb + + fw_config + field FEATURE 0 + option DISABLED 0 + option ENABLED 1 + end + field DAUGHTER_BOARD 1 2 + option NONE 0 + option REFERENCE_DB 1 + end + end + +#### overridetree.cb + + fw_config + field DAUGHTER_BOARD + option VARIANT_DB_ONE 2 + option VARIANT_DB_TWO 3 + end + end + +The result of this table defined in `devicetree.cb` is a list of constants that can be used +to check if fields match the firmware configuration options determined at runtime with a +simple check of the field mask and the option value. + +#### static.h + +```c +/* field: FEATURE */ +#define FW_CONFIG_FIELD_FEATURE_NAME "FEATURE" +#define FW_CONFIG_FIELD_FEATURE_MASK 0x00000001 +#define FW_CONFIG_FIELD_FEATURE_OPTION_DISABLED_NAME "DISABLED" +#define FW_CONFIG_FIELD_FEATURE_OPTION_DISABLED_VALUE 0x00000000 +#define FW_CONFIG_FIELD_FEATURE_OPTION_ENABLED_NAME "ENABLED" +#define FW_CONFIG_FIELD_FEATURE_OPTION_ENABLED_VALUE 0x00000001 + +/* field: DAUGHTER_BOARD */ +#define FW_CONFIG_FIELD_DAUGHTER_BOARD_NAME "DAUGHTER_BOARD" +#define FW_CONFIG_FIELD_DAUGHTER_BOARD_MASK 0x00000006 +#define FW_CONFIG_FIELD_DAUGHTER_BOARD_OPTION_NONE_NAME "NONE" +#define FW_CONFIG_FIELD_DAUGHTER_BOARD_OPTION_NONE_VALUE 0x00000000 +#define FW_CONFIG_FIELD_DAUGHTER_BOARD_OPTION_REFERENCE_DB_NAME "REFERENCE_DB" +#define FW_CONFIG_FIELD_DAUGHTER_BOARD_OPTION_REFERENCE_DB_VALUE 0x00000002 +#define FW_CONFIG_FIELD_DAUGHTER_BOARD_OPTION_VARIANT_DB_ONE_NAME "VARIANT_DB_ONE" +#define FW_CONFIG_FIELD_DAUGHTER_BOARD_OPTION_VARIANT_DB_ONE_VALUE 0x00000004 +#define FW_CONFIG_FIELD_DAUGHTER_BOARD_OPTION_VARIANT_DB_TWO_NAME "VARIANT_DB_TWO" +#define FW_CONFIG_FIELD_DAUGHTER_BOARD_OPTION_VARIANT_DB_TWO_VALUE 0x00000006 +``` + +## Device Probing + +One use of the firmware configuration interface in devicetree is to allow device probing to be +specified directly with the devices themselves. A new `probe` token is introduced to allow a +device to be probed by field and option name. Multiple `probe` entries may be present for +each device and any successful probe will consider the device to be present. + +### Probing Example + +Continuing with the previous example this device would be considered present if the field +`DAUGHTER_BOARD` was set to either `VARIANT_DB_ONE` or `VARIANT_DB_TWO`: + +#### overridetree.cb + + chip drivers/generic/example + device generic 0 on + probe DAUGHTER_BOARD VARIANT_DB_ONE + probe DAUGHTER_BOARD VARIANT_DB_TWO + end + end + +If the field were set to any other option, including `NONE` and `REFERENCE_DB` and any +undefined value then the device would be disabled. + +### Probe Overrides + +When a device is declared with a probe in the baseboard `devicetree.cb` and the same device +is also present in the `overridetree.cb` then the probing information from the baseboard +is discarded and the override device must provide all necessary probing information. + +In this example a device is listed in the baseboard with `DAUGHTER_BOARD` field probing for +`REFERENCE_DB` as a field option, It is also defined as an override device with the field +probing for the `VARIANT_DB_ONE` option instead. + +In this case only the probe listed in the override is checked and a field option of +`REFERENCE_DB` will not mark this device present. If both options are desired then the +override device must list both. This allows an override device to remove a probe entry that +was defined in the baseboard. + +#### devicetree.cb + + chip drivers/generic/example + device generic 0 on + probe DAUGHTER_BOARD REFERENCE_DB + end + end + +#### overridetree.cb + + chip drivers/generic/example + device generic 0 on + probe DAUGHTER_BOARD VARIANT_DB_ONE + end + end + +### Automatic Device Probing + +At boot time the firmware configuration interface will walk the device tree and apply any +probe entries that were defined in `devicetree.cb`. This probing takes effect before the +`BS_DEV_ENUMERATE` step during the boot state machine in ramstage. + +Devices that have a probe list but do do not find a match are disabled by setting +`dev->enabled = 0` but the chip `enable_dev()` and device `enable()` handlers will still +be executed to allow any device disable code to execute. + +The result of this probe definition is to provide an array of structures describing each +field and option to check. + +#### fw_config.h + +```c +/** + * struct fw_config - Firmware configuration field and option. + * @field_name: Name of the field that this option belongs to. + * @option_name: Name of the option within this field. + * @mask: Bitmask of the field. + * @value: Value of the option within the mask. + */ +struct fw_config { + const char *field_name; + const char *option_name; + uint32_t mask; + uint32_t value; +}; +``` + +#### static.c + +```c +STORAGE struct fw_config __devN_probe_list[] = { + { + .field_name = FW_CONFIG_FIELD_DAUGHTER_BOARD_NAME, + .option_name = FW_CONFIG_FIELD_DAUGHTER_BOARD_OPTION_VARIANT_DB_ONE_NAME, + .mask = FW_CONFIG_FIELD_DAUGHTER_BOARD_MASK, + .value = FW_CONFIG_FIELD_DAUGHTER_BOARD_OPTION_VARIANT_DB_ONE_VALUE + }, + { + .field_name = FW_CONFIG_FIELD_DAUGHTER_BOARD_NAME, + .option_name = FW_CONFIG_FIELD_DAUGHTER_BOARD_OPTION_VARIANT_DB_TWO_NAME, + .mask = FW_CONFIG_FIELD_DAUGHTER_BOARD_MASK, + .value = FW_CONFIG_FIELD_DAUGHTER_BOARD_OPTION_VARIANT_DB_TWO_VALUE + }, + { } +}; +``` + +### Runtime Probing + +The device driver probing allows for seamless integration with the mainboard but it is only +effective in ramstage and for specific devices declared in devicetree.cb. There are other +situations where code may need to probe or check the value of a field in romstage or at other +points in ramstage. For this reason it is also possible to use the firmware configuration +interface directly. + +```c +/** + * fw_config_probe() - Check if field and option matches. + * @match: Structure containing field and option to probe. + * + * Return %true if match is found, %false if match is not found. + */ +bool fw_config_probe(const struct fw_config *match); +``` + +The argument provided to this function can be created from a macro for easy use: + + FW_CONFIG(field, option) + +This example has a mainboard check if a feature is disabled and set an FSP UPD before memory +training. This example expects that the default value of this `register` is set to `true` in +`devicetree.cb` and this code is disabling that feature before FSP is executed. + +```c +#include + +void mainboard_memory_init_params(FSPM_UPD *mupd) +{ + if (fw_config_probe_one(FW_CONFIG(FEATURE, DISABLED)) + mupd->ExampleFeature = false; +} +``` diff --git a/Documentation/lib/index.md b/Documentation/lib/index.md index 99b8061325..d64b4e999e 100644 --- a/Documentation/lib/index.md +++ b/Documentation/lib/index.md @@ -3,7 +3,7 @@ This section contains documentation about coreboot internal technical information and libraries. -## Structure and layout - [Flashmap and Flashmap Descriptor](flashmap.md) - [ABI data consumption](abi-data-consumption.md) - [Timestamps](timestamp.md) +- [Firmware Configuration Interface](fw_config.md) diff --git a/src/Kconfig b/src/Kconfig index 2a2a144235..b96d51f526 100644 --- a/src/Kconfig +++ b/src/Kconfig @@ -323,6 +323,33 @@ config BOOTSPLASH_FILE The path and filename of the file to use as graphical bootsplash screen. The file format has to be jpg. +config FW_CONFIG + bool "Firmware Configuration Probing" + default n + help + Enable support for probing devices with fw_config. This is a simple + bitmask broken into fields and options for probing. + +config FW_CONFIG_SOURCE_CBFS + bool "Obtain Firmware Configuration value from CBFS" + depends on FW_CONFIG + default n + help + With this option enabled coreboot will look for the 32bit firmware + configuration value in CBFS at the selected prefix with the file name + "fw_config". This option will override other sources and allow the + local image to preempt the mainboard selected source. + +config FW_CONFIG_SOURCE_CHROMEEC_CBI + bool "Obtain Firmware Configuration value from Google Chrome EC CBI" + depends on FW_CONFIG && EC_GOOGLE_CHROMEEC + default n + help + This option tells coreboot to read the firmware configuration value + from the Google Chrome Embedded Controller CBI interface. This source + is not tried if FW_CONFIG_SOURCE_CBFS is enabled and the value was + found in CBFS. + config HAVE_RAMPAYLOAD bool diff --git a/src/include/device/device.h b/src/include/device/device.h index 46efbfe679..082dcbb4d3 100644 --- a/src/include/device/device.h +++ b/src/include/device/device.h @@ -8,6 +8,7 @@ #include #include +struct fw_config; struct device; struct pci_operations; struct i2c_bus_operations; @@ -147,6 +148,9 @@ struct device { #endif #endif DEVTREE_CONST void *chip_info; + + /* Zero-terminated array of fields and options to probe. */ + DEVTREE_CONST struct fw_config *probe_list; }; /** diff --git a/src/include/fw_config.h b/src/include/fw_config.h new file mode 100644 index 0000000000..d41afd6c5d --- /dev/null +++ b/src/include/fw_config.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef __FW_CONFIG__ +#define __FW_CONFIG__ + +#include +#include /* Provides fw_config definitions from devicetree.cb */ +#include +#include + +/** + * struct fw_config - Firmware configuration field and option. + * @field_name: Name of the field that this option belongs to. + * @option_name: Name of the option within this field. + * @mask: Bitmask of the field. + * @value: Value of the option within the mask. + */ +struct fw_config { + const char *field_name; + const char *option_name; + uint32_t mask; + uint32_t value; +}; + +/* Generate a pointer to a compound literal of the fw_config structure. */ +#define FW_CONFIG(__field, __option) (&(const struct fw_config) { \ + .field_name = FW_CONFIG_FIELD_##__field##_NAME, \ + .option_name = FW_CONFIG_FIELD_##__field##_OPTION_##__option##_NAME, \ + .mask = FW_CONFIG_FIELD_##__field##_MASK, \ + .value = FW_CONFIG_FIELD_##__field##_OPTION_##__option##_VALUE \ +}) + +#if CONFIG(FW_CONFIG) + +/** + * fw_config_probe() - Check if field and option matches. + * @match: Structure containing field and option to probe. + * + * Return %true if match is found, %false if match is not found. + */ +bool fw_config_probe(const struct fw_config *match); + +#else + +static inline bool fw_config_probe(const struct fw_config *match) +{ + /* Always return true when probing with disabled fw_config. */ + return true; +} + +#endif /* CONFIG(FW_CONFIG) */ + +#endif /* __FW_CONFIG__ */ diff --git a/src/lib/Makefile.inc b/src/lib/Makefile.inc index 6511c0c328..e0003bd1cf 100644 --- a/src/lib/Makefile.inc +++ b/src/lib/Makefile.inc @@ -159,6 +159,11 @@ romstage-y += hexdump.c verstage-y += hexdump.c smm-y += hexdump.c +bootblock-$(CONFIG_FW_CONFIG) += fw_config.c +verstage-$(CONFIG_FW_CONFIG) += fw_config.c +romstage-$(CONFIG_FW_CONFIG) += fw_config.c +ramstage-$(CONFIG_FW_CONFIG) += fw_config.c + bootblock-$(CONFIG_ESPI_DEBUG) += espi_debug.c verstage-$(CONFIG_ESPI_DEBUG) += espi_debug.c romstage-$(CONFIG_ESPI_DEBUG) += espi_debug.c diff --git a/src/lib/fw_config.c b/src/lib/fw_config.c new file mode 100644 index 0000000000..e97cfdc72a --- /dev/null +++ b/src/lib/fw_config.c @@ -0,0 +1,94 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * fw_config_get() - Provide firmware configuration value. + * + * Return 32bit firmware configuration value determined for the system. + */ +static uint32_t fw_config_get(void) +{ + static uint32_t fw_config_value; + static bool fw_config_value_initialized; + + /* Nothing to prepare if setup is already done. */ + if (fw_config_value_initialized) + return fw_config_value; + fw_config_value_initialized = true; + + /* Look in CBFS to allow override of value. */ + if (CONFIG(FW_CONFIG_SOURCE_CBFS)) { + if (cbfs_boot_load_file(CONFIG_CBFS_PREFIX "/fw_config", + &fw_config_value, sizeof(fw_config_value), + CBFS_TYPE_RAW) != sizeof(fw_config_value)) { + printk(BIOS_WARNING, "%s: Could not get fw_config from CBFS\n", + __func__); + fw_config_value = 0; + } else { + printk(BIOS_INFO, "FW_CONFIG value from CBFS is 0x%08x\n", + fw_config_value); + return fw_config_value; + } + } + + /* Read the value from EC CBI. */ + if (CONFIG(FW_CONFIG_SOURCE_CHROMEEC_CBI)) { + if (google_chromeec_cbi_get_fw_config(&fw_config_value)) + printk(BIOS_WARNING, "%s: Could not get fw_config from EC\n", __func__); + } + + printk(BIOS_INFO, "FW_CONFIG value is 0x%08x\n", fw_config_value); + return fw_config_value; +} + +bool fw_config_probe(const struct fw_config *match) +{ + /* Compare to system value. */ + if ((fw_config_get() & match->mask) == match->value) { + if (match->field_name && match->option_name) + printk(BIOS_INFO, "fw_config match found: %s=%s\n", match->field_name, + match->option_name); + else + printk(BIOS_INFO, "fw_config match found: mask=0x%08x value=0x%08x\n", + match->mask, match->value); + return true; + } + + return false; +} + +#if ENV_RAMSTAGE +static void fw_config_init(void *unused) +{ + struct device *dev; + + for (dev = all_devices; dev; dev = dev->next) { + const struct fw_config *probe; + bool match = false; + + if (!dev->probe_list) + continue; + + for (probe = dev->probe_list; probe && probe->mask != 0; probe++) { + if (fw_config_probe(probe)) { + match = true; + break; + } + } + + if (!match) { + printk(BIOS_INFO, "%s disabled by fw_config\n", dev_path(dev)); + dev->enabled = 0; + } + } +} +BOOT_STATE_INIT_ENTRY(BS_DEV_ENUMERATE, BS_ON_ENTRY, fw_config_init, NULL); +#endif