/* SPDX-License-Identifier: GPL-2.0-or-later WITH Linux-syscall-note */ /* * daedalus-v4l2 — kernel ↔ daemon wire protocol. * * Shared header used by both the kernel module * (drivers/daedalus_v4l2_chardev.c) and the userspace daemon * (daemon/src/main.c). ABI: pre-1.0 — no stability guarantees * until DAEDALUS_PROTO_VERSION reaches 1. * * Transport: a single-instance chardev at /dev/daedalus-v4l2. * The userspace daemon opens the chardev O_RDWR, then drives a * blocking read() / write() loop: * * write(): submit a response to a prior request (RESP_*). * read(): block until the next request from the kernel * (REQ_*) is available. * * Each message is a `struct daedalus_msg_hdr` followed by an * optional variable-length payload of `hdr.payload_len` bytes. * * Phase 8.2 (chardev bridge): PING / PONG. * Phase 8.4 (decode end-to-end): REQ_DECODE / RESP_FRAME. */ #ifndef DAEDALUS_V4L2_PROTO_H #define DAEDALUS_V4L2_PROTO_H #include #include #define DAEDALUS_PROTO_MAGIC 0x44303456u /* 'D04V' */ #define DAEDALUS_PROTO_VERSION 1u /* pre-1.0; bumped for * REQ_DECODE.src_pts + * RESP_FRAME.flags + * RESP_FRAME.output_src_pts * (H.264 B-frame reorder fix, * daedalus-v4l2#6). */ /* * Wire-protocol message types. * * Request types (kernel → daemon) live in 0x0000_0000..0x7fff_ffff. * Response types (daemon → kernel) live in 0x8000_0000..0xffff_ffff. * The high bit is what distinguishes "kernel produced this" from * "daemon produced this" on the wire. * * These are #defines rather than an enum because the high-bit * values (>= 0x80000000) exceed INT_MAX, and pre-C23 enums can't * portably hold them — kernel uABI headers follow the same * convention. */ #define DAEDALUS_MSG_PING 0x00000001u #define DAEDALUS_MSG_REQ_DECODE 0x00000002u #define DAEDALUS_MSG_HELLO 0x80000001u #define DAEDALUS_MSG_PONG 0x80000002u #define DAEDALUS_MSG_RESP_FRAME 0x80000003u /** * struct daedalus_msg_hdr - on-the-wire message header * @magic: must be DAEDALUS_PROTO_MAGIC; rejects gibberish * @version: protocol version (DAEDALUS_PROTO_VERSION) * @type: one of enum daedalus_msg_type * @cookie: caller-supplied identifier; copied verbatim into * the matching response so the kernel can pair * response with request * @payload_len: number of bytes immediately following this * struct (max DAEDALUS_PROTO_MAX_PAYLOAD) * @reserved: must be zero for future use */ struct daedalus_msg_hdr { __u32 magic; __u32 version; __u32 type; __u32 cookie; __u32 payload_len; __u32 reserved; }; #define DAEDALUS_PROTO_MAX_PAYLOAD (64u * 1024u) /* 64 KiB */ /* -- REQ_DECODE / RESP_FRAME payload structures ---------------------- */ /** * enum daedalus_codec_id - codec selector for REQ_DECODE * @DAEDALUS_CODEC_VP9: libavcodec AV_CODEC_ID_VP9 * @DAEDALUS_CODEC_AV1: libavcodec AV_CODEC_ID_AV1 (Phase 8.6) * @DAEDALUS_CODEC_H264: libavcodec AV_CODEC_ID_H264 (Phase 8.6) * * Wire-stable across phases. The daemon maps these to the * libavcodec AV_CODEC_ID_* values internally so we don't leak * FFmpeg's enum into the kernel ABI. */ enum daedalus_codec_id { DAEDALUS_CODEC_VP9 = 1, DAEDALUS_CODEC_AV1 = 2, DAEDALUS_CODEC_H264 = 3, }; /** * DAEDALUS_REQ_FLAG_H264_META - daedalus_req_decode.flags bit * * Set when a struct daedalus_h264_meta is present between the * daedalus_req_decode prefix and the slice bitstream. Required for * H.264 (codec_id == DAEDALUS_CODEC_H264) since libavcodec needs * SPS/PPS that the V4L2 stateless API delivers as separate ctrls, * not in the OUTPUT buffer. Other codecs ignore this bit. */ #define DAEDALUS_REQ_FLAG_H264_META 0x00000001u /** * struct daedalus_req_decode - REQ_DECODE payload prefix * @codec_id: enum daedalus_codec_id * @bitstream_len: bytes of bitstream following this struct * (after any optional metadata blocks — see flags) * @capture_width: CAPTURE buffer width in pixels * @capture_height: CAPTURE buffer height in pixels * @capture_pix_fmt: V4L2 fourcc of the CAPTURE format * (e.g. V4L2_PIX_FMT_NV12M) * @capture_num_planes: number of dmabuf planes the daemon should * fetch via DAEDALUS_IOC_GET_DMABUF (1..3) * @capture_plane_size: per-plane sizeimage from V4L2 S_FMT * (plane[0..N-1]). Unused entries = 0. * @capture_plane_stride: per-plane bytesperline from V4L2 S_FMT. * @flags: bitmask of DAEDALUS_REQ_FLAG_* * * Wire layout for a REQ_DECODE payload: * * struct daedalus_req_decode req; * IF (req.flags & DAEDALUS_REQ_FLAG_H264_META): * struct daedalus_h264_meta meta; * u8 bitstream[req.bitstream_len]; * * Total payload_len = sizeof(req) + (meta ? sizeof(meta) : 0) * + req.bitstream_len. * * The daemon uses (capture_*) to fetch + mmap the right CAPTURE * plane via DAEDALUS_IOC_GET_DMABUF, then decodes pixels * directly into the dmabuf. */ struct daedalus_req_decode { __u32 codec_id; __u32 bitstream_len; __u32 capture_width; __u32 capture_height; __u32 capture_pix_fmt; __u32 capture_num_planes; __u32 capture_plane_size[3]; __u32 capture_plane_stride[3]; __u32 flags; __u32 reserved0; /* explicit pad to 8-byte align src_pts */ /* * The V4L2 OUTPUT (bitstream) buffer's vb2 timestamp at submission * time. The daemon sets pkt->pts = src_pts before * avcodec_send_packet so libavcodec's display-ordered * receive_frame can return frame->pts == src_pts of the bitstream * the frame's slices belong to. Decouples kernel cookie (decode * order, in-kernel identity) from display order — required for * H.264 B-frame correctness (daedalus-v4l2#6). */ __u64 src_pts; }; /** * struct daedalus_h264_meta - H.264 stateless-decode metadata * * Optional block following the daedalus_req_decode prefix when * DAEDALUS_REQ_FLAG_H264_META is set in req.flags. Carries the * structured controls the kernel collected from * V4L2_CID_STATELESS_H264_* — the daemon converts them into * AnnexB SPS+PPS NAL units (via an Exp-Golomb writer) and * prepends those NAL units to the slice bitstream before * handing it to libavcodec. * * The kernel never inspects these fields beyond capturing them * verbatim from the v4l2_ctrl_handler at device_run time; the * field semantics are governed entirely by the linux uABI * V4L2 stateless H.264 control definitions. * * Wire-stable across phases. If the kernel V4L2 H.264 control * structs grow new fields the protocol version bumps with them. */ struct daedalus_h264_meta { struct v4l2_ctrl_h264_sps sps; struct v4l2_ctrl_h264_pps pps; struct v4l2_ctrl_h264_scaling_matrix scaling_matrix; struct v4l2_ctrl_h264_decode_params decode_params; }; /** * enum daedalus_decode_status - RESP_FRAME outcome codes * @DAEDALUS_DECODE_OK: frame produced; fields below populated * @DAEDALUS_DECODE_NO_FRAME: codec consumed input but no frame * ready yet (e.g. lacks reference) * @DAEDALUS_DECODE_ERR_OPEN: avcodec_open2 failed * @DAEDALUS_DECODE_ERR_SEND: avcodec_send_packet failed * @DAEDALUS_DECODE_ERR_RECV: avcodec_receive_frame failed * @DAEDALUS_DECODE_ERR_CODEC: unknown codec_id */ enum daedalus_decode_status { DAEDALUS_DECODE_OK = 0, DAEDALUS_DECODE_NO_FRAME = 1, DAEDALUS_DECODE_ERR_OPEN = 100, DAEDALUS_DECODE_ERR_SEND = 101, DAEDALUS_DECODE_ERR_RECV = 102, DAEDALUS_DECODE_ERR_CODEC = 103, }; /** * struct daedalus_resp_frame - RESP_FRAME payload * @status: enum daedalus_decode_status * @codec_id: echoes the request's codec_id * @width: decoded frame width in pixels (0 if !OK) * @height: decoded frame height in pixels (0 if !OK) * @pix_fmt: libavcodec AVPixelFormat as int (informational) * @luma_len: Y-plane byte count actually hashed * @chroma_len: U+V byte count actually hashed (planar combined) * @fnv1a_yuv: FNV-1a 32-bit hash of Y,U,V planes concatenated * (line-by-line, stripping any libav alignment * stride padding). Lets the kernel side compare * against an offline reference without shipping * full pixel data through the chardev. * @reserved: must be zero * * Fixed size — keeps wire parsing simple. No variable-length * pixel data in Phase 8.4; dmabuf in Phase 8.5 carries that. */ /** * DAEDALUS_RESP_FLAG_HAS_PIXELS - this RESP delivers a decoded frame's * pixels. The owning CAPTURE buffer is identified by output_src_pts * (matched against an in-flight item's src_pts on the kernel side), * NOT by the chardev message header's cookie. Required since * libavcodec's H.264 decoder reorders to display order — the cookie * the daemon just received the REQ on may not be the cookie whose * bitstream produced the frame just popped from receive_frame. * * DAEDALUS_RESP_FLAG_SRC_CONSUMED - the chardev header's cookie's * OUTPUT bitstream buffer is done from the daemon's perspective * (libavcodec has accepted the slice data via avcodec_send_packet). * Kernel releases src_buf for the cookie and runs job_finish so the * m2m scheduler can dispatch the next REQ. Independent of any * pixel delivery — the dst_buf paired with this cookie may still * be parked, awaiting a future RESP with HAS_PIXELS + matching * output_src_pts. * * Both flags may be set in a single message (steady-state path with * no codec reorder lag — the just-sent packet immediately yielded a * frame whose pts == this REQ's src_pts). */ #define DAEDALUS_RESP_FLAG_HAS_PIXELS 0x00000001u #define DAEDALUS_RESP_FLAG_SRC_CONSUMED 0x00000002u struct daedalus_resp_frame { __u32 status; __u32 codec_id; __u32 width; __u32 height; __s32 pix_fmt; __u32 luma_len; __u32 chroma_len; __u32 fnv1a_yuv; __u32 flags; /* bitmask of DAEDALUS_RESP_FLAG_* */ __u32 reserved0; /* explicit pad to 8-byte align output_src_pts */ /* * Set when DAEDALUS_RESP_FLAG_HAS_PIXELS is in flags. Identifies * which OUTPUT bitstream's slices produced the pixels in this * RESP — kernel completes the CAPTURE buffer whose inflight item * has src_pts == output_src_pts. Ignored when HAS_PIXELS is * clear. */ __u64 output_src_pts; }; /* -- chardev ioctl ABI ----------------------------------------------- */ /** * struct daedalus_get_dmabuf - DAEDALUS_IOC_GET_DMABUF args * @cookie: cookie from the matching REQ_DECODE (in) * @plane: plane index, 0-based (in) * @flags: O_CLOEXEC etc.; passed through to dma_buf_fd (in) * @fd: exported dmabuf fd, installed in the calling * (daemon) task's fd table (out) * * The daemon calls this ioctl from REQ_DECODE handling to obtain * a per-plane dmabuf fd for the CAPTURE buffer the kernel * scheduled. The kernel resolves cookie → in-flight V4L2 * request → CAPTURE vb2 buffer, then calls vb2_core_expbuf in * the daemon's task context (so the fd lands in the daemon's * fd table). Daemon mmaps the fd, writes decoded pixels in * place, munmaps and close()s — then sends RESP_FRAME. */ struct daedalus_get_dmabuf { __u32 cookie; __u32 plane; __u32 flags; __s32 fd; }; #define DAEDALUS_IOC_MAGIC 'D' #define DAEDALUS_IOC_GET_DMABUF \ _IOWR(DAEDALUS_IOC_MAGIC, 1, struct daedalus_get_dmabuf) #endif /* DAEDALUS_V4L2_PROTO_H */