* [PATCH 1/2] mm/shmem: add shmem_backup_folio() helper
@ 2026-07-21 17:47 Matthew Brost
2026-07-21 17:47 ` [PATCH 2/2] drm/ttm: use shmem_backup_folio() for folio backup Matthew Brost
2026-07-22 9:43 ` [PATCH 1/2] mm/shmem: add shmem_backup_folio() helper Christoph Hellwig
0 siblings, 2 replies; 3+ messages in thread
From: Matthew Brost @ 2026-07-21 17:47 UTC (permalink / raw)
To: intel-xe, dri-devel, linux-mm, linux-kernel
Cc: Hugh Dickins, Baolin Wang, Andrew Morton, Christian Koenig,
Huang Rui, Matthew Auld, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Christoph Hellwig
Add a new generic helper, shmem_backup_folio(), that copies the pages
of an arbitrary source folio into a range of a shmem-backed file,
optionally issuing writeback for each destination folio. It is
extracted from drivers/gpu/drm/ttm's existing shmem-backup loop so
that other subsystems (starting with TTM in a follow-up patch) can
share the same implementation.
Semantics:
- On full success, 0 is returned and *nr_pages_backed is set to
(1 << order).
- On a mid-range -ENOMEM after at least one destination folio has
been populated, the partial backup is retained: *nr_pages_backed
holds the successfully backed-up prefix and -ENOMEM is returned so
the caller can commit that prefix and decide how to proceed
(e.g. via a reactive split-and-retry path).
- On any other error, or -ENOMEM with no progress, the pages already
written are truncated from the backup file via
shmem_truncate_range(), *nr_pages_backed is reset to 0, and the
original error is returned.
@order is passed explicitly rather than derived from
folio_order(@folio). Some callers (e.g. TTM) allocate high-order
pages without __GFP_COMP, so folio_order() on such a "folio-shaped"
range would report 0.
folio_mark_dirty() runs after the copy loop, matching the kernel
write-path convention of lock -> modify -> mark_dirty.
Cc: Hugh Dickins <hughd@google.com>
Cc: Baolin Wang <baolin.wang@linux.alibaba.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Christian Koenig <christian.koenig@amd.com>
Cc: Huang Rui <ray.huang@amd.com>
Cc: Matthew Auld <matthew.auld@intel.com>
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: dri-devel@lists.freedesktop.org
Cc: linux-mm@kvack.org
Cc: linux-kernel@vger.kernel.org
Suggested-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Matthew Brost <matthew.brost@intel.com>
Assisted-by: GitHub-Copilot:claude-sonnet-5
---
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/shmem_fs.h | 3 +
mm/shmem.c | 125 +++++++++++++++++++++++++++++++++++++++
2 files changed, 128 insertions(+)
diff --git a/include/linux/shmem_fs.h b/include/linux/shmem_fs.h
index e729b9b0e38d..541f1ab675b7 100644
--- a/include/linux/shmem_fs.h
+++ b/include/linux/shmem_fs.h
@@ -127,6 +127,9 @@ int shmem_writeout(struct folio *folio, struct swap_iocb **plug,
struct list_head *folio_list);
void shmem_truncate_range(struct inode *inode, loff_t start, uoff_t end);
int shmem_unuse(unsigned int type);
+int shmem_backup_folio(struct folio *folio, struct file *backup, pgoff_t index,
+ gfp_t gfp, unsigned int order, pgoff_t *nr_pages_backed,
+ bool writeback);
#if defined(CONFIG_TRANSPARENT_HUGEPAGE) && defined(CONFIG_SHMEM)
unsigned long shmem_allowable_huge_orders(struct inode *inode,
diff --git a/mm/shmem.c b/mm/shmem.c
index b51f83c970bb..bc11c4039b7b 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -5961,3 +5961,128 @@ struct page *shmem_read_mapping_page_gfp(struct address_space *mapping,
return page;
}
EXPORT_SYMBOL_GPL(shmem_read_mapping_page_gfp);
+
+/**
+ * shmem_backup_folio() - Copy a folio's contents into a shmem-backed file.
+ * @folio: Source folio whose contents are to be backed up. Must remain
+ * pinned by the caller for the duration of the call. The source folio
+ * is not modified; on success its contents live in @backup and the
+ * caller may release the source memory.
+ * @backup: A file previously created with shmem_file_setup() (or a wrapper
+ * such as ttm_backup_shmem_create()). Must be shmem-backed;
+ * non-shmem files are rejected with -EINVAL.
+ * @index: shmem page index at which to begin storing the backup. The
+ * subpages of @folio are written to consecutive indices starting at
+ * @index. The caller is responsible for ensuring the range
+ * [@index, @index + nr_pages) does not collide with other in-flight
+ * backups against the same @backup.
+ * @gfp: Allocation flags used when reading/allocating the destination
+ * shmem folios.
+ * @order: Number of source subpages to copy, expressed as a power of two
+ * (nr_pages = 1 << @order). Passed explicitly rather than derived
+ * from folio_order(@folio) so that callers may back up higher-order
+ * page ranges that were allocated without __GFP_COMP (and for which
+ * folio_order() would therefore return 0).
+ * @nr_pages_backed: Out parameter. Reset to 0 on entry. On any return
+ * value, holds the number of source subpages whose contents are
+ * guaranteed to be present in @backup and reachable via consecutive
+ * shmem indices starting at @index. A value less than (1 << @order)
+ * with return value -ENOMEM indicates a partial backup that the
+ * caller may keep (see below).
+ * @writeback: If true, attempt immediate writeout of each destination
+ * shmem folio after it is populated. This trades throughput for
+ * promptly evicting the backup from page cache. Only meaningful for
+ * unmapped destination folios.
+ *
+ * Copy the contents of @folio into @backup starting at shmem index
+ * @index, one destination shmem folio at a time. Destination folios may
+ * themselves be higher-order; the copy loop advances by the natural
+ * order of each destination folio so a large destination folio is
+ * populated in a single iteration.
+ *
+ * The function distinguishes two failure modes:
+ *
+ * - Mid-folio -ENOMEM after at least one destination folio has been
+ * populated: the partial backup is *retained*. @nr_pages_backed is
+ * left at the number of subpages successfully copied so far, and
+ * -ENOMEM is returned. Callers that support partial backup (e.g. a
+ * subsequent split-and-retry of the source) can commit the returned
+ * prefix by treating handles [@index, @index + *@nr_pages_backed) as
+ * valid.
+ *
+ * - Any other error, or -ENOMEM with no progress: the pages already
+ * written (if any) are truncated from @backup via
+ * shmem_truncate_range(), @nr_pages_backed is reset to 0, and the
+ * original error is returned. On return the caller owns no valid
+ * handles in @backup.
+ *
+ * Context: May sleep. The caller is responsible for any locking of
+ * @backup's page range against concurrent access.
+ *
+ * Return: 0 on full success (all 1 << @order subpages backed up),
+ * -ENOMEM on partial success (see above), or another negative errno on
+ * failure (backup fully rolled back).
+ */
+int shmem_backup_folio(struct folio *folio, struct file *backup, pgoff_t index,
+ gfp_t gfp, unsigned int order, pgoff_t *nr_pages_backed,
+ bool writeback)
+{
+ struct address_space *mapping = backup->f_mapping;
+ int nr_pages = 1 << order;
+ int ret, i;
+
+ *nr_pages_backed = 0;
+ if (!shmem_file(backup))
+ return -EINVAL;
+
+ for (i = 0; i < nr_pages; ) {
+ struct folio *to_folio;
+ int to_nr, j;
+
+ to_folio = shmem_read_folio_gfp(mapping, index + i, gfp);
+ if (IS_ERR(to_folio)) {
+ ret = PTR_ERR(to_folio);
+
+ /* All errors aside from -ENOMEM drop partial backup */
+ if (*nr_pages_backed && ret != -ENOMEM) {
+ shmem_truncate_range(file_inode(backup),
+ (loff_t)index << PAGE_SHIFT,
+ ((loff_t)(index + i) << PAGE_SHIFT) - 1);
+ *nr_pages_backed = 0;
+ }
+
+ return ret;
+ }
+
+ to_nr = min_t(int, nr_pages - i,
+ folio_next_index(to_folio) - (index + i));
+
+ folio_mark_accessed(to_folio);
+ folio_lock(to_folio);
+
+ for (j = 0; j < to_nr; j++)
+ copy_highpage(folio_file_page(to_folio, index + i + j),
+ folio_page(folio, i + j));
+
+ folio_mark_dirty(to_folio);
+
+ if (writeback && !folio_mapped(to_folio) &&
+ folio_clear_dirty_for_io(to_folio)) {
+ folio_set_reclaim(to_folio);
+ ret = shmem_writeout(to_folio, NULL, NULL);
+ if (!folio_test_writeback(to_folio))
+ folio_clear_reclaim(to_folio);
+ if (ret == AOP_WRITEPAGE_ACTIVATE)
+ folio_unlock(to_folio);
+ } else {
+ folio_unlock(to_folio);
+ }
+
+ folio_put(to_folio);
+ i += to_nr;
+ *nr_pages_backed = i;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(shmem_backup_folio);
--
2.34.1
^ permalink raw reply related [flat|nested] 3+ messages in thread
* [PATCH 2/2] drm/ttm: use shmem_backup_folio() for folio backup
2026-07-21 17:47 [PATCH 1/2] mm/shmem: add shmem_backup_folio() helper Matthew Brost
@ 2026-07-21 17:47 ` Matthew Brost
2026-07-22 9:43 ` [PATCH 1/2] mm/shmem: add shmem_backup_folio() helper Christoph Hellwig
1 sibling, 0 replies; 3+ messages in thread
From: Matthew Brost @ 2026-07-21 17:47 UTC (permalink / raw)
To: intel-xe, dri-devel, linux-mm, linux-kernel
Cc: Hugh Dickins, Baolin Wang, Andrew Morton, Christian Koenig,
Huang Rui, Matthew Auld, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Christoph Hellwig
Replace the open-coded shmem-backup loop in ttm_backup_backup_folio()
with a thin wrapper around the new shmem_backup_folio() helper. The
wrapper maps the helper's (int errno, *nr_pages_backed) contract onto
TTM's existing (s64 handle) return convention:
- 0 -> base handle
- -ENOMEM with *nr_pages_backed > 0 -> base handle (short backup;
partial progress kept)
- any other error -> that error verbatim
The mid-compound -ENOMEM fault-injection point that used to live
inside the per-subpage loop moves up to the wrapper and takes a new
shape: instead of synthesizing an error partway through the loop, the
wrapper passes order-1 to shmem_backup_folio() when
ttm_backup_fault_inject_folio() fires. shmem_backup_folio() then
backs up the first half of the compound normally, returns 0 with
*nr_pages_backed = (1 << (order - 1)), and TTM's caller sees the same
"short backup, nr_backed < (1 << order)" it would see on a real
mid-compound OOM and drives its reactive-split path unchanged.
Injection is skipped for order == 0 since there is nothing to shrink;
the previous "inject only on i > 0" guard mapped onto the same
observable subset of runs.
No functional change intended for non-injection paths.
Cc: Hugh Dickins <hughd@google.com>
Cc: Baolin Wang <baolin.wang@linux.alibaba.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Christian Koenig <christian.koenig@amd.com>
Cc: Huang Rui <ray.huang@amd.com>
Cc: Matthew Auld <matthew.auld@intel.com>
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: dri-devel@lists.freedesktop.org
Cc: linux-mm@kvack.org
Cc: linux-kernel@vger.kernel.org
Suggested-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Matthew Brost <matthew.brost@intel.com>
Assisted-by: GitHub-Copilot:claude-sonnet-5
---
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.
---
drivers/gpu/drm/ttm/ttm_backup.c | 91 ++++++++------------------------
1 file changed, 21 insertions(+), 70 deletions(-)
diff --git a/drivers/gpu/drm/ttm/ttm_backup.c b/drivers/gpu/drm/ttm/ttm_backup.c
index 3c067aadc52d..a6c4e6cb643b 100644
--- a/drivers/gpu/drm/ttm/ttm_backup.c
+++ b/drivers/gpu/drm/ttm/ttm_backup.c
@@ -105,76 +105,27 @@ ttm_backup_backup_folio(struct file *backup, struct folio *folio,
gfp_t folio_gfp, gfp_t alloc_gfp,
pgoff_t *nr_pages_backed)
{
- struct address_space *mapping = backup->f_mapping;
- int nr_pages = 1 << order;
- struct folio *to_folio;
- int ret, i;
-
- *nr_pages_backed = 0;
-
- for (i = 0; i < nr_pages; ) {
- int to_nr, j;
-
- /*
- * Only inject past the first subpage so *nr_pages_backed is
- * always > 0 here, matching a genuine mid-compound -ENOMEM
- * and driving the caller's reactive split fallback instead
- * of an early, no-progress failure.
- */
- if (IS_ENABLED(CONFIG_FAULT_INJECTION) && i &&
- ttm_backup_fault_inject_folio())
- to_folio = ERR_PTR(-ENOMEM);
- else
- to_folio = shmem_read_folio_gfp(mapping, idx + i, alloc_gfp);
- if (IS_ERR(to_folio)) {
- int err = PTR_ERR(to_folio);
-
- if (err == -ENOMEM && *nr_pages_backed)
- return ttm_backup_shmem_idx_to_handle(idx);
-
- if (*nr_pages_backed) {
- shmem_truncate_range(file_inode(backup),
- (loff_t)idx << PAGE_SHIFT,
- ((loff_t)(idx + i) << PAGE_SHIFT) - 1);
- /*
- * The pages just truncated are no longer
- * backed up; don't let the caller mistake
- * them for valid handles.
- */
- *nr_pages_backed = 0;
- }
- return err;
- }
-
- to_nr = min_t(int, nr_pages - i,
- folio_next_index(to_folio) - (idx + i));
-
- folio_mark_accessed(to_folio);
- folio_lock(to_folio);
- folio_mark_dirty(to_folio);
-
- for (j = 0; j < to_nr; j++)
- copy_highpage(folio_file_page(to_folio, idx + i + j),
- folio_page(folio, i + j));
-
- if (writeback && !folio_mapped(to_folio) &&
- folio_clear_dirty_for_io(to_folio)) {
- folio_set_reclaim(to_folio);
- ret = shmem_writeout(to_folio, NULL, NULL);
- if (!folio_test_writeback(to_folio))
- folio_clear_reclaim(to_folio);
- if (ret == AOP_WRITEPAGE_ACTIVATE)
- folio_unlock(to_folio);
- } else {
- folio_unlock(to_folio);
- }
-
- folio_put(to_folio);
- i += to_nr;
- *nr_pages_backed = i;
- }
-
- return ttm_backup_shmem_idx_to_handle(idx);
+ unsigned int backup_order = order;
+ int err;
+
+ /*
+ * Fault injection: back up only the first half of the folio to
+ * simulate a mid-compound OOM. The caller sees *nr_pages_backed
+ * < (1 << order) on success and drives its reactive-split path
+ * exactly as it would on a real short return. order == 0 cannot
+ * be shrunk further, so injection is skipped in that case.
+ */
+ if (IS_ENABLED(CONFIG_FAULT_INJECTION) && order &&
+ ttm_backup_fault_inject_folio())
+ backup_order = order - 1;
+
+ err = shmem_backup_folio(folio, backup, idx, alloc_gfp, backup_order,
+ nr_pages_backed, writeback);
+
+ if (!err || (err == -ENOMEM && *nr_pages_backed))
+ return ttm_backup_shmem_idx_to_handle(idx);
+
+ return err;
}
/**
--
2.34.1
^ permalink raw reply related [flat|nested] 3+ messages in thread
* Re: [PATCH 1/2] mm/shmem: add shmem_backup_folio() helper
2026-07-21 17:47 [PATCH 1/2] mm/shmem: add shmem_backup_folio() helper Matthew Brost
2026-07-21 17:47 ` [PATCH 2/2] drm/ttm: use shmem_backup_folio() for folio backup Matthew Brost
@ 2026-07-22 9:43 ` Christoph Hellwig
1 sibling, 0 replies; 3+ messages in thread
From: Christoph Hellwig @ 2026-07-22 9:43 UTC (permalink / raw)
To: Matthew Brost
Cc: intel-xe, dri-devel, linux-mm, linux-kernel, Hugh Dickins,
Baolin Wang, Andrew Morton, Christian Koenig, Huang Rui,
Matthew Auld, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
David Airlie, Simona Vetter, Christoph Hellwig
On Tue, Jul 21, 2026 at 10:47:22AM -0700, Matthew Brost wrote:
> Add a new generic helper, shmem_backup_folio(), that copies the pages
> of an arbitrary source folio into a range of a shmem-backed file,
> optionally issuing writeback for each destination folio. It is
> extracted from drivers/gpu/drm/ttm's existing shmem-backup loop so
> that other subsystems (starting with TTM in a follow-up patch) can
> share the same implementation.
Hmm, what is the difference to just doing a ITER_BVEC write with a bvec
containing the folio for this the pure write to shmem part of this?
You'd probably want a kernel_write_iter helper wrapping
__kernel_write_iter for the writecount, but otherwise this sounds
like a normal write.
We'd want something for the writeback part, preferably a purely
range based API to be generic.
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-07-22 9:43 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-21 17:47 [PATCH 1/2] mm/shmem: add shmem_backup_folio() helper Matthew Brost
2026-07-21 17:47 ` [PATCH 2/2] drm/ttm: use shmem_backup_folio() for folio backup Matthew Brost
2026-07-22 9:43 ` [PATCH 1/2] mm/shmem: add shmem_backup_folio() helper Christoph Hellwig
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox