Files
daedalus-v4l2/kernel/daedalus_v4l2_main.h
marfrit 5965805d86 Phase 8.7: media controller + multi-frame streaming verification
Two pieces — both shipped:

1. Media controller binding closes the last v4l2-compliance
   failure from 8.6 (DECODER_CMD, which requires has_media on
   stateless decoders) and unlocks the V4L2 request API for
   libva-v4l2-request.

2. Multi-frame streaming test exercises the daemon's
   AVCodecContext state preservation across many REQ_DECODE
   calls — Phase 8.6's tests pushed exactly one keyframe per
   invocation; real content has P-frame references.

Compliance now reaches **49/49 passing.**

Kernel (kernel/daedalus_v4l2_main.{c,h}):
- Added `struct media_device mdev` to daedalus_dev.
- media_device_init(&mdev) BEFORE v4l2_device_register so
  v4l2-core sees v4l2_dev.mdev = &mdev and binds the m2m
  entities into the graph during register.
- After video_register_device:
  v4l2_m2m_register_media_controller(..., MEDIA_ENT_F_PROC_VIDEO_DECODER)
  then media_device_register so userspace sees the complete
  graph in /dev/mediaN with the decoder entity tagged.
- daedalus_remove unwinds in reverse: unregister media,
  unregister mc, unregister video, release m2m, unregister
  v4l2, cleanup mdev.
- Error paths added for both new failure points.

Test harness (tools/test_m2m_stream.c, new):
- Multi-frame V4L2 m2m client: parses IVF → 4-deep buffer
  rings on both queues → per-frame QBUF/DQBUF loop →
  concatenates decoded NV12 to output file. Returns 0 only
  if every input frame decoded without error.
- Same codec vocabulary as test_m2m_decode (vp9 | av1 |
  h264 via 5th arg).

Verification on hertz (Pi 5, 6.12.75+rpt-rpi-2712):

v4l2-compliance: 49 tests, 49 passed, 0 failed, 0 warnings.

  $ v4l2-ctl --list-devices
  daedalus-fourier V3D7+NEON (platform:daedalus_v4l2):
        /dev/video0
        /dev/media3

VP9 320×240 30 frames (1 keyframe + 29 P-frames, 3.46 MB
NV12): byte-for-byte match vs `ffmpeg -i in.ivf -pix_fmt
nv12 -f rawvideo`.

VP9 1920×1080 10 frames (31 MB NV12 through the dmabuf
path): byte-for-byte match vs same reference command.

Daemon log shows cookies 1..30 all completing cleanly in
order; lazily-opened AVCodecContext maintains reference
frames across the chardev round-trips.

Clean SIGTERM + rmmod, no oops/WARN.

Roadmap update (docs/roadmap.md):
- 8.7 marked closed with closure-doc reference.
- 8.8 reshaped: perf profiling, QPU dispatch substitution
  via daedalus-fourier, multi-frame AV1/H.264, HDR (P010M).

Per correctness-before-speed:
- Order-correct media controller lifecycle (init → bind
  v4l2_dev → register video → register mc → register
  media; reverse for teardown).
- 4-deep buffer rings on both queues — the scheduler
  actually pipelines multiple in-flight cookies through
  the chardev (not just one-at-a-time as in 8.5/8.6 tests).
- Bit-exact comparison against ffmpeg, not "looks right."
- All resource paths cleaned on every error branch.

Phase 8.8 next: profile daemon hot loops, dlopen
daedalus-fourier from the daemon, swap FFmpeg per-block
calls for daedalus_dispatch_* where the kernel matches,
target 30fps@1080p from 30fps-floor-is-fine memory.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 16:21:58 +00:00

107 lines
3.8 KiB
C

