18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright (C) 2016 Linaro Ltd. <ard.biesheuvel@linaro.org>
48c2ecf20Sopenharmony_ci */
58c2ecf20Sopenharmony_ci
68c2ecf20Sopenharmony_ci#define pr_fmt(fmt)	"efi: memattr: " fmt
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/efi.h>
98c2ecf20Sopenharmony_ci#include <linux/init.h>
108c2ecf20Sopenharmony_ci#include <linux/io.h>
118c2ecf20Sopenharmony_ci#include <linux/memblock.h>
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_ci#include <asm/early_ioremap.h>
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_cistatic int __initdata tbl_size;
168c2ecf20Sopenharmony_ciunsigned long __ro_after_init efi_mem_attr_table = EFI_INVALID_TABLE_ADDR;
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_ci/*
198c2ecf20Sopenharmony_ci * Reserve the memory associated with the Memory Attributes configuration
208c2ecf20Sopenharmony_ci * table, if it exists.
218c2ecf20Sopenharmony_ci */
228c2ecf20Sopenharmony_ciint __init efi_memattr_init(void)
238c2ecf20Sopenharmony_ci{
248c2ecf20Sopenharmony_ci	efi_memory_attributes_table_t *tbl;
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_ci	if (efi_mem_attr_table == EFI_INVALID_TABLE_ADDR)
278c2ecf20Sopenharmony_ci		return 0;
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci	tbl = early_memremap(efi_mem_attr_table, sizeof(*tbl));
308c2ecf20Sopenharmony_ci	if (!tbl) {
318c2ecf20Sopenharmony_ci		pr_err("Failed to map EFI Memory Attributes table @ 0x%lx\n",
328c2ecf20Sopenharmony_ci		       efi_mem_attr_table);
338c2ecf20Sopenharmony_ci		return -ENOMEM;
348c2ecf20Sopenharmony_ci	}
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci	if (tbl->version > 2) {
378c2ecf20Sopenharmony_ci		pr_warn("Unexpected EFI Memory Attributes table version %d\n",
388c2ecf20Sopenharmony_ci			tbl->version);
398c2ecf20Sopenharmony_ci		goto unmap;
408c2ecf20Sopenharmony_ci	}
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci	tbl_size = sizeof(*tbl) + tbl->num_entries * tbl->desc_size;
438c2ecf20Sopenharmony_ci	memblock_reserve(efi_mem_attr_table, tbl_size);
448c2ecf20Sopenharmony_ci	set_bit(EFI_MEM_ATTR, &efi.flags);
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_ciunmap:
478c2ecf20Sopenharmony_ci	early_memunmap(tbl, sizeof(*tbl));
488c2ecf20Sopenharmony_ci	return 0;
498c2ecf20Sopenharmony_ci}
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_ci/*
528c2ecf20Sopenharmony_ci * Returns a copy @out of the UEFI memory descriptor @in if it is covered
538c2ecf20Sopenharmony_ci * entirely by a UEFI memory map entry with matching attributes. The virtual
548c2ecf20Sopenharmony_ci * address of @out is set according to the matching entry that was found.
558c2ecf20Sopenharmony_ci */
568c2ecf20Sopenharmony_cistatic bool entry_is_valid(const efi_memory_desc_t *in, efi_memory_desc_t *out)
578c2ecf20Sopenharmony_ci{
588c2ecf20Sopenharmony_ci	u64 in_paddr = in->phys_addr;
598c2ecf20Sopenharmony_ci	u64 in_size = in->num_pages << EFI_PAGE_SHIFT;
608c2ecf20Sopenharmony_ci	efi_memory_desc_t *md;
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci	*out = *in;
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci	if (in->type != EFI_RUNTIME_SERVICES_CODE &&
658c2ecf20Sopenharmony_ci	    in->type != EFI_RUNTIME_SERVICES_DATA) {
668c2ecf20Sopenharmony_ci		pr_warn("Entry type should be RuntimeServiceCode/Data\n");
678c2ecf20Sopenharmony_ci		return false;
688c2ecf20Sopenharmony_ci	}
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci	if (PAGE_SIZE > EFI_PAGE_SIZE &&
718c2ecf20Sopenharmony_ci	    (!PAGE_ALIGNED(in->phys_addr) ||
728c2ecf20Sopenharmony_ci	     !PAGE_ALIGNED(in->num_pages << EFI_PAGE_SHIFT))) {
738c2ecf20Sopenharmony_ci		/*
748c2ecf20Sopenharmony_ci		 * Since arm64 may execute with page sizes of up to 64 KB, the
758c2ecf20Sopenharmony_ci		 * UEFI spec mandates that RuntimeServices memory regions must
768c2ecf20Sopenharmony_ci		 * be 64 KB aligned. We need to validate this here since we will
778c2ecf20Sopenharmony_ci		 * not be able to tighten permissions on such regions without
788c2ecf20Sopenharmony_ci		 * affecting adjacent regions.
798c2ecf20Sopenharmony_ci		 */
808c2ecf20Sopenharmony_ci		pr_warn("Entry address region misaligned\n");
818c2ecf20Sopenharmony_ci		return false;
828c2ecf20Sopenharmony_ci	}
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci	for_each_efi_memory_desc(md) {
858c2ecf20Sopenharmony_ci		u64 md_paddr = md->phys_addr;
868c2ecf20Sopenharmony_ci		u64 md_size = md->num_pages << EFI_PAGE_SHIFT;
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ci		if (!(md->attribute & EFI_MEMORY_RUNTIME))
898c2ecf20Sopenharmony_ci			continue;
908c2ecf20Sopenharmony_ci		if (md->virt_addr == 0 && md->phys_addr != 0) {
918c2ecf20Sopenharmony_ci			/* no virtual mapping has been installed by the stub */
928c2ecf20Sopenharmony_ci			break;
938c2ecf20Sopenharmony_ci		}
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ci		if (md_paddr > in_paddr || (in_paddr - md_paddr) >= md_size)
968c2ecf20Sopenharmony_ci			continue;
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ci		/*
998c2ecf20Sopenharmony_ci		 * This entry covers the start of @in, check whether
1008c2ecf20Sopenharmony_ci		 * it covers the end as well.
1018c2ecf20Sopenharmony_ci		 */
1028c2ecf20Sopenharmony_ci		if (md_paddr + md_size < in_paddr + in_size) {
1038c2ecf20Sopenharmony_ci			pr_warn("Entry covers multiple EFI memory map regions\n");
1048c2ecf20Sopenharmony_ci			return false;
1058c2ecf20Sopenharmony_ci		}
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci		if (md->type != in->type) {
1088c2ecf20Sopenharmony_ci			pr_warn("Entry type deviates from EFI memory map region type\n");
1098c2ecf20Sopenharmony_ci			return false;
1108c2ecf20Sopenharmony_ci		}
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci		out->virt_addr = in_paddr + (md->virt_addr - md_paddr);
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci		return true;
1158c2ecf20Sopenharmony_ci	}
1168c2ecf20Sopenharmony_ci
1178c2ecf20Sopenharmony_ci	pr_warn("No matching entry found in the EFI memory map\n");
1188c2ecf20Sopenharmony_ci	return false;
1198c2ecf20Sopenharmony_ci}
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ci/*
1228c2ecf20Sopenharmony_ci * To be called after the EFI page tables have been populated. If a memory
1238c2ecf20Sopenharmony_ci * attributes table is available, its contents will be used to update the
1248c2ecf20Sopenharmony_ci * mappings with tightened permissions as described by the table.
1258c2ecf20Sopenharmony_ci * This requires the UEFI memory map to have already been populated with
1268c2ecf20Sopenharmony_ci * virtual addresses.
1278c2ecf20Sopenharmony_ci */
1288c2ecf20Sopenharmony_ciint __init efi_memattr_apply_permissions(struct mm_struct *mm,
1298c2ecf20Sopenharmony_ci					 efi_memattr_perm_setter fn)
1308c2ecf20Sopenharmony_ci{
1318c2ecf20Sopenharmony_ci	efi_memory_attributes_table_t *tbl;
1328c2ecf20Sopenharmony_ci	int i, ret;
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci	if (tbl_size <= sizeof(*tbl))
1358c2ecf20Sopenharmony_ci		return 0;
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	/*
1388c2ecf20Sopenharmony_ci	 * We need the EFI memory map to be setup so we can use it to
1398c2ecf20Sopenharmony_ci	 * lookup the virtual addresses of all entries in the  of EFI
1408c2ecf20Sopenharmony_ci	 * Memory Attributes table. If it isn't available, this
1418c2ecf20Sopenharmony_ci	 * function should not be called.
1428c2ecf20Sopenharmony_ci	 */
1438c2ecf20Sopenharmony_ci	if (WARN_ON(!efi_enabled(EFI_MEMMAP)))
1448c2ecf20Sopenharmony_ci		return 0;
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_ci	tbl = memremap(efi_mem_attr_table, tbl_size, MEMREMAP_WB);
1478c2ecf20Sopenharmony_ci	if (!tbl) {
1488c2ecf20Sopenharmony_ci		pr_err("Failed to map EFI Memory Attributes table @ 0x%lx\n",
1498c2ecf20Sopenharmony_ci		       efi_mem_attr_table);
1508c2ecf20Sopenharmony_ci		return -ENOMEM;
1518c2ecf20Sopenharmony_ci	}
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci	if (efi_enabled(EFI_DBG))
1548c2ecf20Sopenharmony_ci		pr_info("Processing EFI Memory Attributes table:\n");
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_ci	for (i = ret = 0; ret == 0 && i < tbl->num_entries; i++) {
1578c2ecf20Sopenharmony_ci		efi_memory_desc_t md;
1588c2ecf20Sopenharmony_ci		unsigned long size;
1598c2ecf20Sopenharmony_ci		bool valid;
1608c2ecf20Sopenharmony_ci		char buf[64];
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci		valid = entry_is_valid((void *)tbl->entry + i * tbl->desc_size,
1638c2ecf20Sopenharmony_ci				       &md);
1648c2ecf20Sopenharmony_ci		size = md.num_pages << EFI_PAGE_SHIFT;
1658c2ecf20Sopenharmony_ci		if (efi_enabled(EFI_DBG) || !valid)
1668c2ecf20Sopenharmony_ci			pr_info("%s 0x%012llx-0x%012llx %s\n",
1678c2ecf20Sopenharmony_ci				valid ? "" : "!", md.phys_addr,
1688c2ecf20Sopenharmony_ci				md.phys_addr + size - 1,
1698c2ecf20Sopenharmony_ci				efi_md_typeattr_format(buf, sizeof(buf), &md));
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_ci		if (valid) {
1728c2ecf20Sopenharmony_ci			ret = fn(mm, &md);
1738c2ecf20Sopenharmony_ci			if (ret)
1748c2ecf20Sopenharmony_ci				pr_err("Error updating mappings, skipping subsequent md's\n");
1758c2ecf20Sopenharmony_ci		}
1768c2ecf20Sopenharmony_ci	}
1778c2ecf20Sopenharmony_ci	memunmap(tbl);
1788c2ecf20Sopenharmony_ci	return ret;
1798c2ecf20Sopenharmony_ci}
180