diff mbox

arm64: suspend: avoid potential TLB conflict

Message ID 1470651050-18291-1-git-send-email-mark.rutland@arm.com
State New
Headers show

Commit Message

Mark Rutland Aug. 8, 2016, 10:10 a.m. UTC
In create_safe_exec_page we install a set of global mappings in TTBR0,
then subsequently invalidate TLBs. While TTBR0 points at the zero page,
and the TLBs should be free of stale global entries, we may have stale
ASID-tagged entries (e.g. from the EFI runtime services mappings) for
the same VAs. Per the ARM ARM these ASID-tagged entries may conflict
with newly-allocated global entries, and we must follow a
Break-Before-Make approach to avoid issues resulting from this.

This patch reworks create_safe_exec_page to invalidate TLBs while the
zero page is still in place, ensuring that there are no potential
conflicts when the new TTBR0 value is installed. As a single CPU is
online while this code executes, we do not need to perform broadcast TLB
maintenance, and can call local_flush_tlb_all(), which also subsumes
some barriers. The remaining assembly is converted to use write_sysreg()
and isb().

Other than this, we safely manipulate TTBRs in the hibernate dance. The
code we install as part of the new TTBR0 mapping (the hibernated
kernel's swsusp_arch_suspend_exit) installs a zero page into TTBR1,
invalidates TLBs, then installs its preferred value. Upon being restored
to the middle of swsusp_arch_suspend, the new image will call
__cpu_suspend_exit, which will call cpu_uninstall_idmap, installing the
zero page in TTBR0 and invalidating all TLB entries.

Signed-off-by: Mark Rutland <mark.rutland@arm.com>

Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: James Morse <james.morse@arm.com>
Cc: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
Cc: Will Deacon <will.deacon@arm.com>
Fixes: 82869ac57b5d3b55 ("arm64: kernel: Add support for hibernate/suspend-to-disk")
---
 arch/arm64/kernel/hibernate.c | 17 +++++++++++------
 1 file changed, 11 insertions(+), 6 deletions(-)

-- 
1.9.1


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

Comments

Mark Rutland Aug. 9, 2016, 4:49 p.m. UTC | #1
On Tue, Aug 09, 2016 at 05:25:37PM +0100, James Morse wrote:
> Hi Mark,

> 

> ~s/suspend/hibernate/ in the subject?


Ah, yes. I'll fix that up.

> >  /*

> > @@ -217,12 +218,16 @@ static int create_safe_exec_page(void *src_start, size_t length,

> >  	set_pte(pte, __pte(virt_to_phys((void *)dst) |

> >  			 pgprot_val(PAGE_KERNEL_EXEC)));

> >  

> > -	/* Load our new page tables */

> > -	asm volatile("msr	ttbr0_el1, %0;"

> > -		     "isb;"

> > -		     "tlbi	vmalle1is;"

> > -		     "dsb	ish;"

> > -		     "isb" : : "r"(virt_to_phys(pgd)));

> > +	/*

> > +	 * Load our new page tables. TTBR0 currently points to the zero page,

> 

> fe12c00d21bb ("PM / hibernate: Introduce test_resume mode for hibernation") came

> in with the merge window, this does a suspend followed by a resume with the user

> page tables still loaded in ttbr0_el1.

> 

> So now we need to call cpu_set_reserved_ttbr0() in here to make this true/safe.


Thanks for the heads-up!

From a quick look, that sounds like the right thing to do.

> > +	 * and the TLBs should be free of global entries, but may contain stale

> > +	 * ASID-tagged entries (e.g. from the EFI runtime services). A strict

> > +	 * BBM approach requires that we destroy these before installing

> > +	 * overlapping global mappings.

> > +	 */

> > +	local_flush_tlb_all();

> > +	write_sysreg(virt_to_phys(pgd), ttbr0_el1);

> > +	isb();

> >  

> >  	*phys_dst_addr = virt_to_phys((void *)dst);

> 

> and it even looks better!

> 

> If you think they're useful:

> Tested-by: James Morse <james.morse@arm.com>

> Acked-by: James Morse <james.morse@arm.com>


Cheers!

I assume those apply with the addition of cpu_set_reserved_ttbr0(),
which I'll fold into v2 along with them.

Thanks,
Mark.

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
Mark Rutland Aug. 9, 2016, 5:51 p.m. UTC | #2
On Tue, Aug 09, 2016 at 05:25:37PM +0100, James Morse wrote:
> > @@ -217,12 +218,16 @@ static int create_safe_exec_page(void *src_start, size_t length,

> >  	set_pte(pte, __pte(virt_to_phys((void *)dst) |

> >  			 pgprot_val(PAGE_KERNEL_EXEC)));

> >  

> > -	/* Load our new page tables */

> > -	asm volatile("msr	ttbr0_el1, %0;"

> > -		     "isb;"

> > -		     "tlbi	vmalle1is;"

> > -		     "dsb	ish;"

> > -		     "isb" : : "r"(virt_to_phys(pgd)));

> > +	/*

> > +	 * Load our new page tables. TTBR0 currently points to the zero page,

> 

> fe12c00d21bb ("PM / hibernate: Introduce test_resume mode for hibernation") came

> in with the merge window, this does a suspend followed by a resume with the user

> page tables still loaded in ttbr0_el1.


Hmmm... given that, it looks like if we bail out in swsusp_arch_resume()
after the call to create_safe_exec_page(), we may return to userspace
with a corrupted TTBR0.

We probably need to defer the call to create_safe_exec_page() after the
other potential failure sites so as to avoid that.

Looking around it's not clear to me how/where the get_safe_page()
allocations are cleaned up when a failure occurs.

Thanks,
Mark.

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
Mark Rutland Aug. 11, 2016, 11:01 a.m. UTC | #3
On Wed, Aug 10, 2016 at 10:39:50AM +0100, James Morse wrote:
> Hi Mark,

> 

> On 09/08/16 18:51, Mark Rutland wrote:

> > On Tue, Aug 09, 2016 at 05:25:37PM +0100, James Morse wrote:

> >> fe12c00d21bb ("PM / hibernate: Introduce test_resume mode for hibernation") came

> >> in with the merge window, this does a suspend followed by a resume with the user

> >> page tables still loaded in ttbr0_el1.

> > 

> > Hmmm... given that, it looks like if we bail out in swsusp_arch_resume()

> > after the call to create_safe_exec_page(), we may return to userspace

> > with a corrupted TTBR0.

> 

> Ah, didn't spot that.

> 

> > We probably need to defer the call to create_safe_exec_page() after the

> > other potential failure sites so as to avoid that.

> > 

> > Looking around it's not clear to me how/where the get_safe_page()

> > allocations are cleaned up when a failure occurs.

> 

> Its dealt with by the core code: they get added to to one of

> kernel/power/snapshot.c's plethora of bitmaps, and freed via

> free_basic_memory_bitmaps() -> memory_bm_free() -> free_list_of_pages() ->

> free_image_page().

> 

> It looks like pages allocated by get_safe_page() are on the 'forbidden_pages_map'.


Thanks for the pointer!

Given that, it looks like a simple reshuffling of swsusp_arch_resume is
all that's necessary. I'll spin that along with v2 of this patch.

Thanks,
Mark.

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
diff mbox

Patch

diff --git a/arch/arm64/kernel/hibernate.c b/arch/arm64/kernel/hibernate.c
index 21ab5df..c59d3e4 100644
--- a/arch/arm64/kernel/hibernate.c
+++ b/arch/arm64/kernel/hibernate.c
@@ -35,6 +35,7 @@ 
 #include <asm/sections.h>
 #include <asm/smp.h>
 #include <asm/suspend.h>
+#include <asm/sysreg.h>
 #include <asm/virt.h>
 
 /*
@@ -217,12 +218,16 @@  static int create_safe_exec_page(void *src_start, size_t length,
 	set_pte(pte, __pte(virt_to_phys((void *)dst) |
 			 pgprot_val(PAGE_KERNEL_EXEC)));
 
-	/* Load our new page tables */
-	asm volatile("msr	ttbr0_el1, %0;"
-		     "isb;"
-		     "tlbi	vmalle1is;"
-		     "dsb	ish;"
-		     "isb" : : "r"(virt_to_phys(pgd)));
+	/*
+	 * Load our new page tables. TTBR0 currently points to the zero page,
+	 * and the TLBs should be free of global entries, but may contain stale
+	 * ASID-tagged entries (e.g. from the EFI runtime services). A strict
+	 * BBM approach requires that we destroy these before installing
+	 * overlapping global mappings.
+	 */
+	local_flush_tlb_all();
+	write_sysreg(virt_to_phys(pgd), ttbr0_el1);
+	isb();
 
 	*phys_dst_addr = virt_to_phys((void *)dst);