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/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