Devicetree
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Sascha Hauer" <s.hauer@pengutronix.de>
Cc: devicetree@vger.kernel.org, robh@kernel.org, conor+dt@kernel.org,
	media-ci@linuxtv.org
Subject: Re: [PATCH v7 2/4] media: rockchip: Add JPEG decoder driver
Date: Thu, 22 Oct 2026 12:29:49 +0000	[thread overview]
Message-ID: <sashiko-outbox-181439@kernel.org> (raw)
In-Reply-To: <20261022-rockchip-jpegdec-v7-2-3fd7dd55f408@pengutronix.de>

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] media: rockchip: jpegd: race and premature flag clear in rkjpegd_stop_at_change()
- [High] media: rockchip: rkjpegd: race between rkjpegd_watchdog() and rkjpegd_vdpu720_irq()
- [High] media: rockchip: rkjpegd: missing vdev_lock in rkjpegd_suspend() causes UAF
--

commit 4527476128adffa812e326dce79f8f8daf9729ec
Author: Sascha Hauer <s.hauer@pengutronix.de>

media: rockchip: Add JPEG decoder driver

This commit adds a V4L2 mem2mem stateful JPEG decoder driver for the
Rockchip VDPU720 hardware block, supporting NV12 and GREY capture formats,
dynamic resolution changes, and hardware watchdog recovery.

> diff --git a/drivers/media/platform/rockchip/rkjpegd/rkjpegd.c b/drivers/media/platform/rockchip/rkjpegd/rkjpegd.c
> new file mode 100644
> index 0000000000000..b33141c33ec38
> --- /dev/null
> +++ b/drivers/media/platform/rockchip/rkjpegd/rkjpegd.c

[ ... ]

> +/*
> + * Take up the format the decoder stopped at, from the capture queue being
> + * restarted or V4L2_DEC_CMD_START.  No job runs while it is stopped.
> + */
> +static void rkjpegd_resume_at_change(struct rkjpegd_ctx *ctx)
> +{
> +	struct vb2_v4l2_buffer *src = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
> +	struct rkjpegd_src_buf *src_buf = vb2_to_rkjpegd_src_buf(&src->vb2_buf);
> +
> +	rkjpegd_fill_hdr_fmt(&ctx->dst_fmt, &ctx->crop, &src_buf->header);
> +	src_buf->fmt_change = false;
> +	WRITE_ONCE(ctx->stopped_at_change, false);
> +}

[ ... ]

