All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 1/3] mm/huge_memory: add folio_split_driver_managed()
@ 2026-07-22  4:42 Matthew Brost
  2026-07-22  4:42 ` [PATCH 2/3] drm/xe: select TRANSPARENT_HUGEPAGE Matthew Brost
                   ` (3 more replies)
  0 siblings, 4 replies; 8+ messages in thread
From: Matthew Brost @ 2026-07-22  4:42 UTC (permalink / raw)
  To: intel-xe, dri-devel, linux-mm, linux-kernel
  Cc: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Christian Koenig, Huang Rui, Matthew Auld,
	Andrew Morton, David Hildenbrand, Lorenzo Stoakes, Zi Yan,
	Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts, Dev Jain,
	Barry Song, Lance Yang, Tvrtko Ursulin, Dave Airlie,
	Matthew Wilcox

Add a lightweight structural split primitive for large (compound) folios
that a driver allocated with __GFP_COMP and manages entirely by itself,
outside of the core mm's view.

The existing split paths - split_folio() and folio_split_unmapped() -
are built for folios that the mm owns: they perform a refcount freeze,
walk and remap the rmap, and take the anon_vma / i_mmap locks, and
folio_split_unmapped() further assumes an anon, pagecache-style refcount
model (nr_pages + 1). None of that applies to a folio that is:

  - singly referenced (the caller holds the only reference),
  - not mapped through the rmap (folio_mapped() == 0),
  - not in the page cache or swap cache (folio->mapping == NULL),
  - not on any LRU or the deferred-split list.

For such a folio the split is purely structural: because nothing else in
the kernel can reach it, there is no need to freeze the refcount or touch
any mapping. folio_split_driver_managed() therefore performs only the
compound and split-accounting teardown via __split_unmapped_folio() and
then hands each resulting order-@new_order folio its own reference,
mirroring split_page() for compound folios. The caller keeps the original
reference on the first resulting folio and is responsible for freeing all
of them individually.

The immediate user is TTM's GPU page pool, which allocates higher-order
compound pages, maps them into userspace via VM_PFNMAP (never through the
rmap), and needs to split them into order-0 folios under memory pressure
so pages can be backed up to shmem and freed one at a time.

A CONFIG_TRANSPARENT_HUGEPAGE=n stub is provided so callers can build
without the split machinery; it warns and returns -EINVAL.

Cc: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Cc: Maxime Ripard <mripard@kernel.org>
Cc: Thomas Zimmermann <tzimmermann@suse.de>
Cc: David Airlie <airlied@gmail.com>
Cc: Simona Vetter <simona@ffwll.ch>
Cc: Christian Koenig <christian.koenig@amd.com>
Cc: Huang Rui <ray.huang@amd.com>
Cc: Matthew Auld <matthew.auld@intel.com>
Cc: Matthew Brost <matthew.brost@intel.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: David Hildenbrand <david@kernel.org>
Cc: Lorenzo Stoakes <ljs@kernel.org>
Cc: Zi Yan <ziy@nvidia.com>
Cc: Baolin Wang <baolin.wang@linux.alibaba.com>
Cc: "Liam R. Howlett" <liam@infradead.org>
Cc: Nico Pache <npache@redhat.com>
Cc: Ryan Roberts <ryan.roberts@arm.com>
Cc: Dev Jain <dev.jain@arm.com>
Cc: Barry Song <baohua@kernel.org>
Cc: Lance Yang <lance.yang@linux.dev>
Cc: Tvrtko Ursulin <tvrtko.ursulin@igalia.com>
Cc: Dave Airlie <airlied@redhat.com>
Cc: dri-devel@lists.freedesktop.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-mm@kvack.org
Suggested-by: Matthew Wilcox <willy@infradead.org>
Signed-off-by: Matthew Brost <matthew.brost@intel.com>
Assisted-by: GitHub-Copilot:claude-opus-4.8

---

The patch is based on drm-tip rather than the core MM branches to
facilitate Intel CI testing and initial review. It can be rebased onto
the core MM branches in a subsequent revision.
---
 include/linux/huge_mm.h |  8 ++++++
 mm/huge_memory.c        | 63 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 71 insertions(+)

diff --git a/include/linux/huge_mm.h b/include/linux/huge_mm.h
index ad20f7f8c179..35661d82d54a 100644
--- a/include/linux/huge_mm.h
+++ b/include/linux/huge_mm.h
@@ -402,6 +402,7 @@ enum split_type {
 int __split_huge_page_to_list_to_order(struct page *page, struct list_head *list,
 		unsigned int new_order);
 int folio_split_unmapped(struct folio *folio, unsigned int new_order);
+int folio_split_driver_managed(struct folio *folio, unsigned int new_order);
 unsigned int min_order_for_split(struct folio *folio);
 int split_folio_to_list(struct folio *folio, struct list_head *list);
 int folio_check_splittable(struct folio *folio, unsigned int new_order,
@@ -656,6 +657,13 @@ static inline int split_folio_to_list(struct folio *folio, struct list_head *lis
 	return -EINVAL;
 }
 
+static inline int folio_split_driver_managed(struct folio *folio,
+					     unsigned int new_order)
+{
+	VM_WARN_ON_ONCE_FOLIO(1, folio);
+	return -EINVAL;
+}
+
 static inline int folio_split(struct folio *folio, unsigned int new_order,
 		struct page *page, struct list_head *list)
 {
diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 2bccb0a53a0a..06f9a5f35df8 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -4185,6 +4185,69 @@ int folio_split_unmapped(struct folio *folio, unsigned int new_order)
 	return ret;
 }
 
+/**
+ * folio_split_driver_managed() - split an exclusively-owned, off-LRU folio
+ * @folio: folio to split. Must be a large (compound) folio that is owned
+ *         exclusively by the caller and is invisible to the core mm.
+ * @new_order: the order of the folios after the split.
+ *
+ * This is a lightweight structural split for folios that a driver allocated
+ * and manages itself (for example TTM's GPU page pool, which allocates
+ * higher-order compound pages with __GFP_COMP and maps them into userspace
+ * via VM_PFNMAP rather than through the rmap). Such folios are:
+ *
+ *   - singly referenced (the caller holds the only reference),
+ *   - not mapped through the rmap (folio_mapcount() == 0),
+ *   - not in the page cache or swap cache (folio->mapping == NULL),
+ *   - not on any LRU or the deferred-split list.
+ *
+ * Because nothing else in the kernel can reach the folio, this helper does
+ * not perform the refcount freeze / remap / anon_vma & i_mmap locking dance
+ * that split_folio() and folio_split_unmapped() require. It performs only
+ * the compound and split-accounting teardown and then hands each resulting
+ * folio its own reference, mirroring split_page() for compound folios.
+ *
+ * The caller is responsible for freeing the resulting folios individually.
+ *
+ * Context: caller holds the only reference and excludes concurrent access.
+ * Does not sleep.
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+int folio_split_driver_managed(struct folio *folio, unsigned int new_order)
+{
+	unsigned int old_order = folio_order(folio);
+	unsigned int split_nr = 1U << new_order;
+	unsigned int nr = 1U << old_order;
+	unsigned int i;
+
+	if (new_order >= old_order)
+		return -EINVAL;
+
+	VM_WARN_ON_ONCE_FOLIO(folio_ref_count(folio) != 1, folio);
+	VM_WARN_ON_ONCE_FOLIO(folio_mapped(folio), folio);
+	VM_WARN_ON_ONCE_FOLIO(folio->mapping, folio);
+	VM_WARN_ON_ONCE_FOLIO(folio_test_swapcache(folio), folio);
+	VM_WARN_ON_ONCE_FOLIO(folio_test_lru(folio), folio);
+
+	/*
+	 * Structural + split-accounting teardown only. No mapping/xarray, no
+	 * refcount freeze: the folio is frozen-by-ownership already.
+	 */
+	__split_unmapped_folio(folio, new_order, &folio->page, NULL, NULL,
+			       SPLIT_TYPE_UNIFORM);
+
+	/*
+	 * Give every resulting head folio its own reference. The original
+	 * reference stays on the first one, exactly like split_page().
+	 */
+	for (i = split_nr; i < nr; i += split_nr)
+		set_page_refcounted(folio_page(folio, i));
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(folio_split_driver_managed);
+
 /*
  * This function splits a large folio into smaller folios of order @new_order.
  * @page can point to any page of the large folio to split. The split operation
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 2/3] drm/xe: select TRANSPARENT_HUGEPAGE
  2026-07-22  4:42 [PATCH 1/3] mm/huge_memory: add folio_split_driver_managed() Matthew Brost
@ 2026-07-22  4:42 ` Matthew Brost
  2026-07-22  4:48   ` sashiko-bot
  2026-07-22  4:42 ` [PATCH 3/3] drm/ttm: allocate pool pages as compound (__GFP_COMP) Matthew Brost
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 8+ messages in thread
From: Matthew Brost @ 2026-07-22  4:42 UTC (permalink / raw)
  To: intel-xe, dri-devel, linux-mm, linux-kernel
  Cc: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Christian Koenig, Huang Rui, Matthew Auld,
	Andrew Morton, David Hildenbrand, Lorenzo Stoakes, Zi Yan,
	Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts, Dev Jain,
	Barry Song, Lance Yang, Tvrtko Ursulin, Dave Airlie,
	Matthew Wilcox

Xe is currently the only TTM driver that drives the TTM shrinker, and
hence the only user of TTM's incremental swap path. That path splits the
pool's higher-order compound folios one page at a time via
folio_split_driver_managed() so individual pages can be backed up to
shmem and freed under memory pressure.

folio_split_driver_managed() is part of the transparent hugepage split
machinery in mm/huge_memory.c, which is only built when
CONFIG_TRANSPARENT_HUGEPAGE is enabled. Select it from DRM_XE so the
split primitive is always available for the driver that needs it, rather
than forcing it on every TTM user.

Cc: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Cc: Maxime Ripard <mripard@kernel.org>
Cc: Thomas Zimmermann <tzimmermann@suse.de>
Cc: David Airlie <airlied@gmail.com>
Cc: Simona Vetter <simona@ffwll.ch>
Cc: Christian Koenig <christian.koenig@amd.com>
Cc: Huang Rui <ray.huang@amd.com>
Cc: Matthew Auld <matthew.auld@intel.com>
Cc: Matthew Brost <matthew.brost@intel.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: David Hildenbrand <david@kernel.org>
Cc: Lorenzo Stoakes <ljs@kernel.org>
Cc: Zi Yan <ziy@nvidia.com>
Cc: Baolin Wang <baolin.wang@linux.alibaba.com>
Cc: "Liam R. Howlett" <liam@infradead.org>
Cc: Nico Pache <npache@redhat.com>
Cc: Ryan Roberts <ryan.roberts@arm.com>
Cc: Dev Jain <dev.jain@arm.com>
Cc: Barry Song <baohua@kernel.org>
Cc: Lance Yang <lance.yang@linux.dev>
Cc: Tvrtko Ursulin <tvrtko.ursulin@igalia.com>
Cc: Dave Airlie <airlied@redhat.com>
Cc: Matthew Wilcox <willy@infradead.org>
Cc: dri-devel@lists.freedesktop.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-mm@kvack.org
Signed-off-by: Matthew Brost <matthew.brost@intel.com>
Assisted-by: GitHub-Copilot:claude-opus-4.8
---
 drivers/gpu/drm/xe/Kconfig | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/drivers/gpu/drm/xe/Kconfig b/drivers/gpu/drm/xe/Kconfig
index 4d7dcaff2b91..88a4f3d09d5f 100644
--- a/drivers/gpu/drm/xe/Kconfig
+++ b/drivers/gpu/drm/xe/Kconfig
@@ -11,6 +11,12 @@ config DRM_XE
 	# the shmem_readpage() which depends upon tmpfs
 	select SHMEM
 	select TMPFS
+	# Xe is the only TTM driver that exercises the TTM shrinker's
+	# incremental swap path, which splits the pool's compound folios one
+	# page at a time via folio_split_driver_managed(). That helper lives
+	# in the THP split machinery, so pull it in here rather than for all
+	# TTM users.
+	select TRANSPARENT_HUGEPAGE
 	select DRM_BUDDY
 	select DRM_CLIENT_SELECTION
 	select DRM_KMS_HELPER
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 3/3] drm/ttm: allocate pool pages as compound (__GFP_COMP)
  2026-07-22  4:42 [PATCH 1/3] mm/huge_memory: add folio_split_driver_managed() Matthew Brost
  2026-07-22  4:42 ` [PATCH 2/3] drm/xe: select TRANSPARENT_HUGEPAGE Matthew Brost
@ 2026-07-22  4:42 ` Matthew Brost
  2026-07-22  4:58   ` sashiko-bot
  2026-07-22  4:48 ` ✗ CI.KUnit: failure for series starting with [1/3] mm/huge_memory: add folio_split_driver_managed() Patchwork
  2026-07-22 14:26 ` [PATCH 1/3] " Zi Yan
  3 siblings, 1 reply; 8+ messages in thread
From: Matthew Brost @ 2026-07-22  4:42 UTC (permalink / raw)
  To: intel-xe, dri-devel, linux-mm, linux-kernel
  Cc: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Christian Koenig, Huang Rui, Matthew Auld,
	Andrew Morton, David Hildenbrand, Lorenzo Stoakes, Zi Yan,
	Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts, Dev Jain,
	Barry Song, Lance Yang, Tvrtko Ursulin, Dave Airlie,
	Matthew Wilcox

Historically ttm_pool_alloc_page() deliberately avoided __GFP_COMP for
higher-order allocations and instead stashed the allocation order in
page->private. The stated reason was that mapping a TTM page into a
userspace process and having that process call put_page() on it would be
illegal on a compound page, because the stray reference would fold into
the compound head and could free the whole block behind TTM's back.

That hazard no longer applies to the non-DMA path. TTM faults its pages
into userspace via VM_PFNMAP (see ttm_bo_vm_fault_reserved() /
vmf_insert_pfn_prot()), so the core mm never takes a struct page
reference on them and GUP rejects the range; a stray userspace put_page()
cannot reach these pages at all.

Convert the !ttm_pool_uses_dma_alloc() path to allocate compound pages
with __GFP_COMP and recover the order via folio_order() instead of
page->private:

  - ttm_pool_alloc_page(): add __GFP_COMP, drop the page->private write.
  - ttm_pool_page_order() / ttm_pool_unmap_and_free(): read the order
    from folio_order() for the non-DMA case.
  - ttm_pool_split_for_swap(): for compound folios, split via the new
    folio_split_driver_managed() helper rather than split_page(), which
    rejects compound pages. Each resulting order-0 folio is then freed
    individually as it is backed up.
  - Drop the now-dead page->private = 0 clears on the purge and
    full-backup free paths.

The DMA path is intentionally left unchanged: dma_alloc_attrs() does not
produce compound pages, so it keeps split_page() and the page->private
order stash.

folio_split_driver_managed() lives in the THP split machinery
(mm/huge_memory.c), which only builds when CONFIG_TRANSPARENT_HUGEPAGE
is enabled. Drivers that drive the TTM shrinker and therefore reach the
split path must select TRANSPARENT_HUGEPAGE.

Cc: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Cc: Maxime Ripard <mripard@kernel.org>
Cc: Thomas Zimmermann <tzimmermann@suse.de>
Cc: David Airlie <airlied@gmail.com>
Cc: Simona Vetter <simona@ffwll.ch>
Cc: Christian Koenig <christian.koenig@amd.com>
Cc: Huang Rui <ray.huang@amd.com>
Cc: Matthew Auld <matthew.auld@intel.com>
Cc: Matthew Brost <matthew.brost@intel.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: David Hildenbrand <david@kernel.org>
Cc: Lorenzo Stoakes <ljs@kernel.org>
Cc: Zi Yan <ziy@nvidia.com>
Cc: Baolin Wang <baolin.wang@linux.alibaba.com>
Cc: "Liam R. Howlett" <liam@infradead.org>
Cc: Nico Pache <npache@redhat.com>
Cc: Ryan Roberts <ryan.roberts@arm.com>
Cc: Dev Jain <dev.jain@arm.com>
Cc: Barry Song <baohua@kernel.org>
Cc: Lance Yang <lance.yang@linux.dev>
Cc: Tvrtko Ursulin <tvrtko.ursulin@igalia.com>
Cc: Dave Airlie <airlied@redhat.com>
Cc: dri-devel@lists.freedesktop.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-mm@kvack.org
Suggested-by: Matthew Wilcox <willy@infradead.org>
Signed-off-by: Matthew Brost <matthew.brost@intel.com>
Assisted-by: GitHub-Copilot:claude-opus-4.8
---
 drivers/gpu/drm/ttm/tests/ttm_pool_test.c | 23 +++++++++---
 drivers/gpu/drm/ttm/ttm_backup.c          |  8 ++--
 drivers/gpu/drm/ttm/ttm_pool.c            | 46 +++++++++++++++--------
 3 files changed, 53 insertions(+), 24 deletions(-)

diff --git a/drivers/gpu/drm/ttm/tests/ttm_pool_test.c b/drivers/gpu/drm/ttm/tests/ttm_pool_test.c
index be75c8abf388..771ed257778c 100644
--- a/drivers/gpu/drm/ttm/tests/ttm_pool_test.c
+++ b/drivers/gpu/drm/ttm/tests/ttm_pool_test.c
@@ -169,7 +169,14 @@ static void ttm_pool_alloc_basic(struct kunit *test)
 			KUNIT_ASSERT_NOT_NULL(test, (void *)fst_page->private);
 			KUNIT_ASSERT_NOT_NULL(test, (void *)last_page->private);
 		} else {
-			KUNIT_ASSERT_EQ(test, fst_page->private, params->order);
+			/*
+			 * The non-DMA path allocates compound pages, so the
+			 * order is recovered from the folio rather than from
+			 * page->private.
+			 */
+			KUNIT_ASSERT_EQ(test,
+					folio_order(page_folio(fst_page)),
+					params->order);
 		}
 	} else {
 		if (ttm_pool_uses_dma_alloc(pool)) {
@@ -177,13 +184,19 @@ static void ttm_pool_alloc_basic(struct kunit *test)
 			KUNIT_ASSERT_NULL(test, (void *)last_page->private);
 		} else {
 			/*
-			 * We expect to alloc one big block, followed by
-			 * order 0 blocks
+			 * We expect to alloc one or more max-order compound
+			 * blocks. page_folio() on any subpage resolves to the
+			 * compound head, so both the first and last pages
+			 * report the max block order.
 			 */
-			KUNIT_ASSERT_EQ(test, fst_page->private,
+			KUNIT_ASSERT_EQ(test,
+					folio_order(page_folio(fst_page)),
+					min_t(unsigned int, MAX_PAGE_ORDER,
+					      params->order));
+			KUNIT_ASSERT_EQ(test,
+					folio_order(page_folio(last_page)),
 					min_t(unsigned int, MAX_PAGE_ORDER,
 					      params->order));
-			KUNIT_ASSERT_EQ(test, last_page->private, 0);
 		}
 	}
 
diff --git a/drivers/gpu/drm/ttm/ttm_backup.c b/drivers/gpu/drm/ttm/ttm_backup.c
index 3c067aadc52d..9194747a1dff 100644
--- a/drivers/gpu/drm/ttm/ttm_backup.c
+++ b/drivers/gpu/drm/ttm/ttm_backup.c
@@ -72,9 +72,11 @@ int ttm_backup_copy_page(struct file *backup, struct page *dst,
  * ttm_backup_backup_folio() - Backup a folio
  * @backup: The struct backup pointer to use.
  * @folio: The folio to back up.
- * @order: The allocation order of @folio.  Since TTM allocates higher-order
- *         pages without __GFP_COMP, folio_nr_pages(@folio) would always
- *         return 1; the caller must pass the true order explicitly.
+ * @order: The allocation order of @folio, passed explicitly. For the DMA
+ *         path TTM allocates higher-order pages without __GFP_COMP, so
+ *         folio_order(@folio) would return 0 rather than the true order;
+ *         the caller therefore passes the order explicitly. (The non-DMA
+ *         path allocates compound pages, for which the two agree.)
  * @writeback: Whether to perform immediate writeback of the folio's pages.
  * This may have performance implications.
  * @idx: A unique integer for the first page of the folio and each struct backup.
diff --git a/drivers/gpu/drm/ttm/ttm_pool.c b/drivers/gpu/drm/ttm/ttm_pool.c
index 1bf37023fed6..364cc7ec7469 100644
--- a/drivers/gpu/drm/ttm/ttm_pool.c
+++ b/drivers/gpu/drm/ttm/ttm_pool.c
@@ -168,9 +168,11 @@ static struct page *ttm_pool_alloc_page(struct ttm_pool *pool, gfp_t gfp_flags,
 	struct page *p;
 	void *vaddr;
 
-	/* Don't set the __GFP_COMP flag for higher order allocations.
-	 * Mapping pages directly into an userspace process and calling
-	 * put_page() on a TTM allocated page is illegal.
+	/*
+	 * For higher-order allocations be a good citizen: don't dip into
+	 * memory reserves, don't retry hard, don't warn on failure and stay
+	 * on the local node. The non-DMA path additionally sets __GFP_COMP
+	 * below; the DMA path allocates via dma_alloc_attrs().
 	 */
 	if (order)
 		gfp_flags |= __GFP_NOMEMALLOC | __GFP_NORETRY | __GFP_NOWARN |
@@ -189,11 +191,9 @@ static struct page *ttm_pool_alloc_page(struct ttm_pool *pool, gfp_t gfp_flags,
 	}
 
 	if (!ttm_pool_uses_dma_alloc(pool)) {
-		p = alloc_pages_node(pool->nid, gfp_flags, order);
-		if (p) {
-			p->private = order;
+		p = alloc_pages_node(pool->nid, gfp_flags | __GFP_COMP, order);
+		if (p)
 			mod_lruvec_page_state(p, NR_GPU_ACTIVE, 1 << order);
-		}
 		return p;
 	}
 
@@ -482,7 +482,7 @@ static unsigned int ttm_pool_page_order(struct ttm_pool *pool, struct page *p)
 		return dma->vaddr & ~PAGE_MASK;
 	}
 
-	return p->private;
+	return folio_order(page_folio(p));
 }
 
 /*
@@ -493,15 +493,31 @@ static unsigned int ttm_pool_page_order(struct ttm_pool *pool, struct page *p)
 static void ttm_pool_split_for_swap(struct ttm_pool *pool, struct page *p)
 {
 	unsigned int order = ttm_pool_page_order(pool, p);
-	pgoff_t nr;
 
 	if (!order)
 		return;
 
-	split_page(p, order);
-	nr = 1UL << order;
-	while (nr--)
-		(p++)->private = 0;
+	if (ttm_pool_uses_dma_alloc(pool)) {
+		pgoff_t nr;
+
+		/*
+		 * DMA-alloc pages are not compound; split the plain
+		 * higher-order allocation and clear the per-page private
+		 * (which held the order for the non-compound case).
+		 */
+		split_page(p, order);
+		nr = 1UL << order;
+		while (nr--)
+			(p++)->private = 0;
+		return;
+	}
+
+	/*
+	 * The non-DMA path allocates compound folios (__GFP_COMP). Split the
+	 * driver-owned, off-LRU, unmapped folio into order-0 folios so each
+	 * page can be freed as soon as it has been backed up.
+	 */
+	folio_split_driver_managed(page_folio(p), 0);
 }
 
 /**
@@ -548,7 +564,7 @@ static pgoff_t ttm_pool_unmap_and_free(struct ttm_pool *pool, struct page *page,
 
 		pt = ttm_pool_select_type(pool, caching, order);
 	} else {
-		order = page->private;
+		order = folio_order(page_folio(page));
 		nr = (1UL << order);
 	}
 
@@ -1124,7 +1140,6 @@ long ttm_pool_backup(struct ttm_pool *pool, struct ttm_tt *tt,
 					       num_pages);
 			if (flags->purge) {
 				shrunken += num_pages;
-				page->private = 0;
 				__free_pages_gpu_account(page, order, false);
 				memset(tt->pages + i, 0,
 				       num_pages * sizeof(*tt->pages));
@@ -1214,7 +1229,6 @@ long ttm_pool_backup(struct ttm_pool *pool, struct ttm_tt *tt,
 		}
 
 		/* Fully backed up: free at native order. */
