diff --git a/drivers/media/platform/apple/avd/avd-av1.c b/drivers/media/platform/apple/avd/avd-av1.c index ba7beea61c842..9a62f1bca92c4 100644 --- a/drivers/media/platform/apple/avd/avd-av1.c +++ b/drivers/media/platform/apple/avd/avd-av1.c @@ -225,12 +225,15 @@ struct avd_av1_run { }; struct avd_av1_ctx { + struct avd_av1_stream *stream; u8 submit_num; struct { struct avd_buf inst; struct avd_buf pipe_state; struct avd_buf probs; struct avd_buf no_prev_seg; + struct avd_buf saved_seg; + struct avd_buf prev_seg; struct avd_buf above_info; struct avd_buf rf_above_info; struct avd_buf az_above; @@ -246,6 +249,25 @@ struct avd_av1_ctx { } bufs; }; +#define AVD_AV1_BANKS AVD_MAX_FRAMES + +struct avd_av1_bank { + struct avd_av1_ctx codec; + bool busy; + bool prepared; + bool vp_done; + struct avd_decoded_buffer *dst; + u64 timestamp; + u32 flags; + u8 seg_flags; +}; + +struct avd_av1_stream { + unsigned int nbanks; + struct avd_av1_bank bank[AVD_AV1_BANKS]; + struct avd_av1_bank *b; +}; + /* * 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); @@ -259,6 +281,113 @@ static u64 avd_av1_ref_ts(const struct v4l2_ctrl_av1_frame *frame, int idx) return frame->reference_frame_ts[idx]; } +static bool avd_av1_default_cdf(const struct v4l2_ctrl_av1_frame *frame) +{ + return frame->primary_ref_frame == AV1_PRIMARY_REF_NONE || + frame->frame_type == V4L2_AV1_KEY_FRAME || + frame->frame_type == V4L2_AV1_INTRA_ONLY_FRAME || + (frame->flags & V4L2_AV1_FRAME_FLAG_ERROR_RESILIENT_MODE); +} + +static bool avd_av1_writes_seg(u8 flags) +{ + return (flags & V4L2_AV1_SEGMENTATION_FLAG_ENABLED) && + (flags & V4L2_AV1_SEGMENTATION_FLAG_UPDATE_MAP); +} + +/* + * No available hardware contract proves AV1 tails CPU-visible at VP DONE. + * Only preparation's immutable CDF/map snapshots can be consumed before PP + * retirement. The consumer's disable_frame_end_update_cdf does not remove + * its CDF input dependency. Slot timestamps, not refresh order, select it. + */ +static bool avd_av1_depends(const struct v4l2_ctrl_av1_frame *frame, + const struct avd_av1_bank *bank) +{ + /* Preserve same-VP ordering for named references when engines differ. */ + for (unsigned int i = 0; i < V4L2_AV1_REFS_PER_FRAME; i++) + if (avd_av1_ref_ts(frame, frame->ref_frame_idx[i]) == bank->timestamp && + (!smp_load_acquire(&bank->vp_done) || + (frame->flags & V4L2_AV1_FRAME_FLAG_USE_REF_FRAME_MVS))) + return true; + + if (frame->primary_ref_frame >= AV1_PRIMARY_REF_NONE || + avd_av1_ref_ts(frame, frame->ref_frame_idx[frame->primary_ref_frame]) != + bank->timestamp) + return false; + if (!avd_av1_default_cdf(frame) && + !(bank->flags & V4L2_AV1_FRAME_FLAG_DISABLE_FRAME_END_UPDATE_CDF)) + return true; + return (frame->segmentation.flags & V4L2_AV1_SEGMENTATION_FLAG_ENABLED) && + avd_av1_writes_seg(bank->seg_flags) && + DIV_ROUND_UP(bank->dst->av1.width + 1, 8) == + DIV_ROUND_UP(frame->frame_width_minus_1 + 1, 8) && + DIV_ROUND_UP(bank->dst->av1.height + 1, 8) == + DIV_ROUND_UP(frame->frame_height_minus_1 + 1, 8); +} + +static bool avd_av1_ready(struct avd_ctx *ctx) +{ + struct avd_av1_ctx *codec = smp_load_acquire(&ctx->priv); + struct avd_av1_stream *av1_ctx; + struct vb2_v4l2_buffer *src; + struct v4l2_ctrl_handler *hdl; + struct v4l2_ctrl_ref *ref; + const struct v4l2_ctrl_av1_frame *frame = NULL; + bool ready = false, free = false, busy = false; + + if (!codec) + return false; + av1_ctx = codec->stream; + for (unsigned int i = 0; i < av1_ctx->nbanks; i++) { + if (!READ_ONCE(av1_ctx->bank[i].busy)) + free = true; + else if (!smp_load_acquire(&av1_ctx->bank[i].prepared)) + return false; + else + busy = true; + } + if (!free || !busy) + return free; + src = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + if (!src || !src->vb2_buf.req_obj.req) + return false; + hdl = v4l2_ctrl_request_hdl_find(src->vb2_buf.req_obj.req, &ctx->ctrl_hdl); + if (!hdl) + return false; + /* + * The queued request is immutable and cannot complete while its OUTPUT + * is still on the ready queue. Read p_req, never the previous picture's + * shared p_cur, without taking a sleeping control mutex under queue_lock. + */ + list_for_each_entry(ref, &hdl->ctrl_refs, node) + if (ref->ctrl->id == V4L2_CID_STATELESS_AV1_FRAME && ref->p_req_valid) { + frame = ref->p_req.p; + break; + } + if (frame) { + ready = true; + for (unsigned int i = 0; i < av1_ctx->nbanks; i++) + if (READ_ONCE(av1_ctx->bank[i].busy) && + avd_av1_depends(frame, &av1_ctx->bank[i])) + ready = false; + } + v4l2_ctrl_request_hdl_put(hdl); + return ready; +} + +static struct avd_av1_bank *avd_av1_ref_bank(struct avd_av1_stream *av1_ctx, + struct avd_decoded_buffer *ref) +{ + if (av1_ctx->nbanks > 1 && ref->bank && ref->bank <= av1_ctx->nbanks) { + struct avd_av1_bank *bank = &av1_ctx->bank[ref->bank - 1]; + + if (bank->busy && bank->prepared && bank->dst == ref) + return bank; + } + return NULL; +} + /* * from verisilicon/rockchip_vpu981_hw_av1_dec.c */ @@ -1160,6 +1289,71 @@ static void avd_av1_set_seg(struct avd_ctx *ctx, struct avd_av1_run *run) memcpy(seg, prev_seg, dst->av1.seg_size); } +/* Preserve the legacy tail preparation; only queued immutable inputs differ. */ +static void avd_av1_prepare_prob(struct avd_ctx *ctx, struct avd_av1_run *run) +{ + struct avd_av1_ctx *codec = ctx->priv; + struct avd_av1_stream *stream = codec->stream; + struct avd_decoded_buffer *dst, *ref; + struct avd_av1_bank *bank = NULL; + + dst = vb2_to_avd_decoded_buf(&run->base.bufs.dst->vb2_buf); + if (stream->nbanks > 1 && !avd_av1_default_cdf(run->frame)) { + ref = avd_get_ref_buf(ctx, &dst->base.vb, + avd_av1_ref_ts(run->frame, + run->frame->ref_frame_idx[run->frame->primary_ref_frame])); + bank = avd_av1_ref_bank(stream, ref); + } + if (!bank) { + avd_av1_set_prob(ctx, run); + return; + } + memcpy(codec->bufs.probs.cpu, bank->codec.bufs.probs.cpu, AVD_CDFS_SIZE); + memcpy(vb2_plane_vaddr(&dst->base.vb.vb2_buf, 0) + + AVD_AV1_CDFS_OFFSET(dst->base.vb.vb2_buf.planes[0].length, + dst->av1.color_size), + codec->bufs.probs.cpu, AVD_CDFS_SIZE); +} + +static void avd_av1_prepare_seg(struct avd_ctx *ctx, struct avd_av1_run *run) +{ + struct avd_av1_ctx *codec = ctx->priv; + struct avd_av1_stream *stream = codec->stream; + const struct v4l2_ctrl_av1_frame *frame = run->frame; + struct avd_decoded_buffer *dst, *ref; + struct avd_av1_bank *bank = NULL; + void *seg; + + dst = vb2_to_avd_decoded_buf(&run->base.bufs.dst->vb2_buf); + seg = vb2_plane_vaddr(&dst->base.vb.vb2_buf, 0) + + AVD_AV1_SEG_OFFSET(dst->base.vb.vb2_buf.planes[0].length, + dst->av1.color_size, dst->av1.seg_size); + if (stream->nbanks > 1 && frame->primary_ref_frame != AV1_PRIMARY_REF_NONE) { + ref = avd_get_ref_buf(ctx, &dst->base.vb, + avd_av1_ref_ts(frame, frame->ref_frame_idx[frame->primary_ref_frame])); + if (ref != dst && + DIV_ROUND_UP(ref->av1.width + 1, 8) == + DIV_ROUND_UP(frame->frame_width_minus_1 + 1, 8) && + DIV_ROUND_UP(ref->av1.height + 1, 8) == + DIV_ROUND_UP(frame->frame_height_minus_1 + 1, 8)) + bank = avd_av1_ref_bank(stream, ref); + } + if (bank && !avd_av1_writes_seg(bank->seg_flags)) { + /* Copy into our bank: producer PP can retire during our VP. */ + memcpy(codec->bufs.prev_seg.cpu, bank->codec.bufs.saved_seg.cpu, + dst->av1.seg_size); + run->addresses.prev_seg = codec->bufs.prev_seg.addr; + if (!(frame->segmentation.flags & V4L2_AV1_SEGMENTATION_FLAG_ENABLED)) + memset(seg, 0, dst->av1.seg_size); + else if (!(frame->segmentation.flags & V4L2_AV1_SEGMENTATION_FLAG_UPDATE_MAP)) + memcpy(seg, codec->bufs.prev_seg.cpu, dst->av1.seg_size); + } else { + avd_av1_set_seg(ctx, run); + } + if (stream->nbanks > 1 && !avd_av1_writes_seg(frame->segmentation.flags)) + memcpy(codec->bufs.saved_seg.cpu, seg, dst->av1.seg_size); +} + static int avd_av1_seg_size(int w, int h) { /* 3 bits per 4x4 block, in rows of 128x128 superblocks (64x64 fit too) */ @@ -1280,6 +1474,17 @@ static int avd_av1_alloc_work_bufs(struct avd_ctx *ctx, struct avd_av1_run *run) int h = frame->frame_height_minus_1 + 1; int bit_depth = seq->bit_depth; + if (av1_ctx->stream->nbanks > 1) { + ret = avd_buf_alloc(avd, &av1_ctx->bufs.saved_seg, + avd_av1_seg_size(w, h)); + if (ret) + return ret; + ret = avd_buf_alloc(avd, &av1_ctx->bufs.prev_seg, + avd_av1_seg_size(w, h)); + if (ret) + return ret; + } + for (i = 0; i < tile_info->tile_cols; i++) { sb = tile_info->width_in_sbs_minus_1[i] + 1; max_sb_col = sb > max_sb_col ? sb : max_sb_col; @@ -1380,6 +1585,7 @@ static int avd_av1_alloc_work_bufs(struct avd_ctx *ctx, struct avd_av1_run *run) static int avd_av1_run(struct avd_ctx *ctx) { struct avd_av1_run run; + struct avd_av1_stream *av1_ctx = ((struct avd_av1_ctx *)ctx->priv)->stream; struct avd_decoded_buffer *dst, *ref; int ret; @@ -1402,6 +1608,29 @@ static int avd_av1_run(struct avd_ctx *ctx) avd_run_postamble(ctx, &run.base); return -ENOMEM; } + if (av1_ctx->nbanks > 1) { + struct avd_av1_bank *bank = NULL; + + for (unsigned int i = 0; i < av1_ctx->nbanks; i++) + if (!av1_ctx->bank[i].busy) { + bank = &av1_ctx->bank[i]; + break; + } + if (!bank) { + avd_run_postamble(ctx, &run.base); + return -EBUSY; + } + av1_ctx->b = bank; + smp_store_release(&ctx->priv, &bank->codec); + WRITE_ONCE(bank->prepared, false); + WRITE_ONCE(bank->vp_done, false); + WRITE_ONCE(bank->busy, true); + bank->dst = dst; + bank->timestamp = run.base.bufs.src->vb2_buf.timestamp; + bank->flags = run.frame->flags; + bank->seg_flags = run.frame->segmentation.flags; + dst->bank = bank - av1_ctx->bank + 1; + } ret = avd_init_job(ctx, AVD_CODEC_AV1, run.frame->tile_info.tile_cols * @@ -1420,33 +1649,63 @@ static int avd_av1_run(struct avd_ctx *ctx) update_dec_buf_info(dst, run.seq, run.frame); - avd_av1_set_prob(ctx, &run); - avd_av1_set_seg(ctx, &run); + avd_av1_prepare_prob(ctx, &run); + avd_av1_prepare_seg(ctx, &run); set_header(ctx, &run); set_tiles(ctx, &run); + if (av1_ctx->nbanks > 1) { + ctx->job_fifo = av1_ctx->b->codec.bufs.inst.addr; + smp_store_release(&av1_ctx->b->prepared, true); + } avd_run_postamble(ctx, &run.base); return avd_submit_job(ctx); } -static int avd_av1_alloc_bufs(struct avd_ctx *ctx) + +/* Tagged engine completion releases ordering, never CAPTURE tail visibility. */ +static void avd_av1_vp_done(struct avd_ctx *ctx, struct vb2_v4l2_buffer *dst) +{ + struct avd_av1_stream *stream = ((struct avd_av1_ctx *)ctx->priv)->stream; + struct avd_decoded_buffer *buf = vb2_to_avd_decoded_buf(&dst->vb2_buf); + struct avd_av1_bank *bank = avd_av1_ref_bank(stream, buf); + + if (bank) + smp_store_release(&bank->vp_done, true); +} + +static void avd_av1_done(struct avd_ctx *ctx, struct vb2_v4l2_buffer *src, + struct vb2_v4l2_buffer *dst, enum vb2_buffer_state result) +{ + struct avd_av1_stream *av1_ctx = ((struct avd_av1_ctx *)ctx->priv)->stream; + struct avd_decoded_buffer *buf = vb2_to_avd_decoded_buf(&dst->vb2_buf); + + if (buf->bank && buf->bank <= av1_ctx->nbanks) { + struct avd_av1_bank *bank = &av1_ctx->bank[buf->bank - 1]; + + if (bank->dst == buf) + WRITE_ONCE(bank->busy, false); + buf->bank = 0; + } +} + +static int avd_av1_alloc_bufs(struct avd_ctx *ctx, struct avd_av1_bank *bank) { struct avd_dev *avd = ctx->dev; - struct avd_av1_ctx *av1_ctx = ctx->priv; int ret; - ret = avd_buf_alloc(avd, &av1_ctx->bufs.inst, fifo_size()); + ret = avd_buf_alloc(avd, &bank->codec.bufs.inst, fifo_size()); if (ret) return ret; /* no m1 to worry about :) */ - ret = avd_buf_alloc(avd, &av1_ctx->bufs.pipe_state, 0x200); + ret = avd_buf_alloc(avd, &bank->codec.bufs.pipe_state, 0x200); if (ret) return ret; - ret = avd_buf_alloc(avd, &av1_ctx->bufs.probs, + ret = avd_buf_alloc(avd, &bank->codec.bufs.probs, sizeof(struct avd_av1_cdfs)); if (ret) return ret; @@ -1454,51 +1713,68 @@ static int avd_av1_alloc_bufs(struct avd_ctx *ctx) return 0; } -static void avd_av1_stop(struct avd_ctx *ctx) +static void avd_av1_free(struct avd_ctx *ctx, struct avd_av1_stream *av1_ctx) { - struct avd_av1_ctx *av1_ctx = ctx->priv; struct avd_dev *avd = ctx->dev; - avd_buf_free(avd, &av1_ctx->bufs.above_info); - avd_buf_free(avd, &av1_ctx->bufs.rf_above_info); - avd_buf_free(avd, &av1_ctx->bufs.az_above); - avd_buf_free(avd, &av1_ctx->bufs.ip_above); - avd_buf_free(avd, &av1_ctx->bufs.lf_above); - avd_buf_free(avd, &av1_ctx->bufs.lf_above_info); - avd_buf_free(avd, &av1_ctx->bufs.lf_left); - avd_buf_free(avd, &av1_ctx->bufs.lf_left_info); - avd_buf_free(avd, &av1_ctx->bufs.sr_left); - avd_buf_free(avd, &av1_ctx->bufs.rf_left); - avd_buf_free(avd, &av1_ctx->bufs.rf_left_info); - avd_buf_free(avd, &av1_ctx->bufs.no_prev_seg); - avd_buf_free(avd, &av1_ctx->bufs.mv_above_info); - - avd_buf_free(avd, &av1_ctx->bufs.pipe_state); - avd_buf_free(avd, &av1_ctx->bufs.inst); - avd_buf_free(avd, &av1_ctx->bufs.probs); - + for (unsigned int i = 0; i < av1_ctx->nbanks; i++) { + struct avd_av1_bank *bank = &av1_ctx->bank[i]; + + avd_buf_free(avd, &bank->codec.bufs.above_info); + avd_buf_free(avd, &bank->codec.bufs.rf_above_info); + avd_buf_free(avd, &bank->codec.bufs.az_above); + avd_buf_free(avd, &bank->codec.bufs.ip_above); + avd_buf_free(avd, &bank->codec.bufs.lf_above); + avd_buf_free(avd, &bank->codec.bufs.lf_above_info); + avd_buf_free(avd, &bank->codec.bufs.lf_left); + avd_buf_free(avd, &bank->codec.bufs.lf_left_info); + avd_buf_free(avd, &bank->codec.bufs.sr_left); + avd_buf_free(avd, &bank->codec.bufs.rf_left); + avd_buf_free(avd, &bank->codec.bufs.rf_left_info); + avd_buf_free(avd, &bank->codec.bufs.no_prev_seg); + avd_buf_free(avd, &bank->codec.bufs.mv_above_info); + + avd_buf_free(avd, &bank->codec.bufs.pipe_state); + avd_buf_free(avd, &bank->codec.bufs.inst); + avd_buf_free(avd, &bank->codec.bufs.probs); + avd_buf_free(avd, &bank->codec.bufs.saved_seg); + avd_buf_free(avd, &bank->codec.bufs.prev_seg); + } kfree(av1_ctx); +} + +static void avd_av1_stop(struct avd_ctx *ctx) +{ + avd_av1_free(ctx, ((struct avd_av1_ctx *)ctx->priv)->stream); ctx->priv = NULL; + ctx->max_frames = 1; } static int avd_av1_start(struct avd_ctx *ctx) { - struct avd_av1_ctx *av1_ctx; + struct avd_av1_stream *av1_ctx; int ret; av1_ctx = kzalloc(sizeof(*av1_ctx), GFP_KERNEL); if (!av1_ctx) return -ENOMEM; - ctx->priv = av1_ctx; - ret = avd_av1_alloc_bufs(ctx); - if (ret) - goto err_free_ctx; - + av1_ctx->nbanks = avd_qfw_has_vp_done(ctx->dev) ? + avd_qfw_picture_banks(ctx->dev, AVD_CODEC_AV1) : 1; + av1_ctx->b = &av1_ctx->bank[0]; + for (unsigned int i = 0; i < av1_ctx->nbanks; i++) { + av1_ctx->bank[i].codec.stream = av1_ctx; + ret = avd_av1_alloc_bufs(ctx, &av1_ctx->bank[i]); + if (ret) + goto err_free_ctx; + } + ctx->max_frames = av1_ctx->nbanks; + /* Readiness must never see partially allocated bank storage. */ + smp_store_release(&ctx->priv, &av1_ctx->bank[0].codec); return 0; err_free_ctx: - avd_av1_stop(ctx); + avd_av1_free(ctx, av1_ctx); return ret; } @@ -1586,6 +1862,9 @@ const struct avd_coded_fmt_ops avd_av1_fmt_ops = { .start = avd_av1_start, .stop = avd_av1_stop, .run = avd_av1_run, + .done = avd_av1_done, + .vp_done = avd_av1_vp_done, + .ready = avd_av1_ready, .submit = avd_av1_submit, .pp_kicks = avd_av1_pp_kicks, .try_ctrl = avd_av1_try_ctrl, diff --git a/drivers/media/platform/apple/avd/avd-backend-queue.c b/drivers/media/platform/apple/avd/avd-backend-queue.c index 5853d9d050a93..178572cb2e5d5 100644 --- a/drivers/media/platform/apple/avd/avd-backend-queue.c +++ b/drivers/media/platform/apple/avd/avd-backend-queue.c @@ -117,8 +117,8 @@ static unsigned int avd_qfw_bank_limit(struct avd_qfw *q, unsigned int codec) unsigned int banks = 1; u32 pool; - /* k4m keeps two banks with old queue firmware. */ - if (!q->multi_vp || codec > AVD_CODEC_H264) + /* Old queue firmware and VP9 retain the two-bank overlap path. */ + if (!q->multi_vp || codec == AVD_CODEC_VP9 || codec > AVD_CODEC_AV1) return 2; /* One bank per VP plus one retained by PP. No SoC counts in the host. */ for (pool = q->vp_pool[codec]; pool; pool &= pool - 1) diff --git a/drivers/media/platform/apple/avd/tests/README.rst b/drivers/media/platform/apple/avd/tests/README.rst index 1d4f3917f63c4..9b17575f8d7f5 100644 --- a/drivers/media/platform/apple/avd/tests/README.rst +++ b/drivers/media/platform/apple/avd/tests/README.rst @@ -200,37 +200,115 @@ This is a backwards-compatible optional ABI 2 feature: bank reuse. Only the original tagged PP completion does that. A missing VP event remains bounded by the original per-picture watchdog. -AV1 analysis -~~~~~~~~~~~~ - -AV1's current ``avd_av1_set_prob()`` reads the primary reference's CDF tail on -the CPU, and seeds the current frame's output CDF because hardware leaves it -untouched when DISABLE_FRAME_END_UPDATE_CDF is set. ``avd_av1_set_seg()`` can -also copy the primary reference's segmentation map on the CPU. These are -separate dependencies; removing only the CDF wait is insufficient. - -=================================== ========================================= -Case CDF source dependency -=================================== ========================================= -Intra/error-resilient/PRIMARY_NONE Defaults; no primary-reference CDF read -Primary ref with frame-end disabled Its initialized CDF, available at prepare -Primary ref with frame-end enabled Its hardware-written CDF tail -=================================== ========================================= - -``disable_frame_end_update_cdf`` on the *consumer* does not remove its primary -reference input dependency. It controls that consumer's future CDF output. -Likewise, a default CDF does not necessarily remove the segmentation-map input -selected by primary_ref_frame. - -The available implementation does not establish whether the CDF tail *and* -segmentation map are fully visible at VP DONE, or which CPU DMA synchronization -is required for an in-flight CAPTURE allocation while PP writes another part. -This differs from VP9's separate coherent count buffer. AV1 therefore retains -its one-picture path. Enabling it needs evidence for both tail writes and a -safe visibility/synchronization rule, then two scratch banks and a dependency -on the actual primary reference (which need not be the preceding picture). -No AV1 or VP9 pixel correctness or throughput claim follows from these host -and emulator tests; device qualification remains necessary. +AV1 picture overlap and codec VP pools +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``av1-test.py`` compiles the production AV1 codec, entropy defaults, open +program builder and queued descriptor writer with ASan/UBSan. It is also +called by the registered ``avd-codec-tail-tests.py`` host check. Run:: + + AVD_AV1_BASELINE_REF=d62b4894 python3 tests/av1-test.py /tmp/avd-av1 + python3 tests/unicorn-av1-test.py /path/to/avd-fw \ + /path/to/avd-fw/build/avd-fw-v5-t2.bin /tmp/avd-av1 + +The independent legacy comparison builds before/after executables and compares +complete emitted programs plus initialized CDF/map tails for twelve header cases. +The flag table covers primary-reference/default CDF cases, both producer and +consumer disable_frame_end_update_cdf, segmentation enabled/update_map on both +pictures, and all 256 refresh masks. Pending request p_req values are used for +readiness; p_cur still describes an earlier picture and must not be used. + +Visibility evidence and limit +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The public AV1 specification sections 7.7, 7.19 and 7.20 define frame CDF, +motion-field and segmentation storage semantics: +https://aomediacodec.github.io/av1-spec/ . They do not define this decoder's +VP/PP DMA boundary. In our driver ``set_header()`` supplies probs_out, seg and +w_color output addresses to the VP program; ``avd_av1_set_prob()`` seeds CDF +output and ``avd_av1_set_seg()`` initializes non-updated maps. The open firmware +``avdq_vp_done()`` changes VP to PP_READY and ``notify_vp()`` publishes a tag +with barriers. Neither reads the AV1 output memory nor supplies an AV1-specific +DMA visibility guarantee. A CM3 DMB orders its descriptor stores; it does not +prove the decoder's prior external tail writes are CPU-visible. + +There is therefore **no evidence here proving that a hardware-written AV1 CDF, +segmentation map or motion field is visible at VP DONE**. There is also no +proof that they are written only at PP DONE. Unicorn injects DONE and cannot +establish either assertion. The implementation keeps those dependencies until +PP retirement instead of inferring a visibility contract from their operands. +No CPU DMA sync is attempted on a CAPTURE allocation still owned by PP. + +Safe subset +^^^^^^^^^^^ + +Queue firmware advertising VP-DONE feature bit 2 enables two scratch banks; +with a validated codec pool (feature bit 3), AV1 instead allocates one bank per +advertised AV1 VP plus one retained by PP, bounded by physical FIFO slots. +There are no host SoC counts. VP9 retains its existing two-bank limit. +Legacy transport and queue firmware without tagged VP DONE retain one bank and identical +programs. The CDF output, segmentation output and motion-field/color output +already live in each picture's distinct CAPTURE tail. Each scratch bank owns +its FIFO, CDF input, pipe/filter/tile scratch, a saved initialized map and a +separate map input snapshot. Only PP retirement releases its bank. VP DONE +never releases storage or publishes hardware-written tails for AV1. It releases +only the producer VP ordering barrier for a named reference. Each saved CAPTURE +bank records its own tagged VP event, independently of the current builder. + +For a pending consumer, each busy producer is tested by the exact timestamp +named through ref_frame_idx/primary_ref_frame, rather than the preceding frame +or a global refresh-context flag. refresh_frame_flags controls which slots +userspace subsequently names; it does not create a dependency by itself. + +=============================== ============================================== +Consumer / producer condition Prepare dependency while producer is in flight +=============================== ============================================== +Named live reference Wait for its tagged VP DONE on any engine +PRIMARY_REF_NONE No primary CDF or map dependency +Intra or error resilient Default CDF; map dependency still checked +Primary slot names retired ref None on an in-flight picture +Primary CDF, producer end update Enabled: wait for producer PP retirement +Primary CDF, producer end update Disabled: use immutable prepared CDF input copy +Consumer end update disabled Does not remove its CDF input dependency +Consumer segmentation disabled No previous-map read +Consumer segmentation enabled Updated producer map: wait for PP (same Mi size) +Producer map not updated Copy immutable saved map into consumer bank +Different reference Mi size Zero map; no map dependency +USE_REF_FRAME_MVS Wait for PP of any named in-flight reference +refresh_frame_flags 0..255 No additional dependency; named slot decides +=============================== ============================================== + +Both consumer update_map values retain the previous-map input when segmentation +is enabled, as the existing program supplies it in either case. A disabled +producer segmentation map is saved as zeroes. Producer map snapshots are copied +into consumer-owned coherent storage, so producer PP retirement and bank reuse +cannot invalidate a live successor's VP input. CDF snapshots are similarly +copied into the consumer's CDF input. No hardware-written tail is sampled by +these copies. Busy banks publish fully prepared metadata with release/acquire; +allocation failures and PP error/reset retirement release the saved CAPTURE's +bank, independent of the current builder. + +The former single-engine subset relied on firmware waiting for VP(N) DONE +before feeding VP(N+1) on the same engine. Across engines that ordering is +explicit: a consumer naming any live reference waits for that producer's tagged +VP event. The callback publishes only ordering, without sampling CAPTURE tails. +The CDF/map snapshot exception then applies; hardware-written CDF/map/MV inputs +still wait for PP retirement. Independent pictures can start on different VPs +before either completes. PP consumes reference reconstruction in submission +order, as in the multi-VP H.264/HEVC path. + +The Unicorn run boots the v5-t2 CM3 reset vector, routes AV1 and PP IRQs, +and checks production AV1 program words/descriptors/configuration. It shows +VP(1) issued during PP(0), concurrent VPs 9/10/11, a fourth picture waiting for +VP9 DONE, reverse engine completion, per-picture VP tokens, ordered PP and +blocked completion mailboxes. A routed error on each AV1 engine drains every +owner; reset boots all engines again. Host tests cover pool sizes, sparse masks, +FIFO bounds, each producer's CDF/map/MV and VP barriers, duplicate VP callbacks, +all-bank allocation failures, exhaustion and saved-bank error retirement. +Host tests poison in-flight CAPTURE tails to ensure snapshot consumers never +read them. Pixels, physical DMA visibility and throughput remain hardware +qualification work for the lead. Full CDF/MV dependency release at VP DONE is +still a separate evidence requirement. H.264 conformance fixes ---------------------- @@ -281,3 +359,12 @@ preamble. Across all four revision/address variants, weight modes and first/continuation slices, its complete program must equal the baseline IDR program exactly. Pictures with references retain their original header. The baseline comparison retains all 32 before/after empty-DPB cases. + +Film grain controls, frame flags and the legacy syntax/program emitters are +unchanged. The independent baseline comparison includes film grain control +values and SHOW_FRAME/SHOWABLE_FRAME combinations at 8 and 10 bits. It checks +program/tail parity, not grain synthesis. The stateless AV1 frame UAPI has no +show_existing_frame decode control: userspace presents the existing CAPTURE +buffer without a new tile decode. No VP event retires that CAPTURE buffer or +its media request early. Hardware qualification must include those presentation +paths as well as pixel hashes; this change makes no new grain-support claim. diff --git a/drivers/media/platform/apple/avd/tests/av1-test.py b/drivers/media/platform/apple/avd/tests/av1-test.py new file mode 100644 index 0000000000000..217b433329978 --- /dev/null +++ b/drivers/media/platform/apple/avd/tests/av1-test.py @@ -0,0 +1,405 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only OR MIT +"""Production AV1 bank/dependency/emitters, with explicit host DMA boundaries. + +Run in a controller build slot: av1-test.py OUT [AVD_DIR [KERNEL_ROOT]]. +AVD_AV1_BASELINE_REF optionally compares complete legacy programs/CDF/map bytes. +No decoder arithmetic or DMA visibility is modeled by these host fixtures. +""" +import hashlib +import importlib.util +import os +from pathlib import Path +import re +import subprocess +import sys + +from host_source import function + +HERE = Path(__file__).resolve().parent +AVD = Path(sys.argv[2]).resolve() if len(sys.argv) > 2 else HERE.parent +KERNEL = Path(sys.argv[3]).resolve() if len(sys.argv) > 3 else AVD.parents[4] +OUT = Path(sys.argv[1]).resolve() +OUT.mkdir(parents=True, exist_ok=True) +spec = importlib.util.spec_from_file_location('program_fixture', HERE / 'program-test.py') +fixture = importlib.util.module_from_spec(spec) +spec.loader.exec_module(fixture) +pre = fixture.PRELUDE +pre = pre.replace('typedef uint8_t u8;', 'typedef uint8_t u8; typedef uint16_t u16; typedef int8_t s8; typedef int16_t s16; typedef int64_t s64;') +pre = pre.replace('#define AVD_MAX_INST 512', '#define AVD_MAX_INST 512\n#define AVD_CODEC_HEVC 0') +pre = pre.replace('struct vb2_buffer { struct { size_t length; } planes[1]; dma_addr_t dma; };', + 'struct vb2_buffer { struct { size_t length; } planes[1]; dma_addr_t dma; void *cpu; size_t payload; u64 timestamp; struct {void *req;} req_obj; };') +header = (AVD / 'avd.h').read_text() +a = header.index('struct avd_av1_decoded_buffer_info {') +b = header.index('\n};', a) + 3 +pre = pre.replace('struct avd_decoded_buffer {', header[a:b] + '\nstruct avd_decoded_buffer {') +pre = pre.replace('struct avd_comp comp; unsigned int bank;', 'struct avd_comp comp; unsigned int bank; struct avd_av1_decoded_buffer_info av1;') +pre = pre.replace('struct vb2_v4l2_buffer *dst; } bufs;', 'struct vb2_v4l2_buffer *src,*dst; } bufs;') +pre = pre.replace('unsigned int fifo_idx, pp_total;', 'unsigned int fifo_idx, pp_total, max_frames; int ctrl_hdl;') +pre = pre.replace('dma_addr_t comp_out, y_out, uv_out;', 'dma_addr_t comp_out, y_out, uv_out, coded_in;') +pre = pre.replace('struct { u32 bytesperline; } plane_fmt[1];', 'u32 width,height; struct { u32 bytesperline; } plane_fmt[1];') +pre = pre.replace('static struct avd_decoded_buffer reference;', 'static struct avd_decoded_buffer reference; static struct avd_decoded_buffer *refs[5];') +pre = pre.replace('(void)c; (void)b; (void)t; return &reference;', '(void)c;for(unsigned int i=0;i<5;i++)if(refs[i]&&refs[i]->base.vb.vb2_buf.timestamp==t)return refs[i];return (void *)b;') +pre = pre.replace('static int avd_buf_alloc(struct avd_dev *d, struct avd_buf *b, size_t n)', + 'static struct avd_ctx *allocating_ctx;\nstatic int fail_alloc_after;\nstatic bool avd_av1_ready(struct avd_ctx *);\nstatic int avd_buf_alloc(struct avd_dev *d, struct avd_buf *b, size_t n)') +pre = pre.replace('{ (void)d; free(b->cpu); b->cpu = calloc(1,n);', + '{ if(allocating_ctx){assert(!avd_av1_ready(allocating_ctx));if(fail_alloc_after&&!--fail_alloc_after)return -ENOMEM;} (void)d; free(b->cpu); b->cpu = calloc(1,n);') +pre += r''' +#define AVD_ALIGN 256 +#define S16_MIN INT16_MIN +#define S16_MAX INT16_MAX +#define clamp_val(x,l,h) clamp(x,l,h) +#define U64_MAX UINT64_MAX +#define BIT_ULL(n) (1ULL<<(n)) +#define ALIGN(x,a) (((x)+(a)-1)&~((__typeof__(x))(a)-1)) +#define ALIGN_DOWN(x,a) ((x)&~((__typeof__(x))(a)-1)) +#define max_t(t,a,b) ((t)(a)>(t)(b)?(t)(a):(t)(b)) +#define min(a,b) ((a)<(b)?(a):(b)) +#define max(a,b) ((a)>(b)?(a):(b)) +#define clamp(x,l,h) ((x)<(l)?(l):(x)>(h)?(h):(x)) +#define WARN_ON(v) (v) +#define GFP_KERNEL 0 +#define smp_load_acquire(p) (*(p)) +#define smp_store_release(p,v) (*(p)=(v)) +#define READ_ONCE(x) (x) +#define WRITE_ONCE(x,v) ((x)=(v)) +#define static_assert _Static_assert +#define VB2_BUF_STATE_DONE 0 +#define VB2_BUF_STATE_ERROR 1 +enum vb2_buffer_state { STATE_DUMMY }; +#define kzalloc(n,f) calloc(1,n) +#define kfree free +static unsigned int avd_qfw_picture_banks(const struct avd_dev *, unsigned int); +static bool vp_feature=true; +static bool avd_qfw_has_vp_done(const struct avd_dev *d){return d->queued&&vp_feature;} +static void *vb2_plane_vaddr(struct vb2_buffer *b,int p){(void)p;return b->cpu;} +static struct vb2_v4l2_buffer *current_src,*current_dst; +static struct vb2_v4l2_buffer *v4l2_m2m_next_src_buf(void *m){(void)m;return current_src;} +struct v4l2_ctrl {unsigned int id; union {void *p;} p_cur;}; +struct v4l2_ctrl_ref {struct v4l2_ctrl_ref *next; struct v4l2_ctrl *ctrl; bool p_req_valid; union {void *p;} p_req;}; +struct v4l2_ctrl_handler {struct v4l2_ctrl_ref *ctrl_refs;}; +#define list_for_each_entry(ref,head,node) for((ref)=*(head);(ref);(ref)=(ref)->next) +static struct v4l2_ctrl_av1_frame params,request_params; +static struct v4l2_ctrl_av1_sequence seq; +static struct v4l2_ctrl_av1_tile_group_entry tile; +static struct v4l2_ctrl_av1_film_grain grain; +static struct v4l2_ctrl controls[4]; +static struct v4l2_ctrl_ref request_ref; +static struct v4l2_ctrl_handler request_hdl; +static unsigned int request_gets,request_puts; +static struct v4l2_ctrl_handler *v4l2_ctrl_request_hdl_find(void *req,void *h){(void)h;assert(req==&request_hdl);request_gets++;return &request_hdl;} +static void v4l2_ctrl_request_hdl_put(struct v4l2_ctrl_handler *h){assert(h==&request_hdl);request_puts++;} +static struct v4l2_ctrl *v4l2_ctrl_find(void *h,u32 id){ + (void)h;controls[0].p_cur.p=&seq;controls[1].p_cur.p=¶ms;controls[2].p_cur.p=&tile;controls[3].p_cur.p=&grain; + if(id==V4L2_CID_STATELESS_AV1_SEQUENCE)return &controls[0]; + if(id==V4L2_CID_STATELESS_AV1_FRAME)return &controls[1]; + if(id==V4L2_CID_STATELESS_AV1_TILE_GROUP_ENTRY)return &controls[2]; + return &controls[3]; +} +static void avd_run_preamble(struct avd_ctx *ctx,struct avd_run *run){ + (void)ctx;memset(run,0,sizeof(*run));run->bufs.src=current_src;run->bufs.dst=current_dst; + run->coded_in=current_src->vb2_buf.dma;run->comp_out=current_dst->vb2_buf.dma; + run->y_out=run->comp_out+0x100000;run->uv_out=run->comp_out+0x300000; +} +static void avd_run_postamble(struct avd_ctx *ctx,struct avd_run *run){(void)ctx;(void)run;} +static int avd_init_job(struct avd_ctx *ctx,int codec,size_t segments){ + free(ctx->job.segments);ctx->job.codec=codec;ctx->job.num=0; + ctx->job.segments=calloc(segments,sizeof(*ctx->job.segments));return ctx->job.segments?0:-ENOMEM; +} +static int avd_submit_job(struct avd_ctx *ctx); +''' + + +def without_includes(s): + return re.sub(r'^#include[^\n]*\n', '', s, flags=re.M) + + +av1 = (AVD / 'avd-av1.c').read_text() +common = pre + without_includes((AVD / 'avd-av1-entropymode.h').read_text()) +common += without_includes((AVD / 'avd-av1-entropymode.c').read_text()) +common += without_includes((AVD / 'avd-inst.h').read_text()) +qfw = (AVD / 'avd-backend-queue.c').read_text() +hw = (AVD / 'avd-hw.c').read_text() +post = function((AVD / 'avd-backend-open.c').read_text(), 'avd_open_build_program') +post += qfw[qfw.index('#define AVDQ_MAGIC'):qfw.index('/* Arguments of dev_dbg()')] +post += hw[hw.index('#define AVD_V3_'):hw.index('int avd_boot(')] +post += '#define w32(r,v) writel((v),avd->ctrl+(r))\n#define m32(r,v) w32(r,(v)|readl(avd->ctrl+(r)))\n' +post += function(hw, 't8112_configure_stream') + function(hw, 't8122_configure_stream') +post += function(qfw, 'avd_qfw_picture_banks') +post += function(qfw, 'avd_qfw_stream') + function(qfw, 'avd_qfw_submit') +post += '\nstatic int avd_submit_job(struct avd_ctx *ctx){return avd_open_build_program(ctx);}\n' + + +def codec(s): + return without_includes(s[s.index('#define V4L2_AV1_SEG_LVL_ALT_LF_Y_H'):s.index('static enum avd_image_fmt avd_av1_get_image_fmt')]) + function(s, 'avd_av1_pp_kicks') + + +MAIN = r''' +static void write_program(const char *dir,const char *name,struct avd_frame *f){ + char path[4096];snprintf(path,sizeof(path),"%s/%s.bin",dir,name);FILE *out=fopen(path,"wb");assert(out); + assert(fwrite(f->prog->cpu,4,f->prog_words,out)==f->prog_words);fclose(out); + struct avd_dev *dev=f->ctx->dev;struct avd_qfw *q=dev->qfw; + q->wp=0;memset(q->sram,0,0x10000);f->tag=0x10|f->idx;assert(!avd_qfw_submit(dev,f)); + snprintf(path,sizeof(path),"%s/%s-desc.bin",dir,name);out=fopen(path,"wb");assert(out); + assert(fwrite(q->sram+0x8000+f->idx*64,1,64,out)==64);fclose(out); + snprintf(path,sizeof(path),"%s/%s-mmio.bin",dir,name);out=fopen(path,"wb");assert(out); + assert(fwrite(dev->ctrl,1,0x10000,out)==0x10000);fclose(out); +} +static void queue_request(struct vb2_v4l2_buffer *src){ + request_params=params;controls[1].id=V4L2_CID_STATELESS_AV1_FRAME; + request_ref=(struct v4l2_ctrl_ref){.ctrl=&controls[1],.p_req_valid=true,.p_req.p=&request_params}; + request_hdl.ctrl_refs=&request_ref;src->vb2_buf.req_obj.req=&request_hdl; +} +static void flag_table(struct avd_av1_bank *b){ + b->vp_done=true; + /* Actual producer timestamp is slot 5, not the immediately prior slot 0. */ + struct v4l2_ctrl_av1_frame f={.frame_type=V4L2_AV1_INTER_FRAME,.primary_ref_frame=2, + .frame_width_minus_1=1919,.frame_height_minus_1=1087}; + for(unsigned int i=0;i<7;i++)f.ref_frame_idx[i]=i; + f.ref_frame_idx[2]=5;f.reference_frame_ts[5]=b->timestamp; + for(unsigned int defaults=0;defaults<4;defaults++) + for(unsigned int output_disabled=0;output_disabled<2;output_disabled++) + for(unsigned int input_disabled=0;input_disabled<2;input_disabled++) + for(unsigned int producer_seg=0;producer_seg<4;producer_seg++) + for(unsigned int consumer_seg=0;consumer_seg<4;consumer_seg++) + for(unsigned int refresh=0;refresh<256;refresh++){ + f.primary_ref_frame=defaults==1?7:2; + f.frame_type=defaults==2?V4L2_AV1_INTRA_ONLY_FRAME:V4L2_AV1_INTER_FRAME; + f.flags=(defaults==3?V4L2_AV1_FRAME_FLAG_ERROR_RESILIENT_MODE:0)| + (output_disabled?V4L2_AV1_FRAME_FLAG_DISABLE_FRAME_END_UPDATE_CDF:0); + f.refresh_frame_flags=refresh; + f.segmentation.flags=(consumer_seg&1?V4L2_AV1_SEGMENTATION_FLAG_ENABLED:0)| + (consumer_seg&2?V4L2_AV1_SEGMENTATION_FLAG_UPDATE_MAP:0); + b->flags=input_disabled?V4L2_AV1_FRAME_FLAG_DISABLE_FRAME_END_UPDATE_CDF:0; + b->seg_flags=(producer_seg&1?V4L2_AV1_SEGMENTATION_FLAG_ENABLED:0)| + (producer_seg&2?V4L2_AV1_SEGMENTATION_FLAG_UPDATE_MAP:0); + bool expect=defaults!=1&&((!defaults&&!input_disabled)||((consumer_seg&1)&&producer_seg==3)); + assert(avd_av1_depends(&f,b)==expect); + } + for(unsigned int primary=0;primary<7;primary++){ + f.primary_ref_frame=primary;f.flags=0;f.frame_type=V4L2_AV1_INTER_FRAME;f.segmentation.flags=0; + b->flags=0;f.ref_frame_idx[primary]=5;assert(avd_av1_depends(&f,b)); + f.reference_frame_ts[5]++;assert(!avd_av1_depends(&f,b));f.reference_frame_ts[5]--; + } + f.primary_ref_frame=2;f.flags=V4L2_AV1_FRAME_FLAG_USE_REF_FRAME_MVS;f.reference_frame_ts[5]=b->timestamp; + assert(avd_av1_depends(&f,b));f.primary_ref_frame=7;assert(avd_av1_depends(&f,b)); + f.reference_frame_ts[5]++;assert(!avd_av1_depends(&f,b)); + puts("PASS: AV1 flag table (primary, consumer/producer CDF flags, segmentation enabled/update_map, every refresh mask), actual slot and MV dependencies"); +} +int main(int argc,char **argv){ + assert(argc==2); + struct avd_variant variant={.revision=4,.quirks=0,.fifo_slots=15,.vp_slots={4,4,1,3}}; + struct avd_dev dev={.variant=&variant,.queued=true,.capabilities=15}; + struct avd_qfw queue={.sram=calloc(1,0x10000),.aperture=0x1094000,.ring_words=0x1800,.fifo_slots=15,.vp_done=true}; + dev.qfw=&queue;dev.ctrl=calloc(1,0x10000);dev.mbox=calloc(1,0x100);variant.configure_stream=t8122_configure_stream; + writel(0x20001,dev.mbox+0x50); + static const struct avd_coded_fmt_ops ops={.pp_kicks=avd_av1_pp_kicks,.vp_done=avd_av1_vp_done}; + static const struct fmt_desc fmt={.ops=&ops}; + struct avd_ctx ctx={.dev=&dev,.coded_fmt_desc=&fmt,.pp_total=1}; + struct avd_buf program={};struct avd_frame frame={.prog=&program,.ctx=&ctx};dev.building=&frame; + struct avd_decoded_buffer dst[5]={};struct vb2_v4l2_buffer src={};u8 coded[512]={}; + src.vb2_buf=(struct vb2_buffer){.dma=0x220000000ULL,.cpu=coded,.payload=512,.timestamp=1000};current_src=&src; + for(unsigned int i=0;i<5;i++){ + dst[i].base.vb.vb2_buf=(struct vb2_buffer){.dma=0x230000000ULL+i*0x1000000ULL,.cpu=calloc(1,0x800000),.planes[0].length=0x800000,.timestamp=1000+i};refs[i]=&dst[i]; + } + ctx.decoded_fmt.fmt.pix_mp.width=1920;ctx.decoded_fmt.fmt.pix_mp.height=1088; + ctx.decoded_fmt.fmt.pix_mp.plane_fmt[0].bytesperline=1920; + assert(!avd_av1_ready(&ctx));allocating_ctx=&ctx;assert(!avd_av1_start(&ctx));allocating_ctx=NULL; + struct avd_av1_stream *p=((struct avd_av1_ctx *)ctx.priv)->stream;assert(p->nbanks==2&&ctx.max_frames==2); + assert(p->bank[0].codec.bufs.probs.addr!=p->bank[1].codec.bufs.probs.addr); + seq=(struct v4l2_ctrl_av1_sequence){.bit_depth=8,.flags=V4L2_AV1_SEQUENCE_FLAG_SUBSAMPLING_X|V4L2_AV1_SEQUENCE_FLAG_SUBSAMPLING_Y}; + params=(struct v4l2_ctrl_av1_frame){.frame_type=V4L2_AV1_KEY_FRAME,.primary_ref_frame=7, + .flags=V4L2_AV1_FRAME_FLAG_DISABLE_FRAME_END_UPDATE_CDF,.frame_width_minus_1=1919,.frame_height_minus_1=1087,.upscaled_width=1920}; + params.tile_info.tile_cols=params.tile_info.tile_rows=1;params.tile_info.width_in_sbs_minus_1[0]=29;params.tile_info.height_in_sbs_minus_1[0]=16; + tile.tile_size=512;tile.tile_col=tile.tile_row=0; + current_dst=&dst[0].base.vb;frame.idx=0;assert(avd_av1_ready(&ctx));assert(!avd_av1_run(&ctx)); + assert(dst[0].bank==1&&p->bank[0].busy&&p->bank[0].prepared); + assert(frame.fifo_iova==p->bank[0].codec.bufs.inst.addr); + write_program(argv[1],"av1-overlap-0",&frame);flag_table(&p->bank[0]); + p->bank[0].flags=params.flags;p->bank[0].seg_flags=0; + params.frame_type=V4L2_AV1_INTER_FRAME;params.primary_ref_frame=0;params.ref_frame_idx[0]=0;params.reference_frame_ts[0]=1000; + params.segmentation.flags=V4L2_AV1_SEGMENTATION_FLAG_ENABLED; + queue_request(&src); + /* p_cur is deliberately stale and different from the pending request. */ + params.flags=0;request_params.flags=V4L2_AV1_FRAME_FLAG_DISABLE_FRAME_END_UPDATE_CDF; + p->bank[0].vp_done=false;assert(!avd_av1_ready(&ctx)); + avd_av1_vp_done(&ctx,&dst[0].base.vb);assert(p->bank[0].busy&&avd_av1_ready(&ctx)); + avd_av1_vp_done(&ctx,&dst[0].base.vb);assert(p->bank[0].busy); + p->bank[0].flags=0;assert(!avd_av1_ready(&ctx));p->bank[0].flags=V4L2_AV1_FRAME_FLAG_DISABLE_FRAME_END_UPDATE_CDF; + p->bank[0].seg_flags=V4L2_AV1_SEGMENTATION_FLAG_ENABLED|V4L2_AV1_SEGMENTATION_FLAG_UPDATE_MAP; + assert(!avd_av1_ready(&ctx));p->bank[0].seg_flags=0; + request_params.flags|=V4L2_AV1_FRAME_FLAG_USE_REF_FRAME_MVS;assert(!avd_av1_ready(&ctx));request_params.flags&=~V4L2_AV1_FRAME_FLAG_USE_REF_FRAME_MVS; + assert(avd_av1_ready(&ctx));params=request_params; + /* CAPTURE tails are poisoned: successor must consume preparation snapshots. */ + size_t tail=AVD_AV1_CDFS_OFFSET(0x800000,dst[0].av1.color_size); + memset((u8 *)dst[0].base.vb.vb2_buf.cpu+tail,0xee,sizeof(struct avd_av1_cdfs)); + memset(p->bank[0].codec.bufs.saved_seg.cpu,0x35,dst[0].av1.seg_size); + current_dst=&dst[1].base.vb;src.vb2_buf.timestamp=1001;frame.idx=1; + assert(!avd_av1_run(&ctx));assert(dst[1].bank==2&&p->bank[0].busy&&p->bank[1].busy); + assert(!memcmp(p->bank[0].codec.bufs.probs.cpu,p->bank[1].codec.bufs.probs.cpu,sizeof(struct avd_av1_cdfs))); + assert(!memcmp(p->bank[0].codec.bufs.saved_seg.cpu,p->bank[1].codec.bufs.prev_seg.cpu,dst[1].av1.seg_size)); + assert(p->bank[1].codec.bufs.prev_seg.addr!=p->bank[0].codec.bufs.saved_seg.addr); + write_program(argv[1],"av1-overlap-1",&frame);assert(!avd_av1_ready(&ctx)); + current_dst=&dst[2].base.vb;assert(avd_av1_run(&ctx)==-EBUSY);assert(!dst[2].bank); + avd_av1_done(&ctx,&src,&dst[0].base.vb,VB2_BUF_STATE_DONE); + assert(!p->bank[0].busy&&p->bank[1].busy&&dst[1].bank==2); + /* Producer bank reuse must not invalidate the consumer's VP map input. */ + memset(p->bank[0].codec.bufs.saved_seg.cpu,0x98,dst[0].av1.seg_size); + assert(((u8 *)p->bank[1].codec.bufs.prev_seg.cpu)[0]==0x35); + avd_av1_done(&ctx,&src,&dst[0].base.vb,VB2_BUF_STATE_DONE);assert(p->bank[1].busy); + avd_av1_done(&ctx,&src,&dst[1].base.vb,VB2_BUF_STATE_ERROR);assert(!p->bank[1].busy&&avd_av1_ready(&ctx)); + assert(request_gets==request_puts); + avd_av1_stop(&ctx);assert(!avd_av1_ready(&ctx)); + for(unsigned int mode=0;mode<2;mode++){ + dev.queued=mode;vp_feature=false;assert(!avd_av1_start(&ctx));p=((struct avd_av1_ctx *)ctx.priv)->stream; + assert(p->nbanks==1&&ctx.max_frames==1);avd_av1_stop(&ctx); + } + dev.queued=true;vp_feature=true; + for(int n=1;n<=6;n++){ + fail_alloc_after=n;allocating_ctx=&ctx;assert(avd_av1_start(&ctx)==-ENOMEM);allocating_ctx=NULL; + assert(!ctx.priv&&!avd_av1_ready(&ctx)); + } + + /* AV1 pool size is firmware-owned, including sparse masks and FIFO bounds. */ + for(unsigned int count=1;count<=3;count++){ + queue.multi_vp=true;queue.vp_pool[3]=((1u<stream; + assert(p->nbanks==count+1&&ctx.max_frames==count+1); + avd_av1_stop(&ctx);assert(ctx.max_frames==1); + } + queue.vp_pool[3]=BIT(9)|BIT(11);queue.fifo_slots=2; + assert(!avd_av1_start(&ctx));p=((struct avd_av1_ctx *)ctx.priv)->stream; + assert(p->nbanks==2);avd_av1_stop(&ctx); + queue.fifo_slots=15;queue.vp_pool[3]=BIT(9)|BIT(10)|BIT(11);queue.next_vp[3]=0; + assert(!avd_av1_start(&ctx));p=((struct avd_av1_ctx *)ctx.priv)->stream; + assert(p->nbanks==4&&ctx.max_frames==4); + params.primary_ref_frame=7;params.flags=V4L2_AV1_FRAME_FLAG_DISABLE_FRAME_END_UPDATE_CDF; + params.segmentation.flags=0; + for(unsigned int i=0;i<7;i++)params.ref_frame_idx[i]=-1; + for(unsigned int n=0;n<4;n++){ + src.vb2_buf.timestamp=1000+n;current_dst=&dst[n].base.vb;frame.idx=n; + queue_request(&src);assert(avd_av1_ready(&ctx));assert(!avd_av1_run(&ctx)); + char stem[64];snprintf(stem,sizeof(stem),"av1-multi-%u",n);write_program(argv[1],stem,&frame); + assert(dst[n].bank==n+1&&p->bank[n].busy&&p->bank[n].prepared); + assert(readl(queue.sram+0x8000+n*64+D_VP)==9+n%3); + for(unsigned int prev=0;prevbank[prev].codec.bufs.inst.addr!=p->bank[n].codec.bufs.inst.addr); + assert(p->bank[prev].codec.bufs.probs.addr!=p->bank[n].codec.bufs.probs.addr); + } + } + assert(!avd_av1_ready(&ctx));current_dst=&dst[4].base.vb; + assert(avd_av1_run(&ctx)==-EBUSY&&!dst[4].bank); + /* Free a bank, then reference each of the other engines' live producers. */ + avd_av1_done(&ctx,&src,&dst[3].base.vb,VB2_BUF_STATE_DONE); + for(unsigned int n=0;n<3;n++){ + request_params.primary_ref_frame=0;request_params.ref_frame_idx[0]=n; + request_params.reference_frame_ts[n]=1000+n; + p->bank[n].flags=V4L2_AV1_FRAME_FLAG_DISABLE_FRAME_END_UPDATE_CDF; + assert(!avd_av1_ready(&ctx));avd_av1_vp_done(&ctx,&dst[n].base.vb); + assert(p->bank[n].busy&&p->bank[n].vp_done&&avd_av1_ready(&ctx)); + p->bank[n].flags=0;assert(!avd_av1_ready(&ctx)); + p->bank[n].flags=V4L2_AV1_FRAME_FLAG_DISABLE_FRAME_END_UPDATE_CDF; + assert(avd_av1_ready(&ctx)); + p->bank[n].seg_flags=V4L2_AV1_SEGMENTATION_FLAG_ENABLED|V4L2_AV1_SEGMENTATION_FLAG_UPDATE_MAP; + request_params.segmentation.flags=V4L2_AV1_SEGMENTATION_FLAG_ENABLED; + assert(!avd_av1_ready(&ctx));request_params.segmentation.flags=0;p->bank[n].seg_flags=0; + request_params.flags|=V4L2_AV1_FRAME_FLAG_USE_REF_FRAME_MVS; + assert(!avd_av1_ready(&ctx));request_params.flags&=~V4L2_AV1_FRAME_FLAG_USE_REF_FRAME_MVS; + } + /* Reset/error retirement must release the saved bank, irrespective of builder. */ + for(unsigned int n=3;n>0;n--){ + avd_av1_done(&ctx,&src,&dst[n-1].base.vb,VB2_BUF_STATE_ERROR); + assert(!p->bank[n-1].busy&&!dst[n-1].bank); + } + assert(avd_av1_ready(&ctx)); + params.primary_ref_frame=7;params.segmentation.flags=0; + current_dst=&dst[0].base.vb;frame.idx=0;assert(!avd_av1_run(&ctx)); + assert(dst[0].bank==1&&p->bank[0].busy); + avd_av1_done(&ctx,&src,&dst[0].base.vb,VB2_BUF_STATE_DONE);avd_av1_stop(&ctx); + /* Fail allocation in every bank, including after earlier banks succeeded. */ + for(int n=1;n<=12;n++){ + fail_alloc_after=n;allocating_ctx=&ctx;assert(avd_av1_start(&ctx)==-ENOMEM);allocating_ctx=NULL; + assert(!ctx.priv&&!avd_av1_ready(&ctx)); + } + puts("PASS: AV1 firmware pool sizes, sparse masks, FIFO bounds, per-engine dispatch, all producer barriers, exhaustion, reset/error and reuse"); + for(unsigned int i=0;i<5;i++)free(dst[i].base.vb.vb2_buf.cpu); + free(program.cpu);free(queue.sram);free(dev.ctrl);free(dev.mbox); + puts("PASS: AV1 snapshot inheritance, poisoned CAPTURE, request lookahead, banks, PP retirement, exhaustion, failures and legacy fallback"); +} +''' + + +def build_run(name, source): + (OUT / f'{name}.c').write_text(source) + cmd = [os.environ.get('CC', 'cc'), '-std=gnu11', '-g', '-O1', '-Wall', '-Wextra', '-Werror', + '-Wno-unused-function', '-Wno-unused-variable', '-Wno-unused-parameter', '-Wno-sign-compare', '-Wno-pointer-sign', + '-fsanitize=address,undefined', '-fno-omit-frame-pointer', str(OUT / f'{name}.c'), '-o', str(OUT / name)] + subprocess.run(cmd, check=True) + subprocess.run([str(OUT / name), str(OUT)], check=True) + + +build_run('av1-host', common + codec(av1) + post + MAIN) +(OUT / 'source.sha256').write_text('\n'.join(hashlib.sha256(p.read_bytes()).hexdigest() + ' ' + str(p) for p in ( + AVD / 'avd-av1.c', AVD / 'avd-av1-entropymode.c', AVD / 'avd-backend-open.c', AVD / 'avd-backend-queue.c')) + '\n') + +base_ref = os.environ.get('AVD_AV1_BASELINE_REF') +if base_ref: + old = subprocess.check_output(['git', '-C', str(KERNEL), 'show', + base_ref + ':drivers/media/platform/apple/avd/avd-av1.c'], text=True) + legacy = r''' +int main(int argc,char **argv){ + assert(argc==2); + struct avd_variant variant={.revision=4,.fifo_slots=15}; + struct avd_dev dev={.variant=&variant,.queued=false}; + struct avd_coded_fmt_ops ops={};struct fmt_desc fmt={.ops=&ops}; + struct avd_ctx ctx={.dev=&dev,.coded_fmt_desc=&fmt}; + struct avd_buf program={};struct avd_frame f={.prog=&program,.ctx=&ctx};dev.building=&f; + struct avd_decoded_buffer dst[2]={};struct vb2_v4l2_buffer src={};u8 coded[512]={}; + current_src=&src;src.vb2_buf=(struct vb2_buffer){.dma=0x220000000ULL,.cpu=coded,.payload=512}; + for(unsigned int i=0;i<2;i++){ + dst[i].base.vb.vb2_buf=(struct vb2_buffer){.dma=0x230000000ULL+i*0x1000000ULL,.cpu=calloc(1,0x800000),.planes[0].length=0x800000,.timestamp=1000+i};refs[i]=&dst[i]; + } + ctx.inst.addr=0x205000000ULL; + ctx.decoded_fmt.fmt.pix_mp.width=1920;ctx.decoded_fmt.fmt.pix_mp.height=1088; + ctx.decoded_fmt.fmt.pix_mp.plane_fmt[0].bytesperline=1920; + seq=(struct v4l2_ctrl_av1_sequence){.bit_depth=8,.flags=V4L2_AV1_SEQUENCE_FLAG_SUBSAMPLING_X|V4L2_AV1_SEQUENCE_FLAG_SUBSAMPLING_Y}; + params=(struct v4l2_ctrl_av1_frame){.frame_type=V4L2_AV1_KEY_FRAME,.primary_ref_frame=7, + .frame_width_minus_1=1919,.frame_height_minus_1=1087,.upscaled_width=1920}; + params.tile_info.tile_cols=params.tile_info.tile_rows=1;params.tile_info.width_in_sbs_minus_1[0]=29;params.tile_info.height_in_sbs_minus_1[0]=16;tile.tile_size=512; + assert(!avd_av1_start(&ctx)); + for(unsigned int mode=0;mode<12;mode++){ + current_dst=&dst[!!mode].base.vb; + params.frame_type=mode==0?V4L2_AV1_KEY_FRAME:mode==2?V4L2_AV1_INTRA_ONLY_FRAME:V4L2_AV1_INTER_FRAME; + params.primary_ref_frame=mode==0||mode==4?7:0; + params.ref_frame_idx[0]=0;params.reference_frame_ts[0]=1000; + params.flags=(mode==3?V4L2_AV1_FRAME_FLAG_ERROR_RESILIENT_MODE:0)|(mode==5?V4L2_AV1_FRAME_FLAG_DISABLE_FRAME_END_UPDATE_CDF:0); + seq.bit_depth=mode>=10?10:8; + if(mode>=8){ + seq.flags|=V4L2_AV1_SEQUENCE_FLAG_FILM_GRAIN_PARAMS_PRESENT; + grain.flags=V4L2_AV1_FILM_GRAIN_FLAG_APPLY_GRAIN|V4L2_AV1_FILM_GRAIN_FLAG_UPDATE_GRAIN; + grain.grain_seed=1234+mode; + params.flags|=mode&1?V4L2_AV1_FRAME_FLAG_SHOW_FRAME:V4L2_AV1_FRAME_FLAG_SHOWABLE_FRAME; + } + params.segmentation.flags=mode>=6?V4L2_AV1_SEGMENTATION_FLAG_ENABLED:0; + if(mode==7)params.segmentation.flags|=V4L2_AV1_SEGMENTATION_FLAG_UPDATE_MAP; + assert(!avd_av1_run(&ctx)); + char path[4096];snprintf(path,sizeof(path),"%s/@NAME@-%u.bin",argv[1],mode);FILE *out=fopen(path,"wb");assert(out); + assert(fwrite(program.cpu,4,f.prog_words,out)==f.prog_words); + struct avd_decoded_buffer *d=&dst[!!mode];size_t off=AVD_AV1_SEG_OFFSET(0x800000,d->av1.color_size,d->av1.seg_size); + assert(fwrite((u8 *)d->base.vb.vb2_buf.cpu+off,1,d->av1.seg_size+ALIGN(AVD_CDFS_SIZE,AVD_ALIGN),out)==d->av1.seg_size+ALIGN(AVD_CDFS_SIZE,AVD_ALIGN));fclose(out); + if(mode==0){ + size_t cdf=AVD_AV1_CDFS_OFFSET(0x800000,dst[0].av1.color_size); + memset((u8 *)dst[0].base.vb.vb2_buf.cpu+cdf,0x57,AVD_CDFS_SIZE); + memset((u8 *)dst[0].base.vb.vb2_buf.cpu+cdf-dst[0].av1.seg_size,0x36,dst[0].av1.seg_size); + } + } + avd_av1_stop(&ctx);free(program.cpu); + for(unsigned int i=0;i<2;i++)free(dst[i].base.vb.vb2_buf.cpu); +} +''' + for name, body in [('legacy-before', old), ('legacy-after', av1)]: + shim = 'static bool avd_av1_ready(struct avd_ctx *ctx){(void)ctx;return false;}\n' if name == 'legacy-before' else '' + build_run(name, common + codec(body) + shim + post + legacy.replace('@NAME@', name)) + for mode in range(12): + assert (OUT / f'legacy-before-{mode}.bin').read_bytes() == (OUT / f'legacy-after-{mode}.bin').read_bytes(), mode + print('PASS: separate baseline/current executables emit byte-identical legacy AV1 programs and CDF/map tails for twelve header cases including film grain/show flags at 8/10 bits') diff --git a/drivers/media/platform/apple/avd/tests/unicorn-av1-test.py b/drivers/media/platform/apple/avd/tests/unicorn-av1-test.py new file mode 100644 index 0000000000000..c635e3c2452e7 --- /dev/null +++ b/drivers/media/platform/apple/avd/tests/unicorn-av1-test.py @@ -0,0 +1,231 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only OR MIT +"""AV1 production programs: VP(N+1) starts after VP(N) DONE, during PP(N). + +Usage: unicorn-av1-test.py FIRMWARE_TREE v5-t2_IMAGE AV1_HOST_OUTPUT +CDF/map input snapshots are host fixtures. CM3 code, descriptors, program words, +PP commands and configure_stream writes are production. No pixels are decoded. +""" +import importlib.util +from pathlib import Path +import struct +import sys +from unicorn.arm_const import UC_ARM_REG_SP + +fw, image, programs = map(lambda p: Path(p).resolve(), sys.argv[1:4]) +spec = importlib.util.spec_from_file_location('transport', fw / 'tests/transport-test.py') +transport = importlib.util.module_from_spec(spec) +spec.loader.exec_module(transport) + + +class AV1(transport.Transport): + def __init__(self, multi=False): + self.multi = multi + self.engines = {} + self.live_engines = {} + self.booting = False + self.expected = [] + self.events = [] + self.live = self.pp_owner = None + self.feed = self.word = 0 + super().__init__(image) + assert self.version == 5 and self.tier == 2 + self.booting = True + self.uc.reg_write(UC_ARM_REG_SP, self.u32(0)) + self.uc.emu_start(self.u32(4), 0, count=500000) + assert not self.booting + + def drain(self, n): + # Simulate a host consuming each available token; self.busy separately + # models a deliberately blocked outbox in the poll-only sections. + for _ in range(n): + self.busy = False + self.poll() + + def route(self, irq): + if irq < 14: + router = self.u32(0x50010010) & (1 << irq) + else: + off = irq - 14 + router = self.u32(0x50010014 + off // 32 * 4) & (1 << (off % 32)) + nvic = self.u32(0xe000e100 + irq // 32 * 4) & (1 << (irq % 32)) + assert router and nvic, f'IRQ {irq} masked' + self.call(f'irq{irq}') + + def write(self, uc, access, addr, size, value, user): + if self.booting and addr == 0x50010090 and value == 1: + self.booting = False + uc.emu_stop() + if self.decode + self.fifo <= addr < self.decode + self.fifo + self.vps * 4: + idx, words = self.expected[self.feed] + assert value == words[self.word], (idx, self.word, value) + vp = self.engines[idx] + assert addr == self.decode + self.fifo + vp * 4 + if not self.word and self.multi: + assert vp not in self.live_engines, "VP reused before its DONE" + self.live_engines[vp] = idx + if not self.word and not self.multi: + assert self.live is None, 'successor issued before VP DONE' + self.live = idx + self.events.append(f'AV1 VP START {idx}; PP owner={self.pp_owner}') + self.word += 1 + if self.word == len(words): + self.feed += 1 + self.word = 0 + if addr == self.decode + self.pp: + idx = (value >> 4) & 15 + assert self.pp_owner is None, 'successor PP issued before predecessor PP DONE' + assert value == 0x2b00020f | idx << 4 + self.pp_owner = idx + self.events.append(f'PP START {idx}') + if addr == 0x50010060: + self.events.append(f'notification {value:#x}') + super().write(uc, access, addr, size, value, user) + + def submit_host(self, idx, start): + stem = f"av1-{'multi' if self.multi else 'overlap'}-{idx}" + data = (programs / f'{stem}.bin').read_bytes() + words = list(struct.unpack('<' + 'I' * (len(data) // 4), data)) + desc = list(struct.unpack('<16I', (programs / f'{stem}-desc.bin').read_bytes())) + vp = 9 + idx % 3 if self.multi else 9 + assert self.pool[3] & (1 << vp) + assert desc[:4] == [0x41565131, 0x10 | idx, 3, vp] + self.engines[idx] = vp + assert desc[4:6] == [0, 0] and desc[6] == len(words) + assert desc[8:16] == [1, 0, 0, 3, 0, 0, 0, 0] + assert words[0] == 0x2b000200 | idx << 4 + assert words[-1] == 0x2b000400 | idx << 4 + mmio = (programs / f'{stem}-mmio.bin').read_bytes() + for offset in (0x1d0, 0x20c, 0x248, 0x284, 0x2c0): + at = offset + idx * 4 + self.put(self.decode + at, struct.unpack_from('> 16 == 0x5453] + + +for blocked in (False, True): + t = AV1() + wp = t.submit_host(0, 0) + t.drain(20) + assert t.live == 0 and t.pp_owner is None and not pp_tokens(t) + t.finish_vp(0) + t.drain(20) + assert t.pp_owner == 0 and t.live is None and not pp_tokens(t) + # The host consumes the tagged VP barrier before dispatching this named + # reference. Its CDF/map inputs come from immutable preparation snapshots. + wp = t.submit_host(1, wp) + t.drain(20) + assert t.live == 1 and t.pp_owner == 0, 'no AV1 VP/PP overlap' + t.finish_vp(1) + t.drain(20) + assert t.pp_owner == 0 and not pp_tokens(t) + t.busy = blocked + t.finish_pp(0) + t.poll(20) + if blocked: + assert not pp_tokens(t) and t.result(0) == [1, 0, 0] + t.busy = False + t.drain(20) + assert t.result(0) == [2, 0, 0x10] and pp_tokens(t) == [0x54530010] + assert t.pp_owner == 1 + t.busy = False + t.finish_pp(1) + t.drain(20) + assert t.result(1) == [2, 0, 0x11] + assert t.notifications == [0x54540010, 0x54540011, 0x54530010, 0x54530011] + assert t.u32(0x10007ff8) == t.u32(0x10007ffc) == wp + assert not t.fetches and not t.ring_writes + assert t.u32(0x1000803c) == t.u32(0x1000807c) == 1 + print('\n'.join('TRACE ' + e for e in t.events)) + print(f'PASS: AV1 snapshot picture overlap, blocked PP mailbox={blocked}; VP(1) issued before PP(0) DONE') + + +for blocked in (False, True): + t = AV1(multi=True) + wp = 0 + for idx in range(4): + wp = t.submit_host(idx, wp) + t.drain(30) + assert t.live_engines == {9: 0, 10: 1, 11: 2} + assert t.feed == 3 and t.pp_owner is None + # DONE on a later engine never starts its PP before predecessor VP DONE. + for idx in (2, 1): + t.finish_vp(idx) + t.drain(10) + assert t.pp_owner is None and not pp_tokens(t) + t.finish_vp(0) + t.drain(20) + assert t.pp_owner == 0 and t.live_engines == {9: 3} + t.finish_vp(3) + t.drain(10) + assert not t.live_engines and t.pp_owner == 0 + for idx in range(4): + assert t.pp_owner == idx + t.busy = blocked + t.finish_pp(idx) + t.poll(10) + if blocked: + assert t.result(idx) == [1, 0, 0] + t.busy = False + t.drain(10) + assert t.result(idx) == [2, 0, 0x10 | idx] + assert pp_tokens(t) == [0x54530010 | idx for idx in range(4)] + assert [word for word in t.notifications if word >> 16 == 0x5454] == [0x54540012, 0x54540011, 0x54540010, 0x54540013] + assert t.u32(0x10007ff8) == t.u32(0x10007ffc) == wp + assert not t.fetches and not t.ring_writes + print(f'PASS: AV1 three-engine dispatch, fourth waits for VP9, reverse VP DONE, ordered PP, blocked mailbox={blocked}') + +# A routed error on each advertised AV1 engine must fault every owner; restart +# from reset then proves the next set of production descriptors can run again. +for failed_vp in (9, 10, 11): + t = AV1(multi=True) + wp = 0 + for idx in range(3): + wp = t.submit_host(idx, wp) + t.drain(20) + assert len(t.live_engines) == 3 + t.live_engines.clear() + t.route(79 + failed_vp * 5) # engine-specific error, not DONE + t.drain(30) + assert t.pp_owner is None + assert all(t.result(idx) == [2, 2, 0x10 | idx] for idx in range(3)) + assert t.notifications == [0x54530010 | idx for idx in range(3)] + # A fresh CM3 reset boots all three engines after the halt. + restarted = AV1(multi=True) + wp = 0 + for idx in range(3): + wp = restarted.submit_host(idx, wp) + restarted.drain(20) + assert restarted.live_engines == {9: 0, 10: 1, 11: 2} + print(f'PASS: AV1 VP{failed_vp} routed error drains all owners; reset restores all engines') diff --git a/tools/asahi/avd-codec-tail-tests.py b/tools/asahi/avd-codec-tail-tests.py index a1d57aa2e763c..80cf67b85a4d0 100755 --- a/tools/asahi/avd-codec-tail-tests.py +++ b/tools/asahi/avd-codec-tail-tests.py @@ -189,6 +189,10 @@ def main(): print(json.dumps({"passed": ok, "cases": len(cases), "variants": [{k: x[k] for k in ("name", "failed_cases", "verdict")} for x in results]})) + # Keep production AV1 bank/dependency coverage in the registered host suite. + if ok and not args.ref and not args.mutations: + subprocess.run([sys.executable, "-B", str(ROOT / "drivers/media/platform/apple/avd/tests/av1-test.py"), + str(args.output / "av1-banks")], check=True) return 0 if ok else 1