bzimage: Hand off initialisation to the EFI boot stub

Linux kernel v3.6-rc1 support an "EFI handover protocol" that allows
most of the initialisation necessary for booting a kernel under EFI to
be performed by a boot stub in the kernel image itself.

Use the boot stub if it is supported by the kernel.

Signed-off-by: Matt Fleming <matt.fleming@intel.com>
diff --git a/loaders/bzimage/bzimage.c b/loaders/bzimage/bzimage.c
index 3b9c560..efdb884 100644
--- a/loaders/bzimage/bzimage.c
+++ b/loaders/bzimage/bzimage.c
@@ -323,6 +323,15 @@
 	memcpy((char *)boot_params, (char *)buf, 2 * 512);
 	boot_params->hdr.code32_start = (UINT32)((UINT64)kernel_start);
 
+	/*
+	 * Use the kernel's EFI boot stub by invoking the handover
+	 * protocol.
+	 */
+	if (buf->hdr.version >= 0x20b) {
+		handover_jump(image, boot_params, kernel_start);
+		goto out;
+	}
+
 	err = setup_graphics(buf);
 	if (err != EFI_SUCCESS)
 		goto out;
diff --git a/loaders/bzimage/bzimage.h b/loaders/bzimage/bzimage.h
index 9d84725..cd11fd3 100644
--- a/loaders/bzimage/bzimage.h
+++ b/loaders/bzimage/bzimage.h
@@ -78,6 +78,7 @@
 	UINT64 setup_data;
 	UINT64 pref_address;
 	UINT32 init_size;
+	UINT32 handover_offset;
 } __attribute__((packed));
 
 struct efi_info {
diff --git a/loaders/bzimage/i386.h b/loaders/bzimage/i386.h
index 6f1cf8f..593e2af 100644
--- a/loaders/bzimage/i386.h
+++ b/loaders/bzimage/i386.h
@@ -42,4 +42,19 @@
 		      :: "m" (boot_params), "m" (kernel_start));
 }
 
+static inline void handover_jump(EFI_HANDLE image, struct boot_params *bp,
+				 EFI_PHYSICAL_ADDRESS kernel_start)
+{
+	kernel_start += bp->hdr.handover_offset;
+
+	asm volatile ("cli		\n"
+		      "pushl %0         \n"
+		      "pushl %1         \n"
+		      "pushl %2         \n"
+		      "movl %3, %%ecx	\n"
+		      "jmp *%%ecx	\n"
+		      :: "m" (bp), "m" (ST),
+		         "m" (image), "m" (kernel_start));
+}
+
 #endif /* __I386_H__ */
diff --git a/loaders/bzimage/x86_64.h b/loaders/bzimage/x86_64.h
index a7ba3af..b63710e 100644
--- a/loaders/bzimage/x86_64.h
+++ b/loaders/bzimage/x86_64.h
@@ -33,6 +33,7 @@
 #define EFI_LOADER_SIGNATURE	"EL64"
 
 typedef void(*kernel_func)(void *, struct boot_params *);
+typedef void(*handover_func)(void *, EFI_SYSTEM_TABLE *, struct boot_params *);
 
 static inline void kernel_jump(EFI_PHYSICAL_ADDRESS kernel_start,
 			       struct boot_params *boot_params)
@@ -51,4 +52,19 @@
 	kf(NULL, boot_params);
 }
 
+static inline void handover_jump(EFI_HANDLE image, struct boot_params *bp,
+				 EFI_PHYSICAL_ADDRESS kernel_start)
+{
+	UINT32 offset = bp->hdr.handover_offset;
+	handover_func hf;
+
+	asm volatile ("cli");
+
+	/* The 64-bit kernel entry is 512 bytes after the start. */
+	kernel_start += 512;
+
+	hf = (handover_func)(kernel_start + offset);
+	hf(image, ST, bp);
+}
+
 #endif /* __X86_64_H__ */