From a29e30820844442bb37a86ded77a86cba52030e0 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Sun, 1 Nov 2026 00:11:41 +1100 Subject: [PATCH 1/7] media: apple: avd: submit tagged multi-VP firmware work Cap admitted slots at the match-data FIFO budget and submit packetized programs through open firmware ABI 2. Give each slot independent FIFO storage and let firmware select an available engine from its codec pool. Keep predecessor-complete dependencies as the default, serialize PP work, and reset before retiring fatal firmware errors. Release the m2m job when a complete picture owns its buffers so separate contexts can run concurrently. Preserve codec-specific PP work counts, which can differ from VP program segments. Add FIFO-limit and PP-count KUnit cases plus a host test linking the real firmware scheduler. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/media/platform/apple/avd/Makefile | 2 +- drivers/media/platform/apple/avd/avd-av1.c | 1 + .../platform/apple/avd/avd-backend-open.c | 26 +- .../platform/apple/avd/avd-backend-queue.c | 240 ++++++++++++++++++ drivers/media/platform/apple/avd/avd-drv.c | 12 +- drivers/media/platform/apple/avd/avd-h264.c | 1 + drivers/media/platform/apple/avd/avd-hevc.c | 1 + .../media/platform/apple/avd/avd-multi-vp.rst | 101 ++++++++ .../platform/apple/avd/avd-open-protocol.h | 30 +++ .../platform/apple/avd/avd-queue-internal.h | 22 +- .../media/platform/apple/avd/avd-queue-test.c | 64 +++++ drivers/media/platform/apple/avd/avd-queue.c | 20 +- drivers/media/platform/apple/avd/avd-queue.h | 4 + drivers/media/platform/apple/avd/avd-vp9.c | 1 + drivers/media/platform/apple/avd/avd.h | 10 + .../apple/avd/tests/include/linux/errno.h | 2 +- .../apple/avd/tests/include/linux/types.h | 10 +- .../platform/apple/avd/tests/multi-vp-host.c | 116 +++++++++ .../apple/avd/tests/run-multi-stream.sh | 26 ++ .../platform/apple/avd/tests/run-multi-vp.sh | 10 + 20 files changed, 676 insertions(+), 23 deletions(-) create mode 100644 drivers/media/platform/apple/avd/avd-backend-queue.c create mode 100644 drivers/media/platform/apple/avd/avd-multi-vp.rst create mode 100644 drivers/media/platform/apple/avd/avd-open-protocol.h create mode 100644 drivers/media/platform/apple/avd/tests/multi-vp-host.c create mode 100644 drivers/media/platform/apple/avd/tests/run-multi-stream.sh create mode 100644 drivers/media/platform/apple/avd/tests/run-multi-vp.sh diff --git a/drivers/media/platform/apple/avd/Makefile b/drivers/media/platform/apple/avd/Makefile index 64095b4242133..e81514ed6c1e9 100644 --- a/drivers/media/platform/apple/avd/Makefile +++ b/drivers/media/platform/apple/avd/Makefile @@ -2,6 +2,6 @@ apple-avd-y := avd-drv.o avd-v4l2.o avd-hw.o avd-h264.o avd-vp9.o avd-hevc.o \ avd-av1.o avd-av1-entropymode.o avd-queue.o avd-backend-open.o \ - avd-backend-apple.o + avd-backend-apple.o avd-backend-queue.o apple-avd-$(CONFIG_VIDEO_APPLE_AVD_KUNIT_TEST) += avd-h264-test.o avd-queue-test.o obj-$(CONFIG_VIDEO_APPLE_AVD) += apple-avd.o diff --git a/drivers/media/platform/apple/avd/avd-av1.c b/drivers/media/platform/apple/avd/avd-av1.c index 60d69bab8a342..2d344befa84a2 100644 --- a/drivers/media/platform/apple/avd/avd-av1.c +++ b/drivers/media/platform/apple/avd/avd-av1.c @@ -1002,6 +1002,7 @@ static void set_tiles(struct avd_ctx *ctx, struct avd_av1_run *run) 4; av1_ctx->submit_num = tile_info->tile_cols * tile_info->tile_rows; + ctx->job.pp_execs = av1_ctx->submit_num; for (row = 0; row < tile_info->tile_rows; row++) { for (col = 0; col < tile_info->tile_cols; col++) { diff --git a/drivers/media/platform/apple/avd/avd-backend-open.c b/drivers/media/platform/apple/avd/avd-backend-open.c index 13729c9e4a0b9..33bbdca377f8e 100644 --- a/drivers/media/platform/apple/avd/avd-backend-open.c +++ b/drivers/media/platform/apple/avd/avd-backend-open.c @@ -44,17 +44,19 @@ int avd_open_build_program(struct avd_ctx *ctx) AVD_OP_EXEC_REV3_VP9_MASK : 0; start = AVD_OP_EXEC_FLAG_START_REV4(avd->variant->revision == 4) | AVD_OP_EXEC_FLAG_START_REV3(avd->variant->revision == 3); - ctx->fifo_idx = 0; + ctx->fifo_idx = avd_open_is_queued(avd) ? f->idx : 0; words = f->prog->cpu; words[pos++] = AVD_OP_EXEC | mask | start | AVD_OP_EXEC_FIFO_IDX(ctx->fifo_idx); for (i = 0; i <= job->num; i++) { for (j = 0; j < job->segments[i].num; j++) words[pos++] = job->segments[i].instructions[j]; if (i) - words[pos++] = AVD_OP_EXEC | mask | AVD_OP_EXEC_FLAG_END(i == job->num); + words[pos++] = AVD_OP_EXEC | mask | AVD_OP_EXEC_FIFO_IDX(ctx->fifo_idx) | + AVD_OP_EXEC_FLAG_END(i == job->num); } f->prog_words = pos; f->cmd[0] = job->codec; + f->cmd[1] = job->pp_execs; kvfree(job->segments); job->segments = NULL; return 0; @@ -62,7 +64,7 @@ int avd_open_build_program(struct avd_ctx *ctx) static unsigned int avd_open_max_inflight(struct avd_dev *avd) { - return 1; + return avd_open_queue_max(avd); } static int avd_open_submit(struct avd_dev *avd, struct avd_frame *f) @@ -73,6 +75,8 @@ static int avd_open_submit(struct avd_dev *avd, struct avd_frame *f) unsigned int i; void __iomem *reg; + if (avd_open_is_queued(avd)) + return avd_open_queue_submit(avd, f); if (avd->compat_active) return -EBUSY; /* Native frame_ops require firmware-side PP chaining and tagged replies. */ @@ -119,7 +123,11 @@ static int avd_open_submit(struct avd_dev *avd, struct avd_frame *f) static irqreturn_t avd_open_irq(struct avd_dev *avd) { struct avd_frame *f = avd->compat_active; - u32 status = readl(avd->mbox + AVD_REG_MBOX1_RETRIEVE); + u32 status; + + if (avd_open_is_queued(avd)) + return avd_open_queue_irq(avd); + status = readl(avd->mbox + AVD_REG_MBOX1_RETRIEVE); writel(AVD_MBOX1_NOT_EMPTY, avd->mbox + AVD_REG_MBOX_IRQ_CLR); if (!f) @@ -140,8 +148,8 @@ static irqreturn_t avd_open_irq(struct avd_dev *avd) } const struct avd_backend_ops avd_open_backend = { - .name = "open-compat", - .boot = avd_boot, + .name = "open", + .boot = avd_open_queue_boot, .shutdown = avd_shutdown, .max_inflight = avd_open_max_inflight, .submit = avd_open_submit, @@ -190,6 +198,9 @@ int avd_backend_select(struct avd_dev *avd) return ret; } avd->backend = &avd_open_backend; + ret = avd_open_queue_probe(avd); + if (ret) + return ret; ret = request_firmware(&avd->fw, avd->variant->fw_name, avd->dev); if (!ret) avd_open_capabilities(avd); @@ -205,6 +216,9 @@ int avd_backend_boot(struct avd_dev *avd) if (ret) return ret; avd->backend = &avd_open_backend; + ret = avd_open_queue_probe(avd); + if (ret) + return ret; avd_open_capabilities(avd); ret = avd->backend->boot(avd); } diff --git a/drivers/media/platform/apple/avd/avd-backend-queue.c b/drivers/media/platform/apple/avd/avd-backend-queue.c new file mode 100644 index 0000000000000..3398c6881d35f --- /dev/null +++ b/drivers/media/platform/apple/avd/avd-backend-queue.c @@ -0,0 +1,240 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Tagged open-firmware queue. VP/FIFO setup and PP chaining run on the CM3. */ +#include +#include +#include +#include + +#include "avd.h" +#include "avd-inst.h" +#include "avd-open-protocol.h" +#include "avd-regs.h" + +#define OPEN_MBOX0_STATUS 0x50 +#define OPEN_MBOX0_TX 0x54 +#define OPEN_MBOX0_EMPTY BIT(0) + +struct avd_open_queue { + void __iomem *sram; + void __iomem *piodma; + resource_size_t sram_phys, sram_size; + bool enabled; + u32 ring, header; + struct avd_buf packets[AVD_MAX_FRAMES]; + struct avd_buf fifo[AVD_MAX_FRAMES]; +}; + +static void avd_open_queue_release(void *data) +{ + struct avd_dev *avd = data; + struct avd_open_queue *q = avd->open_priv; + unsigned int i; + + for (i = 0; i < AVD_MAX_FRAMES; i++) { + avd_buf_free(avd, &q->packets[i]); + avd_buf_free(avd, &q->fifo[i]); + } +} + +int avd_open_queue_probe(struct avd_dev *avd) +{ + struct avd_open_queue *q; + struct resource *sram, *piodma; + + if (avd->open_priv) + return 0; + q = devm_kzalloc(avd->dev, sizeof(*q), GFP_KERNEL); + if (!q) + return -ENOMEM; + avd->open_priv = q; + sram = platform_get_resource_byname(avd->pdev, IORESOURCE_MEM, "sram"); + piodma = platform_get_resource_byname(avd->pdev, IORESOURCE_MEM, "piodma"); + if (sram && piodma) { + q->sram = devm_ioremap_resource(avd->dev, sram); + if (IS_ERR(q->sram)) + return PTR_ERR(q->sram); + q->piodma = devm_ioremap_resource(avd->dev, piodma); + if (IS_ERR(q->piodma)) + return PTR_ERR(q->piodma); + q->sram_phys = sram->start; + q->sram_size = resource_size(sram); + } + return devm_add_action_or_reset(avd->dev, avd_open_queue_release, avd); +} + +bool avd_open_is_queued(struct avd_dev *avd) +{ + struct avd_open_queue *q = avd->open_priv; + + return q && READ_ONCE(q->enabled); +} + +int avd_open_queue_boot(struct avd_dev *avd) +{ + struct avd_open_queue *q = avd->open_priv; + u32 info[16], dst, header; + unsigned int i; + int ret; + + WRITE_ONCE(q->enabled, false); + /* Stop old DMA/IRQs and drain stale notifications before a new generation. */ + avd_shutdown(avd); + for (i = 0; i < AVD_MAX_FRAMES + 1; i++) { + if (readl(avd->mbox + AVD_REG_MBOX1_STATUS) & AVD_MBOX1_EMPTY) + break; + readl(avd->mbox + AVD_REG_MBOX1_RETRIEVE); + writel(AVD_MBOX1_NOT_EMPTY, avd->mbox + AVD_REG_MBOX_IRQ_CLR); + } + if (q->piodma) + writel(q->sram_phys >> 4, q->piodma + 0x24); + ret = avd_boot(avd); + if (ret) + return ret; + /* Old DT/firmware retains the serial compatibility transport. */ + if (!q->sram || q->sram_size < 0x9400) + return 0; + for (i = 0; i < ARRAY_SIZE(info); i++) + info[i] = readl(q->sram + AVD_OPEN_INFO + i * 4); + if (info[0] != AVD_OPEN_MAGIC) + return 0; + if (info[1] != AVD_OPEN_VERSION || info[2] != AVD_MAX_FRAMES || + info[3] != sizeof(struct avd_open_desc) || info[5] != AVD_OPEN_RING || + info[6] != AVD_OPEN_PACKET_WORDS || info[10] != avd->variant->fifo_slots || + info[4] > U32_MAX - AVD_OPEN_RING - AVD_MAX_FRAMES * AVD_OPEN_DESC_BYTES) + return -EPROTO; + dst = info[4] + 0x9000; + header = (dst & 0xfffeU) | ((dst & 0x10000U) << 1) | 1U; + if (info[7] != header) + return -EPROTO; + /* Both DART stream IDs must be attached to the device's DMA domain. */ + if (of_count_phandle_with_args(avd->dev->of_node, "iommus", "#iommu-cells") < 2) + return -ENODEV; + for (i = 0; i < 4; i++) { + u32 base = avd->variant->vp_slot_base[i]; + + if (!avd->variant->vp_slots[i]) + continue; + if ((info[11 + i] & 0xff) != base || + (info[11 + i] >> 8) != avd->variant->vp_slots[i] || + base + avd->variant->vp_slots[i] > info[8]) + return -EPROTO; + } + avd->capabilities &= info[9]; + q->ring = info[4] + AVD_OPEN_RING; + q->header = info[7]; + WRITE_ONCE(q->enabled, true); + writel(AVD_MBOX_ENABLE, avd->mbox + OPEN_MBOX0_STATUS); + return 0; +} + +unsigned int avd_open_queue_max(struct avd_dev *avd) +{ + return avd_open_is_queued(avd) ? avd->variant->fifo_slots : 1; +} + +int avd_open_queue_submit(struct avd_dev *avd, struct avd_frame *f) +{ + struct avd_open_queue *q = avd->open_priv; + struct avd_buf *packet = &q->packets[f->idx], *fifo = &q->fifo[f->idx]; + struct avd_open_desc desc = {}; + u32 *src = f->prog->cpu, *dst; + size_t packets, bytes, p, n; + unsigned int codec = f->cmd[0]; + void __iomem *record; + int ret; + + static_assert(sizeof(desc) == AVD_OPEN_DESC_BYTES); + if (f->idx >= avd->variant->fifo_slots || codec > AVD_CODEC_AV1 || + !(avd->capabilities & BIT(codec)) || !src || !f->prog_words || + f->prog_words > 0x100000 || f->prog_words > f->prog->size / sizeof(u32) || + !f->cmd[1] || f->cmd[1] > f->prog_words) + return -EINVAL; + if (!(readl(avd->mbox + OPEN_MBOX0_STATUS) & OPEN_MBOX0_EMPTY)) + return -EBUSY; + packets = DIV_ROUND_UP(f->prog_words, AVD_OPEN_PACKET_WORDS); + if (check_mul_overflow(packets, (size_t)AVD_OPEN_PACKET_BYTES, &bytes)) + return -EOVERFLOW; + if (packet->size < bytes) { + ret = avd_buf_alloc(avd, packet, bytes); + if (ret) + return ret; + } + if (!fifo->cpu) { + ret = avd_buf_alloc(avd, fifo, fifo_size()); + if (ret) + return ret; + } + dst = packet->cpu; + memset(dst, 0, bytes); + for (p = 0; p < packets; p++) { + size_t words = min_t(size_t, AVD_OPEN_PACKET_WORDS, + f->prog_words - p * AVD_OPEN_PACKET_WORDS); + + *dst++ = cpu_to_le32(q->header); + for (n = 0; n < words; n++) + dst[n] = cpu_to_le32(src[p * AVD_OPEN_PACKET_WORDS + n]); + dst += AVD_OPEN_PACKET_WORDS; + } + desc.magic = cpu_to_le32(AVD_OPEN_MAGIC); + desc.tag = cpu_to_le32(f->tag); + desc.codec = cpu_to_le32(codec); + desc.fifo = cpu_to_le32(f->idx); + desc.program_lo = cpu_to_le32(lower_32_bits(packet->addr)); + desc.program_hi = cpu_to_le32(upper_32_bits(packet->addr)); + desc.program_words = cpu_to_le32(f->prog_words); + desc.num_segments = cpu_to_le32(f->cmd[1]); + desc.state = cpu_to_le32(1); + desc.context = cpu_to_le32(f->ctx->client_id); + desc.dependency = cpu_to_le32(f->dep); + desc.fifo_lo = cpu_to_le32(lower_32_bits(fifo->addr)); + desc.fifo_hi = cpu_to_le32(upper_32_bits(fifo->addr)); + record = q->sram + AVD_OPEN_RING + f->idx * sizeof(desc); + dma_wmb(); + memcpy_toio(record, &desc, sizeof(desc)); + /* Flush the descriptor aperture before ringing its doorbell. */ + wmb(); + readl(record + 0x20); + writel(q->ring + f->idx * sizeof(desc), avd->mbox + OPEN_MBOX0_TX); + return 0; +} + +static void avd_open_queue_fault(struct avd_dev *avd) +{ + /* Reuse the core's whole-device reset, before any mappings can retire. */ + avd->queue_error = -EIO; + mod_delayed_work(system_wq, &avd->queue_work, 0); +} + +irqreturn_t avd_open_queue_irq(struct avd_dev *avd) +{ + struct avd_open_queue *q = avd->open_priv; + unsigned int budget = AVD_MAX_FRAMES + 1; + + while (budget-- && !(readl(avd->mbox + AVD_REG_MBOX1_STATUS) & AVD_MBOX1_EMPTY)) { + u32 word = readl(avd->mbox + AVD_REG_MBOX1_RETRIEVE); + struct avd_frame *f = &avd->frames[word & 15]; + void __iomem *record = q->sram + AVD_OPEN_RING + f->idx * AVD_OPEN_DESC_BYTES; + u32 state, status, tag; + + writel(AVD_MBOX1_NOT_EMPTY, avd->mbox + AVD_REG_MBOX_IRQ_CLR); + if ((word & 0xffff0000U) != AVD_OPEN_NOTIFY) { + avd_open_queue_fault(avd); + break; + } + state = readl(record + 0x20); + status = readl(record + 0x24); + tag = readl(record + 0x28); + if (state != 2 || !avd_open_completion(word, tag) || tag != f->tag || + f->state != AVD_FRAME_SUBMITTED || status > 2) { + avd_open_queue_fault(avd); + break; + } + /* Fatal completions do not establish hardware/DMA quiescence. */ + if (status == 2) { + avd_open_queue_fault(avd); + break; + } + avd_frame_complete(avd, tag, status ? -EINVAL : 0); + } + return IRQ_HANDLED; +} diff --git a/drivers/media/platform/apple/avd/avd-drv.c b/drivers/media/platform/apple/avd/avd-drv.c index 4abdc1de5f032..586c50bc4ae49 100644 --- a/drivers/media/platform/apple/avd/avd-drv.c +++ b/drivers/media/platform/apple/avd/avd-drv.c @@ -122,6 +122,7 @@ int avd_init_job(struct avd_ctx *ctx, enum avd_codec codec, size_t segments) job->codec = codec; job->num = 0; + job->pp_execs = 0; /* A table that was built but never submitted still belongs to us. */ kvfree(job->segments); job->segments = kvcalloc(segments, sizeof(*job->segments), GFP_KERNEL); @@ -367,6 +368,7 @@ static const struct avd_variant avd_t8103_variant = { [AVD_CODEC_H264] = 1, [AVD_CODEC_VP9] = 1, }, + .vp_slot_base = { 0, 2, 3, 0 }, .fifo_slots = 7, .capabilities = AVD_CAPABILITY_HEVC | AVD_CAPABILITY_H264 | @@ -387,6 +389,7 @@ static const struct avd_variant avd_t6000_variant = { [AVD_CODEC_H264] = 4, [AVD_CODEC_VP9] = 1, }, + .vp_slot_base = { 0, 4, 8, 9 }, .fifo_slots = 15, .capabilities = AVD_CAPABILITY_HEVC | AVD_CAPABILITY_H264 | @@ -407,6 +410,7 @@ static const struct avd_variant avd_t8112_variant = { [AVD_CODEC_H264] = 4, [AVD_CODEC_VP9] = 1, }, + .vp_slot_base = { 0, 4, 8, 9 }, .fifo_slots = 15, .capabilities = AVD_CAPABILITY_HEVC | AVD_CAPABILITY_H264 | @@ -427,12 +431,13 @@ static const struct avd_variant avd_t6020_variant = { [AVD_CODEC_H264] = 4, [AVD_CODEC_VP9] = 1, }, + .vp_slot_base = { 0, 4, 8, 9 }, .fifo_slots = 15, .capabilities = AVD_CAPABILITY_HEVC | AVD_CAPABILITY_H264 | AVD_CAPABILITY_VP9, .configure_stream = t8112_configure_stream, - .fw_name = "apple/avd-fw-v3-t1.bin", + .fw_name = "apple/avd-fw-v3-t2.bin", .revision = 4, .vp_slot_offset = 0xc, .submit_offset = 0x30, @@ -447,6 +452,7 @@ static const struct avd_variant avd_t8122_variant = { [AVD_CODEC_VP9] = 1, [AVD_CODEC_AV1] = 2, }, + .vp_slot_base = { 0, 4, 8, 9 }, .fifo_slots = 15, .configure_stream = t8122_configure_stream, .capabilities = AVD_CAPABILITY_HEVC | @@ -497,6 +503,7 @@ static const struct avd_variant avd_t8132_variant = { [AVD_CODEC_VP9] = 1, [AVD_CODEC_AV1] = 3, }, + .vp_slot_base = { 0, 4, 8, 9 }, .fifo_slots = 15, .configure_stream = t8122_configure_stream, .capabilities = AVD_CAPABILITY_HEVC | @@ -527,12 +534,13 @@ static const struct avd_variant avd_t6040_variant = { [AVD_CODEC_VP9] = 1, [AVD_CODEC_AV1] = 3, }, + .vp_slot_base = { 0, 4, 8, 9 }, .fifo_slots = 15, .configure_stream = t8122_configure_stream, .capabilities = AVD_CAPABILITY_H264 | AVD_CAPABILITY_VP9 | AVD_CAPABILITY_AV1, - .fw_name = "apple/avd-fw-v5-t1.bin", + .fw_name = "apple/avd-fw-v5-t2.bin", .revision = 4, .vp_slot_offset = 0xc, .submit_offset = 0x40, diff --git a/drivers/media/platform/apple/avd/avd-h264.c b/drivers/media/platform/apple/avd/avd-h264.c index e8bc8d0066ead..f2f1a09e25f97 100644 --- a/drivers/media/platform/apple/avd/avd-h264.c +++ b/drivers/media/platform/apple/avd/avd-h264.c @@ -940,6 +940,7 @@ static int avd_h264_run(struct avd_ctx *ctx) return 0; } + ctx->job.pp_execs = 1; return avd_submit_job(ctx); err_free_jobs: diff --git a/drivers/media/platform/apple/avd/avd-hevc.c b/drivers/media/platform/apple/avd/avd-hevc.c index 9dfa20ab71913..aa7b3ace54f9e 100644 --- a/drivers/media/platform/apple/avd/avd-hevc.c +++ b/drivers/media/platform/apple/avd/avd-hevc.c @@ -990,6 +990,7 @@ static int stream_slices(struct avd_ctx *ctx, struct avd_hevc_run *run) } hevc_ctx->submit_num = pos; + ctx->job.pp_execs = pos; return 0; } diff --git a/drivers/media/platform/apple/avd/avd-multi-vp.rst b/drivers/media/platform/apple/avd/avd-multi-vp.rst new file mode 100644 index 0000000000000..02769db1ab397 --- /dev/null +++ b/drivers/media/platform/apple/avd/avd-multi-vp.rst @@ -0,0 +1,101 @@ +Open firmware multi-VP queue +=========================== + +The open backend discovers avd-fw ABI 2 after CM3 boot. Match data describes +both the base register index and engine count for each codec pool. Firmware +selects a free VP from that pool; the host submits a tagged DMA program and +receives a tagged PP completion. The host never handles a queued VP-to-PP kick. + +The total admitted slot count is the minimum of AVD_MAX_FRAMES, match-data +fifo_slots and the backend limit. Slots are allocated only from this range, +so frame index equals hardware FIFO index. There are at most 7 independent +FIFOs on T8103/T8140 and 15 on other supported parts. Each open slot gets its +own instruction FIFO storage and packet buffer, retained until retirement. +These buffers are allocated lazily; FIFO storage uses the existing 12-MiB size. + +The firmware image names select both register addressing and engine pools. +T6020 selects avd-fw-v3-t2 (non-LSR FIFO addressing, existing v3t1 tuning). +T6040 selects avd-fw-v5-t2 (its SRAM aperture and v5t2 tuning). Its full-size +VP counts remain an inference requiring device validation. Codec capability +restrictions in match data remain authoritative. + +ABI and dependency contract +--------------------------- + +The wire layout is in avd-open-protocol.h and in avd-fw src/queue.h. +ABI 1 is incompatible and is rejected when its discovery magic is present. +Firmware without queue discovery, or DT without SRAM/PIODMA resources, +retains the one-frame compatibility transport. A queued image requires both +DART stream IDs on the AVD device so its coherent packet addresses are valid +in the PIODMA domain. The DT resource/IOMMU update is a separate dependency. + +Codec prepare defaults to AVD_DEP_PREV_DONE. H.264 and HEVC keep this policy: +VP-stage reference independence and mutable scratch safety are not proven. +Different contexts can run on different VPs. Independent same-context work +may opt into AVD_DEP_NONE only after proving those properties. Firmware also +checks context dependencies and serializes PP. Both endpoints preserve +submission order within each context when retiring completions. + +For open ABI 2, prepare fills cmd[0] with the codec and cmd[1] with the number +of PP EXECs, which can differ from VP instruction-program segments. Every program EXEC uses f->idx as its FIFO index. +The compatibility CPU builder supplies these fields for existing codecs. +A completed picture releases the m2m scheduler job as soon as the slot owns +its buffers; earlier HOLD_CAPTURE_BUF slice requests complete immediately. + +A fatal firmware error or malformed completion schedules the whole-device +reset before buffers can retire. Error records alone do not prove DMA has +stopped. The existing per-frame watchdog and streamoff/reset paths still own +recovery. No queue-depth module parameter or runtime policy override is added. + +Host validation +--------------- + +With an avd-fw checkout at the matching ABI revision:: + + sh drivers/media/platform/apple/avd/tests/run-host.sh /tmp/avd-host-test + sh drivers/media/platform/apple/avd/tests/run-multi-vp.sh /path/to/avd-fw /tmp/avd-multi-vp-test + +The second test links the real host slot helpers and the real firmware +scheduler. It checks two contexts on distinct VPs/FIFOs across all eight image +tables, reverse completion across contexts, ordered completion within a context, +and the 7/15 FIFO caps. Firmware's Unicorn transport tests additionally execute +the actual CM3 machine code and IRQ wrappers with a synthetic MMIO/PIODMA model. +KUnit covers core admission, FIFO/backend limits and existing tag/retirement +helpers. These tests establish software dispatch behavior, not pixel decoding. + +Build the driver and KUnit objects with ARCH=arm64 LLVM=1 W=1 WERROR=1 in a +controller build slot. Enable VIDEO_APPLE_AVD and VIDEO_APPLE_AVD_KUNIT_TEST, +with their media-controller, platform-driver and KUnit dependencies. + +Device commands for the lead +--------------------------- + +Run these command bodies only through controller-approved requests naming the +specific device. First install the matching kernel, DT with PIODMA/DART stream +1, and ABI 2 image through the normal reversible packaging workflow. Preserve +the previous package and bootguard fallback. Use a licensed local input clip. + +Identify the video/media pair belonging to the same AVD instance:: + + v4l2-ctl --list-devices + v4l2-ctl -d /dev/videoN --info --list-formats-out + +Set VIDEO and MEDIA to that explicit pair, BENCH to the avd-bench executable, +CLIP to the test stream, and TESTS to this driver's tests directory. Each +process opens its own context on that same pair. Run the exact N=1,2,4 cases:: + + sh "$TESTS/run-multi-stream.sh" "$BENCH" "$VIDEO" "$MEDIA" "$CLIP" 1 /tmp/avd-multi-vp-n1 + sh "$TESTS/run-multi-stream.sh" "$BENCH" "$VIDEO" "$MEDIA" "$CLIP" 2 /tmp/avd-multi-vp-n2 + sh "$TESTS/run-multi-stream.sh" "$BENCH" "$VIDEO" "$MEDIA" "$CLIP" 4 /tmp/avd-multi-vp-n4 + +Each stream uses avd-bench --queue 4 --passes 6 with separate JSONL, stderr and +per-frame hash files. The script creates a fresh directory and preserves all +failures. Repeat with distinct fresh output names for H.264, HEVC, VP9 and AV1 +where supported, including 8/10-bit output. The benchmark's mandatory software +pixel comparison must pass before interpreting throughput. Aggregate wall time +and per-stream fps separately; concurrent process startup is not synchronized. + +Also exercise streamoff with another context active, watchdog/reset, repeated +slot reuse, reverse VP completion and both 7/15-slot variants. Confirm distinct +VP indices and FIFO indices in the lead's trace. Request count is not physical +hardware concurrency evidence. This change makes no silicon performance claim. diff --git a/drivers/media/platform/apple/avd/avd-open-protocol.h b/drivers/media/platform/apple/avd/avd-open-protocol.h new file mode 100644 index 0000000000000..6b56691059456 --- /dev/null +++ b/drivers/media/platform/apple/avd/avd-open-protocol.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ +#ifndef AVD_OPEN_PROTOCOL_H_ +#define AVD_OPEN_PROTOCOL_H_ + +#include + +#define AVD_OPEN_MAGIC 0x41565131U +#define AVD_OPEN_VERSION 2U +#define AVD_OPEN_INFO 0x7fc0U +#define AVD_OPEN_RING 0x8000U +#define AVD_OPEN_DESC_BYTES 64U +#define AVD_OPEN_PACKET_WORDS 256U +#define AVD_OPEN_PACKET_BYTES 1028U +#define AVD_OPEN_NOTIFY 0x54530000U +#define AVD_OPEN_FATAL 0x5452ffffU + +/* Little endian, kept in sync with avd-fw src/queue.h. */ +struct avd_open_desc { + __le32 magic, tag, codec, fifo; + __le32 program_lo, program_hi, program_words, num_segments; + __le32 state, status, completion_tag, context, dependency; + __le32 fifo_lo, fifo_hi, reserved; +}; + +static inline bool avd_open_completion(u32 word, u32 tag) +{ + return (word & 0xffff0000U) == AVD_OPEN_NOTIFY && + (word & 0xffffU) == (tag & 0xffffU); +} +#endif diff --git a/drivers/media/platform/apple/avd/avd-queue-internal.h b/drivers/media/platform/apple/avd/avd-queue-internal.h index 0e2a668d5a62f..2edbf9221770f 100644 --- a/drivers/media/platform/apple/avd/avd-queue-internal.h +++ b/drivers/media/platform/apple/avd/avd-queue-internal.h @@ -8,6 +8,25 @@ #include "avd-queue.h" +static inline unsigned int avd_inflight_limit(unsigned int fifo_slots, + unsigned int backend_limit) +{ + unsigned int limit = fifo_slots < backend_limit ? fifo_slots : backend_limit; + + return limit < AVD_MAX_FRAMES ? limit : AVD_MAX_FRAMES; +} + +static inline struct avd_frame *avd_slot_available(struct avd_frame *pool, + unsigned int limit) +{ + unsigned int i; + + for (i = 0; i < limit && i < AVD_MAX_FRAMES; i++) + if (pool[i].state == AVD_FRAME_FREE) + return &pool[i]; + return NULL; +} + /* Caller serializes the pool; tags are never recycled during device lifetime. */ static inline int avd_slot_reserve(struct avd_frame *f, struct avd_ctx *ctx, u64 seq) @@ -20,7 +39,8 @@ static inline int avd_slot_reserve(struct avd_frame *f, struct avd_ctx *ctx, f->tag = (seq << 4) | f->idx; f->ctx = ctx; f->state = AVD_FRAME_RESERVED; - f->dep = AVD_DEP_NONE; + /* H.264/HEVC VP independence is unproven; codecs must opt in. */ + f->dep = AVD_DEP_PREV_DONE; f->status = 0; f->prog_words = 0; f->codec_priv = NULL; diff --git a/drivers/media/platform/apple/avd/avd-queue-test.c b/drivers/media/platform/apple/avd/avd-queue-test.c index eec4761f850e9..0dc86ff31d8ec 100644 --- a/drivers/media/platform/apple/avd/avd-queue-test.c +++ b/drivers/media/platform/apple/avd/avd-queue-test.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only OR MIT #include #include +#include #include "avd.h" #include "avd-apple-protocol.h" @@ -163,6 +164,7 @@ static void avd_dispatch_reservation(struct kunit *test) static const struct avd_backend_ops backend = { .max_inflight = avd_test_max_inflight, }; + static const struct avd_variant variant = { .fifo_slots = 7 }; struct avd_dev *avd = kunit_kzalloc(test, sizeof(*avd), GFP_KERNEL); struct avd_ctx *ctx = kunit_kcalloc(test, 2, sizeof(*ctx), GFP_KERNEL); @@ -170,12 +172,18 @@ static void avd_dispatch_reservation(struct kunit *test) KUNIT_ASSERT_NOT_NULL(test, ctx); spin_lock_init(&avd->queue_lock); avd->backend = &backend; + avd->variant = &variant; ctx[0].dev = avd; ctx[1].dev = avd; KUNIT_EXPECT_EQ(test, avd_queue_ready(&ctx[0]), 1); /* The second context must not reserve the same observed capacity. */ KUNIT_EXPECT_EQ(test, avd_queue_ready(&ctx[1]), 0); avd->queued_ctx = NULL; + avd->inflight = 7; + KUNIT_EXPECT_EQ(test, avd_queue_ready(&ctx[1]), 0); + avd->inflight = 6; + KUNIT_EXPECT_EQ(test, avd_queue_ready(&ctx[1]), 1); + avd->queued_ctx = NULL; avd->inflight = AVD_MAX_FRAMES; KUNIT_EXPECT_EQ(test, avd_queue_ready(&ctx[1]), 0); avd->inflight = 1; @@ -187,6 +195,60 @@ static void avd_dispatch_reservation(struct kunit *test) KUNIT_EXPECT_EQ(test, avd_queue_ready(&ctx[1]), 1); } +static void avd_fifo_limits(struct kunit *test) +{ + struct avd_frame *pool; + unsigned int i; + + pool = kunit_kcalloc(test, AVD_MAX_FRAMES, sizeof(*pool), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, pool); + for (i = 0; i < 7; i++) + pool[i].state = AVD_FRAME_SUBMITTED; + KUNIT_EXPECT_PTR_EQ(test, + avd_slot_available(pool, avd_inflight_limit(7, 16)), NULL); + KUNIT_EXPECT_PTR_EQ(test, + avd_slot_available(pool, avd_inflight_limit(15, 16)), &pool[7]); + for (; i < 15; i++) + pool[i].state = AVD_FRAME_SUBMITTED; + KUNIT_EXPECT_PTR_EQ(test, + avd_slot_available(pool, avd_inflight_limit(15, 16)), NULL); + KUNIT_EXPECT_EQ(test, avd_inflight_limit(7, 16), 7U); + KUNIT_EXPECT_EQ(test, avd_inflight_limit(15, 16), 15U); + KUNIT_EXPECT_EQ(test, avd_inflight_limit(15, 1), 1U); + KUNIT_EXPECT_EQ(test, avd_inflight_limit(32, 32), 16U); + KUNIT_EXPECT_EQ(test, avd_inflight_limit(0, 16), 0U); +} + +static void avd_pp_work_count(struct kunit *test) +{ + static const struct avd_variant variant = { .revision = 4 }; + struct avd_dev *avd = kunit_kzalloc(test, sizeof(*avd), GFP_KERNEL); + struct avd_ctx *ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + struct avd_buf prog = {}; + struct avd_frame f = { .prog = &prog }; + unsigned int i; + + KUNIT_ASSERT_NOT_NULL(test, avd); + KUNIT_ASSERT_NOT_NULL(test, ctx); + prog.cpu = kunit_kcalloc(test, 16, sizeof(u32), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, prog.cpu); + prog.size = 16 * sizeof(u32); + avd->variant = &variant; + avd->building = &f; + ctx->dev = avd; + ctx->job.codec = AVD_CODEC_H264; + ctx->job.num = 3; + ctx->job.pp_execs = 1; /* Three slices, one PP picture. */ + ctx->job.segments = kvcalloc(4, sizeof(*ctx->job.segments), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->job.segments); + for (i = 0; i < 4; i++) + ctx->job.segments[i].num = 1; + KUNIT_ASSERT_EQ(test, avd_open_build_program(ctx), 0); + KUNIT_EXPECT_EQ(test, f.cmd[1], 1U); + KUNIT_EXPECT_EQ(test, f.prog_words, (size_t)8); + KUNIT_EXPECT_PTR_EQ(test, ctx->job.segments, NULL); +} + static struct kunit_case avd_queue_cases[] = { KUNIT_CASE(avd_slots_and_tags), KUNIT_CASE(avd_retirement_order), @@ -194,6 +256,8 @@ static struct kunit_case avd_queue_cases[] = { KUNIT_CASE(avd_completion_parser), KUNIT_CASE(avd_command_builders), KUNIT_CASE(avd_dispatch_reservation), + KUNIT_CASE(avd_fifo_limits), + KUNIT_CASE(avd_pp_work_count), {} }; diff --git a/drivers/media/platform/apple/avd/avd-queue.c b/drivers/media/platform/apple/avd/avd-queue.c index 4991b5a38bac7..6224ac411eb39 100644 --- a/drivers/media/platform/apple/avd/avd-queue.c +++ b/drivers/media/platform/apple/avd/avd-queue.c @@ -88,6 +88,7 @@ static void avd_reset_queue(struct avd_dev *avd, int status) if (ret) dev_err(avd->dev, "queue reset failed: %d\n", ret); avd->compat_active = NULL; + avd->queue_error = 0; list_for_each_entry(ctx, &avd->contexts, queue_node) ctx->session_started = false; spin_lock_irqsave(&avd->queue_lock, flags); @@ -141,6 +142,8 @@ static void avd_queue_worker(struct work_struct *work) mutex_lock(&avd->queue_mutex); if (avd->stopping) goto unlock; + if (avd->queue_error) + avd_reset_queue(avd, avd->queue_error); for (i = 0; i < AVD_MAX_FRAMES; i++) { bool expired; @@ -217,7 +220,8 @@ int avd_queue_ready(void *priv) ready = !avd->queued_ctx && !READ_ONCE(ctx->draining) && !READ_ONCE(avd->stopping) && !READ_ONCE(avd->faulted) && - avd->inflight < avd->backend->max_inflight(avd); + avd->inflight < avd_inflight_limit(avd->variant->fifo_slots, + avd->backend->max_inflight(avd)); for (i = 0; ready && i < AVD_MAX_FRAMES; i++) if (ctx->next_dep == AVD_DEP_PREV_DONE && avd->frames[i].state != AVD_FRAME_FREE && avd->frames[i].ctx == ctx) @@ -236,7 +240,6 @@ static void avd_queue_submit_work(struct work_struct *work) const struct avd_codec_frame_ops *ops = ctx->coded_fmt_desc->frame_ops; struct avd_frame *f = NULL; unsigned long flags; - unsigned int i; int ret; mutex_lock(&avd->queue_mutex); @@ -249,12 +252,9 @@ static void avd_queue_submit_work(struct work_struct *work) VB2_BUF_STATE_ERROR); goto unlock; } - for (i = 0; i < AVD_MAX_FRAMES; i++) { - if (avd->frames[i].state == AVD_FRAME_FREE) { - f = &avd->frames[i]; - break; - } - } + f = avd_slot_available(avd->frames, + avd_inflight_limit(avd->variant->fifo_slots, + avd->backend->max_inflight(avd))); ret = f ? avd_slot_reserve(f, ctx, ++avd->frame_seq) : -EBUSY; if (!ret) avd->inflight++; @@ -333,6 +333,10 @@ static void avd_queue_submit_work(struct work_struct *work) VB2_BUF_STATE_DONE); goto unlock; } + /* A complete picture owns its buffers independently of the m2m job. + * Earlier HOLD_CAPTURE_BUF requests took the immediate-retire path. + */ + f->scheduler_held = false; v4l2_m2m_buf_copy_metadata(f->src, f->dst); if (!f->scheduler_held) { v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); diff --git a/drivers/media/platform/apple/avd/avd-queue.h b/drivers/media/platform/apple/avd/avd-queue.h index 438afc17184ec..f7fd2e8ced8ed 100644 --- a/drivers/media/platform/apple/avd/avd-queue.h +++ b/drivers/media/platform/apple/avd/avd-queue.h @@ -75,6 +75,10 @@ struct avd_backend_ops { struct avd_codec_frame_ops { /* Read the request's controls, set f->dep, fill f->prog (open backend) * or f->fp (apple backend), allocate f->codec_priv if needed. */ + /* Open ABI 2: cmd[0] = codec, cmd[1] = codec PP EXEC count. + * All VP EXECs use FIFO index f->idx. Core defaults to PREV_DONE; + * use NONE only when reference and codec-state independence is proven. + */ int (*prepare)(struct avd_ctx *ctx, struct avd_frame *f); /* Apple backend: fill the codec part of f->cmd (SPEC 6.x). */ int (*pack_cmd)(struct avd_ctx *ctx, struct avd_frame *f); diff --git a/drivers/media/platform/apple/avd/avd-vp9.c b/drivers/media/platform/apple/avd/avd-vp9.c index 4cdcb6594c4d2..6931a08ff205d 100644 --- a/drivers/media/platform/apple/avd/avd-vp9.c +++ b/drivers/media/platform/apple/avd/avd-vp9.c @@ -801,6 +801,7 @@ static int avd_vp9_run(struct avd_ctx *ctx) set_tiles(ctx, &run); avd_run_postamble(ctx, &run.base); + ctx->job.pp_execs = vp9_ctx->submit_num; return avd_submit_job(ctx); } diff --git a/drivers/media/platform/apple/avd/avd.h b/drivers/media/platform/apple/avd/avd.h index 69787064c4eec..a47e73d51c581 100644 --- a/drivers/media/platform/apple/avd/avd.h +++ b/drivers/media/platform/apple/avd/avd.h @@ -188,6 +188,7 @@ struct avd_apple_hw; struct avd_variant { const struct avd_apple_hw *apple; size_t fp_size; + /* Number of engines in each codec pool; bases include tier-0 holes. */ unsigned int vp_slots[4]; unsigned int vp_slot_base[4]; unsigned int fifo_slots; @@ -235,6 +236,8 @@ struct avd_dev { struct avd_buf *frame_fp[AVD_MAX_FRAMES]; u64 frame_seq; unsigned int inflight; + int queue_error; + void *open_priv; bool stopping; bool faulted; u64 fw_generation; @@ -258,6 +261,7 @@ struct avd_segment { struct avd_job { enum avd_codec codec; + u32 pp_execs; /* Codec PP work count, distinct from VP program segments. */ size_t num; struct avd_segment *segments; }; @@ -356,6 +360,12 @@ void avd_queue_drain(struct avd_ctx *ctx); void avd_queue_add_ctx(struct avd_ctx *ctx); void avd_queue_del_ctx(struct avd_ctx *ctx); int avd_open_build_program(struct avd_ctx *ctx); +int avd_open_queue_probe(struct avd_dev *avd); +int avd_open_queue_boot(struct avd_dev *avd); +bool avd_open_is_queued(struct avd_dev *avd); +unsigned int avd_open_queue_max(struct avd_dev *avd); +int avd_open_queue_submit(struct avd_dev *avd, struct avd_frame *f); +irqreturn_t avd_open_queue_irq(struct avd_dev *avd); extern const struct avd_backend_ops avd_open_backend; extern const struct avd_backend_ops avd_apple_backend; int avd_backend_select(struct avd_dev *avd); diff --git a/drivers/media/platform/apple/avd/tests/include/linux/errno.h b/drivers/media/platform/apple/avd/tests/include/linux/errno.h index 32a2df3ba8d20..56c61b35451d2 100644 --- a/drivers/media/platform/apple/avd/tests/include/linux/errno.h +++ b/drivers/media/platform/apple/avd/tests/include/linux/errno.h @@ -1,2 +1,2 @@ /* SPDX-License-Identifier: GPL-2.0-only OR MIT */ -#include_next +#include diff --git a/drivers/media/platform/apple/avd/tests/include/linux/types.h b/drivers/media/platform/apple/avd/tests/include/linux/types.h index 3d29df0e18138..b29b6b621c8ad 100644 --- a/drivers/media/platform/apple/avd/tests/include/linux/types.h +++ b/drivers/media/platform/apple/avd/tests/include/linux/types.h @@ -3,8 +3,10 @@ #define AVD_HOST_TYPES_H_ #include #include -#include_next -typedef __u8 u8; -typedef __u32 u32; -typedef __u64 u64; +#include +typedef uint8_t u8; +typedef uint32_t u32; +typedef uint64_t u64; +/* Host transport test runs on little-endian build hosts. */ +typedef uint32_t __le32; #endif diff --git a/drivers/media/platform/apple/avd/tests/multi-vp-host.c b/drivers/media/platform/apple/avd/tests/multi-vp-host.c new file mode 100644 index 0000000000000..0203608a91389 --- /dev/null +++ b/drivers/media/platform/apple/avd/tests/multi-vp-host.c @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Real host slot helpers and real MIT firmware scheduler, linked on the host. */ +#include +#include +#include +#include "../avd-queue-internal.h" +#include "../avd-open-protocol.h" +#include "queue.h" + +#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__ +#error "run the wire integration test on a little-endian host" +#endif + +struct transport { + struct avd_frame host[AVD_MAX_FRAMES]; + unsigned int vp[AVD_MAX_FRAMES], kicks, completed; + u32 tags[AVD_MAX_FRAMES]; +}; +static int feed(void *cookie, struct avdq_frame *f) +{ + struct transport *t = cookie; + t->vp[f->desc.fifo] = f->vp; + f->pushed = f->desc.program_words; + return 1; +} +static void kick(void *cookie, uint32_t word) +{ + struct transport *t = cookie; + assert((word & 0xff000000U) == AVDQ_EXEC); + t->kicks++; +} +static int complete(void *cookie, unsigned int idx, const struct avdq_frame *f) +{ + struct transport *t = cookie; + assert(avd_open_completion(avdq_notification(f->desc.tag, idx), f->desc.tag)); + assert(avd_slot_complete(&t->host[idx], f->desc.tag, f->status ? -EIO : 0)); + t->tags[t->completed++] = f->desc.tag; + return 1; +} +static const struct avdq_ops ops = { feed, kick, complete }; +static void submit(struct avdq *q, struct transport *t, unsigned int idx, + struct avd_ctx *ctx, u64 seq, unsigned int context, bool independent) +{ + struct avd_frame *h = &t->host[idx]; + struct avd_open_desc wire = {0}; + struct avdq_desc desc; + h->idx = idx; + assert(!avd_slot_reserve(h, ctx, seq)); + assert(h->dep == AVD_DEP_PREV_DONE); + if (independent) + h->dep = AVD_DEP_NONE; + wire.magic = AVD_OPEN_MAGIC; + wire.tag = h->tag; + wire.codec = AVDQ_HEVC; + wire.fifo = idx; + wire.program_lo = 0x4000; + wire.program_words = wire.num_segments = wire.state = 1; + wire.context = context; + wire.dependency = h->dep; + wire.fifo_lo = 0x1000000 + idx * 0x1000000; + _Static_assert(sizeof(wire) == sizeof(desc), "host/firmware ABI mismatch"); + memcpy(&desc, &wire, sizeof(desc)); + h->state = AVD_FRAME_SUBMITTED; + assert(!avdq_submit(q, idx, &desc)); +} +int main(void) +{ + struct transport t = {0}; + struct avdq q; + int context[2]; + unsigned int i; + + assert(avd_inflight_limit(7, 16) == 7); + assert(avd_inflight_limit(15, 16) == 15); + assert(avd_inflight_limit(15, 1) == 1); + for (i = 0; i < 7; i++) + t.host[i].state = AVD_FRAME_SUBMITTED; + assert(!avd_slot_available(t.host, avd_inflight_limit(7, 16))); + assert(avd_slot_available(t.host, avd_inflight_limit(15, 16)) == &t.host[7]); + for (; i < 15; i++) + t.host[i].state = AVD_FRAME_SUBMITTED; + assert(!avd_slot_available(t.host, avd_inflight_limit(15, 16))); + for (i = 0; i < 8; i++) { + memset(&t, 0, sizeof(t)); + avdq_init(&q, &avdq_variants[i]); + submit(&q, &t, 0, (void *)&context[0], 1, 100, false); + submit(&q, &t, 1, (void *)&context[1], 2, 200, false); + avdq_step(&q, &ops, &t); avdq_step(&q, &ops, &t); + assert(q.frame[0].phase == AVDQ_VP && q.frame[1].phase == AVDQ_VP); + assert(t.vp[0] == 0 && t.vp[1] == 1); + /* Two concurrent contexts on distinct VPs and distinct hardware FIFOs. */ + avdq_vp_done(&q, 1); avdq_step(&q, &ops, &t); + avdq_pp_done(&q); avdq_step(&q, &ops, &t); + assert(t.host[1].state == AVD_FRAME_DONE && t.host[0].state == AVD_FRAME_SUBMITTED); + assert(avd_slot_first(t.host, &t.host[1])); + avdq_vp_done(&q, 0); avdq_step(&q, &ops, &t); + avdq_pp_done(&q); avdq_step(&q, &ops, &t); + assert(t.completed == 2 && t.tags[0] == 33 && t.tags[1] == 16); + } + memset(&t, 0, sizeof(t)); + avdq_init(&q, &avdq_variants[1]); + submit(&q, &t, 0, (void *)&context[0], 1, 100, true); + submit(&q, &t, 1, (void *)&context[0], 2, 100, true); + avdq_step(&q, &ops, &t); avdq_step(&q, &ops, &t); + avdq_vp_done(&q, 1); avdq_step(&q, &ops, &t); + avdq_pp_done(&q); avdq_step(&q, &ops, &t); + assert(!t.completed && !avd_slot_first(t.host, &t.host[1])); + avdq_vp_done(&q, 0); avdq_step(&q, &ops, &t); + avdq_pp_done(&q); avdq_step(&q, &ops, &t); avdq_step(&q, &ops, &t); + assert(t.completed == 2 && t.tags[0] == 16 && t.tags[1] == 33); + assert(avd_slot_first(t.host, &t.host[0])); + t.host[0].state = AVD_FRAME_FREE; + assert(avd_slot_first(t.host, &t.host[1])); + puts("PASS: two host contexts concurrently on VP0/VP1, all eight images, FIFO caps and ordered tagged completion"); + return 0; +} diff --git a/drivers/media/platform/apple/avd/tests/run-multi-stream.sh b/drivers/media/platform/apple/avd/tests/run-multi-stream.sh new file mode 100644 index 0000000000000..09c0e93ed5bc8 --- /dev/null +++ b/drivers/media/platform/apple/avd/tests/run-multi-stream.sh @@ -0,0 +1,26 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# Run only on an explicitly approved decoder device; each process owns a context. +set -eu +bench=${1:?usage: run-multi-stream.sh BENCH VIDEO MEDIA CLIP N OUT} +video=${2:?explicit AVD video node required} +media=${3:?explicit matching media node required} +clip=${4:?input clip required} +streams=${5:?stream count required} +out=${6:?new output directory required} +case "$streams" in 1|2|4) ;; *) exit 2 ;; esac +mkdir "$out" +pids= +i=0 +while [ "$i" -lt "$streams" ]; do + "$bench" --input "$clip" --video "$video" --media "$media" \ + --queue 4 --passes 6 --hashes "$out/$i.hashes.tsv" \ + >"$out/$i.jsonl" 2>"$out/$i.stderr" & + pids="$pids $!" + i=$((i + 1)) +done +status=0 +for pid in $pids; do + wait "$pid" || status=1 +done +exit "$status" diff --git a/drivers/media/platform/apple/avd/tests/run-multi-vp.sh b/drivers/media/platform/apple/avd/tests/run-multi-vp.sh new file mode 100644 index 0000000000000..ac15531c09da1 --- /dev/null +++ b/drivers/media/platform/apple/avd/tests/run-multi-vp.sh @@ -0,0 +1,10 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-only OR MIT +set -eu +avd_test_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +avd_fw_dir=${1:?usage: run-multi-vp.sh AVD_FW_DIR OUTPUT_BINARY} +avd_test_out=${2:?usage: run-multi-vp.sh AVD_FW_DIR OUTPUT_BINARY} +${CC:-cc} -std=gnu11 -Wall -Wextra -Werror -O2 \ + -I"$avd_test_dir/include" -I"$avd_fw_dir/src" \ + "$avd_test_dir/multi-vp-host.c" "$avd_fw_dir/src/queue.c" -o "$avd_test_out" +"$avd_test_out" From 6aa2d5a35b5b04b8bd9616719e6e3b42ec9e8b78 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Sun, 1 Nov 2026 00:12:55 +1100 Subject: [PATCH 2/7] media: apple: avd: tidy multi-VP test documentation Add the document license tag and format the native integration test. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/media/platform/apple/avd/avd-multi-vp.rst | 2 ++ drivers/media/platform/apple/avd/tests/multi-vp-host.c | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/media/platform/apple/avd/avd-multi-vp.rst b/drivers/media/platform/apple/avd/avd-multi-vp.rst index 02769db1ab397..40d3961f8edcd 100644 --- a/drivers/media/platform/apple/avd/avd-multi-vp.rst +++ b/drivers/media/platform/apple/avd/avd-multi-vp.rst @@ -1,3 +1,5 @@ +.. SPDX-License-Identifier: GPL-2.0-only OR MIT + Open firmware multi-VP queue =========================== diff --git a/drivers/media/platform/apple/avd/tests/multi-vp-host.c b/drivers/media/platform/apple/avd/tests/multi-vp-host.c index 0203608a91389..daf8caf644af8 100644 --- a/drivers/media/platform/apple/avd/tests/multi-vp-host.c +++ b/drivers/media/platform/apple/avd/tests/multi-vp-host.c @@ -19,6 +19,7 @@ struct transport { static int feed(void *cookie, struct avdq_frame *f) { struct transport *t = cookie; + t->vp[f->desc.fifo] = f->vp; f->pushed = f->desc.program_words; return 1; @@ -26,12 +27,14 @@ static int feed(void *cookie, struct avdq_frame *f) static void kick(void *cookie, uint32_t word) { struct transport *t = cookie; + assert((word & 0xff000000U) == AVDQ_EXEC); t->kicks++; } static int complete(void *cookie, unsigned int idx, const struct avdq_frame *f) { struct transport *t = cookie; + assert(avd_open_completion(avdq_notification(f->desc.tag, idx), f->desc.tag)); assert(avd_slot_complete(&t->host[idx], f->desc.tag, f->status ? -EIO : 0)); t->tags[t->completed++] = f->desc.tag; @@ -44,6 +47,7 @@ static void submit(struct avdq *q, struct transport *t, unsigned int idx, struct avd_frame *h = &t->host[idx]; struct avd_open_desc wire = {0}; struct avdq_desc desc; + h->idx = idx; assert(!avd_slot_reserve(h, ctx, seq)); assert(h->dep == AVD_DEP_PREV_DONE); @@ -111,6 +115,7 @@ int main(void) assert(avd_slot_first(t.host, &t.host[0])); t.host[0].state = AVD_FRAME_FREE; assert(avd_slot_first(t.host, &t.host[1])); - puts("PASS: two host contexts concurrently on VP0/VP1, all eight images, FIFO caps and ordered tagged completion"); + puts("PASS: two host contexts concurrently on VP0/VP1, all eight images, " + "FIFO caps and ordered tagged completion"); return 0; } From dbe2dede3dfbea49a40e1720c89aa60dfec8c1f4 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Sun, 1 Nov 2026 00:15:51 +1100 Subject: [PATCH 3/7] media: apple: avd: stop firmware after queue validation failure Require the device DMA domain before enabling queued PIODMA work and stop the CM3 when ABI or pool validation fails. Assisted-by: LLM Signed-off-by: Ryan Murray --- .../platform/apple/avd/avd-backend-queue.c | 28 +++++++++++++------ .../platform/apple/avd/tests/multi-vp-host.c | 3 +- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/drivers/media/platform/apple/avd/avd-backend-queue.c b/drivers/media/platform/apple/avd/avd-backend-queue.c index 3398c6881d35f..0427fb264d353 100644 --- a/drivers/media/platform/apple/avd/avd-backend-queue.c +++ b/drivers/media/platform/apple/avd/avd-backend-queue.c @@ -100,15 +100,22 @@ int avd_open_queue_boot(struct avd_dev *avd) if (info[1] != AVD_OPEN_VERSION || info[2] != AVD_MAX_FRAMES || info[3] != sizeof(struct avd_open_desc) || info[5] != AVD_OPEN_RING || info[6] != AVD_OPEN_PACKET_WORDS || info[10] != avd->variant->fifo_slots || - info[4] > U32_MAX - AVD_OPEN_RING - AVD_MAX_FRAMES * AVD_OPEN_DESC_BYTES) - return -EPROTO; + info[4] > U32_MAX - AVD_OPEN_RING - AVD_MAX_FRAMES * AVD_OPEN_DESC_BYTES) { + ret = -EPROTO; + goto shutdown; + } dst = info[4] + 0x9000; header = (dst & 0xfffeU) | ((dst & 0x10000U) << 1) | 1U; - if (info[7] != header) - return -EPROTO; + if (info[7] != header) { + ret = -EPROTO; + goto shutdown; + } /* Both DART stream IDs must be attached to the device's DMA domain. */ - if (of_count_phandle_with_args(avd->dev->of_node, "iommus", "#iommu-cells") < 2) - return -ENODEV; + if (!avd->domain || + of_count_phandle_with_args(avd->dev->of_node, "iommus", "#iommu-cells") < 2) { + ret = -ENODEV; + goto shutdown; + } for (i = 0; i < 4; i++) { u32 base = avd->variant->vp_slot_base[i]; @@ -116,8 +123,10 @@ int avd_open_queue_boot(struct avd_dev *avd) continue; if ((info[11 + i] & 0xff) != base || (info[11 + i] >> 8) != avd->variant->vp_slots[i] || - base + avd->variant->vp_slots[i] > info[8]) - return -EPROTO; + base + avd->variant->vp_slots[i] > info[8]) { + ret = -EPROTO; + goto shutdown; + } } avd->capabilities &= info[9]; q->ring = info[4] + AVD_OPEN_RING; @@ -125,6 +134,9 @@ int avd_open_queue_boot(struct avd_dev *avd) WRITE_ONCE(q->enabled, true); writel(AVD_MBOX_ENABLE, avd->mbox + OPEN_MBOX0_STATUS); return 0; +shutdown: + avd_shutdown(avd); + return ret; } unsigned int avd_open_queue_max(struct avd_dev *avd) diff --git a/drivers/media/platform/apple/avd/tests/multi-vp-host.c b/drivers/media/platform/apple/avd/tests/multi-vp-host.c index daf8caf644af8..df68e3fa2aafa 100644 --- a/drivers/media/platform/apple/avd/tests/multi-vp-host.c +++ b/drivers/media/platform/apple/avd/tests/multi-vp-host.c @@ -115,7 +115,6 @@ int main(void) assert(avd_slot_first(t.host, &t.host[0])); t.host[0].state = AVD_FRAME_FREE; assert(avd_slot_first(t.host, &t.host[1])); - puts("PASS: two host contexts concurrently on VP0/VP1, all eight images, " - "FIFO caps and ordered tagged completion"); + puts("PASS: two contexts on VP0/VP1, eight images, FIFO caps and ordered tags"); return 0; } From 4612cf08146f130191baf28d78b9b1b43eb3dd6c Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Sun, 1 Nov 2026 00:37:51 +1100 Subject: [PATCH 4/7] media: apple: avd: keep legacy decode when queued DMA is unavailable Recognize ABI 1 images without submitting ABI 2 records to them. Keep the existing one-frame transport when DT cannot supply queued PIODMA mappings, so source and DART requirements remain enforced without losing legacy decode. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/media/platform/apple/avd/avd-backend-queue.c | 9 +++++++-- drivers/media/platform/apple/avd/avd-multi-vp.rst | 11 ++++++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/drivers/media/platform/apple/avd/avd-backend-queue.c b/drivers/media/platform/apple/avd/avd-backend-queue.c index 25bf138c5ae8c..1a2728f694f36 100644 --- a/drivers/media/platform/apple/avd/avd-backend-queue.c +++ b/drivers/media/platform/apple/avd/avd-backend-queue.c @@ -95,6 +95,11 @@ int avd_open_queue_boot(struct avd_dev *avd) info[i] = readl(q->sram + AVD_OPEN_INFO + i * 4); if (info[0] != AVD_OPEN_MAGIC) return 0; + /* ABI 1 images retain the same legacy IRQ path until a queue doorbell. */ + if (info[1] == 1) { + dev_info_once(avd->dev, "queue ABI 1 image uses the open compatibility transport\n"); + return 0; + } if (info[1] != AVD_OPEN_VERSION || info[2] != AVD_MAX_FRAMES || info[3] != sizeof(struct avd_open_desc) || info[5] != AVD_OPEN_RING || info[6] != AVD_OPEN_PACKET_WORDS || info[10] != avd->variant->fifo_slots || @@ -111,8 +116,8 @@ int avd_open_queue_boot(struct avd_dev *avd) /* Both DART stream IDs must be attached to the device's DMA domain. */ if (!avd->pm.piodma || !avd->domain || of_count_phandle_with_args(avd->dev->of_node, "iommus", "#iommu-cells") < 2) { - ret = -ENODEV; - goto shutdown; + dev_info_once(avd->dev, "queued PIODMA unavailable; using the open compatibility transport\n"); + return 0; } for (i = 0; i < 4; i++) { u32 base = avd->variant->vp_slot_base[i]; diff --git a/drivers/media/platform/apple/avd/avd-multi-vp.rst b/drivers/media/platform/apple/avd/avd-multi-vp.rst index 40d3961f8edcd..f4cb8df75cbb4 100644 --- a/drivers/media/platform/apple/avd/avd-multi-vp.rst +++ b/drivers/media/platform/apple/avd/avd-multi-vp.rst @@ -25,11 +25,16 @@ ABI and dependency contract --------------------------- The wire layout is in avd-open-protocol.h and in avd-fw src/queue.h. -ABI 1 is incompatible and is rejected when its discovery magic is present. +ABI 1 queued descriptors are incompatible and are never submitted. Known ABI 1 +images retain their legacy compatibility transport. Firmware without queue discovery, or DT without SRAM/PIODMA resources, -retains the one-frame compatibility transport. A queued image requires both +retains the one-frame compatibility transport. Missing PIODMA mappings or a +second DART stream also select that compatibility path. A queued PIODMA image requires both DART stream IDs on the AVD device so its coherent packet addresses are valid -in the PIODMA domain. The DT resource/IOMMU update is a separate dependency. +in the PIODMA domain. The DT resource/IOMMU update is a separate dependency. VP dispatch itself uses +a transport feed callback; a future SRAM-direct entry can use the same scheduler +without issuing PIODMA. The SRAM-direct wire extension must be negotiated before +using that path; this driver currently submits ABI 2 PIODMA entries only. Codec prepare defaults to AVD_DEP_PREV_DONE. H.264 and HEVC keep this policy: VP-stage reference independence and mutable scratch safety are not proven. From 72e3166395cb82ccea87415df689cf7e12391d02 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Sun, 1 Nov 2026 00:49:52 +1100 Subject: [PATCH 5/7] media: apple: avd: negotiate multi-VP metadata on ABI 1 Keep the original descriptor layout and reserve a high metadata flag while leaving transport flags and geometry available for SRAM-direct support. Check a root capability as well as the separate pool block so stale SRAM from a rolled-back image cannot enable extended submissions. Assisted-by: LLM Signed-off-by: Ryan Murray --- .../platform/apple/avd/avd-backend-queue.c | 39 ++++++++++++------- .../media/platform/apple/avd/avd-multi-vp.rst | 15 ++++--- .../platform/apple/avd/avd-open-protocol.h | 27 ++++++++++--- .../media/platform/apple/avd/avd-queue-test.c | 13 +++++++ drivers/media/platform/apple/avd/avd-queue.h | 2 +- .../platform/apple/avd/tests/multi-vp-host.c | 20 +++++++--- 6 files changed, 83 insertions(+), 33 deletions(-) diff --git a/drivers/media/platform/apple/avd/avd-backend-queue.c b/drivers/media/platform/apple/avd/avd-backend-queue.c index 1a2728f694f36..614795582b831 100644 --- a/drivers/media/platform/apple/avd/avd-backend-queue.c +++ b/drivers/media/platform/apple/avd/avd-backend-queue.c @@ -72,7 +72,7 @@ bool avd_open_is_queued(struct avd_dev *avd) int avd_open_queue_boot(struct avd_dev *avd) { struct avd_open_queue *q = avd->open_priv; - u32 info[16], dst, header; + u32 info[16], multi[7], dst, header; unsigned int i; int ret; @@ -95,11 +95,6 @@ int avd_open_queue_boot(struct avd_dev *avd) info[i] = readl(q->sram + AVD_OPEN_INFO + i * 4); if (info[0] != AVD_OPEN_MAGIC) return 0; - /* ABI 1 images retain the same legacy IRQ path until a queue doorbell. */ - if (info[1] == 1) { - dev_info_once(avd->dev, "queue ABI 1 image uses the open compatibility transport\n"); - return 0; - } if (info[1] != AVD_OPEN_VERSION || info[2] != AVD_MAX_FRAMES || info[3] != sizeof(struct avd_open_desc) || info[5] != AVD_OPEN_RING || info[6] != AVD_OPEN_PACKET_WORDS || info[10] != avd->variant->fifo_slots || @@ -113,6 +108,12 @@ int avd_open_queue_boot(struct avd_dev *avd) ret = -EPROTO; goto shutdown; } + for (i = 0; i < ARRAY_SIZE(multi); i++) + multi[i] = readl(q->sram + AVD_OPEN_MULTI_INFO + i * 4); + if (!avd_open_has_multi(info[9], multi)) { + dev_info_once(avd->dev, "firmware uses the open compatibility transport\n"); + return 0; + } /* Both DART stream IDs must be attached to the device's DMA domain. */ if (!avd->pm.piodma || !avd->domain || of_count_phandle_with_args(avd->dev->of_node, "iommus", "#iommu-cells") < 2) { @@ -124,8 +125,8 @@ int avd_open_queue_boot(struct avd_dev *avd) if (!avd->variant->vp_slots[i]) continue; - if ((info[11 + i] & 0xff) != base || - (info[11 + i] >> 8) != avd->variant->vp_slots[i] || + if ((multi[2 + i] & 0xff) != base || + (multi[2 + i] >> 8) != avd->variant->vp_slots[i] || base + avd->variant->vp_slots[i] > info[8]) { ret = -EPROTO; goto shutdown; @@ -162,7 +163,8 @@ int avd_open_queue_submit(struct avd_dev *avd, struct avd_frame *f) if (f->idx >= avd->variant->fifo_slots || codec > AVD_CODEC_AV1 || !(avd->capabilities & BIT(codec)) || !src || !f->prog_words || f->prog_words > 0x100000 || f->prog_words > f->prog->size / sizeof(u32) || - !f->cmd[1] || f->cmd[1] > f->prog_words) + !f->cmd[1] || f->cmd[1] > f->prog_words || + f->ctx->client_id > AVD_OPEN_CONTEXT_MASK || f->dep > AVD_DEP_PREV_DONE) return -EINVAL; if (!(readl(avd->mbox + OPEN_MBOX0_STATUS) & OPEN_MBOX0_EMPTY)) return -EBUSY; @@ -193,16 +195,23 @@ int avd_open_queue_submit(struct avd_dev *avd, struct avd_frame *f) desc.magic = cpu_to_le32(AVD_OPEN_MAGIC); desc.tag = cpu_to_le32(f->tag); desc.codec = cpu_to_le32(codec); - desc.fifo = cpu_to_le32(f->idx); + desc.vp_slot = cpu_to_le32(avd->variant->vp_slot_base[codec]); + desc.pp_kick = cpu_to_le32(AVD_OP_EXEC | AVD_OP_EXEC_FIFO_IDX(f->idx) | + AVD_OP_EXEC_FIFO_MASK(avd->variant->fifo_slots) | + AVD_OP_EXEC_FLAG_START_REV4(avd->variant->revision == 4) | + AVD_OP_EXEC_FLAG_START_REV3(avd->variant->revision == 3) | + (codec == AVD_CODEC_VP9 && avd->variant->revision == 3 ? + AVD_OP_EXEC_REV3_VP9_MASK : 0)); desc.program_lo = cpu_to_le32(lower_32_bits(packet->addr)); desc.program_hi = cpu_to_le32(upper_32_bits(packet->addr)); desc.program_words = cpu_to_le32(f->prog_words); - desc.num_segments = cpu_to_le32(f->cmd[1]); desc.state = cpu_to_le32(1); - desc.context = cpu_to_le32(f->ctx->client_id); - desc.dependency = cpu_to_le32(f->dep); - desc.fifo_lo = cpu_to_le32(lower_32_bits(fifo->addr)); - desc.fifo_hi = cpu_to_le32(upper_32_bits(fifo->addr)); + desc.reserved[0] = cpu_to_le32(AVD_OPEN_FLAG_MULTI_VP); + desc.reserved[1] = cpu_to_le32(f->ctx->client_id | + (f->dep == AVD_DEP_PREV_DONE ? AVD_OPEN_CONTEXT_DEP : 0)); + desc.reserved[2] = cpu_to_le32(lower_32_bits(fifo->addr)); + desc.reserved[3] = cpu_to_le32(upper_32_bits(fifo->addr)); + desc.reserved[4] = cpu_to_le32(f->cmd[1]); record = q->sram + AVD_OPEN_RING + f->idx * sizeof(desc); dma_wmb(); memcpy_toio(record, &desc, sizeof(desc)); diff --git a/drivers/media/platform/apple/avd/avd-multi-vp.rst b/drivers/media/platform/apple/avd/avd-multi-vp.rst index f4cb8df75cbb4..1da3a4a26706b 100644 --- a/drivers/media/platform/apple/avd/avd-multi-vp.rst +++ b/drivers/media/platform/apple/avd/avd-multi-vp.rst @@ -3,7 +3,7 @@ Open firmware multi-VP queue =========================== -The open backend discovers avd-fw ABI 2 after CM3 boot. Match data describes +The open backend discovers avd-fw ABI 1 with multi-VP metadata after CM3 boot. Match data describes both the base register index and engine count for each codec pool. Firmware selects a free VP from that pool; the host submits a tagged DMA program and receives a tagged PP completion. The host never handles a queued VP-to-PP kick. @@ -25,8 +25,11 @@ ABI and dependency contract --------------------------- The wire layout is in avd-open-protocol.h and in avd-fw src/queue.h. -ABI 1 queued descriptors are incompatible and are never submitted. Known ABI 1 -images retain their legacy compatibility transport. +The original ABI 1 fields are unchanged. Bit 31 of root codec capabilities +and a separate AVM1 block at SRAM +0x7f80 negotiate the multi-VP extension. +Descriptor reserved[0] bit 31 enables context/dependency, FIFO IOVA and PP-count +metadata; low bits remain available to transport flags. Legacy ABI 1 entries +remain valid, and images without the extension use the compatibility transport. Firmware without queue discovery, or DT without SRAM/PIODMA resources, retains the one-frame compatibility transport. Missing PIODMA mappings or a second DART stream also select that compatibility path. A queued PIODMA image requires both @@ -34,7 +37,7 @@ DART stream IDs on the AVD device so its coherent packet addresses are valid in the PIODMA domain. The DT resource/IOMMU update is a separate dependency. VP dispatch itself uses a transport feed callback; a future SRAM-direct entry can use the same scheduler without issuing PIODMA. The SRAM-direct wire extension must be negotiated before -using that path; this driver currently submits ABI 2 PIODMA entries only. +using that path; this driver currently submits extended ABI 1 PIODMA entries only. Codec prepare defaults to AVD_DEP_PREV_DONE. H.264 and HEVC keep this policy: VP-stage reference independence and mutable scratch safety are not proven. @@ -43,7 +46,7 @@ may opt into AVD_DEP_NONE only after proving those properties. Firmware also checks context dependencies and serializes PP. Both endpoints preserve submission order within each context when retiring completions. -For open ABI 2, prepare fills cmd[0] with the codec and cmd[1] with the number +For open multi-VP work, prepare fills cmd[0] with the codec and cmd[1] with the number of PP EXECs, which can differ from VP instruction-program segments. Every program EXEC uses f->idx as its FIFO index. The compatibility CPU builder supplies these fields for existing codecs. A completed picture releases the m2m scheduler job as soon as the slot owns @@ -79,7 +82,7 @@ Device commands for the lead Run these command bodies only through controller-approved requests naming the specific device. First install the matching kernel, DT with PIODMA/DART stream -1, and ABI 2 image through the normal reversible packaging workflow. Preserve +1, and matching multi-VP image through the normal reversible packaging workflow. Preserve the previous package and bootguard fallback. Use a licensed local input clip. Identify the video/media pair belonging to the same AVD instance:: diff --git a/drivers/media/platform/apple/avd/avd-open-protocol.h b/drivers/media/platform/apple/avd/avd-open-protocol.h index 6b56691059456..1f250514f654d 100644 --- a/drivers/media/platform/apple/avd/avd-open-protocol.h +++ b/drivers/media/platform/apple/avd/avd-open-protocol.h @@ -5,7 +5,7 @@ #include #define AVD_OPEN_MAGIC 0x41565131U -#define AVD_OPEN_VERSION 2U +#define AVD_OPEN_VERSION 1U #define AVD_OPEN_INFO 0x7fc0U #define AVD_OPEN_RING 0x8000U #define AVD_OPEN_DESC_BYTES 64U @@ -14,14 +14,29 @@ #define AVD_OPEN_NOTIFY 0x54530000U #define AVD_OPEN_FATAL 0x5452ffffU -/* Little endian, kept in sync with avd-fw src/queue.h. */ +#define AVD_OPEN_MULTI_INFO 0x7f80U +#define AVD_OPEN_MULTI_MAGIC 0x41564d31U +#define AVD_OPEN_MULTI_VERSION 1U +#define AVD_OPEN_CAP_MULTI_VP (1U << 31) +#define AVD_OPEN_FLAG_MULTI_VP (1U << 31) +#define AVD_OPEN_CONTEXT_MASK 0x3ffU +#define AVD_OPEN_CONTEXT_DEP (1U << 31) + +/* ABI 1 wire fields stay unchanged; the extension uses reserved words. */ struct avd_open_desc { - __le32 magic, tag, codec, fifo; - __le32 program_lo, program_hi, program_words, num_segments; - __le32 state, status, completion_tag, context, dependency; - __le32 fifo_lo, fifo_hi, reserved; + __le32 magic, tag, codec, vp_slot; + __le32 program_lo, program_hi, program_words, pp_kick; + __le32 state, status, completion_tag, reserved[5]; }; +/* The ABI 1 root capability prevents stale extension SRAM from an old image. */ +static inline bool avd_open_has_multi(u32 capabilities, const u32 multi[7]) +{ + return (capabilities & AVD_OPEN_CAP_MULTI_VP) && + multi[0] == AVD_OPEN_MULTI_MAGIC && multi[1] == AVD_OPEN_MULTI_VERSION && + (multi[6] & AVD_OPEN_FLAG_MULTI_VP); +} + static inline bool avd_open_completion(u32 word, u32 tag) { return (word & 0xffff0000U) == AVD_OPEN_NOTIFY && diff --git a/drivers/media/platform/apple/avd/avd-queue-test.c b/drivers/media/platform/apple/avd/avd-queue-test.c index 0dc86ff31d8ec..2bda1902957e2 100644 --- a/drivers/media/platform/apple/avd/avd-queue-test.c +++ b/drivers/media/platform/apple/avd/avd-queue-test.c @@ -6,6 +6,7 @@ #include "avd.h" #include "avd-apple-protocol.h" #include "avd-queue-internal.h" +#include "avd-open-protocol.h" static void avd_slots_and_tags(struct kunit *test) { @@ -249,6 +250,17 @@ static void avd_pp_work_count(struct kunit *test) KUNIT_EXPECT_PTR_EQ(test, ctx->job.segments, NULL); } +static void avd_open_metadata_capability(struct kunit *test) +{ + u32 info[7] = { AVD_OPEN_MULTI_MAGIC, AVD_OPEN_MULTI_VERSION }; + + info[6] = AVD_OPEN_FLAG_MULTI_VP; + KUNIT_EXPECT_FALSE(test, avd_open_has_multi(15, info)); + KUNIT_EXPECT_TRUE(test, avd_open_has_multi(15 | AVD_OPEN_CAP_MULTI_VP, info)); + info[1]++; + KUNIT_EXPECT_FALSE(test, avd_open_has_multi(15 | AVD_OPEN_CAP_MULTI_VP, info)); +} + static struct kunit_case avd_queue_cases[] = { KUNIT_CASE(avd_slots_and_tags), KUNIT_CASE(avd_retirement_order), @@ -258,6 +270,7 @@ static struct kunit_case avd_queue_cases[] = { KUNIT_CASE(avd_dispatch_reservation), KUNIT_CASE(avd_fifo_limits), KUNIT_CASE(avd_pp_work_count), + KUNIT_CASE(avd_open_metadata_capability), {} }; diff --git a/drivers/media/platform/apple/avd/avd-queue.h b/drivers/media/platform/apple/avd/avd-queue.h index f7fd2e8ced8ed..5bf6bb9c06f7b 100644 --- a/drivers/media/platform/apple/avd/avd-queue.h +++ b/drivers/media/platform/apple/avd/avd-queue.h @@ -75,7 +75,7 @@ struct avd_backend_ops { struct avd_codec_frame_ops { /* Read the request's controls, set f->dep, fill f->prog (open backend) * or f->fp (apple backend), allocate f->codec_priv if needed. */ - /* Open ABI 2: cmd[0] = codec, cmd[1] = codec PP EXEC count. + /* Open multi-VP extension: cmd[0] = codec, cmd[1] = codec PP EXEC count. * All VP EXECs use FIFO index f->idx. Core defaults to PREV_DONE; * use NONE only when reference and codec-state independence is proven. */ diff --git a/drivers/media/platform/apple/avd/tests/multi-vp-host.c b/drivers/media/platform/apple/avd/tests/multi-vp-host.c index df68e3fa2aafa..22409db28c625 100644 --- a/drivers/media/platform/apple/avd/tests/multi-vp-host.c +++ b/drivers/media/platform/apple/avd/tests/multi-vp-host.c @@ -56,12 +56,15 @@ static void submit(struct avdq *q, struct transport *t, unsigned int idx, wire.magic = AVD_OPEN_MAGIC; wire.tag = h->tag; wire.codec = AVDQ_HEVC; - wire.fifo = idx; + wire.vp_slot = 0; + wire.pp_kick = AVDQ_EXEC | (idx << 4); wire.program_lo = 0x4000; - wire.program_words = wire.num_segments = wire.state = 1; - wire.context = context; - wire.dependency = h->dep; - wire.fifo_lo = 0x1000000 + idx * 0x1000000; + wire.program_words = wire.state = 1; + wire.reserved[0] = AVD_OPEN_FLAG_MULTI_VP; + wire.reserved[4] = 1; + wire.reserved[1] = context | + (h->dep == AVD_DEP_PREV_DONE ? AVD_OPEN_CONTEXT_DEP : 0); + wire.reserved[2] = 0x1000000 + idx * 0x1000000; _Static_assert(sizeof(wire) == sizeof(desc), "host/firmware ABI mismatch"); memcpy(&desc, &wire, sizeof(desc)); h->state = AVD_FRAME_SUBMITTED; @@ -73,6 +76,13 @@ int main(void) struct avdq q; int context[2]; unsigned int i; + u32 info[7] = { AVD_OPEN_MULTI_MAGIC, AVD_OPEN_MULTI_VERSION }; + + info[6] = AVD_OPEN_FLAG_MULTI_VP; + assert(!avd_open_has_multi(15, info)); /* Old image, stale extension SRAM. */ + assert(avd_open_has_multi(15 | AVD_OPEN_CAP_MULTI_VP, info)); + info[1]++; + assert(!avd_open_has_multi(15 | AVD_OPEN_CAP_MULTI_VP, info)); assert(avd_inflight_limit(7, 16) == 7); assert(avd_inflight_limit(15, 16) == 15); From ddbbc4f2adda9a0954533ddc77438d1ef22953ca Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Sun, 1 Nov 2026 01:16:38 +1100 Subject: [PATCH 6/7] media: apple: avd: follow normalized firmware scheduling work Keep the host integration test aligned with the firmware source-adapter boundary. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/media/platform/apple/avd/tests/multi-vp-host.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/platform/apple/avd/tests/multi-vp-host.c b/drivers/media/platform/apple/avd/tests/multi-vp-host.c index 22409db28c625..d8a1abd0b530c 100644 --- a/drivers/media/platform/apple/avd/tests/multi-vp-host.c +++ b/drivers/media/platform/apple/avd/tests/multi-vp-host.c @@ -20,7 +20,7 @@ static int feed(void *cookie, struct avdq_frame *f) { struct transport *t = cookie; - t->vp[f->desc.fifo] = f->vp; + t->vp[f->work.fifo] = f->vp; f->pushed = f->desc.program_words; return 1; } From 4c786e9fbfd161f73186da26ddc99bf0df03b95b Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Sun, 1 Nov 2026 01:43:39 +1100 Subject: [PATCH 7/7] media: apple: avd: retain full PP counts for tiled frames Use full-width AV1 and VP9 work counters so 256-tile frames do not wrap to zero before preparing queue metadata. Iterate continuations without unsigned subtraction and add a builder regression for the 256-work case. Assisted-by: LLM Signed-off-by: Ryan Murray --- drivers/media/platform/apple/avd/avd-av1.c | 4 ++-- drivers/media/platform/apple/avd/avd-queue-test.c | 15 +++++++++++++++ drivers/media/platform/apple/avd/avd-vp9.c | 4 ++-- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/drivers/media/platform/apple/avd/avd-av1.c b/drivers/media/platform/apple/avd/avd-av1.c index 2d344befa84a2..aa664aaf0c4bb 100644 --- a/drivers/media/platform/apple/avd/avd-av1.c +++ b/drivers/media/platform/apple/avd/avd-av1.c @@ -225,7 +225,7 @@ struct avd_av1_run { }; struct avd_av1_ctx { - u8 submit_num; + u32 submit_num; struct { struct avd_buf inst; struct avd_buf pipe_state; @@ -1527,7 +1527,7 @@ static void avd_av1_submit(struct avd_ctx *ctx) AVD_OP_EXEC_FIFO_IDX(ctx->fifo_idx) | AVD_OP_EXEC_FIFO_MASK(avd->variant->fifo_slots), avd->ctrl + avd->variant->submit_offset); - for (int i = 0; i < av1_ctx->submit_num - 1; i++) { + for (u32 i = 1; i < av1_ctx->submit_num; i++) { writel(AVD_OP_EXEC | AVD_OP_EXEC_FIFO_IDX(ctx->fifo_idx) | AVD_OP_EXEC_FIFO_MASK(avd->variant->fifo_slots), avd->ctrl + avd->variant->submit_offset); diff --git a/drivers/media/platform/apple/avd/avd-queue-test.c b/drivers/media/platform/apple/avd/avd-queue-test.c index 2bda1902957e2..1222d5205ccd8 100644 --- a/drivers/media/platform/apple/avd/avd-queue-test.c +++ b/drivers/media/platform/apple/avd/avd-queue-test.c @@ -248,6 +248,21 @@ static void avd_pp_work_count(struct kunit *test) KUNIT_EXPECT_EQ(test, f.cmd[1], 1U); KUNIT_EXPECT_EQ(test, f.prog_words, (size_t)8); KUNIT_EXPECT_PTR_EQ(test, ctx->job.segments, NULL); + /* A valid 256-tile frame must not truncate its PP work count to u8. */ + prog.cpu = kunit_kcalloc(test, 1024, sizeof(u32), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, prog.cpu); + prog.size = 1024 * sizeof(u32); + ctx->job.codec = AVD_CODEC_VP9; + ctx->job.num = 256; + ctx->job.pp_execs = 256; + ctx->job.segments = kvcalloc(257, sizeof(*ctx->job.segments), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, ctx->job.segments); + for (i = 0; i < 257; i++) + ctx->job.segments[i].num = 1; + KUNIT_ASSERT_EQ(test, avd_open_build_program(ctx), 0); + KUNIT_EXPECT_EQ(test, f.cmd[1], 256U); + KUNIT_EXPECT_EQ(test, f.prog_words, (size_t)514); + } static void avd_open_metadata_capability(struct kunit *test) diff --git a/drivers/media/platform/apple/avd/avd-vp9.c b/drivers/media/platform/apple/avd/avd-vp9.c index 6931a08ff205d..9b8cad46c32da 100644 --- a/drivers/media/platform/apple/avd/avd-vp9.c +++ b/drivers/media/platform/apple/avd/avd-vp9.c @@ -191,7 +191,7 @@ struct avd_vp9_ctx { } bufs; struct avd_vp9_frame_info cur; struct avd_vp9_frame_info last; - u8 submit_num; + u32 submit_num; }; static void set_refs(struct avd_ctx *ctx, struct avd_vp9_run *run) @@ -1066,7 +1066,7 @@ static void avd_vp9_submit(struct avd_ctx *ctx) AVD_OP_EXEC_FIFO_IDX(ctx->fifo_idx) | AVD_OP_EXEC_FIFO_MASK(avd->variant->fifo_slots), avd->ctrl + avd->variant->submit_offset); - for (int i = 0; i < vp9_ctx->submit_num - 1; i++) + for (u32 i = 1; i < vp9_ctx->submit_num; i++) writel(AVD_OP_EXEC | submit_mask | AVD_OP_EXEC_FIFO_IDX(ctx->fifo_idx) | AVD_OP_EXEC_FIFO_MASK(avd->variant->fifo_slots),