| /* |
| * This file is part of the coreboot project. |
| * |
| * Copyright (C) 2013 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 <baytrail/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; |
| |
| 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); |
| } |
| |
| if (verified_cache == NULL) |
| return -1; |
| |
| *cache = 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 (%u bytes)\n", |
| data, cache, size); |
| |
| mrc_cache_fill(cache, data, size); |
| |
| return 0; |
| } |
| |
| #else |
| /* ramstage code */ |
| #include <bootstate.h> |
| #include <baytrail/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__) */ |