devicetree.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Atanas Filipov" <atanas.filipov@oss.qualcomm.com>
Cc: media-ci@linuxtv.org, conor+dt@kernel.org, robh@kernel.org,
	devicetree@vger.kernel.org
Subject: Re: [PATCH v6 2/3] media: qcom: jpeg: Add Qualcomm JPEG V4L2 encoder
Date: Thu, 30 Jul 2026 15:20:25 +0000	[thread overview]
Message-ID: <20260730152025.B36251F00A3A@smtp.kernel.org> (raw)
In-Reply-To: <20260730150524.2659577-3-atanas.filipov@oss.qualcomm.com>

Thank you for your contribution! Sashiko AI review found 12 potential issue(s) to consider:
- [Critical] Job abort and `stop_streaming` return buffers to userspace without stopping the hardware, causing DMA into freed or repurposed memory.
- [High] The driver lacks a `.remove` callback and performs an illegal devm `kfree()` in the `v4l2_dev.release` callback, leaking `/dev/videoX` and causing a SLUB crash.
- [High] The driver configures its videobuf2 queues with `vb2_dma_sg_memops` but only programs the hardware with the first scatterlist chunk's DMA address, leading to Out-Of-Bounds memory accesses.
- [High] Race condition between the threaded IRQ handler and `fop_jpeg_file_release` leads to a Use-After-Free of `jenc_context`.
- [High] Calling `VIDIOC_CREATE_BUFS` overwrites the JPEG header cache with zeroes without resetting `quality_programmed`, leading to a divide-by-zero panic in `jpeg_upload_dmi_table`.
- [High] Unsynchronized hardware power and clock transitions during concurrent `start_streaming` and `stop_streaming` cause a race condition.
- [High] The context is freed while a work item is actively queued, leading to a Use-After-Free in the workqueue handler.
- [High] `jpeg_stop` is invoked in the threaded IRQ handler after completing the previous job, creating a race that halts the newly started job.
- [High] The hardware write engine limits are not adjusted for the JPEG header offset, allowing DMA to write past the end of the destination buffer.
- [Medium] Buffers are permanently leaked to the driver if `start_streaming` fails to acquire the hardware.
- [Medium] The driver synchronizes the capture buffer with `DMA_BIDIRECTIONAL` while it was mapped as `DMA_FROM_DEVICE`, violating the DMA API.
- [Medium] The write engine's DMA pointer is programmed with an unaligned address, risking AXI/AHB burst access faults.
--

--- Patch [2]: [PATCH v6 2/3] media: qcom: jpeg: Add Qualcomm JPEG V4L2 encoder ---
commit edb4e8b505654235a8ee08242db0afd01b3b3cca
Author: Atanas Filipov <atanas.filipov@oss.qualcomm.com>

media: qcom: jpeg: Add Qualcomm JPEG V4L2 encoder

Add a Qualcomm JPEG encoder driver implemented on top of the V4L2 mem2mem 
framework.

