summaryrefslogtreecommitdiff
path: root/drivers/firmware
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/firmware')
-rw-r--r--drivers/firmware/efi/libstub/arm-stub.c15
-rw-r--r--drivers/firmware/efi/libstub/efi-stub-helper.c86
-rw-r--r--drivers/firmware/efi/libstub/efistub.h4
-rw-r--r--drivers/firmware/efi/libstub/x86-stub.c23
4 files changed, 125 insertions, 3 deletions
diff --git a/drivers/firmware/efi/libstub/arm-stub.c b/drivers/firmware/efi/libstub/arm-stub.c
index a11257a90dd0..11c673a7e95b 100644
--- a/drivers/firmware/efi/libstub/arm-stub.c
+++ b/drivers/firmware/efi/libstub/arm-stub.c
@@ -165,6 +165,7 @@ efi_status_t efi_entry(efi_handle_t handle, efi_system_table_t *sys_table_arg)
enum efi_secureboot_mode secure_boot;
struct screen_info *si;
efi_properties_table_t *prop_tbl;
+ unsigned long max_addr;
sys_table = sys_table_arg;
@@ -267,10 +268,18 @@ efi_status_t efi_entry(efi_handle_t handle, efi_system_table_t *sys_table_arg)
if (!fdt_addr)
pr_efi("Generating empty DTB\n");
- status = efi_load_initrd(image, &initrd_addr, &initrd_size, ULONG_MAX,
- efi_get_max_initrd_addr(dram_base, image_addr));
+ max_addr = efi_get_max_initrd_addr(dram_base, image_addr);
+ status = efi_load_initrd_dev_path(&initrd_addr, &initrd_size, max_addr);
+ if (status == EFI_SUCCESS) {
+ pr_efi("Loaded initrd from LINUX_EFI_INITRD_MEDIA_GUID device path\n");
+ } else if (status == EFI_NOT_FOUND) {
+ status = efi_load_initrd(image, &initrd_addr, &initrd_size,
+ ULONG_MAX, max_addr);
+ if (status == EFI_SUCCESS)
+ pr_efi("Loaded initrd from command line option\n");
+ }
if (status != EFI_SUCCESS)
- pr_efi_err("Failed initrd from command line!\n");
+ pr_efi_err("Failed to load initrd!\n");
efi_random_get_seed();
diff --git a/drivers/firmware/efi/libstub/efi-stub-helper.c b/drivers/firmware/efi/libstub/efi-stub-helper.c
index 49008ac88b63..6017b968cef7 100644
--- a/drivers/firmware/efi/libstub/efi-stub-helper.c
+++ b/drivers/firmware/efi/libstub/efi-stub-helper.c
@@ -299,3 +299,89 @@ void efi_char16_printk(efi_char16_t *str)
efi_call_proto(efi_table_attr(efi_system_table(), con_out),
output_string, str);
}
+
+/*
+ * The LINUX_EFI_INITRD_MEDIA_GUID vendor media device path below provides a way
+ * for the firmware or bootloader to expose the initrd data directly to the stub
+ * via the trivial LoadFile2 protocol, which is defined in the UEFI spec, and is
+ * very easy to implement. It is a simple Linux initrd specific conduit between
+ * kernel and firmware, allowing us to put the EFI stub (being part of the
+ * kernel) in charge of where and when to load the initrd, while leaving it up
+ * to the firmware to decide whether it needs to expose its filesystem hierarchy
+ * via EFI protocols.
+ */
+static const struct {
+ struct efi_vendor_dev_path vendor;
+ struct efi_generic_dev_path end;
+} __packed initrd_dev_path = {
+ {
+ {
+ EFI_DEV_MEDIA,
+ EFI_DEV_MEDIA_VENDOR,
+ sizeof(struct efi_vendor_dev_path),
+ },
+ LINUX_EFI_INITRD_MEDIA_GUID
+ }, {
+ EFI_DEV_END_PATH,
+ EFI_DEV_END_ENTIRE,
+ sizeof(struct efi_generic_dev_path)
+ }
+};
+
+/**
+ * efi_load_initrd_dev_path - load the initrd from the Linux initrd device path
+ * @load_addr: pointer to store the address where the initrd was loaded
+ * @load_size: pointer to store the size of the loaded initrd
+ * @max: upper limit for the initrd memory allocation
+ * @return: %EFI_SUCCESS if the initrd was loaded successfully, in which
+ * case @load_addr and @load_size are assigned accordingly
+ * %EFI_NOT_FOUND if no LoadFile2 protocol exists on the initrd
+ * device path
+ * %EFI_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
+ */
+efi_status_t efi_load_initrd_dev_path(unsigned long *load_addr,
+ unsigned long *load_size,
+ 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;
+
+ if (!load_addr || !load_size)
+ return EFI_INVALID_PARAMETER;
+
+ dp = (efi_device_path_protocol_t *)&initrd_dev_path;
+ status = efi_bs_call(locate_device_path, &lf2_proto_guid, &dp, &handle);
+ if (status != EFI_SUCCESS)
+ return status;
+
+ status = efi_bs_call(handle_protocol, handle, &lf2_proto_guid,
+ (void **)&lf2);
+ if (status != EFI_SUCCESS)
+ return status;
+
+ 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);
+ if (status != EFI_SUCCESS)
+ return status;
+
+ status = efi_call_proto(lf2, load_file, dp, false, &initrd_size,
+ (void *)initrd_addr);
+ if (status != EFI_SUCCESS) {
+ efi_free(initrd_size, initrd_addr);
+ return EFI_LOAD_ERROR;
+ }
+
+ *load_addr = initrd_addr;
+ *load_size = initrd_size;
+ return EFI_SUCCESS;
+}
diff --git a/drivers/firmware/efi/libstub/efistub.h b/drivers/firmware/efi/libstub/efistub.h
index 34fe3fad042f..b58cb2c4474e 100644
--- a/drivers/firmware/efi/libstub/efistub.h
+++ b/drivers/firmware/efi/libstub/efistub.h
@@ -640,4 +640,8 @@ efi_status_t efi_load_initrd(efi_loaded_image_t *image,
unsigned long soft_limit,
unsigned long hard_limit);
+efi_status_t efi_load_initrd_dev_path(unsigned long *load_addr,
+ unsigned long *load_size,
+ unsigned long max);
+
#endif
diff --git a/drivers/firmware/efi/libstub/x86-stub.c b/drivers/firmware/efi/libstub/x86-stub.c
index 681b620d8d40..16bf4ed21f1f 100644
--- a/drivers/firmware/efi/libstub/x86-stub.c
+++ b/drivers/firmware/efi/libstub/x86-stub.c
@@ -699,9 +699,14 @@ struct boot_params *efi_main(efi_handle_t handle,
{
unsigned long bzimage_addr = (unsigned long)startup_32;
struct setup_header *hdr = &boot_params->hdr;
+ unsigned long max_addr = hdr->initrd_addr_max;
+ unsigned long initrd_addr, initrd_size;
efi_status_t status;
unsigned long cmdline_paddr;
+ if (hdr->xloadflags & XLF_CAN_BE_LOADED_ABOVE_4G)
+ max_addr = ULONG_MAX;
+
sys_table = sys_table_arg;
/* Check if we were booted by the EFI firmware */
@@ -735,6 +740,24 @@ struct boot_params *efi_main(efi_handle_t handle,
efi_parse_options((char *)cmdline_paddr);
/*
+ * At this point, an initrd may already have been loaded, either by
+ * the bootloader and passed via bootparams, or loaded from a initrd=
+ * command line option by efi_pe_entry() above. In either case, we
+ * permit an initrd loaded from the LINUX_EFI_INITRD_MEDIA_GUID device
+ * path to supersede it.
+ */
+ status = efi_load_initrd_dev_path(&initrd_addr, &initrd_size, max_addr);
+ if (status == EFI_SUCCESS) {
+ hdr->ramdisk_image = (u32)initrd_addr;
+ hdr->ramdisk_size = (u32)initrd_size;
+ boot_params->ext_ramdisk_image = (u64)initrd_addr >> 32;
+ boot_params->ext_ramdisk_size = (u64)initrd_size >> 32;
+ } else if (status != EFI_NOT_FOUND) {
+ efi_printk("efi_load_initrd_dev_path() failed!\n");
+ goto fail;
+ }
+
+ /*
* If the boot loader gave us a value for secure_boot then we use that,
* otherwise we ask the BIOS.
*/