aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/firmware/efi
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/firmware/efi')
-rw-r--r--drivers/firmware/efi/Kconfig124
-rw-r--r--drivers/firmware/efi/Makefile1
-rw-r--r--drivers/firmware/efi/apple-properties.c2
-rw-r--r--drivers/firmware/efi/arm-runtime.c2
-rw-r--r--drivers/firmware/efi/capsule-loader.c31
-rw-r--r--drivers/firmware/efi/cper.c64
-rw-r--r--drivers/firmware/efi/dev-path-parser.c10
-rw-r--r--drivers/firmware/efi/efi-init.c67
-rw-r--r--drivers/firmware/efi/efi-pstore.c377
-rw-r--r--drivers/firmware/efi/efi.c170
-rw-r--r--drivers/firmware/efi/efibc.c79
-rw-r--r--drivers/firmware/efi/efivars.c670
-rw-r--r--drivers/firmware/efi/esrt.c4
-rw-r--r--drivers/firmware/efi/libstub/Makefile44
-rw-r--r--drivers/firmware/efi/libstub/Makefile.zboot49
-rw-r--r--drivers/firmware/efi/libstub/arm32-stub.c3
-rw-r--r--drivers/firmware/efi/libstub/arm64-stub.c67
-rw-r--r--drivers/firmware/efi/libstub/efi-stub-helper.c233
-rw-r--r--drivers/firmware/efi/libstub/efi-stub.c126
-rw-r--r--drivers/firmware/efi/libstub/efistub.h211
-rw-r--r--drivers/firmware/efi/libstub/fdt.c183
-rw-r--r--drivers/firmware/efi/libstub/file.c23
-rw-r--r--drivers/firmware/efi/libstub/intrinsics.c30
-rw-r--r--drivers/firmware/efi/libstub/loongarch-stub.c102
-rw-r--r--drivers/firmware/efi/libstub/mem.c93
-rw-r--r--drivers/firmware/efi/libstub/random.c7
-rw-r--r--drivers/firmware/efi/libstub/randomalloc.c36
-rw-r--r--drivers/firmware/efi/libstub/relocate.c21
-rw-r--r--drivers/firmware/efi/libstub/riscv-stub.c56
-rw-r--r--drivers/firmware/efi/libstub/secureboot.c8
-rw-r--r--drivers/firmware/efi/libstub/smbios.c48
-rw-r--r--drivers/firmware/efi/libstub/systable.c8
-rw-r--r--drivers/firmware/efi/libstub/x86-stub.c178
-rw-r--r--drivers/firmware/efi/libstub/zboot-header.S143
-rw-r--r--drivers/firmware/efi/libstub/zboot.c302
-rw-r--r--drivers/firmware/efi/libstub/zboot.lds45
-rw-r--r--drivers/firmware/efi/memmap.c5
-rw-r--r--drivers/firmware/efi/mokvar-table.c2
-rw-r--r--drivers/firmware/efi/reboot.c21
-rw-r--r--drivers/firmware/efi/riscv-runtime.c2
-rw-r--r--drivers/firmware/efi/runtime-map.c3
-rw-r--r--drivers/firmware/efi/sysfb_efi.c2
-rw-r--r--drivers/firmware/efi/tpm.c2
-rw-r--r--drivers/firmware/efi/vars.c1226
44 files changed, 2101 insertions, 2779 deletions
diff --git a/drivers/firmware/efi/Kconfig b/drivers/firmware/efi/Kconfig
index 2c3dac5ecb36..6787ed8dfacf 100644
--- a/drivers/firmware/efi/Kconfig
+++ b/drivers/firmware/efi/Kconfig
@@ -2,18 +2,6 @@
menu "EFI (Extensible Firmware Interface) Support"
depends on EFI
-config EFI_VARS
- tristate "EFI Variable Support via sysfs"
- depends on EFI && (X86 || IA64)
- default n
- help
- If you say Y here, you are able to get EFI (Extensible Firmware
- Interface) variable information via sysfs. You may read,
- write, create, and destroy EFI variables through this interface.
- Note that this driver is only retained for compatibility with
- legacy users: new users should use the efivarfs filesystem
- instead.
-
config EFI_ESRT
bool
depends on EFI && !IA64
@@ -22,6 +10,7 @@ config EFI_ESRT
config EFI_VARS_PSTORE
tristate "Register efivars backend for pstore"
depends on PSTORE
+ select UCS2_STRING
default y
help
Say Y here to enable use efivars as a backend to pstore. This
@@ -91,6 +80,18 @@ config EFI_SOFT_RESERVE
If unsure, say Y.
+config EFI_DXE_MEM_ATTRIBUTES
+ bool "Adjust memory attributes in EFISTUB"
+ depends on EFI && EFI_STUB && X86
+ default y
+ help
+ UEFI specification does not guarantee all memory to be
+ accessible for both write and execute as the kernel expects
+ it to be.
+ Use DXE services to check and alter memory protection
+ attributes during boot via EFISTUB to ensure that memory
+ ranges used by the kernel are writable and executable.
+
config EFI_PARAMS_FROM_FDT
bool
help
@@ -104,9 +105,28 @@ config EFI_RUNTIME_WRAPPERS
config EFI_GENERIC_STUB
bool
+config EFI_ZBOOT
+ bool "Enable the generic EFI decompressor"
+ depends on EFI_GENERIC_STUB && !ARM
+ select HAVE_KERNEL_GZIP
+ select HAVE_KERNEL_LZ4
+ select HAVE_KERNEL_LZMA
+ select HAVE_KERNEL_LZO
+ select HAVE_KERNEL_XZ
+ select HAVE_KERNEL_ZSTD
+ help
+ Create the bootable image as an EFI application that carries the
+ actual kernel image in compressed form, and decompresses it into
+ memory before executing it via LoadImage/StartImage EFI boot service
+ calls. For compatibility with non-EFI loaders, the payload can be
+ decompressed and executed by the loader as well, provided that the
+ loader implements the decompression algorithm and that non-EFI boot
+ is supported by the encapsulated image. (The compression algorithm
+ used is described in the zboot image header)
+
config EFI_ARMSTUB_DTB_LOADER
bool "Enable the DTB loader"
- depends on EFI_GENERIC_STUB && !RISCV
+ depends on EFI_GENERIC_STUB && !RISCV && !LOONGARCH
default y
help
Select this config option to add support for the dtb= command
@@ -123,7 +143,7 @@ config EFI_GENERIC_STUB_INITRD_CMDLINE_LOADER
bool "Enable the command line initrd loader" if !X86
depends on EFI_STUB && (EFI_GENERIC_STUB || X86)
default y if X86
- depends on !RISCV
+ depends on !RISCV && !LOONGARCH
help
Select this config option to add support for the initrd= command
line parameter, allowing an initrd that resides on the same volume
@@ -133,6 +153,7 @@ config EFI_GENERIC_STUB_INITRD_CMDLINE_LOADER
config EFI_BOOTLOADER_CONTROL
tristate "EFI Bootloader Control"
+ select UCS2_STRING
default n
help
This module installs a reboot hook, such that if reboot() is
@@ -181,6 +202,9 @@ config EFI_TEST
Say Y here to enable the runtime services support via /dev/efi_test.
If unsure, say N.
+config EFI_DEV_PATH_PARSER
+ bool
+
config APPLE_PROPERTIES
bool "Apple Device Properties"
depends on EFI_STUB && X86
@@ -243,13 +267,59 @@ config EFI_DISABLE_PCI_DMA
options "efi=disable_early_pci_dma" or "efi=no_disable_early_pci_dma"
may be used to override this option.
-endmenu
+config EFI_EARLYCON
+ def_bool y
+ depends on SERIAL_EARLYCON && !ARM && !IA64
+ select FONT_SUPPORT
+ select ARCH_USE_MEMREMAP_PROT
+
+config EFI_CUSTOM_SSDT_OVERLAYS
+ bool "Load custom ACPI SSDT overlay from an EFI variable"
+ depends on ACPI
+ default ACPI_TABLE_UPGRADE
+ help
+ Allow loading of an ACPI SSDT overlay from an EFI variable specified
+ by a kernel command line option.
+
+ See Documentation/admin-guide/acpi/ssdt-overlays.rst for more
+ information.
+
+config EFI_DISABLE_RUNTIME
+ bool "Disable EFI runtime services support by default"
+ default y if PREEMPT_RT
+ help
+ Allow to disable the EFI runtime services support by default. This can
+ already be achieved by using the efi=noruntime option, but it could be
+ useful to have this default without any kernel command line parameter.
+
+ The EFI runtime services are disabled by default when PREEMPT_RT is
+ enabled, because measurements have shown that some EFI functions calls
+ might take too much time to complete, causing large latencies which is
+ an issue for Real-Time kernels.
+
+ This default can be overridden by using the efi=runtime option.
+
+config EFI_COCO_SECRET
+ bool "EFI Confidential Computing Secret Area Support"
+ help
+ Confidential Computing platforms (such as AMD SEV) allow the
+ Guest Owner to securely inject secrets during guest VM launch.
+ The secrets are placed in a designated EFI reserved memory area.
+
+ In order to use the secrets in the kernel, the location of the secret
+ area (as published in the EFI config table) must be kept.
+
+ If you say Y here, the address of the EFI secret area will be kept
+ for usage inside the kernel. This will allow the
+ virt/coco/efi_secret module to access the secrets, which in turn
+ allows userspace programs to access the injected secrets.
config EFI_EMBEDDED_FIRMWARE
bool
- depends on EFI
select CRYPTO_LIB_SHA256
+endmenu
+
config UEFI_CPER
bool
@@ -262,25 +332,3 @@ config UEFI_CPER_X86
bool
depends on UEFI_CPER && X86
default y
-
-config EFI_DEV_PATH_PARSER
- bool
- depends on ACPI
- default n
-
-config EFI_EARLYCON
- def_bool y
- depends on EFI && SERIAL_EARLYCON && !ARM && !IA64
- select FONT_SUPPORT
- select ARCH_USE_MEMREMAP_PROT
-
-config EFI_CUSTOM_SSDT_OVERLAYS
- bool "Load custom ACPI SSDT overlay from an EFI variable"
- depends on EFI && ACPI
- default ACPI_TABLE_UPGRADE
- help
- Allow loading of an ACPI SSDT overlay from an EFI variable specified
- by a kernel command line option.
-
- See Documentation/admin-guide/acpi/ssdt-overlays.rst for more
- information.
diff --git a/drivers/firmware/efi/Makefile b/drivers/firmware/efi/Makefile
index c02ff25dd477..8d151e332584 100644
--- a/drivers/firmware/efi/Makefile
+++ b/drivers/firmware/efi/Makefile
@@ -17,7 +17,6 @@ ifneq ($(CONFIG_EFI_CAPSULE_LOADER),)
obj-$(CONFIG_EFI) += capsule.o
endif
obj-$(CONFIG_EFI_PARAMS_FROM_FDT) += fdtparams.o
-obj-$(CONFIG_EFI_VARS) += efivars.o
obj-$(CONFIG_EFI_ESRT) += esrt.o
obj-$(CONFIG_EFI_VARS_PSTORE) += efi-pstore.o
obj-$(CONFIG_UEFI_CPER) += cper.o
diff --git a/drivers/firmware/efi/apple-properties.c b/drivers/firmware/efi/apple-properties.c
index 4c3201e290e2..ea84108035eb 100644
--- a/drivers/firmware/efi/apple-properties.c
+++ b/drivers/firmware/efi/apple-properties.c
@@ -24,7 +24,7 @@ static bool dump_properties __initdata;
static int __init dump_properties_enable(char *arg)
{
dump_properties = true;
- return 0;
+ return 1;
}
__setup("dump_apple_properties", dump_properties_enable);
diff --git a/drivers/firmware/efi/arm-runtime.c b/drivers/firmware/efi/arm-runtime.c
index 3359ae2adf24..7c48c380d722 100644
--- a/drivers/firmware/efi/arm-runtime.c
+++ b/drivers/firmware/efi/arm-runtime.c
@@ -63,7 +63,7 @@ static bool __init efi_virtmap_init(void)
if (!(md->attribute & EFI_MEMORY_RUNTIME))
continue;
- if (md->virt_addr == 0)
+ if (md->virt_addr == U64_MAX)
return false;
ret = efi_create_mapping(&efi_mm, md);
diff --git a/drivers/firmware/efi/capsule-loader.c b/drivers/firmware/efi/capsule-loader.c
index 4dde8edd53b6..3e8d4b51a814 100644
--- a/drivers/firmware/efi/capsule-loader.c
+++ b/drivers/firmware/efi/capsule-loader.c
@@ -243,29 +243,6 @@ failed:
}
/**
- * efi_capsule_flush - called by file close or file flush
- * @file: file pointer
- * @id: not used
- *
- * If a capsule is being partially uploaded then calling this function
- * will be treated as upload termination and will free those completed
- * buffer pages and -ECANCELED will be returned.
- **/
-static int efi_capsule_flush(struct file *file, fl_owner_t id)
-{
- int ret = 0;
- struct capsule_info *cap_info = file->private_data;
-
- if (cap_info->index > 0) {
- pr_err("capsule upload not complete\n");
- efi_free_all_buff_pages(cap_info);
- ret = -ECANCELED;
- }
-
- return ret;
-}
-
-/**
* efi_capsule_release - called by file close
* @inode: not used
* @file: file pointer
@@ -277,6 +254,13 @@ static int efi_capsule_release(struct inode *inode, struct file *file)
{
struct capsule_info *cap_info = file->private_data;
+ if (cap_info->index > 0 &&
+ (cap_info->header.headersize == 0 ||
+ cap_info->count < cap_info->total_size)) {
+ pr_err("capsule upload not complete\n");
+ efi_free_all_buff_pages(cap_info);
+ }
+
kfree(cap_info->pages);
kfree(cap_info->phys);
kfree(file->private_data);
@@ -324,7 +308,6 @@ static const struct file_operations efi_capsule_fops = {
.owner = THIS_MODULE,
.open = efi_capsule_open,
.write = efi_capsule_write,
- .flush = efi_capsule_flush,
.release = efi_capsule_release,
.llseek = no_llseek,
};
diff --git a/drivers/firmware/efi/cper.c b/drivers/firmware/efi/cper.c
index 6ec8edec6329..e4e5ea7ce910 100644
--- a/drivers/firmware/efi/cper.c
+++ b/drivers/firmware/efi/cper.c
@@ -211,7 +211,33 @@ const char *cper_mem_err_type_str(unsigned int etype)
}
EXPORT_SYMBOL_GPL(cper_mem_err_type_str);
-static int cper_mem_err_location(struct cper_mem_err_compact *mem, char *msg)
+const char *cper_mem_err_status_str(u64 status)
+{
+ switch ((status >> 8) & 0xff) {
+ case 1: return "Error detected internal to the component";
+ case 4: return "Storage error in DRAM memory";
+ case 5: return "Storage error in TLB";
+ case 6: return "Storage error in cache";
+ case 7: return "Error in one or more functional units";
+ case 8: return "Component failed self test";
+ case 9: return "Overflow or undervalue of internal queue";
+ case 16: return "Error detected in the bus";
+ case 17: return "Virtual address not found on IO-TLB or IO-PDIR";
+ case 18: return "Improper access error";
+ case 19: return "Access to a memory address which is not mapped to any component";
+ case 20: return "Loss of Lockstep";
+ case 21: return "Response not associated with a request";
+ case 22: return "Bus parity error - must also set the A, C, or D Bits";
+ case 23: return "Detection of a protocol error";
+ case 24: return "Detection of a PATH_ERROR";
+ case 25: return "Bus operation timeout";
+ case 26: return "A read was issued to data that has been poisoned";
+ default: return "Reserved";
+ }
+}
+EXPORT_SYMBOL_GPL(cper_mem_err_status_str);
+
+int cper_mem_err_location(struct cper_mem_err_compact *mem, char *msg)
{
u32 len, n;
@@ -221,51 +247,51 @@ static int cper_mem_err_location(struct cper_mem_err_compact *mem, char *msg)
n = 0;
len = CPER_REC_LEN;
if (mem->validation_bits & CPER_MEM_VALID_NODE)
- n += scnprintf(msg + n, len - n, "node: %d ", mem->node);
+ n += scnprintf(msg + n, len - n, "node:%d ", mem->node);
if (mem->validation_bits & CPER_MEM_VALID_CARD)
- n += scnprintf(msg + n, len - n, "card: %d ", mem->card);
+ n += scnprintf(msg + n, len - n, "card:%d ", mem->card);
if (mem->validation_bits & CPER_MEM_VALID_MODULE)
- n += scnprintf(msg + n, len - n, "module: %d ", mem->module);
+ n += scnprintf(msg + n, len - n, "module:%d ", mem->module);
if (mem->validation_bits & CPER_MEM_VALID_RANK_NUMBER)
- n += scnprintf(msg + n, len - n, "rank: %d ", mem->rank);
+ n += scnprintf(msg + n, len - n, "rank:%d ", mem->rank);
if (mem->validation_bits & CPER_MEM_VALID_BANK)
- n += scnprintf(msg + n, len - n, "bank: %d ", mem->bank);
+ n += scnprintf(msg + n, len - n, "bank:%d ", mem->bank);
if (mem->validation_bits & CPER_MEM_VALID_BANK_GROUP)
- n += scnprintf(msg + n, len - n, "bank_group: %d ",
+ n += scnprintf(msg + n, len - n, "bank_group:%d ",
mem->bank >> CPER_MEM_BANK_GROUP_SHIFT);
if (mem->validation_bits & CPER_MEM_VALID_BANK_ADDRESS)
- n += scnprintf(msg + n, len - n, "bank_address: %d ",
+ n += scnprintf(msg + n, len - n, "bank_address:%d ",
mem->bank & CPER_MEM_BANK_ADDRESS_MASK);
if (mem->validation_bits & CPER_MEM_VALID_DEVICE)
- n += scnprintf(msg + n, len - n, "device: %d ", mem->device);
+ n += scnprintf(msg + n, len - n, "device:%d ", mem->device);
if (mem->validation_bits & (CPER_MEM_VALID_ROW | CPER_MEM_VALID_ROW_EXT)) {
u32 row = mem->row;
row |= cper_get_mem_extension(mem->validation_bits, mem->extended);
- n += scnprintf(msg + n, len - n, "row: %d ", row);
+ n += scnprintf(msg + n, len - n, "row:%d ", row);
}
if (mem->validation_bits & CPER_MEM_VALID_COLUMN)
- n += scnprintf(msg + n, len - n, "column: %d ", mem->column);
+ n += scnprintf(msg + n, len - n, "column:%d ", mem->column);
if (mem->validation_bits & CPER_MEM_VALID_BIT_POSITION)
- n += scnprintf(msg + n, len - n, "bit_position: %d ",
+ n += scnprintf(msg + n, len - n, "bit_position:%d ",
mem->bit_pos);
if (mem->validation_bits & CPER_MEM_VALID_REQUESTOR_ID)
- n += scnprintf(msg + n, len - n, "requestor_id: 0x%016llx ",
+ n += scnprintf(msg + n, len - n, "requestor_id:0x%016llx ",
mem->requestor_id);
if (mem->validation_bits & CPER_MEM_VALID_RESPONDER_ID)
- n += scnprintf(msg + n, len - n, "responder_id: 0x%016llx ",
+ n += scnprintf(msg + n, len - n, "responder_id:0x%016llx ",
mem->responder_id);
if (mem->validation_bits & CPER_MEM_VALID_TARGET_ID)
- n += scnprintf(msg + n, len - n, "target_id: 0x%016llx ",
+ n += scnprintf(msg + n, len - n, "target_id:0x%016llx ",
mem->target_id);
if (mem->validation_bits & CPER_MEM_VALID_CHIP_ID)
- n += scnprintf(msg + n, len - n, "chip_id: %d ",
+ n += scnprintf(msg + n, len - n, "chip_id:%d ",
mem->extended >> CPER_MEM_CHIP_ID_SHIFT);
return n;
}
-static int cper_dimm_err_location(struct cper_mem_err_compact *mem, char *msg)
+int cper_dimm_err_location(struct cper_mem_err_compact *mem, char *msg)
{
u32 len, n;
const char *bank = NULL, *device = NULL;
@@ -334,7 +360,9 @@ static void cper_print_mem(const char *pfx, const struct cper_sec_mem_err *mem,
return;
}
if (mem->validation_bits & CPER_MEM_VALID_ERROR_STATUS)
- printk("%s""error_status: 0x%016llx\n", pfx, mem->error_status);
+ printk("%s error_status: %s (0x%016llx)\n",
+ pfx, cper_mem_err_status_str(mem->error_status),
+ mem->error_status);
if (mem->validation_bits & CPER_MEM_VALID_PA)
printk("%s""physical_address: 0x%016llx\n",
pfx, mem->physical_addr);
diff --git a/drivers/firmware/efi/dev-path-parser.c b/drivers/firmware/efi/dev-path-parser.c
index eb9c65f97841..f80d87c199c3 100644
--- a/drivers/firmware/efi/dev-path-parser.c
+++ b/drivers/firmware/efi/dev-path-parser.c
@@ -15,9 +15,11 @@
static long __init parse_acpi_path(const struct efi_dev_path *node,
struct device *parent, struct device **child)
{
- char hid[ACPI_ID_LEN], uid[11]; /* UINT_MAX + null byte */
struct acpi_device *adev;
struct device *phys_dev;
+ char hid[ACPI_ID_LEN];
+ u64 uid;
+ int ret;
if (node->header.length != 12)
return -EINVAL;
@@ -27,12 +29,12 @@ static long __init parse_acpi_path(const struct efi_dev_path *node,
'A' + ((node->acpi.hid >> 5) & 0x1f) - 1,
'A' + ((node->acpi.hid >> 0) & 0x1f) - 1,
node->acpi.hid >> 16);
- sprintf(uid, "%u", node->acpi.uid);
for_each_acpi_dev_match(adev, hid, NULL, -1) {
- if (adev->pnp.unique_id && !strcmp(adev->pnp.unique_id, uid))
+ ret = acpi_dev_uid_to_integer(adev, &uid);
+ if (ret == 0 && node->acpi.uid == uid)
break;
- if (!adev->pnp.unique_id && node->acpi.uid == 0)
+ if (ret == -ENODATA && node->acpi.uid == 0)
break;
}
if (!adev)
diff --git a/drivers/firmware/efi/efi-init.c b/drivers/firmware/efi/efi-init.c
index b19ce1a83f91..2fd770b499a3 100644
--- a/drivers/firmware/efi/efi-init.c
+++ b/drivers/firmware/efi/efi-init.c
@@ -51,34 +51,10 @@ static phys_addr_t __init efi_to_phys(unsigned long addr)
return addr;
}
-static __initdata unsigned long screen_info_table = EFI_INVALID_TABLE_ADDR;
-static __initdata unsigned long cpu_state_table = EFI_INVALID_TABLE_ADDR;
-
-static const efi_config_table_type_t arch_tables[] __initconst = {
- {LINUX_EFI_ARM_SCREEN_INFO_TABLE_GUID, &screen_info_table},
- {LINUX_EFI_ARM_CPU_STATE_TABLE_GUID, &cpu_state_table},
- {}
-};
+extern __weak const efi_config_table_type_t efi_arch_tables[];
static void __init init_screen_info(void)
{
- struct screen_info *si;
-
- if (IS_ENABLED(CONFIG_ARM) &&
- screen_info_table != EFI_INVALID_TABLE_ADDR) {
- si = early_memremap_ro(screen_info_table, sizeof(*si));
- if (!si) {
- pr_err("Could not map screen_info config table\n");
- return;
- }
- screen_info = *si;
- early_memunmap(si, sizeof(*si));
-
- /* dummycon on ARM needs non-zero values for columns/lines */
- screen_info.orig_video_cols = 80;
- screen_info.orig_video_lines = 25;
- }
-
if (screen_info.orig_video_isVGA == VIDEO_TYPE_EFI &&
memblock_is_map_memory(screen_info.lfb_base))
memblock_mark_nomap(screen_info.lfb_base, screen_info.lfb_size);
@@ -119,8 +95,7 @@ static int __init uefi_init(u64 efi_system_table)
goto out;
}
retval = efi_config_parse_tables(config_tables, systab->nr_tables,
- IS_ENABLED(CONFIG_ARM) ? arch_tables
- : NULL);
+ efi_arch_tables);
early_memunmap(config_tables, table_size);
out:
@@ -235,6 +210,12 @@ void __init efi_init(void)
}
reserve_regions();
+ /*
+ * For memblock manipulation, the cap should come after the memblock_add().
+ * And now, memblock is fully populated, it is time to do capping.
+ */
+ early_init_dt_check_for_usable_mem_range();
+ efi_find_mirror();
efi_esrt_init();
efi_mokvar_table_init();
@@ -242,36 +223,4 @@ void __init efi_init(void)
PAGE_ALIGN(data.size + (data.phys_map & ~PAGE_MASK)));
init_screen_info();
-
-#ifdef CONFIG_ARM
- /* ARM does not permit early mappings to persist across paging_init() */
- efi_memmap_unmap();
-
- if (cpu_state_table != EFI_INVALID_TABLE_ADDR) {
- struct efi_arm_entry_state *state;
- bool dump_state = true;
-
- state = early_memremap_ro(cpu_state_table,
- sizeof(struct efi_arm_entry_state));
- if (state == NULL) {
- pr_warn("Unable to map CPU entry state table.\n");
- return;
- }
-
- if ((state->sctlr_before_ebs & 1) == 0)
- pr_warn(FW_BUG "EFI stub was entered with MMU and Dcache disabled, please fix your firmware!\n");
- else if ((state->sctlr_after_ebs & 1) == 0)
- pr_warn(FW_BUG "ExitBootServices() returned with MMU and Dcache disabled, please fix your firmware!\n");
- else
- dump_state = false;
-
- if (dump_state || efi_enabled(EFI_DBG)) {
- pr_info("CPSR at EFI stub entry : 0x%08x\n", state->cpsr_before_ebs);
- pr_info("SCTLR at EFI stub entry : 0x%08x\n", state->sctlr_before_ebs);
- pr_info("CPSR after ExitBootServices() : 0x%08x\n", state->cpsr_after_ebs);
- pr_info("SCTLR after ExitBootServices(): 0x%08x\n", state->sctlr_after_ebs);
- }
- early_memunmap(state, sizeof(struct efi_arm_entry_state));
- }
-#endif
}
diff --git a/drivers/firmware/efi/efi-pstore.c b/drivers/firmware/efi/efi-pstore.c
index 0ef086e43090..3bddc152fcd4 100644
--- a/drivers/firmware/efi/efi-pstore.c
+++ b/drivers/firmware/efi/efi-pstore.c
@@ -6,6 +6,8 @@
#include <linux/slab.h>
#include <linux/ucs2_string.h>
+MODULE_IMPORT_NS(EFIVAR);
+
#define DUMP_NAME_LEN 66
#define EFIVARS_DATA_SIZE_MAX 1024
@@ -20,18 +22,25 @@ module_param_named(pstore_disable, efivars_pstore_disable, bool, 0644);
EFI_VARIABLE_BOOTSERVICE_ACCESS | \
EFI_VARIABLE_RUNTIME_ACCESS)
-static LIST_HEAD(efi_pstore_list);
-static DECLARE_WORK(efivar_work, NULL);
-
static int efi_pstore_open(struct pstore_info *psi)
{
- psi->data = NULL;
+ int err;
+
+ err = efivar_lock();
+ if (err)
+ return err;
+
+ psi->data = kzalloc(EFIVARS_DATA_SIZE_MAX, GFP_KERNEL);
+ if (!psi->data)
+ return -ENOMEM;
+
return 0;
}
static int efi_pstore_close(struct pstore_info *psi)
{
- psi->data = NULL;
+ efivar_unlock();
+ kfree(psi->data);
return 0;
}
@@ -40,22 +49,17 @@ static inline u64 generic_id(u64 timestamp, unsigned int part, int count)
return (timestamp * 100 + part) * 1000 + count;
}
-static int efi_pstore_read_func(struct efivar_entry *entry,
- struct pstore_record *record)
+static int efi_pstore_read_func(struct pstore_record *record,
+ efi_char16_t *varname)
{
- efi_guid_t vendor = LINUX_EFI_CRASH_GUID;
+ unsigned long wlen, size = EFIVARS_DATA_SIZE_MAX;
char name[DUMP_NAME_LEN], data_type;
- int i;
+ efi_status_t status;
int cnt;
unsigned int part;
- unsigned long size;
u64 time;
- if (efi_guidcmp(entry->var.VendorGuid, vendor))
- return 0;
-
- for (i = 0; i < DUMP_NAME_LEN; i++)
- name[i] = entry->var.VariableName[i];
+ ucs2_as_utf8(name, varname, DUMP_NAME_LEN);
if (sscanf(name, "dump-type%u-%u-%d-%llu-%c",
&record->type, &part, &cnt, &time, &data_type) == 5) {
@@ -95,161 +99,75 @@ static int efi_pstore_read_func(struct efivar_entry *entry,
} else
return 0;
- entry->var.DataSize = 1024;
- __efivar_entry_get(entry, &entry->var.Attributes,
- &entry->var.DataSize, entry->var.Data);
- size = entry->var.DataSize;
- memcpy(record->buf, entry->var.Data,
- (size_t)min_t(unsigned long, EFIVARS_DATA_SIZE_MAX, size));
-
- return size;
-}
-
-/**
- * efi_pstore_scan_sysfs_enter
- * @pos: scanning entry
- * @next: next entry
- * @head: list head
- */
-static void efi_pstore_scan_sysfs_enter(struct efivar_entry *pos,
- struct efivar_entry *next,
- struct list_head *head)
-{
- pos->scanning = true;
- if (&next->list != head)
- next->scanning = true;
-}
-
-/**
- * __efi_pstore_scan_sysfs_exit
- * @entry: deleting entry
- * @turn_off_scanning: Check if a scanning flag should be turned off
- */
-static inline int __efi_pstore_scan_sysfs_exit(struct efivar_entry *entry,
- bool turn_off_scanning)
-{
- if (entry->deleting) {
- list_del(&entry->list);
- efivar_entry_iter_end();
- kfree(entry);
- if (efivar_entry_iter_begin())
- return -EINTR;
- } else if (turn_off_scanning)
- entry->scanning = false;
-
- return 0;
-}
-
-/**
- * efi_pstore_scan_sysfs_exit
- * @pos: scanning entry
- * @next: next entry
- * @head: list head
- * @stop: a flag checking if scanning will stop
- */
-static int efi_pstore_scan_sysfs_exit(struct efivar_entry *pos,
- struct efivar_entry *next,
- struct list_head *head, bool stop)
-{
- int ret = __efi_pstore_scan_sysfs_exit(pos, true);
-
- if (ret)
- return ret;
-
- if (stop)
- ret = __efi_pstore_scan_sysfs_exit(next, &next->list != head);
- return ret;
-}
+ record->buf = kmalloc(size, GFP_KERNEL);
+ if (!record->buf)
+ return -ENOMEM;
-/**
- * efi_pstore_sysfs_entry_iter
- *
- * @record: pstore record to pass to callback
- *
- * You MUST call efivar_entry_iter_begin() before this function, and
- * efivar_entry_iter_end() afterwards.
- *
- */
-static int efi_pstore_sysfs_entry_iter(struct pstore_record *record)
-{
- struct efivar_entry **pos = (struct efivar_entry **)&record->psi->data;
- struct efivar_entry *entry, *n;
- struct list_head *head = &efi_pstore_list;
- int size = 0;
- int ret;
-
- if (!*pos) {
- list_for_each_entry_safe(entry, n, head, list) {
- efi_pstore_scan_sysfs_enter(entry, n, head);
-
- size = efi_pstore_read_func(entry, record);
- ret = efi_pstore_scan_sysfs_exit(entry, n, head,
- size < 0);
- if (ret)
- return ret;
- if (size)
- break;
- }
- *pos = n;
- return size;
+ status = efivar_get_variable(varname, &LINUX_EFI_CRASH_GUID, NULL,
+ &size, record->buf);
+ if (status != EFI_SUCCESS) {
+ kfree(record->buf);
+ return -EIO;
}
- list_for_each_entry_safe_from((*pos), n, head, list) {
- efi_pstore_scan_sysfs_enter((*pos), n, head);
-
- size = efi_pstore_read_func((*pos), record);
- ret = efi_pstore_scan_sysfs_exit((*pos), n, head, size < 0);
- if (ret)
- return ret;
- if (size)
- break;
+ /*
+ * Store the name of the variable in the pstore_record priv field, so
+ * we can reuse it later if we need to delete the EFI variable from the
+ * variable store.
+ */
+ wlen = (ucs2_strnlen(varname, DUMP_NAME_LEN) + 1) * sizeof(efi_char16_t);
+ record->priv = kmemdup(varname, wlen, GFP_KERNEL);
+ if (!record->priv) {
+ kfree(record->buf);
+ return -ENOMEM;
}
- *pos = n;
+
return size;
}
-/**
- * efi_pstore_read
- *
- * This function returns a size of NVRAM entry logged via efi_pstore_write().
- * The meaning and behavior of efi_pstore/pstore are as below.
- *
- * size > 0: Got data of an entry logged via efi_pstore_write() successfully,
- * and pstore filesystem will continue reading subsequent entries.
- * size == 0: Entry was not logged via efi_pstore_write(),
- * and efi_pstore driver will continue reading subsequent entries.
- * size < 0: Failed to get data of entry logging via efi_pstore_write(),
- * and pstore will stop reading entry.
- */
static ssize_t efi_pstore_read(struct pstore_record *record)
{
- ssize_t size;
+ efi_char16_t *varname = record->psi->data;
+ efi_guid_t guid = LINUX_EFI_CRASH_GUID;
+ unsigned long varname_size;
+ efi_status_t status;
- record->buf = kzalloc(EFIVARS_DATA_SIZE_MAX, GFP_KERNEL);
- if (!record->buf)
- return -ENOMEM;
+ for (;;) {
+ varname_size = EFIVARS_DATA_SIZE_MAX;
- if (efivar_entry_iter_begin()) {
- size = -EINTR;
- goto out;
- }
- size = efi_pstore_sysfs_entry_iter(record);
- efivar_entry_iter_end();
+ /*
+ * If this is the first read() call in the pstore enumeration,
+ * varname will be the empty string, and the GetNextVariable()
+ * runtime service call will return the first EFI variable in
+ * its own enumeration order, ignoring the guid argument.
+ *
+ * Subsequent calls to GetNextVariable() must pass the name and
+ * guid values returned by the previous call, which is why we
+ * store varname in record->psi->data. Given that we only
+ * enumerate variables with the efi-pstore GUID, there is no
+ * need to record the guid return value.
+ */
+ status = efivar_get_next_variable(&varname_size, varname, &guid);
+ if (status == EFI_NOT_FOUND)
+ return 0;
-out:
- if (size <= 0) {
- kfree(record->buf);
- record->buf = NULL;
+ if (status != EFI_SUCCESS)
+ return -EIO;
+
+ /* skip variables that don't concern us */
+ if (efi_guidcmp(guid, LINUX_EFI_CRASH_GUID))
+ continue;
+
+ return efi_pstore_read_func(record, varname);
}
- return size;
}
static int efi_pstore_write(struct pstore_record *record)
{
char name[DUMP_NAME_LEN];
efi_char16_t efi_name[DUMP_NAME_LEN];
- efi_guid_t vendor = LINUX_EFI_CRASH_GUID;
- int i, ret = 0;
+ efi_status_t status;
+ int i;
record->id = generic_id(record->time.tv_sec, record->part,
record->count);
@@ -265,88 +183,26 @@ static int efi_pstore_write(struct pstore_record *record)
for (i = 0; i < DUMP_NAME_LEN; i++)
efi_name[i] = name[i];
- ret = efivar_entry_set_safe(efi_name, vendor, PSTORE_EFI_ATTRIBUTES,
- preemptible(), record->size, record->psi->buf);
-
- if (record->reason == KMSG_DUMP_OOPS && try_module_get(THIS_MODULE))
- if (!schedule_work(&efivar_work))
- module_put(THIS_MODULE);
-
- return ret;
+ if (efivar_trylock())
+ return -EBUSY;
+ status = efivar_set_variable_locked(efi_name, &LINUX_EFI_CRASH_GUID,
+ PSTORE_EFI_ATTRIBUTES,
+ record->size, record->psi->buf,
+ true);
+ efivar_unlock();
+ return status == EFI_SUCCESS ? 0 : -EIO;
};
-/*
- * Clean up an entry with the same name
- */
-static int efi_pstore_erase_func(struct efivar_entry *entry, void *data)
-{
- efi_char16_t *efi_name = data;
- efi_guid_t vendor = LINUX_EFI_CRASH_GUID;
- unsigned long ucs2_len = ucs2_strlen(efi_name);
-
- if (efi_guidcmp(entry->var.VendorGuid, vendor))
- return 0;
-
- if (ucs2_strncmp(entry->var.VariableName, efi_name, (size_t)ucs2_len))
- return 0;
-
- if (entry->scanning) {
- /*
- * Skip deletion because this entry will be deleted
- * after scanning is completed.
- */
- entry->deleting = true;
- } else
- list_del(&entry->list);
-
- /* found */
- __efivar_entry_delete(entry);
-
- return 1;
-}
-
-static int efi_pstore_erase_name(const char *name)
-{
- struct efivar_entry *entry = NULL;
- efi_char16_t efi_name[DUMP_NAME_LEN];
- int found, i;
-
- for (i = 0; i < DUMP_NAME_LEN; i++) {
- efi_name[i] = name[i];
- if (name[i] == '\0')
- break;
- }
-
- if (efivar_entry_iter_begin())
- return -EINTR;
-
- found = __efivar_entry_iter(efi_pstore_erase_func, &efi_pstore_list,
- efi_name, &entry);
- efivar_entry_iter_end();
-
- if (found && !entry->scanning)
- kfree(entry);
-
- return found ? 0 : -ENOENT;
-}
-
static int efi_pstore_erase(struct pstore_record *record)
{
- char name[DUMP_NAME_LEN];
- int ret;
-
- snprintf(name, sizeof(name), "dump-type%u-%u-%d-%lld",
- record->type, record->part, record->count,
- (long long)record->time.tv_sec);
- ret = efi_pstore_erase_name(name);
- if (ret != -ENOENT)
- return ret;
+ efi_status_t status;
- snprintf(name, sizeof(name), "dump-type%u-%u-%lld",
- record->type, record->part, (long long)record->time.tv_sec);
- ret = efi_pstore_erase_name(name);
+ status = efivar_set_variable(record->priv, &LINUX_EFI_CRASH_GUID,
+ PSTORE_EFI_ATTRIBUTES, 0, NULL);
- return ret;
+ if (status != EFI_SUCCESS && status != EFI_NOT_FOUND)
+ return -EIO;
+ return 0;
}
static struct pstore_info efi_pstore_info = {
@@ -360,77 +216,14 @@ static struct pstore_info efi_pstore_info = {
.erase = efi_pstore_erase,
};
-static int efi_pstore_callback(efi_char16_t *name, efi_guid_t vendor,
- unsigned long name_size, void *data)
-{
- struct efivar_entry *entry;
- int ret;
-
- entry = kzalloc(sizeof(*entry), GFP_KERNEL);
- if (!entry)
- return -ENOMEM;
-
- memcpy(entry->var.VariableName, name, name_size);
- entry->var.VendorGuid = vendor;
-
- ret = efivar_entry_add(entry, &efi_pstore_list);
- if (ret)
- kfree(entry);
-
- return ret;
-}
-
-static int efi_pstore_update_entry(efi_char16_t *name, efi_guid_t vendor,
- unsigned long name_size, void *data)
-{
- struct efivar_entry *entry = data;
-
- if (efivar_entry_find(name, vendor, &efi_pstore_list, false))
- return 0;
-
- memcpy(entry->var.VariableName, name, name_size);
- memcpy(&(entry->var.VendorGuid), &vendor, sizeof(efi_guid_t));
-
- return 1;
-}
-
-static void efi_pstore_update_entries(struct work_struct *work)
-{
- struct efivar_entry *entry;
- int err;
-
- /* Add new sysfs entries */
- while (1) {
- entry = kzalloc(sizeof(*entry), GFP_KERNEL);
- if (!entry)
- return;
-
- err = efivar_init(efi_pstore_update_entry, entry,
- false, &efi_pstore_list);
- if (!err)
- break;
-
- efivar_entry_add(entry, &efi_pstore_list);
- }
-
- kfree(entry);
- module_put(THIS_MODULE);
-}
-
static __init int efivars_pstore_init(void)
{
- int ret;
-
- if (!efivars_kobject() || !efivar_supports_writes())
+ if (!efivar_supports_writes())
return 0;
if (efivars_pstore_disable)
return 0;
- ret = efivar_init(efi_pstore_callback, NULL, true, &efi_pstore_list);
- if (ret)
- return ret;
-
efi_pstore_info.buf = kmalloc(4096, GFP_KERNEL);
if (!efi_pstore_info.buf)
return -ENOMEM;
@@ -443,8 +236,6 @@ static __init int efivars_pstore_init(void)
efi_pstore_info.bufsize = 0;
}
- INIT_WORK(&efivar_work, efi_pstore_update_entries);
-
return 0;
}
diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c
index ae79c3300129..a46df5d1d094 100644
--- a/drivers/firmware/efi/efi.c
+++ b/drivers/firmware/efi/efi.c
@@ -21,6 +21,7 @@
#include <linux/device.h>
#include <linux/efi.h>
#include <linux/of.h>
+#include <linux/initrd.h>
#include <linux/io.h>
#include <linux/kexec.h>
#include <linux/platform_device.h>
@@ -46,15 +47,19 @@ struct efi __read_mostly efi = {
#ifdef CONFIG_LOAD_UEFI_KEYS
.mokvar_table = EFI_INVALID_TABLE_ADDR,
#endif
+#ifdef CONFIG_EFI_COCO_SECRET
+ .coco_secret = EFI_INVALID_TABLE_ADDR,
+#endif
};
EXPORT_SYMBOL(efi);
unsigned long __ro_after_init efi_rng_seed = EFI_INVALID_TABLE_ADDR;
static unsigned long __initdata mem_reserve = EFI_INVALID_TABLE_ADDR;
static unsigned long __initdata rt_prop = EFI_INVALID_TABLE_ADDR;
+static unsigned long __initdata initrd = EFI_INVALID_TABLE_ADDR;
struct mm_struct efi_mm = {
- .mm_rb = RB_ROOT,
+ .mm_mt = MTREE_INIT_EXT(mm_mt, MM_MT_FLAGS, efi_mm.mmap_lock),
.mm_users = ATOMIC_INIT(2),
.mm_count = ATOMIC_INIT(1),
.write_protect_seq = SEQCNT_ZERO(efi_mm.write_protect_seq),
@@ -66,7 +71,7 @@ struct mm_struct efi_mm = {
struct workqueue_struct *efi_rts_wq;
-static bool disable_runtime = IS_ENABLED(CONFIG_PREEMPT_RT);
+static bool disable_runtime = IS_ENABLED(CONFIG_EFI_DISABLE_RUNTIME);
static int __init setup_noefi(char *arg)
{
disable_runtime = true;
@@ -199,7 +204,7 @@ static void generic_ops_unregister(void)
}
#ifdef CONFIG_EFI_CUSTOM_SSDT_OVERLAYS
-#define EFIVAR_SSDT_NAME_MAX 16
+#define EFIVAR_SSDT_NAME_MAX 16UL
static char efivar_ssdt[EFIVAR_SSDT_NAME_MAX] __initdata;
static int __init efivar_ssdt_setup(char *str)
{
@@ -212,87 +217,68 @@ static int __init efivar_ssdt_setup(char *str)
memcpy(efivar_ssdt, str, strlen(str));
else
pr_warn("efivar_ssdt: name too long: %s\n", str);
- return 0;
+ return 1;
}
__setup("efivar_ssdt=", efivar_ssdt_setup);
-static __init int efivar_ssdt_iter(efi_char16_t *name, efi_guid_t vendor,
- unsigned long name_size, void *data)
-{
- struct efivar_entry *entry;
- struct list_head *list = data;
- char utf8_name[EFIVAR_SSDT_NAME_MAX];
- int limit = min_t(unsigned long, EFIVAR_SSDT_NAME_MAX, name_size);
-
- ucs2_as_utf8(utf8_name, name, limit - 1);
- if (strncmp(utf8_name, efivar_ssdt, limit) != 0)
- return 0;
-
- entry = kmalloc(sizeof(*entry), GFP_KERNEL);
- if (!entry)
- return 0;
-
- memcpy(entry->var.VariableName, name, name_size);
- memcpy(&entry->var.VendorGuid, &vendor, sizeof(efi_guid_t));
-
- efivar_entry_add(entry, list);
-
- return 0;
-}
-
static __init int efivar_ssdt_load(void)
{
- LIST_HEAD(entries);
- struct efivar_entry *entry, *aux;
- unsigned long size;
- void *data;
- int ret;
+ unsigned long name_size = 256;
+ efi_char16_t *name = NULL;
+ efi_status_t status;
+ efi_guid_t guid;
if (!efivar_ssdt[0])
return 0;
- ret = efivar_init(efivar_ssdt_iter, &entries, true, &entries);
-
- list_for_each_entry_safe(entry, aux, &entries, list) {
- pr_info("loading SSDT from variable %s-%pUl\n", efivar_ssdt,
- &entry->var.VendorGuid);
+ name = kzalloc(name_size, GFP_KERNEL);
+ if (!name)
+ return -ENOMEM;
- list_del(&entry->list);
+ for (;;) {
+ char utf8_name[EFIVAR_SSDT_NAME_MAX];
+ unsigned long data_size = 0;
+ void *data;
+ int limit;
- ret = efivar_entry_size(entry, &size);
- if (ret) {
- pr_err("failed to get var size\n");
- goto free_entry;
+ status = efi.get_next_variable(&name_size, name, &guid);
+ if (status == EFI_NOT_FOUND) {
+ break;
+ } else if (status == EFI_BUFFER_TOO_SMALL) {
+ name = krealloc(name, name_size, GFP_KERNEL);
+ if (!name)
+ return -ENOMEM;
+ continue;
}
- data = kmalloc(size, GFP_KERNEL);
- if (!data) {
- ret = -ENOMEM;
- goto free_entry;
- }
+ limit = min(EFIVAR_SSDT_NAME_MAX, name_size);
+ ucs2_as_utf8(utf8_name, name, limit - 1);
+ if (strncmp(utf8_name, efivar_ssdt, limit) != 0)
+ continue;
- ret = efivar_entry_get(entry, NULL, &size, data);
- if (ret) {
- pr_err("failed to get var data\n");
- goto free_data;
- }
+ pr_info("loading SSDT from variable %s-%pUl\n", efivar_ssdt, &guid);
- ret = acpi_load_table(data, NULL);
- if (ret) {
- pr_err("failed to load table: %d\n", ret);
- goto free_data;
- }
+ status = efi.get_variable(name, &guid, NULL, &data_size, NULL);
+ if (status != EFI_BUFFER_TOO_SMALL || !data_size)
+ return -EIO;
- goto free_entry;
+ data = kmalloc(data_size, GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
-free_data:
+ status = efi.get_variable(name, &guid, NULL, &data_size, data);
+ if (status == EFI_SUCCESS) {
+ acpi_status ret = acpi_load_table(data, NULL);
+ if (ret)
+ pr_err("failed to load table: %u\n", ret);
+ else
+ continue;
+ } else {
+ pr_err("failed to get var data: 0x%lx\n", status);
+ }
kfree(data);
-
-free_entry:
- kfree(entry);
}
-
- return ret;
+ return 0;
}
#else
static inline int efivar_ssdt_load(void) { return 0; }
@@ -422,6 +408,11 @@ static int __init efisubsys_init(void)
if (efi_enabled(EFI_DBG) && efi_enabled(EFI_PRESERVE_BS_REGIONS))
efi_debugfs_init();
+#ifdef CONFIG_EFI_COCO_SECRET
+ if (efi.coco_secret != EFI_INVALID_TABLE_ADDR)
+ platform_device_register_simple("efi_secret", 0, NULL, 0);
+#endif
+
return 0;
err_remove_group:
@@ -438,6 +429,29 @@ err_put:
subsys_initcall(efisubsys_init);
+void __init efi_find_mirror(void)
+{
+ efi_memory_desc_t *md;
+ u64 mirror_size = 0, total_size = 0;
+
+ if (!efi_enabled(EFI_MEMMAP))
+ return;
+
+ for_each_efi_memory_desc(md) {
+ unsigned long long start = md->phys_addr;
+ unsigned long long size = md->num_pages << EFI_PAGE_SHIFT;
+
+ total_size += size;
+ if (md->attribute & EFI_MEMORY_MORE_RELIABLE) {
+ memblock_mark_mirror(start, size);
+ mirror_size += size;
+ }
+ }
+ if (mirror_size)
+ pr_info("Memory: %lldM/%lldM mirrored memory\n",
+ mirror_size>>20, total_size>>20);
+}
+
/*
* Find the efi memory descriptor for a given physical address. Given a
* physical address, determine if it exists within an EFI Memory Map entry,
@@ -522,6 +536,7 @@ static const efi_config_table_type_t common_tables[] __initconst = {
{LINUX_EFI_TPM_EVENT_LOG_GUID, &efi.tpm_log, "TPMEventLog" },
{LINUX_EFI_TPM_FINAL_LOG_GUID, &efi.tpm_final_log, "TPMFinalLog" },
{LINUX_EFI_MEMRESERVE_TABLE_GUID, &mem_reserve, "MEMRESERVE" },
+ {LINUX_EFI_INITRD_MEDIA_GUID, &initrd, "INITRD" },
{EFI_RT_PROPERTIES_TABLE_GUID, &rt_prop, "RTPROP" },
#ifdef CONFIG_EFI_RCI2_TABLE
{DELLEMC_EFI_RCI2_TABLE_GUID, &rci2_table_phys },
@@ -529,6 +544,9 @@ static const efi_config_table_type_t common_tables[] __initconst = {
#ifdef CONFIG_LOAD_UEFI_KEYS
{LINUX_EFI_MOK_VARIABLE_TABLE_GUID, &efi.mokvar_table, "MOKvar" },
#endif
+#ifdef CONFIG_EFI_COCO_SECRET
+ {LINUX_EFI_COCO_SECRET_AREA_GUID, &efi.coco_secret, "CocoSecret" },
+#endif
{},
};
@@ -593,7 +611,7 @@ int __init efi_config_parse_tables(const efi_config_table_t *config_tables,
seed = early_memremap(efi_rng_seed, sizeof(*seed));
if (seed != NULL) {
- size = READ_ONCE(seed->size);
+ size = min(seed->size, EFI_RANDOM_SEED_SIZE);
early_memunmap(seed, sizeof(*seed));
} else {
pr_err("Could not map UEFI random seed!\n");
@@ -661,6 +679,18 @@ int __init efi_config_parse_tables(const efi_config_table_t *config_tables,
}
}
+ if (IS_ENABLED(CONFIG_BLK_DEV_INITRD) &&
+ initrd != EFI_INVALID_TABLE_ADDR && phys_initrd_size == 0) {
+ struct linux_efi_initrd *tbl;
+
+ tbl = early_memremap(initrd, sizeof(*tbl));
+ if (tbl) {
+ phys_initrd_start = tbl->base;
+ phys_initrd_size = tbl->size;
+ early_memunmap(tbl, sizeof(*tbl));
+ }
+ }
+
return 0;
}
@@ -722,6 +752,13 @@ void __init efi_systab_report_header(const efi_table_hdr_t *systab_hdr,
systab_hdr->revision >> 16,
systab_hdr->revision & 0xffff,
vendor);
+
+ if (IS_ENABLED(CONFIG_X86_64) &&
+ systab_hdr->revision > EFI_1_10_SYSTEM_TABLE_REVISION &&
+ !strcmp(vendor, "Apple")) {
+ pr_info("Apple Mac detected, using EFI v1.10 runtime services only\n");
+ efi.runtime_version = EFI_1_10_SYSTEM_TABLE_REVISION;
+ }
}
static __initdata char memory_type_name[][13] = {
@@ -879,6 +916,7 @@ int efi_status_to_err(efi_status_t status)
return err;
}
+EXPORT_SYMBOL_GPL(efi_status_to_err);
static DEFINE_SPINLOCK(efi_mem_reserve_persistent_lock);
static struct linux_efi_memreserve *efi_memreserve_root __ro_after_init;
diff --git a/drivers/firmware/efi/efibc.c b/drivers/firmware/efi/efibc.c
index 15a47539dc56..4f9fb086eab7 100644
--- a/drivers/firmware/efi/efibc.c
+++ b/drivers/firmware/efi/efibc.c
@@ -10,69 +10,54 @@
#include <linux/module.h>
#include <linux/reboot.h>
#include <linux/slab.h>
+#include <linux/ucs2_string.h>
-static void efibc_str_to_str16(const char *str, efi_char16_t *str16)
-{
- size_t i;
-
- for (i = 0; i < strlen(str); i++)
- str16[i] = str[i];
-
- str16[i] = '\0';
-}
+#define MAX_DATA_LEN 512
-static int efibc_set_variable(const char *name, const char *value)
+static int efibc_set_variable(efi_char16_t *name, efi_char16_t *value,
+ unsigned long len)
{
- int ret;
- efi_guid_t guid = LINUX_EFI_LOADER_ENTRY_GUID;
- struct efivar_entry *entry;
- size_t size = (strlen(value) + 1) * sizeof(efi_char16_t);
+ efi_status_t status;
- if (size > sizeof(entry->var.Data)) {
- pr_err("value is too large (%zu bytes) for '%s' EFI variable\n", size, name);
- return -EINVAL;
- }
+ status = efi.set_variable(name, &LINUX_EFI_LOADER_ENTRY_GUID,
+ EFI_VARIABLE_NON_VOLATILE
+ | EFI_VARIABLE_BOOTSERVICE_ACCESS
+ | EFI_VARIABLE_RUNTIME_ACCESS,
+ len * sizeof(efi_char16_t), value);
- entry = kmalloc(sizeof(*entry), GFP_KERNEL);
- if (!entry) {
- pr_err("failed to allocate efivar entry for '%s' EFI variable\n", name);
- return -ENOMEM;
+ if (status != EFI_SUCCESS) {
+ pr_err("failed to set EFI variable: 0x%lx\n", status);
+ return -EIO;
}
-
- efibc_str_to_str16(name, entry->var.VariableName);
- efibc_str_to_str16(value, (efi_char16_t *)entry->var.Data);
- memcpy(&entry->var.VendorGuid, &guid, sizeof(guid));
-
- ret = efivar_entry_set_safe(entry->var.VariableName,
- entry->var.VendorGuid,
- EFI_VARIABLE_NON_VOLATILE
- | EFI_VARIABLE_BOOTSERVICE_ACCESS
- | EFI_VARIABLE_RUNTIME_ACCESS,
- false, size, entry->var.Data);
-
- if (ret)
- pr_err("failed to set %s EFI variable: 0x%x\n",
- name, ret);
-
- kfree(entry);
- return ret;
+ return 0;
}
static int efibc_reboot_notifier_call(struct notifier_block *notifier,
unsigned long event, void *data)
{
- const char *reason = "shutdown";
+ efi_char16_t *reason = event == SYS_RESTART ? L"reboot"
+ : L"shutdown";
+ const u8 *str = data;
+ efi_char16_t *wdata;
+ unsigned long l;
int ret;
- if (event == SYS_RESTART)
- reason = "reboot";
-
- ret = efibc_set_variable("LoaderEntryRebootReason", reason);
+ ret = efibc_set_variable(L"LoaderEntryRebootReason", reason,
+ ucs2_strlen(reason));
if (ret || !data)
return NOTIFY_DONE;
- efibc_set_variable("LoaderEntryOneShot", (char *)data);
+ wdata = kmalloc(MAX_DATA_LEN * sizeof(efi_char16_t), GFP_KERNEL);
+ if (!wdata)
+ return NOTIFY_DONE;
+
+ for (l = 0; l < MAX_DATA_LEN - 1 && str[l] != '\0'; l++)
+ wdata[l] = str[l];
+ wdata[l] = L'\0';
+
+ efibc_set_variable(L"LoaderEntryOneShot", wdata, l);
+ kfree(wdata);
return NOTIFY_DONE;
}
@@ -84,7 +69,7 @@ static int __init efibc_init(void)
{
int ret;
- if (!efivars_kobject() || !efivar_supports_writes())
+ if (!efi_rt_services_supported(EFI_RT_SUPPORTED_SET_VARIABLE))
return -ENODEV;
ret = register_reboot_notifier(&efibc_reboot_notifier);
diff --git a/drivers/firmware/efi/efivars.c b/drivers/firmware/efi/efivars.c
deleted file mode 100644
index e6b16b3a17a8..000000000000
--- a/drivers/firmware/efi/efivars.c
+++ /dev/null
@@ -1,670 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0+
-/*
- * Originally from efivars.c,
- *
- * Copyright (C) 2001,2003,2004 Dell <Matt_Domsch@dell.com>
- * Copyright (C) 2004 Intel Corporation <matthew.e.tolentino@intel.com>
- *
- * This code takes all variables accessible from EFI runtime and
- * exports them via sysfs
- */
-
-#include <linux/efi.h>
-#include <linux/module.h>
-#include <linux/slab.h>
-#include <linux/ucs2_string.h>
-#include <linux/compat.h>
-
-#define EFIVARS_VERSION "0.08"
-#define EFIVARS_DATE "2004-May-17"
-
-MODULE_AUTHOR("Matt Domsch <Matt_Domsch@Dell.com>");
-MODULE_DESCRIPTION("sysfs interface to EFI Variables");
-MODULE_LICENSE("GPL");
-MODULE_VERSION(EFIVARS_VERSION);
-
-static LIST_HEAD(efivar_sysfs_list);
-
-static struct kset *efivars_kset;
-
-static struct bin_attribute *efivars_new_var;
-static struct bin_attribute *efivars_del_var;
-
-struct compat_efi_variable {
- efi_char16_t VariableName[EFI_VAR_NAME_LEN/sizeof(efi_char16_t)];
- efi_guid_t VendorGuid;
- __u32 DataSize;
- __u8 Data[1024];
- __u32 Status;
- __u32 Attributes;
-} __packed;
-
-struct efivar_attribute {
- struct attribute attr;
- ssize_t (*show) (struct efivar_entry *entry, char *buf);
- ssize_t (*store)(struct efivar_entry *entry, const char *buf, size_t count);
-};
-
-#define EFIVAR_ATTR(_name, _mode, _show, _store) \
-struct efivar_attribute efivar_attr_##_name = { \
- .attr = {.name = __stringify(_name), .mode = _mode}, \
- .show = _show, \
- .store = _store, \
-};
-
-#define to_efivar_attr(_attr) container_of(_attr, struct efivar_attribute, attr)
-#define to_efivar_entry(obj) container_of(obj, struct efivar_entry, kobj)
-
-/*
- * Prototype for sysfs creation function
- */
-static int
-efivar_create_sysfs_entry(struct efivar_entry *new_var);
-
-static ssize_t
-efivar_guid_read(struct efivar_entry *entry, char *buf)
-{
- struct efi_variable *var = &entry->var;
- char *str = buf;
-
- if (!entry || !buf)
- return 0;
-
- efi_guid_to_str(&var->VendorGuid, str);
- str += strlen(str);
- str += sprintf(str, "\n");
-
- return str - buf;
-}
-
-static ssize_t
-efivar_attr_read(struct efivar_entry *entry, char *buf)
-{
- struct efi_variable *var = &entry->var;
- unsigned long size = sizeof(var->Data);
- char *str = buf;
- int ret;
-
- if (!entry || !buf)
- return -EINVAL;
-
- ret = efivar_entry_get(entry, &var->Attributes, &size, var->Data);
- var->DataSize = size;
- if (ret)
- return -EIO;
-
- if (var->Attributes & EFI_VARIABLE_NON_VOLATILE)
- str += sprintf(str, "EFI_VARIABLE_NON_VOLATILE\n");
- if (var->Attributes & EFI_VARIABLE_BOOTSERVICE_ACCESS)
- str += sprintf(str, "EFI_VARIABLE_BOOTSERVICE_ACCESS\n");
- if (var->Attributes & EFI_VARIABLE_RUNTIME_ACCESS)
- str += sprintf(str, "EFI_VARIABLE_RUNTIME_ACCESS\n");
- if (var->Attributes & EFI_VARIABLE_HARDWARE_ERROR_RECORD)
- str += sprintf(str, "EFI_VARIABLE_HARDWARE_ERROR_RECORD\n");
- if (var->Attributes & EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS)
- str += sprintf(str,
- "EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS\n");
- if (var->Attributes &
- EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS)
- str += sprintf(str,
- "EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS\n");
- if (var->Attributes & EFI_VARIABLE_APPEND_WRITE)
- str += sprintf(str, "EFI_VARIABLE_APPEND_WRITE\n");
- return str - buf;
-}
-
-static ssize_t
-efivar_size_read(struct efivar_entry *entry, char *buf)
-{
- struct efi_variable *var = &entry->var;
- unsigned long size = sizeof(var->Data);
- char *str = buf;
- int ret;
-
- if (!entry || !buf)
- return -EINVAL;
-
- ret = efivar_entry_get(entry, &var->Attributes, &size, var->Data);
- var->DataSize = size;
- if (ret)
- return -EIO;
-
- str += sprintf(str, "0x%lx\n", var->DataSize);
- return str - buf;
-}
-
-static ssize_t
-efivar_data_read(struct efivar_entry *entry, char *buf)
-{
- struct efi_variable *var = &entry->var;
- unsigned long size = sizeof(var->Data);
- int ret;
-
- if (!entry || !buf)
- return -EINVAL;
-
- ret = efivar_entry_get(entry, &var->Attributes, &size, var->Data);
- var->DataSize = size;
- if (ret)
- return -EIO;
-
- memcpy(buf, var->Data, var->DataSize);
- return var->DataSize;
-}
-
-static inline int
-sanity_check(struct efi_variable *var, efi_char16_t *name, efi_guid_t vendor,
- unsigned long size, u32 attributes, u8 *data)
-{
- /*
- * If only updating the variable data, then the name
- * and guid should remain the same
- */
- if (memcmp(name, var->VariableName, sizeof(var->VariableName)) ||
- efi_guidcmp(vendor, var->VendorGuid)) {
- printk(KERN_ERR "efivars: Cannot edit the wrong variable!\n");
- return -EINVAL;
- }
-
- if ((size <= 0) || (attributes == 0)){
- printk(KERN_ERR "efivars: DataSize & Attributes must be valid!\n");
- return -EINVAL;
- }
-
- if ((attributes & ~EFI_VARIABLE_MASK) != 0 ||
- efivar_validate(vendor, name, data, size) == false) {
- printk(KERN_ERR "efivars: Malformed variable content\n");
- return -EINVAL;
- }
-
- return 0;
-}
-
-static void
-copy_out_compat(struct efi_variable *dst, struct compat_efi_variable *src)
-{
- memcpy(dst->VariableName, src->VariableName, EFI_VAR_NAME_LEN);
- memcpy(dst->Data, src->Data, sizeof(src->Data));
-
- dst->VendorGuid = src->VendorGuid;
- dst->DataSize = src->DataSize;
- dst->Attributes = src->Attributes;
-}
-
-/*
- * We allow each variable to be edited via rewriting the
- * entire efi variable structure.
- */
-static ssize_t
-efivar_store_raw(struct efivar_entry *entry, const char *buf, size_t count)
-{
- struct efi_variable *new_var, *var = &entry->var;
- efi_char16_t *name;
- unsigned long size;
- efi_guid_t vendor;
- u32 attributes;
- u8 *data;
- int err;
-
- if (!entry || !buf)
- return -EINVAL;
-
- if (in_compat_syscall()) {
- struct compat_efi_variable *compat;
-
- if (count != sizeof(*compat))
- return -EINVAL;
-
- compat = (struct compat_efi_variable *)buf;
- attributes = compat->Attributes;
- vendor = compat->VendorGuid;
- name = compat->VariableName;
- size = compat->DataSize;
- data = compat->Data;
-
- err = sanity_check(var, name, vendor, size, attributes, data);
- if (err)
- return err;
-
- copy_out_compat(&entry->var, compat);
- } else {
- if (count != sizeof(struct efi_variable))
- return -EINVAL;
-
- new_var = (struct efi_variable *)buf;
-
- attributes = new_var->Attributes;
- vendor = new_var->VendorGuid;
- name = new_var->VariableName;
- size = new_var->DataSize;
- data = new_var->Data;
-
- err = sanity_check(var, name, vendor, size, attributes, data);
- if (err)
- return err;
-
- memcpy(&entry->var, new_var, count);
- }
-
- err = efivar_entry_set(entry, attributes, size, data, NULL);
- if (err) {
- printk(KERN_WARNING "efivars: set_variable() failed: status=%d\n", err);
- return -EIO;
- }
-
- return count;
-}
-
-static ssize_t
-efivar_show_raw(struct efivar_entry *entry, char *buf)
-{
- struct efi_variable *var = &entry->var;
- struct compat_efi_variable *compat;
- unsigned long datasize = sizeof(var->Data);
- size_t size;
- int ret;
-
- if (!entry || !buf)
- return 0;
-
- ret = efivar_entry_get(entry, &var->Attributes, &datasize, var->Data);
- var->DataSize = datasize;
- if (ret)
- return -EIO;
-
- if (in_compat_syscall()) {
- compat = (struct compat_efi_variable *)buf;
-
- size = sizeof(*compat);
- memcpy(compat->VariableName, var->VariableName,
- EFI_VAR_NAME_LEN);
- memcpy(compat->Data, var->Data, sizeof(compat->Data));
-
- compat->VendorGuid = var->VendorGuid;
- compat->DataSize = var->DataSize;
- compat->Attributes = var->Attributes;
- } else {
- size = sizeof(*var);
- memcpy(buf, var, size);
- }
-
- return size;
-}
-
-/*
- * Generic read/write functions that call the specific functions of
- * the attributes...
- */
-static ssize_t efivar_attr_show(struct kobject *kobj, struct attribute *attr,
- char *buf)
-{
- struct efivar_entry *var = to_efivar_entry(kobj);
- struct efivar_attribute *efivar_attr = to_efivar_attr(attr);
- ssize_t ret = -EIO;
-
- if (!capable(CAP_SYS_ADMIN))
- return -EACCES;
-
- if (efivar_attr->show) {
- ret = efivar_attr->show(var, buf);
- }
- return ret;
-}
-
-static ssize_t efivar_attr_store(struct kobject *kobj, struct attribute *attr,
- const char *buf, size_t count)
-{
- struct efivar_entry *var = to_efivar_entry(kobj);
- struct efivar_attribute *efivar_attr = to_efivar_attr(attr);
- ssize_t ret = -EIO;
-
- if (!capable(CAP_SYS_ADMIN))
- return -EACCES;
-
- if (efivar_attr->store)
- ret = efivar_attr->store(var, buf, count);
-
- return ret;
-}
-
-static const struct sysfs_ops efivar_attr_ops = {
- .show = efivar_attr_show,
- .store = efivar_attr_store,
-};
-
-static void efivar_release(struct kobject *kobj)
-{
- struct efivar_entry *var = to_efivar_entry(kobj);
- kfree(var);
-}
-
-static EFIVAR_ATTR(guid, 0400, efivar_guid_read, NULL);
-static EFIVAR_ATTR(attributes, 0400, efivar_attr_read, NULL);
-static EFIVAR_ATTR(size, 0400, efivar_size_read, NULL);
-static EFIVAR_ATTR(data, 0400, efivar_data_read, NULL);
-static EFIVAR_ATTR(raw_var, 0600, efivar_show_raw, efivar_store_raw);
-
-static struct attribute *def_attrs[] = {
- &efivar_attr_guid.attr,
- &efivar_attr_size.attr,
- &efivar_attr_attributes.attr,
- &efivar_attr_data.attr,
- &efivar_attr_raw_var.attr,
- NULL,
-};
-
-static struct kobj_type efivar_ktype = {
- .release = efivar_release,
- .sysfs_ops = &efivar_attr_ops,
- .default_attrs = def_attrs,
-};
-
-static ssize_t efivar_create(struct file *filp, struct kobject *kobj,
- struct bin_attribute *bin_attr,
- char *buf, loff_t pos, size_t count)
-{
- struct compat_efi_variable *compat = (struct compat_efi_variable *)buf;
- struct efi_variable *new_var = (struct efi_variable *)buf;
- struct efivar_entry *new_entry;
- bool need_compat = in_compat_syscall();
- efi_char16_t *name;
- unsigned long size;
- u32 attributes;
- u8 *data;
- int err;
-
- if (!capable(CAP_SYS_ADMIN))
- return -EACCES;
-
- if (need_compat) {
- if (count != sizeof(*compat))
- return -EINVAL;
-
- attributes = compat->Attributes;
- name = compat->VariableName;
- size = compat->DataSize;
- data = compat->Data;
- } else {
- if (count != sizeof(*new_var))
- return -EINVAL;
-
- attributes = new_var->Attributes;
- name = new_var->VariableName;
- size = new_var->DataSize;
- data = new_var->Data;
- }
-
- if ((attributes & ~EFI_VARIABLE_MASK) != 0 ||
- efivar_validate(new_var->VendorGuid, name, data,
- size) == false) {
- printk(KERN_ERR "efivars: Malformed variable content\n");
- return -EINVAL;
- }
-
- new_entry = kzalloc(sizeof(*new_entry), GFP_KERNEL);
- if (!new_entry)
- return -ENOMEM;
-
- if (need_compat)
- copy_out_compat(&new_entry->var, compat);
- else
- memcpy(&new_entry->var, new_var, sizeof(*new_var));
-
- err = efivar_entry_set(new_entry, attributes, size,
- data, &efivar_sysfs_list);
- if (err) {
- if (err == -EEXIST)
- err = -EINVAL;
- goto out;
- }
-
- if (efivar_create_sysfs_entry(new_entry)) {
- printk(KERN_WARNING "efivars: failed to create sysfs entry.\n");
- kfree(new_entry);
- }
- return count;
-
-out:
- kfree(new_entry);
- return err;
-}
-
-static ssize_t efivar_delete(struct file *filp, struct kobject *kobj,
- struct bin_attribute *bin_attr,
- char *buf, loff_t pos, size_t count)
-{
- struct efi_variable *del_var = (struct efi_variable *)buf;
- struct compat_efi_variable *compat;
- struct efivar_entry *entry;
- efi_char16_t *name;
- efi_guid_t vendor;
- int err = 0;
-
- if (!capable(CAP_SYS_ADMIN))
- return -EACCES;
-
- if (in_compat_syscall()) {
- if (count != sizeof(*compat))
- return -EINVAL;
-
- compat = (struct compat_efi_variable *)buf;
- name = compat->VariableName;
- vendor = compat->VendorGuid;
- } else {
- if (count != sizeof(*del_var))
- return -EINVAL;
-
- name = del_var->VariableName;
- vendor = del_var->VendorGuid;
- }
-
- if (efivar_entry_iter_begin())
- return -EINTR;
- entry = efivar_entry_find(name, vendor, &efivar_sysfs_list, true);
- if (!entry)
- err = -EINVAL;
- else if (__efivar_entry_delete(entry))
- err = -EIO;
-
- if (err) {
- efivar_entry_iter_end();
- return err;
- }
-
- if (!entry->scanning) {
- efivar_entry_iter_end();
- efivar_unregister(entry);
- } else
- efivar_entry_iter_end();
-
- /* It's dead Jim.... */
- return count;
-}
-
-/**
- * efivar_create_sysfs_entry - create a new entry in sysfs
- * @new_var: efivar entry to create
- *
- * Returns 0 on success, negative error code on failure
- */
-static int
-efivar_create_sysfs_entry(struct efivar_entry *new_var)
-{
- int short_name_size;
- char *short_name;
- unsigned long utf8_name_size;
- efi_char16_t *variable_name = new_var->var.VariableName;
- int ret;
-
- /*
- * Length of the variable bytes in UTF8, plus the '-' separator,
- * plus the GUID, plus trailing NUL
- */
- utf8_name_size = ucs2_utf8size(variable_name);
- short_name_size = utf8_name_size + 1 + EFI_VARIABLE_GUID_LEN + 1;
-
- short_name = kmalloc(short_name_size, GFP_KERNEL);
- if (!short_name)
- return -ENOMEM;
-
- ucs2_as_utf8(short_name, variable_name, short_name_size);
-
- /* This is ugly, but necessary to separate one vendor's
- private variables from another's. */
- short_name[utf8_name_size] = '-';
- efi_guid_to_str(&new_var->var.VendorGuid,
- short_name + utf8_name_size + 1);
-
- new_var->kobj.kset = efivars_kset;
-
- ret = kobject_init_and_add(&new_var->kobj, &efivar_ktype,
- NULL, "%s", short_name);
- kfree(short_name);
- if (ret) {
- kobject_put(&new_var->kobj);
- return ret;
- }
-
- kobject_uevent(&new_var->kobj, KOBJ_ADD);
- if (efivar_entry_add(new_var, &efivar_sysfs_list)) {
- efivar_unregister(new_var);
- return -EINTR;
- }
-
- return 0;
-}
-
-static int
-create_efivars_bin_attributes(void)
-{
- struct bin_attribute *attr;
- int error;
-
- /* new_var */
- attr = kzalloc(sizeof(*attr), GFP_KERNEL);
- if (!attr)
- return -ENOMEM;
-
- attr->attr.name = "new_var";
- attr->attr.mode = 0200;
- attr->write = efivar_create;
- efivars_new_var = attr;
-
- /* del_var */
- attr = kzalloc(sizeof(*attr), GFP_KERNEL);
- if (!attr) {
- error = -ENOMEM;
- goto out_free;
- }
- attr->attr.name = "del_var";
- attr->attr.mode = 0200;
- attr->write = efivar_delete;
- efivars_del_var = attr;
-
- sysfs_bin_attr_init(efivars_new_var);
- sysfs_bin_attr_init(efivars_del_var);
-
- /* Register */
- error = sysfs_create_bin_file(&efivars_kset->kobj, efivars_new_var);
- if (error) {
- printk(KERN_ERR "efivars: unable to create new_var sysfs file"
- " due to error %d\n", error);
- goto out_free;
- }
-
- error = sysfs_create_bin_file(&efivars_kset->kobj, efivars_del_var);
- if (error) {
- printk(KERN_ERR "efivars: unable to create del_var sysfs file"
- " due to error %d\n", error);
- sysfs_remove_bin_file(&efivars_kset->kobj, efivars_new_var);
- goto out_free;
- }
-
- return 0;
-out_free:
- kfree(efivars_del_var);
- efivars_del_var = NULL;
- kfree(efivars_new_var);
- efivars_new_var = NULL;
- return error;
-}
-
-static int efivars_sysfs_callback(efi_char16_t *name, efi_guid_t vendor,
- unsigned long name_size, void *data)
-{
- struct efivar_entry *entry;
-
- entry = kzalloc(sizeof(*entry), GFP_KERNEL);
- if (!entry)
- return -ENOMEM;
-
- memcpy(entry->var.VariableName, name, name_size);
- memcpy(&(entry->var.VendorGuid), &vendor, sizeof(efi_guid_t));
-
- efivar_create_sysfs_entry(entry);
-
- return 0;
-}
-
-static int efivar_sysfs_destroy(struct efivar_entry *entry, void *data)
-{
- int err = efivar_entry_remove(entry);
-
- if (err)
- return err;
- efivar_unregister(entry);
- return 0;
-}
-
-static void efivars_sysfs_exit(void)
-{
- /* Remove all entries and destroy */
- int err;
-
- err = __efivar_entry_iter(efivar_sysfs_destroy, &efivar_sysfs_list,
- NULL, NULL);
- if (err) {
- pr_err("efivars: Failed to destroy sysfs entries\n");
- return;
- }
-
- if (efivars_new_var)
- sysfs_remove_bin_file(&efivars_kset->kobj, efivars_new_var);
- if (efivars_del_var)
- sysfs_remove_bin_file(&efivars_kset->kobj, efivars_del_var);
- kfree(efivars_new_var);
- kfree(efivars_del_var);
- kset_unregister(efivars_kset);
-}
-
-static int efivars_sysfs_init(void)
-{
- struct kobject *parent_kobj = efivars_kobject();
- int error = 0;
-
- /* No efivars has been registered yet */
- if (!parent_kobj || !efivar_supports_writes())
- return 0;
-
- printk(KERN_INFO "EFI Variables Facility v%s %s\n", EFIVARS_VERSION,
- EFIVARS_DATE);
-
- efivars_kset = kset_create_and_add("vars", NULL, parent_kobj);
- if (!efivars_kset) {
- printk(KERN_ERR "efivars: Subsystem registration failed.\n");
- return -ENOMEM;
- }
-
- efivar_init(efivars_sysfs_callback, NULL, true, &efivar_sysfs_list);
-
- error = create_efivars_bin_attributes();
- if (error) {
- efivars_sysfs_exit();
- return error;
- }
-
- return 0;
-}
-
-module_init(efivars_sysfs_init);
-module_exit(efivars_sysfs_exit);
diff --git a/drivers/firmware/efi/esrt.c b/drivers/firmware/efi/esrt.c
index d5915272141f..2a2f52b017e7 100644
--- a/drivers/firmware/efi/esrt.c
+++ b/drivers/firmware/efi/esrt.c
@@ -146,6 +146,8 @@ static struct attribute *esre1_attrs[] = {
&esre_last_attempt_status.attr,
NULL
};
+ATTRIBUTE_GROUPS(esre1);
+
static void esre_release(struct kobject *kobj)
{
struct esre_entry *entry = to_entry(kobj);
@@ -157,7 +159,7 @@ static void esre_release(struct kobject *kobj)
static struct kobj_type esre1_ktype = {
.release = esre_release,
.sysfs_ops = &esre_attr_ops,
- .default_attrs = esre1_attrs,
+ .default_groups = esre1_groups,
};
diff --git a/drivers/firmware/efi/libstub/Makefile b/drivers/firmware/efi/libstub/Makefile
index d0537573501e..ef5045a53ce0 100644
--- a/drivers/firmware/efi/libstub/Makefile
+++ b/drivers/firmware/efi/libstub/Makefile
@@ -26,8 +26,10 @@ cflags-$(CONFIG_ARM) := $(subst $(CC_FLAGS_FTRACE),,$(KBUILD_CFLAGS)) \
$(call cc-option,-mno-single-pic-base)
cflags-$(CONFIG_RISCV) := $(subst $(CC_FLAGS_FTRACE),,$(KBUILD_CFLAGS)) \
-fpic
+cflags-$(CONFIG_LOONGARCH) := $(subst $(CC_FLAGS_FTRACE),,$(KBUILD_CFLAGS)) \
+ -fpie
-cflags-$(CONFIG_EFI_GENERIC_STUB) += -I$(srctree)/scripts/dtc/libfdt
+cflags-$(CONFIG_EFI_PARAMS_FROM_FDT) += -I$(srctree)/scripts/dtc/libfdt
KBUILD_CFLAGS := $(cflags-y) -Os -DDISABLE_BRANCH_PROFILING \
-include $(srctree)/include/linux/hidden.h \
@@ -37,8 +39,17 @@ KBUILD_CFLAGS := $(cflags-y) -Os -DDISABLE_BRANCH_PROFILING \
$(call cc-option,-fno-addrsig) \
-D__DISABLE_EXPORTS
+#
+# struct randomization only makes sense for Linux internal types, which the EFI
+# stub code never touches, so let's turn off struct randomization for the stub
+# altogether
+#
+KBUILD_CFLAGS := $(filter-out $(RANDSTRUCT_CFLAGS), $(KBUILD_CFLAGS))
+
# remove SCS flags from all objects in this directory
KBUILD_CFLAGS := $(filter-out $(CC_FLAGS_SCS), $(KBUILD_CFLAGS))
+# disable CFI
+KBUILD_CFLAGS := $(filter-out $(CC_FLAGS_CFI), $(KBUILD_CFLAGS))
# disable LTO
KBUILD_CFLAGS := $(filter-out $(CC_FLAGS_LTO), $(KBUILD_CFLAGS))
@@ -46,6 +57,7 @@ GCOV_PROFILE := n
# Sanitizer runtimes are unavailable and cannot be linked here.
KASAN_SANITIZE := n
KCSAN_SANITIZE := n
+KMSAN_SANITIZE := n
UBSAN_SANITIZE := n
OBJECT_FILES_NON_STANDARD := y
@@ -57,21 +69,32 @@ lib-y := efi-stub-helper.o gop.o secureboot.o tpm.o \
skip_spaces.o lib-cmdline.o lib-ctype.o \
alignedmem.o relocate.o vsprintf.o
-# include the stub's generic dependencies from lib/ when building for ARM/arm64
-efi-deps-y := fdt_rw.c fdt_ro.c fdt_wip.c fdt.c fdt_empty_tree.c fdt_sw.c
+# include the stub's libfdt dependencies from lib/ when needed
+libfdt-deps := fdt_rw.c fdt_ro.c fdt_wip.c fdt.c \
+ fdt_empty_tree.c fdt_sw.c
+
+lib-$(CONFIG_EFI_PARAMS_FROM_FDT) += fdt.o \
+ $(patsubst %.c,lib-%.o,$(libfdt-deps))
$(obj)/lib-%.o: $(srctree)/lib/%.c FORCE
$(call if_changed_rule,cc_o_c)
-lib-$(CONFIG_EFI_GENERIC_STUB) += efi-stub.o fdt.o string.o \
- $(patsubst %.c,lib-%.o,$(efi-deps-y))
+lib-$(CONFIG_EFI_GENERIC_STUB) += efi-stub.o string.o intrinsics.o systable.o
lib-$(CONFIG_ARM) += arm32-stub.o
-lib-$(CONFIG_ARM64) += arm64-stub.o
+lib-$(CONFIG_ARM64) += arm64-stub.o smbios.o
lib-$(CONFIG_X86) += x86-stub.o
lib-$(CONFIG_RISCV) += riscv-stub.o
+lib-$(CONFIG_LOONGARCH) += loongarch-stub.o
+
CFLAGS_arm32-stub.o := -DTEXT_OFFSET=$(TEXT_OFFSET)
+zboot-obj-$(CONFIG_RISCV) := lib-clz_ctz.o lib-ashldi3.o
+lib-$(CONFIG_EFI_ZBOOT) += zboot.o $(zboot-obj-y)
+
+extra-y := $(lib-y)
+lib-y := $(patsubst %.o,%.stub.o,$(lib-y))
+
# Even when -mbranch-protection=none is set, Clang will generate a
# .note.gnu.property for code-less object files (like lib/ctype.c),
# so work around this by explicitly removing the unwanted section.
@@ -111,9 +134,6 @@ STUBCOPY_RELOC-$(CONFIG_ARM) := R_ARM_ABS
# a verification pass to see if any absolute relocations exist in any of the
# object files.
#
-extra-y := $(lib-y)
-lib-y := $(patsubst %.o,%.stub.o,$(lib-y))
-
STUBCOPY_FLAGS-$(CONFIG_ARM64) += --prefix-alloc-sections=.init \
--prefix-symbols=__efistub_
STUBCOPY_RELOC-$(CONFIG_ARM64) := R_AARCH64_ABS
@@ -125,6 +145,12 @@ STUBCOPY_FLAGS-$(CONFIG_RISCV) += --prefix-alloc-sections=.init \
--prefix-symbols=__efistub_
STUBCOPY_RELOC-$(CONFIG_RISCV) := R_RISCV_HI20
+# For LoongArch, keep all the symbols in .init section and make sure that no
+# absolute symbols references exist.
+STUBCOPY_FLAGS-$(CONFIG_LOONGARCH) += --prefix-alloc-sections=.init \
+ --prefix-symbols=__efistub_
+STUBCOPY_RELOC-$(CONFIG_LOONGARCH) := R_LARCH_MARK_LA
+
$(obj)/%.stub.o: $(obj)/%.o FORCE
$(call if_changed,stubcopy)
diff --git a/drivers/firmware/efi/libstub/Makefile.zboot b/drivers/firmware/efi/libstub/Makefile.zboot
new file mode 100644
index 000000000000..3340b385a05b
--- /dev/null
+++ b/drivers/firmware/efi/libstub/Makefile.zboot
@@ -0,0 +1,49 @@
+# SPDX-License-Identifier: GPL-2.0
+
+# to be include'd by arch/$(ARCH)/boot/Makefile after setting
+# EFI_ZBOOT_PAYLOAD, EFI_ZBOOT_BFD_TARGET and EFI_ZBOOT_MACH_TYPE
+
+comp-type-$(CONFIG_KERNEL_GZIP) := gzip
+comp-type-$(CONFIG_KERNEL_LZ4) := lz4
+comp-type-$(CONFIG_KERNEL_LZMA) := lzma
+comp-type-$(CONFIG_KERNEL_LZO) := lzo
+comp-type-$(CONFIG_KERNEL_XZ) := xzkern
+comp-type-$(CONFIG_KERNEL_ZSTD) := zstd22
+
+# in GZIP, the appended le32 carrying the uncompressed size is part of the
+# format, but in other cases, we just append it at the end for convenience,
+# causing the original tools to complain when checking image integrity.
+# So disregard it when calculating the payload size in the zimage header.
+zboot-method-y := $(comp-type-y)_with_size
+zboot-size-len-y := 4
+
+zboot-method-$(CONFIG_KERNEL_GZIP) := gzip
+zboot-size-len-$(CONFIG_KERNEL_GZIP) := 0
+
+$(obj)/vmlinuz: $(obj)/$(EFI_ZBOOT_PAYLOAD) FORCE
+ $(call if_changed,$(zboot-method-y))
+
+OBJCOPYFLAGS_vmlinuz.o := -I binary -O $(EFI_ZBOOT_BFD_TARGET) \
+ --rename-section .data=.gzdata,load,alloc,readonly,contents
+$(obj)/vmlinuz.o: $(obj)/vmlinuz FORCE
+ $(call if_changed,objcopy)
+
+AFLAGS_zboot-header.o += -DMACHINE_TYPE=IMAGE_FILE_MACHINE_$(EFI_ZBOOT_MACH_TYPE) \
+ -DZBOOT_EFI_PATH="\"$(realpath $(obj)/vmlinuz.efi.elf)\"" \
+ -DZBOOT_SIZE_LEN=$(zboot-size-len-y) \
+ -DCOMP_TYPE="\"$(comp-type-y)\""
+
+$(obj)/zboot-header.o: $(srctree)/drivers/firmware/efi/libstub/zboot-header.S FORCE
+ $(call if_changed_rule,as_o_S)
+
+ZBOOT_DEPS := $(obj)/zboot-header.o $(objtree)/drivers/firmware/efi/libstub/lib.a
+
+LDFLAGS_vmlinuz.efi.elf := -T $(srctree)/drivers/firmware/efi/libstub/zboot.lds
+$(obj)/vmlinuz.efi.elf: $(obj)/vmlinuz.o $(ZBOOT_DEPS) FORCE
+ $(call if_changed,ld)
+
+OBJCOPYFLAGS_vmlinuz.efi := -O binary
+$(obj)/vmlinuz.efi: $(obj)/vmlinuz.efi.elf FORCE
+ $(call if_changed,objcopy)
+
+targets += zboot-header.o vmlinuz vmlinuz.o vmlinuz.efi.elf vmlinuz.efi
diff --git a/drivers/firmware/efi/libstub/arm32-stub.c b/drivers/firmware/efi/libstub/arm32-stub.c
index 4b5b2403b3a0..0131e3aaa605 100644
--- a/drivers/firmware/efi/libstub/arm32-stub.c
+++ b/drivers/firmware/efi/libstub/arm32-stub.c
@@ -117,7 +117,8 @@ efi_status_t handle_kernel_image(unsigned long *image_addr,
unsigned long *image_size,
unsigned long *reserve_addr,
unsigned long *reserve_size,
- efi_loaded_image_t *image)
+ efi_loaded_image_t *image,
+ efi_handle_t image_handle)
{
const int slack = TEXT_OFFSET - 5 * PAGE_SIZE;
int alloc_size = MAX_UNCOMP_KERNEL_SIZE + EFI_PHYS_ALIGN;
diff --git a/drivers/firmware/efi/libstub/arm64-stub.c b/drivers/firmware/efi/libstub/arm64-stub.c
index 2363fee9211c..f9de5217ea65 100644
--- a/drivers/firmware/efi/libstub/arm64-stub.c
+++ b/drivers/firmware/efi/libstub/arm64-stub.c
@@ -15,16 +15,39 @@
#include "efistub.h"
+static bool system_needs_vamap(void)
+{
+ const u8 *type1_family = efi_get_smbios_string(1, family);
+
+ /*
+ * Ampere Altra machines crash in SetTime() if SetVirtualAddressMap()
+ * has not been called prior.
+ */
+ if (!type1_family || strcmp(type1_family, "Altra"))
+ return false;
+
+ efi_warn("Working around broken SetVirtualAddressMap()\n");
+ return true;
+}
+
efi_status_t check_platform_features(void)
{
u64 tg;
+ /*
+ * If we have 48 bits of VA space for TTBR0 mappings, we can map the
+ * UEFI runtime regions 1:1 and so calling SetVirtualAddressMap() is
+ * unnecessary.
+ */
+ if (VA_BITS_MIN >= 48 && !system_needs_vamap())
+ efi_novamap = true;
+
/* UEFI mandates support for 4 KB granularity, no need to check */
if (IS_ENABLED(CONFIG_ARM64_4K_PAGES))
return EFI_SUCCESS;
- tg = (read_cpuid(ID_AA64MMFR0_EL1) >> ID_AA64MMFR0_TGRAN_SHIFT) & 0xf;
- if (tg < ID_AA64MMFR0_TGRAN_SUPPORTED_MIN || tg > ID_AA64MMFR0_TGRAN_SUPPORTED_MAX) {
+ tg = (read_cpuid(ID_AA64MMFR0_EL1) >> ID_AA64MMFR0_EL1_TGRAN_SHIFT) & 0xf;
+ if (tg < ID_AA64MMFR0_EL1_TGRAN_SUPPORTED_MIN || tg > ID_AA64MMFR0_EL1_TGRAN_SUPPORTED_MAX) {
if (IS_ENABLED(CONFIG_ARM64_64K_PAGES))
efi_err("This 64 KB granular kernel is not supported by your CPU\n");
else
@@ -42,26 +65,17 @@ efi_status_t check_platform_features(void)
*/
static bool check_image_region(u64 base, u64 size)
{
- unsigned long map_size, desc_size, buff_size;
- efi_memory_desc_t *memory_map;
- struct efi_boot_memmap map;
+ struct efi_boot_memmap *map;
efi_status_t status;
bool ret = false;
int map_offset;
- map.map = &memory_map;
- map.map_size = &map_size;
- map.desc_size = &desc_size;
- map.desc_ver = NULL;
- map.key_ptr = NULL;
- map.buff_size = &buff_size;
-
- status = efi_get_memory_map(&map);
+ status = efi_get_memory_map(&map, false);
if (status != EFI_SUCCESS)
return false;
- for (map_offset = 0; map_offset < map_size; map_offset += desc_size) {
- efi_memory_desc_t *md = (void *)memory_map + map_offset;
+ for (map_offset = 0; map_offset < map->map_size; map_offset += map->desc_size) {
+ efi_memory_desc_t *md = (void *)map->map + map_offset;
u64 end = md->phys_addr + md->num_pages * EFI_PAGE_SIZE;
/*
@@ -74,7 +88,7 @@ static bool check_image_region(u64 base, u64 size)
}
}
- efi_bs_call(free_pool, memory_map);
+ efi_bs_call(free_pool, map);
return ret;
}
@@ -83,7 +97,8 @@ efi_status_t handle_kernel_image(unsigned long *image_addr,
unsigned long *image_size,
unsigned long *reserve_addr,
unsigned long *reserve_size,
- efi_loaded_image_t *image)
+ efi_loaded_image_t *image,
+ efi_handle_t image_handle)
{
efi_status_t status;
unsigned long kernel_size, kernel_memsize = 0;
@@ -100,7 +115,15 @@ efi_status_t handle_kernel_image(unsigned long *image_addr,
u64 min_kimg_align = efi_nokaslr ? MIN_KIMG_ALIGN : EFI_KIMG_ALIGN;
if (IS_ENABLED(CONFIG_RANDOMIZE_BASE)) {
- if (!efi_nokaslr) {
+ efi_guid_t li_fixed_proto = LINUX_EFI_LOADED_IMAGE_FIXED_GUID;
+ void *p;
+
+ if (efi_nokaslr) {
+ efi_info("KASLR disabled on kernel command line\n");
+ } else if (efi_bs_call(handle_protocol, image_handle,
+ &li_fixed_proto, &p) == EFI_SUCCESS) {
+ efi_info("Image placement fixed by loader\n");
+ } else {
status = efi_get_random_bytes(sizeof(phys_seed),
(u8 *)&phys_seed);
if (status == EFI_NOT_FOUND) {
@@ -111,17 +134,15 @@ efi_status_t handle_kernel_image(unsigned long *image_addr,
status);
efi_nokaslr = true;
}
- } else {
- efi_info("KASLR disabled on kernel command line\n");
}
}
if (image->image_base != _text)
efi_err("FIRMWARE BUG: efi_loaded_image_t::image_base has bogus value\n");
- if (!IS_ALIGNED((u64)_text, EFI_KIMG_ALIGN))
- efi_err("FIRMWARE BUG: kernel image not aligned on %ldk boundary\n",
- EFI_KIMG_ALIGN >> 10);
+ if (!IS_ALIGNED((u64)_text, SEGMENT_ALIGN))
+ efi_err("FIRMWARE BUG: kernel image not aligned on %dk boundary\n",
+ SEGMENT_ALIGN >> 10);
kernel_size = _edata - _text;
kernel_memsize = kernel_size + (_end - _edata);
diff --git a/drivers/firmware/efi/libstub/efi-stub-helper.c b/drivers/firmware/efi/libstub/efi-stub-helper.c
index d489bdc645fe..0c493521b25b 100644
--- a/drivers/firmware/efi/libstub/efi-stub-helper.c
+++ b/drivers/firmware/efi/libstub/efi-stub-helper.c
@@ -20,10 +20,10 @@
bool efi_nochunk;
bool efi_nokaslr = !IS_ENABLED(CONFIG_RANDOMIZE_BASE);
-bool efi_noinitrd;
int efi_loglevel = CONSOLE_LOGLEVEL_DEFAULT;
bool efi_novamap;
+static bool efi_noinitrd;
static bool efi_nosoftreserve;
static bool efi_disable_pci_dma = IS_ENABLED(CONFIG_EFI_DISABLE_PCI_DMA);
@@ -218,7 +218,7 @@ efi_status_t efi_parse_options(char const *cmdline)
efi_noinitrd = true;
} else if (!strcmp(param, "efi") && val) {
efi_nochunk = parse_option_str(val, "nochunk");
- efi_novamap = parse_option_str(val, "novamap");
+ efi_novamap |= parse_option_str(val, "novamap");
efi_nosoftreserve = IS_ENABLED(CONFIG_EFI_SOFT_RESERVE) &&
parse_option_str(val, "nosoftreserve");
@@ -310,7 +310,7 @@ bool efi_load_option_unpack(efi_load_option_unpacked_t *dest,
*
* Detect this case and extract OptionalData.
*/
-void efi_apply_loadoptions_quirk(const void **load_options, int *load_options_size)
+void efi_apply_loadoptions_quirk(const void **load_options, u32 *load_options_size)
{
const efi_load_option_t *load_option = *load_options;
efi_load_option_unpacked_t load_option_unpacked;
@@ -334,6 +334,85 @@ void efi_apply_loadoptions_quirk(const void **load_options, int *load_options_si
*load_options_size = load_option_unpacked.optional_data_size;
}
+enum efistub_event {
+ EFISTUB_EVT_INITRD,
+ EFISTUB_EVT_LOAD_OPTIONS,
+ EFISTUB_EVT_COUNT,
+};
+
+#define STR_WITH_SIZE(s) sizeof(s), s
+
+static const struct {
+ u32 pcr_index;
+ u32 event_id;
+ u32 event_data_len;
+ u8 event_data[52];
+} events[] = {
+ [EFISTUB_EVT_INITRD] = {
+ 9,
+ INITRD_EVENT_TAG_ID,
+ STR_WITH_SIZE("Linux initrd")
+ },
+ [EFISTUB_EVT_LOAD_OPTIONS] = {
+ 9,
+ LOAD_OPTIONS_EVENT_TAG_ID,
+ STR_WITH_SIZE("LOADED_IMAGE::LoadOptions")
+ },
+};
+
+static efi_status_t efi_measure_tagged_event(unsigned long load_addr,
+ unsigned long load_size,
+ enum efistub_event event)
+{
+ efi_guid_t tcg2_guid = EFI_TCG2_PROTOCOL_GUID;
+ efi_tcg2_protocol_t *tcg2 = NULL;
+ efi_status_t status;
+
+ efi_bs_call(locate_protocol, &tcg2_guid, NULL, (void **)&tcg2);
+ if (tcg2) {
+ struct efi_measured_event {
+ efi_tcg2_event_t event_data;
+ efi_tcg2_tagged_event_t tagged_event;
+ u8 tagged_event_data[];
+ } *evt;
+ int size = sizeof(*evt) + events[event].event_data_len;
+
+ status = efi_bs_call(allocate_pool, EFI_LOADER_DATA, size,
+ (void **)&evt);
+ if (status != EFI_SUCCESS)
+ goto fail;
+
+ evt->event_data = (struct efi_tcg2_event){
+ .event_size = size,
+ .event_header.header_size = sizeof(evt->event_data.event_header),
+ .event_header.header_version = EFI_TCG2_EVENT_HEADER_VERSION,
+ .event_header.pcr_index = events[event].pcr_index,
+ .event_header.event_type = EV_EVENT_TAG,
+ };
+
+ evt->tagged_event = (struct efi_tcg2_tagged_event){
+ .tagged_event_id = events[event].event_id,
+ .tagged_event_data_size = events[event].event_data_len,
+ };
+
+ memcpy(evt->tagged_event_data, events[event].event_data,
+ events[event].event_data_len);
+
+ status = efi_call_proto(tcg2, hash_log_extend_event, 0,
+ load_addr, load_size, &evt->event_data);
+ efi_bs_call(free_pool, evt);
+
+ if (status != EFI_SUCCESS)
+ goto fail;
+ return EFI_SUCCESS;
+ }
+
+ return EFI_UNSUPPORTED;
+fail:
+ efi_warn("Failed to measure data for event %d: 0x%lx\n", event, status);
+ return status;
+}
+
/*
* Convert the unicode UEFI command line to ASCII to pass to kernel.
* Size of memory allocated return in *cmd_line_len.
@@ -341,21 +420,26 @@ void efi_apply_loadoptions_quirk(const void **load_options, int *load_options_si
*/
char *efi_convert_cmdline(efi_loaded_image_t *image, int *cmd_line_len)
{
- const u16 *s2;
- unsigned long cmdline_addr = 0;
- int options_chars = efi_table_attr(image, load_options_size);
- const u16 *options = efi_table_attr(image, load_options);
+ const efi_char16_t *options = efi_table_attr(image, load_options);
+ u32 options_size = efi_table_attr(image, load_options_size);
int options_bytes = 0, safe_options_bytes = 0; /* UTF-8 bytes */
+ unsigned long cmdline_addr = 0;
+ const efi_char16_t *s2;
bool in_quote = false;
efi_status_t status;
+ u32 options_chars;
- efi_apply_loadoptions_quirk((const void **)&options, &options_chars);
- options_chars /= sizeof(*options);
+ if (options_size > 0)
+ efi_measure_tagged_event((unsigned long)options, options_size,
+ EFISTUB_EVT_LOAD_OPTIONS);
+
+ efi_apply_loadoptions_quirk((const void **)&options, &options_size);
+ options_chars = options_size / sizeof(efi_char16_t);
if (options) {
s2 = options;
while (options_bytes < COMMAND_LINE_SIZE && options_chars--) {
- u16 c = *s2++;
+ efi_char16_t c = *s2++;
if (c < 0x80) {
if (c == L'\0' || c == L'\n')
@@ -419,7 +503,6 @@ char *efi_convert_cmdline(efi_loaded_image_t *image, int *cmd_line_len)
/**
* efi_exit_boot_services() - Exit boot services
* @handle: handle of the exiting image
- * @map: pointer to receive the memory map
* @priv: argument to be passed to @priv_func
* @priv_func: function to process the memory map before exiting boot services
*
@@ -432,26 +515,26 @@ char *efi_convert_cmdline(efi_loaded_image_t *image, int *cmd_line_len)
*
* Return: status code
*/
-efi_status_t efi_exit_boot_services(void *handle,
- struct efi_boot_memmap *map,
- void *priv,
+efi_status_t efi_exit_boot_services(void *handle, void *priv,
efi_exit_boot_map_processing priv_func)
{
+ struct efi_boot_memmap *map;
efi_status_t status;
- status = efi_get_memory_map(map);
-
+ status = efi_get_memory_map(&map, true);
if (status != EFI_SUCCESS)
- goto fail;
+ return status;
status = priv_func(map, priv);
- if (status != EFI_SUCCESS)
- goto free_map;
+ if (status != EFI_SUCCESS) {
+ efi_bs_call(free_pool, map);
+ return status;
+ }
if (efi_disable_pci_dma)
efi_pci_disable_bridge_busmaster();
- status = efi_bs_call(exit_boot_services, handle, *map->key_ptr);
+ status = efi_bs_call(exit_boot_services, handle, map->map_key);
if (status == EFI_INVALID_PARAMETER) {
/*
@@ -467,35 +550,26 @@ efi_status_t efi_exit_boot_services(void *handle,
* buffer should account for any changes in the map so the call
* to get_memory_map() is expected to succeed here.
*/
- *map->map_size = *map->buff_size;
+ map->map_size = map->buff_size;
status = efi_bs_call(get_memory_map,
- map->map_size,
- *map->map,
- map->key_ptr,
- map->desc_size,
- map->desc_ver);
+ &map->map_size,
+ &map->map,
+ &map->map_key,
+ &map->desc_size,
+ &map->desc_ver);
/* exit_boot_services() was called, thus cannot free */
if (status != EFI_SUCCESS)
- goto fail;
+ return status;
status = priv_func(map, priv);
/* exit_boot_services() was called, thus cannot free */
if (status != EFI_SUCCESS)
- goto fail;
+ return status;
- status = efi_bs_call(exit_boot_services, handle, *map->key_ptr);
+ status = efi_bs_call(exit_boot_services, handle, map->map_key);
}
- /* exit_boot_services() was called, thus cannot free */
- if (status != EFI_SUCCESS)
- goto fail;
-
- return EFI_SUCCESS;
-
-free_map:
- efi_bs_call(free_pool, *map->map);
-fail:
return status;
}
@@ -560,20 +634,16 @@ static const struct {
* * %EFI_SUCCESS if the initrd was loaded successfully, in which
* case @load_addr and @load_size are assigned accordingly
* * %EFI_NOT_FOUND if no LoadFile2 protocol exists on the initrd device path
- * * %EFI_INVALID_PARAMETER if load_addr == NULL or load_size == NULL
* * %EFI_OUT_OF_RESOURCES if memory allocation failed
* * %EFI_LOAD_ERROR in all other cases
*/
static
-efi_status_t efi_load_initrd_dev_path(unsigned long *load_addr,
- unsigned long *load_size,
+efi_status_t efi_load_initrd_dev_path(struct linux_efi_initrd *initrd,
unsigned long max)
{
efi_guid_t lf2_proto_guid = EFI_LOAD_FILE2_PROTOCOL_GUID;
efi_device_path_protocol_t *dp;
efi_load_file2_protocol_t *lf2;
- unsigned long initrd_addr;
- unsigned long initrd_size;
efi_handle_t handle;
efi_status_t status;
@@ -587,75 +657,98 @@ efi_status_t efi_load_initrd_dev_path(unsigned long *load_addr,
if (status != EFI_SUCCESS)
return status;
- status = efi_call_proto(lf2, load_file, dp, false, &initrd_size, NULL);
+ initrd->size = 0;
+ status = efi_call_proto(lf2, load_file, dp, false, &initrd->size, NULL);
if (status != EFI_BUFFER_TOO_SMALL)
return EFI_LOAD_ERROR;
- status = efi_allocate_pages(initrd_size, &initrd_addr, max);
+ status = efi_allocate_pages(initrd->size, &initrd->base, max);
if (status != EFI_SUCCESS)
return status;
- status = efi_call_proto(lf2, load_file, dp, false, &initrd_size,
- (void *)initrd_addr);
+ status = efi_call_proto(lf2, load_file, dp, false, &initrd->size,
+ (void *)initrd->base);
if (status != EFI_SUCCESS) {
- efi_free(initrd_size, initrd_addr);
+ efi_free(initrd->size, initrd->base);
return EFI_LOAD_ERROR;
}
-
- *load_addr = initrd_addr;
- *load_size = initrd_size;
return EFI_SUCCESS;
}
static
efi_status_t efi_load_initrd_cmdline(efi_loaded_image_t *image,
- unsigned long *load_addr,
- unsigned long *load_size,
+ struct linux_efi_initrd *initrd,
unsigned long soft_limit,
unsigned long hard_limit)
{
if (!IS_ENABLED(CONFIG_EFI_GENERIC_STUB_INITRD_CMDLINE_LOADER) ||
- (IS_ENABLED(CONFIG_X86) && (!efi_is_native() || image == NULL))) {
- *load_addr = *load_size = 0;
- return EFI_SUCCESS;
- }
+ (IS_ENABLED(CONFIG_X86) && (!efi_is_native() || image == NULL)))
+ return EFI_UNSUPPORTED;
return handle_cmdline_files(image, L"initrd=", sizeof(L"initrd=") - 2,
soft_limit, hard_limit,
- load_addr, load_size);
+ &initrd->base, &initrd->size);
}
/**
* efi_load_initrd() - Load initial RAM disk
* @image: EFI loaded image protocol
- * @load_addr: pointer to loaded initrd
- * @load_size: size of loaded initrd
* @soft_limit: preferred address for loading the initrd
* @hard_limit: upper limit address for loading the initrd
*
* Return: status code
*/
efi_status_t efi_load_initrd(efi_loaded_image_t *image,
- unsigned long *load_addr,
- unsigned long *load_size,
unsigned long soft_limit,
- unsigned long hard_limit)
+ unsigned long hard_limit,
+ const struct linux_efi_initrd **out)
{
- efi_status_t status;
+ efi_guid_t tbl_guid = LINUX_EFI_INITRD_MEDIA_GUID;
+ efi_status_t status = EFI_SUCCESS;
+ struct linux_efi_initrd initrd, *tbl;
- if (!load_addr || !load_size)
- return EFI_INVALID_PARAMETER;
+ if (!IS_ENABLED(CONFIG_BLK_DEV_INITRD) || efi_noinitrd)
+ return EFI_SUCCESS;
- status = efi_load_initrd_dev_path(load_addr, load_size, hard_limit);
+ status = efi_load_initrd_dev_path(&initrd, hard_limit);
if (status == EFI_SUCCESS) {
efi_info("Loaded initrd from LINUX_EFI_INITRD_MEDIA_GUID device path\n");
+ if (initrd.size > 0 &&
+ efi_measure_tagged_event(initrd.base, initrd.size,
+ EFISTUB_EVT_INITRD) == EFI_SUCCESS)
+ efi_info("Measured initrd data into PCR 9\n");
} else if (status == EFI_NOT_FOUND) {
- status = efi_load_initrd_cmdline(image, load_addr, load_size,
- soft_limit, hard_limit);
- if (status == EFI_SUCCESS && *load_size > 0)
+ status = efi_load_initrd_cmdline(image, &initrd, soft_limit,
+ hard_limit);
+ /* command line loader disabled or no initrd= passed? */
+ if (status == EFI_UNSUPPORTED || status == EFI_NOT_READY)
+ return EFI_SUCCESS;
+ if (status == EFI_SUCCESS)
efi_info("Loaded initrd from command line option\n");
}
+ if (status != EFI_SUCCESS)
+ goto failed;
+
+ status = efi_bs_call(allocate_pool, EFI_LOADER_DATA, sizeof(initrd),
+ (void **)&tbl);
+ if (status != EFI_SUCCESS)
+ goto free_initrd;
+
+ *tbl = initrd;
+ status = efi_bs_call(install_configuration_table, &tbl_guid, tbl);
+ if (status != EFI_SUCCESS)
+ goto free_tbl;
+
+ if (out)
+ *out = tbl;
+ return EFI_SUCCESS;
+free_tbl:
+ efi_bs_call(free_pool, tbl);
+free_initrd:
+ efi_free(initrd.size, initrd.base);
+failed:
+ efi_err("Failed to load initrd: 0x%lx\n", status);
return status;
}
diff --git a/drivers/firmware/efi/libstub/efi-stub.c b/drivers/firmware/efi/libstub/efi-stub.c
index 26e69788f27a..cf474f0dd261 100644
--- a/drivers/firmware/efi/libstub/efi-stub.c
+++ b/drivers/firmware/efi/libstub/efi-stub.c
@@ -10,7 +10,6 @@
*/
#include <linux/efi.h>
-#include <linux/libfdt.h>
#include <asm/efi.h>
#include "efistub.h"
@@ -40,14 +39,22 @@
#ifdef CONFIG_ARM64
# define EFI_RT_VIRTUAL_LIMIT DEFAULT_MAP_WINDOW_64
-#else
+#elif defined(CONFIG_RISCV) || defined(CONFIG_LOONGARCH)
+# define EFI_RT_VIRTUAL_LIMIT TASK_SIZE_MIN
+#else /* Only if TASK_SIZE is a constant */
# define EFI_RT_VIRTUAL_LIMIT TASK_SIZE
#endif
-static u64 virtmap_base = EFI_RT_VIRTUAL_BASE;
-static bool flat_va_mapping;
+/*
+ * Some architectures map the EFI regions into the kernel's linear map using a
+ * fixed offset.
+ */
+#ifndef EFI_RT_VIRTUAL_OFFSET
+#define EFI_RT_VIRTUAL_OFFSET 0
+#endif
-const efi_system_table_t *efi_system_table;
+static u64 virtmap_base = EFI_RT_VIRTUAL_BASE;
+static bool flat_va_mapping = (EFI_RT_VIRTUAL_OFFSET != 0);
static struct screen_info *setup_graphics(void)
{
@@ -122,19 +129,13 @@ efi_status_t __efiapi efi_pe_entry(efi_handle_t handle,
unsigned long image_addr;
unsigned long image_size = 0;
/* addr/point and size pairs for memory management*/
- unsigned long initrd_addr = 0;
- unsigned long initrd_size = 0;
- unsigned long fdt_addr = 0; /* Original DTB */
- unsigned long fdt_size = 0;
char *cmdline_ptr = NULL;
int cmdline_size = 0;
efi_guid_t loaded_image_proto = LOADED_IMAGE_PROTOCOL_GUID;
unsigned long reserve_addr = 0;
unsigned long reserve_size = 0;
- enum efi_secureboot_mode secure_boot;
struct screen_info *si;
efi_properties_table_t *prop_tbl;
- unsigned long max_addr;
efi_system_table = sys_table_arg;
@@ -153,8 +154,8 @@ efi_status_t __efiapi efi_pe_entry(efi_handle_t handle,
* information about the running image, such as size and the command
* line.
*/
- status = efi_system_table->boottime->handle_protocol(handle,
- &loaded_image_proto, (void *)&image);
+ status = efi_bs_call(handle_protocol, handle, &loaded_image_proto,
+ (void *)&image);
if (status != EFI_SUCCESS) {
efi_err("Failed to get loaded image protocol\n");
goto fail;
@@ -197,7 +198,7 @@ efi_status_t __efiapi efi_pe_entry(efi_handle_t handle,
status = handle_kernel_image(&image_addr, &image_size,
&reserve_addr,
&reserve_size,
- image);
+ image, handle);
if (status != EFI_SUCCESS) {
efi_err("Failed to relocate kernel\n");
goto fail_free_screeninfo;
@@ -208,45 +209,8 @@ efi_status_t __efiapi efi_pe_entry(efi_handle_t handle,
/* Ask the firmware to clear memory on unclean shutdown */
efi_enable_reset_attack_mitigation();
- secure_boot = efi_get_secureboot();
-
- /*
- * Unauthenticated device tree data is a security hazard, so ignore
- * 'dtb=' unless UEFI Secure Boot is disabled. We assume that secure
- * boot is enabled if we can't determine its state.
- */
- if (!IS_ENABLED(CONFIG_EFI_ARMSTUB_DTB_LOADER) ||
- secure_boot != efi_secureboot_mode_disabled) {
- if (strstr(cmdline_ptr, "dtb="))
- efi_err("Ignoring DTB from command line.\n");
- } else {
- status = efi_load_dtb(image, &fdt_addr, &fdt_size);
-
- if (status != EFI_SUCCESS) {
- efi_err("Failed to load device tree!\n");
- goto fail_free_image;
- }
- }
-
- if (fdt_addr) {
- efi_info("Using DTB from command line\n");
- } else {
- /* Look for a device tree configuration table entry. */
- fdt_addr = (uintptr_t)get_fdt(&fdt_size);
- if (fdt_addr)
- efi_info("Using DTB from configuration table\n");
- }
-
- if (!fdt_addr)
- efi_info("Generating empty DTB\n");
-
- if (!efi_noinitrd) {
- max_addr = efi_get_max_initrd_addr(image_addr);
- status = efi_load_initrd(image, &initrd_addr, &initrd_size,
- ULONG_MAX, max_addr);
- if (status != EFI_SUCCESS)
- efi_err("Failed to load initrd!\n");
- }
+ efi_load_initrd(image, ULONG_MAX, efi_get_max_initrd_addr(image_addr),
+ NULL);
efi_random_get_seed();
@@ -258,8 +222,8 @@ efi_status_t __efiapi efi_pe_entry(efi_handle_t handle,
* The easiest way to achieve that is to simply use a 1:1 mapping.
*/
prop_tbl = get_efi_config_table(EFI_PROPERTIES_TABLE_GUID);
- flat_va_mapping = prop_tbl &&
- (prop_tbl->memory_protection_attribute &
+ flat_va_mapping |= prop_tbl &&
+ (prop_tbl->memory_protection_attribute &
EFI_PROPERTIES_RUNTIME_MEMORY_PROTECTION_NON_EXECUTABLE_PE_DATA);
/* force efi_novamap if SetVirtualAddressMap() is unsupported */
@@ -288,25 +252,8 @@ efi_status_t __efiapi efi_pe_entry(efi_handle_t handle,
install_memreserve_table();
- status = allocate_new_fdt_and_exit_boot(handle, &fdt_addr,
- initrd_addr, initrd_size,
- cmdline_ptr, fdt_addr, fdt_size);
- if (status != EFI_SUCCESS)
- goto fail_free_initrd;
+ status = efi_boot_kernel(handle, image, image_addr, cmdline_ptr);
- if (IS_ENABLED(CONFIG_ARM))
- efi_handle_post_ebs_state();
-
- efi_enter_kernel(image_addr, fdt_addr, fdt_totalsize((void *)fdt_addr));
- /* not reached */
-
-fail_free_initrd:
- efi_err("Failed to update FDT and exit boot services\n");
-
- efi_free(initrd_size, initrd_addr);
- efi_free(fdt_size, fdt_addr);
-
-fail_free_image:
efi_free(image_size, image_addr);
efi_free(reserve_size, reserve_addr);
fail_free_screeninfo:
@@ -318,6 +265,35 @@ fail:
}
/*
+ * efi_allocate_virtmap() - create a pool allocation for the virtmap
+ *
+ * Create an allocation that is of sufficient size to hold all the memory
+ * descriptors that will be passed to SetVirtualAddressMap() to inform the
+ * firmware about the virtual mapping that will be used under the OS to call
+ * into the firmware.
+ */
+efi_status_t efi_alloc_virtmap(efi_memory_desc_t **virtmap,
+ unsigned long *desc_size, u32 *desc_ver)
+{
+ unsigned long size, mmap_key;
+ efi_status_t status;
+
+ /*
+ * Use the size of the current memory map as an upper bound for the
+ * size of the buffer we need to pass to SetVirtualAddressMap() to
+ * cover all EFI_MEMORY_RUNTIME regions.
+ */
+ size = 0;
+ status = efi_bs_call(get_memory_map, &size, NULL, &mmap_key, desc_size,
+ desc_ver);
+ if (status != EFI_BUFFER_TOO_SMALL)
+ return EFI_LOAD_ERROR;
+
+ return efi_bs_call(allocate_pool, EFI_LOADER_DATA, size,
+ (void **)virtmap);
+}
+
+/*
* efi_get_virtmap() - create a virtual mapping for the EFI memory map
*
* This function populates the virt_addr fields of all memory region descriptors
@@ -332,6 +308,8 @@ void efi_get_virtmap(efi_memory_desc_t *memory_map, unsigned long map_size,
efi_memory_desc_t *in, *out = runtime_map;
int l;
+ *count = 0;
+
for (l = 0; l < map_size; l += desc_size) {
u64 paddr, size;
@@ -342,7 +320,7 @@ void efi_get_virtmap(efi_memory_desc_t *memory_map, unsigned long map_size,
paddr = in->phys_addr;
size = in->num_pages * EFI_PAGE_SIZE;
- in->virt_addr = in->phys_addr;
+ in->virt_addr = in->phys_addr + EFI_RT_VIRTUAL_OFFSET;
if (efi_novamap) {
continue;
}
diff --git a/drivers/firmware/efi/libstub/efistub.h b/drivers/firmware/efi/libstub/efistub.h
index cde0a2ef507d..eb03d5a9aac8 100644
--- a/drivers/firmware/efi/libstub/efistub.h
+++ b/drivers/firmware/efi/libstub/efistub.h
@@ -31,12 +31,14 @@
extern bool efi_nochunk;
extern bool efi_nokaslr;
-extern bool efi_noinitrd;
extern int efi_loglevel;
extern bool efi_novamap;
extern const efi_system_table_t *efi_system_table;
+typedef union efi_dxe_services_table efi_dxe_services_table_t;
+extern const efi_dxe_services_table_t *efi_dxe_table;
+
efi_status_t __efiapi efi_pe_entry(efi_handle_t handle,
efi_system_table_t *sys_table_arg);
@@ -45,6 +47,7 @@ efi_status_t __efiapi efi_pe_entry(efi_handle_t handle,
#define efi_is_native() (true)
#define efi_bs_call(func, ...) efi_system_table->boottime->func(__VA_ARGS__)
#define efi_rt_call(func, ...) efi_system_table->runtime->func(__VA_ARGS__)
+#define efi_dxe_call(func, ...) efi_dxe_table->func(__VA_ARGS__)
#define efi_table_attr(inst, attr) (inst->attr)
#define efi_call_proto(inst, func, ...) inst->func(inst, ##__VA_ARGS__)
@@ -157,16 +160,24 @@ void efi_set_u64_split(u64 data, u32 *lo, u32 *hi)
*/
#define EFI_MMAP_NR_SLACK_SLOTS 8
-struct efi_boot_memmap {
- efi_memory_desc_t **map;
- unsigned long *map_size;
- unsigned long *desc_size;
- u32 *desc_ver;
- unsigned long *key_ptr;
- unsigned long *buff_size;
+typedef struct efi_generic_dev_path efi_device_path_protocol_t;
+
+union efi_device_path_to_text_protocol {
+ struct {
+ efi_char16_t *(__efiapi *convert_device_node_to_text)(
+ const efi_device_path_protocol_t *,
+ bool, bool);
+ efi_char16_t *(__efiapi *convert_device_path_to_text)(
+ const efi_device_path_protocol_t *,
+ bool, bool);
+ };
+ struct {
+ u32 convert_device_node_to_text;
+ u32 convert_device_path_to_text;
+ } mixed_mode;
};
-typedef struct efi_generic_dev_path efi_device_path_protocol_t;
+typedef union efi_device_path_to_text_protocol efi_device_path_to_text_protocol_t;
typedef void *efi_event_t;
/* Note that notifications won't work in mixed mode */
@@ -251,13 +262,17 @@ union efi_boot_services {
efi_handle_t *);
efi_status_t (__efiapi *install_configuration_table)(efi_guid_t *,
void *);
- void *load_image;
- void *start_image;
+ efi_status_t (__efiapi *load_image)(bool, efi_handle_t,
+ efi_device_path_protocol_t *,
+ void *, unsigned long,
+ efi_handle_t *);
+ efi_status_t (__efiapi *start_image)(efi_handle_t, unsigned long *,
+ efi_char16_t **);
efi_status_t __noreturn (__efiapi *exit)(efi_handle_t,
efi_status_t,
unsigned long,
efi_char16_t *);
- void *unload_image;
+ efi_status_t (__efiapi *unload_image)(efi_handle_t);
efi_status_t (__efiapi *exit_boot_services)(efi_handle_t,
unsigned long);
void *get_next_monotonic_count;
@@ -274,11 +289,11 @@ union efi_boot_services {
void *locate_handle_buffer;
efi_status_t (__efiapi *locate_protocol)(efi_guid_t *, void *,
void **);
- void *install_multiple_protocol_interfaces;
- void *uninstall_multiple_protocol_interfaces;
+ efi_status_t (__efiapi *install_multiple_protocol_interfaces)(efi_handle_t *, ...);
+ efi_status_t (__efiapi *uninstall_multiple_protocol_interfaces)(efi_handle_t, ...);
void *calculate_crc32;
- void *copy_mem;
- void *set_mem;
+ void (__efiapi *copy_mem)(void *, const void *, unsigned long);
+ void (__efiapi *set_mem)(void *, unsigned long, unsigned char);
void *create_event_ex;
};
struct {
@@ -330,6 +345,76 @@ union efi_boot_services {
} mixed_mode;
};
+typedef enum {
+ EfiGcdMemoryTypeNonExistent,
+ EfiGcdMemoryTypeReserved,
+ EfiGcdMemoryTypeSystemMemory,
+ EfiGcdMemoryTypeMemoryMappedIo,
+ EfiGcdMemoryTypePersistent,
+ EfiGcdMemoryTypeMoreReliable,
+ EfiGcdMemoryTypeMaximum
+} efi_gcd_memory_type_t;
+
+typedef struct {
+ efi_physical_addr_t base_address;
+ u64 length;
+ u64 capabilities;
+ u64 attributes;
+ efi_gcd_memory_type_t gcd_memory_type;
+ void *image_handle;
+ void *device_handle;
+} efi_gcd_memory_space_desc_t;
+
+/*
+ * EFI DXE Services table
+ */
+union efi_dxe_services_table {
+ struct {
+ efi_table_hdr_t hdr;
+ void *add_memory_space;
+ void *allocate_memory_space;
+ void *free_memory_space;
+ void *remove_memory_space;
+ efi_status_t (__efiapi *get_memory_space_descriptor)(efi_physical_addr_t,
+ efi_gcd_memory_space_desc_t *);
+ efi_status_t (__efiapi *set_memory_space_attributes)(efi_physical_addr_t,
+ u64, u64);
+ void *get_memory_space_map;
+ void *add_io_space;
+ void *allocate_io_space;
+ void *free_io_space;
+ void *remove_io_space;
+ void *get_io_space_descriptor;
+ void *get_io_space_map;
+ void *dispatch;
+ void *schedule;
+ void *trust;
+ void *process_firmware_volume;
+ void *set_memory_space_capabilities;
+ };
+ struct {
+ efi_table_hdr_t hdr;
+ u32 add_memory_space;
+ u32 allocate_memory_space;
+ u32 free_memory_space;
+ u32 remove_memory_space;
+ u32 get_memory_space_descriptor;
+ u32 set_memory_space_attributes;
+ u32 get_memory_space_map;
+ u32 add_io_space;
+ u32 allocate_io_space;
+ u32 free_io_space;
+ u32 remove_io_space;
+ u32 get_io_space_descriptor;
+ u32 get_io_space_map;
+ u32 dispatch;
+ u32 schedule;
+ u32 trust;
+ u32 process_firmware_volume;
+ u32 set_memory_space_capabilities;
+ } mixed_mode;
+};
+
typedef union efi_uga_draw_protocol efi_uga_draw_protocol_t;
union efi_uga_draw_protocol {
@@ -667,6 +752,30 @@ union apple_properties_protocol {
typedef u32 efi_tcg2_event_log_format;
+#define INITRD_EVENT_TAG_ID 0x8F3B22ECU
+#define LOAD_OPTIONS_EVENT_TAG_ID 0x8F3B22EDU
+#define EV_EVENT_TAG 0x00000006U
+#define EFI_TCG2_EVENT_HEADER_VERSION 0x1
+
+struct efi_tcg2_event {
+ u32 event_size;
+ struct {
+ u32 header_size;
+ u16 header_version;
+ u32 pcr_index;
+ u32 event_type;
+ } __packed event_header;
+ /* u8[] event follows here */
+} __packed;
+
+struct efi_tcg2_tagged_event {
+ u32 tagged_event_id;
+ u32 tagged_event_data_size;
+ /* u8 tagged event data follows here */
+} __packed;
+
+typedef struct efi_tcg2_event efi_tcg2_event_t;
+typedef struct efi_tcg2_tagged_event efi_tcg2_tagged_event_t;
typedef union efi_tcg2_protocol efi_tcg2_protocol_t;
union efi_tcg2_protocol {
@@ -677,7 +786,11 @@ union efi_tcg2_protocol {
efi_physical_addr_t *,
efi_physical_addr_t *,
efi_bool_t *);
- void *hash_log_extend_event;
+ efi_status_t (__efiapi *hash_log_extend_event)(efi_tcg2_protocol_t *,
+ u64,
+ efi_physical_addr_t,
+ u64,
+ const efi_tcg2_event_t *);
void *submit_command;
void *get_active_pcr_banks;
void *set_active_pcr_banks;
@@ -694,6 +807,13 @@ union efi_tcg2_protocol {
} mixed_mode;
};
+struct riscv_efi_boot_protocol {
+ u64 revision;
+
+ efi_status_t (__efiapi *get_boot_hartid)(struct riscv_efi_boot_protocol *,
+ unsigned long *boot_hartid);
+};
+
typedef union efi_load_file_protocol efi_load_file_protocol_t;
typedef union efi_load_file_protocol efi_load_file2_protocol_t;
@@ -733,7 +853,7 @@ typedef struct {
u16 file_path_list_length;
const efi_char16_t *description;
const efi_device_path_protocol_t *file_path_list;
- size_t optional_data_size;
+ u32 optional_data_size;
const void *optional_data;
} efi_load_option_unpacked_t;
@@ -743,20 +863,16 @@ typedef efi_status_t (*efi_exit_boot_map_processing)(
struct efi_boot_memmap *map,
void *priv);
-efi_status_t efi_exit_boot_services(void *handle,
- struct efi_boot_memmap *map,
- void *priv,
+efi_status_t efi_exit_boot_services(void *handle, void *priv,
efi_exit_boot_map_processing priv_func);
-efi_status_t allocate_new_fdt_and_exit_boot(void *handle,
- unsigned long *new_fdt_addr,
- u64 initrd_addr, u64 initrd_size,
- char *cmdline_ptr,
- unsigned long fdt_addr,
- unsigned long fdt_size);
+efi_status_t efi_boot_kernel(void *handle, efi_loaded_image_t *image,
+ unsigned long kernel_addr, char *cmdline_ptr);
void *get_fdt(unsigned long *fdt_size);
+efi_status_t efi_alloc_virtmap(efi_memory_desc_t **virtmap,
+ unsigned long *desc_size, u32 *desc_ver);
void efi_get_virtmap(efi_memory_desc_t *memory_map, unsigned long map_size,
unsigned long desc_size, efi_memory_desc_t *runtime_map,
int *count);
@@ -778,11 +894,12 @@ __printf(1, 2) int efi_printk(char const *fmt, ...);
void efi_free(unsigned long size, unsigned long addr);
-void efi_apply_loadoptions_quirk(const void **load_options, int *load_options_size);
+void efi_apply_loadoptions_quirk(const void **load_options, u32 *load_options_size);
char *efi_convert_cmdline(efi_loaded_image_t *image, int *cmd_line_len);
-efi_status_t efi_get_memory_map(struct efi_boot_memmap *map);
+efi_status_t efi_get_memory_map(struct efi_boot_memmap **map,
+ bool install_cfg_tbl);
efi_status_t efi_allocate_pages(unsigned long size, unsigned long *addr,
unsigned long max);
@@ -825,10 +942,9 @@ static inline efi_status_t efi_load_dtb(efi_loaded_image_t *image,
}
efi_status_t efi_load_initrd(efi_loaded_image_t *image,
- unsigned long *load_addr,
- unsigned long *load_size,
unsigned long soft_limit,
- unsigned long hard_limit);
+ unsigned long hard_limit,
+ const struct linux_efi_initrd **out);
/*
* This function handles the architcture specific differences between arm and
* arm64 regarding where the kernel image must be loaded and any memory that
@@ -839,7 +955,8 @@ efi_status_t handle_kernel_image(unsigned long *image_addr,
unsigned long *image_size,
unsigned long *reserve_addr,
unsigned long *reserve_size,
- efi_loaded_image_t *image);
+ efi_loaded_image_t *image,
+ efi_handle_t image_handle);
asmlinkage void __noreturn efi_enter_kernel(unsigned long entrypoint,
unsigned long fdt_addr,
@@ -858,4 +975,32 @@ efi_enable_reset_attack_mitigation(void) { }
void efi_retrieve_tpm2_eventlog(void);
+struct efi_smbios_record {
+ u8 type;
+ u8 length;
+ u16 handle;
+};
+
+struct efi_smbios_type1_record {
+ struct efi_smbios_record header;
+
+ u8 manufacturer;
+ u8 product_name;
+ u8 version;
+ u8 serial_number;
+ efi_guid_t uuid;
+ u8 wakeup_type;
+ u8 sku_number;
+ u8 family;
+};
+
+#define efi_get_smbios_string(__type, __name) ({ \
+ int size = sizeof(struct efi_smbios_type ## __type ## _record); \
+ int off = offsetof(struct efi_smbios_type ## __type ## _record, \
+ __name); \
+ __efi_get_smbios_string(__type, off, size); \
+})
+
+const u8 *__efi_get_smbios_string(u8 type, int offset, int recsize);
+
#endif
diff --git a/drivers/firmware/efi/libstub/fdt.c b/drivers/firmware/efi/libstub/fdt.c
index fe567be0f118..70e9789ff9de 100644
--- a/drivers/firmware/efi/libstub/fdt.c
+++ b/drivers/firmware/efi/libstub/fdt.c
@@ -28,8 +28,7 @@ static void fdt_update_cell_size(void *fdt)
}
static efi_status_t update_fdt(void *orig_fdt, unsigned long orig_fdt_size,
- void *fdt, int new_fdt_size, char *cmdline_ptr,
- u64 initrd_addr, u64 initrd_size)
+ void *fdt, int new_fdt_size, char *cmdline_ptr)
{
int node, num_rsv;
int status;
@@ -93,21 +92,6 @@ static efi_status_t update_fdt(void *orig_fdt, unsigned long orig_fdt_size,
goto fdt_set_fail;
}
- /* Set initrd address/end in device tree, if present */
- if (initrd_size != 0) {
- u64 initrd_image_end;
- u64 initrd_image_start = cpu_to_fdt64(initrd_addr);
-
- status = fdt_setprop_var(fdt, node, "linux,initrd-start", initrd_image_start);
- if (status)
- goto fdt_set_fail;
-
- initrd_image_end = cpu_to_fdt64(initrd_addr + initrd_size);
- status = fdt_setprop_var(fdt, node, "linux,initrd-end", initrd_image_end);
- if (status)
- goto fdt_set_fail;
- }
-
/* Add FDT entries for EFI runtime services in chosen node. */
node = fdt_subnode_offset(fdt, 0, "chosen");
fdt_val64 = cpu_to_fdt64((u64)(unsigned long)efi_system_table);
@@ -170,25 +154,25 @@ static efi_status_t update_fdt_memmap(void *fdt, struct efi_boot_memmap *map)
if (node < 0)
return EFI_LOAD_ERROR;
- fdt_val64 = cpu_to_fdt64((unsigned long)*map->map);
+ fdt_val64 = cpu_to_fdt64((unsigned long)map->map);
err = fdt_setprop_inplace_var(fdt, node, "linux,uefi-mmap-start", fdt_val64);
if (err)
return EFI_LOAD_ERROR;
- fdt_val32 = cpu_to_fdt32(*map->map_size);
+ fdt_val32 = cpu_to_fdt32(map->map_size);
err = fdt_setprop_inplace_var(fdt, node, "linux,uefi-mmap-size", fdt_val32);
if (err)
return EFI_LOAD_ERROR;
- fdt_val32 = cpu_to_fdt32(*map->desc_size);
+ fdt_val32 = cpu_to_fdt32(map->desc_size);
err = fdt_setprop_inplace_var(fdt, node, "linux,uefi-mmap-desc-size", fdt_val32);
if (err)
return EFI_LOAD_ERROR;
- fdt_val32 = cpu_to_fdt32(*map->desc_ver);
+ fdt_val32 = cpu_to_fdt32(map->desc_ver);
err = fdt_setprop_inplace_var(fdt, node, "linux,uefi-mmap-desc-ver", fdt_val32);
if (err)
@@ -198,22 +182,25 @@ static efi_status_t update_fdt_memmap(void *fdt, struct efi_boot_memmap *map)
}
struct exit_boot_struct {
+ struct efi_boot_memmap *boot_memmap;
efi_memory_desc_t *runtime_map;
- int *runtime_entry_count;
+ int runtime_entry_count;
void *new_fdt_addr;
};
-static efi_status_t exit_boot_func(struct efi_boot_memmap *map,
- void *priv)
+static efi_status_t exit_boot_func(struct efi_boot_memmap *map, void *priv)
{
struct exit_boot_struct *p = priv;
+
+ p->boot_memmap = map;
+
/*
* Update the memory map with virtual addresses. The function will also
* populate @runtime_map with copies of just the EFI_MEMORY_RUNTIME
* entries so that we can pass it straight to SetVirtualAddressMap()
*/
- efi_get_virtmap(*map->map, *map->map_size, *map->desc_size,
- p->runtime_map, p->runtime_entry_count);
+ efi_get_virtmap(map->map, map->map_size, map->desc_size,
+ p->runtime_map, &p->runtime_entry_count);
return update_fdt_memmap(p->new_fdt_addr, map);
}
@@ -223,86 +210,86 @@ static efi_status_t exit_boot_func(struct efi_boot_memmap *map,
#endif
/*
- * Allocate memory for a new FDT, then add EFI, commandline, and
- * initrd related fields to the FDT. This routine increases the
- * FDT allocation size until the allocated memory is large
- * enough. EFI allocations are in EFI_PAGE_SIZE granules,
- * which are fixed at 4K bytes, so in most cases the first
- * allocation should succeed.
- * EFI boot services are exited at the end of this function.
- * There must be no allocations between the get_memory_map()
- * call and the exit_boot_services() call, so the exiting of
- * boot services is very tightly tied to the creation of the FDT
- * with the final memory map in it.
+ * Allocate memory for a new FDT, then add EFI and commandline related fields
+ * to the FDT. This routine increases the FDT allocation size until the
+ * allocated memory is large enough. EFI allocations are in EFI_PAGE_SIZE
+ * granules, which are fixed at 4K bytes, so in most cases the first allocation
+ * should succeed. EFI boot services are exited at the end of this function.
+ * There must be no allocations between the get_memory_map() call and the
+ * exit_boot_services() call, so the exiting of boot services is very tightly
+ * tied to the creation of the FDT with the final memory map in it.
*/
-
+static
efi_status_t allocate_new_fdt_and_exit_boot(void *handle,
+ efi_loaded_image_t *image,
unsigned long *new_fdt_addr,
- u64 initrd_addr, u64 initrd_size,
- char *cmdline_ptr,
- unsigned long fdt_addr,
- unsigned long fdt_size)
+ char *cmdline_ptr)
{
- unsigned long map_size, desc_size, buff_size;
+ unsigned long desc_size;
u32 desc_ver;
- unsigned long mmap_key;
- efi_memory_desc_t *memory_map, *runtime_map;
efi_status_t status;
- int runtime_entry_count;
- struct efi_boot_memmap map;
struct exit_boot_struct priv;
+ unsigned long fdt_addr = 0;
+ unsigned long fdt_size = 0;
- map.map = &runtime_map;
- map.map_size = &map_size;
- map.desc_size = &desc_size;
- map.desc_ver = &desc_ver;
- map.key_ptr = &mmap_key;
- map.buff_size = &buff_size;
+ if (!efi_novamap) {
+ status = efi_alloc_virtmap(&priv.runtime_map, &desc_size,
+ &desc_ver);
+ if (status != EFI_SUCCESS) {
+ efi_err("Unable to retrieve UEFI memory map.\n");
+ return status;
+ }
+ }
/*
- * Get a copy of the current memory map that we will use to prepare
- * the input for SetVirtualAddressMap(). We don't have to worry about
- * subsequent allocations adding entries, since they could not affect
- * the number of EFI_MEMORY_RUNTIME regions.
+ * Unauthenticated device tree data is a security hazard, so ignore
+ * 'dtb=' unless UEFI Secure Boot is disabled. We assume that secure
+ * boot is enabled if we can't determine its state.
*/
- status = efi_get_memory_map(&map);
- if (status != EFI_SUCCESS) {
- efi_err("Unable to retrieve UEFI memory map.\n");
- return status;
+ if (!IS_ENABLED(CONFIG_EFI_ARMSTUB_DTB_LOADER) ||
+ efi_get_secureboot() != efi_secureboot_mode_disabled) {
+ if (strstr(cmdline_ptr, "dtb="))
+ efi_err("Ignoring DTB from command line.\n");
+ } else {
+ status = efi_load_dtb(image, &fdt_addr, &fdt_size);
+
+ if (status != EFI_SUCCESS && status != EFI_NOT_READY) {
+ efi_err("Failed to load device tree!\n");
+ goto fail;
+ }
}
+ if (fdt_addr) {
+ efi_info("Using DTB from command line\n");
+ } else {
+ /* Look for a device tree configuration table entry. */
+ fdt_addr = (uintptr_t)get_fdt(&fdt_size);
+ if (fdt_addr)
+ efi_info("Using DTB from configuration table\n");
+ }
+
+ if (!fdt_addr)
+ efi_info("Generating empty DTB\n");
+
efi_info("Exiting boot services...\n");
- map.map = &memory_map;
status = efi_allocate_pages(MAX_FDT_SIZE, new_fdt_addr, ULONG_MAX);
if (status != EFI_SUCCESS) {
efi_err("Unable to allocate memory for new device tree.\n");
goto fail;
}
- /*
- * Now that we have done our final memory allocation (and free)
- * we can get the memory map key needed for exit_boot_services().
- */
- status = efi_get_memory_map(&map);
- if (status != EFI_SUCCESS)
- goto fail_free_new_fdt;
-
status = update_fdt((void *)fdt_addr, fdt_size,
- (void *)*new_fdt_addr, MAX_FDT_SIZE, cmdline_ptr,
- initrd_addr, initrd_size);
+ (void *)*new_fdt_addr, MAX_FDT_SIZE, cmdline_ptr);
if (status != EFI_SUCCESS) {
efi_err("Unable to construct new device tree.\n");
goto fail_free_new_fdt;
}
- runtime_entry_count = 0;
- priv.runtime_map = runtime_map;
- priv.runtime_entry_count = &runtime_entry_count;
- priv.new_fdt_addr = (void *)*new_fdt_addr;
+ priv.new_fdt_addr = (void *)*new_fdt_addr;
- status = efi_exit_boot_services(handle, &map, &priv, exit_boot_func);
+ status = efi_exit_boot_services(handle, &priv, exit_boot_func);
if (status == EFI_SUCCESS) {
efi_set_virtual_address_map_t *svam;
@@ -312,8 +299,8 @@ efi_status_t allocate_new_fdt_and_exit_boot(void *handle,
/* Install the new virtual address map */
svam = efi_system_table->runtime->set_virtual_address_map;
- status = svam(runtime_entry_count * desc_size, desc_size,
- desc_ver, runtime_map);
+ status = svam(priv.runtime_entry_count * desc_size, desc_size,
+ desc_ver, priv.runtime_map);
/*
* We are beyond the point of no return here, so if the call to
@@ -321,19 +308,21 @@ efi_status_t allocate_new_fdt_and_exit_boot(void *handle,
* incoming kernel but proceed normally otherwise.
*/
if (status != EFI_SUCCESS) {
+ efi_memory_desc_t *p;
int l;
/*
* Set the virtual address field of all
- * EFI_MEMORY_RUNTIME entries to 0. This will signal
- * the incoming kernel that no virtual translation has
- * been installed.
+ * EFI_MEMORY_RUNTIME entries to U64_MAX. This will
+ * signal the incoming kernel that no virtual
+ * translation has been installed.
*/
- for (l = 0; l < map_size; l += desc_size) {
- efi_memory_desc_t *p = (void *)memory_map + l;
+ for (l = 0; l < priv.boot_memmap->map_size;
+ l += priv.boot_memmap->desc_size) {
+ p = (void *)priv.boot_memmap->map + l;
if (p->attribute & EFI_MEMORY_RUNTIME)
- p->virt_addr = 0;
+ p->virt_addr = U64_MAX;
}
}
return EFI_SUCCESS;
@@ -345,11 +334,33 @@ fail_free_new_fdt:
efi_free(MAX_FDT_SIZE, *new_fdt_addr);
fail:
- efi_system_table->boottime->free_pool(runtime_map);
+ efi_free(fdt_size, fdt_addr);
+
+ efi_bs_call(free_pool, priv.runtime_map);
return EFI_LOAD_ERROR;
}
+efi_status_t efi_boot_kernel(void *handle, efi_loaded_image_t *image,
+ unsigned long kernel_addr, char *cmdline_ptr)
+{
+ unsigned long fdt_addr;
+ efi_status_t status;
+
+ status = allocate_new_fdt_and_exit_boot(handle, image, &fdt_addr,
+ cmdline_ptr);
+ if (status != EFI_SUCCESS) {
+ efi_err("Failed to update FDT and exit boot services\n");
+ return status;
+ }
+
+ if (IS_ENABLED(CONFIG_ARM))
+ efi_handle_post_ebs_state();
+
+ efi_enter_kernel(kernel_addr, fdt_addr, fdt_totalsize((void *)fdt_addr));
+ /* not reached */
+}
+
void *get_fdt(unsigned long *fdt_size)
{
void *fdt;
diff --git a/drivers/firmware/efi/libstub/file.c b/drivers/firmware/efi/libstub/file.c
index dd95f330fe6e..f756c61396e9 100644
--- a/drivers/firmware/efi/libstub/file.c
+++ b/drivers/firmware/efi/libstub/file.c
@@ -66,10 +66,28 @@ static efi_status_t efi_open_file(efi_file_protocol_t *volume,
static efi_status_t efi_open_volume(efi_loaded_image_t *image,
efi_file_protocol_t **fh)
{
+ struct efi_vendor_dev_path *dp = image->file_path;
+ efi_guid_t li_proto = LOADED_IMAGE_PROTOCOL_GUID;
efi_guid_t fs_proto = EFI_FILE_SYSTEM_GUID;
efi_simple_file_system_protocol_t *io;
efi_status_t status;
+ // If we are using EFI zboot, we should look for the file system
+ // protocol on the parent image's handle instead
+ if (IS_ENABLED(CONFIG_EFI_ZBOOT) &&
+ image->parent_handle != NULL &&
+ dp != NULL &&
+ dp->header.type == EFI_DEV_MEDIA &&
+ dp->header.sub_type == EFI_DEV_MEDIA_VENDOR &&
+ !efi_guidcmp(dp->vendorguid, LINUX_EFI_ZBOOT_MEDIA_GUID)) {
+ status = efi_bs_call(handle_protocol, image->parent_handle,
+ &li_proto, (void *)&image);
+ if (status != EFI_SUCCESS) {
+ efi_err("Failed to locate parent image handle\n");
+ return status;
+ }
+ }
+
status = efi_bs_call(handle_protocol, image->device_handle, &fs_proto,
(void **)&io);
if (status != EFI_SUCCESS) {
@@ -136,7 +154,7 @@ efi_status_t handle_cmdline_files(efi_loaded_image_t *image,
unsigned long *load_size)
{
const efi_char16_t *cmdline = image->load_options;
- int cmdline_len = image->load_options_size;
+ u32 cmdline_len = image->load_options_size;
unsigned long efi_chunk_size = ULONG_MAX;
efi_file_protocol_t *volume = NULL;
efi_file_protocol_t *file;
@@ -238,6 +256,9 @@ efi_status_t handle_cmdline_files(efi_loaded_image_t *image,
if (volume)
volume->close(volume);
+
+ if (*load_size == 0)
+ return EFI_NOT_READY;
return EFI_SUCCESS;
err_close_file:
diff --git a/drivers/firmware/efi/libstub/intrinsics.c b/drivers/firmware/efi/libstub/intrinsics.c
new file mode 100644
index 000000000000..a04ab39292b6
--- /dev/null
+++ b/drivers/firmware/efi/libstub/intrinsics.c
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/efi.h>
+#include <asm/efi.h>
+#include <asm/string.h>
+
+#include "efistub.h"
+
+#ifdef CONFIG_KASAN
+#undef memcpy
+#undef memmove
+#undef memset
+void *__memcpy(void *__dest, const void *__src, size_t __n) __alias(memcpy);
+void *__memmove(void *__dest, const void *__src, size_t count) __alias(memmove);
+void *__memset(void *s, int c, size_t count) __alias(memset);
+#endif
+
+void *memcpy(void *dst, const void *src, size_t len)
+{
+ efi_bs_call(copy_mem, dst, src, len);
+ return dst;
+}
+
+extern void *memmove(void *dst, const void *src, size_t len) __alias(memcpy);
+
+void *memset(void *dst, int c, size_t len)
+{
+ efi_bs_call(set_mem, dst, len, c & U8_MAX);
+ return dst;
+}
diff --git a/drivers/firmware/efi/libstub/loongarch-stub.c b/drivers/firmware/efi/libstub/loongarch-stub.c
new file mode 100644
index 000000000000..32329f2a92f9
--- /dev/null
+++ b/drivers/firmware/efi/libstub/loongarch-stub.c
@@ -0,0 +1,102 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Author: Yun Liu <liuyun@loongson.cn>
+ * Huacai Chen <chenhuacai@loongson.cn>
+ * Copyright (C) 2020-2022 Loongson Technology Corporation Limited
+ */
+
+#include <asm/efi.h>
+#include <asm/addrspace.h>
+#include "efistub.h"
+
+typedef void __noreturn (*kernel_entry_t)(bool efi, unsigned long cmdline,
+ unsigned long systab);
+
+extern int kernel_asize;
+extern int kernel_fsize;
+extern int kernel_offset;
+extern kernel_entry_t kernel_entry;
+
+efi_status_t check_platform_features(void)
+{
+ return EFI_SUCCESS;
+}
+
+efi_status_t handle_kernel_image(unsigned long *image_addr,
+ unsigned long *image_size,
+ unsigned long *reserve_addr,
+ unsigned long *reserve_size,
+ efi_loaded_image_t *image,
+ efi_handle_t image_handle)
+{
+ efi_status_t status;
+ unsigned long kernel_addr = 0;
+
+ kernel_addr = (unsigned long)&kernel_offset - kernel_offset;
+
+ status = efi_relocate_kernel(&kernel_addr, kernel_fsize, kernel_asize,
+ PHYSADDR(VMLINUX_LOAD_ADDRESS), SZ_2M, 0x0);
+
+ *image_addr = kernel_addr;
+ *image_size = kernel_asize;
+
+ return status;
+}
+
+struct exit_boot_struct {
+ efi_memory_desc_t *runtime_map;
+ int runtime_entry_count;
+};
+
+static efi_status_t exit_boot_func(struct efi_boot_memmap *map, void *priv)
+{
+ struct exit_boot_struct *p = priv;
+
+ /*
+ * Update the memory map with virtual addresses. The function will also
+ * populate @runtime_map with copies of just the EFI_MEMORY_RUNTIME
+ * entries so that we can pass it straight to SetVirtualAddressMap()
+ */
+ efi_get_virtmap(map->map, map->map_size, map->desc_size,
+ p->runtime_map, &p->runtime_entry_count);
+
+ return EFI_SUCCESS;
+}
+
+efi_status_t efi_boot_kernel(void *handle, efi_loaded_image_t *image,
+ unsigned long kernel_addr, char *cmdline_ptr)
+{
+ kernel_entry_t real_kernel_entry;
+ struct exit_boot_struct priv;
+ unsigned long desc_size;
+ efi_status_t status;
+ u32 desc_ver;
+
+ status = efi_alloc_virtmap(&priv.runtime_map, &desc_size, &desc_ver);
+ if (status != EFI_SUCCESS) {
+ efi_err("Unable to retrieve UEFI memory map.\n");
+ return status;
+ }
+
+ efi_info("Exiting boot services\n");
+
+ efi_novamap = false;
+ status = efi_exit_boot_services(handle, &priv, exit_boot_func);
+ if (status != EFI_SUCCESS)
+ return status;
+
+ /* Install the new virtual address map */
+ efi_rt_call(set_virtual_address_map,
+ priv.runtime_entry_count * desc_size, desc_size,
+ desc_ver, priv.runtime_map);
+
+ /* Config Direct Mapping */
+ csr_write64(CSR_DMW0_INIT, LOONGARCH_CSR_DMWIN0);
+ csr_write64(CSR_DMW1_INIT, LOONGARCH_CSR_DMWIN1);
+
+ real_kernel_entry = (kernel_entry_t)
+ ((unsigned long)&kernel_entry - kernel_addr + VMLINUX_LOAD_ADDRESS);
+
+ real_kernel_entry(true, (unsigned long)cmdline_ptr,
+ (unsigned long)efi_system_table);
+}
diff --git a/drivers/firmware/efi/libstub/mem.c b/drivers/firmware/efi/libstub/mem.c
index feef8d4be113..45841ef55a9f 100644
--- a/drivers/firmware/efi/libstub/mem.c
+++ b/drivers/firmware/efi/libstub/mem.c
@@ -5,71 +5,66 @@
#include "efistub.h"
-static inline bool mmap_has_headroom(unsigned long buff_size,
- unsigned long map_size,
- unsigned long desc_size)
-{
- unsigned long slack = buff_size - map_size;
-
- return slack / desc_size >= EFI_MMAP_NR_SLACK_SLOTS;
-}
-
/**
* efi_get_memory_map() - get memory map
- * @map: on return pointer to memory map
+ * @map: pointer to memory map pointer to which to assign the
+ * newly allocated memory map
+ * @install_cfg_tbl: whether or not to install the boot memory map as a
+ * configuration table
*
* Retrieve the UEFI memory map. The allocated memory leaves room for
* up to EFI_MMAP_NR_SLACK_SLOTS additional memory map entries.
*
* Return: status code
*/
-efi_status_t efi_get_memory_map(struct efi_boot_memmap *map)
+efi_status_t efi_get_memory_map(struct efi_boot_memmap **map,
+ bool install_cfg_tbl)
{
- efi_memory_desc_t *m = NULL;
+ int memtype = install_cfg_tbl ? EFI_ACPI_RECLAIM_MEMORY
+ : EFI_LOADER_DATA;
+ efi_guid_t tbl_guid = LINUX_EFI_BOOT_MEMMAP_GUID;
+ struct efi_boot_memmap *m, tmp;
efi_status_t status;
- unsigned long key;
- u32 desc_version;
-
- *map->desc_size = sizeof(*m);
- *map->map_size = *map->desc_size * 32;
- *map->buff_size = *map->map_size;
-again:
- status = efi_bs_call(allocate_pool, EFI_LOADER_DATA,
- *map->map_size, (void **)&m);
+ unsigned long size;
+
+ tmp.map_size = 0;
+ status = efi_bs_call(get_memory_map, &tmp.map_size, NULL, &tmp.map_key,
+ &tmp.desc_size, &tmp.desc_ver);
+ if (status != EFI_BUFFER_TOO_SMALL)
+ return EFI_LOAD_ERROR;
+
+ size = tmp.map_size + tmp.desc_size * EFI_MMAP_NR_SLACK_SLOTS;
+ status = efi_bs_call(allocate_pool, memtype, sizeof(*m) + size,
+ (void **)&m);
if (status != EFI_SUCCESS)
- goto fail;
-
- *map->desc_size = 0;
- key = 0;
- status = efi_bs_call(get_memory_map, map->map_size, m,
- &key, map->desc_size, &desc_version);
- if (status == EFI_BUFFER_TOO_SMALL ||
- !mmap_has_headroom(*map->buff_size, *map->map_size,
- *map->desc_size)) {
- efi_bs_call(free_pool, m);
+ return status;
+
+ if (install_cfg_tbl) {
/*
- * Make sure there is some entries of headroom so that the
- * buffer can be reused for a new map after allocations are
- * no longer permitted. Its unlikely that the map will grow to
- * exceed this headroom once we are ready to trigger
- * ExitBootServices()
+ * Installing a configuration table might allocate memory, and
+ * this may modify the memory map. This means we should install
+ * the configuration table first, and re-install or delete it
+ * as needed.
*/
- *map->map_size += *map->desc_size * EFI_MMAP_NR_SLACK_SLOTS;
- *map->buff_size = *map->map_size;
- goto again;
+ status = efi_bs_call(install_configuration_table, &tbl_guid, m);
+ if (status != EFI_SUCCESS)
+ goto free_map;
}
- if (status == EFI_SUCCESS) {
- if (map->key_ptr)
- *map->key_ptr = key;
- if (map->desc_ver)
- *map->desc_ver = desc_version;
- } else {
- efi_bs_call(free_pool, m);
- }
+ m->buff_size = m->map_size = size;
+ status = efi_bs_call(get_memory_map, &m->map_size, m->map, &m->map_key,
+ &m->desc_size, &m->desc_ver);
+ if (status != EFI_SUCCESS)
+ goto uninstall_table;
+
+ *map = m;
+ return EFI_SUCCESS;
-fail:
- *map->map = m;
+uninstall_table:
+ if (install_cfg_tbl)
+ efi_bs_call(install_configuration_table, &tbl_guid, NULL);
+free_map:
+ efi_bs_call(free_pool, m);
return status;
}
diff --git a/drivers/firmware/efi/libstub/random.c b/drivers/firmware/efi/libstub/random.c
index 24aa37535372..33ab56769595 100644
--- a/drivers/firmware/efi/libstub/random.c
+++ b/drivers/firmware/efi/libstub/random.c
@@ -75,7 +75,12 @@ efi_status_t efi_random_get_seed(void)
if (status != EFI_SUCCESS)
return status;
- status = efi_bs_call(allocate_pool, EFI_RUNTIME_SERVICES_DATA,
+ /*
+ * Use EFI_ACPI_RECLAIM_MEMORY here so that it is guaranteed that the
+ * allocation will survive a kexec reboot (although we refresh the seed
+ * beforehand)
+ */
+ status = efi_bs_call(allocate_pool, EFI_ACPI_RECLAIM_MEMORY,
sizeof(*seed) + EFI_RANDOM_SEED_SIZE,
(void **)&seed);
if (status != EFI_SUCCESS)
diff --git a/drivers/firmware/efi/libstub/randomalloc.c b/drivers/firmware/efi/libstub/randomalloc.c
index 724155b9e10d..9fb5869896be 100644
--- a/drivers/firmware/efi/libstub/randomalloc.c
+++ b/drivers/firmware/efi/libstub/randomalloc.c
@@ -55,21 +55,13 @@ efi_status_t efi_random_alloc(unsigned long size,
unsigned long *addr,
unsigned long random_seed)
{
- unsigned long map_size, desc_size, total_slots = 0, target_slot;
- unsigned long buff_size;
+ unsigned long total_slots = 0, target_slot;
+ unsigned long total_mirrored_slots = 0;
+ struct efi_boot_memmap *map;
efi_status_t status;
- efi_memory_desc_t *memory_map;
int map_offset;
- struct efi_boot_memmap map;
- map.map = &memory_map;
- map.map_size = &map_size;
- map.desc_size = &desc_size;
- map.desc_ver = NULL;
- map.key_ptr = NULL;
- map.buff_size = &buff_size;
-
- status = efi_get_memory_map(&map);
+ status = efi_get_memory_map(&map, false);
if (status != EFI_SUCCESS)
return status;
@@ -79,15 +71,21 @@ efi_status_t efi_random_alloc(unsigned long size,
size = round_up(size, EFI_ALLOC_ALIGN);
/* count the suitable slots in each memory map entry */
- for (map_offset = 0; map_offset < map_size; map_offset += desc_size) {
- efi_memory_desc_t *md = (void *)memory_map + map_offset;
+ for (map_offset = 0; map_offset < map->map_size; map_offset += map->desc_size) {
+ efi_memory_desc_t *md = (void *)map->map + map_offset;
unsigned long slots;
slots = get_entry_num_slots(md, size, ilog2(align));
MD_NUM_SLOTS(md) = slots;
total_slots += slots;
+ if (md->attribute & EFI_MEMORY_MORE_RELIABLE)
+ total_mirrored_slots += slots;
}
+ /* consider only mirrored slots for randomization if any exist */
+ if (total_mirrored_slots > 0)
+ total_slots = total_mirrored_slots;
+
/* find a random number between 0 and total_slots */
target_slot = (total_slots * (u64)(random_seed & U32_MAX)) >> 32;
@@ -102,11 +100,15 @@ efi_status_t efi_random_alloc(unsigned long size,
* to calculate the randomly chosen address, and allocate it directly
* using EFI_ALLOCATE_ADDRESS.
*/
- for (map_offset = 0; map_offset < map_size; map_offset += desc_size) {
- efi_memory_desc_t *md = (void *)memory_map + map_offset;
+ for (map_offset = 0; map_offset < map->map_size; map_offset += map->desc_size) {
+ efi_memory_desc_t *md = (void *)map->map + map_offset;
efi_physical_addr_t target;
unsigned long pages;
+ if (total_mirrored_slots > 0 &&
+ !(md->attribute & EFI_MEMORY_MORE_RELIABLE))
+ continue;
+
if (target_slot >= MD_NUM_SLOTS(md)) {
target_slot -= MD_NUM_SLOTS(md);
continue;
@@ -122,7 +124,7 @@ efi_status_t efi_random_alloc(unsigned long size,
break;
}
- efi_bs_call(free_pool, memory_map);
+ efi_bs_call(free_pool, map);
return status;
}
diff --git a/drivers/firmware/efi/libstub/relocate.c b/drivers/firmware/efi/libstub/relocate.c
index 8ee9eb2b9039..bf6fbd5d22a1 100644
--- a/drivers/firmware/efi/libstub/relocate.c
+++ b/drivers/firmware/efi/libstub/relocate.c
@@ -23,21 +23,12 @@
efi_status_t efi_low_alloc_above(unsigned long size, unsigned long align,
unsigned long *addr, unsigned long min)
{
- unsigned long map_size, desc_size, buff_size;
- efi_memory_desc_t *map;
+ struct efi_boot_memmap *map;
efi_status_t status;
unsigned long nr_pages;
int i;
- struct efi_boot_memmap boot_map;
- boot_map.map = &map;
- boot_map.map_size = &map_size;
- boot_map.desc_size = &desc_size;
- boot_map.desc_ver = NULL;
- boot_map.key_ptr = NULL;
- boot_map.buff_size = &buff_size;
-
- status = efi_get_memory_map(&boot_map);
+ status = efi_get_memory_map(&map, false);
if (status != EFI_SUCCESS)
goto fail;
@@ -52,12 +43,12 @@ efi_status_t efi_low_alloc_above(unsigned long size, unsigned long align,
size = round_up(size, EFI_ALLOC_ALIGN);
nr_pages = size / EFI_PAGE_SIZE;
- for (i = 0; i < map_size / desc_size; i++) {
+ for (i = 0; i < map->map_size / map->desc_size; i++) {
efi_memory_desc_t *desc;
- unsigned long m = (unsigned long)map;
+ unsigned long m = (unsigned long)map->map;
u64 start, end;
- desc = efi_early_memdesc_ptr(m, desc_size, i);
+ desc = efi_early_memdesc_ptr(m, map->desc_size, i);
if (desc->type != EFI_CONVENTIONAL_MEMORY)
continue;
@@ -87,7 +78,7 @@ efi_status_t efi_low_alloc_above(unsigned long size, unsigned long align,
}
}
- if (i == map_size / desc_size)
+ if (i == map->map_size / map->desc_size)
status = EFI_NOT_FOUND;
efi_bs_call(free_pool, map);
diff --git a/drivers/firmware/efi/libstub/riscv-stub.c b/drivers/firmware/efi/libstub/riscv-stub.c
index 380e4e251399..b450ebf95977 100644
--- a/drivers/firmware/efi/libstub/riscv-stub.c
+++ b/drivers/firmware/efi/libstub/riscv-stub.c
@@ -8,6 +8,7 @@
#include <asm/efi.h>
#include <asm/sections.h>
+#include <asm/unaligned.h>
#include "efistub.h"
@@ -21,37 +22,63 @@
#define MIN_KIMG_ALIGN SZ_4M
#endif
-typedef void __noreturn (*jump_kernel_func)(unsigned int, unsigned long);
+typedef void __noreturn (*jump_kernel_func)(unsigned long, unsigned long);
-static u32 hartid;
+static unsigned long hartid;
-static u32 get_boot_hartid_from_fdt(void)
+static int get_boot_hartid_from_fdt(void)
{
const void *fdt;
int chosen_node, len;
- const fdt32_t *prop;
+ const void *prop;
fdt = get_efi_config_table(DEVICE_TREE_GUID);
if (!fdt)
- return U32_MAX;
+ return -EINVAL;
chosen_node = fdt_path_offset(fdt, "/chosen");
if (chosen_node < 0)
- return U32_MAX;
+ return -EINVAL;
prop = fdt_getprop((void *)fdt, chosen_node, "boot-hartid", &len);
- if (!prop || len != sizeof(u32))
- return U32_MAX;
+ if (!prop)
+ return -EINVAL;
- return fdt32_to_cpu(*prop);
+ if (len == sizeof(u32))
+ hartid = (unsigned long) fdt32_to_cpu(*(fdt32_t *)prop);
+ else if (len == sizeof(u64))
+ hartid = (unsigned long) fdt64_to_cpu(__get_unaligned_t(fdt64_t, prop));
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+static efi_status_t get_boot_hartid_from_efi(void)
+{
+ efi_guid_t boot_protocol_guid = RISCV_EFI_BOOT_PROTOCOL_GUID;
+ struct riscv_efi_boot_protocol *boot_protocol;
+ efi_status_t status;
+
+ status = efi_bs_call(locate_protocol, &boot_protocol_guid, NULL,
+ (void **)&boot_protocol);
+ if (status != EFI_SUCCESS)
+ return status;
+ return efi_call_proto(boot_protocol, get_boot_hartid, &hartid);
}
efi_status_t check_platform_features(void)
{
- hartid = get_boot_hartid_from_fdt();
- if (hartid == U32_MAX) {
- efi_err("/chosen/boot-hartid missing or invalid!\n");
- return EFI_UNSUPPORTED;
+ efi_status_t status;
+ int ret;
+
+ status = get_boot_hartid_from_efi();
+ if (status != EFI_SUCCESS) {
+ ret = get_boot_hartid_from_fdt();
+ if (ret) {
+ efi_err("Failed to get boot hartid!\n");
+ return EFI_UNSUPPORTED;
+ }
}
return EFI_SUCCESS;
}
@@ -77,7 +104,8 @@ efi_status_t handle_kernel_image(unsigned long *image_addr,
unsigned long *image_size,
unsigned long *reserve_addr,
unsigned long *reserve_size,
- efi_loaded_image_t *image)
+ efi_loaded_image_t *image,
+ efi_handle_t image_handle)
{
unsigned long kernel_size = 0;
unsigned long preferred_addr;
diff --git a/drivers/firmware/efi/libstub/secureboot.c b/drivers/firmware/efi/libstub/secureboot.c
index 8a18930f3eb6..516f4f0069bd 100644
--- a/drivers/firmware/efi/libstub/secureboot.c
+++ b/drivers/firmware/efi/libstub/secureboot.c
@@ -14,7 +14,7 @@
/* SHIM variables */
static const efi_guid_t shim_guid = EFI_SHIM_LOCK_GUID;
-static const efi_char16_t shim_MokSBState_name[] = L"MokSBState";
+static const efi_char16_t shim_MokSBState_name[] = L"MokSBStateRT";
static efi_status_t get_var(efi_char16_t *name, efi_guid_t *vendor, u32 *attr,
unsigned long *data_size, void *data)
@@ -43,8 +43,8 @@ enum efi_secureboot_mode efi_get_secureboot(void)
/*
* See if a user has put the shim into insecure mode. If so, and if the
- * variable doesn't have the runtime attribute set, we might as well
- * honor that.
+ * variable doesn't have the non-volatile attribute set, we might as
+ * well honor that.
*/
size = sizeof(moksbstate);
status = get_efi_var(shim_MokSBState_name, &shim_guid,
@@ -53,7 +53,7 @@ enum efi_secureboot_mode efi_get_secureboot(void)
/* If it fails, we don't care why. Default to secure */
if (status != EFI_SUCCESS)
goto secure_boot_enabled;
- if (!(attr & EFI_VARIABLE_RUNTIME_ACCESS) && moksbstate == 1)
+ if (!(attr & EFI_VARIABLE_NON_VOLATILE) && moksbstate == 1)
return efi_secureboot_mode_disabled;
secure_boot_enabled:
diff --git a/drivers/firmware/efi/libstub/smbios.c b/drivers/firmware/efi/libstub/smbios.c
new file mode 100644
index 000000000000..460418b7f5f5
--- /dev/null
+++ b/drivers/firmware/efi/libstub/smbios.c
@@ -0,0 +1,48 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright 2022 Google LLC
+// Author: Ard Biesheuvel <ardb@google.com>
+
+#include <linux/efi.h>
+
+#include "efistub.h"
+
+typedef struct efi_smbios_protocol efi_smbios_protocol_t;
+
+struct efi_smbios_protocol {
+ efi_status_t (__efiapi *add)(efi_smbios_protocol_t *, efi_handle_t,
+ u16 *, struct efi_smbios_record *);
+ efi_status_t (__efiapi *update_string)(efi_smbios_protocol_t *, u16 *,
+ unsigned long *, u8 *);
+ efi_status_t (__efiapi *remove)(efi_smbios_protocol_t *, u16);
+ efi_status_t (__efiapi *get_next)(efi_smbios_protocol_t *, u16 *, u8 *,
+ struct efi_smbios_record **,
+ efi_handle_t *);
+
+ u8 major_version;
+ u8 minor_version;
+};
+
+const u8 *__efi_get_smbios_string(u8 type, int offset, int recsize)
+{
+ struct efi_smbios_record *record;
+ efi_smbios_protocol_t *smbios;
+ efi_status_t status;
+ u16 handle = 0xfffe;
+ const u8 *strtable;
+
+ status = efi_bs_call(locate_protocol, &EFI_SMBIOS_PROTOCOL_GUID, NULL,
+ (void **)&smbios) ?:
+ efi_call_proto(smbios, get_next, &handle, &type, &record, NULL);
+ if (status != EFI_SUCCESS)
+ return NULL;
+
+ strtable = (u8 *)record + recsize;
+ for (int i = 1; i < ((u8 *)record)[offset]; i++) {
+ int len = strlen(strtable);
+
+ if (!len)
+ return NULL;
+ strtable += len + 1;
+ }
+ return strtable;
+}
diff --git a/drivers/firmware/efi/libstub/systable.c b/drivers/firmware/efi/libstub/systable.c
new file mode 100644
index 000000000000..91d016b02f8c
--- /dev/null
+++ b/drivers/firmware/efi/libstub/systable.c
@@ -0,0 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/efi.h>
+#include <asm/efi.h>
+
+#include "efistub.h"
+
+const efi_system_table_t *efi_system_table;
diff --git a/drivers/firmware/efi/libstub/x86-stub.c b/drivers/firmware/efi/libstub/x86-stub.c
index f14c4ff5839f..33a7811e12c6 100644
--- a/drivers/firmware/efi/libstub/x86-stub.c
+++ b/drivers/firmware/efi/libstub/x86-stub.c
@@ -22,6 +22,7 @@
#define MAXMEM_X86_64_4LEVEL (1ull << 46)
const efi_system_table_t *efi_system_table;
+const efi_dxe_services_table_t *efi_dxe_table;
extern u32 image_offset;
static efi_loaded_image_t *image = NULL;
@@ -211,9 +212,109 @@ static void retrieve_apple_device_properties(struct boot_params *boot_params)
}
}
+static void
+adjust_memory_range_protection(unsigned long start, unsigned long size)
+{
+ efi_status_t status;
+ efi_gcd_memory_space_desc_t desc;
+ unsigned long end, next;
+ unsigned long rounded_start, rounded_end;
+ unsigned long unprotect_start, unprotect_size;
+
+ if (efi_dxe_table == NULL)
+ return;
+
+ rounded_start = rounddown(start, EFI_PAGE_SIZE);
+ rounded_end = roundup(start + size, EFI_PAGE_SIZE);
+
+ /*
+ * Don't modify memory region attributes, they are
+ * already suitable, to lower the possibility to
+ * encounter firmware bugs.
+ */
+
+ for (end = start + size; start < end; start = next) {
+
+ status = efi_dxe_call(get_memory_space_descriptor, start, &desc);
+
+ if (status != EFI_SUCCESS)
+ return;
+
+ next = desc.base_address + desc.length;
+
+ /*
+ * Only system memory is suitable for trampoline/kernel image placement,
+ * so only this type of memory needs its attributes to be modified.
+ */
+
+ if (desc.gcd_memory_type != EfiGcdMemoryTypeSystemMemory ||
+ (desc.attributes & (EFI_MEMORY_RO | EFI_MEMORY_XP)) == 0)
+ continue;
+
+ unprotect_start = max(rounded_start, (unsigned long)desc.base_address);
+ unprotect_size = min(rounded_end, next) - unprotect_start;
+
+ status = efi_dxe_call(set_memory_space_attributes,
+ unprotect_start, unprotect_size,
+ EFI_MEMORY_WB);
+
+ if (status != EFI_SUCCESS) {
+ efi_warn("Unable to unprotect memory range [%08lx,%08lx]: %lx\n",
+ unprotect_start,
+ unprotect_start + unprotect_size,
+ status);
+ }
+ }
+}
+
+/*
+ * Trampoline takes 2 pages and can be loaded in first megabyte of memory
+ * with its end placed between 128k and 640k where BIOS might start.
+ * (see arch/x86/boot/compressed/pgtable_64.c)
+ *
+ * We cannot find exact trampoline placement since memory map
+ * can be modified by UEFI, and it can alter the computed address.
+ */
+
+#define TRAMPOLINE_PLACEMENT_BASE ((128 - 8)*1024)
+#define TRAMPOLINE_PLACEMENT_SIZE (640*1024 - (128 - 8)*1024)
+
+void startup_32(struct boot_params *boot_params);
+
+static void
+setup_memory_protection(unsigned long image_base, unsigned long image_size)
+{
+ /*
+ * Allow execution of possible trampoline used
+ * for switching between 4- and 5-level page tables
+ * and relocated kernel image.
+ */
+
+ adjust_memory_range_protection(TRAMPOLINE_PLACEMENT_BASE,
+ TRAMPOLINE_PLACEMENT_SIZE);
+
+#ifdef CONFIG_64BIT
+ if (image_base != (unsigned long)startup_32)
+ adjust_memory_range_protection(image_base, image_size);
+#else
+ /*
+ * Clear protection flags on a whole range of possible
+ * addresses used for KASLR. We don't need to do that
+ * on x86_64, since KASLR/extraction is performed after
+ * dedicated identity page tables are built and we only
+ * need to remove possible protection on relocated image
+ * itself disregarding further relocations.
+ */
+ adjust_memory_range_protection(LOAD_PHYSICAL_ADDR,
+ KERNEL_IMAGE_SIZE - LOAD_PHYSICAL_ADDR);
+#endif
+}
+
static const efi_char16_t apple[] = L"Apple";
-static void setup_quirks(struct boot_params *boot_params)
+static void setup_quirks(struct boot_params *boot_params,
+ unsigned long image_base,
+ unsigned long image_size)
{
efi_char16_t *fw_vendor = (efi_char16_t *)(unsigned long)
efi_table_attr(efi_system_table, fw_vendor);
@@ -222,6 +323,9 @@ static void setup_quirks(struct boot_params *boot_params)
if (IS_ENABLED(CONFIG_APPLE_PROPERTIES))
retrieve_apple_device_properties(boot_params);
}
+
+ if (IS_ENABLED(CONFIG_EFI_DXE_MEM_ATTRIBUTES))
+ setup_memory_protection(image_base, image_size);
}
/*
@@ -341,8 +445,6 @@ static void __noreturn efi_exit(efi_handle_t handle, efi_status_t status)
asm("hlt");
}
-void startup_32(struct boot_params *boot_params);
-
void __noreturn efi_stub_entry(efi_handle_t handle,
efi_system_table_t *sys_table_arg,
struct boot_params *boot_params);
@@ -414,6 +516,13 @@ efi_status_t __efiapi efi_pe_entry(efi_handle_t handle,
hdr->ramdisk_image = 0;
hdr->ramdisk_size = 0;
+ /*
+ * Disregard any setup data that was provided by the bootloader:
+ * setup_data could be pointing anywhere, and we have no way of
+ * authenticating or validating the payload.
+ */
+ hdr->setup_data = 0;
+
efi_stub_entry(handle, sys_table_arg, boot_params);
/* not reached */
@@ -613,32 +722,22 @@ static efi_status_t exit_boot_func(struct efi_boot_memmap *map,
efi_set_u64_split((unsigned long)efi_system_table,
&p->efi->efi_systab, &p->efi->efi_systab_hi);
- p->efi->efi_memdesc_size = *map->desc_size;
- p->efi->efi_memdesc_version = *map->desc_ver;
- efi_set_u64_split((unsigned long)*map->map,
+ p->efi->efi_memdesc_size = map->desc_size;
+ p->efi->efi_memdesc_version = map->desc_ver;
+ efi_set_u64_split((unsigned long)map->map,
&p->efi->efi_memmap, &p->efi->efi_memmap_hi);
- p->efi->efi_memmap_size = *map->map_size;
+ p->efi->efi_memmap_size = map->map_size;
return EFI_SUCCESS;
}
static efi_status_t exit_boot(struct boot_params *boot_params, void *handle)
{
- unsigned long map_sz, key, desc_size, buff_size;
- efi_memory_desc_t *mem_map;
struct setup_data *e820ext = NULL;
__u32 e820ext_size = 0;
efi_status_t status;
- __u32 desc_version;
- struct efi_boot_memmap map;
struct exit_boot_struct priv;
- map.map = &mem_map;
- map.map_size = &map_sz;
- map.desc_size = &desc_size;
- map.desc_ver = &desc_version;
- map.key_ptr = &key;
- map.buff_size = &buff_size;
priv.boot_params = boot_params;
priv.efi = &boot_params->efi_info;
@@ -647,7 +746,7 @@ static efi_status_t exit_boot(struct boot_params *boot_params, void *handle)
return status;
/* Might as well exit boot services now */
- status = efi_exit_boot_services(handle, &map, &priv, exit_boot_func);
+ status = efi_exit_boot_services(handle, &priv, exit_boot_func);
if (status != EFI_SUCCESS)
return status;
@@ -666,21 +765,28 @@ static efi_status_t exit_boot(struct boot_params *boot_params, void *handle)
* relocated by efi_relocate_kernel.
* On failure, we exit to the firmware via efi_exit instead of returning.
*/
-unsigned long efi_main(efi_handle_t handle,
- efi_system_table_t *sys_table_arg,
- struct boot_params *boot_params)
+asmlinkage unsigned long efi_main(efi_handle_t handle,
+ efi_system_table_t *sys_table_arg,
+ struct boot_params *boot_params)
{
unsigned long bzimage_addr = (unsigned long)startup_32;
unsigned long buffer_start, buffer_end;
struct setup_header *hdr = &boot_params->hdr;
+ const struct linux_efi_initrd *initrd = NULL;
efi_status_t status;
efi_system_table = sys_table_arg;
-
/* Check if we were booted by the EFI firmware */
if (efi_system_table->hdr.signature != EFI_SYSTEM_TABLE_SIGNATURE)
efi_exit(handle, EFI_INVALID_PARAMETER);
+ efi_dxe_table = get_efi_config_table(EFI_DXE_SERVICES_TABLE_GUID);
+ if (efi_dxe_table &&
+ efi_dxe_table->hdr.signature != EFI_DXE_SERVICES_TABLE_SIGNATURE) {
+ efi_warn("Ignoring DXE services table: invalid signature\n");
+ efi_dxe_table = NULL;
+ }
+
/*
* If the kernel isn't already loaded at a suitable address,
* relocate it.
@@ -761,24 +867,18 @@ unsigned long efi_main(efi_handle_t handle,
* arguments will be processed only if image is not NULL, which will be
* the case only if we were loaded via the PE entry point.
*/
- if (!efi_noinitrd) {
- unsigned long addr, size;
-
- status = efi_load_initrd(image, &addr, &size,
- hdr->initrd_addr_max, ULONG_MAX);
-
- if (status != EFI_SUCCESS) {
- efi_err("Failed to load initrd!\n");
- goto fail;
- }
- if (size > 0) {
- efi_set_u64_split(addr, &hdr->ramdisk_image,
- &boot_params->ext_ramdisk_image);
- efi_set_u64_split(size, &hdr->ramdisk_size,
- &boot_params->ext_ramdisk_size);
- }
+ status = efi_load_initrd(image, hdr->initrd_addr_max, ULONG_MAX,
+ &initrd);
+ if (status != EFI_SUCCESS)
+ goto fail;
+ if (initrd && initrd->size > 0) {
+ efi_set_u64_split(initrd->base, &hdr->ramdisk_image,
+ &boot_params->ext_ramdisk_image);
+ efi_set_u64_split(initrd->size, &hdr->ramdisk_size,
+ &boot_params->ext_ramdisk_size);
}
+
/*
* If the boot loader gave us a value for secure_boot then we use that,
* otherwise we ask the BIOS.
@@ -797,7 +897,7 @@ unsigned long efi_main(efi_handle_t handle,
setup_efi_pci(boot_params);
- setup_quirks(boot_params);
+ setup_quirks(boot_params, bzimage_addr, buffer_end - buffer_start);
status = exit_boot(boot_params, handle);
if (status != EFI_SUCCESS) {
diff --git a/drivers/firmware/efi/libstub/zboot-header.S b/drivers/firmware/efi/libstub/zboot-header.S
new file mode 100644
index 000000000000..9e6fe061ab07
--- /dev/null
+++ b/drivers/firmware/efi/libstub/zboot-header.S
@@ -0,0 +1,143 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#include <linux/pe.h>
+
+#ifdef CONFIG_64BIT
+ .set .Lextra_characteristics, 0x0
+ .set .Lpe_opt_magic, PE_OPT_MAGIC_PE32PLUS
+#else
+ .set .Lextra_characteristics, IMAGE_FILE_32BIT_MACHINE
+ .set .Lpe_opt_magic, PE_OPT_MAGIC_PE32
+#endif
+
+ .section ".head", "a"
+ .globl __efistub_efi_zboot_header
+__efistub_efi_zboot_header:
+.Ldoshdr:
+ .long MZ_MAGIC
+ .ascii "zimg" // image type
+ .long __efistub__gzdata_start - .Ldoshdr // payload offset
+ .long __efistub__gzdata_size - ZBOOT_SIZE_LEN // payload size
+ .long 0, 0 // reserved
+ .asciz COMP_TYPE // compression type
+ .org .Ldoshdr + 0x3c
+ .long .Lpehdr - .Ldoshdr // PE header offset
+
+.Lpehdr:
+ .long PE_MAGIC
+ .short MACHINE_TYPE
+ .short .Lsection_count
+ .long 0
+ .long 0
+ .long 0
+ .short .Lsection_table - .Loptional_header
+ .short IMAGE_FILE_DEBUG_STRIPPED | \
+ IMAGE_FILE_EXECUTABLE_IMAGE | \
+ IMAGE_FILE_LINE_NUMS_STRIPPED |\
+ .Lextra_characteristics
+
+.Loptional_header:
+ .short .Lpe_opt_magic
+ .byte 0, 0
+ .long _etext - .Lefi_header_end
+ .long __data_size
+ .long 0
+ .long __efistub_efi_zboot_entry - .Ldoshdr
+ .long .Lefi_header_end - .Ldoshdr
+
+#ifdef CONFIG_64BIT
+ .quad 0
+#else
+ .long _etext - .Ldoshdr, 0x0
+#endif
+ .long 4096
+ .long 512
+ .short 0, 0
+ .short LINUX_EFISTUB_MAJOR_VERSION // MajorImageVersion
+ .short LINUX_EFISTUB_MINOR_VERSION // MinorImageVersion
+ .short 0, 0
+ .long 0
+ .long _end - .Ldoshdr
+
+ .long .Lefi_header_end - .Ldoshdr
+ .long 0
+ .short IMAGE_SUBSYSTEM_EFI_APPLICATION
+ .short 0
+#ifdef CONFIG_64BIT
+ .quad 0, 0, 0, 0
+#else
+ .long 0, 0, 0, 0
+#endif
+ .long 0
+ .long (.Lsection_table - .) / 8
+
+ .quad 0 // ExportTable
+ .quad 0 // ImportTable
+ .quad 0 // ResourceTable
+ .quad 0 // ExceptionTable
+ .quad 0 // CertificationTable
+ .quad 0 // BaseRelocationTable
+#ifdef CONFIG_DEBUG_EFI
+ .long .Lefi_debug_table - .Ldoshdr // DebugTable
+ .long .Lefi_debug_table_size
+#endif
+
+.Lsection_table:
+ .ascii ".text\0\0\0"
+ .long _etext - .Lefi_header_end
+ .long .Lefi_header_end - .Ldoshdr
+ .long _etext - .Lefi_header_end
+ .long .Lefi_header_end - .Ldoshdr
+
+ .long 0, 0
+ .short 0, 0
+ .long IMAGE_SCN_CNT_CODE | \
+ IMAGE_SCN_MEM_READ | \
+ IMAGE_SCN_MEM_EXECUTE
+
+ .ascii ".data\0\0\0"
+ .long __data_size
+ .long _etext - .Ldoshdr
+ .long __data_rawsize
+ .long _etext - .Ldoshdr
+
+ .long 0, 0
+ .short 0, 0
+ .long IMAGE_SCN_CNT_INITIALIZED_DATA | \
+ IMAGE_SCN_MEM_READ | \
+ IMAGE_SCN_MEM_WRITE
+
+ .set .Lsection_count, (. - .Lsection_table) / 40
+
+#ifdef CONFIG_DEBUG_EFI
+ .section ".rodata", "a"
+ .align 2
+.Lefi_debug_table:
+ // EFI_IMAGE_DEBUG_DIRECTORY_ENTRY
+ .long 0 // Characteristics
+ .long 0 // TimeDateStamp
+ .short 0 // MajorVersion
+ .short 0 // MinorVersion
+ .long IMAGE_DEBUG_TYPE_CODEVIEW // Type
+ .long .Lefi_debug_entry_size // SizeOfData
+ .long 0 // RVA
+ .long .Lefi_debug_entry - .Ldoshdr // FileOffset
+
+ .set .Lefi_debug_table_size, . - .Lefi_debug_table
+ .previous
+
+.Lefi_debug_entry:
+ // EFI_IMAGE_DEBUG_CODEVIEW_NB10_ENTRY
+ .ascii "NB10" // Signature
+ .long 0 // Unknown
+ .long 0 // Unknown2
+ .long 0 // Unknown3
+
+ .asciz ZBOOT_EFI_PATH
+
+ .set .Lefi_debug_entry_size, . - .Lefi_debug_entry
+#endif
+
+ .p2align 12
+.Lefi_header_end:
+
diff --git a/drivers/firmware/efi/libstub/zboot.c b/drivers/firmware/efi/libstub/zboot.c
new file mode 100644
index 000000000000..ea72c8f27da6
--- /dev/null
+++ b/drivers/firmware/efi/libstub/zboot.c
@@ -0,0 +1,302 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/efi.h>
+#include <linux/pe.h>
+#include <asm/efi.h>
+#include <asm/unaligned.h>
+
+#include "efistub.h"
+
+static unsigned char zboot_heap[SZ_256K] __aligned(64);
+static unsigned long free_mem_ptr, free_mem_end_ptr;
+
+#define STATIC static
+#if defined(CONFIG_KERNEL_GZIP)
+#include "../../../../lib/decompress_inflate.c"
+#elif defined(CONFIG_KERNEL_LZ4)
+#include "../../../../lib/decompress_unlz4.c"
+#elif defined(CONFIG_KERNEL_LZMA)
+#include "../../../../lib/decompress_unlzma.c"
+#elif defined(CONFIG_KERNEL_LZO)
+#include "../../../../lib/decompress_unlzo.c"
+#elif defined(CONFIG_KERNEL_XZ)
+#undef memcpy
+#define memcpy memcpy
+#undef memmove
+#define memmove memmove
+#include "../../../../lib/decompress_unxz.c"
+#elif defined(CONFIG_KERNEL_ZSTD)
+#include "../../../../lib/decompress_unzstd.c"
+#endif
+
+extern char efi_zboot_header[];
+extern char _gzdata_start[], _gzdata_end[];
+
+static void log(efi_char16_t str[])
+{
+ efi_call_proto(efi_table_attr(efi_system_table, con_out),
+ output_string, L"EFI decompressor: ");
+ efi_call_proto(efi_table_attr(efi_system_table, con_out),
+ output_string, str);
+ efi_call_proto(efi_table_attr(efi_system_table, con_out),
+ output_string, L"\n");
+}
+
+static void error(char *x)
+{
+ log(L"error() called from decompressor library\n");
+}
+
+// Local version to avoid pulling in memcmp()
+static bool guids_eq(const efi_guid_t *a, const efi_guid_t *b)
+{
+ const u32 *l = (u32 *)a;
+ const u32 *r = (u32 *)b;
+
+ return l[0] == r[0] && l[1] == r[1] && l[2] == r[2] && l[3] == r[3];
+}
+
+static efi_status_t __efiapi
+load_file(efi_load_file_protocol_t *this, efi_device_path_protocol_t *rem,
+ bool boot_policy, unsigned long *bufsize, void *buffer)
+{
+ unsigned long compressed_size = _gzdata_end - _gzdata_start;
+ struct efi_vendor_dev_path *vendor_dp;
+ bool decompress = false;
+ unsigned long size;
+ int ret;
+
+ if (rem == NULL || bufsize == NULL)
+ return EFI_INVALID_PARAMETER;
+
+ if (boot_policy)
+ return EFI_UNSUPPORTED;
+
+ // Look for our vendor media device node in the remaining file path
+ if (rem->type == EFI_DEV_MEDIA &&
+ rem->sub_type == EFI_DEV_MEDIA_VENDOR) {
+ vendor_dp = container_of(rem, struct efi_vendor_dev_path, header);
+ if (!guids_eq(&vendor_dp->vendorguid, &LINUX_EFI_ZBOOT_MEDIA_GUID))
+ return EFI_NOT_FOUND;
+
+ decompress = true;
+ rem = (void *)(vendor_dp + 1);
+ }
+
+ if (rem->type != EFI_DEV_END_PATH ||
+ rem->sub_type != EFI_DEV_END_ENTIRE)
+ return EFI_NOT_FOUND;
+
+ // The uncompressed size of the payload is appended to the raw bit
+ // stream, and may therefore appear misaligned in memory
+ size = decompress ? get_unaligned_le32(_gzdata_end - 4)
+ : compressed_size;
+ if (buffer == NULL || *bufsize < size) {
+ *bufsize = size;
+ return EFI_BUFFER_TOO_SMALL;
+ }
+
+ if (decompress) {
+ ret = __decompress(_gzdata_start, compressed_size, NULL, NULL,
+ buffer, size, NULL, error);
+ if (ret < 0) {
+ log(L"Decompression failed");
+ return EFI_DEVICE_ERROR;
+ }
+ } else {
+ memcpy(buffer, _gzdata_start, compressed_size);
+ }
+
+ return EFI_SUCCESS;
+}
+
+// Return the length in bytes of the device path up to the first end node.
+static int device_path_length(const efi_device_path_protocol_t *dp)
+{
+ int len = 0;
+
+ while (dp->type != EFI_DEV_END_PATH) {
+ len += dp->length;
+ dp = (void *)((u8 *)dp + dp->length);
+ }
+ return len;
+}
+
+static void append_rel_offset_node(efi_device_path_protocol_t **dp,
+ unsigned long start, unsigned long end)
+{
+ struct efi_rel_offset_dev_path *rodp = (void *)*dp;
+
+ rodp->header.type = EFI_DEV_MEDIA;
+ rodp->header.sub_type = EFI_DEV_MEDIA_REL_OFFSET;
+ rodp->header.length = sizeof(struct efi_rel_offset_dev_path);
+ rodp->reserved = 0;
+ rodp->starting_offset = start;
+ rodp->ending_offset = end;
+
+ *dp = (void *)(rodp + 1);
+}
+
+static void append_ven_media_node(efi_device_path_protocol_t **dp,
+ efi_guid_t *guid)
+{
+ struct efi_vendor_dev_path *vmdp = (void *)*dp;
+
+ vmdp->header.type = EFI_DEV_MEDIA;
+ vmdp->header.sub_type = EFI_DEV_MEDIA_VENDOR;
+ vmdp->header.length = sizeof(struct efi_vendor_dev_path);
+ vmdp->vendorguid = *guid;
+
+ *dp = (void *)(vmdp + 1);
+}
+
+static void append_end_node(efi_device_path_protocol_t **dp)
+{
+ (*dp)->type = EFI_DEV_END_PATH;
+ (*dp)->sub_type = EFI_DEV_END_ENTIRE;
+ (*dp)->length = sizeof(struct efi_generic_dev_path);
+
+ ++*dp;
+}
+
+asmlinkage efi_status_t __efiapi
+efi_zboot_entry(efi_handle_t handle, efi_system_table_t *systab)
+{
+ struct efi_mem_mapped_dev_path mmdp = {
+ .header.type = EFI_DEV_HW,
+ .header.sub_type = EFI_DEV_MEM_MAPPED,
+ .header.length = sizeof(struct efi_mem_mapped_dev_path)
+ };
+ efi_device_path_protocol_t *parent_dp, *dpp, *lf2_dp, *li_dp;
+ efi_load_file2_protocol_t zboot_load_file2;
+ efi_loaded_image_t *parent, *child;
+ unsigned long exit_data_size;
+ efi_handle_t child_handle;
+ efi_handle_t zboot_handle;
+ efi_char16_t *exit_data;
+ efi_status_t status;
+ void *dp_alloc;
+ int dp_len;
+
+ WRITE_ONCE(efi_system_table, systab);
+
+ free_mem_ptr = (unsigned long)&zboot_heap;
+ free_mem_end_ptr = free_mem_ptr + sizeof(zboot_heap);
+
+ exit_data = NULL;
+ exit_data_size = 0;
+
+ status = efi_bs_call(handle_protocol, handle,
+ &LOADED_IMAGE_PROTOCOL_GUID, (void **)&parent);
+ if (status != EFI_SUCCESS) {
+ log(L"Failed to locate parent's loaded image protocol");
+ return status;
+ }
+
+ status = efi_bs_call(handle_protocol, handle,
+ &LOADED_IMAGE_DEVICE_PATH_PROTOCOL_GUID,
+ (void **)&parent_dp);
+ if (status != EFI_SUCCESS || parent_dp == NULL) {
+ // Create a MemoryMapped() device path node to describe
+ // the parent image if no device path was provided.
+ mmdp.memory_type = parent->image_code_type;
+ mmdp.starting_addr = (unsigned long)parent->image_base;
+ mmdp.ending_addr = (unsigned long)parent->image_base +
+ parent->image_size - 1;
+ parent_dp = &mmdp.header;
+ dp_len = sizeof(mmdp);
+ } else {
+ dp_len = device_path_length(parent_dp);
+ }
+
+ // Allocate some pool memory for device path protocol data
+ status = efi_bs_call(allocate_pool, EFI_LOADER_DATA,
+ 2 * (dp_len + sizeof(struct efi_rel_offset_dev_path) +
+ sizeof(struct efi_generic_dev_path)) +
+ sizeof(struct efi_vendor_dev_path),
+ (void **)&dp_alloc);
+ if (status != EFI_SUCCESS) {
+ log(L"Failed to allocate device path pool memory");
+ return status;
+ }
+
+ // Create a device path describing the compressed payload in this image
+ // <...parent_dp...>/Offset(<start>, <end>)
+ lf2_dp = memcpy(dp_alloc, parent_dp, dp_len);
+ dpp = (void *)((u8 *)lf2_dp + dp_len);
+ append_rel_offset_node(&dpp,
+ (unsigned long)(_gzdata_start - efi_zboot_header),
+ (unsigned long)(_gzdata_end - efi_zboot_header - 1));
+ append_end_node(&dpp);
+
+ // Create a device path describing the decompressed payload in this image
+ // <...parent_dp...>/Offset(<start>, <end>)/VenMedia(ZBOOT_MEDIA_GUID)
+ dp_len += sizeof(struct efi_rel_offset_dev_path);
+ li_dp = memcpy(dpp, lf2_dp, dp_len);
+ dpp = (void *)((u8 *)li_dp + dp_len);
+ append_ven_media_node(&dpp, &LINUX_EFI_ZBOOT_MEDIA_GUID);
+ append_end_node(&dpp);
+
+ zboot_handle = NULL;
+ zboot_load_file2.load_file = load_file;
+ status = efi_bs_call(install_multiple_protocol_interfaces,
+ &zboot_handle,
+ &EFI_DEVICE_PATH_PROTOCOL_GUID, lf2_dp,
+ &EFI_LOAD_FILE2_PROTOCOL_GUID, &zboot_load_file2,
+ NULL);
+ if (status != EFI_SUCCESS) {
+ log(L"Failed to install LoadFile2 protocol and device path");
+ goto free_dpalloc;
+ }
+
+ status = efi_bs_call(load_image, false, handle, li_dp, NULL, 0,
+ &child_handle);
+ if (status != EFI_SUCCESS) {
+ log(L"Failed to load image");
+ goto uninstall_lf2;
+ }
+
+ status = efi_bs_call(handle_protocol, child_handle,
+ &LOADED_IMAGE_PROTOCOL_GUID, (void **)&child);
+ if (status != EFI_SUCCESS) {
+ log(L"Failed to locate child's loaded image protocol");
+ goto unload_image;
+ }
+
+ // Copy the kernel command line
+ child->load_options = parent->load_options;
+ child->load_options_size = parent->load_options_size;
+
+ status = efi_bs_call(start_image, child_handle, &exit_data_size,
+ &exit_data);
+ if (status != EFI_SUCCESS) {
+ log(L"StartImage() returned with error");
+ if (exit_data_size > 0)
+ log(exit_data);
+
+ // If StartImage() returns EFI_SECURITY_VIOLATION, the image is
+ // not unloaded so we need to do it by hand.
+ if (status == EFI_SECURITY_VIOLATION)
+unload_image:
+ efi_bs_call(unload_image, child_handle);
+ }
+
+uninstall_lf2:
+ efi_bs_call(uninstall_multiple_protocol_interfaces,
+ zboot_handle,
+ &EFI_DEVICE_PATH_PROTOCOL_GUID, lf2_dp,
+ &EFI_LOAD_FILE2_PROTOCOL_GUID, &zboot_load_file2,
+ NULL);
+
+free_dpalloc:
+ efi_bs_call(free_pool, dp_alloc);
+
+ efi_bs_call(exit, handle, status, exit_data_size, exit_data);
+
+ // Free ExitData in case Exit() returned with a failure code,
+ // but return the original status code.
+ log(L"Exit() returned with failure code");
+ if (exit_data != NULL)
+ efi_bs_call(free_pool, exit_data);
+ return status;
+}
diff --git a/drivers/firmware/efi/libstub/zboot.lds b/drivers/firmware/efi/libstub/zboot.lds
new file mode 100644
index 000000000000..93d33f68333b
--- /dev/null
+++ b/drivers/firmware/efi/libstub/zboot.lds
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+ENTRY(__efistub_efi_zboot_header);
+
+SECTIONS
+{
+ .head : ALIGN(4096) {
+ *(.head)
+ }
+
+ .text : {
+ *(.text* .init.text*)
+ }
+
+ .rodata : ALIGN(8) {
+ __efistub__gzdata_start = .;
+ *(.gzdata)
+ __efistub__gzdata_end = .;
+ *(.rodata* .init.rodata* .srodata*)
+ _etext = ALIGN(4096);
+ . = _etext;
+ }
+
+ .data : ALIGN(4096) {
+ *(.data* .init.data*)
+ _edata = ALIGN(512);
+ . = _edata;
+ }
+
+ .bss : {
+ *(.bss* .init.bss*)
+ _end = ALIGN(512);
+ . = _end;
+ }
+
+ /DISCARD/ : {
+ *(.modinfo .init.modinfo)
+ }
+}
+
+PROVIDE(__efistub__gzdata_size =
+ ABSOLUTE(__efistub__gzdata_end - __efistub__gzdata_start));
+
+PROVIDE(__data_rawsize = ABSOLUTE(_edata - _etext));
+PROVIDE(__data_size = ABSOLUTE(_end - _etext));
diff --git a/drivers/firmware/efi/memmap.c b/drivers/firmware/efi/memmap.c
index 4df55a55da84..6ec7970dbd40 100644
--- a/drivers/firmware/efi/memmap.c
+++ b/drivers/firmware/efi/memmap.c
@@ -59,8 +59,7 @@ static void __init efi_memmap_free(void)
* Depending on whether mm_init() has already been invoked or not,
* either memblock or "normal" page allocation is used.
*
- * Returns the physical address of the allocated memory map on
- * success, zero on failure.
+ * Returns zero on success, a negative error code on failure.
*/
int __init efi_memmap_alloc(unsigned int num_entries,
struct efi_memory_map_data *data)
@@ -245,7 +244,7 @@ int __init efi_memmap_install(struct efi_memory_map_data *data)
* @range: Address range (start, end) to split around
*
* Returns the number of additional EFI memmap entries required to
- * accomodate @range.
+ * accommodate @range.
*/
int __init efi_memmap_split_count(efi_memory_desc_t *md, struct range *range)
{
diff --git a/drivers/firmware/efi/mokvar-table.c b/drivers/firmware/efi/mokvar-table.c
index 38722d2009e2..5ed0602c2f75 100644
--- a/drivers/firmware/efi/mokvar-table.c
+++ b/drivers/firmware/efi/mokvar-table.c
@@ -359,4 +359,4 @@ static int __init efi_mokvar_sysfs_init(void)
}
return err;
}
-device_initcall(efi_mokvar_sysfs_init);
+fs_initcall(efi_mokvar_sysfs_init);
diff --git a/drivers/firmware/efi/reboot.c b/drivers/firmware/efi/reboot.c
index 73089a24f04b..ceae84c19d22 100644
--- a/drivers/firmware/efi/reboot.c
+++ b/drivers/firmware/efi/reboot.c
@@ -6,7 +6,7 @@
#include <linux/efi.h>
#include <linux/reboot.h>
-static void (*orig_pm_power_off)(void);
+static struct sys_off_handler *efi_sys_off_handler;
int efi_reboot_quirk_mode = -1;
@@ -51,15 +51,11 @@ bool __weak efi_poweroff_required(void)
return false;
}
-static void efi_power_off(void)
+static int efi_power_off(struct sys_off_data *data)
{
efi.reset_system(EFI_RESET_SHUTDOWN, EFI_SUCCESS, 0, NULL);
- /*
- * The above call should not return, if it does fall back to
- * the original power off method (typically ACPI poweroff).
- */
- if (orig_pm_power_off)
- orig_pm_power_off();
+
+ return NOTIFY_DONE;
}
static int __init efi_shutdown_init(void)
@@ -68,8 +64,13 @@ static int __init efi_shutdown_init(void)
return -ENODEV;
if (efi_poweroff_required()) {
- orig_pm_power_off = pm_power_off;
- pm_power_off = efi_power_off;
+ /* SYS_OFF_PRIO_FIRMWARE + 1 so that it runs before acpi_power_off */
+ efi_sys_off_handler =
+ register_sys_off_handler(SYS_OFF_MODE_POWER_OFF,
+ SYS_OFF_PRIO_FIRMWARE + 1,
+ efi_power_off, NULL);
+ if (IS_ERR(efi_sys_off_handler))
+ return PTR_ERR(efi_sys_off_handler);
}
return 0;
diff --git a/drivers/firmware/efi/riscv-runtime.c b/drivers/firmware/efi/riscv-runtime.c
index d28e715d2bcc..d0daacd2c903 100644
--- a/drivers/firmware/efi/riscv-runtime.c
+++ b/drivers/firmware/efi/riscv-runtime.c
@@ -41,7 +41,7 @@ static bool __init efi_virtmap_init(void)
if (!(md->attribute & EFI_MEMORY_RUNTIME))
continue;
- if (md->virt_addr == 0)
+ if (md->virt_addr == U64_MAX)
return false;
ret = efi_create_mapping(&efi_mm, md);
diff --git a/drivers/firmware/efi/runtime-map.c b/drivers/firmware/efi/runtime-map.c
index ad9ddefc9dcb..92a3d45a795c 100644
--- a/drivers/firmware/efi/runtime-map.c
+++ b/drivers/firmware/efi/runtime-map.c
@@ -79,6 +79,7 @@ static struct attribute *def_attrs[] = {
&map_attribute_attr.attr,
NULL
};
+ATTRIBUTE_GROUPS(def);
static const struct sysfs_ops map_attr_ops = {
.show = map_attr_show,
@@ -94,7 +95,7 @@ static void map_release(struct kobject *kobj)
static struct kobj_type __refdata map_ktype = {
.sysfs_ops = &map_attr_ops,
- .default_attrs = def_attrs,
+ .default_groups = def_groups,
.release = map_release,
};
diff --git a/drivers/firmware/efi/sysfb_efi.c b/drivers/firmware/efi/sysfb_efi.c
index 4c7c9dd7733f..7882d4b3f2be 100644
--- a/drivers/firmware/efi/sysfb_efi.c
+++ b/drivers/firmware/efi/sysfb_efi.c
@@ -26,8 +26,6 @@
#include <linux/sysfb.h>
#include <video/vga.h>
-#include <asm/efi.h>
-
enum {
OVERRIDE_NONE = 0x0,
OVERRIDE_BASE = 0x1,
diff --git a/drivers/firmware/efi/tpm.c b/drivers/firmware/efi/tpm.c
index 8f665678e9e3..e8d69bd548f3 100644
--- a/drivers/firmware/efi/tpm.c
+++ b/drivers/firmware/efi/tpm.c
@@ -97,7 +97,7 @@ int __init efi_tpm_eventlog_init(void)
goto out_calc;
}
- memblock_reserve((unsigned long)final_tbl,
+ memblock_reserve(efi.tpm_final_log,
tbl_size + sizeof(*final_tbl));
efi_tpm_final_log_size = tbl_size;
diff --git a/drivers/firmware/efi/vars.c b/drivers/firmware/efi/vars.c
index abdc8a6a3963..0ba9f18312f5 100644
--- a/drivers/firmware/efi/vars.c
+++ b/drivers/firmware/efi/vars.c
@@ -6,1214 +6,238 @@
* Copyright (C) 2004 Intel Corporation <matthew.e.tolentino@intel.com>
*/
-#include <linux/capability.h>
#include <linux/types.h>
+#include <linux/sizes.h>
#include <linux/errno.h>
#include <linux/init.h>
-#include <linux/mm.h>
#include <linux/module.h>
#include <linux/string.h>
#include <linux/smp.h>
#include <linux/efi.h>
-#include <linux/sysfs.h>
-#include <linux/device.h>
-#include <linux/slab.h>
-#include <linux/ctype.h>
#include <linux/ucs2_string.h>
/* Private pointer to registered efivars */
static struct efivars *__efivars;
-/*
- * efivars_lock protects three things:
- * 1) efivarfs_list and efivars_sysfs_list
- * 2) ->ops calls
- * 3) (un)registration of __efivars
- */
static DEFINE_SEMAPHORE(efivars_lock);
-static bool
-validate_device_path(efi_char16_t *var_name, int match, u8 *buffer,
- unsigned long len)
-{
- struct efi_generic_dev_path *node;
- int offset = 0;
-
- node = (struct efi_generic_dev_path *)buffer;
-
- if (len < sizeof(*node))
- return false;
-
- while (offset <= len - sizeof(*node) &&
- node->length >= sizeof(*node) &&
- node->length <= len - offset) {
- offset += node->length;
-
- if ((node->type == EFI_DEV_END_PATH ||
- node->type == EFI_DEV_END_PATH2) &&
- node->sub_type == EFI_DEV_END_ENTIRE)
- return true;
-
- node = (struct efi_generic_dev_path *)(buffer + offset);
- }
-
- /*
- * If we're here then either node->length pointed past the end
- * of the buffer or we reached the end of the buffer without
- * finding a device path end node.
- */
- return false;
-}
-
-static bool
-validate_boot_order(efi_char16_t *var_name, int match, u8 *buffer,
- unsigned long len)
-{
- /* An array of 16-bit integers */
- if ((len % 2) != 0)
- return false;
-
- return true;
-}
-
-static bool
-validate_load_option(efi_char16_t *var_name, int match, u8 *buffer,
- unsigned long len)
-{
- u16 filepathlength;
- int i, desclength = 0, namelen;
-
- namelen = ucs2_strnlen(var_name, EFI_VAR_NAME_LEN);
-
- /* Either "Boot" or "Driver" followed by four digits of hex */
- for (i = match; i < match+4; i++) {
- if (var_name[i] > 127 ||
- hex_to_bin(var_name[i] & 0xff) < 0)
- return true;
- }
-
- /* Reject it if there's 4 digits of hex and then further content */
- if (namelen > match + 4)
- return false;
-
- /* A valid entry must be at least 8 bytes */
- if (len < 8)
- return false;
-
- filepathlength = buffer[4] | buffer[5] << 8;
-
- /*
- * There's no stored length for the description, so it has to be
- * found by hand
- */
- desclength = ucs2_strsize((efi_char16_t *)(buffer + 6), len - 6) + 2;
-
- /* Each boot entry must have a descriptor */
- if (!desclength)
- return false;
-
- /*
- * If the sum of the length of the description, the claimed filepath
- * length and the original header are greater than the length of the
- * variable, it's malformed
- */
- if ((desclength + filepathlength + 6) > len)
- return false;
-
- /*
- * And, finally, check the filepath
- */
- return validate_device_path(var_name, match, buffer + desclength + 6,
- filepathlength);
-}
-
-static bool
-validate_uint16(efi_char16_t *var_name, int match, u8 *buffer,
- unsigned long len)
-{
- /* A single 16-bit integer */
- if (len != 2)
- return false;
-
- return true;
-}
-
-static bool
-validate_ascii_string(efi_char16_t *var_name, int match, u8 *buffer,
- unsigned long len)
-{
- int i;
-
- for (i = 0; i < len; i++) {
- if (buffer[i] > 127)
- return false;
-
- if (buffer[i] == 0)
- return true;
- }
-
- return false;
-}
-
-struct variable_validate {
- efi_guid_t vendor;
- char *name;
- bool (*validate)(efi_char16_t *var_name, int match, u8 *data,
- unsigned long len);
-};
-
-/*
- * This is the list of variables we need to validate, as well as the
- * whitelist for what we think is safe not to default to immutable.
- *
- * If it has a validate() method that's not NULL, it'll go into the
- * validation routine. If not, it is assumed valid, but still used for
- * whitelisting.
- *
- * Note that it's sorted by {vendor,name}, but globbed names must come after
- * any other name with the same prefix.
- */
-static const struct variable_validate variable_validate[] = {
- { EFI_GLOBAL_VARIABLE_GUID, "BootNext", validate_uint16 },
- { EFI_GLOBAL_VARIABLE_GUID, "BootOrder", validate_boot_order },
- { EFI_GLOBAL_VARIABLE_GUID, "Boot*", validate_load_option },
- { EFI_GLOBAL_VARIABLE_GUID, "DriverOrder", validate_boot_order },
- { EFI_GLOBAL_VARIABLE_GUID, "Driver*", validate_load_option },
- { EFI_GLOBAL_VARIABLE_GUID, "ConIn", validate_device_path },
- { EFI_GLOBAL_VARIABLE_GUID, "ConInDev", validate_device_path },
- { EFI_GLOBAL_VARIABLE_GUID, "ConOut", validate_device_path },
- { EFI_GLOBAL_VARIABLE_GUID, "ConOutDev", validate_device_path },
- { EFI_GLOBAL_VARIABLE_GUID, "ErrOut", validate_device_path },
- { EFI_GLOBAL_VARIABLE_GUID, "ErrOutDev", validate_device_path },
- { EFI_GLOBAL_VARIABLE_GUID, "Lang", validate_ascii_string },
- { EFI_GLOBAL_VARIABLE_GUID, "OsIndications", NULL },
- { EFI_GLOBAL_VARIABLE_GUID, "PlatformLang", validate_ascii_string },
- { EFI_GLOBAL_VARIABLE_GUID, "Timeout", validate_uint16 },
- { LINUX_EFI_CRASH_GUID, "*", NULL },
- { NULL_GUID, "", NULL },
-};
-
-/*
- * Check if @var_name matches the pattern given in @match_name.
- *
- * @var_name: an array of @len non-NUL characters.
- * @match_name: a NUL-terminated pattern string, optionally ending in "*". A
- * final "*" character matches any trailing characters @var_name,
- * including the case when there are none left in @var_name.
- * @match: on output, the number of non-wildcard characters in @match_name
- * that @var_name matches, regardless of the return value.
- * @return: whether @var_name fully matches @match_name.
- */
-static bool
-variable_matches(const char *var_name, size_t len, const char *match_name,
- int *match)
-{
- for (*match = 0; ; (*match)++) {
- char c = match_name[*match];
-
- switch (c) {
- case '*':
- /* Wildcard in @match_name means we've matched. */
- return true;
-
- case '\0':
- /* @match_name has ended. Has @var_name too? */
- return (*match == len);
-
- default:
- /*
- * We've reached a non-wildcard char in @match_name.
- * Continue only if there's an identical character in
- * @var_name.
- */
- if (*match < len && c == var_name[*match])
- continue;
- return false;
- }
- }
-}
-
-bool
-efivar_validate(efi_guid_t vendor, efi_char16_t *var_name, u8 *data,
- unsigned long data_size)
-{
- int i;
- unsigned long utf8_size;
- u8 *utf8_name;
-
- utf8_size = ucs2_utf8size(var_name);
- utf8_name = kmalloc(utf8_size + 1, GFP_KERNEL);
- if (!utf8_name)
- return false;
-
- ucs2_as_utf8(utf8_name, var_name, utf8_size);
- utf8_name[utf8_size] = '\0';
-
- for (i = 0; variable_validate[i].name[0] != '\0'; i++) {
- const char *name = variable_validate[i].name;
- int match = 0;
-
- if (efi_guidcmp(vendor, variable_validate[i].vendor))
- continue;
-
- if (variable_matches(utf8_name, utf8_size+1, name, &match)) {
- if (variable_validate[i].validate == NULL)
- break;
- kfree(utf8_name);
- return variable_validate[i].validate(var_name, match,
- data, data_size);
- }
- }
- kfree(utf8_name);
- return true;
-}
-EXPORT_SYMBOL_GPL(efivar_validate);
-
-bool
-efivar_variable_is_removable(efi_guid_t vendor, const char *var_name,
- size_t len)
-{
- int i;
- bool found = false;
- int match = 0;
-
- /*
- * Check if our variable is in the validated variables list
- */
- for (i = 0; variable_validate[i].name[0] != '\0'; i++) {
- if (efi_guidcmp(variable_validate[i].vendor, vendor))
- continue;
-
- if (variable_matches(var_name, len,
- variable_validate[i].name, &match)) {
- found = true;
- break;
- }
- }
-
- /*
- * If it's in our list, it is removable.
- */
- return found;
-}
-EXPORT_SYMBOL_GPL(efivar_variable_is_removable);
-
-static efi_status_t
-check_var_size(u32 attributes, unsigned long size)
+static efi_status_t check_var_size(bool nonblocking, u32 attributes,
+ unsigned long size)
{
const struct efivar_operations *fops;
-
- if (!__efivars)
- return EFI_UNSUPPORTED;
-
- fops = __efivars->ops;
-
- if (!fops->query_variable_store)
- return EFI_UNSUPPORTED;
-
- return fops->query_variable_store(attributes, size, false);
-}
-
-static efi_status_t
-check_var_size_nonblocking(u32 attributes, unsigned long size)
-{
- const struct efivar_operations *fops;
-
- if (!__efivars)
- return EFI_UNSUPPORTED;
+ efi_status_t status;
fops = __efivars->ops;
if (!fops->query_variable_store)
- return EFI_UNSUPPORTED;
-
- return fops->query_variable_store(attributes, size, true);
-}
-
-static bool variable_is_present(efi_char16_t *variable_name, efi_guid_t *vendor,
- struct list_head *head)
-{
- struct efivar_entry *entry, *n;
- unsigned long strsize1, strsize2;
- bool found = false;
-
- strsize1 = ucs2_strsize(variable_name, 1024);
- list_for_each_entry_safe(entry, n, head, list) {
- strsize2 = ucs2_strsize(entry->var.VariableName, 1024);
- if (strsize1 == strsize2 &&
- !memcmp(variable_name, &(entry->var.VariableName),
- strsize2) &&
- !efi_guidcmp(entry->var.VendorGuid,
- *vendor)) {
- found = true;
- break;
- }
- }
- return found;
-}
-
-/*
- * Returns the size of variable_name, in bytes, including the
- * terminating NULL character, or variable_name_size if no NULL
- * character is found among the first variable_name_size bytes.
- */
-static unsigned long var_name_strnsize(efi_char16_t *variable_name,
- unsigned long variable_name_size)
-{
- unsigned long len;
- efi_char16_t c;
-
- /*
- * The variable name is, by definition, a NULL-terminated
- * string, so make absolutely sure that variable_name_size is
- * the value we expect it to be. If not, return the real size.
- */
- for (len = 2; len <= variable_name_size; len += sizeof(c)) {
- c = variable_name[(len / sizeof(c)) - 1];
- if (!c)
- break;
- }
-
- return min(len, variable_name_size);
-}
-
-/*
- * Print a warning when duplicate EFI variables are encountered and
- * disable the sysfs workqueue since the firmware is buggy.
- */
-static void dup_variable_bug(efi_char16_t *str16, efi_guid_t *vendor_guid,
- unsigned long len16)
-{
- size_t i, len8 = len16 / sizeof(efi_char16_t);
- char *str8;
-
- str8 = kzalloc(len8, GFP_KERNEL);
- if (!str8)
- return;
-
- for (i = 0; i < len8; i++)
- str8[i] = str16[i];
-
- printk(KERN_WARNING "efivars: duplicate variable: %s-%pUl\n",
- str8, vendor_guid);
- kfree(str8);
+ status = EFI_UNSUPPORTED;
+ else
+ status = fops->query_variable_store(attributes, size,
+ nonblocking);
+ if (status == EFI_UNSUPPORTED)
+ return (size <= SZ_64K) ? EFI_SUCCESS : EFI_OUT_OF_RESOURCES;
+ return status;
}
/**
- * efivar_init - build the initial list of EFI variables
- * @func: callback function to invoke for every variable
- * @data: function-specific data to pass to @func
- * @duplicates: error if we encounter duplicates on @head?
- * @head: initialised head of variable list
- *
- * Get every EFI variable from the firmware and invoke @func. @func
- * should call efivar_entry_add() to build the list of variables.
+ * efivars_kobject - get the kobject for the registered efivars
*
- * Returns 0 on success, or a kernel error code on failure.
+ * If efivars_register() has not been called we return NULL,
+ * otherwise return the kobject used at registration time.
*/
-int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *),
- void *data, bool duplicates, struct list_head *head)
+struct kobject *efivars_kobject(void)
{
- const struct efivar_operations *ops;
- unsigned long variable_name_size = 1024;
- efi_char16_t *variable_name;
- efi_status_t status;
- efi_guid_t vendor_guid;
- int err = 0;
-
if (!__efivars)
- return -EFAULT;
-
- ops = __efivars->ops;
-
- variable_name = kzalloc(variable_name_size, GFP_KERNEL);
- if (!variable_name) {
- printk(KERN_ERR "efivars: Memory allocation failed.\n");
- return -ENOMEM;
- }
-
- if (down_interruptible(&efivars_lock)) {
- err = -EINTR;
- goto free;
- }
-
- /*
- * Per EFI spec, the maximum storage allocated for both
- * the variable name and variable data is 1024 bytes.
- */
-
- do {
- variable_name_size = 1024;
-
- status = ops->get_next_variable(&variable_name_size,
- variable_name,
- &vendor_guid);
- switch (status) {
- case EFI_SUCCESS:
- if (duplicates)
- up(&efivars_lock);
-
- variable_name_size = var_name_strnsize(variable_name,
- variable_name_size);
-
- /*
- * Some firmware implementations return the
- * same variable name on multiple calls to
- * get_next_variable(). Terminate the loop
- * immediately as there is no guarantee that
- * we'll ever see a different variable name,
- * and may end up looping here forever.
- */
- if (duplicates &&
- variable_is_present(variable_name, &vendor_guid,
- head)) {
- dup_variable_bug(variable_name, &vendor_guid,
- variable_name_size);
- status = EFI_NOT_FOUND;
- } else {
- err = func(variable_name, vendor_guid,
- variable_name_size, data);
- if (err)
- status = EFI_NOT_FOUND;
- }
-
- if (duplicates) {
- if (down_interruptible(&efivars_lock)) {
- err = -EINTR;
- goto free;
- }
- }
-
- break;
- case EFI_UNSUPPORTED:
- err = -EOPNOTSUPP;
- status = EFI_NOT_FOUND;
- break;
- case EFI_NOT_FOUND:
- break;
- default:
- printk(KERN_WARNING "efivars: get_next_variable: status=%lx\n",
- status);
- status = EFI_NOT_FOUND;
- break;
- }
-
- } while (status != EFI_NOT_FOUND);
-
- up(&efivars_lock);
-free:
- kfree(variable_name);
+ return NULL;
- return err;
+ return __efivars->kobject;
}
-EXPORT_SYMBOL_GPL(efivar_init);
+EXPORT_SYMBOL_GPL(efivars_kobject);
/**
- * efivar_entry_add - add entry to variable list
- * @entry: entry to add to list
- * @head: list head
+ * efivars_register - register an efivars
+ * @efivars: efivars to register
+ * @ops: efivars operations
+ * @kobject: @efivars-specific kobject
*
- * Returns 0 on success, or a kernel error code on failure.
+ * Only a single efivars can be registered at any time.
*/
-int efivar_entry_add(struct efivar_entry *entry, struct list_head *head)
+int efivars_register(struct efivars *efivars,
+ const struct efivar_operations *ops,
+ struct kobject *kobject)
{
if (down_interruptible(&efivars_lock))
return -EINTR;
- list_add(&entry->list, head);
- up(&efivars_lock);
- return 0;
-}
-EXPORT_SYMBOL_GPL(efivar_entry_add);
+ efivars->ops = ops;
+ efivars->kobject = kobject;
-/**
- * efivar_entry_remove - remove entry from variable list
- * @entry: entry to remove from list
- *
- * Returns 0 on success, or a kernel error code on failure.
- */
-int efivar_entry_remove(struct efivar_entry *entry)
-{
- if (down_interruptible(&efivars_lock))
- return -EINTR;
- list_del(&entry->list);
- up(&efivars_lock);
+ __efivars = efivars;
- return 0;
-}
-EXPORT_SYMBOL_GPL(efivar_entry_remove);
+ pr_info("Registered efivars operations\n");
-/*
- * efivar_entry_list_del_unlock - remove entry from variable list
- * @entry: entry to remove
- *
- * Remove @entry from the variable list and release the list lock.
- *
- * NOTE: slightly weird locking semantics here - we expect to be
- * called with the efivars lock already held, and we release it before
- * returning. This is because this function is usually called after
- * set_variable() while the lock is still held.
- */
-static void efivar_entry_list_del_unlock(struct efivar_entry *entry)
-{
- list_del(&entry->list);
up(&efivars_lock);
-}
-
-/**
- * __efivar_entry_delete - delete an EFI variable
- * @entry: entry containing EFI variable to delete
- *
- * Delete the variable from the firmware but leave @entry on the
- * variable list.
- *
- * This function differs from efivar_entry_delete() because it does
- * not remove @entry from the variable list. Also, it is safe to be
- * called from within a efivar_entry_iter_begin() and
- * efivar_entry_iter_end() region, unlike efivar_entry_delete().
- *
- * Returns 0 on success, or a converted EFI status code if
- * set_variable() fails.
- */
-int __efivar_entry_delete(struct efivar_entry *entry)
-{
- efi_status_t status;
-
- if (!__efivars)
- return -EINVAL;
-
- status = __efivars->ops->set_variable(entry->var.VariableName,
- &entry->var.VendorGuid,
- 0, 0, NULL);
-
- return efi_status_to_err(status);
-}
-EXPORT_SYMBOL_GPL(__efivar_entry_delete);
-
-/**
- * efivar_entry_delete - delete variable and remove entry from list
- * @entry: entry containing variable to delete
- *
- * Delete the variable from the firmware and remove @entry from the
- * variable list. It is the caller's responsibility to free @entry
- * once we return.
- *
- * Returns 0 on success, -EINTR if we can't grab the semaphore,
- * converted EFI status code if set_variable() fails.
- */
-int efivar_entry_delete(struct efivar_entry *entry)
-{
- const struct efivar_operations *ops;
- efi_status_t status;
- if (down_interruptible(&efivars_lock))
- return -EINTR;
-
- if (!__efivars) {
- up(&efivars_lock);
- return -EINVAL;
- }
- ops = __efivars->ops;
- status = ops->set_variable(entry->var.VariableName,
- &entry->var.VendorGuid,
- 0, 0, NULL);
- if (!(status == EFI_SUCCESS || status == EFI_NOT_FOUND)) {
- up(&efivars_lock);
- return efi_status_to_err(status);
- }
-
- efivar_entry_list_del_unlock(entry);
return 0;
}
-EXPORT_SYMBOL_GPL(efivar_entry_delete);
+EXPORT_SYMBOL_GPL(efivars_register);
/**
- * efivar_entry_set - call set_variable()
- * @entry: entry containing the EFI variable to write
- * @attributes: variable attributes
- * @size: size of @data buffer
- * @data: buffer containing variable data
- * @head: head of variable list
- *
- * Calls set_variable() for an EFI variable. If creating a new EFI
- * variable, this function is usually followed by efivar_entry_add().
- *
- * Before writing the variable, the remaining EFI variable storage
- * space is checked to ensure there is enough room available.
- *
- * If @head is not NULL a lookup is performed to determine whether
- * the entry is already on the list.
+ * efivars_unregister - unregister an efivars
+ * @efivars: efivars to unregister
*
- * Returns 0 on success, -EINTR if we can't grab the semaphore,
- * -EEXIST if a lookup is performed and the entry already exists on
- * the list, or a converted EFI status code if set_variable() fails.
+ * The caller must have already removed every entry from the list,
+ * failure to do so is an error.
*/
-int efivar_entry_set(struct efivar_entry *entry, u32 attributes,
- unsigned long size, void *data, struct list_head *head)
+int efivars_unregister(struct efivars *efivars)
{
- const struct efivar_operations *ops;
- efi_status_t status;
- efi_char16_t *name = entry->var.VariableName;
- efi_guid_t vendor = entry->var.VendorGuid;
+ int rv;
if (down_interruptible(&efivars_lock))
return -EINTR;
if (!__efivars) {
- up(&efivars_lock);
- return -EINVAL;
- }
- ops = __efivars->ops;
- if (head && efivar_entry_find(name, vendor, head, false)) {
- up(&efivars_lock);
- return -EEXIST;
- }
-
- status = check_var_size(attributes, size + ucs2_strsize(name, 1024));
- if (status == EFI_SUCCESS || status == EFI_UNSUPPORTED)
- status = ops->set_variable(name, &vendor,
- attributes, size, data);
-
- up(&efivars_lock);
-
- return efi_status_to_err(status);
-
-}
-EXPORT_SYMBOL_GPL(efivar_entry_set);
-
-/*
- * efivar_entry_set_nonblocking - call set_variable_nonblocking()
- *
- * This function is guaranteed to not block and is suitable for calling
- * from crash/panic handlers.
- *
- * Crucially, this function will not block if it cannot acquire
- * efivars_lock. Instead, it returns -EBUSY.
- */
-static int
-efivar_entry_set_nonblocking(efi_char16_t *name, efi_guid_t vendor,
- u32 attributes, unsigned long size, void *data)
-{
- const struct efivar_operations *ops;
- efi_status_t status;
-
- if (down_trylock(&efivars_lock))
- return -EBUSY;
-
- if (!__efivars) {
- up(&efivars_lock);
- return -EINVAL;
- }
-
- status = check_var_size_nonblocking(attributes,
- size + ucs2_strsize(name, 1024));
- if (status != EFI_SUCCESS) {
- up(&efivars_lock);
- return -ENOSPC;
- }
-
- ops = __efivars->ops;
- status = ops->set_variable_nonblocking(name, &vendor, attributes,
- size, data);
-
- up(&efivars_lock);
- return efi_status_to_err(status);
-}
-
-/**
- * efivar_entry_set_safe - call set_variable() if enough space in firmware
- * @name: buffer containing the variable name
- * @vendor: variable vendor guid
- * @attributes: variable attributes
- * @block: can we block in this context?
- * @size: size of @data buffer
- * @data: buffer containing variable data
- *
- * Ensures there is enough free storage in the firmware for this variable, and
- * if so, calls set_variable(). If creating a new EFI variable, this function
- * is usually followed by efivar_entry_add().
- *
- * Returns 0 on success, -ENOSPC if the firmware does not have enough
- * space for set_variable() to succeed, or a converted EFI status code
- * if set_variable() fails.
- */
-int efivar_entry_set_safe(efi_char16_t *name, efi_guid_t vendor, u32 attributes,
- bool block, unsigned long size, void *data)
-{
- const struct efivar_operations *ops;
- efi_status_t status;
-
- if (!__efivars)
- return -EINVAL;
-
- ops = __efivars->ops;
- if (!ops->query_variable_store)
- return -ENOSYS;
-
- /*
- * If the EFI variable backend provides a non-blocking
- * ->set_variable() operation and we're in a context where we
- * cannot block, then we need to use it to avoid live-locks,
- * since the implication is that the regular ->set_variable()
- * will block.
- *
- * If no ->set_variable_nonblocking() is provided then
- * ->set_variable() is assumed to be non-blocking.
- */
- if (!block && ops->set_variable_nonblocking)
- return efivar_entry_set_nonblocking(name, vendor, attributes,
- size, data);
-
- if (!block) {
- if (down_trylock(&efivars_lock))
- return -EBUSY;
- } else {
- if (down_interruptible(&efivars_lock))
- return -EINTR;
+ printk(KERN_ERR "efivars not registered\n");
+ rv = -EINVAL;
+ goto out;
}
- status = check_var_size(attributes, size + ucs2_strsize(name, 1024));
- if (status != EFI_SUCCESS) {
- up(&efivars_lock);
- return -ENOSPC;
+ if (__efivars != efivars) {
+ rv = -EINVAL;
+ goto out;
}
- status = ops->set_variable(name, &vendor, attributes, size, data);
+ pr_info("Unregistered efivars operations\n");
+ __efivars = NULL;
+ rv = 0;
+out:
up(&efivars_lock);
-
- return efi_status_to_err(status);
+ return rv;
}
-EXPORT_SYMBOL_GPL(efivar_entry_set_safe);
+EXPORT_SYMBOL_GPL(efivars_unregister);
-/**
- * efivar_entry_find - search for an entry
- * @name: the EFI variable name
- * @guid: the EFI variable vendor's guid
- * @head: head of the variable list
- * @remove: should we remove the entry from the list?
- *
- * Search for an entry on the variable list that has the EFI variable
- * name @name and vendor guid @guid. If an entry is found on the list
- * and @remove is true, the entry is removed from the list.
- *
- * The caller MUST call efivar_entry_iter_begin() and
- * efivar_entry_iter_end() before and after the invocation of this
- * function, respectively.
- *
- * Returns the entry if found on the list, %NULL otherwise.
- */
-struct efivar_entry *efivar_entry_find(efi_char16_t *name, efi_guid_t guid,
- struct list_head *head, bool remove)
+int efivar_supports_writes(void)
{
- struct efivar_entry *entry, *n;
- int strsize1, strsize2;
- bool found = false;
-
- list_for_each_entry_safe(entry, n, head, list) {
- strsize1 = ucs2_strsize(name, 1024);
- strsize2 = ucs2_strsize(entry->var.VariableName, 1024);
- if (strsize1 == strsize2 &&
- !memcmp(name, &(entry->var.VariableName), strsize1) &&
- !efi_guidcmp(guid, entry->var.VendorGuid)) {
- found = true;
- break;
- }
- }
-
- if (!found)
- return NULL;
-
- if (remove) {
- if (entry->scanning) {
- /*
- * The entry will be deleted
- * after scanning is completed.
- */
- entry->deleting = true;
- } else
- list_del(&entry->list);
- }
-
- return entry;
+ return __efivars && __efivars->ops->set_variable;
}
-EXPORT_SYMBOL_GPL(efivar_entry_find);
+EXPORT_SYMBOL_GPL(efivar_supports_writes);
-/**
- * efivar_entry_size - obtain the size of a variable
- * @entry: entry for this variable
- * @size: location to store the variable's size
+/*
+ * efivar_lock() - obtain the efivar lock, wait for it if needed
+ * @return 0 on success, error code on failure
*/
-int efivar_entry_size(struct efivar_entry *entry, unsigned long *size)
+int efivar_lock(void)
{
- const struct efivar_operations *ops;
- efi_status_t status;
-
- *size = 0;
-
if (down_interruptible(&efivars_lock))
return -EINTR;
- if (!__efivars) {
+ if (!__efivars->ops) {
up(&efivars_lock);
- return -EINVAL;
+ return -ENODEV;
}
- ops = __efivars->ops;
- status = ops->get_variable(entry->var.VariableName,
- &entry->var.VendorGuid, NULL, size, NULL);
- up(&efivars_lock);
-
- if (status != EFI_BUFFER_TOO_SMALL)
- return efi_status_to_err(status);
-
return 0;
}
-EXPORT_SYMBOL_GPL(efivar_entry_size);
-
-/**
- * __efivar_entry_get - call get_variable()
- * @entry: read data for this variable
- * @attributes: variable attributes
- * @size: size of @data buffer
- * @data: buffer to store variable data
- *
- * The caller MUST call efivar_entry_iter_begin() and
- * efivar_entry_iter_end() before and after the invocation of this
- * function, respectively.
- */
-int __efivar_entry_get(struct efivar_entry *entry, u32 *attributes,
- unsigned long *size, void *data)
-{
- efi_status_t status;
-
- if (!__efivars)
- return -EINVAL;
-
- status = __efivars->ops->get_variable(entry->var.VariableName,
- &entry->var.VendorGuid,
- attributes, size, data);
-
- return efi_status_to_err(status);
-}
-EXPORT_SYMBOL_GPL(__efivar_entry_get);
+EXPORT_SYMBOL_NS_GPL(efivar_lock, EFIVAR);
-/**
- * efivar_entry_get - call get_variable()
- * @entry: read data for this variable
- * @attributes: variable attributes
- * @size: size of @data buffer
- * @data: buffer to store variable data
+/*
+ * efivar_lock() - obtain the efivar lock if it is free
+ * @return 0 on success, error code on failure
*/
-int efivar_entry_get(struct efivar_entry *entry, u32 *attributes,
- unsigned long *size, void *data)
+int efivar_trylock(void)
{
- efi_status_t status;
-
- if (down_interruptible(&efivars_lock))
- return -EINTR;
-
- if (!__efivars) {
+ if (down_trylock(&efivars_lock))
+ return -EBUSY;
+ if (!__efivars->ops) {
up(&efivars_lock);
- return -EINVAL;
+ return -ENODEV;
}
-
- status = __efivars->ops->get_variable(entry->var.VariableName,
- &entry->var.VendorGuid,
- attributes, size, data);
- up(&efivars_lock);
-
- return efi_status_to_err(status);
-}
-EXPORT_SYMBOL_GPL(efivar_entry_get);
-
-/**
- * efivar_entry_set_get_size - call set_variable() and get new size (atomic)
- * @entry: entry containing variable to set and get
- * @attributes: attributes of variable to be written
- * @size: size of data buffer
- * @data: buffer containing data to write
- * @set: did the set_variable() call succeed?
- *
- * This is a pretty special (complex) function. See efivarfs_file_write().
- *
- * Atomically call set_variable() for @entry and if the call is
- * successful, return the new size of the variable from get_variable()
- * in @size. The success of set_variable() is indicated by @set.
- *
- * Returns 0 on success, -EINVAL if the variable data is invalid,
- * -ENOSPC if the firmware does not have enough available space, or a
- * converted EFI status code if either of set_variable() or
- * get_variable() fail.
- *
- * If the EFI variable does not exist when calling set_variable()
- * (EFI_NOT_FOUND), @entry is removed from the variable list.
- */
-int efivar_entry_set_get_size(struct efivar_entry *entry, u32 attributes,
- unsigned long *size, void *data, bool *set)
-{
- const struct efivar_operations *ops;
- efi_char16_t *name = entry->var.VariableName;
- efi_guid_t *vendor = &entry->var.VendorGuid;
- efi_status_t status;
- int err;
-
- *set = false;
-
- if (efivar_validate(*vendor, name, data, *size) == false)
- return -EINVAL;
-
- /*
- * The lock here protects the get_variable call, the conditional
- * set_variable call, and removal of the variable from the efivars
- * list (in the case of an authenticated delete).
- */
- if (down_interruptible(&efivars_lock))
- return -EINTR;
-
- if (!__efivars) {
- err = -EINVAL;
- goto out;
- }
-
- /*
- * Ensure that the available space hasn't shrunk below the safe level
- */
- status = check_var_size(attributes, *size + ucs2_strsize(name, 1024));
- if (status != EFI_SUCCESS) {
- if (status != EFI_UNSUPPORTED) {
- err = efi_status_to_err(status);
- goto out;
- }
-
- if (*size > 65536) {
- err = -ENOSPC;
- goto out;
- }
- }
-
- ops = __efivars->ops;
-
- status = ops->set_variable(name, vendor, attributes, *size, data);
- if (status != EFI_SUCCESS) {
- err = efi_status_to_err(status);
- goto out;
- }
-
- *set = true;
-
- /*
- * Writing to the variable may have caused a change in size (which
- * could either be an append or an overwrite), or the variable to be
- * deleted. Perform a GetVariable() so we can tell what actually
- * happened.
- */
- *size = 0;
- status = ops->get_variable(entry->var.VariableName,
- &entry->var.VendorGuid,
- NULL, size, NULL);
-
- if (status == EFI_NOT_FOUND)
- efivar_entry_list_del_unlock(entry);
- else
- up(&efivars_lock);
-
- if (status && status != EFI_BUFFER_TOO_SMALL)
- return efi_status_to_err(status);
-
return 0;
-
-out:
- up(&efivars_lock);
- return err;
-
-}
-EXPORT_SYMBOL_GPL(efivar_entry_set_get_size);
-
-/**
- * efivar_entry_iter_begin - begin iterating the variable list
- *
- * Lock the variable list to prevent entry insertion and removal until
- * efivar_entry_iter_end() is called. This function is usually used in
- * conjunction with __efivar_entry_iter() or efivar_entry_iter().
- */
-int efivar_entry_iter_begin(void)
-{
- return down_interruptible(&efivars_lock);
}
-EXPORT_SYMBOL_GPL(efivar_entry_iter_begin);
+EXPORT_SYMBOL_NS_GPL(efivar_trylock, EFIVAR);
-/**
- * efivar_entry_iter_end - finish iterating the variable list
- *
- * Unlock the variable list and allow modifications to the list again.
+/*
+ * efivar_unlock() - release the efivar lock
*/
-void efivar_entry_iter_end(void)
+void efivar_unlock(void)
{
up(&efivars_lock);
}
-EXPORT_SYMBOL_GPL(efivar_entry_iter_end);
+EXPORT_SYMBOL_NS_GPL(efivar_unlock, EFIVAR);
-/**
- * __efivar_entry_iter - iterate over variable list
- * @func: callback function
- * @head: head of the variable list
- * @data: function-specific data to pass to callback
- * @prev: entry to begin iterating from
- *
- * Iterate over the list of EFI variables and call @func with every
- * entry on the list. It is safe for @func to remove entries in the
- * list via efivar_entry_delete().
- *
- * You MUST call efivar_entry_iter_begin() before this function, and
- * efivar_entry_iter_end() afterwards.
- *
- * It is possible to begin iteration from an arbitrary entry within
- * the list by passing @prev. @prev is updated on return to point to
- * the last entry passed to @func. To begin iterating from the
- * beginning of the list @prev must be %NULL.
- *
- * The restrictions for @func are the same as documented for
- * efivar_entry_iter().
- */
-int __efivar_entry_iter(int (*func)(struct efivar_entry *, void *),
- struct list_head *head, void *data,
- struct efivar_entry **prev)
-{
- struct efivar_entry *entry, *n;
- int err = 0;
-
- if (!prev || !*prev) {
- list_for_each_entry_safe(entry, n, head, list) {
- err = func(entry, data);
- if (err)
- break;
- }
-
- if (prev)
- *prev = entry;
-
- return err;
- }
-
-
- list_for_each_entry_safe_continue((*prev), n, head, list) {
- err = func(*prev, data);
- if (err)
- break;
- }
-
- return err;
-}
-EXPORT_SYMBOL_GPL(__efivar_entry_iter);
-
-/**
- * efivar_entry_iter - iterate over variable list
- * @func: callback function
- * @head: head of variable list
- * @data: function-specific data to pass to callback
- *
- * Iterate over the list of EFI variables and call @func with every
- * entry on the list. It is safe for @func to remove entries in the
- * list via efivar_entry_delete() while iterating.
+/*
+ * efivar_get_variable() - retrieve a variable identified by name/vendor
*
- * Some notes for the callback function:
- * - a non-zero return value indicates an error and terminates the loop
- * - @func is called from atomic context
+ * Must be called with efivars_lock held.
*/
-int efivar_entry_iter(int (*func)(struct efivar_entry *, void *),
- struct list_head *head, void *data)
+efi_status_t efivar_get_variable(efi_char16_t *name, efi_guid_t *vendor,
+ u32 *attr, unsigned long *size, void *data)
{
- int err = 0;
-
- err = efivar_entry_iter_begin();
- if (err)
- return err;
- err = __efivar_entry_iter(func, head, data, NULL);
- efivar_entry_iter_end();
-
- return err;
+ return __efivars->ops->get_variable(name, vendor, attr, size, data);
}
-EXPORT_SYMBOL_GPL(efivar_entry_iter);
+EXPORT_SYMBOL_NS_GPL(efivar_get_variable, EFIVAR);
-/**
- * efivars_kobject - get the kobject for the registered efivars
+/*
+ * efivar_get_next_variable() - enumerate the next name/vendor pair
*
- * If efivars_register() has not been called we return NULL,
- * otherwise return the kobject used at registration time.
+ * Must be called with efivars_lock held.
*/
-struct kobject *efivars_kobject(void)
+efi_status_t efivar_get_next_variable(unsigned long *name_size,
+ efi_char16_t *name, efi_guid_t *vendor)
{
- if (!__efivars)
- return NULL;
-
- return __efivars->kobject;
+ return __efivars->ops->get_next_variable(name_size, name, vendor);
}
-EXPORT_SYMBOL_GPL(efivars_kobject);
+EXPORT_SYMBOL_NS_GPL(efivar_get_next_variable, EFIVAR);
-/**
- * efivars_register - register an efivars
- * @efivars: efivars to register
- * @ops: efivars operations
- * @kobject: @efivars-specific kobject
+/*
+ * efivar_set_variable_locked() - set a variable identified by name/vendor
*
- * Only a single efivars can be registered at any time.
+ * Must be called with efivars_lock held. If @nonblocking is set, it will use
+ * non-blocking primitives so it is guaranteed not to sleep.
*/
-int efivars_register(struct efivars *efivars,
- const struct efivar_operations *ops,
- struct kobject *kobject)
+efi_status_t efivar_set_variable_locked(efi_char16_t *name, efi_guid_t *vendor,
+ u32 attr, unsigned long data_size,
+ void *data, bool nonblocking)
{
- if (down_interruptible(&efivars_lock))
- return -EINTR;
-
- efivars->ops = ops;
- efivars->kobject = kobject;
-
- __efivars = efivars;
+ efi_set_variable_t *setvar;
+ efi_status_t status;
- pr_info("Registered efivars operations\n");
+ if (data_size > 0) {
+ status = check_var_size(nonblocking, attr,
+ data_size + ucs2_strsize(name, 1024));
+ if (status != EFI_SUCCESS)
+ return status;
+ }
- up(&efivars_lock);
+ /*
+ * If no _nonblocking variant exists, the ordinary one
+ * is assumed to be non-blocking.
+ */
+ setvar = __efivars->ops->set_variable_nonblocking;
+ if (!setvar || !nonblocking)
+ setvar = __efivars->ops->set_variable;
- return 0;
+ return setvar(name, vendor, attr, data_size, data);
}
-EXPORT_SYMBOL_GPL(efivars_register);
+EXPORT_SYMBOL_NS_GPL(efivar_set_variable_locked, EFIVAR);
-/**
- * efivars_unregister - unregister an efivars
- * @efivars: efivars to unregister
+/*
+ * efivar_set_variable() - set a variable identified by name/vendor
*
- * The caller must have already removed every entry from the list,
- * failure to do so is an error.
+ * Can be called without holding the efivars_lock. Will sleep on obtaining the
+ * lock, or on obtaining other locks that are needed in order to complete the
+ * call.
*/
-int efivars_unregister(struct efivars *efivars)
+efi_status_t efivar_set_variable(efi_char16_t *name, efi_guid_t *vendor,
+ u32 attr, unsigned long data_size, void *data)
{
- int rv;
-
- if (down_interruptible(&efivars_lock))
- return -EINTR;
-
- if (!__efivars) {
- printk(KERN_ERR "efivars not registered\n");
- rv = -EINVAL;
- goto out;
- }
-
- if (__efivars != efivars) {
- rv = -EINVAL;
- goto out;
- }
-
- pr_info("Unregistered efivars operations\n");
- __efivars = NULL;
+ efi_status_t status;
- rv = 0;
-out:
- up(&efivars_lock);
- return rv;
-}
-EXPORT_SYMBOL_GPL(efivars_unregister);
+ if (efivar_lock())
+ return EFI_ABORTED;
-int efivar_supports_writes(void)
-{
- return __efivars && __efivars->ops->set_variable;
+ status = efivar_set_variable_locked(name, vendor, attr, data_size,
+ data, false);
+ efivar_unlock();
+ return status;
}
-EXPORT_SYMBOL_GPL(efivar_supports_writes);
+EXPORT_SYMBOL_NS_GPL(efivar_set_variable, EFIVAR);