[PATCH 2/2] iommufd: copy to and from folios in batch_rw()

From: Tal Zussman

Date: Sat Oct 31 2026 - 16:34:39 EST


batch_rw() copies one page at a time and, for writes, dirties each page
with set_page_dirty_lock(). Walk the pfn batch a folio at a time
instead, copying with memcpy_to_folio() and memcpy_from_folio() and
marking each folio dirty once. Convert the single-page copy in
iopt_pages_rw_page() to the same helper.

iopt_pages_rw_access() rejects dmabuf pages, so only pinned user memory
and memfd pages can reach batch_rw(), and those are always folios.

memcpy_to_folio() also flushes the dcache after the write, which the
open-coded memcpy() didn't.

This removes the last use of set_page_dirty_lock() in iommufd.

With ftrace timing iopt_pages_rw_slow() through the mock driver on a
non-debug kernel, slow-path writes to THP-backed pages drop from ~1310 to
~930 ns/page (now the same cost as a read), and writes to 4K folios drop
~9%.

Signed-off-by: Tal Zussman <tz2294@xxxxxxxxxxxx>
---
drivers/iommu/iommufd/pages.c | 35 ++++++++++++++++++++---------------
1 file changed, 20 insertions(+), 15 deletions(-)

diff --git a/drivers/iommu/iommufd/pages.c b/drivers/iommu/iommufd/pages.c
index 2f6153108add..133fed4495dd 100644
--- a/drivers/iommu/iommufd/pages.c
+++ b/drivers/iommu/iommufd/pages.c
@@ -721,19 +721,15 @@ static void batch_unpin(struct pfn_batch *batch, struct iopt_pages *pages,
}
}

-static void copy_data_page(struct page *page, void *data, unsigned long offset,
- size_t length, unsigned int flags)
+static void copy_data_folio(struct folio *folio, size_t offset, void *data,
+ size_t length, unsigned int flags)
{
- void *mem;
-
- mem = kmap_local_page(page);
if (flags & IOMMUFD_ACCESS_RW_WRITE) {
- memcpy(mem + offset, data, length);
- set_page_dirty_lock(page);
+ memcpy_to_folio(folio, offset, data, length);
+ folio_mark_dirty_lock(folio);
} else {
- memcpy(data, mem + offset, length);
+ memcpy_from_folio(data, folio, offset, length);
}
- kunmap_local(mem);
}

static unsigned long batch_rw(struct pfn_batch *batch, void *data,
@@ -745,15 +741,21 @@ static unsigned long batch_rw(struct pfn_batch *batch, void *data,
unsigned int cur = 0;

while (cur < batch->end) {
- unsigned long bytes = min(length, PAGE_SIZE - offset);
-
- copy_data_page(pfn_to_page(batch->pfns[cur] + npage), data,
- offset, bytes, flags);
+ unsigned long pfn = batch->pfns[cur] + npage;
+ struct folio *folio = pfn_folio(pfn);
+ unsigned long idx = pfn - folio_pfn(folio);
+ unsigned long nr = min_t(unsigned long,
+ folio_nr_pages(folio) - idx,
+ batch->npfns[cur] - npage);
+ unsigned long bytes = min(length, nr * PAGE_SIZE - offset);
+
+ copy_data_folio(folio, idx * PAGE_SIZE + offset, data, bytes,
+ flags);
offset = 0;
length -= bytes;
data += bytes;
copied += bytes;
- npage++;
+ npage += nr;
if (npage == batch->npfns[cur]) {
npage = 0;
cur++;
@@ -2306,6 +2308,7 @@ static int iopt_pages_rw_page(struct iopt_pages *pages, unsigned long index,
unsigned long length, unsigned int flags)
{
struct page *page = NULL;
+ struct folio *folio;
int rc;

if (IS_ENABLED(CONFIG_IOMMUFD_TEST) &&
@@ -2332,7 +2335,9 @@ static int iopt_pages_rw_page(struct iopt_pages *pages, unsigned long index,
rc = -EINVAL;
goto out_mmput;
}
- copy_data_page(page, data, offset, length, flags);
+ folio = page_folio(page);
+ copy_data_folio(folio, folio_page_idx(folio, page) * PAGE_SIZE + offset,
+ data, length, flags);
unpin_user_page(page);
rc = 0;


--
2.39.5