git: 9c9428825f4c - main - video: add generic video(4) capture framework

From: Adrian Chadd <adrian_at_FreeBSD.org>
Date: Sun, 09 Aug 2026 17:01:38 UTC
The branch main has been updated by adrian:

URL: https://cgit.FreeBSD.org/src/commit/?id=9c9428825f4c55e3cb37412c661bb9d385db4c68

commit 9c9428825f4c55e3cb37412c661bb9d385db4c68
Author:     Abdelkader Boudih <freebsd@seuros.com>
AuthorDate: 2026-08-09 16:48:13 +0000
Commit:     Adrian Chadd <adrian@FreeBSD.org>
CommitDate: 2026-08-09 16:48:22 +0000

    video: add generic video(4) capture framework
    
    Add a new video(4) framework that provides /dev/videoN, buffer
    management, mmap lifetime, and V4L2 ioctl dispatch for video capture
    drivers. Hardware drivers implement struct video_hw_ops callbacks
    and use video_buf_acquire/write/done to deliver frames.
    
    Reviewed by:    adrian
    Differential Revision:  https://reviews.freebsd.org/D58367
---
 share/man/man4/Makefile                            |    1 +
 share/man/man4/video.4                             |  101 ++
 sys/conf/files                                     |    6 +-
 sys/dev/usb/video/uvideo.h                         |    2 +-
 sys/dev/video/video.c                              | 1382 ++++++++++++++++++++
 sys/dev/video/video.h                              |  117 ++
 sys/dev/video/video_if.m                           |  196 +++
 sys/dev/video/video_internal.h                     |  117 ++
 sys/modules/Makefile                               |    1 +
 sys/modules/video/Makefile                         |   12 +
 sys/{dev/usb/video/uvideo_v4l2.h => sys/videoio.h} |   34 +-
 11 files changed, 1954 insertions(+), 15 deletions(-)

diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile
index 9ced5eef0af4..34a665e14b72 100644
--- a/share/man/man4/Makefile
+++ b/share/man/man4/Makefile
@@ -1148,6 +1148,7 @@ MAN+=	\
 	uvisor.4 \
 	uvscom.4 \
 	veriexec.4 \
+	video.4 \
 	zyd.4
 
 MLINKS+=geom_zero.4 gzero.4
