mirror of
https://github.com/AsahiLinux/m1n1
synced 2024-11-22 14:43:08 +00:00
16a6b41c98
Not much to see here, most of the juice is over at: https://github.com/eiln/avd.git The kernel driver (m1n1.fw.avd) only really pipes the instruction stream into the respective hardware FIFOs and then hushes the interrupt lines. Most of the work (bitstream syntax parsing and instruction generation) is done in the avid repo above. I'm hoping to keep this userland-kernel separation in the very imminent actual driver. experiments/avd.py: Decode on the command line. Read file for usage. experiments/avd_e.py: Decode via emulated instruction stream. experiments/avd_f.py: Decode via Cortex-M3 firmware (for debugging). hv/trace_avd.py: Tracer. Read file for usage. m1n1/fw/avd/__init__.py: Driver base class (power, tunables, etc). m1n1/fw/avd/decoder.py: Codec-specific decode logic + mini media player. Signed-off-by: Eileen Yoon <eyn@gmx.com>
224 lines
9.2 KiB
Python
224 lines
9.2 KiB
Python
# SPDX-License-Identifier: MIT
|
|
from m1n1.trace import Tracer
|
|
from m1n1.trace.dart import DARTTracer
|
|
from m1n1.utils import *
|
|
from m1n1.proxyutils import RegMonitor
|
|
hv.p.hv_set_time_stealing(0, 1)
|
|
|
|
# Usage
|
|
#
|
|
# 2023/12/25: Only tested on J293AP (AVD revision "Viola"/V3).
|
|
# Should work on all baseline M1s.
|
|
#
|
|
# 1. Start tracer under the hypervisor
|
|
#
|
|
# 2. Send over the bitstream(s) to the target machine
|
|
# Supported formats: .264, .265, .ivf (unless you want to add a demuxer to avid/codecs)
|
|
#
|
|
# 3. The tracer is purposely not activated at boot. As of 13.5, it takes ~2 HEVC runs
|
|
# to get to the login screen - it's probably decoding the login screen. By "activate",
|
|
# I mean the tracer variable "outdir" is NULLed s.t. the tracer will not save the
|
|
# traced data and merely log the IPC transactions.
|
|
#
|
|
# [cpu0] [AVDTracer@/arm-io/avd] sent fw command at 0x108eb30
|
|
# 00000000 00000801 00030007 0050c000 000002a8 00000003 01091a70 01091a78 00000001
|
|
# 00000020 0050caa4 01091af0 01091bc0 01091c50 0050c2a4 0050c210 0050c28c 00000000
|
|
# 00000040 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
|
|
# [cpu0] [0xfffffe00149d9984] MMIO: W.4 0x269098054 (avd[0], offset 0x1098054) = 0x108eb30
|
|
# [cpu7] [0xfffffe00149d99b0] MMIO: R.4 0x269098048 (avd[0], offset 0x1098048) = 0x9
|
|
#
|
|
# 4. To save the trace contents, break into the hypervisor console and set
|
|
#
|
|
# >>> tracer.outdir = "matrix_1080X512"
|
|
#
|
|
# The output data dir will become 'data/[inferred codec name]/$outdir'.
|
|
# The data dir will be created if it does not exist. The directory structure is
|
|
# meant to look like this:
|
|
#
|
|
# proxyclient/
|
|
# data/
|
|
# h264/*
|
|
# h265/*
|
|
# vp9/*
|
|
#
|
|
# 5. After the directory name is configured, trigger avd from the target machine:
|
|
#
|
|
# ffmpeg -hwaccel videotoolbox -i matrix_1080X512.ivf
|
|
#
|
|
# Or you can access VT directly yourself with some Obj-C (but why would you want to do that..)
|
|
# Though I may need to do that to test some sekrit undocumented features.
|
|
# The input bitstream is hopefully the matching one as the directory name just set.
|
|
#
|
|
# 6. If all goes well (i.e. the bitstream is decoding on AVD), the tracer will save:
|
|
#
|
|
# >> ~/m1n1/proxyclient $ ls data/*
|
|
# data/vp9/matrix_1080X512:
|
|
# frame.2023-12-17T21:17:47.519048.00004000.bin probs.2023-12-17T21:17:47.519048.00004000.00004000.bin
|
|
# frame.2023-12-17T21:17:47.578537.000bc000.bin probs.2023-12-17T21:17:47.578537.000bc000.0000c000.bin
|
|
# frame.2023-12-17T21:17:47.633768.00174000.bin probs.2023-12-17T21:17:47.633768.00174000.00014000.bin
|
|
# frame.2023-12-17T21:17:47.688067.0022c000.bin probs.2023-12-17T21:17:47.688067.0022c000.0001c000.bin
|
|
#
|
|
# The "frame" is the macOS source frame_params struct, and this directory is the
|
|
# one intended to be supplied to all the avid tools (e.g. emulator, differs).
|
|
# For VP9 (and presumably AV1) the tracer will additionally save the "probs" blob.
|
|
# You can also bypass FairPlay encryption and save the coded bitstream, but that's
|
|
# an exercise left for the reader.
|
|
#
|
|
# 7. Copy the data directory over to `avd/data/*` & have fun :D
|
|
#
|
|
# python3 avd_emu.py -i frame.2023-12-17T21:17:47.519048.00004000.bin # emulate single fp
|
|
# python3 avd_emu.py -d vp9/matrix_1080X512 -a # emulate all using trace dir name
|
|
# python3 tools/test.py -m vp9 -d vp9/matrix_1080X512 -e -a # test against emulated output
|
|
#
|
|
# Optionally save the firmware to run on the emulator via
|
|
# >>> tracer.save_firmware("data/fw.bin")
|
|
|
|
import datetime
|
|
import os
|
|
import struct
|
|
import time
|
|
|
|
class AVDTracer(Tracer):
|
|
DEFAULT_MODE = TraceMode.SYNC
|
|
|
|
def __init__(self, hv, dev_path, dart_tracer, verbose=False):
|
|
super().__init__(hv, verbose=verbose, ident=type(self).__name__ + "@" + dev_path)
|
|
self.dev = hv.adt[dev_path]
|
|
self.dart_tracer = dart_tracer
|
|
self.base = self.dev.get_reg(0)[0] # 0x268000000
|
|
self.p = hv.p
|
|
self.u = hv.u
|
|
self.dart = dart_tracer.dart
|
|
|
|
mon = RegMonitor(hv.u)
|
|
AVD_REGS = [
|
|
#(0x1000000, 0x4000, "unk0"),
|
|
#(0x1010000, 0x4000, "dart"),
|
|
#(0x1002000, 0x1000, "unk2"),
|
|
(0x1070000, 0x4000, "piodma"),
|
|
(0x1088000, 0x4000, "sram"),
|
|
(0x108c000, 0xc000, "cmd"),
|
|
#(0x1098000, 0x4000, "mbox"),
|
|
#(0x10a3000, 0x1000, "unka"),
|
|
(0x1100000, 0xc000, "config"),
|
|
(0x110c000, 0x4000, "dma"),
|
|
#(0x1400000, 0x4000, "wrap"),
|
|
]
|
|
#for (offset, size, name) in AVD_REGS: mon.add(self.base + offset, size, name=name)
|
|
self.mon = mon
|
|
|
|
iomon = RegMonitor(hv.u, ascii=True)
|
|
iomon1 = RegMonitor(hv.u, ascii=True)
|
|
def readmem_iova(addr, size, readfn=None):
|
|
try:
|
|
return dart_tracer.dart.ioread(0, addr, size)
|
|
except Exception as e:
|
|
print(e)
|
|
return None
|
|
iomon.readmem = readmem_iova
|
|
def readmem_iova(addr, size, readfn=None):
|
|
try:
|
|
return dart_tracer.dart.ioread(1, addr, size)
|
|
except Exception as e:
|
|
print(e)
|
|
return None
|
|
iomon1.readmem = readmem_iova
|
|
self.iomon = iomon
|
|
self.iomon1 = iomon1
|
|
self.state_active = False
|
|
self.outdir = ""
|
|
|
|
def avd_r32(self, off): return self.p.read32(self.base + off)
|
|
def avd_w32(self, off, x): return self.p.write32(self.base + off, x)
|
|
def avd_r64(self, off): return self.p.read64(self.base + off)
|
|
def avd_w64(self, off, x): return self.p.write64(self.base + off, x)
|
|
|
|
def start(self):
|
|
self.hv.trace_range(irange(self.dev.get_reg(0)[0], self.dev.get_reg(0)[1]), mode=TraceMode.SYNC)
|
|
self.hv.trace_range(irange(self.base + 0x1080000, 0x18000), False)
|
|
self.hv.add_tracer(irange(self.base + 0x1098054, 4), "avd-mbox-54", TraceMode.SYNC, self.evt_rw_hook, self.w_AVD_MBOX_0054)
|
|
self.hv.add_tracer(irange(self.base + 0x1098064, 4), "avd-mbox-64", TraceMode.SYNC, self.r_AVD_MBOX_0064, self.evt_rw_hook)
|
|
|
|
def poll(self):
|
|
self.mon.poll()
|
|
self.iomon.poll()
|
|
self.iomon1.poll()
|
|
|
|
def evt_rw_hook(self, x):
|
|
self.poll()
|
|
|
|
def w_AVD_MBOX_0054(self, x):
|
|
if ((x.data >= 0x1080000) and (x.data <= 0x10a0000)):
|
|
self.log("Sent fw command at 0x%x" % (x.data))
|
|
self.poll()
|
|
cmd = self.read_regs(self.base + x.data, 0x60)
|
|
chexdump32(cmd)
|
|
|
|
opcode = struct.unpack("<I", cmd[:4])[0] & 0xf
|
|
if (opcode == 0):
|
|
self.log("Command start")
|
|
self.state_active = True
|
|
self.access_idx = 0
|
|
|
|
elif (opcode == 1):
|
|
frame_params_iova = self.p.read32(self.base + x.data + 0x8)
|
|
if (self.outdir) and (frame_params_iova != 0x0):
|
|
t = datetime.datetime.now().isoformat()
|
|
frame_params = self.dart.ioread(1, frame_params_iova, 0xb0000)
|
|
|
|
word = self.p.read32(self.base + x.data)
|
|
if (word & 0x000) == 0x000: # h265
|
|
name = "h265"
|
|
elif (word & 0x400) == 0x400: # h264
|
|
name = "h264"
|
|
elif (word & 0x800) == 0x800: # vp9
|
|
name = "vp9"
|
|
else:
|
|
name = "unk"
|
|
outdir = os.path.join("data", name, self.outdir)
|
|
os.makedirs(outdir, exist_ok=True)
|
|
open(os.path.join(outdir, f'frame.{t}.{frame_params_iova:08x}.bin'), "wb").write(frame_params)
|
|
|
|
if (word & 0x800) == 0x800: # save probs for vp9
|
|
iova = [0x4000, 0xc000, 0x14000, 0x1c000][self.access_idx % 4]
|
|
open(os.path.join(outdir, f'probs.{t}.{frame_params_iova:08x}.{iova:08x}.bin'), "wb").write(self.dart.ioread(0, iova, 0x4000))
|
|
self.access_idx += 1
|
|
|
|
elif (opcode == 2):
|
|
self.log("Command end")
|
|
self.state_active = False
|
|
self.access_idx = 0
|
|
|
|
def r_AVD_MBOX_0064(self, x):
|
|
if ((x.data >= 0x1080000) and (x.data <= 0x10a0000)):
|
|
self.log("Received fw command at 0x%x" % (x.data))
|
|
cmd = self.read_regs(self.base + x.data, 0x60)
|
|
chexdump32(cmd)
|
|
self.poll()
|
|
|
|
def read_regs(self, addr, size):
|
|
scratch = self.u.malloc(size)
|
|
p.memcpy32(scratch, addr, size)
|
|
return self.p.iface.readmem(scratch, size)
|
|
|
|
def read_iova(self, start, end, stream=0):
|
|
data = b''
|
|
for i in range((end - start) // 0x4000):
|
|
try:
|
|
d = self.dart_tracer.dart.ioread(stream, start + (i * 0x4000), 0x4000)
|
|
except:
|
|
d = b'\0' * 0x4000
|
|
data += d
|
|
return data
|
|
|
|
def save_firmware(self, path="fw.bin"):
|
|
firmware = self.read_regs(self.base + 0x1080000, 0x10000)
|
|
open(path, "wb").write(firmware)
|
|
|
|
p.pmgr_adt_clocks_enable('/arm-io/dart-avd')
|
|
p.pmgr_adt_clocks_enable('/arm-io/avd')
|
|
dart_tracer = DARTTracer(hv, "/arm-io/dart-avd", verbose=0)
|
|
dart_tracer.start()
|
|
dart = dart_tracer.dart
|
|
tracer = AVDTracer(hv, '/arm-io/avd', dart_tracer, verbose=3)
|
|
tracer.start()
|