Skip to contentSkip to conversation

arm64: kernel: Add support for hibernate/suspend-to-disk

PATCH Superseded James Morse 2 participants 9 messages Oct 12, 2015 – Oct 22, 2015

Subsystems (4)

v1 of 9 Superseded Latest is v9 →

lore
Share & tools
lore
#0 lore raw

cover arm64: kernel: Add support for hibernate/suspend-to-disk

Hi all,

This series adds hibernate support for arm64.
The first two patches are borrowed from kexec-v8 [0] and the related patches [1]. (Patch 2 is superseded by a v5 [2], but its changes to the cpu hotplug hook are causing a problem.)

Patches 3 and 4 provide some cleanup to the cpu_suspend() API:
* allowing it to be used with a 'finisher' that needs to return success,
* and turn the MMU on early to allow sleep_save_sp to be accessed by VA.

The last patch adds hibernate support, following the x86 approach, it creates a temporary set of page tables and copies the hibernate_exit code. The implementation requires that exactly the same kernel is booted on the same hardware, and that the kernel is loaded at the same physical address.

The amount of user-space cache cleaning code in hibernate.c is annoying. None of this is arch specific, all modified-harvard architectures will need to do this. Ideally I would like to move this to the hibernate core code (and make use of the fancy memory-bitmaps), and use flush_cache_range() to clean the caches.

This has only been tested on one board - I look forward to hearing how it works for others!

Thanks,

James

[0] http://lists.infradead.org/pipermail/kexec/2015-March/013432.html
[1] http://lists.infradead.org/pipermail/kexec/2015-May/013709.html
[2] https://lists.linaro.org/pipermail/linaro-kernel/2015-May/021575.html

AKASHI Takahiro (1):
arm64: kvm: add a cpu tear-down function

Geoff Levand (1):
arm64: Fold proc-macros.S into assembler.h

James Morse (4):
arm64: kernel: Rework finisher callback out of __cpu_suspend_enter().
arm64: Change cpu_resume() to enable mmu early then access sleep_sp by
va
arm64: kernel: Include _AC definition in page.h
arm64: kernel: Add support for hibernate/suspend-to-disk.

 arch/arm/include/asm/kvm_asm.h     |   1 +
 arch/arm/include/asm/kvm_host.h    |   7 +
 arch/arm/include/asm/kvm_mmu.h     |   7 +
 arch/arm/kvm/arm.c                 |  18 ++
 arch/arm/kvm/init.S                |   5 +
 arch/arm/kvm/mmu.c                 |   7 +-
 arch/arm64/Kconfig                 |   3 +
 arch/arm64/include/asm/assembler.h |  48 +++-
 arch/arm64/include/asm/kvm_asm.h   |   1 +
 arch/arm64/include/asm/kvm_host.h  |   8 +
 arch/arm64/include/asm/kvm_mmu.h   |   7 +
 arch/arm64/include/asm/page.h      |   2 +
 arch/arm64/include/asm/suspend.h   |  16 +-
 arch/arm64/kernel/Makefile         |   1 +
 arch/arm64/kernel/asm-offsets.c    |   9 +-
 arch/arm64/kernel/head.S           |   3 +-
 arch/arm64/kernel/hibernate-asm.S  | 133 +++++++++++
 arch/arm64/kernel/hibernate.c      | 441 +++++++++++++++++++++++++++++++++++++
 arch/arm64/kernel/setup.c          |   1 -
 arch/arm64/kernel/sleep.S          | 142 ++++--------
 arch/arm64/kernel/suspend.c        | 110 +++++----
 arch/arm64/kernel/vmlinux.lds.S    |  15 ++
 arch/arm64/kvm/hyp-init.S          |  38 +++-
 arch/arm64/mm/cache.S              |   2 -
 arch/arm64/mm/proc-macros.S        |  64 ------
 arch/arm64/mm/proc.S               |  30 +--
 26 files changed, 860 insertions(+), 259 deletions(-)
 create mode 100644 arch/arm64/kernel/hibernate-asm.S
 create mode 100644 arch/arm64/kernel/hibernate.c
 delete mode 100644 arch/arm64/mm/proc-macros.S
Signature · 1 line
2.1.4
#1 lore raw

PATCH 1/6 arm64: kvm: add a cpu tear-down function

From: AKASHI Takahiro <[email protected]>

The CPU must be put back into its initial state, at least, in the following cases in order to shutdown the system and/or re-initialize CPUs later on:
1) kexec/kdump
2) cpu hotplug (offline)
3) removing kvm as a module
4) resume from hibernate (pgd+stack moved)

To address those issues in later patches, this patch adds a tear-down function, kvm_cpu_reset(), that disables the MMU and restores the vector table to the initial stub at EL2.

[use kvm_call_hyp(), simplified mmu-off code]

---
This is based on v4 from
http://lists.infradead.org/pipermail/kexec/2015-May/013709.html.
This patch is superseded by a v5 [0], but its changes to the cpu hotplug hook are causing a problem.

[0] https://lists.linaro.org/pipermail/linaro-kernel/2015-May/021575.html

 arch/arm/include/asm/kvm_asm.h    |  1 +
 arch/arm/include/asm/kvm_host.h   |  7 +++++++
 arch/arm/include/asm/kvm_mmu.h    |  7 +++++++
 arch/arm/kvm/arm.c                | 18 ++++++++++++++++++
 arch/arm/kvm/init.S               |  5 +++++
 arch/arm/kvm/mmu.c                |  7 +++++--
 arch/arm64/include/asm/kvm_asm.h  |  1 +
 arch/arm64/include/asm/kvm_host.h |  8 ++++++++
 arch/arm64/include/asm/kvm_mmu.h  |  7 +++++++
 arch/arm64/kvm/hyp-init.S         | 37 +++++++++++++++++++++++++++++++++++++
 10 files changed, 96 insertions(+), 2 deletions(-)
arch/arm/include/asm/kvm_asm.h +1 -0
index 194c91b610ff..6ecd59127f3f 100644
@@ -85,6 +85,7 @@ struct kvm_vcpu;
 
 extern char __kvm_hyp_init[];
 extern char __kvm_hyp_init_end[];
+extern char __kvm_hyp_reset[];
 
 extern char __kvm_hyp_exit[];
 extern char __kvm_hyp_exit_end[];
arch/arm/include/asm/kvm_host.h +7 -0
index c4072d9f32c7..f27d45f9e346 100644
@@ -44,6 +44,7 @@
 u32 *kvm_vcpu_reg(struct kvm_vcpu *vcpu, u8 reg_num, u32 mode);
 int __attribute_const__ kvm_target_cpu(void);
 int kvm_reset_vcpu(struct kvm_vcpu *vcpu);