> diff --git a/drivers/media/platform/qcom/jpeg/qcom_jenc_dev.c b/drivers/media/platform/qcom/jpeg/qcom_jenc_dev.c
> index e4472a30b412d..0000000000000 100644
> --- a/drivers/media/platform/qcom/jpeg/qcom_jenc_dev.c
> +++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_dev.c
> @@ -267,8 +267,7 @@ static const struct of_device_id qcom_jpeg_of_match[] = {
>  MODULE_DEVICE_TABLE(of, qcom_jpeg_of_match);
>  
>  static struct platform_driver qcom_jpeg_platform_driver = {
>  	.probe  = qcom_jpeg_probe,
>  	.driver = {

[Severity: High]
Does this leave the video device registered after module unload? 
It appears the platform driver lacks a .remove callback, which means
video_unregister_device() might never be called when the module is removed,
leaving a stale device node.

> diff --git a/drivers/media/platform/qcom/jpeg/qcom_jenc_ops.c b/drivers/media/platform/qcom/jpeg/qcom_jenc_ops.c
> index b756cf10e5c03..0000000000000 100644
> --- a/drivers/media/platform/qcom/jpeg/qcom_jenc_ops.c
> +++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_ops.c
> @@ -348,6 +348,6 @@ static int jpeg_store_fe_next(struct jenc_context *ectx, struct vb2_buffer *vb2)
>  		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);

[Severity: High]
By using vb2_dma_sg_memops during initialization, we allow fragmented buffers,
but here we only extract the DMA address of the very first scatterlist chunk.
Will the hardware assume a contiguous physical buffer and perform out-of-bounds
DMA past the first fragment?

[ ... ]

> @@ -487,6 +487,6 @@ static void jpeg_upload_dmi_table(struct jenc_context *ectx)
>  	/* DMI Luma upload - values are stored in zigzag order in hdr_cache */
>  	for (i = 0; i < ARRAY_SIZE(t81k1_dct_luma_table); i++) {
>  		reg_val = div_u64(U16_MAX + 1U, luma_qt[i]);

[Severity: High]
Can this lead to a divide-by-zero panic? If a user triggers VIDIOC_CREATE_BUFS
after a frame is encoded, it re-initializes the header cache with zeroes. 
When the next frame encodes, if quality_programmed matches quality_requested, 
the cache update is skipped, and luma_qt[i] would be zero when used as a 
denominator here.

[ ... ]

> @@ -612,12 +612,12 @@ static int jpeg_apply_we_addr(struct jenc_context *ectx, struct qcom_jenc_queue
>  
>  	header_size = min_t(size_t, vb->planes[0].length, ectx->hdr_cache.size);
>  	header_size = qcom_jenc_header_emit(&ectx->hdr_cache, mptr,
>  					    (u32)header_size, (u16)q->vf.width,
>  					    (u16)q->vf.height);
>  
>  	dma += header_size;

[Severity: Medium]
Since the size of the emitted JPEG header is added directly to the DMA base
address, the resulting pointer might not be cache-line or burst aligned. If
the hardware performs burst writes, could this unaligned address cause AXI or
AHB bus faults?

>  	qcom_jenc_dqts_emit(&ectx->hdr_cache, mptr);
>  
>  	/*
>  	 * Flush CPU writes to the header before handing the buffer to the
>  	 * hardware DMA engine.
>  	 */
>  	jpeg_sync_sg(jenc->dev, frame, DMA_BIDIRECTIONAL, true);

[Severity: Medium]
The queue initializes the capture buffer without the bidirectional flag, so it
is implicitly mapped for DMA_FROM_DEVICE. Synchronizing the buffer explicitly
with DMA_BIDIRECTIONAL here violates the DMA API. Could this trigger kernel
warnings if CONFIG_DMA_API_DEBUG is enabled?

[ ... ]

> @@ -665,6 +665,6 @@ static int jpeg_setup_we_size(struct jenc_context *ectx, struct qcom_jenc_queue
>  	for (pln = 0; pln < QCOM_JPEG_MAX_PLANES; pln++)
>  		jpeg_rw_bits(jenc, jpeg_v420_we_stride[pln], JPEG_V420_PLN_WR_STRIDE_STRIDE_MSK, 0);
>  
>  	jpeg_io_write(jenc, JPEG_V420_REG_WE_BSIZE_0, dfmt->plane_fmt[0].sizeimage);

[Severity: High]
Will this allow the hardware to write past the end of the destination buffer?
The DMA pointer was advanced by header_size in jpeg_apply_we_addr(), but the 
hardware's buffer size limit is programmed with the full original sizeimage, 
potentially extending the write by header_size bytes beyond the allocation.

[ ... ]

> @@ -806,12 +806,12 @@ static irqreturn_t op_jpeg_irq_bot(int irq, void *data)
>  
>  		if (ctx && ctx->bufq[JENC_DST_QUEUE].buff_id >= 0) {
>  			struct qcom_jenc_queue *dq = &ctx->bufq[JENC_DST_QUEUE];
>  			struct qcom_jpeg_buff *frame = &dq->buff[dq->buff_id];
>  			size_t out_size = jpeg_rd_bits(jenc, JPEG_V420_REG_ENC_OUT_SIZE,
>  						       JPEG_V420_STATUS_OUT_SIZE_BYTES_MSK);
>  
>  			dev_dbg(jenc->dev, "complete idx:%d addr=%pad size=%zu\n",
>  				dq->buff_id, &frame->plns[0].dma, out_size);
>  
>  			jenc->enc_hw_irq_cb(ctx, VB2_BUF_STATE_DONE,
>  					    out_size + ctx->hdr_cache.size);
>  		}

[Severity: High]
Can this result in a use-after-free? The threaded IRQ handler drops the
spinlock right before invoking enc_hw_irq_cb(), so fop_jpeg_file_release()
could run concurrently, acquire the lock, see actx is already NULL, and free
the context while the IRQ handler is still using it.

>  		jpeg_stop(jenc);

[Severity: High]
Does this unconditionally stop the hardware even if the next job has already
started? The enc_hw_irq_cb() schedules a worker that could immediately submit
a new job via jpeg_exec(). If this threaded IRQ handler resumes and blindly
calls jpeg_stop(), it might halt the newly started job mid-flight.

[ ... ]

> @@ -912,6 +912,6 @@ static int op_jpeg_queue_setup(struct jenc_context *ectx, enum qcom_enc_qid id)
>  	if (id == JENC_SRC_QUEUE) {
>  		struct qcom_jenc_queue *q = &ectx->bufq[id];
>  
>  		rc = qcom_jenc_header_init(&ectx->hdr_cache, q->vf.pixelformat);

[Severity: High]
Is it intentional that VIDIOC_CREATE_BUFS calls queue_setup, which resets 
the header cache with zeroes, but doesn't reset quality_programmed? This seems
to be what later causes the divide-by-zero panic in the DMI upload.

[ ... ]

> @@ -1556,6 +1556,6 @@ static int op_jpeg_acquire(struct jenc_context *ectx, struct vb2_queue *q)
>  		}
>  
>  		enable_irq(jenc->irq);
>  
>  		rc = jpeg_init(jenc);

[Severity: High]
Is it safe for this hardware enablement to potentially race with teardown? 
If another thread concurrently stops a stream and calls hw_release() without 
holding dev_mutex, it could lead to hardware initialization running at the same 
time as teardown, clobbering the state and causing unbalanced IRQ enablement.

> diff --git a/drivers/media/platform/qcom/jpeg/qcom_jenc_v4l2.c b/drivers/media/platform/qcom/jpeg/qcom_jenc_v4l2.c
> index 6cb162f8eef90..0000000000000 100644
> --- a/drivers/media/platform/qcom/jpeg/qcom_jenc_v4l2.c
> +++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_v4l2.c
> @@ -338,12 +338,12 @@ static void jpeg_v4l2_work_stop(struct jenc_context *ctx, enum vb2_buffer_state
>  	while ((vb = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx))) {
>  		if (was_stopping && !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);
>  		}
>  	}

[Severity: Critical]
Do we return the buffers to userspace before the underlying hardware has
actually stopped? If v4l2_m2m_buf_done() is called here during a streamoff, 
the buffer might be reused by userspace, but the hardware may still be actively 
executing the job and corrupting that repurposed memory.

>  	while ((vb = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx)))
>  		v4l2_m2m_buf_done(vb, buff_state);
>  
>  	/* Always call job_finish to let m2m schedule the next job. */
>  	schedule_work(&ctx->finish_work);

[Severity: High]
Could this lead to a use-after-free in the workqueue? The release function
cancels finish_work and then calls v4l2_m2m_ctx_release(), which eventually
invokes jpeg_v4l2_work_stop(). Re-queuing finish_work right here leaves a 
pending work item on memory that is about to be freed.

[ ... ]

> @@ -796,6 +796,6 @@ static int bop_jpeg_vb2_start_streaming(struct vb2_queue *q, unsigned int count)
>  	} else {
>  		rc = 0;
>  	}
>  
>  	mutex_unlock(&jenc->dev_mutex);
>  
>  	return rc;

[Severity: Medium]
If hw_acquire() fails above, the error is returned immediately. Doesn't this
permanently leak the buffers to the driver since they are not returned to the
vb2 framework using vb2_buffer_done() with VB2_BUF_STATE_QUEUED?

[ ... ]

> @@ -837,12 +837,12 @@ static void bop_jpeg_vb2_stop_streaming(struct vb2_queue *q)
>  
>  	mutex_lock(&jenc->dev_mutex);
>  
>  	jpeg_v4l2_work_stop(ectx, VB2_BUF_STATE_ERROR);
>  
>  	do_release = ectx->hw_acquired;
>  	ectx->hw_acquired = false;
>  
>  	mutex_unlock(&jenc->dev_mutex);
>  
>  	/*
>  	 * hw_release() calls jpeg_deinit() which issues HW_STOP and waits for
>  	 * the STOP_ACK interrupt completion.  It must be called outside
>  	 * dev_mutex to avoid a deadlock: the threaded IRQ handler acquires
>  	 * dev_mutex before signalling the completion.
>  	 */
>  	if (do_release)
>  		hw->hw_release(ectx, q);

[Severity: High]
Is it safe to drop dev_mutex before invoking hw_release()? If another thread
concurrently starts a stream and calls hw_acquire() under the lock, it could 
run hardware enablement at the same time as teardown, potentially clobbering 
hardware state and causing unbalanced IRQ enablement.

[ ... ]

> @@ -1105,6 +1105,6 @@ static void jenc_v4l2_dev_release(struct v4l2_device *v4l2_dev)
>  	struct qcom_jenc_dev *jenc = container_of(v4l2_dev, struct qcom_jenc_dev, v4l2_dev);
>  
>  	v4l2_device_unregister(&jenc->v4l2_dev);
>  	v4l2_m2m_put(jenc->m2m_dev);
>  	kfree(jenc);

[Severity: High]
Is it safe to manually kfree(jenc) here when the structure was originally 
allocated using devm_kzalloc() during probe? It looks like this would trigger
a double-free SLUB violation.

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

  reply	other threads:[~2026-07-30 15:20 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-30 15:05 [PATCH v6 0/3] Add Qualcomm JPEG V4L2 encoder for SM8250 Atanas Filipov
2026-07-30 15:05 ` [PATCH v6 1/3] dt-bindings: media: qcom: Add JPEG encoder binding Atanas Filipov
2026-07-30 15:39   ` Dmitry Baryshkov
2026-07-30 16:24     ` Atanas Filipov
2026-07-30 15:05 ` [PATCH v6 2/3] media: qcom: jpeg: Add Qualcomm JPEG V4L2 encoder Atanas Filipov
2026-07-30 15:20   ` sashiko-bot [this message]
2026-07-30 15:57   ` Dmitry Baryshkov
2026-07-30 15:05 ` [PATCH v6 3/3] arm64: dts: qcom: sm8250: Add JPEG encoder node Atanas Filipov
2026-07-30 15:31   ` sashiko-bot
2026-07-30 16:01   ` Dmitry Baryshkov
2026-07-30 18:02     ` 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=20260730152025.B36251F00A3A@smtp.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;
as well as URLs for NNTP newsgroup(s).