aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatt Fleming <matt.fleming@intel.com>2013-06-26 07:50:20 +0100
committerMatt Fleming <matt.fleming@intel.com>2013-07-08 15:47:22 +0100
commitf5c665e0acd6953aa3c75864eaefc4b18a6e6694 (patch)
tree4940561dbd62e7f4771ffea2bc0fe5257ac055d2
parent047fcac405fb2bb29d1f7b08eb1f99b8d3e3141c (diff)
downloadsyslinux-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.h9
-rw-r--r--efi/efi.h8
-rw-r--r--efi/i386/linux.S30
-rw-r--r--efi/main.c91
-rw-r--r--efi/x86_64/linux.S18
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 {
diff --git a/efi/efi.h b/efi/efi.h
index 9bb0e20c..3304527b 100644
--- a/efi/efi.h
+++ b/efi/efi.h
@@ -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
diff --git a/efi/main.c b/efi/main.c
index f68f05e1..7f45ecc2 100644
--- a/efi/main.c
+++ b/efi/main.c
@@ -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