# 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("= 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()