mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
179 lines
4.7 KiB
179 lines
4.7 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
/* |
|
* Copyright (C) 2016 Linaro Ltd. <[email protected]> |
|
*/ |
|
|
|
#define pr_fmt(fmt) "efi: memattr: " fmt |
|
|
|
#include <linux/efi.h> |
|
#include <linux/init.h> |
|
#include <linux/io.h> |
|
#include <linux/memblock.h> |
|
|
|
#include <asm/early_ioremap.h> |
|
|
|
static int __initdata tbl_size; |
|
unsigned long __ro_after_init efi_mem_attr_table = EFI_INVALID_TABLE_ADDR; |
|
|
|
/* |
|
* Reserve the memory associated with the Memory Attributes configuration |
|
* table, if it exists. |
|
*/ |
|
int __init efi_memattr_init(void) |
|
{ |
|
efi_memory_attributes_table_t *tbl; |
|
|
|
if (efi_mem_attr_table == EFI_INVALID_TABLE_ADDR) |
|
return 0; |
|
|
|
tbl = early_memremap(efi_mem_attr_table, sizeof(*tbl)); |
|
if (!tbl) { |
|
pr_err("Failed to map EFI Memory Attributes table @ 0x%lx\n", |
|
efi_mem_attr_table); |
|
return -ENOMEM; |
|
} |
|
|
|
if (tbl->version > 1) { |
|
pr_warn("Unexpected EFI Memory Attributes table version %d\n", |
|
tbl->version); |
|
goto unmap; |
|
} |
|
|
|
tbl_size = sizeof(*tbl) + tbl->num_entries * tbl->desc_size; |
|
memblock_reserve(efi_mem_attr_table, tbl_size); |
|
set_bit(EFI_MEM_ATTR, &efi.flags); |
|
|
|
unmap: |
|
early_memunmap(tbl, sizeof(*tbl)); |
|
return 0; |
|
} |
|
|
|
/* |
|
* Returns a copy @out of the UEFI memory descriptor @in if it is covered |
|
* entirely by a UEFI memory map entry with matching attributes. The virtual |
|
* address of @out is set according to the matching entry that was found. |
|
*/ |
|
static bool entry_is_valid(const efi_memory_desc_t *in, efi_memory_desc_t *out) |
|
{ |
|
u64 in_paddr = in->phys_addr; |
|
u64 in_size = in->num_pages << EFI_PAGE_SHIFT; |
|
efi_memory_desc_t *md; |
|
|
|
*out = *in; |
|
|
|
if (in->type != EFI_RUNTIME_SERVICES_CODE && |
|
in->type != EFI_RUNTIME_SERVICES_DATA) { |
|
pr_warn("Entry type should be RuntimeServiceCode/Data\n"); |
|
return false; |
|
} |
|
|
|
if (PAGE_SIZE > EFI_PAGE_SIZE && |
|
(!PAGE_ALIGNED(in->phys_addr) || |
|
!PAGE_ALIGNED(in->num_pages << EFI_PAGE_SHIFT))) { |
|
/* |
|
* Since arm64 may execute with page sizes of up to 64 KB, the |
|
* UEFI spec mandates that RuntimeServices memory regions must |
|
* be 64 KB aligned. We need to validate this here since we will |
|
* not be able to tighten permissions on such regions without |
|
* affecting adjacent regions. |
|
*/ |
|
pr_warn("Entry address region misaligned\n"); |
|
return false; |
|
} |
|
|
|
for_each_efi_memory_desc(md) { |
|
u64 md_paddr = md->phys_addr; |
|
u64 md_size = md->num_pages << EFI_PAGE_SHIFT; |
|
|
|
if (!(md->attribute & EFI_MEMORY_RUNTIME)) |
|
continue; |
|
if (md->virt_addr == 0 && md->phys_addr != 0) { |
|
/* no virtual mapping has been installed by the stub */ |
|
break; |
|
} |
|
|
|
if (md_paddr > in_paddr || (in_paddr - md_paddr) >= md_size) |
|
continue; |
|
|
|
/* |
|
* This entry covers the start of @in, check whether |
|
* it covers the end as well. |
|
*/ |
|
if (md_paddr + md_size < in_paddr + in_size) { |
|
pr_warn("Entry covers multiple EFI memory map regions\n"); |
|
return false; |
|
} |
|
|
|
if (md->type != in->type) { |
|
pr_warn("Entry type deviates from EFI memory map region type\n"); |
|
return false; |
|
} |
|
|
|
out->virt_addr = in_paddr + (md->virt_addr - md_paddr); |
|
|
|
return true; |
|
} |
|
|
|
pr_warn("No matching entry found in the EFI memory map\n"); |
|
return false; |
|
} |
|
|
|
/* |
|
* To be called after the EFI page tables have been populated. If a memory |
|
* attributes table is available, its contents will be used to update the |
|
* mappings with tightened permissions as described by the table. |
|
* This requires the UEFI memory map to have already been populated with |
|
* virtual addresses. |
|
*/ |
|
int __init efi_memattr_apply_permissions(struct mm_struct *mm, |
|
efi_memattr_perm_setter fn) |
|
{ |
|
efi_memory_attributes_table_t *tbl; |
|
int i, ret; |
|
|
|
if (tbl_size <= sizeof(*tbl)) |
|
return 0; |
|
|
|
/* |
|
* We need the EFI memory map to be setup so we can use it to |
|
* lookup the virtual addresses of all entries in the of EFI |
|
* Memory Attributes table. If it isn't available, this |
|
* function should not be called. |
|
*/ |
|
if (WARN_ON(!efi_enabled(EFI_MEMMAP))) |
|
return 0; |
|
|
|
tbl = memremap(efi_mem_attr_table, tbl_size, MEMREMAP_WB); |
|
if (!tbl) { |
|
pr_err("Failed to map EFI Memory Attributes table @ 0x%lx\n", |
|
efi_mem_attr_table); |
|
return -ENOMEM; |
|
} |
|
|
|
if (efi_enabled(EFI_DBG)) |
|
pr_info("Processing EFI Memory Attributes table:\n"); |
|
|
|
for (i = ret = 0; ret == 0 && i < tbl->num_entries; i++) { |
|
efi_memory_desc_t md; |
|
unsigned long size; |
|
bool valid; |
|
char buf[64]; |
|
|
|
valid = entry_is_valid((void *)tbl->entry + i * tbl->desc_size, |
|
&md); |
|
size = md.num_pages << EFI_PAGE_SHIFT; |
|
if (efi_enabled(EFI_DBG) || !valid) |
|
pr_info("%s 0x%012llx-0x%012llx %s\n", |
|
valid ? "" : "!", md.phys_addr, |
|
md.phys_addr + size - 1, |
|
efi_md_typeattr_format(buf, sizeof(buf), &md)); |
|
|
|
if (valid) { |
|
ret = fn(mm, &md); |
|
if (ret) |
|
pr_err("Error updating mappings, skipping subsequent md's\n"); |
|
} |
|
} |
|
memunmap(tbl); |
|
return ret; |
|
}
|
|
|