+void kvm_reset_cpu(void);
 void kvm_reset_coprocs(struct kvm_vcpu *vcpu);
 
 struct kvm_arch {
@@ -211,6 +212,12 @@ static inline void __cpu_init_hyp_mode(phys_addr_t boot_pgd_ptr,
 	kvm_call_hyp((void*)hyp_stack_ptr, vector_ptr, pgd_ptr);
 }
 
+static inline void __cpu_reset_hyp_mode(phys_addr_t boot_pgd_ptr,
+					phys_addr_t phys_idmap_start,
+					unsigned long reset_func)
+{
+}
+
 static inline int kvm_arch_dev_ioctl_check_extension(long ext)
 {
 	return 0;
arch/arm/include/asm/kvm_mmu.h +7 -0
index 405aa1883307..64201f4f2de8 100644
@@ -66,6 +66,8 @@ void kvm_mmu_free_memory_caches(struct kvm_vcpu *vcpu);
 phys_addr_t kvm_mmu_get_httbr(void);
 phys_addr_t kvm_mmu_get_boot_httbr(void);
 phys_addr_t kvm_get_idmap_vector(void);
+phys_addr_t kvm_get_idmap_start(void);
+extern char  __hyp_idmap_text_start[], __hyp_idmap_text_end[];
 int kvm_mmu_init(void);
 void kvm_clear_hyp_idmap(void);
 
@@ -269,6 +271,11 @@ static inline void __kvm_flush_dcache_pud(pud_t pud)
 void kvm_set_way_flush(struct kvm_vcpu *vcpu);
 void kvm_toggle_cache(struct kvm_vcpu *vcpu, bool was_enabled);
 
+#define kvm_virt_to_trampoline(x)	\
+		(TRAMPOLINE_VA		\
+		+ ((unsigned long)(x)	\
+		- ((unsigned long)__hyp_idmap_text_start & PAGE_MASK)))
+
 static inline bool __kvm_cpu_uses_extended_idmap(void)
 {
 	return false;
arch/arm/kvm/arm.c +18 -0
index dc017adfddc8..f145c4453893 100644
@@ -937,6 +937,24 @@ static void cpu_init_hyp_mode(void *dummy)
 	kvm_arm_init_debug();
 }
 
+void kvm_reset_cpu(void)
+{
+	phys_addr_t boot_pgd_ptr = kvm_mmu_get_boot_httbr();
+	phys_addr_t phys_idmap_start = kvm_get_idmap_start();
+
+	/* Is KVM initialised? */
+	if (boot_pgd_ptr == virt_to_phys(NULL) ||
+	    phys_idmap_start == virt_to_phys(NULL))
+		return;
+
+	/* Do we need to return the vectors to hyp_default_vectors? */
+	if (__hyp_get_vectors() == hyp_default_vectors)
+		return;
+
+	__cpu_reset_hyp_mode(boot_pgd_ptr, phys_idmap_start,
+			     kvm_virt_to_trampoline(__kvm_hyp_reset));
+}
+
 static int hyp_init_cpu_notify(struct notifier_block *self,
 			       unsigned long action, void *cpu)
 {
arch/arm/kvm/init.S +5 -0
index 3988e72d16ff..23bdeac287da 100644
@@ -151,6 +151,11 @@ target:	@ We're now in the trampoline code, switch page tables
 
 	eret
 
+	.globl __kvm_hyp_reset
+__kvm_hyp_reset:
+	/* not yet implemented */
+	ret	lr
+
 	.ltorg
 
 	.globl __kvm_hyp_init_end
arch/arm/kvm/mmu.c +5 -2
index 6984342da13d..88e7d29d8da8 100644
@@ -31,8 +31,6 @@
 
 #include "trace.h"
 
-extern char  __hyp_idmap_text_start[], __hyp_idmap_text_end[];
-
 static pgd_t *boot_hyp_pgd;
 static pgd_t *hyp_pgd;
 static pgd_t *merged_hyp_pgd;
@@ -1644,6 +1642,11 @@ phys_addr_t kvm_get_idmap_vector(void)
 	return hyp_idmap_vector;
 }
 
+phys_addr_t kvm_get_idmap_start(void)
+{
+	return hyp_idmap_start;
+}
+
 int kvm_mmu_init(void)
 {
 	int err;
arch/arm64/include/asm/kvm_asm.h +1 -0
index 5e377101f919..fae48c9584c3 100644
@@ -108,6 +108,7 @@ struct kvm_vcpu;
 
 extern char __kvm_hyp_init[];
 extern char __kvm_hyp_init_end[];
+extern char __kvm_hyp_reset[];
 
 extern char __kvm_hyp_vector[];
 
arch/arm64/include/asm/kvm_host.h +8 -0
index ed039688c221..91157de8a30a 100644
@@ -44,6 +44,7 @@
 
 int __attribute_const__ kvm_target_cpu(void);
 int kvm_reset_vcpu(struct kvm_vcpu *vcpu);
+void kvm_reset_cpu(void);
 int kvm_arch_dev_ioctl_check_extension(long ext);
 
 struct kvm_arch {
@@ -244,6 +245,13 @@ static inline void __cpu_init_hyp_mode(phys_addr_t boot_pgd_ptr,
 		     hyp_stack_ptr, vector_ptr);
 }
 
+static inline void __cpu_reset_hyp_mode(phys_addr_t boot_pgd_ptr,
+					phys_addr_t phys_idmap_start,
+					unsigned long reset_func)
+{
+	kvm_call_hyp((void *)reset_func, boot_pgd_ptr, phys_idmap_start);
+}
+
 static inline void kvm_arch_hardware_disable(void) {}
 static inline void kvm_arch_hardware_unsetup(void) {}
 static inline void kvm_arch_sync_events(struct kvm *kvm) {}
arch/arm64/include/asm/kvm_mmu.h +7 -0
index 61505676d085..31c52e3bc518 100644
@@ -98,6 +98,8 @@ void kvm_mmu_free_memory_caches(struct kvm_vcpu *vcpu);
 phys_addr_t kvm_mmu_get_httbr(void);
 phys_addr_t kvm_mmu_get_boot_httbr(void);
 phys_addr_t kvm_get_idmap_vector(void);
+phys_addr_t kvm_get_idmap_start(void);
+extern char  __hyp_idmap_text_start[], __hyp_idmap_text_end[];
 int kvm_mmu_init(void);
 void kvm_clear_hyp_idmap(void);
 
@@ -271,6 +273,11 @@ static inline void __kvm_flush_dcache_pud(pud_t pud)
 void kvm_set_way_flush(struct kvm_vcpu *vcpu);
 void kvm_toggle_cache(struct kvm_vcpu *vcpu, bool was_enabled);
 
+#define kvm_virt_to_trampoline(x)	\
+		(TRAMPOLINE_VA		\
+		+ ((unsigned long)(x)	\
+		  - ((unsigned long)__hyp_idmap_text_start & PAGE_MASK)))
+
 static inline bool __kvm_cpu_uses_extended_idmap(void)
 {
 	return __cpu_uses_extended_idmap();
arch/arm64/kvm/hyp-init.S +37 -0
index 178ba2248a98..009a9ffdfca3 100644
@@ -140,6 +140,43 @@ merged:
 	eret
 ENDPROC(__kvm_hyp_init)
 
+	/*
+	 * x0: HYP boot pgd
+	 * x1: HYP phys_idmap_start
+	 */
+ENTRY(__kvm_hyp_reset)
+	/*
+	 * Restore el1's lr so we can eret from here. The stack is inaccessible
+	 * after we turn the mmu off. This value was pushed in el1_sync().
+	 */
+	pop lr, xzr
+
+	/* We're in trampoline code in VA, switch back to boot page tables */
+	msr	ttbr0_el2, x0
+	isb
+
+	/* Invalidate the old TLBs */
+	tlbi	alle2
+	dsb	sy
+
+	/* Branch into PA space */
+	adr	x0, 1f
+	bfi	x1, x0, #0, #PAGE_SHIFT
+	br	x1
+
+	/* We're now in idmap, disable MMU */
+1:	mrs	x0, sctlr_el2
+	bic	x0, x0, #SCTLR_EL2_M
+	msr	sctlr_el2, x0
+	isb
+
+	/* Install stub vectors */
+	adr_l	x2, __hyp_stub_vectors
+	msr	vbar_el2, x2
+
+	eret
+ENDPROC(__kvm_hyp_reset)
+
 	.ltorg
 
 	.popsection
Signature · 1 line
2.1.4
#2 lore raw

PATCH 2/6 arm64: Fold proc-macros.S into assembler.h

In reply to James Morse

From: Geoff Levand <[email protected]>

To allow the assembler macros defined in arch/arm64/mm/proc-macros.S to be used outside the mm code move the contents of proc-macros.S to asm/assembler.h. Also, delete proc-macros.S, and fix up all references to proc-macros.S.

---
This is v8 from
http://lists.infradead.org/pipermail/kexec/2015-March/013432.html

 arch/arm64/include/asm/assembler.h | 48 +++++++++++++++++++++++++++-
 arch/arm64/kernel/head.S           |  1 -
 arch/arm64/kvm/hyp-init.S          |  1 -
 arch/arm64/mm/cache.S              |  2 --
 arch/arm64/mm/proc-macros.S        | 64 --------------------------------------
 arch/arm64/mm/proc.S               |  3 --
 6 files changed, 47 insertions(+), 72 deletions(-)
 delete mode 100644 arch/arm64/mm/proc-macros.S
arch/arm64/include/asm/assembler.h +47 -1
index b51f2cc22ca9..91cb311d33de 100644
@@ -1,5 +1,5 @@
 /*
- * Based on arch/arm/include/asm/assembler.h
+ * * Based on arch/arm/include/asm/assembler.h, arch/arm/mm/proc-macros.S
  *
  * Copyright (C) 1996-2000 Russell King
  * Copyright (C) 2012 ARM Ltd.
@@ -23,6 +23,8 @@
 #ifndef __ASM_ASSEMBLER_H
 #define __ASM_ASSEMBLER_H
 
+#include <asm/asm-offsets.h>
+#include <asm/pgtable-hwdef.h>
 #include <asm/ptrace.h>
 #include <asm/thread_info.h>
 
@@ -193,4 +195,48 @@ lr	.req	x30		// link register
 	str	\src, [\tmp, :lo12:\sym]
 	.endm
 
+/*
+ * vma_vm_mm - get mm pointer from vma pointer (vma->vm_mm)
+ */
+	.macro	vma_vm_mm, rd, rn
+	ldr	\rd, [\rn, #VMA_VM_MM]
+	.endm
+
+/*
+ * mmid - get context id from mm pointer (mm->context.id)
+ */
+	.macro	mmid, rd, rn
+	ldr	\rd, [\rn, #MM_CONTEXT_ID]
+	.endm
+
+/*
+ * dcache_line_size - get the minimum D-cache line size from the CTR register.
+ */
+	.macro	dcache_line_size, reg, tmp
+	mrs	\tmp, ctr_el0			// read CTR
+	ubfm	\tmp, \tmp, #16, #19		// cache line size encoding
+	mov	\reg, #4			// bytes per word
+	lsl	\reg, \reg, \tmp		// actual cache line size
+	.endm
+
+/*
+ * icache_line_size - get the minimum I-cache line size from the CTR register.
+ */
+	.macro	icache_line_size, reg, tmp
+	mrs	\tmp, ctr_el0			// read CTR
+	and	\tmp, \tmp, #0xf		// cache line size encoding
+	mov	\reg, #4			// bytes per word
+	lsl	\reg, \reg, \tmp		// actual cache line size
+	.endm
+
+/*
+ * tcr_set_idmap_t0sz - update TCR.T0SZ so that we can load the ID map
+ */
+	.macro	tcr_set_idmap_t0sz, valreg, tmpreg
+#ifndef CONFIG_ARM64_VA_BITS_48
+	ldr_l	\tmpreg, idmap_t0sz
+	bfi	\valreg, \tmpreg, #TCR_T0SZ_OFFSET, #TCR_TxSZ_WIDTH
+#endif
+	.endm
+
 #endif	/* __ASM_ASSEMBLER_H */
arch/arm64/kernel/head.S +0 -1
index 90d09eddd5b2..9ad8b1f15b19 100644
@@ -31,7 +31,6 @@
 #include <asm/cputype.h>
 #include <asm/memory.h>
 #include <asm/thread_info.h>
-#include <asm/pgtable-hwdef.h>
 #include <asm/pgtable.h>
 #include <asm/page.h>
 #include <asm/virt.h>
arch/arm64/kvm/hyp-init.S +0 -1
index 009a9ffdfca3..72c093eb885b 100644
@@ -20,7 +20,6 @@
 #include <asm/assembler.h>
 #include <asm/kvm_arm.h>
 #include <asm/kvm_mmu.h>
-#include <asm/pgtable-hwdef.h>
 
 	.text
 	.pushsection	.hyp.idmap.text, "ax"
arch/arm64/mm/cache.S +0 -2
index eb48d5df4a0f..9e13cb53c927 100644
@@ -24,8 +24,6 @@
 #include <asm/cpufeature.h>
 #include <asm/alternative.h>
 
-#include "proc-macros.S"
-
 /*
  *	flush_icache_range(start,end)
  *
arch/arm64/mm/proc-macros.S deleted +0 -64

64 lines not on this page · raw message

arch/arm64/mm/proc.S +0 -3
index e4ee7bd8830a..456c1c5f8ecd 100644
@@ -23,11 +23,8 @@
 #include <asm/assembler.h>
 #include <asm/asm-offsets.h>
 #include <asm/hwcap.h>
-#include <asm/pgtable-hwdef.h>
 #include <asm/pgtable.h>
 
-#include "proc-macros.S"
-
 #ifdef CONFIG_ARM64_64K_PAGES
 #define TCR_TG_FLAGS	TCR_TG0_64K | TCR_TG1_64K
 #else
Signature · 1 line
2.1.4
#3 lore raw

PATCH 3/6 arm64: kernel: Rework finisher callback out of __cpu_suspend_enter().

In reply to James Morse

Hibernate could make use of the cpu_suspend() code to save/restore cpu state, however it needs to be able to return '0' from the 'finisher'.

Rework cpu_suspend() so that the finisher is called from C code, independently from the save/restore of cpu state. Space to save the context in is allocated in the caller's stack frame, and passed into __cpu_suspend_enter().

Hibernate's use of this API will look like a copy of the cpu_suspend() function.

---
 arch/arm64/include/asm/suspend.h |  8 ++++
 arch/arm64/kernel/asm-offsets.c  |  2 +
 arch/arm64/kernel/sleep.S        | 86 +++++++++++++---------------------------
 arch/arm64/kernel/suspend.c      | 81 ++++++++++++++++++++++---------------
 4 files changed, 86 insertions(+), 91 deletions(-)
arch/arm64/include/asm/suspend.h +8 -0
index 59a5b0f1e81c..a9de0d3f543f 100644
@@ -2,6 +2,7 @@
 #define __ASM_SUSPEND_H
 
 #define NR_CTX_REGS 11
+#define NR_CALLEE_SAVED_REGS 12
 
 /*
  * struct cpu_suspend_ctx must be 16-byte aligned since it is allocated on
@@ -21,6 +22,13 @@ struct sleep_save_sp {
 	phys_addr_t save_ptr_stash_phys;
 };
 
+struct sleep_stack_data {
+	struct cpu_suspend_ctx	system_regs;
+	unsigned long		callee_saved_regs[NR_CALLEE_SAVED_REGS];
+};
+
 extern int cpu_suspend(unsigned long arg, int (*fn)(unsigned long));
 extern void cpu_resume(void);
+int __cpu_suspend_enter(struct sleep_stack_data *state);
+void __cpu_suspend_exit(struct mm_struct *mm);
 #endif
arch/arm64/kernel/asm-offsets.c +2 -0
index 8d89cf8dae55..5daa4e692932 100644
@@ -160,6 +160,8 @@ int main(void)
   DEFINE(SLEEP_SAVE_SP_SZ,	sizeof(struct sleep_save_sp));
   DEFINE(SLEEP_SAVE_SP_PHYS,	offsetof(struct sleep_save_sp, save_ptr_stash_phys));
   DEFINE(SLEEP_SAVE_SP_VIRT,	offsetof(struct sleep_save_sp, save_ptr_stash));
+  DEFINE(SLEEP_STACK_DATA_SYSTEM_REGS,	offsetof(struct sleep_stack_data, system_regs));
+  DEFINE(SLEEP_STACK_DATA_CALLEE_REGS,	offsetof(struct sleep_stack_data, callee_saved_regs));
 #endif
   return 0;
 }
arch/arm64/kernel/sleep.S +27 -59

120 lines not on this page · raw message

arch/arm64/kernel/suspend.c +49 -32

121 lines not on this page · raw message

Signature · 1 line
2.1.4
#4 lore raw

PATCH 3/6 arm64: kernel: Rework finisher callback out of __cpu_suspend_enter().

Hi James,

Quoted 11 lines · James Morse

Hibernate could make use of the cpu_suspend() code to save/restore cpu state, however it needs to be able to return '0' from the 'finisher'.

Rework cpu_suspend() so that the finisher is called from C code, independently from the save/restore of cpu state. Space to save the context in is allocated in the caller's stack frame, and passed into __cpu_suspend_enter().

Hibernate's use of this API will look like a copy of the cpu_suspend() function.

Quoted 7 lines
---
 arch/arm64/include/asm/suspend.h |  8 ++++
 arch/arm64/kernel/asm-offsets.c  |  2 +
 arch/arm64/kernel/sleep.S        | 86 +++++++++++++---------------------------
 arch/arm64/kernel/suspend.c      | 81 ++++++++++++++++++++++---------------
 4 files changed, 86 insertions(+), 91 deletions(-)

Two minor requests below to update some comments, otherwise:

Quoted 13 lines
arch/arm64/include/asm/suspend.h +1 -0
index 59a5b0f1e81c..a9de0d3f543f 100644
@@ -2,6 +2,7 @@
 #define __ASM_SUSPEND_H
 
 #define NR_CTX_REGS 11
+#define NR_CALLEE_SAVED_REGS 12
 
 /*
  * struct cpu_suspend_ctx must be 16-byte aligned since it is allocated on

arch/arm64/include/asm/suspend.h struct sleep_save_sp { · line 27

Quoted 6 lines
arch/arm64/include/asm/suspend.h +3 -0
@@ -21,6 +22,13 @@ struct sleep_save_sp {
 	phys_addr_t save_ptr_stash_phys;
 };
 
+struct sleep_stack_data {
+	struct cpu_suspend_ctx	system_regs;
+	unsigned long		callee_saved_regs[NR_CALLEE_SAVED_REGS];

Please add a comment referring to the __cpu_suspend_enter expected registers layout and how this struct and __cpu_suspend_enter are related.

Quoted 25 lines
arch/arm64/include/asm/suspend.h +4 -0
+};
+
 extern int cpu_suspend(unsigned long arg, int (*fn)(unsigned long));
 extern void cpu_resume(void);
+int __cpu_suspend_enter(struct sleep_stack_data *state);
+void __cpu_suspend_exit(struct mm_struct *mm);
 #endif
arch/arm64/kernel/asm-offsets.c +2 -0
index 8d89cf8dae55..5daa4e692932 100644
@@ -160,6 +160,8 @@ int main(void)
   DEFINE(SLEEP_SAVE_SP_SZ,	sizeof(struct sleep_save_sp));
   DEFINE(SLEEP_SAVE_SP_PHYS,	offsetof(struct sleep_save_sp, save_ptr_stash_phys));
   DEFINE(SLEEP_SAVE_SP_VIRT,	offsetof(struct sleep_save_sp, save_ptr_stash));
+  DEFINE(SLEEP_STACK_DATA_SYSTEM_REGS,	offsetof(struct sleep_stack_data, system_regs));
+  DEFINE(SLEEP_STACK_DATA_CALLEE_REGS,	offsetof(struct sleep_stack_data, callee_saved_regs));
 #endif
   return 0;
 }
arch/arm64/kernel/sleep.S +0 -0
index f586f7c875e2..6182388e32a5 100644

arch/arm64/kernel/sleep.S line 52

Quoted 3 lines
arch/arm64/kernel/sleep.S +0 -0
@@ -50,36 +50,24 @@
 	.endm
 /*
  * Save CPU state for a suspend and execute the suspend finisher.

This function does not call the finisher anymore, please update the comment above.

arch/arm64/kernel/sleep.S line 54

Quoted 9 lines
arch/arm64/kernel/sleep.S +2 -7
- * On success it will return 0 through cpu_resume - ie through a CPU
- * soft/hard reboot from the reset vector.
- * On failure it returns the suspend finisher return value or force
- * -EOPNOTSUPP if the finisher erroneously returns 0 (the suspend finisher
- * is not allowed to return, if it does this must be considered failure).
- * It saves callee registers, and allocates space on the kernel stack
- * to save the CPU specific registers + some other data for resume.
+ * This function returns a non-zero value. Resuming through cpu_resume()
+ * will cause 0 to appear to be returned by this function.

Nit: please replace the description with an updated one to explain what __cpu_suspend_enter is meant to achieve, in particular the reasoning behind the return value (and code path) logic.

Thanks,
Lorenzo

Quoted 244 lines
arch/arm64/kernel/sleep.S +25 -52
  *
- *  x0 = suspend finisher argument
- *  x1 = suspend finisher function pointer
+ *  x0 = struct sleep_stack_data area
  */
 ENTRY(__cpu_suspend_enter)
-	stp	x29, lr, [sp, #-96]!
-	stp	x19, x20, [sp,#16]
-	stp	x21, x22, [sp,#32]
-	stp	x23, x24, [sp,#48]
-	stp	x25, x26, [sp,#64]
-	stp	x27, x28, [sp,#80]
-	/*
-	 * Stash suspend finisher and its argument in x20 and x19
-	 */
-	mov	x19, x0
-	mov	x20, x1
+	stp	x29, lr, [x0, #SLEEP_STACK_DATA_CALLEE_REGS]
+	stp	x19, x20, [x0,#SLEEP_STACK_DATA_CALLEE_REGS+16]
+	stp	x21, x22, [x0,#SLEEP_STACK_DATA_CALLEE_REGS+32]
+	stp	x23, x24, [x0,#SLEEP_STACK_DATA_CALLEE_REGS+48]
+	stp	x25, x26, [x0,#SLEEP_STACK_DATA_CALLEE_REGS+64]
+	stp	x27, x28, [x0,#SLEEP_STACK_DATA_CALLEE_REGS+80]
+
+	/* save the sp in cpu_suspend_ctx */
 	mov	x2, sp
-	sub	sp, sp, #CPU_SUSPEND_SZ	// allocate cpu_suspend_ctx
-	mov	x0, sp
-	/*
-	 * x0 now points to struct cpu_suspend_ctx allocated on the stack
-	 */
-	str	x2, [x0, #CPU_CTX_SP]
+	str	x2, [x0, #SLEEP_STACK_DATA_SYSTEM_REGS + CPU_CTX_SP]
+
+	/* find the mpidr_hash */
 	ldr	x1, =sleep_save_sp
 	ldr	x1, [x1, #SLEEP_SAVE_SP_VIRT]
 	mrs	x7, mpidr_el1
@@ -93,34 +81,11 @@ ENTRY(__cpu_suspend_enter)
 	ldp	w5, w6, [x9, #(MPIDR_HASH_SHIFTS + 8)]
 	compute_mpidr_hash x8, x3, x4, x5, x6, x7, x10
 	add	x1, x1, x8, lsl #3
+
+	push	x29, lr
 	bl	__cpu_suspend_save
-	/*
-	 * Grab suspend finisher in x20 and its argument in x19
-	 */
-	mov	x0, x19
-	mov	x1, x20
-	/*
-	 * We are ready for power down, fire off the suspend finisher
-	 * in x1, with argument in x0
-	 */
-	blr	x1
-        /*
-	 * Never gets here, unless suspend finisher fails.
-	 * Successful cpu_suspend should return from cpu_resume, returning
-	 * through this code path is considered an error
-	 * If the return value is set to 0 force x0 = -EOPNOTSUPP
-	 * to make sure a proper error condition is propagated
-	 */
-	cmp	x0, #0
-	mov	x3, #-EOPNOTSUPP
-	csel	x0, x3, x0, eq
-	add	sp, sp, #CPU_SUSPEND_SZ	// rewind stack pointer
-	ldp	x19, x20, [sp, #16]
-	ldp	x21, x22, [sp, #32]
-	ldp	x23, x24, [sp, #48]
-	ldp	x25, x26, [sp, #64]
-	ldp	x27, x28, [sp, #80]
-	ldp	x29, lr, [sp], #96
+	pop	x29, lr
+	mov	x0, #1
 	ret
 ENDPROC(__cpu_suspend_enter)
 	.ltorg
@@ -146,12 +111,6 @@ ENDPROC(cpu_resume_mmu)
 	.popsection
 cpu_resume_after_mmu:
 	mov	x0, #0			// return zero on success
-	ldp	x19, x20, [sp, #16]
-	ldp	x21, x22, [sp, #32]
-	ldp	x23, x24, [sp, #48]
-	ldp	x25, x26, [sp, #64]
-	ldp	x27, x28, [sp, #80]
-	ldp	x29, lr, [sp], #96
 	ret
 ENDPROC(cpu_resume_after_mmu)
 
@@ -168,6 +127,8 @@ ENTRY(cpu_resume)
         /* x7 contains hash index, let's use it to grab context pointer */
 	ldr_l	x0, sleep_save_sp + SLEEP_SAVE_SP_PHYS
 	ldr	x0, [x0, x7, lsl #3]
+	add	x29, x0, #SLEEP_STACK_DATA_CALLEE_REGS
+	add	x0, x0, #SLEEP_STACK_DATA_SYSTEM_REGS
 	/* load sp from context */
 	ldr	x2, [x0, #CPU_CTX_SP]
 	/* load physical address of identity map page table in x1 */
@@ -178,5 +139,12 @@ ENTRY(cpu_resume)
 	 * pointer and x1 to contain physical address of 1:1 page tables
 	 */
 	bl	cpu_do_resume		// PC relative jump, MMU off
+	/* Can't access these by physical address once the MMU is on */
+	ldp	x19, x20, [x29, #16]
+	ldp	x21, x22, [x29, #32]
+	ldp	x23, x24, [x29, #48]
+	ldp	x25, x26, [x29, #64]
+	ldp	x27, x28, [x29, #80]
+	ldp	x29, lr, [x29]
 	b	cpu_resume_mmu		// Resume MMU, never returns
 ENDPROC(cpu_resume)
arch/arm64/kernel/suspend.c +49 -32
index 8297d502217e..2c1a1fd0b4bb 100644
@@ -9,22 +9,22 @@
 #include <asm/suspend.h>
 #include <asm/tlbflush.h>
 
-extern int __cpu_suspend_enter(unsigned long arg, int (*fn)(unsigned long));
+
 /*
  * This is called by __cpu_suspend_enter() to save the state, and do whatever
  * flushing is required to ensure that when the CPU goes to sleep we have
  * the necessary data available when the caches are not searched.
  *
- * ptr: CPU context virtual address
+ * ptr: sleep_stack_data containing cpu state virtual address.
  * save_ptr: address of the location where the context physical address
  *           must be saved
  */
-void notrace __cpu_suspend_save(struct cpu_suspend_ctx *ptr,
+void notrace __cpu_suspend_save(struct sleep_stack_data *ptr,
 				phys_addr_t *save_ptr)
 {
 	*save_ptr = virt_to_phys(ptr);
 
-	cpu_do_suspend(ptr);
+	cpu_do_suspend(&ptr->system_regs);
 	/*
 	 * Only flush the context that must be retrieved with the MMU
 	 * off. VA primitives ensure the flush is applied to all
@@ -50,6 +50,37 @@ void __init cpu_suspend_set_dbg_restorer(void (*hw_bp_restore)(void *))
 	hw_breakpoint_restore = hw_bp_restore;
 }
 
+void notrace __cpu_suspend_exit(struct mm_struct *mm)
+{
+	/*
+	 * We are resuming from reset with TTBR0_EL1 set to the
+	 * idmap to enable the MMU; restore the active_mm mappings in
+	 * TTBR0_EL1 unless the active_mm == &init_mm, in which case
+	 * the thread entered cpu_suspend with TTBR0_EL1 set to
+	 * reserved TTBR0 page tables and should be restored as such.
+	 */
+	if (mm == &init_mm)
+		cpu_set_reserved_ttbr0();
+	else
+		cpu_switch_mm(mm->pgd, mm);
+
+	flush_tlb_all();
+
+	/*
+	 * Restore per-cpu offset before any kernel
+	 * subsystem relying on it has a chance to run.
+	 */
+	set_my_cpu_offset(per_cpu_offset(smp_processor_id()));
+
+	/*
+	 * Restore HW breakpoint registers to sane values
+	 * before debug exceptions are possibly reenabled
+	 * through local_dbg_restore.
+	 */
+	if (hw_breakpoint_restore)
+		hw_breakpoint_restore(NULL);
+}
+
 /*
  * cpu_suspend
  *
@@ -60,8 +91,9 @@ void __init cpu_suspend_set_dbg_restorer(void (*hw_bp_restore)(void *))
 int cpu_suspend(unsigned long arg, int (*fn)(unsigned long))
 {
 	struct mm_struct *mm = current->active_mm;
-	int ret;
+	int ret = 0;
 	unsigned long flags;
+	struct sleep_stack_data state;
 
 	/*
 	 * From this point debug exceptions are disabled to prevent
@@ -76,36 +108,21 @@ int cpu_suspend(unsigned long arg, int (*fn)(unsigned long))
 	 * page tables, so that the thread address space is properly
 	 * set-up on function return.
 	 */
-	ret = __cpu_suspend_enter(arg, fn);
-	if (ret == 0) {
-		/*
-		 * We are resuming from reset with TTBR0_EL1 set to the
-		 * idmap to enable the MMU; restore the active_mm mappings in
-		 * TTBR0_EL1 unless the active_mm == &init_mm, in which case
-		 * the thread entered cpu_suspend with TTBR0_EL1 set to
-		 * reserved TTBR0 page tables and should be restored as such.
-		 */
-		if (mm == &init_mm)
-			cpu_set_reserved_ttbr0();
-		else
-			cpu_switch_mm(mm->pgd, mm);
-
-		flush_tlb_all();
-
-		/*
-		 * Restore per-cpu offset before any kernel
-		 * subsystem relying on it has a chance to run.
-		 */
-		set_my_cpu_offset(per_cpu_offset(smp_processor_id()));
+	if (__cpu_suspend_enter(&state)) {
+		/* Call the suspend finisher */
+		ret = fn(arg);
 
 		/*
-		 * Restore HW breakpoint registers to sane values
-		 * before debug exceptions are possibly reenabled
-		 * through local_dbg_restore.
+		 * Never gets here, unless suspend finisher fails.
+		 * Successful cpu_suspend should return from cpu_resume,
+		 * returning through this code path is considered an error
+		 * If the return value is set to 0 force ret = -EOPNOTSUPP
+		 * to make sure a proper error condition is propagated
 		 */
-		if (hw_breakpoint_restore)
-			hw_breakpoint_restore(NULL);
-	}
+		if (!ret)
+			ret = -EOPNOTSUPP;
+	} else
+		__cpu_suspend_exit(mm);
 
 	/*
 	 * Restore pstate flags. OS lock and mdscr have been already
Signature · 1 line
2.1.4
#5 lore raw

PATCH 4/6 arm64: Change cpu_resume() to enable mmu early then access sleep_sp by va

In reply to James Morse

By enabling the MMU early in cpu_resume(), the sleep_save_sp and stack can be accessed by VA, which avoids the need to convert-addresses and clean to PoC on the suspend path.

MMU setup is shared with the boot path, meaning the swapper_pg_dir is restored directly: ttbr1_el1 is no longer saved/restored.

struct sleep_save_sp is removed, replacing it with a single array of pointers.

cpu_do_{suspend,resume} could be further reduced to not restore: cpacr_el1, mdscr_el1, tcr_el1, vbar_el1 and sctlr_el1, all of which are set by __cpu_setup(). However these values all contain res0 bits that may be used to enable future features.

---
 arch/arm64/include/asm/suspend.h |  7 +----
 arch/arm64/kernel/asm-offsets.c  |  3 ---
 arch/arm64/kernel/head.S         |  2 +-
 arch/arm64/kernel/setup.c        |  1 -
 arch/arm64/kernel/sleep.S        | 57 ++++++++++++++--------------------------
 arch/arm64/kernel/suspend.c      | 37 +++-----------------------
 arch/arm64/mm/proc.S             | 27 +++++--------------
 7 files changed, 33 insertions(+), 101 deletions(-)
arch/arm64/include/asm/suspend.h +1 -6
index a9de0d3f543f..36f35ba41fa2 100644
@@ -1,7 +1,7 @@
 #ifndef __ASM_SUSPEND_H
 #define __ASM_SUSPEND_H
 
-#define NR_CTX_REGS 11
+#define NR_CTX_REGS 10
 #define NR_CALLEE_SAVED_REGS 12
 
 /*
@@ -17,11 +17,6 @@ struct cpu_suspend_ctx {
 	u64 sp;
 } __aligned(16);
 
-struct sleep_save_sp {
-	phys_addr_t *save_ptr_stash;
-	phys_addr_t save_ptr_stash_phys;
-};
-
 struct sleep_stack_data {
 	struct cpu_suspend_ctx	system_regs;
 	unsigned long		callee_saved_regs[NR_CALLEE_SAVED_REGS];
arch/arm64/kernel/asm-offsets.c +0 -3
index 5daa4e692932..3cb1383d3deb 100644
@@ -157,9 +157,6 @@ int main(void)
   DEFINE(CPU_CTX_SP,		offsetof(struct cpu_suspend_ctx, sp));
   DEFINE(MPIDR_HASH_MASK,	offsetof(struct mpidr_hash, mask));
   DEFINE(MPIDR_HASH_SHIFTS,	offsetof(struct mpidr_hash, shift_aff));
-  DEFINE(SLEEP_SAVE_SP_SZ,	sizeof(struct sleep_save_sp));
-  DEFINE(SLEEP_SAVE_SP_PHYS,	offsetof(struct sleep_save_sp, save_ptr_stash_phys));
-  DEFINE(SLEEP_SAVE_SP_VIRT,	offsetof(struct sleep_save_sp, save_ptr_stash));
   DEFINE(SLEEP_STACK_DATA_SYSTEM_REGS,	offsetof(struct sleep_stack_data, system_regs));
   DEFINE(SLEEP_STACK_DATA_CALLEE_REGS,	offsetof(struct sleep_stack_data, callee_saved_regs));
 #endif
arch/arm64/kernel/head.S +1 -1
index 9ad8b1f15b19..cf4e0bdf6533 100644
@@ -630,7 +630,7 @@ ENDPROC(__secondary_switched)
  * other registers depend on the function called upon completion
  */
 	.section	".idmap.text", "ax"
-__enable_mmu:
+ENTRY(__enable_mmu)
 	ldr	x5, =vectors
 	msr	vbar_el1, x5
 	msr	ttbr0_el1, x25			// load TTBR0
arch/arm64/kernel/setup.c +0 -1
index 232247945b1c..5a338235ba1a 100644
@@ -192,7 +192,6 @@ static void __init smp_build_mpidr_hash(void)
 	 */
 	if (mpidr_hash_size() > 4 * num_possible_cpus())
 		pr_warn("Large number of MPIDR hash buckets detected\n");
-	__flush_dcache_area(&mpidr_hash, sizeof(struct mpidr_hash));
 }
 
 static void __init setup_processor(void)
arch/arm64/kernel/sleep.S +20 -37

94 lines not on this page · raw message

arch/arm64/kernel/suspend.c +4 -33
index 2c1a1fd0b4bb..0e761cf34202 100644
@@ -11,30 +11,6 @@
 
 
 /*
- * This is called by __cpu_suspend_enter() to save the state, and do whatever
- * flushing is required to ensure that when the CPU goes to sleep we have
- * the necessary data available when the caches are not searched.
- *
- * ptr: sleep_stack_data containing cpu state virtual address.
- * save_ptr: address of the location where the context physical address
- *           must be saved
- */
-void notrace __cpu_suspend_save(struct sleep_stack_data *ptr,
-				phys_addr_t *save_ptr)
-{
-	*save_ptr = virt_to_phys(ptr);
-
-	cpu_do_suspend(&ptr->system_regs);
-	/*
-	 * Only flush the context that must be retrieved with the MMU
-	 * off. VA primitives ensure the flush is applied to all
-	 * cache levels so context is pushed to DRAM.
-	 */
-	__flush_dcache_area(ptr, sizeof(*ptr));
-	__flush_dcache_area(save_ptr, sizeof(*save_ptr));
-}
-
-/*
  * This hook is provided so that cpu_suspend code can restore HW
  * breakpoints as early as possible in the resume path, before reenabling
  * debug exceptions. Code cannot be run from a CPU PM notifier since by the
@@ -134,22 +110,17 @@ int cpu_suspend(unsigned long arg, int (*fn)(unsigned long))
 	return ret;
 }
 
-struct sleep_save_sp sleep_save_sp;
+unsigned long *sleep_save_stash;
 
 static int __init cpu_suspend_init(void)
 {
-	void *ctx_ptr;
-
 	/* ctx_ptr is an array of physical addresses */
-	ctx_ptr = kcalloc(mpidr_hash_size(), sizeof(phys_addr_t), GFP_KERNEL);
+	sleep_save_stash = kcalloc(mpidr_hash_size(), sizeof(*sleep_save_stash),
+				   GFP_KERNEL);
 
-	if (WARN_ON(!ctx_ptr))
+	if (WARN_ON(!sleep_save_stash))
 		return -ENOMEM;
 
-	sleep_save_sp.save_ptr_stash = ctx_ptr;
-	sleep_save_sp.save_ptr_stash_phys = virt_to_phys(ctx_ptr);
-	__flush_dcache_area(&sleep_save_sp, sizeof(struct sleep_save_sp));
-
 	return 0;
 }
 early_initcall(cpu_suspend_init);
arch/arm64/mm/proc.S +7 -20
index 456c1c5f8ecd..b3afb6123c81 100644
@@ -59,20 +59,17 @@ ENTRY(cpu_do_suspend)
 	mrs	x2, tpidr_el0
 	mrs	x3, tpidrro_el0
 	mrs	x4, contextidr_el1
-	mrs	x5, mair_el1
 	mrs	x6, cpacr_el1
-	mrs	x7, ttbr1_el1
 	mrs	x8, tcr_el1
 	mrs	x9, vbar_el1
 	mrs	x10, mdscr_el1
 	mrs	x11, oslsr_el1
 	mrs	x12, sctlr_el1
 	stp	x2, x3, [x0]
-	stp	x4, x5, [x0, #16]
-	stp	x6, x7, [x0, #32]
-	stp	x8, x9, [x0, #48]
-	stp	x10, x11, [x0, #64]
-	str	x12, [x0, #80]
+	stp	x4, xzr, [x0, #16]
+	stp	x6, x8, [x0, #32]
+	stp	x9, x10, [x0, #48]
+	stp	x11, x12, [x0, #64]
 	ret
 ENDPROC(cpu_do_suspend)
 
@@ -80,29 +77,20 @@ ENDPROC(cpu_do_suspend)
  * cpu_do_resume - restore CPU register context
  *
  * x0: Physical address of context pointer
- * x1: ttbr0_el1 to be restored
  *
  * Returns:
  *	sctlr_el1 value in x0
  */
 ENTRY(cpu_do_resume)
-	/*
-	 * Invalidate local tlb entries before turning on MMU
-	 */
-	tlbi	vmalle1
 	ldp	x2, x3, [x0]
 	ldp	x4, x5, [x0, #16]
-	ldp	x6, x7, [x0, #32]
-	ldp	x8, x9, [x0, #48]
-	ldp	x10, x11, [x0, #64]
-	ldr	x12, [x0, #80]
+	ldp	x6, x8, [x0, #32]
+	ldp	x9, x10, [x0, #48]
+	ldp	x11, x12, [x0, #64]
 	msr	tpidr_el0, x2
 	msr	tpidrro_el0, x3
 	msr	contextidr_el1, x4
-	msr	mair_el1, x5
 	msr	cpacr_el1, x6
-	msr	ttbr0_el1, x1
-	msr	ttbr1_el1, x7
 	tcr_set_idmap_t0sz x8, x7
 	msr	tcr_el1, x8
 	msr	vbar_el1, x9
@@ -113,7 +101,6 @@ ENTRY(cpu_do_resume)
 	ubfx	x11, x11, #1, #1
 	msr	oslar_el1, x11
 	mov	x0, x12
-	dsb	nsh		// Make sure local tlb invalidation completed
 	isb
 	ret
 ENDPROC(cpu_do_resume)
Signature · 1 line
2.1.4
#6 lore raw

PATCH 5/6 arm64: kernel: Include _AC definition in page.h

In reply to James Morse

page.h uses '_AC' in the definition of PAGE_SIZE, but doesn't include linux/const.h where this is defined. This produces build warnings when only asm/page.h is included by asm code.

---
 arch/arm64/include/asm/page.h | 2 ++
 1 file changed, 2 insertions(+)
arch/arm64/include/asm/page.h +2 -0
index 7d9c7e4a424b..74f67d049a63 100644
@@ -19,6 +19,8 @@
 #ifndef __ASM_PAGE_H
 #define __ASM_PAGE_H
 
+#include <linux/const.h>
+
 /* PAGE_SHIFT determines the page size */
 #ifdef CONFIG_ARM64_64K_PAGES
 #define PAGE_SHIFT		16
Signature · 1 line
2.1.4
#7 lore raw

PATCH 6/6 arm64: kernel: Add support for hibernate/suspend-to-disk.

In reply to James Morse

Add support for hibernate/suspend-to-disk.

Suspend borrows code from cpu_suspend() to write cpu state onto the stack, before calling swusp_save() to save the memory image.

Restore creates a set of temporary page tables, covering only the linear map, and copies the restore code to a 'safe' page, then
uses the copy to restore the memory image. It calls into cpu_resume(), and then follows the normal cpu_suspend() path back into the suspend code.

The suspend C code also includes some post-hibernate cache cleanup.

The implementation assumes that exactly the same kernel is booted on the same hardware, and that the kernel is loaded at the same physical address.

---
 arch/arm64/Kconfig                |   3 +
 arch/arm64/include/asm/suspend.h  |   5 +
 arch/arm64/kernel/Makefile        |   1 +
 arch/arm64/kernel/asm-offsets.c   |   4 +
 arch/arm64/kernel/hibernate-asm.S | 133 ++++++++++++
 arch/arm64/kernel/hibernate.c     | 441 ++++++++++++++++++++++++++++++++++++++
 arch/arm64/kernel/sleep.S         |   1 +
 arch/arm64/kernel/vmlinux.lds.S   |  15 ++
 8 files changed, 603 insertions(+)
 create mode 100644 arch/arm64/kernel/hibernate-asm.S
 create mode 100644 arch/arm64/kernel/hibernate.c
arch/arm64/Kconfig +3 -0
index 07d1811aa03f..d081dbc35335 100644
@@ -707,6 +707,9 @@ menu "Power management options"
 
 source "kernel/power/Kconfig"
 
+config ARCH_HIBERNATION_POSSIBLE
+	def_bool y
+
 config ARCH_SUSPEND_POSSIBLE
 	def_bool y
 
arch/arm64/include/asm/suspend.h +5 -0
index 36f35ba41fa2..d7405ca4e6c8 100644
@@ -22,6 +22,11 @@ struct sleep_stack_data {
 	unsigned long		callee_saved_regs[NR_CALLEE_SAVED_REGS];
 };
 
+extern int swsusp_arch_suspend(void);
+extern int swsusp_arch_resume(void);
+int swsusp_arch_suspend_enter(struct cpu_suspend_ctx *ptr);
+void __noreturn swsusp_arch_suspend_exit(phys_addr_t tmp_pg_dir,
+					 phys_addr_t swapper_pg_dir);
 extern int cpu_suspend(unsigned long arg, int (*fn)(unsigned long));
 extern void cpu_resume(void);
 int __cpu_suspend_enter(struct sleep_stack_data *state);
arch/arm64/kernel/Makefile +1 -0
index 22dc9bc781be..b9151ae4a7ae 100644
@@ -36,6 +36,7 @@ arm64-obj-$(CONFIG_EFI)			+= efi.o efi-stub.o efi-entry.o
 arm64-obj-$(CONFIG_PCI)			+= pci.o
 arm64-obj-$(CONFIG_ARMV8_DEPRECATED)	+= armv8_deprecated.o
 arm64-obj-$(CONFIG_ACPI)		+= acpi.o
+arm64-obj-$(CONFIG_HIBERNATION)		+= hibernate.o hibernate-asm.o
 
 obj-y					+= $(arm64-obj-y) vdso/
 obj-m					+= $(arm64-obj-m)
arch/arm64/kernel/asm-offsets.c +4 -0
index 3cb1383d3deb..b5d9495a94a1 100644
@@ -22,6 +22,7 @@
 #include <linux/mm.h>
 #include <linux/dma-mapping.h>
 #include <linux/kvm_host.h>
+#include <linux/suspend.h>
 #include <asm/thread_info.h>
 #include <asm/memory.h>
 #include <asm/smp_plat.h>
@@ -160,5 +161,8 @@ int main(void)
   DEFINE(SLEEP_STACK_DATA_SYSTEM_REGS,	offsetof(struct sleep_stack_data, system_regs));
   DEFINE(SLEEP_STACK_DATA_CALLEE_REGS,	offsetof(struct sleep_stack_data, callee_saved_regs));
 #endif
+  DEFINE(HIBERN_PBE_ORIG,	offsetof(struct pbe, orig_address));
+  DEFINE(HIBERN_PBE_ADDR,	offsetof(struct pbe, address));
+  DEFINE(HIBERN_PBE_NEXT,	offsetof(struct pbe, next));
   return 0;
 }
arch/arm64/kernel/hibernate-asm.S new file +133 -0

133 lines not on this page · raw message

arch/arm64/kernel/hibernate.c new file +441 -0

441 lines not on this page · raw message

arch/arm64/kernel/sleep.S +1 -0
index da4405062d83..f58008d6dadf 100644
@@ -2,6 +2,7 @@
 #include <linux/linkage.h>
 #include <asm/asm-offsets.h>
 #include <asm/assembler.h>
+#include <asm/memory.h>
 
 	.text
 /*
arch/arm64/kernel/vmlinux.lds.S +15 -0
index 98073332e2d0..3d8284d91f4c 100644
@@ -44,6 +44,16 @@ jiffies = jiffies_64;
 	*(.idmap.text)					\
 	VMLINUX_SYMBOL(__idmap_text_end) = .;
 
+#ifdef CONFIG_HIBERNATION
+#define HIBERNATE_TEXT					\
+	. = ALIGN(SZ_4K);				\
+	VMLINUX_SYMBOL(__hibernate_exit_text_start) = .;\
+	*(.hibernate_exit.text)				\
+	VMLINUX_SYMBOL(__hibernate_exit_text_end) = .;
+#else
+#define HIBERNATE_TEXT
+#endif
+
 /*
  * The size of the PE/COFF section that covers the kernel image, which
  * runs from stext to _edata, must be a round multiple of the PE/COFF
@@ -102,6 +112,7 @@ SECTIONS
 			LOCK_TEXT
 			HYPERVISOR_TEXT
 			IDMAP_TEXT
+			HIBERNATE_TEXT
 			*(.fixup)
 			*(.gnu.warning)
 		. = ALIGN(16);
@@ -181,6 +192,10 @@ ASSERT(__hyp_idmap_text_end - (__hyp_idmap_text_start & ~(SZ_4K - 1)) <= SZ_4K,
 	"HYP init code too big or misaligned")
 ASSERT(__idmap_text_end - (__idmap_text_start & ~(SZ_4K - 1)) <= SZ_4K,
 	"ID map text too big or misaligned")
+#ifdef CONFIG_HIBERNATION
+ASSERT(__hibernate_exit_text_end - (__hibernate_exit_text_start & ~(SZ_4K - 1))
+	<= SZ_4K, "Hibernate exit text too big or misaligned")
+#endif
 
 /*
  * If padding is applied before .head.text, virt<->phys conversions will fail.
Signature · 1 line
2.1.4
#8 lore raw

PATCH 6/6 arm64: kernel: Add support for hibernate/suspend-to-disk.

Hi Pavel, Rafael,

On Mon, Oct 12, 2015 at 02:17:38PM +0100, James Morse wrote:

[...]

Quoted 76 lines
arch/arm64/kernel/hibernate.c new file +76 -0
+/*
+ * Walk the provided mm's page tables, from start_addr to end_addr. Translate
+ * each page to its alias in the linear map, and clean that to the PoU.
+ * This is safe to call on user-space mm's, as all the access is to page tables
+ * and kernel linear-map addresses.
+ *
+ * Uses __clean_dcache_pou(), which does not provide any barriers or icache
+ * maintenance. Ensure start_addr is page aligned.
+ */
+static void clean_mapped_range(struct mm_struct *mm, unsigned long start_addr,
+			       unsigned long end_addr)
+{
+	pte_t *pte;
+	size_t length;
+	unsigned long map_addr;
+	unsigned long linear_addr;
+
+	for (map_addr = start_addr; map_addr < end_addr; map_addr += length) {
+		pte = lookup_address(pgd_offset(mm, map_addr), map_addr,
+				     &length);
+		/* length is valid, even if pte is NULL */
+		if (!pte || !pte_valid(*pte))
+			continue;
+
+		linear_addr = (unsigned long)pfn_to_kaddr(pte_pfn(*pte));
+		__clean_dcache_pou(linear_addr, linear_addr+length);
+	}
+}
+
+int swsusp_arch_suspend(void)
+{
+	int ret = 0;
+	unsigned long flags;
+	struct task_struct *p;
+	struct vm_area_struct *vma;
+	struct sleep_stack_data state;
+	struct mm_struct *mm = current->active_mm;
+
+	local_dbg_save(flags);
+
+	if (__cpu_suspend_enter(&state))
+		ret = swsusp_save();
+	else {
+		__cpu_suspend_exit(mm);
+
+		pr_info("Performing cache maintenance.\n");
+
+		/*
+		 * We clean the 'tricky' cache ranges here. Modules and user
+		 * space executable code may have been written to via its
+		 * alias in the kernel linear mapping.
+		 *
+		 * To clean these ranges, we walk the page tables to find the
+		 * physical pages, and then their position in the linear map.
+		 *
+		 * The restore_pblist used during restore only contains pages
+		 * that were in use - other pages containing executable code
+		 * may have been written by core hibernate code.
+		 */
+		clean_mapped_range(&init_mm, MODULES_VADDR, MODULES_END);
+
+		/*
+		 * Any user space executable code that isn't going to be
+		 * reloaded from disk (e.g. jit code) is now potentially
+		 * in the data cache, and needs cleaning.
+		 *
+		 * TODO: Some pages are mapped to user-space many times.
+		 *       Implement a 'cleaned' bitmap so we only clean each
+		 *       page once.
+		 */
+		read_lock(&tasklist_lock);
+		for_each_process(p) {
+			if (!p->mm || p->mm == &init_mm)
+				continue;
+
+			down_read(&p->mm->mmap_sem);

arch/arm64/kernel/hibernate.c line 205

Quoted 10 lines
arch/arm64/kernel/hibernate.c new file +10 -0
+			for_each_vma(p->mm, vma) {
+				if (!(vma->vm_flags & VM_EXEC))
+					continue;
+
+				clean_mapped_range(p->mm, vma->vm_start,
+						   vma->vm_end);
+			}
+			up_read(&p->mm->mmap_sem);
+		}
+		read_unlock(&tasklist_lock);

After resuming from hibernate in the code above, we are forced to walk the list of processes to make sure that for executable pages that were copied by hibernate core code (I mean pages that are not part of the restore_pblist and were already "restored" by the time swsusp_arch_resume is called) the I-cache(s) and D-cache(s) are in sync. We were wondering if you would accept moving that code to hibernate core code, we see two options for that:

1) Add a cache flushing hook to do_copy_page() in snapshot.c so that we can make sure that, if the page in question contains executable code (I do not think there is an easy way to detect that though unless we walk the processes page tables), the D-cache and I-cache are in-sync (the hook might be empty on architectures like x86 where this is not an issue)
2) We add code like the loop above in hibernate core code either to create a bitmap of pages that need I-cache/D-cache synchronization on resume from hibernate (the bitmap can be created at snapshot preparation time) or to just do what James does here, basically walk the list of processes and call a cache sync hook to make sure I-cache and D-caches are in-sync

I do not think that the code above belongs in architecture specific code since it is an arch-agnostic issue and it is already dealt with in the kernel with the cache flushing API (eg flush_dcache_page) for different purposes, I guess hibernate core code does not need this at present because on x86 it is a non-existing problem.

If you are not opposed to moving this code to hibernate core code we can put a patch together to deal with this in core code, please let us know.

Thanks,
Lorenzo

Quoted 291 lines
arch/arm64/kernel/hibernate.c new file +236 -0
+
+		/* page tables may still be cached -how does this affect dma? */
+
+		/* all cache cleaning should have finished */
+		dsb(ish);
+		__flush_icache_all();
+	}
+
+	local_dbg_restore(flags);
+
+	return ret;
+}
+
+static int copy_pte(pmd_t *dst, pmd_t *src, unsigned long start_addr)
+{
+	int i;
+	pte_t *old_pte = pte_offset_kernel(src, start_addr);
+	pte_t *new_pte = pte_offset_kernel(dst, start_addr);
+
+	for (i = pte_index(start_addr); i < PTRS_PER_PTE;
+	     i++, old_pte++, new_pte++) {
+		if (pte_val(*old_pte))
+			set_pte(new_pte,
+				__pte(pte_val(*old_pte) & ~PTE_RDONLY));
+	}
+
+	return 0;
+}
+
+static int copy_pmd(pud_t *dst, pud_t *src, unsigned long start_addr)
+{
+	int i;
+	int rc = 0;
+	pte_t *new_pte;
+	pmd_t *old_pmd = pmd_offset(src, start_addr);
+	pmd_t *new_pmd = pmd_offset(dst, start_addr);
+
+	for (i = pmd_index(start_addr); i < PTRS_PER_PMD;
+	     i++, start_addr += PMD_SIZE, old_pmd++, new_pmd++) {
+		if (!pmd_val(*old_pmd))
+			continue;
+
+		if (pmd_table(*(old_pmd))) {
+			new_pte = (pte_t *)get_safe_page(GFP_ATOMIC);
+			if (!new_pte) {
+				rc = -ENOMEM;
+				break;
+			}
+
+			set_pmd(new_pmd, __pmd(virt_to_phys(new_pte)
+					       | PMD_TYPE_TABLE));
+
+			rc = copy_pte(new_pmd, old_pmd, start_addr);
+			if (rc)
+				break;
+		} else
+			set_pmd(new_pmd,
+				__pmd(pmd_val(*old_pmd) & ~PMD_SECT_RDONLY));
+	}
+
+	return rc;
+}
+
+static int copy_pud(pgd_t *dst, pgd_t *src, unsigned long start_addr)
+{
+	int i;
+	int rc = 0;
+	pmd_t *new_pmd;
+	pud_t *old_pud = pud_offset(src, start_addr);
+	pud_t *new_pud = pud_offset(dst, start_addr);
+
+	for (i = pud_index(start_addr); i < PTRS_PER_PUD;
+	     i++, start_addr += PUD_SIZE, old_pud++, new_pud++) {
+		if (!pud_val(*old_pud))
+			continue;
+
+		if (pud_table(*(old_pud))) {
+			if (PTRS_PER_PMD != 1) {
+				new_pmd = (pmd_t *)get_safe_page(GFP_ATOMIC);
+				if (!new_pmd) {
+					rc = -ENOMEM;
+					break;
+				}
+
+				set_pud(new_pud, __pud(virt_to_phys(new_pmd)
+						       | PUD_TYPE_TABLE));
+			}
+
+			rc = copy_pmd(new_pud, old_pud, start_addr);
+			if (rc)
+				break;
+		} else
+			set_pud(new_pud,
+				__pud(pud_val(*old_pud) & ~PMD_SECT_RDONLY));
+	}
+
+	return rc;
+}
+
+static int copy_linear_map(pgd_t *new_pgd)
+{
+	int i;
+	int rc = 0;
+	pud_t *new_pud;
+	unsigned long start_addr = PAGE_OFFSET;
+	pgd_t *old_pgd = pgd_offset_k(start_addr);
+
+	new_pgd += pgd_index(start_addr);
+
+	for (i = pgd_index(start_addr); i < PTRS_PER_PGD;
+	     i++, start_addr += PGDIR_SIZE, old_pgd++, new_pgd++) {
+		if (!pgd_val(*old_pgd))
+			continue;
+
+		if (PTRS_PER_PUD != 1) {
+			new_pud = (pud_t *)get_safe_page(GFP_ATOMIC);
+			if (!new_pud) {
+				rc = -ENOMEM;
+				break;
+			}
+
+			set_pgd(new_pgd, __pgd(virt_to_phys(new_pud)
+					       | PUD_TYPE_TABLE));
+		}
+
+		rc = copy_pud(new_pgd, old_pgd, start_addr);
+		if (rc)
+			break;
+	}
+
+	return rc;
+}
+
+/*
+ * Setup then Resume from the hibernate image using swsusp_arch_suspend_exit().
+ *
+ * Memory allocated by get_safe_page() will be dealt with by the hibernate code,
+ * we don't need to free it here.
+ *
+ * Allocate a safe zero page to use as ttbr0, as all existing page tables, and
+ * even the empty_zero_page will be overwritten.
+ */
+int swsusp_arch_resume(void)
+{
+	int rc = 0;
+	pgd_t *pgd;
+	size_t length;
+	size_t exit_size;
+	pgd_t *tmp_pg_dir;
+	pte_t *exit_page_pte;
+	pte_t exit_page_pte_orig;
+	unsigned long exit_page;
+	void *safe_zero_page_mem;
+	void __noreturn (*hibernate_exit)(phys_addr_t, phys_addr_t);
+
+	/* Copy swsusp_arch_suspend_exit() to a safe page. */
+	exit_page = get_safe_page(GFP_ATOMIC);
+	if (!exit_page) {
+		pr_err("Failed to allocate memory for hibernate_exit code.");
+		rc = -ENOMEM;
+		goto out;
+	}
+	exit_size = __hibernate_exit_text_end - __hibernate_exit_text_start;
+	memcpy((void *)exit_page, __hibernate_exit_text_start, exit_size);
+	flush_icache_range(exit_page, exit_page + exit_size);
+	if (IS_ENABLED(CONFIG_DEBUG_RODATA)) {
+		/*
+		 * set_memory_x() is only for the module ranges. We only have
+		 * the linear-map mapped - so need to make the copied page
+		 * executable now, and when we run with the copied page tables.
+		 * The process of restoring the hibernate kernel will undo
+		 * this change.
+		 */
+		pgd = pgd_offset(&init_mm, exit_page);
+		exit_page_pte = lookup_address(pgd, exit_page, &length);
+		if (exit_page_pte) {
+			exit_page_pte_orig = pte_val(*exit_page_pte);
+			set_pte_at(&init_mm, exit_page, exit_page_pte,
+				  __pte(pte_val(*exit_page_pte) & ~PTE_PXN));
+			flush_tlb_kernel_range(exit_page, exit_page + PAGE_SIZE);
+		}
+		else {
+			pr_err("Failed to find page table entry for hibernate_exit code!");
+			rc = -EFAULT;
+			goto out;
+		}
+	}
+	hibernate_exit = (void *)exit_page;
+
+	/*
+	 * Even the zero page may get overwritten during restore.
+	 * get_safe_page() only returns zero'd pages.
+	 */
+	safe_zero_page_mem = (void *)get_safe_page(GFP_ATOMIC);
+	if (!safe_zero_page_mem) {
+		pr_err("Failed to allocate memory for zero page.");
+		rc = -ENOMEM;
+		goto pte_undo;
+	}
+	empty_zero_page = virt_to_page(safe_zero_page_mem);
+	cpu_set_reserved_ttbr0();
+
+	/*
+	 * Restoring the memory image will overwrite the ttbr1 page tables.
+	 * Create a second copy, of just the linear map, and use this when
+	 * restoring.
+	 */
+	tmp_pg_dir = (pgd_t *)get_safe_page(GFP_ATOMIC);
+	if (!tmp_pg_dir) {
+		pr_err("Failed to allocate memory for temporary page tables.");
+		rc = -ENOMEM;
+		goto pte_undo;
+	}
+	rc = copy_linear_map(tmp_pg_dir);
+	if (rc)
+		goto pte_undo;
+
+	/*
+	 * EL2 may get upset if we overwrite its page-tables/stack.
+	 * kvm_reset_cpu() returns EL2 to the hyp stub. This isn't needed
+	 * on normal suspend/resume as PSCI prevents us from ruining EL2.
+	 */
+	if (IS_ENABLED(CONFIG_KVM_ARM_HOST))
+		kvm_reset_cpu();
+
+	hibernate_exit(virt_to_phys(tmp_pg_dir), virt_to_phys(swapper_pg_dir));
+
+pte_undo:
+	if (IS_ENABLED(CONFIG_DEBUG_RODATA)) {
+		set_pte_at(&init_mm, exit_page, exit_page_pte,
+			   exit_page_pte_orig);
+		flush_tlb_kernel_range(exit_page, exit_page + PAGE_SIZE);
+	}
+out:
+	return rc;
+}
arch/arm64/kernel/sleep.S +1 -0
index da4405062d83..f58008d6dadf 100644
@@ -2,6 +2,7 @@
 #include <linux/linkage.h>
 #include <asm/asm-offsets.h>
 #include <asm/assembler.h>
+#include <asm/memory.h>
 
 	.text
 /*
arch/arm64/kernel/vmlinux.lds.S +15 -0
index 98073332e2d0..3d8284d91f4c 100644
@@ -44,6 +44,16 @@ jiffies = jiffies_64;
 	*(.idmap.text)					\
 	VMLINUX_SYMBOL(__idmap_text_end) = .;
 
+#ifdef CONFIG_HIBERNATION
+#define HIBERNATE_TEXT					\
+	. = ALIGN(SZ_4K);				\
+	VMLINUX_SYMBOL(__hibernate_exit_text_start) = .;\
+	*(.hibernate_exit.text)				\
+	VMLINUX_SYMBOL(__hibernate_exit_text_end) = .;
+#else
+#define HIBERNATE_TEXT
+#endif
+
 /*
  * The size of the PE/COFF section that covers the kernel image, which
  * runs from stext to _edata, must be a round multiple of the PE/COFF
@@ -102,6 +112,7 @@ SECTIONS
 			LOCK_TEXT
 			HYPERVISOR_TEXT
 			IDMAP_TEXT
+			HIBERNATE_TEXT
 			*(.fixup)
 			*(.gnu.warning)
 		. = ALIGN(16);
@@ -181,6 +192,10 @@ ASSERT(__hyp_idmap_text_end - (__hyp_idmap_text_start & ~(SZ_4K - 1)) <= SZ_4K,
 	"HYP init code too big or misaligned")
 ASSERT(__idmap_text_end - (__idmap_text_start & ~(SZ_4K - 1)) <= SZ_4K,
 	"ID map text too big or misaligned")
+#ifdef CONFIG_HIBERNATION
+ASSERT(__hibernate_exit_text_end - (__hibernate_exit_text_start & ~(SZ_4K - 1))
+	<= SZ_4K, "Hibernate exit text too big or misaligned")
+#endif
 
 /*
  * If padding is applied before .head.text, virt<->phys conversions will fail.
Signature · 1 line
2.1.4