summaryrefslogtreecommitdiff
path: root/drivers/firmware/efi
diff options
context:
space:
mode:
authorArd Biesheuvel <ardb@kernel.org>2022-09-26 22:32:16 +0300
committerArd Biesheuvel <ardb@kernel.org>2022-11-18 11:14:08 +0300
commit70912985545adc81716164271401c9ffb0acdd0f (patch)
tree58505e0cd83845246e2cecc207a20bdf3c09ec2f /drivers/firmware/efi
parent9cf42bca30e98a1c6c9e8abf876940a551eaa3d1 (diff)
downloadlinux-70912985545adc81716164271401c9ffb0acdd0f.tar.xz
efi: libstub: Implement devicepath support for initrd commandline loader
Currently, the initrd= command line option to the EFI stub only supports loading files that reside on the same volume as the loaded image, which is not workable for loaders like GRUB that don't even implement the volume abstraction (EFI_SIMPLE_FILE_SYSTEM_PROTOCOL), and load the kernel from an anonymous buffer in memory. For this reason, another method was devised that relies on the LoadFile2 protocol. However, the command line loader is rather useful when using the UEFI shell or other generic loaders that have no awareness of Linux specific protocols so let's make it a bit more flexible, by permitting textual device paths to be provided to initrd= as well, provided that they refer to a file hosted on a EFI_SIMPLE_FILE_SYSTEM_PROTOCOL volume. E.g., initrd=PciRoot(0x0)/Pci(0x3,0x0)/HD(1,MBR,0xBE1AFDFA,0x3F,0xFBFC1)/rootfs.cpio.gz Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
Diffstat (limited to 'drivers/firmware/efi')
-rw-r--r--drivers/firmware/efi/libstub/efistub.h15
-rw-r--r--drivers/firmware/efi/libstub/file.c78
2 files changed, 81 insertions, 12 deletions
diff --git a/drivers/firmware/efi/libstub/efistub.h b/drivers/firmware/efi/libstub/efistub.h
index e85916ed5311..1fde9cbe6899 100644
--- a/drivers/firmware/efi/libstub/efistub.h
+++ b/drivers/firmware/efi/libstub/efistub.h
@@ -179,6 +179,21 @@ union efi_device_path_to_text_protocol {
typedef union efi_device_path_to_text_protocol efi_device_path_to_text_protocol_t;
+union efi_device_path_from_text_protocol {
+ struct {
+ efi_device_path_protocol_t *
+ (__efiapi *convert_text_to_device_node)(const efi_char16_t *);
+ efi_device_path_protocol_t *
+ (__efiapi *convert_text_to_device_path)(const efi_char16_t *);
+ };
+ struct {
+ u32 convert_text_to_device_node;
+ u32 convert_text_to_device_path;
+ } mixed_mode;
+};
+
+typedef union efi_device_path_from_text_protocol efi_device_path_from_text_protocol_t;
+
typedef void *efi_event_t;
/* Note that notifications won't work in mixed mode */
typedef void (__efiapi *efi_event_notify_t)(efi_event_t, void *);
diff --git a/drivers/firmware/efi/libstub/file.c b/drivers/firmware/efi/libstub/file.c
index 246ccc5b015d..20d3530ca9c5 100644
--- a/drivers/firmware/efi/libstub/file.c
+++ b/drivers/firmware/efi/libstub/file.c
@@ -43,6 +43,13 @@ static efi_status_t efi_open_file(efi_file_protocol_t *volume,
efi_file_protocol_t *fh;
unsigned long info_sz;
efi_status_t status;
+ efi_char16_t *c;
+
+ /* Replace UNIX dir separators with EFI standard ones */
+ for (c = fi->filename; *c != L'\0'; c++) {
+ if (*c == L'/')
+ *c = L'\\';
+ }
status = volume->open(volume, &fh, fi->filename, EFI_FILE_MODE_READ, 0);
if (status != EFI_SUCCESS) {
@@ -111,16 +118,61 @@ static int find_file_option(const efi_char16_t *cmdline, int cmdline_len,
if (c == L'\0' || c == L'\n' || c == L' ')
break;
- else if (c == L'/')
- /* Replace UNIX dir separators with EFI standard ones */
- *result++ = L'\\';
- else
- *result++ = c;
+ *result++ = c;
}
*result = L'\0';
return i;
}
+static efi_status_t efi_open_device_path(efi_file_protocol_t **volume,
+ struct finfo *fi)
+{
+ efi_guid_t text_to_dp_guid = EFI_DEVICE_PATH_FROM_TEXT_PROTOCOL_GUID;
+ static efi_device_path_from_text_protocol_t *text_to_dp = NULL;
+ efi_guid_t fs_proto = EFI_FILE_SYSTEM_GUID;
+ efi_device_path_protocol_t *initrd_dp;
+ efi_simple_file_system_protocol_t *io;
+ struct efi_file_path_dev_path *fpath;
+ efi_handle_t handle;
+ efi_status_t status;
+
+ /* See if the text to device path protocol exists */
+ if (!text_to_dp &&
+ efi_bs_call(locate_protocol, &text_to_dp_guid, NULL,
+ (void **)&text_to_dp) != EFI_SUCCESS)
+ return EFI_UNSUPPORTED;
+
+
+ /* Convert the filename wide string into a device path */
+ initrd_dp = text_to_dp->convert_text_to_device_path(fi->filename);
+
+ /* Check whether the device path in question implements simple FS */
+ if ((efi_bs_call(locate_device_path, &fs_proto, &initrd_dp, &handle) ?:
+ efi_bs_call(handle_protocol, handle, &fs_proto, (void **)&io))
+ != EFI_SUCCESS)
+ return EFI_NOT_FOUND;
+
+ /* Check whether the remaining device path is a file device path */
+ if (initrd_dp->type != EFI_DEV_MEDIA ||
+ initrd_dp->sub_type != EFI_DEV_MEDIA_FILE) {
+ efi_warn("Unexpected device path node type: (%x, %x)\n",
+ initrd_dp->type, initrd_dp->sub_type);
+ return EFI_LOAD_ERROR;
+ }
+
+ /* Copy the remaining file path into the fi structure */
+ fpath = (struct efi_file_path_dev_path *)initrd_dp;
+ memcpy(fi->filename, fpath->filename,
+ min(sizeof(fi->filename),
+ fpath->header.length - sizeof(fpath->header)));
+
+ status = io->open_volume(io, volume);
+ if (status != EFI_SUCCESS)
+ efi_err("Failed to open volume\n");
+
+ return status;
+}
+
/*
* Check the cmdline for a LILO-style file= arguments.
*
@@ -170,11 +222,13 @@ efi_status_t handle_cmdline_files(efi_loaded_image_t *image,
cmdline += offset;
cmdline_len -= offset;
- if (!volume) {
+ status = efi_open_device_path(&volume, &fi);
+ if (status == EFI_UNSUPPORTED || status == EFI_NOT_FOUND)
+ /* try the volume that holds the kernel itself */
status = efi_open_volume(image, &volume);
- if (status != EFI_SUCCESS)
- return status;
- }
+
+ if (status != EFI_SUCCESS)
+ goto err_free_alloc;
status = efi_open_file(volume, &fi, &file, &size);
if (status != EFI_SUCCESS)
@@ -231,14 +285,12 @@ efi_status_t handle_cmdline_files(efi_loaded_image_t *image,
size -= chunksize;
}
file->close(file);
+ volume->close(volume);
} while (offset > 0);
*load_addr = alloc_addr;
*load_size = alloc_size;
- if (volume)
- volume->close(volume);
-
if (*load_size == 0)
return EFI_NOT_READY;
return EFI_SUCCESS;
@@ -248,6 +300,8 @@ err_close_file:
err_close_volume:
volume->close(volume);
+
+err_free_alloc:
efi_free(alloc_size, alloc_addr);
return status;
}