/* SPDX-License-Identifier: GPL-2.0-only */

#include "psp_verstage.h"

#include <bl_uapp/bl_errorcodes_public.h>
#include <bl_uapp/bl_syscall_public.h>
#include <boot_device.h>
#include <commonlib/region.h>
#include <console/console.h>
#include <string.h>

#define DEST_BUF_ALIGNMENT 16

static uint32_t active_map_count;

static void *boot_dev_mmap(const struct region_device *rd, size_t offset, size_t size)
{
	const struct mem_region_device *mdev;
	void *mapping = NULL;
	uint32_t ret;

	mdev = container_of(rd, __typeof__(*mdev), rdev);
	if (CONFIG(PSP_VERSTAGE_MAP_ENTIRE_SPIROM))
		return &(mdev->base[offset]);

	if (mdev->base) {
		if ((ret = svc_map_spi_rom(&mdev->base[offset], size, (void **)&mapping))
										!= BL_OK)
			printk(BIOS_ERR, "Failed(%d) to map SPI ROM offset: %zu, size: %zu\n",
									ret, offset, size);
		else
			active_map_count++;
	}

	return mapping;
}

static int boot_dev_munmap(const struct region_device *rd, void *mapping)
{
	uint32_t ret;

	if (CONFIG(PSP_VERSTAGE_MAP_ENTIRE_SPIROM))
		return 0;

	active_map_count--;
	if ((ret = svc_unmap_spi_rom(mapping)) != BL_OK)
		printk(BIOS_ERR, "Failed(%d) to unmap SPI ROM mapping %p\n", ret, mapping);
	return 0;
}

static ssize_t boot_dev_mmap_readat(const struct region_device *rd, void *dest,
							size_t offset, size_t size)
{
	void *mapping = boot_dev_mmap(rd, offset, size);

	if (!mapping)
		return -1;

	memcpy(dest, mapping, size);
	boot_dev_munmap(rd, mapping);
	return size;
}

static ssize_t boot_dev_dma_readat(const struct region_device *rd, void *dest,
							size_t offset, size_t size)
{
	size_t memcpy_size = ALIGN_UP((uintptr_t)dest, DEST_BUF_ALIGNMENT) - (uintptr_t)dest;
	const struct mem_region_device *mdev = container_of(rd, __typeof__(*mdev), rdev);
	int ret;

	if (memcpy_size > size)
		memcpy_size = size;
	/* Alignment requirement is only on dest buffer for CCP DMA. So do a memcpy
	   until the destination buffer alignment requirement is met. */
	if (memcpy_size && boot_dev_mmap_readat(rd, dest, offset, memcpy_size) != memcpy_size)
		return -1;

	dest = ((char *)dest + memcpy_size);
	offset += memcpy_size;
	size -= memcpy_size;
	if (!size)
		return memcpy_size;

	ret = svc_ccp_dma((uint32_t)offset, dest, (uint32_t)size);
	if (ret != BL_OK) {
		printk(BIOS_ERR, "%s: Failed dest:%p offset:%zu size:%zu ret:%d\n",
						__func__, dest, offset, size, ret);
		return -1;
	}

	return size + memcpy_size;
}

static ssize_t boot_dev_readat(const struct region_device *rd, void *dest,
						size_t offset, size_t size)
{
	const struct mem_region_device *mdev = container_of(rd, __typeof__(*mdev), rdev);

	if (CONFIG(PSP_VERSTAGE_CCP_DMA))
		return boot_dev_dma_readat(rd, dest, offset, size);

	return boot_dev_mmap_readat(rd, dest, offset, size);
}

const struct region_device_ops boot_dev_rdev_ro_ops = {
	.mmap = boot_dev_mmap,
	.munmap = boot_dev_munmap,
	.readat = boot_dev_readat,
};

static struct mem_region_device boot_dev = {
	.rdev = REGION_DEV_INIT(&boot_dev_rdev_ro_ops, 0, CONFIG_ROM_SIZE),
};

const struct region_device *boot_device_ro(void)
{
	if (!boot_dev.base)
		boot_dev.base = (void *)map_spi_rom();
	return &boot_dev.rdev;
}

uint32_t boot_dev_get_active_map_count(void)
{
	return active_map_count;
}
