Devicetree
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Atanas Filipov" <atanas.filipov@oss.qualcomm.com>
Cc: devicetree@vger.kernel.org, robh@kernel.org, conor+dt@kernel.org,
	media-ci@linuxtv.org
Subject: Re: [PATCH v7 4/8] media: qcom: jpeg: Add Qualcomm JPEG V4L2 encoder
Date: Fri, 06 Nov 2026 08:59:00 +0000	[thread overview]
Message-ID: <sashiko-outbox-201096@kernel.org> (raw)
In-Reply-To: <20261106082522.1448390-5-atanas.filipov@oss.qualcomm.com>

Thank you for your contribution! Sashiko AI review found 11 potential
issues to consider:
- [High] media: qcom: jpeg: missing hardware reinit in
  qcom_jpeg_pm_system_resume()
- [High] media: qcom: jpeg: use-after-free of m2m_ctx in
  jpeg_finish_work()
- [High] media: qcom: jpeg: stop_streaming() drains both queues and
  releases HW early
- [High] media: qcom: jpeg: unverified contiguous DMA length in
  jpeg_store_fe/we_next()
- [Medium] media: qcom: jpeg: unadjusted WE_BSIZE_0 limit in
  jpeg_setup_we_size()
- [Medium] media: qcom: jpeg: 1-bit MAL_BOUNDARY masks truncate 3-bit
  boundary values
- [Medium] media: qcom: jpeg: disjoint vdev->lock and vq->lock leave
  ioctls unserialized
- [Medium] media: qcom: jpeg: fop_jpeg_file_open() skips
  v4l2_ctrl_handler_free()
- [Medium] media: qcom: jpeg: unhandled IRQs unmasked in jpeg_init()
  risk lost edge IRQ
- [Medium] media: qcom: jpeg: non-exclusive IRQ status handling in
  qcom_jenc_v420_irq_bot()
- [Low] media: qcom: jpeg: 32-bit multiplication overflow in
  jpeg_calc_optimal_clk()
--

commit 0f8ce3b457607190f1f36e252188757adec6db68
Author: Atanas Filipov <atanas.filipov@oss.qualcomm.com>

media: qcom: jpeg: Add Qualcomm JPEG V4L2 encoder

This commit adds a V4L2 mem2mem driver for the standalone Qualcomm JPEG
encoder hardware block on SM8250 and SC7280 platforms.

> diff --git a/drivers/media/platform/qcom/jpeg/qcom_jenc_dev.c b/drivers/media/platform/qcom/jpeg/qcom_jenc_dev.c
> new file mode 100644
> index 0000000000000..da69c0bb3bbe7
> --- /dev/null
> +++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_dev.c
[ ... ]
> +static int qcom_jpeg_pm_resume(struct device *dev)
> +{
> +	struct qcom_jenc_dev *jenc = dev_get_drvdata(dev);
> +	int rc;
> +
> +	rc = qcom_jpeg_clk_on(jenc);
> +	if (rc)
> +		return rc;
> +
> +	return 0;
> +}
> +
> +static int qcom_jpeg_pm_system_suspend(struct device *dev)
> +{
> +	struct qcom_jenc_dev *jenc = dev_get_drvdata(dev);
> +	int rc;
> +
> +	v4l2_m2m_suspend(jenc->m2m_dev);
> +
> +	rc = pm_runtime_force_suspend(dev);
> +	if (rc)
> +		v4l2_m2m_resume(jenc->m2m_dev);
> +
> +	return rc;
> +}
> +
> +static int qcom_jpeg_pm_system_resume(struct device *dev)
> +{
> +	struct qcom_jenc_dev *jenc = dev_get_drvdata(dev);
> +	int rc;
> +
> +	rc = pm_runtime_force_resume(dev);
> +	if (rc)
> +		return rc;
> +
> +	v4l2_m2m_resume(jenc->m2m_dev);
> +
> +	return 0;
> +}

[Severity: High]
Does system suspend and resume while streaming is active leave the JPEG
hardware uninitialized and JPEG_V420_REG_INT_MASK cleared?

