mirror of
https://github.com/systemd/systemd.git
synced 2024-12-22 17:35:35 +03:00
boot: Support booting in EFI mixed mode
The kernel provides a ".compat" PE section that contains a list of compat entry points with their respective arches. This entry point does all the heavy lifting to support running 64bit kernels when the UEFI firmware is 32bit. Note that the EFI handover protocol code in linux_x86.c does not need any adjustments as it already correctly calls the 32bit handover code. Fixes: #17056
This commit is contained in:
parent
46ce6cf774
commit
c43a282c29
@ -2417,14 +2417,13 @@ static EFI_STATUS image_start(
|
||||
if (EFI_ERROR(err))
|
||||
return log_error_status_stall(err, L"Error registering initrd: %r", err);
|
||||
|
||||
EFI_LOADED_IMAGE *loaded_image;
|
||||
err = BS->HandleProtocol(image, &LoadedImageProtocol, (void **) &loaded_image);
|
||||
if (EFI_ERROR(err))
|
||||
return log_error_status_stall(err, L"Error getting LoadedImageProtocol handle: %r", err);
|
||||
|
||||
CHAR16 *options = options_initrd ?: entry->options;
|
||||
if (options) {
|
||||
EFI_LOADED_IMAGE *loaded_image;
|
||||
|
||||
err = BS->OpenProtocol(image, &LoadedImageProtocol, (void **)&loaded_image,
|
||||
parent_image, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
|
||||
if (EFI_ERROR(err))
|
||||
return log_error_status_stall(err, L"Error getting LoadedImageProtocol handle: %r", err);
|
||||
loaded_image->LoadOptions = options;
|
||||
loaded_image->LoadOptionsSize = StrSize(options);
|
||||
|
||||
@ -2435,10 +2434,29 @@ static EFI_STATUS image_start(
|
||||
efivar_set_time_usec(LOADER_GUID, L"LoaderTimeExecUSec", 0);
|
||||
err = BS->StartImage(image, NULL, NULL);
|
||||
graphics_mode(FALSE);
|
||||
if (err != EFI_SUCCESS)
|
||||
return log_error_status_stall(err, L"Failed to execute %s (%s): %r", entry->title_show, entry->loader, err);
|
||||
if (err == EFI_SUCCESS)
|
||||
return EFI_SUCCESS;
|
||||
|
||||
return EFI_SUCCESS;
|
||||
/* Try calling the kernel compat entry point if one exists. */
|
||||
if (err == EFI_UNSUPPORTED && entry->type == LOADER_LINUX) {
|
||||
UINT32 kernel_entry_address;
|
||||
|
||||
err = pe_alignment_info(loaded_image->ImageBase, &kernel_entry_address, NULL, NULL);
|
||||
if (EFI_ERROR(err)) {
|
||||
if (err != EFI_UNSUPPORTED)
|
||||
return log_error_status_stall(err, L"Error finding kernel compat entry address: %r", err);
|
||||
} else {
|
||||
EFI_IMAGE_ENTRY_POINT kernel_entry =
|
||||
(EFI_IMAGE_ENTRY_POINT) ((UINT8 *) loaded_image->ImageBase + kernel_entry_address);
|
||||
|
||||
err = kernel_entry(image, ST);
|
||||
graphics_mode(FALSE);
|
||||
if (err == EFI_SUCCESS)
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
return log_error_status_stall(err, L"Failed to execute %s (%s): %r", entry->title_show, entry->loader, err);
|
||||
}
|
||||
|
||||
static void config_free(Config *config) {
|
||||
|
@ -12,17 +12,22 @@
|
||||
#define MAX_SECTIONS 96
|
||||
|
||||
#if defined(__i386__)
|
||||
#define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_IA32
|
||||
# define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_IA32
|
||||
# define TARGET_MACHINE_TYPE_COMPATIBILITY EFI_IMAGE_MACHINE_X64
|
||||
#elif defined(__x86_64__)
|
||||
#define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_X64
|
||||
# define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_X64
|
||||
#elif defined(__aarch64__)
|
||||
#define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_AARCH64
|
||||
# define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_AARCH64
|
||||
#elif defined(__arm__)
|
||||
#define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_ARMTHUMB_MIXED
|
||||
# define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_ARMTHUMB_MIXED
|
||||
#elif defined(__riscv) && __riscv_xlen == 64
|
||||
#define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_RISCV64
|
||||
# define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_RISCV64
|
||||
#else
|
||||
#error Unknown EFI arch
|
||||
# error Unknown EFI arch
|
||||
#endif
|
||||
|
||||
#ifndef TARGET_MACHINE_TYPE_COMPATIBILITY
|
||||
# define TARGET_MACHINE_TYPE_COMPATIBILITY 0
|
||||
#endif
|
||||
|
||||
struct DosFileHeader {
|
||||
@ -117,10 +122,11 @@ static inline BOOLEAN verify_dos(const struct DosFileHeader *dos) {
|
||||
return CompareMem(dos->Magic, DOS_FILE_MAGIC, STRLEN(DOS_FILE_MAGIC)) == 0;
|
||||
}
|
||||
|
||||
static inline BOOLEAN verify_pe(const struct PeFileHeader *pe) {
|
||||
static inline BOOLEAN verify_pe(const struct PeFileHeader *pe, BOOLEAN allow_compatibility) {
|
||||
assert(pe);
|
||||
return CompareMem(pe->Magic, PE_FILE_MAGIC, STRLEN(PE_FILE_MAGIC)) == 0 &&
|
||||
pe->FileHeader.Machine == TARGET_MACHINE_TYPE &&
|
||||
(pe->FileHeader.Machine == TARGET_MACHINE_TYPE ||
|
||||
(allow_compatibility && pe->FileHeader.Machine == TARGET_MACHINE_TYPE_COMPATIBILITY)) &&
|
||||
pe->FileHeader.NumberOfSections > 0 &&
|
||||
pe->FileHeader.NumberOfSections <= MAX_SECTIONS &&
|
||||
IN_SET(pe->OptionalHeader.Magic, OPTHDR32_MAGIC, OPTHDR64_MAGIC);
|
||||
@ -160,6 +166,49 @@ static void locate_sections(
|
||||
}
|
||||
}
|
||||
|
||||
static UINT32 get_compatibility_entry_address(const struct DosFileHeader *dos, const struct PeFileHeader *pe) {
|
||||
UINTN addr = 0, size = 0;
|
||||
static const CHAR8 *sections[] = { (CHAR8 *) ".compat", NULL };
|
||||
|
||||
/* The kernel may provide alternative PE entry points for different PE architectures. This allows
|
||||
* booting a 64bit kernel on 32bit EFI that is otherwise running on a 64bit CPU. The locations of any
|
||||
* such compat entry points are located in a special PE section. */
|
||||
|
||||
locate_sections((const struct PeSectionHeader *) ((const UINT8 *) dos + section_table_offset(dos, pe)),
|
||||
pe->FileHeader.NumberOfSections,
|
||||
sections,
|
||||
&addr,
|
||||
NULL,
|
||||
&size);
|
||||
|
||||
if (size == 0)
|
||||
return 0;
|
||||
|
||||
typedef struct {
|
||||
UINT8 type;
|
||||
UINT8 size;
|
||||
UINT16 machine_type;
|
||||
UINT32 entry_point;
|
||||
} _packed_ LinuxPeCompat1;
|
||||
|
||||
while (size >= sizeof(LinuxPeCompat1) && addr % __alignof__(LinuxPeCompat1) == 0) {
|
||||
LinuxPeCompat1 *compat = (LinuxPeCompat1 *) ((UINT8 *) dos + addr);
|
||||
|
||||
if (compat->type == 0 || compat->size == 0 || compat->size > size)
|
||||
break;
|
||||
|
||||
if (compat->type == 1 &&
|
||||
compat->size >= sizeof(LinuxPeCompat1) &&
|
||||
compat->machine_type == TARGET_MACHINE_TYPE)
|
||||
return compat->entry_point;
|
||||
|
||||
addr += compat->size;
|
||||
size -= compat->size;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
EFI_STATUS pe_alignment_info(
|
||||
const void *base,
|
||||
UINT32 *ret_entry_point_address,
|
||||
@ -171,20 +220,30 @@ EFI_STATUS pe_alignment_info(
|
||||
|
||||
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))
|
||||
if (!verify_pe(pe, /* allow_compatibility= */ TRUE))
|
||||
return EFI_LOAD_ERROR;
|
||||
|
||||
*ret_entry_point_address = pe->OptionalHeader.AddressOfEntryPoint;
|
||||
*ret_size_of_image = pe->OptionalHeader.SizeOfImage;
|
||||
*ret_section_alignment = pe->OptionalHeader.SectionAlignment;
|
||||
UINT32 entry_address = pe->OptionalHeader.AddressOfEntryPoint;
|
||||
|
||||
/* Look for a compat entry point. */
|
||||
if (pe->FileHeader.Machine != TARGET_MACHINE_TYPE) {
|
||||
entry_address = get_compatibility_entry_address(dos, pe);
|
||||
if (entry_address == 0)
|
||||
/* Image type not supported and no compat entry found. */
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
*ret_entry_point_address = entry_address;
|
||||
if (ret_size_of_image)
|
||||
*ret_size_of_image = pe->OptionalHeader.SizeOfImage;
|
||||
if (ret_section_alignment)
|
||||
*ret_section_alignment = pe->OptionalHeader.SectionAlignment;
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
@ -207,7 +266,7 @@ EFI_STATUS pe_memory_locate_sections(
|
||||
return EFI_LOAD_ERROR;
|
||||
|
||||
pe = (const struct PeFileHeader*)&base[dos->ExeHeader];
|
||||
if (!verify_pe(pe))
|
||||
if (!verify_pe(pe, /* allow_compatibility= */ FALSE))
|
||||
return EFI_LOAD_ERROR;
|
||||
|
||||
offset = section_table_offset(dos, pe);
|
||||
@ -255,7 +314,7 @@ EFI_STATUS pe_file_locate_sections(
|
||||
err = handle->Read(handle, &len, &pe);
|
||||
if (EFI_ERROR(err))
|
||||
return err;
|
||||
if (len != sizeof(pe) || !verify_pe(&pe))
|
||||
if (len != sizeof(pe) || !verify_pe(&pe, /* allow_compatibility= */ FALSE))
|
||||
return EFI_LOAD_ERROR;
|
||||
|
||||
section_table_len = pe.FileHeader.NumberOfSections * sizeof(struct PeSectionHeader);
|
||||
|
Loading…
Reference in New Issue
Block a user