aboutsummaryrefslogtreecommitdiffstats
path: root/roms/skiboot/core/flash.c
diff options
context:
space:
mode:
Diffstat (limited to 'roms/skiboot/core/flash.c')
-rw-r--r--roms/skiboot/core/flash.c1186
1 files changed, 1186 insertions, 0 deletions
diff --git a/roms/skiboot/core/flash.c b/roms/skiboot/core/flash.c
new file mode 100644
index 000000000..8c1e788c4
--- /dev/null
+++ b/roms/skiboot/core/flash.c
@@ -0,0 +1,1186 @@
+// 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);
+ 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);
+
+ /* we fix to 32-bits */
+ dt_add_property_cells(flash_node, "#address-cells", 1);
+ dt_add_property_cells(flash_node, "#size-cells", 1);
+
+ /* Add partition container node */
+ partition_container_node = dt_new(flash_node, "partitions");
+ dt_add_property_strings(partition_container_node, "compatible", "fixed-partitions");
+
+ /* we fix to 32-bits */
+ dt_add_property_cells(partition_container_node, "#address-cells", 1);
+ dt_add_property_cells(partition_container_node, "#size-cells", 1);
+
+ /* 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);
+ dt_add_property_cells(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");
+ dt_add_property_cells(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);
+}