jpeg_init() is only called when qcom_jenc_v420_acquire() transitions
jenc->ref_count from 0 to 1.  When qcom_jpeg_pm_system_suspend() runs
pm_runtime_force_suspend(), the titan_top power domain can collapse and
reset volatile registers, including JPEG_V420_REG_INT_MASK.  On resume,
pm_runtime_force_resume() only enables clocks in qcom_jpeg_pm_resume()
before v4l2_m2m_resume() restarts queued jobs, so subsequent jobs run with
interrupts masked in hardware and hang VIDIOC_STREAMOFF or close() in
v4l2_m2m_cancel_job().

> diff --git a/drivers/media/platform/qcom/jpeg/qcom_jenc_v420.c b/drivers/media/platform/qcom/jpeg/qcom_jenc_v420.c
> new file mode 100644
> index 0000000000000..cd1501bca71a7
> --- /dev/null
> +++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_v420.c
[ ... ]
> +static int jpeg_init(struct qcom_jenc_dev *jenc)
> +{
[ ... ]
> +	jpeg_clear_planes_queues(jenc);
> +
> +	jpeg_io_write(jenc, JPEG_V420_REG_INT_CLR, JPEG_V420_IRQ_STATUS_ALL_BITS);
> +	jpeg_io_write(jenc, JPEG_V420_REG_INT_MASK, JPEG_V420_IRQ_STATUS_ALL_BITS);
> +
> +	return 0;
> +}

[Severity: Medium]
Can unmasking JPEG_V420_IRQ_STATUS_ALL_BITS here cause unnecessary
threaded IRQ wakeups or a lost edge interrupt on SESSION_DONE?

JPEG_V420_IRQ_STATUS_ALL_BITS enables RD_BUF_PLNS_DONE_MSK,
WR_BUF_PLN0_DONE, RD_BUF_PLNS_REQ_ATTN_MSK, and WR_BUF_PLNS_REQ_ATTN_MSK,
which neither qcom_jenc_v420_irq_top() nor qcom_jenc_v420_irq_bot()
handles.  When one of those plane bits fires at the end of a frame,
qcom_jenc_v420_irq_top() wakes the IRQ thread for a no-op, and if
SESSION_DONE asserts in JPEG_V420_REG_INT_STATUS between jpeg_io_read()
and jpeg_io_write(..., JPEG_V420_REG_INT_CLR, ...) while the plane bit is
still holding the rising-edge interrupt line high, clearing only the
earlier status bits leaves SESSION_DONE asserted with no new rising edge
to complete the job.

[ ... ]
> +static int jpeg_store_fe_next(struct jenc_context *ectx, struct vb2_buffer *vb2)
> +{
> +	struct qcom_jenc_queue *q = &ectx->bufq[TYPE2QID(vb2->type)];
> +	struct qcom_jpeg_buff *buff = &q->buff[vb2->index];
> +	u8 pln;
> +
> +	for (pln = 0; pln < q->vf.num_planes; pln++) {
> +		buff->plns[pln].sgt = vb2_dma_sg_plane_desc(vb2, pln);
> +		if (!buff->plns[pln].sgt || !buff->plns[pln].sgt->sgl)
> +			return -EINVAL;
> +
> +		buff->plns[pln].dma = sg_dma_address(buff->plns[pln].sgt->sgl);
> +		if (!buff->plns[pln].dma)
> +			return -EINVAL;
> +
> +		buff->plns[pln].size = vb2_plane_size(vb2, pln);
> +		if (!buff->plns[pln].size)
> +			return -EINVAL;
> +	}
> +
> +	return 0;
> +}

[Severity: High]
Can jpeg_store_fe_next() and jpeg_store_we_next() cause out-of-bounds DMA
when a buffer is mapped as multiple scatterlist segments?

Both queues use vb2_dma_sg_memops with VB2_USERPTR and VB2_DMABUF enabled,
which does not guarantee a single contiguous DMA segment (for example, an
unaligned userptr on arm64 where iommu_dma_map_sg() falls back to
iommu_dma_map_sg_swiotlb(), or a multi-segment dmabuf).  Because the
driver programs the hardware with sg_dma_address(sgt->sgl) for the whole
plane without checking that sg_dma_len(sgt->sgl) covers
vb2_plane_size(vb2, pln), the hardware will DMA linearly past the end of
the first segment.  Should the queues use vb2_dma_contig_memops, or verify
that sg_dma_len(sgt->sgl) >= vb2_plane_size(vb2, pln)?

