--- /dev/null
+/* 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;
+}
#include <efilib.h>
#include "linux.h"
+#include "initrd.h"
#include "util.h"
#ifdef __i386__
handover(image, ST, params);
}
-EFI_STATUS linux_exec(EFI_HANDLE image,
- CHAR8 *cmdline, UINTN cmdline_len,
- UINTN linux_addr,
- UINTN initrd_addr, UINTN initrd_size) {
+EFI_STATUS linux_exec(
+ EFI_HANDLE image,
+ const CHAR8 *cmdline, UINTN cmdline_len,
+ const VOID *linux_buffer,
+ const VOID *initrd_buffer, UINTN initrd_length) {
const struct boot_params *image_params;
struct boot_params *boot_params;
+ EFI_HANDLE initrd_handle = NULL;
EFI_PHYSICAL_ADDRESS addr;
UINT8 setup_sectors;
EFI_STATUS err;
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 ||
image_params->hdr.header != SETUP_MAGIC ||
boot_params->hdr = image_params->hdr;
boot_params->hdr.type_of_loader = 0xff;
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) {
addr = 0xA0000;
boot_params->hdr.cmd_line_ptr = (UINT32) addr;
}
- boot_params->hdr.ramdisk_image = (UINT32) initrd_addr;
- boot_params->hdr.ramdisk_size = (UINT32) initrd_size;
-
+ /* Providing the initrd via LINUX_INITRD_MEDIA_GUID is only supported by Linux 5.8+ (5.7+ on ARM64).
+ 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);
+ (void) initrd_unregister(initrd_handle);
+ initrd_handle = NULL;
return EFI_LOAD_ERROR;
}