diff --git a/share/man/man4/video.4 b/share/man/man4/video.4
new file mode 100644
index 000000000000..c578f1bfd4dd
--- /dev/null
+++ b/share/man/man4/video.4
@@ -0,0 +1,101 @@
+.\"
+.\" Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com>
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd July 28, 2026
+.Dt VIDEO 4
+.Os
+.Sh NAME
+.Nm video
+.Nd video capture framework
+.Sh SYNOPSIS
+.Cd device video
+.Pp
+.In sys/videoio.h
+.Sh DESCRIPTION
+The
+.Nm
+framework provides a common interface for video capture devices.
+It creates
+.Pa /dev/video%d
+device nodes and handles buffer management, memory mapping, and
+ioctl dispatch on behalf of hardware drivers.
+.Pp
+Userland applications access capture devices through the
+.Sy Video4Linux2
+.Pq V4L2
+ioctl interface.
+The framework translates V4L2 requests into driver callbacks,
+so hardware drivers do not implement ioctls directly.
+.Pp
+The following V4L2 ioctls are supported:
+.Bl -bullet -compact
+.It
+.Dv VIDIOC_QUERYCAP
+.It
+.Dv VIDIOC_ENUM_FMT ,
+.Dv VIDIOC_G_FMT ,
+.Dv VIDIOC_S_FMT ,
+.Dv VIDIOC_TRY_FMT
+.It
+.Dv VIDIOC_ENUM_FRAMESIZES ,
+.Dv VIDIOC_ENUM_FRAMEINTERVALS
+.It
+.Dv VIDIOC_G_PARM ,
+.Dv VIDIOC_S_PARM
+.It
+.Dv VIDIOC_ENUMINPUT ,
+.Dv VIDIOC_G_INPUT ,
+.Dv VIDIOC_S_INPUT
+.It
+.Dv VIDIOC_QUERYCTRL ,
+.Dv VIDIOC_G_CTRL ,
+.Dv VIDIOC_S_CTRL
+.It
+.Dv VIDIOC_REQBUFS ,
+.Dv VIDIOC_QUERYBUF ,
+.Dv VIDIOC_QBUF ,
+.Dv VIDIOC_DQBUF
+.It
+.Dv VIDIOC_STREAMON ,
+.Dv VIDIOC_STREAMOFF
+.El
+.Pp
+Two capture modes are supported:
+.Bl -tag -width 6n
+.It Cm read
+Frames are obtained by calling
+.Xr read 2
+on the device node.
+Streaming starts automatically on the first read.
+.It Cm mmap
+Buffers are allocated with
+.Dv VIDIOC_REQBUFS ,
+mapped with
+.Xr mmap 2 ,
+and cycled with
+.Dv VIDIOC_QBUF
+and
+.Dv VIDIOC_DQBUF .
+Streaming is started and stopped with
+.Dv VIDIOC_STREAMON
+and
+.Dv VIDIOC_STREAMOFF .
+.El
+.Pp
+Up to 8 buffers may be allocated per device.
+Buffer memory remains valid until the last mapping is unmapped,
+even if the device is closed or detached.
+.Sh SEE ALSO
+.Xr mmap 2 ,
+.Xr read 2 ,
+.Xr fwcam 4 ,
+.Xr uvideo 4
+.Sh HISTORY
+The
+.Nm
+framework first appeared in
+.Fx 16.0 .
+.Sh AUTHORS
+.An Abdelkader Boudih Aq Mt freebsd@seuros.com
diff --git a/sys/conf/files b/sys/conf/files
index 165bcba4356b..44cb551d7fe7 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -1740,7 +1740,7 @@ dev/filemon/filemon.c		optional filemon
 dev/firewire/firewire.c		optional firewire
 dev/firewire/fwcrom.c		optional firewire
 dev/firewire/fwdev.c		optional firewire
-dev/firewire/fwcam.c		optional fwcam
+dev/firewire/fwcam.c		optional fwcam video
 dev/firewire/fwdma.c		optional firewire
 dev/firewire/fwdv.c		optional fwdv
 dev/firewire/fwisound.c		optional fwisound sound
@@ -3514,10 +3514,12 @@ dev/usb/template/usb_template_cdceem.c	optional usb_template
 # USB video drivers
 #
 dev/usb/video/udl.c			optional udl
-dev/usb/video/uvideo.c			optional uvideo
+dev/usb/video/uvideo.c			optional uvideo video
 #
 # USB END
 #
+dev/video/video.c			optional video
+dev/video/video_if.m			optional video
 dev/videomode/videomode.c		optional videomode
 dev/videomode/edid.c			optional videomode
 dev/videomode/pickmode.c		optional videomode
diff --git a/sys/dev/usb/video/uvideo.h b/sys/dev/usb/video/uvideo.h
index 9f8570aaf7ed..fbcc0b2ec930 100644
--- a/sys/dev/usb/video/uvideo.h
+++ b/sys/dev/usb/video/uvideo.h
@@ -21,7 +21,7 @@
 #define _UVIDEO_H_
 
 #include <sys/queue.h>
