Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,174 @@
# SPDX-License-Identifier: GPL-2.0
#
# The stub may be linked into the kernel proper or into a separate boot binary,
# but in either case, it executes before the kernel does (with MMU disabled) so
# 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__ $(CC_FLAGS_DIALECT) \
-fPIC -fno-strict-aliasing -mno-red-zone \
-mno-mmx -mno-sse -fshort-wchar \
-Wno-pointer-sign \
$(call cc-disable-warning, address-of-packed-member) \
-fno-asynchronous-unwind-tables \
$(CLANG_FLAGS)
# arm64 uses the full KBUILD_CFLAGS so it's necessary to explicitly
# disable the stackleak plugin
cflags-$(CONFIG_ARM64) += -fpie $(DISABLE_KSTACK_ERASE) \
-fno-unwind-tables -fno-asynchronous-unwind-tables
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) \
$(DISABLE_KSTACK_ERASE)
cflags-$(CONFIG_RISCV) += -fpic -DNO_ALTERNATIVE -mno-relax \
$(DISABLE_KSTACK_ERASE)
cflags-$(CONFIG_LOONGARCH) += -fpie $(DISABLE_KSTACK_ERASE)
cflags-$(CONFIG_EFI_PARAMS_FROM_FDT) += -I$(srctree)/scripts/dtc/libfdt
KBUILD_CFLAGS := $(subst $(CC_FLAGS_FTRACE),,$(cflags-y)) \
-Os -DDISABLE_BRANCH_PROFILING \
-include $(srctree)/include/linux/hidden.h \
-D__NO_FORTIFY \
-ffreestanding \
-fno-stack-protector \
$(call cc-option,-fno-addrsig) \
-D__DISABLE_EXPORTS
#
# struct randomization only makes sense for Linux internal types, which the EFI
# stub code never touches, so let's turn off struct randomization for the stub
# altogether
#
KBUILD_CFLAGS := $(filter-out $(RANDSTRUCT_CFLAGS), $(KBUILD_CFLAGS))
# remove SCS flags from all objects in this directory
KBUILD_CFLAGS := $(filter-out $(CC_FLAGS_SCS), $(KBUILD_CFLAGS))
# disable CFI
KBUILD_CFLAGS := $(filter-out $(CC_FLAGS_CFI), $(KBUILD_CFLAGS))
# disable LTO
KBUILD_CFLAGS := $(filter-out $(CC_FLAGS_LTO), $(KBUILD_CFLAGS))
# The .data section would be renamed to .data.efistub, therefore, remove
# `-fdata-sections` flag from KBUILD_CFLAGS_KERNEL
KBUILD_CFLAGS_KERNEL := $(filter-out -fdata-sections, $(KBUILD_CFLAGS_KERNEL))
KBUILD_AFLAGS := $(KBUILD_CFLAGS) -D__ASSEMBLY__
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 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 \
fdt_empty_tree.c fdt_sw.c
lib-$(CONFIG_EFI_PARAMS_FROM_FDT) += fdt.o \
$(patsubst %.c,lib-%.o,$(libfdt-deps))
$(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 \
primary_display.o efi-stub-entry.o
lib-$(CONFIG_ARM) += arm32-stub.o
lib-$(CONFIG_ARM64) += kaslr.o arm64.o arm64-stub.o smbios.o
lib-$(CONFIG_X86) += x86-stub.o smbios.o
lib-$(CONFIG_X86_64) += x86-5lvl.o
lib-$(CONFIG_RISCV) += kaslr.o riscv.o riscv-stub.o
lib-$(CONFIG_LOONGARCH) += loongarch.o loongarch-stub.o
CFLAGS_arm32-stub.o := -DTEXT_OFFSET=$(TEXT_OFFSET)
zboot-obj-y := zboot-decompress-gzip.o
CFLAGS_zboot-decompress-gzip.o += -I$(srctree)/lib/zlib_inflate
zboot-obj-$(CONFIG_KERNEL_ZSTD) := zboot-decompress-zstd.o lib-xxhash.o
CFLAGS_zboot-decompress-zstd.o += -I$(srctree)/lib/zstd
zboot-riscv-obj-$(CONFIG_32BIT) := lib-ashldi3.o lib-lshrdi3.o
zboot-obj-$(CONFIG_RISCV) += lib-clz_ctz.o $(zboot-riscv-obj-y)
zboot-loongarch-obj-$(CONFIG_32BIT) := lib-ashldi3.o lib-lshrdi3.o
zboot-obj-$(CONFIG_LOONGARCH) += lib-clz_ctz.o $(zboot-loongarch-obj-y)
lib-$(CONFIG_EFI_ZBOOT) += zboot.o $(zboot-obj-y)
lib-$(CONFIG_UNACCEPTED_MEMORY) += unaccepted_memory.o bitmap.o find.o
targets := $(lib-y)
lib-y := $(patsubst %.o,%.stub.o,$(lib-y))
# Even when -mbranch-protection=none is set, Clang will generate a
# .note.gnu.property for code-less object files (like lib/ctype.c),
# so work around this by explicitly removing the unwanted section.
# https://llvm.org/pr46480
STUBCOPY_FLAGS-y += --remove-section=.note.gnu.property
STUBCOPY_RELOC-$(CONFIG_X86_32) := R_386_32
STUBCOPY_RELOC-$(CONFIG_X86_64) := R_X86_64_64
#
# ARM discards the .data section because it disallows r/w data in the
# decompressor. So move our .data to .data.efistub and .bss to .bss.efistub,
# which are preserved explicitly by the decompressor linker script.
#
STUBCOPY_FLAGS-$(CONFIG_ARM) += --rename-section .data=.data.efistub \
--rename-section .bss=.bss.efistub,load,alloc
STUBCOPY_RELOC-$(CONFIG_ARM) := R_ARM_ABS
#
# arm64 puts the stub in the kernel proper, which will unnecessarily retain all
# code indefinitely unless it is annotated as __init/__initdata/__initconst etc.
# So let's apply the __init annotations at the section level, by prefixing
# the section names directly. This will ensure that even all the inline string
# literals are covered.
# The fact that the stub and the kernel proper are essentially the same binary
# also means that we need to be extra careful to make sure that the stub does
# not rely on any absolute symbol references, considering that the virtual
# kernel mapping that the linker uses is not active yet when the stub is
# executing. So build all C dependencies of the EFI stub into libstub, and do
# a verification pass to see if any absolute relocations exist in any of the
# object files.
#
STUBCOPY_FLAGS-$(CONFIG_ARM64) += --prefix-alloc-sections=.init \
--prefix-symbols=__efistub_
STUBCOPY_RELOC-$(CONFIG_ARM64) := R_AARCH64_ABS
# 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
# exist.
STUBCOPY_FLAGS-$(CONFIG_RISCV) += --prefix-alloc-sections=.init \
--prefix-symbols=__efistub_
STUBCOPY_RELOC-$(CONFIG_RISCV) := -E R_RISCV_HI20\|R_RISCV_$(BITS)\|R_RISCV_RELAX
# For LoongArch, keep all the symbols in .init section and make sure that no
# absolute symbols references exist.
STUBCOPY_FLAGS-$(CONFIG_LOONGARCH) += --prefix-alloc-sections=.init \
--prefix-symbols=__efistub_
STUBCOPY_RELOC-$(CONFIG_LOONGARCH) := R_LARCH_MARK_LA
$(obj)/%.stub.o: $(obj)/%.o FORCE
$(call if_changed,stubcopy)
#
# Strip debug sections and some other sections that may legally contain
# absolute relocations, so that we can inspect the remaining sections for
# such relocations. If none are found, regenerate the output object, but
# this time, use objcopy and leave all sections in place.
#
quiet_cmd_stubcopy = STUBCPY $@
cmd_stubcopy = \
$(STRIP) --strip-debug -o $@ $<; \
if $(OBJDUMP) -r $@ | grep $(STUBCOPY_RELOC-y); then \
echo "$@: absolute symbol references not allowed in the EFI stub" >&2; \
/bin/false; \
fi; \
$(OBJCOPY) $(STUBCOPY_FLAGS-y) $< $@
@@ -0,0 +1,61 @@
# SPDX-License-Identifier: GPL-2.0
# to be include'd by arch/$(ARCH)/boot/Makefile after setting
# EFI_ZBOOT_PAYLOAD, EFI_ZBOOT_BFD_TARGET, EFI_ZBOOT_MACH_TYPE and
# EFI_ZBOOT_FORWARD_CFI
quiet_cmd_copy_and_pad = PAD $@
cmd_copy_and_pad = cp $< $@; \
truncate -s $$(hexdump -s16 -n4 -e '"%u"' $<) $@
# Pad the file to the size of the uncompressed image in memory, including BSS
$(obj)/vmlinux.bin: $(obj)/$(EFI_ZBOOT_PAYLOAD) FORCE
$(call if_changed,copy_and_pad)
# 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.
comp-type-y := gzip
zboot-method-y := gzip
zboot-size-len-y := 0
comp-type-$(CONFIG_KERNEL_ZSTD) := zstd
zboot-method-$(CONFIG_KERNEL_ZSTD) := zstd22_with_size
zboot-size-len-$(CONFIG_KERNEL_ZSTD) := 4
$(obj)/vmlinuz: $(obj)/vmlinux.bin FORCE
$(call if_changed,$(zboot-method-y))
# avoid eager evaluation to prevent references to non-existent build artifacts
OBJCOPYFLAGS_vmlinuz.o = -I binary -O $(EFI_ZBOOT_BFD_TARGET) $(EFI_ZBOOT_OBJCOPY_FLAGS) \
--rename-section .data=.gzdata,load,alloc,readonly,contents
$(obj)/vmlinuz.o: $(obj)/vmlinuz FORCE
$(call if_changed,objcopy)
aflags-zboot-header-$(EFI_ZBOOT_FORWARD_CFI) := \
-DPE_DLL_CHAR_EX=IMAGE_DLLCHARACTERISTICS_EX_FORWARD_CFI_COMPAT
AFLAGS_zboot-header.o += -DMACHINE_TYPE=IMAGE_FILE_MACHINE_$(EFI_ZBOOT_MACH_TYPE) \
-DZBOOT_EFI_PATH="\"$(abspath $(obj)/vmlinuz.efi.elf)\"" \
-DZBOOT_SIZE_LEN=$(zboot-size-len-y) \
-DCOMP_TYPE="\"$(comp-type-y)\"" \
$(aflags-zboot-header-y)
$(obj)/zboot-header.o: $(srctree)/drivers/firmware/efi/libstub/zboot-header.S FORCE
$(call if_changed_rule,as_o_S)
ifneq ($(CONFIG_EFI_SBAT_FILE),)
$(obj)/zboot-header.o: $(CONFIG_EFI_SBAT_FILE)
endif
ZBOOT_DEPS := $(obj)/zboot-header.o $(objtree)/drivers/firmware/efi/libstub/lib.a
LDFLAGS_vmlinuz.efi.elf := -T $(srctree)/drivers/firmware/efi/libstub/zboot.lds
$(obj)/vmlinuz.efi.elf: $(obj)/vmlinuz.o $(ZBOOT_DEPS) FORCE
$(call if_changed,ld)
OBJCOPYFLAGS_vmlinuz.efi := -O binary
$(obj)/vmlinuz.efi: $(obj)/vmlinuz.efi.elf FORCE
$(call if_changed,objcopy)
targets += zboot-header.o vmlinux.bin vmlinuz vmlinuz.o vmlinuz.efi.elf vmlinuz.efi
@@ -0,0 +1,61 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/efi.h>
#include <asm/efi.h>
#include "efistub.h"
/**
* efi_allocate_pages_aligned() - Allocate memory pages
* @size: minimum number of bytes to allocate
* @addr: On return the address of the first allocated page. The first
* allocated page has alignment EFI_ALLOC_ALIGN which is an
* architecture dependent multiple of the page size.
* @max: the address that the last allocated memory page shall not
* exceed
* @align: minimum alignment of the base of the allocation
* @memory_type: the type of memory to allocate
*
* Allocate pages as EFI_LOADER_DATA. The allocated pages are aligned according
* to @align, which should be >= EFI_ALLOC_ALIGN. The last allocated page will
* not exceed the address given by @max.
*
* Return: status code
*/
efi_status_t efi_allocate_pages_aligned(unsigned long size, unsigned long *addr,
unsigned long max, unsigned long align,
int memory_type)
{
efi_physical_addr_t alloc_addr;
efi_status_t status;
int slack;
max = min(max, EFI_ALLOC_LIMIT);
if (align < EFI_ALLOC_ALIGN)
align = EFI_ALLOC_ALIGN;
alloc_addr = ALIGN_DOWN(max + 1, align) - 1;
size = round_up(size, EFI_ALLOC_ALIGN);
slack = align / EFI_PAGE_SIZE - 1;
status = efi_bs_call(allocate_pages, EFI_ALLOCATE_MAX_ADDRESS,
memory_type, size / EFI_PAGE_SIZE + slack,
&alloc_addr);
if (status != EFI_SUCCESS)
return status;
*addr = ALIGN((unsigned long)alloc_addr, align);
if (slack > 0) {
int l = (alloc_addr & (align - 1)) / EFI_PAGE_SIZE;
if (l) {
efi_bs_call(free_pages, alloc_addr, slack - l + 1);
slack = l - 1;
}
if (slack)
efi_bs_call(free_pages, *addr + size, slack);
}
return EFI_SUCCESS;
}
@@ -0,0 +1,134 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2013 Linaro Ltd; <roy.franz@linaro.org>
*/
#include <linux/efi.h>
#include <asm/efi.h>
#include "efistub.h"
static efi_guid_t cpu_state_guid = LINUX_EFI_ARM_CPU_STATE_TABLE_GUID;
struct efi_arm_entry_state *efi_entry_state;
static void get_cpu_state(u32 *cpsr, u32 *sctlr)
{
asm("mrs %0, cpsr" : "=r"(*cpsr));
if ((*cpsr & MODE_MASK) == HYP_MODE)
asm("mrc p15, 4, %0, c1, c0, 0" : "=r"(*sctlr));
else
asm("mrc p15, 0, %0, c1, c0, 0" : "=r"(*sctlr));
}
efi_status_t check_platform_features(void)
{
efi_status_t status;
u32 cpsr, sctlr;
int block;
get_cpu_state(&cpsr, &sctlr);
efi_info("Entering in %s mode with MMU %sabled\n",
((cpsr & MODE_MASK) == HYP_MODE) ? "HYP" : "SVC",
(sctlr & 1) ? "en" : "dis");
status = efi_bs_call(allocate_pool, EFI_LOADER_DATA,
sizeof(*efi_entry_state),
(void **)&efi_entry_state);
if (status != EFI_SUCCESS) {
efi_err("allocate_pool() failed\n");
return status;
}
efi_entry_state->cpsr_before_ebs = cpsr;
efi_entry_state->sctlr_before_ebs = sctlr;
status = efi_bs_call(install_configuration_table, &cpu_state_guid,
efi_entry_state);
if (status != EFI_SUCCESS) {
efi_err("install_configuration_table() failed\n");
goto free_state;
}
/* non-LPAE kernels can run anywhere */
if (!IS_ENABLED(CONFIG_ARM_LPAE))
return EFI_SUCCESS;
/* LPAE kernels need compatible hardware */
block = cpuid_feature_extract(CPUID_EXT_MMFR0, 0);
if (block < 5) {
efi_err("This LPAE kernel is not supported by your CPU\n");
status = EFI_UNSUPPORTED;
goto drop_table;
}
return EFI_SUCCESS;
drop_table:
efi_bs_call(install_configuration_table, &cpu_state_guid, NULL);
free_state:
efi_bs_call(free_pool, efi_entry_state);
return status;
}
void efi_handle_post_ebs_state(void)
{
get_cpu_state(&efi_entry_state->cpsr_after_ebs,
&efi_entry_state->sctlr_after_ebs);
}
efi_status_t handle_kernel_image(unsigned long *image_addr,
unsigned long *image_size,
unsigned long *reserve_addr,
unsigned long *reserve_size,
efi_loaded_image_t *image,
efi_handle_t image_handle)
{
const int slack = TEXT_OFFSET - 5 * PAGE_SIZE;
int alloc_size = MAX_UNCOMP_KERNEL_SIZE + EFI_PHYS_ALIGN;
unsigned long alloc_base, kernel_base;
efi_status_t status;
/*
* Allocate space for the decompressed kernel as low as possible.
* The region should be 16 MiB aligned, but the first 'slack' bytes
* are not used by Linux, so we allow those to be occupied by the
* firmware.
*/
status = efi_low_alloc_above(alloc_size, EFI_PAGE_SIZE, &alloc_base, 0x0);
if (status != EFI_SUCCESS) {
efi_err("Unable to allocate memory for uncompressed kernel.\n");
return status;
}
if ((alloc_base % EFI_PHYS_ALIGN) > slack) {
/*
* More than 'slack' bytes are already occupied at the base of
* the allocation, so we need to advance to the next 16 MiB block.
*/
kernel_base = round_up(alloc_base, EFI_PHYS_ALIGN);
efi_info("Free memory starts at 0x%lx, setting kernel_base to 0x%lx\n",
alloc_base, kernel_base);
} else {
kernel_base = round_down(alloc_base, EFI_PHYS_ALIGN);
}
*reserve_addr = kernel_base + slack;
*reserve_size = MAX_UNCOMP_KERNEL_SIZE;
/* now free the parts that we will not use */
if (*reserve_addr > alloc_base) {
efi_bs_call(free_pages, alloc_base,
(*reserve_addr - alloc_base) / EFI_PAGE_SIZE);
alloc_size -= *reserve_addr - alloc_base;
}
efi_bs_call(free_pages, *reserve_addr + MAX_UNCOMP_KERNEL_SIZE,
(alloc_size - MAX_UNCOMP_KERNEL_SIZE) / EFI_PAGE_SIZE);
*image_addr = kernel_base + TEXT_OFFSET;
*image_size = 0;
efi_debug("image addr == 0x%lx, reserve_addr == 0x%lx\n",
*image_addr, *reserve_addr);
return EFI_SUCCESS;
}
@@ -0,0 +1,63 @@
// 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/sections.h>
#include "efistub.h"
efi_status_t handle_kernel_image(unsigned long *image_addr,
unsigned long *image_size,
unsigned long *reserve_addr,
unsigned long *reserve_size,
efi_loaded_image_t *image,
efi_handle_t image_handle)
{
unsigned long kernel_size, kernel_codesize, kernel_memsize;
if (image->image_base != _text) {
efi_err("FIRMWARE BUG: efi_loaded_image_t::image_base has bogus value\n");
image->image_base = _text;
}
if (!IS_ALIGNED((u64)_text, SEGMENT_ALIGN))
efi_err("FIRMWARE BUG: kernel image not aligned on %dk boundary\n",
SEGMENT_ALIGN >> 10);
kernel_size = _edata - _text;
kernel_codesize = __inittext_end - _text;
kernel_memsize = kernel_size + (_end - _edata);
*reserve_size = kernel_memsize;
*image_addr = (unsigned long)_text;
return efi_kaslr_relocate_kernel(image_addr, reserve_addr, reserve_size,
kernel_size, kernel_codesize, kernel_memsize,
efi_kaslr_get_phys_seed(image_handle));
}
asmlinkage void primary_entry(void);
unsigned long primary_entry_offset(void)
{
/*
* 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.
*/
return (char *)primary_entry - _text;
}
void efi_icache_sync(unsigned long start, unsigned long end)
{
caches_clean_inval_pou(start, end);
}
@@ -0,0 +1,139 @@
// 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/image.h>
#include <asm/memory.h>
#include <asm/sysreg.h>
#include "efistub.h"
static bool system_needs_vamap(void)
{
const struct efi_smbios_type4_record *record;
const u32 __aligned(1) *socid;
const u8 *version;
/*
* Ampere eMAG, Altra, and Altra Max machines crash in SetTime() if
* SetVirtualAddressMap() has not been called prior. Most Altra systems
* can be identified by the SMCCC soc ID, which is conveniently exposed
* via the type 4 SMBIOS records. Otherwise, test the processor version
* field. eMAG systems all appear to have the processor version field
* set to "eMAG".
*/
record = (struct efi_smbios_type4_record *)efi_get_smbios_record(4);
if (!record)
return false;
socid = (u32 *)record->processor_id;
switch (*socid & 0xffff000f) {
static char const altra[] = "Ampere(TM) Altra(TM) Processor";
static char const emag[] = "eMAG";
default:
version = efi_get_smbios_string(record, processor_version);
if (!version || (strncmp(version, altra, sizeof(altra) - 1) &&
strncmp(version, emag, sizeof(emag) - 1)))
break;
fallthrough;
case 0x0a160001: // Altra
case 0x0a160002: // Altra Max
efi_warn("Working around broken SetVirtualAddressMap()\n");
return true;
}
return false;
}
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;
}
#ifdef CONFIG_ARM64_WORKAROUND_CLEAN_CACHE
#define DCTYPE "civac"
#else
#define DCTYPE "cvau"
#endif
u32 __weak code_size;
void efi_cache_sync_image(unsigned long image_base,
unsigned long alloc_size)
{
u32 ctr = read_cpuid_effective_cachetype();
u64 lsize = 4 << cpuid_feature_extract_unsigned_field(ctr,
CTR_EL0_DminLine_SHIFT);
/* only perform the cache maintenance if needed for I/D coherency */
if (!(ctr & BIT(CTR_EL0_IDC_SHIFT))) {
unsigned long base = image_base;
unsigned long size = code_size;
do {
asm("dc " DCTYPE ", %0" :: "r"(base));
base += lsize;
size -= lsize;
} while (size >= lsize);
}
asm("ic ialluis");
dsb(ish);
isb();
efi_remap_image(image_base, alloc_size, code_size);
}
unsigned long __weak primary_entry_offset(void)
{
/*
* By default, we can invoke the kernel via the branch instruction in
* the image header, so offset #0. This will be overridden by the EFI
* stub build that is linked into the core kernel, as in that case, the
* image header may not have been loaded into memory, or may be mapped
* with non-executable permissions.
*/
return 0;
}
void __noreturn efi_enter_kernel(unsigned long entrypoint,
unsigned long fdt_addr,
unsigned long fdt_size)
{
void (* __noreturn enter_kernel)(u64, u64, u64, u64);
enter_kernel = (void *)entrypoint + primary_entry_offset();
enter_kernel(fdt_addr, 0, 0, 0);
}
@@ -0,0 +1,41 @@
#include <linux/bitmap.h>
void __bitmap_set(unsigned long *map, unsigned int start, int len)
{
unsigned long *p = map + BIT_WORD(start);
const unsigned int size = start + len;
int bits_to_set = BITS_PER_LONG - (start % BITS_PER_LONG);
unsigned long mask_to_set = BITMAP_FIRST_WORD_MASK(start);
while (len - bits_to_set >= 0) {
*p |= mask_to_set;
len -= bits_to_set;
bits_to_set = BITS_PER_LONG;
mask_to_set = ~0UL;
p++;
}
if (len) {
mask_to_set &= BITMAP_LAST_WORD_MASK(size);
*p |= mask_to_set;
}
}
void __bitmap_clear(unsigned long *map, unsigned int start, int len)
{
unsigned long *p = map + BIT_WORD(start);
const unsigned int size = start + len;
int bits_to_clear = BITS_PER_LONG - (start % BITS_PER_LONG);
unsigned long mask_to_clear = BITMAP_FIRST_WORD_MASK(start);
while (len - bits_to_clear >= 0) {
*p &= ~mask_to_clear;
len -= bits_to_clear;
bits_to_clear = BITS_PER_LONG;
mask_to_clear = ~0UL;
p++;
}
if (len) {
mask_to_clear &= BITMAP_LAST_WORD_MASK(size);
*p &= ~mask_to_clear;
}
}
@@ -0,0 +1,89 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/efi.h>
#include <linux/sysfb.h>
#include <asm/efi.h>
#include "efistub.h"
static unsigned long kernel_image_offset;
static void *kernel_image_addr(void *addr)
{
return addr + kernel_image_offset;
}
struct sysfb_display_info *alloc_primary_display(void)
{
if (IS_ENABLED(CONFIG_ARM))
return __alloc_primary_display();
if (IS_ENABLED(CONFIG_X86) ||
IS_ENABLED(CONFIG_EFI_EARLYCON) ||
IS_ENABLED(CONFIG_SYSFB))
return kernel_image_addr(&sysfb_primary_display);
return NULL;
}
/*
* 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;
}
kernel_image_offset = image_addr - (unsigned long)image->image_base;
status = efi_stub_common(handle, image, image_addr, cmdline_ptr);
efi_free(image_size, image_addr);
efi_free(reserve_size, reserve_addr);
return status;
}
@@ -0,0 +1,776 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Helper functions used by the EFI stub on multiple
* architectures. This should be #included by the EFI stub
* implementation files.
*
* Copyright 2011 Intel Corporation; author Matt Fleming
*/
#include <linux/stdarg.h>
#include <linux/efi.h>
#include <linux/kernel.h>
#include <linux/overflow.h>
#include <asm/efi.h>
#include <asm/setup.h>
#include "efistub.h"
bool efi_nochunk;
bool efi_nokaslr = !IS_ENABLED(CONFIG_RANDOMIZE_BASE);
bool efi_novamap;
static bool efi_noinitrd;
static bool efi_nosoftreserve;
static bool efi_disable_pci_dma = IS_ENABLED(CONFIG_EFI_DISABLE_PCI_DMA);
int efi_mem_encrypt;
bool __pure __efi_soft_reserve_enabled(void)
{
return !efi_nosoftreserve;
}
/**
* efi_parse_options() - Parse EFI command line options
* @cmdline: kernel command line
*
* Parse the ASCII string @cmdline for EFI options, denoted by the efi=
* option, e.g. efi=nochunk.
*
* It should be noted that efi= is parsed in two very different
* environments, first in the early boot environment of the EFI boot
* stub, and subsequently during the kernel boot.
*
* Return: status code
*/
efi_status_t efi_parse_options(char const *cmdline)
{
char *buf __free(efi_pool) = NULL;
efi_status_t status;
size_t len;
char *str;
if (!cmdline)
return EFI_SUCCESS;
len = strnlen(cmdline, COMMAND_LINE_SIZE - 1) + 1;
status = efi_bs_call(allocate_pool, EFI_LOADER_DATA, len, (void **)&buf);
if (status != EFI_SUCCESS)
return status;
memcpy(buf, cmdline, len - 1);
buf[len - 1] = '\0';
str = skip_spaces(buf);
while (*str) {
char *param, *val;
str = next_arg(str, &param, &val);
if (!val && !strcmp(param, "--"))
break;
if (!strcmp(param, "nokaslr")) {
efi_nokaslr = true;
} else if (!strcmp(param, "quiet")) {
efi_loglevel = CONSOLE_LOGLEVEL_QUIET;
} else if (!strcmp(param, "noinitrd")) {
efi_noinitrd = true;
} else if (IS_ENABLED(CONFIG_X86_64) && !strcmp(param, "no5lvl")) {
efi_no5lvl = true;
} else if (IS_ENABLED(CONFIG_LOONGARCH) &&
IS_ENABLED(CONFIG_HIBERNATION) &&
!strcmp(param, "resume") && val) {
efi_nokaslr = true; /* LoongArch can't KASLR for hibernation */
} else if (IS_ENABLED(CONFIG_ARCH_HAS_MEM_ENCRYPT) &&
!strcmp(param, "mem_encrypt") && val) {
if (parse_option_str(val, "on"))
efi_mem_encrypt = 1;
else if (parse_option_str(val, "off"))
efi_mem_encrypt = -1;
} else if (!strcmp(param, "efi") && val) {
efi_nochunk = parse_option_str(val, "nochunk");
efi_novamap |= parse_option_str(val, "novamap");
efi_nosoftreserve = IS_ENABLED(CONFIG_EFI_SOFT_RESERVE) &&
parse_option_str(val, "nosoftreserve");
if (parse_option_str(val, "disable_early_pci_dma"))
efi_disable_pci_dma = true;
if (parse_option_str(val, "no_disable_early_pci_dma"))
efi_disable_pci_dma = false;
if (parse_option_str(val, "debug"))
efi_loglevel = CONSOLE_LOGLEVEL_DEBUG;
} else if (!strcmp(param, "video") &&
val && strstarts(val, "efifb:")) {
efi_parse_option_graphics(val + strlen("efifb:"));
}
}
return EFI_SUCCESS;
}
/*
* The EFI_LOAD_OPTION descriptor has the following layout:
* u32 Attributes;
* u16 FilePathListLength;
* u16 Description[];
* efi_device_path_protocol_t FilePathList[];
* u8 OptionalData[];
*
* This function validates and unpacks the variable-size data fields.
*/
static
bool efi_load_option_unpack(efi_load_option_unpacked_t *dest,
const efi_load_option_t *src, size_t size)
{
const void *pos;
u16 c;
efi_device_path_protocol_t header;
const efi_char16_t *description;
const efi_device_path_protocol_t *file_path_list;
if (size < offsetof(efi_load_option_t, variable_data))
return false;
pos = src->variable_data;
size -= offsetof(efi_load_option_t, variable_data);
if ((src->attributes & ~EFI_LOAD_OPTION_MASK) != 0)
return false;
/* Scan description. */
description = pos;
do {
if (size < sizeof(c))
return false;
c = *(const u16 *)pos;
pos += sizeof(c);
size -= sizeof(c);
} while (c != L'\0');
/* Scan file_path_list. */
file_path_list = pos;
do {
if (size < sizeof(header))
return false;
header = *(const efi_device_path_protocol_t *)pos;
if (header.length < sizeof(header))
return false;
if (size < header.length)
return false;
pos += header.length;
size -= header.length;
} while ((header.type != EFI_DEV_END_PATH && header.type != EFI_DEV_END_PATH2) ||
(header.sub_type != EFI_DEV_END_ENTIRE));
if (pos != (const void *)file_path_list + src->file_path_list_length)
return false;
dest->attributes = src->attributes;
dest->file_path_list_length = src->file_path_list_length;
dest->description = description;
dest->file_path_list = file_path_list;
dest->optional_data_size = size;
dest->optional_data = size ? pos : NULL;
return true;
}
/*
* At least some versions of Dell firmware pass the entire contents of the
* Boot#### variable, i.e. the EFI_LOAD_OPTION descriptor, rather than just the
* OptionalData field.
*
* Detect this case and extract OptionalData.
*/
void efi_apply_loadoptions_quirk(const void **load_options, u32 *load_options_size)
{
const efi_load_option_t *load_option = *load_options;
efi_load_option_unpacked_t load_option_unpacked;
if (!IS_ENABLED(CONFIG_X86))
return;
if (!load_option)
return;
if (*load_options_size < sizeof(*load_option))
return;
if ((load_option->attributes & ~EFI_LOAD_OPTION_BOOT_MASK) != 0)
return;
if (!efi_load_option_unpack(&load_option_unpacked, load_option, *load_options_size))
return;
efi_warn_once(FW_BUG "LoadOptions is an EFI_LOAD_OPTION descriptor\n");
efi_warn_once(FW_BUG "Using OptionalData as a workaround\n");
*load_options = load_option_unpacked.optional_data;
*load_options_size = load_option_unpacked.optional_data_size;
}
enum efistub_event_type {
EFISTUB_EVT_INITRD,
EFISTUB_EVT_LOAD_OPTIONS,
EFISTUB_EVT_COUNT,
};
#define STR_WITH_SIZE(s) sizeof(s), s
static const struct {
u32 pcr_index;
u32 event_id;
u32 event_data_len;
u8 event_data[52];
} events[] = {
[EFISTUB_EVT_INITRD] = {
9,
INITRD_EVENT_TAG_ID,
STR_WITH_SIZE("Linux initrd")
},
[EFISTUB_EVT_LOAD_OPTIONS] = {
9,
LOAD_OPTIONS_EVENT_TAG_ID,
STR_WITH_SIZE("LOADED_IMAGE::LoadOptions")
},
};
static_assert(sizeof(efi_tcg2_event_t) == sizeof(efi_cc_event_t));
union efistub_event {
efi_tcg2_event_t tcg2_data;
efi_cc_event_t cc_data;
};
struct efistub_measured_event {
union efistub_event event_data;
TCG_PCClientTaggedEvent tagged_event __packed;
};
static efi_status_t efi_measure_tagged_event(unsigned long load_addr,
unsigned long load_size,
enum efistub_event_type event)
{
union {
efi_status_t
(__efiapi *hash_log_extend_event)(void *, u64, efi_physical_addr_t,
u64, const union efistub_event *);
struct { u32 hash_log_extend_event; } mixed_mode;
} method;
struct efistub_measured_event *evt __free(efi_pool) = NULL;
int size = struct_size(evt, tagged_event.tagged_event_data,
events[event].event_data_len);
efi_guid_t tcg2_guid = EFI_TCG2_PROTOCOL_GUID;
efi_tcg2_protocol_t *tcg2 = NULL;
union efistub_event ev;
efi_status_t status;
void *protocol;
efi_bs_call(locate_protocol, &tcg2_guid, NULL, (void **)&tcg2);
if (tcg2) {
ev.tcg2_data = (struct efi_tcg2_event){
.event_size = size,
.event_header.header_size = sizeof(ev.tcg2_data.event_header),
.event_header.header_version = EFI_TCG2_EVENT_HEADER_VERSION,
.event_header.pcr_index = events[event].pcr_index,
.event_header.event_type = EV_EVENT_TAG,
};
protocol = tcg2;
method.hash_log_extend_event =
(void *)efi_table_attr(tcg2, hash_log_extend_event);
} else {
efi_guid_t cc_guid = EFI_CC_MEASUREMENT_PROTOCOL_GUID;
efi_cc_protocol_t *cc = NULL;
efi_bs_call(locate_protocol, &cc_guid, NULL, (void **)&cc);
if (!cc)
return EFI_UNSUPPORTED;
ev.cc_data = (struct efi_cc_event){
.event_size = size,
.event_header.header_size = sizeof(ev.cc_data.event_header),
.event_header.header_version = EFI_CC_EVENT_HEADER_VERSION,
.event_header.event_type = EV_EVENT_TAG,
};
status = efi_call_proto(cc, map_pcr_to_mr_index,
events[event].pcr_index,
&ev.cc_data.event_header.mr_index);
if (status != EFI_SUCCESS)
goto fail;
protocol = cc;
method.hash_log_extend_event =
(void *)efi_table_attr(cc, hash_log_extend_event);
}
status = efi_bs_call(allocate_pool, EFI_LOADER_DATA, size, (void **)&evt);
if (status != EFI_SUCCESS)
goto fail;
*evt = (struct efistub_measured_event) {
.event_data = ev,
.tagged_event.tagged_event_id = events[event].event_id,
.tagged_event.tagged_event_data_size = events[event].event_data_len,
};
memcpy(evt->tagged_event.tagged_event_data, events[event].event_data,
events[event].event_data_len);
status = efi_fn_call(&method, hash_log_extend_event, protocol, 0,
load_addr, load_size, &evt->event_data);
if (status == EFI_SUCCESS)
return EFI_SUCCESS;
fail:
efi_warn("Failed to measure data for event %d: 0x%lx\n", event, status);
return status;
}
/*
* Convert the unicode UEFI command line to ASCII to pass to kernel.
* Size of memory allocated return in *cmd_line_len.
* Returns NULL on error.
*/
char *efi_convert_cmdline(efi_loaded_image_t *image)
{
const efi_char16_t *options = efi_table_attr(image, load_options);
u32 options_size = efi_table_attr(image, load_options_size);
int options_bytes = 0, safe_options_bytes = 0; /* UTF-8 bytes */
unsigned long cmdline_addr = 0;
const efi_char16_t *s2;
bool in_quote = false;
efi_status_t status;
u32 options_chars;
if (options_size > 0)
efi_measure_tagged_event((unsigned long)options, options_size,
EFISTUB_EVT_LOAD_OPTIONS);
efi_apply_loadoptions_quirk((const void **)&options, &options_size);
options_chars = options_size / sizeof(efi_char16_t);
if (options) {
s2 = options;
while (options_bytes < COMMAND_LINE_SIZE && options_chars--) {
efi_char16_t c = *s2++;
if (c < 0x80) {
if (c == L'\0' || c == L'\n')
break;
if (c == L'"')
in_quote = !in_quote;
else if (!in_quote && isspace((char)c))
safe_options_bytes = options_bytes;
options_bytes++;
continue;
}
/*
* Get the number of UTF-8 bytes corresponding to a
* UTF-16 character.
* The first part handles everything in the BMP.
*/
options_bytes += 2 + (c >= 0x800);
/*
* Add one more byte for valid surrogate pairs. Invalid
* surrogates will be replaced with 0xfffd and take up
* only 3 bytes.
*/
if ((c & 0xfc00) == 0xd800) {
/*
* If the very last word is a high surrogate,
* we must ignore it since we can't access the
* low surrogate.
*/
if (!options_chars) {
options_bytes -= 3;
} else if ((*s2 & 0xfc00) == 0xdc00) {
options_bytes++;
options_chars--;
s2++;
}
}
}
if (options_bytes >= COMMAND_LINE_SIZE) {
options_bytes = safe_options_bytes;
efi_err("Command line is too long: truncated to %d bytes\n",
options_bytes);
}
}
options_bytes++; /* NUL termination */
status = efi_bs_call(allocate_pool, EFI_LOADER_DATA, options_bytes,
(void **)&cmdline_addr);
if (status != EFI_SUCCESS)
return NULL;
snprintf((char *)cmdline_addr, options_bytes, "%.*ls",
options_bytes - 1, options);
return (char *)cmdline_addr;
}
/**
* efi_exit_boot_services() - Exit boot services
* @handle: handle of the exiting image
* @priv: argument to be passed to @priv_func
* @priv_func: function to process the memory map before exiting boot services
*
* Handle calling ExitBootServices according to the requirements set out by the
* spec. Obtains the current memory map, and returns that info after calling
* ExitBootServices. The client must specify a function to perform any
* processing of the memory map data prior to ExitBootServices. A client
* specific structure may be passed to the function via priv. The client
* function may be called multiple times.
*
* Return: status code
*/
efi_status_t efi_exit_boot_services(void *handle, void *priv,
efi_exit_boot_map_processing priv_func)
{
struct efi_boot_memmap *map;
efi_status_t status;
if (efi_disable_pci_dma)
efi_pci_disable_bridge_busmaster();
status = efi_get_memory_map(&map, true);
if (status != EFI_SUCCESS)
return status;
status = priv_func(map, priv);
if (status != EFI_SUCCESS) {
efi_bs_call(free_pool, map);
return status;
}
status = efi_bs_call(exit_boot_services, handle, map->map_key);
if (status == EFI_INVALID_PARAMETER) {
/*
* The memory map changed between efi_get_memory_map() and
* exit_boot_services(). Per the UEFI Spec v2.6, Section 6.4:
* EFI_BOOT_SERVICES.ExitBootServices we need to get the
* updated map, and try again. The spec implies one retry
* should be sufficent, which is confirmed against the EDK2
* implementation. Per the spec, we can only invoke
* get_memory_map() and exit_boot_services() - we cannot alloc
* so efi_get_memory_map() cannot be used, and we must reuse
* the buffer. For all practical purposes, the headroom in the
* buffer should account for any changes in the map so the call
* to get_memory_map() is expected to succeed here.
*/
map->map_size = map->buff_size;
status = efi_bs_call(get_memory_map,
&map->map_size,
&map->map,
&map->map_key,
&map->desc_size,
&map->desc_ver);
/* exit_boot_services() was called, thus cannot free */
if (status != EFI_SUCCESS)
return status;
status = priv_func(map, priv);
/* exit_boot_services() was called, thus cannot free */
if (status != EFI_SUCCESS)
return status;
status = efi_bs_call(exit_boot_services, handle, map->map_key);
}
return status;
}
/**
* get_efi_config_table() - retrieve UEFI configuration table
* @guid: GUID of the configuration table to be retrieved
* Return: pointer to the configuration table or NULL
*/
void *get_efi_config_table(efi_guid_t guid)
{
unsigned long tables = efi_table_attr(efi_system_table, tables);
int nr_tables = efi_table_attr(efi_system_table, nr_tables);
int i;
for (i = 0; i < nr_tables; i++) {
efi_config_table_t *t = (void *)tables;
if (efi_guidcmp(t->guid, guid) == 0)
return efi_table_attr(t, table);
tables += efi_is_native() ? sizeof(efi_config_table_t)
: sizeof(efi_config_table_32_t);
}
return NULL;
}
/*
* The LINUX_EFI_INITRD_MEDIA_GUID vendor media device path below provides a way
* for the firmware or bootloader to expose the initrd data directly to the stub
* via the trivial LoadFile2 protocol, which is defined in the UEFI spec, and is
* very easy to implement. It is a simple Linux initrd specific conduit between
* kernel and firmware, allowing us to put the EFI stub (being part of the
* kernel) in charge of where and when to load the initrd, while leaving it up
* to the firmware to decide whether it needs to expose its filesystem hierarchy
* via EFI protocols.
*/
static const struct {
struct efi_vendor_dev_path vendor;
struct efi_generic_dev_path end;
} __packed initrd_dev_path = {
{
{
EFI_DEV_MEDIA,
EFI_DEV_MEDIA_VENDOR,
sizeof(struct efi_vendor_dev_path),
},
LINUX_EFI_INITRD_MEDIA_GUID
}, {
EFI_DEV_END_PATH,
EFI_DEV_END_ENTIRE,
sizeof(struct efi_generic_dev_path)
}
};
/**
* efi_load_initrd_dev_path() - load the initrd from the Linux initrd device path
* @initrd: pointer of struct to store the address where the initrd was loaded
* and the size of the loaded initrd
* @max: upper limit for the initrd memory allocation
*
* Return:
* * %EFI_SUCCESS if the initrd was loaded successfully, in which
* case @load_addr and @load_size are assigned accordingly
* * %EFI_NOT_FOUND if no LoadFile2 protocol exists on the initrd device path
* * %EFI_OUT_OF_RESOURCES if memory allocation failed
* * %EFI_LOAD_ERROR in all other cases
*/
static
efi_status_t efi_load_initrd_dev_path(struct linux_efi_initrd *initrd,
unsigned long max)
{
efi_guid_t lf2_proto_guid = EFI_LOAD_FILE2_PROTOCOL_GUID;
efi_device_path_protocol_t *dp;
efi_load_file2_protocol_t *lf2;
efi_handle_t handle;
efi_status_t status;
dp = (efi_device_path_protocol_t *)&initrd_dev_path;
status = efi_bs_call(locate_device_path, &lf2_proto_guid, &dp, &handle);
if (status != EFI_SUCCESS)
return status;
status = efi_bs_call(handle_protocol, handle, &lf2_proto_guid,
(void **)&lf2);
if (status != EFI_SUCCESS)
return status;
initrd->size = 0;
status = efi_call_proto(lf2, load_file, dp, false, &initrd->size, NULL);
if (status != EFI_BUFFER_TOO_SMALL)
return EFI_LOAD_ERROR;
status = efi_allocate_pages(initrd->size, &initrd->base, max);
if (status != EFI_SUCCESS)
return status;
status = efi_call_proto(lf2, load_file, dp, false, &initrd->size,
(void *)initrd->base);
if (status != EFI_SUCCESS) {
efi_free(initrd->size, initrd->base);
return EFI_LOAD_ERROR;
}
return EFI_SUCCESS;
}
static
efi_status_t efi_load_initrd_cmdline(efi_loaded_image_t *image,
struct linux_efi_initrd *initrd,
unsigned long soft_limit,
unsigned long hard_limit)
{
if (image == NULL)
return EFI_UNSUPPORTED;
return handle_cmdline_files(image, L"initrd=", sizeof(L"initrd=") - 2,
soft_limit, hard_limit,
&initrd->base, &initrd->size);
}
/**
* efi_load_initrd() - Load initial RAM disk
* @image: EFI loaded image protocol
* @soft_limit: preferred address for loading the initrd
* @hard_limit: upper limit address for loading the initrd
* @out: pointer to store the address of the initrd table
*
* Return: status code
*/
efi_status_t efi_load_initrd(efi_loaded_image_t *image,
unsigned long soft_limit,
unsigned long hard_limit,
const struct linux_efi_initrd **out)
{
efi_guid_t tbl_guid = LINUX_EFI_INITRD_MEDIA_GUID;
efi_status_t status = EFI_SUCCESS;
struct linux_efi_initrd initrd, *tbl;
if (!IS_ENABLED(CONFIG_BLK_DEV_INITRD) || efi_noinitrd)
return EFI_SUCCESS;
status = efi_load_initrd_dev_path(&initrd, hard_limit);
if (status == EFI_SUCCESS) {
efi_info("Loaded initrd from LINUX_EFI_INITRD_MEDIA_GUID device path\n");
} else if (status == EFI_NOT_FOUND) {
status = efi_load_initrd_cmdline(image, &initrd, soft_limit,
hard_limit);
/* command line loader disabled or no initrd= passed? */
if (status == EFI_UNSUPPORTED || status == EFI_NOT_READY)
return EFI_SUCCESS;
if (status == EFI_SUCCESS)
efi_info("Loaded initrd from command line option\n");
}
if (status != EFI_SUCCESS)
goto failed;
if (initrd.size > 0 &&
efi_measure_tagged_event(initrd.base, initrd.size,
EFISTUB_EVT_INITRD) == EFI_SUCCESS)
efi_info("Measured initrd data into PCR 9\n");
status = efi_bs_call(allocate_pool, EFI_LOADER_DATA, sizeof(initrd),
(void **)&tbl);
if (status != EFI_SUCCESS)
goto free_initrd;
*tbl = initrd;
status = efi_bs_call(install_configuration_table, &tbl_guid, tbl);
if (status != EFI_SUCCESS)
goto free_tbl;
if (out)
*out = tbl;
return EFI_SUCCESS;
free_tbl:
efi_bs_call(free_pool, tbl);
free_initrd:
efi_free(initrd.size, initrd.base);
failed:
efi_err("Failed to load initrd: 0x%lx\n", status);
return status;
}
/**
* efi_wait_for_key() - Wait for key stroke
* @usec: number of microseconds to wait for key stroke
* @key: key entered
*
* Wait for up to @usec microseconds for a key stroke.
*
* Return: status code, EFI_SUCCESS if key received
*/
efi_status_t efi_wait_for_key(unsigned long usec, efi_input_key_t *key)
{
efi_event_t events[2], timer;
unsigned long index;
efi_simple_text_input_protocol_t *con_in;
efi_status_t status;
con_in = efi_table_attr(efi_system_table, con_in);
if (!con_in)
return EFI_UNSUPPORTED;
efi_set_event_at(events, 0, efi_table_attr(con_in, wait_for_key));
status = efi_bs_call(create_event, EFI_EVT_TIMER, 0, NULL, NULL, &timer);
if (status != EFI_SUCCESS)
return status;
status = efi_bs_call(set_timer, timer, EfiTimerRelative,
EFI_100NSEC_PER_USEC * usec);
if (status != EFI_SUCCESS)
return status;
efi_set_event_at(events, 1, timer);
status = efi_bs_call(wait_for_event, 2, events, &index);
if (status == EFI_SUCCESS) {
if (index == 0)
status = efi_call_proto(con_in, read_keystroke, key);
else
status = EFI_TIMEOUT;
}
efi_bs_call(close_event, timer);
return status;
}
/**
* efi_remap_image - Remap a loaded image with the appropriate permissions
* for code and data
*
* @image_base: the base of the image in memory
* @alloc_size: the size of the area in memory occupied by the image
* @code_size: the size of the leading part of the image containing code
* and read-only data
*
* efi_remap_image() uses the EFI memory attribute protocol to remap the code
* region of the loaded image read-only/executable, and the remainder
* read-write/non-executable. The code region is assumed to start at the base
* of the image, and will therefore cover the PE/COFF header as well.
*/
void efi_remap_image(unsigned long image_base, unsigned alloc_size,
unsigned long code_size)
{
efi_guid_t guid = EFI_MEMORY_ATTRIBUTE_PROTOCOL_GUID;
efi_memory_attribute_protocol_t *memattr;
efi_status_t status;
u64 attr;
/*
* If the firmware implements the EFI_MEMORY_ATTRIBUTE_PROTOCOL, let's
* invoke it to remap the text/rodata region of the decompressed image
* as read-only and the data/bss region as non-executable.
*/
status = efi_bs_call(locate_protocol, &guid, NULL, (void **)&memattr);
if (status != EFI_SUCCESS)
return;
// Get the current attributes for the entire region
status = memattr->get_memory_attributes(memattr, image_base,
alloc_size, &attr);
if (status != EFI_SUCCESS) {
efi_warn("Failed to retrieve memory attributes for image region: 0x%lx\n",
status);
return;
}
// Mark the code region as read-only
status = memattr->set_memory_attributes(memattr, image_base, code_size,
EFI_MEMORY_RO);
if (status != EFI_SUCCESS) {
efi_warn("Failed to remap code region read-only\n");
return;
}
// If the entire region was already mapped as non-exec, clear the
// attribute from the code region. Otherwise, set it on the data
// region.
if (attr & EFI_MEMORY_XP) {
status = memattr->clear_memory_attributes(memattr, image_base,
code_size,
EFI_MEMORY_XP);
if (status != EFI_SUCCESS)
efi_warn("Failed to remap code region executable\n");
} else {
status = memattr->set_memory_attributes(memattr,
image_base + code_size,
alloc_size - code_size,
EFI_MEMORY_XP);
if (status != EFI_SUCCESS)
efi_warn("Failed to remap data region non-executable\n");
}
}
@@ -0,0 +1,280 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* EFI stub implementation that is shared by arm and arm64 architectures.
* This should be #included by the EFI stub implementation files.
*
* Copyright (C) 2013,2014 Linaro Limited
* Roy Franz <roy.franz@linaro.org
* Copyright (C) 2013 Red Hat, Inc.
* Mark Salter <msalter@redhat.com>
*/
#include <linux/efi.h>
#include <linux/sysfb.h>
#include <asm/efi.h>
#include "efistub.h"
/*
* This is the base address at which to start allocating virtual memory ranges
* 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
* be mapped efficiently.
* 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
/*
* Some architectures map the EFI regions into the kernel's linear map using a
* fixed offset.
*/
#ifndef EFI_RT_VIRTUAL_OFFSET
#define EFI_RT_VIRTUAL_OFFSET 0
#endif
static u64 virtmap_base = EFI_RT_VIRTUAL_BASE;
static bool flat_va_mapping = (EFI_RT_VIRTUAL_OFFSET != 0);
void __weak free_primary_display(struct sysfb_display_info *dpy)
{ }
static struct sysfb_display_info *setup_primary_display(void)
{
struct sysfb_display_info *dpy;
struct screen_info *screen = NULL;
struct edid_info *edid = NULL;
efi_status_t status;
dpy = alloc_primary_display();
if (!dpy)
return NULL;
screen = &dpy->screen;
#if defined(CONFIG_FIRMWARE_EDID)
edid = &dpy->edid;
#endif
status = efi_setup_graphics(screen, edid);
if (status != EFI_SUCCESS)
goto err_free_primary_display;
return dpy;
err_free_primary_display:
free_primary_display(dpy);
return NULL;
}
static void install_memreserve_table(void)
{
struct linux_efi_memreserve *rsv;
efi_guid_t memreserve_table_guid = LINUX_EFI_MEMRESERVE_TABLE_GUID;
efi_status_t status;
status = efi_bs_call(allocate_pool, EFI_LOADER_DATA, sizeof(*rsv),
(void **)&rsv);
if (status != EFI_SUCCESS) {
efi_err("Failed to allocate memreserve entry!\n");
return;
}
rsv->next = 0;
rsv->size = 0;
atomic_set(&rsv->count, 0);
status = efi_bs_call(install_configuration_table,
&memreserve_table_guid, rsv);
if (status != EFI_SUCCESS)
efi_err("Failed to install memreserve config table!\n");
}
static u32 get_supported_rt_services(void)
{
const efi_rt_properties_table_t *rt_prop_table;
u32 supported = EFI_RT_SUPPORTED_ALL;
rt_prop_table = get_efi_config_table(EFI_RT_PROPERTIES_TABLE_GUID);
if (rt_prop_table)
supported &= rt_prop_table->runtime_services_supported;
return supported;
}
efi_status_t efi_handle_cmdline(efi_loaded_image_t *image, char **cmdline_ptr)
{
char *cmdline __free(efi_pool) = NULL;
efi_status_t status;
/*
* 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 = efi_convert_cmdline(image);
if (!cmdline) {
efi_err("getting command line via LOADED_IMAGE_PROTOCOL\n");
return EFI_OUT_OF_RESOURCES;
}
if (!IS_ENABLED(CONFIG_CMDLINE_FORCE)) {
status = efi_parse_options(cmdline);
if (status != EFI_SUCCESS) {
efi_err("Failed to parse EFI load options\n");
return status;
}
}
if (IS_ENABLED(CONFIG_CMDLINE_EXTEND) ||
IS_ENABLED(CONFIG_CMDLINE_FORCE) ||
cmdline[0] == 0) {
status = efi_parse_options(CONFIG_CMDLINE);
if (status != EFI_SUCCESS) {
efi_err("Failed to parse built-in command line\n");
return status;
}
}
*cmdline_ptr = no_free_ptr(cmdline);
return EFI_SUCCESS;
}
efi_status_t efi_stub_common(efi_handle_t handle,
efi_loaded_image_t *image,
unsigned long image_addr,
char *cmdline_ptr)
{
struct sysfb_display_info *dpy;
efi_status_t status;
status = check_platform_features();
if (status != EFI_SUCCESS)
return status;
dpy = setup_primary_display();
efi_retrieve_eventlog();
/* Ask the firmware to clear memory on unclean shutdown */
efi_enable_reset_attack_mitigation();
efi_load_initrd(image, ULONG_MAX, efi_get_max_initrd_addr(image_addr),
NULL);
efi_random_get_seed();
/* force efi_novamap if SetVirtualAddressMap() is unsupported */
efi_novamap |= !(get_supported_rt_services() &
EFI_RT_SUPPORTED_SET_VIRTUAL_ADDRESS_MAP);
install_memreserve_table();
status = efi_boot_kernel(handle, image, image_addr, cmdline_ptr);
free_primary_display(dpy);
return status;
}
/*
* efi_allocate_virtmap() - create a pool allocation for the virtmap
*
* Create an allocation that is of sufficient size to hold all the memory
* descriptors that will be passed to SetVirtualAddressMap() to inform the
* firmware about the virtual mapping that will be used under the OS to call
* into the firmware.
*/
efi_status_t efi_alloc_virtmap(efi_memory_desc_t **virtmap,
unsigned long *desc_size, u32 *desc_ver)
{
unsigned long size, mmap_key;
efi_status_t status;
/*
* Use the size of the current memory map as an upper bound for the
* size of the buffer we need to pass to SetVirtualAddressMap() to
* cover all EFI_MEMORY_RUNTIME regions.
*/
size = 0;
status = efi_bs_call(get_memory_map, &size, NULL, &mmap_key, desc_size,
desc_ver);
if (status != EFI_BUFFER_TOO_SMALL)
return EFI_LOAD_ERROR;
return efi_bs_call(allocate_pool, EFI_LOADER_DATA, size,
(void **)virtmap);
}
/*
* efi_get_virtmap() - create a virtual mapping for the EFI memory map
*
* This function populates the virt_addr fields of all memory region descriptors
* in @memory_map whose EFI_MEMORY_RUNTIME attribute is set. Those descriptors
* are also copied to @runtime_map, and their total count is returned in @count.
*/
void efi_get_virtmap(efi_memory_desc_t *memory_map, unsigned long map_size,
unsigned long desc_size, efi_memory_desc_t *runtime_map,
int *count)
{
u64 efi_virt_base = virtmap_base;
efi_memory_desc_t *in, *out = runtime_map;
int l;
*count = 0;
for (l = 0; l < map_size; l += desc_size) {
u64 paddr, size;
in = (void *)memory_map + l;
if (!(in->attribute & EFI_MEMORY_RUNTIME))
continue;
paddr = in->phys_addr;
size = in->num_pages * EFI_PAGE_SIZE;
in->virt_addr = in->phys_addr + EFI_RT_VIRTUAL_OFFSET;
if (efi_novamap) {
continue;
}
/*
* Make the mapping compatible with 64k pages: this allows
* a 4k page size kernel to kexec a 64k page size kernel and
* vice versa.
*/
if (!flat_va_mapping) {
paddr = round_down(in->phys_addr, SZ_64K);
size += in->phys_addr - paddr;
/*
* Avoid wasting memory on PTEs by choosing a virtual
* base that is compatible with section mappings if this
* region has the appropriate size and physical
* alignment. (Sections are 2 MB on 4k granule kernels)
*/
if (IS_ALIGNED(in->phys_addr, SZ_2M) && size >= SZ_2M)
efi_virt_base = round_up(efi_virt_base, SZ_2M);
else
efi_virt_base = round_up(efi_virt_base, SZ_64K);
in->virt_addr += efi_virt_base - paddr;
efi_virt_base += size;
}
memcpy(out, in, desc_size);
out = (void *)out + desc_size;
++*count;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,379 @@
// SPDX-License-Identifier: GPL-2.0
/*
* FDT related Helper functions used by the EFI stub on multiple
* architectures. This should be #included by the EFI stub
* implementation files.
*
* Copyright 2013 Linaro Limited; author Roy Franz
*/
#include <linux/efi.h>
#include <linux/libfdt.h>
#include <asm/efi.h>
#include "efistub.h"
#define EFI_DT_ADDR_CELLS_DEFAULT 2
#define EFI_DT_SIZE_CELLS_DEFAULT 2
static void fdt_update_cell_size(void *fdt)
{
int offset;
offset = fdt_path_offset(fdt, "/");
/* Set the #address-cells and #size-cells values for an empty tree */
fdt_setprop_u32(fdt, offset, "#address-cells", EFI_DT_ADDR_CELLS_DEFAULT);
fdt_setprop_u32(fdt, offset, "#size-cells", EFI_DT_SIZE_CELLS_DEFAULT);
}
static efi_status_t update_fdt(void *orig_fdt, unsigned long orig_fdt_size,
void *fdt, int new_fdt_size, char *cmdline_ptr)
{
int node, num_rsv;
int status;
fdt32_t fdt_val32;
fdt64_t fdt_val64;
/* Do some checks on provided FDT, if it exists: */
if (orig_fdt) {
if (fdt_check_header(orig_fdt)) {
efi_err("Device Tree header not valid!\n");
return EFI_LOAD_ERROR;
}
/*
* We don't get the size of the FDT if we get if from a
* configuration table:
*/
if (orig_fdt_size && fdt_totalsize(orig_fdt) > orig_fdt_size) {
efi_err("Truncated device tree! foo!\n");
return EFI_LOAD_ERROR;
}
}
if (orig_fdt) {
status = fdt_open_into(orig_fdt, fdt, new_fdt_size);
} else {
status = fdt_create_empty_tree(fdt, new_fdt_size);
if (status == 0) {
/*
* Any failure from the following function is
* non-critical:
*/
fdt_update_cell_size(fdt);
}
}
if (status != 0)
goto fdt_set_fail;
/*
* Delete all memory reserve map entries. When booting via UEFI,
* kernel will use the UEFI memory map to find reserved regions.
*/
num_rsv = fdt_num_mem_rsv(fdt);
while (num_rsv-- > 0)
fdt_del_mem_rsv(fdt, num_rsv);
node = fdt_subnode_offset(fdt, 0, "chosen");
if (node < 0) {
node = fdt_add_subnode(fdt, 0, "chosen");
if (node < 0) {
/* 'node' is an error code when negative: */
status = node;
goto fdt_set_fail;
}
}
if (cmdline_ptr != NULL && strlen(cmdline_ptr) > 0) {
status = fdt_setprop(fdt, node, "bootargs", cmdline_ptr,
strlen(cmdline_ptr) + 1);
if (status)
goto fdt_set_fail;
}
/* Add FDT entries for EFI runtime services in chosen node. */
node = fdt_subnode_offset(fdt, 0, "chosen");
fdt_val64 = cpu_to_fdt64((u64)(unsigned long)efi_system_table);
status = fdt_setprop_var(fdt, node, "linux,uefi-system-table", fdt_val64);
if (status)
goto fdt_set_fail;
fdt_val64 = cpu_to_fdt64(U64_MAX); /* placeholder */
status = fdt_setprop_var(fdt, node, "linux,uefi-mmap-start", fdt_val64);
if (status)
goto fdt_set_fail;
fdt_val32 = cpu_to_fdt32(U32_MAX); /* placeholder */
status = fdt_setprop_var(fdt, node, "linux,uefi-mmap-size", fdt_val32);
if (status)
goto fdt_set_fail;
status = fdt_setprop_var(fdt, node, "linux,uefi-mmap-desc-size", fdt_val32);
if (status)
goto fdt_set_fail;
status = fdt_setprop_var(fdt, node, "linux,uefi-mmap-desc-ver", fdt_val32);
if (status)
goto fdt_set_fail;
if (IS_ENABLED(CONFIG_RANDOMIZE_BASE) && !efi_nokaslr) {
efi_status_t efi_status;
efi_status = efi_get_random_bytes(sizeof(fdt_val64),
(u8 *)&fdt_val64);
if (efi_status == EFI_SUCCESS) {
status = fdt_setprop_var(fdt, node, "kaslr-seed", fdt_val64);
if (status)
goto fdt_set_fail;
}
}
/* Shrink the FDT back to its minimum size: */
fdt_pack(fdt);
return EFI_SUCCESS;
fdt_set_fail:
if (status == -FDT_ERR_NOSPACE)
return EFI_BUFFER_TOO_SMALL;
return EFI_LOAD_ERROR;
}
static efi_status_t update_fdt_memmap(void *fdt, struct efi_boot_memmap *map)
{
int node = fdt_path_offset(fdt, "/chosen");
fdt64_t fdt_val64;
fdt32_t fdt_val32;
int err;
if (node < 0)
return EFI_LOAD_ERROR;
fdt_val64 = cpu_to_fdt64((unsigned long)map->map);
err = fdt_setprop_inplace_var(fdt, node, "linux,uefi-mmap-start", fdt_val64);
if (err)
return EFI_LOAD_ERROR;
fdt_val32 = cpu_to_fdt32(map->map_size);
err = fdt_setprop_inplace_var(fdt, node, "linux,uefi-mmap-size", fdt_val32);
if (err)
return EFI_LOAD_ERROR;
fdt_val32 = cpu_to_fdt32(map->desc_size);
err = fdt_setprop_inplace_var(fdt, node, "linux,uefi-mmap-desc-size", fdt_val32);
if (err)
return EFI_LOAD_ERROR;
fdt_val32 = cpu_to_fdt32(map->desc_ver);
err = fdt_setprop_inplace_var(fdt, node, "linux,uefi-mmap-desc-ver", fdt_val32);
if (err)
return EFI_LOAD_ERROR;
return EFI_SUCCESS;
}
struct exit_boot_struct {
struct efi_boot_memmap *boot_memmap;
efi_memory_desc_t *runtime_map;
int runtime_entry_count;
void *new_fdt_addr;
};
static efi_status_t exit_boot_func(struct efi_boot_memmap *map, void *priv)
{
struct exit_boot_struct *p = priv;
p->boot_memmap = map;
/*
* 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 update_fdt_memmap(p->new_fdt_addr, map);
}
#ifndef MAX_FDT_SIZE
# define MAX_FDT_SIZE SZ_2M
#endif
/*
* Allocate memory for a new FDT, then add EFI and commandline related fields
* to the FDT. This routine increases the FDT allocation size until the
* allocated memory is large enough. EFI allocations are in EFI_PAGE_SIZE
* granules, which are fixed at 4K bytes, so in most cases the first allocation
* should succeed. EFI boot services are exited at the end of this function.
* There must be no allocations between the get_memory_map() call and the
* exit_boot_services() call, so the exiting of boot services is very tightly
* tied to the creation of the FDT with the final memory map in it.
*/
static
efi_status_t allocate_new_fdt_and_exit_boot(void *handle,
efi_loaded_image_t *image,
unsigned long *new_fdt_addr,
char *cmdline_ptr)
{
unsigned long desc_size;
u32 desc_ver;
efi_status_t status;
struct exit_boot_struct priv;
unsigned long fdt_addr = 0;
unsigned long fdt_size = 0;
if (!efi_novamap) {
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;
}
}
/*
* Unauthenticated device tree data is a security hazard, so ignore
* 'dtb=' unless UEFI Secure Boot is disabled. We assume that secure
* boot is enabled if we can't determine its state.
*/
if (!IS_ENABLED(CONFIG_EFI_ARMSTUB_DTB_LOADER) ||
efi_get_secureboot() != efi_secureboot_mode_disabled) {
if (strstr(cmdline_ptr, "dtb="))
efi_err("Ignoring DTB from command line.\n");
} else {
status = efi_load_dtb(image, &fdt_addr, &fdt_size);
if (status != EFI_SUCCESS && status != EFI_NOT_READY) {
efi_err("Failed to load device tree!\n");
goto fail;
}
}
if (fdt_addr) {
efi_info("Using DTB from command line\n");
} else {
/* Look for a device tree configuration table entry. */
fdt_addr = (uintptr_t)get_fdt(&fdt_size);
if (fdt_addr)
efi_info("Using DTB from configuration table\n");
}
if (!fdt_addr)
efi_info("Generating empty DTB\n");
efi_info("Exiting boot services...\n");
status = efi_allocate_pages(MAX_FDT_SIZE, new_fdt_addr, ULONG_MAX);
if (status != EFI_SUCCESS) {
efi_err("Unable to allocate memory for new device tree.\n");
goto fail;
}
status = update_fdt((void *)fdt_addr, fdt_size,
(void *)*new_fdt_addr, MAX_FDT_SIZE, cmdline_ptr);
if (status != EFI_SUCCESS) {
efi_err("Unable to construct new device tree.\n");
goto fail_free_new_fdt;
}
priv.new_fdt_addr = (void *)*new_fdt_addr;
status = efi_exit_boot_services(handle, &priv, exit_boot_func);
if (status == EFI_SUCCESS) {
efi_set_virtual_address_map_t *svam;
if (efi_novamap)
return EFI_SUCCESS;
/* Install the new virtual address map */
svam = efi_system_table->runtime->set_virtual_address_map;
status = svam(priv.runtime_entry_count * desc_size, desc_size,
desc_ver, priv.runtime_map);
/*
* We are beyond the point of no return here, so if the call to
* SetVirtualAddressMap() failed, we need to signal that to the
* incoming kernel but proceed normally otherwise.
*/
if (status != EFI_SUCCESS) {
efi_memory_desc_t *p;
int l;
/*
* Set the virtual address field of all
* EFI_MEMORY_RUNTIME entries to U64_MAX. This will
* signal the incoming kernel that no virtual
* translation has been installed.
*/
for (l = 0; l < priv.boot_memmap->map_size;
l += priv.boot_memmap->desc_size) {
p = (void *)priv.boot_memmap->map + l;
if (p->attribute & EFI_MEMORY_RUNTIME)
p->virt_addr = U64_MAX;
}
}
return EFI_SUCCESS;
}
efi_err("Exit boot services failed.\n");
fail_free_new_fdt:
efi_free(MAX_FDT_SIZE, *new_fdt_addr);
fail:
efi_free(fdt_size, fdt_addr);
if (!efi_novamap)
efi_bs_call(free_pool, priv.runtime_map);
return EFI_LOAD_ERROR;
}
efi_status_t efi_boot_kernel(void *handle, efi_loaded_image_t *image,
unsigned long kernel_addr, char *cmdline_ptr)
{
unsigned long fdt_addr;
efi_status_t status;
status = allocate_new_fdt_and_exit_boot(handle, image, &fdt_addr,
cmdline_ptr);
if (status != EFI_SUCCESS) {
efi_err("Failed to update FDT and exit boot services\n");
return status;
}
if (IS_ENABLED(CONFIG_ARM))
efi_handle_post_ebs_state();
efi_enter_kernel(kernel_addr, fdt_addr, fdt_totalsize((void *)fdt_addr));
/* not reached */
}
void *get_fdt(unsigned long *fdt_size)
{
void *fdt;
fdt = get_efi_config_table(DEVICE_TREE_GUID);
if (!fdt)
return NULL;
if (fdt_check_header(fdt) != 0) {
efi_err("Invalid header detected on UEFI supplied FDT, ignoring ...\n");
return NULL;
}
*fdt_size = fdt_totalsize(fdt);
return fdt;
}
@@ -0,0 +1,331 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Helper functions used by the EFI stub on multiple
* architectures. This should be #included by the EFI stub
* implementation files.
*
* Copyright 2011 Intel Corporation; author Matt Fleming
*/
#include <linux/efi.h>
#include <asm/efi.h>
#include "efistub.h"
#define MAX_FILENAME_SIZE 256
/*
* Some firmware implementations have problems reading files in one go.
* A read chunk size of 1MB seems to work for most platforms.
*
* Unfortunately, reading files in chunks triggers *other* bugs on some
* platforms, so we provide a way to disable this workaround, which can
* be done by passing "efi=nochunk" on the EFI boot stub command line.
*
* If you experience issues with initrd images being corrupt it's worth
* trying efi=nochunk, but chunking is enabled by default on x86 because
* there are far more machines that require the workaround than those that
* break with it enabled.
*/
#define EFI_READ_CHUNK_SIZE SZ_1M
struct finfo {
efi_file_info_t info;
efi_char16_t filename[MAX_FILENAME_SIZE];
};
static efi_status_t efi_open_file(efi_file_protocol_t *volume,
struct finfo *fi,
efi_file_protocol_t **handle,
unsigned long *file_size)
{
efi_guid_t info_guid = EFI_FILE_INFO_ID;
efi_file_protocol_t *fh;
unsigned long info_sz;
efi_status_t status;
efi_char16_t *c;
/* Replace UNIX dir separators with EFI standard ones */
for (c = fi->filename; *c != L'\0'; c++) {
if (*c == L'/')
*c = L'\\';
}
status = efi_call_proto(volume, open, &fh, fi->filename,
EFI_FILE_MODE_READ, 0);
if (status != EFI_SUCCESS) {
efi_err("Failed to open file: %ls\n", fi->filename);
return status;
}
info_sz = sizeof(struct finfo);
status = efi_call_proto(fh, get_info, &info_guid, &info_sz, fi);
if (status != EFI_SUCCESS) {
efi_err("Failed to get file info\n");
efi_call_proto(fh, close);
return status;
}
*handle = fh;
*file_size = fi->info.file_size;
return EFI_SUCCESS;
}
static efi_status_t efi_open_volume(efi_loaded_image_t *image,
efi_file_protocol_t **fh)
{
efi_guid_t fs_proto = EFI_FILE_SYSTEM_GUID;
efi_simple_file_system_protocol_t *io;
efi_status_t status;
status = efi_bs_call(handle_protocol, efi_table_attr(image, device_handle),
&fs_proto, (void **)&io);
if (status != EFI_SUCCESS) {
efi_err("Failed to handle fs_proto\n");
return status;
}
status = efi_call_proto(io, open_volume, fh);
if (status != EFI_SUCCESS)
efi_err("Failed to open volume\n");
return status;
}
static int find_file_option(const efi_char16_t *cmdline, int cmdline_len,
const efi_char16_t *prefix, int prefix_size,
efi_char16_t *result, int result_len)
{
int prefix_len = prefix_size / 2;
bool found = false;
int i;
for (i = prefix_len; i < cmdline_len; i++) {
if (!memcmp(&cmdline[i - prefix_len], prefix, prefix_size)) {
found = true;
break;
}
}
if (!found)
return 0;
/* Skip any leading slashes */
while (i < cmdline_len && (cmdline[i] == L'/' || cmdline[i] == L'\\'))
i++;
while (--result_len > 0 && i < cmdline_len) {
efi_char16_t c = cmdline[i++];
if (c == L'\0' || c == L'\n' || c == L' ')
break;
*result++ = c;
}
*result = L'\0';
return i;
}
static efi_status_t efi_open_device_path(efi_file_protocol_t **volume,
struct finfo *fi)
{
efi_guid_t text_to_dp_guid = EFI_DEVICE_PATH_FROM_TEXT_PROTOCOL_GUID;
static efi_device_path_from_text_protocol_t *text_to_dp = NULL;
efi_guid_t fs_proto = EFI_FILE_SYSTEM_GUID;
efi_device_path_protocol_t *initrd_dp;
efi_simple_file_system_protocol_t *io;
struct efi_file_path_dev_path *fpath;
efi_handle_t handle;
efi_status_t status;
/* See if the text to device path protocol exists */
if (!text_to_dp &&
efi_bs_call(locate_protocol, &text_to_dp_guid, NULL,
(void **)&text_to_dp) != EFI_SUCCESS)
return EFI_UNSUPPORTED;
/* Convert the filename wide string into a device path */
initrd_dp = efi_fn_call(text_to_dp, convert_text_to_device_path,
fi->filename);
/* Check whether the device path in question implements simple FS */
if ((efi_bs_call(locate_device_path, &fs_proto, &initrd_dp, &handle) ?:
efi_bs_call(handle_protocol, handle, &fs_proto, (void **)&io))
!= EFI_SUCCESS)
return EFI_NOT_FOUND;
/* Check whether the remaining device path is a file device path */
if (initrd_dp->type != EFI_DEV_MEDIA ||
initrd_dp->sub_type != EFI_DEV_MEDIA_FILE) {
efi_warn("Unexpected device path node type: (%x, %x)\n",
initrd_dp->type, initrd_dp->sub_type);
return EFI_LOAD_ERROR;
}
/* Copy the remaining file path into the fi structure */
fpath = (struct efi_file_path_dev_path *)initrd_dp;
memcpy(fi->filename, fpath->filename,
min(sizeof(fi->filename),
fpath->header.length - sizeof(fpath->header)));
status = efi_call_proto(io, open_volume, volume);
if (status != EFI_SUCCESS)
efi_err("Failed to open volume\n");
return status;
}
#ifndef CONFIG_CMDLINE
#define CONFIG_CMDLINE
#endif
static const efi_char16_t builtin_cmdline[] = L"" CONFIG_CMDLINE;
/*
* Check the cmdline for a LILO-style file= arguments.
*
* We only support loading a file from the same filesystem as
* the kernel image.
*/
efi_status_t handle_cmdline_files(efi_loaded_image_t *image,
const efi_char16_t *optstr,
int optstr_size,
unsigned long soft_limit,
unsigned long hard_limit,
unsigned long *load_addr,
unsigned long *load_size)
{
const bool ignore_load_options = IS_ENABLED(CONFIG_CMDLINE_OVERRIDE) ||
IS_ENABLED(CONFIG_CMDLINE_FORCE);
const efi_char16_t *cmdline = efi_table_attr(image, load_options);
u32 cmdline_len = efi_table_attr(image, load_options_size);
unsigned long efi_chunk_size = ULONG_MAX;
efi_file_protocol_t *volume = NULL;
efi_file_protocol_t *file;
unsigned long alloc_addr;
unsigned long alloc_size;
efi_status_t status;
bool twopass;
int offset;
if (!load_addr || !load_size)
return EFI_INVALID_PARAMETER;
efi_apply_loadoptions_quirk((const void **)&cmdline, &cmdline_len);
cmdline_len /= sizeof(*cmdline);
if (IS_ENABLED(CONFIG_X86) && !efi_nochunk)
efi_chunk_size = EFI_READ_CHUNK_SIZE;
alloc_addr = alloc_size = 0;
if (!ignore_load_options && cmdline_len > 0) {
twopass = IS_ENABLED(CONFIG_CMDLINE_BOOL) ||
IS_ENABLED(CONFIG_CMDLINE_EXTEND);
} else {
do_builtin: cmdline = builtin_cmdline;
cmdline_len = ARRAY_SIZE(builtin_cmdline) - 1;
twopass = false;
}
do {
struct finfo fi;
unsigned long size;
void *addr;
offset = find_file_option(cmdline, cmdline_len,
optstr, optstr_size,
fi.filename, ARRAY_SIZE(fi.filename));
if (!offset)
break;
cmdline += offset;
cmdline_len -= offset;
status = efi_open_device_path(&volume, &fi);
if (status == EFI_UNSUPPORTED || status == EFI_NOT_FOUND)
/* try the volume that holds the kernel itself */
status = efi_open_volume(image, &volume);
if (status != EFI_SUCCESS)
goto err_free_alloc;
status = efi_open_file(volume, &fi, &file, &size);
if (status != EFI_SUCCESS)
goto err_close_volume;
/*
* Check whether the existing allocation can contain the next
* file. This condition will also trigger naturally during the
* first (and typically only) iteration of the loop, given that
* alloc_size == 0 in that case.
*/
if (round_up(alloc_size + size, EFI_ALLOC_ALIGN) >
round_up(alloc_size, EFI_ALLOC_ALIGN)) {
unsigned long old_addr = alloc_addr;
status = EFI_OUT_OF_RESOURCES;
if (soft_limit < hard_limit)
status = efi_allocate_pages(alloc_size + size,
&alloc_addr,
soft_limit);
if (status == EFI_OUT_OF_RESOURCES)
status = efi_allocate_pages(alloc_size + size,
&alloc_addr,
hard_limit);
if (status != EFI_SUCCESS) {
efi_err("Failed to allocate memory for files\n");
goto err_close_file;
}
if (old_addr != 0) {
/*
* This is not the first time we've gone
* around this loop, and so we are loading
* multiple files that need to be concatenated
* and returned in a single buffer.
*/
memcpy((void *)alloc_addr, (void *)old_addr, alloc_size);
efi_free(alloc_size, old_addr);
}
}
addr = (void *)alloc_addr + alloc_size;
alloc_size += size;
while (size) {
unsigned long chunksize = min(size, efi_chunk_size);
status = efi_call_proto(file, read, &chunksize, addr);
if (status != EFI_SUCCESS) {
efi_err("Failed to read file\n");
goto err_close_file;
}
addr += chunksize;
size -= chunksize;
}
efi_call_proto(file, close);
efi_call_proto(volume, close);
} while (offset > 0);
if (twopass)
goto do_builtin;
*load_addr = alloc_addr;
*load_size = alloc_size;
if (*load_size == 0)
return EFI_NOT_READY;
return EFI_SUCCESS;
err_close_file:
efi_call_proto(file, close);
err_close_volume:
efi_call_proto(volume, close);
err_free_alloc:
efi_free(alloc_size, alloc_addr);
return status;
}
@@ -0,0 +1,43 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/bitmap.h>
#include <linux/math.h>
#include <linux/minmax.h>
/*
* Common helper for find_next_bit() function family
* @FETCH: The expression that fetches and pre-processes each word of bitmap(s)
* @MUNGE: The expression that post-processes a word containing found bit (may be empty)
* @size: The bitmap size in bits
* @start: The bitnumber to start searching at
*/
#define FIND_NEXT_BIT(FETCH, MUNGE, size, start) \
({ \
unsigned long mask, idx, tmp, sz = (size), __start = (start); \
\
if (unlikely(__start >= sz)) \
goto out; \
\
mask = MUNGE(BITMAP_FIRST_WORD_MASK(__start)); \
idx = __start / BITS_PER_LONG; \
\
for (tmp = (FETCH) & mask; !tmp; tmp = (FETCH)) { \
if ((idx + 1) * BITS_PER_LONG >= sz) \
goto out; \
idx++; \
} \
\
sz = min(idx * BITS_PER_LONG + __ffs(MUNGE(tmp)), sz); \
out: \
sz; \
})
unsigned long _find_next_bit(const unsigned long *addr, unsigned long nbits, unsigned long start)
{
return FIND_NEXT_BIT(addr[idx], /* nop */, nbits, start);
}
unsigned long _find_next_zero_bit(const unsigned long *addr, unsigned long nbits,
unsigned long start)
{
return FIND_NEXT_BIT(~addr[idx], /* nop */, nbits, start);
}
@@ -0,0 +1,532 @@
// SPDX-License-Identifier: GPL-2.0
/* -----------------------------------------------------------------------
*
* Copyright 2011 Intel Corporation; author Matt Fleming
*
* ----------------------------------------------------------------------- */
#include <linux/bitops.h>
#include <linux/ctype.h>
#include <linux/efi.h>
#include <linux/screen_info.h>
#include <linux/string.h>
#include <asm/efi.h>
#include <asm/setup.h>
#include <video/edid.h>
#include "efistub.h"
enum efi_cmdline_option {
EFI_CMDLINE_NONE,
EFI_CMDLINE_MODE_NUM,
EFI_CMDLINE_RES,
EFI_CMDLINE_AUTO,
EFI_CMDLINE_LIST
};
static struct {
enum efi_cmdline_option option;
union {
u32 mode;
struct {
u32 width, height;
int format;
u8 depth;
} res;
};
} cmdline = { .option = EFI_CMDLINE_NONE };
static bool parse_modenum(char *option, char **next)
{
u32 m;
if (!strstarts(option, "mode="))
return false;
option += strlen("mode=");
m = simple_strtoull(option, &option, 0);
if (*option && *option++ != ',')
return false;
cmdline.option = EFI_CMDLINE_MODE_NUM;
cmdline.mode = m;
*next = option;
return true;
}
static bool parse_res(char *option, char **next)
{
u32 w, h, d = 0;
int pf = -1;
if (!isdigit(*option))
return false;
w = simple_strtoull(option, &option, 10);
if (*option++ != 'x' || !isdigit(*option))
return false;
h = simple_strtoull(option, &option, 10);
if (*option == '-') {
option++;
if (strstarts(option, "rgb")) {
option += strlen("rgb");
pf = PIXEL_RGB_RESERVED_8BIT_PER_COLOR;
} else if (strstarts(option, "bgr")) {
option += strlen("bgr");
pf = PIXEL_BGR_RESERVED_8BIT_PER_COLOR;
} else if (isdigit(*option))
d = simple_strtoull(option, &option, 10);
else
return false;
}
if (*option && *option++ != ',')
return false;
cmdline.option = EFI_CMDLINE_RES;
cmdline.res.width = w;
cmdline.res.height = h;
cmdline.res.format = pf;
cmdline.res.depth = d;
*next = option;
return true;
}
static bool parse_auto(char *option, char **next)
{
if (!strstarts(option, "auto"))
return false;
option += strlen("auto");
if (*option && *option++ != ',')
return false;
cmdline.option = EFI_CMDLINE_AUTO;
*next = option;
return true;
}
static bool parse_list(char *option, char **next)
{
if (!strstarts(option, "list"))
return false;
option += strlen("list");
if (*option && *option++ != ',')
return false;
cmdline.option = EFI_CMDLINE_LIST;
*next = option;
return true;
}
void efi_parse_option_graphics(char *option)
{
while (*option) {
if (parse_modenum(option, &option))
continue;
if (parse_res(option, &option))
continue;
if (parse_auto(option, &option))
continue;
if (parse_list(option, &option))
continue;
while (*option && *option++ != ',')
;
}
}
static u32 choose_mode_modenum(efi_graphics_output_protocol_t *gop)
{
efi_graphics_output_mode_info_t *info __free(efi_pool) = NULL;
efi_graphics_output_protocol_mode_t *mode;
unsigned long info_size;
u32 max_mode, cur_mode;
efi_status_t status;
int pf;
mode = efi_table_attr(gop, mode);
cur_mode = efi_table_attr(mode, mode);
if (cmdline.mode == cur_mode)
return cur_mode;
max_mode = efi_table_attr(mode, max_mode);
if (cmdline.mode >= max_mode) {
efi_err("Requested mode is invalid\n");
return cur_mode;
}
status = efi_call_proto(gop, query_mode, cmdline.mode, &info_size, &info);
if (status != EFI_SUCCESS) {
efi_err("Couldn't get mode information\n");
return cur_mode;
}
pf = info->pixel_format;
if (pf == PIXEL_BLT_ONLY || pf >= PIXEL_FORMAT_MAX) {
efi_err("Invalid PixelFormat\n");
return cur_mode;
}
return cmdline.mode;
}
static u32 choose_mode(efi_graphics_output_protocol_t *gop,
bool (*match)(const efi_graphics_output_mode_info_t *, u32, void *),
void *ctx)
{
efi_graphics_output_protocol_mode_t *mode = efi_table_attr(gop, mode);
u32 max_mode = efi_table_attr(mode, max_mode);
for (u32 m = 0; m < max_mode; m++) {
efi_graphics_output_mode_info_t *info __free(efi_pool) = NULL;
unsigned long info_size;
efi_status_t status;
status = efi_call_proto(gop, query_mode, m, &info_size, &info);
if (status != EFI_SUCCESS)
continue;
if (match(info, m, ctx))
return m;
}
return (unsigned long)ctx;
}
static u8 pixel_bpp(int pixel_format, efi_pixel_bitmask_t pixel_info)
{
if (pixel_format == PIXEL_BIT_MASK) {
u32 mask = pixel_info.red_mask | pixel_info.green_mask |
pixel_info.blue_mask | pixel_info.reserved_mask;
if (!mask)
return 0;
return __fls(mask) - __ffs(mask) + 1;
} else
return 32;
}
static bool match_res(const efi_graphics_output_mode_info_t *info, u32 mode, void *ctx)
{
efi_pixel_bitmask_t pi = info->pixel_information;
int pf = info->pixel_format;
if (pf == PIXEL_BLT_ONLY || pf >= PIXEL_FORMAT_MAX)
return false;
return cmdline.res.width == info->horizontal_resolution &&
cmdline.res.height == info->vertical_resolution &&
(cmdline.res.format < 0 || cmdline.res.format == pf) &&
(!cmdline.res.depth || cmdline.res.depth == pixel_bpp(pf, pi));
}
static u32 choose_mode_res(efi_graphics_output_protocol_t *gop)
{
efi_graphics_output_protocol_mode_t *mode = efi_table_attr(gop, mode);
unsigned long cur_mode = efi_table_attr(mode, mode);
if (match_res(efi_table_attr(mode, info), cur_mode, NULL))
return cur_mode;
return choose_mode(gop, match_res, (void *)cur_mode);
}
struct match {
u32 mode;
u32 area;
u8 depth;
};
static bool match_auto(const efi_graphics_output_mode_info_t *info, u32 mode, void *ctx)
{
u32 area = info->horizontal_resolution * info->vertical_resolution;
efi_pixel_bitmask_t pi = info->pixel_information;
int pf = info->pixel_format;
u8 depth = pixel_bpp(pf, pi);
struct match *m = ctx;
if (pf == PIXEL_BLT_ONLY || pf >= PIXEL_FORMAT_MAX)
return false;
if (area > m->area || (area == m->area && depth > m->depth))
*m = (struct match){ mode, area, depth };
return false;
}
static u32 choose_mode_auto(efi_graphics_output_protocol_t *gop)
{
struct match match = {};
choose_mode(gop, match_auto, &match);
return match.mode;
}
static bool match_list(const efi_graphics_output_mode_info_t *info, u32 mode, void *ctx)
{
efi_pixel_bitmask_t pi = info->pixel_information;
u32 cur_mode = (unsigned long)ctx;
int pf = info->pixel_format;
const char *dstr;
u8 depth = 0;
bool valid;
valid = !(pf == PIXEL_BLT_ONLY || pf >= PIXEL_FORMAT_MAX);
switch (pf) {
case PIXEL_RGB_RESERVED_8BIT_PER_COLOR:
dstr = "rgb";
break;
case PIXEL_BGR_RESERVED_8BIT_PER_COLOR:
dstr = "bgr";
break;
case PIXEL_BIT_MASK:
dstr = "";
depth = pixel_bpp(pf, pi);
break;
case PIXEL_BLT_ONLY:
dstr = "blt";
break;
default:
dstr = "xxx";
break;
}
efi_printk("Mode %3u %c%c: Resolution %ux%u-%s%.0hhu\n",
mode,
(mode == cur_mode) ? '*' : ' ',
!valid ? '-' : ' ',
info->horizontal_resolution,
info->vertical_resolution,
dstr, depth);
return false;
}
static u32 choose_mode_list(efi_graphics_output_protocol_t *gop)
{
efi_graphics_output_protocol_mode_t *mode = efi_table_attr(gop, mode);
unsigned long cur_mode = efi_table_attr(mode, mode);
u32 max_mode = efi_table_attr(mode, max_mode);
efi_input_key_t key;
efi_status_t status;
efi_printk("Available graphics modes are 0-%u\n", max_mode-1);
efi_puts(" * = current mode\n"
" - = unusable mode\n");
choose_mode(gop, match_list, (void *)cur_mode);
efi_puts("\nPress any key to continue (or wait 10 seconds)\n");
status = efi_wait_for_key(10 * EFI_USEC_PER_SEC, &key);
if (status != EFI_SUCCESS && status != EFI_TIMEOUT) {
efi_err("Unable to read key, continuing in 10 seconds\n");
efi_bs_call(stall, 10 * EFI_USEC_PER_SEC);
}
return cur_mode;
}
static void set_mode(efi_graphics_output_protocol_t *gop)
{
efi_graphics_output_protocol_mode_t *mode;
u32 cur_mode, new_mode;
switch (cmdline.option) {
case EFI_CMDLINE_MODE_NUM:
new_mode = choose_mode_modenum(gop);
break;
case EFI_CMDLINE_RES:
new_mode = choose_mode_res(gop);
break;
case EFI_CMDLINE_AUTO:
new_mode = choose_mode_auto(gop);
break;
case EFI_CMDLINE_LIST:
new_mode = choose_mode_list(gop);
break;
default:
return;
}
mode = efi_table_attr(gop, mode);
cur_mode = efi_table_attr(mode, mode);
if (new_mode == cur_mode)
return;
if (efi_call_proto(gop, set_mode, new_mode) != EFI_SUCCESS)
efi_err("Failed to set requested mode\n");
}
static void find_bits(u32 mask, u8 *pos, u8 *size)
{
if (!mask) {
*pos = *size = 0;
return;
}
/* UEFI spec guarantees that the set bits are contiguous */
*pos = __ffs(mask);
*size = __fls(mask) - *pos + 1;
}
static void setup_screen_info(struct screen_info *si, const efi_graphics_output_protocol_t *gop)
{
const efi_graphics_output_protocol_mode_t *mode = efi_table_attr(gop, mode);
const efi_graphics_output_mode_info_t *info = efi_table_attr(mode, info);
si->orig_video_isVGA = VIDEO_TYPE_EFI;
si->lfb_width = info->horizontal_resolution;
si->lfb_height = info->vertical_resolution;
efi_set_u64_split(efi_table_attr(mode, frame_buffer_base),
&si->lfb_base, &si->ext_lfb_base);
if (si->ext_lfb_base)
si->capabilities |= VIDEO_CAPABILITY_64BIT_BASE;
si->pages = 1;
if (info->pixel_format == PIXEL_BIT_MASK) {
find_bits(info->pixel_information.red_mask, &si->red_pos, &si->red_size);
find_bits(info->pixel_information.green_mask, &si->green_pos, &si->green_size);
find_bits(info->pixel_information.blue_mask, &si->blue_pos, &si->blue_size);
find_bits(info->pixel_information.reserved_mask, &si->rsvd_pos, &si->rsvd_size);
si->lfb_depth = si->red_size + si->green_size + si->blue_size + si->rsvd_size;
si->lfb_linelength = (info->pixels_per_scan_line * si->lfb_depth) / 8;
} else {
if (info->pixel_format == PIXEL_RGB_RESERVED_8BIT_PER_COLOR) {
si->red_pos = 0;
si->blue_pos = 16;
} else /* PIXEL_BGR_RESERVED_8BIT_PER_COLOR */ {
si->blue_pos = 0;
si->red_pos = 16;
}
si->green_pos = 8;
si->rsvd_pos = 24;
si->red_size = 8;
si->green_size = 8;
si->blue_size = 8;
si->rsvd_size = 8;
si->lfb_depth = 32;
si->lfb_linelength = info->pixels_per_scan_line * 4;
}
si->lfb_size = si->lfb_linelength * si->lfb_height;
si->capabilities |= VIDEO_CAPABILITY_SKIP_QUIRKS;
}
static void setup_edid_info(struct edid_info *edid, u32 gop_size_of_edid, u8 *gop_edid)
{
if (!gop_edid || gop_size_of_edid < 128)
memset(edid->dummy, 0, sizeof(edid->dummy));
else
memcpy(edid->dummy, gop_edid, min(gop_size_of_edid, sizeof(edid->dummy)));
}
static efi_handle_t find_handle_with_primary_gop(unsigned long num, const efi_handle_t handles[],
efi_graphics_output_protocol_t **found_gop)
{
efi_graphics_output_protocol_t *first_gop;
efi_handle_t h, first_gop_handle;
first_gop_handle = NULL;
first_gop = NULL;
for_each_efi_handle(h, handles, num) {
efi_status_t status;
efi_graphics_output_protocol_t *gop;
efi_graphics_output_protocol_mode_t *mode;
efi_graphics_output_mode_info_t *info;
void *dummy = NULL;
status = efi_bs_call(handle_protocol, h,
&EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID,
(void **)&gop);
if (status != EFI_SUCCESS)
continue;
mode = efi_table_attr(gop, mode);
info = efi_table_attr(mode, info);
if (info->pixel_format == PIXEL_BLT_ONLY ||
info->pixel_format >= PIXEL_FORMAT_MAX)
continue;
/*
* Systems that use the UEFI Console Splitter may
* provide multiple GOP devices, not all of which are
* backed by real hardware. The workaround is to search
* for a GOP implementing the ConOut protocol, and if
* one isn't found, to just fall back to the first GOP.
*
* Once we've found a GOP supporting ConOut,
* don't bother looking any further.
*/
status = efi_bs_call(handle_protocol, h,
&EFI_CONSOLE_OUT_DEVICE_GUID, &dummy);
if (status == EFI_SUCCESS) {
if (found_gop)
*found_gop = gop;
return h;
} else if (!first_gop_handle) {
first_gop_handle = h;
first_gop = gop;
}
}
if (found_gop)
*found_gop = first_gop;
return first_gop_handle;
}
efi_status_t efi_setup_graphics(struct screen_info *si, struct edid_info *edid)
{
efi_handle_t *handles __free(efi_pool) = NULL;
efi_handle_t handle;
efi_graphics_output_protocol_t *gop;
efi_status_t status;
unsigned long num;
status = efi_bs_call(locate_handle_buffer, EFI_LOCATE_BY_PROTOCOL,
&EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID, NULL, &num,
&handles);
if (status != EFI_SUCCESS)
return status;
handle = find_handle_with_primary_gop(num, handles, &gop);
if (!handle)
return EFI_NOT_FOUND;
/* Change mode if requested */
set_mode(gop);
/* EFI framebuffer */
if (si)
setup_screen_info(si, gop);
/* Display EDID for primary GOP */
if (edid) {
efi_edid_discovered_protocol_t *discovered_edid;
efi_edid_active_protocol_t *active_edid;
u32 gop_size_of_edid = 0;
u8 *gop_edid = NULL;
status = efi_bs_call(handle_protocol, handle, &EFI_EDID_ACTIVE_PROTOCOL_GUID,
(void **)&active_edid);
if (status == EFI_SUCCESS) {
gop_size_of_edid = efi_table_attr(active_edid, size_of_edid);
gop_edid = efi_table_attr(active_edid, edid);
} else {
status = efi_bs_call(handle_protocol, handle,
&EFI_EDID_DISCOVERED_PROTOCOL_GUID,
(void **)&discovered_edid);
if (status == EFI_SUCCESS) {
gop_size_of_edid = efi_table_attr(discovered_edid, size_of_edid);
gop_edid = efi_table_attr(discovered_edid, edid);
}
}
setup_edid_info(edid, gop_size_of_edid, gop_edid);
}
return EFI_SUCCESS;
}
@@ -0,0 +1,74 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/efi.h>
#include <asm/efi.h>
#include <asm/string.h>
#include "efistub.h"
#ifdef CONFIG_KASAN
#undef memcpy
#undef memmove
#undef memset
void *__memcpy(void *__dest, const void *__src, size_t __n) __alias(memcpy);
void *__memmove(void *__dest, const void *__src, size_t count) __alias(memmove);
void *__memset(void *s, int c, size_t count) __alias(memset);
#endif
static void *efistub_memmove(u8 *dst, const u8 *src, size_t len)
{
if (src > dst || dst >= (src + len))
for (size_t i = 0; i < len; i++)
dst[i] = src[i];
else
for (ssize_t i = len - 1; i >= 0; i--)
dst[i] = src[i];
return dst;
}
static void *efistub_memset(void *dst, int c, size_t len)
{
for (u8 *d = dst; len--; d++)
*d = c;
return dst;
}
void *memcpy(void *dst, const void *src, size_t len)
{
if (efi_table_attr(efi_system_table, boottime) == NULL)
return efistub_memmove(dst, src, len);
efi_bs_call(copy_mem, dst, src, len);
return dst;
}
extern void *memmove(void *dst, const void *src, size_t len) __alias(memcpy);
void *memset(void *dst, int c, size_t len)
{
if (efi_table_attr(efi_system_table, boottime) == NULL)
return efistub_memset(dst, c, 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;
}
@@ -0,0 +1,157 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Helper functions used by the EFI stub on multiple
* architectures to deal with physical address space randomization.
*/
#include <linux/efi.h>
#include "efistub.h"
/**
* efi_kaslr_get_phys_seed() - Get random seed for physical kernel KASLR
* @image_handle: Handle to the image
*
* If KASLR is not disabled, obtain a random seed using EFI_RNG_PROTOCOL
* that will be used to move the kernel physical mapping.
*
* Return: the random seed
*/
u32 efi_kaslr_get_phys_seed(efi_handle_t image_handle)
{
efi_guid_t li_fixed_proto = LINUX_EFI_LOADED_IMAGE_FIXED_GUID;
void *p;
if (!IS_ENABLED(CONFIG_RANDOMIZE_BASE))
return 0;
if (efi_nokaslr) {
efi_info("KASLR disabled on kernel command line\n");
} else if (efi_bs_call(handle_protocol, image_handle,
&li_fixed_proto, &p) == EFI_SUCCESS) {
efi_info("Image placement fixed by loader\n");
} else {
efi_status_t status;
u32 phys_seed;
status = efi_get_random_bytes(sizeof(phys_seed),
(u8 *)&phys_seed);
if (status == EFI_SUCCESS)
return phys_seed;
if (status == EFI_NOT_FOUND)
efi_info("EFI_RNG_PROTOCOL unavailable\n");
else
efi_err("efi_get_random_bytes() failed (0x%lx)\n", status);
efi_nokaslr = true;
}
return 0;
}
/*
* 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
* checking that the first and the last byte of the image are covered by the
* same EFI memory map entry.
*/
static bool check_image_region(u64 base, u64 size)
{
struct efi_boot_memmap *map __free(efi_pool) = NULL;
efi_status_t status;
bool ret = false;
int map_offset;
status = efi_get_memory_map(&map, false);
if (status != EFI_SUCCESS)
return false;
for (map_offset = 0; map_offset < map->map_size; map_offset += map->desc_size) {
efi_memory_desc_t *md = (void *)map->map + map_offset;
u64 end = md->phys_addr + md->num_pages * EFI_PAGE_SIZE;
/*
* Find the region that covers base, and return whether
* it covers base+size bytes.
*/
if (base >= md->phys_addr && base < end) {
ret = (base + size) <= end;
break;
}
}
return ret;
}
/**
* efi_kaslr_relocate_kernel() - Relocate the kernel (random if KASLR enabled)
* @image_addr: Pointer to the current kernel location
* @reserve_addr: Pointer to the relocated kernel location
* @reserve_size: Size of the relocated kernel
* @kernel_size: Size of the text + data
* @kernel_codesize: Size of the text
* @kernel_memsize: Size of the text + data + bss
* @phys_seed: Random seed used for the relocation
*
* If KASLR is not enabled, this function relocates the kernel to a fixed
* address (or leave it as its current location). If KASLR is enabled, the
* kernel physical location is randomized using the seed in parameter.
*
* Return: status code, EFI_SUCCESS if relocation is successful
*/
efi_status_t efi_kaslr_relocate_kernel(unsigned long *image_addr,
unsigned long *reserve_addr,
unsigned long *reserve_size,
unsigned long kernel_size,
unsigned long kernel_codesize,
unsigned long kernel_memsize,
u32 phys_seed)
{
efi_status_t status;
u64 min_kimg_align = efi_get_kimg_min_align();
if (IS_ENABLED(CONFIG_RANDOMIZE_BASE) && phys_seed != 0) {
/*
* If KASLR is enabled, and we have some randomness available,
* locate the kernel at a randomized offset in physical memory.
*/
status = efi_random_alloc(*reserve_size, min_kimg_align,
reserve_addr, phys_seed,
EFI_LOADER_CODE, 0, EFI_ALLOC_LIMIT);
if (status != EFI_SUCCESS)
efi_warn("efi_random_alloc() failed: 0x%lx\n", status);
} else {
status = EFI_OUT_OF_RESOURCES;
}
if (status != EFI_SUCCESS) {
if (!check_image_region(*image_addr, kernel_memsize)) {
efi_err("FIRMWARE BUG: Image BSS overlaps adjacent EFI memory region\n");
} else if (IS_ALIGNED(*image_addr, min_kimg_align) &&
(unsigned long)_end < EFI_ALLOC_LIMIT) {
/*
* Just execute from wherever we were loaded by the
* UEFI PE/COFF loader if the placement is suitable.
*/
*reserve_size = 0;
return EFI_SUCCESS;
}
status = efi_allocate_pages_aligned(*reserve_size, reserve_addr,
ULONG_MAX, min_kimg_align,
EFI_LOADER_CODE);
if (status != EFI_SUCCESS) {
efi_err("Failed to relocate kernel\n");
*reserve_size = 0;
return status;
}
}
memcpy((void *)*reserve_addr, (void *)*image_addr, kernel_size);
*image_addr = *reserve_addr;
efi_icache_sync(*image_addr, *image_addr + kernel_codesize);
efi_remap_image(*image_addr, *reserve_size, kernel_codesize);
return status;
}
@@ -0,0 +1,124 @@
// 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"
#include "loongarch-stub.h"
extern int kernel_asize;
extern int kernel_fsize;
extern int kernel_entry;
/**
* efi_relocate_kernel() - copy memory area
* @image_addr: pointer to address of memory area to copy
* @image_size: size of memory area to copy
* @alloc_size: minimum size of memory to allocate, must be greater or
* equal to image_size
* @preferred_addr: preferred target address
* @alignment: minimum alignment of the allocated memory area. It
* should be a power of two.
* @min_addr: minimum target address
*
* Copy a memory area to a newly allocated memory area aligned according
* to @alignment but at least EFI_ALLOC_ALIGN. If the preferred address
* is not available, the allocated address will not be below @min_addr.
* On exit, @image_addr is updated to the target copy address that was used.
*
* This function is used to copy the Linux kernel verbatim. It does not apply
* any relocation changes.
*
* Return: status code
*/
static
efi_status_t efi_relocate_kernel(unsigned long *image_addr,
unsigned long image_size,
unsigned long alloc_size,
unsigned long preferred_addr,
unsigned long alignment,
unsigned long min_addr)
{
unsigned long cur_image_addr;
unsigned long new_addr = 0;
efi_status_t status;
unsigned long nr_pages;
efi_physical_addr_t efi_addr = preferred_addr;
if (!image_addr || !image_size || !alloc_size)
return EFI_INVALID_PARAMETER;
if (alloc_size < image_size)
return EFI_INVALID_PARAMETER;
cur_image_addr = *image_addr;
/*
* The EFI firmware loader could have placed the kernel image
* anywhere in memory, but the kernel has restrictions on the
* max physical address it can run at. Some architectures
* also have a preferred address, so first try to relocate
* to the preferred address. If that fails, allocate as low
* as possible while respecting the required alignment.
*/
nr_pages = round_up(alloc_size, EFI_ALLOC_ALIGN) / EFI_PAGE_SIZE;
status = efi_bs_call(allocate_pages, EFI_ALLOCATE_ADDRESS,
EFI_LOADER_DATA, nr_pages, &efi_addr);
new_addr = efi_addr;
/*
* If preferred address allocation failed allocate as low as
* possible.
*/
if (status != EFI_SUCCESS) {
status = efi_low_alloc_above(alloc_size, alignment, &new_addr,
min_addr);
}
if (status != EFI_SUCCESS) {
efi_err("Failed to allocate usable memory for kernel.\n");
return status;
}
/*
* We know source/dest won't overlap since both memory ranges
* have been allocated by UEFI, so we can safely use memcpy.
*/
memcpy((void *)new_addr, (void *)cur_image_addr, image_size);
efi_cache_sync_image(new_addr, image_size);
/* Return the new address of the relocated image. */
*image_addr = new_addr;
return status;
}
efi_status_t handle_kernel_image(unsigned long *image_addr,
unsigned long *image_size,
unsigned long *reserve_addr,
unsigned long *reserve_size,
efi_loaded_image_t *image,
efi_handle_t image_handle)
{
efi_status_t status;
unsigned long kernel_addr = 0;
kernel_addr = (unsigned long)image->image_base;
status = efi_relocate_kernel(&kernel_addr, kernel_fsize, kernel_asize,
EFI_KIMG_PREFERRED_ADDRESS, efi_get_kimg_min_align(), 0x0);
*image_addr = kernel_addr;
*image_size = kernel_asize;
return status;
}
unsigned long kernel_entry_address(unsigned long kernel_addr,
efi_loaded_image_t *image)
{
unsigned long base = (unsigned long)image->image_base;
return (unsigned long)&kernel_entry - base + kernel_addr;
}
@@ -0,0 +1,4 @@
/* SPDX-License-Identifier: GPL-2.0-only */
unsigned long kernel_entry_address(unsigned long kernel_addr,
efi_loaded_image_t *image);
@@ -0,0 +1,105 @@
// 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"
#include "loongarch-stub.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;
}
void efi_cache_sync_image(unsigned long image_base, unsigned long alloc_size)
{
asm volatile ("ibar 0" ::: "memory");
}
unsigned long efi_get_kimg_kaslr_address(void)
{
unsigned int random_offset = 0;
#ifdef CONFIG_RANDOMIZE_BASE
if (!efi_nokaslr) {
efi_get_random_bytes(sizeof(random_offset), (u8 *)&random_offset);
random_offset ^= (random_get_entropy() << 16);
random_offset &= (CONFIG_RANDOMIZE_BASE_MAX_OFFSET - 1);
random_offset = ALIGN(random_offset + SZ_64K, SZ_64K);
}
#endif
return PHYSADDR(VMLINUX_LOAD_ADDRESS) + random_offset;
}
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(unsigned long kernel_addr,
efi_loaded_image_t *image)
{
return *(unsigned long *)(kernel_addr + 8) - PHYSADDR(VMLINUX_LOAD_ADDRESS) + kernel_addr;
}
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_write(CSR_DMW0_INIT, LOONGARCH_CSR_DMWIN0);
csr_write(CSR_DMW1_INIT, LOONGARCH_CSR_DMWIN1);
csr_write(CSR_DMW2_INIT, LOONGARCH_CSR_DMWIN2);
csr_write(CSR_DMW3_INIT, LOONGARCH_CSR_DMWIN3);
real_kernel_entry = (void *)kernel_entry_address(kernel_addr, image);
real_kernel_entry(true, (unsigned long)cmdline_ptr,
(unsigned long)efi_system_table);
}
@@ -0,0 +1,208 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/efi.h>
#include <asm/efi.h>
#include "efistub.h"
/**
* efi_get_memory_map() - get memory map
* @map: pointer to memory map pointer to which to assign the
* newly allocated memory map
* @install_cfg_tbl: whether or not to install the boot memory map as a
* configuration table
*
* Retrieve the UEFI memory map. The allocated memory leaves room for
* up to EFI_MMAP_NR_SLACK_SLOTS additional memory map entries.
*
* Return: status code
*/
efi_status_t efi_get_memory_map(struct efi_boot_memmap **map,
bool install_cfg_tbl)
{
struct efi_boot_memmap tmp, *m __free(efi_pool) = NULL;
int memtype = install_cfg_tbl ? EFI_ACPI_RECLAIM_MEMORY
: EFI_LOADER_DATA;
efi_guid_t tbl_guid = LINUX_EFI_BOOT_MEMMAP_GUID;
efi_status_t status;
unsigned long size;
tmp.map_size = 0;
status = efi_bs_call(get_memory_map, &tmp.map_size, NULL, &tmp.map_key,
&tmp.desc_size, &tmp.desc_ver);
if (status != EFI_BUFFER_TOO_SMALL)
return EFI_LOAD_ERROR;
size = tmp.map_size + tmp.desc_size * EFI_MMAP_NR_SLACK_SLOTS;
status = efi_bs_call(allocate_pool, memtype, sizeof(*m) + size,
(void **)&m);
if (status != EFI_SUCCESS)
return status;
if (install_cfg_tbl) {
/*
* Installing a configuration table might allocate memory, and
* this may modify the memory map. This means we should install
* the configuration table first, and re-install or delete it
* as needed.
*/
status = efi_bs_call(install_configuration_table, &tbl_guid, m);
if (status != EFI_SUCCESS)
return status;
}
m->buff_size = m->map_size = size;
status = efi_bs_call(get_memory_map, &m->map_size, m->map, &m->map_key,
&m->desc_size, &m->desc_ver);
if (status != EFI_SUCCESS) {
if (install_cfg_tbl)
efi_bs_call(install_configuration_table, &tbl_guid, NULL);
return status;
}
*map = no_free_ptr(m);
return EFI_SUCCESS;
}
/**
* efi_allocate_pages() - Allocate memory pages
* @size: minimum number of bytes to allocate
* @addr: On return the address of the first allocated page. The first
* allocated page has alignment EFI_ALLOC_ALIGN which is an
* architecture dependent multiple of the page size.
* @max: the address that the last allocated memory page shall not
* exceed
*
* Allocate pages as EFI_LOADER_DATA. The allocated pages are aligned according
* to EFI_ALLOC_ALIGN. The last allocated page will not exceed the address
* given by @max.
*
* Return: status code
*/
efi_status_t efi_allocate_pages(unsigned long size, unsigned long *addr,
unsigned long max)
{
efi_physical_addr_t alloc_addr;
efi_status_t status;
max = min(max, EFI_ALLOC_LIMIT);
if (EFI_ALLOC_ALIGN > EFI_PAGE_SIZE)
return efi_allocate_pages_aligned(size, addr, max,
EFI_ALLOC_ALIGN,
EFI_LOADER_DATA);
alloc_addr = ALIGN_DOWN(max + 1, EFI_ALLOC_ALIGN) - 1;
status = efi_bs_call(allocate_pages, EFI_ALLOCATE_MAX_ADDRESS,
EFI_LOADER_DATA, DIV_ROUND_UP(size, EFI_PAGE_SIZE),
&alloc_addr);
if (status != EFI_SUCCESS)
return status;
*addr = alloc_addr;
return EFI_SUCCESS;
}
/**
* efi_free() - free memory pages
* @size: size of the memory area to free in bytes
* @addr: start of the memory area to free (must be EFI_PAGE_SIZE
* aligned)
*
* @size is rounded up to a multiple of EFI_ALLOC_ALIGN which is an
* architecture specific multiple of EFI_PAGE_SIZE. So this function should
* only be used to return pages allocated with efi_allocate_pages() or
* efi_low_alloc_above().
*/
void efi_free(unsigned long size, unsigned long addr)
{
unsigned long nr_pages;
if (!size)
return;
nr_pages = round_up(size, EFI_ALLOC_ALIGN) / EFI_PAGE_SIZE;
efi_bs_call(free_pages, addr, nr_pages);
}
/**
* efi_low_alloc_above() - allocate pages at or above given address
* @size: size of the memory area to allocate
* @align: minimum alignment of the allocated memory area. It should
* a power of two.
* @addr: on exit the address of the allocated memory
* @min: minimum address to used for the memory allocation
*
* Allocate at the lowest possible address that is not below @min as
* EFI_LOADER_DATA. The allocated pages are aligned according to @align but at
* least EFI_ALLOC_ALIGN. The first allocated page will not below the address
* given by @min.
*
* Return: status code
*/
efi_status_t efi_low_alloc_above(unsigned long size, unsigned long align,
unsigned long *addr, unsigned long min)
{
struct efi_boot_memmap *map __free(efi_pool) = NULL;
efi_status_t status;
unsigned long nr_pages;
int i;
status = efi_get_memory_map(&map, false);
if (status != EFI_SUCCESS)
return status;
/*
* Enforce minimum alignment that EFI or Linux requires when
* requesting a specific address. We are doing page-based (or
* larger) allocations, and both the address and size must meet
* alignment constraints.
*/
if (align < EFI_ALLOC_ALIGN)
align = EFI_ALLOC_ALIGN;
size = round_up(size, EFI_ALLOC_ALIGN);
nr_pages = size / EFI_PAGE_SIZE;
for (i = 0; i < map->map_size / map->desc_size; i++) {
efi_memory_desc_t *desc;
unsigned long m = (unsigned long)map->map;
u64 start, end;
desc = efi_memdesc_ptr(m, map->desc_size, i);
if (desc->type != EFI_CONVENTIONAL_MEMORY)
continue;
if (desc->attribute & EFI_MEMORY_HOT_PLUGGABLE)
continue;
if (efi_soft_reserve_enabled() &&
(desc->attribute & EFI_MEMORY_SP))
continue;
if (desc->num_pages < nr_pages)
continue;
start = desc->phys_addr;
end = start + desc->num_pages * EFI_PAGE_SIZE;
if (start < min)
start = min;
start = round_up(start, align);
if ((start + size) > end)
continue;
status = efi_bs_call(allocate_pages, EFI_ALLOCATE_ADDRESS,
EFI_LOADER_DATA, nr_pages, &start);
if (status == EFI_SUCCESS) {
*addr = start;
break;
}
}
if (i == map->map_size / map->desc_size)
return EFI_NOT_FOUND;
return EFI_SUCCESS;
}
@@ -0,0 +1,94 @@
// SPDX-License-Identifier: GPL-2.0
/*
* PCI-related functions used by the EFI stub on multiple
* architectures.
*
* Copyright 2019 Google, LLC
*/
#include <linux/efi.h>
#include <linux/pci.h>
#include <asm/efi.h>
#include "efistub.h"
void efi_pci_disable_bridge_busmaster(void)
{
efi_guid_t pci_proto = EFI_PCI_IO_PROTOCOL_GUID;
efi_handle_t *pci_handle __free(efi_pool) = NULL;
unsigned long pci_handle_num;
efi_handle_t handle;
efi_status_t status;
u16 class, command;
status = efi_bs_call(locate_handle_buffer, EFI_LOCATE_BY_PROTOCOL,
&pci_proto, NULL, &pci_handle_num, &pci_handle);
if (status != EFI_SUCCESS) {
efi_err("Failed to locate PCI I/O handles\n");
return;
}
for_each_efi_handle(handle, pci_handle, pci_handle_num) {
efi_pci_io_protocol_t *pci;
unsigned long segment_nr, bus_nr, device_nr, func_nr;
status = efi_bs_call(handle_protocol, handle, &pci_proto,
(void **)&pci);
if (status != EFI_SUCCESS)
continue;
/*
* Disregard devices living on bus 0 - these are not behind a
* bridge so no point in disconnecting them from their drivers.
*/
status = efi_call_proto(pci, get_location, &segment_nr, &bus_nr,
&device_nr, &func_nr);
if (status != EFI_SUCCESS || bus_nr == 0)
continue;
/*
* Don't disconnect VGA controllers so we don't risk losing
* access to the framebuffer. Drivers for true PCIe graphics
* controllers that are behind a PCIe root port do not use
* DMA to implement the GOP framebuffer anyway [although they
* may use it in their implementation of Gop->Blt()], and so
* disabling DMA in the PCI bridge should not interfere with
* normal operation of the device.
*/
status = efi_call_proto(pci, pci.read, EfiPciIoWidthUint16,
PCI_CLASS_DEVICE, 1, &class);
if (status != EFI_SUCCESS || class == PCI_CLASS_DISPLAY_VGA)
continue;
/* Disconnect this handle from all its drivers */
efi_bs_call(disconnect_controller, handle, NULL, NULL);
}
for_each_efi_handle(handle, pci_handle, pci_handle_num) {
efi_pci_io_protocol_t *pci;
status = efi_bs_call(handle_protocol, handle, &pci_proto,
(void **)&pci);
if (status != EFI_SUCCESS || !pci)
continue;
status = efi_call_proto(pci, pci.read, EfiPciIoWidthUint16,
PCI_CLASS_DEVICE, 1, &class);
if (status != EFI_SUCCESS || class != PCI_CLASS_BRIDGE_PCI)
continue;
/* Disable busmastering */
status = efi_call_proto(pci, pci.read, EfiPciIoWidthUint16,
PCI_COMMAND, 1, &command);
if (status != EFI_SUCCESS || !(command & PCI_COMMAND_MASTER))
continue;
command &= ~PCI_COMMAND_MASTER;
status = efi_call_proto(pci, pci.write, EfiPciIoWidthUint16,
PCI_COMMAND, 1, &command);
if (status != EFI_SUCCESS)
efi_err("Failed to disable PCI busmastering\n");
}
}
@@ -0,0 +1,56 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/efi.h>
#include <linux/sysfb.h>
#include <asm/efi.h>
#include "efistub.h"
/*
* There are two ways of populating the core kernel's sysfb_primary_display
* via the stub:
*
* - using a configuration table, which relies on the EFI init code to
* locate the table and copy the contents; or
*
* - 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.
*/
static efi_guid_t primary_display_guid = LINUX_EFI_PRIMARY_DISPLAY_TABLE_GUID;
struct sysfb_display_info *__alloc_primary_display(void)
{
struct sysfb_display_info *dpy;
efi_status_t status;
status = efi_bs_call(allocate_pool, EFI_ACPI_RECLAIM_MEMORY,
sizeof(*dpy), (void **)&dpy);
if (status != EFI_SUCCESS)
return NULL;
memset(dpy, 0, sizeof(*dpy));
status = efi_bs_call(install_configuration_table,
&primary_display_guid, dpy);
if (status == EFI_SUCCESS)
return dpy;
efi_bs_call(free_pool, dpy);
return NULL;
}
void free_primary_display(struct sysfb_display_info *dpy)
{
if (!dpy)
return;
efi_bs_call(install_configuration_table, &primary_display_guid, NULL);
efi_bs_call(free_pool, dpy);
}
@@ -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/kern_levels.h>
#include <asm/efi.h>
#include <asm/setup.h>
#include "efistub.h"
int efi_loglevel = LOGLEVEL_NOTICE;
/**
* 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;
}
@@ -0,0 +1,179 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2016 Linaro Ltd; <ard.biesheuvel@linaro.org>
*/
#include <linux/efi.h>
#include <asm/efi.h>
#include "efistub.h"
typedef union efi_rng_protocol efi_rng_protocol_t;
union efi_rng_protocol {
struct {
efi_status_t (__efiapi *get_info)(efi_rng_protocol_t *,
unsigned long *,
efi_guid_t *);
efi_status_t (__efiapi *get_rng)(efi_rng_protocol_t *,
efi_guid_t *, unsigned long,
u8 *out);
};
struct {
u32 get_info;
u32 get_rng;
} mixed_mode;
};
/**
* efi_get_random_bytes() - fill a buffer with random bytes
* @size: size of the buffer
* @out: caller allocated buffer to receive the random bytes
*
* The call will fail if either the firmware does not implement the
* EFI_RNG_PROTOCOL or there are not enough random bytes available to fill
* the buffer.
*
* Return: status code
*/
efi_status_t efi_get_random_bytes(unsigned long size, u8 *out)
{
efi_guid_t rng_proto = EFI_RNG_PROTOCOL_GUID;
efi_status_t status;
efi_rng_protocol_t *rng = NULL;
status = efi_bs_call(locate_protocol, &rng_proto, NULL, (void **)&rng);
if (status != EFI_SUCCESS)
return status;
return efi_call_proto(rng, get_rng, NULL, size, out);
}
/**
* efi_random_get_seed() - provide random seed as configuration table
*
* The EFI_RNG_PROTOCOL is used to read random bytes. These random bytes are
* saved as a configuration table which can be used as entropy by the kernel
* for the initialization of its pseudo random number generator.
*
* If the EFI_RNG_PROTOCOL is not available or there are not enough random bytes
* available, the configuration table will not be installed and an error code
* will be returned.
*
* Return: status code
*/
efi_status_t efi_random_get_seed(void)
{
efi_guid_t rng_proto = EFI_RNG_PROTOCOL_GUID;
efi_guid_t rng_algo_raw = EFI_RNG_ALGORITHM_RAW;
efi_guid_t rng_table_guid = LINUX_EFI_RANDOM_SEED_TABLE_GUID;
struct linux_efi_random_seed *prev_seed, *seed = NULL;
int prev_seed_size = 0, seed_size = EFI_RANDOM_SEED_SIZE;
unsigned long nv_seed_size = 0, offset = 0;
efi_rng_protocol_t *rng = NULL;
efi_status_t status;
status = efi_bs_call(locate_protocol, &rng_proto, NULL, (void **)&rng);
if (status != EFI_SUCCESS)
seed_size = 0;
// Call GetVariable() with a zero length buffer to obtain the size
get_efi_var(L"RandomSeed", &rng_table_guid, NULL, &nv_seed_size, NULL);
if (!seed_size && !nv_seed_size)
return status;
seed_size += nv_seed_size;
/*
* Check whether a seed was provided by a prior boot stage. In that
* case, instead of overwriting it, let's create a new buffer that can
* hold both, and concatenate the existing and the new seeds.
* Note that we should read the seed size with caution, in case the
* table got corrupted in memory somehow.
*/
prev_seed = get_efi_config_table(rng_table_guid);
if (prev_seed && prev_seed->size <= 512U) {
prev_seed_size = prev_seed->size;
seed_size += prev_seed_size;
}
/*
* Use EFI_ACPI_RECLAIM_MEMORY here so that it is guaranteed that the
* allocation will survive a kexec reboot (although we refresh the seed
* beforehand)
*/
status = efi_bs_call(allocate_pool, EFI_ACPI_RECLAIM_MEMORY,
struct_size(seed, bits, seed_size),
(void **)&seed);
if (status != EFI_SUCCESS) {
efi_warn("Failed to allocate memory for RNG seed.\n");
goto err_warn;
}
if (rng) {
status = efi_call_proto(rng, get_rng, &rng_algo_raw,
EFI_RANDOM_SEED_SIZE, seed->bits);
if (status == EFI_UNSUPPORTED)
/*
* Use whatever algorithm we have available if the raw algorithm
* is not implemented.
*/
status = efi_call_proto(rng, get_rng, NULL,
EFI_RANDOM_SEED_SIZE, seed->bits);
if (status == EFI_SUCCESS)
offset = EFI_RANDOM_SEED_SIZE;
}
if (nv_seed_size) {
status = get_efi_var(L"RandomSeed", &rng_table_guid, NULL,
&nv_seed_size, seed->bits + offset);
if (status == EFI_SUCCESS)
/*
* We delete the seed here, and /hope/ that this causes
* EFI to also zero out its representation on disk.
* This is somewhat idealistic, but overwriting the
* variable with zeros is likely just as fraught too.
* TODO: in the future, maybe we can hash it forward
* instead, and write a new seed.
*/
status = set_efi_var(L"RandomSeed", &rng_table_guid, 0,
0, NULL);
if (status == EFI_SUCCESS)
offset += nv_seed_size;
else
memzero_explicit(seed->bits + offset, nv_seed_size);
}
if (!offset)
goto err_freepool;
if (prev_seed_size) {
memcpy(seed->bits + offset, prev_seed->bits, prev_seed_size);
offset += prev_seed_size;
}
seed->size = offset;
status = efi_bs_call(install_configuration_table, &rng_table_guid, seed);
if (status != EFI_SUCCESS)
goto err_freepool;
if (prev_seed_size) {
/* wipe and free the old seed if we managed to install the new one */
memzero_explicit(prev_seed->bits, prev_seed_size);
efi_bs_call(free_pool, prev_seed);
}
return EFI_SUCCESS;
err_freepool:
memzero_explicit(seed, struct_size(seed, bits, seed_size));
efi_bs_call(free_pool, seed);
efi_warn("Failed to obtain seed from EFI_RNG_PROTOCOL or EFI variable\n");
err_warn:
if (prev_seed)
efi_warn("Retaining bootloader-supplied seed only");
return status;
}
@@ -0,0 +1,141 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2016 Linaro Ltd; <ard.biesheuvel@linaro.org>
*/
#include <linux/efi.h>
#include <linux/log2.h>
#include <asm/efi.h>
#include "efistub.h"
/*
* Return the number of slots covered by this entry, i.e., the number of
* addresses it covers that are suitably aligned and supply enough room
* for the allocation.
*/
static unsigned long get_entry_num_slots(efi_memory_desc_t *md,
unsigned long size,
unsigned long align_shift,
u64 alloc_min, u64 alloc_max)
{
unsigned long align = 1UL << align_shift;
u64 first_slot, last_slot, region_end;
if (md->type != EFI_CONVENTIONAL_MEMORY)
return 0;
if (md->attribute & EFI_MEMORY_HOT_PLUGGABLE)
return 0;
if (efi_soft_reserve_enabled() &&
(md->attribute & EFI_MEMORY_SP))
return 0;
region_end = min(md->phys_addr + md->num_pages * EFI_PAGE_SIZE - 1,
alloc_max);
if (region_end < size)
return 0;
first_slot = round_up(max(md->phys_addr, alloc_min), align);
last_slot = round_down(region_end - size + 1, align);
if (first_slot > last_slot)
return 0;
return ((unsigned long)(last_slot - first_slot) >> align_shift) + 1;
}
/*
* The UEFI memory descriptors have a virtual address field that is only used
* when installing the virtual mapping using SetVirtualAddressMap(). Since it
* is unused here, we can reuse it to keep track of each descriptor's slot
* count.
*/
#define MD_NUM_SLOTS(md) ((md)->virt_addr)
efi_status_t efi_random_alloc(unsigned long size,
unsigned long align,
unsigned long *addr,
unsigned long random_seed,
int memory_type,
unsigned long alloc_min,
unsigned long alloc_max)
{
struct efi_boot_memmap *map __free(efi_pool) = NULL;
unsigned long total_slots = 0, target_slot;
unsigned long total_mirrored_slots = 0;
efi_status_t status;
int map_offset;
status = efi_get_memory_map(&map, false);
if (status != EFI_SUCCESS)
return status;
if (align < EFI_ALLOC_ALIGN)
align = EFI_ALLOC_ALIGN;
/* Avoid address 0x0, as it can be mistaken for NULL */
if (alloc_min == 0)
alloc_min = align;
size = round_up(size, EFI_ALLOC_ALIGN);
/* count the suitable slots in each memory map entry */
for (map_offset = 0; map_offset < map->map_size; map_offset += map->desc_size) {
efi_memory_desc_t *md = (void *)map->map + map_offset;
unsigned long slots;
slots = get_entry_num_slots(md, size, ilog2(align), alloc_min,
alloc_max);
MD_NUM_SLOTS(md) = slots;
total_slots += slots;
if (md->attribute & EFI_MEMORY_MORE_RELIABLE)
total_mirrored_slots += slots;
}
/* consider only mirrored slots for randomization if any exist */
if (total_mirrored_slots > 0)
total_slots = total_mirrored_slots;
/* find a random number between 0 and total_slots */
target_slot = (total_slots * (u64)(random_seed & U32_MAX)) >> 32;
/*
* target_slot is now a value in the range [0, total_slots), and so
* it corresponds with exactly one of the suitable slots we recorded
* when iterating over the memory map the first time around.
*
* So iterate over the memory map again, subtracting the number of
* slots of each entry at each iteration, until we have found the entry
* that covers our chosen slot. Use the residual value of target_slot
* to calculate the randomly chosen address, and allocate it directly
* using EFI_ALLOCATE_ADDRESS.
*/
status = EFI_OUT_OF_RESOURCES;
for (map_offset = 0; map_offset < map->map_size; map_offset += map->desc_size) {
efi_memory_desc_t *md = (void *)map->map + map_offset;
efi_physical_addr_t target;
unsigned long pages;
if (total_mirrored_slots > 0 &&
!(md->attribute & EFI_MEMORY_MORE_RELIABLE))
continue;
if (target_slot >= MD_NUM_SLOTS(md)) {
target_slot -= MD_NUM_SLOTS(md);
continue;
}
target = round_up(max_t(u64, md->phys_addr, alloc_min), align) + target_slot * align;
pages = size / EFI_PAGE_SIZE;
status = efi_bs_call(allocate_pages, EFI_ALLOCATE_ADDRESS,
memory_type, pages, &target);
if (status == EFI_SUCCESS)
*addr = target;
break;
}
return status;
}
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2020 Western Digital Corporation or its affiliates.
*/
#include <linux/efi.h>
#include <asm/efi.h>
#include <asm/sections.h>
#include <linux/unaligned.h>
#include "efistub.h"
unsigned long stext_offset(void)
{
/*
* 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.
*/
return _start_kernel - _start;
}
efi_status_t handle_kernel_image(unsigned long *image_addr,
unsigned long *image_size,
unsigned long *reserve_addr,
unsigned long *reserve_size,
efi_loaded_image_t *image,
efi_handle_t image_handle)
{
unsigned long kernel_size, kernel_codesize, kernel_memsize;
efi_status_t status;
kernel_size = _edata - _start;
kernel_codesize = __init_text_end - _start;
kernel_memsize = kernel_size + (_end - _edata);
*image_addr = (unsigned long)_start;
*image_size = kernel_memsize;
*reserve_size = *image_size;
status = efi_kaslr_relocate_kernel(image_addr,
reserve_addr, reserve_size,
kernel_size, kernel_codesize, kernel_memsize,
efi_kaslr_get_phys_seed(image_handle));
if (status != EFI_SUCCESS) {
efi_err("Failed to relocate kernel\n");
*image_size = 0;
}
return status;
}
void efi_icache_sync(unsigned long start, unsigned long end)
{
asm volatile ("fence.i" ::: "memory");
}
@@ -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 <linux/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);
}
@@ -0,0 +1,62 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Secure boot handling.
*
* Copyright (C) 2013,2014 Linaro Limited
* Roy Franz <roy.franz@linaro.org
* Copyright (C) 2013 Red Hat, Inc.
* Mark Salter <msalter@redhat.com>
*/
#include <linux/efi.h>
#include <asm/efi.h>
#include "efistub.h"
/* SHIM variables */
static const efi_guid_t shim_guid = EFI_SHIM_LOCK_GUID;
static const efi_char16_t shim_MokSBState_name[] = L"MokSBStateRT";
static efi_status_t get_var(efi_char16_t *name, efi_guid_t *vendor, u32 *attr,
unsigned long *data_size, void *data)
{
return get_efi_var(name, vendor, attr, data_size, data);
}
/*
* Determine whether we're in secure boot mode.
*/
enum efi_secureboot_mode efi_get_secureboot(void)
{
u32 attr;
unsigned long size;
enum efi_secureboot_mode mode;
efi_status_t status;
u8 moksbstate;
mode = efi_get_secureboot_mode(get_var);
if (mode == efi_secureboot_mode_unknown) {
efi_err("Could not determine UEFI Secure Boot status.\n");
return efi_secureboot_mode_unknown;
}
if (mode != efi_secureboot_mode_enabled)
return mode;
/*
* See if a user has put the shim into insecure mode. If so, and if the
* variable doesn't have the non-volatile attribute set, we might as
* well honor that.
*/
size = sizeof(moksbstate);
status = get_efi_var(shim_MokSBState_name, &shim_guid,
&attr, &size, &moksbstate);
/* If it fails, we don't care why. Default to secure */
if (status != EFI_SUCCESS)
goto secure_boot_enabled;
if (!(attr & EFI_VARIABLE_NON_VOLATILE) && moksbstate == 1)
return efi_secureboot_mode_disabled;
secure_boot_enabled:
efi_info("UEFI Secure Boot is enabled.\n");
return efi_secureboot_mode_enabled;
}
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/ctype.h>
#include <linux/string.h>
#include <linux/types.h>
char *skip_spaces(const char *str)
{
while (isspace(*str))
++str;
return (char *)str;
}
@@ -0,0 +1,68 @@
// SPDX-License-Identifier: GPL-2.0-only
// Copyright 2022 Google LLC
// Author: Ard Biesheuvel <ardb@google.com>
#include <linux/efi.h>
#include "efistub.h"
typedef union efi_smbios_protocol efi_smbios_protocol_t;
union efi_smbios_protocol {
struct {
efi_status_t (__efiapi *add)(efi_smbios_protocol_t *, efi_handle_t,
u16 *, struct efi_smbios_record *);
efi_status_t (__efiapi *update_string)(efi_smbios_protocol_t *, u16 *,
unsigned long *, u8 *);
efi_status_t (__efiapi *remove)(efi_smbios_protocol_t *, u16);
efi_status_t (__efiapi *get_next)(efi_smbios_protocol_t *, u16 *, u8 *,
struct efi_smbios_record **,
efi_handle_t *);
u8 major_version;
u8 minor_version;
};
struct {
u32 add;
u32 update_string;
u32 remove;
u32 get_next;
u8 major_version;
u8 minor_version;
} mixed_mode;
};
const struct efi_smbios_record *efi_get_smbios_record(u8 type)
{
struct efi_smbios_record *record;
efi_smbios_protocol_t *smbios;
efi_status_t status;
u16 handle = 0xfffe;
status = efi_bs_call(locate_protocol, &EFI_SMBIOS_PROTOCOL_GUID, NULL,
(void **)&smbios) ?:
efi_call_proto(smbios, get_next, &handle, &type, &record, NULL);
if (status != EFI_SUCCESS)
return NULL;
return record;
}
const u8 *__efi_get_smbios_string(const struct efi_smbios_record *record,
const u8 *offset)
{
const u8 *strtable;
if (!record)
return NULL;
strtable = (u8 *)record + record->length;
for (int i = 1; i < *offset; i++) {
int len = strlen(strtable);
if (!len)
return NULL;
strtable += len + 1;
}
return strtable;
}
@@ -0,0 +1,204 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Taken from:
* linux/lib/string.c
*
* Copyright (C) 1991, 1992 Linus Torvalds
*/
#include <linux/ctype.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/string.h>
#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
* @s2: The string to search for
*/
char *strstr(const char *s1, const char *s2)
{
size_t l1, l2;
l2 = strlen(s2);
if (!l2)
return (char *)s1;
l1 = strlen(s1);
while (l1 >= l2) {
l1--;
if (!memcmp(s1, s2, l2))
return (char *)s1;
s1++;
}
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
/**
* strncmp - Compare two length-limited strings
* @cs: One string
* @ct: Another string
* @count: The maximum number of bytes to compare
*/
int strncmp(const char *cs, const char *ct, size_t count)
{
unsigned char c1, c2;
while (count) {
c1 = *cs++;
c2 = *ct++;
if (c1 != c2)
return c1 < c2 ? -1 : 1;
if (!c1)
break;
count--;
}
return 0;
}
/* Works only for digits and letters, but small and fast */
#define TOLOWER(x) ((x) | 0x20)
static unsigned int simple_guess_base(const char *cp)
{
if (cp[0] == '0') {
if (TOLOWER(cp[1]) == 'x' && isxdigit(cp[2]))
return 16;
else
return 8;
} else {
return 10;
}
}
/**
* simple_strtoull - convert a string to an unsigned long long
* @cp: The start of the string
* @endp: A pointer to the end of the parsed string will be placed here
* @base: The number base to use
*/
unsigned long long simple_strtoull(const char *cp, char **endp, unsigned int base)
{
unsigned long long result = 0;
if (!base)
base = simple_guess_base(cp);
if (base == 16 && cp[0] == '0' && TOLOWER(cp[1]) == 'x')
cp += 2;
while (isxdigit(*cp)) {
unsigned int value;
value = isdigit(*cp) ? *cp - '0' : TOLOWER(*cp) - 'a' + 10;
if (value >= base)
break;
result = result * base + value;
cp++;
}
if (endp)
*endp = (char *)cp;
return result;
}
long simple_strtol(const char *cp, char **endp, unsigned int base)
{
if (*cp == '-')
return -simple_strtoull(cp + 1, endp, 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
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/efi.h>
#include <asm/efi.h>
#include "efistub.h"
const efi_system_table_t *efi_system_table;
@@ -0,0 +1,196 @@
// SPDX-License-Identifier: GPL-2.0
/*
* TPM handling.
*
* Copyright (C) 2016 CoreOS, Inc
* Copyright (C) 2017 Google, Inc.
* Matthew Garrett <mjg59@google.com>
* Thiebaud Weksteen <tweek@google.com>
*/
#include <linux/efi.h>
#include <linux/tpm_eventlog.h>
#include <asm/efi.h>
#include "efistub.h"
#ifdef CONFIG_RESET_ATTACK_MITIGATION
static const efi_char16_t efi_MemoryOverWriteRequest_name[] =
L"MemoryOverwriteRequestControl";
#define MEMORY_ONLY_RESET_CONTROL_GUID \
EFI_GUID(0xe20939be, 0x32d4, 0x41be, 0xa1, 0x50, 0x89, 0x7f, 0x85, 0xd4, 0x98, 0x29)
/*
* Enable reboot attack mitigation. This requests that the firmware clear the
* RAM on next reboot before proceeding with boot, ensuring that any secrets
* are cleared. If userland has ensured that all secrets have been removed
* from RAM before reboot it can simply reset this variable.
*/
void efi_enable_reset_attack_mitigation(void)
{
u8 val = 1;
efi_guid_t var_guid = MEMORY_ONLY_RESET_CONTROL_GUID;
efi_status_t status;
unsigned long datasize = 0;
status = get_efi_var(efi_MemoryOverWriteRequest_name, &var_guid,
NULL, &datasize, NULL);
if (status == EFI_NOT_FOUND)
return;
set_efi_var(efi_MemoryOverWriteRequest_name, &var_guid,
EFI_VARIABLE_NON_VOLATILE |
EFI_VARIABLE_BOOTSERVICE_ACCESS |
EFI_VARIABLE_RUNTIME_ACCESS, sizeof(val), &val);
}
#endif
static void efi_retrieve_tcg2_eventlog(int version, efi_physical_addr_t log_location,
efi_physical_addr_t log_last_entry,
efi_bool_t truncated,
struct efi_tcg2_final_events_table *final_events_table)
{
efi_guid_t linux_eventlog_guid = LINUX_EFI_TPM_EVENT_LOG_GUID;
efi_status_t status;
struct linux_efi_tpm_eventlog *log_tbl = NULL;
unsigned long first_entry_addr, last_entry_addr;
size_t log_size, last_entry_size;
u32 final_events_size = 0;
first_entry_addr = (unsigned long) log_location;
/*
* We populate the EFI table even if the logs are empty.
*/
if (!log_last_entry) {
log_size = 0;
} else {
last_entry_addr = (unsigned long) log_last_entry;
/*
* get_event_log only returns the address of the last entry.
* We need to calculate its size to deduce the full size of
* the logs.
*
* CC Event log also uses TCG2 format, handle it same as TPM2.
*/
if (version > EFI_TCG2_EVENT_LOG_FORMAT_TCG_1_2) {
/*
* The TCG2 log format has variable length entries,
* and the information to decode the hash algorithms
* back into a size is contained in the first entry -
* pass a pointer to the final entry (to calculate its
* size) and the first entry (so we know how long each
* digest is)
*/
last_entry_size =
__calc_tpm2_event_size((void *)last_entry_addr,
(void *)(long)log_location,
false);
} else {
last_entry_size = sizeof(struct tcpa_event) +
((struct tcpa_event *) last_entry_addr)->event_size;
}
log_size = log_last_entry - log_location + last_entry_size;
}
/* Allocate space for the logs and copy them. */
status = efi_bs_call(allocate_pool, EFI_ACPI_RECLAIM_MEMORY,
sizeof(*log_tbl) + log_size, (void **)&log_tbl);
if (status != EFI_SUCCESS) {
efi_err("Unable to allocate memory for event log\n");
return;
}
/*
* Figure out whether any events have already been logged to the
* final events structure, and if so how much space they take up
*/
if (final_events_table && final_events_table->nr_events) {
struct tcg_pcr_event2_head *header;
u32 offset;
void *data;
u32 event_size;
int i = final_events_table->nr_events;
data = (void *)final_events_table;
offset = sizeof(final_events_table->version) +
sizeof(final_events_table->nr_events);
while (i > 0) {
header = data + offset + final_events_size;
event_size = __calc_tpm2_event_size(header,
(void *)(long)log_location,
false);
/* If calc fails this is a malformed log */
if (!event_size)
break;
final_events_size += event_size;
i--;
}
}
memset(log_tbl, 0, sizeof(*log_tbl) + log_size);
log_tbl->size = log_size;
log_tbl->final_events_preboot_size = final_events_size;
log_tbl->version = version;
memcpy(log_tbl->log, (void *) first_entry_addr, log_size);
status = efi_bs_call(install_configuration_table,
&linux_eventlog_guid, log_tbl);
if (status != EFI_SUCCESS)
goto err_free;
return;
err_free:
efi_bs_call(free_pool, log_tbl);
}
void efi_retrieve_eventlog(void)
{
struct efi_tcg2_final_events_table *final_events_table = NULL;
efi_physical_addr_t log_location = 0, log_last_entry = 0;
efi_guid_t tpm2_guid = EFI_TCG2_PROTOCOL_GUID;
int version = EFI_TCG2_EVENT_LOG_FORMAT_TCG_2;
efi_tcg2_protocol_t *tpm2 = NULL;
efi_bool_t truncated;
efi_status_t status;
status = efi_bs_call(locate_protocol, &tpm2_guid, NULL, (void **)&tpm2);
if (status == EFI_SUCCESS) {
status = efi_call_proto(tpm2, get_event_log, version, &log_location,
&log_last_entry, &truncated);
if (status != EFI_SUCCESS || !log_location) {
version = EFI_TCG2_EVENT_LOG_FORMAT_TCG_1_2;
status = efi_call_proto(tpm2, get_event_log, version,
&log_location, &log_last_entry,
&truncated);
} else {
final_events_table =
get_efi_config_table(EFI_TCG2_FINAL_EVENTS_TABLE_GUID);
}
} else {
efi_guid_t cc_guid = EFI_CC_MEASUREMENT_PROTOCOL_GUID;
efi_cc_protocol_t *cc = NULL;
status = efi_bs_call(locate_protocol, &cc_guid, NULL, (void **)&cc);
if (status != EFI_SUCCESS)
return;
version = EFI_CC_EVENT_LOG_FORMAT_TCG_2;
status = efi_call_proto(cc, get_event_log, version, &log_location,
&log_last_entry, &truncated);
final_events_table =
get_efi_config_table(EFI_CC_FINAL_EVENTS_TABLE_GUID);
}
if (status != EFI_SUCCESS || !log_location)
return;
efi_retrieve_tcg2_eventlog(version, log_location, log_last_entry,
truncated, final_events_table);
}
@@ -0,0 +1,223 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/efi.h>
#include <asm/efi.h>
#include "efistub.h"
struct efi_unaccepted_memory *unaccepted_table;
efi_status_t allocate_unaccepted_bitmap(__u32 nr_desc,
struct efi_boot_memmap *map)
{
efi_guid_t unaccepted_table_guid = LINUX_EFI_UNACCEPTED_MEM_TABLE_GUID;
u64 unaccepted_start = ULLONG_MAX, unaccepted_end = 0, bitmap_size;
efi_status_t status;
int i;
/* Check if the table is already installed */
unaccepted_table = get_efi_config_table(unaccepted_table_guid);
if (unaccepted_table) {
if (unaccepted_table->version != 1) {
efi_err("Unknown version of unaccepted memory table\n");
return EFI_UNSUPPORTED;
}
return EFI_SUCCESS;
}
/* Check if there's any unaccepted memory and find the max address */
for (i = 0; i < nr_desc; i++) {
efi_memory_desc_t *d;
unsigned long m = (unsigned long)map->map;
d = efi_memdesc_ptr(m, map->desc_size, i);
if (d->type != EFI_UNACCEPTED_MEMORY)
continue;
unaccepted_start = min(unaccepted_start, d->phys_addr);
unaccepted_end = max(unaccepted_end,
d->phys_addr + d->num_pages * PAGE_SIZE);
}
if (unaccepted_start == ULLONG_MAX)
return EFI_SUCCESS;
unaccepted_start = round_down(unaccepted_start,
EFI_UNACCEPTED_UNIT_SIZE);
unaccepted_end = round_up(unaccepted_end, EFI_UNACCEPTED_UNIT_SIZE);
/*
* If unaccepted memory is present, allocate a bitmap to track what
* memory has to be accepted before access.
*
* One bit in the bitmap represents 2MiB in the address space:
* A 4k bitmap can track 64GiB of physical address space.
*
* In the worst case scenario -- a huge hole in the middle of the
* address space -- It needs 256MiB to handle 4PiB of the address
* space.
*
* The bitmap will be populated in setup_e820() according to the memory
* map after efi_exit_boot_services().
*/
bitmap_size = DIV_ROUND_UP(unaccepted_end - unaccepted_start,
EFI_UNACCEPTED_UNIT_SIZE * BITS_PER_BYTE);
status = efi_bs_call(allocate_pool, EFI_ACPI_RECLAIM_MEMORY,
sizeof(*unaccepted_table) + bitmap_size,
(void **)&unaccepted_table);
if (status != EFI_SUCCESS) {
efi_err("Failed to allocate unaccepted memory config table\n");
return status;
}
unaccepted_table->version = 1;
unaccepted_table->unit_size = EFI_UNACCEPTED_UNIT_SIZE;
unaccepted_table->phys_base = unaccepted_start;
unaccepted_table->size = bitmap_size;
memset(unaccepted_table->bitmap, 0, bitmap_size);
status = efi_bs_call(install_configuration_table,
&unaccepted_table_guid, unaccepted_table);
if (status != EFI_SUCCESS) {
efi_bs_call(free_pool, unaccepted_table);
efi_err("Failed to install unaccepted memory config table!\n");
}
return status;
}
/*
* The accepted memory bitmap only works at unit_size granularity. Take
* unaligned start/end addresses and either:
* 1. Accepts the memory immediately and in its entirety
* 2. Accepts unaligned parts, and marks *some* aligned part unaccepted
*
* The function will never reach the bitmap_set() with zero bits to set.
*/
void process_unaccepted_memory(u64 start, u64 end)
{
u64 unit_size = unaccepted_table->unit_size;
u64 unit_mask = unaccepted_table->unit_size - 1;
u64 bitmap_size = unaccepted_table->size;
/*
* Ensure that at least one bit will be set in the bitmap by
* immediately accepting all regions under 2*unit_size. This is
* imprecise and may immediately accept some areas that could
* have been represented in the bitmap. But, results in simpler
* code below
*
* Consider case like this (assuming unit_size == 2MB):
*
* | 4k | 2044k | 2048k |
* ^ 0x0 ^ 2MB ^ 4MB
*
* Only the first 4k has been accepted. The 0MB->2MB region can not be
* represented in the bitmap. The 2MB->4MB region can be represented in
* the bitmap. But, the 0MB->4MB region is <2*unit_size and will be
* immediately accepted in its entirety.
*/
if (end - start < 2 * unit_size) {
arch_accept_memory(start, end);
return;
}
/*
* No matter how the start and end are aligned, at least one unaccepted
* unit_size area will remain to be marked in the bitmap.
*/
/* Immediately accept a <unit_size piece at the start: */
if (start & unit_mask) {
arch_accept_memory(start, round_up(start, unit_size));
start = round_up(start, unit_size);
}
/* Immediately accept a <unit_size piece at the end: */
if (end & unit_mask) {
arch_accept_memory(round_down(end, unit_size), end);
end = round_down(end, unit_size);
}
/*
* Accept part of the range that before phys_base and cannot be recorded
* into the bitmap.
*/
if (start < unaccepted_table->phys_base) {
arch_accept_memory(start,
min(unaccepted_table->phys_base, end));
start = unaccepted_table->phys_base;
}
/* Nothing to record */
if (end < unaccepted_table->phys_base)
return;
/* Translate to offsets from the beginning of the bitmap */
start -= unaccepted_table->phys_base;
end -= unaccepted_table->phys_base;
/* Accept memory that doesn't fit into bitmap */
if (end > bitmap_size * unit_size * BITS_PER_BYTE) {
unsigned long phys_start, phys_end;
phys_start = bitmap_size * unit_size * BITS_PER_BYTE +
unaccepted_table->phys_base;
phys_end = end + unaccepted_table->phys_base;
arch_accept_memory(phys_start, phys_end);
end = bitmap_size * unit_size * BITS_PER_BYTE;
}
/*
* 'start' and 'end' are now both unit_size-aligned.
* Record the range as being unaccepted:
*/
bitmap_set(unaccepted_table->bitmap,
start / unit_size, (end - start) / unit_size);
}
void accept_memory(phys_addr_t start, unsigned long size)
{
unsigned long range_start, range_end;
phys_addr_t end = start + size;
unsigned long bitmap_size;
u64 unit_size;
if (!unaccepted_table)
return;
unit_size = unaccepted_table->unit_size;
/*
* Only care for the part of the range that is represented
* in the bitmap.
*/
if (start < unaccepted_table->phys_base)
start = unaccepted_table->phys_base;
if (end < unaccepted_table->phys_base)
return;
/* Translate to offsets from the beginning of the bitmap */
start -= unaccepted_table->phys_base;
end -= unaccepted_table->phys_base;
/* Make sure not to overrun the bitmap */
if (end > unaccepted_table->size * unit_size * BITS_PER_BYTE)
end = unaccepted_table->size * unit_size * BITS_PER_BYTE;
range_start = start / unit_size;
bitmap_size = DIV_ROUND_UP(end, unit_size);
for_each_set_bitrange_from(range_start, range_end,
unaccepted_table->bitmap, bitmap_size) {
unsigned long phys_start, phys_end;
phys_start = range_start * unit_size + unaccepted_table->phys_base;
phys_end = range_end * unit_size + unaccepted_table->phys_base;
arch_accept_memory(phys_start, phys_end);
bitmap_clear(unaccepted_table->bitmap,
range_start, range_end - range_start);
}
}
@@ -0,0 +1,564 @@
// SPDX-License-Identifier: GPL-2.0-only
/* -*- linux-c -*- ------------------------------------------------------- *
*
* Copyright (C) 1991, 1992 Linus Torvalds
* Copyright 2007 rPath, Inc. - All Rights Reserved
*
* ----------------------------------------------------------------------- */
/*
* Oh, it's a waste of space, but oh-so-yummy for debugging.
*/
#include <linux/stdarg.h>
#include <linux/compiler.h>
#include <linux/ctype.h>
#include <linux/kernel.h>
#include <linux/limits.h>
#include <linux/string.h>
#include <linux/types.h>
static
int skip_atoi(const char **s)
{
int i = 0;
while (isdigit(**s))
i = i * 10 + *((*s)++) - '0';
return i;
}
/*
* put_dec_full4 handles numbers in the range 0 <= r < 10000.
* The multiplier 0xccd is round(2^15/10), and the approximation
* r/10 == (r * 0xccd) >> 15 is exact for all r < 16389.
*/
static
void put_dec_full4(char *end, unsigned int r)
{
int i;
for (i = 0; i < 3; i++) {
unsigned int q = (r * 0xccd) >> 15;
*--end = '0' + (r - q * 10);
r = q;
}
*--end = '0' + r;
}
/* put_dec is copied from lib/vsprintf.c with small modifications */
/*
* Call put_dec_full4 on x % 10000, return x / 10000.
* The approximation x/10000 == (x * 0x346DC5D7) >> 43
* holds for all x < 1,128,869,999. The largest value this
* helper will ever be asked to convert is 1,125,520,955.
* (second call in the put_dec code, assuming n is all-ones).
*/
static
unsigned int put_dec_helper4(char *end, unsigned int x)
{
unsigned int q = (x * 0x346DC5D7ULL) >> 43;
put_dec_full4(end, x - q * 10000);
return q;
}
/* Based on code by Douglas W. Jones found at
* <http://www.cs.uiowa.edu/~jones/bcd/decimal.html#sixtyfour>
* (with permission from the author).
* Performs no 64-bit division and hence should be fast on 32-bit machines.
*/
static
char *put_dec(char *end, unsigned long long n)
{
unsigned int d3, d2, d1, q, h;
char *p = end;
d1 = ((unsigned int)n >> 16); /* implicit "& 0xffff" */
h = (n >> 32);
d2 = (h ) & 0xffff;
d3 = (h >> 16); /* implicit "& 0xffff" */
/* n = 2^48 d3 + 2^32 d2 + 2^16 d1 + d0
= 281_4749_7671_0656 d3 + 42_9496_7296 d2 + 6_5536 d1 + d0 */
q = 656 * d3 + 7296 * d2 + 5536 * d1 + ((unsigned int)n & 0xffff);
q = put_dec_helper4(p, q);
p -= 4;
q += 7671 * d3 + 9496 * d2 + 6 * d1;
q = put_dec_helper4(p, q);
p -= 4;
q += 4749 * d3 + 42 * d2;
q = put_dec_helper4(p, q);
p -= 4;
q += 281 * d3;
q = put_dec_helper4(p, q);
p -= 4;
put_dec_full4(p, q);
p -= 4;
/* strip off the extra 0's we printed */
while (p < end && *p == '0')
++p;
return p;
}
static
char *number(char *end, unsigned long long num, int base, char locase)
{
/*
* locase = 0 or 0x20. ORing digits or letters with 'locase'
* produces same digits or (maybe lowercased) letters
*/
/* we are called with base 8, 10 or 16, only, thus don't need "G..." */
static const char digits[16] = "0123456789ABCDEF"; /* "GHIJKLMNOPQRSTUVWXYZ"; */
switch (base) {
case 10:
if (num != 0)
end = put_dec(end, num);
break;
case 8:
for (; num != 0; num >>= 3)
*--end = '0' + (num & 07);
break;
case 16:
for (; num != 0; num >>= 4)
*--end = digits[num & 0xf] | locase;
break;
default:
unreachable();
}
return end;
}
#define ZEROPAD 1 /* pad with zero */
#define SIGN 2 /* unsigned/signed long */
#define PLUS 4 /* show plus */
#define SPACE 8 /* space if plus */
#define LEFT 16 /* left justified */
#define SMALL 32 /* Must be 32 == 0x20 */
#define SPECIAL 64 /* 0x */
#define WIDE 128 /* UTF-16 string */
static
int get_flags(const char **fmt)
{
int flags = 0;
do {
switch (**fmt) {
case '-':
flags |= LEFT;
break;
case '+':
flags |= PLUS;
break;
case ' ':
flags |= SPACE;
break;
case '#':
flags |= SPECIAL;
break;
case '0':
flags |= ZEROPAD;
break;
default:
return flags;
}
++(*fmt);
} while (1);
}
static
int get_int(const char **fmt, va_list *ap)
{
if (isdigit(**fmt))
return skip_atoi(fmt);
if (**fmt == '*') {
++(*fmt);
/* it's the next argument */
return va_arg(*ap, int);
}
return 0;
}
static
unsigned long long get_number(int sign, int qualifier, va_list *ap)
{
if (sign) {
switch (qualifier) {
case 'L':
return va_arg(*ap, long long);
case 'l':
return va_arg(*ap, long);
case 'h':
return (short)va_arg(*ap, int);
case 'H':
return (signed char)va_arg(*ap, int);
default:
return va_arg(*ap, int);
};
} else {
switch (qualifier) {
case 'L':
return va_arg(*ap, unsigned long long);
case 'l':
return va_arg(*ap, unsigned long);
case 'h':
return (unsigned short)va_arg(*ap, int);
case 'H':
return (unsigned char)va_arg(*ap, int);
default:
return va_arg(*ap, unsigned int);
}
}
}
static
char get_sign(long long *num, int flags)
{
if (!(flags & SIGN))
return 0;
if (*num < 0) {
*num = -(*num);
return '-';
}
if (flags & PLUS)
return '+';
if (flags & SPACE)
return ' ';
return 0;
}
static
size_t utf16s_utf8nlen(const u16 *s16, size_t maxlen)
{
size_t len, clen;
for (len = 0; len < maxlen && *s16; len += clen) {
u16 c0 = *s16++;
/* First, get the length for a BMP character */
clen = 1 + (c0 >= 0x80) + (c0 >= 0x800);
if (len + clen > maxlen)
break;
/*
* If this is a high surrogate, and we're already at maxlen, we
* can't include the character if it's a valid surrogate pair.
* Avoid accessing one extra word just to check if it's valid
* or not.
*/
if ((c0 & 0xfc00) == 0xd800) {
if (len + clen == maxlen)
break;
if ((*s16 & 0xfc00) == 0xdc00) {
++s16;
++clen;
}
}
}
return len;
}
static
u32 utf16_to_utf32(const u16 **s16)
{
u16 c0, c1;
c0 = *(*s16)++;
/* not a surrogate */
if ((c0 & 0xf800) != 0xd800)
return c0;
/* invalid: low surrogate instead of high */
if (c0 & 0x0400)
return 0xfffd;
c1 = **s16;
/* invalid: missing low surrogate */
if ((c1 & 0xfc00) != 0xdc00)
return 0xfffd;
/* valid surrogate pair */
++(*s16);
return (0x10000 - (0xd800 << 10) - 0xdc00) + (c0 << 10) + c1;
}
#define PUTC(c) \
do { \
if (pos < size) \
buf[pos] = (c); \
++pos; \
} while (0);
int vsnprintf(char *buf, size_t size, const char *fmt, va_list ap)
{
/* The maximum space required is to print a 64-bit number in octal */
char tmp[(sizeof(unsigned long long) * 8 + 2) / 3];
char *tmp_end = &tmp[ARRAY_SIZE(tmp)];
long long num;
int base;
const char *s;
size_t len, pos;
char sign;
int flags; /* flags to number() */
int field_width; /* width of output field */
int precision; /* min. # of digits for integers; max
number of chars for from string */
int qualifier; /* 'h', 'hh', 'l' or 'll' for integer fields */
va_list args;
/*
* We want to pass our input va_list to helper functions by reference,
* but there's an annoying edge case. If va_list was originally passed
* to us by value, we could just pass &ap down to the helpers. This is
* the case on, for example, X86_32.
* However, on X86_64 (and possibly others), va_list is actually a
* size-1 array containing a structure. Our function parameter ap has
* decayed from T[1] to T*, and &ap has type T** rather than T(*)[1],
* which is what will be expected by a function taking a va_list *
* parameter.
* One standard way to solve this mess is by creating a copy in a local
* variable of type va_list and then passing a pointer to that local
* copy instead, which is what we do here.
*/
va_copy(args, ap);
for (pos = 0; *fmt; ++fmt) {
if (*fmt != '%' || *++fmt == '%') {
PUTC(*fmt);
continue;
}
/* process flags */
flags = get_flags(&fmt);
/* get field width */
field_width = get_int(&fmt, &args);
if (field_width < 0) {
field_width = -field_width;
flags |= LEFT;
}
if (flags & LEFT)
flags &= ~ZEROPAD;
/* get the precision */
precision = -1;
if (*fmt == '.') {
++fmt;
precision = get_int(&fmt, &args);
if (precision >= 0)
flags &= ~ZEROPAD;
}
/* get the conversion qualifier */
qualifier = -1;
if (*fmt == 'h' || *fmt == 'l') {
qualifier = *fmt;
++fmt;
if (qualifier == *fmt) {
qualifier -= 'a'-'A';
++fmt;
}
}
sign = 0;
switch (*fmt) {
case 'c':
flags &= LEFT;
s = tmp;
if (qualifier == 'l') {
((u16 *)tmp)[0] = (u16)va_arg(args, unsigned int);
((u16 *)tmp)[1] = L'\0';
precision = INT_MAX;
goto wstring;
} else {
tmp[0] = (unsigned char)va_arg(args, int);
precision = len = 1;
}
goto output;
case 's':
flags &= LEFT;
if (precision < 0)
precision = INT_MAX;
s = va_arg(args, void *);
if (!s)
s = precision < 6 ? "" : "(null)";
else if (qualifier == 'l') {
wstring:
flags |= WIDE;
precision = len = utf16s_utf8nlen((const u16 *)s, precision);
goto output;
}
precision = len = strnlen(s, precision);
goto output;
/* integer number formats - set up the flags and "break" */
case 'o':
base = 8;
break;
case 'p':
if (precision < 0)
precision = 2 * sizeof(void *);
fallthrough;
case 'x':
flags |= SMALL;
fallthrough;
case 'X':
base = 16;
break;
case 'd':
case 'i':
flags |= SIGN;
fallthrough;
case 'u':
flags &= ~SPECIAL;
base = 10;
break;
default:
/*
* Bail out if the conversion specifier is invalid.
* There's probably a typo in the format string and the
* remaining specifiers are unlikely to match up with
* the arguments.
*/
goto fail;
}
if (*fmt == 'p') {
num = (unsigned long)va_arg(args, void *);
} else {
num = get_number(flags & SIGN, qualifier, &args);
}
sign = get_sign(&num, flags);
if (sign)
--field_width;
s = number(tmp_end, num, base, flags & SMALL);
len = tmp_end - s;
/* default precision is 1 */
if (precision < 0)
precision = 1;
/* precision is minimum number of digits to print */
if (precision < len)
precision = len;
if (flags & SPECIAL) {
/*
* For octal, a leading 0 is printed only if necessary,
* i.e. if it's not already there because of the
* precision.
*/
if (base == 8 && precision == len)
++precision;
/*
* For hexadecimal, the leading 0x is skipped if the
* output is empty, i.e. both the number and the
* precision are 0.
*/
if (base == 16 && precision > 0)
field_width -= 2;
else
flags &= ~SPECIAL;
}
/*
* For zero padding, increase the precision to fill the field
* width.
*/
if ((flags & ZEROPAD) && field_width > precision)
precision = field_width;
output:
/* Calculate the padding necessary */
field_width -= precision;
/* Leading padding with ' ' */
if (!(flags & LEFT))
while (field_width-- > 0)
PUTC(' ');
/* sign */
if (sign)
PUTC(sign);
/* 0x/0X for hexadecimal */
if (flags & SPECIAL) {
PUTC('0');
PUTC( 'X' | (flags & SMALL));
}
/* Zero padding and excess precision */
while (precision-- > len)
PUTC('0');
/* Actual output */
if (flags & WIDE) {
const u16 *ws = (const u16 *)s;
while (len-- > 0) {
u32 c32 = utf16_to_utf32(&ws);
u8 *s8;
size_t clen;
if (c32 < 0x80) {
PUTC(c32);
continue;
}
/* Number of trailing octets */
clen = 1 + (c32 >= 0x800) + (c32 >= 0x10000);
len -= clen;
s8 = (u8 *)&buf[pos];
/* Avoid writing partial character */
PUTC('\0');
pos += clen;
if (pos >= size)
continue;
/* Set high bits of leading octet */
*s8 = (0xf00 >> 1) >> clen;
/* Write trailing octets in reverse order */
for (s8 += clen; clen; --clen, c32 >>= 6)
*s8-- = 0x80 | (c32 & 0x3f);
/* Set low bits of leading octet */
*s8 |= c32;
}
} else {
while (len-- > 0)
PUTC(*s++);
}
/* Trailing padding with ' ' */
while (field_width-- > 0)
PUTC(' ');
}
fail:
va_end(args);
if (size)
buf[min(pos, size-1)] = '\0';
return pos;
}
int snprintf(char *buf, size_t size, const char *fmt, ...)
{
va_list args;
int i;
va_start(args, fmt);
i = vsnprintf(buf, size, fmt, args);
va_end(args);
return i;
}
@@ -0,0 +1,96 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/efi.h>
#include <asm/boot.h>
#include <asm/cpuid/api.h>
#include <asm/desc.h>
#include <asm/efi.h>
#include "efistub.h"
#include "x86-stub.h"
bool efi_no5lvl;
static void (*la57_toggle)(void *cr3);
static const struct desc_struct gdt[] = {
[GDT_ENTRY_KERNEL32_CS] = GDT_ENTRY_INIT(DESC_CODE32, 0, 0xfffff),
[GDT_ENTRY_KERNEL_CS] = GDT_ENTRY_INIT(DESC_CODE64, 0, 0xfffff),
};
/*
* Enabling (or disabling) 5 level paging is tricky, because it can only be
* done from 32-bit mode with paging disabled. This means not only that the
* code itself must be running from 32-bit addressable physical memory, but
* also that the root page table must be 32-bit addressable, as programming
* a 64-bit value into CR3 when running in 32-bit mode is not supported.
*/
efi_status_t efi_setup_5level_paging(void)
{
u8 tmpl_size = (u8 *)&trampoline_ljmp_imm_offset - (u8 *)&trampoline_32bit_src;
efi_status_t status;
u8 *la57_code;
if (!efi_is_64bit())
return EFI_SUCCESS;
/* check for 5 level paging support */
if (native_cpuid_eax(0) < 7 ||
!(native_cpuid_ecx(7) & (1 << (X86_FEATURE_LA57 & 31))))
return EFI_SUCCESS;
/* allocate some 32-bit addressable memory for code and a page table */
status = efi_allocate_pages(2 * PAGE_SIZE, (unsigned long *)&la57_code,
U32_MAX);
if (status != EFI_SUCCESS)
return status;
la57_toggle = memcpy(la57_code, trampoline_32bit_src, tmpl_size);
memset(la57_code + tmpl_size, 0x90, PAGE_SIZE - tmpl_size);
/*
* To avoid the need to allocate a 32-bit addressable stack, the
* trampoline uses a LJMP instruction to switch back to long mode.
* LJMP takes an absolute destination address, which needs to be
* fixed up at runtime.
*/
*(u32 *)&la57_code[trampoline_ljmp_imm_offset] += (unsigned long)la57_code;
efi_adjust_memory_range_protection((unsigned long)la57_toggle, PAGE_SIZE);
return EFI_SUCCESS;
}
void efi_5level_switch(void)
{
bool want_la57 = !efi_no5lvl;
bool have_la57 = native_read_cr4() & X86_CR4_LA57;
bool need_toggle = want_la57 ^ have_la57;
u64 *pgt = (void *)la57_toggle + PAGE_SIZE;
pgd_t *cr3 = (pgd_t *)native_read_cr3_pa();
u64 *new_cr3;
if (!la57_toggle || !need_toggle)
return;
if (!have_la57) {
/*
* 5 level paging will be enabled, so a root level page needs
* to be allocated from the 32-bit addressable physical region,
* with its first entry referring to the existing hierarchy.
*/
new_cr3 = memset(pgt, 0, PAGE_SIZE);
new_cr3[0] = (u64)cr3 | _PAGE_TABLE_NOENC;
} else {
/* take the new root table pointer from the current entry #0 */
new_cr3 = (u64 *)(native_pgd_val(cr3[0]) & PTE_PFN_MASK);
/* copy the new root table if it is not 32-bit addressable */
if ((u64)new_cr3 > U32_MAX)
new_cr3 = memcpy(pgt, new_cr3, PAGE_SIZE);
}
native_load_gdt(&(struct desc_ptr){ sizeof(gdt) - 1, (u64)gdt });
la57_toggle(new_cr3);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,17 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#include <linux/efi.h>
extern void trampoline_32bit_src(void *, bool);
extern const u16 trampoline_ljmp_imm_offset;
efi_status_t efi_adjust_memory_range_protection(unsigned long start,
unsigned long size);
#ifdef CONFIG_X86_64
efi_status_t efi_setup_5level_paging(void);
void efi_5level_switch(void);
#else
static inline efi_status_t efi_setup_5level_paging(void) { return EFI_SUCCESS; }
static inline void efi_5level_switch(void) {}
#endif
@@ -0,0 +1,68 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/efi.h>
#include <linux/zlib.h>
#include <asm/efi.h>
#include "efistub.h"
#include "inftrees.c"
#include "inffast.c"
#include "inflate.c"
extern unsigned char _gzdata_start[], _gzdata_end[];
extern u32 __aligned(1) payload_size;
static struct z_stream_s stream;
efi_status_t efi_zboot_decompress_init(unsigned long *alloc_size)
{
efi_status_t status;
int rc;
/* skip the 10 byte header, assume no recorded filename */
stream.next_in = _gzdata_start + 10;
stream.avail_in = _gzdata_end - stream.next_in;
status = efi_allocate_pages(zlib_inflate_workspacesize(),
(unsigned long *)&stream.workspace,
ULONG_MAX);
if (status != EFI_SUCCESS)
return status;
rc = zlib_inflateInit2(&stream, -MAX_WBITS);
if (rc != Z_OK) {
efi_err("failed to initialize GZIP decompressor: %d\n", rc);
status = EFI_LOAD_ERROR;
goto out;
}
*alloc_size = payload_size;
return EFI_SUCCESS;
out:
efi_free(zlib_inflate_workspacesize(), (unsigned long)stream.workspace);
return status;
}
efi_status_t efi_zboot_decompress(u8 *out, unsigned long outlen)
{
int rc;
stream.next_out = out;
stream.avail_out = outlen;
rc = zlib_inflate(&stream, 0);
zlib_inflateEnd(&stream);
efi_free(zlib_inflate_workspacesize(), (unsigned long)stream.workspace);
if (rc != Z_STREAM_END) {
efi_err("GZIP decompression failed with status %d\n", rc);
return EFI_LOAD_ERROR;
}
efi_cache_sync_image((unsigned long)out, outlen);
return EFI_SUCCESS;
}
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/efi.h>
#include <linux/zstd.h>
#include <asm/efi.h>
#include "decompress_sources.h"
#include "efistub.h"
extern unsigned char _gzdata_start[], _gzdata_end[];
extern u32 __aligned(1) payload_size;
static size_t wksp_size;
static void *wksp;
efi_status_t efi_zboot_decompress_init(unsigned long *alloc_size)
{
efi_status_t status;
wksp_size = zstd_dctx_workspace_bound();
status = efi_allocate_pages(wksp_size, (unsigned long *)&wksp, ULONG_MAX);
if (status != EFI_SUCCESS)
return status;
*alloc_size = payload_size;
return EFI_SUCCESS;
}
efi_status_t efi_zboot_decompress(u8 *out, unsigned long outlen)
{
zstd_dctx *dctx = zstd_init_dctx(wksp, wksp_size);
size_t ret;
int retval;
ret = zstd_decompress_dctx(dctx, out, outlen, _gzdata_start,
_gzdata_end - _gzdata_start - 4);
efi_free(wksp_size, (unsigned long)wksp);
retval = zstd_get_error_code(ret);
if (retval) {
efi_err("ZSTD-decompression failed with status %d\n", retval);
return EFI_LOAD_ERROR;
}
efi_cache_sync_image((unsigned long)out, outlen);
return EFI_SUCCESS;
}
@@ -0,0 +1,177 @@
/* SPDX-License-Identifier: GPL-2.0 */
#include <linux/pe.h>
#ifdef CONFIG_64BIT
.set .Lextra_characteristics, 0x0
.set .Lpe_opt_magic, IMAGE_NT_OPTIONAL_HDR64_MAGIC
#else
.set .Lextra_characteristics, IMAGE_FILE_32BIT_MACHINE
.set .Lpe_opt_magic, IMAGE_NT_OPTIONAL_HDR32_MAGIC
#endif
.section ".head", "a"
.globl __efistub_efi_zboot_header
__efistub_efi_zboot_header:
.Ldoshdr:
.long IMAGE_DOS_SIGNATURE
.ascii "zimg" // image type
.long __efistub__gzdata_start - .Ldoshdr // payload offset
.long __efistub__gzdata_size - ZBOOT_SIZE_LEN // payload size
.long 0, 0 // reserved
.asciz COMP_TYPE // compression type
.org .Ldoshdr + 0x38
.long LINUX_PE_MAGIC
.long .Lpehdr - .Ldoshdr // PE header offset
.Lpehdr:
.long IMAGE_NT_SIGNATURE
.short MACHINE_TYPE
.short .Lsection_count
.long 0
.long 0
.long 0
.short .Lsection_table - .Loptional_header
.short IMAGE_FILE_DEBUG_STRIPPED | \
IMAGE_FILE_EXECUTABLE_IMAGE | \
IMAGE_FILE_LINE_NUMS_STRIPPED |\
.Lextra_characteristics
.Loptional_header:
.short .Lpe_opt_magic
.byte 0, 0
.long _etext - .Lefi_header_end
.long __data_size
.long 0
.long __efistub_efi_zboot_entry - .Ldoshdr
.long .Lefi_header_end - .Ldoshdr
#ifdef CONFIG_64BIT
.quad 0
#else
.long _etext - .Ldoshdr, 0x0
#endif
.long 4096
.long 512
.short 0, 0
.short LINUX_EFISTUB_MAJOR_VERSION // MajorImageVersion
.short LINUX_EFISTUB_MINOR_VERSION // MinorImageVersion
.short 0, 0
.long 0
.long _end - .Ldoshdr
.long .Lefi_header_end - .Ldoshdr
.long 0
.short IMAGE_SUBSYSTEM_EFI_APPLICATION
.short IMAGE_DLLCHARACTERISTICS_NX_COMPAT
#ifdef CONFIG_64BIT
.quad 0, 0, 0, 0
#else
.long 0, 0, 0, 0
#endif
.long 0
.long (.Lsection_table - .) / 8
.quad 0 // ExportTable
.quad 0 // ImportTable
.quad 0 // ResourceTable
.quad 0 // ExceptionTable
.quad 0 // CertificationTable
.quad 0 // BaseRelocationTable
#if defined(PE_DLL_CHAR_EX) || defined(CONFIG_DEBUG_EFI)
.long .Lefi_debug_table - .Ldoshdr // DebugTable
.long .Lefi_debug_table_size
.section ".rodata", "a"
.p2align 2
.Lefi_debug_table:
// EFI_IMAGE_DEBUG_DIRECTORY_ENTRY[]
#ifdef PE_DLL_CHAR_EX
.long 0 // Characteristics
.long 0 // TimeDateStamp
.short 0 // MajorVersion
.short 0 // MinorVersion
.long IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS // Type
.long 4 // SizeOfData
.long 0 // RVA
.long .Lefi_dll_characteristics_ex - .Ldoshdr // FileOffset
#endif
#ifdef CONFIG_DEBUG_EFI
.long 0 // Characteristics
.long 0 // TimeDateStamp
.short 0 // MajorVersion
.short 0 // MinorVersion
.long IMAGE_DEBUG_TYPE_CODEVIEW // Type
.long .Lefi_debug_entry_size // SizeOfData
.long 0 // RVA
.long .Lefi_debug_entry - .Ldoshdr // FileOffset
#endif
.set .Lefi_debug_table_size, . - .Lefi_debug_table
.previous
#endif
.Lsection_table:
.ascii ".text\0\0\0"
.long _etext - .Lefi_header_end
.long .Lefi_header_end - .Ldoshdr
.long _etext - .Lefi_header_end
.long .Lefi_header_end - .Ldoshdr
.long 0, 0
.short 0, 0
.long IMAGE_SCN_CNT_CODE | \
IMAGE_SCN_MEM_READ | \
IMAGE_SCN_MEM_EXECUTE
#ifdef CONFIG_EFI_SBAT
.ascii ".sbat\0\0\0"
.long __sbat_size
.long _sbat - .Ldoshdr
.long __sbat_size
.long _sbat - .Ldoshdr
.long 0, 0
.short 0, 0
.long IMAGE_SCN_CNT_INITIALIZED_DATA | \
IMAGE_SCN_MEM_READ | \
IMAGE_SCN_MEM_DISCARDABLE
.pushsection ".sbat", "a", @progbits
.incbin CONFIG_EFI_SBAT_FILE
.popsection
#endif
.ascii ".data\0\0\0"
.long __data_size
.long _data - .Ldoshdr
.long __data_rawsize
.long _data - .Ldoshdr
.long 0, 0
.short 0, 0
.long IMAGE_SCN_CNT_INITIALIZED_DATA | \
IMAGE_SCN_MEM_READ | \
IMAGE_SCN_MEM_WRITE
.set .Lsection_count, (. - .Lsection_table) / 40
#ifdef PE_DLL_CHAR_EX
.Lefi_dll_characteristics_ex:
.long PE_DLL_CHAR_EX
#endif
#ifdef CONFIG_DEBUG_EFI
.Lefi_debug_entry:
// EFI_IMAGE_DEBUG_CODEVIEW_NB10_ENTRY
.ascii "NB10" // Signature
.long 0 // Unknown
.long 0 // Unknown2
.long 0 // Unknown3
.asciz ZBOOT_EFI_PATH
.set .Lefi_debug_entry_size, . - .Lefi_debug_entry
#endif
.p2align 12
.Lefi_header_end:
@@ -0,0 +1,100 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/efi.h>
#include <linux/pe.h>
#include <asm/efi.h>
#include <linux/unaligned.h>
#include "efistub.h"
static unsigned long alloc_preferred_address(unsigned long alloc_size)
{
#ifdef EFI_KIMG_PREFERRED_ADDRESS
efi_physical_addr_t efi_addr = EFI_KIMG_PREFERRED_ADDRESS;
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;
}
void __weak efi_cache_sync_image(unsigned long image_base,
unsigned long alloc_size)
{
// Provided by the arch to perform the cache maintenance necessary for
// executable code loaded into memory to be safe for execution.
}
struct sysfb_display_info *alloc_primary_display(void)
{
return __alloc_primary_display();
}
asmlinkage efi_status_t __efiapi
efi_zboot_entry(efi_handle_t handle, efi_system_table_t *systab)
{
char *cmdline_ptr __free(efi_pool) = NULL;
unsigned long image_base, alloc_size;
efi_loaded_image_t *image;
efi_status_t status;
WRITE_ONCE(efi_system_table, systab);
status = efi_bs_call(handle_protocol, handle,
&LOADED_IMAGE_PROTOCOL_GUID, (void **)&image);
if (status != EFI_SUCCESS) {
efi_err("Failed to locate parent's loaded image protocol\n");
return status;
}
status = efi_handle_cmdline(image, &cmdline_ptr);
if (status != EFI_SUCCESS)
return status;
efi_info("Decompressing Linux Kernel...\n");
status = efi_zboot_decompress_init(&alloc_size);
if (status != EFI_SUCCESS)
return status;
// 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_random_alloc(alloc_size, min_kimg_align, &image_base,
seed, EFI_LOADER_CODE, 0, EFI_ALLOC_LIMIT);
if (status != EFI_SUCCESS) {
efi_err("Failed to allocate memory\n");
return status;
}
}
// Decompress the payload into the newly allocated buffer
status = efi_zboot_decompress((void *)image_base, alloc_size) ?:
efi_stub_common(handle, image, image_base, cmdline_ptr);
efi_free(alloc_size, image_base);
return status;
}
@@ -0,0 +1,63 @@
/* SPDX-License-Identifier: GPL-2.0 */
ENTRY(__efistub_efi_zboot_header);
PROVIDE(zboot_code_size = ABSOLUTE(0));
SECTIONS
{
.head : ALIGN(4096) {
*(.head)
}
.text : {
*(.text* .init.text*)
}
.rodata : ALIGN(8) {
__efistub__gzdata_start = .;
*(.gzdata)
__efistub_payload_size = . - 4;
__efistub__gzdata_end = .;
*(.rodata* .init.rodata* .srodata*)
. = ALIGN(4);
__efistub_code_size = .;
LONG(zboot_code_size);
_etext = ALIGN(4096);
. = _etext;
}
.sbat : ALIGN(4096) {
_sbat = .;
*(.sbat)
_esbat = ALIGN(4096);
. = _esbat;
}
.data : ALIGN(4096) {
_data = .;
*(.data* .init.data*)
_edata = ALIGN(512);
. = _edata;
}
.bss : {
*(.bss* .init.bss*)
_end = ALIGN(512);
. = _end;
}
/DISCARD/ : {
*(.discard .discard.*)
*(.modinfo .init.modinfo)
}
}
PROVIDE(__efistub__gzdata_size =
ABSOLUTE(__efistub__gzdata_end - __efistub__gzdata_start));
PROVIDE(__data_rawsize = ABSOLUTE(_edata - _data));
PROVIDE(__data_size = ABSOLUTE(_end - _data));
PROVIDE(__sbat_size = ABSOLUTE(_esbat - _sbat));