From: sashiko-bot@kernel.org
To: christian.koever-draxl@student.uibk.ac.at
Cc: devicetree@vger.kernel.org, robh@kernel.org, conor+dt@kernel.org,
media-ci@linuxtv.org
Subject: Re: [PATCH 08/11] media: amlogic: vdec: add VP9 decoding on the HEVC core
Date: Sun, 25 Oct 2026 20:23:57 +0000 [thread overview]
Message-ID: <sashiko-outbox-184424@kernel.org> (raw)
In-Reply-To: <20261025192507.210058-9-christian.koever-draxl@student.uibk.ac.at>
Thank you for your contribution! Sashiko AI review found 11 potential issue(s) to consider:
- [Critical] media: amlogic: vdec: missing CAPTURE buffer bounds checks in vp9_copy_out()
- [Critical] media: amlogic: vdec: missing core stop and IRQ sync in aml_vp9_exit()
- [High] media: amlogic: vdec: broken alt-ref and show_existing_frame in vp9_copy_out()
- [High] media: amlogic: vdec: intra-only frames reset refs in aml_vp9_config_picture()
- [High] media: amlogic: vdec: vp9_alloc_slots() frees active reference slots on resize
- [High] media: amlogic: vdec: workspace overflow for height > 2304 in vp9.c
- [High] media: amlogic: vdec: aml_vp9_feed() omits AMLV header from FIFO buffer size
- [High] media: amlogic: vdec: DMA buffer leak in aml_vp9_exit() after timeout or error
- [High] media: amlogic: vdec: premature job completion in vp9_finish() races with run()
- [Medium] media: amlogic: vdec: aml_vp9_feed() frees active FIFO before halting AMRISC
- [Low] media: amlogic: vdec: dead IVF harness and file I/O in aml_vp9_save_nv12()
--
commit 068064c3014d8dcbf3ec35bb56cb4ba67e04474c
Author: Christian Stefan Kövér-Draxl <ckoever@outlook.de>
media: amlogic: vdec: add VP9 decoding on the HEVC core
This commit adds stateless V4L2 VP9 decoding support for profile 0 and
profile 2 on the Amlogic S4 HEVC core. It manages the hardware workspace,
reference frame slots, and frame context probability buffers to drive the
VP9 microcode.
> diff --git a/drivers/media/platform/amlogic/vdec/vp9.c b/drivers/media/platform/amlogic/vdec/vp9.c
> new file mode 100644
> index 0000000000000..b67d503b93511
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/vdec/vp9.c
[ ... ]
> +int aml_vp9_feed(struct aml_vdec_hw *hw, const void *data, size_t size)
> +{
> + size_t want = ALIGN(size + VP9_PADDING_SIZE, SZ_64K);
> + u32 end;
> +
> + if (!data || !size)
> + return -EINVAL;
> +
> + if (vp9_ws.stream_vaddr && vp9_ws.stream_size < want) {
> + dma_free_coherent(hw->dev, vp9_ws.stream_size,
> + vp9_ws.stream_vaddr, vp9_ws.stream_paddr);
> + vp9_ws.stream_vaddr = NULL;
> + }
[Severity: Medium]
In aml_vp9_dec_run(), aml_vp9_feed() is called before HEVC_MPSR and
HEVC_CPSR are cleared and before DOS_SW_RESET3 is pulsed. At the end of a
frame, HEVC_STREAM_CONTROL bit 0 (stream fetch enable) and HEVC_MPSR
remain set.
If a subsequent frame is larger and triggers this reallocation in
aml_vp9_feed(), can dma_free_coherent() free vp9_ws.stream_vaddr while
stream fetching is still enabled in HEVC_STREAM_CONTROL and the hardware
FIFO registers still point to the old physical address?
> + if (!vp9_ws.stream_vaddr) {
> + vp9_ws.stream_vaddr = dma_alloc_coherent(hw->dev, want,
> + &vp9_ws.stream_paddr,
> + GFP_KERNEL);
> + if (!vp9_ws.stream_vaddr)
> + return -ENOMEM;
> + vp9_ws.stream_size = want;
> + }
[ ... ]
> + u32 psize = size + VP9_AMLV_HEADER_SIZE;
> + u8 *h = vp9_ws.stream_vaddr;
> +
> + memset(h, 0, VP9_AMLV_HEADER_SIZE);
> + h[0] = psize >> 24;
> + h[1] = psize >> 16;
> + h[2] = psize >> 8;
> + h[3] = psize;
> + h[4] = h[0] ^ 0xff;
> + h[5] = h[1] ^ 0xff;
> + h[6] = h[2] ^ 0xff;
> + h[7] = h[3] ^ 0xff;
> + h[11] = 1;
> + h[12] = 'A';
> + h[13] = 'M';
> + h[14] = 'L';
> + h[15] = 'V';
> + memcpy(vp9_ws.stream_vaddr + VP9_AMLV_HEADER_SIZE, data, size);
> + size += VP9_AMLV_HEADER_SIZE;
> + vp9_ws.payload = size;
> +
> + /*
> + * The end pointer bounds the FIFO, not the payload - ending it at the
> + * payload puts the write pointer outside the ring and the microcode
> + * waits for data that can never arrive. This is the bug that cost the
> + * AV1 path a long time; keep the whole buffer in the ring.
> + */
> + end = vp9_ws.stream_paddr + vp9_ws.stream_size - 1;
> +
> + wr(hw, HEVC_STREAM_START_ADDR, vp9_ws.stream_paddr);
> + wr(hw, HEVC_STREAM_END_ADDR, end);
> + wr(hw, HEVC_STREAM_RD_PTR, vp9_ws.stream_paddr);
> + wr(hw, HEVC_STREAM_WR_PTR,
> + vp9_ws.stream_paddr + vp9_ws.payload + VP9_PADDING_SIZE);
[Severity: High]
At the top of aml_vp9_feed(), the allocation size is calculated without
VP9_AMLV_HEADER_SIZE:
size_t want = ALIGN(size + VP9_PADDING_SIZE, SZ_64K);
After prepending the 16-byte AMLV header, size is incremented by
VP9_AMLV_HEADER_SIZE and stored in vp9_ws.payload, and HEVC_STREAM_WR_PTR
is set to vp9_ws.stream_paddr + vp9_ws.payload + VP9_PADDING_SIZE.
When the original size + VP9_PADDING_SIZE falls within 16 bytes of a
64 KiB boundary (for example, size = 49152 so want = 65536), doesn't
vp9_ws.payload + VP9_PADDING_SIZE reach up to 65552, placing
HEVC_STREAM_WR_PTR past HEVC_STREAM_END_ADDR and up to 16 bytes outside
the allocated vp9_ws.stream_vaddr DMA buffer?
[ ... ]
> +static int vp9_alloc_slots(struct aml_vdec_hw *hw, u32 width, u32 height)
> +{
> + size_t body = ALIGN(vp9_comp_body_size(width, height), PAGE_SIZE);
> + size_t hdr = vp9_hdr_alloc_size(width, height);
> + size_t mv = vp9_mv_buf_size(width, height);
> + size_t dw = vp9_dw_target_y ? 0 : ALIGN(width * height * 3 / 2, SZ_64K);
> + int i;
> +
> + if (vp9_slots[0].body_v && body == vp9_slot_body_size &&
> + hdr == vp9_slot_hdr_size && mv == vp9_slot_mv_size &&
> + dw == vp9_slot_dw_size)
> + return 0;
> +
> + vp9_free_slots(hw);
[Severity: High]
VP9 allows inter frames to change resolution and scale from prior reference
frames, and vp9_config_mc_buffer() sets up reference scaling using each
reference slot's ref->width and ref->height:
for (i = 0; i < VP9_REFS_PER_FRAME; i++) {
struct vp9_slot *ref = &vp9_slots[vp9_frame_slot[i]];
u32 rw = ref->width ? ref->width : width;
u32 rh = ref->height ? ref->height : height;
However, aml_vp9_config_picture() calls vp9_alloc_slots() before calling
vp9_config_pic_list() and vp9_config_mc_buffer(). When an inter frame
changes dimensions such that body, hdr, mv, or dw changes size, won't
vp9_alloc_slots() call vp9_free_slots(hw) and wipe out all 10 vp9_slots[]
buffers along with their stored width and height before the inter frame is
decoded?
[ ... ]
> +int aml_vp9_config_picture(struct aml_vdec_hw *hw)
> +{
> + const u16 *raw;
> + u16 pr[VP9_RPM_COUNT];
> + u32 width, height, lcu_total, uv_size, uv_h;
> + u32 data32, body, header;
> + dma_addr_t dw_y, dw_c;
> + bool inter, intra_only, use_prev_mvs;
> + int i, j, ret;
> +
> + if (!vp9_ws.side_vaddr)
> + return -EINVAL;
> +
> + raw = vp9_ws.side_vaddr + (vp9_ws.rpm - vp9_ws.side_paddr);
> + for (i = 0; i < VP9_RPM_COUNT; i += 4)
> + for (j = 0; j < 4; j++)
> + pr[i + j] = raw[i + 3 - j];
> +
> + width = pr[VP9_RPM_WIDTH];
> + height = pr[VP9_RPM_HEIGHT];
> +
> + /*
> + * The MMU page table, the compressed body and the picture slots are
> + * all sized from these. If the microcode never actually parsed a
> + * header they hold whatever was left in the RPM, and building a page
> + * table from that points the hardware at memory it does not own -
> + * which resets the board outright with no oops to read afterwards.
> + */
> + if (width < VP9_MIN_DIM || height < VP9_MIN_DIM ||
> + width > VP9_MAX_DIM || height > VP9_MAX_DIM) {
> + dev_err(hw->dev, "vp9: implausible picture size %ux%u, refusing\n",
> + width, height);
> + return -EINVAL;
> + }
[Severity: High]
The workspace buffers in vp9_alloc() (such as VP9_SEG_MAP_SIZE = 0xd800 and
VP9_MMU_VBH_SIZE = 0x5000), HEVC_DBLK_CFG3, and the advertised format limit
AML_VDEC_4K_MAX_H (2304) are all sized for a maximum resolution of
4096x2304.
Since VP9_MAX_DIM is 4096, an 8-bit frame with height between 2305 and 4096
(for example, 2160x3840 or 4096x2880, which fits within the 0x1200-page
limit in vp9_fill_mmu_map()) passes this check. Can this cause the hardware
to DMA past the end of vp9_ws.seg_map and vp9_ws.mmu_vbh?
[ ... ]
> + intra_only = (pr[VP9_RPM_SHOW_FRAME] & 1) ? false :
> + (pr[VP9_RPM_INTRA_ONLY] & 1);
> + inter = pr[VP9_RPM_FRAME_TYPE] != VP9_KEY_FRAME && !intra_only;
[ ... ]
> + /* A key frame resets the reference state; otherwise take a slot no
> + * reference names, so decoding cannot destroy a picture still needed.
> + */
> + if (!inter) {
> + vp9_cur_slot = 0;
> + for (i = 0; i < VP9_REF_FRAMES; i++)
> + vp9_ref_slot[i] = 0;
> + } else {
[Severity: High]
Because inter is false for both keyframes and intra-only frames, checking
!inter here causes an intra-only frame to select slot 0 (overwriting the
existing reference picture in slot 0) and reset all 8 entries of
vp9_ref_slot[] to 0.
Per the VP9 specification, intra-only frames only update the reference
slots indicated by refresh_frame_flags (which vp9_pending_refresh preserves
just above) and must leave the remaining reference slots intact for later
inter frames. Should this check vp9_cur_kf instead of !inter?
[ ... ]
> +void aml_vp9_frame_done(struct aml_vdec_hw *hw)
> +{
> + int i;
> +
> + for (i = 0; i < VP9_REF_FRAMES; i++)
> + if (vp9_pending_refresh & (1 << i))
> + vp9_ref_slot[i] = vp9_cur_slot;
> +
> + if (vp9_cur_show)
> + vp9_out_slot = vp9_cur_slot;
> + vp9_prev_slot = vp9_cur_slot;
> + /* The next frame's adaptation needs this frame's type. */
> +}
[Severity: High]
In V4L2 stateless VP9 decoding (V4L2_PIX_FMT_VP9_FRAME), userspace submits
a CAPTURE buffer for every coded frame, including hidden alt-ref frames
where show_frame == 0, and later displays that decoded CAPTURE buffer itself
when show_existing_frame == 1 occurs.
When a hidden alt-ref frame finishes decoding (vp9_cur_show == false):
1. aml_vp9_frame_done() leaves vp9_out_slot at -1 (unlike
aml_av1_frame_done() in av1.c, which checks av1_cur_show || av1_stateless).
2. vp9_copy_out() sees slot < 0 and returns early without copying the
decoded frame (when vp9_dw_target_y == 0) and without setting
vp9->have_picture = true.
3. vp9_finish() sees !vp9->have_picture and sets the CAPTURE buffer plane
payloads to 0, discarding the decoded alt-ref frame.
Conversely, if a frame with show_existing_frame == 1 is submitted while
vp9_dw_target_y is non-zero, vp9_alloc_slots() skipped allocating per-slot
dw_v buffers (dw = 0), aml_vp9_config_picture() sets
vp9_out_slot = vp9_ref_slot[idx] and returns -EAGAIN without running the
decoder, and vp9_copy_out() takes the if (vp9_dw_target_y) branch and marks
the new CAPTURE buffer as having a full payload even though nothing was
written to it.
How are alt-ref frames and show_existing_frame intended to work with
V4L2 stateless CAPTURE buffers here?
[ ... ]
> +int aml_vp9_save_nv12(struct aml_vdec_hw *hw, const char *path, bool append)
> +{
> + const u8 *y;
> + size_t i, nz = 0, len;
> + struct file *f;
> + loff_t pos = 0;
> + int slot = vp9_out_slot;
> +
> + if (slot < 0)
> + return -EAGAIN;
> + vp9_out_slot = -1;
> + y = vp9_slots[slot].dw_v;
> + if (!y)
> + return -ENODEV;
> + dma_sync_single_for_cpu(hw->dev, vp9_slots[slot].dw_p,
> + vp9_slot_dw_size, DMA_FROM_DEVICE);
> +
> + len = (size_t)vp9_slots[slot].width * vp9_slots[slot].height * 3 / 2;
> + for (i = 0; i < len; i++)
> + if (y[i])
> + nz++;
> + dev_info(hw->dev, "vp9: nv12 %zu/%zu non-zero, Y[0..15] %*ph\n",
> + nz, len, 16, y);
> +
> + f = filp_open(path, O_WRONLY | O_CREAT |
> + (append ? O_APPEND : O_TRUNC), 0644);
> + if (IS_ERR(f))
> + return PTR_ERR(f);
> + if (append)
> + pos = i_size_read(file_inode(f));
> + kernel_write(f, y, len, &pos);
> + filp_close(f, NULL);
> + dev_info(hw->dev, "vp9: wrote %zu bytes of NV12 to %s\n", len, path);
> + return 0;
> +}
> +
> +/* ------------------------------------------------------------------------
> + * IVF test harness
> + *
> + * The decoder is frame based and start-code free, so it wants exactly one
> + * frame in front of the shifter at a time. An IVF file is the easiest way to
> + * carry a sequence of them: a 32-byte file header, then a 12-byte length and
> + * timestamp in front of each frame.
> + * ------------------------------------------------------------------------
> + */
> +
> +static u8 *vp9_ivf;
> +static size_t vp9_ivf_size;
> +static size_t vp9_ivf_pos;
> +
> +void aml_vp9_ivf_release(void)
> +{
> + kfree(vp9_ivf);
> + vp9_ivf = NULL;
> + vp9_ivf_size = 0;
> + vp9_ivf_pos = 0;
> +}
> +
> +int aml_vp9_ivf_load(struct aml_vdec_hw *hw, const char *name)
> +{
[Severity: Low]
aml_vp9_save_nv12() performs direct file I/O from kernel space using
filp_open() and kernel_write(), and along with the IVF test harness
(aml_vp9_ivf_load(), aml_vp9_ivf_feed_next(), aml_vp9_ivf_release()) and
debug helpers (aml_vp9_feed_file(), aml_vp9_dump_state(),
aml_vp9_dump_params(), aml_vp9_dump_output(), aml_vp9_reset_core(),
aml_vp9_prot_reinit()) has no callers in the tree.
Additionally, the comments at the top of vp9.c and above aml_vp9_feed() say
there is no AMLV reframing even though aml_vp9_feed() prepends a 16-byte
AMLV header, and vp9_prob.h has a leftover kernel-doc comment for a removed
adaptation function. Could this unused test/debug code be removed and the
comments updated?
[ ... ]
> +static void vp9_set_dw_target(struct aml_vdec_ctx *ctx)
> +{
> + struct v4l2_pix_format_mplane *pf = &ctx->pix_fmt[AML_FMT_DST];
> + struct vb2_v4l2_buffer *dst = v4l2_m2m_next_dst_buf(ctx->m2m_ctx);
> + struct vb2_buffer *vb;
> +
> + vp9_dw_target_y = 0;
> + vp9_dw_target_c = 0;
> + vp9_dw_target_size = 0;
> + if (!dst)
> + return;
> + vb = &dst->vb2_buf;
> + if (pf->plane_fmt[0].bytesperline != pf->width)
> + return;
> +
> + vp9_dw_target_y = vb2_dma_contig_plane_dma_addr(vb, 0);
> + if (!vp9_dw_target_y)
> + return;
> + if (vb->num_planes >= 2) {
> + vp9_dw_target_c = vb2_dma_contig_plane_dma_addr(vb, 1);
> + vp9_dw_target_size = vb2_plane_size(vb, 0) +
> + vb2_plane_size(vb, 1);
> + if (!vp9_dw_target_c) {
> + vp9_dw_target_y = 0;
> + return;
> + }
> + } else {
> + vp9_dw_target_c = vp9_dw_target_y +
> + (dma_addr_t)pf->width * pf->height;
> + vp9_dw_target_size = vb2_plane_size(vb, 0);
> + }
> +}
> +
> +static void vp9_copy_out(struct aml_vp9_ctx *vp9, struct vb2_v4l2_buffer *dst)
> +{
> + struct v4l2_pix_format_mplane *pf =
> + &vp9->v4l2_ctx->pix_fmt[AML_FMT_DST];
> + struct vb2_buffer *vb = &dst->vb2_buf;
> + u32 dst_stride = pf->plane_fmt[0].bytesperline ? : vp9_last_width;
> + u32 dst_h = pf->height ? : vp9_last_height;
> + int slot = vp9_out_slot;
> + const u8 *src;
> + u8 *y, *c;
> + u32 row, w, h;
> +
> + if (slot < 0)
> + return;
> + vp9_out_slot = -1;
> +
> + if (vp9_dw_target_y) {
> + if (vb->num_planes >= 2) {
> + vb2_set_plane_payload(vb, 0, dst_stride * dst_h);
> + vb2_set_plane_payload(vb, 1, dst_stride * dst_h / 2);
> + } else {
> + vb2_set_plane_payload(vb, 0,
> + dst_stride * dst_h * 3 / 2);
> + }
> + vp9->have_picture = true;
> + return;
> + }
> +
> + src = vp9_slots[slot].dw_v;
> + if (!src)
> + return;
> + dma_sync_single_for_cpu(vp9->hw->dev, vp9_slots[slot].dw_p,
> + vp9_slot_dw_size, DMA_FROM_DEVICE);
> +
> + w = vp9_slots[slot].width;
> + h = vp9_slots[slot].height;
> + y = vb2_plane_vaddr(vb, 0);
> + if (!y)
> + return;
> + c = vb->num_planes >= 2 ? vb2_plane_vaddr(vb, 1) :
> + y + (size_t)dst_stride * dst_h;
> + if (!c)
> + return;
> +
> + for (row = 0; row < h; row++)
> + memcpy(y + (size_t)row * dst_stride, src + (size_t)row * w, w);
> + for (row = 0; row < h / 2; row++)
> + memcpy(c + (size_t)row * dst_stride,
> + src + (size_t)(h + row) * w, w);
[Severity: Critical]
Can this corrupt CAPTURE buffer memory when the coded frame dimensions in
the VP9 bitstream do not match the negotiated CAPTURE format?
1. When vp9_dw_target_y == 0 (for instance, if bytesperline != pf->width),
aml_vp9_config_picture() does not check that the parsed frame's width
and height fit within the CAPTURE buffer. In vp9_copy_out(), the loops
copy h rows of w bytes into y and h / 2 rows of w bytes into c without
checking that w <= dst_stride, h <= dst_h, or that the destination
vb2_buffer planes are large enough, which can overflow the CAPTURE
planes via memcpy().
2. When vp9_dw_target_y != 0, vp9_set_dw_target() computes vp9_dw_target_c
from pf->width * pf->height for single-plane NV12 and sums both plane
sizes into a single vp9_dw_target_size for two-plane NV12M before the
bitstream dimensions are parsed. In aml_vp9_config_picture(), the
hardware is configured for 64-byte line alignment (ALIGN(width, 64)),
but only (size_t)width * height * 3 / 2 > vp9_dw_target_size is checked:
if (vp9_dw_target_y) {
if ((size_t)width * height * 3 / 2 > vp9_dw_target_size) {
...
return -EINVAL;
}
dw_y = vp9_dw_target_y;
dw_c = vp9_dw_target_c;
}
If ALIGN(width, 64) != pf->width or height > pf->height while
width * height * 3 / 2 <= vp9_dw_target_size (or if plane 0 of an NV12M
buffer is smaller than ALIGN(width, 64) * height), won't the double-write
unit DMA past the end of the CAPTURE buffer planes?
[ ... ]
> +static void vp9_finish(struct aml_vp9_ctx *vp9, enum vb2_buffer_state state)
> +{
> + struct aml_vdec_ctx *ctx = vp9->v4l2_ctx;
> + struct vb2_v4l2_buffer *buf;
> + unsigned int i;
> +
> + if (!vp9->have_picture) {
> + buf = v4l2_m2m_next_dst_buf(ctx->m2m_ctx);
> + if (buf)
> + for (i = 0; i < buf->vb2_buf.num_planes; i++)
> + vb2_set_plane_payload(&buf->vb2_buf, i, 0);
> + }
> +
> + buf = v4l2_m2m_next_src_buf(ctx->m2m_ctx);
> + if (buf && buf->vb2_buf.req_obj.req)
> + v4l2_ctrl_request_complete(buf->vb2_buf.req_obj.req,
> + &ctx->ctrl_handler);
> +
> + vp9_finish_frame_ctx(vp9);
> +
> + ctx->int_cond = 1;
> + ctx->job_finished = true;
> + v4l2_m2m_buf_done_and_job_finish(ctx->dev->m2m_dev_dec, ctx->m2m_ctx,
> + state);
> + wake_up(&ctx->queue);
> +}
[Severity: High]
Does calling v4l2_m2m_buf_done_and_job_finish() here in vp9_finish() race
with aml_vp9_dec_run() and m2mops_vdec_device_run()?
m2mops_vdec_device_run() calls ctx->codec_ops->run(ctx) (aml_vp9_dec_run()),
which blocks in wait_event_timeout(ctx->queue, ctx->int_cond, ...). When
vp9_irq_thread() calls vp9_finish(), v4l2_m2m_buf_done_and_job_finish()
completes src_buf and dst_buf, clears TRANS_RUNNING, and can start the next
m2m job or unblock a concurrent v4l2_m2m_cancel_job() in streamoff/close
before wake_up(&ctx->queue) runs and before aml_vp9_dec_run() returns:
drivers/media/platform/amlogic/vdec/aml_vdec.c:m2mops_vdec_device_run() {
...
ctx->job_finished = false;
ret = ctx->codec_ops->run(ctx);
v4l2_m2m_buf_copy_metadata(src_buf, dst_buf);
...
}
When aml_vp9_dec_run() wakes up, it writes ctx->int_cond = 0 (which can
clobber the next job's state), m2mops_vdec_device_run() calls
v4l2_m2m_buf_copy_metadata(src_buf, dst_buf) on buffers already returned to
vb2/userspace, and if streamoff/close ran after TRANS_RUNNING was cleared,
ctx and vp9 may already be freed.
> +
> +static irqreturn_t vp9_irq_thread(int irq, void *priv)
> +{
> + struct aml_vp9_ctx *vp9 = priv;
> + struct aml_vdec_ctx *ctx = vp9->v4l2_ctx;
> + struct aml_vdec_hw *hw = vp9->hw;
> + u32 st = hw->hevc_irq_status & 0xff;
> + u32 next = VP9_10B_DECODE_SLICE;
[ ... ]
> + case VP9_NAL_DECODE_DONE:
> + case HEVC_ACTION_DONE:
> + case VP9_DECODE_BUFEMPTY:
> + case VP9_SEARCH_BUFEMPTY:
> + case VP9_EOS:
> + vp9_finish(vp9, VB2_BUF_STATE_DONE);
> + return IRQ_HANDLED;
> + default:
> + dev_dbg(hw->dev, "vp9: unhandled status %02x\n", st);
> + break;
> + }
> +
> + regmap_write(hw->map[DOS_BUS], HEVC_ASSIST_MBOX0_CLR_REG, 1);
> + regmap_write(hw->map[DOS_BUS], HEVC_DEC_STATUS_REG, next);
> + return IRQ_HANDLED;
> +}
[Severity: High]
In vp9_irq_thread(), next is initialized to VP9_10B_DECODE_SLICE. If the
microcode posts any status code that hits the default branch (such as
VP9_HEAD_SEARCH_DONE, VP9_DECPIC_DATA_ERROR, or VP9_DECODE_OVER_SIZE)
before VP9_HEAD_PARSER_DONE has run aml_vp9_config_picture(), won't writing
next to HEVC_DEC_STATUS_REG command the microcode to decode a slice using
unconfigured or stale DMA addresses?
[ ... ]
> +void aml_vp9_exit(void *priv)
> +{
> + struct aml_vdec_ctx *ctx = priv;
> + struct aml_vp9_ctx *vp9 = ctx->codec_priv;
> +
> + if (!vp9)
> + return;
> +
> + vp9->hw->hevc_irq_thread_fn = NULL;
> + vp9->hw->hevc_priv = NULL;
> + if (vp9->started) {
> + regmap_write(vp9->hw->map[DOS_BUS], HEVC_MPSR, 0);
> + aml_vp9_teardown(vp9->hw);
> + /*
> + * Deliberately not hvdec_disable(): powering the HEVC core
> + * down here leaves it in a state the next context's bring-up
> + * does not recover from, and every frame of the *second*
> + * decode session on this core then times out - whichever
> + * codec it is. The domain is left up until the module is
> + * unloaded.
> + */
> + }
> + kfree(vp9);
> + ctx->codec_priv = NULL;
> +}
[Severity: Critical]
Can aml_vp9_exit() race with an in-flight threaded interrupt or a second
context opening the decoder?
1. hevc_irq_thread() in aml_vdec_hw.c reads hw->hevc_irq_thread_fn and
hw->hevc_priv without synchronization:
if (hw->hevc_irq_thread_fn)
return hw->hevc_irq_thread_fn(irq, hw->hevc_priv);
Because aml_vp9_exit() clears hevc_irq_thread_fn and hevc_priv before
halting the hardware and never calls synchronize_irq(vp9->hw->hevc_irq),
an in-flight threaded IRQ can either call vp9_irq_thread(irq, NULL) or
continue running vp9_irq_thread() while aml_vp9_teardown() and
kfree(vp9) free vp9_ws, vp9_slots, and vp9.
2. Clearing vp9->hw->hevc_priv = NULL before aml_vp9_teardown(vp9->hw)
allows another thread in aml_vp9_init() to succeed at
cmpxchg(&hw->hevc_priv, NULL, vp9) and access the global vp9_ws and
vp9_slots while aml_vp9_teardown() is freeing them.
> +
> +int aml_vp9_dec_run(void *priv)
> +{
[ ... ]
> + if (!vp9->started) {
> + ret = aml_vp9_bringup(hw, ctx->pix_fmt[AML_FMT_SRC].width,
> + ctx->pix_fmt[AML_FMT_SRC].height);
> + if (ret) {
> + hvdec_disable(hw);
> + return ret;
> + }
> + }
> +
> + vp9_prepare_frame_ctx(vp9, dec, hdr);
> + vp9_set_dw_target(ctx);
> +
> + ret = aml_vp9_feed(hw, vb2_plane_vaddr(vb, 0),
> + vb2_get_plane_payload(vb, 0));
> + if (ret)
> + return ret;
[ ... ]
> + if (!ret) {
> + int k;
> +
> + dev_err(hw->dev, "vp9: decode timeout, status %02x pc %08x\n",
> + hw->hevc_irq_status, read_dos_reg(hw, HEVC_MPC_E));
> + dev_err(hw->dev,
> + "vp9: dec_status %08x adapt %08x parser_ctl %08x shift %08x\n",
> + read_dos_reg(hw, HEVC_DEC_STATUS_REG),
> + read_dos_reg(hw, VP9_ADAPT_PROB_REG),
> + read_dos_reg(hw, REG_ALIGN(0x3050)),
> + read_dos_reg(hw, HEVC_SHIFT_BYTE_COUNT));
> + for (k = 0; k < 16; k++)
> + dev_err(hw->dev, "vp9: scratch[%x] %08x\n", k,
> + read_dos_reg(hw, REG_ALIGN(0x30c0 + k)));
> + /*
> + * After a timeout the core decodes every later frame to garbage
> + * - in this session and the next ones - until its memories are
> + * cycled. Have the next frame cycle them, reload the microcode
> + * and bring the decoder up again; the stream picks up at its
> + * next key frame.
> + */
> + hw->hevc_dirty = true;
> + vp9->ucode_loaded = false;
> + vp9->started = false;
> + ret = -ETIMEDOUT;
> + } else {
> + ret = 0;
> + }
[Severity: High]
There seem to be two issues when aml_vp9_dec_run() fails or times out:
1. Does this leak the DMA buffers in vp9_ws and vp9_slots[]?
In aml_vp9_exit(), aml_vp9_teardown(vp9->hw) is only called if
vp9->started is true. However, on timeout here, vp9->started is set to
false without calling aml_vp9_teardown(). Similarly, if aml_vp9_feed()
fails on the first frame after aml_vp9_bringup() has allocated vp9_ws,
aml_vp9_dec_run() returns early before vp9->started is set to true. If
the user then closes the context, aml_vp9_exit() skips
aml_vp9_teardown(), leaking vp9_ws.vaddr, vp9_ws.side_vaddr,
vp9_ws.stream_vaddr, and all 10 vp9_slots[] allocations (as well as
leaving hvdec_enable(hw) unbalanced on every frame).
2. On timeout, aml_vp9_dec_run() returns -ETIMEDOUT without stopping the
AMRISC (HEVC_MPSR / HEVC_CPSR), disabling stream fetch
(HEVC_STREAM_CONTROL), masking HEVC_ASSIST_MBOX0_MASK, or synchronizing
with hw->hevc_irq. When m2mops_vdec_device_run() sees the error return,
it immediately completes the CAPTURE buffer with VB2_BUF_STATE_ERROR and
finishes the job. Can the still-running hardware continue DMA-writing
into vp9_dw_target_y after the CAPTURE buffer is returned to userspace,
or raise a late interrupt that calls vp9_finish() a second time?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261025192507.210058-1-christian.koever-draxl@student.uibk.ac.at?part=8
next prev parent reply other threads:[~2026-10-25 20:23 UTC|newest]
Thread overview: 20+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-25 19:24 [PATCH 00/11] media: amlogic: vdec: S4 decoding beyond H.264 Baseline christian.koever-draxl
2026-10-25 19:24 ` [PATCH 01/11] MAINTAINERS: fix the Amlogic VDEC binding path christian.koever-draxl
2026-10-25 19:24 ` [PATCH 02/11] dt-bindings: media: amlogic,s4-vdec: describe both decoder cores christian.koever-draxl
2026-10-25 19:24 ` [PATCH 03/11] media: amlogic: vdec: fix the microcode loading and prepare the HEVC core christian.koever-draxl
2026-10-25 20:25 ` sashiko-bot
2026-10-25 19:25 ` [PATCH 04/11] media: amlogic: vdec: h264: decode Main and High profile bit-exact christian.koever-draxl
2026-10-25 19:25 ` [PATCH 05/11] media: amlogic: vdec: add MPEG-1/2 decoding christian.koever-draxl
2026-10-25 20:28 ` sashiko-bot
2026-10-25 19:25 ` [PATCH 06/11] media: amlogic: vdec: add MJPEG decoding christian.koever-draxl
2026-10-25 20:20 ` sashiko-bot
2026-10-25 19:25 ` [PATCH 07/11] media: amlogic: vdec: add AV1 decoding on the HEVC core christian.koever-draxl
2026-10-25 20:27 ` sashiko-bot
2026-10-25 19:25 ` [PATCH 08/11] media: amlogic: vdec: add VP9 " christian.koever-draxl
2026-10-25 20:23 ` sashiko-bot [this message]
2026-10-25 19:25 ` [PATCH 09/11] media: amlogic: vdec: add HEVC " christian.koever-draxl
2026-10-25 20:26 ` sashiko-bot
2026-10-25 19:25 ` [PATCH 10/11] media: amlogic: vdec: add MPEG-4 Part 2 decoding christian.koever-draxl
2026-10-25 20:39 ` sashiko-bot
2026-10-25 19:25 ` [PATCH 11/11] media: amlogic: vdec: give the regmaps their own names christian.koever-draxl
2026-10-26 5:53 ` [PATCH 00/11] media: amlogic: vdec: S4 decoding beyond H.264 Baseline Zhentao Guo
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-184424@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=christian.koever-draxl@student.uibk.ac.at \
--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