From 89e658c75ad36c73bfa2f872ac020e8a686d9229 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Sun, 1 Nov 2026 04:59:04 +1100 Subject: [PATCH 1/6] media: apple: avd: bound AV1 reference indices before using them ref_frame_idx entries are -1 on frames that use no references (FFmpeg fills key frames that way), and the AV1 builder indexed reference_frame_ts with them directly: UBSAN reported index -1 into reference_frame_ts[8] in set_header and its helpers on the first AV1 decode on the J616s and the Neo. Look the timestamp up through a helper that returns a value no buffer has for an out-of-range index, so the lookup takes the existing missing-reference path. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/media/platform/apple/avd/avd-av1.c | 46 +++++++++++++--------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/drivers/media/platform/apple/avd/avd-av1.c b/drivers/media/platform/apple/avd/avd-av1.c index 60d69bab8a342..64b441be90ffb 100644 --- a/drivers/media/platform/apple/avd/avd-av1.c +++ b/drivers/media/platform/apple/avd/avd-av1.c @@ -246,6 +246,19 @@ struct avd_av1_ctx { } bufs; }; +/* + * Timestamp of the reference a ref_frame_idx entry names. The entries are + * -1 on frames that use no references (FFmpeg fills key frames that way); + * indexing reference_frame_ts with them read out of bounds. Return a + * timestamp no buffer has, so the lookup takes the missing-reference path. + */ +static u64 avd_av1_ref_ts(const struct v4l2_ctrl_av1_frame *frame, int idx) +{ + if (idx < 0 || idx >= V4L2_AV1_TOTAL_REFS_PER_FRAME) + return U64_MAX; + return frame->reference_frame_ts[idx]; +} + /* * from verisilicon/rockchip_vpu981_hw_av1_dec.c */ @@ -367,8 +380,7 @@ static void set_refs(struct avd_ctx *ctx, struct avd_av1_run *run) ref_idx = i + V4L2_AV1_REF_LAST_FRAME; ref = avd_get_ref_buf( ctx, &dst->base.vb, - frame->reference_frame_ts - [frame->ref_frame_idx[i]]); + avd_av1_ref_ts(frame, frame->ref_frame_idx[i])); } addr = vb2_dma_contig_plane_dma_addr(&ref->base.vb.vb2_buf, 0) + ref->comp.start_offset; @@ -455,9 +467,8 @@ static bool is_valid_ref_frame(struct avd_ctx *ctx, struct avd_av1_run *run, const struct v4l2_ctrl_av1_frame *frame = run->frame; struct avd_decoded_buffer *ref = avd_get_ref_buf( ctx, &dst->base.vb, - frame->reference_frame_ts - [frame->ref_frame_idx[ref_frame - - V4L2_AV1_REF_LAST_FRAME]]); + avd_av1_ref_ts(frame, frame->ref_frame_idx[ref_frame - + V4L2_AV1_REF_LAST_FRAME])); int ref_mi_cols = DIV_ROUND_UP(ref->av1.width + 1, 8); int ref_mi_rows = DIV_ROUND_UP(ref->av1.height + 1, 8); bool intra_only = ((ref->av1.frame_type == V4L2_AV1_KEY_FRAME) || @@ -485,7 +496,7 @@ static int avd_select_refs(struct avd_ctx *ctx, struct avd_av1_run *run, if (seq->flags & (V4L2_AV1_SEQUENCE_FLAG_ENABLE_ORDER_HINT | V4L2_AV1_SEQUENCE_FLAG_ENABLE_REF_FRAME_MVS)) { ref = avd_get_ref_buf(ctx, &dst->base.vb, - frame->reference_frame_ts[last_ref_idx]); + avd_av1_ref_ts(frame, last_ref_idx)); if (dst != ref) { use_last = (ref->av1.order_hints[V4L2_AV1_REF_ALTREF_FRAME] != @@ -554,7 +565,7 @@ static void set_ref_hints(struct avd_ctx *ctx, struct avd_av1_run *run, V4L2_AV1_REF_LAST_FRAME]; ref = avd_get_ref_buf( ctx, &dst->base.vb, - frame->reference_frame_ts[ref_idx]); + avd_av1_ref_ts(frame, ref_idx)); push(AV1_ORDER_HINT0(ref->av1.order_hints[3]) | AV1_ORDER_HINT1(ref->av1.order_hints[2]) | @@ -585,8 +596,8 @@ static void set_ref_hdr(struct avd_ctx *ctx, struct avd_av1_run *run) /* point to the first index where the frames equal */ for (i = 0; i < 6; i++) { for (j = i + 1; j < 7; j++) { - if (frame->reference_frame_ts[frame->ref_frame_idx[i]] == - frame->reference_frame_ts[frame->ref_frame_idx[j]]) { + if (avd_av1_ref_ts(frame, frame->ref_frame_idx[i]) == + avd_av1_ref_ts(frame, frame->ref_frame_idx[j])) { ref_slots[j] = ref_slots[i]; break; } @@ -831,7 +842,7 @@ static void set_header(struct avd_ctx *ctx, struct avd_av1_run *run) V4L2_AV1_REF_LAST_FRAME]; ref = avd_get_ref_buf( ctx, &dst->base.vb, - frame->reference_frame_ts[ref_idx]); + avd_av1_ref_ts(frame, ref_idx)); ref_addr = vb2_dma_contig_plane_dma_addr( &ref->base.vb.vb2_buf, 0) + @@ -1075,9 +1086,8 @@ static void avd_av1_set_prob(struct avd_ctx *ctx, struct avd_av1_run *run) } else { ref = avd_get_ref_buf( ctx, &dst->base.vb, - run->frame->reference_frame_ts - [run->frame->ref_frame_idx - [run->frame->primary_ref_frame]]); + avd_av1_ref_ts(run->frame, + run->frame->ref_frame_idx[run->frame->primary_ref_frame])); memcpy(av1_ctx->bufs.probs.cpu, vb2_plane_vaddr(&ref->base.vb.vb2_buf, 0) + AVD_AV1_CDFS_OFFSET( @@ -1118,8 +1128,8 @@ static void avd_av1_set_seg(struct avd_ctx *ctx, struct avd_av1_run *run) if (frame->primary_ref_frame != AV1_PRIMARY_REF_NONE) { ref = avd_get_ref_buf( ctx, &dst->base.vb, - frame->reference_frame_ts - [frame->ref_frame_idx[frame->primary_ref_frame]]); + avd_av1_ref_ts(frame, + frame->ref_frame_idx[frame->primary_ref_frame])); /* avd_get_ref_buf() returns dst for a missing reference */ /* same MiCols and MiRows, as libdav1d compares them */ if (ref == dst || @@ -1384,9 +1394,9 @@ static int avd_av1_run(struct avd_ctx *ctx) ref = dst; if (run.frame->primary_ref_frame != AV1_PRIMARY_REF_NONE) ref = avd_get_ref_buf(ctx, &dst->base.vb, - run.frame->reference_frame_ts - [run.frame->ref_frame_idx - [run.frame->primary_ref_frame]]); + avd_av1_ref_ts(run.frame, + run.frame->ref_frame_idx + [run.frame->primary_ref_frame])); if (!vb2_plane_vaddr(&dst->base.vb.vb2_buf, 0) || !vb2_plane_vaddr(&ref->base.vb.vb2_buf, 0)) { avd_run_postamble(ctx, &run.base); From 752a3701719ce119f10968d0ad23255b6bbd8816 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Sun, 1 Nov 2026 05:00:24 +1100 Subject: [PATCH 2/6] media: apple: avd: do not pop an empty firmware mailbox The interrupt handler read MBOX1_RETRIEVE without checking that the mailbox held a message. Reading an empty mailbox returns the previous message again, so a spurious or early interrupt replayed the last PP DONE: the next frame finished before its PP had run, userspace got a wrong picture, the frame's own PP DONE never came and the watchdog reset the decoder. Multi-stream H.264 benchmarks on the J616s hit this as bit-exactness failures, "ignoring stale completion" and timeouts. Check the empty flag in MBOX1_STATUS (bit 17) first, give a message that is not readable yet up to 10 us, and otherwise acknowledge and ignore the interrupt. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/media/platform/apple/avd/avd-drv.c | 14 ++++++++++++++ drivers/media/platform/apple/avd/avd-regs.h | 2 ++ 2 files changed, 16 insertions(+) diff --git a/drivers/media/platform/apple/avd/avd-drv.c b/drivers/media/platform/apple/avd/avd-drv.c index bf5bf96f4c62a..65cecd267b545 100644 --- a/drivers/media/platform/apple/avd/avd-drv.c +++ b/drivers/media/platform/apple/avd/avd-drv.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -300,6 +301,19 @@ static irqreturn_t avd_irq_handler(int irq, void *data) if (!ctx) return IRQ_HANDLED; + /* + * Reading RETRIEVE on an empty mailbox returns the previous message + * again. A repeated PP DONE finishes the next frame before its PP has + * run: userspace gets a wrong picture, the frame's own PP DONE never + * comes, and the watchdog resets the decoder. Give a late message + * 10 us, otherwise acknowledge and ignore the interrupt. + */ + if (readl_poll_timeout_atomic(avd->mbox + AVD_REG_MBOX1_STATUS, status, + !(status & AVD_MBOX1_STATUS_EMPTY), 1, 10)) { + writel(AVD_MBOX1_NOT_EMPTY, avd->mbox + AVD_REG_MBOX_IRQ_CLR); + dev_dbg_ratelimited(avd->dev, "spurious interrupt, mailbox empty\n"); + return IRQ_HANDLED; + } status = readl(avd->mbox + AVD_REG_MBOX1_RETRIEVE); writel(AVD_MBOX1_NOT_EMPTY, avd->mbox + AVD_REG_MBOX_IRQ_CLR); diff --git a/drivers/media/platform/apple/avd/avd-regs.h b/drivers/media/platform/apple/avd/avd-regs.h index 388f3e64895f5..ace0e4c48f40a 100644 --- a/drivers/media/platform/apple/avd/avd-regs.h +++ b/drivers/media/platform/apple/avd/avd-regs.h @@ -13,6 +13,8 @@ #define AVD_MBOX1_NOT_EMPTY BIT(3) #define AVD_REG_MBOX1_STATUS 0x5c +/* Read side: bits 8-11 write pointer, bits 12-15 read pointer, bit 17 empty. */ +#define AVD_MBOX1_STATUS_EMPTY BIT(17) #define AVD_REG_MBOX1_RETRIEVE 0x64 #define AVD_MBOX_ENABLE BIT(0) From fb2b82403531651766b49dcdf07380e0a02edcb4 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Sun, 1 Nov 2026 05:00:29 +1100 Subject: [PATCH 3/6] media: apple: avd: finish a job only on the PP DONE of its own PP The firmware reports VP DONE, the driver kicks PP, and PP DONE finishes the job. HEVC, VP9 and AV1 pictures with several segments kick PP once per segment, and a surplus PP DONE could arrive after the next job was submitted and finish it before its VP had even completed. Its buffers then went back to userspace while the decoder was still writing them; the J616s logged DART write faults at just-released addresses in such a run. Remember per job that its VP finished and its PP was kicked, and treat a PP DONE before that as stale. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/media/platform/apple/avd/avd-drv.c | 13 ++++++++++++- drivers/media/platform/apple/avd/avd.h | 2 ++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/drivers/media/platform/apple/avd/avd-drv.c b/drivers/media/platform/apple/avd/avd-drv.c index 65cecd267b545..52a687ce4ba85 100644 --- a/drivers/media/platform/apple/avd/avd-drv.c +++ b/drivers/media/platform/apple/avd/avd-drv.c @@ -331,6 +331,10 @@ static irqreturn_t avd_irq_handler(int irq, void *data) /* a vp is done, kick the pp and hope for the best */ if(ctx->coded_fmt_desc->ops->submit) ctx->coded_fmt_desc->ops->submit(ctx); + spin_lock_irqsave(&avd->job_lock, flags); + if (avd->job_ctx == ctx) + avd->job_vp_done = true; + spin_unlock_irqrestore(&avd->job_lock, flags); goto done; } else { dev_err(avd->dev, "H%d error", status); @@ -352,7 +356,13 @@ static irqreturn_t avd_irq_handler(int irq, void *data) * let avd_device_run() finish the job when it returns. */ spin_lock_irqsave(&avd->job_lock, flags); - if (avd->job_ctx != ctx) { + if (avd->job_ctx != ctx || !avd->job_vp_done) { + /* + * Only the PP kicked for this job's VP can finish it. A surplus + * PP DONE of a multi-kick picture used to finish the next job + * early, so userspace reused its buffers while the decoder was + * still writing them. + */ stale = true; } else if (avd->job_state == AVD_JOB_SUBMITTED) { avd->job_state = AVD_JOB_FINISHING; @@ -415,6 +425,7 @@ static void avd_device_run(void *priv) avd->job_ctx = ctx; avd->job_state = AVD_JOB_RUNNING; avd->job_end_sent = false; + avd->job_vp_done = false; avd->job_pending = false; spin_unlock_irqrestore(&avd->job_lock, flags); dev_dbg(avd->dev, "avd-job: run seq %llu\n", seq); diff --git a/drivers/media/platform/apple/avd/avd.h b/drivers/media/platform/apple/avd/avd.h index ad7accb6e9615..2a1d914bd9d71 100644 --- a/drivers/media/platform/apple/avd/avd.h +++ b/drivers/media/platform/apple/avd/avd.h @@ -229,6 +229,8 @@ struct avd_dev { struct avd_ctx *job_ctx; u64 job_seq; bool job_end_sent; + /* The current job's VP finished and its PP was kicked. */ + bool job_vp_done; bool job_pending; enum vb2_buffer_state job_pending_result; From b6104a4ff2a3842ef8aeef323b82e140e8ef21c4 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Sun, 1 Nov 2026 05:00:32 +1100 Subject: [PATCH 4/6] media: apple: avd: quiesce the decoder before a reset and retire it after repeated ones The J616s hung hard three times in one night, each time after decoder timeouts and resets, and the Neo hung after resetting once per frame during a burst of VP errors. The reset asserted the pmgr reset with the CM3 still running, with DMA still able to reach memory, and while the threaded interrupt handler could be reading the mailbox; nothing bounded how often it ran. Before resetting: disable the interrupt (waiting for a running handler), stop the CM3, attach the empty IOMMU domain so a transfer still in flight faults at the DART instead of writing memory that may already be reused, and wait 5 ms for such transfers to drain. Then reset, restore and check the DMA domain, and boot. Each step is logged before it runs. Recover at most twice a minute and at least 20 ms apart. A failed recovery or a third one within a minute retires the decoder until the module is reloaded. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/media/platform/apple/avd/avd-drv.c | 80 +++++++++++++++++++--- drivers/media/platform/apple/avd/avd.h | 6 ++ 2 files changed, 76 insertions(+), 10 deletions(-) diff --git a/drivers/media/platform/apple/avd/avd-drv.c b/drivers/media/platform/apple/avd/avd-drv.c index 52a687ce4ba85..1824c3de61ed4 100644 --- a/drivers/media/platform/apple/avd/avd-drv.c +++ b/drivers/media/platform/apple/avd/avd-drv.c @@ -223,29 +223,88 @@ int avd_submit_job(struct avd_ctx *ctx) return 0; } +/* Recover at most twice a minute, never twice within this gap. */ +#define AVD_RECOVER_MAX 2 +#define AVD_RECOVER_WINDOW msecs_to_jiffies(60000) +#define AVD_RECOVER_GAP_MS 20 + +/* + * Reset the decoder after a timeout or a VP error. Every reset is a risk + * to the whole machine (the J616s hung hard after timeouts and resets + * three times in one night, and the Neo after a burst of one reset per + * frame), so: stop the interrupt handler and the CM3 first, block the + * decoder's DMA at its DART so a transfer still in flight faults instead + * of reaching memory that may already be reused, give such transfers time + * to drain, and only then assert the pmgr reset. Recover sparingly, and + * retire the decoder after a failed or a third recovery within a minute. + */ static int avd_reset(struct avd_dev *avd) { int ret = 0; + if (avd->retired) + return -ENODEV; + if (!avd->recoveries || + time_after(jiffies, avd->recover_window_start + AVD_RECOVER_WINDOW)) { + avd->recover_window_start = jiffies; + avd->recoveries = 0; + } + if (++avd->recoveries > AVD_RECOVER_MAX) { + ret = -EBUSY; + goto retire; + } + if (avd->last_recover && + time_before(jiffies, avd->last_recover + msecs_to_jiffies(AVD_RECOVER_GAP_MS))) + msleep(AVD_RECOVER_GAP_MS); + ret = pm_runtime_resume_and_get(avd->dev); if (ret < 0) - return ret; + goto retire; + /* No handler may touch the mailbox while the block is in reset. */ + disable_irq(avd->irq); + dev_warn(avd->dev, "recover: stop firmware\n"); + avd_shutdown(avd); + if (avd->empty_domain) { + dev_warn(avd->dev, "recover: block DMA\n"); + ret = iommu_attach_device(avd->empty_domain, avd->dev); + if (ret) { + dev_err(avd->dev, "recover: DART empty attach failed: %d\n", ret); + goto put; + } + usleep_range(5000, 6000); + } + dev_warn(avd->dev, "recover: reset\n"); ret = reset_control_reset(avd->rstc); if (ret) - dev_err(avd->dev, "reset: failed: %d", ret); - + dev_err(avd->dev, "recover: reset failed: %d\n", ret); if (avd->empty_domain) { - iommu_attach_device(avd->empty_domain, avd->dev); + dev_warn(avd->dev, "recover: restore DMA\n"); iommu_detach_device(avd->empty_domain, avd->dev); + if (avd->domain && iommu_get_domain_for_dev(avd->dev) != avd->domain) { + dev_err(avd->dev, "recover: DART domain not restored\n"); + ret = -EIO; + } + } + if (!ret) { + dev_warn(avd->dev, "recover: boot firmware\n"); + ret = avd_boot(avd); + if (ret) + dev_err(avd->dev, "recover: boot failed: %d\n", ret); + } +put: + avd->last_recover = jiffies ?: 1; + if (!ret) { + enable_irq(avd->irq); + dev_warn(avd->dev, "recover: done\n"); } - - ret = avd_boot(avd); - if (ret) - dev_err(avd->dev, "reset: failed to boot"); - pm_runtime_put_autosuspend(avd->dev); - + if (!ret) + return 0; +retire: + avd->retired = true; + dev_err(avd->dev, "decoder retired after %s (%d): reload the module to use it again\n", + ret == -EBUSY ? "repeated resets" : "a failed reset", ret); return ret; } @@ -979,6 +1038,7 @@ static int avd_probe(struct platform_device *pdev) irq = platform_get_irq(pdev, 1); if (irq < 0) return irq; + avd->irq = irq; ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, avd_irq_handler, IRQF_ONESHOT, dev_name(&pdev->dev), avd); diff --git a/drivers/media/platform/apple/avd/avd.h b/drivers/media/platform/apple/avd/avd.h index 2a1d914bd9d71..98010d1fb0786 100644 --- a/drivers/media/platform/apple/avd/avd.h +++ b/drivers/media/platform/apple/avd/avd.h @@ -235,6 +235,12 @@ struct avd_dev { enum vb2_buffer_state job_pending_result; struct reset_control *rstc; + int irq; + /* Recovery budget; a retired decoder fails every job until reload. */ + unsigned long recover_window_start; + unsigned int recoveries; + unsigned long last_recover; + bool retired; const struct avd_variant *variant; unsigned int capabilities; From 68be6ab98e85052afd4b3a3a874148fb580bfd42 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Sun, 1 Nov 2026 05:00:35 +1100 Subject: [PATCH 5/6] media: apple: avd: stop a stream after a frame fails in hardware A stream whose frame timed out or hit a VP error usually fails the next frame the same way. The Neo reset the decoder once per frame through a broken HEVC stream until it hung. After such a failure, finish every job of that stream with an error without touching the hardware, completing the request controls so the request fds still signal, until streaming is restarted. A retired decoder fails every job the same way. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/media/platform/apple/avd/avd-drv.c | 16 ++++++++++++++++ drivers/media/platform/apple/avd/avd-v4l2.c | 4 ++++ drivers/media/platform/apple/avd/avd.h | 2 ++ 3 files changed, 22 insertions(+) diff --git a/drivers/media/platform/apple/avd/avd-drv.c b/drivers/media/platform/apple/avd/avd-drv.c index 1824c3de61ed4..462df919265d5 100644 --- a/drivers/media/platform/apple/avd/avd-drv.c +++ b/drivers/media/platform/apple/avd/avd-drv.c @@ -344,6 +344,12 @@ static void avd_watchdog_func(struct work_struct *work) ret = avd_reset(avd); if (ret) dev_err(avd->dev, "failed to reset: %d", ret); + /* + * A stream whose frame failed in hardware usually fails the next one + * too (a reset per frame took the Neo down), so stop it here. + */ + ctx->broken = true; + dev_err(avd->dev, "stream stopped after a failed frame; restart streaming to retry\n"); avd_job_finish(ctx, VB2_BUF_STATE_ERROR); } @@ -461,6 +467,16 @@ static void avd_device_run(void *priv) if (WARN_ON(!desc)) return; + if (READ_ONCE(avd->retired) || ctx->broken) { + struct vb2_v4l2_buffer *src = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + + /* Complete the request's controls too, or its fd never signals. */ + if (src && src->vb2_buf.req_obj.req) + v4l2_ctrl_request_complete(src->vb2_buf.req_obj.req, &ctx->ctrl_hdl); + avd_job_finish_no_pm(ctx, VB2_BUF_STATE_ERROR); + return; + } + /* * IRQ completion cannot wait for an already running watchdog. Drain it * here, before reusing wd_seq, so an old callback cannot time out this diff --git a/drivers/media/platform/apple/avd/avd-v4l2.c b/drivers/media/platform/apple/avd/avd-v4l2.c index 4622e92055486..23917fd62bdca 100644 --- a/drivers/media/platform/apple/avd/avd-v4l2.c +++ b/drivers/media/platform/apple/avd/avd-v4l2.c @@ -889,6 +889,10 @@ static void avd_buf_request_complete(struct vb2_buffer *vb) static int avd_start_streaming(struct vb2_queue *q, unsigned int count) { struct avd_ctx *ctx = vb2_get_drv_priv(q); + + /* A new stream gets a fresh start after a hardware failure. */ + if (!V4L2_TYPE_IS_CAPTURE(q->type)) + ctx->broken = false; const struct avd_coded_fmt_desc *desc; struct vb2_v4l2_buffer *vbuf; int ret; diff --git a/drivers/media/platform/apple/avd/avd.h b/drivers/media/platform/apple/avd/avd.h index 98010d1fb0786..20411c991ae31 100644 --- a/drivers/media/platform/apple/avd/avd.h +++ b/drivers/media/platform/apple/avd/avd.h @@ -266,6 +266,8 @@ struct avd_buf { struct avd_ctx { struct v4l2_fh fh; struct avd_dev *dev; + /* A frame failed in hardware: fail every job until streaming restarts. */ + bool broken; struct v4l2_format coded_fmt; struct v4l2_format decoded_fmt; From 9e3e963d4d789d2bbc4caf33e7479ee54ea8b982 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Sun, 1 Nov 2026 05:00:39 +1100 Subject: [PATCH 6/6] media: apple: avd: accept FFmpeg's HEVC slice layout The HEVC builder computes each slice's coded data address and size from GStreamer's layout: slices tile the OUTPUT buffer as whole NALs, bit_size covers the whole NAL and data_byte_offset is the slice data's offset in it. FFmpeg's V4L2 request backend gives data_byte_offset from the start of the buffer and a bit_size of the slice data only. Every slice then got a wrong address and a size short by its header, the VP raised its error interrupt on every frame, and the decoder was reset once per frame. This is the "H0 error" storm the Neo hit in an FFmpeg-based benchmark. When the slices describe a complete, unambiguous absolute layout, rewrite the run's private copy into the equivalent NAL-relative description. The GStreamer layout and anything ambiguous are left untouched. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/media/platform/apple/avd/avd-hevc.c | 47 +++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/drivers/media/platform/apple/avd/avd-hevc.c b/drivers/media/platform/apple/avd/avd-hevc.c index 9dfa20ab71913..49d8f046ac5c9 100644 --- a/drivers/media/platform/apple/avd/avd-hevc.c +++ b/drivers/media/platform/apple/avd/avd-hevc.c @@ -1282,6 +1282,51 @@ static int avd_hevc_compute_tiles(struct avd_ctx *ctx, struct avd_hevc_run *run) return 0; } +/* + * The slice builder below expects the GStreamer layout: the slices tile the + * OUTPUT buffer as whole NALs, bit_size covers the whole NAL and + * data_byte_offset is the slice data's offset inside it. FFmpeg's request + * API backend instead gives each slice's data_byte_offset from the start of + * the buffer and a bit_size of the slice data only, so every slice was given + * a wrong address and size and the VP raised its error interrupt on every + * frame. Detect the absolute layout (complete and unambiguous) and rewrite + * the run's private copy into the equivalent NAL-relative description. + */ +static void avd_hevc_normalize_slices(struct avd_hevc_run *run, u32 payload) +{ + struct v4l2_ctrl_hevc_slice_params *sl = + (struct v4l2_ctrl_hevc_slice_params *)run->sl; + u64 nal_end = 0, data_end = 0, prev_end = 0; + bool nal = true, absolute = true; + int i; + + if (!run->num_slices || !payload) + return; + for (i = 0; i < run->num_slices; i++) { + u64 bytes = DIV_ROUND_UP((u64)sl[i].bit_size, 8); + u64 start = sl[i].data_byte_offset; + + if (!bytes || start >= bytes || nal_end + bytes > payload) + nal = false; + nal_end += bytes; + if (!bytes || start < data_end || start + bytes > payload) + absolute = false; + data_end = start + bytes; + } + nal &= nal_end == payload; + absolute &= data_end == payload; + if (!absolute || nal) + return; + for (i = 0; i < run->num_slices; i++) { + u64 bytes = DIV_ROUND_UP((u64)sl[i].bit_size, 8); + u64 start = sl[i].data_byte_offset; + + sl[i].data_byte_offset = start - prev_end; + sl[i].bit_size = (start - prev_end + bytes) * 8; + prev_end = start + bytes; + } +} + static int avd_hevc_run_preamble(struct avd_ctx *ctx, struct avd_hevc_run *run) { struct v4l2_ctrl *ctrl, *sl, *ep; @@ -1353,6 +1398,8 @@ static int avd_hevc_run(struct avd_ctx *ctx) if (ret) return ret; + avd_hevc_normalize_slices(&run, + vb2_get_plane_payload(&run.base.bufs.src->vb2_buf, 0)); dst = vb2_to_avd_decoded_buf(&run.base.bufs.dst->vb2_buf); update_dec_buf_info(dst, &run.sl[0]);