blob: 6d362435c40de0db4da8bd068e53e55a3da7c7c2 [file] [log] [blame]
/* 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"
#include "util.h"
/* extend LoadFileProtocol */
struct initrd_loader {
EFI_LOAD_FILE_PROTOCOL load_file;
const void *address;
size_t 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 }
}
};
static EFIAPI EFI_STATUS initrd_load_file(
EFI_LOAD_FILE_PROTOCOL *this,
EFI_DEVICE_PATH *file_path,
BOOLEAN boot_policy,
size_t *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;
}
memcpy(buffer, loader->address, loader->length);
*buffer_size = loader->length;
return EFI_SUCCESS;
}
EFI_STATUS initrd_register(
const void *initrd_address,
size_t 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 registered.
LocateDevicePath checks for the "closest DevicePath" and returns its handle,
where as InstallMultipleProtocolInterfaces only matches identical DevicePaths.
*/
err = BS->LocateDevicePath(MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL), &dp, &handle);
if (err != EFI_NOT_FOUND) /* InitrdMedia is already registered */
return EFI_ALREADY_STARTED;
loader = xnew(struct initrd_loader, 1);
*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 = BS->InstallMultipleProtocolInterfaces(
ret_initrd_handle, MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL),
&efi_initrd_device_path, MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL),
loader,
NULL);
if (err != EFI_SUCCESS)
free(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 = BS->OpenProtocol(
initrd_handle,
MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL),
(void **) &loader,
NULL,
NULL,
EFI_OPEN_PROTOCOL_GET_PROTOCOL);
if (err != EFI_SUCCESS)
return err;
/* close the handle */
(void) BS->CloseProtocol(initrd_handle, MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL), NULL, NULL);
/* uninstall all protocols thus destroying the handle */
err = BS->UninstallMultipleProtocolInterfaces(
initrd_handle, MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL),
&efi_initrd_device_path, MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL),
loader,
NULL);
if (err != EFI_SUCCESS)
return err;
initrd_handle = NULL;
free(loader);
return EFI_SUCCESS;
}