[ ... ]
> +static int jpeg_apply_we_addr(struct jenc_context *ectx, struct qcom_jenc_queue *q,
> +			      struct vb2_buffer *vb)
> +{
[ ... ]
> +	header_size = ectx->hdr_cache.layout.size;
> +	if (vb->planes[0].length < header_size)
> +		return -EINVAL;
> +
> +	memcpy(mptr, ectx->hdr_cache.data, header_size);
> +	v4l2_jpeg_hdr_set_size(mptr, &ectx->hdr_cache.layout, q->vf.width, q->vf.height);
> +
> +	dma += header_size;
> +
> +	/*
> +	 * Flush CPU writes to the header before handing the buffer to the
> +	 * hardware DMA engine.
> +	 */
> +	jpeg_sync_sg(jenc->dev, frame, DMA_BIDIRECTIONAL, true);
> +
> +	jpeg_io_write(jenc, jpeg_v420_we_pntr[pln], dma);
[ ... ]
> +}
[ ... ]
> +static void jpeg_setup_we_size(struct jenc_context *ectx, struct qcom_jenc_queue *q)
> +{
> +	struct v4l2_pix_format_mplane *dfmt = &q->vf;
> +	struct qcom_jenc_dev *jenc = ectx->jenc;
> +	u8 pln;
> +
> +	for (pln = 0; pln < QCOM_JPEG_MAX_PLANES; pln++)
> +		jpeg_rw_bits(jenc, jpeg_v420_we_stride[pln], JPEG_V420_PLN_WR_STRIDE_MSK, 0);
> +
> +	jpeg_io_write(jenc, JPEG_V420_REG_WE_BSIZE_0, dfmt->plane_fmt[0].sizeimage);
> +
> +	dev_dbg(ectx->dev, "WE size=%u\n", dfmt->plane_fmt[0].sizeimage);
> +}

[Severity: Medium]
Should JPEG_V420_REG_WE_BSIZE_0 be reduced by header_size?

jpeg_apply_we_addr() advances JPEG_V420_REG_WE_PNTR_0 by header_size into
the destination buffer, but jpeg_setup_we_size() programs
JPEG_V420_REG_WE_BSIZE_0 with the full dfmt->plane_fmt[0].sizeimage.  If a
compressed frame ever reaches sizeimage - header_size, the write engine is
allowed to DMA up to header_size bytes past the end of a sizeimage-sized
buffer, and out_size + header_size in qcom_jenc_v420_irq_bot() will exceed
vb->planes[0].length.

[ ... ]
> +irqreturn_t qcom_jenc_v420_irq_bot(int irq, void *data)
> +{
> +	struct qcom_jenc_dev *jenc = data;
> +	unsigned long flags;
> +	u32 irq_status;
> +
> +	irq_status = READ_ONCE(jenc->pending_irq_status);
> +
> +	if (FIELD_GET(JPEG_V420_IRQ_STATUS_SESSION_DONE, irq_status)) {
> +		struct jenc_context *ctx;
> +
> +		spin_lock_irqsave(&jenc->hw_lock, flags);
> +		ctx = jenc->actx;
> +		jenc->actx = NULL;
> +		spin_unlock_irqrestore(&jenc->hw_lock, flags);
> +
> +		if (ctx && ctx->bufq[JENC_DST_QUEUE].buff_id >= 0) {
[ ... ]
> +			jpeg_stop(jenc);
> +			jenc->enc_hw_irq_cb(ctx, VB2_BUF_STATE_DONE,
> +					    out_size + ctx->hdr_cache.layout.size);
> +		} else {
> +			jpeg_stop(jenc);
> +		}
> +	}
> +
> +	if (FIELD_GET(JPEG_V420_IRQ_STATUS_SESION_ERROR_MSK, irq_status)) {
> +		struct jenc_context *ctx;
> +
> +		spin_lock_irqsave(&jenc->hw_lock, flags);
> +		ctx = jenc->actx;
> +		jenc->actx = NULL;
> +		spin_unlock_irqrestore(&jenc->hw_lock, flags);
> +
> +		dev_err(jenc->dev, "encoder hardware failure=0x%lx\n",
> +			FIELD_GET(JPEG_V420_IRQ_STATUS_SESION_ERROR_MSK, irq_status));
> +
> +		jpeg_stop(jenc);
> +		if (ctx)
> +			jenc->enc_hw_irq_cb(ctx, VB2_BUF_STATE_ERROR, 0);
> +	}
> +
> +	return IRQ_HANDLED;
> +}

