lkml.org 
[lkml]   [2020]   [Aug]   [12]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Subject[PATCH v5 8/9] RISC-V: Add EFI runtime services
    Date
    This patch adds EFI runtime service support for RISC-V.

    Signed-off-by: Atish Patra <atish.patra@wdc.com>
    ---
    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
    \
     
     \ /
      Last update: 2020-08-13 01:49    [W:3.154 / U:0.060 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site