RISC-V: Add EFI runtime services
This patch adds EFI runtime service support for RISC-V. Signed-off-by: Atish Patra <atish.patra@wdc.com> [ardb: - Remove the page check] Signed-off-by: Ard Biesheuvel <ardb@kernel.org> Acked-by: Ard Biesheuvel <ardb@kernel.org> Signed-off-by: Palmer Dabbelt <palmerdabbelt@google.com>
This commit is contained in:
parent
d7071743db
commit
b91540d52a
@ -412,7 +412,9 @@ config EFI
|
||||
select EFI_PARAMS_FROM_FDT
|
||||
select EFI_STUB
|
||||
select EFI_GENERIC_STUB
|
||||
select EFI_RUNTIME_WRAPPERS
|
||||
select RISCV_ISA_C
|
||||
depends on MMU
|
||||
default y
|
||||
help
|
||||
This option provides support for runtime services provided
|
||||
|
@ -5,11 +5,28 @@
|
||||
#ifndef _ASM_EFI_H
|
||||
#define _ASM_EFI_H
|
||||
|
||||
#include <asm/csr.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/mmu_context.h>
|
||||
#include <asm/ptrace.h>
|
||||
#include <asm/tlbflush.h>
|
||||
|
||||
#ifdef CONFIG_EFI
|
||||
extern void efi_init(void);
|
||||
#else
|
||||
#define efi_init()
|
||||
#endif
|
||||
|
||||
int efi_create_mapping(struct mm_struct *mm, efi_memory_desc_t *md);
|
||||
int efi_set_mapping_permissions(struct mm_struct *mm, efi_memory_desc_t *md);
|
||||
|
||||
#define arch_efi_call_virt_setup() efi_virtmap_load()
|
||||
#define arch_efi_call_virt_teardown() efi_virtmap_unload()
|
||||
|
||||
#define arch_efi_call_virt(p, f, args...) p->f(args)
|
||||
|
||||
#define ARCH_EFI_IRQ_FLAGS_MASK (SR_IE | SR_SPIE)
|
||||
|
||||
/* on RISC-V, the FDT may be located anywhere in system RAM */
|
||||
static inline unsigned long efi_get_max_fdt_addr(unsigned long image_addr)
|
||||
{
|
||||
@ -32,4 +49,7 @@ static inline void efifb_setup_from_dmi(struct screen_info *si, const char *opt)
|
||||
{
|
||||
}
|
||||
|
||||
void efi_virtmap_load(void);
|
||||
void efi_virtmap_unload(void);
|
||||
|
||||
#endif /* _ASM_EFI_H */
|
||||
|
@ -20,6 +20,8 @@ typedef struct {
|
||||
#endif
|
||||
} mm_context_t;
|
||||
|
||||
void __init create_pgd_mapping(pgd_t *pgdp, uintptr_t va, phys_addr_t pa,
|
||||
phys_addr_t sz, pgprot_t prot);
|
||||
#endif /* __ASSEMBLY__ */
|
||||
|
||||
#endif /* _ASM_RISCV_MMU_H */
|
||||
|
@ -100,6 +100,10 @@
|
||||
|
||||
#define PAGE_KERNEL __pgprot(_PAGE_KERNEL)
|
||||
#define PAGE_KERNEL_EXEC __pgprot(_PAGE_KERNEL | _PAGE_EXEC)
|
||||
#define PAGE_KERNEL_READ __pgprot(_PAGE_KERNEL & ~_PAGE_WRITE)
|
||||
#define PAGE_KERNEL_EXEC __pgprot(_PAGE_KERNEL | _PAGE_EXEC)
|
||||
#define PAGE_KERNEL_READ_EXEC __pgprot((_PAGE_KERNEL & ~_PAGE_WRITE) \
|
||||
| _PAGE_EXEC)
|
||||
|
||||
#define PAGE_TABLE __pgprot(_PAGE_TABLE)
|
||||
|
||||
|
@ -55,4 +55,6 @@ obj-$(CONFIG_KGDB) += kgdb.o
|
||||
|
||||
obj-$(CONFIG_JUMP_LABEL) += jump_label.o
|
||||
|
||||
obj-$(CONFIG_EFI) += efi.o
|
||||
|
||||
clean:
|
||||
|
96
arch/riscv/kernel/efi.c
Normal file
96
arch/riscv/kernel/efi.c
Normal file
@ -0,0 +1,96 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) 2020 Western Digital Corporation or its affiliates.
|
||||
* Adapted from arch/arm64/kernel/efi.c
|
||||
*/
|
||||
|
||||
#include <linux/efi.h>
|
||||
#include <linux/init.h>
|
||||
|
||||
#include <asm/efi.h>
|
||||
#include <asm/pgtable.h>
|
||||
#include <asm/pgtable-bits.h>
|
||||
|
||||
/*
|
||||
* 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 pgprot_t efimem_to_pgprot_map(efi_memory_desc_t *md)
|
||||
{
|
||||
u64 attr = md->attribute;
|
||||
u32 type = md->type;
|
||||
|
||||
if (type == EFI_MEMORY_MAPPED_IO)
|
||||
return PAGE_KERNEL;
|
||||
|
||||
/* R-- */
|
||||
if ((attr & (EFI_MEMORY_XP | EFI_MEMORY_RO)) ==
|
||||
(EFI_MEMORY_XP | EFI_MEMORY_RO))
|
||||
return PAGE_KERNEL_READ;
|
||||
|
||||
/* R-X */
|
||||
if (attr & EFI_MEMORY_RO)
|
||||
return PAGE_KERNEL_READ_EXEC;
|
||||
|
||||
/* RW- */
|
||||
if (((attr & (EFI_MEMORY_RP | EFI_MEMORY_WP | EFI_MEMORY_XP)) ==
|
||||
EFI_MEMORY_XP) ||
|
||||
type != EFI_RUNTIME_SERVICES_CODE)
|
||||
return PAGE_KERNEL;
|
||||
|
||||
/* RWX */
|
||||
return PAGE_KERNEL_EXEC;
|
||||
}
|
||||
|
||||
int __init efi_create_mapping(struct mm_struct *mm, efi_memory_desc_t *md)
|
||||
{
|
||||
pgprot_t prot = __pgprot(pgprot_val(efimem_to_pgprot_map(md)) &
|
||||
~(_PAGE_GLOBAL));
|
||||
int i;
|
||||
|
||||
/* RISC-V maps one page at a time */
|
||||
for (i = 0; i < md->num_pages; i++)
|
||||
create_pgd_mapping(mm->pgd, md->virt_addr + i * PAGE_SIZE,
|
||||
md->phys_addr + i * PAGE_SIZE,
|
||||
PAGE_SIZE, prot);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init set_permissions(pte_t *ptep, unsigned long addr, void *data)
|
||||
{
|
||||
efi_memory_desc_t *md = data;
|
||||
pte_t pte = READ_ONCE(*ptep);
|
||||
unsigned long val;
|
||||
|
||||
if (md->attribute & EFI_MEMORY_RO) {
|
||||
val = pte_val(pte) & ~_PAGE_WRITE;
|
||||
val = pte_val(pte) | _PAGE_READ;
|
||||
pte = __pte(val);
|
||||
}
|
||||
if (md->attribute & EFI_MEMORY_XP) {
|
||||
val = pte_val(pte) & ~_PAGE_EXEC;
|
||||
pte = __pte(val);
|
||||
}
|
||||
set_pte(ptep, pte);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __init efi_set_mapping_permissions(struct mm_struct *mm,
|
||||
efi_memory_desc_t *md)
|
||||
{
|
||||
BUG_ON(md->type != EFI_RUNTIME_SERVICES_CODE &&
|
||||
md->type != EFI_RUNTIME_SERVICES_DATA);
|
||||
|
||||
/*
|
||||
* 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, md);
|
||||
}
|
@ -17,6 +17,7 @@
|
||||
#include <linux/sched/task.h>
|
||||
#include <linux/swiotlb.h>
|
||||
#include <linux/smp.h>
|
||||
#include <linux/efi.h>
|
||||
|
||||
#include <asm/cpu_ops.h>
|
||||
#include <asm/early_ioremap.h>
|
||||
@ -26,11 +27,12 @@
|
||||
#include <asm/tlbflush.h>
|
||||
#include <asm/thread_info.h>
|
||||
#include <asm/kasan.h>
|
||||
#include <asm/efi.h>
|
||||
|
||||
#include "head.h"
|
||||
|
||||
#ifdef CONFIG_DUMMY_CONSOLE
|
||||
struct screen_info screen_info = {
|
||||
#if defined(CONFIG_DUMMY_CONSOLE) || defined(CONFIG_EFI)
|
||||
struct screen_info screen_info __section(.data) = {
|
||||
.orig_video_lines = 30,
|
||||
.orig_video_cols = 80,
|
||||
.orig_video_mode = 0,
|
||||
@ -75,6 +77,7 @@ void __init setup_arch(char **cmdline_p)
|
||||
early_ioremap_setup();
|
||||
parse_early_param();
|
||||
|
||||
efi_init();
|
||||
setup_bootmem();
|
||||
paging_init();
|
||||
#if IS_ENABLED(CONFIG_BUILTIN_DTB)
|
||||
|
@ -390,7 +390,7 @@ static void __init create_pmd_mapping(pmd_t *pmdp,
|
||||
#define fixmap_pgd_next fixmap_pte
|
||||
#endif
|
||||
|
||||
static void __init create_pgd_mapping(pgd_t *pgdp,
|
||||
void __init create_pgd_mapping(pgd_t *pgdp,
|
||||
uintptr_t va, phys_addr_t pa,
|
||||
phys_addr_t sz, pgprot_t prot)
|
||||
{
|
||||
|
@ -35,6 +35,8 @@ fake_map-$(CONFIG_X86) += x86_fake_mem.o
|
||||
arm-obj-$(CONFIG_EFI) := efi-init.o arm-runtime.o
|
||||
obj-$(CONFIG_ARM) += $(arm-obj-y)
|
||||
obj-$(CONFIG_ARM64) += $(arm-obj-y)
|
||||
riscv-obj-$(CONFIG_EFI) := efi-init.o riscv-runtime.o
|
||||
obj-$(CONFIG_RISCV) += $(riscv-obj-y)
|
||||
obj-$(CONFIG_EFI_CAPSULE_LOADER) += capsule-loader.o
|
||||
obj-$(CONFIG_EFI_EARLYCON) += earlycon.o
|
||||
obj-$(CONFIG_UEFI_CPER_ARM) += cper-arm.o
|
||||
|
@ -17,7 +17,10 @@
|
||||
|
||||
/*
|
||||
* This is the base address at which to start allocating virtual memory ranges
|
||||
* for UEFI Runtime Services. This is in the low TTBR0 range so that we can use
|
||||
* for UEFI Runtime Services.
|
||||
*
|
||||
* For ARM/ARM64:
|
||||
* This is in the low TTBR0 range so that we can use
|
||||
* any allocation we choose, and eliminate the risk of a conflict after kexec.
|
||||
* The value chosen is the largest non-zero power of 2 suitable for this purpose
|
||||
* both on 32-bit and 64-bit ARM CPUs, to maximize the likelihood that it can
|
||||
@ -25,6 +28,12 @@
|
||||
* Since 32-bit ARM could potentially execute with a 1G/3G user/kernel split,
|
||||
* map everything below 1 GB. (512 MB is a reasonable upper bound for the
|
||||
* entire footprint of the UEFI runtime services memory regions)
|
||||
*
|
||||
* For RISC-V:
|
||||
* There is no specific reason for which, this address (512MB) can't be used
|
||||
* EFI runtime virtual address for RISC-V. It also helps to use EFI runtime
|
||||
* services on both RV32/RV64. Keep the same runtime virtual address for RISC-V
|
||||
* as well to minimize the code churn.
|
||||
*/
|
||||
#define EFI_RT_VIRTUAL_BASE SZ_512M
|
||||
#define EFI_RT_VIRTUAL_SIZE SZ_512M
|
||||
|
143
drivers/firmware/efi/riscv-runtime.c
Normal file
143
drivers/firmware/efi/riscv-runtime.c
Normal file
@ -0,0 +1,143 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Extensible Firmware Interface
|
||||
*
|
||||
* Copyright (C) 2020 Western Digital Corporation or its affiliates.
|
||||
*
|
||||
* Based on Extensible Firmware Interface Specification version 2.4
|
||||
* Adapted from drivers/firmware/efi/arm-runtime.c
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/efi.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/memblock.h>
|
||||
#include <linux/mm_types.h>
|
||||
#include <linux/preempt.h>
|
||||
#include <linux/rbtree.h>
|
||||
#include <linux/rwsem.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/pgtable.h>
|
||||
|
||||
#include <asm/cacheflush.h>
|
||||
#include <asm/efi.h>
|
||||
#include <asm/mmu.h>
|
||||
#include <asm/pgalloc.h>
|
||||
|
||||
static bool __init efi_virtmap_init(void)
|
||||
{
|
||||
efi_memory_desc_t *md;
|
||||
|
||||
efi_mm.pgd = pgd_alloc(&efi_mm);
|
||||
mm_init_cpumask(&efi_mm);
|
||||
init_new_context(NULL, &efi_mm);
|
||||
|
||||
for_each_efi_memory_desc(md) {
|
||||
phys_addr_t phys = md->phys_addr;
|
||||
int ret;
|
||||
|
||||
if (!(md->attribute & EFI_MEMORY_RUNTIME))
|
||||
continue;
|
||||
if (md->virt_addr == 0)
|
||||
return false;
|
||||
|
||||
ret = efi_create_mapping(&efi_mm, md);
|
||||
if (ret) {
|
||||
pr_warn(" EFI remap %pa: failed to create mapping (%d)\n",
|
||||
&phys, ret);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (efi_memattr_apply_permissions(&efi_mm, efi_set_mapping_permissions))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Enable the UEFI Runtime Services if all prerequisites are in place, i.e.,
|
||||
* non-early mapping of the UEFI system table and virtual mappings for all
|
||||
* EFI_MEMORY_RUNTIME regions.
|
||||
*/
|
||||
static int __init riscv_enable_runtime_services(void)
|
||||
{
|
||||
u64 mapsize;
|
||||
|
||||
if (!efi_enabled(EFI_BOOT)) {
|
||||
pr_info("EFI services will not be available.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
efi_memmap_unmap();
|
||||
|
||||
mapsize = efi.memmap.desc_size * efi.memmap.nr_map;
|
||||
|
||||
if (efi_memmap_init_late(efi.memmap.phys_map, mapsize)) {
|
||||
pr_err("Failed to remap EFI memory map\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (efi_soft_reserve_enabled()) {
|
||||
efi_memory_desc_t *md;
|
||||
|
||||
for_each_efi_memory_desc(md) {
|
||||
int md_size = md->num_pages << EFI_PAGE_SHIFT;
|
||||
struct resource *res;
|
||||
|
||||
if (!(md->attribute & EFI_MEMORY_SP))
|
||||
continue;
|
||||
|
||||
res = kzalloc(sizeof(*res), GFP_KERNEL);
|
||||
if (WARN_ON(!res))
|
||||
break;
|
||||
|
||||
res->start = md->phys_addr;
|
||||
res->end = md->phys_addr + md_size - 1;
|
||||
res->name = "Soft Reserved";
|
||||
res->flags = IORESOURCE_MEM;
|
||||
res->desc = IORES_DESC_SOFT_RESERVED;
|
||||
|
||||
insert_resource(&iomem_resource, res);
|
||||
}
|
||||
}
|
||||
|
||||
if (efi_runtime_disabled()) {
|
||||
pr_info("EFI runtime services will be disabled.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (efi_enabled(EFI_RUNTIME_SERVICES)) {
|
||||
pr_info("EFI runtime services access via paravirt.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
pr_info("Remapping and enabling EFI services.\n");
|
||||
|
||||
if (!efi_virtmap_init()) {
|
||||
pr_err("UEFI virtual mapping missing or invalid -- runtime services will not be available\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* Set up runtime services function pointers */
|
||||
efi_native_runtime_setup();
|
||||
set_bit(EFI_RUNTIME_SERVICES, &efi.flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
early_initcall(riscv_enable_runtime_services);
|
||||
|
||||
void efi_virtmap_load(void)
|
||||
{
|
||||
preempt_disable();
|
||||
switch_mm(current->active_mm, &efi_mm, NULL);
|
||||
}
|
||||
|
||||
void efi_virtmap_unload(void)
|
||||
{
|
||||
switch_mm(&efi_mm, current->active_mm, NULL);
|
||||
preempt_enable();
|
||||
}
|
Loading…
Reference in New Issue
Block a user