diff options
Diffstat (limited to 'src/soc/intel/common/mrc_cache.c')
-rw-r--r-- | src/soc/intel/common/mrc_cache.c | 311 |
1 files changed, 311 insertions, 0 deletions
diff --git a/src/soc/intel/common/mrc_cache.c b/src/soc/intel/common/mrc_cache.c new file mode 100644 index 0000000000..8f0d18f8b3 --- /dev/null +++ b/src/soc/intel/common/mrc_cache.c @@ -0,0 +1,311 @@ +/* + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <string.h> +#include <console/console.h> +#include <cbmem.h> +#include <ip_checksum.h> +#if CONFIG_CHROMEOS +#include <vendorcode/google/chromeos/fmap.h> +#endif +#include "mrc_cache.h" + +#define MRC_DATA_ALIGN 0x1000 +#define MRC_DATA_SIGNATURE (('M'<<0)|('R'<<8)|('C'<<16)|('D'<<24)) + +/* The mrc_data_region describes the larger non-volatile area to store + * mrc_saved_data objects.*/ +struct mrc_data_region { + void *base; + uint32_t size; +}; + +/* common code */ +static int mrc_cache_get_region(struct mrc_data_region *region) +{ +#if CONFIG_CHROMEOS + int ret; + ret = find_fmap_entry("RW_MRC_CACHE", ®ion->base); + if (ret >= 0) { + region->size = ret; + return 0; + } +#endif + region->base = (void *)CONFIG_MRC_SETTINGS_CACHE_BASE; + region->size = CONFIG_MRC_SETTINGS_CACHE_SIZE; + return 0; +} + +static int mrc_cache_in_region(const struct mrc_data_region *region, + const struct mrc_saved_data *cache) +{ + uintptr_t region_end; + uintptr_t cache_end; + + if ((uintptr_t)cache < (uintptr_t)region->base) + return 0; + + region_end = (uintptr_t)region->base; + region_end += region->size; + + if ((uintptr_t)cache >= region_end) + return 0; + + if ((sizeof(*cache) + (uintptr_t)cache) >= region_end) + return 0; + + cache_end = (uintptr_t)cache; + cache_end += cache->size + sizeof(*cache); + + if (cache_end > region_end) + return 0; + + return 1; +} + +static int mrc_cache_valid(const struct mrc_data_region *region, + const struct mrc_saved_data *cache) +{ + uint32_t checksum; + + if (cache->signature != MRC_DATA_SIGNATURE) + return 0; + + if (cache->size > region->size) + return 0; + + if (cache->reserved != 0) + return 0; + + checksum = compute_ip_checksum((void *)&cache->data[0], cache->size); + + if (cache->checksum != checksum) + return 0; + + return 1; +} + +static const struct mrc_saved_data * +next_cache_block(const struct mrc_saved_data *cache) +{ + uintptr_t next = (uintptr_t)cache; + + next += ALIGN(cache->size + sizeof(*cache), MRC_DATA_ALIGN); + + return (const struct mrc_saved_data *)next; +} + +/* Locate the most recently saved MRC data. */ +static int __mrc_cache_get_current(const struct mrc_data_region *region, + const struct mrc_saved_data **cache) +{ + const struct mrc_saved_data *msd; + const struct mrc_saved_data *verified_cache; + int slot = 0; + + msd = region->base; + + verified_cache = NULL; + + while (mrc_cache_in_region(region, msd) && + mrc_cache_valid(region, msd)) { + verified_cache = msd; + msd = next_cache_block(msd); + slot++; + } + + if (verified_cache == NULL) + return -1; + + *cache = verified_cache; + printk(BIOS_DEBUG, "MRC cache slot %d @ %p\n", slot-1, verified_cache); + + return 0; +} + +int mrc_cache_get_current(const struct mrc_saved_data **cache) +{ + struct mrc_data_region region; + + if (mrc_cache_get_region(®ion) < 0) + return -1; + + return __mrc_cache_get_current(®ion, cache); +} + +#if defined(__PRE_RAM__) + +/* + * romstage code + */ + +/* Fill in mrc_saved_data structure with payload. */ +static void mrc_cache_fill(struct mrc_saved_data *cache, void *data, + size_t size) +{ + cache->signature = MRC_DATA_SIGNATURE; + cache->size = size; + cache->reserved = 0; + memcpy(&cache->data[0], data, size); + cache->checksum = compute_ip_checksum((void *)&cache->data[0], + cache->size); +} + +int mrc_cache_stash_data(void *data, size_t size) +{ + int cbmem_size; + struct mrc_saved_data *cache; + + cbmem_size = sizeof(*cache) + ALIGN(size, 16); + + cache = cbmem_add(CBMEM_ID_MRCDATA, cbmem_size); + + if (cache == NULL) { + printk(BIOS_ERR, "No space in cbmem for MRC data.\n"); + return -1; + } + + /* Clear alignment padding bytes at end of data. */ + memset(&cache->data[size], 0, cbmem_size - size - sizeof(*cache)); + + printk(BIOS_DEBUG, "Relocate MRC DATA from %p to %p (%zu bytes)\n", + data, cache, size); + + mrc_cache_fill(cache, data, size); + + return 0; +} + +#else + +/* + * ramstage code + */ + +#include <bootstate.h> +#include "nvm.h" + +static int mrc_slot_valid(const struct mrc_data_region *region, + const struct mrc_saved_data *slot, + const struct mrc_saved_data *to_save) +{ + uintptr_t region_begin; + uintptr_t region_end; + uintptr_t slot_end; + uintptr_t slot_begin; + uint32_t size; + + region_begin = (uintptr_t)region->base; + region_end = region_begin + region->size; + slot_begin = (uintptr_t)slot; + size = to_save->size + sizeof(*to_save); + slot_end = slot_begin + size; + + if (slot_begin < region_begin || slot_begin >= region_end) + return 0; + + if (size > region->size) + return 0; + + if (slot_end > region_end || slot_end < region_begin) + return 0; + + if (!nvm_is_erased(slot, size)) + return 0; + + return 1; +} + +static const struct mrc_saved_data * +mrc_cache_next_slot(const struct mrc_data_region *region, + const struct mrc_saved_data *current_slot) +{ + const struct mrc_saved_data *next_slot; + + if (current_slot == NULL) { + next_slot = region->base; + } else { + next_slot = next_cache_block(current_slot); + } + + return next_slot; +} + +static void update_mrc_cache(void *unused) +{ + const struct mrc_saved_data *current_boot; + const struct mrc_saved_data *current_saved; + const struct mrc_saved_data *next_slot; + struct mrc_data_region region; + + printk(BIOS_DEBUG, "Updating MRC cache data.\n"); + + current_boot = cbmem_find(CBMEM_ID_MRCDATA); + if (!current_boot) { + printk(BIOS_ERR, "No MRC cache in cbmem.\n"); + return; + } + + if (mrc_cache_get_region(®ion)) { + printk(BIOS_ERR, "Could not obtain MRC cache region.\n"); + return; + } + + if (!mrc_cache_valid(®ion, current_boot)) { + printk(BIOS_ERR, "MRC cache data in cbmem invalid.\n"); + return; + } + + current_saved = NULL; + + if (!__mrc_cache_get_current(®ion, ¤t_saved)) { + if (current_saved->size == current_boot->size && + !memcmp(¤t_saved->data[0], ¤t_boot->data[0], + current_saved->size)) { + printk(BIOS_DEBUG, "MRC cache up to date.\n"); + return; + } + } + + next_slot = mrc_cache_next_slot(®ion, current_saved); + + if (!mrc_slot_valid(®ion, next_slot, current_boot)) { + printk(BIOS_DEBUG, "Slot @ %p is invalid.\n", next_slot); + if (!nvm_is_erased(region.base, region.size)) { + if (nvm_erase(region.base, region.size) < 0) { + printk(BIOS_DEBUG, "Failure erasing region.\n"); + return; + } + } + next_slot = region.base; + } + + if (nvm_write((void *)next_slot, current_boot, + current_boot->size + sizeof(*current_boot))) { + printk(BIOS_DEBUG, "Failure writing MRC cache to %p.\n", + next_slot); + } +} + +BOOT_STATE_INIT_ENTRIES(mrc_cache_update) = { + BOOT_STATE_INIT_ENTRY(BS_WRITE_TABLES, BS_ON_ENTRY, + update_mrc_cache, NULL), +}; + +#endif /* defined(__PRE_RAM__) */ |