fw_config: Add firmware configuration interface
This change introduces a new top-level interface for interacting with a
bitmask providing firmware configuration information.
This is motivated by Chromebook mainboards that need to support multiple
different configurations at runtime with the same BIOS. In these
devices the Embedded Controller provides a bitmask that can be broken
down into different fields and each field can then be broken down into
different options.
The firmware configuration value could also be stored in CBFS and this
interface will look in CBFS first to allow the Embedded Controller value
to be overridden.
The firmware configuration interface is intended to easily integrate
into devicetree.cb and lead to less code duplication for new mainboards
that make use of this feature.
BUG=b:147462631
TEST=this provides a new interface that is tested in subsequent commits
Change-Id: I1e889c235a81545e2ec0e3a34dfa750ac828a330
Signed-off-by: Duncan Laurie <dlaurie@google.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/41209
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Furquan Shaikh <furquan@google.com>
diff --git a/Documentation/lib/fw_config.md b/Documentation/lib/fw_config.md
new file mode 100644
index 0000000..63a56dc
--- /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 <name> <start-bit> <end-bit> [option...] end
+
+For single-bit fields only one number is needed:
+
+ field <name> <bit> [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 <name> <value>
+
+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 <name> [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 <fw_config.h>
+
+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 99b8061..d64b4e9 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 2a2a144..b96d51f 100644
--- a/src/Kconfig
+++ b/src/Kconfig
@@ -323,6 +323,33 @@
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 46efbfe..082dcbb 100644
--- a/src/include/device/device.h
+++ b/src/include/device/device.h
@@ -8,6 +8,7 @@
#include <smbios.h>
#include <types.h>
+struct fw_config;
struct device;
struct pci_operations;
struct i2c_bus_operations;
@@ -147,6 +148,9 @@
#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 0000000..d41afd6
--- /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 <device/device.h>
+#include <static.h> /* Provides fw_config definitions from devicetree.cb */
+#include <stdbool.h>
+#include <stdint.h>
+
+/**
+ * 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 6511c0c..e0003bd 100644
--- a/src/lib/Makefile.inc
+++ b/src/lib/Makefile.inc
@@ -159,6 +159,11 @@
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 0000000..e97cfdc
--- /dev/null
+++ b/src/lib/fw_config.c
@@ -0,0 +1,94 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#include <bootstate.h>
+#include <cbfs.h>
+#include <console/console.h>
+#include <device/device.h>
+#include <ec/google/chromeec/ec.h>
+#include <fw_config.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+/**
+ * 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