From 34ff636f322c6d5e4f0a52338fa63a7dff09a495 Mon Sep 17 00:00:00 2001 From: Joel Fernandes Date: Sat, 18 Jul 2026 13:15:36 -0400 Subject: scftorture: Count single_rpc offline failures in statistics output scf_torture_stats_print() aggregates each invoker thread's counters into a local scf_statistics structure before printing, but the n_single_rpc_ofl field is missing from the aggregation loop. As a result, the "single_rpc_ofl" value printed in the statistics line is always zero, even when smp_call_function_single() invocations for the RPC test have failed due to offline CPUs and been counted by the invoker threads. Add the missing accumulation so that the printed value reflects the actual counts. Signed-off-by: Joel Fernandes Signed-off-by: Paul E. McKenney --- drivers/firmware/efi/Kconfig | 345 ++++++ drivers/firmware/efi/Makefile | 46 + drivers/firmware/efi/apple-properties.c | 236 ++++ drivers/firmware/efi/arm-runtime.c | 178 +++ drivers/firmware/efi/capsule-loader.c | 342 ++++++ drivers/firmware/efi/capsule.c | 315 +++++ drivers/firmware/efi/cper-arm.c | 343 ++++++ drivers/firmware/efi/cper-x86.c | 361 ++++++ drivers/firmware/efi/cper.c | 779 ++++++++++++ drivers/firmware/efi/cper_cxl.c | 156 +++ drivers/firmware/efi/dev-path-parser.c | 181 +++ drivers/firmware/efi/earlycon.c | 275 +++++ drivers/firmware/efi/efi-bgrt.c | 91 ++ drivers/firmware/efi/efi-init.c | 278 +++++ drivers/firmware/efi/efi-pstore.c | 306 +++++ drivers/firmware/efi/efi.c | 1216 +++++++++++++++++++ drivers/firmware/efi/efibc.c | 92 ++ drivers/firmware/efi/embedded-firmware.c | 147 +++ drivers/firmware/efi/esrt.c | 429 +++++++ drivers/firmware/efi/fdtparams.c | 133 +++ drivers/firmware/efi/libstub/Makefile | 174 +++ drivers/firmware/efi/libstub/Makefile.zboot | 61 + drivers/firmware/efi/libstub/alignedmem.c | 61 + drivers/firmware/efi/libstub/arm32-stub.c | 134 +++ drivers/firmware/efi/libstub/arm64-stub.c | 63 + drivers/firmware/efi/libstub/arm64.c | 139 +++ drivers/firmware/efi/libstub/bitmap.c | 41 + drivers/firmware/efi/libstub/efi-stub-entry.c | 89 ++ drivers/firmware/efi/libstub/efi-stub-helper.c | 776 ++++++++++++ drivers/firmware/efi/libstub/efi-stub.c | 280 +++++ drivers/firmware/efi/libstub/efistub.h | 1263 ++++++++++++++++++++ drivers/firmware/efi/libstub/fdt.c | 379 ++++++ drivers/firmware/efi/libstub/file.c | 331 +++++ drivers/firmware/efi/libstub/find.c | 43 + drivers/firmware/efi/libstub/gop.c | 532 +++++++++ drivers/firmware/efi/libstub/intrinsics.c | 74 ++ drivers/firmware/efi/libstub/kaslr.c | 157 +++ drivers/firmware/efi/libstub/loongarch-stub.c | 124 ++ drivers/firmware/efi/libstub/loongarch-stub.h | 4 + drivers/firmware/efi/libstub/loongarch.c | 105 ++ drivers/firmware/efi/libstub/mem.c | 208 ++++ drivers/firmware/efi/libstub/pci.c | 94 ++ drivers/firmware/efi/libstub/primary_display.c | 56 + drivers/firmware/efi/libstub/printk.c | 154 +++ drivers/firmware/efi/libstub/random.c | 179 +++ drivers/firmware/efi/libstub/randomalloc.c | 141 +++ drivers/firmware/efi/libstub/riscv-stub.c | 58 + drivers/firmware/efi/libstub/riscv.c | 98 ++ drivers/firmware/efi/libstub/secureboot.c | 62 + drivers/firmware/efi/libstub/skip_spaces.c | 12 + drivers/firmware/efi/libstub/smbios.c | 68 ++ drivers/firmware/efi/libstub/string.c | 204 ++++ drivers/firmware/efi/libstub/systable.c | 8 + drivers/firmware/efi/libstub/tpm.c | 196 +++ drivers/firmware/efi/libstub/unaccepted_memory.c | 223 ++++ drivers/firmware/efi/libstub/vsprintf.c | 564 +++++++++ drivers/firmware/efi/libstub/x86-5lvl.c | 96 ++ drivers/firmware/efi/libstub/x86-stub.c | 1070 +++++++++++++++++ drivers/firmware/efi/libstub/x86-stub.h | 17 + .../firmware/efi/libstub/zboot-decompress-gzip.c | 68 ++ .../firmware/efi/libstub/zboot-decompress-zstd.c | 49 + drivers/firmware/efi/libstub/zboot-header.S | 177 +++ drivers/firmware/efi/libstub/zboot.c | 100 ++ drivers/firmware/efi/libstub/zboot.lds | 63 + drivers/firmware/efi/memattr.c | 218 ++++ drivers/firmware/efi/memmap.c | 148 +++ drivers/firmware/efi/mokvar-table.c | 359 ++++++ drivers/firmware/efi/ovmf-debug-log.c | 111 ++ drivers/firmware/efi/rci2-table.c | 142 +++ drivers/firmware/efi/reboot.c | 78 ++ drivers/firmware/efi/riscv-runtime.c | 159 +++ drivers/firmware/efi/runtime-wrappers.c | 604 ++++++++++ drivers/firmware/efi/stmm/mm_communication.h | 236 ++++ drivers/firmware/efi/stmm/tee_stmm_efi.c | 597 +++++++++ drivers/firmware/efi/sysfb_efi.c | 440 +++++++ drivers/firmware/efi/test/Makefile | 2 + drivers/firmware/efi/test/efi_test.c | 784 ++++++++++++ drivers/firmware/efi/test/efi_test.h | 124 ++ drivers/firmware/efi/tpm.c | 116 ++ drivers/firmware/efi/unaccepted_memory.c | 235 ++++ drivers/firmware/efi/vars.c | 267 +++++ 81 files changed, 19664 insertions(+) create mode 100644 drivers/firmware/efi/Kconfig create mode 100644 drivers/firmware/efi/Makefile create mode 100644 drivers/firmware/efi/apple-properties.c create mode 100644 drivers/firmware/efi/arm-runtime.c create mode 100644 drivers/firmware/efi/capsule-loader.c create mode 100644 drivers/firmware/efi/capsule.c create mode 100644 drivers/firmware/efi/cper-arm.c create mode 100644 drivers/firmware/efi/cper-x86.c create mode 100644 drivers/firmware/efi/cper.c create mode 100644 drivers/firmware/efi/cper_cxl.c create mode 100644 drivers/firmware/efi/dev-path-parser.c create mode 100644 drivers/firmware/efi/earlycon.c create mode 100644 drivers/firmware/efi/efi-bgrt.c create mode 100644 drivers/firmware/efi/efi-init.c create mode 100644 drivers/firmware/efi/efi-pstore.c create mode 100644 drivers/firmware/efi/efi.c create mode 100644 drivers/firmware/efi/efibc.c create mode 100644 drivers/firmware/efi/embedded-firmware.c create mode 100644 drivers/firmware/efi/esrt.c create mode 100644 drivers/firmware/efi/fdtparams.c create mode 100644 drivers/firmware/efi/libstub/Makefile create mode 100644 drivers/firmware/efi/libstub/Makefile.zboot create mode 100644 drivers/firmware/efi/libstub/alignedmem.c create mode 100644 drivers/firmware/efi/libstub/arm32-stub.c create mode 100644 drivers/firmware/efi/libstub/arm64-stub.c create mode 100644 drivers/firmware/efi/libstub/arm64.c create mode 100644 drivers/firmware/efi/libstub/bitmap.c create mode 100644 drivers/firmware/efi/libstub/efi-stub-entry.c create mode 100644 drivers/firmware/efi/libstub/efi-stub-helper.c create mode 100644 drivers/firmware/efi/libstub/efi-stub.c create mode 100644 drivers/firmware/efi/libstub/efistub.h create mode 100644 drivers/firmware/efi/libstub/fdt.c create mode 100644 drivers/firmware/efi/libstub/file.c create mode 100644 drivers/firmware/efi/libstub/find.c create mode 100644 drivers/firmware/efi/libstub/gop.c create mode 100644 drivers/firmware/efi/libstub/intrinsics.c create mode 100644 drivers/firmware/efi/libstub/kaslr.c create mode 100644 drivers/firmware/efi/libstub/loongarch-stub.c create mode 100644 drivers/firmware/efi/libstub/loongarch-stub.h create mode 100644 drivers/firmware/efi/libstub/loongarch.c create mode 100644 drivers/firmware/efi/libstub/mem.c create mode 100644 drivers/firmware/efi/libstub/pci.c create mode 100644 drivers/firmware/efi/libstub/primary_display.c create mode 100644 drivers/firmware/efi/libstub/printk.c create mode 100644 drivers/firmware/efi/libstub/random.c create mode 100644 drivers/firmware/efi/libstub/randomalloc.c create mode 100644 drivers/firmware/efi/libstub/riscv-stub.c create mode 100644 drivers/firmware/efi/libstub/riscv.c create mode 100644 drivers/firmware/efi/libstub/secureboot.c create mode 100644 drivers/firmware/efi/libstub/skip_spaces.c create mode 100644 drivers/firmware/efi/libstub/smbios.c create mode 100644 drivers/firmware/efi/libstub/string.c create mode 100644 drivers/firmware/efi/libstub/systable.c create mode 100644 drivers/firmware/efi/libstub/tpm.c create mode 100644 drivers/firmware/efi/libstub/unaccepted_memory.c create mode 100644 drivers/firmware/efi/libstub/vsprintf.c create mode 100644 drivers/firmware/efi/libstub/x86-5lvl.c create mode 100644 drivers/firmware/efi/libstub/x86-stub.c create mode 100644 drivers/firmware/efi/libstub/x86-stub.h create mode 100644 drivers/firmware/efi/libstub/zboot-decompress-gzip.c create mode 100644 drivers/firmware/efi/libstub/zboot-decompress-zstd.c create mode 100644 drivers/firmware/efi/libstub/zboot-header.S create mode 100644 drivers/firmware/efi/libstub/zboot.c create mode 100644 drivers/firmware/efi/libstub/zboot.lds create mode 100644 drivers/firmware/efi/memattr.c create mode 100644 drivers/firmware/efi/memmap.c create mode 100644 drivers/firmware/efi/mokvar-table.c create mode 100644 drivers/firmware/efi/ovmf-debug-log.c create mode 100644 drivers/firmware/efi/rci2-table.c create mode 100644 drivers/firmware/efi/reboot.c create mode 100644 drivers/firmware/efi/riscv-runtime.c create mode 100644 drivers/firmware/efi/runtime-wrappers.c create mode 100644 drivers/firmware/efi/stmm/mm_communication.h create mode 100644 drivers/firmware/efi/stmm/tee_stmm_efi.c create mode 100644 drivers/firmware/efi/sysfb_efi.c create mode 100644 drivers/firmware/efi/test/Makefile create mode 100644 drivers/firmware/efi/test/efi_test.c create mode 100644 drivers/firmware/efi/test/efi_test.h create mode 100644 drivers/firmware/efi/tpm.c create mode 100644 drivers/firmware/efi/unaccepted_memory.c create mode 100644 drivers/firmware/efi/vars.c (limited to 'drivers/firmware/efi') diff --git a/drivers/firmware/efi/Kconfig b/drivers/firmware/efi/Kconfig new file mode 100644 index 000000000..29e072929 --- /dev/null +++ b/drivers/firmware/efi/Kconfig @@ -0,0 +1,345 @@ +# SPDX-License-Identifier: GPL-2.0-only +menu "EFI (Extensible Firmware Interface) Support" + depends on EFI + +config EFI_ESRT + bool + depends on EFI + default y + +config EFI_VARS_PSTORE + tristate "Register efivars backend for pstore" + depends on PSTORE + select UCS2_STRING + default y + help + Say Y here to enable use efivars as a backend to pstore. This + will allow writing console messages, crash dumps, or anything + else supported by pstore to EFI variables. + +config EFI_VARS_PSTORE_DEFAULT_DISABLE + bool "Disable using efivars as a pstore backend by default" + depends on EFI_VARS_PSTORE + default n + help + Saying Y here will disable the use of efivars as a storage + backend for pstore by default. This setting can be overridden + using the efivars module's pstore_disable parameter. + +config EFI_SOFT_RESERVE + bool "Reserve EFI Specific Purpose Memory" + depends on EFI && EFI_STUB && ACPI_HMAT + default ACPI_HMAT + help + On systems that have mixed performance classes of memory EFI + may indicate specific purpose memory with an attribute (See + EFI_MEMORY_SP in UEFI 2.8). A memory range tagged with this + attribute may have unique performance characteristics compared + to the system's general purpose "System RAM" pool. On the + expectation that such memory has application specific usage, + and its base EFI memory type is "conventional" answer Y to + arrange for the kernel to reserve it as a "Soft Reserved" + resource, and set aside for direct-access (device-dax) by + default. The memory range can later be optionally assigned to + the page allocator by system administrator policy via the + device-dax kmem facility. Say N to have the kernel treat this + memory as "System RAM" by default. + + If unsure, say Y. + +config EFI_DXE_MEM_ATTRIBUTES + bool "Adjust memory attributes in EFISTUB" + depends on EFI && EFI_STUB && X86 + default y + help + UEFI specification does not guarantee all memory to be + accessible for both write and execute as the kernel expects + it to be. + Use DXE services to check and alter memory protection + attributes during boot via EFISTUB to ensure that memory + ranges used by the kernel are writable and executable. + +config EFI_PARAMS_FROM_FDT + bool + help + Select this config option from the architecture Kconfig if + the EFI runtime support gets system table address, memory + map address, and other parameters from the device tree. + +config EFI_RUNTIME_WRAPPERS + bool + +config EFI_GENERIC_STUB + bool + +config EFI_ZBOOT + bool "Enable the generic EFI decompressor" + depends on EFI_GENERIC_STUB && !ARM + select HAVE_KERNEL_GZIP + select HAVE_KERNEL_ZSTD + help + Create the bootable image as an EFI application that carries the + actual kernel image in compressed form, and decompresses it into + memory before executing it. For compatibility with non-EFI loaders, + the payload can be decompressed and executed by the loader as well, + provided that the loader implements the decompression algorithm. + (The compression algorithm used is described in the zboot header) + +config EFI_ARMSTUB_DTB_LOADER + bool "Enable the DTB loader" + depends on EFI_GENERIC_STUB && !RISCV && !LOONGARCH + default y + help + Select this config option to add support for the dtb= command + line parameter, allowing a device tree blob to be loaded into + memory from the EFI System Partition by the stub. + + If the device tree is provided by the platform or by + the bootloader this option may not be needed. + But, for various development reasons and to maintain existing + functionality for bootloaders that do not have such support + this option is necessary. + +config EFI_BOOTLOADER_CONTROL + tristate "EFI Bootloader Control" + select UCS2_STRING + default n + help + This module installs a reboot hook, such that if reboot() is + invoked with a string argument NNN, "NNN" is copied to the + "LoaderEntryOneShot" EFI variable, to be read by the + bootloader. If the string matches one of the boot labels + defined in its configuration, the bootloader will boot once + to that label. The "LoaderEntryRebootReason" EFI variable is + set with the reboot reason: "reboot" or "shutdown". The + bootloader reads this reboot reason and takes particular + action according to its policy. + +config EFI_CAPSULE_LOADER + tristate "EFI capsule loader" + depends on EFI + help + This option exposes a loader interface "/dev/efi_capsule_loader" for + users to load EFI capsules. This driver requires working runtime + capsule support in the firmware, which many OEMs do not provide. + + Most users should say N. + +config EFI_CAPSULE_QUIRK_QUARK_CSH + bool "Add support for Quark capsules with non-standard headers" + depends on X86 && !64BIT + select EFI_CAPSULE_LOADER + default y + help + Add support for processing Quark X1000 EFI capsules, whose header + layout deviates from the layout mandated by the UEFI specification. + +config EFI_TEST + tristate "EFI Runtime Service Tests Support" + depends on EFI + default n + help + This driver uses the efi. function pointers directly instead + of going through the efivar API, because it is not trying to test the + kernel subsystem, just for testing the UEFI runtime service + interfaces which are provided by the firmware. This driver is used + by the Firmware Test Suite (FWTS) for testing the UEFI runtime + interfaces readiness of the firmware. + Details for FWTS are available from: + + + Say Y here to enable the runtime services support via /dev/efi_test. + If unsure, say N. + +config EFI_DEV_PATH_PARSER + bool + +config APPLE_PROPERTIES + bool "Apple Device Properties" + depends on EFI_STUB && X86 + select EFI_DEV_PATH_PARSER + select UCS2_STRING + help + Retrieve properties from EFI on Apple Macs and assign them to + devices, allowing for improved support of Apple hardware. + Properties that would otherwise be missing include the + Thunderbolt Device ROM and GPU configuration data. + + If unsure, say Y if you have a Mac. Otherwise N. + +config RESET_ATTACK_MITIGATION + bool "Reset memory attack mitigation" + depends on EFI_STUB + help + Request that the firmware clear the contents of RAM after a reboot + using the TCG Platform Reset Attack Mitigation specification. This + protects against an attacker forcibly rebooting the system while it + still contains secrets in RAM, booting another OS and extracting the + secrets. This should only be enabled when userland is configured to + clear the MemoryOverwriteRequest flag on clean shutdown after secrets + have been evicted, since otherwise it will trigger even on clean + reboots. + +config EFI_RCI2_TABLE + bool "EFI Runtime Configuration Interface Table Version 2 Support" + depends on X86 || COMPILE_TEST + help + Displays the content of the Runtime Configuration Interface + Table version 2 on Dell EMC PowerEdge systems as a binary + attribute 'rci2' under /sys/firmware/efi/tables directory. + + RCI2 table contains BIOS HII in XML format and is used to populate + BIOS setup page in Dell EMC OpenManage Server Administrator tool. + The BIOS setup page contains BIOS tokens which can be configured. + + Say Y here for Dell EMC PowerEdge systems. + +config EFI_DISABLE_PCI_DMA + bool "Clear Busmaster bit on PCI bridges during ExitBootServices()" + help + Disable the busmaster bit in the control register on all PCI bridges + while calling ExitBootServices() and passing control to the runtime + kernel. System firmware may configure the IOMMU to prevent malicious + PCI devices from being able to attack the OS via DMA. However, since + firmware can't guarantee that the OS is IOMMU-aware, it will tear + down IOMMU configuration when ExitBootServices() is called. This + leaves a window between where a hostile device could still cause + damage before Linux configures the IOMMU again. + + If you say Y here, the EFI stub will clear the busmaster bit on all + PCI bridges before ExitBootServices() is called. This will prevent + any malicious PCI devices from being able to perform DMA until the + kernel reenables busmastering after configuring the IOMMU. + + This option will cause failures with some poorly behaved hardware + and should not be enabled without testing. The kernel commandline + options "efi=disable_early_pci_dma" or "efi=no_disable_early_pci_dma" + may be used to override this option. + +config EFI_EARLYCON + def_bool y + depends on SERIAL_EARLYCON && !ARM + select FONT_SUPPORT + select ARCH_USE_MEMREMAP_PROT + +config EFI_CUSTOM_SSDT_OVERLAYS + bool "Load custom ACPI SSDT overlay from an EFI variable" + depends on ACPI + default ACPI_TABLE_UPGRADE + help + Allow loading of an ACPI SSDT overlay from an EFI variable specified + by a kernel command line option. + + See Documentation/admin-guide/acpi/ssdt-overlays.rst for more + information. + +config EFI_DISABLE_RUNTIME + bool "Disable EFI runtime services support by default" + default y if PREEMPT_RT + help + Allow to disable the EFI runtime services support by default. This can + already be achieved by using the efi=noruntime option, but it could be + useful to have this default without any kernel command line parameter. + + The EFI runtime services are disabled by default when PREEMPT_RT is + enabled, because measurements have shown that some EFI functions calls + might take too much time to complete, causing large latencies which is + an issue for Real-Time kernels. + + This default can be overridden by using the efi=runtime option. + +config EFI_COCO_SECRET + bool "EFI Confidential Computing Secret Area Support" + help + Confidential Computing platforms (such as AMD SEV) allow the + Guest Owner to securely inject secrets during guest VM launch. + The secrets are placed in a designated EFI reserved memory area. + + In order to use the secrets in the kernel, the location of the secret + area (as published in the EFI config table) must be kept. + + If you say Y here, the address of the EFI secret area will be kept + for usage inside the kernel. This will allow the + virt/coco/efi_secret module to access the secrets, which in turn + allows userspace programs to access the injected secrets. + +config OVMF_DEBUG_LOG + bool "Expose OVMF firmware debug log via sysfs" + depends on EFI + help + Recent versions of the Open Virtual Machine Firmware + (edk2-stable202508 + newer) can write their debug log to a memory + buffer. This driver exposes the log content via sysfs + (/sys/firmware/efi/ovmf_debug_log). + +config UNACCEPTED_MEMORY + bool + depends on EFI_STUB + help + Some Virtual Machine platforms, such as Intel TDX, require + some memory to be "accepted" by the guest before it can be used. + This mechanism helps prevent malicious hosts from making changes + to guest memory. + + UEFI specification v2.9 introduced EFI_UNACCEPTED_MEMORY memory type. + + This option adds support for unaccepted memory and makes such memory + usable by the kernel. + +config EFI_EMBEDDED_FIRMWARE + bool + select CRYPTO_LIB_SHA256 + +config EFI_SBAT + def_bool y if EFI_SBAT_FILE!="" + +config EFI_SBAT_FILE + string "Embedded SBAT section file path" + depends on EFI_ZBOOT || (EFI_STUB && X86) + help + SBAT section provides a way to improve SecureBoot revocations of UEFI + binaries by introducing a generation-based mechanism. With SBAT, older + UEFI binaries can be prevented from booting by bumping the minimal + required generation for the specific component in the bootloader. + + Note: SBAT information is distribution specific, i.e. the owner of the + signing SecureBoot certificate must define the SBAT policy. Linux + kernel upstream does not define SBAT components and their generations. + + See https://github.com/rhboot/shim/blob/main/SBAT.md for the additional + details. + + Specify a file with SBAT data which is going to be embedded as '.sbat' + section into the kernel. + + If unsure, leave blank. + +endmenu + +config UEFI_CPER + bool + +config UEFI_CPER_ARM + bool + depends on UEFI_CPER && ( ARM || ARM64 ) + default y + +config UEFI_CPER_X86 + bool + depends on UEFI_CPER && X86 + default y + +config TEE_STMM_EFI + tristate "TEE-based EFI runtime variable service driver" + depends on EFI && OPTEE + help + Select this config option if TEE is compiled to include StandAloneMM + as a separate secure partition. It has the ability to check and store + EFI variables on an RPMB or any other non-volatile medium used by + StandAloneMM. + + Enabling this will change the EFI runtime services from the firmware + provided functions to TEE calls. + + To compile this driver as a module, choose M here: the module + will be called tee_stmm_efi. diff --git a/drivers/firmware/efi/Makefile b/drivers/firmware/efi/Makefile new file mode 100644 index 000000000..8efbcf699 --- /dev/null +++ b/drivers/firmware/efi/Makefile @@ -0,0 +1,46 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for linux kernel +# + +# +# ARM64 maps efi runtime services in userspace addresses +# which don't have KASAN shadow. So dereference of these addresses +# in efi_call_virt() will cause crash if this code instrumented. +# +KASAN_SANITIZE_runtime-wrappers.o := n + +obj-$(CONFIG_ACPI_BGRT) += efi-bgrt.o +obj-$(CONFIG_EFI) += efi.o vars.o reboot.o memattr.o tpm.o +obj-$(CONFIG_EFI) += memmap.o +ifneq ($(CONFIG_EFI_CAPSULE_LOADER),) +obj-$(CONFIG_EFI) += capsule.o +endif +obj-$(CONFIG_EFI_PARAMS_FROM_FDT) += fdtparams.o +obj-$(CONFIG_EFI_ESRT) += esrt.o +obj-$(CONFIG_EFI_VARS_PSTORE) += efi-pstore.o +obj-$(CONFIG_UEFI_CPER) += cper.o cper_cxl.o +obj-$(CONFIG_EFI_RUNTIME_WRAPPERS) += runtime-wrappers.o +subdir-$(CONFIG_EFI_STUB) += libstub +obj-$(CONFIG_EFI_BOOTLOADER_CONTROL) += efibc.o +obj-$(CONFIG_EFI_TEST) += test/ +obj-$(CONFIG_EFI_DEV_PATH_PARSER) += dev-path-parser.o +obj-$(CONFIG_APPLE_PROPERTIES) += apple-properties.o +obj-$(CONFIG_EFI_RCI2_TABLE) += rci2-table.o +obj-$(CONFIG_EFI_EMBEDDED_FIRMWARE) += embedded-firmware.o +obj-$(CONFIG_LOAD_UEFI_KEYS) += mokvar-table.o +obj-$(CONFIG_OVMF_DEBUG_LOG) += ovmf-debug-log.o + +obj-$(CONFIG_SYSFB) += sysfb_efi.o + +arm-obj-$(CONFIG_EFI) := efi-init.o arm-runtime.o +obj-$(CONFIG_ARM) += $(arm-obj-y) +obj-$(CONFIG_ARM64) += $(arm-obj-y) +riscv-obj-$(CONFIG_EFI) := efi-init.o riscv-runtime.o +obj-$(CONFIG_RISCV) += $(riscv-obj-y) +obj-$(CONFIG_EFI_CAPSULE_LOADER) += capsule-loader.o +obj-$(CONFIG_EFI_EARLYCON) += earlycon.o +obj-$(CONFIG_UEFI_CPER_ARM) += cper-arm.o +obj-$(CONFIG_UEFI_CPER_X86) += cper-x86.o +obj-$(CONFIG_UNACCEPTED_MEMORY) += unaccepted_memory.o +obj-$(CONFIG_TEE_STMM_EFI) += stmm/tee_stmm_efi.o diff --git a/drivers/firmware/efi/apple-properties.c b/drivers/firmware/efi/apple-properties.c new file mode 100644 index 000000000..2e525e17f --- /dev/null +++ b/drivers/firmware/efi/apple-properties.c @@ -0,0 +1,236 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * apple-properties.c - EFI device properties on Macs + * Copyright (C) 2016 Lukas Wunner + * + * Properties are stored either as: + * u8 arrays which can be retrieved with device_property_read_u8_array() or + * booleans which can be queried with device_property_present(). + */ + +#define pr_fmt(fmt) "apple-properties: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +static bool dump_properties __initdata; + +static int __init dump_properties_enable(char *arg) +{ + dump_properties = true; + return 1; +} + +__setup("dump_apple_properties", dump_properties_enable); + +struct dev_header { + u32 len; + u32 prop_count; + struct efi_dev_path path[]; + /* + * followed by key/value pairs, each key and value preceded by u32 len, + * len includes itself, value may be empty (in which case its len is 4) + */ +}; + +struct properties_header { + u32 len; + u32 version; + u32 dev_count; + struct dev_header dev_header[]; +}; + +static void __init unmarshal_key_value_pairs(struct dev_header *dev_header, + struct device *dev, const void *ptr, + struct property_entry entry[]) +{ + int i; + + for (i = 0; i < dev_header->prop_count; i++) { + int remaining = dev_header->len - (ptr - (void *)dev_header); + u32 key_len, val_len, entry_len; + const u8 *entry_data; + char *key; + + if (sizeof(key_len) > remaining) + break; + + key_len = *(typeof(key_len) *)ptr; + if (key_len + sizeof(val_len) > remaining || + key_len < sizeof(key_len) + sizeof(efi_char16_t) || + *(efi_char16_t *)(ptr + sizeof(key_len)) == 0) { + dev_err(dev, "invalid property name len at %#zx\n", + ptr - (void *)dev_header); + break; + } + + val_len = *(typeof(val_len) *)(ptr + key_len); + if (key_len + val_len > remaining || + val_len < sizeof(val_len)) { + dev_err(dev, "invalid property val len at %#zx\n", + ptr - (void *)dev_header + key_len); + break; + } + + /* 4 bytes to accommodate UTF-8 code points + null byte */ + key = kzalloc((key_len - sizeof(key_len)) * 4 + 1, GFP_KERNEL); + if (!key) { + dev_err(dev, "cannot allocate property name\n"); + break; + } + ucs2_as_utf8(key, ptr + sizeof(key_len), + key_len - sizeof(key_len)); + + entry_data = ptr + key_len + sizeof(val_len); + entry_len = val_len - sizeof(val_len); + if (entry_len) + entry[i] = PROPERTY_ENTRY_U8_ARRAY_LEN(key, entry_data, + entry_len); + else + entry[i] = PROPERTY_ENTRY_BOOL(key); + + if (dump_properties) { + dev_info(dev, "property: %s\n", key); + print_hex_dump(KERN_INFO, pr_fmt(), DUMP_PREFIX_OFFSET, + 16, 1, entry_data, entry_len, true); + } + + ptr += key_len + val_len; + } + + if (i != dev_header->prop_count) { + dev_err(dev, "got %d device properties, expected %u\n", i, + dev_header->prop_count); + print_hex_dump(KERN_ERR, pr_fmt(), DUMP_PREFIX_OFFSET, + 16, 1, dev_header, dev_header->len, true); + return; + } + + dev_info(dev, "assigning %d device properties\n", i); +} + +static int __init unmarshal_devices(struct properties_header *properties) +{ + size_t offset = offsetof(struct properties_header, dev_header[0]); + + while (offset + sizeof(struct dev_header) < properties->len) { + struct dev_header *dev_header = (void *)properties + offset; + struct property_entry *entry = NULL; + const struct efi_dev_path *ptr; + struct device *dev; + size_t len; + int ret, i; + + if (offset + dev_header->len > properties->len || + dev_header->len <= sizeof(*dev_header)) { + pr_err("invalid len in dev_header at %#zx\n", offset); + return -EINVAL; + } + + ptr = dev_header->path; + len = dev_header->len - sizeof(*dev_header); + + dev = efi_get_device_by_path(&ptr, &len); + if (IS_ERR(dev)) { + pr_err("device path parse error %ld at %#zx:\n", + PTR_ERR(dev), (void *)ptr - (void *)dev_header); + print_hex_dump(KERN_ERR, pr_fmt(), DUMP_PREFIX_OFFSET, + 16, 1, dev_header, dev_header->len, true); + dev = NULL; + goto skip_device; + } + + entry = kzalloc_objs(*entry, dev_header->prop_count + 1); + if (!entry) { + dev_err(dev, "cannot allocate properties\n"); + goto skip_device; + } + + unmarshal_key_value_pairs(dev_header, dev, ptr, entry); + if (!entry[0].name) + goto skip_device; + + ret = device_create_managed_software_node(dev, entry, NULL); + if (ret) + dev_err(dev, "error %d assigning properties\n", ret); + + for (i = 0; entry[i].name; i++) + kfree(entry[i].name); + +skip_device: + kfree(entry); + put_device(dev); + offset += dev_header->len; + } + + return 0; +} + +static int __init map_properties(void) +{ + struct properties_header *properties; + struct setup_data *data; + u32 data_len; + u64 pa_data; + int ret; + + if (!x86_apple_machine) + return 0; + + pa_data = boot_params.hdr.setup_data; + while (pa_data) { + data = memremap(pa_data, sizeof(*data), MEMREMAP_WB); + if (!data) { + pr_err("cannot map setup_data header\n"); + return -ENOMEM; + } + + if (data->type != SETUP_APPLE_PROPERTIES) { + pa_data = data->next; + memunmap(data); + continue; + } + + data_len = data->len; + memunmap(data); + + data = memremap(pa_data, sizeof(*data) + data_len, MEMREMAP_WB); + if (!data) { + pr_err("cannot map setup_data payload\n"); + return -ENOMEM; + } + + properties = (struct properties_header *)data->data; + if (properties->version != 1) { + pr_err("unsupported version:\n"); + print_hex_dump(KERN_ERR, pr_fmt(), DUMP_PREFIX_OFFSET, + 16, 1, properties, data_len, true); + ret = -ENOTSUPP; + } else if (properties->len != data_len) { + pr_err("length mismatch, expected %u\n", data_len); + print_hex_dump(KERN_ERR, pr_fmt(), DUMP_PREFIX_OFFSET, + 16, 1, properties, data_len, true); + ret = -EINVAL; + } else + ret = unmarshal_devices(properties); + + /* + * Can only free the setup_data payload but not its header + * to avoid breaking the chain of ->next pointers. + */ + data->len = 0; + memunmap(data); + memblock_phys_free(pa_data + sizeof(*data), data_len); + + return ret; + } + return 0; +} + +fs_initcall(map_properties); diff --git a/drivers/firmware/efi/arm-runtime.c b/drivers/firmware/efi/arm-runtime.c new file mode 100644 index 000000000..3167cab62 --- /dev/null +++ b/drivers/firmware/efi/arm-runtime.c @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Extensible Firmware Interface + * + * Based on Extensible Firmware Interface Specification version 2.4 + * + * Copyright (C) 2013, 2014 Linaro Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#if defined(CONFIG_PTDUMP_DEBUGFS) || defined(CONFIG_ARM_PTDUMP_DEBUGFS) +#include + +static struct ptdump_info efi_ptdump_info = { + .mm = &efi_mm, + .markers = (struct addr_marker[]){ + { 0, "UEFI runtime start" }, + { EFI_RUNTIME_MAP_END, "UEFI runtime end" }, + { -1, NULL } + }, + .base_addr = 0, +}; + +static int __init ptdump_init(void) +{ + if (efi_enabled(EFI_RUNTIME_SERVICES)) + ptdump_debugfs_register(&efi_ptdump_info, "efi_page_tables"); + + return 0; +} +device_initcall(ptdump_init); + +#endif + +static bool __init efi_virtmap_init(void) +{ + efi_memory_desc_t *md; + + efi_mm.pgd = pgd_alloc(&efi_mm); + mm_init_cpumask(&efi_mm); + init_new_context(NULL, &efi_mm); + + for_each_efi_memory_desc(md) { + phys_addr_t phys = md->phys_addr; + int ret; + + if (!(md->attribute & EFI_MEMORY_RUNTIME)) + continue; + if (md->virt_addr == U64_MAX) + return false; + + ret = efi_create_mapping(&efi_mm, md); + if (ret) { + pr_warn(" EFI remap %pa: failed to create mapping (%d)\n", + &phys, ret); + return false; + } + } + + if (efi_memattr_apply_permissions(&efi_mm, efi_set_mapping_permissions)) + return false; + + return true; +} + +/* + * Enable the UEFI Runtime Services if all prerequisites are in place, i.e., + * non-early mapping of the UEFI system table and virtual mappings for all + * EFI_MEMORY_RUNTIME regions. + */ +static int __init arm_enable_runtime_services(void) +{ + u64 mapsize; + + if (!efi_enabled(EFI_BOOT)) { + pr_info("EFI services will not be available.\n"); + return 0; + } + + efi_memmap_unmap(); + + mapsize = efi.memmap.desc_size * efi.memmap.nr_map; + + if (efi_memmap_init_late(efi.memmap.phys_map, mapsize)) { + pr_err("Failed to remap EFI memory map\n"); + return 0; + } + + if (efi_soft_reserve_enabled()) { + efi_memory_desc_t *md; + + for_each_efi_memory_desc(md) { + u64 md_size = md->num_pages << EFI_PAGE_SHIFT; + struct resource *res; + + if (!(md->attribute & EFI_MEMORY_SP)) + continue; + + res = kzalloc_obj(*res); + if (WARN_ON(!res)) + break; + + res->start = md->phys_addr; + res->end = md->phys_addr + md_size - 1; + res->name = "Soft Reserved"; + res->flags = IORESOURCE_MEM; + res->desc = IORES_DESC_SOFT_RESERVED; + + insert_resource(&iomem_resource, res); + } + } + + if (efi_runtime_disabled()) { + pr_info("EFI runtime services will be disabled.\n"); + return 0; + } + + if (efi_enabled(EFI_RUNTIME_SERVICES)) { + pr_info("EFI runtime services access via paravirt.\n"); + return 0; + } + + pr_info("Remapping and enabling EFI services.\n"); + + if (!efi_virtmap_init()) { + pr_err("UEFI virtual mapping missing or invalid -- runtime services will not be available\n"); + return -ENOMEM; + } + + /* Set up runtime services function pointers */ + efi_native_runtime_setup(); + set_bit(EFI_RUNTIME_SERVICES, &efi.flags); + + return 0; +} +early_initcall(arm_enable_runtime_services); + +void efi_virtmap_load(void) +{ + preempt_disable(); + efi_set_pgd(&efi_mm); +} + +void efi_virtmap_unload(void) +{ + efi_set_pgd(current->active_mm); + preempt_enable(); +} + + +static int __init arm_dmi_init(void) +{ + /* + * On arm64/ARM, DMI depends on UEFI, and dmi_setup() needs to + * be called early because dmi_id_init(), which is an arch_initcall + * itself, depends on dmi_scan_machine() having been called already. + */ + dmi_setup(); + return 0; +} +core_initcall(arm_dmi_init); diff --git a/drivers/firmware/efi/capsule-loader.c b/drivers/firmware/efi/capsule-loader.c new file mode 100644 index 000000000..8e8f81f0a --- /dev/null +++ b/drivers/firmware/efi/capsule-loader.c @@ -0,0 +1,342 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * EFI capsule loader driver. + * + * Copyright 2015 Intel Corporation + */ + +#define pr_fmt(fmt) "efi: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define NO_FURTHER_WRITE_ACTION -1 + +/** + * efi_free_all_buff_pages - free all previous allocated buffer pages + * @cap_info: pointer to current instance of capsule_info structure + * + * In addition to freeing buffer pages, it flags NO_FURTHER_WRITE_ACTION + * to cease processing data in subsequent write(2) calls until close(2) + * is called. + **/ +static void efi_free_all_buff_pages(struct capsule_info *cap_info) +{ + while (cap_info->index > 0) + __free_page(cap_info->pages[--cap_info->index]); + + cap_info->index = NO_FURTHER_WRITE_ACTION; +} + +int __efi_capsule_setup_info(struct capsule_info *cap_info) +{ + size_t pages_needed; + int ret; + void *temp_page; + + pages_needed = ALIGN(cap_info->total_size, PAGE_SIZE) / PAGE_SIZE; + + if (pages_needed == 0) { + pr_err("invalid capsule size\n"); + return -EINVAL; + } + + /* Check if the capsule binary supported */ + ret = efi_capsule_supported(cap_info->header.guid, + cap_info->header.flags, + cap_info->header.imagesize, + &cap_info->reset_type); + if (ret) { + pr_err("capsule not supported\n"); + return ret; + } + + temp_page = krealloc(cap_info->pages, + pages_needed * sizeof(void *), + GFP_KERNEL | __GFP_ZERO); + if (!temp_page) + return -ENOMEM; + + cap_info->pages = temp_page; + + temp_page = krealloc(cap_info->phys, + pages_needed * sizeof(phys_addr_t), + GFP_KERNEL | __GFP_ZERO); + if (!temp_page) + return -ENOMEM; + + cap_info->phys = temp_page; + + return 0; +} + +/** + * efi_capsule_setup_info - obtain the efi capsule header in the binary and + * setup capsule_info structure + * @cap_info: pointer to current instance of capsule_info structure + * @kbuff: a mapped first page buffer pointer + * @hdr_bytes: the total received number of bytes for efi header + * + * Platforms with non-standard capsule update mechanisms can override + * this __weak function so they can perform any required capsule + * image munging. See quark_quirk_function() for an example. + **/ +int __weak efi_capsule_setup_info(struct capsule_info *cap_info, void *kbuff, + size_t hdr_bytes) +{ + /* Only process data block that is larger than efi header size */ + if (hdr_bytes < sizeof(efi_capsule_header_t)) + return 0; + + memcpy(&cap_info->header, kbuff, sizeof(cap_info->header)); + cap_info->total_size = cap_info->header.imagesize; + + return __efi_capsule_setup_info(cap_info); +} + +/** + * efi_capsule_submit_update - invoke the efi_capsule_update API once binary + * upload done + * @cap_info: pointer to current instance of capsule_info structure + **/ +static ssize_t efi_capsule_submit_update(struct capsule_info *cap_info) +{ + bool do_vunmap = false; + int ret; + + /* + * cap_info->capsule may have been assigned already by a quirk + * handler, so only overwrite it if it is NULL + */ + if (!cap_info->capsule) { + cap_info->capsule = vmap(cap_info->pages, cap_info->index, + VM_MAP, PAGE_KERNEL); + if (!cap_info->capsule) + return -ENOMEM; + do_vunmap = true; + } + + ret = efi_capsule_update(cap_info->capsule, cap_info->phys); + if (do_vunmap) + vunmap(cap_info->capsule); + if (ret) { + pr_err("capsule update failed\n"); + return ret; + } + + /* Indicate capsule binary uploading is done */ + cap_info->index = NO_FURTHER_WRITE_ACTION; + + if (cap_info->header.flags & EFI_CAPSULE_PERSIST_ACROSS_RESET) { + pr_info("Successfully uploaded capsule file with reboot type '%s'\n", + !cap_info->reset_type ? "RESET_COLD" : + cap_info->reset_type == 1 ? "RESET_WARM" : + "RESET_SHUTDOWN"); + } else { + pr_info("Successfully processed capsule file\n"); + } + + return 0; +} + +/** + * efi_capsule_write - store the capsule binary and pass it to + * efi_capsule_update() API + * @file: file pointer + * @buff: buffer pointer + * @count: number of bytes in @buff + * @offp: not used + * + * Expectation: + * - A user space tool should start at the beginning of capsule binary and + * pass data in sequentially. + * - Users should close and re-open this file note in order to upload more + * capsules. + * - After an error returned, user should close the file and restart the + * operation for the next try otherwise -EIO will be returned until the + * file is closed. + * - An EFI capsule header must be located at the beginning of capsule + * binary file and passed in as first block data of write operation. + **/ +static ssize_t efi_capsule_write(struct file *file, const char __user *buff, + size_t count, loff_t *offp) +{ + int ret; + struct capsule_info *cap_info = file->private_data; + struct page *page; + void *kbuff = NULL; + size_t write_byte; + + if (count == 0) + return 0; + + /* Return error while NO_FURTHER_WRITE_ACTION is flagged */ + if (cap_info->index < 0) + return -EIO; + + /* Only alloc a new page when previous page is full */ + if (!cap_info->page_bytes_remain) { + page = alloc_page(GFP_KERNEL); + if (!page) { + ret = -ENOMEM; + goto failed; + } + + cap_info->pages[cap_info->index] = page; + cap_info->phys[cap_info->index] = page_to_phys(page); + cap_info->page_bytes_remain = PAGE_SIZE; + cap_info->index++; + } else { + page = cap_info->pages[cap_info->index - 1]; + } + + kbuff = kmap(page); + kbuff += PAGE_SIZE - cap_info->page_bytes_remain; + + /* Copy capsule binary data from user space to kernel space buffer */ + write_byte = min_t(size_t, count, cap_info->page_bytes_remain); + if (copy_from_user(kbuff, buff, write_byte)) { + ret = -EFAULT; + goto fail_unmap; + } + cap_info->page_bytes_remain -= write_byte; + + /* Setup capsule binary info structure */ + if (cap_info->header.headersize == 0) { + ret = efi_capsule_setup_info(cap_info, kbuff - cap_info->count, + cap_info->count + write_byte); + if (ret) + goto fail_unmap; + } + + cap_info->count += write_byte; + kunmap(page); + + /* Submit the full binary to efi_capsule_update() API */ + if (cap_info->header.headersize > 0 && + cap_info->count >= cap_info->total_size) { + if (cap_info->count > cap_info->total_size) { + pr_err("capsule upload size exceeded header defined size\n"); + ret = -EINVAL; + goto failed; + } + + ret = efi_capsule_submit_update(cap_info); + if (ret) + goto failed; + } + + return write_byte; + +fail_unmap: + kunmap(page); +failed: + efi_free_all_buff_pages(cap_info); + return ret; +} + +/** + * efi_capsule_release - called by file close + * @inode: not used + * @file: file pointer + * + * We will not free successfully submitted pages since efi update + * requires data to be maintained across system reboot. + **/ +static int efi_capsule_release(struct inode *inode, struct file *file) +{ + struct capsule_info *cap_info = file->private_data; + + if (cap_info->index > 0 && + (cap_info->header.headersize == 0 || + cap_info->count < cap_info->total_size)) { + pr_err("capsule upload not complete\n"); + efi_free_all_buff_pages(cap_info); + } + + kfree(cap_info->pages); + kfree(cap_info->phys); + kfree(file->private_data); + file->private_data = NULL; + return 0; +} + +/** + * efi_capsule_open - called by file open + * @inode: not used + * @file: file pointer + * + * Will allocate each capsule_info memory for each file open call. + * This provided the capability to support multiple file open feature + * where user is not needed to wait for others to finish in order to + * upload their capsule binary. + **/ +static int efi_capsule_open(struct inode *inode, struct file *file) +{ + struct capsule_info *cap_info; + + cap_info = kzalloc_obj(*cap_info); + if (!cap_info) + return -ENOMEM; + + cap_info->pages = kzalloc(sizeof(void *), GFP_KERNEL); + if (!cap_info->pages) { + kfree(cap_info); + return -ENOMEM; + } + + cap_info->phys = kzalloc_obj(phys_addr_t); + if (!cap_info->phys) { + kfree(cap_info->pages); + kfree(cap_info); + return -ENOMEM; + } + + file->private_data = cap_info; + + return 0; +} + +static const struct file_operations efi_capsule_fops = { + .owner = THIS_MODULE, + .open = efi_capsule_open, + .write = efi_capsule_write, + .release = efi_capsule_release, +}; + +static struct miscdevice efi_capsule_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "efi_capsule_loader", + .fops = &efi_capsule_fops, +}; + +static int __init efi_capsule_loader_init(void) +{ + int ret; + + if (!efi_enabled(EFI_RUNTIME_SERVICES)) + return -ENODEV; + + ret = misc_register(&efi_capsule_misc); + if (ret) + pr_err("Unable to register capsule loader device\n"); + + return ret; +} +module_init(efi_capsule_loader_init); + +static void __exit efi_capsule_loader_exit(void) +{ + misc_deregister(&efi_capsule_misc); +} +module_exit(efi_capsule_loader_exit); + +MODULE_DESCRIPTION("EFI capsule firmware binary loader"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/firmware/efi/capsule.c b/drivers/firmware/efi/capsule.c new file mode 100644 index 000000000..dd6252638 --- /dev/null +++ b/drivers/firmware/efi/capsule.c @@ -0,0 +1,315 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * EFI capsule support. + * + * Copyright 2013 Intel Corporation; author Matt Fleming + */ + +#define pr_fmt(fmt) "efi: " fmt + +#include +#include +#include +#include +#include +#include +#include + +typedef struct { + u64 length; + u64 data; +} efi_capsule_block_desc_t; + +static bool capsule_pending; +static bool stop_capsules; +static int efi_reset_type = -1; + +/* + * capsule_mutex serialises access to both capsule_pending and + * efi_reset_type and stop_capsules. + */ +static DEFINE_MUTEX(capsule_mutex); + +/** + * efi_capsule_pending - has a capsule been passed to the firmware? + * @reset_type: store the type of EFI reset if capsule is pending + * + * To ensure that the registered capsule is processed correctly by the + * firmware we need to perform a specific type of reset. If a capsule is + * pending return the reset type in @reset_type. + * + * This function will race with callers of efi_capsule_update(), for + * example, calling this function while somebody else is in + * efi_capsule_update() but hasn't reached efi_capsue_update_locked() + * will miss the updates to capsule_pending and efi_reset_type after + * efi_capsule_update_locked() completes. + * + * A non-racy use is from platform reboot code because we use + * system_state to ensure no capsules can be sent to the firmware once + * we're at SYSTEM_RESTART. See efi_capsule_update_locked(). + */ +bool efi_capsule_pending(int *reset_type) +{ + if (!capsule_pending) + return false; + + if (reset_type) + *reset_type = efi_reset_type; + + return true; +} + +/* + * Whitelist of EFI capsule flags that we support. + * + * We do not handle EFI_CAPSULE_INITIATE_RESET because that would + * require us to prepare the kernel for reboot. Refuse to load any + * capsules with that flag and any other flags that we do not know how + * to handle. + */ +#define EFI_CAPSULE_SUPPORTED_FLAG_MASK \ + (EFI_CAPSULE_PERSIST_ACROSS_RESET | EFI_CAPSULE_POPULATE_SYSTEM_TABLE) + +/** + * efi_capsule_supported - does the firmware support the capsule? + * @guid: vendor guid of capsule + * @flags: capsule flags + * @size: size of capsule data + * @reset: the reset type required for this capsule + * + * Check whether a capsule with @flags is supported by the firmware + * and that @size doesn't exceed the maximum size for a capsule. + * + * No attempt is made to check @reset against the reset type required + * by any pending capsules because of the races involved. + */ +int efi_capsule_supported(efi_guid_t guid, u32 flags, size_t size, int *reset) +{ + efi_capsule_header_t capsule; + efi_capsule_header_t *cap_list[] = { &capsule }; + efi_status_t status; + u64 max_size; + + if (flags & ~EFI_CAPSULE_SUPPORTED_FLAG_MASK) + return -EINVAL; + + capsule.headersize = capsule.imagesize = sizeof(capsule); + memcpy(&capsule.guid, &guid, sizeof(efi_guid_t)); + capsule.flags = flags; + + status = efi.query_capsule_caps(cap_list, 1, &max_size, reset); + if (status != EFI_SUCCESS) + return efi_status_to_err(status); + + if (size > max_size) + return -ENOSPC; + + return 0; +} +EXPORT_SYMBOL_GPL(efi_capsule_supported); + +/* + * Every scatter gather list (block descriptor) page must end with a + * continuation pointer. The last continuation pointer of the last + * page must be zero to mark the end of the chain. + */ +#define SGLIST_PER_PAGE ((PAGE_SIZE / sizeof(efi_capsule_block_desc_t)) - 1) + +/* + * How many scatter gather list (block descriptor) pages do we need + * to map @count pages? + */ +static inline unsigned int sg_pages_num(unsigned int count) +{ + return DIV_ROUND_UP(count, SGLIST_PER_PAGE); +} + +/** + * efi_capsule_update_locked - pass a single capsule to the firmware + * @capsule: capsule to send to the firmware + * @sg_pages: array of scatter gather (block descriptor) pages + * @reset: the reset type required for @capsule + * + * Since this function must be called under capsule_mutex check + * whether efi_reset_type will conflict with @reset, and atomically + * set it and capsule_pending if a capsule was successfully sent to + * the firmware. + * + * We also check to see if the system is about to restart, and if so, + * abort. This avoids races between efi_capsule_update() and + * efi_capsule_pending(). + */ +static int +efi_capsule_update_locked(efi_capsule_header_t *capsule, + struct page **sg_pages, int reset) +{ + efi_physical_addr_t sglist_phys; + efi_status_t status; + + lockdep_assert_held(&capsule_mutex); + + /* + * If someone has already registered a capsule that requires a + * different reset type, we're out of luck and must abort. + */ + if (efi_reset_type >= 0 && efi_reset_type != reset) { + pr_err("Conflicting capsule reset type %d (%d).\n", + reset, efi_reset_type); + return -EINVAL; + } + + /* + * If the system is getting ready to restart it may have + * called efi_capsule_pending() to make decisions (such as + * whether to force an EFI reboot), and we're racing against + * that call. Abort in that case. + */ + if (unlikely(stop_capsules)) { + pr_warn("Capsule update raced with reboot, aborting.\n"); + return -EINVAL; + } + + sglist_phys = page_to_phys(sg_pages[0]); + + status = efi.update_capsule(&capsule, 1, sglist_phys); + if (status == EFI_SUCCESS) { + capsule_pending = true; + efi_reset_type = reset; + } + + return efi_status_to_err(status); +} + +/** + * efi_capsule_update - send a capsule to the firmware + * @capsule: capsule to send to firmware + * @pages: an array of capsule data pages + * + * Build a scatter gather list with EFI capsule block descriptors to + * map the capsule described by @capsule with its data in @pages and + * send it to the firmware via the UpdateCapsule() runtime service. + * + * @capsule must be a virtual mapping of the complete capsule update in the + * kernel address space, as the capsule can be consumed immediately. + * A capsule_header_t that describes the entire contents of the capsule + * must be at the start of the first data page. + * + * Even though this function will validate that the firmware supports + * the capsule guid, users will likely want to check that + * efi_capsule_supported() returns true before calling this function + * because it makes it easier to print helpful error messages. + * + * If the capsule is successfully submitted to the firmware, any + * subsequent calls to efi_capsule_pending() will return true. @pages + * must not be released or modified if this function returns + * successfully. + * + * Callers must be prepared for this function to fail, which can + * happen if we raced with system reboot or if there is already a + * pending capsule that has a reset type that conflicts with the one + * required by @capsule. Do NOT use efi_capsule_pending() to detect + * this conflict since that would be racy. Instead, submit the capsule + * to efi_capsule_update() and check the return value. + * + * Return 0 on success, a converted EFI status code on failure. + */ +int efi_capsule_update(efi_capsule_header_t *capsule, phys_addr_t *pages) +{ + u32 imagesize = capsule->imagesize; + efi_guid_t guid = capsule->guid; + unsigned int count, sg_count; + u32 flags = capsule->flags; + struct page **sg_pages; + int rv, reset_type; + int i, j; + + rv = efi_capsule_supported(guid, flags, imagesize, &reset_type); + if (rv) + return rv; + + count = DIV_ROUND_UP(imagesize, PAGE_SIZE); + sg_count = sg_pages_num(count); + + sg_pages = kzalloc_objs(*sg_pages, sg_count); + if (!sg_pages) + return -ENOMEM; + + for (i = 0; i < sg_count; i++) { + sg_pages[i] = alloc_page(GFP_KERNEL); + if (!sg_pages[i]) { + rv = -ENOMEM; + goto out; + } + } + + for (i = 0; i < sg_count; i++) { + efi_capsule_block_desc_t *sglist; + + sglist = kmap_atomic(sg_pages[i]); + + for (j = 0; j < SGLIST_PER_PAGE && count > 0; j++) { + u64 sz = min_t(u64, imagesize, + PAGE_SIZE - (u64)*pages % PAGE_SIZE); + + sglist[j].length = sz; + sglist[j].data = *pages++; + + imagesize -= sz; + count--; + } + + /* Continuation pointer */ + sglist[j].length = 0; + + if (i + 1 == sg_count) + sglist[j].data = 0; + else + sglist[j].data = page_to_phys(sg_pages[i + 1]); + +#if defined(CONFIG_ARM) || defined(CONFIG_ARM64) + /* + * At runtime, the firmware has no way to find out where the + * sglist elements are mapped, if they are mapped in the first + * place. Therefore, on architectures that can only perform + * cache maintenance by virtual address, the firmware is unable + * to perform this maintenance, and so it is up to the OS to do + * it instead. + */ + efi_capsule_flush_cache_range(sglist, PAGE_SIZE); +#endif + kunmap_atomic(sglist); + } + + mutex_lock(&capsule_mutex); + rv = efi_capsule_update_locked(capsule, sg_pages, reset_type); + mutex_unlock(&capsule_mutex); + +out: + for (i = 0; rv && i < sg_count; i++) { + if (sg_pages[i]) + __free_page(sg_pages[i]); + } + + kfree(sg_pages); + return rv; +} +EXPORT_SYMBOL_GPL(efi_capsule_update); + +static int capsule_reboot_notify(struct notifier_block *nb, unsigned long event, void *cmd) +{ + mutex_lock(&capsule_mutex); + stop_capsules = true; + mutex_unlock(&capsule_mutex); + + return NOTIFY_DONE; +} + +static struct notifier_block capsule_reboot_nb = { + .notifier_call = capsule_reboot_notify, +}; + +static int __init capsule_reboot_register(void) +{ + return register_reboot_notifier(&capsule_reboot_nb); +} +core_initcall(capsule_reboot_register); diff --git a/drivers/firmware/efi/cper-arm.c b/drivers/firmware/efi/cper-arm.c new file mode 100644 index 000000000..b21cb1232 --- /dev/null +++ b/drivers/firmware/efi/cper-arm.c @@ -0,0 +1,343 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * UEFI Common Platform Error Record (CPER) support + * + * Copyright (C) 2017, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const char * const arm_reg_ctx_strs[] = { + "AArch32 general purpose registers", + "AArch32 EL1 context registers", + "AArch32 EL2 context registers", + "AArch32 secure context registers", + "AArch64 general purpose registers", + "AArch64 EL1 context registers", + "AArch64 EL2 context registers", + "AArch64 EL3 context registers", + "Misc. system register structure", +}; + +static const char * const arm_err_trans_type_strs[] = { + "Instruction", + "Data Access", + "Generic", +}; + +static const char * const arm_bus_err_op_strs[] = { + "Generic error (type cannot be determined)", + "Generic read (type of instruction or data request cannot be determined)", + "Generic write (type of instruction of data request cannot be determined)", + "Data read", + "Data write", + "Instruction fetch", + "Prefetch", +}; + +static const char * const arm_cache_err_op_strs[] = { + "Generic error (type cannot be determined)", + "Generic read (type of instruction or data request cannot be determined)", + "Generic write (type of instruction of data request cannot be determined)", + "Data read", + "Data write", + "Instruction fetch", + "Prefetch", + "Eviction", + "Snooping (processor initiated a cache snoop that resulted in an error)", + "Snooped (processor raised a cache error caused by another processor or device snooping its cache)", + "Management", +}; + +static const char * const arm_tlb_err_op_strs[] = { + "Generic error (type cannot be determined)", + "Generic read (type of instruction or data request cannot be determined)", + "Generic write (type of instruction of data request cannot be determined)", + "Data read", + "Data write", + "Instruction fetch", + "Prefetch", + "Local management operation (processor initiated a TLB management operation that resulted in an error)", + "External management operation (processor raised a TLB error caused by another processor or device broadcasting TLB operations)", +}; + +static const char * const arm_bus_err_part_type_strs[] = { + "Local processor originated request", + "Local processor responded to request", + "Local processor observed", + "Generic", +}; + +static const char * const arm_bus_err_addr_space_strs[] = { + "External Memory Access", + "Internal Memory Access", + "Unknown", + "Device Memory Access", +}; + +static void cper_print_arm_err_info(const char *pfx, u32 type, + u64 error_info) +{ + u8 trans_type, op_type, level, participation_type, address_space; + u16 mem_attributes; + bool proc_context_corrupt, corrected, precise_pc, restartable_pc; + bool time_out, access_mode; + + /* + * Vendor type errors have error information values that are vendor + * specific. + */ + if (type & CPER_ARM_VENDOR_ERROR) + return; + + if (error_info & CPER_ARM_ERR_VALID_TRANSACTION_TYPE) { + trans_type = ((error_info >> CPER_ARM_ERR_TRANSACTION_SHIFT) + & CPER_ARM_ERR_TRANSACTION_MASK); + if (trans_type < ARRAY_SIZE(arm_err_trans_type_strs)) { + printk("%stransaction type: %s\n", pfx, + arm_err_trans_type_strs[trans_type]); + } + } + + if (error_info & CPER_ARM_ERR_VALID_OPERATION_TYPE) { + op_type = ((error_info >> CPER_ARM_ERR_OPERATION_SHIFT) + & CPER_ARM_ERR_OPERATION_MASK); + if (type & CPER_ARM_CACHE_ERROR) { + if (op_type < ARRAY_SIZE(arm_cache_err_op_strs)) { + printk("%scache error, operation type: %s\n", pfx, + arm_cache_err_op_strs[op_type]); + } + } + if (type & CPER_ARM_TLB_ERROR) { + if (op_type < ARRAY_SIZE(arm_tlb_err_op_strs)) { + printk("%sTLB error, operation type: %s\n", pfx, + arm_tlb_err_op_strs[op_type]); + } + } + if (type & CPER_ARM_BUS_ERROR) { + if (op_type < ARRAY_SIZE(arm_bus_err_op_strs)) { + printk("%sbus error, operation type: %s\n", pfx, + arm_bus_err_op_strs[op_type]); + } + } + } + + if (error_info & CPER_ARM_ERR_VALID_LEVEL) { + level = ((error_info >> CPER_ARM_ERR_LEVEL_SHIFT) + & CPER_ARM_ERR_LEVEL_MASK); + if (type & CPER_ARM_CACHE_ERROR) + printk("%scache level: %d\n", pfx, level); + + if (type & CPER_ARM_TLB_ERROR) + printk("%sTLB level: %d\n", pfx, level); + + if (type & CPER_ARM_BUS_ERROR) + printk("%saffinity level at which the bus error occurred: %d\n", + pfx, level); + } + + if (error_info & CPER_ARM_ERR_VALID_PROC_CONTEXT_CORRUPT) { + proc_context_corrupt = ((error_info >> CPER_ARM_ERR_PC_CORRUPT_SHIFT) + & CPER_ARM_ERR_PC_CORRUPT_MASK); + if (proc_context_corrupt) + printk("%sprocessor context corrupted\n", pfx); + else + printk("%sprocessor context not corrupted\n", pfx); + } + + if (error_info & CPER_ARM_ERR_VALID_CORRECTED) { + corrected = ((error_info >> CPER_ARM_ERR_CORRECTED_SHIFT) + & CPER_ARM_ERR_CORRECTED_MASK); + if (corrected) + printk("%sthe error has been corrected\n", pfx); + else + printk("%sthe error has not been corrected\n", pfx); + } + + if (error_info & CPER_ARM_ERR_VALID_PRECISE_PC) { + precise_pc = ((error_info >> CPER_ARM_ERR_PRECISE_PC_SHIFT) + & CPER_ARM_ERR_PRECISE_PC_MASK); + if (precise_pc) + printk("%sPC is precise\n", pfx); + else + printk("%sPC is imprecise\n", pfx); + } + + if (error_info & CPER_ARM_ERR_VALID_RESTARTABLE_PC) { + restartable_pc = ((error_info >> CPER_ARM_ERR_RESTARTABLE_PC_SHIFT) + & CPER_ARM_ERR_RESTARTABLE_PC_MASK); + if (restartable_pc) + printk("%sProgram execution can be restarted reliably at the PC associated with the error.\n", pfx); + } + + /* The rest of the fields are specific to bus errors */ + if (type != CPER_ARM_BUS_ERROR) + return; + + if (error_info & CPER_ARM_ERR_VALID_PARTICIPATION_TYPE) { + participation_type = ((error_info >> CPER_ARM_ERR_PARTICIPATION_TYPE_SHIFT) + & CPER_ARM_ERR_PARTICIPATION_TYPE_MASK); + if (participation_type < ARRAY_SIZE(arm_bus_err_part_type_strs)) { + printk("%sparticipation type: %s\n", pfx, + arm_bus_err_part_type_strs[participation_type]); + } + } + + if (error_info & CPER_ARM_ERR_VALID_TIME_OUT) { + time_out = ((error_info >> CPER_ARM_ERR_TIME_OUT_SHIFT) + & CPER_ARM_ERR_TIME_OUT_MASK); + if (time_out) + printk("%srequest timed out\n", pfx); + } + + if (error_info & CPER_ARM_ERR_VALID_ADDRESS_SPACE) { + address_space = ((error_info >> CPER_ARM_ERR_ADDRESS_SPACE_SHIFT) + & CPER_ARM_ERR_ADDRESS_SPACE_MASK); + if (address_space < ARRAY_SIZE(arm_bus_err_addr_space_strs)) { + printk("%saddress space: %s\n", pfx, + arm_bus_err_addr_space_strs[address_space]); + } + } + + if (error_info & CPER_ARM_ERR_VALID_MEM_ATTRIBUTES) { + mem_attributes = ((error_info >> CPER_ARM_ERR_MEM_ATTRIBUTES_SHIFT) + & CPER_ARM_ERR_MEM_ATTRIBUTES_MASK); + printk("%smemory access attributes:0x%x\n", pfx, mem_attributes); + } + + if (error_info & CPER_ARM_ERR_VALID_ACCESS_MODE) { + access_mode = ((error_info >> CPER_ARM_ERR_ACCESS_MODE_SHIFT) + & CPER_ARM_ERR_ACCESS_MODE_MASK); + if (access_mode) + printk("%saccess mode: normal\n", pfx); + else + printk("%saccess mode: secure\n", pfx); + } +} + +void cper_print_proc_arm(const char *pfx, + const struct cper_sec_proc_arm *proc, + u32 length) +{ + int i, len, max_ctx_type; + struct cper_arm_err_info *err_info; + struct cper_arm_ctx_info *ctx_info; + char newpfx[64], infopfx[ARRAY_SIZE(newpfx) + 1]; + char error_type[120]; + + printk("%sMIDR: 0x%016llx\n", pfx, proc->midr); + + len = proc->section_length - (sizeof(*proc) + + proc->err_info_num * (sizeof(*err_info))); + + if (len < 0 || proc->section_length > length) { + printk("%ssection length: %d, CPER size: %d\n", + pfx, proc->section_length, length); + printk("%ssection length is too %s\n", pfx, + (len < 0) ? "small" : "big"); + printk("%sfirmware-generated error record is incorrect\n", pfx); + printk("%sERR_INFO_NUM is %d\n", pfx, proc->err_info_num); + return; + } + + if (proc->validation_bits & CPER_ARM_VALID_MPIDR) + printk("%sMultiprocessor Affinity Register (MPIDR): 0x%016llx\n", + pfx, proc->mpidr); + + if (proc->validation_bits & CPER_ARM_VALID_AFFINITY_LEVEL) + printk("%serror affinity level: %d\n", pfx, + proc->affinity_level); + + if (proc->validation_bits & CPER_ARM_VALID_RUNNING_STATE) { + printk("%srunning state: 0x%x\n", pfx, proc->running_state); + printk("%sPower State Coordination Interface state: %d\n", + pfx, proc->psci_state); + } + + snprintf(newpfx, sizeof(newpfx), "%s ", pfx); + + err_info = (struct cper_arm_err_info *)(proc + 1); + for (i = 0; i < proc->err_info_num; i++) { + printk("%sError info structure %d:\n", pfx, i); + + printk("%snum errors: %d\n", pfx, err_info->multiple_error + 1); + + if (err_info->validation_bits & CPER_ARM_INFO_VALID_FLAGS) { + if (err_info->flags & CPER_ARM_INFO_FLAGS_FIRST) + printk("%sfirst error captured\n", newpfx); + if (err_info->flags & CPER_ARM_INFO_FLAGS_LAST) + printk("%slast error captured\n", newpfx); + if (err_info->flags & CPER_ARM_INFO_FLAGS_PROPAGATED) + printk("%spropagated error captured\n", + newpfx); + if (err_info->flags & CPER_ARM_INFO_FLAGS_OVERFLOW) + printk("%soverflow occurred, error info is incomplete\n", + newpfx); + } + + cper_bits_to_str(error_type, sizeof(error_type), + FIELD_GET(CPER_ARM_ERR_TYPE_MASK, err_info->type), + cper_proc_error_type_strs, + ARRAY_SIZE(cper_proc_error_type_strs)); + + printk("%serror_type: 0x%02x: %s%s\n", newpfx, err_info->type, + error_type, + (err_info->type & ~CPER_ARM_ERR_TYPE_MASK) ? " with reserved bit(s)" : ""); + + if (err_info->validation_bits & CPER_ARM_INFO_VALID_ERR_INFO) { + printk("%serror_info: 0x%016llx\n", newpfx, + err_info->error_info); + snprintf(infopfx, sizeof(infopfx), "%s ", newpfx); + cper_print_arm_err_info(infopfx, err_info->type, + err_info->error_info); + } + if (err_info->validation_bits & CPER_ARM_INFO_VALID_VIRT_ADDR) + printk("%svirtual fault address: 0x%016llx\n", + newpfx, err_info->virt_fault_addr); + if (err_info->validation_bits & CPER_ARM_INFO_VALID_PHYSICAL_ADDR) + printk("%sphysical fault address: 0x%016llx\n", + newpfx, err_info->physical_fault_addr); + err_info += 1; + } + + ctx_info = (struct cper_arm_ctx_info *)err_info; + max_ctx_type = ARRAY_SIZE(arm_reg_ctx_strs) - 1; + for (i = 0; i < proc->context_info_num; i++) { + int size = ALIGN(sizeof(*ctx_info) + ctx_info->size, 16); + + printk("%sContext info structure %d:\n", pfx, i); + if (len < size) { + printk("%ssection length is too small\n", newpfx); + printk("%sfirmware-generated error record is incorrect\n", pfx); + return; + } + if (ctx_info->type > max_ctx_type) { + printk("%sInvalid context type: %d (max: %d)\n", + newpfx, ctx_info->type, max_ctx_type); + return; + } + printk("%sregister context type: %s\n", newpfx, + arm_reg_ctx_strs[ctx_info->type]); + print_hex_dump(newpfx, "", DUMP_PREFIX_OFFSET, 16, 4, + (ctx_info + 1), ctx_info->size, 0); + len -= size; + ctx_info = (struct cper_arm_ctx_info *)((long)ctx_info + size); + } + + if (len > 0) { + printk("%sVendor specific error info has %u bytes:\n", pfx, + len); + print_hex_dump(newpfx, "", DUMP_PREFIX_OFFSET, 16, 4, ctx_info, + len, true); + } +} diff --git a/drivers/firmware/efi/cper-x86.c b/drivers/firmware/efi/cper-x86.c new file mode 100644 index 000000000..3949d7b5e --- /dev/null +++ b/drivers/firmware/efi/cper-x86.c @@ -0,0 +1,361 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2018, Advanced Micro Devices, Inc. + +#include +#include + +/* + * We don't need a "CPER_IA" prefix since these are all locally defined. + * This will save us a lot of line space. + */ +#define VALID_LAPIC_ID BIT_ULL(0) +#define VALID_CPUID_INFO BIT_ULL(1) +#define VALID_PROC_ERR_INFO_NUM(bits) (((bits) & GENMASK_ULL(7, 2)) >> 2) +#define VALID_PROC_CXT_INFO_NUM(bits) (((bits) & GENMASK_ULL(13, 8)) >> 8) + +#define INFO_ERR_STRUCT_TYPE_CACHE \ + GUID_INIT(0xA55701F5, 0xE3EF, 0x43DE, 0xAC, 0x72, 0x24, 0x9B, \ + 0x57, 0x3F, 0xAD, 0x2C) +#define INFO_ERR_STRUCT_TYPE_TLB \ + GUID_INIT(0xFC06B535, 0x5E1F, 0x4562, 0x9F, 0x25, 0x0A, 0x3B, \ + 0x9A, 0xDB, 0x63, 0xC3) +#define INFO_ERR_STRUCT_TYPE_BUS \ + GUID_INIT(0x1CF3F8B3, 0xC5B1, 0x49a2, 0xAA, 0x59, 0x5E, 0xEF, \ + 0x92, 0xFF, 0xA6, 0x3C) +#define INFO_ERR_STRUCT_TYPE_MS \ + GUID_INIT(0x48AB7F57, 0xDC34, 0x4f6c, 0xA7, 0xD3, 0xB0, 0xB5, \ + 0xB0, 0xA7, 0x43, 0x14) + +#define INFO_VALID_CHECK_INFO BIT_ULL(0) +#define INFO_VALID_TARGET_ID BIT_ULL(1) +#define INFO_VALID_REQUESTOR_ID BIT_ULL(2) +#define INFO_VALID_RESPONDER_ID BIT_ULL(3) +#define INFO_VALID_IP BIT_ULL(4) + +#define CHECK_VALID_TRANS_TYPE BIT_ULL(0) +#define CHECK_VALID_OPERATION BIT_ULL(1) +#define CHECK_VALID_LEVEL BIT_ULL(2) +#define CHECK_VALID_PCC BIT_ULL(3) +#define CHECK_VALID_UNCORRECTED BIT_ULL(4) +#define CHECK_VALID_PRECISE_IP BIT_ULL(5) +#define CHECK_VALID_RESTARTABLE_IP BIT_ULL(6) +#define CHECK_VALID_OVERFLOW BIT_ULL(7) + +#define CHECK_VALID_BUS_PART_TYPE BIT_ULL(8) +#define CHECK_VALID_BUS_TIME_OUT BIT_ULL(9) +#define CHECK_VALID_BUS_ADDR_SPACE BIT_ULL(10) + +#define CHECK_VALID_BITS(check) (((check) & GENMASK_ULL(15, 0))) +#define CHECK_TRANS_TYPE(check) (((check) & GENMASK_ULL(17, 16)) >> 16) +#define CHECK_OPERATION(check) (((check) & GENMASK_ULL(21, 18)) >> 18) +#define CHECK_LEVEL(check) (((check) & GENMASK_ULL(24, 22)) >> 22) +#define CHECK_PCC BIT_ULL(25) +#define CHECK_UNCORRECTED BIT_ULL(26) +#define CHECK_PRECISE_IP BIT_ULL(27) +#define CHECK_RESTARTABLE_IP BIT_ULL(28) +#define CHECK_OVERFLOW BIT_ULL(29) + +#define CHECK_BUS_PART_TYPE(check) (((check) & GENMASK_ULL(31, 30)) >> 30) +#define CHECK_BUS_TIME_OUT BIT_ULL(32) +#define CHECK_BUS_ADDR_SPACE(check) (((check) & GENMASK_ULL(34, 33)) >> 33) + +#define CHECK_VALID_MS_ERR_TYPE BIT_ULL(0) +#define CHECK_VALID_MS_PCC BIT_ULL(1) +#define CHECK_VALID_MS_UNCORRECTED BIT_ULL(2) +#define CHECK_VALID_MS_PRECISE_IP BIT_ULL(3) +#define CHECK_VALID_MS_RESTARTABLE_IP BIT_ULL(4) +#define CHECK_VALID_MS_OVERFLOW BIT_ULL(5) + +#define CHECK_MS_ERR_TYPE(check) (((check) & GENMASK_ULL(18, 16)) >> 16) +#define CHECK_MS_PCC BIT_ULL(19) +#define CHECK_MS_UNCORRECTED BIT_ULL(20) +#define CHECK_MS_PRECISE_IP BIT_ULL(21) +#define CHECK_MS_RESTARTABLE_IP BIT_ULL(22) +#define CHECK_MS_OVERFLOW BIT_ULL(23) + +#define CTX_TYPE_MSR 1 +#define CTX_TYPE_MMREG 7 + +enum err_types { + ERR_TYPE_CACHE = 0, + ERR_TYPE_TLB, + ERR_TYPE_BUS, + ERR_TYPE_MS, + N_ERR_TYPES +}; + +static enum err_types cper_get_err_type(const guid_t *err_type) +{ + if (guid_equal(err_type, &INFO_ERR_STRUCT_TYPE_CACHE)) + return ERR_TYPE_CACHE; + else if (guid_equal(err_type, &INFO_ERR_STRUCT_TYPE_TLB)) + return ERR_TYPE_TLB; + else if (guid_equal(err_type, &INFO_ERR_STRUCT_TYPE_BUS)) + return ERR_TYPE_BUS; + else if (guid_equal(err_type, &INFO_ERR_STRUCT_TYPE_MS)) + return ERR_TYPE_MS; + else + return N_ERR_TYPES; +} + +static const char * const ia_check_trans_type_strs[] = { + "Instruction", + "Data Access", + "Generic", +}; + +static const char * const ia_check_op_strs[] = { + "generic error", + "generic read", + "generic write", + "data read", + "data write", + "instruction fetch", + "prefetch", + "eviction", + "snoop", +}; + +static const char * const ia_check_bus_part_type_strs[] = { + "Local Processor originated request", + "Local Processor responded to request", + "Local Processor observed", + "Generic", +}; + +static const char * const ia_check_bus_addr_space_strs[] = { + "Memory Access", + "Reserved", + "I/O", + "Other Transaction", +}; + +static const char * const ia_check_ms_error_type_strs[] = { + "No Error", + "Unclassified", + "Microcode ROM Parity Error", + "External Error", + "FRC Error", + "Internal Unclassified", +}; + +static const char * const ia_reg_ctx_strs[] = { + "Unclassified Data", + "MSR Registers (Machine Check and other MSRs)", + "32-bit Mode Execution Context", + "64-bit Mode Execution Context", + "FXSAVE Context", + "32-bit Mode Debug Registers (DR0-DR7)", + "64-bit Mode Debug Registers (DR0-DR7)", + "Memory Mapped Registers", +}; + +static inline void print_bool(char *str, const char *pfx, u64 check, u64 bit) +{ + printk("%s%s: %s\n", pfx, str, (check & bit) ? "true" : "false"); +} + +static void print_err_info_ms(const char *pfx, u16 validation_bits, u64 check) +{ + if (validation_bits & CHECK_VALID_MS_ERR_TYPE) { + u8 err_type = CHECK_MS_ERR_TYPE(check); + + printk("%sError Type: %u, %s\n", pfx, err_type, + err_type < ARRAY_SIZE(ia_check_ms_error_type_strs) ? + ia_check_ms_error_type_strs[err_type] : "unknown"); + } + + if (validation_bits & CHECK_VALID_MS_PCC) + print_bool("Processor Context Corrupt", pfx, check, CHECK_MS_PCC); + + if (validation_bits & CHECK_VALID_MS_UNCORRECTED) + print_bool("Uncorrected", pfx, check, CHECK_MS_UNCORRECTED); + + if (validation_bits & CHECK_VALID_MS_PRECISE_IP) + print_bool("Precise IP", pfx, check, CHECK_MS_PRECISE_IP); + + if (validation_bits & CHECK_VALID_MS_RESTARTABLE_IP) + print_bool("Restartable IP", pfx, check, CHECK_MS_RESTARTABLE_IP); + + if (validation_bits & CHECK_VALID_MS_OVERFLOW) + print_bool("Overflow", pfx, check, CHECK_MS_OVERFLOW); +} + +static void print_err_info(const char *pfx, u8 err_type, u64 check) +{ + u16 validation_bits = CHECK_VALID_BITS(check); + + /* + * The MS Check structure varies a lot from the others, so use a + * separate function for decoding. + */ + if (err_type == ERR_TYPE_MS) + return print_err_info_ms(pfx, validation_bits, check); + + if (validation_bits & CHECK_VALID_TRANS_TYPE) { + u8 trans_type = CHECK_TRANS_TYPE(check); + + printk("%sTransaction Type: %u, %s\n", pfx, trans_type, + trans_type < ARRAY_SIZE(ia_check_trans_type_strs) ? + ia_check_trans_type_strs[trans_type] : "unknown"); + } + + if (validation_bits & CHECK_VALID_OPERATION) { + u8 op = CHECK_OPERATION(check); + + /* + * CACHE has more operation types than TLB or BUS, though the + * name and the order are the same. + */ + u8 max_ops = (err_type == ERR_TYPE_CACHE) ? 9 : 7; + + printk("%sOperation: %u, %s\n", pfx, op, + op < max_ops ? ia_check_op_strs[op] : "unknown"); + } + + if (validation_bits & CHECK_VALID_LEVEL) + printk("%sLevel: %llu\n", pfx, CHECK_LEVEL(check)); + + if (validation_bits & CHECK_VALID_PCC) + print_bool("Processor Context Corrupt", pfx, check, CHECK_PCC); + + if (validation_bits & CHECK_VALID_UNCORRECTED) + print_bool("Uncorrected", pfx, check, CHECK_UNCORRECTED); + + if (validation_bits & CHECK_VALID_PRECISE_IP) + print_bool("Precise IP", pfx, check, CHECK_PRECISE_IP); + + if (validation_bits & CHECK_VALID_RESTARTABLE_IP) + print_bool("Restartable IP", pfx, check, CHECK_RESTARTABLE_IP); + + if (validation_bits & CHECK_VALID_OVERFLOW) + print_bool("Overflow", pfx, check, CHECK_OVERFLOW); + + if (err_type != ERR_TYPE_BUS) + return; + + if (validation_bits & CHECK_VALID_BUS_PART_TYPE) { + u8 part_type = CHECK_BUS_PART_TYPE(check); + + printk("%sParticipation Type: %u, %s\n", pfx, part_type, + part_type < ARRAY_SIZE(ia_check_bus_part_type_strs) ? + ia_check_bus_part_type_strs[part_type] : "unknown"); + } + + if (validation_bits & CHECK_VALID_BUS_TIME_OUT) + print_bool("Time Out", pfx, check, CHECK_BUS_TIME_OUT); + + if (validation_bits & CHECK_VALID_BUS_ADDR_SPACE) { + u8 addr_space = CHECK_BUS_ADDR_SPACE(check); + + printk("%sAddress Space: %u, %s\n", pfx, addr_space, + addr_space < ARRAY_SIZE(ia_check_bus_addr_space_strs) ? + ia_check_bus_addr_space_strs[addr_space] : "unknown"); + } +} + +void cper_print_proc_ia(const char *pfx, const struct cper_sec_proc_ia *proc) +{ + int i; + struct cper_ia_err_info *err_info; + struct cper_ia_proc_ctx *ctx_info; + char newpfx[64], infopfx[64]; + u8 err_type; + + if (proc->validation_bits & VALID_LAPIC_ID) + printk("%sLocal APIC_ID: 0x%llx\n", pfx, proc->lapic_id); + + if (proc->validation_bits & VALID_CPUID_INFO) { + printk("%sCPUID Info:\n", pfx); + print_hex_dump(pfx, "", DUMP_PREFIX_OFFSET, 16, 4, proc->cpuid, + sizeof(proc->cpuid), 0); + } + + snprintf(newpfx, sizeof(newpfx), "%s ", pfx); + + err_info = (struct cper_ia_err_info *)(proc + 1); + for (i = 0; i < VALID_PROC_ERR_INFO_NUM(proc->validation_bits); i++) { + printk("%sError Information Structure %d:\n", pfx, i); + + err_type = cper_get_err_type(&err_info->err_type); + printk("%sError Structure Type: %s\n", newpfx, + err_type < ARRAY_SIZE(cper_proc_error_type_strs) ? + cper_proc_error_type_strs[err_type] : "unknown"); + + if (err_type >= N_ERR_TYPES) { + printk("%sError Structure Type: %pUl\n", newpfx, + &err_info->err_type); + } + + if (err_info->validation_bits & INFO_VALID_CHECK_INFO) { + printk("%sCheck Information: 0x%016llx\n", newpfx, + err_info->check_info); + + if (err_type < N_ERR_TYPES) { + snprintf(infopfx, sizeof(infopfx), "%s ", + newpfx); + + print_err_info(infopfx, err_type, + err_info->check_info); + } + } + + if (err_info->validation_bits & INFO_VALID_TARGET_ID) { + printk("%sTarget Identifier: 0x%016llx\n", + newpfx, err_info->target_id); + } + + if (err_info->validation_bits & INFO_VALID_REQUESTOR_ID) { + printk("%sRequestor Identifier: 0x%016llx\n", + newpfx, err_info->requestor_id); + } + + if (err_info->validation_bits & INFO_VALID_RESPONDER_ID) { + printk("%sResponder Identifier: 0x%016llx\n", + newpfx, err_info->responder_id); + } + + if (err_info->validation_bits & INFO_VALID_IP) { + printk("%sInstruction Pointer: 0x%016llx\n", + newpfx, err_info->ip); + } + + err_info++; + } + + ctx_info = (struct cper_ia_proc_ctx *)err_info; + for (i = 0; i < VALID_PROC_CXT_INFO_NUM(proc->validation_bits); i++) { + int size = ALIGN(sizeof(*ctx_info) + ctx_info->reg_arr_size, 16); + int groupsize = 4; + + printk("%sContext Information Structure %d:\n", pfx, i); + + printk("%sRegister Context Type: %s\n", newpfx, + ctx_info->reg_ctx_type < ARRAY_SIZE(ia_reg_ctx_strs) ? + ia_reg_ctx_strs[ctx_info->reg_ctx_type] : "unknown"); + + printk("%sRegister Array Size: 0x%04x\n", newpfx, + ctx_info->reg_arr_size); + + if (ctx_info->reg_ctx_type == CTX_TYPE_MSR) { + groupsize = 8; /* MSRs are 8 bytes wide. */ + printk("%sMSR Address: 0x%08x\n", newpfx, + ctx_info->msr_addr); + } + + if (ctx_info->reg_ctx_type == CTX_TYPE_MMREG) { + printk("%sMM Register Address: 0x%016llx\n", newpfx, + ctx_info->mm_reg_addr); + } + + if (ctx_info->reg_ctx_type != CTX_TYPE_MSR || + arch_apei_report_x86_error(ctx_info, proc->lapic_id)) { + printk("%sRegister Array:\n", newpfx); + print_hex_dump(newpfx, "", DUMP_PREFIX_OFFSET, 16, + groupsize, (ctx_info + 1), + ctx_info->reg_arr_size, 0); + } + + ctx_info = (struct cper_ia_proc_ctx *)((long)ctx_info + size); + } +} diff --git a/drivers/firmware/efi/cper.c b/drivers/firmware/efi/cper.c new file mode 100644 index 000000000..06b4fdb59 --- /dev/null +++ b/drivers/firmware/efi/cper.c @@ -0,0 +1,779 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * UEFI Common Platform Error Record (CPER) support + * + * Copyright (C) 2010, Intel Corp. + * Author: Huang Ying + * + * CPER is the format used to describe platform hardware error by + * various tables, such as ERST, BERT and HEST etc. + * + * For more information about CPER, please refer to Appendix N of UEFI + * Specification version 2.4. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * CPER record ID need to be unique even after reboot, because record + * ID is used as index for ERST storage, while CPER records from + * multiple boot may co-exist in ERST. + */ +u64 cper_next_record_id(void) +{ + static atomic64_t seq; + + if (!atomic64_read(&seq)) { + time64_t time = ktime_get_real_seconds(); + + /* + * This code is unlikely to still be needed in year 2106, + * but just in case, let's use a few more bits for timestamps + * after y2038 to be sure they keep increasing monotonically + * for the next few hundred years... + */ + if (time < 0x80000000) + atomic64_set(&seq, (ktime_get_real_seconds()) << 32); + else + atomic64_set(&seq, 0x8000000000000000ull | + ktime_get_real_seconds() << 24); + } + + return atomic64_inc_return(&seq); +} +EXPORT_SYMBOL_GPL(cper_next_record_id); + +static const char * const severity_strs[] = { + "recoverable", + "fatal", + "corrected", + "info", +}; + +const char *cper_severity_str(unsigned int severity) +{ + return severity < ARRAY_SIZE(severity_strs) ? + severity_strs[severity] : "unknown"; +} +EXPORT_SYMBOL_GPL(cper_severity_str); + +/** + * cper_print_bits - print strings for set bits + * @pfx: prefix for each line, including log level and prefix string + * @bits: bit mask + * @strs: string array, indexed by bit position + * @strs_size: size of the string array: @strs + * + * For each set bit in @bits, print the corresponding string in @strs. + * If the output length is longer than 80, multiple line will be + * printed, with @pfx is printed at the beginning of each line. + */ +void cper_print_bits(const char *pfx, unsigned int bits, + const char * const strs[], unsigned int strs_size) +{ + int i, len = 0; + const char *str; + char buf[84]; + + for (i = 0; i < strs_size; i++) { + if (!(bits & (1U << i))) + continue; + str = strs[i]; + if (!str) + continue; + if (len && len + strlen(str) + 2 > 80) { + printk("%s\n", buf); + len = 0; + } + if (!len) + len = snprintf(buf, sizeof(buf), "%s%s", pfx, str); + else + len += scnprintf(buf+len, sizeof(buf)-len, ", %s", str); + } + if (len) + printk("%s\n", buf); +} + +/** + * cper_bits_to_str - return a string for set bits + * @buf: buffer to store the output string + * @buf_size: size of the output string buffer + * @bits: bit mask + * @strs: string array, indexed by bit position + * @strs_size: size of the string array: @strs + * + * Add to @buf the bitmask in hexadecimal. Then, for each set bit in @bits, + * add the corresponding string describing the bit in @strs to @buf. + * + * A typical example is:: + * + * const char * const bits[] = { + * "bit 3 name", + * "bit 4 name", + * "bit 5 name", + * }; + * char str[120]; + * unsigned int bitmask = BIT(3) | BIT(5); + * #define MASK GENMASK(5,3) + * + * cper_bits_to_str(str, sizeof(str), FIELD_GET(MASK, bitmask), + * bits, ARRAY_SIZE(bits)); + * + * The above code fills the string ``str`` with ``bit 3 name|bit 5 name``. + * + * Return: number of bytes stored or an error code if lower than zero. + */ +int cper_bits_to_str(char *buf, int buf_size, unsigned long bits, + const char * const strs[], unsigned int strs_size) +{ + int len = buf_size; + char *str = buf; + int i, size; + + *buf = '\0'; + + for_each_set_bit(i, &bits, strs_size) { + if (!(bits & BIT_ULL(i))) + continue; + + if (*buf && len > 0) { + *str = '|'; + len--; + str++; + } + + size = strscpy(str, strs[i], len); + if (size < 0) + return size; + + len -= size; + str += size; + } + return buf_size - len; +} +EXPORT_SYMBOL_GPL(cper_bits_to_str); + +static const char * const proc_type_strs[] = { + "IA32/X64", + "IA64", + "ARM", +}; + +static const char * const proc_isa_strs[] = { + "IA32", + "IA64", + "X64", + "ARM A32/T32", + "ARM A64", +}; + +const char * const cper_proc_error_type_strs[] = { + "cache error", + "TLB error", + "bus error", + "micro-architectural error", +}; + +static const char * const proc_op_strs[] = { + "unknown or generic", + "data read", + "data write", + "instruction execution", +}; + +static const char * const proc_flag_strs[] = { + "restartable", + "precise IP", + "overflow", + "corrected", +}; + +static void cper_print_proc_generic(const char *pfx, + const struct cper_sec_proc_generic *proc) +{ + if (proc->validation_bits & CPER_PROC_VALID_TYPE) + printk("%s""processor_type: %d, %s\n", pfx, proc->proc_type, + proc->proc_type < ARRAY_SIZE(proc_type_strs) ? + proc_type_strs[proc->proc_type] : "unknown"); + if (proc->validation_bits & CPER_PROC_VALID_ISA) + printk("%s""processor_isa: %d, %s\n", pfx, proc->proc_isa, + proc->proc_isa < ARRAY_SIZE(proc_isa_strs) ? + proc_isa_strs[proc->proc_isa] : "unknown"); + if (proc->validation_bits & CPER_PROC_VALID_ERROR_TYPE) { + printk("%s""error_type: 0x%02x\n", pfx, proc->proc_error_type); + cper_print_bits(pfx, proc->proc_error_type, + cper_proc_error_type_strs, + ARRAY_SIZE(cper_proc_error_type_strs)); + } + if (proc->validation_bits & CPER_PROC_VALID_OPERATION) + printk("%s""operation: %d, %s\n", pfx, proc->operation, + proc->operation < ARRAY_SIZE(proc_op_strs) ? + proc_op_strs[proc->operation] : "unknown"); + if (proc->validation_bits & CPER_PROC_VALID_FLAGS) { + printk("%s""flags: 0x%02x\n", pfx, proc->flags); + cper_print_bits(pfx, proc->flags, proc_flag_strs, + ARRAY_SIZE(proc_flag_strs)); + } + if (proc->validation_bits & CPER_PROC_VALID_LEVEL) + printk("%s""level: %d\n", pfx, proc->level); + if (proc->validation_bits & CPER_PROC_VALID_VERSION) + printk("%s""version_info: 0x%016llx\n", pfx, proc->cpu_version); + if (proc->validation_bits & CPER_PROC_VALID_ID) + printk("%s""processor_id: 0x%016llx\n", pfx, proc->proc_id); + if (proc->validation_bits & CPER_PROC_VALID_TARGET_ADDRESS) + printk("%s""target_address: 0x%016llx\n", + pfx, proc->target_addr); + if (proc->validation_bits & CPER_PROC_VALID_REQUESTOR_ID) + printk("%s""requestor_id: 0x%016llx\n", + pfx, proc->requestor_id); + if (proc->validation_bits & CPER_PROC_VALID_RESPONDER_ID) + printk("%s""responder_id: 0x%016llx\n", + pfx, proc->responder_id); + if (proc->validation_bits & CPER_PROC_VALID_IP) + printk("%s""IP: 0x%016llx\n", pfx, proc->ip); +} + +static const char * const mem_err_type_strs[] = { + "unknown", + "no error", + "single-bit ECC", + "multi-bit ECC", + "single-symbol chipkill ECC", + "multi-symbol chipkill ECC", + "master abort", + "target abort", + "parity error", + "watchdog timeout", + "invalid address", + "mirror Broken", + "memory sparing", + "scrub corrected error", + "scrub uncorrected error", + "physical memory map-out event", +}; + +const char *cper_mem_err_type_str(unsigned int etype) +{ + return etype < ARRAY_SIZE(mem_err_type_strs) ? + mem_err_type_strs[etype] : "unknown"; +} +EXPORT_SYMBOL_GPL(cper_mem_err_type_str); + +const char *cper_mem_err_status_str(u64 status) +{ + switch ((status >> 8) & 0xff) { + case 1: return "Error detected internal to the component"; + case 4: return "Storage error in DRAM memory"; + case 5: return "Storage error in TLB"; + case 6: return "Storage error in cache"; + case 7: return "Error in one or more functional units"; + case 8: return "Component failed self test"; + case 9: return "Overflow or undervalue of internal queue"; + case 16: return "Error detected in the bus"; + case 17: return "Virtual address not found on IO-TLB or IO-PDIR"; + case 18: return "Improper access error"; + case 19: return "Access to a memory address which is not mapped to any component"; + case 20: return "Loss of Lockstep"; + case 21: return "Response not associated with a request"; + case 22: return "Bus parity error - must also set the A, C, or D Bits"; + case 23: return "Detection of a protocol error"; + case 24: return "Detection of a PATH_ERROR"; + case 25: return "Bus operation timeout"; + case 26: return "A read was issued to data that has been poisoned"; + default: return "Reserved"; + } +} +EXPORT_SYMBOL_GPL(cper_mem_err_status_str); + +int cper_mem_err_location(struct cper_mem_err_compact *mem, char *msg) +{ + u32 len, n; + + if (!msg) + return 0; + + n = 0; + len = CPER_REC_LEN; + if (mem->validation_bits & CPER_MEM_VALID_NODE) + n += scnprintf(msg + n, len - n, "node:%d ", mem->node); + if (mem->validation_bits & CPER_MEM_VALID_CARD) + n += scnprintf(msg + n, len - n, "card:%d ", mem->card); + if (mem->validation_bits & CPER_MEM_VALID_MODULE) + n += scnprintf(msg + n, len - n, "module:%d ", mem->module); + if (mem->validation_bits & CPER_MEM_VALID_RANK_NUMBER) + n += scnprintf(msg + n, len - n, "rank:%d ", mem->rank); + if (mem->validation_bits & CPER_MEM_VALID_BANK) + n += scnprintf(msg + n, len - n, "bank:%d ", mem->bank); + if (mem->validation_bits & CPER_MEM_VALID_BANK_GROUP) + n += scnprintf(msg + n, len - n, "bank_group:%d ", + mem->bank >> CPER_MEM_BANK_GROUP_SHIFT); + if (mem->validation_bits & CPER_MEM_VALID_BANK_ADDRESS) + n += scnprintf(msg + n, len - n, "bank_address:%d ", + mem->bank & CPER_MEM_BANK_ADDRESS_MASK); + if (mem->validation_bits & CPER_MEM_VALID_DEVICE) + n += scnprintf(msg + n, len - n, "device:%d ", mem->device); + if (mem->validation_bits & (CPER_MEM_VALID_ROW | CPER_MEM_VALID_ROW_EXT)) { + u32 row = mem->row; + + row |= cper_get_mem_extension(mem->validation_bits, mem->extended); + n += scnprintf(msg + n, len - n, "row:%d ", row); + } + if (mem->validation_bits & CPER_MEM_VALID_COLUMN) + n += scnprintf(msg + n, len - n, "column:%d ", mem->column); + if (mem->validation_bits & CPER_MEM_VALID_BIT_POSITION) + n += scnprintf(msg + n, len - n, "bit_position:%d ", + mem->bit_pos); + if (mem->validation_bits & CPER_MEM_VALID_REQUESTOR_ID) + n += scnprintf(msg + n, len - n, "requestor_id:0x%016llx ", + mem->requestor_id); + if (mem->validation_bits & CPER_MEM_VALID_RESPONDER_ID) + n += scnprintf(msg + n, len - n, "responder_id:0x%016llx ", + mem->responder_id); + if (mem->validation_bits & CPER_MEM_VALID_TARGET_ID) + n += scnprintf(msg + n, len - n, "target_id:0x%016llx ", + mem->target_id); + if (mem->validation_bits & CPER_MEM_VALID_CHIP_ID) + n += scnprintf(msg + n, len - n, "chip_id:%d ", + mem->extended >> CPER_MEM_CHIP_ID_SHIFT); + + return n; +} +EXPORT_SYMBOL_GPL(cper_mem_err_location); + +int cper_dimm_err_location(struct cper_mem_err_compact *mem, char *msg) +{ + u32 len, n; + const char *bank = NULL, *device = NULL; + + if (!msg || !(mem->validation_bits & CPER_MEM_VALID_MODULE_HANDLE)) + return 0; + + len = CPER_REC_LEN; + dmi_memdev_name(mem->mem_dev_handle, &bank, &device); + if (bank && device) + n = snprintf(msg, len, "DIMM location: %s %s ", bank, device); + else + n = snprintf(msg, len, + "DIMM location: not present. DMI handle: 0x%.4x ", + mem->mem_dev_handle); + + return n; +} +EXPORT_SYMBOL_GPL(cper_dimm_err_location); + +void cper_mem_err_pack(const struct cper_sec_mem_err *mem, + struct cper_mem_err_compact *cmem) +{ + cmem->validation_bits = mem->validation_bits; + cmem->node = mem->node; + cmem->card = mem->card; + cmem->module = mem->module; + cmem->bank = mem->bank; + cmem->device = mem->device; + cmem->row = mem->row; + cmem->column = mem->column; + cmem->bit_pos = mem->bit_pos; + cmem->requestor_id = mem->requestor_id; + cmem->responder_id = mem->responder_id; + cmem->target_id = mem->target_id; + cmem->extended = mem->extended; + cmem->rank = mem->rank; + cmem->mem_array_handle = mem->mem_array_handle; + cmem->mem_dev_handle = mem->mem_dev_handle; +} +EXPORT_SYMBOL_GPL(cper_mem_err_pack); + +const char *cper_mem_err_unpack(struct trace_seq *p, + struct cper_mem_err_compact *cmem) +{ + const char *ret = trace_seq_buffer_ptr(p); + char rcd_decode_str[CPER_REC_LEN]; + + if (cper_mem_err_location(cmem, rcd_decode_str)) + trace_seq_printf(p, "%s", rcd_decode_str); + if (cper_dimm_err_location(cmem, rcd_decode_str)) + trace_seq_printf(p, "%s", rcd_decode_str); + trace_seq_putc(p, '\0'); + + return ret; +} + +static void cper_print_mem(const char *pfx, const struct cper_sec_mem_err *mem, + int len) +{ + struct cper_mem_err_compact cmem; + char rcd_decode_str[CPER_REC_LEN]; + + /* Don't trust UEFI 2.1/2.2 structure with bad validation bits */ + if (len == sizeof(struct cper_sec_mem_err_old) && + (mem->validation_bits & ~(CPER_MEM_VALID_RANK_NUMBER - 1))) { + pr_err(FW_WARN "valid bits set for fields beyond structure\n"); + return; + } + if (mem->validation_bits & CPER_MEM_VALID_ERROR_STATUS) + printk("%s error_status: %s (0x%016llx)\n", + pfx, cper_mem_err_status_str(mem->error_status), + mem->error_status); + if (mem->validation_bits & CPER_MEM_VALID_PA) + printk("%s""physical_address: 0x%016llx\n", + pfx, mem->physical_addr); + if (mem->validation_bits & CPER_MEM_VALID_PA_MASK) + printk("%s""physical_address_mask: 0x%016llx\n", + pfx, mem->physical_addr_mask); + cper_mem_err_pack(mem, &cmem); + if (cper_mem_err_location(&cmem, rcd_decode_str)) + printk("%s%s\n", pfx, rcd_decode_str); + if (mem->validation_bits & CPER_MEM_VALID_ERROR_TYPE) { + u8 etype = mem->error_type; + printk("%s""error_type: %d, %s\n", pfx, etype, + cper_mem_err_type_str(etype)); + } + if (cper_dimm_err_location(&cmem, rcd_decode_str)) + printk("%s%s\n", pfx, rcd_decode_str); +} + +static const char * const pcie_port_type_strs[] = { + "PCIe end point", + "legacy PCI end point", + "unknown", + "unknown", + "root port", + "upstream switch port", + "downstream switch port", + "PCIe to PCI/PCI-X bridge", + "PCI/PCI-X to PCIe bridge", + "root complex integrated endpoint device", + "root complex event collector", +}; + +static void cper_print_pcie(const char *pfx, const struct cper_sec_pcie *pcie, + const struct acpi_hest_generic_data *gdata) +{ + if (pcie->validation_bits & CPER_PCIE_VALID_PORT_TYPE) + printk("%s""port_type: %d, %s\n", pfx, pcie->port_type, + pcie->port_type < ARRAY_SIZE(pcie_port_type_strs) ? + pcie_port_type_strs[pcie->port_type] : "unknown"); + if (pcie->validation_bits & CPER_PCIE_VALID_VERSION) + printk("%s""version: %d.%d\n", pfx, + pcie->version.major, pcie->version.minor); + if (pcie->validation_bits & CPER_PCIE_VALID_COMMAND_STATUS) + printk("%s""command: 0x%04x, status: 0x%04x\n", pfx, + pcie->command, pcie->status); + if (pcie->validation_bits & CPER_PCIE_VALID_DEVICE_ID) { + const __u8 *p; + printk("%s""device_id: %04x:%02x:%02x.%x\n", pfx, + pcie->device_id.segment, pcie->device_id.bus, + pcie->device_id.device, pcie->device_id.function); + printk("%s""slot: %d\n", pfx, + pcie->device_id.slot >> CPER_PCIE_SLOT_SHIFT); + printk("%s""secondary_bus: 0x%02x\n", pfx, + pcie->device_id.secondary_bus); + printk("%s""vendor_id: 0x%04x, device_id: 0x%04x\n", pfx, + pcie->device_id.vendor_id, pcie->device_id.device_id); + p = pcie->device_id.class_code; + printk("%s""class_code: %02x%02x%02x\n", pfx, p[2], p[1], p[0]); + } + if (pcie->validation_bits & CPER_PCIE_VALID_SERIAL_NUMBER) + printk("%s""serial number: 0x%04x, 0x%04x\n", pfx, + pcie->serial_number.lower, pcie->serial_number.upper); + if (pcie->validation_bits & CPER_PCIE_VALID_BRIDGE_CONTROL_STATUS) + printk( + "%s""bridge: secondary_status: 0x%04x, control: 0x%04x\n", + pfx, pcie->bridge.secondary_status, pcie->bridge.control); + + /* + * Print all valid AER info. Record may be from BERT (boot-time) or GHES (run-time). + * + * Fatal errors call __ghes_panic() before AER handler prints this. + */ + if (pcie->validation_bits & CPER_PCIE_VALID_AER_INFO) { + struct aer_capability_regs *aer; + + aer = (struct aer_capability_regs *)pcie->aer_info; + printk("%saer_cor_status: 0x%08x, aer_cor_mask: 0x%08x\n", + pfx, aer->cor_status, aer->cor_mask); + printk("%saer_uncor_status: 0x%08x, aer_uncor_mask: 0x%08x\n", + pfx, aer->uncor_status, aer->uncor_mask); + printk("%saer_uncor_severity: 0x%08x\n", + pfx, aer->uncor_severity); + printk("%sTLP Header: %08x %08x %08x %08x\n", pfx, + aer->header_log.dw[0], aer->header_log.dw[1], + aer->header_log.dw[2], aer->header_log.dw[3]); + } +} + +static const char * const fw_err_rec_type_strs[] = { + "IPF SAL Error Record", + "SOC Firmware Error Record Type1 (Legacy CrashLog Support)", + "SOC Firmware Error Record Type2", +}; + +static void cper_print_fw_err(const char *pfx, + struct acpi_hest_generic_data *gdata, + const struct cper_sec_fw_err_rec_ref *fw_err) +{ + void *buf = acpi_hest_get_payload(gdata); + u32 offset, length = gdata->error_data_length; + + printk("%s""Firmware Error Record Type: %s\n", pfx, + fw_err->record_type < ARRAY_SIZE(fw_err_rec_type_strs) ? + fw_err_rec_type_strs[fw_err->record_type] : "unknown"); + printk("%s""Revision: %d\n", pfx, fw_err->revision); + + /* Record Type based on UEFI 2.7 */ + if (fw_err->revision == 0) { + printk("%s""Record Identifier: %08llx\n", pfx, + fw_err->record_identifier); + } else if (fw_err->revision == 2) { + printk("%s""Record Identifier: %pUl\n", pfx, + &fw_err->record_identifier_guid); + } + + /* + * The FW error record may contain trailing data beyond the + * structure defined by the specification. As the fields + * defined (and hence the offset of any trailing data) vary + * with the revision, set the offset to account for this + * variation. + */ + if (fw_err->revision == 0) { + /* record_identifier_guid not defined */ + offset = offsetof(struct cper_sec_fw_err_rec_ref, + record_identifier_guid); + } else if (fw_err->revision == 1) { + /* record_identifier not defined */ + offset = offsetof(struct cper_sec_fw_err_rec_ref, + record_identifier); + } else { + offset = sizeof(*fw_err); + } + if (offset > length) { + printk("%s""error section length is too small: offset=%d, length=%d\n", + pfx, offset, length); + return; + } + + buf += offset; + length -= offset; + + print_hex_dump(pfx, "", DUMP_PREFIX_OFFSET, 16, 4, buf, length, true); +} + +static void cper_print_tstamp(const char *pfx, + struct acpi_hest_generic_data_v300 *gdata) +{ + __u8 hour, min, sec, day, mon, year, century, *timestamp; + + if (gdata->validation_bits & ACPI_HEST_GEN_VALID_TIMESTAMP) { + timestamp = (__u8 *)&(gdata->time_stamp); + sec = bcd2bin(timestamp[0]); + min = bcd2bin(timestamp[1]); + hour = bcd2bin(timestamp[2]); + day = bcd2bin(timestamp[4]); + mon = bcd2bin(timestamp[5]); + year = bcd2bin(timestamp[6]); + century = bcd2bin(timestamp[7]); + + printk("%s%ststamp: %02d%02d-%02d-%02d %02d:%02d:%02d\n", pfx, + (timestamp[3] & 0x1 ? "precise " : "imprecise "), + century, year, mon, day, hour, min, sec); + } +} + +struct ignore_section { + guid_t guid; + const char *name; +}; + +static const struct ignore_section ignore_sections[] = { + { .guid = CPER_SEC_CXL_GEN_MEDIA_GUID, .name = "CXL General Media Event" }, + { .guid = CPER_SEC_CXL_DRAM_GUID, .name = "CXL DRAM Event" }, + { .guid = CPER_SEC_CXL_MEM_MODULE_GUID, .name = "CXL Memory Module Event" }, +}; + +static void +cper_estatus_print_section(const char *pfx, struct acpi_hest_generic_data *gdata, + int sec_no) +{ + guid_t *sec_type = (guid_t *)gdata->section_type; + __u16 severity; + char newpfx[64]; + + if (acpi_hest_get_version(gdata) >= 3) + cper_print_tstamp(pfx, (struct acpi_hest_generic_data_v300 *)gdata); + + severity = gdata->error_severity; + printk("%s""Error %d, type: %s\n", pfx, sec_no, + cper_severity_str(severity)); + if (gdata->validation_bits & CPER_SEC_VALID_FRU_ID) + printk("%s""fru_id: %pUl\n", pfx, gdata->fru_id); + if (gdata->validation_bits & CPER_SEC_VALID_FRU_TEXT) + printk("%s""fru_text: %.20s\n", pfx, gdata->fru_text); + + snprintf(newpfx, sizeof(newpfx), "%s ", pfx); + + for (int i = 0; i < ARRAY_SIZE(ignore_sections); i++) { + if (guid_equal(sec_type, &ignore_sections[i].guid)) { + printk("%ssection_type: %s\n", newpfx, ignore_sections[i].name); + return; + } + } + + if (guid_equal(sec_type, &CPER_SEC_PROC_GENERIC)) { + struct cper_sec_proc_generic *proc_err = acpi_hest_get_payload(gdata); + + printk("%s""section_type: general processor error\n", newpfx); + if (gdata->error_data_length >= sizeof(*proc_err)) + cper_print_proc_generic(newpfx, proc_err); + else + goto err_section_too_small; + } else if (guid_equal(sec_type, &CPER_SEC_PLATFORM_MEM)) { + struct cper_sec_mem_err *mem_err = acpi_hest_get_payload(gdata); + + printk("%s""section_type: memory error\n", newpfx); + if (gdata->error_data_length >= + sizeof(struct cper_sec_mem_err_old)) + cper_print_mem(newpfx, mem_err, + gdata->error_data_length); + else + goto err_section_too_small; + } else if (guid_equal(sec_type, &CPER_SEC_PCIE)) { + struct cper_sec_pcie *pcie = acpi_hest_get_payload(gdata); + + printk("%s""section_type: PCIe error\n", newpfx); + if (gdata->error_data_length >= sizeof(*pcie)) + cper_print_pcie(newpfx, pcie, gdata); + else + goto err_section_too_small; +#if defined(CONFIG_ARM64) || defined(CONFIG_ARM) + } else if (guid_equal(sec_type, &CPER_SEC_PROC_ARM)) { + struct cper_sec_proc_arm *arm_err = acpi_hest_get_payload(gdata); + + printk("%ssection_type: ARM processor error\n", newpfx); + if (gdata->error_data_length >= sizeof(*arm_err)) + cper_print_proc_arm(newpfx, arm_err, + gdata->error_data_length); + else + goto err_section_too_small; +#endif +#if defined(CONFIG_UEFI_CPER_X86) + } else if (guid_equal(sec_type, &CPER_SEC_PROC_IA)) { + struct cper_sec_proc_ia *ia_err = acpi_hest_get_payload(gdata); + + printk("%ssection_type: IA32/X64 processor error\n", newpfx); + if (gdata->error_data_length >= sizeof(*ia_err)) + cper_print_proc_ia(newpfx, ia_err); + else + goto err_section_too_small; +#endif + } else if (guid_equal(sec_type, &CPER_SEC_FW_ERR_REC_REF)) { + struct cper_sec_fw_err_rec_ref *fw_err = acpi_hest_get_payload(gdata); + + printk("%ssection_type: Firmware Error Record Reference\n", + newpfx); + /* The minimal FW Error Record contains 16 bytes */ + if (gdata->error_data_length >= SZ_16) + cper_print_fw_err(newpfx, gdata, fw_err); + else + goto err_section_too_small; + } else if (guid_equal(sec_type, &CPER_SEC_CXL_PROT_ERR)) { + struct cxl_cper_sec_prot_err *prot_err = acpi_hest_get_payload(gdata); + + printk("%ssection_type: CXL Protocol Error\n", newpfx); + if (gdata->error_data_length >= sizeof(*prot_err)) + cxl_cper_print_prot_err(newpfx, prot_err); + else + goto err_section_too_small; + } else { + const void *err = acpi_hest_get_payload(gdata); + + printk("%ssection type: unknown, %pUl\n", newpfx, sec_type); + printk("%ssection length: %#x\n", newpfx, + gdata->error_data_length); + print_hex_dump(newpfx, "", DUMP_PREFIX_OFFSET, 16, 4, err, + gdata->error_data_length, true); + } + + return; + +err_section_too_small: + pr_err(FW_WARN "error section length is too small\n"); +} + +void cper_estatus_print(const char *pfx, + const struct acpi_hest_generic_status *estatus) +{ + struct acpi_hest_generic_data *gdata; + int sec_no = 0; + char newpfx[64]; + __u16 severity; + + severity = estatus->error_severity; + if (severity == CPER_SEV_CORRECTED) + printk("%s%s\n", pfx, + "It has been corrected by h/w " + "and requires no further action"); + printk("%s""event severity: %s\n", pfx, cper_severity_str(severity)); + snprintf(newpfx, sizeof(newpfx), "%s ", pfx); + + apei_estatus_for_each_section(estatus, gdata) { + cper_estatus_print_section(newpfx, gdata, sec_no); + sec_no++; + } +} +EXPORT_SYMBOL_GPL(cper_estatus_print); + +int cper_estatus_check_header(const struct acpi_hest_generic_status *estatus) +{ + if (estatus->data_length && + estatus->data_length < sizeof(struct acpi_hest_generic_data)) + return -EINVAL; + if (estatus->raw_data_length && + estatus->raw_data_offset < sizeof(*estatus) + estatus->data_length) + return -EINVAL; + + return 0; +} +EXPORT_SYMBOL_GPL(cper_estatus_check_header); + +int cper_estatus_check(const struct acpi_hest_generic_status *estatus) +{ + struct acpi_hest_generic_data *gdata; + unsigned int data_len, record_size; + int rc; + + rc = cper_estatus_check_header(estatus); + if (rc) + return rc; + + data_len = estatus->data_length; + + apei_estatus_for_each_section(estatus, gdata) { + if (acpi_hest_get_size(gdata) > data_len) + return -EINVAL; + + record_size = acpi_hest_get_record_size(gdata); + if (record_size > data_len) + return -EINVAL; + + data_len -= record_size; + } + if (data_len) + return -EINVAL; + + return 0; +} +EXPORT_SYMBOL_GPL(cper_estatus_check); diff --git a/drivers/firmware/efi/cper_cxl.c b/drivers/firmware/efi/cper_cxl.c new file mode 100644 index 000000000..8a7667faf --- /dev/null +++ b/drivers/firmware/efi/cper_cxl.c @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * UEFI Common Platform Error Record (CPER) support for CXL Section. + * + * Copyright (C) 2022 Advanced Micro Devices, Inc. + * + * Author: Smita Koralahalli + */ + +#include +#include + +static const char * const prot_err_agent_type_strs[] = { + "Restricted CXL Device", + "Restricted CXL Host Downstream Port", + "CXL Device", + "CXL Logical Device", + "CXL Fabric Manager managed Logical Device", + "CXL Root Port", + "CXL Downstream Switch Port", + "CXL Upstream Switch Port", +}; + +void cxl_cper_print_prot_err(const char *pfx, + const struct cxl_cper_sec_prot_err *prot_err) +{ + if (prot_err->valid_bits & PROT_ERR_VALID_AGENT_TYPE) + pr_info("%s agent_type: %d, %s\n", pfx, prot_err->agent_type, + prot_err->agent_type < ARRAY_SIZE(prot_err_agent_type_strs) + ? prot_err_agent_type_strs[prot_err->agent_type] + : "unknown"); + + if (prot_err->valid_bits & PROT_ERR_VALID_AGENT_ADDRESS) { + switch (prot_err->agent_type) { + /* + * According to UEFI 2.10 Section N.2.13, the term CXL Device + * is used to refer to Restricted CXL Device, CXL Device, CXL + * Logical Device or a CXL Fabric Manager Managed Logical + * Device. + */ + case RCD: + case DEVICE: + case LD: + case FMLD: + case RP: + case DSP: + case USP: + pr_info("%s agent_address: %04x:%02x:%02x.%x\n", + pfx, prot_err->agent_addr.segment, + prot_err->agent_addr.bus, + prot_err->agent_addr.device, + prot_err->agent_addr.function); + break; + case RCH_DP: + pr_info("%s rcrb_base_address: 0x%016llx\n", pfx, + prot_err->agent_addr.rcrb_base_addr); + break; + default: + break; + } + } + + if (prot_err->valid_bits & PROT_ERR_VALID_DEVICE_ID) { + const __u8 *class_code; + + switch (prot_err->agent_type) { + case RCD: + case DEVICE: + case LD: + case FMLD: + case RP: + case DSP: + case USP: + pr_info("%s slot: %d\n", pfx, + prot_err->device_id.slot >> CPER_PCIE_SLOT_SHIFT); + pr_info("%s vendor_id: 0x%04x, device_id: 0x%04x\n", + pfx, prot_err->device_id.vendor_id, + prot_err->device_id.device_id); + pr_info("%s sub_vendor_id: 0x%04x, sub_device_id: 0x%04x\n", + pfx, prot_err->device_id.subsystem_vendor_id, + prot_err->device_id.subsystem_id); + class_code = prot_err->device_id.class_code; + pr_info("%s class_code: %02x%02x\n", pfx, + class_code[1], class_code[0]); + break; + default: + break; + } + } + + if (prot_err->valid_bits & PROT_ERR_VALID_SERIAL_NUMBER) { + switch (prot_err->agent_type) { + case RCD: + case DEVICE: + case LD: + case FMLD: + pr_info("%s lower_dw: 0x%08x, upper_dw: 0x%08x\n", pfx, + prot_err->dev_serial_num.lower_dw, + prot_err->dev_serial_num.upper_dw); + break; + default: + break; + } + } + + if (prot_err->valid_bits & PROT_ERR_VALID_CAPABILITY) { + switch (prot_err->agent_type) { + case RCD: + case DEVICE: + case LD: + case FMLD: + case RP: + case DSP: + case USP: + print_hex_dump(pfx, "", DUMP_PREFIX_OFFSET, 16, 4, + prot_err->capability, + sizeof(prot_err->capability), 0); + break; + default: + break; + } + } + + if (prot_err->valid_bits & PROT_ERR_VALID_DVSEC) { + pr_info("%s DVSEC length: 0x%04x\n", pfx, prot_err->dvsec_len); + + pr_info("%s CXL DVSEC:\n", pfx); + print_hex_dump(pfx, "", DUMP_PREFIX_OFFSET, 16, 4, (prot_err + 1), + prot_err->dvsec_len, 0); + } + + if (prot_err->valid_bits & PROT_ERR_VALID_ERROR_LOG) { + size_t size = sizeof(*prot_err) + prot_err->dvsec_len; + struct cxl_ras_capability_regs *cxl_ras; + + pr_info("%s Error log length: 0x%04x\n", pfx, prot_err->err_len); + + pr_info("%s CXL Error Log:\n", pfx); + cxl_ras = (struct cxl_ras_capability_regs *)((long)prot_err + size); + pr_info("%s cxl_ras_uncor_status: 0x%08x", pfx, + cxl_ras->uncor_status); + pr_info("%s cxl_ras_uncor_mask: 0x%08x\n", pfx, + cxl_ras->uncor_mask); + pr_info("%s cxl_ras_uncor_severity: 0x%08x\n", pfx, + cxl_ras->uncor_severity); + pr_info("%s cxl_ras_cor_status: 0x%08x", pfx, + cxl_ras->cor_status); + pr_info("%s cxl_ras_cor_mask: 0x%08x\n", pfx, + cxl_ras->cor_mask); + pr_info("%s cap_control: 0x%08x\n", pfx, + cxl_ras->cap_control); + pr_info("%s Header Log Registers:\n", pfx); + print_hex_dump(pfx, "", DUMP_PREFIX_OFFSET, 16, 4, cxl_ras->header_log, + sizeof(cxl_ras->header_log), 0); + } +} diff --git a/drivers/firmware/efi/dev-path-parser.c b/drivers/firmware/efi/dev-path-parser.c new file mode 100644 index 000000000..13ea141c0 --- /dev/null +++ b/drivers/firmware/efi/dev-path-parser.c @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dev-path-parser.c - EFI Device Path parser + * Copyright (C) 2016 Lukas Wunner + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License (version 2) as + * published by the Free Software Foundation. + */ + +#include +#include +#include + +static long __init parse_acpi_path(const struct efi_dev_path *node, + struct device *parent, struct device **child) +{ + struct acpi_device *adev; + struct device *phys_dev; + char hid[ACPI_ID_LEN]; + + if (node->header.length != 12) + return -EINVAL; + + sprintf(hid, "%c%c%c%04X", + 'A' + ((node->acpi.hid >> 10) & 0x1f) - 1, + 'A' + ((node->acpi.hid >> 5) & 0x1f) - 1, + 'A' + ((node->acpi.hid >> 0) & 0x1f) - 1, + node->acpi.hid >> 16); + + for_each_acpi_dev_match(adev, hid, NULL, -1) { + if (acpi_dev_uid_match(adev, node->acpi.uid)) + break; + if (!acpi_device_uid(adev) && node->acpi.uid == 0) + break; + } + if (!adev) + return -ENODEV; + + phys_dev = acpi_get_first_physical_node(adev); + if (phys_dev) { + *child = get_device(phys_dev); + acpi_dev_put(adev); + } else + *child = &adev->dev; + + return 0; +} + +static int __init match_pci_dev(struct device *dev, const void *data) +{ + unsigned int devfn = *(const unsigned int *)data; + + return dev_is_pci(dev) && to_pci_dev(dev)->devfn == devfn; +} + +static long __init parse_pci_path(const struct efi_dev_path *node, + struct device *parent, struct device **child) +{ + unsigned int devfn; + + if (node->header.length != 6) + return -EINVAL; + if (!parent) + return -EINVAL; + + devfn = PCI_DEVFN(node->pci.dev, node->pci.fn); + + *child = device_find_child(parent, &devfn, match_pci_dev); + if (!*child) + return -ENODEV; + + return 0; +} + +/* + * Insert parsers for further node types here. + * + * Each parser takes a pointer to the @node and to the @parent (will be NULL + * for the first device path node). If a device corresponding to @node was + * found below @parent, its reference count should be incremented and the + * device returned in @child. + * + * The return value should be 0 on success or a negative int on failure. + * The special return values 0x01 (EFI_DEV_END_INSTANCE) and 0xFF + * (EFI_DEV_END_ENTIRE) signal the end of the device path, only + * parse_end_path() is supposed to return this. + * + * Be sure to validate the node length and contents before commencing the + * search for a device. + */ + +static long __init parse_end_path(const struct efi_dev_path *node, + struct device *parent, struct device **child) +{ + if (node->header.length != 4) + return -EINVAL; + if (node->header.sub_type != EFI_DEV_END_INSTANCE && + node->header.sub_type != EFI_DEV_END_ENTIRE) + return -EINVAL; + if (!parent) + return -ENODEV; + + *child = get_device(parent); + return node->header.sub_type; +} + +/** + * efi_get_device_by_path - find device by EFI Device Path + * @node: EFI Device Path + * @len: maximum length of EFI Device Path in bytes + * + * Parse a series of EFI Device Path nodes at @node and find the corresponding + * device. If the device was found, its reference count is incremented and a + * pointer to it is returned. The caller needs to drop the reference with + * put_device() after use. The @node pointer is updated to point to the + * location immediately after the "End of Hardware Device Path" node. + * + * If another Device Path instance follows, @len is decremented by the number + * of bytes consumed. Otherwise @len is set to %0. + * + * If a Device Path node is malformed or its corresponding device is not found, + * @node is updated to point to this offending node and an ERR_PTR is returned. + * + * If @len is initially %0, the function returns %NULL. Thus, to iterate over + * all instances in a path, the following idiom may be used: + * + * while (!IS_ERR_OR_NULL(dev = efi_get_device_by_path(&node, &len))) { + * // do something with dev + * put_device(dev); + * } + * if (IS_ERR(dev)) + * // report error + * + * Devices can only be found if they're already instantiated. Most buses + * instantiate devices in the "subsys" initcall level, hence the earliest + * initcall level in which this function should be called is "fs". + * + * Returns the device on success or + * %ERR_PTR(-ENODEV) if no device was found, + * %ERR_PTR(-EINVAL) if a node is malformed or exceeds @len, + * %ERR_PTR(-ENOTSUPP) if support for a node type is not yet implemented. + */ +struct device * __init efi_get_device_by_path(const struct efi_dev_path **node, + size_t *len) +{ + struct device *parent = NULL, *child; + long ret = 0; + + if (!*len) + return NULL; + + while (!ret) { + if (*len < 4 || *len < (*node)->header.length) + ret = -EINVAL; + else if ((*node)->header.type == EFI_DEV_ACPI && + (*node)->header.sub_type == EFI_DEV_BASIC_ACPI) + ret = parse_acpi_path(*node, parent, &child); + else if ((*node)->header.type == EFI_DEV_HW && + (*node)->header.sub_type == EFI_DEV_PCI) + ret = parse_pci_path(*node, parent, &child); + else if (((*node)->header.type == EFI_DEV_END_PATH || + (*node)->header.type == EFI_DEV_END_PATH2)) + ret = parse_end_path(*node, parent, &child); + else + ret = -ENOTSUPP; + + put_device(parent); + if (ret < 0) + return ERR_PTR(ret); + + parent = child; + *node = (void *)*node + (*node)->header.length; + *len -= (*node)->header.length; + } + + if (ret == EFI_DEV_END_ENTIRE) + *len = 0; + + return child; +} diff --git a/drivers/firmware/efi/earlycon.c b/drivers/firmware/efi/earlycon.c new file mode 100644 index 000000000..3d060d599 --- /dev/null +++ b/drivers/firmware/efi/earlycon.c @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2013 Intel Corporation; author Matt Fleming + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static const struct console *earlycon_console __initdata; +static const struct font_desc *font; +static u16 cur_line_y, max_line_y; +static u32 efi_x_array[1024]; +static u32 efi_x, efi_y; +static u64 fb_base; +static bool fb_wb; +static void *efi_fb; + +/* + * EFI earlycon needs to use early_memremap() to map the framebuffer. + * But early_memremap() is not usable for 'earlycon=efifb keep_bootcon', + * memremap() should be used instead. memremap() will be available after + * paging_init() which is earlier than initcall callbacks. Thus adding this + * early initcall function early_efi_map_fb() to map the whole EFI framebuffer. + */ +static int __init efi_earlycon_remap_fb(void) +{ + const struct screen_info *si = &sysfb_primary_display.screen; + + /* bail if there is no bootconsole or it was unregistered already */ + if (!earlycon_console || !console_is_registered(earlycon_console)) + return 0; + + efi_fb = memremap(fb_base, si->lfb_size, fb_wb ? MEMREMAP_WB : MEMREMAP_WC); + + return efi_fb ? 0 : -ENOMEM; +} +early_initcall(efi_earlycon_remap_fb); + +static int __init efi_earlycon_unmap_fb(void) +{ + /* unmap the bootconsole fb unless keep_bootcon left it registered */ + if (efi_fb && !console_is_registered(earlycon_console)) + memunmap(efi_fb); + return 0; +} +late_initcall(efi_earlycon_unmap_fb); + +static __ref void *efi_earlycon_map(unsigned long start, unsigned long len) +{ + pgprot_t fb_prot; + + if (efi_fb) + return efi_fb + start; + + fb_prot = fb_wb ? PAGE_KERNEL : pgprot_writecombine(PAGE_KERNEL); + return early_memremap_prot(fb_base + start, len, pgprot_val(fb_prot)); +} + +static __ref void efi_earlycon_unmap(void *addr, unsigned long len) +{ + if (efi_fb) + return; + + early_memunmap(addr, len); +} + +static void efi_earlycon_clear_scanline(unsigned int y, const struct screen_info *si) +{ + unsigned long *dst; + u16 len; + + len = si->lfb_linelength; + dst = efi_earlycon_map(y*len, len); + if (!dst) + return; + + memset(dst, 0, len); + efi_earlycon_unmap(dst, len); +} + +static void efi_earlycon_scroll_up(const struct screen_info *si) +{ + unsigned long *dst, *src; + u16 maxlen = 0; + u16 len; + u32 i, height; + + /* Find the cached maximum x coordinate */ + for (i = 0; i < max_line_y; i++) { + if (efi_x_array[i] > maxlen) + maxlen = efi_x_array[i]; + } + maxlen *= 4; + + len = si->lfb_linelength; + height = si->lfb_height; + + for (i = 0; i < height - font->height; i++) { + dst = efi_earlycon_map(i*len, len); + if (!dst) + return; + + src = efi_earlycon_map((i + font->height) * len, len); + if (!src) { + efi_earlycon_unmap(dst, len); + return; + } + + memmove(dst, src, maxlen); + + efi_earlycon_unmap(src, len); + efi_earlycon_unmap(dst, len); + } +} + +static void efi_earlycon_write_char(u32 *dst, unsigned char c, unsigned int h, + const struct screen_info *si) +{ + const u32 color_black = 0x00000000; + const u32 color_white = 0x00ffffff; + const u8 *src; + int m, n, bytes; + u8 x; + + bytes = BITS_TO_BYTES(font->width); + src = font->data + c * font->height * bytes + h * bytes; + + for (m = 0; m < font->width; m++) { + n = m % 8; + x = *(src + m / 8); + if ((x >> (7 - n)) & 1) + *dst = color_white; + else + *dst = color_black; + dst++; + } +} + +static void +efi_earlycon_write(struct console *con, const char *str, unsigned int num) +{ + const struct screen_info *si = &sysfb_primary_display.screen; + u32 cur_efi_x = efi_x; + unsigned int len; + const char *s; + void *dst; + + len = si->lfb_linelength; + + while (num) { + unsigned int linemax = (si->lfb_width - efi_x) / font->width; + unsigned int h, count; + + count = strnchrnul(str, num, '\n') - str; + if (count > linemax) + count = linemax; + + for (h = 0; h < font->height; h++) { + unsigned int n, x; + + dst = efi_earlycon_map((efi_y + h) * len, len); + if (!dst) + return; + + s = str; + n = count; + x = efi_x; + + while (n-- > 0) { + efi_earlycon_write_char(dst + x * 4, *s, h, si); + x += font->width; + s++; + } + + efi_earlycon_unmap(dst, len); + } + + num -= count; + efi_x += count * font->width; + str += count; + + if (num > 0 && *s == '\n') { + cur_efi_x = efi_x; + efi_x = 0; + efi_y += font->height; + str++; + num--; + } + + if (efi_x + font->width > si->lfb_width) { + cur_efi_x = efi_x; + efi_x = 0; + efi_y += font->height; + } + + if (efi_y + font->height > si->lfb_height) { + u32 i; + + efi_x_array[cur_line_y] = cur_efi_x; + cur_line_y = (cur_line_y + 1) % max_line_y; + + efi_y -= font->height; + efi_earlycon_scroll_up(si); + + for (i = 0; i < font->height; i++) + efi_earlycon_clear_scanline(efi_y + i, si); + } + } +} + +static bool __initdata fb_probed; + +void __init efi_earlycon_reprobe(void) +{ + if (fb_probed) + setup_earlycon("efifb"); +} + +static int __init efi_earlycon_setup(struct earlycon_device *device, + const char *opt) +{ + const struct screen_info *si = &sysfb_primary_display.screen; + u16 xres, yres; + u32 i; + + fb_wb = opt && !strcmp(opt, "ram"); + + if (si->orig_video_isVGA != VIDEO_TYPE_EFI) { + fb_probed = true; + return -ENODEV; + } + + fb_base = si->lfb_base; + if (si->capabilities & VIDEO_CAPABILITY_64BIT_BASE) + fb_base |= (u64)si->ext_lfb_base << 32; + + xres = si->lfb_width; + yres = si->lfb_height; + + /* + * efi_earlycon_write_char() implicitly assumes a framebuffer with + * 32 bits per pixel. + */ + if (si->lfb_depth != 32) + return -ENODEV; + + font = get_default_font(xres, yres, NULL, NULL); + if (!font) + return -ENODEV; + + /* Fill the cache with maximum possible value of x coordinate */ + memset32(efi_x_array, rounddown(xres, font->width), ARRAY_SIZE(efi_x_array)); + efi_y = rounddown(yres, font->height); + + /* Make sure we have cache for the x coordinate for the full screen */ + max_line_y = efi_y / font->height + 1; + cur_line_y = 0; + + efi_y -= font->height; + for (i = 0; i < (yres - efi_y) / font->height; i++) + efi_earlycon_scroll_up(si); + + device->con->write = efi_earlycon_write; + earlycon_console = device->con; + return 0; +} +EARLYCON_DECLARE(efifb, efi_earlycon_setup); diff --git a/drivers/firmware/efi/efi-bgrt.c b/drivers/firmware/efi/efi-bgrt.c new file mode 100644 index 000000000..1da451582 --- /dev/null +++ b/drivers/firmware/efi/efi-bgrt.c @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2012 Intel Corporation + * Author: Josh Triplett + * + * Based on the bgrt driver: + * Copyright 2012 Red Hat, Inc + * Author: Matthew Garrett + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include + +struct acpi_table_bgrt bgrt_tab; +size_t bgrt_image_size; + +struct bmp_header { + u16 id; + u32 size; +} __packed; + +void __init efi_bgrt_init(struct acpi_table_header *table) +{ + void *image; + struct bmp_header bmp_header; + struct acpi_table_bgrt *bgrt = &bgrt_tab; + int mem_type; + + if (acpi_disabled) + return; + + if (!efi_enabled(EFI_MEMMAP) && !efi_enabled(EFI_PARAVIRT)) + return; + + if (table->length < sizeof(bgrt_tab)) { + pr_notice("Ignoring BGRT: invalid length %u (expected %zu)\n", + table->length, sizeof(bgrt_tab)); + return; + } + *bgrt = *(struct acpi_table_bgrt *)table; + /* + * Only version 1 is defined but some older laptops (seen on Lenovo + * Ivy Bridge models) have a correct version 1 BGRT table with the + * version set to 0, so we accept version 0 and 1. + */ + if (bgrt->version > 1) { + pr_notice("Ignoring BGRT: invalid version %u (expected 1)\n", + bgrt->version); + goto out; + } + if (bgrt->image_type != 0) { + pr_notice("Ignoring BGRT: invalid image type %u (expected 0)\n", + bgrt->image_type); + goto out; + } + if (!bgrt->image_address) { + pr_notice("Ignoring BGRT: null image address\n"); + goto out; + } + + mem_type = efi_mem_type(bgrt->image_address); + if (mem_type != EFI_BOOT_SERVICES_DATA && + mem_type != EFI_ACPI_RECLAIM_MEMORY) { + pr_notice("Ignoring BGRT: invalid image address\n"); + goto out; + } + image = early_memremap(bgrt->image_address, sizeof(bmp_header)); + if (!image) { + pr_notice("Ignoring BGRT: failed to map image header memory\n"); + goto out; + } + + memcpy(&bmp_header, image, sizeof(bmp_header)); + early_memunmap(image, sizeof(bmp_header)); + if (bmp_header.id != 0x4d42) { + pr_notice("Ignoring BGRT: Incorrect BMP magic number 0x%x (expected 0x4d42)\n", + bmp_header.id); + goto out; + } + bgrt_image_size = bmp_header.size; + efi_mem_reserve(bgrt->image_address, bgrt_image_size); + + return; +out: + memset(bgrt, 0, sizeof(bgrt_tab)); +} diff --git a/drivers/firmware/efi/efi-init.c b/drivers/firmware/efi/efi-init.c new file mode 100644 index 000000000..6103b1a08 --- /dev/null +++ b/drivers/firmware/efi/efi-init.c @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Extensible Firmware Interface + * + * Based on Extensible Firmware Interface Specification version 2.4 + * + * Copyright (C) 2013 - 2015 Linaro Ltd. + */ + +#define pr_fmt(fmt) "efi: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +unsigned long __initdata primary_display_table = EFI_INVALID_TABLE_ADDR; + +static int __init is_memory(efi_memory_desc_t *md) +{ + if (md->attribute & (EFI_MEMORY_WB|EFI_MEMORY_WT|EFI_MEMORY_WC)) + return 1; + return 0; +} + +/* + * Translate a EFI virtual address into a physical address: this is necessary, + * as some data members of the EFI system table are virtually remapped after + * SetVirtualAddressMap() has been called. + */ +static phys_addr_t __init efi_to_phys(unsigned long addr) +{ + efi_memory_desc_t *md; + + for_each_efi_memory_desc(md) { + if (!(md->attribute & EFI_MEMORY_RUNTIME)) + continue; + if (md->virt_addr == 0) + /* no virtual mapping has been installed by the stub */ + break; + if (md->virt_addr <= addr && + (addr - md->virt_addr) < (md->num_pages << EFI_PAGE_SHIFT)) + return md->phys_addr + addr - md->virt_addr; + } + return addr; +} + +extern __weak const efi_config_table_type_t efi_arch_tables[]; + +/* + * x86 defines its own instance of sysfb_primary_display and uses + * it even without EFI, everything else can get them from here. + */ +#if !defined(CONFIG_X86) && (defined(CONFIG_SYSFB) || defined(CONFIG_EFI_EARLYCON) || defined(CONFIG_FIRMWARE_EDID)) +struct sysfb_display_info sysfb_primary_display __section(".data"); +EXPORT_SYMBOL_GPL(sysfb_primary_display); +#endif + +static void __init init_primary_display(void) +{ + struct sysfb_display_info *dpy; + + if (primary_display_table != EFI_INVALID_TABLE_ADDR) { + dpy = early_memremap(primary_display_table, sizeof(*dpy)); + if (!dpy) { + pr_err("Could not map primary_display config table\n"); + return; + } + sysfb_primary_display = *dpy; + memset(dpy, 0, sizeof(*dpy)); + early_memunmap(dpy, sizeof(*dpy)); + + if (memblock_is_map_memory(sysfb_primary_display.screen.lfb_base)) + memblock_mark_nomap(sysfb_primary_display.screen.lfb_base, + sysfb_primary_display.screen.lfb_size); + + if (IS_ENABLED(CONFIG_EFI_EARLYCON)) + efi_earlycon_reprobe(); + } +} + +static int __init uefi_init(u64 efi_system_table) +{ + efi_config_table_t *config_tables; + efi_system_table_t *systab; + size_t table_size; + int retval; + + systab = early_memremap_ro(efi_system_table, sizeof(efi_system_table_t)); + if (systab == NULL) { + pr_warn("Unable to map EFI system table.\n"); + return -ENOMEM; + } + + set_bit(EFI_BOOT, &efi.flags); + if (IS_ENABLED(CONFIG_64BIT)) + set_bit(EFI_64BIT, &efi.flags); + + retval = efi_systab_check_header(&systab->hdr); + if (retval) + goto out; + + efi.runtime = systab->runtime; + efi.runtime_version = systab->hdr.revision; + + efi_systab_report_header(&systab->hdr, efi_to_phys(systab->fw_vendor)); + + table_size = sizeof(efi_config_table_t) * systab->nr_tables; + config_tables = early_memremap_ro(efi_to_phys(systab->tables), + table_size); + if (config_tables == NULL) { + pr_warn("Unable to map EFI config table array.\n"); + retval = -ENOMEM; + goto out; + } + retval = efi_config_parse_tables(config_tables, systab->nr_tables, + efi_arch_tables); + + early_memunmap(config_tables, table_size); +out: + early_memunmap(systab, sizeof(efi_system_table_t)); + return retval; +} + +/* + * Return true for regions that can be used as System RAM. + */ +static __init int is_usable_memory(efi_memory_desc_t *md) +{ + switch (md->type) { + case EFI_LOADER_CODE: + case EFI_LOADER_DATA: + case EFI_ACPI_RECLAIM_MEMORY: + case EFI_BOOT_SERVICES_CODE: + case EFI_BOOT_SERVICES_DATA: + case EFI_CONVENTIONAL_MEMORY: + case EFI_PERSISTENT_MEMORY: + /* + * According to the spec, these regions are no longer reserved + * after calling ExitBootServices(). However, we can only use + * them as System RAM if they can be mapped writeback cacheable. + */ + return (md->attribute & EFI_MEMORY_WB); + default: + break; + } + return false; +} + +static __init void reserve_regions(void) +{ + efi_memory_desc_t *md; + u64 paddr, npages, size; + + if (efi_enabled(EFI_DBG)) + pr_info("Processing EFI memory map:\n"); + + /* + * Discard memblocks discovered so far except for KHO scratch + * regions. Most memblocks at this point originate from memory nodes + * in the DT and UEFI uses its own memory map instead. However, if + * KHO is enabled, scratch regions, which are good known memory + * must be preserved. + */ + memblock_dump_all(); + + if (is_kho_boot()) { + struct memblock_region *r; + + /* Remove all non-KHO regions */ + for_each_mem_region(r) { + if (!memblock_is_kho_scratch(r)) { + memblock_remove(r->base, r->size); + r--; + } + } + } else { + /* + * KHO is disabled. Discard memblocks discovered so far: + * if there are any at this point, they originate from memory + * nodes in the DT, and UEFI uses its own memory map instead. + */ + memblock_remove(0, PHYS_ADDR_MAX); + } + + for_each_efi_memory_desc(md) { + paddr = md->phys_addr; + npages = md->num_pages; + + if (efi_enabled(EFI_DBG)) { + char buf[64]; + + pr_info(" 0x%012llx-0x%012llx %s\n", + paddr, paddr + (npages << EFI_PAGE_SHIFT) - 1, + efi_md_typeattr_format(buf, sizeof(buf), md)); + } + + memrange_efi_to_native(&paddr, &npages); + size = npages << PAGE_SHIFT; + + if (is_memory(md)) { + /* + * Special purpose memory is 'soft reserved', which + * means it is set aside initially. Don't add a memblock + * for it now so that it can be hotplugged back in or + * be assigned to the dax driver after boot. + */ + if (efi_soft_reserve_enabled() && + (md->attribute & EFI_MEMORY_SP)) + continue; + + early_init_dt_add_memory_arch(paddr, size); + + if (!is_usable_memory(md)) + memblock_mark_nomap(paddr, size); + + /* keep ACPI reclaim memory intact for kexec etc. */ + if (md->type == EFI_ACPI_RECLAIM_MEMORY) + memblock_reserve(paddr, size); + } + } +} + +void __init efi_init(void) +{ + struct efi_memory_map_data data; + u64 efi_system_table; + + /* Grab UEFI information placed in FDT by stub */ + efi_system_table = efi_get_fdt_params(&data); + if (!efi_system_table) + return; + + if (efi_memmap_init_early(&data) < 0) { + /* + * If we are booting via UEFI, the UEFI memory map is the only + * description of memory we have, so there is little point in + * proceeding if we cannot access it. + */ + panic("Unable to map EFI memory map.\n"); + } + + WARN(efi.memmap.desc_version != 1, + "Unexpected EFI_MEMORY_DESCRIPTOR version %ld", + efi.memmap.desc_version); + + if (uefi_init(efi_system_table) < 0) { + efi_memmap_unmap(); + return; + } + + reserve_regions(); + /* + * For memblock manipulation, the cap should come after the memblock_add(). + * And now, memblock is fully populated, it is time to do capping. + */ + early_init_dt_check_for_usable_mem_range(); + efi_find_mirror(); + efi_esrt_init(); + efi_mokvar_table_init(); + + memblock_reserve(data.phys_map & PAGE_MASK, + PAGE_ALIGN(data.size + (data.phys_map & ~PAGE_MASK))); + + if (IS_ENABLED(CONFIG_X86) || + IS_ENABLED(CONFIG_SYSFB) || + IS_ENABLED(CONFIG_EFI_EARLYCON)) + init_primary_display(); +} diff --git a/drivers/firmware/efi/efi-pstore.c b/drivers/firmware/efi/efi-pstore.c new file mode 100644 index 000000000..a5db3534f --- /dev/null +++ b/drivers/firmware/efi/efi-pstore.c @@ -0,0 +1,306 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include +#include +#include +#include +#include + +MODULE_IMPORT_NS("EFIVAR"); + +#define DUMP_NAME_LEN 66 + +static unsigned int record_size = 1024; +module_param(record_size, uint, 0444); +MODULE_PARM_DESC(record_size, "size of each pstore UEFI var (in bytes, min/default=1024)"); + +#define PSTORE_EFI_ATTRIBUTES \ + (EFI_VARIABLE_NON_VOLATILE | \ + EFI_VARIABLE_BOOTSERVICE_ACCESS | \ + EFI_VARIABLE_RUNTIME_ACCESS) + +static bool pstore_disable = IS_ENABLED(CONFIG_EFI_VARS_PSTORE_DEFAULT_DISABLE); + +static int efivars_pstore_init(void); +static void efivars_pstore_exit(void); + +static int efi_pstore_disable_set(const char *val, const struct kernel_param *kp) +{ + int err; + bool old_pstore_disable = pstore_disable; + + err = param_set_bool(val, kp); + if (err) + return err; + + if (old_pstore_disable != pstore_disable) { + if (pstore_disable) + efivars_pstore_exit(); + else + efivars_pstore_init(); + } + + return 0; +} + +static const struct kernel_param_ops pstore_disable_ops = { + .set = efi_pstore_disable_set, + .get = param_get_bool, +}; + +module_param_cb(pstore_disable, &pstore_disable_ops, &pstore_disable, 0644); +__MODULE_PARM_TYPE(pstore_disable, "bool"); + +static int efi_pstore_open(struct pstore_info *psi) +{ + int err; + + err = efivar_lock(); + if (err) + return err; + + psi->data = kzalloc(record_size, GFP_KERNEL); + if (!psi->data) { + efivar_unlock(); + return -ENOMEM; + } + + return 0; +} + +static int efi_pstore_close(struct pstore_info *psi) +{ + efivar_unlock(); + kfree(psi->data); + return 0; +} + +static inline u64 generic_id(u64 timestamp, unsigned int part, int count) +{ + return (timestamp * 100 + part) * 1000 + count; +} + +static int efi_pstore_read_func(struct pstore_record *record, + efi_char16_t *varname) +{ + unsigned long wlen, size = record_size; + char name[DUMP_NAME_LEN], data_type; + efi_status_t status; + int cnt; + unsigned int part; + u64 time; + + ucs2_as_utf8(name, varname, DUMP_NAME_LEN); + + if (sscanf(name, "dump-type%u-%u-%d-%llu-%c", + &record->type, &part, &cnt, &time, &data_type) == 5) { + record->id = generic_id(time, part, cnt); + record->part = part; + record->count = cnt; + record->time.tv_sec = time; + record->time.tv_nsec = 0; + if (data_type == 'C') + record->compressed = true; + else + record->compressed = false; + record->ecc_notice_size = 0; + } else if (sscanf(name, "dump-type%u-%u-%d-%llu", + &record->type, &part, &cnt, &time) == 4) { + record->id = generic_id(time, part, cnt); + record->part = part; + record->count = cnt; + record->time.tv_sec = time; + record->time.tv_nsec = 0; + record->compressed = false; + record->ecc_notice_size = 0; + } else if (sscanf(name, "dump-type%u-%u-%llu", + &record->type, &part, &time) == 3) { + /* + * Check if an old format, + * which doesn't support holding + * multiple logs, remains. + */ + record->id = generic_id(time, part, 0); + record->part = part; + record->count = 0; + record->time.tv_sec = time; + record->time.tv_nsec = 0; + record->compressed = false; + record->ecc_notice_size = 0; + } else + return 0; + + record->buf = kmalloc(size, GFP_KERNEL); + if (!record->buf) + return -ENOMEM; + + status = efivar_get_variable(varname, &LINUX_EFI_CRASH_GUID, NULL, + &size, record->buf); + if (status != EFI_SUCCESS) { + kfree(record->buf); + return efi_status_to_err(status); + } + + /* + * Store the name of the variable in the pstore_record priv field, so + * we can reuse it later if we need to delete the EFI variable from the + * variable store. + */ + wlen = (ucs2_strnlen(varname, DUMP_NAME_LEN) + 1) * sizeof(efi_char16_t); + record->priv = kmemdup(varname, wlen, GFP_KERNEL); + if (!record->priv) { + kfree(record->buf); + return -ENOMEM; + } + + return size; +} + +static ssize_t efi_pstore_read(struct pstore_record *record) +{ + efi_char16_t *varname = record->psi->data; + efi_guid_t guid = LINUX_EFI_CRASH_GUID; + unsigned long varname_size; + efi_status_t status; + + for (;;) { + /* + * A small set of old UEFI implementations reject sizes + * above a certain threshold, the lowest seen in the wild + * is 512. + * + * TODO: Commonize with the iteration implementation in + * fs/efivarfs to keep all the quirks in one place. + */ + varname_size = 512; + + /* + * If this is the first read() call in the pstore enumeration, + * varname will be the empty string, and the GetNextVariable() + * runtime service call will return the first EFI variable in + * its own enumeration order, ignoring the guid argument. + * + * Subsequent calls to GetNextVariable() must pass the name and + * guid values returned by the previous call, which is why we + * store varname in record->psi->data. Given that we only + * enumerate variables with the efi-pstore GUID, there is no + * need to record the guid return value. + */ + status = efivar_get_next_variable(&varname_size, varname, &guid); + if (status == EFI_NOT_FOUND) + return 0; + + if (status != EFI_SUCCESS) + return efi_status_to_err(status); + + /* skip variables that don't concern us */ + if (efi_guidcmp(guid, LINUX_EFI_CRASH_GUID)) + continue; + + return efi_pstore_read_func(record, varname); + } +} + +static int efi_pstore_write(struct pstore_record *record) +{ + char name[DUMP_NAME_LEN]; + efi_char16_t efi_name[DUMP_NAME_LEN]; + efi_status_t status; + int i; + + record->id = generic_id(record->time.tv_sec, record->part, + record->count); + + /* Since we copy the entire length of name, make sure it is wiped. */ + memset(name, 0, sizeof(name)); + + snprintf(name, sizeof(name), "dump-type%u-%u-%d-%lld-%c", + record->type, record->part, record->count, + (long long)record->time.tv_sec, + record->compressed ? 'C' : 'D'); + + for (i = 0; i < DUMP_NAME_LEN; i++) + efi_name[i] = name[i]; + + if (efivar_trylock()) + return -EBUSY; + status = efivar_set_variable_locked(efi_name, &LINUX_EFI_CRASH_GUID, + PSTORE_EFI_ATTRIBUTES, + record->size, record->psi->buf, + true); + efivar_unlock(); + return efi_status_to_err(status); +}; + +static int efi_pstore_erase(struct pstore_record *record) +{ + efi_status_t status; + + status = efivar_set_variable(record->priv, &LINUX_EFI_CRASH_GUID, + PSTORE_EFI_ATTRIBUTES, 0, NULL); + + if (status != EFI_SUCCESS && status != EFI_NOT_FOUND) + return efi_status_to_err(status); + return 0; +} + +static struct pstore_info efi_pstore_info = { + .owner = THIS_MODULE, + .name = KBUILD_MODNAME, + .flags = PSTORE_FLAGS_DMESG, + .open = efi_pstore_open, + .close = efi_pstore_close, + .read = efi_pstore_read, + .write = efi_pstore_write, + .erase = efi_pstore_erase, +}; + +static int efivars_pstore_init(void) +{ + if (!efivar_supports_writes()) + return 0; + + if (pstore_disable) + return 0; + + /* + * Notice that 1024 is the minimum here to prevent issues with + * decompression algorithms that were spotted during tests; + * even in the case of not using compression, smaller values would + * just pollute more the pstore FS with many small collected files. + */ + if (record_size < 1024) + record_size = 1024; + + efi_pstore_info.buf = kmalloc(record_size, GFP_KERNEL); + if (!efi_pstore_info.buf) + return -ENOMEM; + + efi_pstore_info.bufsize = record_size; + + if (pstore_register(&efi_pstore_info)) { + kfree(efi_pstore_info.buf); + efi_pstore_info.buf = NULL; + efi_pstore_info.bufsize = 0; + } + + return 0; +} + +static void efivars_pstore_exit(void) +{ + if (!efi_pstore_info.bufsize) + return; + + pstore_unregister(&efi_pstore_info); + kfree(efi_pstore_info.buf); + efi_pstore_info.buf = NULL; + efi_pstore_info.bufsize = 0; +} + +module_init(efivars_pstore_init); +module_exit(efivars_pstore_exit); + +MODULE_DESCRIPTION("EFI variable backend for pstore"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:efivars"); diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c new file mode 100644 index 000000000..0327a39d3 --- /dev/null +++ b/drivers/firmware/efi/efi.c @@ -0,0 +1,1216 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * efi.c - EFI subsystem + * + * Copyright (C) 2001,2003,2004 Dell + * Copyright (C) 2004 Intel Corporation + * Copyright (C) 2013 Tom Gundersen + * + * This code registers /sys/firmware/efi{,/efivars} when EFI is supported, + * allowing the efivarfs to be mounted or the efivars module to be loaded. + * The existance of /sys/firmware/efi may also be used by userspace to + * determine that the system supports EFI. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +struct efi __read_mostly efi = { + .runtime_supported_mask = EFI_RT_SUPPORTED_ALL, + .acpi = EFI_INVALID_TABLE_ADDR, + .acpi20 = EFI_INVALID_TABLE_ADDR, + .smbios = EFI_INVALID_TABLE_ADDR, + .smbios3 = EFI_INVALID_TABLE_ADDR, + .esrt = EFI_INVALID_TABLE_ADDR, + .tpm_log = EFI_INVALID_TABLE_ADDR, + .tpm_final_log = EFI_INVALID_TABLE_ADDR, + .ovmf_debug_log = EFI_INVALID_TABLE_ADDR, +#ifdef CONFIG_LOAD_UEFI_KEYS + .mokvar_table = EFI_INVALID_TABLE_ADDR, +#endif +#ifdef CONFIG_EFI_COCO_SECRET + .coco_secret = EFI_INVALID_TABLE_ADDR, +#endif +#ifdef CONFIG_UNACCEPTED_MEMORY + .unaccepted = EFI_INVALID_TABLE_ADDR, +#endif +}; +EXPORT_SYMBOL(efi); + +unsigned long __ro_after_init efi_rng_seed = EFI_INVALID_TABLE_ADDR; +static unsigned long __initdata mem_reserve = EFI_INVALID_TABLE_ADDR; +static unsigned long __initdata rt_prop = EFI_INVALID_TABLE_ADDR; +static unsigned long __initdata initrd = EFI_INVALID_TABLE_ADDR; + +extern unsigned long primary_display_table; + +struct mm_struct efi_mm = { + .mm_mt = MTREE_INIT_EXT(mm_mt, MM_MT_FLAGS, efi_mm.mmap_lock), + .mm_users = ATOMIC_INIT(2), + .mm_count = ATOMIC_INIT(1), + .write_protect_seq = SEQCNT_ZERO(efi_mm.write_protect_seq), + MMAP_LOCK_INITIALIZER(efi_mm) + .page_table_lock = __SPIN_LOCK_UNLOCKED(efi_mm.page_table_lock), + .mmlist = LIST_HEAD_INIT(efi_mm.mmlist), +#ifdef CONFIG_SCHED_MM_CID + .mm_cid.lock = __RAW_SPIN_LOCK_UNLOCKED(efi_mm.mm_cid.lock), +#endif + .flexible_array = MM_STRUCT_FLEXIBLE_ARRAY_INIT, +}; + +struct workqueue_struct *efi_rts_wq; + +static bool disable_runtime = IS_ENABLED(CONFIG_EFI_DISABLE_RUNTIME); +static int __init setup_noefi(char *arg) +{ + disable_runtime = true; + return 0; +} +early_param("noefi", setup_noefi); + +bool efi_runtime_disabled(void) +{ + return disable_runtime; +} + +bool __pure __efi_soft_reserve_enabled(void) +{ + return !efi_enabled(EFI_MEM_NO_SOFT_RESERVE); +} + +static int __init parse_efi_cmdline(char *str) +{ + if (!str) { + pr_warn("need at least one option\n"); + return -EINVAL; + } + + if (parse_option_str(str, "debug")) + set_bit(EFI_DBG, &efi.flags); + + if (parse_option_str(str, "noruntime")) + disable_runtime = true; + + if (parse_option_str(str, "runtime")) + disable_runtime = false; + + if (parse_option_str(str, "nosoftreserve")) + set_bit(EFI_MEM_NO_SOFT_RESERVE, &efi.flags); + + return 0; +} +early_param("efi", parse_efi_cmdline); + +struct kobject *efi_kobj; + +/* + * Let's not leave out systab information that snuck into + * the efivars driver + * Note, do not add more fields in systab sysfs file as it breaks sysfs + * one value per file rule! + */ +static ssize_t systab_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + char *str = buf; + + if (!kobj || !buf) + return -EINVAL; + + if (efi.acpi20 != EFI_INVALID_TABLE_ADDR) + str += sprintf(str, "ACPI20=0x%lx\n", efi.acpi20); + if (efi.acpi != EFI_INVALID_TABLE_ADDR) + str += sprintf(str, "ACPI=0x%lx\n", efi.acpi); + /* + * If both SMBIOS and SMBIOS3 entry points are implemented, the + * SMBIOS3 entry point shall be preferred, so we list it first to + * let applications stop parsing after the first match. + */ + if (efi.smbios3 != EFI_INVALID_TABLE_ADDR) + str += sprintf(str, "SMBIOS3=0x%lx\n", efi.smbios3); + if (efi.smbios != EFI_INVALID_TABLE_ADDR) + str += sprintf(str, "SMBIOS=0x%lx\n", efi.smbios); + + return str - buf; +} + +static struct kobj_attribute efi_attr_systab = __ATTR_RO_MODE(systab, 0400); + +static ssize_t fw_platform_size_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", efi_enabled(EFI_64BIT) ? 64 : 32); +} + +extern __weak struct kobj_attribute efi_attr_fw_vendor; +extern __weak struct kobj_attribute efi_attr_runtime; +extern __weak struct kobj_attribute efi_attr_config_table; +static struct kobj_attribute efi_attr_fw_platform_size = + __ATTR_RO(fw_platform_size); + +static struct attribute *efi_subsys_attrs[] = { + &efi_attr_systab.attr, + &efi_attr_fw_platform_size.attr, + &efi_attr_fw_vendor.attr, + &efi_attr_runtime.attr, + &efi_attr_config_table.attr, + NULL, +}; + +umode_t __weak efi_attr_is_visible(struct kobject *kobj, struct attribute *attr, + int n) +{ + return attr->mode; +} + +static const struct attribute_group efi_subsys_attr_group = { + .attrs = efi_subsys_attrs, + .is_visible = efi_attr_is_visible, +}; + +struct blocking_notifier_head efivar_ops_nh; +EXPORT_SYMBOL_GPL(efivar_ops_nh); + +static struct efivars generic_efivars; +static struct efivar_operations generic_ops; + +static bool generic_ops_supported(void) +{ + unsigned long name_size; + efi_status_t status; + efi_char16_t name; + efi_guid_t guid; + + name_size = sizeof(name); + + if (!efi.get_next_variable) + return false; + status = efi.get_next_variable(&name_size, &name, &guid); + if (status == EFI_UNSUPPORTED) + return false; + + return true; +} + +static int generic_ops_register(void) +{ + if (!generic_ops_supported()) + return 0; + + generic_ops.get_variable = efi.get_variable; + generic_ops.get_next_variable = efi.get_next_variable; + generic_ops.query_variable_store = efi_query_variable_store; + generic_ops.query_variable_info = efi.query_variable_info; + + if (efi_rt_services_supported(EFI_RT_SUPPORTED_SET_VARIABLE)) { + generic_ops.set_variable = efi.set_variable; + generic_ops.set_variable_nonblocking = efi.set_variable_nonblocking; + } + return efivars_register(&generic_efivars, &generic_ops); +} + +static void generic_ops_unregister(void) +{ + if (!generic_ops.get_variable) + return; + + efivars_unregister(&generic_efivars); +} + +void efivars_generic_ops_register(void) +{ + generic_ops_register(); +} +EXPORT_SYMBOL_GPL(efivars_generic_ops_register); + +void efivars_generic_ops_unregister(void) +{ + generic_ops_unregister(); +} +EXPORT_SYMBOL_GPL(efivars_generic_ops_unregister); + +#ifdef CONFIG_EFI_CUSTOM_SSDT_OVERLAYS +#define EFIVAR_SSDT_NAME_MAX 16UL +static char efivar_ssdt[EFIVAR_SSDT_NAME_MAX] __initdata; +static int __init efivar_ssdt_setup(char *str) +{ + int ret = security_locked_down(LOCKDOWN_ACPI_TABLES); + + if (ret) + return ret; + + if (strlen(str) < sizeof(efivar_ssdt)) + memcpy(efivar_ssdt, str, strlen(str)); + else + pr_warn("efivar_ssdt: name too long: %s\n", str); + return 1; +} +__setup("efivar_ssdt=", efivar_ssdt_setup); + +static __init int efivar_ssdt_load(void) +{ + unsigned long name_size = 256; + efi_char16_t *name = NULL; + efi_status_t status; + efi_guid_t guid; + int ret = 0; + + if (!efivar_ssdt[0]) + return 0; + + name = kzalloc(name_size, GFP_KERNEL); + if (!name) + return -ENOMEM; + + for (;;) { + char utf8_name[EFIVAR_SSDT_NAME_MAX]; + unsigned long data_size = 0; + void *data; + int limit; + + status = efi.get_next_variable(&name_size, name, &guid); + if (status == EFI_NOT_FOUND) { + break; + } else if (status == EFI_BUFFER_TOO_SMALL) { + efi_char16_t *name_tmp = + krealloc(name, name_size, GFP_KERNEL); + if (!name_tmp) { + ret = -ENOMEM; + goto out; + } + name = name_tmp; + continue; + } + + limit = min(EFIVAR_SSDT_NAME_MAX, name_size); + ucs2_as_utf8(utf8_name, name, limit - 1); + if (strncmp(utf8_name, efivar_ssdt, limit) != 0) + continue; + + pr_info("loading SSDT from variable %s-%pUl\n", efivar_ssdt, &guid); + + status = efi.get_variable(name, &guid, NULL, &data_size, NULL); + if (status != EFI_BUFFER_TOO_SMALL || !data_size) { + ret = -EIO; + goto out; + } + + data = kmalloc(data_size, GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto out; + } + + status = efi.get_variable(name, &guid, NULL, &data_size, data); + if (status == EFI_SUCCESS) { + acpi_status acpi_ret = acpi_load_table(data, NULL); + if (ACPI_FAILURE(acpi_ret)) { + pr_err("efivar_ssdt: failed to load table: %u\n", + acpi_ret); + } else { + /* + * The @data will be in use by ACPI engine, + * do not free it! + */ + continue; + } + } else { + pr_err("efivar_ssdt: failed to get var data: 0x%lx\n", status); + } + kfree(data); + } +out: + kfree(name); + return ret; +} +#else +static inline int efivar_ssdt_load(void) { return 0; } +#endif + +#ifdef CONFIG_DEBUG_FS + +#define EFI_DEBUGFS_MAX_BLOBS 32 + +static struct debugfs_blob_wrapper debugfs_blob[EFI_DEBUGFS_MAX_BLOBS]; + +static void __init efi_debugfs_init(void) +{ + struct dentry *efi_debugfs; + efi_memory_desc_t *md; + char name[32]; + int type_count[EFI_BOOT_SERVICES_DATA + 1] = {}; + int i = 0; + + efi_debugfs = debugfs_create_dir("efi", NULL); + if (IS_ERR(efi_debugfs)) + return; + + for_each_efi_memory_desc(md) { + switch (md->type) { + case EFI_BOOT_SERVICES_CODE: + snprintf(name, sizeof(name), "boot_services_code%d", + type_count[md->type]++); + break; + case EFI_BOOT_SERVICES_DATA: + snprintf(name, sizeof(name), "boot_services_data%d", + type_count[md->type]++); + break; + default: + continue; + } + + if (i >= EFI_DEBUGFS_MAX_BLOBS) { + pr_warn("More then %d EFI boot service segments, only showing first %d in debugfs\n", + EFI_DEBUGFS_MAX_BLOBS, EFI_DEBUGFS_MAX_BLOBS); + break; + } + + debugfs_blob[i].size = md->num_pages << EFI_PAGE_SHIFT; + debugfs_blob[i].data = memremap(md->phys_addr, + debugfs_blob[i].size, + MEMREMAP_WB); + if (!debugfs_blob[i].data) + continue; + + debugfs_create_blob(name, 0400, efi_debugfs, &debugfs_blob[i]); + i++; + } +} +#else +static inline void efi_debugfs_init(void) {} +#endif + +static int __init efipostcore_init(void) +{ + if (!efi_enabled(EFI_RUNTIME_SERVICES)) + efi.runtime_supported_mask = 0; + + if (efi.runtime_supported_mask) { + /* + * Since we process only one efi_runtime_service() at a time, an + * ordered workqueue (which creates only one execution context) + * should suffice for all our needs. + */ + efi_rts_wq = alloc_ordered_workqueue("efi_runtime", WQ_SYSFS); + if (!efi_rts_wq) { + pr_err("Creating efi_rts_wq failed, EFI runtime services disabled.\n"); + clear_bit(EFI_RUNTIME_SERVICES, &efi.flags); + efi.runtime_supported_mask = 0; + } + } + return 0; +} +postcore_initcall(efipostcore_init); + +/* + * We register the efi subsystem with the firmware subsystem and the + * efivars subsystem with the efi subsystem, if the system was booted with + * EFI. + */ +static int __init efisubsys_init(void) +{ + int error; + + if (!efi_enabled(EFI_BOOT)) + return 0; + + if (efi_rt_services_supported(EFI_RT_SUPPORTED_TIME_SERVICES)) + platform_device_register_simple("rtc-efi", 0, NULL, 0); + + /* We register the efi directory at /sys/firmware/efi */ + efi_kobj = kobject_create_and_add("efi", firmware_kobj); + if (!efi_kobj) { + pr_err("efi: Firmware registration failed.\n"); + error = -ENOMEM; + goto err_destroy_wq; + } + + if (efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE | + EFI_RT_SUPPORTED_GET_NEXT_VARIABLE_NAME)) { + error = generic_ops_register(); + if (error) + goto err_put; + error = efivar_ssdt_load(); + if (error) + pr_err("efi: failed to load SSDT, error %d.\n", error); + platform_device_register_simple("efivars", 0, NULL, 0); + } + + BLOCKING_INIT_NOTIFIER_HEAD(&efivar_ops_nh); + + error = sysfs_create_group(efi_kobj, &efi_subsys_attr_group); + if (error) { + pr_err("efi: Sysfs attribute export failed with error %d.\n", + error); + goto err_unregister; + } + + /* and the standard mountpoint for efivarfs */ + error = sysfs_create_mount_point(efi_kobj, "efivars"); + if (error) { + pr_err("efivars: Subsystem registration failed.\n"); + goto err_remove_group; + } + + if (efi_enabled(EFI_DBG) && efi_enabled(EFI_PRESERVE_BS_REGIONS)) + efi_debugfs_init(); + +#ifdef CONFIG_EFI_COCO_SECRET + if (efi.coco_secret != EFI_INVALID_TABLE_ADDR) + platform_device_register_simple("efi_secret", 0, NULL, 0); +#endif + + if (IS_ENABLED(CONFIG_OVMF_DEBUG_LOG) && + efi.ovmf_debug_log != EFI_INVALID_TABLE_ADDR) + ovmf_log_probe(efi.ovmf_debug_log); + + return 0; + +err_remove_group: + sysfs_remove_group(efi_kobj, &efi_subsys_attr_group); +err_unregister: + if (efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE | + EFI_RT_SUPPORTED_GET_NEXT_VARIABLE_NAME)) + generic_ops_unregister(); +err_put: + kobject_put(efi_kobj); + efi_kobj = NULL; +err_destroy_wq: + if (efi_rts_wq) + destroy_workqueue(efi_rts_wq); + + return error; +} + +subsys_initcall(efisubsys_init); + +void __init efi_find_mirror(void) +{ + efi_memory_desc_t *md; + u64 mirror_size = 0, total_size = 0; + + if (!efi_enabled(EFI_MEMMAP)) + return; + + for_each_efi_memory_desc(md) { + unsigned long long start = md->phys_addr; + unsigned long long size = md->num_pages << EFI_PAGE_SHIFT; + + total_size += size; + if (md->attribute & EFI_MEMORY_MORE_RELIABLE) { + memblock_mark_mirror(start, size); + mirror_size += size; + } + } + if (mirror_size) + pr_info("Memory: %lldM/%lldM mirrored memory\n", + mirror_size>>20, total_size>>20); +} + +/* + * Find the efi memory descriptor for a given physical address. Given a + * physical address, determine if it exists within an EFI Memory Map entry, + * and if so, populate the supplied memory descriptor with the appropriate + * data. + */ +int __efi_mem_desc_lookup(u64 phys_addr, efi_memory_desc_t *out_md) +{ + efi_memory_desc_t *md; + + if (!efi_enabled(EFI_MEMMAP)) { + pr_err_once("EFI_MEMMAP is not enabled.\n"); + return -EINVAL; + } + + if (!out_md) { + pr_err_once("out_md is null.\n"); + return -EINVAL; + } + + for_each_efi_memory_desc(md) { + u64 size; + u64 end; + + /* skip bogus entries (including empty ones) */ + if ((md->phys_addr & (EFI_PAGE_SIZE - 1)) || + (md->num_pages <= 0) || + (md->num_pages > (U64_MAX - md->phys_addr) >> EFI_PAGE_SHIFT)) + continue; + + size = md->num_pages << EFI_PAGE_SHIFT; + end = md->phys_addr + size; + if (phys_addr >= md->phys_addr && phys_addr < end) { + memcpy(out_md, md, sizeof(*out_md)); + return 0; + } + } + return -ENOENT; +} + +extern int efi_mem_desc_lookup(u64 phys_addr, efi_memory_desc_t *out_md) + __weak __alias(__efi_mem_desc_lookup); +EXPORT_SYMBOL_GPL(efi_mem_desc_lookup); + +/* + * Calculate the highest address of an efi memory descriptor. + */ +u64 __init efi_mem_desc_end(efi_memory_desc_t *md) +{ + u64 size = md->num_pages << EFI_PAGE_SHIFT; + u64 end = md->phys_addr + size; + return end; +} + +void __init __weak efi_arch_mem_reserve(phys_addr_t addr, u64 size) {} + +/** + * efi_mem_reserve - Reserve an EFI memory region + * @addr: Physical address to reserve + * @size: Size of reservation + * + * Mark a region as reserved from general kernel allocation and + * prevent it being released by efi_free_boot_services(). + * + * This function should be called drivers once they've parsed EFI + * configuration tables to figure out where their data lives, e.g. + * efi_esrt_init(). + */ +void __init efi_mem_reserve(phys_addr_t addr, u64 size) +{ + /* efi_mem_reserve() does not work under Xen */ + if (WARN_ON_ONCE(efi_enabled(EFI_PARAVIRT))) + return; + + if (!memblock_is_region_reserved(addr, size)) + memblock_reserve_kern(addr, size); + else + memblock_reserved_mark_kern(addr, size); + + /* + * Some architectures (x86) reserve all boot services ranges + * until efi_free_boot_services() because of buggy firmware + * implementations. This means the above memblock_reserve() is + * superfluous on x86 and instead what it needs to do is + * ensure the @start, @size is not freed. + */ + efi_arch_mem_reserve(addr, size); +} + +static const efi_config_table_type_t common_tables[] __initconst = { + {ACPI_20_TABLE_GUID, &efi.acpi20, "ACPI 2.0" }, + {ACPI_TABLE_GUID, &efi.acpi, "ACPI" }, + {SMBIOS_TABLE_GUID, &efi.smbios, "SMBIOS" }, + {SMBIOS3_TABLE_GUID, &efi.smbios3, "SMBIOS 3.0" }, + {EFI_SYSTEM_RESOURCE_TABLE_GUID, &efi.esrt, "ESRT" }, + {EFI_MEMORY_ATTRIBUTES_TABLE_GUID, &efi_mem_attr_table, "MEMATTR" }, + {LINUX_EFI_RANDOM_SEED_TABLE_GUID, &efi_rng_seed, "RNG" }, + {LINUX_EFI_TPM_EVENT_LOG_GUID, &efi.tpm_log, "TPMEventLog" }, + {EFI_TCG2_FINAL_EVENTS_TABLE_GUID, &efi.tpm_final_log, "TPMFinalLog" }, + {EFI_CC_FINAL_EVENTS_TABLE_GUID, &efi.tpm_final_log, "CCFinalLog" }, + {LINUX_EFI_MEMRESERVE_TABLE_GUID, &mem_reserve, "MEMRESERVE" }, + {LINUX_EFI_INITRD_MEDIA_GUID, &initrd, "INITRD" }, + {EFI_RT_PROPERTIES_TABLE_GUID, &rt_prop, "RTPROP" }, +#ifdef CONFIG_OVMF_DEBUG_LOG + {OVMF_MEMORY_LOG_TABLE_GUID, &efi.ovmf_debug_log, "OvmfDebugLog" }, +#endif +#ifdef CONFIG_EFI_RCI2_TABLE + {DELLEMC_EFI_RCI2_TABLE_GUID, &rci2_table_phys }, +#endif +#ifdef CONFIG_LOAD_UEFI_KEYS + {LINUX_EFI_MOK_VARIABLE_TABLE_GUID, &efi.mokvar_table, "MOKvar" }, +#endif +#ifdef CONFIG_EFI_COCO_SECRET + {LINUX_EFI_COCO_SECRET_AREA_GUID, &efi.coco_secret, "CocoSecret" }, +#endif +#ifdef CONFIG_UNACCEPTED_MEMORY + {LINUX_EFI_UNACCEPTED_MEM_TABLE_GUID, &efi.unaccepted, "Unaccepted" }, +#endif +#ifdef CONFIG_EFI_GENERIC_STUB + {LINUX_EFI_PRIMARY_DISPLAY_TABLE_GUID, &primary_display_table }, +#endif + {}, +}; + +static __init int match_config_table(const efi_guid_t *guid, + unsigned long table, + const efi_config_table_type_t *table_types) +{ + int i; + + for (i = 0; efi_guidcmp(table_types[i].guid, NULL_GUID); i++) { + if (efi_guidcmp(*guid, table_types[i].guid)) + continue; + + if (!efi_config_table_is_usable(guid, table)) { + if (table_types[i].name[0]) + pr_cont("(%s=0x%lx unusable) ", + table_types[i].name, table); + return 1; + } + + *(table_types[i].ptr) = table; + if (table_types[i].name[0]) + pr_cont("%s=0x%lx ", table_types[i].name, table); + return 1; + } + + return 0; +} + +/** + * reserve_unaccepted - Map and reserve unaccepted configuration table + * @unaccepted: Pointer to unaccepted memory table + * + * memblock_add() makes sure that the table is mapped in direct mapping. During + * normal boot it happens automatically because the table is allocated from + * usable memory. But during crashkernel boot only memory specifically reserved + * for crash scenario is mapped. memblock_add() forces the table to be mapped + * in crashkernel case. + * + * Align the range to the nearest page borders. Ranges smaller than page size + * are not going to be mapped. + * + * memblock_reserve() makes sure that future allocations will not touch the + * table. + */ + +static __init void reserve_unaccepted(struct efi_unaccepted_memory *unaccepted) +{ + phys_addr_t start, end; + + start = PAGE_ALIGN_DOWN(efi.unaccepted); + end = PAGE_ALIGN(efi.unaccepted + sizeof(*unaccepted) + unaccepted->size); + + memblock_add(start, end - start); + memblock_reserve(start, end - start); +} + +int __init efi_config_parse_tables(const efi_config_table_t *config_tables, + int count, + const efi_config_table_type_t *arch_tables) +{ + const efi_config_table_64_t *tbl64 = (void *)config_tables; + const efi_config_table_32_t *tbl32 = (void *)config_tables; + const efi_guid_t *guid; + unsigned long table; + int i; + + pr_info(""); + for (i = 0; i < count; i++) { + if (!IS_ENABLED(CONFIG_X86)) { + guid = &config_tables[i].guid; + table = (unsigned long)config_tables[i].table; + } else if (efi_enabled(EFI_64BIT)) { + guid = &tbl64[i].guid; + table = tbl64[i].table; + + if (IS_ENABLED(CONFIG_X86_32) && + tbl64[i].table > U32_MAX) { + pr_cont("\n"); + pr_err("Table located above 4GB, disabling EFI.\n"); + return -EINVAL; + } + } else { + guid = &tbl32[i].guid; + table = tbl32[i].table; + } + + if (!match_config_table(guid, table, common_tables) && arch_tables) + match_config_table(guid, table, arch_tables); + } + pr_cont("\n"); + set_bit(EFI_CONFIG_TABLES, &efi.flags); + + if (efi_rng_seed != EFI_INVALID_TABLE_ADDR) { + struct linux_efi_random_seed *seed; + u32 size = 0; + + seed = early_memremap(efi_rng_seed, sizeof(*seed)); + if (seed != NULL) { + size = min_t(u32, seed->size, SZ_1K); // sanity check + early_memunmap(seed, sizeof(*seed)); + } else { + pr_err("Could not map UEFI random seed!\n"); + } + if (size > 0) { + seed = early_memremap(efi_rng_seed, + sizeof(*seed) + size); + if (seed != NULL) { + add_bootloader_randomness(seed->bits, size); + memzero_explicit(seed->bits, size); + early_memunmap(seed, sizeof(*seed) + size); + } else { + pr_err("Could not map UEFI random seed!\n"); + } + } + } + + if (!IS_ENABLED(CONFIG_X86_32) && efi_enabled(EFI_MEMMAP)) + efi_memattr_init(); + + efi_tpm_eventlog_init(); + + if (mem_reserve != EFI_INVALID_TABLE_ADDR) { + unsigned long prsv = mem_reserve; + + while (prsv) { + struct linux_efi_memreserve *rsv; + u8 *p; + + /* + * Just map a full page: that is what we will get + * anyway, and it permits us to map the entire entry + * before knowing its size. + */ + p = early_memremap(ALIGN_DOWN(prsv, PAGE_SIZE), + PAGE_SIZE); + if (p == NULL) { + pr_err("Could not map UEFI memreserve entry!\n"); + return -ENOMEM; + } + + rsv = (void *)(p + prsv % PAGE_SIZE); + + /* reserve the entry itself */ + memblock_reserve(prsv, + struct_size(rsv, entry, rsv->size)); + + for (i = 0; i < atomic_read(&rsv->count); i++) { + memblock_reserve(rsv->entry[i].base, + rsv->entry[i].size); + } + + prsv = rsv->next; + early_memunmap(p, PAGE_SIZE); + } + } + + if (rt_prop != EFI_INVALID_TABLE_ADDR) { + efi_rt_properties_table_t *tbl; + + tbl = early_memremap(rt_prop, sizeof(*tbl)); + if (tbl) { + efi.runtime_supported_mask &= tbl->runtime_services_supported; + early_memunmap(tbl, sizeof(*tbl)); + } + } + + if (IS_ENABLED(CONFIG_BLK_DEV_INITRD) && + initrd != EFI_INVALID_TABLE_ADDR && phys_initrd_size == 0) { + struct linux_efi_initrd *tbl; + + tbl = early_memremap(initrd, sizeof(*tbl)); + if (tbl) { + phys_initrd_start = tbl->base; + phys_initrd_size = tbl->size; + tbl->base = tbl->size = 0; + early_memunmap(tbl, sizeof(*tbl)); + } + } + + if (IS_ENABLED(CONFIG_UNACCEPTED_MEMORY) && + efi.unaccepted != EFI_INVALID_TABLE_ADDR) { + struct efi_unaccepted_memory *unaccepted; + + unaccepted = early_memremap(efi.unaccepted, sizeof(*unaccepted)); + if (unaccepted) { + + if (unaccepted->version == 1) { + reserve_unaccepted(unaccepted); + } else { + efi.unaccepted = EFI_INVALID_TABLE_ADDR; + } + + early_memunmap(unaccepted, sizeof(*unaccepted)); + } + } + + return 0; +} + +int __init efi_systab_check_header(const efi_table_hdr_t *systab_hdr) +{ + if (systab_hdr->signature != EFI_SYSTEM_TABLE_SIGNATURE) { + pr_err("System table signature incorrect!\n"); + return -EINVAL; + } + + return 0; +} + +static const efi_char16_t *__init map_fw_vendor(unsigned long fw_vendor, + size_t size) +{ + const efi_char16_t *ret; + + ret = early_memremap_ro(fw_vendor, size); + if (!ret) + pr_err("Could not map the firmware vendor!\n"); + return ret; +} + +static void __init unmap_fw_vendor(const void *fw_vendor, size_t size) +{ + early_memunmap((void *)fw_vendor, size); +} + +void __init efi_systab_report_header(const efi_table_hdr_t *systab_hdr, + unsigned long fw_vendor) +{ + char vendor[100] = "unknown"; + const efi_char16_t *c16; + size_t i; + u16 rev; + + c16 = map_fw_vendor(fw_vendor, sizeof(vendor) * sizeof(efi_char16_t)); + if (c16) { + for (i = 0; i < sizeof(vendor) - 1 && c16[i]; ++i) + vendor[i] = c16[i]; + vendor[i] = '\0'; + + unmap_fw_vendor(c16, sizeof(vendor) * sizeof(efi_char16_t)); + } + + rev = (u16)systab_hdr->revision; + pr_info("EFI v%u.%u", systab_hdr->revision >> 16, rev / 10); + + rev %= 10; + if (rev) + pr_cont(".%u", rev); + + pr_cont(" by %s\n", vendor); + + if (IS_ENABLED(CONFIG_X86_64) && + systab_hdr->revision > EFI_1_10_SYSTEM_TABLE_REVISION && + !strcmp(vendor, "Apple")) { + pr_info("Apple Mac detected, using EFI v1.10 runtime services only\n"); + efi.runtime_version = EFI_1_10_SYSTEM_TABLE_REVISION; + } +} + +static __initdata char memory_type_name[][13] = { + "Reserved", + "Loader Code", + "Loader Data", + "Boot Code", + "Boot Data", + "Runtime Code", + "Runtime Data", + "Conventional", + "Unusable", + "ACPI Reclaim", + "ACPI Mem NVS", + "MMIO", + "MMIO Port", + "PAL Code", + "Persistent", + "Unaccepted", +}; + +char * __init efi_md_typeattr_format(char *buf, size_t size, + const efi_memory_desc_t *md) +{ + char *pos; + int type_len; + u64 attr; + + pos = buf; + if (md->type >= ARRAY_SIZE(memory_type_name)) + type_len = snprintf(pos, size, "[type=%u", md->type); + else + type_len = snprintf(pos, size, "[%-*s", + (int)(sizeof(memory_type_name[0]) - 1), + memory_type_name[md->type]); + if (type_len >= size) + return buf; + + pos += type_len; + size -= type_len; + + attr = md->attribute; + if (attr & ~(EFI_MEMORY_UC | EFI_MEMORY_WC | EFI_MEMORY_WT | + EFI_MEMORY_WB | EFI_MEMORY_UCE | EFI_MEMORY_RO | + EFI_MEMORY_WP | EFI_MEMORY_RP | EFI_MEMORY_XP | + EFI_MEMORY_NV | EFI_MEMORY_SP | EFI_MEMORY_CPU_CRYPTO | + EFI_MEMORY_MORE_RELIABLE | EFI_MEMORY_HOT_PLUGGABLE | + EFI_MEMORY_RUNTIME)) + snprintf(pos, size, "|attr=0x%016llx]", + (unsigned long long)attr); + else + snprintf(pos, size, + "|%3s|%2s|%2s|%2s|%2s|%2s|%2s|%2s|%2s|%2s|%3s|%2s|%2s|%2s|%2s]", + attr & EFI_MEMORY_RUNTIME ? "RUN" : "", + attr & EFI_MEMORY_HOT_PLUGGABLE ? "HP" : "", + attr & EFI_MEMORY_MORE_RELIABLE ? "MR" : "", + attr & EFI_MEMORY_CPU_CRYPTO ? "CC" : "", + attr & EFI_MEMORY_SP ? "SP" : "", + attr & EFI_MEMORY_NV ? "NV" : "", + attr & EFI_MEMORY_XP ? "XP" : "", + attr & EFI_MEMORY_RP ? "RP" : "", + attr & EFI_MEMORY_WP ? "WP" : "", + attr & EFI_MEMORY_RO ? "RO" : "", + attr & EFI_MEMORY_UCE ? "UCE" : "", + attr & EFI_MEMORY_WB ? "WB" : "", + attr & EFI_MEMORY_WT ? "WT" : "", + attr & EFI_MEMORY_WC ? "WC" : "", + attr & EFI_MEMORY_UC ? "UC" : ""); + return buf; +} + +/* + * efi_mem_attributes - lookup memmap attributes for physical address + * @phys_addr: the physical address to lookup + * + * Search in the EFI memory map for the region covering + * @phys_addr. Returns the EFI memory attributes if the region + * was found in the memory map, 0 otherwise. + */ +u64 efi_mem_attributes(unsigned long phys_addr) +{ + efi_memory_desc_t md; + + if (efi_mem_desc_lookup(phys_addr, &md)) + return 0; + + return md.attribute; +} + +/* + * efi_mem_type - lookup memmap type for physical address + * @phys_addr: the physical address to lookup + * + * Search in the EFI memory map for the region covering @phys_addr. + * Returns the EFI memory type if the region was found in the memory + * map, -EINVAL otherwise. + */ +int efi_mem_type(unsigned long phys_addr) +{ + efi_memory_desc_t md; + + if (!efi_enabled(EFI_MEMMAP) && !efi_enabled(EFI_PARAVIRT)) + return -ENOTSUPP; + + if (efi_mem_desc_lookup(phys_addr, &md)) + return -EINVAL; + + return md.type; +} + +int efi_status_to_err(efi_status_t status) +{ + int err; + + switch (status) { + case EFI_SUCCESS: + err = 0; + break; + case EFI_INVALID_PARAMETER: + err = -EINVAL; + break; + case EFI_OUT_OF_RESOURCES: + err = -ENOSPC; + break; + case EFI_DEVICE_ERROR: + err = -EIO; + break; + case EFI_WRITE_PROTECTED: + err = -EROFS; + break; + case EFI_SECURITY_VIOLATION: + err = -EACCES; + break; + case EFI_NOT_FOUND: + err = -ENOENT; + break; + case EFI_ABORTED: + err = -EINTR; + break; + default: + err = -EINVAL; + } + + return err; +} +EXPORT_SYMBOL_GPL(efi_status_to_err); + +static DEFINE_SPINLOCK(efi_mem_reserve_persistent_lock); +static struct linux_efi_memreserve *efi_memreserve_root __ro_after_init; + +static int __init efi_memreserve_map_root(void) +{ + if (mem_reserve == EFI_INVALID_TABLE_ADDR) + return -ENODEV; + + efi_memreserve_root = memremap(mem_reserve, + sizeof(*efi_memreserve_root), + MEMREMAP_WB); + if (WARN_ON_ONCE(!efi_memreserve_root)) + return -ENOMEM; + return 0; +} + +static int efi_mem_reserve_iomem(phys_addr_t addr, u64 size) +{ + struct resource *res, *parent; + int ret; + + res = kzalloc_obj(struct resource, GFP_ATOMIC); + if (!res) + return -ENOMEM; + + res->name = "reserved"; + res->flags = IORESOURCE_MEM; + res->start = addr; + res->end = addr + size - 1; + + /* we expect a conflict with a 'System RAM' region */ + parent = request_resource_conflict(&iomem_resource, res); + ret = parent ? request_resource(parent, res) : 0; + + /* + * Given that efi_mem_reserve_iomem() can be called at any + * time, only call memblock_reserve() if the architecture + * keeps the infrastructure around. + */ + if (IS_ENABLED(CONFIG_ARCH_KEEP_MEMBLOCK) && !ret) + memblock_reserve(addr, size); + + return ret; +} + +int __ref efi_mem_reserve_persistent(phys_addr_t addr, u64 size) +{ + struct linux_efi_memreserve *rsv; + unsigned long prsv; + int rc, index; + + if (efi_memreserve_root == (void *)ULONG_MAX) + return -ENODEV; + + if (!efi_memreserve_root) { + rc = efi_memreserve_map_root(); + if (rc) + return rc; + } + + /* first try to find a slot in an existing linked list entry */ + for (prsv = efi_memreserve_root->next; prsv; ) { + rsv = memremap(prsv, sizeof(*rsv), MEMREMAP_WB); + if (!rsv) + return -ENOMEM; + index = atomic_fetch_add_unless(&rsv->count, 1, rsv->size); + if (index < rsv->size) { + rsv->entry[index].base = addr; + rsv->entry[index].size = size; + + memunmap(rsv); + return efi_mem_reserve_iomem(addr, size); + } + prsv = rsv->next; + memunmap(rsv); + } + + /* no slot found - allocate a new linked list entry */ + rsv = (struct linux_efi_memreserve *)__get_free_page(GFP_ATOMIC); + if (!rsv) + return -ENOMEM; + + rc = efi_mem_reserve_iomem(__pa(rsv), SZ_4K); + if (rc) { + free_page((unsigned long)rsv); + return rc; + } + + /* + * The memremap() call above assumes that a linux_efi_memreserve entry + * never crosses a page boundary, so let's ensure that this remains true + * even when kexec'ing a 4k pages kernel from a >4k pages kernel, by + * using SZ_4K explicitly in the size calculation below. + */ + rsv->size = EFI_MEMRESERVE_COUNT(SZ_4K); + atomic_set(&rsv->count, 1); + rsv->entry[0].base = addr; + rsv->entry[0].size = size; + + spin_lock(&efi_mem_reserve_persistent_lock); + rsv->next = efi_memreserve_root->next; + efi_memreserve_root->next = __pa(rsv); + spin_unlock(&efi_mem_reserve_persistent_lock); + + return efi_mem_reserve_iomem(addr, size); +} + +static int __init efi_memreserve_root_init(void) +{ + if (efi_memreserve_root) + return 0; + if (efi_memreserve_map_root()) + efi_memreserve_root = (void *)ULONG_MAX; + return 0; +} +early_initcall(efi_memreserve_root_init); + +#ifdef CONFIG_KEXEC +static int update_efi_random_seed(struct notifier_block *nb, + unsigned long code, void *unused) +{ + struct linux_efi_random_seed *seed; + u32 size = 0; + + if (!kexec_in_progress) + return NOTIFY_DONE; + + seed = memremap(efi_rng_seed, sizeof(*seed), MEMREMAP_WB); + if (seed != NULL) { + size = min(seed->size, EFI_RANDOM_SEED_SIZE); + memunmap(seed); + } else { + pr_err("Could not map UEFI random seed!\n"); + } + if (size > 0) { + seed = memremap(efi_rng_seed, sizeof(*seed) + size, + MEMREMAP_WB); + if (seed != NULL) { + seed->size = size; + get_random_bytes(seed->bits, seed->size); + memunmap(seed); + } else { + pr_err("Could not map UEFI random seed!\n"); + } + } + return NOTIFY_DONE; +} + +static struct notifier_block efi_random_seed_nb = { + .notifier_call = update_efi_random_seed, +}; + +static int __init register_update_efi_random_seed(void) +{ + if (efi_rng_seed == EFI_INVALID_TABLE_ADDR) + return 0; + return register_reboot_notifier(&efi_random_seed_nb); +} +late_initcall(register_update_efi_random_seed); +#endif diff --git a/drivers/firmware/efi/efibc.c b/drivers/firmware/efi/efibc.c new file mode 100644 index 000000000..345a10aa7 --- /dev/null +++ b/drivers/firmware/efi/efibc.c @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * efibc: control EFI bootloaders which obey LoaderEntryOneShot var + * Copyright (c) 2013-2016, Intel Corporation. + */ + +#define pr_fmt(fmt) "efibc: " fmt + +#include +#include +#include +#include +#include + +#define MAX_DATA_LEN 512 + +static int efibc_set_variable(efi_char16_t *name, efi_char16_t *value, + unsigned long len) +{ + efi_status_t status; + + status = efi.set_variable(name, &LINUX_EFI_LOADER_ENTRY_GUID, + EFI_VARIABLE_NON_VOLATILE + | EFI_VARIABLE_BOOTSERVICE_ACCESS + | EFI_VARIABLE_RUNTIME_ACCESS, + len * sizeof(efi_char16_t), value); + + if (status != EFI_SUCCESS) { + pr_err("failed to set EFI variable: 0x%lx\n", status); + return -EIO; + } + return 0; +} + +static int efibc_reboot_notifier_call(struct notifier_block *notifier, + unsigned long event, void *data) +{ + efi_char16_t *reason = event == SYS_RESTART ? L"reboot" + : L"shutdown"; + const u8 *str = data; + efi_char16_t *wdata; + unsigned long l; + int ret; + + ret = efibc_set_variable(L"LoaderEntryRebootReason", reason, + ucs2_strlen(reason)); + if (ret || !data) + return NOTIFY_DONE; + + wdata = kmalloc_objs(efi_char16_t, MAX_DATA_LEN); + if (!wdata) + return NOTIFY_DONE; + + for (l = 0; l < MAX_DATA_LEN - 1 && str[l] != '\0'; l++) + wdata[l] = str[l]; + wdata[l] = L'\0'; + + efibc_set_variable(L"LoaderEntryOneShot", wdata, l); + + kfree(wdata); + return NOTIFY_DONE; +} + +static struct notifier_block efibc_reboot_notifier = { + .notifier_call = efibc_reboot_notifier_call, +}; + +static int __init efibc_init(void) +{ + int ret; + + if (!efi_rt_services_supported(EFI_RT_SUPPORTED_SET_VARIABLE)) + return -ENODEV; + + ret = register_reboot_notifier(&efibc_reboot_notifier); + if (ret) + pr_err("unable to register reboot notifier\n"); + + return ret; +} +module_init(efibc_init); + +static void __exit efibc_exit(void) +{ + unregister_reboot_notifier(&efibc_reboot_notifier); +} +module_exit(efibc_exit); + +MODULE_AUTHOR("Jeremy Compostella "); +MODULE_AUTHOR("Matt Gumbel + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Exported for use by lib/test_firmware.c only */ +LIST_HEAD(efi_embedded_fw_list); +EXPORT_SYMBOL_NS_GPL(efi_embedded_fw_list, "TEST_FIRMWARE"); +bool efi_embedded_fw_checked; +EXPORT_SYMBOL_NS_GPL(efi_embedded_fw_checked, "TEST_FIRMWARE"); + +static const struct dmi_system_id * const embedded_fw_table[] = { +#ifdef CONFIG_TOUCHSCREEN_DMI + touchscreen_dmi_table, +#endif + NULL +}; + +/* + * Note the efi_check_for_embedded_firmwares() code currently makes the + * following 2 assumptions. This may needs to be revisited if embedded firmware + * is found where this is not true: + * 1) The firmware is only found in EFI_BOOT_SERVICES_CODE memory segments + * 2) The firmware always starts at an offset which is a multiple of 8 bytes + */ +static int __init efi_check_md_for_embedded_firmware( + efi_memory_desc_t *md, const struct efi_embedded_fw_desc *desc) +{ + struct efi_embedded_fw *fw; + u8 hash[32]; + u64 i, size; + u8 *map; + + size = md->num_pages << EFI_PAGE_SHIFT; + map = memremap(md->phys_addr, size, MEMREMAP_WB); + if (!map) { + pr_err("Error mapping EFI mem at %#llx\n", md->phys_addr); + return -ENOMEM; + } + + for (i = 0; (i + desc->length) <= size; i += 8) { + if (memcmp(map + i, desc->prefix, EFI_EMBEDDED_FW_PREFIX_LEN)) + continue; + + sha256(map + i, desc->length, hash); + if (memcmp(hash, desc->sha256, 32) == 0) + break; + } + if ((i + desc->length) > size) { + memunmap(map); + return -ENOENT; + } + + pr_info("Found EFI embedded fw '%s'\n", desc->name); + + fw = kmalloc_obj(*fw); + if (!fw) { + memunmap(map); + return -ENOMEM; + } + + fw->data = kmemdup(map + i, desc->length, GFP_KERNEL); + memunmap(map); + if (!fw->data) { + kfree(fw); + return -ENOMEM; + } + + fw->name = desc->name; + fw->length = desc->length; + list_add(&fw->list, &efi_embedded_fw_list); + + return 0; +} + +void __init efi_check_for_embedded_firmwares(void) +{ + const struct efi_embedded_fw_desc *fw_desc; + const struct dmi_system_id *dmi_id; + efi_memory_desc_t *md; + int i, r; + + for (i = 0; embedded_fw_table[i]; i++) { + dmi_id = dmi_first_match(embedded_fw_table[i]); + if (!dmi_id) + continue; + + fw_desc = dmi_id->driver_data; + + /* + * In some drivers the struct driver_data contains may contain + * other driver specific data after the fw_desc struct; and + * the fw_desc struct itself may be empty, skip these. + */ + if (!fw_desc->name) + continue; + + for_each_efi_memory_desc(md) { + if (md->type != EFI_BOOT_SERVICES_CODE) + continue; + + r = efi_check_md_for_embedded_firmware(md, fw_desc); + if (r == 0) + break; + } + } + + efi_embedded_fw_checked = true; +} + +int efi_get_embedded_fw(const char *name, const u8 **data, size_t *size) +{ + struct efi_embedded_fw *iter, *fw = NULL; + + if (!efi_embedded_fw_checked) { + pr_warn("Warning %s called while we did not check for embedded fw\n", + __func__); + return -ENOENT; + } + + list_for_each_entry(iter, &efi_embedded_fw_list, list) { + if (strcmp(name, iter->name) == 0) { + fw = iter; + break; + } + } + + if (!fw) + return -ENOENT; + + *data = fw->data; + *size = fw->length; + + return 0; +} +EXPORT_SYMBOL_GPL(efi_get_embedded_fw); diff --git a/drivers/firmware/efi/esrt.c b/drivers/firmware/efi/esrt.c new file mode 100644 index 000000000..df148f733 --- /dev/null +++ b/drivers/firmware/efi/esrt.c @@ -0,0 +1,429 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * esrt.c + * + * This module exports EFI System Resource Table (ESRT) entries into userspace + * through the sysfs file system. The ESRT provides a read-only catalog of + * system components for which the system accepts firmware upgrades via UEFI's + * "Capsule Update" feature. This module allows userland utilities to evaluate + * what firmware updates can be applied to this system, and potentially arrange + * for those updates to occur. + * + * Data is currently found below /sys/firmware/efi/esrt/... + */ +#define pr_fmt(fmt) "esrt: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +struct efi_system_resource_entry_v1 { + efi_guid_t fw_class; + u32 fw_type; + u32 fw_version; + u32 lowest_supported_fw_version; + u32 capsule_flags; + u32 last_attempt_version; + u32 last_attempt_status; +}; + +/* + * _count and _version are what they seem like. _max is actually just + * accounting info for the firmware when creating the table; it should never + * have been exposed to us. To wit, the spec says: + * The maximum number of resource array entries that can be within the + * table without reallocating the table, must not be zero. + * Since there's no guidance about what that means in terms of memory layout, + * it means nothing to us. + */ +struct efi_system_resource_table { + u32 fw_resource_count; + u32 fw_resource_count_max; + u64 fw_resource_version; + u8 entries[]; +}; + +static phys_addr_t esrt_data; +static size_t esrt_data_size; + +static struct efi_system_resource_table *esrt; + +struct esre_entry { + union { + struct efi_system_resource_entry_v1 *esre1; + } esre; + + struct kobject kobj; + struct list_head list; +}; + +/* global list of esre_entry. */ +static LIST_HEAD(entry_list); + +/* entry attribute */ +struct esre_attribute { + struct attribute attr; + ssize_t (*show)(struct esre_entry *entry, char *buf); +}; + +static struct esre_entry *to_entry(struct kobject *kobj) +{ + return container_of(kobj, struct esre_entry, kobj); +} + +static struct esre_attribute *to_attr(struct attribute *attr) +{ + return container_of(attr, struct esre_attribute, attr); +} + +static ssize_t esre_attr_show(struct kobject *kobj, + struct attribute *_attr, char *buf) +{ + struct esre_entry *entry = to_entry(kobj); + struct esre_attribute *attr = to_attr(_attr); + + return attr->show(entry, buf); +} + +static const struct sysfs_ops esre_attr_ops = { + .show = esre_attr_show, +}; + +/* Generic ESRT Entry ("ESRE") support. */ +static ssize_t fw_class_show(struct esre_entry *entry, char *buf) +{ + char *str = buf; + + efi_guid_to_str(&entry->esre.esre1->fw_class, str); + str += strlen(str); + str += sprintf(str, "\n"); + + return str - buf; +} + +static struct esre_attribute esre_fw_class = __ATTR_RO_MODE(fw_class, 0400); + +#define esre_attr_decl(name, size, fmt) \ +static ssize_t name##_show(struct esre_entry *entry, char *buf) \ +{ \ + return sprintf(buf, fmt "\n", \ + le##size##_to_cpu(entry->esre.esre1->name)); \ +} \ +\ +static struct esre_attribute esre_##name = __ATTR_RO_MODE(name, 0400) + +esre_attr_decl(fw_type, 32, "%u"); +esre_attr_decl(fw_version, 32, "%u"); +esre_attr_decl(lowest_supported_fw_version, 32, "%u"); +esre_attr_decl(capsule_flags, 32, "0x%x"); +esre_attr_decl(last_attempt_version, 32, "%u"); +esre_attr_decl(last_attempt_status, 32, "%u"); + +static struct attribute *esre1_attrs[] = { + &esre_fw_class.attr, + &esre_fw_type.attr, + &esre_fw_version.attr, + &esre_lowest_supported_fw_version.attr, + &esre_capsule_flags.attr, + &esre_last_attempt_version.attr, + &esre_last_attempt_status.attr, + NULL +}; +ATTRIBUTE_GROUPS(esre1); + +static void esre_release(struct kobject *kobj) +{ + struct esre_entry *entry = to_entry(kobj); + + list_del(&entry->list); + kfree(entry); +} + +static const struct kobj_type esre1_ktype = { + .release = esre_release, + .sysfs_ops = &esre_attr_ops, + .default_groups = esre1_groups, +}; + + +static struct kobject *esrt_kobj; +static struct kset *esrt_kset; + +static int esre_create_sysfs_entry(void *esre, int entry_num) +{ + struct esre_entry *entry; + + entry = kzalloc_obj(*entry); + if (!entry) + return -ENOMEM; + + entry->kobj.kset = esrt_kset; + + if (esrt->fw_resource_version == 1) { + int rc = 0; + + entry->esre.esre1 = esre; + rc = kobject_init_and_add(&entry->kobj, &esre1_ktype, NULL, + "entry%d", entry_num); + if (rc) { + kobject_put(&entry->kobj); + return rc; + } + } + + list_add_tail(&entry->list, &entry_list); + return 0; +} + +/* support for displaying ESRT fields at the top level */ +#define esrt_attr_decl(name, size, fmt) \ +static ssize_t name##_show(struct kobject *kobj, \ + struct kobj_attribute *attr, char *buf)\ +{ \ + return sprintf(buf, fmt "\n", le##size##_to_cpu(esrt->name)); \ +} \ +\ +static struct kobj_attribute esrt_##name = __ATTR_RO_MODE(name, 0400) + +esrt_attr_decl(fw_resource_count, 32, "%u"); +esrt_attr_decl(fw_resource_count_max, 32, "%u"); +esrt_attr_decl(fw_resource_version, 64, "%llu"); + +static struct attribute *esrt_attrs[] = { + &esrt_fw_resource_count.attr, + &esrt_fw_resource_count_max.attr, + &esrt_fw_resource_version.attr, + NULL, +}; + +static inline int esrt_table_exists(void) +{ + if (!efi_enabled(EFI_CONFIG_TABLES)) + return 0; + if (efi.esrt == EFI_INVALID_TABLE_ADDR) + return 0; + return 1; +} + +static umode_t esrt_attr_is_visible(struct kobject *kobj, + struct attribute *attr, int n) +{ + if (!esrt_table_exists()) + return 0; + return attr->mode; +} + +static const struct attribute_group esrt_attr_group = { + .attrs = esrt_attrs, + .is_visible = esrt_attr_is_visible, +}; + +/* + * remap the table, validate it, mark it reserved and unmap it. + */ +void __init efi_esrt_init(void) +{ + void *va; + struct efi_system_resource_table tmpesrt; + size_t size, max, entry_size, entries_size; + efi_memory_desc_t md; + int rc; + phys_addr_t end; + + if (!efi_enabled(EFI_MEMMAP) && !efi_enabled(EFI_PARAVIRT)) + return; + + pr_debug("esrt-init: loading.\n"); + if (!esrt_table_exists()) + return; + + rc = efi_mem_desc_lookup(efi.esrt, &md); + if (rc < 0 || + (!(md.attribute & EFI_MEMORY_RUNTIME) && + md.type != EFI_BOOT_SERVICES_DATA && + md.type != EFI_RUNTIME_SERVICES_DATA && + md.type != EFI_ACPI_RECLAIM_MEMORY && + md.type != EFI_ACPI_MEMORY_NVS)) { + pr_warn("ESRT header is not in the memory map.\n"); + return; + } + + max = efi_mem_desc_end(&md) - efi.esrt; + size = sizeof(*esrt); + + if (max < size) { + pr_err("ESRT header doesn't fit on single memory map entry. (size: %zu max: %zu)\n", + size, max); + return; + } + + va = early_memremap(efi.esrt, size); + if (!va) { + pr_err("early_memremap(%p, %zu) failed.\n", (void *)efi.esrt, + size); + return; + } + + memcpy(&tmpesrt, va, sizeof(tmpesrt)); + early_memunmap(va, size); + + if (tmpesrt.fw_resource_version != 1) { + pr_err("Unsupported ESRT version %lld.\n", + tmpesrt.fw_resource_version); + return; + } + + entry_size = sizeof(struct efi_system_resource_entry_v1); + if (tmpesrt.fw_resource_count > 0 && max - size < entry_size) { + pr_err("ESRT memory map entry can only hold the header. (max: %zu size: %zu)\n", + max - size, entry_size); + return; + } + + /* + * The format doesn't really give us any boundary to test here, + * so I'm making up 128 as the max number of individually updatable + * components we support. + * 128 should be pretty excessive, but there's still some chance + * somebody will do that someday and we'll need to raise this. + */ + if (tmpesrt.fw_resource_count > 128) { + pr_err("ESRT says fw_resource_count has very large value %d.\n", + tmpesrt.fw_resource_count); + return; + } + + /* + * We know it can't be larger than N * sizeof() here, and N is limited + * by the previous test to a small number, so there's no overflow. + */ + entries_size = tmpesrt.fw_resource_count * entry_size; + if (max < size + entries_size) { + pr_err("ESRT does not fit on single memory map entry (size: %zu max: %zu)\n", + size, max); + return; + } + + size += entries_size; + + esrt_data = (phys_addr_t)efi.esrt; + esrt_data_size = size; + + end = esrt_data + size; + pr_info("Reserving ESRT space from %pa to %pa.\n", &esrt_data, &end); + if (md.type == EFI_BOOT_SERVICES_DATA) + efi_mem_reserve(esrt_data, esrt_data_size); + + pr_debug("esrt-init: loaded.\n"); +} + +static int __init register_entries(void) +{ + struct efi_system_resource_entry_v1 *v1_entries = (void *)esrt->entries; + int i, rc; + + if (!esrt_table_exists()) + return 0; + + for (i = 0; i < le32_to_cpu(esrt->fw_resource_count); i++) { + void *esre = NULL; + if (esrt->fw_resource_version == 1) { + esre = &v1_entries[i]; + } else { + pr_err("Unsupported ESRT version %lld.\n", + esrt->fw_resource_version); + return -EINVAL; + } + + rc = esre_create_sysfs_entry(esre, i); + if (rc < 0) { + pr_err("ESRT entry creation failed with error %d.\n", + rc); + return rc; + } + } + return 0; +} + +static void cleanup_entry_list(void) +{ + struct esre_entry *entry, *next; + + list_for_each_entry_safe(entry, next, &entry_list, list) { + kobject_put(&entry->kobj); + } +} + +static int __init esrt_sysfs_init(void) +{ + int error; + + pr_debug("esrt-sysfs: loading.\n"); + if (!esrt_data || !esrt_data_size) + return -ENOSYS; + + esrt = memremap(esrt_data, esrt_data_size, MEMREMAP_WB); + if (!esrt) { + pr_err("memremap(%pa, %zu) failed.\n", &esrt_data, + esrt_data_size); + return -ENOMEM; + } + + esrt_kobj = kobject_create_and_add("esrt", efi_kobj); + if (!esrt_kobj) { + pr_err("Firmware table registration failed.\n"); + error = -ENOMEM; + goto err; + } + + error = sysfs_create_group(esrt_kobj, &esrt_attr_group); + if (error) { + pr_err("Sysfs attribute export failed with error %d.\n", + error); + goto err_remove_esrt; + } + + esrt_kset = kset_create_and_add("entries", NULL, esrt_kobj); + if (!esrt_kset) { + pr_err("kset creation failed.\n"); + error = -ENOMEM; + goto err_remove_group; + } + + error = register_entries(); + if (error) + goto err_cleanup_list; + + pr_debug("esrt-sysfs: loaded.\n"); + + return 0; +err_cleanup_list: + cleanup_entry_list(); + kset_unregister(esrt_kset); +err_remove_group: + sysfs_remove_group(esrt_kobj, &esrt_attr_group); +err_remove_esrt: + kobject_put(esrt_kobj); +err: + memunmap(esrt); + esrt = NULL; + return error; +} +device_initcall(esrt_sysfs_init); + +/* +MODULE_AUTHOR("Peter Jones "); +MODULE_DESCRIPTION("EFI System Resource Table support"); +MODULE_LICENSE("GPL"); +*/ diff --git a/drivers/firmware/efi/fdtparams.c b/drivers/firmware/efi/fdtparams.c new file mode 100644 index 000000000..b815d2a75 --- /dev/null +++ b/drivers/firmware/efi/fdtparams.c @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#define pr_fmt(fmt) "efi: " fmt + +#include +#include +#include +#include +#include + +#include + +enum { + SYSTAB, + MMBASE, + MMSIZE, + DCSIZE, + DCVERS, + + PARAMCOUNT +}; + +static __initconst const char name[][22] = { + [SYSTAB] = "System Table ", + [MMBASE] = "MemMap Address ", + [MMSIZE] = "MemMap Size ", + [DCSIZE] = "MemMap Desc. Size ", + [DCVERS] = "MemMap Desc. Version ", +}; + +static __initconst const struct { + const char path[17]; + u8 paravirt; + const char params[PARAMCOUNT][26]; +} dt_params[] = { + { +#ifdef CONFIG_XEN // <-------17------> + .path = "/hypervisor/uefi", + .paravirt = 1, + .params = { + [SYSTAB] = "xen,uefi-system-table", + [MMBASE] = "xen,uefi-mmap-start", + [MMSIZE] = "xen,uefi-mmap-size", + [DCSIZE] = "xen,uefi-mmap-desc-size", + [DCVERS] = "xen,uefi-mmap-desc-ver", + } + }, { +#endif + .path = "/chosen", + .params = { // <-----------26-----------> + [SYSTAB] = "linux,uefi-system-table", + [MMBASE] = "linux,uefi-mmap-start", + [MMSIZE] = "linux,uefi-mmap-size", + [DCSIZE] = "linux,uefi-mmap-desc-size", + [DCVERS] = "linux,uefi-mmap-desc-ver", + } + } +}; + +static int __init efi_get_fdt_prop(const void *fdt, int node, const char *pname, + const char *rname, void *var, int size) +{ + const void *prop; + int len; + u64 val; + + prop = fdt_getprop(fdt, node, pname, &len); + if (!prop) + return 1; + + val = (len == 4) ? (u64)be32_to_cpup(prop) : get_unaligned_be64(prop); + + if (size == 8) + *(u64 *)var = val; + else + *(u32 *)var = (val < U32_MAX) ? val : U32_MAX; // saturate + + if (efi_enabled(EFI_DBG)) + pr_info(" %s: 0x%0*llx\n", rname, size * 2, val); + + return 0; +} + +u64 __init efi_get_fdt_params(struct efi_memory_map_data *mm) +{ + const void *fdt = initial_boot_params; + unsigned long systab; + int i, j, node; + struct { + void *var; + int size; + } target[] = { + [SYSTAB] = { &systab, sizeof(systab) }, + [MMBASE] = { &mm->phys_map, sizeof(mm->phys_map) }, + [MMSIZE] = { &mm->size, sizeof(mm->size) }, + [DCSIZE] = { &mm->desc_size, sizeof(mm->desc_size) }, + [DCVERS] = { &mm->desc_version, sizeof(mm->desc_version) }, + }; + + BUILD_BUG_ON(ARRAY_SIZE(target) != ARRAY_SIZE(name)); + BUILD_BUG_ON(ARRAY_SIZE(target) != ARRAY_SIZE(dt_params[0].params)); + + if (!fdt) + return 0; + + for (i = 0; i < ARRAY_SIZE(dt_params); i++) { + node = fdt_path_offset(fdt, dt_params[i].path); + if (node < 0) + continue; + + if (efi_enabled(EFI_DBG)) + pr_info("Getting UEFI parameters from %s in DT:\n", + dt_params[i].path); + + for (j = 0; j < ARRAY_SIZE(target); j++) { + const char *pname = dt_params[i].params[j]; + + if (!efi_get_fdt_prop(fdt, node, pname, name[j], + target[j].var, target[j].size)) + continue; + if (!j) + goto notfound; + pr_err("Can't find property '%s' in DT!\n", pname); + return 0; + } + if (dt_params[i].paravirt) + set_bit(EFI_PARAVIRT, &efi.flags); + return systab; + } +notfound: + pr_info("UEFI not found.\n"); + return 0; +} diff --git a/drivers/firmware/efi/libstub/Makefile b/drivers/firmware/efi/libstub/Makefile new file mode 100644 index 000000000..77a2b2d74 --- /dev/null +++ b/drivers/firmware/efi/libstub/Makefile @@ -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) $< $@ diff --git a/drivers/firmware/efi/libstub/Makefile.zboot b/drivers/firmware/efi/libstub/Makefile.zboot new file mode 100644 index 000000000..832deee36 --- /dev/null +++ b/drivers/firmware/efi/libstub/Makefile.zboot @@ -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 diff --git a/drivers/firmware/efi/libstub/alignedmem.c b/drivers/firmware/efi/libstub/alignedmem.c new file mode 100644 index 000000000..31928bd87 --- /dev/null +++ b/drivers/firmware/efi/libstub/alignedmem.c @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include + +#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; +} diff --git a/drivers/firmware/efi/libstub/arm32-stub.c b/drivers/firmware/efi/libstub/arm32-stub.c new file mode 100644 index 000000000..1073dd947 --- /dev/null +++ b/drivers/firmware/efi/libstub/arm32-stub.c @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2013 Linaro Ltd; + */ +#include +#include + +#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; +} diff --git a/drivers/firmware/efi/libstub/arm64-stub.c b/drivers/firmware/efi/libstub/arm64-stub.c new file mode 100644 index 000000000..2c3869356 --- /dev/null +++ b/drivers/firmware/efi/libstub/arm64-stub.c @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2013, 2014 Linaro Ltd; + * + * This file implements the EFI boot stub for the arm64 kernel. + * Adapted from ARM version by Mark Salter + */ + + +#include +#include +#include +#include + +#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); +} diff --git a/drivers/firmware/efi/libstub/arm64.c b/drivers/firmware/efi/libstub/arm64.c new file mode 100644 index 000000000..e57cd3de0 --- /dev/null +++ b/drivers/firmware/efi/libstub/arm64.c @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2013, 2014 Linaro Ltd; + * + * This file implements the EFI boot stub for the arm64 kernel. + * Adapted from ARM version by Mark Salter + */ + + +#include +#include +#include +#include +#include + +#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); +} diff --git a/drivers/firmware/efi/libstub/bitmap.c b/drivers/firmware/efi/libstub/bitmap.c new file mode 100644 index 000000000..5c9bba0d5 --- /dev/null +++ b/drivers/firmware/efi/libstub/bitmap.c @@ -0,0 +1,41 @@ +#include + +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; + } +} diff --git a/drivers/firmware/efi/libstub/efi-stub-entry.c b/drivers/firmware/efi/libstub/efi-stub-entry.c new file mode 100644 index 000000000..aa85e910f --- /dev/null +++ b/drivers/firmware/efi/libstub/efi-stub-entry.c @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include + +#include + +#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; +} diff --git a/drivers/firmware/efi/libstub/efi-stub-helper.c b/drivers/firmware/efi/libstub/efi-stub-helper.c new file mode 100644 index 000000000..f27f2e1f0 --- /dev/null +++ b/drivers/firmware/efi/libstub/efi-stub-helper.c @@ -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 + +#include +#include +#include +#include +#include + +#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, ¶m, &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"); + } +} diff --git a/drivers/firmware/efi/libstub/efi-stub.c b/drivers/firmware/efi/libstub/efi-stub.c new file mode 100644 index 000000000..42d6073bc --- /dev/null +++ b/drivers/firmware/efi/libstub/efi-stub.c @@ -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 + */ + +#include +#include +#include + +#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; + } +} diff --git a/drivers/firmware/efi/libstub/efistub.h b/drivers/firmware/efi/libstub/efistub.h new file mode 100644 index 000000000..fd91fc15e --- /dev/null +++ b/drivers/firmware/efi/libstub/efistub.h @@ -0,0 +1,1263 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef _DRIVERS_FIRMWARE_EFI_EFISTUB_H +#define _DRIVERS_FIRMWARE_EFI_EFISTUB_H + +#include +#include +#include +#include +#include +#include +#include + +/* + * __init annotations should not be used in the EFI stub, since the code is + * either included in the decompressor (x86, ARM) where they have no effect, + * or the whole stub is __init annotated at the section level (arm64), by + * renaming the sections, in which case the __init annotation will be + * redundant, and will result in section names like .init.init.text, and our + * linker script does not expect that. + */ +#undef __init + +/* + * Allow the platform to override the allocation granularity: this allows + * systems that have the capability to run with a larger page size to deal + * with the allocations for initrd and fdt more efficiently. + */ +#ifndef EFI_ALLOC_ALIGN +#define EFI_ALLOC_ALIGN EFI_PAGE_SIZE +#endif + +#ifndef EFI_ALLOC_LIMIT +#define EFI_ALLOC_LIMIT ULONG_MAX +#endif + +struct edid_info; +struct screen_info; +struct sysfb_display_info; + +extern bool efi_no5lvl; +extern bool efi_nochunk; +extern bool efi_nokaslr; +extern int efi_loglevel; +extern int efi_mem_encrypt; +extern bool efi_novamap; +extern const efi_system_table_t *efi_system_table; + +typedef union efi_dxe_services_table efi_dxe_services_table_t; +extern const efi_dxe_services_table_t *efi_dxe_table; + +efi_status_t __efiapi efi_pe_entry(efi_handle_t handle, + efi_system_table_t *sys_table_arg); + +#ifndef ARCH_HAS_EFISTUB_WRAPPERS + +#define efi_is_native() (true) +#define efi_table_attr(inst, attr) (inst)->attr +#define efi_fn_call(inst, func, ...) (inst)->func(__VA_ARGS__) + +#endif + +#define efi_call_proto(inst, func, ...) ({ \ + __typeof__(inst) __inst = (inst); \ + efi_fn_call(__inst, func, __inst, ##__VA_ARGS__); \ +}) +#define efi_bs_call(func, ...) \ + efi_fn_call(efi_table_attr(efi_system_table, boottime), func, ##__VA_ARGS__) +#define efi_rt_call(func, ...) \ + efi_fn_call(efi_table_attr(efi_system_table, runtime), func, ##__VA_ARGS__) +#define efi_dxe_call(func, ...) \ + efi_fn_call(efi_dxe_table, func, ##__VA_ARGS__) + +#define efi_info(fmt, ...) \ + efi_printk(KERN_INFO fmt, ##__VA_ARGS__) +#define efi_warn(fmt, ...) \ + efi_printk(KERN_WARNING "WARNING: " fmt, ##__VA_ARGS__) +#define efi_err(fmt, ...) \ + efi_printk(KERN_ERR "ERROR: " fmt, ##__VA_ARGS__) +#define efi_debug(fmt, ...) \ + efi_printk(KERN_DEBUG "DEBUG: " fmt, ##__VA_ARGS__) + +#define efi_printk_once(fmt, ...) \ +({ \ + static bool __print_once; \ + bool __ret_print_once = !__print_once; \ + \ + if (!__print_once) { \ + __print_once = true; \ + efi_printk(fmt, ##__VA_ARGS__); \ + } \ + __ret_print_once; \ +}) + +#define efi_info_once(fmt, ...) \ + efi_printk_once(KERN_INFO fmt, ##__VA_ARGS__) +#define efi_warn_once(fmt, ...) \ + efi_printk_once(KERN_WARNING "WARNING: " fmt, ##__VA_ARGS__) +#define efi_err_once(fmt, ...) \ + efi_printk_once(KERN_ERR "ERROR: " fmt, ##__VA_ARGS__) +#define efi_debug_once(fmt, ...) \ + efi_printk_once(KERN_DEBUG "DEBUG: " fmt, ##__VA_ARGS__) + +/* Helper macros for the usual case of using simple C variables: */ +#ifndef fdt_setprop_inplace_var +#define fdt_setprop_inplace_var(fdt, node_offset, name, var) \ + fdt_setprop_inplace((fdt), (node_offset), (name), &(var), sizeof(var)) +#endif + +#ifndef fdt_setprop_var +#define fdt_setprop_var(fdt, node_offset, name, var) \ + fdt_setprop((fdt), (node_offset), (name), &(var), sizeof(var)) +#endif + +#define get_efi_var(name, vendor, ...) \ + efi_rt_call(get_variable, (efi_char16_t *)(name), \ + (efi_guid_t *)(vendor), __VA_ARGS__) + +#define set_efi_var(name, vendor, ...) \ + efi_rt_call(set_variable, (efi_char16_t *)(name), \ + (efi_guid_t *)(vendor), __VA_ARGS__) + +#define efi_get_handle_at(array, idx) \ + (efi_is_native() ? (array)[idx] \ + : (efi_handle_t)(unsigned long)((u32 *)(array))[idx]) + +#define efi_get_handle_num(size) \ + ((size) / (efi_is_native() ? sizeof(efi_handle_t) : sizeof(u32))) + +#define for_each_efi_handle(handle, array, num) \ + for (int __i = 0; __i < (num) && \ + ((handle = efi_get_handle_at((array), __i)) || true); \ + __i++) + +static inline +void efi_set_u64_split(u64 data, u32 *lo, u32 *hi) +{ + *lo = lower_32_bits(data); + *hi = upper_32_bits(data); +} + +/* + * Allocation types for calls to boottime->allocate_pages. + */ +#define EFI_ALLOCATE_ANY_PAGES 0 +#define EFI_ALLOCATE_MAX_ADDRESS 1 +#define EFI_ALLOCATE_ADDRESS 2 +#define EFI_MAX_ALLOCATE_TYPE 3 + +/* + * The type of search to perform when calling boottime->locate_handle + */ +#define EFI_LOCATE_ALL_HANDLES 0 +#define EFI_LOCATE_BY_REGISTER_NOTIFY 1 +#define EFI_LOCATE_BY_PROTOCOL 2 + +/* + * boottime->stall takes the time period in microseconds + */ +#define EFI_USEC_PER_SEC 1000000 + +/* + * boottime->set_timer takes the time in 100ns units + */ +#define EFI_100NSEC_PER_USEC ((u64)10) + +/* + * An efi_boot_memmap is used by efi_get_memory_map() to return the + * EFI memory map in a dynamically allocated buffer. + * + * The buffer allocated for the EFI memory map includes extra room for + * a minimum of EFI_MMAP_NR_SLACK_SLOTS additional EFI memory descriptors. + * This facilitates the reuse of the EFI memory map buffer when a second + * call to ExitBootServices() is needed because of intervening changes to + * the EFI memory map. Other related structures, e.g. x86 e820ext, need + * to factor in this headroom requirement as well. + */ +#define EFI_MMAP_NR_SLACK_SLOTS 32 + +typedef struct efi_generic_dev_path efi_device_path_protocol_t; + +union efi_device_path_to_text_protocol { + struct { + efi_char16_t *(__efiapi *convert_device_node_to_text)( + const efi_device_path_protocol_t *, + bool, bool); + efi_char16_t *(__efiapi *convert_device_path_to_text)( + const efi_device_path_protocol_t *, + bool, bool); + }; + struct { + u32 convert_device_node_to_text; + u32 convert_device_path_to_text; + } mixed_mode; +}; + +typedef union efi_device_path_to_text_protocol efi_device_path_to_text_protocol_t; + +union efi_device_path_from_text_protocol { + struct { + efi_device_path_protocol_t * + (__efiapi *convert_text_to_device_node)(const efi_char16_t *); + efi_device_path_protocol_t * + (__efiapi *convert_text_to_device_path)(const efi_char16_t *); + }; + struct { + u32 convert_text_to_device_node; + u32 convert_text_to_device_path; + } mixed_mode; +}; + +typedef union efi_device_path_from_text_protocol efi_device_path_from_text_protocol_t; + +typedef void *efi_event_t; +/* Note that notifications won't work in mixed mode */ +typedef void (__efiapi *efi_event_notify_t)(efi_event_t, void *); + +#define EFI_EVT_TIMER 0x80000000U +#define EFI_EVT_RUNTIME 0x40000000U +#define EFI_EVT_NOTIFY_WAIT 0x00000100U +#define EFI_EVT_NOTIFY_SIGNAL 0x00000200U + +/** + * efi_set_event_at() - add event to events array + * + * @events: array of UEFI events + * @ids: index where to put the event in the array + * @event: event to add to the aray + * + * boottime->wait_for_event() takes an array of events as input. + * Provide a helper to set it up correctly for mixed mode. + */ +static inline +void efi_set_event_at(efi_event_t *events, size_t idx, efi_event_t event) +{ + if (efi_is_native()) + events[idx] = event; + else + ((u32 *)events)[idx] = (u32)(unsigned long)event; +} + +#define EFI_TPL_APPLICATION 4 +#define EFI_TPL_CALLBACK 8 +#define EFI_TPL_NOTIFY 16 +#define EFI_TPL_HIGH_LEVEL 31 + +typedef enum { + EfiTimerCancel, + EfiTimerPeriodic, + EfiTimerRelative +} EFI_TIMER_DELAY; + +/* + * EFI Boot Services table + */ +union efi_boot_services { + struct { + efi_table_hdr_t hdr; + void *raise_tpl; + void *restore_tpl; + efi_status_t (__efiapi *allocate_pages)(int, int, unsigned long, + efi_physical_addr_t *); + efi_status_t (__efiapi *free_pages)(efi_physical_addr_t, + unsigned long); + efi_status_t (__efiapi *get_memory_map)(unsigned long *, void *, + unsigned long *, + unsigned long *, u32 *); + efi_status_t (__efiapi *allocate_pool)(int, unsigned long, + void **); + efi_status_t (__efiapi *free_pool)(void *); + efi_status_t (__efiapi *create_event)(u32, unsigned long, + efi_event_notify_t, void *, + efi_event_t *); + efi_status_t (__efiapi *set_timer)(efi_event_t, + EFI_TIMER_DELAY, u64); + efi_status_t (__efiapi *wait_for_event)(unsigned long, + efi_event_t *, + unsigned long *); + void *signal_event; + efi_status_t (__efiapi *close_event)(efi_event_t); + void *check_event; + void *install_protocol_interface; + void *reinstall_protocol_interface; + void *uninstall_protocol_interface; + efi_status_t (__efiapi *handle_protocol)(efi_handle_t, + efi_guid_t *, void **); + void *__reserved; + void *register_protocol_notify; + efi_status_t (__efiapi *locate_handle)(int, efi_guid_t *, + void *, unsigned long *, + efi_handle_t *); + efi_status_t (__efiapi *locate_device_path)(efi_guid_t *, + efi_device_path_protocol_t **, + efi_handle_t *); + efi_status_t (__efiapi *install_configuration_table)(efi_guid_t *, + void *); + efi_status_t (__efiapi *load_image)(bool, efi_handle_t, + efi_device_path_protocol_t *, + void *, unsigned long, + efi_handle_t *); + efi_status_t (__efiapi *start_image)(efi_handle_t, unsigned long *, + efi_char16_t **); + efi_status_t __noreturn (__efiapi *exit)(efi_handle_t, + efi_status_t, + unsigned long, + efi_char16_t *); + efi_status_t (__efiapi *unload_image)(efi_handle_t); + efi_status_t (__efiapi *exit_boot_services)(efi_handle_t, + unsigned long); + void *get_next_monotonic_count; + efi_status_t (__efiapi *stall)(unsigned long); + void *set_watchdog_timer; + void *connect_controller; + efi_status_t (__efiapi *disconnect_controller)(efi_handle_t, + efi_handle_t, + efi_handle_t); + void *open_protocol; + void *close_protocol; + void *open_protocol_information; + void *protocols_per_handle; + efi_status_t (__efiapi *locate_handle_buffer)(int, efi_guid_t *, + void *, unsigned long *, + efi_handle_t **); + efi_status_t (__efiapi *locate_protocol)(efi_guid_t *, void *, + void **); + efi_status_t (__efiapi *install_multiple_protocol_interfaces)(efi_handle_t *, ...); + efi_status_t (__efiapi *uninstall_multiple_protocol_interfaces)(efi_handle_t, ...); + void *calculate_crc32; + void (__efiapi *copy_mem)(void *, const void *, unsigned long); + void (__efiapi *set_mem)(void *, unsigned long, unsigned char); + void *create_event_ex; + }; + struct { + efi_table_hdr_t hdr; + u32 raise_tpl; + u32 restore_tpl; + u32 allocate_pages; + u32 free_pages; + u32 get_memory_map; + u32 allocate_pool; + u32 free_pool; + u32 create_event; + u32 set_timer; + u32 wait_for_event; + u32 signal_event; + u32 close_event; + u32 check_event; + u32 install_protocol_interface; + u32 reinstall_protocol_interface; + u32 uninstall_protocol_interface; + u32 handle_protocol; + u32 __reserved; + u32 register_protocol_notify; + u32 locate_handle; + u32 locate_device_path; + u32 install_configuration_table; + u32 load_image; + u32 start_image; + u32 exit; + u32 unload_image; + u32 exit_boot_services; + u32 get_next_monotonic_count; + u32 stall; + u32 set_watchdog_timer; + u32 connect_controller; + u32 disconnect_controller; + u32 open_protocol; + u32 close_protocol; + u32 open_protocol_information; + u32 protocols_per_handle; + u32 locate_handle_buffer; + u32 locate_protocol; + u32 install_multiple_protocol_interfaces; + u32 uninstall_multiple_protocol_interfaces; + u32 calculate_crc32; + u32 copy_mem; + u32 set_mem; + u32 create_event_ex; + } mixed_mode; +}; + +typedef enum { + EfiGcdMemoryTypeNonExistent, + EfiGcdMemoryTypeReserved, + EfiGcdMemoryTypeSystemMemory, + EfiGcdMemoryTypeMemoryMappedIo, + EfiGcdMemoryTypePersistent, + EfiGcdMemoryTypeMoreReliable, + EfiGcdMemoryTypeMaximum +} efi_gcd_memory_type_t; + +typedef struct { + efi_physical_addr_t base_address; + u64 length; + u64 capabilities; + u64 attributes; + efi_gcd_memory_type_t gcd_memory_type; + void *image_handle; + void *device_handle; +} efi_gcd_memory_space_desc_t; + +/* + * EFI DXE Services table + */ +union efi_dxe_services_table { + struct { + efi_table_hdr_t hdr; + void *add_memory_space; + void *allocate_memory_space; + void *free_memory_space; + void *remove_memory_space; + efi_status_t (__efiapi *get_memory_space_descriptor)(efi_physical_addr_t, + efi_gcd_memory_space_desc_t *); + efi_status_t (__efiapi *set_memory_space_attributes)(efi_physical_addr_t, + u64, u64); + void *get_memory_space_map; + void *add_io_space; + void *allocate_io_space; + void *free_io_space; + void *remove_io_space; + void *get_io_space_descriptor; + void *get_io_space_map; + void *dispatch; + void *schedule; + void *trust; + void *process_firmware_volume; + void *set_memory_space_capabilities; + }; + struct { + efi_table_hdr_t hdr; + u32 add_memory_space; + u32 allocate_memory_space; + u32 free_memory_space; + u32 remove_memory_space; + u32 get_memory_space_descriptor; + u32 set_memory_space_attributes; + u32 get_memory_space_map; + u32 add_io_space; + u32 allocate_io_space; + u32 free_io_space; + u32 remove_io_space; + u32 get_io_space_descriptor; + u32 get_io_space_map; + u32 dispatch; + u32 schedule; + u32 trust; + u32 process_firmware_volume; + u32 set_memory_space_capabilities; + } mixed_mode; +}; + +typedef union efi_memory_attribute_protocol efi_memory_attribute_protocol_t; + +union efi_memory_attribute_protocol { + struct { + efi_status_t (__efiapi *get_memory_attributes)( + efi_memory_attribute_protocol_t *, efi_physical_addr_t, u64, u64 *); + + efi_status_t (__efiapi *set_memory_attributes)( + efi_memory_attribute_protocol_t *, efi_physical_addr_t, u64, u64); + + efi_status_t (__efiapi *clear_memory_attributes)( + efi_memory_attribute_protocol_t *, efi_physical_addr_t, u64, u64); + }; + struct { + u32 get_memory_attributes; + u32 set_memory_attributes; + u32 clear_memory_attributes; + } mixed_mode; +}; + +typedef union efi_uga_draw_protocol efi_uga_draw_protocol_t; + +union efi_uga_draw_protocol { + struct { + efi_status_t (__efiapi *get_mode)(efi_uga_draw_protocol_t *, + u32*, u32*, u32*, u32*); + void *set_mode; + void *blt; + }; + struct { + u32 get_mode; + u32 set_mode; + u32 blt; + } mixed_mode; +}; + +typedef struct { + u16 scan_code; + efi_char16_t unicode_char; +} efi_input_key_t; + +union efi_simple_text_input_protocol { + struct { + void *reset; + efi_status_t (__efiapi *read_keystroke)(efi_simple_text_input_protocol_t *, + efi_input_key_t *); + efi_event_t wait_for_key; + }; + struct { + u32 reset; + u32 read_keystroke; + u32 wait_for_key; + } mixed_mode; +}; + +efi_status_t efi_wait_for_key(unsigned long usec, efi_input_key_t *key); + +union efi_simple_text_output_protocol { + struct { + void *reset; + efi_status_t (__efiapi *output_string)(efi_simple_text_output_protocol_t *, + efi_char16_t *); + void *test_string; + }; + struct { + u32 reset; + u32 output_string; + u32 test_string; + } mixed_mode; +}; + +#define PIXEL_RGB_RESERVED_8BIT_PER_COLOR 0 +#define PIXEL_BGR_RESERVED_8BIT_PER_COLOR 1 +#define PIXEL_BIT_MASK 2 +#define PIXEL_BLT_ONLY 3 +#define PIXEL_FORMAT_MAX 4 + +typedef struct { + u32 red_mask; + u32 green_mask; + u32 blue_mask; + u32 reserved_mask; +} efi_pixel_bitmask_t; + +typedef struct { + u32 version; + u32 horizontal_resolution; + u32 vertical_resolution; + int pixel_format; + efi_pixel_bitmask_t pixel_information; + u32 pixels_per_scan_line; +} efi_graphics_output_mode_info_t; + +typedef union efi_graphics_output_protocol_mode efi_graphics_output_protocol_mode_t; + +union efi_graphics_output_protocol_mode { + struct { + u32 max_mode; + u32 mode; + efi_graphics_output_mode_info_t *info; + unsigned long size_of_info; + efi_physical_addr_t frame_buffer_base; + unsigned long frame_buffer_size; + }; + struct { + u32 max_mode; + u32 mode; + u32 info; + u32 size_of_info; + u64 frame_buffer_base; + u32 frame_buffer_size; + } mixed_mode; +}; + +typedef union efi_graphics_output_protocol efi_graphics_output_protocol_t; + +union efi_graphics_output_protocol { + struct { + efi_status_t (__efiapi *query_mode)(efi_graphics_output_protocol_t *, + u32, unsigned long *, + efi_graphics_output_mode_info_t **); + efi_status_t (__efiapi *set_mode) (efi_graphics_output_protocol_t *, u32); + void *blt; + efi_graphics_output_protocol_mode_t *mode; + }; + struct { + u32 query_mode; + u32 set_mode; + u32 blt; + u32 mode; + } mixed_mode; +}; + +typedef union efi_edid_discovered_protocol efi_edid_discovered_protocol_t; + +union efi_edid_discovered_protocol { + struct { + u32 size_of_edid; + u8 *edid; + }; + struct { + u32 size_of_edid; + u32 edid; + } mixed_mode; +}; + +typedef union efi_edid_active_protocol efi_edid_active_protocol_t; + +union efi_edid_active_protocol { + struct { + u32 size_of_edid; + u8 *edid; + }; + struct { + u32 size_of_edid; + u32 edid; + } mixed_mode; +}; + +typedef union { + struct { + u32 revision; + efi_handle_t parent_handle; + efi_system_table_t *system_table; + efi_handle_t device_handle; + void *file_path; + void *reserved; + u32 load_options_size; + void *load_options; + void *image_base; + __aligned_u64 image_size; + unsigned int image_code_type; + unsigned int image_data_type; + efi_status_t (__efiapi *unload)(efi_handle_t image_handle); + }; + struct { + u32 revision; + u32 parent_handle; + u32 system_table; + u32 device_handle; + u32 file_path; + u32 reserved; + u32 load_options_size; + u32 load_options; + u32 image_base; + __aligned_u64 image_size; + u32 image_code_type; + u32 image_data_type; + u32 unload; + } mixed_mode; +} efi_loaded_image_t; + +typedef struct { + u64 size; + u64 file_size; + u64 phys_size; + efi_time_t create_time; + efi_time_t last_access_time; + efi_time_t modification_time; + __aligned_u64 attribute; + efi_char16_t filename[]; +} efi_file_info_t; + +typedef union efi_file_protocol efi_file_protocol_t; + +union efi_file_protocol { + struct { + u64 revision; + efi_status_t (__efiapi *open) (efi_file_protocol_t *, + efi_file_protocol_t **, + efi_char16_t *, u64, + u64); + efi_status_t (__efiapi *close) (efi_file_protocol_t *); + efi_status_t (__efiapi *delete) (efi_file_protocol_t *); + efi_status_t (__efiapi *read) (efi_file_protocol_t *, + unsigned long *, + void *); + efi_status_t (__efiapi *write) (efi_file_protocol_t *, + unsigned long, void *); + efi_status_t (__efiapi *get_position)(efi_file_protocol_t *, + u64 *); + efi_status_t (__efiapi *set_position)(efi_file_protocol_t *, + u64); + efi_status_t (__efiapi *get_info) (efi_file_protocol_t *, + efi_guid_t *, + unsigned long *, + void *); + efi_status_t (__efiapi *set_info) (efi_file_protocol_t *, + efi_guid_t *, + unsigned long, + void *); + efi_status_t (__efiapi *flush) (efi_file_protocol_t *); + }; + struct { + u64 revision; + u32 open; + u32 close; + u32 delete; + u32 read; + u32 write; + u32 get_position; + u32 set_position; + u32 get_info; + u32 set_info; + u32 flush; + } mixed_mode; +}; + +typedef union efi_simple_file_system_protocol efi_simple_file_system_protocol_t; + +union efi_simple_file_system_protocol { + struct { + u64 revision; + efi_status_t (__efiapi *open_volume)(efi_simple_file_system_protocol_t *, + efi_file_protocol_t **); + }; + struct { + u64 revision; + u32 open_volume; + } mixed_mode; +}; + +#define EFI_FILE_MODE_READ 0x0000000000000001 +#define EFI_FILE_MODE_WRITE 0x0000000000000002 +#define EFI_FILE_MODE_CREATE 0x8000000000000000 + +typedef enum { + EfiPciIoWidthUint8, + EfiPciIoWidthUint16, + EfiPciIoWidthUint32, + EfiPciIoWidthUint64, + EfiPciIoWidthFifoUint8, + EfiPciIoWidthFifoUint16, + EfiPciIoWidthFifoUint32, + EfiPciIoWidthFifoUint64, + EfiPciIoWidthFillUint8, + EfiPciIoWidthFillUint16, + EfiPciIoWidthFillUint32, + EfiPciIoWidthFillUint64, + EfiPciIoWidthMaximum +} EFI_PCI_IO_PROTOCOL_WIDTH; + +typedef enum { + EfiPciIoAttributeOperationGet, + EfiPciIoAttributeOperationSet, + EfiPciIoAttributeOperationEnable, + EfiPciIoAttributeOperationDisable, + EfiPciIoAttributeOperationSupported, + EfiPciIoAttributeOperationMaximum +} EFI_PCI_IO_PROTOCOL_ATTRIBUTE_OPERATION; + +typedef struct { + u32 read; + u32 write; +} efi_pci_io_protocol_access_32_t; + +typedef union efi_pci_io_protocol efi_pci_io_protocol_t; + +typedef +efi_status_t (__efiapi *efi_pci_io_protocol_cfg_t)(efi_pci_io_protocol_t *, + EFI_PCI_IO_PROTOCOL_WIDTH, + u32 offset, + unsigned long count, + void *buffer); + +typedef struct { + void *read; + void *write; +} efi_pci_io_protocol_access_t; + +typedef struct { + efi_pci_io_protocol_cfg_t read; + efi_pci_io_protocol_cfg_t write; +} efi_pci_io_protocol_config_access_t; + +union efi_pci_io_protocol { + struct { + void *poll_mem; + void *poll_io; + efi_pci_io_protocol_access_t mem; + efi_pci_io_protocol_access_t io; + efi_pci_io_protocol_config_access_t pci; + void *copy_mem; + void *map; + void *unmap; + void *allocate_buffer; + void *free_buffer; + void *flush; + efi_status_t (__efiapi *get_location)(efi_pci_io_protocol_t *, + unsigned long *segment_nr, + unsigned long *bus_nr, + unsigned long *device_nr, + unsigned long *func_nr); + void *attributes; + void *get_bar_attributes; + void *set_bar_attributes; + uint64_t romsize; + void *romimage; + }; + struct { + u32 poll_mem; + u32 poll_io; + efi_pci_io_protocol_access_32_t mem; + efi_pci_io_protocol_access_32_t io; + efi_pci_io_protocol_access_32_t pci; + u32 copy_mem; + u32 map; + u32 unmap; + u32 allocate_buffer; + u32 free_buffer; + u32 flush; + u32 get_location; + u32 attributes; + u32 get_bar_attributes; + u32 set_bar_attributes; + u64 romsize; + u32 romimage; + } mixed_mode; +}; + +#define EFI_PCI_IO_ATTRIBUTE_ISA_MOTHERBOARD_IO 0x0001 +#define EFI_PCI_IO_ATTRIBUTE_ISA_IO 0x0002 +#define EFI_PCI_IO_ATTRIBUTE_VGA_PALETTE_IO 0x0004 +#define EFI_PCI_IO_ATTRIBUTE_VGA_MEMORY 0x0008 +#define EFI_PCI_IO_ATTRIBUTE_VGA_IO 0x0010 +#define EFI_PCI_IO_ATTRIBUTE_IDE_PRIMARY_IO 0x0020 +#define EFI_PCI_IO_ATTRIBUTE_IDE_SECONDARY_IO 0x0040 +#define EFI_PCI_IO_ATTRIBUTE_MEMORY_WRITE_COMBINE 0x0080 +#define EFI_PCI_IO_ATTRIBUTE_IO 0x0100 +#define EFI_PCI_IO_ATTRIBUTE_MEMORY 0x0200 +#define EFI_PCI_IO_ATTRIBUTE_BUS_MASTER 0x0400 +#define EFI_PCI_IO_ATTRIBUTE_MEMORY_CACHED 0x0800 +#define EFI_PCI_IO_ATTRIBUTE_MEMORY_DISABLE 0x1000 +#define EFI_PCI_IO_ATTRIBUTE_EMBEDDED_DEVICE 0x2000 +#define EFI_PCI_IO_ATTRIBUTE_EMBEDDED_ROM 0x4000 +#define EFI_PCI_IO_ATTRIBUTE_DUAL_ADDRESS_CYCLE 0x8000 +#define EFI_PCI_IO_ATTRIBUTE_ISA_IO_16 0x10000 +#define EFI_PCI_IO_ATTRIBUTE_VGA_PALETTE_IO_16 0x20000 +#define EFI_PCI_IO_ATTRIBUTE_VGA_IO_16 0x40000 + +struct efi_dev_path; + +typedef union apple_properties_protocol apple_properties_protocol_t; + +union apple_properties_protocol { + struct { + unsigned long version; + efi_status_t (__efiapi *get)(apple_properties_protocol_t *, + struct efi_dev_path *, + efi_char16_t *, void *, u32 *); + efi_status_t (__efiapi *set)(apple_properties_protocol_t *, + struct efi_dev_path *, + efi_char16_t *, void *, u32); + efi_status_t (__efiapi *del)(apple_properties_protocol_t *, + struct efi_dev_path *, + efi_char16_t *); + efi_status_t (__efiapi *get_all)(apple_properties_protocol_t *, + void *buffer, u32 *); + }; + struct { + u32 version; + u32 get; + u32 set; + u32 del; + u32 get_all; + } mixed_mode; +}; + +typedef u32 efi_tcg2_event_log_format; + +#define INITRD_EVENT_TAG_ID 0x8F3B22ECU +#define LOAD_OPTIONS_EVENT_TAG_ID 0x8F3B22EDU +#define EV_EVENT_TAG 0x00000006U +#define EFI_TCG2_EVENT_HEADER_VERSION 0x1 + +struct efi_tcg2_event { + u32 event_size; + struct { + u32 header_size; + u16 header_version; + u32 pcr_index; + u32 event_type; + } __packed event_header; + /* u8[] event follows here */ +} __packed; + +/* from TCG PC Client Platform Firmware Profile Specification */ +typedef struct tdTCG_PCClientTaggedEvent { + u32 tagged_event_id; + u32 tagged_event_data_size; + u8 tagged_event_data[]; +} TCG_PCClientTaggedEvent; + +typedef struct efi_tcg2_event efi_tcg2_event_t; +typedef union efi_tcg2_protocol efi_tcg2_protocol_t; + +union efi_tcg2_protocol { + struct { + void *get_capability; + efi_status_t (__efiapi *get_event_log)(efi_tcg2_protocol_t *, + efi_tcg2_event_log_format, + efi_physical_addr_t *, + efi_physical_addr_t *, + efi_bool_t *); + efi_status_t (__efiapi *hash_log_extend_event)(efi_tcg2_protocol_t *, + u64, + efi_physical_addr_t, + u64, + const efi_tcg2_event_t *); + void *submit_command; + void *get_active_pcr_banks; + void *set_active_pcr_banks; + void *get_result_of_set_active_pcr_banks; + }; + struct { + u32 get_capability; + u32 get_event_log; + u32 hash_log_extend_event; + u32 submit_command; + u32 get_active_pcr_banks; + u32 set_active_pcr_banks; + u32 get_result_of_set_active_pcr_banks; + } mixed_mode; +}; + +typedef struct { + u8 major; + u8 minor; +} efi_cc_version_t; + +typedef struct { + u8 type; + u8 sub_type; +} efi_cc_type_t; + +/* EFI CC type/subtype defines */ +#define EFI_CC_TYPE_NONE 0 +#define EFI_CC_TYPE_AMD_SEV 1 +#define EFI_CC_TYPE_INTEL_TDX 2 + +typedef u32 efi_cc_mr_index_t; + +struct efi_cc_event { + u32 event_size; + struct { + u32 header_size; + u16 header_version; + u32 mr_index; + u32 event_type; + } __packed event_header; + /* u8[] event follows here */ +} __packed; + +typedef struct efi_cc_event efi_cc_event_t; + +typedef u32 efi_cc_event_log_bitmap_t; +typedef u32 efi_cc_event_log_format_t; +typedef u32 efi_cc_event_algorithm_bitmap_t; + +typedef struct { + u8 size; + efi_cc_version_t structure_version; + efi_cc_version_t protocol_version; + efi_cc_event_algorithm_bitmap_t hash_algorithm_bitmap; + efi_cc_event_log_bitmap_t supported_event_logs; + efi_cc_type_t cc_type; +} efi_cc_boot_service_cap_t; + +#define EFI_CC_EVENT_HEADER_VERSION 1 + +#define EFI_CC_BOOT_HASH_ALG_SHA384 0x00000004 + +#define EFI_CC_EVENT_LOG_FORMAT_TCG_2 0x00000002 + +typedef union efi_cc_protocol efi_cc_protocol_t; + +union efi_cc_protocol { + struct { + efi_status_t + (__efiapi *get_capability)(efi_cc_protocol_t *, + efi_cc_boot_service_cap_t *); + + efi_status_t + (__efiapi *get_event_log)(efi_cc_protocol_t *, + efi_cc_event_log_format_t, + efi_physical_addr_t *, + efi_physical_addr_t *, + efi_bool_t *); + + efi_status_t + (__efiapi *hash_log_extend_event)(efi_cc_protocol_t *, u64, + efi_physical_addr_t, u64, + const efi_cc_event_t *); + + efi_status_t + (__efiapi *map_pcr_to_mr_index)(efi_cc_protocol_t *, u32, + efi_cc_mr_index_t *); + }; + struct { + u32 get_capability; + u32 get_event_log; + u32 hash_log_extend_event; + u32 map_pcr_to_mr_index; + } mixed_mode; +}; + +struct riscv_efi_boot_protocol { + u64 revision; + + efi_status_t (__efiapi *get_boot_hartid)(struct riscv_efi_boot_protocol *, + unsigned long *boot_hartid); +}; + +typedef union efi_load_file_protocol efi_load_file_protocol_t; +typedef union efi_load_file_protocol efi_load_file2_protocol_t; + +union efi_load_file_protocol { + struct { + efi_status_t (__efiapi *load_file)(efi_load_file_protocol_t *, + efi_device_path_protocol_t *, + bool, unsigned long *, void *); + }; + struct { + u32 load_file; + } mixed_mode; +}; + +typedef struct { + u32 attributes; + u16 file_path_list_length; + u8 variable_data[]; + // efi_char16_t description[]; + // efi_device_path_protocol_t file_path_list[]; + // u8 optional_data[]; +} __packed efi_load_option_t; + +#define EFI_LOAD_OPTION_ACTIVE 0x0001U +#define EFI_LOAD_OPTION_FORCE_RECONNECT 0x0002U +#define EFI_LOAD_OPTION_HIDDEN 0x0008U +#define EFI_LOAD_OPTION_CATEGORY 0x1f00U +#define EFI_LOAD_OPTION_CATEGORY_BOOT 0x0000U +#define EFI_LOAD_OPTION_CATEGORY_APP 0x0100U + +#define EFI_LOAD_OPTION_BOOT_MASK \ + (EFI_LOAD_OPTION_ACTIVE|EFI_LOAD_OPTION_HIDDEN|EFI_LOAD_OPTION_CATEGORY) +#define EFI_LOAD_OPTION_MASK (EFI_LOAD_OPTION_FORCE_RECONNECT|EFI_LOAD_OPTION_BOOT_MASK) + +typedef struct { + u32 attributes; + u16 file_path_list_length; + const efi_char16_t *description; + const efi_device_path_protocol_t *file_path_list; + u32 optional_data_size; + const void *optional_data; +} efi_load_option_unpacked_t; + +void efi_pci_disable_bridge_busmaster(void); + +typedef efi_status_t (*efi_exit_boot_map_processing)( + struct efi_boot_memmap *map, + void *priv); + +efi_status_t efi_exit_boot_services(void *handle, void *priv, + efi_exit_boot_map_processing priv_func); + +efi_status_t efi_boot_kernel(void *handle, efi_loaded_image_t *image, + unsigned long kernel_addr, char *cmdline_ptr); + +void *get_fdt(unsigned long *fdt_size); + +efi_status_t efi_alloc_virtmap(efi_memory_desc_t **virtmap, + unsigned long *desc_size, u32 *desc_ver); +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); + +efi_status_t efi_get_random_bytes(unsigned long size, u8 *out); + +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); + +efi_status_t efi_random_get_seed(void); + +efi_status_t check_platform_features(void); + +void *get_efi_config_table(efi_guid_t guid); + +/* NOTE: These functions do not print a trailing newline after the string */ +void efi_char16_puts(efi_char16_t *); +void efi_puts(const char *str); + +__printf(1, 2) int efi_printk(char const *fmt, ...); + +void efi_free(unsigned long size, unsigned long addr); +DEFINE_FREE(efi_pool, void *, if (_T) efi_bs_call(free_pool, _T)); + +void efi_apply_loadoptions_quirk(const void **load_options, u32 *load_options_size); + +char *efi_convert_cmdline(efi_loaded_image_t *image); + +efi_status_t efi_get_memory_map(struct efi_boot_memmap **map, + bool install_cfg_tbl); + +efi_status_t efi_allocate_pages(unsigned long size, unsigned long *addr, + unsigned long max); + +efi_status_t efi_allocate_pages_aligned(unsigned long size, unsigned long *addr, + unsigned long max, unsigned long align, + int memory_type); + +efi_status_t efi_low_alloc_above(unsigned long size, unsigned long align, + unsigned long *addr, unsigned long min); + +efi_status_t efi_parse_options(char const *cmdline); + +void efi_parse_option_graphics(char *option); + +efi_status_t efi_setup_graphics(struct screen_info *si, struct edid_info *edid); + +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); + + +static inline efi_status_t efi_load_dtb(efi_loaded_image_t *image, + unsigned long *load_addr, + unsigned long *load_size) +{ + return handle_cmdline_files(image, L"dtb=", sizeof(L"dtb=") - 2, + ULONG_MAX, ULONG_MAX, load_addr, load_size); +} + +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); +/* + * This function handles the architcture specific differences between arm and + * arm64 regarding where the kernel image must be loaded and any memory that + * must be reserved. On failure it is required to free all + * all allocations it has made. + */ +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); + +/* shared entrypoint between the normal stub and the zboot stub */ +efi_status_t efi_stub_common(efi_handle_t handle, + efi_loaded_image_t *image, + unsigned long image_addr, + char *cmdline_ptr); + +efi_status_t efi_handle_cmdline(efi_loaded_image_t *image, char **cmdline_ptr); + +asmlinkage void __noreturn efi_enter_kernel(unsigned long entrypoint, + unsigned long fdt_addr, + unsigned long fdt_size); + +void efi_handle_post_ebs_state(void); + +enum efi_secureboot_mode efi_get_secureboot(void); + +#ifdef CONFIG_RESET_ATTACK_MITIGATION +void efi_enable_reset_attack_mitigation(void); +#else +static inline void +efi_enable_reset_attack_mitigation(void) { } +#endif + +void efi_retrieve_eventlog(void); + +struct sysfb_display_info *alloc_primary_display(void); +struct sysfb_display_info *__alloc_primary_display(void); +void free_primary_display(struct sysfb_display_info *dpy); + +void efi_cache_sync_image(unsigned long image_base, + unsigned long alloc_size); + +struct efi_smbios_record { + u8 type; + u8 length; + u16 handle; +}; + +const struct efi_smbios_record *efi_get_smbios_record(u8 type); + +struct efi_smbios_type1_record { + struct efi_smbios_record header; + + u8 manufacturer; + u8 product_name; + u8 version; + u8 serial_number; + efi_guid_t uuid; + u8 wakeup_type; + u8 sku_number; + u8 family; +}; + +struct efi_smbios_type4_record { + struct efi_smbios_record header; + + u8 socket; + u8 processor_type; + u8 processor_family; + u8 processor_manufacturer; + u8 processor_id[8]; + u8 processor_version; + u8 voltage; + u16 external_clock; + u16 max_speed; + u16 current_speed; + u8 status; + u8 processor_upgrade; + u16 l1_cache_handle; + u16 l2_cache_handle; + u16 l3_cache_handle; + u8 serial_number; + u8 asset_tag; + u8 part_number; + u8 core_count; + u8 enabled_core_count; + u8 thread_count; + u16 processor_characteristics; + u16 processor_family2; + u16 core_count2; + u16 enabled_core_count2; + u16 thread_count2; + u16 thread_enabled; +}; + +#define efi_get_smbios_string(__record, __field) ({ \ + __typeof__(__record) __rec = __record; \ + __efi_get_smbios_string(&__rec->header, &__rec->__field); \ +}) + +const u8 *__efi_get_smbios_string(const struct efi_smbios_record *record, + const u8 *offset); + +void efi_remap_image(unsigned long image_base, unsigned alloc_size, + unsigned long code_size); +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); +u32 efi_kaslr_get_phys_seed(efi_handle_t image_handle); + +asmlinkage efi_status_t __efiapi +efi_zboot_entry(efi_handle_t handle, efi_system_table_t *systab); + +efi_status_t allocate_unaccepted_bitmap(__u32 nr_desc, + struct efi_boot_memmap *map); +void process_unaccepted_memory(u64 start, u64 end); +void accept_memory(phys_addr_t start, unsigned long size); +void arch_accept_memory(phys_addr_t start, phys_addr_t end); + +efi_status_t efi_zboot_decompress_init(unsigned long *alloc_size); +efi_status_t efi_zboot_decompress(u8 *out, unsigned long outlen); + +#endif diff --git a/drivers/firmware/efi/libstub/fdt.c b/drivers/firmware/efi/libstub/fdt.c new file mode 100644 index 000000000..23b3543d3 --- /dev/null +++ b/drivers/firmware/efi/libstub/fdt.c @@ -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 +#include +#include + +#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; +} diff --git a/drivers/firmware/efi/libstub/file.c b/drivers/firmware/efi/libstub/file.c new file mode 100644 index 000000000..bd626d55d --- /dev/null +++ b/drivers/firmware/efi/libstub/file.c @@ -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 +#include + +#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; +} diff --git a/drivers/firmware/efi/libstub/find.c b/drivers/firmware/efi/libstub/find.c new file mode 100644 index 000000000..4e7740d28 --- /dev/null +++ b/drivers/firmware/efi/libstub/find.c @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include +#include +#include + +/* + * 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); +} diff --git a/drivers/firmware/efi/libstub/gop.c b/drivers/firmware/efi/libstub/gop.c new file mode 100644 index 000000000..80dc8cfeb --- /dev/null +++ b/drivers/firmware/efi/libstub/gop.c @@ -0,0 +1,532 @@ +// SPDX-License-Identifier: GPL-2.0 +/* ----------------------------------------------------------------------- + * + * Copyright 2011 Intel Corporation; author Matt Fleming + * + * ----------------------------------------------------------------------- */ + +#include +#include +#include +#include +#include +#include +#include +#include