From nobody Sun Aug 9 17:01:38 2026 X-Original-To: dev-commits-src-main@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4hJ4143HVFz6nlqJ for ; Sun, 09 Aug 2026 17:01:44 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "YR1" (not verified)) by mx1.freebsd.org (Postfix) with ESMTPS id 4hJ4133mGnz3lpm for ; Sun, 09 Aug 2026 17:01:43 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1786294903; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=lmKREy/tzIfCyuCJ3iqIBi/k7nParAwMVwlQ97SZTXA=; b=mazI6+nU1El6o1TpKmg2CWGeVGMmIxJ/7on7tfDbZaC+giWrxwhN4ZjCYeQIgwwQhOl/5R /5nJurz0/9kNfPhxIF4HJOSM5xBvZwvZeL+q8PXZ4Pl94MSilk1Tnf8ViE+MKLsCXUS9bV wiZEzZofvPpzzRx6PFmzsUPkB4p2iGiB1RjdvMZoUkIaJzDjiejLKnUvtYelLBfjXqG4BD Z8sXxiqTBMI7ULzCUQSO66t5BF5S5M+6Y6UxR0ssrMM1vfLqd9eCZUQhNvVY6Nljhdk2Yv nkLM1SuvyF024EJ8035zZYS4STQBmooOeamga6XoNCUzyRf05uNEeaJIynOybg== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1786294903; a=rsa-sha256; cv=none; b=IzZjXKm0qtglvLoWUTYBfD61TMQhGDm6BEqCmvy2GGc+yE6VA54+vKH+GofSdX6GTB5f6a H4vNXKJEU8snE1QKPCSBc3u3QvrnEKP1Fl4raPeWH9NjYQjCh7FOqMrqWNq2UsfyNEXNSD 3ZycEqpMzCFWnQGephJOPMGGTqVmAtnTSICfgoSaLWRAdBD+LZMrKAKC4evEnKlY3DI+X4 MoQn558Z6GTYexx6a47ey7PaxsQvHGmNKt9TijvszpQbOW9o6on+N+oGLswQdjTQL38czO ZQXLbvPf/Bbn3/i/NT05WcvSvDY+Za6wON+HKRLzH9bV8agTus6tS0mJWH1uMQ== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1786294903; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=lmKREy/tzIfCyuCJ3iqIBi/k7nParAwMVwlQ97SZTXA=; b=tJhkI345px8ZRUskbLg+YZzdPtgDETsBFhhcWUVoHKH1c3v1FLvNdvK2NaNNldwtuV5glp jSHcM6mHwYiKXdAFomXG8sbG6Z+5WgeURXR4owfUTmlkg7yRqbPit5WnNlLHVDkdNphBd7 QutW5GaJ7/EbO6bRzLgxPHo9QgxkHjXtLQOgLMlLpRy3Dkk7p6LN6Lo5W0kK4/rJZfyXdH wBxsBDKlsb7UuwBhK7CemjaRoZcgbGi5VBErp3dWQwBVKrTYXmoyuCyRJ4dqfqLOtuhX9x 2qp0y3PYFbu7hBaXGucM1q2ZxUuho0o90vb2/sXpqSk0Q/v4GdPc8ovkV/yMUQ== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4hJ4132SkXzY2R for ; Sun, 09 Aug 2026 17:01:43 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 3907f by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Sun, 09 Aug 2026 17:01:38 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org Cc: Abdelkader Boudih From: Adrian Chadd Subject: git: 9c9428825f4c - main - video: add generic video(4) capture framework List-Id: Commit messages for the main branch of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-main List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-main@freebsd.org Sender: owner-dev-commits-src-main@FreeBSD.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: adrian X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: 9c9428825f4c55e3cb37412c661bb9d385db4c68 Auto-Submitted: auto-generated Date: Sun, 09 Aug 2026 17:01:38 +0000 Message-Id: <6a78b272.3907f.7a963a59@gitrepo.freebsd.org> The branch main has been updated by adrian: URL: https://cgit.FreeBSD.org/src/commit/?id=9c9428825f4c55e3cb37412c661bb9d385db4c68 commit 9c9428825f4c55e3cb37412c661bb9d385db4c68 Author: Abdelkader Boudih AuthorDate: 2026-08-09 16:48:13 +0000 Commit: Adrian Chadd 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 +.\" +.\" 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 -#include "uvideo_v4l2.h" +#include /* * 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 + * Copyright (c) 2008 Robert Nagy + * Copyright (c) 2008 Marcus Glocker + * + * SPDX-License-Identifier: BSD-2-Clause AND ISC + * + * The cdev, kqfilter and buffer-pool handling are derived from uvideo(4). + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 ***