[Severity: Medium]
What happens if both JPEG_V420_IRQ_STATUS_SESSION_DONE and
JPEG_V420_IRQ_STATUS_SESION_ERROR_MSK are set in irq_status?

The first block pops jenc->actx, completes the failed frame as
VB2_BUF_STATE_DONE, and schedules finish_work, which can start the next
job and set a new jenc->actx on another CPU; the second block then runs,
calls jpeg_stop(jenc) without dev_mutex, and aborts that newly started
job.  Checking JPEG_V420_IRQ_STATUS_SESION_ERROR_MSK first and using else
if for JPEG_V420_IRQ_STATUS_SESSION_DONE would avoid both completing a
failed frame and clobbering the next job.

[ ... ]
> +static unsigned long jpeg_calc_optimal_clk(struct qcom_jenc_dev *jenc,
> +					   u32 width, u32 height, u32 fps)
> +{
> +	unsigned long ref_pixel_rate;
> +	unsigned long optimal_clk;
> +	unsigned long pixel_rate;
> +
> +	if (!width || !height || !fps)
> +		return jenc->max_freq;
> +
> +	ref_pixel_rate = JPEG_REF_THROUGHPUT_MPPS * 1000000UL;
> +	pixel_rate = (unsigned long)width * height * fps;
> +
> +	optimal_clk = DIV_ROUND_UP_ULL(pixel_rate * JPEG_REF_CLK_HZ, ref_pixel_rate);
> +
> +	return min_t(unsigned long, jenc->max_freq, optimal_clk);
> +}

[Severity: Low]
Can pixel_rate * JPEG_REF_CLK_HZ overflow on 32-bit builds (such as
COMPILE_TEST) before DIV_ROUND_UP_ULL() widens the product to 64 bits?
Both pixel_rate and JPEG_REF_CLK_HZ have type unsigned long, so casting
one operand to u64 (or using mul_u32_u32()) is needed to avoid 32-bit
truncation.

> diff --git a/drivers/media/platform/qcom/jpeg/qcom_jenc_v420_hw_info.h b/drivers/media/platform/qcom/jpeg/qcom_jenc_v420_hw_info.h
> new file mode 100644
> index 0000000000000..4fc5eb980c5f7
> --- /dev/null
> +++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_v420_hw_info.h
[ ... ]
> +#define JPEG_V420_FE_CFG_SIXTEEN_MCU_EN		BIT(21)
> +#define JPEG_V420_FE_CFG_MCUS_PER_BLOCK		BIT(22)
> +#define JPEG_V420_FE_CFG_MAL_BOUNDARY		BIT(24)
> +#define JPEG_V420_FE_CFG_MAL_EN			BIT(27)
> +#define	JPEG_V420_FE_CFG_PLNS_EN_MSK		GENMASK(18, 16)
[ ... ]
> +/* WE_CFG fields */
> +#define JPEG_V420_WE_CFG_MEMORY_FORMAT_MSK	GENMASK(9, 8)
> +#define JPEG_V420_WE_CFG_CBCR_ORDER		BIT(12)
> +#define JPEG_V420_WE_CFG_PLN0_EN		BIT(16)
> +#define JPEG_V420_WE_CFG_MAL_BOUNDARY		BIT(24)
> +#define JPEG_V420_WE_CFG_MAL_EN			BIT(27)
> +#define JPEG_V420_WE_CFG_POP_BUFF_ON_EOS	BIT(28)

[Severity: Medium]
Should JPEG_V420_FE_CFG_MAL_BOUNDARY and JPEG_V420_WE_CFG_MAL_BOUNDARY be
GENMASK(26, 24) instead of BIT(24)?

enum jpeg_mal_bounds defines 3-bit values 0x0..0x7, and for valid widths
(256..8192) jpeg_get_mal_boundary() returns 0x3..0x7.  Passing BIT(24) to
jpeg_rw_bits() masks out bits [2:1] of the boundary value, programming the
MAL boundary as 32B or 64B instead of 256B..4KB.

