Skip to content

Commit 194a974

Browse files
kirylIngo Molnar
authored andcommitted
x86/boot/compressed/64: Handle 5-level paging boot if kernel is above 4G
This patch addresses a shortcoming in current boot process on machines that supports 5-level paging. If a bootloader enables 64-bit mode with 4-level paging, we might need to switch over to 5-level paging. The switching requires the disabling paging. It works fine if kernel itself is loaded below 4G. But if the bootloader put the kernel above 4G (not sure if anybody does this), we would lose control as soon as paging is disabled, because the code becomes unreachable to the CPU. This patch implements a trampoline in lower memory to handle this situation. We only need the memory for a very short time, until the main kernel image sets up own page tables. We go through the trampoline even if we don't have to: if we're already in 5-level paging mode or if we don't need to switch to it. This way the trampoline gets tested on every boot. Signed-off-by: Kirill A. Shutemov <[email protected]> Cc: Andy Lutomirski <[email protected]> Cc: Andy Lutomirski <[email protected]> Cc: Borislav Petkov <[email protected]> Cc: Borislav Petkov <[email protected]> Cc: Brian Gerst <[email protected]> Cc: Cyrill Gorcunov <[email protected]> Cc: Denys Vlasenko <[email protected]> Cc: H. Peter Anvin <[email protected]> Cc: Josh Poimboeuf <[email protected]> Cc: Linus Torvalds <[email protected]> Cc: Matthew Wilcox <[email protected]> Cc: Peter Zijlstra <[email protected]> Cc: Thomas Gleixner <[email protected]> Cc: [email protected] Link: http://lkml.kernel.org/r/[email protected] Signed-off-by: Ingo Molnar <[email protected]>
1 parent 0a1756b commit 194a974

File tree

1 file changed

+53
-16
lines changed

1 file changed

+53
-16
lines changed

arch/x86/boot/compressed/head_64.S

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -307,11 +307,27 @@ ENTRY(startup_64)
307307

308308
/*
309309
* At this point we are in long mode with 4-level paging enabled,
310-
* but we want to enable 5-level paging.
310+
* but we might want to enable 5-level paging or vice versa.
311311
*
312-
* The problem is that we cannot do it directly. Setting LA57 in
313-
* long mode would trigger #GP. So we need to switch off long mode
314-
* first.
312+
* The problem is that we cannot do it directly. Setting or clearing
313+
* CR4.LA57 in long mode would trigger #GP. So we need to switch off
314+
* long mode and paging first.
315+
*
316+
* We also need a trampoline in lower memory to switch over from
317+
* 4- to 5-level paging for cases when the bootloader puts the kernel
318+
* above 4G, but didn't enable 5-level paging for us.
319+
*
320+
* The same trampoline can be used to switch from 5- to 4-level paging
321+
* mode, like when starting 4-level paging kernel via kexec() when
322+
* original kernel worked in 5-level paging mode.
323+
*
324+
* For the trampoline, we need the top page table to reside in lower
325+
* memory as we don't have a way to load 64-bit values into CR3 in
326+
* 32-bit mode.
327+
*
328+
* We go though the trampoline even if we don't have to: if we're
329+
* already in a desired paging mode. This way the trampoline code gets
330+
* tested on every boot.
315331
*/
316332

317333
/* Make sure we have GDT with 32-bit code segment */
@@ -336,13 +352,18 @@ ENTRY(startup_64)
336352
/* Save the trampoline address in RCX */
337353
movq %rax, %rcx
338354

355+
/*
356+
* Load the address of trampoline_return() into RDI.
357+
* It will be used by the trampoline to return to the main code.
358+
*/
359+
leaq trampoline_return(%rip), %rdi
339360

340361
/* Switch to compatibility mode (CS.L = 0 CS.D = 1) via far return */
341362
pushq $__KERNEL32_CS
342-
leaq compatible_mode(%rip), %rax
363+
leaq TRAMPOLINE_32BIT_CODE_OFFSET(%rax), %rax
343364
pushq %rax
344365
lretq
345-
lvl5:
366+
trampoline_return:
346367
/* Restore the stack, the 32-bit trampoline uses its own stack */
347368
leaq boot_stack_end(%rbx), %rsp
348369

@@ -492,8 +513,14 @@ relocated:
492513
jmp *%rax
493514

494515
.code32
516+
/*
517+
* This is the 32-bit trampoline that will be copied over to low memory.
518+
*
519+
* RDI contains the return address (might be above 4G).
520+
* ECX contains the base address of the trampoline memory.
521+
* Non zero RDX on return means we need to enable 5-level paging.
522+
*/
495523
ENTRY(trampoline_32bit_src)
496-
compatible_mode:
497524
/* Set up data and stack segments */
498525
movl $__KERNEL_DS, %eax
499526
movl %eax, %ds
@@ -534,24 +561,34 @@ compatible_mode:
534561
1:
535562
movl %eax, %cr4
536563

537-
/* Calculate address we are running at */
538-
call 1f
539-
1: popl %edi
540-
subl $1b, %edi
564+
/* Calculate address of paging_enabled() once we are executing in the trampoline */
565+
leal paging_enabled - trampoline_32bit_src + TRAMPOLINE_32BIT_CODE_OFFSET(%ecx), %eax
541566

542-
/* Prepare stack for far return to Long Mode */
567+
/* Prepare the stack for far return to Long Mode */
543568
pushl $__KERNEL_CS
544-
leal lvl5(%edi), %eax
545-
push %eax
569+
pushl %eax
546570

547-
/* Enable paging back */
571+
/* Enable paging again */
548572
movl $(X86_CR0_PG | X86_CR0_PE), %eax
549573
movl %eax, %cr0
550574

551575
lret
552576

577+
.code64
578+
paging_enabled:
579+
/* Return from the trampoline */
580+
jmp *%rdi
581+
582+
/*
583+
* The trampoline code has a size limit.
584+
* Make sure we fail to compile if the trampoline code grows
585+
* beyond TRAMPOLINE_32BIT_CODE_SIZE bytes.
586+
*/
587+
.org trampoline_32bit_src + TRAMPOLINE_32BIT_CODE_SIZE
588+
589+
.code32
553590
no_longmode:
554-
/* This isn't an x86-64 CPU so hang */
591+
/* This isn't an x86-64 CPU, so hang intentionally, we cannot continue */
555592
1:
556593
hlt
557594
jmp 1b

0 commit comments

Comments
 (0)