alistair23-linux/arch/x86/kernel/suspend_asm_64.S
Rafael J. Wysocki d158cbdf39 Hibernation: Arbitrary boot kernel support on x86_64
Make it possible to restore a hibernation image on x86_64 with the help of a
kernel different from the one in the image.

The idea is to split the core restoration code into two separate parts and to
place each of them in a different page.   The first part belongs to the boot
kernel and is executed as the last step of the image kernel's memory
restoration procedure.   Before being executed, it is relocated to a safe page
that won't be overwritten while copying the image kernel pages.

The final operation performed by it is a jump to the second part of the core
restoration code that belongs to the image kernel and has just been restored.
This code makes the CPU switch to the image kernel's page tables and restores
the state of general purpose registers (including the stack pointer) from
before the hibernation.

The main issue with this idea is that in order to jump to the second part of
the core restoration code the boot kernel needs to know its address.
 However, this address may be passed to it in the image header.   Namely, the
part of the image header previously used for checking if the version of the
image kernel is correct can be replaced with some architecture specific data
that will allow the boot kernel to jump to the right address within the image
kernel.   These data should also be used for checking if the image kernel is
compatible with the boot kernel (as far as the memory restroration procedure
is concerned).  It can be done, for example, with the help of a "magic" value
that has to be equal in both kernels, so that they can be regarded as
compatible.

Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
Acked-by: Pavel Machek <pavel@ucw.cz>
Cc: Andi Kleen <ak@suse.de>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2007-10-18 14:37:19 -07:00

138 lines
3.9 KiB
ArmAsm

/* Copyright 2004,2005 Pavel Machek <pavel@suse.cz>, Andi Kleen <ak@suse.de>, Rafael J. Wysocki <rjw@sisk.pl>
*
* Distribute under GPLv2.
*
* swsusp_arch_resume must not use any stack or any nonlocal variables while
* copying pages:
*
* Its rewriting one kernel image with another. What is stack in "old"
* image could very well be data page in "new" image, and overwriting
* your own stack under you is bad idea.
*/
.text
#include <linux/linkage.h>
#include <asm/segment.h>
#include <asm/page.h>
#include <asm/asm-offsets.h>
ENTRY(swsusp_arch_suspend)
movq %rsp, saved_context_esp(%rip)
movq %rax, saved_context_eax(%rip)
movq %rbx, saved_context_ebx(%rip)
movq %rcx, saved_context_ecx(%rip)
movq %rdx, saved_context_edx(%rip)
movq %rbp, saved_context_ebp(%rip)
movq %rsi, saved_context_esi(%rip)
movq %rdi, saved_context_edi(%rip)
movq %r8, saved_context_r08(%rip)
movq %r9, saved_context_r09(%rip)
movq %r10, saved_context_r10(%rip)
movq %r11, saved_context_r11(%rip)
movq %r12, saved_context_r12(%rip)
movq %r13, saved_context_r13(%rip)
movq %r14, saved_context_r14(%rip)
movq %r15, saved_context_r15(%rip)
pushfq ; popq saved_context_eflags(%rip)
/* save the address of restore_registers */
movq $restore_registers, %rax
movq %rax, restore_jump_address(%rip)
call swsusp_save
ret
ENTRY(restore_image)
/* switch to temporary page tables */
movq $__PAGE_OFFSET, %rdx
movq temp_level4_pgt(%rip), %rax
subq %rdx, %rax
movq %rax, %cr3
/* Flush TLB */
movq mmu_cr4_features(%rip), %rax
movq %rax, %rdx
andq $~(1<<7), %rdx # PGE
movq %rdx, %cr4; # turn off PGE
movq %cr3, %rcx; # flush TLB
movq %rcx, %cr3;
movq %rax, %cr4; # turn PGE back on
/* prepare to jump to the image kernel */
movq restore_jump_address(%rip), %rax
/* prepare to copy image data to their original locations */
movq restore_pblist(%rip), %rdx
movq relocated_restore_code(%rip), %rcx
jmpq *%rcx
/* code below has been relocated to a safe page */
ENTRY(core_restore_code)
loop:
testq %rdx, %rdx
jz done
/* get addresses from the pbe and copy the page */
movq pbe_address(%rdx), %rsi
movq pbe_orig_address(%rdx), %rdi
movq $(PAGE_SIZE >> 3), %rcx
rep
movsq
/* progress to the next pbe */
movq pbe_next(%rdx), %rdx
jmp loop
done:
/* jump to the restore_registers address from the image header */
jmpq *%rax
/*
* NOTE: This assumes that the boot kernel's text mapping covers the
* image kernel's page containing restore_registers and the address of
* this page is the same as in the image kernel's text mapping (it
* should always be true, because the text mapping is linear, starting
* from 0, and is supposed to cover the entire kernel text for every
* kernel).
*
* code below belongs to the image kernel
*/
ENTRY(restore_registers)
/* go back to the original page tables */
movq $(init_level4_pgt - __START_KERNEL_map), %rax
addq phys_base(%rip), %rax
movq %rax, %cr3
/* Flush TLB, including "global" things (vmalloc) */
movq mmu_cr4_features(%rip), %rax
movq %rax, %rdx
andq $~(1<<7), %rdx; # PGE
movq %rdx, %cr4; # turn off PGE
movq %cr3, %rcx; # flush TLB
movq %rcx, %cr3
movq %rax, %cr4; # turn PGE back on
movq saved_context_esp(%rip), %rsp
movq saved_context_ebp(%rip), %rbp
/* restore GPRs (we don't restore %rax, it must be 0 anyway) */
movq saved_context_ebx(%rip), %rbx
movq saved_context_ecx(%rip), %rcx
movq saved_context_edx(%rip), %rdx
movq saved_context_esi(%rip), %rsi
movq saved_context_edi(%rip), %rdi
movq saved_context_r08(%rip), %r8
movq saved_context_r09(%rip), %r9
movq saved_context_r10(%rip), %r10
movq saved_context_r11(%rip), %r11
movq saved_context_r12(%rip), %r12
movq saved_context_r13(%rip), %r13
movq saved_context_r14(%rip), %r14
movq saved_context_r15(%rip), %r15
pushq saved_context_eflags(%rip) ; popfq
xorq %rax, %rax
/* tell the hibernation core that we've just restored the memory */
movq %rax, in_suspend(%rip)
ret