@@ -581,6 +581,27 @@ void contpte_clear_young_dirty_ptes(struct vm_area_struct *vma,
581581}
582582EXPORT_SYMBOL_GPL (contpte_clear_young_dirty_ptes );
583583
584+ static bool contpte_all_subptes_match_access_flags (pte_t * ptep , pte_t entry )
585+ {
586+ pte_t * cont_ptep = contpte_align_down (ptep );
587+ /*
588+ * PFNs differ per sub-PTE. Match only bits consumed by
589+ * __ptep_set_access_flags(): AF, DIRTY and write permission.
590+ */
591+ const pteval_t cmp_mask = PTE_RDONLY | PTE_AF | PTE_WRITE | PTE_DIRTY ;
592+ pteval_t entry_cmp = pte_val (entry ) & cmp_mask ;
593+ int i ;
594+
595+ for (i = 0 ; i < CONT_PTES ; i ++ ) {
596+ pteval_t pte_cmp = pte_val (__ptep_get (cont_ptep + i )) & cmp_mask ;
597+
598+ if (pte_cmp != entry_cmp )
599+ return false;
600+ }
601+
602+ return true;
603+ }
604+
584605int contpte_ptep_set_access_flags (struct vm_area_struct * vma ,
585606 unsigned long addr , pte_t * ptep ,
586607 pte_t entry , int dirty )
@@ -590,13 +611,37 @@ int contpte_ptep_set_access_flags(struct vm_area_struct *vma,
590611 int i ;
591612
592613 /*
593- * Gather the access/dirty bits for the contiguous range. If nothing has
594- * changed, its a noop.
614+ * Check whether all sub-PTEs in the CONT block already match the
615+ * requested access flags/write permission, using raw per-PTE values
616+ * rather than the gathered ptep_get() view.
617+ *
618+ * __ptep_set_access_flags() can update AF, dirty and write
619+ * permission, but only to make the mapping more permissive.
620+ *
621+ * ptep_get() gathers AF/dirty state across the whole CONT block,
622+ * which is correct for a CPU with FEAT_HAFDBS. But page-table
623+ * walkers that evaluate each descriptor individually (e.g. a CPU
624+ * without DBM support, or an SMMU without HTTU, or with HA/HD
625+ * disabled in CD.TCR) can keep faulting on the target sub-PTE if
626+ * only a sibling has been updated. Gathering can therefore cause
627+ * false no-ops when only a sibling has been updated:
628+ * - write faults: target still has PTE_RDONLY (needs PTE_RDONLY cleared)
629+ * - read faults: target still lacks PTE_AF
630+ *
631+ * Per Arm ARM (DDI 0487) D8.7.1, any sub-PTE in a CONT range may
632+ * become the effective cached translation, so all entries must have
633+ * consistent attributes. Check the full CONT block before returning
634+ * no-op, and when any sub-PTE mismatches, proceed to update the whole
635+ * range.
595636 */
596- orig_pte = pte_mknoncont (ptep_get (ptep ));
597- if (pte_val (orig_pte ) == pte_val (entry ))
637+ if (contpte_all_subptes_match_access_flags (ptep , entry ))
598638 return 0 ;
599639
640+ /*
641+ * Use raw target pte (not gathered) for write-bit unfold decision.
642+ */
643+ orig_pte = pte_mknoncont (__ptep_get (ptep ));
644+
600645 /*
601646 * We can fix up access/dirty bits without having to unfold the contig
602647 * range. But if the write bit is changing, we must unfold.
0 commit comments