/*
 * Copyright 2021 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/check.h"
#include "hf/fdt.h"
#include "hf/fdt_handler.h"
#include "hf/ffa.h"
#include "hf/memiter.h"
#include "hf/mm.h"
#include "hf/std.h"
#include "hf/stdout.h"
#include "hf/string.h"

#include "vmapi/hf/call.h"

#include "../msr.h"
#include "test/hftest.h"
#include "test/hftest_impl.h"
#include "test/vmapi/arch/exception_handler.h"
#include "test/vmapi/ffa.h"

extern struct hftest_test hftest_begin[];
extern struct hftest_test hftest_end[];

static struct hftest_context global_context;

static alignas(PAGE_SIZE) uint8_t secondary_ec_stack[MAX_CPUS][PAGE_SIZE];

uint8_t *hftest_get_secondary_ec_stack(size_t id)
{
	assert(id < MAX_CPUS);
	return secondary_ec_stack[id];
}

struct hftest_context *hftest_get_context(void)
{
	return &global_context;
}

static bool uint32list_has_next(const struct memiter *list)
{
	return memiter_size(list) > 0;
}

static void uint32list_get_next(struct memiter *list, uint32_t *out)
{
	uint64_t num;

	CHECK(uint32list_has_next(list));
	if (!fdt_parse_number(list, sizeof(uint32_t), &num)) {
		return;
	}

	*out = (uint32_t)num;
}

noreturn void abort(void)
{
	HFTEST_LOG("Service contained failures.");
	/* Cause a fault, as a secondary/SP can't power down the machine. */
	*((volatile uint8_t *)1) = 1;

	/* This should never be reached, but to make the compiler happy... */
	for (;;) {
	}
}

/** Find the service with the name passed in the arguments. */
static hftest_test_fn find_service(struct memiter *args)
{
	struct memiter service_name;
	struct hftest_test *test;

	if (!memiter_parse_str(args, &service_name)) {
		return NULL;
	}

	for (test = hftest_begin; test < hftest_end; ++test) {
		if (test->kind == HFTEST_KIND_SERVICE &&
		    memiter_iseq(&service_name, test->name)) {
			return test->fn;
		}
	}

	return NULL;
}

void hftest_context_init(struct hftest_context *ctx, void *send, void *recv)
{
	memset_s(ctx, sizeof(*ctx), 0, sizeof(*ctx));
	ctx->abort = abort;
	ctx->send = send;
	ctx->recv = recv;
}

/*
 * Parse the FF-A partition's manifest.
 * This function assumes the 'fdt' field of the passed 'ctx' has been
 * initialized.
 * TODO: Parse other fields as needed.
 */
