Kernel KVM virtualization development
 help / color / mirror / Atom feed
From: Fred Griffoul <griffoul@gmail.com>
To: Ackerley Tng <ackerleytng@google.com>,
	Paolo Bonzini <pbonzini@redhat.com>,
	Sean Christopherson <seanjc@google.com>,
	Jason Gunthorpe <jgg@ziepe.ca>, Kevin Tian <kevin.tian@intel.com>
Cc: "Alex Williamson" <alex@shazbot.org>,
	"Alexander Graf" <graf@amazon.de>,
	"Alexander Viro" <viro@zeniv.linux.org.uk>,
	"Alexey Kardashevskiy" <aik@amd.com>,
	"Andrew Morton" <akpm@linux-foundation.org>,
	"Aneesh Kumar K . V" <aneesh.kumar@kernel.org>,
	"Ashish Kalra" <ashish.kalra@amd.com>,
	"Baolin Wang" <baolin.wang@linux.alibaba.com>,
	"Baolu Lu" <baolu.lu@linux.intel.com>,
	"Bill Wendling" <morbo@google.com>,
	"Borislav Petkov" <bp@alien8.de>,
	"Catalin Marinas" <catalin.marinas@arm.com>,
	"Christian Brauner" <brauner@kernel.org>,
	"Christian König" <christian.koenig@amd.com>,
	"Connor Williamson" <connordw@amazon.com>,
	"Dave Hansen" <dave.hansen@linux.intel.com>,
	"Dave Jiang" <dave.jiang@intel.com>,
	"David Hildenbrand" <david@kernel.org>,
	"David Rientjes" <rientjes@google.com>,
	"David Woodhouse" <dwmw2@infradead.org>,
	"Erdem Aktas" <erdemaktas@google.com>,
	"Frank van der Linden" <fvdl@google.com>,
	"Gregory Price" <gourry@gourry.net>,
	"H . Peter Anvin" <hpa@zytor.com>,
	"Hugh Dickins" <hughd@google.com>,
	"Ingo Molnar" <mingo@redhat.com>,
	"Jacob Pan" <jacob.pan@linux.microsoft.com>,
	"James Houghton" <jthoughton@google.com>,
	"Jan Kara" <jack@suse.cz>, "Jianxiong Gao" <jxgao@google.com>,
	"Joerg Roedel" <joro@8bytes.org>,
	"Joey Gouly" <joey.gouly@arm.com>,
	"John Starks" <John.Starks@microsoft.com>,
	"Jonathan Corbet" <corbet@lwn.net>,
	"Justin Stitt" <justinstitt@google.com>,
	"Lorenzo Stoakes" <ljs@kernel.org>,
	"Marc Zyngier" <maz@kernel.org>,
	"Michael Roth" <michael.roth@amd.com>,
	"Mike Rapoport" <rppt@kernel.org>,
	"Nathan Chancellor" <nathan@kernel.org>,
	"Nick Desaulniers" <ndesaulniers@google.com>,
	"Nicolin Chen" <nicolinc@nvidia.com>,
	"Oliver Upton" <oupton@kernel.org>,
	"Pratyush Yadav" <pratyush@kernel.org>,
	"Randy Dunlap" <rdunlap@infradead.org>,
	"Robin Murphy" <robin.murphy@arm.com>,
	"Samiullah Khawaja" <skhawaja@google.com>,
	"Shuah Khan" <shuah@kernel.org>,
	"Steffen Eiden" <seiden@linux.ibm.com>,
	"Steven Price" <steven.price@arm.com>,
	"Suzuki K Poulose" <suzuki.poulose@arm.com>,
	"Tarun Sahu" <tarunsahu@google.com>,
	"Thomas Gleixner" <tglx@kernel.org>,
	"Tom Lendacky" <thomas.lendacky@amd.com>,
	"Tycho Andersen" <tycho@kernel.org>,
	"Vishal Annapurve" <vannapurve@google.com>,
	"Vlastimil Babka" <vbabka@kernel.org>,
	"Will Deacon" <will@kernel.org>,
	"Xu Yilun" <yilun.xu@linux.intel.com>,
	"Yan Zhao" <yan.y.zhao@intel.com>,
	"Zenghui Yu" <yuzenghui@huawei.com>,
	x86@kernel.org, "Shuah Khan" <skhan@linuxfoundation.org>,
	"Fuad Tabba" <fuad.tabba@linux.dev>,
	kvm@vger.kernel.org, linux-doc@vger.kernel.org,
	linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-kselftest@vger.kernel.org, linux-mm@kvack.org,
	llvm@lists.linux.dev, iommu@lists.linux.dev
Subject: [RFC PATCH 09/14] iommufd: Map guest_memfd files
Date: Tue, 27 Oct 2026 15:46:44 +0000	[thread overview]
Message-ID: <20261027154649.91571-10-griffoul@gmail.com> (raw)
In-Reply-To: <20261027154649.91571-1-griffoul@gmail.com>

From: Fred Griffoul <fgriffo@amazon.co.uk>

A VMM that assigns a device to a guest backed by guest_memfd needs the
IOMMU mappings to follow the guest's shared memory. IOMMU_IOAS_MAP_FILE
pins what it maps, which guest_memfd does not allow.

