arm64: mm: swap: support THP_SWAP on hardware with MTE
authorBarry Song <v-songbaohua@oppo.com>
Fri, 22 Mar 2024 11:41:36 +0000 (00:41 +1300)
committerAndrew Morton <akpm@linux-foundation.org>
Fri, 26 Apr 2024 03:56:07 +0000 (20:56 -0700)
Commit d0637c505f8a1 ("arm64: enable THP_SWAP for arm64") brings up
THP_SWAP on ARM64, but it doesn't enable THP_SWP on hardware with MTE as
the MTE code works with the assumption tags save/restore is always
handling a folio with only one page.

The limitation should be removed as more and more ARM64 SoCs have this
feature.  Co-existence of MTE and THP_SWAP becomes more and more
important.

This patch makes MTE tags saving support large folios, then we don't need
to split large folios into base pages for swapping out on ARM64 SoCs with
MTE any more.

arch_prepare_to_swap() should take folio rather than page as parameter
because we support THP swap-out as a whole.  It saves tags for all pages
in a large folio.

As now we are restoring tags based-on folio, in arch_swap_restore(), we
may increase some extra loops and early-exitings while refaulting a large
folio which is still in swapcache in do_swap_page().  In case a large
folio has nr pages, do_swap_page() will only set the PTE of the particular
page which is causing the page fault.  Thus do_swap_page() runs nr times,
and each time, arch_swap_restore() will loop nr times for those subpages
in the folio.  So right now the algorithmic complexity becomes O(nr^2).

Once we support mapping large folios in do_swap_page(), extra loops and
early-exitings will decrease while not being completely removed as a large
folio might get partially tagged in corner cases such as, 1.  a large
folio in swapcache can be partially unmapped, thus, MTE tags for the
unmapped pages will be invalidated; 2.  users might use mprotect() to set
MTEs on a part of a large folio.

arch_thp_swp_supported() is dropped since ARM64 MTE was the only one who
needed it.

Link: https://lkml.kernel.org/r/20240322114136.61386-2-21cnbao@gmail.com
Signed-off-by: Barry Song <v-songbaohua@oppo.com>
Reviewed-by: Steven Price <steven.price@arm.com>
Acked-by: Chris Li <chrisl@kernel.org>
Reviewed-by: Ryan Roberts <ryan.roberts@arm.com>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: David Hildenbrand <david@redhat.com>
Cc: Kemeng Shi <shikemeng@huaweicloud.com>
Cc: "Matthew Wilcox (Oracle)" <willy@infradead.org>
Cc: Anshuman Khandual <anshuman.khandual@arm.com>
Cc: Peter Collingbourne <pcc@google.com>
Cc: Yosry Ahmed <yosryahmed@google.com>
Cc: Peter Xu <peterx@redhat.com>
Cc: Lorenzo Stoakes <lstoakes@gmail.com>
Cc: "Mike Rapoport (IBM)" <rppt@kernel.org>
Cc: Hugh Dickins <hughd@google.com>
Cc: "Aneesh Kumar K.V" <aneesh.kumar@linux.ibm.com>
Cc: Rick Edgecombe <rick.p.edgecombe@intel.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
arch/arm64/include/asm/pgtable.h
arch/arm64/mm/mteswap.c
include/linux/huge_mm.h
include/linux/pgtable.h
mm/internal.h
mm/memory.c
mm/page_io.c
mm/shmem.c
mm/swap_slots.c
mm/swapfile.c

index 6870b60158fc8bd2810b88291f22964b6e8e19ec..9fd8613b2db2a93afebc9b313c0ad5289cd71754 100644 (file)
        __flush_tlb_range(vma, addr, end, PUD_SIZE, false, 1)
 #endif /* CONFIG_TRANSPARENT_HUGEPAGE */
 
-static inline bool arch_thp_swp_supported(void)
-{
-       return !system_supports_mte();
-}
-#define arch_thp_swp_supported arch_thp_swp_supported
-
 /*
  * Outside of a few very special situations (e.g. hibernation), we always
  * use broadcast TLB invalidation instructions, therefore a spurious page
@@ -1282,12 +1276,7 @@ static inline pmd_t pmdp_establish(struct vm_area_struct *vma,
 #ifdef CONFIG_ARM64_MTE
 
 #define __HAVE_ARCH_PREPARE_TO_SWAP
-static inline int arch_prepare_to_swap(struct page *page)
-{
-       if (system_supports_mte())
-               return mte_save_tags(page);
-       return 0;
-}
+extern int arch_prepare_to_swap(struct folio *folio);
 
 #define __HAVE_ARCH_SWAP_INVALIDATE
 static inline void arch_swap_invalidate_page(int type, pgoff_t offset)
@@ -1303,11 +1292,7 @@ static inline void arch_swap_invalidate_area(int type)
 }
 
 #define __HAVE_ARCH_SWAP_RESTORE
-static inline void arch_swap_restore(swp_entry_t entry, struct folio *folio)
-{
-       if (system_supports_mte())
-               mte_restore_tags(entry, &folio->page);
-}
+extern void arch_swap_restore(swp_entry_t entry, struct folio *folio);
 
 #endif /* CONFIG_ARM64_MTE */
 
index a31833e3ddc544c88abe1ef4a8ef6d292cd2bd8d..63e8d72f202a3b2848e4cf1e57d424e30d65f768 100644 (file)
@@ -68,6 +68,13 @@ void mte_invalidate_tags(int type, pgoff_t offset)
        mte_free_tag_storage(tags);
 }
 
+static inline void __mte_invalidate_tags(struct page *page)
+{
+       swp_entry_t entry = page_swap_entry(page);
+
+       mte_invalidate_tags(swp_type(entry), swp_offset(entry));
+}
+
 void mte_invalidate_tags_area(int type)
 {
        swp_entry_t entry = swp_entry(type, 0);
@@ -83,3 +90,41 @@ void mte_invalidate_tags_area(int type)
        }
        xa_unlock(&mte_pages);
 }
+
+int arch_prepare_to_swap(struct folio *folio)
+{
+       long i, nr;
+       int err;
+
+       if (!system_supports_mte())
+               return 0;
+
+       nr = folio_nr_pages(folio);
+
+       for (i = 0; i < nr; i++) {
+               err = mte_save_tags(folio_page(folio, i));
+               if (err)
+                       goto out;
+       }
+       return 0;
+
+out:
+       while (i--)
+               __mte_invalidate_tags(folio_page(folio, i));
+       return err;
+}
+
+void arch_swap_restore(swp_entry_t entry, struct folio *folio)
+{
+       long i, nr;
+
+       if (!system_supports_mte())
+               return;
+
+       nr = folio_nr_pages(folio);
+
+       for (i = 0; i < nr; i++) {
+               mte_restore_tags(entry, folio_page(folio, i));
+               entry.val++;
+       }
+}
index 0e16451adaba3aed829706c5e25cbc45829885b3..7576025db55df24b12cf98aae1206a7e9b0e45bb 100644 (file)
@@ -532,16 +532,4 @@ static inline int split_folio_to_order(struct folio *folio, int new_order)
 #define split_folio_to_list(f, l) split_folio_to_list_to_order(f, l, 0)
 #define split_folio(f) split_folio_to_order(f, 0)
 
-/*
- * archs that select ARCH_WANTS_THP_SWAP but don't support THP_SWP due to
- * limitations in the implementation like arm64 MTE can override this to
- * false
- */
-#ifndef arch_thp_swp_supported
-static inline bool arch_thp_swp_supported(void)
-{
-       return true;
-}
-#endif
-
 #endif /* _LINUX_HUGE_MM_H */
index 2a1c044ae4672e3ce5d98347eb7925dbb5c7169f..600e17d036599be32fd94a00bb4ecd7e335bfeb0 100644 (file)
@@ -1050,7 +1050,7 @@ static inline int arch_unmap_one(struct mm_struct *mm,
  * prototypes must be defined in the arch-specific asm/pgtable.h file.
  */
 #ifndef __HAVE_ARCH_PREPARE_TO_SWAP
-static inline int arch_prepare_to_swap(struct page *page)
+static inline int arch_prepare_to_swap(struct folio *folio)
 {
        return 0;
 }
index 63bdac6d04130364532bda7380b765c1f5b92895..6c8d3844b6a3b76aa8474ad922db9112aab83bfb 100644 (file)
@@ -76,6 +76,20 @@ static inline int folio_nr_pages_mapped(struct folio *folio)
        return atomic_read(&folio->_nr_pages_mapped) & FOLIO_PAGES_MAPPED;
 }
 
+/*
+ * Retrieve the first entry of a folio based on a provided entry within the
+ * folio. We cannot rely on folio->swap as there is no guarantee that it has
+ * been initialized. Used for calling arch_swap_restore()
+ */
+static inline swp_entry_t folio_swap(swp_entry_t entry, struct folio *folio)
+{
+       swp_entry_t swap = {
+               .val = ALIGN_DOWN(entry.val, folio_nr_pages(folio)),
+       };
+
+       return swap;
+}
+
 static inline void *folio_raw_mapping(struct folio *folio)
 {
        unsigned long mapping = (unsigned long)folio->mapping;
index c859a09b4f722e271a5d637c4b8b886ea031c3e7..805cebb6fd72eacf8e35e72611e50fceaa75a50f 100644 (file)
@@ -4190,7 +4190,7 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)
         * when reading from swap. This metadata may be indexed by swap entry
         * so this must be called before swap_free().
         */
-       arch_swap_restore(entry, folio);
+       arch_swap_restore(folio_swap(entry, folio), folio);
 
        /*
         * Remove the swap entry and conditionally try to free up the swapcache.
index ae2b49055e435713d75bed34cf1c58168925aa45..a9a7c236aeccfba81ce34ec7638f67d8ce1cd7c8 100644 (file)
@@ -189,7 +189,7 @@ int swap_writepage(struct page *page, struct writeback_control *wbc)
         * Arch code may have to preserve more data than just the page
         * contents, e.g. memory tags.
         */
-       ret = arch_prepare_to_swap(&folio->page);
+       ret = arch_prepare_to_swap(folio);
        if (ret) {
                folio_mark_dirty(folio);
                folio_unlock(folio);
index 94ab99b6b574a461e34bb875fdec497ad24728ce..98985179f495d0b1797e1d3e48109e353f99756a 100644 (file)
@@ -1907,7 +1907,7 @@ static int shmem_swapin_folio(struct inode *inode, pgoff_t index,
         * Some architectures may have to restore extra metadata to the
         * folio after reading from swap.
         */
-       arch_swap_restore(swap, folio);
+       arch_swap_restore(folio_swap(swap, folio), folio);
 
        if (shmem_should_replace_folio(folio, gfp)) {
                error = shmem_replace_folio(&folio, gfp, info, index);
index 90973ce7881db2a65c38d21b7b2fad28e60ade36..53abeaf1371d07c60a371e0b2f1e4182b0a74d11 100644 (file)
@@ -310,7 +310,7 @@ swp_entry_t folio_alloc_swap(struct folio *folio)
        entry.val = 0;
 
        if (folio_test_large(folio)) {
-               if (IS_ENABLED(CONFIG_THP_SWAP) && arch_thp_swp_supported())
+               if (IS_ENABLED(CONFIG_THP_SWAP))
                        get_swap_pages(1, &entry, folio_nr_pages(folio));
                goto out;
        }
index 4919423cce76a3a44a2f26e6f31e2fd7c3577e0a..5e6d2304a2a47d6f2dca7b29410864f065dcf05c 100644 (file)
@@ -1806,7 +1806,7 @@ static int unuse_pte(struct vm_area_struct *vma, pmd_t *pmd,
         * when reading from swap. This metadata may be indexed by swap entry
         * so this must be called before swap_free().
         */
-       arch_swap_restore(entry, folio);
+       arch_swap_restore(folio_swap(entry, folio), folio);
 
        dec_mm_counter(vma->vm_mm, MM_SWAPENTS);
        inc_mm_counter(vma->vm_mm, MM_ANONPAGES);