/*
 * Copyright 2019 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/arch/vm/mm.h"

#include "test/hftest.h"

/* Number of pages reserved for page tables. Increase if necessary. */
#define PTABLE_PAGES 10

/**
 * Start address space mapping at 0x1000 for the mm to create a L2 table to
 * which the first L1 descriptor points to.
 * Provided SPMC and SP images reside below 1GB, same as peripherals, this
 * prevents a case in which the mm library has to break down the first
 * L1 block descriptor, while currently executing from a region within
 * the same L1 descriptor. This is not architecturally possible.
 */
#define HFTEST_STAGE1_START_ADDRESS (0x1000)

alignas(alignof(struct mm_page_table)) static char ptable_buf
	[sizeof(struct mm_page_table) * PTABLE_PAGES];

static struct mpool ppool;
static struct mm_ptable ptable;

struct mm_stage1_locked hftest_mm_get_stage1(void)
{
	return (struct mm_stage1_locked){.ptable = &ptable};
}

struct mpool *hftest_mm_get_ppool(void)
{
	return &ppool;
}

bool hftest_mm_init(void)
{
	struct mm_stage1_locked stage1_locked;

	/* Call arch init before calling below mapping routines */
	if (!arch_vm_mm_init()) {
		return false;
	}

	mpool_init(&ppool, sizeof(struct mm_page_table));
	if (!mpool_add_chunk(&ppool, ptable_buf, sizeof(ptable_buf))) {
		HFTEST_FAIL(true, "Failed to add buffer to page-table pool.");
	}

	if (!mm_ptable_init(&ptable, 0, true, &ppool)) {
		HFTEST_FAIL(true, "Unable to allocate memory for page table.");
	}

	stage1_locked = hftest_mm_get_stage1();
	mm_identity_map(stage1_locked,
			pa_init((uintptr_t)HFTEST_STAGE1_START_ADDRESS),
			pa_init(mm_ptable_addr_space_end(stage1_locked.ptable)),
			MM_MODE_R | MM_MODE_W | MM_MODE_X, &ppool);

	arch_vm_mm_enable(&ptable);

	return true;
}

bool hftest_mm_get_mode(const void *base, size_t size, mm_mode_t *mode)
{
	vaddr_t start = va_from_ptr(base);
	vaddr_t end = va_add(start, size);
	struct mm_stage1_locked stage1_locked = hftest_mm_get_stage1();

	assert(mode != NULL);

	return mm_get_mode(stage1_locked.ptable, start, end, mode);
}

void hftest_mm_identity_map(const void *base, size_t size, mm_mode_t mode)
{
	struct mm_stage1_locked stage1_locked = hftest_mm_get_stage1();
	paddr_t start = pa_from_va(va_from_ptr(base));
	paddr_t end = pa_add(start, size);

	if (mm_identity_map(stage1_locked, start, end, mode, &ppool) != base) {
		FAIL("Could not add new page table mapping. Try increasing "
		     "size of the page table buffer.");
	}
}

void hftest_mm_vcpu_init(void)
{
	arch_vm_mm_enable(&ptable);
}
