diff options
author | Matt Fleming <matt.fleming@intel.com> | 2013-06-26 07:50:20 +0100 |
---|---|---|
committer | Matt Fleming <matt.fleming@intel.com> | 2013-07-08 15:47:22 +0100 |
commit | f5c665e0acd6953aa3c75864eaefc4b18a6e6694 (patch) | |
tree | 4940561dbd62e7f4771ffea2bc0fe5257ac055d2 | |
parent | 047fcac405fb2bb29d1f7b08eb1f99b8d3e3141c (diff) | |
download | syslinux-f5c665e0acd6953aa3c75864eaefc4b18a6e6694.tar.gz syslinux-f5c665e0acd6953aa3c75864eaefc4b18a6e6694.tar.xz syslinux-f5c665e0acd6953aa3c75864eaefc4b18a6e6694.zip |
efi: implement Linux kernel handover protocol support
The handover protocol is the preferred method of booting kernels on EFI
because it allows workarounds for various firmware bugs to be contained
in one place and applied irrespective of the chosen bootloader. Use it
if available, but ensure that we fallback to the legacy boot method.
Also, update the linux_header structure with recent changes made in the
kernel source.
Signed-off-by: Matt Fleming <matt.fleming@intel.com>
-rw-r--r-- | com32/include/syslinux/linux.h | 9 | ||||
-rw-r--r-- | efi/efi.h | 8 | ||||
-rw-r--r-- | efi/i386/linux.S | 30 | ||||
-rw-r--r-- | efi/main.c | 91 | ||||
-rw-r--r-- | efi/x86_64/linux.S | 18 |
5 files changed, 150 insertions, 6 deletions
diff --git a/com32/include/syslinux/linux.h b/com32/include/syslinux/linux.h index a8c6f2f5..700ac9a8 100644 --- a/com32/include/syslinux/linux.h +++ b/com32/include/syslinux/linux.h @@ -69,6 +69,11 @@ struct setup_data { #define SETUP_E820_EXT 1 #define SETUP_DTB 2 +#define XLF_KERNEL_64 (1 << 0) +#define XLF_CAN_BE_LOADED_ABOVE_4G (1 << 1) +#define XLF_EFI_HANDOVER_32 (1 << 2) +#define XLF_EFI_HANDOVER_64 (1 << 3) + struct linux_header { uint8_t boot_sector_1[0x0020]; uint16_t old_cmd_line_magic; @@ -100,7 +105,8 @@ struct linux_header { uint32_t initrd_addr_max; uint32_t kernel_alignment; uint8_t relocatable_kernel; - uint8_t pad2[3]; + uint8_t min_alignment; + uint16_t xloadflags; uint32_t cmdline_max_len; uint32_t hardware_subarch; uint64_t hardware_subarch_data; @@ -109,6 +115,7 @@ struct linux_header { uint64_t setup_data; uint64_t pref_address; uint32_t init_size; + uint32_t handover_offset; } __packed; struct screen_info { @@ -61,4 +61,12 @@ efi_setup_event(EFI_EVENT *ev, EFI_EVENT_NOTIFY func, void *ctx) return status; } +struct boot_params; +typedef void (handover_func_t)(void *, EFI_SYSTEM_TABLE *, + struct boot_params *, unsigned long); + +handover_func_t efi_handover_32; +handover_func_t efi_handover_64; +handover_func_t efi_handover; + #endif /* _SYSLINUX_EFI_H */ diff --git a/efi/i386/linux.S b/efi/i386/linux.S index 557d3e20..4049ad4d 100644 --- a/efi/i386/linux.S +++ b/efi/i386/linux.S @@ -18,3 +18,33 @@ kernel_jump: movl 0x8(%esp), %esi movl 0x4(%esp), %ecx jmp *%ecx + + /* + * The default handover function should only be invoked for + * bzImage boot protocol versions < 2.12. + */ + .globl efi_handover + .type efi_handover,@function +efi_handover: + cli + popl %ecx /* discard return address */ + movl 0xc(%esp), %ecx + jmp *%ecx + + .globl efi_handover_32 + .type efi_handover_32,@function +efi_handover_32: + cli + popl %ecx /* discard return address */ + movl 0xc(%esp), %ecx + call *%ecx + + .globl efi_handover_64 + .type efi_handover_64,@function +efi_handover_64: + call 1f +1: + popl %eax + subl $1b, %eax + movl $38, errno(%eax) /* ENOSYS */ + ret @@ -709,6 +709,87 @@ void efree(EFI_PHYSICAL_ADDRESS memory, UINTN size) free_pages(memory, nr_pages); } +/* + * Check whether 'buf' contains a PE/COFF header and that the PE/COFF + * file can be executed by this architecture. + */ +static bool valid_pecoff_image(char *buf) +{ + struct pe_header { + uint16_t signature; + uint8_t _pad[0x3a]; + uint32_t offset; + } *pehdr = (struct pe_header *)buf; + struct coff_header { + uint32_t signature; + uint16_t machine; + } *chdr; + + if (pehdr->signature != 0x5a4d) { + dprintf("Invalid MS-DOS header signature\n"); + return false; + } + + if (!pehdr->offset || pehdr->offset > 512) { + dprintf("Invalid PE header offset\n"); + return false; + } + + chdr = (struct coff_header *)&buf[pehdr->offset]; + if (chdr->signature != 0x4550) { + dprintf("Invalid PE header signature\n"); + return false; + } + +#if defined(__x86_64__) + if (chdr->machine != 0x8664) { + dprintf("Invalid PE machine field\n"); + return false; + } +#else + if (chdr->machine != 0x14c) { + dprintf("Invalid PE machine field\n"); + return false; + } +#endif + + return true; +} + +/* + * Boot a Linux kernel using the EFI boot stub handover protocol. + * + * This function will not return to its caller if booting the kernel + * image succeeds. If booting the kernel image fails, a legacy boot + * method should be attempted. + */ +static void handover_boot(struct linux_header *hdr, struct boot_params *bp) +{ + unsigned long address = hdr->code32_start + hdr->handover_offset; + handover_func_t *func = efi_handover; + + dprintf("Booting kernel using handover protocol\n"); + + /* + * Ensure that the kernel is a valid PE32(+) file and that the + * architecture of the file matches this version of Syslinux - we + * can't mix firmware and kernel bitness (e.g. 32-bit kernel on + * 64-bit EFI firmware) using the handover protocol. + */ + if (!valid_pecoff_image((char *)hdr)) + return; + + if (hdr->version >= 0x20c) { + if (hdr->xloadflags & XLF_EFI_HANDOVER_32) + func = efi_handover_32; + + if (hdr->xloadflags & XLF_EFI_HANDOVER_64) + func = efi_handover_64; + } + + func(image_handle, ST, bp, address); +} + /* efi_boot_linux: * Boots the linux kernel using the image and parameters to boot with. * The EFI boot loader is reworked taking the cue from @@ -836,13 +917,13 @@ int efi_boot_linux(void *kernel_buf, size_t kernel_size, kernel_start, kernel_size, initramfs, setup_data, _cmdline); si = &_bp->screen_info; memset(si, 0, sizeof(*si)); + + /* Attempt to use the handover protocol if available */ + if (hdr->version >= 0x20b && hdr->handover_offset) + handover_boot(bhdr, _bp); + setup_screen(si); - /* - * FIXME: implement handover protocol - * Use the kernel's EFI boot stub by invoking the handover - * protocol. - */ /* Allocate gdt consistent with the alignment for architecture */ status = emalloc(gdt.limit, __SIZEOF_POINTER__ , (EFI_PHYSICAL_ADDRESS *)&gdt.base); if (status != EFI_SUCCESS) { diff --git a/efi/x86_64/linux.S b/efi/x86_64/linux.S index 4b1b88be..0a0e9965 100644 --- a/efi/x86_64/linux.S +++ b/efi/x86_64/linux.S @@ -43,3 +43,21 @@ pm_code: /* Far return */ lret + + .code64 + .align 4 + .globl efi_handover_32 + .type efi_handover_32,@function +efi_handover_32: + movl $38, errno(%rip) /* ENOSYS */ + ret + + .globl efi_handover_64 + .globl efi_handover + .type efi_handover_64,@function + .type efi_handover,@function +efi_handover_64: +efi_handover: + add $512, %rcx + cli + jmp *%rcx |