From a9ed3aa9b87ee41e8ab3ff471b1331c154767e45 Mon Sep 17 00:00:00 2001 From: Dave Airlie Date: Sat, 26 Sep 2026 07:57:59 +1000 Subject: Merge tag 'drm-misc-fixes-2026-09-24' of https://gitlab.freedesktop.org/drm/misc/kernel into drm-fixes A number of fixes: - bridge: - samsung-dsim: fix GPIO lifetime - client: Null pointer dereference fix - imagination: error handling fix, page handling fix - nouveau: fix reference leaks, double-frees, out-of-bounds accesses, use-after-frees, don't reject config without SCDC, a number of workarounds - virtio: fix memory leak, reference leaks, null pointer dereference, add pixel blend mode, cache coherency fix Signed-off-by: Dave Airlie From: Maxime Ripard Link: https://patch.msgid.link/arU22zzqUGDEco1y@houat --- drivers/firmware/efi/Kconfig | 345 ++++++ drivers/firmware/efi/Makefile | 46 + drivers/firmware/efi/apple-properties.c | 239 ++++ 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 | 654 ++++++++++ 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, 19717 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..f401a04ed --- /dev/null +++ b/drivers/firmware/efi/apple-properties.c @@ -0,0 +1,239 @@ +// 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 (data_len < sizeof(*properties)) { + pr_err("truncated properties header\n"); + ret = -EINVAL; + } else 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