* [PATCH 0/2] iommufd: remove the last use of set_page_dirty_lock()
@ 2026-10-31 20:33 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 ` [PATCH 2/2] iommufd: copy to and from folios in batch_rw() Tal Zussman
0 siblings, 2 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 to and from pinned user pages one page at a time and
dirties each with set_page_dirty_lock(). The second patch walks the pfn
batch a folio at a time with memcpy_to_folio() and memcpy_from_folio()
and marks each folio dirty once. It removes the last use of
set_page_dirty_lock() in iommufd.
The issue fixed in the first patch was found while testing the second:
iommufd_fail_nth turned up a dev_iommu leak in iommu_fwspec_init() under
kmemleak. The fix is independent and could go through the iommu tree on
its own.
Tested on x86 under virtme-ng with KASAN and lockdep against the iommufd
mock driver. The selftests and iommufd_fail_nth all pass.
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%.
---
Tal Zussman (2):
iommu: free dev->iommu when iommu_fwspec_init() cannot allocate the fwspec
iommufd: copy to and from folios in batch_rw()
drivers/iommu/iommu.c | 6 +++++-
drivers/iommu/iommufd/pages.c | 35 ++++++++++++++++++++---------------
2 files changed, 25 insertions(+), 16 deletions(-)
---
base-commit: 04bcf84613346b973a8cc0c59238b6b5bc902688
change-id: 20261015-iommufd-folio-dirty-8abff4281f3a
Best regards,
--
Tal Zussman <tz2294@columbia.edu>
^ permalink raw reply [flat|nested] 4+ messages in thread
* [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
* Re: [PATCH 1/2] iommu: free dev->iommu when iommu_fwspec_init() cannot allocate the fwspec
2026-10-31 20:33 ` [PATCH 1/2] iommu: free dev->iommu when iommu_fwspec_init() cannot allocate the fwspec Tal Zussman
@ 2026-11-02 16:51 ` Robin Murphy
0 siblings, 0 replies; 4+ messages in thread
From: Robin Murphy @ 2026-11-02 16:51 UTC (permalink / raw)
To: Tal Zussman, Jason Gunthorpe, Kevin Tian, Joerg Roedel (AMD),
Will Deacon, Greg Kroah-Hartman, Jean-Philippe Brucker
Cc: iommu, linux-kernel
On 31/10/2026 8:33 pm, Tal Zussman wrote:
> 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")
No, the bug is clearly the early return in 2a918911ed3d ("iommufd:
Register iommufd mock devices with fwspec"), so fix it there please.
iommu_fwspec_init() has always been designed to be called in conjunction
with iommu_probe_device() which handles cleanup appropriately. All of
this mock_device hackery not using the proper probe paths is enough of a
pain as it is...
Thanks,
Robin.
> 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;
>
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-11-02 16:51 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
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-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
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®