1194 lines
28 KiB
C
1194 lines
28 KiB
C
// SPDX-License-Identifier: Apache-2.0
|
|
/*
|
|
* Init, manage, read, write, and load resources from flash
|
|
*
|
|
* Copyright 2013-2019 IBM Corp.
|
|
* Copyright 2018-2019 Raptor Engineering, LLC
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "FLASH: " fmt
|
|
|
|
#include <skiboot.h>
|
|
#include <cpu.h>
|
|
#include <lock.h>
|
|
#include <opal.h>
|
|
#include <opal-msg.h>
|
|
#include <platform.h>
|
|
#include <device.h>
|
|
#include <libflash/libflash.h>
|
|
#include <libflash/libffs.h>
|
|
#include <libflash/ipmi-hiomap.h>
|
|
#include <libflash/blocklevel.h>
|
|
#include <libflash/ecc.h>
|
|
#include <libstb/secureboot.h>
|
|
#include <libstb/trustedboot.h>
|
|
#include <libxz/xz.h>
|
|
#include <elf.h>
|
|
#include <timebase.h>
|
|
|
|
struct flash {
|
|
struct list_node list;
|
|
bool busy;
|
|
bool no_erase;
|
|
struct blocklevel_device *bl;
|
|
uint64_t size;
|
|
uint32_t block_size;
|
|
int id;
|
|
};
|
|
|
|
static struct {
|
|
enum resource_id id;
|
|
uint32_t subid;
|
|
char name[PART_NAME_MAX+1];
|
|
} part_name_map[] = {
|
|
{ RESOURCE_ID_KERNEL, RESOURCE_SUBID_NONE, "BOOTKERNEL" },
|
|
{ RESOURCE_ID_INITRAMFS,RESOURCE_SUBID_NONE, "ROOTFS" },
|
|
{ RESOURCE_ID_CAPP, RESOURCE_SUBID_SUPPORTED, "CAPP" },
|
|
{ RESOURCE_ID_IMA_CATALOG, RESOURCE_SUBID_SUPPORTED, "IMA_CATALOG" },
|
|
{ RESOURCE_ID_VERSION, RESOURCE_SUBID_NONE, "VERSION" },
|
|
{ RESOURCE_ID_KERNEL_FW, RESOURCE_SUBID_NONE, "BOOTKERNFW" },
|
|
};
|
|
|
|
static LIST_HEAD(flashes);
|
|
static struct flash *system_flash;
|
|
|
|
/* Using a single lock as we only have one flash at present. */
|
|
static struct lock flash_lock;
|
|
|
|
/* nvram-on-flash support */
|
|
static struct flash *nvram_flash;
|
|
static u32 nvram_offset, nvram_size;
|
|
|
|
/* secboot-on-flash support */
|
|
static struct flash *secboot_flash;
|
|
static u32 secboot_offset, secboot_size;
|
|
|
|
bool flash_reserve(void)
|
|
{
|
|
bool rc = false;
|
|
|
|
if (!try_lock(&flash_lock))
|
|
return false;
|
|
|
|
if (!system_flash->busy) {
|
|
system_flash->busy = true;
|
|
rc = true;
|
|
}
|
|
unlock(&flash_lock);
|
|
|
|
return rc;
|
|
}
|
|
|
|
void flash_release(void)
|
|
{
|
|
lock(&flash_lock);
|
|
system_flash->busy = false;
|
|
unlock(&flash_lock);
|
|
}
|
|
|
|
bool flash_unregister(void)
|
|
{
|
|
struct blocklevel_device *bl = system_flash->bl;
|
|
|
|
if (bl->exit)
|
|
return bl->exit(bl);
|
|
|
|
prlog(PR_NOTICE, "Unregister flash device is not supported\n");
|
|
return true;
|
|
}
|
|
|
|
int flash_secboot_info(uint32_t *total_size)
|
|
{
|
|
int rc;
|
|
|
|
lock(&flash_lock);
|
|
if (!secboot_flash) {
|
|
rc = OPAL_HARDWARE;
|
|
} else if (secboot_flash->busy) {
|
|
rc = OPAL_BUSY;
|
|
} else {
|
|
*total_size = secboot_size;
|
|
rc = OPAL_SUCCESS;
|
|
}
|
|
unlock(&flash_lock);
|
|
|
|
return rc;
|
|
}
|
|
|
|
int flash_secboot_read(void *dst, uint32_t src, uint32_t len)
|
|
{
|
|
int rc;
|
|
|
|
if (!try_lock(&flash_lock))
|
|
return OPAL_BUSY;
|
|
|
|
if (!secboot_flash) {
|
|
rc = OPAL_HARDWARE;
|
|
goto out;
|
|
}
|
|
|
|
if (secboot_flash->busy) {
|
|
rc = OPAL_BUSY;
|
|
goto out;
|
|
}
|
|
|
|
if ((src + len) > secboot_size) {
|
|
prerror("FLASH_SECBOOT: read out of bound (0x%x,0x%x)\n",
|
|
src, len);
|
|
rc = OPAL_PARAMETER;
|
|
goto out;
|
|
}
|
|
|
|
secboot_flash->busy = true;
|
|
unlock(&flash_lock);
|
|
|
|
rc = blocklevel_read(secboot_flash->bl, secboot_offset + src, dst, len);
|
|
|
|
lock(&flash_lock);
|
|
secboot_flash->busy = false;
|
|
out:
|
|
unlock(&flash_lock);
|
|
return rc;
|
|
}
|
|
|
|
int flash_secboot_write(uint32_t dst, void *src, uint32_t len)
|
|
{
|
|
int rc;
|
|
|
|
if (!try_lock(&flash_lock))
|
|
return OPAL_BUSY;
|
|
|
|
if (secboot_flash->busy) {
|
|
rc = OPAL_BUSY;
|
|
goto out;
|
|
}
|
|
|
|
if ((dst + len) > secboot_size) {
|
|
prerror("FLASH_SECBOOT: write out of bound (0x%x,0x%x)\n",
|
|
dst, len);
|
|
rc = OPAL_PARAMETER;
|
|
goto out;
|
|
}
|
|
|
|
secboot_flash->busy = true;
|
|
unlock(&flash_lock);
|
|
|
|
rc = blocklevel_write(secboot_flash->bl, secboot_offset + dst, src, len);
|
|
|
|
lock(&flash_lock);
|
|
secboot_flash->busy = false;
|
|
out:
|
|
unlock(&flash_lock);
|
|
return rc;
|
|
}
|
|
|
|
static int flash_nvram_info(uint32_t *total_size)
|
|
{
|
|
int rc;
|
|
|
|
lock(&flash_lock);
|
|
if (!nvram_flash) {
|
|
rc = OPAL_HARDWARE;
|
|
} else if (nvram_flash->busy) {
|
|
rc = OPAL_BUSY;
|
|
} else {
|
|
*total_size = nvram_size;
|
|
rc = OPAL_SUCCESS;
|
|
}
|
|
unlock(&flash_lock);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int flash_nvram_start_read(void *dst, uint32_t src, uint32_t len)
|
|
{
|
|
int rc;
|
|
|
|
if (!try_lock(&flash_lock))
|
|
return OPAL_BUSY;
|
|
|
|
if (!nvram_flash) {
|
|
rc = OPAL_HARDWARE;
|
|
goto out;
|
|
}
|
|
|
|
if (nvram_flash->busy) {
|
|
rc = OPAL_BUSY;
|
|
goto out;
|
|
}
|
|
|
|
if ((src + len) > nvram_size) {
|
|
prerror("NVRAM: read out of bound (0x%x,0x%x)\n",
|
|
src, len);
|
|
rc = OPAL_PARAMETER;
|
|
goto out;
|
|
}
|
|
|
|
nvram_flash->busy = true;
|
|
unlock(&flash_lock);
|
|
|
|
rc = blocklevel_read(nvram_flash->bl, nvram_offset + src, dst, len);
|
|
|
|
lock(&flash_lock);
|
|
nvram_flash->busy = false;
|
|
out:
|
|
unlock(&flash_lock);
|
|
if (!rc)
|
|
nvram_read_complete(true);
|
|
return rc;
|
|
}
|
|
|
|
static int flash_nvram_write(uint32_t dst, void *src, uint32_t len)
|
|
{
|
|
int rc;
|
|
|
|
if (!try_lock(&flash_lock))
|
|
return OPAL_BUSY;
|
|
|
|
if (nvram_flash->busy) {
|
|
rc = OPAL_BUSY;
|
|
goto out;
|
|
}
|
|
|
|
/* TODO: When we have async jobs for PRD, turn this into one */
|
|
|
|
if ((dst + len) > nvram_size) {
|
|
prerror("NVRAM: write out of bound (0x%x,0x%x)\n",
|
|
dst, len);
|
|
rc = OPAL_PARAMETER;
|
|
goto out;
|
|
}
|
|
|
|
nvram_flash->busy = true;
|
|
unlock(&flash_lock);
|
|
|
|
rc = blocklevel_write(nvram_flash->bl, nvram_offset + dst, src, len);
|
|
|
|
lock(&flash_lock);
|
|
nvram_flash->busy = false;
|
|
out:
|
|
unlock(&flash_lock);
|
|
return rc;
|
|
}
|
|
|
|
|
|
static int flash_secboot_probe(struct flash *flash, struct ffs_handle *ffs)
|
|
{
|
|
uint32_t start, size, part;
|
|
bool ecc;
|
|
int rc;
|
|
|
|
prlog(PR_DEBUG, "FLASH: probing for SECBOOT\n");
|
|
|
|
rc = ffs_lookup_part(ffs, "SECBOOT", &part);
|
|
if (rc) {
|
|
prlog(PR_WARNING, "FLASH: no SECBOOT partition found\n");
|
|
return OPAL_HARDWARE;
|
|
}
|
|
|
|
rc = ffs_part_info(ffs, part, NULL,
|
|
&start, &size, NULL, &ecc);
|
|
if (rc) {
|
|
/**
|
|
* @fwts-label SECBOOTNoPartition
|
|
* @fwts-advice OPAL could not find an SECBOOT partition
|
|
* on the system flash. Check that the system flash
|
|
* has a valid partition table, and that the firmware
|
|
* build process has added a SECBOOT partition.
|
|
*/
|
|
prlog(PR_ERR, "FLASH: Can't parse ffs info for SECBOOT\n");
|
|
return OPAL_HARDWARE;
|
|
}
|
|
|
|
secboot_flash = flash;
|
|
secboot_offset = start;
|
|
secboot_size = ecc ? ecc_buffer_size_minus_ecc(size) : size;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int flash_nvram_probe(struct flash *flash, struct ffs_handle *ffs)
|
|
{
|
|
uint32_t start, size, part;
|
|
bool ecc;
|
|
int rc;
|
|
|
|
prlog(PR_INFO, "probing for NVRAM\n");
|
|
|
|
rc = ffs_lookup_part(ffs, "NVRAM", &part);
|
|
if (rc) {
|
|
prlog(PR_WARNING, "no NVRAM partition found\n");
|
|
return OPAL_HARDWARE;
|
|
}
|
|
|
|
rc = ffs_part_info(ffs, part, NULL,
|
|
&start, &size, NULL, &ecc);
|
|
if (rc) {
|
|
/**
|
|
* @fwts-label NVRAMNoPartition
|
|
* @fwts-advice OPAL could not find an NVRAM partition
|
|
* on the system flash. Check that the system flash
|
|
* has a valid partition table, and that the firmware
|
|
* build process has added a NVRAM partition.
|
|
*/
|
|
prlog(PR_ERR, "Can't parse ffs info for NVRAM\n");
|
|
return OPAL_HARDWARE;
|
|
}
|
|
|
|
nvram_flash = flash;
|
|
nvram_offset = start;
|
|
nvram_size = ecc ? ecc_buffer_size_minus_ecc(size) : size;
|
|
|
|
platform.nvram_info = flash_nvram_info;
|
|
platform.nvram_start_read = flash_nvram_start_read;
|
|
platform.nvram_write = flash_nvram_write;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* core flash support */
|
|
|
|
static struct dt_node *flash_add_dt_node(struct flash *flash, int id)
|
|
{
|
|
int i;
|
|
int rc;
|
|
const char *name;
|
|
bool ecc;
|
|
struct ffs_handle *ffs;
|
|
int ffs_part_num, ffs_part_start, ffs_part_size;
|
|
struct dt_node *flash_node;
|
|
struct dt_node *partition_container_node;
|
|
struct dt_node *partition_node;
|
|
|
|
flash_node = dt_new_addr(opal_node, "flash", id);
|
|
dt_add_property_strings(flash_node, "compatible", "ibm,opal-flash");
|
|
dt_add_property_cells(flash_node, "ibm,opal-id", id);
|
|
/*
|
|
* linux kernel reads this as u64 and not address-cells and size-cells encoded
|
|
* value.
|
|
*/
|
|
dt_add_property_u64(flash_node, "reg", flash->size);
|
|
dt_add_property_cells(flash_node, "ibm,flash-block-size",
|
|
flash->block_size);
|
|
if (flash->no_erase)
|
|
dt_add_property(flash_node, "no-erase", NULL, 0);
|
|
|
|
/* Add partition container node */
|
|
partition_container_node = dt_new(flash_node, "partitions");
|
|
dt_add_property_strings(partition_container_node, "compatible", "fixed-partitions");
|
|
|
|
dt_add_property_cells(partition_container_node, "#address-cells", 2);
|
|
dt_add_property_cells(partition_container_node, "#size-cells", 2);
|
|
|
|
/* Add partitions */
|
|
for (i = 0, name = NULL; i < ARRAY_SIZE(part_name_map); i++) {
|
|
name = part_name_map[i].name;
|
|
|
|
rc = ffs_init(0, flash->size, flash->bl, &ffs, 1);
|
|
if (rc) {
|
|
prerror("Can't open ffs handle\n");
|
|
continue;
|
|
}
|
|
|
|
rc = ffs_lookup_part(ffs, name, &ffs_part_num);
|
|
if (rc) {
|
|
/* This is not an error per-se, some partitions
|
|
* are purposefully absent, don't spam the logs
|
|
*/
|
|
prlog(PR_DEBUG, "No %s partition\n", name);
|
|
continue;
|
|
}
|
|
rc = ffs_part_info(ffs, ffs_part_num, NULL,
|
|
&ffs_part_start, NULL, &ffs_part_size, &ecc);
|
|
if (rc) {
|
|
prerror("Failed to get %s partition info\n", name);
|
|
continue;
|
|
}
|
|
|
|
partition_node = dt_new_addr(partition_container_node, "partition", ffs_part_start);
|
|
dt_add_property_strings(partition_node, "label", name);
|
|
/*
|
|
* flash API supports only 32 bit offset and size. So encode
|
|
* the high bits as zero.
|
|
*/
|
|
dt_add_property_u64s(partition_node, "reg", ffs_part_start, ffs_part_size);
|
|
|
|
if (part_name_map[i].id != RESOURCE_ID_KERNEL_FW) {
|
|
/* Mark all partitions other than the full PNOR and the boot kernel
|
|
* firmware as read only. These two partitions are the only partitions
|
|
* that are properly erase block aligned at this time.
|
|
*/
|
|
dt_add_property(partition_node, "read-only", NULL, 0);
|
|
}
|
|
}
|
|
|
|
partition_node = dt_new_addr(partition_container_node, "partition", 0);
|
|
dt_add_property_strings(partition_node, "label", "PNOR");
|
|
/*
|
|
* we can find larger size here due to support for more than 4G
|
|
* mambo bogus disk. So encode them correctly.
|
|
*/
|
|
dt_add_property_u64s(partition_node, "reg", 0, flash->size);
|
|
|
|
return flash_node;
|
|
}
|
|
|
|
static void setup_system_flash(struct flash *flash, struct dt_node *node,
|
|
const char *name, struct ffs_handle *ffs)
|
|
{
|
|
char *path;
|
|
|
|
if (!ffs)
|
|
return;
|
|
|
|
if (system_flash) {
|
|
/**
|
|
* @fwts-label SystemFlashMultiple
|
|
* @fwts-advice OPAL Found multiple system flash.
|
|
* Since we've already found a system flash we are
|
|
* going to use that one but this ordering is not
|
|
* guaranteed so may change in future.
|
|
*/
|
|
prlog(PR_WARNING, "Attempted to register multiple system "
|
|
"flash: %s\n", name);
|
|
return;
|
|
}
|
|
|
|
prlog(PR_NOTICE, "Found system flash: %s id:%i\n",
|
|
name, flash->id);
|
|
|
|
system_flash = flash;
|
|
path = dt_get_path(node);
|
|
dt_add_property_string(dt_chosen, "ibm,system-flash", path);
|
|
free(path);
|
|
|
|
prlog(PR_INFO, "registered system flash device %s\n", name);
|
|
|
|
flash_nvram_probe(flash, ffs);
|
|
flash_secboot_probe(flash, ffs);
|
|
}
|
|
|
|
static int num_flashes(void)
|
|
{
|
|
struct flash *flash;
|
|
int i = 0;
|
|
|
|
list_for_each(&flashes, flash, list)
|
|
i++;
|
|
|
|
return i;
|
|
}
|
|
|
|
int flash_register(struct blocklevel_device *bl)
|
|
{
|
|
uint64_t size;
|
|
uint32_t block_size;
|
|
struct ffs_handle *ffs;
|
|
struct dt_node *node;
|
|
struct flash *flash;
|
|
const char *name;
|
|
int rc;
|
|
|
|
rc = blocklevel_get_info(bl, &name, &size, &block_size);
|
|
if (rc)
|
|
return rc;
|
|
|
|
if (!name)
|
|
name = "(unnamed)";
|
|
|
|
prlog(PR_INFO, "registering flash device %s "
|
|
"(size 0x%llx, blocksize 0x%x)\n",
|
|
name, size, block_size);
|
|
|
|
flash = malloc(sizeof(struct flash));
|
|
if (!flash) {
|
|
prlog(PR_ERR, "Error allocating flash structure\n");
|
|
return OPAL_RESOURCE;
|
|
}
|
|
|
|
flash->busy = false;
|
|
flash->bl = bl;
|
|
flash->no_erase = !(bl->flags & WRITE_NEED_ERASE);
|
|
flash->size = size;
|
|
flash->block_size = block_size;
|
|
flash->id = num_flashes();
|
|
|
|
rc = ffs_init(0, flash->size, bl, &ffs, 1);
|
|
if (rc) {
|
|
/**
|
|
* @fwts-label NoFFS
|
|
* @fwts-advice System flash isn't formatted as expected.
|
|
* This could mean several OPAL utilities do not function
|
|
* as expected. e.g. gard, pflash.
|
|
*/
|
|
prlog(PR_WARNING, "No ffs info; "
|
|
"using raw device only\n");
|
|
ffs = NULL;
|
|
}
|
|
|
|
node = flash_add_dt_node(flash, flash->id);
|
|
|
|
setup_system_flash(flash, node, name, ffs);
|
|
|
|
if (ffs)
|
|
ffs_close(ffs);
|
|
|
|
lock(&flash_lock);
|
|
list_add(&flashes, &flash->list);
|
|
unlock(&flash_lock);
|
|
|
|
return OPAL_SUCCESS;
|
|
}
|
|
|
|
enum flash_op {
|
|
FLASH_OP_READ,
|
|
FLASH_OP_WRITE,
|
|
FLASH_OP_ERASE,
|
|
};
|
|
|
|
static int64_t opal_flash_op(enum flash_op op, uint64_t id, uint64_t offset,
|
|
uint64_t buf, uint64_t size, uint64_t token)
|
|
{
|
|
struct flash *flash = NULL;
|
|
int rc;
|
|
|
|
if (!try_lock(&flash_lock))
|
|
return OPAL_BUSY;
|
|
|
|
list_for_each(&flashes, flash, list)
|
|
if (flash->id == id)
|
|
break;
|
|
|
|
if (flash->id != id) {
|
|
/* Couldn't find the flash */
|
|
rc = OPAL_PARAMETER;
|
|
goto err;
|
|
}
|
|
|
|
if (flash->busy) {
|
|
rc = OPAL_BUSY;
|
|
goto err;
|
|
}
|
|
|
|
if (size >= flash->size || offset >= flash->size
|
|
|| offset + size > flash->size) {
|
|
rc = OPAL_PARAMETER;
|
|
goto err;
|
|
}
|
|
|
|
/*
|
|
* These ops intentionally have no smarts (ecc correction or erase
|
|
* before write) to them.
|
|
* Skiboot is simply exposing the PNOR flash to the host.
|
|
* The host is expected to understand that this is a raw flash
|
|
* device and treat it as such.
|
|
*/
|
|
switch (op) {
|
|
case FLASH_OP_READ:
|
|
rc = blocklevel_raw_read(flash->bl, offset, (void *)buf, size);
|
|
break;
|
|
case FLASH_OP_WRITE:
|
|
rc = blocklevel_raw_write(flash->bl, offset, (void *)buf, size);
|
|
break;
|
|
case FLASH_OP_ERASE:
|
|
rc = blocklevel_erase(flash->bl, offset, size);
|
|
break;
|
|
default:
|
|
assert(0);
|
|
}
|
|
|
|
if (rc) {
|
|
rc = OPAL_HARDWARE;
|
|
goto err;
|
|
}
|
|
|
|
unlock(&flash_lock);
|
|
|
|
opal_queue_msg(OPAL_MSG_ASYNC_COMP, NULL, NULL,
|
|
cpu_to_be64(token),
|
|
cpu_to_be64(rc));
|
|
|
|
return OPAL_ASYNC_COMPLETION;
|
|
|
|
err:
|
|
unlock(&flash_lock);
|
|
return rc;
|
|
}
|
|
|
|
static int64_t opal_flash_read(uint64_t id, uint64_t offset, uint64_t buf,
|
|
uint64_t size, uint64_t token)
|
|
{
|
|
if (!opal_addr_valid((void *)buf))
|
|
return OPAL_PARAMETER;
|
|
|
|
return opal_flash_op(FLASH_OP_READ, id, offset, buf, size, token);
|
|
}
|
|
|
|
static int64_t opal_flash_write(uint64_t id, uint64_t offset, uint64_t buf,
|
|
uint64_t size, uint64_t token)
|
|
{
|
|
if (!opal_addr_valid((void *)buf))
|
|
return OPAL_PARAMETER;
|
|
|
|
return opal_flash_op(FLASH_OP_WRITE, id, offset, buf, size, token);
|
|
}
|
|
|
|
static int64_t opal_flash_erase(uint64_t id, uint64_t offset, uint64_t size,
|
|
uint64_t token)
|
|
{
|
|
return opal_flash_op(FLASH_OP_ERASE, id, offset, 0L, size, token);
|
|
}
|
|
|
|
opal_call(OPAL_FLASH_READ, opal_flash_read, 5);
|
|
opal_call(OPAL_FLASH_WRITE, opal_flash_write, 5);
|
|
opal_call(OPAL_FLASH_ERASE, opal_flash_erase, 4);
|
|
|
|
/* flash resource API */
|
|
const char *flash_map_resource_name(enum resource_id id)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(part_name_map); i++) {
|
|
if (part_name_map[i].id == id)
|
|
return part_name_map[i].name;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static size_t sizeof_elf_from_hdr(void *buf)
|
|
{
|
|
struct elf_hdr *elf = (struct elf_hdr *)buf;
|
|
size_t sz = 0;
|
|
|
|
BUILD_ASSERT(SECURE_BOOT_HEADERS_SIZE > sizeof(struct elf_hdr));
|
|
BUILD_ASSERT(SECURE_BOOT_HEADERS_SIZE > sizeof(struct elf64be_hdr));
|
|
BUILD_ASSERT(SECURE_BOOT_HEADERS_SIZE > sizeof(struct elf32be_hdr));
|
|
|
|
if (elf->ei_ident == ELF_IDENT) {
|
|
if (elf->ei_class == ELF_CLASS_64) {
|
|
if (elf->ei_data == ELF_DATA_LSB) {
|
|
struct elf64le_hdr *kh = (struct elf64le_hdr *)buf;
|
|
sz = le64_to_cpu(kh->e_shoff) +
|
|
((uint32_t)le16_to_cpu(kh->e_shentsize) *
|
|
(uint32_t)le16_to_cpu(kh->e_shnum));
|
|
} else {
|
|
struct elf64be_hdr *kh = (struct elf64be_hdr *)buf;
|
|
sz = be64_to_cpu(kh->e_shoff) +
|
|
((uint32_t)be16_to_cpu(kh->e_shentsize) *
|
|
(uint32_t)be16_to_cpu(kh->e_shnum));
|
|
}
|
|
} else if (elf->ei_class == ELF_CLASS_32) {
|
|
if (elf->ei_data == ELF_DATA_LSB) {
|
|
struct elf32le_hdr *kh = (struct elf32le_hdr *)buf;
|
|
sz = le32_to_cpu(kh->e_shoff) +
|
|
(le16_to_cpu(kh->e_shentsize) *
|
|
le16_to_cpu(kh->e_shnum));
|
|
} else {
|
|
struct elf32be_hdr *kh = (struct elf32be_hdr *)buf;
|
|
sz = be32_to_cpu(kh->e_shoff) +
|
|
(be16_to_cpu(kh->e_shentsize) *
|
|
be16_to_cpu(kh->e_shnum));
|
|
}
|
|
}
|
|
}
|
|
|
|
return sz;
|
|
}
|
|
|
|
/*
|
|
* load a resource from FLASH
|
|
* buf and len shouldn't account for ECC even if partition is ECCed.
|
|
*
|
|
* The API here is a bit strange.
|
|
* If resource has a STB container, buf will contain it
|
|
* If loading subpartition with STB container, buff will *NOT* contain it
|
|
* For trusted boot, the whole partition containing the subpart is measured.
|
|
*
|
|
* Additionally, the logic to work out how much to read from flash is insane.
|
|
*/
|
|
static int flash_load_resource(enum resource_id id, uint32_t subid,
|
|
void *buf, size_t *len)
|
|
{
|
|
int i;
|
|
int rc = OPAL_RESOURCE;
|
|
struct ffs_handle *ffs;
|
|
struct flash *flash;
|
|
const char *name;
|
|
bool status = false;
|
|
bool ecc;
|
|
bool part_signed = false;
|
|
void *bufp = buf;
|
|
size_t bufsz = *len;
|
|
int ffs_part_num, ffs_part_start, ffs_part_size;
|
|
int content_size = 0;
|
|
int offset = 0;
|
|
|
|
lock(&flash_lock);
|
|
|
|
if (!system_flash) {
|
|
/**
|
|
* @fwts-label SystemFlashNotFound
|
|
* @fwts-advice No system flash was found. Check for missing
|
|
* calls flash_register(...).
|
|
*/
|
|
prlog(PR_WARNING, "Can't load resource id:%i. "
|
|
"No system flash found\n", id);
|
|
goto out_unlock;
|
|
}
|
|
|
|
flash = system_flash;
|
|
|
|
if (flash->busy)
|
|
goto out_unlock;
|
|
|
|
for (i = 0, name = NULL; i < ARRAY_SIZE(part_name_map); i++) {
|
|
if (part_name_map[i].id == id) {
|
|
name = part_name_map[i].name;
|
|
break;
|
|
}
|
|
}
|
|
if (!name) {
|
|
prerror("Couldn't find partition for id %d\n", id);
|
|
goto out_unlock;
|
|
}
|
|
/*
|
|
* If partition doesn't have a subindex but the caller specifies one,
|
|
* we fail. eg. kernel partition doesn't have a subindex
|
|
*/
|
|
if ((part_name_map[i].subid == RESOURCE_SUBID_NONE) &&
|
|
(subid != RESOURCE_SUBID_NONE)) {
|
|
prerror("PLAT: Partition %s doesn't have subindex\n", name);
|
|
goto out_unlock;
|
|
}
|
|
|
|
rc = ffs_init(0, flash->size, flash->bl, &ffs, 1);
|
|
if (rc) {
|
|
prerror("Can't open ffs handle: %d\n", rc);
|
|
goto out_unlock;
|
|
}
|
|
|
|
rc = ffs_lookup_part(ffs, name, &ffs_part_num);
|
|
if (rc) {
|
|
/* This is not an error per-se, some partitions
|
|
* are purposefully absent, don't spam the logs
|
|
*/
|
|
prlog(PR_DEBUG, "No %s partition\n", name);
|
|
goto out_free_ffs;
|
|
}
|
|
rc = ffs_part_info(ffs, ffs_part_num, NULL,
|
|
&ffs_part_start, NULL, &ffs_part_size, &ecc);
|
|
if (rc) {
|
|
prerror("Failed to get %s partition info\n", name);
|
|
goto out_free_ffs;
|
|
}
|
|
prlog(PR_DEBUG,"%s partition %s ECC\n",
|
|
name, ecc ? "has" : "doesn't have");
|
|
|
|
/*
|
|
* FIXME: Make the fact we don't support partitions smaller than 4K
|
|
* more explicit.
|
|
*/
|
|
if (ffs_part_size < SECURE_BOOT_HEADERS_SIZE) {
|
|
prerror("secboot headers bigger than "
|
|
"partition size 0x%x\n", ffs_part_size);
|
|
goto out_free_ffs;
|
|
}
|
|
|
|
rc = blocklevel_read(flash->bl, ffs_part_start, bufp,
|
|
SECURE_BOOT_HEADERS_SIZE);
|
|
if (rc) {
|
|
prerror("failed to read the first 0x%x from "
|
|
"%s partition, rc %d\n", SECURE_BOOT_HEADERS_SIZE,
|
|
name, rc);
|
|
goto out_free_ffs;
|
|
}
|
|
|
|
part_signed = stb_is_container(bufp, SECURE_BOOT_HEADERS_SIZE);
|
|
|
|
prlog(PR_DEBUG, "%s partition %s signed\n", name,
|
|
part_signed ? "is" : "isn't");
|
|
|
|
/*
|
|
* part_start/size are raw pointers into the partition.
|
|
* ie. they will account for ECC if included.
|
|
*/
|
|
|
|
if (part_signed) {
|
|
bufp += SECURE_BOOT_HEADERS_SIZE;
|
|
bufsz -= SECURE_BOOT_HEADERS_SIZE;
|
|
content_size = stb_sw_payload_size(buf, SECURE_BOOT_HEADERS_SIZE);
|
|
*len = content_size + SECURE_BOOT_HEADERS_SIZE;
|
|
|
|
if (content_size > bufsz) {
|
|
prerror("content size > buffer size\n");
|
|
rc = OPAL_PARAMETER;
|
|
goto out_free_ffs;
|
|
}
|
|
|
|
if (*len > ffs_part_size) {
|
|
prerror("FLASH: Cannot load %s. Content is larger than the partition\n",
|
|
name);
|
|
rc = OPAL_PARAMETER;
|
|
goto out_free_ffs;
|
|
}
|
|
|
|
ffs_part_start += SECURE_BOOT_HEADERS_SIZE;
|
|
|
|
rc = blocklevel_read(flash->bl, ffs_part_start, bufp,
|
|
content_size);
|
|
if (rc) {
|
|
prerror("failed to read content size %d"
|
|
" %s partition, rc %d\n",
|
|
content_size, name, rc);
|
|
goto out_free_ffs;
|
|
}
|
|
|
|
if (subid == RESOURCE_SUBID_NONE)
|
|
goto done_reading;
|
|
|
|
rc = flash_subpart_info(bufp, content_size, ffs_part_size,
|
|
NULL, subid, &offset, &content_size);
|
|
if (rc) {
|
|
prerror("Failed to parse subpart info for %s\n",
|
|
name);
|
|
goto out_free_ffs;
|
|
}
|
|
bufp += offset;
|
|
goto done_reading;
|
|
} else /* stb_signed */ {
|
|
/*
|
|
* Back to the old way of doing things, no STB header.
|
|
*/
|
|
if (subid == RESOURCE_SUBID_NONE) {
|
|
if (id == RESOURCE_ID_KERNEL ||
|
|
id == RESOURCE_ID_INITRAMFS) {
|
|
/*
|
|
* Because actualSize is a lie, we compute the
|
|
* size of the BOOTKERNEL based on what the ELF
|
|
* headers say. Otherwise we end up reading more
|
|
* than we should
|
|
*/
|
|
content_size = sizeof_elf_from_hdr(buf);
|
|
if (!content_size) {
|
|
prerror("Invalid ELF header part"
|
|
" %s\n", name);
|
|
rc = OPAL_RESOURCE;
|
|
goto out_free_ffs;
|
|
}
|
|
} else {
|
|
content_size = ffs_part_size;
|
|
}
|
|
if (content_size > bufsz) {
|
|
prerror("%s content size %d > "
|
|
" buffer size %lu\n", name,
|
|
content_size, bufsz);
|
|
rc = OPAL_PARAMETER;
|
|
goto out_free_ffs;
|
|
}
|
|
prlog(PR_DEBUG, "computed %s size %u\n",
|
|
name, content_size);
|
|
rc = blocklevel_read(flash->bl, ffs_part_start,
|
|
buf, content_size);
|
|
if (rc) {
|
|
prerror("failed to read content size %d"
|
|
" %s partition, rc %d\n",
|
|
content_size, name, rc);
|
|
goto out_free_ffs;
|
|
}
|
|
*len = content_size;
|
|
goto done_reading;
|
|
}
|
|
BUILD_ASSERT(FLASH_SUBPART_HEADER_SIZE <= SECURE_BOOT_HEADERS_SIZE);
|
|
rc = flash_subpart_info(bufp, SECURE_BOOT_HEADERS_SIZE,
|
|
ffs_part_size, &ffs_part_size, subid,
|
|
&offset, &content_size);
|
|
if (rc) {
|
|
prerror("FAILED reading subpart info. rc=%d\n",
|
|
rc);
|
|
goto out_free_ffs;
|
|
}
|
|
|
|
*len = ffs_part_size;
|
|
prlog(PR_DEBUG, "Computed %s partition size: %u "
|
|
"(subpart %u size %u offset %u)\n", name, ffs_part_size,
|
|
subid, content_size, offset);
|
|
/*
|
|
* For a sub partition, we read the whole (computed)
|
|
* partition, and then measure that.
|
|
* Afterwards, we memmove() things back into place for
|
|
* the caller.
|
|
*/
|
|
rc = blocklevel_read(flash->bl, ffs_part_start,
|
|
buf, ffs_part_size);
|
|
|
|
bufp += offset;
|
|
}
|
|
|
|
done_reading:
|
|
/*
|
|
* Verify and measure the retrieved PNOR partition as part of the
|
|
* secure boot and trusted boot requirements
|
|
*/
|
|
secureboot_verify(id, buf, *len);
|
|
trustedboot_measure(id, buf, *len);
|
|
|
|
/* Find subpartition */
|
|
if (subid != RESOURCE_SUBID_NONE) {
|
|
memmove(buf, bufp, content_size);
|
|
*len = content_size;
|
|
}
|
|
|
|
status = true;
|
|
|
|
out_free_ffs:
|
|
ffs_close(ffs);
|
|
out_unlock:
|
|
unlock(&flash_lock);
|
|
return status ? OPAL_SUCCESS : rc;
|
|
}
|
|
|
|
|
|
struct flash_load_resource_item {
|
|
enum resource_id id;
|
|
uint32_t subid;
|
|
int result;
|
|
void *buf;
|
|
size_t *len;
|
|
struct list_node link;
|
|
};
|
|
|
|
static LIST_HEAD(flash_load_resource_queue);
|
|
static LIST_HEAD(flash_loaded_resources);
|
|
static struct lock flash_load_resource_lock = LOCK_UNLOCKED;
|
|
static struct cpu_job *flash_load_job = NULL;
|
|
|
|
int flash_resource_loaded(enum resource_id id, uint32_t subid)
|
|
{
|
|
struct flash_load_resource_item *resource = NULL;
|
|
struct flash_load_resource_item *r;
|
|
int rc = OPAL_BUSY;
|
|
|
|
lock(&flash_load_resource_lock);
|
|
list_for_each(&flash_loaded_resources, r, link) {
|
|
if (r->id == id && r->subid == subid) {
|
|
resource = r;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (resource) {
|
|
rc = resource->result;
|
|
list_del(&resource->link);
|
|
free(resource);
|
|
}
|
|
|
|
if (list_empty(&flash_load_resource_queue) && flash_load_job) {
|
|
cpu_wait_job(flash_load_job, true);
|
|
flash_load_job = NULL;
|
|
}
|
|
|
|
unlock(&flash_load_resource_lock);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Retry for 10 minutes in 5 second intervals: allow 5 minutes for a BMC reboot
|
|
* (need the BMC if we're using HIOMAP flash access), then 2x for some margin.
|
|
*/
|
|
#define FLASH_LOAD_WAIT_MS 5000
|
|
#define FLASH_LOAD_RETRIES (2 * 5 * (60 / (FLASH_LOAD_WAIT_MS / 1000)))
|
|
|
|
static void flash_load_resources(void *data __unused)
|
|
{
|
|
struct flash_load_resource_item *r;
|
|
int retries = FLASH_LOAD_RETRIES;
|
|
int result = OPAL_RESOURCE;
|
|
|
|
lock(&flash_load_resource_lock);
|
|
do {
|
|
if (list_empty(&flash_load_resource_queue)) {
|
|
break;
|
|
}
|
|
r = list_top(&flash_load_resource_queue,
|
|
struct flash_load_resource_item, link);
|
|
if (r->result != OPAL_EMPTY)
|
|
prerror("flash_load_resources() list_top unexpected "
|
|
" result %d\n", r->result);
|
|
r->result = OPAL_BUSY;
|
|
unlock(&flash_load_resource_lock);
|
|
|
|
while (retries) {
|
|
result = flash_load_resource(r->id, r->subid, r->buf,
|
|
r->len);
|
|
if (result == OPAL_SUCCESS) {
|
|
retries = FLASH_LOAD_RETRIES;
|
|
break;
|
|
}
|
|
|
|
if (result != FLASH_ERR_AGAIN &&
|
|
result != FLASH_ERR_DEVICE_GONE)
|
|
break;
|
|
|
|
time_wait_ms(FLASH_LOAD_WAIT_MS);
|
|
|
|
retries--;
|
|
|
|
prlog(PR_WARNING,
|
|
"Retrying load of %d:%d, %d attempts remain\n",
|
|
r->id, r->subid, retries);
|
|
}
|
|
|
|
lock(&flash_load_resource_lock);
|
|
r = list_pop(&flash_load_resource_queue,
|
|
struct flash_load_resource_item, link);
|
|
/* Will reuse the result from when we hit retries == 0 */
|
|
r->result = result;
|
|
list_add_tail(&flash_loaded_resources, &r->link);
|
|
} while(true);
|
|
unlock(&flash_load_resource_lock);
|
|
}
|
|
|
|
static void start_flash_load_resource_job(void)
|
|
{
|
|
if (flash_load_job)
|
|
cpu_wait_job(flash_load_job, true);
|
|
|
|
flash_load_job = cpu_queue_job(NULL, "flash_load_resources",
|
|
flash_load_resources, NULL);
|
|
|
|
cpu_process_local_jobs();
|
|
}
|
|
|
|
int flash_start_preload_resource(enum resource_id id, uint32_t subid,
|
|
void *buf, size_t *len)
|
|
{
|
|
struct flash_load_resource_item *r;
|
|
bool start_thread = false;
|
|
|
|
r = malloc(sizeof(struct flash_load_resource_item));
|
|
|
|
assert(r != NULL);
|
|
r->id = id;
|
|
r->subid = subid;
|
|
r->buf = buf;
|
|
r->len = len;
|
|
r->result = OPAL_EMPTY;
|
|
|
|
prlog(PR_DEBUG, "Queueing preload of %x/%x\n",
|
|
r->id, r->subid);
|
|
|
|
lock(&flash_load_resource_lock);
|
|
if (list_empty(&flash_load_resource_queue)) {
|
|
start_thread = true;
|
|
}
|
|
list_add_tail(&flash_load_resource_queue, &r->link);
|
|
unlock(&flash_load_resource_lock);
|
|
|
|
if (start_thread)
|
|
start_flash_load_resource_job();
|
|
|
|
return OPAL_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* The `libxz` decompression routines are blocking; the new decompression
|
|
* routines, wrapper around `libxz` functions, provide support for asynchronous
|
|
* decompression. There are two routines, which start the decompression, and one
|
|
* which waits for the decompression to complete.
|
|
*
|
|
* The decompressed image will be present in the `dst` parameter of
|
|
* `xz_decompress` structure.
|
|
*
|
|
* When the decompression is successful, the xz_decompress->status will be
|
|
* `OPAL_SUCCESS` else OPAL_PARAMETER, see definition of xz_decompress structure
|
|
* for details.
|
|
*/
|
|
static void xz_decompress(void *data)
|
|
{
|
|
struct xz_decompress *xz = (struct xz_decompress *)data;
|
|
struct xz_dec *s;
|
|
struct xz_buf b;
|
|
|
|
/* Initialize the xz library first */
|
|
xz_crc32_init();
|
|
s = xz_dec_init(XZ_SINGLE, 0);
|
|
if (s == NULL) {
|
|
prerror("initialization error for xz\n");
|
|
xz->status = OPAL_NO_MEM;
|
|
return;
|
|
}
|
|
|
|
xz->xz_error = XZ_DATA_ERROR;
|
|
xz->status = OPAL_PARTIAL;
|
|
|
|
b.in = xz->src;
|
|
b.in_pos = 0;
|
|
b.in_size = xz->src_size;
|
|
b.out = xz->dst;
|
|
b.out_pos = 0;
|
|
b.out_size = xz->dst_size;
|
|
|
|
/* Start decompressing */
|
|
xz->xz_error = xz_dec_run(s, &b);
|
|
if (xz->xz_error != XZ_STREAM_END) {
|
|
prerror("failed to decompress subpartition\n");
|
|
xz->status = OPAL_PARAMETER;
|
|
} else
|
|
xz->status = OPAL_SUCCESS;
|
|
|
|
xz_dec_end(s);
|
|
}
|
|
|
|
/*
|
|
* xz_start_decompress: start the decompression job and return.
|
|
*
|
|
* struct xz_decompress *xz, should be populated by the caller with
|
|
* - the starting address of the compressed binary
|
|
* - the address where the decompressed image should be placed
|
|
* - the sizes of the source and the destination
|
|
*
|
|
* xz->src: Source address (The compressed binary)
|
|
* xz->src_size: Source size
|
|
* xz->dst: Destination address (The memory area where the `src` will be
|
|
* decompressed)
|
|
* xz->dst_size: Destination size
|
|
*
|
|
* The `status` value will be OPAL_PARTIAL till the job completes (successfully
|
|
* or not)
|
|
*/
|
|
void xz_start_decompress(struct xz_decompress *xz)
|
|
{
|
|
struct cpu_job *job;
|
|
|
|
if (!xz)
|
|
return;
|
|
|
|
if (!xz->dst || !xz->dst_size || !xz->src || !xz->src_size) {
|
|
xz->status = OPAL_PARAMETER;
|
|
return;
|
|
}
|
|
|
|
job = cpu_queue_job(NULL, "xz_decompress", xz_decompress,
|
|
(void *) xz);
|
|
if (!job) {
|
|
xz->status = OPAL_NO_MEM;
|
|
return;
|
|
}
|
|
|
|
xz->job = job;
|
|
}
|
|
|
|
/*
|
|
* This function waits for the decompression job to complete. The `ret`
|
|
* structure member in `xz_decompress` will have the status code.
|
|
*
|
|
* status == OPAL_SUCCESS on success, else the corresponding error code.
|
|
*/
|
|
void wait_xz_decompress(struct xz_decompress *xz)
|
|
{
|
|
if (!xz)
|
|
return;
|
|
|
|
cpu_wait_job(xz->job, true);
|
|
}
|