From: Eliav Farber <farbere@amazon.com>
To: Andrew Morton <akpm@linux-foundation.org>,
Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: David Hildenbrand <david@kernel.org>,
Lorenzo Stoakes <ljs@kernel.org>,
"Liam R. Howlett" <liam@infradead.org>,
Vlastimil Babka <vbabka@kernel.org>,
Mike Rapoport <rppt@kernel.org>,
Suren Baghdasaryan <surenb@google.com>,
Michal Hocko <mhocko@suse.com>, Jann Horn <jannh@google.com>,
Pedro Falcato <pfalcato@suse.de>, <linux-mm@kvack.org>,
<linux-kernel@vger.kernel.org>, Eliav Farber <farbere@amazon.com>
Subject: [PATCH 4/4] uio: implement huge_fault support for physical mappings
Date: Wed, 11 Nov 2026 11:30:45 +0000 [thread overview]
Message-ID: <20261111113045.35368-5-farbere@amazon.com> (raw)
In-Reply-To: <20261111113045.35368-1-farbere@amazon.com>
UIO physical mappings (UIO_MEM_PHYS, UIO_MEM_IOVA) often expose large
device memory regions to userspace. uio_mmap_physical() maps them at
mmap() time with remap_pfn_range(), i.e. with 4K PTEs, even when the
region and the user address are aligned for huge mappings.
Map non-COW mappings on fault instead, as vfio-pci does since commit
f9e54c3a2f5b ("vfio/pci: implement huge_fault support"): call
pfn_range_track() rather than remap_pfn_range(), so the VMA is set up
and its memory type tracked as before, and add ->fault/->huge_fault
handlers that map the faulting window with vmf_insert_pfn_order(), using
a PMD or PUD where alignment allows. Like uio_vma_fault(), the handlers
take idev->info_lock and fail with SIGBUS once the device is gone. COW
mappings cannot be populated by vmf_insert_pfn*() and stay eager.
vm_pgoff holds the UIO map index, which a fault handler cannot use: a
VMA split (partial munmap(), mprotect(), ...) adds the split offset to
vm_pgoff of the upper part, so the index no longer identifies the map.
Store the base pfn in vm_pgoff instead, as remap_pfn_range() does for
COW mappings; splits then keep it correct. As a side effect,
/proc/<pid>/maps shows the physical address as the offset of such a
mapping, rather than N * PAGE_SIZE.
generic_access_phys() only follows present PTEs, so .access now faults
the page in first; otherwise remote access (ptrace, /proc/pid/mem) to an
untouched page would fail. A mapping that does not permit the fault,
PROT_NONE for example, keeps that gap: fixup_user_fault() refuses it,
where the eager mapping left a present PTE.
Validation at mmap() time is unchanged, except for the L1TF pfn check:
remap_pfn_range() rejected a disallowed pfn with -EACCES from mmap(),
where the fault handler now raises SIGBUS on first touch.
Once the device is unregistered, a page with no present entry gets
SIGBUS, where the eager mapping kept it mapped. Not only untouched
pages: splitting a huge entry with a partial munmap() or mprotect()
zaps it, since __split_huge_pmd_locked() clears a PFN-mapped PMD rather
than converting it to PTEs.
Without huge PFN mappings, or for small or unaligned mappings, the
region is populated one page per fault, paid only for pages that are
actually touched.
Signed-off-by: Eliav Farber <farbere@amazon.com>
---
drivers/uio/uio.c | 102 +++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 96 insertions(+), 6 deletions(-)
diff --git a/drivers/uio/uio.c b/drivers/uio/uio.c
index f8fa20522660..6b4c79340e5c 100644
--- a/drivers/uio/uio.c
+++ b/drivers/uio/uio.c
@@ -733,9 +733,80 @@ static int uio_mmap_logical(struct vm_area_struct *vma)
return 0;
}
+/*
+ * Populate UIO physical mappings on fault, using a PMD or PUD mapping where the
+ * architecture, the fault address and the pfn allow it.
+ */
+static vm_fault_t uio_physical_huge_fault(struct vm_fault *vmf,
+ unsigned int order)
+{
+ struct vm_area_struct *vma = vmf->vma;
+ unsigned long addr = vmf->address & ~((PAGE_SIZE << order) - 1);
+ struct uio_device *idev = vma->vm_private_data;
+ vm_fault_t ret = VM_FAULT_SIGBUS;
+ unsigned long pfn;
+
+ /*
+ * vm_pgoff is the base pfn of the mapping (see uio_mmap_physical()).
+ * vmf_pfn_order_ok() ignores pfn if the window starts below vm_start.
+ */
+ pfn = vma->vm_pgoff + ((addr - vma->vm_start) >> PAGE_SHIFT);
+ if (!vmf_pfn_order_ok(vmf, pfn, order))
+ return VM_FAULT_FALLBACK;
+
+ mutex_lock(&idev->info_lock);
+
+ if (idev->info)
+ ret = vmf_insert_pfn_order(vmf, pfn, order);
+
+ mutex_unlock(&idev->info_lock);
+
+ return ret;
+}
+
+static vm_fault_t uio_physical_fault(struct vm_fault *vmf)
+{
+ return uio_physical_huge_fault(vmf, 0);
+}
+
+#ifdef CONFIG_HAVE_IOREMAP_PROT
+/*
+ * generic_access_phys() only follows present PTEs, and a lazily populated
+ * mapping has none for pages the target has not touched yet. Fault the page in
+ * first (through the handlers above, so info_lock is honoured), so that remote
+ * access (ptrace, /proc/pid/mem) keeps working.
+ *
+ * fixup_user_fault() refuses a fault the vma does not permit, so an untouched
+ * page of a mapping without VM_READ stays unreadable, where the eager mapping
+ * left a present PTE.
+ */
+static int uio_physical_access(struct vm_area_struct *vma, unsigned long addr,
+ void *buf, int len, int write)
+{
+ unsigned int fault_flags = FAULT_FLAG_REMOTE;
+
+ if (write)
+ fault_flags |= FAULT_FLAG_WRITE;
+
+ /*
+ * COW mappings are populated at mmap() time, and a write fault would
+ * copy the page instead. With unlocked == NULL, fixup_user_fault()
+ * keeps the mmap lock held and the vma valid.
+ */
+ if (!vma_is_cow_mapping(vma))
+ fixup_user_fault(vma->vm_mm, addr, fault_flags, NULL);
+
+ return generic_access_phys(vma, addr, buf, len, write);
+}
+#endif
+
static const struct vm_operations_struct uio_physical_vm_ops = {
#ifdef CONFIG_HAVE_IOREMAP_PROT
- .access = generic_access_phys,
+ .access = uio_physical_access,
+#endif
+ .fault = uio_physical_fault,
+#ifdef CONFIG_ARCH_SUPPORTS_HUGE_PFNMAP
+ .huge_fault = uio_physical_huge_fault,
#endif
};
@@ -767,11 +838,30 @@ static int uio_mmap_physical(struct vm_area_struct *vma)
* So we just do the physical mmap without a page
* offset.
*/
- return remap_pfn_range(vma,
- vma->vm_start,
- mem->addr >> PAGE_SHIFT,
- vma->vm_end - vma->vm_start,
- vma->vm_page_prot);
+ if (vma_is_cow_mapping(vma)) {
+ /* COW mappings cannot be populated on fault: map them now. */
+ return remap_pfn_range(vma,
+ vma->vm_start,
+ mem->addr >> PAGE_SHIFT,
+ vma->vm_end - vma->vm_start,
+ vma->vm_page_prot);
+ }
+
+ /*
+ * Populate on fault (see uio_physical_huge_fault()). Tracking the PFN
+ * range keeps its memory type reserved, for PAT conflict detection.
+ *
+ * vm_pgoff holds the map index, which the fault handler cannot use:
+ * splitting the VMA adds the page offset of the split to vm_pgoff of
+ * the upper part, so uio_find_mem_index() then returns the wrong map,
+ * or none, and the start of that part would be mapped to mem->addr.
+ * Store the base pfn instead, as remap_pfn_range() does for COW
+ * mappings; VMA splits then keep vm_pgoff correct for each part.
+ */
+ vma->vm_pgoff = mem->addr >> PAGE_SHIFT;
+
+ return pfn_range_track(vma, vma->vm_start, mem->addr >> PAGE_SHIFT,
+ vma->vm_end - vma->vm_start);
}
static int uio_mmap_dma_coherent(struct vm_area_struct *vma)
--
2.47.3
prev parent reply other threads:[~2026-11-11 11:31 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-11 11:30 [PATCH 0/4] mm, uio: huge PFN mappings for /dev/mem and UIO Eliav Farber
2026-11-11 11:30 ` [PATCH 1/4] mm: add MMAP_PFN_TRACK action and pfn_range_track() for lazy PFN maps Eliav Farber
2026-11-11 11:30 ` [PATCH 2/4] mm: add vmf_insert_pfn_order() and vmf_pfn_order_ok() helpers Eliav Farber
2026-11-11 11:30 ` [PATCH 3/4] mm/char-mem: implement huge_fault support for /dev/mem Eliav Farber
2026-11-11 11:30 ` Eliav Farber [this message]
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=20261111113045.35368-5-farbere@amazon.com \
--to=farbere@amazon.com \
--cc=akpm@linux-foundation.org \
--cc=david@kernel.org \
--cc=gregkh@linuxfoundation.org \
--cc=jannh@google.com \
--cc=liam@infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=ljs@kernel.org \
--cc=mhocko@suse.com \
--cc=pfalcato@suse.de \
--cc=rppt@kernel.org \
--cc=surenb@google.com \
--cc=vbabka@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®