mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-06-06 22:26:50 -04:00
The EFI specification has some elaborate rules about which runtime services may be called while another runtime service call is already in progress. In Linux, however, for simplicity, all EFI runtime service invocations are serialized via the efi_runtime_lock semaphore. This implies that calls to the helper pair arch_efi_call_virt_setup() and arch_efi_call_virt_teardown() are serialized too, and are guaranteed not to nest. Furthermore, the arm64 arch code has its own spinlock to serialize use of the EFI runtime stack, of which only a single instance exists. This all means that the FP/SIMD and SVE state preserve/restore logic in __efi_fpsimd_begin() and __efi_fpsimd_end() are also serialized, and only a single instance of the associated per-CPU variables can ever be in use at the same time. There is therefore no need at all for per-CPU variables here, and they can all be replaced with singleton instances. This saves a non-trivial amount of memory on systems with many CPUs. To be more robust against potential future changes in the core EFI code that may invalidate the reasoning above, move the invocations of __efi_fpsimd_begin() and __efi_fpsimd_end() into the critical section covered by the efi_rt_lock spinlock. Signed-off-by: Ard Biesheuvel <ardb@kernel.org> Reviewed-by: Mark Brown <broonie@kernel.org> Link: https://lore.kernel.org/r/20250318132421.3155799-2-ardb+git@google.com Signed-off-by: Will Deacon <will@kernel.org>
230 lines
6.2 KiB
C
230 lines
6.2 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Extensible Firmware Interface
|
|
*
|
|
* Based on Extensible Firmware Interface Specification version 2.4
|
|
*
|
|
* Copyright (C) 2013, 2014 Linaro Ltd.
|
|
*/
|
|
|
|
#include <linux/efi.h>
|
|
#include <linux/init.h>
|
|
#include <linux/kmemleak.h>
|
|
#include <linux/screen_info.h>
|
|
#include <linux/vmalloc.h>
|
|
|
|
#include <asm/efi.h>
|
|
#include <asm/stacktrace.h>
|
|
|
|
static bool region_is_misaligned(const efi_memory_desc_t *md)
|
|
{
|
|
if (PAGE_SIZE == EFI_PAGE_SIZE)
|
|
return false;
|
|
return !PAGE_ALIGNED(md->phys_addr) ||
|
|
!PAGE_ALIGNED(md->num_pages << EFI_PAGE_SHIFT);
|
|
}
|
|
|
|
/*
|
|
* Only regions of type EFI_RUNTIME_SERVICES_CODE need to be
|
|
* executable, everything else can be mapped with the XN bits
|
|
* set. Also take the new (optional) RO/XP bits into account.
|
|
*/
|
|
static __init pteval_t create_mapping_protection(efi_memory_desc_t *md)
|
|
{
|
|
u64 attr = md->attribute;
|
|
u32 type = md->type;
|
|
|
|
if (type == EFI_MEMORY_MAPPED_IO) {
|
|
pgprot_t prot = __pgprot(PROT_DEVICE_nGnRE);
|
|
|
|
if (arm64_is_protected_mmio(md->phys_addr,
|
|
md->num_pages << EFI_PAGE_SHIFT))
|
|
prot = pgprot_encrypted(prot);
|
|
else
|
|
prot = pgprot_decrypted(prot);
|
|
return pgprot_val(prot);
|
|
}
|
|
|
|
if (region_is_misaligned(md)) {
|
|
static bool __initdata code_is_misaligned;
|
|
|
|
/*
|
|
* Regions that are not aligned to the OS page size cannot be
|
|
* mapped with strict permissions, as those might interfere
|
|
* with the permissions that are needed by the adjacent
|
|
* region's mapping. However, if we haven't encountered any
|
|
* misaligned runtime code regions so far, we can safely use
|
|
* non-executable permissions for non-code regions.
|
|
*/
|
|
code_is_misaligned |= (type == EFI_RUNTIME_SERVICES_CODE);
|
|
|
|
return code_is_misaligned ? pgprot_val(PAGE_KERNEL_EXEC)
|
|
: pgprot_val(PAGE_KERNEL);
|
|
}
|
|
|
|
/* R-- */
|
|
if ((attr & (EFI_MEMORY_XP | EFI_MEMORY_RO)) ==
|
|
(EFI_MEMORY_XP | EFI_MEMORY_RO))
|
|
return pgprot_val(PAGE_KERNEL_RO);
|
|
|
|
/* R-X */
|
|
if (attr & EFI_MEMORY_RO)
|
|
return pgprot_val(PAGE_KERNEL_ROX);
|
|
|
|
/* RW- */
|
|
if (((attr & (EFI_MEMORY_RP | EFI_MEMORY_WP | EFI_MEMORY_XP)) ==
|
|
EFI_MEMORY_XP) ||
|
|
type != EFI_RUNTIME_SERVICES_CODE)
|
|
return pgprot_val(PAGE_KERNEL);
|
|
|
|
/* RWX */
|
|
return pgprot_val(PAGE_KERNEL_EXEC);
|
|
}
|
|
|
|
int __init efi_create_mapping(struct mm_struct *mm, efi_memory_desc_t *md)
|
|
{
|
|
pteval_t prot_val = create_mapping_protection(md);
|
|
bool page_mappings_only = (md->type == EFI_RUNTIME_SERVICES_CODE ||
|
|
md->type == EFI_RUNTIME_SERVICES_DATA);
|
|
|
|
/*
|
|
* If this region is not aligned to the page size used by the OS, the
|
|
* mapping will be rounded outwards, and may end up sharing a page
|
|
* frame with an adjacent runtime memory region. Given that the page
|
|
* table descriptor covering the shared page will be rewritten when the
|
|
* adjacent region gets mapped, we must avoid block mappings here so we
|
|
* don't have to worry about splitting them when that happens.
|
|
*/
|
|
if (region_is_misaligned(md))
|
|
page_mappings_only = true;
|
|
|
|
create_pgd_mapping(mm, md->phys_addr, md->virt_addr,
|
|
md->num_pages << EFI_PAGE_SHIFT,
|
|
__pgprot(prot_val | PTE_NG), page_mappings_only);
|
|
return 0;
|
|
}
|
|
|
|
struct set_perm_data {
|
|
const efi_memory_desc_t *md;
|
|
bool has_bti;
|
|
};
|
|
|
|
static int __init set_permissions(pte_t *ptep, unsigned long addr, void *data)
|
|
{
|
|
struct set_perm_data *spd = data;
|
|
const efi_memory_desc_t *md = spd->md;
|
|
pte_t pte = __ptep_get(ptep);
|
|
|
|
if (md->attribute & EFI_MEMORY_RO)
|
|
pte = set_pte_bit(pte, __pgprot(PTE_RDONLY));
|
|
if (md->attribute & EFI_MEMORY_XP)
|
|
pte = set_pte_bit(pte, __pgprot(PTE_PXN));
|
|
else if (system_supports_bti_kernel() && spd->has_bti)
|
|
pte = set_pte_bit(pte, __pgprot(PTE_GP));
|
|
__set_pte(ptep, pte);
|
|
return 0;
|
|
}
|
|
|
|
int __init efi_set_mapping_permissions(struct mm_struct *mm,
|
|
efi_memory_desc_t *md,
|
|
bool has_bti)
|
|
{
|
|
struct set_perm_data data = { md, has_bti };
|
|
|
|
BUG_ON(md->type != EFI_RUNTIME_SERVICES_CODE &&
|
|
md->type != EFI_RUNTIME_SERVICES_DATA);
|
|
|
|
if (region_is_misaligned(md))
|
|
return 0;
|
|
|
|
/*
|
|
* Calling apply_to_page_range() is only safe on regions that are
|
|
* guaranteed to be mapped down to pages. Since we are only called
|
|
* for regions that have been mapped using efi_create_mapping() above
|
|
* (and this is checked by the generic Memory Attributes table parsing
|
|
* routines), there is no need to check that again here.
|
|
*/
|
|
return apply_to_page_range(mm, md->virt_addr,
|
|
md->num_pages << EFI_PAGE_SHIFT,
|
|
set_permissions, &data);
|
|
}
|
|
|
|
/*
|
|
* UpdateCapsule() depends on the system being shutdown via
|
|
* ResetSystem().
|
|
*/
|
|
bool efi_poweroff_required(void)
|
|
{
|
|
return efi_enabled(EFI_RUNTIME_SERVICES);
|
|
}
|
|
|
|
asmlinkage efi_status_t efi_handle_corrupted_x18(efi_status_t s, const char *f)
|
|
{
|
|
pr_err_ratelimited(FW_BUG "register x18 corrupted by EFI %s\n", f);
|
|
return s;
|
|
}
|
|
|
|
static DEFINE_RAW_SPINLOCK(efi_rt_lock);
|
|
|
|
void arch_efi_call_virt_setup(void)
|
|
{
|
|
efi_virtmap_load();
|
|
raw_spin_lock(&efi_rt_lock);
|
|
__efi_fpsimd_begin();
|
|
}
|
|
|
|
void arch_efi_call_virt_teardown(void)
|
|
{
|
|
__efi_fpsimd_end();
|
|
raw_spin_unlock(&efi_rt_lock);
|
|
efi_virtmap_unload();
|
|
}
|
|
|
|
asmlinkage u64 *efi_rt_stack_top __ro_after_init;
|
|
|
|
asmlinkage efi_status_t __efi_rt_asm_recover(void);
|
|
|
|
bool efi_runtime_fixup_exception(struct pt_regs *regs, const char *msg)
|
|
{
|
|
/* Check whether the exception occurred while running the firmware */
|
|
if (!current_in_efi() || regs->pc >= TASK_SIZE_64)
|
|
return false;
|
|
|
|
pr_err(FW_BUG "Unable to handle %s in EFI runtime service\n", msg);
|
|
add_taint(TAINT_FIRMWARE_WORKAROUND, LOCKDEP_STILL_OK);
|
|
clear_bit(EFI_RUNTIME_SERVICES, &efi.flags);
|
|
|
|
regs->regs[0] = EFI_ABORTED;
|
|
regs->regs[30] = efi_rt_stack_top[-1];
|
|
regs->pc = (u64)__efi_rt_asm_recover;
|
|
|
|
if (IS_ENABLED(CONFIG_SHADOW_CALL_STACK))
|
|
regs->regs[18] = efi_rt_stack_top[-2];
|
|
|
|
return true;
|
|
}
|
|
|
|
/* EFI requires 8 KiB of stack space for runtime services */
|
|
static_assert(THREAD_SIZE >= SZ_8K);
|
|
|
|
static int __init arm64_efi_rt_init(void)
|
|
{
|
|
void *p;
|
|
|
|
if (!efi_enabled(EFI_RUNTIME_SERVICES))
|
|
return 0;
|
|
|
|
p = __vmalloc_node(THREAD_SIZE, THREAD_ALIGN, GFP_KERNEL,
|
|
NUMA_NO_NODE, &&l);
|
|
l: if (!p) {
|
|
pr_warn("Failed to allocate EFI runtime stack\n");
|
|
clear_bit(EFI_RUNTIME_SERVICES, &efi.flags);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
kmemleak_not_leak(p);
|
|
efi_rt_stack_top = p + THREAD_SIZE;
|
|
return 0;
|
|
}
|
|
core_initcall(arm64_efi_rt_init);
|