aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCatalin Marinas <catalin.marinas@arm.com>2017-06-26 09:27:36 -0400
committerCatalin Marinas <catalin.marinas@arm.com>2017-08-21 06:12:29 -0400
commit3bbf7157ac66a88d94b291d4d5e2b2a9319a0f90 (patch)
tree9b06c2107a0c7ee8f5496fcd6bfc06f94441c46e
parenta7ba38d68017477bd441c62b11bb6a1fc9140ecd (diff)
arm64: Convert pte handling from inline asm to using (cmp)xchg
With the support for hardware updates of the access and dirty states, the following pte handling functions had to be implemented using exclusives: __ptep_test_and_clear_young(), ptep_get_and_clear(), ptep_set_wrprotect() and ptep_set_access_flags(). To take advantage of the LSE atomic instructions and also make the code cleaner, convert these pte functions to use the more generic cmpxchg()/xchg(). Reviewed-by: Will Deacon <will.deacon@arm.com> Acked-by: Mark Rutland <mark.rutland@arm.com> Acked-by: Steve Capper <steve.capper@arm.com> Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
-rw-r--r--arch/arm64/include/asm/pgtable.h71
-rw-r--r--arch/arm64/mm/fault.c24
2 files changed, 44 insertions, 51 deletions
diff --git a/arch/arm64/include/asm/pgtable.h b/arch/arm64/include/asm/pgtable.h
index 6eae342ced6b..9127688ae775 100644
--- a/arch/arm64/include/asm/pgtable.h
+++ b/arch/arm64/include/asm/pgtable.h
@@ -39,6 +39,7 @@
39 39
40#ifndef __ASSEMBLY__ 40#ifndef __ASSEMBLY__
41 41
42#include <asm/cmpxchg.h>
42#include <asm/fixmap.h> 43#include <asm/fixmap.h>
43#include <linux/mmdebug.h> 44#include <linux/mmdebug.h>
44 45
@@ -173,6 +174,11 @@ static inline pte_t pte_clear_rdonly(pte_t pte)
173 return clear_pte_bit(pte, __pgprot(PTE_RDONLY)); 174 return clear_pte_bit(pte, __pgprot(PTE_RDONLY));
174} 175}
175 176
177static inline pte_t pte_set_rdonly(pte_t pte)
178{
179 return set_pte_bit(pte, __pgprot(PTE_RDONLY));
180}
181
176static inline pte_t pte_mkpresent(pte_t pte) 182static inline pte_t pte_mkpresent(pte_t pte)
177{ 183{
178 return set_pte_bit(pte, __pgprot(PTE_VALID)); 184 return set_pte_bit(pte, __pgprot(PTE_VALID));
@@ -593,20 +599,17 @@ static inline int pmdp_set_access_flags(struct vm_area_struct *vma,
593#define __HAVE_ARCH_PTEP_TEST_AND_CLEAR_YOUNG 599#define __HAVE_ARCH_PTEP_TEST_AND_CLEAR_YOUNG
594static inline int __ptep_test_and_clear_young(pte_t *ptep) 600static inline int __ptep_test_and_clear_young(pte_t *ptep)
595{ 601{
596 pteval_t pteval; 602 pte_t old_pte, pte;
597 unsigned int tmp, res;
598 603
599 asm volatile("// __ptep_test_and_clear_young\n" 604 pte = READ_ONCE(*ptep);
600 " prfm pstl1strm, %2\n" 605 do {
601 "1: ldxr %0, %2\n" 606 old_pte = pte;
602 " ubfx %w3, %w0, %5, #1 // extract PTE_AF (young)\n" 607 pte = pte_mkold(pte);
603 " and %0, %0, %4 // clear PTE_AF\n" 608 pte_val(pte) = cmpxchg_relaxed(&pte_val(*ptep),
604 " stxr %w1, %0, %2\n" 609 pte_val(old_pte), pte_val(pte));
605 " cbnz %w1, 1b\n" 610 } while (pte_val(pte) != pte_val(old_pte));
606 : "=&r" (pteval), "=&r" (tmp), "+Q" (pte_val(*ptep)), "=&r" (res)
607 : "L" (~PTE_AF), "I" (ilog2(PTE_AF)));
608 611
609 return res; 612 return pte_young(pte);
610} 613}
611 614
612static inline int ptep_test_and_clear_young(struct vm_area_struct *vma, 615static inline int ptep_test_and_clear_young(struct vm_area_struct *vma,
@@ -630,17 +633,7 @@ static inline int pmdp_test_and_clear_young(struct vm_area_struct *vma,
630static inline pte_t ptep_get_and_clear(struct mm_struct *mm, 633static inline pte_t ptep_get_and_clear(struct mm_struct *mm,
631 unsigned long address, pte_t *ptep) 634 unsigned long address, pte_t *ptep)
632{ 635{
633 pteval_t old_pteval; 636 return __pte(xchg_relaxed(&pte_val(*ptep), 0));
634 unsigned int tmp;
635
636 asm volatile("// ptep_get_and_clear\n"
637 " prfm pstl1strm, %2\n"
638 "1: ldxr %0, %2\n"
639 " stxr %w1, xzr, %2\n"
640 " cbnz %w1, 1b\n"
641 : "=&r" (old_pteval), "=&r" (tmp), "+Q" (pte_val(*ptep)));
642
643 return __pte(old_pteval);
644} 637}
645 638
646#ifdef CONFIG_TRANSPARENT_HUGEPAGE 639#ifdef CONFIG_TRANSPARENT_HUGEPAGE
@@ -659,21 +652,23 @@ static inline pmd_t pmdp_huge_get_and_clear(struct mm_struct *mm,
659#define __HAVE_ARCH_PTEP_SET_WRPROTECT 652#define __HAVE_ARCH_PTEP_SET_WRPROTECT
660static inline void ptep_set_wrprotect(struct mm_struct *mm, unsigned long address, pte_t *ptep) 653static inline void ptep_set_wrprotect(struct mm_struct *mm, unsigned long address, pte_t *ptep)
661{ 654{
662 pteval_t pteval; 655 pte_t old_pte, pte;
663 unsigned long tmp; 656
664 657 pte = READ_ONCE(*ptep);
665 asm volatile("// ptep_set_wrprotect\n" 658 do {
666 " prfm pstl1strm, %2\n" 659 old_pte = pte;
667 "1: ldxr %0, %2\n" 660 /*
668 " tst %0, %4 // check for hw dirty (!PTE_RDONLY)\n" 661 * If hardware-dirty (PTE_WRITE/DBM bit set and PTE_RDONLY
669 " csel %1, %3, xzr, eq // set PTE_DIRTY|PTE_RDONLY if dirty\n" 662 * clear), set the PTE_DIRTY and PTE_RDONLY bits.
670 " orr %0, %0, %1 // if !dirty, PTE_RDONLY is already set\n" 663 */
671 " and %0, %0, %5 // clear PTE_WRITE/PTE_DBM\n" 664 if (pte_hw_dirty(pte)) {
672 " stxr %w1, %0, %2\n" 665 pte = pte_mkdirty(pte);
673 " cbnz %w1, 1b\n" 666 pte = pte_set_rdonly(pte);
674 : "=&r" (pteval), "=&r" (tmp), "+Q" (pte_val(*ptep)) 667 }
675 : "r" (PTE_DIRTY|PTE_RDONLY), "L" (PTE_RDONLY), "L" (~PTE_WRITE) 668 pte = pte_wrprotect(pte);
676 : "cc"); 669 pte_val(pte) = cmpxchg_relaxed(&pte_val(*ptep),
670 pte_val(old_pte), pte_val(pte));
671 } while (pte_val(pte) != pte_val(old_pte));
677} 672}
678 673
679#ifdef CONFIG_TRANSPARENT_HUGEPAGE 674#ifdef CONFIG_TRANSPARENT_HUGEPAGE
diff --git a/arch/arm64/mm/fault.c b/arch/arm64/mm/fault.c
index 52ee273afeec..430eaf82da49 100644
--- a/arch/arm64/mm/fault.c
+++ b/arch/arm64/mm/fault.c
@@ -34,6 +34,7 @@
34#include <linux/hugetlb.h> 34#include <linux/hugetlb.h>
35 35
36#include <asm/bug.h> 36#include <asm/bug.h>
37#include <asm/cmpxchg.h>
37#include <asm/cpufeature.h> 38#include <asm/cpufeature.h>
38#include <asm/exception.h> 39#include <asm/exception.h>
39#include <asm/debug-monitors.h> 40#include <asm/debug-monitors.h>
@@ -197,8 +198,7 @@ int ptep_set_access_flags(struct vm_area_struct *vma,
197 unsigned long address, pte_t *ptep, 198 unsigned long address, pte_t *ptep,
198 pte_t entry, int dirty) 199 pte_t entry, int dirty)
199{ 200{
200 pteval_t old_pteval; 201 pteval_t old_pteval, pteval;
201 unsigned int tmp;
202 202
203 if (pte_same(*ptep, entry)) 203 if (pte_same(*ptep, entry))
204 return 0; 204 return 0;
@@ -208,7 +208,7 @@ int ptep_set_access_flags(struct vm_area_struct *vma,
208 208
209 /* set PTE_RDONLY if actual read-only or clean PTE */ 209 /* set PTE_RDONLY if actual read-only or clean PTE */
210 if (!pte_write(entry) || !pte_sw_dirty(entry)) 210 if (!pte_write(entry) || !pte_sw_dirty(entry))
211 pte_val(entry) |= PTE_RDONLY; 211 entry = pte_set_rdonly(entry);
212 212
213 /* 213 /*
214 * Setting the flags must be done atomically to avoid racing with the 214 * Setting the flags must be done atomically to avoid racing with the
@@ -217,16 +217,14 @@ int ptep_set_access_flags(struct vm_area_struct *vma,
217 * (calculated as: a & b == ~(~a | ~b)). 217 * (calculated as: a & b == ~(~a | ~b)).
218 */ 218 */
219 pte_val(entry) ^= PTE_RDONLY; 219 pte_val(entry) ^= PTE_RDONLY;
220 asm volatile("// ptep_set_access_flags\n" 220 pteval = READ_ONCE(pte_val(*ptep));
221 " prfm pstl1strm, %2\n" 221 do {
222 "1: ldxr %0, %2\n" 222 old_pteval = pteval;
223 " eor %0, %0, %3 // negate PTE_RDONLY in *ptep\n" 223 pteval ^= PTE_RDONLY;
224 " orr %0, %0, %4 // set flags\n" 224 pteval |= pte_val(entry);
225 " eor %0, %0, %3 // negate final PTE_RDONLY\n" 225 pteval ^= PTE_RDONLY;
226 " stxr %w1, %0, %2\n" 226 pteval = cmpxchg_relaxed(&pte_val(*ptep), old_pteval, pteval);
227 " cbnz %w1, 1b\n" 227 } while (pteval != old_pteval);
228 : "=&r" (old_pteval), "=&r" (tmp), "+Q" (pte_val(*ptep))
229 : "L" (PTE_RDONLY), "r" (pte_val(entry)));
230 228
231 flush_tlb_fix_spurious_fault(vma, address); 229 flush_tlb_fix_spurious_fault(vma, address);
232 return 1; 230 return 1;