Date: Sun, 09 Aug 2026 17:01:38 +0000 From: Adrian Chadd <adrian@FreeBSD.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org Cc: Abdelkader Boudih <freebsd@seuros.com> Subject: git: 9c9428825f4c - main - video: add generic video(4) capture framework Message-ID: <6a78b272.3907f.7a963a59@gitrepo.freebsd.org>
index | next in thread | raw e-mail
The branch main has been updated by adrian: URL: https://cgit.FreeBSD.org/src/commit/?id=9c9428825f4c55e3cb37412c661bb9d385db4c68 commit 9c9428825f4c55e3cb37412c661bb9d385db4c68 Author: Abdelkader Boudih <freebsd@seuros.com> AuthorDate: 2026-08-09 16:48:13 +0000 Commit: Adrian Chadd <adrian@FreeBSD.org> CommitDate: 2026-08-09 16:48:22 +0000 video: add generic video(4) capture framework Add a new video(4) framework that provides /dev/videoN, buffer management, mmap lifetime, and V4L2 ioctl dispatch for video capture drivers. Hardware drivers implement struct video_hw_ops callbacks and use video_buf_acquire/write/done to deliver frames. Reviewed by: adrian Differential Revision: https://reviews.freebsd.org/D58367 --- share/man/man4/Makefile | 1 + share/man/man4/video.4 | 101 ++ sys/conf/files | 6 +- sys/dev/usb/video/uvideo.h | 2 +- sys/dev/video/video.c | 1382 ++++++++++++++++++++ sys/dev/video/video.h | 117 ++ sys/dev/video/video_if.m | 196 +++ sys/dev/video/video_internal.h | 117 ++ sys/modules/Makefile | 1 + sys/modules/video/Makefile | 12 + sys/{dev/usb/video/uvideo_v4l2.h => sys/videoio.h} | 34 +- 11 files changed, 1954 insertions(+), 15 deletions(-) diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile index 9ced5eef0af4..34a665e14b72 100644 --- a/share/man/man4/Makefile +++ b/share/man/man4/Makefile @@ -1148,6 +1148,7 @@ MAN+= \ uvisor.4 \ uvscom.4 \ veriexec.4 \ + video.4 \ zyd.4 MLINKS+=geom_zero.4 gzero.4 diff --git a/share/man/man4/video.4 b/share/man/man4/video.4 new file mode 100644 index 000000000000..c578f1bfd4dd --- /dev/null +++ b/share/man/man4/video.4 @@ -0,0 +1,101 @@ +.\" +.\" Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com> +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.Dd July 28, 2026 +.Dt VIDEO 4 +.Os +.Sh NAME +.Nm video +.Nd video capture framework +.Sh SYNOPSIS +.Cd device video +.Pp +.In sys/videoio.h +.Sh DESCRIPTION +The +.Nm +framework provides a common interface for video capture devices. +It creates +.Pa /dev/video%d +device nodes and handles buffer management, memory mapping, and +ioctl dispatch on behalf of hardware drivers. +.Pp +Userland applications access capture devices through the +.Sy Video4Linux2 +.Pq V4L2 +ioctl interface. +The framework translates V4L2 requests into driver callbacks, +so hardware drivers do not implement ioctls directly. +.Pp +The following V4L2 ioctls are supported: +.Bl -bullet -compact +.It +.Dv VIDIOC_QUERYCAP +.It +.Dv VIDIOC_ENUM_FMT , +.Dv VIDIOC_G_FMT , +.Dv VIDIOC_S_FMT , +.Dv VIDIOC_TRY_FMT +.It +.Dv VIDIOC_ENUM_FRAMESIZES , +.Dv VIDIOC_ENUM_FRAMEINTERVALS +.It +.Dv VIDIOC_G_PARM , +.Dv VIDIOC_S_PARM +.It +.Dv VIDIOC_ENUMINPUT , +.Dv VIDIOC_G_INPUT , +.Dv VIDIOC_S_INPUT +.It +.Dv VIDIOC_QUERYCTRL , +.Dv VIDIOC_G_CTRL , +.Dv VIDIOC_S_CTRL +.It +.Dv VIDIOC_REQBUFS , +.Dv VIDIOC_QUERYBUF , +.Dv VIDIOC_QBUF , +.Dv VIDIOC_DQBUF +.It +.Dv VIDIOC_STREAMON , +.Dv VIDIOC_STREAMOFF +.El +.Pp +Two capture modes are supported: +.Bl -tag -width 6n +.It Cm read +Frames are obtained by calling +.Xr read 2 +on the device node. +Streaming starts automatically on the first read. +.It Cm mmap +Buffers are allocated with +.Dv VIDIOC_REQBUFS , +mapped with +.Xr mmap 2 , +and cycled with +.Dv VIDIOC_QBUF +and +.Dv VIDIOC_DQBUF . +Streaming is started and stopped with +.Dv VIDIOC_STREAMON +and +.Dv VIDIOC_STREAMOFF . +.El +.Pp +Up to 8 buffers may be allocated per device. +Buffer memory remains valid until the last mapping is unmapped, +even if the device is closed or detached. +.Sh SEE ALSO +.Xr mmap 2 , +.Xr read 2 , +.Xr fwcam 4 , +.Xr uvideo 4 +.Sh HISTORY +The +.Nm +framework first appeared in +.Fx 16.0 . +.Sh AUTHORS +.An Abdelkader Boudih Aq Mt freebsd@seuros.com diff --git a/sys/conf/files b/sys/conf/files index 165bcba4356b..44cb551d7fe7 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -1740,7 +1740,7 @@ dev/filemon/filemon.c optional filemon dev/firewire/firewire.c optional firewire dev/firewire/fwcrom.c optional firewire dev/firewire/fwdev.c optional firewire -dev/firewire/fwcam.c optional fwcam +dev/firewire/fwcam.c optional fwcam video dev/firewire/fwdma.c optional firewire dev/firewire/fwdv.c optional fwdv dev/firewire/fwisound.c optional fwisound sound @@ -3514,10 +3514,12 @@ dev/usb/template/usb_template_cdceem.c optional usb_template # USB video drivers # dev/usb/video/udl.c optional udl -dev/usb/video/uvideo.c optional uvideo +dev/usb/video/uvideo.c optional uvideo video # # USB END # +dev/video/video.c optional video +dev/video/video_if.m optional video dev/videomode/videomode.c optional videomode dev/videomode/edid.c optional videomode dev/videomode/pickmode.c optional videomode diff --git a/sys/dev/usb/video/uvideo.h b/sys/dev/usb/video/uvideo.h index 9f8570aaf7ed..fbcc0b2ec930 100644 --- a/sys/dev/usb/video/uvideo.h +++ b/sys/dev/usb/video/uvideo.h @@ -21,7 +21,7 @@ #define _UVIDEO_H_ #include <sys/queue.h> -#include "uvideo_v4l2.h" +#include <sys/videoio.h> /* * USB Video Class diff --git a/sys/dev/video/video.c b/sys/dev/video/video.c new file mode 100644 index 000000000000..e2de3974c607 --- /dev/null +++ b/sys/dev/video/video.c @@ -0,0 +1,1382 @@ +/* + * Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com> + * Copyright (c) 2008 Robert Nagy <robert@openbsd.org> + * Copyright (c) 2008 Marcus Glocker <mglocker@openbsd.org> + * + * SPDX-License-Identifier: BSD-2-Clause AND ISC + * + * The cdev, kqfilter and buffer-pool handling are derived from uvideo(4). + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/bus.h> +#include <sys/conf.h> +#include <sys/fcntl.h> +#include <sys/kernel.h> +#include <sys/lock.h> +#include <sys/rwlock.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/mutex.h> +#include <sys/poll.h> +#include <sys/proc.h> +#include <sys/selinfo.h> +#include <sys/sx.h> +#include <sys/uio.h> +#include <sys/videoio.h> + +#include <vm/vm.h> +#include <vm/vm_extern.h> +#include <vm/vm_kern.h> +#include <vm/vm_object.h> +#include <vm/vm_page.h> +#include <vm/vm_pager.h> +#include <vm/vm_param.h> +#include <vm/pmap.h> +#include <vm/vm_map.h> +#include <vm/uma.h> + +#include <dev/video/video_internal.h> + +#include "video_if.h" + +MALLOC_DEFINE(M_VIDEO, "video", "video(4) framework"); + +#define VIDEO_UNIT_RETRIES 32 + +static struct unrhdr *video_unrhdr; + +static d_open_t video_open; +static d_close_t video_close; +static d_read_t video_read; +static d_ioctl_t video_ioctl; +static d_poll_t video_poll; +static d_kqfilter_t video_kqfilter; +static d_mmap_single_t video_mmap_single; + +static struct cdevsw video_cdevsw = { + .d_version = D_VERSION, + .d_open = video_open, + .d_close = video_close, + .d_read = video_read, + .d_ioctl = video_ioctl, + .d_poll = video_poll, + .d_kqfilter = video_kqfilter, + .d_mmap_single = video_mmap_single, + .d_name = "video", +}; + +static struct video_buf_pool * +video_pool_alloc(u_int nbufs, size_t buf_size) +{ + struct video_buf_pool *pool; + struct video_buf *vb; + size_t map_size; + vm_object_t obj; + vm_offset_t kva; + u_int i; + int error; + + buf_size = round_page(buf_size); + if (buf_size == 0 || nbufs == 0 || nbufs > VIDEO_MAX_BUFFERS) + return (NULL); + if (SIZE_MAX / nbufs < buf_size) + return (NULL); + map_size = (size_t)nbufs * buf_size; + + /* + * Use phys_pager_allocate() rather than a bare vm_object_allocate(): + * the latter leaves un_pager.phys.ops NULL, which faults when the VM + * system calls phys_pager_getpages() from vm_map_wire() or from a + * userspace fault on the mapping. + */ + obj = phys_pager_allocate(NULL, &default_phys_pg_ops, NULL, map_size, + VM_PROT_ALL, 0, curthread->td_ucred); + if (obj == NULL) + return (NULL); + + kva = vm_map_min(kernel_map); + error = vm_map_find(kernel_map, obj, 0, &kva, map_size, 0, + VMFS_OPTIMAL_SPACE, VM_PROT_READ | VM_PROT_WRITE, + VM_PROT_READ | VM_PROT_WRITE, 0); + if (error != KERN_SUCCESS) { + vm_object_deallocate(obj); + return (NULL); + } + error = vm_map_wire(kernel_map, kva, kva + map_size, + VM_MAP_WIRE_SYSTEM | VM_MAP_WIRE_NOHOLES); + if (error != KERN_SUCCESS) { + vm_map_remove(kernel_map, kva, kva + map_size); + return (NULL); + } + + pool = malloc(sizeof(*pool), M_VIDEO, M_WAITOK | M_ZERO); + pool->obj = obj; + pool->kva = kva; + pool->nbufs = nbufs; + pool->buf_size = buf_size; + pool->map_size = map_size; + + for (i = 0; i < nbufs; i++) { + vb = &pool->bufs[i]; + vb->pool = pool; + vb->index = i; + vb->state = VB_IDLE; + vb->length = buf_size; + vb->buf = (uint8_t *)kva + (size_t)i * buf_size; + } + return (pool); +} + +static void +video_pool_free(struct video_buf_pool *pool) +{ + + /* + * Removing the kernel mapping drops the map's reference on the + * object. Any surviving userspace mapping holds its own reference, + * so the frames stay alive until the last one goes away. + */ + vm_map_remove(kernel_map, pool->kva, pool->kva + pool->map_size); + free(pool, M_VIDEO); +} + +static void +video_pool_detach(struct video_device *vd) +{ + struct video_buf_pool *pool; + + mtx_lock(&vd->mtx); + if (vd->owner != NULL) { + vd->owner->read_buf = NULL; + vd->owner->read_offset = 0; + } + while (vd->readers > 0) + msleep(&vd->readers, &vd->mtx, 0, "vdrain", 0); + pool = vd->pool; + vd->pool = NULL; + STAILQ_INIT(&vd->queued); + STAILQ_INIT(&vd->done); + mtx_unlock(&vd->mtx); + + if (pool != NULL) + video_pool_free(pool); +} + +static void +video_file_dtor(void *arg) +{ + struct video_file *vf = arg; + struct video_device *vd = vf->vd; + + sx_xlock(&vd->cfg_sx); + + if (vf->is_owner) { + if (vd->streaming) { + mtx_lock(&vd->mtx); + vd->stopping = true; + mtx_unlock(&vd->mtx); + + VIDEO_STOP_STREAM(vd->dev); + + mtx_lock(&vd->mtx); + vd->streaming = false; + vd->stopping = false; + STAILQ_INIT(&vd->queued); + STAILQ_INIT(&vd->done); + mtx_unlock(&vd->mtx); + } + + video_pool_detach(vd); + + mtx_lock(&vd->mtx); + vd->owner = NULL; + mtx_unlock(&vd->mtx); + vd->mode = VMODE_NONE; + + VIDEO_CLOSE(vd->dev); + } + + sx_xunlock(&vd->cfg_sx); + + free(vf, M_VIDEO); +} + +static int +video_open(struct cdev *dev, int flags, int fmt, struct thread *td) +{ + struct video_device *vd = dev->si_drv1; + struct video_file *vf; + + if (vd == NULL || vd->dying) + return (ENXIO); + + vf = malloc(sizeof(*vf), M_VIDEO, M_WAITOK | M_ZERO); + vf->vd = vd; + + return (devfs_set_cdevpriv(vf, video_file_dtor)); +} + +static int +video_close(struct cdev *dev, int flags, int fmt, struct thread *td) +{ + + return (0); +} + +static int +video_claim_owner(struct video_device *vd, struct video_file *vf) +{ + int error; + + sx_assert(&vd->cfg_sx, SA_XLOCKED); + + if (vd->dying) + return (ENXIO); + if (vd->owner == vf) + return (0); + if (vd->owner != NULL) + return (EBUSY); + + error = VIDEO_OPEN(vd->dev); + if (error != 0) + return (error); + + mtx_lock(&vd->mtx); + vd->owner = vf; + mtx_unlock(&vd->mtx); + vf->is_owner = true; + return (0); +} + +struct video_buf * +video_buf_acquire(struct video_device *vd) +{ + struct video_buf *vb; + + mtx_lock(&vd->mtx); + vb = STAILQ_FIRST(&vd->queued); + if (vb != NULL) { + STAILQ_REMOVE_HEAD(&vd->queued, entry); + vb->state = VB_ACTIVE; + vb->bytesused = 0; + } + mtx_unlock(&vd->mtx); + return (vb); +} + +int +video_buf_write(struct video_buf *vb, size_t offset, const void *src, + size_t len) +{ + + if (offset > vb->length || len > vb->length - offset) + return (ENOSPC); + + memcpy((uint8_t *)vb->buf + offset, src, len); + return (0); +} + +void +video_buf_done(struct video_buf *vb, size_t bytesused, uint32_t sequence) +{ + struct video_device *vd; + bool overrun; + + overrun = bytesused > vb->length; + if (overrun) + bytesused = 0; + + vd = vb->vd; + mtx_lock(&vd->mtx); + if (vb->state != VB_ACTIVE) { + mtx_unlock(&vd->mtx); + return; + } + vb->bytesused = bytesused; + vb->sequence = sequence; + getmicrouptime(&vb->timestamp); + vb->flags = V4L2_BUF_FLAG_DONE | V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + if (overrun) + vb->flags |= V4L2_BUF_FLAG_ERROR; + vb->state = VB_DONE; + STAILQ_INSERT_TAIL(&vd->done, vb, entry); + selwakeup(&vd->sel); + wakeup(&vd->done); + mtx_unlock(&vd->mtx); +} + +void +video_buf_error(struct video_buf *vb) +{ + struct video_device *vd = vb->vd; + + mtx_lock(&vd->mtx); + if (vb->state != VB_ACTIVE) { + mtx_unlock(&vd->mtx); + return; + } + vb->bytesused = 0; + vb->flags = V4L2_BUF_FLAG_ERROR; + getmicrouptime(&vb->timestamp); + vb->state = VB_ERROR; + STAILQ_INSERT_TAIL(&vd->done, vb, entry); + selwakeup(&vd->sel); + wakeup(&vd->done); + mtx_unlock(&vd->mtx); +} + +static int +video_querycap(struct video_device *vd, struct v4l2_capability *cap) +{ + struct video_caps vc; + int error; + + memset(&vc, 0, sizeof(vc)); + error = VIDEO_QUERYCAP(vd->dev, &vc); + if (error != 0) + return (error); + + memset(cap, 0, sizeof(*cap)); + strlcpy((char *)cap->driver, vc.driver, sizeof(cap->driver)); + strlcpy((char *)cap->card, vc.card, sizeof(cap->card)); + strlcpy((char *)cap->bus_info, vc.bus_info, sizeof(cap->bus_info)); + cap->version = vc.version; + cap->device_caps = vc.capabilities; + cap->capabilities = vc.capabilities | V4L2_CAP_DEVICE_CAPS; + return (0); +} + +static int +video_enum_fmt(struct video_device *vd, struct v4l2_fmtdesc *fd) +{ + struct video_format vf; + int error; + + if (fd->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return (EINVAL); + + memset(&vf, 0, sizeof(vf)); + error = VIDEO_ENUM_FORMAT(vd->dev, fd->index, &vf); + if (error != 0) + return (error); + + fd->pixelformat = vf.pixelformat; + fd->flags = vf.flags; + strlcpy((char *)fd->description, vf.description, + sizeof(fd->description)); + return (0); +} + +static int +video_g_fmt(struct video_device *vd, struct v4l2_format *f) +{ + struct video_format vf; + int error; + + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return (EINVAL); + + memset(&vf, 0, sizeof(vf)); + error = VIDEO_GET_FORMAT(vd->dev, &vf); + if (error != 0) + return (error); + + memset(&f->fmt.pix, 0, sizeof(f->fmt.pix)); + f->fmt.pix.width = vf.width; + f->fmt.pix.height = vf.height; + f->fmt.pix.pixelformat = vf.pixelformat; + f->fmt.pix.field = vf.field; + f->fmt.pix.bytesperline = vf.bytesperline; + f->fmt.pix.sizeimage = vf.sizeimage; + f->fmt.pix.colorspace = vf.colorspace; + f->fmt.pix.xfer_func = vf.xfer_func; + f->fmt.pix.ycbcr_enc = vf.ycbcr_enc; + f->fmt.pix.flags = vf.flags; + return (0); +} + +static int +video_s_fmt(struct video_device *vd, struct video_file *vf, + struct v4l2_format *f) +{ + struct video_format fmt; + int error; + + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return (EINVAL); + + sx_xlock(&vd->cfg_sx); + error = video_claim_owner(vd, vf); + if (error != 0) { + sx_xunlock(&vd->cfg_sx); + return (error); + } + if (vd->streaming) { + sx_xunlock(&vd->cfg_sx); + return (EBUSY); + } + + memset(&fmt, 0, sizeof(fmt)); + fmt.width = f->fmt.pix.width; + fmt.height = f->fmt.pix.height; + fmt.pixelformat = f->fmt.pix.pixelformat; + fmt.field = f->fmt.pix.field; + + error = VIDEO_SET_FORMAT(vd->dev, &fmt); + if (error == 0) { + VIDEO_GET_FORMAT(vd->dev, &vd->format); + + f->fmt.pix.width = vd->format.width; + f->fmt.pix.height = vd->format.height; + f->fmt.pix.pixelformat = vd->format.pixelformat; + f->fmt.pix.field = vd->format.field; + f->fmt.pix.bytesperline = vd->format.bytesperline; + f->fmt.pix.sizeimage = vd->format.sizeimage; + f->fmt.pix.colorspace = vd->format.colorspace; + } + sx_xunlock(&vd->cfg_sx); + return (error); +} + +static int +video_try_fmt(struct video_device *vd, struct v4l2_format *f) +{ + struct video_format fmt; + int error; + + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return (EINVAL); + memset(&fmt, 0, sizeof(fmt)); + fmt.width = f->fmt.pix.width; + fmt.height = f->fmt.pix.height; + fmt.pixelformat = f->fmt.pix.pixelformat; + fmt.field = f->fmt.pix.field; + + error = VIDEO_TRY_FORMAT(vd->dev, &fmt); + if (error == 0) { + f->fmt.pix.width = fmt.width; + f->fmt.pix.height = fmt.height; + f->fmt.pix.pixelformat = fmt.pixelformat; + f->fmt.pix.field = fmt.field; + f->fmt.pix.bytesperline = fmt.bytesperline; + f->fmt.pix.sizeimage = fmt.sizeimage; + f->fmt.pix.colorspace = fmt.colorspace; + } + return (error); +} + +static int +video_reqbufs(struct video_device *vd, struct video_file *vf, + struct v4l2_requestbuffers *rb) +{ + struct video_buf_pool *pool; + int error; + + if (rb->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return (EINVAL); + if (rb->memory != V4L2_MEMORY_MMAP) + return (EINVAL); + + sx_xlock(&vd->cfg_sx); + error = video_claim_owner(vd, vf); + if (error != 0) { + sx_xunlock(&vd->cfg_sx); + return (error); + } + if (vd->streaming) { + sx_xunlock(&vd->cfg_sx); + return (EBUSY); + } + + video_pool_detach(vd); + + if (rb->count == 0) { + vd->mode = VMODE_NONE; + sx_xunlock(&vd->cfg_sx); + rb->capabilities = V4L2_BUF_CAP_SUPPORTS_MMAP; + return (0); + } + + if (rb->count > VIDEO_MAX_BUFFERS) + rb->count = VIDEO_MAX_BUFFERS; + if (rb->count < 2) + rb->count = 2; + + if (vd->format.sizeimage == 0) { + error = VIDEO_GET_FORMAT(vd->dev, &vd->format); + if (error != 0 || vd->format.sizeimage == 0) { + sx_xunlock(&vd->cfg_sx); + return (error != 0 ? error : EINVAL); + } + } + + pool = video_pool_alloc(rb->count, vd->format.sizeimage); + if (pool == NULL) { + sx_xunlock(&vd->cfg_sx); + return (ENOMEM); + } + mtx_lock(&vd->mtx); + vd->pool = pool; + mtx_unlock(&vd->mtx); + + vd->mode = VMODE_MMAP; + rb->capabilities = V4L2_BUF_CAP_SUPPORTS_MMAP; + sx_xunlock(&vd->cfg_sx); + return (0); +} + +static int +video_querybuf(struct video_device *vd, struct video_file *vf, + struct v4l2_buffer *b) +{ + struct video_buf *vb; + + if (b->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return (EINVAL); + + mtx_lock(&vd->mtx); + if (vd->owner != vf) { + mtx_unlock(&vd->mtx); + return (EPERM); + } + if (vd->pool == NULL || b->index >= vd->pool->nbufs) { + mtx_unlock(&vd->mtx); + return (EINVAL); + } + + vb = &vd->pool->bufs[b->index]; + b->memory = V4L2_MEMORY_MMAP; + b->length = vb->length; + b->m.offset = vb->index * vd->pool->buf_size; + b->bytesused = vb->bytesused; + b->field = V4L2_FIELD_NONE; + b->timestamp = vb->timestamp; + b->sequence = vb->sequence; + b->flags = 0; + + switch (vb->state) { + case VB_QUEUED: + case VB_ACTIVE: + b->flags |= V4L2_BUF_FLAG_QUEUED; + break; + case VB_DONE: + b->flags |= V4L2_BUF_FLAG_DONE; + break; + case VB_ERROR: + b->flags |= V4L2_BUF_FLAG_DONE | V4L2_BUF_FLAG_ERROR; + break; + default: + break; + } + b->flags |= V4L2_BUF_FLAG_MAPPED | V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + mtx_unlock(&vd->mtx); + return (0); +} + +static int +video_qbuf(struct video_device *vd, struct video_file *vf, + struct v4l2_buffer *b) +{ + struct video_buf *vb; + + if (b->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return (EINVAL); + if (b->memory != V4L2_MEMORY_MMAP) + return (EINVAL); + + mtx_lock(&vd->mtx); + if (vd->owner != vf) { + mtx_unlock(&vd->mtx); + return (EPERM); + } + if (vd->pool == NULL || vd->mode != VMODE_MMAP || + b->index >= vd->pool->nbufs) { + mtx_unlock(&vd->mtx); + return (EINVAL); + } + + vb = &vd->pool->bufs[b->index]; + if (vb->state != VB_IDLE) { + mtx_unlock(&vd->mtx); + return (EINVAL); + } + + vb->state = VB_QUEUED; + vb->bytesused = 0; + vb->vd = vd; + STAILQ_INSERT_TAIL(&vd->queued, vb, entry); + mtx_unlock(&vd->mtx); + + b->flags = V4L2_BUF_FLAG_QUEUED | V4L2_BUF_FLAG_MAPPED; + return (0); +} + +static int +video_dqbuf(struct video_device *vd, struct video_file *vf, + struct v4l2_buffer *b, int flags) +{ + struct video_buf *vb; + int error; + + if (b->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return (EINVAL); + if (b->memory != V4L2_MEMORY_MMAP) + return (EINVAL); + + mtx_lock(&vd->mtx); + if (vd->owner != vf) { + mtx_unlock(&vd->mtx); + return (EPERM); + } + while (STAILQ_EMPTY(&vd->done)) { + if (!vd->streaming || vd->stopping) { + mtx_unlock(&vd->mtx); + return (EINVAL); + } + if (flags & O_NONBLOCK) { + mtx_unlock(&vd->mtx); + return (EAGAIN); + } + error = msleep(&vd->done, &vd->mtx, PCATCH, "vdqbuf", 0); + if (error != 0) { + mtx_unlock(&vd->mtx); + return (error); + } + } + + vb = STAILQ_FIRST(&vd->done); + STAILQ_REMOVE_HEAD(&vd->done, entry); + + b->index = vb->index; + b->bytesused = vb->bytesused; + b->field = V4L2_FIELD_NONE; + b->timestamp = vb->timestamp; + b->sequence = vb->sequence; + b->memory = V4L2_MEMORY_MMAP; + b->m.offset = vb->index * vd->pool->buf_size; + b->length = vb->length; + b->flags = V4L2_BUF_FLAG_MAPPED | V4L2_BUF_FLAG_DONE | + V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + if (vb->flags & V4L2_BUF_FLAG_ERROR) + b->flags |= V4L2_BUF_FLAG_ERROR; + + vb->state = VB_IDLE; + mtx_unlock(&vd->mtx); + + return (0); +} + +static int +video_streamon(struct video_device *vd, struct video_file *vf, int *type) +{ + int error; + + if (*type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return (EINVAL); + + sx_xlock(&vd->cfg_sx); + error = video_claim_owner(vd, vf); + if (error != 0) { + sx_xunlock(&vd->cfg_sx); + return (error); + } + if (vd->streaming) { + sx_xunlock(&vd->cfg_sx); + return (0); + } + if (vd->pool == NULL || vd->mode != VMODE_MMAP) { + sx_xunlock(&vd->cfg_sx); + return (EINVAL); + } + + error = VIDEO_START_STREAM(vd->dev); + if (error != 0) { + sx_xunlock(&vd->cfg_sx); + return (error); + } + + mtx_lock(&vd->mtx); + vd->streaming = true; + mtx_unlock(&vd->mtx); + + sx_xunlock(&vd->cfg_sx); + return (0); +} + +static int +video_streamoff(struct video_device *vd, struct video_file *vf, int *type) +{ + struct video_buf *vb; + u_int i; + + if (*type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return (EINVAL); + + sx_xlock(&vd->cfg_sx); + if (vd->owner != vf) { + sx_xunlock(&vd->cfg_sx); + return (EBUSY); + } + if (!vd->streaming) { + sx_xunlock(&vd->cfg_sx); + return (0); + } + + mtx_lock(&vd->mtx); + vd->stopping = true; + wakeup(&vd->done); + mtx_unlock(&vd->mtx); + + VIDEO_STOP_STREAM(vd->dev); + + mtx_lock(&vd->mtx); + vd->streaming = false; + vd->stopping = false; + STAILQ_INIT(&vd->queued); + STAILQ_INIT(&vd->done); + for (i = 0; i < vd->pool->nbufs; i++) { + vb = &vd->pool->bufs[i]; + vb->state = VB_IDLE; + vb->bytesused = 0; + vb->vd = NULL; + } + vf->read_buf = NULL; + vf->read_offset = 0; + mtx_unlock(&vd->mtx); + + sx_xunlock(&vd->cfg_sx); + return (0); +} + +static int +video_g_parm(struct video_device *vd, struct v4l2_streamparm *sp) +{ + struct video_fract fract; + int error; + + if (sp->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return (EINVAL); + memset(&fract, 0, sizeof(fract)); + error = VIDEO_GET_PARM(vd->dev, &fract); + if (error != 0) + return (error); + + memset(&sp->parm.capture, 0, sizeof(sp->parm.capture)); + sp->parm.capture.capability = V4L2_CAP_TIMEPERFRAME; + sp->parm.capture.timeperframe.numerator = fract.numerator; + sp->parm.capture.timeperframe.denominator = fract.denominator; + return (0); +} + +static int +video_s_parm(struct video_device *vd, struct v4l2_streamparm *sp) +{ + struct video_fract fract; + int error; + + if (sp->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return (EINVAL); + fract.numerator = sp->parm.capture.timeperframe.numerator; + fract.denominator = sp->parm.capture.timeperframe.denominator; + + error = VIDEO_SET_PARM(vd->dev, &fract); + if (error != 0) + return (error); + + sp->parm.capture.capability = V4L2_CAP_TIMEPERFRAME; *** 1152 LINES SKIPPED ***home | help
Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?6a78b272.3907f.7a963a59>