-		page->private = 0;
 		__free_pages_gpu_account(page, order, false);
 	}
 
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* ✗ CI.KUnit: failure for series starting with [1/3] mm/huge_memory: add folio_split_driver_managed()
  2026-07-22  4:42 [PATCH 1/3] mm/huge_memory: add folio_split_driver_managed() Matthew Brost
  2026-07-22  4:42 ` [PATCH 2/3] drm/xe: select TRANSPARENT_HUGEPAGE Matthew Brost
  2026-07-22  4:42 ` [PATCH 3/3] drm/ttm: allocate pool pages as compound (__GFP_COMP) Matthew Brost
@ 2026-07-22  4:48 ` Patchwork
  2026-07-22 14:26 ` [PATCH 1/3] " Zi Yan
  3 siblings, 0 replies; 8+ messages in thread
From: Patchwork @ 2026-07-22  4:48 UTC (permalink / raw)
  To: Matthew Brost; +Cc: intel-xe

== Series Details ==

Series: series starting with [1/3] mm/huge_memory: add folio_split_driver_managed()
URL   : https://patchwork.freedesktop.org/series/170875/
State : failure

== Summary ==

+ trap cleanup EXIT
+ /kernel/tools/testing/kunit/kunit.py run --kunitconfig /kernel/drivers/gpu/drm/xe/.kunitconfig
ERROR:root:In file included from ../include/linux/mm.h:31,
                 from ../include/linux/pid_namespace.h:7,
                 from ../include/linux/ptrace.h:10,
                 from ../include/linux/audit.h:13,
                 from ../arch/um/kernel/asm-offsets.c:8:
