Phase 8.1: kernel V4L2 device skeleton (out-of-tree module)
Out-of-tree Linux kernel module registering /dev/videoNN. Phase 8.1 scope: skeleton only — VIDIOC_QUERYCAP works, no codec ioctls / no vb2_queue / no controls yet. Real V4L2 plumbing throughout per "correctness before speed": platform_device + v4l2_device + video_device, properly nested with error paths and devm_kzalloc-managed lifetime. Per-cycle 9 discipline ports to kernel code: SPDX header, kernel coding style (8-tab, static-by-default), kerneldoc on structs, no shortcuts. Files (~250 LOC total): - kernel/Makefile — out-of-tree kbuild with checkpatch target - kernel/daedalus_v4l2_main.c — module init/exit + probe/remove Verification on hertz (Pi 5, 6.12.75+rpt-rpi-2712): - Builds clean with -Wall -Wextra. No warnings. - modprobe / rmmod round-trip clean. No dmesg taints beyond the expected "out-of-tree taint" line. - v4l2-ctl --list-devices shows: "daedalus-fourier V3D7+NEON (platform:daedalus_v4l2): /dev/video0" - VIDIOC_QUERYCAP returns driver/card/bus/caps as specified. - v4l2-compliance: 44/48 passing. The 4 failures are exactly the format/buffer ioctls Phase 8.2 will implement (ENUM_FMT, G_FMT, Scaling, REQBUFS) — not skeleton bugs, legitimately-absent features. Documentation: docs/phase_8_1_closure.md captures full verification output + Phase 8.2 plan. Phase 8.1 acceptance criteria met: - ✓ /dev/videoNN appears via v4l2-ctl --list-devices - ✓ VIDIOC_QUERYCAP responds with sensible values - ✓ rmmod is clean (no kref leaks) - ✓ v4l2-compliance passes except for explicit Phase 8.2 work Next: Phase 8.2 chardev bridge for kernel ↔ daemon IPC. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,129 @@
|
|||||||
|
# Phase 8.1 closure — kernel module skeleton
|
||||||
|
|
||||||
|
**Status:** closed 2026-05-18.
|
||||||
|
|
||||||
|
Out-of-tree Linux kernel module `daedalus_v4l2` that registers a
|
||||||
|
`/dev/videoNN` V4L2 device on a synthesised platform device.
|
||||||
|
Phase 8.1's scope: skeleton only — no actual decoder ioctls, no
|
||||||
|
buffer queue, no controls. Subsequent phases (8.2 chardev
|
||||||
|
bridge, 8.3 daemon parse, 8.4 VP9 end-to-end, etc.) build on
|
||||||
|
this base.
|
||||||
|
|
||||||
|
## What lands
|
||||||
|
|
||||||
|
- `kernel/Makefile` — out-of-tree kbuild stub. `make` against
|
||||||
|
the running kernel via `/lib/modules/$(uname -r)/build`.
|
||||||
|
Includes `make checkpatch` target for kernel coding-style
|
||||||
|
verification.
|
||||||
|
- `kernel/daedalus_v4l2_main.c` — ~190 lines. Real V4L2
|
||||||
|
plumbing: `platform_device` + `v4l2_device` +
|
||||||
|
`video_device`. Implements `VIDIOC_QUERYCAP`; everything
|
||||||
|
else falls through to `v4l2-core` defaults.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
On hertz (Pi 5, 6.12.75+rpt-rpi-2712):
|
||||||
|
|
||||||
|
### Build
|
||||||
|
|
||||||
|
```
|
||||||
|
$ cd ~/src/daedalus-v4l2/kernel && make
|
||||||
|
make -C /lib/modules/6.12.75+rpt-rpi-2712/build M=... modules
|
||||||
|
CC [M] daedalus_v4l2_main.o
|
||||||
|
LD [M] daedalus_v4l2.o
|
||||||
|
MODPOST Module.symvers
|
||||||
|
CC [M] daedalus_v4l2.mod.o
|
||||||
|
LD [M] daedalus_v4l2.ko
|
||||||
|
```
|
||||||
|
|
||||||
|
Builds clean with `-Wall -Wextra`. No warnings.
|
||||||
|
|
||||||
|
### Load + dmesg
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo insmod daedalus_v4l2.ko
|
||||||
|
$ sudo dmesg | tail -2
|
||||||
|
daedalus_v4l2: loading out-of-tree module taints kernel.
|
||||||
|
daedalus_v4l2 daedalus_v4l2: daedalus-v4l2 registered as /dev/video0
|
||||||
|
(Phase 8.1 skeleton)
|
||||||
|
```
|
||||||
|
|
||||||
|
### v4l2-ctl --list-devices
|
||||||
|
|
||||||
|
```
|
||||||
|
daedalus-fourier V3D7+NEON (platform:daedalus_v4l2):
|
||||||
|
/dev/video0
|
||||||
|
```
|
||||||
|
|
||||||
|
(Appears alongside the existing `pispbe` and `rpi-hevc-dec`
|
||||||
|
devices.)
|
||||||
|
|
||||||
|
### VIDIOC_QUERYCAP
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo v4l2-ctl --device /dev/video0 --info
|
||||||
|
Driver Info:
|
||||||
|
Driver name : daedalus_v4l2
|
||||||
|
Card type : daedalus-fourier V3D7+NEON
|
||||||
|
Bus info : platform:daedalus_v4l2
|
||||||
|
Driver version : 6.12.75
|
||||||
|
Capabilities : 0x84204000
|
||||||
|
Video Memory-to-Memory Multiplanar
|
||||||
|
Streaming
|
||||||
|
Extended Pix Format
|
||||||
|
```
|
||||||
|
|
||||||
|
### v4l2-compliance
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo v4l2-compliance --device /dev/video0
|
||||||
|
Total for daedalus_v4l2 device /dev/video0: 48, Succeeded: 44,
|
||||||
|
Failed: 4, Warnings: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
44/48 passing. The 4 failures are exactly what Phase 8.2 implements:
|
||||||
|
|
||||||
|
- `VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS`: no formats yet
|
||||||
|
- `VIDIOC_G_FMT`: no format negotiated
|
||||||
|
- `Scaling`: no output format negotiated (no `g_fmt_vid_out_mplane`)
|
||||||
|
- `VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF`: no `vb2_queue`
|
||||||
|
|
||||||
|
These are all Phase 8.2 + 8.4 work and are intentionally absent
|
||||||
|
from Phase 8.1.
|
||||||
|
|
||||||
|
### Unload
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo rmmod daedalus_v4l2
|
||||||
|
```
|
||||||
|
|
||||||
|
Clean unload; no leak in dmesg.
|
||||||
|
|
||||||
|
## Coding-style note
|
||||||
|
|
||||||
|
Module written in kernel style (8-tab indent, `static`-by-default,
|
||||||
|
SPDX header, kerneldoc on `struct daedalus_dev`). Builds clean
|
||||||
|
with the kernel's `-Wall -Wextra` defaults. Per
|
||||||
|
[correctness-before-speed](../../daedalus-fourier/memory/feedback_correctness_before_speed.md)
|
||||||
|
session memory.
|
||||||
|
|
||||||
|
## What's next — Phase 8.2
|
||||||
|
|
||||||
|
Add a chardev bridge so a userspace daemon can talk to the
|
||||||
|
kernel module over `/dev/daedalus-v4l2`. Protocol stub:
|
||||||
|
|
||||||
|
```c
|
||||||
|
struct daedalus_req {
|
||||||
|
u32 type; /* DAEDALUS_REQ_DECODE, DAEDALUS_REQ_QUERY, ... */
|
||||||
|
u32 stream_id;
|
||||||
|
u32 frame_idx;
|
||||||
|
u32 bitstream_len;
|
||||||
|
/* followed by bitstream blob + control structs */
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
The kernel side becomes a thin marshal layer; all decoding work
|
||||||
|
moves to the daemon.
|
||||||
|
|
||||||
|
After 8.2 lands, Phase 8.3 adds the daemon's FFmpeg parse path
|
||||||
|
(dlopen at runtime, Option γ).
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
#
|
||||||
|
# daedalus-v4l2 — out-of-tree kbuild for the Linux kernel V4L2
|
||||||
|
# stateless decoder shim. Forwards bitstream + controls to the
|
||||||
|
# daedalus-v4l2 userspace daemon via a chardev bridge.
|
||||||
|
#
|
||||||
|
# Build against the running kernel:
|
||||||
|
# make
|
||||||
|
# Or against a specific kernel:
|
||||||
|
# make KERNELDIR=/path/to/kernel/source
|
||||||
|
# Install (requires root):
|
||||||
|
# sudo make install
|
||||||
|
# Clean:
|
||||||
|
# make clean
|
||||||
|
|
||||||
|
obj-m := daedalus_v4l2.o
|
||||||
|
daedalus_v4l2-objs := daedalus_v4l2_main.o
|
||||||
|
|
||||||
|
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
|
||||||
|
PWD := $(shell pwd)
|
||||||
|
|
||||||
|
# Be strict: warnings are errors, kernel coding style enforced.
|
||||||
|
ccflags-y := -Wall -Wextra -Wno-unused-parameter
|
||||||
|
|
||||||
|
all:
|
||||||
|
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
|
||||||
|
|
||||||
|
install: all
|
||||||
|
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
|
||||||
|
depmod -a
|
||||||
|
|
||||||
|
clean:
|
||||||
|
$(MAKE) -C $(KERNELDIR) M=$(PWD) clean
|
||||||
|
|
||||||
|
# Run kernel checkpatch.pl against the source. Picks up the
|
||||||
|
# kernel-tree-installed checkpatch via the running kernel's
|
||||||
|
# build directory if present.
|
||||||
|
checkpatch:
|
||||||
|
@if [ -x $(KERNELDIR)/scripts/checkpatch.pl ]; then \
|
||||||
|
$(KERNELDIR)/scripts/checkpatch.pl --no-tree --strict -f *.c; \
|
||||||
|
else \
|
||||||
|
echo "checkpatch.pl not available at $(KERNELDIR)/scripts/"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
.PHONY: all install clean checkpatch
|
||||||
@@ -0,0 +1,215 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* daedalus-v4l2 — V4L2 stateless decoder shim.
|
||||||
|
*
|
||||||
|
* Out-of-tree Linux kernel module that exposes a /dev/videoNN
|
||||||
|
* V4L2 device for the daedalus-fourier kernel library. Real
|
||||||
|
* decoding work happens in a userspace daemon (this module
|
||||||
|
* forwards bitstream + stateless-codec control structs via a
|
||||||
|
* chardev bridge — that part lands in Phase 8.2).
|
||||||
|
*
|
||||||
|
* Phase 8.1 (this commit): minimal viable skeleton. Registers a
|
||||||
|
* platform device + v4l2_device + video_device and answers
|
||||||
|
* VIDIOC_QUERYCAP with reasonable values. Other ioctls fall
|
||||||
|
* through to v4l2-core defaults; modprobe / rmmod is a clean
|
||||||
|
* round-trip.
|
||||||
|
*
|
||||||
|
* Project: https://git.reauktion.de/reauktion/daedalus-v4l2
|
||||||
|
* Sibling kernel library: https://git.reauktion.de/marfrit/daedalus-fourier
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
|
||||||
|
#include <media/v4l2-device.h>
|
||||||
|
#include <media/v4l2-dev.h>
|
||||||
|
#include <media/v4l2-ioctl.h>
|
||||||
|
|
||||||
|
#define DAEDALUS_DRV_NAME "daedalus_v4l2"
|
||||||
|
#define DAEDALUS_VIDEO_NAME "daedalus"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct daedalus_dev - top-level device state
|
||||||
|
* @pdev: owning platform device (synthesised in module_init)
|
||||||
|
* @v4l2_dev: V4L2 device parent for any video_device we register
|
||||||
|
* @vdev: video_device exposed as /dev/videoNN
|
||||||
|
*
|
||||||
|
* One-instance singleton for Phase 8.1. Multi-instance support
|
||||||
|
* (one decoder per /dev/videoNN) lands when m2m wiring goes in.
|
||||||
|
*/
|
||||||
|
struct daedalus_dev {
|
||||||
|
struct platform_device *pdev;
|
||||||
|
struct v4l2_device v4l2_dev;
|
||||||
|
struct video_device vdev;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* V4L2 ioctl dispatch table. Phase 8.1 only implements
|
||||||
|
* VIDIOC_QUERYCAP; everything else returns -ENOTTY via the
|
||||||
|
* v4l2-core's default handler when the op is NULL.
|
||||||
|
*/
|
||||||
|
static int daedalus_querycap(struct file *file, void *priv,
|
||||||
|
struct v4l2_capability *cap)
|
||||||
|
{
|
||||||
|
strscpy(cap->driver, DAEDALUS_DRV_NAME, sizeof(cap->driver));
|
||||||
|
/*
|
||||||
|
* cap->card is 32 bytes incl. NUL terminator. Pick a name
|
||||||
|
* that fits without truncation.
|
||||||
|
*/
|
||||||
|
strscpy(cap->card, "daedalus-fourier V3D7+NEON",
|
||||||
|
sizeof(cap->card));
|
||||||
|
snprintf(cap->bus_info, sizeof(cap->bus_info),
|
||||||
|
"platform:%s", DAEDALUS_DRV_NAME);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct v4l2_ioctl_ops daedalus_ioctl_ops = {
|
||||||
|
.vidioc_querycap = daedalus_querycap,
|
||||||
|
/*
|
||||||
|
* Phase 8.2+ adds:
|
||||||
|
* .vidioc_enum_fmt_vid_{cap,out}_mplane
|
||||||
|
* .vidioc_g/s_fmt_vid_{cap,out}_mplane
|
||||||
|
* .vidioc_reqbufs / vidioc_{q,dq,query}buf
|
||||||
|
* .vidioc_streamon / .vidioc_streamoff
|
||||||
|
* stateless-codec controls via the v4l2_ctrl_handler.
|
||||||
|
*/
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Phase 8.1 placeholder for .release. We DON'T yet have a
|
||||||
|
* vb2_queue (that's Phase 8.2), so the real vb2_fop_release
|
||||||
|
* isn't usable. Use the minimal v4l2_fh_release for now; the
|
||||||
|
* Phase 8.2 patch swaps this for vb2_fop_release.
|
||||||
|
*/
|
||||||
|
static int daedalus_release_phase81(struct file *file)
|
||||||
|
{
|
||||||
|
return v4l2_fh_release(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* File operations. v4l2_fh_open provides the default open the
|
||||||
|
* v4l2-core machinery expects; .release is our Phase 8.1
|
||||||
|
* placeholder; .unlocked_ioctl uses the kernel's video_ioctl2
|
||||||
|
* dispatcher against our v4l2_ioctl_ops table.
|
||||||
|
*/
|
||||||
|
static const struct v4l2_file_operations daedalus_fops = {
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.open = v4l2_fh_open,
|
||||||
|
.release = daedalus_release_phase81,
|
||||||
|
.unlocked_ioctl = video_ioctl2,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void daedalus_vdev_release(struct video_device *vdev)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* The video_device is embedded inside our daedalus_dev which
|
||||||
|
* lives as long as the platform_device. Nothing to free here
|
||||||
|
* directly; this no-op release just satisfies v4l2-core's
|
||||||
|
* requirement that .release be set.
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
static int daedalus_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct daedalus_dev *dev;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
|
||||||
|
if (!dev)
|
||||||
|
return -ENOMEM;
|
||||||
|
dev->pdev = pdev;
|
||||||
|
platform_set_drvdata(pdev, dev);
|
||||||
|
|
||||||
|
ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev);
|
||||||
|
if (ret) {
|
||||||
|
dev_err(&pdev->dev, "v4l2_device_register: %d\n", ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set up video_device. Embedded; vdev->release is no-op. */
|
||||||
|
strscpy(dev->vdev.name, DAEDALUS_VIDEO_NAME, sizeof(dev->vdev.name));
|
||||||
|
dev->vdev.fops = &daedalus_fops;
|
||||||
|
dev->vdev.ioctl_ops = &daedalus_ioctl_ops;
|
||||||
|
dev->vdev.release = daedalus_vdev_release;
|
||||||
|
dev->vdev.v4l2_dev = &dev->v4l2_dev;
|
||||||
|
dev->vdev.vfl_dir = VFL_DIR_M2M; /* mem2mem: bitstream in, frames out */
|
||||||
|
dev->vdev.device_caps = V4L2_CAP_VIDEO_M2M_MPLANE
|
||||||
|
| V4L2_CAP_STREAMING;
|
||||||
|
video_set_drvdata(&dev->vdev, dev);
|
||||||
|
|
||||||
|
ret = video_register_device(&dev->vdev, VFL_TYPE_VIDEO, -1);
|
||||||
|
if (ret) {
|
||||||
|
dev_err(&pdev->dev, "video_register_device: %d\n", ret);
|
||||||
|
v4l2_device_unregister(&dev->v4l2_dev);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
v4l2_info(&dev->v4l2_dev,
|
||||||
|
"daedalus-v4l2 registered as /dev/video%d (Phase 8.1 skeleton)\n",
|
||||||
|
dev->vdev.num);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void daedalus_remove(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct daedalus_dev *dev = platform_get_drvdata(pdev);
|
||||||
|
|
||||||
|
video_unregister_device(&dev->vdev);
|
||||||
|
v4l2_device_unregister(&dev->v4l2_dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct platform_driver daedalus_platform_driver = {
|
||||||
|
.probe = daedalus_probe,
|
||||||
|
.remove = daedalus_remove,
|
||||||
|
.driver = {
|
||||||
|
.name = DAEDALUS_DRV_NAME,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The platform device that our driver binds to. Synthesised
|
||||||
|
* at module load time since we have no device tree node yet
|
||||||
|
* (out-of-tree module; not vendored into the rpi DT).
|
||||||
|
*/
|
||||||
|
static struct platform_device *daedalus_platform_device;
|
||||||
|
|
||||||
|
static int __init daedalus_init(void)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
daedalus_platform_device = platform_device_alloc(DAEDALUS_DRV_NAME, -1);
|
||||||
|
if (!daedalus_platform_device)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
ret = platform_device_add(daedalus_platform_device);
|
||||||
|
if (ret) {
|
||||||
|
platform_device_put(daedalus_platform_device);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = platform_driver_register(&daedalus_platform_driver);
|
||||||
|
if (ret) {
|
||||||
|
platform_device_unregister(daedalus_platform_device);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __exit daedalus_exit(void)
|
||||||
|
{
|
||||||
|
platform_driver_unregister(&daedalus_platform_driver);
|
||||||
|
platform_device_unregister(daedalus_platform_device);
|
||||||
|
}
|
||||||
|
|
||||||
|
module_init(daedalus_init);
|
||||||
|
module_exit(daedalus_exit);
|
||||||
|
|
||||||
|
MODULE_AUTHOR("Markus Fritsche <fritsche.markus@gmail.com>");
|
||||||
|
MODULE_DESCRIPTION("V4L2 stateless decoder shim for daedalus-fourier (Pi 5 / VC7)");
|
||||||
|
MODULE_LICENSE("GPL v2");
|
||||||
|
MODULE_VERSION("0.0.1");
|
||||||
Reference in New Issue
Block a user