Re: [PATCH v5 8/9] RISC-V: Add EFI runtime services

From: Ard Biesheuvel
Date: Fri Aug 14 2020 - 04:20:35 EST


On Thu, 13 Aug 2020 at 01:48, Atish Patra <atish.patra@xxxxxxx> wrote:
>
> This patch adds EFI runtime service support for RISC-V.
>
> Signed-off-by: Atish Patra <atish.patra@xxxxxxx>

Acked-by: Ard Biesheuvel <ardb@xxxxxxxxxx>

> ---
> arch/riscv/Kconfig | 2 +
> arch/riscv/include/asm/efi.h | 20 ++++
> arch/riscv/include/asm/mmu.h | 2 +
> arch/riscv/include/asm/pgtable.h | 4 +
> arch/riscv/kernel/Makefile | 1 +
> arch/riscv/kernel/efi.c | 105 +++++++++++++++++
> arch/riscv/kernel/setup.c | 7 +-
> arch/riscv/mm/init.c | 2 +-
> drivers/firmware/efi/Makefile | 2 +
> drivers/firmware/efi/libstub/efi-stub.c | 11 +-
> drivers/firmware/efi/riscv-runtime.c | 143 ++++++++++++++++++++++++
> 11 files changed, 295 insertions(+), 4 deletions(-)
> create mode 100644 arch/riscv/kernel/efi.c
> create mode 100644 drivers/firmware/efi/riscv-runtime.c
>
> diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig
> index e11907cc7a43..b2164109483d 100644
> --- a/arch/riscv/Kconfig
> +++ b/arch/riscv/Kconfig
> @@ -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
> diff --git a/arch/riscv/include/asm/efi.h b/arch/riscv/include/asm/efi.h
> index 86da231909bb..93c305a638f4 100644
> --- a/arch/riscv/include/asm/efi.h
> +++ b/arch/riscv/include/asm/efi.h
> @@ -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 dram_base)
> {
> @@ -33,4 +50,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 */
> diff --git a/arch/riscv/include/asm/mmu.h b/arch/riscv/include/asm/mmu.h
> index 967eacb01ab5..dabcf2cfb3dc 100644
> --- a/arch/riscv/include/asm/mmu.h
> +++ b/arch/riscv/include/asm/mmu.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 */
> diff --git a/arch/riscv/include/asm/pgtable.h b/arch/riscv/include/asm/pgtable.h
> index 815f8c959dd4..183f1f4b2ae6 100644
> --- a/arch/riscv/include/asm/pgtable.h
> +++ b/arch/riscv/include/asm/pgtable.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)
>
> diff --git a/arch/riscv/kernel/Makefile b/arch/riscv/kernel/Makefile
> index eabec4dce50b..0b48059cc9da 100644
> --- a/arch/riscv/kernel/Makefile
> +++ b/arch/riscv/kernel/Makefile
> @@ -36,6 +36,7 @@ OBJCOPYFLAGS := --prefix-symbols=__efistub_
> $(obj)/%.stub.o: $(obj)/%.o FORCE
> $(call if_changed,objcopy)
>
> +obj-$(CONFIG_EFI) += efi.o
> obj-$(CONFIG_FPU) += fpu.o
> obj-$(CONFIG_SMP) += smpboot.o
> obj-$(CONFIG_SMP) += smp.o
> diff --git a/arch/riscv/kernel/efi.c b/arch/riscv/kernel/efi.c
> new file mode 100644
> index 000000000000..d7a723b446c3
> --- /dev/null
> +++ b/arch/riscv/kernel/efi.c
> @@ -0,0 +1,105 @@
> +// 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;
> +
> + if (WARN_ONCE(!PAGE_ALIGNED(md->phys_addr),
> + "UEFI Runtime regions are not aligned to page size -- buggy firmware?"))
> + /*
> + * If the region is not aligned to the page size of the OS, we
> + * can not use strict permissions, since that would also affect
> + * the mapping attributes of the adjacent regions.
> + */
> + return PAGE_EXEC;
> +
> + /* 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);
> +}
> diff --git a/arch/riscv/kernel/setup.c b/arch/riscv/kernel/setup.c
> index c71788e6aff4..7f2a0d6dca7d 100644
> --- a/arch/riscv/kernel/setup.c
> +++ b/arch/riscv/kernel/setup.c
> @@ -17,6 +17,7 @@
> #include <linux/sched/task.h>
> #include <linux/swiotlb.h>
> #include <linux/smp.h>
> +#include <linux/efi.h>
>
> #include <asm/clint.h>
> #include <asm/cpu_ops.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)
> diff --git a/arch/riscv/mm/init.c b/arch/riscv/mm/init.c
> index d238cdc501ee..9fb2fe2f4a3e 100644
> --- a/arch/riscv/mm/init.c
> +++ b/arch/riscv/mm/init.c
> @@ -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)
> {
> diff --git a/drivers/firmware/efi/Makefile b/drivers/firmware/efi/Makefile
> index 61fd1e8b26fb..4d628081bb2f 100644
> --- a/drivers/firmware/efi/Makefile
> +++ b/drivers/firmware/efi/Makefile
> @@ -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
> diff --git a/drivers/firmware/efi/libstub/efi-stub.c b/drivers/firmware/efi/libstub/efi-stub.c
> index a5a405d8ab44..5c26725d8fd0 100644
> --- a/drivers/firmware/efi/libstub/efi-stub.c
> +++ b/drivers/firmware/efi/libstub/efi-stub.c
> @@ -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
> diff --git a/drivers/firmware/efi/riscv-runtime.c b/drivers/firmware/efi/riscv-runtime.c
> new file mode 100644
> index 000000000000..d28e715d2bcc
> --- /dev/null
> +++ b/drivers/firmware/efi/riscv-runtime.c
> @@ -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();
> +}
> --
> 2.24.0
>