void hftest_parse_ffa_manifest(struct hftest_context *ctx, struct fdt *fdt)
{
	struct fdt_node root;
	struct fdt_node ffa_node;
	struct string mem_region_node_name = STRING_INIT("memory-regions");
	struct string dev_region_node_name = STRING_INIT("device-regions");
	struct memiter uuid;
	struct memiter description;
	uint32_t uuid_word = 0;
	uint16_t j = 0;
	uint16_t i = 0;
	uint64_t number;

	CHECK(ctx != NULL);
	CHECK(fdt != NULL);

	ASSERT_TRUE(fdt_find_node(fdt, "/", &root));
	EXPECT_TRUE(fdt_is_compatible(&root, "arm,ffa-manifest-1.0"));
	ASSERT_TRUE(fdt_read_number(&root, "load-address",
				    &ctx->partition_manifest.load_addr));
	EXPECT_TRUE(fdt_read_number(&root, "ffa-version", &number));
	ctx->partition_manifest.ffa_version = number;

	EXPECT_TRUE(fdt_read_number(&root, "execution-ctx-count", &number));
	ctx->partition_manifest.execution_ctx_count = (uint16_t)number;

	EXPECT_TRUE(fdt_read_number(&root, "exception-level", &number));
	ctx->partition_manifest.run_time_el = (uint16_t)number;

	EXPECT_TRUE(fdt_read_property(&root, "uuid", &uuid));

	/* Parse UUIDs and populate uuid count.*/
	while (uint32list_has_next(&uuid) && j < PARTITION_MAX_UUIDS) {
		while (uint32list_has_next(&uuid) && i < 4) {
			uint32list_get_next(&uuid, &uuid_word);
			ctx->partition_manifest.uuids[j].uuid[i] = uuid_word;
			i++;
		}

		EXPECT_FALSE(
			ffa_uuid_is_null(&ctx->partition_manifest.uuids[j]));

		dlog_verbose("  UUID %#x-%x-%x-%x\n",
			     ctx->partition_manifest.uuids[j].uuid[0],
			     ctx->partition_manifest.uuids[j].uuid[1],
			     ctx->partition_manifest.uuids[j].uuid[2],
			     ctx->partition_manifest.uuids[j].uuid[3]);
		j++;
		i = 0;
	}

	ctx->partition_manifest.uuid_count = j;

	ffa_node = root;

	/* Look for the memory region node. */
	if (fdt_find_child(&ffa_node, &mem_region_node_name) &&
	    fdt_first_child(&ffa_node)) {
		uint32_t mem_count = 0;

		do {
			struct memory_region *cur_region =
				&ctx->partition_manifest.mem_regions[mem_count];
			EXPECT_TRUE(fdt_read_number(&ffa_node, "pages-count",
						    &number));
			cur_region->page_count = (uint32_t)number;

			if (!fdt_read_number(&ffa_node, "base-address",
					     &cur_region->base_address)) {
				EXPECT_TRUE(fdt_read_number(
					&ffa_node,
					"load-address-relative-offset",
					&number));
				cur_region->base_address =
					ctx->partition_manifest.load_addr +
					number;
				cur_region->is_relative = true;
			}

			if (fdt_read_property(&ffa_node, "description",
					      &description)) {
				EXPECT_EQ(string_init(&cur_region->description,
						      &description),
					  STRING_SUCCESS);
			}

			EXPECT_TRUE(fdt_read_number(&ffa_node, "attributes",
						    &number));
			cur_region->attributes = (uint32_t)number;
			mem_count++;
		} while (fdt_next_sibling(&ffa_node));

		assert(mem_count < PARTITION_MAX_MEMORY_REGIONS);

		ctx->partition_manifest.mem_region_count = mem_count;
	}

	ffa_node = root;

	/* Look for the device region node. */
	if (fdt_find_child(&ffa_node, &dev_region_node_name) &&
	    fdt_first_child(&ffa_node)) {
		uint32_t dev_region_count = 0;

		do {
			struct device_region *cur_region =
				&ctx->partition_manifest
					 .dev_regions[dev_region_count];
			EXPECT_TRUE(fdt_read_number(&ffa_node, "pages-count",
						    &number));
			cur_region->page_count = (uint32_t)number;

			if (!fdt_read_number(&ffa_node, "base-address",
					     &cur_region->base_address)) {
				EXPECT_TRUE(fdt_read_number(
					&ffa_node,
					"load-address-relative-offset",
					&number));
				cur_region->base_address =
					ctx->partition_manifest.load_addr +
					number;
			}

			EXPECT_TRUE(fdt_read_number(&ffa_node, "attributes",
						    &number));
			cur_region->attributes = (uint32_t)number;
			dev_region_count++;
		} while (fdt_next_sibling(&ffa_node));

		assert(dev_region_count < PARTITION_MAX_DEVICE_REGIONS);

		ctx->partition_manifest.dev_region_count = dev_region_count;
	}

	ctx->is_ffa_manifest_parsed = true;
}

void hftest_service_set_up(struct hftest_context *ctx, struct fdt *fdt)
{
	struct fdt_node node;
	struct hftest_test *hftest_info;

	ASSERT_TRUE(fdt_find_node(fdt, "/", &node));

	if (!fdt_find_child(&node, &(STRING_INIT("hftest-service-setup")))) {
		return;
	}

	EXPECT_TRUE(fdt_is_compatible(&node, "arm,hftest"));

	for (hftest_info = hftest_begin; hftest_info < hftest_end;
	     ++hftest_info) {
		struct memiter data;
		if (hftest_info->kind != HFTEST_KIND_SERVICE_SET_UP) {
			continue;
		}
		if (fdt_read_property(&node, hftest_info->name, &data)) {
			HFTEST_LOG("Running service_setup: %s\n",
				   hftest_info->name);
			hftest_info->fn();
			if (ctx->failures) {
				HFTEST_LOG_FAILURE();
				HFTEST_LOG(HFTEST_LOG_INDENT
					   "%s service_setup failed\n",
					   hftest_info->name);
				abort();
			}
		} else {
			HFTEST_LOG("Skipping service_setup: %s\n",
				   hftest_info->name);
		}
	}
}

