/*
 * Copyright 2024 The Hafnium Authors.
 *
 * Use of this source code is governed by a BSD-style
 * license that can be found in the LICENSE file or at
 * https://opensource.org/licenses/BSD-3-Clause.
 */

#include "hf/partition_pkg.h"

#include <stdint.h>

#include "hf/arch/std.h"

#include "hf/addr.h"
#include "hf/check.h"
#include "hf/dlog.h"
#include "hf/mm.h"
#include "hf/sp_pkg.h"
#include "hf/std.h"
#include "hf/transfer_list.h"

static void dump_partition_package(struct partition_pkg *pkg)
{
	dlog_verbose("%s: total %lx %lx\n", __func__, pa_addr(pkg->total.begin),
		     pa_addr(pkg->total.end));
	dlog_verbose("%s: pm: %lx %lx\n", __func__, pa_addr(pkg->pm.begin),
		     pa_addr(pkg->pm.end));
	dlog_verbose("%s: img: %lx %lx\n", __func__, pa_addr(pkg->img.begin),
		     pa_addr(pkg->img.end));
	dlog_verbose("%s: boot_info: %lx %lx\n", __func__,
		     pa_addr(pkg->boot_info.begin),
		     pa_addr(pkg->boot_info.end));
	if (mem_range_is_valid(pkg->hob)) {
		dlog_verbose("%s: hob %lx %lx\n", __func__,
			     pa_addr(pkg->hob.begin), pa_addr(pkg->hob.end));
	}
}

static bool partition_pkg_from_sp_pkg(struct mm_stage1_locked stage1_locked,
				      paddr_t pkg_start,
				      struct partition_pkg *pkg,
				      struct mpool *ppool)
{
	struct sp_pkg_header header;
	bool ret = sp_pkg_init(stage1_locked, pkg_start, &header, ppool);
	size_t total_mem_size = sp_pkg_get_mem_size(&header);

	pkg->total.begin = pkg_start;
	pkg->total.end = pa_add(pkg_start, total_mem_size);

	pkg->pm.begin = pa_add(pkg_start, header.pm_offset);
	pkg->pm.end = pa_add(pkg->pm.begin, header.pm_size);

	pkg->img.begin = pa_add(pkg_start, header.img_offset);
	pkg->img.end = pa_add(pkg->img.begin, header.img_size);

	/*
	 * Repurpose the first page of the SP Package.
	 * FF-A boot info will overwrite the package, but it doesn't
	 * matter at this stage, given Hafnium already parsed it.
	 */
	pkg->boot_info.begin = pkg_start;
	pkg->boot_info.end = pa_add(pkg_start, header.pm_offset);

	/* HOB section doesn't exist in the SP Pkg type. */
	pkg->hob.begin = pa_init(0);
	pkg->hob.end = pa_init(0);

	if (ret) {
		/* Map the whole package as RO. */
		CHECK(mm_identity_map(stage1_locked, pkg->total.begin,
				      pkg->total.end, MM_MODE_R,
				      ppool) != NULL);
	}

	return ret;
}

/**
 * It creates a memory range structure which relates to the region of the
 * TE data.
 * It returns false if there is no TE entry with the specified type.
 * It returns true, if there is a TE entry with the specified type, and
 * returns the memory range via `mem_range`.
 */
static bool partition_pkg_init_memory_range_from_te(
	struct mem_range *mem_range, struct transfer_list_entry *te)
{
	void *te_data;

	assert(mem_range != NULL);

	te_data = transfer_list_entry_data(te);

	if (te == NULL || te_data == NULL) {
		mem_range->begin = pa_init(0);
		mem_range->end = pa_init(0);
		return false;
	}

	mem_range->begin = pa_from_va(va_init((uintptr_t)te_data));
	mem_range->end = pa_add(mem_range->begin, te->data_size);

	return true;
}

static bool partition_pkg_from_tl(struct mm_stage1_locked stage1_locked,
				  paddr_t pkg_start, struct partition_pkg *pkg,
				  struct mpool *ppool)
{
	struct transfer_list_header *tl = ptr_from_va(va_from_pa(pkg_start));
	enum transfer_list_ops tl_res;

	dlog_verbose("%s: partition loaded in a transfer list.\n", __func__);

	/* The total memory for the partition package. */
	pkg->total.begin = pkg_start;
	pkg->total.end = pa_add(pkg_start, tl->size);

	/* Map the whole TL as RO. */
	CHECK(mm_identity_map(stage1_locked, pkg->total.begin, pkg->total.end,
			      MM_MODE_R, ppool));

	tl_res = transfer_list_check_header(tl);

	if (tl_res == TL_OPS_NON || tl_res == TL_OPS_CUS) {
		return false;
	}

