diff options
Diffstat (limited to 'lib/efi_loader')
56 files changed, 32652 insertions, 0 deletions
diff --git a/lib/efi_loader/.gitignore b/lib/efi_loader/.gitignore new file mode 100644 index 00000000000..f2d7c144479 --- /dev/null +++ b/lib/efi_loader/.gitignore @@ -0,0 +1,3 @@ +*.efi +*.so +*.S diff --git a/lib/efi_loader/Kconfig b/lib/efi_loader/Kconfig new file mode 100644 index 00000000000..6130af14337 --- /dev/null +++ b/lib/efi_loader/Kconfig @@ -0,0 +1,594 @@ +menu "UEFI Support" + +config EFI_LOADER + bool "Support running UEFI applications" + depends on ( \ + ARM && (SYS_CPU = arm1136 || \ + SYS_CPU = arm1176 || \ + SYS_CPU = armv7 || \ + SYS_CPU = armv8) || \ + X86 || RISCV || SANDBOX) + # We have not fully removed the requirement for some block device + depends on BLK + # We need EFI_STUB_64BIT to be set on x86_64 with EFI_STUB + depends on !EFI_STUB || !X86_64 || EFI_STUB_64BIT + # We need EFI_STUB_32BIT to be set on x86_32 with EFI_STUB + depends on !EFI_STUB || !X86 || X86_64 || EFI_STUB_32BIT + depends on !EFI_APP + default y if !ARM || SYS_CPU = armv7 || SYS_CPU = armv8 + select CHARSET + # We need to send DM events, dynamically, in the EFI block driver + select DM_EVENT + select EVENT_DYNAMIC + select LIB_UUID + select LMB + select OF_LIBFDT + imply PARTITION_UUIDS + select REGEX + imply FAT + imply FAT_WRITE + imply FAT_RENAME + imply USB_KEYBOARD_FN_KEYS + imply VIDEO_ANSI + help + Select this option if you want to run UEFI applications (like GNU + GRUB or iPXE) on top of U-Boot. If this option is enabled, U-Boot + will expose the UEFI API to a loaded application, enabling it to + reuse U-Boot's device drivers. + +if EFI_LOADER + +config EFI_BINARY_EXEC + bool "Execute UEFI binary" + default y + help + Select this option if you want to execute the UEFI binary after + loading it with U-Boot load commands or other methods. + You may enable CMD_BOOTEFI_BINARY so that you can use bootefi + command to do that. + +config EFI_SECURE_BOOT + bool "Enable EFI secure boot support" + depends on EFI_LOADER && FIT_SIGNATURE + select HASH + select SHA256 + select RSA + select RSA_VERIFY_WITH_PKEY + select IMAGE_SIGN_INFO + select ASYMMETRIC_KEY_TYPE + select ASYMMETRIC_PUBLIC_KEY_SUBTYPE + select X509_CERTIFICATE_PARSER + select PKCS7_MESSAGE_PARSER + select PKCS7_VERIFY + select MSCODE_PARSER + select EFI_SIGNATURE_SUPPORT + help + Select this option to enable EFI secure boot support. + Once SecureBoot mode is enforced, any EFI binary can run only if + it is signed with a trusted key. To do that, you need to install, + at least, PK, KEK and db. + +config EFI_SIGNATURE_SUPPORT + bool + +menu "UEFI services" + +config EFI_GET_TIME + bool "GetTime() runtime service" + depends on DM_RTC + default y + help + Provide the GetTime() runtime service at boottime. This service + can be used by an EFI application to read the real time clock. + +config EFI_SET_TIME + bool "SetTime() runtime service" + depends on EFI_GET_TIME + default y if ARCH_QEMU || SANDBOX + help + Provide the SetTime() runtime service at boottime. This service + can be used by an EFI application to adjust the real time clock. + +config EFI_HAVE_RUNTIME_RESET + # bool "Reset runtime service is available" + bool + default y + depends on ARCH_BCM283X || FSL_LAYERSCAPE || PSCI_RESET || \ + SANDBOX || SYSRESET_SBI || SYSRESET_X86 + +endmenu + +menu "UEFI Variables" + +choice + prompt "Store for non-volatile UEFI variables" + default EFI_VARIABLE_FILE_STORE + help + Select where non-volatile UEFI variables shall be stored. + +config EFI_VARIABLE_FILE_STORE + bool "Store non-volatile UEFI variables as file" + depends on FAT_WRITE + help + Select this option if you want non-volatile UEFI variables to be + stored as file /ubootefi.var on the EFI system partition. + +config EFI_RT_VOLATILE_STORE + bool "Allow variable runtime services in volatile storage (e.g RAM)" + depends on EFI_VARIABLE_FILE_STORE + help + When EFI variables are stored on file we don't allow SetVariableRT, + since the OS doesn't know how to write that file. At the same time + we copy runtime variables in DRAM and support GetVariableRT + + Enable this option to allow SetVariableRT on the RAM backend of + the EFI variable storage. The OS will be responsible for syncing + the RAM contents to the file, otherwise any changes made during + runtime won't persist reboots. + Authenticated variables are not supported. Note that this will + violate the EFI spec since writing auth variables will return + EFI_INVALID_PARAMETER + +config EFI_MM_COMM_TEE + bool "UEFI variables storage service via the trusted world" + depends on OPTEE + help + Allowing access to the MM SP services (SPs such as StandAlonneMM, smm-gateway). + When using the u-boot OP-TEE driver, StandAlonneMM is supported. + When using the u-boot FF-A driver any MM SP is supported. + + If OP-TEE is present and running StandAloneMM, dispatch all UEFI + variable related operations to that. The application will verify, + authenticate and store the variables on an RPMB. + + When ARM_FFA_TRANSPORT is used, dispatch all UEFI variable related + operations to the MM SP running in the secure world. + A door bell mechanism is used to notify the SP when there is data in the shared + MM buffer. The data is copied by u-boot to the shared buffer before issuing + the door bell event. + +config FFA_SHARED_MM_BUF_SIZE + int "Memory size of the shared MM communication buffer" + depends on EFI_MM_COMM_TEE && ARM_FFA_TRANSPORT + help + This defines the size in bytes of the memory area reserved for the shared + buffer used for communication between the MM feature in U-Boot and + the MM SP in secure world. + The size of the memory region must be a multiple of the size of the maximum + translation granule size that is specified in the ID_AA64MMFR0_EL1 System register. + It is assumed that the MM SP knows the size of the shared MM communication buffer. + +config FFA_SHARED_MM_BUF_OFFSET + int "Data offset in the shared MM communication buffer" + depends on EFI_MM_COMM_TEE && ARM_FFA_TRANSPORT + help + This defines the offset in bytes of the data read or written to in the shared + buffer by the MM SP. + +config FFA_SHARED_MM_BUF_ADDR + hex "Define the address of the shared MM communication buffer" + depends on EFI_MM_COMM_TEE && ARM_FFA_TRANSPORT + help + This defines the address of the shared MM communication buffer + used for communication between the MM feature in U-Boot and + the MM SP in secure world. + It is assumed that the MM SP knows the address of the shared MM communication buffer. + +config EFI_VARIABLE_NO_STORE + bool "Don't persist non-volatile UEFI variables" + help + If you choose this option, non-volatile variables cannot be persisted. + You could still provide non-volatile variables via + EFI_VARIABLES_PRESEED. + +endchoice + +config EFI_VARIABLES_PRESEED + bool "Initial values for UEFI variables" + depends on !EFI_MM_COMM_TEE + help + Include a file with the initial values for non-volatile UEFI variables + into the U-Boot binary. If this configuration option is set, changes + to authentication related variables (PK, KEK, db, dbx) are not + allowed. + +if EFI_VARIABLES_PRESEED + +config EFI_VAR_SEED_FILE + string "File with initial values of non-volatile UEFI variables" + default "ubootefi.var" + help + File with initial values of non-volatile UEFI variables. The file must + be in the same format as the storage in the EFI system partition. The + easiest way to create it is by setting the non-volatile variables in + U-Boot. If a relative file path is used, it is relative to the source + directory. + +endif + +config EFI_VAR_BUF_SIZE + int "Memory size of the UEFI variable store" + default 131072 + range 4096 2147483647 + help + This defines the size in bytes of the memory area reserved for keeping + UEFI variables. + + When using StandAloneMM (CONFIG_EFI_MM_COMM_TEE=y) is used the + available size for storing variables is defined in + PcdFlashNvStorageVariableSize. + That value is probed at runtime from U-Boot. In that case, + EFI_VAR_BUF_SIZE represents the memory U-Boot reserves to present + runtime variables to the OS. + + Minimum 4096, default 131072 + +config EFI_PLATFORM_LANG_CODES + string "Language codes supported by firmware" + default "en-US" + help + This value is used to initialize the PlatformLangCodes variable. Its + value is a semicolon (;) separated list of language codes in native + RFC 4646 format, e.g. "en-US;de-DE". The first language code is used + to initialize the PlatformLang variable. + +endmenu + +menu "Capsule support" + +config EFI_HAVE_CAPSULE_SUPPORT + bool + +config EFI_RUNTIME_UPDATE_CAPSULE + bool "UpdateCapsule() runtime service" + select EFI_HAVE_CAPSULE_SUPPORT + help + Select this option if you want to use UpdateCapsule and + QueryCapsuleCapabilities API's. + +config EFI_CAPSULE_ON_DISK + bool "Enable capsule-on-disk support" + depends on SYSRESET + select EFI_HAVE_CAPSULE_SUPPORT + help + Select this option if you want to use capsule-on-disk feature, + that is, capsules can be fetched and executed from files + under a specific directory on UEFI system partition instead of + via UpdateCapsule API. + +config EFI_IGNORE_OSINDICATIONS + bool "Ignore OsIndications for CapsuleUpdate on-disk" + depends on EFI_CAPSULE_ON_DISK + default y if !EFI_RT_VOLATILE_STORE + help + There are boards where U-Boot does not support SetVariable at runtime. + Select this option if you want to use the capsule-on-disk feature + without setting the EFI_OS_INDICATIONS_FILE_CAPSULE_DELIVERY_SUPPORTED + flag in variable OsIndications. + +config EFI_CAPSULE_ON_DISK_EARLY + bool "Initiate capsule-on-disk at U-Boot boottime" + depends on EFI_CAPSULE_ON_DISK + help + Normally, without this option enabled, capsules will be + executed only at the first time of invoking one of efi command. + If this option is enabled, capsules will be enforced to be + executed as part of U-Boot initialisation so that they will + surely take place whatever is set to distro_bootcmd. + +config EFI_CAPSULE_NAMESPACE_GUID + string "Namespace for dynamic capsule GUIDs" + # v4 UUID as a default for upstream U-Boot boards + default "8c9f137e-91dc-427b-b2d6-b420faebaf2a" + depends on EFI_HAVE_CAPSULE_SUPPORT + help + Define the namespace or "salt" GUID used to generate the per-image + GUIDs. This should be a GUID in the standard 8-4-4-4-12 format. + + Device vendors are expected to generate their own namespace GUID + to avoid conflicts with upstream/community images. + +config EFI_CAPSULE_FIRMWARE + bool + +config EFI_CAPSULE_FIRMWARE_MANAGEMENT + bool "Capsule: Firmware Management Protocol" + depends on EFI_HAVE_CAPSULE_SUPPORT + default y + help + Select this option if you want to enable capsule-based + firmware update using Firmware Management Protocol. + +config EFI_CAPSULE_FIRMWARE_FIT + bool "FMP driver for FIT images" + depends on FIT + depends on EFI_CAPSULE_FIRMWARE_MANAGEMENT + select UPDATE_FIT + select DFU + select SET_DFU_ALT_INFO + select EFI_CAPSULE_FIRMWARE + help + Select this option if you want to enable firmware management protocol + driver for FIT image + +config EFI_CAPSULE_FIRMWARE_RAW + bool "FMP driver for raw images" + depends on EFI_CAPSULE_FIRMWARE_MANAGEMENT + depends on SANDBOX || (!SANDBOX && !EFI_CAPSULE_FIRMWARE_FIT) + select DFU_WRITE_ALT + select DFU + select SET_DFU_ALT_INFO + select EFI_CAPSULE_FIRMWARE + help + Select this option if you want to enable firmware management protocol + driver for raw image + +config EFI_CAPSULE_AUTHENTICATE + bool "Update Capsule authentication" + depends on EFI_CAPSULE_FIRMWARE + depends on EFI_CAPSULE_ON_DISK + depends on EFI_CAPSULE_FIRMWARE_MANAGEMENT + select HASH + select SHA256 + select RSA + select RSA_VERIFY + select RSA_VERIFY_WITH_PKEY + select X509_CERTIFICATE_PARSER + select PKCS7_MESSAGE_PARSER + select PKCS7_VERIFY + select IMAGE_SIGN_INFO + select EFI_SIGNATURE_SUPPORT + help + Select this option if you want to enable capsule + authentication + +config EFI_CAPSULE_MAX + int "Max value for capsule index" + default 15 + range 0 65535 + help + Select the max capsule index value used for capsule report + variables. This value is used to create CapsuleMax variable. + +config EFI_CAPSULE_CRT_FILE + string "Path to the EFI capsule public key certificate" + depends on EFI_CAPSULE_AUTHENTICATE + help + Provides the path to the EFI capsule public key certificate that + corresponds to the capsule signing key. This certificate will be used + to generate the EFI capsule ESL (signature list file) that gets + embedded in the platform's device tree and used for capsule + authentication at the time of capsule update. + +endmenu + +menu "UEFI protocol support" + +config EFI_DEVICE_PATH_TO_TEXT + bool "Device path to text protocol" + default y + help + The device path to text protocol converts device nodes and paths to + human readable strings. + +config EFI_DEVICE_PATH_UTIL + bool "Device path utilities protocol" + default y + help + The device path utilities protocol creates and manipulates device + paths and device nodes. It is required to run the EFI Shell. + +config EFI_DT_FIXUP + bool "Device tree fixup protocol" + depends on !GENERATE_ACPI_TABLE + default y + help + The EFI device-tree fix-up protocol provides a function to let the + firmware apply fix-ups. This may be used by boot loaders. + +config EFI_LOADER_HII + bool "HII protocols" + default y if !HAS_BOARD_SIZE_LIMIT + help + The Human Interface Infrastructure is a complicated framework that + allows UEFI applications to draw fancy menus and hook strings using + a translation framework. + + U-Boot implements enough of its features to be able to run the UEFI + Shell, but not more than that. + +config EFI_UNICODE_COLLATION_PROTOCOL2 + bool "Unicode collation protocol" + default y + help + The Unicode collation protocol is used for lexical comparisons. It is + required to run the UEFI shell. + +if EFI_UNICODE_COLLATION_PROTOCOL2 + +config EFI_UNICODE_CAPITALIZATION + bool "Support Unicode capitalization" + default y if !HAS_BOARD_SIZE_LIMIT + help + Select this option to enable correct handling of the capitalization of + Unicode codepoints in the range 0x0000-0xffff. If this option is not + set, only the the correct handling of the letters of the codepage + used by the FAT file system is ensured. + +endif + +config EFI_RNG_PROTOCOL + bool "EFI_RNG_PROTOCOL support" + depends on DM_RNG + default y + help + Provide a EFI_RNG_PROTOCOL implementation using the hardware random + number generator of the platform. + +config EFI_TCG2_PROTOCOL + bool "EFI_TCG2_PROTOCOL support" + default y + depends on TPM_V2 + select SHA1 + select SHA256 + select SHA384 + select SHA512 + select HASH + select SMBIOS_PARSER + help + Provide a EFI_TCG2_PROTOCOL implementation using the TPM hardware + of the platform. + +config EFI_TCG2_PROTOCOL_MEASURE_DTB + bool "Measure DTB with EFI_TCG2_PROTOCOL" + depends on EFI_TCG2_PROTOCOL + help + When enabled, the DTB image passed to the booted EFI image is + measured using the EFI TCG2 protocol. Do not enable this feature if + the passed DTB contains data that change across platform reboots + and cannot be used has a predictable measurement. Otherwise + this feature allows better measurement of the system boot + sequence. + +config EFI_LOAD_FILE2_INITRD + bool "EFI_FILE_LOAD2_PROTOCOL for Linux initial ramdisk" + default y + help + Linux v5.7 and later can make use of this option. If the boot option + selected by the UEFI boot manager specifies an existing file to be used + as initial RAM disk, a Linux specific Load File2 protocol will be + installed and Linux 5.7+ will ignore any initrd=<ramdisk> command line + argument. + +config EFI_RISCV_BOOT_PROTOCOL + bool "RISCV_EFI_BOOT_PROTOCOL support" + default y + depends on RISCV + help + The EFI_RISCV_BOOT_PROTOCOL is used to transfer the boot hart ID + to the next boot stage. It should be enabled as it is meant to + replace the transfer via the device-tree. The latter is not + possible on systems using ACPI. + +config EFI_IP4_CONFIG2_PROTOCOL + bool "EFI_IP4_CONFIG2_PROTOCOL support" + default y if ARCH_QEMU || SANDBOX + depends on NET || NET_LWIP + help + Provides an implementation of the EFI_IP4_CONFIG2_PROTOCOL, this + protocol can be used to set and get the current ip address and + other network information. + +config EFI_HTTP_PROTOCOL + bool "EFI_HTTP_PROTOCOL support" + default y if ARCH_QEMU || SANDBOX + depends on WGET + help + Provides an EFI HTTP driver implementing the EFI_HTTP_PROTOCOL. and + EFI_HTTP_SERVICE_BINDING_PROTOCOL. + +endmenu + +menu "Misc options" +config EFI_LOADER_BOUNCE_BUFFER + bool "EFI Applications use bounce buffers for DMA operations" + help + Some hardware does not support DMA to full 64bit addresses. For this + hardware we can create a bounce buffer so that payloads don't have to + worry about platform details. + +config EFI_GRUB_ARM32_WORKAROUND + bool "Workaround for GRUB on 32bit ARM" + default n if ARCH_BCM283X || ARCH_SUNXI || ARCH_QEMU + default y + depends on ARM && !ARM64 + help + GRUB prior to version 2.04 requires U-Boot to disable caches. This + workaround currently is also needed on systems with caches that + cannot be managed via CP15. + +config EFI_ESRT + bool "Enable the UEFI ESRT generation" + depends on EFI_CAPSULE_FIRMWARE_MANAGEMENT + default y + help + Enabling this option creates the ESRT UEFI system table. + +config EFI_ECPT + bool "Enable the UEFI ECPT generation" + default y + help + Enabling this option created the ECPT UEFI table. + +config EFI_EBBR_2_1_CONFORMANCE + bool "Add the EBBRv2.1 conformance entry to the ECPT table" + depends on BOOTMETH_EFI_BOOTMGR + depends on EFI_ECPT + depends on EFI_LOADER_HII + depends on EFI_RISCV_BOOT_PROTOCOL || !RISCV + depends on EFI_RNG_PROTOCOL || !DM_RNG + depends on EFI_UNICODE_COLLATION_PROTOCOL2 + default y + help + Enabling this option adds the EBBRv2.1 conformance entry to the ECPT UEFI table. + +config EFI_SCROLL_ON_CLEAR_SCREEN + bool "Avoid overwriting previous output on clear screen" + help + Instead of erasing the screen content when the console screen should + be cleared, emit blank new lines so that previous output is scrolled + out of sight rather than overwritten. On serial consoles this allows + to capture complete boot logs (except for interactive menus etc.) + and can ease debugging related issues. + +endmenu + +menu "EFI bootmanager" + +config EFI_BOOTMGR + bool "UEFI Boot Manager" + default y + help + Select this option if you want to select the UEFI binary to be booted + via UEFI variables Boot####, BootOrder, and BootNext. You should also + normally enable CMD_BOOTEFI_BOOTMGR so that the command is available. + +config EFI_HTTP_BOOT + bool "EFI HTTP Boot support" + depends on NET || NET_LWIP + select CMD_NET + select CMD_DNS + select CMD_WGET + select BLKMAP + help + Enabling this option adds EFI HTTP Boot support. It allows to + directly boot from network. +endmenu + +config BOOTEFI_HELLO_COMPILE + bool "Compile a standard EFI hello world binary for testing" + default y + help + This compiles a standard EFI hello world application with U-Boot so + that it can be used with the test/py testing framework. This is useful + for testing that EFI is working at a basic level, and for bringing + up EFI support on a new architecture. + + No additional space will be required in the resulting U-Boot binary + when this option is enabled. + +config BOOTEFI_TESTAPP_COMPILE + bool "Compile an EFI test app for testing" + default y + help + This compiles an app designed for testing. It is packed into an image + by the test.py testing frame in the setup_efi_image() function. + + No additional space will be required in the resulting U-Boot binary + when this option is enabled. + +endif + +source "lib/efi/Kconfig" + +endmenu diff --git a/lib/efi_loader/Makefile b/lib/efi_loader/Makefile new file mode 100644 index 00000000000..2a0b4172bd7 --- /dev/null +++ b/lib/efi_loader/Makefile @@ -0,0 +1,82 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# (C) Copyright 2016 Alexander Graf +# + +# This file only gets included with CONFIG_EFI_LOADER set, so all +# object inclusion implicitly depends on it + +asflags-y += -I. + +CFLAGS_efi_boottime.o += \ + -DFW_VERSION="0x$(VERSION)" \ + -DFW_PATCHLEVEL="0x$(PATCHLEVEL)" + +# These are the apps that are built +apps-$(CONFIG_RISCV) += boothart +apps-$(CONFIG_BOOTEFI_HELLO_COMPILE) += helloworld +apps-$(CONFIG_GENERATE_SMBIOS_TABLE) += smbiosdump +apps-$(CONFIG_EFI_LOAD_FILE2_INITRD) += initrddump +ifeq ($(CONFIG_GENERATE_ACPI_TABLE),) +apps-y += dtbdump +endif +apps-$(CONFIG_BOOTEFI_TESTAPP_COMPILE) += testapp + +obj-$(CONFIG_CMD_BOOTEFI_HELLO) += helloworld_efi.o +obj-$(CONFIG_EFI_BOOTMGR) += efi_bootmgr.o +obj-$(CONFIG_EFI_BINARY_EXEC) += efi_bootbin.o +obj-y += efi_boottime.o +obj-y += efi_helper.o +obj-$(CONFIG_EFI_HAVE_CAPSULE_SUPPORT) += efi_capsule.o +obj-$(CONFIG_EFI_CAPSULE_FIRMWARE) += efi_firmware.o +obj-y += efi_console.o +obj-y += efi_device_path.o +obj-$(CONFIG_EFI_DEVICE_PATH_TO_TEXT) += efi_device_path_to_text.o +obj-$(CONFIG_EFI_DEVICE_PATH_UTIL) += efi_device_path_utilities.o +obj-y += efi_dt_fixup.o +obj-y += efi_fdt.o +obj-y += efi_file.o +obj-$(CONFIG_EFI_LOADER_HII) += efi_hii.o +obj-y += efi_image_loader.o +obj-y += efi_load_options.o +obj-y += efi_memory.o +obj-y += efi_root_node.o +obj-y += efi_runtime.o +obj-y += efi_setup.o +obj-y += efi_string.o +obj-$(CONFIG_EFI_UNICODE_COLLATION_PROTOCOL2) += efi_unicode_collation.o +obj-y += efi_var_common.o +obj-y += efi_var_mem.o +ifeq ($(CONFIG_EFI_MM_COMM_TEE),y) +obj-y += efi_variable_tee.o +else +obj-y += efi_variable.o +obj-y += efi_var_file.o +obj-$(CONFIG_EFI_VARIABLES_PRESEED) += efi_var_seed.o +endif +obj-y += efi_watchdog.o +obj-$(CONFIG_EFI_ESRT) += efi_esrt.o +obj-$(CONFIG_VIDEO) += efi_gop.o +obj-$(CONFIG_BLK) += efi_disk.o +obj-$(CONFIG_NETDEVICES) += efi_net.o +obj-$(CONFIG_EFI_IP4_CONFIG2_PROTOCOL) += efi_ipconfig.o +obj-$(CONFIG_EFI_HTTP_PROTOCOL) += efi_http.o +obj-$(CONFIG_ACPI) += efi_acpi.o +obj-$(CONFIG_SMBIOS) += efi_smbios.o +obj-$(CONFIG_EFI_RNG_PROTOCOL) += efi_rng.o +obj-$(CONFIG_EFI_TCG2_PROTOCOL) += efi_tcg2.o +obj-$(CONFIG_EFI_RISCV_BOOT_PROTOCOL) += efi_riscv.o +obj-$(CONFIG_EFI_LOAD_FILE2_INITRD) += efi_load_initrd.o +obj-$(CONFIG_EFI_SIGNATURE_SUPPORT) += efi_signature.o +obj-$(CONFIG_EFI_ECPT) += efi_conformance.o + +EFI_VAR_SEED_FILE := $(subst $\",,$(CONFIG_EFI_VAR_SEED_FILE)) +$(obj)/efi_var_seed.o: $(srctree)/$(EFI_VAR_SEED_FILE) + +# Set the C flags to add and remove for each app +$(foreach f,$(apps-y),\ + $(eval CFLAGS_$(f).o := $(CFLAGS_EFI) -Os -ffreestanding)\ + $(eval CFLAGS_REMOVE_$(f).o := $(CFLAGS_NON_EFI))) + +always += $(foreach f,$(apps-y),$(f).efi) +targets += $(foreach f,$(apps-y),$(f).o) diff --git a/lib/efi_loader/boothart.c b/lib/efi_loader/boothart.c new file mode 100644 index 00000000000..df176ee4814 --- /dev/null +++ b/lib/efi_loader/boothart.c @@ -0,0 +1,335 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Check RISC-V boot hart ID + * + * Copyright 2022, Heinrich Schuchardt <xypron.glpk@gmx.de> + * + * This test program reads the boot HART ID both from the device-tree from the + * RISCV_EFI_BOOT_PROTOCOL and writes both values to the console. + */ + +#include <efi_api.h> +#include <efi_riscv.h> +#include <linux/libfdt.h> + +static const efi_guid_t riscv_efi_boot_protocol_guid = + RISCV_EFI_BOOT_PROTOCOL_GUID; +static const efi_guid_t fdt_guid = EFI_FDT_GUID; + +static struct efi_system_table *systable; +static struct efi_boot_services *boottime; +static struct efi_simple_text_output_protocol *con_out; +static const char *fdt; + +/** + * Print an unsigned 32bit value as decimal number to an u16 string + * + * @value: value to be printed + * @buf: pointer to buffer address + */ +static void uint2dec(u32 value, u16 *buf) +{ + u16 *pos = buf; + int i; + u16 c; + u64 f; + + /* + * Increment by .5 and multiply with + * (2 << 60) / 1,000,000,000 = 0x44B82FA0.9B5A52CC + * to move the first digit to bit 60-63. + */ + f = 0x225C17D0; + f += (0x9B5A52DULL * value) >> 28; + f += 0x44B82FA0ULL * value; + + for (i = 0; i < 10; ++i) { + /* Write current digit */ + c = f >> 60; + if (c || pos != buf) + *pos++ = c + '0'; + /* Eliminate current digit */ + f &= 0xfffffffffffffff; + /* Get next digit */ + f *= 0xaULL; + } + if (pos == buf) + *pos++ = '0'; + *pos = 0; +} + +/** + * f2h() - convert FDT value to host endianness. + * + * UEFI code is always low endian. The FDT is big endian. + * + * @val: FDT value + * Return: converted value + */ +static uint32_t f2h(fdt32_t val) +{ + char *buf = (char *)&val; + char i; + + /* Swap the bytes */ + i = buf[0]; buf[0] = buf[3]; buf[3] = i; + i = buf[1]; buf[1] = buf[2]; buf[2] = i; + + return val; +} + +/** + * memcomp() - compare two memory buffers + * + * s1: first buffer + * s2: second buffer + * n: size of buffers + * Return: 0 if both buffers have the same content + */ +static int memcomp(const void *s1, const void *s2, size_t n) +{ + const char *pos1 = s1, *pos2 = s2; + + for (size_t count = 0; count < n ; ++pos1, ++pos2, --count) { + if (*pos1 != *pos2) + return *pos1 - *pos2; + } + return 0; +} + +/** + * strcomp() - compare to strings + * + * @buf1: first string + * @buf2: second string + * Return: 0 if both strings are the same + */ +static int strcomp(const char *buf1, const char *buf2) +{ + for (; *buf1 || *buf2; ++buf1, ++buf2) { + if (*buf1 != *buf2) + return *buf1 - *buf2; + } + return 0; +} + +/** + * get_property() - return value of a property of an FDT node + * + * A property of the root node or one of its direct children can be + * retrieved. + * + * @property name of the property + * @node name of the node or NULL for root node + * Return: value of the property + */ +static char *get_property(const char *property, const char *node) +{ + struct fdt_header *header = (struct fdt_header *)fdt; + const fdt32_t *end; + const fdt32_t *pos; + const char *strings; + size_t level = 0; + const char *nodelabel = NULL; + + if (!header) { + con_out->output_string(con_out, u"Missing device tree\r\n"); + return NULL; + } + + if (f2h(header->magic) != FDT_MAGIC) { + con_out->output_string(con_out, u"Wrong device tree magic\r\n"); + return NULL; + } + + pos = (fdt32_t *)(fdt + f2h(header->off_dt_struct)); + end = &pos[f2h(header->totalsize) >> 2]; + strings = fdt + f2h(header->off_dt_strings); + + for (; pos < end;) { + switch (f2h(pos[0])) { + case FDT_BEGIN_NODE: { + const char *c = (char *)&pos[1]; + size_t i; + + if (level == 1) + nodelabel = c; + ++level; + for (i = 0; c[i]; ++i) + ; + pos = &pos[2 + (i >> 2)]; + break; + } + case FDT_PROP: { + struct fdt_property *prop = (struct fdt_property *)pos; + const char *label = &strings[f2h(prop->nameoff)]; + efi_status_t ret; + + /* Check if this is the property to be returned */ + if (!strcomp(property, label) && + ((level == 1 && !node) || + (level == 2 && node && + !strcomp(node, nodelabel)))) { + char *str; + efi_uintn_t len = f2h(prop->len); + + if (!len) + return NULL; + /* + * The string might not be 0 terminated. + * It is safer to make a copy. + */ + ret = boottime->allocate_pool( + EFI_LOADER_DATA, len + 1, + (void **)&str); + if (ret != EFI_SUCCESS) { + con_out->output_string( + con_out, + u"AllocatePool failed\r\n"); + return NULL; + } + boottime->copy_mem(str, &pos[3], len); + str[len] = 0; + + return str; + } + + pos = &pos[3 + ((f2h(prop->len) + 3) >> 2)]; + break; + } + case FDT_NOP: + ++pos; + break; + case FDT_END_NODE: + --level; + ++pos; + break; + case FDT_END: + return NULL; + default: + con_out->output_string( + con_out, u"Invalid device tree token\r\n"); + return NULL; + } + } + con_out->output_string( + con_out, u"Missing FDT_END token\r\n"); + return NULL; +} + +/** + * get_config_table() - get configuration table + * + * @guid: table GUID + * Return: pointer to table or NULL + */ +static void *get_config_table(const efi_guid_t *guid) +{ + size_t i; + + for (i = 0; i < systable->nr_tables; i++) { + if (!memcomp(guid, &systable->tables[i].guid, 16)) + return systable->tables[i].table; + } + return NULL; +} + +/** + * fdt_get_hart() - get hart ID via RISC-V device-tree + * + * @hartid: boot hart ID + * Return: status code + */ +static efi_status_t fdt_get_hart(efi_uintn_t *hartid) +{ + char *str; + + fdt = get_config_table(&fdt_guid); + if (!fdt) { + con_out->output_string(con_out, u"Missing device tree\r\n"); + return EFI_NOT_FOUND; + } + + str = get_property("boot-hartid", "chosen"); + if (!str) { + con_out->output_string(con_out, + u"/chosen/boot-hartid missing\r\n"); + return EFI_NOT_FOUND; + } + *hartid = f2h(*(fdt32_t *)str); + boottime->free_pool(str); + + return EFI_SUCCESS; +} + +/** + * prot_get_hart() - get hart ID via RISC-V Boot Protocol + * + * @hartid: boot hart ID + * Return: status code + */ +static efi_status_t prot_get_hart(efi_uintn_t *hartid) +{ + efi_status_t ret; + struct riscv_efi_boot_protocol *prot; + + /* Get RISC-V boot protocol */ + ret = boottime->locate_protocol(&riscv_efi_boot_protocol_guid, NULL, + (void **)&prot); + if (ret != EFI_SUCCESS) { + con_out->output_string( + con_out, u"RISC-V Boot Protocol not available\r\n"); + return ret; + } + + /* Get boot hart ID from EFI protocol */ + ret = prot->get_boot_hartid(prot, hartid); + if (ret != EFI_SUCCESS) + con_out->output_string(con_out, + u"Could not retrieve boot hart ID\r\n"); + return ret; +} + +/** + * efi_main() - entry point of the EFI application. + * + * @handle: handle of the loaded image + * @systab: system table + * Return: status code + */ +efi_status_t EFIAPI efi_main(efi_handle_t handle, + struct efi_system_table *systab) +{ + efi_status_t ret; + efi_uintn_t hartid; + u16 buf[16]; + + systable = systab; + boottime = systable->boottime; + con_out = systable->con_out; + + con_out->output_string(con_out, + u"\r\nBoot hart ID\r\n------------\r\n\r\n"); + + ret = fdt_get_hart(&hartid); + if (ret == EFI_SUCCESS) { + con_out->output_string(con_out, u"Device-tree: "); + uint2dec(hartid, buf); + con_out->output_string(con_out, buf); + con_out->output_string(con_out, u"\r\n"); + } + + ret = prot_get_hart(&hartid); + if (ret == EFI_SUCCESS) { + con_out->output_string(con_out, u"RISCV_EFI_BOOT_PROTOCOL: "); + uint2dec(hartid, buf); + con_out->output_string(con_out, buf); + con_out->output_string(con_out, u"\r\n"); + } + + con_out->output_string(con_out, u"\r\n"); + boottime->exit(handle, EFI_SUCCESS, 0, NULL); + + /* We should never arrive here */ + return EFI_SUCCESS; +} diff --git a/lib/efi_loader/capsule_esl.dtsi.in b/lib/efi_loader/capsule_esl.dtsi.in new file mode 100644 index 00000000000..bc7db836faa --- /dev/null +++ b/lib/efi_loader/capsule_esl.dtsi.in @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Devicetree file with the public key EFI Signature List(ESL) + * node. This file is used to generate the dtsi file to be + * included into the DTB. + */ +/ { + signature { + capsule-key = /incbin/("ESL_BIN_FILE"); + }; +}; diff --git a/lib/efi_loader/dtbdump.c b/lib/efi_loader/dtbdump.c new file mode 100644 index 00000000000..a3d59a2fd3b --- /dev/null +++ b/lib/efi_loader/dtbdump.c @@ -0,0 +1,799 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2020, Heinrich Schuchardt <xypron.glpk@gmx.de> + * + * dtbdump.efi saves the device tree provided as a configuration table + * to a file. + */ + +#include <efi_api.h> +#include <efi_dt_fixup.h> +#include <part.h> +#include <linux/libfdt.h> + +#define BUFFER_SIZE 64 +#define ESC 0x17 + +#define efi_size_in_pages(size) ((size + EFI_PAGE_MASK) >> EFI_PAGE_SHIFT) + +static struct efi_simple_text_output_protocol *cerr; +static struct efi_simple_text_output_protocol *cout; +static struct efi_simple_text_input_protocol *cin; +static struct efi_boot_services *bs; +static const efi_guid_t fdt_guid = EFI_FDT_GUID; +static const efi_guid_t loaded_image_guid = EFI_LOADED_IMAGE_PROTOCOL_GUID; +static const efi_guid_t guid_simple_file_system_protocol = + EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID; +static efi_handle_t handle; +static struct efi_system_table *systable; +static const efi_guid_t efi_dt_fixup_protocol_guid = EFI_DT_FIXUP_PROTOCOL_GUID; +static const efi_guid_t efi_file_info_guid = EFI_FILE_INFO_GUID; +static const efi_guid_t efi_system_partition_guid = PARTITION_SYSTEM_GUID; + +/** + * print() - print string + * + * @string: text + */ +static void print(u16 *string) +{ + cout->output_string(cout, string); +} + +/** + * print_char() - print character + * + * 0x00 is replaced by '", "'. + * + * @c: - character + */ +static void print_char(unsigned char c) +{ + u16 out[2] = u"?"; + + if (!c) { + print(u"\", \""); + return; + } + + if (c > 0x1f && c < 0x80) + out[0] = c; + + print(out); +} + +/** + * print_hex_digit() - print hexadecimal digit + * + * @digit: digit to print + */ +static void print_hex_digit(unsigned char digit) +{ + if (digit < 10) + digit += '0'; + else + digit += 'a' - 10; + print_char(digit); +} + +/** + * printx() - print hexadecimal byte + * + * @val: value to print + */ +static void printx(unsigned char val) +{ + print_hex_digit(val >> 4); + print_hex_digit(val & 0xf); +} + +/** + * error() - print error string + * + * @string: error text + */ +static void error(u16 *string) +{ + cout->set_attribute(cout, EFI_LIGHTRED | EFI_BACKGROUND_BLACK); + print(string); + cout->set_attribute(cout, EFI_LIGHTBLUE | EFI_BACKGROUND_BLACK); +} + +/** + * efi_input_yn() - get answer to yes/no question + * + * Return: + * y or Y + * EFI_SUCCESS + * n or N + * EFI_ACCESS_DENIED + * ESC + * EFI_ABORTED + */ +static efi_status_t efi_input_yn(void) +{ + struct efi_input_key key = {0}; + efi_uintn_t index; + efi_status_t ret; + + /* Drain the console input */ + ret = cin->reset(cin, true); + for (;;) { + ret = bs->wait_for_event(1, &cin->wait_for_key, &index); + if (ret != EFI_SUCCESS) + continue; + ret = cin->read_key_stroke(cin, &key); + if (ret != EFI_SUCCESS) + continue; + switch (key.scan_code) { + case 0x17: /* Escape */ + return EFI_ABORTED; + default: + break; + } + /* Convert to lower case */ + switch (key.unicode_char | 0x20) { + case 'y': + return EFI_SUCCESS; + case 'n': + return EFI_ACCESS_DENIED; + default: + break; + } + } +} + +/** + * efi_input() - read string from console + * + * @buffer: input buffer + * @buffer_size: buffer size + * Return: status code + */ +static efi_status_t efi_input(u16 *buffer, efi_uintn_t buffer_size) +{ + struct efi_input_key key = {0}; + efi_uintn_t index; + efi_uintn_t pos = 0; + u16 outbuf[2] = u" "; + efi_status_t ret; + + /* Drain the console input */ + ret = cin->reset(cin, true); + *buffer = 0; + for (;;) { + ret = bs->wait_for_event(1, &cin->wait_for_key, &index); + if (ret != EFI_SUCCESS) + continue; + ret = cin->read_key_stroke(cin, &key); + if (ret != EFI_SUCCESS) + continue; + switch (key.scan_code) { + case 0x17: /* Escape */ + print(u"\r\nAborted\r\n"); + return EFI_ABORTED; + default: + break; + } + switch (key.unicode_char) { + case 0x08: /* Backspace */ + if (pos) { + buffer[pos--] = 0; + print(u"\b \b"); + } + break; + case 0x0a: /* Linefeed */ + case 0x0d: /* Carriage return */ + print(u"\r\n"); + return EFI_SUCCESS; + default: + break; + } + /* Ignore surrogate codes */ + if (key.unicode_char >= 0xD800 && key.unicode_char <= 0xDBFF) + continue; + if (key.unicode_char >= 0x20 && + pos < buffer_size - 1) { + *outbuf = key.unicode_char; + buffer[pos++] = key.unicode_char; + buffer[pos] = 0; + print(outbuf); + } + } +} + +/* + * Convert FDT value to host endianness. + * + * @val FDT value + * Return: converted value + */ +static u32 f2h(fdt32_t val) +{ + char *buf = (char *)&val; + char i; + + /* Swap the bytes */ + i = buf[0]; buf[0] = buf[3]; buf[3] = i; + i = buf[1]; buf[1] = buf[2]; buf[2] = i; + return *(u32 *)buf; +} + +/** + * get_dtb() - get device tree + * + * @systable: system table + * Return: device tree or NULL + */ +static void *get_dtb(struct efi_system_table *systable) +{ + void *dtb = NULL; + efi_uintn_t i; + + for (i = 0; i < systable->nr_tables; ++i) { + if (!memcmp(&systable->tables[i].guid, &fdt_guid, + sizeof(efi_guid_t))) { + dtb = systable->tables[i].table; + break; + } + } + return dtb; +} + +/** + * skip_whitespace() - skip over leading whitespace + * + * @pos: UTF-16 string + * Return: pointer to first non-whitespace + */ +static u16 *skip_whitespace(u16 *pos) +{ + for (; *pos && *pos <= 0x20; ++pos) + ; + return pos; +} + +/** + * starts_with() - check if @string starts with @keyword + * + * @string: string to search for keyword + * @keyword: keyword to be searched + * Return: true fi @string starts with the keyword + */ +static bool starts_with(u16 *string, u16 *keyword) +{ + for (; *keyword; ++string, ++keyword) { + if (*string != *keyword) + return false; + } + return true; +} + +/** + * do_help() - print help + */ +static void do_help(void) +{ + error(u"dump - print device-tree\r\n"); + error(u"load <dtb> - load device-tree from file\r\n"); + error(u"save <dtb> - save device-tree to file\r\n"); + error(u"exit - exit the shell\r\n"); +} + +/** + * open_file_system() - open simple file system protocol + * + * file_system: interface of the simple file system protocol + * Return: status code + */ +static efi_status_t +open_file_system(struct efi_simple_file_system_protocol **file_system) +{ + struct efi_loaded_image *loaded_image; + efi_status_t ret; + efi_handle_t *handle_buffer = NULL; + efi_uintn_t count; + + ret = bs->open_protocol(handle, &loaded_image_guid, + (void **)&loaded_image, NULL, NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if (ret != EFI_SUCCESS) { + error(u"Loaded image protocol not found\r\n"); + return ret; + } + + /* Open the simple file system protocol on the same partition */ + ret = bs->open_protocol(loaded_image->device_handle, + &guid_simple_file_system_protocol, + (void **)file_system, NULL, NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if (ret == EFI_SUCCESS) + return ret; + + /* Open the simple file system protocol on the UEFI system partition */ + ret = bs->locate_handle_buffer(BY_PROTOCOL, &efi_system_partition_guid, + NULL, &count, &handle_buffer); + if (ret == EFI_SUCCESS && handle_buffer) + ret = bs->open_protocol(handle_buffer[0], + &guid_simple_file_system_protocol, + (void **)file_system, NULL, NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if (ret != EFI_SUCCESS) + error(u"Failed to open simple file system protocol\r\n"); + if (handle) + bs->free_pool(handle_buffer); + + return ret; +} + +/** + * do_load() - load and install device-tree + * + * @filename: file name + * Return: status code + */ +static efi_status_t do_load(u16 *filename) +{ + struct efi_dt_fixup_protocol *dt_fixup_prot; + struct efi_simple_file_system_protocol *file_system; + struct efi_file_handle *root = NULL, *file = NULL; + u64 addr = 0; + struct efi_file_info *info; + struct fdt_header *dtb; + efi_uintn_t buffer_size; + efi_uintn_t pages; + efi_status_t ret, ret2; + + ret = bs->locate_protocol(&efi_dt_fixup_protocol_guid, NULL, + (void **)&dt_fixup_prot); + if (ret != EFI_SUCCESS) { + error(u"Device-tree fix-up protocol not found\r\n"); + return ret; + } + + filename = skip_whitespace(filename); + + ret = open_file_system(&file_system); + if (ret != EFI_SUCCESS) + goto out; + + /* Open volume */ + ret = file_system->open_volume(file_system, &root); + if (ret != EFI_SUCCESS) { + error(u"Failed to open volume\r\n"); + goto out; + } + + /* Open file */ + ret = root->open(root, &file, filename, EFI_FILE_MODE_READ, 0); + if (ret != EFI_SUCCESS) { + error(u"File not found\r\n"); + goto out; + } + /* Get file size */ + buffer_size = 0; + ret = file->getinfo(file, &efi_file_info_guid, &buffer_size, NULL); + if (ret != EFI_BUFFER_TOO_SMALL) { + error(u"Can't get file info size\r\n"); + goto out; + } + ret = bs->allocate_pool(EFI_LOADER_DATA, buffer_size, (void **)&info); + if (ret != EFI_SUCCESS) { + error(u"Out of memory\r\n"); + goto out; + } + ret = file->getinfo(file, &efi_file_info_guid, &buffer_size, info); + if (ret != EFI_SUCCESS) { + error(u"Can't get file info\r\n"); + goto out; + } + buffer_size = info->file_size; + pages = efi_size_in_pages(buffer_size); + ret = bs->free_pool(info); + if (ret != EFI_SUCCESS) + error(u"Can't free memory pool\r\n"); + /* Read file */ + ret = bs->allocate_pages(EFI_ALLOCATE_ANY_PAGES, + EFI_ACPI_RECLAIM_MEMORY, + pages, &addr); + if (ret != EFI_SUCCESS) { + error(u"Out of memory\r\n"); + goto out; + } + dtb = (struct fdt_header *)(uintptr_t)addr; + ret = file->read(file, &buffer_size, dtb); + if (ret != EFI_SUCCESS) { + error(u"Can't read file\r\n"); + goto out; + } + /* Fixup file, expecting EFI_BUFFER_TOO_SMALL */ + ret = dt_fixup_prot->fixup(dt_fixup_prot, dtb, &buffer_size, + EFI_DT_APPLY_FIXUPS | EFI_DT_RESERVE_MEMORY | + EFI_DT_INSTALL_TABLE); + if (ret == EFI_BUFFER_TOO_SMALL) { + /* Read file into larger buffer */ + ret = bs->free_pages(addr, pages); + if (ret != EFI_SUCCESS) + error(u"Can't free memory pages\r\n"); + pages = efi_size_in_pages(buffer_size); + ret = bs->allocate_pages(EFI_ALLOCATE_ANY_PAGES, + EFI_ACPI_RECLAIM_MEMORY, + pages, &addr); + if (ret != EFI_SUCCESS) { + error(u"Out of memory\r\n"); + goto out; + } + dtb = (struct fdt_header *)(uintptr_t)addr; + ret = file->setpos(file, 0); + if (ret != EFI_SUCCESS) { + error(u"Can't position file\r\n"); + goto out; + } + ret = file->read(file, &buffer_size, dtb); + if (ret != EFI_SUCCESS) { + error(u"Can't read file\r\n"); + goto out; + } + buffer_size = pages << EFI_PAGE_SHIFT; + ret = dt_fixup_prot->fixup( + dt_fixup_prot, dtb, &buffer_size, + EFI_DT_APPLY_FIXUPS | EFI_DT_RESERVE_MEMORY | + EFI_DT_INSTALL_TABLE); + } + if (ret == EFI_SUCCESS) + print(u"device-tree installed\r\n"); + else + error(u"Device-tree fix-up failed\r\n"); +out: + if (addr) { + ret2 = bs->free_pages(addr, pages); + if (ret2 != EFI_SUCCESS) + error(u"Can't free memory pages\r\n"); + } + if (file) { + ret2 = file->close(file); + if (ret2 != EFI_SUCCESS) + error(u"Can't close file\r\n"); + } + if (root) { + ret2 = root->close(root); + if (ret2 != EFI_SUCCESS) + error(u"Can't close volume\r\n"); + } + return ret; +} + +/** + * do_save() - save current device-tree + * + * @filename: file name + * Return: status code + */ +static efi_status_t do_save(u16 *filename) +{ + struct efi_simple_file_system_protocol *file_system; + efi_uintn_t dtb_size; + struct efi_file_handle *root, *file; + struct fdt_header *dtb; + efi_uintn_t ret; + + dtb = get_dtb(systable); + if (!dtb) { + error(u"DTB not found\r\n"); + return EFI_NOT_FOUND; + } + if (f2h(dtb->magic) != FDT_MAGIC) { + error(u"Wrong device tree magic\r\n"); + return EFI_NOT_FOUND; + } + dtb_size = f2h(dtb->totalsize); + + filename = skip_whitespace(filename); + + ret = open_file_system(&file_system); + if (ret != EFI_SUCCESS) + return ret; + + /* Open volume */ + ret = file_system->open_volume(file_system, &root); + if (ret != EFI_SUCCESS) { + error(u"Failed to open volume\r\n"); + return ret; + } + /* Check if file already exists */ + ret = root->open(root, &file, filename, EFI_FILE_MODE_READ, 0); + if (ret == EFI_SUCCESS) { + file->close(file); + print(u"Overwrite existing file (y/n)? "); + ret = efi_input_yn(); + print(u"\r\n"); + if (ret != EFI_SUCCESS) { + root->close(root); + error(u"Aborted by user\r\n"); + return ret; + } + } + + /* Create file */ + ret = root->open(root, &file, filename, + EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | + EFI_FILE_MODE_CREATE, EFI_FILE_ARCHIVE); + if (ret == EFI_SUCCESS) { + /* Write file */ + ret = file->write(file, &dtb_size, dtb); + if (ret != EFI_SUCCESS) + error(u"Failed to write file\r\n"); + file->close(file); + } else { + error(u"Failed to open file\r\n"); + } + root->close(root); + + if (ret == EFI_SUCCESS) { + print(filename); + print(u" written\r\n"); + } + + return ret; +} + +/** + * indent() - print a number of tabstops + * + * @level: indentation level + */ +static void indent(u32 level) +{ + for (; level; --level) + print(u"\t"); +} + +/** + * is_string_value() - determine if property is a string + * + * If a property is a string, an x-string, or a u32 cannot be deducted + * from the device-tree. Therefore a heuristic is used. + * + * @str: pointer to device-tree property + * @len: length of the device-tree property + * Return: 1 for string, 0 otherwise + */ +static int is_string_value(const unsigned char *str, u32 len) +{ + int nonzero_flag = 0; + + /* Zero length or not ending with 0x00 */ + if (!len || str[len - 1]) + return 0; + + for (u32 i = 0; i < len; ++i) { + if (!str[i]) { + /* Zero length string or two consecutive 0x00 */ + if (!nonzero_flag) + return 0; + + nonzero_flag = 0; + + continue; + } + /* Non-printable */ + if (str[i] < 0x20 || str[i] >= 0x80) + return 0; + + nonzero_flag = 1; + } + + return 1; +} + +/** + * print_property() - print device-tree property + * + * If a property is a string, an x-string, or a u32 cannot be deducted + * from the device-tree. Therefore a heuristic is used. + * + * @str: property value + * @len: length of property value + */ +static void print_property(const unsigned char *val, u32 len) +{ + if (is_string_value(val, len)) { + /* string */ + print(u"\""); + for (int i = 0; i < len - 1; ++i) + print_char(val[i]); + print(u"\""); + } else if (len & 0x3) { + /* byte string */ + print(u"["); + for (int i = 0; i < len; ++i) { + if (i) + print(u" "); + printx(val[i]); + } + print(u"]\""); + } else { + /* cell list */ + print(u"<"); + for (u32 i = 0; i < len; ++i) { + if ((i & 0x3) == 0) { + if (i > 0) + print(u" "); + print(u"0x"); + } + printx(val[i]); + } + print(u">"); + } +} + +/** + * print_mem_res_block() - print memory reservation block + * + * @rsvblk: memory reservation block + */ +static void print_mem_res_block(const struct fdt_reserve_entry *rsvblk) +{ + for (; rsvblk->address || rsvblk->size; ++rsvblk) { + const unsigned char *val; + + print(u"/memreserve/ 0x"); + val = (const unsigned char *)&rsvblk->address; + for (u32 i = 0; i < sizeof(u64); ++i) + printx(val[i]); + print(u" 0x"); + val = (const unsigned char *)&rsvblk->size; + for (u32 i = 0; i < sizeof(u64); ++i) + printx(val[i]); + print(u";\r\n"); + } +} + +/** + * do_dump() - print device-tree + */ +static efi_status_t do_dump(void) +{ + const unsigned char *fdt; + struct fdt_header *header; + const u32 *end; + const u32 *pos; + const char *strings; + u32 level = 0; + + fdt = get_dtb(systable); + if (!fdt) { + error(u"DTB not found\r\n"); + return EFI_NOT_FOUND; + } + + header = (struct fdt_header *)fdt; + if (f2h(header->magic) != FDT_MAGIC) { + error(u"Wrong device tree magic\r\n"); + error(u"Not a device-tree\r\n"); + return EFI_LOAD_ERROR; + } + + pos = (u32 *)(fdt + f2h(header->off_dt_struct)); + end = &pos[f2h(header->totalsize) >> 2]; + strings = fdt + f2h(header->off_dt_strings); + + print(u"/dts-v1/;\r\n"); + + print_mem_res_block((const struct fdt_reserve_entry *) + (fdt + f2h(header->off_mem_rsvmap))); + + print(u"/"); + for (; pos < end;) { + switch (f2h(pos[0])) { + case FDT_BEGIN_NODE: { + const char *c = (char *)&pos[1]; + size_t i; + + indent(level); + for (i = 0; c[i]; ++i) + print_char(c[i]); + print(u" {\n\r"); + + ++level; + pos = &pos[2 + (i >> 2)]; + break; + } + case FDT_PROP: { + struct fdt_property *prop = (struct fdt_property *)pos; + const unsigned char *label = &strings[f2h(prop->nameoff)]; + u32 len = f2h(prop->len); + const unsigned char *str = (unsigned char *)&pos[3]; + + indent(level); + for (int i = 0; label[i]; ++i) + print_char(label[i]); + + if (len) { + print(u" = "); + print_property(str, len); + } + print(u";\r\n"); + + pos = &pos[3 + ((f2h(prop->len) + 3) >> 2)]; + break; + } + case FDT_NOP: + ++pos; + break; + case FDT_END_NODE: + if (!level) { + error(u"Extraneous end node\r\n"); + return EFI_LOAD_ERROR; + } + + --level; + indent(level); + print(u"};\n\r"); + ++pos; + break; + case FDT_END: + if (level) { + error(u"Missing end node\r\n"); + return EFI_LOAD_ERROR; + } + return EFI_SUCCESS; + default: + error(u"Invalid device tree token\r\n"); + return EFI_LOAD_ERROR; + } + } + error(u"Overrun\r\n"); + + return EFI_LOAD_ERROR; +} + +/** + * efi_main() - entry point of the EFI application. + * + * @handle: handle of the loaded image + * @systab: system table + * Return: status code + */ +efi_status_t EFIAPI efi_main(efi_handle_t image_handle, + struct efi_system_table *systab) +{ + handle = image_handle; + systable = systab; + cerr = systable->std_err; + cout = systable->con_out; + cin = systable->con_in; + bs = systable->boottime; + + cout->set_attribute(cout, EFI_LIGHTBLUE | EFI_BACKGROUND_BLACK); + cout->clear_screen(cout); + cout->set_attribute(cout, EFI_WHITE | EFI_BACKGROUND_BLACK); + print(u"DTB Dump\r\n========\r\n\r\n"); + cout->set_attribute(cout, EFI_LIGHTBLUE | EFI_BACKGROUND_BLACK); + + for (;;) { + u16 command[BUFFER_SIZE]; + u16 *pos; + efi_uintn_t ret; + + print(u"=> "); + ret = efi_input(command, sizeof(command)); + if (ret == EFI_ABORTED) + break; + pos = skip_whitespace(command); + if (starts_with(pos, u"exit")) + break; + else if (starts_with(pos, u"dump")) + do_dump(); + else if (starts_with(pos, u"load ")) + do_load(pos + 5); + else if (starts_with(pos, u"save ")) + do_save(pos + 5); + else + do_help(); + } + + cout->set_attribute(cout, EFI_LIGHTGRAY | EFI_BACKGROUND_BLACK); + cout->clear_screen(cout); + return EFI_SUCCESS; +} diff --git a/lib/efi_loader/efi_acpi.c b/lib/efi_loader/efi_acpi.c new file mode 100644 index 00000000000..4422b31ac6a --- /dev/null +++ b/lib/efi_loader/efi_acpi.c @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI application ACPI tables support + * + * Copyright (C) 2018, Bin Meng <bmeng.cn@gmail.com> + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <efi_loader.h> +#include <log.h> +#include <mapmem.h> +#include <acpi/acpi_table.h> +#include <asm/global_data.h> + +DECLARE_GLOBAL_DATA_PTR; + +static const efi_guid_t acpi_guid = EFI_ACPI_TABLE_GUID; + +/* + * Install the ACPI table as a configuration table. + * + * Return: status code + */ +efi_status_t efi_acpi_register(void) +{ + ulong addr, start, end; + efi_status_t ret; + + /* + * The bloblist is already marked reserved. For now, we don't bother + * marking it with EFI_ACPI_RECLAIM_MEMORY since we would need to cut a + * hole in the EFI_BOOT_SERVICES_CODE region added by + * add_u_boot_and_runtime(). At some point that function could create a + * more detailed map. + */ + if (IS_ENABLED(CONFIG_BLOBLIST_TABLES)) + return EFI_SUCCESS; + + /* Mark space used for tables */ + start = ALIGN_DOWN(gd->arch.table_start, EFI_PAGE_MASK); + end = ALIGN(gd->arch.table_end, EFI_PAGE_MASK); + ret = efi_add_memory_map(start, end - start, EFI_ACPI_RECLAIM_MEMORY); + if (ret != EFI_SUCCESS) + return ret; + if (gd->arch.table_start_high) { + start = ALIGN_DOWN(gd->arch.table_start_high, EFI_PAGE_MASK); + end = ALIGN(gd->arch.table_end_high, EFI_PAGE_MASK); + ret = efi_add_memory_map(start, end - start, + EFI_ACPI_RECLAIM_MEMORY); + if (ret != EFI_SUCCESS) + return ret; + } + + addr = gd_acpi_start(); + log_debug("EFI using ACPI tables at %lx\n", addr); + + /* And expose them to our EFI payload */ + return efi_install_configuration_table(&acpi_guid, + (void *)(ulong)addr); +} diff --git a/lib/efi_loader/efi_bootbin.c b/lib/efi_loader/efi_bootbin.c new file mode 100644 index 00000000000..deafb2ce1c2 --- /dev/null +++ b/lib/efi_loader/efi_bootbin.c @@ -0,0 +1,361 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * For the code moved from cmd/bootefi.c + * Copyright (c) 2016 Alexander Graf + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <bootflow.h> +#include <charset.h> +#include <dm.h> +#include <efi.h> +#include <efi_loader.h> +#include <env.h> +#include <image.h> +#include <log.h> +#include <malloc.h> +#include <mapmem.h> +#include <net.h> + +static struct efi_device_path *bootefi_image_path; +static struct efi_device_path *bootefi_device_path; +static void *image_addr; +static size_t image_size; + +/** + * efi_get_image_parameters() - return image parameters + * + * @img_addr: address of loaded image in memory + * @img_size: size of loaded image + */ +void efi_get_image_parameters(void **img_addr, size_t *img_size) +{ + *img_addr = image_addr; + *img_size = image_size; +} + +/** + * efi_clear_bootdev() - clear boot device + */ +void efi_clear_bootdev(void) +{ + efi_free_pool(bootefi_device_path); + efi_free_pool(bootefi_image_path); + bootefi_device_path = NULL; + bootefi_image_path = NULL; + image_addr = NULL; + image_size = 0; +} + +/** + * calculate_paths() - Calculate the device and image patch from strings + * + * @dev: device, e.g. "MMC" + * @devnr: number of the device, e.g. "1:2" + * @path: path to file loaded + * @device_pathp: returns EFI device path + * @image_pathp: returns EFI image path + * Return: EFI_SUCCESS on success, else error code + */ +static efi_status_t calculate_paths(const char *dev, const char *devnr, + const char *path, + struct efi_device_path **device_pathp, + struct efi_device_path **image_pathp) +{ + struct efi_device_path *image, *device; + efi_status_t ret; + +#if IS_ENABLED(CONFIG_NETDEVICES) + if (!strcmp(dev, "Net") || !strcmp(dev, "Http")) { + ret = efi_net_new_dp(dev, devnr, eth_get_dev()); + if (ret != EFI_SUCCESS) + return ret; + } +#endif + + ret = efi_dp_from_name(dev, devnr, path, &device, &image); + if (ret != EFI_SUCCESS) + return ret; + + *device_pathp = device; + if (image) { + /* FIXME: image should not contain device */ + struct efi_device_path *image_tmp = image; + + efi_dp_split_file_path(image, &device, &image); + efi_free_pool(image_tmp); + } + *image_pathp = image; + log_debug("- boot device %pD\n", device); + if (image) + log_debug("- image %pD\n", image); + + return EFI_SUCCESS; +} + +/** + * efi_set_bootdev() - set boot device + * + * This function is called when a file is loaded, e.g. via the 'load' command. + * We use the path to this file to inform the UEFI binary about the boot device. + * + * For a valid image, it sets: + * - image_addr to the provided buffer + * - image_size to the provided buffer_size + * - bootefi_device_path to the EFI device-path + * - bootefi_image_path to the EFI image-path + * + * @dev: device, e.g. "MMC" + * @devnr: number of the device, e.g. "1:2" + * @path: path to file loaded + * @buffer: buffer with file loaded + * @buffer_size: size of file loaded + */ +void efi_set_bootdev(const char *dev, const char *devnr, const char *path, + void *buffer, size_t buffer_size) +{ + efi_status_t ret; + + log_debug("dev=%s, devnr=%s, path=%s, buffer=%p, size=%zx\n", dev, + devnr, path, buffer, buffer_size); + + /* Forget overwritten image */ + if (buffer + buffer_size >= image_addr && + image_addr + image_size >= buffer) + efi_clear_bootdev(); + + /* Remember only PE-COFF and FIT images */ + if (efi_check_pe(buffer, buffer_size, NULL) != EFI_SUCCESS) { + if (IS_ENABLED(CONFIG_FIT) && + !fit_check_format(buffer, IMAGE_SIZE_INVAL)) { + /* + * FIT images of type EFI_OS are started via command + * bootm. We should not use their boot device with the + * bootefi command. + */ + buffer = 0; + buffer_size = 0; + } else { + log_debug("- not remembering image\n"); + return; + } + } + + /* efi_set_bootdev() is typically called repeatedly, recover memory */ + efi_clear_bootdev(); + + image_addr = buffer; + image_size = buffer_size; + + ret = calculate_paths(dev, devnr, path, &bootefi_device_path, + &bootefi_image_path); + if (ret) { + log_debug("- efi_dp_from_name() failed, err=%lx\n", ret); + efi_clear_bootdev(); + } +} + +/** + * efi_run_image() - run loaded UEFI image + * + * @source_buffer: memory address of the UEFI image + * @source_size: size of the UEFI image + * @dp_dev: EFI device-path + * @dp_img: EFI image-path + * Return: status code + */ +static efi_status_t efi_run_image(void *source_buffer, efi_uintn_t source_size, + struct efi_device_path *dp_dev, + struct efi_device_path *dp_img) +{ + efi_handle_t handle; + struct efi_device_path *msg_path, *file_path; + efi_status_t ret; + u16 *load_options; + + file_path = efi_dp_concat(dp_dev, dp_img, 0); + msg_path = dp_img; + + log_info("Booting %pD\n", msg_path); + + ret = EFI_CALL(efi_load_image(false, efi_root, file_path, source_buffer, + source_size, &handle)); + if (ret != EFI_SUCCESS) { + log_err("Loading image failed\n"); + goto out; + } + + /* Transfer environment variable as load options */ + ret = efi_env_set_load_options(handle, "bootargs", &load_options); + if (ret != EFI_SUCCESS) + goto out; + + ret = do_bootefi_exec(handle, load_options); + +out: + + return ret; +} + +/** + * efi_binary_run_dp() - run loaded UEFI image + * + * @image: memory address of the UEFI image + * @size: size of the UEFI image + * @fdt: device-tree + * @dp_dev: EFI device-path + * @dp_img: EFI image-path + * + * Execute an EFI binary image loaded at @image. + * @size may be zero if the binary is loaded with U-Boot load command. + * + * Return: status code + */ +static efi_status_t efi_binary_run_dp(void *image, size_t size, void *fdt, + struct efi_device_path *dp_dev, + struct efi_device_path *dp_img) +{ + efi_status_t ret; + + /* Initialize EFI drivers */ + ret = efi_init_obj_list(); + if (ret != EFI_SUCCESS) { + log_err("Error: Cannot initialize UEFI sub-system, r = %lu\n", + ret & ~EFI_ERROR_MASK); + return -1; + } + + ret = efi_install_fdt(fdt); + if (ret != EFI_SUCCESS) + return ret; + + return efi_run_image(image, size, dp_dev, dp_img); +} + +/** + * efi_binary_run() - run loaded UEFI image + * + * @image: memory address of the UEFI image + * @size: size of the UEFI image + * @fdt: device-tree + * + * Execute an EFI binary image loaded at @image. + * @size may be zero if the binary is loaded with U-Boot load command. + * + * Return: status code + */ +efi_status_t efi_binary_run(void *image, size_t size, void *fdt) +{ + efi_handle_t mem_handle = NULL; + struct efi_device_path *file_path = NULL; + efi_status_t ret; + + if (!bootefi_device_path || !bootefi_image_path) { + log_debug("Not loaded from disk\n"); + /* + * Special case for efi payload not loaded from disk, + * such as 'bootefi hello' or for example payload + * loaded directly into memory via JTAG, etc: + */ + file_path = efi_dp_from_mem(EFI_RESERVED_MEMORY_TYPE, + (uintptr_t)image, size); + /* + * Make sure that device for device_path exist + * in load_image(). Otherwise, shell and grub will fail. + */ + ret = efi_install_multiple_protocol_interfaces(&mem_handle, + &efi_guid_device_path, + file_path, NULL); + if (ret != EFI_SUCCESS) + goto out; + } else { + log_debug("Loaded from disk\n"); + } + + ret = efi_binary_run_dp(image, size, fdt, bootefi_device_path, + bootefi_image_path); +out: + if (mem_handle) { + efi_status_t r; + + r = efi_uninstall_multiple_protocol_interfaces(mem_handle, + &efi_guid_device_path, file_path, NULL); + if (r != EFI_SUCCESS) + log_err("Uninstalling protocol interfaces failed\n"); + } + efi_free_pool(file_path); + + return ret; +} + +/** + * calc_dev_name() - Calculate the device name to give to EFI + * + * If not supported, this shows an error. + * + * Return name, or NULL if not supported + */ +static const char *calc_dev_name(struct bootflow *bflow) +{ + const struct udevice *media_dev; + + media_dev = dev_get_parent(bflow->dev); + + if (!bflow->blk) { + if (device_get_uclass_id(media_dev) == UCLASS_ETH) + return "Net"; + + log_err("Cannot boot EFI app on media '%s'\n", + dev_get_uclass_name(media_dev)); + + return NULL; + } + + if (device_get_uclass_id(media_dev) == UCLASS_MASS_STORAGE) + return "usb"; + + return blk_get_uclass_name(device_get_uclass_id(media_dev)); +} + +efi_status_t efi_bootflow_run(struct bootflow *bflow) +{ + struct efi_device_path *device, *image; + const struct udevice *media_dev; + struct blk_desc *desc = NULL; + const char *dev_name; + char devnum_str[9]; + efi_status_t ret; + void *fdt; + + media_dev = dev_get_parent(bflow->dev); + if (bflow->blk) { + desc = dev_get_uclass_plat(bflow->blk); + + snprintf(devnum_str, sizeof(devnum_str), "%x:%x", + desc ? desc->devnum : dev_seq(media_dev), bflow->part); + } else { + *devnum_str = '\0'; + } + + dev_name = calc_dev_name(bflow); + log_debug("dev_name '%s' devnum_str '%s' fname '%s' media_dev '%s'\n", + dev_name, devnum_str, bflow->fname, media_dev->name); + if (!dev_name) + return EFI_UNSUPPORTED; + ret = calculate_paths(dev_name, devnum_str, bflow->fname, &device, + &image); + if (ret) + return EFI_UNSUPPORTED; + + if (bflow->flags & BOOTFLOWF_USE_BUILTIN_FDT) { + log_debug("Booting with built-in fdt\n"); + fdt = EFI_FDT_USE_INTERNAL; + } else { + log_debug("Booting with external fdt\n"); + fdt = map_sysmem(bflow->fdt_addr, 0); + } + ret = efi_binary_run_dp(bflow->buf, bflow->size, fdt, device, image); + + return ret; +} diff --git a/lib/efi_loader/efi_bootmgr.c b/lib/efi_loader/efi_bootmgr.c new file mode 100644 index 00000000000..f9534ef85ed --- /dev/null +++ b/lib/efi_loader/efi_bootmgr.c @@ -0,0 +1,1329 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI boot manager + * + * Copyright (c) 2017 Rob Clark + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <blk.h> +#include <blkmap.h> +#include <charset.h> +#include <dm.h> +#include <efi.h> +#include <log.h> +#include <malloc.h> +#include <net.h> +#include <efi_loader.h> +#include <efi_variable.h> +#include <asm/unaligned.h> +#include <linux/kernel.h> +#include <linux/sizes.h> + +static const struct efi_boot_services *bs; +static const struct efi_runtime_services *rs; + +/** + * struct uridp_context - uri device path resource + * + * @image_size: image size + * @image_addr: image address + * @loaded_dp: pointer to loaded device path + * @ramdisk_blk_dev: pointer to the ramdisk blk device + * @mem_handle: efi_handle to the loaded PE-COFF image + */ +struct uridp_context { + ulong image_size; + ulong image_addr; + struct efi_device_path *loaded_dp; + struct udevice *ramdisk_blk_dev; + efi_handle_t mem_handle; +}; + +const efi_guid_t efi_guid_bootmenu_auto_generated = + EFICONFIG_AUTO_GENERATED_ENTRY_GUID; + +/* + * bootmgr implements the logic of trying to find a payload to boot + * based on the BootOrder + BootXXXX variables, and then loading it. + * + * TODO detecting a special key held (f9?) and displaying a boot menu + * like you would get on a PC would be clever. + * + * TODO if we had a way to write and persist variables after the OS + * has started, we'd also want to check OsIndications to see if we + * should do normal or recovery boot. + */ + +/** + * expand_media_path() - expand a device path for default file name + * @device_path: device path to check against + * + * If @device_path is a media or disk partition which houses a file + * system, this function returns a full device path which contains + * an architecture-specific default file name for removable media. + * + * Return: a newly allocated device path + */ +static +struct efi_device_path *expand_media_path(struct efi_device_path *device_path) +{ + struct efi_device_path *rem, *full_path; + efi_handle_t handle; + + if (!device_path) + return NULL; + + /* + * If device_path is a (removable) media or partition which provides + * simple file system protocol, append a default file name to support + * booting from removable media. + */ + handle = efi_dp_find_obj(device_path, + &efi_simple_file_system_protocol_guid, &rem); + if (handle) { + if (rem->type == DEVICE_PATH_TYPE_END) { + char fname[30]; + + snprintf(fname, sizeof(fname), "/EFI/BOOT/%s", + efi_get_basename()); + full_path = efi_dp_from_file(device_path, fname); + + } else { + full_path = efi_dp_dup(device_path); + } + } else { + full_path = efi_dp_dup(device_path); + } + + return full_path; +} + +/** + * try_load_from_file_path() - try to load a file + * + * Given a file media path iterate through a list of handles and try to + * to load the file from each of them until the first success. + * + * @fs_handles: array of handles with the simple file protocol + * @num: number of handles in fs_handles + * @fp: file path to open + * @handle: on return pointer to handle for loaded image + * @removable: if true only consider removable media, else only non-removable + */ +static efi_status_t try_load_from_file_path(efi_handle_t *fs_handles, + efi_uintn_t num, + struct efi_device_path *fp, + efi_handle_t *handle, + bool removable) +{ + struct efi_handler *handler; + struct efi_device_path *dp; + int i; + efi_status_t ret; + + for (i = 0; i < num; i++) { + if (removable != efi_disk_is_removable(fs_handles[i])) + continue; + + ret = efi_search_protocol(fs_handles[i], &efi_guid_device_path, + &handler); + if (ret != EFI_SUCCESS) + continue; + + dp = handler->protocol_interface; + if (!dp) + continue; + + dp = efi_dp_concat(dp, fp, 0); + if (!dp) + continue; + + ret = EFI_CALL(efi_load_image(true, efi_root, dp, NULL, 0, + handle)); + efi_free_pool(dp); + if (ret == EFI_SUCCESS) + return ret; + } + + return EFI_NOT_FOUND; +} + +/** + * try_load_from_short_path + * @fp: file path + * @handle: pointer to handle for newly installed image + * + * Enumerate all the devices which support file system operations, + * prepend its media device path to the file path, @fp, and + * try to load the file. + * This function should be called when handling a short-form path + * which is starting with a file device path. + * + * Return: status code + */ +static efi_status_t try_load_from_short_path(struct efi_device_path *fp, + efi_handle_t *handle) +{ + efi_handle_t *fs_handles; + efi_uintn_t num; + efi_status_t ret; + + ret = EFI_CALL(efi_locate_handle_buffer( + BY_PROTOCOL, + &efi_simple_file_system_protocol_guid, + NULL, + &num, &fs_handles)); + if (ret != EFI_SUCCESS) + return ret; + if (!num) + return EFI_NOT_FOUND; + + /* removable media first */ + ret = try_load_from_file_path(fs_handles, num, fp, handle, true); + if (ret == EFI_SUCCESS) + goto out; + + /* fixed media */ + ret = try_load_from_file_path(fs_handles, num, fp, handle, false); + if (ret == EFI_SUCCESS) + goto out; + +out: + return ret; +} + +/** + * mount_image() - mount the image with blkmap + * + * @lo_label: u16 label string of load option + * @addr: image address + * @size: image size + * Return: pointer to the UCLASS_BLK udevice, NULL if failed + */ +static struct udevice *mount_image(u16 *lo_label, ulong addr, ulong size) +{ + int err; + struct blkmap *bm; + struct udevice *bm_dev; + char *label = NULL, *p; + + label = efi_alloc(utf16_utf8_strlen(lo_label) + 1); + if (!label) + return NULL; + + p = label; + utf16_utf8_strcpy(&p, lo_label); + err = blkmap_create_ramdisk(label, addr, size, &bm_dev); + if (err) { + efi_free_pool(label); + return NULL; + } + bm = dev_get_plat(bm_dev); + + efi_free_pool(label); + + return bm->blk; +} + +/** + * search_default_file() - search default file + * + * @dev: pointer to the UCLASS_BLK or UCLASS_PARTITION udevice + * @loaded_dp: pointer to default file device path + * Return: status code + */ +static efi_status_t search_default_file(struct udevice *dev, + struct efi_device_path **loaded_dp) +{ + efi_status_t ret; + efi_handle_t handle; + u16 *default_file_name = NULL; + struct efi_file_handle *root, *f; + struct efi_device_path *dp = NULL, *fp = NULL; + struct efi_simple_file_system_protocol *file_system; + struct efi_device_path *device_path, *full_path = NULL; + + if (dev_tag_get_ptr(dev, DM_TAG_EFI, (void **)&handle)) { + log_warning("DM_TAG_EFI not found\n"); + return EFI_INVALID_PARAMETER; + } + + ret = EFI_CALL(bs->open_protocol(handle, &efi_guid_device_path, + (void **)&device_path, efi_root, NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL)); + if (ret != EFI_SUCCESS) + return ret; + + ret = EFI_CALL(bs->open_protocol(handle, &efi_simple_file_system_protocol_guid, + (void **)&file_system, efi_root, NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL)); + if (ret != EFI_SUCCESS) + return ret; + + ret = EFI_CALL(file_system->open_volume(file_system, &root)); + if (ret != EFI_SUCCESS) + return ret; + + full_path = expand_media_path(device_path); + ret = efi_dp_split_file_path(full_path, &dp, &fp); + if (ret != EFI_SUCCESS) + goto err; + + default_file_name = efi_dp_str(fp); + efi_free_pool(dp); + efi_free_pool(fp); + if (!default_file_name) { + ret = EFI_OUT_OF_RESOURCES; + goto err; + } + + ret = EFI_CALL(root->open(root, &f, default_file_name, + EFI_FILE_MODE_READ, 0)); + efi_free_pool(default_file_name); + if (ret != EFI_SUCCESS) + goto err; + + EFI_CALL(f->close(f)); + EFI_CALL(root->close(root)); + + *loaded_dp = full_path; + + return EFI_SUCCESS; + +err: + EFI_CALL(root->close(root)); + efi_free_pool(full_path); + + return ret; +} + +/** + * fill_default_file_path() - get fallback boot device path for block device + * + * Provide the device path to the fallback UEFI boot file, e.g. + * EFI/BOOT/BOOTAA64.EFI if that file exists on the block device @blk. + * + * @blk: pointer to the UCLASS_BLK udevice + * @dp: pointer to store the fallback boot device path + * Return: status code + */ +static efi_status_t fill_default_file_path(struct udevice *blk, + struct efi_device_path **dp) +{ + efi_status_t ret; + struct udevice *partition; + + /* image that has no partition table but a file system */ + ret = search_default_file(blk, dp); + if (ret == EFI_SUCCESS) + return ret; + + /* try the partitions */ + device_foreach_child(partition, blk) { + enum uclass_id id; + + id = device_get_uclass_id(partition); + if (id != UCLASS_PARTITION) + continue; + + ret = search_default_file(partition, dp); + if (ret == EFI_SUCCESS) + return ret; + } + + return EFI_NOT_FOUND; +} + +/** + * prepare_loaded_image() - prepare ramdisk for downloaded image + * + * @label: label of load option + * @addr: image address + * @size: image size + * @dp: pointer to default file device path + * @blk: pointer to created blk udevice + * Return: status code + */ +static efi_status_t prepare_loaded_image(u16 *label, ulong addr, ulong size, + struct efi_device_path **dp, + struct udevice **blk) +{ + u64 pages; + efi_status_t ret; + struct udevice *ramdisk_blk; + + ramdisk_blk = mount_image(label, addr, size); + if (!ramdisk_blk) + return EFI_LOAD_ERROR; + + ret = fill_default_file_path(ramdisk_blk, dp); + if (ret != EFI_SUCCESS) { + log_info("Cannot boot from downloaded image\n"); + goto err; + } + + /* + * Linux supports 'pmem' which allows OS installers to find, reclaim + * the mounted images and continue the installation since the contents + * of the pmem region are treated as local media. + * + * The memory regions used for it needs to be carved out of the EFI + * memory map. + */ + pages = efi_size_in_pages(size + (addr & EFI_PAGE_MASK)); + ret = efi_update_memory_map(addr, pages, EFI_CONVENTIONAL_MEMORY, + false, true); + if (ret != EFI_SUCCESS) { + log_err("Failed to reserve memory\n"); + goto err; + } + + *blk = ramdisk_blk; + + return EFI_SUCCESS; + +err: + if (blkmap_destroy(ramdisk_blk->parent)) + log_err("Destroying blkmap failed\n"); + + return ret; +} + +/** + * efi_bootmgr_release_uridp() - cleanup uri device path resource + * + * @ctx: event context + * Return: status code + */ +static efi_status_t efi_bootmgr_release_uridp(struct uridp_context *ctx) +{ + efi_status_t ret = EFI_SUCCESS; + efi_status_t ret2 = EFI_SUCCESS; + + if (!ctx) + return ret; + + /* cleanup for iso or img image */ + if (ctx->ramdisk_blk_dev) { + ret = efi_add_memory_map(ctx->image_addr, ctx->image_size, + EFI_CONVENTIONAL_MEMORY); + if (ret != EFI_SUCCESS) + log_err("Reclaiming memory failed\n"); + + if (blkmap_destroy(ctx->ramdisk_blk_dev->parent)) { + log_err("Destroying blkmap failed\n"); + ret = EFI_DEVICE_ERROR; + } + } + + /* cleanup for PE-COFF image */ + if (ctx->mem_handle) { + ret2 = efi_uninstall_multiple_protocol_interfaces(ctx->mem_handle, + &efi_guid_device_path, + ctx->loaded_dp, + NULL); + if (ret2 != EFI_SUCCESS) + log_err("Uninstall device_path protocol failed\n"); + } + + efi_free_pool(ctx->loaded_dp); + free(ctx); + + return ret == EFI_SUCCESS ? ret2 : ret; +} + +/** + * efi_bootmgr_http_return() - return to efibootmgr callback + * + * @event: the event for which this notification function is registered + * @context: event context + */ +static void EFIAPI efi_bootmgr_http_return(struct efi_event *event, + void *context) +{ + efi_status_t ret; + + EFI_ENTRY("%p, %p", event, context); + ret = efi_bootmgr_release_uridp(context); + EFI_EXIT(ret); +} + +/** + * try_load_from_uri_path() - Handle the URI device path + * + * @uridp: uri device path + * @lo_label: label of load option + * @handle: pointer to handle for newly installed image + * Return: status code + */ +static efi_status_t try_load_from_uri_path(struct efi_device_path_uri *uridp, + u16 *lo_label, + efi_handle_t *handle) +{ + char *s; + int err; + int uri_len; + efi_status_t ret; + void *source_buffer; + efi_uintn_t source_size; + struct uridp_context *ctx; + struct udevice *blk = NULL; + struct efi_event *event = NULL; + efi_handle_t mem_handle = NULL; + struct efi_device_path *loaded_dp; + static ulong image_size, image_addr; + + ctx = calloc(1, sizeof(struct uridp_context)); + if (!ctx) + return EFI_OUT_OF_RESOURCES; + + s = env_get("loadaddr"); + if (!s) { + log_err("Error: loadaddr is not set\n"); + ret = EFI_INVALID_PARAMETER; + goto err; + } + + image_addr = hextoul(s, NULL); + err = wget_do_request(image_addr, uridp->uri); + if (err < 0) { + ret = EFI_INVALID_PARAMETER; + goto err; + } + + image_size = env_get_hex("filesize", 0); + if (!image_size) { + ret = EFI_INVALID_PARAMETER; + goto err; + } + /* + * Depending on the kernel configuration, pmem memory areas must be + * page aligned or 2MiB aligned. PowerPC is an exception here and + * requires 16MiB alignment, but since we don't have EFI support for + * it, limit the alignment to 2MiB. + */ + image_size = ALIGN(image_size, SZ_2M); + + /* + * If the file extension is ".iso" or ".img", mount it and try to load + * the default file. + * If the file is PE-COFF image, load the downloaded file. + */ + uri_len = strlen(uridp->uri); + if (!strncmp(&uridp->uri[uri_len - 4], ".iso", 4) || + !strncmp(&uridp->uri[uri_len - 4], ".img", 4)) { + ret = prepare_loaded_image(lo_label, image_addr, image_size, + &loaded_dp, &blk); + if (ret != EFI_SUCCESS) + goto err; + + source_buffer = NULL; + source_size = 0; + } else if (efi_check_pe((void *)image_addr, image_size, NULL) == EFI_SUCCESS) { + /* + * loaded_dp must exist until efi application returns, + * will be freed in return_to_efibootmgr event callback. + */ + loaded_dp = efi_dp_from_mem(EFI_RESERVED_MEMORY_TYPE, + (uintptr_t)image_addr, image_size); + ret = efi_install_multiple_protocol_interfaces( + &mem_handle, &efi_guid_device_path, loaded_dp, NULL); + if (ret != EFI_SUCCESS) + goto err; + + source_buffer = (void *)image_addr; + source_size = image_size; + } else { + log_err("Error: file type is not supported\n"); + ret = EFI_UNSUPPORTED; + goto err; + } + + ctx->image_size = image_size; + ctx->image_addr = image_addr; + ctx->loaded_dp = loaded_dp; + ctx->ramdisk_blk_dev = blk; + ctx->mem_handle = mem_handle; + + ret = EFI_CALL(efi_load_image(false, efi_root, loaded_dp, source_buffer, + source_size, handle)); + if (ret != EFI_SUCCESS) + goto err; + + /* create event for cleanup when the image returns or error occurs */ + ret = efi_create_event(EVT_NOTIFY_SIGNAL, TPL_CALLBACK, + efi_bootmgr_http_return, ctx, + &efi_guid_event_group_return_to_efibootmgr, + &event); + if (ret != EFI_SUCCESS) { + log_err("Creating event failed\n"); + goto err; + } + + return ret; + +err: + efi_bootmgr_release_uridp(ctx); + + return ret; +} + +/** + * try_load_from_media() - load file from media + * + * @file_path: file path + * @handle_img: on return handle for the newly installed image + * + * If @file_path contains a file name, load the file. + * If @file_path does not have a file name, search the architecture-specific + * fallback boot file and load it. + * TODO: If the FilePathList[0] device does not support + * EFI_SIMPLE_FILE_SYSTEM_PROTOCOL but supports EFI_BLOCK_IO_PROTOCOL, + * call EFI_BOOT_SERVICES.ConnectController() + * TODO: FilePathList[0] device supports the EFI_SIMPLE_FILE_SYSTEM_PROTOCOL + * not based on EFI_BLOCK_IO_PROTOCOL + * + * Return: status code + */ +static efi_status_t try_load_from_media(struct efi_device_path *file_path, + efi_handle_t *handle_img) +{ + efi_handle_t handle_blkdev; + efi_status_t ret = EFI_SUCCESS; + struct efi_device_path *rem, *dp = NULL; + struct efi_device_path *final_dp = file_path; + + handle_blkdev = efi_dp_find_obj(file_path, &efi_block_io_guid, &rem); + if (handle_blkdev) { + if (rem->type == DEVICE_PATH_TYPE_END) { + /* no file name present, try default file */ + ret = fill_default_file_path(handle_blkdev->dev, &dp); + if (ret != EFI_SUCCESS) + return ret; + + final_dp = dp; + } + } + + ret = EFI_CALL(efi_load_image(true, efi_root, final_dp, NULL, 0, handle_img)); + + efi_free_pool(dp); + + return ret; +} + +/** + * try_load_entry() - try to load image for boot option + * + * Attempt to load load-option number 'n', returning device_path and file_path + * if successful. This checks that the EFI_LOAD_OPTION is active (enabled) + * and that the specified file to boot exists. + * + * @n: number of the boot option, e.g. 0x0a13 for Boot0A13 + * @handle: on return handle for the newly installed image + * @load_options: load options set on the loaded image protocol + * Return: status code + */ +static efi_status_t try_load_entry(u16 n, efi_handle_t *handle, + void **load_options) +{ + struct efi_load_option lo; + u16 varname[9]; + void *load_option; + efi_uintn_t size; + efi_status_t ret; + u32 attributes; + + *handle = NULL; + *load_options = NULL; + + efi_create_indexed_name(varname, sizeof(varname), "Boot", n); + load_option = efi_get_var(varname, &efi_global_variable_guid, &size); + if (!load_option) + return EFI_LOAD_ERROR; + + ret = efi_deserialize_load_option(&lo, load_option, &size); + if (ret != EFI_SUCCESS) { + log_warning("Invalid load option for %ls\n", varname); + goto error; + } + + if (!(lo.attributes & LOAD_OPTION_ACTIVE)) { + ret = EFI_LOAD_ERROR; + goto error; + } + + log_debug("trying to load \"%ls\" from %pD\n", lo.label, lo.file_path); + + if (EFI_DP_TYPE(lo.file_path, MEDIA_DEVICE, FILE_PATH)) { + /* file_path doesn't contain a device path */ + ret = try_load_from_short_path(lo.file_path, handle); + } else if (EFI_DP_TYPE(lo.file_path, MESSAGING_DEVICE, MSG_URI)) { + if (IS_ENABLED(CONFIG_EFI_HTTP_BOOT)) + ret = try_load_from_uri_path( + (struct efi_device_path_uri *)lo.file_path, + lo.label, handle); + else + ret = EFI_LOAD_ERROR; + } else { + ret = try_load_from_media(lo.file_path, handle); + } + if (ret != EFI_SUCCESS) { + log_warning("Loading %ls '%ls' failed\n", + varname, lo.label); + goto error; + } + + attributes = EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS; + ret = efi_set_variable_int(u"BootCurrent", &efi_global_variable_guid, + attributes, sizeof(n), &n, false); + if (ret != EFI_SUCCESS) + goto error; + + /* try to register load file2 for initrd's */ + if (IS_ENABLED(CONFIG_EFI_LOAD_FILE2_INITRD)) { + ret = efi_initrd_register(); + if (ret != EFI_SUCCESS) + goto error; + } + + log_info("Booting: %ls\n", lo.label); + + /* Ignore the optional data in auto-generated boot options */ + if (size >= sizeof(efi_guid_t) && + !guidcmp(lo.optional_data, &efi_guid_bootmenu_auto_generated)) + size = 0; + + /* Set optional data in loaded file protocol */ + if (size) { + *load_options = malloc(size); + if (!*load_options) { + ret = EFI_OUT_OF_RESOURCES; + goto error; + } + memcpy(*load_options, lo.optional_data, size); + ret = efi_set_load_options(*handle, size, *load_options); + if (ret != EFI_SUCCESS) + free(load_options); + } + +error: + if (ret != EFI_SUCCESS && *handle && + EFI_CALL(efi_unload_image(*handle)) != EFI_SUCCESS) + log_err("Unloading image failed\n"); + + free(load_option); + + return ret; +} + +/** + * efi_bootmgr_load() - try to load from BootNext or BootOrder + * + * Attempt to load from BootNext or in the order specified by BootOrder + * EFI variable, the available load-options, finding and returning + * the first one that can be loaded successfully. + * + * @handle: on return handle for the newly installed image + * @load_options: load options set on the loaded image protocol + * Return: status code + */ +efi_status_t efi_bootmgr_load(efi_handle_t *handle, void **load_options) +{ + u16 bootnext, *bootorder; + efi_uintn_t size; + int i, num; + efi_status_t ret; + + bs = systab.boottime; + rs = systab.runtime; + + /* BootNext */ + size = sizeof(bootnext); + ret = efi_get_variable_int(u"BootNext", + &efi_global_variable_guid, + NULL, &size, &bootnext, NULL); + if (ret == EFI_SUCCESS || ret == EFI_BUFFER_TOO_SMALL) { + /* BootNext does exist here */ + if (ret == EFI_BUFFER_TOO_SMALL || size != sizeof(u16)) + log_err("BootNext must be 16-bit integer\n"); + + /* delete BootNext */ + ret = efi_set_variable_int(u"BootNext", + &efi_global_variable_guid, + 0, 0, NULL, false); + + /* load BootNext */ + if (ret == EFI_SUCCESS) { + if (size == sizeof(u16)) { + ret = try_load_entry(bootnext, handle, + load_options); + if (ret == EFI_SUCCESS) + return ret; + log_warning( + "Loading from BootNext failed, falling back to BootOrder\n"); + } + } else { + log_err("Deleting BootNext failed\n"); + } + } + + /* BootOrder */ + bootorder = efi_get_var(u"BootOrder", &efi_global_variable_guid, &size); + if (!bootorder) { + log_info("BootOrder not defined\n"); + ret = EFI_NOT_FOUND; + goto error; + } + + num = size / sizeof(uint16_t); + for (i = 0; i < num; i++) { + log_debug("trying to load Boot%04X\n", bootorder[i]); + ret = try_load_entry(bootorder[i], handle, load_options); + if (ret == EFI_SUCCESS) + break; + } + + free(bootorder); + +error: + return ret; +} + +/** + * efi_bootmgr_enumerate_boot_options() - enumerate the possible bootable media + * + * @opt: pointer to the media boot option structure + * @index: index of the opt array to store the boot option + * @handles: pointer to block device handles + * @count: On entry number of handles to block devices. + * On exit number of boot options. + * @removable: flag to parse removable only + * Return: status code + */ +static efi_status_t +efi_bootmgr_enumerate_boot_options(struct eficonfig_media_boot_option *opt, + efi_uintn_t index, efi_handle_t *handles, + efi_uintn_t *count, bool removable) +{ + u32 i, num = index; + struct efi_handler *handler; + efi_status_t ret = EFI_SUCCESS; + + for (i = 0; i < *count; i++) { + u16 *p; + u16 dev_name[BOOTMENU_DEVICE_NAME_MAX]; + char *optional_data; + struct efi_load_option lo; + char buf[BOOTMENU_DEVICE_NAME_MAX]; + struct efi_device_path *device_path; + struct efi_device_path *short_dp; + struct efi_block_io *blkio; + + ret = efi_search_protocol(handles[i], &efi_block_io_guid, &handler); + blkio = handler->protocol_interface; + + if (blkio->media->logical_partition) + continue; + + if (removable != (blkio->media->removable_media != 0)) + continue; + + ret = efi_search_protocol(handles[i], &efi_guid_device_path, &handler); + if (ret != EFI_SUCCESS) + continue; + ret = efi_protocol_open(handler, (void **)&device_path, + efi_root, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if (ret != EFI_SUCCESS) + continue; + + ret = efi_disk_get_device_name(handles[i], buf, BOOTMENU_DEVICE_NAME_MAX); + if (ret != EFI_SUCCESS) + continue; + + p = dev_name; + utf8_utf16_strncpy(&p, buf, strlen(buf)); + + /* prefer to short form device path */ + short_dp = efi_dp_shorten(device_path); + if (short_dp) + device_path = short_dp; + + lo.label = dev_name; + lo.attributes = LOAD_OPTION_ACTIVE; + lo.file_path = device_path; + lo.file_path_length = efi_dp_size(device_path) + sizeof(END); + /* + * Set the dedicated guid to optional_data, it is used to identify + * the boot option that automatically generated by the bootmenu. + * efi_serialize_load_option() expects optional_data is null-terminated + * utf8 string, so set the "1234567" string to allocate enough space + * to store guid, instead of realloc the load_option. + */ + lo.optional_data = "1234567"; + opt[num].size = efi_serialize_load_option(&lo, (u8 **)&opt[num].lo); + if (!opt[num].size) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + /* set the guid */ + optional_data = (char *)opt[num].lo + (opt[num].size - u16_strsize(u"1234567")); + memcpy(optional_data, &efi_guid_bootmenu_auto_generated, sizeof(efi_guid_t)); + num++; + + if (num >= *count) + break; + } + +out: + *count = num; + + return ret; +} + +/** + * efi_bootmgr_delete_invalid_boot_option() - delete non-existing boot option + * + * @opt: pointer to the media boot option structure + * @count: number of media boot option structure + * Return: status code + */ +static efi_status_t efi_bootmgr_delete_invalid_boot_option(struct eficonfig_media_boot_option *opt, + efi_status_t count) +{ + efi_uintn_t size; + void *load_option; + u32 i, list_size = 0; + struct efi_load_option lo; + u16 *var_name16 = NULL; + u16 varname[] = u"Boot####"; + efi_status_t ret = EFI_SUCCESS; + u16 *delete_index_list = NULL, *p; + efi_uintn_t buf_size; + + buf_size = 128; + var_name16 = malloc(buf_size); + if (!var_name16) + return EFI_OUT_OF_RESOURCES; + + var_name16[0] = 0; + for (;;) { + int index; + efi_guid_t guid; + efi_uintn_t tmp; + + ret = efi_next_variable_name(&buf_size, &var_name16, &guid); + if (ret == EFI_NOT_FOUND) { + /* + * EFI_NOT_FOUND indicates we retrieved all EFI variables. + * This should be treated as success. + */ + ret = EFI_SUCCESS; + break; + } + + if (ret != EFI_SUCCESS) + goto out; + + if (!efi_varname_is_load_option(var_name16, &index)) + continue; + + efi_create_indexed_name(varname, sizeof(varname), "Boot", index); + load_option = efi_get_var(varname, &efi_global_variable_guid, &size); + if (!load_option) + continue; + + tmp = size; + ret = efi_deserialize_load_option(&lo, load_option, &size); + if (ret != EFI_SUCCESS) + goto next; + + if (size >= sizeof(efi_guid_bootmenu_auto_generated) && + !guidcmp(lo.optional_data, &efi_guid_bootmenu_auto_generated)) { + for (i = 0; i < count; i++) { + if (opt[i].size == tmp && + memcmp(opt[i].lo, load_option, tmp) == 0) { + opt[i].exist = true; + break; + } + } + + /* + * The entire list of variables must be retrieved by + * efi_get_next_variable_name_int() before deleting the invalid + * boot option, just save the index here. + */ + if (i == count) { + p = realloc(delete_index_list, sizeof(u32) * + (list_size + 1)); + if (!p) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + delete_index_list = p; + delete_index_list[list_size++] = index; + } + } +next: + free(load_option); + } + + /* delete all invalid boot options */ + for (i = 0; i < list_size; i++) { + ret = efi_bootmgr_delete_boot_option(delete_index_list[i]); + if (ret != EFI_SUCCESS) + goto out; + } + +out: + free(var_name16); + free(delete_index_list); + + return ret; +} + +/** + * efi_bootmgr_get_unused_bootoption() - get unused "Boot####" index + * + * @buf: pointer to the buffer to store boot option variable name + * @buf_size: buffer size + * @index: pointer to store the index in the BootOrder variable + * Return: status code + */ +efi_status_t efi_bootmgr_get_unused_bootoption(u16 *buf, efi_uintn_t buf_size, + unsigned int *index) +{ + u32 i; + efi_status_t ret; + efi_uintn_t size; + + if (buf_size < u16_strsize(u"Boot####")) + return EFI_BUFFER_TOO_SMALL; + + for (i = 0; i <= 0xFFFF; i++) { + size = 0; + efi_create_indexed_name(buf, buf_size, "Boot", i); + ret = efi_get_variable_int(buf, &efi_global_variable_guid, + NULL, &size, NULL, NULL); + if (ret == EFI_BUFFER_TOO_SMALL) + continue; + else + break; + } + + if (i > 0xFFFF) + return EFI_OUT_OF_RESOURCES; + + *index = i; + + return EFI_SUCCESS; +} + +/** + * efi_bootmgr_append_bootorder() - append new boot option in BootOrder variable + * + * @index: "Boot####" index to append to BootOrder variable + * Return: status code + */ +efi_status_t efi_bootmgr_append_bootorder(u16 index) +{ + u16 *bootorder; + efi_status_t ret; + u16 *new_bootorder = NULL; + efi_uintn_t last, size, new_size; + + /* append new boot option */ + bootorder = efi_get_var(u"BootOrder", &efi_global_variable_guid, &size); + last = size / sizeof(u16); + new_size = size + sizeof(u16); + new_bootorder = calloc(1, new_size); + if (!new_bootorder) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + memcpy(new_bootorder, bootorder, size); + new_bootorder[last] = index; + + ret = efi_set_variable_int(u"BootOrder", &efi_global_variable_guid, + EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS, + new_size, new_bootorder, false); + if (ret != EFI_SUCCESS) + goto out; + +out: + free(bootorder); + free(new_bootorder); + + return ret; +} + +/** + * efi_bootmgr_delete_boot_option() - delete selected boot option + * + * @boot_index: boot option index to delete + * Return: status code + */ +efi_status_t efi_bootmgr_delete_boot_option(u16 boot_index) +{ + u16 *bootorder; + u16 varname[9]; + efi_status_t ret; + unsigned int index; + efi_uintn_t num, size; + + efi_create_indexed_name(varname, sizeof(varname), + "Boot", boot_index); + ret = efi_set_variable_int(varname, &efi_global_variable_guid, + 0, 0, NULL, false); + if (ret != EFI_SUCCESS) { + log_err("delete boot option(%ls) failed\n", varname); + return ret; + } + + /* update BootOrder if necessary */ + bootorder = efi_get_var(u"BootOrder", &efi_global_variable_guid, &size); + if (!bootorder) + return EFI_SUCCESS; + + num = size / sizeof(u16); + if (!efi_search_bootorder(bootorder, num, boot_index, &index)) + return EFI_SUCCESS; + + memmove(&bootorder[index], &bootorder[index + 1], + (num - index - 1) * sizeof(u16)); + size -= sizeof(u16); + ret = efi_set_variable_int(u"BootOrder", &efi_global_variable_guid, + EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS, + size, bootorder, false); + + return ret; +} + +/** + * efi_bootmgr_update_media_device_boot_option() - generate the media device boot option + * + * This function enumerates all BlockIo devices and add the boot option for it. + * This function also provide the BOOT#### variable maintenance for + * the media device entries. + * - Automatically create the BOOT#### variable for the newly detected device, + * this BOOT#### variable is distinguished by the special GUID + * stored in the EFI_LOAD_OPTION.optional_data + * - If the device is not attached to the system, the associated BOOT#### variable + * is automatically deleted. + * + * Return: status code + */ +efi_status_t efi_bootmgr_update_media_device_boot_option(void) +{ + u32 i; + efi_status_t ret; + efi_uintn_t count, num, total; + efi_handle_t *handles = NULL; + struct eficonfig_media_boot_option *opt = NULL; + + ret = efi_locate_handle_buffer_int(BY_PROTOCOL, + &efi_block_io_guid, + NULL, &count, + (efi_handle_t **)&handles); + if (ret != EFI_SUCCESS) + goto out; + + opt = calloc(count, sizeof(struct eficonfig_media_boot_option)); + if (!opt) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + + /* parse removable block io followed by fixed block io */ + num = count; + ret = efi_bootmgr_enumerate_boot_options(opt, 0, handles, &num, true); + if (ret != EFI_SUCCESS) + goto out; + + total = num; + num = count; + ret = efi_bootmgr_enumerate_boot_options(opt, total, handles, &num, false); + if (ret != EFI_SUCCESS) + goto out; + + total = num; + + /* + * System hardware configuration may vary depending on the user setup. + * The boot option is automatically added by the bootmenu. + * If the device is not attached to the system, the boot option needs + * to be deleted. + */ + ret = efi_bootmgr_delete_invalid_boot_option(opt, total); + if (ret != EFI_SUCCESS) + goto out; + + /* add non-existent boot option */ + for (i = 0; i < total; i++) { + u32 boot_index; + u16 var_name[9]; + + if (!opt[i].exist) { + ret = efi_bootmgr_get_unused_bootoption(var_name, sizeof(var_name), + &boot_index); + if (ret != EFI_SUCCESS) + goto out; + + ret = efi_set_variable_int(var_name, &efi_global_variable_guid, + EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS, + opt[i].size, opt[i].lo, false); + if (ret != EFI_SUCCESS) + goto out; + + ret = efi_bootmgr_append_bootorder(boot_index); + if (ret != EFI_SUCCESS) { + efi_set_variable_int(var_name, &efi_global_variable_guid, + 0, 0, NULL, false); + goto out; + } + } + } + +out: + if (opt) { + for (i = 0; i < total; i++) + free(opt[i].lo); + } + free(opt); + efi_free_pool(handles); + + if (ret == EFI_NOT_FOUND) + return EFI_SUCCESS; + return ret; +} + +/** + * load_fdt_from_load_option - load device-tree from load option + * + * @fdt: pointer to loaded device-tree or NULL + * Return: status code + */ +static efi_status_t load_fdt_from_load_option(void **fdt) +{ + struct efi_device_path *dp = NULL; + struct efi_file_handle *f = NULL; + efi_uintn_t filesize; + efi_status_t ret; + + *fdt = NULL; + + dp = efi_get_dp_from_boot(&efi_guid_fdt); + if (!dp) + return EFI_SUCCESS; + + /* Open file */ + f = efi_file_from_path(dp); + if (!f) { + log_err("Can't find %pD specified in Boot####\n", dp); + ret = EFI_NOT_FOUND; + goto out; + } + + /* Get file size */ + ret = efi_file_size(f, &filesize); + if (ret != EFI_SUCCESS) + goto out; + + *fdt = calloc(1, filesize); + if (!*fdt) { + log_err("Out of memory\n"); + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + ret = EFI_CALL(f->read(f, &filesize, *fdt)); + if (ret != EFI_SUCCESS) { + log_err("Can't read fdt\n"); + free(*fdt); + *fdt = NULL; + } + +out: + efi_free_pool(dp); + if (f) + EFI_CALL(f->close(f)); + + return ret; +} + +/** + * efi_bootmgr_run() - execute EFI boot manager + * @fdt: Flat device tree + * + * Invoke EFI boot manager and execute a binary depending on + * boot options. If @fdt is not NULL, it will be passed to + * the executed binary. + * + * Return: status code + */ +efi_status_t efi_bootmgr_run(void *fdt) +{ + efi_handle_t handle; + void *load_options; + efi_status_t ret; + void *fdt_lo, *fdt_distro = NULL; + efi_uintn_t fdt_size; + + /* Initialize EFI drivers */ + ret = efi_init_obj_list(); + if (ret != EFI_SUCCESS) { + log_err("Error: Cannot initialize UEFI sub-system, r = %lu\n", + ret & ~EFI_ERROR_MASK); + return CMD_RET_FAILURE; + } + + ret = efi_bootmgr_load(&handle, &load_options); + if (ret != EFI_SUCCESS) { + log_notice("EFI boot manager: Cannot load any image\n"); + return ret; + } + + if (!IS_ENABLED(CONFIG_GENERATE_ACPI_TABLE)) { + ret = load_fdt_from_load_option(&fdt_lo); + if (ret != EFI_SUCCESS) + return ret; + if (fdt_lo) + fdt = fdt_lo; + if (!fdt) { + efi_load_distro_fdt(handle, &fdt_distro, &fdt_size); + fdt = fdt_distro; + } + } + + /* + * Needed in ACPI case to create reservations based on + * control device-tree. + */ + ret = efi_install_fdt(fdt); + + if (!IS_ENABLED(CONFIG_GENERATE_ACPI_TABLE)) { + free(fdt_lo); + if (fdt_distro) + efi_free_pages((uintptr_t)fdt_distro, + efi_size_in_pages(fdt_size)); + } + + if (ret != EFI_SUCCESS) { + if (EFI_CALL(efi_unload_image(handle)) == EFI_SUCCESS) + free(load_options); + else + log_err("Unloading image failed\n"); + + return ret; + } + + return do_bootefi_exec(handle, load_options); +} diff --git a/lib/efi_loader/efi_boottime.c b/lib/efi_loader/efi_boottime.c new file mode 100644 index 00000000000..c8d9a6037f7 --- /dev/null +++ b/lib/efi_loader/efi_boottime.c @@ -0,0 +1,4033 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI application boot time services + * + * Copyright (c) 2016 Alexander Graf + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <bootm.h> +#include <div64.h> +#include <dm/device.h> +#include <dm/root.h> +#include <efi_loader.h> +#include <irq_func.h> +#include <log.h> +#include <malloc.h> +#include <pe.h> +#include <time.h> +#include <u-boot/crc.h> +#include <usb.h> +#include <watchdog.h> +#include <asm/global_data.h> +#include <linux/libfdt_env.h> + +DECLARE_GLOBAL_DATA_PTR; + +/* Task priority level */ +static efi_uintn_t efi_tpl = TPL_APPLICATION; + +/* This list contains all the EFI objects our payload has access to */ +LIST_HEAD(efi_obj_list); + +/* List of all events */ +__efi_runtime_data LIST_HEAD(efi_events); + +/* List of queued events */ +static LIST_HEAD(efi_event_queue); + +/* Flag to disable timer activity in ExitBootServices() */ +static bool timers_enabled = true; + +/* Flag used by the selftest to avoid detaching devices in ExitBootServices() */ +bool efi_st_keep_devices; + +/* List of all events registered by RegisterProtocolNotify() */ +static LIST_HEAD(efi_register_notify_events); + +/* Handle of the currently executing image */ +static efi_handle_t current_image; + +#if defined(CONFIG_ARM) || defined(CONFIG_RISCV) +/* + * The "gd" pointer lives in a register on ARM and RISC-V that we declare + * fixed when compiling U-Boot. However, the payload does not know about that + * restriction so we need to manually swap its and our view of that register on + * EFI callback entry/exit. + */ +static volatile gd_t *efi_gd, *app_gd; +#endif + +efi_status_t efi_uninstall_protocol + (efi_handle_t handle, const efi_guid_t *protocol, + void *protocol_interface, bool preserve); + +/* 1 if inside U-Boot code, 0 if inside EFI payload code */ +static int entry_count = 1; +static int nesting_level; +/* GUID of the device tree table */ +const efi_guid_t efi_guid_fdt = EFI_FDT_GUID; +/* GUID of the EFI_DRIVER_BINDING_PROTOCOL */ +const efi_guid_t efi_guid_driver_binding_protocol = + EFI_DRIVER_BINDING_PROTOCOL_GUID; + +/* event group ExitBootServices() invoked */ +const efi_guid_t efi_guid_event_group_exit_boot_services = + EFI_EVENT_GROUP_EXIT_BOOT_SERVICES; +/* event group before ExitBootServices() invoked */ +const efi_guid_t efi_guid_event_group_before_exit_boot_services = + EFI_EVENT_GROUP_BEFORE_EXIT_BOOT_SERVICES; +/* event group SetVirtualAddressMap() invoked */ +const efi_guid_t efi_guid_event_group_virtual_address_change = + EFI_EVENT_GROUP_VIRTUAL_ADDRESS_CHANGE; +/* event group memory map changed */ +const efi_guid_t efi_guid_event_group_memory_map_change = + EFI_EVENT_GROUP_MEMORY_MAP_CHANGE; +/* event group boot manager about to boot */ +const efi_guid_t efi_guid_event_group_ready_to_boot = + EFI_EVENT_GROUP_READY_TO_BOOT; +/* event group ResetSystem() invoked (before ExitBootServices) */ +const efi_guid_t efi_guid_event_group_reset_system = + EFI_EVENT_GROUP_RESET_SYSTEM; +/* event group return to efibootmgr */ +const efi_guid_t efi_guid_event_group_return_to_efibootmgr = + EFI_EVENT_GROUP_RETURN_TO_EFIBOOTMGR; +/* GUIDs of the Load File and Load File2 protocols */ +const efi_guid_t efi_guid_load_file_protocol = EFI_LOAD_FILE_PROTOCOL_GUID; +const efi_guid_t efi_guid_load_file2_protocol = EFI_LOAD_FILE2_PROTOCOL_GUID; +/* GUID of the SMBIOS table */ +const efi_guid_t smbios_guid = SMBIOS_TABLE_GUID; + +efi_status_t EFIAPI efi_disconnect_controller( + efi_handle_t controller_handle, + efi_handle_t driver_image_handle, + efi_handle_t child_handle); + +efi_status_t EFIAPI efi_connect_controller(efi_handle_t controller_handle, + efi_handle_t *driver_image_handle, + struct efi_device_path *remain_device_path, + bool recursive); + +/* Called on every callback entry */ +int __efi_entry_check(void) +{ + int ret = entry_count++ == 0; +#if defined(CONFIG_ARM) || defined(CONFIG_RISCV) + assert(efi_gd); + app_gd = gd; + set_gd(efi_gd); +#endif + return ret; +} + +/* Called on every callback exit */ +int __efi_exit_check(void) +{ + int ret = --entry_count == 0; +#if defined(CONFIG_ARM) || defined(CONFIG_RISCV) + set_gd(app_gd); +#endif + return ret; +} + +/** + * efi_save_gd() - save global data register + * + * On the ARM and RISC-V architectures gd is mapped to a fixed register. + * As this register may be overwritten by an EFI payload we save it here + * and restore it on every callback entered. + * + * This function is called after relocation from initr_reloc_global_data(). + */ +void efi_save_gd(void) +{ +#if defined(CONFIG_ARM) || defined(CONFIG_RISCV) + efi_gd = gd; +#endif +} + +/** + * efi_restore_gd() - restore global data register + * + * On the ARM and RISC-V architectures gd is mapped to a fixed register. + * Restore it after returning from the UEFI world to the value saved via + * efi_save_gd(). + */ +void efi_restore_gd(void) +{ +#if defined(CONFIG_ARM) || defined(CONFIG_RISCV) + /* Only restore if we're already in EFI context */ + if (!efi_gd) + return; + set_gd(efi_gd); +#endif +} + +/** + * indent_string() - returns a string for indenting with two spaces per level + * @level: indent level + * + * A maximum of ten indent levels is supported. Higher indent levels will be + * truncated. + * + * Return: A string for indenting with two spaces per level is + * returned. + */ +static const char *indent_string(int level) +{ + const char *indent = " "; + const int max = strlen(indent); + + level = min(max, level * 2); + return &indent[max - level]; +} + +const char *__efi_nesting(void) +{ + return indent_string(nesting_level); +} + +const char *__efi_nesting_inc(void) +{ + return indent_string(nesting_level++); +} + +const char *__efi_nesting_dec(void) +{ + return indent_string(--nesting_level); +} + +/** + * efi_event_is_queued() - check if an event is queued + * + * @event: event + * Return: true if event is queued + */ +static bool efi_event_is_queued(struct efi_event *event) +{ + return !!event->queue_link.next; +} + +/** + * efi_purge_handle() - Clean the deleted handle from the various lists + * @handle: handle to remove + * + * Return: status code + */ +static efi_status_t efi_purge_handle(efi_handle_t handle) +{ + struct efi_register_notify_event *item; + + if (!list_empty(&handle->protocols)) + return EFI_ACCESS_DENIED; + /* The handle is about to be freed. Remove it from events */ + list_for_each_entry(item, &efi_register_notify_events, link) { + struct efi_protocol_notification *hitem, *hnext; + + list_for_each_entry_safe(hitem, hnext, &item->handles, link) { + if (handle == hitem->handle) { + list_del(&hitem->link); + free(hitem); + } + } + } + /* The last protocol has been removed, delete the handle. */ + list_del(&handle->link); + free(handle); + + return EFI_SUCCESS; +} + +/** + * efi_process_event_queue() - process event queue + */ +static void efi_process_event_queue(void) +{ + while (!list_empty(&efi_event_queue)) { + struct efi_event *event; + efi_uintn_t old_tpl; + + event = list_first_entry(&efi_event_queue, struct efi_event, + queue_link); + if (efi_tpl >= event->notify_tpl) + return; + list_del(&event->queue_link); + event->queue_link.next = NULL; + event->queue_link.prev = NULL; + /* Events must be executed at the event's TPL */ + old_tpl = efi_tpl; + efi_tpl = event->notify_tpl; + EFI_CALL_VOID(event->notify_function(event, + event->notify_context)); + efi_tpl = old_tpl; + if (event->type == EVT_NOTIFY_SIGNAL) + event->is_signaled = 0; + } +} + +/** + * efi_queue_event() - queue an EFI event + * @event: event to signal + * + * This function queues the notification function of the event for future + * execution. + * + */ +static void efi_queue_event(struct efi_event *event) +{ + struct efi_event *item; + + if (!event->notify_function) + return; + + if (!efi_event_is_queued(event)) { + /* + * Events must be notified in order of decreasing task priority + * level. Insert the new event accordingly. + */ + list_for_each_entry(item, &efi_event_queue, queue_link) { + if (item->notify_tpl < event->notify_tpl) { + list_add_tail(&event->queue_link, + &item->queue_link); + event = NULL; + break; + } + } + if (event) + list_add_tail(&event->queue_link, &efi_event_queue); + efi_process_event_queue(); + } +} + +/** + * is_valid_tpl() - check if the task priority level is valid + * + * @tpl: TPL level to check + * Return: status code + */ +static efi_status_t is_valid_tpl(efi_uintn_t tpl) +{ + switch (tpl) { + case TPL_APPLICATION: + case TPL_CALLBACK: + case TPL_NOTIFY: + return EFI_SUCCESS; + default: + return EFI_INVALID_PARAMETER; + } +} + +/** + * efi_signal_event() - signal an EFI event + * @event: event to signal + * + * This function signals an event. If the event belongs to an event group, all + * events of the group are signaled. If they are of type EVT_NOTIFY_SIGNAL, + * their notification function is queued. + * + * For the SignalEvent service see efi_signal_event_ext. + */ +void efi_signal_event(struct efi_event *event) +{ + if (event->is_signaled) + return; + if (event->group) { + struct efi_event *evt; + + /* + * The signaled state has to set before executing any + * notification function + */ + list_for_each_entry(evt, &efi_events, link) { + if (!evt->group || guidcmp(evt->group, event->group)) + continue; + if (evt->is_signaled) + continue; + evt->is_signaled = true; + } + list_for_each_entry(evt, &efi_events, link) { + if (!evt->group || guidcmp(evt->group, event->group)) + continue; + efi_queue_event(evt); + } + } else { + event->is_signaled = true; + efi_queue_event(event); + } +} + +/** + * efi_raise_tpl() - raise the task priority level + * @new_tpl: new value of the task priority level + * + * This function implements the RaiseTpl service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: old value of the task priority level + */ +static unsigned long EFIAPI efi_raise_tpl(efi_uintn_t new_tpl) +{ + efi_uintn_t old_tpl = efi_tpl; + + EFI_ENTRY("0x%zx", new_tpl); + + if (new_tpl < efi_tpl) + EFI_PRINT("WARNING: new_tpl < current_tpl in %s\n", __func__); + efi_tpl = new_tpl; + if (efi_tpl > TPL_HIGH_LEVEL) + efi_tpl = TPL_HIGH_LEVEL; + + EFI_EXIT(EFI_SUCCESS); + return old_tpl; +} + +/** + * efi_restore_tpl() - lower the task priority level + * @old_tpl: value of the task priority level to be restored + * + * This function implements the RestoreTpl service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + */ +static void EFIAPI efi_restore_tpl(efi_uintn_t old_tpl) +{ + EFI_ENTRY("0x%zx", old_tpl); + + if (old_tpl > efi_tpl) + EFI_PRINT("WARNING: old_tpl > current_tpl in %s\n", __func__); + efi_tpl = old_tpl; + if (efi_tpl > TPL_HIGH_LEVEL) + efi_tpl = TPL_HIGH_LEVEL; + + /* + * Lowering the TPL may have made queued events eligible for execution. + */ + efi_timer_check(); + + EFI_EXIT(EFI_SUCCESS); +} + +/** + * efi_allocate_pages_ext() - allocate memory pages + * @type: type of allocation to be performed + * @memory_type: usage type of the allocated memory + * @pages: number of pages to be allocated + * @memory: allocated memory + * + * This function implements the AllocatePages service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_allocate_pages_ext(int type, int memory_type, + efi_uintn_t pages, + uint64_t *memory) +{ + efi_status_t r; + + EFI_ENTRY("%d, %d, 0x%zx, %p", type, memory_type, pages, memory); + r = efi_allocate_pages(type, memory_type, pages, memory); + return EFI_EXIT(r); +} + +/** + * efi_free_pages_ext() - Free memory pages. + * @memory: start of the memory area to be freed + * @pages: number of pages to be freed + * + * This function implements the FreePages service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_free_pages_ext(uint64_t memory, + efi_uintn_t pages) +{ + efi_status_t r; + + EFI_ENTRY("%llx, 0x%zx", memory, pages); + r = efi_free_pages(memory, pages); + return EFI_EXIT(r); +} + +/** + * efi_get_memory_map_ext() - get map describing memory usage + * @memory_map_size: on entry the size, in bytes, of the memory map buffer, + * on exit the size of the copied memory map + * @memory_map: buffer to which the memory map is written + * @map_key: key for the memory map + * @descriptor_size: size of an individual memory descriptor + * @descriptor_version: version number of the memory descriptor structure + * + * This function implements the GetMemoryMap service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_get_memory_map_ext( + efi_uintn_t *memory_map_size, + struct efi_mem_desc *memory_map, + efi_uintn_t *map_key, + efi_uintn_t *descriptor_size, + uint32_t *descriptor_version) +{ + efi_status_t r; + + EFI_ENTRY("%p, %p, %p, %p, %p", memory_map_size, memory_map, + map_key, descriptor_size, descriptor_version); + r = efi_get_memory_map(memory_map_size, memory_map, map_key, + descriptor_size, descriptor_version); + return EFI_EXIT(r); +} + +/** + * efi_allocate_pool_ext() - allocate memory from pool + * @pool_type: type of the pool from which memory is to be allocated + * @size: number of bytes to be allocated + * @buffer: allocated memory + * + * This function implements the AllocatePool service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_allocate_pool_ext(int pool_type, + efi_uintn_t size, + void **buffer) +{ + efi_status_t r; + + EFI_ENTRY("%d, %zu, %p", pool_type, size, buffer); + r = efi_allocate_pool(pool_type, size, buffer); + return EFI_EXIT(r); +} + +/** + * efi_free_pool_ext() - free memory from pool + * @buffer: start of memory to be freed + * + * This function implements the FreePool service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_free_pool_ext(void *buffer) +{ + efi_status_t r; + + EFI_ENTRY("%p", buffer); + r = efi_free_pool(buffer); + return EFI_EXIT(r); +} + +/** + * efi_add_handle() - add a new handle to the object list + * + * @handle: handle to be added + * + * The protocols list is initialized. The handle is added to the list of known + * UEFI objects. + */ +void efi_add_handle(efi_handle_t handle) +{ + if (!handle) + return; + INIT_LIST_HEAD(&handle->protocols); + list_add_tail(&handle->link, &efi_obj_list); +} + +/** + * efi_create_handle() - create handle + * @handle: new handle + * + * Return: status code + */ +efi_status_t efi_create_handle(efi_handle_t *handle) +{ + struct efi_object *obj; + + obj = calloc(1, sizeof(struct efi_object)); + if (!obj) + return EFI_OUT_OF_RESOURCES; + + efi_add_handle(obj); + *handle = obj; + + return EFI_SUCCESS; +} + +/** + * efi_search_protocol() - find a protocol on a handle. + * @handle: handle + * @protocol_guid: GUID of the protocol + * @handler: reference to the protocol + * + * Return: status code + */ +efi_status_t efi_search_protocol(const efi_handle_t handle, + const efi_guid_t *protocol_guid, + struct efi_handler **handler) +{ + struct efi_object *efiobj; + struct list_head *lhandle; + + if (!handle || !protocol_guid) + return EFI_INVALID_PARAMETER; + efiobj = efi_search_obj(handle); + if (!efiobj) + return EFI_INVALID_PARAMETER; + list_for_each(lhandle, &efiobj->protocols) { + struct efi_handler *protocol; + + protocol = list_entry(lhandle, struct efi_handler, link); + if (!guidcmp(&protocol->guid, protocol_guid)) { + if (handler) + *handler = protocol; + return EFI_SUCCESS; + } + } + return EFI_NOT_FOUND; +} + +/** + * efi_remove_protocol() - delete protocol from a handle + * @handle: handle from which the protocol shall be deleted + * @protocol: GUID of the protocol to be deleted + * @protocol_interface: interface of the protocol implementation + * + * Return: status code + */ +static efi_status_t efi_remove_protocol(const efi_handle_t handle, + const efi_guid_t *protocol, + void *protocol_interface) +{ + struct efi_handler *handler; + efi_status_t ret; + + ret = efi_search_protocol(handle, protocol, &handler); + if (ret != EFI_SUCCESS) + return ret; + if (handler->protocol_interface != protocol_interface) + return EFI_NOT_FOUND; + list_del(&handler->link); + free(handler); + return EFI_SUCCESS; +} + +/** + * efi_remove_all_protocols() - delete all protocols from a handle + * @handle: handle from which the protocols shall be deleted + * + * Return: status code + */ +static efi_status_t efi_remove_all_protocols(const efi_handle_t handle) +{ + struct efi_object *efiobj; + struct efi_handler *protocol; + struct efi_handler *pos; + + efiobj = efi_search_obj(handle); + if (!efiobj) + return EFI_INVALID_PARAMETER; + list_for_each_entry_safe(protocol, pos, &efiobj->protocols, link) { + efi_status_t ret; + + ret = efi_uninstall_protocol(handle, &protocol->guid, + protocol->protocol_interface, true); + if (ret != EFI_SUCCESS) + return ret; + } + return EFI_SUCCESS; +} + +/** + * efi_delete_handle() - delete handle + * + * @handle: handle to delete + * + * Return: status code + */ +efi_status_t efi_delete_handle(efi_handle_t handle) +{ + efi_status_t ret; + + ret = efi_remove_all_protocols(handle); + if (ret != EFI_SUCCESS) { + log_err("Handle %p has protocols installed. Unable to delete\n", handle); + return ret; + } + + return efi_purge_handle(handle); +} + +/** + * efi_is_event() - check if a pointer is a valid event + * @event: pointer to check + * + * Return: status code + */ +static efi_status_t efi_is_event(const struct efi_event *event) +{ + const struct efi_event *evt; + + if (!event) + return EFI_INVALID_PARAMETER; + list_for_each_entry(evt, &efi_events, link) { + if (evt == event) + return EFI_SUCCESS; + } + return EFI_INVALID_PARAMETER; +} + +/** + * efi_create_event() - create an event + * + * @type: type of the event to create + * @notify_tpl: task priority level of the event + * @notify_function: notification function of the event + * @notify_context: pointer passed to the notification function + * @group: event group + * @event: created event + * + * This function is used inside U-Boot code to create an event. + * + * For the API function implementing the CreateEvent service see + * efi_create_event_ext. + * + * Return: status code + */ +efi_status_t efi_create_event(uint32_t type, efi_uintn_t notify_tpl, + void (EFIAPI *notify_function) ( + struct efi_event *event, + void *context), + void *notify_context, const efi_guid_t *group, + struct efi_event **event) +{ + struct efi_event *evt; + efi_status_t ret; + int pool_type; + + if (event == NULL) + return EFI_INVALID_PARAMETER; + + switch (type) { + case 0: + case EVT_TIMER: + case EVT_NOTIFY_SIGNAL: + case EVT_TIMER | EVT_NOTIFY_SIGNAL: + case EVT_NOTIFY_WAIT: + case EVT_TIMER | EVT_NOTIFY_WAIT: + case EVT_SIGNAL_EXIT_BOOT_SERVICES: + pool_type = EFI_BOOT_SERVICES_DATA; + break; + case EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE: + pool_type = EFI_RUNTIME_SERVICES_DATA; + break; + default: + return EFI_INVALID_PARAMETER; + } + + /* + * The UEFI specification requires event notification levels to be + * > TPL_APPLICATION and <= TPL_HIGH_LEVEL. + * + * Parameter NotifyTpl should not be checked if it is not used. + */ + if ((type & (EVT_NOTIFY_WAIT | EVT_NOTIFY_SIGNAL)) && + (!notify_function || is_valid_tpl(notify_tpl) != EFI_SUCCESS || + notify_tpl == TPL_APPLICATION)) + return EFI_INVALID_PARAMETER; + + ret = efi_allocate_pool(pool_type, sizeof(struct efi_event), + (void **)&evt); + if (ret != EFI_SUCCESS) + return ret; + memset(evt, 0, sizeof(struct efi_event)); + evt->type = type; + evt->notify_tpl = notify_tpl; + evt->notify_function = notify_function; + evt->notify_context = notify_context; + evt->group = group; + /* Disable timers on boot up */ + evt->trigger_next = -1ULL; + list_add_tail(&evt->link, &efi_events); + *event = evt; + return EFI_SUCCESS; +} + +/* + * efi_create_event_ex() - create an event in a group + * + * @type: type of the event to create + * @notify_tpl: task priority level of the event + * @notify_function: notification function of the event + * @notify_context: pointer passed to the notification function + * @event: created event + * @event_group: event group + * + * This function implements the CreateEventEx service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static +efi_status_t EFIAPI efi_create_event_ex(uint32_t type, efi_uintn_t notify_tpl, + void (EFIAPI *notify_function) ( + struct efi_event *event, + void *context), + void *notify_context, + const efi_guid_t *event_group, + struct efi_event **event) +{ + efi_status_t ret; + + EFI_ENTRY("%d, 0x%zx, %p, %p, %pUs", type, notify_tpl, notify_function, + notify_context, event_group); + + /* + * The allowable input parameters are the same as in CreateEvent() + * except for the following two disallowed event types. + */ + switch (type) { + case EVT_SIGNAL_EXIT_BOOT_SERVICES: + case EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE: + ret = EFI_INVALID_PARAMETER; + goto out; + } + + ret = efi_create_event(type, notify_tpl, notify_function, + notify_context, event_group, event); +out: + return EFI_EXIT(ret); +} + +/** + * efi_create_event_ext() - create an event + * @type: type of the event to create + * @notify_tpl: task priority level of the event + * @notify_function: notification function of the event + * @notify_context: pointer passed to the notification function + * @event: created event + * + * This function implements the CreateEvent service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_create_event_ext( + uint32_t type, efi_uintn_t notify_tpl, + void (EFIAPI *notify_function) ( + struct efi_event *event, + void *context), + void *notify_context, struct efi_event **event) +{ + EFI_ENTRY("%d, 0x%zx, %p, %p", type, notify_tpl, notify_function, + notify_context); + return EFI_EXIT(efi_create_event(type, notify_tpl, notify_function, + notify_context, NULL, event)); +} + +/** + * efi_timer_check() - check if a timer event has occurred + * + * Check if a timer event has occurred or a queued notification function should + * be called. + * + * Our timers have to work without interrupts, so we check whenever keyboard + * input or disk accesses happen if enough time elapsed for them to fire. + */ +void efi_timer_check(void) +{ + struct efi_event *evt; + u64 now = timer_get_us(); + + list_for_each_entry(evt, &efi_events, link) { + if (!timers_enabled) + continue; + if (!(evt->type & EVT_TIMER) || now < evt->trigger_next) + continue; + switch (evt->trigger_type) { + case EFI_TIMER_RELATIVE: + evt->trigger_type = EFI_TIMER_STOP; + break; + case EFI_TIMER_PERIODIC: + evt->trigger_next += evt->trigger_time; + break; + default: + continue; + } + evt->is_signaled = false; + efi_signal_event(evt); + } + efi_process_event_queue(); + schedule(); +} + +/** + * efi_set_timer() - set the trigger time for a timer event or stop the event + * @event: event for which the timer is set + * @type: type of the timer + * @trigger_time: trigger period in multiples of 100 ns + * + * This is the function for internal usage in U-Boot. For the API function + * implementing the SetTimer service see efi_set_timer_ext. + * + * Return: status code + */ +efi_status_t efi_set_timer(struct efi_event *event, enum efi_timer_delay type, + uint64_t trigger_time) +{ + /* Check that the event is valid */ + if (efi_is_event(event) != EFI_SUCCESS || !(event->type & EVT_TIMER)) + return EFI_INVALID_PARAMETER; + + /* + * The parameter defines a multiple of 100 ns. + * We use multiples of 1000 ns. So divide by 10. + */ + do_div(trigger_time, 10); + + switch (type) { + case EFI_TIMER_STOP: + event->trigger_next = -1ULL; + break; + case EFI_TIMER_PERIODIC: + case EFI_TIMER_RELATIVE: + event->trigger_next = timer_get_us() + trigger_time; + break; + default: + return EFI_INVALID_PARAMETER; + } + event->trigger_type = type; + event->trigger_time = trigger_time; + event->is_signaled = false; + return EFI_SUCCESS; +} + +/** + * efi_set_timer_ext() - Set the trigger time for a timer event or stop the + * event + * @event: event for which the timer is set + * @type: type of the timer + * @trigger_time: trigger period in multiples of 100 ns + * + * This function implements the SetTimer service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * + * Return: status code + */ +static efi_status_t EFIAPI efi_set_timer_ext(struct efi_event *event, + enum efi_timer_delay type, + uint64_t trigger_time) +{ + EFI_ENTRY("%p, %d, %llx", event, type, trigger_time); + return EFI_EXIT(efi_set_timer(event, type, trigger_time)); +} + +/** + * efi_wait_for_event() - wait for events to be signaled + * @num_events: number of events to be waited for + * @event: events to be waited for + * @index: index of the event that was signaled + * + * This function implements the WaitForEvent service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_wait_for_event(efi_uintn_t num_events, + struct efi_event **event, + efi_uintn_t *index) +{ + int i; + + EFI_ENTRY("%zu, %p, %p", num_events, event, index); + + /* Check parameters */ + if (!num_events || !event) + return EFI_EXIT(EFI_INVALID_PARAMETER); + /* Check TPL */ + if (efi_tpl != TPL_APPLICATION) + return EFI_EXIT(EFI_UNSUPPORTED); + for (i = 0; i < num_events; ++i) { + if (efi_is_event(event[i]) != EFI_SUCCESS) + return EFI_EXIT(EFI_INVALID_PARAMETER); + if (!event[i]->type || event[i]->type & EVT_NOTIFY_SIGNAL) + return EFI_EXIT(EFI_INVALID_PARAMETER); + if (!event[i]->is_signaled) + efi_queue_event(event[i]); + } + + /* Wait for signal */ + for (;;) { + for (i = 0; i < num_events; ++i) { + if (event[i]->is_signaled) + goto out; + } + /* Allow events to occur. */ + efi_timer_check(); + } + +out: + /* + * Reset the signal which is passed to the caller to allow periodic + * events to occur. + */ + event[i]->is_signaled = false; + if (index) + *index = i; + + return EFI_EXIT(EFI_SUCCESS); +} + +/** + * efi_signal_event_ext() - signal an EFI event + * @event: event to signal + * + * This function implements the SignalEvent service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * This functions sets the signaled state of the event and queues the + * notification function for execution. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_signal_event_ext(struct efi_event *event) +{ + EFI_ENTRY("%p", event); + if (efi_is_event(event) != EFI_SUCCESS) + return EFI_EXIT(EFI_INVALID_PARAMETER); + efi_signal_event(event); + return EFI_EXIT(EFI_SUCCESS); +} + +/** + * efi_close_event() - close an EFI event + * @event: event to close + * + * This function implements the CloseEvent service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +efi_status_t EFIAPI efi_close_event(struct efi_event *event) +{ + struct efi_register_notify_event *item, *next; + + EFI_ENTRY("%p", event); + if (efi_is_event(event) != EFI_SUCCESS) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + /* Remove protocol notify registrations for the event */ + list_for_each_entry_safe(item, next, &efi_register_notify_events, + link) { + if (event == item->event) { + struct efi_protocol_notification *hitem, *hnext; + + /* Remove signaled handles */ + list_for_each_entry_safe(hitem, hnext, &item->handles, + link) { + list_del(&hitem->link); + free(hitem); + } + list_del(&item->link); + free(item); + } + } + /* Remove event from queue */ + if (efi_event_is_queued(event)) + list_del(&event->queue_link); + + list_del(&event->link); + efi_free_pool(event); + return EFI_EXIT(EFI_SUCCESS); +} + +/** + * efi_check_event() - check if an event is signaled + * @event: event to check + * + * This function implements the CheckEvent service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * If an event is not signaled yet, the notification function is queued. The + * signaled state is cleared. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_check_event(struct efi_event *event) +{ + EFI_ENTRY("%p", event); + efi_timer_check(); + if (efi_is_event(event) != EFI_SUCCESS || + event->type & EVT_NOTIFY_SIGNAL) + return EFI_EXIT(EFI_INVALID_PARAMETER); + if (!event->is_signaled) + efi_queue_event(event); + if (event->is_signaled) { + event->is_signaled = false; + return EFI_EXIT(EFI_SUCCESS); + } + return EFI_EXIT(EFI_NOT_READY); +} + +/** + * efi_search_obj() - find the internal EFI object for a handle + * @handle: handle to find + * + * Return: EFI object + */ +struct efi_object *efi_search_obj(const efi_handle_t handle) +{ + struct efi_object *efiobj; + + if (!handle) + return NULL; + + list_for_each_entry(efiobj, &efi_obj_list, link) { + if (efiobj == handle) + return efiobj; + } + return NULL; +} + +/** + * efi_open_protocol_info_entry() - create open protocol info entry and add it + * to a protocol + * @handler: handler of a protocol + * + * Return: open protocol info entry + */ +static struct efi_open_protocol_info_entry *efi_create_open_info( + struct efi_handler *handler) +{ + struct efi_open_protocol_info_item *item; + + item = calloc(1, sizeof(struct efi_open_protocol_info_item)); + if (!item) + return NULL; + /* Append the item to the open protocol info list. */ + list_add_tail(&item->link, &handler->open_infos); + + return &item->info; +} + +/** + * efi_delete_open_info() - remove an open protocol info entry from a protocol + * @item: open protocol info entry to delete + * + * Return: status code + */ +static efi_status_t efi_delete_open_info( + struct efi_open_protocol_info_item *item) +{ + list_del(&item->link); + free(item); + return EFI_SUCCESS; +} + +/** + * efi_add_protocol() - install new protocol on a handle + * @handle: handle on which the protocol shall be installed + * @protocol: GUID of the protocol to be installed + * @protocol_interface: interface of the protocol implementation + * + * Return: status code + */ +efi_status_t efi_add_protocol(const efi_handle_t handle, + const efi_guid_t *protocol, + void *protocol_interface) +{ + struct efi_object *efiobj; + struct efi_handler *handler; + efi_status_t ret; + struct efi_register_notify_event *event; + + efiobj = efi_search_obj(handle); + if (!efiobj) + return EFI_INVALID_PARAMETER; + ret = efi_search_protocol(handle, protocol, NULL); + if (ret != EFI_NOT_FOUND) + return EFI_INVALID_PARAMETER; + handler = calloc(1, sizeof(struct efi_handler)); + if (!handler) + return EFI_OUT_OF_RESOURCES; + memcpy((void *)&handler->guid, protocol, sizeof(efi_guid_t)); + handler->protocol_interface = protocol_interface; + INIT_LIST_HEAD(&handler->open_infos); + list_add_tail(&handler->link, &efiobj->protocols); + + /* Notify registered events */ + list_for_each_entry(event, &efi_register_notify_events, link) { + if (!guidcmp(protocol, &event->protocol)) { + struct efi_protocol_notification *notif; + + notif = calloc(1, sizeof(*notif)); + if (!notif) { + list_del(&handler->link); + free(handler); + return EFI_OUT_OF_RESOURCES; + } + notif->handle = handle; + list_add_tail(¬if->link, &event->handles); + event->event->is_signaled = false; + efi_signal_event(event->event); + } + } + + if (!guidcmp(&efi_guid_device_path, protocol)) + EFI_PRINT("installed device path '%pD'\n", protocol_interface); + return EFI_SUCCESS; +} + +/** + * efi_install_protocol_interface() - install protocol interface + * @handle: handle on which the protocol shall be installed + * @protocol: GUID of the protocol to be installed + * @protocol_interface_type: type of the interface to be installed, + * always EFI_NATIVE_INTERFACE + * @protocol_interface: interface of the protocol implementation + * + * This function implements the InstallProtocolInterface service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_install_protocol_interface( + efi_handle_t *handle, const efi_guid_t *protocol, + int protocol_interface_type, void *protocol_interface) +{ + efi_status_t r; + + EFI_ENTRY("%p, %pUs, %d, %p", handle, protocol, protocol_interface_type, + protocol_interface); + + if (!handle || !protocol || + protocol_interface_type != EFI_NATIVE_INTERFACE) { + r = EFI_INVALID_PARAMETER; + goto out; + } + + /* Create new handle if requested. */ + if (!*handle) { + r = efi_create_handle(handle); + if (r != EFI_SUCCESS) + goto out; + EFI_PRINT("new handle %p\n", *handle); + } else { + EFI_PRINT("handle %p\n", *handle); + } + /* Add new protocol */ + r = efi_add_protocol(*handle, protocol, protocol_interface); +out: + return EFI_EXIT(r); +} + +/** + * efi_get_drivers() - get all drivers associated to a controller + * @handle: handle of the controller + * @protocol: protocol GUID (optional) + * @number_of_drivers: number of child controllers + * @driver_handle_buffer: handles of the the drivers + * + * The allocated buffer has to be freed with free(). + * + * Return: status code + */ +static efi_status_t efi_get_drivers(efi_handle_t handle, + const efi_guid_t *protocol, + efi_uintn_t *number_of_drivers, + efi_handle_t **driver_handle_buffer) +{ + struct efi_handler *handler; + struct efi_open_protocol_info_item *item; + efi_uintn_t count = 0, i; + bool duplicate; + + /* Count all driver associations */ + list_for_each_entry(handler, &handle->protocols, link) { + if (protocol && guidcmp(&handler->guid, protocol)) + continue; + list_for_each_entry(item, &handler->open_infos, link) { + if (item->info.attributes & + EFI_OPEN_PROTOCOL_BY_DRIVER) + ++count; + } + } + *number_of_drivers = 0; + if (!count) { + *driver_handle_buffer = NULL; + return EFI_SUCCESS; + } + /* + * Create buffer. In case of duplicate driver assignments the buffer + * will be too large. But that does not harm. + */ + *driver_handle_buffer = calloc(count, sizeof(efi_handle_t)); + if (!*driver_handle_buffer) + return EFI_OUT_OF_RESOURCES; + /* Collect unique driver handles */ + list_for_each_entry(handler, &handle->protocols, link) { + if (protocol && guidcmp(&handler->guid, protocol)) + continue; + list_for_each_entry(item, &handler->open_infos, link) { + if (item->info.attributes & + EFI_OPEN_PROTOCOL_BY_DRIVER) { + /* Check this is a new driver */ + duplicate = false; + for (i = 0; i < *number_of_drivers; ++i) { + if ((*driver_handle_buffer)[i] == + item->info.agent_handle) + duplicate = true; + } + /* Copy handle to buffer */ + if (!duplicate) { + i = (*number_of_drivers)++; + (*driver_handle_buffer)[i] = + item->info.agent_handle; + } + } + } + } + return EFI_SUCCESS; +} + +/** + * efi_disconnect_all_drivers() - disconnect all drivers from a controller + * @handle: handle of the controller + * @protocol: protocol GUID (optional) + * @child_handle: handle of the child to destroy + * + * This function implements the DisconnectController service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t efi_disconnect_all_drivers + (efi_handle_t handle, + const efi_guid_t *protocol, + efi_handle_t child_handle) +{ + efi_uintn_t number_of_drivers; + efi_handle_t *driver_handle_buffer; + efi_status_t r, ret; + + ret = efi_get_drivers(handle, protocol, &number_of_drivers, + &driver_handle_buffer); + if (ret != EFI_SUCCESS) + return ret; + if (!number_of_drivers) + return EFI_SUCCESS; + + while (number_of_drivers) { + r = EFI_CALL(efi_disconnect_controller( + handle, + driver_handle_buffer[--number_of_drivers], + child_handle)); + if (r != EFI_SUCCESS) + ret = r; + } + + free(driver_handle_buffer); + return ret; +} + +/** + * efi_uninstall_protocol() - uninstall protocol interface + * + * @handle: handle from which the protocol shall be removed + * @protocol: GUID of the protocol to be removed + * @protocol_interface: interface to be removed + * @preserve: preserve or delete the handle and remove it from any + * list it participates if no protocols remain + * + * This function DOES NOT delete a handle without installed protocol. + * + * Return: status code + */ +efi_status_t efi_uninstall_protocol + (efi_handle_t handle, const efi_guid_t *protocol, + void *protocol_interface, bool preserve) +{ + struct efi_handler *handler; + struct efi_open_protocol_info_item *item; + struct efi_open_protocol_info_item *pos; + efi_status_t r; + + /* Find the protocol on the handle */ + r = efi_search_protocol(handle, protocol, &handler); + if (r != EFI_SUCCESS) + goto out; + if (handler->protocol_interface != protocol_interface) + return EFI_NOT_FOUND; + /* Disconnect controllers */ + r = efi_disconnect_all_drivers(handle, protocol, NULL); + if (r != EFI_SUCCESS) { + r = EFI_ACCESS_DENIED; + /* + * This will reconnect all controllers of the handle, even ones + * that were not connected before. This can be done better + * but we are following the EDKII implementation on this for + * now + */ + EFI_CALL(efi_connect_controller(handle, NULL, NULL, true)); + goto out; + } + /* Close protocol */ + list_for_each_entry_safe(item, pos, &handler->open_infos, link) { + if (item->info.attributes == + EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL || + item->info.attributes == EFI_OPEN_PROTOCOL_GET_PROTOCOL || + item->info.attributes == EFI_OPEN_PROTOCOL_TEST_PROTOCOL) + efi_delete_open_info(item); + } + /* if agents didn't close the protocols properly */ + if (!list_empty(&handler->open_infos)) { + r = EFI_ACCESS_DENIED; + EFI_CALL(efi_connect_controller(handle, NULL, NULL, true)); + goto out; + } + r = efi_remove_protocol(handle, protocol, protocol_interface); + if (r != EFI_SUCCESS) + return r; + /* + * We don't care about the return value here since the + * handle might have more protocols installed + */ + if (!preserve) + efi_purge_handle(handle); +out: + return r; +} + +/** + * efi_uninstall_protocol_interface() - uninstall protocol interface + * @handle: handle from which the protocol shall be removed + * @protocol: GUID of the protocol to be removed + * @protocol_interface: interface to be removed + * + * This function implements the UninstallProtocolInterface service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_uninstall_protocol_interface + (efi_handle_t handle, const efi_guid_t *protocol, + void *protocol_interface) +{ + efi_status_t ret; + + EFI_ENTRY("%p, %pUs, %p", handle, protocol, protocol_interface); + + ret = efi_uninstall_protocol(handle, protocol, protocol_interface, false); + if (ret != EFI_SUCCESS) + goto out; + +out: + return EFI_EXIT(ret); +} + +/** + * efi_register_protocol_notify() - register an event for notification when a + * protocol is installed. + * @protocol: GUID of the protocol whose installation shall be notified + * @event: event to be signaled upon installation of the protocol + * @registration: key for retrieving the registration information + * + * This function implements the RegisterProtocolNotify service. + * See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * Return: status code + */ +efi_status_t EFIAPI efi_register_protocol_notify(const efi_guid_t *protocol, + struct efi_event *event, + void **registration) +{ + struct efi_register_notify_event *item; + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%pUs, %p, %p", protocol, event, registration); + + if (!protocol || !event || !registration) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + item = calloc(1, sizeof(struct efi_register_notify_event)); + if (!item) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + + item->event = event; + guidcpy(&item->protocol, protocol); + INIT_LIST_HEAD(&item->handles); + + list_add_tail(&item->link, &efi_register_notify_events); + + *registration = item; +out: + return EFI_EXIT(ret); +} + +/** + * efi_search() - determine if an EFI handle implements a protocol + * + * @search_type: selection criterion + * @protocol: GUID of the protocol + * @handle: handle + * + * See the documentation of the LocateHandle service in the UEFI specification. + * + * Return: 0 if the handle implements the protocol + */ +static int efi_search(enum efi_locate_search_type search_type, + const efi_guid_t *protocol, efi_handle_t handle) +{ + efi_status_t ret; + + switch (search_type) { + case ALL_HANDLES: + return 0; + case BY_PROTOCOL: + ret = efi_search_protocol(handle, protocol, NULL); + return (ret != EFI_SUCCESS); + default: + /* Invalid search type */ + return -1; + } +} + +/** + * efi_check_register_notify_event() - check if registration key is valid + * + * Check that a pointer is a valid registration key as returned by + * RegisterProtocolNotify(). + * + * @key: registration key + * Return: valid registration key or NULL + */ +static struct efi_register_notify_event *efi_check_register_notify_event + (void *key) +{ + struct efi_register_notify_event *event; + + list_for_each_entry(event, &efi_register_notify_events, link) { + if (event == (struct efi_register_notify_event *)key) + return event; + } + return NULL; +} + +/** + * efi_locate_handle() - locate handles implementing a protocol + * + * @search_type: selection criterion + * @protocol: GUID of the protocol + * @search_key: registration key + * @buffer_size: size of the buffer to receive the handles in bytes + * @buffer: buffer to receive the relevant handles + * + * This function is meant for U-Boot internal calls. For the API implementation + * of the LocateHandle service see efi_locate_handle_ext. + * + * Return: status code + */ +static efi_status_t efi_locate_handle( + enum efi_locate_search_type search_type, + const efi_guid_t *protocol, void *search_key, + efi_uintn_t *buffer_size, efi_handle_t *buffer) +{ + struct efi_object *efiobj; + efi_uintn_t size = 0; + struct efi_register_notify_event *event; + struct efi_protocol_notification *handle = NULL; + + /* Check parameters */ + switch (search_type) { + case ALL_HANDLES: + break; + case BY_REGISTER_NOTIFY: + if (!search_key) + return EFI_INVALID_PARAMETER; + /* Check that the registration key is valid */ + event = efi_check_register_notify_event(search_key); + if (!event) + return EFI_INVALID_PARAMETER; + break; + case BY_PROTOCOL: + if (!protocol) + return EFI_INVALID_PARAMETER; + break; + default: + return EFI_INVALID_PARAMETER; + } + + /* Count how much space we need */ + if (search_type == BY_REGISTER_NOTIFY) { + if (list_empty(&event->handles)) + return EFI_NOT_FOUND; + handle = list_first_entry(&event->handles, + struct efi_protocol_notification, + link); + efiobj = handle->handle; + size += sizeof(void *); + } else { + list_for_each_entry(efiobj, &efi_obj_list, link) { + if (!efi_search(search_type, protocol, efiobj)) + size += sizeof(void *); + } + if (size == 0) + return EFI_NOT_FOUND; + } + + if (!buffer_size) + return EFI_INVALID_PARAMETER; + + if (*buffer_size < size) { + *buffer_size = size; + return EFI_BUFFER_TOO_SMALL; + } + + *buffer_size = size; + + /* The buffer size is sufficient but there is no buffer */ + if (!buffer) + return EFI_INVALID_PARAMETER; + + /* Then fill the array */ + if (search_type == BY_REGISTER_NOTIFY) { + *buffer = efiobj; + list_del(&handle->link); + } else { + list_for_each_entry(efiobj, &efi_obj_list, link) { + if (!efi_search(search_type, protocol, efiobj)) + *buffer++ = efiobj; + } + } + + return EFI_SUCCESS; +} + +/** + * efi_locate_handle_ext() - locate handles implementing a protocol. + * @search_type: selection criterion + * @protocol: GUID of the protocol + * @search_key: registration key + * @buffer_size: size of the buffer to receive the handles in bytes + * @buffer: buffer to receive the relevant handles + * + * This function implements the LocateHandle service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: 0 if the handle implements the protocol + */ +static efi_status_t EFIAPI efi_locate_handle_ext( + enum efi_locate_search_type search_type, + const efi_guid_t *protocol, void *search_key, + efi_uintn_t *buffer_size, efi_handle_t *buffer) +{ + EFI_ENTRY("%d, %pUs, %p, %p, %p", search_type, protocol, search_key, + buffer_size, buffer); + + return EFI_EXIT(efi_locate_handle(search_type, protocol, search_key, + buffer_size, buffer)); +} + +/** + * efi_remove_configuration_table() - collapses configuration table entries, + * removing index i + * + * @i: index of the table entry to be removed + */ +static void efi_remove_configuration_table(int i) +{ + struct efi_configuration_table *this = &systab.tables[i]; + struct efi_configuration_table *next = &systab.tables[i + 1]; + struct efi_configuration_table *end = &systab.tables[systab.nr_tables]; + + memmove(this, next, (ulong)end - (ulong)next); + systab.nr_tables--; +} + +/** + * efi_install_configuration_table() - adds, updates, or removes a + * configuration table + * @guid: GUID of the installed table + * @table: table to be installed + * + * This function is used for internal calls. For the API implementation of the + * InstallConfigurationTable service see efi_install_configuration_table_ext. + * + * Return: status code + */ +efi_status_t efi_install_configuration_table(const efi_guid_t *guid, + void *table) +{ + struct efi_event *evt; + int i; + + if (!guid) + return EFI_INVALID_PARAMETER; + + /* Check for GUID override */ + for (i = 0; i < systab.nr_tables; i++) { + if (!guidcmp(guid, &systab.tables[i].guid)) { + if (table) + systab.tables[i].table = table; + else + efi_remove_configuration_table(i); + goto out; + } + } + + if (!table) + return EFI_NOT_FOUND; + + /* No override, check for overflow */ + if (i >= EFI_MAX_CONFIGURATION_TABLES) + return EFI_OUT_OF_RESOURCES; + + /* Add a new entry */ + guidcpy(&systab.tables[i].guid, guid); + systab.tables[i].table = table; + systab.nr_tables = i + 1; + +out: + /* systab.nr_tables may have changed. So we need to update the CRC32 */ + efi_update_table_header_crc32(&systab.hdr); + + /* Notify that the configuration table was changed */ + list_for_each_entry(evt, &efi_events, link) { + if (evt->group && !guidcmp(evt->group, guid)) { + efi_signal_event(evt); + break; + } + } + + return EFI_SUCCESS; +} + +/** + * efi_install_configuration_table_ex() - Adds, updates, or removes a + * configuration table. + * @guid: GUID of the installed table + * @table: table to be installed + * + * This function implements the InstallConfigurationTable service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t +EFIAPI efi_install_configuration_table_ext(const efi_guid_t *guid, + void *table) +{ + EFI_ENTRY("%pUs, %p", guid, table); + return EFI_EXIT(efi_install_configuration_table(guid, table)); +} + +/** + * efi_setup_loaded_image() - initialize a loaded image + * + * Initialize a loaded_image_info and loaded_image_info object with correct + * protocols, boot-device, etc. + * + * In case of an error \*handle_ptr and \*info_ptr are set to NULL and an error + * code is returned. + * + * @device_path: device path of the loaded image + * @file_path: file path of the loaded image + * @handle_ptr: handle of the loaded image + * @info_ptr: loaded image protocol + * Return: status code + */ +efi_status_t efi_setup_loaded_image(struct efi_device_path *device_path, + struct efi_device_path *file_path, + struct efi_loaded_image_obj **handle_ptr, + struct efi_loaded_image **info_ptr) +{ + efi_status_t ret; + struct efi_loaded_image *info = NULL; + struct efi_loaded_image_obj *obj = NULL; + struct efi_device_path *dp; + + /* In case of EFI_OUT_OF_RESOURCES avoid illegal free by caller. */ + *handle_ptr = NULL; + *info_ptr = NULL; + + info = calloc(1, sizeof(*info)); + if (!info) + return EFI_OUT_OF_RESOURCES; + obj = calloc(1, sizeof(*obj)); + if (!obj) { + free(info); + return EFI_OUT_OF_RESOURCES; + } + obj->header.type = EFI_OBJECT_TYPE_LOADED_IMAGE; + + /* Add internal object to object list */ + efi_add_handle(&obj->header); + + info->revision = EFI_LOADED_IMAGE_PROTOCOL_REVISION; + info->file_path = file_path; + info->system_table = &systab; + + if (device_path) { + info->device_handle = efi_dp_find_obj(device_path, NULL, NULL); + + dp = efi_dp_concat(device_path, file_path, 0); + if (!dp) { + ret = EFI_OUT_OF_RESOURCES; + goto failure; + } + } else { + dp = NULL; + } + ret = efi_add_protocol(&obj->header, + &efi_guid_loaded_image_device_path, dp); + if (ret != EFI_SUCCESS) + goto failure; + + /* + * When asking for the loaded_image interface, just + * return handle which points to loaded_image_info + */ + ret = efi_add_protocol(&obj->header, + &efi_guid_loaded_image, info); + if (ret != EFI_SUCCESS) + goto failure; + + *info_ptr = info; + *handle_ptr = obj; + + return ret; +failure: + printf("ERROR: Failure to install protocols for loaded image\n"); + efi_delete_handle(&obj->header); + free(info); + return ret; +} + +/** + * efi_locate_device_path() - Get the device path and handle of an device + * implementing a protocol + * @protocol: GUID of the protocol + * @device_path: device path + * @device: handle of the device + * + * This function implements the LocateDevicePath service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +efi_status_t EFIAPI efi_locate_device_path(const efi_guid_t *protocol, + struct efi_device_path **device_path, + efi_handle_t *device) +{ + struct efi_device_path *dp; + size_t i; + struct efi_handler *handler; + efi_handle_t *handles; + size_t len, len_dp; + size_t len_best = 0; + efi_uintn_t no_handles; + u8 *remainder; + efi_status_t ret; + + EFI_ENTRY("%pUs, %p, %p", protocol, device_path, device); + + if (!protocol || !device_path || !*device_path) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + /* Find end of device path */ + len = efi_dp_instance_size(*device_path); + + /* Get all handles implementing the protocol */ + ret = EFI_CALL(efi_locate_handle_buffer(BY_PROTOCOL, protocol, NULL, + &no_handles, &handles)); + if (ret != EFI_SUCCESS) + goto out; + + for (i = 0; i < no_handles; ++i) { + /* Find the device path protocol */ + ret = efi_search_protocol(handles[i], &efi_guid_device_path, + &handler); + if (ret != EFI_SUCCESS) + continue; + dp = (struct efi_device_path *)handler->protocol_interface; + len_dp = efi_dp_instance_size(dp); + /* + * This handle can only be a better fit + * if its device path length is longer than the best fit and + * if its device path length is shorter of equal the searched + * device path. + */ + if (len_dp <= len_best || len_dp > len) + continue; + /* Check if dp is a subpath of device_path */ + if (memcmp(*device_path, dp, len_dp)) + continue; + if (!device) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + *device = handles[i]; + len_best = len_dp; + } + if (len_best) { + remainder = (u8 *)*device_path + len_best; + *device_path = (struct efi_device_path *)remainder; + ret = EFI_SUCCESS; + } else { + ret = EFI_NOT_FOUND; + } +out: + return EFI_EXIT(ret); +} + +/** + * efi_load_image_from_file() - load an image from file system + * + * Read a file into a buffer allocated as EFI_BOOT_SERVICES_DATA. It is the + * callers obligation to update the memory type as needed. + * + * @file_path: the path of the image to load + * @buffer: buffer containing the loaded image + * @size: size of the loaded image + * Return: status code + */ +static +efi_status_t efi_load_image_from_file(struct efi_device_path *file_path, + void **buffer, efi_uintn_t *size) +{ + struct efi_file_handle *f; + efi_status_t ret; + u64 addr; + efi_uintn_t bs; + + /* Open file */ + f = efi_file_from_path(file_path); + if (!f) + return EFI_NOT_FOUND; + + ret = efi_file_size(f, &bs); + if (ret != EFI_SUCCESS) + goto error; + + /* + * When reading the file we do not yet know if it contains an + * application, a boottime driver, or a runtime driver. So here we + * allocate a buffer as EFI_BOOT_SERVICES_DATA. The caller has to + * update the reservation according to the image type. + */ + ret = efi_allocate_pages(EFI_ALLOCATE_ANY_PAGES, + EFI_BOOT_SERVICES_DATA, + efi_size_in_pages(bs), &addr); + if (ret != EFI_SUCCESS) { + ret = EFI_OUT_OF_RESOURCES; + goto error; + } + + /* Read file */ + EFI_CALL(ret = f->read(f, &bs, (void *)(uintptr_t)addr)); + if (ret != EFI_SUCCESS) + efi_free_pages(addr, efi_size_in_pages(bs)); + *buffer = (void *)(uintptr_t)addr; + *size = bs; +error: + EFI_CALL(f->close(f)); + return ret; +} + +/** + * efi_load_image_from_path() - load an image using a file path + * + * Read a file into a buffer allocated as EFI_BOOT_SERVICES_DATA. It is the + * callers obligation to update the memory type as needed. + * + * @boot_policy: true for request originating from the boot manager + * @file_path: the path of the image to load + * @buffer: buffer containing the loaded image + * @size: size of the loaded image + * Return: status code + */ +efi_status_t efi_load_image_from_path(bool boot_policy, + struct efi_device_path *file_path, + void **buffer, efi_uintn_t *size) +{ + efi_handle_t device; + efi_status_t ret; + struct efi_device_path *dp, *rem; + struct efi_load_file_protocol *load_file_protocol = NULL; + efi_uintn_t buffer_size; + uint64_t addr, pages; + const efi_guid_t *guid; + struct efi_handler *handler; + + /* In case of failure nothing is returned */ + *buffer = NULL; + *size = 0; + + dp = file_path; + device = efi_dp_find_obj(dp, NULL, &rem); + ret = efi_search_protocol(device, &efi_simple_file_system_protocol_guid, + NULL); + if (ret == EFI_SUCCESS) + return efi_load_image_from_file(file_path, buffer, size); + + ret = efi_search_protocol(device, &efi_guid_load_file_protocol, NULL); + if (ret == EFI_SUCCESS) { + guid = &efi_guid_load_file_protocol; + } else if (!boot_policy) { + guid = &efi_guid_load_file2_protocol; + ret = efi_search_protocol(device, guid, NULL); + } + if (ret != EFI_SUCCESS) + return EFI_NOT_FOUND; + ret = efi_search_protocol(device, guid, &handler); + if (ret != EFI_SUCCESS) + return EFI_NOT_FOUND; + buffer_size = 0; + load_file_protocol = handler->protocol_interface; + ret = EFI_CALL(load_file_protocol->load_file( + load_file_protocol, rem, boot_policy, + &buffer_size, NULL)); + if (ret != EFI_BUFFER_TOO_SMALL) + goto out; + pages = efi_size_in_pages(buffer_size); + ret = efi_allocate_pages(EFI_ALLOCATE_ANY_PAGES, EFI_BOOT_SERVICES_DATA, + pages, &addr); + if (ret != EFI_SUCCESS) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + ret = EFI_CALL(load_file_protocol->load_file( + load_file_protocol, rem, boot_policy, + &buffer_size, (void *)(uintptr_t)addr)); + if (ret != EFI_SUCCESS) + efi_free_pages(addr, pages); +out: + efi_close_protocol(device, guid, efi_root, NULL); + if (ret == EFI_SUCCESS) { + *buffer = (void *)(uintptr_t)addr; + *size = buffer_size; + } + + return ret; +} + +/** + * efi_load_image() - load an EFI image into memory + * @boot_policy: true for request originating from the boot manager + * @parent_image: the caller's image handle + * @file_path: the path of the image to load + * @source_buffer: memory location from which the image is installed + * @source_size: size of the memory area from which the image is installed + * @image_handle: handle for the newly installed image + * + * This function implements the LoadImage service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * Return: status code + */ +efi_status_t EFIAPI efi_load_image(bool boot_policy, + efi_handle_t parent_image, + struct efi_device_path *file_path, + void *source_buffer, + efi_uintn_t source_size, + efi_handle_t *image_handle) +{ + struct efi_device_path *dp, *fp; + struct efi_loaded_image *info = NULL; + struct efi_loaded_image_obj **image_obj = + (struct efi_loaded_image_obj **)image_handle; + efi_status_t ret; + void *dest_buffer; + + EFI_ENTRY("%d, %p, %pD, %p, %zu, %p", boot_policy, parent_image, + file_path, source_buffer, source_size, image_handle); + + if (!image_handle || (!source_buffer && !file_path) || + !efi_search_obj(parent_image) || + /* The parent image handle must refer to a loaded image */ + !parent_image->type) { + ret = EFI_INVALID_PARAMETER; + goto error; + } + + if (!source_buffer) { + ret = efi_load_image_from_path(boot_policy, file_path, + &dest_buffer, &source_size); + if (ret != EFI_SUCCESS) + goto error; + } else { + dest_buffer = source_buffer; + } + /* split file_path which contains both the device and file parts */ + efi_dp_split_file_path(file_path, &dp, &fp); + ret = efi_setup_loaded_image(dp, fp, image_obj, &info); + if (ret == EFI_SUCCESS) + ret = efi_load_pe(*image_obj, dest_buffer, source_size, info); + if (!source_buffer) + /* Release buffer to which file was loaded */ + efi_free_pages((uintptr_t)dest_buffer, + efi_size_in_pages(source_size)); + if (ret == EFI_SUCCESS || ret == EFI_SECURITY_VIOLATION) { + info->system_table = &systab; + info->parent_handle = parent_image; + } else { + /* The image is invalid. Release all associated resources. */ + efi_delete_handle(*image_handle); + *image_handle = NULL; + free(info); + } +error: + return EFI_EXIT(ret); +} + +/** + * efi_exit_caches() - fix up caches for EFI payloads if necessary + */ +static void efi_exit_caches(void) +{ +#if defined(CONFIG_EFI_GRUB_ARM32_WORKAROUND) + /* + * Boooting Linux via GRUB prior to version 2.04 fails on 32bit ARM if + * caches are enabled. + * + * TODO: + * According to the UEFI spec caches that can be managed via CP15 + * operations should be enabled. Caches requiring platform information + * to manage should be disabled. This should not happen in + * ExitBootServices() but before invoking any UEFI binary is invoked. + * + * We want to keep the current workaround while GRUB prior to version + * 2.04 is still in use. + */ + cleanup_before_linux(); +#endif +} + +/** + * efi_exit_boot_services() - stop all boot services + * @image_handle: handle of the loaded image + * @map_key: key of the memory map + * + * This function implements the ExitBootServices service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * All timer events are disabled. For exit boot services events the + * notification function is called. The boot services are disabled in the + * system table. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_exit_boot_services(efi_handle_t image_handle, + efi_uintn_t map_key) +{ + struct efi_event *evt, *next_event; + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p, %zx", image_handle, map_key); + + /* Check that the caller has read the current memory map */ + if (map_key != efi_memory_map_key) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + /* Check if ExitBootServices has already been called */ + if (!systab.boottime) + goto out; + + /* Notify EFI_EVENT_GROUP_BEFORE_EXIT_BOOT_SERVICES event group. */ + list_for_each_entry(evt, &efi_events, link) { + if (evt->group && + !guidcmp(evt->group, + &efi_guid_event_group_before_exit_boot_services)) { + efi_signal_event(evt); + break; + } + } + + /* Stop all timer related activities */ + timers_enabled = false; + + /* Add related events to the event group */ + list_for_each_entry(evt, &efi_events, link) { + if (evt->type == EVT_SIGNAL_EXIT_BOOT_SERVICES) + evt->group = &efi_guid_event_group_exit_boot_services; + } + /* Notify that ExitBootServices is invoked. */ + list_for_each_entry(evt, &efi_events, link) { + if (evt->group && + !guidcmp(evt->group, + &efi_guid_event_group_exit_boot_services)) { + efi_signal_event(evt); + break; + } + } + + /* Make sure that notification functions are not called anymore */ + efi_tpl = TPL_HIGH_LEVEL; + + /* Notify variable services */ + efi_variables_boot_exit_notify(); + + /* Remove all events except EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE */ + list_for_each_entry_safe(evt, next_event, &efi_events, link) { + if (evt->type != EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE) + list_del(&evt->link); + } + + if (!efi_st_keep_devices) { + bootm_disable_interrupts(); + board_quiesce_devices(); + dm_remove_devices_active(); + } + + /* Patch out unsupported runtime function */ + efi_runtime_detach(); + + /* Fix up caches for EFI payloads if necessary */ + efi_exit_caches(); + + /* Disable boot time services */ + systab.con_in_handle = NULL; + systab.con_in = NULL; + systab.con_out_handle = NULL; + systab.con_out = NULL; + systab.stderr_handle = NULL; + systab.std_err = NULL; + systab.boottime = NULL; + + /* Recalculate CRC32 */ + efi_update_table_header_crc32(&systab.hdr); + + /* Give the payload some time to boot */ + efi_set_watchdog(0); + schedule(); +out: + if (IS_ENABLED(CONFIG_EFI_TCG2_PROTOCOL)) { + if (ret != EFI_SUCCESS) + efi_tcg2_notify_exit_boot_services_failed(); + } + + return EFI_EXIT(ret); +} + +/** + * efi_get_next_monotonic_count() - get next value of the counter + * @count: returned value of the counter + * + * This function implements the NextMonotonicCount service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_get_next_monotonic_count(uint64_t *count) +{ + static uint64_t mono; + efi_status_t ret; + + EFI_ENTRY("%p", count); + if (!count) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + *count = mono++; + ret = EFI_SUCCESS; +out: + return EFI_EXIT(ret); +} + +/** + * efi_stall() - sleep + * @microseconds: period to sleep in microseconds + * + * This function implements the Stall service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_stall(unsigned long microseconds) +{ + u64 end_tick; + + EFI_ENTRY("%ld", microseconds); + + end_tick = get_ticks() + usec_to_tick(microseconds); + while (get_ticks() < end_tick) + efi_timer_check(); + + return EFI_EXIT(EFI_SUCCESS); +} + +/** + * efi_set_watchdog_timer() - reset the watchdog timer + * @timeout: seconds before reset by watchdog + * @watchdog_code: code to be logged when resetting + * @data_size: size of buffer in bytes + * @watchdog_data: buffer with data describing the reset reason + * + * This function implements the SetWatchdogTimer service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_set_watchdog_timer(unsigned long timeout, + uint64_t watchdog_code, + unsigned long data_size, + uint16_t *watchdog_data) +{ + EFI_ENTRY("%ld, 0x%llx, %ld, %p", timeout, watchdog_code, + data_size, watchdog_data); + return EFI_EXIT(efi_set_watchdog(timeout)); +} + +/** + * efi_close_protocol() - close a protocol + * @handle: handle on which the protocol shall be closed + * @protocol: GUID of the protocol to close + * @agent_handle: handle of the driver + * @controller_handle: handle of the controller + * + * This is the function implementing the CloseProtocol service is for internal + * usage in U-Boot. For API usage wrapper efi_close_protocol_ext() is provided. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +efi_status_t efi_close_protocol(efi_handle_t handle, const efi_guid_t *protocol, + efi_handle_t agent_handle, + efi_handle_t controller_handle) +{ + struct efi_handler *handler; + struct efi_open_protocol_info_item *item; + struct efi_open_protocol_info_item *pos; + efi_status_t ret; + + if (!efi_search_obj(agent_handle) || + (controller_handle && !efi_search_obj(controller_handle))) + return EFI_INVALID_PARAMETER; + ret = efi_search_protocol(handle, protocol, &handler); + if (ret != EFI_SUCCESS) + return ret; + + ret = EFI_NOT_FOUND; + list_for_each_entry_safe(item, pos, &handler->open_infos, link) { + if (item->info.agent_handle == agent_handle && + item->info.controller_handle == controller_handle) { + efi_delete_open_info(item); + ret = EFI_SUCCESS; + } + } + + return ret; +} + +/** + * efi_close_protocol_ext() - close a protocol + * @handle: handle on which the protocol shall be closed + * @protocol: GUID of the protocol to close + * @agent_handle: handle of the driver + * @controller_handle: handle of the controller + * + * This function implements the CloseProtocol service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI +efi_close_protocol_ext(efi_handle_t handle, const efi_guid_t *protocol, + efi_handle_t agent_handle, + efi_handle_t controller_handle) +{ + efi_status_t ret; + + EFI_ENTRY("%p, %pUs, %p, %p", handle, protocol, agent_handle, + controller_handle); + + ret = efi_close_protocol(handle, protocol, + agent_handle, controller_handle); + + return EFI_EXIT(ret); +} + +/** + * efi_open_protocol_information() - provide information about then open status + * of a protocol on a handle + * @handle: handle for which the information shall be retrieved + * @protocol: GUID of the protocol + * @entry_buffer: buffer to receive the open protocol information + * @entry_count: number of entries available in the buffer + * + * This function implements the OpenProtocolInformation service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_open_protocol_information( + efi_handle_t handle, const efi_guid_t *protocol, + struct efi_open_protocol_info_entry **entry_buffer, + efi_uintn_t *entry_count) +{ + unsigned long buffer_size; + unsigned long count; + struct efi_handler *handler; + struct efi_open_protocol_info_item *item; + efi_status_t r; + + EFI_ENTRY("%p, %pUs, %p, %p", handle, protocol, entry_buffer, + entry_count); + + /* Check parameters */ + if (!entry_buffer) { + r = EFI_INVALID_PARAMETER; + goto out; + } + r = efi_search_protocol(handle, protocol, &handler); + if (r != EFI_SUCCESS) + goto out; + + /* Count entries */ + count = 0; + list_for_each_entry(item, &handler->open_infos, link) { + if (item->info.open_count) + ++count; + } + *entry_count = count; + *entry_buffer = NULL; + if (!count) { + r = EFI_SUCCESS; + goto out; + } + + /* Copy entries */ + buffer_size = count * sizeof(struct efi_open_protocol_info_entry); + r = efi_allocate_pool(EFI_BOOT_SERVICES_DATA, buffer_size, + (void **)entry_buffer); + if (r != EFI_SUCCESS) + goto out; + list_for_each_entry_reverse(item, &handler->open_infos, link) { + if (item->info.open_count) + (*entry_buffer)[--count] = item->info; + } +out: + return EFI_EXIT(r); +} + +/** + * efi_protocols_per_handle() - get protocols installed on a handle + * @handle: handle for which the information is retrieved + * @protocol_buffer: buffer with protocol GUIDs + * @protocol_buffer_count: number of entries in the buffer + * + * This function implements the ProtocolsPerHandleService. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_protocols_per_handle( + efi_handle_t handle, efi_guid_t ***protocol_buffer, + efi_uintn_t *protocol_buffer_count) +{ + unsigned long buffer_size; + struct efi_object *efiobj; + struct list_head *protocol_handle; + efi_status_t r; + + EFI_ENTRY("%p, %p, %p", handle, protocol_buffer, + protocol_buffer_count); + + if (!handle || !protocol_buffer || !protocol_buffer_count) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + *protocol_buffer = NULL; + + efiobj = efi_search_obj(handle); + if (!efiobj) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + *protocol_buffer_count = list_count_nodes(&efiobj->protocols); + + /* Copy GUIDs */ + if (*protocol_buffer_count) { + size_t j = 0; + + buffer_size = sizeof(efi_guid_t *) * *protocol_buffer_count; + r = efi_allocate_pool(EFI_BOOT_SERVICES_DATA, buffer_size, + (void **)protocol_buffer); + if (r != EFI_SUCCESS) + return EFI_EXIT(r); + list_for_each(protocol_handle, &efiobj->protocols) { + struct efi_handler *protocol; + + protocol = list_entry(protocol_handle, + struct efi_handler, link); + (*protocol_buffer)[j] = (void *)&protocol->guid; + ++j; + } + } + + return EFI_EXIT(EFI_SUCCESS); +} + +efi_status_t efi_locate_handle_buffer_int(enum efi_locate_search_type search_type, + const efi_guid_t *protocol, void *search_key, + efi_uintn_t *no_handles, efi_handle_t **buffer) +{ + efi_status_t r; + efi_uintn_t buffer_size = 0; + + if (!no_handles || !buffer) { + r = EFI_INVALID_PARAMETER; + goto out; + } + *no_handles = 0; + *buffer = NULL; + r = efi_locate_handle(search_type, protocol, search_key, &buffer_size, + *buffer); + if (r != EFI_BUFFER_TOO_SMALL) + goto out; + r = efi_allocate_pool(EFI_BOOT_SERVICES_DATA, buffer_size, + (void **)buffer); + if (r != EFI_SUCCESS) + goto out; + r = efi_locate_handle(search_type, protocol, search_key, &buffer_size, + *buffer); + if (r == EFI_SUCCESS) + *no_handles = buffer_size / sizeof(efi_handle_t); +out: + return r; +} + +/** + * efi_locate_handle_buffer() - locate handles implementing a protocol + * @search_type: selection criterion + * @protocol: GUID of the protocol + * @search_key: registration key + * @no_handles: number of returned handles + * @buffer: buffer with the returned handles + * + * This function implements the LocateHandleBuffer service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +efi_status_t EFIAPI efi_locate_handle_buffer( + enum efi_locate_search_type search_type, + const efi_guid_t *protocol, void *search_key, + efi_uintn_t *no_handles, efi_handle_t **buffer) +{ + efi_status_t r; + + EFI_ENTRY("%d, %pUs, %p, %p, %p", search_type, protocol, search_key, + no_handles, buffer); + + r = efi_locate_handle_buffer_int(search_type, protocol, search_key, + no_handles, buffer); + + return EFI_EXIT(r); +} + +/** + * efi_locate_protocol() - find an interface implementing a protocol + * @protocol: GUID of the protocol + * @registration: registration key passed to the notification function + * @protocol_interface: interface implementing the protocol + * + * This function implements the LocateProtocol service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_locate_protocol(const efi_guid_t *protocol, + void *registration, + void **protocol_interface) +{ + struct efi_handler *handler; + efi_status_t ret; + struct efi_object *efiobj; + + EFI_ENTRY("%pUs, %p, %p", protocol, registration, protocol_interface); + + /* + * The UEFI spec explicitly requires a protocol even if a registration + * key is provided. This differs from the logic in LocateHandle(). + */ + if (!protocol || !protocol_interface) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + if (registration) { + struct efi_register_notify_event *event; + struct efi_protocol_notification *handle; + + event = efi_check_register_notify_event(registration); + if (!event) + return EFI_EXIT(EFI_INVALID_PARAMETER); + /* + * The UEFI spec requires to return EFI_NOT_FOUND if no + * protocol instance matches protocol and registration. + * So let's do the same for a mismatch between protocol and + * registration. + */ + if (guidcmp(&event->protocol, protocol)) + goto not_found; + if (list_empty(&event->handles)) + goto not_found; + handle = list_first_entry(&event->handles, + struct efi_protocol_notification, + link); + efiobj = handle->handle; + list_del(&handle->link); + free(handle); + ret = efi_search_protocol(efiobj, protocol, &handler); + if (ret == EFI_SUCCESS) + goto found; + } else { + list_for_each_entry(efiobj, &efi_obj_list, link) { + ret = efi_search_protocol(efiobj, protocol, &handler); + if (ret == EFI_SUCCESS) + goto found; + } + } +not_found: + *protocol_interface = NULL; + return EFI_EXIT(EFI_NOT_FOUND); +found: + *protocol_interface = handler->protocol_interface; + return EFI_EXIT(EFI_SUCCESS); +} + +/** + * efi_install_multiple_protocol_interfaces_int() - Install multiple protocol + * interfaces + * @handle: handle on which the protocol interfaces shall be installed + * @argptr: va_list of args + * + * Core functionality of efi_install_multiple_protocol_interfaces + * Must not be called directly + * + * Return: status code + */ +static efi_status_t EFIAPI +efi_install_multiple_protocol_interfaces_int(efi_handle_t *handle, + efi_va_list argptr) +{ + const efi_guid_t *protocol; + void *protocol_interface; + efi_handle_t old_handle; + efi_status_t ret = EFI_SUCCESS; + int i = 0; + efi_va_list argptr_copy; + + if (!handle) + return EFI_INVALID_PARAMETER; + + efi_va_copy(argptr_copy, argptr); + for (;;) { + protocol = efi_va_arg(argptr, efi_guid_t*); + if (!protocol) + break; + protocol_interface = efi_va_arg(argptr, void*); + /* Check that a device path has not been installed before */ + if (!guidcmp(protocol, &efi_guid_device_path)) { + struct efi_device_path *dp = protocol_interface; + + ret = EFI_CALL(efi_locate_device_path(protocol, &dp, + &old_handle)); + if (ret == EFI_SUCCESS && + dp->type == DEVICE_PATH_TYPE_END) { + EFI_PRINT("Path %pD already installed\n", + protocol_interface); + ret = EFI_ALREADY_STARTED; + break; + } + } + ret = EFI_CALL(efi_install_protocol_interface(handle, protocol, + EFI_NATIVE_INTERFACE, + protocol_interface)); + if (ret != EFI_SUCCESS) + break; + i++; + } + if (ret == EFI_SUCCESS) + goto out; + + /* If an error occurred undo all changes. */ + for (; i; --i) { + protocol = efi_va_arg(argptr_copy, efi_guid_t*); + protocol_interface = efi_va_arg(argptr_copy, void*); + EFI_CALL(efi_uninstall_protocol_interface(*handle, protocol, + protocol_interface)); + } + +out: + efi_va_end(argptr_copy); + return ret; + +} + +/** + * efi_install_multiple_protocol_interfaces() - Install multiple protocol + * interfaces + * @handle: handle on which the protocol interfaces shall be installed + * @...: NULL terminated argument list with pairs of protocol GUIDS and + * interfaces + * + * + * This is the function for internal usage in U-Boot. For the API function + * implementing the InstallMultipleProtocol service see + * efi_install_multiple_protocol_interfaces_ext() + * + * Return: status code + */ +efi_status_t EFIAPI +efi_install_multiple_protocol_interfaces(efi_handle_t *handle, ...) +{ + efi_status_t ret; + efi_va_list argptr; + + efi_va_start(argptr, handle); + ret = efi_install_multiple_protocol_interfaces_int(handle, argptr); + efi_va_end(argptr); + return ret; +} + +/** + * efi_install_multiple_protocol_interfaces_ext() - Install multiple protocol + * interfaces + * @handle: handle on which the protocol interfaces shall be installed + * @...: NULL terminated argument list with pairs of protocol GUIDS and + * interfaces + * + * This function implements the MultipleProtocolInterfaces service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI +efi_install_multiple_protocol_interfaces_ext(efi_handle_t *handle, ...) +{ + EFI_ENTRY("%p", handle); + efi_status_t ret; + efi_va_list argptr; + + efi_va_start(argptr, handle); + ret = efi_install_multiple_protocol_interfaces_int(handle, argptr); + efi_va_end(argptr); + return EFI_EXIT(ret); +} + +/** + * efi_uninstall_multiple_protocol_interfaces_int() - wrapper for uninstall + * multiple protocol + * interfaces + * @handle: handle from which the protocol interfaces shall be removed + * @argptr: va_list of args + * + * Core functionality of efi_uninstall_multiple_protocol_interfaces + * Must not be called directly + * + * Return: status code + */ +static efi_status_t EFIAPI +efi_uninstall_multiple_protocol_interfaces_int(efi_handle_t handle, + efi_va_list argptr) +{ + const efi_guid_t *protocol, *next_protocol; + void *protocol_interface; + efi_status_t ret = EFI_SUCCESS; + size_t i = 0; + efi_va_list argptr_copy; + + if (!handle) + return EFI_INVALID_PARAMETER; + + efi_va_copy(argptr_copy, argptr); + protocol = efi_va_arg(argptr, efi_guid_t*); + for (;;) { + /* + * If efi_uninstall_protocol() fails we need to be able to + * reinstall the previously uninstalled protocols on the same + * handle. + * Instead of calling efi_uninstall_protocol(...,..., false) + * and potentially removing the handle, only allow the handle + * removal on the last protocol that we requested to uninstall. + * That way we can preserve the handle in case the latter fails + */ + bool preserve = true; + + if (!protocol) + break; + protocol_interface = efi_va_arg(argptr, void*); + next_protocol = efi_va_arg(argptr, efi_guid_t*); + if (!next_protocol) + preserve = false; + ret = efi_uninstall_protocol(handle, protocol, + protocol_interface, preserve); + if (ret != EFI_SUCCESS) + break; + i++; + protocol = next_protocol; + } + if (ret == EFI_SUCCESS) + goto out; + + /* If an error occurred undo all changes. */ + for (; i; --i) { + protocol = efi_va_arg(argptr_copy, efi_guid_t*); + protocol_interface = efi_va_arg(argptr_copy, void*); + EFI_CALL(efi_install_protocol_interface(&handle, protocol, + EFI_NATIVE_INTERFACE, + protocol_interface)); + } + /* + * If any errors are generated while the protocol interfaces are being + * uninstalled, then the protocols uninstalled prior to the error will + * be reinstalled using InstallProtocolInterface() and the status code + * EFI_INVALID_PARAMETER is returned. + */ + ret = EFI_INVALID_PARAMETER; + +out: + efi_va_end(argptr_copy); + return ret; +} + +/** + * efi_uninstall_multiple_protocol_interfaces() - uninstall multiple protocol + * interfaces + * @handle: handle from which the protocol interfaces shall be removed + * @...: NULL terminated argument list with pairs of protocol GUIDS and + * interfaces + * + * This function implements the UninstallMultipleProtocolInterfaces service. + * + * This is the function for internal usage in U-Boot. For the API function + * implementing the UninstallMultipleProtocolInterfaces service see + * efi_uninstall_multiple_protocol_interfaces_ext() + * + * Return: status code + */ +efi_status_t EFIAPI +efi_uninstall_multiple_protocol_interfaces(efi_handle_t handle, ...) +{ + efi_status_t ret; + efi_va_list argptr; + + efi_va_start(argptr, handle); + ret = efi_uninstall_multiple_protocol_interfaces_int(handle, argptr); + efi_va_end(argptr); + return ret; +} + +/** + * efi_uninstall_multiple_protocol_interfaces_ext() - uninstall multiple protocol + * interfaces + * @handle: handle from which the protocol interfaces shall be removed + * @...: NULL terminated argument list with pairs of protocol GUIDS and + * interfaces + * + * This function implements the UninstallMultipleProtocolInterfaces service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI +efi_uninstall_multiple_protocol_interfaces_ext(efi_handle_t handle, ...) +{ + EFI_ENTRY("%p", handle); + efi_status_t ret; + efi_va_list argptr; + + efi_va_start(argptr, handle); + ret = efi_uninstall_multiple_protocol_interfaces_int(handle, argptr); + efi_va_end(argptr); + return EFI_EXIT(ret); +} + +/** + * efi_calculate_crc32() - calculate cyclic redundancy code + * @data: buffer with data + * @data_size: size of buffer in bytes + * @crc32_p: cyclic redundancy code + * + * This function implements the CalculateCrc32 service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_calculate_crc32(const void *data, + efi_uintn_t data_size, + u32 *crc32_p) +{ + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p, %zu", data, data_size); + if (!data || !data_size || !crc32_p) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + *crc32_p = crc32(0, data, data_size); +out: + return EFI_EXIT(ret); +} + +/** + * efi_copy_mem() - copy memory + * @destination: destination of the copy operation + * @source: source of the copy operation + * @length: number of bytes to copy + * + * This function implements the CopyMem service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + */ +static void EFIAPI efi_copy_mem(void *destination, const void *source, + size_t length) +{ + EFI_ENTRY("%p, %p, %ld", destination, source, (unsigned long)length); + memmove(destination, source, length); + EFI_EXIT(EFI_SUCCESS); +} + +/** + * efi_set_mem() - Fill memory with a byte value. + * @buffer: buffer to fill + * @size: size of buffer in bytes + * @value: byte to copy to the buffer + * + * This function implements the SetMem service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + */ +static void EFIAPI efi_set_mem(void *buffer, size_t size, uint8_t value) +{ + EFI_ENTRY("%p, %ld, 0x%x", buffer, (unsigned long)size, value); + memset(buffer, value, size); + EFI_EXIT(EFI_SUCCESS); +} + +/** + * efi_protocol_open() - open protocol interface on a handle + * @handler: handler of a protocol + * @protocol_interface: interface implementing the protocol + * @agent_handle: handle of the driver + * @controller_handle: handle of the controller + * @attributes: attributes indicating how to open the protocol + * + * Return: status code + */ +efi_status_t efi_protocol_open( + struct efi_handler *handler, + void **protocol_interface, void *agent_handle, + void *controller_handle, uint32_t attributes) +{ + struct efi_open_protocol_info_item *item; + struct efi_open_protocol_info_entry *match = NULL; + bool opened_by_driver = false; + bool opened_exclusive = false; + + /* If there is no agent, only return the interface */ + if (!agent_handle) + goto out; + + /* For TEST_PROTOCOL ignore interface attribute */ + if (attributes != EFI_OPEN_PROTOCOL_TEST_PROTOCOL) + *protocol_interface = NULL; + + /* + * Check if the protocol is already opened by a driver with the same + * attributes or opened exclusively + */ + list_for_each_entry(item, &handler->open_infos, link) { + if (item->info.agent_handle == agent_handle) { + if ((attributes & EFI_OPEN_PROTOCOL_BY_DRIVER) && + (item->info.attributes == attributes)) + return EFI_ALREADY_STARTED; + } else { + if (item->info.attributes & + EFI_OPEN_PROTOCOL_BY_DRIVER) + opened_by_driver = true; + } + if (item->info.attributes & EFI_OPEN_PROTOCOL_EXCLUSIVE) + opened_exclusive = true; + } + + /* Only one controller can open the protocol exclusively */ + if (attributes & EFI_OPEN_PROTOCOL_EXCLUSIVE) { + if (opened_exclusive) + return EFI_ACCESS_DENIED; + } else if (attributes & EFI_OPEN_PROTOCOL_BY_DRIVER) { + if (opened_exclusive || opened_by_driver) + return EFI_ACCESS_DENIED; + } + + /* Prepare exclusive opening */ + if (attributes & EFI_OPEN_PROTOCOL_EXCLUSIVE) { + /* Try to disconnect controllers */ +disconnect_next: + opened_by_driver = false; + list_for_each_entry(item, &handler->open_infos, link) { + efi_status_t ret; + + if (item->info.attributes == + EFI_OPEN_PROTOCOL_BY_DRIVER) { + ret = EFI_CALL(efi_disconnect_controller( + item->info.controller_handle, + item->info.agent_handle, + NULL)); + if (ret == EFI_SUCCESS) + /* + * Child controllers may have been + * removed from the open_infos list. So + * let's restart the loop. + */ + goto disconnect_next; + else + opened_by_driver = true; + } + } + /* Only one driver can be connected */ + if (opened_by_driver) + return EFI_ACCESS_DENIED; + } + + /* Find existing entry */ + list_for_each_entry(item, &handler->open_infos, link) { + if (item->info.agent_handle == agent_handle && + item->info.controller_handle == controller_handle && + item->info.attributes == attributes) + match = &item->info; + } + /* None found, create one */ + if (!match) { + match = efi_create_open_info(handler); + if (!match) + return EFI_OUT_OF_RESOURCES; + } + + match->agent_handle = agent_handle; + match->controller_handle = controller_handle; + match->attributes = attributes; + match->open_count++; + +out: + /* For TEST_PROTOCOL ignore interface attribute. */ + if (attributes != EFI_OPEN_PROTOCOL_TEST_PROTOCOL) + *protocol_interface = handler->protocol_interface; + + return EFI_SUCCESS; +} + +/** + * efi_open_protocol() - open protocol interface on a handle + * @handle: handle on which the protocol shall be opened + * @protocol: GUID of the protocol + * @protocol_interface: interface implementing the protocol + * @agent_handle: handle of the driver + * @controller_handle: handle of the controller + * @attributes: attributes indicating how to open the protocol + * + * This function implements the OpenProtocol interface. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_open_protocol + (efi_handle_t handle, const efi_guid_t *protocol, + void **protocol_interface, efi_handle_t agent_handle, + efi_handle_t controller_handle, uint32_t attributes) +{ + struct efi_handler *handler; + efi_status_t r = EFI_INVALID_PARAMETER; + + EFI_ENTRY("%p, %pUs, %p, %p, %p, 0x%x", handle, protocol, + protocol_interface, agent_handle, controller_handle, + attributes); + + if (!handle || !protocol || + (!protocol_interface && attributes != + EFI_OPEN_PROTOCOL_TEST_PROTOCOL)) { + goto out; + } + + switch (attributes) { + case EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL: + case EFI_OPEN_PROTOCOL_GET_PROTOCOL: + case EFI_OPEN_PROTOCOL_TEST_PROTOCOL: + break; + case EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER: + if (controller_handle == handle) + goto out; + /* fall-through */ + case EFI_OPEN_PROTOCOL_BY_DRIVER: + case EFI_OPEN_PROTOCOL_BY_DRIVER | EFI_OPEN_PROTOCOL_EXCLUSIVE: + /* Check that the controller handle is valid */ + if (!efi_search_obj(controller_handle)) + goto out; + /* fall-through */ + case EFI_OPEN_PROTOCOL_EXCLUSIVE: + /* Check that the agent handle is valid */ + if (!efi_search_obj(agent_handle)) + goto out; + break; + default: + goto out; + } + + r = efi_search_protocol(handle, protocol, &handler); + switch (r) { + case EFI_SUCCESS: + break; + case EFI_NOT_FOUND: + r = EFI_UNSUPPORTED; + goto out; + default: + goto out; + } + + r = efi_protocol_open(handler, protocol_interface, agent_handle, + controller_handle, attributes); +out: + return EFI_EXIT(r); +} + +/** + * efi_start_image() - call the entry point of an image + * @image_handle: handle of the image + * @exit_data_size: size of the buffer + * @exit_data: buffer to receive the exit data of the called image + * + * This function implements the StartImage service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +efi_status_t EFIAPI efi_start_image(efi_handle_t image_handle, + efi_uintn_t *exit_data_size, + u16 **exit_data) +{ + struct efi_loaded_image_obj *image_obj = + (struct efi_loaded_image_obj *)image_handle; + efi_status_t ret; + void *info; + efi_handle_t parent_image = current_image; + efi_status_t exit_status; + jmp_buf exit_jmp; + + EFI_ENTRY("%p, %p, %p", image_handle, exit_data_size, exit_data); + + if (!efi_search_obj(image_handle)) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + /* Check parameters */ + if (image_obj->header.type != EFI_OBJECT_TYPE_LOADED_IMAGE) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + if (image_obj->auth_status != EFI_IMAGE_AUTH_PASSED) + return EFI_EXIT(EFI_SECURITY_VIOLATION); + + ret = EFI_CALL(efi_open_protocol(image_handle, &efi_guid_loaded_image, + &info, NULL, NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL)); + if (ret != EFI_SUCCESS) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + image_obj->exit_data_size = exit_data_size; + image_obj->exit_data = exit_data; + image_obj->exit_status = &exit_status; + image_obj->exit_jmp = &exit_jmp; + + if (IS_ENABLED(CONFIG_EFI_TCG2_PROTOCOL)) { + if (image_obj->image_type == IMAGE_SUBSYSTEM_EFI_APPLICATION) { + ret = efi_tcg2_measure_efi_app_invocation(image_obj); + if (ret == EFI_SECURITY_VIOLATION) { + /* + * TCG2 Protocol is installed but no TPM device found, + * this is not expected. + */ + return EFI_EXIT(EFI_SECURITY_VIOLATION); + } + } + } + + /* call the image! */ + if (setjmp(exit_jmp)) { + /* + * We called the entry point of the child image with EFI_CALL + * in the lines below. The child image called the Exit() boot + * service efi_exit() which executed the long jump that brought + * us to the current line. This implies that the second half + * of the EFI_CALL macro has not been executed. + */ +#if defined(CONFIG_ARM) || defined(CONFIG_RISCV) + /* + * efi_exit() called efi_restore_gd(). We have to undo this + * otherwise __efi_entry_check() will put the wrong value into + * app_gd. + */ + set_gd(app_gd); +#endif + /* + * To get ready to call EFI_EXIT below we have to execute the + * missed out steps of EFI_CALL. + */ + EFI_RETURN(exit_status); + + current_image = parent_image; + + return EFI_EXIT(exit_status); + } + + current_image = image_handle; + image_obj->header.type = EFI_OBJECT_TYPE_STARTED_IMAGE; + EFI_PRINT("Jumping into 0x%p\n", image_obj->entry); + ret = EFI_CALL(image_obj->entry(image_handle, &systab)); + + /* + * Control is returned from a started UEFI image either by calling + * Exit() (where exit data can be provided) or by simply returning from + * the entry point. In the latter case call Exit() on behalf of the + * image. + */ + return EFI_CALL(systab.boottime->exit(image_handle, ret, 0, NULL)); +} + +/** + * efi_delete_image() - delete loaded image from memory) + * + * @image_obj: handle of the loaded image + * @loaded_image_protocol: loaded image protocol + */ +static efi_status_t efi_delete_image + (struct efi_loaded_image_obj *image_obj, + struct efi_loaded_image *loaded_image_protocol) +{ + struct efi_object *efiobj; + efi_status_t r, ret = EFI_SUCCESS; + +close_next: + list_for_each_entry(efiobj, &efi_obj_list, link) { + struct efi_handler *protocol; + + list_for_each_entry(protocol, &efiobj->protocols, link) { + struct efi_open_protocol_info_item *info; + + list_for_each_entry(info, &protocol->open_infos, link) { + if (info->info.agent_handle != + (efi_handle_t)image_obj) + continue; + r = efi_close_protocol( + efiobj, &protocol->guid, + info->info.agent_handle, + info->info.controller_handle); + if (r != EFI_SUCCESS) + ret = r; + /* + * Closing protocols may results in further + * items being deleted. To play it safe loop + * over all elements again. + */ + goto close_next; + } + } + } + + efi_free_pages((uintptr_t)loaded_image_protocol->image_base, + efi_size_in_pages(loaded_image_protocol->image_size)); + efi_delete_handle(&image_obj->header); + + return ret; +} + +/** + * efi_unload_image() - unload an EFI image + * @image_handle: handle of the image to be unloaded + * + * This function implements the UnloadImage service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +efi_status_t EFIAPI efi_unload_image(efi_handle_t image_handle) +{ + efi_status_t ret = EFI_SUCCESS; + struct efi_object *efiobj; + struct efi_loaded_image *loaded_image_protocol; + + EFI_ENTRY("%p", image_handle); + + efiobj = efi_search_obj(image_handle); + if (!efiobj) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + /* Find the loaded image protocol */ + ret = EFI_CALL(efi_open_protocol(image_handle, &efi_guid_loaded_image, + (void **)&loaded_image_protocol, + NULL, NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL)); + if (ret != EFI_SUCCESS) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + switch (efiobj->type) { + case EFI_OBJECT_TYPE_STARTED_IMAGE: + /* Call the unload function */ + if (!loaded_image_protocol->unload) { + ret = EFI_UNSUPPORTED; + goto out; + } + ret = EFI_CALL(loaded_image_protocol->unload(image_handle)); + if (ret != EFI_SUCCESS) + goto out; + break; + case EFI_OBJECT_TYPE_LOADED_IMAGE: + break; + default: + ret = EFI_INVALID_PARAMETER; + goto out; + } + efi_delete_image((struct efi_loaded_image_obj *)efiobj, + loaded_image_protocol); +out: + return EFI_EXIT(ret); +} + +/** + * efi_update_exit_data() - fill exit data parameters of StartImage() + * + * @image_obj: image handle + * @exit_data_size: size of the exit data buffer + * @exit_data: buffer with data returned by UEFI payload + * Return: status code + */ +static efi_status_t efi_update_exit_data(struct efi_loaded_image_obj *image_obj, + efi_uintn_t exit_data_size, + u16 *exit_data) +{ + efi_status_t ret; + + /* + * If exit_data is not provided to StartImage(), exit_data_size must be + * ignored. + */ + if (!image_obj->exit_data) + return EFI_SUCCESS; + if (image_obj->exit_data_size) + *image_obj->exit_data_size = exit_data_size; + if (exit_data_size && exit_data) { + ret = efi_allocate_pool(EFI_BOOT_SERVICES_DATA, + exit_data_size, + (void **)image_obj->exit_data); + if (ret != EFI_SUCCESS) + return ret; + memcpy(*image_obj->exit_data, exit_data, exit_data_size); + } else { + image_obj->exit_data = NULL; + } + return EFI_SUCCESS; +} + +/** + * efi_exit() - leave an EFI application or driver + * @image_handle: handle of the application or driver that is exiting + * @exit_status: status code + * @exit_data_size: size of the buffer in bytes + * @exit_data: buffer with data describing an error + * + * This function implements the Exit service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_exit(efi_handle_t image_handle, + efi_status_t exit_status, + efi_uintn_t exit_data_size, + u16 *exit_data) +{ + /* + * TODO: We should call the unload procedure of the loaded + * image protocol. + */ + efi_status_t ret; + struct efi_loaded_image *loaded_image_protocol; + struct efi_loaded_image_obj *image_obj = + (struct efi_loaded_image_obj *)image_handle; + jmp_buf *exit_jmp; + + EFI_ENTRY("%p, %ld, %zu, %p", image_handle, exit_status, + exit_data_size, exit_data); + + /* Check parameters */ + ret = EFI_CALL(efi_open_protocol(image_handle, &efi_guid_loaded_image, + (void **)&loaded_image_protocol, + NULL, NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL)); + if (ret != EFI_SUCCESS) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + /* Unloading of unstarted images */ + switch (image_obj->header.type) { + case EFI_OBJECT_TYPE_STARTED_IMAGE: + break; + case EFI_OBJECT_TYPE_LOADED_IMAGE: + efi_delete_image(image_obj, loaded_image_protocol); + ret = EFI_SUCCESS; + goto out; + default: + /* Handle does not refer to loaded image */ + ret = EFI_INVALID_PARAMETER; + goto out; + } + /* A started image can only be unloaded it is the last one started. */ + if (image_handle != current_image) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + /* Exit data is only foreseen in case of failure. */ + if (exit_status != EFI_SUCCESS) { + ret = efi_update_exit_data(image_obj, exit_data_size, + exit_data); + /* Exiting has priority. Don't return error to caller. */ + if (ret != EFI_SUCCESS) + EFI_PRINT("%s: out of memory\n", __func__); + } + /* efi_delete_image() frees image_obj. Copy before the call. */ + exit_jmp = image_obj->exit_jmp; + *image_obj->exit_status = exit_status; + if (image_obj->image_type == IMAGE_SUBSYSTEM_EFI_APPLICATION || + exit_status != EFI_SUCCESS) + efi_delete_image(image_obj, loaded_image_protocol); + + if (IS_ENABLED(CONFIG_EFI_TCG2_PROTOCOL)) { + if (image_obj->image_type == IMAGE_SUBSYSTEM_EFI_APPLICATION) { + ret = efi_tcg2_measure_efi_app_exit(); + if (ret != EFI_SUCCESS) + log_debug("tcg2 measurement fails (0x%lx)\n", + ret); + } + } + + /* Make sure entry/exit counts for EFI world cross-overs match */ + EFI_EXIT(exit_status); + + /* + * But longjmp out with the U-Boot gd, not the application's, as + * the other end is a setjmp call inside EFI context. + */ + efi_restore_gd(); + + longjmp(*exit_jmp, 1); + + panic("EFI application exited"); +out: + return EFI_EXIT(ret); +} + +/** + * efi_handle_protocol() - get interface of a protocol on a handle + * @handle: handle on which the protocol shall be opened + * @protocol: GUID of the protocol + * @protocol_interface: interface implementing the protocol + * + * This function implements the HandleProtocol service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +efi_status_t EFIAPI efi_handle_protocol(efi_handle_t handle, + const efi_guid_t *protocol, + void **protocol_interface) +{ + return efi_open_protocol(handle, protocol, protocol_interface, efi_root, + NULL, EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL); +} + +/** + * efi_bind_controller() - bind a single driver to a controller + * @controller_handle: controller handle + * @driver_image_handle: driver handle + * @remain_device_path: remaining path + * + * Return: status code + */ +static efi_status_t efi_bind_controller( + efi_handle_t controller_handle, + efi_handle_t driver_image_handle, + struct efi_device_path *remain_device_path) +{ + struct efi_driver_binding_protocol *binding_protocol; + efi_status_t r; + + r = EFI_CALL(efi_open_protocol(driver_image_handle, + &efi_guid_driver_binding_protocol, + (void **)&binding_protocol, + driver_image_handle, NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL)); + if (r != EFI_SUCCESS) + return r; + r = EFI_CALL(binding_protocol->supported(binding_protocol, + controller_handle, + remain_device_path)); + if (r == EFI_SUCCESS) + r = EFI_CALL(binding_protocol->start(binding_protocol, + controller_handle, + remain_device_path)); + efi_close_protocol(driver_image_handle, + &efi_guid_driver_binding_protocol, + driver_image_handle, NULL); + return r; +} + +/** + * efi_connect_single_controller() - connect a single driver to a controller + * @controller_handle: controller + * @driver_image_handle: driver + * @remain_device_path: remaining path + * + * Return: status code + */ +static efi_status_t efi_connect_single_controller( + efi_handle_t controller_handle, + efi_handle_t *driver_image_handle, + struct efi_device_path *remain_device_path) +{ + efi_handle_t *buffer; + size_t count; + size_t i; + efi_status_t r; + size_t connected = 0; + + /* Get buffer with all handles with driver binding protocol */ + r = EFI_CALL(efi_locate_handle_buffer(BY_PROTOCOL, + &efi_guid_driver_binding_protocol, + NULL, &count, &buffer)); + if (r != EFI_SUCCESS) + return r; + + /* Context Override */ + if (driver_image_handle) { + for (; *driver_image_handle; ++driver_image_handle) { + for (i = 0; i < count; ++i) { + if (buffer[i] == *driver_image_handle) { + buffer[i] = NULL; + r = efi_bind_controller( + controller_handle, + *driver_image_handle, + remain_device_path); + /* + * For drivers that do not support the + * controller or are already connected + * we receive an error code here. + */ + if (r == EFI_SUCCESS) + ++connected; + } + } + } + } + + /* + * TODO: Some overrides are not yet implemented: + * - Platform Driver Override + * - Driver Family Override Search + * - Bus Specific Driver Override + */ + + /* Driver Binding Search */ + for (i = 0; i < count; ++i) { + if (buffer[i]) { + r = efi_bind_controller(controller_handle, + buffer[i], + remain_device_path); + if (r == EFI_SUCCESS) + ++connected; + } + } + + efi_free_pool(buffer); + if (!connected) + return EFI_NOT_FOUND; + return EFI_SUCCESS; +} + +/** + * efi_connect_controller() - connect a controller to a driver + * @controller_handle: handle of the controller + * @driver_image_handle: handle of the driver + * @remain_device_path: device path of a child controller + * @recursive: true to connect all child controllers + * + * This function implements the ConnectController service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * First all driver binding protocol handles are tried for binding drivers. + * Afterwards all handles that have opened a protocol of the controller + * with EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER are connected to drivers. + * + * Return: status code + */ +efi_status_t EFIAPI efi_connect_controller( + efi_handle_t controller_handle, + efi_handle_t *driver_image_handle, + struct efi_device_path *remain_device_path, + bool recursive) +{ + efi_status_t r; + efi_status_t ret = EFI_NOT_FOUND; + struct efi_object *efiobj; + + EFI_ENTRY("%p, %p, %pD, %d", controller_handle, driver_image_handle, + remain_device_path, recursive); + + efiobj = efi_search_obj(controller_handle); + if (!efiobj) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + r = efi_connect_single_controller(controller_handle, + driver_image_handle, + remain_device_path); + if (r == EFI_SUCCESS) + ret = EFI_SUCCESS; + if (recursive) { + struct efi_handler *handler; + struct efi_open_protocol_info_item *item; + + list_for_each_entry(handler, &efiobj->protocols, link) { + list_for_each_entry(item, &handler->open_infos, link) { + if (item->info.attributes & + EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER) { + r = EFI_CALL(efi_connect_controller( + item->info.controller_handle, + driver_image_handle, + remain_device_path, + recursive)); + if (r == EFI_SUCCESS) + ret = EFI_SUCCESS; + } + } + } + } + /* Check for child controller specified by end node */ + if (ret != EFI_SUCCESS && remain_device_path && + remain_device_path->type == DEVICE_PATH_TYPE_END) + ret = EFI_SUCCESS; +out: + return EFI_EXIT(ret); +} + +/** + * efi_reinstall_protocol_interface() - reinstall protocol interface + * @handle: handle on which the protocol shall be reinstalled + * @protocol: GUID of the protocol to be installed + * @old_interface: interface to be removed + * @new_interface: interface to be installed + * + * This function implements the ReinstallProtocolInterface service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * The old interface is uninstalled. The new interface is installed. + * Drivers are connected. + * + * Return: status code + */ +efi_status_t EFIAPI efi_reinstall_protocol_interface( + efi_handle_t handle, const efi_guid_t *protocol, + void *old_interface, void *new_interface) +{ + efi_status_t ret; + + EFI_ENTRY("%p, %pUs, %p, %p", handle, protocol, old_interface, + new_interface); + + /* Uninstall protocol but do not delete handle */ + ret = efi_uninstall_protocol(handle, protocol, old_interface, true); + if (ret != EFI_SUCCESS) + goto out; + + /* Install the new protocol */ + ret = efi_add_protocol(handle, protocol, new_interface); + /* + * The UEFI spec does not specify what should happen to the handle + * if in case of an error no protocol interface remains on the handle. + * So let's do nothing here. + */ + if (ret != EFI_SUCCESS) + goto out; + /* + * The returned status code has to be ignored. + * Do not create an error if no suitable driver for the handle exists. + */ + EFI_CALL(efi_connect_controller(handle, NULL, NULL, true)); +out: + return EFI_EXIT(ret); +} + +/** + * efi_get_child_controllers() - get all child controllers associated to a driver + * @efiobj: handle of the controller + * @driver_handle: handle of the driver + * @number_of_children: number of child controllers + * @child_handle_buffer: handles of the the child controllers + * + * The allocated buffer has to be freed with free(). + * + * Return: status code + */ +static efi_status_t efi_get_child_controllers( + struct efi_object *efiobj, + efi_handle_t driver_handle, + efi_uintn_t *number_of_children, + efi_handle_t **child_handle_buffer) +{ + struct efi_handler *handler; + struct efi_open_protocol_info_item *item; + efi_uintn_t count = 0, i; + bool duplicate; + + /* Count all child controller associations */ + list_for_each_entry(handler, &efiobj->protocols, link) { + list_for_each_entry(item, &handler->open_infos, link) { + if (item->info.agent_handle == driver_handle && + item->info.attributes & + EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER) + ++count; + } + } + /* + * Create buffer. In case of duplicate child controller assignments + * the buffer will be too large. But that does not harm. + */ + *number_of_children = 0; + if (!count) + return EFI_SUCCESS; + *child_handle_buffer = calloc(count, sizeof(efi_handle_t)); + if (!*child_handle_buffer) + return EFI_OUT_OF_RESOURCES; + /* Copy unique child handles */ + list_for_each_entry(handler, &efiobj->protocols, link) { + list_for_each_entry(item, &handler->open_infos, link) { + if (item->info.agent_handle == driver_handle && + item->info.attributes & + EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER) { + /* Check this is a new child controller */ + duplicate = false; + for (i = 0; i < *number_of_children; ++i) { + if ((*child_handle_buffer)[i] == + item->info.controller_handle) + duplicate = true; + } + /* Copy handle to buffer */ + if (!duplicate) { + i = (*number_of_children)++; + (*child_handle_buffer)[i] = + item->info.controller_handle; + } + } + } + } + return EFI_SUCCESS; +} + +/** + * efi_disconnect_controller() - disconnect a controller from a driver + * @controller_handle: handle of the controller + * @driver_image_handle: handle of the driver + * @child_handle: handle of the child to destroy + * + * This function implements the DisconnectController service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +efi_status_t EFIAPI efi_disconnect_controller( + efi_handle_t controller_handle, + efi_handle_t driver_image_handle, + efi_handle_t child_handle) +{ + struct efi_driver_binding_protocol *binding_protocol; + efi_handle_t *child_handle_buffer = NULL; + size_t number_of_children = 0; + efi_status_t r; + struct efi_object *efiobj; + bool sole_child; + + EFI_ENTRY("%p, %p, %p", controller_handle, driver_image_handle, + child_handle); + + efiobj = efi_search_obj(controller_handle); + if (!efiobj) { + r = EFI_INVALID_PARAMETER; + goto out; + } + + if (child_handle && !efi_search_obj(child_handle)) { + r = EFI_INVALID_PARAMETER; + goto out; + } + + /* If no driver handle is supplied, disconnect all drivers */ + if (!driver_image_handle) { + r = efi_disconnect_all_drivers(efiobj, NULL, child_handle); + goto out; + } + + /* Create list of child handles */ + r = efi_get_child_controllers(efiobj, + driver_image_handle, + &number_of_children, + &child_handle_buffer); + if (r != EFI_SUCCESS) + return r; + sole_child = (number_of_children == 1); + + if (child_handle) { + number_of_children = 1; + free(child_handle_buffer); + child_handle_buffer = &child_handle; + } + + /* Get the driver binding protocol */ + r = EFI_CALL(efi_open_protocol(driver_image_handle, + &efi_guid_driver_binding_protocol, + (void **)&binding_protocol, + driver_image_handle, NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL)); + if (r != EFI_SUCCESS) { + r = EFI_INVALID_PARAMETER; + goto out; + } + /* Remove the children */ + if (number_of_children) { + r = EFI_CALL(binding_protocol->stop(binding_protocol, + controller_handle, + number_of_children, + child_handle_buffer)); + if (r != EFI_SUCCESS) { + r = EFI_DEVICE_ERROR; + goto out; + } + } + /* Remove the driver */ + if (!child_handle || sole_child) { + r = EFI_CALL(binding_protocol->stop(binding_protocol, + controller_handle, + 0, NULL)); + if (r != EFI_SUCCESS) { + r = EFI_DEVICE_ERROR; + goto out; + } + } + efi_close_protocol(driver_image_handle, + &efi_guid_driver_binding_protocol, + driver_image_handle, NULL); + r = EFI_SUCCESS; +out: + if (!child_handle) + free(child_handle_buffer); + return EFI_EXIT(r); +} + +static struct efi_boot_services efi_boot_services = { + .hdr = { + .signature = EFI_BOOT_SERVICES_SIGNATURE, + .revision = EFI_SPECIFICATION_VERSION, + .headersize = sizeof(struct efi_boot_services), + }, + .raise_tpl = efi_raise_tpl, + .restore_tpl = efi_restore_tpl, + .allocate_pages = efi_allocate_pages_ext, + .free_pages = efi_free_pages_ext, + .get_memory_map = efi_get_memory_map_ext, + .allocate_pool = efi_allocate_pool_ext, + .free_pool = efi_free_pool_ext, + .create_event = efi_create_event_ext, + .set_timer = efi_set_timer_ext, + .wait_for_event = efi_wait_for_event, + .signal_event = efi_signal_event_ext, + .close_event = efi_close_event, + .check_event = efi_check_event, + .install_protocol_interface = efi_install_protocol_interface, + .reinstall_protocol_interface = efi_reinstall_protocol_interface, + .uninstall_protocol_interface = efi_uninstall_protocol_interface, + .handle_protocol = efi_handle_protocol, + .reserved = NULL, + .register_protocol_notify = efi_register_protocol_notify, + .locate_handle = efi_locate_handle_ext, + .locate_device_path = efi_locate_device_path, + .install_configuration_table = efi_install_configuration_table_ext, + .load_image = efi_load_image, + .start_image = efi_start_image, + .exit = efi_exit, + .unload_image = efi_unload_image, + .exit_boot_services = efi_exit_boot_services, + .get_next_monotonic_count = efi_get_next_monotonic_count, + .stall = efi_stall, + .set_watchdog_timer = efi_set_watchdog_timer, + .connect_controller = efi_connect_controller, + .disconnect_controller = efi_disconnect_controller, + .open_protocol = efi_open_protocol, + .close_protocol = efi_close_protocol_ext, + .open_protocol_information = efi_open_protocol_information, + .protocols_per_handle = efi_protocols_per_handle, + .locate_handle_buffer = efi_locate_handle_buffer, + .locate_protocol = efi_locate_protocol, + .install_multiple_protocol_interfaces = + efi_install_multiple_protocol_interfaces_ext, + .uninstall_multiple_protocol_interfaces = + efi_uninstall_multiple_protocol_interfaces_ext, + .calculate_crc32 = efi_calculate_crc32, + .copy_mem = efi_copy_mem, + .set_mem = efi_set_mem, + .create_event_ex = efi_create_event_ex, +}; + +static u16 __efi_runtime_data firmware_vendor[] = u"Das U-Boot"; + +struct efi_system_table __efi_runtime_data systab = { + .hdr = { + .signature = EFI_SYSTEM_TABLE_SIGNATURE, + .revision = EFI_SPECIFICATION_VERSION, + .headersize = sizeof(struct efi_system_table), + }, + .fw_vendor = firmware_vendor, + .fw_revision = FW_VERSION << 16 | FW_PATCHLEVEL << 8, + .runtime = &efi_runtime_services, + .nr_tables = 0, + .tables = NULL, +}; + +/** + * efi_initialize_system_table() - Initialize system table + * + * Return: status code + */ +efi_status_t efi_initialize_system_table(void) +{ + efi_status_t ret; + + /* Allocate configuration table array */ + ret = efi_allocate_pool(EFI_RUNTIME_SERVICES_DATA, + EFI_MAX_CONFIGURATION_TABLES * + sizeof(struct efi_configuration_table), + (void **)&systab.tables); + + /* + * These entries will be set to NULL in ExitBootServices(). To avoid + * relocation in SetVirtualAddressMap(), set them dynamically. + */ + systab.con_in_handle = efi_root; + systab.con_in = &efi_con_in; + systab.con_out_handle = efi_root; + systab.con_out = &efi_con_out; + systab.stderr_handle = efi_root; + systab.std_err = &efi_con_out; + systab.boottime = &efi_boot_services; + + /* Set CRC32 field in table headers */ + efi_update_table_header_crc32(&systab.hdr); + efi_update_table_header_crc32(&efi_runtime_services.hdr); + efi_update_table_header_crc32(&efi_boot_services.hdr); + + return ret; +} diff --git a/lib/efi_loader/efi_capsule.c b/lib/efi_loader/efi_capsule.c new file mode 100644 index 00000000000..f8a4a7c6ef4 --- /dev/null +++ b/lib/efi_loader/efi_capsule.c @@ -0,0 +1,1400 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI Capsule + * + * Copyright (c) 2018 Linaro Limited + * Author: AKASHI Takahiro + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <efi_loader.h> +#include <efi_variable.h> +#include <env.h> +#include <fdtdec.h> +#include <fs.h> +#include <fwu.h> +#include <hang.h> +#include <malloc.h> +#include <mapmem.h> +#include <sort.h> +#include <sysreset.h> +#include <asm/global_data.h> +#include <u-boot/uuid.h> + +#include <crypto/pkcs7.h> +#include <crypto/pkcs7_parser.h> +#include <linux/err.h> + +DECLARE_GLOBAL_DATA_PTR; + +const efi_guid_t efi_guid_capsule_report = EFI_CAPSULE_REPORT_GUID; +static const efi_guid_t efi_guid_firmware_management_capsule_id = + EFI_FIRMWARE_MANAGEMENT_CAPSULE_ID_GUID; +const efi_guid_t efi_guid_firmware_management_protocol = + EFI_FIRMWARE_MANAGEMENT_PROTOCOL_GUID; +const efi_guid_t fwu_guid_os_request_fw_revert = + FWU_OS_REQUEST_FW_REVERT_GUID; +const efi_guid_t fwu_guid_os_request_fw_accept = + FWU_OS_REQUEST_FW_ACCEPT_GUID; + +#define FW_ACCEPT_OS (u32)0x8000 + +#ifdef CONFIG_EFI_CAPSULE_ON_DISK +/* for file system access */ +static struct efi_file_handle *bootdev_root; +#endif + +static __maybe_unused unsigned int get_capsule_index(const u16 *variable_name) +{ + u16 value16[11]; /* "CapsuleXXXX": non-null-terminated */ + char value[5]; + efi_uintn_t size; + unsigned long index = 0xffff; + efi_status_t ret; + int i; + + size = sizeof(value16); + ret = efi_get_variable_int(variable_name, &efi_guid_capsule_report, + NULL, &size, value16, NULL); + if (ret != EFI_SUCCESS || size != 22 || + u16_strncmp(value16, u"Capsule", 7)) + goto err; + for (i = 0; i < 4; ++i) { + u16 c = value16[i + 7]; + + if (!c || c > 0x7f) + goto err; + value[i] = c; + } + value[4] = 0; + if (strict_strtoul(value, 16, &index)) + index = 0xffff; +err: + return index; +} + +/** + * get_last_capsule - get the last capsule index + * + * Retrieve the index of the capsule invoked last time from "CapsuleLast" + * variable. + * + * Return: + * * > 0 - the last capsule index invoked + * * 0xffff - on error, or no capsule invoked yet + */ +static __maybe_unused unsigned int get_last_capsule(void) +{ + return get_capsule_index(u"CapsuleLast"); +} + +/** + * get_max_capsule - get the max capsule index + * + * Retrieve the max capsule index value from "CapsuleMax" variable. + * + * Return: + * * > 0 - the max capsule index + * * 0xffff - on error, or "CapsuleMax" variable does not exist + */ +static __maybe_unused unsigned int get_max_capsule(void) +{ + return get_capsule_index(u"CapsuleMax"); +} + +/** + * set_capsule_result - set a result variable + * @capsule: Capsule + * @return_status: Return status + * + * Create and set a result variable, "CapsuleXXXX", for the capsule, + * @capsule. + */ +static __maybe_unused +void set_capsule_result(int index, struct efi_capsule_header *capsule, + efi_status_t return_status) +{ + u16 variable_name16[12]; + struct efi_capsule_result_variable_header result; + struct efi_time time; + efi_status_t ret; + + efi_create_indexed_name(variable_name16, sizeof(variable_name16), + "Capsule", index); + result.variable_total_size = sizeof(result); + result.capsule_guid = capsule->capsule_guid; + ret = EFI_CALL((*efi_runtime_services.get_time)(&time, NULL)); + if (ret == EFI_SUCCESS) + memcpy(&result.capsule_processed, &time, sizeof(time)); + else + memset(&result.capsule_processed, 0, sizeof(time)); + result.capsule_status = return_status; + ret = efi_set_variable_int(variable_name16, &efi_guid_capsule_report, + EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS, + sizeof(result), &result, false); + if (ret != EFI_SUCCESS) { + log_err("Setting %ls failed\n", variable_name16); + return; + } + + /* Variable CapsuleLast must not include terminating 0x0000 */ + ret = efi_set_variable_int(u"CapsuleLast", &efi_guid_capsule_report, + EFI_VARIABLE_READ_ONLY | + EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS, + 22, variable_name16, false); + if (ret != EFI_SUCCESS) + log_err("Setting %ls failed\n", u"CapsuleLast"); +} + +#ifdef CONFIG_EFI_CAPSULE_FIRMWARE_MANAGEMENT +/** + * efi_fmp_find - search for Firmware Management Protocol drivers + * @image_type: Image type guid + * @image_index: Image Index + * @instance: Instance number + * @handles: Handles of FMP drivers + * @no_handles: Number of handles + * + * Search for Firmware Management Protocol drivers, matching the image + * type, @image_type and the machine instance, @instance, from the list, + * @handles. + * + * Return: + * * Protocol instance - on success + * * NULL - on failure + */ +static struct efi_firmware_management_protocol * +efi_fmp_find(efi_guid_t *image_type, u8 image_index, u64 instance, + efi_handle_t *handles, efi_uintn_t no_handles) +{ + efi_handle_t *handle; + struct efi_firmware_management_protocol *fmp; + struct efi_firmware_image_descriptor *image_info, *desc; + efi_uintn_t info_size, descriptor_size; + u32 descriptor_version; + u8 descriptor_count; + u32 package_version; + u16 *package_version_name; + bool found = false; + int i, j; + efi_status_t ret; + + for (i = 0, handle = handles; i < no_handles; i++, handle++) { + struct efi_handler *fmp_handler; + + ret = efi_search_protocol( + *handle, &efi_guid_firmware_management_protocol, + &fmp_handler); + if (ret != EFI_SUCCESS) + continue; + fmp = fmp_handler->protocol_interface; + + /* get device's image info */ + info_size = 0; + image_info = NULL; + descriptor_version = 0; + descriptor_count = 0; + descriptor_size = 0; + package_version = 0; + package_version_name = NULL; + ret = EFI_CALL(fmp->get_image_info(fmp, &info_size, + image_info, + &descriptor_version, + &descriptor_count, + &descriptor_size, + &package_version, + &package_version_name)); + if (ret != EFI_BUFFER_TOO_SMALL) + goto skip; + + image_info = malloc(info_size); + if (!image_info) + goto skip; + + ret = EFI_CALL(fmp->get_image_info(fmp, &info_size, + image_info, + &descriptor_version, + &descriptor_count, + &descriptor_size, + &package_version, + &package_version_name)); + if (ret != EFI_SUCCESS || + descriptor_version != EFI_FIRMWARE_IMAGE_DESCRIPTOR_VERSION) + goto skip; + + /* matching */ + for (j = 0, desc = image_info; j < descriptor_count; + j++, desc = (void *)desc + descriptor_size) { + log_debug("+++ desc[%d] index: %d, name: %ls\n", + j, desc->image_index, desc->image_id_name); + if (!guidcmp(&desc->image_type_id, image_type) && + (desc->image_index == image_index) && + (!instance || + !desc->hardware_instance || + desc->hardware_instance == instance)) + found = true; + } + +skip: + efi_free_pool(package_version_name); + free(image_info); + if (found) + return fmp; + } + + return NULL; +} + +/** + * efi_remove_auth_hdr - remove authentication data from image + * @image: Pointer to pointer to Image + * @image_size: Pointer to Image size + * + * Remove the authentication data from image if possible. + * Update @image and @image_size. + * + * Return: status code + */ +static efi_status_t efi_remove_auth_hdr(void **image, efi_uintn_t *image_size) +{ + struct efi_firmware_image_authentication *auth_hdr; + efi_status_t ret = EFI_INVALID_PARAMETER; + + auth_hdr = (struct efi_firmware_image_authentication *)*image; + if (*image_size < sizeof(*auth_hdr)) + goto out; + + if (auth_hdr->auth_info.hdr.dwLength <= + offsetof(struct win_certificate_uefi_guid, cert_data)) + goto out; + + *image = (uint8_t *)*image + sizeof(auth_hdr->monotonic_count) + + auth_hdr->auth_info.hdr.dwLength; + *image_size = *image_size - auth_hdr->auth_info.hdr.dwLength - + sizeof(auth_hdr->monotonic_count); + + ret = EFI_SUCCESS; +out: + return ret; +} + +#if defined(CONFIG_EFI_CAPSULE_AUTHENTICATE) +int efi_get_public_key_data(void **pkey, efi_uintn_t *pkey_len) +{ + const void *fdt_blob = gd->fdt_blob; + const void *blob; + const char *cnode_name = "capsule-key"; + const char *snode_name = "signature"; + int sig_node; + int len; + + sig_node = fdt_subnode_offset(fdt_blob, 0, snode_name); + if (sig_node < 0) { + log_err("Unable to get signature node offset\n"); + + return -FDT_ERR_NOTFOUND; + } + + blob = fdt_getprop(fdt_blob, sig_node, cnode_name, &len); + + if (!blob || len < 0) { + log_err("Unable to get capsule-key value\n"); + *pkey = NULL; + *pkey_len = 0; + + return -FDT_ERR_NOTFOUND; + } + + *pkey = (void *)blob; + *pkey_len = len; + + return 0; +} + +efi_status_t efi_capsule_authenticate(const void *capsule, efi_uintn_t capsule_size, + void **image, efi_uintn_t *image_size) +{ + u8 *buf; + int ret; + void *fdt_pkey, *pkey; + efi_uintn_t pkey_len; + uint64_t monotonic_count; + struct efi_signature_store *truststore; + struct pkcs7_message *capsule_sig; + struct efi_image_regions *regs; + struct efi_firmware_image_authentication *auth_hdr; + efi_status_t status; + + status = EFI_SECURITY_VIOLATION; + capsule_sig = NULL; + truststore = NULL; + regs = NULL; + + /* Sanity checks */ + if (capsule == NULL || capsule_size == 0) + goto out; + + *image = (uint8_t *)capsule; + *image_size = capsule_size; + if (efi_remove_auth_hdr(image, image_size) != EFI_SUCCESS) + goto out; + + auth_hdr = (struct efi_firmware_image_authentication *)capsule; + if (guidcmp(&auth_hdr->auth_info.cert_type, &efi_guid_cert_type_pkcs7)) + goto out; + + memcpy(&monotonic_count, &auth_hdr->monotonic_count, + sizeof(monotonic_count)); + + /* data to be digested */ + regs = calloc(sizeof(*regs) + sizeof(struct image_region) * 2, 1); + if (!regs) + goto out; + + regs->max = 2; + efi_image_region_add(regs, (uint8_t *)*image, + (uint8_t *)*image + *image_size, 1); + + efi_image_region_add(regs, (uint8_t *)&monotonic_count, + (uint8_t *)&monotonic_count + sizeof(monotonic_count), + 1); + + capsule_sig = efi_parse_pkcs7_header(auth_hdr->auth_info.cert_data, + auth_hdr->auth_info.hdr.dwLength + - sizeof(auth_hdr->auth_info), + &buf); + if (!capsule_sig) { + debug("Parsing variable's pkcs7 header failed\n"); + goto out; + } + + ret = efi_get_public_key_data(&fdt_pkey, &pkey_len); + if (ret < 0) + goto out; + + pkey = malloc(pkey_len); + if (!pkey) + goto out; + + memcpy(pkey, fdt_pkey, pkey_len); + truststore = efi_build_signature_store(pkey, pkey_len); + if (!truststore) + goto out; + + /* verify signature */ + if (efi_signature_verify(regs, capsule_sig, truststore, NULL)) { + debug("Verified\n"); + } else { + debug("Verifying variable's signature failed\n"); + goto out; + } + + status = EFI_SUCCESS; + +out: + efi_sigstore_free(truststore); + pkcs7_free_message(capsule_sig); + free(regs); + + return status; +} +#endif /* CONFIG_EFI_CAPSULE_AUTHENTICATE */ + +static __maybe_unused bool fwu_empty_capsule(struct efi_capsule_header *capsule) +{ + return !guidcmp(&capsule->capsule_guid, + &fwu_guid_os_request_fw_revert) || + !guidcmp(&capsule->capsule_guid, + &fwu_guid_os_request_fw_accept); +} + +static __maybe_unused efi_status_t fwu_to_efi_error(int err) +{ + efi_status_t ret; + + switch(err) { + case 0: + ret = EFI_SUCCESS; + break; + case -ERANGE: + case -EIO: + ret = EFI_DEVICE_ERROR; + break; + case -EINVAL: + ret = EFI_INVALID_PARAMETER; + break; + case -ENODEV: + ret = EFI_NOT_FOUND; + break; + default: + ret = EFI_OUT_OF_RESOURCES; + } + + return ret; +} + +static __maybe_unused efi_status_t fwu_empty_capsule_process( + struct efi_capsule_header *capsule) +{ + int status; + u32 active_idx; + efi_guid_t *image_guid; + efi_status_t ret = EFI_INVALID_PARAMETER; + + if (!guidcmp(&capsule->capsule_guid, + &fwu_guid_os_request_fw_revert)) { + /* + * One of the previously updated image has + * failed the OS acceptance test. OS has + * requested to revert back to the earlier + * boot index + */ + status = fwu_revert_boot_index(); + ret = fwu_to_efi_error(status); + if (ret == EFI_SUCCESS) + log_debug("Reverted the FWU active_index. Recommend rebooting the system\n"); + else + log_err("Failed to revert the FWU boot index\n"); + } else if (!guidcmp(&capsule->capsule_guid, + &fwu_guid_os_request_fw_accept)) { + /* + * Image accepted by the OS. Set the acceptance + * status for the image. + */ + image_guid = (void *)(char *)capsule + + capsule->header_size; + + status = fwu_get_active_index(&active_idx); + ret = fwu_to_efi_error(status); + if (ret != EFI_SUCCESS) { + log_err("Unable to get the active_index from the FWU metadata\n"); + return ret; + } + + status = fwu_accept_image(image_guid, active_idx); + ret = fwu_to_efi_error(status); + if (ret != EFI_SUCCESS) + log_err("Unable to set the Accept bit for the image %pUs\n", + image_guid); + + status = fwu_state_machine_updates(0, active_idx); + if (status < 0) + ret = EFI_DEVICE_ERROR; + + } + + return ret; +} + +static __maybe_unused void fwu_post_update_checks( + struct efi_capsule_header *capsule, + bool *fw_accept_os, bool *capsule_update) +{ + if (fwu_empty_capsule(capsule)) + *capsule_update = false; + else + if (!*fw_accept_os) + *fw_accept_os = + capsule->flags & FW_ACCEPT_OS ? true : false; +} + +static __maybe_unused efi_status_t fwu_post_update_process(bool fw_accept_os) +{ + int status; + uint update_index; + efi_status_t ret; + + status = fwu_plat_get_update_index(&update_index); + if (status < 0) { + log_err("Failed to get the FWU update_index value\n"); + return EFI_DEVICE_ERROR; + } + + /* + * All the capsules have been updated successfully, + * update the FWU metadata. + */ + log_debug("Update Complete. Now updating active_index to %u\n", + update_index); + status = fwu_set_active_index(update_index); + ret = fwu_to_efi_error(status); + if (ret != EFI_SUCCESS) { + log_err("Failed to update FWU metadata index values\n"); + } else { + log_debug("Successfully updated the active_index\n"); + status = fwu_state_machine_updates(fw_accept_os ? 1 : 0, + update_index); + if (status < 0) + ret = EFI_DEVICE_ERROR; + } + + return ret; +} + +/** + * efi_capsule_update_firmware - update firmware from capsule + * @capsule_data: Capsule + * + * Update firmware, using a capsule, @capsule_data. Loading any FMP + * drivers embedded in a capsule is not supported. + * + * Return: status code + */ +static efi_status_t efi_capsule_update_firmware( + struct efi_capsule_header *capsule_data) +{ + struct efi_firmware_management_capsule_header *capsule; + struct efi_firmware_management_capsule_image_header *image; + size_t capsule_size, image_binary_size; + void *image_binary, *vendor_code; + efi_handle_t *handles; + efi_uintn_t no_handles; + int item; + struct efi_firmware_management_protocol *fmp; + u16 *abort_reason; + efi_guid_t *image_type_id; + efi_status_t ret = EFI_SUCCESS; + int status; + uint update_index; + bool fw_accept_os; + + if (IS_ENABLED(CONFIG_FWU_MULTI_BANK_UPDATE)) { + if (fwu_empty_capsule(capsule_data)) { + if (fwu_empty_capsule_checks_pass()) { + return fwu_empty_capsule_process(capsule_data); + } else { + log_err("FWU empty capsule checks failed. Cannot start update\n"); + return EFI_INVALID_PARAMETER; + } + } + + if (!fwu_update_checks_pass()) { + log_err("FWU checks failed. Cannot start update\n"); + return EFI_INVALID_PARAMETER; + } + + /* Obtain the update_index from the platform */ + status = fwu_plat_get_update_index(&update_index); + if (status < 0) { + log_err("Failed to get the FWU update_index value\n"); + return EFI_DEVICE_ERROR; + } + + fw_accept_os = capsule_data->flags & FW_ACCEPT_OS ? 0x1 : 0x0; + } + + if (guidcmp(&capsule_data->capsule_guid, + &efi_guid_firmware_management_capsule_id)) { + log_err("Unsupported capsule type: %pUs\n", + &capsule_data->capsule_guid); + return EFI_UNSUPPORTED; + } + + /* sanity check */ + if (capsule_data->header_size < sizeof(*capsule) || + capsule_data->header_size >= capsule_data->capsule_image_size) + return EFI_INVALID_PARAMETER; + + capsule = (void *)capsule_data + capsule_data->header_size; + capsule_size = capsule_data->capsule_image_size + - capsule_data->header_size; + + if (capsule->version != 0x00000001) + return EFI_UNSUPPORTED; + + handles = NULL; + ret = EFI_CALL(efi_locate_handle_buffer( + BY_PROTOCOL, + &efi_guid_firmware_management_protocol, + NULL, &no_handles, (efi_handle_t **)&handles)); + if (ret != EFI_SUCCESS) + return EFI_UNSUPPORTED; + + /* Payload */ + for (item = capsule->embedded_driver_count; + item < capsule->embedded_driver_count + + capsule->payload_item_count; item++) { + /* sanity check */ + if ((capsule->item_offset_list[item] + sizeof(*image) + >= capsule_size)) { + log_err("Capsule does not have enough data\n"); + ret = EFI_INVALID_PARAMETER; + goto out; + } + + image = (void *)capsule + capsule->item_offset_list[item]; + + if (image->version != 0x00000003) { + ret = EFI_UNSUPPORTED; + goto out; + } + + /* find a device for update firmware */ + fmp = efi_fmp_find(&image->update_image_type_id, + image->update_image_index, + image->update_hardware_instance, + handles, no_handles); + if (!fmp) { + log_err("FMP driver not found for firmware type %pUs, hardware instance %lld\n", + &image->update_image_type_id, + image->update_hardware_instance); + ret = EFI_UNSUPPORTED; + goto out; + } + + /* do update */ + if (IS_ENABLED(CONFIG_EFI_CAPSULE_AUTHENTICATE) && + !(image->image_capsule_support & + CAPSULE_SUPPORT_AUTHENTICATION)) { + /* no signature */ + ret = EFI_SECURITY_VIOLATION; + goto out; + } + + image_binary = (void *)image + sizeof(*image); + image_binary_size = image->update_image_size; + vendor_code = image_binary + image_binary_size; + if (!IS_ENABLED(CONFIG_EFI_CAPSULE_AUTHENTICATE) && + (image->image_capsule_support & + CAPSULE_SUPPORT_AUTHENTICATION)) { + ret = efi_remove_auth_hdr(&image_binary, + &image_binary_size); + if (ret != EFI_SUCCESS) + goto out; + } + + abort_reason = NULL; + ret = EFI_CALL(fmp->set_image(fmp, image->update_image_index, + image_binary, + image_binary_size, + vendor_code, NULL, + &abort_reason)); + if (ret != EFI_SUCCESS) { + log_err("Firmware update failed: %ls\n", + abort_reason); + efi_free_pool(abort_reason); + goto out; + } + + if (IS_ENABLED(CONFIG_FWU_MULTI_BANK_UPDATE)) { + image_type_id = &image->update_image_type_id; + if (!fw_accept_os) { + /* + * The OS will not be accepting the firmware + * images. Set the accept bit of all the + * images contained in this capsule. + */ + status = fwu_accept_image(image_type_id, + update_index); + } else { + status = fwu_clear_accept_image(image_type_id, + update_index); + } + ret = fwu_to_efi_error(status); + if (ret != EFI_SUCCESS) { + log_err("Unable to %s the accept bit for the image %pUs\n", + fw_accept_os ? "clear" : "set", + image_type_id); + goto out; + } + + log_debug("%s the accepted bit for Image %pUs\n", + fw_accept_os ? "Cleared" : "Set", + image_type_id); + } + + } + +out: + efi_free_pool(handles); + + return ret; +} +#else +static efi_status_t efi_capsule_update_firmware( + struct efi_capsule_header *capsule_data) +{ + return EFI_UNSUPPORTED; +} +#endif /* CONFIG_EFI_CAPSULE_FIRMWARE_MANAGEMENT */ + +/** + * efi_update_capsule() - process information from operating system + * @capsule_header_array: Array of virtual address pointers + * @capsule_count: Number of pointers in capsule_header_array + * @scatter_gather_list: Array of physical address pointers + * + * This function implements the UpdateCapsule() runtime service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +efi_status_t EFIAPI efi_update_capsule( + struct efi_capsule_header **capsule_header_array, + efi_uintn_t capsule_count, + u64 scatter_gather_list) +{ + struct efi_capsule_header *capsule; + unsigned int i; + efi_status_t ret; + + EFI_ENTRY("%p, %zu, %llu\n", capsule_header_array, capsule_count, + scatter_gather_list); + + if (!capsule_count) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + ret = EFI_SUCCESS; + for (i = 0, capsule = *capsule_header_array; i < capsule_count; + i++, capsule = *(++capsule_header_array)) { + /* sanity check */ + if (capsule->header_size < sizeof(*capsule) || + capsule->capsule_image_size < sizeof(*capsule)) { + log_err("Capsule does not have enough data\n"); + continue; + } + + log_debug("Capsule[%d] (guid:%pUs)\n", + i, &capsule->capsule_guid); + ret = efi_capsule_update_firmware(capsule); + if (ret != EFI_SUCCESS) + goto out; + } + + if (IS_ENABLED(CONFIG_EFI_ESRT)) { + /* Rebuild the ESRT to reflect any updated FW images. */ + ret = efi_esrt_populate(); + if (ret != EFI_SUCCESS) + log_warning("ESRT update failed\n"); + } +out: + + return EFI_EXIT(ret); +} + +/** + * efi_query_capsule_caps() - check if capsule is supported + * @capsule_header_array: Array of virtual pointers + * @capsule_count: Number of pointers in capsule_header_array + * @maximum_capsule_size: Maximum capsule size + * @reset_type: Type of reset needed for capsule update + * + * This function implements the QueryCapsuleCapabilities() runtime service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +efi_status_t EFIAPI efi_query_capsule_caps( + struct efi_capsule_header **capsule_header_array, + efi_uintn_t capsule_count, + u64 *maximum_capsule_size, + u32 *reset_type) +{ + struct efi_capsule_header *capsule __attribute__((unused)); + unsigned int i; + efi_status_t ret; + + EFI_ENTRY("%p, %zu, %p, %p\n", capsule_header_array, capsule_count, + maximum_capsule_size, reset_type); + + if (!maximum_capsule_size) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + *maximum_capsule_size = U64_MAX; + *reset_type = EFI_RESET_COLD; + + ret = EFI_SUCCESS; + for (i = 0, capsule = *capsule_header_array; i < capsule_count; + i++, capsule = *(++capsule_header_array)) { + /* TODO */ + } +out: + return EFI_EXIT(ret); +} + +/** + * efi_load_capsule_drivers - initialize capsule drivers + * + * Generic FMP drivers backed by DFU + * + * Return: status code + */ +efi_status_t __weak efi_load_capsule_drivers(void) +{ + __maybe_unused efi_handle_t handle; + efi_status_t ret = EFI_SUCCESS; + + if (IS_ENABLED(CONFIG_EFI_CAPSULE_FIRMWARE_FIT)) { + handle = NULL; + ret = efi_install_multiple_protocol_interfaces(&handle, + &efi_guid_firmware_management_protocol, + &efi_fmp_fit, + NULL); + } + + if (IS_ENABLED(CONFIG_EFI_CAPSULE_FIRMWARE_RAW)) { + handle = NULL; + ret = efi_install_multiple_protocol_interfaces(&handle, + &efi_guid_firmware_management_protocol, + &efi_fmp_raw, + NULL); + } + + return ret; +} + +#ifdef CONFIG_EFI_CAPSULE_ON_DISK +/** + * get_dp_device - retrieve a device path from boot variable + * @boot_var: Boot variable name + * @device_dp Device path + * + * Retrieve a device patch from boot variable, @boot_var. + * + * Return: status code + */ +static efi_status_t get_dp_device(u16 *boot_var, + struct efi_device_path **device_dp) +{ + void *buf = NULL; + efi_uintn_t size; + struct efi_load_option lo; + struct efi_device_path *file_dp; + efi_status_t ret; + + size = 0; + ret = efi_get_variable_int(boot_var, &efi_global_variable_guid, + NULL, &size, NULL, NULL); + if (ret == EFI_BUFFER_TOO_SMALL) { + buf = malloc(size); + if (!buf) + return EFI_OUT_OF_RESOURCES; + ret = efi_get_variable_int(boot_var, &efi_global_variable_guid, + NULL, &size, buf, NULL); + } + if (ret != EFI_SUCCESS) + return ret; + + efi_deserialize_load_option(&lo, buf, &size); + + if (lo.attributes & LOAD_OPTION_ACTIVE) { + efi_dp_split_file_path(lo.file_path, device_dp, &file_dp); + efi_free_pool(file_dp); + + ret = EFI_SUCCESS; + } else { + ret = EFI_NOT_FOUND; + } + + free(buf); + + return ret; +} + +/** + * device_is_present_and_system_part - check if a device exists + * + * Check if a device pointed to by the device path, @dp, exists and is + * located in UEFI system partition. + * + * @dp device path + * Return: true - yes, false - no + */ +static bool device_is_present_and_system_part(struct efi_device_path *dp) +{ + efi_handle_t handle; + struct efi_device_path *rem; + + /* Check device exists */ + handle = efi_dp_find_obj(dp, NULL, NULL); + if (!handle) + return false; + + /* Check device is on system partition */ + handle = efi_dp_find_obj(dp, &efi_system_partition_guid, &rem); + if (!handle) + return false; + + return true; +} + +/** + * find_boot_device - identify the boot device + * + * Identify the boot device from boot-related variables as UEFI + * specification describes and put its handle into bootdev_root. + * + * Return: status code + */ +static efi_status_t find_boot_device(void) +{ + char boot_var[9]; + u16 boot_var16[9], *p, bootnext, *boot_order = NULL; + efi_uintn_t size; + int i, num; + struct efi_simple_file_system_protocol *volume; + struct efi_device_path *boot_dev = NULL; + efi_status_t ret; + + /* find active boot device in BootNext */ + bootnext = 0; + size = sizeof(bootnext); + ret = efi_get_variable_int(u"BootNext", + (efi_guid_t *)&efi_global_variable_guid, + NULL, &size, &bootnext, NULL); + if (ret == EFI_SUCCESS || ret == EFI_BUFFER_TOO_SMALL) { + /* BootNext does exist here */ + if (ret == EFI_BUFFER_TOO_SMALL || size != sizeof(u16)) { + log_err("BootNext must be 16-bit integer\n"); + goto skip; + } + sprintf((char *)boot_var, "Boot%04X", bootnext); + p = boot_var16; + utf8_utf16_strcpy(&p, boot_var); + + ret = get_dp_device(boot_var16, &boot_dev); + if (ret == EFI_SUCCESS) { + if (device_is_present_and_system_part(boot_dev)) { + goto found; + } else { + efi_free_pool(boot_dev); + boot_dev = NULL; + } + } + } + +skip: + /* find active boot device in BootOrder */ + size = 0; + ret = efi_get_variable_int(u"BootOrder", &efi_global_variable_guid, + NULL, &size, NULL, NULL); + if (ret == EFI_BUFFER_TOO_SMALL) { + boot_order = malloc(size); + if (!boot_order) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + + ret = efi_get_variable_int(u"BootOrder", + &efi_global_variable_guid, + NULL, &size, boot_order, NULL); + } + if (ret != EFI_SUCCESS) + goto out; + + /* check in higher order */ + num = size / sizeof(u16); + for (i = 0; i < num; i++) { + sprintf((char *)boot_var, "Boot%04X", boot_order[i]); + p = boot_var16; + utf8_utf16_strcpy(&p, boot_var); + ret = get_dp_device(boot_var16, &boot_dev); + if (ret != EFI_SUCCESS) + continue; + + if (device_is_present_and_system_part(boot_dev)) + break; + + efi_free_pool(boot_dev); + boot_dev = NULL; + } +found: + if (boot_dev) { + log_debug("Boot device %pD\n", boot_dev); + + volume = efi_fs_from_path(boot_dev); + if (!volume) + ret = EFI_DEVICE_ERROR; + else + ret = EFI_CALL(volume->open_volume(volume, + &bootdev_root)); + efi_free_pool(boot_dev); + } else { + ret = EFI_NOT_FOUND; + } +out: + free(boot_order); + + return ret; +} + +/** + * efi_capsule_scan_dir - traverse a capsule directory in boot device + * @files: Array of file names + * @num: Number of elements in @files + * + * Traverse a capsule directory in boot device. + * Called by initialization code, and returns an array of capsule file + * names in @files. + * + * Return: status code + */ +static efi_status_t efi_capsule_scan_dir(u16 ***files, unsigned int *num) +{ + struct efi_file_handle *dirh; + struct efi_file_info *dirent; + efi_uintn_t dirent_size, tmp_size; + unsigned int count; + u16 **tmp_files; + efi_status_t ret; + + ret = find_boot_device(); + if (ret == EFI_NOT_FOUND) { + log_debug("Boot device is not set\n"); + *num = 0; + return EFI_SUCCESS; + } else if (ret != EFI_SUCCESS) { + return EFI_DEVICE_ERROR; + } + + /* count capsule files */ + ret = EFI_CALL((*bootdev_root->open)(bootdev_root, &dirh, + EFI_CAPSULE_DIR, + EFI_FILE_MODE_READ, 0)); + if (ret != EFI_SUCCESS) { + *num = 0; + return EFI_SUCCESS; + } + + dirent_size = 256; + dirent = malloc(dirent_size); + if (!dirent) + return EFI_OUT_OF_RESOURCES; + + count = 0; + while (1) { + tmp_size = dirent_size; + ret = EFI_CALL((*dirh->read)(dirh, &tmp_size, dirent)); + if (ret == EFI_BUFFER_TOO_SMALL) { + struct efi_file_info *old_dirent = dirent; + + dirent = realloc(dirent, tmp_size); + if (!dirent) { + dirent = old_dirent; + ret = EFI_OUT_OF_RESOURCES; + goto err; + } + dirent_size = tmp_size; + ret = EFI_CALL((*dirh->read)(dirh, &tmp_size, dirent)); + } + if (ret != EFI_SUCCESS) + goto err; + if (!tmp_size) + break; + + if (!(dirent->attribute & EFI_FILE_DIRECTORY)) + count++; + } + + ret = EFI_CALL((*dirh->setpos)(dirh, 0)); + if (ret != EFI_SUCCESS) + goto err; + + /* make a list */ + tmp_files = malloc(count * sizeof(*tmp_files)); + if (!tmp_files) { + ret = EFI_OUT_OF_RESOURCES; + goto err; + } + + count = 0; + while (1) { + tmp_size = dirent_size; + ret = EFI_CALL((*dirh->read)(dirh, &tmp_size, dirent)); + if (ret != EFI_SUCCESS) + goto err; + if (!tmp_size) + break; + + if (!(dirent->attribute & EFI_FILE_DIRECTORY) && + u16_strcmp(dirent->file_name, u".") && + u16_strcmp(dirent->file_name, u"..")) + tmp_files[count++] = u16_strdup(dirent->file_name); + } + /* ignore an error */ + EFI_CALL((*dirh->close)(dirh)); + + /* + * Capsule files are applied in case insensitive alphabetic order + * + * TODO: special handling of rightmost period + */ + qsort(tmp_files, count, sizeof(*tmp_files), + (int (*)(const void *, const void *))u16_strcasecmp); + *files = tmp_files; + *num = count; + ret = EFI_SUCCESS; +err: + free(dirent); + + return ret; +} + +/** + * efi_capsule_read_file - read in a capsule file + * @filename: File name + * @capsule: Pointer to buffer for capsule + * + * Read a capsule file and put its content in @capsule. + * + * Return: status code + */ +static efi_status_t efi_capsule_read_file(const u16 *filename, + struct efi_capsule_header **capsule) +{ + struct efi_file_handle *dirh, *fh; + struct efi_file_info *file_info = NULL; + struct efi_capsule_header *buf = NULL; + efi_uintn_t size; + efi_status_t ret; + + ret = EFI_CALL((*bootdev_root->open)(bootdev_root, &dirh, + EFI_CAPSULE_DIR, + EFI_FILE_MODE_READ, 0)); + if (ret != EFI_SUCCESS) + return ret; + ret = EFI_CALL((*dirh->open)(dirh, &fh, (u16 *)filename, + EFI_FILE_MODE_READ, 0)); + /* ignore an error */ + EFI_CALL((*dirh->close)(dirh)); + if (ret != EFI_SUCCESS) + return ret; + + /* file size */ + size = 0; + ret = EFI_CALL((*fh->getinfo)(fh, &efi_file_info_guid, + &size, file_info)); + if (ret == EFI_BUFFER_TOO_SMALL) { + file_info = malloc(size); + if (!file_info) { + ret = EFI_OUT_OF_RESOURCES; + goto err; + } + ret = EFI_CALL((*fh->getinfo)(fh, &efi_file_info_guid, + &size, file_info)); + } + if (ret != EFI_SUCCESS) + goto err; + size = file_info->file_size; + free(file_info); + buf = malloc(size); + if (!buf) { + ret = EFI_OUT_OF_RESOURCES; + goto err; + } + + /* fetch data */ + ret = EFI_CALL((*fh->read)(fh, &size, buf)); + if (ret == EFI_SUCCESS) { + if (size >= buf->capsule_image_size) { + *capsule = buf; + } else { + free(buf); + ret = EFI_INVALID_PARAMETER; + } + } else { + free(buf); + } +err: + EFI_CALL((*fh->close)(fh)); + + return ret; +} + +/** + * efi_capsule_delete_file - delete a capsule file + * @filename: File name + * + * Delete a capsule file from capsule directory. + * + * Return: status code + */ +static efi_status_t efi_capsule_delete_file(const u16 *filename) +{ + struct efi_file_handle *dirh, *fh; + efi_status_t ret; + + ret = EFI_CALL((*bootdev_root->open)(bootdev_root, &dirh, + EFI_CAPSULE_DIR, + EFI_FILE_MODE_READ, 0)); + if (ret != EFI_SUCCESS) + return ret; + ret = EFI_CALL((*dirh->open)(dirh, &fh, (u16 *)filename, + EFI_FILE_MODE_READ, 0)); + /* ignore an error */ + EFI_CALL((*dirh->close)(dirh)); + + if (ret == EFI_SUCCESS) + ret = EFI_CALL((*fh->delete)(fh)); + + return ret; +} + +/** + * efi_capsule_scan_done - reset a scan help function + * + * Reset a scan help function + */ +static void efi_capsule_scan_done(void) +{ + EFI_CALL((*bootdev_root->close)(bootdev_root)); + bootdev_root = NULL; +} + +/** + * check_run_capsules() - check whether capsule update should run + * + * The spec says OsIndications must be set in order to run the capsule update + * on-disk. Since U-Boot doesn't support runtime SetVariable, allow capsules to + * run explicitly if CONFIG_EFI_IGNORE_OSINDICATIONS is selected + * + * Return: EFI_SUCCESS if update to run, EFI_NOT_FOUND otherwise + */ +static efi_status_t check_run_capsules(void) +{ + u64 os_indications = 0x0; + efi_uintn_t size; + efi_status_t r; + + size = sizeof(os_indications); + r = efi_get_variable_int(u"OsIndications", &efi_global_variable_guid, + NULL, &size, &os_indications, NULL); + if (!IS_ENABLED(CONFIG_EFI_IGNORE_OSINDICATIONS) && + (r != EFI_SUCCESS || size != sizeof(os_indications))) + return EFI_NOT_FOUND; + + if (os_indications & + EFI_OS_INDICATIONS_FILE_CAPSULE_DELIVERY_SUPPORTED) { + os_indications &= + ~EFI_OS_INDICATIONS_FILE_CAPSULE_DELIVERY_SUPPORTED; + r = efi_set_variable_int(u"OsIndications", + &efi_global_variable_guid, + EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS, + sizeof(os_indications), + &os_indications, false); + if (r != EFI_SUCCESS) + log_err("Setting %ls failed\n", L"OsIndications"); + return EFI_SUCCESS; + } else if (IS_ENABLED(CONFIG_EFI_IGNORE_OSINDICATIONS)) { + return EFI_SUCCESS; + } else { + return EFI_NOT_FOUND; + } +} + +/** + * efi_launch_capsule - launch capsules + * + * Launch all the capsules in system at boot time. + * Called by efi init code + * + * Return: status codde + */ +efi_status_t efi_launch_capsules(void) +{ + struct efi_capsule_header *capsule = NULL; + u16 **files; + unsigned int nfiles, index, index_max, i; + efi_status_t ret; + bool capsule_update = true; + bool update_status = true; + bool fw_accept_os = false; + + if (check_run_capsules() != EFI_SUCCESS) + return EFI_SUCCESS; + + index_max = get_max_capsule(); + index = get_last_capsule(); + + /* + * Find capsules on disk. + * All the capsules are collected at the beginning because + * capsule files will be removed instantly. + */ + nfiles = 0; + files = NULL; + ret = efi_capsule_scan_dir(&files, &nfiles); + if (ret != EFI_SUCCESS) + return ret; + if (!nfiles) + return EFI_SUCCESS; + + /* Launch capsules */ + for (i = 0, ++index; i < nfiles; i++, index++) { + log_debug("Applying %ls\n", files[i]); + if (index > index_max) + index = 0; + ret = efi_capsule_read_file(files[i], &capsule); + if (ret == EFI_SUCCESS) { + ret = efi_capsule_update_firmware(capsule); + if (ret != EFI_SUCCESS) { + log_err("Applying capsule %ls failed.\n", + files[i]); + update_status = false; + } else { + log_info("Applying capsule %ls succeeded.\n", + files[i]); + if (IS_ENABLED(CONFIG_FWU_MULTI_BANK_UPDATE)) { + fwu_post_update_checks(capsule, + &fw_accept_os, + &capsule_update); + } + } + + /* create CapsuleXXXX */ + set_capsule_result(index, capsule, ret); + + free(capsule); + } else { + log_err("Reading capsule %ls failed\n", files[i]); + update_status = false; + } + /* delete a capsule either in case of success or failure */ + ret = efi_capsule_delete_file(files[i]); + if (ret != EFI_SUCCESS) + log_err("Deleting capsule %ls failed\n", + files[i]); + } + + efi_capsule_scan_done(); + + if (IS_ENABLED(CONFIG_FWU_MULTI_BANK_UPDATE)) { + if (capsule_update == true && update_status == true) { + ret = fwu_post_update_process(fw_accept_os); + } else if (capsule_update == true && update_status == false) { + log_err("All capsules were not updated. Not updating FWU metadata\n"); + } + } + + for (i = 0; i < nfiles; i++) + free(files[i]); + free(files); + + /* + * UEFI spec requires to reset system after complete processing capsule + * update on the storage. + */ + log_info("Reboot after firmware update.\n"); + /* Cold reset is required for loading the new firmware. */ + sysreset_walk_halt(SYSRESET_COLD); + hang(); + /* not reach here */ + + return 0; +} +#endif /* CONFIG_EFI_CAPSULE_ON_DISK */ diff --git a/lib/efi_loader/efi_conformance.c b/lib/efi_loader/efi_conformance.c new file mode 100644 index 00000000000..2bae93a94bd --- /dev/null +++ b/lib/efi_loader/efi_conformance.c @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * EFI conformance profile table + * + * Copyright (C) 2022 Arm Ltd. + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <efi_loader.h> +#include <log.h> +#include <efi_api.h> +#include <malloc.h> + +static const efi_guid_t efi_ecpt_guid = EFI_CONFORMANCE_PROFILES_TABLE_GUID; +static const efi_guid_t efi_ebbr_2_1_guid = + EFI_CONFORMANCE_PROFILE_EBBR_2_1_GUID; + +/** + * efi_ecpt_register() - Install the ECPT system table. + * + * Return: status code + */ +efi_status_t efi_ecpt_register(void) +{ + u16 num_entries = 0; + struct efi_conformance_profiles_table *ecpt; + efi_status_t ret; + size_t ecpt_size; + + ecpt_size = num_entries * sizeof(efi_guid_t) + + sizeof(struct efi_conformance_profiles_table); + ret = efi_allocate_pool(EFI_BOOT_SERVICES_DATA, ecpt_size, + (void **)&ecpt); + + if (ret != EFI_SUCCESS) { + log_err("Out of memory\n"); + + return ret; + } + + if (CONFIG_IS_ENABLED(EFI_EBBR_2_1_CONFORMANCE)) + guidcpy(&ecpt->conformance_profiles[num_entries++], + &efi_ebbr_2_1_guid); + + ecpt->version = EFI_CONFORMANCE_PROFILES_TABLE_VERSION; + ecpt->number_of_profiles = num_entries; + + /* Install the ECPT in the system configuration table. */ + ret = efi_install_configuration_table(&efi_ecpt_guid, (void *)ecpt); + if (ret != EFI_SUCCESS) { + log_err("Failed to install ECPT\n"); + efi_free_pool(ecpt); + + return ret; + } + + log_debug("ECPT created\n"); + + return EFI_SUCCESS; +} diff --git a/lib/efi_loader/efi_console.c b/lib/efi_loader/efi_console.c new file mode 100644 index 00000000000..9d9f786a6db --- /dev/null +++ b/lib/efi_loader/efi_console.c @@ -0,0 +1,1426 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI application console interface + * + * Copyright (c) 2016 Alexander Graf + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <ansi.h> +#include <charset.h> +#include <malloc.h> +#include <time.h> +#include <dm/device.h> +#include <efi_loader.h> +#include <env.h> +#include <log.h> +#include <stdio_dev.h> +#include <video_console.h> +#include <linux/delay.h> + +#define EFI_COUT_MODE_2 2 +#define EFI_MAX_COUT_MODE 3 + +struct cout_mode { + unsigned long columns; + unsigned long rows; + int present; +}; + +__maybe_unused static struct efi_object uart_obj; + +static struct cout_mode efi_cout_modes[] = { + /* EFI Mode 0 is 80x25 and always present */ + { + .columns = 80, + .rows = 25, + .present = 1, + }, + /* EFI Mode 1 is always 80x50 */ + { + .columns = 80, + .rows = 50, + .present = 0, + }, + /* Value are unknown until we query the console */ + { + .columns = 0, + .rows = 0, + .present = 0, + }, +}; + +const efi_guid_t efi_guid_text_input_ex_protocol = + EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL_GUID; +const efi_guid_t efi_guid_text_input_protocol = + EFI_SIMPLE_TEXT_INPUT_PROTOCOL_GUID; +const efi_guid_t efi_guid_text_output_protocol = + EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL_GUID; + +#define cESC '\x1b' +#define ESC "\x1b" + +/* + * efi_con_mode - mode information of the Simple Text Output Protocol + * + * Use safe settings before efi_setup_console_size() is called. + * By default enable only the 80x25 mode which must always exist. + */ +static struct simple_text_output_mode efi_con_mode = { + .max_mode = 1, + .mode = 0, + .attribute = 0, + .cursor_column = 0, + .cursor_row = 0, + .cursor_visible = 1, +}; + +/** + * term_get_char() - read a character from the console + * + * Wait for up to 100 ms to read a character from the console. + * + * @c: pointer to the buffer to receive the character + * Return: 0 on success, 1 otherwise + */ +static int term_get_char(s32 *c) +{ + u64 timeout; + + /* Wait up to 100 ms for a character */ + timeout = timer_get_us() + 100000; + + while (!tstc()) + if (timer_get_us() > timeout) + return 1; + + *c = getchar(); + return 0; +} + +/** + * term_read_reply() - receive and parse a reply from the terminal + * + * @n: array of return values + * @num: number of return values expected + * @end_char: character indicating end of terminal message + * Return: non-zero indicates error + */ +static int term_read_reply(int *n, int num, char end_char) +{ + s32 c; + int i = 0; + + if (term_get_char(&c) || c != cESC) + return -1; + + if (term_get_char(&c) || c != '[') + return -1; + + n[0] = 0; + while (1) { + if (!term_get_char(&c)) { + if (c == ';') { + i++; + if (i >= num) + return -1; + n[i] = 0; + continue; + } else if (c == end_char) { + break; + } else if (c > '9' || c < '0') { + return -1; + } + + /* Read one more decimal position */ + n[i] *= 10; + n[i] += c - '0'; + } else { + return -1; + } + } + if (i != num - 1) + return -1; + + return 0; +} + +/** + * efi_cout_output_string() - write Unicode string to console + * + * This function implements the OutputString service of the simple text output + * protocol. See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * @this: simple text output protocol + * @string: u16 string + * Return: status code + */ +static efi_status_t EFIAPI efi_cout_output_string( + struct efi_simple_text_output_protocol *this, + const u16 *string) +{ + struct simple_text_output_mode *con = &efi_con_mode; + struct cout_mode *mode = &efi_cout_modes[con->mode]; + char *buf, *pos; + const u16 *p; + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p, %p", this, string); + + if (!this || !string) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + buf = malloc(utf16_utf8_strlen(string) + 1); + if (!buf) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + pos = buf; + utf16_utf8_strcpy(&pos, string); + puts(buf); + free(buf); + + /* + * Update the cursor position. + * + * The UEFI spec provides advance rules for U+0000, U+0008, U+000A, + * and U000D. All other control characters are ignored. Any non-control + * character increase the column by one. + */ + for (p = string; *p; ++p) { + switch (*p) { + case '\b': /* U+0008, backspace */ + if (con->cursor_column) + con->cursor_column--; + break; + case '\n': /* U+000A, newline */ + con->cursor_column = 0; + con->cursor_row++; + break; + case '\r': /* U+000D, carriage-return */ + con->cursor_column = 0; + break; + case 0xd800 ... 0xdbff: + /* + * Ignore high surrogates, we do not want to count a + * Unicode character twice. + */ + break; + default: + /* Exclude control codes */ + if (*p > 0x1f) + con->cursor_column++; + break; + } + if (con->cursor_column >= mode->columns) { + con->cursor_column = 0; + con->cursor_row++; + } + /* + * When we exceed the row count the terminal will scroll up one + * line. We have to adjust the cursor position. + */ + if (con->cursor_row >= mode->rows && con->cursor_row) + con->cursor_row--; + } + +out: + return EFI_EXIT(ret); +} + +/** + * efi_cout_test_string() - test writing Unicode string to console + * + * This function implements the TestString service of the simple text output + * protocol. See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * As in OutputString we simply convert UTF-16 to UTF-8 there are no unsupported + * code points and we can always return EFI_SUCCESS. + * + * @this: simple text output protocol + * @string: u16 string + * Return: status code + */ +static efi_status_t EFIAPI efi_cout_test_string( + struct efi_simple_text_output_protocol *this, + const u16 *string) +{ + EFI_ENTRY("%p, %p", this, string); + return EFI_EXIT(EFI_SUCCESS); +} + +/** + * cout_mode_matches() - check if mode has given terminal size + * + * @mode: text mode + * @rows: number of rows + * @cols: number of columns + * Return: true if number of rows and columns matches the mode and + * the mode is present + */ +static bool cout_mode_matches(struct cout_mode *mode, int rows, int cols) +{ + if (!mode->present) + return false; + + return (mode->rows == rows) && (mode->columns == cols); +} + +/** + * query_console_serial() - query serial console size + * + * When using a serial console or the net console we can only devise the + * terminal size by querying the terminal using ECMA-48 control sequences. + * + * @rows: pointer to return number of rows + * @cols: pointer to return number of columns + * Returns: 0 on success + */ +static int query_console_serial(int *rows, int *cols) +{ + int ret = 0; + int n[2]; + + /* Empty input buffer */ + while (tstc()) + getchar(); + + /* + * Not all terminals understand CSI [18t for querying the console size. + * We should adhere to escape sequences documented in the console_codes + * man page and the ECMA-48 standard. + * + * So here we follow a different approach. We position the cursor to the + * bottom right and query its position. Before leaving the function we + * restore the original cursor position. + */ + printf(ESC "7" /* Save cursor position */ + ESC "[r" /* Set scrolling region to full window */ + ESC "[999;999H" /* Move to bottom right corner */ + ESC "[6n"); /* Query cursor position */ + + /* Read {rows,cols} */ + if (term_read_reply(n, 2, 'R')) { + ret = 1; + goto out; + } + + *cols = n[1]; + *rows = n[0]; +out: + printf(ESC "8"); /* Restore cursor position */ + return ret; +} + +/** + * query_vidconsole() - query video console size + * + * + * @rows: pointer to return number of rows + * @cols: pointer to return number of columns + * Returns: 0 on success + */ +static int __maybe_unused query_vidconsole(int *rows, int *cols) +{ + const char *stdout_name = env_get("stdout"); + struct stdio_dev *stdout_dev; + struct udevice *dev; + struct vidconsole_priv *priv; + + if (!stdout_name || strncmp(stdout_name, "vidconsole", 10)) + return -ENODEV; + stdout_dev = stdio_get_by_name("vidconsole"); + if (!stdout_dev) + return -ENODEV; + dev = stdout_dev->priv; + if (!dev) + return -ENODEV; + priv = dev_get_uclass_priv(dev); + if (!priv) + return -ENODEV; + *rows = priv->rows; + *cols = priv->cols; + return 0; +} + +/** + * efi_setup_console_size() - update the mode table. + * + * By default the only mode available is 80x25. If the console has at least 50 + * lines, enable mode 80x50. If we can query the console size and it is neither + * 80x25 nor 80x50, set it as an additional mode. + */ +void efi_setup_console_size(void) +{ + int rows = 25, cols = 80; + int ret = -ENODEV; + + if (IS_ENABLED(CONFIG_VIDEO)) + ret = query_vidconsole(&rows, &cols); + if (ret) + ret = query_console_serial(&rows, &cols); + if (ret) + return; + + log_debug("Console size %dx%d\n", rows, cols); + + /* Test if we can have Mode 1 */ + if (cols >= 80 && rows >= 50) { + efi_cout_modes[1].present = 1; + efi_con_mode.max_mode = 2; + } + + /* + * Install our mode as mode 2 if it is different + * than mode 0 or 1 and set it as the currently selected mode + */ + if (!cout_mode_matches(&efi_cout_modes[0], rows, cols) && + !cout_mode_matches(&efi_cout_modes[1], rows, cols)) { + efi_cout_modes[EFI_COUT_MODE_2].columns = cols; + efi_cout_modes[EFI_COUT_MODE_2].rows = rows; + efi_cout_modes[EFI_COUT_MODE_2].present = 1; + efi_con_mode.max_mode = EFI_MAX_COUT_MODE; + efi_con_mode.mode = EFI_COUT_MODE_2; + } +} + +/** + * efi_cout_query_mode() - get terminal size for a text mode + * + * This function implements the QueryMode service of the simple text output + * protocol. See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * @this: simple text output protocol + * @mode_number: mode number to retrieve information on + * @columns: number of columns + * @rows: number of rows + * Return: status code + */ +static efi_status_t EFIAPI efi_cout_query_mode( + struct efi_simple_text_output_protocol *this, + unsigned long mode_number, unsigned long *columns, + unsigned long *rows) +{ + EFI_ENTRY("%p, %ld, %p, %p", this, mode_number, columns, rows); + + if (mode_number >= efi_con_mode.max_mode) + return EFI_EXIT(EFI_UNSUPPORTED); + + if (efi_cout_modes[mode_number].present != 1) + return EFI_EXIT(EFI_UNSUPPORTED); + + if (columns) + *columns = efi_cout_modes[mode_number].columns; + if (rows) + *rows = efi_cout_modes[mode_number].rows; + + return EFI_EXIT(EFI_SUCCESS); +} + +static const struct { + unsigned int fg; + unsigned int bg; +} color[] = { + { 30, 40 }, /* 0: black */ + { 34, 44 }, /* 1: blue */ + { 32, 42 }, /* 2: green */ + { 36, 46 }, /* 3: cyan */ + { 31, 41 }, /* 4: red */ + { 35, 45 }, /* 5: magenta */ + { 33, 43 }, /* 6: brown, map to yellow as EDK2 does*/ + { 37, 47 }, /* 7: light gray, map to white */ +}; + +/** + * efi_cout_set_attribute() - set fore- and background color + * + * This function implements the SetAttribute service of the simple text output + * protocol. See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * @this: simple text output protocol + * @attribute: foreground color - bits 0-3, background color - bits 4-6 + * Return: status code + */ +static efi_status_t EFIAPI efi_cout_set_attribute( + struct efi_simple_text_output_protocol *this, + unsigned long attribute) +{ + unsigned int bold = EFI_ATTR_BOLD(attribute); + unsigned int fg = EFI_ATTR_FG(attribute); + unsigned int bg = EFI_ATTR_BG(attribute); + + EFI_ENTRY("%p, %lx", this, attribute); + + efi_con_mode.attribute = attribute; + if (attribute) + printf(ESC"[%u;%u;%um", bold, color[fg].fg, color[bg].bg); + else + printf(ESC"[0;37;40m"); + + return EFI_EXIT(EFI_SUCCESS); +} + +/** + * efi_clear_screen() - clear screen + */ +static void efi_clear_screen(void) +{ + if (CONFIG_IS_ENABLED(EFI_SCROLL_ON_CLEAR_SCREEN)) { + unsigned int row, screen_rows, screen_columns; + + /* Avoid overwriting previous outputs on streaming consoles */ + screen_rows = efi_cout_modes[efi_con_mode.mode].rows; + screen_columns = efi_cout_modes[efi_con_mode.mode].columns; + printf(ESC "[%u;%uH", screen_rows, screen_columns); + for (row = 1; row < screen_rows; row++) + printf("\n"); + } + + /* + * The Linux console wants both a clear and a home command. The video + * uclass does not support <ESC>[H without coordinates, yet. + */ + printf(ESC "[2J" ESC "[1;1H"); + efi_con_mode.cursor_column = 0; + efi_con_mode.cursor_row = 0; +} + +/** + * efi_cout_clear_screen() - clear screen + * + * This function implements the ClearScreen service of the simple text output + * protocol. See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * @this: pointer to the protocol instance + * Return: status code + */ +static efi_status_t EFIAPI efi_cout_clear_screen( + struct efi_simple_text_output_protocol *this) +{ + EFI_ENTRY("%p", this); + + /* Set default colors if not done yet */ + if (efi_con_mode.attribute == 0) { + efi_con_mode.attribute = 0x07; + printf(ESC "[0;37;40m"); + } + + efi_clear_screen(); + + return EFI_EXIT(EFI_SUCCESS); +} + +/** + * efi_cout_clear_set_mode() - set text model + * + * This function implements the SetMode service of the simple text output + * protocol. See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * @this: pointer to the protocol instance + * @mode_number: number of the text mode to set + * Return: status code + */ +static efi_status_t EFIAPI efi_cout_set_mode( + struct efi_simple_text_output_protocol *this, + unsigned long mode_number) +{ + EFI_ENTRY("%p, %ld", this, mode_number); + + if (mode_number >= efi_con_mode.max_mode) + return EFI_EXIT(EFI_UNSUPPORTED); + + if (!efi_cout_modes[mode_number].present) + return EFI_EXIT(EFI_UNSUPPORTED); + + efi_con_mode.mode = mode_number; + efi_clear_screen(); + + return EFI_EXIT(EFI_SUCCESS); +} + +/** + * efi_cout_reset() - reset the terminal + * + * This function implements the Reset service of the simple text output + * protocol. See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * @this: pointer to the protocol instance + * @extended_verification: if set an extended verification may be executed + * Return: status code + */ +static efi_status_t EFIAPI efi_cout_reset( + struct efi_simple_text_output_protocol *this, + char extended_verification) +{ + EFI_ENTRY("%p, %d", this, extended_verification); + + /* Set default colors */ + efi_con_mode.attribute = 0x07; + printf(ESC "[0;37;40m"); + /* Clear screen */ + efi_clear_screen(); + + return EFI_EXIT(EFI_SUCCESS); +} + +/** + * efi_cout_set_cursor_position() - reset the terminal + * + * This function implements the SetCursorPosition service of the simple text + * output protocol. See the Unified Extensible Firmware Interface (UEFI) + * specification for details. + * + * @this: pointer to the protocol instance + * @column: column to move to + * @row: row to move to + * Return: status code + */ +static efi_status_t EFIAPI efi_cout_set_cursor_position( + struct efi_simple_text_output_protocol *this, + unsigned long column, unsigned long row) +{ + efi_status_t ret = EFI_SUCCESS; + struct simple_text_output_mode *con = &efi_con_mode; + struct cout_mode *mode = &efi_cout_modes[con->mode]; + + EFI_ENTRY("%p, %ld, %ld", this, column, row); + + /* Check parameters */ + if (!this) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + if (row >= mode->rows || column >= mode->columns) { + ret = EFI_UNSUPPORTED; + goto out; + } + + /* + * Set cursor position by sending CSI H. + * EFI origin is [0, 0], terminal origin is [1, 1]. + */ + printf(ESC "[%d;%dH", (int)row + 1, (int)column + 1); + efi_con_mode.cursor_column = column; + efi_con_mode.cursor_row = row; +out: + return EFI_EXIT(ret); +} + +/** + * efi_cout_enable_cursor() - enable the cursor + * + * This function implements the EnableCursor service of the simple text output + * protocol. See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * @this: pointer to the protocol instance + * @enable: if true enable, if false disable the cursor + * Return: status code + */ +static efi_status_t EFIAPI efi_cout_enable_cursor( + struct efi_simple_text_output_protocol *this, + bool enable) +{ + EFI_ENTRY("%p, %d", this, enable); + + printf(ESC"[?25%c", enable ? 'h' : 'l'); + efi_con_mode.cursor_visible = !!enable; + + return EFI_EXIT(EFI_SUCCESS); +} + +struct efi_simple_text_output_protocol efi_con_out = { + .reset = efi_cout_reset, + .output_string = efi_cout_output_string, + .test_string = efi_cout_test_string, + .query_mode = efi_cout_query_mode, + .set_mode = efi_cout_set_mode, + .set_attribute = efi_cout_set_attribute, + .clear_screen = efi_cout_clear_screen, + .set_cursor_position = efi_cout_set_cursor_position, + .enable_cursor = efi_cout_enable_cursor, + .mode = (void*)&efi_con_mode, +}; + +/** + * struct efi_cin_notify_function - registered console input notify function + * + * @link: link to list + * @key: key to notify + * @function: function to call + */ +struct efi_cin_notify_function { + struct list_head link; + struct efi_key_data key; + efi_status_t (EFIAPI *function) + (struct efi_key_data *key_data); +}; + +static bool key_available; +static struct efi_key_data next_key; +static LIST_HEAD(cin_notify_functions); + +/** + * set_shift_mask() - set shift mask + * + * @mod: Xterm shift mask + * @key_state: receives the state of the shift, alt, control, and logo keys + */ +static void set_shift_mask(int mod, struct efi_key_state *key_state) +{ + key_state->key_shift_state = EFI_SHIFT_STATE_VALID; + if (mod) { + --mod; + if (mod & 1) + key_state->key_shift_state |= EFI_LEFT_SHIFT_PRESSED; + if (mod & 2) + key_state->key_shift_state |= EFI_LEFT_ALT_PRESSED; + if (mod & 4) + key_state->key_shift_state |= EFI_LEFT_CONTROL_PRESSED; + if (!mod || (mod & 8)) + key_state->key_shift_state |= EFI_LEFT_LOGO_PRESSED; + } +} + +/** + * analyze_modifiers() - analyze modifiers (shift, alt, ctrl) for function keys + * + * This gets called when we have already parsed CSI. + * + * @key_state: receives the state of the shift, alt, control, and logo keys + * Return: the unmodified code + */ +static int analyze_modifiers(struct efi_key_state *key_state) +{ + int c, mod = 0, ret = 0; + + c = getchar(); + + if (c != ';') { + ret = c; + if (c == '~') + goto out; + c = getchar(); + } + for (;;) { + switch (c) { + case '0'...'9': + mod *= 10; + mod += c - '0'; + /* fall through */ + case ';': + c = getchar(); + break; + default: + goto out; + } + } +out: + set_shift_mask(mod, key_state); + if (!ret) + ret = c; + return ret; +} + +/** + * efi_cin_read_key() - read a key from the console input + * + * @key: - key received + * Return: - status code + */ +static efi_status_t efi_cin_read_key(struct efi_key_data *key) +{ + struct efi_input_key pressed_key = { + .scan_code = 0, + .unicode_char = 0, + }; + s32 ch; + + if (console_read_unicode(&ch)) + return EFI_NOT_READY; + + key->key_state.key_shift_state = EFI_SHIFT_STATE_INVALID; + key->key_state.key_toggle_state = EFI_TOGGLE_STATE_INVALID; + + /* We do not support multi-word codes */ + if (ch >= 0x10000) + ch = '?'; + + switch (ch) { + case 0x1b: + /* + * If a second key is received within 10 ms, assume that we are + * dealing with an escape sequence. Otherwise consider this the + * escape key being hit. 10 ms is long enough to work fine at + * 1200 baud and above. + */ + udelay(10000); + if (!tstc()) { + pressed_key.scan_code = 23; + break; + } + /* + * Xterm Control Sequences + * https://www.xfree86.org/4.8.0/ctlseqs.html + */ + ch = getchar(); + switch (ch) { + case cESC: /* ESC */ + pressed_key.scan_code = 23; + break; + case 'O': /* F1 - F4, End */ + ch = getchar(); + /* consider modifiers */ + if (ch == 'F') { /* End */ + pressed_key.scan_code = 6; + break; + } else if (ch < 'P') { + set_shift_mask(ch - '0', &key->key_state); + ch = getchar(); + } + pressed_key.scan_code = ch - 'P' + 11; + break; + case '[': + ch = getchar(); + switch (ch) { + case 'A'...'D': /* up, down right, left */ + pressed_key.scan_code = ch - 'A' + 1; + break; + case 'F': /* End */ + pressed_key.scan_code = 6; + break; + case 'H': /* Home */ + pressed_key.scan_code = 5; + break; + case '1': + ch = analyze_modifiers(&key->key_state); + switch (ch) { + case '1'...'5': /* F1 - F5 */ + pressed_key.scan_code = ch - '1' + 11; + break; + case '6'...'9': /* F5 - F8 */ + pressed_key.scan_code = ch - '6' + 15; + break; + case 'A'...'D': /* up, down right, left */ + pressed_key.scan_code = ch - 'A' + 1; + break; + case 'F': /* End */ + pressed_key.scan_code = 6; + break; + case 'H': /* Home */ + pressed_key.scan_code = 5; + break; + case '~': /* Home */ + pressed_key.scan_code = 5; + break; + } + break; + case '2': + ch = analyze_modifiers(&key->key_state); + switch (ch) { + case '0'...'1': /* F9 - F10 */ + pressed_key.scan_code = ch - '0' + 19; + break; + case '3'...'4': /* F11 - F12 */ + pressed_key.scan_code = ch - '3' + 21; + break; + case '~': /* INS */ + pressed_key.scan_code = 7; + break; + } + break; + case '3': /* DEL */ + pressed_key.scan_code = 8; + analyze_modifiers(&key->key_state); + break; + case '5': /* PG UP */ + pressed_key.scan_code = 9; + analyze_modifiers(&key->key_state); + break; + case '6': /* PG DOWN */ + pressed_key.scan_code = 10; + analyze_modifiers(&key->key_state); + break; + } /* [ */ + break; + default: + /* ALT key */ + set_shift_mask(3, &key->key_state); + } + break; + case 0x7f: + /* Backspace */ + ch = 0x08; + } + if (pressed_key.scan_code) { + key->key_state.key_shift_state |= EFI_SHIFT_STATE_VALID; + } else { + pressed_key.unicode_char = ch; + + /* + * Assume left control key for control characters typically + * entered using the control key. + */ + if (ch >= 0x01 && ch <= 0x1f) { + key->key_state.key_shift_state |= + EFI_SHIFT_STATE_VALID; + switch (ch) { + case 0x01 ... 0x07: + case 0x0b ... 0x0c: + case 0x0e ... 0x1f: + key->key_state.key_shift_state |= + EFI_LEFT_CONTROL_PRESSED; + } + } + } + key->key = pressed_key; + + return EFI_SUCCESS; +} + +/** + * efi_cin_notify() - notify registered functions + */ +static void efi_cin_notify(void) +{ + struct efi_cin_notify_function *item; + + list_for_each_entry(item, &cin_notify_functions, link) { + bool match = true; + + /* We do not support toggle states */ + if (item->key.key.unicode_char || item->key.key.scan_code) { + if (item->key.key.unicode_char != + next_key.key.unicode_char || + item->key.key.scan_code != next_key.key.scan_code) + match = false; + } + if (item->key.key_state.key_shift_state && + item->key.key_state.key_shift_state != + next_key.key_state.key_shift_state) + match = false; + + if (match) + /* We don't bother about the return code */ + EFI_CALL(item->function(&next_key)); + } +} + +/** + * efi_cin_check() - check if keyboard input is available + */ +static void efi_cin_check(void) +{ + efi_status_t ret; + + if (key_available) { + efi_signal_event(efi_con_in.wait_for_key); + return; + } + + if (tstc()) { + ret = efi_cin_read_key(&next_key); + if (ret == EFI_SUCCESS) { + key_available = true; + + /* Notify registered functions */ + efi_cin_notify(); + + /* Queue the wait for key event */ + if (key_available) + efi_signal_event(efi_con_in.wait_for_key); + } + } +} + +/** + * efi_cin_empty_buffer() - empty input buffer + */ +static void efi_cin_empty_buffer(void) +{ + while (tstc()) + getchar(); + key_available = false; +} + +/** + * efi_cin_reset_ex() - reset console input + * + * @this: - the extended simple text input protocol + * @extended_verification: - extended verification + * + * This function implements the reset service of the + * EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: old value of the task priority level + */ +static efi_status_t EFIAPI efi_cin_reset_ex( + struct efi_simple_text_input_ex_protocol *this, + bool extended_verification) +{ + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p, %d", this, extended_verification); + + /* Check parameters */ + if (!this) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + efi_cin_empty_buffer(); +out: + return EFI_EXIT(ret); +} + +/** + * efi_cin_read_key_stroke_ex() - read key stroke + * + * @this: instance of the EFI_SIMPLE_TEXT_INPUT_PROTOCOL + * @key_data: key read from console + * Return: status code + * + * This function implements the ReadKeyStrokeEx service of the + * EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + */ +static efi_status_t EFIAPI efi_cin_read_key_stroke_ex( + struct efi_simple_text_input_ex_protocol *this, + struct efi_key_data *key_data) +{ + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p, %p", this, key_data); + + /* Check parameters */ + if (!this || !key_data) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + /* We don't do interrupts, so check for timers cooperatively */ + efi_timer_check(); + + /* Enable console input after ExitBootServices */ + efi_cin_check(); + + if (!key_available) { + memset(key_data, 0, sizeof(struct efi_key_data)); + ret = EFI_NOT_READY; + goto out; + } + /* + * CTRL+A - CTRL+Z have to be signaled as a - z. + * SHIFT+CTRL+A - SHIFT+CTRL+Z have to be signaled as A - Z. + * CTRL+\ - CTRL+_ have to be signaled as \ - _. + */ + switch (next_key.key.unicode_char) { + case 0x01 ... 0x07: + case 0x0b ... 0x0c: + case 0x0e ... 0x1a: + if (!(next_key.key_state.key_toggle_state & + EFI_CAPS_LOCK_ACTIVE) ^ + !(next_key.key_state.key_shift_state & + (EFI_LEFT_SHIFT_PRESSED | EFI_RIGHT_SHIFT_PRESSED))) + next_key.key.unicode_char += 0x40; + else + next_key.key.unicode_char += 0x60; + break; + case 0x1c ... 0x1f: + next_key.key.unicode_char += 0x40; + } + *key_data = next_key; + key_available = false; + efi_con_in.wait_for_key->is_signaled = false; + +out: + return EFI_EXIT(ret); +} + +/** + * efi_cin_set_state() - set toggle key state + * + * @this: instance of the EFI_SIMPLE_TEXT_INPUT_PROTOCOL + * @key_toggle_state: pointer to key toggle state + * Return: status code + * + * This function implements the SetState service of the + * EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + */ +static efi_status_t EFIAPI efi_cin_set_state( + struct efi_simple_text_input_ex_protocol *this, + u8 *key_toggle_state) +{ + EFI_ENTRY("%p, %p", this, key_toggle_state); + /* + * U-Boot supports multiple console input sources like serial and + * net console for which a key toggle state cannot be set at all. + * + * According to the UEFI specification it is allowable to not implement + * this service. + */ + return EFI_EXIT(EFI_UNSUPPORTED); +} + +/** + * efi_cin_register_key_notify() - register key notification function + * + * @this: instance of the EFI_SIMPLE_TEXT_INPUT_PROTOCOL + * @key_data: key to be notified + * @key_notify_function: function to be called if the key is pressed + * @notify_handle: handle for unregistering the notification + * Return: status code + * + * This function implements the SetState service of the + * EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + */ +static efi_status_t EFIAPI efi_cin_register_key_notify( + struct efi_simple_text_input_ex_protocol *this, + struct efi_key_data *key_data, + efi_status_t (EFIAPI *key_notify_function)( + struct efi_key_data *key_data), + void **notify_handle) +{ + efi_status_t ret = EFI_SUCCESS; + struct efi_cin_notify_function *notify_function; + + EFI_ENTRY("%p, %p, %p, %p", + this, key_data, key_notify_function, notify_handle); + + /* Check parameters */ + if (!this || !key_data || !key_notify_function || !notify_handle) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + EFI_PRINT("u+%04x, sc %04x, sh %08x, tg %02x\n", + key_data->key.unicode_char, + key_data->key.scan_code, + key_data->key_state.key_shift_state, + key_data->key_state.key_toggle_state); + + notify_function = calloc(1, sizeof(struct efi_cin_notify_function)); + if (!notify_function) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + notify_function->key = *key_data; + notify_function->function = key_notify_function; + list_add_tail(¬ify_function->link, &cin_notify_functions); + *notify_handle = notify_function; +out: + return EFI_EXIT(ret); +} + +/** + * efi_cin_unregister_key_notify() - unregister key notification function + * + * @this: instance of the EFI_SIMPLE_TEXT_INPUT_PROTOCOL + * @notification_handle: handle received when registering + * Return: status code + * + * This function implements the SetState service of the + * EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + */ +static efi_status_t EFIAPI efi_cin_unregister_key_notify( + struct efi_simple_text_input_ex_protocol *this, + void *notification_handle) +{ + efi_status_t ret = EFI_INVALID_PARAMETER; + struct efi_cin_notify_function *item, *notify_function = + notification_handle; + + EFI_ENTRY("%p, %p", this, notification_handle); + + /* Check parameters */ + if (!this || !notification_handle) + goto out; + + list_for_each_entry(item, &cin_notify_functions, link) { + if (item == notify_function) { + ret = EFI_SUCCESS; + break; + } + } + if (ret != EFI_SUCCESS) + goto out; + + /* Remove the notify function */ + list_del(¬ify_function->link); + free(notify_function); +out: + return EFI_EXIT(ret); +} + +/** + * efi_cin_reset() - drain the input buffer + * + * @this: instance of the EFI_SIMPLE_TEXT_INPUT_PROTOCOL + * @extended_verification: allow for exhaustive verification + * Return: status code + * + * This function implements the Reset service of the + * EFI_SIMPLE_TEXT_INPUT_PROTOCOL. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + */ +static efi_status_t EFIAPI efi_cin_reset + (struct efi_simple_text_input_protocol *this, + bool extended_verification) +{ + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p, %d", this, extended_verification); + + /* Check parameters */ + if (!this) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + efi_cin_empty_buffer(); +out: + return EFI_EXIT(ret); +} + +/** + * efi_cin_read_key_stroke() - read key stroke + * + * @this: instance of the EFI_SIMPLE_TEXT_INPUT_PROTOCOL + * @key: key read from console + * Return: status code + * + * This function implements the ReadKeyStroke service of the + * EFI_SIMPLE_TEXT_INPUT_PROTOCOL. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + */ +static efi_status_t EFIAPI efi_cin_read_key_stroke + (struct efi_simple_text_input_protocol *this, + struct efi_input_key *key) +{ + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p, %p", this, key); + + /* Check parameters */ + if (!this || !key) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + /* We don't do interrupts, so check for timers cooperatively */ + efi_timer_check(); + + /* Enable console input after ExitBootServices */ + efi_cin_check(); + + if (!key_available) { + ret = EFI_NOT_READY; + goto out; + } + *key = next_key.key; + key_available = false; + efi_con_in.wait_for_key->is_signaled = false; +out: + return EFI_EXIT(ret); +} + +static struct efi_simple_text_input_ex_protocol efi_con_in_ex = { + .reset = efi_cin_reset_ex, + .read_key_stroke_ex = efi_cin_read_key_stroke_ex, + .wait_for_key_ex = NULL, + .set_state = efi_cin_set_state, + .register_key_notify = efi_cin_register_key_notify, + .unregister_key_notify = efi_cin_unregister_key_notify, +}; + +struct efi_simple_text_input_protocol efi_con_in = { + .reset = efi_cin_reset, + .read_key_stroke = efi_cin_read_key_stroke, + .wait_for_key = NULL, +}; + +static struct efi_event *console_timer_event; + +/* + * efi_console_timer_notify() - notify the console timer event + * + * @event: console timer event + * @context: not used + */ +static void EFIAPI efi_console_timer_notify(struct efi_event *event, + void *context) +{ + EFI_ENTRY("%p, %p", event, context); + efi_cin_check(); + EFI_EXIT(EFI_SUCCESS); +} + +/** + * efi_key_notify() - notify the wait for key event + * + * @event: wait for key event + * @context: not used + */ +static void EFIAPI efi_key_notify(struct efi_event *event, void *context) +{ + EFI_ENTRY("%p, %p", event, context); + efi_cin_check(); + EFI_EXIT(EFI_SUCCESS); +} + +/** + * efi_console_register() - install the console protocols + * + * This function is called from do_bootefi_exec(). + * + * Return: status code + */ +efi_status_t efi_console_register(void) +{ + efi_status_t r; + struct efi_device_path *dp; + + /* Install protocols on root node */ + r = efi_install_multiple_protocol_interfaces(&efi_root, + &efi_guid_text_output_protocol, + &efi_con_out, + &efi_guid_text_input_protocol, + &efi_con_in, + &efi_guid_text_input_ex_protocol, + &efi_con_in_ex, + NULL); + + /* Create console node and install device path protocols */ + if (CONFIG_IS_ENABLED(DM_SERIAL)) { + dp = efi_dp_from_uart(); + if (!dp) + goto out_of_memory; + + /* Hook UART up to the device list */ + efi_add_handle(&uart_obj); + + /* Install device path */ + r = efi_add_protocol(&uart_obj, &efi_guid_device_path, dp); + if (r != EFI_SUCCESS) + goto out_of_memory; + } + + /* Create console events */ + r = efi_create_event(EVT_NOTIFY_WAIT, TPL_CALLBACK, efi_key_notify, + NULL, NULL, &efi_con_in.wait_for_key); + if (r != EFI_SUCCESS) { + printf("ERROR: Failed to register WaitForKey event\n"); + return r; + } + efi_con_in_ex.wait_for_key_ex = efi_con_in.wait_for_key; + r = efi_create_event(EVT_TIMER | EVT_NOTIFY_SIGNAL, TPL_CALLBACK, + efi_console_timer_notify, NULL, NULL, + &console_timer_event); + if (r != EFI_SUCCESS) { + printf("ERROR: Failed to register console event\n"); + return r; + } + /* 5000 ns cycle is sufficient for 2 MBaud */ + r = efi_set_timer(console_timer_event, EFI_TIMER_PERIODIC, 50); + if (r != EFI_SUCCESS) + printf("ERROR: Failed to set console timer\n"); + return r; +out_of_memory: + printf("ERROR: Out of memory\n"); + return r; +} + +/** + * efi_console_get_u16_string() - get user input string + * + * @cin: protocol interface to EFI_SIMPLE_TEXT_INPUT_PROTOCOL + * @buf: buffer to store user input string in UTF16 + * @count: number of u16 string including NULL terminator that buf has + * @filter_func: callback to filter user input + * @row: row number to locate user input form + * @col: column number to locate user input form + * Return: status code + */ +efi_status_t efi_console_get_u16_string(struct efi_simple_text_input_protocol *cin, + u16 *buf, efi_uintn_t count, + efi_console_filter_func filter_func, + int row, int col) +{ + efi_status_t ret; + efi_uintn_t len = 0; + struct efi_input_key key; + + printf(ANSI_CURSOR_POSITION + ANSI_CLEAR_LINE_TO_END + ANSI_CURSOR_SHOW, row, col); + + efi_cin_empty_buffer(); + + for (;;) { + do { + ret = EFI_CALL(cin->read_key_stroke(cin, &key)); + mdelay(10); + } while (ret == EFI_NOT_READY); + + if (key.unicode_char == u'\b') { + if (len > 0) + buf[--len] = u'\0'; + + printf(ANSI_CURSOR_POSITION + "%ls" + ANSI_CLEAR_LINE_TO_END, row, col, buf); + continue; + } else if (key.unicode_char == u'\r') { + buf[len] = u'\0'; + return EFI_SUCCESS; + } else if (key.unicode_char == 0x3 || key.scan_code == 23) { + return EFI_ABORTED; + } else if (key.unicode_char < 0x20) { + /* ignore control codes other than Ctrl+C, '\r' and '\b' */ + continue; + } else if (key.scan_code != 0) { + /* only accept single ESC press for cancel */ + continue; + } + + if (filter_func) { + if (filter_func(&key) != EFI_SUCCESS) + continue; + } + + if (len >= (count - 1)) + continue; + + buf[len] = key.unicode_char; + len++; + printf(ANSI_CURSOR_POSITION "%ls", row, col, buf); + } +} diff --git a/lib/efi_loader/efi_device_path.c b/lib/efi_loader/efi_device_path.c new file mode 100644 index 00000000000..c9bf2726fe2 --- /dev/null +++ b/lib/efi_loader/efi_device_path.c @@ -0,0 +1,1318 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI device path from u-boot device-model mapping + * + * (C) Copyright 2017 Rob Clark + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <blk.h> +#include <dm.h> +#include <dm/root.h> +#include <log.h> +#include <net.h> +#include <usb.h> +#include <mmc.h> +#include <nvme.h> +#include <efi_loader.h> +#include <part.h> +#include <u-boot/uuid.h> +#include <asm-generic/unaligned.h> +#include <linux/compat.h> /* U16_MAX */ + +/* template END node: */ +const struct efi_device_path END = { + .type = DEVICE_PATH_TYPE_END, + .sub_type = DEVICE_PATH_SUB_TYPE_END, + .length = sizeof(END), +}; + +#if defined(CONFIG_MMC) +/* + * Determine if an MMC device is an SD card. + * + * @desc block device descriptor + * Return: true if the device is an SD card + */ +static bool is_sd(struct blk_desc *desc) +{ + struct mmc *mmc = find_mmc_device(desc->devnum); + + if (!mmc) + return false; + + return IS_SD(mmc) != 0U; +} +#endif + +/* + * Iterate to next block in device-path, terminating (returning NULL) + * at /End* node. + */ +struct efi_device_path *efi_dp_next(const struct efi_device_path *dp) +{ + if (dp == NULL) + return NULL; + if (dp->type == DEVICE_PATH_TYPE_END) + return NULL; + dp = ((void *)dp) + dp->length; + if (dp->type == DEVICE_PATH_TYPE_END) + return NULL; + return (struct efi_device_path *)dp; +} + +/* + * Compare two device-paths, stopping when the shorter of the two hits + * an End* node. This is useful to, for example, compare a device-path + * representing a device with one representing a file on the device, or + * a device with a parent device. + */ +int efi_dp_match(const struct efi_device_path *a, + const struct efi_device_path *b) +{ + while (1) { + int ret; + + ret = memcmp(&a->length, &b->length, sizeof(a->length)); + if (ret) + return ret; + + ret = memcmp(a, b, a->length); + if (ret) + return ret; + + a = efi_dp_next(a); + b = efi_dp_next(b); + + if (!a || !b) + return 0; + } +} + +/** + * efi_dp_shorten() - shorten device-path + * + * When creating a short boot option we want to use a device-path that is + * independent of the location where the block device is plugged in. + * + * UsbWwi() nodes contain a serial number, hard drive paths a partition + * UUID. Both should be unique. + * + * See UEFI spec, section 3.1.2 for "short-form device path". + * + * @dp: original device-path + * Return: shortened device-path or NULL + */ +struct efi_device_path *efi_dp_shorten(struct efi_device_path *dp) +{ + while (dp) { + if (EFI_DP_TYPE(dp, MESSAGING_DEVICE, MSG_USB_WWI) || + EFI_DP_TYPE(dp, MEDIA_DEVICE, HARD_DRIVE_PATH) || + EFI_DP_TYPE(dp, MEDIA_DEVICE, FILE_PATH)) + return dp; + + dp = efi_dp_next(dp); + } + + return dp; +} + +/** + * find_handle() - find handle by device path and installed protocol + * + * If @rem is provided, the handle with the longest partial match is returned. + * + * @dp: device path to search + * @guid: GUID of protocol that must be installed on path or NULL + * @short_path: use short form device path for matching + * @rem: pointer to receive remaining device path + * Return: matching handle + */ +static efi_handle_t find_handle(struct efi_device_path *dp, + const efi_guid_t *guid, bool short_path, + struct efi_device_path **rem) +{ + efi_handle_t handle, best_handle = NULL; + efi_uintn_t len, best_len = 0; + + len = efi_dp_instance_size(dp); + + list_for_each_entry(handle, &efi_obj_list, link) { + struct efi_handler *handler; + struct efi_device_path *dp_current; + efi_uintn_t len_current; + efi_status_t ret; + + if (guid) { + ret = efi_search_protocol(handle, guid, &handler); + if (ret != EFI_SUCCESS) + continue; + } + ret = efi_search_protocol(handle, &efi_guid_device_path, + &handler); + if (ret != EFI_SUCCESS) + continue; + dp_current = handler->protocol_interface; + if (short_path) { + dp_current = efi_dp_shorten(dp_current); + if (!dp_current) + continue; + } + len_current = efi_dp_instance_size(dp_current); + if (rem) { + if (len_current > len) + continue; + } else { + if (len_current != len) + continue; + } + if (memcmp(dp_current, dp, len_current)) + continue; + if (!rem) + return handle; + if (len_current > best_len) { + best_len = len_current; + best_handle = handle; + *rem = (void*)((u8 *)dp + len_current); + } + } + return best_handle; +} + +/** + * efi_dp_find_obj() - find handle by device path + * + * If @rem is provided, the handle with the longest partial match is returned. + * + * @dp: device path to search + * @guid: GUID of protocol that must be installed on path or NULL + * @rem: pointer to receive remaining device path + * Return: matching handle + */ +efi_handle_t efi_dp_find_obj(struct efi_device_path *dp, + const efi_guid_t *guid, + struct efi_device_path **rem) +{ + efi_handle_t handle; + + handle = find_handle(dp, guid, false, rem); + if (!handle) + /* Match short form device path */ + handle = find_handle(dp, guid, true, rem); + + return handle; +} + +/* + * Determine the last device path node that is not the end node. + * + * @dp device path + * Return: last node before the end node if it exists + * otherwise NULL + */ +const struct efi_device_path *efi_dp_last_node(const struct efi_device_path *dp) +{ + struct efi_device_path *ret; + + if (!dp || dp->type == DEVICE_PATH_TYPE_END) + return NULL; + while (dp) { + ret = (struct efi_device_path *)dp; + dp = efi_dp_next(dp); + } + return ret; +} + +/* get size of the first device path instance excluding end node */ +efi_uintn_t efi_dp_instance_size(const struct efi_device_path *dp) +{ + efi_uintn_t sz = 0; + + if (!dp || dp->type == DEVICE_PATH_TYPE_END) + return 0; + while (dp) { + sz += dp->length; + dp = efi_dp_next(dp); + } + + return sz; +} + +/* get size of multi-instance device path excluding end node */ +efi_uintn_t efi_dp_size(const struct efi_device_path *dp) +{ + const struct efi_device_path *p = dp; + + if (!p) + return 0; + while (p->type != DEVICE_PATH_TYPE_END || + p->sub_type != DEVICE_PATH_SUB_TYPE_END) + p = (void *)p + p->length; + + return (void *)p - (void *)dp; +} + +/* copy multi-instance device path */ +struct efi_device_path *efi_dp_dup(const struct efi_device_path *dp) +{ + struct efi_device_path *ndp; + size_t sz = efi_dp_size(dp) + sizeof(END); + + if (!dp) + return NULL; + + ndp = efi_alloc(sz); + if (!ndp) + return NULL; + memcpy(ndp, dp, sz); + + return ndp; +} + +/** + * efi_dp_concat() - Concatenate two device paths and add and terminate them + * with an end node. + * + * @dp1: First device path + * @dp2: Second device path + * @split_end_node: + * * 0 to concatenate + * * 1 to concatenate with end node added as separator + * * size of dp1 excluding last end node to concatenate with end node as + * separator in case dp1 contains an end node + * + * Return: + * concatenated device path or NULL. Caller must free the returned value + */ +struct +efi_device_path *efi_dp_concat(const struct efi_device_path *dp1, + const struct efi_device_path *dp2, + size_t split_end_node) +{ + struct efi_device_path *ret; + size_t end_size; + + if (!dp1 && !dp2) { + /* return an end node */ + ret = efi_dp_dup(&END); + } else if (!dp1) { + ret = efi_dp_dup(dp2); + } else if (!dp2) { + ret = efi_dp_dup(dp1); + } else { + /* both dp1 and dp2 are non-null */ + size_t sz1; + size_t sz2 = efi_dp_size(dp2); + void *p; + + if (split_end_node < sizeof(struct efi_device_path)) + sz1 = efi_dp_size(dp1); + else + sz1 = split_end_node; + + if (split_end_node) + end_size = 2 * sizeof(END); + else + end_size = sizeof(END); + p = efi_alloc(sz1 + sz2 + end_size); + if (!p) + return NULL; + ret = p; + memcpy(p, dp1, sz1); + p += sz1; + + if (split_end_node) { + memcpy(p, &END, sizeof(END)); + p += sizeof(END); + } + + /* the end node of the second device path has to be retained */ + memcpy(p, dp2, sz2); + p += sz2; + memcpy(p, &END, sizeof(END)); + } + + return ret; +} + +struct efi_device_path *efi_dp_append_node(const struct efi_device_path *dp, + const struct efi_device_path *node) +{ + struct efi_device_path *ret; + + if (!node && !dp) { + ret = efi_dp_dup(&END); + } else if (!node) { + ret = efi_dp_dup(dp); + } else if (!dp) { + size_t sz = node->length; + void *p = efi_alloc(sz + sizeof(END)); + if (!p) + return NULL; + memcpy(p, node, sz); + memcpy(p + sz, &END, sizeof(END)); + ret = p; + } else { + /* both dp and node are non-null */ + size_t sz = efi_dp_size(dp); + void *p = efi_alloc(sz + node->length + sizeof(END)); + if (!p) + return NULL; + memcpy(p, dp, sz); + memcpy(p + sz, node, node->length); + memcpy(p + sz + node->length, &END, sizeof(END)); + ret = p; + } + + return ret; +} + +struct efi_device_path *efi_dp_create_device_node(const u8 type, + const u8 sub_type, + const u16 length) +{ + struct efi_device_path *ret; + + if (length < sizeof(struct efi_device_path)) + return NULL; + + ret = efi_alloc(length); + if (!ret) + return ret; + ret->type = type; + ret->sub_type = sub_type; + ret->length = length; + return ret; +} + +struct efi_device_path *efi_dp_append_instance( + const struct efi_device_path *dp, + const struct efi_device_path *dpi) +{ + size_t sz, szi; + struct efi_device_path *p, *ret; + + if (!dpi) + return NULL; + if (!dp) + return efi_dp_dup(dpi); + sz = efi_dp_size(dp); + szi = efi_dp_instance_size(dpi); + p = efi_alloc(sz + szi + 2 * sizeof(END)); + if (!p) + return NULL; + ret = p; + memcpy(p, dp, sz + sizeof(END)); + p = (void *)p + sz; + p->sub_type = DEVICE_PATH_SUB_TYPE_INSTANCE_END; + p = (void *)p + sizeof(END); + memcpy(p, dpi, szi); + p = (void *)p + szi; + memcpy(p, &END, sizeof(END)); + return ret; +} + +struct efi_device_path *efi_dp_get_next_instance(struct efi_device_path **dp, + efi_uintn_t *size) +{ + size_t sz; + struct efi_device_path *p; + + if (size) + *size = 0; + if (!dp || !*dp) + return NULL; + sz = efi_dp_instance_size(*dp); + p = efi_alloc(sz + sizeof(END)); + if (!p) + return NULL; + memcpy(p, *dp, sz + sizeof(END)); + *dp = (void *)*dp + sz; + if ((*dp)->sub_type == DEVICE_PATH_SUB_TYPE_INSTANCE_END) + *dp = (void *)*dp + sizeof(END); + else + *dp = NULL; + if (size) + *size = sz + sizeof(END); + return p; +} + +bool efi_dp_is_multi_instance(const struct efi_device_path *dp) +{ + const struct efi_device_path *p = dp; + + if (!p) + return false; + while (p->type != DEVICE_PATH_TYPE_END) + p = (void *)p + p->length; + return p->sub_type == DEVICE_PATH_SUB_TYPE_INSTANCE_END; +} + +/* size of device-path not including END node for device and all parents + * up to the root device. + */ +__maybe_unused static unsigned int dp_size(struct udevice *dev) +{ + if (!dev || !dev->driver) + return sizeof(struct efi_device_path_udevice); + + switch (device_get_uclass_id(dev)) { + case UCLASS_ROOT: + /* stop traversing parents at this point: */ + return sizeof(struct efi_device_path_udevice); + case UCLASS_ETH: + return dp_size(dev->parent) + + sizeof(struct efi_device_path_mac_addr); + case UCLASS_BLK: + switch (dev->parent->uclass->uc_drv->id) { +#ifdef CONFIG_IDE + case UCLASS_IDE: + return dp_size(dev->parent) + + sizeof(struct efi_device_path_atapi); +#endif +#if defined(CONFIG_SCSI) + case UCLASS_SCSI: + return dp_size(dev->parent) + + sizeof(struct efi_device_path_scsi); +#endif +#if defined(CONFIG_MMC) + case UCLASS_MMC: + return dp_size(dev->parent) + + sizeof(struct efi_device_path_sd_mmc_path); +#endif +#if defined(CONFIG_AHCI) || defined(CONFIG_SATA) + case UCLASS_AHCI: + return dp_size(dev->parent) + + sizeof(struct efi_device_path_sata); +#endif +#if defined(CONFIG_NVME) + case UCLASS_NVME: + return dp_size(dev->parent) + + sizeof(struct efi_device_path_nvme); +#endif +#ifdef CONFIG_USB + case UCLASS_MASS_STORAGE: + return dp_size(dev->parent) + + sizeof(struct efi_device_path_controller); +#endif + default: + /* UCLASS_BLKMAP, UCLASS_HOST, UCLASS_VIRTIO */ + return dp_size(dev->parent) + + sizeof(struct efi_device_path_udevice); + } +#if defined(CONFIG_MMC) + case UCLASS_MMC: + return dp_size(dev->parent) + + sizeof(struct efi_device_path_sd_mmc_path); +#endif + case UCLASS_MASS_STORAGE: + case UCLASS_USB_HUB: + return dp_size(dev->parent) + + sizeof(struct efi_device_path_usb); + default: + return dp_size(dev->parent) + + sizeof(struct efi_device_path_udevice); + } +} + +/* + * Recursively build a device path. + * + * @buf pointer to the end of the device path + * @dev device + * Return: pointer to the end of the device path + */ +__maybe_unused static void *dp_fill(void *buf, struct udevice *dev) +{ + enum uclass_id uclass_id; + + if (!dev || !dev->driver) + return buf; + + uclass_id = device_get_uclass_id(dev); + if (uclass_id != UCLASS_ROOT) + buf = dp_fill(buf, dev->parent); + + switch (uclass_id) { +#ifdef CONFIG_NETDEVICES + case UCLASS_ETH: { + struct efi_device_path_mac_addr *dp = buf; + struct eth_pdata *pdata = dev_get_plat(dev); + + dp->dp.type = DEVICE_PATH_TYPE_MESSAGING_DEVICE; + dp->dp.sub_type = DEVICE_PATH_SUB_TYPE_MSG_MAC_ADDR; + dp->dp.length = sizeof(*dp); + memset(&dp->mac, 0, sizeof(dp->mac)); + /* We only support IPv4 */ + memcpy(&dp->mac, &pdata->enetaddr, ARP_HLEN); + /* Ethernet */ + dp->if_type = 1; + return &dp[1]; + } +#endif + case UCLASS_BLK: + switch (device_get_uclass_id(dev->parent)) { +#ifdef CONFIG_IDE + case UCLASS_IDE: { + struct efi_device_path_atapi *dp = buf; + struct blk_desc *desc = dev_get_uclass_plat(dev); + + dp->dp.type = DEVICE_PATH_TYPE_MESSAGING_DEVICE; + dp->dp.sub_type = DEVICE_PATH_SUB_TYPE_MSG_ATAPI; + dp->dp.length = sizeof(*dp); + dp->logical_unit_number = desc->devnum; + dp->primary_secondary = IDE_BUS(desc->devnum); + dp->slave_master = desc->devnum % + (CONFIG_SYS_IDE_MAXDEVICE / + CONFIG_SYS_IDE_MAXBUS); + return &dp[1]; + } +#endif +#if defined(CONFIG_SCSI) + case UCLASS_SCSI: { + struct efi_device_path_scsi *dp = buf; + struct blk_desc *desc = dev_get_uclass_plat(dev); + + dp->dp.type = DEVICE_PATH_TYPE_MESSAGING_DEVICE; + dp->dp.sub_type = DEVICE_PATH_SUB_TYPE_MSG_SCSI; + dp->dp.length = sizeof(*dp); + dp->logical_unit_number = desc->lun; + dp->target_id = desc->target; + return &dp[1]; + } +#endif +#if defined(CONFIG_MMC) + case UCLASS_MMC: { + struct efi_device_path_sd_mmc_path *sddp = buf; + struct blk_desc *desc = dev_get_uclass_plat(dev); + + sddp->dp.type = DEVICE_PATH_TYPE_MESSAGING_DEVICE; + sddp->dp.sub_type = is_sd(desc) ? + DEVICE_PATH_SUB_TYPE_MSG_SD : + DEVICE_PATH_SUB_TYPE_MSG_MMC; + sddp->dp.length = sizeof(*sddp); + sddp->slot_number = dev_seq(dev); + return &sddp[1]; + } +#endif +#if defined(CONFIG_AHCI) || defined(CONFIG_SATA) + case UCLASS_AHCI: { + struct efi_device_path_sata *dp = buf; + struct blk_desc *desc = dev_get_uclass_plat(dev); + + dp->dp.type = DEVICE_PATH_TYPE_MESSAGING_DEVICE; + dp->dp.sub_type = DEVICE_PATH_SUB_TYPE_MSG_SATA; + dp->dp.length = sizeof(*dp); + dp->hba_port = desc->devnum; + /* default 0xffff implies no port multiplier */ + dp->port_multiplier_port = 0xffff; + dp->logical_unit_number = desc->lun; + return &dp[1]; + } +#endif +#if defined(CONFIG_NVME) + case UCLASS_NVME: { + struct efi_device_path_nvme *dp = buf; + u32 ns_id; + + dp->dp.type = DEVICE_PATH_TYPE_MESSAGING_DEVICE; + dp->dp.sub_type = DEVICE_PATH_SUB_TYPE_MSG_NVME; + dp->dp.length = sizeof(*dp); + nvme_get_namespace_id(dev, &ns_id, dp->eui64); + memcpy(&dp->ns_id, &ns_id, sizeof(ns_id)); + return &dp[1]; + } +#endif +#if defined(CONFIG_USB) + case UCLASS_MASS_STORAGE: { + struct blk_desc *desc = dev_get_uclass_plat(dev); + struct efi_device_path_controller *dp = buf; + + dp->dp.type = DEVICE_PATH_TYPE_HARDWARE_DEVICE; + dp->dp.sub_type = DEVICE_PATH_SUB_TYPE_CONTROLLER; + dp->dp.length = sizeof(*dp); + dp->controller_number = desc->lun; + return &dp[1]; + } +#endif + default: { + /* UCLASS_BLKMAP, UCLASS_HOST, UCLASS_VIRTIO */ + struct efi_device_path_udevice *dp = buf; + struct blk_desc *desc = dev_get_uclass_plat(dev); + + dp->dp.type = DEVICE_PATH_TYPE_HARDWARE_DEVICE; + dp->dp.sub_type = DEVICE_PATH_SUB_TYPE_VENDOR; + dp->dp.length = sizeof(*dp); + memcpy(&dp->guid, &efi_u_boot_guid, + sizeof(efi_guid_t)); + dp->uclass_id = (UCLASS_BLK & 0xffff) | + (desc->uclass_id << 16); + dp->dev_number = desc->devnum; + + return &dp[1]; + } + } +#if defined(CONFIG_MMC) + case UCLASS_MMC: { + struct efi_device_path_sd_mmc_path *sddp = buf; + struct mmc *mmc = mmc_get_mmc_dev(dev); + struct blk_desc *desc = mmc_get_blk_desc(mmc); + + sddp->dp.type = DEVICE_PATH_TYPE_MESSAGING_DEVICE; + sddp->dp.sub_type = is_sd(desc) ? + DEVICE_PATH_SUB_TYPE_MSG_SD : + DEVICE_PATH_SUB_TYPE_MSG_MMC; + sddp->dp.length = sizeof(*sddp); + sddp->slot_number = dev_seq(dev); + + return &sddp[1]; + } +#endif + case UCLASS_MASS_STORAGE: + case UCLASS_USB_HUB: { + struct efi_device_path_usb *udp = buf; + + switch (device_get_uclass_id(dev->parent)) { + case UCLASS_USB_HUB: { + struct usb_device *udev = dev_get_parent_priv(dev); + + udp->parent_port_number = udev->portnr; + break; + } + default: + udp->parent_port_number = 0; + } + udp->dp.type = DEVICE_PATH_TYPE_MESSAGING_DEVICE; + udp->dp.sub_type = DEVICE_PATH_SUB_TYPE_MSG_USB; + udp->dp.length = sizeof(*udp); + udp->usb_interface = 0; + + return &udp[1]; + } + default: { + struct efi_device_path_udevice *vdp = buf; + + vdp->dp.type = DEVICE_PATH_TYPE_HARDWARE_DEVICE; + vdp->dp.sub_type = DEVICE_PATH_SUB_TYPE_VENDOR; + vdp->dp.length = sizeof(*vdp); + memcpy(&vdp->guid, &efi_u_boot_guid, sizeof(efi_guid_t)); + vdp->uclass_id = uclass_id; + vdp->dev_number = dev->seq_; + + return &vdp[1]; + } + } +} + +static unsigned dp_part_size(struct blk_desc *desc, int part) +{ + unsigned dpsize; + struct udevice *dev = desc->bdev; + + dpsize = dp_size(dev); + + if (part == 0) /* the actual disk, not a partition */ + return dpsize; + + if (desc->part_type == PART_TYPE_ISO) + dpsize += sizeof(struct efi_device_path_cdrom_path); + else + dpsize += sizeof(struct efi_device_path_hard_drive_path); + + return dpsize; +} + +/* + * Create a device node for a block device partition. + * + * @buf buffer to which the device path is written + * @desc block device descriptor + * @part partition number, 0 identifies a block device + * + * Return: pointer to position after the node + */ +static void *dp_part_node(void *buf, struct blk_desc *desc, int part) +{ + struct disk_partition info; + int ret; + + ret = part_get_info(desc, part, &info); + if (ret < 0) + return buf; + + if (desc->part_type == PART_TYPE_ISO) { + struct efi_device_path_cdrom_path *cddp = buf; + + cddp->boot_entry = part; + cddp->dp.type = DEVICE_PATH_TYPE_MEDIA_DEVICE; + cddp->dp.sub_type = DEVICE_PATH_SUB_TYPE_CDROM_PATH; + cddp->dp.length = sizeof(*cddp); + cddp->partition_start = info.start; + cddp->partition_size = info.size; + + buf = &cddp[1]; + } else { + struct efi_device_path_hard_drive_path *hddp = buf; + + hddp->dp.type = DEVICE_PATH_TYPE_MEDIA_DEVICE; + hddp->dp.sub_type = DEVICE_PATH_SUB_TYPE_HARD_DRIVE_PATH; + hddp->dp.length = sizeof(*hddp); + hddp->partition_number = part; + hddp->partition_start = info.start; + hddp->partition_end = info.size; + if (desc->part_type == PART_TYPE_EFI) + hddp->partmap_type = 2; + else + hddp->partmap_type = 1; + + switch (desc->sig_type) { + case SIG_TYPE_NONE: + default: + hddp->signature_type = 0; + memset(hddp->partition_signature, 0, + sizeof(hddp->partition_signature)); + break; + case SIG_TYPE_MBR: + hddp->signature_type = 1; + memset(hddp->partition_signature, 0, + sizeof(hddp->partition_signature)); + memcpy(hddp->partition_signature, &desc->mbr_sig, + sizeof(desc->mbr_sig)); + break; + case SIG_TYPE_GUID: + hddp->signature_type = 2; +#if CONFIG_IS_ENABLED(PARTITION_UUIDS) + /* info.uuid exists only with PARTITION_UUIDS */ + if (uuid_str_to_bin(info.uuid, + hddp->partition_signature, + UUID_STR_FORMAT_GUID)) { + log_warning( + "Partition %d: invalid GUID %s\n", + part, info.uuid); + } +#endif + break; + } + + buf = &hddp[1]; + } + + return buf; +} + +/* + * Create a device path for a block device or one of its partitions. + * + * @buf buffer to which the device path is written + * @desc block device descriptor + * @part partition number, 0 identifies a block device + */ +static void *dp_part_fill(void *buf, struct blk_desc *desc, int part) +{ + struct udevice *dev = desc->bdev; + + buf = dp_fill(buf, dev); + + if (part == 0) /* the actual disk, not a partition */ + return buf; + + return dp_part_node(buf, desc, part); +} + +/* Construct a device-path from a partition on a block device: */ +struct efi_device_path *efi_dp_from_part(struct blk_desc *desc, int part) +{ + void *buf, *start; + + start = buf = efi_alloc(dp_part_size(desc, part) + sizeof(END)); + if (!buf) + return NULL; + + buf = dp_part_fill(buf, desc, part); + + *((struct efi_device_path *)buf) = END; + + return start; +} + +/* + * Create a device node for a block device partition. + * + * @buf buffer to which the device path is written + * @desc block device descriptor + * @part partition number, 0 identifies a block device + */ +struct efi_device_path *efi_dp_part_node(struct blk_desc *desc, int part) +{ + efi_uintn_t dpsize; + void *buf; + + if (desc->part_type == PART_TYPE_ISO) + dpsize = sizeof(struct efi_device_path_cdrom_path); + else + dpsize = sizeof(struct efi_device_path_hard_drive_path); + buf = efi_alloc(dpsize); + + if (buf) + dp_part_node(buf, desc, part); + + return buf; +} + +/** + * path_to_uefi() - convert UTF-8 path to an UEFI style path + * + * Convert UTF-8 path to a UEFI style path (i.e. with backslashes as path + * separators and UTF-16). + * + * @src: source buffer + * @uefi: target buffer, possibly unaligned + */ +static void path_to_uefi(void *uefi, const char *src) +{ + u16 *pos = uefi; + + /* + * efi_set_bootdev() calls this routine indirectly before the UEFI + * subsystem is initialized. So we cannot assume unaligned access to be + * enabled. + */ + allow_unaligned(); + + while (*src) { + s32 code = utf8_get(&src); + + if (code < 0) + code = '?'; + else if (code == '/') + code = '\\'; + utf16_put(code, &pos); + } + *pos = 0; +} + +/** + * efi_dp_from_file() - append file path node to device path. + * + * @dp: device path or NULL + * @path: file path or NULL + * Return: device path or NULL in case of an error + */ +struct efi_device_path *efi_dp_from_file(const struct efi_device_path *dp, + const char *path) +{ + struct efi_device_path_file_path *fp; + void *buf, *pos; + size_t dpsize, fpsize; + + dpsize = efi_dp_size(dp); + fpsize = sizeof(struct efi_device_path) + + 2 * (utf8_utf16_strlen(path) + 1); + if (fpsize > U16_MAX) + return NULL; + + buf = efi_alloc(dpsize + fpsize + sizeof(END)); + if (!buf) + return NULL; + + memcpy(buf, dp, dpsize); + pos = buf + dpsize; + + /* add file-path: */ + if (*path) { + fp = pos; + fp->dp.type = DEVICE_PATH_TYPE_MEDIA_DEVICE; + fp->dp.sub_type = DEVICE_PATH_SUB_TYPE_FILE_PATH; + fp->dp.length = (u16)fpsize; + path_to_uefi(fp->str, path); + pos += fpsize; + } + + memcpy(pos, &END, sizeof(END)); + + return buf; +} + +struct efi_device_path *efi_dp_from_uart(void) +{ + void *buf, *pos; + struct efi_device_path_uart *uart; + size_t dpsize = dp_size(dm_root()) + sizeof(*uart) + sizeof(END); + + buf = efi_alloc(dpsize); + if (!buf) + return NULL; + pos = dp_fill(buf, dm_root()); + uart = pos; + uart->dp.type = DEVICE_PATH_TYPE_MESSAGING_DEVICE; + uart->dp.sub_type = DEVICE_PATH_SUB_TYPE_MSG_UART; + uart->dp.length = sizeof(*uart); + pos += sizeof(*uart); + memcpy(pos, &END, sizeof(END)); + + return buf; +} + +struct efi_device_path __maybe_unused *efi_dp_from_eth(struct udevice *dev) +{ + void *buf, *start; + unsigned dpsize = 0; + + assert(dev); + + dpsize += dp_size(dev); + + start = buf = efi_alloc(dpsize + sizeof(END)); + if (!buf) + return NULL; + + buf = dp_fill(buf, dev); + + *((struct efi_device_path *)buf) = END; + + return start; +} + +/** + * efi_dp_from_ipv4() - set device path from IPv4 address + * + * Set the device path to an ethernet device path as provided by + * efi_dp_from_eth() concatenated with a device path of subtype + * DEVICE_PATH_SUB_TYPE_MSG_IPV4, and an END node. + * + * @ip: IPv4 local address + * @mask: network mask + * @srv: IPv4 remote/server address + * @dev: net udevice + * Return: pointer to device path, NULL on error + */ +static struct efi_device_path *efi_dp_from_ipv4(struct efi_ipv4_address *ip, + struct efi_ipv4_address *mask, + struct efi_ipv4_address *srv, + struct udevice *dev) +{ + struct efi_device_path *dp1, *dp2, *pos; + struct { + struct efi_device_path_ipv4 ipv4dp; + struct efi_device_path end; + } dp; + + memset(&dp.ipv4dp, 0, sizeof(dp.ipv4dp)); + dp.ipv4dp.dp.type = DEVICE_PATH_TYPE_MESSAGING_DEVICE; + dp.ipv4dp.dp.sub_type = DEVICE_PATH_SUB_TYPE_MSG_IPV4; + dp.ipv4dp.dp.length = sizeof(dp.ipv4dp); + dp.ipv4dp.protocol = 6; + if (ip) + memcpy(&dp.ipv4dp.local_ip_address, ip, sizeof(*ip)); + if (mask) + memcpy(&dp.ipv4dp.subnet_mask, mask, sizeof(*mask)); + if (srv) + memcpy(&dp.ipv4dp.remote_ip_address, srv, sizeof(*srv)); + pos = &dp.end; + memcpy(pos, &END, sizeof(END)); + + dp1 = efi_dp_from_eth(dev); + if (!dp1) + return NULL; + + dp2 = efi_dp_concat(dp1, (const struct efi_device_path *)&dp, 0); + + efi_free_pool(dp1); + + return dp2; +} + +/** + * efi_dp_from_http() - set device path from http + * + * Set the device path to an IPv4 path as provided by efi_dp_from_ipv4 + * concatenated with a device path of subtype DEVICE_PATH_SUB_TYPE_MSG_URI, + * and an END node. + * + * @server: URI of remote server + * @dev: net udevice + * Return: pointer to HTTP device path, NULL on error + */ +struct efi_device_path *efi_dp_from_http(const char *server, struct udevice *dev) +{ + struct efi_device_path *dp1, *dp2; + struct efi_device_path_uri *uridp; + efi_uintn_t uridp_len; + char *pos; + char tmp[128]; + struct efi_ipv4_address ip; + struct efi_ipv4_address mask; + + if ((server && strlen("http://") + strlen(server) + 1 > sizeof(tmp)) || + (!server && IS_ENABLED(CONFIG_NET_LWIP))) + return NULL; + + efi_net_get_addr(&ip, &mask, NULL, dev); + + dp1 = efi_dp_from_ipv4(&ip, &mask, NULL, dev); + if (!dp1) + return NULL; + + + strcpy(tmp, "http://"); + + if (server) { + strlcat(tmp, server, sizeof(tmp)); +#if !IS_ENABLED(CONFIG_NET_LWIP) + } else { + ip_to_string(net_server_ip, tmp + strlen("http://")); +#endif + } + + uridp_len = sizeof(struct efi_device_path) + strlen(tmp) + 1; + uridp = efi_alloc(uridp_len + sizeof(END)); + if (!uridp) { + log_err("Out of memory\n"); + return NULL; + } + uridp->dp.type = DEVICE_PATH_TYPE_MESSAGING_DEVICE; + uridp->dp.sub_type = DEVICE_PATH_SUB_TYPE_MSG_URI; + uridp->dp.length = uridp_len; + debug("device path: setting uri device path to %s\n", tmp); + memcpy(uridp->uri, tmp, strlen(tmp) + 1); + + pos = (char *)uridp + uridp_len; + memcpy(pos, &END, sizeof(END)); + + dp2 = efi_dp_concat(dp1, (const struct efi_device_path *)uridp, 0); + + efi_free_pool(uridp); + efi_free_pool(dp1); + + return dp2; +} + +/* Construct a device-path for memory-mapped image */ +struct efi_device_path *efi_dp_from_mem(uint32_t memory_type, + uint64_t start_address, + size_t size) +{ + struct efi_device_path_memory *mdp; + void *buf, *start; + + start = buf = efi_alloc(sizeof(*mdp) + sizeof(END)); + if (!buf) + return NULL; + + mdp = buf; + mdp->dp.type = DEVICE_PATH_TYPE_HARDWARE_DEVICE; + mdp->dp.sub_type = DEVICE_PATH_SUB_TYPE_MEMORY; + mdp->dp.length = sizeof(*mdp); + mdp->memory_type = memory_type; + mdp->start_address = start_address; + mdp->end_address = start_address + size; + buf = &mdp[1]; + + *((struct efi_device_path *)buf) = END; + + return start; +} + +/** + * efi_dp_split_file_path() - split of relative file path from device path + * + * Given a device path indicating a file on a device, separate the device + * path in two: the device path of the actual device and the file path + * relative to this device. + * + * @full_path: device path including device and file path + * @device_path: path of the device + * @file_path: relative path of the file or NULL if there is none + * Return: status code + */ +efi_status_t efi_dp_split_file_path(struct efi_device_path *full_path, + struct efi_device_path **device_path, + struct efi_device_path **file_path) +{ + struct efi_device_path *p, *dp, *fp = NULL; + + *device_path = NULL; + *file_path = NULL; + dp = efi_dp_dup(full_path); + if (!dp) + return EFI_OUT_OF_RESOURCES; + p = dp; + while (!EFI_DP_TYPE(p, MEDIA_DEVICE, FILE_PATH)) { + p = efi_dp_next(p); + if (!p) + goto out; + } + fp = efi_dp_dup(p); + if (!fp) + return EFI_OUT_OF_RESOURCES; + p->type = DEVICE_PATH_TYPE_END; + p->sub_type = DEVICE_PATH_SUB_TYPE_END; + p->length = sizeof(*p); + +out: + *device_path = dp; + *file_path = fp; + return EFI_SUCCESS; +} + +/** + * efi_dp_from_name() - convert U-Boot device and file path to device path + * + * @dev: U-Boot device, e.g. 'mmc' + * @devnr: U-Boot device number, e.g. 1 for 'mmc:1' + * @path: file path relative to U-Boot device, may be NULL + * @device: pointer to receive device path of the device + * @file: pointer to receive device path for the file + * Return: status code + */ +efi_status_t efi_dp_from_name(const char *dev, const char *devnr, + const char *path, + struct efi_device_path **device, + struct efi_device_path **file) +{ + struct blk_desc *desc = NULL; + struct efi_device_path *dp; + struct disk_partition fs_partition; + size_t image_size; + void *image_addr; + int part = 0; + + if (path && !file) + return EFI_INVALID_PARAMETER; + + if (IS_ENABLED(CONFIG_EFI_BINARY_EXEC) && + (!strcmp(dev, "Mem") || !strcmp(dev, "hostfs"))) { + /* loadm command and semihosting */ + efi_get_image_parameters(&image_addr, &image_size); + + dp = efi_dp_from_mem(EFI_RESERVED_MEMORY_TYPE, + (uintptr_t)image_addr, image_size); + } else if (IS_ENABLED(CONFIG_NETDEVICES) && + (!strcmp(dev, "Net") || !strcmp(dev, "Http"))) { + efi_net_dp_from_dev(&dp, eth_get_dev(), false); + } else if (!strcmp(dev, "Uart")) { + dp = efi_dp_from_uart(); + } else { + part = blk_get_device_part_str(dev, devnr, &desc, &fs_partition, + 1); + if (part < 0 || !desc) + return EFI_INVALID_PARAMETER; + + dp = efi_dp_from_part(desc, part); + } + if (device) + *device = dp; + + if (!path) + return EFI_SUCCESS; + + *file = efi_dp_from_file(dp, path); + if (!*file) + return EFI_OUT_OF_RESOURCES; + + return EFI_SUCCESS; +} + +/** + * efi_dp_check_length() - check length of a device path + * + * @dp: pointer to device path + * @maxlen: maximum length of the device path + * Return: + * * length of the device path if it is less or equal @maxlen + * * -1 if the device path is longer then @maxlen + * * -1 if a device path node has a length of less than 4 + * * -EINVAL if maxlen exceeds SSIZE_MAX + */ +ssize_t efi_dp_check_length(const struct efi_device_path *dp, + const size_t maxlen) +{ + ssize_t ret = 0; + u16 len; + + if (maxlen > SSIZE_MAX) + return -EINVAL; + for (;;) { + len = dp->length; + if (len < 4) + return -1; + ret += len; + if (ret > maxlen) + return -1; + if (dp->type == DEVICE_PATH_TYPE_END && + dp->sub_type == DEVICE_PATH_SUB_TYPE_END) + return ret; + dp = (const struct efi_device_path *)((const u8 *)dp + len); + } +} + +/** + * efi_dp_from_lo() - get device-path from load option + * + * The load options in U-Boot may contain multiple concatenated device-paths. + * The first device-path indicates the EFI binary to execute. Subsequent + * device-paths start with a VenMedia node where the GUID identifies the + * function (initrd or fdt). + * + * @lo: EFI load option containing a valid device path + * @guid: GUID identifying device-path or NULL for the EFI binary + * + * Return: + * device path excluding the matched VenMedia node or NULL. + * Caller must free the returned value. + */ +struct +efi_device_path *efi_dp_from_lo(struct efi_load_option *lo, + const efi_guid_t *guid) +{ + struct efi_device_path *fp = lo->file_path; + struct efi_device_path_vendor *vendor; + int lo_len = lo->file_path_length; + + if (!guid) + return efi_dp_dup(fp); + + for (; lo_len >= sizeof(struct efi_device_path); + lo_len -= fp->length, fp = (void *)fp + fp->length) { + if (lo_len < 0 || efi_dp_check_length(fp, lo_len) < 0) + break; + if (fp->type != DEVICE_PATH_TYPE_MEDIA_DEVICE || + fp->sub_type != DEVICE_PATH_SUB_TYPE_VENDOR_PATH) + continue; + + vendor = (struct efi_device_path_vendor *)fp; + if (!guidcmp(&vendor->guid, guid)) + return efi_dp_dup(efi_dp_next(fp)); + } + log_debug("VenMedia(%pUl) not found in %ls\n", &guid, lo->label); + + return NULL; +} + +/** + * search_gpt_dp_node() - search gpt device path node + * + * @device_path: device path + * + * Return: pointer to the gpt device path node + */ +struct efi_device_path *search_gpt_dp_node(struct efi_device_path *device_path) +{ + struct efi_device_path *dp = device_path; + + while (dp) { + if (dp->type == DEVICE_PATH_TYPE_MEDIA_DEVICE && + dp->sub_type == DEVICE_PATH_SUB_TYPE_HARD_DRIVE_PATH) { + struct efi_device_path_hard_drive_path *hd_dp = + (struct efi_device_path_hard_drive_path *)dp; + + if (hd_dp->partmap_type == PART_FORMAT_GPT && + hd_dp->signature_type == SIG_TYPE_GUID) + return dp; + } + dp = efi_dp_next(dp); + } + + return NULL; +} diff --git a/lib/efi_loader/efi_device_path_to_text.c b/lib/efi_loader/efi_device_path_to_text.c new file mode 100644 index 00000000000..f6889cb7399 --- /dev/null +++ b/lib/efi_loader/efi_device_path_to_text.c @@ -0,0 +1,483 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI device path interface + * + * Copyright (c) 2017 Heinrich Schuchardt + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <blk.h> +#include <efi_loader.h> +#include <malloc.h> +#include <net.h> + +#define MAC_OUTPUT_LEN 22 +#define UNKNOWN_OUTPUT_LEN 23 + +#define MAX_NODE_LEN 512 +#define MAX_PATH_LEN 1024 + +const efi_guid_t efi_guid_device_path_to_text_protocol = + EFI_DEVICE_PATH_TO_TEXT_PROTOCOL_GUID; + +/** + * efi_str_to_u16() - convert ASCII string to UTF-16 + * + * A u16 buffer is allocated from pool. The ASCII string is copied to the u16 + * buffer. + * + * @str: ASCII string + * Return: UTF-16 string. NULL if out of memory. + */ +static u16 *efi_str_to_u16(char *str) +{ + efi_uintn_t len; + u16 *out, *dst; + + len = sizeof(u16) * (utf8_utf16_strlen(str) + 1); + out = efi_alloc(len); + if (!out) + return NULL; + dst = out; + utf8_utf16_strcpy(&dst, str); + return out; +} + +static char *dp_unknown(char *s, struct efi_device_path *dp) +{ + s += sprintf(s, "UNKNOWN(%04x,%04x)", dp->type, dp->sub_type); + return s; +} + +static char *dp_hardware(char *s, struct efi_device_path *dp) +{ + switch (dp->sub_type) { + case DEVICE_PATH_SUB_TYPE_MEMORY: { + struct efi_device_path_memory *mdp = + (struct efi_device_path_memory *)dp; + s += sprintf(s, "MemoryMapped(0x%x,0x%llx,0x%llx)", + mdp->memory_type, + mdp->start_address, + mdp->end_address); + break; + } + case DEVICE_PATH_SUB_TYPE_VENDOR: { + int i, n; + struct efi_device_path_vendor *vdp = + (struct efi_device_path_vendor *)dp; + + s += sprintf(s, "VenHw(%pUl", &vdp->guid); + n = (int)vdp->dp.length - sizeof(struct efi_device_path_vendor); + /* Node must fit into MAX_NODE_LEN) */ + if (n > 0 && n < MAX_NODE_LEN / 2 - 22) { + s += sprintf(s, ","); + for (i = 0; i < n; ++i) + s += sprintf(s, "%02x", vdp->vendor_data[i]); + } + s += sprintf(s, ")"); + break; + } + case DEVICE_PATH_SUB_TYPE_CONTROLLER: { + struct efi_device_path_controller *cdp = + (struct efi_device_path_controller *)dp; + + s += sprintf(s, "Ctrl(0x%0x)", cdp->controller_number); + break; + } + default: + s = dp_unknown(s, dp); + break; + } + return s; +} + +static char *dp_acpi(char *s, struct efi_device_path *dp) +{ + switch (dp->sub_type) { + case DEVICE_PATH_SUB_TYPE_ACPI_DEVICE: { + struct efi_device_path_acpi_path *adp = + (struct efi_device_path_acpi_path *)dp; + + s += sprintf(s, "Acpi(PNP%04X,%d)", EISA_PNP_NUM(adp->hid), + adp->uid); + break; + } + default: + s = dp_unknown(s, dp); + break; + } + return s; +} + +static char *dp_msging(char *s, struct efi_device_path *dp) +{ + switch (dp->sub_type) { + case DEVICE_PATH_SUB_TYPE_MSG_ATAPI: { + struct efi_device_path_atapi *ide = + (struct efi_device_path_atapi *)dp; + s += sprintf(s, "Ata(%d,%d,%d)", ide->primary_secondary, + ide->slave_master, ide->logical_unit_number); + break; + } + case DEVICE_PATH_SUB_TYPE_MSG_SCSI: { + struct efi_device_path_scsi *ide = + (struct efi_device_path_scsi *)dp; + s += sprintf(s, "Scsi(%u,%u)", ide->target_id, + ide->logical_unit_number); + break; + } + case DEVICE_PATH_SUB_TYPE_MSG_UART: { + struct efi_device_path_uart *uart = + (struct efi_device_path_uart *)dp; + const char parity_str[6] = {'D', 'N', 'E', 'O', 'M', 'S'}; + const char *stop_bits_str[4] = { "D", "1", "1.5", "2" }; + + s += sprintf(s, "Uart(%lld,%d,", uart->baud_rate, + uart->data_bits); + + /* + * Parity and stop bits can either both use keywords or both use + * numbers but numbers and keywords should not be mixed. Let's + * go for keywords as this is what EDK II does. For illegal + * values fall back to numbers. + */ + if (uart->parity < 6) + s += sprintf(s, "%c,", parity_str[uart->parity]); + else + s += sprintf(s, "%d,", uart->parity); + if (uart->stop_bits < 4) + s += sprintf(s, "%s)", stop_bits_str[uart->stop_bits]); + else + s += sprintf(s, "%d)", uart->stop_bits); + break; + } + case DEVICE_PATH_SUB_TYPE_MSG_USB: { + struct efi_device_path_usb *udp = + (struct efi_device_path_usb *)dp; + s += sprintf(s, "USB(0x%x,0x%x)", udp->parent_port_number, + udp->usb_interface); + break; + } + case DEVICE_PATH_SUB_TYPE_MSG_MAC_ADDR: { + int i, n = sizeof(struct efi_mac_addr); + struct efi_device_path_mac_addr *mdp = + (struct efi_device_path_mac_addr *)dp; + + if (mdp->if_type <= 1) + n = 6; + s += sprintf(s, "MAC("); + for (i = 0; i < n; ++i) + s += sprintf(s, "%02x", mdp->mac.addr[i]); + s += sprintf(s, ",%u)", mdp->if_type); + + break; + } + case DEVICE_PATH_SUB_TYPE_MSG_IPV4: { + struct efi_device_path_ipv4 *idp = + (struct efi_device_path_ipv4 *)dp; + + s += sprintf(s, "IPv4(%pI4,", &idp->remote_ip_address); + switch (idp->protocol) { + case IPPROTO_TCP: + s += sprintf(s, "TCP,"); + case IPPROTO_UDP: + s += sprintf(s, "UDP,"); + default: + s += sprintf(s, "0x%x,", idp->protocol); + } + s += sprintf(s, idp->static_ip_address ? "Static" : "DHCP"); + s += sprintf(s, ",%pI4", &idp->local_ip_address); + if (idp->dp.length == sizeof(struct efi_device_path_ipv4)) + s += sprintf(s, ",%pI4,%pI4", &idp->gateway_ip_address, + &idp->subnet_mask); + s += sprintf(s, ")"); + + break; + } + case DEVICE_PATH_SUB_TYPE_MSG_USB_CLASS: { + struct efi_device_path_usb_class *ucdp = + (struct efi_device_path_usb_class *)dp; + + s += sprintf(s, "UsbClass(0x%x,0x%x,0x%x,0x%x,0x%x)", + ucdp->vendor_id, ucdp->product_id, + ucdp->device_class, ucdp->device_subclass, + ucdp->device_protocol); + + break; + } + case DEVICE_PATH_SUB_TYPE_MSG_SATA: { + struct efi_device_path_sata *sdp = + (struct efi_device_path_sata *) dp; + + s += sprintf(s, "Sata(0x%x,0x%x,0x%x)", + sdp->hba_port, + sdp->port_multiplier_port, + sdp->logical_unit_number); + break; + } + case DEVICE_PATH_SUB_TYPE_MSG_NVME: { + struct efi_device_path_nvme *ndp = + (struct efi_device_path_nvme *)dp; + u32 ns_id; + + memcpy(&ns_id, &ndp->ns_id, sizeof(ns_id)); + s += sprintf(s, "NVMe(0x%x,", ns_id); + + /* Display byte 7 first, byte 0 last */ + for (int i = 0; i < 8; ++i) + s += sprintf(s, "%s%02x", i ? "-" : "", + ndp->eui64[i ^ 7]); + s += sprintf(s, ")"); + + break; + } + case DEVICE_PATH_SUB_TYPE_MSG_URI: { + struct efi_device_path_uri *udp = + (struct efi_device_path_uri *)dp; + int n; + + n = (int)udp->dp.length - sizeof(struct efi_device_path_uri); + + s += sprintf(s, "Uri("); + if (n > 0 && n < MAX_NODE_LEN - 6) + s += snprintf(s, n, "%s", (char *)udp->uri); + s += sprintf(s, ")"); + break; + } + case DEVICE_PATH_SUB_TYPE_MSG_SD: + case DEVICE_PATH_SUB_TYPE_MSG_MMC: { + const char *typename = + (dp->sub_type == DEVICE_PATH_SUB_TYPE_MSG_SD) ? + "SD" : "eMMC"; + struct efi_device_path_sd_mmc_path *sddp = + (struct efi_device_path_sd_mmc_path *)dp; + s += sprintf(s, "%s(%u)", typename, sddp->slot_number); + break; + } + default: + s = dp_unknown(s, dp); + break; + } + return s; +} + +/* + * Convert a media device path node to text. + * + * @s output buffer + * @dp device path node + * Return: next unused buffer address + */ +static char *dp_media(char *s, struct efi_device_path *dp) +{ + switch (dp->sub_type) { + case DEVICE_PATH_SUB_TYPE_HARD_DRIVE_PATH: { + struct efi_device_path_hard_drive_path *hddp = + (struct efi_device_path_hard_drive_path *)dp; + void *sig = hddp->partition_signature; + u64 start; + u64 end; + + /* Copy from packed structure to aligned memory */ + memcpy(&start, &hddp->partition_start, sizeof(start)); + memcpy(&end, &hddp->partition_end, sizeof(end)); + + switch (hddp->signature_type) { + case SIG_TYPE_MBR: { + u32 signature; + + memcpy(&signature, sig, sizeof(signature)); + s += sprintf( + s, "HD(%d,MBR,0x%08x,0x%llx,0x%llx)", + hddp->partition_number, signature, start, end); + break; + } + case SIG_TYPE_GUID: + s += sprintf( + s, "HD(%d,GPT,%pUl,0x%llx,0x%llx)", + hddp->partition_number, sig, start, end); + break; + default: + s += sprintf( + s, "HD(%d,0x%02x,0,0x%llx,0x%llx)", + hddp->partition_number, hddp->partmap_type, + start, end); + break; + } + + break; + } + case DEVICE_PATH_SUB_TYPE_CDROM_PATH: { + struct efi_device_path_cdrom_path *cddp = + (struct efi_device_path_cdrom_path *)dp; + s += sprintf(s, "CDROM(%u,0x%llx,0x%llx)", cddp->boot_entry, + cddp->partition_start, cddp->partition_size); + break; + } + case DEVICE_PATH_SUB_TYPE_VENDOR_PATH: { + int i, n; + struct efi_device_path_vendor *vdp = + (struct efi_device_path_vendor *)dp; + + s += sprintf(s, "VenMedia(%pUl", &vdp->guid); + n = (int)vdp->dp.length - sizeof(struct efi_device_path_vendor); + /* Node must fit into MAX_NODE_LEN) */ + if (n > 0 && n < MAX_NODE_LEN / 2 - 24) { + s += sprintf(s, ","); + for (i = 0; i < n; ++i) + s += sprintf(s, "%02x", vdp->vendor_data[i]); + } + s += sprintf(s, ")"); + break; + } + case DEVICE_PATH_SUB_TYPE_FILE_PATH: { + struct efi_device_path_file_path *fp = + (struct efi_device_path_file_path *)dp; + u16 *buffer; + int slen = dp->length - sizeof(*dp); + + /* two bytes for \0, extra byte if dp->length is odd */ + buffer = calloc(1, slen + 3); + if (!buffer) { + log_err("Out of memory\n"); + return s; + } + memcpy(buffer, fp->str, dp->length - sizeof(*dp)); + s += snprintf(s, MAX_NODE_LEN - 1, "%ls", buffer); + free(buffer); + break; + } + default: + s = dp_unknown(s, dp); + break; + } + return s; +} + +/* + * Converts a single node to a char string. + * + * @buffer output buffer + * @dp device path or node + * Return: end of string + */ +static char *efi_convert_single_device_node_to_text( + char *buffer, + struct efi_device_path *dp) +{ + char *str = buffer; + + switch (dp->type) { + case DEVICE_PATH_TYPE_HARDWARE_DEVICE: + str = dp_hardware(str, dp); + break; + case DEVICE_PATH_TYPE_ACPI_DEVICE: + str = dp_acpi(str, dp); + break; + case DEVICE_PATH_TYPE_MESSAGING_DEVICE: + str = dp_msging(str, dp); + break; + case DEVICE_PATH_TYPE_MEDIA_DEVICE: + str = dp_media(str, dp); + break; + case DEVICE_PATH_TYPE_END: + break; + default: + str = dp_unknown(str, dp); + } + + *str = '\0'; + return str; +} + +/* + * This function implements the ConvertDeviceNodeToText service of the + * EFI_DEVICE_PATH_TO_TEXT_PROTOCOL. + * See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * device_node device node to be converted + * display_only true if the shorter text representation shall be used + * allow_shortcuts true if shortcut forms may be used + * Return: text representation of the device path + * NULL if out of memory of device_path is NULL + */ +static uint16_t EFIAPI *efi_convert_device_node_to_text( + struct efi_device_path *device_node, + bool display_only, + bool allow_shortcuts) +{ + char str[MAX_NODE_LEN]; + uint16_t *text = NULL; + + EFI_ENTRY("%p, %d, %d", device_node, display_only, allow_shortcuts); + + if (!device_node) + goto out; + efi_convert_single_device_node_to_text(str, device_node); + + text = efi_str_to_u16(str); + +out: + EFI_EXIT(EFI_SUCCESS); + return text; +} + +/* + * This function implements the ConvertDevicePathToText service of the + * EFI_DEVICE_PATH_TO_TEXT_PROTOCOL. + * See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * device_path device path to be converted + * display_only true if the shorter text representation shall be used + * allow_shortcuts true if shortcut forms may be used + * Return: text representation of the device path + * NULL if out of memory of device_path is NULL + */ +static uint16_t EFIAPI *efi_convert_device_path_to_text( + struct efi_device_path *device_path, + bool display_only, + bool allow_shortcuts) +{ + uint16_t *text = NULL; + char buffer[MAX_PATH_LEN]; + char *str = buffer; + + EFI_ENTRY("%p, %d, %d", device_path, display_only, allow_shortcuts); + + if (!device_path) + goto out; + while (device_path && str + MAX_NODE_LEN < buffer + MAX_PATH_LEN) { + if (device_path->type == DEVICE_PATH_TYPE_END) { + if (device_path->sub_type != + DEVICE_PATH_SUB_TYPE_INSTANCE_END) + break; + *str++ = ','; + } else { + *str++ = '/'; + str = efi_convert_single_device_node_to_text( + str, device_path); + } + *(u8 **)&device_path += device_path->length; + } + + *str = 0; + text = efi_str_to_u16(buffer); + +out: + EFI_EXIT(EFI_SUCCESS); + return text; +} + +/* helper for debug prints.. efi_free_pool() the result. */ +uint16_t *efi_dp_str(struct efi_device_path *dp) +{ + return EFI_CALL(efi_convert_device_path_to_text(dp, true, true)); +} + +const struct efi_device_path_to_text_protocol efi_device_path_to_text = { + .convert_device_node_to_text = efi_convert_device_node_to_text, + .convert_device_path_to_text = efi_convert_device_path_to_text, +}; diff --git a/lib/efi_loader/efi_device_path_utilities.c b/lib/efi_loader/efi_device_path_utilities.c new file mode 100644 index 00000000000..552c5bb1f05 --- /dev/null +++ b/lib/efi_loader/efi_device_path_utilities.c @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI device path interface + * + * Copyright (c) 2017 Leif Lindholm + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <efi_loader.h> + +const efi_guid_t efi_guid_device_path_utilities_protocol = + EFI_DEVICE_PATH_UTILITIES_PROTOCOL_GUID; + +/* + * Get size of a device path. + * + * This function implements the GetDevicePathSize service of the device path + * utilities protocol. The device path length includes the end of path tag + * which may be an instance end. + * + * See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * @device_path device path + * Return: size in bytes + */ +static efi_uintn_t EFIAPI get_device_path_size( + const struct efi_device_path *device_path) +{ + efi_uintn_t sz = 0; + + EFI_ENTRY("%pD", device_path); + /* size includes the END node: */ + if (device_path) + sz = efi_dp_size(device_path) + sizeof(struct efi_device_path); + return EFI_EXIT(sz); +} + +/* + * Duplicate a device path. + * + * This function implements the DuplicateDevicePath service of the device path + * utilities protocol. + * + * The UEFI spec does not indicate what happens to the end tag. We follow the + * EDK2 logic: In case the device path ends with an end of instance tag, the + * copy will also end with an end of instance tag. + * + * See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * @device_path device path + * Return: copy of the device path + */ +static struct efi_device_path * EFIAPI duplicate_device_path( + const struct efi_device_path *device_path) +{ + EFI_ENTRY("%pD", device_path); + return EFI_EXIT(efi_dp_dup(device_path)); +} + +/* + * Append device path. + * + * This function implements the AppendDevicePath service of the device path + * utilities protocol. + * + * See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * @src1 1st device path + * @src2 2nd device path + * Return: concatenated device path + */ +static struct efi_device_path * EFIAPI append_device_path( + const struct efi_device_path *src1, + const struct efi_device_path *src2) +{ + EFI_ENTRY("%pD, %pD", src1, src2); + return EFI_EXIT(efi_dp_concat(src1, src2, 0)); +} + +/* + * Append device path node. + * + * This function implements the AppendDeviceNode service of the device path + * utilities protocol. + * + * See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * @device_path device path + * @device_node device node + * Return: concatenated device path + */ +static struct efi_device_path * EFIAPI append_device_node( + const struct efi_device_path *device_path, + const struct efi_device_path *device_node) +{ + EFI_ENTRY("%pD, %p", device_path, device_node); + return EFI_EXIT(efi_dp_append_node(device_path, device_node)); +} + +/* + * Append device path instance. + * + * This function implements the AppendDevicePathInstance service of the device + * path utilities protocol. + * + * See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * @device_path 1st device path + * @device_path_instance 2nd device path + * Return: concatenated device path + */ +static struct efi_device_path * EFIAPI append_device_path_instance( + const struct efi_device_path *device_path, + const struct efi_device_path *device_path_instance) +{ + EFI_ENTRY("%pD, %pD", device_path, device_path_instance); + return EFI_EXIT(efi_dp_append_instance(device_path, + device_path_instance)); +} + +/* + * Get next device path instance. + * + * This function implements the GetNextDevicePathInstance service of the device + * path utilities protocol. + * + * See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * @device_path_instance next device path instance + * @device_path_instance_size size of the device path instance + * Return: concatenated device path + */ +static struct efi_device_path * EFIAPI get_next_device_path_instance( + struct efi_device_path **device_path_instance, + efi_uintn_t *device_path_instance_size) +{ + EFI_ENTRY("%pD, %p", device_path_instance, device_path_instance_size); + return EFI_EXIT(efi_dp_get_next_instance(device_path_instance, + device_path_instance_size)); +} + +/* + * Check if a device path contains more than one instance. + * + * This function implements the AppendDeviceNode service of the device path + * utilities protocol. + * + * See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * @device_path device path + * @device_node device node + * Return: concatenated device path + */ +static bool EFIAPI is_device_path_multi_instance( + const struct efi_device_path *device_path) +{ + EFI_ENTRY("%pD", device_path); + return EFI_EXIT(efi_dp_is_multi_instance(device_path)); +} + +/* + * Create device node. + * + * This function implements the CreateDeviceNode service of the device path + * utilities protocol. + * + * See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * @node_type node type + * @node_sub_type node sub type + * @node_length node length + * Return: device path node + */ +static struct efi_device_path * EFIAPI create_device_node( + uint8_t node_type, uint8_t node_sub_type, uint16_t node_length) +{ + EFI_ENTRY("%u, %u, %u", node_type, node_sub_type, node_length); + return EFI_EXIT(efi_dp_create_device_node(node_type, node_sub_type, + node_length)); +} + +const struct efi_device_path_utilities_protocol efi_device_path_utilities = { + .get_device_path_size = get_device_path_size, + .duplicate_device_path = duplicate_device_path, + .append_device_path = append_device_path, + .append_device_node = append_device_node, + .append_device_path_instance = append_device_path_instance, + .get_next_device_path_instance = get_next_device_path_instance, + .is_device_path_multi_instance = is_device_path_multi_instance, + .create_device_node = create_device_node, +}; diff --git a/lib/efi_loader/efi_disk.c b/lib/efi_loader/efi_disk.c new file mode 100644 index 00000000000..5452640354e --- /dev/null +++ b/lib/efi_loader/efi_disk.c @@ -0,0 +1,843 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI application disk support + * + * Copyright (c) 2016 Alexander Graf + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <blk.h> +#include <dm.h> +#include <dm/device-internal.h> +#include <dm/tag.h> +#include <event.h> +#include <efi_driver.h> +#include <efi_loader.h> +#include <fs.h> +#include <log.h> +#include <part.h> +#include <malloc.h> + +struct efi_system_partition efi_system_partition = { + .uclass_id = UCLASS_INVALID, +}; + +const efi_guid_t efi_block_io_guid = EFI_BLOCK_IO_PROTOCOL_GUID; +const efi_guid_t efi_system_partition_guid = PARTITION_SYSTEM_GUID; + +/** + * struct efi_disk_obj - EFI disk object + * + * @header: EFI object header + * @ops: EFI disk I/O protocol interface + * @media: block I/O media information + * @dp: device path to the block device + * @volume: simple file system protocol of the partition + */ +struct efi_disk_obj { + struct efi_object header; + struct efi_block_io ops; + struct efi_block_io_media media; + struct efi_device_path *dp; + struct efi_simple_file_system_protocol *volume; +}; + +/** + * efi_disk_reset() - reset block device + * + * This function implements the Reset service of the EFI_BLOCK_IO_PROTOCOL. + * + * As U-Boot's block devices do not have a reset function simply return + * EFI_SUCCESS. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @this: pointer to the BLOCK_IO_PROTOCOL + * @extended_verification: extended verification + * Return: status code + */ +static efi_status_t EFIAPI efi_disk_reset(struct efi_block_io *this, + char extended_verification) +{ + EFI_ENTRY("%p, %x", this, extended_verification); + return EFI_EXIT(EFI_SUCCESS); +} + +/** + * efi_disk_is_removable() - check if the device is removable media + * @handle: efi object handle; + * + * Examine the device and determine if the device is a local block device + * and removable media. + * + * Return: true if removable, false otherwise + */ +bool efi_disk_is_removable(efi_handle_t handle) +{ + struct efi_handler *handler; + struct efi_block_io *io; + efi_status_t ret; + + ret = efi_search_protocol(handle, &efi_block_io_guid, &handler); + if (ret != EFI_SUCCESS) + return false; + + io = handler->protocol_interface; + + if (!io || !io->media) + return false; + + return (bool)io->media->removable_media; +} + +enum efi_disk_direction { + EFI_DISK_READ, + EFI_DISK_WRITE, +}; + +static efi_status_t efi_disk_rw_blocks(struct efi_block_io *this, + u32 media_id, u64 lba, unsigned long buffer_size, + void *buffer, enum efi_disk_direction direction) +{ + struct efi_disk_obj *diskobj; + int blksz; + int blocks; + unsigned long n; + + diskobj = container_of(this, struct efi_disk_obj, ops); + blksz = diskobj->media.block_size; + blocks = buffer_size / blksz; + + EFI_PRINT("blocks=%x lba=%llx blksz=%x dir=%d\n", + blocks, lba, blksz, direction); + + /* We only support full block access */ + if (buffer_size & (blksz - 1)) + return EFI_BAD_BUFFER_SIZE; + + if (CONFIG_IS_ENABLED(PARTITIONS) && + device_get_uclass_id(diskobj->header.dev) == UCLASS_PARTITION) { + if (direction == EFI_DISK_READ) + n = disk_blk_read(diskobj->header.dev, lba, blocks, + buffer); + else + n = disk_blk_write(diskobj->header.dev, lba, blocks, + buffer); + } else { + /* dev is a block device (UCLASS_BLK) */ + struct blk_desc *desc; + + desc = dev_get_uclass_plat(diskobj->header.dev); + if (direction == EFI_DISK_READ) + n = blk_dread(desc, lba, blocks, buffer); + else + n = blk_dwrite(desc, lba, blocks, buffer); + } + + /* We don't do interrupts, so check for timers cooperatively */ + efi_timer_check(); + + EFI_PRINT("n=%lx blocks=%x\n", n, blocks); + + if (n != blocks) + return EFI_DEVICE_ERROR; + + return EFI_SUCCESS; +} + +/** + * efi_disk_read_blocks() - reads blocks from device + * + * This function implements the ReadBlocks service of the EFI_BLOCK_IO_PROTOCOL. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @this: pointer to the BLOCK_IO_PROTOCOL + * @media_id: id of the medium to be read from + * @lba: starting logical block for reading + * @buffer_size: size of the read buffer + * @buffer: pointer to the destination buffer + * Return: status code + */ +static efi_status_t EFIAPI efi_disk_read_blocks(struct efi_block_io *this, + u32 media_id, u64 lba, efi_uintn_t buffer_size, + void *buffer) +{ + void *real_buffer = buffer; + efi_status_t r; + + if (!this) + return EFI_INVALID_PARAMETER; + /* TODO: check for media changes */ + if (media_id != this->media->media_id) + return EFI_MEDIA_CHANGED; + if (!this->media->media_present) + return EFI_NO_MEDIA; + /* media->io_align is a power of 2 or 0 */ + if (this->media->io_align && + (uintptr_t)buffer & (this->media->io_align - 1)) + return EFI_INVALID_PARAMETER; + if (lba * this->media->block_size + buffer_size > + (this->media->last_block + 1) * this->media->block_size) + return EFI_INVALID_PARAMETER; + +#ifdef CONFIG_EFI_LOADER_BOUNCE_BUFFER + if (buffer_size > EFI_LOADER_BOUNCE_BUFFER_SIZE) { + r = efi_disk_read_blocks(this, media_id, lba, + EFI_LOADER_BOUNCE_BUFFER_SIZE, buffer); + if (r != EFI_SUCCESS) + return r; + return efi_disk_read_blocks(this, media_id, lba + + EFI_LOADER_BOUNCE_BUFFER_SIZE / this->media->block_size, + buffer_size - EFI_LOADER_BOUNCE_BUFFER_SIZE, + buffer + EFI_LOADER_BOUNCE_BUFFER_SIZE); + } + + real_buffer = efi_bounce_buffer; +#endif + + EFI_ENTRY("%p, %x, %llx, %zx, %p", this, media_id, lba, + buffer_size, buffer); + + r = efi_disk_rw_blocks(this, media_id, lba, buffer_size, real_buffer, + EFI_DISK_READ); + + /* Copy from bounce buffer to real buffer if necessary */ + if ((r == EFI_SUCCESS) && (real_buffer != buffer)) + memcpy(buffer, real_buffer, buffer_size); + + return EFI_EXIT(r); +} + +/** + * efi_disk_write_blocks() - writes blocks to device + * + * This function implements the WriteBlocks service of the + * EFI_BLOCK_IO_PROTOCOL. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @this: pointer to the BLOCK_IO_PROTOCOL + * @media_id: id of the medium to be written to + * @lba: starting logical block for writing + * @buffer_size: size of the write buffer + * @buffer: pointer to the source buffer + * Return: status code + */ +static efi_status_t EFIAPI efi_disk_write_blocks(struct efi_block_io *this, + u32 media_id, u64 lba, efi_uintn_t buffer_size, + void *buffer) +{ + void *real_buffer = buffer; + efi_status_t r; + + if (!this) + return EFI_INVALID_PARAMETER; + if (this->media->read_only) + return EFI_WRITE_PROTECTED; + /* TODO: check for media changes */ + if (media_id != this->media->media_id) + return EFI_MEDIA_CHANGED; + if (!this->media->media_present) + return EFI_NO_MEDIA; + /* media->io_align is a power of 2 or 0 */ + if (this->media->io_align && + (uintptr_t)buffer & (this->media->io_align - 1)) + return EFI_INVALID_PARAMETER; + if (lba * this->media->block_size + buffer_size > + (this->media->last_block + 1) * this->media->block_size) + return EFI_INVALID_PARAMETER; + +#ifdef CONFIG_EFI_LOADER_BOUNCE_BUFFER + if (buffer_size > EFI_LOADER_BOUNCE_BUFFER_SIZE) { + r = efi_disk_write_blocks(this, media_id, lba, + EFI_LOADER_BOUNCE_BUFFER_SIZE, buffer); + if (r != EFI_SUCCESS) + return r; + return efi_disk_write_blocks(this, media_id, lba + + EFI_LOADER_BOUNCE_BUFFER_SIZE / this->media->block_size, + buffer_size - EFI_LOADER_BOUNCE_BUFFER_SIZE, + buffer + EFI_LOADER_BOUNCE_BUFFER_SIZE); + } + + real_buffer = efi_bounce_buffer; +#endif + + EFI_ENTRY("%p, %x, %llx, %zx, %p", this, media_id, lba, + buffer_size, buffer); + + /* Populate bounce buffer if necessary */ + if (real_buffer != buffer) + memcpy(real_buffer, buffer, buffer_size); + + r = efi_disk_rw_blocks(this, media_id, lba, buffer_size, real_buffer, + EFI_DISK_WRITE); + + return EFI_EXIT(r); +} + +/** + * efi_disk_flush_blocks() - flushes modified data to the device + * + * This function implements the FlushBlocks service of the + * EFI_BLOCK_IO_PROTOCOL. + * + * As we always write synchronously nothing is done here. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @this: pointer to the BLOCK_IO_PROTOCOL + * Return: status code + */ +static efi_status_t EFIAPI efi_disk_flush_blocks(struct efi_block_io *this) +{ + EFI_ENTRY("%p", this); + return EFI_EXIT(EFI_SUCCESS); +} + +static const struct efi_block_io block_io_disk_template = { + .reset = &efi_disk_reset, + .read_blocks = &efi_disk_read_blocks, + .write_blocks = &efi_disk_write_blocks, + .flush_blocks = &efi_disk_flush_blocks, +}; + +/** + * efi_fs_from_path() - retrieve simple file system protocol + * + * Gets the simple file system protocol for a file device path. + * + * The full path provided is split into device part and into a file + * part. The device part is used to find the handle on which the + * simple file system protocol is installed. + * + * @full_path: device path including device and file + * Return: simple file system protocol + */ +struct efi_simple_file_system_protocol * +efi_fs_from_path(struct efi_device_path *full_path) +{ + struct efi_object *efiobj; + struct efi_handler *handler; + struct efi_device_path *device_path; + struct efi_device_path *file_path; + efi_status_t ret; + + /* Split the path into a device part and a file part */ + ret = efi_dp_split_file_path(full_path, &device_path, &file_path); + if (ret != EFI_SUCCESS) + return NULL; + efi_free_pool(file_path); + + /* Get the EFI object for the partition */ + efiobj = efi_dp_find_obj(device_path, NULL, NULL); + efi_free_pool(device_path); + if (!efiobj) + return NULL; + + /* Find the simple file system protocol */ + ret = efi_search_protocol(efiobj, &efi_simple_file_system_protocol_guid, + &handler); + if (ret != EFI_SUCCESS) + return NULL; + + /* Return the simple file system protocol for the partition */ + return handler->protocol_interface; +} + +/** + * efi_fs_exists() - check if a partition bears a file system + * + * @desc: block device descriptor + * @part: partition number + * Return: 1 if a file system exists on the partition + * 0 otherwise + */ +static int efi_fs_exists(struct blk_desc *desc, int part) +{ + if (fs_set_blk_dev_with_part(desc, part)) + return 0; + + if (fs_get_type() == FS_TYPE_ANY) + return 0; + + fs_close(); + + return 1; +} + +static void efi_disk_free_diskobj(struct efi_disk_obj *diskobj) +{ + struct efi_device_path *dp = diskobj->dp; + struct efi_simple_file_system_protocol *volume = diskobj->volume; + + /* + * ignore error of efi_delete_handle() since this function + * is expected to be called in error path. + */ + efi_delete_handle(&diskobj->header); + efi_free_pool(dp); + free(volume); +} + +/** + * efi_disk_add_dev() - create a handle for a partition or disk + * + * @parent: parent handle + * @dp_parent: parent device path + * @desc: internal block device + * @part_info: partition info + * @part: partition + * @disk: pointer to receive the created handle + * @agent_handle: handle of the EFI block driver + * Return: disk object + */ +static efi_status_t efi_disk_add_dev( + efi_handle_t parent, + struct efi_device_path *dp_parent, + struct blk_desc *desc, + struct disk_partition *part_info, + unsigned int part, + struct efi_disk_obj **disk, + efi_handle_t agent_handle) +{ + struct efi_disk_obj *diskobj; + struct efi_object *handle; + const efi_guid_t *esp_guid = NULL; + efi_status_t ret; + + /* Don't add empty devices */ + if (!desc->lba) + return EFI_NOT_READY; + + diskobj = calloc(1, sizeof(*diskobj)); + if (!diskobj) + return EFI_OUT_OF_RESOURCES; + + /* Hook up to the device list */ + efi_add_handle(&diskobj->header); + + /* Fill in object data */ + if (part_info) { + struct efi_device_path *node = efi_dp_part_node(desc, part); + struct efi_handler *handler; + void *protocol_interface; + + if (!node) { + ret = EFI_OUT_OF_RESOURCES; + log_debug("no node\n"); + goto error; + } + + /* Parent must expose EFI_BLOCK_IO_PROTOCOL */ + ret = efi_search_protocol(parent, &efi_block_io_guid, &handler); + if (ret != EFI_SUCCESS) { + log_debug("search failed\n"); + goto error; + } + + /* + * Link the partition (child controller) to the block device + * (controller). + */ + ret = efi_protocol_open(handler, &protocol_interface, NULL, + &diskobj->header, + EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER); + if (ret != EFI_SUCCESS) { + log_debug("prot open failed\n"); + goto error; + } + + diskobj->dp = efi_dp_append_node(dp_parent, node); + efi_free_pool(node); + diskobj->media.last_block = part_info->size - 1; + if (part_info->bootable & PART_EFI_SYSTEM_PARTITION) + esp_guid = &efi_system_partition_guid; + } else { + diskobj->dp = efi_dp_from_part(desc, part); + diskobj->media.last_block = desc->lba - 1; + } + + /* + * Install the device path and the block IO protocol. + * + * InstallMultipleProtocolInterfaces() checks if the device path is + * already installed on an other handle and returns EFI_ALREADY_STARTED + * in this case. + */ + handle = &diskobj->header; + ret = efi_install_multiple_protocol_interfaces( + &handle, + &efi_guid_device_path, diskobj->dp, + &efi_block_io_guid, &diskobj->ops, + /* + * esp_guid must be last entry as it + * can be NULL. Its interface is NULL. + */ + esp_guid, NULL, + NULL); + if (ret != EFI_SUCCESS) { + log_debug("install failed %lx\n", ret); + goto error; + } + + /* + * On partitions or whole disks without partitions install the + * simple file system protocol if a file system is available. + */ + if ((part || desc->part_type == PART_TYPE_UNKNOWN) && + efi_fs_exists(desc, part)) { + ret = efi_create_simple_file_system(desc, part, diskobj->dp, + &diskobj->volume); + if (ret != EFI_SUCCESS) + goto error; + + ret = efi_add_protocol(&diskobj->header, + &efi_simple_file_system_protocol_guid, + diskobj->volume); + if (ret != EFI_SUCCESS) + goto error; + } + diskobj->ops = block_io_disk_template; + + /* Fill in EFI IO Media info (for read/write callbacks) */ + diskobj->media.removable_media = desc->removable; + diskobj->media.media_present = 1; + /* + * MediaID is just an arbitrary counter. + * We have to change it if the medium is removed or changed. + */ + diskobj->media.media_id = 1; + diskobj->media.block_size = desc->blksz; + diskobj->media.io_align = desc->blksz; + if (part) + diskobj->media.logical_partition = 1; + diskobj->ops.media = &diskobj->media; + if (disk) + *disk = diskobj; + + EFI_PRINT("BlockIO: part %u, present %d, logical %d, removable %d" + ", last_block %llu\n", + part, + diskobj->media.media_present, + diskobj->media.logical_partition, + diskobj->media.removable_media, + diskobj->media.last_block); + + /* Store first EFI system partition */ + if (part && efi_system_partition.uclass_id == UCLASS_INVALID) { + if (part_info && + part_info->bootable & PART_EFI_SYSTEM_PARTITION) { + efi_system_partition.uclass_id = desc->uclass_id; + efi_system_partition.devnum = desc->devnum; + efi_system_partition.part = part; + EFI_PRINT("EFI system partition: %s %x:%x\n", + blk_get_uclass_name(desc->uclass_id), + desc->devnum, part); + } + } + return EFI_SUCCESS; +error: + efi_disk_free_diskobj(diskobj); + return ret; +} + +/** + * efi_disk_create_raw() - create a handle for a whole raw disk + * + * @dev: udevice (UCLASS_BLK) + * @agent_handle: handle of the EFI block driver + * + * Create an efi_disk object which is associated with @dev. + * The type of @dev must be UCLASS_BLK. + * + * Return: 0 on success, -1 otherwise + */ +static int efi_disk_create_raw(struct udevice *dev, efi_handle_t agent_handle) +{ + struct efi_disk_obj *disk; + struct blk_desc *desc; + efi_status_t ret; + + desc = dev_get_uclass_plat(dev); + + ret = efi_disk_add_dev(NULL, NULL, desc, + NULL, 0, &disk, agent_handle); + if (ret != EFI_SUCCESS) { + if (ret == EFI_NOT_READY) { + log_notice("Disk %s not ready\n", dev->name); + ret = -EBUSY; + } else { + log_err("Adding block device %s failed, r = %lu\n", + dev->name, ret & ~EFI_ERROR_MASK); + ret = -ENOENT; + } + + return ret; + } + if (efi_link_dev(&disk->header, dev)) { + efi_disk_free_diskobj(disk); + + return -EINVAL; + } + + return 0; +} + +/** + * efi_disk_create_part() - create a handle for a disk partition + * + * @dev: udevice (UCLASS_PARTITION) + * @agent_handle: handle of the EFI block driver + * + * Create an efi_disk object which is associated with @dev. + * The type of @dev must be UCLASS_PARTITION. + * + * Return: 0 on success, -1 otherwise + */ +static int efi_disk_create_part(struct udevice *dev, efi_handle_t agent_handle) +{ + efi_handle_t parent; + struct blk_desc *desc; + struct disk_part *part_data; + struct disk_partition *info; + unsigned int part; + struct efi_handler *handler; + struct efi_device_path *dp_parent; + struct efi_disk_obj *disk; + efi_status_t ret; + + if (dev_tag_get_ptr(dev_get_parent(dev), DM_TAG_EFI, (void **)&parent)) + return -1; + + desc = dev_get_uclass_plat(dev_get_parent(dev)); + + part_data = dev_get_uclass_plat(dev); + part = part_data->partnum; + info = &part_data->gpt_part_info; + + ret = efi_search_protocol(parent, &efi_guid_device_path, &handler); + if (ret != EFI_SUCCESS) + return -1; + dp_parent = (struct efi_device_path *)handler->protocol_interface; + + ret = efi_disk_add_dev(parent, dp_parent, desc, + info, part, &disk, agent_handle); + if (ret != EFI_SUCCESS) { + log_err("Adding partition for %s failed\n", dev->name); + return -1; + } + if (efi_link_dev(&disk->header, dev)) { + efi_disk_free_diskobj(disk); + + /* TODO: closing the parent EFI_BLOCK_IO_PROTOCOL is missing. */ + + return -1; + } + + return 0; +} + +/** + * efi_disk_probe() - create efi_disk objects for a block device + * + * @ctx: event context - driver binding protocol + * @event: EV_PM_POST_PROBE event + * + * Create efi_disk objects for partitions as well as a raw disk + * which is associated with @dev. + * The type of @dev must be UCLASS_BLK. + * This function is expected to be called at EV_PM_POST_PROBE. + * + * Return: 0 on success, -1 otherwise + */ +int efi_disk_probe(void *ctx, struct event *event) +{ + struct udevice *dev; + enum uclass_id id; + struct blk_desc *desc; + struct udevice *child; + struct efi_driver_binding_extended_protocol *db_prot = ctx; + efi_handle_t agent_handle = db_prot->bp.driver_binding_handle; + int ret; + + dev = event->data.dm.dev; + id = device_get_uclass_id(dev); + + /* We won't support partitions in a partition */ + if (id != UCLASS_BLK) + return 0; + + /* + * Avoid creating duplicated objects now that efi_driver + * has already created an efi_disk at this moment. + */ + desc = dev_get_uclass_plat(dev); + if (desc->uclass_id != UCLASS_EFI_LOADER) { + ret = efi_disk_create_raw(dev, agent_handle); + if (ret) + return -1; + } + + device_foreach_child(child, dev) { + ret = efi_disk_create_part(child, agent_handle); + if (ret) + return -1; + } + + /* only do the boot option management when UEFI sub-system is initialized */ + if (IS_ENABLED(CONFIG_CMD_BOOTEFI_BOOTMGR) && efi_obj_list_initialized == EFI_SUCCESS) { + ret = efi_bootmgr_update_media_device_boot_option(); + if (ret != EFI_SUCCESS) + return -1; + } + + return 0; +} + +/** + * efi_disk_remove - delete an efi_disk object for a block device or partition + * + * @ctx: event context: driver binding protocol + * @event: EV_PM_PRE_REMOVE event + * + * Delete an efi_disk object which is associated with the UCLASS_BLK or + * UCLASS_PARTITION device for which the EV_PM_PRE_REMOVE event is raised. + * + * Return: 0 on success, -1 otherwise + */ +int efi_disk_remove(void *ctx, struct event *event) +{ + enum uclass_id id; + struct udevice *dev = event->data.dm.dev; + efi_handle_t handle; + struct blk_desc *desc; + struct efi_device_path *dp = NULL; + struct efi_disk_obj *diskobj = NULL; + struct efi_simple_file_system_protocol *volume = NULL; + efi_status_t ret; + + if (dev_tag_get_ptr(dev, DM_TAG_EFI, (void **)&handle)) + return 0; + + id = device_get_uclass_id(dev); + switch (id) { + case UCLASS_BLK: + desc = dev_get_uclass_plat(dev); + if (desc && desc->uclass_id == UCLASS_EFI_LOADER) + /* + * EFI application/driver manages the EFI handle, + * no need to delete EFI handle. + */ + return 0; + + diskobj = (struct efi_disk_obj *)handle; + break; + case UCLASS_PARTITION: + diskobj = (struct efi_disk_obj *)handle; + + /* TODO: closing the parent EFI_BLOCK_IO_PROTOCOL is missing. */ + + break; + default: + return 0; + } + + dp = diskobj->dp; + volume = diskobj->volume; + + ret = efi_delete_handle(handle); + /* Do not delete DM device if there are still EFI drivers attached. */ + if (ret != EFI_SUCCESS) + return -1; + + efi_free_pool(dp); + free(volume); + dev_tag_del(dev, DM_TAG_EFI); + + return 0; + + /* + * TODO A flag to distinguish below 2 different scenarios of this + * function call is needed: + * a) Unplugging of a removable media under U-Boot + * b) U-Boot exiting and booting an OS + * In case of scenario a), efi_bootmgr_update_media_device_boot_option() + * needs to be invoked here to update the boot options and remove the + * unnecessary ones. + */ + +} + +/** + * efi_disk_get_device_name() - get U-Boot device name associated with EFI handle + * + * @handle: pointer to the EFI handle + * @buf: pointer to the buffer to store the string + * @size: size of buffer + * Return: status code + */ +efi_status_t efi_disk_get_device_name(const efi_handle_t handle, char *buf, int size) +{ + int count; + int diskid; + enum uclass_id id; + unsigned int part; + struct udevice *dev; + struct blk_desc *desc; + const char *if_typename; + bool is_partition = false; + struct disk_part *part_data; + + if (!handle || !buf || !size) + return EFI_INVALID_PARAMETER; + + dev = handle->dev; + id = device_get_uclass_id(dev); + if (id == UCLASS_BLK) { + desc = dev_get_uclass_plat(dev); + } else if (id == UCLASS_PARTITION) { + desc = dev_get_uclass_plat(dev_get_parent(dev)); + is_partition = true; + } else { + return EFI_INVALID_PARAMETER; + } + if_typename = blk_get_uclass_name(desc->uclass_id); + diskid = desc->devnum; + + if (is_partition) { + part_data = dev_get_uclass_plat(dev); + part = part_data->partnum; + count = snprintf(buf, size, "%s %d:%u", if_typename, diskid, + part); + } else { + count = snprintf(buf, size, "%s %d", if_typename, diskid); + } + + if (count < 0 || (count + 1) > size) + return EFI_INVALID_PARAMETER; + + return EFI_SUCCESS; +} + +/** + * efi_disks_register() - ensure all block devices are available in UEFI + * + * The function probes all block devices. As we store UEFI variables on the + * EFI system partition this function has to be called before enabling + * variable services. + */ +efi_status_t efi_disks_register(void) +{ + struct udevice *dev; + + uclass_foreach_dev_probe(UCLASS_BLK, dev) { + } + + return EFI_SUCCESS; +} diff --git a/lib/efi_loader/efi_dt_fixup.c b/lib/efi_loader/efi_dt_fixup.c new file mode 100644 index 00000000000..26928cfc454 --- /dev/null +++ b/lib/efi_loader/efi_dt_fixup.c @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI_DT_FIXUP_PROTOCOL + * + * Copyright (c) 2020 Heinrich Schuchardt + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <efi_dt_fixup.h> +#include <efi_loader.h> +#include <efi_rng.h> +#include <fdtdec.h> +#include <mapmem.h> + +const efi_guid_t efi_guid_dt_fixup_protocol = EFI_DT_FIXUP_PROTOCOL_GUID; + +/** + * efi_reserve_memory() - add reserved memory to memory map + * + * @addr: start address of the reserved memory range + * @size: size of the reserved memory range + * @nomap: indicates that the memory range shall not be accessed by the + * UEFI payload + */ +static void efi_reserve_memory(u64 addr, u64 size, bool nomap) +{ + int type; + efi_uintn_t ret; + + /* Convert from sandbox address space. */ + addr = (uintptr_t)map_sysmem(addr, 0); + + if (nomap) + type = EFI_RESERVED_MEMORY_TYPE; + else + type = EFI_BOOT_SERVICES_DATA; + + ret = efi_add_memory_map(addr, size, type); + if (ret != EFI_SUCCESS) + log_err("Reserved memory mapping failed addr %llx size %llx\n", + addr, size); +} + +/** + * efi_try_purge_rng_seed() - Remove unused kaslr-seed, rng-seed + * + * Kernel's EFI STUB only relies on EFI_RNG_PROTOCOL for randomization + * and completely ignores the kaslr-seed for its own randomness needs + * (i.e the randomization of the physical placement of the kernel). + * Weed it out from the DTB we hand over, which would mess up our DTB + * TPM measurements as well. + * + * @fdt: Pointer to device tree + */ +void efi_try_purge_rng_seed(void *fdt) +{ + const char * const prop[] = {"kaslr-seed", "rng-seed"}; + const efi_guid_t efi_guid_rng_protocol = EFI_RNG_PROTOCOL_GUID; + struct efi_handler *handler; + efi_status_t ret; + int nodeoff = 0; + int err = 0; + + ret = efi_search_protocol(efi_root, &efi_guid_rng_protocol, &handler); + if (ret != EFI_SUCCESS) + return; + + nodeoff = fdt_path_offset(fdt, "/chosen"); + if (nodeoff < 0) + return; + + for (size_t i = 0; i < ARRAY_SIZE(prop); ++i) { + err = fdt_delprop(fdt, nodeoff, prop[i]); + if (err < 0 && err != -FDT_ERR_NOTFOUND) + log_err("Error deleting %s\n", prop[i]); + else + log_debug("Deleted /chosen/%s\n", prop[i]); + } +} + +/** + * efi_carve_out_dt_rsv() - Carve out DT reserved memory ranges + * + * The mem_rsv entries of the FDT are added to the memory map. Any failures are + * ignored because this is not critical and we would rather continue to try to + * boot. + * + * @fdt: Pointer to device tree + */ +void efi_carve_out_dt_rsv(void *fdt) +{ + int nr_rsv, i; + u64 addr, size; + int nodeoffset, subnode; + + nr_rsv = fdt_num_mem_rsv(fdt); + + /* Look for an existing entry and add it to the efi mem map. */ + for (i = 0; i < nr_rsv; i++) { + if (fdt_get_mem_rsv(fdt, i, &addr, &size) != 0) + continue; + efi_reserve_memory(addr, size, true); + } + + /* process reserved-memory */ + nodeoffset = fdt_subnode_offset(fdt, 0, "reserved-memory"); + if (nodeoffset >= 0) { + subnode = fdt_first_subnode(fdt, nodeoffset); + while (subnode >= 0) { + fdt_addr_t fdt_addr; + fdt_size_t fdt_size; + + /* check if this subnode has a reg property */ + fdt_addr = fdtdec_get_addr_size_auto_parent( + fdt, nodeoffset, subnode, + "reg", 0, &fdt_size, false); + /* + * The /reserved-memory node may have children with + * a size instead of a reg property. + */ + if (fdt_addr != FDT_ADDR_T_NONE && + fdtdec_get_is_enabled(fdt, subnode)) { + bool nomap; + + nomap = !!fdt_getprop(fdt, subnode, "no-map", + NULL); + efi_reserve_memory(fdt_addr, fdt_size, nomap); + } + subnode = fdt_next_subnode(fdt, subnode); + } + } +} + +/** + * efi_dt_fixup() - fix up device tree + * + * This function implements the Fixup() service of the + * EFI Device Tree Fixup Protocol. + * + * @this: instance of the protocol + * @dtb: device tree provided by caller + * @buffer_size: size of buffer for the device tree including free space + * @flags: bit field designating action to be performed + * Return: status code + */ +static efi_status_t __maybe_unused EFIAPI +efi_dt_fixup(struct efi_dt_fixup_protocol *this, void *dtb, + efi_uintn_t *buffer_size, u32 flags) +{ + efi_status_t ret; + size_t required_size; + size_t total_size; + struct bootm_headers img = { 0 }; + + EFI_ENTRY("%p, %p, %p, %d", this, dtb, buffer_size, flags); + + if (this != &efi_dt_fixup_prot || !dtb || !buffer_size || + !flags || (flags & ~EFI_DT_ALL)) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + if (fdt_check_header(dtb)) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + if (flags & EFI_DT_APPLY_FIXUPS) { + /* Check size */ + required_size = fdt_off_dt_strings(dtb) + + fdt_size_dt_strings(dtb) + + 0x3000; + total_size = fdt_totalsize(dtb); + if (required_size < total_size) + required_size = total_size; + if (required_size > *buffer_size) { + *buffer_size = required_size; + ret = EFI_BUFFER_TOO_SMALL; + goto out; + } + + fdt_set_totalsize(dtb, *buffer_size); + if (image_setup_libfdt(&img, dtb, false)) { + log_err("failed to process device tree\n"); + ret = EFI_INVALID_PARAMETER; + goto out; + } + } + if (flags & EFI_DT_RESERVE_MEMORY) + efi_carve_out_dt_rsv(dtb); + + if (flags & EFI_DT_INSTALL_TABLE) { + ret = efi_install_configuration_table(&efi_guid_fdt, dtb); + if (ret != EFI_SUCCESS) { + log_err("failed to install device tree\n"); + goto out; + } + } + + ret = EFI_SUCCESS; +out: + return EFI_EXIT(ret); +} + +struct efi_dt_fixup_protocol efi_dt_fixup_prot = { + .revision = EFI_DT_FIXUP_PROTOCOL_REVISION, + .fixup = efi_dt_fixup +}; diff --git a/lib/efi_loader/efi_esrt.c b/lib/efi_loader/efi_esrt.c new file mode 100644 index 00000000000..e235c8fe91c --- /dev/null +++ b/lib/efi_loader/efi_esrt.c @@ -0,0 +1,517 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * EFI application ESRT tables support + * + * Copyright (C) 2021 Arm Ltd. + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <efi_loader.h> +#include <log.h> +#include <efi_api.h> +#include <malloc.h> + +const efi_guid_t efi_esrt_guid = EFI_SYSTEM_RESOURCE_TABLE_GUID; + +static struct efi_system_resource_table *esrt; + +#define EFI_ESRT_VERSION 1 + +/** + * efi_esrt_image_info_to_entry() - copy the information present in a fw image + * descriptor to a ESRT entry. + * The function ensures the ESRT entry matches the image_type_id in @img_info. + * In case of a mismatch we leave the entry unchanged. + * + * @img_info: the source image info descriptor + * @entry: pointer to the ESRT entry to be filled + * @desc_version: the version of the elements in img_info + * @image_type: the image type value to be set in the ESRT entry + * @flags: the capsule flags value to be set in the ESRT entry + * + * Return: + * - EFI_SUCCESS if the entry is correctly updated + * - EFI_INVALID_PARAMETER if entry does not match image_type_id in @img_info. + */ +static efi_status_t +efi_esrt_image_info_to_entry(struct efi_firmware_image_descriptor *img_info, + struct efi_system_resource_entry *entry, + u32 desc_version, u32 image_type, u32 flags) +{ + if (guidcmp(&entry->fw_class, &img_info->image_type_id)) { + EFI_PRINT("ESRT entry %pUL mismatches img_type_id %pUL\n", + &entry->fw_class, &img_info->image_type_id); + return EFI_INVALID_PARAMETER; + } + + entry->fw_version = img_info->version; + + entry->fw_type = image_type; + entry->capsule_flags = flags; + + /* + * The field lowest_supported_image_version is only present + * on image info structure of version 2 or greater. + * See the EFI_FIRMWARE_IMAGE_DESCRIPTOR definition in UEFI. + */ + if (desc_version >= 2) + entry->lowest_supported_fw_version = + img_info->lowest_supported_image_version; + else + entry->lowest_supported_fw_version = 0; + + /* + * The fields last_attempt_version and last_attempt_status + * are only present on image info structure of version 3 or + * greater. + * See the EFI_FIRMWARE_IMAGE_DESCRIPTOR definition in UEFI. + */ + if (desc_version >= 3) { + entry->last_attempt_version = + img_info->last_attempt_version; + + entry->last_attempt_status = + img_info->last_attempt_status; + } else { + entry->last_attempt_version = 0; + entry->last_attempt_status = LAST_ATTEMPT_STATUS_SUCCESS; + } + + return EFI_SUCCESS; +} + +/** + * efi_esrt_entries_to_size() - Obtain the bytes used by an ESRT + * datastructure with @num_entries. + * + * @num_entries: the number of entries in the ESRT. + * + * Return: the number of bytes an ESRT with @num_entries occupies in memory. + */ +static +inline u32 efi_esrt_entries_to_size(u32 num_entries) +{ + u32 esrt_size = sizeof(struct efi_system_resource_table) + + num_entries * sizeof(struct efi_system_resource_entry); + + return esrt_size; +} + +/** + * efi_esrt_allocate_install() - Allocates @num_entries for the ESRT and + * performs basic ESRT initialization. + * + * @num_entries: the number of entries that the ESRT will hold. + * + * Return: + * - pointer to the ESRT if successful. + * - NULL otherwise. + */ +static +efi_status_t efi_esrt_allocate_install(u32 num_entries) +{ + efi_status_t ret; + struct efi_system_resource_table *new_esrt; + u32 size = efi_esrt_entries_to_size(num_entries); + efi_guid_t esrt_guid = efi_esrt_guid; + + /* Reserve memory for ESRT */ + ret = efi_allocate_pool(EFI_BOOT_SERVICES_DATA, size, + (void **)&new_esrt); + + if (ret != EFI_SUCCESS) { + EFI_PRINT("ESRT cannot allocate memory for %u entries (%u bytes)\n", + num_entries, size); + + return ret; + } + + new_esrt->fw_resource_count_max = num_entries; + new_esrt->fw_resource_count = 0; + new_esrt->fw_resource_version = EFI_ESRT_VERSION; + + /* Install the ESRT in the system configuration table. */ + ret = efi_install_configuration_table(&esrt_guid, (void *)new_esrt); + if (ret != EFI_SUCCESS) { + EFI_PRINT("ESRT failed to install the ESRT in the system table\n"); + return ret; + } + + /* If there was a previous ESRT, deallocate its memory now. */ + if (esrt) + ret = efi_free_pool(esrt); + + esrt = new_esrt; + + return EFI_SUCCESS; +} + +/** + * esrt_find_entry() - Obtain the ESRT entry for the image with GUID + * @img_fw_class. + * + * If the img_fw_class is not yet present in the ESRT, this function + * reserves the tail element of the current ESRT as the entry for that fw_class. + * The number of elements in the ESRT is updated in that case. + * + * @img_fw_class: the GUID of the FW image which ESRT entry we want to obtain. + * + * Return: + * - A pointer to the ESRT entry for the image with GUID img_fw_class, + * - NULL if: + * - there is no more space in the ESRT, + * - ESRT is not initialized, + */ +static +struct efi_system_resource_entry *esrt_find_entry(efi_guid_t *img_fw_class) +{ + u32 filled_entries; + u32 max_entries; + struct efi_system_resource_entry *entry; + + if (!esrt) { + EFI_PRINT("ESRT access before initialized\n"); + return NULL; + } + + filled_entries = esrt->fw_resource_count; + entry = esrt->entries; + + /* Check if the image with img_fw_class is already in the ESRT. */ + for (u32 idx = 0; idx < filled_entries; idx++) { + if (!guidcmp(&entry[idx].fw_class, img_fw_class)) { + EFI_PRINT("ESRT found entry for image %pUs at index %u\n", + img_fw_class, idx); + return &entry[idx]; + } + } + + max_entries = esrt->fw_resource_count_max; + /* + * Since the image with img_fw_class is not present in the ESRT, check + * if ESRT is full before appending the new entry to it. + */ + if (filled_entries == max_entries) { + EFI_PRINT("ESRT full, this should not happen\n"); + return NULL; + } + + /* + * This is a new entry for a fw image, increment the element + * number in the table and set the fw_class field. + */ + esrt->fw_resource_count++; + entry[filled_entries].fw_class = *img_fw_class; + EFI_PRINT("ESRT allocated new entry for image %pUs at index %u\n", + img_fw_class, filled_entries); + + return &entry[filled_entries]; +} + +/** + * efi_esrt_add_from_fmp() - Populates a sequence of ESRT entries from the FW + * images in the FMP. + * + * @fmp: the FMP instance from which FW images are added to the ESRT + * + * Return: + * - EFI_SUCCESS if all the FW images in the FMP are added to the ESRT + * - Error status otherwise + */ +static +efi_status_t efi_esrt_add_from_fmp(struct efi_firmware_management_protocol *fmp) +{ + struct efi_system_resource_entry *entry = NULL; + size_t info_size = 0; + struct efi_firmware_image_descriptor *img_info = NULL; + u32 desc_version; + u8 desc_count; + size_t desc_size; + u32 package_version; + u16 *package_version_name; + efi_status_t ret = EFI_SUCCESS; + + /* + * TODO: set the field image_type depending on the FW image type + * defined in a platform basis. + */ + u32 image_type = ESRT_FW_TYPE_UNKNOWN; + + /* TODO: set the capsule flags as a function of the FW image type. */ + u32 flags = 0; + + ret = EFI_CALL(fmp->get_image_info(fmp, &info_size, img_info, + &desc_version, &desc_count, + &desc_size, NULL, NULL)); + + if (ret != EFI_BUFFER_TOO_SMALL) { + /* + * An input of info_size=0 should always lead + * fmp->get_image_info to return BUFFER_TO_SMALL. + */ + EFI_PRINT("Erroneous FMP implementation\n"); + return EFI_INVALID_PARAMETER; + } + + ret = efi_allocate_pool(EFI_BOOT_SERVICES_DATA, info_size, + (void **)&img_info); + if (ret != EFI_SUCCESS) { + EFI_PRINT("ESRT failed to allocate memory for image info.\n"); + return ret; + } + + ret = EFI_CALL(fmp->get_image_info(fmp, &info_size, img_info, + &desc_version, &desc_count, + &desc_size, &package_version, + &package_version_name)); + if (ret != EFI_SUCCESS) { + EFI_PRINT("ESRT failed to obtain the FMP image info\n"); + goto out; + } + + /* + * Iterate over all the FW images in the FMP. + */ + for (u32 desc_idx = 0; desc_idx < desc_count; desc_idx++) { + struct efi_firmware_image_descriptor *cur_img_info = + (struct efi_firmware_image_descriptor *) + ((uintptr_t)img_info + desc_idx * desc_size); + + /* + * Obtain the ESRT entry for the FW image with fw_class + * equal to cur_img_info->image_type_id. + */ + entry = esrt_find_entry(&cur_img_info->image_type_id); + + if (entry) { + ret = efi_esrt_image_info_to_entry(cur_img_info, entry, + desc_version, + image_type, flags); + if (ret != EFI_SUCCESS) + EFI_PRINT("ESRT entry mismatches image_type\n"); + + } else { + EFI_PRINT("ESRT failed to add entry for %pUs\n", + &cur_img_info->image_type_id); + continue; + } + } + +out: + efi_free_pool(img_info); + return EFI_SUCCESS; +} + +/** + * efi_esrt_populate() - Populates the ESRT entries from the FMP instances + * present in the system. + * If an ESRT already exists, the old ESRT is replaced in the system table. + * The memory of the old ESRT is deallocated. + * + * Return: + * - EFI_SUCCESS if the ESRT is correctly created + * - error code otherwise. + */ +efi_status_t efi_esrt_populate(void) +{ + efi_handle_t *base_handle = NULL; + efi_handle_t *it_handle; + efi_uintn_t no_handles = 0; + struct efi_firmware_management_protocol *fmp; + efi_status_t ret; + u32 num_entries = 0; + struct efi_handler *handler; + + /* + * Obtain the number of registered FMP handles. + */ + ret = EFI_CALL(efi_locate_handle_buffer(BY_PROTOCOL, + &efi_guid_firmware_management_protocol, + NULL, &no_handles, + (efi_handle_t **)&base_handle)); + + if (ret != EFI_SUCCESS) { + EFI_PRINT("ESRT There are no FMP instances\n"); + + ret = efi_esrt_allocate_install(0); + if (ret != EFI_SUCCESS) { + EFI_PRINT("ESRT failed to create table with 0 entries\n"); + return ret; + } + return EFI_SUCCESS; + } + + EFI_PRINT("ESRT populate esrt from (%zd) available FMP handles\n", + no_handles); + + /* + * Iterate over all FMPs to determine an upper bound on the number of + * ESRT entries. + */ + it_handle = base_handle; + for (u32 idx = 0; idx < no_handles; idx++, it_handle++) { + struct efi_firmware_image_descriptor *img_info = NULL; + size_t info_size = 0; + u32 desc_version = 0; + u8 desc_count = 0; + size_t desc_size = 0; + u32 package_version; + u16 *package_version_name; + + ret = efi_search_protocol(*it_handle, + &efi_guid_firmware_management_protocol, + &handler); + + if (ret != EFI_SUCCESS) { + EFI_PRINT("ESRT Unable to find FMP handle (%u)\n", + idx); + continue; + } + fmp = handler->protocol_interface; + + ret = EFI_CALL(fmp->get_image_info(fmp, &info_size, NULL, + &desc_version, &desc_count, + &desc_size, &package_version, + &package_version_name)); + + if (ret != EFI_BUFFER_TOO_SMALL) { + /* + * An input of info_size=0 should always lead + * fmp->get_image_info to return BUFFER_TO_SMALL. + */ + EFI_PRINT("ESRT erroneous FMP implementation\n"); + continue; + } + + ret = efi_allocate_pool(EFI_BOOT_SERVICES_DATA, info_size, + (void **)&img_info); + if (ret != EFI_SUCCESS) { + EFI_PRINT("ESRT failed to allocate memory for image info\n"); + continue; + } + + /* + * Calls to a FMP get_image_info method do not return the + * desc_count value if the return status differs from EFI_SUCCESS. + * We need to repeat the call to get_image_info with a properly + * sized buffer in order to obtain the real number of images + * handled by the FMP. + */ + ret = EFI_CALL(fmp->get_image_info(fmp, &info_size, img_info, + &desc_version, &desc_count, + &desc_size, &package_version, + &package_version_name)); + + if (ret != EFI_SUCCESS) { + EFI_PRINT("ESRT failed to obtain image info from FMP\n"); + efi_free_pool(img_info); + continue; + } + + num_entries += desc_count; + + efi_free_pool(img_info); + } + + /* error occurs in fmp->get_image_info() if num_entries is 0 here */ + if (!num_entries) { + EFI_PRINT("Error occurs, num_entries should not be 0\n"); + ret = EFI_INVALID_PARAMETER; + goto out; + } + + EFI_PRINT("ESRT create table with %u entries\n", num_entries); + /* + * Allocate an ESRT with the sufficient number of entries to accommodate + * all the FMPs in the system. + */ + ret = efi_esrt_allocate_install(num_entries); + if (ret != EFI_SUCCESS) { + EFI_PRINT("ESRT failed to initialize table\n"); + goto out; + } + + /* + * Populate the ESRT entries with all existing FMP. + */ + it_handle = base_handle; + for (u32 idx = 0; idx < no_handles; idx++, it_handle++) { + ret = efi_search_protocol(*it_handle, + &efi_guid_firmware_management_protocol, + &handler); + + if (ret != EFI_SUCCESS) { + EFI_PRINT("ESRT unable to find FMP handle (%u)\n", + idx); + continue; + } + fmp = handler->protocol_interface; + + ret = efi_esrt_add_from_fmp(fmp); + if (ret != EFI_SUCCESS) + EFI_PRINT("ESRT failed to add FMP to the table\n"); + } + +out: + + efi_free_pool(base_handle); + + return ret; +} + +/** + * efi_esrt_new_fmp_notify() - Callback for the EVT_NOTIFY_SIGNAL event raised + * when a new FMP protocol instance is registered in the system. + */ +static void EFIAPI efi_esrt_new_fmp_notify(struct efi_event *event, + void *context) +{ + efi_status_t ret; + + EFI_ENTRY(); + + ret = efi_esrt_populate(); + if (ret != EFI_SUCCESS) + EFI_PRINT("ESRT failed to populate ESRT entry\n"); + + EFI_EXIT(ret); +} + +/** + * efi_esrt_register() - Install the ESRT system table. + * + * Return: status code + */ +efi_status_t efi_esrt_register(void) +{ + struct efi_event *ev = NULL; + void *registration; + efi_status_t ret; + + EFI_PRINT("ESRT creation start\n"); + + ret = efi_esrt_populate(); + if (ret != EFI_SUCCESS) { + EFI_PRINT("ESRT failed to initiate the table\n"); + return ret; + } + + ret = efi_create_event(EVT_NOTIFY_SIGNAL, TPL_CALLBACK, + efi_esrt_new_fmp_notify, NULL, NULL, &ev); + if (ret != EFI_SUCCESS) { + EFI_PRINT("ESRT failed to create event\n"); + return ret; + } + + ret = EFI_CALL(efi_register_protocol_notify(&efi_guid_firmware_management_protocol, + ev, ®istration)); + if (ret != EFI_SUCCESS) { + EFI_PRINT("ESRT failed to register FMP callback\n"); + return ret; + } + + EFI_PRINT("ESRT table created\n"); + + return ret; +} diff --git a/lib/efi_loader/efi_fdt.c b/lib/efi_loader/efi_fdt.c new file mode 100644 index 00000000000..1ba6641d821 --- /dev/null +++ b/lib/efi_loader/efi_fdt.c @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Bootmethod for distro boot via EFI + * + * Copyright 2021 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <efi_loader.h> +#include <env.h> +#include <errno.h> +#include <log.h> +#include <string.h> +#include <vsprintf.h> + +/** + * efi_get_distro_fdt_name() - get the filename for reading the .dtb file + * + * @fname: buffer for filename + * @size: buffer size + * @seq: sequence number, to cycle through options (0=first) + * + * Returns: + * 0 on success, + * -ENOENT if the "fdtfile" env var does not exist, + * -EINVAL if there are no more options, + * -EALREADY if the control FDT should be used + */ +int efi_get_distro_fdt_name(char *fname, int size, int seq) +{ + const char *fdt_fname; + const char *prefix; + + /* select the prefix */ + switch (seq) { + case 0: + /* this is the default */ + prefix = "/dtb"; + break; + case 1: + prefix = ""; + break; + case 2: + prefix = "/dtb/current"; + break; + case 3: + prefix = "/dtbs"; + break; + default: + return log_msg_ret("pref", -EINVAL); + } + + fdt_fname = env_get("fdtfile"); + if (fdt_fname) { + snprintf(fname, size, "%s/%s", prefix, fdt_fname); + log_debug("Using device tree: %s\n", fname); + } else if (IS_ENABLED(CONFIG_OF_HAS_PRIOR_STAGE)) { + strcpy(fname, "<prior>"); + return log_msg_ret("pref", -EALREADY); + /* Use this fallback only for 32-bit ARM */ + } else if (IS_ENABLED(CONFIG_ARM) && !IS_ENABLED(CONFIG_ARM64)) { + const char *soc = env_get("soc"); + const char *board = env_get("board"); + const char *boardver = env_get("boardver"); + + /* cf the code in label_boot() which seems very complex */ + snprintf(fname, size, "%s/%s%s%s%s.dtb", prefix, + soc ? soc : "", soc ? "-" : "", board ? board : "", + boardver ? boardver : ""); + log_debug("Using default device tree: %s\n", fname); + } else { + return log_msg_ret("env", -ENOENT); + } + + return 0; +} + +/** + * efi_load_distro_fdt() - load distro device-tree + * + * @handle: handle of loaded image + * @fdt: on return device-tree, must be freed via efi_free_pages() + * @fdt_size: buffer size + */ +void efi_load_distro_fdt(efi_handle_t handle, void **fdt, efi_uintn_t *fdt_size) +{ + struct efi_device_path *dp; + efi_status_t ret; + struct efi_handler *handler; + struct efi_loaded_image *loaded_image; + efi_handle_t device; + + *fdt = NULL; + + /* Get boot device from loaded image protocol */ + ret = efi_search_protocol(handle, &efi_guid_loaded_image, &handler); + if (ret != EFI_SUCCESS) + return; + loaded_image = handler->protocol_interface; + device = loaded_image->device_handle; + + /* Get device path of boot device */ + ret = efi_search_protocol(device, &efi_guid_device_path, &handler); + if (ret != EFI_SUCCESS) + return; + dp = handler->protocol_interface; + + /* Try the various available names */ + for (int seq = 0; ; ++seq) { + struct efi_device_path *file; + char buf[255]; + + if (efi_get_distro_fdt_name(buf, sizeof(buf), seq)) + break; + file = efi_dp_from_file(dp, buf); + if (!file) + break; + ret = efi_load_image_from_path(true, file, fdt, fdt_size); + efi_free_pool(file); + if (ret == EFI_SUCCESS) { + log_debug("Fdt %pD loaded\n", file); + break; + } + } +} diff --git a/lib/efi_loader/efi_file.c b/lib/efi_loader/efi_file.c new file mode 100644 index 00000000000..7d81da8f2d8 --- /dev/null +++ b/lib/efi_loader/efi_file.c @@ -0,0 +1,1264 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI_FILE_PROTOCOL + * + * Copyright (c) 2017 Rob Clark + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <charset.h> +#include <efi_loader.h> +#include <log.h> +#include <malloc.h> +#include <mapmem.h> +#include <fs.h> +#include <part.h> + +/* GUID for file system information */ +const efi_guid_t efi_file_system_info_guid = EFI_FILE_SYSTEM_INFO_GUID; + +/* GUID to obtain the volume label */ +const efi_guid_t efi_system_volume_label_id = EFI_FILE_SYSTEM_VOLUME_LABEL_ID; + +struct file_system { + struct efi_simple_file_system_protocol base; + struct efi_device_path *dp; + struct blk_desc *desc; + int part; +}; +#define to_fs(x) container_of(x, struct file_system, base) + +struct file_handle { + struct efi_file_handle base; + struct file_system *fs; + loff_t offset; /* current file position/cursor */ + int isdir; + u64 open_mode; + + /* for reading a directory: */ + struct fs_dir_stream *dirs; + struct fs_dirent *dent; + + char *path; +}; +#define to_fh(x) container_of(x, struct file_handle, base) + +static const struct efi_file_handle efi_file_handle_protocol; + +static char *basename(struct file_handle *fh) +{ + char *s = strrchr(fh->path, '/'); + if (s) + return s + 1; + return fh->path; +} + +static int set_blk_dev(struct file_handle *fh) +{ + return fs_set_blk_dev_with_part(fh->fs->desc, fh->fs->part); +} + +/** + * is_dir() - check if file handle points to directory + * + * We assume that set_blk_dev(fh) has been called already. + * + * @fh: file handle + * Return: true if file handle points to a directory + */ +static int is_dir(struct file_handle *fh) +{ + struct fs_dir_stream *dirs; + + dirs = fs_opendir(fh->path); + if (!dirs) + return 0; + + fs_closedir(dirs); + + return 1; +} + +/* + * Normalize a path which may include either back or fwd slashes, + * double slashes, . or .. entries in the path, etc. + */ +static int sanitize_path(char *path) +{ + char *p; + + /* backslash to slash: */ + p = path; + while ((p = strchr(p, '\\'))) + *p++ = '/'; + + /* handle double-slashes: */ + p = path; + while ((p = strstr(p, "//"))) { + char *src = p + 1; + memmove(p, src, strlen(src) + 1); + } + + /* handle extra /.'s */ + p = path; + while ((p = strstr(p, "/."))) { + /* + * You'd be tempted to do this *after* handling ".."s + * below to avoid having to check if "/." is start of + * a "/..", but that won't have the correct results.. + * for example, "/foo/./../bar" would get resolved to + * "/foo/bar" if you did these two passes in the other + * order + */ + if (p[2] == '.') { + p += 2; + continue; + } + char *src = p + 2; + memmove(p, src, strlen(src) + 1); + } + + /* handle extra /..'s: */ + p = path; + while ((p = strstr(p, "/.."))) { + char *src = p + 3; + + p--; + + /* find beginning of previous path entry: */ + while (true) { + if (p < path) + return -1; + if (*p == '/') + break; + p--; + } + + memmove(p, src, strlen(src) + 1); + } + + return 0; +} + +/** + * efi_create_file() - create file or directory + * + * @fh: file handle + * @attributes: attributes for newly created file + * Returns: 0 for success + */ +static int efi_create_file(struct file_handle *fh, u64 attributes) +{ + loff_t actwrite; + void *buffer = &actwrite; + + if (attributes & EFI_FILE_DIRECTORY) + return fs_mkdir(fh->path); + else + return fs_write(fh->path, map_to_sysmem(buffer), 0, 0, + &actwrite); +} + +/** + * file_open() - open a file handle + * + * @fs: file system + * @parent: directory relative to which the file is to be opened + * @file_name: path of the file to be opened. '\', '.', or '..' may + * be used as modifiers. A leading backslash indicates an + * absolute path. + * @open_mode: bit mask indicating the access mode (read, write, + * create) + * @attributes: attributes for newly created file + * Returns: handle to the opened file or NULL + */ +static struct efi_file_handle *file_open(struct file_system *fs, + struct file_handle *parent, u16 *file_name, u64 open_mode, + u64 attributes) +{ + struct file_handle *fh; + char *path; + char f0[MAX_UTF8_PER_UTF16] = {0}; + int plen = 0; + int flen = 0; + + if (file_name) { + utf16_to_utf8((u8 *)f0, file_name, 1); + flen = u16_strlen(file_name); + } + + /* we could have a parent, but also an absolute path: */ + if (f0[0] == '\\') { + plen = 0; + } else if (parent) { + plen = strlen(parent->path) + 1; + } + + fh = calloc(1, sizeof(*fh)); + /* +2 is for null and '/' */ + path = calloc(1, plen + (flen * MAX_UTF8_PER_UTF16) + 2); + if (!fh || !path) + goto error; + + fh->path = path; + fh->open_mode = open_mode; + fh->base = efi_file_handle_protocol; + fh->fs = fs; + + if (parent) { + char *p = fh->path; + int exists; + + if (plen > 0) { + strcpy(p, parent->path); + p += plen - 1; + *p++ = '/'; + } + + utf16_to_utf8((u8 *)p, file_name, flen); + + if (sanitize_path(fh->path)) + goto error; + + /* check if file exists: */ + if (set_blk_dev(fh)) + goto error; + + exists = fs_exists(fh->path); + /* fs_exists() calls fs_close(), so open file system again */ + if (set_blk_dev(fh)) + goto error; + + if (!exists) { + if (!(open_mode & EFI_FILE_MODE_CREATE) || + efi_create_file(fh, attributes)) + goto error; + if (set_blk_dev(fh)) + goto error; + } + + /* figure out if file is a directory: */ + fh->isdir = is_dir(fh); + } else { + fh->isdir = 1; + strcpy(fh->path, ""); + } + + return &fh->base; + +error: + free(fh->path); + free(fh); + return NULL; +} + +efi_status_t efi_file_open_int(struct efi_file_handle *this, + struct efi_file_handle **new_handle, + u16 *file_name, u64 open_mode, + u64 attributes) +{ + struct file_handle *fh = to_fh(this); + efi_status_t ret; + + /* Check parameters */ + if (!this || !new_handle || !file_name) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + if (open_mode != EFI_FILE_MODE_READ && + open_mode != (EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE) && + open_mode != (EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | + EFI_FILE_MODE_CREATE)) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + /* + * The UEFI spec requires that attributes are only set in create mode. + * The SCT does not care about this and sets EFI_FILE_DIRECTORY in + * read mode. EDK2 does not check that attributes are zero if not in + * create mode. + * + * So here we only check attributes in create mode and do not check + * that they are zero otherwise. + */ + if ((open_mode & EFI_FILE_MODE_CREATE) && + (attributes & (EFI_FILE_READ_ONLY | ~EFI_FILE_VALID_ATTR))) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + /* Open file */ + *new_handle = file_open(fh->fs, fh, file_name, open_mode, attributes); + if (*new_handle) { + EFI_PRINT("file handle %p\n", *new_handle); + ret = EFI_SUCCESS; + } else { + ret = EFI_NOT_FOUND; + } +out: + return ret; +} + +/** + * efi_file_open() - open file synchronously + * + * This function implements the Open service of the File Protocol. + * See the UEFI spec for details. + * + * @this: EFI_FILE_PROTOCOL instance + * @new_handle: on return pointer to file handle + * @file_name: file name + * @open_mode: mode to open the file (read, read/write, create/read/write) + * @attributes: attributes for newly created file + */ +static efi_status_t EFIAPI efi_file_open(struct efi_file_handle *this, + struct efi_file_handle **new_handle, + u16 *file_name, u64 open_mode, + u64 attributes) +{ + efi_status_t ret; + + EFI_ENTRY("%p, %p, \"%ls\", %llx, %llu", this, new_handle, + file_name, open_mode, attributes); + + ret = efi_file_open_int(this, new_handle, file_name, open_mode, + attributes); + + return EFI_EXIT(ret); +} + +/** + * efi_file_open_ex() - open file asynchronously + * + * This function implements the OpenEx service of the File Protocol. + * See the UEFI spec for details. + * + * @this: EFI_FILE_PROTOCOL instance + * @new_handle: on return pointer to file handle + * @file_name: file name + * @open_mode: mode to open the file (read, read/write, create/read/write) + * @attributes: attributes for newly created file + * @token: transaction token + */ +static efi_status_t EFIAPI efi_file_open_ex(struct efi_file_handle *this, + struct efi_file_handle **new_handle, + u16 *file_name, u64 open_mode, + u64 attributes, + struct efi_file_io_token *token) +{ + efi_status_t ret; + + EFI_ENTRY("%p, %p, \"%ls\", %llx, %llu, %p", this, new_handle, + file_name, open_mode, attributes, token); + + if (!token) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + ret = efi_file_open_int(this, new_handle, file_name, open_mode, + attributes); + + if (ret == EFI_SUCCESS && token->event) { + token->status = EFI_SUCCESS; + efi_signal_event(token->event); + } + +out: + return EFI_EXIT(ret); +} + +static efi_status_t file_close(struct file_handle *fh) +{ + fs_closedir(fh->dirs); + free(fh->path); + free(fh); + return EFI_SUCCESS; +} + +efi_status_t efi_file_close_int(struct efi_file_handle *file) +{ + struct file_handle *fh = to_fh(file); + + return file_close(fh); +} + +static efi_status_t EFIAPI efi_file_close(struct efi_file_handle *file) +{ + EFI_ENTRY("%p", file); + return EFI_EXIT(efi_file_close_int(file)); +} + +static efi_status_t EFIAPI efi_file_delete(struct efi_file_handle *file) +{ + struct file_handle *fh = to_fh(file); + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p", file); + + if (set_blk_dev(fh) || fs_unlink(fh->path)) + ret = EFI_WARN_DELETE_FAILURE; + + file_close(fh); + return EFI_EXIT(ret); +} + +/** + * efi_get_file_size() - determine the size of a file + * + * @fh: file handle + * @file_size: pointer to receive file size + * Return: status code + */ +static efi_status_t efi_get_file_size(struct file_handle *fh, + loff_t *file_size) +{ + if (set_blk_dev(fh)) + return EFI_DEVICE_ERROR; + + if (fs_size(fh->path, file_size)) + return EFI_DEVICE_ERROR; + + return EFI_SUCCESS; +} + +/** + * efi_file_size() - Get the size of a file using an EFI file handle + * + * @fh: EFI file handle + * @size: buffer to fill in the discovered size + * + * Return: size of the file + */ +efi_status_t efi_file_size(struct efi_file_handle *fh, efi_uintn_t *size) +{ + struct efi_file_info *info = NULL; + efi_uintn_t bs = 0; + efi_status_t ret; + + *size = 0; + ret = EFI_CALL(fh->getinfo(fh, (efi_guid_t *)&efi_file_info_guid, &bs, + info)); + if (ret != EFI_BUFFER_TOO_SMALL) { + ret = EFI_DEVICE_ERROR; + goto out; + } + + info = malloc(bs); + if (!info) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + ret = EFI_CALL(fh->getinfo(fh, (efi_guid_t *)&efi_file_info_guid, &bs, + info)); + if (ret != EFI_SUCCESS) + goto out; + + *size = info->file_size; + +out: + free(info); + return ret; +} + +static efi_status_t file_read(struct file_handle *fh, u64 *buffer_size, + void *buffer) +{ + loff_t actread; + efi_status_t ret; + loff_t file_size; + + if (!buffer) { + ret = EFI_INVALID_PARAMETER; + return ret; + } + + ret = efi_get_file_size(fh, &file_size); + if (ret != EFI_SUCCESS) + return ret; + if (file_size < fh->offset) { + ret = EFI_DEVICE_ERROR; + return ret; + } + + if (set_blk_dev(fh)) + return EFI_DEVICE_ERROR; + if (fs_read(fh->path, map_to_sysmem(buffer), fh->offset, + *buffer_size, &actread)) + return EFI_DEVICE_ERROR; + + *buffer_size = actread; + fh->offset += actread; + + return EFI_SUCCESS; +} + +static void rtc2efi(struct efi_time *time, struct rtc_time *tm) +{ + memset(time, 0, sizeof(struct efi_time)); + time->year = tm->tm_year; + time->month = tm->tm_mon; + time->day = tm->tm_mday; + time->hour = tm->tm_hour; + time->minute = tm->tm_min; + time->second = tm->tm_sec; +} + +static efi_status_t dir_read(struct file_handle *fh, u64 *buffer_size, + void *buffer) +{ + struct efi_file_info *info = buffer; + struct fs_dirent *dent; + u64 required_size; + u16 *dst; + + if (set_blk_dev(fh)) + return EFI_DEVICE_ERROR; + + if (!fh->dirs) { + assert(fh->offset == 0); + fh->dirs = fs_opendir(fh->path); + if (!fh->dirs) + return EFI_DEVICE_ERROR; + fh->dent = NULL; + } + + /* + * So this is a bit awkward. Since fs layer is stateful and we + * can't rewind an entry, in the EFI_BUFFER_TOO_SMALL case below + * we might have to return without consuming the dent.. so we + * have to stash it for next call. + */ + if (fh->dent) { + dent = fh->dent; + } else { + dent = fs_readdir(fh->dirs); + } + + if (!dent) { + /* no more files in directory */ + *buffer_size = 0; + return EFI_SUCCESS; + } + + /* check buffer size: */ + required_size = sizeof(*info) + + 2 * (utf8_utf16_strlen(dent->name) + 1); + if (*buffer_size < required_size) { + *buffer_size = required_size; + fh->dent = dent; + return EFI_BUFFER_TOO_SMALL; + } + if (!buffer) + return EFI_INVALID_PARAMETER; + fh->dent = NULL; + + *buffer_size = required_size; + memset(info, 0, required_size); + + info->size = required_size; + info->file_size = dent->size; + info->physical_size = dent->size; + info->attribute = dent->attr; + rtc2efi(&info->create_time, &dent->create_time); + rtc2efi(&info->modification_time, &dent->change_time); + rtc2efi(&info->last_access_time, &dent->access_time); + + if (dent->type == FS_DT_DIR) + info->attribute |= EFI_FILE_DIRECTORY; + + dst = info->file_name; + utf8_utf16_strcpy(&dst, dent->name); + + fh->offset++; + + return EFI_SUCCESS; +} + +efi_status_t efi_file_read_int(struct efi_file_handle *this, + efi_uintn_t *buffer_size, void *buffer) +{ + struct file_handle *fh = to_fh(this); + efi_status_t ret = EFI_SUCCESS; + u64 bs; + + if (!this || !buffer_size) + return EFI_INVALID_PARAMETER; + + bs = *buffer_size; + if (fh->isdir) + ret = dir_read(fh, &bs, buffer); + else + ret = file_read(fh, &bs, buffer); + if (bs <= SIZE_MAX) + *buffer_size = bs; + else + *buffer_size = SIZE_MAX; + + return ret; +} + +/** + * efi_file_read() - read file + * + * This function implements the Read() service of the EFI_FILE_PROTOCOL. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @this: file protocol instance + * @buffer_size: number of bytes to read + * @buffer: read buffer + * Return: status code + */ +static efi_status_t EFIAPI efi_file_read(struct efi_file_handle *this, + efi_uintn_t *buffer_size, void *buffer) +{ + efi_status_t ret; + + EFI_ENTRY("%p, %p, %p", this, buffer_size, buffer); + + ret = efi_file_read_int(this, buffer_size, buffer); + + return EFI_EXIT(ret); +} + +/** + * efi_file_read_ex() - read file asynchonously + * + * This function implements the ReadEx() service of the EFI_FILE_PROTOCOL. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @this: file protocol instance + * @token: transaction token + * Return: status code + */ +static efi_status_t EFIAPI efi_file_read_ex(struct efi_file_handle *this, + struct efi_file_io_token *token) +{ + efi_status_t ret; + + EFI_ENTRY("%p, %p", this, token); + + if (!token) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + ret = efi_file_read_int(this, &token->buffer_size, token->buffer); + + if (ret == EFI_SUCCESS && token->event) { + token->status = EFI_SUCCESS; + efi_signal_event(token->event); + } + +out: + return EFI_EXIT(ret); +} + +static efi_status_t efi_file_write_int(struct efi_file_handle *this, + efi_uintn_t *buffer_size, void *buffer) +{ + struct file_handle *fh = to_fh(this); + efi_status_t ret = EFI_SUCCESS; + loff_t actwrite; + + if (!this || !buffer_size || !buffer) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + if (fh->isdir) { + ret = EFI_UNSUPPORTED; + goto out; + } + if (!(fh->open_mode & EFI_FILE_MODE_WRITE)) { + ret = EFI_ACCESS_DENIED; + goto out; + } + + if (!*buffer_size) + goto out; + + if (set_blk_dev(fh)) { + ret = EFI_DEVICE_ERROR; + goto out; + } + if (fs_write(fh->path, map_to_sysmem(buffer), fh->offset, *buffer_size, + &actwrite)) { + ret = EFI_DEVICE_ERROR; + goto out; + } + *buffer_size = actwrite; + fh->offset += actwrite; + +out: + return ret; +} + +/** + * efi_file_write() - write to file + * + * This function implements the Write() service of the EFI_FILE_PROTOCOL. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @this: file protocol instance + * @buffer_size: number of bytes to write + * @buffer: buffer with the bytes to write + * Return: status code + */ +static efi_status_t EFIAPI efi_file_write(struct efi_file_handle *this, + efi_uintn_t *buffer_size, + void *buffer) +{ + efi_status_t ret; + + EFI_ENTRY("%p, %p, %p", this, buffer_size, buffer); + + ret = efi_file_write_int(this, buffer_size, buffer); + + return EFI_EXIT(ret); +} + +/** + * efi_file_write_ex() - write to file + * + * This function implements the WriteEx() service of the EFI_FILE_PROTOCOL. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @this: file protocol instance + * @token: transaction token + * Return: status code + */ +static efi_status_t EFIAPI efi_file_write_ex(struct efi_file_handle *this, + struct efi_file_io_token *token) +{ + efi_status_t ret; + + EFI_ENTRY("%p, %p", this, token); + + if (!token) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + ret = efi_file_write_int(this, &token->buffer_size, token->buffer); + + if (ret == EFI_SUCCESS && token->event) { + token->status = EFI_SUCCESS; + efi_signal_event(token->event); + } + +out: + return EFI_EXIT(ret); +} + +/** + * efi_file_getpos() - get current position in file + * + * This function implements the GetPosition service of the EFI file protocol. + * See the UEFI spec for details. + * + * @file: file handle + * @pos: pointer to file position + * Return: status code + */ +static efi_status_t EFIAPI efi_file_getpos(struct efi_file_handle *file, + u64 *pos) +{ + efi_status_t ret = EFI_SUCCESS; + struct file_handle *fh = to_fh(file); + + EFI_ENTRY("%p, %p", file, pos); + + if (fh->isdir) { + ret = EFI_UNSUPPORTED; + goto out; + } + + *pos = fh->offset; +out: + return EFI_EXIT(ret); +} + +efi_status_t efi_file_setpos_int(struct efi_file_handle *file, u64 pos) +{ + struct file_handle *fh = to_fh(file); + efi_status_t ret = EFI_SUCCESS; + + if (fh->isdir) { + if (pos != 0) { + ret = EFI_UNSUPPORTED; + goto error; + } + fs_closedir(fh->dirs); + fh->dirs = NULL; + } + + if (pos == ~0ULL) { + loff_t file_size; + + ret = efi_get_file_size(fh, &file_size); + if (ret != EFI_SUCCESS) + goto error; + pos = file_size; + } + + fh->offset = pos; + +error: + return ret; +} + +/** + * efi_file_setpos() - set current position in file + * + * This function implements the SetPosition service of the EFI file protocol. + * See the UEFI spec for details. + * + * @file: file handle + * @pos: new file position + * Return: status code + */ +static efi_status_t EFIAPI efi_file_setpos(struct efi_file_handle *file, + u64 pos) +{ + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p, %llu", file, pos); + + ret = efi_file_setpos_int(file, pos); + + return EFI_EXIT(ret); +} + +static efi_status_t EFIAPI efi_file_getinfo(struct efi_file_handle *file, + const efi_guid_t *info_type, + efi_uintn_t *buffer_size, + void *buffer) +{ + struct file_handle *fh = to_fh(file); + efi_status_t ret = EFI_SUCCESS; + u16 *dst; + + EFI_ENTRY("%p, %pUs, %p, %p", file, info_type, buffer_size, buffer); + + if (!file || !info_type || !buffer_size || + (*buffer_size && !buffer)) { + ret = EFI_INVALID_PARAMETER; + goto error; + } + + if (!guidcmp(info_type, &efi_file_info_guid)) { + struct efi_file_info *info = buffer; + char *filename = basename(fh); + unsigned int required_size; + loff_t file_size; + + /* check buffer size: */ + required_size = sizeof(*info) + + 2 * (utf8_utf16_strlen(filename) + 1); + if (*buffer_size < required_size) { + *buffer_size = required_size; + ret = EFI_BUFFER_TOO_SMALL; + goto error; + } + + ret = efi_get_file_size(fh, &file_size); + if (ret != EFI_SUCCESS) { + if (!fh->isdir) + goto error; + /* + * Some file drivers don't implement fs_size() for + * directories. Use a dummy non-zero value. + */ + file_size = 4096; + ret = EFI_SUCCESS; + } + + memset(info, 0, required_size); + + info->size = required_size; + info->file_size = file_size; + info->physical_size = file_size; + + if (fh->isdir) + info->attribute |= EFI_FILE_DIRECTORY; + + dst = info->file_name; + utf8_utf16_strcpy(&dst, filename); + } else if (!guidcmp(info_type, &efi_file_system_info_guid)) { + struct efi_file_system_info *info = buffer; + struct disk_partition part; + efi_uintn_t required_size; + int r; + + if (fh->fs->part >= 1) + r = part_get_info(fh->fs->desc, fh->fs->part, &part); + else + r = part_get_info_whole_disk(fh->fs->desc, &part); + if (r < 0) { + ret = EFI_DEVICE_ERROR; + goto error; + } + required_size = sizeof(*info) + 2; + if (*buffer_size < required_size) { + *buffer_size = required_size; + ret = EFI_BUFFER_TOO_SMALL; + goto error; + } + + memset(info, 0, required_size); + + info->size = required_size; + /* + * TODO: We cannot determine if the volume can be written to. + */ + info->read_only = false; + info->volume_size = part.size * part.blksz; + /* + * TODO: We currently have no function to determine the free + * space. The volume size is the best upper bound we have. + */ + info->free_space = info->volume_size; + info->block_size = part.blksz; + /* + * TODO: The volume label is not available in U-Boot. + */ + info->volume_label[0] = 0; + } else if (!guidcmp(info_type, &efi_system_volume_label_id)) { + if (*buffer_size < 2) { + *buffer_size = 2; + ret = EFI_BUFFER_TOO_SMALL; + goto error; + } + *(u16 *)buffer = 0; + } else { + ret = EFI_UNSUPPORTED; + } + +error: + return EFI_EXIT(ret); +} + +static efi_status_t EFIAPI efi_file_setinfo(struct efi_file_handle *file, + const efi_guid_t *info_type, + efi_uintn_t buffer_size, + void *buffer) +{ + struct file_handle *fh = to_fh(file); + efi_status_t ret = EFI_UNSUPPORTED; + char *new_file_name = NULL, *new_path = NULL; + + EFI_ENTRY("%p, %pUs, %zu, %p", file, info_type, buffer_size, buffer); + + if (!guidcmp(info_type, &efi_file_info_guid)) { + struct efi_file_info *info = (struct efi_file_info *)buffer; + char *filename = basename(fh); + char *new_file_name, *pos; + loff_t file_size; + + /* The buffer will always contain a file name. */ + if (buffer_size < sizeof(struct efi_file_info) + 2 || + buffer_size < info->size) { + ret = EFI_BAD_BUFFER_SIZE; + goto out; + } + /* We cannot change the directory attribute */ + if (!fh->isdir != !(info->attribute & EFI_FILE_DIRECTORY)) { + ret = EFI_ACCESS_DENIED; + goto out; + } + /* Check for renaming */ + new_file_name = malloc(utf16_utf8_strlen(info->file_name) + 1); + if (!new_file_name) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + pos = new_file_name; + utf16_utf8_strcpy(&pos, info->file_name); + if (strcmp(new_file_name, filename)) { + int dlen; + int rv; + + if (set_blk_dev(fh)) { + ret = EFI_DEVICE_ERROR; + goto out; + } + dlen = filename - fh->path; + new_path = calloc(1, dlen + strlen(new_file_name) + 1); + if (!new_path) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + memcpy(new_path, fh->path, dlen); + strcpy(new_path + dlen, new_file_name); + sanitize_path(new_path); + rv = fs_exists(new_path); + if (rv) { + ret = EFI_ACCESS_DENIED; + goto out; + } + /* fs_exists() calls fs_close(), so open file system again */ + if (set_blk_dev(fh)) { + ret = EFI_DEVICE_ERROR; + goto out; + } + rv = fs_rename(fh->path, new_path); + if (rv) { + ret = EFI_ACCESS_DENIED; + goto out; + } + free(fh->path); + fh->path = new_path; + /* Prevent new_path from being freed on out */ + new_path = NULL; + ret = EFI_SUCCESS; + } + /* Check for truncation */ + if (!fh->isdir) { + ret = efi_get_file_size(fh, &file_size); + if (ret != EFI_SUCCESS) + goto out; + if (file_size != info->file_size) { + /* TODO: we do not support truncation */ + EFI_PRINT("Truncation not supported\n"); + ret = EFI_ACCESS_DENIED; + goto out; + } + } + /* + * We do not care for the other attributes + * TODO: Support read only + */ + ret = EFI_SUCCESS; + } else { + /* TODO: We do not support changing the volume label */ + ret = EFI_UNSUPPORTED; + } +out: + free(new_path); + free(new_file_name); + return EFI_EXIT(ret); +} + +/** + * efi_file_flush_int() - flush file + * + * This is the internal implementation of the Flush() and FlushEx() services of + * the EFI_FILE_PROTOCOL. + * + * @this: file protocol instance + * Return: status code + */ +static efi_status_t efi_file_flush_int(struct efi_file_handle *this) +{ + struct file_handle *fh = to_fh(this); + + if (!this) + return EFI_INVALID_PARAMETER; + + if (!(fh->open_mode & EFI_FILE_MODE_WRITE)) + return EFI_ACCESS_DENIED; + + /* TODO: flush for file position after end of file */ + return EFI_SUCCESS; +} + +/** + * efi_file_flush() - flush file + * + * This function implements the Flush() service of the EFI_FILE_PROTOCOL. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @this: file protocol instance + * Return: status code + */ +static efi_status_t EFIAPI efi_file_flush(struct efi_file_handle *this) +{ + efi_status_t ret; + + EFI_ENTRY("%p", this); + + ret = efi_file_flush_int(this); + + return EFI_EXIT(ret); +} + +/** + * efi_file_flush_ex() - flush file + * + * This function implements the FlushEx() service of the EFI_FILE_PROTOCOL. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @this: file protocol instance + * @token: transaction token + * Return: status code + */ +static efi_status_t EFIAPI efi_file_flush_ex(struct efi_file_handle *this, + struct efi_file_io_token *token) +{ + efi_status_t ret; + + EFI_ENTRY("%p, %p", this, token); + + if (!token) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + ret = efi_file_flush_int(this); + + if (ret == EFI_SUCCESS && token->event) { + token->status = EFI_SUCCESS; + efi_signal_event(token->event); + } + +out: + return EFI_EXIT(ret); +} + +static const struct efi_file_handle efi_file_handle_protocol = { + .rev = EFI_FILE_PROTOCOL_REVISION2, + .open = efi_file_open, + .close = efi_file_close, + .delete = efi_file_delete, + .read = efi_file_read, + .write = efi_file_write, + .getpos = efi_file_getpos, + .setpos = efi_file_setpos, + .getinfo = efi_file_getinfo, + .setinfo = efi_file_setinfo, + .flush = efi_file_flush, + .open_ex = efi_file_open_ex, + .read_ex = efi_file_read_ex, + .write_ex = efi_file_write_ex, + .flush_ex = efi_file_flush_ex, +}; + +/** + * efi_file_from_path() - open file via device path + * + * The device path @fp consists of the device path of the handle with the + * simple file system protocol and one or more file path device path nodes. + * The concatenation of all file path names provides the total file path. + * + * The code starts at the first file path node and tries to open that file or + * directory. If there is a succeding file path node, the code opens it relative + * to this directory and continues iterating until reaching the last file path + * node. + * + * @fp: device path + * Return: EFI_FILE_PROTOCOL for the file or NULL + */ +struct efi_file_handle *efi_file_from_path(struct efi_device_path *fp) +{ + struct efi_simple_file_system_protocol *v; + struct efi_file_handle *f; + efi_status_t ret; + + v = efi_fs_from_path(fp); + if (!v) + return NULL; + + EFI_CALL(ret = v->open_volume(v, &f)); + if (ret != EFI_SUCCESS) + return NULL; + + /* Skip over device-path nodes before the file path. */ + while (fp && !EFI_DP_TYPE(fp, MEDIA_DEVICE, FILE_PATH)) + fp = efi_dp_next(fp); + + /* + * Step through the nodes of the directory path until the actual file + * node is reached which is the final node in the device path. + */ + while (fp) { + struct efi_device_path_file_path *fdp = + container_of(fp, struct efi_device_path_file_path, dp); + struct efi_file_handle *f2; + u16 *filename; + size_t filename_sz; + + if (!EFI_DP_TYPE(fp, MEDIA_DEVICE, FILE_PATH)) { + printf("bad file path!\n"); + EFI_CALL(f->close(f)); + return NULL; + } + + /* + * UEFI specification requires pointers that are passed to + * protocol member functions to be aligned. So memcpy it + * unconditionally + */ + if (fdp->dp.length <= offsetof(struct efi_device_path_file_path, str)) + return NULL; + filename_sz = fdp->dp.length - + offsetof(struct efi_device_path_file_path, str); + filename = malloc(filename_sz); + if (!filename) + return NULL; + memcpy(filename, fdp->str, filename_sz); + EFI_CALL(ret = f->open(f, &f2, filename, + EFI_FILE_MODE_READ, 0)); + free(filename); + if (ret != EFI_SUCCESS) + return NULL; + + fp = efi_dp_next(fp); + + EFI_CALL(f->close(f)); + f = f2; + } + + return f; +} + +efi_status_t efi_open_volume_int(struct efi_simple_file_system_protocol *this, + struct efi_file_handle **root) +{ + struct file_system *fs = to_fs(this); + + *root = file_open(fs, NULL, NULL, 0, 0); + + return EFI_SUCCESS; +} + +static efi_status_t EFIAPI +efi_open_volume(struct efi_simple_file_system_protocol *this, + struct efi_file_handle **root) +{ + EFI_ENTRY("%p, %p", this, root); + + return EFI_EXIT(efi_open_volume_int(this, root)); +} + +efi_status_t +efi_create_simple_file_system(struct blk_desc *desc, int part, + struct efi_device_path *dp, + struct efi_simple_file_system_protocol **fsp) +{ + struct file_system *fs; + + fs = calloc(1, sizeof(*fs)); + if (!fs) + return EFI_OUT_OF_RESOURCES; + fs->base.rev = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_REVISION; + fs->base.open_volume = efi_open_volume; + fs->desc = desc; + fs->part = part; + fs->dp = dp; + *fsp = &fs->base; + + return EFI_SUCCESS; +} diff --git a/lib/efi_loader/efi_firmware.c b/lib/efi_loader/efi_firmware.c new file mode 100644 index 00000000000..5a754c9cd03 --- /dev/null +++ b/lib/efi_loader/efi_firmware.c @@ -0,0 +1,767 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI Firmware management protocol + * + * Copyright (c) 2020 Linaro Limited + * Author: AKASHI Takahiro + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <charset.h> +#include <dfu.h> +#include <efi_loader.h> +#include <efi_variable.h> +#include <fwu.h> +#include <image.h> +#include <signatures.h> + +#include <linux/list.h> + +#define FMP_PAYLOAD_HDR_SIGNATURE SIGNATURE_32('M', 'S', 'S', '1') + +/** + * struct fmp_payload_header - EDK2 header for the FMP payload + * + * This structure describes the header which is preprended to the + * FMP payload by the edk2 capsule generation scripts. + * + * @signature: Header signature used to identify the header + * @header_size: Size of the structure + * @fw_version: Firmware versions used + * @lowest_supported_version: Lowest supported version + */ +struct fmp_payload_header { + u32 signature; + u32 header_size; + u32 fw_version; + u32 lowest_supported_version; +}; + +/** + * struct fmp_state - fmp firmware update state + * + * This structure describes the state of the firmware update + * through FMP protocol. + * + * @fw_version: Firmware versions used + * @lowest_supported_version: Lowest supported version + * @last_attempt_version: Last attempt version + * @last_attempt_status: Last attempt status + */ +struct fmp_state { + u32 fw_version; + u32 lowest_supported_version; /* not used */ + u32 last_attempt_version; /* not used */ + u32 last_attempt_status; /* not used */ +}; + +__weak void set_dfu_alt_info(char *interface, char *devstr) +{ + env_set("dfu_alt_info", update_info.dfu_string); +} + +/** + * efi_firmware_get_image_type_id - get image_type_id + * @image_index: image index + * + * Return the image_type_id identified by the image index. + * + * Return: pointer to the image_type_id, NULL if image_index is invalid + */ +static +efi_guid_t *efi_firmware_get_image_type_id(u8 image_index) +{ + int i; + struct efi_fw_image *fw_array; + + fw_array = update_info.images; + for (i = 0; i < update_info.num_images; i++) { + if (fw_array[i].image_index == image_index) + return &fw_array[i].image_type_id; + } + + return NULL; +} + +/* Place holder; not supported */ +static +efi_status_t EFIAPI efi_firmware_get_image_unsupported( + struct efi_firmware_management_protocol *this, + u8 image_index, + void *image, + efi_uintn_t *image_size) +{ + EFI_ENTRY("%p %d %p %p\n", this, image_index, image, image_size); + + return EFI_EXIT(EFI_UNSUPPORTED); +} + +/* Place holder; not supported */ +static +efi_status_t EFIAPI efi_firmware_check_image_unsupported( + struct efi_firmware_management_protocol *this, + u8 image_index, + const void *image, + efi_uintn_t *image_size, + u32 *image_updatable) +{ + EFI_ENTRY("%p %d %p %p %p\n", this, image_index, image, image_size, + image_updatable); + + return EFI_EXIT(EFI_UNSUPPORTED); +} + +/* Place holder; not supported */ +static +efi_status_t EFIAPI efi_firmware_get_package_info_unsupported( + struct efi_firmware_management_protocol *this, + u32 *package_version, + u16 **package_version_name, + u32 *package_version_name_maxlen, + u64 *attributes_supported, + u64 *attributes_setting) +{ + EFI_ENTRY("%p %p %p %p %p %p\n", this, package_version, + package_version_name, package_version_name_maxlen, + attributes_supported, attributes_setting); + + return EFI_EXIT(EFI_UNSUPPORTED); +} + +/* Place holder; not supported */ +static +efi_status_t EFIAPI efi_firmware_set_package_info_unsupported( + struct efi_firmware_management_protocol *this, + const void *image, + efi_uintn_t *image_size, + const void *vendor_code, + u32 package_version, + const u16 *package_version_name) +{ + EFI_ENTRY("%p %p %p %p %x %p\n", this, image, image_size, vendor_code, + package_version, package_version_name); + + return EFI_EXIT(EFI_UNSUPPORTED); +} + +/** + * efi_firmware_get_lsv_from_dtb - get lowest supported version from dtb + * @image_index: Image index + * @image_type_id: Image type id + * @lsv: Pointer to store the lowest supported version + * + * Read the firmware version information from dtb. + */ +static void efi_firmware_get_lsv_from_dtb(u8 image_index, + efi_guid_t *image_type_id, u32 *lsv) +{ + const void *fdt = gd->fdt_blob; + const fdt32_t *val; + const char *guid_str; + int len, offset, index; + int parent, ret; + + *lsv = 0; + + parent = fdt_subnode_offset(fdt, 0, "firmware-version"); + if (parent < 0) + return; + + fdt_for_each_subnode(offset, fdt, parent) { + efi_guid_t guid; + + guid_str = fdt_getprop(fdt, offset, "image-type-id", &len); + if (!guid_str) + continue; + ret = uuid_str_to_bin(guid_str, guid.b, UUID_STR_FORMAT_GUID); + if (ret < 0) { + log_warning("Wrong image-type-id format.\n"); + continue; + } + + val = fdt_getprop(fdt, offset, "image-index", &len); + if (!val) + continue; + index = fdt32_to_cpu(*val); + + if (!guidcmp(&guid, image_type_id) && index == image_index) { + val = fdt_getprop(fdt, offset, + "lowest-supported-version", &len); + if (val) + *lsv = fdt32_to_cpu(*val); + } + } +} + +/** + * efi_firmware_fill_version_info - fill the version information + * @image_info: Image information + * @fw_array: Pointer to size of new image + * + * Fill the version information into image_info strucrure. + * + */ +static +void efi_firmware_fill_version_info(struct efi_firmware_image_descriptor *image_info, + struct efi_fw_image *fw_array) +{ + u16 varname[13]; /* u"FmpStateXXXX" */ + efi_status_t ret; + efi_uintn_t size, expected_size; + uint num_banks = 1; + uint active_index = 0; + struct fmp_state *var_state; + + efi_firmware_get_lsv_from_dtb(fw_array->image_index, + &fw_array->image_type_id, + &image_info->lowest_supported_image_version); + + image_info->version_name = NULL; /* not supported */ + image_info->last_attempt_version = 0; + image_info->last_attempt_status = LAST_ATTEMPT_STATUS_SUCCESS; + image_info->version = 0; + + /* get the fw_version */ + efi_create_indexed_name(varname, sizeof(varname), "FmpState", + fw_array->image_index); + if (IS_ENABLED(CONFIG_FWU_MULTI_BANK_UPDATE)) { + ret = fwu_get_active_index(&active_index); + if (ret) + return; + + num_banks = CONFIG_FWU_NUM_BANKS; + } + + size = num_banks * sizeof(*var_state); + expected_size = size; + var_state = calloc(1, size); + if (!var_state) + return; + + ret = efi_get_variable_int(varname, &fw_array->image_type_id, + NULL, &size, var_state, NULL); + if (ret == EFI_SUCCESS && expected_size == size) + image_info->version = var_state[active_index].fw_version; + + free(var_state); +} + +/** + * efi_gen_capsule_guids - generate GUIDs for the images + * + * Generate the image_type_id for each image in the update_info.images array + * using the first compatible from the device tree and a salt + * UUID defined at build time. + * + * Returns: status code + */ +static efi_status_t efi_gen_capsule_guids(void) +{ + int ret, i; + struct uuid namespace; + const char *compatible; /* Full array including null bytes */ + struct efi_fw_image *fw_array; + + fw_array = update_info.images; + /* Check if we need to run (there are images and we didn't already generate their IDs) */ + if (!update_info.num_images || + memchr_inv(&fw_array[0].image_type_id, 0, sizeof(fw_array[0].image_type_id))) + return EFI_SUCCESS; + + ret = uuid_str_to_bin(CONFIG_EFI_CAPSULE_NAMESPACE_GUID, + (unsigned char *)&namespace, UUID_STR_FORMAT_GUID); + if (ret) { + log_debug("%s: EFI_CAPSULE_NAMESPACE_GUID is invalid: %d\n", __func__, ret); + return EFI_INVALID_PARAMETER; + } + + compatible = ofnode_read_string(ofnode_root(), "compatible"); + if (!compatible) { + log_debug("%s: model or compatible not defined\n", __func__); + return EFI_INVALID_PARAMETER; + } + + for (i = 0; i < update_info.num_images; i++) { + if (!fw_array[i].fw_name) { + log_err("fw_name is not defined. Not generating capsule GUIDs\n"); + return EFI_INVALID_PARAMETER; + } + gen_v5_guid(&namespace, + &fw_array[i].image_type_id, + compatible, strlen(compatible), + fw_array[i].fw_name, u16_strlen(fw_array[i].fw_name) * sizeof(uint16_t), + NULL); + + log_debug("Image %ls UUID %pUl\n", fw_array[i].fw_name, + &fw_array[i].image_type_id); + } + + return EFI_SUCCESS; +} + +/** + * efi_fill_image_desc_array - populate image descriptor array + * @image_info_size: Size of @image_info + * @image_info: Image information + * @descriptor_version: Pointer to version number + * @descriptor_count: Image count + * @descriptor_size: Pointer to descriptor size + * @package_version: Package version + * @package_version_name: Package version's name + * + * Return information about the current firmware image in @image_info. + * @image_info will consist of a number of descriptors. + * Each descriptor will be created based on efi_fw_image array. + * + * Return status code + */ +static efi_status_t efi_fill_image_desc_array( + efi_uintn_t *image_info_size, + struct efi_firmware_image_descriptor *image_info, + u32 *descriptor_version, + u8 *descriptor_count, + efi_uintn_t *descriptor_size, + u32 *package_version, + u16 **package_version_name) +{ + size_t total_size; + struct efi_fw_image *fw_array; + int i, ret; + + total_size = sizeof(*image_info) * update_info.num_images; + + if (*image_info_size < total_size) { + *image_info_size = total_size; + + return EFI_BUFFER_TOO_SMALL; + } + *image_info_size = total_size; + + ret = efi_gen_capsule_guids(); + if (ret != EFI_SUCCESS) + return ret; + + fw_array = update_info.images; + *descriptor_count = update_info.num_images; + *descriptor_version = EFI_FIRMWARE_IMAGE_DESCRIPTOR_VERSION; + *descriptor_size = sizeof(*image_info); + *package_version = 0xffffffff; /* not supported */ + *package_version_name = NULL; /* not supported */ + + for (i = 0; i < update_info.num_images; i++) { + image_info[i].image_index = fw_array[i].image_index; + image_info[i].image_type_id = fw_array[i].image_type_id; + image_info[i].image_id = fw_array[i].image_index; + image_info[i].image_id_name = fw_array[i].fw_name; + + efi_firmware_fill_version_info(&image_info[i], &fw_array[i]); + + image_info[i].size = 0; + image_info[i].attributes_supported = + IMAGE_ATTRIBUTE_IMAGE_UPDATABLE | + IMAGE_ATTRIBUTE_AUTHENTICATION_REQUIRED; + image_info[i].attributes_setting = + IMAGE_ATTRIBUTE_IMAGE_UPDATABLE; + + /* Check if the capsule authentication is enabled */ + if (IS_ENABLED(CONFIG_EFI_CAPSULE_AUTHENTICATE)) + image_info[0].attributes_setting |= + IMAGE_ATTRIBUTE_AUTHENTICATION_REQUIRED; + + image_info[i].hardware_instance = 1; + image_info[i].dependencies = NULL; + } + + return EFI_SUCCESS; +} + +/** + * efi_firmware_capsule_authenticate - authenticate the capsule if enabled + * @p_image: Pointer to new image + * @p_image_size: Pointer to size of new image + * + * Authenticate the capsule if authentication is enabled. + * The image pointer and the image size are updated in case of success. + * + * Return: status code + */ +static +efi_status_t efi_firmware_capsule_authenticate(const void **p_image, + efi_uintn_t *p_image_size) +{ + const void *image = *p_image; + efi_uintn_t image_size = *p_image_size; + void *capsule_payload; + efi_status_t status; + efi_uintn_t capsule_payload_size; + + if (IS_ENABLED(CONFIG_EFI_CAPSULE_AUTHENTICATE)) { + capsule_payload = NULL; + capsule_payload_size = 0; + status = efi_capsule_authenticate(image, image_size, + &capsule_payload, + &capsule_payload_size); + + if (status == EFI_SECURITY_VIOLATION) { + printf("Capsule authentication check failed. Aborting update\n"); + return status; + } else if (status != EFI_SUCCESS) { + return status; + } + + debug("Capsule authentication successful\n"); + image = capsule_payload; + image_size = capsule_payload_size; + } else { + debug("Capsule authentication disabled. "); + debug("Updating capsule without authenticating.\n"); + } + + *p_image = image; + *p_image_size = image_size; + return EFI_SUCCESS; +} + +/** + * efi_firmware_set_fmp_state_var - set FmpStateXXXX variable + * @state: Pointer to fmp state + * @image_index: image index + * + * Update the FmpStateXXXX variable with the firmware update state. + * + * Return: status code + */ +static +efi_status_t efi_firmware_set_fmp_state_var(struct fmp_state *state, u8 image_index) +{ + u16 varname[13]; /* u"FmpStateXXXX" */ + efi_status_t ret; + uint num_banks = 1; + uint update_bank = 0; + efi_uintn_t size; + efi_guid_t *image_type_id; + struct fmp_state *var_state; + + image_type_id = efi_firmware_get_image_type_id(image_index); + if (!image_type_id) + return EFI_INVALID_PARAMETER; + + efi_create_indexed_name(varname, sizeof(varname), "FmpState", + image_index); + + if (IS_ENABLED(CONFIG_FWU_MULTI_BANK_UPDATE)) { + ret = fwu_plat_get_update_index(&update_bank); + if (ret) + return EFI_INVALID_PARAMETER; + + num_banks = CONFIG_FWU_NUM_BANKS; + } + + size = num_banks * sizeof(*var_state); + var_state = malloc(size); + if (!var_state) + return EFI_OUT_OF_RESOURCES; + + /* + * GetVariable may fail, EFI_NOT_FOUND is returned if FmpState + * variable has not been set yet. + */ + ret = efi_get_variable_int(varname, image_type_id, NULL, &size, + var_state, NULL); + if (ret != EFI_SUCCESS) + memset(var_state, 0, num_banks * sizeof(*var_state)); + + /* + * Only the fw_version is set here. + * lowest_supported_version in FmpState variable is ignored since + * it can be tampered if the file based EFI variable storage is used. + */ + var_state[update_bank].fw_version = state->fw_version; + + size = num_banks * sizeof(*var_state); + ret = efi_set_variable_int(varname, image_type_id, + EFI_VARIABLE_READ_ONLY | + EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS, + size, var_state, false); + + free(var_state); + + return ret; +} + +/** + * efi_firmware_get_fw_version - get fw_version from FMP payload header + * @p_image: Pointer to new image + * @p_image_size: Pointer to size of new image + * @state: Pointer to fmp state + * + * Parse the FMP payload header and fill the fmp_state structure. + * If no FMP payload header is found, fmp_state structure is not updated. + * + */ +static void efi_firmware_get_fw_version(const void **p_image, + efi_uintn_t *p_image_size, + struct fmp_state *state) +{ + const struct fmp_payload_header *header; + u32 fmp_hdr_signature = FMP_PAYLOAD_HDR_SIGNATURE; + + header = *p_image; + if (header->signature == fmp_hdr_signature) { + /* FMP header is inserted above the capsule payload */ + state->fw_version = header->fw_version; + + *p_image += header->header_size; + *p_image_size -= header->header_size; + } +} + +/** + * efi_firmware_verify_image - verify image + * @p_image: Pointer to new image + * @p_image_size: Pointer to size of new image + * @image_index: Image index + * @state: Pointer to fmp state + * + * Verify the capsule authentication and check if the fw_version + * is equal or greater than the lowest supported version. + * + * Return: status code + */ +static +efi_status_t efi_firmware_verify_image(const void **p_image, + efi_uintn_t *p_image_size, + u8 image_index, + struct fmp_state *state) +{ + u32 lsv; + efi_status_t ret; + efi_guid_t *image_type_id; + + ret = efi_firmware_capsule_authenticate(p_image, p_image_size); + if (ret != EFI_SUCCESS) + return ret; + + efi_firmware_get_fw_version(p_image, p_image_size, state); + + image_type_id = efi_firmware_get_image_type_id(image_index); + if (!image_type_id) + return EFI_INVALID_PARAMETER; + + efi_firmware_get_lsv_from_dtb(image_index, image_type_id, &lsv); + if (state->fw_version < lsv) { + log_err("Firmware version %u too low. Expecting >= %u. Aborting update\n", + state->fw_version, lsv); + return EFI_INVALID_PARAMETER; + } + + return ret; +} + +/** + * efi_firmware_get_image_info - return information about the current + * firmware image + * @this: Protocol instance + * @image_info_size: Size of @image_info + * @image_info: Image information + * @descriptor_version: Pointer to version number + * @descriptor_count: Pointer to number of descriptors + * @descriptor_size: Pointer to descriptor size + * @package_version: Package version + * @package_version_name: Package version's name + * + * Return information bout the current firmware image in @image_info. + * @image_info will consist of a number of descriptors. + * Each descriptor will be created based on "dfu_alt_info" variable. + * + * Return status code + */ +static +efi_status_t EFIAPI efi_firmware_get_image_info( + struct efi_firmware_management_protocol *this, + efi_uintn_t *image_info_size, + struct efi_firmware_image_descriptor *image_info, + u32 *descriptor_version, + u8 *descriptor_count, + efi_uintn_t *descriptor_size, + u32 *package_version, + u16 **package_version_name) +{ + efi_status_t ret; + + EFI_ENTRY("%p %p %p %p %p %p %p %p\n", this, + image_info_size, image_info, + descriptor_version, descriptor_count, descriptor_size, + package_version, package_version_name); + + if (!image_info_size) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + if (*image_info_size && + (!image_info || !descriptor_version || !descriptor_count || + !descriptor_size || !package_version || !package_version_name)) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + ret = efi_fill_image_desc_array(image_info_size, image_info, + descriptor_version, descriptor_count, + descriptor_size, package_version, + package_version_name); + + return EFI_EXIT(ret); +} + +#ifdef CONFIG_EFI_CAPSULE_FIRMWARE_FIT +/* + * This FIRMWARE_MANAGEMENT_PROTOCOL driver provides a firmware update + * method with existing FIT image format, and handles + * - multiple regions of firmware via DFU + * but doesn't support + * - versioning of firmware image + * - package information + */ + +/** + * efi_firmware_fit_set_image - update the firmware image + * @this: Protocol instance + * @image_index: Image index number + * @image: New image + * @image_size: Size of new image + * @vendor_code: Vendor-specific update policy + * @progress: Function to report the progress of update + * @abort_reason: Pointer to string of abort reason + * + * Update the firmware to new image, using dfu. The new image should + * have FIT image format commonly used in U-Boot. + * @vendor_code, @progress and @abort_reason are not supported. + * + * Return: status code + */ +static +efi_status_t EFIAPI efi_firmware_fit_set_image( + struct efi_firmware_management_protocol *this, + u8 image_index, + const void *image, + efi_uintn_t image_size, + const void *vendor_code, + efi_status_t (*progress)(efi_uintn_t completion), + u16 **abort_reason) +{ + efi_status_t status; + struct fmp_state state = { 0 }; + + EFI_ENTRY("%p %d %p %zu %p %p %p\n", this, image_index, image, + image_size, vendor_code, progress, abort_reason); + + if (!image || image_index != 1) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + status = efi_firmware_verify_image(&image, &image_size, image_index, + &state); + if (status != EFI_SUCCESS) + return EFI_EXIT(status); + + if (fit_update(image)) + return EFI_EXIT(EFI_DEVICE_ERROR); + + efi_firmware_set_fmp_state_var(&state, image_index); + + return EFI_EXIT(EFI_SUCCESS); +} + +const struct efi_firmware_management_protocol efi_fmp_fit = { + .get_image_info = efi_firmware_get_image_info, + .get_image = efi_firmware_get_image_unsupported, + .set_image = efi_firmware_fit_set_image, + .check_image = efi_firmware_check_image_unsupported, + .get_package_info = efi_firmware_get_package_info_unsupported, + .set_package_info = efi_firmware_set_package_info_unsupported, +}; +#endif /* CONFIG_EFI_CAPSULE_FIRMWARE_FIT */ + +#ifdef CONFIG_EFI_CAPSULE_FIRMWARE_RAW +/* + * This FIRMWARE_MANAGEMENT_PROTOCOL driver provides a firmware update + * method with raw data. + */ + +/** + * efi_firmware_raw_set_image - update the firmware image + * @this: Protocol instance + * @image_index: Image index number + * @image: New image + * @image_size: Size of new image + * @vendor_code: Vendor-specific update policy + * @progress: Function to report the progress of update + * @abort_reason: Pointer to string of abort reason + * + * Update the firmware to new image, using dfu. The new image should + * be a single raw image. + * @vendor_code, @progress and @abort_reason are not supported. + * + * Return: status code + */ +static +efi_status_t EFIAPI efi_firmware_raw_set_image( + struct efi_firmware_management_protocol *this, + u8 image_index, + const void *image, + efi_uintn_t image_size, + const void *vendor_code, + efi_status_t (*progress)(efi_uintn_t completion), + u16 **abort_reason) +{ + int ret; + u8 dfu_alt_num; + efi_status_t status; + struct fmp_state state = { 0 }; + + EFI_ENTRY("%p %d %p %zu %p %p %p\n", this, image_index, image, + image_size, vendor_code, progress, abort_reason); + + if (!image) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + status = efi_firmware_verify_image(&image, &image_size, image_index, + &state); + if (status != EFI_SUCCESS) + return EFI_EXIT(status); + + /* + * dfu_alt_num is assigned from 0 while image_index starts from 1. + * dfu_alt_num is calculated by (image_index - 1) when multi bank update + * is not used. + */ + dfu_alt_num = image_index - 1; + if (IS_ENABLED(CONFIG_FWU_MULTI_BANK_UPDATE)) { + /* + * Based on the value of update bank, derive the + * image index value. + */ + ret = fwu_get_dfu_alt_num(image_index, &dfu_alt_num); + if (ret) { + log_debug("Unable to get FWU image_index\n"); + return EFI_EXIT(EFI_DEVICE_ERROR); + } + } + + if (dfu_write_by_alt(dfu_alt_num, (void *)image, image_size, + NULL, NULL)) + return EFI_EXIT(EFI_DEVICE_ERROR); + + efi_firmware_set_fmp_state_var(&state, image_index); + + return EFI_EXIT(EFI_SUCCESS); +} + +const struct efi_firmware_management_protocol efi_fmp_raw = { + .get_image_info = efi_firmware_get_image_info, + .get_image = efi_firmware_get_image_unsupported, + .set_image = efi_firmware_raw_set_image, + .check_image = efi_firmware_check_image_unsupported, + .get_package_info = efi_firmware_get_package_info_unsupported, + .set_package_info = efi_firmware_set_package_info_unsupported, +}; +#endif /* CONFIG_EFI_CAPSULE_FIRMWARE_RAW */ diff --git a/lib/efi_loader/efi_freestanding.c b/lib/efi_loader/efi_freestanding.c new file mode 100644 index 00000000000..b2786095c32 --- /dev/null +++ b/lib/efi_loader/efi_freestanding.c @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Library for freestanding binary + * + * Copyright 2019, Heinrich Schuchardt <xypron.glpk@gmx.de> + * + * GCC requires that freestanding programs provide memcpy(), memmove(), + * memset(), and memcmp(). + */ + +#include <linux/types.h> + +/** + * memcmp() - compare memory areas + * + * @s1: pointer to first area + * @s2: pointer to second area + * @n: number of bytes to compare + * Return: 0 if both memory areas are the same, otherwise the sign of the + * result value is the same as the sign of the difference between + * the first differing pair of bytes taken as u8. + */ +int memcmp(const void *s1, const void *s2, size_t n) +{ + const u8 *pos1 = s1; + const u8 *pos2 = s2; + + for (; n; --n) { + if (*pos1 != *pos2) + return *pos1 - *pos2; + ++pos1; + ++pos2; + } + return 0; +} + +/** + * memcpy() - copy memory area + * + * @dest: destination buffer + * @src: source buffer + * @n: number of bytes to copy + * Return: pointer to destination buffer + */ +void *memmove(void *dest, const void *src, size_t n) +{ + u8 *d = dest; + const u8 *s = src; + + if (d <= s) { + for (; n; --n) + *d++ = *s++; + } else { + d += n; + s += n; + for (; n; --n) + *--d = *--s; + } + return dest; +} + +/** + * memcpy() - copy memory area + * + * @dest: destination buffer + * @src: source buffer + * @n: number of bytes to copy + * Return: pointer to destination buffer + */ +void *memcpy(void *dest, const void *src, size_t n) +{ + return memmove(dest, src, n); +} + +/** + * memset() - fill memory with a constant byte + * + * @s: destination buffer + * @c: byte value + * @n: number of bytes to set + * Return: pointer to destination buffer + */ +void *memset(void *s, int c, size_t n) +{ + u8 *d = s; + + for (; n; --n) + *d++ = c; + return s; +} + +/** + * __cyg_profile_func_enter() - record function entry + * + * This is called on every function entry when compiling with + * -finstrument-functions. + * + * We do nothing here. + * + * func_ptr: Pointer to function being entered + * caller: Pointer to function which called this function + */ +void notrace +__cyg_profile_func_enter(void *func_ptr, void *caller) +{ +} + +/** + * __cyg_profile_func_exit() - record function exit + * + * This is called on every function exit when compiling with + * -finstrument-functions. + * + * We do nothing here. + * + * func_ptr: Pointer to function being entered + * caller: Pointer to function which called this function + */ +void notrace +__cyg_profile_func_exit(void *func_ptr, void *caller) +{ +} diff --git a/lib/efi_loader/efi_gop.c b/lib/efi_loader/efi_gop.c new file mode 100644 index 00000000000..4593975be5a --- /dev/null +++ b/lib/efi_loader/efi_gop.c @@ -0,0 +1,556 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI application disk support + * + * Copyright (c) 2016 Alexander Graf + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <dm.h> +#include <efi_loader.h> +#include <log.h> +#include <malloc.h> +#include <mapmem.h> +#include <video.h> +#include <asm/global_data.h> + +DECLARE_GLOBAL_DATA_PTR; + +static const efi_guid_t efi_gop_guid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID; + +/** + * struct efi_gop_obj - graphical output protocol object + * + * @header: EFI object header + * @ops: graphical output protocol interface + * @info: graphical output mode information + * @mode: graphical output mode + * @bpix: bits per pixel + * @fb: frame buffer + */ +struct efi_gop_obj { + struct efi_object header; + struct efi_gop ops; + struct efi_gop_mode_info info; + struct efi_gop_mode mode; + /* Fields we only have access to during init */ + u32 bpix; + void *fb; +}; + +static efi_status_t EFIAPI gop_query_mode(struct efi_gop *this, u32 mode_number, + efi_uintn_t *size_of_info, + struct efi_gop_mode_info **info) +{ + struct efi_gop_obj *gopobj; + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p, %x, %p, %p", this, mode_number, size_of_info, info); + + if (!this || !size_of_info || !info || mode_number) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + gopobj = container_of(this, struct efi_gop_obj, ops); + ret = efi_allocate_pool(EFI_BOOT_SERVICES_DATA, sizeof(gopobj->info), + (void **)info); + if (ret != EFI_SUCCESS) + goto out; + *size_of_info = sizeof(gopobj->info); + memcpy(*info, &gopobj->info, sizeof(gopobj->info)); + +out: + return EFI_EXIT(ret); +} + +static __always_inline struct efi_gop_pixel efi_vid30_to_blt_col(u32 vid) +{ + struct efi_gop_pixel blt = { + .reserved = 0, + }; + + blt.blue = (vid & 0x3ff) >> 2; + vid >>= 10; + blt.green = (vid & 0x3ff) >> 2; + vid >>= 10; + blt.red = (vid & 0x3ff) >> 2; + return blt; +} + +static __always_inline u32 efi_blt_col_to_vid30(struct efi_gop_pixel *blt) +{ + return (u32)(blt->red << 2) << 20 | + (u32)(blt->green << 2) << 10 | + (u32)(blt->blue << 2); +} + +static __always_inline struct efi_gop_pixel efi_vid16_to_blt_col(u16 vid) +{ + struct efi_gop_pixel blt = { + .reserved = 0, + }; + + blt.blue = (vid & 0x1f) << 3; + vid >>= 5; + blt.green = (vid & 0x3f) << 2; + vid >>= 6; + blt.red = (vid & 0x1f) << 3; + return blt; +} + +static __always_inline u16 efi_blt_col_to_vid16(struct efi_gop_pixel *blt) +{ + return (u16)(blt->red >> 3) << 11 | + (u16)(blt->green >> 2) << 5 | + (u16)(blt->blue >> 3); +} + +static __always_inline efi_status_t gop_blt_int(struct efi_gop *this, + struct efi_gop_pixel *bufferp, + u32 operation, efi_uintn_t sx, + efi_uintn_t sy, efi_uintn_t dx, + efi_uintn_t dy, + efi_uintn_t width, + efi_uintn_t height, + efi_uintn_t delta, + efi_uintn_t vid_bpp) +{ + struct efi_gop_obj *gopobj = container_of(this, struct efi_gop_obj, ops); + efi_uintn_t i, j, linelen, slineoff = 0, dlineoff, swidth, dwidth; + u32 *fb32 = gopobj->fb; + u16 *fb16 = gopobj->fb; + struct efi_gop_pixel *buffer = __builtin_assume_aligned(bufferp, 4); + + if (delta) { + /* Check for 4 byte alignment */ + if (delta & 3) + return EFI_INVALID_PARAMETER; + linelen = delta >> 2; + } else { + linelen = width; + } + + /* Check source rectangle */ + switch (operation) { + case EFI_BLT_VIDEO_FILL: + break; + case EFI_BLT_BUFFER_TO_VIDEO: + if (sx + width > linelen) + return EFI_INVALID_PARAMETER; + break; + case EFI_BLT_VIDEO_TO_BLT_BUFFER: + case EFI_BLT_VIDEO_TO_VIDEO: + if (sx + width > gopobj->info.width || + sy + height > gopobj->info.height) + return EFI_INVALID_PARAMETER; + break; + default: + return EFI_INVALID_PARAMETER; + } + + /* Check destination rectangle */ + switch (operation) { + case EFI_BLT_VIDEO_FILL: + case EFI_BLT_BUFFER_TO_VIDEO: + case EFI_BLT_VIDEO_TO_VIDEO: + if (dx + width > gopobj->info.width || + dy + height > gopobj->info.height) + return EFI_INVALID_PARAMETER; + break; + case EFI_BLT_VIDEO_TO_BLT_BUFFER: + if (dx + width > linelen) + return EFI_INVALID_PARAMETER; + break; + } + + /* Calculate line width */ + switch (operation) { + case EFI_BLT_BUFFER_TO_VIDEO: + swidth = linelen; + break; + case EFI_BLT_VIDEO_TO_BLT_BUFFER: + case EFI_BLT_VIDEO_TO_VIDEO: + swidth = gopobj->info.width; + if (!vid_bpp) + return EFI_UNSUPPORTED; + break; + case EFI_BLT_VIDEO_FILL: + swidth = 0; + break; + } + + switch (operation) { + case EFI_BLT_BUFFER_TO_VIDEO: + case EFI_BLT_VIDEO_FILL: + case EFI_BLT_VIDEO_TO_VIDEO: + dwidth = gopobj->info.width; + if (!vid_bpp) + return EFI_UNSUPPORTED; + break; + case EFI_BLT_VIDEO_TO_BLT_BUFFER: + dwidth = linelen; + break; + } + + slineoff = swidth * sy; + dlineoff = dwidth * dy; + for (i = 0; i < height; i++) { + for (j = 0; j < width; j++) { + struct efi_gop_pixel pix; + + /* Read source pixel */ + switch (operation) { + case EFI_BLT_VIDEO_FILL: + pix = *buffer; + break; + case EFI_BLT_BUFFER_TO_VIDEO: + pix = buffer[slineoff + j + sx]; + break; + case EFI_BLT_VIDEO_TO_BLT_BUFFER: + case EFI_BLT_VIDEO_TO_VIDEO: + if (vid_bpp == 32) + pix = *(struct efi_gop_pixel *)&fb32[ + slineoff + j + sx]; + else if (vid_bpp == 30) + pix = efi_vid30_to_blt_col(fb32[ + slineoff + j + sx]); + else + pix = efi_vid16_to_blt_col(fb16[ + slineoff + j + sx]); + break; + } + + /* Write destination pixel */ + switch (operation) { + case EFI_BLT_VIDEO_TO_BLT_BUFFER: + buffer[dlineoff + j + dx] = pix; + break; + case EFI_BLT_BUFFER_TO_VIDEO: + case EFI_BLT_VIDEO_FILL: + case EFI_BLT_VIDEO_TO_VIDEO: + if (vid_bpp == 32) + fb32[dlineoff + j + dx] = *(u32 *)&pix; + else if (vid_bpp == 30) + fb32[dlineoff + j + dx] = + efi_blt_col_to_vid30(&pix); + else + fb16[dlineoff + j + dx] = + efi_blt_col_to_vid16(&pix); + break; + } + } + slineoff += swidth; + dlineoff += dwidth; + } + + return EFI_SUCCESS; +} + +static efi_uintn_t gop_get_bpp(struct efi_gop *this) +{ + struct efi_gop_obj *gopobj = container_of(this, struct efi_gop_obj, ops); + efi_uintn_t vid_bpp = 0; + + switch (gopobj->bpix) { + case VIDEO_BPP32: + if (gopobj->info.pixel_format == EFI_GOT_BGRA8) + vid_bpp = 32; + else + vid_bpp = 30; + break; + case VIDEO_BPP16: + vid_bpp = 16; + break; + } + + return vid_bpp; +} + +/* + * GCC can't optimize our BLT function well, but we need to make sure that + * our 2-dimensional loop gets executed very quickly, otherwise the system + * will feel slow. + * + * By manually putting all obvious branch targets into functions which call + * our generic BLT function with constants, the compiler can successfully + * optimize for speed. + */ +static efi_status_t gop_blt_video_fill(struct efi_gop *this, + struct efi_gop_pixel *buffer, + u32 foo, efi_uintn_t sx, + efi_uintn_t sy, efi_uintn_t dx, + efi_uintn_t dy, efi_uintn_t width, + efi_uintn_t height, efi_uintn_t delta, + efi_uintn_t vid_bpp) +{ + return gop_blt_int(this, buffer, EFI_BLT_VIDEO_FILL, sx, sy, dx, + dy, width, height, delta, vid_bpp); +} + +static efi_status_t gop_blt_buf_to_vid16(struct efi_gop *this, + struct efi_gop_pixel *buffer, + u32 foo, efi_uintn_t sx, + efi_uintn_t sy, efi_uintn_t dx, + efi_uintn_t dy, efi_uintn_t width, + efi_uintn_t height, efi_uintn_t delta) +{ + return gop_blt_int(this, buffer, EFI_BLT_BUFFER_TO_VIDEO, sx, sy, dx, + dy, width, height, delta, 16); +} + +static efi_status_t gop_blt_buf_to_vid30(struct efi_gop *this, + struct efi_gop_pixel *buffer, + u32 foo, efi_uintn_t sx, + efi_uintn_t sy, efi_uintn_t dx, + efi_uintn_t dy, efi_uintn_t width, + efi_uintn_t height, efi_uintn_t delta) +{ + return gop_blt_int(this, buffer, EFI_BLT_BUFFER_TO_VIDEO, sx, sy, dx, + dy, width, height, delta, 30); +} + +static efi_status_t gop_blt_buf_to_vid32(struct efi_gop *this, + struct efi_gop_pixel *buffer, + u32 foo, efi_uintn_t sx, + efi_uintn_t sy, efi_uintn_t dx, + efi_uintn_t dy, efi_uintn_t width, + efi_uintn_t height, efi_uintn_t delta) +{ + return gop_blt_int(this, buffer, EFI_BLT_BUFFER_TO_VIDEO, sx, sy, dx, + dy, width, height, delta, 32); +} + +static efi_status_t gop_blt_vid_to_vid(struct efi_gop *this, + struct efi_gop_pixel *buffer, + u32 foo, efi_uintn_t sx, + efi_uintn_t sy, efi_uintn_t dx, + efi_uintn_t dy, efi_uintn_t width, + efi_uintn_t height, efi_uintn_t delta, + efi_uintn_t vid_bpp) +{ + return gop_blt_int(this, buffer, EFI_BLT_VIDEO_TO_VIDEO, sx, sy, dx, + dy, width, height, delta, vid_bpp); +} + +static efi_status_t gop_blt_vid_to_buf(struct efi_gop *this, + struct efi_gop_pixel *buffer, + u32 foo, efi_uintn_t sx, + efi_uintn_t sy, efi_uintn_t dx, + efi_uintn_t dy, efi_uintn_t width, + efi_uintn_t height, efi_uintn_t delta, + efi_uintn_t vid_bpp) +{ + return gop_blt_int(this, buffer, EFI_BLT_VIDEO_TO_BLT_BUFFER, sx, sy, + dx, dy, width, height, delta, vid_bpp); +} + +/** + * gop_set_mode() - set graphical output mode + * + * This function implements the SetMode() service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @this: the graphical output protocol + * @mode_number: the mode to be set + * Return: status code + */ +static efi_status_t EFIAPI gop_set_mode(struct efi_gop *this, u32 mode_number) +{ + struct efi_gop_obj *gopobj; + struct efi_gop_pixel buffer = {0, 0, 0, 0}; + efi_uintn_t vid_bpp; + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p, %x", this, mode_number); + + if (!this) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + if (mode_number) { + ret = EFI_UNSUPPORTED; + goto out; + } + gopobj = container_of(this, struct efi_gop_obj, ops); + vid_bpp = gop_get_bpp(this); + ret = gop_blt_video_fill(this, &buffer, EFI_BLT_VIDEO_FILL, 0, 0, 0, 0, + gopobj->info.width, gopobj->info.height, 0, + vid_bpp); +out: + return EFI_EXIT(ret); +} + +/* + * Copy rectangle. + * + * This function implements the Blt service of the EFI_GRAPHICS_OUTPUT_PROTOCOL. + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @this: EFI_GRAPHICS_OUTPUT_PROTOCOL + * @buffer: pixel buffer + * @sx: source x-coordinate + * @sy: source y-coordinate + * @dx: destination x-coordinate + * @dy: destination y-coordinate + * @width: width of rectangle + * @height: height of rectangle + * @delta: length in bytes of a line in the pixel buffer (optional) + * Return: status code + */ +static efi_status_t EFIAPI gop_blt(struct efi_gop *this, + struct efi_gop_pixel *buffer, + u32 operation, efi_uintn_t sx, + efi_uintn_t sy, efi_uintn_t dx, + efi_uintn_t dy, efi_uintn_t width, + efi_uintn_t height, efi_uintn_t delta) +{ + efi_status_t ret = EFI_INVALID_PARAMETER; + efi_uintn_t vid_bpp; + + EFI_ENTRY("%p, %p, %u, %zu, %zu, %zu, %zu, %zu, %zu, %zu", this, + buffer, operation, sx, sy, dx, dy, width, height, delta); + + vid_bpp = gop_get_bpp(this); + + /* Allow for compiler optimization */ + switch (operation) { + case EFI_BLT_VIDEO_FILL: + ret = gop_blt_video_fill(this, buffer, operation, sx, sy, dx, + dy, width, height, delta, vid_bpp); + break; + case EFI_BLT_BUFFER_TO_VIDEO: + /* This needs to be super-fast, so duplicate for 16/32bpp */ + if (vid_bpp == 32) + ret = gop_blt_buf_to_vid32(this, buffer, operation, sx, + sy, dx, dy, width, height, + delta); + else if (vid_bpp == 30) + ret = gop_blt_buf_to_vid30(this, buffer, operation, sx, + sy, dx, dy, width, height, + delta); + else + ret = gop_blt_buf_to_vid16(this, buffer, operation, sx, + sy, dx, dy, width, height, + delta); + break; + case EFI_BLT_VIDEO_TO_VIDEO: + ret = gop_blt_vid_to_vid(this, buffer, operation, sx, sy, dx, + dy, width, height, delta, vid_bpp); + break; + case EFI_BLT_VIDEO_TO_BLT_BUFFER: + ret = gop_blt_vid_to_buf(this, buffer, operation, sx, sy, dx, + dy, width, height, delta, vid_bpp); + break; + default: + ret = EFI_INVALID_PARAMETER; + } + + if (ret != EFI_SUCCESS) + return EFI_EXIT(ret); + + video_sync_all(); + + return EFI_EXIT(EFI_SUCCESS); +} + +/* + * Install graphical output protocol. + * + * If no supported video device exists this is not considered as an + * error. + */ +efi_status_t efi_gop_register(void) +{ + struct efi_gop_obj *gopobj; + u32 bpix, format, col, row; + u64 fb_base, fb_size; + efi_status_t ret; + struct udevice *vdev; + struct video_priv *priv; + struct video_uc_plat *plat; + + /* We only support a single video output device for now */ + if (uclass_first_device_err(UCLASS_VIDEO, &vdev)) { + debug("WARNING: No video device\n"); + return EFI_SUCCESS; + } + + priv = dev_get_uclass_priv(vdev); + bpix = priv->bpix; + format = priv->format; + col = video_get_xsize(vdev); + row = video_get_ysize(vdev); + + plat = dev_get_uclass_plat(vdev); + fb_base = IS_ENABLED(CONFIG_VIDEO_COPY) ? plat->copy_base : plat->base; + fb_size = plat->size; + + switch (bpix) { + case VIDEO_BPP16: + case VIDEO_BPP32: + break; + default: + /* So far, we only work in 16 or 32 bit mode */ + debug("WARNING: Unsupported video mode\n"); + return EFI_SUCCESS; + } + + gopobj = calloc(1, sizeof(*gopobj)); + if (!gopobj) { + printf("ERROR: Out of memory\n"); + return EFI_OUT_OF_RESOURCES; + } + + /* Hook up to the device list */ + efi_add_handle(&gopobj->header); + + /* Fill in object data */ + ret = efi_add_protocol(&gopobj->header, &efi_gop_guid, + &gopobj->ops); + if (ret != EFI_SUCCESS) { + printf("ERROR: Failure adding GOP protocol\n"); + return ret; + } + gopobj->ops.query_mode = gop_query_mode; + gopobj->ops.set_mode = gop_set_mode; + gopobj->ops.blt = gop_blt; + gopobj->ops.mode = &gopobj->mode; + + gopobj->mode.max_mode = 1; + gopobj->mode.info = &gopobj->info; + gopobj->mode.info_size = sizeof(gopobj->info); + + gopobj->mode.fb_base = fb_base; + gopobj->mode.fb_size = fb_size; + + gopobj->info.version = 0; + gopobj->info.width = col; + gopobj->info.height = row; + if (bpix == VIDEO_BPP32) + { + if (format == VIDEO_X2R10G10B10) { + gopobj->info.pixel_format = EFI_GOT_BITMASK; + gopobj->info.pixel_bitmask[0] = 0x3ff00000; /* red */ + gopobj->info.pixel_bitmask[1] = 0x000ffc00; /* green */ + gopobj->info.pixel_bitmask[2] = 0x000003ff; /* blue */ + gopobj->info.pixel_bitmask[3] = 0xc0000000; /* reserved */ + } else { + gopobj->info.pixel_format = EFI_GOT_BGRA8; + } + } else { + gopobj->info.pixel_format = EFI_GOT_BITMASK; + gopobj->info.pixel_bitmask[0] = 0xf800; /* red */ + gopobj->info.pixel_bitmask[1] = 0x07e0; /* green */ + gopobj->info.pixel_bitmask[2] = 0x001f; /* blue */ + } + gopobj->info.pixels_per_scanline = col; + gopobj->bpix = bpix; + gopobj->fb = map_sysmem(fb_base, fb_size); + + return EFI_SUCCESS; +} diff --git a/lib/efi_loader/efi_helper.c b/lib/efi_loader/efi_helper.c new file mode 100644 index 00000000000..8c32059edda --- /dev/null +++ b/lib/efi_loader/efi_helper.c @@ -0,0 +1,731 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2020, Linaro Limited + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <blkmap.h> +#include <bootm.h> +#include <env.h> +#include <image.h> +#include <log.h> +#include <malloc.h> +#include <mapmem.h> +#include <dm.h> +#include <fs.h> +#include <efi.h> +#include <efi_api.h> +#include <efi_load_initrd.h> +#include <efi_loader.h> +#include <efi_variable.h> +#include <host_arch.h> +#include <linux/libfdt.h> +#include <linux/list.h> + +#undef BOOTEFI_NAME + +#if HOST_ARCH == HOST_ARCH_X86_64 +#define HOST_BOOTEFI_NAME "BOOTX64.EFI" +#define HOST_PXE_ARCH 0x6 +#elif HOST_ARCH == HOST_ARCH_X86 +#define HOST_BOOTEFI_NAME "BOOTIA32.EFI" +#define HOST_PXE_ARCH 0x7 +#elif HOST_ARCH == HOST_ARCH_AARCH64 +#define HOST_BOOTEFI_NAME "BOOTAA64.EFI" +#define HOST_PXE_ARCH 0xb +#elif HOST_ARCH == HOST_ARCH_ARM +#define HOST_BOOTEFI_NAME "BOOTARM.EFI" +#define HOST_PXE_ARCH 0xa +#elif HOST_ARCH == HOST_ARCH_RISCV32 +#define HOST_BOOTEFI_NAME "BOOTRISCV32.EFI" +#define HOST_PXE_ARCH 0x19 +#elif HOST_ARCH == HOST_ARCH_RISCV64 +#define HOST_BOOTEFI_NAME "BOOTRISCV64.EFI" +#define HOST_PXE_ARCH 0x1b +#else +#error Unsupported Host architecture +#endif + +#if defined(CONFIG_SANDBOX) +#define BOOTEFI_NAME "BOOTSBOX.EFI" +#elif defined(CONFIG_ARM64) +#define BOOTEFI_NAME "BOOTAA64.EFI" +#elif defined(CONFIG_ARM) +#define BOOTEFI_NAME "BOOTARM.EFI" +#elif defined(CONFIG_X86_64) +#define BOOTEFI_NAME "BOOTX64.EFI" +#elif defined(CONFIG_X86) +#define BOOTEFI_NAME "BOOTIA32.EFI" +#elif defined(CONFIG_ARCH_RV32I) +#define BOOTEFI_NAME "BOOTRISCV32.EFI" +#elif defined(CONFIG_ARCH_RV64I) +#define BOOTEFI_NAME "BOOTRISCV64.EFI" +#else +#error Unsupported UEFI architecture +#endif + +#if defined(CONFIG_CMD_EFIDEBUG) || defined(CONFIG_EFI_LOAD_FILE2_INITRD) +/* GUID used by Linux to identify the LoadFile2 protocol with the initrd */ +const efi_guid_t efi_lf2_initrd_guid = EFI_INITRD_MEDIA_GUID; +#endif + +const char *efi_get_basename(void) +{ + return efi_use_host_arch() ? HOST_BOOTEFI_NAME : BOOTEFI_NAME; +} + +int efi_get_pxe_arch(void) +{ + if (efi_use_host_arch()) + return HOST_PXE_ARCH; + + /* http://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xml */ + if (IS_ENABLED(CONFIG_ARM64)) + return 0xb; + else if (IS_ENABLED(CONFIG_ARM)) + return 0xa; + else if (IS_ENABLED(CONFIG_X86_64)) + return 0x6; + else if (IS_ENABLED(CONFIG_X86)) + return 0x7; + else if (IS_ENABLED(CONFIG_ARCH_RV32I)) + return 0x19; + else if (IS_ENABLED(CONFIG_ARCH_RV64I)) + return 0x1b; + + return -EINVAL; +} + +/** + * efi_create_current_boot_var() - Return Boot#### name were #### is replaced by + * the value of BootCurrent + * + * @var_name: variable name + * @var_name_size: size of var_name + * + * Return: Status code + */ +static efi_status_t efi_create_current_boot_var(u16 var_name[], + size_t var_name_size) +{ + efi_uintn_t boot_current_size; + efi_status_t ret; + u16 boot_current; + u16 *pos; + + boot_current_size = sizeof(boot_current); + ret = efi_get_variable_int(u"BootCurrent", + &efi_global_variable_guid, NULL, + &boot_current_size, &boot_current, NULL); + if (ret != EFI_SUCCESS) + goto out; + + pos = efi_create_indexed_name(var_name, var_name_size, "Boot", + boot_current); + if (!pos) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + +out: + return ret; +} + +/** + * efi_get_dp_from_boot() - Retrieve and return a device path from an EFI + * Boot### variable. + * A boot option may contain an array of device paths. + * We use a VenMedia() with a specific GUID to identify + * the usage of the array members. This function is + * used to extract a specific device path + * + * @guid: vendor GUID of the VenMedia() device path node identifying the + * device path + * + * Return: device path or NULL. Caller must free the returned value + */ +struct efi_device_path *efi_get_dp_from_boot(const efi_guid_t *guid) +{ + struct efi_device_path *file_path = NULL; + struct efi_load_option lo; + void *var_value; + efi_uintn_t size; + efi_status_t ret; + u16 var_name[16]; + + ret = efi_create_current_boot_var(var_name, sizeof(var_name)); + if (ret != EFI_SUCCESS) + return NULL; + + var_value = efi_get_var(var_name, &efi_global_variable_guid, &size); + if (!var_value) + return NULL; + + ret = efi_deserialize_load_option(&lo, var_value, &size); + if (ret != EFI_SUCCESS) + goto err; + + file_path = efi_dp_from_lo(&lo, guid); + +err: + free(var_value); + return file_path; +} + +/** + * efi_load_option_dp_join() - join device-paths for load option + * + * @dp: in: binary device-path, out: joined device-path + * @dp_size: size of joined device-path + * @initrd_dp: initrd device-path or NULL + * @fdt_dp: device-tree device-path or NULL + * Return: status_code + */ +efi_status_t efi_load_option_dp_join(struct efi_device_path **dp, + size_t *dp_size, + struct efi_device_path *initrd_dp, + struct efi_device_path *fdt_dp) +{ + if (!dp) + return EFI_INVALID_PARAMETER; + + *dp_size = efi_dp_size(*dp); + + if (initrd_dp) { + struct efi_device_path *tmp_dp = *dp; + + *dp = efi_dp_concat(tmp_dp, initrd_dp, *dp_size); + efi_free_pool(tmp_dp); + if (!*dp) + return EFI_OUT_OF_RESOURCES; + *dp_size += efi_dp_size(initrd_dp) + sizeof(END); + } + + if (fdt_dp) { + struct efi_device_path *tmp_dp = *dp; + + *dp = efi_dp_concat(tmp_dp, fdt_dp, *dp_size); + efi_free_pool(tmp_dp); + if (!*dp) + return EFI_OUT_OF_RESOURCES; + *dp_size += efi_dp_size(fdt_dp) + sizeof(END); + } + + *dp_size += sizeof(END); + + return EFI_SUCCESS; +} + +const struct guid_to_hash_map { + efi_guid_t guid; + const char algo[32]; + u32 bits; +} guid_to_hash[] = { + { + EFI_CERT_X509_SHA256_GUID, + "sha256", + SHA256_SUM_LEN * 8, + }, + { + EFI_CERT_SHA256_GUID, + "sha256", + SHA256_SUM_LEN * 8, + }, + { + EFI_CERT_X509_SHA384_GUID, + "sha384", + SHA384_SUM_LEN * 8, + }, + { + EFI_CERT_X509_SHA512_GUID, + "sha512", + SHA512_SUM_LEN * 8, + }, +}; + +#define MAX_GUID_TO_HASH_COUNT ARRAY_SIZE(guid_to_hash) + +/** guid_to_sha_str - return the sha string e.g "sha256" for a given guid + * used on EFI security databases + * + * @guid: guid to check + * + * Return: len or 0 if no match is found + */ +const char *guid_to_sha_str(const efi_guid_t *guid) +{ + size_t i; + + for (i = 0; i < MAX_GUID_TO_HASH_COUNT; i++) { + if (!guidcmp(guid, &guid_to_hash[i].guid)) + return guid_to_hash[i].algo; + } + + return NULL; +} + +/** algo_to_len - return the sha size in bytes for a given string + * + * @algo: string indicating hashing algorithm to check + * + * Return: length of hash in bytes or 0 if no match is found + */ +int algo_to_len(const char *algo) +{ + size_t i; + + for (i = 0; i < MAX_GUID_TO_HASH_COUNT; i++) { + if (!strcmp(algo, guid_to_hash[i].algo)) + return guid_to_hash[i].bits / 8; + } + + return 0; +} + +/** efi_link_dev - link the efi_handle_t and udevice + * + * @handle: efi handle to associate with udevice + * @dev: udevice to associate with efi handle + * + * Return: 0 on success, negative on failure + */ +int efi_link_dev(efi_handle_t handle, struct udevice *dev) +{ + handle->dev = dev; + return dev_tag_set_ptr(dev, DM_TAG_EFI, handle); +} + +/** + * efi_unlink_dev() - unlink udevice and handle + * + * @handle: EFI handle to unlink + * + * Return: 0 on success, negative on failure + */ +int efi_unlink_dev(efi_handle_t handle) +{ + int ret; + + ret = dev_tag_del(handle->dev, DM_TAG_EFI); + if (ret) + return ret; + handle->dev = NULL; + + return 0; +} + +static int u16_tohex(u16 c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + + /* not hexadecimal */ + return -1; +} + +bool efi_varname_is_load_option(u16 *var_name16, int *index) +{ + int id, i, digit; + + if (memcmp(var_name16, u"Boot", 8)) + return false; + + for (id = 0, i = 0; i < 4; i++) { + digit = u16_tohex(var_name16[4 + i]); + if (digit < 0) + break; + id = (id << 4) + digit; + } + if (i == 4 && !var_name16[8]) { + if (index) + *index = id; + return true; + } + + return false; +} + +/** + * efi_next_variable_name() - get next variable name + * + * This function is a wrapper of efi_get_next_variable_name_int(). + * If efi_get_next_variable_name_int() returns EFI_BUFFER_TOO_SMALL, + * @size and @buf are updated by new buffer size and realloced buffer. + * + * @size: pointer to the buffer size + * @buf: pointer to the buffer + * @guid: pointer to the guid + * Return: status code + */ +efi_status_t efi_next_variable_name(efi_uintn_t *size, u16 **buf, efi_guid_t *guid) +{ + u16 *p; + efi_status_t ret; + efi_uintn_t buf_size = *size; + + ret = efi_get_next_variable_name_int(&buf_size, *buf, guid); + if (ret == EFI_NOT_FOUND) + return ret; + if (ret == EFI_BUFFER_TOO_SMALL) { + p = realloc(*buf, buf_size); + if (!p) + return EFI_OUT_OF_RESOURCES; + + *buf = p; + *size = buf_size; + ret = efi_get_next_variable_name_int(&buf_size, *buf, guid); + } + + return ret; +} + +/** + * efi_search_bootorder() - search the boot option index in BootOrder + * + * @bootorder: pointer to the BootOrder variable + * @num: number of BootOrder entry + * @target: target boot option index to search + * @index: pointer to store the index of BootOrder variable + * Return: true if exists, false otherwise + */ +bool efi_search_bootorder(u16 *bootorder, efi_uintn_t num, u32 target, u32 *index) +{ + u32 i; + + for (i = 0; i < num; i++) { + if (target == bootorder[i]) { + if (index) + *index = i; + + return true; + } + } + + return false; +} + +/** + * efi_env_set_load_options() - set load options from environment variable + * + * @handle: the image handle + * @env_var: name of the environment variable + * @load_options: pointer to load options (output) + * Return: status code + */ +efi_status_t efi_env_set_load_options(efi_handle_t handle, + const char *env_var, + u16 **load_options) +{ + const char *env = env_get(env_var); + size_t size; + u16 *pos; + efi_status_t ret; + + *load_options = NULL; + if (!env) + return EFI_SUCCESS; + size = sizeof(u16) * (utf8_utf16_strlen(env) + 1); + pos = calloc(size, 1); + if (!pos) + return EFI_OUT_OF_RESOURCES; + *load_options = pos; + utf8_utf16_strcpy(&pos, env); + ret = efi_set_load_options(handle, size, *load_options); + if (ret != EFI_SUCCESS) { + free(*load_options); + *load_options = NULL; + } + return ret; +} + +/** + * copy_fdt() - Copy the device tree to a new location available to EFI + * + * The FDT is copied to a suitable location within the EFI memory map. + * Additional 12 KiB are added to the space in case the device tree needs to be + * expanded later with fdt_open_into(). + * + * @fdtp: On entry a pointer to the flattened device tree. + * On exit a pointer to the copy of the flattened device tree. + * FDT start + * Return: status code + */ +static efi_status_t copy_fdt(void **fdtp) +{ + efi_status_t ret = 0; + void *fdt, *new_fdt; + static u64 new_fdt_addr; + static efi_uintn_t fdt_pages; + ulong fdt_size; + + /* + * Remove the configuration table that might already be + * installed, ignoring EFI_NOT_FOUND if no device-tree + * is installed + */ + efi_install_configuration_table(&efi_guid_fdt, NULL); + + if (new_fdt_addr) { + log_debug("%s: Found allocated memory at %#llx, with %#zx pages\n", + __func__, new_fdt_addr, fdt_pages); + + ret = efi_free_pages(new_fdt_addr, fdt_pages); + if (ret != EFI_SUCCESS) + log_err("Unable to free up existing FDT memory region\n"); + + new_fdt_addr = 0; + fdt_pages = 0; + } + + /* + * Give us at least 12 KiB of breathing room in case the device tree + * needs to be expanded later. + */ + fdt = *fdtp; + fdt_pages = efi_size_in_pages(fdt_totalsize(fdt) + 0x3000); + fdt_size = fdt_pages << EFI_PAGE_SHIFT; + + ret = efi_allocate_pages(EFI_ALLOCATE_ANY_PAGES, + EFI_ACPI_RECLAIM_MEMORY, fdt_pages, + &new_fdt_addr); + if (ret != EFI_SUCCESS) { + log_err("Failed to reserve space for FDT\n"); + return ret; + } + log_debug("%s: Allocated memory at %#llx, with %#zx pages\n", + __func__, new_fdt_addr, fdt_pages); + + new_fdt = (void *)(uintptr_t)new_fdt_addr; + memcpy(new_fdt, fdt, fdt_totalsize(fdt)); + fdt_set_totalsize(new_fdt, fdt_size); + + *fdtp = new_fdt; + + return EFI_SUCCESS; +} + +/** + * efi_get_configuration_table() - get configuration table + * + * @guid: GUID of the configuration table + * Return: pointer to configuration table or NULL + */ +void *efi_get_configuration_table(const efi_guid_t *guid) +{ + size_t i; + + for (i = 0; i < systab.nr_tables; i++) { + if (!guidcmp(guid, &systab.tables[i].guid)) + return systab.tables[i].table; + } + return NULL; +} + +/** + * efi_install_fdt() - install device tree + * + * If fdt is not EFI_FDT_USE_INTERNAL, the device tree located at that memory + * address will be installed as configuration table, otherwise the device + * tree located at the address indicated by environment variable fdt_addr or as + * fallback fdtcontroladdr will be used. + * + * On architectures using ACPI tables device trees shall not be installed as + * configuration table. + * + * @fdt: address of device tree or EFI_FDT_USE_INTERNAL to use + * the hardware device tree as indicated by environment variable + * fdt_addr or as fallback the internal device tree as indicated by + * the environment variable fdtcontroladdr + * Return: status code + */ +efi_status_t efi_install_fdt(void *fdt) +{ + struct bootm_headers img = { 0 }; + efi_status_t ret; + + /* + * The EBBR spec requires that we have either an FDT or an ACPI table + * but not both. + */ + if (CONFIG_IS_ENABLED(GENERATE_ACPI_TABLE) && fdt) + log_warning("Can't have ACPI table and device tree - ignoring DT.\n"); + + if (fdt == EFI_FDT_USE_INTERNAL) { + const char *fdt_opt; + uintptr_t fdt_addr; + + /* Check if there is a hardware device tree */ + fdt_opt = env_get("fdt_addr"); + /* Use our own device tree as fallback */ + if (!fdt_opt) { + fdt_opt = env_get("fdtcontroladdr"); + if (!fdt_opt) { + log_err("need device tree\n"); + return EFI_NOT_FOUND; + } + } + fdt_addr = hextoul(fdt_opt, NULL); + if (!fdt_addr) { + log_err("invalid $fdt_addr or $fdtcontroladdr\n"); + return EFI_LOAD_ERROR; + } + fdt = map_sysmem(fdt_addr, 0); + } + + /* Install device tree */ + if (fdt_check_header(fdt)) { + log_err("invalid device tree\n"); + return EFI_LOAD_ERROR; + } + + if (CONFIG_IS_ENABLED(GENERATE_ACPI_TABLE)) { + /* Create memory reservations as indicated by the device tree */ + efi_carve_out_dt_rsv(fdt); + return EFI_SUCCESS; + } + + /* Prepare device tree for payload */ + ret = copy_fdt(&fdt); + if (ret) { + log_err("out of memory\n"); + return EFI_OUT_OF_RESOURCES; + } + + if (image_setup_libfdt(&img, fdt, false)) { + log_err("failed to process device tree\n"); + return EFI_LOAD_ERROR; + } + + /* Create memory reservations as indicated by the device tree */ + efi_carve_out_dt_rsv(fdt); + + efi_try_purge_rng_seed(fdt); + + if (CONFIG_IS_ENABLED(EFI_TCG2_PROTOCOL_MEASURE_DTB)) { + ret = efi_tcg2_measure_dtb(fdt); + if (ret == EFI_SECURITY_VIOLATION) { + log_err("failed to measure DTB\n"); + return ret; + } + } + + /* Install device tree as UEFI table */ + ret = efi_install_configuration_table(&efi_guid_fdt, fdt); + if (ret != EFI_SUCCESS) { + log_err("failed to install device tree\n"); + return ret; + } + + return EFI_SUCCESS; +} + +/** + * do_bootefi_exec() - execute EFI binary + * + * The image indicated by @handle is started. When it returns the allocated + * memory for the @load_options is freed. + * + * @handle: handle of loaded image + * @load_options: load options + * Return: status code + * + * Load the EFI binary into a newly assigned memory unwinding the relocation + * information, install the loaded image protocol, and call the binary. + */ +efi_status_t do_bootefi_exec(efi_handle_t handle, void *load_options) +{ + efi_status_t ret; + efi_uintn_t exit_data_size = 0; + u16 *exit_data = NULL; + struct efi_event *evt; + + /* On ARM switch from EL3 or secure mode to EL2 or non-secure mode */ + switch_to_non_secure_mode(); + + /* + * The UEFI standard requires that the watchdog timer is set to five + * minutes when invoking an EFI boot option. + * + * Unified Extensible Firmware Interface (UEFI), version 2.7 Errata A + * 7.5. Miscellaneous Boot Services - EFI_BOOT_SERVICES.SetWatchdogTimer + */ + ret = efi_set_watchdog(300); + if (ret != EFI_SUCCESS) { + log_err("failed to set watchdog timer\n"); + goto out; + } + + /* Call our payload! */ + ret = EFI_CALL(efi_start_image(handle, &exit_data_size, &exit_data)); + if (ret != EFI_SUCCESS) { + log_err("## Application failed, r = %lu\n", + ret & ~EFI_ERROR_MASK); + if (exit_data) { + log_err("## %ls\n", exit_data); + efi_free_pool(exit_data); + } + } + +out: + free(load_options); + + /* Notify EFI_EVENT_GROUP_RETURN_TO_EFIBOOTMGR event group. */ + list_for_each_entry(evt, &efi_events, link) { + if (evt->group && + !guidcmp(evt->group, + &efi_guid_event_group_return_to_efibootmgr)) { + efi_signal_event(evt); + EFI_CALL(systab.boottime->close_event(evt)); + break; + } + } + + /* Control is returned to U-Boot, disable EFI watchdog */ + efi_set_watchdog(0); + + return ret; +} + +/** + * pmem_node_efi_memmap_setup() - Add pmem node and tweak EFI memmap + * @fdt: The devicetree to which pmem node is added + * @addr: start address of the pmem node + * @size: size of the memory of the pmem node + * + * The function adds the pmem node to the device-tree along with removing + * the corresponding region from the EFI memory map. Used primarily to + * pass the information of a RAM based ISO image to the OS. + * + * Return: 0 on success, -ve value on error + */ +static int pmem_node_efi_memmap_setup(void *fdt, u64 addr, u64 size) +{ + int ret; + u64 pages; + efi_status_t status; + + ret = fdt_fixup_pmem_region(fdt, addr, size); + if (ret) { + log_err("Failed to setup pmem node for addr %#llx, size %#llx, err %d\n", + addr, size, ret); + return ret; + } + + /* Remove the pmem region from the EFI memory map */ + pages = efi_size_in_pages(size + (addr & EFI_PAGE_MASK)); + status = efi_update_memory_map(addr, pages, EFI_CONVENTIONAL_MEMORY, + false, true); + if (status != EFI_SUCCESS) + return -1; + + return 0; +} + +int fdt_efi_pmem_setup(void *fdt) +{ + return blkmap_get_preserved_pmem_slices(pmem_node_efi_memmap_setup, + fdt); +} diff --git a/lib/efi_loader/efi_hii.c b/lib/efi_loader/efi_hii.c new file mode 100644 index 00000000000..44235970a7c --- /dev/null +++ b/lib/efi_loader/efi_hii.c @@ -0,0 +1,1077 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI Human Interface Infrastructure ... database and packages + * + * Copyright (c) 2017 Leif Lindholm + * Copyright (c) 2018 AKASHI Takahiro, Linaro Limited + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <efi_loader.h> +#include <malloc.h> +#include <asm/unaligned.h> + +const efi_guid_t efi_guid_hii_database_protocol + = EFI_HII_DATABASE_PROTOCOL_GUID; +const efi_guid_t efi_guid_hii_string_protocol = EFI_HII_STRING_PROTOCOL_GUID; + +static LIST_HEAD(efi_package_lists); +static LIST_HEAD(efi_keyboard_layout_list); + +struct efi_hii_packagelist { + struct list_head link; + // TODO should there be an associated efi_object? + efi_handle_t driver_handle; + u32 max_string_id; + struct list_head string_tables; /* list of efi_string_table */ + struct list_head guid_list; + struct list_head keyboard_packages; + + /* we could also track fonts, images, etc */ +}; + +static int efi_hii_packagelist_exists(efi_hii_handle_t package_list) +{ + struct efi_hii_packagelist *hii; + int found = 0; + + list_for_each_entry(hii, &efi_package_lists, link) { + if (hii == package_list) { + found = 1; + break; + } + } + + return found; +} + +static u32 efi_hii_package_type(struct efi_hii_package_header *header) +{ + u32 fields; + + fields = get_unaligned_le32(&header->fields); + + return (fields >> __EFI_HII_PACKAGE_TYPE_SHIFT) + & __EFI_HII_PACKAGE_TYPE_MASK; +} + +static u32 efi_hii_package_len(struct efi_hii_package_header *header) +{ + u32 fields; + + fields = get_unaligned_le32(&header->fields); + + return (fields >> __EFI_HII_PACKAGE_LEN_SHIFT) + & __EFI_HII_PACKAGE_LEN_MASK; +} + +struct efi_string_info { + efi_string_t string; + /* we could also track font info, etc */ +}; + +struct efi_string_table { + struct list_head link; + efi_string_id_t language_name; + char *language; + u32 nstrings; + /* + * NOTE: + * string id starts at 1 so value is stbl->strings[id-1], + * and strings[] is a array of stbl->nstrings elements + */ + struct efi_string_info *strings; +}; + +struct efi_guid_data { + struct list_head link; + struct efi_hii_guid_package package; +}; + +struct efi_keyboard_layout_data { + struct list_head link; /* in package */ + struct list_head link_sys; /* in global list */ + struct efi_hii_keyboard_layout keyboard_layout; +}; + +struct efi_keyboard_package_data { + struct list_head link; /* in package_list */ + struct list_head keyboard_layout_list; +}; + +static void free_strings_table(struct efi_string_table *stbl) +{ + int i; + + for (i = 0; i < stbl->nstrings; i++) + free(stbl->strings[i].string); + free(stbl->strings); + free(stbl->language); + free(stbl); +} + +static void remove_strings_package(struct efi_hii_packagelist *hii) +{ + while (!list_empty(&hii->string_tables)) { + struct efi_string_table *stbl; + + stbl = list_first_entry(&hii->string_tables, + struct efi_string_table, link); + list_del(&stbl->link); + free_strings_table(stbl); + } +} + +static efi_status_t +add_strings_package(struct efi_hii_packagelist *hii, + struct efi_hii_strings_package *strings_package) +{ + struct efi_hii_string_block *block; + void *end; + u32 nstrings = 0, idx = 0; + struct efi_string_table *stbl = NULL; + efi_status_t ret; + + EFI_PRINT("header_size: %08x\n", + get_unaligned_le32(&strings_package->header_size)); + EFI_PRINT("string_info_offset: %08x\n", + get_unaligned_le32(&strings_package->string_info_offset)); + EFI_PRINT("language_name: %u\n", + get_unaligned_le16(&strings_package->language_name)); + EFI_PRINT("language: %s\n", strings_package->language); + + /* count # of string entries: */ + end = ((void *)strings_package) + + efi_hii_package_len(&strings_package->header); + block = ((void *)strings_package) + + get_unaligned_le32(&strings_package->string_info_offset); + + while ((void *)block < end) { + switch (block->block_type) { + case EFI_HII_SIBT_STRING_UCS2: { + struct efi_hii_sibt_string_ucs2_block *ucs2; + + ucs2 = (void *)block; + nstrings++; + block = efi_hii_sibt_string_ucs2_block_next(ucs2); + break; + } + case EFI_HII_SIBT_END: + block = end; + break; + default: + EFI_PRINT("unknown HII string block type: %02x\n", + block->block_type); + return EFI_INVALID_PARAMETER; + } + } + + stbl = calloc(sizeof(*stbl), 1); + if (!stbl) { + ret = EFI_OUT_OF_RESOURCES; + goto error; + } + stbl->strings = calloc(sizeof(stbl->strings[0]), nstrings); + if (!stbl->strings) { + ret = EFI_OUT_OF_RESOURCES; + goto error; + } + stbl->language_name = + get_unaligned_le16(&strings_package->language_name); + stbl->language = strdup((char *)strings_package->language); + if (!stbl->language) { + ret = EFI_OUT_OF_RESOURCES; + goto error; + } + stbl->nstrings = nstrings; + + /* and now parse string entries and populate efi_string_table */ + block = ((void *)strings_package) + + get_unaligned_le32(&strings_package->string_info_offset); + + while ((void *)block < end) { + switch (block->block_type) { + case EFI_HII_SIBT_STRING_UCS2: { + struct efi_hii_sibt_string_ucs2_block *ucs2; + + ucs2 = (void *)block; + EFI_PRINT("%4u: \"%ls\"\n", idx + 1, ucs2->string_text); + stbl->strings[idx].string = + u16_strdup(ucs2->string_text); + if (!stbl->strings[idx].string) { + ret = EFI_OUT_OF_RESOURCES; + goto error; + } + idx++; + /* FIXME: accessing u16 * here */ + block = efi_hii_sibt_string_ucs2_block_next(ucs2); + break; + } + case EFI_HII_SIBT_END: + goto out; + default: + EFI_PRINT("unknown HII string block type: %02x\n", + block->block_type); + ret = EFI_INVALID_PARAMETER; + goto error; + } + } + +out: + list_add(&stbl->link, &hii->string_tables); + if (hii->max_string_id < nstrings) + hii->max_string_id = nstrings; + + return EFI_SUCCESS; + +error: + if (stbl) { + free(stbl->language); + while (idx > 0) + free(stbl->strings[--idx].string); + free(stbl->strings); + } + free(stbl); + + return ret; +} + +static void remove_guid_package(struct efi_hii_packagelist *hii) +{ + struct efi_guid_data *data; + + while (!list_empty(&hii->guid_list)) { + data = list_first_entry(&hii->guid_list, + struct efi_guid_data, link); + list_del(&data->link); + free(data); + } +} + +static efi_status_t +add_guid_package(struct efi_hii_packagelist *hii, + struct efi_hii_guid_package *package) +{ + struct efi_guid_data *data; + + data = calloc(sizeof(*data), 1); + if (!data) + return EFI_OUT_OF_RESOURCES; + + /* TODO: we don't know any about data field */ + memcpy(&data->package, package, sizeof(*package)); + list_add_tail(&data->link, &hii->guid_list); + + return EFI_SUCCESS; +} + +static void free_keyboard_layouts(struct efi_keyboard_package_data *package) +{ + struct efi_keyboard_layout_data *layout_data; + + while (!list_empty(&package->keyboard_layout_list)) { + layout_data = list_first_entry(&package->keyboard_layout_list, + struct efi_keyboard_layout_data, + link); + list_del(&layout_data->link); + list_del(&layout_data->link_sys); + free(layout_data); + } +} + +static void remove_keyboard_package(struct efi_hii_packagelist *hii) +{ + struct efi_keyboard_package_data *package; + + while (!list_empty(&hii->keyboard_packages)) { + package = list_first_entry(&hii->keyboard_packages, + struct efi_keyboard_package_data, + link); + free_keyboard_layouts(package); + list_del(&package->link); + free(package); + } +} + +static efi_status_t +add_keyboard_package(struct efi_hii_packagelist *hii, + struct efi_hii_keyboard_package *keyboard_package) +{ + struct efi_keyboard_package_data *package_data; + struct efi_hii_keyboard_layout *layout; + struct efi_keyboard_layout_data *layout_data; + u16 layout_count, layout_length; + int i; + + package_data = malloc(sizeof(*package_data)); + if (!package_data) + return EFI_OUT_OF_RESOURCES; + INIT_LIST_HEAD(&package_data->link); + INIT_LIST_HEAD(&package_data->keyboard_layout_list); + + layout = &keyboard_package->layout[0]; + layout_count = get_unaligned_le16(&keyboard_package->layout_count); + for (i = 0; i < layout_count; i++) { + layout_length = get_unaligned_le16(&layout->layout_length); + layout_data = malloc(sizeof(*layout_data) + layout_length); + if (!layout_data) + goto out; + + memcpy(&layout_data->keyboard_layout, layout, layout_length); + list_add_tail(&layout_data->link, + &package_data->keyboard_layout_list); + list_add_tail(&layout_data->link_sys, + &efi_keyboard_layout_list); + + layout += layout_length; + } + + list_add_tail(&package_data->link, &hii->keyboard_packages); + + return EFI_SUCCESS; + +out: + free_keyboard_layouts(package_data); + free(package_data); + + return EFI_OUT_OF_RESOURCES; +} + +static struct efi_hii_packagelist *new_packagelist(void) +{ + struct efi_hii_packagelist *hii; + + hii = malloc(sizeof(*hii)); + list_add_tail(&hii->link, &efi_package_lists); + hii->max_string_id = 0; + INIT_LIST_HEAD(&hii->string_tables); + INIT_LIST_HEAD(&hii->guid_list); + INIT_LIST_HEAD(&hii->keyboard_packages); + + return hii; +} + +static void free_packagelist(struct efi_hii_packagelist *hii) +{ + remove_strings_package(hii); + remove_guid_package(hii); + remove_keyboard_package(hii); + + list_del(&hii->link); + free(hii); +} + +static efi_status_t +add_packages(struct efi_hii_packagelist *hii, + const struct efi_hii_package_list_header *package_list) +{ + struct efi_hii_package_header *package; + void *end; + efi_status_t ret = EFI_SUCCESS; + + end = ((void *)package_list) + + get_unaligned_le32(&package_list->package_length); + + EFI_PRINT("package_list: %pUs (%u)\n", &package_list->package_list_guid, + get_unaligned_le32(&package_list->package_length)); + + package = ((void *)package_list) + sizeof(*package_list); + while ((void *)package < end) { + EFI_PRINT("package=%p, package type=%x, length=%u\n", package, + efi_hii_package_type(package), + efi_hii_package_len(package)); + + switch (efi_hii_package_type(package)) { + case EFI_HII_PACKAGE_TYPE_GUID: + ret = add_guid_package(hii, + (struct efi_hii_guid_package *)package); + break; + case EFI_HII_PACKAGE_FORMS: + EFI_PRINT("Form package not supported\n"); + ret = EFI_INVALID_PARAMETER; + break; + case EFI_HII_PACKAGE_STRINGS: + ret = add_strings_package(hii, + (struct efi_hii_strings_package *)package); + break; + case EFI_HII_PACKAGE_FONTS: + EFI_PRINT("Font package not supported\n"); + ret = EFI_INVALID_PARAMETER; + break; + case EFI_HII_PACKAGE_IMAGES: + EFI_PRINT("Image package not supported\n"); + ret = EFI_INVALID_PARAMETER; + break; + case EFI_HII_PACKAGE_SIMPLE_FONTS: + EFI_PRINT("Simple font package not supported\n"); + ret = EFI_INVALID_PARAMETER; + break; + case EFI_HII_PACKAGE_DEVICE_PATH: + EFI_PRINT("Device path package not supported\n"); + ret = EFI_INVALID_PARAMETER; + break; + case EFI_HII_PACKAGE_KEYBOARD_LAYOUT: + ret = add_keyboard_package(hii, + (struct efi_hii_keyboard_package *)package); + break; + case EFI_HII_PACKAGE_ANIMATIONS: + EFI_PRINT("Animation package not supported\n"); + ret = EFI_INVALID_PARAMETER; + break; + case EFI_HII_PACKAGE_END: + goto out; + case EFI_HII_PACKAGE_TYPE_SYSTEM_BEGIN: + case EFI_HII_PACKAGE_TYPE_SYSTEM_END: + default: + break; + } + + if (ret != EFI_SUCCESS) + return ret; + + package = (void *)package + efi_hii_package_len(package); + } +out: + // TODO in theory there is some notifications that should be sent.. + return EFI_SUCCESS; +} + +/* + * EFI_HII_DATABASE_PROTOCOL + */ + +static efi_status_t EFIAPI +new_package_list(const struct efi_hii_database_protocol *this, + const struct efi_hii_package_list_header *package_list, + const efi_handle_t driver_handle, + efi_hii_handle_t *handle) +{ + struct efi_hii_packagelist *hii; + efi_status_t ret; + + EFI_ENTRY("%p, %p, %p, %p", this, package_list, driver_handle, handle); + + if (!package_list || !handle) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + hii = new_packagelist(); + if (!hii) + return EFI_EXIT(EFI_OUT_OF_RESOURCES); + + ret = add_packages(hii, package_list); + if (ret != EFI_SUCCESS) { + free_packagelist(hii); + return EFI_EXIT(ret); + } + + hii->driver_handle = driver_handle; + *handle = hii; + + return EFI_EXIT(EFI_SUCCESS); +} + +static efi_status_t EFIAPI +remove_package_list(const struct efi_hii_database_protocol *this, + efi_hii_handle_t handle) +{ + struct efi_hii_packagelist *hii = handle; + + EFI_ENTRY("%p, %p", this, handle); + + if (!handle || !efi_hii_packagelist_exists(handle)) + return EFI_EXIT(EFI_NOT_FOUND); + + free_packagelist(hii); + + return EFI_EXIT(EFI_SUCCESS); +} + +static efi_status_t EFIAPI +update_package_list(const struct efi_hii_database_protocol *this, + efi_hii_handle_t handle, + const struct efi_hii_package_list_header *package_list) +{ + struct efi_hii_packagelist *hii = handle; + struct efi_hii_package_header *package; + void *end; + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p, %p, %p", this, handle, package_list); + + if (!handle || !efi_hii_packagelist_exists(handle)) + return EFI_EXIT(EFI_NOT_FOUND); + + if (!package_list) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + EFI_PRINT("package_list: %pUs (%u)\n", &package_list->package_list_guid, + get_unaligned_le32(&package_list->package_length)); + + package = ((void *)package_list) + sizeof(*package_list); + end = ((void *)package_list) + + get_unaligned_le32(&package_list->package_length); + + while ((void *)package < end) { + EFI_PRINT("package=%p, package type=%x, length=%u\n", package, + efi_hii_package_type(package), + efi_hii_package_len(package)); + + switch (efi_hii_package_type(package)) { + case EFI_HII_PACKAGE_TYPE_GUID: + remove_guid_package(hii); + break; + case EFI_HII_PACKAGE_FORMS: + EFI_PRINT("Form package not supported\n"); + ret = EFI_INVALID_PARAMETER; + break; + case EFI_HII_PACKAGE_STRINGS: + remove_strings_package(hii); + break; + case EFI_HII_PACKAGE_FONTS: + EFI_PRINT("Font package not supported\n"); + ret = EFI_INVALID_PARAMETER; + break; + case EFI_HII_PACKAGE_IMAGES: + EFI_PRINT("Image package not supported\n"); + ret = EFI_INVALID_PARAMETER; + break; + case EFI_HII_PACKAGE_SIMPLE_FONTS: + EFI_PRINT("Simple font package not supported\n"); + ret = EFI_INVALID_PARAMETER; + break; + case EFI_HII_PACKAGE_DEVICE_PATH: + EFI_PRINT("Device path package not supported\n"); + ret = EFI_INVALID_PARAMETER; + break; + case EFI_HII_PACKAGE_KEYBOARD_LAYOUT: + remove_keyboard_package(hii); + break; + case EFI_HII_PACKAGE_ANIMATIONS: + EFI_PRINT("Animation package not supported\n"); + ret = EFI_INVALID_PARAMETER; + break; + case EFI_HII_PACKAGE_END: + goto out; + case EFI_HII_PACKAGE_TYPE_SYSTEM_BEGIN: + case EFI_HII_PACKAGE_TYPE_SYSTEM_END: + default: + break; + } + + /* TODO: already removed some packages */ + if (ret != EFI_SUCCESS) + return EFI_EXIT(ret); + + package = ((void *)package) + + efi_hii_package_len(package); + } +out: + ret = add_packages(hii, package_list); + + return EFI_EXIT(ret); +} + +static efi_status_t EFIAPI +list_package_lists(const struct efi_hii_database_protocol *this, + u8 package_type, + const efi_guid_t *package_guid, + efi_uintn_t *handle_buffer_length, + efi_hii_handle_t *handle) +{ + struct efi_hii_packagelist *hii = + (struct efi_hii_packagelist *)handle; + int package_cnt, package_max; + efi_status_t ret = EFI_NOT_FOUND; + + EFI_ENTRY("%p, %u, %pUs, %p, %p", this, package_type, package_guid, + handle_buffer_length, handle); + + if (!handle_buffer_length || + (*handle_buffer_length && !handle)) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + if ((package_type != EFI_HII_PACKAGE_TYPE_GUID && package_guid) || + (package_type == EFI_HII_PACKAGE_TYPE_GUID && !package_guid)) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + EFI_PRINT("package type=%x, guid=%pUs, length=%zu\n", (int)package_type, + package_guid, *handle_buffer_length); + + package_cnt = 0; + package_max = *handle_buffer_length / sizeof(*handle); + list_for_each_entry(hii, &efi_package_lists, link) { + switch (package_type) { + case EFI_HII_PACKAGE_TYPE_ALL: + break; + case EFI_HII_PACKAGE_TYPE_GUID: + if (!list_empty(&hii->guid_list)) + break; + continue; + case EFI_HII_PACKAGE_STRINGS: + if (!list_empty(&hii->string_tables)) + break; + continue; + case EFI_HII_PACKAGE_KEYBOARD_LAYOUT: + if (!list_empty(&hii->keyboard_packages)) + break; + continue; + default: + continue; + } + + package_cnt++; + if (package_cnt <= package_max) { + *handle++ = hii; + ret = EFI_SUCCESS; + } else { + ret = EFI_BUFFER_TOO_SMALL; + } + } + *handle_buffer_length = package_cnt * sizeof(*handle); +out: + return EFI_EXIT(ret); +} + +static efi_status_t EFIAPI +export_package_lists(const struct efi_hii_database_protocol *this, + efi_hii_handle_t handle, + efi_uintn_t *buffer_size, + struct efi_hii_package_list_header *buffer) +{ + EFI_ENTRY("%p, %p, %p, %p", this, handle, buffer_size, buffer); + + if (!buffer_size || !buffer) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + return EFI_EXIT(EFI_NOT_FOUND); +} + +static efi_status_t EFIAPI +register_package_notify(const struct efi_hii_database_protocol *this, + u8 package_type, + const efi_guid_t *package_guid, + const void *package_notify_fn, + efi_uintn_t notify_type, + efi_handle_t *notify_handle) +{ + EFI_ENTRY("%p, %u, %pUs, %p, %zu, %p", this, package_type, + package_guid, package_notify_fn, notify_type, + notify_handle); + + if (!notify_handle) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + if ((package_type != EFI_HII_PACKAGE_TYPE_GUID && package_guid) || + (package_type == EFI_HII_PACKAGE_TYPE_GUID && !package_guid)) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + return EFI_EXIT(EFI_OUT_OF_RESOURCES); +} + +static efi_status_t EFIAPI +unregister_package_notify(const struct efi_hii_database_protocol *this, + efi_handle_t notification_handle) +{ + EFI_ENTRY("%p, %p", this, notification_handle); + + return EFI_EXIT(EFI_NOT_FOUND); +} + +static efi_status_t EFIAPI +find_keyboard_layouts(const struct efi_hii_database_protocol *this, + u16 *key_guid_buffer_length, + efi_guid_t *key_guid_buffer) +{ + struct efi_keyboard_layout_data *layout_data; + int package_cnt, package_max; + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p, %p, %p", this, key_guid_buffer_length, key_guid_buffer); + + if (!key_guid_buffer_length || + (*key_guid_buffer_length && !key_guid_buffer)) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + package_cnt = 0; + package_max = *key_guid_buffer_length / sizeof(*key_guid_buffer); + list_for_each_entry(layout_data, &efi_keyboard_layout_list, link_sys) { + package_cnt++; + if (package_cnt <= package_max) + memcpy(key_guid_buffer++, + &layout_data->keyboard_layout.guid, + sizeof(*key_guid_buffer)); + else + ret = EFI_BUFFER_TOO_SMALL; + } + *key_guid_buffer_length = package_cnt * sizeof(*key_guid_buffer); + + return EFI_EXIT(ret); +} + +static efi_status_t EFIAPI +get_keyboard_layout(const struct efi_hii_database_protocol *this, + efi_guid_t *key_guid, + u16 *keyboard_layout_length, + struct efi_hii_keyboard_layout *keyboard_layout) +{ + struct efi_keyboard_layout_data *layout_data; + u16 layout_length; + + EFI_ENTRY("%p, %pUs, %p, %p", this, key_guid, keyboard_layout_length, + keyboard_layout); + + if (!keyboard_layout_length || + (*keyboard_layout_length && !keyboard_layout)) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + /* TODO: no notion of current keyboard layout */ + if (!key_guid) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + list_for_each_entry(layout_data, &efi_keyboard_layout_list, link_sys) { + if (!guidcmp(&layout_data->keyboard_layout.guid, key_guid)) + goto found; + } + + return EFI_EXIT(EFI_NOT_FOUND); + +found: + layout_length = + get_unaligned_le16(&layout_data->keyboard_layout.layout_length); + if (*keyboard_layout_length < layout_length) { + *keyboard_layout_length = layout_length; + return EFI_EXIT(EFI_BUFFER_TOO_SMALL); + } + + memcpy(keyboard_layout, &layout_data->keyboard_layout, layout_length); + + return EFI_EXIT(EFI_SUCCESS); +} + +static efi_status_t EFIAPI +set_keyboard_layout(const struct efi_hii_database_protocol *this, + efi_guid_t *key_guid) +{ + EFI_ENTRY("%p, %pUs", this, key_guid); + + if (!key_guid) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + return EFI_EXIT(EFI_NOT_FOUND); +} + +static efi_status_t EFIAPI +get_package_list_handle(const struct efi_hii_database_protocol *this, + efi_hii_handle_t package_list_handle, + efi_handle_t *driver_handle) +{ + struct efi_hii_packagelist *hii; + + EFI_ENTRY("%p, %p, %p", this, package_list_handle, driver_handle); + + if (!driver_handle) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + list_for_each_entry(hii, &efi_package_lists, link) { + if (hii == package_list_handle) { + *driver_handle = hii->driver_handle; + return EFI_EXIT(EFI_SUCCESS); + } + } + + return EFI_EXIT(EFI_INVALID_PARAMETER); +} + +const struct efi_hii_database_protocol efi_hii_database = { + .new_package_list = new_package_list, + .remove_package_list = remove_package_list, + .update_package_list = update_package_list, + .list_package_lists = list_package_lists, + .export_package_lists = export_package_lists, + .register_package_notify = register_package_notify, + .unregister_package_notify = unregister_package_notify, + .find_keyboard_layouts = find_keyboard_layouts, + .get_keyboard_layout = get_keyboard_layout, + .set_keyboard_layout = set_keyboard_layout, + .get_package_list_handle = get_package_list_handle +}; + +/* + * EFI_HII_STRING_PROTOCOL + */ + +static bool language_match(char *language, char *languages) +{ + size_t n; + + n = strlen(language); + /* match primary language? */ + if (!strncasecmp(language, languages, n) && + (languages[n] == ';' || languages[n] == '\0')) + return true; + + return false; +} + +static efi_status_t EFIAPI +new_string(const struct efi_hii_string_protocol *this, + efi_hii_handle_t package_list, + efi_string_id_t *string_id, + const u8 *language, + const u16 *language_name, + const efi_string_t string, + const struct efi_font_info *string_font_info) +{ + struct efi_hii_packagelist *hii = package_list; + struct efi_string_table *stbl; + + EFI_ENTRY("%p, %p, %p, \"%s\", %p, \"%ls\", %p", this, package_list, + string_id, language, language_name, string, + string_font_info); + + if (!package_list || !efi_hii_packagelist_exists(package_list)) + return EFI_EXIT(EFI_NOT_FOUND); + + if (!string_id || !language || !string) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + list_for_each_entry(stbl, &hii->string_tables, link) { + if (language_match((char *)language, stbl->language)) { + efi_string_id_t new_id; + void *buf; + efi_string_t str; + + new_id = ++hii->max_string_id; + if (stbl->nstrings < new_id) { + buf = realloc(stbl->strings, + sizeof(stbl->strings[0]) + * new_id); + if (!buf) + return EFI_EXIT(EFI_OUT_OF_RESOURCES); + + memset(&stbl->strings[stbl->nstrings], 0, + (new_id - stbl->nstrings) + * sizeof(stbl->strings[0])); + stbl->strings = buf; + stbl->nstrings = new_id; + } + + str = u16_strdup(string); + if (!str) + return EFI_EXIT(EFI_OUT_OF_RESOURCES); + + stbl->strings[new_id - 1].string = str; + *string_id = new_id; + + return EFI_EXIT(EFI_SUCCESS); + } + } + + return EFI_EXIT(EFI_NOT_FOUND); +} + +static efi_status_t EFIAPI +get_string(const struct efi_hii_string_protocol *this, + const u8 *language, + efi_hii_handle_t package_list, + efi_string_id_t string_id, + efi_string_t string, + efi_uintn_t *string_size, + struct efi_font_info **string_font_info) +{ + struct efi_hii_packagelist *hii = package_list; + struct efi_string_table *stbl; + + EFI_ENTRY("%p, \"%s\", %p, %u, %p, %p, %p", this, language, + package_list, string_id, string, string_size, + string_font_info); + + if (!package_list || !efi_hii_packagelist_exists(package_list)) + return EFI_EXIT(EFI_NOT_FOUND); + + list_for_each_entry(stbl, &hii->string_tables, link) { + if (language_match((char *)language, stbl->language)) { + efi_string_t str; + size_t len; + + if (stbl->nstrings < string_id) + return EFI_EXIT(EFI_NOT_FOUND); + + str = stbl->strings[string_id - 1].string; + if (str) { + len = u16_strsize(str); + if (*string_size < len) { + *string_size = len; + + return EFI_EXIT(EFI_BUFFER_TOO_SMALL); + } + memcpy(string, str, len); + *string_size = len; + } else { + return EFI_EXIT(EFI_NOT_FOUND); + } + + return EFI_EXIT(EFI_SUCCESS); + } + } + + return EFI_EXIT(EFI_NOT_FOUND); +} + +static efi_status_t EFIAPI +set_string(const struct efi_hii_string_protocol *this, + efi_hii_handle_t package_list, + efi_string_id_t string_id, + const u8 *language, + const efi_string_t string, + const struct efi_font_info *string_font_info) +{ + struct efi_hii_packagelist *hii = package_list; + struct efi_string_table *stbl; + + EFI_ENTRY("%p, %p, %u, \"%s\", \"%ls\", %p", this, package_list, + string_id, language, string, string_font_info); + + if (!package_list || !efi_hii_packagelist_exists(package_list)) + return EFI_EXIT(EFI_NOT_FOUND); + + if (string_id > hii->max_string_id) + return EFI_EXIT(EFI_NOT_FOUND); + + if (!string || !language) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + list_for_each_entry(stbl, &hii->string_tables, link) { + if (language_match((char *)language, stbl->language)) { + efi_string_t str; + + if (hii->max_string_id < string_id) + return EFI_EXIT(EFI_NOT_FOUND); + + if (stbl->nstrings < string_id) { + void *buf; + + buf = realloc(stbl->strings, + string_id + * sizeof(stbl->strings[0])); + if (!buf) + return EFI_EXIT(EFI_OUT_OF_RESOURCES); + + memset(&stbl->strings[string_id - 1], 0, + (string_id - stbl->nstrings) + * sizeof(stbl->strings[0])); + stbl->strings = buf; + } + + str = u16_strdup(string); + if (!str) + return EFI_EXIT(EFI_OUT_OF_RESOURCES); + + free(stbl->strings[string_id - 1].string); + stbl->strings[string_id - 1].string = str; + + return EFI_EXIT(EFI_SUCCESS); + } + } + + return EFI_EXIT(EFI_NOT_FOUND); +} + +static efi_status_t EFIAPI +get_languages(const struct efi_hii_string_protocol *this, + efi_hii_handle_t package_list, + u8 *languages, + efi_uintn_t *languages_size) +{ + struct efi_hii_packagelist *hii = package_list; + struct efi_string_table *stbl; + size_t len = 0; + char *p; + + EFI_ENTRY("%p, %p, %p, %p", this, package_list, languages, + languages_size); + + if (!package_list || !efi_hii_packagelist_exists(package_list)) + return EFI_EXIT(EFI_NOT_FOUND); + + if (!languages_size || + (*languages_size && !languages)) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + /* figure out required size: */ + list_for_each_entry(stbl, &hii->string_tables, link) { + len += strlen((char *)stbl->language) + 1; + } + + if (*languages_size < len) { + *languages_size = len; + + return EFI_EXIT(EFI_BUFFER_TOO_SMALL); + } + + p = (char *)languages; + list_for_each_entry(stbl, &hii->string_tables, link) { + if (p != (char *)languages) + *p++ = ';'; + strcpy(p, stbl->language); + p += strlen((char *)stbl->language); + } + *p = '\0'; + + EFI_PRINT("languages: %s\n", languages); + + return EFI_EXIT(EFI_SUCCESS); +} + +static efi_status_t EFIAPI +get_secondary_languages(const struct efi_hii_string_protocol *this, + efi_hii_handle_t package_list, + const u8 *primary_language, + u8 *secondary_languages, + efi_uintn_t *secondary_languages_size) +{ + struct efi_hii_packagelist *hii = package_list; + struct efi_string_table *stbl; + bool found = false; + + EFI_ENTRY("%p, %p, \"%s\", %p, %p", this, package_list, + primary_language, secondary_languages, + secondary_languages_size); + + if (!package_list || !efi_hii_packagelist_exists(package_list)) + return EFI_EXIT(EFI_NOT_FOUND); + + if (!secondary_languages_size || + (*secondary_languages_size && !secondary_languages)) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + list_for_each_entry(stbl, &hii->string_tables, link) { + if (language_match((char *)primary_language, stbl->language)) { + found = true; + break; + } + } + if (!found) + return EFI_EXIT(EFI_INVALID_LANGUAGE); + + /* + * TODO: What is secondary language? + * *secondary_languages = '\0'; + * *secondary_languages_size = 0; + */ + + return EFI_EXIT(EFI_NOT_FOUND); +} + +const struct efi_hii_string_protocol efi_hii_string = { + .new_string = new_string, + .get_string = get_string, + .set_string = set_string, + .get_languages = get_languages, + .get_secondary_languages = get_secondary_languages +}; diff --git a/lib/efi_loader/efi_hii_config.c b/lib/efi_loader/efi_hii_config.c new file mode 100644 index 00000000000..37d8c6629e2 --- /dev/null +++ b/lib/efi_loader/efi_hii_config.c @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI Human Interface Infrastructure ... Configuration + * + * Copyright (c) 2017 Leif Lindholm + * Copyright (c) 2018 AKASHI Takahiro, Linaro Limited + * + * As this is still a non-working stub and the protocol is neither required + * by the EFI shell nor by the UEFI SCT this module has been removed from + * the Makefile. + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <efi_loader.h> + +const efi_guid_t efi_guid_hii_config_routing_protocol + = EFI_HII_CONFIG_ROUTING_PROTOCOL_GUID; +const efi_guid_t efi_guid_hii_config_access_protocol + = EFI_HII_CONFIG_ACCESS_PROTOCOL_GUID; + +/* + * EFI_HII_CONFIG_ROUTING_PROTOCOL + */ + +static efi_status_t EFIAPI +extract_config(const struct efi_hii_config_routing_protocol *this, + const efi_string_t request, + efi_string_t *progress, + efi_string_t *results) +{ + EFI_ENTRY("%p, \"%ls\", %p, %p", this, request, progress, results); + + return EFI_EXIT(EFI_OUT_OF_RESOURCES); +} + +static efi_status_t EFIAPI +export_config(const struct efi_hii_config_routing_protocol *this, + efi_string_t *results) +{ + EFI_ENTRY("%p, %p", this, results); + + return EFI_EXIT(EFI_OUT_OF_RESOURCES); +} + +static efi_status_t EFIAPI +route_config(const struct efi_hii_config_routing_protocol *this, + const efi_string_t configuration, + efi_string_t *progress) +{ + EFI_ENTRY("%p, \"%ls\", %p", this, configuration, progress); + + return EFI_EXIT(EFI_OUT_OF_RESOURCES); +} + +static efi_status_t EFIAPI +block_to_config(const struct efi_hii_config_routing_protocol *this, + const efi_string_t config_request, + const u8 *block, + const efi_uintn_t block_size, + efi_string_t *config, + efi_string_t *progress) +{ + EFI_ENTRY("%p, \"%ls\", %p, %zu, %p, %p", this, config_request, + block, block_size, config, progress); + + return EFI_EXIT(EFI_OUT_OF_RESOURCES); +} + +static efi_status_t EFIAPI +config_to_block(const struct efi_hii_config_routing_protocol *this, + const efi_string_t config_resp, + const u8 *block, + const efi_uintn_t *block_size, + efi_string_t *progress) +{ + EFI_ENTRY("%p, \"%ls\", %p, %p, %p", this, config_resp, + block, block_size, progress); + + return EFI_EXIT(EFI_OUT_OF_RESOURCES); +} + +static efi_status_t EFIAPI +get_alt_config(const struct efi_hii_config_routing_protocol *this, + const efi_string_t config_resp, + const efi_guid_t *guid, + const efi_string_t name, + const struct efi_device_path *device_path, + const efi_string_t alt_cfg_id, + efi_string_t *alt_cfg_resp) +{ + EFI_ENTRY("%p, \"%ls\", %pUs, \"%ls\", %p, \"%ls\", %p", + this, config_resp, guid, name, device_path, + alt_cfg_id, alt_cfg_resp); + + return EFI_EXIT(EFI_OUT_OF_RESOURCES); +} + +/* + * EFI_HII_ACCESS_PROTOCOL + */ + +efi_status_t EFIAPI +extract_config_access(const struct efi_hii_config_access_protocol *this, + const efi_string_t request, + efi_string_t *progress, + efi_string_t *results) +{ + EFI_ENTRY("%p, \"%ls\", %p, %p", this, request, progress, results); + + return EFI_EXIT(EFI_OUT_OF_RESOURCES); +}; + +efi_status_t EFIAPI +route_config_access(const struct efi_hii_config_access_protocol *this, + const efi_string_t configuration, + efi_string_t *progress) +{ + EFI_ENTRY("%p, \"%ls\", %p", this, configuration, progress); + + return EFI_EXIT(EFI_OUT_OF_RESOURCES); +}; + +efi_status_t EFIAPI +form_callback(const struct efi_hii_config_access_protocol *this, + efi_browser_action_t action, + efi_question_id_t question_id, + u8 type, + union efi_ifr_type_value *value, + efi_browser_action_request_t *action_request) +{ + EFI_ENTRY("%p, 0x%zx, 0x%x, 0x%x, %p, %p", this, action, + question_id, type, value, action_request); + + return EFI_EXIT(EFI_DEVICE_ERROR); +}; + +const struct efi_hii_config_routing_protocol efi_hii_config_routing = { + .extract_config = extract_config, + .export_config = export_config, + .route_config = route_config, + .block_to_config = block_to_config, + .config_to_block = config_to_block, + .get_alt_config = get_alt_config +}; + +const struct efi_hii_config_access_protocol efi_hii_config_access = { + .extract_config_access = extract_config_access, + .route_config_access = route_config_access, + .form_callback = form_callback +}; diff --git a/lib/efi_loader/efi_http.c b/lib/efi_loader/efi_http.c new file mode 100644 index 00000000000..189317fe2d2 --- /dev/null +++ b/lib/efi_loader/efi_http.c @@ -0,0 +1,552 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * An HTTP driver + * + * HTTP_PROTOCOL + * HTTP_SERVICE_BINDING_PROTOCOL + * IP4_CONFIG2_PROTOCOL + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <charset.h> +#include <efi_loader.h> +#include <image.h> +#include <malloc.h> +#include <mapmem.h> +#include <net.h> + +static const efi_guid_t efi_http_service_binding_guid = EFI_HTTP_SERVICE_BINDING_PROTOCOL_GUID; +static const efi_guid_t efi_http_guid = EFI_HTTP_PROTOCOL_GUID; + +/** + * struct efi_http_instance - EFI object representing an HTTP protocol instance + * + * @http: EFI_HTTP_PROTOCOL interface + * @handle: handle to efi object + * @configured: configuration status + * @http_load_addr: data buffer + * @file_size: size of data + * @current_offset: offset in data buffer + * @status_code: HTTP status code + * @num_headers: number of received headers + * @headers: array of headers + * @headers_buffer: raw buffer with headers + */ +struct efi_http_instance { + struct efi_http_protocol http; + efi_handle_t handle; + struct efi_service_binding_protocol *parent; + bool configured; + void *http_load_addr; + ulong file_size; + ulong current_offset; + u32 status_code; + ulong num_headers; + struct http_header headers[MAX_HTTP_HEADERS]; + char headers_buffer[MAX_HTTP_HEADERS_SIZE]; +}; + +static int num_instances; + +/* + * efi_u32_to_httpstatus() - convert u32 to status + * + */ +enum efi_http_status_code efi_u32_to_httpstatus(u32 status); + +/* + * efi_http_send_data() - sends data to client + * + * + * @client_buffer: client buffer to send data to + * @client_buffer_size: size of the client buffer + * @inst: HTTP instance for which to send data + * + * Return: status code + */ +static efi_status_t efi_http_send_data(void *client_buffer, + efi_uintn_t *client_buffer_size, + struct efi_http_instance *inst) +{ + efi_status_t ret = EFI_SUCCESS; + ulong total_size, transfer_size; + uchar *ptr; + + // Amount of data left; + total_size = inst->file_size; + transfer_size = total_size - inst->current_offset; + debug("efi_http: sending data to client, total size %lu\n", total_size); + // Amount of data the client is willing to receive + if (transfer_size > *client_buffer_size) + transfer_size = *client_buffer_size; + else + *client_buffer_size = transfer_size; + debug("efi_http: transfer size %lu\n", transfer_size); + if (!transfer_size) // Ok, only headers + goto out; + + if (!client_buffer) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + // Send data + ptr = (uchar *)inst->http_load_addr + inst->current_offset; + memcpy(client_buffer, ptr, transfer_size); + + inst->current_offset += transfer_size; + + // Whole file served, clean the buffer: + if (inst->current_offset == inst->file_size) { + efi_free_pool(inst->http_load_addr); + inst->http_load_addr = NULL; + inst->current_offset = 0; + inst->file_size = 0; + } + +out: + return ret; +} + +/* EFI_HTTP_PROTOCOL */ + +/* + * efi_http_get_mode_data() - Gets the current operational status. + * + * This function implements EFI_HTTP_PROTOCOL.GetModeData(). + * See the Unified Extensible Firmware Interface + * (UEFI) specification for details. + * + * @this: pointer to the protocol instance + * @data: pointer to the buffer for operational parameters + * of this HTTP instance + * Return: status code + */ +static efi_status_t EFIAPI efi_http_get_mode_data(struct efi_http_protocol *this, + struct efi_http_config_data *data) +{ + EFI_ENTRY("%p, %p", this, data); + + efi_status_t ret = EFI_UNSUPPORTED; + + return EFI_EXIT(ret); +} + +/* + * efi_http_configure() - Initializes operational status for this + * EFI HTTP instance. + * + * This function implements EFI_HTTP_PROTOCOL.Configure(). + * See the Unified Extensible Firmware Interface + * (UEFI) specification for details. + * + * @this: pointer to the protocol instance + * @data: pointer to the buffer for operational parameters of + * this HTTP instance + * Return: status code + */ +static efi_status_t EFIAPI efi_http_configure(struct efi_http_protocol *this, + struct efi_http_config_data *data) +{ + EFI_ENTRY("%p, %p", this, data); + + efi_status_t ret = EFI_SUCCESS; + enum efi_http_version http_version; + struct efi_httpv4_access_point *ipv4_node; + struct efi_http_instance *http_instance; + + if (!this) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + http_instance = (struct efi_http_instance *)this; + + if (!data) { + efi_free_pool(http_instance->http_load_addr); + http_instance->http_load_addr = NULL; + http_instance->current_offset = 0; + http_instance->configured = false; + + goto out; + } + + if (http_instance->configured) { + ret = EFI_ALREADY_STARTED; + goto out; + } + + http_version = data->http_version; + ipv4_node = data->access_point.ipv4_node; + + if ((http_version != HTTPVERSION10 && + http_version != HTTPVERSION11) || + data->is_ipv6 || !ipv4_node) { /* Only support ipv4 */ + ret = EFI_UNSUPPORTED; + goto out; + } + + if (!ipv4_node->use_default_address) { + efi_net_set_addr((struct efi_ipv4_address *)&ipv4_node->local_address, + (struct efi_ipv4_address *)&ipv4_node->local_subnet, NULL, NULL); + } + + http_instance->current_offset = 0; + http_instance->configured = true; + +out: + return EFI_EXIT(ret); +} + +/* + * efi_http_request() - Queues an HTTP request to this HTTP instance + * + * This function implements EFI_HTTP_PROTOCOL.Request(). + * See the Unified Extensible Firmware Interface + * (UEFI) specification for details. + * + * @this: pointer to the protocol instance + * @token: pointer to storage containing HTTP request token + * Return: status code + */ +static efi_status_t EFIAPI efi_http_request(struct efi_http_protocol *this, + struct efi_http_token *token) +{ + EFI_ENTRY("%p, %p", this, token); + + efi_status_t ret = EFI_SUCCESS; + u8 *tmp; + u8 url_8[1024]; + u16 *url_16; + enum efi_http_method current_method; + struct efi_http_instance *http_instance; + + if (!token || !this || !token->message || + !token->message->data.request) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + http_instance = (struct efi_http_instance *)this; + + if (!http_instance->configured) { + ret = EFI_NOT_STARTED; + goto out; + } + + current_method = token->message->data.request->method; + url_16 = token->message->data.request->url; + + /* Parse URL. It comes in UCS-2 encoding and follows RFC3986 */ + tmp = url_8; + utf16_utf8_strncpy((char **)&tmp, url_16, 1024); + + ret = efi_net_do_request(url_8, current_method, &http_instance->http_load_addr, + &http_instance->status_code, &http_instance->file_size, + http_instance->headers_buffer, http_instance->parent); + if (ret != EFI_SUCCESS) + goto out; + + // We have a successful request + efi_net_parse_headers(&http_instance->num_headers, http_instance->headers); + http_instance->current_offset = 0; + token->status = EFI_SUCCESS; + goto out_signal; + +out_signal: + efi_signal_event(token->event); +out: + return EFI_EXIT(ret); +} + +/* + * efi_http_cancel() - Abort an asynchronous HTTP request or response token + * + * This function implements EFI_HTTP_PROTOCOL.Cancel(). + * See the Unified Extensible Firmware Interface + * (UEFI) specification for details. + * + * @this: pointer to the protocol instance + * @token: pointer to storage containing HTTP request token + * Return: status code + */ +static efi_status_t EFIAPI efi_http_cancel(struct efi_http_protocol *this, + struct efi_http_token *token) +{ + EFI_ENTRY("%p, %p", this, token); + + efi_status_t ret = EFI_UNSUPPORTED; + + return EFI_EXIT(ret); +} + +/* + * efi_http_response() - Queues an HTTP response to this HTTP instance + * + * This function implements EFI_HTTP_PROTOCOL.Response(). + * See the Unified Extensible Firmware Interface + * (UEFI) specification for details. + * + * @this: pointer to the protocol instance + * @token: pointer to storage containing HTTP request token + * Return: status code + */ +static efi_status_t EFIAPI efi_http_response(struct efi_http_protocol *this, + struct efi_http_token *token) +{ + EFI_ENTRY("%p, %p", this, token); + + efi_status_t ret = EFI_SUCCESS; + struct efi_http_instance *http_instance; + struct efi_http_header **client_headers; + struct efi_http_response_data *response; + + if (!token || !this || !token->message) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + http_instance = (struct efi_http_instance *)this; + + // Set HTTP status code + if (token->message->data.response) { // TODO extra check, see spec. + response = token->message->data.response; + response->status_code = efi_u32_to_httpstatus(http_instance->status_code); + } + + client_headers = &token->message->headers; + + ret = efi_allocate_pool(EFI_BOOT_SERVICES_DATA, + (http_instance->num_headers) * sizeof(struct efi_http_header), + (void **)client_headers); // This is deallocated by the client. + if (ret != EFI_SUCCESS) + goto out_bad_signal; + + // Send headers + token->message->header_count = http_instance->num_headers; + for (int i = 0; i < http_instance->num_headers; i++) { + (*client_headers)[i].field_name = http_instance->headers[i].name; + (*client_headers)[i].field_value = http_instance->headers[i].value; + } + + ret = efi_http_send_data(token->message->body, &token->message->body_length, http_instance); + if (ret != EFI_SUCCESS) + goto out_bad_signal; + + token->status = EFI_SUCCESS; + goto out_signal; + +out_bad_signal: + token->status = EFI_ABORTED; +out_signal: + efi_signal_event(token->event); +out: + return EFI_EXIT(ret); +} + +/* + * efi_http_poll() - Polls for incoming data packets and processes outgoing data packets + * + * This function implements EFI_HTTP_PROTOCOL.Poll(). + * See the Unified Extensible Firmware Interface + * (UEFI) specification for details. + * + * @this: pointer to the protocol instance + * @token: pointer to storage containing HTTP request token + * Return: status code + */ +static efi_status_t EFIAPI efi_http_poll(struct efi_http_protocol *this) +{ + EFI_ENTRY("%p", this); + + efi_status_t ret = EFI_UNSUPPORTED; + + return EFI_EXIT(ret); +} + +/* EFI_HTTP_SERVICE_BINDING_PROTOCOL */ + +/* + * efi_http_service_binding_create_child() - Creates a child handle + * and installs a protocol + * + * This function implements EFI_HTTP_SERVICE_BINDING.CreateChild(). + * See the Unified Extensible Firmware Interface + * (UEFI) specification for details. + * + * @this: pointer to the protocol instance + * @child_handle: pointer to child handle + * Return: status code + */ +static efi_status_t EFIAPI efi_http_service_binding_create_child( + struct efi_service_binding_protocol *this, + efi_handle_t *child_handle) +{ + EFI_ENTRY("%p, %p", this, child_handle); + + efi_status_t ret = EFI_SUCCESS; + struct efi_http_instance *new_instance; + + if (!child_handle) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + new_instance = calloc(1, sizeof(struct efi_http_instance)); + if (!new_instance) { + ret = EFI_OUT_OF_RESOURCES; + goto failure_to_add_protocol; + } + + if (*child_handle) { + new_instance->handle = *child_handle; + goto install; + } + + new_instance->handle = calloc(1, sizeof(struct efi_object)); + if (!new_instance->handle) { + efi_free_pool((void *)new_instance); + ret = EFI_OUT_OF_RESOURCES; + goto failure_to_add_protocol; + } + + new_instance->parent = this; + efi_add_handle(new_instance->handle); + *child_handle = new_instance->handle; + +install: + ret = efi_add_protocol(new_instance->handle, &efi_http_guid, + &new_instance->http); + if (ret != EFI_SUCCESS) + goto failure_to_add_protocol; + + new_instance->http.get_mode_data = efi_http_get_mode_data; + new_instance->http.configure = efi_http_configure; + new_instance->http.request = efi_http_request; + new_instance->http.cancel = efi_http_cancel; + new_instance->http.response = efi_http_response; + new_instance->http.poll = efi_http_poll; + ++num_instances; + + return EFI_EXIT(EFI_SUCCESS); + +failure_to_add_protocol: + return EFI_EXIT(ret); +} + +/* + * efi_http_service_binding_destroy_child() - Destroys a child handle with + * a protocol installed on it + * + * This function implements EFI_HTTP_SERVICE_BINDING.DestroyChild(). + * See the Unified Extensible Firmware Interface + * (UEFI) specification for details. + * + * @this: pointer to the protocol instance + * @child_handle: child handle + * Return: status code + */ +static efi_status_t EFIAPI efi_http_service_binding_destroy_child( + struct efi_service_binding_protocol *this, + efi_handle_t child_handle) +{ + EFI_ENTRY("%p, %p", this, child_handle); + efi_status_t ret = EFI_SUCCESS; + struct efi_http_instance *http_instance; + struct efi_handler *phandler; + void *protocol_interface; + + if (num_instances == 0) + return EFI_EXIT(EFI_NOT_FOUND); + + if (!child_handle) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + efi_search_protocol(child_handle, &efi_http_guid, &phandler); + + if (phandler) + protocol_interface = phandler->protocol_interface; + + ret = efi_delete_handle(child_handle); + if (ret != EFI_SUCCESS) + return EFI_EXIT(ret); + + http_instance = (struct efi_http_instance *)protocol_interface; + efi_free_pool(http_instance->http_load_addr); + http_instance->http_load_addr = NULL; + + free(protocol_interface); + + num_instances--; + + return EFI_EXIT(EFI_SUCCESS); +} + +/** + * efi_http_register() - register the http protocol + * + */ +efi_status_t efi_http_register(const efi_handle_t handle, + struct efi_service_binding_protocol *http_service_binding) +{ + efi_status_t r = EFI_SUCCESS; + + r = efi_add_protocol(handle, &efi_http_service_binding_guid, + http_service_binding); + if (r != EFI_SUCCESS) + goto failure_to_add_protocol; + + http_service_binding->create_child = efi_http_service_binding_create_child; + http_service_binding->destroy_child = efi_http_service_binding_destroy_child; + + return EFI_SUCCESS; +failure_to_add_protocol: + return r; +} + +enum efi_http_status_code efi_u32_to_httpstatus(u32 status) +{ + switch (status) { + case 100: return HTTP_STATUS_100_CONTINUE; + case 101: return HTTP_STATUS_101_SWITCHING_PROTOCOLS; + case 200: return HTTP_STATUS_200_OK; + case 201: return HTTP_STATUS_201_CREATED; + case 202: return HTTP_STATUS_202_ACCEPTED; + case 203: return HTTP_STATUS_203_NON_AUTHORITATIVE_INFORMATION; + case 204: return HTTP_STATUS_204_NO_CONTENT; + case 205: return HTTP_STATUS_205_RESET_CONTENT; + case 206: return HTTP_STATUS_206_PARTIAL_CONTENT; + case 300: return HTTP_STATUS_300_MULTIPLE_CHOICES; + case 301: return HTTP_STATUS_301_MOVED_PERMANENTLY; + case 302: return HTTP_STATUS_302_FOUND; + case 303: return HTTP_STATUS_303_SEE_OTHER; + case 304: return HTTP_STATUS_304_NOT_MODIFIED; + case 305: return HTTP_STATUS_305_USE_PROXY; + case 307: return HTTP_STATUS_307_TEMPORARY_REDIRECT; + case 400: return HTTP_STATUS_400_BAD_REQUEST; + case 401: return HTTP_STATUS_401_UNAUTHORIZED; + case 402: return HTTP_STATUS_402_PAYMENT_REQUIRED; + case 403: return HTTP_STATUS_403_FORBIDDEN; + case 404: return HTTP_STATUS_404_NOT_FOUND; + case 405: return HTTP_STATUS_405_METHOD_NOT_ALLOWED; + case 406: return HTTP_STATUS_406_NOT_ACCEPTABLE; + case 407: return HTTP_STATUS_407_PROXY_AUTHENTICATION_REQUIRED; + case 408: return HTTP_STATUS_408_REQUEST_TIME_OUT; + case 409: return HTTP_STATUS_409_CONFLICT; + case 410: return HTTP_STATUS_410_GONE; + case 411: return HTTP_STATUS_411_LENGTH_REQUIRED; + case 412: return HTTP_STATUS_412_PRECONDITION_FAILED; + case 413: return HTTP_STATUS_413_REQUEST_ENTITY_TOO_LARGE; + case 414: return HTTP_STATUS_414_REQUEST_URI_TOO_LARGE; + case 415: return HTTP_STATUS_415_UNSUPPORTED_MEDIA_TYPE; + case 416: return HTTP_STATUS_416_REQUESTED_RANGE_NOT_SATISFIED; + case 417: return HTTP_STATUS_417_EXPECTATION_FAILED; + case 500: return HTTP_STATUS_500_INTERNAL_SERVER_ERROR; + case 501: return HTTP_STATUS_501_NOT_IMPLEMENTED; + case 502: return HTTP_STATUS_502_BAD_GATEWAY; + case 503: return HTTP_STATUS_503_SERVICE_UNAVAILABLE; + case 504: return HTTP_STATUS_504_GATEWAY_TIME_OUT; + case 505: return HTTP_STATUS_505_HTTP_VERSION_NOT_SUPPORTED; + case 308: return HTTP_STATUS_308_PERMANENT_REDIRECT; + default: return HTTP_STATUS_UNSUPPORTED_STATUS; + } +} diff --git a/lib/efi_loader/efi_image_loader.c b/lib/efi_loader/efi_image_loader.c new file mode 100644 index 00000000000..d002eb0c744 --- /dev/null +++ b/lib/efi_loader/efi_image_loader.c @@ -0,0 +1,1002 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI image loader + * + * based partly on wine code + * + * Copyright (c) 2016 Alexander Graf + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <cpu_func.h> +#include <efi_loader.h> +#include <log.h> +#include <malloc.h> +#include <mapmem.h> +#include <pe.h> +#include <sort.h> +#include <crypto/mscode.h> +#include <crypto/pkcs7_parser.h> +#include <linux/err.h> + +const efi_guid_t efi_global_variable_guid = EFI_GLOBAL_VARIABLE_GUID; +const efi_guid_t efi_guid_device_path = EFI_DEVICE_PATH_PROTOCOL_GUID; +const efi_guid_t efi_guid_loaded_image = EFI_LOADED_IMAGE_PROTOCOL_GUID; +const efi_guid_t efi_guid_loaded_image_device_path = + EFI_LOADED_IMAGE_DEVICE_PATH_PROTOCOL_GUID; +const efi_guid_t efi_simple_file_system_protocol_guid = + EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID; +const efi_guid_t efi_file_info_guid = EFI_FILE_INFO_GUID; + +static int machines[] = { +#if defined(__aarch64__) + IMAGE_FILE_MACHINE_ARM64, +#elif defined(__arm__) + IMAGE_FILE_MACHINE_ARM, + IMAGE_FILE_MACHINE_THUMB, + IMAGE_FILE_MACHINE_ARMNT, +#endif + +#if defined(__x86_64__) + IMAGE_FILE_MACHINE_AMD64, +#elif defined(__i386__) + IMAGE_FILE_MACHINE_I386, +#endif + +#if defined(__riscv) && (__riscv_xlen == 32) + IMAGE_FILE_MACHINE_RISCV32, +#endif + +#if defined(__riscv) && (__riscv_xlen == 64) + IMAGE_FILE_MACHINE_RISCV64, +#endif + 0 }; + +/** + * efi_print_image_info() - print information about a loaded image + * + * If the program counter is located within the image the offset to the base + * address is shown. + * + * @obj: EFI object + * @image: loaded image + * @pc: program counter (use NULL to suppress offset output) + * Return: status code + */ +static efi_status_t efi_print_image_info(struct efi_loaded_image_obj *obj, + struct efi_loaded_image *image, + void *pc) +{ + printf("UEFI image"); + printf(" [0x%p:0x%p]", + image->image_base, image->image_base + image->image_size - 1); + if (pc && pc >= image->image_base && + pc < image->image_base + image->image_size) + printf(" pc=0x%zx", pc - image->image_base); + if (image->file_path) + printf(" '%pD'", image->file_path); + printf("\n"); + return EFI_SUCCESS; +} + +/** + * efi_print_image_infos() - print information about all loaded images + * + * @pc: program counter (use NULL to suppress offset output) + */ +void efi_print_image_infos(void *pc) +{ + struct efi_object *efiobj; + struct efi_handler *handler; + + list_for_each_entry(efiobj, &efi_obj_list, link) { + list_for_each_entry(handler, &efiobj->protocols, link) { + if (!guidcmp(&handler->guid, &efi_guid_loaded_image)) { + efi_print_image_info( + (struct efi_loaded_image_obj *)efiobj, + handler->protocol_interface, pc); + } + } + } +} + +/** + * efi_loader_relocate() - relocate UEFI binary + * + * @rel: pointer to the relocation table + * @rel_size: size of the relocation table in bytes + * @efi_reloc: actual load address of the image + * @pref_address: preferred load address of the image + * Return: status code + */ +static efi_status_t efi_loader_relocate(const IMAGE_BASE_RELOCATION *rel, + unsigned long rel_size, void *efi_reloc, + unsigned long pref_address) +{ + unsigned long delta = (unsigned long)efi_reloc - pref_address; + const IMAGE_BASE_RELOCATION *end; + int i; + + if (delta == 0) + return EFI_SUCCESS; + + end = (const IMAGE_BASE_RELOCATION *)((const char *)rel + rel_size); + while (rel + 1 < end && rel->SizeOfBlock) { + const uint16_t *relocs = (const uint16_t *)(rel + 1); + i = (rel->SizeOfBlock - sizeof(*rel)) / sizeof(uint16_t); + while (i--) { + uint32_t offset = (uint32_t)(*relocs & 0xfff) + + rel->VirtualAddress; + int type = *relocs >> EFI_PAGE_SHIFT; + uint64_t *x64 = efi_reloc + offset; + uint32_t *x32 = efi_reloc + offset; + uint16_t *x16 = efi_reloc + offset; + + switch (type) { + case IMAGE_REL_BASED_ABSOLUTE: + break; + case IMAGE_REL_BASED_HIGH: + *x16 += ((uint32_t)delta) >> 16; + break; + case IMAGE_REL_BASED_LOW: + *x16 += (uint16_t)delta; + break; + case IMAGE_REL_BASED_HIGHLOW: + *x32 += (uint32_t)delta; + break; + case IMAGE_REL_BASED_DIR64: + *x64 += (uint64_t)delta; + break; +#ifdef __riscv + case IMAGE_REL_BASED_RISCV_HI20: + *x32 = ((*x32 & 0xfffff000) + (uint32_t)delta) | + (*x32 & 0x00000fff); + break; + case IMAGE_REL_BASED_RISCV_LOW12I: + case IMAGE_REL_BASED_RISCV_LOW12S: + /* We know that we're 4k aligned */ + if (delta & 0xfff) { + log_err("Unsupported reloc offset\n"); + return EFI_LOAD_ERROR; + } + break; +#endif + default: + log_err("Unknown Relocation off %x type %x\n", + offset, type); + return EFI_LOAD_ERROR; + } + relocs++; + } + rel = (const IMAGE_BASE_RELOCATION *)relocs; + } + return EFI_SUCCESS; +} + +/** + * efi_set_code_and_data_type() - determine the memory types to be used for code + * and data. + * + * @loaded_image_info: image descriptor + * @image_type: field Subsystem of the optional header for + * Windows specific field + */ +static void efi_set_code_and_data_type( + struct efi_loaded_image *loaded_image_info, + uint16_t image_type) +{ + switch (image_type) { + case IMAGE_SUBSYSTEM_EFI_APPLICATION: + loaded_image_info->image_code_type = EFI_LOADER_CODE; + loaded_image_info->image_data_type = EFI_LOADER_DATA; + break; + case IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER: + loaded_image_info->image_code_type = EFI_BOOT_SERVICES_CODE; + loaded_image_info->image_data_type = EFI_BOOT_SERVICES_DATA; + break; + case IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER: + case IMAGE_SUBSYSTEM_EFI_ROM: + loaded_image_info->image_code_type = EFI_RUNTIME_SERVICES_CODE; + loaded_image_info->image_data_type = EFI_RUNTIME_SERVICES_DATA; + break; + default: + log_err("invalid image type: %u\n", image_type); + /* Let's assume it is an application */ + loaded_image_info->image_code_type = EFI_LOADER_CODE; + loaded_image_info->image_data_type = EFI_LOADER_DATA; + break; + } +} + +/** + * efi_image_region_add() - add an entry of region + * @regs: Pointer to array of regions + * @start: Start address of region (included) + * @end: End address of region (excluded) + * @nocheck: flag against overlapped regions + * + * Take one entry of region \[@start, @end\[ and insert it into the list. + * + * * If @nocheck is false, the list will be sorted ascending by address. + * Overlapping entries will not be allowed. + * + * * If @nocheck is true, the list will be sorted ascending by sequence + * of adding the entries. Overlapping is allowed. + * + * Return: status code + */ +efi_status_t efi_image_region_add(struct efi_image_regions *regs, + const void *start, const void *end, + int nocheck) +{ + struct image_region *reg; + int i, j; + + if (regs->num >= regs->max) { + log_err("%s: no more room for regions\n", __func__); + return EFI_OUT_OF_RESOURCES; + } + + if (end < start) + return EFI_INVALID_PARAMETER; + + for (i = 0; i < regs->num; i++) { + reg = ®s->reg[i]; + if (nocheck) + continue; + + /* new data after registered region */ + if (start >= reg->data + reg->size) + continue; + + /* new data preceding registered region */ + if (end <= reg->data) { + for (j = regs->num - 1; j >= i; j--) + memcpy(®s->reg[j + 1], ®s->reg[j], + sizeof(*reg)); + break; + } + + /* new data overlapping registered region */ + log_err("%s: new region already part of another\n", __func__); + return EFI_INVALID_PARAMETER; + } + + reg = ®s->reg[i]; + reg->data = start; + reg->size = end - start; + regs->num++; + + return EFI_SUCCESS; +} + +/** + * cmp_pe_section() - compare virtual addresses of two PE image sections + * @arg1: pointer to pointer to first section header + * @arg2: pointer to pointer to second section header + * + * Compare the virtual addresses of two sections of an portable executable. + * The arguments are defined as const void * to allow usage with qsort(). + * + * Return: -1 if the virtual address of arg1 is less than that of arg2, + * 0 if the virtual addresses are equal, 1 if the virtual address + * of arg1 is greater than that of arg2. + */ +static int cmp_pe_section(const void *arg1, const void *arg2) +{ + const IMAGE_SECTION_HEADER *section1, *section2; + + section1 = *((const IMAGE_SECTION_HEADER **)arg1); + section2 = *((const IMAGE_SECTION_HEADER **)arg2); + + if (section1->VirtualAddress < section2->VirtualAddress) + return -1; + else if (section1->VirtualAddress == section2->VirtualAddress) + return 0; + else + return 1; +} + +/** + * efi_prepare_aligned_image() - prepare 8-byte aligned image + * @efi: pointer to the EFI binary + * @efi_size: size of @efi binary + * + * If @efi is not 8-byte aligned, this function newly allocates + * the image buffer. + * + * Return: valid pointer to a image, return NULL if allocation fails. + */ +void *efi_prepare_aligned_image(void *efi, u64 *efi_size) +{ + size_t new_efi_size; + void *new_efi; + + /* + * Size must be 8-byte aligned and the trailing bytes must be + * zero'ed. Otherwise hash value may be incorrect. + */ + if (!IS_ALIGNED(*efi_size, 8)) { + new_efi_size = ALIGN(*efi_size, 8); + new_efi = calloc(new_efi_size, 1); + if (!new_efi) + return NULL; + memcpy(new_efi, efi, *efi_size); + *efi_size = new_efi_size; + return new_efi; + } else { + return efi; + } +} + +/** + * efi_image_parse() - parse a PE image + * @efi: Pointer to image + * @len: Size of @efi + * @regp: Pointer to a list of regions + * @auth: Pointer to a pointer to authentication data in PE + * @auth_len: Size of @auth + * + * Parse image binary in PE32(+) format, assuming that sanity of PE image + * has been checked by a caller. + * On success, an address of authentication data in @efi and its size will + * be returned in @auth and @auth_len, respectively. + * + * Return: true on success, false on error + */ +bool efi_image_parse(void *efi, size_t len, struct efi_image_regions **regp, + WIN_CERTIFICATE **auth, size_t *auth_len) +{ + struct efi_image_regions *regs; + IMAGE_DOS_HEADER *dos; + IMAGE_NT_HEADERS32 *nt; + IMAGE_SECTION_HEADER *sections, **sorted; + int num_regions, num_sections, i; + int ctidx = IMAGE_DIRECTORY_ENTRY_SECURITY; + u32 align, size, authsz, authoff; + size_t bytes_hashed; + + dos = (void *)efi; + nt = (void *)(efi + dos->e_lfanew); + authoff = 0; + authsz = 0; + + /* + * Count maximum number of regions to be digested. + * We don't have to have an exact number here. + * See efi_image_region_add()'s in parsing below. + */ + num_regions = 3; /* for header */ + num_regions += nt->FileHeader.NumberOfSections; + num_regions++; /* for extra */ + + regs = calloc(sizeof(*regs) + sizeof(struct image_region) * num_regions, + 1); + if (!regs) + goto err; + regs->max = num_regions; + + /* + * Collect data regions for hash calculation + * 1. File headers + */ + if (nt->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) { + IMAGE_NT_HEADERS64 *nt64 = (void *)nt; + IMAGE_OPTIONAL_HEADER64 *opt = &nt64->OptionalHeader; + + /* Skip CheckSum */ + efi_image_region_add(regs, efi, &opt->CheckSum, 0); + if (nt64->OptionalHeader.NumberOfRvaAndSizes <= ctidx) { + efi_image_region_add(regs, + &opt->Subsystem, + efi + opt->SizeOfHeaders, 0); + } else { + /* Skip Certificates Table */ + efi_image_region_add(regs, + &opt->Subsystem, + &opt->DataDirectory[ctidx], 0); + efi_image_region_add(regs, + &opt->DataDirectory[ctidx] + 1, + efi + opt->SizeOfHeaders, 0); + + authoff = opt->DataDirectory[ctidx].VirtualAddress; + authsz = opt->DataDirectory[ctidx].Size; + } + + bytes_hashed = opt->SizeOfHeaders; + align = opt->FileAlignment; + } else if (nt->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) { + IMAGE_OPTIONAL_HEADER32 *opt = &nt->OptionalHeader; + + /* Skip CheckSum */ + efi_image_region_add(regs, efi, &opt->CheckSum, 0); + if (nt->OptionalHeader.NumberOfRvaAndSizes <= ctidx) { + efi_image_region_add(regs, + &opt->Subsystem, + efi + opt->SizeOfHeaders, 0); + } else { + /* Skip Certificates Table */ + efi_image_region_add(regs, &opt->Subsystem, + &opt->DataDirectory[ctidx], 0); + efi_image_region_add(regs, + &opt->DataDirectory[ctidx] + 1, + efi + opt->SizeOfHeaders, 0); + + authoff = opt->DataDirectory[ctidx].VirtualAddress; + authsz = opt->DataDirectory[ctidx].Size; + } + + bytes_hashed = opt->SizeOfHeaders; + align = opt->FileAlignment; + } else { + log_err("%s: Invalid optional header magic %x\n", __func__, + nt->OptionalHeader.Magic); + goto err; + } + + /* 2. Sections */ + num_sections = nt->FileHeader.NumberOfSections; + sections = (void *)((uint8_t *)&nt->OptionalHeader + + nt->FileHeader.SizeOfOptionalHeader); + sorted = calloc(sizeof(IMAGE_SECTION_HEADER *), num_sections); + if (!sorted) { + log_err("%s: Out of memory\n", __func__); + goto err; + } + + /* + * Make sure the section list is in ascending order. + */ + for (i = 0; i < num_sections; i++) + sorted[i] = §ions[i]; + qsort(sorted, num_sections, sizeof(sorted[0]), cmp_pe_section); + + for (i = 0; i < num_sections; i++) { + if (!sorted[i]->SizeOfRawData) + continue; + + size = (sorted[i]->SizeOfRawData + align - 1) & ~(align - 1); + efi_image_region_add(regs, efi + sorted[i]->PointerToRawData, + efi + sorted[i]->PointerToRawData + size, + 0); + log_debug("section[%d](%s): raw: 0x%x-0x%x, virt: %x-%x\n", + i, sorted[i]->Name, + sorted[i]->PointerToRawData, + sorted[i]->PointerToRawData + size, + sorted[i]->VirtualAddress, + sorted[i]->VirtualAddress + + sorted[i]->Misc.VirtualSize); + + bytes_hashed += size; + } + free(sorted); + + /* 3. Extra data excluding Certificates Table */ + if (bytes_hashed + authsz < len) { + log_debug("extra data for hash: %zu\n", + len - (bytes_hashed + authsz)); + efi_image_region_add(regs, efi + bytes_hashed, + efi + len - authsz, 0); + } + + /* Return Certificates Table */ + if (authsz) { + if (len < authoff + authsz) { + log_err("%s: Size for auth too large: %u >= %zu\n", + __func__, authsz, len - authoff); + goto err; + } + if (authsz < sizeof(*auth)) { + log_err("%s: Size for auth too small: %u < %zu\n", + __func__, authsz, sizeof(*auth)); + goto err; + } + *auth = efi + authoff; + *auth_len = authsz; + log_debug("WIN_CERTIFICATE: 0x%x, size: 0x%x\n", authoff, + authsz); + } else { + *auth = NULL; + *auth_len = 0; + } + + *regp = regs; + + return true; + +err: + free(regs); + + return false; +} + +#ifdef CONFIG_EFI_SECURE_BOOT +/** + * efi_image_verify_digest - verify image's message digest + * @regs: Array of memory regions to digest + * @msg: Signature in pkcs7 structure + * + * @regs contains all the data in a PE image to digest. Calculate + * a hash value based on @regs and compare it with a messaged digest + * in the content (SpcPeImageData) of @msg's contentInfo. + * + * Return: true if verified, false if not + */ +static bool efi_image_verify_digest(struct efi_image_regions *regs, + struct pkcs7_message *msg) +{ + struct pefile_context ctx; + void *hash; + int hash_len, ret; + + const void *data; + size_t data_len; + size_t asn1hdrlen; + + /* get pkcs7's contentInfo */ + ret = pkcs7_get_content_data(msg, &data, &data_len, &asn1hdrlen); + if (ret < 0 || !data) + return false; + + /* parse data and retrieve a message digest into ctx */ + ret = mscode_parse(&ctx, data, data_len, asn1hdrlen); + if (ret < 0) + return false; + + /* calculate a hash value of PE image */ + hash = NULL; + if (!efi_hash_regions(regs->reg, regs->num, &hash, ctx.digest_algo, + &hash_len)) + return false; + + /* match the digest */ + if (ctx.digest_len != hash_len || memcmp(ctx.digest, hash, hash_len)) + return false; + + return true; +} + +/** + * efi_image_authenticate() - verify a signature of signed image + * @efi: Pointer to image + * @efi_size: Size of @efi + * + * A signed image should have its signature stored in a table of its PE header. + * So if an image is signed and only if if its signature is verified using + * signature databases, an image is authenticated. + * If an image is not signed, its validity is checked by using + * efi_image_unsigned_authenticated(). + * TODO: + * When AuditMode==0, if the image's signature is not found in + * the authorized database, or is found in the forbidden database, + * the image will not be started and instead, information about it + * will be placed in this table. + * When AuditMode==1, an EFI_IMAGE_EXECUTION_INFO element is created + * in the EFI_IMAGE_EXECUTION_INFO_TABLE for every certificate found + * in the certificate table of every image that is validated. + * + * Return: true if authenticated, false if not + */ +static bool efi_image_authenticate(void *efi, size_t efi_size) +{ + struct efi_image_regions *regs = NULL; + WIN_CERTIFICATE *wincerts = NULL, *wincert; + size_t wincerts_len; + struct pkcs7_message *msg = NULL; + struct efi_signature_store *db = NULL, *dbx = NULL; + void *new_efi = NULL; + u8 *auth, *wincerts_end; + u64 new_efi_size = efi_size; + size_t auth_size; + bool ret = false; + + log_debug("%s: Enter, %d\n", __func__, ret); + + if (!efi_secure_boot_enabled()) + return true; + + new_efi = efi_prepare_aligned_image(efi, &new_efi_size); + if (!new_efi) + return false; + + if (!efi_image_parse(new_efi, new_efi_size, ®s, &wincerts, + &wincerts_len)) { + log_err("Parsing PE executable image failed\n"); + goto out; + } + + /* + * verify signature using db and dbx + */ + db = efi_sigstore_parse_sigdb(u"db"); + if (!db) { + log_err("Getting signature database(db) failed\n"); + goto out; + } + + dbx = efi_sigstore_parse_sigdb(u"dbx"); + if (!dbx) { + log_err("Getting signature database(dbx) failed\n"); + goto out; + } + + if (efi_signature_lookup_digest(regs, dbx, true)) { + log_debug("Image's digest was found in \"dbx\"\n"); + goto out; + } + + /* + * go through WIN_CERTIFICATE list + * NOTE: + * We may have multiple signatures either as WIN_CERTIFICATE's + * in PE header, or as pkcs7 SignerInfo's in SignedData. + * So the verification policy here is: + * - Success if, at least, one of signatures is verified + * - unless signature is rejected explicitly with its digest. + */ + + for (wincert = wincerts, wincerts_end = (u8 *)wincerts + wincerts_len; + (u8 *)wincert < wincerts_end; + wincert = (WIN_CERTIFICATE *) + ((u8 *)wincert + ALIGN(wincert->dwLength, 8))) { + if ((u8 *)wincert + sizeof(*wincert) >= wincerts_end) + break; + + if (wincert->dwLength <= sizeof(*wincert)) { + log_debug("dwLength too small: %u < %zu\n", + wincert->dwLength, sizeof(*wincert)); + continue; + } + + log_debug("WIN_CERTIFICATE_TYPE: 0x%x\n", + wincert->wCertificateType); + + auth = (u8 *)wincert + sizeof(*wincert); + auth_size = wincert->dwLength - sizeof(*wincert); + if (wincert->wCertificateType == WIN_CERT_TYPE_EFI_GUID) { + if (auth + sizeof(efi_guid_t) >= wincerts_end) + break; + + if (auth_size <= sizeof(efi_guid_t)) { + log_debug("dwLength too small: %u < %zu\n", + wincert->dwLength, sizeof(*wincert)); + continue; + } + if (guidcmp(auth, &efi_guid_cert_type_pkcs7)) { + log_debug("Certificate type not supported: %pUs\n", + auth); + ret = false; + goto out; + } + + auth += sizeof(efi_guid_t); + auth_size -= sizeof(efi_guid_t); + } else if (wincert->wCertificateType + != WIN_CERT_TYPE_PKCS_SIGNED_DATA) { + log_debug("Certificate type not supported\n"); + ret = false; + goto out; + } + + msg = pkcs7_parse_message(auth, auth_size); + if (IS_ERR(msg)) { + log_err("Parsing image's signature failed\n"); + msg = NULL; + continue; + } + + /* + * verify signatures in pkcs7's signedInfos which are + * to authenticate the integrity of pkcs7's contentInfo. + * + * NOTE: + * UEFI specification defines two signature types possible + * in signature database: + * a. x509 certificate, where a signature in image is + * a message digest encrypted by RSA public key + * (EFI_CERT_X509_GUID) + * b. bare hash value of message digest + * (EFI_CERT_SHAxxx_GUID) + * + * efi_signature_verify() handles case (a), while + * efi_signature_lookup_digest() handles case (b). + * + * There is a third type: + * c. message digest of a certificate + * (EFI_CERT_X509_SHAAxxx_GUID) + * This type of signature is used only in revocation list + * (dbx) and handled as part of efi_signatgure_verify(). + */ + /* try black-list first */ + if (efi_signature_verify_one(regs, msg, dbx)) { + ret = false; + log_debug("Signature was rejected by \"dbx\"\n"); + goto out; + } + + if (!efi_signature_check_signers(msg, dbx)) { + ret = false; + log_debug("Signer(s) in \"dbx\"\n"); + goto out; + } + + /* try white-list */ + if (!efi_signature_verify(regs, msg, db, dbx)) { + log_debug("Signature was not verified by \"db\"\n"); + continue; + } + + /* + * now calculate an image's hash value and compare it with + * a messaged digest embedded in pkcs7's contentInfo + */ + if (efi_image_verify_digest(regs, msg)) { + ret = true; + continue; + } + + log_debug("Message digest doesn't match\n"); + } + + /* last resort try the image sha256 hash in db */ + if (!ret && efi_signature_lookup_digest(regs, db, false)) + ret = true; + +out: + efi_sigstore_free(db); + efi_sigstore_free(dbx); + pkcs7_free_message(msg); + free(regs); + if (new_efi != efi) + free(new_efi); + + log_debug("%s: Exit, %d\n", __func__, ret); + return ret; +} +#else +static bool efi_image_authenticate(void *efi, size_t efi_size) +{ + return true; +} +#endif /* CONFIG_EFI_SECURE_BOOT */ + +/** + * efi_check_pe() - check if a memory buffer contains a PE-COFF image + * + * @buffer: buffer to check + * @size: size of buffer + * @nt_header: on return pointer to NT header of PE-COFF image + * Return: EFI_SUCCESS if the buffer contains a PE-COFF image + */ +efi_status_t efi_check_pe(void *buffer, size_t size, void **nt_header) +{ + IMAGE_DOS_HEADER *dos = buffer; + IMAGE_NT_HEADERS32 *nt; + + if (size < sizeof(*dos)) + return EFI_INVALID_PARAMETER; + + /* Check for DOS magix */ + if (dos->e_magic != IMAGE_DOS_SIGNATURE) + return EFI_INVALID_PARAMETER; + + /* + * Check if the image section header fits into the file. Knowing that at + * least one section header follows we only need to check for the length + * of the 64bit header which is longer than the 32bit header. + */ + if (size < dos->e_lfanew + sizeof(IMAGE_NT_HEADERS32)) + return EFI_INVALID_PARAMETER; + nt = (IMAGE_NT_HEADERS32 *)((u8 *)buffer + dos->e_lfanew); + + /* Check for PE-COFF magic */ + if (nt->Signature != IMAGE_NT_SIGNATURE) + return EFI_INVALID_PARAMETER; + + if (nt_header) + *nt_header = nt; + + return EFI_SUCCESS; +} + +/** + * section_size() - determine size of section + * + * The size of a section in memory if normally given by VirtualSize. + * If VirtualSize is not provided, use SizeOfRawData. + * + * @sec: section header + * Return: size of section in memory + */ +static u32 section_size(IMAGE_SECTION_HEADER *sec) +{ + if (sec->Misc.VirtualSize) + return sec->Misc.VirtualSize; + else + return sec->SizeOfRawData; +} + +/** + * efi_load_pe() - relocate EFI binary + * + * This function loads all sections from a PE binary into a newly reserved + * piece of memory. On success the entry point is returned as handle->entry. + * + * @handle: loaded image handle + * @efi: pointer to the EFI binary + * @efi_size: size of @efi binary + * @loaded_image_info: loaded image protocol + * Return: status code + */ +efi_status_t efi_load_pe(struct efi_loaded_image_obj *handle, + void *efi, size_t efi_size, + struct efi_loaded_image *loaded_image_info) +{ + IMAGE_NT_HEADERS32 *nt; + IMAGE_DOS_HEADER *dos; + IMAGE_SECTION_HEADER *sections; + int num_sections; + void *efi_reloc; + int i; + const IMAGE_BASE_RELOCATION *rel; + unsigned long rel_size; + int rel_idx = IMAGE_DIRECTORY_ENTRY_BASERELOC; + uint64_t image_base; + unsigned long virt_size = 0; + int supported = 0; + efi_status_t ret; + + ret = efi_check_pe(efi, efi_size, (void **)&nt); + if (ret != EFI_SUCCESS) { + log_err("Not a PE-COFF file\n"); + return EFI_LOAD_ERROR; + } + + for (i = 0; machines[i]; i++) + if (machines[i] == nt->FileHeader.Machine) { + supported = 1; + break; + } + + if (!supported) { + log_err("Machine type 0x%04x is not supported\n", + nt->FileHeader.Machine); + return EFI_LOAD_ERROR; + } + + num_sections = nt->FileHeader.NumberOfSections; + sections = (void *)&nt->OptionalHeader + + nt->FileHeader.SizeOfOptionalHeader; + + if (efi_size < ((void *)sections + sizeof(sections[0]) * num_sections + - efi)) { + log_err("Invalid number of sections: %d\n", num_sections); + return EFI_LOAD_ERROR; + } + + /* Authenticate an image */ + if (efi_image_authenticate(efi, efi_size)) { + handle->auth_status = EFI_IMAGE_AUTH_PASSED; + } else { + handle->auth_status = EFI_IMAGE_AUTH_FAILED; + log_err("Image not authenticated\n"); + } + + /* Calculate upper virtual address boundary */ + for (i = num_sections - 1; i >= 0; i--) { + IMAGE_SECTION_HEADER *sec = §ions[i]; + + virt_size = max_t(unsigned long, virt_size, + sec->VirtualAddress + section_size(sec)); + } + + /* Read 32/64bit specific header bits */ + if (nt->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) { + IMAGE_NT_HEADERS64 *nt64 = (void *)nt; + IMAGE_OPTIONAL_HEADER64 *opt = &nt64->OptionalHeader; + image_base = opt->ImageBase; + efi_set_code_and_data_type(loaded_image_info, opt->Subsystem); + handle->image_type = opt->Subsystem; + efi_reloc = efi_alloc_aligned_pages(virt_size, + loaded_image_info->image_code_type, + opt->SectionAlignment); + if (!efi_reloc) { + log_err("Out of memory\n"); + ret = EFI_OUT_OF_RESOURCES; + goto err; + } + handle->entry = efi_reloc + opt->AddressOfEntryPoint; + rel_size = opt->DataDirectory[rel_idx].Size; + rel = efi_reloc + opt->DataDirectory[rel_idx].VirtualAddress; + } else if (nt->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) { + IMAGE_OPTIONAL_HEADER32 *opt = &nt->OptionalHeader; + image_base = opt->ImageBase; + efi_set_code_and_data_type(loaded_image_info, opt->Subsystem); + handle->image_type = opt->Subsystem; + efi_reloc = efi_alloc_aligned_pages(virt_size, + loaded_image_info->image_code_type, + opt->SectionAlignment); + if (!efi_reloc) { + log_err("Out of memory\n"); + ret = EFI_OUT_OF_RESOURCES; + goto err; + } + handle->entry = efi_reloc + opt->AddressOfEntryPoint; + rel_size = opt->DataDirectory[rel_idx].Size; + rel = efi_reloc + opt->DataDirectory[rel_idx].VirtualAddress; + } else { + log_err("Invalid optional header magic %x\n", + nt->OptionalHeader.Magic); + ret = EFI_LOAD_ERROR; + goto err; + } + +#if IS_ENABLED(CONFIG_EFI_TCG2_PROTOCOL) + /* Measure an PE/COFF image */ + ret = tcg2_measure_pe_image(efi, efi_size, handle, loaded_image_info); + if (ret == EFI_SECURITY_VIOLATION) { + /* + * TCG2 Protocol is installed but no TPM device found, + * this is not expected. + */ + log_err("PE image measurement failed, no tpm device found\n"); + goto err; + } + +#endif + + /* Copy PE headers */ + memcpy(efi_reloc, efi, + sizeof(*dos) + + sizeof(*nt) + + nt->FileHeader.SizeOfOptionalHeader + + num_sections * sizeof(IMAGE_SECTION_HEADER)); + + /* Load sections into RAM */ + for (i = num_sections - 1; i >= 0; i--) { + IMAGE_SECTION_HEADER *sec = §ions[i]; + u32 copy_size = section_size(sec); + + if (copy_size > sec->SizeOfRawData) { + copy_size = sec->SizeOfRawData; + memset(efi_reloc + sec->VirtualAddress, 0, + sec->Misc.VirtualSize); + } + memcpy(efi_reloc + sec->VirtualAddress, + efi + sec->PointerToRawData, + copy_size); + } + + /* Run through relocations */ + if (efi_loader_relocate(rel, rel_size, efi_reloc, + (unsigned long)image_base) != EFI_SUCCESS) { + efi_free_pages((uintptr_t) efi_reloc, + (virt_size + EFI_PAGE_MASK) >> EFI_PAGE_SHIFT); + ret = EFI_LOAD_ERROR; + goto err; + } + + /* Flush cache */ + flush_cache(map_to_sysmem(efi_reloc), + ALIGN(virt_size, EFI_CACHELINE_SIZE)); + + /* + * If on x86 a write affects a prefetched instruction, + * the prefetch queue is invalidated. + */ + if (!CONFIG_IS_ENABLED(X86)) + invalidate_icache_all(); + + /* Populate the loaded image interface bits */ + loaded_image_info->image_base = efi_reloc; + loaded_image_info->image_size = virt_size; + + if (handle->auth_status == EFI_IMAGE_AUTH_PASSED) + return EFI_SUCCESS; + else + return EFI_SECURITY_VIOLATION; + +err: + return ret; +} diff --git a/lib/efi_loader/efi_ipconfig.c b/lib/efi_loader/efi_ipconfig.c new file mode 100644 index 00000000000..9f51f77fa9a --- /dev/null +++ b/lib/efi_loader/efi_ipconfig.c @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Implementation of EFI_IP4_CONFIG2_PROTOCOL + * + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <efi_loader.h> +#include <image.h> +#include <malloc.h> +#include <mapmem.h> +#include <net.h> + +static const efi_guid_t efi_ip4_config2_guid = EFI_IP4_CONFIG2_PROTOCOL_GUID; + +struct efi_ip4_config2_manual_address current_http_ip; +static enum efi_ip4_config2_policy current_policy; +static char current_mac_addr[32]; + +/* EFI_IP4_CONFIG2_PROTOCOL */ + +/* + * efi_ip4_config2_set_data() - Set the configuration for the EFI IPv4 network + * stack running on the communication device + * + * This function implements EFI_IP4_CONFIG2_PROTOCOL.SetData() + * See the Unified Extensible Firmware Interface + * (UEFI) specification for details. + * + * @this: pointer to the protocol instance + * @data_type: the type of data to set + * @data_size: size of the buffer pointed to by data in bytes + * @data: the data buffer to set + * Return: status code + */ +static efi_status_t EFIAPI efi_ip4_config2_set_data(struct efi_ip4_config2_protocol *this, + enum efi_ip4_config2_data_type data_type, + efi_uintn_t data_size, + void *data) +{ + EFI_ENTRY("%p, %d, %zu, %p", this, data_type, data_size, data); + efi_status_t ret = EFI_SUCCESS; + + if (!this || (data && !data_size) || (!data && data_size)) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + switch (data_type) { + case EFI_IP4_CONFIG2_DATA_TYPE_INTERFACEINFO: + return EFI_EXIT(EFI_WRITE_PROTECTED); + case EFI_IP4_CONFIG2_DATA_TYPE_MANUAL_ADDRESS: + if (current_policy != EFI_IP4_CONFIG2_POLICY_STATIC) + return EFI_EXIT(EFI_WRITE_PROTECTED); + if (!data_size && !data) { + memset((void *)¤t_http_ip, 0, + sizeof(current_http_ip)); + return EFI_EXIT(EFI_SUCCESS); + } + if (data && data_size == sizeof(struct efi_ip4_config2_manual_address)) { + memcpy((void *)¤t_http_ip, data, + sizeof(struct efi_ip4_config2_manual_address)); + efi_net_set_addr(¤t_http_ip.address, + ¤t_http_ip.subnet_mask, NULL, NULL); + return EFI_EXIT(EFI_SUCCESS); + } + return EFI_EXIT(EFI_BAD_BUFFER_SIZE); + case EFI_IP4_CONFIG2_DATA_TYPE_POLICY: + if (data && data_size == sizeof(enum efi_ip4_config2_policy)) { + current_policy = *(enum efi_ip4_config2_policy *)data; + return EFI_EXIT(EFI_SUCCESS); + } + return EFI_EXIT(EFI_BAD_BUFFER_SIZE); + + default: + return EFI_EXIT(EFI_UNSUPPORTED); + } + + return EFI_EXIT(ret); +} + +/* + * efi_ip4_config2_get_data() - Get the configuration for the EFI IPv4 network + * stack running on the communication device + * + * This function implements EFI_IP4_CONFIG2_PROTOCOL.GetData() + * See the Unified Extensible Firmware Interface + * (UEFI) specification for details. + * + * @this: pointer to the protocol instance + * @data_type: the type of data to get + * @data_size: size + * @data: the data buffer + * Return: status code + */ +static efi_status_t EFIAPI efi_ip4_config2_get_data(struct efi_ip4_config2_protocol *this, + enum efi_ip4_config2_data_type data_type, + efi_uintn_t *data_size, + void *data) +{ + EFI_ENTRY("%p, %d, %p, %p", this, data_type, data_size, data); + + efi_status_t ret = EFI_SUCCESS; + struct efi_ip4_config2_interface_info *info; + int tmp; + + if (!this || !data_size) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + if (*data_size && !data) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + tmp = sizeof(struct efi_ip4_config2_interface_info) + sizeof(struct efi_ip4_route_table); + + switch (data_type) { + case EFI_IP4_CONFIG2_DATA_TYPE_INTERFACEINFO: + if (*data_size < tmp) { + *data_size = tmp; + return EFI_EXIT(EFI_BUFFER_TOO_SMALL); + } + + info = (struct efi_ip4_config2_interface_info *)data; + memset(info, 0, sizeof(*info)); + + info->hw_address_size = 6; + memcpy(info->hw_address.mac_addr, current_mac_addr, 6); + // Set the route table size + + info->route_table_size = 0; + break; + case EFI_IP4_CONFIG2_DATA_TYPE_MANUAL_ADDRESS: + if (*data_size < sizeof(struct efi_ip4_config2_manual_address)) { + *data_size = sizeof(struct efi_ip4_config2_manual_address); + return EFI_EXIT(EFI_BUFFER_TOO_SMALL); + } + + efi_net_get_addr(¤t_http_ip.address, ¤t_http_ip.subnet_mask, NULL, NULL); + memcpy(data, (void *)¤t_http_ip, + sizeof(struct efi_ip4_config2_manual_address)); + + break; + default: + return EFI_EXIT(EFI_NOT_FOUND); + } + return EFI_EXIT(ret); +} + +/* + * efi_ip4_config2_register_notify() - Register an event that is to be signaled whenever + * a configuration process on the specified configuration + * data is done + * + * This function implements EFI_IP4_CONFIG2_PROTOCOL.RegisterDataNotify() + * See the Unified Extensible Firmware Interface + * (UEFI) specification for details. + * + * @this: pointer to the protocol instance + * @data_type: the type of data to register the event for + * @event: the event to register + * Return: status code + */ +static efi_status_t EFIAPI efi_ip4_config2_register_notify(struct efi_ip4_config2_protocol *this, + enum efi_ip4_config2_data_type data_type, + struct efi_event *event) +{ + EFI_ENTRY("%p, %d, %p", this, data_type, event); + + return EFI_EXIT(EFI_UNSUPPORTED); +} + +/* + * efi_ip4_config2_unregister_notify() - Remove a previously registered eventfor + * the specified configuration data + * + * This function implements EFI_IP4_CONFIG2_PROTOCOL.UnregisterDataNotify() + * See the Unified Extensible Firmware Interface + * (UEFI) specification for details. + * + * @this: pointer to the protocol instance + * @data_type: the type of data to remove the event for + * @event: the event to unregister + * Return: status code + */ +static efi_status_t EFIAPI efi_ip4_config2_unregister_notify(struct efi_ip4_config2_protocol *this, + enum efi_ip4_config2_data_type data_type, + struct efi_event *event) +{ + EFI_ENTRY("%p, %d, %p", this, data_type, event); + + return EFI_EXIT(EFI_UNSUPPORTED); +} + +/** + * efi_ipconfig_register() - register the ip4_config2 protocol + * + */ +efi_status_t efi_ipconfig_register(const efi_handle_t handle, + struct efi_ip4_config2_protocol *ip4config) +{ + efi_status_t r = EFI_SUCCESS; + + r = efi_add_protocol(handle, &efi_ip4_config2_guid, + ip4config); + if (r != EFI_SUCCESS) { + log_err("ERROR: Failure to add protocol\n"); + return r; + } + + memcpy(current_mac_addr, eth_get_ethaddr(), 6); + + ip4config->set_data = efi_ip4_config2_set_data; + ip4config->get_data = efi_ip4_config2_get_data; + ip4config->register_data_notify = efi_ip4_config2_register_notify; + ip4config->unregister_data_notify = efi_ip4_config2_unregister_notify; + + return EFI_SUCCESS; +} diff --git a/lib/efi_loader/efi_load_initrd.c b/lib/efi_loader/efi_load_initrd.c new file mode 100644 index 00000000000..fb8cc7bcbe3 --- /dev/null +++ b/lib/efi_loader/efi_load_initrd.c @@ -0,0 +1,276 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2020, Linaro Limited + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <efi_loader.h> +#include <efi_load_initrd.h> +#include <efi_variable.h> +#include <fs.h> +#include <malloc.h> +#include <mapmem.h> + +static efi_status_t EFIAPI +efi_load_file2_initrd(struct efi_load_file_protocol *this, + struct efi_device_path *file_path, bool boot_policy, + efi_uintn_t *buffer_size, void *buffer); + +static const struct efi_load_file_protocol efi_lf2_protocol = { + .load_file = efi_load_file2_initrd, +}; + +/* + * Device path defined by Linux to identify the handle providing the + * EFI_LOAD_FILE2_PROTOCOL used for loading the initial ramdisk. + */ +static const struct efi_lo_dp_prefix dp_lf2_handle = { + .vendor = { + { + DEVICE_PATH_TYPE_MEDIA_DEVICE, + DEVICE_PATH_SUB_TYPE_VENDOR_PATH, + sizeof(dp_lf2_handle.vendor), + }, + EFI_INITRD_MEDIA_GUID, + }, + .end = { + DEVICE_PATH_TYPE_END, + DEVICE_PATH_SUB_TYPE_END, + sizeof(dp_lf2_handle.end), + } +}; + +static efi_handle_t efi_initrd_handle; + +/** + * get_initrd_fp() - Get initrd device path from a FilePathList device path + * + * @initrd_fp: the final initrd filepath + * + * Return: status code. Caller must free initrd_fp + */ +static efi_status_t get_initrd_fp(struct efi_device_path **initrd_fp) +{ + struct efi_device_path *dp = NULL; + + /* + * if bootmgr is setup with and initrd, the device path will be + * in the FilePathList[] of our load options in Boot####. + * The first device path of the multi instance device path will + * start with a VenMedia and the initrds will follow. + * + * If the device path is not found return EFI_INVALID_PARAMETER. + * We can then use this specific return value and not install the + * protocol, while allowing the boot to continue + */ + dp = efi_get_dp_from_boot(&efi_lf2_initrd_guid); + if (!dp) + return EFI_INVALID_PARAMETER; + + *initrd_fp = dp; + return EFI_SUCCESS; +} + +/** + * efi_load_file2_initrd() - load initial RAM disk + * + * This function implements the LoadFile service of the EFI_LOAD_FILE2_PROTOCOL + * in order to load an initial RAM disk requested by the Linux kernel stub. + * + * See the UEFI spec for details. + * + * @this: EFI_LOAD_FILE2_PROTOCOL instance + * @file_path: media device path of the file, "" in this case + * @boot_policy: must be false + * @buffer_size: size of allocated buffer + * @buffer: buffer to load the file + * + * Return: status code + */ +static efi_status_t EFIAPI +efi_load_file2_initrd(struct efi_load_file_protocol *this, + struct efi_device_path *file_path, bool boot_policy, + efi_uintn_t *buffer_size, void *buffer) +{ + struct efi_device_path *initrd_fp = NULL; + efi_status_t ret = EFI_NOT_FOUND; + struct efi_file_handle *f = NULL; + efi_uintn_t bs; + + EFI_ENTRY("%p, %p, %d, %p, %p", this, file_path, boot_policy, + buffer_size, buffer); + + if (!this || this != &efi_lf2_protocol || + !buffer_size) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + if (file_path->type != dp_lf2_handle.end.type || + file_path->sub_type != dp_lf2_handle.end.sub_type) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + if (boot_policy) { + ret = EFI_UNSUPPORTED; + goto out; + } + + ret = get_initrd_fp(&initrd_fp); + if (ret != EFI_SUCCESS) + goto out; + + /* Open file */ + f = efi_file_from_path(initrd_fp); + if (!f) { + log_err("Can't find initrd specified in Boot####\n"); + ret = EFI_NOT_FOUND; + goto out; + } + + /* Get file size */ + ret = efi_file_size(f, &bs); + if (ret != EFI_SUCCESS) + goto out; + + if (!buffer || *buffer_size < bs) { + ret = EFI_BUFFER_TOO_SMALL; + *buffer_size = bs; + } else { + ret = EFI_CALL(f->read(f, &bs, (void *)(uintptr_t)buffer)); + *buffer_size = bs; + } + +out: + efi_free_pool(initrd_fp); + if (f) + EFI_CALL(f->close(f)); + return EFI_EXIT(ret); +} + +/** + * check_initrd() - Determine if the file defined as an initrd in Boot#### + * load_options device path is present + * + * Return: status code + */ +static efi_status_t check_initrd(void) +{ + struct efi_device_path *initrd_fp = NULL; + struct efi_file_handle *f; + efi_status_t ret; + + ret = get_initrd_fp(&initrd_fp); + if (ret != EFI_SUCCESS) + goto out; + + /* + * If the file is not found, but the file path is set, return an error + * and trigger the bootmgr fallback + */ + f = efi_file_from_path(initrd_fp); + if (!f) { + log_err("Can't find initrd specified in Boot####\n"); + ret = EFI_NOT_FOUND; + goto out; + } + + EFI_CALL(f->close(f)); + +out: + efi_free_pool(initrd_fp); + return ret; +} + +/** + * efi_initrd_deregister() - delete the handle for loading initial RAM disk + * + * This will delete the handle containing the Linux specific vendor device + * path and EFI_LOAD_FILE2_PROTOCOL for loading an initrd + * + * Return: status code + */ +efi_status_t efi_initrd_deregister(void) +{ + efi_status_t ret; + + if (!efi_initrd_handle) + return EFI_SUCCESS; + + ret = efi_uninstall_multiple_protocol_interfaces(efi_initrd_handle, + /* initramfs */ + &efi_guid_device_path, + &dp_lf2_handle, + /* LOAD_FILE2 */ + &efi_guid_load_file2_protocol, + &efi_lf2_protocol, + NULL); + efi_initrd_handle = NULL; + + return ret; +} + +/** + * efi_initrd_return_notify() - return to efibootmgr callback + * + * @event: the event for which this notification function is registered + * @context: event context + */ +static void EFIAPI efi_initrd_return_notify(struct efi_event *event, + void *context) +{ + efi_status_t ret; + + EFI_ENTRY("%p, %p", event, context); + ret = efi_initrd_deregister(); + EFI_EXIT(ret); +} + +/** + * efi_initrd_register() - create handle for loading initial RAM disk + * + * This function creates a new handle and installs a Linux specific vendor + * device path and an EFI_LOAD_FILE2_PROTOCOL. Linux uses the device path + * to identify the handle and then calls the LoadFile service of the + * EFI_LOAD_FILE2_PROTOCOL to read the initial RAM disk. + * + * Return: status code + */ +efi_status_t efi_initrd_register(void) +{ + efi_status_t ret; + struct efi_event *event; + + /* + * Allow the user to continue if Boot#### file path is not set for + * an initrd + */ + ret = check_initrd(); + if (ret == EFI_INVALID_PARAMETER) + return EFI_SUCCESS; + if (ret != EFI_SUCCESS) + return ret; + + ret = efi_install_multiple_protocol_interfaces(&efi_initrd_handle, + /* initramfs */ + &efi_guid_device_path, &dp_lf2_handle, + /* LOAD_FILE2 */ + &efi_guid_load_file2_protocol, + &efi_lf2_protocol, + NULL); + if (ret != EFI_SUCCESS) { + log_err("installing EFI_LOAD_FILE2_PROTOCOL failed\n"); + return ret; + } + + ret = efi_create_event(EVT_NOTIFY_SIGNAL, TPL_CALLBACK, + efi_initrd_return_notify, NULL, + &efi_guid_event_group_return_to_efibootmgr, + &event); + if (ret != EFI_SUCCESS) + log_err("Creating event failed\n"); + + return ret; +} diff --git a/lib/efi_loader/efi_load_options.c b/lib/efi_loader/efi_load_options.c new file mode 100644 index 00000000000..01984235e24 --- /dev/null +++ b/lib/efi_loader/efi_load_options.c @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI boot manager + * + * Copyright (c) 2018 AKASHI Takahiro, et.al. + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <charset.h> +#include <log.h> +#include <malloc.h> +#include <efi_loader.h> +#include <asm/unaligned.h> + +/** + * efi_set_load_options() - set the load options of a loaded image + * + * @handle: the image handle + * @load_options_size: size of load options + * @load_options: pointer to load options + * Return: status code + */ +efi_status_t efi_set_load_options(efi_handle_t handle, + efi_uintn_t load_options_size, + void *load_options) +{ + struct efi_loaded_image *loaded_image_info; + struct efi_handler *handler; + efi_status_t ret; + + ret = efi_search_protocol(handle, &efi_guid_loaded_image, &handler); + if (ret != EFI_SUCCESS) + return EFI_INVALID_PARAMETER; + + loaded_image_info = handler->protocol_interface; + loaded_image_info->load_options = load_options; + loaded_image_info->load_options_size = load_options_size; + + return EFI_SUCCESS; +} + +/** + * efi_deserialize_load_option() - parse serialized data + * + * Parse serialized data describing a load option and transform it to the + * efi_load_option structure. + * + * @lo: pointer to target + * @data: serialized data + * @size: size of the load option, on return size of the optional data + * Return: status code + */ +efi_status_t efi_deserialize_load_option(struct efi_load_option *lo, u8 *data, + efi_uintn_t *size) +{ + efi_uintn_t len; + + len = sizeof(u32); + if (*size < len + 2 * sizeof(u16)) + return EFI_INVALID_PARAMETER; + lo->attributes = get_unaligned_le32(data); + data += len; + *size -= len; + + len = sizeof(u16); + lo->file_path_length = get_unaligned_le16(data); + data += len; + *size -= len; + + lo->label = (u16 *)data; + len = u16_strnlen(lo->label, *size / sizeof(u16) - 1); + if (lo->label[len]) + return EFI_INVALID_PARAMETER; + len = (len + 1) * sizeof(u16); + if (*size < len) + return EFI_INVALID_PARAMETER; + data += len; + *size -= len; + + len = lo->file_path_length; + if (*size < len) + return EFI_INVALID_PARAMETER; + lo->file_path = (struct efi_device_path *)data; + if (efi_dp_check_length(lo->file_path, len) < 0) + return EFI_INVALID_PARAMETER; + data += len; + *size -= len; + + lo->optional_data = data; + + return EFI_SUCCESS; +} + +/** + * efi_serialize_load_option() - serialize load option + * + * Serialize efi_load_option structure into byte stream for BootXXXX. + * + * @data: buffer for serialized data + * @lo: load option + * Return: size of allocated buffer + */ +unsigned long efi_serialize_load_option(struct efi_load_option *lo, u8 **data) +{ + unsigned long label_len; + unsigned long size; + u8 *p; + + label_len = u16_strsize(lo->label); + + /* total size */ + size = sizeof(lo->attributes); + size += sizeof(lo->file_path_length); + size += label_len; + size += lo->file_path_length; + if (lo->optional_data) + size += (utf8_utf16_strlen((const char *)lo->optional_data) + + 1) * sizeof(u16); + p = malloc(size); + if (!p) + return 0; + + /* copy data */ + *data = p; + memcpy(p, &lo->attributes, sizeof(lo->attributes)); + p += sizeof(lo->attributes); + + memcpy(p, &lo->file_path_length, sizeof(lo->file_path_length)); + p += sizeof(lo->file_path_length); + + memcpy(p, lo->label, label_len); + p += label_len; + + memcpy(p, lo->file_path, lo->file_path_length); + p += lo->file_path_length; + + if (lo->optional_data) { + utf8_utf16_strcpy((u16 **)&p, (const char *)lo->optional_data); + p += sizeof(u16); /* size of trailing \0 */ + } + return size; +} diff --git a/lib/efi_loader/efi_memory.c b/lib/efi_loader/efi_memory.c new file mode 100644 index 00000000000..0abb1f6159a --- /dev/null +++ b/lib/efi_loader/efi_memory.c @@ -0,0 +1,896 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI application memory management + * + * Copyright (c) 2016 Alexander Graf + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <efi_loader.h> +#include <init.h> +#include <lmb.h> +#include <log.h> +#include <malloc.h> +#include <mapmem.h> +#include <watchdog.h> +#include <asm/cache.h> +#include <asm/global_data.h> +#include <asm/sections.h> +#include <linux/list_sort.h> +#include <linux/sizes.h> + +DECLARE_GLOBAL_DATA_PTR; + +/* Magic number identifying memory allocated from pool */ +#define EFI_ALLOC_POOL_MAGIC 0x1fe67ddf6491caa2 + +efi_uintn_t efi_memory_map_key; + +struct efi_mem_list { + struct list_head link; + struct efi_mem_desc desc; +}; + +#define EFI_CARVE_NO_OVERLAP -1 +#define EFI_CARVE_LOOP_AGAIN -2 +#define EFI_CARVE_OVERLAPS_NONRAM -3 +#define EFI_CARVE_OUT_OF_RESOURCES -4 + +/* This list contains all memory map items */ +static LIST_HEAD(efi_mem); + +#ifdef CONFIG_EFI_LOADER_BOUNCE_BUFFER +void *efi_bounce_buffer; +#endif + +/** + * struct efi_pool_allocation - memory block allocated from pool + * + * @num_pages: number of pages allocated + * @checksum: checksum + * @data: allocated pool memory + * + * U-Boot services each UEFI AllocatePool() request as a separate + * (multiple) page allocation. We have to track the number of pages + * to be able to free the correct amount later. + * + * The checksum calculated in function checksum() is used in FreePool() to avoid + * freeing memory not allocated by AllocatePool() and duplicate freeing. + * + * EFI requires 8 byte alignment for pool allocations, so we can + * prepend each allocation with these header fields. + */ +struct efi_pool_allocation { + u64 num_pages; + u64 checksum; + char data[] __aligned(ARCH_DMA_MINALIGN); +}; + +/** + * checksum() - calculate checksum for memory allocated from pool + * + * @alloc: allocation header + * Return: checksum, always non-zero + */ +static u64 checksum(struct efi_pool_allocation *alloc) +{ + u64 addr = (uintptr_t)alloc; + u64 ret = (addr >> 32) ^ (addr << 32) ^ alloc->num_pages ^ + EFI_ALLOC_POOL_MAGIC; + if (!ret) + ++ret; + return ret; +} + +/** + * efi_mem_cmp() - comparator function for sorting memory map + * + * Sorts the memory list from highest address to lowest address + * + * When allocating memory we should always start from the highest + * address chunk, so sort the memory list such that the first list + * iterator gets the highest address and goes lower from there. + * + * @priv: unused + * @a: first memory area + * @b: second memory area + * Return: 1 if @a is before @b, -1 if @b is before @a, 0 if equal + */ +static int efi_mem_cmp(void *priv, struct list_head *a, struct list_head *b) +{ + struct efi_mem_list *mema = list_entry(a, struct efi_mem_list, link); + struct efi_mem_list *memb = list_entry(b, struct efi_mem_list, link); + + if (mema->desc.physical_start == memb->desc.physical_start) + return 0; + else if (mema->desc.physical_start < memb->desc.physical_start) + return 1; + else + return -1; +} + +/** + * desc_get_end() - get end address of memory area + * + * @desc: memory descriptor + * Return: end address + 1 + */ +static uint64_t desc_get_end(struct efi_mem_desc *desc) +{ + return desc->physical_start + (desc->num_pages << EFI_PAGE_SHIFT); +} + +/** + * efi_mem_sort() - sort memory map + * + * Sort the memory map and then try to merge adjacent memory areas. + */ +static void efi_mem_sort(void) +{ + struct efi_mem_list *lmem; + struct efi_mem_list *prevmem = NULL; + bool merge_again = true; + + list_sort(NULL, &efi_mem, efi_mem_cmp); + + /* Now merge entries that can be merged */ + while (merge_again) { + merge_again = false; + list_for_each_entry(lmem, &efi_mem, link) { + struct efi_mem_desc *prev; + struct efi_mem_desc *cur; + uint64_t pages; + + if (!prevmem) { + prevmem = lmem; + continue; + } + + cur = &lmem->desc; + prev = &prevmem->desc; + + if ((desc_get_end(cur) == prev->physical_start) && + (prev->type == cur->type) && + (prev->attribute == cur->attribute)) { + /* There is an existing map before, reuse it */ + pages = cur->num_pages; + prev->num_pages += pages; + prev->physical_start -= pages << EFI_PAGE_SHIFT; + prev->virtual_start -= pages << EFI_PAGE_SHIFT; + list_del(&lmem->link); + free(lmem); + + merge_again = true; + break; + } + + prevmem = lmem; + } + } +} + +/** + * efi_mem_carve_out() - unmap memory region + * + * @map: memory map + * @carve_desc: memory region to unmap + * @overlap_conventional: the carved out region may only overlap free, + * or conventional memory + * Return: the number of overlapping pages which have been + * removed from the map, + * EFI_CARVE_NO_OVERLAP, if the regions don't + * overlap, EFI_CARVE_OVERLAPS_NONRAM, if the carve + * and map overlap, and the map contains anything + * but free ram(only when overlap_conventional is + * true), + * EFI_CARVE_LOOP_AGAIN, if the mapping list should + * be traversed again, as it has been altered. + * + * Unmaps all memory occupied by the carve_desc region from the list entry + * pointed to by map. + * + * In case of EFI_CARVE_OVERLAPS_NONRAM it is the callers responsibility + * to re-add the already carved out pages to the mapping. + */ +static s64 efi_mem_carve_out(struct efi_mem_list *map, + struct efi_mem_desc *carve_desc, + bool overlap_conventional) +{ + struct efi_mem_list *newmap; + struct efi_mem_desc *map_desc = &map->desc; + uint64_t map_start = map_desc->physical_start; + uint64_t map_end = map_start + (map_desc->num_pages << EFI_PAGE_SHIFT); + uint64_t carve_start = carve_desc->physical_start; + uint64_t carve_end = carve_start + + (carve_desc->num_pages << EFI_PAGE_SHIFT); + + /* check whether we're overlapping */ + if ((carve_end <= map_start) || (carve_start >= map_end)) + return EFI_CARVE_NO_OVERLAP; + + /* We're overlapping with non-RAM, warn the caller if desired */ + if (overlap_conventional && (map_desc->type != EFI_CONVENTIONAL_MEMORY)) + return EFI_CARVE_OVERLAPS_NONRAM; + + /* Sanitize carve_start and carve_end to lie within our bounds */ + carve_start = max(carve_start, map_start); + carve_end = min(carve_end, map_end); + + /* Carving at the beginning of our map? Just move it! */ + if (carve_start == map_start) { + if (map_end == carve_end) { + /* Full overlap, just remove map */ + list_del(&map->link); + free(map); + } else { + map->desc.physical_start = carve_end; + map->desc.virtual_start = carve_end; + map->desc.num_pages = (map_end - carve_end) + >> EFI_PAGE_SHIFT; + } + + return (carve_end - carve_start) >> EFI_PAGE_SHIFT; + } + + /* + * Overlapping maps, just split the list map at carve_start, + * it will get moved or removed in the next iteration. + * + * [ map_desc |__carve_start__| newmap ] + */ + + /* Create a new map from [ carve_start ... map_end ] */ + newmap = calloc(1, sizeof(*newmap)); + if (!newmap) + return EFI_CARVE_OUT_OF_RESOURCES; + newmap->desc = map->desc; + newmap->desc.physical_start = carve_start; + newmap->desc.virtual_start = carve_start; + newmap->desc.num_pages = (map_end - carve_start) >> EFI_PAGE_SHIFT; + /* Insert before current entry (descending address order) */ + list_add_tail(&newmap->link, &map->link); + + /* Shrink the map to [ map_start ... carve_start ] */ + map_desc->num_pages = (carve_start - map_start) >> EFI_PAGE_SHIFT; + + return EFI_CARVE_LOOP_AGAIN; +} + +/** + * efi_update_memory_map() - update the memory map by adding/removing pages + * + * @start: start address, must be a multiple of + * EFI_PAGE_SIZE + * @pages: number of pages to add + * @memory_type: type of memory added + * @overlap_conventional: region may only overlap free(conventional) + * memory + * @remove: remove memory map + * Return: status code + */ +efi_status_t efi_update_memory_map(u64 start, u64 pages, int memory_type, + bool overlap_conventional, bool remove) +{ + struct efi_mem_list *lmem; + struct efi_mem_list *newlist; + bool carve_again; + uint64_t carved_pages = 0; + struct efi_event *evt; + + EFI_PRINT("%s: 0x%llx 0x%llx %d %s %s\n", __func__, + start, pages, memory_type, overlap_conventional ? + "yes" : "no", remove ? "remove" : "add"); + + if (memory_type >= EFI_MAX_MEMORY_TYPE) + return EFI_INVALID_PARAMETER; + + if (!pages) + return EFI_SUCCESS; + + ++efi_memory_map_key; + newlist = calloc(1, sizeof(*newlist)); + if (!newlist) + return EFI_OUT_OF_RESOURCES; + newlist->desc.type = memory_type; + newlist->desc.physical_start = start; + newlist->desc.virtual_start = start; + newlist->desc.num_pages = pages; + + switch (memory_type) { + case EFI_RUNTIME_SERVICES_CODE: + case EFI_RUNTIME_SERVICES_DATA: + newlist->desc.attribute = EFI_MEMORY_WB | EFI_MEMORY_RUNTIME; + break; + case EFI_MMAP_IO: + newlist->desc.attribute = EFI_MEMORY_RUNTIME; + break; + default: + newlist->desc.attribute = EFI_MEMORY_WB; + break; + } + + /* Add our new map */ + do { + carve_again = false; + list_for_each_entry(lmem, &efi_mem, link) { + s64 r; + + r = efi_mem_carve_out(lmem, &newlist->desc, + overlap_conventional); + switch (r) { + case EFI_CARVE_OUT_OF_RESOURCES: + free(newlist); + return EFI_OUT_OF_RESOURCES; + case EFI_CARVE_OVERLAPS_NONRAM: + /* + * The user requested to only have RAM overlaps, + * but we hit a non-RAM region. Error out. + */ + free(newlist); + return EFI_NO_MAPPING; + case EFI_CARVE_NO_OVERLAP: + /* Just ignore this list entry */ + break; + case EFI_CARVE_LOOP_AGAIN: + /* + * We split an entry, but need to loop through + * the list again to actually carve it. + */ + carve_again = true; + break; + default: + /* We carved a number of pages */ + carved_pages += r; + carve_again = true; + break; + } + + if (carve_again) { + /* The list changed, we need to start over */ + break; + } + } + } while (carve_again); + + if (overlap_conventional && (carved_pages != pages)) { + /* + * The payload wanted to have RAM overlaps, but we overlapped + * with an unallocated region. Error out. + */ + free(newlist); + return EFI_NO_MAPPING; + } + + /* Add our new map */ + if (!remove) + list_add_tail(&newlist->link, &efi_mem); + else + free(newlist); + + /* And make sure memory is listed in descending order */ + efi_mem_sort(); + + /* Notify that the memory map was changed */ + list_for_each_entry(evt, &efi_events, link) { + if (evt->group && + !guidcmp(evt->group, + &efi_guid_event_group_memory_map_change)) { + efi_signal_event(evt); + break; + } + } + + return EFI_SUCCESS; +} + +/** + * efi_add_memory_map() - add memory area to the memory map + * + * @start: start address of the memory area + * @size: length in bytes of the memory area + * @memory_type: type of memory added + * + * Return: status code + * + * This function automatically aligns the start and size of the memory area + * to EFI_PAGE_SIZE. + */ +efi_status_t efi_add_memory_map(u64 start, u64 size, int memory_type) +{ + u64 pages; + + pages = efi_size_in_pages(size + (start & EFI_PAGE_MASK)); + start &= ~EFI_PAGE_MASK; + + return efi_update_memory_map(start, pages, memory_type, false, false); +} + +/** + * efi_check_allocated() - validate address to be freed + * + * Check that the address is within allocated memory: + * + * * The address must be in a range of the memory map. + * * The address may not point to EFI_CONVENTIONAL_MEMORY. + * + * Page alignment is not checked as this is not a requirement of + * efi_free_pool(). + * + * @addr: address of page to be freed + * @must_be_allocated: return success if the page is allocated + * Return: status code + */ +static efi_status_t efi_check_allocated(u64 addr, bool must_be_allocated) +{ + struct efi_mem_list *item; + + list_for_each_entry(item, &efi_mem, link) { + u64 start = item->desc.physical_start; + u64 end = start + (item->desc.num_pages << EFI_PAGE_SHIFT); + + if (addr >= start && addr < end) { + if (must_be_allocated ^ + (item->desc.type == EFI_CONVENTIONAL_MEMORY)) + return EFI_SUCCESS; + else + return EFI_NOT_FOUND; + } + } + + return EFI_NOT_FOUND; +} + +/** + * efi_allocate_pages - allocate memory pages + * + * @type: type of allocation to be performed + * @memory_type: usage type of the allocated memory + * @pages: number of pages to be allocated + * @memory: allocated memory + * Return: status code + */ +efi_status_t efi_allocate_pages(enum efi_allocate_type type, + enum efi_memory_type memory_type, + efi_uintn_t pages, uint64_t *memory) +{ + u64 efi_addr, len; + uint flags; + efi_status_t ret; + phys_addr_t addr; + + /* Check import parameters */ + if (memory_type >= EFI_PERSISTENT_MEMORY_TYPE && + memory_type <= 0x6FFFFFFF) + return EFI_INVALID_PARAMETER; + if (!memory) + return EFI_INVALID_PARAMETER; + len = (u64)pages << EFI_PAGE_SHIFT; + /* Catch possible overflow on 64bit systems */ + if (sizeof(efi_uintn_t) == sizeof(u64) && + (len >> EFI_PAGE_SHIFT) != (u64)pages) + return EFI_OUT_OF_RESOURCES; + + flags = LMB_NOOVERWRITE | LMB_NONOTIFY; + switch (type) { + case EFI_ALLOCATE_ANY_PAGES: + /* Any page */ + addr = (u64)lmb_alloc_base(len, EFI_PAGE_SIZE, + LMB_ALLOC_ANYWHERE, flags); + if (!addr) + return EFI_OUT_OF_RESOURCES; + break; + case EFI_ALLOCATE_MAX_ADDRESS: + /* Max address */ + addr = map_to_sysmem((void *)(uintptr_t)*memory); + addr = (u64)lmb_alloc_base(len, EFI_PAGE_SIZE, addr, + flags); + if (!addr) + return EFI_OUT_OF_RESOURCES; + break; + case EFI_ALLOCATE_ADDRESS: + if (*memory & EFI_PAGE_MASK) + return EFI_NOT_FOUND; + + addr = map_to_sysmem((void *)(uintptr_t)*memory); + if (lmb_alloc_addr(addr, len, flags)) + return EFI_NOT_FOUND; + break; + default: + /* UEFI doesn't specify other allocation types */ + return EFI_INVALID_PARAMETER; + } + + efi_addr = (u64)(uintptr_t)map_sysmem(addr, 0); + /* Reserve that map in our memory maps */ + ret = efi_update_memory_map(efi_addr, pages, memory_type, true, false); + if (ret != EFI_SUCCESS) { + /* Map would overlap, bail out */ + lmb_free_flags(addr, (u64)pages << EFI_PAGE_SHIFT, flags); + unmap_sysmem((void *)(uintptr_t)efi_addr); + return EFI_OUT_OF_RESOURCES; + } + + *memory = efi_addr; + + return EFI_SUCCESS; +} + +/** + * efi_free_pages() - free memory pages + * + * @memory: start of the memory area to be freed + * @pages: number of pages to be freed + * Return: status code + */ +efi_status_t efi_free_pages(uint64_t memory, efi_uintn_t pages) +{ + u64 len; + long status; + efi_status_t ret; + + ret = efi_check_allocated(memory, true); + if (ret != EFI_SUCCESS) + return ret; + + /* Sanity check */ + if (!memory || (memory & EFI_PAGE_MASK) || !pages) { + printf("%s: illegal free 0x%llx, 0x%zx\n", __func__, + memory, pages); + return EFI_INVALID_PARAMETER; + } + + len = (u64)pages << EFI_PAGE_SHIFT; + /* + * The 'memory' variable for sandbox holds a pointer which has already + * been mapped with map_sysmem() from efi_allocate_pages(). Convert + * it back to an address LMB understands + */ + status = lmb_free_flags(map_to_sysmem((void *)(uintptr_t)memory), len, + LMB_NOOVERWRITE); + if (status) + return EFI_NOT_FOUND; + + unmap_sysmem((void *)(uintptr_t)memory); + + return ret; +} + +/** + * efi_alloc_aligned_pages() - allocate aligned memory pages + * + * @len: len in bytes + * @memory_type: usage type of the allocated memory + * @align: alignment in bytes + * Return: aligned memory or NULL + */ +void *efi_alloc_aligned_pages(u64 len, int memory_type, size_t align) +{ + u64 req_pages = efi_size_in_pages(len); + u64 true_pages = req_pages + efi_size_in_pages(align) - 1; + u64 free_pages; + u64 aligned_mem; + efi_status_t r; + u64 mem; + + /* align must be zero or a power of two */ + if (align & (align - 1)) + return NULL; + + /* Check for overflow */ + if (true_pages < req_pages) + return NULL; + + if (align < EFI_PAGE_SIZE) { + r = efi_allocate_pages(EFI_ALLOCATE_ANY_PAGES, memory_type, + req_pages, &mem); + return (r == EFI_SUCCESS) ? (void *)(uintptr_t)mem : NULL; + } + + r = efi_allocate_pages(EFI_ALLOCATE_ANY_PAGES, memory_type, + true_pages, &mem); + if (r != EFI_SUCCESS) + return NULL; + + aligned_mem = ALIGN(mem, align); + /* Free pages before alignment */ + free_pages = efi_size_in_pages(aligned_mem - mem); + if (free_pages) + efi_free_pages(mem, free_pages); + + /* Free trailing pages */ + free_pages = true_pages - (req_pages + free_pages); + if (free_pages) { + mem = aligned_mem + req_pages * EFI_PAGE_SIZE; + efi_free_pages(mem, free_pages); + } + + return (void *)(uintptr_t)aligned_mem; +} + +/** + * efi_allocate_pool - allocate memory from pool + * + * @pool_type: type of the pool from which memory is to be allocated + * @size: number of bytes to be allocated + * @buffer: allocated memory + * Return: status code + */ +efi_status_t efi_allocate_pool(enum efi_memory_type pool_type, efi_uintn_t size, void **buffer) +{ + efi_status_t r; + u64 addr; + struct efi_pool_allocation *alloc; + u64 num_pages = efi_size_in_pages(size + + sizeof(struct efi_pool_allocation)); + + if (!buffer) + return EFI_INVALID_PARAMETER; + + if (size == 0) { + *buffer = NULL; + return EFI_SUCCESS; + } + + r = efi_allocate_pages(EFI_ALLOCATE_ANY_PAGES, pool_type, num_pages, + &addr); + if (r == EFI_SUCCESS) { + alloc = (struct efi_pool_allocation *)(uintptr_t)addr; + alloc->num_pages = num_pages; + alloc->checksum = checksum(alloc); + *buffer = alloc->data; + } + + return r; +} + +/** + * efi_alloc() - allocate boot services data pool memory + * + * Allocate memory from pool and zero it out. + * + * @size: number of bytes to allocate + * Return: pointer to allocated memory or NULL + */ +void *efi_alloc(size_t size) +{ + void *buf; + + if (efi_allocate_pool(EFI_BOOT_SERVICES_DATA, size, &buf) != + EFI_SUCCESS) { + log_err("out of memory\n"); + return NULL; + } + memset(buf, 0, size); + + return buf; +} + +/** + * efi_free_pool() - free memory from pool + * + * @buffer: start of memory to be freed + * Return: status code + */ +efi_status_t efi_free_pool(void *buffer) +{ + efi_status_t ret; + struct efi_pool_allocation *alloc; + + if (!buffer) + return EFI_INVALID_PARAMETER; + + ret = efi_check_allocated((uintptr_t)buffer, true); + if (ret != EFI_SUCCESS) + return ret; + + alloc = container_of(buffer, struct efi_pool_allocation, data); + + /* Check that this memory was allocated by efi_allocate_pool() */ + if (((uintptr_t)alloc & EFI_PAGE_MASK) || + alloc->checksum != checksum(alloc)) { + printf("%s: illegal free 0x%p\n", __func__, buffer); + return EFI_INVALID_PARAMETER; + } + /* Avoid double free */ + alloc->checksum = 0; + + ret = efi_free_pages((uintptr_t)alloc, alloc->num_pages); + + return ret; +} + +/** + * efi_get_memory_map() - get map describing memory usage. + * + * @memory_map_size: on entry the size, in bytes, of the memory map buffer, + * on exit the size of the copied memory map + * @memory_map: buffer to which the memory map is written + * @map_key: key for the memory map + * @descriptor_size: size of an individual memory descriptor + * @descriptor_version: version number of the memory descriptor structure + * Return: status code + */ +efi_status_t efi_get_memory_map(efi_uintn_t *memory_map_size, + struct efi_mem_desc *memory_map, + efi_uintn_t *map_key, + efi_uintn_t *descriptor_size, + uint32_t *descriptor_version) +{ + size_t map_entries; + efi_uintn_t map_size = 0; + struct efi_mem_list *lmem; + efi_uintn_t provided_map_size; + + if (!memory_map_size) + return EFI_INVALID_PARAMETER; + + provided_map_size = *memory_map_size; + + map_entries = list_count_nodes(&efi_mem); + + map_size = map_entries * sizeof(struct efi_mem_desc); + + *memory_map_size = map_size; + + if (descriptor_size) + *descriptor_size = sizeof(struct efi_mem_desc); + + if (descriptor_version) + *descriptor_version = EFI_MEMORY_DESCRIPTOR_VERSION; + + if (provided_map_size < map_size) + return EFI_BUFFER_TOO_SMALL; + + if (!memory_map) + return EFI_INVALID_PARAMETER; + + /* Copy list into array */ + /* Return the list in ascending order */ + memory_map = &memory_map[map_entries - 1]; + list_for_each_entry(lmem, &efi_mem, link) { + *memory_map = lmem->desc; + memory_map--; + } + + if (map_key) + *map_key = efi_memory_map_key; + + return EFI_SUCCESS; +} + +/** + * efi_get_memory_map_alloc() - allocate map describing memory usage + * + * The caller is responsible for calling FreePool() if the call succeeds. + * + * @map_size: size of the memory map + * @memory_map: buffer to which the memory map is written + * Return: status code + */ +efi_status_t efi_get_memory_map_alloc(efi_uintn_t *map_size, + struct efi_mem_desc **memory_map) +{ + efi_status_t ret; + + *memory_map = NULL; + *map_size = 0; + ret = efi_get_memory_map(map_size, *memory_map, NULL, NULL, NULL); + if (ret == EFI_BUFFER_TOO_SMALL) { + *map_size += sizeof(struct efi_mem_desc); /* for the map */ + ret = efi_allocate_pool(EFI_BOOT_SERVICES_DATA, *map_size, + (void **)memory_map); + if (ret != EFI_SUCCESS) + return ret; + ret = efi_get_memory_map(map_size, *memory_map, + NULL, NULL, NULL); + if (ret != EFI_SUCCESS) { + efi_free_pool(*memory_map); + *memory_map = NULL; + } + } + + return ret; +} + +/** + * efi_add_known_memory() - add memory types to the EFI memory map + * + * This function is to be used to add different memory types other + * than EFI_CONVENTIONAL_MEMORY to the EFI memory map. The conventional + * memory is handled by the LMB module and gets added to the memory + * map through the LMB module. + * + * This function may be overridden for architectures specific purposes. + */ +__weak void efi_add_known_memory(void) +{ +} + +/** + * add_u_boot_and_runtime() - add U-Boot code to memory map + * + * Add memory regions for U-Boot's memory and for the runtime services code. + */ +static void add_u_boot_and_runtime(void) +{ + unsigned long runtime_start, runtime_end, runtime_pages; + unsigned long runtime_mask = EFI_PAGE_MASK; + unsigned long uboot_start, uboot_pages; + unsigned long uboot_stack_size = CONFIG_STACK_SIZE; + + /* Add U-Boot */ + uboot_start = ((uintptr_t)map_sysmem(gd->start_addr_sp, 0) - + uboot_stack_size) & ~EFI_PAGE_MASK; + uboot_pages = ((uintptr_t)map_sysmem(gd->ram_top - 1, 0) - + uboot_start + EFI_PAGE_MASK) >> EFI_PAGE_SHIFT; + efi_update_memory_map(uboot_start, uboot_pages, EFI_BOOT_SERVICES_CODE, + false, false); +#if defined(__aarch64__) + /* + * Runtime Services must be 64KiB aligned according to the + * "AArch64 Platforms" section in the UEFI spec (2.7+). + */ + + runtime_mask = SZ_64K - 1; +#endif + + /* + * Add Runtime Services. We mark surrounding boottime code as runtime as + * well to fulfill the runtime alignment constraints but avoid padding. + */ + runtime_start = (uintptr_t)__efi_runtime_start & ~runtime_mask; + runtime_end = (uintptr_t)__efi_runtime_stop; + runtime_end = (runtime_end + runtime_mask) & ~runtime_mask; + runtime_pages = (runtime_end - runtime_start) >> EFI_PAGE_SHIFT; + efi_update_memory_map(runtime_start, runtime_pages, + EFI_RUNTIME_SERVICES_CODE, false, false); +} + +int efi_memory_init(void) +{ + efi_add_known_memory(); + + add_u_boot_and_runtime(); + +#ifdef CONFIG_EFI_LOADER_BOUNCE_BUFFER + /* Request a 32bit 64MB bounce buffer region */ + uint64_t efi_bounce_buffer_addr = 0xffffffff; + + if (efi_allocate_pages(EFI_ALLOCATE_MAX_ADDRESS, EFI_BOOT_SERVICES_DATA, + (64 * 1024 * 1024) >> EFI_PAGE_SHIFT, + &efi_bounce_buffer_addr) != EFI_SUCCESS) + return -1; + + efi_bounce_buffer = (void*)(uintptr_t)efi_bounce_buffer_addr; +#endif + + return 0; +} + +int efi_map_update_notify(phys_addr_t addr, phys_size_t size, + enum lmb_map_op op) +{ + u64 efi_addr; + u64 pages; + efi_status_t status; + + efi_addr = (uintptr_t)map_sysmem(addr, 0); + pages = efi_size_in_pages(size + (efi_addr & EFI_PAGE_MASK)); + efi_addr &= ~EFI_PAGE_MASK; + + status = efi_update_memory_map(efi_addr, pages, + op == LMB_MAP_OP_RESERVE ? + EFI_BOOT_SERVICES_DATA : + EFI_CONVENTIONAL_MEMORY, + false, false); + if (status != EFI_SUCCESS) { + log_err("LMB Map notify failure %lu\n", + status & ~EFI_ERROR_MASK); + return -1; + } + unmap_sysmem((void *)(uintptr_t)efi_addr); + + return 0; +} + diff --git a/lib/efi_loader/efi_net.c b/lib/efi_loader/efi_net.c new file mode 100644 index 00000000000..b3291b4f1d5 --- /dev/null +++ b/lib/efi_loader/efi_net.c @@ -0,0 +1,1715 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Simple network protocol + * PXE base code protocol + * + * Copyright (c) 2016 Alexander Graf + * + * The simple network protocol has the following statuses and services + * to move between them: + * + * Start(): EfiSimpleNetworkStopped -> EfiSimpleNetworkStarted + * Initialize(): EfiSimpleNetworkStarted -> EfiSimpleNetworkInitialized + * Shutdown(): EfiSimpleNetworkInitialized -> EfiSimpleNetworkStarted + * Stop(): EfiSimpleNetworkStarted -> EfiSimpleNetworkStopped + * Reset(): EfiSimpleNetworkInitialized -> EfiSimpleNetworkInitialized + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <efi_loader.h> +#include <dm.h> +#include <linux/sizes.h> +#include <malloc.h> +#include <vsprintf.h> +#include <net.h> + +#define MAX_EFI_NET_OBJS 10 +#define MAX_NUM_DHCP_ENTRIES 10 +#define MAX_NUM_DP_ENTRIES 10 + +const efi_guid_t efi_net_guid = EFI_SIMPLE_NETWORK_PROTOCOL_GUID; +static const efi_guid_t efi_pxe_base_code_protocol_guid = + EFI_PXE_BASE_CODE_PROTOCOL_GUID; + +struct dp_entry { + struct efi_device_path *net_dp; + struct udevice *dev; + bool is_valid; +}; + +/* + * The network device path cache. An entry is added when a new bootfile + * is downloaded from the network. If the bootfile is then loaded as an + * efi image, the most recent entry corresponding to the device is passed + * as the device path of the loaded image. + */ +static struct dp_entry dp_cache[MAX_NUM_DP_ENTRIES]; +static int next_dp_entry; + +#if IS_ENABLED(CONFIG_EFI_HTTP_PROTOCOL) +static struct wget_http_info efi_wget_info = { + .set_bootdev = false, + .check_buffer_size = true, + +}; +#endif + +struct dhcp_entry { + struct efi_pxe_packet *dhcp_ack; + struct udevice *dev; + bool is_valid; +}; + +static struct dhcp_entry dhcp_cache[MAX_NUM_DHCP_ENTRIES]; +static int next_dhcp_entry; + +/** + * struct efi_net_obj - EFI object representing a network interface + * + * @header: EFI object header + * @dev: net udevice + * @net: simple network protocol interface + * @net_mode: status of the network interface + * @pxe: PXE base code protocol interface + * @pxe_mode: status of the PXE base code protocol + * @ip4_config2: IP4 Config2 protocol interface + * @http_service_binding: Http service binding protocol interface + * @new_tx_packet: new transmit packet + * @transmit_buffer: transmit buffer + * @receive_buffer: array of receive buffers + * @receive_lengths: array of lengths for received packets + * @rx_packet_idx: index of the current receive packet + * @rx_packet_num: number of received packets + * @wait_for_packet: signaled when a packet has been received + * @network_timer_event: event to check for new network packets. + * @efi_seq_num: sequence number of the EFI net object. + */ +struct efi_net_obj { + struct efi_object header; + struct udevice *dev; + struct efi_simple_network net; + struct efi_simple_network_mode net_mode; + struct efi_pxe_base_code_protocol pxe; + struct efi_pxe_mode pxe_mode; +#if IS_ENABLED(CONFIG_EFI_IP4_CONFIG2_PROTOCOL) + struct efi_ip4_config2_protocol ip4_config2; +#endif +#if IS_ENABLED(CONFIG_EFI_HTTP_PROTOCOL) + struct efi_service_binding_protocol http_service_binding; +#endif + void *new_tx_packet; + void *transmit_buffer; + uchar **receive_buffer; + size_t *receive_lengths; + int rx_packet_idx; + int rx_packet_num; + struct efi_event *wait_for_packet; + struct efi_event *network_timer_event; + int efi_seq_num; +}; + +static int curr_efi_net_obj; +static struct efi_net_obj *net_objs[MAX_EFI_NET_OBJS]; + +/** + * efi_netobj_is_active() - checks if a netobj is active in the efi subsystem + * + * @netobj: pointer to efi_net_obj + * Return: true if active + */ +static bool efi_netobj_is_active(struct efi_net_obj *netobj) +{ + if (!netobj || !efi_search_obj(&netobj->header)) + return false; + + return true; +} + +/* + * efi_netobj_from_snp() - get efi_net_obj from simple network protocol + * + * + * @snp: pointer to the simple network protocol + * Return: pointer to efi_net_obj, NULL on error + */ +static struct efi_net_obj *efi_netobj_from_snp(struct efi_simple_network *snp) +{ + int i; + + for (i = 0; i < MAX_EFI_NET_OBJS; i++) { + if (net_objs[i] && &net_objs[i]->net == snp) { + // Do not register duplicate devices + return net_objs[i]; + } + } + return NULL; +} + +/* + * efi_net_start() - start the network interface + * + * This function implements the Start service of the + * EFI_SIMPLE_NETWORK_PROTOCOL. See the Unified Extensible Firmware Interface + * (UEFI) specification for details. + * + * @this: pointer to the protocol instance + * Return: status code + */ +static efi_status_t EFIAPI efi_net_start(struct efi_simple_network *this) +{ + efi_status_t ret = EFI_SUCCESS; + struct efi_net_obj *nt; + + EFI_ENTRY("%p", this); + /* Check parameters */ + if (!this) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + nt = efi_netobj_from_snp(this); + + if (this->mode->state != EFI_NETWORK_STOPPED) { + ret = EFI_ALREADY_STARTED; + } else { + this->int_status = 0; + nt->wait_for_packet->is_signaled = false; + this->mode->state = EFI_NETWORK_STARTED; + } +out: + return EFI_EXIT(ret); +} + +/* + * efi_net_stop() - stop the network interface + * + * This function implements the Stop service of the + * EFI_SIMPLE_NETWORK_PROTOCOL. See the Unified Extensible Firmware Interface + * (UEFI) specification for details. + * + * @this: pointer to the protocol instance + * Return: status code + */ +static efi_status_t EFIAPI efi_net_stop(struct efi_simple_network *this) +{ + efi_status_t ret = EFI_SUCCESS; + struct efi_net_obj *nt; + + EFI_ENTRY("%p", this); + + /* Check parameters */ + if (!this) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + nt = efi_netobj_from_snp(this); + + if (this->mode->state == EFI_NETWORK_STOPPED) { + ret = EFI_NOT_STARTED; + } else { + /* Disable hardware and put it into the reset state */ + eth_set_dev(nt->dev); + env_set("ethact", eth_get_name()); + eth_halt(); + /* Clear cache of packets */ + nt->rx_packet_num = 0; + this->mode->state = EFI_NETWORK_STOPPED; + } +out: + return EFI_EXIT(ret); +} + +/* + * efi_net_initialize() - initialize the network interface + * + * This function implements the Initialize service of the + * EFI_SIMPLE_NETWORK_PROTOCOL. See the Unified Extensible Firmware Interface + * (UEFI) specification for details. + * + * @this: pointer to the protocol instance + * @extra_rx: extra receive buffer to be allocated + * @extra_tx: extra transmit buffer to be allocated + * Return: status code + */ +static efi_status_t EFIAPI efi_net_initialize(struct efi_simple_network *this, + ulong extra_rx, ulong extra_tx) +{ + int ret; + efi_status_t r = EFI_SUCCESS; + struct efi_net_obj *nt; + + EFI_ENTRY("%p, %lx, %lx", this, extra_rx, extra_tx); + + /* Check parameters */ + if (!this) { + r = EFI_INVALID_PARAMETER; + goto out; + } + nt = efi_netobj_from_snp(this); + + switch (this->mode->state) { + case EFI_NETWORK_INITIALIZED: + case EFI_NETWORK_STARTED: + break; + default: + r = EFI_NOT_STARTED; + goto out; + } + + /* Setup packet buffers */ + net_init(); + /* Clear cache of packets */ + nt->rx_packet_num = 0; + /* Set the net device corresponding to the efi net object */ + eth_set_dev(nt->dev); + env_set("ethact", eth_get_name()); + /* Get hardware ready for send and receive operations */ + ret = eth_start_udev(nt->dev); + if (ret < 0) { + eth_halt(); + this->mode->state = EFI_NETWORK_STOPPED; + r = EFI_DEVICE_ERROR; + goto out; + } else { + this->int_status = 0; + nt->wait_for_packet->is_signaled = false; + this->mode->state = EFI_NETWORK_INITIALIZED; + } +out: + return EFI_EXIT(r); +} + +/* + * efi_net_reset() - reinitialize the network interface + * + * This function implements the Reset service of the + * EFI_SIMPLE_NETWORK_PROTOCOL. See the Unified Extensible Firmware Interface + * (UEFI) specification for details. + * + * @this: pointer to the protocol instance + * @extended_verification: execute exhaustive verification + * Return: status code + */ +static efi_status_t EFIAPI efi_net_reset(struct efi_simple_network *this, + int extended_verification) +{ + efi_status_t ret; + + EFI_ENTRY("%p, %x", this, extended_verification); + + /* Check parameters */ + if (!this) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + switch (this->mode->state) { + case EFI_NETWORK_INITIALIZED: + break; + case EFI_NETWORK_STOPPED: + ret = EFI_NOT_STARTED; + goto out; + default: + ret = EFI_DEVICE_ERROR; + goto out; + } + + this->mode->state = EFI_NETWORK_STARTED; + ret = EFI_CALL(efi_net_initialize(this, 0, 0)); +out: + return EFI_EXIT(ret); +} + +/* + * efi_net_shutdown() - shut down the network interface + * + * This function implements the Shutdown service of the + * EFI_SIMPLE_NETWORK_PROTOCOL. See the Unified Extensible Firmware Interface + * (UEFI) specification for details. + * + * @this: pointer to the protocol instance + * Return: status code + */ +static efi_status_t EFIAPI efi_net_shutdown(struct efi_simple_network *this) +{ + efi_status_t ret = EFI_SUCCESS; + struct efi_net_obj *nt; + + EFI_ENTRY("%p", this); + + /* Check parameters */ + if (!this) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + nt = efi_netobj_from_snp(this); + + switch (this->mode->state) { + case EFI_NETWORK_INITIALIZED: + break; + case EFI_NETWORK_STOPPED: + ret = EFI_NOT_STARTED; + goto out; + default: + ret = EFI_DEVICE_ERROR; + goto out; + } + + eth_set_dev(nt->dev); + env_set("ethact", eth_get_name()); + eth_halt(); + + this->int_status = 0; + nt->wait_for_packet->is_signaled = false; + this->mode->state = EFI_NETWORK_STARTED; + +out: + return EFI_EXIT(ret); +} + +/* + * efi_net_receive_filters() - mange multicast receive filters + * + * This function implements the ReceiveFilters service of the + * EFI_SIMPLE_NETWORK_PROTOCOL. See the Unified Extensible Firmware Interface + * (UEFI) specification for details. + * + * @this: pointer to the protocol instance + * @enable: bit mask of receive filters to enable + * @disable: bit mask of receive filters to disable + * @reset_mcast_filter: true resets contents of the filters + * @mcast_filter_count: number of hardware MAC addresses in the new filters list + * @mcast_filter: list of new filters + * Return: status code + */ +static efi_status_t EFIAPI efi_net_receive_filters + (struct efi_simple_network *this, u32 enable, u32 disable, + int reset_mcast_filter, ulong mcast_filter_count, + struct efi_mac_address *mcast_filter) +{ + EFI_ENTRY("%p, %x, %x, %x, %lx, %p", this, enable, disable, + reset_mcast_filter, mcast_filter_count, mcast_filter); + + return EFI_EXIT(EFI_UNSUPPORTED); +} + +/* + * efi_net_station_address() - set the hardware MAC address + * + * This function implements the StationAddress service of the + * EFI_SIMPLE_NETWORK_PROTOCOL. See the Unified Extensible Firmware Interface + * (UEFI) specification for details. + * + * @this: pointer to the protocol instance + * @reset: if true reset the address to default + * @new_mac: new MAC address + * Return: status code + */ +static efi_status_t EFIAPI efi_net_station_address + (struct efi_simple_network *this, int reset, + struct efi_mac_address *new_mac) +{ + EFI_ENTRY("%p, %x, %p", this, reset, new_mac); + + return EFI_EXIT(EFI_UNSUPPORTED); +} + +/* + * efi_net_statistics() - reset or collect statistics of the network interface + * + * This function implements the Statistics service of the + * EFI_SIMPLE_NETWORK_PROTOCOL. See the Unified Extensible Firmware Interface + * (UEFI) specification for details. + * + * @this: pointer to the protocol instance + * @reset: if true, the statistics are reset + * @stat_size: size of the statistics table + * @stat_table: table to receive the statistics + * Return: status code + */ +static efi_status_t EFIAPI efi_net_statistics(struct efi_simple_network *this, + int reset, ulong *stat_size, + void *stat_table) +{ + EFI_ENTRY("%p, %x, %p, %p", this, reset, stat_size, stat_table); + + return EFI_EXIT(EFI_UNSUPPORTED); +} + +/* + * efi_net_mcastiptomac() - translate multicast IP address to MAC address + * + * This function implements the MCastIPtoMAC service of the + * EFI_SIMPLE_NETWORK_PROTOCOL. See the Unified Extensible Firmware Interface + * (UEFI) specification for details. + * + * @this: pointer to the protocol instance + * @ipv6: true if the IP address is an IPv6 address + * @ip: IP address + * @mac: MAC address + * Return: status code + */ +static efi_status_t EFIAPI efi_net_mcastiptomac(struct efi_simple_network *this, + int ipv6, + struct efi_ip_address *ip, + struct efi_mac_address *mac) +{ + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p, %x, %p, %p", this, ipv6, ip, mac); + + if (!this || !ip || !mac) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + if (ipv6) { + ret = EFI_UNSUPPORTED; + goto out; + } + + /* Multi-cast addresses are in the range 224.0.0.0 - 239.255.255.255 */ + if ((ip->ip_addr[0] & 0xf0) != 0xe0) { + ret = EFI_INVALID_PARAMETER; + goto out; + }; + + switch (this->mode->state) { + case EFI_NETWORK_INITIALIZED: + case EFI_NETWORK_STARTED: + break; + default: + ret = EFI_NOT_STARTED; + goto out; + } + + memset(mac, 0, sizeof(struct efi_mac_address)); + + /* + * Copy lower 23 bits of IPv4 multi-cast address + * RFC 1112, RFC 7042 2.1.1. + */ + mac->mac_addr[0] = 0x01; + mac->mac_addr[1] = 0x00; + mac->mac_addr[2] = 0x5E; + mac->mac_addr[3] = ip->ip_addr[1] & 0x7F; + mac->mac_addr[4] = ip->ip_addr[2]; + mac->mac_addr[5] = ip->ip_addr[3]; +out: + return EFI_EXIT(ret); +} + +/** + * efi_net_nvdata() - read or write NVRAM + * + * This function implements the GetStatus service of the Simple Network + * Protocol. See the UEFI spec for details. + * + * @this: the instance of the Simple Network Protocol + * @read_write: true for read, false for write + * @offset: offset in NVRAM + * @buffer_size: size of buffer + * @buffer: buffer + * Return: status code + */ +static efi_status_t EFIAPI efi_net_nvdata(struct efi_simple_network *this, + int read_write, ulong offset, + ulong buffer_size, char *buffer) +{ + EFI_ENTRY("%p, %x, %lx, %lx, %p", this, read_write, offset, buffer_size, + buffer); + + return EFI_EXIT(EFI_UNSUPPORTED); +} + +/** + * efi_net_get_status() - get interrupt status + * + * This function implements the GetStatus service of the Simple Network + * Protocol. See the UEFI spec for details. + * + * @this: the instance of the Simple Network Protocol + * @int_status: interface status + * @txbuf: transmission buffer + */ +static efi_status_t EFIAPI efi_net_get_status(struct efi_simple_network *this, + u32 *int_status, void **txbuf) +{ + efi_status_t ret = EFI_SUCCESS; + struct efi_net_obj *nt; + + EFI_ENTRY("%p, %p, %p", this, int_status, txbuf); + + efi_timer_check(); + + /* Check parameters */ + if (!this) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + nt = efi_netobj_from_snp(this); + + switch (this->mode->state) { + case EFI_NETWORK_STOPPED: + ret = EFI_NOT_STARTED; + goto out; + case EFI_NETWORK_STARTED: + ret = EFI_DEVICE_ERROR; + goto out; + default: + break; + } + + if (int_status) { + *int_status = this->int_status; + this->int_status = 0; + } + if (txbuf) + *txbuf = nt->new_tx_packet; + + nt->new_tx_packet = NULL; +out: + return EFI_EXIT(ret); +} + +/** + * efi_net_transmit() - transmit a packet + * + * This function implements the Transmit service of the Simple Network Protocol. + * See the UEFI spec for details. + * + * @this: the instance of the Simple Network Protocol + * @header_size: size of the media header + * @buffer_size: size of the buffer to receive the packet + * @buffer: buffer to receive the packet + * @src_addr: source hardware MAC address + * @dest_addr: destination hardware MAC address + * @protocol: type of header to build + * Return: status code + */ +static efi_status_t EFIAPI efi_net_transmit + (struct efi_simple_network *this, size_t header_size, + size_t buffer_size, void *buffer, + struct efi_mac_address *src_addr, + struct efi_mac_address *dest_addr, u16 *protocol) +{ + efi_status_t ret = EFI_SUCCESS; + struct efi_net_obj *nt; + + EFI_ENTRY("%p, %lu, %lu, %p, %p, %p, %p", this, + (unsigned long)header_size, (unsigned long)buffer_size, + buffer, src_addr, dest_addr, protocol); + + efi_timer_check(); + + /* Check parameters */ + if (!this || !buffer) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + nt = efi_netobj_from_snp(this); + + /* We do not support jumbo packets */ + if (buffer_size > PKTSIZE_ALIGN) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + /* At least the IP header has to fit into the buffer */ + if (buffer_size < this->mode->media_header_size) { + ret = EFI_BUFFER_TOO_SMALL; + goto out; + } + + /* + * TODO: + * Support VLANs. Use net_set_ether() for copying the header. Use a + * U_BOOT_ENV_CALLBACK to update the media header size. + */ + if (header_size) { + struct ethernet_hdr *header = buffer; + + if (!dest_addr || !protocol || + header_size != this->mode->media_header_size) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + if (!src_addr) + src_addr = &this->mode->current_address; + + memcpy(header->et_dest, dest_addr, ARP_HLEN); + memcpy(header->et_src, src_addr, ARP_HLEN); + header->et_protlen = htons(*protocol); + } + + switch (this->mode->state) { + case EFI_NETWORK_STOPPED: + ret = EFI_NOT_STARTED; + goto out; + case EFI_NETWORK_STARTED: + ret = EFI_DEVICE_ERROR; + goto out; + default: + break; + } + + eth_set_dev(nt->dev); + env_set("ethact", eth_get_name()); + + /* Ethernet packets always fit, just bounce */ + memcpy(nt->transmit_buffer, buffer, buffer_size); + net_send_packet(nt->transmit_buffer, buffer_size); + + nt->new_tx_packet = buffer; + this->int_status |= EFI_SIMPLE_NETWORK_TRANSMIT_INTERRUPT; +out: + return EFI_EXIT(ret); +} + +/** + * efi_net_receive() - receive a packet from a network interface + * + * This function implements the Receive service of the Simple Network Protocol. + * See the UEFI spec for details. + * + * @this: the instance of the Simple Network Protocol + * @header_size: size of the media header + * @buffer_size: size of the buffer to receive the packet + * @buffer: buffer to receive the packet + * @src_addr: source MAC address + * @dest_addr: destination MAC address + * @protocol: protocol + * Return: status code + */ +static efi_status_t EFIAPI efi_net_receive + (struct efi_simple_network *this, size_t *header_size, + size_t *buffer_size, void *buffer, + struct efi_mac_address *src_addr, + struct efi_mac_address *dest_addr, u16 *protocol) +{ + efi_status_t ret = EFI_SUCCESS; + struct ethernet_hdr *eth_hdr; + size_t hdr_size = sizeof(struct ethernet_hdr); + u16 protlen; + struct efi_net_obj *nt; + + EFI_ENTRY("%p, %p, %p, %p, %p, %p, %p", this, header_size, + buffer_size, buffer, src_addr, dest_addr, protocol); + + /* Execute events */ + efi_timer_check(); + + /* Check parameters */ + if (!this || !buffer || !buffer_size) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + nt = efi_netobj_from_snp(this); + + switch (this->mode->state) { + case EFI_NETWORK_STOPPED: + ret = EFI_NOT_STARTED; + goto out; + case EFI_NETWORK_STARTED: + ret = EFI_DEVICE_ERROR; + goto out; + default: + break; + } + + if (!nt->rx_packet_num) { + ret = EFI_NOT_READY; + goto out; + } + /* Fill export parameters */ + eth_hdr = (struct ethernet_hdr *)nt->receive_buffer[nt->rx_packet_idx]; + protlen = ntohs(eth_hdr->et_protlen); + if (protlen == 0x8100) { + hdr_size += 4; + protlen = ntohs(*(u16 *)&nt->receive_buffer[nt->rx_packet_idx][hdr_size - 2]); + } + if (header_size) + *header_size = hdr_size; + if (dest_addr) + memcpy(dest_addr, eth_hdr->et_dest, ARP_HLEN); + if (src_addr) + memcpy(src_addr, eth_hdr->et_src, ARP_HLEN); + if (protocol) + *protocol = protlen; + if (*buffer_size < nt->receive_lengths[nt->rx_packet_idx]) { + /* Packet doesn't fit, try again with bigger buffer */ + *buffer_size = nt->receive_lengths[nt->rx_packet_idx]; + ret = EFI_BUFFER_TOO_SMALL; + goto out; + } + /* Copy packet */ + memcpy(buffer, nt->receive_buffer[nt->rx_packet_idx], + nt->receive_lengths[nt->rx_packet_idx]); + *buffer_size = nt->receive_lengths[nt->rx_packet_idx]; + nt->rx_packet_idx = (nt->rx_packet_idx + 1) % ETH_PACKETS_BATCH_RECV; + nt->rx_packet_num--; + if (nt->rx_packet_num) + nt->wait_for_packet->is_signaled = true; + else + this->int_status &= ~EFI_SIMPLE_NETWORK_RECEIVE_INTERRUPT; +out: + return EFI_EXIT(ret); +} + +/** + * efi_net_set_dhcp_ack() - take note of a selected DHCP IP address + * + * This function is called by dhcp_handler(). + * + * @pkt: packet received by dhcp_handler() + * @len: length of the packet received + */ +void efi_net_set_dhcp_ack(void *pkt, int len) +{ + struct efi_pxe_packet **dhcp_ack; + struct udevice *dev; + int i; + + dhcp_ack = &dhcp_cache[next_dhcp_entry].dhcp_ack; + + /* For now this function gets called only by the current device */ + dev = eth_get_dev(); + + int maxsize = sizeof(**dhcp_ack); + + if (!*dhcp_ack) { + *dhcp_ack = malloc(maxsize); + if (!*dhcp_ack) + return; + } + memset(*dhcp_ack, 0, maxsize); + memcpy(*dhcp_ack, pkt, min(len, maxsize)); + + dhcp_cache[next_dhcp_entry].is_valid = true; + dhcp_cache[next_dhcp_entry].dev = dev; + next_dhcp_entry++; + next_dhcp_entry %= MAX_NUM_DHCP_ENTRIES; + + for (i = 0; i < MAX_EFI_NET_OBJS; i++) { + if (net_objs[i] && net_objs[i]->dev == dev) { + net_objs[i]->pxe_mode.dhcp_ack = **dhcp_ack; + } + } +} + +/** + * efi_net_push() - callback for received network packet + * + * This function is called when a network packet is received by eth_rx(). + * + * @pkt: network packet + * @len: length + */ +static void efi_net_push(void *pkt, int len) +{ + int rx_packet_next; + struct efi_net_obj *nt; + + nt = net_objs[curr_efi_net_obj]; + if (!nt) + return; + + /* Check that we at least received an Ethernet header */ + if (len < sizeof(struct ethernet_hdr)) + return; + + /* Check that the buffer won't overflow */ + if (len > PKTSIZE_ALIGN) + return; + + /* Can't store more than pre-alloced buffer */ + if (nt->rx_packet_num >= ETH_PACKETS_BATCH_RECV) + return; + + rx_packet_next = (nt->rx_packet_idx + nt->rx_packet_num) % + ETH_PACKETS_BATCH_RECV; + memcpy(nt->receive_buffer[rx_packet_next], pkt, len); + nt->receive_lengths[rx_packet_next] = len; + + nt->rx_packet_num++; +} + +/** + * efi_network_timer_notify() - check if a new network packet has been received + * + * This notification function is called in every timer cycle. + * + * @event: the event for which this notification function is registered + * @context: event context - not used in this function + */ +static void EFIAPI efi_network_timer_notify(struct efi_event *event, + void *context) +{ + struct efi_simple_network *this = (struct efi_simple_network *)context; + struct efi_net_obj *nt; + + EFI_ENTRY("%p, %p", event, context); + + /* + * Some network drivers do not support calling eth_rx() before + * initialization. + */ + if (!this || this->mode->state != EFI_NETWORK_INITIALIZED) + goto out; + + nt = efi_netobj_from_snp(this); + curr_efi_net_obj = nt->efi_seq_num; + + if (!nt->rx_packet_num) { + eth_set_dev(nt->dev); + env_set("ethact", eth_get_name()); + push_packet = efi_net_push; + eth_rx(); + push_packet = NULL; + if (nt->rx_packet_num) { + this->int_status |= + EFI_SIMPLE_NETWORK_RECEIVE_INTERRUPT; + nt->wait_for_packet->is_signaled = true; + } + } +out: + EFI_EXIT(EFI_SUCCESS); +} + +static efi_status_t EFIAPI efi_pxe_base_code_start( + struct efi_pxe_base_code_protocol *this, + u8 use_ipv6) +{ + return EFI_UNSUPPORTED; +} + +static efi_status_t EFIAPI efi_pxe_base_code_stop( + struct efi_pxe_base_code_protocol *this) +{ + return EFI_UNSUPPORTED; +} + +static efi_status_t EFIAPI efi_pxe_base_code_dhcp( + struct efi_pxe_base_code_protocol *this, + u8 sort_offers) +{ + return EFI_UNSUPPORTED; +} + +static efi_status_t EFIAPI efi_pxe_base_code_discover( + struct efi_pxe_base_code_protocol *this, + u16 type, u16 *layer, u8 bis, + struct efi_pxe_base_code_discover_info *info) +{ + return EFI_UNSUPPORTED; +} + +static efi_status_t EFIAPI efi_pxe_base_code_mtftp( + struct efi_pxe_base_code_protocol *this, + u32 operation, void *buffer_ptr, + u8 overwrite, efi_uintn_t *buffer_size, + struct efi_ip_address server_ip, char *filename, + struct efi_pxe_base_code_mtftp_info *info, + u8 dont_use_buffer) +{ + return EFI_UNSUPPORTED; +} + +static efi_status_t EFIAPI efi_pxe_base_code_udp_write( + struct efi_pxe_base_code_protocol *this, + u16 op_flags, struct efi_ip_address *dest_ip, + u16 *dest_port, + struct efi_ip_address *gateway_ip, + struct efi_ip_address *src_ip, u16 *src_port, + efi_uintn_t *header_size, void *header_ptr, + efi_uintn_t *buffer_size, void *buffer_ptr) +{ + return EFI_UNSUPPORTED; +} + +static efi_status_t EFIAPI efi_pxe_base_code_udp_read( + struct efi_pxe_base_code_protocol *this, + u16 op_flags, struct efi_ip_address *dest_ip, + u16 *dest_port, struct efi_ip_address *src_ip, + u16 *src_port, efi_uintn_t *header_size, + void *header_ptr, efi_uintn_t *buffer_size, + void *buffer_ptr) +{ + return EFI_UNSUPPORTED; +} + +static efi_status_t EFIAPI efi_pxe_base_code_set_ip_filter( + struct efi_pxe_base_code_protocol *this, + struct efi_pxe_base_code_filter *new_filter) +{ + return EFI_UNSUPPORTED; +} + +static efi_status_t EFIAPI efi_pxe_base_code_arp( + struct efi_pxe_base_code_protocol *this, + struct efi_ip_address *ip_addr, + struct efi_mac_address *mac_addr) +{ + return EFI_UNSUPPORTED; +} + +static efi_status_t EFIAPI efi_pxe_base_code_set_parameters( + struct efi_pxe_base_code_protocol *this, + u8 *new_auto_arp, u8 *new_send_guid, + u8 *new_ttl, u8 *new_tos, + u8 *new_make_callback) +{ + return EFI_UNSUPPORTED; +} + +static efi_status_t EFIAPI efi_pxe_base_code_set_station_ip( + struct efi_pxe_base_code_protocol *this, + struct efi_ip_address *new_station_ip, + struct efi_ip_address *new_subnet_mask) +{ + return EFI_UNSUPPORTED; +} + +static efi_status_t EFIAPI efi_pxe_base_code_set_packets( + struct efi_pxe_base_code_protocol *this, + u8 *new_dhcp_discover_valid, + u8 *new_dhcp_ack_received, + u8 *new_proxy_offer_received, + u8 *new_pxe_discover_valid, + u8 *new_pxe_reply_received, + u8 *new_pxe_bis_reply_received, + EFI_PXE_BASE_CODE_PACKET *new_dchp_discover, + EFI_PXE_BASE_CODE_PACKET *new_dhcp_acc, + EFI_PXE_BASE_CODE_PACKET *new_proxy_offer, + EFI_PXE_BASE_CODE_PACKET *new_pxe_discover, + EFI_PXE_BASE_CODE_PACKET *new_pxe_reply, + EFI_PXE_BASE_CODE_PACKET *new_pxe_bis_reply) +{ + return EFI_UNSUPPORTED; +} + +/** + * efi_netobj_set_dp() - set device path of a netobj + * + * @netobj: pointer to efi_net_obj + * @dp: device path to set, allocated by caller + * Return: status code + */ +efi_status_t efi_netobj_set_dp(struct efi_net_obj *netobj, struct efi_device_path *dp) +{ + efi_status_t ret; + struct efi_handler *phandler; + struct efi_device_path *new_net_dp; + + if (!efi_netobj_is_active(netobj)) + return EFI_SUCCESS; + + // Create a device path for the netobj + new_net_dp = dp; + if (!new_net_dp) + return EFI_OUT_OF_RESOURCES; + + phandler = NULL; + efi_search_protocol(&netobj->header, &efi_guid_device_path, &phandler); + + // If the device path protocol is not yet installed, install it + if (!phandler) + goto add; + + // If it is already installed, try to update it + ret = efi_reinstall_protocol_interface(&netobj->header, &efi_guid_device_path, + phandler->protocol_interface, new_net_dp); + if (ret != EFI_SUCCESS) + return ret; + + return EFI_SUCCESS; +add: + ret = efi_add_protocol(&netobj->header, &efi_guid_device_path, + new_net_dp); + if (ret != EFI_SUCCESS) + return ret; + + return EFI_SUCCESS; +} + +/** + * efi_netobj_get_dp() - get device path of a netobj + * + * @netobj: pointer to efi_net_obj + * Return: device path, NULL on error + */ +static struct efi_device_path *efi_netobj_get_dp(struct efi_net_obj *netobj) +{ + struct efi_handler *phandler; + + if (!efi_netobj_is_active(netobj)) + return NULL; + + phandler = NULL; + efi_search_protocol(&netobj->header, &efi_guid_device_path, &phandler); + + if (phandler && phandler->protocol_interface) + return efi_dp_dup(phandler->protocol_interface); + + return NULL; +} + +/** + * efi_net_do_start() - start the efi network stack + * + * This gets called from do_bootefi_exec() each time a payload gets executed. + * + * @dev: net udevice + * Return: status code + */ +efi_status_t efi_net_do_start(struct udevice *dev) +{ + efi_status_t r = EFI_SUCCESS; + struct efi_net_obj *netobj; + struct efi_device_path *net_dp; + int i; + + netobj = NULL; + for (i = 0; i < MAX_EFI_NET_OBJS; i++) { + if (net_objs[i] && net_objs[i]->dev == dev) { + netobj = net_objs[i]; + break; + } + } + + if (!efi_netobj_is_active(netobj)) + return r; + + efi_net_dp_from_dev(&net_dp, netobj->dev, true); + // If no dp cache entry applies and there already + // is a device path installed, continue + if (!net_dp) { + if (efi_netobj_get_dp(netobj)) + goto set_addr; + else + net_dp = efi_dp_from_eth(netobj->dev); + + } + + if (!net_dp) + return EFI_OUT_OF_RESOURCES; + + r = efi_netobj_set_dp(netobj, net_dp); + if (r != EFI_SUCCESS) + return r; +set_addr: +#ifdef CONFIG_EFI_HTTP_PROTOCOL + /* + * No harm on doing the following. If the PXE handle is present, the client could + * find it and try to get its IP address from it. In here the PXE handle is present + * but the PXE protocol is not yet implmenented, so we add this in the meantime. + */ + efi_net_get_addr((struct efi_ipv4_address *)&netobj->pxe_mode.station_ip, + (struct efi_ipv4_address *)&netobj->pxe_mode.subnet_mask, NULL, dev); +#endif + + return r; +} + +/** + * efi_net_register() - register the simple network protocol + * + * This gets called from do_bootefi_exec(). + * @dev: net udevice + */ +efi_status_t efi_net_register(struct udevice *dev) +{ + efi_status_t r; + int seq_num; + struct efi_net_obj *netobj; + void *transmit_buffer = NULL; + uchar **receive_buffer = NULL; + size_t *receive_lengths; + int i, j; + + if (!dev) { + /* No network device active, don't expose any */ + return EFI_SUCCESS; + } + + for (i = 0; i < MAX_EFI_NET_OBJS; i++) { + if (net_objs[i] && net_objs[i]->dev == dev) { + // Do not register duplicate devices + return EFI_SUCCESS; + } + } + + seq_num = -1; + for (i = 0; i < MAX_EFI_NET_OBJS; i++) { + if (!net_objs[i]) { + seq_num = i; + break; + } + } + if (seq_num < 0) + return EFI_OUT_OF_RESOURCES; + + /* We only expose the "active" network device, so one is enough */ + netobj = calloc(1, sizeof(*netobj)); + if (!netobj) + goto out_of_resources; + + netobj->dev = dev; + + /* Allocate an aligned transmit buffer */ + transmit_buffer = calloc(1, PKTSIZE_ALIGN + PKTALIGN); + if (!transmit_buffer) + goto out_of_resources; + transmit_buffer = (void *)ALIGN((uintptr_t)transmit_buffer, PKTALIGN); + netobj->transmit_buffer = transmit_buffer; + + /* Allocate a number of receive buffers */ + receive_buffer = calloc(ETH_PACKETS_BATCH_RECV, + sizeof(*receive_buffer)); + if (!receive_buffer) + goto out_of_resources; + for (i = 0; i < ETH_PACKETS_BATCH_RECV; i++) { + receive_buffer[i] = malloc(PKTSIZE_ALIGN); + if (!receive_buffer[i]) + goto out_of_resources; + } + netobj->receive_buffer = receive_buffer; + + receive_lengths = calloc(ETH_PACKETS_BATCH_RECV, + sizeof(*receive_lengths)); + if (!receive_lengths) + goto out_of_resources; + netobj->receive_lengths = receive_lengths; + + /* Hook net up to the device list */ + efi_add_handle(&netobj->header); + + /* Fill in object data */ + r = efi_add_protocol(&netobj->header, &efi_net_guid, + &netobj->net); + if (r != EFI_SUCCESS) + goto failure_to_add_protocol; + + r = efi_add_protocol(&netobj->header, &efi_pxe_base_code_protocol_guid, + &netobj->pxe); + if (r != EFI_SUCCESS) + goto failure_to_add_protocol; + netobj->net.revision = EFI_SIMPLE_NETWORK_PROTOCOL_REVISION; + netobj->net.start = efi_net_start; + netobj->net.stop = efi_net_stop; + netobj->net.initialize = efi_net_initialize; + netobj->net.reset = efi_net_reset; + netobj->net.shutdown = efi_net_shutdown; + netobj->net.receive_filters = efi_net_receive_filters; + netobj->net.station_address = efi_net_station_address; + netobj->net.statistics = efi_net_statistics; + netobj->net.mcastiptomac = efi_net_mcastiptomac; + netobj->net.nvdata = efi_net_nvdata; + netobj->net.get_status = efi_net_get_status; + netobj->net.transmit = efi_net_transmit; + netobj->net.receive = efi_net_receive; + netobj->net.mode = &netobj->net_mode; + netobj->net_mode.state = EFI_NETWORK_STOPPED; + if (dev_get_plat(dev)) + memcpy(netobj->net_mode.current_address.mac_addr, + ((struct eth_pdata *)dev_get_plat(dev))->enetaddr, 6); + netobj->net_mode.hwaddr_size = ARP_HLEN; + netobj->net_mode.media_header_size = ETHER_HDR_SIZE; + netobj->net_mode.max_packet_size = PKTSIZE; + netobj->net_mode.if_type = ARP_ETHER; + + netobj->pxe.revision = EFI_PXE_BASE_CODE_PROTOCOL_REVISION; + netobj->pxe.start = efi_pxe_base_code_start; + netobj->pxe.stop = efi_pxe_base_code_stop; + netobj->pxe.dhcp = efi_pxe_base_code_dhcp; + netobj->pxe.discover = efi_pxe_base_code_discover; + netobj->pxe.mtftp = efi_pxe_base_code_mtftp; + netobj->pxe.udp_write = efi_pxe_base_code_udp_write; + netobj->pxe.udp_read = efi_pxe_base_code_udp_read; + netobj->pxe.set_ip_filter = efi_pxe_base_code_set_ip_filter; + netobj->pxe.arp = efi_pxe_base_code_arp; + netobj->pxe.set_parameters = efi_pxe_base_code_set_parameters; + netobj->pxe.set_station_ip = efi_pxe_base_code_set_station_ip; + netobj->pxe.set_packets = efi_pxe_base_code_set_packets; + netobj->pxe.mode = &netobj->pxe_mode; + + /* + * Scan dhcp entries for one corresponding + * to this udevice, from newest to oldest + */ + i = (next_dhcp_entry + MAX_NUM_DHCP_ENTRIES - 1) % MAX_NUM_DHCP_ENTRIES; + for (j = 0; dhcp_cache[i].is_valid && j < MAX_NUM_DHCP_ENTRIES; + i = (i + MAX_NUM_DHCP_ENTRIES - 1) % MAX_NUM_DHCP_ENTRIES, j++) { + if (dev == dhcp_cache[i].dev) { + netobj->pxe_mode.dhcp_ack = *dhcp_cache[i].dhcp_ack; + break; + } + } + + /* + * Create WaitForPacket event. + */ + r = efi_create_event(EVT_NOTIFY_WAIT, TPL_CALLBACK, + efi_network_timer_notify, NULL, NULL, + &netobj->wait_for_packet); + if (r != EFI_SUCCESS) { + printf("ERROR: Failed to register network event\n"); + return r; + } + netobj->net.wait_for_packet = netobj->wait_for_packet; + /* + * Create a timer event. + * + * The notification function is used to check if a new network packet + * has been received. + * + * iPXE is running at TPL_CALLBACK most of the time. Use a higher TPL. + */ + r = efi_create_event(EVT_TIMER | EVT_NOTIFY_SIGNAL, TPL_NOTIFY, + efi_network_timer_notify, &netobj->net, NULL, + &netobj->network_timer_event); + if (r != EFI_SUCCESS) { + printf("ERROR: Failed to register network event\n"); + return r; + } + /* Network is time critical, create event in every timer cycle */ + r = efi_set_timer(netobj->network_timer_event, EFI_TIMER_PERIODIC, 0); + if (r != EFI_SUCCESS) { + printf("ERROR: Failed to set network timer\n"); + return r; + } + +#if IS_ENABLED(CONFIG_EFI_IP4_CONFIG2_PROTOCOL) + r = efi_ipconfig_register(&netobj->header, &netobj->ip4_config2); + if (r != EFI_SUCCESS) + goto failure_to_add_protocol; +#endif + +#ifdef CONFIG_EFI_HTTP_PROTOCOL + r = efi_http_register(&netobj->header, &netobj->http_service_binding); + if (r != EFI_SUCCESS) + goto failure_to_add_protocol; +#endif + netobj->efi_seq_num = seq_num; + net_objs[seq_num] = netobj; + return EFI_SUCCESS; +failure_to_add_protocol: + printf("ERROR: Failure to add protocol\n"); + return r; +out_of_resources: + free(netobj); + netobj = NULL; + free(transmit_buffer); + if (receive_buffer) + for (i = 0; i < ETH_PACKETS_BATCH_RECV; i++) + free(receive_buffer[i]); + free(receive_buffer); + free(receive_lengths); + printf("ERROR: Out of memory\n"); + return EFI_OUT_OF_RESOURCES; +} + +/** + * efi_net_new_dp() - update device path associated to a net udevice + * + * This gets called to update the device path when a new boot + * file is downloaded + * + * @dev: dev to set the device path from + * @server: remote server address + * @udev: net udevice + * Return: status code + */ +efi_status_t efi_net_new_dp(const char *dev, const char *server, struct udevice *udev) +{ + efi_status_t ret; + struct efi_net_obj *netobj; + struct efi_device_path *old_net_dp, *new_net_dp; + struct efi_device_path **dp; + int i; + + dp = &dp_cache[next_dp_entry].net_dp; + + dp_cache[next_dp_entry].dev = udev; + dp_cache[next_dp_entry].is_valid = true; + next_dp_entry++; + next_dp_entry %= MAX_NUM_DP_ENTRIES; + + old_net_dp = *dp; + new_net_dp = NULL; + if (!strcmp(dev, "Net")) + new_net_dp = efi_dp_from_eth(udev); + else if (!strcmp(dev, "Http")) + new_net_dp = efi_dp_from_http(server, udev); + if (!new_net_dp) + return EFI_OUT_OF_RESOURCES; + + *dp = new_net_dp; + // Free the old cache entry + efi_free_pool(old_net_dp); + + netobj = NULL; + for (i = 0; i < MAX_EFI_NET_OBJS; i++) { + if (net_objs[i] && net_objs[i]->dev == udev) { + netobj = net_objs[i]; + break; + } + } + if (!netobj) + return EFI_SUCCESS; + + new_net_dp = efi_dp_dup(*dp); + if (!new_net_dp) + return EFI_OUT_OF_RESOURCES; + ret = efi_netobj_set_dp(netobj, new_net_dp); + if (ret != EFI_SUCCESS) + efi_free_pool(new_net_dp); + + return ret; +} + +/** + * efi_net_dp_from_dev() - get device path associated to a net udevice + * + * Produce a copy of the current device path + * + * @dp: copy of the current device path + * @udev: net udevice + * @cache_only: get device path from cache only + */ +void efi_net_dp_from_dev(struct efi_device_path **dp, struct udevice *udev, bool cache_only) +{ + int i, j; + + if (!dp) + return; + + *dp = NULL; + + if (cache_only) + goto cache; + + // If a netobj matches: + for (i = 0; i < MAX_EFI_NET_OBJS; i++) { + if (net_objs[i] && net_objs[i]->dev == udev) { + *dp = efi_netobj_get_dp(net_objs[i]); + if (*dp) + return; + } + } +cache: + // Search in the cache + i = (next_dp_entry + MAX_NUM_DP_ENTRIES - 1) % MAX_NUM_DP_ENTRIES; + for (j = 0; dp_cache[i].is_valid && j < MAX_NUM_DP_ENTRIES; + i = (i + MAX_NUM_DP_ENTRIES - 1) % MAX_NUM_DP_ENTRIES, j++) { + if (dp_cache[i].dev == udev) { + *dp = efi_dp_dup(dp_cache[i].net_dp); + return; + } + } +} + +/** + * efi_net_get_addr() - get IP address information + * + * Copy the current IP address, mask, and gateway into the + * efi_ipv4_address structs pointed to by ip, mask and gw, + * respectively. + * + * @ip: pointer to an efi_ipv4_address struct to + * be filled with the current IP address + * @mask: pointer to an efi_ipv4_address struct to + * be filled with the current network mask + * @gw: pointer to an efi_ipv4_address struct to be + * filled with the current network gateway + * @dev: udevice + */ +void efi_net_get_addr(struct efi_ipv4_address *ip, + struct efi_ipv4_address *mask, + struct efi_ipv4_address *gw, + struct udevice *dev) +{ + if (!dev) + dev = eth_get_dev(); +#ifdef CONFIG_NET_LWIP + char ipstr[] = "ipaddr\0\0"; + char maskstr[] = "netmask\0\0"; + char gwstr[] = "gatewayip\0\0"; + int idx; + struct in_addr tmp; + char *env; + + idx = dev_seq(dev); + + if (idx < 0 || idx > 99) { + log_err("unexpected idx %d\n", idx); + return; + } + + if (idx) { + sprintf(ipstr, "ipaddr%d", idx); + sprintf(maskstr, "netmask%d", idx); + sprintf(gwstr, "gatewayip%d", idx); + } + + env = env_get(ipstr); + if (env && ip) { + tmp = string_to_ip(env); + memcpy(ip, &tmp, sizeof(tmp)); + } + + env = env_get(maskstr); + if (env && mask) { + tmp = string_to_ip(env); + memcpy(mask, &tmp, sizeof(tmp)); + } + env = env_get(gwstr); + if (env && gw) { + tmp = string_to_ip(env); + memcpy(gw, &tmp, sizeof(tmp)); + } +#else + if (ip) + memcpy(ip, &net_ip, sizeof(net_ip)); + if (mask) + memcpy(mask, &net_netmask, sizeof(net_netmask)); +#endif +} + +/** + * efi_net_set_addr() - set IP address information + * + * Set the current IP address, mask, and gateway to the + * efi_ipv4_address structs pointed to by ip, mask and gw, + * respectively. + * + * @ip: pointer to new IP address + * @mask: pointer to new network mask to set + * @gw: pointer to new network gateway + * @dev: udevice + */ +void efi_net_set_addr(struct efi_ipv4_address *ip, + struct efi_ipv4_address *mask, + struct efi_ipv4_address *gw, + struct udevice *dev) +{ + if (!dev) + dev = eth_get_dev(); +#ifdef CONFIG_NET_LWIP + char ipstr[] = "ipaddr\0\0"; + char maskstr[] = "netmask\0\0"; + char gwstr[] = "gatewayip\0\0"; + int idx; + struct in_addr *addr; + char tmp[46]; + + idx = dev_seq(dev); + + if (idx < 0 || idx > 99) { + log_err("unexpected idx %d\n", idx); + return; + } + + if (idx) { + sprintf(ipstr, "ipaddr%d", idx); + sprintf(maskstr, "netmask%d", idx); + sprintf(gwstr, "gatewayip%d", idx); + } + + if (ip) { + addr = (struct in_addr *)ip; + ip_to_string(*addr, tmp); + env_set(ipstr, tmp); + } + + if (mask) { + addr = (struct in_addr *)mask; + ip_to_string(*addr, tmp); + env_set(maskstr, tmp); + } + + if (gw) { + addr = (struct in_addr *)gw; + ip_to_string(*addr, tmp); + env_set(gwstr, tmp); + } +#else + if (ip) + memcpy(&net_ip, ip, sizeof(*ip)); + if (mask) + memcpy(&net_netmask, mask, sizeof(*mask)); +#endif +} + +#if IS_ENABLED(CONFIG_EFI_HTTP_PROTOCOL) +/** + * efi_net_set_buffer() - allocate a buffer of min 64K + * + * @buffer: allocated buffer + * @size: desired buffer size + * Return: status code + */ +static efi_status_t efi_net_set_buffer(void **buffer, size_t size) +{ + efi_status_t ret = EFI_SUCCESS; + + if (size < SZ_64K) + size = SZ_64K; + + *buffer = efi_alloc(size); + if (!*buffer) + ret = EFI_OUT_OF_RESOURCES; + + efi_wget_info.buffer_size = (ulong)size; + + return ret; +} + +/** + * efi_net_parse_headers() - parse HTTP headers + * + * Parses the raw buffer efi_wget_info.headers into an array headers + * of efi structs http_headers. The array should be at least + * MAX_HTTP_HEADERS long. + * + * @num_headers: number of headers + * @headers: caller provided array of struct http_headers + */ +void efi_net_parse_headers(ulong *num_headers, struct http_header *headers) +{ + if (!num_headers || !headers) + return; + + // Populate info with http headers. + *num_headers = 0; + const uchar *line_start = efi_wget_info.headers; + const uchar *line_end; + ulong count; + struct http_header *current_header; + const uchar *separator; + size_t name_length, value_length; + + // Skip the first line (request or status line) + line_end = strstr(line_start, "\r\n"); + + if (line_end) + line_start = line_end + 2; + + while ((line_end = strstr(line_start, "\r\n")) != NULL) { + count = *num_headers; + if (line_start == line_end || count >= MAX_HTTP_HEADERS) + break; + current_header = headers + count; + separator = strchr(line_start, ':'); + if (separator) { + name_length = separator - line_start; + ++separator; + while (*separator == ' ') + ++separator; + value_length = line_end - separator; + if (name_length < MAX_HTTP_HEADER_NAME && + value_length < MAX_HTTP_HEADER_VALUE) { + strncpy(current_header->name, line_start, name_length); + current_header->name[name_length] = '\0'; + strncpy(current_header->value, separator, value_length); + current_header->value[value_length] = '\0'; + (*num_headers)++; + } + } + line_start = line_end + 2; + } +} + +/** + * efi_net_do_request() - issue an HTTP request using wget + * + * @url: url + * @method: HTTP method + * @buffer: data buffer + * @status_code: HTTP status code + * @file_size: file size in bytes + * @headers_buffer: headers buffer + * @parent: service binding protocol + * Return: status code + */ +efi_status_t efi_net_do_request(u8 *url, enum efi_http_method method, void **buffer, + u32 *status_code, ulong *file_size, char *headers_buffer, + struct efi_service_binding_protocol *parent) +{ + efi_status_t ret = EFI_SUCCESS; + int wget_ret; + static bool last_head; + struct udevice *dev; + int i; + + if (!buffer || !file_size || !parent) + return EFI_ABORTED; + + efi_wget_info.method = (enum wget_http_method)method; + efi_wget_info.headers = headers_buffer; + + // Set corresponding udevice + dev = NULL; + for (i = 0; i < MAX_EFI_NET_OBJS; i++) { + if (net_objs[i] && &net_objs[i]->http_service_binding == parent) + dev = net_objs[i]->dev; + } + if (!dev) + return EFI_ABORTED; + + switch (method) { + case HTTP_METHOD_GET: + ret = efi_net_set_buffer(buffer, last_head ? (size_t)efi_wget_info.hdr_cont_len : 0); + if (ret != EFI_SUCCESS) + goto out; + eth_set_dev(dev); + env_set("ethact", eth_get_name()); + wget_ret = wget_request((ulong)*buffer, url, &efi_wget_info); + if ((ulong)efi_wget_info.hdr_cont_len > efi_wget_info.buffer_size) { + // Try again with updated buffer size + efi_free_pool(*buffer); + ret = efi_net_set_buffer(buffer, (size_t)efi_wget_info.hdr_cont_len); + if (ret != EFI_SUCCESS) + goto out; + eth_set_dev(dev); + env_set("ethact", eth_get_name()); + if (wget_request((ulong)*buffer, url, &efi_wget_info)) { + efi_free_pool(*buffer); + ret = EFI_DEVICE_ERROR; + goto out; + } + } else if (wget_ret) { + efi_free_pool(*buffer); + ret = EFI_DEVICE_ERROR; + goto out; + } + // Pass the actual number of received bytes to the application + *file_size = efi_wget_info.file_size; + *status_code = efi_wget_info.status_code; + last_head = false; + break; + case HTTP_METHOD_HEAD: + ret = efi_net_set_buffer(buffer, 0); + if (ret != EFI_SUCCESS) + goto out; + eth_set_dev(dev); + env_set("ethact", eth_get_name()); + wget_request((ulong)*buffer, url, &efi_wget_info); + *file_size = 0; + *status_code = efi_wget_info.status_code; + last_head = true; + break; + default: + ret = EFI_UNSUPPORTED; + break; + } + +out: + return ret; +} +#endif diff --git a/lib/efi_loader/efi_riscv.c b/lib/efi_loader/efi_riscv.c new file mode 100644 index 00000000000..6f2ccf4f7e6 --- /dev/null +++ b/lib/efi_loader/efi_riscv.c @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Defines APIs that allow an OS to interact with UEFI firmware to query + * information about the boot hart ID. + * + * Copyright (c) 2022, Ventana Micro Systems Inc + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <efi_loader.h> +#include <efi_variable.h> +#include <log.h> +#include <asm/global_data.h> +#include <efi_riscv.h> + +DECLARE_GLOBAL_DATA_PTR; + +static const efi_guid_t efi_guid_riscv_boot_protocol = RISCV_EFI_BOOT_PROTOCOL_GUID; + +/** + * efi_riscv_get_boot_hartid() - return boot hart ID + * @this: RISCV_EFI_BOOT_PROTOCOL instance + * @boot_hartid: caller allocated memory to return boot hart id + * Return: status code + */ +static efi_status_t EFIAPI +efi_riscv_get_boot_hartid(struct riscv_efi_boot_protocol *this, + efi_uintn_t *boot_hartid) +{ + EFI_ENTRY("%p, %p", this, boot_hartid); + + if (this != &riscv_efi_boot_prot || !boot_hartid) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + *boot_hartid = gd->arch.boot_hart; + + return EFI_EXIT(EFI_SUCCESS); +} + +struct riscv_efi_boot_protocol riscv_efi_boot_prot = { + .revision = RISCV_EFI_BOOT_PROTOCOL_REVISION, + .get_boot_hartid = efi_riscv_get_boot_hartid +}; + +/** + * efi_riscv_register() - register RISCV_EFI_BOOT_PROTOCOL + * + * Return: status code + */ +efi_status_t efi_riscv_register(void) +{ + efi_status_t ret = EFI_SUCCESS; + + ret = efi_add_protocol(efi_root, &efi_guid_riscv_boot_protocol, + (void *)&riscv_efi_boot_prot); + if (ret != EFI_SUCCESS) + log_err("Cannot install RISCV_EFI_BOOT_PROTOCOL\n"); + return ret; +} diff --git a/lib/efi_loader/efi_rng.c b/lib/efi_loader/efi_rng.c new file mode 100644 index 00000000000..4734f95eee1 --- /dev/null +++ b/lib/efi_loader/efi_rng.c @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2019, Linaro Limited + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <dm.h> +#include <efi_loader.h> +#include <efi_rng.h> +#include <log.h> +#include <rng.h> +#include <asm/global_data.h> + +DECLARE_GLOBAL_DATA_PTR; + +const efi_guid_t efi_guid_rng_protocol = EFI_RNG_PROTOCOL_GUID; + +/** + * platform_get_rng_device() - retrieve random number generator + * + * This function retrieves the udevice implementing a hardware random + * number generator. + * + * This function may be overridden if special initialization is needed. + * + * @dev: udevice + * Return: status code + */ +__weak efi_status_t platform_get_rng_device(struct udevice **dev) +{ + int ret; + struct udevice *devp; + + ret = uclass_get_device(UCLASS_RNG, 0, &devp); + if (ret) { + debug("Unable to get rng device\n"); + return EFI_DEVICE_ERROR; + } + + *dev = devp; + + return EFI_SUCCESS; +} + +/** + * rng_getinfo() - get information about random number generation + * + * This function implement the GetInfo() service of the EFI random number + * generator protocol. See the UEFI spec for details. + * + * @this: random number generator protocol instance + * @rng_algorithm_list_size: number of random number generation algorithms + * @rng_algorithm_list: descriptions of random number generation + * algorithms + * Return: status code + */ +static efi_status_t EFIAPI rng_getinfo(struct efi_rng_protocol *this, + efi_uintn_t *rng_algorithm_list_size, + efi_guid_t *rng_algorithm_list) +{ + efi_status_t ret = EFI_SUCCESS; + efi_guid_t rng_algo_guid = EFI_RNG_ALGORITHM_RAW; + + EFI_ENTRY("%p, %p, %p", this, rng_algorithm_list_size, + rng_algorithm_list); + + if (!this || !rng_algorithm_list_size) { + ret = EFI_INVALID_PARAMETER; + goto back; + } + + if (!rng_algorithm_list || + *rng_algorithm_list_size < sizeof(*rng_algorithm_list)) { + *rng_algorithm_list_size = sizeof(*rng_algorithm_list); + ret = EFI_BUFFER_TOO_SMALL; + goto back; + } + + /* + * For now, use EFI_RNG_ALGORITHM_RAW as the default + * algorithm. If a new algorithm gets added in the + * future through a Kconfig, rng_algo_guid will be set + * based on that Kconfig option + */ + *rng_algorithm_list_size = sizeof(*rng_algorithm_list); + guidcpy(rng_algorithm_list, &rng_algo_guid); + +back: + return EFI_EXIT(ret); +} + +/** + * getrng() - get random value + * + * This function implement the GetRng() service of the EFI random number + * generator protocol. See the UEFI spec for details. + * + * @this: random number generator protocol instance + * @rng_algorithm: random number generation algorithm + * @rng_value_length: number of random bytes to generate, buffer length + * @rng_value: buffer to receive random bytes + * Return: status code + */ +static efi_status_t EFIAPI getrng(struct efi_rng_protocol *this, + efi_guid_t *rng_algorithm, + efi_uintn_t rng_value_length, + uint8_t *rng_value) +{ + int ret; + efi_status_t status = EFI_SUCCESS; + struct udevice *dev; + const efi_guid_t rng_raw_guid = EFI_RNG_ALGORITHM_RAW; + + EFI_ENTRY("%p, %p, %zu, %p", this, rng_algorithm, rng_value_length, + rng_value); + + if (!this || !rng_value || !rng_value_length) { + status = EFI_INVALID_PARAMETER; + goto back; + } + + if (rng_algorithm) { + EFI_PRINT("RNG algorithm %pUs\n", rng_algorithm); + if (guidcmp(rng_algorithm, &rng_raw_guid)) { + status = EFI_UNSUPPORTED; + goto back; + } + } + + ret = platform_get_rng_device(&dev); + if (ret != EFI_SUCCESS) { + EFI_PRINT("Rng device not found\n"); + status = EFI_UNSUPPORTED; + goto back; + } + + ret = dm_rng_read(dev, rng_value, rng_value_length); + if (ret < 0) { + EFI_PRINT("Rng device read failed\n"); + status = EFI_DEVICE_ERROR; + goto back; + } + +back: + return EFI_EXIT(status); +} + +static const struct efi_rng_protocol efi_rng_protocol = { + .get_info = rng_getinfo, + .get_rng = getrng, +}; + +/** + * efi_rng_register() - register EFI_RNG_PROTOCOL + * + * If a RNG device is available, the Random Number Generator Protocol is + * registered. + * + * Return: An error status is only returned if adding the protocol fails. + */ +efi_status_t efi_rng_register(void) +{ + efi_status_t ret; + struct udevice *dev; + + ret = platform_get_rng_device(&dev); + if (ret != EFI_SUCCESS) { + log_warning("Missing RNG device for EFI_RNG_PROTOCOL\n"); + return EFI_SUCCESS; + } + ret = efi_add_protocol(efi_root, &efi_guid_rng_protocol, + (void *)&efi_rng_protocol); + if (ret != EFI_SUCCESS) + log_err("Cannot install EFI_RNG_PROTOCOL\n"); + + return ret; +} diff --git a/lib/efi_loader/efi_root_node.c b/lib/efi_loader/efi_root_node.c new file mode 100644 index 00000000000..74225edad29 --- /dev/null +++ b/lib/efi_loader/efi_root_node.c @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Root node for system services + * + * Copyright (c) 2018 Heinrich Schuchardt + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <malloc.h> +#include <efi_dt_fixup.h> +#include <efi_loader.h> + +const efi_guid_t efi_u_boot_guid = U_BOOT_GUID; + +efi_handle_t efi_root = NULL; + +struct efi_root_dp { + struct efi_device_path_vendor vendor; + struct efi_device_path end; +} __packed; + +/** + * efi_root_node_register() - create root node + * + * Create the root node on which we install all protocols that are + * not related to a loaded image or a driver. + * + * Return: status code + */ +efi_status_t efi_root_node_register(void) +{ + efi_status_t ret; + struct efi_root_dp *dp; + + /* Create device path protocol */ + dp = calloc(1, sizeof(*dp)); + if (!dp) + return EFI_OUT_OF_RESOURCES; + + /* Fill vendor node */ + dp->vendor.dp.type = DEVICE_PATH_TYPE_HARDWARE_DEVICE; + dp->vendor.dp.sub_type = DEVICE_PATH_SUB_TYPE_VENDOR; + dp->vendor.dp.length = sizeof(struct efi_device_path_vendor); + dp->vendor.guid = efi_u_boot_guid; + + /* Fill end node */ + dp->end.type = DEVICE_PATH_TYPE_END; + dp->end.sub_type = DEVICE_PATH_SUB_TYPE_END; + dp->end.length = sizeof(struct efi_device_path); + + /* Create root node and install protocols */ + ret = efi_install_multiple_protocol_interfaces + (&efi_root, + /* Device path protocol */ + &efi_guid_device_path, dp, +#if CONFIG_IS_ENABLED(EFI_DEVICE_PATH_TO_TEXT) + /* Device path to text protocol */ + &efi_guid_device_path_to_text_protocol, + &efi_device_path_to_text, +#endif +#if IS_ENABLED(CONFIG_EFI_DEVICE_PATH_UTIL) + /* Device path utilities protocol */ + &efi_guid_device_path_utilities_protocol, + &efi_device_path_utilities, +#endif +#if CONFIG_IS_ENABLED(EFI_DT_FIXUP) + /* Device-tree fix-up protocol */ + &efi_guid_dt_fixup_protocol, + &efi_dt_fixup_prot, +#endif +#if IS_ENABLED(CONFIG_EFI_UNICODE_COLLATION_PROTOCOL2) + &efi_guid_unicode_collation_protocol2, + &efi_unicode_collation_protocol2, +#endif +#if IS_ENABLED(CONFIG_EFI_LOADER_HII) + /* HII string protocol */ + &efi_guid_hii_string_protocol, + &efi_hii_string, + /* HII database protocol */ + &efi_guid_hii_database_protocol, + &efi_hii_database, +#endif + NULL); + efi_root->type = EFI_OBJECT_TYPE_U_BOOT_FIRMWARE; + return ret; +} diff --git a/lib/efi_loader/efi_runtime.c b/lib/efi_loader/efi_runtime.c new file mode 100644 index 00000000000..35eb6a77766 --- /dev/null +++ b/lib/efi_loader/efi_runtime.c @@ -0,0 +1,1012 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI application runtime services + * + * Copyright (c) 2016 Alexander Graf + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <command.h> +#include <cpu_func.h> +#include <dm.h> +#include <elf.h> +#include <efi_loader.h> +#include <efi_variable.h> +#include <log.h> +#include <malloc.h> +#include <rtc.h> +#include <asm/global_data.h> +#include <u-boot/crc.h> +#include <asm/sections.h> + +/* For manual relocation support */ +DECLARE_GLOBAL_DATA_PTR; + +/* GUID of the runtime properties table */ +static const efi_guid_t efi_rt_properties_table_guid = + EFI_RT_PROPERTIES_TABLE_GUID; + +struct efi_runtime_mmio_list { + struct list_head link; + void **ptr; + u64 paddr; + u64 len; +}; + +/* This list contains all runtime available mmio regions */ +static LIST_HEAD(efi_runtime_mmio); + +static efi_status_t __efi_runtime EFIAPI efi_unimplemented(void); + +/* + * TODO(sjg@chromium.org): These defines and structures should come from the ELF + * header for each architecture (or a generic header) rather than being repeated + * here. + */ +#if defined(__aarch64__) +#define R_RELATIVE R_AARCH64_RELATIVE +#define R_MASK 0xffffffffULL +#define IS_RELA 1 +#elif defined(__arm__) +#define R_RELATIVE R_ARM_RELATIVE +#define R_MASK 0xffULL +#elif defined(__i386__) +#define R_RELATIVE R_386_RELATIVE +#define R_MASK 0xffULL +#elif defined(__x86_64__) +#define R_RELATIVE R_X86_64_RELATIVE +#define R_MASK 0xffffffffULL +#define IS_RELA 1 +#elif defined(__riscv) +#define R_RELATIVE R_RISCV_RELATIVE +#define R_MASK 0xffULL +#define IS_RELA 1 + +struct dyn_sym { + ulong foo1; + ulong addr; + u32 foo2; + u32 foo3; +}; +#if (__riscv_xlen == 32) +#define R_ABSOLUTE R_RISCV_32 +#define SYM_INDEX 8 +#elif (__riscv_xlen == 64) +#define R_ABSOLUTE R_RISCV_64 +#define SYM_INDEX 32 +#else +#error unknown riscv target +#endif +#else +#error Need to add relocation awareness +#endif + +struct elf_rel { + ulong *offset; + ulong info; +}; + +struct elf_rela { + ulong *offset; + ulong info; + long addend; +}; + +static __efi_runtime_data struct efi_mem_desc *efi_virtmap; +static __efi_runtime_data efi_uintn_t efi_descriptor_count; +static __efi_runtime_data efi_uintn_t efi_descriptor_size; + +/* + * EFI runtime code lives in two stages. In the first stage, U-Boot and an EFI + * payload are running concurrently at the same time. In this mode, we can + * handle a good number of runtime callbacks + */ + +/** + * efi_init_runtime_supported() - create runtime properties table + * + * Create a configuration table specifying which services are available at + * runtime. + * + * Return: status code + */ +efi_status_t efi_init_runtime_supported(void) +{ + const efi_guid_t efi_guid_efi_rt_var_file = U_BOOT_EFI_RT_VAR_FILE_GUID; + efi_status_t ret; + struct efi_rt_properties_table *rt_table; + + ret = efi_allocate_pool(EFI_RUNTIME_SERVICES_DATA, + sizeof(struct efi_rt_properties_table), + (void **)&rt_table); + if (ret != EFI_SUCCESS) + return ret; + + rt_table->version = EFI_RT_PROPERTIES_TABLE_VERSION; + rt_table->length = sizeof(struct efi_rt_properties_table); + rt_table->runtime_services_supported = + EFI_RT_SUPPORTED_GET_VARIABLE | + EFI_RT_SUPPORTED_GET_NEXT_VARIABLE_NAME | + EFI_RT_SUPPORTED_SET_VIRTUAL_ADDRESS_MAP | + EFI_RT_SUPPORTED_CONVERT_POINTER; + + if (IS_ENABLED(CONFIG_EFI_VARIABLE_FILE_STORE)) + rt_table->runtime_services_supported |= + EFI_RT_SUPPORTED_QUERY_VARIABLE_INFO; + + if (IS_ENABLED(CONFIG_EFI_RT_VOLATILE_STORE)) { + u8 s = 0; + + ret = efi_set_variable_int(u"RTStorageVolatile", + &efi_guid_efi_rt_var_file, + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS | + EFI_VARIABLE_READ_ONLY, + sizeof(EFI_VAR_FILE_NAME), + EFI_VAR_FILE_NAME, false); + if (ret != EFI_SUCCESS) { + log_err("Failed to set RTStorageVolatile\n"); + return ret; + } + /* + * This variable needs to be visible so users can read it, + * but the real contents are going to be filled during + * GetVariable + */ + ret = efi_set_variable_int(u"VarToFile", + &efi_guid_efi_rt_var_file, + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS | + EFI_VARIABLE_READ_ONLY, + sizeof(s), + &s, false); + if (ret != EFI_SUCCESS) { + log_err("Failed to set VarToFile\n"); + efi_set_variable_int(u"RTStorageVolatile", + &efi_guid_efi_rt_var_file, + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS | + EFI_VARIABLE_READ_ONLY, + 0, NULL, false); + + return ret; + } + rt_table->runtime_services_supported |= EFI_RT_SUPPORTED_SET_VARIABLE; + } + + /* + * This value must be synced with efi_runtime_detach_list + * as well as efi_runtime_services. + */ +#ifdef CONFIG_EFI_HAVE_RUNTIME_RESET + rt_table->runtime_services_supported |= EFI_RT_SUPPORTED_RESET_SYSTEM; +#endif + + ret = efi_install_configuration_table(&efi_rt_properties_table_guid, + rt_table); + return ret; +} + +/** + * efi_memcpy_runtime() - copy memory area + * + * At runtime memcpy() is not available. + * + * Overlapping memory areas can be copied safely if src >= dest. + * + * @dest: destination buffer + * @src: source buffer + * @n: number of bytes to copy + * Return: pointer to destination buffer + */ +void __efi_runtime efi_memcpy_runtime(void *dest, const void *src, size_t n) +{ + u8 *d = dest; + const u8 *s = src; + + for (; n; --n) + *d++ = *s++; +} + +/** + * efi_update_table_header_crc32() - Update crc32 in table header + * + * @table: EFI table + */ +void __efi_runtime efi_update_table_header_crc32(struct efi_table_hdr *table) +{ + table->crc32 = 0; + table->crc32 = crc32(0, (const unsigned char *)table, + table->headersize); +} + +/** + * efi_reset_system_boottime() - reset system at boot time + * + * This function implements the ResetSystem() runtime service before + * SetVirtualAddressMap() is called. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @reset_type: type of reset to perform + * @reset_status: status code for the reset + * @data_size: size of reset_data + * @reset_data: information about the reset + */ +static void EFIAPI efi_reset_system_boottime( + enum efi_reset_type reset_type, + efi_status_t reset_status, + unsigned long data_size, void *reset_data) +{ + struct efi_event *evt; + + EFI_ENTRY("%d %lx %lx %p", reset_type, reset_status, data_size, + reset_data); + + /* Notify reset */ + list_for_each_entry(evt, &efi_events, link) { + if (evt->group && + !guidcmp(evt->group, + &efi_guid_event_group_reset_system)) { + efi_signal_event(evt); + break; + } + } + switch (reset_type) { + case EFI_RESET_COLD: + case EFI_RESET_WARM: + case EFI_RESET_PLATFORM_SPECIFIC: + do_reset(NULL, 0, 0, NULL); + break; + case EFI_RESET_SHUTDOWN: +#ifdef CONFIG_CMD_POWEROFF + do_poweroff(NULL, 0, 0, NULL); +#endif + break; + } + + while (1) { } +} + +/** + * efi_get_time_boottime() - get current time at boot time + * + * This function implements the GetTime runtime service before + * SetVirtualAddressMap() is called. + * + * See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * @time: pointer to structure to receive current time + * @capabilities: pointer to structure to receive RTC properties + * Returns: status code + */ +static efi_status_t EFIAPI efi_get_time_boottime( + struct efi_time *time, + struct efi_time_cap *capabilities) +{ +#ifdef CONFIG_EFI_GET_TIME + efi_status_t ret = EFI_SUCCESS; + struct rtc_time tm; + struct udevice *dev; + + EFI_ENTRY("%p %p", time, capabilities); + + if (!time) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + if (uclass_get_device(UCLASS_RTC, 0, &dev) || + dm_rtc_get(dev, &tm)) { + ret = EFI_UNSUPPORTED; + goto out; + } + if (dm_rtc_get(dev, &tm)) { + ret = EFI_DEVICE_ERROR; + goto out; + } + + memset(time, 0, sizeof(*time)); + time->year = tm.tm_year; + time->month = tm.tm_mon; + time->day = tm.tm_mday; + time->hour = tm.tm_hour; + time->minute = tm.tm_min; + time->second = tm.tm_sec; + if (tm.tm_isdst > 0) + time->daylight = + EFI_TIME_ADJUST_DAYLIGHT | EFI_TIME_IN_DAYLIGHT; + else if (!tm.tm_isdst) + time->daylight = EFI_TIME_ADJUST_DAYLIGHT; + else + time->daylight = 0; + time->timezone = EFI_UNSPECIFIED_TIMEZONE; + + if (capabilities) { + /* Set reasonable dummy values */ + capabilities->resolution = 1; /* 1 Hz */ + capabilities->accuracy = 100000000; /* 100 ppm */ + capabilities->sets_to_zero = false; + } +out: + return EFI_EXIT(ret); +#else + EFI_ENTRY("%p %p", time, capabilities); + return EFI_EXIT(EFI_UNSUPPORTED); +#endif +} + +#ifdef CONFIG_EFI_SET_TIME + +/** + * efi_validate_time() - checks if timestamp is valid + * + * @time: timestamp to validate + * Returns: 0 if timestamp is valid, 1 otherwise + */ +static int efi_validate_time(struct efi_time *time) +{ + return (!time || + time->year < 1900 || time->year > 9999 || + !time->month || time->month > 12 || !time->day || + time->day > rtc_month_days(time->month - 1, time->year) || + time->hour > 23 || time->minute > 59 || time->second > 59 || + time->nanosecond > 999999999 || + time->daylight & + ~(EFI_TIME_IN_DAYLIGHT | EFI_TIME_ADJUST_DAYLIGHT) || + ((time->timezone < -1440 || time->timezone > 1440) && + time->timezone != EFI_UNSPECIFIED_TIMEZONE)); +} + +#endif + +/** + * efi_set_time_boottime() - set current time + * + * This function implements the SetTime() runtime service before + * SetVirtualAddressMap() is called. + * + * See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * @time: pointer to structure to with current time + * Returns: status code + */ +static efi_status_t EFIAPI efi_set_time_boottime(struct efi_time *time) +{ +#ifdef CONFIG_EFI_SET_TIME + efi_status_t ret = EFI_SUCCESS; + struct rtc_time tm; + struct udevice *dev; + + EFI_ENTRY("%p", time); + + if (efi_validate_time(time)) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + if (uclass_get_device(UCLASS_RTC, 0, &dev)) { + ret = EFI_UNSUPPORTED; + goto out; + } + + memset(&tm, 0, sizeof(tm)); + tm.tm_year = time->year; + tm.tm_mon = time->month; + tm.tm_mday = time->day; + tm.tm_hour = time->hour; + tm.tm_min = time->minute; + tm.tm_sec = time->second; + switch (time->daylight) { + case EFI_TIME_ADJUST_DAYLIGHT: + tm.tm_isdst = 0; + break; + case EFI_TIME_ADJUST_DAYLIGHT | EFI_TIME_IN_DAYLIGHT: + tm.tm_isdst = 1; + break; + default: + tm.tm_isdst = -1; + break; + } + /* Calculate day of week */ + rtc_calc_weekday(&tm); + + if (dm_rtc_set(dev, &tm)) + ret = EFI_DEVICE_ERROR; +out: + return EFI_EXIT(ret); +#else + EFI_ENTRY("%p", time); + return EFI_EXIT(EFI_UNSUPPORTED); +#endif +} +/** + * efi_reset_system() - reset system + * + * This function implements the ResetSystem() runtime service after + * SetVirtualAddressMap() is called. As this placeholder cannot reset the + * system it simply return to the caller. + * + * Boards may override the helpers below to implement reset functionality. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @reset_type: type of reset to perform + * @reset_status: status code for the reset + * @data_size: size of reset_data + * @reset_data: information about the reset + */ +void __weak __efi_runtime EFIAPI efi_reset_system( + enum efi_reset_type reset_type, + efi_status_t reset_status, + unsigned long data_size, void *reset_data) +{ + return; +} + +/** + * efi_reset_system_init() - initialize the reset driver + * + * Boards may override this function to initialize the reset driver. + */ +efi_status_t __weak efi_reset_system_init(void) +{ + return EFI_SUCCESS; +} + +/** + * efi_get_time() - get current time + * + * This function implements the GetTime runtime service after + * SetVirtualAddressMap() is called. As the U-Boot driver are not available + * anymore only an error code is returned. + * + * See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * @time: pointer to structure to receive current time + * @capabilities: pointer to structure to receive RTC properties + * Returns: status code + */ +efi_status_t __weak __efi_runtime EFIAPI efi_get_time( + struct efi_time *time, + struct efi_time_cap *capabilities) +{ + return EFI_UNSUPPORTED; +} + +/** + * efi_set_time() - set current time + * + * This function implements the SetTime runtime service after + * SetVirtualAddressMap() is called. As the U-Boot driver are not available + * anymore only an error code is returned. + * + * See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * @time: pointer to structure to with current time + * Returns: status code + */ +efi_status_t __weak __efi_runtime EFIAPI efi_set_time(struct efi_time *time) +{ + return EFI_UNSUPPORTED; +} + +/** + * efi_update_capsule_unsupported() - process information from operating system + * + * This function implements the UpdateCapsule() runtime service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @capsule_header_array: pointer to array of virtual pointers + * @capsule_count: number of pointers in capsule_header_array + * @scatter_gather_list: pointer to array of physical pointers + * Returns: status code + */ +static efi_status_t __efi_runtime EFIAPI efi_update_capsule_unsupported( + struct efi_capsule_header **capsule_header_array, + efi_uintn_t capsule_count, + u64 scatter_gather_list) +{ + return EFI_UNSUPPORTED; +} + +/** + * efi_query_capsule_caps_unsupported() - check if capsule is supported + * + * This function implements the QueryCapsuleCapabilities() runtime service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @capsule_header_array: pointer to array of virtual pointers + * @capsule_count: number of pointers in capsule_header_array + * @maximum_capsule_size: maximum capsule size + * @reset_type: type of reset needed for capsule update + * Returns: status code + */ +static efi_status_t __efi_runtime EFIAPI efi_query_capsule_caps_unsupported( + struct efi_capsule_header **capsule_header_array, + efi_uintn_t capsule_count, + u64 *maximum_capsule_size, + u32 *reset_type) +{ + return EFI_UNSUPPORTED; +} + +/** + * efi_is_runtime_service_pointer() - check if pointer points to runtime table + * + * @p: pointer to check + * Return: true if the pointer points to a service function pointer in the + * runtime table + */ +static bool efi_is_runtime_service_pointer(void *p) +{ + return (p >= (void *)&efi_runtime_services.get_time && + p <= (void *)&efi_runtime_services.query_variable_info) || + p == (void *)&efi_events.prev || + p == (void *)&efi_events.next; +} + +/** + * efi_runtime_detach() - detach unimplemented runtime functions + */ +void efi_runtime_detach(void) +{ + efi_runtime_services.reset_system = efi_reset_system; + efi_runtime_services.get_time = efi_get_time; + efi_runtime_services.set_time = efi_set_time; + if (IS_ENABLED(CONFIG_EFI_RUNTIME_UPDATE_CAPSULE)) { + /* won't support at runtime */ + efi_runtime_services.update_capsule = + efi_update_capsule_unsupported; + efi_runtime_services.query_capsule_caps = + efi_query_capsule_caps_unsupported; + } + + /* Update CRC32 */ + efi_update_table_header_crc32(&efi_runtime_services.hdr); +} + +/** + * efi_set_virtual_address_map_runtime() - change from physical to virtual + * mapping + * + * This function implements the SetVirtualAddressMap() runtime service after + * it is first called. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @memory_map_size: size of the virtual map + * @descriptor_size: size of an entry in the map + * @descriptor_version: version of the map entries + * @virtmap: virtual address mapping information + * Return: status code EFI_UNSUPPORTED + */ +static __efi_runtime efi_status_t EFIAPI efi_set_virtual_address_map_runtime( + efi_uintn_t memory_map_size, + efi_uintn_t descriptor_size, + uint32_t descriptor_version, + struct efi_mem_desc *virtmap) +{ + return EFI_UNSUPPORTED; +} + +/** + * efi_convert_pointer_runtime() - convert from physical to virtual pointer + * + * This function implements the ConvertPointer() runtime service after + * the first call to SetVirtualAddressMap(). + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @debug_disposition: indicates if pointer may be converted to NULL + * @address: pointer to be converted + * Return: status code EFI_UNSUPPORTED + */ +static __efi_runtime efi_status_t EFIAPI efi_convert_pointer_runtime( + efi_uintn_t debug_disposition, void **address) +{ + return EFI_UNSUPPORTED; +} + +/** + * efi_convert_pointer() - convert from physical to virtual pointer + * + * This function implements the ConvertPointer() runtime service until + * the first call to SetVirtualAddressMap(). + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @debug_disposition: indicates if pointer may be converted to NULL + * @address: pointer to be converted + * Return: status code + */ +__efi_runtime efi_status_t EFIAPI +efi_convert_pointer(efi_uintn_t debug_disposition, void **address) +{ + efi_physical_addr_t addr; + efi_uintn_t i; + efi_status_t ret = EFI_NOT_FOUND; + + if (!efi_virtmap) { + ret = EFI_UNSUPPORTED; + goto out; + } + + if (!address) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + if (!*address) { + if (debug_disposition & EFI_OPTIONAL_PTR) + return EFI_SUCCESS; + else + return EFI_INVALID_PARAMETER; + } + + addr = (uintptr_t)*address; + for (i = 0; i < efi_descriptor_count; i++) { + struct efi_mem_desc *map = (void *)efi_virtmap + + (efi_descriptor_size * i); + + if (addr >= map->physical_start && + (addr < map->physical_start + + (map->num_pages << EFI_PAGE_SHIFT))) { + *address = (void *)(uintptr_t) + (addr + map->virtual_start - + map->physical_start); + + ret = EFI_SUCCESS; + break; + } + } + +out: + return ret; +} + +static __efi_runtime void efi_relocate_runtime_table(ulong offset) +{ + ulong patchoff; + void **pos; + + /* Relocate the runtime services pointers */ + patchoff = offset - gd->relocaddr; + for (pos = (void **)&efi_runtime_services.get_time; + pos <= (void **)&efi_runtime_services.query_variable_info; ++pos) { + if (*pos) + *pos += patchoff; + } + + /* + * The entry for SetVirtualAddress() must point to a physical address. + * After the first execution the service must return EFI_UNSUPPORTED. + */ + efi_runtime_services.set_virtual_address_map = + &efi_set_virtual_address_map_runtime; + + /* + * The entry for ConvertPointer() must point to a physical address. + * The service is not usable after SetVirtualAddress(). + */ + efi_runtime_services.convert_pointer = &efi_convert_pointer_runtime; + + /* + * TODO: Update UEFI variable RuntimeServicesSupported removing flags + * EFI_RT_SUPPORTED_SET_VIRTUAL_ADDRESS_MAP and + * EFI_RT_SUPPORTED_CONVERT_POINTER as required by the UEFI spec 2.8. + */ + + /* Update CRC32 */ + efi_update_table_header_crc32(&efi_runtime_services.hdr); +} + +/* Relocate EFI runtime to uboot_reloc_base = offset */ +void efi_runtime_relocate(ulong offset, struct efi_mem_desc *map) +{ +#ifdef IS_RELA + struct elf_rela *rel = (void *)__efi_runtime_rel_start; +#else + struct elf_rel *rel = (void *)__efi_runtime_rel_start; + static ulong lastoff = CONFIG_TEXT_BASE; +#endif + + debug("%s: Relocating to offset=%lx\n", __func__, offset); + for (; (uintptr_t)rel < (uintptr_t)__efi_runtime_rel_stop; rel++) { + ulong base = CONFIG_TEXT_BASE; + ulong *p; + ulong newaddr; + + p = (void*)((ulong)rel->offset - base) + gd->relocaddr; + + /* + * The runtime services table is updated in + * efi_relocate_runtime_table() + */ + if (map && efi_is_runtime_service_pointer(p)) + continue; + + debug("%s: rel->info=%#lx *p=%#lx rel->offset=%p\n", __func__, + rel->info, *p, rel->offset); + + switch (rel->info & R_MASK) { + case R_RELATIVE: +#ifdef IS_RELA + newaddr = rel->addend + offset - CONFIG_TEXT_BASE; +#else + newaddr = *p - lastoff + offset; +#endif + break; +#ifdef R_ABSOLUTE + case R_ABSOLUTE: { + ulong symidx = rel->info >> SYM_INDEX; + extern struct dyn_sym __dyn_sym_start[]; + newaddr = __dyn_sym_start[symidx].addr + offset; +#ifdef IS_RELA + newaddr -= CONFIG_TEXT_BASE; +#endif + break; + } +#endif + default: + printf("%s: Unknown relocation type %llx\n", + __func__, rel->info & R_MASK); + continue; + } + + /* Check if the relocation is inside bounds */ + if (map && ((newaddr < map->virtual_start) || + newaddr > (map->virtual_start + + (map->num_pages << EFI_PAGE_SHIFT)))) { + printf("%s: Relocation at %p is out of range (%lx)\n", + __func__, p, newaddr); + continue; + } + + debug("%s: Setting %p to %lx\n", __func__, p, newaddr); + *p = newaddr; + flush_dcache_range((ulong)p & ~(EFI_CACHELINE_SIZE - 1), + ALIGN((ulong)&p[1], EFI_CACHELINE_SIZE)); + } + +#ifndef IS_RELA + lastoff = offset; +#endif + + /* + * If on x86 a write affects a prefetched instruction, + * the prefetch queue is invalidated. + */ + if (!CONFIG_IS_ENABLED(X86)) + invalidate_icache_all(); +} + +/** + * efi_set_virtual_address_map() - change from physical to virtual mapping + * + * This function implements the SetVirtualAddressMap() runtime service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @memory_map_size: size of the virtual map + * @descriptor_size: size of an entry in the map + * @descriptor_version: version of the map entries + * @virtmap: virtual address mapping information + * Return: status code + */ +static efi_status_t EFIAPI efi_set_virtual_address_map( + efi_uintn_t memory_map_size, + efi_uintn_t descriptor_size, + uint32_t descriptor_version, + struct efi_mem_desc *virtmap) +{ + efi_uintn_t n = memory_map_size / descriptor_size; + efi_uintn_t i; + efi_status_t ret = EFI_INVALID_PARAMETER; + int rt_code_sections = 0; + struct efi_event *event; + + EFI_ENTRY("%zx %zx %x %p", memory_map_size, descriptor_size, + descriptor_version, virtmap); + + if (descriptor_version != EFI_MEMORY_DESCRIPTOR_VERSION || + descriptor_size < sizeof(struct efi_mem_desc)) + goto out; + + efi_virtmap = virtmap; + efi_descriptor_size = descriptor_size; + efi_descriptor_count = n; + + /* + * TODO: + * Further down we are cheating. While really we should implement + * SetVirtualAddressMap() events and ConvertPointer() to allow + * dynamically loaded drivers to expose runtime services, we don't + * today. + * + * So let's ensure we see exactly one single runtime section, as + * that is the built-in one. If we see more (or less), someone must + * have tried adding or removing to that which we don't support yet. + * In that case, let's better fail rather than expose broken runtime + * services. + */ + for (i = 0; i < n; i++) { + struct efi_mem_desc *map = (void*)virtmap + + (descriptor_size * i); + + if (map->type == EFI_RUNTIME_SERVICES_CODE) + rt_code_sections++; + } + + if (rt_code_sections != 1) { + /* + * We expose exactly one single runtime code section, so + * something is definitely going wrong. + */ + goto out; + } + + /* Notify EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE */ + list_for_each_entry(event, &efi_events, link) { + if (event->notify_function) + EFI_CALL_VOID(event->notify_function( + event, event->notify_context)); + } + + /* Rebind mmio pointers */ + for (i = 0; i < n; i++) { + struct efi_mem_desc *map = (void*)virtmap + + (descriptor_size * i); + struct list_head *lhandle; + efi_physical_addr_t map_start = map->physical_start; + efi_physical_addr_t map_len = map->num_pages << EFI_PAGE_SHIFT; + efi_physical_addr_t map_end = map_start + map_len; + u64 off = map->virtual_start - map_start; + + /* Adjust all mmio pointers in this region */ + list_for_each(lhandle, &efi_runtime_mmio) { + struct efi_runtime_mmio_list *lmmio; + + lmmio = list_entry(lhandle, + struct efi_runtime_mmio_list, + link); + if ((map_start <= lmmio->paddr) && + (map_end >= lmmio->paddr)) { + uintptr_t new_addr = lmmio->paddr + off; + *lmmio->ptr = (void *)new_addr; + } + } + if ((map_start <= (uintptr_t)systab.tables) && + (map_end >= (uintptr_t)systab.tables)) { + char *ptr = (char *)systab.tables; + + ptr += off; + systab.tables = (struct efi_configuration_table *)ptr; + } + } + + /* Relocate the runtime. See TODO above */ + for (i = 0; i < n; i++) { + struct efi_mem_desc *map; + + map = (void*)virtmap + (descriptor_size * i); + if (map->type == EFI_RUNTIME_SERVICES_CODE) { + ulong new_offset = map->virtual_start - + map->physical_start + gd->relocaddr; + + efi_relocate_runtime_table(new_offset); + efi_runtime_relocate(new_offset, map); + ret = EFI_SUCCESS; + goto out; + } + } + +out: + return EFI_EXIT(ret); +} + +/** + * efi_add_runtime_mmio() - add memory-mapped IO region + * + * This function adds a memory-mapped IO region to the memory map to make it + * available at runtime. + * + * @mmio_ptr: pointer to a pointer to the start of the memory-mapped + * IO region + * @len: size of the memory-mapped IO region + * Returns: status code + */ +efi_status_t efi_add_runtime_mmio(void *mmio_ptr, u64 len) +{ + struct efi_runtime_mmio_list *newmmio; + uint64_t addr = *(uintptr_t *)mmio_ptr; + efi_status_t ret; + + ret = efi_add_memory_map(addr, len, EFI_MMAP_IO); + if (ret != EFI_SUCCESS) + return EFI_OUT_OF_RESOURCES; + + newmmio = calloc(1, sizeof(*newmmio)); + if (!newmmio) + return EFI_OUT_OF_RESOURCES; + newmmio->ptr = mmio_ptr; + newmmio->paddr = *(uintptr_t *)mmio_ptr; + newmmio->len = len; + list_add_tail(&newmmio->link, &efi_runtime_mmio); + + return EFI_SUCCESS; +} + +/* + * In the second stage, U-Boot has disappeared. To isolate our runtime code + * that at this point still exists from the rest, we put it into a special + * section. + * + * !!WARNING!! + * + * This means that we can not rely on any code outside of this file in any + * function or variable below this line. + * + * Please keep everything fully self-contained and annotated with + * __efi_runtime and __efi_runtime_data markers. + */ + +/* + * Relocate the EFI runtime stub to a different place. We need to call this + * the first time we expose the runtime interface to a user and on set virtual + * address map calls. + */ + +/** + * efi_unimplemented() - replacement function, returns EFI_UNSUPPORTED + * + * This function is used after SetVirtualAddressMap() is called as replacement + * for services that are not available anymore due to constraints of the U-Boot + * implementation. + * + * Return: EFI_UNSUPPORTED + */ +static efi_status_t __efi_runtime EFIAPI efi_unimplemented(void) +{ + return EFI_UNSUPPORTED; +} + +struct efi_runtime_services __efi_runtime_data efi_runtime_services = { + .hdr = { + .signature = EFI_RUNTIME_SERVICES_SIGNATURE, + .revision = EFI_SPECIFICATION_VERSION, + .headersize = sizeof(struct efi_runtime_services), + }, + .get_time = &efi_get_time_boottime, + .set_time = &efi_set_time_boottime, + .get_wakeup_time = (void *)&efi_unimplemented, + .set_wakeup_time = (void *)&efi_unimplemented, + .set_virtual_address_map = &efi_set_virtual_address_map, + .convert_pointer = efi_convert_pointer, + .get_variable = efi_get_variable, + .get_next_variable_name = efi_get_next_variable_name, + .set_variable = efi_set_variable, + .get_next_high_mono_count = (void *)&efi_unimplemented, + .reset_system = &efi_reset_system_boottime, +#ifdef CONFIG_EFI_RUNTIME_UPDATE_CAPSULE + .update_capsule = efi_update_capsule, + .query_capsule_caps = efi_query_capsule_caps, +#else + .update_capsule = efi_update_capsule_unsupported, + .query_capsule_caps = efi_query_capsule_caps_unsupported, +#endif + .query_variable_info = efi_query_variable_info, +}; diff --git a/lib/efi_loader/efi_setup.c b/lib/efi_loader/efi_setup.c new file mode 100644 index 00000000000..48f91da5df7 --- /dev/null +++ b/lib/efi_loader/efi_setup.c @@ -0,0 +1,378 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI setup code + * + * Copyright (c) 2016-2018 Alexander Graf et al. + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <efi_loader.h> +#include <efi_variable.h> +#include <log.h> +#include <asm-generic/unaligned.h> +#include <net.h> + +#define OBJ_LIST_INITIALIZED 0 +#define OBJ_LIST_NOT_INITIALIZED 1 + +efi_status_t efi_obj_list_initialized = OBJ_LIST_NOT_INITIALIZED; + +/* + * Allow unaligned memory access. + * + * This routine is overridden by architectures providing this feature. + */ +void __weak allow_unaligned(void) +{ +} + +/** + * efi_init_platform_lang() - define supported languages + * + * Set the PlatformLangCodes and PlatformLang variables. + * + * Return: status code + */ +static efi_status_t efi_init_platform_lang(void) +{ + efi_status_t ret; + efi_uintn_t data_size = 0; + char *lang = CONFIG_EFI_PLATFORM_LANG_CODES; + char *pos; + + /* + * Variable PlatformLangCodes defines the language codes that the + * machine can support. + */ + ret = efi_set_variable_int(u"PlatformLangCodes", + &efi_global_variable_guid, + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS | + EFI_VARIABLE_READ_ONLY, + sizeof(CONFIG_EFI_PLATFORM_LANG_CODES), + CONFIG_EFI_PLATFORM_LANG_CODES, false); + if (ret != EFI_SUCCESS) + goto out; + + /* + * Variable PlatformLang defines the language that the machine has been + * configured for. + */ + ret = efi_get_variable_int(u"PlatformLang", + &efi_global_variable_guid, + NULL, &data_size, &pos, NULL); + if (ret == EFI_BUFFER_TOO_SMALL) { + /* The variable is already set. Do not change it. */ + ret = EFI_SUCCESS; + goto out; + } + + /* + * The list of supported languages is semicolon separated. Use the first + * language to initialize PlatformLang. + */ + pos = strchr(lang, ';'); + if (pos) + *pos = 0; + + ret = efi_set_variable_int(u"PlatformLang", + &efi_global_variable_guid, + EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS, + 1 + strlen(lang), lang, false); +out: + if (ret != EFI_SUCCESS) + printf("EFI: cannot initialize platform language settings\n"); + return ret; +} + +/** + * efi_init_secure_boot - initialize secure boot state + * + * Return: status code + */ +static efi_status_t efi_init_secure_boot(void) +{ + efi_guid_t signature_types[] = { + EFI_CERT_SHA256_GUID, + EFI_CERT_X509_GUID, + }; + efi_status_t ret; + + ret = efi_set_variable_int(u"SignatureSupport", + &efi_global_variable_guid, + EFI_VARIABLE_READ_ONLY | + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS, + sizeof(signature_types), + &signature_types, false); + if (ret != EFI_SUCCESS) + printf("EFI: cannot initialize SignatureSupport variable\n"); + + return ret; +} + +/** + * efi_init_capsule - initialize capsule update state + * + * Return: status code + */ +static efi_status_t efi_init_capsule(void) +{ + efi_status_t ret = EFI_SUCCESS; + + if (IS_ENABLED(CONFIG_EFI_HAVE_CAPSULE_SUPPORT)) { + u16 var_name16[12]; + + efi_create_indexed_name(var_name16, sizeof(var_name16), + "Capsule", CONFIG_EFI_CAPSULE_MAX); + + ret = efi_set_variable_int(u"CapsuleMax", + &efi_guid_capsule_report, + EFI_VARIABLE_READ_ONLY | + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS, + 22, var_name16, false); + if (ret != EFI_SUCCESS) + printf("EFI: cannot initialize CapsuleMax variable\n"); + } + + return ret; +} + +/** + * efi_init_os_indications() - indicate supported features for OS requests + * + * Set the OsIndicationsSupported variable. + * + * Return: status code + */ +static efi_status_t efi_init_os_indications(void) +{ + u64 os_indications_supported = 0; + + if (IS_ENABLED(CONFIG_EFI_HAVE_CAPSULE_SUPPORT)) + os_indications_supported |= + EFI_OS_INDICATIONS_CAPSULE_RESULT_VAR_SUPPORTED; + + if (IS_ENABLED(CONFIG_EFI_CAPSULE_ON_DISK)) + os_indications_supported |= + EFI_OS_INDICATIONS_FILE_CAPSULE_DELIVERY_SUPPORTED; + + if (IS_ENABLED(CONFIG_EFI_CAPSULE_FIRMWARE_MANAGEMENT)) + os_indications_supported |= + EFI_OS_INDICATIONS_FMP_CAPSULE_SUPPORTED; + + return efi_set_variable_int(u"OsIndicationsSupported", + &efi_global_variable_guid, + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS | + EFI_VARIABLE_READ_ONLY, + sizeof(os_indications_supported), + &os_indications_supported, false); +} + +/** + * efi_init_early() - handle initialization at early stage + * + * expected to be called in board_init_r(). + * + * Return: status code + */ +int efi_init_early(void) +{ + efi_status_t ret; + + /* Allow unaligned memory access */ + allow_unaligned(); + + /* Initialize root node */ + ret = efi_root_node_register(); + if (ret != EFI_SUCCESS) + goto out; + + ret = efi_console_register(); + if (ret != EFI_SUCCESS) + goto out; + + /* Initialize EFI driver uclass */ + ret = efi_driver_init(); + if (ret != EFI_SUCCESS) + goto out; + + return 0; +out: + /* never re-init UEFI subsystem */ + efi_obj_list_initialized = ret; + + return -1; +} + +/** + * efi_start_obj_list() - Start EFI object list + * + * Return: status code + */ +static efi_status_t efi_start_obj_list(void) +{ + efi_status_t ret = EFI_SUCCESS; + + if (IS_ENABLED(CONFIG_NETDEVICES)) + ret = efi_net_do_start(eth_get_dev()); + + return ret; +} + +/** + * efi_init_obj_list() - Initialize and populate EFI object list + * + * Return: status code + */ +efi_status_t efi_init_obj_list(void) +{ + efi_status_t ret = EFI_SUCCESS; + + /* Initialize only once, but start every time if correctly initialized*/ + if (efi_obj_list_initialized == OBJ_LIST_INITIALIZED) + return efi_start_obj_list(); + if (efi_obj_list_initialized != OBJ_LIST_NOT_INITIALIZED) + return efi_obj_list_initialized; + + /* Set up console modes */ + efi_setup_console_size(); + + /* + * Probe block devices to find the ESP. + * efi_disks_register() must be called before efi_init_variables(). + */ + ret = efi_disks_register(); + if (ret != EFI_SUCCESS) + goto out; + + /* Initialize variable services */ + ret = efi_init_variables(); + if (ret != EFI_SUCCESS) + goto out; + + if (IS_ENABLED(CONFIG_CMD_BOOTEFI_BOOTMGR)) { + /* update boot option after variable service initialized */ + ret = efi_bootmgr_update_media_device_boot_option(); + if (ret != EFI_SUCCESS) + goto out; + } + + /* Define supported languages */ + ret = efi_init_platform_lang(); + if (ret != EFI_SUCCESS) + goto out; + + /* Indicate supported features */ + ret = efi_init_os_indications(); + if (ret != EFI_SUCCESS) + goto out; + + /* Initialize system table */ + ret = efi_initialize_system_table(); + if (ret != EFI_SUCCESS) + goto out; + + if (IS_ENABLED(CONFIG_EFI_ECPT)) { + ret = efi_ecpt_register(); + if (ret != EFI_SUCCESS) + goto out; + } + + if (IS_ENABLED(CONFIG_EFI_ESRT)) { + ret = efi_esrt_register(); + if (ret != EFI_SUCCESS) + goto out; + } + + if (IS_ENABLED(CONFIG_EFI_TCG2_PROTOCOL)) { + ret = efi_tcg2_register(); + if (ret != EFI_SUCCESS) + goto out; + + ret = efi_tcg2_do_initial_measurement(); + if (ret == EFI_SECURITY_VIOLATION) + goto out; + } + + /* Install EFI_RNG_PROTOCOL */ + if (IS_ENABLED(CONFIG_EFI_RNG_PROTOCOL)) { + ret = efi_rng_register(); + if (ret != EFI_SUCCESS) + goto out; + } + + if (IS_ENABLED(CONFIG_EFI_RISCV_BOOT_PROTOCOL)) { + ret = efi_riscv_register(); + if (ret != EFI_SUCCESS) + goto out; + } + + /* Secure boot */ + if (IS_ENABLED(CONFIG_EFI_SECURE_BOOT)) { + ret = efi_init_secure_boot(); + if (ret != EFI_SUCCESS) + goto out; + } + + /* Indicate supported runtime services */ + ret = efi_init_runtime_supported(); + if (ret != EFI_SUCCESS) + goto out; + + if (IS_ENABLED(CONFIG_EFI_HAVE_CAPSULE_SUPPORT)) { + ret = efi_load_capsule_drivers(); + if (ret != EFI_SUCCESS) + goto out; + } + + if (IS_ENABLED(CONFIG_VIDEO)) { + ret = efi_gop_register(); + if (ret != EFI_SUCCESS) + goto out; + } + if (IS_ENABLED(CONFIG_NETDEVICES)) { + ret = efi_net_register(eth_get_dev()); + if (ret != EFI_SUCCESS) + goto out; + } + if (IS_ENABLED(CONFIG_ACPI)) { + ret = efi_acpi_register(); + if (ret != EFI_SUCCESS) + goto out; + } + if (IS_ENABLED(CONFIG_SMBIOS)) { + ret = efi_smbios_register(); + if (ret != EFI_SUCCESS) + goto out; + } + ret = efi_watchdog_register(); + if (ret != EFI_SUCCESS) + goto out; + + ret = efi_init_capsule(); + if (ret != EFI_SUCCESS) + goto out; + + /* Initialize EFI runtime services */ + ret = efi_reset_system_init(); + if (ret != EFI_SUCCESS) + goto out; + + /* Execute capsules after reboot */ + if (IS_ENABLED(CONFIG_EFI_CAPSULE_ON_DISK) && + !IS_ENABLED(CONFIG_EFI_CAPSULE_ON_DISK_EARLY)) + ret = efi_launch_capsules(); + if (ret != EFI_SUCCESS) + goto out; + + ret = efi_start_obj_list(); +out: + efi_obj_list_initialized = ret; + return ret; +} diff --git a/lib/efi_loader/efi_signature.c b/lib/efi_loader/efi_signature.c new file mode 100644 index 00000000000..93a4f257016 --- /dev/null +++ b/lib/efi_loader/efi_signature.c @@ -0,0 +1,823 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2018 Patrick Wildt <patrick@blueri.se> + * Copyright (c) 2019 Linaro Limited, Author: AKASHI Takahiro + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <charset.h> +#include <efi_loader.h> +#include <efi_variable.h> +#include <image.h> +#include <hexdump.h> +#include <malloc.h> +#include <crypto/pkcs7.h> +#include <crypto/pkcs7_parser.h> +#include <crypto/public_key.h> +#include <linux/compat.h> +#include <linux/oid_registry.h> +#include <u-boot/hash-checksum.h> +#include <u-boot/rsa.h> + +const efi_guid_t efi_guid_sha256 = EFI_CERT_SHA256_GUID; +const efi_guid_t efi_guid_cert_rsa2048 = EFI_CERT_RSA2048_GUID; +const efi_guid_t efi_guid_cert_x509 = EFI_CERT_X509_GUID; +const efi_guid_t efi_guid_cert_x509_sha256 = EFI_CERT_X509_SHA256_GUID; +const efi_guid_t efi_guid_cert_x509_sha384 = EFI_CERT_X509_SHA384_GUID; +const efi_guid_t efi_guid_cert_x509_sha512 = EFI_CERT_X509_SHA512_GUID; +const efi_guid_t efi_guid_cert_type_pkcs7 = EFI_CERT_TYPE_PKCS7_GUID; + +static u8 pkcs7_hdr[] = { + /* SEQUENCE */ + 0x30, 0x82, 0x05, 0xc7, + /* OID: pkcs7-signedData */ + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x02, + /* Context Structured? */ + 0xa0, 0x82, 0x05, 0xb8, +}; + +/** + * efi_parse_pkcs7_header - parse a signature in payload + * @buf: Pointer to payload's value + * @buflen: Length of @buf + * @tmpbuf: Pointer to temporary buffer + * + * Parse a signature embedded in payload's value and instantiate + * a pkcs7_message structure. Since pkcs7_parse_message() accepts only + * pkcs7's signedData, some header needed be prepended for correctly + * parsing authentication data + * A temporary buffer will be allocated if needed, and it should be + * kept valid during the authentication because some data in the buffer + * will be referenced by efi_signature_verify(). + * + * Return: Pointer to pkcs7_message structure on success, NULL on error + */ +struct pkcs7_message *efi_parse_pkcs7_header(const void *buf, + size_t buflen, + u8 **tmpbuf) +{ + u8 *ebuf; + size_t ebuflen, len; + struct pkcs7_message *msg; + + /* + * This is the best assumption to check if the binary is + * already in a form of pkcs7's signedData. + */ + if (buflen > sizeof(pkcs7_hdr) && + !memcmp(&((u8 *)buf)[4], &pkcs7_hdr[4], 11)) { + msg = pkcs7_parse_message(buf, buflen); + if (IS_ERR(msg)) + return NULL; + return msg; + } + + /* + * Otherwise, we should add a dummy prefix sequence for pkcs7 + * message parser to be able to process. + * NOTE: EDK2 also uses similar hack in WrapPkcs7Data() + * in CryptoPkg/Library/BaseCryptLib/Pk/CryptPkcs7VerifyCommon.c + * TODO: + * The header should be composed in a more refined manner. + */ + EFI_PRINT("Makeshift prefix added to authentication data\n"); + ebuflen = sizeof(pkcs7_hdr) + buflen; + if (ebuflen <= 0x7f) { + EFI_PRINT("Data is too short\n"); + return NULL; + } + + ebuf = malloc(ebuflen); + if (!ebuf) { + EFI_PRINT("Out of memory\n"); + return NULL; + } + + memcpy(ebuf, pkcs7_hdr, sizeof(pkcs7_hdr)); + memcpy(ebuf + sizeof(pkcs7_hdr), buf, buflen); + len = ebuflen - 4; + ebuf[2] = (len >> 8) & 0xff; + ebuf[3] = len & 0xff; + len = ebuflen - 0x13; + ebuf[0x11] = (len >> 8) & 0xff; + ebuf[0x12] = len & 0xff; + + msg = pkcs7_parse_message(ebuf, ebuflen); + + if (IS_ERR(msg)) { + free(ebuf); + return NULL; + } + + *tmpbuf = ebuf; + return msg; +} + +/** + * efi_hash_regions - calculate a hash value + * @regs: Array of regions + * @count: Number of regions + * @hash: Pointer to a pointer to buffer holding a hash value + * @size: Size of buffer to be returned + * + * Calculate a sha256 value of @regs and return a value in @hash. + * + * Return: true on success, false on error + */ +bool efi_hash_regions(struct image_region *regs, int count, + void **hash, const char *hash_algo, int *len) +{ + int ret, hash_len; + + if (!hash_algo) + return false; + + hash_len = algo_to_len(hash_algo); + if (!hash_len) + return false; + + if (!*hash) { + *hash = calloc(1, hash_len); + if (!*hash) { + EFI_PRINT("Out of memory\n"); + return false; + } + } + + ret = hash_calculate(hash_algo, regs, count, *hash); + if (ret) + return false; + + if (len) + *len = hash_len; +#ifdef DEBUG + EFI_PRINT("hash calculated:\n"); + print_hex_dump(" ", DUMP_PREFIX_OFFSET, 16, 1, + *hash, hash_len, false); +#endif + + return true; +} + +/** + * hash_algo_supported - check if the requested hash algorithm is supported + * @guid: guid of the algorithm + * + * Return: true if supported false otherwise + */ +static bool hash_algo_supported(const efi_guid_t guid) +{ + int i; + const efi_guid_t unsupported_hashes[] = { + EFI_CERT_SHA1_GUID, + EFI_CERT_SHA224_GUID, + EFI_CERT_SHA384_GUID, + EFI_CERT_SHA512_GUID, + }; + + for (i = 0; i < ARRAY_SIZE(unsupported_hashes); i++) { + if (!guidcmp(&unsupported_hashes[i], &guid)) + return false; + } + + return true; +} + +/** + * efi_signature_lookup_digest - search for an image's digest in sigdb + * @regs: List of regions to be authenticated + * @db: Signature database for trusted certificates + * @dbx Caller needs to set this to true if he is searching dbx + * + * A message digest of image pointed to by @regs is calculated and + * its hash value is compared to entries in signature database pointed + * to by @db. + * + * Return: true if found, false if not + */ +bool efi_signature_lookup_digest(struct efi_image_regions *regs, + struct efi_signature_store *db, + bool dbx) + +{ + struct efi_signature_store *siglist; + struct efi_sig_data *sig_data; + void *hash = NULL; + bool found = false; + bool hash_done = false; + + EFI_PRINT("%s: Enter, %p, %p\n", __func__, regs, db); + + if (!regs || !db || !db->sig_data_list) + goto out; + + for (siglist = db; siglist; siglist = siglist->next) { + int len = 0; + const char *hash_algo = NULL; + /* + * if the hash algorithm is unsupported and we get an entry in + * dbx reject the image + */ + if (dbx && !hash_algo_supported(siglist->sig_type)) { + found = true; + continue; + }; + /* + * Only support sha256 for now, that's what + * hash-to-efi-sig-list produces + */ + if (guidcmp(&siglist->sig_type, &efi_guid_sha256)) + continue; + + hash_algo = guid_to_sha_str(&efi_guid_sha256); + /* + * We could check size and hash_algo but efi_hash_regions() + * will do that for us + */ + if (!hash_done && + !efi_hash_regions(regs->reg, regs->num, &hash, hash_algo, + &len)) { + EFI_PRINT("Digesting an image failed\n"); + break; + } + hash_done = true; + + for (sig_data = siglist->sig_data_list; sig_data; + sig_data = sig_data->next) { +#ifdef DEBUG + EFI_PRINT("Msg digest in database:\n"); + print_hex_dump(" ", DUMP_PREFIX_OFFSET, 16, 1, + sig_data->data, sig_data->size, false); +#endif + if (sig_data->size == len && + !memcmp(sig_data->data, hash, len)) { + found = true; + free(hash); + goto out; + } + } + + free(hash); + hash = NULL; + } + +out: + EFI_PRINT("%s: Exit, found: %d\n", __func__, found); + return found; +} + +/** + * efi_lookup_certificate - find a certificate within db + * @msg: Signature + * @db: Signature database + * + * Search signature database pointed to by @db and find a certificate + * pointed to by @cert. + * + * Return: true if found, false otherwise. + */ +static bool efi_lookup_certificate(struct x509_certificate *cert, + struct efi_signature_store *db) +{ + struct efi_signature_store *siglist; + struct efi_sig_data *sig_data; + struct image_region reg[1]; + void *hash = NULL, *hash_tmp = NULL; + int len = 0; + bool found = false; + const char *hash_algo = NULL; + + EFI_PRINT("%s: Enter, %p, %p\n", __func__, cert, db); + + if (!cert || !db || !db->sig_data_list) + goto out; + + /* + * TODO: identify a certificate using sha256 digest + * Is there any better way? + */ + /* calculate hash of TBSCertificate */ + reg[0].data = cert->tbs; + reg[0].size = cert->tbs_size; + + /* We just need any sha256 algo to start the matching */ + hash_algo = guid_to_sha_str(&efi_guid_sha256); + if (!efi_hash_regions(reg, 1, &hash, hash_algo, &len)) + goto out; + + EFI_PRINT("%s: searching for %s\n", __func__, cert->subject); + for (siglist = db; siglist; siglist = siglist->next) { + /* only with x509 certificate */ + if (guidcmp(&siglist->sig_type, &efi_guid_cert_x509)) + continue; + + for (sig_data = siglist->sig_data_list; sig_data; + sig_data = sig_data->next) { + struct x509_certificate *cert_tmp; + + cert_tmp = x509_cert_parse(sig_data->data, + sig_data->size); + if (IS_ERR_OR_NULL(cert_tmp)) + continue; + + EFI_PRINT("%s: against %s\n", __func__, + cert_tmp->subject); + reg[0].data = cert_tmp->tbs; + reg[0].size = cert_tmp->tbs_size; + if (!efi_hash_regions(reg, 1, &hash_tmp, hash_algo, + NULL)) + goto out; + + x509_free_certificate(cert_tmp); + + if (!memcmp(hash, hash_tmp, len)) { + found = true; + goto out; + } + } + } +out: + free(hash); + free(hash_tmp); + + EFI_PRINT("%s: Exit, found: %d\n", __func__, found); + return found; +} + +/** + * efi_verify_certificate - verify certificate's signature with database + * @signer: Certificate + * @db: Signature database + * @root: Certificate to verify @signer + * + * Determine if certificate pointed to by @signer may be verified + * by one of certificates in signature database pointed to by @db. + * + * Return: true if certificate is verified, false otherwise. + */ +static bool efi_verify_certificate(struct x509_certificate *signer, + struct efi_signature_store *db, + struct x509_certificate **root) +{ + struct efi_signature_store *siglist; + struct efi_sig_data *sig_data; + struct x509_certificate *cert; + bool verified = false; + int ret; + + EFI_PRINT("%s: Enter, %p, %p\n", __func__, signer, db); + + if (!signer || !db || !db->sig_data_list) + goto out; + + for (siglist = db; siglist; siglist = siglist->next) { + /* only with x509 certificate */ + if (guidcmp(&siglist->sig_type, &efi_guid_cert_x509)) + continue; + + for (sig_data = siglist->sig_data_list; sig_data; + sig_data = sig_data->next) { + cert = x509_cert_parse(sig_data->data, sig_data->size); + if (IS_ERR_OR_NULL(cert)) { + EFI_PRINT("Cannot parse x509 certificate\n"); + continue; + } + + ret = public_key_verify_signature(cert->pub, + signer->sig); + if (!ret) { + verified = true; + if (root) + *root = cert; + else + x509_free_certificate(cert); + goto out; + } + x509_free_certificate(cert); + } + } + +out: + EFI_PRINT("%s: Exit, verified: %d\n", __func__, verified); + return verified; +} + +/** + * efi_signature_check_revocation - check revocation with dbx + * @sinfo: Signer's info + * @cert: x509 certificate + * @dbx: Revocation signature database + * + * Search revocation signature database pointed to by @dbx and find + * an entry matching to certificate pointed to by @cert. + * + * While this entry contains revocation time, we don't support timestamp + * protocol at this time and any image will be unconditionally revoked + * when this match occurs. + * + * Return: true if check passed (not found), false otherwise. + */ +static bool efi_signature_check_revocation(struct pkcs7_signed_info *sinfo, + struct x509_certificate *cert, + struct efi_signature_store *dbx) +{ + struct efi_signature_store *siglist; + struct efi_sig_data *sig_data; + struct image_region reg[1]; + void *hash = NULL; + int len = 0; + time64_t revoc_time; + bool revoked = false; + const char *hash_algo = NULL; + + EFI_PRINT("%s: Enter, %p, %p, %p\n", __func__, sinfo, cert, dbx); + + if (!sinfo || !cert || !dbx || !dbx->sig_data_list) + goto out; + + EFI_PRINT("Checking revocation against %s\n", cert->subject); + for (siglist = dbx; siglist; siglist = siglist->next) { + hash_algo = guid_to_sha_str(&siglist->sig_type); + if (!hash_algo) + continue; + + /* calculate hash of TBSCertificate */ + reg[0].data = cert->tbs; + reg[0].size = cert->tbs_size; + if (!efi_hash_regions(reg, 1, &hash, hash_algo, &len)) + goto out; + + for (sig_data = siglist->sig_data_list; sig_data; + sig_data = sig_data->next) { + /* + * struct efi_cert_x509_sha256 { + * u8 tbs_hash[256/8]; + * time64_t revocation_time; + * }; + */ +#ifdef DEBUG + if (sig_data->size >= len) { + EFI_PRINT("hash in db:\n"); + print_hex_dump(" ", DUMP_PREFIX_OFFSET, + 16, 1, + sig_data->data, len, false); + } +#endif + if ((sig_data->size < len + sizeof(time64_t)) || + memcmp(sig_data->data, hash, len)) + continue; + + memcpy(&revoc_time, sig_data->data + len, + sizeof(revoc_time)); + EFI_PRINT("revocation time: 0x%llx\n", revoc_time); + /* + * TODO: compare signing timestamp in sinfo + * with revocation time + */ + + revoked = true; + free(hash); + goto out; + } + free(hash); + hash = NULL; + } +out: + EFI_PRINT("%s: Exit, revoked: %d\n", __func__, revoked); + return !revoked; +} + +/* + * efi_signature_verify - verify signatures with db and dbx + * @regs: List of regions to be authenticated + * @msg: Signature + * @db: Signature database for trusted certificates + * @dbx: Revocation signature database + * + * All the signature pointed to by @msg against image pointed to by @regs + * will be verified by signature database pointed to by @db and @dbx. + * + * Return: true if verification for all signatures passed, false otherwise + */ +bool efi_signature_verify(struct efi_image_regions *regs, + struct pkcs7_message *msg, + struct efi_signature_store *db, + struct efi_signature_store *dbx) +{ + struct pkcs7_signed_info *sinfo; + struct x509_certificate *signer, *root; + bool verified = false; + int ret; + + EFI_PRINT("%s: Enter, %p, %p, %p, %p\n", __func__, regs, msg, db, dbx); + + if (!regs || !msg || !db || !db->sig_data_list) + goto out; + + for (sinfo = msg->signed_infos; sinfo; sinfo = sinfo->next) { + EFI_PRINT("Signed Info: digest algo: %s, pkey algo: %s\n", + sinfo->sig->hash_algo, sinfo->sig->pkey_algo); + + /* + * only for authenticated variable. + * + * If this function is called for image, + * hash calculation will be done in + * pkcs7_verify_one(). + */ + if (!msg->data && + !efi_hash_regions(regs->reg, regs->num, + (void **)&sinfo->sig->digest, + guid_to_sha_str(&efi_guid_sha256), + NULL)) { + EFI_PRINT("Digesting an image failed\n"); + goto out; + } + + EFI_PRINT("Verifying certificate chain\n"); + signer = NULL; + ret = pkcs7_verify_one(msg, sinfo, &signer); + if (ret == -ENOPKG) + continue; + + if (ret < 0 || !signer) + goto out; + + if (sinfo->blacklisted) + goto out; + + EFI_PRINT("Verifying last certificate in chain\n"); + if (efi_lookup_certificate(signer, db)) + if (efi_signature_check_revocation(sinfo, signer, dbx)) + break; + if (!signer->self_signed && + efi_verify_certificate(signer, db, &root)) { + bool check; + + check = efi_signature_check_revocation(sinfo, root, + dbx); + x509_free_certificate(root); + if (check) + break; + } + + EFI_PRINT("Certificate chain didn't reach trusted CA\n"); + } + if (sinfo) + verified = true; +out: + EFI_PRINT("%s: Exit, verified: %d\n", __func__, verified); + return verified; +} + +/** + * efi_signature_check_signers - check revocation against all signers with dbx + * @msg: Signature + * @dbx: Revocation signature database + * + * Determine if none of signers' certificates in @msg are revoked + * by signature database pointed to by @dbx. + * + * Return: true if all signers passed, false otherwise. + */ +bool efi_signature_check_signers(struct pkcs7_message *msg, + struct efi_signature_store *dbx) +{ + struct pkcs7_signed_info *sinfo; + bool revoked = false; + + EFI_PRINT("%s: Enter, %p, %p\n", __func__, msg, dbx); + + if (!msg || !dbx) + goto out; + + for (sinfo = msg->signed_infos; sinfo; sinfo = sinfo->next) { + if (sinfo->signer && + !efi_signature_check_revocation(sinfo, sinfo->signer, + dbx)) { + revoked = true; + break; + } + } +out: + EFI_PRINT("%s: Exit, revoked: %d\n", __func__, revoked); + return !revoked; +} + +/** + * efi_sigstore_free - free signature store + * @sigstore: Pointer to signature store structure + * + * Feee all the memories held in signature store and itself, + * which were allocated by efi_sigstore_parse_sigdb(). + */ +void efi_sigstore_free(struct efi_signature_store *sigstore) +{ + struct efi_signature_store *sigstore_next; + struct efi_sig_data *sig_data, *sig_data_next; + + while (sigstore) { + sigstore_next = sigstore->next; + + sig_data = sigstore->sig_data_list; + while (sig_data) { + sig_data_next = sig_data->next; + free(sig_data->data); + free(sig_data); + sig_data = sig_data_next; + } + + free(sigstore); + sigstore = sigstore_next; + } +} + +/** + * efi_sigstore_parse_siglist - parse a signature list + * @name: Pointer to signature list + * + * Parse signature list and instantiate a signature store structure. + * Signature database is a simple concatenation of one or more + * signature list(s). + * + * Return: Pointer to signature store on success, NULL on error + */ +static struct efi_signature_store * +efi_sigstore_parse_siglist(struct efi_signature_list *esl) +{ + struct efi_signature_store *siglist = NULL; + struct efi_sig_data *sig_data, *sig_data_next; + struct efi_signature_data *esd; + size_t left; + + /* + * UEFI specification defines certificate types: + * for non-signed images, + * EFI_CERT_SHA256_GUID + * EFI_CERT_RSA2048_GUID + * EFI_CERT_RSA2048_SHA256_GUID + * EFI_CERT_SHA1_GUID + * EFI_CERT_RSA2048_SHA_GUID + * EFI_CERT_SHA224_GUID + * EFI_CERT_SHA384_GUID + * EFI_CERT_SHA512_GUID + * + * for signed images, + * EFI_CERT_X509_GUID + * NOTE: Each certificate will normally be in a separate + * EFI_SIGNATURE_LIST as the size may vary depending on + * its algo's. + * + * for timestamp revocation of certificate, + * EFI_CERT_X509_SHA512_GUID + * EFI_CERT_X509_SHA256_GUID + * EFI_CERT_X509_SHA384_GUID + */ + + if (esl->signature_list_size + <= (sizeof(*esl) + esl->signature_header_size)) { + EFI_PRINT("Siglist in wrong format\n"); + return NULL; + } + + /* Create a head */ + siglist = calloc(sizeof(*siglist), 1); + if (!siglist) { + EFI_PRINT("Out of memory\n"); + goto err; + } + memcpy(&siglist->sig_type, &esl->signature_type, sizeof(efi_guid_t)); + + /* Go through the list */ + sig_data_next = NULL; + left = esl->signature_list_size + - (sizeof(*esl) + esl->signature_header_size); + esd = (struct efi_signature_data *) + ((u8 *)esl + sizeof(*esl) + esl->signature_header_size); + + while (left > 0) { + /* Signature must exist if there is remaining data. */ + if (left < esl->signature_size) { + EFI_PRINT("Certificate is too small\n"); + goto err; + } + + sig_data = calloc(esl->signature_size + - sizeof(esd->signature_owner), 1); + if (!sig_data) { + EFI_PRINT("Out of memory\n"); + goto err; + } + + /* Append signature data */ + memcpy(&sig_data->owner, &esd->signature_owner, + sizeof(efi_guid_t)); + sig_data->size = esl->signature_size + - sizeof(esd->signature_owner); + sig_data->data = malloc(sig_data->size); + if (!sig_data->data) { + EFI_PRINT("Out of memory\n"); + goto err; + } + memcpy(sig_data->data, esd->signature_data, sig_data->size); + + sig_data->next = sig_data_next; + sig_data_next = sig_data; + + /* Next */ + esd = (struct efi_signature_data *) + ((u8 *)esd + esl->signature_size); + left -= esl->signature_size; + } + siglist->sig_data_list = sig_data_next; + + return siglist; + +err: + efi_sigstore_free(siglist); + + return NULL; +} + +/** + * efi_sigstore_parse_sigdb - parse the signature list and populate + * the signature store + * + * @sig_list: Pointer to the signature list + * @size: Size of the signature list + * + * Parse the efi signature list and instantiate a signature store + * structure. + * + * Return: Pointer to signature store on success, NULL on error + */ +struct efi_signature_store *efi_build_signature_store(void *sig_list, + efi_uintn_t size) +{ + struct efi_signature_list *esl; + struct efi_signature_store *sigstore = NULL, *siglist; + + esl = sig_list; + while (size > 0) { + /* List must exist if there is remaining data. */ + if (size < sizeof(*esl)) { + EFI_PRINT("Signature list in wrong format\n"); + goto err; + } + + if (size < esl->signature_list_size) { + EFI_PRINT("Signature list in wrong format\n"); + goto err; + } + + /* Parse a single siglist. */ + siglist = efi_sigstore_parse_siglist(esl); + if (!siglist) { + EFI_PRINT("Parsing of signature list of failed\n"); + goto err; + } + + /* Append siglist */ + siglist->next = sigstore; + sigstore = siglist; + + /* Next */ + size -= esl->signature_list_size; + esl = (void *)esl + esl->signature_list_size; + } + free(sig_list); + + return sigstore; + +err: + efi_sigstore_free(sigstore); + free(sig_list); + + return NULL; +} + +/** + * efi_sigstore_parse_sigdb - parse a signature database variable + * @name: Variable's name + * + * Read in a value of signature database variable pointed to by + * @name, parse it and instantiate a signature store structure. + * + * Return: Pointer to signature store on success, NULL on error + */ +struct efi_signature_store *efi_sigstore_parse_sigdb(u16 *name) +{ + const efi_guid_t *vendor; + void *db; + efi_uintn_t db_size; + + vendor = efi_auth_var_get_guid(name); + db = efi_get_var(name, vendor, &db_size); + if (!db) { + EFI_PRINT("variable, %ls, not found\n", name); + return calloc(sizeof(struct efi_signature_store), 1); + } + + return efi_build_signature_store(db, db_size); +} diff --git a/lib/efi_loader/efi_smbios.c b/lib/efi_loader/efi_smbios.c new file mode 100644 index 00000000000..8d2ef6deb51 --- /dev/null +++ b/lib/efi_loader/efi_smbios.c @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI application tables support + * + * Copyright (c) 2016 Alexander Graf + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <efi_loader.h> +#include <log.h> +#include <malloc.h> +#include <mapmem.h> +#include <smbios.h> +#include <linux/sizes.h> +#include <asm/global_data.h> + +DECLARE_GLOBAL_DATA_PTR; + +const efi_guid_t smbios3_guid = SMBIOS3_TABLE_GUID; + +enum { + TABLE_SIZE = SZ_4K, +}; + +/* + * Install the SMBIOS table as a configuration table. + * + * Return: status code + */ +efi_status_t efi_smbios_register(void) +{ + ulong addr; + efi_status_t ret; + void *buf; + + addr = gd_smbios_start(); + if (!addr) { + log_err("No SMBIOS tables to install\n"); + return EFI_NOT_FOUND; + } + + /* Mark space used for tables */ + ret = efi_add_memory_map(addr, TABLE_SIZE, EFI_RUNTIME_SERVICES_DATA); + if (ret) + return ret; + + log_debug("EFI using SMBIOS tables at %lx\n", addr); + + /* Install SMBIOS information as configuration table */ + buf = map_sysmem(addr, 0); + ret = efi_install_configuration_table(&smbios3_guid, buf); + unmap_sysmem(buf); + + return ret; +} + +static int install_smbios_table(void) +{ + ulong addr; + void *buf; + + if (!IS_ENABLED(CONFIG_GENERATE_SMBIOS_TABLE) || + IS_ENABLED(CONFIG_X86) || + IS_ENABLED(CONFIG_QFW_SMBIOS)) + return 0; + + /* Align the table to a 4KB boundary to keep EFI happy */ + buf = memalign(SZ_4K, TABLE_SIZE); + if (!buf) + return log_msg_ret("mem", -ENOMEM); + + addr = map_to_sysmem(buf); + if (!write_smbios_table(addr)) { + log_err("Failed to write SMBIOS table\n"); + return log_msg_ret("smbios", -EINVAL); + } + + /* Make a note of where we put it */ + log_debug("SMBIOS tables written to %lx\n", addr); + gd->arch.smbios_start = addr; + + return 0; +} +EVENT_SPY_SIMPLE(EVT_LAST_STAGE_INIT, install_smbios_table); diff --git a/lib/efi_loader/efi_string.c b/lib/efi_loader/efi_string.c new file mode 100644 index 00000000000..50d1daf33a9 --- /dev/null +++ b/lib/efi_loader/efi_string.c @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * String functions + * + * Copyright (c) 2020 AKASHI Takahiro, Linaro Limited + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <charset.h> +#include <efi_loader.h> +#include <malloc.h> + +/** + * efi_create_indexed_name - create a string name with an index + * @buffer: Buffer + * @name: Name string + * @index: Index + * + * Create a utf-16 string with @name, appending @index. + * For example, u"Capsule0001" + * + * The caller must ensure that the buffer has enough space for the resulting + * string including the trailing L'\0'. + * + * Return: A pointer to the next position after the created string + * in @buffer, or NULL otherwise + */ +u16 *efi_create_indexed_name(u16 *buffer, size_t buffer_size, const char *name, + unsigned int index) +{ + u16 *p = buffer; + char index_buf[5]; + size_t size; + + size = (utf8_utf16_strlen(name) * sizeof(u16) + + sizeof(index_buf) * sizeof(u16)); + if (buffer_size < size) + return NULL; + utf8_utf16_strcpy(&p, name); + snprintf(index_buf, sizeof(index_buf), "%04X", index); + utf8_utf16_strcpy(&p, index_buf); + + return p; +} + +/** + * efi_convert_string - Convert an ASCII or UTF-8 string to UTF-16 + * @str: String to be converted + * + * Return: Converted string in UTF-16 format. The caller is responsible for + * freeing this string when it is no longer needed. + */ +efi_string_t efi_convert_string(const char *str) +{ + efi_string_t str_16, tmp; + size_t sz_16; + + sz_16 = utf8_utf16_strlen(str); + str_16 = calloc(sz_16 + 1, sizeof(u16)); + if (!str_16) + return NULL; + + tmp = str_16; + utf8_utf16_strcpy(&tmp, str); + + return str_16; +} diff --git a/lib/efi_loader/efi_tcg2.c b/lib/efi_loader/efi_tcg2.c new file mode 100644 index 00000000000..210a846ebc8 --- /dev/null +++ b/lib/efi_loader/efi_tcg2.c @@ -0,0 +1,1626 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Defines APIs that allow an OS to interact with UEFI firmware to query + * information about the device. + * https://trustedcomputinggroup.org/resource/tcg-efi-protocol-specification/ + * + * Copyright (c) 2020, Linaro Limited + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <dm.h> +#include <efi_loader.h> +#include <efi_variable.h> +#include <efi_tcg2.h> +#include <log.h> +#include <malloc.h> +#include <smbios.h> +#include <version_string.h> +#include <tpm_api.h> +#include <u-boot/hash-checksum.h> +#include <linux/unaligned/be_byteshift.h> +#include <linux/unaligned/le_byteshift.h> +#include <linux/unaligned/generic.h> +#include <hexdump.h> + +/** + * struct event_log_buffer - internal eventlog management structure + * + * @buffer: eventlog buffer + * @final_buffer: finalevent config table buffer + * @pos: current position of 'buffer' + * @final_pos: current position of 'final_buffer' + * @get_event_called: true if GetEventLog has been invoked at least once + * @ebs_called: true if ExitBootServices has been invoked + * @truncated: true if the 'buffer' is truncated + */ +struct event_log_buffer { + void *buffer; + void *final_buffer; + size_t pos; /* eventlog position */ + size_t final_pos; /* final events config table position */ + size_t last_event_size; + bool get_event_called; + bool ebs_called; + bool truncated; +}; + +static struct event_log_buffer event_log; +static bool tcg2_efi_app_invoked; +/* + * When requesting TPM2_CAP_TPM_PROPERTIES the value is on a standard offset. + * Since the current tpm2_get_capability() response buffers starts at + * 'union tpmu_capabilities data' of 'struct tpms_capability_data', calculate + * the response size and offset once for all consumers + */ +#define TPM2_RESPONSE_BUFFER_SIZE (sizeof(struct tpms_capability_data) - \ + offsetof(struct tpms_capability_data, data)) +#define properties_offset (offsetof(struct tpml_tagged_tpm_property, tpm_property) + \ + offsetof(struct tpms_tagged_property, value)) + +static const efi_guid_t efi_guid_tcg2_protocol = EFI_TCG2_PROTOCOL_GUID; +static const efi_guid_t efi_guid_final_events = EFI_TCG2_FINAL_EVENTS_TABLE_GUID; + +struct variable_info { + const u16 *name; + bool accept_empty; + u32 pcr_index; +}; + +static struct variable_info secure_variables[] = { + {u"SecureBoot", true, 7}, + {u"PK", true, 7}, + {u"KEK", true, 7}, + {u"db", true, 7}, + {u"dbx", true, 7}, + {u"dbt", false, 7}, + {u"dbr", false, 7}, + {u"DeployedMode", false, 1}, + {u"AuditMode", false, 1}, +}; + +static bool is_tcg2_protocol_installed(void) +{ + struct efi_handler *handler; + efi_status_t ret; + + ret = efi_search_protocol(efi_root, &efi_guid_tcg2_protocol, &handler); + return ret == EFI_SUCCESS; +} + +/* tcg2_agile_log_append - Append an agile event to an eventlog + * + * @pcr_index: PCR index + * @event_type: type of event added + * @digest_list: list of digest algorithms to add + * @size: size of event + * @event: event to add + * @log: log buffer to append the event + * + * @Return: status code + */ +static efi_status_t tcg2_agile_log_append(u32 pcr_index, u32 event_type, + struct tpml_digest_values *digest_list, + u32 size, u8 event[]) +{ + void *log = (void *)((uintptr_t)event_log.buffer + event_log.pos); + u32 event_size = size + tcg2_event_get_size(digest_list); + struct efi_tcg2_final_events_table *final_event; + efi_status_t ret = EFI_SUCCESS; + + /* if ExitBootServices hasn't been called update the normal log */ + if (!event_log.ebs_called) { + if (event_log.truncated || + event_log.pos + event_size > CONFIG_TPM2_EVENT_LOG_SIZE) { + event_log.truncated = true; + return EFI_VOLUME_FULL; + } + tcg2_log_append(pcr_index, event_type, digest_list, size, event, log); + event_log.pos += event_size; + event_log.last_event_size = event_size; + } + + if (!event_log.get_event_called) + return ret; + + /* if GetEventLog has been called update FinalEventLog as well */ + if (event_log.final_pos + event_size > CONFIG_TPM2_EVENT_LOG_SIZE) + return EFI_VOLUME_FULL; + + log = (void *)((uintptr_t)event_log.final_buffer + event_log.final_pos); + tcg2_log_append(pcr_index, event_type, digest_list, size, event, log); + + final_event = event_log.final_buffer; + final_event->number_of_events++; + event_log.final_pos += event_size; + + return ret; +} + +/** + * tpm2_get_max_command_size() - get the supported max command size + * + * @dev: TPM device + * @max_command_size: output buffer for the size + * + * Return: 0 on success, -1 on error + */ +static int tpm2_get_max_command_size(struct udevice *dev, u16 *max_command_size) +{ + u8 response[TPM2_RESPONSE_BUFFER_SIZE]; + u32 ret; + + memset(response, 0, sizeof(response)); + ret = tpm2_get_capability(dev, TPM2_CAP_TPM_PROPERTIES, + TPM2_PT_MAX_COMMAND_SIZE, response, 1); + if (ret) + return -1; + + *max_command_size = (uint16_t)get_unaligned_be32(response + + properties_offset); + + return 0; +} + +/** + * tpm2_get_max_response_size() - get the supported max response size + * + * @dev: TPM device + * @max_response_size: output buffer for the size + * + * Return: 0 on success, -1 on error + */ +static int tpm2_get_max_response_size(struct udevice *dev, + u16 *max_response_size) +{ + u8 response[TPM2_RESPONSE_BUFFER_SIZE]; + u32 ret; + + memset(response, 0, sizeof(response)); + ret = tpm2_get_capability(dev, TPM2_CAP_TPM_PROPERTIES, + TPM2_PT_MAX_RESPONSE_SIZE, response, 1); + if (ret) + return -1; + + *max_response_size = (uint16_t)get_unaligned_be32(response + + properties_offset); + + return 0; +} + +/** + * tpm2_get_manufacturer_id() - get the manufacturer ID + * + * @dev: TPM device + * @manufacturer_id: output buffer for the id + * + * Return: 0 on success, -1 on error + */ +static int tpm2_get_manufacturer_id(struct udevice *dev, u32 *manufacturer_id) +{ + u8 response[TPM2_RESPONSE_BUFFER_SIZE]; + u32 ret; + + memset(response, 0, sizeof(response)); + ret = tpm2_get_capability(dev, TPM2_CAP_TPM_PROPERTIES, + TPM2_PT_MANUFACTURER, response, 1); + if (ret) + return -1; + + *manufacturer_id = get_unaligned_be32(response + properties_offset); + + return 0; +} + +/** + * efi_tcg2_get_capability() - protocol capability information and state information + * + * @this: TCG2 protocol instance + * @capability: caller allocated memory with size field to the size of + * the structure allocated + + * Return: status code + */ +static efi_status_t EFIAPI +efi_tcg2_get_capability(struct efi_tcg2_protocol *this, + struct efi_tcg2_boot_service_capability *capability) +{ + struct udevice *dev; + efi_status_t efi_ret; + int ret; + + EFI_ENTRY("%p, %p", this, capability); + + if (!this || !capability) { + efi_ret = EFI_INVALID_PARAMETER; + goto out; + } + + if (capability->size < BOOT_SERVICE_CAPABILITY_MIN) { + capability->size = BOOT_SERVICE_CAPABILITY_MIN; + efi_ret = EFI_BUFFER_TOO_SMALL; + goto out; + } + + if (capability->size < sizeof(*capability)) { + capability->size = sizeof(*capability); + efi_ret = EFI_BUFFER_TOO_SMALL; + goto out; + } + + capability->structure_version.major = 1; + capability->structure_version.minor = 1; + capability->protocol_version.major = 1; + capability->protocol_version.minor = 1; + + ret = tcg2_platform_get_tpm2(&dev); + if (ret) { + capability->supported_event_logs = 0; + capability->hash_algorithm_bitmap = 0; + capability->tpm_present_flag = false; + capability->max_command_size = 0; + capability->max_response_size = 0; + capability->manufacturer_id = 0; + capability->number_of_pcr_banks = 0; + capability->active_pcr_banks = 0; + + efi_ret = EFI_SUCCESS; + goto out; + } + + /* We only allow a TPMv2 device to register the EFI protocol */ + capability->supported_event_logs = TCG2_EVENT_LOG_FORMAT_TCG_2; + + capability->tpm_present_flag = true; + + /* Supported and active PCRs */ + capability->hash_algorithm_bitmap = 0; + capability->active_pcr_banks = 0; + ret = tcg2_get_pcr_info(dev, &capability->hash_algorithm_bitmap, + &capability->active_pcr_banks, + &capability->number_of_pcr_banks); + if (ret) { + efi_ret = EFI_DEVICE_ERROR; + goto out; + } + + /* Max command size */ + ret = tpm2_get_max_command_size(dev, &capability->max_command_size); + if (ret) { + efi_ret = EFI_DEVICE_ERROR; + goto out; + } + + /* Max response size */ + ret = tpm2_get_max_response_size(dev, &capability->max_response_size); + if (ret) { + efi_ret = EFI_DEVICE_ERROR; + goto out; + } + + /* Manufacturer ID */ + ret = tpm2_get_manufacturer_id(dev, &capability->manufacturer_id); + if (ret) { + efi_ret = EFI_DEVICE_ERROR; + goto out; + } + + return EFI_EXIT(EFI_SUCCESS); +out: + return EFI_EXIT(efi_ret); +} + +/** + * efi_tcg2_get_eventlog() - retrieve the the address of an event log and its + * last entry + * + * @this: TCG2 protocol instance + * @log_format: type of event log format + * @event_log_location: pointer to the memory address of the event log + * @event_log_last_entry: pointer to the address of the start of the last + * entry in the event log in memory, if log contains + * more than 1 entry + * @event_log_truncated: set to true, if the Event Log is missing at i + * least one entry + * + * Return: status code + */ +static efi_status_t EFIAPI +efi_tcg2_get_eventlog(struct efi_tcg2_protocol *this, + efi_tcg_event_log_format log_format, + u64 *event_log_location, u64 *event_log_last_entry, + bool *event_log_truncated) +{ + efi_status_t ret = EFI_SUCCESS; + struct udevice *dev; + + EFI_ENTRY("%p, %u, %p, %p, %p", this, log_format, event_log_location, + event_log_last_entry, event_log_truncated); + + if (!this || !event_log_location || !event_log_last_entry || + !event_log_truncated) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + /* Only support TPMV2 */ + if (log_format != TCG2_EVENT_LOG_FORMAT_TCG_2) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + if (tcg2_platform_get_tpm2(&dev)) { + event_log_location = NULL; + event_log_last_entry = NULL; + *event_log_truncated = false; + ret = EFI_SUCCESS; + goto out; + } + *event_log_location = (uintptr_t)event_log.buffer; + *event_log_last_entry = (uintptr_t)(event_log.buffer + event_log.pos - + event_log.last_event_size); + *event_log_truncated = event_log.truncated; + event_log.get_event_called = true; + +out: + return EFI_EXIT(ret); +} + +/** + * tcg2_hash_pe_image() - calculate PE/COFF image hash + * + * @efi: pointer to the EFI binary + * @efi_size: size of @efi binary + * @digest_list: list of digest algorithms to extend + * + * Return: status code + */ +static efi_status_t tcg2_hash_pe_image(void *efi, u64 efi_size, + struct tpml_digest_values *digest_list) +{ + WIN_CERTIFICATE *wincerts = NULL; + size_t wincerts_len; + struct efi_image_regions *regs = NULL; + void *new_efi = NULL; + u8 hash[TPM2_SHA512_DIGEST_SIZE]; + struct udevice *dev; + efi_status_t ret = EFI_SUCCESS; + u32 active; + int i; + + new_efi = efi_prepare_aligned_image(efi, &efi_size); + if (!new_efi) + return EFI_OUT_OF_RESOURCES; + + if (!efi_image_parse(new_efi, efi_size, ®s, &wincerts, + &wincerts_len)) { + log_err("Parsing PE executable image failed\n"); + ret = EFI_UNSUPPORTED; + goto out; + } + + if (tcg2_platform_get_tpm2(&dev)) { + ret = EFI_DEVICE_ERROR; + goto out; + } + + if (tcg2_get_active_pcr_banks(dev, &active)) { + ret = EFI_DEVICE_ERROR; + goto out; + } + + digest_list->count = 0; + for (i = 0; i < ARRAY_SIZE(hash_algo_list); i++) { + u16 hash_alg = hash_algo_list[i].hash_alg; + + if (!(active & hash_algo_list[i].hash_mask)) + continue; + switch (hash_alg) { + case TPM2_ALG_SHA1: + hash_calculate("sha1", regs->reg, regs->num, hash); + break; + case TPM2_ALG_SHA256: + hash_calculate("sha256", regs->reg, regs->num, hash); + break; + case TPM2_ALG_SHA384: + hash_calculate("sha384", regs->reg, regs->num, hash); + break; + case TPM2_ALG_SHA512: + hash_calculate("sha512", regs->reg, regs->num, hash); + break; + default: + continue; + } + digest_list->digests[digest_list->count].hash_alg = hash_alg; + memcpy(&digest_list->digests[digest_list->count].digest, hash, + (u32)tpm2_algorithm_to_len(hash_alg)); + digest_list->count++; + } + +out: + if (new_efi != efi) + free(new_efi); + free(regs); + + return ret; +} + +/** + * tcg2_measure_pe_image() - measure PE/COFF image + * + * @efi: pointer to the EFI binary + * @efi_size: size of @efi binary + * @handle: loaded image handle + * @loaded_image: loaded image protocol + * + * Return: status code + */ +efi_status_t tcg2_measure_pe_image(void *efi, u64 efi_size, + struct efi_loaded_image_obj *handle, + struct efi_loaded_image *loaded_image) +{ + struct tpml_digest_values digest_list; + efi_status_t ret; + struct udevice *dev; + u32 pcr_index, event_type, event_size; + struct uefi_image_load_event *image_load_event; + struct efi_device_path *device_path; + u32 device_path_length; + IMAGE_DOS_HEADER *dos; + IMAGE_NT_HEADERS32 *nt; + struct efi_handler *handler; + int rc; + + if (!is_tcg2_protocol_installed()) + return EFI_SUCCESS; + + if (tcg2_platform_get_tpm2(&dev)) + return EFI_SECURITY_VIOLATION; + + switch (handle->image_type) { + case IMAGE_SUBSYSTEM_EFI_APPLICATION: + pcr_index = 4; + event_type = EV_EFI_BOOT_SERVICES_APPLICATION; + break; + case IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER: + pcr_index = 2; + event_type = EV_EFI_BOOT_SERVICES_DRIVER; + break; + case IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER: + pcr_index = 2; + event_type = EV_EFI_RUNTIME_SERVICES_DRIVER; + break; + default: + return EFI_UNSUPPORTED; + } + + ret = tcg2_hash_pe_image(efi, efi_size, &digest_list); + if (ret != EFI_SUCCESS) + return ret; + + rc = tcg2_pcr_extend(dev, pcr_index, &digest_list); + if (rc) + return EFI_DEVICE_ERROR; + + ret = efi_search_protocol(&handle->header, + &efi_guid_loaded_image_device_path, &handler); + if (ret != EFI_SUCCESS) + return ret; + + device_path = handler->protocol_interface; + device_path_length = efi_dp_size(device_path); + if (device_path_length > 0) { + /* add end node size */ + device_path_length += sizeof(struct efi_device_path); + } + event_size = sizeof(struct uefi_image_load_event) + device_path_length; + image_load_event = calloc(1, event_size); + if (!image_load_event) + return EFI_OUT_OF_RESOURCES; + + image_load_event->image_location_in_memory = (uintptr_t)efi; + image_load_event->image_length_in_memory = efi_size; + image_load_event->length_of_device_path = device_path_length; + + dos = (IMAGE_DOS_HEADER *)efi; + nt = (IMAGE_NT_HEADERS32 *)(efi + dos->e_lfanew); + if (nt->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) { + IMAGE_NT_HEADERS64 *nt64 = (IMAGE_NT_HEADERS64 *)nt; + + image_load_event->image_link_time_address = + nt64->OptionalHeader.ImageBase; + } else if (nt->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) { + image_load_event->image_link_time_address = + nt->OptionalHeader.ImageBase; + } else { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + /* device_path_length might be zero */ + memcpy(image_load_event->device_path, device_path, device_path_length); + + ret = tcg2_agile_log_append(pcr_index, event_type, &digest_list, + event_size, (u8 *)image_load_event); + +out: + free(image_load_event); + + return ret; +} + +/** + * efi_tcg2_hash_log_extend_event() - extend and optionally log events + * + * @this: TCG2 protocol instance + * @flags: bitmap providing additional information on the + * operation + * @data_to_hash: physical address of the start of the data buffer + * to be hashed + * @data_to_hash_len: the length in bytes of the buffer referenced by + * data_to_hash + * @efi_tcg_event: pointer to data buffer containing information + * about the event + * + * Return: status code + */ +static efi_status_t EFIAPI +efi_tcg2_hash_log_extend_event(struct efi_tcg2_protocol *this, u64 flags, + u64 data_to_hash, u64 data_to_hash_len, + struct efi_tcg2_event *efi_tcg_event) +{ + struct udevice *dev; + efi_status_t ret = EFI_SUCCESS; + u32 event_type, pcr_index, event_size; + struct tpml_digest_values digest_list; + int rc = 0; + + EFI_ENTRY("%p, %llu, %llu, %llu, %p", this, flags, data_to_hash, + data_to_hash_len, efi_tcg_event); + + if (!this || !data_to_hash || !efi_tcg_event) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + if (tcg2_platform_get_tpm2(&dev)) { + ret = EFI_DEVICE_ERROR; + goto out; + } + + if (efi_tcg_event->size < efi_tcg_event->header.header_size + + sizeof(u32)) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + if (efi_tcg_event->header.pcr_index > EFI_TCG2_MAX_PCR_INDEX) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + /* + * if PE_COFF_IMAGE is set we need to make sure the image is not + * corrupted, verify it and hash the PE/COFF image in accordance with + * the procedure specified in "Calculating the PE Image Hash" + * section of the "Windows Authenticode Portable Executable Signature + * Format" + */ + if (flags & PE_COFF_IMAGE) { + ret = efi_check_pe((void *)(uintptr_t)data_to_hash, + data_to_hash_len, NULL); + if (ret != EFI_SUCCESS) { + ret = EFI_UNSUPPORTED; + goto out; + } + ret = tcg2_hash_pe_image((void *)(uintptr_t)data_to_hash, + data_to_hash_len, &digest_list); + } else { + rc = tcg2_create_digest(dev, (u8 *)(uintptr_t)data_to_hash, + data_to_hash_len, &digest_list); + if (rc) + ret = EFI_DEVICE_ERROR; + } + + if (ret != EFI_SUCCESS) + goto out; + + pcr_index = efi_tcg_event->header.pcr_index; + event_type = efi_tcg_event->header.event_type; + + rc = tcg2_pcr_extend(dev, pcr_index, &digest_list); + if (rc) { + ret = EFI_DEVICE_ERROR; + goto out; + } + + if (flags & EFI_TCG2_EXTEND_ONLY) { + if (event_log.truncated) + ret = EFI_VOLUME_FULL; + goto out; + } + + /* + * The efi_tcg_event size includes the size component and the + * headersize + */ + event_size = efi_tcg_event->size - sizeof(efi_tcg_event->size) - + efi_tcg_event->header.header_size; + ret = tcg2_agile_log_append(pcr_index, event_type, &digest_list, + event_size, efi_tcg_event->event); +out: + return EFI_EXIT(ret); +} + +/** + * efi_tcg2_submit_command() - Send command to the TPM + * + * @this: TCG2 protocol instance + * @input_param_block_size: size of the TPM input parameter block + * @input_param_block: pointer to the TPM input parameter block + * @output_param_block_size: size of the TPM output parameter block + * @output_param_block: pointer to the TPM output parameter block + * + * Return: status code + */ +static efi_status_t EFIAPI +efi_tcg2_submit_command(struct efi_tcg2_protocol *this, + u32 input_param_block_size, + u8 *input_param_block, + u32 output_param_block_size, + u8 *output_param_block) +{ + struct udevice *dev; + efi_status_t ret = EFI_SUCCESS; + u32 rc; + size_t resp_buf_size = output_param_block_size; + + EFI_ENTRY("%p, %u, %p, %u, %p", this, input_param_block_size, + input_param_block, output_param_block_size, output_param_block); + + if (!this || !input_param_block || !input_param_block_size) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + if (tcg2_platform_get_tpm2(&dev)) { + ret = EFI_DEVICE_ERROR; + goto out; + } + + rc = tpm2_submit_command(dev, input_param_block, + output_param_block, &resp_buf_size); + if (rc) { + ret = (rc == -ENOSPC) ? EFI_OUT_OF_RESOURCES : EFI_DEVICE_ERROR; + + goto out; + } + +out: + return EFI_EXIT(ret); +} + +/** + * efi_tcg2_get_active_pcr_banks() - returns the currently active PCR banks + * + * @this: TCG2 protocol instance + * @active_pcr_banks: pointer for receiving the bitmap of currently + * active PCR banks + * + * Return: status code + */ +static efi_status_t EFIAPI +efi_tcg2_get_active_pcr_banks(struct efi_tcg2_protocol *this, + u32 *active_pcr_banks) +{ + struct udevice *dev; + efi_status_t ret = EFI_INVALID_PARAMETER; + + EFI_ENTRY("%p, %p", this, active_pcr_banks); + + if (!this || !active_pcr_banks) + goto out; + + if (tcg2_platform_get_tpm2(&dev)) + goto out; + + /* + * EFI_INVALID_PARAMETER does not convey the problem type. + * but that's what currently the spec specifies. + * EFI_DEVICE_ERROR would be better + */ + if (tcg2_get_active_pcr_banks(dev, active_pcr_banks)) + goto out; + + ret = EFI_SUCCESS; + +out: + return EFI_EXIT(ret); +} + +/** + * efi_tcg2_set_active_pcr_banks() - sets the currently active PCR banks + * + * @this: TCG2 protocol instance + * @active_pcr_banks: bitmap of the requested active PCR banks + * + * Return: status code + */ +static efi_status_t EFIAPI +efi_tcg2_set_active_pcr_banks(__maybe_unused struct efi_tcg2_protocol *this, + u32 __maybe_unused active_pcr_banks) +{ + return EFI_UNSUPPORTED; +} + +/** + * efi_tcg2_get_result_of_set_active_pcr_banks() - retrieve result for previous + * set_active_pcr_banks() + * + * @this: TCG2 protocol instance + * @operation_present: non-zero value to indicate a + * set_active_pcr_banks operation was + * invoked during last boot + * @response: result value could be returned + * + * Return: status code + */ +static efi_status_t EFIAPI +efi_tcg2_get_result_of_set_active_pcr_banks(__maybe_unused struct efi_tcg2_protocol *this, + u32 __maybe_unused *operation_present, + u32 __maybe_unused *response) +{ + return EFI_UNSUPPORTED; +} + +static const struct efi_tcg2_protocol efi_tcg2_protocol = { + .get_capability = efi_tcg2_get_capability, + .get_eventlog = efi_tcg2_get_eventlog, + .hash_log_extend_event = efi_tcg2_hash_log_extend_event, + .submit_command = efi_tcg2_submit_command, + .get_active_pcr_banks = efi_tcg2_get_active_pcr_banks, + .set_active_pcr_banks = efi_tcg2_set_active_pcr_banks, + .get_result_of_set_active_pcr_banks = efi_tcg2_get_result_of_set_active_pcr_banks, +}; + +/** + * tcg2_uninit - remove the final event table and free efi memory on failures + */ +static void tcg2_uninit(void) +{ + efi_status_t ret; + + ret = efi_install_configuration_table(&efi_guid_final_events, NULL); + if (ret != EFI_SUCCESS && ret != EFI_NOT_FOUND) + log_err("Failed to delete final events config table\n"); + + efi_free_pool(event_log.buffer); + event_log.buffer = NULL; + efi_free_pool(event_log.final_buffer); + event_log.final_buffer = NULL; + + if (!is_tcg2_protocol_installed()) + return; + + ret = efi_uninstall_multiple_protocol_interfaces(efi_root, &efi_guid_tcg2_protocol, + &efi_tcg2_protocol, NULL); + if (ret != EFI_SUCCESS) + log_err("Failed to remove EFI TCG2 protocol\n"); +} + +/** + * create_final_event() - Create the final event and install the config + * defined by the TCG EFI spec + */ +static efi_status_t create_final_event(void) +{ + struct efi_tcg2_final_events_table *final_event; + efi_status_t ret; + + /* + * All events generated after the invocation of + * EFI_TCG2_GET_EVENT_LOGS need to be stored in an instance of an + * EFI_CONFIGURATION_TABLE + */ + ret = efi_allocate_pool(EFI_ACPI_MEMORY_NVS, CONFIG_TPM2_EVENT_LOG_SIZE, + &event_log.final_buffer); + if (ret != EFI_SUCCESS) + goto out; + + memset(event_log.final_buffer, 0xff, CONFIG_TPM2_EVENT_LOG_SIZE); + final_event = event_log.final_buffer; + final_event->number_of_events = 0; + final_event->version = EFI_TCG2_FINAL_EVENTS_TABLE_VERSION; + event_log.final_pos = sizeof(*final_event); + ret = efi_install_configuration_table(&efi_guid_final_events, + final_event); + if (ret != EFI_SUCCESS) { + efi_free_pool(event_log.final_buffer); + event_log.final_buffer = NULL; + } + +out: + return ret; +} + +/** + * measure_event() - common function to add event log and extend PCR + * + * @dev: TPM device + * @pcr_index: PCR index + * @event_type: type of event added + * @size: event size + * @event: event data + * + * Return: status code + */ +static efi_status_t measure_event(struct udevice *dev, u32 pcr_index, + u32 event_type, u32 size, u8 event[]) +{ + struct tpml_digest_values digest_list; + efi_status_t ret = EFI_DEVICE_ERROR; + int rc; + + rc = tcg2_create_digest(dev, event, size, &digest_list); + if (rc) + goto out; + + rc = tcg2_pcr_extend(dev, pcr_index, &digest_list); + if (rc) + goto out; + + ret = tcg2_agile_log_append(pcr_index, event_type, &digest_list, + size, event); + +out: + return ret; +} + +/** + * efi_append_scrtm_version - Append an S-CRTM EV_S_CRTM_VERSION event on the + * eventlog and extend the PCRs + * + * @dev: TPM device + * + * @Return: status code + */ +static efi_status_t efi_append_scrtm_version(struct udevice *dev) +{ + efi_status_t ret; + + ret = measure_event(dev, 0, EV_S_CRTM_VERSION, + strlen(version_string) + 1, (u8 *)version_string); + + return ret; +} + +/** + * efi_init_event_log() - initialize an eventlog + * + * Return: status code + */ +static efi_status_t efi_init_event_log(void) +{ + /* + * vendor_info_size is currently set to 0, we need to change the length + * and allocate the flexible array member if this changes + */ + struct tcg2_event_log elog; + struct udevice *dev; + efi_status_t ret; + int rc; + + if (tcg2_platform_get_tpm2(&dev)) + return EFI_DEVICE_ERROR; + + ret = efi_allocate_pool(EFI_BOOT_SERVICES_DATA, + CONFIG_TPM2_EVENT_LOG_SIZE, + (void **)&event_log.buffer); + if (ret != EFI_SUCCESS) + return ret; + + /* + * initialize log area as 0xff so the OS can easily figure out the + * last log entry + */ + memset(event_log.buffer, 0xff, CONFIG_TPM2_EVENT_LOG_SIZE); + + /* + * The log header is defined to be in SHA1 event log entry format. + * Setup event header + */ + event_log.pos = 0; + event_log.last_event_size = 0; + event_log.get_event_called = false; + event_log.ebs_called = false; + event_log.truncated = false; + + /* + * Check if earlier firmware have passed any eventlog. Different + * platforms can use different ways to do so. + */ + elog.log = event_log.buffer; + elog.log_size = CONFIG_TPM2_EVENT_LOG_SIZE; + rc = tcg2_log_prepare_buffer(dev, &elog, false); + if (rc) { + ret = (rc == -ENOBUFS) ? EFI_BUFFER_TOO_SMALL : EFI_DEVICE_ERROR; + goto free_pool; + } + + event_log.pos = elog.log_position; + + /* + * Add SCRTM version to the log if previous firmmware + * doesn't pass an eventlog. + */ + if (!elog.found) { + ret = efi_append_scrtm_version(dev); + if (ret != EFI_SUCCESS) + goto free_pool; + } + + ret = create_final_event(); + if (ret != EFI_SUCCESS) + goto free_pool; + + return ret; + +free_pool: + efi_free_pool(event_log.buffer); + event_log.buffer = NULL; + return ret; +} + +/** + * tcg2_measure_variable() - add variable event log and extend PCR + * + * @dev: TPM device + * @pcr_index: PCR index + * @event_type: type of event added + * @var_name: variable name + * @guid: guid + * @data_size: variable data size + * @data: variable data + * + * Return: status code + */ +static efi_status_t tcg2_measure_variable(struct udevice *dev, u32 pcr_index, + u32 event_type, const u16 *var_name, + const efi_guid_t *guid, + efi_uintn_t data_size, u8 *data) +{ + u32 event_size; + efi_status_t ret; + struct efi_tcg2_uefi_variable_data *event; + + event_size = sizeof(event->variable_name) + + sizeof(event->unicode_name_length) + + sizeof(event->variable_data_length) + + (u16_strlen(var_name) * sizeof(u16)) + data_size; + event = malloc(event_size); + if (!event) + return EFI_OUT_OF_RESOURCES; + + guidcpy(&event->variable_name, guid); + event->unicode_name_length = u16_strlen(var_name); + event->variable_data_length = data_size; + memcpy(event->unicode_name, var_name, + (event->unicode_name_length * sizeof(u16))); + if (data) { + memcpy((u16 *)event->unicode_name + event->unicode_name_length, + data, data_size); + } + ret = measure_event(dev, pcr_index, event_type, event_size, + (u8 *)event); + free(event); + return ret; +} + +/** + * tcg2_measure_boot_variable() - measure boot variables + * + * @dev: TPM device + * + * Return: status code + */ +static efi_status_t tcg2_measure_boot_variable(struct udevice *dev) +{ + u16 *boot_order; + u16 *boot_index; + u16 var_name[] = u"BootOrder"; + u16 boot_name[] = u"Boot####"; + u8 *bootvar; + efi_uintn_t var_data_size; + u32 count, i; + efi_status_t ret; + + boot_order = efi_get_var(var_name, &efi_global_variable_guid, + &var_data_size); + if (!boot_order) { + /* If "BootOrder" is not defined, skip the boot variable measurement */ + return EFI_SUCCESS; + } + + ret = tcg2_measure_variable(dev, 1, EV_EFI_VARIABLE_BOOT2, var_name, + &efi_global_variable_guid, var_data_size, + (u8 *)boot_order); + if (ret != EFI_SUCCESS) + goto error; + + count = var_data_size / sizeof(*boot_order); + boot_index = boot_order; + for (i = 0; i < count; i++) { + efi_create_indexed_name(boot_name, sizeof(boot_name), + "Boot", *boot_index++); + + bootvar = efi_get_var(boot_name, &efi_global_variable_guid, + &var_data_size); + + if (!bootvar) { + log_debug("%ls not found\n", boot_name); + continue; + } + + ret = tcg2_measure_variable(dev, 1, EV_EFI_VARIABLE_BOOT2, + boot_name, + &efi_global_variable_guid, + var_data_size, bootvar); + free(bootvar); + if (ret != EFI_SUCCESS) + goto error; + } + +error: + free(boot_order); + return ret; +} + +/** + * tcg2_measure_smbios() - measure smbios table + * + * @dev: TPM device + * @entry: pointer to the smbios_entry structure + * + * Return: status code + */ +static efi_status_t +tcg2_measure_smbios(struct udevice *dev, + const struct smbios3_entry *entry) +{ + efi_status_t ret; + struct smbios_header *smbios_copy; + struct smbios_handoff_table_pointers2 *event = NULL; + u32 event_size; + const char smbios3_anchor[] = "_SM3_"; + + /* We only support SMBIOS 3.0 Entry Point structure */ + if (memcmp(entry->anchor, smbios3_anchor, sizeof(smbios3_anchor) - 1)) + return EFI_UNSUPPORTED; + + /* + * TCG PC Client PFP Spec says + * "SMBIOS structures that contain static configuration information + * (e.g. Platform Manufacturer Enterprise Number assigned by IANA, + * platform model number, Vendor and Device IDs for each SMBIOS table) + * that is relevant to the security of the platform MUST be measured". + * Device dependent parameters such as serial number are cleared to + * zero or spaces for the measurement. + */ + event_size = sizeof(struct smbios_handoff_table_pointers2) + + FIELD_SIZEOF(struct efi_configuration_table, guid) + + entry->table_maximum_size; + event = calloc(1, event_size); + if (!event) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + + event->table_description_size = sizeof(SMBIOS_HANDOFF_TABLE_DESC); + memcpy(event->table_description, SMBIOS_HANDOFF_TABLE_DESC, + sizeof(SMBIOS_HANDOFF_TABLE_DESC)); + put_unaligned_le64(1, &event->number_of_tables); + guidcpy(&event->table_entry[0].guid, &smbios3_guid); + smbios_copy = (struct smbios_header *)((uintptr_t)&event->table_entry[0].table); + memcpy(&event->table_entry[0].table, + (void *)((uintptr_t)entry->struct_table_address), + entry->table_maximum_size); + + smbios_prepare_measurement(entry, smbios_copy); + + ret = measure_event(dev, 1, EV_EFI_HANDOFF_TABLES2, event_size, + (u8 *)event); + if (ret != EFI_SUCCESS) + goto out; + +out: + free(event); + + return ret; +} + +/** + * tcg2_measure_gpt_table() - measure gpt table + * + * @dev: TPM device + * @loaded_image: handle to the loaded image + * + * Return: status code + */ +static efi_status_t +tcg2_measure_gpt_data(struct udevice *dev, + struct efi_loaded_image_obj *loaded_image) +{ + efi_status_t ret; + efi_handle_t handle; + struct efi_handler *dp_handler, *io_handler; + struct efi_device_path *orig_device_path; + struct efi_device_path *device_path; + struct efi_device_path *dp; + struct efi_block_io *block_io; + struct efi_gpt_data *event = NULL; + efi_guid_t null_guid = NULL_GUID; + gpt_header *gpt_h; + gpt_entry *entry = NULL; + gpt_entry *gpt_e; + u32 num_of_valid_entry = 0; + u32 event_size; + u32 i; + u32 total_gpt_entry_size; + + ret = efi_search_protocol(&loaded_image->header, + &efi_guid_loaded_image_device_path, + &dp_handler); + if (ret != EFI_SUCCESS) + return ret; + + orig_device_path = dp_handler->protocol_interface; + if (!orig_device_path) /* no device path, skip GPT measurement */ + return EFI_SUCCESS; + + device_path = efi_dp_dup(orig_device_path); + if (!device_path) + return EFI_OUT_OF_RESOURCES; + + dp = search_gpt_dp_node(device_path); + if (!dp) { + /* no GPT device path node found, skip GPT measurement */ + ret = EFI_SUCCESS; + goto out1; + } + + /* read GPT header */ + dp->type = DEVICE_PATH_TYPE_END; + dp->sub_type = DEVICE_PATH_SUB_TYPE_END; + dp = device_path; + ret = EFI_CALL(systab.boottime->locate_device_path(&efi_block_io_guid, + &dp, &handle)); + if (ret != EFI_SUCCESS) + goto out1; + + ret = efi_search_protocol(handle, &efi_block_io_guid, &io_handler); + if (ret != EFI_SUCCESS) + goto out1; + block_io = io_handler->protocol_interface; + + gpt_h = memalign(block_io->media->io_align, block_io->media->block_size); + if (!gpt_h) { + ret = EFI_OUT_OF_RESOURCES; + goto out2; + } + + ret = block_io->read_blocks(block_io, block_io->media->media_id, 1, + block_io->media->block_size, gpt_h); + if (ret != EFI_SUCCESS) + goto out2; + + /* read GPT entry */ + total_gpt_entry_size = gpt_h->num_partition_entries * + gpt_h->sizeof_partition_entry; + entry = memalign(block_io->media->io_align, total_gpt_entry_size); + if (!entry) { + ret = EFI_OUT_OF_RESOURCES; + goto out2; + } + + ret = block_io->read_blocks(block_io, block_io->media->media_id, + gpt_h->partition_entry_lba, + total_gpt_entry_size, entry); + if (ret != EFI_SUCCESS) + goto out2; + + /* count valid GPT entry */ + gpt_e = entry; + for (i = 0; i < gpt_h->num_partition_entries; i++) { + if (guidcmp(&null_guid, &gpt_e->partition_type_guid)) + num_of_valid_entry++; + + gpt_e = (gpt_entry *)((u8 *)gpt_e + gpt_h->sizeof_partition_entry); + } + + /* prepare event data for measurement */ + event_size = sizeof(struct efi_gpt_data) + + (num_of_valid_entry * gpt_h->sizeof_partition_entry); + event = calloc(1, event_size); + if (!event) { + ret = EFI_OUT_OF_RESOURCES; + goto out2; + } + memcpy(event, gpt_h, sizeof(gpt_header)); + put_unaligned_le64(num_of_valid_entry, &event->number_of_partitions); + + /* copy valid GPT entry */ + gpt_e = entry; + num_of_valid_entry = 0; + for (i = 0; i < gpt_h->num_partition_entries; i++) { + if (guidcmp(&null_guid, &gpt_e->partition_type_guid)) { + memcpy((u8 *)event->partitions + + (num_of_valid_entry * gpt_h->sizeof_partition_entry), + gpt_e, gpt_h->sizeof_partition_entry); + num_of_valid_entry++; + } + + gpt_e = (gpt_entry *)((u8 *)gpt_e + gpt_h->sizeof_partition_entry); + } + + ret = measure_event(dev, 5, EV_EFI_GPT_EVENT, event_size, (u8 *)event); + +out2: + free(gpt_h); + free(entry); + free(event); +out1: + efi_free_pool(device_path); + + return ret; +} + +/* Return the byte size of reserved map area in DTB or -1 upon error */ +static ssize_t size_of_rsvmap(void *dtb) +{ + struct fdt_reserve_entry e; + ssize_t size_max; + ssize_t size; + u8 *rsvmap_base; + + rsvmap_base = (u8 *)dtb + fdt_off_mem_rsvmap(dtb); + size_max = fdt_totalsize(dtb) - fdt_off_mem_rsvmap(dtb); + size = 0; + + do { + memcpy(&e, rsvmap_base + size, sizeof(e)); + size += sizeof(e); + if (size > size_max) + return -1; + } while (e.size); + + return size; +} + +/** + * efi_tcg2_measure_dtb() - measure DTB passed to the OS + * + * @dtb: pointer to the device tree blob + * + * Return: status code + */ +efi_status_t efi_tcg2_measure_dtb(void *dtb) +{ + struct uefi_platform_firmware_blob2 *blob; + struct fdt_header *header; + sha256_context hash_ctx; + struct udevice *dev; + ssize_t rsvmap_size; + efi_status_t ret; + u32 event_size; + + if (!is_tcg2_protocol_installed()) + return EFI_SUCCESS; + + if (tcg2_platform_get_tpm2(&dev)) + return EFI_SECURITY_VIOLATION; + + rsvmap_size = size_of_rsvmap(dtb); + if (rsvmap_size < 0) + return EFI_SECURITY_VIOLATION; + + event_size = sizeof(*blob) + sizeof(EFI_DTB_EVENT_STRING) + SHA256_SUM_LEN; + blob = calloc(1, event_size); + if (!blob) + return EFI_OUT_OF_RESOURCES; + + blob->blob_description_size = sizeof(EFI_DTB_EVENT_STRING); + memcpy(blob->data, EFI_DTB_EVENT_STRING, blob->blob_description_size); + + /* Measure populated areas of the DTB */ + header = dtb; + sha256_starts(&hash_ctx); + sha256_update(&hash_ctx, (u8 *)header, sizeof(struct fdt_header)); + sha256_update(&hash_ctx, (u8 *)dtb + fdt_off_dt_struct(dtb), fdt_size_dt_strings(dtb)); + sha256_update(&hash_ctx, (u8 *)dtb + fdt_off_dt_strings(dtb), fdt_size_dt_struct(dtb)); + sha256_update(&hash_ctx, (u8 *)dtb + fdt_off_mem_rsvmap(dtb), rsvmap_size); + sha256_finish(&hash_ctx, blob->data + blob->blob_description_size); + + ret = measure_event(dev, 1, EV_POST_CODE, event_size, (u8 *)blob); + + free(blob); + return ret; +} + +/** + * efi_tcg2_measure_efi_app_invocation() - measure efi app invocation + * + * Return: status code + */ +efi_status_t efi_tcg2_measure_efi_app_invocation(struct efi_loaded_image_obj *handle) +{ + efi_status_t ret; + u32 pcr_index; + struct udevice *dev; + u32 event = 0; + struct smbios3_entry *entry; + + if (!is_tcg2_protocol_installed()) + return EFI_SUCCESS; + + if (tcg2_efi_app_invoked) + return EFI_SUCCESS; + + if (tcg2_platform_get_tpm2(&dev)) + return EFI_SECURITY_VIOLATION; + + ret = tcg2_measure_boot_variable(dev); + if (ret != EFI_SUCCESS) + goto out; + + ret = measure_event(dev, 4, EV_EFI_ACTION, + strlen(EFI_CALLING_EFI_APPLICATION), + (u8 *)EFI_CALLING_EFI_APPLICATION); + if (ret != EFI_SUCCESS) + goto out; + + entry = efi_get_configuration_table(&smbios3_guid); + if (entry) { + ret = tcg2_measure_smbios(dev, entry); + if (ret != EFI_SUCCESS) + goto out; + } + + ret = tcg2_measure_gpt_data(dev, handle); + if (ret != EFI_SUCCESS) + goto out; + + for (pcr_index = 0; pcr_index <= 7; pcr_index++) { + ret = measure_event(dev, pcr_index, EV_SEPARATOR, + sizeof(event), (u8 *)&event); + if (ret != EFI_SUCCESS) + goto out; + } + + tcg2_efi_app_invoked = true; +out: + return ret; +} + +/** + * efi_tcg2_measure_efi_app_exit() - measure efi app exit + * + * Return: status code + */ +efi_status_t efi_tcg2_measure_efi_app_exit(void) +{ + efi_status_t ret; + struct udevice *dev; + + if (!is_tcg2_protocol_installed()) + return EFI_SUCCESS; + + if (tcg2_platform_get_tpm2(&dev)) + return EFI_SECURITY_VIOLATION; + + ret = measure_event(dev, 4, EV_EFI_ACTION, + strlen(EFI_RETURNING_FROM_EFI_APPLICATION), + (u8 *)EFI_RETURNING_FROM_EFI_APPLICATION); + return ret; +} + +/** + * efi_tcg2_notify_exit_boot_services() - ExitBootService callback + * + * @event: callback event + * @context: callback context + */ +static void EFIAPI +efi_tcg2_notify_exit_boot_services(struct efi_event *event, void *context) +{ + efi_status_t ret; + struct udevice *dev; + + EFI_ENTRY("%p, %p", event, context); + + event_log.ebs_called = true; + + if (!is_tcg2_protocol_installed()) { + ret = EFI_SUCCESS; + goto out; + } + + if (tcg2_platform_get_tpm2(&dev)) { + ret = EFI_SECURITY_VIOLATION; + goto out; + } + + ret = measure_event(dev, 5, EV_EFI_ACTION, + strlen(EFI_EXIT_BOOT_SERVICES_INVOCATION), + (u8 *)EFI_EXIT_BOOT_SERVICES_INVOCATION); + if (ret != EFI_SUCCESS) + goto out; + + ret = measure_event(dev, 5, EV_EFI_ACTION, + strlen(EFI_EXIT_BOOT_SERVICES_SUCCEEDED), + (u8 *)EFI_EXIT_BOOT_SERVICES_SUCCEEDED); + +out: + EFI_EXIT(ret); +} + +/** + * efi_tcg2_notify_exit_boot_services_failed() + * - notify ExitBootServices() is failed + * + * Return: status code + */ +efi_status_t efi_tcg2_notify_exit_boot_services_failed(void) +{ + struct udevice *dev; + efi_status_t ret; + + if (!is_tcg2_protocol_installed()) + return EFI_SUCCESS; + + if (tcg2_platform_get_tpm2(&dev)) + return EFI_SECURITY_VIOLATION; + + ret = measure_event(dev, 5, EV_EFI_ACTION, + strlen(EFI_EXIT_BOOT_SERVICES_INVOCATION), + (u8 *)EFI_EXIT_BOOT_SERVICES_INVOCATION); + if (ret != EFI_SUCCESS) + goto out; + + ret = measure_event(dev, 5, EV_EFI_ACTION, + strlen(EFI_EXIT_BOOT_SERVICES_FAILED), + (u8 *)EFI_EXIT_BOOT_SERVICES_FAILED); + +out: + return ret; +} + +/** + * tcg2_measure_secure_boot_variable() - measure secure boot variables + * + * @dev: TPM device + * + * Return: status code + */ +static efi_status_t tcg2_measure_secure_boot_variable(struct udevice *dev) +{ + u8 *data; + efi_uintn_t data_size; + u32 count, i; + efi_status_t ret; + u8 deployed_mode; + efi_uintn_t size; + u32 deployed_audit_pcr_index = 1; + + size = sizeof(deployed_mode); + ret = efi_get_variable_int(u"DeployedMode", &efi_global_variable_guid, + NULL, &size, &deployed_mode, NULL); + if (ret != EFI_SUCCESS || !deployed_mode) + deployed_audit_pcr_index = 7; + + count = ARRAY_SIZE(secure_variables); + for (i = 0; i < count; i++) { + const efi_guid_t *guid; + + guid = efi_auth_var_get_guid(secure_variables[i].name); + + data = efi_get_var(secure_variables[i].name, guid, &data_size); + if (!data && !secure_variables[i].accept_empty) + continue; + + if (u16_strcmp(u"DeployedMode", secure_variables[i].name)) + secure_variables[i].pcr_index = deployed_audit_pcr_index; + if (u16_strcmp(u"AuditMode", secure_variables[i].name)) + secure_variables[i].pcr_index = deployed_audit_pcr_index; + + ret = tcg2_measure_variable(dev, secure_variables[i].pcr_index, + EV_EFI_VARIABLE_DRIVER_CONFIG, + secure_variables[i].name, guid, + data_size, data); + free(data); + if (ret != EFI_SUCCESS) + goto error; + } + +error: + return ret; +} + +/** + * efi_tcg2_do_initial_measurement() - do initial measurement + * + * Return: status code + */ +efi_status_t efi_tcg2_do_initial_measurement(void) +{ + efi_status_t ret; + struct udevice *dev; + + if (!is_tcg2_protocol_installed()) + return EFI_SUCCESS; + + if (tcg2_platform_get_tpm2(&dev)) + return EFI_SECURITY_VIOLATION; + + ret = tcg2_measure_secure_boot_variable(dev); + if (ret != EFI_SUCCESS) + goto out; + +out: + return ret; +} + +/** + * efi_tcg2_register() - register EFI_TCG2_PROTOCOL + * + * If a TPM2 device is available, the TPM TCG2 Protocol is registered + * + * Return: status code + */ +efi_status_t efi_tcg2_register(void) +{ + efi_status_t ret = EFI_SUCCESS; + struct udevice *dev; + struct efi_event *event; + u32 err; + + if (tcg2_platform_get_tpm2(&dev)) { + log_warning("Missing TPMv2 device for EFI_TCG_PROTOCOL\n"); + return EFI_SUCCESS; + } + + /* initialize the TPM as early as possible. */ + err = tpm_auto_start(dev); + if (err) { + ret = EFI_DEVICE_ERROR; + log_err("TPM startup failed\n"); + goto fail; + } + + ret = efi_init_event_log(); + if (ret != EFI_SUCCESS) { + tcg2_uninit(); + goto fail; + } + + ret = efi_install_multiple_protocol_interfaces(&efi_root, &efi_guid_tcg2_protocol, + &efi_tcg2_protocol, NULL); + if (ret != EFI_SUCCESS) { + tcg2_uninit(); + goto fail; + } + + ret = efi_create_event(EVT_SIGNAL_EXIT_BOOT_SERVICES, TPL_CALLBACK, + efi_tcg2_notify_exit_boot_services, NULL, + NULL, &event); + if (ret != EFI_SUCCESS) { + tcg2_uninit(); + goto fail; + } + + return ret; + +fail: + log_err("Cannot install EFI_TCG2_PROTOCOL\n"); + return ret; +} diff --git a/lib/efi_loader/efi_unicode_collation.c b/lib/efi_loader/efi_unicode_collation.c new file mode 100644 index 00000000000..df2a988ee39 --- /dev/null +++ b/lib/efi_loader/efi_unicode_collation.c @@ -0,0 +1,327 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI Unicode collation protocol + * + * Copyright (c) 2018 Heinrich Schuchardt <xypron.glpk@gmx.de> + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <charset.h> +#include <cp1250.h> +#include <cp437.h> +#include <efi_loader.h> + +/* Characters that may not be used in FAT 8.3 file names */ +static const char illegal[] = "+,<=>:;\"/\\|?*[]\x7f"; + +/* + * EDK2 assumes codepage 1250 when creating FAT 8.3 file names. + * Linux defaults to codepage 437 for FAT 8.3 file names. + */ +#if CONFIG_FAT_DEFAULT_CODEPAGE == 1250 +/* Unicode code points for code page 1250 characters 0x80 - 0xff */ +static const u16 codepage[] = CP1250; +#else +/* Unicode code points for code page 437 characters 0x80 - 0xff */ +static const u16 *codepage = codepage_437; +#endif + +/* GUID of the EFI_UNICODE_COLLATION_PROTOCOL2 */ +const efi_guid_t efi_guid_unicode_collation_protocol2 = + EFI_UNICODE_COLLATION_PROTOCOL2_GUID; + +/** + * efi_stri_coll() - compare utf-16 strings case-insenitively + * + * @this: unicode collation protocol instance + * @s1: first string + * @s2: second string + * + * This function implements the StriColl() service of the + * EFI_UNICODE_COLLATION_PROTOCOL2. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: 0: s1 == s2, > 0: s1 > s2, < 0: s1 < s2 + */ +static efi_intn_t EFIAPI efi_stri_coll( + struct efi_unicode_collation_protocol *this, u16 *s1, u16 *s2) +{ + s32 c1, c2; + efi_intn_t ret = 0; + + EFI_ENTRY("%p, %ls, %ls", this, s1, s2); + for (; *s1 | *s2; ++s1, ++s2) { + c1 = utf_to_upper(*s1); + c2 = utf_to_upper(*s2); + if (c1 < c2) { + ret = -1; + goto out; + } else if (c1 > c2) { + ret = 1; + goto out; + } + } +out: + EFI_EXIT(EFI_SUCCESS); + return ret; +} + +/** + * next_lower() - get next codepoint converted to lower case + * + * @string: pointer to u16 string, on return advanced by one codepoint + * Return: first codepoint of string converted to lower case + */ +static s32 next_lower(const u16 **string) +{ + return utf_to_lower(utf16_get(string)); +} + +/** + * metai_match() - compare utf-16 string with a pattern string case-insenitively + * + * @string: string to compare + * @pattern: pattern string + * + * The pattern string may use these: + * - * matches >= 0 characters + * - ? matches 1 character + * - [<char1><char2>...<charN>] match any character in the set + * - [<char1>-<char2>] matches any character in the range + * + * This function is called my efi_metai_match(). + * + * For '*' pattern searches this function calls itself recursively. + * Performance-wise this is suboptimal, especially for multiple '*' wildcards. + * But it results in simple code. + * + * Return: true if the string is matched. + */ +static bool metai_match(const u16 *string, const u16 *pattern) +{ + s32 first, s, p; + + for (; *string && *pattern;) { + const u16 *string_old = string; + + s = next_lower(&string); + p = next_lower(&pattern); + + switch (p) { + case '*': + /* Match 0 or more characters */ + for (;; s = next_lower(&string)) { + if (metai_match(string_old, pattern)) + return true; + if (!s) + return false; + string_old = string; + } + case '?': + /* Match any one character */ + break; + case '[': + /* Match any character in the set */ + p = next_lower(&pattern); + first = p; + if (first == ']') + /* Empty set */ + return false; + p = next_lower(&pattern); + if (p == '-') { + /* Range */ + p = next_lower(&pattern); + if (s < first || s > p) + return false; + p = next_lower(&pattern); + if (p != ']') + return false; + } else { + /* Set */ + bool hit = false; + + if (s == first) + hit = true; + for (; p && p != ']'; + p = next_lower(&pattern)) { + if (p == s) + hit = true; + } + if (!hit || p != ']') + return false; + } + break; + default: + /* Match one character */ + if (p != s) + return false; + } + } + if (!*pattern && !*string) + return true; + return false; +} + +/** + * efi_metai_match() - compare utf-16 string with a pattern string + * case-insenitively + * + * @this: unicode collation protocol instance + * @string: string to compare + * @pattern: pattern string + * + * The pattern string may use these: + * - * matches >= 0 characters + * - ? matches 1 character + * - [<char1><char2>...<charN>] match any character in the set + * - [<char1>-<char2>] matches any character in the range + * + * This function implements the MetaMatch() service of the + * EFI_UNICODE_COLLATION_PROTOCOL2. + * + * Return: true if the string is matched. + */ +static bool EFIAPI efi_metai_match(struct efi_unicode_collation_protocol *this, + const u16 *string, const u16 *pattern) +{ + bool ret; + + EFI_ENTRY("%p, %ls, %ls", this, string, pattern); + ret = metai_match(string, pattern); + EFI_EXIT(EFI_SUCCESS); + return ret; +} + +/** + * efi_str_lwr() - convert to lower case + * + * @this: unicode collation protocol instance + * @string: string to convert + * + * The conversion is done in place. As long as upper and lower letters use the + * same number of words this does not pose a problem. + * + * This function implements the StrLwr() service of the + * EFI_UNICODE_COLLATION_PROTOCOL2. + */ +static void EFIAPI efi_str_lwr(struct efi_unicode_collation_protocol *this, + u16 *string) +{ + EFI_ENTRY("%p, %ls", this, string); + for (; *string; ++string) + *string = utf_to_lower(*string); + EFI_EXIT(EFI_SUCCESS); +} + +/** + * efi_str_upr() - convert to upper case + * + * @this: unicode collation protocol instance + * @string: string to convert + * + * The conversion is done in place. As long as upper and lower letters use the + * same number of words this does not pose a problem. + * + * This function implements the StrUpr() service of the + * EFI_UNICODE_COLLATION_PROTOCOL2. + */ +static void EFIAPI efi_str_upr(struct efi_unicode_collation_protocol *this, + u16 *string) +{ + EFI_ENTRY("%p, %ls", this, string); + for (; *string; ++string) + *string = utf_to_upper(*string); + EFI_EXIT(EFI_SUCCESS); +} + +/** + * efi_fat_to_str() - convert an 8.3 file name from an OEM codepage to Unicode + * + * @this: unicode collation protocol instance + * @fat_size: size of the string to convert + * @fat: string to convert + * @string: converted string + * + * This function implements the FatToStr() service of the + * EFI_UNICODE_COLLATION_PROTOCOL2. + */ +static void EFIAPI efi_fat_to_str(struct efi_unicode_collation_protocol *this, + efi_uintn_t fat_size, char *fat, u16 *string) +{ + efi_uintn_t i; + u16 c; + + EFI_ENTRY("%p, %zu, %s, %p", this, fat_size, fat, string); + for (i = 0; i < fat_size; ++i) { + c = (unsigned char)fat[i]; + if (c > 0x80) + c = codepage[c - 0x60]; + string[i] = c; + if (!c) + break; + } + string[i] = 0; + EFI_EXIT(EFI_SUCCESS); +} + +/** + * efi_str_to_fat() - convert a utf-16 string to legal characters for a FAT + * file name in an OEM code page + * + * @this: unicode collation protocol instance + * @string: Unicode string to convert + * @fat_size: size of the target buffer + * @fat: converted string + * + * This function implements the StrToFat() service of the + * EFI_UNICODE_COLLATION_PROTOCOL2. + * + * Return: true if an illegal character was substituted by '_'. + */ +static bool EFIAPI efi_str_to_fat(struct efi_unicode_collation_protocol *this, + const u16 *string, efi_uintn_t fat_size, + char *fat) +{ + efi_uintn_t i; + s32 c; + bool ret = false; + + EFI_ENTRY("%p, %ls, %zu, %p", this, string, fat_size, fat); + for (i = 0; i < fat_size;) { + c = utf16_get(&string); + switch (c) { + /* Ignore period and space */ + case '.': + case ' ': + continue; + case 0: + break; + } + c = utf_to_upper(c); + if (utf_to_cp(&c, codepage) || + (c && (c < 0x20 || strchr(illegal, c)))) { + ret = true; + c = '_'; + } + + fat[i] = c; + if (!c) + break; + ++i; + } + EFI_EXIT(EFI_SUCCESS); + return ret; +} + +const struct efi_unicode_collation_protocol efi_unicode_collation_protocol2 = { + .stri_coll = efi_stri_coll, + .metai_match = efi_metai_match, + .str_lwr = efi_str_lwr, + .str_upr = efi_str_upr, + .fat_to_str = efi_fat_to_str, + .str_to_fat = efi_str_to_fat, + .supported_languages = "en", +}; diff --git a/lib/efi_loader/efi_var_common.c b/lib/efi_loader/efi_var_common.c new file mode 100644 index 00000000000..4b34a58b4cf --- /dev/null +++ b/lib/efi_loader/efi_var_common.c @@ -0,0 +1,490 @@ +/* + * UEFI runtime variable services + * + * Copyright (c) 2020, Heinrich Schuchardt <xypron.glpk@gmx.de> + * Copyright (c) 2020 Linaro Limited, Author: AKASHI Takahiro + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <efi_loader.h> +#include <efi_variable.h> +#include <stdlib.h> +#include <u-boot/crc.h> + +enum efi_secure_mode { + EFI_MODE_SETUP, + EFI_MODE_USER, + EFI_MODE_AUDIT, + EFI_MODE_DEPLOYED, +}; + +struct efi_auth_var_name_type { + const u16 *name; + const efi_guid_t *guid; + const enum efi_auth_var_type type; +}; + +const efi_guid_t efi_guid_image_security_database = + EFI_IMAGE_SECURITY_DATABASE_GUID; + +static const struct efi_auth_var_name_type name_type[] = { + {u"PK", &efi_global_variable_guid, EFI_AUTH_VAR_PK}, + {u"KEK", &efi_global_variable_guid, EFI_AUTH_VAR_KEK}, + {u"db", &efi_guid_image_security_database, EFI_AUTH_VAR_DB}, + {u"dbx", &efi_guid_image_security_database, EFI_AUTH_VAR_DBX}, + {u"dbt", &efi_guid_image_security_database, EFI_AUTH_VAR_DBT}, + {u"dbr", &efi_guid_image_security_database, EFI_AUTH_VAR_DBR}, + {u"AuditMode", &efi_global_variable_guid, EFI_AUTH_MODE}, + {u"DeployedMode", &efi_global_variable_guid, EFI_AUTH_MODE}, +}; + +static bool efi_secure_boot; +static enum efi_secure_mode efi_secure_mode; + +/** + * efi_efi_get_variable() - retrieve value of a UEFI variable + * + * This function implements the GetVariable runtime service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @variable_name: name of the variable + * @vendor: vendor GUID + * @attributes: attributes of the variable + * @data_size: size of the buffer to which the variable value is copied + * @data: buffer to which the variable value is copied + * Return: status code + */ +efi_status_t EFIAPI efi_get_variable(u16 *variable_name, + const efi_guid_t *vendor, u32 *attributes, + efi_uintn_t *data_size, void *data) +{ + efi_status_t ret; + + EFI_ENTRY("\"%ls\" %pUs %p %p %p", variable_name, vendor, attributes, + data_size, data); + + ret = efi_get_variable_int(variable_name, vendor, attributes, + data_size, data, NULL); + + /* Remove EFI_VARIABLE_READ_ONLY flag */ + if (attributes) + *attributes &= EFI_VARIABLE_MASK; + + return EFI_EXIT(ret); +} + +/** + * efi_set_variable() - set value of a UEFI variable + * + * This function implements the SetVariable runtime service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @variable_name: name of the variable + * @vendor: vendor GUID + * @attributes: attributes of the variable + * @data_size: size of the buffer with the variable value + * @data: buffer with the variable value + * Return: status code + */ +efi_status_t EFIAPI efi_set_variable(u16 *variable_name, + const efi_guid_t *vendor, u32 attributes, + efi_uintn_t data_size, const void *data) +{ + efi_status_t ret; + + EFI_ENTRY("\"%ls\" %pUs %x %zu %p", variable_name, vendor, attributes, + data_size, data); + + /* Make sure that the EFI_VARIABLE_READ_ONLY flag is not set */ + if (attributes & ~EFI_VARIABLE_MASK) + ret = EFI_INVALID_PARAMETER; + else + ret = efi_set_variable_int(variable_name, vendor, attributes, + data_size, data, true); + + return EFI_EXIT(ret); +} + +/** + * efi_get_next_variable_name() - enumerate the current variable names + * + * @variable_name_size: size of variable_name buffer in byte + * @variable_name: name of uefi variable's name in u16 + * @vendor: vendor's guid + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +efi_status_t EFIAPI efi_get_next_variable_name(efi_uintn_t *variable_name_size, + u16 *variable_name, + efi_guid_t *vendor) +{ + efi_status_t ret; + + EFI_ENTRY("%p \"%ls\" %pUs", variable_name_size, variable_name, vendor); + + ret = efi_get_next_variable_name_int(variable_name_size, variable_name, + vendor); + + return EFI_EXIT(ret); +} + +/** + * efi_query_variable_info() - get information about EFI variables + * + * This function implements the QueryVariableInfo() runtime service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @attributes: bitmask to select variables to be + * queried + * @maximum_variable_storage_size: maximum size of storage area for the + * selected variable types + * @remaining_variable_storage_size: remaining size of storage are for the + * selected variable types + * @maximum_variable_size: maximum size of a variable of the + * selected type + * Returns: status code + */ +efi_status_t EFIAPI efi_query_variable_info( + u32 attributes, u64 *maximum_variable_storage_size, + u64 *remaining_variable_storage_size, + u64 *maximum_variable_size) +{ + efi_status_t ret; + + EFI_ENTRY("%x %p %p %p", attributes, maximum_variable_storage_size, + remaining_variable_storage_size, maximum_variable_size); + + ret = efi_query_variable_info_int(attributes, + maximum_variable_storage_size, + remaining_variable_storage_size, + maximum_variable_size); + + return EFI_EXIT(ret); +} + +efi_status_t __efi_runtime EFIAPI +efi_get_variable_runtime(u16 *variable_name, const efi_guid_t *guid, + u32 *attributes, efi_uintn_t *data_size, void *data) +{ + efi_status_t ret; + + ret = efi_get_variable_mem(variable_name, guid, attributes, data_size, + data, NULL, EFI_VARIABLE_RUNTIME_ACCESS); + + /* Remove EFI_VARIABLE_READ_ONLY flag */ + if (attributes) + *attributes &= EFI_VARIABLE_MASK; + + return ret; +} + +efi_status_t __efi_runtime EFIAPI +efi_get_next_variable_name_runtime(efi_uintn_t *variable_name_size, + u16 *variable_name, efi_guid_t *guid) +{ + return efi_get_next_variable_name_mem(variable_name_size, variable_name, + guid, EFI_VARIABLE_RUNTIME_ACCESS); +} + +/** + * efi_set_secure_state - modify secure boot state variables + * @secure_boot: value of SecureBoot + * @setup_mode: value of SetupMode + * @audit_mode: value of AuditMode + * @deployed_mode: value of DeployedMode + * + * Modify secure boot status related variables as indicated. + * + * Return: status code + */ +static efi_status_t efi_set_secure_state(u8 secure_boot, u8 setup_mode, + u8 audit_mode, u8 deployed_mode) +{ + efi_status_t ret; + const u32 attributes_ro = EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS | + EFI_VARIABLE_READ_ONLY; + const u32 attributes_rw = EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS; + + efi_secure_boot = secure_boot; + + ret = efi_set_variable_int(u"SecureBoot", &efi_global_variable_guid, + attributes_ro, sizeof(secure_boot), + &secure_boot, false); + if (ret != EFI_SUCCESS) + goto err; + + ret = efi_set_variable_int(u"SetupMode", &efi_global_variable_guid, + attributes_ro, sizeof(setup_mode), + &setup_mode, false); + if (ret != EFI_SUCCESS) + goto err; + + ret = efi_set_variable_int(u"AuditMode", &efi_global_variable_guid, + audit_mode || setup_mode ? + attributes_ro : attributes_rw, + sizeof(audit_mode), &audit_mode, false); + if (ret != EFI_SUCCESS) + goto err; + + ret = efi_set_variable_int(u"DeployedMode", + &efi_global_variable_guid, + audit_mode || deployed_mode || setup_mode ? + attributes_ro : attributes_rw, + sizeof(deployed_mode), &deployed_mode, + false); +err: + return ret; +} + +/** + * efi_transfer_secure_state - handle a secure boot state transition + * @mode: new state + * + * Depending on @mode, secure boot related variables are updated. + * Those variables are *read-only* for users, efi_set_variable_int() + * is called here. + * + * Return: status code + */ +static efi_status_t efi_transfer_secure_state(enum efi_secure_mode mode) +{ + efi_status_t ret; + + EFI_PRINT("Switching secure state from %d to %d\n", efi_secure_mode, + mode); + + if (mode == EFI_MODE_DEPLOYED) { + ret = efi_set_secure_state(1, 0, 0, 1); + if (ret != EFI_SUCCESS) + goto err; + } else if (mode == EFI_MODE_AUDIT) { + ret = efi_set_variable_int(u"PK", &efi_global_variable_guid, + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS, + 0, NULL, false); + if (ret != EFI_SUCCESS) + goto err; + + ret = efi_set_secure_state(0, 1, 1, 0); + if (ret != EFI_SUCCESS) + goto err; + } else if (mode == EFI_MODE_USER) { + ret = efi_set_secure_state(1, 0, 0, 0); + if (ret != EFI_SUCCESS) + goto err; + } else if (mode == EFI_MODE_SETUP) { + ret = efi_set_secure_state(0, 1, 0, 0); + if (ret != EFI_SUCCESS) + goto err; + } else { + return EFI_INVALID_PARAMETER; + } + + efi_secure_mode = mode; + + return EFI_SUCCESS; + +err: + /* TODO: What action should be taken here? */ + printf("ERROR: Secure state transition failed\n"); + return ret; +} + +efi_status_t efi_init_secure_state(void) +{ + enum efi_secure_mode mode; + u8 efi_vendor_keys = 0; + efi_uintn_t size; + efi_status_t ret; + u8 deployed_mode = 0; + u8 audit_mode = 0; + u8 setup_mode = 1; + + if (IS_ENABLED(CONFIG_EFI_SECURE_BOOT)) { + size = sizeof(deployed_mode); + ret = efi_get_variable_int(u"DeployedMode", &efi_global_variable_guid, + NULL, &size, &deployed_mode, NULL); + size = sizeof(audit_mode); + ret = efi_get_variable_int(u"AuditMode", &efi_global_variable_guid, + NULL, &size, &audit_mode, NULL); + size = 0; + ret = efi_get_variable_int(u"PK", &efi_global_variable_guid, + NULL, &size, NULL, NULL); + if (ret == EFI_BUFFER_TOO_SMALL) { + setup_mode = 0; + audit_mode = 0; + } else { + setup_mode = 1; + deployed_mode = 0; + } + } + if (deployed_mode) + mode = EFI_MODE_DEPLOYED; + else if (audit_mode) + mode = EFI_MODE_AUDIT; + else if (setup_mode) + mode = EFI_MODE_SETUP; + else + mode = EFI_MODE_USER; + + ret = efi_transfer_secure_state(mode); + if (ret != EFI_SUCCESS) + return ret; + + /* As we do not provide vendor keys this variable is always 0. */ + ret = efi_set_variable_int(u"VendorKeys", + &efi_global_variable_guid, + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS | + EFI_VARIABLE_READ_ONLY, + sizeof(efi_vendor_keys), + &efi_vendor_keys, false); + return ret; +} + +/** + * efi_secure_boot_enabled - return if secure boot is enabled or not + * + * Return: true if enabled, false if disabled + */ +bool efi_secure_boot_enabled(void) +{ + return efi_secure_boot; +} + +enum efi_auth_var_type efi_auth_var_get_type(const u16 *name, + const efi_guid_t *guid) +{ + for (size_t i = 0; i < ARRAY_SIZE(name_type); ++i) { + if (!u16_strcmp(name, name_type[i].name) && + !guidcmp(guid, name_type[i].guid)) + return name_type[i].type; + } + return EFI_AUTH_VAR_NONE; +} + +const efi_guid_t *efi_auth_var_get_guid(const u16 *name) +{ + for (size_t i = 0; i < ARRAY_SIZE(name_type); ++i) { + if (!u16_strcmp(name, name_type[i].name)) + return name_type[i].guid; + } + return &efi_global_variable_guid; +} + +/** + * efi_get_var() - read value of an EFI variable + * + * @name: variable name + * @start: vendor GUID + * @size: size of allocated buffer + * + * Return: buffer with variable data or NULL + */ +void *efi_get_var(const u16 *name, const efi_guid_t *vendor, efi_uintn_t *size) +{ + efi_status_t ret; + void *buf = NULL; + + *size = 0; + ret = efi_get_variable_int(name, vendor, NULL, size, buf, NULL); + if (ret == EFI_BUFFER_TOO_SMALL) { + buf = malloc(*size); + if (!buf) + return NULL; + ret = efi_get_variable_int(name, vendor, NULL, size, buf, NULL); + } + + if (ret != EFI_SUCCESS) { + free(buf); + *size = 0; + return NULL; + } + + return buf; +} + +/** + * efi_var_collect() - Copy EFI variables matching attributes mask + * + * @bufp: buffer containing variable collection + * @lenp: buffer length + * @attr_mask: mask of matched attributes + * + * Return: Status code + */ +efi_status_t __maybe_unused efi_var_collect(struct efi_var_file **bufp, loff_t *lenp, + u32 check_attr_mask) +{ + size_t len = EFI_VAR_BUF_SIZE; + struct efi_var_file *buf; + struct efi_var_entry *var, *old_var; + size_t old_var_name_length = 2; + + *bufp = NULL; /* Avoid double free() */ + buf = calloc(1, len); + if (!buf) + return EFI_OUT_OF_RESOURCES; + var = buf->var; + old_var = var; + for (;;) { + efi_uintn_t data_length, var_name_length; + u8 *data; + efi_status_t ret; + + if ((uintptr_t)buf + len <= + (uintptr_t)var->name + old_var_name_length) + return EFI_BUFFER_TOO_SMALL; + + var_name_length = (uintptr_t)buf + len - (uintptr_t)var->name; + memcpy(var->name, old_var->name, old_var_name_length); + guidcpy(&var->guid, &old_var->guid); + ret = efi_get_next_variable_name_int( + &var_name_length, var->name, &var->guid); + if (ret == EFI_NOT_FOUND) + break; + if (ret != EFI_SUCCESS) { + free(buf); + return ret; + } + old_var_name_length = var_name_length; + old_var = var; + + data = (u8 *)var->name + old_var_name_length; + data_length = (uintptr_t)buf + len - (uintptr_t)data; + ret = efi_get_variable_int(var->name, &var->guid, + &var->attr, &data_length, data, + &var->time); + if (ret != EFI_SUCCESS) { + free(buf); + return ret; + } + if ((var->attr & check_attr_mask) == check_attr_mask) { + var->length = data_length; + var = (struct efi_var_entry *)ALIGN((uintptr_t)data + data_length, 8); + } + } + + buf->reserved = 0; + buf->magic = EFI_VAR_FILE_MAGIC; + len = (uintptr_t)var - (uintptr_t)buf; + buf->crc32 = crc32(0, (u8 *)buf->var, + len - sizeof(struct efi_var_file)); + buf->length = len; + *bufp = buf; + *lenp = len; + + return EFI_SUCCESS; +} diff --git a/lib/efi_loader/efi_var_file.c b/lib/efi_loader/efi_var_file.c new file mode 100644 index 00000000000..ba0bf33ffbd --- /dev/null +++ b/lib/efi_loader/efi_var_file.c @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * File interface for UEFI variables + * + * Copyright (c) 2020, Heinrich Schuchardt + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <charset.h> +#include <fs.h> +#include <log.h> +#include <malloc.h> +#include <mapmem.h> +#include <efi_loader.h> +#include <efi_variable.h> +#include <u-boot/crc.h> + +#define PART_STR_LEN 10 + +/* GUID used by Shim to store the MOK database */ +#define SHIM_LOCK_GUID \ + EFI_GUID(0x605dab50, 0xe046, 0x4300, \ + 0xab, 0xb6, 0x3d, 0xd8, 0x10, 0xdd, 0x8b, 0x23) + +static const efi_guid_t shim_lock_guid = SHIM_LOCK_GUID; + +/** + * efi_set_blk_dev_to_system_partition() - select EFI system partition + * + * Set the EFI system partition as current block device. + * + * Return: status code + */ +static efi_status_t __maybe_unused efi_set_blk_dev_to_system_partition(void) +{ + char part_str[PART_STR_LEN]; + int r; + + if (efi_system_partition.uclass_id == UCLASS_INVALID) + return EFI_DEVICE_ERROR; + + snprintf(part_str, PART_STR_LEN, "%x:%x", + efi_system_partition.devnum, efi_system_partition.part); + r = fs_set_blk_dev(blk_get_uclass_name(efi_system_partition.uclass_id), + part_str, FS_TYPE_ANY); + if (r) + return EFI_DEVICE_ERROR; + + return EFI_SUCCESS; +} + +/** + * efi_var_to_file() - save non-volatile variables as file + * + * File ubootefi.var is created on the EFI system partion. + * + * Return: status code + */ +efi_status_t efi_var_to_file(void) +{ +#ifdef CONFIG_EFI_VARIABLE_FILE_STORE + efi_status_t ret; + struct efi_var_file *buf; + loff_t len; + loff_t actlen; + int r; + static bool once; + + ret = efi_var_collect(&buf, &len, EFI_VARIABLE_NON_VOLATILE); + if (ret != EFI_SUCCESS) + goto error; + + ret = efi_set_blk_dev_to_system_partition(); + if (ret != EFI_SUCCESS) { + if (!once) { + log_warning("Cannot persist EFI variables without system partition\n"); + once = true; + } + goto out; + } + once = false; + + r = fs_write(EFI_VAR_FILE_NAME, map_to_sysmem(buf), 0, len, &actlen); + if (r || len != actlen) + ret = EFI_DEVICE_ERROR; + +error: + if (ret != EFI_SUCCESS) + log_err("Failed to persist EFI variables\n"); +out: + free(buf); + return ret; +#else + return EFI_SUCCESS; +#endif +} + +efi_status_t efi_var_restore(struct efi_var_file *buf, bool safe) +{ + struct efi_var_entry *var, *last_var; + u16 *data; + efi_status_t ret; + + if (buf->reserved || buf->magic != EFI_VAR_FILE_MAGIC || + buf->crc32 != crc32(0, (u8 *)buf->var, + buf->length - sizeof(struct efi_var_file))) { + log_err("Invalid EFI variables file\n"); + return EFI_INVALID_PARAMETER; + } + + last_var = (struct efi_var_entry *)((u8 *)buf + buf->length); + for (var = buf->var; var < last_var; + var = (struct efi_var_entry *) + ALIGN((uintptr_t)data + var->length, 8)) { + + data = var->name + u16_strlen(var->name) + 1; + + /* + * Secure boot related and volatile variables shall only be + * restored from U-Boot's preseed. + */ + if (!safe && + (efi_auth_var_get_type(var->name, &var->guid) != + EFI_AUTH_VAR_NONE || + !guidcmp(&var->guid, &shim_lock_guid) || + !(var->attr & EFI_VARIABLE_NON_VOLATILE))) + continue; + if (!var->length) + continue; + if (efi_var_mem_find(&var->guid, var->name, NULL)) + continue; + ret = efi_var_mem_ins(var->name, &var->guid, var->attr, + var->length, data, 0, NULL, + var->time); + if (ret != EFI_SUCCESS) + log_err("Failed to set EFI variable %ls\n", var->name); + } + return EFI_SUCCESS; +} + +/** + * efi_var_from_file() - read variables from file + * + * File ubootefi.var is read from the EFI system partitions and the variables + * stored in the file are created. + * + * On first boot the file ubootefi.var does not exist yet. This is why we must + * return EFI_SUCCESS in this case. + * + * If the variable file is corrupted, e.g. incorrect CRC32, we do not want to + * stop the boot process. We deliberately return EFI_SUCCESS in this case, too. + * + * Return: status code + */ +efi_status_t efi_var_from_file(void) +{ +#ifdef CONFIG_EFI_VARIABLE_FILE_STORE + struct efi_var_file *buf; + loff_t len; + efi_status_t ret; + int r; + + buf = calloc(1, EFI_VAR_BUF_SIZE); + if (!buf) { + log_err("Out of memory\n"); + return EFI_OUT_OF_RESOURCES; + } + + ret = efi_set_blk_dev_to_system_partition(); + if (ret != EFI_SUCCESS) + goto error; + r = fs_read(EFI_VAR_FILE_NAME, map_to_sysmem(buf), 0, EFI_VAR_BUF_SIZE, + &len); + if (r || len < sizeof(struct efi_var_file)) { + log_err("Failed to load EFI variables\n"); + goto error; + } + if (buf->length != len || efi_var_restore(buf, false) != EFI_SUCCESS) + log_err("Invalid EFI variables file\n"); +error: + free(buf); +#endif + return EFI_SUCCESS; +} diff --git a/lib/efi_loader/efi_var_mem.c b/lib/efi_loader/efi_var_mem.c new file mode 100644 index 00000000000..31180df9e3a --- /dev/null +++ b/lib/efi_loader/efi_var_mem.c @@ -0,0 +1,402 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * File interface for UEFI variables + * + * Copyright (c) 2020, Heinrich Schuchardt + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <efi_loader.h> +#include <efi_variable.h> +#include <u-boot/crc.h> + +/* + * The variables efi_var_file and efi_var_entry must be static to avoid + * referencing them via the global offset table (section .got). The GOT + * is neither mapped as EfiRuntimeServicesData nor do we support its + * relocation during SetVirtualAddressMap(). + */ +static struct efi_var_file __efi_runtime_data *efi_var_buf; +static struct efi_var_entry __efi_runtime_data *efi_current_var; +static const u16 __efi_runtime_rodata vtf[] = u"VarToFile"; + +/** + * efi_var_mem_compare() - compare GUID and name with a variable + * + * @var: variable to compare + * @guid: GUID to compare + * @name: variable name to compare + * @next: pointer to next variable + * Return: true if match + */ +static bool __efi_runtime +efi_var_mem_compare(struct efi_var_entry *var, const efi_guid_t *guid, + const u16 *name, struct efi_var_entry **next) +{ + int i; + u8 *guid1, *guid2; + const u16 *data, *var_name; + bool match = true; + + for (guid1 = (u8 *)&var->guid, guid2 = (u8 *)guid, i = 0; + i < sizeof(efi_guid_t) && match; ++i) + match = (guid1[i] == guid2[i]); + + for (data = var->name, var_name = name;; ++data) { + if (match) + match = (*data == *var_name); + if (!*data) + break; + if (*var_name) + ++var_name; + } + + ++data; + + if (next) + *next = (struct efi_var_entry *) + ALIGN((uintptr_t)data + var->length, 8); + + if (match) + efi_current_var = var; + + return match; +} + +/** + * efi_var_entry_len() - Get the entry len including headers & name + * + * @var: pointer to variable start + * + * Return: 8-byte aligned variable entry length + */ + +u32 __efi_runtime efi_var_entry_len(struct efi_var_entry *var) +{ + if (!var) + return 0; + + return ALIGN((sizeof(u16) * (u16_strlen(var->name) + 1)) + + var->length + sizeof(*var), 8); +} + +struct efi_var_entry __efi_runtime +*efi_var_mem_find(const efi_guid_t *guid, const u16 *name, + struct efi_var_entry **next) +{ + struct efi_var_entry *var, *last; + + last = (struct efi_var_entry *) + ((uintptr_t)efi_var_buf + efi_var_buf->length); + + if (!*name) { + if (next) { + *next = efi_var_buf->var; + if (*next >= last) + *next = NULL; + } + return NULL; + } + if (efi_current_var && + efi_var_mem_compare(efi_current_var, guid, name, next)) { + if (next && *next >= last) + *next = NULL; + return efi_current_var; + } + + var = efi_var_buf->var; + if (var < last) { + for (; var;) { + struct efi_var_entry *pos; + bool match; + + match = efi_var_mem_compare(var, guid, name, &pos); + if (pos >= last) + pos = NULL; + if (match) { + if (next) + *next = pos; + return var; + } + var = pos; + } + } + if (next) + *next = NULL; + return NULL; +} + +void __efi_runtime efi_var_mem_del(struct efi_var_entry *var) +{ + u16 *data; + struct efi_var_entry *next, *last; + + if (!var) + return; + + last = (struct efi_var_entry *) + ((uintptr_t)efi_var_buf + efi_var_buf->length); + if (var <= efi_current_var) + efi_current_var = NULL; + + for (data = var->name; *data; ++data) + ; + ++data; + next = (struct efi_var_entry *) + ALIGN((uintptr_t)data + var->length, 8); + efi_var_buf->length -= (uintptr_t)next - (uintptr_t)var; + + /* efi_memcpy_runtime() can be used because next >= var. */ + efi_memcpy_runtime(var, next, (uintptr_t)last - (uintptr_t)next); + efi_var_buf->crc32 = crc32(0, (u8 *)efi_var_buf->var, + efi_var_buf->length - + sizeof(struct efi_var_file)); +} + +efi_status_t __efi_runtime efi_var_mem_ins( + const u16 *variable_name, + const efi_guid_t *vendor, u32 attributes, + const efi_uintn_t size1, const void *data1, + const efi_uintn_t size2, const void *data2, + const u64 time) +{ + u16 *data; + struct efi_var_entry *var; + u32 var_name_len; + + var = (struct efi_var_entry *) + ((uintptr_t)efi_var_buf + efi_var_buf->length); + var_name_len = u16_strlen(variable_name) + 1; + data = var->name + var_name_len; + + if ((uintptr_t)data - (uintptr_t)efi_var_buf + size1 + size2 > + EFI_VAR_BUF_SIZE) + return EFI_OUT_OF_RESOURCES; + + var->attr = attributes; + var->length = size1 + size2; + var->time = time; + + efi_memcpy_runtime(&var->guid, vendor, sizeof(efi_guid_t)); + efi_memcpy_runtime(var->name, variable_name, + sizeof(u16) * var_name_len); + efi_memcpy_runtime(data, data1, size1); + efi_memcpy_runtime((u8 *)data + size1, data2, size2); + + var = (struct efi_var_entry *) + ALIGN((uintptr_t)data + var->length, 8); + efi_var_buf->length = (uintptr_t)var - (uintptr_t)efi_var_buf; + efi_var_buf->crc32 = crc32(0, (u8 *)efi_var_buf->var, + efi_var_buf->length - + sizeof(struct efi_var_file)); + + return EFI_SUCCESS; +} + +u64 __efi_runtime efi_var_mem_free(void) +{ + if (efi_var_buf->length + sizeof(struct efi_var_entry) >= + EFI_VAR_BUF_SIZE) + return 0; + + return EFI_VAR_BUF_SIZE - efi_var_buf->length - + sizeof(struct efi_var_entry); +} + +/** + * efi_var_mem_notify_exit_boot_services() - SetVirtualMemoryMap callback + * + * @event: callback event + * @context: callback context + */ +static void EFIAPI __efi_runtime +efi_var_mem_notify_virtual_address_map(struct efi_event *event, void *context) +{ + efi_convert_pointer(0, (void **)&efi_var_buf); + efi_current_var = NULL; +} + +efi_status_t efi_var_mem_init(void) +{ + u64 memory; + efi_status_t ret; + struct efi_event *event; + + ret = efi_allocate_pages(EFI_ALLOCATE_ANY_PAGES, + EFI_RUNTIME_SERVICES_DATA, + efi_size_in_pages(EFI_VAR_BUF_SIZE), + &memory); + if (ret != EFI_SUCCESS) + return ret; + efi_var_buf = (struct efi_var_file *)(uintptr_t)memory; + memset(efi_var_buf, 0, EFI_VAR_BUF_SIZE); + efi_var_buf->magic = EFI_VAR_FILE_MAGIC; + efi_var_buf->length = (uintptr_t)efi_var_buf->var - + (uintptr_t)efi_var_buf; + + ret = efi_create_event(EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE, TPL_CALLBACK, + efi_var_mem_notify_virtual_address_map, NULL, + NULL, &event); + if (ret != EFI_SUCCESS) + return ret; + return ret; +} + +/** + * efi_var_collect_mem() - Copy EFI variables matching attributes mask from + * efi_var_buf + * + * @buf: buffer containing variable collection + * @lenp: buffer length + * @mask: mask of matched attributes + * + * Return: Status code + */ +efi_status_t __efi_runtime +efi_var_collect_mem(struct efi_var_file *buf, efi_uintn_t *lenp, u32 mask) +{ + static struct efi_var_file __efi_runtime_data hdr = { + .magic = EFI_VAR_FILE_MAGIC, + }; + struct efi_var_entry *last, *var, *var_to; + + hdr.length = sizeof(struct efi_var_file); + + var = efi_var_buf->var; + last = (struct efi_var_entry *) + ((uintptr_t)efi_var_buf + efi_var_buf->length); + if (buf) + var_to = buf->var; + + while (var < last) { + u32 len = efi_var_entry_len(var); + + if ((var->attr & mask) != mask) { + var = (void *)((uintptr_t)var + len); + continue; + } + + hdr.length += len; + + if (buf && hdr.length <= *lenp) { + efi_memcpy_runtime(var_to, var, len); + var_to = (void *)var_to + len; + } + var = (void *)var + len; + } + + if (!buf && hdr.length <= *lenp) { + *lenp = hdr.length; + return EFI_INVALID_PARAMETER; + } + + if (!buf || hdr.length > *lenp) { + *lenp = hdr.length; + return EFI_BUFFER_TOO_SMALL; + } + hdr.crc32 = crc32(0, (u8 *)buf->var, + hdr.length - sizeof(struct efi_var_file)); + + efi_memcpy_runtime(buf, &hdr, sizeof(hdr)); + *lenp = hdr.length; + + return EFI_SUCCESS; +} + +efi_status_t __efi_runtime +efi_get_variable_mem(const u16 *variable_name, const efi_guid_t *vendor, + u32 *attributes, efi_uintn_t *data_size, void *data, + u64 *timep, u32 mask) +{ + efi_uintn_t old_size; + struct efi_var_entry *var; + u16 *pdata; + + if (!variable_name || !vendor || !data_size) + return EFI_INVALID_PARAMETER; + var = efi_var_mem_find(vendor, variable_name, NULL); + if (!var) + return EFI_NOT_FOUND; + + /* + * This function is used at runtime to dump EFI variables. + * The memory backend we keep around has BS-only variables as + * well. At runtime we filter them here + */ + if (mask && !((var->attr & mask) == mask)) + return EFI_NOT_FOUND; + + if (attributes) + *attributes = var->attr; + if (timep) + *timep = var->time; + + if (!u16_strcmp(variable_name, vtf)) + return efi_var_collect_mem(data, data_size, EFI_VARIABLE_NON_VOLATILE); + + old_size = *data_size; + *data_size = var->length; + if (old_size < var->length) + return EFI_BUFFER_TOO_SMALL; + + if (!data) + return EFI_INVALID_PARAMETER; + + for (pdata = var->name; *pdata; ++pdata) + ; + ++pdata; + + efi_memcpy_runtime(data, pdata, var->length); + + return EFI_SUCCESS; +} + +efi_status_t __efi_runtime +efi_get_next_variable_name_mem(efi_uintn_t *variable_name_size, + u16 *variable_name, efi_guid_t *vendor, + u32 mask) +{ + struct efi_var_entry *var; + efi_uintn_t len, old_size; + u16 *pdata; + + if (!variable_name_size || !variable_name || !vendor) + return EFI_INVALID_PARAMETER; + +skip: + len = *variable_name_size >> 1; + if (u16_strnlen(variable_name, len) == len) + return EFI_INVALID_PARAMETER; + + if (!efi_var_mem_find(vendor, variable_name, &var) && *variable_name) + return EFI_INVALID_PARAMETER; + + if (!var) + return EFI_NOT_FOUND; + + for (pdata = var->name; *pdata; ++pdata) + ; + ++pdata; + + old_size = *variable_name_size; + *variable_name_size = (uintptr_t)pdata - (uintptr_t)var->name; + + if (old_size < *variable_name_size) + return EFI_BUFFER_TOO_SMALL; + + efi_memcpy_runtime(variable_name, var->name, *variable_name_size); + efi_memcpy_runtime(vendor, &var->guid, sizeof(efi_guid_t)); + + if (mask && !((var->attr & mask) == mask)) { + *variable_name_size = old_size; + goto skip; + } + + return EFI_SUCCESS; +} + +void efi_var_buf_update(struct efi_var_file *var_buf) +{ + memcpy(efi_var_buf, var_buf, EFI_VAR_BUF_SIZE); +} diff --git a/lib/efi_loader/efi_var_seed.S b/lib/efi_loader/efi_var_seed.S new file mode 100644 index 00000000000..e0a40cf46c8 --- /dev/null +++ b/lib/efi_loader/efi_var_seed.S @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Predefined UEFI variables + * + * Copyright (c) 2020, Heinrich Schuchardt <xypron.glpk@gmx.de> + */ + +#include <config.h> + +.section .rodata.efi_seed.init,"a" +.balign 16 +.global __efi_var_file_begin +__efi_var_file_begin: +.incbin CONFIG_EFI_VAR_SEED_FILE +.global __efi_var_file_end +__efi_var_file_end: +.balign 16 diff --git a/lib/efi_loader/efi_variable.c b/lib/efi_loader/efi_variable.c new file mode 100644 index 00000000000..f3533f4def3 --- /dev/null +++ b/lib/efi_loader/efi_variable.c @@ -0,0 +1,608 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * UEFI runtime variable services + * + * Copyright (c) 2017 Rob Clark + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <efi_loader.h> +#include <efi_variable.h> +#include <env.h> +#include <env_internal.h> +#include <hexdump.h> +#include <log.h> +#include <malloc.h> +#include <rtc.h> +#include <search.h> +#include <u-boot/uuid.h> +#include <crypto/pkcs7_parser.h> +#include <linux/compat.h> +#include <u-boot/crc.h> +#include <asm/sections.h> + +#ifdef CONFIG_EFI_SECURE_BOOT + +/** + * efi_variable_authenticate - authenticate a variable + * @variable: Variable name in u16 + * @vendor: Guid of variable + * @data_size: Size of @data + * @data: Pointer to variable's value + * @given_attr: Attributes to be given at SetVariable() + * @env_attr: Attributes that an existing variable holds + * @time: signed time that an existing variable holds + * + * Called by efi_set_variable() to verify that the input is correct. + * Will replace the given data pointer with another that points to + * the actual data to store in the internal memory. + * On success, @data and @data_size will be replaced with variable's + * actual data, excluding authentication data, and its size, and variable's + * attributes and signed time will also be returned in @env_attr and @time, + * respectively. + * + * Return: status code + */ +static efi_status_t efi_variable_authenticate(const u16 *variable, + const efi_guid_t *vendor, + efi_uintn_t *data_size, + const void **data, u32 given_attr, + u32 *env_attr, u64 *time) +{ + const struct efi_variable_authentication_2 *auth; + struct efi_signature_store *truststore, *truststore2; + struct pkcs7_message *var_sig; + struct efi_image_regions *regs; + struct efi_time timestamp; + struct rtc_time tm; + u64 new_time; + u8 *ebuf; + enum efi_auth_var_type var_type; + efi_status_t ret; + + var_sig = NULL; + truststore = NULL; + truststore2 = NULL; + regs = NULL; + ebuf = NULL; + ret = EFI_SECURITY_VIOLATION; + + if (*data_size < sizeof(struct efi_variable_authentication_2)) + goto err; + + /* authentication data */ + auth = *data; + if (*data_size < (sizeof(auth->time_stamp) + + auth->auth_info.hdr.dwLength)) + goto err; + + if (guidcmp(&auth->auth_info.cert_type, &efi_guid_cert_type_pkcs7)) + goto err; + + memcpy(×tamp, &auth->time_stamp, sizeof(timestamp)); + if (timestamp.pad1 || timestamp.nanosecond || timestamp.timezone || + timestamp.daylight || timestamp.pad2) + goto err; + + *data += sizeof(auth->time_stamp) + auth->auth_info.hdr.dwLength; + *data_size -= (sizeof(auth->time_stamp) + + auth->auth_info.hdr.dwLength); + + memset(&tm, 0, sizeof(tm)); + tm.tm_year = timestamp.year; + tm.tm_mon = timestamp.month; + tm.tm_mday = timestamp.day; + tm.tm_hour = timestamp.hour; + tm.tm_min = timestamp.minute; + tm.tm_sec = timestamp.second; + new_time = rtc_mktime(&tm); + + if (!efi_secure_boot_enabled()) { + /* finished checking */ + *time = new_time; + return EFI_SUCCESS; + } + + if (new_time <= *time) + goto err; + + /* data to be digested */ + regs = calloc(sizeof(*regs) + sizeof(struct image_region) * 5, 1); + if (!regs) + goto err; + regs->max = 5; + efi_image_region_add(regs, (uint8_t *)variable, + (uint8_t *)variable + + u16_strlen(variable) * sizeof(u16), 1); + efi_image_region_add(regs, (uint8_t *)vendor, + (uint8_t *)vendor + sizeof(*vendor), 1); + efi_image_region_add(regs, (uint8_t *)&given_attr, + (uint8_t *)&given_attr + sizeof(given_attr), 1); + efi_image_region_add(regs, (uint8_t *)×tamp, + (uint8_t *)×tamp + sizeof(timestamp), 1); + efi_image_region_add(regs, (uint8_t *)*data, + (uint8_t *)*data + *data_size, 1); + + /* variable's signature list */ + if (auth->auth_info.hdr.dwLength < sizeof(auth->auth_info)) + goto err; + + /* ebuf should be kept valid during the authentication */ + var_sig = efi_parse_pkcs7_header(auth->auth_info.cert_data, + auth->auth_info.hdr.dwLength + - sizeof(auth->auth_info), + &ebuf); + if (!var_sig) { + EFI_PRINT("Parsing variable's signature failed\n"); + goto err; + } + + /* signature database used for authentication */ + var_type = efi_auth_var_get_type(variable, vendor); + switch (var_type) { + case EFI_AUTH_VAR_PK: + case EFI_AUTH_VAR_KEK: + /* with PK */ + truststore = efi_sigstore_parse_sigdb(u"PK"); + if (!truststore) + goto err; + break; + case EFI_AUTH_VAR_DB: + case EFI_AUTH_VAR_DBX: + /* with PK and KEK */ + truststore = efi_sigstore_parse_sigdb(u"KEK"); + truststore2 = efi_sigstore_parse_sigdb(u"PK"); + if (!truststore) { + if (!truststore2) + goto err; + + truststore = truststore2; + truststore2 = NULL; + } + break; + default: + /* TODO: support private authenticated variables */ + ret = EFI_UNSUPPORTED; + goto err; + } + + /* verify signature */ + if (efi_signature_verify(regs, var_sig, truststore, NULL)) { + EFI_PRINT("Verified\n"); + } else { + if (truststore2 && + efi_signature_verify(regs, var_sig, truststore2, NULL)) { + EFI_PRINT("Verified\n"); + } else { + EFI_PRINT("Verifying variable's signature failed\n"); + goto err; + } + } + + /* finished checking */ + *time = new_time; + ret = EFI_SUCCESS; + +err: + efi_sigstore_free(truststore); + efi_sigstore_free(truststore2); + pkcs7_free_message(var_sig); + free(ebuf); + free(regs); + + return ret; +} +#else +static efi_status_t efi_variable_authenticate(const u16 *variable, + const efi_guid_t *vendor, + efi_uintn_t *data_size, + const void **data, u32 given_attr, + u32 *env_attr, u64 *time) +{ + return EFI_SUCCESS; +} +#endif /* CONFIG_EFI_SECURE_BOOT */ + +efi_status_t __efi_runtime +efi_get_variable_int(const u16 *variable_name, const efi_guid_t *vendor, + u32 *attributes, efi_uintn_t *data_size, void *data, + u64 *timep) +{ + return efi_get_variable_mem(variable_name, vendor, attributes, data_size, + data, timep, 0); +} + +efi_status_t __efi_runtime +efi_get_next_variable_name_int(efi_uintn_t *variable_name_size, + u16 *variable_name, efi_guid_t *vendor) +{ + return efi_get_next_variable_name_mem(variable_name_size, variable_name, + vendor, 0); +} + +/** + * setvariable_allowed() - checks defined by the UEFI spec for setvariable + * + * @variable_name: name of the variable + * @vendor: vendor GUID + * @attributes: attributes of the variable + * @data_size: size of the buffer with the variable value + * @data: buffer with the variable value + * Return: status code + */ +static efi_status_t __efi_runtime +setvariable_allowed(const u16 *variable_name, const efi_guid_t *vendor, + u32 attributes, efi_uintn_t data_size, const void *data) +{ + if (!variable_name || !*variable_name || !vendor) + return EFI_INVALID_PARAMETER; + + if (data_size && !data) + return EFI_INVALID_PARAMETER; + + /* + * EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS is deprecated. + * We don't support EFI_VARIABLE_ENHANCED_AUTHENTICATED_ACCESS. + */ + if (attributes & (EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS | \ + EFI_VARIABLE_ENHANCED_AUTHENTICATED_ACCESS)) + return EFI_UNSUPPORTED; + + /* Make sure if runtime bit is set, boot service bit is set also */ + if ((attributes & + (EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_BOOTSERVICE_ACCESS)) == + EFI_VARIABLE_RUNTIME_ACCESS) + return EFI_INVALID_PARAMETER; + + /* only EFI_VARIABLE_NON_VOLATILE attribute is invalid */ + if ((attributes & EFI_VARIABLE_MASK) == EFI_VARIABLE_NON_VOLATILE) + return EFI_INVALID_PARAMETER; + + /* Make sure HR is set with NV, BS and RT */ + if (attributes & EFI_VARIABLE_HARDWARE_ERROR_RECORD && + (!(attributes & EFI_VARIABLE_NON_VOLATILE) || + !(attributes & EFI_VARIABLE_RUNTIME_ACCESS) || + !(attributes & EFI_VARIABLE_BOOTSERVICE_ACCESS))) + return EFI_INVALID_PARAMETER; + + return EFI_SUCCESS; +} + +efi_status_t efi_set_variable_int(const u16 *variable_name, + const efi_guid_t *vendor, + u32 attributes, efi_uintn_t data_size, + const void *data, bool ro_check) +{ + struct efi_var_entry *var; + efi_uintn_t ret; + bool append, delete; + u64 time = 0; + enum efi_auth_var_type var_type; + + ret = setvariable_allowed(variable_name, vendor, attributes, data_size, + data); + if (ret != EFI_SUCCESS) + return ret; + + /* check if a variable exists */ + var = efi_var_mem_find(vendor, variable_name, NULL); + append = !!(attributes & EFI_VARIABLE_APPEND_WRITE); + delete = !append && (!data_size || !attributes); + + /* check attributes */ + var_type = efi_auth_var_get_type(variable_name, vendor); + if (var) { + if (ro_check && (var->attr & EFI_VARIABLE_READ_ONLY)) + return EFI_WRITE_PROTECTED; + + if (IS_ENABLED(CONFIG_EFI_VARIABLES_PRESEED)) { + if (var_type >= EFI_AUTH_VAR_PK) + return EFI_WRITE_PROTECTED; + } + + /* attributes won't be changed */ + if (!delete && + ((ro_check && var->attr != (attributes & ~EFI_VARIABLE_APPEND_WRITE)) || + (!ro_check && ((var->attr & ~EFI_VARIABLE_READ_ONLY) + != (attributes & ~EFI_VARIABLE_READ_ONLY))))) { + return EFI_INVALID_PARAMETER; + } + time = var->time; + } else { + /* + * UEFI specification does not clearly describe the expected + * behavior of append write with data size 0, we follow + * the EDK II reference implementation. + */ + if (append && !data_size) + return EFI_SUCCESS; + + /* + * EFI_VARIABLE_APPEND_WRITE to non-existent variable is accepted + * and new variable is created in EDK II reference implementation. + * We follow it and only check the deletion here. + */ + if (delete) + /* Trying to delete a non-existent variable. */ + return EFI_NOT_FOUND; + } + + if (var_type >= EFI_AUTH_VAR_PK) { + /* authentication is mandatory */ + if (!(attributes & + EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS)) { + EFI_PRINT("%ls: TIME_BASED_AUTHENTICATED_WRITE_ACCESS required\n", + variable_name); + return EFI_INVALID_PARAMETER; + } + } + + /* authenticate a variable */ + if (IS_ENABLED(CONFIG_EFI_SECURE_BOOT)) { + if (attributes & + EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS) { + u32 env_attr; + + ret = efi_variable_authenticate(variable_name, vendor, + &data_size, &data, + attributes, &env_attr, + &time); + if (ret != EFI_SUCCESS) + return ret; + + /* last chance to check for delete */ + if (!data_size) + delete = true; + } + } else { + if (attributes & + EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS) { + EFI_PRINT("Secure boot is not configured\n"); + return EFI_INVALID_PARAMETER; + } + } + + if (delete) { + /* EFI_NOT_FOUND has been handled before */ + attributes = var->attr; + ret = EFI_SUCCESS; + } else if (append && var) { + /* + * data is appended if EFI_VARIABLE_APPEND_WRITE is set and + * variable exists. + */ + u16 *old_data = var->name; + + for (; *old_data; ++old_data) + ; + ++old_data; + ret = efi_var_mem_ins(variable_name, vendor, + attributes & ~EFI_VARIABLE_APPEND_WRITE, + var->length, old_data, data_size, data, + time); + } else { + ret = efi_var_mem_ins(variable_name, vendor, attributes, + data_size, data, 0, NULL, time); + } + + if (ret != EFI_SUCCESS) + return ret; + + efi_var_mem_del(var); + + if (var_type == EFI_AUTH_VAR_PK) + ret = efi_init_secure_state(); + else + ret = EFI_SUCCESS; + + /* + * Write non-volatile EFI variables to file + * TODO: check if a value change has occured to avoid superfluous writes + */ + if (attributes & EFI_VARIABLE_NON_VOLATILE) + efi_var_to_file(); + + return EFI_SUCCESS; +} + +efi_status_t __efi_runtime +efi_query_variable_info_int(u32 attributes, + u64 *maximum_variable_storage_size, + u64 *remaining_variable_storage_size, + u64 *maximum_variable_size) +{ + if (!maximum_variable_storage_size || + !remaining_variable_storage_size || + !maximum_variable_size || !attributes) + return EFI_INVALID_PARAMETER; + + /* EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS is deprecated */ + if ((attributes & EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS) || + ((attributes & EFI_VARIABLE_MASK) == 0)) + return EFI_UNSUPPORTED; + + if ((attributes & EFI_VARIABLE_MASK) == EFI_VARIABLE_NON_VOLATILE) + return EFI_INVALID_PARAMETER; + + /* Make sure if runtime bit is set, boot service bit is set also. */ + if ((attributes & + (EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_BOOTSERVICE_ACCESS)) == + EFI_VARIABLE_RUNTIME_ACCESS) + return EFI_INVALID_PARAMETER; + + if (attributes & ~EFI_VARIABLE_MASK) + return EFI_INVALID_PARAMETER; + + *maximum_variable_storage_size = EFI_VAR_BUF_SIZE - + sizeof(struct efi_var_file); + *remaining_variable_storage_size = efi_var_mem_free(); + *maximum_variable_size = EFI_VAR_BUF_SIZE - + sizeof(struct efi_var_file) - + sizeof(struct efi_var_entry); + return EFI_SUCCESS; +} + +/** + * efi_query_variable_info_runtime() - runtime implementation of + * QueryVariableInfo() + * + * @attributes: bitmask to select variables to be + * queried + * @maximum_variable_storage_size: maximum size of storage area for the + * selected variable types + * @remaining_variable_storage_size: remaining size of storage are for the + * selected variable types + * @maximum_variable_size: maximum size of a variable of the + * selected type + * Returns: status code + */ +static efi_status_t __efi_runtime EFIAPI efi_query_variable_info_runtime( + u32 attributes, + u64 *maximum_variable_storage_size, + u64 *remaining_variable_storage_size, + u64 *maximum_variable_size) +{ + if (!(attributes & EFI_VARIABLE_RUNTIME_ACCESS)) + return EFI_INVALID_PARAMETER; + if ((attributes & (EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS | + EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS | + EFI_VARIABLE_ENHANCED_AUTHENTICATED_ACCESS))) + return EFI_UNSUPPORTED; + + return efi_query_variable_info_int(attributes, + maximum_variable_storage_size, + remaining_variable_storage_size, + maximum_variable_size); +} + +/** + * efi_set_variable_runtime() - runtime implementation of SetVariable() + * + * @variable_name: name of the variable + * @vendor: vendor GUID + * @attributes: attributes of the variable + * @data_size: size of the buffer with the variable value + * @data: buffer with the variable value + * Return: status code + */ +static efi_status_t __efi_runtime EFIAPI +efi_set_variable_runtime(u16 *variable_name, const efi_guid_t *vendor, + u32 attributes, efi_uintn_t data_size, + const void *data) +{ + struct efi_var_entry *var; + efi_uintn_t ret; + bool append, delete; + u64 time = 0; + + if (!IS_ENABLED(CONFIG_EFI_RT_VOLATILE_STORE)) + return EFI_UNSUPPORTED; + + /* + * Authenticated variables are not supported. The EFI spec + * in §32.3.6 requires keys to be stored in non-volatile storage which + * is tamper and delete resistant. + * The rest of the checks are in setvariable_allowed() + */ + if (attributes & EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS) + return EFI_INVALID_PARAMETER; + + ret = setvariable_allowed(variable_name, vendor, attributes, data_size, + data); + if (ret != EFI_SUCCESS) + return ret; + + /* check if a variable exists */ + var = efi_var_mem_find(vendor, variable_name, NULL); + append = !!(attributes & EFI_VARIABLE_APPEND_WRITE); + attributes &= ~EFI_VARIABLE_APPEND_WRITE; + delete = !append && (!data_size || !attributes); + + /* BS only variables are hidden deny writing them */ + if (!delete && !(attributes & EFI_VARIABLE_RUNTIME_ACCESS)) + return EFI_INVALID_PARAMETER; + + if (var) { + if (var->attr & EFI_VARIABLE_READ_ONLY || + !(var->attr & EFI_VARIABLE_NON_VOLATILE)) + return EFI_WRITE_PROTECTED; + + /* attributes won't be changed */ + if (!delete && (((var->attr & ~EFI_VARIABLE_READ_ONLY) != + (attributes & ~EFI_VARIABLE_READ_ONLY)))) + return EFI_INVALID_PARAMETER; + time = var->time; + } else { + if (!(attributes & EFI_VARIABLE_NON_VOLATILE)) + return EFI_INVALID_PARAMETER; + if (append && !data_size) + return EFI_SUCCESS; + if (delete) + return EFI_NOT_FOUND; + } + + if (delete) { + /* EFI_NOT_FOUND has been handled before */ + attributes = var->attr; + ret = EFI_SUCCESS; + } else if (append && var) { + u16 *old_data = (void *)((uintptr_t)var->name + + sizeof(u16) * (u16_strlen(var->name) + 1)); + + ret = efi_var_mem_ins(variable_name, vendor, attributes, + var->length, old_data, data_size, data, + time); + } else { + ret = efi_var_mem_ins(variable_name, vendor, attributes, + data_size, data, 0, NULL, time); + } + + if (ret != EFI_SUCCESS) + return ret; + /* We are always inserting new variables, get rid of the old copy */ + efi_var_mem_del(var); + + return EFI_SUCCESS; +} + +/** + * efi_variables_boot_exit_notify() - notify ExitBootServices() is called + */ +void efi_variables_boot_exit_notify(void) +{ + /* Switch variable services functions to runtime version */ + efi_runtime_services.get_variable = efi_get_variable_runtime; + efi_runtime_services.get_next_variable_name = + efi_get_next_variable_name_runtime; + efi_runtime_services.set_variable = efi_set_variable_runtime; + efi_runtime_services.query_variable_info = + efi_query_variable_info_runtime; + efi_update_table_header_crc32(&efi_runtime_services.hdr); +} + +/** + * efi_init_variables() - initialize variable services + * + * Return: status code + */ +efi_status_t efi_init_variables(void) +{ + efi_status_t ret; + + ret = efi_var_mem_init(); + if (ret != EFI_SUCCESS) + return ret; + + ret = efi_var_from_file(); + if (ret != EFI_SUCCESS) + return ret; + if (IS_ENABLED(CONFIG_EFI_VARIABLES_PRESEED)) { + ret = efi_var_restore((struct efi_var_file *) + __efi_var_file_begin, true); + if (ret != EFI_SUCCESS) + log_err("Invalid EFI variable seed\n"); + } + + return efi_init_secure_state(); +} diff --git a/lib/efi_loader/efi_variable_tee.c b/lib/efi_loader/efi_variable_tee.c new file mode 100644 index 00000000000..6a1fa39bb6f --- /dev/null +++ b/lib/efi_loader/efi_variable_tee.c @@ -0,0 +1,1014 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI variable service via OP-TEE + * + * Copyright (C) 2019 Linaro Ltd. <sughosh.ganu@linaro.org> + * Copyright (C) 2019 Linaro Ltd. <ilias.apalodimas@linaro.org> + * Copyright 2022-2023 Arm Limited and/or its affiliates <open-source-office@arm.com> + * + * Authors: + * Abdellatif El Khlifi <abdellatif.elkhlifi@arm.com> + */ + +#define LOG_CATEGORY LOGC_EFI + +#if CONFIG_IS_ENABLED(ARM_FFA_TRANSPORT) +#include <arm_ffa.h> +#endif +#include <cpu_func.h> +#include <dm.h> +#include <efi.h> +#include <efi_api.h> +#include <efi_loader.h> +#include <efi_variable.h> +#include <malloc.h> +#include <mapmem.h> +#include <mm_communication.h> +#include <tee.h> + +#if CONFIG_IS_ENABLED(ARM_FFA_TRANSPORT) +/* MM return codes */ +#define MM_SUCCESS (0) +#define MM_NOT_SUPPORTED (-1) +#define MM_INVALID_PARAMETER (-2) +#define MM_DENIED (-3) +#define MM_NO_MEMORY (-5) + +static const char *mm_sp_svc_uuid = MM_SP_UUID; +static u16 mm_sp_id; +#endif + +extern struct efi_var_file __efi_runtime_data *efi_var_buf; +static efi_uintn_t max_buffer_size; /* comm + var + func + data */ +static efi_uintn_t max_payload_size; /* func + data */ +static const u16 __efi_runtime_rodata pk[] = u"PK"; + +struct mm_connection { + struct udevice *tee; + u32 session; +}; + +/** + * get_connection() - Retrieve OP-TEE session for a specific UUID. + * + * @conn: session buffer to fill + * Return: status code + */ +static int get_connection(struct mm_connection *conn) +{ + static const struct tee_optee_ta_uuid uuid = PTA_STMM_UUID; + struct udevice *tee = NULL; + struct tee_open_session_arg arg; + int rc = -ENODEV; + + tee = tee_find_device(tee, NULL, NULL, NULL); + if (!tee) + goto out; + + memset(&arg, 0, sizeof(arg)); + tee_optee_ta_uuid_to_octets(arg.uuid, &uuid); + rc = tee_open_session(tee, &arg, 0, NULL); + if (rc) + goto out; + + /* Check the internal OP-TEE result */ + if (arg.ret != TEE_SUCCESS) { + rc = -EIO; + goto out; + } + + conn->tee = tee; + conn->session = arg.session; + + return 0; +out: + return rc; +} + +/** + * optee_mm_communicate() - Pass a buffer to StandaloneMM running in OP-TEE + * + * @comm_buf: locally allocted communcation buffer + * @dsize: buffer size + * Return: status code + */ +static efi_status_t optee_mm_communicate(void *comm_buf, ulong dsize) +{ + ulong buf_size; + efi_status_t ret; + struct efi_mm_communicate_header *mm_hdr; + struct mm_connection conn = { NULL, 0 }; + struct tee_invoke_arg arg; + struct tee_param param[2]; + struct tee_shm *shm = NULL; + int rc; + + if (!comm_buf) + return EFI_INVALID_PARAMETER; + + mm_hdr = (struct efi_mm_communicate_header *)comm_buf; + buf_size = mm_hdr->message_len + sizeof(efi_guid_t) + sizeof(size_t); + + if (dsize != buf_size) + return EFI_INVALID_PARAMETER; + + rc = get_connection(&conn); + if (rc) { + log_err("Unable to open OP-TEE session (err=%d)\n", rc); + return EFI_UNSUPPORTED; + } + + if (tee_shm_register(conn.tee, comm_buf, buf_size, 0, &shm)) { + log_err("Unable to register shared memory\n"); + tee_close_session(conn.tee, conn.session); + return EFI_UNSUPPORTED; + } + + memset(&arg, 0, sizeof(arg)); + arg.func = PTA_STMM_CMDID_COMMUNICATE; + arg.session = conn.session; + + memset(param, 0, sizeof(param)); + param[0].attr = TEE_PARAM_ATTR_TYPE_MEMREF_INOUT; + param[0].u.memref.size = buf_size; + param[0].u.memref.shm = shm; + param[1].attr = TEE_PARAM_ATTR_TYPE_VALUE_OUTPUT; + + rc = tee_invoke_func(conn.tee, &arg, 2, param); + tee_shm_free(shm); + tee_close_session(conn.tee, conn.session); + if (rc) + return EFI_DEVICE_ERROR; + if (arg.ret == TEE_ERROR_EXCESS_DATA) + log_err("Variable payload too large\n"); + if (arg.ret != TEE_SUCCESS) + return EFI_DEVICE_ERROR; + + switch (param[1].u.value.a) { + case ARM_SVC_SPM_RET_SUCCESS: + ret = EFI_SUCCESS; + break; + + case ARM_SVC_SPM_RET_INVALID_PARAMS: + ret = EFI_INVALID_PARAMETER; + break; + + case ARM_SVC_SPM_RET_DENIED: + ret = EFI_ACCESS_DENIED; + break; + + case ARM_SVC_SPM_RET_NO_MEMORY: + ret = EFI_OUT_OF_RESOURCES; + break; + + default: + ret = EFI_ACCESS_DENIED; + } + + return ret; +} + +#if CONFIG_IS_ENABLED(ARM_FFA_TRANSPORT) +/** + * ffa_notify_mm_sp() - Announce there is data in the shared buffer + * + * Notify the MM partition in the trusted world that + * data is available in the shared buffer. + * This is a blocking call during which trusted world has exclusive access + * to the MM shared buffer. + * + * Return: + * + * 0 on success + */ +static int ffa_notify_mm_sp(void) +{ + struct ffa_send_direct_data msg = {0}; + int ret; + int sp_event_ret; + struct udevice *dev; + + ret = uclass_first_device_err(UCLASS_FFA, &dev); + if (ret) { + log_err("EFI: Cannot find FF-A bus device, notify MM SP failure\n"); + return ret; + } + + msg.data0 = CONFIG_FFA_SHARED_MM_BUF_OFFSET; /* x3 */ + + ret = ffa_sync_send_receive(dev, mm_sp_id, &msg, 1); + if (ret) + return ret; + + sp_event_ret = msg.data0; /* x3 */ + + switch (sp_event_ret) { + case MM_SUCCESS: + ret = 0; + break; + case MM_NOT_SUPPORTED: + ret = -EINVAL; + break; + case MM_INVALID_PARAMETER: + ret = -EPERM; + break; + case MM_DENIED: + ret = -EACCES; + break; + case MM_NO_MEMORY: + ret = -EBUSY; + break; + default: + ret = -EACCES; + } + + return ret; +} + +/** + * ffa_discover_mm_sp_id() - Query the MM partition ID + * + * Use the FF-A driver to get the MM partition ID. + * If multiple partitions are found, use the first one. + * This is a boot time function. + * + * Return: + * + * 0 on success + */ +static int ffa_discover_mm_sp_id(void) +{ + u32 count = 0; + int ret; + struct ffa_partition_desc *descs; + struct udevice *dev; + + ret = uclass_first_device_err(UCLASS_FFA, &dev); + if (ret) { + log_err("EFI: Cannot find FF-A bus device, MM SP discovery failure\n"); + return ret; + } + + /* Ask the driver to fill the buffer with the SPs info */ + ret = ffa_partition_info_get(dev, mm_sp_svc_uuid, &count, &descs); + if (ret) { + log_err("EFI: Failure in querying SPs info (%d), MM SP discovery failure\n", ret); + return ret; + } + + /* MM SPs found , use the first one */ + + mm_sp_id = descs[0].info.id; + + log_info("EFI: MM partition ID 0x%x\n", mm_sp_id); + + return 0; +} + +/** + * ffa_mm_communicate() - Exchange EFI services data with the MM partition using FF-A + * @comm_buf: locally allocated communication buffer used for rx/tx + * @dsize: communication buffer size + * + * Issue a door bell event to notify the MM partition (SP) running in OP-TEE + * that there is data to read from the shared buffer. + * Communication with the MM SP is performed using FF-A transport. + * On the event, MM SP can read the data from the buffer and + * update the MM shared buffer with response data. + * The response data is copied back to the communication buffer. + * + * Return: + * + * EFI status code + */ +static efi_status_t ffa_mm_communicate(void *comm_buf, ulong comm_buf_size) +{ + ulong tx_data_size; + int ffa_ret; + efi_status_t efi_ret; + struct efi_mm_communicate_header *mm_hdr; + void *virt_shared_buf; + + if (!comm_buf) + return EFI_INVALID_PARAMETER; + + /* Discover MM partition ID at boot time */ + if (!mm_sp_id && ffa_discover_mm_sp_id()) { + log_err("EFI: Failure to discover MM SP ID at boot time, FF-A MM comms failure\n"); + return EFI_UNSUPPORTED; + } + + mm_hdr = (struct efi_mm_communicate_header *)comm_buf; + tx_data_size = mm_hdr->message_len + sizeof(efi_guid_t) + sizeof(size_t); + + if (comm_buf_size != tx_data_size || tx_data_size > CONFIG_FFA_SHARED_MM_BUF_SIZE) + return EFI_INVALID_PARAMETER; + + /* Copy the data to the shared buffer */ + + virt_shared_buf = map_sysmem((phys_addr_t)CONFIG_FFA_SHARED_MM_BUF_ADDR, 0); + memcpy(virt_shared_buf, comm_buf, tx_data_size); + + /* + * The secure world might have cache disabled for + * the device region used for shared buffer (which is the case for Optee). + * In this case, the secure world reads the data from DRAM. + * Let's flush the cache so the DRAM is updated with the latest data. + */ +#ifdef CONFIG_ARM64 + invalidate_dcache_all(); +#endif + + /* Announce there is data in the shared buffer */ + + ffa_ret = ffa_notify_mm_sp(); + + switch (ffa_ret) { + case 0: { + ulong rx_data_size; + /* Copy the MM SP response from the shared buffer to the communication buffer */ + rx_data_size = ((struct efi_mm_communicate_header *)virt_shared_buf)->message_len + + sizeof(efi_guid_t) + + sizeof(size_t); + + if (rx_data_size > comm_buf_size) { + efi_ret = EFI_OUT_OF_RESOURCES; + break; + } + + memcpy(comm_buf, virt_shared_buf, rx_data_size); + efi_ret = EFI_SUCCESS; + break; + } + case -EINVAL: + efi_ret = EFI_DEVICE_ERROR; + break; + case -EPERM: + efi_ret = EFI_INVALID_PARAMETER; + break; + case -EACCES: + efi_ret = EFI_ACCESS_DENIED; + break; + case -EBUSY: + efi_ret = EFI_OUT_OF_RESOURCES; + break; + default: + efi_ret = EFI_ACCESS_DENIED; + } + + unmap_sysmem(virt_shared_buf); + return efi_ret; +} + +/** + * get_mm_comms() - detect the available MM transport + * + * Make sure the FF-A bus is probed successfully + * which means FF-A communication with secure world works and ready + * for use. + * + * If FF-A bus is not ready, use OPTEE comms. + * + * Return: + * + * MM_COMMS_FFA or MM_COMMS_OPTEE + */ +static enum mm_comms_select get_mm_comms(void) +{ + struct udevice *dev; + int ret; + + ret = uclass_first_device_err(UCLASS_FFA, &dev); + if (ret) { + log_debug("EFI: Cannot find FF-A bus device, trying Optee comms\n"); + return MM_COMMS_OPTEE; + } + + return MM_COMMS_FFA; +} +#endif + +/** + * mm_communicate() - Adjust the communication buffer to the MM SP and send + * it to OP-TEE + * + * @comm_buf: locally allocated communication buffer + * @dsize: buffer size + * + * The SP (also called partition) can be any MM SP such as StandAlonneMM or smm-gateway. + * The comm_buf format is the same for both partitions. + * When using the u-boot OP-TEE driver, StandAlonneMM is supported. + * When using the u-boot FF-A driver, any MM SP is supported. + * + * Return: status code + */ +static efi_status_t mm_communicate(u8 *comm_buf, efi_uintn_t dsize) +{ + efi_status_t ret; + struct efi_mm_communicate_header *mm_hdr; + struct smm_variable_communicate_header *var_hdr; +#if CONFIG_IS_ENABLED(ARM_FFA_TRANSPORT) + enum mm_comms_select mm_comms; +#endif + + dsize += MM_COMMUNICATE_HEADER_SIZE + MM_VARIABLE_COMMUNICATE_SIZE; + mm_hdr = (struct efi_mm_communicate_header *)comm_buf; + var_hdr = (struct smm_variable_communicate_header *)mm_hdr->data; + +#if CONFIG_IS_ENABLED(ARM_FFA_TRANSPORT) + mm_comms = get_mm_comms(); + if (mm_comms == MM_COMMS_FFA) + ret = ffa_mm_communicate(comm_buf, dsize); + else + ret = optee_mm_communicate(comm_buf, dsize); +#else + ret = optee_mm_communicate(comm_buf, dsize); +#endif + + if (ret != EFI_SUCCESS) { + log_err("%s failed!\n", __func__); + return ret; + } + + return var_hdr->ret_status; +} + +/** + * setup_mm_hdr() - Allocate a buffer for StandAloneMM and initialize the + * header data. + * + * @dptr: pointer address of the corresponding StandAloneMM + * function + * @payload_size: buffer size + * @func: standAloneMM function number + * @ret: EFI return code + * Return: buffer or NULL + */ +static u8 *setup_mm_hdr(void **dptr, efi_uintn_t payload_size, + efi_uintn_t func, efi_status_t *ret) +{ + const efi_guid_t mm_var_guid = EFI_MM_VARIABLE_GUID; + struct efi_mm_communicate_header *mm_hdr; + struct smm_variable_communicate_header *var_hdr; + u8 *comm_buf; + + /* In the init function we initialize max_buffer_size with + * get_max_payload(). So skip the test if max_buffer_size is initialized + * StandAloneMM will perform similar checks and drop the buffer if it's + * too long + */ + if (max_buffer_size && max_buffer_size < + (MM_COMMUNICATE_HEADER_SIZE + + MM_VARIABLE_COMMUNICATE_SIZE + + payload_size)) { + *ret = EFI_INVALID_PARAMETER; + return NULL; + } + + comm_buf = calloc(1, MM_COMMUNICATE_HEADER_SIZE + + MM_VARIABLE_COMMUNICATE_SIZE + + payload_size); + if (!comm_buf) { + *ret = EFI_OUT_OF_RESOURCES; + return NULL; + } + + mm_hdr = (struct efi_mm_communicate_header *)comm_buf; + guidcpy(&mm_hdr->header_guid, &mm_var_guid); + mm_hdr->message_len = MM_VARIABLE_COMMUNICATE_SIZE + payload_size; + + var_hdr = (struct smm_variable_communicate_header *)mm_hdr->data; + var_hdr->function = func; + if (dptr) + *dptr = var_hdr->data; + *ret = EFI_SUCCESS; + + return comm_buf; +} + +/** + * get_max_payload() - Get variable payload size from StandAloneMM. + * + * @size: size of the variable in storage + * Return: status code + */ +efi_status_t EFIAPI get_max_payload(efi_uintn_t *size) +{ + struct smm_variable_payload_size *var_payload = NULL; + efi_uintn_t payload_size; + u8 *comm_buf = NULL; + efi_status_t ret; + + if (!size) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + payload_size = sizeof(*var_payload); + comm_buf = setup_mm_hdr((void **)&var_payload, payload_size, + SMM_VARIABLE_FUNCTION_GET_PAYLOAD_SIZE, &ret); + if (!comm_buf) + goto out; + + ret = mm_communicate(comm_buf, payload_size); + if (ret != EFI_SUCCESS) + goto out; + + /* Make sure the buffer is big enough for storing variables */ + if (var_payload->size < MM_VARIABLE_ACCESS_HEADER_SIZE + 0x20) { + ret = EFI_DEVICE_ERROR; + goto out; + } + *size = var_payload->size; + /* + * There seems to be a bug in EDK2 miscalculating the boundaries and + * size checks, so deduct 2 more bytes to fulfill this requirement. Fix + * it up here to ensure backwards compatibility with older versions + * (cf. StandaloneMmPkg/Drivers/StandaloneMmCpu/AArch64/EventHandle.c. + * sizeof (EFI_MM_COMMUNICATE_HEADER) instead the size minus the + * flexible array member). + * + * size is guaranteed to be > 2 due to checks on the beginning. + */ + *size -= 2; +out: + free(comm_buf); + return ret; +} + +/* + * StMM can store internal attributes and properties for variables, i.e enabling + * R/O variables + */ +static efi_status_t set_property_int(const u16 *variable_name, + efi_uintn_t name_size, + const efi_guid_t *vendor, + struct var_check_property *var_property) +{ + struct smm_variable_var_check_property *smm_property; + efi_uintn_t payload_size; + u8 *comm_buf = NULL; + efi_status_t ret; + + payload_size = sizeof(*smm_property) + name_size; + if (payload_size > max_payload_size) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + comm_buf = setup_mm_hdr((void **)&smm_property, payload_size, + SMM_VARIABLE_FUNCTION_VAR_CHECK_VARIABLE_PROPERTY_SET, + &ret); + if (!comm_buf) + goto out; + + guidcpy(&smm_property->guid, vendor); + smm_property->name_size = name_size; + memcpy(&smm_property->property, var_property, + sizeof(smm_property->property)); + memcpy(smm_property->name, variable_name, name_size); + + ret = mm_communicate(comm_buf, payload_size); + +out: + free(comm_buf); + return ret; +} + +static efi_status_t get_property_int(const u16 *variable_name, + efi_uintn_t name_size, + const efi_guid_t *vendor, + struct var_check_property *var_property) +{ + struct smm_variable_var_check_property *smm_property; + efi_uintn_t payload_size; + u8 *comm_buf = NULL; + efi_status_t ret; + + memset(var_property, 0, sizeof(*var_property)); + payload_size = sizeof(*smm_property) + name_size; + if (payload_size > max_payload_size) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + comm_buf = setup_mm_hdr((void **)&smm_property, payload_size, + SMM_VARIABLE_FUNCTION_VAR_CHECK_VARIABLE_PROPERTY_GET, + &ret); + if (!comm_buf) + goto out; + + guidcpy(&smm_property->guid, vendor); + smm_property->name_size = name_size; + memcpy(smm_property->name, variable_name, name_size); + + ret = mm_communicate(comm_buf, payload_size); + /* + * Currently only R/O property is supported in StMM. + * Variables that are not set to R/O will not set the property in StMM + * and the call will return EFI_NOT_FOUND. We are setting the + * properties to 0x0 so checking against that is enough for the + * EFI_NOT_FOUND case. + */ + if (ret == EFI_NOT_FOUND) + ret = EFI_SUCCESS; + if (ret != EFI_SUCCESS) + goto out; + memcpy(var_property, &smm_property->property, sizeof(*var_property)); + +out: + free(comm_buf); + return ret; +} + +efi_status_t efi_get_variable_int(const u16 *variable_name, + const efi_guid_t *vendor, + u32 *attributes, efi_uintn_t *data_size, + void *data, u64 *timep) +{ + struct var_check_property var_property; + struct smm_variable_access *var_acc; + efi_uintn_t payload_size; + efi_uintn_t name_size; + efi_uintn_t tmp_dsize; + u8 *comm_buf = NULL; + efi_status_t ret, tmp; + + if (!variable_name || !vendor || !data_size) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + /* Check payload size */ + name_size = u16_strsize(variable_name); + if (name_size > max_payload_size - MM_VARIABLE_ACCESS_HEADER_SIZE) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + /* Trim output buffer size */ + tmp_dsize = *data_size; + if (name_size + tmp_dsize > + max_payload_size - MM_VARIABLE_ACCESS_HEADER_SIZE) { + tmp_dsize = max_payload_size - + MM_VARIABLE_ACCESS_HEADER_SIZE - + name_size; + } + + /* Get communication buffer and initialize header */ + payload_size = MM_VARIABLE_ACCESS_HEADER_SIZE + name_size + tmp_dsize; + comm_buf = setup_mm_hdr((void **)&var_acc, payload_size, + SMM_VARIABLE_FUNCTION_GET_VARIABLE, &ret); + if (!comm_buf) + goto out; + + /* Fill in contents */ + guidcpy(&var_acc->guid, vendor); + var_acc->data_size = tmp_dsize; + var_acc->name_size = name_size; + var_acc->attr = attributes ? *attributes : 0; + memcpy(var_acc->name, variable_name, name_size); + + /* Communicate */ + ret = mm_communicate(comm_buf, payload_size); + if (ret != EFI_SUCCESS && ret != EFI_BUFFER_TOO_SMALL) + goto out; + + /* Update with reported data size for trimmed case */ + *data_size = var_acc->data_size; + /* + * UEFI > 2.7 needs the attributes set even if the buffer is + * smaller + */ + if (attributes) { + tmp = get_property_int(variable_name, name_size, vendor, + &var_property); + if (tmp != EFI_SUCCESS) { + ret = tmp; + goto out; + } + *attributes = var_acc->attr; + if (var_property.property & + VAR_CHECK_VARIABLE_PROPERTY_READ_ONLY) + *attributes |= EFI_VARIABLE_READ_ONLY; + } + + /* return if ret is EFI_BUFFER_TOO_SMALL */ + if (ret != EFI_SUCCESS) + goto out; + + if (data) + memcpy(data, (u8 *)var_acc->name + var_acc->name_size, + var_acc->data_size); + else + ret = EFI_INVALID_PARAMETER; + +out: + free(comm_buf); + return ret; +} + +efi_status_t efi_get_next_variable_name_int(efi_uintn_t *variable_name_size, + u16 *variable_name, + efi_guid_t *guid) +{ + struct smm_variable_getnext *var_getnext; + efi_uintn_t payload_size; + efi_uintn_t out_name_size; + efi_uintn_t in_name_size; + u8 *comm_buf = NULL; + efi_status_t ret; + + if (!variable_name_size || !variable_name || !guid) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + out_name_size = *variable_name_size; + in_name_size = u16_strsize(variable_name); + + if (out_name_size < in_name_size) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + if (in_name_size > max_payload_size - MM_VARIABLE_GET_NEXT_HEADER_SIZE) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + /* Trim output buffer size */ + if (out_name_size > max_payload_size - MM_VARIABLE_GET_NEXT_HEADER_SIZE) + out_name_size = max_payload_size - MM_VARIABLE_GET_NEXT_HEADER_SIZE; + + payload_size = MM_VARIABLE_GET_NEXT_HEADER_SIZE + out_name_size; + comm_buf = setup_mm_hdr((void **)&var_getnext, payload_size, + SMM_VARIABLE_FUNCTION_GET_NEXT_VARIABLE_NAME, + &ret); + if (!comm_buf) + goto out; + + /* Fill in contents */ + guidcpy(&var_getnext->guid, guid); + var_getnext->name_size = out_name_size; + memcpy(var_getnext->name, variable_name, in_name_size); + memset((u8 *)var_getnext->name + in_name_size, 0x0, + out_name_size - in_name_size); + + /* Communicate */ + ret = mm_communicate(comm_buf, payload_size); + if (ret == EFI_SUCCESS || ret == EFI_BUFFER_TOO_SMALL) { + /* Update with reported data size for trimmed case */ + *variable_name_size = var_getnext->name_size; + } + if (ret != EFI_SUCCESS) + goto out; + + guidcpy(guid, &var_getnext->guid); + memcpy(variable_name, var_getnext->name, var_getnext->name_size); + +out: + free(comm_buf); + return ret; +} + +efi_status_t efi_set_variable_int(const u16 *variable_name, + const efi_guid_t *vendor, u32 attributes, + efi_uintn_t data_size, const void *data, + bool ro_check) +{ + efi_status_t ret, alt_ret = EFI_SUCCESS; + struct var_check_property var_property; + struct smm_variable_access *var_acc; + efi_uintn_t payload_size; + efi_uintn_t name_size; + u8 *comm_buf = NULL; + bool ro; + + if (!variable_name || variable_name[0] == 0 || !vendor) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + if (data_size > 0 && !data) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + /* Check payload size */ + name_size = u16_strsize(variable_name); + payload_size = MM_VARIABLE_ACCESS_HEADER_SIZE + name_size + data_size; + if (payload_size > max_payload_size) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + /* + * Allocate the buffer early, before switching to RW (if needed) + * so we won't need to account for any failures in reading/setting + * the properties, if the allocation fails + */ + comm_buf = setup_mm_hdr((void **)&var_acc, payload_size, + SMM_VARIABLE_FUNCTION_SET_VARIABLE, &ret); + if (!comm_buf) + goto out; + + ro = !!(attributes & EFI_VARIABLE_READ_ONLY); + attributes &= EFI_VARIABLE_MASK; + + /* + * The API has the ability to override RO flags. If no RO check was + * requested switch the variable to RW for the duration of this call + */ + ret = get_property_int(variable_name, name_size, vendor, + &var_property); + if (ret != EFI_SUCCESS) + goto out; + + if (var_property.property & VAR_CHECK_VARIABLE_PROPERTY_READ_ONLY) { + /* Bypass r/o check */ + if (!ro_check) { + var_property.property &= ~VAR_CHECK_VARIABLE_PROPERTY_READ_ONLY; + ret = set_property_int(variable_name, name_size, vendor, &var_property); + if (ret != EFI_SUCCESS) + goto out; + } else { + ret = EFI_WRITE_PROTECTED; + goto out; + } + } + + /* Fill in contents */ + guidcpy(&var_acc->guid, vendor); + var_acc->data_size = data_size; + var_acc->name_size = name_size; + var_acc->attr = attributes; + memcpy(var_acc->name, variable_name, name_size); + memcpy((u8 *)var_acc->name + name_size, data, data_size); + + /* Communicate */ + ret = mm_communicate(comm_buf, payload_size); + if (ret != EFI_SUCCESS) + alt_ret = ret; + + if (ro && !(var_property.property & VAR_CHECK_VARIABLE_PROPERTY_READ_ONLY)) { + var_property.revision = VAR_CHECK_VARIABLE_PROPERTY_REVISION; + var_property.property |= VAR_CHECK_VARIABLE_PROPERTY_READ_ONLY; + var_property.attributes = attributes; + var_property.minsize = 1; + var_property.maxsize = var_acc->data_size; + ret = set_property_int(variable_name, name_size, vendor, &var_property); + } + + if (alt_ret != EFI_SUCCESS) + goto out; + + if (!u16_strcmp(variable_name, pk)) + alt_ret = efi_init_secure_state(); +out: + free(comm_buf); + return alt_ret == EFI_SUCCESS ? ret : alt_ret; +} + +efi_status_t efi_query_variable_info_int(u32 attributes, + u64 *max_variable_storage_size, + u64 *remain_variable_storage_size, + u64 *max_variable_size) +{ + struct smm_variable_query_info *mm_query_info; + efi_uintn_t payload_size; + efi_status_t ret; + u8 *comm_buf; + + if (!max_variable_storage_size || + !remain_variable_storage_size || + !max_variable_size || !attributes) + return EFI_INVALID_PARAMETER; + + payload_size = sizeof(*mm_query_info); + comm_buf = setup_mm_hdr((void **)&mm_query_info, payload_size, + SMM_VARIABLE_FUNCTION_QUERY_VARIABLE_INFO, + &ret); + if (!comm_buf) + goto out; + + mm_query_info->attr = attributes; + ret = mm_communicate(comm_buf, payload_size); + if (ret != EFI_SUCCESS) + goto out; + *max_variable_storage_size = mm_query_info->max_variable_storage; + *remain_variable_storage_size = + mm_query_info->remaining_variable_storage; + *max_variable_size = mm_query_info->max_variable_size; + +out: + free(comm_buf); + return ret; +} + +/** + * efi_query_variable_info() - get information about EFI variables + * + * This function implements the QueryVariableInfo() runtime service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @attributes: bitmask to select variables to be + * queried + * @maximum_variable_storage_size: maximum size of storage area for the + * selected variable types + * @remaining_variable_storage_size: remaining size of storage are for the + * selected variable types + * @maximum_variable_size: maximum size of a variable of the + * selected type + * Return: status code + */ +efi_status_t EFIAPI __efi_runtime +efi_query_variable_info_runtime(u32 attributes, u64 *max_variable_storage_size, + u64 *remain_variable_storage_size, + u64 *max_variable_size) +{ + return EFI_UNSUPPORTED; +} + +/** + * efi_set_variable_runtime() - runtime implementation of SetVariable() + * + * @variable_name: name of the variable + * @guid: vendor GUID + * @attributes: attributes of the variable + * @data_size: size of the buffer with the variable value + * @data: buffer with the variable value + * Return: status code + */ +static efi_status_t __efi_runtime EFIAPI +efi_set_variable_runtime(u16 *variable_name, const efi_guid_t *guid, + u32 attributes, efi_uintn_t data_size, + const void *data) +{ + return EFI_UNSUPPORTED; +} + +/** + * efi_variables_boot_exit_notify() - notify ExitBootServices() is called + */ +void efi_variables_boot_exit_notify(void) +{ + efi_status_t ret; + u8 *comm_buf; + loff_t len; + struct efi_var_file *var_buf; + + comm_buf = setup_mm_hdr(NULL, 0, + SMM_VARIABLE_FUNCTION_EXIT_BOOT_SERVICE, &ret); + if (comm_buf) + ret = mm_communicate(comm_buf, 0); + else + ret = EFI_NOT_FOUND; + + if (ret != EFI_SUCCESS) + log_err("Unable to notify the MM partition for ExitBootServices\n"); + free(comm_buf); + + ret = efi_var_collect(&var_buf, &len, EFI_VARIABLE_RUNTIME_ACCESS); + if (ret != EFI_SUCCESS) + log_err("Can't populate EFI variables. No runtime variables will be available\n"); + else + efi_var_buf_update(var_buf); + free(var_buf); + + /* Update runtime service table */ + efi_runtime_services.query_variable_info = + efi_query_variable_info_runtime; + efi_runtime_services.get_variable = efi_get_variable_runtime; + efi_runtime_services.get_next_variable_name = + efi_get_next_variable_name_runtime; + efi_runtime_services.set_variable = efi_set_variable_runtime; + efi_update_table_header_crc32(&efi_runtime_services.hdr); +} + +/** + * efi_init_variables() - initialize variable services + * + * Return: status code + */ +efi_status_t efi_init_variables(void) +{ + efi_status_t ret; + + /* Create a cached copy of the variables that will be enabled on ExitBootServices() */ + ret = efi_var_mem_init(); + if (ret != EFI_SUCCESS) + return ret; + + ret = get_max_payload(&max_payload_size); + if (ret != EFI_SUCCESS) + return ret; + + max_buffer_size = MM_COMMUNICATE_HEADER_SIZE + + MM_VARIABLE_COMMUNICATE_SIZE + + max_payload_size; + + ret = efi_init_secure_state(); + if (ret != EFI_SUCCESS) + return ret; + + return EFI_SUCCESS; +} diff --git a/lib/efi_loader/efi_watchdog.c b/lib/efi_loader/efi_watchdog.c new file mode 100644 index 00000000000..c21d8086074 --- /dev/null +++ b/lib/efi_loader/efi_watchdog.c @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI watchdog + * + * Copyright (c) 2017 Heinrich Schuchardt + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <efi_loader.h> + +/* Conversion factor from seconds to multiples of 100ns */ +#define EFI_SECONDS_TO_100NS 10000000ULL + +static struct efi_event *watchdog_timer_event; + +/** + * efi_watchdog_timer_notify() - resets system upon watchdog event + * + * Reset the system when the watchdog event is notified. + * + * @event: the watchdog event + * @context: not used + */ +static void EFIAPI efi_watchdog_timer_notify(struct efi_event *event, + void *context) +{ + EFI_ENTRY("%p, %p", event, context); + + printf("\nEFI: Watchdog timeout\n"); + do_reset(NULL, 0, 0, NULL); + + EFI_EXIT(EFI_UNSUPPORTED); +} + +/** + * efi_set_watchdog() - resets the watchdog timer + * + * This function is used by the SetWatchdogTimer service. + * + * @timeout: seconds before reset by watchdog + * Return: status code + */ +efi_status_t efi_set_watchdog(unsigned long timeout) +{ + efi_status_t r; + + if (timeout) + /* Reset watchdog */ + r = efi_set_timer(watchdog_timer_event, EFI_TIMER_RELATIVE, + EFI_SECONDS_TO_100NS * timeout); + else + /* Deactivate watchdog */ + r = efi_set_timer(watchdog_timer_event, EFI_TIMER_STOP, 0); + return r; +} + +/** + * efi_watchdog_register() - initializes the EFI watchdog + * + * This function is called by efi_init_obj_list(). + * + * Return: status code + */ +efi_status_t efi_watchdog_register(void) +{ + efi_status_t r; + + /* + * Create a timer event. + */ + r = efi_create_event(EVT_TIMER | EVT_NOTIFY_SIGNAL, TPL_CALLBACK, + efi_watchdog_timer_notify, NULL, NULL, + &watchdog_timer_event); + if (r != EFI_SUCCESS) { + printf("ERROR: Failed to register watchdog event\n"); + return r; + } + + return EFI_SUCCESS; +} diff --git a/lib/efi_loader/elf_efi.ldsi b/lib/efi_loader/elf_efi.ldsi new file mode 100644 index 00000000000..4fa5ca43872 --- /dev/null +++ b/lib/efi_loader/elf_efi.ldsi @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * U-Boot EFI linker script include + * + * Modified from elf_aarch64_efi.lds in gnu-efi + */ + +PHDRS +{ + data PT_LOAD FLAGS(3); /* SHF_WRITE | SHF_ALLOC */ +} + +ENTRY(_start) +SECTIONS +{ + .text 0x0 : { + _text = .; + *(.text.head) + *(.text) + *(.text.*) + *(.gnu.linkonce.t.*) + *(.srodata) + *(.rodata*) + } + . = ALIGN(16); + .dynamic : { *(.dynamic) } + . = ALIGN(512); + .rela.dyn : { *(.rela.dyn) } + .rela.plt : { *(.rela.plt) } + .rela.got : { *(.rela.got) } + .rela.data : { *(.rela.data) *(.rela.data*) } + . = ALIGN(4096); + _etext = .; + _text_size = . - _text; + .data : { + _data = .; + *(.sdata) + *(.data) + *(.data1) + *(.data.*) + *(.got.plt) + *(.got) + + /* + * The EFI loader doesn't seem to like a .bss section, so we + * stick it all into .data: + */ + . = ALIGN(16); + _bss = .; + *(.sbss) + *(.scommon) + *(.dynbss) + *(.bss) + *(.bss.*) + *(COMMON) + . = ALIGN(512); + _bss_end = .; + _edata = .; + } :data + _data_size = _edata - _data; + + . = ALIGN(4096); + .dynsym : { *(.dynsym) } + . = ALIGN(4096); + .dynstr : { *(.dynstr) } + . = ALIGN(4096); + .note.gnu.build-id : { *(.note.gnu.build-id) } + /DISCARD/ : { + *(.rel.reloc) + *(.eh_frame) + *(.note.GNU-stack) + } + .comment 0 : { *(.comment) } +} diff --git a/lib/efi_loader/helloworld.c b/lib/efi_loader/helloworld.c new file mode 100644 index 00000000000..d10a5229f74 --- /dev/null +++ b/lib/efi_loader/helloworld.c @@ -0,0 +1,287 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Hello world EFI application + * + * Copyright (c) 2016 Google, Inc + * Written by Simon Glass <sjg@chromium.org> + * + * Copyright 2020, Heinrich Schuchardt <xypron.glpk@gmx.de> + * + * This test program is used to test the invocation of an EFI application. + * It writes + * + * * a greeting + * * the firmware's UEFI version + * * the installed configuration tables + * * the boot device's device path and the file path + * + * to the console. + */ + +#include <efi_api.h> + +static const efi_guid_t loaded_image_guid = EFI_LOADED_IMAGE_PROTOCOL_GUID; +static const efi_guid_t device_path_to_text_protocol_guid = + EFI_DEVICE_PATH_TO_TEXT_PROTOCOL_GUID; +static const efi_guid_t device_path_guid = EFI_DEVICE_PATH_PROTOCOL_GUID; +static const efi_guid_t fdt_guid = EFI_FDT_GUID; +static const efi_guid_t acpi_guid = EFI_ACPI_TABLE_GUID; +static const efi_guid_t smbios_guid = SMBIOS_TABLE_GUID; + +static struct efi_system_table *systable; +static struct efi_boot_services *boottime; +static struct efi_simple_text_output_protocol *con_out; + +/* + * Print an unsigned 32bit value as decimal number to an u16 string + * + * @value: value to be printed + * @buf: pointer to buffer address + * on return position of terminating zero word + */ +static void uint2dec(u32 value, u16 **buf) +{ + u16 *pos = *buf; + int i; + u16 c; + u64 f; + + /* + * Increment by .5 and multiply with + * (2 << 60) / 1,000,000,000 = 0x44B82FA0.9B5A52CC + * to move the first digit to bit 60-63. + */ + f = 0x225C17D0; + f += (0x9B5A52DULL * value) >> 28; + f += 0x44B82FA0ULL * value; + + for (i = 0; i < 10; ++i) { + /* Write current digit */ + c = f >> 60; + if (c || pos != *buf) + *pos++ = c + '0'; + /* Eliminate current digit */ + f &= 0xfffffffffffffff; + /* Get next digit */ + f *= 0xaULL; + } + if (pos == *buf) + *pos++ = '0'; + *pos = 0; + *buf = pos; +} + +/** + * Print an unsigned 32bit value as hexadecimal number to an u16 string + * + * @value: value to be printed + * @buf: pointer to buffer address + * on return position of terminating zero word + */ +static void uint2hex(u32 value, u16 **buf) +{ + u16 *pos = *buf; + int i; + u16 c; + + for (i = 0; i < 8; ++i) { + /* Write current digit */ + c = value >> 28; + value <<= 4; + if (c < 10) + c += '0'; + else + c += 'a' - 10; + *pos++ = c; + } + *pos = 0; + *buf = pos; +} + +/** + * print_uefi_revision() - print UEFI revision number + */ +static void print_uefi_revision(void) +{ + u16 rev[13] = {0}; + u16 *buf = rev; + u16 digit; + + uint2dec(systable->hdr.revision >> 16, &buf); + *buf++ = '.'; + uint2dec(systable->hdr.revision & 0xffff, &buf); + + /* Minor revision is only to be shown if non-zero */ + digit = *--buf; + if (digit == '0') { + *buf = 0; + } else { + *buf++ = '.'; + *buf = digit; + } + + con_out->output_string(con_out, u"Running on UEFI "); + con_out->output_string(con_out, rev); + con_out->output_string(con_out, u"\r\n"); + + con_out->output_string(con_out, u"Firmware vendor: "); + con_out->output_string(con_out, systable->fw_vendor); + con_out->output_string(con_out, u"\r\n"); + + buf = rev; + uint2hex(systable->fw_revision, &buf); + con_out->output_string(con_out, u"Firmware revision: "); + con_out->output_string(con_out, rev); + con_out->output_string(con_out, u"\r\n"); +} + +/** + * print_config_tables() - print configuration tables + */ +static void print_config_tables(void) +{ + efi_uintn_t i; + + /* Find configuration tables */ + for (i = 0; i < systable->nr_tables; ++i) { + if (!memcmp(&systable->tables[i].guid, &fdt_guid, + sizeof(efi_guid_t))) + con_out->output_string + (con_out, u"Have device tree\r\n"); + if (!memcmp(&systable->tables[i].guid, &acpi_guid, + sizeof(efi_guid_t))) + con_out->output_string + (con_out, u"Have ACPI 2.0 table\r\n"); + if (!memcmp(&systable->tables[i].guid, &smbios_guid, + sizeof(efi_guid_t))) + con_out->output_string + (con_out, u"Have SMBIOS table\r\n"); + } +} + +/** + * print_load_options() - print load options + * + * @systable: system table + * @con_out: simple text output protocol + */ +static void print_load_options(struct efi_loaded_image *loaded_image) +{ + /* Output the load options */ + con_out->output_string(con_out, u"Load options: "); + if (loaded_image->load_options_size && loaded_image->load_options) + con_out->output_string(con_out, + (u16 *)loaded_image->load_options); + else + con_out->output_string(con_out, u"<none>"); + con_out->output_string(con_out, u"\r\n"); +} + +/** + * print_device_path() - print device path + * + * @device_path: device path to print + * @dp2txt: device path to text protocol + */ +static +efi_status_t print_device_path(struct efi_device_path *device_path, + struct efi_device_path_to_text_protocol *dp2txt) +{ + u16 *string; + efi_status_t ret; + + if (!device_path) { + con_out->output_string(con_out, u"<none>\r\n"); + return EFI_SUCCESS; + } + + string = dp2txt->convert_device_path_to_text(device_path, true, false); + if (!string) { + con_out->output_string + (con_out, u"Cannot convert device path to text\r\n"); + return EFI_OUT_OF_RESOURCES; + } + con_out->output_string(con_out, string); + con_out->output_string(con_out, u"\r\n"); + ret = boottime->free_pool(string); + if (ret != EFI_SUCCESS) { + con_out->output_string(con_out, u"Cannot free pool memory\r\n"); + return ret; + } + return EFI_SUCCESS; +} + +/** + * efi_main() - entry point of the EFI application. + * + * @handle: handle of the loaded image + * @systab: system table + * Return: status code + */ +efi_status_t EFIAPI efi_main(efi_handle_t handle, + struct efi_system_table *systab) +{ + struct efi_loaded_image *loaded_image; + struct efi_device_path_to_text_protocol *device_path_to_text; + struct efi_device_path *device_path; + efi_status_t ret; + + systable = systab; + boottime = systable->boottime; + con_out = systable->con_out; + + /* UEFI requires CR LF */ + con_out->output_string(con_out, u"Hello, world!\r\n"); + + print_uefi_revision(); + print_config_tables(); + + /* Get the loaded image protocol */ + ret = boottime->open_protocol(handle, &loaded_image_guid, + (void **)&loaded_image, NULL, NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL); + + if (ret != EFI_SUCCESS) { + con_out->output_string + (con_out, u"Cannot open loaded image protocol\r\n"); + goto out; + } + print_load_options(loaded_image); + + /* Get the device path to text protocol */ + ret = boottime->locate_protocol(&device_path_to_text_protocol_guid, + NULL, (void **)&device_path_to_text); + if (ret != EFI_SUCCESS) { + con_out->output_string + (con_out, u"Cannot open device path to text protocol\r\n"); + goto out; + } + con_out->output_string(con_out, u"File path: "); + ret = print_device_path(loaded_image->file_path, device_path_to_text); + if (ret != EFI_SUCCESS) + goto out; + if (!loaded_image->device_handle) { + con_out->output_string + (con_out, u"Missing device handle\r\n"); + goto out; + } + ret = boottime->open_protocol(loaded_image->device_handle, + &device_path_guid, + (void **)&device_path, NULL, NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if (ret != EFI_SUCCESS) { + con_out->output_string + (con_out, u"Missing device path for device handle\r\n"); + goto out; + } + con_out->output_string(con_out, u"Boot device: "); + ret = print_device_path(device_path, device_path_to_text); + if (ret != EFI_SUCCESS) + goto out; + +out: + boottime->exit(handle, ret, 0, NULL); + + /* We should never arrive here */ + return ret; +} diff --git a/lib/efi_loader/initrddump.c b/lib/efi_loader/initrddump.c new file mode 100644 index 00000000000..615119043d1 --- /dev/null +++ b/lib/efi_loader/initrddump.c @@ -0,0 +1,514 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2020, Heinrich Schuchardt <xypron.glpk@gmx.de> + * + * initrddump.efi saves the initial RAM disk provided via the + * EFI_LOAD_FILE2_PROTOCOL. + * + * Specifying 'nocolor' as load option data suppresses colored output and + * clearing of the screen. + */ + +#include <efi_api.h> +#include <efi_load_initrd.h> + +#define BUFFER_SIZE 64 +#define ESC 0x17 + +#define efi_size_in_pages(size) (((size) + EFI_PAGE_MASK) >> EFI_PAGE_SHIFT) + +static struct efi_system_table *systable; +static struct efi_boot_services *bs; +static struct efi_simple_text_output_protocol *cerr; +static struct efi_simple_text_output_protocol *cout; +static struct efi_simple_text_input_protocol *cin; +static const efi_guid_t loaded_image_guid = EFI_LOADED_IMAGE_PROTOCOL_GUID; +static const efi_guid_t guid_simple_file_system_protocol = + EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID; +static const efi_guid_t load_file2_guid = EFI_LOAD_FILE2_PROTOCOL_GUID; +static efi_handle_t handle; +static bool nocolor; + +/* + * Device path defined by Linux to identify the handle providing the + * EFI_LOAD_FILE2_PROTOCOL used for loading the initial ramdisk. + */ +static const struct efi_lo_dp_prefix initrd_dp = { + .vendor = { + { + DEVICE_PATH_TYPE_MEDIA_DEVICE, + DEVICE_PATH_SUB_TYPE_VENDOR_PATH, + sizeof(initrd_dp.vendor), + }, + EFI_INITRD_MEDIA_GUID, + }, + .end = { + DEVICE_PATH_TYPE_END, + DEVICE_PATH_SUB_TYPE_END, + sizeof(initrd_dp.end), + } +}; + +/** + * color() - set foreground color + * + * @color: foreground color + */ +static void color(u8 color) +{ + if (!nocolor) + cout->set_attribute(cout, color | EFI_BACKGROUND_BLACK); +} + +/** + * print() - print string + * + * @string: text + */ +static void print(u16 *string) +{ + cout->output_string(cout, string); +} + +/** + * cls() - clear screen + */ +static void cls(void) +{ + if (nocolor) + print(u"\r\n"); + else + cout->clear_screen(cout); +} + +/** + * error() - print error string + * + * @string: error text + */ +static void error(u16 *string) +{ + color(EFI_LIGHTRED); + print(string); + color(EFI_LIGHTBLUE); +} + +/* + * printx() - print hexadecimal number + * + * @val: value to print; + * @prec: minimum number of digits to print + */ +static void printx(u64 val, u32 prec) +{ + int i; + u16 c; + u16 buf[16]; + u16 *pos = buf; + + for (i = 2 * sizeof(val) - 1; i >= 0; --i) { + c = (val >> (4 * i)) & 0x0f; + if (c || pos != buf || !i || i < prec) { + c += '0'; + if (c > '9') + c += 'a' - '9' - 1; + *pos++ = c; + } + } + *pos = 0; + print(buf); +} + +/** + * efi_drain_input() - drain console input + */ +static void efi_drain_input(void) +{ + cin->reset(cin, true); +} + +/** + * efi_input_yn() - get answer to yes/no question + * + * Return: + * y or Y + * EFI_SUCCESS + * n or N + * EFI_ACCESS_DENIED + * ESC + * EFI_ABORTED + */ +static efi_status_t efi_input_yn(void) +{ + struct efi_input_key key = {0}; + efi_uintn_t index; + efi_status_t ret; + + for (;;) { + ret = bs->wait_for_event(1, &cin->wait_for_key, &index); + if (ret != EFI_SUCCESS) + continue; + ret = cin->read_key_stroke(cin, &key); + if (ret != EFI_SUCCESS) + continue; + switch (key.scan_code) { + case 0x17: /* Escape */ + return EFI_ABORTED; + default: + break; + } + /* Convert to lower case */ + switch (key.unicode_char | 0x20) { + case 'y': + return EFI_SUCCESS; + case 'n': + return EFI_ACCESS_DENIED; + default: + break; + } + } +} + +/** + * efi_input() - read string from console + * + * @buffer: input buffer + * @buffer_size: buffer size + * Return: status code + */ +static efi_status_t efi_input(u16 *buffer, efi_uintn_t buffer_size) +{ + struct efi_input_key key = {0}; + efi_uintn_t index; + efi_uintn_t pos = 0; + u16 outbuf[2] = u" "; + efi_status_t ret; + + *buffer = 0; + for (;;) { + ret = bs->wait_for_event(1, &cin->wait_for_key, &index); + if (ret != EFI_SUCCESS) + continue; + ret = cin->read_key_stroke(cin, &key); + if (ret != EFI_SUCCESS) + continue; + switch (key.scan_code) { + case 0x17: /* Escape */ + print(u"\r\nAborted\r\n"); + return EFI_ABORTED; + default: + break; + } + switch (key.unicode_char) { + case 0x08: /* Backspace */ + if (pos) { + buffer[pos--] = 0; + print(u"\b \b"); + } + break; + case 0x0a: /* Linefeed */ + case 0x0d: /* Carriage return */ + print(u"\r\n"); + return EFI_SUCCESS; + default: + break; + } + /* Ignore surrogate codes */ + if (key.unicode_char >= 0xD800 && key.unicode_char <= 0xDBFF) + continue; + if (key.unicode_char >= 0x20 && + pos < buffer_size - 1) { + *outbuf = key.unicode_char; + buffer[pos++] = key.unicode_char; + buffer[pos] = 0; + print(outbuf); + } + } +} + +/** + * skip_whitespace() - skip over leading whitespace + * + * @pos: UTF-16 string + * Return: pointer to first non-whitespace + */ +static u16 *skip_whitespace(u16 *pos) +{ + for (; *pos && *pos <= 0x20; ++pos) + ; + return pos; +} + +/** + * starts_with() - check if @string starts with @keyword + * + * @string: string to search for keyword + * @keyword: keyword to be searched + * Return: true if @string starts with the keyword + */ +static bool starts_with(u16 *string, u16 *keyword) +{ + if (!string || !keyword) + return false; + + for (; *keyword; ++string, ++keyword) { + if (*string != *keyword) + return false; + } + return true; +} + +/** + * do_help() - print help + */ +static void do_help(void) +{ + error(u"load - show length and CRC32 of initial RAM disk\r\n"); + error(u"save <initrd> - save initial RAM disk to file\r\n"); + error(u"exit - exit the shell\r\n"); +} + +/** + * get_initrd() - read initial RAM disk via EFI_LOAD_FILE2_PROTOCOL + * + * @initrd: on return buffer with initial RAM disk + * @initrd_size: size of initial RAM disk + * Return: status code + */ +static efi_status_t get_initrd(void **initrd, efi_uintn_t *initrd_size) +{ + struct efi_device_path *dp = (struct efi_device_path *)&initrd_dp; + struct efi_load_file_protocol *load_file2_prot; + u64 buffer; + efi_handle_t handle; + efi_status_t ret; + + *initrd = NULL; + *initrd_size = 0; + ret = bs->locate_device_path(&load_file2_guid, &dp, &handle); + if (ret != EFI_SUCCESS) { + error(u"Load File2 protocol not found\r\n"); + return ret; + } + ret = bs->open_protocol(handle, &load_file2_guid, + (void **)&load_file2_prot, NULL, NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL); + ret = load_file2_prot->load_file(load_file2_prot, dp, false, + initrd_size, NULL); + if (ret != EFI_BUFFER_TOO_SMALL) { + error(u"Load File2 protocol does not provide file length\r\n"); + return EFI_LOAD_ERROR; + } + ret = bs->allocate_pages(EFI_ALLOCATE_ANY_PAGES, EFI_LOADER_DATA, + efi_size_in_pages(*initrd_size), &buffer); + if (ret != EFI_SUCCESS) { + error(u"Out of memory\r\n"); + return ret; + } + *initrd = (void *)(uintptr_t)buffer; + ret = load_file2_prot->load_file(load_file2_prot, dp, false, + initrd_size, *initrd); + if (ret != EFI_SUCCESS) { + error(u"Load File2 protocol failed to provide file\r\n"); + bs->free_pages(buffer, efi_size_in_pages(*initrd_size)); + return EFI_LOAD_ERROR; + } + return ret; +} + +/** + * do_load() - load initial RAM disk and display CRC32 and length + * + * @filename: file name + * Return: status code + */ +static efi_status_t do_load(void) +{ + void *initrd; + efi_uintn_t initrd_size; + u32 crc32; + efi_uintn_t ret; + + ret = get_initrd(&initrd, &initrd_size); + if (ret != EFI_SUCCESS) + return ret; + print(u"length: 0x"); + printx(initrd_size, 1); + print(u"\r\n"); + + ret = bs->calculate_crc32(initrd, initrd_size, &crc32); + if (ret != EFI_SUCCESS) { + error(u"Calculating CRC32 failed\r\n"); + return EFI_LOAD_ERROR; + } + print(u"crc32: 0x"); + printx(crc32, 8); + print(u"\r\n"); + + return EFI_SUCCESS; +} + +/** + * do_save() - save initial RAM disk + * + * @filename: file name + * Return: status code + */ +static efi_status_t do_save(u16 *filename) +{ + struct efi_loaded_image *loaded_image; + struct efi_simple_file_system_protocol *file_system; + struct efi_file_handle *root, *file; + void *initrd; + efi_uintn_t initrd_size; + efi_uintn_t ret; + + ret = get_initrd(&initrd, &initrd_size); + if (ret != EFI_SUCCESS) + return ret; + + filename = skip_whitespace(filename); + + ret = bs->open_protocol(handle, &loaded_image_guid, + (void **)&loaded_image, NULL, NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if (ret != EFI_SUCCESS) { + error(u"Loaded image protocol not found\r\n"); + goto out; + } + + /* Open the simple file system protocol */ + ret = bs->open_protocol(loaded_image->device_handle, + &guid_simple_file_system_protocol, + (void **)&file_system, NULL, NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if (ret != EFI_SUCCESS) { + error(u"Failed to open simple file system protocol\r\n"); + goto out; + } + + /* Open volume */ + ret = file_system->open_volume(file_system, &root); + if (ret != EFI_SUCCESS) { + error(u"Failed to open volume\r\n"); + goto out; + } + /* Check if file already exists */ + ret = root->open(root, &file, filename, EFI_FILE_MODE_READ, 0); + if (ret == EFI_SUCCESS) { + file->close(file); + efi_drain_input(); + print(u"Overwrite existing file (y/n)? "); + ret = efi_input_yn(); + print(u"\r\n"); + if (ret != EFI_SUCCESS) { + root->close(root); + error(u"Aborted by user\r\n"); + goto out; + } + } + + /* Create file */ + ret = root->open(root, &file, filename, + EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | + EFI_FILE_MODE_CREATE, EFI_FILE_ARCHIVE); + if (ret == EFI_SUCCESS) { + /* Write file */ + ret = file->write(file, &initrd_size, initrd); + if (ret != EFI_SUCCESS) { + error(u"Failed to write file\r\n"); + } else { + print(filename); + print(u" written\r\n"); + } + file->close(file); + } else { + error(u"Failed to open file\r\n"); + } + root->close(root); + +out: + if (initrd) + bs->free_pages((uintptr_t)initrd, + efi_size_in_pages(initrd_size)); + return ret; +} + +/** + * get_load_options() - get load options + * + * Return: load options or NULL + */ +static u16 *get_load_options(void) +{ + efi_status_t ret; + struct efi_loaded_image *loaded_image; + + ret = bs->open_protocol(handle, &loaded_image_guid, + (void **)&loaded_image, NULL, NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if (ret != EFI_SUCCESS) { + error(u"Loaded image protocol not found\r\n"); + return NULL; + } + + if (!loaded_image->load_options_size || !loaded_image->load_options) + return NULL; + + return loaded_image->load_options; +} + +/** + * efi_main() - entry point of the EFI application. + * + * @handle: handle of the loaded image + * @systab: system table + * Return: status code + */ +efi_status_t EFIAPI efi_main(efi_handle_t image_handle, + struct efi_system_table *systab) +{ + u16 *load_options; + + handle = image_handle; + systable = systab; + cerr = systable->std_err; + cout = systable->con_out; + cin = systable->con_in; + bs = systable->boottime; + load_options = get_load_options(); + + if (starts_with(load_options, u"nocolor")) + nocolor = true; + + color(EFI_WHITE); + cls(); + print(u"INITRD Dump\r\n===========\r\n\r\n"); + color(EFI_LIGHTBLUE); + + for (;;) { + u16 command[BUFFER_SIZE]; + u16 *pos; + efi_uintn_t ret; + + efi_drain_input(); + print(u"=> "); + ret = efi_input(command, sizeof(command)); + if (ret == EFI_ABORTED) + break; + pos = skip_whitespace(command); + if (starts_with(pos, u"exit")) + break; + else if (starts_with(pos, u"load")) + do_load(); + else if (starts_with(pos, u"save ")) + do_save(pos + 5); + else + do_help(); + } + + color(EFI_LIGHTGRAY); + cls(); + + return EFI_SUCCESS; +} diff --git a/lib/efi_loader/smbiosdump.c b/lib/efi_loader/smbiosdump.c new file mode 100644 index 00000000000..d7f2ba30a95 --- /dev/null +++ b/lib/efi_loader/smbiosdump.c @@ -0,0 +1,622 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2023, Heinrich Schuchardt <heinrich.schuchardt@canonical.com> + * + * smbiosdump.efi saves the SMBIOS table as file. + * + * Specifying 'nocolor' as load option data suppresses colored output and + * clearing of the screen. + */ + +#include <efi_api.h> +#include <part.h> +#include <smbios.h> +#include <string.h> + +#define BUFFER_SIZE 64 + +static struct efi_simple_text_output_protocol *cerr; +static struct efi_simple_text_output_protocol *cout; +static struct efi_simple_text_input_protocol *cin; +static struct efi_boot_services *bs; +static efi_handle_t handle; +static struct efi_system_table *systable; +static const efi_guid_t smbios_guid = SMBIOS_TABLE_GUID; +static const efi_guid_t smbios3_guid = SMBIOS3_TABLE_GUID; +static const efi_guid_t loaded_image_guid = EFI_LOADED_IMAGE_PROTOCOL_GUID; +static const efi_guid_t guid_simple_file_system_protocol = + EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID; +static const efi_guid_t efi_system_partition_guid = PARTITION_SYSTEM_GUID; +static bool nocolor; + +/** + * color() - set foreground color + * + * @color: foreground color + */ +static void color(u8 color) +{ + if (!nocolor) + cout->set_attribute(cout, color | EFI_BACKGROUND_BLACK); +} + +/** + * print() - print string + * + * @string: text + */ +static void print(u16 *string) +{ + cout->output_string(cout, string); +} + +/** + * cls() - clear screen + */ +static void cls(void) +{ + if (nocolor) + print(u"\r\n"); + else + cout->clear_screen(cout); +} + +/** + * error() - print error string + * + * @string: error text + */ +static void error(u16 *string) +{ + color(EFI_LIGHTRED); + print(string); + color(EFI_LIGHTBLUE); +} + +/** + * efi_input_yn() - get answer to yes/no question + * + * Return: + * y or Y + * EFI_SUCCESS + * n or N + * EFI_ACCESS_DENIED + * ESC + * EFI_ABORTED + */ +static efi_status_t efi_input_yn(void) +{ + struct efi_input_key key = {0}; + efi_uintn_t index; + efi_status_t ret; + + /* Drain the console input */ + ret = cin->reset(cin, true); + for (;;) { + ret = bs->wait_for_event(1, &cin->wait_for_key, &index); + if (ret != EFI_SUCCESS) + continue; + ret = cin->read_key_stroke(cin, &key); + if (ret != EFI_SUCCESS) + continue; + switch (key.scan_code) { + case 0x17: /* Escape */ + return EFI_ABORTED; + default: + break; + } + /* Convert to lower case */ + switch (key.unicode_char | 0x20) { + case 'y': + return EFI_SUCCESS; + case 'n': + return EFI_ACCESS_DENIED; + default: + break; + } + } +} + +/** + * efi_input() - read string from console + * + * @buffer: input buffer + * @buffer_size: buffer size + * Return: status code + */ +static efi_status_t efi_input(u16 *buffer, efi_uintn_t buffer_size) +{ + struct efi_input_key key = {0}; + efi_uintn_t index; + efi_uintn_t pos = 0; + u16 outbuf[2] = u" "; + efi_status_t ret; + + /* Drain the console input */ + ret = cin->reset(cin, true); + *buffer = 0; + for (;;) { + ret = bs->wait_for_event(1, &cin->wait_for_key, &index); + if (ret != EFI_SUCCESS) + continue; + ret = cin->read_key_stroke(cin, &key); + if (ret != EFI_SUCCESS) + continue; + switch (key.scan_code) { + case 0x17: /* Escape */ + print(u"\r\nAborted\r\n"); + return EFI_ABORTED; + default: + break; + } + switch (key.unicode_char) { + case 0x08: /* Backspace */ + if (pos) { + buffer[pos--] = 0; + print(u"\b \b"); + } + break; + case 0x0a: /* Linefeed */ + case 0x0d: /* Carriage return */ + print(u"\r\n"); + return EFI_SUCCESS; + default: + break; + } + /* Ignore surrogate codes */ + if (key.unicode_char >= 0xD800 && key.unicode_char <= 0xDBFF) + continue; + if (key.unicode_char >= 0x20 && + pos < buffer_size - 1) { + *outbuf = key.unicode_char; + buffer[pos++] = key.unicode_char; + buffer[pos] = 0; + print(outbuf); + } + } +} + +/** + * skip_whitespace() - skip over leading whitespace + * + * @pos: UTF-16 string + * Return: pointer to first non-whitespace + */ +static u16 *skip_whitespace(u16 *pos) +{ + for (; *pos && *pos <= 0x20; ++pos) + ; + return pos; +} + +/** + * starts_with() - check if @string starts with @keyword + * + * @string: string to search for keyword + * @keyword: keyword to be searched + * Return: true fi @string starts with the keyword + */ +static bool starts_with(u16 *string, u16 *keyword) +{ + if (!string || !keyword) + return NULL; + + for (; *keyword; ++string, ++keyword) { + if (*string != *keyword) + return false; + } + return true; +} + +/** + * open_file_system() - open simple file system protocol + * + * file_system: interface of the simple file system protocol + * Return: status code + */ +static efi_status_t +open_file_system(struct efi_simple_file_system_protocol **file_system) +{ + struct efi_loaded_image *loaded_image; + efi_status_t ret; + efi_handle_t *handle_buffer = NULL; + efi_uintn_t count; + + ret = bs->open_protocol(handle, &loaded_image_guid, + (void **)&loaded_image, NULL, NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if (ret != EFI_SUCCESS) { + error(u"Loaded image protocol not found\r\n"); + return ret; + } + + /* Open the simple file system protocol on the same partition */ + ret = bs->open_protocol(loaded_image->device_handle, + &guid_simple_file_system_protocol, + (void **)file_system, NULL, NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if (ret == EFI_SUCCESS) + return ret; + + /* Open the simple file system protocol on the UEFI system partition */ + ret = bs->locate_handle_buffer(BY_PROTOCOL, &efi_system_partition_guid, + NULL, &count, &handle_buffer); + if (ret == EFI_SUCCESS && handle_buffer) + ret = bs->open_protocol(handle_buffer[0], + &guid_simple_file_system_protocol, + (void **)file_system, NULL, NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if (ret != EFI_SUCCESS) + error(u"Failed to open simple file system protocol\r\n"); + if (handle) + bs->free_pool(handle_buffer); + + return ret; +} + +/** + * do_help() - print help + */ +static void do_help(void) +{ + error(u"check - check SMBIOS table\r\n"); + error(u"save <file> - save SMBIOS table to file\r\n"); + error(u"exit - exit the shell\r\n"); +} + +/** + * get_config_table() - get configuration table + * + * @guid: GUID of the configuration table + * Return: pointer to configuration table or NULL + */ +static void *get_config_table(const efi_guid_t *guid) +{ + size_t i; + + for (i = 0; i < systable->nr_tables; ++i) { + if (!memcmp(guid, &systable->tables[i].guid, 16)) + return systable->tables[i].table; + } + + return NULL; +} + +/** + * checksum() - calculate checksum + * + * @buf: buffer to checksum + * @len: length of buffer + * Return: checksum + */ +static u8 checksum(void *buf, int len) +{ + u8 ret = 0; + + for (u8 *ptr = buf; len; --len, ++ptr) + ret -= *ptr; + + return ret; +} + +/** + * do_check() - check SMBIOS table + * + * Return: status code + */ +static efi_status_t do_check(void) +{ + struct smbios3_entry *smbios3_anchor; + void *table, *table_end; + u32 len; + + smbios3_anchor = get_config_table(&smbios3_guid); + if (smbios3_anchor) { + int r; + + r = memcmp(smbios3_anchor->anchor, "_SM3_", 5); + if (r) { + error(u"Invalid anchor string\n"); + return EFI_LOAD_ERROR; + } + print(u"Found SMBIOS 3 entry point\n"); + if (smbios3_anchor->length != 0x18) { + error(u"Invalid anchor length\n"); + return EFI_LOAD_ERROR; + } + if (checksum(smbios3_anchor, smbios3_anchor->length)) { + error(u"Invalid anchor checksum\n"); + return EFI_LOAD_ERROR; + } + table = (void *)(uintptr_t)smbios3_anchor->struct_table_address; + len = smbios3_anchor->table_maximum_size; + } else { + struct smbios_entry *smbios_anchor; + int r; + + smbios_anchor = get_config_table(&smbios_guid); + if (!smbios_anchor) { + error(u"No SMBIOS table\n"); + return EFI_NOT_FOUND; + } + r = memcmp(smbios_anchor->anchor, "_SM_", 4); + if (r) { + error(u"Invalid anchor string\n"); + return EFI_LOAD_ERROR; + } + print(u"Found SMBIOS 2.1 entry point\n"); + if (smbios_anchor->length != 0x1f) { + error(u"Invalid anchor length\n"); + return EFI_LOAD_ERROR; + } + if (checksum(smbios_anchor, smbios_anchor->length)) { + error(u"Invalid anchor checksum\n"); + return EFI_LOAD_ERROR; + } + r = memcmp(smbios_anchor->intermediate_anchor, "_DMI_", 5); + if (r) { + error(u"Invalid intermediate anchor string\n"); + return EFI_LOAD_ERROR; + } + if (checksum(&smbios_anchor->intermediate_anchor, 0xf)) { + error(u"Invalid intermediate anchor checksum\n"); + return EFI_LOAD_ERROR; + } + table = (void *)(uintptr_t)smbios_anchor->struct_table_address; + len = smbios_anchor->struct_table_length; + } + + table_end = (void *)((u8 *)table + len); + for (struct smbios_header *pos = table; ;) { + u8 *str = (u8 *)pos + pos->length; + + if (!*str) + ++str; + while (*str) { + for (; *str; ++str) { + if ((void *)str >= table_end) { + error(u"Structure table length exceeded\n"); + return EFI_LOAD_ERROR; + } + } + ++str; + } + ++str; + if ((void *)str > table_end) { + error(u"Structure table length exceeded\n"); + return EFI_LOAD_ERROR; + } + if (pos->type == 0x7f) /* End of table */ + break; + pos = (struct smbios_header *)str; + } + + return EFI_SUCCESS; +} + +/** + * save_file() - save file to EFI system partition + * + * @filename: file name + * @buf: buffer to write + * @size: size of the buffer + */ +static efi_status_t save_file(u16 *filename, void *buf, efi_uintn_t size) +{ + efi_uintn_t ret; + struct efi_simple_file_system_protocol *file_system; + struct efi_file_handle *root, *file; + + ret = open_file_system(&file_system); + if (ret != EFI_SUCCESS) + return ret; + + /* Open volume */ + ret = file_system->open_volume(file_system, &root); + if (ret != EFI_SUCCESS) { + error(u"Failed to open volume\r\n"); + return ret; + } + /* Check if file already exists */ + ret = root->open(root, &file, filename, EFI_FILE_MODE_READ, 0); + if (ret == EFI_SUCCESS) { + file->close(file); + print(u"Overwrite existing file (y/n)? "); + ret = efi_input_yn(); + print(u"\r\n"); + if (ret != EFI_SUCCESS) { + root->close(root); + error(u"Aborted by user\r\n"); + bs->free_pool(buf); + return ret; + } + } + + /* Create file */ + ret = root->open(root, &file, filename, + EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | + EFI_FILE_MODE_CREATE, EFI_FILE_ARCHIVE); + if (ret == EFI_SUCCESS) { + /* Write file */ + ret = file->write(file, &size, buf); + if (ret != EFI_SUCCESS) + error(u"Failed to write file\r\n"); + file->close(file); + } else { + error(u"Failed to open file\r\n"); + } + root->close(root); + + return ret; +} + +/** + * do_save() - save SMBIOS table + * + * @filename: file name + * Return: status code + */ +static efi_status_t do_save(u16 *filename) +{ + struct smbios3_entry *smbios3_anchor; + u8 *buf; + efi_uintn_t size; + efi_uintn_t ret; + + ret = do_check(); + if (ret != EFI_SUCCESS) + return ret; + + smbios3_anchor = get_config_table(&smbios3_guid); + if (smbios3_anchor) { + size = 0x20 + smbios3_anchor->table_maximum_size; + ret = bs->allocate_pool(EFI_LOADER_DATA, size, (void **)&buf); + if (ret != EFI_SUCCESS) { + error(u"Out of memory\n"); + return ret; + } + + memset(buf, 0, size); + memcpy(buf, smbios3_anchor, smbios3_anchor->length); + memcpy(buf + 0x20, + (void *)(uintptr_t)smbios3_anchor->struct_table_address, + smbios3_anchor->table_maximum_size); + + smbios3_anchor = (struct smbios3_entry *)buf; + smbios3_anchor->struct_table_address = 0x20; + smbios3_anchor->checksum += + checksum(smbios3_anchor, smbios3_anchor->length); + } else { + struct smbios_entry *smbios_anchor; + + smbios_anchor = get_config_table(&smbios_guid); + if (!smbios_anchor) { + /* Should not be reached after successful do_check() */ + error(u"No SMBIOS table\n"); + return EFI_NOT_FOUND; + } + + size = 0x20 + smbios_anchor->struct_table_length; + + ret = bs->allocate_pool(EFI_LOADER_DATA, size, (void **)&buf); + if (ret != EFI_SUCCESS) { + error(u"Out of memory\n"); + return ret; + } + + memset(buf, 0, size); + memcpy(buf, smbios_anchor, smbios_anchor->length); + memcpy(buf + 0x20, + (void *)(uintptr_t)smbios_anchor->struct_table_address, + smbios_anchor->struct_table_length); + + smbios_anchor = (struct smbios_entry *)buf; + smbios_anchor->struct_table_address = 0x20; + smbios_anchor->intermediate_checksum += + checksum(&smbios_anchor->intermediate_anchor, 0xf); + smbios_anchor->checksum += + checksum(smbios_anchor, smbios_anchor->length); + } + + filename = skip_whitespace(filename); + + ret = save_file(filename, buf, size); + + if (ret == EFI_SUCCESS) { + print(filename); + print(u" written\r\n"); + } + + bs->free_pool(buf); + + return ret; +} + +/** + * get_load_options() - get load options + * + * Return: load options or NULL + */ +static u16 *get_load_options(void) +{ + efi_status_t ret; + struct efi_loaded_image *loaded_image; + + ret = bs->open_protocol(handle, &loaded_image_guid, + (void **)&loaded_image, NULL, NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if (ret != EFI_SUCCESS) { + error(u"Loaded image protocol not found\r\n"); + return NULL; + } + + if (!loaded_image->load_options_size || !loaded_image->load_options) + return NULL; + + return loaded_image->load_options; +} + +/** + * command_loop - process user commands + */ +static void command_loop(void) +{ + for (;;) { + u16 command[BUFFER_SIZE]; + u16 *pos; + efi_uintn_t ret; + + print(u"=> "); + ret = efi_input(command, sizeof(command)); + if (ret == EFI_ABORTED) + break; + pos = skip_whitespace(command); + if (starts_with(pos, u"exit")) { + break; + } else if (starts_with(pos, u"check")) { + ret = do_check(); + if (ret == EFI_SUCCESS) + print(u"OK\n"); + } else if (starts_with(pos, u"save ")) { + do_save(pos + 5); + } else { + do_help(); + } + } +} + +/** + * efi_main() - entry point of the EFI application. + * + * @handle: handle of the loaded image + * @systab: system table + * Return: status code + */ +efi_status_t EFIAPI efi_main(efi_handle_t image_handle, + struct efi_system_table *systab) +{ + u16 *load_options; + + handle = image_handle; + systable = systab; + cerr = systable->std_err; + cout = systable->con_out; + cin = systable->con_in; + bs = systable->boottime; + load_options = get_load_options(); + + if (starts_with(load_options, u"nocolor")) + nocolor = true; + + color(EFI_WHITE); + cls(); + print(u"SMBIOS Dump\r\n===========\r\n\r\n"); + color(EFI_LIGHTBLUE); + + command_loop(); + + color(EFI_LIGHTGRAY); + cls(); + + return EFI_SUCCESS; +} diff --git a/lib/efi_loader/testapp.c b/lib/efi_loader/testapp.c new file mode 100644 index 00000000000..804ca7e4679 --- /dev/null +++ b/lib/efi_loader/testapp.c @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EFI test application + * + * Copyright 2024 Google LLC + * Written by Simon Glass <sjg@chromium.org> + * + * This test program is used to test the invocation of an EFI application. + * It writes a few messages to the console and then exits boot services + */ + +#include <efi_api.h> + +static const efi_guid_t loaded_image_guid = EFI_LOADED_IMAGE_PROTOCOL_GUID; + +static struct efi_system_table *systable; +static struct efi_boot_services *boottime; +static struct efi_simple_text_output_protocol *con_out; + +/** + * efi_main() - entry point of the EFI application. + * + * @handle: handle of the loaded image + * @systab: system table + * Return: status code + */ +efi_status_t EFIAPI efi_main(efi_handle_t handle, + struct efi_system_table *systab) +{ + struct efi_loaded_image *loaded_image; + efi_status_t ret; + + systable = systab; + boottime = systable->boottime; + con_out = systable->con_out; + + /* Get the loaded image protocol */ + ret = boottime->open_protocol(handle, &loaded_image_guid, + (void **)&loaded_image, NULL, NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if (ret != EFI_SUCCESS) { + con_out->output_string + (con_out, u"Cannot open loaded image protocol\r\n"); + goto out; + } + + /* UEFI requires CR LF */ + con_out->output_string(con_out, u"U-Boot test app for EFI_LOADER\r\n"); + +out: + con_out->output_string(con_out, u"Exiting test app\n"); + ret = boottime->exit(handle, ret, 0, NULL); + + /* We should never arrive here */ + return ret; +} |