From: Fred Griffoul <griffoul@gmail.com>
To: Joerg Roedel <joro@8bytes.org>, Will Deacon <will@kernel.org>,
Jason Gunthorpe <jgg@ziepe.ca>, Kevin Tian <kevin.tian@intel.com>
Cc: Robin Murphy <robin.murphy@arm.com>,
Shuah Khan <shuah@kernel.org>,
Mostafa Saleh <smostafa@google.com>,
Guanghui Feng <guanghuifeng@linux.alibaba.com>,
iommu@lists.linux.dev, linux-kernel@vger.kernel.org,
linux-kselftest@vger.kernel.org, nh-open-source@amazon.com
Subject: [PATCH 2/3] iommufd: Use iommu_unmap_sparse() to unpin domain pages
Date: Wed, 11 Nov 2026 18:59:30 +0000 [thread overview]
Message-ID: <20261111185931.94882-3-griffoul@gmail.com> (raw)
In-Reply-To: <20261111185931.94882-1-griffoul@gmail.com>
From: Fred Griffoul <fgriffo@amazon.co.uk>
iommufd keeps no copy of the PFNs it pinned for an area, so
iopt_area_unpin_domain() reads each one back from the domain with
iommu_iova_to_phys(), one CPU page at a time, and then unmaps the range
in a second walk. With 4 KiB pages, a 1 GiB IOPTE costs 262144 lookups
before the unmap starts.
Call iommu_unmap_sparse() instead, and add the PFNs of each IOPTE it
removes to the pfn_batch, so one walk both unmaps the range and
collects the PFNs. The last IOPTE removed can hold pages beyond the
ones this pass unpins. The loop already handles that case: it keeps
those pages in the batch and carries them over to the next pass.
Domains that do not use iommupt keep the per-page read-back, and so do
areas whose IOVA or memory starts or ends inside a CPU page. Such areas
exist only when the smallest page size of every domain in the IOAS is
smaller than PAGE_SIZE.
Signed-off-by: Fred Griffoul <fgriffo@amazon.co.uk>
---
drivers/iommu/iommufd/pages.c | 98 +++++++++++++++++++++++++++++++++--
1 file changed, 93 insertions(+), 5 deletions(-)
diff --git a/drivers/iommu/iommufd/pages.c b/drivers/iommu/iommufd/pages.c
index 2f6153108add7..c2a04a8845cc8 100644
--- a/drivers/iommu/iommufd/pages.c
+++ b/drivers/iommu/iommufd/pages.c
@@ -468,6 +468,79 @@ static void batch_from_domain_continue(struct pfn_batch *batch,
batch->array_size = array_size;
}
+struct batch_unmap {
+ struct pfn_batch *batch;
+ unsigned long first_iova;
+ unsigned long last_iova;
+};
+
+static bool batch_record_unmapped(void *priv, unsigned long iova,
+ phys_addr_t paddr, size_t size)
+{
+ struct batch_unmap *unmap = priv;
+ unsigned long first, end;
+
+ /* Stop after the entry that holds the last page to unpin */
+ if (iova > unmap->last_iova)
+ return false;
+
+ /*
+ * Count each CPU page once, from the entry that holds its first byte.
+ * Pages before first_iova belong to an access and stay pinned.
+ */
+ first = max(ALIGN(iova, PAGE_SIZE), unmap->first_iova);
+ end = ALIGN(iova + size, PAGE_SIZE);
+ if (first >= end)
+ return true;
+ return batch_add_pfn_num(unmap->batch, PHYS_PFN(paddr + (first - iova)),
+ (end - first) >> PAGE_SHIFT, BATCH_CPU_MEMORY);
+}
+
+/* True if the area starts and ends on CPU pages, in both IOVA and memory */
+static bool iopt_area_pages_aligned(struct iopt_area *area)
+{
+ return !area->page_offset &&
+ PAGE_ALIGNED(iopt_area_iova(area) |
+ (iopt_area_last_iova(area) + 1));
+}
+
+/*
+ * Unmap from unmapped_index and collect the PFNs from start_index on into the
+ * batch in one walk. The walk ends after the entry that holds last_index, which
+ * can reach past last_index, or before an entry the batch has no room for.
+ * Returns the number of indexes unmapped, 0 if the caller has to read the PFNs
+ * back with batch_from_domain() instead.
+ */
+static unsigned long batch_unmap_from_domain(struct pfn_batch *batch,
+ struct iommu_domain *domain,
+ struct iopt_area *area,
+ unsigned long unmapped_index,
+ unsigned long start_index,
+ unsigned long last_index,
+ unsigned long real_last_index)
+{
+ unsigned long start = max(start_index, unmapped_index);
+ struct batch_unmap unmap = {
+ .batch = batch,
+ .first_iova = iopt_area_index_to_iova(area, start),
+ .last_iova = iopt_area_index_to_iova_last(area, last_index),
+ };
+ unsigned long iova = iopt_area_index_to_iova(area, unmapped_index);
+ unsigned long last =
+ iopt_area_index_to_iova_last(area, real_last_index);
+ size_t unmapped;
+
+ if (!iopt_area_pages_aligned(area))
+ return 0;
+
+ /* The range runs to the area end, batch_record_unmapped() stops it */
+ unmapped = iommu_unmap_sparse(domain, iova, last - iova + 1,
+ batch_record_unmapped, &unmap);
+ if (IS_ENABLED(CONFIG_IOMMUFD_TEST))
+ WARN_ON(unmapped % PAGE_SIZE);
+ return unmapped >> PAGE_SHIFT;
+}
+
/*
* This is part of the VFIO compatibility support for VFIO_TYPE1_IOMMU. That
* mode permits splitting a mapped area up, and then one of the splits is
@@ -1689,19 +1762,33 @@ iopt_area_unpin_domain(struct pfn_batch *batch, struct iopt_area *area,
{
while (start_index <= last_index) {
unsigned long batch_last_index;
+ bool read_ahead = true;
if (*unmapped_end_index <= last_index) {
unsigned long start =
max(start_index, *unmapped_end_index);
+ unsigned long unmapped, batch_end;
if (IS_ENABLED(CONFIG_IOMMUFD_TEST) &&
batch->total_pfns)
WARN_ON(*unmapped_end_index -
batch->total_pfns !=
start_index);
- batch_from_domain(batch, domain, area, start,
- last_index);
- batch_last_index = start_index + batch->total_pfns - 1;
+ unmapped = batch_unmap_from_domain(batch, domain, area,
+ *unmapped_end_index,
+ start_index,
+ last_index,
+ real_last_index);
+ if (unmapped) {
+ /* The walk ended on an entry boundary */
+ *unmapped_end_index += unmapped;
+ read_ahead = false;
+ } else {
+ batch_from_domain(batch, domain, area, start,
+ last_index);
+ }
+ batch_end = start_index + batch->total_pfns - 1;
+ batch_last_index = min(batch_end, last_index);
} else {
batch_last_index = last_index;
}
@@ -1716,9 +1803,10 @@ iopt_area_unpin_domain(struct pfn_batch *batch, struct iopt_area *area,
* middle of the domains we need to keep reading pfns until we
* find a cut point to do the unmap. The pfns we read are
* carried over and either skipped or integrated into the next
- * batch.
+ * batch. batch_unmap_from_domain() already ends on an entry
+ * boundary and needs no read-ahead.
*/
- if (batch_last_index == last_index &&
+ if (read_ahead && batch_last_index == last_index &&
last_index != real_last_index)
batch_from_domain_continue(batch, domain, area,
last_index + 1,
--
2.47.3
next prev parent reply other threads:[~2026-11-11 18:59 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-11 18:59 [PATCH 0/3] iommupt: Unmap and return the PFNs in one walk Fred Griffoul
2026-11-11 18:59 ` [PATCH 1/3] iommupt: Add iommu_unmap_sparse() Fred Griffoul
2026-11-11 18:59 ` Fred Griffoul [this message]
2026-11-11 18:59 ` [PATCH 3/3] iommufd/selftest: Unmap areas that start inside a CPU page Fred Griffoul
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261111185931.94882-3-griffoul@gmail.com \
--to=griffoul@gmail.com \
--cc=guanghuifeng@linux.alibaba.com \
--cc=iommu@lists.linux.dev \
--cc=jgg@ziepe.ca \
--cc=joro@8bytes.org \
--cc=kevin.tian@intel.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=nh-open-source@amazon.com \
--cc=robin.murphy@arm.com \
--cc=shuah@kernel.org \
--cc=smostafa@google.com \
--cc=will@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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®