Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions drivers/media/platform/apple/avd/H264-FRAME-OPS.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
H.264 frame operations (integration draft)
=========================================

The descriptor exports ``avd_h264_frame_ops`` alongside the legacy callbacks so
this codec branch builds on aurora-wip while the queue core is developed. The
legacy entry points and the weak open-backend compatibility predicate must be
removed when integrating the frame queue. Neither
prepare nor retire operates hardware or removes mem2mem buffers. The queue owns
request control setup/completion, source/destination lifetime and error retire.
The core must call retire after prepare failures that acquired codec_priv.

Open prepare writes little-endian instruction words directly into frame->prog,
with a capacity check before every word. Coded data uses the OUTPUT DMA address
plus the plane's data_offset and the validated emulation-prevention-aware header
offset. No coherent allocation or copy of coded slice bytes remains. Scratch is
cached separately for each of the sixteen frame indices and retained until the
core drains the context. Motion vectors remain in each CAPTURE allocation's
reserved tail, so references retain their own vectors. H.264 declares DEP_NONE.

Unresolved contract and protocol facts
--------------------------------------

A multi-buffer HOLD_CAPTURE_BUF picture cannot safely return intermediate
OUTPUT buffers before the final decode. Userspace may immediately overwrite a
returned OUTPUT, even if a kernel reference keeps its allocation alive. The
current shared contract says to return those buffers immediately. Until core
retains every source through the picture's completion, prepare and the legacy
path reject HOLD_CAPTURE_BUF with EOPNOTSUPP. This is an explicit limitation,
not successful multi-slice support.

The V3 payload projection uses the MIT public layout in eiln/avd
``avid/h264/fp.py`` and its ``halv3.py`` field encoding. Transport PIODMA words
and FIFO configuration remain transport-owned and are not fabricated. The
public sources do not establish a Borage syntax layout. SPEC 6.2 establishes
only part of the command: the exact +0x3c/+0x44 packed-address rules and both
work-count bounds are still absent. The known Borage command projection returns
EOPNOTSUPP; it must never be published. V3 commands also return EOPNOTSUPP because
they cannot use the Borage command ABI. Apple decoding is not yet enabled.
T8112/T8140 are explicit TODO rows tied to per-SoC ledger task t-3862.

Verification
------------

Run on a Linux build host::

python3 drivers/media/platform/apple/avd/tests/run-host-tests.py

The host harness compiles the production builder and parameter writer with
kernel plumbing replaced by a small shim. It compares the word stream against
a frozen, licensed aurora-wip builder for synthetic V4L2 I/P/B controls,
1/4-slice programs, CABAC/CAVLC, scaling, weights and both address formats.
Four-slice program equivalence does not imply that the queue lifetime problem
above has been solved. Parameter tests check V3 offsets, address alignment,
array capacity, known Borage command offsets and unsupported-layout rejection.
These tests do not feed an encoded ffmpeg clip through GStreamer or compare
against an execution of eiln's bitstream decoder; those remain integration work.
2 changes: 1 addition & 1 deletion drivers/media/platform/apple/avd/Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: GPL-2.0-only

apple-avd-y := avd-drv.o avd-v4l2.o avd-hw.o avd-h264.o avd-vp9.o avd-hevc.o \
apple-avd-y := avd-drv.o avd-v4l2.o avd-hw.o avd-h264.o avd-h264-fp.o avd-vp9.o avd-hevc.o \
avd-av1.o avd-av1-entropymode.o
apple-avd-$(CONFIG_VIDEO_APPLE_AVD_KUNIT_TEST) += avd-h264-test.o
obj-$(CONFIG_VIDEO_APPLE_AVD) += apple-avd.o
12 changes: 12 additions & 0 deletions drivers/media/platform/apple/avd/avd-drv.c
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,14 @@ static int avd_wait_submission_queue(struct avd_ctx *ctx, int vp)
return 0;
}

/* Standalone codec branches still have only the legacy open transport.
* The queue core supplies the strong backend implementation when integrated.
*/
bool __weak avd_backend_is_apple(const struct avd_dev *avd)
{
return false;
}

