162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2016 Linaro Ltd. <ard.biesheuvel@linaro.org>
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#define pr_fmt(fmt)	"efi: memattr: " fmt
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/efi.h>
962306a36Sopenharmony_ci#include <linux/init.h>
1062306a36Sopenharmony_ci#include <linux/io.h>
1162306a36Sopenharmony_ci#include <linux/memblock.h>
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#include <asm/early_ioremap.h>
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_cistatic int __initdata tbl_size;
1662306a36Sopenharmony_ciunsigned long __ro_after_init efi_mem_attr_table = EFI_INVALID_TABLE_ADDR;
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci/*
1962306a36Sopenharmony_ci * Reserve the memory associated with the Memory Attributes configuration
2062306a36Sopenharmony_ci * table, if it exists.
2162306a36Sopenharmony_ci */
2262306a36Sopenharmony_ciint __init efi_memattr_init(void)
2362306a36Sopenharmony_ci{
2462306a36Sopenharmony_ci	efi_memory_attributes_table_t *tbl;
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci	if (efi_mem_attr_table == EFI_INVALID_TABLE_ADDR)
2762306a36Sopenharmony_ci		return 0;
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci	tbl = early_memremap(efi_mem_attr_table, sizeof(*tbl));
3062306a36Sopenharmony_ci	if (!tbl) {
3162306a36Sopenharmony_ci		pr_err("Failed to map EFI Memory Attributes table @ 0x%lx\n",
3262306a36Sopenharmony_ci		       efi_mem_attr_table);
3362306a36Sopenharmony_ci		return -ENOMEM;
3462306a36Sopenharmony_ci	}
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci	if (tbl->version > 2) {
3762306a36Sopenharmony_ci		pr_warn("Unexpected EFI Memory Attributes table version %d\n",
3862306a36Sopenharmony_ci			tbl->version);
3962306a36Sopenharmony_ci		goto unmap;
4062306a36Sopenharmony_ci	}
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci	tbl_size = sizeof(*tbl) + tbl->num_entries * tbl->desc_size;
4362306a36Sopenharmony_ci	memblock_reserve(efi_mem_attr_table, tbl_size);
4462306a36Sopenharmony_ci	set_bit(EFI_MEM_ATTR, &efi.flags);
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ciunmap:
4762306a36Sopenharmony_ci	early_memunmap(tbl, sizeof(*tbl));
4862306a36Sopenharmony_ci	return 0;
4962306a36Sopenharmony_ci}
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci/*
5262306a36Sopenharmony_ci * Returns a copy @out of the UEFI memory descriptor @in if it is covered
5362306a36Sopenharmony_ci * entirely by a UEFI memory map entry with matching attributes. The virtual
5462306a36Sopenharmony_ci * address of @out is set according to the matching entry that was found.
5562306a36Sopenharmony_ci */
5662306a36Sopenharmony_cistatic bool entry_is_valid(const efi_memory_desc_t *in, efi_memory_desc_t *out)
5762306a36Sopenharmony_ci{
5862306a36Sopenharmony_ci	u64 in_paddr = in->phys_addr;
5962306a36Sopenharmony_ci	u64 in_size = in->num_pages << EFI_PAGE_SHIFT;
6062306a36Sopenharmony_ci	efi_memory_desc_t *md;
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	*out = *in;
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	if (in->type != EFI_RUNTIME_SERVICES_CODE &&
6562306a36Sopenharmony_ci	    in->type != EFI_RUNTIME_SERVICES_DATA) {
6662306a36Sopenharmony_ci		pr_warn("Entry type should be RuntimeServiceCode/Data\n");
6762306a36Sopenharmony_ci		return false;
6862306a36Sopenharmony_ci	}
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	if (PAGE_SIZE > EFI_PAGE_SIZE &&
7162306a36Sopenharmony_ci	    (!PAGE_ALIGNED(in->phys_addr) ||
7262306a36Sopenharmony_ci	     !PAGE_ALIGNED(in->num_pages << EFI_PAGE_SHIFT))) {
7362306a36Sopenharmony_ci		/*
7462306a36Sopenharmony_ci		 * Since arm64 may execute with page sizes of up to 64 KB, the
7562306a36Sopenharmony_ci		 * UEFI spec mandates that RuntimeServices memory regions must
7662306a36Sopenharmony_ci		 * be 64 KB aligned. We need to validate this here since we will
7762306a36Sopenharmony_ci		 * not be able to tighten permissions on such regions without
7862306a36Sopenharmony_ci		 * affecting adjacent regions.
7962306a36Sopenharmony_ci		 */
8062306a36Sopenharmony_ci		pr_warn("Entry address region misaligned\n");
8162306a36Sopenharmony_ci		return false;
8262306a36Sopenharmony_ci	}
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	for_each_efi_memory_desc(md) {
8562306a36Sopenharmony_ci		u64 md_paddr = md->phys_addr;
8662306a36Sopenharmony_ci		u64 md_size = md->num_pages << EFI_PAGE_SHIFT;
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci		if (!(md->attribute & EFI_MEMORY_RUNTIME))
8962306a36Sopenharmony_ci			continue;
9062306a36Sopenharmony_ci		if (md->virt_addr == 0 && md->phys_addr != 0) {
9162306a36Sopenharmony_ci			/* no virtual mapping has been installed by the stub */
9262306a36Sopenharmony_ci			break;
9362306a36Sopenharmony_ci		}
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci		if (md_paddr > in_paddr || (in_paddr - md_paddr) >= md_size)
9662306a36Sopenharmony_ci			continue;
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci		/*
9962306a36Sopenharmony_ci		 * This entry covers the start of @in, check whether
10062306a36Sopenharmony_ci		 * it covers the end as well.
10162306a36Sopenharmony_ci		 */
10262306a36Sopenharmony_ci		if (md_paddr + md_size < in_paddr + in_size) {
10362306a36Sopenharmony_ci			pr_warn("Entry covers multiple EFI memory map regions\n");
10462306a36Sopenharmony_ci			return false;
10562306a36Sopenharmony_ci		}
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci		if (md->type != in->type) {
10862306a36Sopenharmony_ci			pr_warn("Entry type deviates from EFI memory map region type\n");
10962306a36Sopenharmony_ci			return false;
11062306a36Sopenharmony_ci		}
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci		out->virt_addr = in_paddr + (md->virt_addr - md_paddr);
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci		return true;
11562306a36Sopenharmony_ci	}
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	pr_warn("No matching entry found in the EFI memory map\n");
11862306a36Sopenharmony_ci	return false;
11962306a36Sopenharmony_ci}
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci/*
12262306a36Sopenharmony_ci * To be called after the EFI page tables have been populated. If a memory
12362306a36Sopenharmony_ci * attributes table is available, its contents will be used to update the
12462306a36Sopenharmony_ci * mappings with tightened permissions as described by the table.
12562306a36Sopenharmony_ci * This requires the UEFI memory map to have already been populated with
12662306a36Sopenharmony_ci * virtual addresses.
12762306a36Sopenharmony_ci */
12862306a36Sopenharmony_ciint __init efi_memattr_apply_permissions(struct mm_struct *mm,
12962306a36Sopenharmony_ci					 efi_memattr_perm_setter fn)
13062306a36Sopenharmony_ci{
13162306a36Sopenharmony_ci	efi_memory_attributes_table_t *tbl;
13262306a36Sopenharmony_ci	bool has_bti = false;
13362306a36Sopenharmony_ci	int i, ret;
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	if (tbl_size <= sizeof(*tbl))
13662306a36Sopenharmony_ci		return 0;
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci	/*
13962306a36Sopenharmony_ci	 * We need the EFI memory map to be setup so we can use it to
14062306a36Sopenharmony_ci	 * lookup the virtual addresses of all entries in the  of EFI
14162306a36Sopenharmony_ci	 * Memory Attributes table. If it isn't available, this
14262306a36Sopenharmony_ci	 * function should not be called.
14362306a36Sopenharmony_ci	 */
14462306a36Sopenharmony_ci	if (WARN_ON(!efi_enabled(EFI_MEMMAP)))
14562306a36Sopenharmony_ci		return 0;
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	tbl = memremap(efi_mem_attr_table, tbl_size, MEMREMAP_WB);
14862306a36Sopenharmony_ci	if (!tbl) {
14962306a36Sopenharmony_ci		pr_err("Failed to map EFI Memory Attributes table @ 0x%lx\n",
15062306a36Sopenharmony_ci		       efi_mem_attr_table);
15162306a36Sopenharmony_ci		return -ENOMEM;
15262306a36Sopenharmony_ci	}
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	if (tbl->version > 1 &&
15562306a36Sopenharmony_ci	    (tbl->flags & EFI_MEMORY_ATTRIBUTES_FLAGS_RT_FORWARD_CONTROL_FLOW_GUARD))
15662306a36Sopenharmony_ci		has_bti = true;
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci	if (efi_enabled(EFI_DBG))
15962306a36Sopenharmony_ci		pr_info("Processing EFI Memory Attributes table:\n");
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	for (i = ret = 0; ret == 0 && i < tbl->num_entries; i++) {
16262306a36Sopenharmony_ci		efi_memory_desc_t md;
16362306a36Sopenharmony_ci		unsigned long size;
16462306a36Sopenharmony_ci		bool valid;
16562306a36Sopenharmony_ci		char buf[64];
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci		valid = entry_is_valid((void *)tbl->entry + i * tbl->desc_size,
16862306a36Sopenharmony_ci				       &md);
16962306a36Sopenharmony_ci		size = md.num_pages << EFI_PAGE_SHIFT;
17062306a36Sopenharmony_ci		if (efi_enabled(EFI_DBG) || !valid)
17162306a36Sopenharmony_ci			pr_info("%s 0x%012llx-0x%012llx %s\n",
17262306a36Sopenharmony_ci				valid ? "" : "!", md.phys_addr,
17362306a36Sopenharmony_ci				md.phys_addr + size - 1,
17462306a36Sopenharmony_ci				efi_md_typeattr_format(buf, sizeof(buf), &md));
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci		if (valid) {
17762306a36Sopenharmony_ci			ret = fn(mm, &md, has_bti);
17862306a36Sopenharmony_ci			if (ret)
17962306a36Sopenharmony_ci				pr_err("Error updating mappings, skipping subsequent md's\n");
18062306a36Sopenharmony_ci		}
18162306a36Sopenharmony_ci	}
18262306a36Sopenharmony_ci	memunmap(tbl);
18362306a36Sopenharmony_ci	return ret;
18462306a36Sopenharmony_ci}
185