summaryrefslogtreecommitdiffstats
path: root/drivers/firmware/efi
diff options
context:
space:
mode:
authorArd Biesheuvel <ardb@kernel.org>2022-11-18 09:13:57 +0100
committerArd Biesheuvel <ardb@kernel.org>2022-11-18 09:13:57 +0100
commit977122898ea5e3d568014ec9fe089cfba7c73e76 (patch)
tree19a43e45225aa0a20d72e841cc5c37e45f5a76af /drivers/firmware/efi
parent094226ad94f471a9f19e8f8e7140a09c2625abaa (diff)
parentc51e97e7f1295d3cf42682565506047f08dfc99b (diff)
downloadlinux-977122898ea5e3d568014ec9fe089cfba7c73e76.tar.bz2
Merge tag 'efi-zboot-direct-for-v6.2' into efi/next
Diffstat (limited to 'drivers/firmware/efi')
-rw-r--r--drivers/firmware/efi/efi-init.c21
-rw-r--r--drivers/firmware/efi/efi.c5
-rw-r--r--drivers/firmware/efi/libstub/Makefile34
-rw-r--r--drivers/firmware/efi/libstub/Makefile.zboot22
-rw-r--r--drivers/firmware/efi/libstub/arm32-stub.c37
-rw-r--r--drivers/firmware/efi/libstub/arm64-entry.S67
-rw-r--r--drivers/firmware/efi/libstub/arm64-stub.c64
-rw-r--r--drivers/firmware/efi/libstub/arm64.c76
-rw-r--r--drivers/firmware/efi/libstub/efi-stub-entry.c65
-rw-r--r--drivers/firmware/efi/libstub/efi-stub-helper.c143
-rw-r--r--drivers/firmware/efi/libstub/efi-stub.c140
-rw-r--r--drivers/firmware/efi/libstub/efistub.h15
-rw-r--r--drivers/firmware/efi/libstub/file.c18
-rw-r--r--drivers/firmware/efi/libstub/intrinsics.c18
-rw-r--r--drivers/firmware/efi/libstub/loongarch-stub.c89
-rw-r--r--drivers/firmware/efi/libstub/loongarch.c80
-rw-r--r--drivers/firmware/efi/libstub/printk.c154
-rw-r--r--drivers/firmware/efi/libstub/riscv-stub.c96
-rw-r--r--drivers/firmware/efi/libstub/riscv.c98
-rw-r--r--drivers/firmware/efi/libstub/screen_info.c56
-rw-r--r--drivers/firmware/efi/libstub/string.c95
-rw-r--r--drivers/firmware/efi/libstub/zboot-header.S2
-rw-r--r--drivers/firmware/efi/libstub/zboot.c307
23 files changed, 925 insertions, 777 deletions
diff --git a/drivers/firmware/efi/efi-init.c b/drivers/firmware/efi/efi-init.c
index 2fd770b499a3..1639159493e3 100644
--- a/drivers/firmware/efi/efi-init.c
+++ b/drivers/firmware/efi/efi-init.c
@@ -22,6 +22,8 @@
#include <asm/efi.h>
+unsigned long __initdata screen_info_table = EFI_INVALID_TABLE_ADDR;
+
static int __init is_memory(efi_memory_desc_t *md)
{
if (md->attribute & (EFI_MEMORY_WB|EFI_MEMORY_WT|EFI_MEMORY_WC))
@@ -55,9 +57,22 @@ extern __weak const efi_config_table_type_t efi_arch_tables[];
static void __init init_screen_info(void)
{
- if (screen_info.orig_video_isVGA == VIDEO_TYPE_EFI &&
- memblock_is_map_memory(screen_info.lfb_base))
- memblock_mark_nomap(screen_info.lfb_base, screen_info.lfb_size);
+ struct screen_info *si;
+
+ if (screen_info_table != EFI_INVALID_TABLE_ADDR) {
+ si = early_memremap(screen_info_table, sizeof(*si));
+ if (!si) {
+ pr_err("Could not map screen_info config table\n");
+ return;
+ }
+ screen_info = *si;
+ memset(si, 0, sizeof(*si));
+ early_memunmap(si, sizeof(*si));
+
+ if (memblock_is_map_memory(screen_info.lfb_base))
+ memblock_mark_nomap(screen_info.lfb_base,
+ screen_info.lfb_size);
+ }
}
static int __init uefi_init(u64 efi_system_table)
diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c
index a46df5d1d094..951a42d27cf4 100644
--- a/drivers/firmware/efi/efi.c
+++ b/drivers/firmware/efi/efi.c
@@ -58,6 +58,8 @@ static unsigned long __initdata mem_reserve = EFI_INVALID_TABLE_ADDR;
static unsigned long __initdata rt_prop = EFI_INVALID_TABLE_ADDR;
static unsigned long __initdata initrd = EFI_INVALID_TABLE_ADDR;
+extern unsigned long screen_info_table;
+
struct mm_struct efi_mm = {
.mm_mt = MTREE_INIT_EXT(mm_mt, MM_MT_FLAGS, efi_mm.mmap_lock),
.mm_users = ATOMIC_INIT(2),
@@ -547,6 +549,9 @@ static const efi_config_table_type_t common_tables[] __initconst = {
#ifdef CONFIG_EFI_COCO_SECRET
{LINUX_EFI_COCO_SECRET_AREA_GUID, &efi.coco_secret, "CocoSecret" },
#endif
+#ifdef CONFIG_EFI_GENERIC_STUB
+ {LINUX_EFI_SCREEN_INFO_TABLE_GUID, &screen_info_table },
+#endif
{},
};
diff --git a/drivers/firmware/efi/libstub/Makefile b/drivers/firmware/efi/libstub/Makefile
index ef5045a53ce0..e19dd1106fa1 100644
--- a/drivers/firmware/efi/libstub/Makefile
+++ b/drivers/firmware/efi/libstub/Makefile
@@ -5,6 +5,10 @@
# things like ftrace and stack-protector are likely to cause trouble if left
# enabled, even if doing so doesn't break the build.
#
+
+# non-x86 reuses KBUILD_CFLAGS, x86 does not
+cflags-y := $(KBUILD_CFLAGS)
+
cflags-$(CONFIG_X86_32) := -march=i386
cflags-$(CONFIG_X86_64) := -mcmodel=small
cflags-$(CONFIG_X86) += -m$(BITS) -D__KERNEL__ \
@@ -18,20 +22,19 @@ cflags-$(CONFIG_X86) += -m$(BITS) -D__KERNEL__ \
# arm64 uses the full KBUILD_CFLAGS so it's necessary to explicitly
# disable the stackleak plugin
-cflags-$(CONFIG_ARM64) := $(subst $(CC_FLAGS_FTRACE),,$(KBUILD_CFLAGS)) \
- -fpie $(DISABLE_STACKLEAK_PLUGIN) \
+cflags-$(CONFIG_ARM64) += -fpie $(DISABLE_STACKLEAK_PLUGIN) \
$(call cc-option,-mbranch-protection=none)
-cflags-$(CONFIG_ARM) := $(subst $(CC_FLAGS_FTRACE),,$(KBUILD_CFLAGS)) \
- -fno-builtin -fpic \
+cflags-$(CONFIG_ARM) += -DEFI_HAVE_STRLEN -DEFI_HAVE_STRNLEN \
+ -DEFI_HAVE_MEMCHR -DEFI_HAVE_STRRCHR \
+ -DEFI_HAVE_STRCMP -fno-builtin -fpic \
$(call cc-option,-mno-single-pic-base)
-cflags-$(CONFIG_RISCV) := $(subst $(CC_FLAGS_FTRACE),,$(KBUILD_CFLAGS)) \
- -fpic
-cflags-$(CONFIG_LOONGARCH) := $(subst $(CC_FLAGS_FTRACE),,$(KBUILD_CFLAGS)) \
- -fpie
+cflags-$(CONFIG_RISCV) += -fpic
+cflags-$(CONFIG_LOONGARCH) += -fpie
cflags-$(CONFIG_EFI_PARAMS_FROM_FDT) += -I$(srctree)/scripts/dtc/libfdt
-KBUILD_CFLAGS := $(cflags-y) -Os -DDISABLE_BRANCH_PROFILING \
+KBUILD_CFLAGS := $(subst $(CC_FLAGS_FTRACE),,$(cflags-y)) \
+ -Os -DDISABLE_BRANCH_PROFILING \
-include $(srctree)/include/linux/hidden.h \
-D__NO_FORTIFY \
-ffreestanding \
@@ -67,7 +70,7 @@ KCOV_INSTRUMENT := n
lib-y := efi-stub-helper.o gop.o secureboot.o tpm.o \
file.o mem.o random.o randomalloc.o pci.o \
skip_spaces.o lib-cmdline.o lib-ctype.o \
- alignedmem.o relocate.o vsprintf.o
+ alignedmem.o relocate.o printk.o vsprintf.o
# include the stub's libfdt dependencies from lib/ when needed
libfdt-deps := fdt_rw.c fdt_ro.c fdt_wip.c fdt.c \
@@ -79,13 +82,14 @@ lib-$(CONFIG_EFI_PARAMS_FROM_FDT) += fdt.o \
$(obj)/lib-%.o: $(srctree)/lib/%.c FORCE
$(call if_changed_rule,cc_o_c)
-lib-$(CONFIG_EFI_GENERIC_STUB) += efi-stub.o string.o intrinsics.o systable.o
+lib-$(CONFIG_EFI_GENERIC_STUB) += efi-stub.o string.o intrinsics.o systable.o \
+ screen_info.o efi-stub-entry.o
lib-$(CONFIG_ARM) += arm32-stub.o
-lib-$(CONFIG_ARM64) += arm64-stub.o smbios.o
+lib-$(CONFIG_ARM64) += arm64.o arm64-stub.o arm64-entry.o smbios.o
lib-$(CONFIG_X86) += x86-stub.o
-lib-$(CONFIG_RISCV) += riscv-stub.o
-lib-$(CONFIG_LOONGARCH) += loongarch-stub.o
+lib-$(CONFIG_RISCV) += riscv.o riscv-stub.o
+lib-$(CONFIG_LOONGARCH) += loongarch.o loongarch-stub.o
CFLAGS_arm32-stub.o := -DTEXT_OFFSET=$(TEXT_OFFSET)
@@ -136,7 +140,7 @@ STUBCOPY_RELOC-$(CONFIG_ARM) := R_ARM_ABS
#
STUBCOPY_FLAGS-$(CONFIG_ARM64) += --prefix-alloc-sections=.init \
--prefix-symbols=__efistub_
-STUBCOPY_RELOC-$(CONFIG_ARM64) := R_AARCH64_ABS
+STUBCOPY_RELOC-$(CONFIG_ARM64) := R_AARCH64_ABS64
# For RISC-V, we don't need anything special other than arm64. Keep all the
# symbols in .init section and make sure that no absolute symbols references
diff --git a/drivers/firmware/efi/libstub/Makefile.zboot b/drivers/firmware/efi/libstub/Makefile.zboot
index 3340b385a05b..43e9a4cab9f5 100644
--- a/drivers/firmware/efi/libstub/Makefile.zboot
+++ b/drivers/firmware/efi/libstub/Makefile.zboot
@@ -10,18 +10,17 @@ comp-type-$(CONFIG_KERNEL_LZO) := lzo
comp-type-$(CONFIG_KERNEL_XZ) := xzkern
comp-type-$(CONFIG_KERNEL_ZSTD) := zstd22
-# in GZIP, the appended le32 carrying the uncompressed size is part of the
-# format, but in other cases, we just append it at the end for convenience,
-# causing the original tools to complain when checking image integrity.
-# So disregard it when calculating the payload size in the zimage header.
-zboot-method-y := $(comp-type-y)_with_size
-zboot-size-len-y := 4
-
-zboot-method-$(CONFIG_KERNEL_GZIP) := gzip
-zboot-size-len-$(CONFIG_KERNEL_GZIP) := 0
+# Copy the SizeOfHeaders, SizeOfCode and SizeOfImage fields from the payload to
+# the end of the compressed image. Note that this presupposes a PE header
+# offset of 64 bytes, which is what arm64, RISC-V and LoongArch use.
+quiet_cmd_compwithsize = $(quiet_cmd_$(comp-type-y))
+ cmd_compwithsize = $(cmd_$(comp-type-y)) && ( \
+ dd status=none if=$< bs=4 count=1 skip=37 ; \
+ dd status=none if=$< bs=4 count=1 skip=23 ; \
+ dd status=none if=$< bs=4 count=1 skip=36 ) >> $@
$(obj)/vmlinuz: $(obj)/$(EFI_ZBOOT_PAYLOAD) FORCE
- $(call if_changed,$(zboot-method-y))
+ $(call if_changed,compwithsize)
OBJCOPYFLAGS_vmlinuz.o := -I binary -O $(EFI_ZBOOT_BFD_TARGET) \
--rename-section .data=.gzdata,load,alloc,readonly,contents
@@ -30,7 +29,6 @@ $(obj)/vmlinuz.o: $(obj)/vmlinuz FORCE
AFLAGS_zboot-header.o += -DMACHINE_TYPE=IMAGE_FILE_MACHINE_$(EFI_ZBOOT_MACH_TYPE) \
-DZBOOT_EFI_PATH="\"$(realpath $(obj)/vmlinuz.efi.elf)\"" \
- -DZBOOT_SIZE_LEN=$(zboot-size-len-y) \
-DCOMP_TYPE="\"$(comp-type-y)\""
$(obj)/zboot-header.o: $(srctree)/drivers/firmware/efi/libstub/zboot-header.S FORCE
@@ -46,4 +44,4 @@ OBJCOPYFLAGS_vmlinuz.efi := -O binary
$(obj)/vmlinuz.efi: $(obj)/vmlinuz.efi.elf FORCE
$(call if_changed,objcopy)
-targets += zboot-header.o vmlinuz vmlinuz.o vmlinuz.efi.elf vmlinuz.efi
+targets += zboot-header.o vmlinuz.o vmlinuz.efi.elf vmlinuz.efi
diff --git a/drivers/firmware/efi/libstub/arm32-stub.c b/drivers/firmware/efi/libstub/arm32-stub.c
index 0131e3aaa605..1073dd947516 100644
--- a/drivers/firmware/efi/libstub/arm32-stub.c
+++ b/drivers/firmware/efi/libstub/arm32-stub.c
@@ -76,43 +76,6 @@ void efi_handle_post_ebs_state(void)
&efi_entry_state->sctlr_after_ebs);
}
-static efi_guid_t screen_info_guid = LINUX_EFI_ARM_SCREEN_INFO_TABLE_GUID;
-
-struct screen_info *alloc_screen_info(void)
-{
- struct screen_info *si;
- efi_status_t status;
-
- /*
- * Unlike on arm64, where we can directly fill out the screen_info
- * structure from the stub, we need to allocate a buffer to hold
- * its contents while we hand over to the kernel proper from the
- * decompressor.
- */
- status = efi_bs_call(allocate_pool, EFI_RUNTIME_SERVICES_DATA,
- sizeof(*si), (void **)&si);
-
- if (status != EFI_SUCCESS)
- return NULL;
-
- status = efi_bs_call(install_configuration_table,
- &screen_info_guid, si);
- if (status == EFI_SUCCESS)
- return si;
-
- efi_bs_call(free_pool, si);
- return NULL;
-}
-
-void free_screen_info(struct screen_info *si)
-{
- if (!si)
- return;
-
- efi_bs_call(install_configuration_table, &screen_info_guid, NULL);
- efi_bs_call(free_pool, si);
-}
-
efi_status_t handle_kernel_image(unsigned long *image_addr,
unsigned long *image_size,
unsigned long *reserve_addr,
diff --git a/drivers/firmware/efi/libstub/arm64-entry.S b/drivers/firmware/efi/libstub/arm64-entry.S
new file mode 100644
index 000000000000..b5c17e89a4fc
--- /dev/null
+++ b/drivers/firmware/efi/libstub/arm64-entry.S
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * EFI entry point.
+ *
+ * Copyright (C) 2013, 2014 Red Hat, Inc.
+ * Author: Mark Salter <msalter@redhat.com>
+ */
+#include <linux/linkage.h>
+#include <asm/assembler.h>
+
+ /*
+ * The entrypoint of a arm64 bare metal image is at offset #0 of the
+ * image, so this is a reasonable default for primary_entry_offset.
+ * Only when the EFI stub is integrated into the core kernel, it is not
+ * guaranteed that the PE/COFF header has been copied to memory too, so
+ * in this case, primary_entry_offset should be overridden by the
+ * linker and point to primary_entry() directly.
+ */
+ .weak primary_entry_offset
+
+SYM_CODE_START(efi_enter_kernel)
+ /*
+ * efi_pe_entry() will have copied the kernel image if necessary and we
+ * end up here with device tree address in x1 and the kernel entry
+ * point stored in x0. Save those values in registers which are
+ * callee preserved.
+ */
+ ldr w2, =primary_entry_offset
+ add x19, x0, x2 // relocated Image entrypoint
+
+ mov x0, x1 // DTB address
+ mov x1, xzr
+ mov x2, xzr
+ mov x3, xzr
+
+ /*
+ * Clean the remainder of this routine to the PoC
+ * so that we can safely disable the MMU and caches.
+ */
+ adr x4, 1f
+ dc civac, x4
+ dsb sy
+
+ /* Turn off Dcache and MMU */
+ mrs x4, CurrentEL
+ cmp x4, #CurrentEL_EL2
+ mrs x4, sctlr_el1
+ b.ne 0f
+ mrs x4, sctlr_el2
+0: bic x4, x4, #SCTLR_ELx_M
+ bic x4, x4, #SCTLR_ELx_C
+ b.eq 1f
+ b 2f
+
+ .balign 32
+1: pre_disable_mmu_workaround
+ msr sctlr_el2, x4
+ isb
+ br x19 // jump to kernel entrypoint
+
+2: pre_disable_mmu_workaround
+ msr sctlr_el1, x4
+ isb
+ br x19 // jump to kernel entrypoint
+
+ .org 1b + 32
+SYM_CODE_END(efi_enter_kernel)
diff --git a/drivers/firmware/efi/libstub/arm64-stub.c b/drivers/firmware/efi/libstub/arm64-stub.c
index f9de5217ea65..c14028b625b4 100644
--- a/drivers/firmware/efi/libstub/arm64-stub.c
+++ b/drivers/firmware/efi/libstub/arm64-stub.c
@@ -11,52 +11,9 @@
#include <asm/efi.h>
#include <asm/memory.h>
#include <asm/sections.h>
-#include <asm/sysreg.h>
#include "efistub.h"
-static bool system_needs_vamap(void)
-{
- const u8 *type1_family = efi_get_smbios_string(1, family);
-
- /*
- * Ampere Altra machines crash in SetTime() if SetVirtualAddressMap()
- * has not been called prior.
- */
- if (!type1_family || strcmp(type1_family, "Altra"))
- return false;
-
- efi_warn("Working around broken SetVirtualAddressMap()\n");
- return true;
-}
-
-efi_status_t check_platform_features(void)
-{
- u64 tg;
-
- /*
- * If we have 48 bits of VA space for TTBR0 mappings, we can map the
- * UEFI runtime regions 1:1 and so calling SetVirtualAddressMap() is
- * unnecessary.
- */
- if (VA_BITS_MIN >= 48 && !system_needs_vamap())
- efi_novamap = true;
-
- /* UEFI mandates support for 4 KB granularity, no need to check */
- if (IS_ENABLED(CONFIG_ARM64_4K_PAGES))
- return EFI_SUCCESS;
-
- tg = (read_cpuid(ID_AA64MMFR0_EL1) >> ID_AA64MMFR0_EL1_TGRAN_SHIFT) & 0xf;
- if (tg < ID_AA64MMFR0_EL1_TGRAN_SUPPORTED_MIN || tg > ID_AA64MMFR0_EL1_TGRAN_SUPPORTED_MAX) {
- if (IS_ENABLED(CONFIG_ARM64_64K_PAGES))
- efi_err("This 64 KB granular kernel is not supported by your CPU\n");
- else
- efi_err("This 16 KB granular kernel is not supported by your CPU\n");
- return EFI_UNSUPPORTED;
- }
- return EFI_SUCCESS;
-}
-
/*
* Distro versions of GRUB may ignore the BSS allocation entirely (i.e., fail
* to provide space, and fail to zero it). Check for this condition by double
@@ -103,16 +60,7 @@ efi_status_t handle_kernel_image(unsigned long *image_addr,
efi_status_t status;
unsigned long kernel_size, kernel_memsize = 0;
u32 phys_seed = 0;
-
- /*
- * Although relocatable kernels can fix up the misalignment with
- * respect to MIN_KIMG_ALIGN, the resulting virtual text addresses are
- * subtly out of sync with those recorded in the vmlinux when kaslr is
- * disabled but the image required relocation anyway. Therefore retain
- * 2M alignment if KASLR was explicitly disabled, even if it was not
- * going to be activated to begin with.
- */
- u64 min_kimg_align = efi_nokaslr ? MIN_KIMG_ALIGN : EFI_KIMG_ALIGN;
+ u64 min_kimg_align = efi_get_kimg_min_align();
if (IS_ENABLED(CONFIG_RANDOMIZE_BASE)) {
efi_guid_t li_fixed_proto = LINUX_EFI_LOADED_IMAGE_FIXED_GUID;
@@ -171,7 +119,7 @@ efi_status_t handle_kernel_image(unsigned long *image_addr,
*/
*image_addr = (u64)_text;
*reserve_size = 0;
- return EFI_SUCCESS;
+ goto clean_image_to_poc;
}
status = efi_allocate_pages_aligned(*reserve_size, reserve_addr,
@@ -187,5 +135,13 @@ efi_status_t handle_kernel_image(unsigned long *image_addr,
*image_addr = *reserve_addr;
memcpy((void *)*image_addr, _text, kernel_size);
+clean_image_to_poc:
+ /*
+ * Clean the copied Image to the PoC, and ensure it is not shadowed by
+ * stale icache entries from before relocation.
+ */
+ dcache_clean_poc(*image_addr, *image_addr + kernel_size);
+ asm("ic ialluis");
+
return EFI_SUCCESS;
}
diff --git a/drivers/firmware/efi/libstub/arm64.c b/drivers/firmware/efi/libstub/arm64.c
new file mode 100644
index 000000000000..ff2d18c42ee7
--- /dev/null
+++ b/drivers/firmware/efi/libstub/arm64.c
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2013, 2014 Linaro Ltd; <roy.franz@linaro.org>
+ *
+ * This file implements the EFI boot stub for the arm64 kernel.
+ * Adapted from ARM version by Mark Salter <msalter@redhat.com>
+ */
+
+
+#include <linux/efi.h>
+#include <asm/efi.h>
+#include <asm/memory.h>
+#include <asm/sysreg.h>
+
+#include "efistub.h"
+
+static bool system_needs_vamap(void)
+{
+ const u8 *type1_family = efi_get_smbios_string(1, family);
+
+ /*
+ * Ampere Altra machines crash in SetTime() if SetVirtualAddressMap()
+ * has not been called prior.
+ */
+ if (!type1_family || strcmp(type1_family, "Altra"))
+ return false;
+
+ efi_warn("Working around broken SetVirtualAddressMap()\n");
+ return true;
+}
+
+efi_status_t check_platform_features(void)
+{
+ u64 tg;
+
+ /*
+ * If we have 48 bits of VA space for TTBR0 mappings, we can map the
+ * UEFI runtime regions 1:1 and so calling SetVirtualAddressMap() is
+ * unnecessary.
+ */
+ if (VA_BITS_MIN >= 48 && !system_needs_vamap())
+ efi_novamap = true;
+
+ /* UEFI mandates support for 4 KB granularity, no need to check */
+ if (IS_ENABLED(CONFIG_ARM64_4K_PAGES))
+ return EFI_SUCCESS;
+
+ tg = (read_cpuid(ID_AA64MMFR0_EL1) >> ID_AA64MMFR0_EL1_TGRAN_SHIFT) & 0xf;
+ if (tg < ID_AA64MMFR0_EL1_TGRAN_SUPPORTED_MIN || tg > ID_AA64MMFR0_EL1_TGRAN_SUPPORTED_MAX) {
+ if (IS_ENABLED(CONFIG_ARM64_64K_PAGES))
+ efi_err("This 64 KB granular kernel is not supported by your CPU\n");
+ else
+ efi_err("This 16 KB granular kernel is not supported by your CPU\n");
+ return EFI_UNSUPPORTED;
+ }
+ return EFI_SUCCESS;
+}
+
+void efi_cache_sync_image(unsigned long image_base,
+ unsigned long alloc_size,
+ unsigned long code_size)
+{
+ u32 ctr = read_cpuid_effective_cachetype();
+ u64 lsize = 4 << cpuid_feature_extract_unsigned_field(ctr,
+ CTR_EL0_DminLine_SHIFT);
+
+ do {
+ asm("dc civac, %0" :: "r"(image_base));
+ image_base += lsize;
+ alloc_size -= lsize;
+ } while (alloc_size >= lsize);
+
+ asm("ic ialluis");
+ dsb(ish);
+ isb();
+}
diff --git a/drivers/firmware/efi/libstub/efi-stub-entry.c b/drivers/firmware/efi/libstub/efi-stub-entry.c
new file mode 100644
index 000000000000..5245c4f031c0
--- /dev/null
+++ b/drivers/firmware/efi/libstub/efi-stub-entry.c
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/efi.h>
+#include <asm/efi.h>
+
+#include "efistub.h"
+
+/*
+ * EFI entry point for the generic EFI stub used by ARM, arm64, RISC-V and
+ * LoongArch. This is the entrypoint that is described in the PE/COFF header
+ * of the core kernel.
+ */
+efi_status_t __efiapi efi_pe_entry(efi_handle_t handle,
+ efi_system_table_t *systab)
+{
+ efi_loaded_image_t *image;
+ efi_status_t status;
+ unsigned long image_addr;
+ unsigned long image_size = 0;
+ /* addr/point and size pairs for memory management*/
+ char *cmdline_ptr = NULL;
+ efi_guid_t loaded_image_proto = LOADED_IMAGE_PROTOCOL_GUID;
+ unsigned long reserve_addr = 0;
+ unsigned long reserve_size = 0;
+
+ WRITE_ONCE(efi_system_table, systab);
+
+ /* Check if we were booted by the EFI firmware */
+ if (efi_system_table->hdr.signature != EFI_SYSTEM_TABLE_SIGNATURE)
+ return EFI_INVALID_PARAMETER;
+
+ /*
+ * Get a handle to the loaded image protocol. This is used to get
+ * information about the running image, such as size and the command
+ * line.
+ */
+ status = efi_bs_call(handle_protocol, handle, &loaded_image_proto,
+ (void *)&image);
+ if (status != EFI_SUCCESS) {
+ efi_err("Failed to get loaded image protocol\n");
+ return status;
+ }
+
+ status = efi_handle_cmdline(image, &cmdline_ptr);
+ if (status != EFI_SUCCESS)
+ return status;
+
+ efi_info("Booting Linux Kernel...\n");
+
+ status = handle_kernel_image(&image_addr, &image_size,
+ &reserve_addr,
+ &reserve_size,
+ image, handle);
+ if (status != EFI_SUCCESS) {
+ efi_err("Failed to relocate kernel\n");
+ return status;
+ }
+
+ status = efi_stub_common(handle, image, image_addr, cmdline_ptr);
+
+ efi_free(image_size, image_addr);
+ efi_free(reserve_size, reserve_addr);
+
+ return status;
+}
diff --git a/drivers/firmware/efi/libstub/efi-stub-helper.c b/drivers/firmware/efi/libstub/efi-stub-helper.c
index 0c493521b25b..2184f3f153ef 100644
--- a/drivers/firmware/efi/libstub/efi-stub-helper.c
+++ b/drivers/firmware/efi/libstub/efi-stub-helper.c
@@ -9,10 +9,8 @@
#include <linux/stdarg.h>
-#include <linux/ctype.h>
#include <linux/efi.h>
#include <linux/kernel.h>
-#include <linux/printk.h> /* For CONSOLE_LOGLEVEL_* */
#include <asm/efi.h>
#include <asm/setup.h>
@@ -20,7 +18,6 @@
bool efi_nochunk;
bool efi_nokaslr = !IS_ENABLED(CONFIG_RANDOMIZE_BASE);
-int efi_loglevel = CONSOLE_LOGLEVEL_DEFAULT;
bool efi_novamap;
static bool efi_noinitrd;
@@ -33,146 +30,6 @@ bool __pure __efi_soft_reserve_enabled(void)
}
/**
- * efi_char16_puts() - Write a UCS-2 encoded string to the console
- * @str: UCS-2 encoded string
- */
-void efi_char16_puts(efi_char16_t *str)
-{
- efi_call_proto(efi_table_attr(efi_system_table, con_out),
- output_string, str);
-}
-
-static
-u32 utf8_to_utf32(const u8 **s8)
-{
- u32 c32;
- u8 c0, cx;
- size_t clen, i;
-
- c0 = cx = *(*s8)++;
- /*
- * The position of the most-significant 0 bit gives us the length of
- * a multi-octet encoding.
- */
- for (clen = 0; cx & 0x80; ++clen)
- cx <<= 1;
- /*
- * If the 0 bit is in position 8, this is a valid single-octet
- * encoding. If the 0 bit is in position 7 or positions 1-3, the
- * encoding is invalid.
- * In either case, we just return the first octet.
- */
- if (clen < 2 || clen > 4)
- return c0;
- /* Get the bits from the first octet. */
- c32 = cx >> clen--;
- for (i = 0; i < clen; ++i) {
- /* Trailing octets must have 10 in most significant bits. */
- cx = (*s8)[i] ^ 0x80;
- if (cx & 0xc0)
- return c0;
- c32 = (c32 << 6) | cx;
- }
- /*
- * Check for validity:
- * - The character must be in the Unicode range.
- * - It must not be a surrogate.
- * - It must be encoded using the correct number of octets.
- */
- if (c32 > 0x10ffff ||
- (c32 & 0xf800) == 0xd800 ||
- clen != (c32 >= 0x80) + (c32 >= 0x800) + (c32 >= 0x10000))
- return c0;
- *s8 += clen;
- return c32;
-}
-
-/**
- * efi_puts() - Write a UTF-8 encoded string to the console
- * @str: UTF-8 encoded string
- */
-void efi_puts(const char *str)
-{
- efi_char16_t buf[128];
- size_t pos = 0, lim = ARRAY_SIZE(buf);
- const u8 *s8 = (const u8 *)str;
- u32 c32;
-
- while (*s8) {
- if (*s8 == '\n')
- buf[pos++] = L'\r';
- c32 = utf8_to_utf32(&s8);
- if (c32 < 0x10000) {
- /* Characters in plane 0 use a single word. */
- buf[pos++] = c32;
- } else {
- /*
- * Characters in other planes encode into a surrogate
- * pair.
- */
- buf[pos++] = (0xd800 - (0x10000 >> 10)) + (c32 >> 10);
- buf[pos++] = 0xdc00 + (c32 & 0x3ff);
- }
- if (*s8 == '\0' || pos >= lim - 2) {
- buf[pos] = L'\0';
- efi_char16_puts(buf);
- pos = 0;
- }
- }
-}
-
-/**
- * efi_printk() - Print a kernel message
- * @fmt: format string
- *
- * The first letter of the format string is used to determine the logging level
- * of the message. If the level is less then the current EFI logging level, the
- * message is suppressed. The message will be truncated to 255 bytes.
- *
- * Return: number of printed characters
- */
-int efi_printk(const char *fmt, ...)
-{
- char printf_buf[256];
- va_list args;
- int printed;
- int loglevel = printk_get_level(fmt);
-
- switch (loglevel) {
- case '0' ... '9':
- loglevel -= '0';
- break;
- default:
- /*
- * Use loglevel -1 for cases where we just want to print to
- * the screen.
- */
- loglevel = -1;
- break;
- }
-
- if (loglevel >= efi_loglevel)
- return 0;
-
- if (loglevel >= 0)
- efi_puts("EFI stub: ");
-
- fmt = printk_skip_level(fmt);
-
- va_start(args, fmt);
- printed = vsnprintf(printf_buf, sizeof(printf_buf), fmt, args);
- va_end(args);
-
- efi_puts(printf_buf);
- if (printed >= sizeof(printf_buf)) {
- efi_puts("[Message truncated]\n");
- return -1;
- }
-
- return printed;
-}
-
-/**
* efi_parse_options() - Parse EFI command line options
* @cmdline: kernel command line
*
diff --git a/drivers/firmware/efi/libstub/efi-stub.c b/drivers/firmware/efi/libstub/efi-stub.c
index cf474f0dd261..2955c1ac6a36 100644
--- a/drivers/firmware/efi/libstub/efi-stub.c
+++ b/drivers/firmware/efi/libstub/efi-stub.c
@@ -35,15 +35,6 @@
* as well to minimize the code churn.
*/
#define EFI_RT_VIRTUAL_BASE SZ_512M
-#define EFI_RT_VIRTUAL_SIZE SZ_512M
-
-#ifdef CONFIG_ARM64
-# define EFI_RT_VIRTUAL_LIMIT DEFAULT_MAP_WINDOW_64
-#elif defined(CONFIG_RISCV) || defined(CONFIG_LOONGARCH)
-# define EFI_RT_VIRTUAL_LIMIT TASK_SIZE_MIN
-#else /* Only if TASK_SIZE is a constant */
-# define EFI_RT_VIRTUAL_LIMIT TASK_SIZE
-#endif
/*
* Some architectures map the EFI regions into the kernel's linear map using a
@@ -56,6 +47,15 @@
static u64 virtmap_base = EFI_RT_VIRTUAL_BASE;
static bool flat_va_mapping = (EFI_RT_VIRTUAL_OFFSET != 0);
+struct screen_info * __weak alloc_screen_info(void)
+{
+ return &screen_info;
+}
+
+void __weak free_screen_info(struct screen_info *si)
+{
+}
+
static struct screen_info *setup_graphics(void)
{
efi_guid_t gop_proto = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
@@ -115,62 +115,21 @@ static u32 get_supported_rt_services(void)
return supported;
}
-/*
- * EFI entry point for the arm/arm64 EFI stubs. This is the entrypoint
- * that is described in the PE/COFF header. Most of the code is the same
- * for both archictectures, with the arch-specific code provided in the
- * handle_kernel_image() function.
- */
-efi_status_t __efiapi efi_pe_entry(efi_handle_t handle,
- efi_system_table_t *sys_table_arg)
+efi_status_t efi_handle_cmdline(efi_loaded_image_t *image, char **cmdline_ptr)
{
- efi_loaded_image_t *image;
- efi_status_t status;
- unsigned long image_addr;
- unsigned long image_size = 0;
- /* addr/point and size pairs for memory management*/
- char *cmdline_ptr = NULL;
int cmdline_size = 0;
- efi_guid_t loaded_image_proto = LOADED_IMAGE_PROTOCOL_GUID;
- unsigned long reserve_addr = 0;
- unsigned long reserve_size = 0;
- struct screen_info *si;
- efi_properties_table_t *prop_tbl;
-
- efi_system_table = sys_table_arg;
-
- /* Check if we were booted by the EFI firmware */
- if (efi_system_table->hdr.signature != EFI_SYSTEM_TABLE_SIGNATURE) {
- status = EFI_INVALID_PARAMETER;
- goto fail;
- }
-
- status = check_platform_features();
- if (status != EFI_SUCCESS)
- goto fail;
-
- /*
- * Get a handle to the loaded image protocol. This is used to get
- * information about the running image, such as size and the command
- * line.
- */
- status = efi_bs_call(handle_protocol, handle, &loaded_image_proto,
- (void *)&image);
- if (status != EFI_SUCCESS) {
- efi_err("Failed to get loaded image protocol\n");
- goto fail;
- }
+ efi_status_t status;
+ char *cmdline;
/*
* Get the command line from EFI, using the LOADED_IMAGE
* protocol. We are going to copy the command line into the
* device tree, so this can be allocated anywhere.
*/
- cmdline_ptr = efi_convert_cmdline(image, &cmdline_size);
- if (!cmdline_ptr) {
+ cmdline = efi_convert_cmdline(image, &cmdline_size);
+ if (!cmdline) {
efi_err("getting command line via LOADED_IMAGE_PROTOCOL\n");
- status = EFI_OUT_OF_RESOURCES;
- goto fail;
+ return EFI_OUT_OF_RESOURCES;
}
if (IS_ENABLED(CONFIG_CMDLINE_EXTEND) ||
@@ -184,25 +143,34 @@ efi_status_t __efiapi efi_pe_entry(efi_handle_t handle,
}
if (!IS_ENABLED(CONFIG_CMDLINE_FORCE) && cmdline_size > 0) {
- status = efi_parse_options(cmdline_ptr);
+ status = efi_parse_options(cmdline);
if (status != EFI_SUCCESS) {
efi_err("Failed to parse options\n");
goto fail_free_cmdline;
}
}
- efi_info("Booting Linux Kernel...\n");
+ *cmdline_ptr = cmdline;
+ return EFI_SUCCESS;
- si = setup_graphics();
+fail_free_cmdline:
+ efi_bs_call(free_pool, cmdline_ptr);
+ return status;
+}
- status = handle_kernel_image(&image_addr, &image_size,
- &reserve_addr,
- &reserve_size,
- image, handle);
- if (status != EFI_SUCCESS) {
- efi_err("Failed to relocate kernel\n");
- goto fail_free_screeninfo;
- }
+efi_status_t efi_stub_common(efi_handle_t handle,
+ efi_loaded_image_t *image,
+ unsigned long image_addr,
+ char *cmdline_ptr)
+{
+ struct screen_info *si;
+ efi_status_t status;
+
+ status = check_platform_features();
+ if (status != EFI_SUCCESS)
+ return status;
+
+ si = setup_graphics();
efi_retrieve_tpm2_eventlog();
@@ -214,53 +182,15 @@ efi_status_t __efiapi efi_pe_entry(efi_handle_t handle,
efi_random_get_seed();
- /*
- * If the NX PE data feature is enabled in the properties table, we
- * should take care not to create a virtual mapping that changes the
- * relative placement of runtime services code and data regions, as
- * they may belong to the same PE/COFF executable image in memory.
- * The easiest way to achieve that is to simply use a 1:1 mapping.
- */
- prop_tbl = get_efi_config_table(EFI_PROPERTIES_TABLE_GUID);
- flat_va_mapping |= prop_tbl &&
- (prop_tbl->memory_protection_attribute &
- EFI_PROPERTIES_RUNTIME_MEMORY_PROTECTION_NON_EXECUTABLE_PE_DATA);
-
/* force efi_novamap if SetVirtualAddressMap() is unsupported */
efi_novamap |= !(get_supported_rt_services() &
EFI_RT_SUPPORTED_SET_VIRTUAL_ADDRESS_MAP);
- /* hibernation expects the runtime regions to stay in the same place */
- if (!IS_ENABLED(CONFIG_HIBERNATION) && !efi_nokaslr && !flat_va_mapping) {
- /*
- * Randomize the base of the UEFI runtime services region.
- * Preserve the 2 MB alignment of the region by taking a
- * shift of 21 bit positions into account when scaling
- * the headroom value using a 32-bit random value.
- */
- static const u64 headroom = EFI_RT_VIRTUAL_LIMIT -
- EFI_RT_VIRTUAL_BASE -
- EFI_RT_VIRTUAL_SIZE;
- u32 rnd;
-
- status = efi_get_random_bytes(sizeof(rnd), (u8 *)&rnd);
- if (status == EFI_SUCCESS) {
- virtmap_base = EFI_RT_VIRTUAL_BASE +
- (((headroom >> 21) * rnd) >> (32 - 21));
- }
- }
-
install_memreserve_table();
status = efi_boot_kernel(handle, image, image_addr, cmdline_ptr);
- efi_free(image_size, image_addr);
- efi_free(reserve_size, reserve_addr);
-fail_free_screeninfo:
free_screen_info(si);
-fail_free_cmdline:
- efi_bs_call(free_pool, cmdline_ptr);
-fail:
return status;
}
diff --git a/drivers/firmware/efi/libstub/efistub.h b/drivers/firmware/efi/libstub/efistub.h
index eb03d5a9aac8..21b01d6c77ba 100644
--- a/drivers/firmware/efi/libstub/efistub.h
+++ b/drivers/firmware/efi/libstub/efistub.h
@@ -958,6 +958,14 @@ efi_status_t handle_kernel_image(unsigned long *image_addr,
efi_loaded_image_t *image,
efi_handle_t image_handle);
+/* shared entrypoint between the normal stub and the zboot stub */
+efi_status_t efi_stub_common(efi_handle_t handle,
+ efi_loaded_image_t *image,
+ unsigned long image_addr,
+ char *cmdline_ptr);
+
+efi_status_t efi_handle_cmdline(efi_loaded_image_t *image, char **cmdline_ptr);
+
asmlinkage void __noreturn efi_enter_kernel(unsigned long entrypoint,
unsigned long fdt_addr,
unsigned long fdt_size);
@@ -975,6 +983,13 @@ efi_enable_reset_attack_mitigation(void) { }
void efi_retrieve_tpm2_eventlog(void);
+struct screen_info *alloc_screen_info(void);
+void free_screen_info(struct screen_info *si);
+
+void efi_cache_sync_image(unsigned long image_base,
+ unsigned long alloc_size,
+ unsigned long code_size);
+
struct efi_smbios_record {
u8 type;
u8 length;
diff --git a/drivers/firmware/efi/libstub/file.c b/drivers/firmware/efi/libstub/file.c
index f756c61396e9..246ccc5b015d 100644
--- a/drivers/firmware/efi/libstub/file.c
+++ b/drivers/firmware/efi/libstub/file.c
@@ -66,28 +66,10 @@ static efi_status_t efi_open_file(efi_file_protocol_t *volume,
static efi_status_t efi_open_volume(efi_loaded_image_t *image,
efi_file_protocol_t **fh)
{
- struct efi_vendor_dev_path *dp = image->file_path;
- efi_guid_t li_proto = LOADED_IMAGE_PROTOCOL_GUID;
efi_guid_t fs_proto = EFI_FILE_SYSTEM_GUID;
efi_simple_file_system_protocol_t *io;
efi_status_t status;
- // If we are using EFI zboot, we should look for the file system
- // protocol on the parent image's handle instead
- if (IS_ENABLED(CONFIG_EFI_ZBOOT) &&
- image->parent_handle != NULL &&
- dp != NULL &&
- dp->header.type == EFI_DEV_MEDIA &&
- dp->header.sub_type == EFI_DEV_MEDIA_VENDOR &&
- !efi_guidcmp(dp->vendorguid, LINUX_EFI_ZBOOT_MEDIA_GUID)) {
- status = efi_bs_call(handle_protocol, image->parent_handle,
- &li_proto, (void *)&image);
- if (status != EFI_SUCCESS) {
- efi_err("Failed to locate parent image handle\n");
- return status;
- }
- }
-
status = efi_bs_call(handle_protocol, image->device_handle, &fs_proto,
(void **)&io);
if (status != EFI_SUCCESS) {
diff --git a/drivers/firmware/efi/libstub/intrinsics.c b/drivers/firmware/efi/libstub/intrinsics.c
index a04ab39292b6..965e734f6f98 100644
--- a/drivers/firmware/efi/libstub/intrinsics.c
+++ b/drivers/firmware/efi/libstub/intrinsics.c
@@ -28,3 +28,21 @@ void *memset(void *dst, int c, size_t len)
efi_bs_call(set_mem, dst, len, c & U8_MAX);
return dst;
}
+
+/**
+ * memcmp - Compare two areas of memory
+ * @cs: One area of memory
+ * @ct: Another area of memory
+ * @count: The size of the area.
+ */
+#undef memcmp
+int memcmp(const void *cs, const void *ct, size_t count)
+{
+ const unsigned char *su1, *su2;
+ int res = 0;
+
+ for (su1 = cs, su2 = ct; 0 < count; ++su1, ++su2, count--)
+ if ((res = *su1 - *su2) != 0)
+ break;
+ return res;
+}
diff --git a/drivers/firmware/efi/libstub/loongarch-stub.c b/drivers/firmware/efi/libstub/loongarch-stub.c
index 32329f2a92f9..eee7ed43cdfb 100644
--- a/drivers/firmware/efi/libstub/loongarch-stub.c
+++ b/drivers/firmware/efi/libstub/loongarch-stub.c
@@ -9,18 +9,10 @@
#include <asm/addrspace.h>
#include "efistub.h"
-typedef void __noreturn (*kernel_entry_t)(bool efi, unsigned long cmdline,
- unsigned long systab);
-
extern int kernel_asize;
extern int kernel_fsize;
extern int kernel_offset;
-extern kernel_entry_t kernel_entry;
-
-efi_status_t check_platform_features(void)
-{
- return EFI_SUCCESS;
-}
+extern int kernel_entry;
efi_status_t handle_kernel_image(unsigned long *image_addr,
unsigned long *image_size,
@@ -29,74 +21,33 @@ efi_status_t handle_kernel_image(unsigned long *image_addr,
efi_loaded_image_t *image,
efi_handle_t image_handle)
{
+ int nr_pages = round_up(kernel_asize, EFI_ALLOC_ALIGN) / EFI_PAGE_SIZE;
+ efi_physical_addr_t kernel_addr = EFI_KIMG_PREFERRED_ADDRESS;
efi_status_t status;
- unsigned long kernel_addr = 0;
-
- kernel_addr = (unsigned long)&kernel_offset - kernel_offset;
-
- status = efi_relocate_kernel(&kernel_addr, kernel_fsize, kernel_asize,
- PHYSADDR(VMLINUX_LOAD_ADDRESS), SZ_2M, 0x0);
-
- *image_addr = kernel_addr;
- *image_size = kernel_asize;
-
- return status;
-}
-
-struct exit_boot_struct {
- efi_memory_desc_t *runtime_map;
- int runtime_entry_count;
-};
-
-static efi_status_t exit_boot_func(struct efi_boot_memmap *map, void *priv)
-{
- struct exit_boot_struct *p = priv;
/*
- * Update the memory map with virtual addresses. The function will also
- * populate @runtime_map with copies of just the EFI_MEMORY_RUNTIME
- * entries so that we can pass it straight to SetVirtualAddressMap()
+ * Allocate space for the kernel image at the preferred offset. This is
+ * the only location in memory from where we can execute the image, so
+ * no point in falling back to another allocation.
*/
- efi_get_virtmap(map->map, map->map_size, map->desc_size,
- p->runtime_map, &p->runtime_entry_count);
-
- return EFI_SUCCESS;
-}
-
-efi_status_t efi_boot_kernel(void *handle, efi_loaded_image_t *image,
- unsigned long kernel_addr, char *cmdline_ptr)
-{
- kernel_entry_t real_kernel_entry;
- struct exit_boot_struct priv;
- unsigned long desc_size;
- efi_status_t status;
- u32 desc_ver;
-
- status = efi_alloc_virtmap(&priv.runtime_map, &desc_size, &desc_ver);
- if (status != EFI_SUCCESS) {
- efi_err("Unable to retrieve UEFI memory map.\n");
- return status;
- }
-
- efi_info("Exiting boot services\n");
-
- efi_novamap = false;
- status = efi_exit_boot_services(handle, &priv, exit_boot_func);
+ status = efi_bs_call(allocate_pages, EFI_ALLOCATE_ADDRESS,
+ EFI_LOADER_DATA, nr_pages, &kernel_addr);
if (status != EFI_SUCCESS)
return status;
- /* Install the new virtual address map */
- efi_rt_call(set_virtual_address_map,
- priv.runtime_entry_count * desc_size, desc_size,
- desc_ver, priv.runtime_map);
+ *image_addr = EFI_KIMG_PREFERRED_ADDRESS;
+ *image_size = kernel_asize;
- /* Config Direct Mapping */
- csr_write64(CSR_DMW0_INIT, LOONGARCH_CSR_DMWIN0);
- csr_write64(CSR_DMW1_INIT, LOONGARCH_CSR_DMWIN1);
+ memcpy((void *)EFI_KIMG_PREFERRED_ADDRESS,
+ (void *)&kernel_offset - kernel_offset,
+ kernel_fsize);
- real_kernel_entry = (kernel_entry_t)
- ((unsigned long)&kernel_entry - kernel_addr + VMLINUX_LOAD_ADDRESS);
+ return status;
+}
+
+unsigned long kernel_entry_address(void)
+{
+ unsigned long base = (unsigned long)&kernel_offset - kernel_offset;
- real_kernel_entry(true, (unsigned long)cmdline_ptr,
- (unsigned long)efi_system_table);
+ return (unsigned long)&kernel_entry - base + VMLINUX_LOAD_ADDRESS;
}
diff --git a/drivers/firmware/efi/libstub/loongarch.c b/drivers/firmware/efi/libstub/loongarch.c
new file mode 100644
index 000000000000..807cba2693fc
--- /dev/null
+++ b/drivers/firmware/efi/libstub/loongarch.c
@@ -0,0 +1,80 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Author: Yun Liu <liuyun@loongson.cn>
+ * Huacai Chen <chenhuacai@loongson.cn>
+ * Copyright (C) 2020-2022 Loongson Technology Corporation Limited
+ */
+
+#include <asm/efi.h>
+#include <asm/addrspace.h>
+#include "efistub.h"
+
+typedef void __noreturn (*kernel_entry_t)(bool efi, unsigned long cmdline,
+ unsigned long systab);
+
+efi_status_t check_platform_features(void)
+{
+ return EFI_SUCCESS;
+}
+
+struct exit_boot_struct {
+ efi_memory_desc_t *runtime_map;
+ int runtime_entry_count;
+};
+
+static efi_status_t exit_boot_func(struct efi_boot_memmap *map, void *priv)
+{
+ struct exit_boot_struct *p = priv;
+
+ /*
+ * Update the memory map with virtual addresses. The function will also
+ * populate @runtime_map with copies of just the EFI_MEMORY_RUNTIME
+ * entries so that we can pass it straight to SetVirtualAddressMap()
+ */
+ efi_get_virtmap(map->map, map->map_size, map->desc_size,
+ p->runtime_map, &p->runtime_entry_count);
+
+ return EFI_SUCCESS;
+}
+
+unsigned long __weak kernel_entry_address(void)
+{
+ return *(unsigned long *)(PHYSADDR(VMLINUX_LOAD_ADDRESS) + 8);
+}
+
+efi_status_t efi_boot_kernel(void *handle, efi_loaded_image_t *image,
+ unsigned long kernel_addr, char *cmdline_ptr)
+{
+ kernel_entry_t real_kernel_entry;
+ struct exit_boot_struct priv;
+ unsigned long desc_size;
+ efi_status_t status;
+ u32 desc_ver;
+
+ status = efi_alloc_virtmap(&priv.runtime_map, &desc_size, &desc_ver);
+ if (status != EFI_SUCCESS) {
+ efi_err("Unable to retrieve UEFI memory map.\n");
+ return status;
+ }
+
+ efi_info("Exiting boot services\n");
+
+ efi_novamap = false;
+ status = efi_exit_boot_services(handle, &priv, exit_boot_func);
+ if (status != EFI_SUCCESS)
+ return status;
+
+ /* Install the new virtual address map */
+ efi_rt_call(set_virtual_address_map,
+ priv.runtime_entry_count * desc_size, desc_size,
+ desc_ver, priv.runtime_map);
+
+ /* Config Direct Mapping */
+ csr_write64(CSR_DMW0_INIT, LOONGARCH_CSR_DMWIN0);
+ csr_write64(CSR_DMW1_INIT, LOONGARCH_CSR_DMWIN1);
+
+ real_kernel_entry = (void *)kernel_entry_address();
+
+ real_kernel_entry(true, (unsigned long)cmdline_ptr,
+ (unsigned long)efi_system_table);
+}
diff --git a/drivers/firmware/efi/libstub/printk.c b/drivers/firmware/efi/libstub/printk.c
new file mode 100644
index 000000000000..3a67a2cea7bd
--- /dev/null
+++ b/drivers/firmware/efi/libstub/printk.c
@@ -0,0 +1,154 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/stdarg.h>
+
+#include <linux/ctype.h>
+#include <linux/efi.h>
+#include <linux/kernel.h>
+#include <linux/printk.h> /* For CONSOLE_LOGLEVEL_* */
+#include <asm/efi.h>
+#include <asm/setup.h>
+
+#include "efistub.h"
+
+int efi_loglevel = CONSOLE_LOGLEVEL_DEFAULT;
+
+/**
+ * efi_char16_puts() - Write a UCS-2 encoded string to the console
+ * @str: UCS-2 encoded string
+ */
+void efi_char16_puts(efi_char16_t *str)
+{
+ efi_call_proto(efi_table_attr(efi_system_table, con_out),
+ output_string, str);
+}
+
+static
+u32 utf8_to_utf32(const u8 **s8)
+{
+ u32 c32;
+ u8 c0, cx;
+ size_t clen, i;
+
+ c0 = cx = *(*s8)++;
+ /*
+ * The position of the most-significant 0 bit gives us the length of
+ * a multi-octet encoding.
+ */
+ for (clen = 0; cx & 0x80; ++clen)
+ cx <<= 1;
+ /*
+ * If the 0 bit is in position 8, this is a valid single-octet
+ * encoding. If the 0 bit is in position 7 or positions 1-3, the
+ * encoding is invalid.
+ * In either case, we just return the first octet.
+ */
+ if (clen < 2 || clen > 4)
+ return c0;
+ /* Get the bits from the first octet. */
+ c32 = cx >> clen--;
+ for (i = 0; i < clen; ++i) {
+ /* Trailing octets must have 10 in most significant bits. */
+ cx = (*s8)[i] ^ 0x80;
+ if (cx & 0xc0)
+ return c0;
+ c32 = (c32 << 6) | cx;
+ }
+ /*
+ * Check for validity:
+ * - The character must be in the Unicode range.
+ * - It must not be a surrogate.
+ * - It must be encoded using the correct number of octets.
+ */
+ if (c32 > 0x10ffff ||
+ (c32 & 0xf800) == 0xd800 ||
+ clen != (c32 >= 0x80) + (c32 >= 0x800) + (c32 >= 0x10000))
+ return c0;
+ *s8 += clen;
+ return c32;
+}
+
+/**
+ * efi_puts() - Write a UTF-8 encoded string to the console
+ * @str: UTF-8 encoded string
+ */
+void efi_puts(const char *str)
+{
+ efi_char16_t buf[128];
+ size_t pos = 0, lim = ARRAY_SIZE(buf);
+ const u8 *s8 = (const u8 *)str;
+ u32 c32;
+
+ while (*s8) {
+ if (*s8 == '\n')
+ buf[pos++] = L'\r';
+ c32 = utf8_to_utf32(&s8);
+ if (c32 < 0x10000) {
+ /* Characters in plane 0 use a single word. */
+ buf[pos++] = c32;
+ } else {
+ /*
+ * Characters in other planes encode into a surrogate
+ * pair.
+ */
+ buf[pos++] = (0xd800 - (0x10000 >> 10)) + (c32 >> 10);
+ buf[pos++] = 0xdc00 + (c32 & 0x3ff);
+ }
+ if (*s8 == '\0' || pos >= lim - 2) {
+ buf[pos] = L'\0';
+ efi_char16_puts(buf);
+ pos = 0;
+ }
+ }
+}
+
+/**
+ * efi_printk() - Print a kernel message
+ * @fmt: format string
+ *
+ * The first letter of the format string is used to determine the logging level
+ * of the message. If the level is less then the current EFI logging level, the
+ * message is suppressed. The message will be truncated to 255 bytes.
+ *
+ * Return: number of printed characters
+ */
+int efi_printk(const char *fmt, ...)
+{
+ char printf_buf[256];
+ va_list args;
+ int printed;
+ int loglevel = printk_get_level(fmt);
+
+ switch (loglevel) {
+ case '0' ... '9':
+ loglevel -= '0';
+ break;
+ default:
+ /*
+ * Use loglevel -1 for cases where we just want to print to
+ * the screen.
+ */
+ loglevel = -1;
+ break;
+ }
+
+ if (loglevel >= efi_loglevel)
+ return 0;
+
+ if (loglevel >= 0)
+ efi_puts("EFI stub: ");
+
+ fmt = printk_skip_level(fmt);
+
+ va_start(args, fmt);
+ printed = vsnprintf(printf_buf, sizeof(printf_buf), fmt, args);
+ va_end(args);
+
+ efi_puts(printf_buf);
+ if (printed >= sizeof(printf_buf)) {
+ efi_puts("[Message truncated]\n");
+ return -1;
+ }
+
+ return printed;
+}
diff --git a/drivers/firmware/efi/libstub/riscv-stub.c b/drivers/firmware/efi/libstub/riscv-stub.c
index b450ebf95977..145c9f0ba217 100644
--- a/drivers/firmware/efi/libstub/riscv-stub.c
+++ b/drivers/firmware/efi/libstub/riscv-stub.c
@@ -4,7 +4,6 @@
*/
#include <linux/efi.h>
-#include <linux/libfdt.h>
#include <asm/efi.h>
#include <asm/sections.h>
@@ -12,92 +11,16 @@
#include "efistub.h"
-/*
- * RISC-V requires the kernel image to placed 2 MB aligned base for 64 bit and
- * 4MB for 32 bit.
- */
-#ifdef CONFIG_64BIT
-#define MIN_KIMG_ALIGN SZ_2M
-#else
-#define MIN_KIMG_ALIGN SZ_4M
-#endif
-
-typedef void __noreturn (*jump_kernel_func)(unsigned long, unsigned long);
-
-static unsigned long hartid;
-
-static int get_boot_hartid_from_fdt(void)
-{
- const void *fdt;
- int chosen_node, len;
- const void *prop;
-
- fdt = get_efi_config_table(DEVICE_TREE_GUID);
- if (!fdt)
- return -EINVAL;
-
- chosen_node = fdt_path_offset(fdt, "/chosen");
- if (chosen_node < 0)
- return -EINVAL;
-
- prop = fdt_getprop((void *)fdt, chosen_node, "boot-hartid", &len);
- if (!prop)
- return -EINVAL;
-
- if (len == sizeof(u32))
- hartid = (unsigned long) fdt32_to_cpu(*(fdt32_t *)prop);
- else if (len == sizeof(u64))
- hartid = (unsigned long) fdt64_to_cpu(__get_unaligned_t(fdt64_t, prop));
- else
- return -EINVAL;
-
- return 0;
-}
-
-static efi_status_t get_boot_hartid_from_efi(void)
+unsigned long stext_offset(void)
{
- efi_guid_t boot_protocol_guid = RISCV_EFI_BOOT_PROTOCOL_GUID;
- struct riscv_efi_boot_protocol *boot_protocol;
- efi_status_t status;
-
- status = efi_bs_call(locate_protocol, &boot_protocol_guid, NULL,
- (void **)&boot_protocol);
- if (status != EFI_SUCCESS)
- return status;
- return efi_call_proto(boot_protocol, get_boot_hartid, &hartid);
-}
-
-efi_status_t check_platform_features(void)
-{
- efi_status_t status;
- int ret;
-
- status = get_boot_hartid_from_efi();
- if (status != EFI_SUCCESS) {
- ret = get_boot_hartid_from_fdt();
- if (ret) {
- efi_err("Failed to get boot hartid!\n");
- return EFI_UNSUPPORTED;
- }
- }
- return EFI_SUCCESS;
-}
-
-void __noreturn efi_enter_kernel(unsigned long entrypoint, unsigned long fdt,
- unsigned long fdt_size)
-{
- unsigned long stext_offset = _start_kernel - _start;
- unsigned long kernel_entry = entrypoint + stext_offset;
- jump_kernel_func jump_kernel = (jump_kernel_func)kernel_entry;
-
/*
- * Jump to real kernel here with following constraints.
- * 1. MMU should be disabled.
- * 2. a0 should contain hartid
- * 3. a1 should DT address
+ * When built as part of the kernel, the EFI stub cannot branch to the
+ * kernel proper via the image header, as the PE/COFF header is
+ * strictly not part of the in-memory presentation of the image, only
+ * of the file representation. So instead, we need to jump to the
+ * actual entrypoint in the .text region of the image.
*/
- csr_write(CSR_SATP, 0);
- jump_kernel(hartid, fdt);
+ return _start_kernel - _start;
}
efi_status_t handle_kernel_image(unsigned long *image_addr,
@@ -125,9 +48,10 @@ efi_status_t handle_kernel_image(unsigned long *image_addr,
* lowest possible memory region as long as the address and size meets
* the alignment constraints.
*/
- preferred_addr = MIN_KIMG_ALIGN;
+ preferred_addr = EFI_KIMG_PREFERRED_ADDRESS;
status = efi_relocate_kernel(image_addr, kernel_size, *image_size,
- preferred_addr, MIN_KIMG_ALIGN, 0x0);
+ preferred_addr, efi_get_kimg_min_align(),
+ 0x0);
if (status != EFI_SUCCESS) {
efi_err("Failed to relocate kernel\n");
diff --git a/drivers/firmware/efi/libstub/riscv.c b/drivers/firmware/efi/libstub/riscv.c
new file mode 100644
index 000000000000..8022b104c3e6
--- /dev/null
+++ b/drivers/firmware/efi/libstub/riscv.c
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020 Western Digital Corporation or its affiliates.
+ */
+
+#include <linux/efi.h>
+#include <linux/libfdt.h>
+
+#include <asm/efi.h>
+#include <asm/unaligned.h>
+
+#include "efistub.h"
+
+typedef void __noreturn (*jump_kernel_func)(unsigned long, unsigned long);
+
+static unsigned long hartid;
+
+static int get_boot_hartid_from_fdt(void)
+{
+ const void *fdt;
+ int chosen_node, len;
+ const void *prop;
+
+ fdt = get_efi_config_table(DEVICE_TREE_GUID);
+ if (!fdt)
+ return -EINVAL;
+
+ chosen_node = fdt_path_offset(fdt, "/chosen");
+ if (chosen_node < 0)
+ return -EINVAL;
+
+ prop = fdt_getprop((void *)fdt, chosen_node, "boot-hartid", &len);
+ if (!prop)
+ return -EINVAL;
+
+ if (len == sizeof(u32))
+ hartid = (unsigned long) fdt32_to_cpu(*(fdt32_t *)prop);
+ else if (len == sizeof(u64))
+ hartid = (unsigned long) fdt64_to_cpu(__get_unaligned_t(fdt64_t, prop));
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+static efi_status_t get_boot_hartid_from_efi(void)
+{
+ efi_guid_t boot_protocol_guid = RISCV_EFI_BOOT_PROTOCOL_GUID;
+ struct riscv_efi_boot_protocol *boot_protocol;
+ efi_status_t status;
+
+ status = efi_bs_call(locate_protocol, &boot_protocol_guid, NULL,
+ (void **)&boot_protocol);
+ if (status != EFI_SUCCESS)
+ return status;
+ return efi_call_proto(boot_protocol, get_boot_hartid, &hartid);
+}
+
+efi_status_t check_platform_features(void)
+{
+ efi_status_t status;
+ int ret;
+
+ status = get_boot_hartid_from_efi();
+ if (status != EFI_SUCCESS) {
+ ret = get_boot_hartid_from_fdt();
+ if (ret) {
+ efi_err("Failed to get boot hartid!\n");
+ return EFI_UNSUPPORTED;
+ }
+ }
+ return EFI_SUCCESS;
+}
+
+unsigned long __weak stext_offset(void)
+{
+ /*
+ * This fallback definition is used by the EFI zboot stub, which loads
+ * the entire image so it can branch via the image header at offset #0.
+ */
+ return 0;
+}
+
+void __noreturn efi_enter_kernel(unsigned long entrypoint, unsigned long fdt,
+ unsigned long fdt_size)
+{
+ unsigned long kernel_entry = entrypoint + stext_offset();
+ jump_kernel_func jump_kernel = (jump_kernel_func)kernel_entry;
+
+ /*
+ * Jump to real kernel here with following constraints.
+ * 1. MMU should be disabled.
+ * 2. a0 should contain hartid
+ * 3. a1 should DT address
+ */
+ csr_write(CSR_SATP, 0);
+ jump_kernel(hartid, fdt);
+}
diff --git a/drivers/firmware/efi/libstub/screen_info.c b/drivers/firmware/efi/libstub/screen_info.c
new file mode 100644
index 000000000000..8e76a8b384ba
--- /dev/null
+++ b/drivers/firmware/efi/libstub/screen_info.c
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/efi.h>
+#include <asm/efi.h>
+
+#include "efistub.h"
+
+/*
+ * There are two ways of populating the core kernel's struct screen_info via the stub:
+ * - using a configuration table, like below, which relies on the EFI init code
+ * to locate the table and copy the contents;
+ * - by linking directly to the core kernel's copy of the global symbol.
+ *
+ * The latter is preferred because it makes the EFIFB earlycon available very
+ * early, but it only works if the EFI stub is part of the core kernel image
+ * itself. The zboot decompressor can only use the configuration table
+ * approach.
+ *
+ * In order to support both methods from the same build of the EFI stub
+ * library, provide this dummy global definition of struct screen_info. If it
+ * is required to satisfy a link dependency, it means we need to override the
+ * __weak alloc and free methods with the ones below, and those will be pulled
+ * in as well.
+ */
+struct screen_info screen_info;
+
+static efi_guid_t screen_info_guid = LINUX_EFI_SCREEN_INFO_TABLE_GUID;
+
+struct screen_info *alloc_screen_info(void)
+{
+ struct screen_info *si;
+ efi_status_t status;
+
+ status = efi_bs_call(allocate_pool, EFI_ACPI_RECLAIM_MEMORY,
+ sizeof(*si), (void **)&si);
+
+ if (status != EFI_SUCCESS)
+ return NULL;
+
+ status = efi_bs_call(install_configuration_table,
+ &screen_info_guid, si);
+ if (status == EFI_SUCCESS)
+ return si;
+
+ efi_bs_call(free_pool, si);
+ return NULL;
+}
+
+void free_screen_info(struct screen_info *si)
+{
+ if (!si)
+ return;
+
+ efi_bs_call(install_configuration_table, &screen_info_guid, NULL);
+ efi_bs_call(free_pool, si);
+}
diff --git a/drivers/firmware/efi/libstub/string.c b/drivers/firmware/efi/libstub/string.c
index 5d13e43869ee..168fe8e79abc 100644
--- a/drivers/firmware/efi/libstub/string.c
+++ b/drivers/firmware/efi/libstub/string.c
@@ -11,7 +11,37 @@
#include <linux/types.h>
#include <linux/string.h>
-#ifndef __HAVE_ARCH_STRSTR
+#ifndef EFI_HAVE_STRLEN
+/**
+ * strlen - Find the length of a string
+ * @s: The string to be sized
+ */
+size_t strlen(const char *s)
+{
+ const char *sc;
+
+ for (sc = s; *sc != '\0'; ++sc)
+ /* nothing */;
+ return sc - s;
+}
+#endif
+
+#ifndef EFI_HAVE_STRNLEN
+/**
+ * strnlen - Find the length of a length-limited string
+ * @s: The string to be sized
+ * @count: The maximum number of bytes to search
+ */
+size_t strnlen(const char *s, size_t count)
+{
+ const char *sc;
+
+ for (sc = s; count-- && *sc != '\0'; ++sc)
+ /* nothing */;
+ return sc - s;
+}
+#endif
+
/**
* strstr - Find the first substring in a %NUL terminated string
* @s1: The string to be searched
@@ -33,9 +63,29 @@ char *strstr(const char *s1, const char *s2)
}
return NULL;
}
+
+#ifndef EFI_HAVE_STRCMP
+/**
+ * strcmp - Compare two strings
+ * @cs: One string
+ * @ct: Another string
+ */
+int strcmp(const char *cs, const char *ct)
+{
+ unsigned char c1, c2;
+
+ while (1) {
+ c1 = *cs++;
+ c2 = *ct++;
+ if (c1 != c2)
+ return c1 < c2 ? -1 : 1;
+ if (!c1)
+ break;
+ }
+ return 0;
+}
#endif
-#ifndef __HAVE_ARCH_STRNCMP
/**
* strncmp - Compare two length-limited strings
* @cs: One string
@@ -57,7 +107,6 @@ int strncmp(const char *cs, const char *ct, size_t count)
}
return 0;
}
-#endif
/* Works only for digits and letters, but small and fast */
#define TOLOWER(x) ((x) | 0x20)
@@ -113,3 +162,43 @@ long simple_strtol(const char *cp, char **endp, unsigned int base)
return simple_strtoull(cp, endp, base);
}
+
+#ifdef CONFIG_EFI_PARAMS_FROM_FDT
+#ifndef EFI_HAVE_STRRCHR
+/**
+ * strrchr - Find the last occurrence of a character in a string
+ * @s: The string to be searched
+ * @c: The character to search for
+ */
+char *strrchr(const char *s, int c)
+{
+ const char *last = NULL;
+ do {
+ if (*s == (char)c)
+ last = s;
+ } while (*s++);
+ return (char *)last;
+}
+#endif
+#ifndef EFI_HAVE_MEMCHR
+/**
+ * memchr - Find a character in an area of memory.
+ * @s: The memory area
+ * @c: The byte to search for
+ * @n: The size of the area.
+ *
+ * returns the address of the first occurrence of @c, or %NULL
+ * if @c is not found
+ */
+void *memchr(const void *s, int c, size_t n)
+{
+ const unsigned char *p = s;
+ while (n-- != 0) {
+ if ((unsigned char)c == *p++) {
+ return (void *)(p - 1);
+ }
+ }
+ return NULL;
+}
+#endif
+#endif
diff --git a/drivers/firmware/efi/libstub/zboot-header.S b/drivers/firmware/efi/libstub/zboot-header.S
index 9e6fe061ab07..bc2d7750d7f1 100644
--- a/drivers/firmware/efi/libstub/zboot-header.S
+++ b/drivers/firmware/efi/libstub/zboot-header.S
@@ -17,7 +17,7 @@ __efistub_efi_zboot_header:
.long MZ_MAGIC
.ascii "zimg" // image type
.long __efistub__gzdata_start - .Ldoshdr // payload offset
- .long __efistub__gzdata_size - ZBOOT_SIZE_LEN // payload size
+ .long __efistub__gzdata_size - 12 // payload size
.long 0, 0 // reserved
.asciz COMP_TYPE // compression type
.org .Ldoshdr + 0x3c
diff --git a/drivers/firmware/efi/libstub/zboot.c b/drivers/firmware/efi/libstub/zboot.c
index ea72c8f27da6..66be5fdc6b58 100644
--- a/drivers/firmware/efi/libstub/zboot.c
+++ b/drivers/firmware/efi/libstub/zboot.c
@@ -32,271 +32,116 @@ static unsigned long free_mem_ptr, free_mem_end_ptr;
extern char efi_zboot_header[];
extern char _gzdata_start[], _gzdata_end[];
-static void log(efi_char16_t str[])
-{
- efi_call_proto(efi_table_attr(efi_system_table, con_out),
- output_string, L"EFI decompressor: ");
- efi_call_proto(efi_table_attr(efi_system_table, con_out),
- output_string, str);
- efi_call_proto(efi_table_attr(efi_system_table, con_out),
- output_string, L"\n");
-}
-
static void error(char *x)
{
- log(L"error() called from decompressor library\n");
-}
-
-// Local version to avoid pulling in memcmp()
-static bool guids_eq(const efi_guid_t *a, const efi_guid_t *b)
-{
- const u32 *l = (u32 *)a;
- const u32 *r = (u32 *)b;
-
- return l[0] == r[0] && l[1] == r[1] && l[2] == r[2] && l[3] == r[3];
-}
-
-static efi_status_t __efiapi
-load_file(efi_load_file_protocol_t *this, efi_device_path_protocol_t *rem,
- bool boot_policy, unsigned long *bufsize, void *buffer)
-{
- unsigned long compressed_size = _gzdata_end - _gzdata_start;
- struct efi_vendor_dev_path *vendor_dp;
- bool decompress = false;
- unsigned long size;
- int ret;
-
- if (rem == NULL || bufsize == NULL)
- return EFI_INVALID_PARAMETER;
-
- if (boot_policy)
- return EFI_UNSUPPORTED;
-
- // Look for our vendor media device node in the remaining file path
- if (rem->type == EFI_DEV_MEDIA &&
- rem->sub_type == EFI_DEV_MEDIA_VENDOR) {
- vendor_dp = container_of(rem, struct efi_vendor_dev_path, header);
- if (!guids_eq(&vendor_dp->vendorguid, &LINUX_EFI_ZBOOT_MEDIA_GUID))
- return EFI_NOT_FOUND;
-
- decompress = true;
- rem = (void *)(vendor_dp + 1);
- }
-
- if (rem->type != EFI_DEV_END_PATH ||
- rem->sub_type != EFI_DEV_END_ENTIRE)
- return EFI_NOT_FOUND;
-
- // The uncompressed size of the payload is appended to the raw bit
- // stream, and may therefore appear misaligned in memory
- size = decompress ? get_unaligned_le32(_gzdata_end - 4)
- : compressed_size;
- if (buffer == NULL || *bufsize < size) {
- *bufsize = size;
- return EFI_BUFFER_TOO_SMALL;
- }
-
- if (decompress) {
- ret = __decompress(_gzdata_start, compressed_size, NULL, NULL,
- buffer, size, NULL, error);
- if (ret < 0) {
- log(L"Decompression failed");
- return EFI_DEVICE_ERROR;
- }
- } else {
- memcpy(buffer, _gzdata_start, compressed_size);
- }
-
- return EFI_SUCCESS;
-}
-
-// Return the length in bytes of the device path up to the first end node.
-static int device_path_length(const efi_device_path_protocol_t *dp)
-{
- int len = 0;
-
- while (dp->type != EFI_DEV_END_PATH) {
- len += dp->length;
- dp = (void *)((u8 *)dp + dp->length);
- }
- return len;
+ efi_err("EFI decompressor: %s\n", x);
}
-static void append_rel_offset_node(efi_device_path_protocol_t **dp,
- unsigned long start, unsigned long end)
+static unsigned long alloc_preferred_address(unsigned long alloc_size)
{
- struct efi_rel_offset_dev_path *rodp = (void *)*dp;
-
- rodp->header.type = EFI_DEV_MEDIA;
- rodp->header.sub_type = EFI_DEV_MEDIA_REL_OFFSET;
- rodp->header.length = sizeof(struct efi_rel_offset_dev_path);
- rodp->reserved = 0;
- rodp->starting_offset = start;
- rodp->ending_offset = end;
+#ifdef EFI_KIMG_PREFERRED_ADDRESS
+ efi_physical_addr_t efi_addr = EFI_KIMG_PREFERRED_ADDRESS;
- *dp = (void *)(rodp + 1);
-}
-
-static void append_ven_media_node(efi_device_path_protocol_t **dp,
- efi_guid_t *guid)
-{
- struct efi_vendor_dev_path *vmdp = (void *)*dp;
-
- vmdp->header.type = EFI_DEV_MEDIA;
- vmdp->header.sub_type = EFI_DEV_MEDIA_VENDOR;
- vmdp->header.length = sizeof(struct efi_vendor_dev_path);
- vmdp->vendorguid = *guid;
-
- *dp = (void *)(vmdp + 1);
+ if (efi_bs_call(allocate_pages, EFI_ALLOCATE_ADDRESS, EFI_LOADER_DATA,
+ alloc_size / EFI_PAGE_SIZE, &efi_addr) == EFI_SUCCESS)
+ return efi_addr;
+#endif
+ return ULONG_MAX;
}
-static void append_end_node(efi_device_path_protocol_t **dp)
+void __weak efi_cache_sync_image(unsigned long image_base,
+ unsigned long alloc_size,
+ unsigned long code_size)
{
- (*dp)->type = EFI_DEV_END_PATH;
- (*dp)->sub_type = EFI_DEV_END_ENTIRE;
- (*dp)->length = sizeof(struct efi_generic_dev_path);
-
- ++*dp;
+ // Provided by the arch to perform the cache maintenance necessary for
+ // executable code loaded into memory to be safe for execution.
}
asmlinkage efi_status_t __efiapi
efi_zboot_entry(efi_handle_t handle, efi_system_table_t *systab)
{
- struct efi_mem_mapped_dev_path mmdp = {
- .header.type = EFI_DEV_HW,
- .header.sub_type = EFI_DEV_MEM_MAPPED,
- .header.length = sizeof(struct efi_mem_mapped_dev_path)
- };
- efi_device_path_protocol_t *parent_dp, *dpp, *lf2_dp, *li_dp;
- efi_load_file2_protocol_t zboot_load_file2;
- efi_loaded_image_t *parent, *child;
- unsigned long exit_data_size;
- efi_handle_t child_handle;
- efi_handle_t zboot_handle;
- efi_char16_t *exit_data;
+ unsigned long compressed_size = _gzdata_end - _gzdata_start;
+ unsigned long image_base, alloc_size, code_size;
+ efi_loaded_image_t *image;
efi_status_t status;
- void *dp_alloc;
- int dp_len;
+ char *cmdline_ptr;
+ int ret;
WRITE_ONCE(efi_system_table, systab);
free_mem_ptr = (unsigned long)&zboot_heap;
free_mem_end_ptr = free_mem_ptr + sizeof(zboot_heap);
- exit_data = NULL;
- exit_data_size = 0;
-
status = efi_bs_call(handle_protocol, handle,
- &LOADED_IMAGE_PROTOCOL_GUID, (void **)&parent);
+ &LOADED_IMAGE_PROTOCOL_GUID, (void **)&image);
if (status != EFI_SUCCESS) {
- log(L"Failed to locate parent's loaded image protocol");
+ error("Failed to locate parent's loaded image protocol");
return status;
}
- status = efi_bs_call(handle_protocol, handle,
- &LOADED_IMAGE_DEVICE_PATH_PROTOCOL_GUID,
- (void **)&parent_dp);
- if (status != EFI_SUCCESS || parent_dp == NULL) {
- // Create a MemoryMapped() device path node to describe
- // the parent image if no device path was provided.
- mmdp.memory_type = parent->image_code_type;
- mmdp.starting_addr = (unsigned long)parent->image_base;
- mmdp.ending_addr = (unsigned long)parent->image_base +
- parent->image_size - 1;
- parent_dp = &mmdp.header;
- dp_len = sizeof(mmdp);
- } else {
- dp_len = device_path_length(parent_dp);
- }
-
- // Allocate some pool memory for device path protocol data
- status = efi_bs_call(allocate_pool, EFI_LOADER_DATA,
- 2 * (dp_len + sizeof(struct efi_rel_offset_dev_path) +
- sizeof(struct efi_generic_dev_path)) +
- sizeof(struct efi_vendor_dev_path),
- (void **)&dp_alloc);
- if (status != EFI_SUCCESS) {
- log(L"Failed to allocate device path pool memory");
+ status = efi_handle_cmdline(image, &cmdline_ptr);
+ if (status != EFI_SUCCESS)
return status;
- }
- // Create a device path describing the compressed payload in this image
- // <...parent_dp...>/Offset(<start>, <end>)
- lf2_dp = memcpy(dp_alloc, parent_dp, dp_len);
- dpp = (void *)((u8 *)lf2_dp + dp_len);
- append_rel_offset_node(&dpp,
- (unsigned long)(_gzdata_start - efi_zboot_header),
- (unsigned long)(_gzdata_end - efi_zboot_header - 1));
- append_end_node(&dpp);
-
- // Create a device path describing the decompressed payload in this image
- // <...parent_dp...>/Offset(<start>, <end>)/VenMedia(ZBOOT_MEDIA_GUID)
- dp_len += sizeof(struct efi_rel_offset_dev_path);
- li_dp = memcpy(dpp, lf2_dp, dp_len);
- dpp = (void *)((u8 *)li_dp + dp_len);
- append_ven_media_node(&dpp, &LINUX_EFI_ZBOOT_MEDIA_GUID);
- append_end_node(&dpp);
-
- zboot_handle = NULL;
- zboot_load_file2.load_file = load_file;
- status = efi_bs_call(install_multiple_protocol_interfaces,
- &zboot_handle,
- &EFI_DEVICE_PATH_PROTOCOL_GUID, lf2_dp,
- &EFI_LOAD_FILE2_PROTOCOL_GUID, &zboot_load_file2,
- NULL);
- if (status != EFI_SUCCESS) {
- log(L"Failed to install LoadFile2 protocol and device path");
- goto free_dpalloc;
- }
-
- status = efi_bs_call(load_image, false, handle, li_dp, NULL, 0,
- &child_handle);
- if (status != EFI_SUCCESS) {
- log(L"Failed to load image");
- goto uninstall_lf2;
- }
+ efi_info("Decompressing Linux Kernel...\n");
+
+ // SizeOfImage from the compressee's PE/COFF header
+ alloc_size = round_up(get_unaligned_le32(_gzdata_end - 4),
+ EFI_ALLOC_ALIGN);
+
+ // SizeOfHeaders and SizeOfCode from the compressee's PE/COFF header
+ code_size = get_unaligned_le32(_gzdata_end - 8) +
+ get_unaligned_le32(_gzdata_end - 12);
+
+ // If the architecture has a preferred address for the image,
+ // try that first.
+ image_base = alloc_preferred_address(alloc_size);
+ if (image_base == ULONG_MAX) {
+ unsigned long min_kimg_align = efi_get_kimg_min_align();
+ u32 seed = U32_MAX;
+
+ if (!IS_ENABLED(CONFIG_RANDOMIZE_BASE)) {
+ // Setting the random seed to 0x0 is the same as
+ // allocating as low as possible
+ seed = 0;
+ } else if (efi_nokaslr) {
+ efi_info("KASLR disabled on kernel command line\n");
+ } else {
+ status = efi_get_random_bytes(sizeof(seed), (u8 *)&seed);
+ if (status == EFI_NOT_FOUND) {
+ efi_info("EFI_RNG_PROTOCOL unavailable\n");
+ efi_nokaslr = true;
+ } else if (status != EFI_SUCCESS) {
+ efi_err("efi_get_random_bytes() failed (0x%lx)\n",
+ status);
+ efi_nokaslr = true;
+ }
+ }
- status = efi_bs_call(handle_protocol, child_handle,
- &LOADED_IMAGE_PROTOCOL_GUID, (void **)&child);
- if (status != EFI_SUCCESS) {
- log(L"Failed to locate child's loaded image protocol");
- goto unload_image;
+ status = efi_random_alloc(alloc_size, min_kimg_align, &image_base,
+ seed, EFI_LOADER_CODE);
+ if (status != EFI_SUCCESS) {
+ efi_err("Failed to allocate memory\n");
+ goto free_cmdline;
+ }
}
- // Copy the kernel command line
- child->load_options = parent->load_options;
- child->load_options_size = parent->load_options_size;
-
- status = efi_bs_call(start_image, child_handle, &exit_data_size,
- &exit_data);
- if (status != EFI_SUCCESS) {
- log(L"StartImage() returned with error");
- if (exit_data_size > 0)
- log(exit_data);
-
- // If StartImage() returns EFI_SECURITY_VIOLATION, the image is
- // not unloaded so we need to do it by hand.
- if (status == EFI_SECURITY_VIOLATION)
-unload_image:
- efi_bs_call(unload_image, child_handle);
+ // Decompress the payload into the newly allocated buffer.
+ ret = __decompress(_gzdata_start, compressed_size, NULL, NULL,
+ (void *)image_base, alloc_size, NULL, error);
+ if (ret < 0) {
+ error("Decompression failed");
+ status = EFI_DEVICE_ERROR;
+ goto free_image;
}
-uninstall_lf2:
- efi_bs_call(uninstall_multiple_protocol_interfaces,
- zboot_handle,
- &EFI_DEVICE_PATH_PROTOCOL_GUID, lf2_dp,
- &EFI_LOAD_FILE2_PROTOCOL_GUID, &zboot_load_file2,
- NULL);
-
-free_dpalloc:
- efi_bs_call(free_pool, dp_alloc);
+ efi_cache_sync_image(image_base, alloc_size, code_size);
- efi_bs_call(exit, handle, status, exit_data_size, exit_data);
+ status = efi_stub_common(handle, image, image_base, cmdline_ptr);
- // Free ExitData in case Exit() returned with a failure code,
- // but return the original status code.
- log(L"Exit() returned with failure code");
- if (exit_data != NULL)
- efi_bs_call(free_pool, exit_data);
+free_image:
+ efi_free(alloc_size, image_base);
+free_cmdline:
+ efi_bs_call(free_pool, cmdline_ptr);
return status;
}