noreturn void hftest_service_main(const void *fdt_ptr)
{
	struct hftest_context *ctx;
	struct memiter args;
	hftest_test_fn service;
	struct ffa_value ret;
	struct fdt fdt;
	const ffa_id_t own_id = hf_vm_get_id();
	ffa_notifications_bitmap_t bitmap;
	const struct ffa_partition_msg *message;
	uint32_t vcpu = get_current_vcpu_index();

	ctx = hftest_get_context();

	/* If boot vcpu, set up mailbox and intialize context abort function. */
	if (vcpu == 0) {
		struct mailbox_buffers mb;
		mb = set_up_mailbox();
		hftest_context_init(ctx, mb.send, mb.recv);
	}

	if (!fdt_struct_from_ptr(fdt_ptr, &fdt)) {
		HFTEST_LOG(HFTEST_LOG_INDENT "Unable to access the FDT");
		abort();
	}

	/*
	 * The memory size argument is to be used only by VMs. It is part of
	 * the dt provided by the Hypervisor. SPs expect to receive their
	 * FF-A manifest which doesn't have a memory size field.
	 */
	if (ffa_is_vm_id(own_id) &&
	    !fdt_get_memory_size(&fdt, &ctx->memory_size)) {
		HFTEST_LOG_FAILURE();
		HFTEST_LOG(HFTEST_LOG_INDENT
			   "No entry in the FDT on memory size details");
		abort();
	} else if (!ffa_is_vm_id(own_id)) {
		/*
		 * It is secure partition. We are currently using the partition
		 * manifest for the SP.
		 */
		hftest_parse_ffa_manifest(ctx, &fdt);
		stdout_init(ctx->partition_manifest.ffa_version);

		/* TODO: Determine memory size referring to the SP Pkg. */
		ctx->memory_size = 1048576;
	}

	/* If boot vcpu, it means it is running in RTM_INIT. */
	if (vcpu == 0) {
		run_service_set_up(ctx, &fdt);
	}

	/* Receive the name of the service to run. */
	ret = ffa_msg_wait();
	EXPECT_EQ(ret.func, FFA_RUN_32);

	message = (struct ffa_partition_msg *)SERVICE_RECV_BUFFER();

	/*
	 * Expect to wake up with indirect message related to the next service
	 * to be executed.
	 */
	ret = ffa_notification_get(own_id, vcpu,
				   FFA_NOTIFICATION_FLAG_BITMAP_SPM |
					   FFA_NOTIFICATION_FLAG_BITMAP_HYP);
	ASSERT_EQ(ret.func, FFA_SUCCESS_32);
	bitmap = ffa_notification_get_from_framework(ret);
	ASSERT_TRUE(is_ffa_spm_buffer_full_notification(bitmap) ||
		    is_ffa_hyp_buffer_full_notification(bitmap));
	ASSERT_EQ(own_id, message->header.receiver);

	if (ctx->is_ffa_manifest_parsed &&
	    ctx->partition_manifest.run_time_el == S_EL1) {
		ASSERT_EQ(hf_interrupt_get(), HF_NOTIFICATION_PENDING_INTID);
	}

	memiter_init(&args, ffa_partition_msg_payload_const(message),
		     message->header.size);

	/* Find service handler. */
	service = find_service(&args);
	EXPECT_EQ(ffa_rx_release().func, FFA_SUCCESS_32);

	/* Check the service was found. */
	if (service == NULL) {
		HFTEST_LOG_FAILURE();
		HFTEST_LOG(HFTEST_LOG_INDENT
			   "Unable to find requested service");
		abort();
	}

	/* Pause so the next time cycles are given the service will be run. */
	ffa_yield();

	/* Let the service run. */
	service();

	/* Cleanly handle it if the service returns. */
	if (ctx->failures) {
		abort();
	}

	for (;;) {
		/* Hang if the service returns. */
	}
}

ffa_id_t hftest_get_dir_req_source_id(void)
{
	struct hftest_context *ctx = hftest_get_context();
	return ctx->dir_req_source_id;
}

void hftest_set_dir_req_source_id(ffa_id_t id)
{
	struct hftest_context *ctx = hftest_get_context();
	ctx->dir_req_source_id = id;
}

void hftest_map_device_regions(struct hftest_context *ctx)
{
	struct device_region *dev_region;
	uint32_t dev_region_count;

	/*
	 * The running partition must have received and parsed its own
	 * partition manifest by now.
	 */
	if (!ctx || !ctx->is_ffa_manifest_parsed) {
		panic("Partition manifest not parsed.\n");
	}

	dev_region_count = ctx->partition_manifest.dev_region_count;

	/* Map the MMIO address space of the devices. */
	for (uint32_t i = 0; i < dev_region_count; i++) {
		dev_region = &ctx->partition_manifest.dev_regions[i];

		hftest_mm_identity_map(
			// NOLINTNEXTLINE(performance-no-int-to-ptr)
			(const void *)dev_region->base_address,
			dev_region->page_count * PAGE_SIZE,
			dev_region->attributes);
	}
}
