summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Brown <mcb30@ipxe.org>2023-02-15 15:48:31 +0000
committerMichael Brown <mcb30@ipxe.org>2023-02-15 17:36:47 +0000
commit6a004be0cceab5d669eedb5a2e6ee2feab31d5bd (patch)
tree433b4f9bb316d977b23d1e6bc7cec8467435c1d5
parentcf9ad00afcd6d8873fbefbbaf1f0813948a796ba (diff)
downloadqemu-ipxe-6a004be0cceab5d669eedb5a2e6ee2feab31d5bd.tar.gz
[efi] Support the initrd autodetection mechanism in newer Linux kernelsefiinitrd
Linux 5.7 added the ability to autodetect an initrd by searching for a handle via a fixed vendor-specific "Linux initrd device path" and then locating and using the EFI_LOAD_FILE2_PROTOCOL instance on that handle. This maps quite naturally onto our existing concept of a "magic initrd" as introduced for EFI in commit e5f0255 ("[efi] Provide an "initrd.magic" file for use by UEFI kernels"). Add an EFI_LOAD_FILE2_PROTOCOL instance to our EFI virtual files (backed by simply calling the existing EFI_SIMPLE_FILE_SYSTEM_PROTOCOL method to read from the file), and install the protocol instance for the "initrd.magic" virtual file onto a new device handle that also provides the Linux initrd device path. The design choice in Linux of using a single fixed device path makes this unfortunately messy to support, since device paths must be unique within a system. When multiple bootloaders are used (e.g. GRUB loading iPXE loading Linux) then only one bootloader can ever install the device path onto a handle. Subsequent bootloaders must locate the existing handle and replace the load file protocol instance with their own. Signed-off-by: Michael Brown <mcb30@ipxe.org>
-rw-r--r--src/interface/efi/efi_file.c190
1 files changed, 190 insertions, 0 deletions
diff --git a/src/interface/efi/efi_file.c b/src/interface/efi/efi_file.c
index 7ed3ea5a..e8debbbe 100644
--- a/src/interface/efi/efi_file.c
+++ b/src/interface/efi/efi_file.c
@@ -43,14 +43,21 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#include <ipxe/efi/Protocol/SimpleFileSystem.h>
#include <ipxe/efi/Protocol/BlockIo.h>
#include <ipxe/efi/Protocol/DiskIo.h>
+#include <ipxe/efi/Protocol/LoadFile2.h>
#include <ipxe/efi/Guid/FileInfo.h>
#include <ipxe/efi/Guid/FileSystemInfo.h>
#include <ipxe/efi/efi_strings.h>
+#include <ipxe/efi/efi_path.h>
#include <ipxe/efi/efi_file.h>
/** EFI media ID */
#define EFI_MEDIA_ID_MAGIC 0x69505845
+/** Linux initrd fixed device path vendor GUID */
+#define LINUX_INITRD_VENDOR_GUID \
+ { 0x5568e427, 0x68fc, 0x4f3d, \
+ { 0xac, 0x74, 0xca, 0x55, 0x52, 0x31, 0xcc, 0x68 } }
+
/** An EFI virtual file reader */
struct efi_file_reader {
/** EFI file */
@@ -69,6 +76,8 @@ struct efi_file {
struct refcnt refcnt;
/** EFI file protocol */
EFI_FILE_PROTOCOL file;
+ /** EFI load file protocol */
+ EFI_LOAD_FILE2_PROTOCOL load;
/** Image (if any) */
struct image *image;
/** Filename */
@@ -370,6 +379,8 @@ efi_file_open ( EFI_FILE_PROTOCOL *this, EFI_FILE_PROTOCOL **new,
ref_init ( &file->refcnt, efi_file_free );
memcpy ( &new_file->file, &efi_file_root.file,
sizeof ( new_file->file ) );
+ memcpy ( &new_file->load, &efi_file_root.load,
+ sizeof ( new_file->load ) );
efi_file_image ( new_file, image_get ( image ) );
*new = &new_file->file;
DBGC ( new_file, "EFIFILE %s opened\n", efi_file_name ( new_file ) );
@@ -684,6 +695,44 @@ static EFI_STATUS EFIAPI efi_file_flush ( EFI_FILE_PROTOCOL *this ) {
return 0;
}
+/**
+ * Load file
+ *
+ * @v this EFI file loader
+ * @v path File path
+ * @v boot Boot policy
+ * @v len Buffer size
+ * @v data Buffer, or NULL
+ * @ret efirc EFI status code
+ */
+static EFI_STATUS EFIAPI efi_file_load ( EFI_LOAD_FILE2_PROTOCOL *this,
+ EFI_DEVICE_PATH_PROTOCOL *path __unused,
+ BOOLEAN boot __unused, UINTN *len,
+ VOID *data ) {
+ struct efi_file *file = container_of ( this, struct efi_file, load );
+ size_t max_len;
+ size_t file_len;
+ EFI_STATUS efirc;
+
+ /* Calculate maximum length */
+ max_len = ( data ? *len : 0 );
+ DBGC ( file, "EFIFILE %s load at %p+%#zx\n",
+ efi_file_name ( file ), data, max_len );
+
+ /* Check buffer size */
+ file_len = efi_file_len ( file );
+ if ( file_len > max_len ) {
+ *len = file_len;
+ return EFI_BUFFER_TOO_SMALL;
+ }
+
+ /* Read from file */
+ if ( ( efirc = efi_file_read ( &file->file, len, data ) ) != 0 )
+ return efirc;
+
+ return 0;
+}
+
/** Root directory */
static struct efi_file efi_file_root = {
.refcnt = REF_INIT ( ref_no_free ),
@@ -700,6 +749,9 @@ static struct efi_file efi_file_root = {
.SetInfo = efi_file_set_info,
.Flush = efi_file_flush,
},
+ .load = {
+ .LoadFile = efi_file_load,
+ },
.image = NULL,
.name = "",
};
@@ -720,11 +772,34 @@ static struct efi_file efi_file_initrd = {
.SetInfo = efi_file_set_info,
.Flush = efi_file_flush,
},
+ .load = {
+ .LoadFile = efi_file_load,
+ },
.image = NULL,
.name = "initrd.magic",
.read = efi_file_read_initrd,
};
+/** Linux initrd fixed device path */
+static struct {
+ VENDOR_DEVICE_PATH vendor;
+ EFI_DEVICE_PATH_PROTOCOL end;
+} __attribute__ (( packed )) efi_file_initrd_path = {
+ .vendor = {
+ .Header = {
+ .Type = MEDIA_DEVICE_PATH,
+ .SubType = MEDIA_VENDOR_DP,
+ .Length[0] = sizeof ( efi_file_initrd_path.vendor ),
+ },
+ .Guid = LINUX_INITRD_VENDOR_GUID,
+ },
+ .end = {
+ .Type = END_DEVICE_PATH_TYPE,
+ .SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE,
+ .Length[0] = sizeof ( efi_file_initrd_path.end ),
+ },
+};
+
/**
* Open root directory
*
@@ -834,6 +909,110 @@ static EFI_DISK_IO_PROTOCOL efi_disk_io_protocol = {
};
/**
+ * (Re)install fixed device path file
+ *
+ * @v path Device path
+ * @v load Load file protocol, or NULL to uninstall protocol
+ * @ret rc Return status code
+ *
+ * Linux 5.7 added the ability to autodetect an initrd by searching
+ * for a handle via a fixed vendor-specific "Linux initrd device path"
+ * and then locating and using the EFI_LOAD_FILE2_PROTOCOL instance on
+ * that handle.
+ *
+ * The design choice in Linux of using a single fixed device path
+ * makes this unfortunately messy to support, since device paths must
+ * be unique within a system. When multiple bootloaders are used
+ * (e.g. GRUB loading iPXE loading Linux) then only one bootloader can
+ * ever install the device path onto a handle. Subsequent bootloaders
+ * must locate the existing handle and replace the load file protocol
+ * instance with their own.
+ */
+static int efi_file_path_install ( EFI_DEVICE_PATH_PROTOCOL *path,
+ EFI_LOAD_FILE2_PROTOCOL *load ) {
+ EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+ EFI_DEVICE_PATH_PROTOCOL *end;
+ EFI_HANDLE handle;
+ VOID *path_copy;
+ VOID *old;
+ size_t path_len;
+ EFI_STATUS efirc;
+ int rc;
+
+ /* Locate or install the handle with this device path */
+ end = path;
+ if ( ( ( efirc = bs->LocateDevicePath ( &efi_device_path_protocol_guid,
+ &end, &handle ) ) == 0 ) &&
+ ( end->Type == END_DEVICE_PATH_TYPE ) ) {
+
+ /* Exact match: reuse (or uninstall from) this handle */
+ if ( load ) {
+ DBGC ( path, "EFIFILE %s reusing existing handle\n",
+ efi_devpath_text ( path ) );
+ }
+
+ } else {
+
+ /* Allocate a permanent copy of the device path, since
+ * this handle will survive after this binary is
+ * unloaded.
+ */
+ path_len = ( efi_path_len ( path ) + sizeof ( *end ) );
+ if ( ( efirc = bs->AllocatePool ( EfiBootServicesData, path_len,
+ &path_copy ) ) != 0 ) {
+ rc = -EEFI ( efirc );
+ DBGC ( path, "EFIFILE %s could not allocate device path: "
+ "%s\n", efi_devpath_text ( path ), strerror ( rc ) );
+ return rc;
+ }
+ memcpy ( path_copy, path, path_len );
+
+ /* Create a new handle with this device path */
+ handle = NULL;
+ if ( ( efirc = bs->InstallMultipleProtocolInterfaces (
+ &handle,
+ &efi_device_path_protocol_guid, path_copy,
+ NULL ) ) != 0 ) {
+ rc = -EEFI ( efirc );
+ DBGC ( path, "EFIFILE %s could not create handle: %s\n",
+ efi_devpath_text ( path ), strerror ( rc ) );
+ return rc;
+ }
+ }
+
+ /* Uninstall existing load file protocol instance, if any */
+ if ( ( ( efirc = bs->HandleProtocol ( handle, &efi_load_file2_protocol_guid,
+ &old ) ) == 0 ) &&
+ ( ( efirc = bs->UninstallMultipleProtocolInterfaces (
+ handle,
+ &efi_load_file2_protocol_guid, old,
+ NULL ) ) != 0 ) ) {
+ rc = -EEFI ( efirc );
+ DBGC ( path, "EFIFILE %s could not uninstall %s: %s\n",
+ efi_devpath_text ( path ),
+ efi_guid_ntoa ( &efi_load_file2_protocol_guid ),
+ strerror ( rc ) );
+ return rc;
+ }
+
+ /* Install new load file protocol instance, if applicable */
+ if ( ( load != NULL ) &&
+ ( ( efirc = bs->InstallMultipleProtocolInterfaces (
+ &handle,
+ &efi_load_file2_protocol_guid, load,
+ NULL ) ) != 0 ) ) {
+ rc = -EEFI ( efirc );
+ DBGC ( path, "EFIFILE %s could not install %s: %s\n",
+ efi_devpath_text ( path ),
+ efi_guid_ntoa ( &efi_load_file2_protocol_guid ),
+ strerror ( rc ) );
+ return rc;
+ }
+
+ return 0;
+}
+
+/**
* Install EFI simple file system protocol
*
* @v handle EFI handle
@@ -903,8 +1082,16 @@ int efi_file_install ( EFI_HANDLE handle ) {
}
assert ( diskio.diskio == &efi_disk_io_protocol );
+ /* Install Linux initrd fixed device path file */
+ if ( ( rc = efi_file_path_install ( &efi_file_initrd_path.vendor.Header,
+ &efi_file_initrd.load ) ) != 0 ) {
+ goto err_initrd;
+ }
+
return 0;
+ efi_file_path_install ( &efi_file_initrd_path.vendor.Header, NULL );
+ err_initrd:
bs->CloseProtocol ( handle, &efi_disk_io_protocol_guid,
efi_image_handle, handle );
err_open:
@@ -930,6 +1117,9 @@ void efi_file_uninstall ( EFI_HANDLE handle ) {
EFI_STATUS efirc;
int rc;
+ /* Uninstall Linux initrd fixed device path file */
+ efi_file_path_install ( &efi_file_initrd_path.vendor.Header, NULL );
+
/* Close our own disk I/O protocol */
bs->CloseProtocol ( handle, &efi_disk_io_protocol_guid,
efi_image_handle, handle );