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

Adrian Chadd <[email protected]>
Newsgroups gmane.os.freebsd.devel.cvs.src
Message-ID <6a78b272.3907f.7a963a59__37209.522949864$1786294982$gmane$org@gitrepo.freebsd.org>
The branch main has been updated by adrian:

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

commit 9c9428825f4c55e3cb37412c661bb9d385db4c68
Author:     Abdelkader Boudih <[email protected]>
AuthorDate: 2026-08-09 16:48:13 +0000
Commit:     Adrian Chadd <[email protected]>
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 <[email protected]>
+.\"
+.\" 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 [email protected]
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 <[email protected]>
+ * Copyright (c) 2008 Robert Nagy <[email protected]>
+ * Copyright (c) 2008 Marcus Glocker <[email protected]>
+ *
+ * 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 ***
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.