Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F169566976
D58367.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
47 KB
Referenced Files
None
Subscribers
None
D58367.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D58367: video: add generic video(4) capture framework
Attached
Detach File
Event Timeline
Log In to Comment