* [PATCH 1/2] iommu: free dev->iommu when iommu_fwspec_init() cannot allocate the fwspec
2026-10-31 20:33 [PATCH 0/2] iommufd: remove the last use of set_page_dirty_lock() Tal Zussman
@ 2026-10-31 20:33 ` Tal Zussman
2026-11-02 16:51 ` Robin Murphy
2026-10-31 20:33 ` [PATCH 2/2] iommufd: copy to and from folios in batch_rw() Tal Zussman
1 sibling, 1 reply; 4+ messages in thread
From: Tal Zussman @ 2026-10-31 20:33 UTC (permalink / raw)
To: Jason Gunthorpe, Kevin Tian, Joerg Roedel (AMD),
Will Deacon, Robin Murphy, Greg Kroah-Hartman,
Jean-Philippe Brucker
Cc: iommu, linux-kernel, Tal Zussman
iommu_fwspec_init() allocates dev->iommu with dev_iommu_get() and then
the fwspec. If the fwspec allocation fails it returns -ENOMEM but leaves
dev->iommu allocated, and it leaks.
kmemleak reports it when iommufd_fail_nth injects the allocation
failure:
unreferenced object 0xffff88811c138b00 (size 192):
comm "iommufd_fail_nt", pid 783
backtrace:
__kmalloc_cache_noprof
dev_iommu_get
iommu_fwspec_init
iommu_mock_device_add
iommufd_test
iommufd_fops_ioctl
Free dev->iommu on failure if this call allocated it. iommu_init_device()
allocates it before calling into the driver and frees it on its own
error path, so leave it alone in that case.
Fixes: 72acd9df18f1 ("iommu: Move iommu_fwspec to struct dev_iommu")
Signed-off-by: Tal Zussman <tz2294@columbia.edu>
---
drivers/iommu/iommu.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index cd1bca7ede9a..69be8819e05d 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -3074,6 +3074,7 @@ int iommu_fwspec_init(struct device *dev, struct fwnode_handle *iommu_fwnode)
{
const struct iommu_device *iommu = iommu_from_fwnode(iommu_fwnode);
struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev);
+ bool new_iommu = !dev->iommu;
if (!iommu)
return driver_deferred_probe_check_state(dev);
@@ -3088,8 +3089,11 @@ int iommu_fwspec_init(struct device *dev, struct fwnode_handle *iommu_fwnode)
/* Preallocate for the overwhelmingly common case of 1 ID */
fwspec = kzalloc_flex(*fwspec, ids, 1);
- if (!fwspec)
+ if (!fwspec) {
+ if (new_iommu)
+ dev_iommu_free(dev);
return -ENOMEM;
+ }
fwnode_handle_get(iommu_fwnode);
fwspec->iommu_fwnode = iommu_fwnode;
--
2.39.5
^ permalink raw reply [flat|nested] 4+ messages in thread* [PATCH 2/2] iommufd: copy to and from folios in batch_rw()
2026-10-31 20:33 [PATCH 0/2] iommufd: remove the last use of set_page_dirty_lock() Tal Zussman
2026-10-31 20:33 ` [PATCH 1/2] iommu: free dev->iommu when iommu_fwspec_init() cannot allocate the fwspec Tal Zussman
@ 2026-10-31 20:33 ` Tal Zussman
1 sibling, 0 replies; 4+ messages in thread
From: Tal Zussman @ 2026-10-31 20:33 UTC (permalink / raw)
To: Jason Gunthorpe, Kevin Tian, Joerg Roedel (AMD),
Will Deacon, Robin Murphy, Greg Kroah-Hartman,
Jean-Philippe Brucker
Cc: iommu, linux-kernel, Tal Zussman
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@columbia.edu>
---
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
^ permalink raw reply [flat|nested] 4+ messages in thread