Accept a guest_memfd in IOMMU_IOAS_MAP_FILE through guest_memfd's export
interface: its pages are not pinned, and each update() unmaps the range
and maps what a lookup returns now, read-only frames without write
permission. This is a demonstration: entries are PAGE_SIZE, a device
that touches the range during an update faults, and a range that fails
to map again stays unmapped, with a warning.

Dirty tracking, the no-IOMMU mode, kernel access, splitting an area and
a frame at address 0 are refused, since iommufd could not follow them.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Fred Griffoul <fgriffo@amazon.co.uk>
---
 drivers/iommu/iommufd/io_pagetable.c |  14 +-
 drivers/iommu/iommufd/io_pagetable.h |  30 ++-
 drivers/iommu/iommufd/pages.c        | 299 ++++++++++++++++++++++++++-
 include/uapi/linux/iommufd.h         |  11 +-
 4 files changed, 349 insertions(+), 5 deletions(-)

diff --git a/drivers/iommu/iommufd/io_pagetable.c b/drivers/iommu/iommufd/io_pagetable.c
index 43d518e4ce58..62fa553cdffc 100644
--- a/drivers/iommu/iommufd/io_pagetable.c
+++ b/drivers/iommu/iommufd/io_pagetable.c
@@ -217,9 +217,13 @@ static int iopt_insert_area(struct io_pagetable *iopt, struct iopt_area *area,
 		return -EPERM;
 
 	area->iommu_prot = iommu_prot;
+	area->gmem = iopt_is_gmem(pages);
 	area->page_offset = start_byte % PAGE_SIZE;
 	if (area->page_offset & (iopt->iova_alignment - 1))
 		return -EINVAL;
+	/* guest_memfd frames are mapped whole, see iopt_gmem_map(). */
+	if (area->gmem && (area->page_offset || !PAGE_ALIGNED(length)))
+		return -EINVAL;
 
 	area->node.start = iova;
 	if (check_add_overflow(iova, length - 1, &area->node.last))
@@ -289,6 +293,9 @@ static int iopt_alloc_area_pages(struct io_pagetable *iopt,
 		case IOPT_ADDRESS_DMABUF:
 			start = elm->start_byte + elm->pages->dmabuf.start;
 			break;
+		case IOPT_ADDRESS_GMEM:
+			start = elm->start_byte + elm->pages->gmem.start;
+			break;
 		}
 		rc = iopt_alloc_iova(iopt, dst_iova, start, length);
 		if (rc)
@@ -515,8 +522,13 @@ int iopt_map_file_pages(struct iommufd_ctx *ictx, struct io_pagetable *iopt,
 		if (!file)
 			return -EBADF;
 
-		pages = iopt_alloc_file_pages(file, start_byte, start, length,
+		/* -ENODEV: not a guest_memfd, so map it as a memfd. */
+		pages = iopt_alloc_gmem_pages(file, start, length,
 					      iommu_prot & IOMMU_WRITE);
+		if (PTR_ERR(pages) == -ENODEV)
+			pages = iopt_alloc_file_pages(file, start_byte, start,
+						      length,
+						      iommu_prot & IOMMU_WRITE);
 		fput(file);
 		if (IS_ERR(pages))
 			return PTR_ERR(pages);
diff --git a/drivers/iommu/iommufd/io_pagetable.h b/drivers/iommu/iommufd/io_pagetable.h
index 0dddfb5db5fd..11e16d22436a 100644
--- a/drivers/iommu/iommufd/io_pagetable.h
+++ b/drivers/iommu/iommufd/io_pagetable.h
@@ -6,6 +6,7 @@
 #define __IO_PAGETABLE_H
 
 #include <linux/dma-buf.h>
+#include <linux/guest_memfd.h>
 #include <linux/interval_tree.h>
 #include <linux/kref.h>
 #include <linux/mutex.h>
@@ -13,6 +14,8 @@
 
 #include "iommufd_private.h"
 
+struct mem_cgroup;
+
 struct iommu_domain;
 
 /*
@@ -48,6 +51,8 @@ struct iopt_area {
 	/* IOMMU_READ, IOMMU_WRITE, etc */
 	int iommu_prot;
 	bool prevent_access : 1;
+	/* The pages come from a guest_memfd and may have holes */
+	bool gmem : 1;
 	unsigned int num_accesses;
 	unsigned int num_locks;
 };
@@ -191,6 +196,7 @@ enum iopt_address_type {
 	IOPT_ADDRESS_USER = 0,
 	IOPT_ADDRESS_FILE,
 	IOPT_ADDRESS_DMABUF,
+	IOPT_ADDRESS_GMEM,
 };
 
 /* One (area, domain) mapping of revocable pages, on iopt_pages::tracker. */
@@ -207,6 +213,17 @@ struct iopt_pages_dmabuf {
 	unsigned long start;
 };
 
+struct iopt_pages_gmem {
+	struct guest_memfd_export export;
+	/* From symbol_get(): KVM may be a module */
+	typeof(kvm_gmem_export_lookup_pfn) *lookup_pfn;
+	typeof(kvm_gmem_export_detach) *detach;
+	/* Byte offset in the guest_memfd, always PAGE_SIZE aligned */
+	unsigned long start;
+	/* Charged for the page tables that a fill allocates */
+	struct mem_cgroup *memcg;
+};
+
 /*
  * This holds a pinned page list for multiple areas of IO address space. The
  * pages always originate from a linear chunk of userspace VA. Multiple
@@ -236,6 +253,8 @@ struct iopt_pages {
 		};
 		/* IOPT_ADDRESS_DMABUF */
 		struct iopt_pages_dmabuf dmabuf;
+		/* IOPT_ADDRESS_GMEM */
+		struct iopt_pages_gmem gmem;
 	};
 	bool writable:1;
 	u8 account_mode;
@@ -260,10 +279,15 @@ static inline bool iopt_is_dmabuf(struct iopt_pages *pages)
 	return pages->type == IOPT_ADDRESS_DMABUF;
 }
 