../include/linux/pgtable.h: In function ‘pmdp_test_and_clear_young’:
../include/linux/pgtable.h:553:17: error: implicit declaration of function ‘set_pmd_at’; did you mean ‘set_pte_at’? [-Werror=implicit-function-declaration]
  553 |                 set_pmd_at(vma->vm_mm, address, pmdp, pmd_mkold(pmd));
      |                 ^~~~~~~~~~
      |                 set_pte_at
../include/linux/pgtable.h:553:55: error: implicit declaration of function ‘pmd_mkold’; did you mean ‘pte_mkold’? [-Werror=implicit-function-declaration]
  553 |                 set_pmd_at(vma->vm_mm, address, pmdp, pmd_mkold(pmd));
      |                                                       ^~~~~~~~~
      |                                                       pte_mkold
../include/linux/pgtable.h: In function ‘pmd_mkwrite’:
../include/linux/pgtable.h:1060:16: error: implicit declaration of function ‘pmd_mkwrite_novma’; did you mean ‘pte_mkwrite_novma’? [-Werror=implicit-function-declaration]
 1060 |         return pmd_mkwrite_novma(pmd);
      |                ^~~~~~~~~~~~~~~~~
      |                pte_mkwrite_novma
../include/linux/pgtable.h:1060:16: error: incompatible types when returning type ‘int’ but ‘pmd_t’ was expected
 1060 |         return pmd_mkwrite_novma(pmd);
      |                ^~~~~~~~~~~~~~~~~~~~~~
../include/linux/pgtable.h: In function ‘pmdp_set_wrprotect’:
../include/linux/pgtable.h:1241:39: error: implicit declaration of function ‘pmd_wrprotect’; did you mean ‘pte_wrprotect’? [-Werror=implicit-function-declaration]
 1241 |         set_pmd_at(mm, address, pmdp, pmd_wrprotect(old_pmd));
      |                                       ^~~~~~~~~~~~~
      |                                       pte_wrprotect
In file included from ../include/linux/mm.h:1790:
../include/linux/huge_mm.h: In function ‘pmd_is_huge’:
../include/linux/huge_mm.h:449:24: error: implicit declaration of function ‘pmd_trans_huge’; did you mean ‘pud_trans_huge’? [-Werror=implicit-function-declaration]
  449 |                 return pmd_trans_huge(pmd);
      |                        ^~~~~~~~~~~~~~
      |                        pud_trans_huge
../include/linux/mm.h: In function ‘folio_mk_pmd’:
../include/linux/mm.h:2608:16: error: implicit declaration of function ‘pmd_mkhuge’; did you mean ‘pmd_is_huge’? [-Werror=implicit-function-declaration]
 2608 |         return pmd_mkhuge(pfn_pmd(folio_pfn(folio), pgprot));
      |                ^~~~~~~~~~
      |                pmd_is_huge
../include/linux/mm.h:2608:16: error: incompatible types when returning type ‘int’ but ‘pmd_t’ was expected
 2608 |         return pmd_mkhuge(pfn_pmd(folio_pfn(folio), pgprot));
      |                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
