soc/power9/rom_media.c: find CBFS in PNOR
Tested on QEMU with ECC. Use mmap_helper to handle loading of compressed ramstage. Bootblock fits in SEEPROM with both console and LZ4 romstage compression, but not with verbose CBFS debug messages. Signed-off-by: Krystian Hebel <krystian.hebel@3mdeb.com> Change-Id: I91c72c52849eb1e3fafe43390351537d04382e46 Reviewed-on: https://review.coreboot.org/c/coreboot/+/67069 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: David Hendricks <david.hendricks@gmail.com>
This commit is contained in:
parent
44ec090551
commit
8f2633cd60
2 changed files with 441 additions and 1 deletions
|
|
@ -8,7 +8,9 @@
|
|||
/* Set MSB to 1 to ignore HRMOR */
|
||||
#define MMIO_GROUP0_CHIP0_LPC_BASE_ADDR 0x8006030000000000
|
||||
#define LPCHC_IO_SPACE 0xD0010000
|
||||
#define LPCHC_FW_SPACE 0xF0000000
|
||||
#define FLASH_IO_SPACE 0xFC000000
|
||||
#define FW_SPACE_SIZE 0x10000000
|
||||
#define LPC_BASE_ADDR (MMIO_GROUP0_CHIP0_LPC_BASE_ADDR + LPCHC_IO_SPACE)
|
||||
#define FLASH_BASE_ADDR (MMIO_GROUP0_CHIP0_LPC_BASE_ADDR + FLASH_IO_SPACE)
|
||||
#define MMIO_GROUP0_CHIP0_SCOM_BASE_ADDR 0x800603FC00000000
|
||||
|
|
|
|||
|
|
@ -1,8 +1,446 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
|
||||
#include <string.h>
|
||||
#include <boot_device.h>
|
||||
#include <arch/io.h>
|
||||
#include <console/console.h>
|
||||
#include <endian.h>
|
||||
#include <cbfs.h>
|
||||
#include <symbols.h>
|
||||
#include "../../../../3rdparty/ffs/ffs/ffs.h"
|
||||
|
||||
#define LPC_FLASH_MIN (MMIO_GROUP0_CHIP0_LPC_BASE_ADDR + LPCHC_FW_SPACE)
|
||||
#define LPC_FLASH_TOP (LPC_FLASH_MIN + FW_SPACE_SIZE)
|
||||
|
||||
#define CBFS_PARTITION_NAME "HBI"
|
||||
|
||||
/* ffs_entry is not complete in included ffs.h, it lacks user data layout.
|
||||
* See https://github.com/open-power/skiboot/blob/master/libflash/ffs.h */
|
||||
|
||||
/* Data integrity flags */
|
||||
#define FFS_ENRY_INTEG_ECC 0x8000
|
||||
|
||||
/* Version Checking : 1 byte */
|
||||
#define FFS_VERS_SHA512 0x80
|
||||
|
||||
enum ecc_status {
|
||||
CLEAN = 0, //< No ECC Error was detected.
|
||||
CORRECTED = 1, //< ECC error detected and corrected.
|
||||
UNCORRECTABLE = 2 //< ECC error detected and uncorrectable.
|
||||
};
|
||||
typedef enum ecc_status ecc_status_t;
|
||||
|
||||
enum ecc_bitfields {
|
||||
GD = 0xff, //< Good, ECC matches.
|
||||
UE = 0xfe, //< Uncorrectable.
|
||||
E0 = 71, //< Error in ECC bit 0
|
||||
E1 = 70, //< Error in ECC bit 1
|
||||
E2 = 69, //< Error in ECC bit 2
|
||||
E3 = 68, //< Error in ECC bit 3
|
||||
E4 = 67, //< Error in ECC bit 4
|
||||
E5 = 66, //< Error in ECC bit 5
|
||||
E6 = 65, //< Error in ECC bit 6
|
||||
E7 = 64 //< Error in ECC bit 7
|
||||
};
|
||||
|
||||
/*
|
||||
static uint64_t ecc_matrix[] = {
|
||||
//0000000000000000111010000100001000111100000011111001100111111111
|
||||
0x0000e8423c0f99ff,
|
||||
//0000000011101000010000100011110000001111100110011111111100000000
|
||||
0x00e8423c0f99ff00,
|
||||
//1110100001000010001111000000111110011001111111110000000000000000
|
||||
0xe8423c0f99ff0000,
|
||||
//0100001000111100000011111001100111111111000000000000000011101000
|
||||
0x423c0f99ff0000e8,
|
||||
//0011110000001111100110011111111100000000000000001110100001000010
|
||||
0x3c0f99ff0000e842,
|
||||
//0000111110011001111111110000000000000000111010000100001000111100
|
||||
0x0f99ff0000e8423c,
|
||||
//1001100111111111000000000000000011101000010000100011110000001111
|
||||
0x99ff0000e8423c0f,
|
||||
//1111111100000000000000001110100001000010001111000000111110011001
|
||||
0xff0000e8423c0f99
|
||||
};
|
||||
*/
|
||||
|
||||
/*
|
||||
* Compressed version of table above, saves 48 bytes. Rotating value in register
|
||||
* results in exactly the same size as full table, due to cost of loading values
|
||||
* into registers.
|
||||
*/
|
||||
static uint8_t ecc_matrix[] = {
|
||||
0x00, 0x00, 0xe8, 0x42, 0x3c, 0x0f, 0x99, 0xff,
|
||||
0x00, 0x00, 0xe8, 0x42, 0x3c, 0x0f, 0x99
|
||||
};
|
||||
|
||||
static uint8_t syndrome_matrix[] = {
|
||||
GD, E7, E6, UE, E5, UE, UE, 47, E4, UE, UE, 37, UE, 35, 39, UE,
|
||||
E3, UE, UE, 48, UE, 30, 29, UE, UE, 57, 27, UE, 31, UE, UE, UE,
|
||||
E2, UE, UE, 17, UE, 18, 40, UE, UE, 58, 22, UE, 21, UE, UE, UE,
|
||||
UE, 16, 49, UE, 19, UE, UE, UE, 23, UE, UE, UE, UE, 20, UE, UE,
|
||||
E1, UE, UE, 51, UE, 46, 9, UE, UE, 34, 10, UE, 32, UE, UE, 36,
|
||||
UE, 62, 50, UE, 14, UE, UE, UE, 13, UE, UE, UE, UE, UE, UE, UE,
|
||||
UE, 61, 8, UE, 41, UE, UE, UE, 11, UE, UE, UE, UE, UE, UE, UE,
|
||||
15, UE, UE, UE, UE, UE, UE, UE, UE, UE, 12, UE, UE, UE, UE, UE,
|
||||
E0, UE, UE, 55, UE, 45, 43, UE, UE, 56, 38, UE, 1, UE, UE, UE,
|
||||
UE, 25, 26, UE, 2, UE, UE, UE, 24, UE, UE, UE, UE, UE, 28, UE,
|
||||
UE, 59, 54, UE, 42, UE, UE, 44, 6, UE, UE, UE, UE, UE, UE, UE,
|
||||
5, UE, UE, UE, UE, UE, UE, UE, UE, UE, UE, UE, UE, UE, UE, UE,
|
||||
UE, 63, 53, UE, 0, UE, UE, UE, 33, UE, UE, UE, UE, UE, UE, UE,
|
||||
3, UE, UE, 52, UE, UE, UE, UE, UE, UE, UE, UE, UE, UE, UE, UE,
|
||||
7, UE, UE, UE, UE, UE, UE, UE, UE, 60, UE, UE, UE, UE, UE, UE,
|
||||
UE, UE, UE, UE, 4, UE, UE, UE, UE, UE, UE, UE, UE, UE, UE, UE,
|
||||
};
|
||||
|
||||
static inline uint32_t rd32(void *addr)
|
||||
{
|
||||
uint64_t ret;
|
||||
|
||||
/* Cache-inhibited load word */
|
||||
asm volatile("lwzcix %0, 0, %1"
|
||||
: "=r" (ret)
|
||||
: "r" (addr)
|
||||
: );
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static uint64_t rd64_unaligned(void *addr, int first_read)
|
||||
{
|
||||
static uint64_t tmp1; /* static is used to reduce number of PNOR reads */
|
||||
uint64_t tmp2;
|
||||
uint64_t ret;
|
||||
uint64_t addr_aligned = ALIGN_DOWN((uint64_t)addr, 8);
|
||||
unsigned int shift = 8 * ((uint64_t) addr - addr_aligned);
|
||||
|
||||
if (shift == 0 /* Previous tmp2 ended with ECC byte */
|
||||
|| first_read) { /* or it is the first invocation from remove_ecc */
|
||||
asm volatile("ldcix %0, 0, %1"
|
||||
: "=r" (tmp1)
|
||||
: "r" (addr_aligned)
|
||||
: );
|
||||
}
|
||||
|
||||
asm volatile("ldcix %0, 0, %1"
|
||||
: "=r" (tmp2)
|
||||
: "r" (addr_aligned+8)
|
||||
: );
|
||||
|
||||
ret = (tmp1 << shift) | (tmp2 >> (64 - shift));
|
||||
tmp1 = tmp2;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* memcpy from cache-inhibited source
|
||||
*
|
||||
* Assume src is 8B-aligned and does not overlap with dest. Copies ALIGN(n,8)
|
||||
* bytes, make sure dest is big enough.
|
||||
*/
|
||||
static inline void memcpy_ci_src(void *dest, const void *src, size_t n)
|
||||
{
|
||||
int i;
|
||||
uint64_t tmp;
|
||||
for (i = 0; i < n; i += 8) {
|
||||
asm volatile("ldcix %0, %1, %2"
|
||||
: "=r" (tmp)
|
||||
: "b"(src), "r" (i));
|
||||
asm volatile("stdx %0, %1, %2"
|
||||
:: "r" (tmp), "b"(dest), "r" (i)
|
||||
: "memory");
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t generate_ecc(uint64_t i_data)
|
||||
{
|
||||
uint8_t result = 0;
|
||||
|
||||
for (int i = 0; i < 8; i++)
|
||||
result |= __builtin_parityll((*(uint64_t *)&ecc_matrix[i]) & i_data) << i;
|
||||
return result;
|
||||
}
|
||||
|
||||
static uint8_t verify_ecc(uint64_t i_data, uint8_t i_ecc)
|
||||
{
|
||||
return syndrome_matrix[generate_ecc(i_data) ^ i_ecc];
|
||||
}
|
||||
|
||||
static uint8_t correct_ecc(uint64_t *io_data, uint8_t *io_ecc)
|
||||
{
|
||||
uint8_t bad_bit = verify_ecc(*io_data, *io_ecc);
|
||||
|
||||
if ((bad_bit != GD) && (bad_bit != UE)) { /* Good is done, UE is hopeless */
|
||||
/* Determine if the ECC or data part is bad, do bit flip. */
|
||||
if (bad_bit >= E7)
|
||||
*io_ecc ^= (1 << (bad_bit - E7));
|
||||
else
|
||||
*io_data ^= (1ull << (63 - bad_bit));
|
||||
}
|
||||
return bad_bit;
|
||||
}
|
||||
|
||||
static ecc_status_t remove_ecc(uint8_t *io_src, size_t i_srcSz,
|
||||
uint8_t *o_dst, size_t i_dstSz)
|
||||
{
|
||||
ecc_status_t rc = CLEAN;
|
||||
int first_read = 1;
|
||||
|
||||
for (size_t i = 0, o = 0; i < i_srcSz;
|
||||
i += sizeof(uint64_t) + sizeof(uint8_t), o += sizeof(uint64_t)) {
|
||||
/*
|
||||
* Read data and ECC parts. Reads from cache-inhibited storage always
|
||||
* have to be aligned!
|
||||
*/
|
||||
uint64_t data = rd64_unaligned(&io_src[i], first_read);
|
||||
first_read = 0;
|
||||
|
||||
uint8_t ecc = io_src[i + sizeof(uint64_t)];
|
||||
|
||||
/* Calculate failing bit and fix data */
|
||||
uint8_t bad_bit = correct_ecc(&data, &ecc);
|
||||
|
||||
/* Perform correction and status update */
|
||||
if (bad_bit == UE)
|
||||
rc = UNCORRECTABLE;
|
||||
/* Unused, our source is not writable */
|
||||
/*
|
||||
else if (bad_bit != GD)
|
||||
{
|
||||
if (rc != UNCORRECTABLE)
|
||||
{
|
||||
rc = CORRECTED;
|
||||
}
|
||||
|
||||
*(uint64_t*)(&io_src[i]) = data;
|
||||
io_src[i + sizeof(uint64_t)] = ecc;
|
||||
}
|
||||
*/
|
||||
|
||||
/* Copy fixed data to destination buffer */
|
||||
*(uint64_t *)(&o_dst[o]) = data;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static char *pnor_base;
|
||||
|
||||
/*
|
||||
* PNOR has to be accessed with Cache Inhibited forms of instructions, and they
|
||||
* require that the address is aligned, so we can just memcpy the data.
|
||||
*/
|
||||
static ssize_t no_ecc_readat(const struct region_device *rd, void *b,
|
||||
size_t offset, size_t size)
|
||||
{
|
||||
uint8_t tmp[8];
|
||||
offset -= rd->region.offset;
|
||||
size_t off_a = ALIGN_DOWN(offset, 8);
|
||||
size_t size_left = size;
|
||||
char *part_base = pnor_base + rd->region.offset;
|
||||
|
||||
/* If offset is not 8B-aligned */
|
||||
if (offset & 0x7) {
|
||||
int i;
|
||||
memcpy_ci_src(tmp, &part_base[off_a], 8);
|
||||
for (i = 8 - (offset & 7); i < 8; i++) {
|
||||
*((uint8_t *)(b++)) = tmp[i];
|
||||
if (!--size_left)
|
||||
return size;
|
||||
}
|
||||
off_a += 8;
|
||||
}
|
||||
|
||||
/* Align down size_left to 8B */
|
||||
memcpy_ci_src(b, &part_base[off_a], ALIGN_DOWN(size_left, 8));
|
||||
|
||||
/* Copy the rest of requested unaligned data, if any */
|
||||
if (size_left & 7) {
|
||||
off_a += ALIGN_DOWN(size_left, 8);
|
||||
b += ALIGN_DOWN(size_left, 8);
|
||||
int i;
|
||||
memcpy_ci_src(tmp, &part_base[off_a], 8);
|
||||
for (i = 0; i < (size_left & 7); i++)
|
||||
*((uint8_t *)(b++)) = tmp[i];
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static ssize_t ecc_readat(const struct region_device *rd, void *b,
|
||||
size_t offset, size_t size)
|
||||
{
|
||||
uint8_t tmp[8];
|
||||
offset -= rd->region.offset;
|
||||
size_t off_a = ALIGN_DOWN(offset, 8);
|
||||
size_t size_left = size;
|
||||
char *part_base = pnor_base + rd->region.offset;
|
||||
|
||||
/* If offset is not 8B-aligned */
|
||||
if (offset & 0x7) {
|
||||
int i;
|
||||
remove_ecc((uint8_t *) &part_base[(off_a * 9)/8], 9, tmp, 8);
|
||||
for (i = 8 - (offset & 7); i < 8; i++) {
|
||||
*((uint8_t *)(b++)) = tmp[i];
|
||||
if (!--size_left)
|
||||
return size;
|
||||
}
|
||||
off_a += 8;
|
||||
}
|
||||
|
||||
/* Align down size_left to 8B */
|
||||
remove_ecc((uint8_t *) &part_base[(off_a * 9)/8],
|
||||
(ALIGN_DOWN(size_left, 8) * 9) / 8,
|
||||
b,
|
||||
ALIGN_DOWN(size_left, 8));
|
||||
|
||||
/* Copy the rest of requested unaligned data, if any */
|
||||
if (size_left & 7) {
|
||||
off_a += ALIGN_DOWN(size_left, 8);
|
||||
b += ALIGN_DOWN(size_left, 8);
|
||||
int i;
|
||||
remove_ecc((uint8_t *) &part_base[(off_a * 9)/8], 9, tmp, 8);
|
||||
for (i = 0; i < (size_left & 7); i++)
|
||||
*((uint8_t *)(b++)) = tmp[i];
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static struct region_device_ops no_ecc_rdev_ops = {
|
||||
.mmap = mmap_helper_rdev_mmap,
|
||||
.munmap = mmap_helper_rdev_munmap,
|
||||
.readat = no_ecc_readat,
|
||||
};
|
||||
|
||||
static struct region_device_ops ecc_rdev_ops = {
|
||||
.mmap = mmap_helper_rdev_mmap,
|
||||
.munmap = mmap_helper_rdev_munmap,
|
||||
.readat = ecc_readat,
|
||||
};
|
||||
|
||||
static void mount_part_from_pnor(const char *part_name,
|
||||
struct mmap_helper_region_device *mdev)
|
||||
{
|
||||
size_t base, size;
|
||||
unsigned int i, block_size, entry_count = 0;
|
||||
struct ffs_hdr *hdr_pnor = (struct ffs_hdr *)LPC_FLASH_TOP;
|
||||
|
||||
/* This loop could be skipped if we may assume that PNOR is always 64M */
|
||||
while (hdr_pnor > (struct ffs_hdr *)LPC_FLASH_MIN) {
|
||||
uint32_t csum = 0;
|
||||
/* Size is aligned up to 8 because of how memcpy_ci_src works */
|
||||
uint8_t buffer[ALIGN(FFS_HDR_SIZE, 8)];
|
||||
struct ffs_hdr *hdr = (struct ffs_hdr *)buffer;
|
||||
|
||||
/* Assume block_size = 4K */
|
||||
hdr_pnor = (struct ffs_hdr *)(((char *)hdr_pnor) - 0x1000);
|
||||
|
||||
if (rd32(&hdr_pnor->magic) != FFS_MAGIC)
|
||||
continue;
|
||||
|
||||
if (rd32(&hdr_pnor->version) != FFS_VERSION_1)
|
||||
continue;
|
||||
|
||||
/* Copy the header so we won't have to rd32() for further accesses */
|
||||
memcpy_ci_src(buffer, hdr_pnor, FFS_HDR_SIZE);
|
||||
csum = hdr->magic ^ hdr->version ^ hdr->size ^ hdr->entry_size ^
|
||||
hdr->entry_count ^ hdr->block_size ^ hdr->block_count ^
|
||||
hdr->resvd[0] ^ hdr->resvd[1] ^ hdr->resvd[2] ^ hdr->resvd[3] ^
|
||||
hdr->checksum;
|
||||
if (csum != 0)
|
||||
continue;
|
||||
|
||||
pnor_base = (char *) LPC_FLASH_TOP - hdr->block_size * hdr->block_count;
|
||||
entry_count = hdr->entry_count;
|
||||
block_size = hdr->block_size;
|
||||
|
||||
/* Every byte counts when building for SEEPROM */
|
||||
if (!CONFIG(BOOTBLOCK_IN_SEEPROM)) {
|
||||
printk(BIOS_DEBUG, "FFS header at %p\n", hdr_pnor);
|
||||
printk(BIOS_SPEW, " size %x\n", hdr->size);
|
||||
printk(BIOS_SPEW, " entry_size %x\n", hdr->entry_size);
|
||||
printk(BIOS_SPEW, " entry_count %x\n", hdr->entry_count);
|
||||
printk(BIOS_SPEW, " block_size %x\n", hdr->block_size);
|
||||
printk(BIOS_SPEW, " block_count %x\n", hdr->block_count);
|
||||
printk(BIOS_DEBUG, "PNOR base at %p\n", pnor_base);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (hdr_pnor <= (struct ffs_hdr *)LPC_FLASH_MIN)
|
||||
die("FFS header not found!\n");
|
||||
|
||||
for (i = 0; i < entry_count; i++) {
|
||||
uint32_t *val, csum = 0;
|
||||
int j;
|
||||
/* Size is aligned up to 8 because of how memcpy_ci_src works */
|
||||
uint8_t buffer[ALIGN(FFS_ENTRY_SIZE, 8)];
|
||||
struct ffs_entry *e = (struct ffs_entry *)buffer;
|
||||
|
||||
/* Copy the entry so we won't have to rd32() for further accesses */
|
||||
memcpy_ci_src(buffer, &hdr_pnor->entries[i], FFS_ENTRY_SIZE);
|
||||
|
||||
/* Every byte counts when building for SEEPROM */
|
||||
if (!CONFIG(BOOTBLOCK_IN_SEEPROM)) {
|
||||
printk(BIOS_SPEW, "%s: base %x, size %x (%x)\n\t type %x, flags %x\n",
|
||||
e->name, e->base, e->size, e->actual, e->type, e->flags);
|
||||
}
|
||||
|
||||
if (strcmp(e->name, part_name) != 0)
|
||||
continue;
|
||||
|
||||
val = (uint32_t *) e;
|
||||
for (j = 0; j < (FFS_ENTRY_SIZE / sizeof(uint32_t)); j++)
|
||||
csum ^= val[j];
|
||||
|
||||
if (csum != 0)
|
||||
continue;
|
||||
|
||||
base = block_size * e->base;
|
||||
/* This is size of the partition, it does not include header or ECC */
|
||||
size = e->actual;
|
||||
|
||||
mdev->rdev.ops = &no_ecc_rdev_ops;
|
||||
|
||||
if (e->user.data[0] & FFS_ENRY_INTEG_ECC) {
|
||||
printk(BIOS_DEBUG, "%s partition has ECC\n", part_name);
|
||||
mdev->rdev.ops = &ecc_rdev_ops;
|
||||
size = size / 9 * 8;
|
||||
}
|
||||
|
||||
if ((e->user.data[1] >> 24) & FFS_VERS_SHA512) {
|
||||
/* Skip PNOR partition header */
|
||||
base += 0x1000;
|
||||
|
||||
/* Possibly skip ECC of the header */
|
||||
if (e->user.data[0] & FFS_ENRY_INTEG_ECC)
|
||||
base += 0x200;
|
||||
}
|
||||
|
||||
mdev->rdev.region.offset = base;
|
||||
mdev->rdev.region.size = size;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static struct mmap_helper_region_device boot_mdev = MMAP_HELPER_DEV_INIT(
|
||||
&no_ecc_rdev_ops, 0, CONFIG_ROM_SIZE, &cbfs_cache);
|
||||
|
||||
void boot_device_init(void)
|
||||
{
|
||||
static int init_done;
|
||||
if (init_done)
|
||||
return;
|
||||
|
||||
mount_part_from_pnor(CBFS_PARTITION_NAME, &boot_mdev);
|
||||
|
||||
init_done = 1;
|
||||
}
|
||||
|
||||
const struct region_device *boot_device_ro(void)
|
||||
{
|
||||
return NULL;
|
||||
return &boot_mdev.rdev;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue