mirror of
https://github.com/systemd/systemd-stable.git
synced 2024-12-24 21:34:08 +03:00
Merge pull request #20983 from mxre/feature/aarch64
[sd-stub] Add support for aarch64 booting via pe-entry point
This commit is contained in:
commit
0a51337df1
@ -49,12 +49,6 @@ typedef void (*free_func_t)(void *p);
|
||||
|
||||
#define malloc0(n) (calloc(1, (n) ?: 1))
|
||||
|
||||
#define mfree(memory) \
|
||||
({ \
|
||||
free(memory); \
|
||||
(typeof(memory)) NULL; \
|
||||
})
|
||||
|
||||
#define free_and_replace(a, b) \
|
||||
({ \
|
||||
typeof(a)* _a = &(a); \
|
||||
|
@ -1,109 +1,186 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
/*
|
||||
* Generic Linux boot protocol using the EFI/PE entry point of the kernel. Passes
|
||||
* initrd with the LINUX_INITRD_MEDIA_GUID DevicePath and cmdline with
|
||||
* EFI LoadedImageProtocol.
|
||||
*
|
||||
* This method works for Linux 5.8 and newer on ARM/Aarch64, x86/x68_64 and RISC-V.
|
||||
*/
|
||||
|
||||
#include <efi.h>
|
||||
#include <efilib.h>
|
||||
|
||||
#include "linux.h"
|
||||
#include "initrd.h"
|
||||
#include "linux.h"
|
||||
#include "pe.h"
|
||||
#include "util.h"
|
||||
|
||||
#ifdef __i386__
|
||||
#define __regparm0__ __attribute__((regparm(0)))
|
||||
#else
|
||||
#define __regparm0__
|
||||
#endif
|
||||
static EFI_LOADED_IMAGE * loaded_image_free(EFI_LOADED_IMAGE *img) {
|
||||
if (!img)
|
||||
return NULL;
|
||||
mfree(img->LoadOptions);
|
||||
return mfree(img);
|
||||
}
|
||||
|
||||
typedef VOID(*handover_f)(VOID *image, EFI_SYSTEM_TABLE *table, struct boot_params *params) __regparm0__;
|
||||
static EFI_STATUS loaded_image_register(
|
||||
const CHAR8 *cmdline, UINTN cmdline_len,
|
||||
const VOID *linux_buffer, UINTN linux_length,
|
||||
EFI_HANDLE *ret_image) {
|
||||
|
||||
static VOID linux_efi_handover(EFI_HANDLE image, struct boot_params *params) {
|
||||
handover_f handover;
|
||||
UINTN start = (UINTN)params->hdr.code32_start;
|
||||
EFI_LOADED_IMAGE *loaded_image = NULL;
|
||||
EFI_STATUS err;
|
||||
|
||||
assert(params);
|
||||
assert(cmdline || cmdline_len > 0);
|
||||
assert(linux_buffer && linux_length > 0);
|
||||
assert(ret_image);
|
||||
|
||||
#ifdef __x86_64__
|
||||
asm volatile ("cli");
|
||||
start += 512;
|
||||
#endif
|
||||
handover = (handover_f)(start + params->hdr.handover_offset);
|
||||
handover(image, ST, params);
|
||||
/* create and install new LoadedImage Protocol */
|
||||
loaded_image = AllocatePool(sizeof(EFI_LOADED_IMAGE));
|
||||
if (!loaded_image)
|
||||
return EFI_OUT_OF_RESOURCES;
|
||||
|
||||
/* provide the image base address and size */
|
||||
*loaded_image = (EFI_LOADED_IMAGE) {
|
||||
.ImageBase = (VOID *) linux_buffer,
|
||||
.ImageSize = linux_length
|
||||
};
|
||||
|
||||
/* if a cmdline is set convert it to UTF16 */
|
||||
if (cmdline) {
|
||||
loaded_image->LoadOptions = stra_to_str(cmdline);
|
||||
if (!loaded_image->LoadOptions) {
|
||||
loaded_image = loaded_image_free(loaded_image);
|
||||
return EFI_OUT_OF_RESOURCES;
|
||||
}
|
||||
loaded_image->LoadOptionsSize = StrSize(loaded_image->LoadOptions);
|
||||
}
|
||||
|
||||
/* install a new LoadedImage protocol. ret_handle is a new image handle */
|
||||
err = uefi_call_wrapper(BS->InstallMultipleProtocolInterfaces, 4,
|
||||
ret_image,
|
||||
&LoadedImageProtocol, loaded_image,
|
||||
NULL);
|
||||
if (EFI_ERROR(err))
|
||||
loaded_image = loaded_image_free(loaded_image);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static EFI_STATUS loaded_image_unregister(EFI_HANDLE loaded_image_handle) {
|
||||
EFI_LOADED_IMAGE_PROTOCOL *loaded_image;
|
||||
EFI_STATUS err;
|
||||
|
||||
if (!loaded_image_handle)
|
||||
return EFI_SUCCESS;
|
||||
|
||||
/* get the LoadedImage protocol that we allocated earlier */
|
||||
err = uefi_call_wrapper(
|
||||
BS->OpenProtocol, 6,
|
||||
loaded_image_handle, &LoadedImageProtocol, (VOID **) &loaded_image,
|
||||
NULL, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
|
||||
if (EFI_ERROR(err))
|
||||
return err;
|
||||
|
||||
/* close the handle */
|
||||
(void) uefi_call_wrapper(
|
||||
BS->CloseProtocol, 4,
|
||||
loaded_image_handle, &LoadedImageProtocol, NULL, NULL);
|
||||
err = uefi_call_wrapper(BS->UninstallMultipleProtocolInterfaces, 4,
|
||||
loaded_image_handle,
|
||||
&LoadedImageProtocol, loaded_image,
|
||||
NULL);
|
||||
if (EFI_ERROR(err))
|
||||
return err;
|
||||
loaded_image_handle = NULL;
|
||||
loaded_image = loaded_image_free(loaded_image);
|
||||
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
static inline void cleanup_initrd(EFI_HANDLE *initrd_handle) {
|
||||
(void) initrd_unregister(*initrd_handle);
|
||||
*initrd_handle = NULL;
|
||||
}
|
||||
|
||||
static inline void cleanup_loaded_image(EFI_HANDLE *loaded_image_handle) {
|
||||
(void) loaded_image_unregister(*loaded_image_handle);
|
||||
*loaded_image_handle = NULL;
|
||||
}
|
||||
|
||||
/* struct to call cleanup_pages */
|
||||
struct pages {
|
||||
EFI_PHYSICAL_ADDRESS addr;
|
||||
UINTN num;
|
||||
};
|
||||
|
||||
static inline void cleanup_pages(struct pages *p) {
|
||||
if (p->addr == 0)
|
||||
return;
|
||||
(void) uefi_call_wrapper(BS->FreePages, 2, p->addr, p->num);
|
||||
}
|
||||
|
||||
EFI_STATUS linux_exec(
|
||||
EFI_HANDLE image,
|
||||
const CHAR8 *cmdline, UINTN cmdline_len,
|
||||
const VOID *linux_buffer,
|
||||
const VOID *linux_buffer, UINTN linux_length,
|
||||
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;
|
||||
_cleanup_(cleanup_initrd) EFI_HANDLE initrd_handle = NULL;
|
||||
_cleanup_(cleanup_loaded_image) EFI_HANDLE loaded_image_handle = NULL;
|
||||
UINT32 kernel_alignment, kernel_size_of_image, kernel_entry_address;
|
||||
EFI_IMAGE_ENTRY_POINT kernel_entry;
|
||||
_cleanup_(cleanup_pages) struct pages kernel = {};
|
||||
VOID *new_buffer;
|
||||
EFI_STATUS err;
|
||||
|
||||
assert(image);
|
||||
assert(cmdline || cmdline_len == 0);
|
||||
assert(linux_buffer);
|
||||
assert(linux_buffer && linux_length > 0);
|
||||
assert(initrd_buffer || initrd_length == 0);
|
||||
|
||||
image_params = (const struct boot_params *) linux_buffer;
|
||||
|
||||
if (image_params->hdr.boot_flag != 0xAA55 ||
|
||||
image_params->hdr.header != SETUP_MAGIC ||
|
||||
image_params->hdr.version < 0x20b ||
|
||||
!image_params->hdr.relocatable_kernel)
|
||||
return EFI_LOAD_ERROR;
|
||||
|
||||
addr = UINT32_MAX; /* Below the 32bit boundary */
|
||||
err = uefi_call_wrapper(
|
||||
BS->AllocatePages, 4,
|
||||
AllocateMaxAddress,
|
||||
EfiLoaderData,
|
||||
EFI_SIZE_TO_PAGES(0x4000),
|
||||
&addr);
|
||||
/* get the necessary fields from the PE header */
|
||||
err = pe_alignment_info(linux_buffer, &kernel_entry_address, &kernel_size_of_image, &kernel_alignment);
|
||||
if (EFI_ERROR(err))
|
||||
return err;
|
||||
/* sanity check */
|
||||
assert(kernel_size_of_image >= linux_length);
|
||||
|
||||
boot_params = (struct boot_params *) PHYSICAL_ADDRESS_TO_POINTER(addr);
|
||||
ZeroMem(boot_params, 0x4000);
|
||||
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) POINTER_TO_PHYSICAL_ADDRESS(linux_buffer) + (setup_sectors + 1) * 512;
|
||||
/* Linux kernel complains if it's not loaded at a properly aligned memory address. The correct alignment
|
||||
is provided by Linux as the SegmentAlignment in the PeOptionalHeader. Additionally the kernel needs to
|
||||
be in a memory segment thats SizeOfImage (again from PeOptionalHeader) large, so that the Kernel has
|
||||
space for its BSS section. SizeOfImage is always larger than linux_length, which is only the size of
|
||||
Code, (static) Data and Headers.
|
||||
|
||||
if (cmdline) {
|
||||
addr = 0xA0000;
|
||||
|
||||
err = uefi_call_wrapper(
|
||||
BS->AllocatePages, 4,
|
||||
AllocateMaxAddress,
|
||||
EfiLoaderData,
|
||||
EFI_SIZE_TO_PAGES(cmdline_len + 1),
|
||||
&addr);
|
||||
if (EFI_ERROR(err))
|
||||
return err;
|
||||
|
||||
CopyMem(PHYSICAL_ADDRESS_TO_POINTER(addr), cmdline, cmdline_len);
|
||||
((CHAR8 *) PHYSICAL_ADDRESS_TO_POINTER(addr))[cmdline_len] = 0;
|
||||
boot_params->hdr.cmd_line_ptr = (UINT32) addr;
|
||||
}
|
||||
|
||||
/* 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.
|
||||
Interrestingly only ARM/Aarch64 and RISC-V kernel stubs check these assertions and can even boot (with warnings)
|
||||
if they are not met. x86 and x86_64 kernel stubs don't do checks and fail if the BSS section is too small.
|
||||
*/
|
||||
boot_params->hdr.ramdisk_image = (UINT32) POINTER_TO_PHYSICAL_ADDRESS(initrd_buffer);
|
||||
boot_params->hdr.ramdisk_size = (UINT32) initrd_length;
|
||||
/* allocate SizeOfImage + SectionAlignment because the new_buffer can move up to Alignment-1 bytes */
|
||||
kernel.num = EFI_SIZE_TO_PAGES(ALIGN_TO(kernel_size_of_image, kernel_alignment) + kernel_alignment);
|
||||
err = uefi_call_wrapper(
|
||||
BS->AllocatePages, 4,
|
||||
AllocateAnyPages, EfiLoaderData,
|
||||
kernel.num, &kernel.addr);
|
||||
if (EFI_ERROR(err))
|
||||
return EFI_OUT_OF_RESOURCES;
|
||||
new_buffer = PHYSICAL_ADDRESS_TO_POINTER(ALIGN_TO(kernel.addr, kernel_alignment));
|
||||
CopyMem(new_buffer, linux_buffer, linux_length);
|
||||
/* zero out rest of memory (probably not needed, but BSS section should be 0) */
|
||||
SetMem((UINT8 *)new_buffer + linux_length, kernel_size_of_image - linux_length, 0);
|
||||
|
||||
/* register LINUX_INITRD_MEDIA_GUID */
|
||||
/* get the entry point inside the relocated kernel */
|
||||
kernel_entry = (EFI_IMAGE_ENTRY_POINT) ((const UINT8 *)new_buffer + kernel_entry_address);
|
||||
|
||||
/* register a LoadedImage Protocol in order to pass on the commandline */
|
||||
err = loaded_image_register(cmdline, cmdline_len, new_buffer, linux_length, &loaded_image_handle);
|
||||
if (EFI_ERROR(err))
|
||||
return err;
|
||||
|
||||
/* register a LINUX_INITRD_MEDIA DevicePath to serve the initrd */
|
||||
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;
|
||||
|
||||
/* call the kernel */
|
||||
return uefi_call_wrapper(kernel_entry, 2, loaded_image_handle, ST);
|
||||
}
|
||||
|
@ -2,90 +2,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <efi.h>
|
||||
#include "macro-fundamental.h"
|
||||
|
||||
#define SETUP_MAGIC 0x53726448 /* "HdrS" */
|
||||
|
||||
struct setup_header {
|
||||
UINT8 setup_sects;
|
||||
UINT16 root_flags;
|
||||
UINT32 syssize;
|
||||
UINT16 ram_size;
|
||||
UINT16 vid_mode;
|
||||
UINT16 root_dev;
|
||||
UINT16 boot_flag;
|
||||
UINT16 jump;
|
||||
UINT32 header;
|
||||
UINT16 version;
|
||||
UINT32 realmode_swtch;
|
||||
UINT16 start_sys_seg;
|
||||
UINT16 kernel_version;
|
||||
UINT8 type_of_loader;
|
||||
UINT8 loadflags;
|
||||
UINT16 setup_move_size;
|
||||
UINT32 code32_start;
|
||||
UINT32 ramdisk_image;
|
||||
UINT32 ramdisk_size;
|
||||
UINT32 bootsect_kludge;
|
||||
UINT16 heap_end_ptr;
|
||||
UINT8 ext_loader_ver;
|
||||
UINT8 ext_loader_type;
|
||||
UINT32 cmd_line_ptr;
|
||||
UINT32 initrd_addr_max;
|
||||
UINT32 kernel_alignment;
|
||||
UINT8 relocatable_kernel;
|
||||
UINT8 min_alignment;
|
||||
UINT16 xloadflags;
|
||||
UINT32 cmdline_size;
|
||||
UINT32 hardware_subarch;
|
||||
UINT64 hardware_subarch_data;
|
||||
UINT32 payload_offset;
|
||||
UINT32 payload_length;
|
||||
UINT64 setup_data;
|
||||
UINT64 pref_address;
|
||||
UINT32 init_size;
|
||||
UINT32 handover_offset;
|
||||
} _packed_;
|
||||
|
||||
/* adapted from linux' bootparam.h */
|
||||
struct boot_params {
|
||||
UINT8 screen_info[64]; // was: struct screen_info
|
||||
UINT8 apm_bios_info[20]; // was: struct apm_bios_info
|
||||
UINT8 _pad2[4];
|
||||
UINT64 tboot_addr;
|
||||
UINT8 ist_info[16]; // was: struct ist_info
|
||||
UINT8 _pad3[16];
|
||||
UINT8 hd0_info[16];
|
||||
UINT8 hd1_info[16];
|
||||
UINT8 sys_desc_table[16]; // was: struct sys_desc_table
|
||||
UINT8 olpc_ofw_header[16]; // was: struct olpc_ofw_header
|
||||
UINT32 ext_ramdisk_image;
|
||||
UINT32 ext_ramdisk_size;
|
||||
UINT32 ext_cmd_line_ptr;
|
||||
UINT8 _pad4[116];
|
||||
UINT8 edid_info[128]; // was: struct edid_info
|
||||
UINT8 efi_info[32]; // was: struct efi_info
|
||||
UINT32 alt_mem_k;
|
||||
UINT32 scratch;
|
||||
UINT8 e820_entries;
|
||||
UINT8 eddbuf_entries;
|
||||
UINT8 edd_mbr_sig_buf_entries;
|
||||
UINT8 kbd_status;
|
||||
UINT8 secure_boot;
|
||||
UINT8 _pad5[2];
|
||||
UINT8 sentinel;
|
||||
UINT8 _pad6[1];
|
||||
struct setup_header hdr;
|
||||
UINT8 _pad7[0x290-0x1f1-sizeof(struct setup_header)];
|
||||
UINT32 edd_mbr_sig_buffer[16]; // was: edd_mbr_sig_buffer[EDD_MBR_SIG_MAX]
|
||||
UINT8 e820_table[20*128]; // was: struct boot_e820_entry e820_table[E820_MAX_ENTRIES_ZEROPAGE]
|
||||
UINT8 _pad8[48];
|
||||
UINT8 eddbuf[6*82]; // was: struct edd_info eddbuf[EDDMAXNR]
|
||||
UINT8 _pad9[276];
|
||||
} _packed_;
|
||||
|
||||
EFI_STATUS linux_exec(
|
||||
EFI_HANDLE image,
|
||||
const CHAR8 *cmdline, UINTN cmdline_len,
|
||||
const VOID *linux_buffer,
|
||||
const VOID *linux_buffer, UINTN linux_length,
|
||||
const VOID *initrd_buffer, UINTN initrd_length);
|
||||
|
200
src/boot/efi/linux_x86.c
Normal file
200
src/boot/efi/linux_x86.c
Normal file
@ -0,0 +1,200 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
/*
|
||||
* x86 specific code to for EFI handover boot protocol
|
||||
* Linux kernels version 5.8 and newer support providing the initrd by
|
||||
* LINUX_INITRD_MEDIA_GUID DevicePath. In order to support older kernels too,
|
||||
* this x86 specific linux_exec function passes the initrd by setting the
|
||||
* corresponding fields in the setup_header struct.
|
||||
*
|
||||
* see https://www.kernel.org/doc/html/latest/x86/boot.html
|
||||
*/
|
||||
|
||||
#include <efi.h>
|
||||
#include <efilib.h>
|
||||
|
||||
#include "initrd.h"
|
||||
#include "linux.h"
|
||||
#include "macro-fundamental.h"
|
||||
#include "util.h"
|
||||
|
||||
#define SETUP_MAGIC 0x53726448 /* "HdrS" */
|
||||
|
||||
struct setup_header {
|
||||
UINT8 setup_sects;
|
||||
UINT16 root_flags;
|
||||
UINT32 syssize;
|
||||
UINT16 ram_size;
|
||||
UINT16 vid_mode;
|
||||
UINT16 root_dev;
|
||||
UINT16 boot_flag;
|
||||
UINT16 jump;
|
||||
UINT32 header;
|
||||
UINT16 version;
|
||||
UINT32 realmode_swtch;
|
||||
UINT16 start_sys_seg;
|
||||
UINT16 kernel_version;
|
||||
UINT8 type_of_loader;
|
||||
UINT8 loadflags;
|
||||
UINT16 setup_move_size;
|
||||
UINT32 code32_start;
|
||||
UINT32 ramdisk_image;
|
||||
UINT32 ramdisk_size;
|
||||
UINT32 bootsect_kludge;
|
||||
UINT16 heap_end_ptr;
|
||||
UINT8 ext_loader_ver;
|
||||
UINT8 ext_loader_type;
|
||||
UINT32 cmd_line_ptr;
|
||||
UINT32 initrd_addr_max;
|
||||
UINT32 kernel_alignment;
|
||||
UINT8 relocatable_kernel;
|
||||
UINT8 min_alignment;
|
||||
UINT16 xloadflags;
|
||||
UINT32 cmdline_size;
|
||||
UINT32 hardware_subarch;
|
||||
UINT64 hardware_subarch_data;
|
||||
UINT32 payload_offset;
|
||||
UINT32 payload_length;
|
||||
UINT64 setup_data;
|
||||
UINT64 pref_address;
|
||||
UINT32 init_size;
|
||||
UINT32 handover_offset;
|
||||
} _packed_;
|
||||
|
||||
/* adapted from linux' bootparam.h */
|
||||
struct boot_params {
|
||||
UINT8 screen_info[64]; // was: struct screen_info
|
||||
UINT8 apm_bios_info[20]; // was: struct apm_bios_info
|
||||
UINT8 _pad2[4];
|
||||
UINT64 tboot_addr;
|
||||
UINT8 ist_info[16]; // was: struct ist_info
|
||||
UINT8 _pad3[16];
|
||||
UINT8 hd0_info[16];
|
||||
UINT8 hd1_info[16];
|
||||
UINT8 sys_desc_table[16]; // was: struct sys_desc_table
|
||||
UINT8 olpc_ofw_header[16]; // was: struct olpc_ofw_header
|
||||
UINT32 ext_ramdisk_image;
|
||||
UINT32 ext_ramdisk_size;
|
||||
UINT32 ext_cmd_line_ptr;
|
||||
UINT8 _pad4[116];
|
||||
UINT8 edid_info[128]; // was: struct edid_info
|
||||
UINT8 efi_info[32]; // was: struct efi_info
|
||||
UINT32 alt_mem_k;
|
||||
UINT32 scratch;
|
||||
UINT8 e820_entries;
|
||||
UINT8 eddbuf_entries;
|
||||
UINT8 edd_mbr_sig_buf_entries;
|
||||
UINT8 kbd_status;
|
||||
UINT8 secure_boot;
|
||||
UINT8 _pad5[2];
|
||||
UINT8 sentinel;
|
||||
UINT8 _pad6[1];
|
||||
struct setup_header hdr;
|
||||
UINT8 _pad7[0x290-0x1f1-sizeof(struct setup_header)];
|
||||
UINT32 edd_mbr_sig_buffer[16]; // was: edd_mbr_sig_buffer[EDD_MBR_SIG_MAX]
|
||||
UINT8 e820_table[20*128]; // was: struct boot_e820_entry e820_table[E820_MAX_ENTRIES_ZEROPAGE]
|
||||
UINT8 _pad8[48];
|
||||
UINT8 eddbuf[6*82]; // was: struct edd_info eddbuf[EDDMAXNR]
|
||||
UINT8 _pad9[276];
|
||||
} _packed_;
|
||||
|
||||
#ifdef __i386__
|
||||
#define __regparm0__ __attribute__((regparm(0)))
|
||||
#else
|
||||
#define __regparm0__
|
||||
#endif
|
||||
|
||||
typedef VOID(*handover_f)(VOID *image, EFI_SYSTEM_TABLE *table, struct boot_params *params) __regparm0__;
|
||||
|
||||
static VOID linux_efi_handover(EFI_HANDLE image, struct boot_params *params) {
|
||||
handover_f handover;
|
||||
UINTN start = (UINTN)params->hdr.code32_start;
|
||||
|
||||
assert(params);
|
||||
|
||||
#ifdef __x86_64__
|
||||
asm volatile ("cli");
|
||||
start += 512;
|
||||
#endif
|
||||
handover = (handover_f)(start + params->hdr.handover_offset);
|
||||
handover(image, ST, params);
|
||||
}
|
||||
|
||||
EFI_STATUS linux_exec(
|
||||
EFI_HANDLE image,
|
||||
const CHAR8 *cmdline, UINTN cmdline_len,
|
||||
const VOID *linux_buffer, UINTN linux_length,
|
||||
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 || cmdline_len == 0);
|
||||
assert(linux_buffer);
|
||||
assert(initrd_buffer || initrd_length == 0);
|
||||
|
||||
image_params = (const struct boot_params *) linux_buffer;
|
||||
|
||||
if (image_params->hdr.boot_flag != 0xAA55 ||
|
||||
image_params->hdr.header != SETUP_MAGIC ||
|
||||
image_params->hdr.version < 0x20b ||
|
||||
!image_params->hdr.relocatable_kernel)
|
||||
return EFI_LOAD_ERROR;
|
||||
|
||||
addr = UINT32_MAX; /* Below the 32bit boundary */
|
||||
err = uefi_call_wrapper(
|
||||
BS->AllocatePages, 4,
|
||||
AllocateMaxAddress,
|
||||
EfiLoaderData,
|
||||
EFI_SIZE_TO_PAGES(0x4000),
|
||||
&addr);
|
||||
if (EFI_ERROR(err))
|
||||
return err;
|
||||
|
||||
boot_params = (struct boot_params *) PHYSICAL_ADDRESS_TO_POINTER(addr);
|
||||
ZeroMem(boot_params, 0x4000);
|
||||
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) POINTER_TO_PHYSICAL_ADDRESS(linux_buffer) + (setup_sectors + 1) * 512;
|
||||
|
||||
if (cmdline) {
|
||||
addr = 0xA0000;
|
||||
|
||||
err = uefi_call_wrapper(
|
||||
BS->AllocatePages, 4,
|
||||
AllocateMaxAddress,
|
||||
EfiLoaderData,
|
||||
EFI_SIZE_TO_PAGES(cmdline_len + 1),
|
||||
&addr);
|
||||
if (EFI_ERROR(err))
|
||||
return err;
|
||||
|
||||
CopyMem(PHYSICAL_ADDRESS_TO_POINTER(addr), cmdline, cmdline_len);
|
||||
((CHAR8 *) PHYSICAL_ADDRESS_TO_POINTER(addr))[cmdline_len] = 0;
|
||||
boot_params->hdr.cmd_line_ptr = (UINT32) addr;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
@ -39,7 +39,6 @@ systemd_boot_sources = '''
|
||||
'''.split()
|
||||
|
||||
stub_sources = '''
|
||||
linux.c
|
||||
initrd.c
|
||||
splash.c
|
||||
stub.c
|
||||
@ -209,6 +208,11 @@ if have_gnu_efi
|
||||
'-include', efi_config_h,
|
||||
'-include', version_h,
|
||||
]
|
||||
if ['ia32', 'x86_64'].contains(efi_arch)
|
||||
stub_sources += 'linux_x86.c'
|
||||
else
|
||||
stub_sources += 'linux.c'
|
||||
endif
|
||||
if efi_arch == 'x86_64'
|
||||
compile_args += ['-mno-red-zone',
|
||||
'-mno-sse',
|
||||
|
@ -56,10 +56,46 @@ struct CoffFileHeader {
|
||||
UINT16 Characteristics;
|
||||
} _packed_;
|
||||
|
||||
#define OPTHDR32_MAGIC 0x10B /* PE32 OptionalHeader */
|
||||
#define OPTHDR64_MAGIC 0x20B /* PE32+ OptionalHeader */
|
||||
|
||||
struct PeOptionalHeader {
|
||||
UINT16 Magic;
|
||||
UINT8 LinkerMajor;
|
||||
UINT8 LinkerMinor;
|
||||
UINT32 SizeOfCode;
|
||||
UINT32 SizeOfInitializedData;
|
||||
UINT32 SizeOfUninitializeData;
|
||||
UINT32 AddressOfEntryPoint;
|
||||
UINT32 BaseOfCode;
|
||||
union {
|
||||
struct { /* PE32 */
|
||||
UINT32 BaseOfData;
|
||||
UINT32 ImageBase32;
|
||||
};
|
||||
UINT64 ImageBase64; /* PE32+ */
|
||||
};
|
||||
UINT32 SectionAlignment;
|
||||
UINT32 FileAlignment;
|
||||
UINT16 MajorOperatingSystemVersion;
|
||||
UINT16 MinorOperatingSystemVersion;
|
||||
UINT16 MajorImageVersion;
|
||||
UINT16 MinorImageVersion;
|
||||
UINT16 MajorSubsystemVersion;
|
||||
UINT16 MinorSubsystemVersion;
|
||||
UINT32 Win32VersionValue;
|
||||
UINT32 SizeOfImage;
|
||||
UINT32 SizeOfHeaders;
|
||||
UINT32 CheckSum;
|
||||
UINT16 Subsystem;
|
||||
UINT16 DllCharacteristics;
|
||||
/* fields with different sizes for 32/64 omitted */
|
||||
} _packed_;
|
||||
|
||||
struct PeFileHeader {
|
||||
UINT8 Magic[4];
|
||||
struct CoffFileHeader FileHeader;
|
||||
/* OptionalHeader omitted */
|
||||
struct PeOptionalHeader OptionalHeader;
|
||||
} _packed_;
|
||||
|
||||
struct PeSectionHeader {
|
||||
@ -91,7 +127,7 @@ static inline BOOLEAN verify_pe(const struct PeFileHeader *pe) {
|
||||
static inline UINTN section_table_offset(const struct DosFileHeader *dos, const struct PeFileHeader *pe) {
|
||||
assert(dos);
|
||||
assert(pe);
|
||||
return dos->ExeHeader + sizeof(struct PeFileHeader) + pe->FileHeader.SizeOfOptionalHeader;
|
||||
return dos->ExeHeader + OFFSETOF(struct PeFileHeader, OptionalHeader) + pe->FileHeader.SizeOfOptionalHeader;
|
||||
}
|
||||
|
||||
static VOID locate_sections(
|
||||
@ -122,6 +158,41 @@ static VOID locate_sections(
|
||||
}
|
||||
}
|
||||
|
||||
EFI_STATUS pe_alignment_info(
|
||||
const VOID *base,
|
||||
UINT32 *ret_entry_point_address,
|
||||
UINT32 *ret_size_of_image,
|
||||
UINT32 *ret_section_alignment) {
|
||||
|
||||
const struct DosFileHeader *dos;
|
||||
const struct PeFileHeader *pe;
|
||||
|
||||
assert(base);
|
||||
assert(ret_entry_point_address);
|
||||
assert(ret_size_of_image);
|
||||
assert(ret_section_alignment);
|
||||
|
||||
dos = (const struct DosFileHeader *) base;
|
||||
if (!verify_dos(dos))
|
||||
return EFI_LOAD_ERROR;
|
||||
|
||||
pe = (const struct PeFileHeader*) ((const UINT8 *)base + dos->ExeHeader);
|
||||
if (!verify_pe(pe))
|
||||
return EFI_LOAD_ERROR;
|
||||
|
||||
*ret_entry_point_address = pe->OptionalHeader.AddressOfEntryPoint;
|
||||
|
||||
if (pe->OptionalHeader.Magic == OPTHDR32_MAGIC) {
|
||||
*ret_size_of_image = pe->OptionalHeader.SizeOfImage;
|
||||
*ret_section_alignment = pe->OptionalHeader.SectionAlignment;
|
||||
} else if (pe->OptionalHeader.Magic == OPTHDR64_MAGIC) {
|
||||
*ret_size_of_image = pe->OptionalHeader.SizeOfImage;
|
||||
*ret_section_alignment = pe->OptionalHeader.SectionAlignment;
|
||||
} else
|
||||
return EFI_UNSUPPORTED;
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
EFI_STATUS pe_memory_locate_sections(
|
||||
const CHAR8 *base,
|
||||
const CHAR8 **sections,
|
||||
|
@ -15,3 +15,9 @@ EFI_STATUS pe_file_locate_sections(
|
||||
const CHAR8 **sections,
|
||||
UINTN *offsets,
|
||||
UINTN *sizes);
|
||||
|
||||
EFI_STATUS pe_alignment_info(
|
||||
const VOID *base,
|
||||
UINT32 *ret_entry_point_address,
|
||||
UINT32 *ret_size_of_image,
|
||||
UINT32 *ret_section_alignment);
|
||||
|
@ -152,7 +152,7 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
|
||||
NULL,
|
||||
};
|
||||
|
||||
UINTN cmdline_len = 0, initrd_size, credential_initrd_size = 0, sysext_initrd_size = 0;
|
||||
UINTN cmdline_len = 0, linux_size, initrd_size, credential_initrd_size = 0, sysext_initrd_size = 0;
|
||||
_cleanup_freepool_ VOID *credential_initrd = NULL, *sysext_initrd = NULL;
|
||||
EFI_PHYSICAL_ADDRESS linux_base, initrd_base;
|
||||
EFI_LOADED_IMAGE *loaded_image;
|
||||
@ -222,6 +222,7 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
|
||||
&sysext_initrd,
|
||||
&sysext_initrd_size);
|
||||
|
||||
linux_size = szs[SECTION_LINUX];
|
||||
linux_base = POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[SECTION_LINUX];
|
||||
|
||||
initrd_size = szs[SECTION_INITRD];
|
||||
@ -250,7 +251,7 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
|
||||
}
|
||||
|
||||
err = linux_exec(image, cmdline, cmdline_len,
|
||||
PHYSICAL_ADDRESS_TO_POINTER(linux_base),
|
||||
PHYSICAL_ADDRESS_TO_POINTER(linux_base), linux_size,
|
||||
PHYSICAL_ADDRESS_TO_POINTER(initrd_base), initrd_size);
|
||||
graphics_mode(FALSE);
|
||||
return log_error_status_stall(err, L"Execution of embedded linux image failed: %r", err);
|
||||
|
@ -61,6 +61,7 @@
|
||||
#endif
|
||||
|
||||
#define memcpy(a, b, c) CopyMem((a), (b), (c))
|
||||
#define free(a) FreePool(a)
|
||||
#endif
|
||||
|
||||
#if defined(static_assert)
|
||||
@ -266,3 +267,9 @@
|
||||
* @x: a string literal.
|
||||
*/
|
||||
#define STRLEN(x) (sizeof(""x"") - sizeof(typeof(x[0])))
|
||||
|
||||
#define mfree(memory) \
|
||||
({ \
|
||||
free(memory); \
|
||||
(typeof(memory)) NULL; \
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user