[syslinux:firmware] efi: implement Linux kernel handover protocol support

syslinux-bot for Matt Fleming matt.fleming at intel.com
Mon Jul 8 09:30:06 PDT 2013


Commit-ID:  f5c665e0acd6953aa3c75864eaefc4b18a6e6694
Gitweb:     http://www.syslinux.org/commit/f5c665e0acd6953aa3c75864eaefc4b18a6e6694
Author:     Matt Fleming <matt.fleming at intel.com>
AuthorDate: Wed, 26 Jun 2013 07:50:20 +0100
Committer:  Matt Fleming <matt.fleming at intel.com>
CommitDate: Mon, 8 Jul 2013 15:47:22 +0100

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 at intel.com>

---
 com32/include/syslinux/linux.h |  9 ++++-
 efi/efi.h                      |  8 ++++
 efi/i386/linux.S               | 30 ++++++++++++++
 efi/main.c                     | 91 +++++++++++++++++++++++++++++++++++++++---
 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 a8c6f2f..700ac9a 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 9bb0e20..3304527 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 557d3e2..4049ad4 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, at function
+efi_handover:
+	cli
+	popl	%ecx		/* discard return address */
+	movl	0xc(%esp), %ecx
+	jmp	*%ecx
+
+	.globl efi_handover_32
+	.type  efi_handover_32, at function
+efi_handover_32:
+	cli
+	popl	%ecx		/* discard return address */
+	movl	0xc(%esp), %ecx
+	call	*%ecx
+
+	.globl efi_handover_64
+	.type  efi_handover_64, at 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 f68f05e..7f45ecc 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 4b1b88b..0a0e996 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, at function
+efi_handover_32:
+	movl	$38, errno(%rip)	/* ENOSYS */
+	ret
+
+	.globl efi_handover_64
+	.globl efi_handover
+	.type  efi_handover_64, at function
+	.type  efi_handover, at function
+efi_handover_64:
+efi_handover:
+	add	$512, %rcx
+	cli
+	jmp	*%rcx


More information about the Syslinux-commits mailing list