]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - arch/arm64/mm/fault.c
arm64: mm: always take dirty state from new pte in ptep_set_access_flags
[karo-tx-linux.git] / arch / arm64 / mm / fault.c
index 92ddac1e8ca2f35b8f24028514bb761aed3591a4..4c1a118c1d096e2873aa2acdec313c13b9d36d99 100644 (file)
@@ -81,6 +81,56 @@ void show_pte(struct mm_struct *mm, unsigned long addr)
        printk("\n");
 }
 
+#ifdef CONFIG_ARM64_HW_AFDBM
+/*
+ * This function sets the access flags (dirty, accessed), as well as write
+ * permission, and only to a more permissive setting.
+ *
+ * It needs to cope with hardware update of the accessed/dirty state by other
+ * agents in the system and can safely skip the __sync_icache_dcache() call as,
+ * like set_pte_at(), the PTE is never changed from no-exec to exec here.
+ *
+ * Returns whether or not the PTE actually changed.
+ */
+int ptep_set_access_flags(struct vm_area_struct *vma,
+                         unsigned long address, pte_t *ptep,
+                         pte_t entry, int dirty)
+{
+       pteval_t old_pteval;
+       unsigned int tmp;
+
+       if (pte_same(*ptep, entry))
+               return 0;
+
+       /* only preserve the access flags and write permission */
+       pte_val(entry) &= PTE_AF | PTE_WRITE | PTE_DIRTY;
+
+       /*
+        * PTE_RDONLY is cleared by default in the asm below, so set it in
+        * back if necessary (read-only or clean PTE).
+        */
+       if (!pte_write(entry) || !pte_sw_dirty(entry))
+               pte_val(entry) |= PTE_RDONLY;
+
+       /*
+        * Setting the flags must be done atomically to avoid racing with the
+        * hardware update of the access/dirty state.
+        */
+       asm volatile("//        ptep_set_access_flags\n"
+       "       prfm    pstl1strm, %2\n"
+       "1:     ldxr    %0, %2\n"
+       "       and     %0, %0, %3              // clear PTE_RDONLY\n"
+       "       orr     %0, %0, %4              // set flags\n"
+       "       stxr    %w1, %0, %2\n"
+       "       cbnz    %w1, 1b\n"
+       : "=&r" (old_pteval), "=&r" (tmp), "+Q" (pte_val(*ptep))
+       : "L" (~PTE_RDONLY), "r" (pte_val(entry)));
+
+       flush_tlb_fix_spurious_fault(vma, address);
+       return 1;
+}
+#endif
+
 /*
  * The kernel tried to access some page that wasn't present.
  */