/* Copyright 2013-2018 IBM Corp.
 * Copyright 2018 Raptor Engineering, LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * 	http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#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/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;

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);
}

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("FLASH_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("FLASH_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_nvram_probe(struct flash *flash, struct ffs_handle *ffs)
{
	uint32_t start, size, part;
	bool ecc;
	int rc;

	prlog(PR_INFO, "FLASH: probing for NVRAM\n");

	rc = ffs_lookup_part(ffs, "NVRAM", &part);
	if (rc) {
		prlog(PR_WARNING, "FLASH: 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, "FLASH: 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("FLASH: 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, "FLASH: 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("FLASH: 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, "FLASH: Attempted to register multiple system "
		      "flash: %s\n", name);
		return;
	}

	prlog(PR_NOTICE, "FLASH: 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, "FLASH: registered system flash device %s\n", name);

	flash_nvram_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, "FLASH: 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, "FLASH: 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, "FLASH: 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, token, 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 elf64_hdr));
	BUILD_ASSERT(SECURE_BOOT_HEADERS_SIZE > sizeof(struct elf32_hdr));

	if (elf->ei_ident == ELF_IDENT) {
		if (elf->ei_class == ELF_CLASS_64) {
			struct elf64_hdr *elf64 = (struct elf64_hdr*) buf;
			sz = le64_to_cpu(elf64->e_shoff) +
				((uint32_t)le16_to_cpu(elf64->e_shentsize) *
				 (uint32_t)le16_to_cpu(elf64->e_shnum));
		} else if (elf->ei_class == ELF_CLASS_32) {
			struct elf32_hdr *elf32 = (struct elf32_hdr*) buf;
			sz = le32_to_cpu(elf32->e_shoff) +
				(le16_to_cpu(elf32->e_shentsize) *
				 le16_to_cpu(elf32->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, "FLASH: 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("FLASH: 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("FLASH: 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, "FLASH: 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("FLASH: Failed to get %s partition info\n", name);
		goto out_free_ffs;
	}
	prlog(PR_DEBUG,"FLASH: %s partition %s ECC\n",
	      name, ecc  ? "has" : "doesn't have");

	if (ffs_part_size < SECURE_BOOT_HEADERS_SIZE) {
		prerror("FLASH: 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("FLASH: 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, "FLASH: %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("FLASH: content size > buffer size\n");
			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("FLASH: 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("FLASH: 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("FLASH: 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("FLASH: %s content size %d > "
					" buffer size %lu\n", name,
					content_size, bufsz);
				rc = OPAL_PARAMETER;
				goto out_free_ffs;
			}
			prlog(PR_DEBUG, "FLASH: computed %s size %u\n",
			      name, content_size);
			rc = blocklevel_read(flash->bl, ffs_part_start,
						  buf, content_size);
			if (rc) {
				prerror("FLASH: 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("FLASH: FAILED reading subpart info. rc=%d\n",
				rc);
			goto out_free_ffs;
		}

		*len = ffs_part_size;
		prlog(PR_DEBUG, "FLASH: 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,
			      "FLASH: 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, "FLASH: 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);
}
