Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F171000443
D58367.id182558.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
41 KB
Referenced Files
None
Subscribers
None
D58367.id182558.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
@@ -1145,6 +1145,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,86 @@
+.\" Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com>
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd July 17, 2026
+.Dt VIDEO 4
+.Os
+.Sh NAME
+.Nm video
+.Nd generic video capture framework
+.Sh SYNOPSIS
+To compile this driver into the kernel,
+place the following line in your
+kernel configuration file:
+.Bd -ragged -offset indent
+.Cd "device video"
+.Ed
+.Pp
+Alternatively, to load the driver as a
+module at boot time, place the following line in
+.Xr loader.conf 5 :
+.Bd -literal -offset indent
+video_load="YES"
+.Ed
+.Sh DESCRIPTION
+The
+.Nm
+framework provides a common interface for video capture devices on
+.Fx .
+It creates
+.Pa /dev/videoN
+device nodes and handles buffer management, memory mapping, and
+ioctl dispatch on behalf of hardware drivers.
+.Pp
+Userland applications access capture devices through the Video4Linux2
+(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
+VIDIOC_QUERYCAP
+.It
+VIDIOC_ENUM_FMT, VIDIOC_G_FMT, VIDIOC_S_FMT, VIDIOC_TRY_FMT
+.It
+VIDIOC_ENUM_FRAMESIZES, VIDIOC_ENUM_FRAMEINTERVALS
+.It
+VIDIOC_G_PARM, VIDIOC_S_PARM
+.It
+VIDIOC_ENUMINPUT, VIDIOC_G_INPUT, VIDIOC_S_INPUT
+.It
+VIDIOC_QUERYCTRL, VIDIOC_G_CTRL, VIDIOC_S_CTRL
+.It
+VIDIOC_REQBUFS, VIDIOC_QUERYBUF, VIDIOC_QBUF, VIDIOC_DQBUF
+.It
+VIDIOC_STREAMON, 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 VIDIOC_REQBUFS, mapped with
+.Xr mmap 2 ,
+and cycled with VIDIOC_QBUF/VIDIOC_DQBUF.
+Streaming is started and stopped with VIDIOC_STREAMON/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
+.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
@@ -3510,6 +3510,7 @@
#
# USB END
#
+dev/video/video.c 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/video/video.c b/sys/dev/video/video.c
new file mode 100644
--- /dev/null
+++ b/sys/dev/video/video.c
@@ -0,0 +1,1416 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com>
+ */
+
+#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 <vm/vm.h>
+#include <vm/vm_extern.h>
+#include <vm/vm_object.h>
+#include <vm/vm_page.h>
+#include <vm/vm_pager.h>
+#include <vm/uma.h>
+
+#include <dev/usb/video/uvideo_v4l2.h>
+#include <dev/video/video_internal.h>
+
+MALLOC_DEFINE(M_VIDEO, "video", "video(4) framework");
+
+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 void
+video_pool_free_bufs(struct video_buf_pool *pool)
+{
+ struct video_buf *vb;
+ u_int i;
+ int j;
+
+ for (i = 0; i < pool->nbufs; i++) {
+ vb = &pool->bufs[i];
+ if (vb->pages == NULL)
+ continue;
+ for (j = 0; j < vb->npages; j++) {
+ vm_page_unwire_noq(vb->pages[j]);
+ vm_page_free(vb->pages[j]);
+ }
+ free(vb->pages, M_VIDEO);
+ vb->pages = NULL;
+ }
+}
+
+struct video_buf_pool *
+video_pool_alloc(u_int nbufs, size_t buf_size)
+{
+ struct video_buf_pool *pool;
+ struct video_buf *vb;
+ u_int i;
+ int npages, j;
+
+ buf_size = round_page(buf_size);
+ npages = buf_size / PAGE_SIZE;
+
+ pool = malloc(sizeof(*pool), M_VIDEO, M_WAITOK | M_ZERO);
+ pool->refs = 1;
+ pool->nbufs = nbufs;
+ pool->buf_size = buf_size;
+ pool->map_size = (size_t)nbufs * buf_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->npages = npages;
+ vb->pages = malloc(npages * sizeof(vm_page_t), M_VIDEO,
+ M_WAITOK | M_ZERO);
+
+ for (j = 0; j < npages; j++) {
+ vb->pages[j] = vm_page_alloc_noobj(VM_ALLOC_WIRED |
+ VM_ALLOC_ZERO);
+ if (vb->pages[j] == NULL) {
+ while (--j >= 0) {
+ vm_page_unwire_noq(vb->pages[j]);
+ vm_page_free(vb->pages[j]);
+ }
+ free(vb->pages, M_VIDEO);
+ vb->pages = NULL;
+ goto fail;
+ }
+ }
+ }
+ return (pool);
+
+fail:
+ video_pool_free_bufs(pool);
+ free(pool, M_VIDEO);
+ return (NULL);
+}
+
+void
+video_pool_ref(struct video_buf_pool *pool)
+{
+
+ atomic_add_int(&pool->refs, 1);
+}
+
+void
+video_pool_rele(struct video_buf_pool *pool)
+{
+
+ if (atomic_fetchadd_int(&pool->refs, -1) != 1)
+ return;
+
+ video_pool_free_bufs(pool);
+ free(pool, M_VIDEO);
+}
+
+static int
+video_pager_ctor(void *handle, vm_ooffset_t size, vm_prot_t prot,
+ vm_ooffset_t foff, struct ucred *cred, u_short *color)
+{
+ struct video_mmap_handle *h = handle;
+
+ dev_ref(h->cdev);
+ return (0);
+}
+
+static void
+video_pager_dtor(void *handle)
+{
+ struct video_mmap_handle *h = handle;
+
+ video_pool_rele(h->pool);
+ dev_rel(h->cdev);
+ free(h, M_VIDEO);
+}
+
+static int
+video_pager_fault(vm_object_t object, vm_ooffset_t offset, int prot,
+ vm_page_t *mres)
+{
+ struct video_mmap_handle *h = object->handle;
+ struct video_buf_pool *pool = h->pool;
+ struct video_buf *vb;
+ u_int buf_idx, page_idx;
+ vm_page_t m;
+ vm_paddr_t paddr;
+
+ if (offset < 0 || (size_t)offset >= pool->map_size)
+ return (VM_PAGER_ERROR);
+
+ buf_idx = offset / pool->buf_size;
+ page_idx = (offset % pool->buf_size) / PAGE_SIZE;
+
+ if (buf_idx >= pool->nbufs)
+ return (VM_PAGER_ERROR);
+
+ vb = &pool->bufs[buf_idx];
+ if (page_idx >= (u_int)vb->npages)
+ return (VM_PAGER_ERROR);
+
+ paddr = VM_PAGE_TO_PHYS(vb->pages[page_idx]);
+
+ if (((*mres)->flags & PG_FICTITIOUS) != 0) {
+ m = *mres;
+ vm_page_updatefake(m, paddr, object->memattr);
+ } else {
+ VM_OBJECT_WUNLOCK(object);
+ m = vm_page_getfake(paddr, object->memattr);
+ VM_OBJECT_WLOCK(object);
+ vm_page_replace(m, object, (*mres)->pindex, *mres);
+ *mres = m;
+ }
+ m->valid = VM_PAGE_BITS_ALL;
+ return (VM_PAGER_OK);
+}
+
+static const struct cdev_pager_ops video_pager_ops = {
+ .cdev_pg_ctor = video_pager_ctor,
+ .cdev_pg_dtor = video_pager_dtor,
+ .cdev_pg_fault = video_pager_fault,
+};
+
+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);
+
+ vd->ops->stop_stream(vd->hw_softc);
+
+ mtx_lock(&vd->mtx);
+ vd->streaming = false;
+ vd->stopping = false;
+ STAILQ_INIT(&vd->queued);
+ STAILQ_INIT(&vd->done);
+ mtx_unlock(&vd->mtx);
+ }
+
+ if (vd->pool != NULL) {
+ video_pool_rele(vd->pool);
+ vd->pool = NULL;
+ }
+
+ vd->owner = NULL;
+ vd->mode = VMODE_NONE;
+
+ if (vd->ops->close != NULL)
+ vd->ops->close(vd->hw_softc);
+ }
+
+ sx_xunlock(&vd->cfg_sx);
+
+ vf->read_buf = NULL;
+ 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)
+{
+
+ sx_assert(&vd->cfg_sx, SA_XLOCKED);
+
+ if (vd->owner == vf)
+ return (0);
+ if (vd->owner != NULL)
+ return (EBUSY);
+ if (vd->dying)
+ return (ENXIO);
+
+ if (vd->ops->open != NULL) {
+ int error = vd->ops->open(vd->hw_softc);
+ if (error != 0)
+ return (error);
+ }
+
+ vd->owner = vf;
+ 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)
+{
+ size_t page_off, chunk;
+ int pidx;
+ caddr_t dst;
+
+ if (offset + len > vb->length)
+ return (ENOSPC);
+
+ while (len > 0) {
+ pidx = offset / PAGE_SIZE;
+ page_off = offset % PAGE_SIZE;
+ chunk = MIN(len, PAGE_SIZE - page_off);
+
+ dst = (caddr_t)PHYS_TO_DMAP(VM_PAGE_TO_PHYS(vb->pages[pidx]));
+ memcpy(dst + page_off, src, chunk);
+
+ src = (const char *)src + chunk;
+ offset += chunk;
+ len -= chunk;
+ }
+ return (0);
+}
+
+void
+video_buf_done(struct video_buf *vb, size_t bytesused, uint32_t sequence)
+{
+ struct video_device *vd;
+
+ vb->bytesused = bytesused;
+ vb->sequence = sequence;
+ getmicrouptime(&vb->timestamp);
+ vb->flags = V4L2_BUF_FLAG_DONE | V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+
+ vd = vb->vd;
+ mtx_lock(&vd->mtx);
+ 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;
+
+ vb->bytesused = 0;
+ vb->flags = V4L2_BUF_FLAG_ERROR;
+ getmicrouptime(&vb->timestamp);
+
+ mtx_lock(&vd->mtx);
+ 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 = vd->ops->querycap(vd->hw_softc, &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 = vd->ops->enum_format(vd->hw_softc, 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 = vd->ops->get_format(vd->hw_softc, &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 = vd->ops->set_format(vd->hw_softc, &fmt);
+ if (error == 0) {
+ vd->ops->get_format(vd->hw_softc, &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);
+ if (vd->ops->try_format == NULL)
+ return (ENOTTY);
+
+ 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 = vd->ops->try_format(vd->hw_softc, &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)
+{
+ 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);
+ }
+
+ if (vd->pool != NULL) {
+ mtx_lock(&vd->mtx);
+ STAILQ_INIT(&vd->queued);
+ STAILQ_INIT(&vd->done);
+ mtx_unlock(&vd->mtx);
+ video_pool_rele(vd->pool);
+ vd->pool = NULL;
+ }
+
+ if (rb->count == 0) {
+ 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 = vd->ops->get_format(vd->hw_softc, &vd->format);
+ if (error != 0 || vd->format.sizeimage == 0) {
+ sx_xunlock(&vd->cfg_sx);
+ return (error != 0 ? error : EINVAL);
+ }
+ }
+
+ vd->pool = video_pool_alloc(rb->count, vd->format.sizeimage);
+ if (vd->pool == NULL) {
+ sx_xunlock(&vd->cfg_sx);
+ return (ENOMEM);
+ }
+
+ 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 || b->index >= vd->pool->nbufs) {
+ mtx_unlock(&vd->mtx);
+ return (EINVAL);
+ }
+
+ vb = &vd->pool->bufs[b->index];
+ if (vb->state != VB_IDLE && vb->state != VB_DONE &&
+ vb->state != VB_ERROR) {
+ 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);
+ vb->state = VB_IDLE;
+ mtx_unlock(&vd->mtx);
+
+ 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;
+ 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 = vd->ops->start_stream(vd->hw_softc);
+ 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);
+
+ vd->ops->stop_stream(vd->hw_softc);
+
+ 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;
+ }
+ 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);
+ if (vd->ops->get_parm == NULL)
+ return (ENOTTY);
+
+ memset(&fract, 0, sizeof(fract));
+ error = vd->ops->get_parm(vd->hw_softc, &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);
+ if (vd->ops->set_parm == NULL)
+ return (ENOTTY);
+
+ fract.numerator = sp->parm.capture.timeperframe.numerator;
+ fract.denominator = sp->parm.capture.timeperframe.denominator;
+
+ error = vd->ops->set_parm(vd->hw_softc, &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;
+
+ if (vd->ops->query_control == NULL)
+ return (ENOTTY);
+
+ memset(&desc, 0, sizeof(desc));
+ desc.id = qc->id;
+ error = vd->ops->query_control(vd->hw_softc, &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;
+
+ if (vd->ops->get_control == NULL)
+ return (ENOTTY);
+
+ vc.id = c->id;
+ vc.value = 0;
+ error = vd->ops->get_control(vd->hw_softc, &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;
+
+ if (vd->ops->set_control == NULL)
+ return (ENOTTY);
+
+ vc.id = c->id;
+ vc.value = c->value;
+ return (vd->ops->set_control(vd->hw_softc, &vc));
+}
+
+static int
+video_enum_framesizes(struct video_device *vd, struct v4l2_frmsizeenum *fs)
+{
+ struct video_frmsizeenum vfs;
+ int error;
+
+ if (vd->ops->enum_framesizes == NULL)
+ return (ENOTTY);
+
+ memset(&vfs, 0, sizeof(vfs));
+ vfs.index = fs->index;
+ vfs.pixelformat = fs->pixel_format;
+ error = vd->ops->enum_framesizes(vd->hw_softc, &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;
+
+ if (vd->ops->enum_frameintervals == NULL)
+ return (ENOTTY);
+
+ memset(&vfi, 0, sizeof(vfi));
+ vfi.index = fi->index;
+ vfi.pixelformat = fi->pixel_format;
+ vfi.width = fi->width;
+ vfi.height = fi->height;
+ error = vd->ops->enum_frameintervals(vd->hw_softc, &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;
+
+ if (vd->ops->enum_input == NULL)
+ return (ENOTTY);
+
+ memset(&vi, 0, sizeof(vi));
+ error = vd->ops->enum_input(vd->hw_softc, 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;
+
+ if (vd->ops->get_input == NULL)
+ return (ENOTTY);
+
+ error = vd->ops->get_input(vd->hw_softc, &idx);
+ if (error == 0)
+ *index = idx;
+ return (error);
+}
+
+static int
+video_s_input(struct video_device *vd, int *index)
+{
+
+ if (vd->ops->set_input == NULL)
+ return (ENOTTY);
+ return (vd->ops->set_input(vd->hw_softc, *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_file *vf;
+ struct video_buf *vb;
+ size_t chunk, page_off;
+ caddr_t src;
+ int pidx, error;
+
+ if (vd == NULL || vd->dying)
+ return (ENXIO);
+
+ error = devfs_get_cdevpriv((void **)&vf);
+ if (error != 0)
+ return (error);
+
+ sx_xlock(&vd->cfg_sx);
+ error = video_claim_owner(vd, vf);
+ if (error != 0) {
+ sx_xunlock(&vd->cfg_sx);
+ return (error);
+ }
+
+ if (!vd->streaming) {
+ if (vd->mode == VMODE_MMAP) {
+ sx_xunlock(&vd->cfg_sx);
+ return (EBUSY);
+ }
+
+ if (vd->format.sizeimage == 0) {
+ error = vd->ops->get_format(vd->hw_softc, &vd->format);
+ if (error != 0 || vd->format.sizeimage == 0) {
+ sx_xunlock(&vd->cfg_sx);
+ return (error != 0 ? error : EIO);
+ }
+ }
+
+ if (vd->pool == NULL) {
+ vd->pool = video_pool_alloc(VIDEO_READ_BUFFERS,
+ vd->format.sizeimage);
+ if (vd->pool == NULL) {
+ sx_xunlock(&vd->cfg_sx);
+ return (ENOMEM);
+ }
+ }
+ 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 = vd->ops->start_stream(vd->hw_softc);
+ if (error != 0) {
+ mtx_lock(&vd->mtx);
+ STAILQ_INIT(&vd->queued);
+ mtx_unlock(&vd->mtx);
+ video_pool_rele(vd->pool);
+ vd->pool = NULL;
+ vd->mode = VMODE_NONE;
+ sx_xunlock(&vd->cfg_sx);
+ return (error);
+ }
+
+ mtx_lock(&vd->mtx);
+ vd->streaming = true;
+ mtx_unlock(&vd->mtx);
+ }
+ sx_xunlock(&vd->cfg_sx);
+
+ if (vf->read_buf == NULL) {
+ mtx_lock(&vd->mtx);
+ while (STAILQ_EMPTY(&vd->done)) {
+ if (!vd->streaming) {
+ mtx_unlock(&vd->mtx);
+ return (EIO);
+ }
+ if (ioflag & O_NONBLOCK) {
+ mtx_unlock(&vd->mtx);
+ return (EAGAIN);
+ }
+ error = msleep(&vd->done, &vd->mtx, PCATCH,
+ "vread", 0);
+ if (error != 0) {
+ mtx_unlock(&vd->mtx);
+ return (error);
+ }
+ }
+ vb = STAILQ_FIRST(&vd->done);
+ STAILQ_REMOVE_HEAD(&vd->done, entry);
+ mtx_unlock(&vd->mtx);
+
+ vf->read_buf = vb;
+ vf->read_offset = 0;
+ }
+
+ vb = vf->read_buf;
+
+ while (uio->uio_resid > 0 && vf->read_offset < vb->bytesused) {
+ pidx = vf->read_offset / PAGE_SIZE;
+ page_off = vf->read_offset % PAGE_SIZE;
+ chunk = MIN((size_t)uio->uio_resid,
+ MIN(PAGE_SIZE - page_off, vb->bytesused - vf->read_offset));
+
+ src = (caddr_t)PHYS_TO_DMAP(
+ VM_PAGE_TO_PHYS(vb->pages[pidx]));
+ error = uiomove(src + page_off, chunk, uio);
+ if (error != 0)
+ return (error);
+ vf->read_offset += chunk;
+ }
+
+ if (vf->read_offset >= vb->bytesused) {
+ vf->read_buf = NULL;
+ vf->read_offset = 0;
+
+ mtx_lock(&vd->mtx);
+ vb->state = VB_QUEUED;
+ vb->bytesused = 0;
+ STAILQ_INSERT_TAIL(&vd->queued, vb, entry);
+ mtx_unlock(&vd->mtx);
+ }
+
+ return (0);
+}
+
+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;
+ struct video_mmap_handle *h;
+ vm_object_t obj;
+ 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 < 0 || (size_t)*offset + size > vd->pool->map_size) {
+ sx_sunlock(&vd->cfg_sx);
+ return (EINVAL);
+ }
+
+ h = malloc(sizeof(*h), M_VIDEO, M_WAITOK);
+ h->cdev = dev;
+ h->pool = vd->pool;
+ video_pool_ref(h->pool);
+ sx_sunlock(&vd->cfg_sx);
+
+ obj = cdev_pager_allocate(h, OBJT_MGTDEVICE, &video_pager_ops,
+ h->pool->map_size, nprot, *offset, curthread->td_ucred);
+ if (obj == NULL) {
+ video_pool_rele(h->pool);
+ free(h, M_VIDEO);
+ return (ENOMEM);
+ }
+
+ *objp = obj;
+ return (0);
+}
+
+int
+video_register(device_t dev, const struct video_hw_ops *ops, void *hw_softc,
+ struct video_device **vdp)
+{
+ struct video_device *vd;
+ int unit;
+
+ vd = malloc(sizeof(*vd), M_VIDEO, M_WAITOK | M_ZERO);
+ vd->dev = dev;
+ vd->ops = ops;
+ vd->hw_softc = hw_softc;
+
+ 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);
+
+ unit = alloc_unr(video_unrhdr);
+ vd->unit = unit;
+
+ vd->cdev = make_dev(&video_cdevsw, unit, UID_ROOT, GID_VIDEO,
+ 0660, "video%d", unit);
+ vd->cdev->si_drv1 = vd;
+
+ *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);
+
+ vd->ops->stop_stream(vd->hw_softc);
+
+ mtx_lock(&vd->mtx);
+ vd->streaming = false;
+ vd->stopping = false;
+ STAILQ_INIT(&vd->queued);
+ STAILQ_INIT(&vd->done);
+ mtx_unlock(&vd->mtx);
+ }
+
+ if (vd->pool != NULL) {
+ video_pool_rele(vd->pool);
+ vd->pool = NULL;
+ }
+ sx_xunlock(&vd->cfg_sx);
+
+ destroy_dev(vd->cdev);
+ free_unr(video_unrhdr, vd->unit);
+
+ 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.h b/sys/dev/video/video_if.h
new file mode 100644
--- /dev/null
+++ b/sys/dev/video/video_if.h
@@ -0,0 +1,154 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com>
+ */
+
+#ifndef _DEV_VIDEO_VIDEO_IF_H_
+#define _DEV_VIDEO_VIDEO_IF_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;
+ };
+};
+
+/*
+ * Callbacks marked [sleepable] are called under cfg_sx.
+ * Buffer completion runs from interrupt/callback context.
+ */
+struct video_hw_ops {
+ int (*open)(void *); /* [sleepable] optional */
+ void (*close)(void *); /* [sleepable] optional */
+
+ int (*querycap)(void *, struct video_caps *);
+ int (*enum_format)(void *, uint32_t index,
+ struct video_format *);
+ int (*enum_framesizes)(void *,
+ struct video_frmsizeenum *);
+ int (*enum_frameintervals)(void *,
+ struct video_frmivalenum *);
+
+ int (*try_format)(void *, struct video_format *);
+ int (*set_format)(void *, const struct video_format *);
+ int (*get_format)(void *, struct video_format *);
+
+ int (*get_parm)(void *, struct video_fract *);
+ int (*set_parm)(void *, struct video_fract *);
+
+ int (*enum_input)(void *, uint32_t index,
+ struct video_input *);
+ int (*get_input)(void *, uint32_t *);
+ int (*set_input)(void *, uint32_t);
+
+ int (*query_control)(void *, struct video_control_desc *);
+ int (*get_control)(void *, struct video_control *);
+ int (*set_control)(void *, const struct video_control *);
+
+ int (*start_stream)(void *); /* [sleepable] */
+ void (*stop_stream)(void *); /* [sleepable] */
+};
+
+int video_register(device_t dev, const struct video_hw_ops *ops,
+ void *hw_softc, 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_IF_H_ */
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,122 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com>
+ */
+
+#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_if.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;
+
+ vm_page_t *pages;
+ int npages;
+ size_t length;
+
+ size_t bytesused;
+ uint32_t sequence;
+ struct timeval timestamp;
+ uint32_t flags;
+};
+
+STAILQ_HEAD(video_buf_list, video_buf);
+
+struct video_buf_pool {
+ u_int refs;
+ bool detached;
+
+ 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;
+ 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;
+
+ const struct video_hw_ops *ops;
+ void *hw_softc;
+
+ 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;
+
+ struct selinfo sel;
+};
+
+struct video_mmap_handle {
+ struct cdev *cdev;
+ struct video_buf_pool *pool;
+};
+
+struct video_buf_pool *video_pool_alloc(u_int nbufs, size_t buf_size);
+void video_pool_ref(struct video_buf_pool *pool);
+void video_pool_rele(struct video_buf_pool *pool);
+
+#endif /* _DEV_VIDEO_VIDEO_INTERNAL_H_ */
diff --git a/sys/modules/Makefile b/sys/modules/Makefile
--- a/sys/modules/Makefile
+++ b/sys/modules/Makefile
@@ -420,6 +420,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,9 @@
+S= ${SRCTOP}/sys
+
+.PATH: $S/dev/video
+
+KMOD= video
+SRCS= video.c \
+ device_if.h bus_if.h
+
+.include <bsd.kmod.mk>
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Wed, Sep 9, 2:54 AM (8 h, 56 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
38556880
Default Alt Text
D58367.id182558.diff (41 KB)
Attached To
Mode
D58367: video: add generic video(4) capture framework
Attached
Detach File
Event Timeline
Log In to Comment