-#include "uvideo_v4l2.h"
+#include <sys/videoio.h>
 
 /*
  * USB Video Class
diff --git a/sys/dev/video/video.c b/sys/dev/video/video.c
new file mode 100644
index 000000000000..e2de3974c607
--- /dev/null
+++ b/sys/dev/video/video.c
@@ -0,0 +1,1382 @@
+/*
+ * Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com>
+ * Copyright (c) 2008 Robert Nagy <robert@openbsd.org>
+ * Copyright (c) 2008 Marcus Glocker <mglocker@openbsd.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause AND ISC
+ *
+ * The cdev, kqfilter and buffer-pool handling are derived from uvideo(4).
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/conf.h>
+#include <sys/fcntl.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/rwlock.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/poll.h>
+#include <sys/proc.h>
+#include <sys/selinfo.h>
+#include <sys/sx.h>
+#include <sys/uio.h>
+#include <sys/videoio.h>
+
+#include <vm/vm.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 <vm/uma.h>
+
+#include <dev/video/video_internal.h>
+
+#include "video_if.h"
+
+MALLOC_DEFINE(M_VIDEO, "video", "video(4) framework");
+
+#define	VIDEO_UNIT_RETRIES	32
+
+static struct unrhdr *video_unrhdr;
+
+static d_open_t		video_open;
+static d_close_t	video_close;
+static d_read_t		video_read;
+static d_ioctl_t	video_ioctl;
+static d_poll_t		video_poll;
+static d_kqfilter_t	video_kqfilter;
+static d_mmap_single_t	video_mmap_single;
+
+static struct cdevsw video_cdevsw = {
+	.d_version =	D_VERSION,
+	.d_open =	video_open,
+	.d_close =	video_close,
+	.d_read =	video_read,
+	.d_ioctl =	video_ioctl,
+	.d_poll =	video_poll,
+	.d_kqfilter =	video_kqfilter,
+	.d_mmap_single = video_mmap_single,
+	.d_name =	"video",
+};
+
+static struct video_buf_pool *
+video_pool_alloc(u_int nbufs, size_t buf_size)
+{
+	struct video_buf_pool *pool;
+	struct video_buf *vb;
+	size_t map_size;
+	vm_object_t obj;
+	vm_offset_t kva;
+	u_int i;
+	int error;
+
+	buf_size = round_page(buf_size);
+	if (buf_size == 0 || nbufs == 0 || nbufs > VIDEO_MAX_BUFFERS)
+		return (NULL);
+	if (SIZE_MAX / nbufs < buf_size)
+		return (NULL);
+	map_size = (size_t)nbufs * buf_size;
+
+	/*
+	 * Use phys_pager_allocate() rather than a bare vm_object_allocate():
+	 * the latter leaves un_pager.phys.ops NULL, which faults when the VM
+	 * system calls phys_pager_getpages() from vm_map_wire() or from a
+	 * userspace fault on the mapping.
+	 */
+	obj = phys_pager_allocate(NULL, &default_phys_pg_ops, NULL, map_size,
+	    VM_PROT_ALL, 0, curthread->td_ucred);
+	if (obj == NULL)
+		return (NULL);
+
+	kva = vm_map_min(kernel_map);
+	error = vm_map_find(kernel_map, obj, 0, &kva, map_size, 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);
+		return (NULL);
+	}
+	error = vm_map_wire(kernel_map, kva, kva + map_size,
+	    VM_MAP_WIRE_SYSTEM | VM_MAP_WIRE_NOHOLES);
+	if (error != KERN_SUCCESS) {
+		vm_map_remove(kernel_map, kva, kva + map_size);
+		return (NULL);
+	}
+
+	pool = malloc(sizeof(*pool), M_VIDEO, M_WAITOK | M_ZERO);
+	pool->obj = obj;
+	pool->kva = kva;
+	pool->nbufs = nbufs;
+	pool->buf_size = buf_size;
+	pool->map_size = map_size;
+
+	for (i = 0; i < nbufs; i++) {
+		vb = &pool->bufs[i];
+		vb->pool = pool;
+		vb->index = i;
+		vb->state = VB_IDLE;
+		vb->length = buf_size;
+		vb->buf = (uint8_t *)kva + (size_t)i * buf_size;
+	}
+	return (pool);
+}
+
+static void
+video_pool_free(struct video_buf_pool *pool)
+{
+
+	/*
+	 * Removing the kernel mapping drops the map's reference on the
+	 * object.  Any surviving userspace mapping holds its own reference,
+	 * so the frames stay alive until the last one goes away.
+	 */
+	vm_map_remove(kernel_map, pool->kva, pool->kva + pool->map_size);
+	free(pool, M_VIDEO);
+}
+
+static void
+video_pool_detach(struct video_device *vd)
+{
+	struct video_buf_pool *pool;
+
+	mtx_lock(&vd->mtx);
+	if (vd->owner != NULL) {
+		vd->owner->read_buf = NULL;
+		vd->owner->read_offset = 0;
+	}
+	while (vd->readers > 0)
+		msleep(&vd->readers, &vd->mtx, 0, "vdrain", 0);
+	pool = vd->pool;
+	vd->pool = NULL;
+	STAILQ_INIT(&vd->queued);
+	STAILQ_INIT(&vd->done);
+	mtx_unlock(&vd->mtx);
+
+	if (pool != NULL)
+		video_pool_free(pool);
+}
+
+static void
+video_file_dtor(void *arg)
+{
+	struct video_file *vf = arg;
+	struct video_device *vd = vf->vd;
+
+	sx_xlock(&vd->cfg_sx);
+
+	if (vf->is_owner) {
+		if (vd->streaming) {
+			mtx_lock(&vd->mtx);
+			vd->stopping = true;
+			mtx_unlock(&vd->mtx);
+
+			VIDEO_STOP_STREAM(vd->dev);
+
+			mtx_lock(&vd->mtx);
+			vd->streaming = false;
+			vd->stopping = false;
+			STAILQ_INIT(&vd->queued);
+			STAILQ_INIT(&vd->done);
+			mtx_unlock(&vd->mtx);
+		}
+
+		video_pool_detach(vd);
+
+		mtx_lock(&vd->mtx);
+		vd->owner = NULL;
+		mtx_unlock(&vd->mtx);
+		vd->mode = VMODE_NONE;
+
+		VIDEO_CLOSE(vd->dev);
+	}
+
+	sx_xunlock(&vd->cfg_sx);
+
+	free(vf, M_VIDEO);
+}
+
+static int
+video_open(struct cdev *dev, int flags, int fmt, struct thread *td)
+{
+	struct video_device *vd = dev->si_drv1;
+	struct video_file *vf;
+
+	if (vd == NULL || vd->dying)
+		return (ENXIO);
+
+	vf = malloc(sizeof(*vf), M_VIDEO, M_WAITOK | M_ZERO);
+	vf->vd = vd;
+
+	return (devfs_set_cdevpriv(vf, video_file_dtor));
+}
+
+static int
+video_close(struct cdev *dev, int flags, int fmt, struct thread *td)
+{
+
+	return (0);
+}
+
+static int
+video_claim_owner(struct video_device *vd, struct video_file *vf)
+{
+	int error;
+
+	sx_assert(&vd->cfg_sx, SA_XLOCKED);
+
+	if (vd->dying)
+		return (ENXIO);
+	if (vd->owner == vf)
+		return (0);
+	if (vd->owner != NULL)
+		return (EBUSY);
+
+	error = VIDEO_OPEN(vd->dev);
+	if (error != 0)
+		return (error);
+
+	mtx_lock(&vd->mtx);
+	vd->owner = vf;
+	mtx_unlock(&vd->mtx);
+	vf->is_owner = true;
+	return (0);
+}
+
+struct video_buf *
+video_buf_acquire(struct video_device *vd)
+{
+	struct video_buf *vb;
+
+	mtx_lock(&vd->mtx);
+	vb = STAILQ_FIRST(&vd->queued);
+	if (vb != NULL) {
+		STAILQ_REMOVE_HEAD(&vd->queued, entry);
+		vb->state = VB_ACTIVE;
+		vb->bytesused = 0;
+	}
+	mtx_unlock(&vd->mtx);
+	return (vb);
+}
+
+int
+video_buf_write(struct video_buf *vb, size_t offset, const void *src,
+    size_t len)
+{
+
+	if (offset > vb->length || len > vb->length - offset)
+		return (ENOSPC);
+
+	memcpy((uint8_t *)vb->buf + offset, src, len);
+	return (0);
+}
+
+void
+video_buf_done(struct video_buf *vb, size_t bytesused, uint32_t sequence)
+{
+	struct video_device *vd;
+	bool overrun;
+
+	overrun = bytesused > vb->length;
+	if (overrun)
+		bytesused = 0;
+
+	vd = vb->vd;
+	mtx_lock(&vd->mtx);
+	if (vb->state != VB_ACTIVE) {
+		mtx_unlock(&vd->mtx);
+		return;
+	}
+	vb->bytesused = bytesused;
+	vb->sequence = sequence;
+	getmicrouptime(&vb->timestamp);
+	vb->flags = V4L2_BUF_FLAG_DONE | V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+	if (overrun)
+		vb->flags |= V4L2_BUF_FLAG_ERROR;
+	vb->state = VB_DONE;
+	STAILQ_INSERT_TAIL(&vd->done, vb, entry);
+	selwakeup(&vd->sel);
+	wakeup(&vd->done);
+	mtx_unlock(&vd->mtx);
+}
+
+void
+video_buf_error(struct video_buf *vb)
+{
+	struct video_device *vd = vb->vd;
+
+	mtx_lock(&vd->mtx);
+	if (vb->state != VB_ACTIVE) {
+		mtx_unlock(&vd->mtx);
+		return;
+	}
+	vb->bytesused = 0;
+	vb->flags = V4L2_BUF_FLAG_ERROR;
+	getmicrouptime(&vb->timestamp);
+	vb->state = VB_ERROR;
+	STAILQ_INSERT_TAIL(&vd->done, vb, entry);
+	selwakeup(&vd->sel);
+	wakeup(&vd->done);
+	mtx_unlock(&vd->mtx);
+}
+
+static int
+video_querycap(struct video_device *vd, struct v4l2_capability *cap)
+{
+	struct video_caps vc;
+	int error;
+
+	memset(&vc, 0, sizeof(vc));
+	error = VIDEO_QUERYCAP(vd->dev, &vc);
+	if (error != 0)
+		return (error);
+
+	memset(cap, 0, sizeof(*cap));
+	strlcpy((char *)cap->driver, vc.driver, sizeof(cap->driver));
+	strlcpy((char *)cap->card, vc.card, sizeof(cap->card));
+	strlcpy((char *)cap->bus_info, vc.bus_info, sizeof(cap->bus_info));
+	cap->version = vc.version;
+	cap->device_caps = vc.capabilities;
+	cap->capabilities = vc.capabilities | V4L2_CAP_DEVICE_CAPS;
+	return (0);
+}
+
+static int
+video_enum_fmt(struct video_device *vd, struct v4l2_fmtdesc *fd)
+{
+	struct video_format vf;
+	int error;
+
+	if (fd->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return (EINVAL);
+
+	memset(&vf, 0, sizeof(vf));
+	error = VIDEO_ENUM_FORMAT(vd->dev, fd->index, &vf);
+	if (error != 0)
+		return (error);
+
+	fd->pixelformat = vf.pixelformat;
+	fd->flags = vf.flags;
+	strlcpy((char *)fd->description, vf.description,
+	    sizeof(fd->description));
+	return (0);
+}
+
+static int
+video_g_fmt(struct video_device *vd, struct v4l2_format *f)
+{
+	struct video_format vf;
+	int error;
+
+	if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return (EINVAL);
+
+	memset(&vf, 0, sizeof(vf));
+	error = VIDEO_GET_FORMAT(vd->dev, &vf);
+	if (error != 0)
+		return (error);
+
+	memset(&f->fmt.pix, 0, sizeof(f->fmt.pix));
+	f->fmt.pix.width = vf.width;
+	f->fmt.pix.height = vf.height;
+	f->fmt.pix.pixelformat = vf.pixelformat;
+	f->fmt.pix.field = vf.field;
+	f->fmt.pix.bytesperline = vf.bytesperline;
+	f->fmt.pix.sizeimage = vf.sizeimage;
+	f->fmt.pix.colorspace = vf.colorspace;
+	f->fmt.pix.xfer_func = vf.xfer_func;
+	f->fmt.pix.ycbcr_enc = vf.ycbcr_enc;
+	f->fmt.pix.flags = vf.flags;
+	return (0);
+}
+
+static int
+video_s_fmt(struct video_device *vd, struct video_file *vf,
+    struct v4l2_format *f)
+{
+	struct video_format fmt;
+	int error;
+
+	if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return (EINVAL);
+
+	sx_xlock(&vd->cfg_sx);
+	error = video_claim_owner(vd, vf);
+	if (error != 0) {
+		sx_xunlock(&vd->cfg_sx);
+		return (error);
+	}
+	if (vd->streaming) {
+		sx_xunlock(&vd->cfg_sx);
+		return (EBUSY);
+	}
+
+	memset(&fmt, 0, sizeof(fmt));
+	fmt.width = f->fmt.pix.width;
+	fmt.height = f->fmt.pix.height;
+	fmt.pixelformat = f->fmt.pix.pixelformat;
+	fmt.field = f->fmt.pix.field;
+
+	error = VIDEO_SET_FORMAT(vd->dev, &fmt);
+	if (error == 0) {
+		VIDEO_GET_FORMAT(vd->dev, &vd->format);
+
+		f->fmt.pix.width = vd->format.width;
+		f->fmt.pix.height = vd->format.height;
+		f->fmt.pix.pixelformat = vd->format.pixelformat;
+		f->fmt.pix.field = vd->format.field;
+		f->fmt.pix.bytesperline = vd->format.bytesperline;
+		f->fmt.pix.sizeimage = vd->format.sizeimage;
+		f->fmt.pix.colorspace = vd->format.colorspace;
+	}
+	sx_xunlock(&vd->cfg_sx);
+	return (error);
+}
+
+static int
+video_try_fmt(struct video_device *vd, struct v4l2_format *f)
+{
+	struct video_format fmt;
+	int error;
+
+	if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return (EINVAL);
+	memset(&fmt, 0, sizeof(fmt));
+	fmt.width = f->fmt.pix.width;
+	fmt.height = f->fmt.pix.height;
+	fmt.pixelformat = f->fmt.pix.pixelformat;
+	fmt.field = f->fmt.pix.field;
+
+	error = VIDEO_TRY_FORMAT(vd->dev, &fmt);
+	if (error == 0) {
+		f->fmt.pix.width = fmt.width;
+		f->fmt.pix.height = fmt.height;
+		f->fmt.pix.pixelformat = fmt.pixelformat;
+		f->fmt.pix.field = fmt.field;
+		f->fmt.pix.bytesperline = fmt.bytesperline;
+		f->fmt.pix.sizeimage = fmt.sizeimage;
+		f->fmt.pix.colorspace = fmt.colorspace;
+	}
+	return (error);
+}
+
+static int
+video_reqbufs(struct video_device *vd, struct video_file *vf,
+    struct v4l2_requestbuffers *rb)
+{
+	struct video_buf_pool *pool;
+	int error;
+
+	if (rb->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return (EINVAL);
+	if (rb->memory != V4L2_MEMORY_MMAP)
+		return (EINVAL);
+
+	sx_xlock(&vd->cfg_sx);
+	error = video_claim_owner(vd, vf);
+	if (error != 0) {
+		sx_xunlock(&vd->cfg_sx);
+		return (error);
+	}
+	if (vd->streaming) {
+		sx_xunlock(&vd->cfg_sx);
+		return (EBUSY);
+	}
+
+	video_pool_detach(vd);
+
+	if (rb->count == 0) {
+		vd->mode = VMODE_NONE;
+		sx_xunlock(&vd->cfg_sx);
+		rb->capabilities = V4L2_BUF_CAP_SUPPORTS_MMAP;
+		return (0);
+	}
+
+	if (rb->count > VIDEO_MAX_BUFFERS)
+		rb->count = VIDEO_MAX_BUFFERS;
+	if (rb->count < 2)
+		rb->count = 2;
+
+	if (vd->format.sizeimage == 0) {
+		error = VIDEO_GET_FORMAT(vd->dev, &vd->format);
+		if (error != 0 || vd->format.sizeimage == 0) {
+			sx_xunlock(&vd->cfg_sx);
+			return (error != 0 ? error : EINVAL);
+		}
+	}
+
+	pool = video_pool_alloc(rb->count, vd->format.sizeimage);
+	if (pool == NULL) {
+		sx_xunlock(&vd->cfg_sx);
+		return (ENOMEM);
+	}
+	mtx_lock(&vd->mtx);
+	vd->pool = pool;
+	mtx_unlock(&vd->mtx);
+
+	vd->mode = VMODE_MMAP;
+	rb->capabilities = V4L2_BUF_CAP_SUPPORTS_MMAP;
+	sx_xunlock(&vd->cfg_sx);
+	return (0);
+}
+
+static int
+video_querybuf(struct video_device *vd, struct video_file *vf,
+    struct v4l2_buffer *b)
+{
+	struct video_buf *vb;
+
+	if (b->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return (EINVAL);
+
+	mtx_lock(&vd->mtx);
+	if (vd->owner != vf) {
+		mtx_unlock(&vd->mtx);
+		return (EPERM);
+	}
+	if (vd->pool == NULL || b->index >= vd->pool->nbufs) {
+		mtx_unlock(&vd->mtx);
+		return (EINVAL);
+	}
+
+	vb = &vd->pool->bufs[b->index];
+	b->memory = V4L2_MEMORY_MMAP;
+	b->length = vb->length;
+	b->m.offset = vb->index * vd->pool->buf_size;
+	b->bytesused = vb->bytesused;
+	b->field = V4L2_FIELD_NONE;
+	b->timestamp = vb->timestamp;
+	b->sequence = vb->sequence;
+	b->flags = 0;
+
+	switch (vb->state) {
+	case VB_QUEUED:
+	case VB_ACTIVE:
+		b->flags |= V4L2_BUF_FLAG_QUEUED;
+		break;
+	case VB_DONE:
+		b->flags |= V4L2_BUF_FLAG_DONE;
+		break;
+	case VB_ERROR:
+		b->flags |= V4L2_BUF_FLAG_DONE | V4L2_BUF_FLAG_ERROR;
+		break;
+	default:
+		break;
+	}
+	b->flags |= V4L2_BUF_FLAG_MAPPED | V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+	mtx_unlock(&vd->mtx);
+	return (0);
+}
+
+static int
+video_qbuf(struct video_device *vd, struct video_file *vf,
+    struct v4l2_buffer *b)
+{
+	struct video_buf *vb;
+
+	if (b->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return (EINVAL);
+	if (b->memory != V4L2_MEMORY_MMAP)
+		return (EINVAL);
+
+	mtx_lock(&vd->mtx);
+	if (vd->owner != vf) {
+		mtx_unlock(&vd->mtx);
+		return (EPERM);
+	}
+	if (vd->pool == NULL || vd->mode != VMODE_MMAP ||
+	    b->index >= vd->pool->nbufs) {
+		mtx_unlock(&vd->mtx);
+		return (EINVAL);
+	}
+
+	vb = &vd->pool->bufs[b->index];
+	if (vb->state != VB_IDLE) {
+		mtx_unlock(&vd->mtx);
+		return (EINVAL);
+	}
+
+	vb->state = VB_QUEUED;
+	vb->bytesused = 0;
+	vb->vd = vd;
+	STAILQ_INSERT_TAIL(&vd->queued, vb, entry);
+	mtx_unlock(&vd->mtx);
+
+	b->flags = V4L2_BUF_FLAG_QUEUED | V4L2_BUF_FLAG_MAPPED;
+	return (0);
+}
+
+static int
+video_dqbuf(struct video_device *vd, struct video_file *vf,
+    struct v4l2_buffer *b, int flags)
+{
+	struct video_buf *vb;
+	int error;
+
+	if (b->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return (EINVAL);
+	if (b->memory != V4L2_MEMORY_MMAP)
+		return (EINVAL);
+
+	mtx_lock(&vd->mtx);
+	if (vd->owner != vf) {
+		mtx_unlock(&vd->mtx);
+		return (EPERM);
+	}
+	while (STAILQ_EMPTY(&vd->done)) {
+		if (!vd->streaming || vd->stopping) {
+			mtx_unlock(&vd->mtx);
+			return (EINVAL);
+		}
+		if (flags & O_NONBLOCK) {
+			mtx_unlock(&vd->mtx);
+			return (EAGAIN);
+		}
+		error = msleep(&vd->done, &vd->mtx, PCATCH, "vdqbuf", 0);
+		if (error != 0) {
+			mtx_unlock(&vd->mtx);
+			return (error);
+		}
+	}
+
+	vb = STAILQ_FIRST(&vd->done);
+	STAILQ_REMOVE_HEAD(&vd->done, entry);
+
+	b->index = vb->index;
+	b->bytesused = vb->bytesused;
+	b->field = V4L2_FIELD_NONE;
+	b->timestamp = vb->timestamp;
+	b->sequence = vb->sequence;
+	b->memory = V4L2_MEMORY_MMAP;
+	b->m.offset = vb->index * vd->pool->buf_size;
+	b->length = vb->length;
+	b->flags = V4L2_BUF_FLAG_MAPPED | V4L2_BUF_FLAG_DONE |
+	    V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+	if (vb->flags & V4L2_BUF_FLAG_ERROR)
+		b->flags |= V4L2_BUF_FLAG_ERROR;
+
+	vb->state = VB_IDLE;
+	mtx_unlock(&vd->mtx);
+
+	return (0);
+}
+
+static int
+video_streamon(struct video_device *vd, struct video_file *vf, int *type)
+{
+	int error;
+
+	if (*type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return (EINVAL);
+
+	sx_xlock(&vd->cfg_sx);
+	error = video_claim_owner(vd, vf);
+	if (error != 0) {
+		sx_xunlock(&vd->cfg_sx);
+		return (error);
+	}
+	if (vd->streaming) {
+		sx_xunlock(&vd->cfg_sx);
+		return (0);
+	}
+	if (vd->pool == NULL || vd->mode != VMODE_MMAP) {
+		sx_xunlock(&vd->cfg_sx);
+		return (EINVAL);
+	}
+
+	error = VIDEO_START_STREAM(vd->dev);
+	if (error != 0) {
+		sx_xunlock(&vd->cfg_sx);
+		return (error);
+	}
+
+	mtx_lock(&vd->mtx);
+	vd->streaming = true;
+	mtx_unlock(&vd->mtx);
+
+	sx_xunlock(&vd->cfg_sx);
+	return (0);
+}
+
+static int
+video_streamoff(struct video_device *vd, struct video_file *vf, int *type)
+{
+	struct video_buf *vb;
+	u_int i;
+
+	if (*type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return (EINVAL);
+
+	sx_xlock(&vd->cfg_sx);
+	if (vd->owner != vf) {
+		sx_xunlock(&vd->cfg_sx);
+		return (EBUSY);
+	}
+	if (!vd->streaming) {
+		sx_xunlock(&vd->cfg_sx);
+		return (0);
+	}
+
+	mtx_lock(&vd->mtx);
+	vd->stopping = true;
+	wakeup(&vd->done);
+	mtx_unlock(&vd->mtx);
+
+	VIDEO_STOP_STREAM(vd->dev);
+
+	mtx_lock(&vd->mtx);
+	vd->streaming = false;
+	vd->stopping = false;
+	STAILQ_INIT(&vd->queued);
+	STAILQ_INIT(&vd->done);
+	for (i = 0; i < vd->pool->nbufs; i++) {
+		vb = &vd->pool->bufs[i];
+		vb->state = VB_IDLE;
+		vb->bytesused = 0;
+		vb->vd = NULL;
+	}
+	vf->read_buf = NULL;
+	vf->read_offset = 0;
+	mtx_unlock(&vd->mtx);
+
+	sx_xunlock(&vd->cfg_sx);
+	return (0);
+}
+
+static int
+video_g_parm(struct video_device *vd, struct v4l2_streamparm *sp)
+{
+	struct video_fract fract;
+	int error;
+
+	if (sp->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return (EINVAL);
+	memset(&fract, 0, sizeof(fract));
+	error = VIDEO_GET_PARM(vd->dev, &fract);
+	if (error != 0)
+		return (error);
+
+	memset(&sp->parm.capture, 0, sizeof(sp->parm.capture));
+	sp->parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
+	sp->parm.capture.timeperframe.numerator = fract.numerator;
+	sp->parm.capture.timeperframe.denominator = fract.denominator;
+	return (0);
+}
+
+static int
+video_s_parm(struct video_device *vd, struct v4l2_streamparm *sp)
+{
+	struct video_fract fract;
+	int error;
+
+	if (sp->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return (EINVAL);
+	fract.numerator = sp->parm.capture.timeperframe.numerator;
+	fract.denominator = sp->parm.capture.timeperframe.denominator;
+
+	error = VIDEO_SET_PARM(vd->dev, &fract);
+	if (error != 0)
+		return (error);
+
+	sp->parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
*** 1152 LINES SKIPPED ***