+static inline bool iopt_is_gmem(struct iopt_pages *pages)
+{
+	return pages->type == IOPT_ADDRESS_GMEM;
+}
+
 /* The owner of the pages can revoke them, so their domains are tracked. */
 static inline bool iopt_pages_tracked(struct iopt_pages *pages)
 {
-	return iopt_is_dmabuf(pages);
+	return iopt_is_dmabuf(pages) || iopt_is_gmem(pages);
 }
 
 static inline bool iopt_dmabuf_revoked(struct iopt_pages *pages)
@@ -285,6 +309,10 @@ struct iopt_pages *iopt_alloc_dmabuf_pages(struct iommufd_ctx *ictx,
 					   unsigned long start_byte,
 					   unsigned long start,
 					   unsigned long length, bool writable);
+struct iopt_pages *iopt_alloc_gmem_pages(struct file *file,
+					 unsigned long start,
+					 unsigned long length,
+					 bool writable);
 void iopt_release_pages(struct kref *kref);
 static inline void iopt_put_pages(struct iopt_pages *pages)
 {
diff --git a/drivers/iommu/iommufd/pages.c b/drivers/iommu/iommufd/pages.c
index cf0f08c4e9a6..88354aa554cf 100644
--- a/drivers/iommu/iommufd/pages.c
+++ b/drivers/iommu/iommufd/pages.c
@@ -52,6 +52,7 @@
 #include <linux/iommu.h>
 #include <linux/iommufd.h>
 #include <linux/kthread.h>
+#include <linux/memcontrol.h>
 #include <linux/overflow.h>
 #include <linux/slab.h>
 #include <linux/sched/mm.h>
@@ -238,6 +239,40 @@ static void iommu_unmap_nofail(struct iommu_domain *domain, unsigned long iova,
 	WARN_ON(ret != size);
 }
 
+/*
+ * guest_memfd pages are mapped with PAGE_SIZE entries, and may have holes.
+ * iommu_unmap() stops at the first IOVA that is not mapped, which
+ * iommu_unmap_nofail() warns about, so find the runs that are mapped and
+ * unmap each one, without touching the holes.
+ * iopt_gmem_map() refuses frame 0, which iova_to_phys() reports for
+ * an IOVA that is not mapped.
+ */
+static void iopt_gmem_unmap(struct iopt_area *area,
+			    struct iommu_domain *domain,
+			    unsigned long start_index,
+			    unsigned long last_index)
+{
+	unsigned long iova = iopt_area_index_to_iova(area, start_index);
+	size_t left = (last_index - start_index + 1) * PAGE_SIZE;
+
+	/* A guest_memfd can be large: yield, once per page scanned. */
+	while (left) {
+		size_t len = 0;
+
+		while (len < left && iommu_iova_to_phys(domain, iova + len)) {
+			len += PAGE_SIZE;
+			cond_resched();
+		}
+		if (len)
+			iommu_unmap_nofail(domain, iova, len);
+		/* Step over the run and the hole page that ended it. */
+		len = min(len + PAGE_SIZE, left);
+		iova += len;
+		left -= len;
+		cond_resched();
+	}
+}
+
 static void iopt_area_unmap_domain_range(struct iopt_area *area,
 					 struct iommu_domain *domain,
 					 unsigned long start_index,
@@ -245,6 +280,11 @@ static void iopt_area_unmap_domain_range(struct iopt_area *area,
 {
 	unsigned long start_iova = iopt_area_index_to_iova(area, start_index);
 
+	if (area->gmem) {
+		iopt_gmem_unmap(area, domain, start_index, last_index);
+		return;
+	}
+
 	iommu_unmap_nofail(domain, start_iova,
 			   iopt_area_index_to_iova_last(area, last_index) -
 				   start_iova + 1);
@@ -1349,6 +1389,10 @@ static int pfn_reader_first(struct pfn_reader *pfns, struct iopt_pages *pages,
 	    WARN_ON(last_index < start_index))
 		return -EINVAL;
 
+	/* guest_memfd pages are read from guest_memfd, see iopt_gmem_map() */
+	if (WARN_ON(iopt_is_gmem(pages)))
+		return -EINVAL;
+
 	rc = pfn_reader_init(pfns, pages, start_index, last_index);
 	if (rc)
 		return rc;
@@ -1647,6 +1691,230 @@ void iopt_pages_untrack_all_domains(struct iopt_area *area,
 	}
 }
 
+static int iopt_gmem_prot(struct iopt_area *area, u32 attrs)
+{
+	int prot = area->iommu_prot;
+
+	if (attrs & GUEST_MEMFD_PFN_READONLY)
+		prot &= ~IOMMU_WRITE;
+	return prot;
+}
+
+/*
+ * Map what guest_memfd backs in [start_index, last_index] of the area into
+ * @domain, with PAGE_SIZE entries, so that updating part of a large block
+ * never has to cut a larger IOMMU page.  On failure, nothing in the range is
+ * mapped.
+ */
+static int iopt_gmem_map(struct iopt_area *area, struct iopt_pages *pages,
+			 struct iommu_domain *domain, unsigned long start_index,
+			 unsigned long last_index)
+{
+	struct guest_memfd_export *e = &pages->gmem.export;
+	unsigned long base = pages->gmem.start >> PAGE_SHIFT;
+	unsigned long index = start_index;
+	int rc = 0;
+
+	lockdep_assert_held(&pages->mutex);
+
+	/* A revoke would race a dirty bitmap read and lose its dirty bits. */
+	if (domain->dirty_ops)
+		return -EOPNOTSUPP;
+
+	/* The device of a no-IOMMU domain keeps the addresses after a revoke. */
+	if (IS_ENABLED(CONFIG_IOMMUFD_NOIOMMU) &&
+	    domain->owner == &iommufd_noiommu_ops)
+		return -EOPNOTSUPP;
+
+	while (index <= last_index) {
+		unsigned long pfn, iova, end, nr;
+		/* No larger than the rest of the range: holes cost per page. */
+		int order = min_t(int, PUD_ORDER, fls_long(last_index - index));
+		u32 attrs;
+		int prot;
+
+		cond_resched();
+
+		rc = pages->gmem.lookup_pfn(e, base + index, &pfn, &order,
+					    &attrs);
+		/* The aligned block of frames, or of holes, around index. */
+		end = ALIGN(base + index + 1, 1UL << order) - base;
+		nr = min(end, last_index + 1) - index;
+		if (rc == -ENOENT) {
+			index += nr;
+			continue;
+		}
+		/* Frame 0 would look unmapped to iopt_gmem_unmap(). */
+		if (!rc && !pfn)
+			rc = -EINVAL;
+		if (rc)
+			goto err_unmap;
+
+		iova = iopt_area_index_to_iova(area, index);
+		prot = iopt_gmem_prot(area, attrs);
+
+		/* A read-only frame in an area that is not readable: a hole. */
+		if (!(prot & (IOMMU_READ | IOMMU_WRITE))) {
+			index += nr;
+			continue;
+		}
+
+		rc = batch_iommu_map_small(domain, iova, PFN_PHYS(pfn),
+					   nr * PAGE_SIZE, prot);
+		if (rc)
+			goto err_unmap;
+		index += nr;
+	}
+	return 0;
+
+err_unmap:
+	if (index > start_index)
+		iopt_gmem_unmap(area, domain, start_index, index - 1);
+	return rc;
+}
+
+/* Map the whole area into every domain of its io_pagetable. */
+static int iopt_gmem_fill_domains(struct iopt_area *area,
+				  struct iopt_pages *pages)
+{
+	struct iommu_domain *domain, *undo;
+	unsigned long index, undo_index;
+	int rc;
+
+	xa_for_each(&area->iopt->domains, index, domain) {
+		rc = iopt_gmem_map(area, pages, domain, iopt_area_index(area),
+				   iopt_area_last_index(area));
+		if (rc)
+			goto err_unmap;
+	}
+	return 0;
+
+err_unmap:
+	xa_for_each(&area->iopt->domains, undo_index, undo) {
+		if (undo_index >= index)
+			break;
+		iopt_gmem_unmap(area, undo, iopt_area_index(area),
+				iopt_area_last_index(area));
+	}
+	return rc;
+}
+
+/*
+ * guest_memfd says that what [start, start + nr_pages) of the file holds may
+ * have changed: in every (area, domain) over part of it, unmap that part and
+ * map what guest_memfd backs now.  A device that accesses it in between, or
+ * after a failure to map, faults.
+ */
+static void iopt_gmem_update(struct guest_memfd_export *e, pgoff_t start,
+			     pgoff_t nr_pages)
+{
+	struct iopt_pages *pages =
+		container_of(e, struct iopt_pages, gmem.export);
+	pgoff_t base = pages->gmem.start >> PAGE_SHIFT;
+	struct iopt_pages_track *track;
+	struct mem_cgroup *old_memcg;
+	unsigned long first, last;
+
+	if (start + nr_pages <= base || start >= base + pages->npages)
+		return;
+	first = max(start, base) - base;
+	last = min(start + nr_pages, base + pages->npages) - base - 1;
+
+	guard(mutex)(&pages->mutex);
+	/* The page tables belong to whoever mapped the file, not to this task. */
+	old_memcg = set_active_memcg(pages->gmem.memcg);
+	list_for_each_entry(track, &pages->tracker, elm) {
+		struct iopt_area *area = track->area;
+		unsigned long s = max(first, iopt_area_index(area));
+		unsigned long l = min(last, iopt_area_last_index(area));
+
+		if (s > l)
+			continue;
+		iopt_gmem_unmap(area, track->domain, s, l);
+		if (iopt_gmem_map(area, pages, track->domain, s, l))
+			pr_warn_ratelimited("iommufd: guest_memfd pages left unmapped after an update\n");
+	}
+	set_active_memcg(old_memcg);
+}
+
+static const struct guest_memfd_export_ops iopt_gmem_export_ops = {
+	.update	= iopt_gmem_update,
+};
+
+/**
+ * iopt_alloc_gmem_pages() - Pages backed by a guest_memfd file
+ * @file: The file
+ * @start: Byte offset in the file
+ * @length: Number of bytes
+ * @writable: The pages may be mapped writable
+ *
+ * The pages are not pinned, and every domain that maps them follows
+ * guest_memfd's changes.
+ *
+ * Return: the pages, ERR_PTR(-ENODEV) if @file is not a guest_memfd file, or
+ * another ERR_PTR().
+ */
+struct iopt_pages *iopt_alloc_gmem_pages(struct file *file,
+					 unsigned long start,
+					 unsigned long length,
+					 bool writable)
+{
+	static struct lock_class_key pages_gmem_mutex_key;
+	int (*attach)(struct file *file, struct guest_memfd_export *e);
+	struct iopt_pages *pages;
+	unsigned long end;
+	int rc;
+
+	/* KVM may be a module, or not loaded: then this is no guest_memfd. */
+	attach = symbol_get(kvm_gmem_export_attach);
+	if (!attach)
+		return ERR_PTR(-ENODEV);
+
+	pages = iopt_alloc_pages(0, length, writable);
+	if (IS_ERR(pages))
+		goto out_put;
+
+	/*
+	 * Pinned pages take the mmap_lock under their mutex.  The mutex of
+	 * guest_memfd pages is taken under guest_memfd's invalidate lock, which
+	 * nests in the mmap_lock of a host fault, so give it its own class.
+	 */
+	lockdep_set_class(&pages->mutex, &pages_gmem_mutex_key);
+
+	/* guest_memfd pages are not pinned, so they are not accounted. */
+	pages->account_mode = IOPT_PAGES_ACCOUNT_NONE;
+	pages->type = IOPT_ADDRESS_GMEM;
+	pages->gmem.start = start;
+	pages->gmem.export.ops = &iopt_gmem_export_ops;
+
+	rc = attach(file, &pages->gmem.export);
+	if (rc) {
+		iopt_put_pages(pages);
+		pages = ERR_PTR(rc);
+		goto out_put;
+	}
+
+	if (check_add_overflow(start, length, &end) ||
+	    end > i_size_read(file_inode(file))) {
+		rc = -EINVAL;
+		goto err_detach;
+	}
+
+	/* The file holds the KVM module from here on. */
+	pages->gmem.lookup_pfn = symbol_get(kvm_gmem_export_lookup_pfn);
+	pages->gmem.detach = symbol_get(kvm_gmem_export_detach);
+	pages->gmem.memcg = get_mem_cgroup_from_mm(current->mm);
+	goto out_put;
+
+err_detach:
+	pages->gmem.detach = symbol_get(kvm_gmem_export_detach);
+	iopt_put_pages(pages);
+	pages = ERR_PTR(rc);
+out_put:
+	symbol_put(kvm_gmem_export_attach);
+	return pages;
+}
+
 void iopt_release_pages(struct kref *kref)
 {
 	struct iopt_pages *pages = container_of(kref, struct iopt_pages, kref);
@@ -1664,6 +1932,14 @@ void iopt_release_pages(struct kref *kref)
 		dma_resv_unlock(dmabuf->resv);
 		dma_buf_detach(dmabuf, pages->dmabuf.attach);
 		dma_buf_put(dmabuf);
+	} else if (iopt_is_gmem(pages)) {
+		if (pages->gmem.detach) {
+			pages->gmem.detach(&pages->gmem.export);
+			symbol_put(kvm_gmem_export_detach);
+		}
+		if (pages->gmem.lookup_pfn)
+			symbol_put(kvm_gmem_export_lookup_pfn);
+		mem_cgroup_put(pages->gmem.memcg);
 	} else if (pages->type == IOPT_ADDRESS_FILE) {
 		fput(pages->file);
 	}
@@ -1814,6 +2090,12 @@ static void iopt_area_unfill_partial_domain(struct iopt_area *area,
  */
 void iopt_area_unmap_domain(struct iopt_area *area, struct iommu_domain *domain)
 {
+	if (area->gmem) {
+		iopt_gmem_unmap(area, domain, iopt_area_index(area),
+				iopt_area_last_index(area));
+		return;
+	}
+
 	iommu_unmap_nofail(domain, iopt_area_iova(area),
 			   iopt_area_length(area));
 }
@@ -1857,6 +2139,11 @@ int iopt_area_fill_domain(struct iopt_area *area, struct iommu_domain *domain)
 	if (iopt_dmabuf_revoked(area->pages))
 		return 0;
 
+	if (iopt_is_gmem(area->pages))
+		return iopt_gmem_map(area, area->pages, domain,
+				     iopt_area_index(area),
+				     iopt_area_last_index(area));
+
 	rc = pfn_reader_first(&pfns, area->pages, iopt_area_index(area),
 			      iopt_area_last_index(area));
 	if (rc)
@@ -1922,7 +2209,11 @@ int iopt_area_fill_domains(struct iopt_area *area, struct iopt_pages *pages)
 			goto out_unlock;
 	}
 
-	if (!iopt_dmabuf_revoked(pages)) {
+	if (iopt_is_gmem(pages)) {
+		rc = iopt_gmem_fill_domains(area, pages);
+		if (rc)
+			goto out_untrack;
+	} else if (!iopt_dmabuf_revoked(pages)) {
 		rc = pfn_reader_first(&pfns, pages, iopt_area_index(area),
 				      iopt_area_last_index(area));
 		if (rc)
@@ -2361,7 +2652,7 @@ int iopt_pages_rw_access(struct iopt_pages *pages, unsigned long start_byte,
 	if ((flags & IOMMUFD_ACCESS_RW_WRITE) && !pages->writable)
 		return -EPERM;
 
-	if (iopt_is_dmabuf(pages))
+	if (iopt_is_dmabuf(pages) || iopt_is_gmem(pages))
 		return -EINVAL;
 
 	if (pages->type != IOPT_ADDRESS_USER)
@@ -2453,6 +2744,10 @@ int iopt_area_add_access(struct iopt_area *area, unsigned long start_index,
 	if (iopt_is_dmabuf(pages))
 		return -EINVAL;
 
+	/* guest_memfd frames may have no struct page, and are not pinned. */
+	if (iopt_is_gmem(pages))
+		return -EINVAL;
+
 	mutex_lock(&pages->mutex);
 	access = iopt_pages_get_exact_access(pages, start_index, last_index);
 	if (access) {
diff --git a/include/uapi/linux/iommufd.h b/include/uapi/linux/iommufd.h
index 206fa667c782..db5f09c21d1f 100644
--- a/include/uapi/linux/iommufd.h
+++ b/include/uapi/linux/iommufd.h
@@ -252,7 +252,7 @@ struct iommu_ioas_noiommu_get_pa {
  * @size: sizeof(struct iommu_ioas_map_file)
  * @flags: same as for iommu_ioas_map
  * @ioas_id: same as for iommu_ioas_map
- * @fd: the memfd or supported dma-buf file to map
+ * @fd: the memfd, guest_memfd or supported dma-buf file to map
  * @start: byte offset from start of the file to map from
  * @length: same as for iommu_ioas_map
  * @iova: same as for iommu_ioas_map
@@ -263,6 +263,15 @@ struct iommu_ioas_noiommu_get_pa {
  * VFIO PCI dma-bufs exported through VFIO_DEVICE_FEATURE_DMA_BUF, and
  * other dma-bufs may be rejected. All other arguments and semantics match
  * those of IOMMU_IOAS_MAP.
+ *
+ * A guest_memfd is also accepted, with @start and @length page aligned. Its
+ * pages are not pinned: only its shared pages that have memory are mapped, a
+ * page as soon as it gets memory, and the mapping follows conversions, punch
+ * holes and its provider's changes. Read-only pages are mapped without write
+ * permission, or not at all without IOMMU_IOAS_MAP_READABLE. Such a mapping
+ * cannot be split by a partial unmap, accessed by the kernel, moved by
+ * IOMMU_IOAS_CHANGE_PROCESS, or used with dirty tracking or the no-IOMMU mode,
+ * and a page at physical address 0 cannot be mapped.
  */
 struct iommu_ioas_map_file {
 	__u32 size;
-- 
2.47.3


  parent reply	other threads:[~2026-10-27 15:47 UTC|newest]

Thread overview: 79+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-22 23:27 [PATCH RFC v2 00/50] guest_memfd alternative providers Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 01/50] KVM: guest_memfd: Split folio allocation and insertion in kvm_gmem_get_folio Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 02/50] KVM: guest_memfd: Route folio allocation through native provider operations Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 03/50] mm: shmem: Implement guest_memfd provider operations for tmpfs Ackerley Tng via B4 Relay
2026-10-23 23:31   ` sashiko-bot
2026-10-22 23:27 ` [PATCH RFC v2 04/50] KVM: guest_memfd: Support provider folio invalidation Ackerley Tng via B4 Relay
2026-10-23 23:31   ` sashiko-bot
2026-10-22 23:27 ` [PATCH RFC v2 05/50] KVM: guest_memfd: Support using resource provider Ackerley Tng via B4 Relay
2026-10-23 23:31   ` sashiko-bot
2026-10-22 23:27 ` [PATCH RFC v2 06/50] KVM: selftests: Add helper to create guest_memfd with a resource file Ackerley Tng via B4 Relay
2026-10-23 23:31   ` sashiko-bot
2026-10-22 23:27 ` [PATCH RFC v2 07/50] KVM: selftests: Test negative validation of resource_fd argument Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 08/50] KVM: selftests: Test rejection of unsupported filesystem for resource_fd Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 09/50] KVM: selftests: Test rejection of memfd " Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 10/50] KVM: selftests: Test rejection of swap tmpfs mounts Ackerley Tng via B4 Relay
2026-10-23 23:31   ` sashiko-bot
2026-10-22 23:27 ` [PATCH RFC v2 11/50] KVM: selftests: Test rejection of hugepage " Ackerley Tng via B4 Relay
2026-10-23 23:32   ` sashiko-bot
2026-10-22 23:27 ` [PATCH RFC v2 12/50] KVM: selftests: Test rejection of regular file in tmpfs mount Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 13/50] KVM: selftests: Test guest_memfd with tmpfs resource pool Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 14/50] KVM: selftests: Test guest_memfd resource pool sharing across instances Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 15/50] KVM: selftests: Test memory allocation against shared tmpfs resource pool Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 16/50] KVM: selftests: Test that shared tmpfs resource pool size limit is respected Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 17/50] KVM: selftests: Test guest execution with tmpfs-backed guest_memfd Ackerley Tng via B4 Relay
2026-10-23 23:32   ` sashiko-bot
2026-10-22 23:27 ` [PATCH RFC v2 18/50] KVM: selftests: Document testing TODOs Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 19/50] KVM: selftests: Test guest_memfd conversions with tmpfs backing Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 20/50] samples/kvm: Add a memory provider sample Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 21/50] samples/kvm: Remove donate, reclaim, and move operations from sample provider Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 22/50] samples/kvm: Remove readonly support " Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 23/50] samples/kvm: Remove child ownership tracking " Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 24/50] samples/kvm: Remove MPS_SETUP support " Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 25/50] samples/kvm: Remove MMAP_CAPABLE flag " Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 26/50] samples/kvm: Remove fixed address and length module parameters Ackerley Tng via B4 Relay
2026-10-22 23:27 ` [PATCH RFC v2 27/50] samples/kvm: Remove mem_provider_ops from sample provider Ackerley Tng via B4 Relay
2026-10-22 23:28 ` [PATCH RFC v2 28/50] samples/kvm: Make provider files independent of a root pool Ackerley Tng via B4 Relay
2026-10-22 23:28 ` [PATCH RFC v2 29/50] samples/kvm: Move set_present ioctl from device to provider file Ackerley Tng via B4 Relay
2026-10-22 23:28 ` [PATCH RFC v2 30/50] KVM: selftests: Add basic test for sample memory provider Ackerley Tng via B4 Relay
2026-10-23 23:32   ` sashiko-bot
2026-10-22 23:28 ` [PATCH RFC v2 31/50] KVM: guest_memfd: Support getting PFNs for stage 2 from providers Ackerley Tng via B4 Relay
2026-10-23 17:23   ` Fred Griffoul
2026-10-23 17:42   ` Fred Griffoul
2026-10-22 23:28 ` [PATCH RFC v2 32/50] KVM: guest_memfd: Refactor error handling in kvm_gmem_fault_user_mapping Ackerley Tng via B4 Relay
2026-10-22 23:28 ` [PATCH RFC v2 33/50] KVM: guest_memfd: Support mmap and user fault handling in PFN mode Ackerley Tng via B4 Relay
2026-10-22 23:28 ` [PATCH RFC v2 34/50] KVM: guest_memfd: Skip checking outstanding references " Ackerley Tng via B4 Relay
2026-10-23 17:29   ` Fred Griffoul
2026-10-22 23:28 ` [PATCH RFC v2 35/50] KVM: guest_memfd: Support converting to shared " Ackerley Tng via B4 Relay
2026-10-22 23:28 ` [PATCH RFC v2 36/50] KVM: guest_memfd: Support preallocating memory via fallocate " Ackerley Tng via B4 Relay
2026-10-22 23:28 ` [PATCH RFC v2 37/50] KVM: guest_memfd: Clamp hole punch range to inode size Ackerley Tng via B4 Relay
2026-10-22 23:28 ` [PATCH RFC v2 38/50] KVM: guest_memfd: Support reclaim during hole punch in PFN mode Ackerley Tng via B4 Relay
2026-10-22 23:28 ` [PATCH RFC v2 39/50] KVM: guest_memfd: Support arch reclaim on inode destruction " Ackerley Tng via B4 Relay
2026-10-22 23:28 ` [PATCH RFC v2 40/50] KVM: guest_memfd: Support put_pfn_range provider op " Ackerley Tng via B4 Relay
2026-10-22 23:28 ` [PATCH RFC v2 41/50] KVM: guest_memfd: Add TODO for memory failure handling " Ackerley Tng via B4 Relay
2026-10-22 23:28 ` [PATCH RFC v2 42/50] KVM: guest_memfd: Allow attaching PFN-backed providers Ackerley Tng via B4 Relay
2026-10-22 23:28 ` [PATCH RFC v2 43/50] KVM: guest_memfd: Export kvm_gmem_revoke for provider-driven revocation Ackerley Tng via B4 Relay
2026-10-22 23:28 ` [PATCH RFC v2 44/50] KVM: selftests: Test flag dependency of PFN_MODE on USE_RESOURCE Ackerley Tng via B4 Relay
2026-10-22 23:28 ` [PATCH RFC v2 45/50] samples/kvm: Implement attach and release provider ops in sample provider Ackerley Tng via B4 Relay
2026-10-23 23:32   ` sashiko-bot
2026-10-22 23:28 ` [PATCH RFC v2 46/50] samples/kvm: Implement get_pfn provider operation Ackerley Tng via B4 Relay
2026-10-22 23:28 ` [PATCH RFC v2 47/50] samples/kvm: Implement put_pfn_range " Ackerley Tng via B4 Relay
2026-10-22 23:28 ` [PATCH RFC v2 48/50] samples/kvm: Support dynamic revocation via kvm_gmem_revoke Ackerley Tng via B4 Relay
2026-10-22 23:28 ` [PATCH RFC v2 49/50] samples/kvm: Implement lookup_pfn_range provider operation Ackerley Tng via B4 Relay
2026-10-22 23:28 ` [PATCH RFC v2 50/50] KVM: selftests: Test guest_memfd conversions with sample memory provider backing Ackerley Tng via B4 Relay
2026-10-23 23:32   ` sashiko-bot
2026-10-27 15:46 ` [RFC PATCH 00/14] guest_memfd: Export guest_memfd files to iommufd Fred Griffoul
2026-10-27 15:46   ` [RFC PATCH 01/14] KVM: guest_memfd: Clamp the order of PFN mode blocks to what KVM can map Fred Griffoul
2026-10-27 15:46   ` [RFC PATCH 02/14] KVM: guest_memfd: Support read-only frames from PFN mode providers Fred Griffoul
2026-10-27 15:46   ` [RFC PATCH 03/14] KVM: guest_memfd: Allow other subsystems to map guest_memfd files Fred Griffoul
2026-10-27 15:46   ` [RFC PATCH 04/14] KVM: guest_memfd: Keep truncated folios until the exports are updated Fred Griffoul
2026-10-27 15:46   ` [RFC PATCH 05/14] KVM: guest_memfd: Export PFN mode files Fred Griffoul
2026-10-27 15:46   ` [RFC PATCH 06/14] KVM: guest_memfd: Report the size of holes in export lookups Fred Griffoul
2026-10-27 15:46   ` [RFC PATCH 07/14] KVM: guest_memfd: Update exports when pages are allocated Fred Griffoul
2026-10-27 15:46   ` [RFC PATCH 08/14] iommufd: Track the domains of revocable pages in struct iopt_pages Fred Griffoul
2026-10-27 15:46   ` Fred Griffoul [this message]
2026-10-27 15:46   ` [RFC PATCH 10/14] iommufd/selftest: Add mock-domain IOVA queries Fred Griffoul
2026-10-27 15:46   ` [RFC PATCH 11/14] KVM: guest_memfd: Add kvm_gmem_restore() for PFN mode providers Fred Griffoul
2026-10-27 15:46   ` [RFC PATCH 12/14] samples/kvm: Add read-only pages and large blocks to the sample provider Fred Griffoul
2026-10-27 15:46   ` [RFC PATCH 13/14] samples/kvm: Back absent pages on demand in " Fred Griffoul
2026-10-27 15:46   ` [RFC PATCH 14/14] KVM: selftests: Test guest_memfd mapped by iommufd 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=20261027154649.91571-10-griffoul@gmail.com \
    --to=griffoul@gmail.com \
    --cc=John.Starks@microsoft.com \
    --cc=ackerleytng@google.com \
    --cc=aik@amd.com \
    --cc=akpm@linux-foundation.org \
    --cc=alex@shazbot.org \
    --cc=aneesh.kumar@kernel.org \
    --cc=ashish.kalra@amd.com \
    --cc=baolin.wang@linux.alibaba.com \
    --cc=baolu.lu@linux.intel.com \
    --cc=bp@alien8.de \
    --cc=brauner@kernel.org \
    --cc=catalin.marinas@arm.com \
    --cc=christian.koenig@amd.com \
    --cc=connordw@amazon.com \
    --cc=corbet@lwn.net \
    --cc=dave.hansen@linux.intel.com \
    --cc=dave.jiang@intel.com \
    --cc=david@kernel.org \
    --cc=dwmw2@infradead.org \
    --cc=erdemaktas@google.com \
    --cc=fuad.tabba@linux.dev \
    --cc=fvdl@google.com \
    --cc=gourry@gourry.net \
    --cc=graf@amazon.de \
    --cc=hpa@zytor.com \
    --cc=hughd@google.com \
    --cc=iommu@lists.linux.dev \
    --cc=jack@suse.cz \
    --cc=jacob.pan@linux.microsoft.com \
    --cc=jgg@ziepe.ca \
    --cc=joey.gouly@arm.com \
    --cc=joro@8bytes.org \
    --cc=jthoughton@google.com \
    --cc=justinstitt@google.com \
    --cc=jxgao@google.com \
    --cc=kevin.tian@intel.com \
    --cc=kvm@vger.kernel.org \
    --cc=linux-doc@vger.kernel.org \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=linux-mm@kvack.org \
    --cc=ljs@kernel.org \
    --cc=llvm@lists.linux.dev \
    --cc=maz@kernel.org \
    --cc=michael.roth@amd.com \
    --cc=mingo@redhat.com \
    --cc=morbo@google.com \
    --cc=nathan@kernel.org \
    --cc=ndesaulniers@google.com \
    --cc=nicolinc@nvidia.com \
    --cc=oupton@kernel.org \
    --cc=pbonzini@redhat.com \
    --cc=pratyush@kernel.org \
    --cc=rdunlap@infradead.org \
    --cc=rientjes@google.com \
    --cc=robin.murphy@arm.com \
    --cc=rppt@kernel.org \
    --cc=seanjc@google.com \
    --cc=seiden@linux.ibm.com \
    --cc=shuah@kernel.org \
    --cc=skhan@linuxfoundation.org \
    --cc=skhawaja@google.com \
    --cc=steven.price@arm.com \
    --cc=suzuki.poulose@arm.com \
    --cc=tarunsahu@google.com \
    --cc=tglx@kernel.org \
    --cc=thomas.lendacky@amd.com \
    --cc=tycho@kernel.org \
    --cc=vannapurve@google.com \
    --cc=vbabka@kernel.org \
    --cc=viro@zeniv.linux.org.uk \
    --cc=will@kernel.org \
    --cc=x86@kernel.org \
    --cc=yan.y.zhao@intel.com \
    --cc=yilun.xu@linux.intel.com \
    --cc=yuzenghui@huawei.com \
    /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