/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* daedalus-v4l2 — kernel-internal device/state declarations.
*
* Shared between daedalus_v4l2_main.c (V4L2 m2m driver) and
* daedalus_v4l2_chardev.c (kernel↔daemon bridge). The chardev
* needs to look up in-flight V4L2 requests by cookie to complete
* the m2m job when RESP_FRAME arrives — that path lives in
* daedalus_complete_resp_frame().
*/
#ifndef DAEDALUS_V4L2_MAIN_H
#define DAEDALUS_V4L2_MAIN_H
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <media/v4l2-device.h>
#include <media/v4l2-dev.h>
#include <media/v4l2-mem2mem.h>
#include <media/media-device.h>
#include "daedalus_v4l2_proto.h"
/**
* struct daedalus_dev - top-level device state (singleton for now)
* @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
* @m2m_dev: mem2mem device shared by all per-open contexts
* @m2m_lock: serialises vb2 queue + v4l2 ioctl ops
* @inflight: list of struct daedalus_inflight (REQ_DECODE sent,
* RESP_FRAME not yet returned)
* @inflight_lock: protects @inflight
*
* Singleton per-module instance. Multi-instance support (one
* decoder per /dev/videoNN) would require breaking g_daedalus_dev
* out of daedalus_v4l2_main.c; not needed yet.
*/
struct daedalus_dev {
struct platform_device *pdev;
struct v4l2_device v4l2_dev;
struct video_device vdev;
struct v4l2_m2m_dev *m2m_dev;
struct media_device mdev;
struct mutex m2m_lock;
struct list_head inflight;
struct mutex inflight_lock;
};
/* Module-wide singleton accessor (chardev needs this for RESP_FRAME). */
struct daedalus_dev *daedalus_get_dev(void);
/**
* daedalus_next_cookie() - shared cookie allocator
*
* Returns the next monotonically increasing request cookie.
* Used by both the V4L2 m2m device_run path (for REQ_DECODE
* from real OUTPUT buffers) and the chardev debugfs
* test_decode path (for hand-crafted REQ_DECODE injection),
* so the two namespaces never collide and RESP_FRAME logs
* stay deterministic.
*/
u32 daedalus_next_cookie(void);
/**
* daedalus_complete_resp_frame() - chardev RESP_FRAME completion
* @cookie: cookie carried by the matching REQ_DECODE
* @fr: RESP_FRAME header from the daemon
* @pixels: inline pixel bytes following the header in the
* chardev payload (may be NULL if @pixels_len == 0)
* @pixels_len: number of inline pixel bytes
*
* Called from the chardev write() path on RESP_FRAME. Looks up
* the in-flight request, copies inline pixel data into the
* CAPTURE vb2 buffer if available (Phase 8.5 path; Phase 8.6
* skips the copy because the daemon decoded directly into the
* dmabuf), then completes both src+dst buffers and finishes
* the m2m job. Silently drops responses for unknown cookies
* (pr_warn_ratelimited).
*/
void daedalus_complete_resp_frame(u32 cookie,
const struct daedalus_resp_frame *fr,
const u8 *pixels, size_t pixels_len);
/**
* daedalus_export_capture_dmabuf() - chardev GET_DMABUF backend
* @cookie: cookie from the matching REQ_DECODE
* @plane: plane index (0-based) within the CAPTURE buffer
* @flags: flags for dma_buf_fd (O_CLOEXEC etc.)
* @out_fd: out: installed dmabuf fd in the calling task's
* fd table (only valid when return value == 0)
*
* Called from the chardev DAEDALUS_IOC_GET_DMABUF ioctl
* handler. Looks up the in-flight V4L2 request by cookie,
* exports the CAPTURE vb2 buffer's plane as a dma_buf via
* vb2_core_expbuf in the daemon's task context. Caller must
* NOT touch out_fd on non-zero return.
*
* Return: 0 on success, -EINVAL for unknown cookie or bad
* plane, propagated -errno from vb2_core_expbuf otherwise.
*/
int daedalus_export_capture_dmabuf(u32 cookie, u32 plane, u32 flags,
int *out_fd);
#endif /* DAEDALUS_V4L2_MAIN_H */