sd-stub: Provide initrd with LINUX_EFI_INITRD_MEDIA_GUID

Register a LINUX_EFI_INITRD_MEDIA_GUID DevicePath with a LoadFile2Protocol interface and serve the initrd to a supported Linux kernel (Version 5.8+)
Leave the x86 code for older kernels in place until supported kernels become more mainstream
This commit is contained in:
Max Resch 2021-09-30 18:43:52 +02:00 committed by Zbigniew Jędrzejewski-Szmek
parent b1967fb83a
commit a6089431d5
7 changed files with 202 additions and 14 deletions

147
src/boot/efi/initrd.c Normal file
View File

@ -0,0 +1,147 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <efi.h>
#include <efilib.h>
#include "initrd.h"
#include "macro-fundamental.h"
#include "missing_efi.h"
/* extend LoadFileProtocol */
struct initrd_loader {
EFI_LOAD_FILE_PROTOCOL load_file;
const VOID *address;
UINTN length;
};
/* static structure for LINUX_INITRD_MEDIA device path
see https://github.com/torvalds/linux/blob/v5.13/drivers/firmware/efi/libstub/efi-stub-helper.c
*/
static const struct {
VENDOR_DEVICE_PATH vendor;
EFI_DEVICE_PATH end;
} _packed_ efi_initrd_device_path = {
.vendor = {
.Header = {
.Type = MEDIA_DEVICE_PATH,
.SubType = MEDIA_VENDOR_DP,
.Length = { sizeof(efi_initrd_device_path.vendor), 0 }
},
.Guid = LINUX_INITRD_MEDIA_GUID
},
.end = {
.Type = END_DEVICE_PATH_TYPE,
.SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE,
.Length = { sizeof(efi_initrd_device_path.end), 0 }
}
};
EFIAPI EFI_STATUS initrd_load_file(
EFI_LOAD_FILE_PROTOCOL *this,
EFI_DEVICE_PATH *file_path,
BOOLEAN boot_policy,
UINTN *buffer_size,
VOID *buffer) {
struct initrd_loader *loader;
if (!this || !buffer_size || !file_path)
return EFI_INVALID_PARAMETER;
if (boot_policy)
return EFI_UNSUPPORTED;
loader = (struct initrd_loader *) this;
if (loader->length == 0 || !loader->address)
return EFI_NOT_FOUND;
if (!buffer || *buffer_size < loader->length) {
*buffer_size = loader->length;
return EFI_BUFFER_TOO_SMALL;
}
CopyMem(buffer, loader->address, loader->length);
*buffer_size = loader->length;
return EFI_SUCCESS;
}
EFI_STATUS initrd_register(
const VOID *initrd_address,
UINTN initrd_length,
EFI_HANDLE *ret_initrd_handle) {
EFI_STATUS err;
EFI_DEVICE_PATH *dp = (EFI_DEVICE_PATH *) &efi_initrd_device_path;
EFI_HANDLE handle;
struct initrd_loader *loader;
assert(ret_initrd_handle);
if (!initrd_address || initrd_length == 0)
return EFI_SUCCESS;
/* check if a LINUX_INITRD_MEDIA_GUID DevicePath is already registed.
LocateDevicePath checks for the "closest DevicePath" and returns its handle,
where as InstallMultipleProtocolInterfaces only maches identical DevicePaths.
*/
err = uefi_call_wrapper(BS->LocateDevicePath, 3, &EfiLoadFile2Protocol, &dp, &handle);
if (err != EFI_NOT_FOUND) /* InitrdMedia is already registered */
return EFI_ALREADY_STARTED;
loader = AllocatePool(sizeof(struct initrd_loader));
if (!loader)
return EFI_OUT_OF_RESOURCES;
*loader = (struct initrd_loader) {
.load_file.LoadFile = initrd_load_file,
.address = initrd_address,
.length = initrd_length
};
/* create a new handle and register the LoadFile2 protocol with the InitrdMediaPath on it */
err = uefi_call_wrapper(
BS->InstallMultipleProtocolInterfaces, 8,
ret_initrd_handle,
&DevicePathProtocol, &efi_initrd_device_path,
&EfiLoadFile2Protocol, loader,
NULL);
if (EFI_ERROR(err))
FreePool(loader);
return err;
}
EFI_STATUS initrd_unregister(EFI_HANDLE initrd_handle) {
EFI_STATUS err;
struct initrd_loader *loader;
if (!initrd_handle)
return EFI_SUCCESS;
/* get the LoadFile2 protocol that we allocated earlier */
err = uefi_call_wrapper(
BS->OpenProtocol, 6,
initrd_handle, &EfiLoadFile2Protocol, (VOID **) &loader,
NULL, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
if (EFI_ERROR(err))
return err;
/* close the handle */
(void) uefi_call_wrapper(
BS->CloseProtocol, 4,
initrd_handle, &EfiLoadFile2Protocol, NULL, NULL);
/* uninstall all protocols thus destroying the handle */
err = uefi_call_wrapper(
BS->UninstallMultipleProtocolInterfaces, 6,
initrd_handle,
&DevicePathProtocol, &efi_initrd_device_path,
&EfiLoadFile2Protocol, loader,
NULL);
if (EFI_ERROR(err))
return err;
initrd_handle = NULL;
FreePool(loader);
return EFI_SUCCESS;
}

11
src/boot/efi/initrd.h Normal file
View File

@ -0,0 +1,11 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <efi.h>
EFI_STATUS initrd_register(
const VOID *initrd_address,
UINTN initrd_length,
EFI_HANDLE *ret_initrd_handle);
EFI_STATUS initrd_unregister(EFI_HANDLE initrd_handle);

View File

@ -4,6 +4,7 @@
#include <efilib.h> #include <efilib.h>
#include "linux.h" #include "linux.h"
#include "initrd.h"
#include "util.h" #include "util.h"
#ifdef __i386__ #ifdef __i386__
@ -28,21 +29,25 @@ static VOID linux_efi_handover(EFI_HANDLE image, struct boot_params *params) {
handover(image, ST, params); handover(image, ST, params);
} }
EFI_STATUS linux_exec(EFI_HANDLE image, EFI_STATUS linux_exec(
CHAR8 *cmdline, UINTN cmdline_len, EFI_HANDLE image,
UINTN linux_addr, const CHAR8 *cmdline, UINTN cmdline_len,
UINTN initrd_addr, UINTN initrd_size) { const VOID *linux_buffer,
const VOID *initrd_buffer, UINTN initrd_length) {
const struct boot_params *image_params; const struct boot_params *image_params;
struct boot_params *boot_params; struct boot_params *boot_params;
EFI_HANDLE initrd_handle = NULL;
EFI_PHYSICAL_ADDRESS addr; EFI_PHYSICAL_ADDRESS addr;
UINT8 setup_sectors; UINT8 setup_sectors;
EFI_STATUS err; EFI_STATUS err;
assert(image); assert(image);
assert(cmdline); assert(cmdline || cmdline_len == 0);
assert(linux_buffer);
assert(initrd_buffer || initrd_length == 0);
image_params = (const struct boot_params *) linux_addr; image_params = (const struct boot_params *) linux_buffer;
if (image_params->hdr.boot_flag != 0xAA55 || if (image_params->hdr.boot_flag != 0xAA55 ||
image_params->hdr.header != SETUP_MAGIC || image_params->hdr.header != SETUP_MAGIC ||
@ -65,7 +70,7 @@ EFI_STATUS linux_exec(EFI_HANDLE image,
boot_params->hdr = image_params->hdr; boot_params->hdr = image_params->hdr;
boot_params->hdr.type_of_loader = 0xff; boot_params->hdr.type_of_loader = 0xff;
setup_sectors = image_params->hdr.setup_sects > 0 ? image_params->hdr.setup_sects : 4; setup_sectors = image_params->hdr.setup_sects > 0 ? image_params->hdr.setup_sects : 4;
boot_params->hdr.code32_start = (UINT32)linux_addr + (setup_sectors + 1) * 512; boot_params->hdr.code32_start = (UINT32) POINTER_TO_PHYSICAL_ADDRESS(linux_buffer) + (setup_sectors + 1) * 512;
if (cmdline) { if (cmdline) {
addr = 0xA0000; addr = 0xA0000;
@ -84,9 +89,21 @@ EFI_STATUS linux_exec(EFI_HANDLE image,
boot_params->hdr.cmd_line_ptr = (UINT32) addr; boot_params->hdr.cmd_line_ptr = (UINT32) addr;
} }
boot_params->hdr.ramdisk_image = (UINT32) initrd_addr; /* Providing the initrd via LINUX_INITRD_MEDIA_GUID is only supported by Linux 5.8+ (5.7+ on ARM64).
boot_params->hdr.ramdisk_size = (UINT32) initrd_size; Until supported kernels become more established, we continue to set ramdisk in the handover struct.
This value is overridden by kernels that support LINUX_INITRD_MEDIA_GUID.
If you need to know which protocol was used by the kernel, pass "efi=debug" to the kernel,
this will print a line when InitrdMediaGuid was successfully used to load the initrd.
*/
boot_params->hdr.ramdisk_image = (UINT32) POINTER_TO_PHYSICAL_ADDRESS(initrd_buffer);
boot_params->hdr.ramdisk_size = (UINT32) initrd_length;
/* register LINUX_INITRD_MEDIA_GUID */
err = initrd_register(initrd_buffer, initrd_length, &initrd_handle);
if (EFI_ERROR(err))
return err;
linux_efi_handover(image, boot_params); linux_efi_handover(image, boot_params);
(void) initrd_unregister(initrd_handle);
initrd_handle = NULL;
return EFI_LOAD_ERROR; return EFI_LOAD_ERROR;
} }

View File

@ -84,7 +84,8 @@ struct boot_params {
UINT8 _pad9[276]; UINT8 _pad9[276];
} _packed_; } _packed_;
EFI_STATUS linux_exec(EFI_HANDLE image, EFI_STATUS linux_exec(
CHAR8 *cmdline, UINTN cmdline_size, EFI_HANDLE image,
UINTN linux_addr, const CHAR8 *cmdline, UINTN cmdline_len,
UINTN initrd_addr, UINTN initrd_size); const VOID *linux_buffer,
const VOID *initrd_buffer, UINTN initrd_length);

View File

@ -38,6 +38,7 @@ systemd_boot_sources = '''
stub_sources = ''' stub_sources = '''
linux.c linux.c
initrd.c
splash.c splash.c
stub.c stub.c
cpio.c cpio.c

View File

@ -331,3 +331,12 @@ typedef struct tdEFI_TCG2_PROTOCOL {
} EFI_TCG2; } EFI_TCG2;
#endif #endif
#ifndef EFI_LOAD_FILE2_PROTOCOL_GUID
#define EFI_LOAD_FILE2_PROTOCOL_GUID \
{0x4006c0c1, 0xfcb3, 0x403e, {0x99, 0x6d, 0x4a, 0x6c, 0x87, 0x24, 0xe0, 0x6d} }
#define EfiLoadFile2Protocol ((EFI_GUID)EFI_LOAD_FILE2_PROTOCOL_GUID)
#endif
#define LINUX_INITRD_MEDIA_GUID \
{0x5568e427, 0x68fc, 0x4f3d, {0xac, 0x74, 0xca, 0x55, 0x52, 0x31, 0xcc, 0x68} }

View File

@ -249,7 +249,9 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
} }
} }
err = linux_exec(image, cmdline, cmdline_len, linux_base, initrd_base, initrd_size); err = linux_exec(image, cmdline, cmdline_len,
PHYSICAL_ADDRESS_TO_POINTER(linux_base),
PHYSICAL_ADDRESS_TO_POINTER(initrd_base), initrd_size);
graphics_mode(FALSE); graphics_mode(FALSE);
return log_error_status_stall(err, L"Execution of embedded linux image failed: %r", err); return log_error_status_stall(err, L"Execution of embedded linux image failed: %r", err);
} }