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