diff --git a/drivers/media/platform/apple/avd/avd-av1.c b/drivers/media/platform/apple/avd/avd-av1.c index 6bd5b1a9a6227..9658ed92e27fd 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; @@ -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++) { @@ -1533,7 +1534,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-backend-open.c b/drivers/media/platform/apple/avd/avd-backend-open.c index 9680dc4ef64e1..9183e98c19d29 100644 --- a/drivers/media/platform/apple/avd/avd-backend-open.c +++ b/drivers/media/platform/apple/avd/avd-backend-open.c @@ -55,10 +55,12 @@ int avd_open_build_program(struct avd_ctx *ctx) 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; diff --git a/drivers/media/platform/apple/avd/avd-backend-queue.c b/drivers/media/platform/apple/avd/avd-backend-queue.c index a706602386c17..6baddd9d95d96 100644 --- a/drivers/media/platform/apple/avd/avd-backend-queue.c +++ b/drivers/media/platform/apple/avd/avd-backend-queue.c @@ -18,6 +18,7 @@ #include "avd.h" #include "avd-inst.h" #include "avd-regs.h" +#include "avd-open-protocol.h" #define AVDQ_MAGIC 0x41565131 /* "AVQ1" */ #define AVDQ_VERSION 2 @@ -41,7 +42,7 @@ #define AVDQ_CM3_APERTURE(off) (0x01000000 + (off)) #define AVD_REG_MBOX0_STATUS 0x50 #define AVD_REG_MBOX0_SEND 0x54 -#define AVDQ_STREAM_TIMEOUT_US (1000 * USEC_PER_MSEC) +#define AVDQ_MBOX0_EMPTY BIT(0) /* Descriptor word offsets (bytes), avd-fw docs/queue.md. */ enum { @@ -61,6 +62,10 @@ struct avd_qfw { u32 fifo_slots; u32 codec_mask; /* info+0x24, bit n = codec n */ bool usable; /* last boot found a matching ABI 2 image */ + bool multi; + struct avd_frame *stream; + u32 stream_sent; + struct avd_buf fallback_fifo[AVD_MAX_FRAMES]; }; static void __iomem *avd_qfw_desc(struct avd_qfw *q, unsigned int idx) @@ -68,6 +73,17 @@ static void __iomem *avd_qfw_desc(struct avd_qfw *q, unsigned int idx) return q->sram + AVDQ_RING_OFFSET + idx * AVDQ_DESC_BYTES; } +void avd_qfw_cleanup(struct avd_dev *avd) +{ + struct avd_qfw *q = avd->qfw; + + if (!q) + return; + q->stream = NULL; + for (unsigned int i = 0; i < AVD_MAX_FRAMES; i++) + avd_buf_free(avd, &q->fallback_fifo[i]); +} + int avd_qfw_probe(struct avd_dev *avd) { struct resource *code, *sram; @@ -95,7 +111,7 @@ int avd_qfw_probe(struct avd_dev *avd) static bool avd_qfw_check(struct avd_dev *avd) { struct avd_qfw *q = avd->qfw; - u32 info[16]; + u32 info[16], multi[7]; unsigned int i; if (!q) @@ -118,16 +134,31 @@ static bool avd_qfw_check(struct avd_dev *avd) info[4], q->aperture); return false; } - if (info[13] < 1024 || info[12] % 4 || + if (info[13] < 1024 || (info[13] & (info[13] - 1)) || info[12] % 4 || (u64)info[12] + 4ULL * info[13] > AVDQ_RING_LIMIT || !info[10] || info[10] > AVDQ_COUNT || info[14] || info[15]) { dev_warn_once(avd->dev, "firmware SRAM ring geometry invalid, using legacy transport\n"); return false; } + q->multi = false; + if (info[9] & AVD_OPEN_CAP_MULTI_VP) { + 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)) + return false; + for (i = 0; i < 4; i++) { + if (!avd->variant->vp_slots[i]) + continue; + if ((multi[2 + i] & 0xff) != avd->variant->vp_slot_base[i] || + (multi[2 + i] >> 8) != avd->variant->vp_slots[i]) + return false; + } + q->multi = true; + } q->ring_off = info[12]; q->ring_words = info[13]; q->fifo_slots = min_t(u32, info[10], avd->variant->fifo_slots ?: 1); - q->codec_mask = info[9]; + q->codec_mask = info[9] & GENMASK(3, 0); q->wp = 0; q->usable = true; return true; @@ -155,41 +186,48 @@ static void avd_qfw_fail_all(struct avd_dev *avd) unsigned int i; /* ABI: any fatal word or I/O status needs a whole-queue reset. */ + avd->qfw->stream = NULL; + avd->queue_error = -EIO; for (i = 0; i < AVD_MAX_FRAMES; i++) if (avd->frames[i].state == AVD_FRAME_SUBMITTED) avd->frames[i].deadline = jiffies; mod_delayed_work(system_wq, &avd->queue_work, 0); } -static int avd_qfw_stream(struct avd_dev *avd, const u32 *words, u32 count) +/* One bounded producer chunk. The core retries while firmware consumes it. */ +static int avd_qfw_pump(struct avd_dev *avd) { struct avd_qfw *q = avd->qfw; - unsigned long timeout = jiffies + usecs_to_jiffies(AVDQ_STREAM_TIMEOUT_US); - u32 done = 0; - - while (done < count) { - u32 rp = readl(q->sram + AVDQ_INFO_RP); - u32 used = q->wp - rp, space, pos, chunk; + struct avd_frame *f = q->stream; + u32 rp, pos, chunk; + int ret; - if (used > q->ring_words) - return -EIO; - space = q->ring_words - used; - if (!space) { - if (time_after(jiffies, timeout)) - return -ETIMEDOUT; - usleep_range(20, 50); - continue; - } - pos = q->wp % q->ring_words; - chunk = min3(space, count - done, q->ring_words - pos); - memcpy_toio(q->sram + q->ring_off + 4 * pos, words + done, 4 * chunk); - q->wp += chunk; - done += chunk; - /* Words before the count that publishes them. */ - wmb(); - writel(q->wp, q->sram + AVDQ_INFO_WP); + if (!f) + return 0; + if (avd->faulted || f->state != AVD_FRAME_SUBMITTED) { + q->stream = NULL; + return 0; } - return 0; + rp = readl(q->sram + AVDQ_INFO_RP); + ret = avd_open_ring_chunk(q->wp, rp, q->ring_words, + f->prog_words - q->stream_sent, &chunk); + if (ret) + return ret; + if (!chunk) + return 1; + pos = q->wp & (q->ring_words - 1); + memcpy_toio(q->sram + q->ring_off + 4 * pos, + (u32 *)f->prog->cpu + q->stream_sent, 4 * chunk); + q->wp += chunk; + q->stream_sent += chunk; + /* Words before the count that publishes them. */ + wmb(); + writel(q->wp, q->sram + AVDQ_INFO_WP); + if (q->stream_sent == f->prog_words) { + q->stream = NULL; + return 0; + } + return 1; } static int avd_qfw_submit(struct avd_dev *avd, struct avd_frame *f) @@ -197,30 +235,53 @@ static int avd_qfw_submit(struct avd_dev *avd, struct avd_frame *f) struct avd_qfw *q = avd->qfw; struct avd_ctx *ctx = f->ctx; const struct avd_coded_fmt_ops *ops = ctx->coded_fmt_desc->ops; - u32 codec = f->cmd[0], vp = 0, kick, cont, mask, extra = 0; + u32 codec = f->cmd[0], vp = 0, kick, cont, mask, count, extra; + dma_addr_t fifo; u32 *words = f->prog->cpu; void __iomem *d = avd_qfw_desc(q, f->idx); unsigned int i; int ret; - if (ctx->coded_fmt_desc->frame_ops) - return -EOPNOTSUPP; + if (q->stream) { + ret = avd_qfw_pump(avd); + if (ret < 0) { + avd_qfw_fail_all(avd); + return -EBUSY; + } + if (ret) + return -EBUSY; + } + if (!(readl(avd->mbox + AVD_REG_MBOX0_STATUS) & AVDQ_MBOX0_EMPTY)) + return -EBUSY; if (codec > AVD_CODEC_AV1 || !words || !f->prog_words || - f->prog_words > f->prog->size / sizeof(u32)) + f->prog_words > f->prog->size / sizeof(u32) || f->prog_words > 0x100000) return -EINVAL; if (!(avd->capabilities & BIT(codec))) return -EOPNOTSUPP; /* The program's EXEC words name FIFO f->idx; it must be a real FIFO. */ - if (f->idx >= q->fifo_slots || ctx->fifo_idx != f->idx) + if (f->idx >= q->fifo_slots || + (!ctx->coded_fmt_desc->frame_ops && ctx->fifo_idx != f->idx)) return -EINVAL; for (i = 0; i < codec; i++) vp += avd->variant->vp_slots[i]; if (avd->variant->vp_slot_base[codec]) vp = avd->variant->vp_slot_base[codec]; - if (ops->pp_kicks) - extra = ops->pp_kicks(ctx) - 1; - if (extra > AVDQ_MAX_PP_EXTRA) + count = f->cmd[1] ?: (ops && ops->pp_kicks ? ops->pp_kicks(ctx) : 1); + if (!count || count > f->prog_words || + (q->multi && (ctx->client_id > AVD_OPEN_CONTEXT_MASK || f->dep > AVD_DEP_PREV_DONE))) + return -EINVAL; + extra = count - 1; + if (!q->multi && extra > AVDQ_MAX_PP_EXTRA) return -E2BIG; + fifo = f->fifo_iova; + if (!fifo) { + if (!q->fallback_fifo[f->idx].cpu) { + ret = avd_buf_alloc(avd, &q->fallback_fifo[f->idx], fifo_size()); + if (ret) + return ret; + } + fifo = q->fallback_fifo[f->idx].addr; + } mask = codec == AVD_CODEC_VP9 && avd->variant->revision == 3 ? AVD_OP_EXEC_REV3_VP9_MASK : 0; @@ -231,7 +292,8 @@ static int avd_qfw_submit(struct avd_dev *avd, struct avd_frame *f) codec != AVD_CODEC_AV1); /* FIFO registers of other live frames are never touched. */ - avd->variant->configure_stream(avd, f->fifo_iova ?: ctx->inst.addr, f->idx, vp); + if (!q->multi) + avd->variant->configure_stream(avd, fifo, f->idx, vp); writel(AVDQ_MAGIC, d + D_MAGIC); writel(f->tag, d + D_TAG); @@ -243,11 +305,19 @@ static int avd_qfw_submit(struct avd_dev *avd, struct avd_frame *f) writel(kick, d + D_KICK); writel(0, d + D_STATUS); writel(0, d + D_CTAG); - writel(AVDQ_FLAG_SRAM, d + D_FLAGS); - writel(extra, d + D_PP_EXTRA); - writel(extra ? cont : 0, d + D_PP_CONT); - writel(0, d + D_RSVD0); - writel(0, d + D_RSVD1); + writel(AVDQ_FLAG_SRAM | (q->multi ? AVD_OPEN_FLAG_MULTI_VP : 0), d + D_FLAGS); + if (q->multi) { + writel(ctx->client_id | (f->dep == AVD_DEP_PREV_DONE ? AVD_OPEN_CONTEXT_DEP : 0), + d + D_PP_EXTRA); + writel(lower_32_bits(fifo), d + D_PP_CONT); + writel(upper_32_bits(fifo), d + D_RSVD0); + writel(count, d + D_RSVD1); + } else { + writel(extra, d + D_PP_EXTRA); + writel(extra ? cont : 0, d + D_PP_CONT); + writel(0, d + D_RSVD0); + writel(0, d + D_RSVD1); + } writel(AVDQ_STATE_SUBMITTED, d + D_STATE); /* Descriptor before the doorbell that hands it to the firmware. */ wmb(); @@ -255,13 +325,12 @@ static int avd_qfw_submit(struct avd_dev *avd, struct avd_frame *f) writel(q->aperture + AVDQ_RING_OFFSET + f->idx * AVDQ_DESC_BYTES, avd->mbox + AVD_REG_MBOX0_SEND); - /* The firmware may start feeding while the rest of the program streams in. */ - ret = avd_qfw_stream(avd, words, f->prog_words); - if (ret) { - dev_err_ratelimited(avd->dev, "queue: program stream failed: %d\n", ret); - /* The descriptor is already posted: only a reset releases it. */ - f->deadline = jiffies; - } + /* Retain ownership while a bounded pump streams any remaining words. */ + q->stream = f; + q->stream_sent = 0; + ret = avd_qfw_pump(avd); + if (ret < 0) + avd_qfw_fail_all(avd); return 0; } @@ -290,12 +359,16 @@ static irqreturn_t avd_qfw_irq(struct avd_dev *avd) f->state != AVD_FRAME_SUBMITTED || ctag != f->tag) { dev_warn_ratelimited(avd->dev, "queue: stale completion %#x (state %u tag %#x)\n", word, state, ctag); + avd_qfw_fail_all(avd); return IRQ_HANDLED; } - writel(0, d + D_STATE); - avd_frame_complete(avd, ctag, status == 0 ? 0 : status == 1 ? -EINVAL : -EIO); - if (status > 1) + if (status || (q->stream == f && q->stream_sent != f->prog_words)) { + /* Rejected SRAM entries leave the ring position unresolved as well. */ avd_qfw_fail_all(avd); + return IRQ_HANDLED; + } + writel(0, d + D_STATE); + avd_frame_complete(avd, ctag, 0); return IRQ_HANDLED; } @@ -305,6 +378,7 @@ const struct avd_backend_ops avd_qfw_backend = { .shutdown = avd_shutdown, .max_inflight = avd_qfw_max_inflight, .submit = avd_qfw_submit, + .pump = avd_qfw_pump, .irq = avd_qfw_irq, .suspend = avd_pm_backend_suspend, .resume = avd_pm_backend_resume, @@ -322,8 +396,11 @@ bool avd_backend_is_qfw(const struct avd_dev *avd) */ void avd_qfw_prepare_boot(struct avd_dev *avd) { - if (avd->qfw) + if (avd->qfw) { + avd->qfw->stream = NULL; + avd->qfw->usable = false; writel(0, avd->qfw->sram + AVDQ_INFO); + } } /* diff --git a/drivers/media/platform/apple/avd/avd-drv.c b/drivers/media/platform/apple/avd/avd-drv.c index fc23c0935f36f..c244c7e3b2cd7 100644 --- a/drivers/media/platform/apple/avd/avd-drv.c +++ b/drivers/media/platform/apple/avd/avd-drv.c @@ -97,6 +97,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); @@ -343,6 +344,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 | @@ -363,6 +365,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 | @@ -383,6 +386,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 | @@ -403,12 +407,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, @@ -423,6 +428,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 | @@ -473,6 +479,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 | @@ -503,6 +510,7 @@ 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_HEVC | @@ -511,9 +519,11 @@ static const struct avd_variant avd_t6040_variant = { AVD_CAPABILITY_AV1, /* HEVC only with firmware whose CODECS trailer lists h265. */ .fw_caps_required = AVD_CAPABILITY_HEVC, + /* v5-t2 matches T6040's SRAM aperture, which the queued transport needs. */ .fw_name = "apple/avd-fw-v5-t2.bin", .fw_name_fallback = "apple/avd-fw-v5-t1.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 5ff0026ef3730..93a433cb16d1d 100644 --- a/drivers/media/platform/apple/avd/avd-h264.c +++ b/drivers/media/platform/apple/avd/avd-h264.c @@ -1011,6 +1011,7 @@ static int avd_h264_run(struct avd_ctx *ctx) return 0; } + ctx->job.pp_execs = 1; ctx->job_fifo = h264_ctx->b->bufs.inst.addr; h264_ctx->b->submitted = true; return avd_submit_job(ctx); diff --git a/drivers/media/platform/apple/avd/avd-hevc.c b/drivers/media/platform/apple/avd/avd-hevc.c index b1fb5aa63d1f9..6a2ee19bf9ab5 100644 --- a/drivers/media/platform/apple/avd/avd-hevc.c +++ b/drivers/media/platform/apple/avd/avd-hevc.c @@ -1002,6 +1002,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..f399694a6c805 --- /dev/null +++ b/drivers/media/platform/apple/avd/avd-multi-vp.rst @@ -0,0 +1,116 @@ +.. SPDX-License-Identifier: GPL-2.0-only OR MIT + +Open firmware multi-VP queue +=========================== + +The open backend discovers avd-fw ABI 2 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 instruction 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 program 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 2 preserves the original 64-byte prefix and adds SRAM-ring flags and +legacy PP continuations. Bit 31 of root codec capabilities and an AVM1 block at +SRAM +0x7f80 negotiate multi-VP metadata. Descriptor flag bit 31 reinterprets +continuation/tail words as context/dependency, FIFO IOVA and full PP count. +Original ABI 2 callers retain their first/continuation PP words and FIFO setup. +Images without a matching ABI/aperture/geometry retain the legacy transport. + +The native queue streams words through the advertised power-of-two SRAM ring; +no PIODMA mapping or second DART stream is required for program delivery. +The decoder's ordinary DMA domain and buffer lifetime rules remain mandatory. +A bounded producer pump runs from queue work until long programs are fully +published. Invalid ring counts, rejected entries or fatal notifications reset +the whole device before mappings can retire. Geometry and u32-wrap tests cover +the 4096-word ring, which streams programs longer than its capacity. + +Firmware dispatch only sees normalized work and a source-readiness callback. +PIODMA tests can bypass unrelated blocked entries, while the SRAM source preserves +global word order before reserving a VP. Both sources share VP pools, context +dependencies, serialized PP and completion handling. + +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 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 +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 SRAM resources and a 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:: + + 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..09dc22cf46a1d --- /dev/null +++ b/drivers/media/platform/apple/avd/avd-open-protocol.h @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ +#ifndef AVD_OPEN_PROTOCOL_H_ +#define AVD_OPEN_PROTOCOL_H_ + +#include +#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 + +#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) +#define AVD_OPEN_FLAG_SRAM 1U +#define AVD_OPEN_FEAT_SRAM_RING 1U +#define AVD_OPEN_FEAT_MULTI_KICK 2U + +/* ABI 1 wire fields stay unchanged; the extension uses reserved words. */ +struct avd_open_desc { + __le32 magic, tag, codec, vp_slot; + __le32 program_lo, program_hi, program_words, pp_kick; + __le32 state, status, completion_tag; + union { + __le32 reserved[5]; + struct { __le32 flags, pp_extra, pp_cont, tail[2]; }; + }; +}; + +/* The 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); +} + +/* A power-of-two ring keeps count-to-slot mapping valid across u32 wrap. */ +static inline int avd_open_ring_chunk(u32 wp, u32 rp, u32 ring, u32 remaining, u32 *chunk) +{ + u32 used = wp - rp, space, pos; + + if (!ring || (ring & (ring - 1)) || used > ring) + return -EIO; + space = ring - used; + pos = wp & (ring - 1); + *chunk = space < remaining ? space : remaining; + if (*chunk > ring - pos) + *chunk = ring - pos; + return 0; +} + +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..f62896a8c5f65 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,9 +39,11 @@ 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->fifo_iova = 0; f->codec_priv = NULL; return 0; } diff --git a/drivers/media/platform/apple/avd/avd-queue-test.c b/drivers/media/platform/apple/avd/avd-queue-test.c index eec4761f850e9..1607a74d78bcd 100644 --- a/drivers/media/platform/apple/avd/avd-queue-test.c +++ b/drivers/media/platform/apple/avd/avd-queue-test.c @@ -1,10 +1,12 @@ // SPDX-License-Identifier: GPL-2.0-only OR MIT #include #include +#include #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) { @@ -163,6 +165,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 +173,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 +196,101 @@ 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); + /* 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) +{ + 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 void avd_sram_ring_wrap(struct kunit *test) +{ + u32 chunk; + + KUNIT_ASSERT_EQ(test, avd_open_ring_chunk(2, U32_MAX - 1, 4096, 4096, &chunk), 0); + KUNIT_EXPECT_EQ(test, chunk, 4092U); + KUNIT_ASSERT_EQ(test, avd_open_ring_chunk(U32_MAX - 1, U32_MAX - 1, + 4096, 10, &chunk), 0); + KUNIT_EXPECT_EQ(test, chunk, 2U); + KUNIT_ASSERT_EQ(test, avd_open_ring_chunk(4096, 0, 4096, 1, &chunk), 0); + KUNIT_EXPECT_EQ(test, chunk, 0U); + KUNIT_EXPECT_EQ(test, avd_open_ring_chunk(4097, 0, 4096, 1, &chunk), -EIO); + KUNIT_EXPECT_EQ(test, avd_open_ring_chunk(0, 0, 6144, 1, &chunk), -EIO); +} + static struct kunit_case avd_queue_cases[] = { KUNIT_CASE(avd_slots_and_tags), KUNIT_CASE(avd_retirement_order), @@ -194,6 +298,10 @@ 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), + KUNIT_CASE(avd_open_metadata_capability), + KUNIT_CASE(avd_sram_ring_wrap), {} }; diff --git a/drivers/media/platform/apple/avd/avd-queue.c b/drivers/media/platform/apple/avd/avd-queue.c index 45759a0593792..49f0212151b39 100644 --- a/drivers/media/platform/apple/avd/avd-queue.c +++ b/drivers/media/platform/apple/avd/avd-queue.c @@ -123,6 +123,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); @@ -172,12 +173,22 @@ static void avd_queue_worker(struct work_struct *work) struct avd_ctx *ctx; unsigned long flags; unsigned int i; - bool pending; + bool pending, producer_pending = false; unsigned long delay = AVD_FRAME_TIMEOUT; mutex_lock(&avd->queue_mutex); if (avd->stopping) goto unlock; + if (avd->queue_error) + avd_reset_queue(avd, avd->queue_error); + if (avd->backend->pump) { + int ret = avd->backend->pump(avd); + + if (ret < 0) + avd_reset_queue(avd, ret); + else + producer_pending = ret > 0; + } for (i = 0; i < AVD_MAX_FRAMES; i++) { bool expired; @@ -209,6 +220,17 @@ static void avd_queue_worker(struct work_struct *work) spin_unlock_irqrestore(&avd->queue_lock, flags); } } + /* A RESERVED retry can have installed a new producer after the first pump. */ + if (avd->backend->pump) { + int ret = avd->backend->pump(avd); + + if (ret < 0) { + avd_reset_queue(avd, ret); + producer_pending = false; + } else { + producer_pending = ret > 0; + } + } avd_retire(avd); /* Freed slots/dependencies must wake contexts whose job_ready was false. */ list_for_each_entry(ctx, &avd->contexts, queue_node) @@ -227,6 +249,8 @@ static void avd_queue_worker(struct work_struct *work) delay = min(delay, remaining); } spin_unlock_irqrestore(&avd->queue_lock, flags); + if (producer_pending) + delay = min(delay, AVD_RETRY_DELAY); if (pending) mod_delayed_work(system_wq, &avd->queue_work, delay); unlock: @@ -261,18 +285,13 @@ int avd_queue_ready(void *priv) */ ready = !avd->queued_ctx && !READ_ONCE(ctx->draining) && !READ_ONCE(avd->stopping) && - avd->inflight < avd->backend->max_inflight(avd); - if (ready && ctx->next_dep == AVD_DEP_PREV_DONE) { - /* Legacy codecs may hold several pictures in separate banks. */ - unsigned int limit = ctx->coded_fmt_desc && !ctx->coded_fmt_desc->frame_ops ? - max(ctx->max_frames, 1U) : 1, held = 0; - - for (i = 0; i < AVD_MAX_FRAMES; i++) - if (avd->frames[i].state != AVD_FRAME_FREE && - avd->frames[i].ctx == ctx) - held++; - ready = held < limit; - } + 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) + ready = false; + /* Prevent several queued m2m contexts from observing the same free slot. */ if (ready) avd->queued_ctx = ctx; @@ -287,7 +306,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); @@ -306,12 +324,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++; @@ -398,6 +413,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); @@ -562,4 +581,6 @@ void avd_queue_cleanup(struct avd_dev *avd) if (avd->frame_fp[i]) avd_buf_free(avd, avd->frame_fp[i]); } + /* Queue DMA storage joins the PM deferred-unmap list before final off. */ + avd_qfw_cleanup(avd); } diff --git a/drivers/media/platform/apple/avd/avd-queue.h b/drivers/media/platform/apple/avd/avd-queue.h index cd912259c636a..2d6c539977d8d 100644 --- a/drivers/media/platform/apple/avd/avd-queue.h +++ b/drivers/media/platform/apple/avd/avd-queue.h @@ -66,6 +66,8 @@ struct avd_backend_ops { void (*session_stop)(struct avd_ctx *ctx); /* apple: cmd 2 */ /* Non-blocking. -EBUSY means inbox full: core retries on next completion. */ int (*submit)(struct avd_dev *avd, struct avd_frame *f); + /* Bounded producer work: >0 retry shortly, <0 reset before retirement. */ + int (*pump)(struct avd_dev *avd); void (*abort)(struct avd_ctx *ctx); /* apple: cmd 5 */ irqreturn_t (*irq)(struct avd_dev *avd); /* calls avd_frame_complete() */ int (*suspend)(struct avd_dev *avd); @@ -77,6 +79,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 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. + */ 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 37374b73e8a86..b7b966ae82ded 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) @@ -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); } @@ -1072,7 +1073,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), diff --git a/drivers/media/platform/apple/avd/avd.h b/drivers/media/platform/apple/avd/avd.h index a9d092bebd56a..a7bc811e40926 100644 --- a/drivers/media/platform/apple/avd/avd.h +++ b/drivers/media/platform/apple/avd/avd.h @@ -194,6 +194,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; @@ -246,6 +247,7 @@ struct avd_dev { struct avd_buf *frame_fp[AVD_MAX_FRAMES]; u64 frame_seq; unsigned int inflight; + int queue_error; bool stopping; bool faulted; /* Recovery pacing: when the last one ran, and a sliding storm count. */ @@ -281,6 +283,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; }; @@ -393,6 +396,7 @@ extern const struct avd_backend_ops avd_apple_backend; extern const struct avd_backend_ops avd_qfw_backend; struct avd_qfw; int avd_qfw_probe(struct avd_dev *avd); +void avd_qfw_cleanup(struct avd_dev *avd); void avd_qfw_select(struct avd_dev *avd); void avd_qfw_prepare_boot(struct avd_dev *avd); bool avd_backend_is_qfw(const 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 8b9638abb9a7f..67d46a4f342af 100644 --- a/drivers/media/platform/apple/avd/tests/include/linux/types.h +++ b/drivers/media/platform/apple/avd/tests/include/linux/types.h @@ -3,9 +3,11 @@ #define AVD_HOST_TYPES_H_ #include #include -#include_next -typedef __u8 u8; -typedef __u32 u32; -typedef __u64 u64; -typedef __u64 dma_addr_t; +#include +typedef uint8_t u8; +typedef uint32_t u32; +typedef uint64_t u64; +typedef uint64_t dma_addr_t; +/* 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..f185aeaddd8da --- /dev/null +++ b/drivers/media/platform/apple/avd/tests/multi-vp-host.c @@ -0,0 +1,137 @@ +// 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->work.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 = feed, .kick = kick, .complete = 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.vp_slot = 0; + wire.pp_kick = AVDQ_EXEC | (idx << 4); + wire.program_lo = 0x4000; + 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; + assert(!avdq_submit(q, idx, &desc)); +} +int main(void) +{ + struct transport t = {0}; + struct avdq q; + int context[2]; + unsigned int i; + u32 chunk; + u32 info[7] = { AVD_OPEN_MULTI_MAGIC, AVD_OPEN_MULTI_VERSION }; + + assert(!avd_open_ring_chunk(4090, 4080, 4096, 20, &chunk) && chunk == 6); + assert(!avd_open_ring_chunk(0xfffffff0U, 0xffffffe0U, 4096, 100, &chunk) && chunk == 16); + assert(!avd_open_ring_chunk(0, 0xfffffff0U, 4096, 100, &chunk) && chunk == 100); + assert(!avd_open_ring_chunk(4096, 0, 4096, 1, &chunk) && !chunk); + assert(avd_open_ring_chunk(4097, 0, 4096, 1, &chunk) == -EIO); + assert(avd_open_ring_chunk(0, 0, 6144, 1, &chunk) == -EIO); + 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); + 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 contexts on VP0/VP1, eight images, FIFO caps and ordered tags"); + 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"