> diff --git a/drivers/media/platform/qcom/jpeg/qcom_jenc_v4l2.c b/drivers/media/platform/qcom/jpeg/qcom_jenc_v4l2.c
> new file mode 100644
> index 0000000000000..b068a9d2322ed
> --- /dev/null
> +++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_v4l2.c
[ ... ]
> +static void jpeg_v4l2_return_bufs(struct jenc_context *ctx, enum vb2_buffer_state buff_state)
> +{
> +	bool draining = ctx->fh.m2m_ctx->is_draining;
> +	struct vb2_v4l2_buffer *vb;
> +
> +	/* Drain CAPTURE queue; signal EOS on last buffer if V4L2_ENC_CMD_STOP. */\n
> +	while ((vb = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx))) {
> +		if (draining && !v4l2_m2m_last_dst_buf(ctx->fh.m2m_ctx)) {
> +			vb2_set_plane_payload(&vb->vb2_buf, 0, 0);
> +			v4l2_m2m_last_buffer_done(ctx->fh.m2m_ctx, vb);
> +		} else {
> +			v4l2_m2m_buf_done(vb, buff_state);
> +		}
> +	}
> +
> +	while ((vb = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx)))
> +		v4l2_m2m_buf_done(vb, buff_state);
> +}
[ ... ]
> +static void bop_jpeg_vb2_stop_streaming(struct vb2_queue *q)
> +{
> +	struct jenc_context *ectx = vb2_get_drv_priv(q);
> +	struct qcom_jenc_dev *jenc = ectx->jenc;
> +	bool do_release;
> +
> +	mutex_lock(&jenc->dev_mutex);
> +
> +	do_release = ectx->hw_acquired;
> +	ectx->hw_acquired = false;
> +
> +	mutex_unlock(&jenc->dev_mutex);
> +
> +	/*
> +	 * qcom_jenc_v420_release() calls jpeg_deinit() which issues HW_STOP and
> +	 * waits for the STOP_ACK interrupt completion. It runs before any buffer
> +	 * is returned below and outside dev_mutex: the threaded IRQ handler takes
> +	 * dev_mutex, and with IRQF_ONESHOT the STOP_ACK interrupt stays masked
> +	 * until that handler returns.
> +	 */
> +	if (do_release)
> +		qcom_jenc_v420_release(ectx);
> +
> +	mutex_lock(&jenc->dev_mutex);
> +	jpeg_v4l2_return_bufs(ectx, VB2_BUF_STATE_ERROR);
> +	v4l2_m2m_update_stop_streaming_state(ectx->fh.m2m_ctx, q);
> +	mutex_unlock(&jenc->dev_mutex);
> +}

[Severity: High]
Doesn't calling VIDIOC_STREAMOFF on one queue in
bop_jpeg_vb2_stop_streaming() fail all buffers on the other queue and
power down the hardware while the other queue is still streaming?

vb2_ops.stop_streaming() is called for a single vb2_queue q, but
jpeg_v4l2_return_bufs() removes and completes buffers from both CAPTURE
and OUTPUT (and if OUTPUT stops while draining with multiple CAPTURE
buffers queued, jpeg_v4l2_return_bufs() fails all but the last CAPTURE
buffer with VB2_BUF_STATE_ERROR and consumes is_draining before
v4l2_m2m_update_stop_streaming_state() runs).  Likewise,
jpeg_v4l2_work_stop() calls jpeg_v4l2_return_bufs() on a single job error,
draining every queued buffer in both queues instead of just the active
job's buffers.

In addition, ectx->hw_acquired is a single boolean cleared on the first
queue's stop_streaming(), so stopping one queue releases the hardware
reference and disables the IRQ while the sibling queue remains streaming.