	/*
	 * Get the memory ranges from the TL for:
	 * - FFA_MANIFEST.
	 * - Partition Image.
	 */
	if (!partition_pkg_init_memory_range_from_te(
		    &(pkg->pm),
		    transfer_list_find(tl, TL_TAG_DT_FFA_MANIFEST)) ||
	    !partition_pkg_init_memory_range_from_te(
		    &(pkg->img),
		    transfer_list_find(tl, TL_TAG_FFA_SP_BINARY))) {
		return false;
	}

	/* An HOB entry is optional. */
	partition_pkg_init_memory_range_from_te(
		&(pkg->hob), transfer_list_find(tl, TL_TAG_HOB_LIST));

	if (!mem_range_aligns(pkg->pm, PAGE_SIZE)) {
		dlog_error(
			"%s: the partition manifest range must be 4k page "
			"aligned.\n",
			__func__);
		return false;
	}

	if (!mem_range_aligns(pkg->img, PAGE_SIZE)) {
		dlog_error(
			"%s: the partition image range must be 4k page "
			"aligned.\n",
			__func__);
		return false;
	}

	if (mem_range_is_valid(pkg->hob) &&
	    !mem_range_aligns(pkg->hob, PAGE_SIZE)) {
		dlog_error("%s: the hob range must be 4k page aligned.\n",
			   __func__);
		return false;
	}

	/*
	 * For the boot information descriptor, repurpose the TL's first page.
	 * The TL is only processed by Hafnium, and all items are placed at
	 * a page aligned offset.
	 * At this point, all references to artefacts in the TL have been
	 * obtained so the first page of the package can be repurposed to the
	 * FF-A boot information. There is no expectation that the boot info
	 * descriptors will need more than a page for the time being. If it does
	 * get full, Hafnium will fail at populating the boot info descriptors.
	 */
	pkg->boot_info.begin = pkg_start;
	pkg->boot_info.end = pa_add(pkg_start, PAGE_SIZE);

	return true;
}

bool partition_pkg_init(struct mm_stage1_locked stage1_locked,
			paddr_t pkg_start, struct partition_pkg *pkg,
			struct mpool *ppool)
{
	bool ret = false;
	paddr_t pkg_first_page = pa_add(pkg_start, PAGE_SIZE);
	uint32_t *magic;
	void *mapped_ptr;

	/* Firstly, map a single page to be able to read package header. */
	mapped_ptr = mm_identity_map(stage1_locked, pkg_start, pkg_first_page,
				     MM_MODE_R, ppool);
	assert(mapped_ptr != NULL);
	assert(pkg != NULL);

	magic = (uint32_t *)mapped_ptr;

	switch (*magic) {
	case SP_PKG_HEADER_MAGIC:
		/*
		 * Leave memory mapped in case it succeeded, to be cleared
		 * later.
		 */
		if (!partition_pkg_from_sp_pkg(stage1_locked, pkg_start, pkg,
					       ppool)) {
			goto out;
		}
		break;
	case TRANSFER_LIST_SIGNATURE:
		if (!partition_pkg_from_tl(stage1_locked, pkg_start, pkg,
					   ppool)) {
			goto out;
		}
		break;
	default:
		dlog_error("%s: invalid secure partition package %x @ %lx\n",
			   __func__, *magic, (uint64_t)magic);
		goto out;
	}

	dump_partition_package(pkg);

	/**
	 * The total memory range should encompass the remaining.
	 * Assert that none of the memory ranges are out of bounds.
	 */
	assert(mem_range_contains_range(pkg->total, pkg->img));
	assert(mem_range_contains_range(pkg->total, pkg->pm));
	assert(mem_range_contains_range(pkg->total, pkg->boot_info));
	assert(!mem_range_is_valid(pkg->hob) ||
	       mem_range_contains_range(pkg->total, pkg->hob));

	/* Map Boot info section as RW. */
	if (pa_addr(pkg->boot_info.begin) != 0U &&
	    pa_addr(pkg->boot_info.end) != 0U) {
		CHECK(mm_identity_map(stage1_locked, pkg->boot_info.begin,
				      pkg->boot_info.end, MM_MODE_R | MM_MODE_W,
				      ppool) != NULL);
	}

	ret = true;

out:
	/* If failing unmap the memory. */
	if (!ret) {
		CHECK(mm_unmap(stage1_locked, pkg_start, pkg_first_page,
			       ppool));
	}

	return ret;
}

void partition_pkg_deinit(struct mm_stage1_locked stage1_locked,
			  struct partition_pkg *pkg, struct mpool *ppool)
{
	CHECK(mm_unmap(stage1_locked, pkg->total.begin, pkg->total.end, ppool));
}
