[syslinux] [PATCH] efi: leaving long mode in kernel_jump routine

Thomas Letan
Tue, 04 Aug 2015 06:55:48 -0000

Syslinux 6.03 (efi64) fails to boot a 32-bit kernel. The way Syslinux
leaves long mode in kernel_jump assembly routine does not follow AMD64
specifications. More precisely:
1. After setting a new GADT, `cs` has to be refresh by doing a long
jump, but it is not
2. Other segments have to be updated, but they are not
3. Disabling paging has to be done before disabling long mode, but the
implementation does the opposite

In most cases, a computer that tries to execute the kernel_jump routine
reboot (it can also hangs). This patch fixes the kernel_jump routine.

Signed-off-by: Thomas Letan <thomas.letan at ssi.gouv.fr>
---
 efi/x86_64/linux.S | 61
++++++++++++++++++++++++++++++++++++------------------
 1 file changed, 41 insertions(+), 20 deletions(-)

diff --git a/efi/x86_64/linux.S b/efi/x86_64/linux.S
index 0a0e996..972c0b2 100644
--- a/efi/x86_64/linux.S
+++ b/efi/x86_64/linux.S
@@ -10,8 +10,9 @@
  *
  *
----------------------------------------------------------------------- */
 -#define CR0_PG_FLAG	0x80000000
-#define MSR_EFER	0xc0000080
+#define CR0_PG_BIT    31
+#define CR4_PAE_BIT   5
+#define MSR_EFER      0xc0000080
  	.globl kernel_jump
 	.type kernel_jump, at function
@@ -19,30 +20,50 @@
 kernel_jump:
 	cli
 -	/*
-	 * Setup our segment selector (0x10) and return address (%rdi)
-	 * on the stack in preparation for the far return below.
-	 */
-	mov	$0x1000000000, %rcx
-	addq	%rcx, %rdi
-	pushq	%rdi
+	/* save the content of rsi (boot_param argument of kernel_jump
function) */
+	mov %rsi, %rbx
+
+	call base_address
+base_address:
+	pop %rsi
+
+	/* need to perform a long jump to update cs
+
+	/* load absolute address of pm_code in jmp_address location */
+	lea (pm_code - base_address)(%rsi, 1), %rax
+	mov %eax, (jmp_address - base_address)(%rsi, 1)
+
+	ljmp *(jmp_address - base_address)(%rsi, 1)
+
+jmp_address:
+	.long 0    /* address */
+	.word 0x10 /* segment */
  	.code32
 pm_code:
 -	/* Disable IA-32e mode by clearing IA32_EFER.LME */
-	xorl	%eax, %eax
-	xorl	%edx, %edx
-	movl	$MSR_EFER, %ecx
-	wrmsr
+	/* cs segment has been updated, now update the rest */
+	mov $0x18, %eax
+	mov %eax, %ds
+	mov %eax, %es
+	mov %eax, %fs
+	mov %eax, %gs
+	mov %eax, %ss
 -	/* Turn off paging to disable long mode */
-	movl	%cr0, %eax
-	andl	$~CR0_PG_FLAG, %eax
-	movl	%eax, %cr0
+	/* disable paging. */
+	mov %cr0, %eax
+	btr $CR0_PG_BIT, %eax /* PG in CR0 */
+	mov %eax, %cr0
+
+	/* disable long mode. */
+	mov $MSR_EFER, %ecx
+	rdmsr
+	btr $8, %eax
+	wrmsr
 -	/* Far return */
-	lret
+	/* kernel jump */
+	mov %ebx, %esi
+	jmp *%edi
  	.code64
 	.align 4
--
2.5.0