Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 22 Jul 2026 15:12:45 +0000
From:      Baptiste Daroussin <bapt@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org
Subject:   git: 4c94869c37e1 - main - uvideo: replace contigmalloc with OBJT_PHYS vm_object for mmap buffer
Message-ID:  <6a60dded.33eb7.43e089@gitrepo.freebsd.org>

index | next in thread | raw e-mail

The branch main has been updated by bapt:

URL: https://cgit.FreeBSD.org/src/commit/?id=4c94869c37e14dc334e2400d1a26d5ded89576fc

commit 4c94869c37e14dc334e2400d1a26d5ded89576fc
Author:     Baptiste Daroussin <bapt@FreeBSD.org>
AuthorDate: 2026-07-22 11:25:31 +0000
Commit:     Baptiste Daroussin <bapt@FreeBSD.org>
CommitDate: 2026-07-22 15:10:57 +0000

    uvideo: replace contigmalloc with OBJT_PHYS vm_object for mmap buffer
    
    Allocate the mmap buffer via phys_pager_allocate() and map it into
    kernel space with vm_map_find()/vm_map_wire(), instead of a custom
    cdev_pager backed by contigmalloc.  phys_pager_allocate() is required
    over a bare vm_object_allocate(OBJT_PHYS) to initialise un_pager.phys.ops,
    otherwise phys_pager_getpages() NULL-derefs during vm_map_wire().
    
    Reviewed by:            markj
    Reported by:            markj
    Differential Revision:  https://reviews.freebsd.org/D58394
---
 sys/dev/usb/video/uvideo.c | 192 ++++++++++++---------------------------------
 1 file changed, 51 insertions(+), 141 deletions(-)

diff --git a/sys/dev/usb/video/uvideo.c b/sys/dev/usb/video/uvideo.c
index 07f74f55b121..bb4d42778998 100644
--- a/sys/dev/usb/video/uvideo.c
+++ b/sys/dev/usb/video/uvideo.c
@@ -46,10 +46,14 @@
 #include <sys/rwlock.h>
 
 #include <vm/vm.h>
-#include <vm/pmap.h>
-#include <vm/vm_page.h>
+#include <vm/vm_extern.h>
+#include <vm/vm_kern.h>
 #include <vm/vm_object.h>
+#include <vm/vm_page.h>
 #include <vm/vm_pager.h>
+#include <vm/vm_param.h>
+#include <vm/pmap.h>
+#include <vm/vm_map.h>
 
 #include <dev/usb/usb.h>
 #include <dev/usb/usbdi.h>
@@ -168,25 +172,6 @@ static d_ioctl_t	uvideo_cdev_ioctl;
 static d_poll_t		uvideo_cdev_poll;
 static d_kqfilter_t	uvideo_cdev_kqfilter;
 static d_mmap_single_t	uvideo_cdev_mmap_single;
-static int		uvideo_pg_ctor(void *, vm_ooffset_t, vm_prot_t,
-			    vm_ooffset_t, struct ucred *, u_short *);
-static void		uvideo_pg_dtor(void *);
-static int		uvideo_pg_fault(vm_object_t, vm_ooffset_t, int,
-			    vm_page_t *);
-
-/*
- * Per-buffer state for mmap lifetime management.  Allocated together
- * with the contig buffer at REQBUFS time and attached to a single
- * shared vm_object whose reference count tracks the active mappings.
- * The buffer is freed by cdev_pg_dtor() when the last reference (the
- * softc's own or a user mapping) is dropped.  Lives independently of
- * the softc so the pager dtor can safely free the buffer even after
- * device detach.
- */
-struct uvideo_mmap_state {
-	uint8_t				*ms_buffer;
-	size_t				ms_buffer_size;
-};
 
 static int	uvideo_querycap(struct uvideo_softc *, struct v4l2_capability *);
 static int	uvideo_enum_fmt(struct uvideo_softc *, struct v4l2_fmtdesc *);
@@ -258,6 +243,7 @@ struct uvideo_softc {
 	struct uvideo_mmap	*sc_mmap_cur;
 	uint8_t			*sc_mmap_buffer;
 	size_t			sc_mmap_buffer_size;
+	vm_offset_t		sc_mmap_kva;
 	int			sc_mmap_buffer_idx;
 	q_mmap			sc_mmap_q;
 	int			sc_mmap_count;
@@ -745,12 +731,6 @@ static const struct usb_config uvideo_bulk_config[1] = {
 	},
 };
 
-static const struct cdev_pager_ops uvideo_pager_ops = {
-	.cdev_pg_ctor = uvideo_pg_ctor,
-	.cdev_pg_dtor = uvideo_pg_dtor,
-	.cdev_pg_fault = uvideo_pg_fault,
-};
-
 /*
  * Character device switch
  */
@@ -940,6 +920,7 @@ uvideo_attach(device_t dev)
 	/* Init mmap queue */
 	STAILQ_INIT(&sc->sc_mmap_q);
 	sc->sc_mmap_count = 0;
