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

#define pr_fmt(fmt) "efi: " fmt

#include <linux/module.h>
#include <linux/init.h>
#include <linux/efi.h>
#include <linux/libfdt.h>
#include <linux/of_fdt.h>

#include <asm/unaligned.h>

enum {
	SYSTAB,
	MMBASE,
	MMSIZE,
	DCSIZE,
	DCVERS,

	PARAMCOUNT
};

static __initconst const char name[][22] = {
	[SYSTAB] = "System Table         ",
	[MMBASE] = "MemMap Address       ",
	[MMSIZE] = "MemMap Size          ",
	[DCSIZE] = "MemMap Desc. Size    ",
	[DCVERS] = "MemMap Desc. Version ",
};

static __initconst const struct {
	const char	path[17];
	u8		paravirt;
	const char	params[PARAMCOUNT][26];
} dt_params[] = {
	{
#ifdef CONFIG_XEN    //  <-------17------>
		.path = "/hypervisor/uefi",
		.paravirt = 1,
		.params = {
			[SYSTAB] = "xen,uefi-system-table",
			[MMBASE] = "xen,uefi-mmap-start",
			[MMSIZE] = "xen,uefi-mmap-size",
			[DCSIZE] = "xen,uefi-mmap-desc-size",
			[DCVERS] = "xen,uefi-mmap-desc-ver",
		}
	}, {
#endif
		.path = "/chosen",
		.params = {	//  <-----------26----------->
			[SYSTAB] = "linux,uefi-system-table",
			[MMBASE] = "linux,uefi-mmap-start",
			[MMSIZE] = "linux,uefi-mmap-size",
			[DCSIZE] = "linux,uefi-mmap-desc-size",
			[DCVERS] = "linux,uefi-mmap-desc-ver",
		}
	}
};

static int __init efi_get_fdt_prop(const void *fdt, int node, const char *pname,
				   const char *rname, void *var, int size)
{
	const void *prop;
	int len;
	u64 val;

	prop = fdt_getprop(fdt, node, pname, &len);
	if (!prop)
		return 1;

	val = (len == 4) ? (u64)be32_to_cpup(prop) : get_unaligned_be64(prop);

	if (size == 8)
		*(u64 *)var = val;
	else
		*(u32 *)var = (val < U32_MAX) ? val : U32_MAX; // saturate

	if (efi_enabled(EFI_DBG))
		pr_info("  %s: 0x%0*llx\n", rname, size * 2, val);

	return 0;
}

u64 __init efi_get_fdt_params(struct efi_memory_map_data *mm)
{
	const void *fdt = initial_boot_params;
	unsigned long systab;
	int i, j, node;
	struct {
		void	*var;
		int	size;
	} target[] = {
		[SYSTAB] = { &systab,		sizeof(systab) },
		[MMBASE] = { &mm->phys_map,	sizeof(mm->phys_map) },
		[MMSIZE] = { &mm->size,		sizeof(mm->size) },
		[DCSIZE] = { &mm->desc_size,	sizeof(mm->desc_size) },
		[DCVERS] = { &mm->desc_version,	sizeof(mm->desc_version) },
	};

	BUILD_BUG_ON(ARRAY_SIZE(target) != ARRAY_SIZE(name));
	BUILD_BUG_ON(ARRAY_SIZE(target) != ARRAY_SIZE(dt_params[0].params));

	if (!fdt)
		return 0;

	for (i = 0; i < ARRAY_SIZE(dt_params); i++) {
		node = fdt_path_offset(fdt, dt_params[i].path);
		if (node < 0)
			continue;

		if (efi_enabled(EFI_DBG))
			pr_info("Getting UEFI parameters from %s in DT:\n",
				dt_params[i].path);

		for (j = 0; j < ARRAY_SIZE(target); j++) {
			const char *pname = dt_params[i].params[j];

			if (!efi_get_fdt_prop(fdt, node, pname, name[j],
					      target[j].var, target[j].size))
				continue;
			if (!j)
				goto notfound;
			pr_err("Can't find property '%s' in DT!\n", pname);
			return 0;
		}
		if (dt_params[i].paravirt)
			set_bit(EFI_PARAVIRT, &efi.flags);
		return systab;
	}
notfound:
	pr_info("UEFI not found.\n");
	return 0;
}