[ ... ]
> +static int jpeg_v4l2_init_queue(void *priv, struct vb2_queue *sq, struct vb2_queue *dq)
> +{
> +	struct jenc_context *ectx = priv;
> +	int rc;
> +
> +	sq->drv_priv		= ectx;
> +	sq->dev			= ectx->dev;
> +	sq->type		= V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
> +	sq->io_modes		= VB2_MMAP | VB2_DMABUF | VB2_USERPTR;
> +	sq->buf_struct_size	= sizeof(struct v4l2_m2m_buffer);
> +	sq->ops			= &qcom_jpeg_v4l2_vb2_ops;
> +	sq->mem_ops		= &vb2_dma_sg_memops;
> +	sq->timestamp_flags	= V4L2_BUF_FLAG_TIMESTAMP_COPY;
> +	sq->lock		= &ectx->ctx_lock;
> +	sq->min_queued_buffers	= 1;

[Severity: Medium]
Does setting sq->lock and dq->lock to &ectx->ctx_lock while
jenc->vdev->lock is &jenc->dev_mutex leave format and encoder command
ioctls unserialized against queue ioctls?

In v4l2_ioctl_get_lock(), queue ioctls (INFO_FL_QUEUE: REQBUFS,
CREATE_BUFS, PREPARE_BUF, QBUF, STREAMON, STREAMOFF) lock
m2m_ctx->q_lock (&ectx->ctx_lock), whereas VIDIOC_S_FMT, VIDIOC_G_FMT, and
VIDIOC_ENCODER_CMD lock vdev->lock (&jenc->dev_mutex) and never take
ectx->ctx_lock:

CPU0                                  CPU1
v4l2_ioctl (VIDIOC_S_FMT)             v4l2_ioctl (VIDIOC_REQBUFS)
  mutex_lock(&jenc->dev_mutex)          mutex_lock(&ectx->ctx_lock)
  jpeg_v4l2_set_format()                bop_jpeg_vb2_queue_setup()
    jpeg_v4l2_queues_busy(ectx)           reads q->vf
    updates sq->vf, dq->vf                qcom_jenc_v420_queue_setup()
    qcom_jenc_v420_src_fmt_update()         jpeg_header_init(&ectx->hdr_cache)
      jpeg_header_init(&ectx->hdr_cache)      <-- data race on vf / hdr_cache

Similarly, iop_jpeg_encoder_command() calls v4l2_m2m_ioctl_encoder_cmd()
under jenc->dev_mutex while VIDIOC_QBUF and VIDIOC_STREAMOFF mutate the
same m2m_ctx draining state and rdy_queue under ectx->ctx_lock.

[ ... ]
> +static int fop_jpeg_file_open(struct file *file)
> +{
[ ... ]
> +	v4l2_ctrl_handler_init(&ectx->ctrl_hdl, 1);
> +	ectx->quality_ctl = v4l2_ctrl_new_std(&ectx->ctrl_hdl,
> +					      &qcom_jpeg_v4l2_ctrl_ops,
> +					      V4L2_CID_JPEG_COMPRESSION_QUALITY,
> +					      QCOM_JPEG_QUALITY_MIN,
> +					      QCOM_JPEG_QUALITY_MAX,
> +					      QCOM_JPEG_QUALITY_UNT,
> +					      QCOM_JPEG_QUALITY_DEF);
> +	if (ectx->ctrl_hdl.error) {
> +		rc = ectx->ctrl_hdl.error;
> +		goto err_fh_exit;
> +	}
> +
> +	ectx->fh.ctrl_handler = &ectx->ctrl_hdl;
> +
> +	rc = v4l2_ctrl_handler_setup(&ectx->ctrl_hdl);
> +	if (rc)
> +		goto err_ctrl_handler_free;
[ ... ]
> +	return 0;
> +
> +err_ctrl_handler_free:
> +	v4l2_ctrl_handler_free(&ectx->ctrl_hdl);
> +err_fh_exit:
> +	v4l2_fh_del(&ectx->fh, file);
> +	v4l2_fh_exit(&ectx->fh);

[Severity: Medium]
Does the ectx->ctrl_hdl.error path leak ectx->ctrl_hdl.buckets by jumping
to err_fh_exit instead of err_ctrl_handler_free after
v4l2_ctrl_handler_init()?

> +err_unlock_free:
> +	mutex_destroy(&ectx->quality_mutex);
> +	mutex_destroy(&ectx->ctx_lock);
> +	kfree(ectx);
> +
> +	return rc;
> +}
> +
> +static int fop_jpeg_file_release(struct file *file)
> +{
> +	struct jenc_context *ectx = jpeg_file2ctx(file);
> +	struct v4l2_m2m_dev *m2m_dev = ectx->fh.m2m_ctx->m2m_dev;
> +	struct qcom_jenc_dev *jenc = ectx->jenc;
> +	unsigned long flags;
> +
> +	/*
> +	 * Waits until a running job of this context completes: its
> +	 * SESSION_DONE interrupt needs jenc->actx, and finish_work must not
> +	 * be cancelled before, as it calls v4l2_m2m_job_finish().
> +	 */
> +	v4l2_m2m_ctx_release(ectx->fh.m2m_ctx);
> +
> +	/*
> +	 * Ensure the threaded IRQ handler cannot dereference this context
> +	 * after it is freed.  Clear jenc->actx under hw_lock before
> +	 * cancelling work.
> +	 */
> +	spin_lock_irqsave(&jenc->hw_lock, flags);
> +	if (jenc->actx == ectx)
> +		jenc->actx = NULL;
> +	spin_unlock_irqrestore(&jenc->hw_lock, flags);
> +
> +	cancel_work_sync(&ectx->finish_work);

[Severity: High]
Can jpeg_finish_work() dereference ectx->fh.m2m_ctx after
v4l2_m2m_ctx_release() frees it?

v4l2_m2m_ctx_release() only waits for TRANS_RUNNING to clear before
freeing m2m_ctx.  Once _v4l2_m2m_job_finish() clears TRANS_RUNNING and
wakes m2m_ctx->finished (or if a prior STREAMOFF already cleared
TRANS_RUNNING while finish_work was still pending), v4l2_m2m_ctx_release()
frees m2m_ctx before cancel_work_sync(&ectx->finish_work) is reached:

CPU0 (close)                          CPU1 (kworker)
fop_jpeg_file_release()               jpeg_finish_work()
  v4l2_m2m_ctx_release(m2m_ctx)         v4l2_m2m_job_finish(..., m2m_ctx)
    v4l2_m2m_cancel_job()                 _v4l2_m2m_job_finish()
      wait_event(m2m_ctx->finished)         clears TRANS_RUNNING
                                            wake_up(&m2m_ctx->finished)
    kfree(m2m_ctx)                        v4l2_m2m_schedule_next_job(..., m2m_ctx)
  ...                                       <-- use-after-free of m2m_ctx
  cancel_work_sync(&ectx->finish_work)

Since v4l2_m2m_job_finish() already defers the next job via
schedule_work(&m2m_dev->job_work), calling v4l2_m2m_job_finish() directly
in jpeg_v4l2_work_done() and jpeg_v4l2_work_stop() would avoid needing
finish_work altogether.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261106082522.1448390-1-atanas.filipov@oss.qualcomm.com?part=4

  reply	other threads:[~2026-11-06  8:59 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-06  8:25 [PATCH v7 0/8] Add Qualcomm JPEG V4L2 encoder for SM8250 and SC7280 Atanas Filipov
2026-11-06  8:25 ` [PATCH v7 1/8] dt-bindings: media: qcom: Add JPEG encoder binding Atanas Filipov
2026-11-06 13:31   ` Rob Herring
2026-11-06 14:42     ` Bryan O'Donoghue
2026-11-06  8:25 ` [PATCH v7 2/8] media: jpeg: Add JPEG baseline and JFIF APP0 constants Atanas Filipov
2026-11-06  8:25 ` [PATCH v7 3/8] media: v4l2-jpeg: Add a baseline JPEG header writer Atanas Filipov
2026-11-06  8:25 ` [PATCH v7 4/8] media: qcom: jpeg: Add Qualcomm JPEG V4L2 encoder Atanas Filipov
2026-11-06  8:59   ` sashiko-bot [this message]
2026-11-06  8:25 ` [PATCH v7 5/8] arm64: dts: qcom: sm8250: Add JPEG encoder node Atanas Filipov
2026-11-06  8:25 ` [PATCH v7 6/8] arm64: dts: qcom: qrb5165-rb5: Enable JPEG encoder Atanas Filipov
2026-11-06  8:25 ` [PATCH v7 7/8] arm64: dts: qcom: kodiak: Add JPEG encoder node Atanas Filipov
2026-11-06  8:25 ` [PATCH v7 8/8] arm64: dts: qcom: qcs6490-rb3gen2: Enable JPEG encoder Atanas Filipov

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-201096@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=atanas.filipov@oss.qualcomm.com \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=media-ci@linuxtv.org \
    --cc=robh@kernel.org \
    --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