cc1: some warnings being treated as errors
make[3]: *** [../scripts/Makefile.build:184: arch/um/kernel/asm-offsets.s] Error 1
make[2]: *** [/kernel/Makefile:1405: prepare0] Error 2
make[1]: *** [/kernel/Makefile:248: __sub-make] Error 2
make: *** [Makefile:248: __sub-make] Error 2

[04:47:55] Configuring KUnit Kernel ...
Generating .config ...
Populating config with:
$ make ARCH=um O=.kunit olddefconfig
[04:48:00] Building KUnit Kernel ...
Populating config with:
$ make ARCH=um O=.kunit olddefconfig
Building with:
$ make all compile_commands.json scripts_gdb ARCH=um O=.kunit --jobs=48
+ cleanup
++ stat -c %u:%g /kernel
+ chown -R 1003:1003 /kernel



^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 2/3] drm/xe: select TRANSPARENT_HUGEPAGE
  2026-07-22  4:42 ` [PATCH 2/3] drm/xe: select TRANSPARENT_HUGEPAGE Matthew Brost
@ 2026-07-22  4:48   ` sashiko-bot
  0 siblings, 0 replies; 8+ messages in thread
From: sashiko-bot @ 2026-07-22  4:48 UTC (permalink / raw)
  To: Matthew Brost; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] DRM_XE unconditionally selects TRANSPARENT_HUGEPAGE without inheriting its architecture and PREEMPT_RT dependencies.
--

--- Patch [2]: [PATCH 2/3] drm/xe: select TRANSPARENT_HUGEPAGE ---
commit 039323cec9a41618d3b6161e1831b08e7ba4069a
Author: Matthew Brost <matthew.brost@intel.com>

drm/xe: select TRANSPARENT_HUGEPAGE

This commit selects TRANSPARENT_HUGEPAGE for the DRM_XE driver to ensure
the THP split machinery is available for Xe's TTM shrinker incremental
swap path.

> diff --git a/drivers/gpu/drm/xe/Kconfig b/drivers/gpu/drm/xe/Kconfig
> index 4d7dcaff2b915..88a4f3d09d5fe 100644
> --- a/drivers/gpu/drm/xe/Kconfig
> +++ b/drivers/gpu/drm/xe/Kconfig
> @@ -11,6 +11,12 @@ config DRM_XE
>  	# the shmem_readpage() which depends upon tmpfs
>  	select SHMEM
>  	select TMPFS
> +	# Xe is the only TTM driver that exercises the TTM shrinker's
> +	# incremental swap path, which splits the pool's compound folios one
> +	# page at a time via folio_split_driver_managed(). That helper lives
> +	# in the THP split machinery, so pull it in here rather than for all
> +	# TTM users.
> +	select TRANSPARENT_HUGEPAGE

[Severity: Low]
Does this select statement need to inherit the dependencies for
TRANSPARENT_HUGEPAGE?

TRANSPARENT_HUGEPAGE has specific architecture dependencies (like
HAVE_ARCH_TRANSPARENT_HUGEPAGE) and can conflict with PREEMPT_RT. Selecting
a config symbol without inheriting its dependencies can cause Kconfig unmet
dependency warnings and potential build failures on configurations where
those dependencies are not met (for example, on non-x86 architectures when
building with COMPILE_TEST).

Should this use a conditional selection (e.g., select TRANSPARENT_HUGEPAGE
if HAVE_ARCH_TRANSPARENT_HUGEPAGE) or be changed to a "depends on" to
ensure it is only pulled in when its requirements are satisfied?

>  	select DRM_BUDDY
>  	select DRM_CLIENT_SELECTION
>  	select DRM_KMS_HELPER

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260722044220.1110278-1-matthew.brost@intel.com?part=2

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 3/3] drm/ttm: allocate pool pages as compound (__GFP_COMP)
  2026-07-22  4:42 ` [PATCH 3/3] drm/ttm: allocate pool pages as compound (__GFP_COMP) Matthew Brost
@ 2026-07-22  4:58   ` sashiko-bot
  0 siblings, 0 replies; 8+ messages in thread
From: sashiko-bot @ 2026-07-22  4:58 UTC (permalink / raw)
  To: Matthew Brost; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Critical] ttm_pool_split_for_swap() ignores the return value of folio_split_driver_managed(), leading to severe buddy allocator corruption if the split fails.
- [High] Kconfig hazard: Forcing TRANSPARENT_HUGEPAGE via select bypasses critical architectural dependencies.
--

--- Patch [3]: [PATCH 3/3] drm/ttm: allocate pool pages as compound (__GFP_COMP) ---
commit dae665417eeff981f7bae0e75da08ad47a4dd0a0
Author: Matthew Brost <matthew.brost@intel.com>

drm/ttm: allocate pool pages as compound (__GFP_COMP)

This commit changes the non-DMA path in ttm_pool_alloc_page() to allocate
compound pages with __GFP_COMP instead of stashing the order in page->private.
It updates the backup, restore, and free paths to read the order via
folio_order() and split compound folios using folio_split_driver_managed().

> folio_split_driver_managed() lives in the THP split machinery
> (mm/huge_memory.c), which only builds when CONFIG_TRANSPARENT_HUGEPAGE
> is enabled. Drivers that drive the TTM shrinker and therefore reach the
> split path must select TRANSPARENT_HUGEPAGE.

[Severity: High]
Is it safe to force select TRANSPARENT_HUGEPAGE in drivers?

The Kconfig select directive forcefully enables the target symbol without
evaluating its dependencies. TRANSPARENT_HUGEPAGE strictly depends on
HAVE_ARCH_TRANSPARENT_HUGEPAGE and !PREEMPT_RT. 

Using select could force THP on architectures that lack support, breaking
the build, or bypass the !PREEMPT_RT dependency and violate strict latency
requirements.

> diff --git a/drivers/gpu/drm/ttm/ttm_pool.c b/drivers/gpu/drm/ttm/ttm_pool.c
> index 1bf37023fed66..364cc7ec7469c 100644
> --- a/drivers/gpu/drm/ttm/ttm_pool.c
> +++ b/drivers/gpu/drm/ttm/ttm_pool.c
[ ... ]
> @@ -493,15 +493,31 @@ static unsigned int ttm_pool_page_order(struct ttm_pool *pool, struct page *p)
>  static void ttm_pool_split_for_swap(struct ttm_pool *pool, struct page *p)
>  {
>  	unsigned int order = ttm_pool_page_order(pool, p);
>  
>  	if (!order)
>  		return;
>  
> -	split_page(p, order);
> -	nr = 1UL << order;
> -	while (nr--)
> -		(p++)->private = 0;
> +	if (ttm_pool_uses_dma_alloc(pool)) {
> +		pgoff_t nr;
> +
> +		/*
> +		 * DMA-alloc pages are not compound; split the plain
> +		 * higher-order allocation and clear the per-page private
> +		 * (which held the order for the non-compound case).
> +		 */
> +		split_page(p, order);
> +		nr = 1UL << order;
> +		while (nr--)
> +			(p++)->private = 0;
> +		return;
> +	}
> +
> +	/*
> +	 * The non-DMA path allocates compound folios (__GFP_COMP). Split the
> +	 * driver-owned, off-LRU, unmapped folio into order-0 folios so each
> +	 * page can be freed as soon as it has been backed up.
> +	 */
> +	folio_split_driver_managed(page_folio(p), 0);
>  }

[Severity: Critical]
Does this code handle split failures correctly?

If CONFIG_TRANSPARENT_HUGEPAGE is disabled, the fallback stub for
folio_split_driver_managed() unconditionally returns -EINVAL and the folio
remains an intact compound page. Because the return value is ignored,
callers like ttm_pool_backup() will incorrectly assume the split succeeded.

Will this corrupt the buddy allocator in ttm_pool_backup()? It follows the
split with this loop:

			ttm_pool_split_for_swap(pool, page);
			for (j = 0; j < nr_backed; j++)
				__free_pages_gpu_account(page + j, 0, false);

Calling __free_pages(page + j, 0) on a tail page of an active compound folio
redirects the refcount decrement to the head page, rapidly underflowing it.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260722044220.1110278-1-matthew.brost@intel.com?part=3

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 1/3] mm/huge_memory: add folio_split_driver_managed()
  2026-07-22  4:42 [PATCH 1/3] mm/huge_memory: add folio_split_driver_managed() Matthew Brost
                   ` (2 preceding siblings ...)
  2026-07-22  4:48 ` ✗ CI.KUnit: failure for series starting with [1/3] mm/huge_memory: add folio_split_driver_managed() Patchwork
@ 2026-07-22 14:26 ` Zi Yan
  2026-07-22 15:28   ` Zi Yan
  3 siblings, 1 reply; 8+ messages in thread
From: Zi Yan @ 2026-07-22 14:26 UTC (permalink / raw)
  To: Matthew Brost, intel-xe, dri-devel, linux-mm, linux-kernel
  Cc: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Christian Koenig, Huang Rui, Matthew Auld,
	Andrew Morton, David Hildenbrand, Lorenzo Stoakes, Baolin Wang,
	Liam R. Howlett, Nico Pache, Ryan Roberts, Dev Jain, Barry Song,
	Lance Yang, Tvrtko Ursulin, Dave Airlie, Matthew Wilcox

On Wed Jul 22, 2026 at 12:42 AM EDT, Matthew Brost wrote:
> Add a lightweight structural split primitive for large (compound) folios
> that a driver allocated with __GFP_COMP and manages entirely by itself,
> outside of the core mm's view.
>
> The existing split paths - split_folio() and folio_split_unmapped() -
> are built for folios that the mm owns: they perform a refcount freeze,
> walk and remap the rmap, and take the anon_vma / i_mmap locks, and
> folio_split_unmapped() further assumes an anon, pagecache-style refcount
> model (nr_pages + 1). None of that applies to a folio that is:
>
>   - singly referenced (the caller holds the only reference),
>   - not mapped through the rmap (folio_mapped() == 0),
>   - not in the page cache or swap cache (folio->mapping == NULL),
>   - not on any LRU or the deferred-split list.
>
> For such a folio the split is purely structural: because nothing else in
> the kernel can reach it, there is no need to freeze the refcount or touch
> any mapping. folio_split_driver_managed() therefore performs only the

I do not think so. PFN scanners like memory compaction should be able to
see them, unless you mean something else about "a driver allocated with
__GFP_COMP". You will need to freeze it to prevent the to-be-split
folios being touched by others.


> compound and split-accounting teardown via __split_unmapped_folio() and
> then hands each resulting order-@new_order folio its own reference,
> mirroring split_page() for compound folios. The caller keeps the original
> reference on the first resulting folio and is responsible for freeing all
> of them individually.
>
> The immediate user is TTM's GPU page pool, which allocates higher-order
> compound pages, maps them into userspace via VM_PFNMAP (never through the
> rmap), and needs to split them into order-0 folios under memory pressure
> so pages can be backed up to shmem and freed one at a time.
>
> A CONFIG_TRANSPARENT_HUGEPAGE=n stub is provided so callers can build
> without the split machinery; it warns and returns -EINVAL.
>
> Cc: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
> Cc: Maxime Ripard <mripard@kernel.org>
> Cc: Thomas Zimmermann <tzimmermann@suse.de>
> Cc: David Airlie <airlied@gmail.com>
> Cc: Simona Vetter <simona@ffwll.ch>
> Cc: Christian Koenig <christian.koenig@amd.com>
> Cc: Huang Rui <ray.huang@amd.com>
> Cc: Matthew Auld <matthew.auld@intel.com>
> Cc: Matthew Brost <matthew.brost@intel.com>
> Cc: Andrew Morton <akpm@linux-foundation.org>
> Cc: David Hildenbrand <david@kernel.org>
> Cc: Lorenzo Stoakes <ljs@kernel.org>
> Cc: Zi Yan <ziy@nvidia.com>
> Cc: Baolin Wang <baolin.wang@linux.alibaba.com>
> Cc: "Liam R. Howlett" <liam@infradead.org>
> Cc: Nico Pache <npache@redhat.com>
> Cc: Ryan Roberts <ryan.roberts@arm.com>
> Cc: Dev Jain <dev.jain@arm.com>
> Cc: Barry Song <baohua@kernel.org>
> Cc: Lance Yang <lance.yang@linux.dev>
> Cc: Tvrtko Ursulin <tvrtko.ursulin@igalia.com>
> Cc: Dave Airlie <airlied@redhat.com>
> Cc: dri-devel@lists.freedesktop.org
> Cc: linux-kernel@vger.kernel.org
> Cc: linux-mm@kvack.org
> Suggested-by: Matthew Wilcox <willy@infradead.org>
> Signed-off-by: Matthew Brost <matthew.brost@intel.com>
> Assisted-by: GitHub-Copilot:claude-opus-4.8
>
> ---
>
> The patch is based on drm-tip rather than the core MM branches to
> facilitate Intel CI testing and initial review. It can be rebased onto
> the core MM branches in a subsequent revision.
> ---
>  include/linux/huge_mm.h |  8 ++++++
>  mm/huge_memory.c        | 63 +++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 71 insertions(+)
>
> diff --git a/include/linux/huge_mm.h b/include/linux/huge_mm.h
> index ad20f7f8c179..35661d82d54a 100644
> --- a/include/linux/huge_mm.h
> +++ b/include/linux/huge_mm.h
> @@ -402,6 +402,7 @@ enum split_type {
>  int __split_huge_page_to_list_to_order(struct page *page, struct list_head *list,
>  		unsigned int new_order);
>  int folio_split_unmapped(struct folio *folio, unsigned int new_order);
> +int folio_split_driver_managed(struct folio *folio, unsigned int new_order);
>  unsigned int min_order_for_split(struct folio *folio);
>  int split_folio_to_list(struct folio *folio, struct list_head *list);
>  int folio_check_splittable(struct folio *folio, unsigned int new_order,
> @@ -656,6 +657,13 @@ static inline int split_folio_to_list(struct folio *folio, struct list_head *lis
>  	return -EINVAL;
>  }
>  
> +static inline int folio_split_driver_managed(struct folio *folio,
> +					     unsigned int new_order)
> +{
> +	VM_WARN_ON_ONCE_FOLIO(1, folio);
> +	return -EINVAL;
> +}
> +
>  static inline int folio_split(struct folio *folio, unsigned int new_order,
>  		struct page *page, struct list_head *list)
>  {
> diff --git a/mm/huge_memory.c b/mm/huge_memory.c
> index 2bccb0a53a0a..06f9a5f35df8 100644
> --- a/mm/huge_memory.c
> +++ b/mm/huge_memory.c
> @@ -4185,6 +4185,69 @@ int folio_split_unmapped(struct folio *folio, unsigned int new_order)
>  	return ret;
>  }
>  
> +/**
> + * folio_split_driver_managed() - split an exclusively-owned, off-LRU folio
> + * @folio: folio to split. Must be a large (compound) folio that is owned
> + *         exclusively by the caller and is invisible to the core mm.
> + * @new_order: the order of the folios after the split.
> + *
> + * This is a lightweight structural split for folios that a driver allocated
> + * and manages itself (for example TTM's GPU page pool, which allocates
> + * higher-order compound pages with __GFP_COMP and maps them into userspace
> + * via VM_PFNMAP rather than through the rmap). Such folios are:
> + *
> + *   - singly referenced (the caller holds the only reference),
> + *   - not mapped through the rmap (folio_mapcount() == 0),
> + *   - not in the page cache or swap cache (folio->mapping == NULL),
> + *   - not on any LRU or the deferred-split list.
> + *
> + * Because nothing else in the kernel can reach the folio, this helper does
> + * not perform the refcount freeze / remap / anon_vma & i_mmap locking dance
> + * that split_folio() and folio_split_unmapped() require. It performs only
> + * the compound and split-accounting teardown and then hands each resulting
> + * folio its own reference, mirroring split_page() for compound folios.
> + *
> + * The caller is responsible for freeing the resulting folios individually.
> + *
> + * Context: caller holds the only reference and excludes concurrent access.
> + * Does not sleep.
> + *
> + * Return: 0 on success, negative errno on failure.
> + */
> +int folio_split_driver_managed(struct folio *folio, unsigned int new_order)
> +{
> +	unsigned int old_order = folio_order(folio);
> +	unsigned int split_nr = 1U << new_order;
> +	unsigned int nr = 1U << old_order;
> +	unsigned int i;
> +
> +	if (new_order >= old_order)
> +		return -EINVAL;
> +
> +	VM_WARN_ON_ONCE_FOLIO(folio_ref_count(folio) != 1, folio);
> +	VM_WARN_ON_ONCE_FOLIO(folio_mapped(folio), folio);
> +	VM_WARN_ON_ONCE_FOLIO(folio->mapping, folio);
> +	VM_WARN_ON_ONCE_FOLIO(folio_test_swapcache(folio), folio);
> +	VM_WARN_ON_ONCE_FOLIO(folio_test_lru(folio), folio);
> +
> +	/*
> +	 * Structural + split-accounting teardown only. No mapping/xarray, no
> +	 * refcount freeze: the folio is frozen-by-ownership already.
> +	 */
> +	__split_unmapped_folio(folio, new_order, &folio->page, NULL, NULL,
> +			       SPLIT_TYPE_UNIFORM);
> +
> +	/*
> +	 * Give every resulting head folio its own reference. The original
> +	 * reference stays on the first one, exactly like split_page().
> +	 */
> +	for (i = split_nr; i < nr; i += split_nr)
> +		set_page_refcounted(folio_page(folio, i));
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL_GPL(folio_split_driver_managed);
> +
>  /*
>   * This function splits a large folio into smaller folios of order @new_order.
>   * @page can point to any page of the large folio to split. The split operation




-- 
Best Regards,
Yan, Zi


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 1/3] mm/huge_memory: add folio_split_driver_managed()
  2026-07-22 14:26 ` [PATCH 1/3] " Zi Yan
@ 2026-07-22 15:28   ` Zi Yan
  0 siblings, 0 replies; 8+ messages in thread
From: Zi Yan @ 2026-07-22 15:28 UTC (permalink / raw)
  To: Matthew Brost, intel-xe, dri-devel, linux-mm, linux-kernel
  Cc: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Christian Koenig, Huang Rui, Matthew Auld,
	Andrew Morton, David Hildenbrand, Lorenzo Stoakes, Baolin Wang,
	Liam R. Howlett, Nico Pache, Ryan Roberts, Dev Jain, Barry Song,
	Lance Yang, Tvrtko Ursulin, Dave Airlie, Matthew Wilcox

On Wed Jul 22, 2026 at 10:26 AM EDT, Zi Yan wrote:
> On Wed Jul 22, 2026 at 12:42 AM EDT, Matthew Brost wrote:
>> Add a lightweight structural split primitive for large (compound) folios
>> that a driver allocated with __GFP_COMP and manages entirely by itself,
>> outside of the core mm's view.
>>
>> The existing split paths - split_folio() and folio_split_unmapped() -
>> are built for folios that the mm owns: they perform a refcount freeze,
>> walk and remap the rmap, and take the anon_vma / i_mmap locks, and
>> folio_split_unmapped() further assumes an anon, pagecache-style refcount
>> model (nr_pages + 1). None of that applies to a folio that is:
>>
>>   - singly referenced (the caller holds the only reference),
>>   - not mapped through the rmap (folio_mapped() == 0),
>>   - not in the page cache or swap cache (folio->mapping == NULL),
>>   - not on any LRU or the deferred-split list.
>>
>> For such a folio the split is purely structural: because nothing else in
>> the kernel can reach it, there is no need to freeze the refcount or touch
>> any mapping. folio_split_driver_managed() therefore performs only the
>
> I do not think so. PFN scanners like memory compaction should be able to
> see them, unless you mean something else about "a driver allocated with
> __GFP_COMP". You will need to freeze it to prevent the to-be-split
> folios being touched by others.
>
>
>> compound and split-accounting teardown via __split_unmapped_folio() and
>> then hands each resulting order-@new_order folio its own reference,
>> mirroring split_page() for compound folios. The caller keeps the original
>> reference on the first resulting folio and is responsible for freeing all
>> of them individually.
>>
>> The immediate user is TTM's GPU page pool, which allocates higher-order
>> compound pages, maps them into userspace via VM_PFNMAP (never through the
>> rmap), and needs to split them into order-0 folios under memory pressure
>> so pages can be backed up to shmem and freed one at a time.
>>
>> A CONFIG_TRANSPARENT_HUGEPAGE=n stub is provided so callers can build
>> without the split machinery; it warns and returns -EINVAL.
>>
>> Cc: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
>> Cc: Maxime Ripard <mripard@kernel.org>
>> Cc: Thomas Zimmermann <tzimmermann@suse.de>
>> Cc: David Airlie <airlied@gmail.com>
>> Cc: Simona Vetter <simona@ffwll.ch>
>> Cc: Christian Koenig <christian.koenig@amd.com>
>> Cc: Huang Rui <ray.huang@amd.com>
>> Cc: Matthew Auld <matthew.auld@intel.com>
>> Cc: Matthew Brost <matthew.brost@intel.com>
>> Cc: Andrew Morton <akpm@linux-foundation.org>
>> Cc: David Hildenbrand <david@kernel.org>
>> Cc: Lorenzo Stoakes <ljs@kernel.org>
>> Cc: Zi Yan <ziy@nvidia.com>
>> Cc: Baolin Wang <baolin.wang@linux.alibaba.com>
>> Cc: "Liam R. Howlett" <liam@infradead.org>
>> Cc: Nico Pache <npache@redhat.com>
>> Cc: Ryan Roberts <ryan.roberts@arm.com>
>> Cc: Dev Jain <dev.jain@arm.com>
>> Cc: Barry Song <baohua@kernel.org>
>> Cc: Lance Yang <lance.yang@linux.dev>
>> Cc: Tvrtko Ursulin <tvrtko.ursulin@igalia.com>
>> Cc: Dave Airlie <airlied@redhat.com>
>> Cc: dri-devel@lists.freedesktop.org
>> Cc: linux-kernel@vger.kernel.org
>> Cc: linux-mm@kvack.org
>> Suggested-by: Matthew Wilcox <willy@infradead.org>
>> Signed-off-by: Matthew Brost <matthew.brost@intel.com>
>> Assisted-by: GitHub-Copilot:claude-opus-4.8
>>
>> ---
>>
>> The patch is based on drm-tip rather than the core MM branches to
>> facilitate Intel CI testing and initial review. It can be rebased onto
>> the core MM branches in a subsequent revision.
>> ---
>>  include/linux/huge_mm.h |  8 ++++++
>>  mm/huge_memory.c        | 63 +++++++++++++++++++++++++++++++++++++++++
>>  2 files changed, 71 insertions(+)
>>
>> diff --git a/include/linux/huge_mm.h b/include/linux/huge_mm.h
>> index ad20f7f8c179..35661d82d54a 100644
>> --- a/include/linux/huge_mm.h
>> +++ b/include/linux/huge_mm.h
>> @@ -402,6 +402,7 @@ enum split_type {
>>  int __split_huge_page_to_list_to_order(struct page *page, struct list_head *list,
>>  		unsigned int new_order);
>>  int folio_split_unmapped(struct folio *folio, unsigned int new_order);
>> +int folio_split_driver_managed(struct folio *folio, unsigned int new_order);
>>  unsigned int min_order_for_split(struct folio *folio);
>>  int split_folio_to_list(struct folio *folio, struct list_head *list);
>>  int folio_check_splittable(struct folio *folio, unsigned int new_order,
>> @@ -656,6 +657,13 @@ static inline int split_folio_to_list(struct folio *folio, struct list_head *lis
>>  	return -EINVAL;
>>  }
>>  
>> +static inline int folio_split_driver_managed(struct folio *folio,
>> +					     unsigned int new_order)
>> +{
>> +	VM_WARN_ON_ONCE_FOLIO(1, folio);
>> +	return -EINVAL;
>> +}
>> +
>>  static inline int folio_split(struct folio *folio, unsigned int new_order,
>>  		struct page *page, struct list_head *list)
>>  {
>> diff --git a/mm/huge_memory.c b/mm/huge_memory.c
>> index 2bccb0a53a0a..06f9a5f35df8 100644
>> --- a/mm/huge_memory.c
>> +++ b/mm/huge_memory.c
>> @@ -4185,6 +4185,69 @@ int folio_split_unmapped(struct folio *folio, unsigned int new_order)
>>  	return ret;
>>  }
>>  
>> +/**
>> + * folio_split_driver_managed() - split an exclusively-owned, off-LRU folio
>> + * @folio: folio to split. Must be a large (compound) folio that is owned
>> + *         exclusively by the caller and is invisible to the core mm.
>> + * @new_order: the order of the folios after the split.
>> + *
>> + * This is a lightweight structural split for folios that a driver allocated
>> + * and manages itself (for example TTM's GPU page pool, which allocates
>> + * higher-order compound pages with __GFP_COMP and maps them into userspace
>> + * via VM_PFNMAP rather than through the rmap). Such folios are:

Strickly speaking, these vm_insert*() compound pages are not folios,
since folios are supposed to be rmappable and they are either anonymous
memory or file-backed memory. I am working on separating them from
rmappable folios by replacing PG_private with PG_folio and marking all
pages in a folio with PG_folio in page_rmappable_folio().

Hopefully, we can find a better name, like refcounted_folio, later for
these non-rmappable compound pages.


>> + *
>> + *   - singly referenced (the caller holds the only reference),
>> + *   - not mapped through the rmap (folio_mapcount() == 0),
>> + *   - not in the page cache or swap cache (folio->mapping == NULL),
>> + *   - not on any LRU or the deferred-split list.
>> + *
>> + * Because nothing else in the kernel can reach the folio, this helper does
>> + * not perform the refcount freeze / remap / anon_vma & i_mmap locking dance
>> + * that split_folio() and folio_split_unmapped() require. It performs only
>> + * the compound and split-accounting teardown and then hands each resulting
>> + * folio its own reference, mirroring split_page() for compound folios.
>> + *
>> + * The caller is responsible for freeing the resulting folios individually.
>> + *
>> + * Context: caller holds the only reference and excludes concurrent access.
>> + * Does not sleep.
>> + *
>> + * Return: 0 on success, negative errno on failure.
>> + */
>> +int folio_split_driver_managed(struct folio *folio, unsigned int new_order)
>> +{
>> +	unsigned int old_order = folio_order(folio);
>> +	unsigned int split_nr = 1U << new_order;
>> +	unsigned int nr = 1U << old_order;
>> +	unsigned int i;
>> +
>> +	if (new_order >= old_order)
>> +		return -EINVAL;
>> +
>> +	VM_WARN_ON_ONCE_FOLIO(folio_ref_count(folio) != 1, folio);
>> +	VM_WARN_ON_ONCE_FOLIO(folio_mapped(folio), folio);
>> +	VM_WARN_ON_ONCE_FOLIO(folio->mapping, folio);
>> +	VM_WARN_ON_ONCE_FOLIO(folio_test_swapcache(folio), folio);
>> +	VM_WARN_ON_ONCE_FOLIO(folio_test_lru(folio), folio);
>> +
>> +	/*
>> +	 * Structural + split-accounting teardown only. No mapping/xarray, no
>> +	 * refcount freeze: the folio is frozen-by-ownership already.
>> +	 */
>> +	__split_unmapped_folio(folio, new_order, &folio->page, NULL, NULL,
>> +			       SPLIT_TYPE_UNIFORM);
>> +
>> +	/*
>> +	 * Give every resulting head folio its own reference. The original
>> +	 * reference stays on the first one, exactly like split_page().
>> +	 */
>> +	for (i = split_nr; i < nr; i += split_nr)
>> +		set_page_refcounted(folio_page(folio, i));
>> +
>> +	return 0;
>> +}
>> +EXPORT_SYMBOL_GPL(folio_split_driver_managed);
>> +
>>  /*
>>   * This function splits a large folio into smaller folios of order @new_order.
>>   * @page can point to any page of the large folio to split. The split operation




-- 
Best Regards,
Yan, Zi


^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2026-07-22 15:28 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-22  4:42 [PATCH 1/3] mm/huge_memory: add folio_split_driver_managed() Matthew Brost
2026-07-22  4:42 ` [PATCH 2/3] drm/xe: select TRANSPARENT_HUGEPAGE Matthew Brost
2026-07-22  4:48   ` sashiko-bot
2026-07-22  4:42 ` [PATCH 3/3] drm/ttm: allocate pool pages as compound (__GFP_COMP) Matthew Brost
2026-07-22  4:58   ` sashiko-bot
2026-07-22  4:48 ` ✗ CI.KUnit: failure for series starting with [1/3] mm/huge_memory: add folio_split_driver_managed() Patchwork
2026-07-22 14:26 ` [PATCH 1/3] " Zi Yan
2026-07-22 15:28   ` Zi Yan

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.