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

Ard Biesheuvel ardb at kernel.org
Fri Aug 14 04:20:20 EDT 2020


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

Acked-by: Ard Biesheuvel <ardb at kernel.org>

> ---
>  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
>



More information about the linux-riscv mailing list