+	sc->sc_mmap_kva = 0;
 	sc->sc_mmap_object = NULL;
 
 	/* Allocate unit number and create character device */
@@ -2296,18 +2277,14 @@ uvideo_vs_free_frame(struct uvideo_softc *sc)
 		fb->buf = NULL;
 	}
 
-	if (sc->sc_mmap_object != NULL) {
-		/*
-		 * Drop the softc's reference to the shared mmap object.
-		 * If user-space mappings still exist, the object and its
-		 * backing contig buffer stay alive until the last mapping
-		 * is dropped, at which point uvideo_pg_dtor() frees them.
-		 */
-		vm_object_deallocate(sc->sc_mmap_object);
-		sc->sc_mmap_object = NULL;
+	if (sc->sc_mmap_kva != 0) {
+		vm_map_remove(kernel_map, sc->sc_mmap_kva,
+		    sc->sc_mmap_kva + sc->sc_mmap_buffer_size);
 		sc->sc_mmap_buffer = NULL;
+		sc->sc_mmap_kva = 0;
 		sc->sc_mmap_buffer_size = 0;
 	}
+	sc->sc_mmap_object = NULL;
 
 	while (!STAILQ_EMPTY(&sc->sc_mmap_q))
 		STAILQ_REMOVE_HEAD(&sc->sc_mmap_q, q_frames);
@@ -2896,11 +2873,6 @@ uvideo_cdev_close(struct cdev *dev, int flags, int fmt, struct thread *td)
 		uvideo_vs_close(sc);
 	}
 
-	/*
-	 * Release mmap buffer.  If there are still active mmap
-	 * mappings, the actual contigfree() is deferred to the
-	 * last uvideo_pg_dtor() invocation.
-	 */
 	uvideo_vs_free_frame(sc);
 
 	if (sc->sc_fbuffer != NULL) {
@@ -3157,79 +3129,6 @@ uvideo_cdev_mmap_single(struct cdev *dev, vm_ooffset_t *offset,
 	return (0);
 }
 
-static int
-uvideo_pg_ctor(void *handle, vm_ooffset_t size, vm_prot_t prot,
-    vm_ooffset_t foff, struct ucred *cred, u_short *color)
-{
-	struct uvideo_mmap_state *ms = handle;
-
-	if (ms->ms_buffer == NULL || foff + size > ms->ms_buffer_size)
-		return (EINVAL);
-
-	*color = 0;
-	return (0);
-}
-
-static void
-uvideo_pg_dtor(void *handle)
-{
-	struct uvideo_mmap_state *ms = handle;
-
-	/*
-	 * The last reference to the shared mmap object is gone (either the
-	 * softc released it, or the final user mapping was dropped).  Free
-	 * the backing contig buffer and the state.
-	 */
-	if (ms->ms_buffer != NULL)
-		contigfree(ms->ms_buffer, ms->ms_buffer_size, M_USBDEV);
-	free(ms, M_USBDEV);
-}
-
-static int
-uvideo_pg_fault(vm_object_t object, vm_ooffset_t offset, int prot,
-    vm_page_t *mres)
-{
-	struct uvideo_mmap_state *ms = object->un_pager.devp.handle;
-	vm_paddr_t paddr;
-	vm_page_t m;
-
-	if (ms->ms_buffer == NULL)
-		return (VM_PAGER_FAIL);
-
-	if (offset >= ms->ms_buffer_size)
-		return (VM_PAGER_FAIL);
-
-	paddr = vtophys(ms->ms_buffer + offset);
-	if (paddr == 0)
-		return (VM_PAGER_FAIL);
-
-	if (((*mres)->flags & PG_FICTITIOUS) != 0) {
-		/*
-		 * The passed-in page is already a fake page: just update
-		 * its physical address in place.
-		 */
-		m = *mres;
-		vm_page_updatefake(m, paddr, object->memattr);
-	} else {
-		/*
-		 * Replace the placeholder page allocated by the generic
-		 * fault path with our own fake page.  vm_page_getfake() can
-		 * allocate and must not be called with the object lock held;
-		 * vm_page_replace() then swaps the new page into the object
-		 * and frees the placeholder.  Without this the busy
-		 * placeholder stays in the object and dev_pager_dealloc()
-		 * dead-locks trying to acquire it.
-		 */
-		VM_OBJECT_WUNLOCK(object);
-		m = vm_page_getfake(paddr, object->memattr);
-		VM_OBJECT_WLOCK(object);
-		vm_page_replace(m, object, (*mres)->pindex, *mres);
-		*mres = m;
-	}
-	m->valid = VM_PAGE_BITS_ALL;
-	return (VM_PAGER_OK);
-}
-
 /* ---------------------------------------------------------------- */
 /*  V4L2 Ioctl Handlers                                             */
 /* ---------------------------------------------------------------- */
@@ -3633,7 +3532,9 @@ static int
 uvideo_reqbufs(struct uvideo_softc *sc, struct v4l2_requestbuffers *rb)
 {
 	int i, buf_size, buf_size_total;
-	struct uvideo_mmap_state *ms;
+	vm_object_t obj;
+	vm_offset_t kva;
+	int error;
 
 	DPRINTFN(1, "reqbufs: count=%d\n", rb->count);
 
@@ -3654,39 +3555,48 @@ uvideo_reqbufs(struct uvideo_softc *sc, struct v4l2_requestbuffers *rb)
 	buf_size_total = sc->sc_mmap_count * buf_size;
 	buf_size_total = round_page(buf_size_total);
 
-	sc->sc_mmap_buffer = contigmalloc(buf_size_total, M_USBDEV,
-	    M_WAITOK | M_ZERO, 0, ~0UL, PAGE_SIZE, 0);
-	if (sc->sc_mmap_buffer == NULL) {
-		device_printf(sc->sc_dev, "can't allocate mmap buffer!\n");
+	/*
+	 * Allocate a physical vm_object of the requested size.  Use
+	 * phys_pager_allocate() so that un_pager.phys.ops is properly
+	 * initialised: a bare vm_object_allocate(OBJT_PHYS, ...)
+	 * leaves ops NULL and causes a page fault when the VM system
+	 * calls phys_pager_getpages() during vm_map_wire() or a
+	 * userspace fault on the mmap mapping.
+	 */
+	obj = phys_pager_allocate(NULL, &default_phys_pg_ops, NULL,
+	    buf_size_total, VM_PROT_ALL, 0, curthread->td_ucred);
+	if (obj == NULL) {
+		device_printf(sc->sc_dev, "can't allocate mmap vm_object!\n");
 		sc->sc_mmap_count = 0;
 		return (ENOMEM);
 	}
-	sc->sc_mmap_buffer_size = buf_size_total;
 
-	/*
-	 * Create a single shared vm_object backing the whole mmap buffer.
-	 * Its reference count (bumped by each mmap() and held by the softc)
-	 * tracks the lifetime of the mappings; the contig buffer is freed
-	 * by uvideo_pg_dtor() when the last reference goes away, which may
-	 * be after device detach.
-	 */
-	ms = malloc(sizeof(*ms), M_USBDEV, M_WAITOK | M_ZERO);
-	ms->ms_buffer = sc->sc_mmap_buffer;
-	ms->ms_buffer_size = sc->sc_mmap_buffer_size;
-	sc->sc_mmap_object = cdev_pager_allocate(ms, OBJT_DEVICE,
-	    &uvideo_pager_ops, sc->sc_mmap_buffer_size, VM_PROT_ALL,
-	    0, curthread->td_ucred);
-	if (sc->sc_mmap_object == NULL) {
-		free(ms, M_USBDEV);
-		contigfree(sc->sc_mmap_buffer, sc->sc_mmap_buffer_size,
-		    M_USBDEV);
-		sc->sc_mmap_buffer = NULL;
-		sc->sc_mmap_buffer_size = 0;
+	kva = vm_map_min(kernel_map);
+	error = vm_map_find(kernel_map, obj, 0, &kva, buf_size_total, 0,
+	    VMFS_OPTIMAL_SPACE, VM_PROT_READ | VM_PROT_WRITE,
+	    VM_PROT_READ | VM_PROT_WRITE, 0);
+	if (error != KERN_SUCCESS) {
+		vm_object_deallocate(obj);
+		device_printf(sc->sc_dev, "vm_map_find failed: %d\n", error);
 		sc->sc_mmap_count = 0;
-		return (ENOMEM);
+		return (vm_mmap_to_errno(error));
 	}
+	error = vm_map_wire(kernel_map, kva, kva + buf_size_total,
+	    VM_MAP_WIRE_SYSTEM | VM_MAP_WIRE_NOHOLES);
+	if (error != KERN_SUCCESS) {
+		vm_map_remove(kernel_map, kva, kva + buf_size_total);
+		device_printf(sc->sc_dev, "vm_map_wire failed: %d\n", error);
+		sc->sc_mmap_count = 0;
+		return (vm_mmap_to_errno(error));
+	}
+
+	sc->sc_mmap_object = obj;
+	sc->sc_mmap_buffer = (uint8_t *)kva;
+	sc->sc_mmap_kva = kva;
+	sc->sc_mmap_buffer_size = buf_size_total;
 
-	DPRINTFN(1, "allocated %d bytes mmap buffer\n", buf_size_total);
+	DPRINTFN(1, "allocated %d bytes mmap buffer at kva %#jx\n",
+	    buf_size_total, (uintmax_t)kva);
 
 	for (i = 0; i < sc->sc_mmap_count; i++) {
 		sc->sc_mmap[i].buf = sc->sc_mmap_buffer + (i * buf_size);


home | help

Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?6a60dded.33eb7.43e089>