int avd_init_job(struct avd_ctx *ctx, enum avd_codec codec, size_t segments)
{
int ret = 0;
Expand Down Expand Up @@ -674,6 +682,7 @@ static const struct avd_variant avd_t8103_variant = {
};

static const struct avd_variant avd_t6000_variant = {
.h264_fp_layout = AVD_H264_FP_V3,
.vp_slots = {
[AVD_CODEC_HEVC] = 4,
[AVD_CODEC_H264] = 4,
Expand All @@ -694,6 +703,7 @@ static const struct avd_variant avd_t6000_variant = {
};

static const struct avd_variant avd_t8112_variant = {
.h264_fp_layout = AVD_H264_FP_T8112_TODO,
.vp_slots = {
[AVD_CODEC_HEVC] = 4,
[AVD_CODEC_H264] = 4,
Expand Down Expand Up @@ -754,6 +764,7 @@ static const struct avd_variant avd_t8122_variant = {
};

static const struct avd_variant avd_t8140_variant = {
.h264_fp_layout = AVD_H264_FP_T8140_TODO,
.vp_slots = {
[AVD_CODEC_HEVC] = 2,
[AVD_CODEC_H264] = 1,
Expand Down Expand Up @@ -811,6 +822,7 @@ static const struct avd_variant avd_t8132_variant = {
* verified on the hardware.
*/
static const struct avd_variant avd_t6040_variant = {
.h264_fp_layout = AVD_H264_FP_BORAGE,
.vp_slots = {
[AVD_CODEC_HEVC] = 4,
[AVD_CODEC_H264] = 4,
Expand Down
215 changes: 215 additions & 0 deletions drivers/media/platform/apple/avd/avd-h264-fp.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT
/* H.264 frame-parameter facts. No firmware or captured frame images. */
#include <linux/errno.h>
#include <linux/limits.h>
#include <linux/string.h>
#include <linux/unaligned.h>

#include "avd.h"
#include "avd-queue.h"
#include "avd-h264-fp.h"

/*
* Each generation has an independent provenance. Register revisions or firmware
* tiers do not imply a frame layout. Select this enum from OF match data only.
*
* V3: https://github.com/eiln/avd/blob/main/avid/h264/fp.py (MIT), with
* avid/h264/halv3.py for the address shifts and instruction fields.
* Borage: clean-room protocol SPEC (2026-10-31), sections 4.1, 6.1, 6.2.
* TODO rows: Daybreak per-SoC ledger task t-3862; no V3/Borage fallback.
*/
struct avd_h264_fp_layout_info {
enum avd_h264_fp_layout layout;
size_t size;
const char *source;
};

static const struct avd_h264_fp_layout_info avd_h264_fp_layouts[] = {
{ AVD_H264_FP_V3, 0x8b4e0, "MIT eiln/avd avid/h264/fp.py: V3, T6000" },
{ AVD_H264_FP_BORAGE, 0x1130d0, "SPEC 6.2: T6040/Borage" },
{ AVD_H264_FP_T8112_TODO, 0, "TODO t-3862: T8112 per-SoC ledger" },
{ AVD_H264_FP_T8140_TODO, 0, "TODO t-3862: T8140 per-SoC ledger" },
};

/* Offsets and array capacities from the public V3 layout, not Borage. */
static const struct {
u16 offset;
u16 count;
} v3_fields[AVD_H264_FP_NONE] = {
[AVD_H264_FP_MV_ABOVE_INFO] = { 0xb8, 1 },
[AVD_H264_FP_MV_COLOR] = { 0xbc, 1 },
[AVD_H264_FP_HDR_D0_REF_HDR] = { 0xd0, 16 },
[AVD_H264_FP_HDR_4C_PIC_SCALING_LIST_DIMS] = { 0x4c, 1 },
[AVD_H264_FP_SCL_46C_PIC_SCALING_MATRIX_4X4] = { 0x46c, 24 },
[AVD_H264_FP_SCL_4CC_PIC_SCALING_MATRIX_8X8] = { 0x4cc, 96 },
[AVD_H264_FP_HDR_34_START_HDR] = { 0x34, 1 },
[AVD_H264_FP_HDR_38_MODE] = { 0x38, 1 },
[AVD_H264_FP_HDR_3C_HEIGHT_WIDTH] = { 0x3c, 1 },
[AVD_H264_FP_HDR_40_ZERO] = { 0x40, 1 },
[AVD_H264_FP_HDR_28_HEIGHT_WIDTH_SHIFT3] = { 0x28, 1 },
[AVD_H264_FP_HDR_2C_SPS_PARAM] = { 0x2c, 1 },
[AVD_H264_FP_HDR_44_FLAGS] = { 0x44, 1 },
[AVD_H264_FP_HDR_48_CHROMA_QP_INDEX_OFFSET] = { 0x48, 1 },
[AVD_H264_FP_HDR_58_CONST_3A] = { 0x58, 1 },
[AVD_H264_FP_HDR_9C_PPS_TILE_ADDR_LSB8] = { 0x9c, 1 },
[AVD_H264_FP_IP_ABOVE] = { 0xa0, 1 },
[AVD_H264_FP_LF_ABOVE] = { 0xa4, 1 },
[AVD_H264_FP_LF_ABOVE_INFO] = { 0xa8, 1 },
[AVD_H264_FP_HDR_210_Y_ADDR_LSB8] = { 0x210, 1 },
[AVD_H264_FP_HDR_218_WIDTH_ALIGN] = { 0x218, 1 },
[AVD_H264_FP_HDR_214_UV_ADDR_LSB8] = { 0x214, 1 },
[AVD_H264_FP_HDR_21C_WIDTH_ALIGN] = { 0x21c, 1 },
[AVD_H264_FP_HDR_54_HEIGHT_WIDTH] = { 0x54, 1 },
[AVD_H264_FP_SLC_76C_CMD_WEIGHTS_DENOM] = { 0x76c, 1 },
[AVD_H264_FP_SLC_LUMA_WEIGHTS] = { 0x770, 96 },
[AVD_H264_FP_SLC_LUMA_OFFSETS] = { 0x8f0, 96 },
[AVD_H264_FP_SLC_CHROMA_WEIGHTS_0_] = { 0x770, 96 },
[AVD_H264_FP_SLC_CHROMA_OFFSETS_0_] = { 0x8f0, 96 },
[AVD_H264_FP_SLC_CHROMA_WEIGHTS_1_] = { 0x770, 96 },
[AVD_H264_FP_SLC_CHROMA_OFFSETS_1_] = { 0x8f0, 96 },
[AVD_H264_FP_SLC_A7C_CMD_SET_CODED_SLICE] = { 0xa7c, 1 },
[AVD_H264_FP_SLC_A84_SLICE_ADDR_LOW] = { 0xa84, 1 },
[AVD_H264_FP_SLC_A88_SLICE_HDR_SIZE] = { 0xa88, 1 },
[AVD_H264_FP_SLC_A70_CMD_QUANT_PARAM] = { 0xa70, 1 },
[AVD_H264_FP_SLC_A74_CMD_DEBLOCKING_FILTER] = { 0xa74, 1 },
[AVD_H264_FP_SLC_6E8_CMD_REF_LIST_0] = { 0x6e8, 32 },
[AVD_H264_FP_SLC_6E4_CMD_REF_TYPE] = { 0x6e4, 1 },
[AVD_H264_FP_SLC_A78_SPS_TILE_ADDR2_LSB8] = { 0xa78, 1 },
};

static int fp_word(struct avd_buf *buf, size_t offset, u32 word)
{
if (!buf || !buf->cpu || buf->size < sizeof(u32) ||
offset > buf->size - sizeof(u32))
return -ENOSPC;
put_unaligned_le32(word, (u8 *)buf->cpu + offset);
return 0;
}

int avd_h264_fp_begin(struct avd_h264_fp_cursor *cursor, struct avd_buf *buf,
enum avd_h264_fp_layout layout)
{
unsigned int i;
size_t size = 0;

memset(cursor, 0, sizeof(*cursor));
for (i = 0; i < ARRAY_SIZE(avd_h264_fp_layouts); i++)
if (avd_h264_fp_layouts[i].layout == layout)
size = avd_h264_fp_layouts[i].size;
if (layout != AVD_H264_FP_V3 || !size)
return -EOPNOTSUPP;
if (!buf || !buf->cpu || buf->size < size)
return -ENOSPC;
memset(buf->cpu, 0, size);
cursor->buf = buf;
/* V3 piodma words are transport-owned; do not invent their encodings. */
fp_word(buf, 0x04, 1);
fp_word(buf, 0x14, 0xdeadcafe);
fp_word(buf, 0x18, 0x101);
fp_word(buf, 0x1c, 1);
fp_word(buf, 0x20, 0x8b4c0);
/* Public DFW metadata in AVDH264V3DFWScalingList. */
fp_word(buf, 0x284, 0x1de28b5);
fp_word(buf, 0x468, 0x1de28b5);
fp_word(buf, 0x64c, 0x764099);
fp_word(buf, 0x6c8, 0x124111);
return 0;
}

int avd_h264_fp_emit(struct avd_h264_fp_cursor *cursor,
enum avd_h264_fp_field field, u32 word)
{
unsigned int index;

if (field == AVD_H264_FP_NONE)
return 0;
if (field >= AVD_H264_FP_NONE)
return -EINVAL;
/* Luma and both chroma components share one ordered weights array. */
if (v3_fields[field].offset == 0x770)
index = cursor->weights++;
else if (v3_fields[field].offset == 0x8f0)
index = cursor->offsets++;
else
index = cursor->count[field]++;
if (index >= v3_fields[field].count)
return -E2BIG;
return fp_word(cursor->buf, v3_fields[field].offset + index * 4, word);
}

int avd_h264_fp_address(struct avd_h264_fp_cursor *cursor,
enum avd_h264_fp_field field, unsigned int index,
dma_addr_t addr)
{
if (field == AVD_H264_FP_NONE)
return 0;
/* Builder scratch annotations already identify distinct fields. */
if (field != AVD_H264_FP_HDR_9C_PPS_TILE_ADDR_LSB8)
index = 0;
if (field >= AVD_H264_FP_NONE || index > 7 ||
(addr & 0xff) || (addr >> 8) > U32_MAX)
return -ERANGE;
return fp_word(cursor->buf, v3_fields[field].offset + index * 4, addr >> 8);
}

int avd_h264_fp_ref(struct avd_h264_fp_cursor *cursor, bool reference,
unsigned int index, unsigned int plane, dma_addr_t addr)
{
if (plane >= 4 || index >= 16 || (addr & 0x7f) || (addr >> 7) > U32_MAX)
return -ERANGE;
return fp_word(cursor->buf,
reference ? 0x110 + plane * 0x40 + index * 4 : 0xc0 + plane * 4,
addr >> 7);
}

/*
* [C] Borage fp+0x3d8 bits 0:6 -> cmd+0x00 bits 2:8 (SPEC 6.2).
* [C] Borage fp+0x18/+0x19 -> cmd+0x00 bits 0/1 (SPEC 6.1).
* [C] cmd+0x3c/+0x44 combine fp+0x3dc/+0x5c0/+0x7a4 and
* MMIO 0x01482220; the shift/mask rules are absent from SPEC 6.2.
* The two +0x1c work counts' exact bounds are also not specified there.
* Keep these fields unnamed; no guessed command may reach firmware.
*/
int avd_h264_borage_cmd_fields(struct avd_frame *f)
{
const u8 *fp;
u32 work, slot, flags;
u16 count;

if (!f || !f->fp || !f->fp->cpu || f->fp->size < 0x1130d0 ||
f->idx >= AVD_MAX_FRAMES)
return -EINVAL;
fp = f->fp->cpu;
count = get_unaligned_le16(fp + 0x1a);
/* validateAvcFields' independently established slice-count bound. */
if (count >= 0x385)
return -EINVAL;
work = (f->cmd[1] >> 16) & 0xffff;
if (work >= 4)
return -EINVAL;
work = 0x01483120 + 0x1814 * work;
slot = 0x1130d0 * f->idx;
flags = (fp[0x18] & 1) | ((fp[0x19] & 1) << 1) |
((get_unaligned_le32(fp + 0x3d8) & 0x7f) << 2) | ((u32)count << 16);
/* Preserve the common prefix outside the codec's documented masks. */
f->cmd[0] = (f->cmd[0] & ~0xffff01ffU) | flags;
f->cmd[1] = (f->cmd[1] & 0xffff0000U) | get_unaligned_le16(fp + 0x1c);
f->cmd[3] = work;
f->cmd[4] = slot + 0x3dc;
f->cmd[5] = work + 0x0c;
f->cmd[6] = slot + 0x5c0;
f->cmd[7] = slot + 0x8a0;
f->cmd[10] = work + 0x34;
f->cmd[11] = work + 0x350;
f->cmd[12] = work + 0x3ac;
/* Projection is reviewable/testable, but not a publishable descriptor. */
return -EOPNOTSUPP;
}

int avd_h264_fp_pack_cmd(enum avd_h264_fp_layout layout, struct avd_frame *f)
{
/* V3 command transport is not the Borage 0x60 command (SPEC 6.2). */
if (layout != AVD_H264_FP_BORAGE)
return -EOPNOTSUPP;
return avd_h264_borage_cmd_fields(f);
}
Loading