Page MenuHomeFreeBSD

D58367.diff
No OneTemporary

D58367.diff

diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile
--- a/share/man/man4/Makefile
+++ b/share/man/man4/Makefile
@@ -1148,6 +1148,7 @@
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
--- /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
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -1740,7 +1740,7 @@
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 @@
# 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
--- 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.h b/sys/dev/video/video.h
new file mode 100644
--- /dev/null
+++ b/sys/dev/video/video.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#ifndef _DEV_VIDEO_VIDEO_H_
+#define _DEV_VIDEO_VIDEO_H_
+
+#include <sys/types.h>
+#include <sys/bus.h>
+
+struct video_device;
+struct video_buf;
+
+struct video_format {
+ uint32_t pixelformat; /* V4L2 fourcc */
+ uint32_t width;
+ uint32_t height;
+ uint32_t bytesperline;
+ uint32_t sizeimage;
+ uint32_t field;
+ uint32_t colorspace;
+ uint32_t xfer_func;
+ uint32_t ycbcr_enc;
+ uint32_t flags; /* V4L2_FMT_FLAG_* */
+ char description[32];
+};
+
+struct video_fract {
+ uint32_t numerator;
+ uint32_t denominator;
+};
+
+struct video_caps {
+ char driver[16];
+ char card[32];
+ char bus_info[32];
+ uint32_t version;
+ uint32_t capabilities;
+};
+
+#define VIDEO_CAP_CAPTURE 0x00000001 /* V4L2_CAP_VIDEO_CAPTURE */
+#define VIDEO_CAP_READWRITE 0x01000000 /* V4L2_CAP_READWRITE */
+#define VIDEO_CAP_STREAMING 0x04000000 /* V4L2_CAP_STREAMING */
+
+struct video_input {
+ uint32_t index;
+ char name[32];
+ uint32_t type;
+};
+
+#define VIDEO_INPUT_TYPE_CAMERA 2
+
+struct video_control_desc {
+ uint32_t id;
+ uint32_t type;
+ char name[32];
+ int32_t minimum;
+ int32_t maximum;
+ int32_t step;
+ int32_t default_value;
+ uint32_t flags;
+};
+
+struct video_control {
+ uint32_t id;
+ int32_t value;
+};
+
+struct video_frmsizeenum {
+ uint32_t index;
+ uint32_t pixelformat;
+ uint32_t type;
+ union {
+ struct {
+ uint32_t width;
+ uint32_t height;
+ } discrete;
+ struct {
+ uint32_t min_width;
+ uint32_t max_width;
+ uint32_t step_width;
+ uint32_t min_height;
+ uint32_t max_height;
+ uint32_t step_height;
+ } stepwise;
+ };
+};
+
+struct video_frmivalenum {
+ uint32_t index;
+ uint32_t pixelformat;
+ uint32_t width;
+ uint32_t height;
+ uint32_t type;
+ union {
+ struct video_fract discrete;
+ struct {
+ struct video_fract min;
+ struct video_fract max;
+ struct video_fract step;
+ } stepwise;
+ };
+};
+
+int video_register(device_t dev, struct video_device **vdp);
+void video_unregister(struct video_device *vd);
+
+struct video_buf *video_buf_acquire(struct video_device *vd);
+int video_buf_write(struct video_buf *vb,
+ size_t offset, const void *src, size_t len);
+void video_buf_done(struct video_buf *vb,
+ size_t bytesused, uint32_t sequence);
+void video_buf_error(struct video_buf *vb);
+
+#endif /* _DEV_VIDEO_VIDEO_H_ */
diff --git a/sys/dev/video/video.c b/sys/dev/video/video.c
new file mode 100644
--- /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;
+ sp->parm.capture.timeperframe.numerator = fract.numerator;
+ sp->parm.capture.timeperframe.denominator = fract.denominator;
+ return (0);
+}
+
+static int
+video_queryctrl(struct video_device *vd, struct v4l2_queryctrl *qc)
+{
+ struct video_control_desc desc;
+ int error;
+
+ memset(&desc, 0, sizeof(desc));
+ desc.id = qc->id;
+ error = VIDEO_QUERY_CONTROL(vd->dev, &desc);
+ if (error != 0)
+ return (error);
+
+ qc->id = desc.id;
+ qc->type = desc.type;
+ strlcpy((char *)qc->name, desc.name, sizeof(qc->name));
+ qc->minimum = desc.minimum;
+ qc->maximum = desc.maximum;
+ qc->step = desc.step;
+ qc->default_value = desc.default_value;
+ qc->flags = desc.flags;
+ return (0);
+}
+
+static int
+video_g_ctrl(struct video_device *vd, struct v4l2_control *c)
+{
+ struct video_control vc;
+ int error;
+
+ vc.id = c->id;
+ vc.value = 0;
+ error = VIDEO_GET_CONTROL(vd->dev, &vc);
+ if (error == 0)
+ c->value = vc.value;
+ return (error);
+}
+
+static int
+video_s_ctrl(struct video_device *vd, struct v4l2_control *c)
+{
+ struct video_control vc;
+
+ vc.id = c->id;
+ vc.value = c->value;
+ return (VIDEO_SET_CONTROL(vd->dev, &vc));
+}
+
+static int
+video_enum_framesizes(struct video_device *vd, struct v4l2_frmsizeenum *fs)
+{
+ struct video_frmsizeenum vfs;
+ int error;
+
+ memset(&vfs, 0, sizeof(vfs));
+ vfs.index = fs->index;
+ vfs.pixelformat = fs->pixel_format;
+ error = VIDEO_ENUM_FRAMESIZES(vd->dev, &vfs);
+ if (error != 0)
+ return (error);
+
+ fs->type = vfs.type;
+ if (vfs.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
+ fs->discrete.width = vfs.discrete.width;
+ fs->discrete.height = vfs.discrete.height;
+ } else {
+ fs->stepwise.min_width = vfs.stepwise.min_width;
+ fs->stepwise.max_width = vfs.stepwise.max_width;
+ fs->stepwise.step_width = vfs.stepwise.step_width;
+ fs->stepwise.min_height = vfs.stepwise.min_height;
+ fs->stepwise.max_height = vfs.stepwise.max_height;
+ fs->stepwise.step_height = vfs.stepwise.step_height;
+ }
+ return (0);
+}
+
+static int
+video_enum_frameintervals(struct video_device *vd,
+ struct v4l2_frmivalenum *fi)
+{
+ struct video_frmivalenum vfi;
+ int error;
+
+ memset(&vfi, 0, sizeof(vfi));
+ vfi.index = fi->index;
+ vfi.pixelformat = fi->pixel_format;
+ vfi.width = fi->width;
+ vfi.height = fi->height;
+ error = VIDEO_ENUM_FRAMEINTERVALS(vd->dev, &vfi);
+ if (error != 0)
+ return (error);
+
+ fi->type = vfi.type;
+ if (vfi.type == V4L2_FRMIVAL_TYPE_DISCRETE) {
+ fi->discrete.numerator = vfi.discrete.numerator;
+ fi->discrete.denominator = vfi.discrete.denominator;
+ } else {
+ fi->stepwise.min.numerator = vfi.stepwise.min.numerator;
+ fi->stepwise.min.denominator = vfi.stepwise.min.denominator;
+ fi->stepwise.max.numerator = vfi.stepwise.max.numerator;
+ fi->stepwise.max.denominator = vfi.stepwise.max.denominator;
+ fi->stepwise.step.numerator = vfi.stepwise.step.numerator;
+ fi->stepwise.step.denominator = vfi.stepwise.step.denominator;
+ }
+ return (0);
+}
+
+static int
+video_enum_input(struct video_device *vd, struct v4l2_input *inp)
+{
+ struct video_input vi;
+ int error;
+
+ memset(&vi, 0, sizeof(vi));
+ error = VIDEO_ENUM_INPUT(vd->dev, inp->index, &vi);
+ if (error != 0)
+ return (error);
+
+ strlcpy((char *)inp->name, vi.name, sizeof(inp->name));
+ inp->type = vi.type;
+ return (0);
+}
+
+static int
+video_g_input(struct video_device *vd, int *index)
+{
+ uint32_t idx;
+ int error;
+
+ error = VIDEO_GET_INPUT(vd->dev, &idx);
+ if (error == 0)
+ *index = idx;
+ return (error);
+}
+
+static int
+video_s_input(struct video_device *vd, int *index)
+{
+
+ return (VIDEO_SET_INPUT(vd->dev, *index));
+}
+
+static int
+video_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag,
+ struct thread *td)
+{
+ struct video_device *vd = dev->si_drv1;
+ struct video_file *vf;
+ int error;
+
+ if (vd == NULL || vd->dying)
+ return (ENXIO);
+
+ error = devfs_get_cdevpriv((void **)&vf);
+ if (error != 0)
+ return (error);
+
+ switch (cmd) {
+ case VIDIOC_QUERYCAP:
+ return (video_querycap(vd, (struct v4l2_capability *)data));
+ case VIDIOC_ENUM_FMT:
+ return (video_enum_fmt(vd, (struct v4l2_fmtdesc *)data));
+ case VIDIOC_G_FMT:
+ return (video_g_fmt(vd, (struct v4l2_format *)data));
+ case VIDIOC_S_FMT:
+ return (video_s_fmt(vd, vf, (struct v4l2_format *)data));
+ case VIDIOC_TRY_FMT:
+ return (video_try_fmt(vd, (struct v4l2_format *)data));
+ case VIDIOC_REQBUFS:
+ return (video_reqbufs(vd, vf,
+ (struct v4l2_requestbuffers *)data));
+ case VIDIOC_QUERYBUF:
+ return (video_querybuf(vd, vf, (struct v4l2_buffer *)data));
+ case VIDIOC_QBUF:
+ return (video_qbuf(vd, vf, (struct v4l2_buffer *)data));
+ case VIDIOC_DQBUF:
+ return (video_dqbuf(vd, vf, (struct v4l2_buffer *)data,
+ fflag));
+ case VIDIOC_STREAMON:
+ return (video_streamon(vd, vf, (int *)data));
+ case VIDIOC_STREAMOFF:
+ return (video_streamoff(vd, vf, (int *)data));
+ case VIDIOC_G_PARM:
+ return (video_g_parm(vd, (struct v4l2_streamparm *)data));
+ case VIDIOC_S_PARM:
+ return (video_s_parm(vd, (struct v4l2_streamparm *)data));
+ case VIDIOC_QUERYCTRL:
+ return (video_queryctrl(vd, (struct v4l2_queryctrl *)data));
+ case VIDIOC_G_CTRL:
+ return (video_g_ctrl(vd, (struct v4l2_control *)data));
+ case VIDIOC_S_CTRL:
+ return (video_s_ctrl(vd, (struct v4l2_control *)data));
+ case VIDIOC_ENUMINPUT:
+ return (video_enum_input(vd, (struct v4l2_input *)data));
+ case VIDIOC_G_INPUT:
+ return (video_g_input(vd, (int *)data));
+ case VIDIOC_S_INPUT:
+ return (video_s_input(vd, (int *)data));
+ case VIDIOC_ENUM_FRAMESIZES:
+ return (video_enum_framesizes(vd,
+ (struct v4l2_frmsizeenum *)data));
+ case VIDIOC_ENUM_FRAMEINTERVALS:
+ return (video_enum_frameintervals(vd,
+ (struct v4l2_frmivalenum *)data));
+ default:
+ return (ENOTTY);
+ }
+}
+
+static int
+video_read(struct cdev *dev, struct uio *uio, int ioflag)
+{
+ struct video_device *vd = dev->si_drv1;
+ struct video_buf_pool *pool;
+ struct video_file *vf;
+ struct video_buf *vb;
+ uint8_t *src;
+ size_t bytesused, chunk, offset;
+ int error;
+
+ if (vd == NULL || vd->dying)
+ return (ENXIO);
+
+ error = devfs_get_cdevpriv((void **)&vf);
+ if (error != 0)
+ return (error);
+
+ mtx_lock(&vd->mtx);
+ while (vf->reading) {
+ error = msleep(&vf->reading, &vd->mtx, PCATCH, "vrdser", 0);
+ if (error != 0) {
+ mtx_unlock(&vd->mtx);
+ return (error);
+ }
+ }
+ vf->reading = true;
+ mtx_unlock(&vd->mtx);
+
+ sx_xlock(&vd->cfg_sx);
+ error = video_claim_owner(vd, vf);
+ if (error != 0) {
+ sx_xunlock(&vd->cfg_sx);
+ goto out;
+ }
+
+ if (!vd->streaming) {
+ if (vd->mode == VMODE_MMAP) {
+ sx_xunlock(&vd->cfg_sx);
+ error = EBUSY;
+ goto out;
+ }
+
+ 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);
+ if (error == 0)
+ error = EIO;
+ goto out;
+ }
+ }
+
+ if (vd->pool == NULL) {
+ pool = video_pool_alloc(VIDEO_READ_BUFFERS,
+ vd->format.sizeimage);
+ if (pool == NULL) {
+ sx_xunlock(&vd->cfg_sx);
+ error = ENOMEM;
+ goto out;
+ }
+ mtx_lock(&vd->mtx);
+ vd->pool = pool;
+ mtx_unlock(&vd->mtx);
+ }
+ vd->mode = VMODE_READ;
+
+ mtx_lock(&vd->mtx);
+ for (u_int i = 0; i < vd->pool->nbufs; i++) {
+ vb = &vd->pool->bufs[i];
+ vb->state = VB_QUEUED;
+ vb->vd = vd;
+ STAILQ_INSERT_TAIL(&vd->queued, vb, entry);
+ }
+ mtx_unlock(&vd->mtx);
+
+ error = VIDEO_START_STREAM(vd->dev);
+ if (error != 0) {
+ video_pool_detach(vd);
+ vd->mode = VMODE_NONE;
+ sx_xunlock(&vd->cfg_sx);
+ goto out;
+ }
+
+ mtx_lock(&vd->mtx);
+ vd->streaming = true;
+ mtx_unlock(&vd->mtx);
+ }
+ sx_xunlock(&vd->cfg_sx);
+
+ mtx_lock(&vd->mtx);
+ while (vf->read_buf == NULL) {
+ if (!STAILQ_EMPTY(&vd->done)) {
+ vf->read_buf = STAILQ_FIRST(&vd->done);
+ STAILQ_REMOVE_HEAD(&vd->done, entry);
+ vf->read_offset = 0;
+ break;
+ }
+ if (!vd->streaming || vd->stopping) {
+ mtx_unlock(&vd->mtx);
+ error = EIO;
+ goto out;
+ }
+ if (ioflag & O_NONBLOCK) {
+ mtx_unlock(&vd->mtx);
+ error = EAGAIN;
+ goto out;
+ }
+ error = msleep(&vd->done, &vd->mtx, PCATCH, "vread", 0);
+ if (error != 0) {
+ mtx_unlock(&vd->mtx);
+ goto out;
+ }
+ }
+
+ vb = vf->read_buf;
+ bytesused = vb->bytesused;
+ offset = vf->read_offset;
+ chunk = 0;
+ if (uio->uio_resid > 0 && offset < bytesused) {
+ chunk = MIN((size_t)uio->uio_resid, bytesused - offset);
+ src = (uint8_t *)vb->buf + offset;
+ vd->readers++;
+ mtx_unlock(&vd->mtx);
+
+ error = uiomove(src, chunk, uio);
+
+ mtx_lock(&vd->mtx);
+ if (--vd->readers == 0)
+ wakeup(&vd->readers);
+ if (error != 0) {
+ mtx_unlock(&vd->mtx);
+ goto out;
+ }
+ }
+
+ if (vf->read_buf == vb) {
+ vf->read_offset = offset + chunk;
+ if (vf->read_offset >= bytesused) {
+ vf->read_buf = NULL;
+ vf->read_offset = 0;
+ vb->state = VB_QUEUED;
+ vb->bytesused = 0;
+ STAILQ_INSERT_TAIL(&vd->queued, vb, entry);
+ }
+ }
+ mtx_unlock(&vd->mtx);
+
+ error = 0;
+out:
+ mtx_lock(&vd->mtx);
+ vf->reading = false;
+ wakeup(&vf->reading);
+ mtx_unlock(&vd->mtx);
+ return (error);
+}
+
+static int
+video_poll(struct cdev *dev, int events, struct thread *td)
+{
+ struct video_device *vd = dev->si_drv1;
+ int revents = 0;
+
+ if (vd == NULL || vd->dying)
+ return (POLLHUP);
+
+ mtx_lock(&vd->mtx);
+ if (events & (POLLIN | POLLRDNORM)) {
+ if (!STAILQ_EMPTY(&vd->done))
+ revents |= events & (POLLIN | POLLRDNORM);
+ else
+ selrecord(td, &vd->sel);
+ }
+ mtx_unlock(&vd->mtx);
+ return (revents);
+}
+
+static void video_kqfilter_detach(struct knote *kn);
+static int video_kqfilter_event(struct knote *kn, long hint);
+
+static const struct filterops video_read_filterops = {
+ .f_isfd = 1,
+ .f_detach = video_kqfilter_detach,
+ .f_event = video_kqfilter_event,
+};
+
+static void
+video_kqfilter_detach(struct knote *kn)
+{
+ struct video_device *vd = kn->kn_hook;
+
+ knlist_remove(&vd->sel.si_note, kn, 0);
+}
+
+static int
+video_kqfilter_event(struct knote *kn, long hint)
+{
+ struct video_device *vd = kn->kn_hook;
+
+ if (!STAILQ_EMPTY(&vd->done)) {
+ kn->kn_data = 1;
+ return (1);
+ }
+ return (0);
+}
+
+static int
+video_kqfilter(struct cdev *dev, struct knote *kn)
+{
+ struct video_device *vd = dev->si_drv1;
+
+ if (vd == NULL || vd->dying)
+ return (ENXIO);
+
+ switch (kn->kn_filter) {
+ case EVFILT_READ:
+ kn->kn_fop = &video_read_filterops;
+ kn->kn_hook = vd;
+ knlist_add(&vd->sel.si_note, kn, 0);
+ return (0);
+ default:
+ return (EINVAL);
+ }
+}
+
+static int
+video_mmap_single(struct cdev *dev, vm_ooffset_t *offset, vm_size_t size,
+ vm_object_t *objp, int nprot)
+{
+ struct video_device *vd = dev->si_drv1;
+ struct video_file *vf;
+ int error;
+
+ if (vd == NULL || vd->dying)
+ return (ENXIO);
+
+ error = devfs_get_cdevpriv((void **)&vf);
+ if (error != 0)
+ return (error);
+
+ sx_slock(&vd->cfg_sx);
+ if (vd->owner != vf || vd->pool == NULL || vd->mode != VMODE_MMAP) {
+ sx_sunlock(&vd->cfg_sx);
+ return (EINVAL);
+ }
+
+ if (*offset >= vd->pool->map_size ||
+ size > vd->pool->map_size - *offset) {
+ sx_sunlock(&vd->cfg_sx);
+ return (EINVAL);
+ }
+
+ /*
+ * Hand out a reference to the object backing the whole pool; the
+ * offset selects which buffer within it gets mapped. The VM system
+ * tracks the mapping lifetime through the object reference count,
+ * so no per-mapping bookkeeping is needed here.
+ */
+ vm_object_reference(vd->pool->obj);
+ *objp = vd->pool->obj;
+ sx_sunlock(&vd->cfg_sx);
+
+ return (0);
+}
+
+int
+video_register(device_t dev, struct video_device **vdp)
+{
+ struct make_dev_args args;
+ struct video_device *vd;
+ int taken[VIDEO_UNIT_RETRIES];
+ int collisions, error, unit;
+
+ vd = malloc(sizeof(*vd), M_VIDEO, M_WAITOK | M_ZERO);
+ vd->dev = dev;
+
+ sx_init(&vd->cfg_sx, "video_cfg");
+ mtx_init(&vd->mtx, "video_mtx", NULL, MTX_DEF);
+ STAILQ_INIT(&vd->queued);
+ STAILQ_INIT(&vd->done);
+ knlist_init_mtx(&vd->sel.si_note, &vd->mtx);
+
+ error = EEXIST;
+ for (collisions = 0; collisions < VIDEO_UNIT_RETRIES; collisions++) {
+ taken[collisions] = unit = alloc_unr(video_unrhdr);
+
+ make_dev_args_init(&args);
+ args.mda_flags = MAKEDEV_CHECKNAME | MAKEDEV_WAITOK;
+ args.mda_devsw = &video_cdevsw;
+ args.mda_uid = UID_ROOT;
+ args.mda_gid = GID_VIDEO;
+ args.mda_mode = 0660;
+ args.mda_unit = unit;
+ args.mda_si_drv1 = vd;
+
+ error = make_dev_s(&args, &vd->cdev, "video%d", unit);
+ if (error != EEXIST)
+ break;
+ }
+
+ while (collisions > 0)
+ free_unr(video_unrhdr, taken[--collisions]);
+
+ if (error != 0) {
+ free_unr(video_unrhdr, unit);
+ device_printf(dev, "failed to create a /dev/video node: %d\n",
+ error);
+ knlist_destroy(&vd->sel.si_note);
+ mtx_destroy(&vd->mtx);
+ sx_destroy(&vd->cfg_sx);
+ free(vd, M_VIDEO);
+ return (error);
+ }
+ vd->unit = unit;
+
+ *vdp = vd;
+ device_printf(dev, "registered as /dev/video%d\n", unit);
+ return (0);
+}
+
+void
+video_unregister(struct video_device *vd)
+{
+
+ sx_xlock(&vd->cfg_sx);
+ vd->dying = true;
+
+ if (vd->streaming) {
+ 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);
+ mtx_unlock(&vd->mtx);
+ }
+
+ sx_xunlock(&vd->cfg_sx);
+
+ destroy_dev(vd->cdev);
+ free_unr(video_unrhdr, vd->unit);
+
+ video_pool_detach(vd);
+
+ knlist_destroy(&vd->sel.si_note);
+ mtx_destroy(&vd->mtx);
+ sx_destroy(&vd->cfg_sx);
+
+ free(vd, M_VIDEO);
+}
+
+static int
+video_modevent(module_t mod, int type, void *data)
+{
+
+ switch (type) {
+ case MOD_LOAD:
+ video_unrhdr = new_unrhdr(0, INT_MAX, NULL);
+ return (0);
+ case MOD_UNLOAD:
+ delete_unrhdr(video_unrhdr);
+ return (0);
+ default:
+ return (EOPNOTSUPP);
+ }
+}
+
+static moduledata_t video_mod = {
+ "video",
+ video_modevent,
+ NULL,
+};
+
+DECLARE_MODULE(video, video_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
+MODULE_VERSION(video, 1);
diff --git a/sys/dev/video/video_if.m b/sys/dev/video/video_if.m
new file mode 100644
--- /dev/null
+++ b/sys/dev/video/video_if.m
@@ -0,0 +1,196 @@
+#
+# Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com>
+#
+# SPDX-License-Identifier: BSD-2-Clause
+#
+
+#include <sys/bus.h>
+#include <dev/video/video.h>
+
+INTERFACE video;
+
+CODE {
+ static int
+ video_default_open(device_t dev __unused)
+ {
+
+ return (0);
+ }
+
+ static void
+ video_default_close(device_t dev __unused)
+ {
+ }
+
+ static int
+ video_default_frmsize(device_t dev __unused,
+ struct video_frmsizeenum *fs __unused)
+ {
+
+ return (ENOTTY);
+ }
+
+ static int
+ video_default_frmival(device_t dev __unused,
+ struct video_frmivalenum *fi __unused)
+ {
+
+ return (ENOTTY);
+ }
+
+ static int
+ video_default_format(device_t dev __unused,
+ struct video_format *fmt __unused)
+ {
+
+ return (ENOTTY);
+ }
+
+ static int
+ video_default_fract(device_t dev __unused,
+ struct video_fract *fr __unused)
+ {
+
+ return (ENOTTY);
+ }
+
+ static int
+ video_default_enum_input(device_t dev __unused, uint32_t index __unused,
+ struct video_input *inp __unused)
+ {
+
+ return (ENOTTY);
+ }
+
+ static int
+ video_default_get_input(device_t dev __unused, uint32_t *idx __unused)
+ {
+
+ return (ENOTTY);
+ }
+
+ static int
+ video_default_set_input(device_t dev __unused, uint32_t idx __unused)
+ {
+
+ return (ENOTTY);
+ }
+
+ static int
+ video_default_query_control(device_t dev __unused,
+ struct video_control_desc *d __unused)
+ {
+
+ return (ENOTTY);
+ }
+
+ static int
+ video_default_get_control(device_t dev __unused,
+ struct video_control *c __unused)
+ {
+
+ return (ENOTTY);
+ }
+
+ static int
+ video_default_set_control(device_t dev __unused,
+ const struct video_control *c __unused)
+ {
+
+ return (ENOTTY);
+ }
+};
+
+METHOD int open {
+ device_t dev;
+} DEFAULT video_default_open;
+
+METHOD void close {
+ device_t dev;
+} DEFAULT video_default_close;
+
+METHOD int querycap {
+ device_t dev;
+ struct video_caps *caps;
+};
+
+METHOD int enum_format {
+ device_t dev;
+ uint32_t index;
+ struct video_format *format;
+};
+
+METHOD int enum_framesizes {
+ device_t dev;
+ struct video_frmsizeenum *fsize;
+} DEFAULT video_default_frmsize;
+
+METHOD int enum_frameintervals {
+ device_t dev;
+ struct video_frmivalenum *fival;
+} DEFAULT video_default_frmival;
+
+METHOD int try_format {
+ device_t dev;
+ struct video_format *format;
+} DEFAULT video_default_format;
+
+METHOD int set_format {
+ device_t dev;
+ const struct video_format *format;
+};
+
+METHOD int get_format {
+ device_t dev;
+ struct video_format *format;
+};
+
+METHOD int get_parm {
+ device_t dev;
+ struct video_fract *fract;
+} DEFAULT video_default_fract;
+
+METHOD int set_parm {
+ device_t dev;
+ struct video_fract *fract;
+} DEFAULT video_default_fract;
+
+METHOD int enum_input {
+ device_t dev;
+ uint32_t index;
+ struct video_input *input;
+} DEFAULT video_default_enum_input;
+
+METHOD int get_input {
+ device_t dev;
+ uint32_t *index;
+} DEFAULT video_default_get_input;
+
+METHOD int set_input {
+ device_t dev;
+ uint32_t index;
+} DEFAULT video_default_set_input;
+
+METHOD int query_control {
+ device_t dev;
+ struct video_control_desc *desc;
+} DEFAULT video_default_query_control;
+
+METHOD int get_control {
+ device_t dev;
+ struct video_control *control;
+} DEFAULT video_default_get_control;
+
+METHOD int set_control {
+ device_t dev;
+ const struct video_control *control;
+} DEFAULT video_default_set_control;
+
+# stop_stream must quiesce the provider before returning.
+METHOD int start_stream {
+ device_t dev;
+};
+
+METHOD void stop_stream {
+ device_t dev;
+};
diff --git a/sys/dev/video/video_internal.h b/sys/dev/video/video_internal.h
new file mode 100644
--- /dev/null
+++ b/sys/dev/video/video_internal.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#ifndef _DEV_VIDEO_VIDEO_INTERNAL_H_
+#define _DEV_VIDEO_VIDEO_INTERNAL_H_
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/conf.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/mutex.h>
+#include <sys/sx.h>
+#include <sys/queue.h>
+#include <sys/selinfo.h>
+
+#include <vm/vm.h>
+#include <vm/vm_object.h>
+#include <vm/vm_page.h>
+#include <vm/vm_pager.h>
+
+#include <dev/video/video.h>
+
+MALLOC_DECLARE(M_VIDEO);
+
+#define VIDEO_MAX_BUFFERS 8
+#define VIDEO_READ_BUFFERS 3
+
+enum video_buf_state {
+ VB_IDLE,
+ VB_QUEUED,
+ VB_ACTIVE,
+ VB_DONE,
+ VB_ERROR,
+};
+
+struct video_buf {
+ STAILQ_ENTRY(video_buf) entry;
+ struct video_buf_pool *pool;
+ struct video_device *vd;
+
+ uint32_t index;
+ enum video_buf_state state;
+
+ void *buf;
+ size_t length;
+
+ size_t bytesused;
+ uint32_t sequence;
+ struct timeval timestamp;
+ uint32_t flags;
+};
+
+STAILQ_HEAD(video_buf_list, video_buf);
+
+/*
+ * All buffers must stay carved out of one OBJT_PHYS vm_object, mapped into
+ * the kernel map and handed to userspace mmap as that same object. Giving
+ * each buffer its own object breaks the mmap path.
+ */
+struct video_buf_pool {
+ vm_object_t obj;
+ vm_offset_t kva;
+
+ u_int nbufs;
+ size_t buf_size;
+ size_t map_size;
+ struct video_buf bufs[VIDEO_MAX_BUFFERS];
+};
+
+struct video_file {
+ struct video_device *vd;
+ bool is_owner;
+ bool reading; /* read(2) in progress */
+ size_t read_offset;
+ struct video_buf *read_buf;
+};
+
+enum video_mode {
+ VMODE_NONE,
+ VMODE_READ,
+ VMODE_MMAP,
+};
+
+/* Lock order: cfg_sx -> mtx. */
+struct video_device {
+ device_t dev;
+ struct cdev *cdev;
+ int unit;
+
+ struct sx cfg_sx;
+ struct mtx mtx;
+
+ bool dying;
+
+ struct video_file *owner;
+ enum video_mode mode;
+ bool streaming;
+ bool stopping;
+
+ struct video_format format;
+
+ struct video_buf_pool *pool;
+ struct video_buf_list queued;
+ struct video_buf_list done;
+ u_int readers; /* copies in flight from pool */
+
+ struct selinfo sel;
+};
+
+
+#endif /* _DEV_VIDEO_VIDEO_INTERNAL_H_ */
diff --git a/sys/modules/Makefile b/sys/modules/Makefile
--- a/sys/modules/Makefile
+++ b/sys/modules/Makefile
@@ -423,6 +423,7 @@
virtio \
vge \
${_viawd} \
+ video \
videomode \
vkbd \
${_vmd} \
diff --git a/sys/modules/video/Makefile b/sys/modules/video/Makefile
new file mode 100644
--- /dev/null
+++ b/sys/modules/video/Makefile
@@ -0,0 +1,12 @@
+S= ${SRCTOP}/sys
+
+.PATH: $S/dev/video
+
+KMOD= video
+SRCS= video.c \
+ video_if.h video_if.c \
+ device_if.h bus_if.h
+
+EXPORT_SYMS= YES
+
+.include <bsd.kmod.mk>
diff --git a/sys/dev/usb/video/uvideo_v4l2.h b/sys/sys/videoio.h
rename from sys/dev/usb/video/uvideo_v4l2.h
rename to sys/sys/videoio.h
--- a/sys/dev/usb/video/uvideo_v4l2.h
+++ b/sys/sys/videoio.h
@@ -32,12 +32,12 @@
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
- * Minimal V4L2 definitions for the FreeBSD UVC driver.
+ * Minimal V4L2 definitions for the video(4) framework.
* Extracted from OpenBSD sys/videoio.h.
*/
-#ifndef _UVIDEO_V4L2_H_
-#define _UVIDEO_V4L2_H_
+#ifndef _SYS_VIDEOIO_H_
+#define _SYS_VIDEOIO_H_
#include <sys/types.h>
#include <sys/ioccom.h>
@@ -193,18 +193,11 @@
u_int32_t xfer_func; /* enum v4l2_xfer_func */
};
-/*
- * v4l2_format: the system header (v4l_compat) includes struct v4l2_window
- * (which contains a pointer) in the fmt union, forcing 8-byte alignment.
- * This creates 4 bytes of implicit padding after 'type' on LP64.
- * We must match this layout for ioctl ABI compatibility.
- * Total size: 4 (type) + 4 (pad) + 200 (union) = 208.
- */
struct v4l2_format {
u_int32_t type;
- u_int32_t _pad;
union {
struct v4l2_pix_format pix;
+ void *_align;
u_int8_t raw_data[200];
} fmt;
};
@@ -365,6 +358,8 @@
/* Luminance+Chrominance formats */
#define V4L2_PIX_FMT_YUYV v4l2_fourcc('Y', 'U', 'Y', 'V')
#define V4L2_PIX_FMT_UYVY v4l2_fourcc('U', 'Y', 'V', 'Y')
+#define V4L2_PIX_FMT_YUV444 v4l2_fourcc('Y', '4', '4', '4')
+#define V4L2_PIX_FMT_Y41P v4l2_fourcc('Y', '4', '1', 'P')
#define V4L2_PIX_FMT_NV12 v4l2_fourcc('N', 'V', '1', '2')
#define V4L2_PIX_FMT_NV21 v4l2_fourcc('N', 'V', '2', '1')
#define V4L2_PIX_FMT_M420 v4l2_fourcc('M', '4', '2', '0')
@@ -383,6 +378,7 @@
#define V4L2_PIX_FMT_Y16 v4l2_fourcc('Y', '1', '6', ' ')
/* RGB formats */
+#define V4L2_PIX_FMT_RGB24 v4l2_fourcc('R', 'G', 'B', '3')
#define V4L2_PIX_FMT_RGB565 v4l2_fourcc('R', 'G', 'B', 'P')
#define V4L2_PIX_FMT_BGR24 v4l2_fourcc('B', 'G', 'R', '3')
#define V4L2_PIX_FMT_XBGR32 v4l2_fourcc('X', 'R', '2', '4')
@@ -499,4 +495,18 @@
#define VIDIOC_ENUM_FRAMESIZES _IOWR('V', 74, struct v4l2_frmsizeenum)
#define VIDIOC_ENUM_FRAMEINTERVALS _IOWR('V', 75, struct v4l2_frmivalenum)
-#endif /* _UVIDEO_V4L2_H_ */
+_Static_assert(sizeof(struct v4l2_pix_format) == 48, "v4l2_pix_format layout");
+_Static_assert(sizeof(struct v4l2_capability) == 104, "v4l2_capability layout");
+_Static_assert(sizeof(struct v4l2_requestbuffers) == 20,
+ "v4l2_requestbuffers layout");
+#ifdef __LP64__
+_Static_assert(__offsetof(struct v4l2_format, fmt) == 8, "v4l2_format layout");
+_Static_assert(sizeof(struct v4l2_format) == 208, "v4l2_format layout");
+_Static_assert(sizeof(struct v4l2_buffer) == 88, "v4l2_buffer layout");
+#else
+_Static_assert(__offsetof(struct v4l2_format, fmt) == 4, "v4l2_format layout");
+_Static_assert(sizeof(struct v4l2_format) == 204, "v4l2_format layout");
+_Static_assert(sizeof(struct v4l2_buffer) == 68, "v4l2_buffer layout");
+#endif
+
+#endif /* _SYS_VIDEOIO_H_ */

File Metadata

Mime Type
text/plain
Expires
Wed, Sep 2, 10:52 PM (20 h, 17 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
37984596
Default Alt Text
D58367.diff (47 KB)

Event Timeline