From a3ada8ba38862ffa5be81cbca1c8f28ebf3a0a1d Mon Sep 17 00:00:00 2001 From: Markus Fritsche Date: Wed, 20 May 2026 21:23:44 +0200 Subject: [PATCH] kernel: per-ctx vb2 lock so concurrent clients don't serialise on dev mutex MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit daedalus_queue_init was wiring both src_vq->lock and dst_vq->lock to ctx->dev->m2m_lock — a device-wide mutex. That serialises every vb2 ioctl (S_FMT, REQBUFS, QBUF, DQBUF, STREAMON, ...) across ALL concurrent clients of /dev/video0. For a single-client consumer like the test_m2m_* tools it doesn't matter; for Firefox, which spawns separate content + RDD + GPU processes that each open /dev/video0 and run libva probe simultaneously, the contention showed up as EBUSY from one libva session's S_FMT(OUTPUT_MPLANE) when another session was mid-streamon on the same device. Observable on higgs (Pi CM5): $ MOZ_VA_API_ENABLED=1 LIBVA_DRIVER_NAME=v4l2_request firefox ... v4l2-request: phase 8.10: opened daedalus_v4l2 at video_fd=32 ... v4l2-request: cap_pool_init: 24 slots ready v4l2-request: Unable to set format for type 10: Device or resource busy After this fix, each open() gets its own ctx->vb_mutex and the per-context vb2_queue locks are independent — Firefox's multi- process VAAPI clients no longer fight each other. YouTube playback on higgs runs through daedalus at ~230 fps sustained (640x368, libavcodec dlopen path), 7× headroom over the 30fps target. cedrus / rkvdec / hantro all use the per-ctx vb mutex pattern for the same reason. This mirrors them. Lifecycle: - mutex_init in daedalus_open (right after the kzalloc that creates ctx, before v4l2_fh_init). - mutex_destroy in daedalus_release (after v4l2_fh_exit, before kfree), and in the err_ctrl unwind path in daedalus_open. Verified end-to-end on higgs: - rmmod + modprobe the rebuilt .ko. - Restart daedalus-v4l2.service. - Firefox YouTube playback engages VAAPI, daemon journal shows cookie=1..N codec=3 (H.264) REQ_DECODE / decoder:OK pairs with unique per-frame fnv1a hashes. - No EBUSY in either firefox stderr or daemon journal during the entire session. Co-Authored-By: Claude Opus 4.7 --- kernel/daedalus_v4l2_main.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/kernel/daedalus_v4l2_main.c b/kernel/daedalus_v4l2_main.c index b493a4b..6eb021a 100644 --- a/kernel/daedalus_v4l2_main.c +++ b/kernel/daedalus_v4l2_main.c @@ -149,6 +149,17 @@ struct daedalus_ctx { struct v4l2_m2m_ctx *m2m_ctx; struct v4l2_ctrl_handler hdl; + /* + * Per-context vb2 queue lock. Was originally pointed at the + * device-wide dev->m2m_lock, which serialised vb2 ioctls across + * every concurrent client — Firefox spawns multiple content/RDD + * processes that each open /dev/video0, and a device-wide lock + * made S_FMT / REQBUFS / QBUF on one client block (and sometimes + * EBUSY-fail) against another client mid-stream. cedrus / rkvdec + * / hantro all use per-ctx vb mutexes for exactly this reason. + */ + struct mutex vb_mutex; + struct v4l2_pix_format_mplane src_fmt; struct v4l2_pix_format_mplane dst_fmt; }; @@ -523,7 +534,9 @@ static int daedalus_queue_init(void *priv, struct vb2_queue *src_vq, */ src_vq->mem_ops = &vb2_dma_contig_memops; src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; - src_vq->lock = &ctx->dev->m2m_lock; + /* Per-ctx lock so concurrent clients don't serialise on a + * device-wide mutex. See struct daedalus_ctx.vb_mutex comment. */ + src_vq->lock = &ctx->vb_mutex; src_vq->dev = &ctx->dev->pdev->dev; src_vq->allow_cache_hints = 1; @@ -538,7 +551,7 @@ static int daedalus_queue_init(void *priv, struct vb2_queue *src_vq, dst_vq->ops = &daedalus_qops; dst_vq->mem_ops = &vb2_dma_contig_memops; dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; - dst_vq->lock = &ctx->dev->m2m_lock; + dst_vq->lock = &ctx->vb_mutex; dst_vq->dev = &ctx->dev->pdev->dev; dst_vq->allow_cache_hints = 1; @@ -1112,6 +1125,7 @@ static int daedalus_open(struct file *file) if (!ctx) return -ENOMEM; ctx->dev = dev; + mutex_init(&ctx->vb_mutex); v4l2_fh_init(&ctx->fh, &dev->vdev); file->private_data = &ctx->fh; @@ -1159,6 +1173,7 @@ static int daedalus_open(struct file *file) err_ctrl: v4l2_ctrl_handler_free(&ctx->hdl); v4l2_fh_exit(&ctx->fh); + mutex_destroy(&ctx->vb_mutex); kfree(ctx); return ret; } @@ -1175,6 +1190,7 @@ static int daedalus_release(struct file *file) v4l2_m2m_ctx_release(ctx->m2m_ctx); v4l2_ctrl_handler_free(&ctx->hdl); v4l2_fh_exit(&ctx->fh); + mutex_destroy(&ctx->vb_mutex); kfree(ctx); return 0; }