Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

stub: Drop kernel EFI handover code #25126

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/boot/efi/boot.c
Original file line number Diff line number Diff line change
Expand Up @@ -2389,8 +2389,8 @@ static EFI_STATUS image_start(
return log_error_status_stall(err, L"Error loading %s: %r", entry->devicetree, err);
}

_cleanup_(cleanup_initrd) EFI_HANDLE initrd_handle = NULL;
err = initrd_register(initrd, initrd_size, &initrd_handle);
_cleanup_(cleanup_initrd) Initrd *initrd_handle = NULL;
err = initrd_register(initrd, initrd_size, NULL, &initrd_handle);
if (err != EFI_SUCCESS)
return log_error_status_stall(err, L"Error registering initrd: %r", err);

Expand Down Expand Up @@ -2418,7 +2418,7 @@ static EFI_STATUS image_start(
if (err == EFI_UNSUPPORTED && entry->type == LOADER_LINUX) {
uint32_t compat_address;

err = pe_kernel_info(loaded_image->ImageBase, &compat_address);
err = pe_kernel_info(loaded_image->ImageBase, &compat_address, NULL);
if (err != EFI_SUCCESS) {
if (err != EFI_UNSUPPORTED)
return log_error_status_stall(err, L"Error finding kernel compat entry address: %r", err);
Expand Down
283 changes: 224 additions & 59 deletions src/boot/efi/initrd.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,37 @@
#include "missing_efi.h"
#include "util.h"

/* extend LoadFileProtocol */
struct initrd_loader {
typedef struct {
EFI_FILE_PROTOCOL efi_file;

/* Our EFI_FILE_PROTOCOL instance extensions. */
uint64_t read_pos;
const void *initrd_addr;
size_t initrd_size;
} InitrdFsFile;

typedef struct {
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL efi_fs;

/* Our EFI_SIMPLE_FILE_SYSTEM_PROTOCOL instance extensions. */
InitrdFsFile initrd;
} InitrdFs;

typedef struct {
EFI_LOAD_FILE_PROTOCOL load_file;

/* Our EFI_LOAD_FILE_PROTOCOL extensions. */
const void *address;
UINTN length;
size_t length;
} InitrdLoader;

struct Initrd {
EFI_HANDLE handle;
const EFI_DEVICE_PATH *dp;
union {
InitrdLoader loader;
InitrdFs fs;
};
};

/* static structure for LINUX_INITRD_MEDIA device path
Expand All @@ -37,104 +63,243 @@ static const struct {
}
};

EFIAPI EFI_STATUS initrd_load_file(
EFI_LOAD_FILE_PROTOCOL *this,
static EFIAPI EFI_STATUS initrd_load_file(
InitrdLoader *this,
EFI_DEVICE_PATH *file_path,
BOOLEAN boot_policy,
UINTN *buffer_size,
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 (this->length == 0 || !this->address)
return EFI_NOT_FOUND;
if (!buffer || *buffer_size < this->length) {
*buffer_size = this->length;
return EFI_BUFFER_TOO_SMALL;
}

memcpy(buffer, this->address, this->length);
*buffer_size = this->length;
return EFI_SUCCESS;
}

#if defined(__i386__) || defined(__x86_64__)
static EFIAPI EFI_STATUS initrd_fs_open(
EFI_FILE *this, EFI_FILE **ret_file, char16_t *file_name, uint64_t open_mode, uint64_t attributes) {

if (loader->length == 0 || !loader->address)
if (!this || !ret_file || !file_name || open_mode != EFI_FILE_MODE_READ || attributes != 0)
return EFI_INVALID_PARAMETER;

if (!streq16(file_name, STUB_INITRD_FILE_NAME))
return EFI_NOT_FOUND;

if (!buffer || *buffer_size < loader->length) {
*buffer_size = loader->length;
/* We keep this simple. Only the kernel will read from our FS and will only ever need to fetch our
* initrd. One file instance is enough for that. */
*ret_file = this;
return EFI_SUCCESS;
}

static EFIAPI EFI_STATUS initrd_fs_close(EFI_FILE *this) {
return EFI_SUCCESS;
}

static EFIAPI EFI_STATUS initrd_fs_delete(EFI_FILE *this) {
return EFI_WARN_DELETE_FAILURE;
}

static EFIAPI EFI_STATUS initrd_fs_read(InitrdFsFile *this, size_t *buffer_size, void *buffer) {
if (!this || !buffer || !buffer_size)
return EFI_INVALID_PARAMETER;

*buffer_size = MIN(*buffer_size, this->initrd_size - this->read_pos);
memcpy(buffer, (uint8_t *) this->initrd_addr + this->read_pos, *buffer_size);

this->read_pos += *buffer_size;
return EFI_SUCCESS;
}

static EFIAPI EFI_STATUS initrd_fs_write(EFI_FILE *this, size_t *buffer_size, void *buffer) {
return EFI_WRITE_PROTECTED;
}

static EFIAPI EFI_STATUS initrd_fs_get_position(EFI_FILE *this, uint64_t *position) {
return EFI_UNSUPPORTED;
}

static EFIAPI EFI_STATUS initrd_fs_set_position(EFI_FILE *this, uint64_t position) {
return EFI_UNSUPPORTED;
}

static EFIAPI EFI_STATUS initrd_fs_get_info(
InitrdFsFile *this, EFI_GUID *information_type, size_t *buffer_size, void *buffer) {

if (!this || !information_type || !buffer_size)
return EFI_INVALID_PARAMETER;

if (memcmp(information_type, &(EFI_GUID) EFI_FILE_INFO_ID, sizeof(EFI_GUID)) != 0)
return EFI_UNSUPPORTED;

size_t name_offset = offsetof(EFI_FILE_INFO, FileName);
size_t info_size = name_offset + sizeof(STUB_INITRD_FILE_NAME);

if (!buffer || *buffer_size < info_size) {
*buffer_size = info_size;
return EFI_BUFFER_TOO_SMALL;
}

memcpy(buffer, loader->address, loader->length);
*buffer_size = loader->length;
EFI_FILE_INFO info = {
.Size = info_size,
.FileSize = this->initrd_size,
};
memcpy(buffer, &info, name_offset);
memcpy((uint8_t *) buffer + name_offset, STUB_INITRD_FILE_NAME, sizeof(STUB_INITRD_FILE_NAME));

return EFI_SUCCESS;
}

EFI_STATUS initrd_register(
static EFIAPI EFI_STATUS initrd_fs_set_info(
EFI_FILE *this, EFI_GUID *information_type, size_t buffer_size, void *buffer) {
return EFI_WRITE_PROTECTED;
}

static EFIAPI EFI_STATUS initrd_fs_flush(EFI_FILE *this) {
return EFI_WRITE_PROTECTED;
}

static EFIAPI EFI_STATUS initrd_fs_volume_open(InitrdFs *this, InitrdFsFile **ret_root) {
if (!this || !ret_root)
return EFI_INVALID_PARAMETER;

*ret_root = &this->initrd;
return EFI_SUCCESS;
}

static EFI_STATUS initrd_register_fs(
const void *initrd_address,
UINTN initrd_length,
EFI_HANDLE *ret_initrd_handle) {
size_t initrd_size,
const EFI_DEVICE_PATH *dp,
Initrd **ret) {

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);
assert(initrd_address);
assert(dp);
assert(ret);

if (!initrd_address || initrd_length == 0)
return EFI_SUCCESS;
/* For older kernels that do not support LINUX_INITRD_MEDIA we can provide the initrd by exposing
* our own EFI filesystem instance and letting the kernel pick it up from a "initrd=" command line
* parameter added by the stub.
*
* To make this work we must register the device path protocol on the device path we pass to
* LoadImage() so that the created EFI_LOADED_IMAGE_PROTOCOL has a DeviceHandle. The kernel will use
* that handle to get the EFI_SIMPLE_FILE_SYSTEM_PROTOCOL created here and look up any initrds passed
* as command line. */

/* 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(&EfiLoadFile2Protocol, &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
_cleanup_free_ Initrd *initrd = xnew(Initrd, 1);
*initrd = (Initrd) {
.dp = dp,
.fs = {
.efi_fs = {
.Revision = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_REVISION,
.OpenVolume = (void *) initrd_fs_volume_open,
},
.initrd = {
.efi_file = {
/* With this revision we do not need to provide *Ex family functions. */
.Revision = EFI_FILE_PROTOCOL_REVISION,
.Open = initrd_fs_open,
.Close = initrd_fs_close,
.Delete = initrd_fs_delete,
.Read = (void *) initrd_fs_read,
.Write = initrd_fs_write,
.GetPosition = initrd_fs_get_position,
.SetPosition = initrd_fs_set_position,
.GetInfo = (void *) initrd_fs_get_info,
.SetInfo = initrd_fs_set_info,
.Flush = initrd_fs_flush,
},
.initrd_addr = initrd_address,
.initrd_size = initrd_size,
},
},
};

