Page MenuHomeFreeBSD

D58367.id182558.diff
No OneTemporary

D58367.id182558.diff

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

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)

Event Timeline