RISC-V: Add EFI runtime services
authorAtish Patra <atish.patra@wdc.com>
Thu, 17 Sep 2020 22:37:15 +0000 (15:37 -0700)
committerPalmer Dabbelt <palmerdabbelt@google.com>
Fri, 2 Oct 2020 21:31:28 +0000 (14:31 -0700)
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>
arch/riscv/Kconfig
arch/riscv/include/asm/efi.h
arch/riscv/include/asm/mmu.h
arch/riscv/include/asm/pgtable.h
arch/riscv/kernel/Makefile
arch/riscv/kernel/efi.c [new file with mode: 0644]
arch/riscv/kernel/setup.c
arch/riscv/mm/init.c
drivers/firmware/efi/Makefile
drivers/firmware/efi/libstub/efi-stub.c
drivers/firmware/efi/riscv-runtime.c [new file with mode: 0644]

index 6ef2394be8578dd34f6c3ad41d5944a0dc4dcab7..c8e57d77c144cb99fb86d6433c120d24c25a9ce8 100644 (file)
@@ -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
index bf6a40e20a8e0a5761ef2eb5c6c1002798227022..7542282f1141c200f56d70b3ae01dfa49eb7b155 100644 (file)
@@ -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 */
index 967eacb01ab578f1b5efe3358c306b33c8bd5508..dabcf2cfb3dc762011662c31c3b36d61db8dc859 100644 (file)
@@ -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 */
index 815f8c959dd4f7b709875c5715440772bcfc6328..183f1f4b2ae66dc4a4d4348e2e962eeb158b2f6f 100644 (file)
 
 #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)
 
index dc93710f0b2f10ba478a96cf97cb5e082babef50..fa896c5f7ccb72f46c42d7aea0c9281e915ee436 100644 (file)
@@ -55,4 +55,6 @@ obj-$(CONFIG_KGDB)            += kgdb.o
 
 obj-$(CONFIG_JUMP_LABEL)       += jump_label.o
 
+obj-$(CONFIG_EFI)              += efi.o
+
 clean:
diff --git a/arch/riscv/kernel/efi.c b/arch/riscv/kernel/efi.c
new file mode 100644 (file)
index 0000000..0241592
--- /dev/null
@@ -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);
+}
index 41ef96d0d97a62ac6026275486bde106e565a2cf..4c96ac198e14be60c39eb4841f8ae7be811aa6dc 100644 (file)
@@ -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>
 #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)
index 63acc8185bfa7567a76723230ec386b5f54fad8e..c888c4470b3482145446afa611f26d7fa8bca69f 100644 (file)
@@ -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)
 {
index 61fd1e8b26fb736734da76810e3a145e61af73ff..4d628081bb2ff8a1a4f41e5f1ae88997ae96e575 100644 (file)
@@ -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
index 311a16802dd6ea25b9ce63478f8ad6a98e9cb8f8..914a343c7785c9a3f2c7a86efd23dc8c28982a4e 100644 (file)
 
 /*
  * 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
  * 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 (file)
index 0000000..d28e715
--- /dev/null
@@ -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();
+}