// 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 m2m (mem2mem) device for the daedalus-fourier kernel * library. Real decoding happens in a userspace daemon; this * module ferries bitstream buffers to the daemon via the * /dev/daedalus-v4l2 chardev bridge and ferries decoded pixels * back into the V4L2 client's CAPTURE buffer. * * Phase 8.5 (this revision): full V4L2 m2m driver with vb2 * queues, real v4l2_ioctl_ops table, device_run wired to REQ_DECODE * over the chardev, RESP_FRAME completion path back into * v4l2_m2m_buf_done. Bitstream + decoded pixel data travel * inline through the 64 KiB chardev payload — enough for small * frames and proof-of-pipe; Phase 8.6 adds dmabuf-export so * larger CAPTURE buffers don't have to round-trip through the * chardev. * * Phase 8.5 does NOT implement the V4L2 stateless control set * (V4L2_CID_STATELESS_VP9_FRAME etc.). The daemon parses VP9 * headers itself via dlopen'd FFmpeg, so per-buffer controls are * not needed for the proof-of-pipe. Phase 8.6 adds the proper * stateless controls when AV1/H.264 land. * * Project: https://git.reauktion.de/reauktion/daedalus-v4l2 * Sibling kernel library: https://git.reauktion.de/marfrit/daedalus-fourier */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "daedalus_v4l2_chardev.h" #include "daedalus_v4l2_proto.h" #include "daedalus_v4l2_main.h" #define DAEDALUS_DRV_NAME "daedalus_v4l2" #define DAEDALUS_VIDEO_NAME "daedalus" /* Coding-format coverage Phase 8.5: VP9 only. 8.6 adds AV1+H.264. */ #define DAEDALUS_OUTPUT_FOURCC V4L2_PIX_FMT_VP9_FRAME #define DAEDALUS_CAPTURE_FOURCC V4L2_PIX_FMT_NV12M /* planar Y + interleaved CbCr */ /* Conservative defaults; userspace S_FMT overrides. */ #define DAEDALUS_DEFAULT_W 320 #define DAEDALUS_DEFAULT_H 240 /* Bound bitstream buffer size to the chardev payload cap. */ #define DAEDALUS_MAX_BITSTREAM (DAEDALUS_PROTO_MAX_PAYLOAD - \ sizeof(struct daedalus_req_decode)) /* -- module-wide state ----------------------------------------------- */ static struct daedalus_dev *g_daedalus_dev; struct daedalus_dev *daedalus_get_dev(void) { return g_daedalus_dev; } /* -- per-open context ------------------------------------------------ */ /** * struct daedalus_ctx - per-open instance state * @fh: V4L2 file handle (must be first to satisfy v4l2-core) * @dev: parent daedalus_dev * @m2m_ctx: v4l2 mem2mem context (one job queue per open) * @hdl: v4l2_ctrl_handler (no controls yet; placeholder for 8.6) * @src_fmt: current OUTPUT (bitstream) format * @dst_fmt: current CAPTURE (decoded) format * * One context per open() of /dev/videoNN. v4l2-core's m2m * scheduler picks one context at a time to call device_run on. */ struct daedalus_ctx { struct v4l2_fh fh; struct daedalus_dev *dev; struct v4l2_m2m_ctx *m2m_ctx; struct v4l2_ctrl_handler hdl; struct v4l2_pix_format_mplane src_fmt; struct v4l2_pix_format_mplane dst_fmt; }; static inline struct daedalus_ctx *file_to_ctx(struct file *file) { return container_of(file->private_data, struct daedalus_ctx, fh); } /* -- format helpers -------------------------------------------------- */ /* NV12M = 2 planes: plane 0 = Y (W*H), plane 1 = interleaved CbCr (W*H/2). */ static void daedalus_fill_capture_fmt(struct v4l2_pix_format_mplane *f, u32 w, u32 h) { f->width = w; f->height = h; f->pixelformat = DAEDALUS_CAPTURE_FOURCC; f->field = V4L2_FIELD_NONE; f->colorspace = V4L2_COLORSPACE_REC709; f->num_planes = 2; f->plane_fmt[0].bytesperline = w; f->plane_fmt[0].sizeimage = w * h; f->plane_fmt[1].bytesperline = w; f->plane_fmt[1].sizeimage = w * h / 2; } /* * OUTPUT is a parsed VP9 access unit. V4L2 convention for * compressed bitstream formats: single plane, sizeimage = * worst-case bitstream size we're willing to accept. */ static void daedalus_fill_output_fmt(struct v4l2_pix_format_mplane *f, u32 w, u32 h) { f->width = w; f->height = h; f->pixelformat = DAEDALUS_OUTPUT_FOURCC; f->field = V4L2_FIELD_NONE; f->colorspace = V4L2_COLORSPACE_REC709; f->num_planes = 1; f->plane_fmt[0].bytesperline = 0; /* compressed */ f->plane_fmt[0].sizeimage = DAEDALUS_MAX_BITSTREAM; } /* -- vb2 queue ops --------------------------------------------------- */ static int daedalus_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers, unsigned int *nplanes, unsigned int sizes[], struct device *alloc_devs[]) { struct daedalus_ctx *ctx = vb2_get_drv_priv(vq); const struct v4l2_pix_format_mplane *fmt; unsigned int p; fmt = V4L2_TYPE_IS_OUTPUT(vq->type) ? &ctx->src_fmt : &ctx->dst_fmt; if (*nplanes) { if (*nplanes != fmt->num_planes) return -EINVAL; for (p = 0; p < *nplanes; p++) if (sizes[p] < fmt->plane_fmt[p].sizeimage) return -EINVAL; return 0; } *nplanes = fmt->num_planes; for (p = 0; p < *nplanes; p++) sizes[p] = fmt->plane_fmt[p].sizeimage; if (*nbuffers < 2) *nbuffers = 2; return 0; } static int daedalus_buf_prepare(struct vb2_buffer *vb) { struct daedalus_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); const struct v4l2_pix_format_mplane *fmt; unsigned int p; fmt = V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type) ? &ctx->src_fmt : &ctx->dst_fmt; for (p = 0; p < vb->num_planes; p++) { unsigned long need = fmt->plane_fmt[p].sizeimage; if (vb2_plane_size(vb, p) < need) { v4l2_err(&ctx->dev->v4l2_dev, "buf_prepare: plane %u size %lu < %lu\n", p, vb2_plane_size(vb, p), need); return -EINVAL; } /* * For OUTPUT (bitstream), payload is set by userspace * via VIDIOC_QBUF (bytesused). For CAPTURE we set the * full plane size; device_run / buf_done updates it on * completion if needed. */ if (!V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type)) vb2_set_plane_payload(vb, p, need); } return 0; } static void daedalus_buf_queue(struct vb2_buffer *vb) { struct daedalus_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); v4l2_m2m_buf_queue(ctx->m2m_ctx, vbuf); } static int daedalus_start_streaming(struct vb2_queue *vq, unsigned int count) { return 0; } static void daedalus_stop_streaming(struct vb2_queue *vq) { struct daedalus_ctx *ctx = vb2_get_drv_priv(vq); struct vb2_v4l2_buffer *vbuf; while ((vbuf = V4L2_TYPE_IS_OUTPUT(vq->type) ? v4l2_m2m_src_buf_remove(ctx->m2m_ctx) : v4l2_m2m_dst_buf_remove(ctx->m2m_ctx)) != NULL) v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR); } static const struct vb2_ops daedalus_qops = { .queue_setup = daedalus_queue_setup, .buf_prepare = daedalus_buf_prepare, .buf_queue = daedalus_buf_queue, .start_streaming = daedalus_start_streaming, .stop_streaming = daedalus_stop_streaming, .wait_prepare = vb2_ops_wait_prepare, .wait_finish = vb2_ops_wait_finish, }; /* -- m2m queue init -------------------------------------------------- */ static int daedalus_queue_init(void *priv, struct vb2_queue *src_vq, struct vb2_queue *dst_vq) { struct daedalus_ctx *ctx = priv; int ret; src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; src_vq->io_modes = VB2_MMAP | VB2_USERPTR; src_vq->drv_priv = ctx; src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); src_vq->ops = &daedalus_qops; src_vq->mem_ops = &vb2_vmalloc_memops; src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; src_vq->lock = &ctx->dev->m2m_lock; ret = vb2_queue_init(src_vq); if (ret) return ret; dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; dst_vq->io_modes = VB2_MMAP; dst_vq->drv_priv = ctx; dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); dst_vq->ops = &daedalus_qops; dst_vq->mem_ops = &vb2_vmalloc_memops; dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; dst_vq->lock = &ctx->dev->m2m_lock; return vb2_queue_init(dst_vq); } /* -- in-flight tracking (cookie → ctx + bufs) ------------------------ */ /* * The chardev RESP_FRAME path needs to find the per-request * context + source/destination buffer pair so it can complete the * V4L2 m2m job. Track in-flight requests in a small list keyed * by cookie. Cookies are monotonically increasing (see * device_run); collisions on wrap-around are astronomically * unlikely in normal use and would self-clear once the older * cookie's response arrives. */ struct daedalus_inflight { struct list_head list; u32 cookie; struct daedalus_ctx *ctx; struct vb2_v4l2_buffer *src_buf; struct vb2_v4l2_buffer *dst_buf; }; static struct daedalus_inflight * daedalus_inflight_pop_locked(struct daedalus_dev *dev, u32 cookie) { struct daedalus_inflight *e; list_for_each_entry(e, &dev->inflight, list) { if (e->cookie == cookie) { list_del(&e->list); return e; } } return NULL; } /* -- v4l2_m2m_ops.device_run ----------------------------------------- */ static atomic_t daedalus_cookie_seq = ATOMIC_INIT(0); static void daedalus_device_run(void *priv) { struct daedalus_ctx *ctx = priv; struct daedalus_dev *dev = ctx->dev; struct vb2_v4l2_buffer *src_buf, *dst_buf; struct daedalus_inflight *inf = NULL; struct daedalus_req_decode *req = NULL; void *bitstream; size_t blen, payload_len; u32 cookie; int ret; src_buf = v4l2_m2m_next_src_buf(ctx->m2m_ctx); dst_buf = v4l2_m2m_next_dst_buf(ctx->m2m_ctx); if (!src_buf || !dst_buf) { v4l2_warn(&dev->v4l2_dev, "device_run with no src/dst buf — scheduler bug?\n"); goto fail_job_finish; } blen = vb2_get_plane_payload(&src_buf->vb2_buf, 0); if (!blen || blen > DAEDALUS_MAX_BITSTREAM) { v4l2_err(&dev->v4l2_dev, "device_run: bitstream length %zu out of range [1, %lu]\n", blen, (unsigned long) DAEDALUS_MAX_BITSTREAM); goto fail_buf_error; } bitstream = vb2_plane_vaddr(&src_buf->vb2_buf, 0); if (!bitstream) { v4l2_err(&dev->v4l2_dev, "device_run: vaddr NULL\n"); goto fail_buf_error; } payload_len = sizeof(*req) + blen; req = kmalloc(payload_len, GFP_KERNEL); if (!req) goto fail_buf_error; req->codec_id = DAEDALUS_CODEC_VP9; req->bitstream_len = (u32) blen; req->flags = 0; memcpy((u8 *) req + sizeof(*req), bitstream, blen); inf = kzalloc(sizeof(*inf), GFP_KERNEL); if (!inf) goto fail_buf_error; cookie = (u32) atomic_inc_return(&daedalus_cookie_seq); inf->cookie = cookie; inf->ctx = ctx; inf->src_buf = src_buf; inf->dst_buf = dst_buf; mutex_lock(&dev->inflight_lock); list_add_tail(&inf->list, &dev->inflight); mutex_unlock(&dev->inflight_lock); ret = daedalus_chardev_enqueue_req(DAEDALUS_MSG_REQ_DECODE, cookie, req, payload_len); kfree(req); req = NULL; if (ret) { v4l2_err(&dev->v4l2_dev, "device_run: enqueue_req failed: %d\n", ret); mutex_lock(&dev->inflight_lock); list_del(&inf->list); mutex_unlock(&dev->inflight_lock); kfree(inf); goto fail_buf_error; } v4l2_dbg(1, 0, &dev->v4l2_dev, "device_run: REQ_DECODE cookie=%u blen=%zu\n", cookie, blen); /* * Job stays open until RESP_FRAME comes back; chardev path * calls v4l2_m2m_buf_done_and_job_finish then. */ return; fail_buf_error: if (src_buf) { v4l2_m2m_src_buf_remove(ctx->m2m_ctx); v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_ERROR); } if (dst_buf) { v4l2_m2m_dst_buf_remove(ctx->m2m_ctx); v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_ERROR); } kfree(req); fail_job_finish: v4l2_m2m_job_finish(dev->m2m_dev, ctx->m2m_ctx); } static const struct v4l2_m2m_ops daedalus_m2m_ops = { .device_run = daedalus_device_run, }; /* -- chardev RESP_FRAME → buf_done bridge ---------------------------- */ void daedalus_complete_resp_frame(u32 cookie, const struct daedalus_resp_frame *fr, const u8 *pixels, size_t pixels_len) { struct daedalus_dev *dev = g_daedalus_dev; struct daedalus_inflight *inf; enum vb2_buffer_state state; void *dst_y, *dst_uv; u32 y_size, uv_size; if (!dev) return; mutex_lock(&dev->inflight_lock); inf = daedalus_inflight_pop_locked(dev, cookie); mutex_unlock(&dev->inflight_lock); if (!inf) { pr_warn_ratelimited( "daedalus_v4l2: RESP_FRAME for unknown cookie=%u\n", cookie); return; } state = (fr->status == DAEDALUS_DECODE_OK) ? VB2_BUF_STATE_DONE : VB2_BUF_STATE_ERROR; /* * Copy inline pixel data into the CAPTURE buffer if the * daemon supplied any. Phase 8.5: bytes-after-header in the * RESP_FRAME payload carry the Y plane followed by the * interleaved CbCr plane (NV12M layout), truncated to fit * the 64 KiB chardev payload cap. Phase 8.6 swaps this for * dmabuf-export so big frames don't get truncated. */ if (state == VB2_BUF_STATE_DONE && pixels_len) { struct vb2_buffer *vb = &inf->dst_buf->vb2_buf; dst_y = vb2_plane_vaddr(vb, 0); dst_uv = vb2_plane_vaddr(vb, 1); y_size = min_t(u32, fr->luma_len, (u32) vb2_plane_size(vb, 0)); uv_size = min_t(u32, fr->chroma_len, (u32) vb2_plane_size(vb, 1)); if (dst_y && y_size && pixels_len >= y_size) { memcpy(dst_y, pixels, y_size); vb2_set_plane_payload(vb, 0, y_size); } else { vb2_set_plane_payload(vb, 0, 0); } if (dst_uv && uv_size && pixels_len >= y_size + uv_size) { memcpy(dst_uv, pixels + y_size, uv_size); vb2_set_plane_payload(vb, 1, uv_size); } else { vb2_set_plane_payload(vb, 1, 0); } } /* * Use the buf_done_and_job_finish helper rather than plain * buf_done + job_finish: the helper pops the buffers off * the m2m queue before marking them done, otherwise the * scheduler immediately re-runs device_run on the same * still-queued src buffer. Caught during Phase 8.5 first * run — second REQ_DECODE with identical bitstream + oops * in stop_streaming when the test client tore down. */ v4l2_m2m_buf_done_and_job_finish(dev->m2m_dev, inf->ctx->m2m_ctx, state); kfree(inf); } /* -- v4l2_ioctl_ops -------------------------------------------------- */ static int daedalus_querycap(struct file *file, void *priv, struct v4l2_capability *cap) { strscpy(cap->driver, DAEDALUS_DRV_NAME, sizeof(cap->driver)); 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 int daedalus_enum_fmt(struct file *file, void *priv, struct v4l2_fmtdesc *f) { if (f->index != 0) return -EINVAL; f->pixelformat = (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) ? DAEDALUS_OUTPUT_FOURCC : DAEDALUS_CAPTURE_FOURCC; if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) f->flags |= V4L2_FMT_FLAG_COMPRESSED; return 0; } static int daedalus_g_fmt(struct file *file, void *priv, struct v4l2_format *f) { struct daedalus_ctx *ctx = file_to_ctx(file); if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) f->fmt.pix_mp = ctx->src_fmt; else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) f->fmt.pix_mp = ctx->dst_fmt; else return -EINVAL; return 0; } static int daedalus_try_fmt(struct file *file, void *priv, struct v4l2_format *f) { struct v4l2_pix_format_mplane *p = &f->fmt.pix_mp; u32 w = clamp_t(u32, p->width, 16, 1920); u32 h = clamp_t(u32, p->height, 16, 1088); if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { daedalus_fill_output_fmt(p, w, h); } else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { daedalus_fill_capture_fmt(p, w, h); } else { return -EINVAL; } return 0; } static int daedalus_s_fmt(struct file *file, void *priv, struct v4l2_format *f) { struct daedalus_ctx *ctx = file_to_ctx(file); struct vb2_queue *vq; int ret; vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type); if (!vq) return -EINVAL; if (vb2_is_busy(vq)) return -EBUSY; ret = daedalus_try_fmt(file, priv, f); if (ret) return ret; if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) ctx->src_fmt = f->fmt.pix_mp; else ctx->dst_fmt = f->fmt.pix_mp; return 0; } static const struct v4l2_ioctl_ops daedalus_ioctl_ops = { .vidioc_querycap = daedalus_querycap, .vidioc_enum_fmt_vid_out = daedalus_enum_fmt, .vidioc_enum_fmt_vid_cap = daedalus_enum_fmt, .vidioc_g_fmt_vid_out_mplane = daedalus_g_fmt, .vidioc_g_fmt_vid_cap_mplane = daedalus_g_fmt, .vidioc_s_fmt_vid_out_mplane = daedalus_s_fmt, .vidioc_s_fmt_vid_cap_mplane = daedalus_s_fmt, .vidioc_try_fmt_vid_out_mplane = daedalus_try_fmt, .vidioc_try_fmt_vid_cap_mplane = daedalus_try_fmt, .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, .vidioc_streamon = v4l2_m2m_ioctl_streamon, .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, .vidioc_unsubscribe_event = v4l2_event_unsubscribe, }; /* -- file operations ------------------------------------------------- */ static int daedalus_open(struct file *file) { struct daedalus_dev *dev = video_drvdata(file); struct daedalus_ctx *ctx; int ret; ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); if (!ctx) return -ENOMEM; ctx->dev = dev; v4l2_fh_init(&ctx->fh, &dev->vdev); file->private_data = &ctx->fh; v4l2_ctrl_handler_init(&ctx->hdl, 0); ctx->fh.ctrl_handler = &ctx->hdl; daedalus_fill_output_fmt(&ctx->src_fmt, DAEDALUS_DEFAULT_W, DAEDALUS_DEFAULT_H); daedalus_fill_capture_fmt(&ctx->dst_fmt, DAEDALUS_DEFAULT_W, DAEDALUS_DEFAULT_H); ctx->m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev, ctx, daedalus_queue_init); if (IS_ERR(ctx->m2m_ctx)) { ret = PTR_ERR(ctx->m2m_ctx); v4l2_err(&dev->v4l2_dev, "m2m_ctx_init: %d\n", ret); goto err_ctrl; } ctx->fh.m2m_ctx = ctx->m2m_ctx; v4l2_fh_add(&ctx->fh); return 0; err_ctrl: v4l2_ctrl_handler_free(&ctx->hdl); v4l2_fh_exit(&ctx->fh); kfree(ctx); return ret; } static int daedalus_release(struct file *file) { struct daedalus_ctx *ctx = file_to_ctx(file); v4l2_fh_del(&ctx->fh); v4l2_m2m_ctx_release(ctx->m2m_ctx); v4l2_ctrl_handler_free(&ctx->hdl); v4l2_fh_exit(&ctx->fh); kfree(ctx); return 0; } static const struct v4l2_file_operations daedalus_fops = { .owner = THIS_MODULE, .open = daedalus_open, .release = daedalus_release, .poll = v4l2_m2m_fop_poll, .unlocked_ioctl = video_ioctl2, .mmap = v4l2_m2m_fop_mmap, }; static void daedalus_vdev_release(struct video_device *vdev) { /* embedded in daedalus_dev (devm) — nothing to free here */ } /* -- platform driver bind -------------------------------------------- */ 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); mutex_init(&dev->m2m_lock); mutex_init(&dev->inflight_lock); INIT_LIST_HEAD(&dev->inflight); ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev); if (ret) { dev_err(&pdev->dev, "v4l2_device_register: %d\n", ret); return ret; } dev->m2m_dev = v4l2_m2m_init(&daedalus_m2m_ops); if (IS_ERR(dev->m2m_dev)) { ret = PTR_ERR(dev->m2m_dev); v4l2_err(&dev->v4l2_dev, "v4l2_m2m_init: %d\n", ret); goto err_v4l2_dev; } 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; dev->vdev.device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING; dev->vdev.lock = &dev->m2m_lock; video_set_drvdata(&dev->vdev, dev); ret = video_register_device(&dev->vdev, VFL_TYPE_VIDEO, -1); if (ret) { v4l2_err(&dev->v4l2_dev, "video_register_device: %d\n", ret); goto err_m2m; } g_daedalus_dev = dev; v4l2_info(&dev->v4l2_dev, "daedalus-v4l2 m2m registered as /dev/video%d (Phase 8.5)\n", dev->vdev.num); return 0; err_m2m: v4l2_m2m_release(dev->m2m_dev); err_v4l2_dev: v4l2_device_unregister(&dev->v4l2_dev); return ret; } static void daedalus_remove(struct platform_device *pdev) { struct daedalus_dev *dev = platform_get_drvdata(pdev); g_daedalus_dev = NULL; video_unregister_device(&dev->vdev); v4l2_m2m_release(dev->m2m_dev); v4l2_device_unregister(&dev->v4l2_dev); } static struct platform_driver daedalus_platform_driver = { .probe = daedalus_probe, .remove = daedalus_remove, .driver = { .name = DAEDALUS_DRV_NAME, }, }; static struct platform_device *daedalus_platform_device; static int __init daedalus_init(void) { int ret; ret = daedalus_chardev_init(); if (ret) return ret; daedalus_platform_device = platform_device_alloc(DAEDALUS_DRV_NAME, -1); if (!daedalus_platform_device) { ret = -ENOMEM; goto err_chardev; } ret = platform_device_add(daedalus_platform_device); if (ret) { platform_device_put(daedalus_platform_device); goto err_chardev; } ret = platform_driver_register(&daedalus_platform_driver); if (ret) { platform_device_unregister(daedalus_platform_device); goto err_chardev; } return 0; err_chardev: daedalus_chardev_exit(); return ret; } static void __exit daedalus_exit(void) { platform_driver_unregister(&daedalus_platform_driver); platform_device_unregister(daedalus_platform_device); daedalus_chardev_exit(); } module_init(daedalus_init); module_exit(daedalus_exit); MODULE_AUTHOR("Markus Fritsche "); MODULE_DESCRIPTION("V4L2 stateless decoder shim for daedalus-fourier (Pi 5 / VC7)"); MODULE_LICENSE("GPL v2"); MODULE_VERSION("0.0.2");