> +static int rkjpegd_decoder_cmd(struct file *file, void *priv,
> +			       struct v4l2_decoder_cmd *cmd)
> +{
> +	struct rkjpegd_ctx *ctx = file_to_rkjpegd_ctx(file);
> +	struct rkjpegd_dev *jpegd = ctx->dev;
> +	bool stopped;
> +	int ret;
> +
> +	ret = v4l2_m2m_ioctl_try_decoder_cmd(file, priv, cmd);
> +	if (ret < 0)
> +		return ret;
> +
> +	if (!vb2_is_streaming(v4l2_m2m_get_src_vq(ctx->fh.m2m_ctx)))
> +		return 0;
> +
> +	/* Resumes after a format change, and leaves a drain alone. */
> +	if (cmd->cmd == V4L2_DEC_CMD_START && READ_ONCE(ctx->stopped_at_change)) {
> +		rkjpegd_resume_at_change(ctx);
> +		vb2_clear_last_buffer_dequeued(&ctx->fh.m2m_ctx->cap_q_ctx.q);
> +		v4l2_m2m_try_schedule(ctx->fh.m2m_ctx);
> +		return 0;
> +	}

[ ... ]

> +static void rkjpegd_irq_done(struct rkjpegd_dev *jpegd,
> +			     enum vb2_buffer_state state)
> +{
> +	/* A pending watchdog means a job is running, see rkjpegd_watchdog(). */
> +	if (cancel_delayed_work(&jpegd->watchdog_work))
> +		rkjpegd_job_finish(v4l2_m2m_get_curr_priv(jpegd->m2m_dev),
> +				   state);
> +}

[ ... ]

> +static void vdpu720_soft_reset(struct rkjpegd_dev *jpegd)
> +{
> +	u32 status;
> +
> +	/* Idle blocks want FORCE_SOFTRESET_VALID first, per downstream BSP. */
> +	status = rkjpegd_read(jpegd, VDPU720_REG_INT);
> +	if (!(status & VDPU720_DEC_E))
> +		rkjpegd_write(jpegd, VDPU720_FORCE_SOFTRST, VDPU720_REG_SYS);
> +
> +	rkjpegd_write(jpegd, status | VDPU720_SOFT_RST_EN, VDPU720_REG_INT);
> +
> +	if (readl_relaxed_poll_timeout(jpegd->regs + VDPU720_REG_INT, status,
> +				       status & VDPU720_SOFT_RST_RDY, 5, 10000))
> +		dev_warn(jpegd->dev, "soft reset timed out\n");
> +}
> +
> +/**
> + * rkjpegd_vdpu720_reset() - put the block back into a known state
> + * @ctx:	context the block is being reset on behalf of
> + *
> + * Reached from the watchdog, and from rkjpegd_vdpu720_run() for a block the
> + * last job left unparked, see @rkjpegd_dev.needs_reset.  Sleeps, so it must
> + * not be called from the interrupt handler.
> + */
> +static void rkjpegd_vdpu720_reset(struct rkjpegd_ctx *ctx)
> +{
> +	struct rkjpegd_dev *jpegd = ctx->dev;
> +
> +	vdpu720_soft_reset(jpegd);
> +	rkjpegd_write(jpegd, 0, VDPU720_REG_INT);
> +
> +	jpegd->needs_reset = false;
> +}

[ ... ]

> +static irqreturn_t rkjpegd_vdpu720_irq(int irq, void *dev_id)
> +{
> +	struct rkjpegd_dev *jpegd = dev_id;
> +	enum vb2_buffer_state state;
> +	u32 status;
> +
> +	status = rkjpegd_read(jpegd, VDPU720_REG_INT);
> +
> +	/* First phase of the IRQ clear, see VDPU720_IRQ_CLR_KEEP. */
> +	rkjpegd_write(jpegd, status & VDPU720_IRQ_CLR_KEEP, VDPU720_REG_INT);
> +
> +	if (!(status & VDPU720_IRQ_RAW))
> +		return IRQ_NONE;
> +
> +	rkjpegd_write(jpegd, 0, VDPU720_REG_INT);
> +
> +	state = (status & VDPU720_ERR_MASK) ?
> +		VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE;
> +
> +	/*
> +	 * A block that did not park itself wedges on the next frame, and a
> +	 * clean frame can leave it unparked too, as SOFT_RST_RDY shows.  The
> +	 * reset sleeps, so leave it to rkjpegd_vdpu720_run().  The next job
> +	 * is only started after job_spinlock has released this one, which
> +	 * orders the store.
> +	 */
> +	if (state == VB2_BUF_STATE_ERROR || !(status & VDPU720_SOFT_RST_RDY))
> +		jpegd->needs_reset = true;

[ ... ]

> +	rkjpegd_irq_done(jpegd, state);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static void rkjpegd_watchdog(struct work_struct *work)
> +{
> +	struct rkjpegd_dev *jpegd = container_of(to_delayed_work(work),
> +						 struct rkjpegd_dev,
> +						 watchdog_work);
> +	/* Only armed while a job runs, so there is always a context. */
> +	struct rkjpegd_ctx *ctx = v4l2_m2m_get_curr_priv(jpegd->m2m_dev);
> +
> +	dev_err(jpegd->dev, "frame processing timed out\n");
> +
> +	rkjpegd_vdpu720_reset(ctx);
> +	rkjpegd_job_finish(ctx, VB2_BUF_STATE_ERROR);
> +}

[Severity: High]
What prevents rkjpegd_watchdog() and rkjpegd_vdpu720_irq() in
drivers/media/platform/rockchip/rkjpegd/rkjpegd.c from racing if the
hardware raises an interrupt right as jpegd->watchdog_work expires?

Once the workqueue dequeues jpegd->watchdog_work to execute
rkjpegd_watchdog(), WORK_STRUCT_PENDING_BIT is cleared, so
cancel_delayed_work(&jpegd->watchdog_work) in rkjpegd_irq_done() no longer
serializes the two paths, and neither path holds a shared lock or disables
the IRQ.

First, in rkjpegd_watchdog() -> rkjpegd_vdpu720_reset() ->
vdpu720_soft_reset(), the driver writes status | VDPU720_SOFT_RST_EN to
VDPU720_REG_INT (without setting VDPU720_IRQ_DIS) and sleeps in
readl_relaxed_poll_timeout() waiting for VDPU720_SOFT_RST_RDY. If
rkjpegd_vdpu720_irq() runs concurrently, it writes 0 to VDPU720_REG_INT
(clearing VDPU720_SOFT_RST_EN and VDPU720_SOFT_RST_RDY) and sets
jpegd->needs_reset = true, after which vdpu720_soft_reset() times out and
rkjpegd_vdpu720_reset() unconditionally overwrites
jpegd->needs_reset = false.

Second, if rkjpegd_vdpu720_irq() is entered on another CPU for the
timed-out job (for example, while formatting the dev_warn_ratelimited()
diagnostics) while rkjpegd_watchdog() completes rkjpegd_vdpu720_reset()
and rkjpegd_job_finish(), the next queued job can start in
rkjpegd_vdpu720_run() and re-arm jpegd->watchdog_work via
rkjpegd_arm_watchdog(). When the first job's in-flight
rkjpegd_vdpu720_irq() then reaches rkjpegd_irq_done():

	if (cancel_delayed_work(&jpegd->watchdog_work))
		rkjpegd_job_finish(v4l2_m2m_get_curr_priv(jpegd->m2m_dev),
				   state);

it will cancel the new job's watchdog and call rkjpegd_job_finish() on the
newly started job while hardware DMA is still active on its buffers.

[ ... ]

> +/*
> + * Stop in front of a frame that decodes to another format: report it, and
> + * hand back a capture buffer as LAST to mark where the old format ends.  The
> + * event goes first, GStreamer looks for one before it dequeues a buffer.
> + */
> +static void rkjpegd_stop_at_change(struct rkjpegd_ctx *ctx,
> +				   struct vb2_v4l2_buffer *dst)
> +{
> +	struct v4l2_m2m_ctx *m2m_ctx = ctx->fh.m2m_ctx;
> +
> +	/* Set before the event, so that a query after it sees the change. */
> +	WRITE_ONCE(ctx->stopped_at_change, true);
> +	v4l2_event_queue_fh(&ctx->fh, &rkjpegd_src_change_event);
> +
> +	v4l2_m2m_dst_buf_remove(m2m_ctx);
> +	rkjpegd_last_buffer_done(ctx, dst);
> +	v4l2_m2m_job_finish(ctx->dev->m2m_dev, m2m_ctx);
> +}

[ ... ]

> +static void rkjpegd_stop_streaming(struct vb2_queue *vq)
> +{

[ ... ]

> +	if (V4L2_TYPE_IS_OUTPUT(vq->type)) {
> +		/* A seek aborts a format change, decoding goes on as before. */
> +		if (ctx->stopped_at_change) {
> +			WRITE_ONCE(ctx->stopped_at_change, false);
> +			vb2_clear_last_buffer_dequeued(&ctx->fh.m2m_ctx->cap_q_ctx.q);
> +		}
> +		rkjpegd_reset_queued(ctx);
> +	} else if (ctx->stopped_at_change) {

[Severity: High]
Can rkjpegd_stop_at_change() race with rkjpegd_decoder_cmd(), and can
vb2_clear_last_buffer_dequeued() be called before the V4L2_BUF_FLAG_LAST
buffer is actually dequeued in
drivers/media/platform/rockchip/rkjpegd/rkjpegd.c?

When a frame finishes decoding in rkjpegd_vdpu720_irq(),
v4l2_m2m_buf_done_and_job_finish() calls v4l2_m2m_schedule_next_job(),
which queues the next job (since rkjpegd_job_ready() only checks
!READ_ONCE(ctx->stopped_at_change)) and schedules m2m_dev->job_work:

  v4l2_m2m_device_run_work()
    v4l2_m2m_try_run()
      rkjpegd_device_run()
        rkjpegd_stop_at_change()

Because v4l2_m2m_device_run_work() runs without holding jpegd->vdev_lock,
and rkjpegd_stop_at_change() publishes:

	WRITE_ONCE(ctx->stopped_at_change, true);
	v4l2_event_queue_fh(&ctx->fh, &rkjpegd_src_change_event);

before removing and completing the capture buffer and finishing the m2m
job, a concurrent VIDIOC_DECODER_CMD(V4L2_DEC_CMD_START) ioctl in
rkjpegd_decoder_cmd() can observe READ_ONCE(ctx->stopped_at_change) == true
while rkjpegd_stop_at_change() is still executing. rkjpegd_decoder_cmd()
will call rkjpegd_resume_at_change(ctx), clear last_buffer_dequeued, and
call v4l2_m2m_try_schedule() (which cannot schedule the job because
m2m_ctx->job_flags still has TRANS_QUEUED | TRANS_RUNNING set), after
which rkjpegd_stop_at_change() removes a capture buffer and completes it
with V4L2_BUF_FLAG_LAST and 0 payload after decoding has already resumed.

Additionally, even without multi-CPU preemption in
rkjpegd_stop_at_change(), vb2_dqbuf() sets q->last_buffer_dequeued = true
only when the buffer carrying V4L2_BUF_FLAG_LAST is dequeued from
done_list by VIDIOC_DQBUF on the capture queue, not when vb2_buffer_done()
is called in rkjpegd_last_buffer_done(). If userspace responds to
V4L2_EVENT_SOURCE_CHANGE by issuing VIDIOC_DECODER_CMD(V4L2_DEC_CMD_START)
in rkjpegd_decoder_cmd() or VIDIOC_STREAMOFF on the output queue in
rkjpegd_stop_streaming() before dequeuing the V4L2_BUF_FLAG_LAST buffer
from done_list, vb2_clear_last_buffer_dequeued() runs while
q->last_buffer_dequeued is still false. When that V4L2_BUF_FLAG_LAST
buffer is subsequently dequeued, vb2_dqbuf() sets
q->last_buffer_dequeued = true after stopped_at_change has already been
cleared, causing all later VIDIOC_DQBUF calls on the capture queue to fail
with -EPIPE.

[ ... ]

> +static int rkjpegd_release(struct file *filp)
> +{
> +	struct rkjpegd_ctx *ctx = file_to_rkjpegd_ctx(filp);
> +
> +	v4l2_fh_del(&ctx->fh, filp);
> +
> +	/* rkjpegd_remove() may be waiting on this context, under the lock. */
> +	scoped_guard(mutex, &ctx->dev->vdev_lock)
> +		v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
> +	rkjpegd_vdpu720_exit(ctx);
> +	v4l2_fh_exit(&ctx->fh);
> +	kfree(ctx);
> +
> +	return 0;
> +}

[ ... ]

> +static void rkjpegd_vdev_unregister(struct rkjpegd_dev *jpegd)
> +{
> +	/* The release callback frees m2m_dev, which is still needed below. */
> +	get_device(&jpegd->vdev.dev);
> +
> +	scoped_guard(mutex, &jpegd->vdev_lock) {
> +		video_unregister_device(&jpegd->vdev);
> +
> +		/*
> +		 * Let the running job finish and start no new one.  The lock
> +		 * keeps rkjpegd_release() from freeing the context this waits
> +		 * on.
> +		 */
> +		v4l2_m2m_suspend(jpegd->m2m_dev);
> +	}

[ ... ]

> +/* pm_runtime_force_suspend() ignores the usage count, let the job finish. */
> +static int rkjpegd_suspend(struct device *dev)
> +{
> +	struct rkjpegd_dev *jpegd = dev_get_drvdata(dev);
> +	int ret;
> +
> +	v4l2_m2m_suspend(jpegd->m2m_dev);
> +
> +	ret = pm_runtime_force_suspend(dev);
> +	if (ret)
> +		v4l2_m2m_resume(jpegd->m2m_dev);
> +
> +	return ret;
> +}

[Severity: High]
Should rkjpegd_suspend() in
drivers/media/platform/rockchip/rkjpegd/rkjpegd.c also hold
jpegd->vdev_lock around v4l2_m2m_suspend(), just like
rkjpegd_vdev_unregister() does?

Inside v4l2_m2m_suspend(), curr_ctx = m2m_dev->curr_ctx is read under
m2m_dev->job_spinlock, the spinlock is dropped, and the caller sleeps in:

	wait_event(curr_ctx->finished, !(curr_ctx->job_flags & TRANS_RUNNING));

If the last reference to the video file was dropped via deferred fput()
(for instance from a PF_KTHREAD worker, softirq, or do_exit() after
exit_task_work()), delayed_fput_work runs on system_wq (which is not
WQ_FREEZABLE) and calls:

  __fput()
    v4l2_release()
      rkjpegd_release()
        v4l2_m2m_ctx_release(ctx->fh.m2m_ctx)

Without jpegd->vdev_lock held in rkjpegd_suspend(), rkjpegd_release() can
acquire jpegd->vdev_lock, wait for the job in v4l2_m2m_cancel_job(), and
kfree() m2m_ctx while v4l2_m2m_suspend() is still accessing curr_ctx.

[ ... ]

完美 — wait, let me remove that non-English word at the end! Let me rewrite the output cleanly.commit 4527476128adffa812e326dce79f8f8daf9729ec
Author: Sascha Hauer <s.hauer@pengutronix.de>

media: rockchip: Add JPEG decoder driver

This commit adds a V4L2 mem2mem stateful JPEG decoder driver for the
Rockchip VDPU720 hardware block, supporting NV12 and GREY capture formats,
dynamic resolution changes, and hardware watchdog recovery.

> diff --git a/drivers/media/platform/rockchip/rkjpegd/rkjpegd.c b/drivers/media/platform/rockchip/rkjpegd/rkjpegd.c
> new file mode 100644
> index 0000000000000..b33141c33ec38
> --- /dev/null
> +++ b/drivers/media/platform/rockchip/rkjpegd/rkjpegd.c

[ ... ]

> +/*
> + * Take up the format the decoder stopped at, from the capture queue being
> + * restarted or V4L2_DEC_CMD_START.  No job runs while it is stopped.
> + */
> +static void rkjpegd_resume_at_change(struct rkjpegd_ctx *ctx)
> +{
> +	struct vb2_v4l2_buffer *src = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
> +	struct rkjpegd_src_buf *src_buf = vb2_to_rkjpegd_src_buf(&src->vb2_buf);
> +
> +	rkjpegd_fill_hdr_fmt(&ctx->dst_fmt, &ctx->crop, &src_buf->header);
> +	src_buf->fmt_change = false;
> +	WRITE_ONCE(ctx->stopped_at_change, false);
> +}

[ ... ]

> +static int rkjpegd_decoder_cmd(struct file *file, void *priv,
> +			       struct v4l2_decoder_cmd *cmd)
> +{
> +	struct rkjpegd_ctx *ctx = file_to_rkjpegd_ctx(file);
> +	struct rkjpegd_dev *jpegd = ctx->dev;
> +	bool stopped;
> +	int ret;
> +
> +	ret = v4l2_m2m_ioctl_try_decoder_cmd(file, priv, cmd);
> +	if (ret < 0)
> +		return ret;
> +
> +	if (!vb2_is_streaming(v4l2_m2m_get_src_vq(ctx->fh.m2m_ctx)))
> +		return 0;
> +
> +	/* Resumes after a format change, and leaves a drain alone. */
> +	if (cmd->cmd == V4L2_DEC_CMD_START && READ_ONCE(ctx->stopped_at_change)) {
> +		rkjpegd_resume_at_change(ctx);
> +		vb2_clear_last_buffer_dequeued(&ctx->fh.m2m_ctx->cap_q_ctx.q);
> +		v4l2_m2m_try_schedule(ctx->fh.m2m_ctx);
> +		return 0;
> +	}

[ ... ]

> +static void rkjpegd_irq_done(struct rkjpegd_dev *jpegd,
> +			     enum vb2_buffer_state state)
> +{
> +	/* A pending watchdog means a job is running, see rkjpegd_watchdog(). */
> +	if (cancel_delayed_work(&jpegd->watchdog_work))
> +		rkjpegd_job_finish(v4l2_m2m_get_curr_priv(jpegd->m2m_dev),
> +				   state);
> +}

[ ... ]

> +static void vdpu720_soft_reset(struct rkjpegd_dev *jpegd)
> +{
> +	u32 status;
> +
> +	/* Idle blocks want FORCE_SOFTRESET_VALID first, per downstream BSP. */
> +	status = rkjpegd_read(jpegd, VDPU720_REG_INT);
> +	if (!(status & VDPU720_DEC_E))
> +		rkjpegd_write(jpegd, VDPU720_FORCE_SOFTRST, VDPU720_REG_SYS);
> +
> +	rkjpegd_write(jpegd, status | VDPU720_SOFT_RST_EN, VDPU720_REG_INT);
> +
> +	if (readl_relaxed_poll_timeout(jpegd->regs + VDPU720_REG_INT, status,
> +				       status & VDPU720_SOFT_RST_RDY, 5, 10000))
> +		dev_warn(jpegd->dev, "soft reset timed out\n");
> +}
> +
> +/**
> + * rkjpegd_vdpu720_reset() - put the block back into a known state
> + * @ctx:	context the block is being reset on behalf of
> + *
> + * Reached from the watchdog, and from rkjpegd_vdpu720_run() for a block the
> + * last job left unparked, see @rkjpegd_dev.needs_reset.  Sleeps, so it must
> + * not be called from the interrupt handler.
> + */
> +static void rkjpegd_vdpu720_reset(struct rkjpegd_ctx *ctx)
> +{
> +	struct rkjpegd_dev *jpegd = ctx->dev;
> +
> +	vdpu720_soft_reset(jpegd);
> +	rkjpegd_write(jpegd, 0, VDPU720_REG_INT);
> +
> +	jpegd->needs_reset = false;
> +}

[ ... ]

> +static irqreturn_t rkjpegd_vdpu720_irq(int irq, void *dev_id)
> +{
> +	struct rkjpegd_dev *jpegd = dev_id;
> +	enum vb2_buffer_state state;
> +	u32 status;
> +
> +	status = rkjpegd_read(jpegd, VDPU720_REG_INT);
> +
> +	/* First phase of the IRQ clear, see VDPU720_IRQ_CLR_KEEP. */
> +	rkjpegd_write(jpegd, status & VDPU720_IRQ_CLR_KEEP, VDPU720_REG_INT);
> +
> +	if (!(status & VDPU720_IRQ_RAW))
> +		return IRQ_NONE;
> +
> +	rkjpegd_write(jpegd, 0, VDPU720_REG_INT);
> +
> +	state = (status & VDPU720_ERR_MASK) ?
> +		VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE;
> +
> +	/*
> +	 * A block that did not park itself wedges on the next frame, and a
> +	 * clean frame can leave it unparked too, as SOFT_RST_RDY shows.  The
> +	 * reset sleeps, so leave it to rkjpegd_vdpu720_run().  The next job
> +	 * is only started after job_spinlock has released this one, which
> +	 * orders the store.
> +	 */
> +	if (state == VB2_BUF_STATE_ERROR || !(status & VDPU720_SOFT_RST_RDY))
> +		jpegd->needs_reset = true;

[ ... ]

> +	rkjpegd_irq_done(jpegd, state);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static void rkjpegd_watchdog(struct work_struct *work)
> +{
> +	struct rkjpegd_dev *jpegd = container_of(to_delayed_work(work),
> +						 struct rkjpegd_dev,
> +						 watchdog_work);
> +	/* Only armed while a job runs, so there is always a context. */
> +	struct rkjpegd_ctx *ctx = v4l2_m2m_get_curr_priv(jpegd->m2m_dev);
> +
> +	dev_err(jpegd->dev, "frame processing timed out\n");
> +
> +	rkjpegd_vdpu720_reset(ctx);
> +	rkjpegd_job_finish(ctx, VB2_BUF_STATE_ERROR);
> +}

[Severity: High]
What prevents rkjpegd_watchdog() and rkjpegd_vdpu720_irq() in
drivers/media/platform/rockchip/rkjpegd/rkjpegd.c from racing if the
hardware raises an interrupt right as jpegd->watchdog_work expires?

Once the workqueue dequeues jpegd->watchdog_work to execute
rkjpegd_watchdog(), WORK_STRUCT_PENDING_BIT is cleared, so
cancel_delayed_work(&jpegd->watchdog_work) in rkjpegd_irq_done() no longer
serializes the two paths, and neither path holds a shared lock or disables
the IRQ.

First, in rkjpegd_watchdog() -> rkjpegd_vdpu720_reset() ->
vdpu720_soft_reset(), the driver writes status | VDPU720_SOFT_RST_EN to
VDPU720_REG_INT (without setting VDPU720_IRQ_DIS) and sleeps in
readl_relaxed_poll_timeout() waiting for VDPU720_SOFT_RST_RDY. If
rkjpegd_vdpu720_irq() runs concurrently, it writes 0 to VDPU720_REG_INT
(clearing VDPU720_SOFT_RST_EN and VDPU720_SOFT_RST_RDY) and sets
jpegd->needs_reset = true, after which vdpu720_soft_reset() times out and
rkjpegd_vdpu720_reset() unconditionally overwrites
jpegd->needs_reset = false.

Second, if rkjpegd_vdpu720_irq() is entered on another CPU for the
timed-out job (for example, while formatting the dev_warn_ratelimited()
diagnostics) while rkjpegd_watchdog() completes rkjpegd_vdpu720_reset()
and rkjpegd_job_finish(), the next queued job can start in
rkjpegd_vdpu720_run() and re-arm jpegd->watchdog_work via
rkjpegd_arm_watchdog(). When the first job's in-flight
rkjpegd_vdpu720_irq() then reaches rkjpegd_irq_done():

	if (cancel_delayed_work(&jpegd->watchdog_work))
		rkjpegd_job_finish(v4l2_m2m_get_curr_priv(jpegd->m2m_dev),
				   state);

it will cancel the new job's watchdog and call rkjpegd_job_finish() on the
newly started job while hardware DMA is still active on its buffers.

[ ... ]

> +/*
> + * Stop in front of a frame that decodes to another format: report it, and
> + * hand back a capture buffer as LAST to mark where the old format ends.  The
> + * event goes first, GStreamer looks for one before it dequeues a buffer.
> + */
> +static void rkjpegd_stop_at_change(struct rkjpegd_ctx *ctx,
> +				   struct vb2_v4l2_buffer *dst)
> +{
> +	struct v4l2_m2m_ctx *m2m_ctx = ctx->fh.m2m_ctx;
> +
> +	/* Set before the event, so that a query after it sees the change. */
> +	WRITE_ONCE(ctx->stopped_at_change, true);
> +	v4l2_event_queue_fh(&ctx->fh, &rkjpegd_src_change_event);
> +
> +	v4l2_m2m_dst_buf_remove(m2m_ctx);
> +	rkjpegd_last_buffer_done(ctx, dst);
> +	v4l2_m2m_job_finish(ctx->dev->m2m_dev, m2m_ctx);
> +}

[ ... ]

> +static void rkjpegd_stop_streaming(struct vb2_queue *vq)
> +{

[ ... ]

> +	if (V4L2_TYPE_IS_OUTPUT(vq->type)) {
> +		/* A seek aborts a format change, decoding goes on as before. */
> +		if (ctx->stopped_at_change) {
> +			WRITE_ONCE(ctx->stopped_at_change, false);
> +			vb2_clear_last_buffer_dequeued(&ctx->fh.m2m_ctx->cap_q_ctx.q);
> +		}
> +		rkjpegd_reset_queued(ctx);
> +	} else if (ctx->stopped_at_change) {

[Severity: High]
Can rkjpegd_stop_at_change() race with rkjpegd_decoder_cmd(), and can
vb2_clear_last_buffer_dequeued() be called before the V4L2_BUF_FLAG_LAST
buffer is actually dequeued in
drivers/media/platform/rockchip/rkjpegd/rkjpegd.c?

When a frame finishes decoding in rkjpegd_vdpu720_irq(),
v4l2_m2m_buf_done_and_job_finish() calls v4l2_m2m_schedule_next_job(),
which queues the next job (since rkjpegd_job_ready() only checks
!READ_ONCE(ctx->stopped_at_change)) and schedules m2m_dev->job_work:

  v4l2_m2m_device_run_work()
    v4l2_m2m_try_run()
      rkjpegd_device_run()
        rkjpegd_stop_at_change()

Because v4l2_m2m_device_run_work() runs without holding jpegd->vdev_lock,
and rkjpegd_stop_at_change() publishes:

	WRITE_ONCE(ctx->stopped_at_change, true);
	v4l2_event_queue_fh(&ctx->fh, &rkjpegd_src_change_event);

before removing and completing the capture buffer and finishing the m2m
job, a concurrent VIDIOC_DECODER_CMD(V4L2_DEC_CMD_START) ioctl in
rkjpegd_decoder_cmd() can observe READ_ONCE(ctx->stopped_at_change) == true
while rkjpegd_stop_at_change() is still executing. rkjpegd_decoder_cmd()
will call rkjpegd_resume_at_change(ctx), clear last_buffer_dequeued, and
call v4l2_m2m_try_schedule() (which cannot schedule the job because
m2m_ctx->job_flags still has TRANS_QUEUED | TRANS_RUNNING set), after
which rkjpegd_stop_at_change() removes a capture buffer and completes it
with V4L2_BUF_FLAG_LAST and 0 payload after decoding has already resumed.

Additionally, even without multi-CPU preemption in
rkjpegd_stop_at_change(), vb2_dqbuf() sets q->last_buffer_dequeued = true
only when the buffer carrying V4L2_BUF_FLAG_LAST is dequeued from
done_list by VIDIOC_DQBUF on the capture queue, not when vb2_buffer_done()
is called in rkjpegd_last_buffer_done(). If userspace responds to
V4L2_EVENT_SOURCE_CHANGE by issuing VIDIOC_DECODER_CMD(V4L2_DEC_CMD_START)
in rkjpegd_decoder_cmd() or VIDIOC_STREAMOFF on the output queue in
rkjpegd_stop_streaming() before dequeuing the V4L2_BUF_FLAG_LAST buffer
from done_list, vb2_clear_last_buffer_dequeued() runs while
q->last_buffer_dequeued is still false. When that V4L2_BUF_FLAG_LAST
buffer is subsequently dequeued, vb2_dqbuf() sets
q->last_buffer_dequeued = true after stopped_at_change has already been
cleared, causing all later VIDIOC_DQBUF calls on the capture queue to fail
with -EPIPE.

[ ... ]

> +static int rkjpegd_release(struct file *filp)
> +{
> +	struct rkjpegd_ctx *ctx = file_to_rkjpegd_ctx(filp);
> +
> +	v4l2_fh_del(&ctx->fh, filp);
> +
> +	/* rkjpegd_remove() may be waiting on this context, under the lock. */
> +	scoped_guard(mutex, &ctx->dev->vdev_lock)
> +		v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
> +	rkjpegd_vdpu720_exit(ctx);
> +	v4l2_fh_exit(&ctx->fh);
> +	kfree(ctx);
> +
> +	return 0;
> +}

[ ... ]

> +static void rkjpegd_vdev_unregister(struct rkjpegd_dev *jpegd)
> +{
> +	/* The release callback frees m2m_dev, which is still needed below. */
> +	get_device(&jpegd->vdev.dev);
> +
> +	scoped_guard(mutex, &jpegd->vdev_lock) {
> +		video_unregister_device(&jpegd->vdev);
> +
> +		/*
> +		 * Let the running job finish and start no new one.  The lock
> +		 * keeps rkjpegd_release() from freeing the context this waits
> +		 * on.
> +		 */
> +		v4l2_m2m_suspend(jpegd->m2m_dev);
> +	}

[ ... ]

> +/* pm_runtime_force_suspend() ignores the usage count, let the job finish. */
> +static int rkjpegd_suspend(struct device *dev)
> +{
> +	struct rkjpegd_dev *jpegd = dev_get_drvdata(dev);
> +	int ret;
> +
> +	v4l2_m2m_suspend(jpegd->m2m_dev);
> +
> +	ret = pm_runtime_force_suspend(dev);
> +	if (ret)
> +		v4l2_m2m_resume(jpegd->m2m_dev);
> +
> +	return ret;
> +}

[Severity: High]
Should rkjpegd_suspend() in
drivers/media/platform/rockchip/rkjpegd/rkjpegd.c also hold
jpegd->vdev_lock around v4l2_m2m_suspend(), just like
rkjpegd_vdev_unregister() does?

Inside v4l2_m2m_suspend(), curr_ctx = m2m_dev->curr_ctx is read under
m2m_dev->job_spinlock, the spinlock is dropped, and the caller sleeps in:

	wait_event(curr_ctx->finished, !(curr_ctx->job_flags & TRANS_RUNNING));

If the last reference to the video file was dropped via deferred fput()
(for instance from a PF_KTHREAD worker, softirq, or do_exit() after
exit_task_work()), delayed_fput_work runs on system_wq (which is not
WQ_FREEZABLE) and calls:

  __fput()
    v4l2_release()
      rkjpegd_release()
        v4l2_m2m_ctx_release(ctx->fh.m2m_ctx)

Without jpegd->vdev_lock held in rkjpegd_suspend(), rkjpegd_release() can
acquire jpegd->vdev_lock, wait for the job in v4l2_m2m_cancel_job(), and
kfree() m2m_ctx while v4l2_m2m_suspend() is still accessing curr_ctx.

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261022-rockchip-jpegdec-v7-0-3fd7dd55f408@pengutronix.de?part=2

  reply	other threads:[~2026-10-22 12:29 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-22 11:06 [PATCH v7 0/4] media: rockchip: Add JPEG decoder driver Sascha Hauer
2026-10-22 11:06 ` [PATCH v7 1/4] media: dt-bindings: Add Rockchip JPEG decoder Sascha Hauer
2026-10-22 11:06 ` [PATCH v7 2/4] media: rockchip: Add JPEG decoder driver Sascha Hauer
2026-10-22 12:29   ` sashiko-bot [this message]
2026-10-22 17:00   ` Torsten Duwe
2026-10-23  6:59     ` Sascha Hauer
2026-10-22 11:06 ` [PATCH v7 3/4] arm64: dts: rockchip: rk3588: Add JPEG decoder node Sascha Hauer
2026-10-22 11:06 ` [PATCH v7 4/4] arm64: dts: rockchip: rk356x: " Sascha Hauer

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=sashiko-outbox-181439@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=media-ci@linuxtv.org \
    --cc=robh@kernel.org \
    --cc=s.hauer@pengutronix.de \
    --cc=sashiko-reviews@lists.linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox