Skip to content

video(4)

Source: sys/dev/video/video.c, video.h, video_if.m, video_internal.h, sys/sys/videoio.h, sys/modules/video/. Derived from OpenBSD’s uvideo(4) framework layer.

The layer between a camera driver and userland. Drivers register a device and implement a small kobj ops table; the framework owns /dev/videoN, the V4L2 ioctl surface, the buffer pool, mmap, read(2), poll and kqueue. A driver never sees a v4l2 struct and never sees a user page.

Two backends register with it today: uvideo(4) for USB Video Class, and fwcam(4) for FireWire IIDC cameras.

Six methods are mandatory: querycap, enum_format, get_format, set_format, start_stream, stop_stream. Everything else defaults to ENOTTY. One contract line carries the whole design: stop_stream must quiesce the provider before returning. Every teardown path in the framework leans on it.

The driver-side data path is four calls, safe from completion context:

vb = video_buf_acquire(vd); /* NULL: nothing queued, drop frame */
video_buf_write(vb, 0, buf, len);
video_buf_done(vb, len, seq++); /* or video_buf_error(vb) */

Buffers live on two intrusive lists, queued (awaiting hardware) and done (awaiting the consumer). An ACTIVE buffer is on neither: the driver owns it.

video_buf_done and video_buf_error are no-ops when the buffer is not ACTIVE. That guard, plus the quiesce contract, is the whole race-with-STREAMOFF story: by the time the framework resets buffer states, no driver can still be holding one.

An empty queued list at acquire time means userland fell behind; the frame is dropped silently and only the driver counts it.

The first file descriptor to set a format, request buffers, stream, or read becomes the exclusive owner; that is when the driver’s open method runs, not at open(2). Everyone else can still query.

Modes are mutually exclusive per device:

ModeEntered byBuffers
mmapREQBUFS count > 02-8, user queues and dequeues
readfirst read(2)3, autoqueued, requeued on consumption

Read mode is fully self-driving: first read starts the stream, every fully-consumed buffer goes straight back to QUEUED, partial reads keep an offset. Continuous capture with zero buffer management.

The pool is a single wired OBJT_PHYS object created through phys_pager_allocate, and mmap_single hands out references to that whole object; the V4L2 m.offset picks the buffer inside it. Two consequences:

  • A bare vm_object_allocate would leave the phys pager ops NULL and fault on first touch, which is why the comment in the source insists on the allocation path.
  • Userland mappings hold real object references, so mapped frames stay valid after close and even after driver detach. Teardown only removes the kernel mapping.

Two locks, strict order cfg_sx then mtx. The sx serializes configuration and is held around driver method calls, so backends may sleep. The mutex protects the lists, states and wakeups, and is the only lock the video_buf_* calls take, which is what makes them completion-context safe. read(2) bumps a readers count and drops the mutex for uiomove; pool teardown drains that count before freeing, so a pool never dies mid-copy.

Unregister sets a dying flag first, force-stops the stream, then destroy_dev drains every cdevsw thread before memory goes away.

Terminal window
kldload video uvideo # or fwcam
ls /dev/video*

crucible (iMac17,1) is the working uvideo box; fwcam adds FireWire cameras to the same /dev/videoN surface.