/* create a new handle and register the LoadFile2 protocol with the InitrdMediaPath on it */
err = BS->InstallMultipleProtocolInterfaces(
ret_initrd_handle,
&DevicePathProtocol, &efi_initrd_device_path,
&EfiLoadFile2Protocol, loader,
&initrd->handle,
&(EFI_GUID) EFI_DEVICE_PATH_PROTOCOL_GUID, initrd->dp,
&(EFI_GUID) EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID, &initrd->fs.efi_fs,
NULL);
if (err != EFI_SUCCESS)
free(loader);
return err;

return err;
*ret = TAKE_PTR(initrd);
return EFI_SUCCESS;
}
#endif

EFI_STATUS initrd_register(
const void *initrd_address,
size_t initrd_length,
const EFI_DEVICE_PATH *install_fs_onto_dp,
Initrd **ret) {

EFI_STATUS initrd_unregister(EFI_HANDLE initrd_handle) {
EFI_STATUS err;
struct initrd_loader *loader;

if (!initrd_handle)
assert(ret);

if (!initrd_address || initrd_length == 0)
return EFI_SUCCESS;

/* get the LoadFile2 protocol that we allocated earlier */
err = BS->OpenProtocol(
initrd_handle, &EfiLoadFile2Protocol, (void **) &loader,
NULL, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
if (err != EFI_SUCCESS)
return err;
#if defined(__i386__) || defined(__x86_64__)
/* Only x86 kernels can load initrds via command line. */
if (install_fs_onto_dp)
return initrd_register_fs(initrd_address, initrd_length, install_fs_onto_dp, ret);
#endif

/* close the handle */
(void) BS->CloseProtocol(initrd_handle, &EfiLoadFile2Protocol, NULL, NULL);
_cleanup_free_ Initrd *initrd = xnew(Initrd, 1);
*initrd = (Initrd) {
.loader = {
.load_file.LoadFile = (void *) initrd_load_file,
.address = initrd_address,
.length = initrd_length,
},
};

/* uninstall all protocols thus destroying the handle */
err = BS->UninstallMultipleProtocolInterfaces(
initrd_handle,
err = BS->InstallMultipleProtocolInterfaces(
&initrd->handle,
&DevicePathProtocol, &efi_initrd_device_path,
&EfiLoadFile2Protocol, loader,
&EfiLoadFile2Protocol, &initrd->loader,
NULL);
if (err != EFI_SUCCESS)
return err;

initrd_handle = NULL;
free(loader);
*ret = TAKE_PTR(initrd);
return EFI_SUCCESS;
}

EFI_STATUS initrd_unregister(Initrd *initrd) {
EFI_STATUS err;

if (!initrd)
return EFI_SUCCESS;

if (initrd->dp)
err = BS->UninstallMultipleProtocolInterfaces(
initrd->handle,
&(EFI_GUID) EFI_DEVICE_PATH_PROTOCOL_GUID, initrd->dp,
&(EFI_GUID) EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID, &initrd->fs.efi_fs,
NULL);
else
err = BS->UninstallMultipleProtocolInterfaces(
initrd->handle,
&(EFI_GUID) EFI_DEVICE_PATH_PROTOCOL_GUID, &efi_initrd_device_path,
&(EFI_GUID) EFI_LOAD_FILE2_PROTOCOL_GUID, &initrd->loader,
NULL);

free(initrd);
return err;
}
17 changes: 11 additions & 6 deletions src/boot/efi/initrd.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,20 @@
#pragma once

#include <efi.h>
#include <stddef.h>

#define STUB_INITRD_FILE_NAME u"systemd-stub-initrd"

typedef struct Initrd Initrd;

EFI_STATUS initrd_register(
const void *initrd_address,
UINTN initrd_length,
EFI_HANDLE *ret_initrd_handle);
size_t initrd_length,
const EFI_DEVICE_PATH *install_fs_onto_dp,
Initrd **ret);

EFI_STATUS initrd_unregister(EFI_HANDLE initrd_handle);
EFI_STATUS initrd_unregister(Initrd *initrd);

static inline void cleanup_initrd(EFI_HANDLE *initrd_handle) {
(void) initrd_unregister(*initrd_handle);
*initrd_handle = NULL;
static inline void cleanup_initrd(Initrd **initrd) {
(void) initrd_unregister(*initrd);
}