mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
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


  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®