git: 9c9428825f4c - main - video: add generic video(4) capture framework
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
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 ***