mirror of https://github.com/ipxe/ipxe.git
[efi] Support the initrd autodetection mechanism in newer Linux kernels
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>
pull/895/head
parent
cf9ad00afc
commit
6a004be0cc
|
@ -43,14 +43,21 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
||||||
#include <ipxe/efi/Protocol/SimpleFileSystem.h>
|
#include <ipxe/efi/Protocol/SimpleFileSystem.h>
|
||||||
#include <ipxe/efi/Protocol/BlockIo.h>
|
#include <ipxe/efi/Protocol/BlockIo.h>
|
||||||
#include <ipxe/efi/Protocol/DiskIo.h>
|
#include <ipxe/efi/Protocol/DiskIo.h>
|
||||||
|
#include <ipxe/efi/Protocol/LoadFile2.h>
|
||||||
#include <ipxe/efi/Guid/FileInfo.h>
|
#include <ipxe/efi/Guid/FileInfo.h>
|
||||||
#include <ipxe/efi/Guid/FileSystemInfo.h>
|
#include <ipxe/efi/Guid/FileSystemInfo.h>
|
||||||
#include <ipxe/efi/efi_strings.h>
|
#include <ipxe/efi/efi_strings.h>
|
||||||
|
#include <ipxe/efi/efi_path.h>
|
||||||
#include <ipxe/efi/efi_file.h>
|
#include <ipxe/efi/efi_file.h>
|
||||||
|
|
||||||
/** EFI media ID */
|
/** EFI media ID */
|
||||||
#define EFI_MEDIA_ID_MAGIC 0x69505845
|
#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 */
|
/** An EFI virtual file reader */
|
||||||
struct efi_file_reader {
|
struct efi_file_reader {
|
||||||
/** EFI file */
|
/** EFI file */
|
||||||
|
@ -69,6 +76,8 @@ struct efi_file {
|
||||||
struct refcnt refcnt;
|
struct refcnt refcnt;
|
||||||
/** EFI file protocol */
|
/** EFI file protocol */
|
||||||
EFI_FILE_PROTOCOL file;
|
EFI_FILE_PROTOCOL file;
|
||||||
|
/** EFI load file protocol */
|
||||||
|
EFI_LOAD_FILE2_PROTOCOL load;
|
||||||
/** Image (if any) */
|
/** Image (if any) */
|
||||||
struct image *image;
|
struct image *image;
|
||||||
/** Filename */
|
/** Filename */
|
||||||
|
@ -370,6 +379,8 @@ efi_file_open ( EFI_FILE_PROTOCOL *this, EFI_FILE_PROTOCOL **new,
|
||||||
ref_init ( &file->refcnt, efi_file_free );
|
ref_init ( &file->refcnt, efi_file_free );
|
||||||
memcpy ( &new_file->file, &efi_file_root.file,
|
memcpy ( &new_file->file, &efi_file_root.file,
|
||||||
sizeof ( new_file->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 ) );
|
efi_file_image ( new_file, image_get ( image ) );
|
||||||
*new = &new_file->file;
|
*new = &new_file->file;
|
||||||
DBGC ( new_file, "EFIFILE %s opened\n", efi_file_name ( new_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;
|
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 */
|
/** Root directory */
|
||||||
static struct efi_file efi_file_root = {
|
static struct efi_file efi_file_root = {
|
||||||
.refcnt = REF_INIT ( ref_no_free ),
|
.refcnt = REF_INIT ( ref_no_free ),
|
||||||
|
@ -700,6 +749,9 @@ static struct efi_file efi_file_root = {
|
||||||
.SetInfo = efi_file_set_info,
|
.SetInfo = efi_file_set_info,
|
||||||
.Flush = efi_file_flush,
|
.Flush = efi_file_flush,
|
||||||
},
|
},
|
||||||
|
.load = {
|
||||||
|
.LoadFile = efi_file_load,
|
||||||
|
},
|
||||||
.image = NULL,
|
.image = NULL,
|
||||||
.name = "",
|
.name = "",
|
||||||
};
|
};
|
||||||
|
@ -720,11 +772,34 @@ static struct efi_file efi_file_initrd = {
|
||||||
.SetInfo = efi_file_set_info,
|
.SetInfo = efi_file_set_info,
|
||||||
.Flush = efi_file_flush,
|
.Flush = efi_file_flush,
|
||||||
},
|
},
|
||||||
|
.load = {
|
||||||
|
.LoadFile = efi_file_load,
|
||||||
|
},
|
||||||
.image = NULL,
|
.image = NULL,
|
||||||
.name = "initrd.magic",
|
.name = "initrd.magic",
|
||||||
.read = efi_file_read_initrd,
|
.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
|
* Open root directory
|
||||||
*
|
*
|
||||||
|
@ -833,6 +908,110 @@ static EFI_DISK_IO_PROTOCOL efi_disk_io_protocol = {
|
||||||
.WriteDisk = efi_disk_io_write_disk,
|
.WriteDisk = efi_disk_io_write_disk,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (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
|
* Install EFI simple file system protocol
|
||||||
*
|
*
|
||||||
|
@ -903,8 +1082,16 @@ int efi_file_install ( EFI_HANDLE handle ) {
|
||||||
}
|
}
|
||||||
assert ( diskio.diskio == &efi_disk_io_protocol );
|
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;
|
return 0;
|
||||||
|
|
||||||
|
efi_file_path_install ( &efi_file_initrd_path.vendor.Header, NULL );
|
||||||
|
err_initrd:
|
||||||
bs->CloseProtocol ( handle, &efi_disk_io_protocol_guid,
|
bs->CloseProtocol ( handle, &efi_disk_io_protocol_guid,
|
||||||
efi_image_handle, handle );
|
efi_image_handle, handle );
|
||||||
err_open:
|
err_open:
|
||||||
|
@ -930,6 +1117,9 @@ void efi_file_uninstall ( EFI_HANDLE handle ) {
|
||||||
EFI_STATUS efirc;
|
EFI_STATUS efirc;
|
||||||
int rc;
|
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 */
|
/* Close our own disk I/O protocol */
|
||||||
bs->CloseProtocol ( handle, &efi_disk_io_protocol_guid,
|
bs->CloseProtocol ( handle, &efi_disk_io_protocol_guid,
|
||||||
efi_image_handle, handle );
|
efi_image_handle, handle );
|
||||||
|
|
Loading…
Reference in New Issue