summaryrefslogtreecommitdiff
path: root/src/drivers/mrc_cache
diff options
context:
space:
mode:
Diffstat (limited to 'src/drivers/mrc_cache')
-rw-r--r--src/drivers/mrc_cache/Kconfig31
-rw-r--r--src/drivers/mrc_cache/Makefile.inc16
-rw-r--r--src/drivers/mrc_cache/mrc_cache.c581
3 files changed, 628 insertions, 0 deletions
diff --git a/src/drivers/mrc_cache/Kconfig b/src/drivers/mrc_cache/Kconfig
new file mode 100644
index 0000000000..3e0bdda2be
--- /dev/null
+++ b/src/drivers/mrc_cache/Kconfig
@@ -0,0 +1,31 @@
+config CACHE_MRC_SETTINGS
+ bool "Save cached MRC settings"
+ default n
+
+if CACHE_MRC_SETTINGS
+
+config MRC_SETTINGS_CACHE_BASE
+ hex
+ default 0xfffe0000
+
+config MRC_SETTINGS_CACHE_SIZE
+ hex
+ default 0x10000
+
+config MRC_SETTINGS_PROTECT
+ bool "Enable protection on MRC settings"
+ default n
+
+config HAS_RECOVERY_MRC_CACHE
+ bool
+ default n
+
+config MRC_CLEAR_NORMAL_CACHE_ON_RECOVERY_RETRAIN
+ bool
+ default n
+
+config MRC_SETTINGS_VARIABLE_DATA
+ bool
+ default n
+
+endif # CACHE_MRC_SETTINGS
diff --git a/src/drivers/mrc_cache/Makefile.inc b/src/drivers/mrc_cache/Makefile.inc
new file mode 100644
index 0000000000..819d637e4f
--- /dev/null
+++ b/src/drivers/mrc_cache/Makefile.inc
@@ -0,0 +1,16 @@
+
+romstage-$(CONFIG_CACHE_MRC_SETTINGS) += mrc_cache.c
+ramstage-$(CONFIG_CACHE_MRC_SETTINGS) += mrc_cache.c
+
+# Create and add the MRC cache to the cbfs image
+ifneq ($(CONFIG_CHROMEOS),y)
+$(obj)/mrc.cache: $(obj)/config.h
+ dd if=/dev/zero count=1 \
+ bs=$(shell printf "%d" $(CONFIG_MRC_SETTINGS_CACHE_SIZE) ) | \
+ tr '\000' '\377' > $@
+
+cbfs-files-$(CONFIG_CACHE_MRC_SETTINGS) += mrc.cache
+mrc.cache-file := $(obj)/mrc.cache
+mrc.cache-position := $(CONFIG_MRC_SETTINGS_CACHE_BASE)
+mrc.cache-type := mrc_cache
+endif
diff --git a/src/drivers/mrc_cache/mrc_cache.c b/src/drivers/mrc_cache/mrc_cache.c
new file mode 100644
index 0000000000..3a9689645f
--- /dev/null
+++ b/src/drivers/mrc_cache/mrc_cache.c
@@ -0,0 +1,581 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright (C) 2014 Google Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <compiler.h>
+#include <string.h>
+#include <boot_device.h>
+#include <bootstate.h>
+#include <bootmode.h>
+#include <console/console.h>
+#include <cbmem.h>
+#include <elog.h>
+#include <fmap.h>
+#include <ip_checksum.h>
+#include <region_file.h>
+#include <security/vboot/vboot_common.h>
+#include <spi_flash.h>
+
+#include "mrc_cache.h"
+
+#define DEFAULT_MRC_CACHE "RW_MRC_CACHE"
+#define VARIABLE_MRC_CACHE "RW_VAR_MRC_CACHE"
+#define RECOVERY_MRC_CACHE "RECOVERY_MRC_CACHE"
+#define UNIFIED_MRC_CACHE "UNIFIED_MRC_CACHE"
+
+#define MRC_DATA_SIGNATURE (('M'<<0)|('R'<<8)|('C'<<16)|('D'<<24))
+
+struct mrc_metadata {
+ uint32_t signature;
+ uint32_t data_size;
+ uint16_t data_checksum;
+ uint16_t header_checksum;
+ uint32_t version;
+} __packed;
+
+enum result {
+ UPDATE_FAILURE = -1,
+ UPDATE_SUCCESS = 0,
+ ALREADY_UPTODATE = 1
+};
+
+#define NORMAL_FLAG (1 << 0)
+#define RECOVERY_FLAG (1 << 1)
+
+struct cache_region {
+ const char *name;
+ uint32_t cbmem_id;
+ int type;
+ int elog_slot;
+ int flags;
+};
+
+static const struct cache_region recovery_training = {
+ .name = RECOVERY_MRC_CACHE,
+ .cbmem_id = CBMEM_ID_MRCDATA,
+ .type = MRC_TRAINING_DATA,
+ .elog_slot = ELOG_MEM_CACHE_UPDATE_SLOT_RECOVERY,
+#if IS_ENABLED(CONFIG_HAS_RECOVERY_MRC_CACHE)
+ .flags = RECOVERY_FLAG,
+#else
+ .flags = 0,
+#endif
+};
+
+static const struct cache_region normal_training = {
+ .name = DEFAULT_MRC_CACHE,
+ .cbmem_id = CBMEM_ID_MRCDATA,
+ .type = MRC_TRAINING_DATA,
+ .elog_slot = ELOG_MEM_CACHE_UPDATE_SLOT_NORMAL,
+ .flags = NORMAL_FLAG | RECOVERY_FLAG,
+};
+
+static const struct cache_region variable_data = {
+ .name = VARIABLE_MRC_CACHE,
+ .cbmem_id = CBMEM_ID_VAR_MRCDATA,
+ .type = MRC_VARIABLE_DATA,
+ .elog_slot = ELOG_MEM_CACHE_UPDATE_SLOT_VARIABLE,
+ .flags = NORMAL_FLAG | RECOVERY_FLAG,
+};
+
+/* Order matters here for priority in matching. */
+static const struct cache_region *cache_regions[] = {
+ &recovery_training,
+ &normal_training,
+ &variable_data,
+};
+
+static int lookup_region_by_name(const char *name, struct region *r)
+{
+ /* This assumes memory mapped boot media just under 4GiB. */
+ const uint32_t pointer_base_32bit = -CONFIG_ROM_SIZE;
+
+ if (fmap_locate_area(name, r) == 0)
+ return 0;
+
+ /* CHROMEOS builds must get their MRC cache from FMAP. */
+ if (IS_ENABLED(CONFIG_CHROMEOS)) {
+ printk(BIOS_ERR, "MRC: Chrome OS lookup failure.\n");
+ return -1;
+ }
+
+ if (!IS_ENABLED(CONFIG_BOOT_DEVICE_MEMORY_MAPPED))
+ return -1;
+
+ /* Base is in the form of a pointer. Make it an offset. */
+ r->offset = CONFIG_MRC_SETTINGS_CACHE_BASE - pointer_base_32bit;
+ r->size = CONFIG_MRC_SETTINGS_CACHE_SIZE;
+
+ return 0;
+}
+
+static const struct cache_region *lookup_region_type(int type)
+{
+ int i;
+ int flags;
+
+ if (vboot_recovery_mode_enabled())
+ flags = RECOVERY_FLAG;
+ else
+ flags = NORMAL_FLAG;
+
+ for (i = 0; i < ARRAY_SIZE(cache_regions); i++) {
+ if (cache_regions[i]->type != type)
+ continue;
+ if ((cache_regions[i]->flags & flags) == flags)
+ return cache_regions[i];
+ }
+
+ return NULL;
+}
+
+int mrc_cache_stash_data(int type, uint32_t version, const void *data,
+ size_t size)
+{
+ const struct cache_region *cr;
+ size_t cbmem_size;
+ struct mrc_metadata *md;
+
+ cr = lookup_region_type(type);
+ if (cr == NULL) {
+ printk(BIOS_ERR, "MRC: failed to add to cbmem for type %d.\n",
+ type);
+ return -1;
+ }
+
+ cbmem_size = sizeof(*md) + size;
+
+ md = cbmem_add(cr->cbmem_id, cbmem_size);
+
+ if (md == NULL) {
+ printk(BIOS_ERR, "MRC: failed to add '%s' to cbmem.\n",
+ cr->name);
+ return -1;
+ }
+
+ memset(md, 0, sizeof(*md));
+ md->signature = MRC_DATA_SIGNATURE;
+ md->data_size = size;
+ md->version = version;
+ md->data_checksum = compute_ip_checksum(data, size);
+ md->header_checksum = compute_ip_checksum(md, sizeof(*md));
+ memcpy(&md[1], data, size);
+
+ return 0;
+}
+
+static const struct cache_region *lookup_region(struct region *r, int type)
+{
+ const struct cache_region *cr;
+
+ cr = lookup_region_type(type);
+
+ if (cr == NULL) {
+ printk(BIOS_ERR, "MRC: failed to locate region type %d.\n",
+ type);
+ return NULL;
+ }
+
+ if (lookup_region_by_name(cr->name, r) < 0)
+ return NULL;
+
+ return cr;
+}
+
+static int mrc_header_valid(struct region_device *rdev, struct mrc_metadata *md)
+{
+ uint16_t checksum;
+ uint16_t checksum_result;
+ size_t size;
+
+ if (rdev_readat(rdev, md, 0, sizeof(*md)) < 0) {
+ printk(BIOS_ERR, "MRC: couldn't read metadata\n");
+ return -1;
+ }
+
+ if (md->signature != MRC_DATA_SIGNATURE) {
+ printk(BIOS_ERR, "MRC: invalid header signature\n");
+ return -1;
+ }
+
+ /* Compute checksum over header with 0 as the value. */
+ checksum = md->header_checksum;
+ md->header_checksum = 0;
+ checksum_result = compute_ip_checksum(md, sizeof(*md));
+
+ if (checksum != checksum_result) {
+ printk(BIOS_ERR, "MRC: header checksum mismatch: %x vs %x\n",
+ checksum, checksum_result);
+ return -1;
+ }
+
+ /* Put back original. */
+ md->header_checksum = checksum;
+
+ /* Re-size the region device according to the metadata as a region_file
+ * does block allocation. */
+ size = sizeof(*md) + md->data_size;
+ if (rdev_chain(rdev, rdev, 0, size) < 0) {
+ printk(BIOS_ERR, "MRC: size exceeds rdev size: %zx vs %zx\n",
+ size, region_device_sz(rdev));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int mrc_data_valid(const struct region_device *rdev,
+ const struct mrc_metadata *md)
+{
+ void *data;
+ uint16_t checksum;
+ const size_t md_size = sizeof(*md);
+ const size_t data_size = md->data_size;
+
+ data = rdev_mmap(rdev, md_size, data_size);
+ if (data == NULL) {
+ printk(BIOS_ERR, "MRC: mmap failure on data verification.\n");
+ return -1;
+ }
+
+ checksum = compute_ip_checksum(data, data_size);
+
+ rdev_munmap(rdev, data);
+ if (md->data_checksum != checksum) {
+ printk(BIOS_ERR, "MRC: data checksum mismatch: %x vs %x\n",
+ md->data_checksum, checksum);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int mrc_cache_latest(const char *name,
+ const struct region_device *backing_rdev,
+ struct mrc_metadata *md,
+ struct region_file *cache_file,
+ struct region_device *rdev,
+ bool fail_bad_data)
+{
+ /* Init and obtain a handle to the file data. */
+ if (region_file_init(cache_file, backing_rdev) < 0) {
+ printk(BIOS_ERR, "MRC: region file invalid in '%s'\n", name);
+ return -1;
+ }
+
+ /* Provide a 0 sized region_device from here on out so the caller
+ * has a valid yet unusable region_device. */
+ rdev_chain(rdev, backing_rdev, 0, 0);
+
+ /* No data to return. */
+ if (region_file_data(cache_file, rdev) < 0) {
+ printk(BIOS_ERR, "MRC: no data in '%s'\n", name);
+ return fail_bad_data ? -1 : 0;
+ }
+
+ /* Validate header and resize region to reflect actual usage on the
+ * saved medium (including metadata and data). */
+ if (mrc_header_valid(rdev, md) < 0) {
+ printk(BIOS_ERR, "MRC: invalid header in '%s'\n", name);
+ return fail_bad_data ? -1 : 0;
+ }
+
+ /* Validate Data */
+ if (mrc_data_valid(rdev, md) < 0) {
+ printk(BIOS_ERR, "MRC: invalid data in '%s'\n", name);
+ return fail_bad_data ? -1 : 0;
+ }
+
+ return 0;
+}
+
+int mrc_cache_get_current(int type, uint32_t version,
+ struct region_device *rdev)
+{
+ const struct cache_region *cr;
+ struct region region;
+ struct region_device read_rdev;
+ struct region_file cache_file;
+ struct mrc_metadata md;
+ size_t data_size;
+ const size_t md_size = sizeof(md);
+ const bool fail_bad_data = true;
+
+ cr = lookup_region(&region, type);
+
+ if (cr == NULL)
+ return -1;
+
+ if (boot_device_ro_subregion(&region, &read_rdev) < 0)
+ return -1;
+
+ if (mrc_cache_latest(cr->name, &read_rdev, &md, &cache_file, rdev,
+ fail_bad_data) < 0)
+ return -1;
+
+ if (version != md.version) {
+ printk(BIOS_INFO, "MRC: version mismatch: %x vs %x\n",
+ md.version, version);
+ return -1;
+ }
+
+ /* Re-size rdev to only contain the data. i.e. remove metadata. */
+ data_size = md.data_size;
+ return rdev_chain(rdev, rdev, md_size, data_size);
+}
+
+static bool mrc_cache_needs_update(const struct region_device *rdev,
+ const struct cbmem_entry *to_be_updated)
+{
+ void *mapping;
+ size_t size = region_device_sz(rdev);
+ bool need_update = false;
+
+ if (cbmem_entry_size(to_be_updated) != size)
+ return true;
+
+ mapping = rdev_mmap_full(rdev);
+
+ if (memcmp(cbmem_entry_start(to_be_updated), mapping, size))
+ need_update = true;
+
+ rdev_munmap(rdev, mapping);
+
+ return need_update;
+}
+
+static void log_event_cache_update(uint8_t slot, enum result res)
+{
+ const int type = ELOG_TYPE_MEM_CACHE_UPDATE;
+ struct elog_event_mem_cache_update event = {
+ .slot = slot
+ };
+
+ /* Filter through interesting events only */
+ switch (res) {
+ case UPDATE_FAILURE:
+ event.status = ELOG_MEM_CACHE_UPDATE_STATUS_FAIL;
+ break;
+ case UPDATE_SUCCESS:
+ event.status = ELOG_MEM_CACHE_UPDATE_STATUS_SUCCESS;
+ break;
+ default:
+ return;
+ }
+
+ if (elog_add_event_raw(type, &event, sizeof(event)) < 0)
+ printk(BIOS_ERR, "Failed to log mem cache update event.\n");
+}
+
+/* During ramstage this code purposefully uses incoherent transactions between
+ * read and write. The read assumes a memory-mapped boot device that can be used
+ * to quickly locate and compare the up-to-date data. However, when an update
+ * is required it uses the writeable region access to perform the update. */
+static void update_mrc_cache_by_type(int type)
+{
+ const struct cache_region *cr;
+ struct region region;
+ struct region_device read_rdev;
+ struct region_device write_rdev;
+ struct region_file cache_file;
+ struct mrc_metadata md;
+ const struct cbmem_entry *to_be_updated;
+ struct incoherent_rdev backing_irdev;
+ const struct region_device *backing_rdev;
+ struct region_device latest_rdev;
+ const bool fail_bad_data = false;
+
+ cr = lookup_region(&region, type);
+
+ if (cr == NULL)
+ return;
+
+ to_be_updated = cbmem_entry_find(cr->cbmem_id);
+ if (to_be_updated == NULL) {
+ printk(BIOS_ERR, "MRC: No data in cbmem for '%s'.\n",
+ cr->name);
+ return;
+ }
+
+ printk(BIOS_DEBUG, "MRC: Checking cached data update for '%s'.\n",
+ cr->name);
+
+ if (boot_device_ro_subregion(&region, &read_rdev) < 0)
+ return;
+
+ if (boot_device_rw_subregion(&region, &write_rdev) < 0)
+ return;
+
+ backing_rdev = incoherent_rdev_init(&backing_irdev, &region, &read_rdev,
+ &write_rdev);
+
+ if (backing_rdev == NULL)
+ return;
+
+ if (mrc_cache_latest(cr->name, backing_rdev, &md, &cache_file,
+ &latest_rdev, fail_bad_data) < 0)
+ return;
+
+ if (!mrc_cache_needs_update(&latest_rdev, to_be_updated)) {
+ log_event_cache_update(cr->elog_slot, ALREADY_UPTODATE);
+ return;
+ }
+
+ printk(BIOS_DEBUG, "MRC: cache data '%s' needs update.\n", cr->name);
+
+ if (region_file_update_data(&cache_file,
+ cbmem_entry_start(to_be_updated),
+ cbmem_entry_size(to_be_updated)) < 0)
+ log_event_cache_update(cr->elog_slot, UPDATE_FAILURE);
+ else
+ log_event_cache_update(cr->elog_slot, UPDATE_SUCCESS);
+}
+
+/* Read flash status register to determine if write protect is active */
+static int nvm_is_write_protected(void)
+{
+ u8 sr1;
+ u8 wp_gpio;
+ u8 wp_spi;
+
+ if (!IS_ENABLED(CONFIG_CHROMEOS))
+ return 0;
+
+ if (!IS_ENABLED(CONFIG_BOOT_DEVICE_SPI_FLASH))
+ return 0;
+
+ /* Read Write Protect GPIO if available */
+ wp_gpio = get_write_protect_state();
+
+ /* Read Status Register 1 */
+ if (spi_flash_status(boot_device_spi_flash(), &sr1) < 0) {
+ printk(BIOS_ERR, "Failed to read SPI status register 1\n");
+ return -1;
+ }
+ wp_spi = !!(sr1 & 0x80);
+
+ printk(BIOS_DEBUG, "SPI flash protection: WPSW=%d SRP0=%d\n",
+ wp_gpio, wp_spi);
+
+ return wp_gpio && wp_spi;
+}
+
+/* Apply protection to a range of flash */
+static int nvm_protect(const struct region *r)
+{
+ if (!IS_ENABLED(CONFIG_MRC_SETTINGS_PROTECT))
+ return 0;
+
+ if (!IS_ENABLED(CONFIG_BOOT_DEVICE_SPI_FLASH))
+ return 0;
+
+ return spi_flash_ctrlr_protect_region(boot_device_spi_flash(), r);
+}
+
+/* Protect mrc region with a Protected Range Register */
+static int protect_mrc_cache(const char *name)
+{
+ struct region region;
+
+ if (!IS_ENABLED(CONFIG_MRC_SETTINGS_PROTECT))
+ return 0;
+
+ if (lookup_region_by_name(name, &region) < 0) {
+ printk(BIOS_ERR, "MRC: Could not find region '%s'\n", name);
+ return -1;
+ }
+
+ if (nvm_is_write_protected() <= 0) {
+ printk(BIOS_INFO, "MRC: NOT enabling PRR for '%s'.\n", name);
+ return 0;
+ }
+
+ if (nvm_protect(&region) < 0) {
+ printk(BIOS_ERR, "MRC: ERROR setting PRR for '%s'.\n", name);
+ return -1;
+ }
+
+ printk(BIOS_INFO, "MRC: Enabled Protected Range on '%s'.\n", name);
+ return 0;
+}
+
+static void protect_mrc_region(void)
+{
+ /*
+ * Check if there is a single unified region that encompasses both
+ * RECOVERY_MRC_CACHE and DEFAULT_MRC_CACHE. In that case protect the
+ * entire region using a single PRR.
+ *
+ * If we are not able to protect the entire region, try protecting
+ * individual regions next.
+ */
+ if (protect_mrc_cache(UNIFIED_MRC_CACHE) == 0)
+ return;
+
+ if (IS_ENABLED(CONFIG_HAS_RECOVERY_MRC_CACHE))
+ protect_mrc_cache(RECOVERY_MRC_CACHE);
+
+ protect_mrc_cache(DEFAULT_MRC_CACHE);
+}
+
+static void invalidate_normal_cache(void)
+{
+ struct region_file cache_file;
+ struct region_device rdev;
+ const char *name = DEFAULT_MRC_CACHE;
+ const uint32_t invalid = ~MRC_DATA_SIGNATURE;
+
+ /* Invalidate only on recovery mode with retraining enabled. */
+ if (!vboot_recovery_mode_enabled())
+ return;
+ if (!vboot_recovery_mode_memory_retrain())
+ return;
+
+ if (fmap_locate_area_as_rdev_rw(name, &rdev) < 0) {
+ printk(BIOS_ERR, "MRC: Couldn't find '%s' region. Invalidation failed\n",
+ name);
+ return;
+ }
+
+ if (region_file_init(&cache_file, &rdev) < 0) {
+ printk(BIOS_ERR, "MRC: region file invalid for '%s'. Invalidation failed\n",
+ name);
+ return;
+ }
+
+ /* Push an update that consists of 4 bytes that is smaller than the
+ * MRC metadata as well as an invalid signature. */
+ if (region_file_update_data(&cache_file, &invalid, sizeof(invalid)) < 0)
+ printk(BIOS_ERR, "MRC: invalidation failed for '%s'.\n", name);
+}
+
+static void update_mrc_cache(void *unused)
+{
+ update_mrc_cache_by_type(MRC_TRAINING_DATA);
+
+ if (IS_ENABLED(CONFIG_MRC_SETTINGS_VARIABLE_DATA))
+ update_mrc_cache_by_type(MRC_VARIABLE_DATA);
+
+ if (IS_ENABLED(CONFIG_MRC_CLEAR_NORMAL_CACHE_ON_RECOVERY_RETRAIN))
+ invalidate_normal_cache();
+
+ protect_mrc_region();
+}
+
+/*
+ * Ensures MRC training data is stored into SPI after PCI enumeration is done
+ * during BS_DEV_ENUMERATE-BS_ON_EXIT and lock down SPI protected ranges
+ * during BS_DEV_RESOURCES-BS_ON_EXIT.
+ */
+BOOT_STATE_INIT_ENTRY(BS_DEV_ENUMERATE, BS_ON_EXIT, update_mrc_cache, NULL);