/*
 * Copyright (c) 2020, ARM Limited and Contributors. All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <assert.h>
#include <common/debug.h>
#include <errno.h>
#include <inttypes.h>
#include <platform_def.h>
#include <stddef.h>
#include <trusty/arm_ffa.h>
#include <trusty/ffa_helpers.h>

#include "qemu_private.h"

#define NS_DRAM0_BITMAP_SIZE DIV_ROUND_UP_2EVAL(NS_DRAM0_SIZE, PAGE_SIZE * 8)
static uint8_t trusty_shmem_shared[NS_DRAM0_BITMAP_SIZE];
static uint8_t trusty_shmem_secure[NS_DRAM0_BITMAP_SIZE];

static bool read_bit(uint8_t *bit_mask, size_t bit_num)
{
	size_t i = bit_num / 8;
	size_t m = 1U << (bit_num % 8);
	return !!(bit_mask[i] & m);
}

static void write_bit(uint8_t *bit_mask, size_t bit_num, bool val)
{
	size_t i = bit_num / 8;
	size_t m = 1U << (bit_num % 8);
	if (val) {
		bit_mask[i] |= m;
	} else {
		bit_mask[i] &= ~m;
	}
}

static int mem_set_shared(bool shared, bool secure, unsigned long long base_pa,
			  size_t size)
{
	unsigned long long page;
	size_t i;

	assert((size % PAGE_SIZE) == 0);

	if (base_pa < NS_DRAM0_BASE ||
	    (base_pa - NS_DRAM0_BASE) + size > NS_DRAM0_SIZE) {
		NOTICE("%s(%d, %d, 0x%llx, 0x%zx) invalid address range\n",
		       __func__, shared, secure, base_pa, size);
		return -EINVAL;
	}
	for (page = (base_pa - NS_DRAM0_BASE) / PAGE_SIZE, i = 0;
	     i < size / PAGE_SIZE; page++, i++) {
		bool was_shared = read_bit(trusty_shmem_shared, page);
		bool was_secure = read_bit(trusty_shmem_secure, page);
		assert(!was_secure || was_shared);
		if (was_shared == shared) {
			/* already shared or reclaimed */
			NOTICE("%s(%d, %d, 0x%llx, 0x%zx) already set\n",
			       __func__, shared, secure, base_pa, size);
			goto err;
		}
		assert(was_secure == (secure && !shared));
		write_bit(trusty_shmem_shared, page, shared);
		if (secure) {
			/*
			 * For emulator testing purposes the memory
			 * is marked as secure, and communicated to
			 * Trusty as such, even though it is not.
			 */
			write_bit(trusty_shmem_secure, page, shared);
		}
	}
	return 0;

err:
	while (i > 0) {
		i--;
		page--;
		write_bit(trusty_shmem_shared, page, !shared);
		if (secure) {
			write_bit(trusty_shmem_secure, page, !shared);
		}
	}
	return -EBUSY;
}

int qemu_ffa_comp_set_shared(void *compv, bool shared, bool secure)
{
	struct ffa_comp_mrd *comp = compv;
	size_t count = comp->address_range_count;
	struct ffa_cons_mrd *cons_mrd;
	int ret = 0;
	size_t i;

	for (i = 0, cons_mrd = comp->address_range_array; i < count;
	     i++, cons_mrd++) {
		ret = mem_set_shared(shared, secure, cons_mrd->address,
				     cons_mrd->page_count * PAGE_SIZE);
		if (ret) {
			goto err;
		}
	}

	return 0;

err:
	NOTICE("%s: %zu/%zu: failed to set shared %d secure %d for %"
	       PRIx64 " (%x)\n",
	       __func__, i, count, shared, secure, cons_mrd->address,
	       cons_mrd->page_count);
	while (i > 0) {
		i--;
		cons_mrd--;
		if (mem_set_shared(!shared, secure, cons_mrd->address,
				   cons_mrd->page_count * PAGE_SIZE)) {
			/* Failed to revert change */
			panic();
		}
	}
	return ret;
}

int plat_mem_set_shared(struct ffa_mtd *mtd, bool shared)
{
	struct ffa_comp_mrd *comp = trusty_ffa_mtd_get_comp_mrd(mtd);
	bool secure = trusty_ffa_should_be_secure(mtd);
	int ret;

	ret = qemu_ffa_comp_set_shared(comp, shared, secure);
	if (!ret && secure) {
		mtd->memory_region_attributes &= ~FFA_MEM_ATTR_NONSECURE;
	}
	return ret;
}
