summaryrefslogtreecommitdiff
path: root/tools/proxyclient
diff options
context:
space:
mode:
Diffstat (limited to 'tools/proxyclient')
-rwxr-xr-xtools/proxyclient/experiments/addrdump.py28
-rw-r--r--tools/proxyclient/experiments/aes.py144
-rw-r--r--tools/proxyclient/experiments/agx_1tri.py840
-rwxr-xr-xtools/proxyclient/experiments/agx_boot.py27
-rw-r--r--tools/proxyclient/experiments/agx_cancel.py246
-rw-r--r--tools/proxyclient/experiments/agx_deps.py242
-rw-r--r--tools/proxyclient/experiments/agx_dumpstructs.py19
-rw-r--r--tools/proxyclient/experiments/agx_parallel.py341
-rw-r--r--tools/proxyclient/experiments/agx_renderframe.py73
-rw-r--r--tools/proxyclient/experiments/agx_tlb.py295
-rw-r--r--tools/proxyclient/experiments/agx_tracetimings.py314
-rwxr-xr-xtools/proxyclient/experiments/aic_test.py411
-rw-r--r--tools/proxyclient/experiments/amcc_err_handler.py43
-rwxr-xr-xtools/proxyclient/experiments/aop.py310
-rwxr-xr-xtools/proxyclient/experiments/audio_capture.py132
-rwxr-xr-xtools/proxyclient/experiments/chickens.py108
-rwxr-xr-xtools/proxyclient/experiments/cpu_pstate_latencies.py239
-rwxr-xr-xtools/proxyclient/experiments/cpu_pstates.py150
-rwxr-xr-xtools/proxyclient/experiments/dart_dump.py19
-rwxr-xr-xtools/proxyclient/experiments/dcp.py351
-rwxr-xr-xtools/proxyclient/experiments/dcp_iboot.py108
-rw-r--r--tools/proxyclient/experiments/dcpext_iboot.py126
-rwxr-xr-xtools/proxyclient/experiments/find_sprr_regs.py82
-rwxr-xr-xtools/proxyclient/experiments/fptest.py114
-rwxr-xr-xtools/proxyclient/experiments/hacr_trap_bits.py128
-rwxr-xr-xtools/proxyclient/experiments/i2c.py124
-rw-r--r--tools/proxyclient/experiments/jpeg.py1222
-rw-r--r--tools/proxyclient/experiments/jpeg_doc.md1019
-rwxr-xr-xtools/proxyclient/experiments/memdump.py22
-rwxr-xr-xtools/proxyclient/experiments/mmio_sweep.py212
-rw-r--r--tools/proxyclient/experiments/mtp.py129
-rwxr-xr-xtools/proxyclient/experiments/ohmmeter.py88
-rwxr-xr-xtools/proxyclient/experiments/pcie_enable_devices.py18
-rw-r--r--tools/proxyclient/experiments/prores.py695
-rw-r--r--tools/proxyclient/experiments/scaler.py1527
-rwxr-xr-xtools/proxyclient/experiments/smc.py47
-rwxr-xr-xtools/proxyclient/experiments/smc_watcher.py115
-rwxr-xr-xtools/proxyclient/experiments/speaker_amp.py148
-rw-r--r--tools/proxyclient/experiments/spi.py182
-rwxr-xr-xtools/proxyclient/experiments/sprr_test_permissions.py366
-rwxr-xr-xtools/proxyclient/experiments/timer_test.py109
-rw-r--r--tools/proxyclient/hv/README.md4
-rw-r--r--tools/proxyclient/hv/trace_agx.py107
-rw-r--r--tools/proxyclient/hv/trace_agx_defer.py35
-rw-r--r--tools/proxyclient/hv/trace_agx_pwr.py158
-rw-r--r--tools/proxyclient/hv/trace_all.py25
-rw-r--r--tools/proxyclient/hv/trace_all_more.py23
-rw-r--r--tools/proxyclient/hv/trace_aop.py349
-rw-r--r--tools/proxyclient/hv/trace_atc.py61
-rw-r--r--tools/proxyclient/hv/trace_cd3217.py119
-rw-r--r--tools/proxyclient/hv/trace_codecs.py45
-rw-r--r--tools/proxyclient/hv/trace_dart.py27
-rw-r--r--tools/proxyclient/hv/trace_dcp.py1113
-rw-r--r--tools/proxyclient/hv/trace_gpio.py40
-rw-r--r--tools/proxyclient/hv/trace_i2c.py41
-rw-r--r--tools/proxyclient/hv/trace_isp.py5
-rw-r--r--tools/proxyclient/hv/trace_keyboard.py206
-rw-r--r--tools/proxyclient/hv/trace_mesa.py212
-rw-r--r--tools/proxyclient/hv/trace_nvme.py275
-rw-r--r--tools/proxyclient/hv/trace_pmgr.py170
-rw-r--r--tools/proxyclient/hv/trace_prores.py88
-rw-r--r--tools/proxyclient/hv/trace_smc.py108
-rw-r--r--tools/proxyclient/hv/trace_wlan.py446
-rw-r--r--tools/proxyclient/hv/trace_z2.py221
-rw-r--r--tools/proxyclient/m1n1/__init__.py0
-rw-r--r--tools/proxyclient/m1n1/adt.py713
-rw-r--r--tools/proxyclient/m1n1/agx/__init__.py343
-rw-r--r--tools/proxyclient/m1n1/agx/channels.py178
-rw-r--r--tools/proxyclient/m1n1/agx/context.py247
-rw-r--r--tools/proxyclient/m1n1/agx/event.py58
-rw-r--r--tools/proxyclient/m1n1/agx/initdata.py387
-rw-r--r--tools/proxyclient/m1n1/agx/object.py263
-rw-r--r--tools/proxyclient/m1n1/agx/render.py1075
-rw-r--r--tools/proxyclient/m1n1/agx/shim.py244
-rw-r--r--tools/proxyclient/m1n1/agx/uapi.py116
-rw-r--r--tools/proxyclient/m1n1/asm.py133
-rw-r--r--tools/proxyclient/m1n1/constructutils.py879
-rw-r--r--tools/proxyclient/m1n1/find_regs.py99
-rw-r--r--tools/proxyclient/m1n1/fw/__init__.py0
-rw-r--r--tools/proxyclient/m1n1/fw/afk/__init__.py0
-rw-r--r--tools/proxyclient/m1n1/fw/afk/epic.py292
-rw-r--r--tools/proxyclient/m1n1/fw/afk/rbep.py225
-rw-r--r--tools/proxyclient/m1n1/fw/agx/__init__.py98
-rw-r--r--tools/proxyclient/m1n1/fw/agx/channels.py481
-rw-r--r--tools/proxyclient/m1n1/fw/agx/cmdqueue.py516
-rw-r--r--tools/proxyclient/m1n1/fw/agx/handoff.py120
-rw-r--r--tools/proxyclient/m1n1/fw/agx/initdata.py1931
-rw-r--r--tools/proxyclient/m1n1/fw/agx/microsequence.py800
-rw-r--r--tools/proxyclient/m1n1/fw/aop/__init__.py31
-rw-r--r--tools/proxyclient/m1n1/fw/aop/bootargs.py64
-rw-r--r--tools/proxyclient/m1n1/fw/aop/ipc.py365
-rw-r--r--tools/proxyclient/m1n1/fw/asc/__init__.py125
-rw-r--r--tools/proxyclient/m1n1/fw/asc/base.py59
-rw-r--r--tools/proxyclient/m1n1/fw/asc/crash.py248
-rw-r--r--tools/proxyclient/m1n1/fw/asc/ioreporting.py56
-rw-r--r--tools/proxyclient/m1n1/fw/asc/kdebug.py57
-rw-r--r--tools/proxyclient/m1n1/fw/asc/mgmt.py144
-rw-r--r--tools/proxyclient/m1n1/fw/asc/oslog.py30
-rw-r--r--tools/proxyclient/m1n1/fw/asc/syslog.py72
-rw-r--r--tools/proxyclient/m1n1/fw/common.py170
-rw-r--r--tools/proxyclient/m1n1/fw/dcp/__init__.py2
-rw-r--r--tools/proxyclient/m1n1/fw/dcp/client.py14
-rw-r--r--tools/proxyclient/m1n1/fw/dcp/dcpav.py110
-rw-r--r--tools/proxyclient/m1n1/fw/dcp/dcpep.py163
-rw-r--r--tools/proxyclient/m1n1/fw/dcp/iboot.py215
-rw-r--r--tools/proxyclient/m1n1/fw/dcp/ipc.py789
-rw-r--r--tools/proxyclient/m1n1/fw/dcp/manager.py319
-rw-r--r--tools/proxyclient/m1n1/fw/dcp/parse_log.py43
-rw-r--r--tools/proxyclient/m1n1/fw/mtp.py411
-rw-r--r--tools/proxyclient/m1n1/fw/pmp.py168
-rw-r--r--tools/proxyclient/m1n1/fw/smc.py202
-rw-r--r--tools/proxyclient/m1n1/gpiola.py254
-rw-r--r--tools/proxyclient/m1n1/hostutils.py96
-rw-r--r--tools/proxyclient/m1n1/hv/__init__.py1849
-rw-r--r--tools/proxyclient/m1n1/hv/gdbserver/__init__.py480
-rw-r--r--tools/proxyclient/m1n1/hv/gdbserver/features/aarch64-core.xml91
-rw-r--r--tools/proxyclient/m1n1/hv/gdbserver/features/aarch64-fpu.xml160
-rw-r--r--tools/proxyclient/m1n1/hv/gdbserver/features/target.xml8
-rw-r--r--tools/proxyclient/m1n1/hv/types.py60
-rw-r--r--tools/proxyclient/m1n1/hv/virtio.py133
-rw-r--r--tools/proxyclient/m1n1/hv/virtutils.py43
-rw-r--r--tools/proxyclient/m1n1/hw/admac.py416
-rw-r--r--tools/proxyclient/m1n1/hw/aes.py110
-rw-r--r--tools/proxyclient/m1n1/hw/agx.py111
-rw-r--r--tools/proxyclient/m1n1/hw/asc.py121
-rw-r--r--tools/proxyclient/m1n1/hw/atc.py455
-rw-r--r--tools/proxyclient/m1n1/hw/codecs/__init__.py109
-rw-r--r--tools/proxyclient/m1n1/hw/codecs/cs42l84.py365
-rw-r--r--tools/proxyclient/m1n1/hw/dart.py102
-rw-r--r--tools/proxyclient/m1n1/hw/dart8020.py381
-rw-r--r--tools/proxyclient/m1n1/hw/dart8110.py541
-rw-r--r--tools/proxyclient/m1n1/hw/dockchannel.py120
-rw-r--r--tools/proxyclient/m1n1/hw/dwc3.py268
-rw-r--r--tools/proxyclient/m1n1/hw/i2c.py251
-rw-r--r--tools/proxyclient/m1n1/hw/isp.py507
-rw-r--r--tools/proxyclient/m1n1/hw/jpeg.py334
-rw-r--r--tools/proxyclient/m1n1/hw/mca.py110
-rw-r--r--tools/proxyclient/m1n1/hw/nco.py89
-rw-r--r--tools/proxyclient/m1n1/hw/pmgr.py71
-rw-r--r--tools/proxyclient/m1n1/hw/pmu.py36
-rw-r--r--tools/proxyclient/m1n1/hw/prores.py250
-rw-r--r--tools/proxyclient/m1n1/hw/scaler.py212
-rw-r--r--tools/proxyclient/m1n1/hw/sep.py169
-rw-r--r--tools/proxyclient/m1n1/hw/spi.py147
-rw-r--r--tools/proxyclient/m1n1/hw/spmi.py98
-rw-r--r--tools/proxyclient/m1n1/hw/uat.py571
-rw-r--r--tools/proxyclient/m1n1/loadobjs.py178
-rw-r--r--tools/proxyclient/m1n1/macho.py270
-rw-r--r--tools/proxyclient/m1n1/malloc.py98
-rw-r--r--tools/proxyclient/m1n1/proxy.py1104
-rw-r--r--tools/proxyclient/m1n1/proxyutils.py550
-rw-r--r--tools/proxyclient/m1n1/setup.py33
-rw-r--r--tools/proxyclient/m1n1/shell.py213
-rw-r--r--tools/proxyclient/m1n1/sysreg.py383
-rw-r--r--tools/proxyclient/m1n1/tgtypes.py30
-rw-r--r--tools/proxyclient/m1n1/trace/__init__.py220
-rw-r--r--tools/proxyclient/m1n1/trace/agx.py1148
-rw-r--r--tools/proxyclient/m1n1/trace/asc.py271
-rw-r--r--tools/proxyclient/m1n1/trace/dart.py69
-rw-r--r--tools/proxyclient/m1n1/trace/dockchannel.py37
-rw-r--r--tools/proxyclient/m1n1/trace/gpio.py67
-rw-r--r--tools/proxyclient/m1n1/trace/i2c.py225
-rw-r--r--tools/proxyclient/m1n1/trace/isp.py118
-rw-r--r--tools/proxyclient/m1n1/trace/pcie.py108
-rw-r--r--tools/proxyclient/m1n1/trace/spi.py10
-rw-r--r--tools/proxyclient/m1n1/utils.py1144
-rw-r--r--tools/proxyclient/m1n1/xnutools.py117
-rwxr-xr-xtools/proxyclient/tools/admac_stream.py87
-rwxr-xr-xtools/proxyclient/tools/chainload.py137
-rwxr-xr-xtools/proxyclient/tools/codecshell.py83
-rwxr-xr-xtools/proxyclient/tools/dump_pmgr.py138
-rwxr-xr-xtools/proxyclient/tools/freebsd.py136
-rwxr-xr-xtools/proxyclient/tools/linux.py153
-rwxr-xr-xtools/proxyclient/tools/picocom-sec.sh7
-rwxr-xr-xtools/proxyclient/tools/pmgr_adt2dt.py79
-rwxr-xr-xtools/proxyclient/tools/reboot.py11
-rwxr-xr-xtools/proxyclient/tools/reset_panic_counter.py9
-rwxr-xr-xtools/proxyclient/tools/run_guest.py115
-rwxr-xr-xtools/proxyclient/tools/run_guest_kernel.sh58
-rwxr-xr-xtools/proxyclient/tools/second_proxy.py10
-rwxr-xr-xtools/proxyclient/tools/shell.py9
-rwxr-xr-xtools/proxyclient/tools/smccli.py19
182 files changed, 44408 insertions, 0 deletions
diff --git a/tools/proxyclient/experiments/addrdump.py b/tools/proxyclient/experiments/addrdump.py
new file mode 100755
index 0000000..ba23226
--- /dev/null
+++ b/tools/proxyclient/experiments/addrdump.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+from m1n1.setup import *
+
+blacklist = []
+
+print("Dumping address space...")
+of = None
+if len(sys.argv) > 1:
+ of = open(sys.argv[1],"w")
+ print("Also dumping to file %s")
+
+for i in range(0x0000, 0x10000):
+ if i in blacklist:
+ v = "%08x: SKIPPED"%(i<<16)
+ else:
+ a = (i<<16) + 0x1000
+ d = p.read32(a)
+ v = "%08x: %08x"%(a, d)
+ print(v)
+ if of:
+ of.write(v+"\n")
+
+if of:
+ of.close()
diff --git a/tools/proxyclient/experiments/aes.py b/tools/proxyclient/experiments/aes.py
new file mode 100644
index 0000000..77d017a
--- /dev/null
+++ b/tools/proxyclient/experiments/aes.py
@@ -0,0 +1,144 @@
+# SPDX-License-Identifier: MIT
+import sys, pathlib
+
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+from m1n1.setup import *
+from m1n1.shell import run_shell
+from m1n1.hw.dart import DART
+from m1n1.hw.aes import *
+
+def aes_set_custom_key(
+ aes,
+ key,
+ encrypt=True,
+ mode=AES_SET_KEY_BLOCK_MODE.CTR,
+ keyslot=0,
+ keygen=0,
+):
+ keylen = {
+ 16: AES_SET_KEY_LEN.AES128,
+ 24: AES_SET_KEY_LEN.AES192,
+ 32: AES_SET_KEY_LEN.AES256,
+ }[len(key)]
+
+ aes.R_CMD_FIFO = AESSetKeyCommand(
+ KEY_SELECT=0,
+ KEYLEN=keylen,
+ ENCRYPT=1 if encrypt else 0,
+ BLOCK_MODE=mode,
+ SLOT=keyslot,
+ KEYGEN=keygen,
+ ).value
+
+ for i in range(0, len(key), 4):
+ aes.R_CMD_FIFO = struct.unpack(">I", key[i : i + 4])[0]
+
+
+def aes_set_hw_key(
+ aes,
+ key,
+ keylen=AES_SET_KEY_LEN.AES128,
+ encrypt=True,
+ mode=AES_SET_KEY_BLOCK_MODE.CTR,
+ slot=0,
+ keygen=0,
+):
+ aes.R_CMD_FIFO = AESSetKeyCommand(
+ KEY_SELECT=key,
+ KEYLEN=keylen,
+ ENCRYPT=1 if encrypt else 0,
+ BLOCK_MODE=mode,
+ SLOT=slot,
+ KEYGEN=keygen,
+ ).value
+
+
+def aes_set_iv(aes, iv, slot=0):
+ assert len(iv) == 16
+ aes.R_CMD_FIFO = AESSetIVCommand(SLOT=slot)
+
+ for i in range(0, len(iv), 4):
+ aes.R_CMD_FIFO = struct.unpack(">I", iv[i : i + 4])[0]
+
+
+def aes_crypt(aes, dart, data, key_slot=0, iv_slot=0):
+ assert len(data) % 16 == 0
+
+ bfr = p.memalign(0x4000, len(data))
+ iova = dart.iomap(1, bfr, len(data))
+ dart.iowrite(1, iova, data)
+
+ aes.R_CMD_FIFO = AESCryptCommand(LEN=len(data), KEY_SLOT=key_slot, IV_SLOT=iv_slot)
+ aes.R_CMD_FIFO = 0 # actually upper bits of addr
+ aes.R_CMD_FIFO = iova # src
+ aes.R_CMD_FIFO = iova # dst
+
+ aes.R_CMD_FIFO = AESBarrierCommand(IRQ=1).value
+ time.sleep(0.1)
+ # while aes.R_IRQ_STATUS.reg.FLAG != 1:
+ # pass
+ # aes.dump_regs()
+ aes.R_IRQ_STATUS = aes.R_IRQ_STATUS.val
+
+ res = dart.ioread(1, iova, len(data))
+ return res
+
+
+def test_hw_key(key, keylen, keygen=0):
+ aes.R_IRQ_STATUS = aes.R_IRQ_STATUS.val
+ aes.R_CONTROL.set(CLEAR_FIFO=1)
+ aes.R_CONTROL.set(RESET=1)
+ aes.R_CONTROL.set(START=1)
+ # aes.dump_regs()
+ aes_set_hw_key(aes, key, keylen, slot=0, keygen=keygen)
+ # print(aes.R_IRQ_STATUS)
+ aes_set_iv(aes, b"\x00" * 16, slot=0)
+ chexdump(aes_crypt(aes, dart, b"\x00" * 16, key_slot=0, iv_slot=1))
+ # aes.dump_regs()
+ aes.R_CONTROL.set(STOP=1)
+
+
+def test_custom_key(key, keygen=0):
+ aes.R_IRQ_STATUS = aes.R_IRQ_STATUS.val
+ aes.R_CONTROL.set(CLEAR_FIFO=1)
+ aes.R_CONTROL.set(RESET=1)
+ aes.R_CONTROL.set(START=1)
+ # aes.dump_regs()
+ aes_set_custom_key(aes, key, keyslot=0, keygen=keygen)
+ aes_set_iv(aes, b"\x00" * 16)
+ aes_set_iv(aes, b"\x11" * 16, slot=1)
+ chexdump(aes_crypt(aes, dart, b"\x00" * 16, key_slot=0, iv_slot=0))
+ # aes.dump_regs()
+ aes.R_CONTROL.set(STOP=1)
+
+
+p.pmgr_adt_clocks_enable("/arm-io/aes")
+
+dart = DART.from_adt(u, "/arm-io/dart-sio")
+dart.initialize()
+
+aes_base, _ = u.adt["/arm-io/aes"].get_reg(0)
+aes = AESRegs(u, aes_base)
+aes.dump_regs()
+
+dart.dump_all()
+
+for keygen in range(4):
+ print(f"zero key, keygen={keygen}", end="")
+ test_custom_key(b"\x00" * 16, keygen=keygen)
+
+for keygen in range(4):
+ print("#" * 10)
+ for keylen in [
+ AES_SET_KEY_LEN.AES128,
+ AES_SET_KEY_LEN.AES192,
+ AES_SET_KEY_LEN.AES256,
+ ]:
+ for i in (1, 3):
+ print(f"key = {i}, keylen={keylen}, keygen={keygen}", end="")
+ test_hw_key(i, keylen, keygen=keygen)
+
+dart.dump_all()
+
+run_shell(globals(), msg="Have fun!")
diff --git a/tools/proxyclient/experiments/agx_1tri.py b/tools/proxyclient/experiments/agx_1tri.py
new file mode 100644
index 0000000..af06713
--- /dev/null
+++ b/tools/proxyclient/experiments/agx_1tri.py
@@ -0,0 +1,840 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+
+import sys, pathlib, time
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+from m1n1.setup import *
+from m1n1.constructutils import Ver
+from m1n1.utils import *
+
+Ver.set_version(u)
+
+from m1n1.shell import run_shell
+
+from m1n1.agx import AGX
+from m1n1.agx.context import *
+
+p.pmgr_adt_clocks_enable("/arm-io/gfx-asc")
+p.pmgr_adt_clocks_enable("/arm-io/sgx")
+#p.pmgr_adt_clocks_enable("/arm-io/pmp")
+
+# [cpu0] [0xfffffe00124bf5c0] MMIO: R.4 0x204d14000 (sgx, offset 0xd14000) = 0x0
+p.read32(0x204000000 + 0xd14000)
+# [cpu0] [0xfffffe00124bf9a8] MMIO: W.4 0x204d14000 (sgx, offset 0xd14000) = 0x70001
+p.write32(0x204000000 + 0xd14000, 0x70001)
+
+#p.read32(0x204010258)
+
+agx = AGX(u)
+
+mon = RegMonitor(u, ascii=True, bufsize=0x8000000)
+agx.mon = mon
+
+sgx = agx.sgx_dev
+mon.add(sgx.gpu_region_base, sgx.gpu_region_size, "contexts")
+mon.add(sgx.gfx_shared_region_base, sgx.gfx_shared_region_size, "gfx-shared")
+mon.add(sgx.gfx_handoff_base, sgx.gfx_handoff_size, "gfx-handoff")
+
+#addr, size = sgx.get_reg(0)
+#mon.add(addr + 0x600000, size - 0x600000, "sgx")
+
+addr, size = u.adt["/arm-io/aic"].get_reg(0)
+mon.add(addr, size, "aic")
+
+def unswizzle(addr, w, h, psize, dump=None, grid=False):
+ tw = 64
+ th = 64
+ ntx = (w + tw - 1) // 64
+ nty = (h + th - 1) // 64
+ data = iface.readmem(addr, ntx * nty * psize * tw * th)
+ new_data = []
+ for y in range(h):
+ ty = y // th
+ for x in range(w):
+ tx = x // tw
+ toff = tw * th * psize * (ty * ntx + tx)
+ j = x & (tw - 1)
+ i = y & (th - 1)
+ off = (
+ ((j & 1) << 0) | ((i & 1) << 1) |
+ ((j & 2) << 1) | ((i & 2) << 2) |
+ ((j & 4) << 2) | ((i & 4) << 3) |
+ ((j & 8) << 3) | ((i & 8) << 4) |
+ ((j & 16) << 4) | ((i & 16) << 5) |
+ ((j & 32) << 5) | ((i & 32) << 6))
+ r,g,b,a = data[toff + psize*off: toff + psize*(off+1)]
+ if grid:
+ if x % 64 == 0 or y % 64 == 0:
+ r,g,b,a = 255,255,255,255
+ elif x % 32 == 0 or y % 32 == 0:
+ r,g,b,a = 128,128,128,255
+ new_data.append(bytes([b, g, r, a]))
+ data = b"".join(new_data)
+ if dump:
+ open(dump, "wb").write(data)
+ iface.writemem(addr, data)
+
+try:
+ agx.start()
+
+ ctx_id = 3
+ buffer_mgr_slot = 2
+
+ #agx.initdata.regionA.add_to_mon(mon)
+ #agx.initdata.regionB.add_to_mon(mon)
+ #agx.initdata.regionC.add_to_mon(mon)
+
+ #agx.initdata.regionB.unk_170.add_to_mon(mon)
+ #agx.initdata.regionB.unk_178.add_to_mon(mon)
+ #agx.initdata.regionB.unk_180.add_to_mon(mon)
+ #agx.initdata.regionB.unk_190.add_to_mon(mon)
+ #agx.initdata.regionB.unk_198.add_to_mon(mon)
+ ##agx.initdata.regionB.fwlog_ring2.add_to_mon(mon)
+ #agx.initdata.regionB.hwdata_a.add_to_mon(mon)
+ #agx.initdata.regionB.hwdata_b.add_to_mon(mon)
+ mon.poll()
+
+ #agx.asc.work_for(0.3)
+
+ #p.write32(agx.initdata.regionC._paddr + 0x8900, 0xffffffff)
+ #p.write32(agx.initdata.regionC._paddr + 0x8904, 0xffffffff)
+ #mon.poll()
+
+ agx.kick_firmware()
+ #agx.asc.work_for(0.3)
+
+ #mon.poll()
+
+ ##### Initialize context and load data
+
+ ctx = GPUContext(agx)
+ ctx.bind(ctx_id)
+
+ #p.read32(0x204000000 + 0xd14000)
+ #p.write32(0x204000000 + 0xd14000, 0x70001)
+
+ #base = "gpudata/1tri/"
+ #base = "gpudata/mesa-flag/"
+ base = "gpudata/bunny/"
+ ctx.load_blob(0x1100000000, True, base + "mem_0_0.bin")
+ ctx.load_blob(0x1100008000, True, base + "mem_8000_0.bin")
+ ctx.load_blob(0x1100010000, True, base + "mem_10000_0.bin")
+ ctx.load_blob(0x1100058000, True, base + "mem_58000_0.bin")
+ ctx.load_blob(0x1100060000, True, base + "mem_60000_0.bin")
+ #ctx.load_blob(0x1100068000, True, base + "mem_68000_0.bin")
+ ctx.load_blob(0x1500000000, False, base + "mem_1500000000_0.bin")
+ ctx.load_blob(0x1500048000, False, base + "mem_1500048000_0.bin")
+ ctx.load_blob(0x15000d0000, False, base + "mem_15000d0000_0.bin")
+ ctx.load_blob(0x1500158000, False, base + "mem_1500158000_0.bin")
+ ctx.load_blob(0x15001e0000, False, base + "mem_15001e0000_0.bin")
+ ctx.load_blob(0x15001e8000, False, base + "mem_15001e8000_0.bin")
+ ctx.load_blob(0x15001f0000, False, base + "mem_15001f0000_0.bin")
+ ctx.load_blob(0x15001f8000, False, base + "mem_15001f8000_0.bin")
+ #ctx.load_blob(0x1500490000, False, base + "mem_1500490000_0.bin")
+ #ctx.load_blob(0x1500518000, False, base + "mem_1500518000_0.bin")
+ #color = ctx.buf_at(0x1500200000, False, 1310720, "Color", track=False)
+ #depth = ctx.buf_at(0x1500348000, False, 1310720, "Depth", track=False)
+ color = ctx.buf_at(0x1500200000, False, 2129920, "Color", track=False)
+ depth = ctx.buf_at(0x1500410000, False, 2129920, "Depth", track=False)
+ ctx.load_blob(0x1500620000, False, base + "mem_1500620000_0.bin", track=False)
+ ctx.load_blob(0x1500890000, False, base + "mem_1500890000_0.bin", track=False)
+
+ mon.poll()
+
+ p.memset32(color._paddr, 0xdeadbeef, color._size)
+ p.memset32(depth._paddr, 0xdeadbeef, depth._size)
+
+ stencil = ctx.buf_at(0x1510410000, False, 2129920, "Stencil", track=False)
+
+ width = 800
+ height = 600
+
+ width_a = align_up(width, 64)
+ height_a = align_up(height, 64)
+
+ depth_addr = depth._addr
+
+ ##### Initialize buffer manager
+
+ #buffer_mgr = GPUBufferManager(agx, ctx, 26)
+ buffer_mgr = GPUBufferManager(agx, ctx, 8)
+
+ ##### Initialize work queues
+
+ wq_3d = GPU3DWorkQueue(agx, ctx)
+ wq_ta = GPUTAWorkQueue(agx, ctx)
+
+ ##### TA stamps
+
+ #Message 1: DAG: Non Sequential Stamp Updates seen entryIdx 0x41 roots.dag 0x1 stampIdx 0x7 stampValue 0x4100 channel 0xffffffa000163f58 channelRingCommandIndex 0x1
+
+ prev_stamp_value = 0x4000
+ stamp_value = 0x4100
+
+ # start?
+ stamp_ta1 = agx.kshared.new(BarrierCounter, name="TA stamp 1")
+ stamp_ta1.value = prev_stamp_value
+ stamp_ta1.push()
+
+ # complete?
+ stamp_ta2 = agx.kobj.new(BarrierCounter, name="TA stamp 2")
+ stamp_ta2.value = prev_stamp_value
+ stamp_ta2.push()
+
+ ##### 3D stamps
+
+ # start?
+ stamp_3d1 = agx.kshared.new(BarrierCounter, name="3D stamp 1")
+ stamp_3d1.value = prev_stamp_value
+ stamp_3d1.push()
+
+ # complete?
+ stamp_3d2 = agx.kobj.new(BarrierCounter, name="3D stamp 2")
+ stamp_3d2.value = prev_stamp_value
+ stamp_3d2.push()
+
+ ##### Some kind of feedback/status buffer, GPU managed?
+
+ event_control = agx.kobj.new(EventControl)
+ event_control.event_count = agx.kobj.new(Int32ul, "Event Count")
+ event_control.base_stamp = 0x15 #0
+ event_control.unk_c = 0
+ event_control.unk_10 = 0x50
+ event_control.push()
+
+ ##### TVB allocations / Tiler config
+
+ tile_width = 32
+ tile_height = 32
+ tiles_x = ((width + tile_width - 1) // tile_width)
+ tiles_y = ((height + tile_height - 1) // tile_height)
+ tiles = tiles_x * tiles_y
+
+ tile_blocks_x = (tiles_x + 15) // 16
+ tile_blocks_y = (tiles_y + 15) // 16
+ tile_blocks = tile_blocks_x * tile_blocks_y
+
+ tiling_params = TilingParameters()
+ tiling_params.size1 = 0x14 * tile_blocks
+ tiling_params.unk_4 = 0x88
+ tiling_params.unk_8 = 0x202
+ tiling_params.x_max = width - 1
+ tiling_params.y_max = height - 1
+ tiling_params.tile_count = ((tiles_y-1) << 12) | (tiles_x-1)
+ tiling_params.x_blocks = (12 * tile_blocks_x) | (tile_blocks_x << 12) | (tile_blocks_x << 20)
+ tiling_params.y_blocks = (12 * tile_blocks_y) | (tile_blocks_y << 12) | (tile_blocks_y << 20)
+ tiling_params.size2 = 0x10 * tile_blocks
+ tiling_params.size3 = 0x20 * tile_blocks
+ tiling_params.unk_24 = 0x100
+ tiling_params.unk_28 = 0x8000
+
+ tvb_something_size = 0x800 * tile_blocks
+ tvb_something = ctx.uobj.new_buf(tvb_something_size, "TVB Something")
+
+ tvb_tilemap_size = 0x800 * tile_blocks
+ tvb_tilemap = ctx.uobj.new_buf(tvb_tilemap_size, "TVB Tilemap")
+
+ tvb_heapmeta_size = 0x4000
+ tvb_heapmeta = ctx.uobj.new_buf(tvb_heapmeta_size, "TVB Heap Meta")
+
+ ##### Buffer stuff?
+
+ # buffer related?
+ buf_desc = agx.kobj.new(BufferThing)
+ buf_desc.unk_0 = 0x0
+ buf_desc.unk_8 = 0x0
+ buf_desc.unk_10 = 0x0
+ buf_desc.unkptr_18 = ctx.uobj.buf(0x80, "BufferThing.unkptr_18")
+ buf_desc.unk_20 = 0x0
+ buf_desc.bm_misc_addr = buffer_mgr.misc_obj._addr
+ buf_desc.unk_2c = 0x0
+ buf_desc.unk_30 = 0x0
+ buf_desc.unk_38 = 0x0
+ buf_desc.push()
+
+ uuid_3d = 0x4000a14
+ uuid_ta = 0x4000a15
+ encoder_id = 0x30009fb
+
+ ##### 3D barrier command
+
+ ev_ta = 6
+ ev_3d = 7
+
+ barrier_cmd = agx.kobj.new(WorkCommandBarrier)
+ barrier_cmd.stamp = stamp_ta2
+ barrier_cmd.stamp_value1 = 0x4100
+ barrier_cmd.stamp_value2 = 0x4100
+ barrier_cmd.event = ev_ta
+ barrier_cmd.uuid = uuid_3d
+
+
+ #stamp.add_to_mon(mon)
+ #stamp2.add_to_mon(mon)
+
+ print(barrier_cmd)
+
+ wq_3d.submit(barrier_cmd.push())
+
+ ##### 3D execution
+
+ wc_3d = agx.kobj.new(WorkCommand3D)
+ wc_3d.context_id = ctx_id
+ wc_3d.unk_8 = 0
+ wc_3d.event_control = event_control
+ wc_3d.buffer_mgr = buffer_mgr.info
+ wc_3d.buf_thing = buf_desc
+ wc_3d.unk_emptybuf_addr = agx.kobj.buf(0x100, "unk_emptybuf")
+ wc_3d.tvb_tilemap = tvb_tilemap._addr
+ wc_3d.unk_40 = 0x88
+ wc_3d.unk_48 = 0x1
+ wc_3d.tile_blocks_y = tile_blocks_y * 4
+ wc_3d.tile_blocks_x = tile_blocks_x * 4
+ wc_3d.unk_50 = 0x0
+ wc_3d.unk_58 = 0x0
+ wc_3d.uuid1 = 0x3b315cae
+ wc_3d.uuid2 = 0x3b6c7b92
+ wc_3d.unk_68 = 0x0
+ wc_3d.tile_count = tiles
+
+ wc_3d.unk_buf = WorkCommand1_UnkBuf()
+ wc_3d.unk_word = BarrierCounter()
+ wc_3d.unk_buf2 = WorkCommand1_UnkBuf2()
+ wc_3d.unk_buf2.unk_0 = 0
+ wc_3d.unk_buf2.unk_8 = 0
+ wc_3d.unk_buf2.unk_10 = 1
+ wc_3d.ts1 = Timestamp()
+ wc_3d.ts2 = Timestamp()
+ wc_3d.ts3 = Timestamp()
+ wc_3d.unk_914 = 0
+ wc_3d.unk_918 = 0
+ wc_3d.unk_920 = 0
+ wc_3d.unk_924 = 1
+
+ # Structures embedded in WorkCommand3D
+ if True:
+ wc_3d.struct_1 = Start3DStruct1()
+ wc_3d.struct_1.store_pipeline_addr = 0x14004 # CHECKED
+ wc_3d.struct_1.unk_8 = 0x0
+ wc_3d.struct_1.unk_c = 0x0
+ wc_3d.struct_1.uuid1 = wc_3d.uuid1
+ wc_3d.struct_1.uuid2 = wc_3d.uuid2
+ wc_3d.struct_1.unk_18 = 0x0
+ wc_3d.struct_1.tile_blocks_y = tile_blocks_y * 4
+ wc_3d.struct_1.tile_blocks_x = tile_blocks_x * 4
+ wc_3d.struct_1.unk_24 = 0x0
+ wc_3d.struct_1.tile_counts = ((tiles_y-1) << 12) | (tiles_x-1)
+ wc_3d.struct_1.unk_2c = 0x8
+ wc_3d.struct_1.depth_clear_val1 = 1.0 # works
+ wc_3d.struct_1.stencil_clear_val1 = 0x0
+ wc_3d.struct_1.unk_38 = 0x0
+ wc_3d.struct_1.unk_3c = 0x1
+ wc_3d.struct_1.unk_40_padding = bytes(0xb0)
+ wc_3d.struct_1.depth_bias_array = Start3DArrayAddr(0x1500158000)
+ wc_3d.struct_1.scissor_array = Start3DArrayAddr(0x15000d0000)
+ wc_3d.struct_1.unk_110 = 0x0
+ wc_3d.struct_1.unk_118 = 0x0
+ wc_3d.struct_1.unk_120 = [0] * 37
+ wc_3d.struct_1.unk_reload_pipeline = Start3DStorePipelineBinding(0xffff8212, 0xfffffff4)
+ wc_3d.struct_1.unk_258 = 0
+ wc_3d.struct_1.unk_260 = 0
+ wc_3d.struct_1.unk_268 = 0
+ wc_3d.struct_1.unk_270 = 0
+ wc_3d.struct_1.reload_pipeline = Start3DClearPipelineBinding(0xffff8212, 0x13004) # CHECKED
+ wc_3d.struct_1.depth_flags = 0x00000
+ wc_3d.struct_1.unk_290 = 0x0
+ wc_3d.struct_1.depth_buffer_ptr1 = depth_addr
+ wc_3d.struct_1.unk_2a0 = 0x0
+ wc_3d.struct_1.unk_2a8 = 0x0
+ wc_3d.struct_1.depth_buffer_ptr2 = depth_addr
+ wc_3d.struct_1.depth_buffer_ptr3 = depth_addr
+ wc_3d.struct_1.unk_2c0 = 0x0
+ wc_3d.struct_1.stencil_buffer_ptr1 = stencil._addr
+ wc_3d.struct_1.unk_2d0 = 0x0
+ wc_3d.struct_1.unk_2d8 = 0x0
+ wc_3d.struct_1.stencil_buffer_ptr2 = stencil._addr
+ wc_3d.struct_1.stencil_buffer_ptr3 = stencil._addr
+ wc_3d.struct_1.unk_2f0 = [0x0, 0x0, 0x0]
+ wc_3d.struct_1.aux_fb_unk0 = 0x4
+ wc_3d.struct_1.unk_30c = 0x0
+ wc_3d.struct_1.aux_fb = AuxFBInfo(0xc000, 0, width, height)
+ wc_3d.struct_1.unk_320_padding = bytes(0x10)
+ wc_3d.struct_1.unk_partial_store_pipeline = Start3DStorePipelineBinding(0xffff8212, 0xfffffff4)
+ wc_3d.struct_1.partial_store_pipeline = Start3DStorePipelineBinding(0x12, 0x14004) # CHECKED
+ wc_3d.struct_1.depth_clear_val2 = 1.0
+ wc_3d.struct_1.stencil_clear_val2 = 0x0
+ wc_3d.struct_1.context_id = ctx_id
+ wc_3d.struct_1.unk_376 = 0x0
+ wc_3d.struct_1.unk_378 = 0x8
+ wc_3d.struct_1.unk_37c = 0x0
+ wc_3d.struct_1.unk_380 = 0x0
+ wc_3d.struct_1.unk_388 = 0x0
+ wc_3d.struct_1.depth_dimensions = 0x12b831f #0xef827f
+
+ if True:
+ wc_3d.struct_2 = Start3DStruct2()
+ wc_3d.struct_2.unk_0 = 0xa000
+ wc_3d.struct_2.clear_pipeline = Start3DClearPipelineBinding(0xffff8002, 0x12004)
+ wc_3d.struct_2.unk_18 = 0x88
+ wc_3d.struct_2.scissor_array = 0x15000d0000
+ wc_3d.struct_2.depth_bias_array = 0x1500158000
+ wc_3d.struct_2.aux_fb = wc_3d.struct_1.aux_fb
+ wc_3d.struct_2.depth_dimensions = wc_3d.struct_1.depth_dimensions
+ wc_3d.struct_2.unk_48 = 0x0
+ wc_3d.struct_2.depth_flags = wc_3d.struct_1.depth_flags
+ wc_3d.struct_2.depth_buffer_ptr1 = depth_addr
+ wc_3d.struct_2.depth_buffer_ptr2 = depth_addr
+ wc_3d.struct_2.stencil_buffer_ptr1 = stencil._addr
+ wc_3d.struct_2.stencil_buffer_ptr2 = stencil._addr
+ wc_3d.struct_2.unk_68 = [0] * 12
+ wc_3d.struct_2.tvb_tilemap = tvb_tilemap._addr
+ wc_3d.struct_2.tvb_heapmeta_addr = tvb_heapmeta._addr
+ wc_3d.struct_2.unk_e8 = 0x50000000 * tile_blocks
+ wc_3d.struct_2.tvb_heapmeta_addr2 = tvb_heapmeta._addr
+ wc_3d.struct_2.unk_f8 = 0x10280 # TODO: varies 0, 0x280, 0x10000, 0x10280
+ wc_3d.struct_2.aux_fb_ptr = 0x1500006000
+ wc_3d.struct_2.unk_108 = [0x0, 0x0, 0x0, 0x0, 0x0, 0x0]
+ wc_3d.struct_2.pipeline_base = 0x1100000000
+ wc_3d.struct_2.unk_140 = 0x8c60
+ wc_3d.struct_2.unk_148 = 0x0
+ wc_3d.struct_2.unk_150 = 0x0
+ wc_3d.struct_2.unk_158 = 0x1c
+ wc_3d.struct_2.unk_160_padding = bytes(0x1e8)
+
+ if True:
+ wc_3d.struct_6 = Start3DStruct6()
+ wc_3d.struct_6.unk_0 = 0x0
+ wc_3d.struct_6.unk_8 = 0x0
+ wc_3d.struct_6.unk_10 = 0x0
+ wc_3d.struct_6.encoder_id = encoder_id
+ wc_3d.struct_6.unk_1c = 0xffffffff
+ wc_3d.struct_6.unknown_buffer = 0x150000e000
+ wc_3d.struct_6.unk_28 = 0x0
+ wc_3d.struct_6.unk_30 = 0x1
+ wc_3d.struct_6.unk_34 = 0x1
+
+ if True:
+ wc_3d.struct_7 = Start3DStruct7()
+ wc_3d.struct_7.unk_0 = 0x0
+ wc_3d.struct_7.stamp1 = stamp_3d1
+ wc_3d.struct_7.stamp2 = stamp_3d2
+ wc_3d.struct_7.stamp_value = stamp_value
+ wc_3d.struct_7.ev_3d = ev_3d
+ wc_3d.struct_7.unk_20 = 0x0
+ wc_3d.struct_7.unk_24 = 0x0 # check
+ wc_3d.struct_7.uuid = uuid_3d
+ wc_3d.struct_7.prev_stamp_value = 0x0
+ wc_3d.struct_7.unk_30 = 0x0
+
+ wc_3d.set_addr() # Update inner structure addresses
+ print("WC3D", hex(wc_3d._addr))
+ print(" s1", hex(wc_3d.struct_1._addr))
+ print(" s2", hex(wc_3d.struct_2._addr))
+ print(" s6", hex(wc_3d.struct_6._addr))
+ print(" s7", hex(wc_3d.struct_7._addr))
+
+ ms = GPUMicroSequence(agx)
+
+ start_3d = Start3DCmd()
+ start_3d.struct1 = wc_3d.struct_1
+ start_3d.struct2 = wc_3d.struct_2
+ start_3d.buf_thing = buf_desc
+ start_3d.unkptr_1c = agx.initdata.regionB.unkptr_178 + 8
+ start_3d.unkptr_24 = wc_3d.unk_word._addr
+ start_3d.struct6 = wc_3d.struct_6
+ start_3d.struct7 = wc_3d.struct_7
+ start_3d.cmdqueue_ptr = wq_3d.info._addr
+ start_3d.workitem_ptr = wc_3d._addr
+ start_3d.context_id = ctx_id
+ start_3d.unk_50 = 0x1
+ start_3d.unk_54 = 0x0
+ start_3d.unk_58 = 0x2
+ start_3d.unk_5c = 0x0
+ start_3d.prev_stamp_value = 0x0
+ start_3d.unk_68 = 0x0
+ start_3d.unk_buf_ptr = wc_3d.unk_buf._addr
+ start_3d.unk_buf2_ptr = wc_3d.unk_buf2._addr
+ start_3d.unk_7c = 0x0
+ start_3d.unk_80 = 0x0
+ start_3d.unk_84 = 0x0
+ start_3d.uuid = uuid_3d
+ start_3d.attachments = [
+ Attachment(color._addr, 0x2800, 0x10017),
+ Attachment(depth._addr, 0x4100, 0x10017),
+ ] + [Attachment(0, 0, 0)] * 14
+ start_3d.num_attachments = 2
+ start_3d.unk_190 = 0x0
+
+ ms.append(start_3d)
+
+ ts1 = TimestampCmd()
+ ts1.unk_1 = 0x0
+ ts1.unk_2 = 0x0
+ ts1.unk_3 = 0x80
+ ts1.ts0_addr = wc_3d.ts1._addr
+ ts1.ts1_addr = wc_3d.ts2._addr
+ ts1.ts2_addr = wc_3d.ts2._addr
+ ts1.cmdqueue_ptr = wq_3d.info._addr
+ ts1.unk_24 = 0x0
+ ts1.uuid = uuid_3d
+ ts1.unk_30_padding = 0x0
+ ms.append(ts1)
+
+ ms.append(WaitForInterruptCmd(0, 1, 0))
+
+ ts2 = TimestampCmd()
+ ts2.unk_1 = 0x0
+ ts2.unk_2 = 0x0
+ ts2.unk_3 = 0x0
+ ts2.ts0_addr = wc_3d.ts1._addr
+ ts2.ts1_addr = wc_3d.ts2._addr
+ ts2.ts2_addr = wc_3d.ts3._addr
+ ts2.cmdqueue_ptr = wq_3d.info._addr
+ ts2.unk_24 = 0x0
+ ts2.uuid = uuid_3d
+ ts2.unk_30_padding = 0x0
+ ms.append(ts2)
+
+ finish_3d = Finalize3DCmd()
+ finish_3d.uuid = uuid_3d
+ finish_3d.unk_8 = 0
+ finish_3d.stamp = stamp_3d2
+ finish_3d.stamp_value = stamp_value
+ finish_3d.unk_18 = 0
+ finish_3d.buf_thing = buf_desc
+ finish_3d.buffer_mgr = buffer_mgr.info
+ finish_3d.unk_2c = 1
+ finish_3d.unkptr_34 = agx.initdata.regionB.unkptr_178 + 8
+ finish_3d.struct7 = wc_3d.struct_7
+ finish_3d.unkptr_44 = wc_3d.unk_word._addr
+ finish_3d.cmdqueue_ptr = wq_3d.info._addr
+ finish_3d.workitem_ptr = wc_3d._addr
+ finish_3d.unk_5c = ctx_id
+ finish_3d.unk_buf_ptr = wc_3d.unk_buf._addr
+ finish_3d.unk_6c = 0
+ finish_3d.unk_74 = 0
+ finish_3d.unk_7c = 0
+ finish_3d.unk_84 = 0
+ finish_3d.unk_8c = 0
+ finish_3d.startcmd_offset = -0x200
+ finish_3d.unk_98 = 1
+ ms.append(finish_3d)
+ ms.finalize()
+
+ wc_3d.microsequence_ptr = ms.obj._addr
+ wc_3d.microsequence_size = ms.size
+
+ print(wc_3d)
+
+ wc_3d.push()
+ ms.dump()
+ print(wc_3d)
+ wq_3d.submit(wc_3d)
+
+ ##### TA init
+
+ #print(ctx_info)
+
+ wc_initbm = agx.kobj.new(WorkCommandInitBM)
+ wc_initbm.context_id = ctx_id
+ wc_initbm.unk_8 = buffer_mgr_slot
+ wc_initbm.unk_c = 0
+ wc_initbm.unk_10 = buffer_mgr.info.block_count
+ wc_initbm.buffer_mgr = buffer_mgr.info
+ wc_initbm.stamp_value = stamp_value
+ wc_initbm.push()
+
+ print(wc_initbm)
+ wq_ta.submit(wc_initbm)
+
+ ##### TA execution
+
+ wc_ta = agx.kobj.new(WorkCommandTA)
+ wc_ta.context_id = ctx_id
+ wc_ta.unk_8 = 0
+ wc_ta.event_control = event_control
+ wc_ta.unk_14 = buffer_mgr_slot
+ wc_ta.buffer_mgr = buffer_mgr.info
+ wc_ta.buf_thing = buf_desc
+ wc_ta.unk_emptybuf_addr = wc_3d.unk_emptybuf_addr
+ wc_ta.unk_34 = 0x0
+
+ wc_ta.unk_154 = bytes(0x268)
+ wc_ta.unk_3e8 = bytes(0x74)
+ wc_ta.unk_594 = WorkCommand0_UnkBuf()
+
+ wc_ta.ts1 = Timestamp()
+ wc_ta.ts2 = Timestamp()
+ wc_ta.ts3 = Timestamp()
+ wc_ta.unk_5c4 = 0
+ wc_ta.unk_5c8 = 0
+ wc_ta.unk_5cc = 0
+ wc_ta.unk_5d0 = 0
+ wc_ta.unk_5d4 = 0x27 #1
+
+ # Structures embedded in WorkCommandTA
+ if True:
+
+ wc_ta.tiling_params = tiling_params
+ #wc_ta.tiling_params.unk_0 = 0x28
+ #wc_ta.tiling_params.unk_4 = 0x88
+ #wc_ta.tiling_params.unk_8 = 0x202
+ #wc_ta.tiling_params.x_max = 639
+ #wc_ta.tiling_params.y_max = 479
+ #wc_ta.tiling_params.unk_10 = 0xe013
+ #wc_ta.tiling_params.unk_14 = 0x20_20_18
+ #wc_ta.tiling_params.unk_18 = 0x10_10_0c
+ #wc_ta.tiling_params.unk_1c = 0x20
+ #wc_ta.tiling_params.unk_20 = 0x40
+ #wc_ta.tiling_params.unk_24 = 0x100
+ #wc_ta.tiling_params.unk_28 = 0x8000
+
+ if True:
+ wc_ta.struct_2 = StartTACmdStruct2()
+ wc_ta.struct_2.unk_0 = 0x200
+ wc_ta.struct_2.unk_8 = 0x1e3ce508 # fixed
+ wc_ta.struct_2.unk_c = 0x1e3ce508 # fixed
+ wc_ta.struct_2.tvb_tilemap = tvb_tilemap._addr
+ wc_ta.struct_2.unkptr_18 = 0x0
+ wc_ta.struct_2.unkptr_20 = tvb_something._addr
+ wc_ta.struct_2.tvb_heapmeta_addr = tvb_heapmeta._addr | 0x8000000000000000
+ wc_ta.struct_2.iogpu_unk_54 = 0x6b0003 # fixed
+ wc_ta.struct_2.iogpu_unk_55 = 0x3a0012 # fixed
+ wc_ta.struct_2.iogpu_unk_56 = 0x1 # fixed
+ wc_ta.struct_2.unk_40 = 0x0 # fixed
+ wc_ta.struct_2.unk_48 = 0xa000 # fixed
+ wc_ta.struct_2.unk_50 = 0x88 # fixed
+ wc_ta.struct_2.tvb_heapmeta_addr2 = tvb_heapmeta._addr
+ wc_ta.struct_2.unk_60 = 0x0 # fixed
+ wc_ta.struct_2.unk_68 = 0x0 # fixed
+ wc_ta.struct_2.iogpu_deflake_1 = 0x15000052a0
+ wc_ta.struct_2.iogpu_deflake_2 = 0x1500005020
+ wc_ta.struct_2.unk_80 = 0x1 # fixed
+ wc_ta.struct_2.iogpu_deflake_3 = 0x1500005000
+ wc_ta.struct_2.encoder_addr = 0x1500048000
+ wc_ta.struct_2.unk_98 = [0x0, 0x0] # fixed
+ wc_ta.struct_2.unk_a8 = 0xa041 # fixed
+ wc_ta.struct_2.unk_b0 = [0x0, 0x0, 0x0, 0x0, 0x0, 0x0] # fixed
+ wc_ta.struct_2.pipeline_base = 0x1100000000
+ wc_ta.struct_2.unk_e8 = 0x0 # fixed
+ wc_ta.struct_2.unk_f0 = 0x1c # fixed
+ wc_ta.struct_2.unk_f8 = 0x8c60 # fixed
+ wc_ta.struct_2.unk_100 = [0x0, 0x0, 0x0] # fixed
+ wc_ta.struct_2.unk_118 = 0x1c # fixed
+
+ if True:
+ wc_ta.struct_3 = StartTACmdStruct3()
+ wc_ta.struct_3.unk_480 = [0x0, 0x0, 0x0, 0x0, 0x0, 0x0] # fixed
+ wc_ta.struct_3.unk_498 = 0x0 # fixed
+ wc_ta.struct_3.unk_4a0 = 0x0 # fixed
+ wc_ta.struct_3.iogpu_deflake_1 = 0x15000052a0
+ wc_ta.struct_3.unk_4ac = 0x0 # fixed
+ wc_ta.struct_3.unk_4b0 = 0x0 # fixed
+ wc_ta.struct_3.unk_4b8 = 0x0 # fixed
+ wc_ta.struct_3.unk_4bc = 0x0 # fixed
+ wc_ta.struct_3.unk_4c4_padding = bytes(0x48)
+ wc_ta.struct_3.unk_50c = 0x0 # fixed
+ wc_ta.struct_3.unk_510 = 0x0 # fixed
+ wc_ta.struct_3.unk_518 = 0x0 # fixed
+ wc_ta.struct_3.unk_520 = 0x0 # fixed
+ wc_ta.struct_3.unk_528 = 0x0 # fixed
+ wc_ta.struct_3.unk_52c = 0x0 # fixed
+ wc_ta.struct_3.unk_530 = 0x0 # fixed
+ wc_ta.struct_3.encoder_id = encoder_id
+ wc_ta.struct_3.unk_538 = 0x0 # fixed
+ wc_ta.struct_3.unk_53c = 0xffffffff
+ wc_ta.struct_3.unknown_buffer = wc_3d.struct_6.unknown_buffer
+ wc_ta.struct_3.unk_548 = 0x0 # fixed
+ wc_ta.struct_3.unk_550 = [
+ 0x0, 0x0, # fixed
+ 0x0, # 1 for boot stuff?
+ 0x0, 0x0, 0x0] # fixed
+ wc_ta.struct_3.stamp1 = stamp_ta1
+ wc_ta.struct_3.stamp2 = stamp_ta2
+ wc_ta.struct_3.stamp_value = stamp_value
+ wc_ta.struct_3.ev_ta = ev_ta
+ wc_ta.struct_3.unk_580 = 0x0 # fixed
+ wc_ta.struct_3.unk_584 = 0x0 # 1 for boot stuff?
+ wc_ta.struct_3.uuid2 = uuid_ta
+ #wc_ta.struct_3.unk_58c = [0x0, 0x0]
+ wc_ta.struct_3.unk_58c = [0x1, 0x0]
+
+ wc_ta.set_addr() # Update inner structure addresses
+ #print("wc_ta", wc_ta)
+
+ ms = GPUMicroSequence(agx)
+
+ start_ta = StartTACmd()
+ start_ta.tiling_params = wc_ta.tiling_params
+ start_ta.struct2 = wc_ta.struct_2
+ start_ta.buffer_mgr = buffer_mgr.info
+ start_ta.buf_thing = buf_desc
+ start_ta.unkptr_24 = agx.initdata.regionB.unkptr_170 + 4
+ start_ta.cmdqueue_ptr = wq_ta.info._addr
+ start_ta.context_id = ctx_id
+ start_ta.unk_38 = 1
+ start_ta.unk_3c = 1 #0
+ start_ta.unk_40 = buffer_mgr_slot
+ start_ta.unk_48 = 1 #0
+ start_ta.unk_50 = 0
+ start_ta.struct3 = wc_ta.struct_3
+
+ start_ta.unkptr_5c = wc_ta.unk_594._addr
+ start_ta.unk_64 = 0x0 # fixed
+ start_ta.uuid = uuid_ta
+ start_ta.unk_70 = 0x0 # fixed
+ start_ta.unk_74 = [ # fixed
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ ]
+ start_ta.unk_15c = 0x0 # fixed
+ start_ta.unk_160 = 0x0 # fixed
+ start_ta.unk_168 = 0x0 # fixed
+ start_ta.unk_16c = 0x0 # fixed
+ start_ta.unk_170 = 0x0 # fixed
+ start_ta.unk_178 = 0x0 # fixed
+ ms.append(start_ta)
+
+ ts1 = TimestampCmd()
+ ts1.unk_1 = 0x0
+ ts1.unk_2 = 0x0
+ ts1.unk_3 = 0x80
+ ts1.ts0_addr = wc_ta.ts1._addr
+ ts1.ts1_addr = wc_ta.ts2._addr
+ ts1.ts2_addr = wc_ta.ts2._addr
+ ts1.cmdqueue_ptr = wq_ta.info._addr
+ ts1.unk_24 = 0x0
+ ts1.uuid = uuid_ta
+ ts1.unk_30_padding = 0x0
+ ms.append(ts1)
+
+ ms.append(WaitForInterruptCmd(1, 0, 0))
+
+ ts2 = TimestampCmd()
+ ts2.unk_1 = 0x0
+ ts2.unk_2 = 0x0
+ ts2.unk_3 = 0x0
+ ts2.ts0_addr = wc_ta.ts1._addr
+ ts2.ts1_addr = wc_ta.ts2._addr
+ ts2.ts2_addr = wc_ta.ts3._addr
+ ts2.cmdqueue_ptr = wq_ta.info._addr
+ ts2.unk_24 = 0x0
+ ts2.uuid = uuid_ta
+ ts2.unk_30_padding = 0x0
+ ms.append(ts2)
+
+ finish_ta = FinalizeTACmd()
+ finish_ta.buf_thing = buf_desc
+ finish_ta.buffer_mgr = buffer_mgr.info
+ finish_ta.unkptr_14 = agx.initdata.regionB.unkptr_170 + 4
+ finish_ta.cmdqueue_ptr = wq_ta.info._addr
+ finish_ta.context_id = ctx_id
+ finish_ta.unk_28 = 0x0 # fixed
+ finish_ta.struct3 = wc_ta.struct_3
+ finish_ta.unk_34 = 0x0 # fixed
+ finish_ta.uuid = uuid_ta
+ finish_ta.stamp = stamp_ta2
+ finish_ta.stamp_value = stamp_value
+ finish_ta.unk_48 = 0x0 # fixed
+ finish_ta.unk_50 = 0x0 # fixed
+ finish_ta.unk_54 = 0x0 # fixed
+ finish_ta.unk_58 = 0x0 # fixed
+ finish_ta.unk_60 = 0x0 # fixed
+ finish_ta.unk_64 = 0x0 # fixed
+ finish_ta.unk_68 = 0x0 # fixed
+ finish_ta.startcmd_offset = -0x1e8 # fixed
+ finish_ta.unk_70 = 0x0 # fixed
+ ms.append(finish_ta)
+
+ ms.finalize()
+
+ wc_ta.unkptr_45c = tvb_something._addr
+ wc_ta.tvb_size = tvb_something_size
+ wc_ta.microsequence_ptr = ms.obj._addr
+ wc_ta.microsequence_size = ms.size
+ wc_ta.ev_3d = ev_3d
+ wc_ta.stamp_value = stamp_value
+
+ wc_ta.push()
+ ms.dump()
+
+ mon.poll()
+
+ print(wc_ta)
+ wq_ta.submit(wc_ta)
+
+ ##### Run queues
+ agx.ch.queue[2].q_3D.run(wq_3d, ev_3d)
+ agx.ch.queue[2].q_TA.run(wq_ta, ev_ta)
+
+ ##### Wait for work
+ agx.asc.work_for(0.3)
+ print("3D:")
+ print(wq_3d.info.pull())
+ print("TA:")
+ print(wq_ta.info.pull())
+ print("Barriers:")
+ print(stamp_ta1.pull())
+ print(stamp_ta2.pull())
+ print(stamp_3d1.pull())
+ print(stamp_3d2.pull())
+
+ event_control.pull()
+ print(event_control)
+
+ print("==")
+ mon.poll()
+ print("==")
+ #agx.kick_firmware()
+ agx.asc.work_for(0.3)
+ p.read32(0x204000000 + 0xd14000)
+ # [cpu0] [0xfffffe00124bf9a8] MMIO: W.4 0x204d14000 (sgx, offset 0xd14000) = 0x70001
+ p.write32(0x204000000 + 0xd14000, 0x70001)
+
+ #agx.uat.dump(ctx_id)
+
+ fault_code = p.read64(0x204017030)
+ fault_addr = fault_code >> 24
+ if fault_addr & 0x8000000000:
+ fault_addr |= 0xffffff8000000000
+ print(f"FAULT CODE: {fault_code:#x}")
+ base, obj = agx.find_object(fault_addr)
+ if obj is not None:
+ print(f"Faulted at : {fault_addr:#x}: {obj!s} + {fault_addr - base:#x}")
+ #agx.kick_firmware()
+ mon.poll()
+
+ #print(buffer_mgr.info.pull())
+ #print(buffer_mgr.counter_obj.pull())
+ #print(buffer_mgr.misc_obj.pull())
+ #print(buffer_mgr.block_ctl_obj.pull())
+
+ width = 800
+ height = 600
+
+ unswizzle(color._paddr, width, height, 4, "fb.bin", grid=True)
+ unswizzle(depth._paddr, width, height, 4, "depth.bin", grid=True)
+
+ p.fb_blit(0, 0, width, height, color._paddr, width)
+
+ print("TVB something:")
+ chexdump(iface.readmem(tvb_something._paddr, tvb_something._size), stride=16, abbreviate=False)
+
+ print("TVB list:")
+ chexdump(iface.readmem(tvb_tilemap._paddr, tvb_tilemap._size), stride=5, abbreviate=False)
+
+ print("Tile params:")
+ print(f"X: {tiles_x} ({tile_blocks_x})")
+ print(f"Y: {tiles_y} ({tile_blocks_y})")
+ print(f"Total: {tiles} ({tile_blocks})")
+
+ agx.stop()
+except:
+ #agx.uat.dump(ctx_id)
+ p.reboot()
+ raise
+ #agx.stop()
+
+#time.sleep(10)
+#p.reboot()
diff --git a/tools/proxyclient/experiments/agx_boot.py b/tools/proxyclient/experiments/agx_boot.py
new file mode 100755
index 0000000..b5d668c
--- /dev/null
+++ b/tools/proxyclient/experiments/agx_boot.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+
+import sys, pathlib
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+from m1n1.setup import *
+from m1n1.constructutils import Ver
+from m1n1.utils import *
+
+Ver.set_version(u)
+
+from m1n1.shell import run_shell
+
+from m1n1.fw.agx import Agx
+
+p.pmgr_adt_clocks_enable("/arm-io/gfx-asc")
+p.pmgr_adt_clocks_enable("/arm-io/sgx")
+
+agx = Agx(u)
+agx.verbose = 10
+
+#agx.uat.dump(0)
+
+agx.build_initdata()
+
+run_shell(globals(), msg="Have fun!")
diff --git a/tools/proxyclient/experiments/agx_cancel.py b/tools/proxyclient/experiments/agx_cancel.py
new file mode 100644
index 0000000..af0881d
--- /dev/null
+++ b/tools/proxyclient/experiments/agx_cancel.py
@@ -0,0 +1,246 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+
+import sys, pathlib, time
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+import atexit, sys
+
+from m1n1.setup import *
+from m1n1.constructutils import Ver
+from m1n1.utils import *
+
+Ver.set_version(u)
+
+from m1n1.agx import AGX
+from m1n1.agx.render import *
+
+from m1n1 import asm
+
+from m1n1.gpiola import GPIOLogicAnalyzer
+
+analyzer_cpu = 1
+
+p.pmgr_adt_clocks_enable("/arm-io/gfx-asc")
+p.pmgr_adt_clocks_enable("/arm-io/sgx")
+p.smp_start_secondaries()
+p.mmu_init_secondary(analyzer_cpu)
+iface.dev.timeout = 42
+
+agx = AGX(u)
+
+mon = RegMonitor(u, ascii=True, bufsize=0x8000000)
+agx.mon = mon
+
+sgx = agx.sgx_dev
+
+atexit.register(p.reboot)
+agx.start()
+
+mon.poll()
+agx.poll_objects()
+
+ctx = GPUContext(agx)
+ctx.bind(63)
+
+f = GPUFrame(ctx, sys.argv[1], track=False)
+
+r = GPURenderer(ctx, 128, bm_slot=0x10, queue=1)
+
+dep_stamp = agx.kobj.new(StampCounter, name="Dep stamp")
+dep_stamp.value = 0x100
+dep_stamp.push()
+
+#r.submit(f.cmdbuf, (dep_stamp._addr, 0x200, 0x10))
+r.submit(f.cmdbuf)
+r.submit(f.cmdbuf)
+
+def t(addr):
+ paddr = agx.uat.iotranslate(0, addr, 4)[0][0]
+ if paddr is None:
+ raise Exception(f"Failed to iotranslate {addr:#x}")
+ return paddr
+
+regs = {
+ "ta_cmds": t(agx.initdata.regionB.stats_ta.addrof("total_cmds")),
+ "ta_ts": t(agx.initdata.regionB.stats_ta.stats.addrof("unk_timestamp")),
+}
+
+pend_base = agx.initdata.regionC.addrof("pending_stamps")
+for i in range(5):
+ regs[f"st{i}_info"] = t(pend_base + i*8)
+ regs[f"st{i}_val"] = t(pend_base + i*8 + 4)
+
+for i in range(4):
+ regs[f"ta{i}_cq"] = t(agx.initdata.regionB.stats_ta.stats.queues[i].addrof("cur_cmdqueue"))
+
+regs.update({
+ #"pwr_status": t(agx.initdata.regionB.hwdata_a.addrof("pwr_status")),
+ #"pstate": t(agx.initdata.regionB.hwdata_a.addrof("cur_pstate")),
+ #"temp_c": t(agx.initdata.regionB.hwdata_a.addrof("temp_c")),
+ #"pwr_mw": t(agx.initdata.regionB.hwdata_a.addrof("avg_power_mw")),
+ #"pwr_ts": t(agx.initdata.regionB.hwdata_a.addrof("update_ts")),
+
+ #"unk_10": t(agx.initdata.regionB.hwdata_a.addrof("unk_10")),
+ #"unk_14": t(agx.initdata.regionB.hwdata_a.addrof("unk_14")),
+ #"actual_pstate": t(agx.initdata.regionB.hwdata_a.addrof("actual_pstate")),
+ #"tgt_pstate": t(agx.initdata.regionB.hwdata_a.addrof("tgt_pstate")),
+ #"unk_40": t(agx.initdata.regionB.hwdata_a.addrof("unk_40")),
+ #"unk_44": t(agx.initdata.regionB.hwdata_a.addrof("unk_44")),
+ #"unk_48": t(agx.initdata.regionB.hwdata_a.addrof("unk_48")),
+ #"freq_mhz": t(agx.initdata.regionB.hwdata_a.addrof("freq_mhz")),
+
+ #"unk_748.0": t(agx.initdata.regionB.hwdata_a.addrof("unk_748")),
+ #"unk_748.1": t(agx.initdata.regionB.hwdata_a.addrof("unk_748")+4),
+ #"unk_748.2": t(agx.initdata.regionB.hwdata_a.addrof("unk_748")+8),
+ #"unk_748.3": t(agx.initdata.regionB.hwdata_a.addrof("unk_748")+12),
+ #"use_percent": t(agx.initdata.regionB.hwdata_a.addrof("use_percent")),
+ #"unk_83c": t(agx.initdata.regionB.hwdata_a.addrof("unk_83c")),
+ #"freq_with_off": t(agx.initdata.regionB.hwdata_a.addrof("freq_with_off")),
+ #"unk_ba0": t(agx.initdata.regionB.hwdata_a.addrof("unk_ba0")),
+ #"unk_bb0": t(agx.initdata.regionB.hwdata_a.addrof("unk_bb0")),
+ #"unk_c44": t(agx.initdata.regionB.hwdata_a.addrof("unk_c44")),
+ #"unk_c58": t(agx.initdata.regionB.hwdata_a.addrof("unk_c58")),
+
+ #"unk_3ca0": t(agx.initdata.regionB.hwdata_a.addrof("unk_3ca0")),
+ #"unk_3ca8": t(agx.initdata.regionB.hwdata_a.addrof("unk_3ca8")),
+ #"unk_3cb0": t(agx.initdata.regionB.hwdata_a.addrof("unk_3cb0")),
+ #"ts_last_idle": t(agx.initdata.regionB.hwdata_a.addrof("ts_last_idle")),
+ #"ts_last_poweron": t(agx.initdata.regionB.hwdata_a.addrof("ts_last_poweron")),
+ #"ts_last_poweroff": t(agx.initdata.regionB.hwdata_a.addrof("ts_last_poweroff")),
+ #"unk_3cd0": t(agx.initdata.regionB.hwdata_a.addrof("unk_3cd0")),
+
+ "halt_count": t(agx.initdata.fw_status.addrof("halt_count")),
+ "halted": t(agx.initdata.fw_status.addrof("halted")),
+ "resume": t(agx.initdata.fw_status.addrof("resume")),
+ "unk_40": t(agx.initdata.fw_status.addrof("unk_40")),
+ "unk_ctr": t(agx.initdata.fw_status.addrof("unk_ctr")),
+ "unk_60": t(agx.initdata.fw_status.addrof("unk_60")),
+ "unk_70": t(agx.initdata.fw_status.addrof("unk_70")),
+ "c_118c0": t(agx.initdata.regionC._addr + 0x118c0),
+ "c_118c4": t(agx.initdata.regionC._addr + 0x118c4),
+ "c_118c8": t(agx.initdata.regionC._addr + 0x118c8),
+ "c_118cc": t(agx.initdata.regionC._addr + 0x118cc),
+ "c_118d0": t(agx.initdata.regionC._addr + 0x118d0),
+ "c_118d4": t(agx.initdata.regionC._addr + 0x118d4),
+ "c_118d8": t(agx.initdata.regionC._addr + 0x118d8),
+ "c_118dc": t(agx.initdata.regionC._addr + 0x118dc),
+ "3d_cmds": t(agx.initdata.regionB.stats_3d.addrof("total_cmds")),
+ #"3d_tvb_oflws_1": t(agx.initdata.regionB.stats_3d.stats.addrof("tvb_overflows_1")),
+ #"3d_tvb_oflws_2": t(agx.initdata.regionB.stats_3d.stats.addrof("tvb_overflows_2")),
+ "3d_cur_stamp_id": t(agx.initdata.regionB.stats_3d.stats.addrof("cur_stamp_id")),
+ "3d_ts": t(agx.initdata.regionB.stats_3d.stats.addrof("unk_timestamp")),
+ #"3d_cur_stamp_id": t(agx.initdata.regionB.stats_3d.stats.addrof("cur_stamp_id")),
+})
+
+for i in range(4):
+ regs[f"3d{i}_cq"] = t(agx.initdata.regionB.stats_3d.stats.queues[i].addrof("cur_cmdqueue"))
+
+
+i = 0
+regs.update({
+ f"r{i}_3d_done": t(r.wq_3d.info.pointers.addrof("gpu_doneptr")),
+ #f"r{i}_3d_rptr": t(r.wq_3d.info.pointers.addrof("gpu_rptr")),
+ f"r{i}_3d_busy": t(r.wq_3d.info.addrof("busy")),
+ #f"r{i}_3d_blk": t(r.wq_3d.info.addrof("blocked_on_barrier")),
+ #f"r{i}_3d_2c": t(r.wq_3d.info.addrof("unk_2c")),
+ #f"r{i}_3d_54": t(r.wq_3d.info.addrof("unk_54")),
+
+ f"r{i}_ta_done": t(r.wq_ta.info.pointers.addrof("gpu_doneptr")),
+ #f"r{i}_ta_rptr": t(r.wq_ta.info.pointers.addrof("gpu_rptr")),
+ f"r{i}_ta_busy": t(r.wq_ta.info.addrof("busy")),
+ #f"r{i}_ta_blk": t(r.wq_ta.info.addrof("blocked_on_barrier")),
+ #f"r{i}_ta_2c": t(r.wq_ta.info.addrof("unk_2c")),
+ #f"r{i}_ta_54": t(r.wq_ta.info.addrof("unk_54")),
+ f"r{i}_ta_stamp1": t(r.stamp_ta1._addr),
+ f"r{i}_ta_stamp2":t(r.stamp_ta2._addr),
+ f"r{i}_3d_stamp1": t(r.stamp_3d1._addr),
+ f"r{i}_3d_stamp2":t(r.stamp_3d2._addr),
+
+ f"r{i}_ev_cnt":t(r.event_control.event_count._addr),
+ f"r{i}_ev_cur":t(r.event_control.addrof("cur_count")),
+ f"r{i}_ev_10":t(r.event_control.addrof("unk_10")),
+})
+
+div=4
+ticks = 24000000 // div * 25
+
+la = GPIOLogicAnalyzer(u, regs=regs, cpu=analyzer_cpu, div=div)
+
+print("Queues:")
+print(f" TA: {r.wq_ta.info._addr:#x} (stamp {r.work[0].ev_ta.id})")
+#print(r.wq_ta.info)
+print(f" 3D: {r.wq_3d.info._addr:#x} (stamp {r.work[0].ev_3d.id})")
+#print(r.wq_3d.info)
+
+print("==========================================")
+print("## Run")
+print("==========================================")
+
+la.start(ticks, bufsize=0x8000000)
+
+t = time.time()
+
+buf = agx.kobj.new_buf(0x1000, "foo")
+buf.val = b"A" * 0x1000
+buf.push()
+agx.uat.flush_dirty()
+
+try:
+ r.run()
+
+ #for a in range(8):
+ #for b in range(8):
+ #agx.ch.devctrl.dc_1e(a, b)
+
+ #agx.uat.flush_dirty()
+ #agx.ch.devctrl.write32(dep_stamp._addr, 0x200)
+
+ #data = struct.pack("<QQQQQI", 0xaaaa, buf._addr, 0xbbbb, 0, 0, 0)
+
+
+ #agx.poll_objects()
+ #mon.poll()
+ #agx.ch.devctrl.send_foo(9, data)
+ #agx.ch.devctrl.dc_09(0xaaaa, buf._addr, 0xbbbb)
+
+ #for i in range(0x28, 0xff):
+ #print(hex(i))
+ #data = struct.pack("<QQQQQI", dep_stamp._addr, 0x10_00000200, buf._addr, 0x12_00000010, 0x13_00000010, 0x10)
+ #agx.ch.devctrl.send_foo(i, data)
+ #agx.asc.work()
+ #time.sleep(0.1)
+ #agx.poll_objects()
+ #mon.poll()
+
+ #time.sleep(0.1)
+ #agx.asc.work()
+
+ #chexdump(buf.pull().val)
+
+ agx.kick_firmware()
+
+ while not r.ev_3d.fired:
+ agx.asc.work()
+ agx.poll_channels()
+ print("==========================================")
+ #agx.poll_objects()
+ #mon.poll()
+ agx.kick_firmware()
+ if time.time() > t + 2:
+ raise Exception("Timeout")
+ r.wait()
+
+finally:
+
+ dep_stamp.pull()
+ print(f"Stamp value: {dep_stamp.value:#x}")
+
+ #agx.poll_objects()
+ #mon.poll()
+
+ la.complete()
+ la.show()
+
+time.sleep(2)
+
diff --git a/tools/proxyclient/experiments/agx_deps.py b/tools/proxyclient/experiments/agx_deps.py
new file mode 100644
index 0000000..9a7c196
--- /dev/null
+++ b/tools/proxyclient/experiments/agx_deps.py
@@ -0,0 +1,242 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+
+import sys, pathlib, time
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+import atexit, sys
+
+from m1n1.setup import *
+from m1n1.constructutils import Ver
+from m1n1.utils import *
+
+Ver.set_version(u)
+
+from m1n1.agx import AGX
+from m1n1.agx.render import *
+
+from m1n1 import asm
+
+from m1n1.gpiola import GPIOLogicAnalyzer
+
+analyzer_cpu = 1
+
+p.pmgr_adt_clocks_enable("/arm-io/gfx-asc")
+p.pmgr_adt_clocks_enable("/arm-io/sgx")
+p.smp_start_secondaries()
+p.mmu_init_secondary(analyzer_cpu)
+iface.dev.timeout = 42
+
+agx = AGX(u)
+
+mon = RegMonitor(u, ascii=True, bufsize=0x8000000)
+agx.mon = mon
+
+sgx = agx.sgx_dev
+#mon.add(sgx.gpu_region_base, sgx.gpu_region_size, "contexts")
+#mon.add(sgx.gfx_shared_region_base, sgx.gfx_shared_region_size, "gfx-shared")
+#mon.add(sgx.gfx_handoff_base, sgx.gfx_handoff_size, "gfx-handoff")
+
+#mon.add(agx.initdasgx.gfx_handoff_base, sgx.gfx_handoff_size, "gfx-handoff")
+
+atexit.register(p.reboot)
+agx.start()
+
+print("==========================================")
+print("## After init")
+print("==========================================")
+mon.poll()
+agx.poll_objects()
+
+ctx = GPUContext(agx)
+ctx.bind(63)
+
+f = GPUFrame(ctx, sys.argv[1], track=False)
+
+RENDERERS = 4
+
+renderers = []
+
+for i in range(RENDERERS):
+ r = GPURenderer(ctx, 128, bm_slot=0x10 + i, queue=1)
+ renderers.append(r)
+
+ for q in (r.wq_3d, r.wq_ta):
+ q.info.set_prio(2)
+ q.info.push()
+
+print("==========================================")
+print("## Submitting")
+print("==========================================")
+
+w = renderers[3].submit(f.cmdbuf)
+w = renderers[0].submit(f.cmdbuf, w)
+w = renderers[2].submit(f.cmdbuf, w)
+w = renderers[1].submit(f.cmdbuf, w)
+w = renderers[3].submit(f.cmdbuf, w)
+w = renderers[3].submit(f.cmdbuf, w)
+w = renderers[1].submit(f.cmdbuf, w)
+w = renderers[1].submit(f.cmdbuf, w)
+w = renderers[0].submit(f.cmdbuf, w)
+
+for i, r in enumerate(renderers):
+ r.submit(f.cmdbuf)
+
+print("==========================================")
+print("## Submitted")
+print("==========================================")
+
+def t(addr):
+ paddr = agx.uat.iotranslate(0, addr, 4)[0][0]
+ if paddr is None:
+ raise Exception(f"Failed to iotranslate {addr:#x}")
+ return paddr
+
+regs = {
+ "ta_cmds": t(agx.initdata.regionB.stats_ta.addrof("total_cmds")),
+ "ta_ts": t(agx.initdata.regionB.stats_ta.stats.addrof("unk_timestamp")),
+}
+
+pend_base = agx.initdata.regionC.addrof("pending_stamps")
+for i in range(5):
+ regs[f"st{i}_info"] = t(pend_base + i*8)
+ regs[f"st{i}_val"] = t(pend_base + i*8 + 4)
+
+for i in range(4):
+ regs[f"ta{i}_cq"] = t(agx.initdata.regionB.stats_ta.stats.queues[i].addrof("cur_cmdqueue"))
+
+regs.update({
+ #"pwr_status": t(agx.initdata.regionB.hwdata_a.addrof("pwr_status")),
+ #"pstate": t(agx.initdata.regionB.hwdata_a.addrof("cur_pstate")),
+ #"temp_c": t(agx.initdata.regionB.hwdata_a.addrof("temp_c")),
+ #"pwr_mw": t(agx.initdata.regionB.hwdata_a.addrof("avg_power_mw")),
+ #"pwr_ts": t(agx.initdata.regionB.hwdata_a.addrof("update_ts")),
+
+ #"unk_10": t(agx.initdata.regionB.hwdata_a.addrof("unk_10")),
+ #"unk_14": t(agx.initdata.regionB.hwdata_a.addrof("unk_14")),
+ #"actual_pstate": t(agx.initdata.regionB.hwdata_a.addrof("actual_pstate")),
+ #"tgt_pstate": t(agx.initdata.regionB.hwdata_a.addrof("tgt_pstate")),
+ #"unk_40": t(agx.initdata.regionB.hwdata_a.addrof("unk_40")),
+ #"unk_44": t(agx.initdata.regionB.hwdata_a.addrof("unk_44")),
+ #"unk_48": t(agx.initdata.regionB.hwdata_a.addrof("unk_48")),
+ #"freq_mhz": t(agx.initdata.regionB.hwdata_a.addrof("freq_mhz")),
+
+ #"unk_748.0": t(agx.initdata.regionB.hwdata_a.addrof("unk_748")),
+ #"unk_748.1": t(agx.initdata.regionB.hwdata_a.addrof("unk_748")+4),
+ #"unk_748.2": t(agx.initdata.regionB.hwdata_a.addrof("unk_748")+8),
+ #"unk_748.3": t(agx.initdata.regionB.hwdata_a.addrof("unk_748")+12),
+ #"use_percent": t(agx.initdata.regionB.hwdata_a.addrof("use_percent")),
+ #"unk_83c": t(agx.initdata.regionB.hwdata_a.addrof("unk_83c")),
+ #"freq_with_off": t(agx.initdata.regionB.hwdata_a.addrof("freq_with_off")),
+ #"unk_ba0": t(agx.initdata.regionB.hwdata_a.addrof("unk_ba0")),
+ #"unk_bb0": t(agx.initdata.regionB.hwdata_a.addrof("unk_bb0")),
+ #"unk_c44": t(agx.initdata.regionB.hwdata_a.addrof("unk_c44")),
+ #"unk_c58": t(agx.initdata.regionB.hwdata_a.addrof("unk_c58")),
+
+ #"unk_3ca0": t(agx.initdata.regionB.hwdata_a.addrof("unk_3ca0")),
+ #"unk_3ca8": t(agx.initdata.regionB.hwdata_a.addrof("unk_3ca8")),
+ #"unk_3cb0": t(agx.initdata.regionB.hwdata_a.addrof("unk_3cb0")),
+ #"ts_last_idle": t(agx.initdata.regionB.hwdata_a.addrof("ts_last_idle")),
+ #"ts_last_poweron": t(agx.initdata.regionB.hwdata_a.addrof("ts_last_poweron")),
+ #"ts_last_poweroff": t(agx.initdata.regionB.hwdata_a.addrof("ts_last_poweroff")),
+ #"unk_3cd0": t(agx.initdata.regionB.hwdata_a.addrof("unk_3cd0")),
+
+ "halt_count": t(agx.initdata.fw_status.addrof("halt_count")),
+ "halted": t(agx.initdata.fw_status.addrof("halted")),
+ "resume": t(agx.initdata.fw_status.addrof("resume")),
+ "unk_40": t(agx.initdata.fw_status.addrof("unk_40")),
+ "unk_ctr": t(agx.initdata.fw_status.addrof("unk_ctr")),
+ "unk_60": t(agx.initdata.fw_status.addrof("unk_60")),
+ "unk_70": t(agx.initdata.fw_status.addrof("unk_70")),
+ "c_118c0": t(agx.initdata.regionC._addr + 0x118c0),
+ "c_118c4": t(agx.initdata.regionC._addr + 0x118c4),
+ "c_118c8": t(agx.initdata.regionC._addr + 0x118c8),
+ "c_118cc": t(agx.initdata.regionC._addr + 0x118cc),
+ "c_118d0": t(agx.initdata.regionC._addr + 0x118d0),
+ "c_118d4": t(agx.initdata.regionC._addr + 0x118d4),
+ "c_118d8": t(agx.initdata.regionC._addr + 0x118d8),
+ "c_118dc": t(agx.initdata.regionC._addr + 0x118dc),
+ "3d_cmds": t(agx.initdata.regionB.stats_3d.addrof("total_cmds")),
+ #"3d_tvb_oflws_1": t(agx.initdata.regionB.stats_3d.stats.addrof("tvb_overflows_1")),
+ #"3d_tvb_oflws_2": t(agx.initdata.regionB.stats_3d.stats.addrof("tvb_overflows_2")),
+ "3d_cur_stamp_id": t(agx.initdata.regionB.stats_3d.stats.addrof("cur_stamp_id")),
+ "3d_ts": t(agx.initdata.regionB.stats_3d.stats.addrof("unk_timestamp")),
+ #"3d_cur_stamp_id": t(agx.initdata.regionB.stats_3d.stats.addrof("cur_stamp_id")),
+})
+
+for i in range(4):
+ regs[f"3d{i}_cq"] = t(agx.initdata.regionB.stats_3d.stats.queues[i].addrof("cur_cmdqueue"))
+
+
+for i, r in enumerate(renderers):
+ regs.update({
+ f"r{i}_3d_done": t(r.wq_3d.info.pointers.addrof("gpu_doneptr")),
+ #f"r{i}_3d_rptr": t(r.wq_3d.info.pointers.addrof("gpu_rptr")),
+ f"r{i}_3d_busy": t(r.wq_3d.info.addrof("busy")),
+ #f"r{i}_3d_blk": t(r.wq_3d.info.addrof("blocked_on_barrier")),
+ #f"r{i}_3d_2c": t(r.wq_3d.info.addrof("unk_2c")),
+ #f"r{i}_3d_54": t(r.wq_3d.info.addrof("unk_54")),
+
+ f"r{i}_ta_done": t(r.wq_ta.info.pointers.addrof("gpu_doneptr")),
+ #f"r{i}_ta_rptr": t(r.wq_ta.info.pointers.addrof("gpu_rptr")),
+ f"r{i}_ta_busy": t(r.wq_ta.info.addrof("busy")),
+ #f"r{i}_ta_blk": t(r.wq_ta.info.addrof("blocked_on_barrier")),
+ #f"r{i}_ta_2c": t(r.wq_ta.info.addrof("unk_2c")),
+ #f"r{i}_ta_54": t(r.wq_ta.info.addrof("unk_54")),
+ f"r{i}_ta_stamp1": t(r.stamp_ta1._addr),
+ f"r{i}_ta_stamp2":t(r.stamp_ta2._addr),
+ f"r{i}_3d_stamp1": t(r.stamp_3d1._addr),
+ f"r{i}_3d_stamp2":t(r.stamp_3d2._addr),
+
+ f"r{i}_ev_cnt":t(r.event_control.event_count._addr),
+ f"r{i}_ev_cur":t(r.event_control.addrof("cur_count")),
+ f"r{i}_ev_10":t(r.event_control.addrof("unk_10")),
+ })
+
+div=4
+ticks = 24000000 // div * 25
+
+la = GPIOLogicAnalyzer(u, regs=regs, cpu=analyzer_cpu, div=div)
+
+print("Queues:")
+for i, r in enumerate(renderers):
+ print(f" Renderer {i}")
+ print(f" TA: {r.wq_ta.info._addr:#x} (stamp {r.work[0].ev_ta.id})")
+ #print(r.wq_ta.info)
+ print(f" 3D: {r.wq_3d.info._addr:#x} (stamp {r.work[0].ev_3d.id})")
+ #print(r.wq_3d.info)
+
+print("==========================================")
+print("## Run")
+print("==========================================")
+
+la.start(ticks, bufsize=0x8000000)
+
+t = time.time()
+
+try:
+ for r in renderers[:RENDERERS]:
+ r.run()
+
+ for r in renderers[:RENDERERS]:
+ while not r.ev_3d.fired:
+ agx.asc.work()
+ agx.poll_channels()
+ print("==========================================")
+ #agx.poll_objects()
+ #mon.poll()
+ agx.kick_firmware()
+ if time.time() > t + 10:
+ raise Exception("Timeout")
+
+ r.wait()
+
+finally:
+ #agx.poll_objects()
+ #mon.poll()
+
+ la.complete()
+ la.show()
+
+time.sleep(2)
+
diff --git a/tools/proxyclient/experiments/agx_dumpstructs.py b/tools/proxyclient/experiments/agx_dumpstructs.py
new file mode 100644
index 0000000..08a7327
--- /dev/null
+++ b/tools/proxyclient/experiments/agx_dumpstructs.py
@@ -0,0 +1,19 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+
+import sys, pathlib, time
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+from m1n1.constructutils import *
+from m1n1.fw.agx import microsequence, initdata
+
+#for v in initdata.__all__:
+#for v in initdata.__dict__:
+def dump(module):
+ for v in module.__dict__:
+ struct = getattr(module, v)
+ if isinstance(struct, type) and issubclass(struct, ConstructClass) and struct is not ConstructClass:
+ print(struct.to_rust())
+ print()
+
+dump(microsequence)
diff --git a/tools/proxyclient/experiments/agx_parallel.py b/tools/proxyclient/experiments/agx_parallel.py
new file mode 100644
index 0000000..163497e
--- /dev/null
+++ b/tools/proxyclient/experiments/agx_parallel.py
@@ -0,0 +1,341 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+
+import sys, pathlib, time
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+import atexit, sys
+
+from m1n1.setup import *
+from m1n1.constructutils import Ver
+from m1n1.utils import *
+
+Ver.set_version(u)
+
+from m1n1.agx import AGX
+from m1n1.agx.render import *
+
+from m1n1 import asm
+
+from m1n1.gpiola import GPIOLogicAnalyzer
+
+analyzer_cpu = 1
+
+p.pmgr_adt_clocks_enable("/arm-io/gfx-asc")
+p.pmgr_adt_clocks_enable("/arm-io/sgx")
+p.smp_start_secondaries()
+p.mmu_init_secondary(analyzer_cpu)
+iface.dev.timeout = 42
+
+## heater code
+if True:
+ code = u.malloc(0x1000)
+
+ util = asm.ARMAsm("""
+ bench:
+ mrs x1, CNTPCT_EL0
+ 1:
+ sub x0, x0, #1
+ cbnz x0, 1b
+
+ mrs x2, CNTPCT_EL0
+ sub x0, x2, x1
+ ret
+ """, code)
+ iface.writemem(code, util.data)
+ p.dc_cvau(code, len(util.data))
+ p.ic_ivau(code, len(util.data))
+
+ LOOPS = 80000000000
+ for idx in range(2, 8):
+ print(f"bench {idx}")
+ p.smp_call(idx, util.bench, LOOPS)
+
+agx = AGX(u)
+
+mon = RegMonitor(u, ascii=True, bufsize=0x8000000)
+agx.mon = mon
+
+sgx = agx.sgx_dev
+#mon.add(sgx.gpu_region_base, sgx.gpu_region_size, "contexts")
+#mon.add(sgx.gfx_shared_region_base, sgx.gfx_shared_region_size, "gfx-shared")
+#mon.add(sgx.gfx_handoff_base, sgx.gfx_handoff_size, "gfx-handoff")
+
+#mon.add(agx.initdasgx.gfx_handoff_base, sgx.gfx_handoff_size, "gfx-handoff")
+
+atexit.register(p.reboot)
+agx.start()
+
+print("==========================================")
+print("## After init")
+print("==========================================")
+mon.poll()
+agx.poll_objects()
+
+ctx = GPUContext(agx)
+ctx.bind(63)
+ctx0 = GPUContext(agx)
+ctx0.bind(62)
+
+f = GPUFrame(ctx, sys.argv[1], track=False)
+f2 = GPUFrame(ctx0, sys.argv[1], track=False)
+
+RENDERERS = 4
+FRAMES = 8
+
+renderers = []
+
+fault_cmdbuf = f.cmdbuf.clone()
+#fault_cmdbuf.depth_buffer = 0xdeadb000
+
+for i in range(RENDERERS):
+ c = ctx0 if i == 0 else ctx
+ r = GPURenderer(c, 32, bm_slot=0x10 + i, queue=1)
+ renderers.append(r)
+
+ for q in (r.wq_3d, r.wq_ta):
+ q.info.set_prio(2)
+ q.info.push()
+
+#for r in renderers[2:4]:
+ #for q in (r.wq_3d, r.wq_ta):
+ #q.info.set_prio(3)
+ #q.info.push()
+
+#for r in renderers[4:6]:
+ #for q in (r.wq_3d, r.wq_ta):
+ #q.info.set_prio(0)
+ #q.info.push()
+
+#for r in renderers[6:8]:
+ #for q in (r.wq_3d, r.wq_ta):
+ #q.info.set_prio(1)
+ #q.info.push()
+
+print("==========================================")
+print("## Submitting")
+print("==========================================")
+
+for i, r in enumerate(renderers):
+ for j in range(FRAMES):
+ if (i, j) in ((1, 0), (2, 1), (3, 1)):
+ r.submit(fault_cmdbuf)
+ elif i == 0:
+ r.submit(f2.cmdbuf)
+ else:
+ r.submit(f.cmdbuf)
+
+print("==========================================")
+print("## Submitted")
+print("==========================================")
+
+def t(addr):
+ paddr = agx.uat.iotranslate(0, addr, 4)[0][0]
+ if paddr is None:
+ raise Exception(f"Failed to iotranslate {addr:#x}")
+ return paddr
+
+regs = {
+ "ta_cmds": t(agx.initdata.regionB.stats_ta.addrof("total_cmds")),
+ "ta_ts": t(agx.initdata.regionB.stats_ta.stats.addrof("unk_timestamp")),
+}
+
+pend_base = agx.initdata.regionC.addrof("pending_stamps")
+for i in range(5):
+ regs[f"st{i}_info"] = t(pend_base + i*8)
+ regs[f"st{i}_val"] = t(pend_base + i*8 + 4)
+
+for i in range(4):
+ regs[f"ta{i}_cq"] = t(agx.initdata.regionB.stats_ta.stats.queues[i].addrof("cur_cmdqueue"))
+
+regs.update({
+ "pwr_status": t(agx.initdata.regionB.hwdata_a.addrof("pwr_status")),
+ "pstate": t(agx.initdata.regionB.hwdata_a.addrof("cur_pstate")),
+ "temp_c": t(agx.initdata.regionB.hwdata_a.addrof("temp_c")),
+ "pwr_mw": t(agx.initdata.regionB.hwdata_a.addrof("avg_power_mw")),
+ "pwr_ts": t(agx.initdata.regionB.hwdata_a.addrof("update_ts")),
+
+ #"unk_10": t(agx.initdata.regionB.hwdata_a.addrof("unk_10")),
+ #"unk_14": t(agx.initdata.regionB.hwdata_a.addrof("unk_14")),
+ "actual_pstate": t(agx.initdata.regionB.hwdata_a.addrof("actual_pstate")),
+ "tgt_pstate": t(agx.initdata.regionB.hwdata_a.addrof("tgt_pstate")),
+ #"unk_40": t(agx.initdata.regionB.hwdata_a.addrof("unk_40")),
+ #"unk_44": t(agx.initdata.regionB.hwdata_a.addrof("unk_44")),
+ #"unk_48": t(agx.initdata.regionB.hwdata_a.addrof("unk_48")),
+ "freq_mhz": t(agx.initdata.regionB.hwdata_a.addrof("freq_mhz")),
+
+ #"unk_748.0": t(agx.initdata.regionB.hwdata_a.addrof("unk_748")),
+ #"unk_748.1": t(agx.initdata.regionB.hwdata_a.addrof("unk_748")+4),
+ #"unk_748.2": t(agx.initdata.regionB.hwdata_a.addrof("unk_748")+8),
+ #"unk_748.3": t(agx.initdata.regionB.hwdata_a.addrof("unk_748")+12),
+ #"use_percent": t(agx.initdata.regionB.hwdata_a.addrof("use_percent")),
+ #"unk_83c": t(agx.initdata.regionB.hwdata_a.addrof("unk_83c")),
+ "freq_with_off": t(agx.initdata.regionB.hwdata_a.addrof("freq_with_off")),
+ #"unk_ba0": t(agx.initdata.regionB.hwdata_a.addrof("unk_ba0")),
+ #"unk_bb0": t(agx.initdata.regionB.hwdata_a.addrof("unk_bb0")),
+ #"unk_c44": t(agx.initdata.regionB.hwdata_a.addrof("unk_c44")),
+ #"unk_c58": t(agx.initdata.regionB.hwdata_a.addrof("unk_c58")),
+
+ #"unk_3ca0": t(agx.initdata.regionB.hwdata_a.addrof("unk_3ca0")),
+ #"unk_3ca8": t(agx.initdata.regionB.hwdata_a.addrof("unk_3ca8")),
+ #"unk_3cb0": t(agx.initdata.regionB.hwdata_a.addrof("unk_3cb0")),
+ #"ts_last_idle": t(agx.initdata.regionB.hwdata_a.addrof("ts_last_idle")),
+ #"ts_last_poweron": t(agx.initdata.regionB.hwdata_a.addrof("ts_last_poweron")),
+ #"ts_last_poweroff": t(agx.initdata.regionB.hwdata_a.addrof("ts_last_poweroff")),
+ #"unk_3cd0": t(agx.initdata.regionB.hwdata_a.addrof("unk_3cd0")),
+
+ "halt_count": t(agx.initdata.fw_status.addrof("halt_count")),
+ "halted": t(agx.initdata.fw_status.addrof("halted")),
+ "resume": t(agx.initdata.fw_status.addrof("resume")),
+ "unk_40": t(agx.initdata.fw_status.addrof("unk_40")),
+ "unk_ctr": t(agx.initdata.fw_status.addrof("unk_ctr")),
+ "unk_60": t(agx.initdata.fw_status.addrof("unk_60")),
+ "unk_70": t(agx.initdata.fw_status.addrof("unk_70")),
+ "c_118c0": t(agx.initdata.regionC._addr + 0x118c0),
+ "c_118c4": t(agx.initdata.regionC._addr + 0x118c4),
+ "c_118c8": t(agx.initdata.regionC._addr + 0x118c8),
+ "c_118cc": t(agx.initdata.regionC._addr + 0x118cc),
+ "c_118d0": t(agx.initdata.regionC._addr + 0x118d0),
+ "c_118d4": t(agx.initdata.regionC._addr + 0x118d4),
+ "c_118d8": t(agx.initdata.regionC._addr + 0x118d8),
+ "c_118dc": t(agx.initdata.regionC._addr + 0x118dc),
+ "3d_cmds": t(agx.initdata.regionB.stats_3d.addrof("total_cmds")),
+ #"3d_tvb_oflws_1": t(agx.initdata.regionB.stats_3d.stats.addrof("tvb_overflows_1")),
+ #"3d_tvb_oflws_2": t(agx.initdata.regionB.stats_3d.stats.addrof("tvb_overflows_2")),
+ "3d_cur_stamp_id": t(agx.initdata.regionB.stats_3d.stats.addrof("cur_stamp_id")),
+ "3d_ts": t(agx.initdata.regionB.stats_3d.stats.addrof("unk_timestamp")),
+ #"3d_cur_stamp_id": t(agx.initdata.regionB.stats_3d.stats.addrof("cur_stamp_id")),
+})
+
+for i in range(4):
+ regs[f"3d{i}_cq"] = t(agx.initdata.regionB.stats_3d.stats.queues[i].addrof("cur_cmdqueue"))
+
+
+for i, r in enumerate(renderers):
+ regs.update({
+ f"r{i}_3d_done": t(r.wq_3d.info.pointers.addrof("gpu_doneptr")),
+ #f"r{i}_3d_rptr": t(r.wq_3d.info.pointers.addrof("gpu_rptr")),
+ f"r{i}_3d_busy": t(r.wq_3d.info.addrof("busy")),
+ #f"r{i}_3d_blk": t(r.wq_3d.info.addrof("blocked_on_barrier")),
+ #f"r{i}_3d_2c": t(r.wq_3d.info.addrof("unk_2c")),
+ #f"r{i}_3d_54": t(r.wq_3d.info.addrof("unk_54")),
+
+ f"r{i}_ta_done": t(r.wq_ta.info.pointers.addrof("gpu_doneptr")),
+ #f"r{i}_ta_rptr": t(r.wq_ta.info.pointers.addrof("gpu_rptr")),
+ f"r{i}_ta_busy": t(r.wq_ta.info.addrof("busy")),
+ #f"r{i}_ta_blk": t(r.wq_ta.info.addrof("blocked_on_barrier")),
+ #f"r{i}_ta_2c": t(r.wq_ta.info.addrof("unk_2c")),
+ #f"r{i}_ta_54": t(r.wq_ta.info.addrof("unk_54")),
+ f"r{i}_f{j}_ta_stamp1": t(r.stamp_ta1._addr),
+ f"r{i}_ta_stamp2":t(r.stamp_ta2._addr),
+ f"r{i}_f{j}_3d_stamp1": t(r.stamp_3d1._addr),
+ f"r{i}_3d_stamp2":t(r.stamp_3d2._addr),
+ })
+
+ for j in range(FRAMES):
+ work = r.work[j]
+ regs.update({
+ f"r{i}_f{j}_3d_ts": t(work.wc_3d.ts1._addr),
+ f"r{i}_f{j}_ta_ts": t(work.wc_ta.ts1._addr),
+ })
+
+div=4
+ticks = 24000000 // div * 25
+
+la = GPIOLogicAnalyzer(u, regs=regs, cpu=analyzer_cpu, div=div)
+
+
+print("==========================================")
+print("## Poll prior to job start")
+print("==========================================")
+
+#mon.poll()
+#agx.poll_objects()
+
+
+print("==========================================")
+print("## After start")
+print("==========================================")
+#agx.poll_objects()
+
+#mon.poll()
+print("==========================================")
+print("## Waiting")
+print("==========================================")
+
+print("Queues:")
+for i, r in enumerate(renderers):
+ print(f" Renderer {i}")
+ print(f" TA: {r.wq_ta.info._addr:#x} (stamp {r.work[0].ev_ta.id})")
+ #print(r.wq_ta.info)
+ print(f" 3D: {r.wq_3d.info._addr:#x} (stamp {r.work[0].ev_3d.id})")
+ #print(r.wq_3d.info)
+
+print("==========================================")
+print("## Run")
+print("==========================================")
+
+la.start(ticks, bufsize=0x8000000)
+
+try:
+ for r in renderers[:RENDERERS]:
+ r.run()
+
+ for r in renderers[:RENDERERS]:
+ while not r.ev_3d.fired:
+ agx.asc.work()
+ agx.poll_channels()
+ print("==========================================")
+ agx.poll_objects()
+ mon.poll()
+
+ r.wait()
+
+ #agx.poll_objects()
+
+ #print("==========================================")
+ #print("## Stop ASC")
+ #print("==========================================")
+
+ #agx.asc.stop()
+
+ ##time.sleep(0.1)
+
+ ##agx.poll_objects()
+
+ #print("==========================================")
+ #print("## Start ASC")
+ #print("==========================================")
+
+ #agx.asc.start()
+
+ ##agx.poll_objects()
+
+ #print("==========================================")
+ #print("## Run 2")
+ #print("==========================================")
+
+ #for r in renderers[RENDERERS//2:]:
+ #r.run()
+
+ #for r in renderers[RENDERERS//2:]:
+ #while not r.ev_3d.fired:
+ #agx.asc.work()
+ #agx.poll_channels()
+ #print("==========================================")
+
+ #r.wait()
+
+ #agx.poll_objects()
+
+ #mon.poll()
+
+finally:
+ #agx.poll_objects()
+ #mon.poll()
+
+ la.complete()
+ la.show()
+
+time.sleep(2)
+
diff --git a/tools/proxyclient/experiments/agx_renderframe.py b/tools/proxyclient/experiments/agx_renderframe.py
new file mode 100644
index 0000000..82ac91c
--- /dev/null
+++ b/tools/proxyclient/experiments/agx_renderframe.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+
+import sys, pathlib, time
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+import atexit, sys
+
+from m1n1.setup import *
+from m1n1.constructutils import Ver
+from m1n1.utils import *
+
+Ver.set_version(u)
+
+from m1n1.agx import AGX
+from m1n1.agx.render import *
+
+from m1n1 import asm
+
+p.pmgr_adt_clocks_enable("/arm-io/gfx-asc")
+p.pmgr_adt_clocks_enable("/arm-io/sgx")
+
+agx = AGX(u)
+mon = RegMonitor(u, ascii=True, bufsize=0x8000000)
+agx.mon = mon
+
+sgx = agx.sgx_dev
+
+try:
+ agx.start()
+ agx.uat.dump(0)
+
+ print("==========================================")
+ print("## After init")
+ print("==========================================")
+ mon.poll()
+ agx.poll_objects()
+
+ ctx = GPUContext(agx)
+ ctx.bind(63)
+
+ f = GPUFrame(ctx, sys.argv[1], track=False)
+
+ r = GPURenderer(ctx, 16, bm_slot=0, queue=1)
+ print("==========================================")
+ print("## Submitting")
+ print("==========================================")
+
+ w = r.submit(f.cmdbuf)
+
+ print("==========================================")
+ print("## Submitted")
+ print("==========================================")
+
+ print("==========================================")
+ print("## Run")
+ print("==========================================")
+
+ r.run()
+
+ while not r.ev_3d.fired:
+ agx.asc.work()
+ agx.poll_channels()
+
+ agx.poll_objects()
+ mon.poll()
+ r.wait()
+
+ time.sleep(3)
+
+finally:
+ #agx.poll_objects()
+ p.reboot()
diff --git a/tools/proxyclient/experiments/agx_tlb.py b/tools/proxyclient/experiments/agx_tlb.py
new file mode 100644
index 0000000..5ec86ca
--- /dev/null
+++ b/tools/proxyclient/experiments/agx_tlb.py
@@ -0,0 +1,295 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+
+import sys, pathlib, time
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+import atexit, sys
+
+from m1n1.setup import *
+from m1n1.constructutils import Ver
+from m1n1.utils import *
+
+Ver.set_version(u)
+
+from m1n1.agx import AGX
+from m1n1.agx.render import *
+
+from m1n1 import asm
+
+from m1n1.gpiola import GPIOLogicAnalyzer
+
+analyzer_cpu = 1
+
+p.pmgr_adt_clocks_enable("/arm-io/gfx-asc")
+p.pmgr_adt_clocks_enable("/arm-io/sgx")
+p.smp_start_secondaries()
+p.mmu_init_secondary(analyzer_cpu)
+iface.dev.timeout = 42
+
+agx = AGX(u)
+
+def initdata_hook(agx):
+ agx.initdata.regionC.idle_to_off_timeout_ms = 20000
+ agx.initdata.regionC.push()
+
+agx.initdata_hook = initdata_hook
+
+mon = RegMonitor(u, ascii=True, bufsize=0x8000000)
+agx.mon = mon
+
+sgx = agx.sgx_dev
+#mon.add(sgx.gpu_region_base, sgx.gpu_region_size, "contexts")
+#mon.add(sgx.gfx_shared_region_base, sgx.gfx_shared_region_size, "gfx-shared")
+#mon.add(sgx.gfx_handoff_base, sgx.gfx_handoff_size, "gfx-handoff")
+
+#mon.add(agx.initdasgx.gfx_handoff_base, sgx.gfx_handoff_size, "gfx-handoff")
+
+atexit.register(p.reboot)
+agx.start()
+
+print("==========================================")
+print("## After init")
+print("==========================================")
+mon.poll()
+agx.poll_objects()
+
+ctx = GPUContext(agx)
+ctx.bind(3)
+
+f = GPUFrame(ctx, sys.argv[1], track=False)
+
+RENDERERS = 1
+FRAMES = 1
+
+renderers = []
+
+for i in range(RENDERERS):
+ r = GPURenderer(ctx, 4, bm_slot=0x10 + i, queue=1)
+ renderers.append(r)
+
+ for q in (r.wq_3d, r.wq_ta):
+ q.info.set_prio(2)
+ q.info.push()
+
+print("==========================================")
+print("## Submitting")
+print("==========================================")
+
+for i, r in enumerate(renderers):
+ for j in range(FRAMES):
+ r.submit(f.cmdbuf)
+
+print("==========================================")
+print("## Submitted")
+print("==========================================")
+
+def t(addr):
+ paddr = agx.uat.iotranslate(0, addr, 4)[0][0]
+ if paddr is None:
+ raise Exception(f"Failed to iotranslate {addr:#x}")
+ return paddr
+
+regs = {
+ "ta_cmds": t(agx.initdata.regionB.stats_ta.addrof("total_cmds")),
+ "ta_ts": t(agx.initdata.regionB.stats_ta.stats.addrof("unk_timestamp")),
+}
+
+#pend_base = agx.initdata.regionC.addrof("pending_stamps")
+#for i in range(5):
+ #regs[f"st{i}_info"] = t(pend_base + i*8)
+ #regs[f"st{i}_val"] = t(pend_base + i*8 + 4)
+
+#for i in range(4):
+ #regs[f"ta{i}_cq"] = t(agx.initdata.regionB.stats_ta.stats.queues[i].addrof("cur_cmdqueue"))
+
+regs.update({
+ "pwr_status": t(agx.initdata.regionB.hwdata_a.addrof("pwr_status")),
+ "pstate": t(agx.initdata.regionB.hwdata_a.addrof("cur_pstate")),
+ "temp_c": t(agx.initdata.regionB.hwdata_a.addrof("temp_c")),
+ "pwr_mw": t(agx.initdata.regionB.hwdata_a.addrof("avg_power_mw")),
+ "pwr_ts": t(agx.initdata.regionB.hwdata_a.addrof("update_ts")),
+
+ "unk_10": t(agx.initdata.regionB.hwdata_a.addrof("unk_10")),
+ "unk_14": t(agx.initdata.regionB.hwdata_a.addrof("unk_14")),
+ "actual_pstate": t(agx.initdata.regionB.hwdata_a.addrof("actual_pstate")),
+ "tgt_pstate": t(agx.initdata.regionB.hwdata_a.addrof("tgt_pstate")),
+ "unk_40": t(agx.initdata.regionB.hwdata_a.addrof("unk_40")),
+ "unk_44": t(agx.initdata.regionB.hwdata_a.addrof("unk_44")),
+ "unk_48": t(agx.initdata.regionB.hwdata_a.addrof("unk_48")),
+ "freq_mhz": t(agx.initdata.regionB.hwdata_a.addrof("freq_mhz")),
+
+ "unk_748.0": t(agx.initdata.regionB.hwdata_a.addrof("unk_748")),
+ "unk_748.1": t(agx.initdata.regionB.hwdata_a.addrof("unk_748")+4),
+ "unk_748.2": t(agx.initdata.regionB.hwdata_a.addrof("unk_748")+8),
+ "unk_748.3": t(agx.initdata.regionB.hwdata_a.addrof("unk_748")+12),
+ "use_percent": t(agx.initdata.regionB.hwdata_a.addrof("use_percent")),
+ "unk_83c": t(agx.initdata.regionB.hwdata_a.addrof("unk_83c")),
+ "freq_with_off": t(agx.initdata.regionB.hwdata_a.addrof("freq_with_off")),
+ "unk_ba0": t(agx.initdata.regionB.hwdata_a.addrof("unk_ba0")),
+ "unk_bb0": t(agx.initdata.regionB.hwdata_a.addrof("unk_bb0")),
+ "unk_c44": t(agx.initdata.regionB.hwdata_a.addrof("unk_c44")),
+ "unk_c58": t(agx.initdata.regionB.hwdata_a.addrof("unk_c58")),
+
+ "unk_3ca0": t(agx.initdata.regionB.hwdata_a.addrof("unk_3ca0")),
+ "unk_3ca8": t(agx.initdata.regionB.hwdata_a.addrof("unk_3ca8")),
+ "unk_3cb0": t(agx.initdata.regionB.hwdata_a.addrof("unk_3cb0")),
+ "ts_last_idle": t(agx.initdata.regionB.hwdata_a.addrof("ts_last_idle")),
+ "ts_last_poweron": t(agx.initdata.regionB.hwdata_a.addrof("ts_last_poweron")),
+ "ts_last_poweroff": t(agx.initdata.regionB.hwdata_a.addrof("ts_last_poweroff")),
+ "unk_3cd0": t(agx.initdata.regionB.hwdata_a.addrof("unk_3cd0")),
+
+ "halt_count": t(agx.initdata.fw_status.addrof("halt_count")),
+ "halted": t(agx.initdata.fw_status.addrof("halted")),
+ "resume": t(agx.initdata.fw_status.addrof("resume")),
+ "unk_40": t(agx.initdata.fw_status.addrof("unk_40")),
+ "unk_ctr": t(agx.initdata.fw_status.addrof("unk_ctr")),
+ "unk_60": t(agx.initdata.fw_status.addrof("unk_60")),
+ "unk_70": t(agx.initdata.fw_status.addrof("unk_70")),
+ "c_118c0": t(agx.initdata.regionC._addr + 0x118c0),
+ "c_118c4": t(agx.initdata.regionC._addr + 0x118c4),
+ "c_118c8": t(agx.initdata.regionC._addr + 0x118c8),
+ "c_118cc": t(agx.initdata.regionC._addr + 0x118cc),
+ "c_118d0": t(agx.initdata.regionC._addr + 0x118d0),
+ "c_118d4": t(agx.initdata.regionC._addr + 0x118d4),
+ "c_118d8": t(agx.initdata.regionC._addr + 0x118d8),
+ "c_118dc": t(agx.initdata.regionC._addr + 0x118dc),
+ "3d_cmds": t(agx.initdata.regionB.stats_3d.addrof("total_cmds")),
+ #"3d_cq": t(agx.initdata.regionB.stats_3d.stats.addrof("cur_cmdqueue")),
+ #"3d_tvb_oflws_1": t(agx.initdata.regionB.stats_3d.stats.addrof("tvb_overflows_1")),
+ #"3d_tvb_oflws_2": t(agx.initdata.regionB.stats_3d.stats.addrof("tvb_overflows_2")),
+ #"3d_cur_stamp_id": t(agx.initdata.regionB.stats_3d.stats.addrof("cur_stamp_id")),
+ "3d_ts": t(agx.initdata.regionB.stats_3d.stats.addrof("unk_timestamp")),
+ "hoff_lock": agx.uat.handoff.reg.LOCK_AP.addr,
+ "hoff_ctx": agx.uat.handoff.reg.CUR_CTX.addr,
+ "hoff_unk2": agx.uat.handoff.reg.UNK2.addr,
+ "hoff_unk3_lo": agx.uat.handoff.reg.UNK3.addr,
+ "hoff_unk3_hi": agx.uat.handoff.reg.UNK3.addr + 4,
+})
+
+for i, r in enumerate(renderers):
+ regs.update({
+ f"r{i}_3d_done": t(r.wq_3d.info.pointers.addrof("gpu_doneptr")),
+ #f"r{i}_3d_rptr": t(r.wq_3d.info.pointers.addrof("gpu_rptr")),
+ f"r{i}_3d_busy": t(r.wq_3d.info.addrof("busy")),
+ #f"r{i}_3d_blk": t(r.wq_3d.info.addrof("blocked_on_barrier")),
+ #f"r{i}_3d_2c": t(r.wq_3d.info.addrof("unk_2c")),
+ #f"r{i}_3d_54": t(r.wq_3d.info.addrof("unk_54")),
+
+ f"r{i}_ta_done": t(r.wq_ta.info.pointers.addrof("gpu_doneptr")),
+ #f"r{i}_ta_rptr": t(r.wq_ta.info.pointers.addrof("gpu_rptr")),
+ f"r{i}_ta_busy": t(r.wq_ta.info.addrof("busy")),
+ #f"r{i}_ta_blk": t(r.wq_ta.info.addrof("blocked_on_barrier")),
+ #f"r{i}_ta_2c": t(r.wq_ta.info.addrof("unk_2c")),
+ #f"r{i}_ta_54": t(r.wq_ta.info.addrof("unk_54")),
+ f"r{i}_f{j}_ta_stamp1": t(r.stamp_ta1._addr),
+ f"r{i}_ta_stamp2":t(r.stamp_ta2._addr),
+ f"r{i}_f{j}_3d_stamp1": t(r.stamp_3d1._addr),
+ f"r{i}_3d_stamp2":t(r.stamp_3d2._addr),
+ })
+
+ #for j in range(FRAMES):
+ #work = r.work[j]
+ #regs.update({
+ #f"r{i}_f{j}_3d_ts": t(work.wc_3d.ts1._addr),
+ #f"r{i}_f{j}_ta_ts": t(work.wc_ta.ts1._addr),
+ #})
+
+div=4
+ticks = 24000000 // div * 25
+
+la = GPIOLogicAnalyzer(u, regs=regs, cpu=analyzer_cpu, div=div)
+
+print("==========================================")
+print("## Run")
+print("==========================================")
+
+la.start(ticks, bufsize=0x8000000)
+
+depth_len = align_up(1920*1080*4, 0x4000)
+#agx.uat.iomap_at(ctx.ctx, f.cmdbuf.depth_buffer, 0, depth_len, VALID=0)
+#agx.uat.flush_dirty()
+
+fb = r.work[0].fb
+
+#agx.uat.iomap_at(ctx.ctx, fb, 0, depth_len, VALID=0)
+#agx.uat.flush_dirty()
+
+agx.kick_firmware()
+
+agx.show_stats = False
+count_pa = renderers[0].event_control.event_count._paddr
+
+print(f"count: {p.read32(count_pa)}")
+
+agx.uat.invalidate_cache()
+agx.uat.dump(ctx.ctx)
+
+mon.add(0x9fff74000, 0x4000)
+
+try:
+ for r in renderers:
+ r.run()
+
+ for r in renderers:
+ while not r.ev_ta.fired:
+ agx.asc.work()
+ agx.poll_channels()
+
+ print("TA fired")
+ print(f"count: {p.read32(count_pa)}")
+
+ for r in renderers:
+ while not r.ev_3d.fired:
+ agx.asc.work()
+ agx.poll_channels()
+ #print("==========================================")
+
+ r.wait()
+
+ print("3D fired")
+ print("Timestamps:")
+ print(f" 3D 1: {r.ts3d_1.pull().val}")
+ print(f" 3D 2: {r.ts3d_2.pull().val}")
+ print(f" TA 1: {r.tsta_1.pull().val}")
+ print(f" TA 2: {r.tsta_2.pull().val}")
+ print("CPU flag:", r.buffer_mgr.misc_obj.pull().cpu_flag)
+
+ mon.poll()
+
+ #agx.uat.iomap_at(ctx.ctx, fb, 0, depth_len, VALID=0)
+ #agx.uat.flush_dirty()
+
+ print(f"fb: {fb:#x}")
+
+ for i, r in enumerate(renderers):
+ for j in range(FRAMES):
+ r.submit(f.cmdbuf)
+
+ r.run()
+
+ for r in renderers:
+ while not r.ev_3d.fired:
+ agx.asc.work()
+ r.wait()
+
+ print("3D fired again")
+ print("Timestamps:")
+ print(f" 3D 1: {r.ts3d_1.pull().val}")
+ print(f" 3D 2: {r.ts3d_2.pull().val}")
+ print(f" TA 1: {r.tsta_1.pull().val}")
+ print(f" TA 2: {r.tsta_2.pull().val}")
+ print("CPU flag:", r.buffer_mgr.misc_obj.pull().cpu_flag)
+
+ time.sleep(0.5)
+
+finally:
+ agx.poll_channels()
+ #agx.poll_objects()
+ #mon.poll()
+
+ la.complete()
+ la.show()
+
+time.sleep(2)
+
diff --git a/tools/proxyclient/experiments/agx_tracetimings.py b/tools/proxyclient/experiments/agx_tracetimings.py
new file mode 100644
index 0000000..52ac2c9
--- /dev/null
+++ b/tools/proxyclient/experiments/agx_tracetimings.py
@@ -0,0 +1,314 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+
+import sys, pathlib, time
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+import atexit, sys
+
+from m1n1.setup import *
+from m1n1.constructutils import Ver
+from m1n1.utils import *
+
+Ver.set_version(u)
+
+from m1n1.agx import AGX
+from m1n1.agx.render import *
+
+from m1n1.gpiola import GPIOLogicAnalyzer
+
+analyzer_cpu = 1
+
+p.pmgr_adt_clocks_enable("/arm-io/gfx-asc")
+p.pmgr_adt_clocks_enable("/arm-io/sgx")
+p.smp_start_secondaries()
+p.mmu_init_secondary(analyzer_cpu)
+iface.dev.timeout = 10
+
+agx = AGX(u)
+
+mon = RegMonitor(u, ascii=True, bufsize=0x8000000)
+agx.mon = mon
+
+sgx = agx.sgx_dev
+#mon.add(sgx.gpu_region_base, sgx.gpu_region_size, "contexts")
+#mon.add(sgx.gfx_shared_region_base, sgx.gfx_shared_region_size, "gfx-shared")
+#mon.add(sgx.gfx_handoff_base, sgx.gfx_handoff_size, "gfx-handoff")
+
+#mon.add(agx.initdasgx.gfx_handoff_base, sgx.gfx_handoff_size, "gfx-handoff")
+
+atexit.register(p.reboot)
+agx.start()
+
+print("==========================================")
+print("## After init")
+print("==========================================")
+mon.poll()
+agx.poll_objects()
+
+ctx = GPUContext(agx)
+ctx.bind(1)
+
+renderer = GPURenderer(ctx, 64, bm_slot=0, queue=0)
+renderer2 = GPURenderer(ctx, 64, bm_slot=1, queue=1)
+
+#for q in (renderer.wq_3d, renderer.wq_ta):#, renderer2.wq_3d, renderer2.wq_ta):
+ #q.info.unk_30 = 2
+ #q.info.unk_34 = 2
+ #q.info.unk_38 = 0xffff000000000000
+ #q.info.unk_40 = 0
+ #q.info.unk_44 = 0
+ #q.info.unk_48 = 2
+ #q.info.unk_50 = 0x1
+ #q.info.push()
+
+f = GPUFrame(ctx, sys.argv[1], track=False)
+#f2 = GPUFrame(renderer2.ctx, sys.argv[1], track=False)
+
+print("==========================================")
+print("## Pre submit")
+print("==========================================")
+
+mon.poll()
+agx.poll_objects()
+
+print("==========================================")
+print("## Submitting")
+print("==========================================")
+
+work = renderer.submit(f.cmdbuf)
+work2 = renderer2.submit(f.cmdbuf)
+workb = renderer.submit(f.cmdbuf)
+work2b = renderer2.submit(f.cmdbuf)
+
+print(work.wc_3d)
+print(work.wc_ta)
+print(work2.wc_3d)
+print(work2.wc_ta)
+
+print("==========================================")
+print("## Submitted")
+print("==========================================")
+
+def t(addr):
+ paddr = agx.uat.iotranslate(0, addr, 4)[0][0]
+ if paddr is None:
+ raise Exception(f"Failed to iotranslate {addr:#x}")
+ return paddr
+
+regs = {
+ "ta_cmds": t(agx.initdata.regionB.stats_ta.addrof("total_cmds")),
+ "ta0_busy": t(agx.initdata.regionB.stats_ta.stats.queues[0].addrof("busy")),
+ "ta0_unk4": t(agx.initdata.regionB.stats_ta.stats.queues[0].addrof("unk_4")),
+ "ta0_cq": t(agx.initdata.regionB.stats_ta.stats.queues[0].addrof("cur_cmdqueue")),
+ "ta0_cnt": t(agx.initdata.regionB.stats_ta.stats.queues[0].addrof("cur_count")),
+ "ta1_busy": t(agx.initdata.regionB.stats_ta.stats.queues[1].addrof("busy")),
+ "ta1_unk4": t(agx.initdata.regionB.stats_ta.stats.queues[1].addrof("unk_4")),
+ "ta1_cq": t(agx.initdata.regionB.stats_ta.stats.queues[1].addrof("cur_cmdqueue")),
+ "ta1_cnt": t(agx.initdata.regionB.stats_ta.stats.queues[1].addrof("cur_count")),
+ "ta_ts": t(agx.initdata.regionB.stats_ta.stats.addrof("unk_timestamp")),
+ "3d_cmds": t(agx.initdata.regionB.stats_3d.addrof("total_cmds")),
+ "3d_cq": t(agx.initdata.regionB.stats_3d.stats.addrof("cur_cmdqueue")),
+ "3d_tvb_oflws_1": t(agx.initdata.regionB.stats_3d.stats.addrof("tvb_overflows_1")),
+ "3d_tvb_oflws_2": t(agx.initdata.regionB.stats_3d.stats.addrof("tvb_overflows_2")),
+ "3d_cur_stamp_id": t(agx.initdata.regionB.stats_3d.stats.addrof("cur_stamp_id")),
+ "3d_ts": t(agx.initdata.regionB.stats_3d.stats.addrof("unk_timestamp")),
+
+ "bmctl_0": t(agx.initdata.regionB.buffer_mgr_ctl._addr + 0),
+ "bmctl_8": t(agx.initdata.regionB.buffer_mgr_ctl._addr + 8),
+ "2_bmctl_0": t(agx.initdata.regionB.buffer_mgr_ctl._addr + 16),
+ "2_bmctl_8": t(agx.initdata.regionB.buffer_mgr_ctl._addr + 24),
+
+ "bmmisc_0": t(renderer.buffer_mgr.info.misc.addrof("gpu_0")),
+ "bmmisc_4": t(renderer.buffer_mgr.info.misc.addrof("gpu_4")),
+ "bmmisc_8": t(renderer.buffer_mgr.info.misc.addrof("gpu_8")),
+ "bmmisc_c": t(renderer.buffer_mgr.info.misc.addrof("gpu_c")),
+ "bmi_gpuc": t(renderer.buffer_mgr.info.addrof("gpu_counter")),
+ "bmi_18": t(renderer.buffer_mgr.info.addrof("unk_18")),
+ "bmi_gpuc2": t(renderer.buffer_mgr.info.addrof("gpu_counter2")),
+
+ "2_bmmisc_0": t(renderer2.buffer_mgr.info.misc.addrof("gpu_0")),
+ "2_bmmisc_4": t(renderer2.buffer_mgr.info.misc.addrof("gpu_4")),
+ "2_bmmisc_8": t(renderer2.buffer_mgr.info.misc.addrof("gpu_8")),
+ "2_bmmisc_c": t(renderer2.buffer_mgr.info.misc.addrof("gpu_c")),
+ "2_bmi_gpuc": t(renderer2.buffer_mgr.info.addrof("gpu_counter")),
+ "2_bmi_18": t(renderer2.buffer_mgr.info.addrof("unk_18")),
+ "2_bmi_gpuc2": t(renderer2.buffer_mgr.info.addrof("gpu_counter2")),
+
+ "ctxdat_0": t(renderer.ctx.gpu_context._addr + 0),
+ "ctxdat_4": t(renderer.ctx.gpu_context._addr + 4),
+ "ctxdat_8": t(renderer.ctx.gpu_context._addr + 8),
+ "ctxdat_c": t(renderer.ctx.gpu_context._addr + 0xc),
+
+ "2_ctxdat_0": t(renderer2.ctx.gpu_context._addr + 0),
+ "2_ctxdat_4": t(renderer2.ctx.gpu_context._addr + 4),
+ "2_ctxdat_8": t(renderer2.ctx.gpu_context._addr + 8),
+ "2_ctxdat_c": t(renderer2.ctx.gpu_context._addr + 0xc),
+
+ "evctl_ta": t(renderer.event_control.addrof("has_ta")),
+ "evctl_pta": t(renderer.event_control.addrof("pstamp_ta")),
+ "evctl_3d": t(renderer.event_control.addrof("has_3d")),
+ "evctl_p3d": t(renderer.event_control.addrof("pstamp_3d")),
+ "evctl_in_list": t(renderer.event_control.addrof("in_list")),
+ "evctl_prev": t(renderer.event_control.list_head.addrof("prev")),
+ "evctl_next": t(renderer.event_control.list_head.addrof("next")),
+
+ "2_evctl_ta": t(renderer2.event_control.addrof("has_ta")),
+ "2_evctl_pta": t(renderer2.event_control.addrof("pstamp_ta")),
+ "2_evctl_3d": t(renderer2.event_control.addrof("has_3d")),
+ "2_evctl_p3d": t(renderer2.event_control.addrof("pstamp_3d")),
+ "2_evctl_in_list":t(renderer2.event_control.addrof("in_list")),
+ "2_evctl_prev": t(renderer2.event_control.list_head.addrof("prev")),
+ "2_evctl_next": t(renderer2.event_control.list_head.addrof("next")),
+
+ "jl_first": t(renderer.job_list.addrof("first_job")),
+ "jl_last": t(renderer.job_list.addrof("last_head")),
+ "jl_10": t(renderer.job_list.addrof("unkptr_10")),
+
+ "2_jl_first": t(renderer2.job_list.addrof("first_job")),
+ "2_jl_last": t(renderer2.job_list.addrof("last_head")),
+ "2_jl_10": t(renderer2.job_list.addrof("unkptr_10")),
+
+ "3d_done": t(renderer.wq_3d.info.pointers.addrof("gpu_doneptr")),
+ "3d_rptr": t(renderer.wq_3d.info.pointers.addrof("gpu_rptr")),
+ "3d_rptr1": t(renderer.wq_3d.info.addrof("gpu_rptr1")),
+ "3d_rptr2": t(renderer.wq_3d.info.addrof("gpu_rptr2")),
+ "3d_rptr3": t(renderer.wq_3d.info.addrof("gpu_rptr3")),
+ "3d_busy": t(renderer.wq_3d.info.addrof("busy")),
+ "3d_blk": t(renderer.wq_3d.info.addrof("blocked_on_barrier")),
+ "3d_2c": t(renderer.wq_3d.info.addrof("unk_2c")),
+ "3d_54": t(renderer.wq_3d.info.addrof("unk_54")),
+ "3d_58": t(renderer.wq_3d.info.addrof("unk_58")),
+
+ "2_3d_done": t(renderer2.wq_3d.info.pointers.addrof("gpu_doneptr")),
+ "2_3d_rptr": t(renderer2.wq_3d.info.pointers.addrof("gpu_rptr")),
+ "2_3d_busy": t(renderer2.wq_3d.info.addrof("busy")),
+ "2_3d_blk": t(renderer2.wq_3d.info.addrof("blocked_on_barrier")),
+ "2_3d_2c": t(renderer2.wq_3d.info.addrof("unk_2c")),
+ "2_3d_54": t(renderer2.wq_3d.info.addrof("unk_54")),
+
+ "ta_done": t(renderer.wq_ta.info.pointers.addrof("gpu_doneptr")),
+ "ta_rptr": t(renderer.wq_ta.info.pointers.addrof("gpu_rptr")),
+ "ta_rptr1": t(renderer.wq_ta.info.addrof("gpu_rptr1")),
+ "ta_rptr2": t(renderer.wq_ta.info.addrof("gpu_rptr2")),
+ "ta_rptr3": t(renderer.wq_ta.info.addrof("gpu_rptr3")),
+ "ta_busy": t(renderer.wq_ta.info.addrof("busy")),
+ "ta_blk": t(renderer.wq_ta.info.addrof("blocked_on_barrier")),
+ "ta_2c": t(renderer.wq_ta.info.addrof("unk_2c")),
+ "ta_54": t(renderer.wq_ta.info.addrof("unk_54")),
+ "ta_58": t(renderer.wq_ta.info.addrof("unk_58")),
+
+ "2_ta_done": t(renderer2.wq_ta.info.pointers.addrof("gpu_doneptr")),
+ "2_ta_rptr": t(renderer2.wq_ta.info.pointers.addrof("gpu_rptr")),
+ "2_ta_busy": t(renderer2.wq_ta.info.addrof("busy")),
+ "2_ta_blk": t(renderer2.wq_ta.info.addrof("blocked_on_barrier")),
+ "2_ta_2c": t(renderer2.wq_ta.info.addrof("unk_2c")),
+ "2_ta_54": t(renderer2.wq_ta.info.addrof("unk_54")),
+
+ "3d_ts1": t(work.wc_3d.ts1._addr),
+ "3d_ts1b": t(workb.wc_3d.ts1._addr),
+ "3d_ts2": t(work.wc_3d.ts2._addr),
+ "3d_ts3": t(work.wc_3d.ts3._addr),
+ "ta_ts1": t(work.wc_ta.ts1._addr),
+ "ta_ts1b": t(workb.wc_ta.ts1._addr),
+ "ta_ts2": t(work.wc_ta.ts2._addr),
+ "ta_ts3": t(work.wc_ta.ts3._addr),
+ "2_3d_ts1": t(work2.wc_3d.ts1._addr),
+ "2_3d_ts1b": t(work2b.wc_3d.ts1._addr),
+ "2_3d_ts2": t(work2.wc_3d.ts2._addr),
+ "2_3d_ts3": t(work2.wc_3d.ts3._addr),
+ "2_ta_ts1": t(work2.wc_ta.ts1._addr),
+ "2_ta_ts1b": t(work2b.wc_ta.ts1._addr),
+ "2_ta_ts2": t(work2.wc_ta.ts2._addr),
+ "2_ta_ts3": t(work2.wc_ta.ts3._addr),
+
+ "ta_stamp1": t(renderer.stamp_ta1._addr),
+ "ta_stamp2": t(renderer.stamp_ta2._addr),
+ "3d_stamp1": t(renderer.stamp_3d1._addr),
+ "3d_stamp2": t(renderer.stamp_3d2._addr),
+
+ "2_ta_stamp1": t(renderer2.stamp_ta1._addr),
+ "2_ta_stamp2": t(renderer2.stamp_ta2._addr),
+ "2_3d_stamp1": t(renderer2.stamp_3d1._addr),
+ "2_3d_stamp2": t(renderer2.stamp_3d2._addr),
+}
+
+div=4
+ticks = 24000000 // div * 3
+
+la = GPIOLogicAnalyzer(u, regs=regs, cpu=analyzer_cpu, div=div)
+
+
+print("==========================================")
+print("## Poll prior to job start")
+print("==========================================")
+
+mon.poll()
+agx.poll_objects()
+
+print("==========================================")
+print("## Run")
+print("==========================================")
+
+la.start(ticks, bufsize=0x400000)
+renderer.run()
+
+print("==========================================")
+print("## After r1 start")
+print("==========================================")
+#agx.poll_objects()
+
+#time.sleep(0.1)
+#mon.poll()
+#time.sleep(0.15)
+#mon.poll()
+renderer2.run()
+
+print("==========================================")
+print("## After r2 start")
+print("==========================================")
+agx.poll_objects()
+
+#mon.poll()
+print("==========================================")
+print("## Waiting")
+print("==========================================")
+
+try:
+
+ #while not work.ev_3d.fired:
+ #agx.asc.work()
+ ##mon.poll()
+ #agx.poll_objects()
+ #agx.poll_channels()
+ #print("==========================================")
+ ##time.sleep(0.1)
+
+ #print("==========================================")
+ #print("## Ev1 Fired")
+ #print("==========================================")
+
+ while not work2.ev_3d.fired:
+ agx.asc.work()
+ #mon.poll()
+ agx.poll_objects()
+ agx.poll_channels()
+ print("==========================================")
+ #time.sleep(0.1)
+
+ print("==========================================")
+ print("## Ev2 Fired")
+ print("==========================================")
+
+ renderer.wait()
+ renderer2.wait()
+
+ agx.poll_objects()
+ #mon.poll()
+
+finally:
+ la.complete()
+ la.show()
+
+time.sleep(2)
+
diff --git a/tools/proxyclient/experiments/aic_test.py b/tools/proxyclient/experiments/aic_test.py
new file mode 100755
index 0000000..aad7e2c
--- /dev/null
+++ b/tools/proxyclient/experiments/aic_test.py
@@ -0,0 +1,411 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+from m1n1.setup import *
+from m1n1 import asm
+
+ULCON = 0x235200000
+UCON = 0x235200004
+UFCON = 0x235200008
+UTRSTAT = 0x235200010
+
+AIC = 0x23b100000
+
+AIC_RST = AIC + 0xc
+AIC_CFG = AIC + 0x10
+
+AIC_TB = 0x23b108000
+AIC_TGT_DST = AIC + 0x3000
+AIC_SW_GEN_SET = AIC + 0x4000
+AIC_SW_GEN_CLR = AIC + 0x4080
+AIC_MASK_SET = AIC + 0x4100
+AIC_MASK_CLR = AIC + 0x4180
+AIC_HW_STATE = AIC + 0x4200
+
+AIC_INTERRUPT_ACK = AIC + 0x2004
+AIC_IPI_SET = AIC + 0x2008
+AIC_IPI_CLR = AIC + 0x200c
+
+AIC_IPI_MASK_SET = AIC + 0x2024
+AIC_IPI_MASK_CLR = AIC + 0x2028
+
+daif = u.mrs(DAIF)
+print("DAIF: %x" % daif)
+daif &= ~0x3c0
+#daif |= 0x3c0
+u.msr(DAIF, daif)
+print("DAIF: %x" % u.mrs(DAIF))
+
+def cpoll():
+ mon.poll()
+ print("<")
+ mon.poll()
+ print(">")
+
+p.write32(AIC + 0xc, 1)
+p.write32(AIC + 0x10, 0xe0777971)
+p.write32(AIC + 0x18, 0)
+p.write32(AIC + 0x20, 0xffffffff)
+p.write32(AIC + 0x24, 0xffffffff)
+p.write32(AIC + 0x28, 0xffffffff)
+p.write32(AIC + 0x2c, 0xffffffff)
+p.write32(AIC + 0x30, 0xffffffff)
+p.write32(AIC + 0x34, 0xffffffff)
+p.write32(AIC + 0x38, 0xffffffff)
+p.write32(AIC + 0x3c, 0xffffffff)
+p.write32(AIC + 0x40, 0xffffffff)
+p.write32(AIC + 0x38, 0xffffffff)
+#p.write32(AIC + 0xc, 0)
+
+p.memset32(AIC_MASK_SET, 0xffffffff, 0x80)
+p.memset32(AIC_SW_GEN_CLR, 0xffffffff, 0x80)
+p.memset32(AIC_TGT_DST, 0x1, 0x1000)
+#p.memset32(AIC_MASK_CLR, 0xffffffff, 0x80)
+
+#p.write32(AIC + 0x10, 0xe0777971)
+
+mon.add(AIC + 0x0000, 0x1000)
+mon.add(AIC + 0x2080, 0x040)
+mon.add(AIC + 0x4000, 0x200)
+mon.add(AIC + 0x5000, 0x080)
+mon.add(AIC + 0x5080, 0x080)
+mon.add(AIC + 0x5100, 0x080)
+mon.add(AIC + 0x5180, 0x080)
+mon.add(AIC + 0x5200, 0x080)
+mon.add(AIC + 0x5280, 0x080)
+mon.add(AIC + 0x5300, 0x080)
+mon.add(AIC + 0x5380, 0x080)
+#mon.add(AIC + 0x3000, 0x400)
+#mon.add(AIC + 0x4000, 0x400)
+#mon.add(AIC + 0x8000, 0x20)
+#mon.add(AIC + 0x8030, 0xd0)
+#mon.add(0x235200000, 0x20)
+
+def test_ipi():
+ cpoll()
+
+ print("Set IPI")
+
+ p.write32(AIC_IPI_SET, 1)
+
+ cpoll()
+ cpoll()
+
+ print("Read ACK reg")
+
+ reason = p.read32(AIC_INTERRUPT_ACK)
+ print("reason: 0x%x" % reason)
+
+ cpoll()
+
+ print("Write reason")
+ p.write32(AIC_INTERRUPT_ACK, reason)
+
+ cpoll()
+
+ reason = p.read32(AIC_INTERRUPT_ACK)
+ print("reason: 0x%x" % reason)
+
+ cpoll()
+
+ print("Write ACK reg")
+ p.write32(AIC_INTERRUPT_ACK, reason)
+ cpoll()
+
+ print("Clear IPI")
+
+ p.write32(AIC_IPI_CLR, 1)
+ cpoll()
+
+ print("Read ACK reg")
+
+ reason = p.read32(AIC_INTERRUPT_ACK)
+
+ print("reason: 0x%x" % reason)
+
+ cpoll()
+
+ print("Write IPI ACK")
+
+ p.write32(AIC_IPI_MASK_CLR, 1)
+
+ cpoll()
+
+def test_timer():
+ cpoll()
+
+ freq = u.mrs(CNTFRQ_EL0)
+ print("Timer freq: %d" % freq)
+
+ #u.msr(CNTP_CTL_EL0, 0)
+ #u.msr(CNTP_TVAL_EL0, freq * 2)
+ #u.msr(CNTP_CTL_EL0, 1)
+ #u.msr(CNTV_CTL_EL0, 0)
+ #u.msr(CNTV_TVAL_EL0, freq * 2)
+ #u.msr(CNTV_CTL_EL0, 1)
+ #u.msr(CNTHV_CTL_EL2, 0)
+ #u.msr(CNTHV_TVAL_EL2, freq * 2)
+ #u.msr(CNTHV_CTL_EL2, 1)
+ u.msr(CNTHP_CTL_EL2, 0)
+ u.msr(CNTHP_TVAL_EL2, freq * 2)
+ u.msr(CNTHP_CTL_EL2, 1)
+
+ iface.ttymode()
+
+ #while True:
+ #p.nop()
+ #time.sleep(0.3)
+ #print(". %x" % u.mrs(CNTP_CTL_EL0))
+
+def get_irq_state(irq):
+ v = p.read32(AIC_HW_STATE + 4* (irq//32))
+ return bool(v & 1<<(irq%32))
+
+def test_uart_irq():
+ cpoll()
+ #p.memset32(AIC_MASK_CLR, 0xffffffff, 0x80)
+
+ print("cleanup")
+ p.write32(UCON, 5)
+ p.write32(UFCON, 0x11)
+ p.write32(UTRSTAT, 0xfff)
+
+ cpoll()
+
+ for irq in range(600, 610):
+ #print("S: ", get_irq_state(irq))
+ p.write32(AIC_SW_GEN_CLR + 4* (irq//32), 1<<(irq%32))
+ #print("S: ", get_irq_state(irq))
+ #print("a")
+ #print("S: ", get_irq_state(irq))
+ p.write32(AIC_MASK_CLR + 4* (irq//32), 1<<(irq%32))
+ #print("S: ", get_irq_state(irq))
+ #print("b")
+
+ irq = 605
+
+ cpoll()
+ print("a")
+ print("S: ", get_irq_state(irq))
+ print("ucon: %x" %p.read32(UCON))
+
+ TX_IRQ_EN = 0x1000
+
+ RX_IRQ_ENABLE = 0x20000
+ RX_IRQ_UNMASK = 0x10000
+
+ RX_IRQ_ENA = 0x20000
+ RX_IRQ_MASK = 0x4000 # defer?
+
+ code = u.malloc(0x1000)
+
+ c = asm.ARMAsm("""
+ ldr x1, =0x235200000
+
+ ldr x3, =0xc000000
+1:
+ subs x3, x3, #1
+ bne 1b
+ mov x2, 'A'
+ #str w2, [x1, #0x20]
+ #str w2, [x1, #0x20]
+ #str w2, [x1, #0x20]
+ #str w2, [x1, #0x20]
+ #str w2, [x1, #0x20]
+ #str w2, [x1, #0x20]
+ #str w2, [x1, #0x20]
+ #str w2, [x1, #0x20]
+ #str w2, [x1, #0x20]
+ #str w2, [x1, #0x20]
+ #str w2, [x1, #0x20]
+ #str w2, [x1, #0x20]
+ #str w2, [x1, #0x20]
+ #str w2, [x1, #0x20]
+ #str w2, [x1, #0x20]
+ #str w2, [x1, #0x20]
+ #str w2, [x1, #0x20]
+ #str w2, [x1, #0x20]
+ #str w2, [x1, #0x20]
+ #str w2, [x1, #0x20]
+ #str w2, [x1, #0x20]
+ #str w2, [x1, #0x20]
+ #str w2, [x1, #0x20]
+ #str w2, [x1, #0x20]
+ #str w2, [x1, #0x20]
+ #str w2, [x1, #0x20]
+ #str w2, [x1, #0x20]
+ #str w2, [x1, #0x20]
+ #str w2, [x1, #0x20]
+ #str w2, [x1, #0x20]
+ #str w2, [x1, #0x20]
+
+ mov x3, #0x3ff
+ str w3, [x1, #0x10]
+ #str w2, [x1, #0x20]
+ str w0, [x1, #4]
+ ldr w0, [x1, #0x10]
+
+ ldr x3, =0xc00000
+1:
+ subs x3, x3, #1
+ bne 1b
+
+ #mov x3, #0x3ff
+ #str w3, [x1, #0x10]
+ #ldr w2, [x1, #4]
+ #mov x2, #0x205
+ #str w2, [x1, #4]
+ #str w0, [x1, #4]
+ ##ldr w0, [x1, #0x10]
+
+ #ldr x3, =0xc00000
+#1:
+ #subs x3, x3, #1
+ #bne 1b
+
+ ldr w0, [x1, #0x10]
+ #mov w0, w2
+ ret
+""", code)
+ iface.writemem(code, c.data)
+ p.dc_cvau(code, len(c.data))
+ p.ic_ivau(code, len(c.data))
+
+ #RX_IRQ_
+
+ """
+UCON UTRSTAT
+00200 TX FIFO thresh IRQ delivery enable
+00080 0200 TX FIFO threshold IRQ unmask
+20000 0100 RX IRQ unmask
+10000 RX IRQ delivery enable
+"""
+
+ # edge triggered
+ TX_FIFO_THRESH_CROSSED_IRQ_UNMASK = 0x2000
+
+ TX_IRQ_UNMASK = 0x200
+ TX_EVENT_ENABLE = 0x80
+ RX_EVENT_ENABLE = 0x20000
+ RX_IRQ_UNMASK = 0x10000
+
+ #flags = 0x7ffc0
+ crash = 0x180000
+ no_irqs = 0x21c5c0
+ instant_irqs = 0x3a00
+ #flags = no_irqs | 0x0000
+ #flags = 0x2e5c0
+ #flags = 0x2000
+
+ #flags = 0x30000
+ #flags = 0x80
+ flags = 0x7ff80
+
+
+ val = flags | 0x005
+ #print("ucon<-%x" % val)
+ #p.write32(UCON, val)
+ p.write32(UTRSTAT, 0xfff)
+ print("utrstat=%x" % p.read32(UTRSTAT))
+ ret = p.call(code, val)
+ print("utrstat::%x" % ret)
+ print("utrstat=%x" % p.read32(UTRSTAT))
+ time.sleep(0.5)
+ iface.dev.write(b'1')
+ #print(iface.dev.read(1))
+ time.sleep(0.1)
+ print("ucon: %x" %p.read32(UCON))
+ print("delay")
+ try:
+ p.udelay(500000)
+ except:
+ pass
+ iface.dev.write(bytes(64))
+ p.nop()
+ print("ucon: %x" %p.read32(UCON))
+ print("S: ", get_irq_state(irq))
+
+ #while True:
+ #print("S: ", get_irq_state(irq))
+ #p.write32(UTRSTAT, 0xfff)
+ #print("utrstat=%x" % p.read32(UTRSTAT))
+ #print("ucon: %x" %p.read32(UCON))
+ #print(">S: ", get_irq_state(irq))
+ #p.write32(UCON, flags | 0x005)
+ #print(">ucon: %x" %p.read32(UCON))
+ #time.sleep(0.1)
+
+
+
+def test_smp_ipi():
+ p.smp_start_secondaries()
+
+ code = u.malloc(0x1000)
+
+ c = asm.ARMAsm("""
+#define sys_reg(op0, op1, CRn, CRm, op2) s##op0##_##op1##_c##CRn##_c##CRm##_##op2
+#define SYS_CYC_OVRD sys_reg(3, 5, 15, 5, 0)
+
+ msr DAIFClr, 7
+ ldr x1, =0x000000
+ msr SYS_CYC_OVRD, x1
+ mrs x0, SYS_CYC_OVRD
+ mov x1, #0x1000000
+1:
+ subs x1, x1, #1
+ mrs x0, HCR_EL2
+ bne 1b
+ ret
+""", code)
+
+ iface.writemem(code, c.data)
+ p.dc_cvau(code, len(c.data))
+ p.ic_ivau(code, len(c.data))
+
+ print("Enable IRQs on secondaries")
+ for i in range(1, 8):
+ ret = p.smp_call_sync(i, code)
+ print("0x%x"%ret)
+
+ #e0477971
+ #p.write32(AIC + 0x10, 0xe0777971)
+ #p.write32(AIC + 0x28, 0xffffffff)
+
+ cpoll()
+
+ print("Clear IPI")
+ p.write32(AIC_IPI_CLR, 0xffffffff)
+ p.write32(AIC_IPI_MASK_CLR, 0xffffffff)
+ for i in range(8):
+ p.write32(AIC_IPI_CLR+0x3000+i*0x80, 0xffffffff)
+ p.write32(AIC_IPI_MASK_CLR+0x3000+i*0x80, 0xffffffff)
+
+ cpoll()
+
+ print("Set IPI")
+ #p.write32(AIC_IPI_SET, 0x00000004)
+ #p.write32(AIC_IPI_SET, 0x00000000)
+
+ cpoll()
+ print("Clear IPI")
+ p.write32(AIC_IPI_CLR, 0xffffffff)
+ p.write32(AIC_IPI_MASK_CLR, 0xffffffff)
+ for i in range(8):
+ p.write32(AIC_IPI_CLR+0x3000+i*0x80, 1)
+ p.write32(AIC_IPI_MASK_CLR+0x3000+i*0x80, 1)
+
+def test_smp_affinity():
+ p.write32(AIC_TGT_DST, 0x6)
+ p.write32(AIC_TGT_DST+4, 0xfe)
+ p.write32(AIC_TGT_DST+8, 0xfe)
+ p.write32(AIC_TGT_DST+12, 0x6)
+ p.write32(AIC_SW_GEN_SET,0x8);
+ p.write32(AIC_MASK_CLR,0x8);
+
+#test_ipi()
+#test_timer()
+#test_uart_irq()
+test_smp_ipi()
+test_smp_affinity()
diff --git a/tools/proxyclient/experiments/amcc_err_handler.py b/tools/proxyclient/experiments/amcc_err_handler.py
new file mode 100644
index 0000000..935a99e
--- /dev/null
+++ b/tools/proxyclient/experiments/amcc_err_handler.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+from m1n1.setup import *
+
+#for i in (0x28e580350, 0x28e580328, 0x28e580380, 0x28e580378):
+ #p.write32(i, 0)
+
+sts = p.read32(0x20002100c)
+print(f"status: {sts:#x}")
+p.write32(0x200021010, 0)
+p.write32(0x20002100c, 0xfff)
+time.sleep(0.1)
+sts = p.read32(0x20002100c)
+print(f"status: {sts:#x}")
+
+print(f"ERRLOG0: {p.read32(0x20000070c):#x}")
+print(f"ERRLOG1: {p.read32(0x200000710):#x}")
+print(f"ERRLOG2: {p.read32(0x200000714):#x}")
+print(f"ERRLOG3: {p.read32(0x200000718):#x}")
+print(f"ERRLOG4: {p.read32(0x20000071c):#x}")
+
+p.write32(0x20000070c, 0xffffffff)
+
+#p.fb_shutdown()
+u.inst("tlbi vmalle1is")
+
+#p.memset32(fb, 0xffffffff, 3024 * 1964 * 4)
+#p.dc_cvac(fb, 3024 * 1964 * 4)
+
+#p.memset32(0x100_80000000, 0xffffffff, 0x80000000)
+
+#p.memcpy32(fb, fb + 0x1000, 0x800)
+#p.memset32(fb, 0xfffff, 3024 * 1964 * 4)
+#p.memset32(fb, 0xffffffff, 3024 * 1964 * 4)
+#p.memcpy32(0x100_80000000, fb + 0x1000, 0x1800)
+#p.read8(fb + 0x10)
+#p.write8(fb + 0x200, 0xdeadbeef)
+#p.memset32(fb, 0xfffffff, 3024 * 1964 * 4)
+
+#p.iodev_write(IODEV.FB, "test\n", 5)
diff --git a/tools/proxyclient/experiments/aop.py b/tools/proxyclient/experiments/aop.py
new file mode 100755
index 0000000..0dba36f
--- /dev/null
+++ b/tools/proxyclient/experiments/aop.py
@@ -0,0 +1,310 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+import struct
+import traceback
+from construct import *
+
+from m1n1.setup import *
+from m1n1.shell import run_shell
+from m1n1.hw.dart import DART, DARTRegs
+from m1n1.fw.asc import StandardASC, ASCDummyEndpoint
+from m1n1.fw.asc.base import *
+from m1n1.fw.aop import *
+from m1n1.fw.aop.ipc import *
+from m1n1.fw.afk.rbep import *
+from m1n1.fw.afk.epic import *
+
+# Set up a secondary proxy channel so that we can stream
+# the microphone samples
+p.usb_iodev_vuart_setup(p.iodev_whoami())
+p.iodev_set_usage(IODEV.USB_VUART, USAGE.UARTPROXY)
+
+p.pmgr_adt_clocks_enable("/arm-io/dart-aop")
+
+adt_dc = u.adt["/arm-io/aop/iop-aop-nub/aop-audio/dc-2400000"]
+
+pdm_config = Container(
+ unk1=2,
+ clockSource=u'pll ',
+ pdmFrequency=2400000,
+ unk3_clk=24000000,
+ unk4_clk=24000000,
+ unk5_clk=24000000,
+ channelPolaritySelect=256,
+ unk7=99,
+ unk8=1013248,
+ unk9=0,
+ ratios=Container(
+ r1=15,
+ r2=5,
+ r3=2,
+ ),
+ filterLengths=0x542c47,
+ coeff_bulk=120,
+ coefficients=GreedyRange(Int32sl).parse(adt_dc.coefficients),
+ unk10=1,
+ micTurnOnTimeMs=20,
+ unk11=1,
+ micSettleTimeMs=50,
+)
+
+decimator_config = Container(
+ latency=15,
+ ratios=Container(
+ r1=15,
+ r2=5,
+ r3=2,
+ ),
+ filterLengths=0x542c47,
+ coeff_bulk=120,
+ coefficients=GreedyRange(Int32sl).parse(adt_dc.coefficients),
+)
+
+class AFKEP_Hello(AFKEPMessage):
+ TYPE = 63, 48, Constant(0x80)
+ UNK = 7, 0
+
+class AFKEP_Hello_Ack(AFKEPMessage):
+ TYPE = 63, 48, Constant(0xa0)
+
+class EPICEndpoint(AFKRingBufEndpoint):
+ BUFSIZE = 0x1000
+
+ def __init__(self, *args, **kwargs):
+ self.seq = 0x0
+ self.wait_reply = False
+ self.ready = False
+ super().__init__(*args, **kwargs)
+
+ @msg_handler(0x80, AFKEP_Hello)
+ def Hello(self, msg):
+ self.rxbuf, self.rxbuf_dva = self.asc.ioalloc(self.BUFSIZE)
+ self.txbuf, self.txbuf_dva = self.asc.ioalloc(self.BUFSIZE)
+
+ self.send(AFKEP_Hello_Ack())
+
+ def handle_hello(self, hdr, sub, fd):
+ if sub.type != 0xc0:
+ return False
+
+ payload = fd.read()
+ name = payload.split(b"\0")[0].decode("ascii")
+ self.log(f"Hello! (endpoint {name})")
+ self.ready = True
+ return True
+
+ def handle_reply(self, hdr, sub, fd):
+ if self.wait_reply:
+ self.pending_call.read_resp(fd)
+ self.wait_reply = False
+ return True
+ return False
+
+ def handle_ipc(self, data):
+ fd = BytesIO(data)
+ hdr = EPICHeader.parse_stream(fd)
+ sub = EPICSubHeaderVer2.parse_stream(fd)
+
+ handled = False
+
+ if sub.category == EPICCategory.REPORT:
+ handled = self.handle_hello(hdr, sub, fd)
+ if sub.category == EPICCategory.REPLY:
+ handled = self.handle_reply(hdr, sub, fd)
+
+ if not handled and getattr(self, 'VERBOSE', False):
+ self.log(f"< 0x{hdr.channel:x} Type {hdr.type} Ver {hdr.version} Tag {hdr.seq}")
+ self.log(f" Len {sub.length} Ver {sub.version} Cat {sub.category} Type {sub.type:#x} Ts {sub.timestamp:#x}")
+ self.log(f" Unk1 {sub.unk1:#x} Unk2 {sub.unk2:#x}")
+ chexdump(fd.read())
+
+ def indirect(self, call, chan=0x1000000d, timeout=0.1):
+ tx = call.ARGS.build(call.args)
+ self.asc.iface.writemem(self.txbuf, tx[4:])
+
+ cmd = self.roundtrip(IndirectCall(
+ txbuf=self.txbuf_dva, txlen=len(tx) - 4,
+ rxbuf=self.rxbuf_dva, rxlen=self.BUFSIZE,
+ retcode=0,
+ ), category=EPICCategory.COMMAND, typ=call.TYPE)
+ fd = BytesIO()
+ fd.write(struct.pack("<I", cmd.rets.retcode))
+ fd.write(self.asc.iface.readmem(self.rxbuf, cmd.rets.rxlen))
+ fd.seek(0)
+ call.read_resp(fd)
+ return call
+
+ def roundtrip(self, call, chan=0x1000000d, timeout=0.3,
+ category=EPICCategory.NOTIFY, typ=None):
+ tx = call.ARGS.build(call.args)
+ fd = BytesIO()
+ fd.write(EPICHeader.build(Container(
+ channel=chan,
+ type=EPICType.NOTIFY,
+ version=2,
+ seq=self.seq,
+ )))
+ self.seq += 1
+ fd.write(EPICSubHeaderVer2.build(Container(
+ length=len(tx),
+ category=category,
+ type=typ or call.TYPE,
+ )))
+ fd.write(tx)
+
+ self.pending_call = call
+ self.wait_reply = True
+ self.send_ipc(fd.getvalue())
+
+ deadline = time.time() + timeout
+ while time.time() < deadline and self.wait_reply:
+ self.asc.work()
+ if self.wait_reply:
+ self.wait_reply = False
+ raise ASCTimeout("ASC reply timed out")
+
+ return call
+
+class SPUAppEndpoint(EPICEndpoint):
+ SHORT = "SPUAppep"
+
+class AccelEndpoint(EPICEndpoint):
+ SHORT = "accelep"
+
+class GyroEndpoint(EPICEndpoint):
+ SHORT = "gyroep"
+
+class UNK23Endpoint(EPICEndpoint):
+ SHORT = "unk23ep"
+
+class LASEndpoint(EPICEndpoint):
+ SHORT = "lasep"
+ #VERBOSE = True # <--- uncomment to see lid angle measurements
+
+class WakehintEndpoint(EPICEndpoint):
+ SHORT = "wakehintep"
+
+class UNK26Endpoint(EPICEndpoint):
+ SHORT = "unk26ep"
+
+class AudioEndpoint(EPICEndpoint):
+ SHORT = "audioep"
+
+
+class OSLogMessage(Register64):
+ TYPE = 63, 56
+
+class OSLog_Init(OSLogMessage):
+ TYPE = 63, 56, Constant(1)
+ UNK = 51, 0
+ DVA = 7, 0
+
+class AOPOSLogEndpoint(ASCBaseEndpoint):
+ BASE_MESSAGE = OSLogMessage
+ SHORT = "oslog"
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.started = False
+
+ @msg_handler(1, OSLog_Init)
+ def Init(self, msg):
+ self.iobuffer, self.iobuffer_dva = self.asc.ioalloc(0x1_0000)
+ self.send(OSLog_Init(DVA=self.iobuffer_dva//0x1000))
+ self.started = True
+ return True
+
+
+class AOPClient(StandardASC, AOPBase):
+ ENDPOINTS = {
+ 8: AOPOSLogEndpoint,
+
+ 0x20: SPUAppEndpoint,
+ 0x21: AccelEndpoint,
+ 0x22: GyroEndpoint,
+ 0x23: UNK23Endpoint,
+ 0x24: LASEndpoint,
+ 0x25: WakehintEndpoint,
+ 0x26: UNK26Endpoint,
+ 0x27: AudioEndpoint,
+ 0x28: EPICEndpoint,
+ 0x29: EPICEndpoint,
+ 0x2a: EPICEndpoint,
+ 0x2b: EPICEndpoint
+ }
+
+ def __init__(self, u, adtpath, dart=None):
+ node = u.adt[adtpath]
+ self.base = node.get_reg(0)[0]
+
+ AOPBase.__init__(self, u, node)
+ super().__init__(u, self.base, dart)
+
+p.dapf_init_all()
+
+dart = DART.from_adt(u, "/arm-io/dart-aop", iova_range=(0x2c000, 0x10_000_000))
+dart.initialize()
+dart.regs.TCR[0].set(BYPASS_DAPF=0, BYPASS_DART=0, TRANSLATE_ENABLE=1)
+dart.regs.TCR[7].set(BYPASS_DAPF=0, BYPASS_DART=0, TRANSLATE_ENABLE=1)
+dart.regs.TCR[15].val = 0x20100
+
+aop = AOPClient(u, "/arm-io/aop", dart)
+
+aop.update_bootargs({
+ 'p0CE': 0x20000,
+# 'laCn': 0x0,
+# 'tPOA': 0x1,
+})
+
+aop.verbose = 4
+
+def set_aop_audio_pstate(devid, pstate):
+ audep.roundtrip(SetDeviceProp(
+ devid=devid,
+ modifier=202,
+ data=Container(
+ devid=devid,
+ cookie=1,
+ target_pstate=pstate,
+ unk2=1,
+ )
+ )).check_retcode()
+
+try:
+ aop.boot()
+ for epno in range(0x20, 0x2c):
+ aop.start_ep(epno)
+
+ timeout = 10
+ while (not aop.audioep.ready) and timeout:
+ aop.work_for(0.1)
+ timeout -= 1
+
+ if not timeout:
+ raise Exception("Timed out waiting on audio endpoint")
+
+ print("Finished boot")
+
+ audep = aop.audioep
+
+ audep.roundtrip(AttachDevice(devid='pdm0')).check_retcode()
+ audep.indirect(SetDeviceProp(
+ devid='pdm0', modifier=200, data=pdm_config)
+ ).check_retcode()
+ audep.indirect(SetDeviceProp(
+ devid='pdm0', modifier=210, data=decimator_config)
+ ).check_retcode()
+ audep.roundtrip(AttachDevice(devid='hpai')).check_retcode()
+ audep.roundtrip(AttachDevice(devid='lpai')).check_retcode()
+ audep.roundtrip(SetDeviceProp(
+ devid='lpai', modifier=301, data=Container(unk1=7, unk2=7, unk3=1, unk4=7))
+ ).check_retcode()
+except KeyboardInterrupt:
+ pass
+except Exception:
+ print(traceback.format_exc())
+
+run_shell(locals(), poll_func=aop.work)
diff --git a/tools/proxyclient/experiments/audio_capture.py b/tools/proxyclient/experiments/audio_capture.py
new file mode 100755
index 0000000..d79cbd4
--- /dev/null
+++ b/tools/proxyclient/experiments/audio_capture.py
@@ -0,0 +1,132 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib
+import time
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+# audio_capture.py -- capture audio on jack microphone input (on M1 macs with cs42l83)
+#
+# sample usage with sox: (recoding can be loud!)
+#
+# ./audio_capture.py | sox -t raw -r 48000 -c 1 -e signed-int -b 32 -L - OUTPUT_FILE
+
+from m1n1.setup import *
+from m1n1.hw.dart import DART, DARTRegs
+from m1n1.hw.i2c import I2C
+from m1n1.hw.pmgr import PMGR
+from m1n1.hw.nco import NCO
+from m1n1.hw.admac import *
+from m1n1.hw.mca import *
+
+p.pmgr_adt_clocks_enable("/arm-io/i2c2")
+p.pmgr_adt_clocks_enable("/arm-io/admac-sio")
+p.pmgr_adt_clocks_enable("/arm-io/dart-sio")
+p.pmgr_adt_clocks_enable("/arm-io/mca-switch")
+p.pmgr_adt_clocks_enable("/arm-io/mca3")
+
+# reset AUDIO_P
+PS_AUDIO_P = PMGR(u).regs[0].PS4[10]
+PS_AUDIO_P.set(DEV_DISABLE=1)
+PS_AUDIO_P.set(RESET=1)
+PS_AUDIO_P.set(RESET=0)
+PS_AUDIO_P.set(DEV_DISABLE=0)
+
+dart_base, _ = u.adt["/arm-io/dart-sio"].get_reg(0)
+dart = DART(iface, DARTRegs(u, dart_base), util=u)
+dart.initialize()
+
+cl_no = 2
+
+admac = ADMAC(u, "/arm-io/admac-sio", dart, debug=True)
+dmachan = admac.chans[4*cl_no+1]
+dmachan.buswidth = E_BUSWIDTH.W_32BIT
+dmachan.framesize = E_FRAME.F_1_WORD
+
+nco = NCO(u, "/arm-io/nco")
+nco[cl_no].set_rate(6000000)
+nco[cl_no].enable()
+
+mca_switch1_base = u.adt["/arm-io/mca-switch"].get_reg(1)[0]
+mca_cl_base = u.adt["/arm-io/mca-switch"].get_reg(0)[0] + 0x4000*cl_no
+cl = MCACluster(u, mca_cl_base)
+
+regs, serdes = cl.regs, cl.rxa
+
+regs.SYNCGEN_STATUS.set(EN=0)
+regs.SYNCGEN_MCLK_SEL.val =(1 + cl_no)
+regs.SYNCGEN_HI_PERIOD.val = 0 # period minus one
+regs.SYNCGEN_LO_PERIOD.val = 0x7b # period minus one
+
+serdes.STATUS.set(EN=0)
+serdes.CONF.set(
+ NSLOTS=0,
+ SLOT_WIDTH=E_SLOT_WIDTH.W_32BIT,
+ BCLK_POL=1,
+ UNK1=1, UNK2=1,
+ SYNC_SEL=(1 + cl_no)
+)
+serdes.UNK1.val = 0x4
+
+serdes.BITDELAY.val = 1
+
+serdes.CHANMASK[0].val = 0xffff_ffff
+serdes.CHANMASK[1].val = 0xffff_fffe
+
+regs.PORT_ENABLES.set(CLOCK1=1, CLOCK2=1, DATA=0)
+regs.PORT_CLK_SEL.set(SEL=(cl_no + 1))
+regs.MCLK_STATUS.set(EN=1)
+regs.SYNCGEN_STATUS.set(EN=1)
+
+cs42l_addr = 0x48
+i2c2 = I2C(u, "/arm-io/i2c2")
+def cs42l_write(regaddr, val):
+ i2c2.write_reg(cs42l_addr, 0x0, [regaddr >> 8])
+ i2c2.write_reg(cs42l_addr, regaddr & 0xff, [val])
+
+p.write32(0x23d1f002c, 0x76a02)
+p.write32(0x23d1f002c, 0x76a03) # take jack codec out of reset
+
+cs42l_write(0x1009, 0x0) # FS_int = MCLK/250
+cs42l_write(0x1101, 0x7a) # power on
+cs42l_write(0x1103, 0x22) # power on ring sense
+cs42l_write(0x1107, 0x1) # SCLK present
+cs42l_write(0x1121, 0xa6) # Headset Switch Control
+cs42l_write(0x1129, 0x1) # Headset Clamp Disable
+cs42l_write(0x1205, 0x7c) # FSYNC period
+cs42l_write(0x1207, 0x20) # ASP Clock Configuration
+cs42l_write(0x1208, 0x12) # BITDELAY = 1
+cs42l_write(0x120c, 0x1) # SCLK_PREDIV = div-by-2
+cs42l_write(0x150a, 0x55) # PLL
+cs42l_write(0x151b, 0x1) # PLL
+cs42l_write(0x1501, 0x1) # power on PLL
+cs42l_write(0x1b70, 0xc3) # HSBIAS sense
+cs42l_write(0x1b71, 0xe0) # v-- headset
+cs42l_write(0x1b73, 0xc0)
+cs42l_write(0x1b74, 0x1f)
+cs42l_write(0x1b75, 0xb6)
+cs42l_write(0x1b76, 0x8f)
+cs42l_write(0x1b79, 0x0)
+cs42l_write(0x1b7a, 0xfc)
+cs42l_write(0x1c03, 0xc0) # HSBIAS
+cs42l_write(0x2506, 0xc) # ASP TX samp. rate
+cs42l_write(0x2609, 0x4c) # SRC output samp. rate
+cs42l_write(0x2901, 0x1) # ASP TX enable & size
+cs42l_write(0x2902, 0x1) # ASP TX channel enable
+
+time.sleep(0.01)
+
+cs42l_write(0x1201, 0x1) # transition to PLL clock
+
+# drain garbled samples (why are they garbled? i am not sure)
+time.sleep(0.5)
+
+dmachan.submit(buflen=0x4000)
+dmachan.enable()
+
+p.write32(mca_switch1_base + 0x8000*cl_no, 0x24800)
+serdes.STATUS.set(EN=1)
+
+while True:
+ while dmachan.can_submit():
+ dmachan.submit(buflen=0x4000)
+ sys.stdout.buffer.write(dmachan.poll())
diff --git a/tools/proxyclient/experiments/chickens.py b/tools/proxyclient/experiments/chickens.py
new file mode 100755
index 0000000..bc2b6ec
--- /dev/null
+++ b/tools/proxyclient/experiments/chickens.py
@@ -0,0 +1,108 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+from m1n1.setup import *
+
+sys_regs = dict([
+ ("HID0", (3, 0, 15, 0, 0)),
+ ("HID1", (3, 0, 15, 1, 0)),
+ ("EHID20", (3, 0, 15, 1, 2)),
+ ("HID2", (3, 0, 15, 2, 0)),
+ ("HID3", (3, 0, 15, 3, 0)),
+ ("HID4", (3, 0, 15, 4, 0)),
+ ("EHID4", (3, 0, 15, 4, 1)),
+ ("HID5", (3, 0, 15, 5, 0)),
+ ("HID6", (3, 0, 15, 6, 0)),
+ ("HID7", (3, 0, 15, 7, 0)),
+ ("HID8", (3, 0, 15, 8, 0)),
+ ("HID9", (3, 0, 15, 9, 0)),
+ ("EHID9", (3, 0, 15, 9, 1)),
+ ("HID10", (3, 0, 15, 10, 0)),
+ ("EHID10", (3, 0, 15, 10, 1)),
+ ("HID11", (3, 0, 15, 11, 0)),
+])
+
+CYC_OVRD = (3, 5, 15, 5, 0)
+CYC_CFG = (3, 5, 15, 4, 0)
+
+L2C_ERR_STS = (3, 3, 15, 8, 0)
+
+s3_6_c15_c1_0 = (3, 6, 15, 1, 0)
+s3_6_c15_c1_6 = (3, 6, 15, 1, 6)
+
+s3_4_c15_c1_4 = (3, 4, 15, 1, 4)
+s3_4_c15_c5_0 = (3, 4, 15, 5, 0)
+
+h13e_chickenbits = [
+ ("EHID4", 0x100000000800, 0),
+ ("HID5", 0x2000000000000000, 0),
+ ("EHID10", 0x2000100000000, 0),
+ ("EHID20", 0x100, 0),
+ ("EHID9", 0, 0x20),
+ ("EHID20", 0x8000, 0),
+ ("EHID20", 0x10000, 0),
+ ("EHID20", 0x600000, 0),
+
+]
+
+tlbi_vmalle1 = 0xd508871f
+
+def h13e_init():
+ mpidr = u.mrs(MPIDR_EL1)
+ print("mpidr = 0x%x" % mpidr)
+
+ #print("OSLAR")
+ #u.msr(OSLAR_EL1, 0)
+ #print("s3_6_c15_c1_0")
+ #u.msr(s3_6_c15_c1_0, 1)
+ #print("tlbi_vmalle1")
+ #u.inst(tlbi_vmalle1)
+
+ ## This looks like APRR stuff?
+ #v = u.mrs(s3_6_c15_c1_6)
+ #print("s3_6_c15_c1_6 == 0x%x" % v)
+ #v = 0x2020a505f020f0f0
+ #print("s3_6_c15_c1_6 <= 0x%x" % v)
+ #u.msr(s3_6_c15_c1_6, v)
+
+ #u.msr(s3_6_c15_c1_0, 0)
+
+ for reg, setb, clearb in h13e_chickenbits:
+ v = u.mrs(sys_regs[reg])
+ print("%r == 0x%x" % (reg, v))
+ v &= ~clearb
+ v |= setb
+ print("%r <= 0x%x" % (reg, v))
+ u.msr(sys_regs[reg], v)
+
+ v = u.mrs(s3_4_c15_c5_0)
+ print("s3_4_c15_c5_0 == 0x%x" % v)
+ print("s3_4_c15_c5_0 <= 0x%x" % (mpidr & 0xff))
+ u.msr(s3_4_c15_c5_0, mpidr & 0xff)
+
+ u.msr(s3_4_c15_c1_4, 0x100)
+
+ v = u.mrs(CYC_OVRD)
+ print("CYC_OVRD == 0x%x" % v)
+ v &= ~0xf00000
+ print("CYC_OVRD <= 0x%x" % v)
+ u.msr(CYC_OVRD, v)
+
+ v = u.mrs(ACTLR_EL1)
+ print("ACTLR_EL1 == 0x%x" % v)
+ v |= 0x200
+ print("ACTLR_EL1 <= 0x%x" % v)
+ u.msr(ACTLR_EL1, v)
+
+ v = u.mrs(CYC_CFG)
+ print("CYC_CFG == 0x%x" % v)
+ v |= 0xc
+ print("CYC_CFG <= 0x%x" % v)
+ u.msr(CYC_CFG, v)
+
+ print("L2C_ERR_STS = %x" % u.mrs(L2C_ERR_STS))
+ u.msr(L2C_ERR_STS, 0)
+
+h13e_init()
diff --git a/tools/proxyclient/experiments/cpu_pstate_latencies.py b/tools/proxyclient/experiments/cpu_pstate_latencies.py
new file mode 100755
index 0000000..0acfc28
--- /dev/null
+++ b/tools/proxyclient/experiments/cpu_pstate_latencies.py
@@ -0,0 +1,239 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib, time
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+from m1n1.setup import *
+from m1n1 import asm
+
+p.smp_start_secondaries()
+
+tfreq = u.mrs(CNTFRQ_EL0)
+
+TEST_CPUS = [1, 4]
+
+CLUSTER_PSTATE = 0x20020
+CLUSTER_STATUS = 0x20050
+
+if u.adt["/chosen"].chip_id == 0x8103:
+ CREG = [
+ 0x210e00000,
+ 0x211e00000,
+ ]
+
+ MAX_PSTATE = [5, 15]
+
+elif u.adt["/chosen"].chip_id == 0x8112:
+ CREG = [
+ 0x210e00000,
+ 0x211e00000,
+ ]
+
+ MAX_PSTATE = [7, 17]
+
+code = u.malloc(0x1000)
+
+util = asm.ARMAsm(f"""
+bench:
+ mrs x1, CNTPCT_EL0
+1:
+ sub x0, x0, #1
+ cbnz x0, 1b
+
+ mrs x2, CNTPCT_EL0
+ sub x0, x2, x1
+ ret
+
+signal_and_write:
+ sev
+ mrs x2, CNTPCT_EL0
+ add x2, x2, #0x800
+1:
+ mrs x3, CNTPCT_EL0
+ sub x4, x3, x2
+ cbnz x4, 1b
+ str x1, [x0]
+ mov x0, x3
+ ret
+
+timelog:
+ mrs x2, s3_1_c15_c0_0 /* SYS_IMP_APL_PMCR0 */
+ orr x2, x2, #1
+ msr s3_1_c15_c0_0, x2
+ mov x2, #0xffffffffffffffff
+ msr s3_1_c15_c1_0, x2
+ isb
+ wfe
+1:
+ mrs x2, CNTPCT_EL0
+ mrs x3, s3_2_c15_c0_0
+ isb
+ stp x2, x3, [x0], #16
+ mov x4, #0x40
+2:
+ sub x4, x4, #1
+ cbnz x4, 2b
+ sub x1, x1, #1
+ cbnz x1, 1b
+
+ ret
+""", code)
+iface.writemem(code, util.data)
+p.dc_cvau(code, len(util.data))
+p.ic_ivau(code, len(util.data))
+
+def bench_cpu(idx, loops=10000000):
+ if idx == 0:
+ elapsed = p.call(util.bench, loops) / tfreq
+ else:
+ elapsed = p.smp_call_sync(idx, util.bench, loops) / tfreq
+ if elapsed == 0:
+ return 0
+ mhz = (loops / elapsed) / 1000000
+ return mhz
+
+def set_pstate(cluster, pstate):
+ p.mask64(CREG[cluster] + CLUSTER_PSTATE, 0x1f01f, (1<<25) | pstate | (pstate << 12))
+
+print()
+
+LOG_ITERS = 10000
+logbuf = u.malloc(LOG_ITERS * 16)
+
+def bench_latency(cluster, cpu, from_pstate, to_pstate, verbose=False):
+ set_pstate(cluster, from_pstate)
+ bench_cpu(cpu)
+
+ p.smp_call(cpu, util.timelog, logbuf, LOG_ITERS)
+ psreg = (p.read64(CREG[cluster] + CLUSTER_PSTATE) & ~0x1f001f) | (1<<25) | to_pstate | (to_pstate << 12)
+ tval = p.call(util.signal_and_write, CREG[cluster] + CLUSTER_PSTATE, psreg)
+ p.smp_wait(cpu)
+
+ logdata = iface.readmem(logbuf, LOG_ITERS * 16)
+ lts, lcyc = None, None
+
+ log = []
+ for i in range(LOG_ITERS):
+ ts, cyc = struct.unpack("<QQ", logdata [i*16:i*16+16])
+ log.append((ts, cyc))
+
+ off = 256
+
+ ts_0, cyc_0 = log[off]
+ ts_e, cyc_e = log[-1]
+ f_init = None
+ f_end = None
+ lts, lcyc = ts_0, cyc_0
+
+ inc = to_pstate > from_pstate
+
+ blip = 0
+ cnt = dts_sum = 0
+ for i in range(off, len(log)):
+ ts, cyc = log[i]
+ dts = ts - lts
+ dcyc = cyc - lcyc
+
+ cnt += 1
+ dts_sum += dts
+
+ blip = max(blip, dts)
+
+ if f_init is None and ts > tval:
+ tidx = i
+ f_init = (lcyc - cyc_0) / (lts - ts_0) * tfreq / 1000000
+ dts_init = dts_sum / cnt
+ if f_end is None and ts > (tval + ts_e) / 2:
+ f_end = (cyc_e - cyc) / (ts_e - ts) * tfreq / 1000000
+ cnt = dts_sum = 0
+
+ #if lts is not None:
+ #print(f"{i}: {ts}: {cyc} ({ts-lts}: {cyc-lcyc})")
+ #else:
+ #print(f"{i}: {ts}: {cyc}")
+ lts, lcyc = ts, cyc
+
+ dts_end = dts_sum / cnt
+
+ window = 32
+
+ if verbose:
+ print(f"Triggered at {tval}")
+
+ thresh = 2/ (1/f_init + 1/f_end)
+
+ for i in range(tidx, LOG_ITERS - window - 1):
+ ts0, cyc0 = log[i - window]
+ ts1, cyc1 = log[i + window]
+ f = (cyc1 - cyc0) / (ts1 - ts0) * tfreq / 1000000
+ if inc and (f > thresh) or ((not inc) and f < thresh):
+ tts = log[i][0]
+ tidx = i
+ if verbose:
+ print(f"Frequency transition at #{i} {tts}")
+ break
+
+ if verbose:
+ print(f"Initial frequency: {f_init:.2f}")
+ print(f"Final frequency: {f_end:.2f}")
+ print(f"Threshold: {thresh:.2f}")
+
+ for i in range(max(window, tidx - 10 * window), tidx + 10 * window):
+ ts0, cyc0 = log[i - window]
+ ts1, cyc1 = log[i + window]
+ lts, lcyc = log[i - 1]
+ ts, cyc = log[i]
+ f = (cyc1 - cyc0) / (ts1 - ts0) * tfreq / 1000000
+ print(f"{i}: {ts}: {cyc} ({ts-lts}: {cyc-lcyc}): {f:.2f}")
+
+ blip -= min(dts_init, dts_end)
+
+ return (tts - tval) / tfreq * 1000000000, blip / tfreq * 1000000000
+
+for cluster, creg in enumerate(CREG):
+ cpu = TEST_CPUS[cluster]
+
+ freqs = []
+
+ print(f"#### Cluster {cluster} ####")
+ print(" P-States:")
+ print(" ", end="")
+ for pstate in range(MAX_PSTATE[cluster] + 1):
+ set_pstate(cluster, pstate)
+ freq = int(round(bench_cpu(cpu)))
+ freqs.append(freq)
+ print(f"{pstate}:{freq}MHz", end=" ")
+ print()
+ print()
+
+ print(" To-> |", end="")
+ for to_pstate in range(1, MAX_PSTATE[cluster] + 1):
+ print(f" {freqs[to_pstate]:7d} |", end="")
+ print()
+ print(" From |", end="")
+ for to_pstate in range(1, MAX_PSTATE[cluster] + 1):
+ print(f"---------+", end="")
+ print()
+
+ maxblip = 0
+
+ for from_pstate in range(1, MAX_PSTATE[cluster] + 1):
+ print(f" {freqs[from_pstate]:4d} |", end="")
+ for to_pstate in range(1, MAX_PSTATE[cluster] + 1):
+ if from_pstate == to_pstate:
+ print(f" ******* |", end="")
+ continue
+ lat, blip = bench_latency(cluster, cpu, from_pstate, to_pstate)
+ print(f" {lat:7.0f} |", end="")
+ maxblip = max(maxblip, blip)
+ print()
+
+ print()
+ print(f"Maximum execution latency spike: {maxblip:.0f} ns")
+ print()
+
+print()
+
+#bench_latency(1, TEST_CPUS[1], 15, 14, True)
+
+
diff --git a/tools/proxyclient/experiments/cpu_pstates.py b/tools/proxyclient/experiments/cpu_pstates.py
new file mode 100755
index 0000000..7403775
--- /dev/null
+++ b/tools/proxyclient/experiments/cpu_pstates.py
@@ -0,0 +1,150 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib, time
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+from m1n1.setup import *
+from m1n1 import asm
+
+LOOPS = 10000000
+freq = u.mrs(CNTFRQ_EL0)
+
+CREG = [
+ 0x210e00000,
+ 0x211e00000,
+]
+
+CLUSTER_PSTATE = 0x20020
+
+# e-core pstates
+# 600 972 1332 1704 2064
+# p-core pstates
+# 600 828 1056 1284 1500 1728 1956 2184 2388 2592 2772 2988 3096 3144 3204
+
+code = u.malloc(0x1000)
+
+util = asm.ARMAsm("""
+bench:
+ mrs x1, CNTPCT_EL0
+1:
+ sub x0, x0, #1
+ cbnz x0, 1b
+
+ mrs x2, CNTPCT_EL0
+ sub x0, x2, x1
+ ret
+""", code)
+iface.writemem(code, util.data)
+p.dc_cvau(code, len(util.data))
+p.ic_ivau(code, len(util.data))
+
+def bench_cpu(idx):
+ if idx == 0:
+ elapsed = p.call(util.bench, LOOPS) / freq
+ else:
+ elapsed = p.smp_call_sync(idx, util.bench, LOOPS) / freq
+ if elapsed == 0:
+ return 0
+ mhz = (LOOPS / elapsed) / 1000000
+ return mhz
+
+print()
+
+e_pstate = p.read64(CREG[0] + CLUSTER_PSTATE)
+p_pstate = p.read64(CREG[1] + CLUSTER_PSTATE)
+
+print(f"E-Core pstate: {e_pstate:x}")
+print(f"P-Core pstate: {p_pstate:x}")
+
+#for cluster in range(2):
+ #print(f"Initializing cluster {cluster} (early)")
+
+ #p.write64(CREG[cluster] + 0x20660, 0x1000000015)
+ #p.write64(CREG[cluster] + 0x48000, 0)
+ #p.write64(CREG[cluster] + 0x48080, 0xa000000000000000)
+
+ #p.clear64(CREG[cluster] + CLUSTER_PSTATE, 1<<22)
+
+#p.set32(PMGR + 0x48000, 1)
+#p.set32(PMGR + 0x48c00, 1)
+#p.set32(PMGR + 0x48800, 1)
+#p.set32(PMGR + 0x48400, 1)
+
+CLUSTER_DVMR = 0x206b8
+CLUSTER_LIMIT2 = 0x40240
+CLUSTER_LIMIT3 = 0x40250
+CLUSTER_LIMIT1 = 0x48400
+
+PMGR_CPUGATING = 0x1c080
+CLUSTER_CTRL = 0x440f8
+CLUSTER_PSCTRL = 0x200f8
+
+for cluster in range(2):
+ print(f"Initializing cluster {cluster}")
+ ena = (1<<63)
+ val = p.read64(CREG[cluster] + CLUSTER_DVMR)
+ if cluster == 1:
+ ena |= (1<<32) | (1<<31)
+ if (val & ena) != ena:
+ print(f"DVMR: {val:#x} -> {val|ena:#x}")
+ p.set64(CREG[cluster] + CLUSTER_DVMR, ena) # CLUSTER_DVMR
+
+ #p.set64(CREG[cluster] + CLUSTER_LIMIT1, 1<<63)
+ #p.clear64(CREG[cluster] + CLUSTER_LIMIT2, 1<<63)
+ #p.set64(CREG[cluster] + CLUSTER_LIMIT3, 1<<63)
+
+ #p.set64(CREG[cluster] + CLUSTER_PSTATE, 0)
+
+ #p.set32(PMGR + PMGR_CPUGATING + 8 * cluster, 1<<31)
+
+ #p.write64(CREG[cluster] + CLUSTER_CTRL, 1)
+
+ #p.set64(CREG[cluster] + CLUSTER_PSCTRL, 1<<40)
+
+ #pstate = p.read64(CREG[cluster] + CLUSTER_PSTATE) & 0xf
+
+p.smp_start_secondaries()
+
+print("== Initial CPU frequencies ==")
+
+for cpu in range(8):
+ print(f"CPU {cpu}: {bench_cpu(cpu):.2f} MHz")
+
+def set_pstate(cluster, pstate):
+ # This really seems to be all that's needed
+
+ p.mask64(CREG[cluster] + CLUSTER_PSTATE, 0xf00f, (1<<25) | pstate | (pstate << 12))
+
+ # Optionally, adjust MCC performance in higher p-core pstates
+ if cluster == 1:
+ if pstate > 8:
+ p0, p1 = 0x133, 0x55555340
+ else:
+ p0, p1 = 0x813057f, 0x1800180
+
+ for lane in range(8):
+ p.write32(0x200200dc4 + lane * 0x40000, p0)
+ p.write32(0x200200dbc + lane * 0x40000, p1)
+
+ # This seems to be about notifying PMP
+ #p.write32(0x23b738004 + cluster*4, pstate)
+ #p.write32(0x23bc34000, 1 << cluster)
+
+set_pstate(1, 15)
+
+e_pstate = p.read64(CREG[0] + CLUSTER_PSTATE)
+p_pstate = p.read64(CREG[1] + CLUSTER_PSTATE)
+
+print(f"E-Core pstate: {e_pstate:x}")
+print(f"P-Core pstate: {p_pstate:x}")
+
+time.sleep(0.5)
+
+print("== Final CPU frequencies ==")
+
+#elapsed = p.smp_call(7, util.bench, 80000000)
+
+for cpu in range(8):
+ print(f"CPU {cpu}: {bench_cpu(cpu):.2f} MHz")
+
+#elapsed = p.smp_wait(7)
diff --git a/tools/proxyclient/experiments/dart_dump.py b/tools/proxyclient/experiments/dart_dump.py
new file mode 100755
index 0000000..f6e0e08
--- /dev/null
+++ b/tools/proxyclient/experiments/dart_dump.py
@@ -0,0 +1,19 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+import struct
+
+from m1n1.setup import *
+from m1n1 import asm
+from m1n1.hw.dart import DART
+
+if len(sys.argv) > 1:
+ dart_name = sys.argv[1]
+else:
+ dart_name = "dart-disp0"
+
+dart = DART.from_adt(u, "arm-io/" + dart_name)
+dart.dump_all()
+dart.dart.regs.dump_regs()
diff --git a/tools/proxyclient/experiments/dcp.py b/tools/proxyclient/experiments/dcp.py
new file mode 100755
index 0000000..93d6167
--- /dev/null
+++ b/tools/proxyclient/experiments/dcp.py
@@ -0,0 +1,351 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+import struct
+from construct import *
+
+from m1n1.setup import *
+from m1n1.shell import run_shell
+from m1n1 import asm
+from m1n1.hw.dart import DART, DARTRegs
+from m1n1.fw.dcp.client import DCPClient
+from m1n1.fw.dcp.manager import DCPManager
+from m1n1.fw.dcp.ipc import ByRef
+from m1n1.proxyutils import RegMonitor
+
+disp_name = "/arm-io/disp0"
+
+external = hasattr(u.adt[disp_name], "external") and u.adt[disp_name].external != 0
+compat = u.adt[disp_name].compatible[0].split(",")[-1]
+
+mon = RegMonitor(u)
+
+if compat == 't8103':
+ #mon.add(0x230000000, 0x18000)
+ #mon.add(0x230018000, 0x4000)
+ #mon.add(0x230068000, 0x8000)
+ #mon.add(0x2300b0000, 0x8000)
+ #mon.add(0x2300f0000, 0x4000)
+ #mon.add(0x230100000, 0x10000)
+ #mon.add(0x230170000, 0x10000)
+ #mon.add(0x230180000, 0x1c000)
+ #mon.add(0x2301a0000, 0x10000)
+ #mon.add(0x2301d0000, 0x4000)
+ #mon.add(0x230230000, 0x10000)
+ #mon.add(0x23038c000, 0x10000)
+ #mon.add(0x230800000, 0x10000)
+ #mon.add(0x230840000, 0xc000)
+ #mon.add(0x230850000, 0x2000)
+ ##mon.add(0x230852000, 0x5000) # big curve / gamma table
+ #mon.add(0x230858000, 0x18000)
+ #mon.add(0x230870000, 0x4000)
+ #mon.add(0x230880000, 0x8000)
+ #mon.add(0x230894000, 0x4000)
+ #mon.add(0x2308a8000, 0x8000)
+ #mon.add(0x2308b0000, 0x8000)
+ #mon.add(0x2308f0000, 0x4000)
+ ##mon.add(0x2308fc000, 0x4000) # stats / RGB color histogram
+ #mon.add(0x230900000, 0x10000)
+ #mon.add(0x230970000, 0x10000)
+ #mon.add(0x230980000, 0x10000)
+ #mon.add(0x2309a0000, 0x10000)
+ #mon.add(0x2309d0000, 0x4000)
+ #mon.add(0x230a30000, 0x20000)
+ #mon.add(0x230b8c000, 0x10000)
+ #mon.add(0x231100000, 0x8000)
+ #mon.add(0x231180000, 0x4000)
+ #mon.add(0x2311bc000, 0x10000)
+ #mon.add(0x231300000, 0x8000)
+ ##mon.add(0x23130c000, 0x4000) # - DCP dart
+ #mon.add(0x231310000, 0x8000)
+ #mon.add(0x231340000, 0x8000)
+ ##mon.add(0x231800000, 0x8000) # breaks DCP
+ ##mon.add(0x231840000, 0x8000) # breaks DCP
+ ##mon.add(0x231850000, 0x8000) # something DCP?
+ ##mon.add(0x231920000, 0x8000) # breaks DCP
+ ##mon.add(0x231960000, 0x8000) # breaks DCP
+ ##mon.add(0x231970000, 0x10000) # breaks DCP
+ ##mon.add(0x231c00000, 0x10000) # DCP mailbox
+
+ mon.add(0x230845840, 0x40) # error regs
+
+def get_color_mode(mgr):
+ best_id = None
+ best_score = -1
+ for mode in mgr.dcpav_prop['ColorElements']:
+ if mode['IsVirtual']:
+ continue
+ if mode['Depth'] != 8:
+ continue
+ if mode['Score'] > best_score:
+ best_score = mode['Score']
+ best_id = mode['ID']
+ return best_id
+
+def get_timing_mode(mgr):
+ best_id = None
+ best_score = -1
+ for mode in mgr.dcpav_prop['TimingElements']:
+ if mode['IsVirtual']:
+ continue
+ if int(mode['Score']) > best_score:
+ best_score = int(mode['Score'])
+ best_id = int(mode['ID'])
+ return best_id
+
+mon.poll()
+
+dart = DART.from_adt(u, "arm-io/dart-dcp")
+disp_dart = DART.from_adt(u, "arm-io/dart-disp0")
+
+print("DCP DART:")
+dart.regs.dump_regs()
+print("DISP DART:")
+disp_dart.regs.dump_regs()
+
+dcp_addr = u.adt["arm-io/dcp"].get_reg(0)[0]
+dcp = DCPClient(u, dcp_addr, dart, disp_dart)
+dcp.dva_offset = getattr(u.adt["/arm-io/dcp"][0], "asc_dram_mask", 0)
+
+dcp.start()
+dcp.start_ep(0x37)
+dcp.dcpep.initialize()
+
+mgr = DCPManager(dcp.dcpep, compat)
+
+mon.poll()
+
+mgr.start_signal()
+
+mon.poll()
+
+mgr.get_color_remap_mode(6)
+mgr.enable_disable_video_power_savings(0)
+
+mgr.update_notify_clients_dcp([0,0,0,0,0,0,1,1,1,0,1,1,1,1])
+mgr.first_client_open()
+print(f"keep on: {mgr.isKeepOnScreen()}")
+print(f"main display: {mgr.is_main_display()}")
+assert mgr.setPowerState(1, False, ByRef(0)) == 0
+
+mon.poll()
+
+if external:
+ assert mgr.set_display_device(2) == 0
+else:
+ assert mgr.set_display_device(0) == 2
+assert mgr.set_parameter_dcp(14, [0], 1) == 0
+
+color_mode = get_color_mode(mgr)
+timing_mode = get_timing_mode(mgr)
+mgr.SetDigitalOutMode(color_mode, timing_mode)
+mon.poll()
+
+while mgr.iomfb_prop['DPTimingModeId'] != timing_mode:
+ print("Try re-setting mode")
+ mgr.SetDigitalOutMode(color_mode, timing_mode)
+ mon.poll()
+
+if external:
+ assert mgr.set_display_device(2) == 0
+else:
+ assert mgr.set_display_device(0) == 2
+assert mgr.set_parameter_dcp(14, [0], 1) == 0
+
+t = ByRef(b"\x00" * 0xc0c)
+assert mgr.get_gamma_table(t) == 2
+assert mgr.set_contrast(0) == 0
+assert mgr.setBrightnessCorrection(65536) == 0
+
+if external:
+ assert mgr.set_display_device(2) == 0
+else:
+ assert mgr.set_display_device(0) == 2
+assert mgr.set_parameter_dcp(14, [0], 1) == 0
+
+mon.poll()
+
+swapid = ByRef(0)
+
+def start():
+ # arg: IOUserClient
+ ret = mgr.swap_start(swapid, {
+ "addr": 0xFFFFFE1667BA4A00,
+ "unk": 0,
+ "flag1": 0,
+ "flag2": 1
+ })
+ assert ret == 0
+ print(f"swap ID: {swapid.val:#x}")
+
+start()
+
+mgr.set_matrix(9, [[1<<32, 0, 0],
+ [0, 1<<32, 0],
+ [0, 0, 1<<32]])
+mgr.setBrightnessCorrection(65536)
+mgr.set_parameter_dcp(3, [65536], 1)
+mgr.set_parameter_dcp(6, [65536], 1)
+
+width = mgr.display_width()
+height = mgr.display_height()
+
+surface_id = 3
+
+swap_rec = Container(
+ flags1 = 0x861202,
+ flags2 = 0x04,
+ swap_id = swapid.val,
+ surf_ids = [surface_id, 0, 0, 0],
+ src_rect = [[0, 0, width, height],[0,0,0,0],[0,0,0,0],[0,0,0,0]],
+ surf_flags = [1, 0, 0, 0],
+ surf_unk = [0, 0, 0, 0],
+ dst_rect = [[0, 0, width, height],[0,0,0,0],[0,0,0,0],[0,0,0,0]],
+ swap_enabled = 0x80000007,
+ swap_completed = 0x80000007,
+ bl_unk = 0x1,
+ bl_val = 0x58f058d0, # ~99 nits
+ bl_power = 0x40,
+)
+
+surf = Container(
+ is_tiled = False,
+ unk_1 = False,
+ unk_2 = False,
+ plane_cnt = 0,
+ plane_cnt2 = 0,
+ format = "BGRA",
+ xfer_func = 13,
+ colorspace = 1,
+ stride = width * 4,
+ pix_size = 4,
+ pel_w = 1,
+ pel_h = 1,
+ offset = 0,
+ width = width,
+ height = height,
+ buf_size = width * height * 4,
+ surface_id = surface_id,
+ has_comp = True,
+ has_planes = True,
+ has_compr_info = False,
+ unk_1f5 = 0,
+ unk_1f9 = 0,
+)
+
+compressed_surf = Container(
+ is_tiled = False,
+ unk_1 = False,
+ unk_2 = False,
+ plane_cnt = 2,
+ plane_cnt2 = 2,
+ format = 'b3a8',
+ unk_f = 0x00000000,
+ xfer_func = 13,
+ colorspace = 2,
+ stride = width,
+ pix_size = 1,
+ pel_w = 1,
+ pel_h = 1,
+ offset = 0,
+ width = width,
+ height = height,
+ buf_size = 0x00A36000,
+ unk_2d = 0,
+ unk_31 = 0,
+ surface_id = 5,
+ comp_types = [
+ Container(count = 0, types =[]),
+ Container(count = 0, types =[]),
+ ],
+ has_comp = True,
+ planes = [
+ Container(
+ width = width,
+ height = height,
+ base = 0,
+ offset = 0,
+ stride = 0x1e000,
+ size = 0x818000,
+ tile_size = 1024,
+ tile_w = 16,
+ tile_h = 16,
+ unk2 = 0x05,
+ ),
+ Container(
+ width = width,
+ height = height,
+ base = 0x818000,
+ offset = 0x818000,
+ stride = 0x7800,
+ size = 0x21e000,
+ tile_size = 256,
+ tile_w = 16,
+ tile_h = 16,
+ unk2 = 0x05,
+ )
+ ],
+ has_planes = True,
+ compression_info = [
+ unhex("""
+ 10 00 00 00 10 00 00 00 00 80 7F 00 00 00 00 00
+ 08 00 00 00 78 00 00 00 44 00 00 00 00 00 00 00
+ 03 00 00 00 00 00 00 00 AA AA AA 00 04 00 00 00
+ E0 01 00 AA
+ """),
+ unhex("""
+ 10 00 00 00 10 00 00 00 00 60 A1 00 00 80 81 00
+ 08 00 00 00 78 00 00 00 44 00 00 00 00 00 00 00
+ 03 00 00 00 00 00 00 00 AA AA AA 00 01 00 00 00
+ 78 00 00 AA
+ """),
+ ],
+ has_compr_info = True,
+ unk_1f5 = 0x100000,
+ unk_1f9 = 0x100000,
+)
+
+
+outB = ByRef(False)
+
+swaps = mgr.swaps
+
+mon.poll()
+
+fb_size = align_up(width * height * 4, 8 * 0x4000)
+print(f"Display {width}x{height}, fb size: {fb_size}")
+
+buf = u.memalign(0x4000, fb_size)
+
+colors = [0xDD0000, 0xFE6230, 0xFEF600, 0x00BB00, 0x009BFE, 0x000083, 0x30009B]
+
+
+for i, color in enumerate(colors):
+ lines = height // len(colors)
+ offset = i * lines * width * 4
+ p.memset32(buf + offset, color, lines * width * 4)
+
+iova = disp_dart.iomap(0, buf, fb_size)
+
+surfaces = [surf, None, None, None]
+#surfaces = [compressed_surf, None, None, None]
+surfAddr = [iova, 0, 0, 0]
+
+def submit():
+ swap_rec.swap_id = swapid.val
+ ret = mgr.swap_submit_dcp(swap_rec=swap_rec, surfaces=surfaces, surfAddr=surfAddr,
+ unkBool=False, unkFloat=0.0, unkInt=0, unkOutBool=outB)
+ print(f"swap returned {ret} / {outB}")
+
+ dcp.work()
+
+ if ret == 0:
+ while swaps == mgr.swaps:
+ dcp.work()
+ print("swap complete!")
+
+submit()
+
+run_shell(globals(), msg="Have fun!")
diff --git a/tools/proxyclient/experiments/dcp_iboot.py b/tools/proxyclient/experiments/dcp_iboot.py
new file mode 100755
index 0000000..9aed5d8
--- /dev/null
+++ b/tools/proxyclient/experiments/dcp_iboot.py
@@ -0,0 +1,108 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+import struct
+from construct import *
+
+from m1n1.setup import *
+from m1n1.shell import run_shell
+from m1n1 import asm
+from m1n1.hw.dart import DART, DARTRegs
+from m1n1.fw.dcp.iboot import DCPIBootClient, SurfaceFormat, EOTF, Transform, AddrFormat, Colorspace
+from m1n1.proxyutils import RegMonitor
+
+print(f"Framebuffer at {u.ba.video.base:#x}")
+
+p.display_shutdown(DCP_SHUTDOWN_MODE.QUIESCED)
+
+dart = DART.from_adt(u, "arm-io/dart-dcp")
+disp_dart = DART.from_adt(u, "arm-io/dart-disp0")
+#disp_dart.dump_all()
+
+dcp_addr = u.adt["arm-io/dcp"].get_reg(0)[0]
+dcp = DCPIBootClient(u, dcp_addr, dart, disp_dart)
+dcp.dva_offset = getattr(u.adt["/arm-io/dcp"][0], "asc_dram_mask", 0)
+
+dcp.start()
+dcp.start_ep(0x23)
+dcp.start_ep(0x24)
+
+dcp.iboot.wait_for("disp0")
+dcp.dptx.wait_for("dcpav0")
+dcp.dptx.wait_for("dcpdp0")
+
+#dcp.dptx.dcpav0.setPower(False)
+#dcp.dptx.dcpav0.forceHotPlugDetect()
+#dcp.dptx.dcpav0.setVirtualDeviceMode(0)
+#dcp.dptx.dcpav0.setPower(True)
+#dcp.dptx.dcpav0.wakeDisplay()
+#dcp.dptx.dcpav0.sleepDisplay()
+#dcp.dptx.dcpav0.wakeDisplay()
+
+print("Waiting for HPD...")
+while True:
+ hpd, ntim, ncolor = dcp.iboot.disp0.getModeCount()
+ if hpd:
+ break
+
+print("HPD asserted")
+
+print(f"Connected:{hpd} Timing modes:{ntim} Color modes:{ncolor}")
+dcp.iboot.disp0.setPower(True)
+
+timing_modes = dcp.iboot.disp0.getTimingModes()
+print("Timing modes:")
+print(timing_modes)
+
+color_modes = dcp.iboot.disp0.getColorModes()
+print("Color modes:")
+print(color_modes)
+
+timing_modes.sort(key=lambda c: (c.valid, c.width <= 1920, c.fps_int <= 60, c.width, c.height, c.fps_int, c.fps_frac))
+timing_mode = timing_modes[-1]
+
+color_modes.sort(key=lambda c: (c.valid, c.bpp <= 32, c.bpp, -int(c.colorimetry), -int(c.encoding), -int(c.eotf)))
+color_mode = color_modes[-1]
+
+print("Chosen timing mode:", timing_mode)
+print("Chosen color mode:", color_mode)
+
+dcp.iboot.disp0.setMode(timing_mode, color_mode)
+
+w, h = timing_mode.width, timing_mode.height
+
+layer = Container(
+ planes = [
+ Container(
+ addr = 0x013ec000,
+ stride = u.ba.video.stride,
+ addr_format = AddrFormat.PLANAR,
+ ),
+ Container(),
+ Container()
+ ],
+ plane_cnt = 1,
+ width = u.ba.video.width,
+ height = u.ba.video.height,
+ surface_fmt = SurfaceFormat.w30r,
+ colorspace = Colorspace.SCRGBFixed,
+ eotf = EOTF.GAMMA_SDR,
+ transform = Transform.NONE,
+)
+
+mw = min(w, u.ba.video.width)
+mh = min(h, u.ba.video.height)
+
+swap = dcp.iboot.disp0.swapBegin()
+print(swap)
+dcp.iboot.disp0.swapSetLayer(0, layer, (mw, mh, 0, 0), (mw, mh, 0, 0))
+dcp.iboot.disp0.swapEnd()
+#dcp.iboot.disp0.swapWait(swap.swap_id)
+
+run_shell(globals(), msg="Have fun!")
+
+# full shutdown!
+dcp.stop(1)
+p.pmgr_reset(0, "DISP0_CPU0")
diff --git a/tools/proxyclient/experiments/dcpext_iboot.py b/tools/proxyclient/experiments/dcpext_iboot.py
new file mode 100644
index 0000000..ceb60bd
--- /dev/null
+++ b/tools/proxyclient/experiments/dcpext_iboot.py
@@ -0,0 +1,126 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+import struct
+from construct import *
+
+from m1n1.setup import *
+from m1n1.shell import run_shell
+from m1n1 import asm
+from m1n1.hw.dart import DART, DARTRegs
+from m1n1.fw.dcp.iboot import DCPIBootClient, SurfaceFormat, EOTF, Transform, AddrFormat
+from m1n1.fw.dcp.dcpav import *
+from m1n1.proxyutils import RegMonitor
+
+dart = DART.from_adt(u, "arm-io/dart-dcpext0")
+disp_dart = DART.from_adt(u, "arm-io/dart-dispext0")
+#disp_dart.dump_all()
+
+dcp_addr = u.adt["arm-io/dcpext0"].get_reg(0)[0]
+dcp = DCPIBootClient(u, dcp_addr, dart, disp_dart)
+dcp.dva_offset = getattr(u.adt["/arm-io/dcpext0"][0], "asc_dram_mask", 0)
+
+dcp.start()
+dcp.start_ep(0x20)
+dcp.start_ep(0x23)
+dcp.start_ep(0x24)
+dcp.start_ep(0x27)
+dcp.start_ep(0x2a)
+
+dcp.system.wait_for("system")
+dcp.iboot.wait_for("disp0")
+dcp.dptx.wait_for("dcpav0")
+dcp.dptx.wait_for("dcpav1")
+dcp.dptx.wait_for("dcpdp0")
+dcp.dptx.wait_for("dcpdp1")
+dcp.dpport.wait_for("port0")
+dcp.dpport.wait_for("port1")
+
+dcp.system.wait_for("system")
+dcp.system.system.setProperty("gAFKConfigLogMask", 0xffff)
+
+print("Connect...")
+dcp.dpport.port0.open()
+dcp.dpport.port0.getLocation()
+dcp.dpport.port0.getLocation()
+dcp.dpport.port0.getUnit()
+# this triggers the power up message
+dcp.dpport.port0.displayRequest()
+# these seem to not work/do anything?
+dcp.dpport.port0.connectTo(True, ATC0, DPPHY, 0)
+
+#dcp.dcpav.controller.setPower(False)
+#dcp.dcpav.controller.forceHotPlugDetect()
+#dcp.dcpav.controller.setVirtualDeviceMode(0)
+#dcp.dcpav.controller.setPower(True)
+#dcp.dcpav.controller.wakeDisplay()
+#dcp.dcpav.controller.sleepDisplay()
+#dcp.dcpav.controller.wakeDisplay()
+
+print("Waiting for HPD...")
+while True:
+ hpd, ntim, ncolor = dcp.iboot.disp0.getModeCount()
+ if hpd:
+ break
+
+print("HPD asserted")
+
+print(f"Connected:{hpd} Timing modes:{ntim} Color modes:{ncolor}")
+dcp.iboot.disp0.setPower(True)
+
+timing_modes = dcp.iboot.disp0.getTimingModes()
+print("Timing modes:")
+print(timing_modes)
+
+color_modes = dcp.iboot.disp0.getColorModes()
+print("Color modes:")
+print(color_modes)
+
+timing_modes.sort(key=lambda c: (c.valid, c.width <= 1920, c.fps_int <= 60, c.width, c.height, c.fps_int, c.fps_frac))
+timing_mode = timing_modes[-1]
+
+color_modes.sort(key=lambda c: (c.valid, c.bpp <= 32, c.bpp, -int(c.colorimetry), -int(c.encoding), -int(c.eotf)))
+color_mode = color_modes[-1]
+
+print("Chosen timing mode:", timing_mode)
+print("Chosen color mode:", color_mode)
+
+dcp.iboot.disp0.setMode(timing_mode, color_mode)
+
+w, h = timing_mode.width, timing_mode.height
+
+layer = Container(
+ planes = [
+ Container(
+ addr = 0x013ec000,
+ stride = u.ba.video.stride,
+ addr_format = AddrFormat.PLANAR,
+ ),
+ Container(),
+ Container()
+ ],
+ plane_cnt = 1,
+ width = u.ba.video.width,
+ height = u.ba.video.height,
+ surface_fmt = SurfaceFormat.w30r,
+ colorspace = 2,
+ eotf = EOTF.GAMMA_SDR,
+ transform = Transform.NONE,
+)
+
+mw = min(w, u.ba.video.width)
+mh = min(h, u.ba.video.height)
+
+swap = dcp.iboot.disp0.swapBegin()
+print(swap)
+dcp.iboot.disp0.swapSetLayer(0, layer, (mw, mh, 0, 0), (mw, mh, 0, 0))
+dcp.iboot.disp0.swapEnd()
+#dcp.iboot.disp0.swapWait(swap.swap_id)
+
+run_shell(globals(), msg="Have fun!")
+
+# full shutdown!
+dcp.stop(1)
+p.pmgr_reset(0, "DISP0_CPU0")
diff --git a/tools/proxyclient/experiments/find_sprr_regs.py b/tools/proxyclient/experiments/find_sprr_regs.py
new file mode 100755
index 0000000..b3261db
--- /dev/null
+++ b/tools/proxyclient/experiments/find_sprr_regs.py
@@ -0,0 +1,82 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+from m1n1.setup import *
+from m1n1.find_regs import *
+from m1n1 import asm
+
+p.iodev_set_usage(IODEV.FB, 0)
+
+if u.mrs(SPRR_CONFIG_EL1):
+ u.msr(GXF_CONFIG_EL12, 0)
+ u.msr(SPRR_CONFIG_EL12, 0)
+ u.msr(GXF_CONFIG_EL1, 0)
+ u.msr(SPRR_CONFIG_EL1, 0)
+
+# Set up HCR_EL2 for EL1, since we can't do it after enabling GXF
+u.inst("nop", call="el1")
+
+all_regs = set()
+for reg in [SPRR_CONFIG_EL1, GXF_CONFIG_EL1, SPRR_CONFIG_EL12, GXF_CONFIG_EL12]:
+ old_regs = set(find_regs(u, values=False))
+ u.msr(reg, 1)
+ el2_items = set(find_regs(u))
+ el2_vals = dict(el2_items)
+ new_regs = set(k for k, v in el2_items)
+
+ all_regs = all_regs.union(new_regs)
+
+ diff_regs = new_regs - old_regs
+
+ print(reg)
+ for r in sorted(diff_regs):
+ print(" %s --> %lx" % (sysreg_name(r), u.mrs(r)))
+
+gl2_items = list(find_regs(u, regs=static_regs,call="gl2"))
+gl2_vals = dict(gl2_items)
+gl2_regs = set(k for k, v in gl2_items)
+
+print("GL2")
+for reg in sorted(gl2_regs - all_regs):
+ print(" %s -> %lx" % (sysreg_name(reg), gl2_vals[reg]))
+for reg in sorted(gl2_regs):
+ if reg in el2_vals and gl2_vals[reg] != el2_vals[reg]:
+ print(" ! %s %lx -> %lx" % (sysreg_name(reg), el2_vals[reg], gl2_vals[reg]))
+
+u.msr(GXF_CONFIG_EL12, 0)
+u.msr(SPRR_CONFIG_EL12, 0)
+u.msr(GXF_CONFIG_EL1, 0)
+u.msr(SPRR_CONFIG_EL1, 0)
+
+gl1_items = list(find_regs(u, regs=static_regs, call="gl1"))
+gl1_vals = dict(gl1_items)
+gl1_regs = set(k for k, v in gl1_items)
+
+print("GL1")
+for reg in sorted(gl1_regs - all_regs):
+ val = gl1_vals[reg]
+ print(" %s -> %lx" % (sysreg_name(reg), val))
+
+ cval = u.mrs(reg, call="gl1", silent=False)
+ print(" cur: 0x%lx" % (cval))
+
+ try:
+ u.msr(reg, cval, call="gl1", silent=False)
+ except:
+ print(">RO")
+ continue
+
+ gl2_vals = dict(find_regs(u, regs=static_regs,call="gl2"))
+ u.msr(reg, cval ^ 0xffff, call="gl1", silent=True)
+
+ for r, v in find_regs(u, regs=static_regs, call="gl2"):
+ if v != gl2_vals[r]:
+ print(" GL2 access: %s %lx -> %lx" % (sysreg_name(r), gl2_vals[r], v))
+
+ u.msr(reg, cval, call="gl1", silent=True)
+
+for reg in sorted(gl1_regs):
+ if reg in el2_vals and gl1_vals[reg] != el2_vals[reg]:
+ print(" ! %s %lx -> %lx" % (sysreg_name(reg), el2_vals[reg], gl1_vals[reg]))
diff --git a/tools/proxyclient/experiments/fptest.py b/tools/proxyclient/experiments/fptest.py
new file mode 100755
index 0000000..6c45bdc
--- /dev/null
+++ b/tools/proxyclient/experiments/fptest.py
@@ -0,0 +1,114 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+from m1n1.setup import *
+from m1n1 import asm
+
+FPCR_FZ = 1 << 24
+
+ACTLR_DEFAULT = 0xc00
+ACTLR_AFP = 1 << 5
+
+AFPCR = (3,6,15,2,5)
+AFPCR_DAZ = 1 << 0
+AFPCR_FTZ = 1 << 1
+
+code_buffer = p.malloc(0x1000)
+data_buffer = p.malloc(0x1000)
+
+code = asm.ARMAsm("""
+ ldr s0, [x0, #0]
+ ldr s1, [x0, #4]
+ fmul s0, s1, s0
+ str s0, [x0, #8]
+
+ ldr s0, [x0, #12]
+ ldr s1, [x0, #16]
+ fmul s0, s1, s0
+ str s0, [x0, #20]
+
+ # to test EL0 access
+ # mrs x0, s3_6_c15_c2_5
+ ret
+""", code_buffer)
+
+iface.writemem(code_buffer, code.data)
+p.dc_cvau(code_buffer, code.len)
+p.ic_ivau(code_buffer, code.len)
+
+def test_denormals():
+
+ data = [
+ 0x00400000, # a denormal
+ 0x40000000, # 2
+ 0,
+ 0x00800000, # smallest non-denormal
+ 0x3f000000, # 0.5
+ 0,
+ ]
+
+ iface.writemem(data_buffer, struct.pack("<%dI" % len(data), *data))
+
+ p.set_exc_guard(GUARD.SKIP)
+ ret = p.el0_call(code_buffer, data_buffer | REGION_RW_EL0)
+ p.set_exc_guard(GUARD.OFF)
+
+ v1 = p.read32(data_buffer + 8)
+ v2 = p.read32(data_buffer + 20)
+
+ print(" Input:", end=" ")
+ if v1 == 0:
+ print("FLUSH ", end=" ")
+ elif v1 == 0x00800000:
+ print("NORMAL", end=" ")
+ else:
+ print("0x08x?" % v1, end=" ")
+
+ print("Output:", end=" ")
+ if v2 == 0:
+ print("FLUSH ", end=" ")
+ elif v2 == 0x00400000:
+ print("NORMAL", end=" ")
+ else:
+ print("0x08x?" % v2, end=" ")
+ print("r = 0x%x" % ret)
+
+
+print("Testing normal mode")
+u.msr(ACTLR_EL1, ACTLR_DEFAULT)
+u.msr(AFPCR, 0)
+
+u.msr(FPCR, 0)
+print("FPCR.FZ = 0")
+test_denormals()
+
+u.msr(FPCR, FPCR_FZ)
+print("FPCR.FZ = 1")
+test_denormals()
+
+print()
+print("Testing Apple mode")
+u.msr(ACTLR_EL1, ACTLR_DEFAULT | ACTLR_AFP)
+u.msr(AFPCR, 0)
+
+u.msr(FPCR, 0)
+print("FPCR.FZ = 0")
+test_denormals()
+
+u.msr(FPCR, FPCR_FZ)
+print("FPCR.FZ = 1")
+test_denormals()
+
+u.msr(AFPCR, AFPCR_DAZ)
+print("AFPCR.<FTZ, DAZ> = 0, 1")
+test_denormals()
+
+u.msr(AFPCR, AFPCR_FTZ)
+print("AFPCR.<FTZ, DAZ> = 1, 0")
+test_denormals()
+
+u.msr(AFPCR, AFPCR_FTZ | AFPCR_DAZ)
+print("AFPCR.<FTZ, DAZ> = 1, 1")
+test_denormals()
diff --git a/tools/proxyclient/experiments/hacr_trap_bits.py b/tools/proxyclient/experiments/hacr_trap_bits.py
new file mode 100755
index 0000000..7982c33
--- /dev/null
+++ b/tools/proxyclient/experiments/hacr_trap_bits.py
@@ -0,0 +1,128 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+from m1n1.setup import *
+from m1n1 import asm
+
+code_len = 12 * 16 * 8 + 4
+data_len = 8 * 16 * 8
+
+if u.mrs(SPRR_CONFIG_EL1):
+ u.msr(GXF_CONFIG_EL12, 0)
+ u.msr(SPRR_CONFIG_EL12, 0)
+ u.msr(GXF_CONFIG_EL1, 0)
+ u.msr(SPRR_CONFIG_EL1, 0)
+
+u.msr(HACR_EL2, 0)
+
+hcr = HCR(u.mrs(HCR_EL2))
+hcr.TIDCP = 0
+hcr.TGE = 0
+u.msr(HCR_EL2, hcr.value)
+u.inst(0xd5033fdf) # isb
+
+ACTLR_DEFAULT = 0xc00
+ACTLR_AFP = 1 << 5
+u.msr(ACTLR_EL1, ACTLR_DEFAULT | ACTLR_AFP)
+
+code_buffer = p.malloc(code_len)
+data_buffer = p.malloc(data_len)
+
+template = asm.ARMAsm("""
+ mov x2, x0
+ mrs x2, s3_0_c0_c0_0
+ str x2, [x1], #8
+ ret
+""", code_buffer)
+
+mov, mrs, st, ret = struct.unpack("4I", template.data)
+
+data = []
+
+BAD = 0xacce5515abad1dea
+
+AUX = [
+ ACTLR_EL1,
+ ACTLR_EL2,
+ AFSR0_EL1,
+ AFSR0_EL2,
+ AFSR1_EL1,
+ AFSR1_EL2,
+ AIDR_EL1,
+ AIDR2_EL1,
+ AMAIR_EL1,
+ AMAIR_EL2,
+ APCTL_EL1,
+ APSTS_EL1,
+]
+
+def test():
+ u.msr(SPRR_CONFIG_EL1, 1)
+ u.msr(GXF_CONFIG_EL1, 1)
+ u.msr(SPRR_CONFIG_EL12, 1)
+ u.msr(GXF_CONFIG_EL12, 1)
+
+ for op1 in range(1 << 3):
+ for CRn in (0b1011, 0b1111):
+ mrs0 = mrs | (op1 << 16) | (CRn << 12)
+ insns = []
+ for CRm in range(1 << 4):
+ for op2 in range(1 << 3):
+ insns.extend((mov, mrs0 | (CRm << 8) | (op2 << 5), st))
+ insns.append(ret)
+ iface.writemem(code_buffer, struct.pack("<385I", *insns))
+ p.dc_cvau(code_buffer, code_len)
+ p.ic_ivau(code_buffer, code_len)
+
+ p.set_exc_guard(GUARD.SILENT | GUARD.SKIP)
+ p.el1_call(code_buffer, BAD, data_buffer)
+ cnt = p.get_exc_count()
+
+ data = iface.readmem(data_buffer, data_len)
+ d = struct.unpack("<128Q", data)
+ i = 0
+ for CRm in range(1 << 4):
+ for op2 in range(1 << 3):
+ v = d[i]
+ if v != BAD:
+ yield (3, op1, CRn, CRm, op2)
+ i += 1
+ for enc in AUX:
+ try:
+ v = u.mrs(enc, call="el1", silent=True)
+ if v != BAD:
+ yield enc
+ except:
+ continue
+
+ u.msr(GXF_CONFIG_EL12, 0)
+ u.msr(SPRR_CONFIG_EL12, 0)
+ u.msr(GXF_CONFIG_EL1, 0)
+ u.msr(SPRR_CONFIG_EL1, 0)
+
+baseline = set(test())
+
+for bit in range(64):
+ print()
+ print ("## HACR_EL2[%d]" % bit)
+ u.msr(HACR_EL2, 1<<bit)
+ u.inst(0xd5033fdf) # isb
+
+ new = set(test())
+
+ added = new - baseline
+ removed = baseline - new
+
+ if added:
+ print("Untraps:")
+ for enc in sorted(added):
+ print(f"{sysreg_name(enc)} ({', '.join(str(i) for i in enc)})")
+
+ if removed:
+ print("Traps:")
+ for enc in sorted(removed):
+ print(f"{sysreg_name(enc)} ({', '.join(str(i) for i in enc)})")
+
+p.set_exc_guard(GUARD.OFF)
diff --git a/tools/proxyclient/experiments/i2c.py b/tools/proxyclient/experiments/i2c.py
new file mode 100755
index 0000000..4100964
--- /dev/null
+++ b/tools/proxyclient/experiments/i2c.py
@@ -0,0 +1,124 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+import struct
+
+from m1n1.setup import *
+from m1n1 import asm
+
+base = 0x235010000
+
+# register defines from https://github.com/torvalds/linux/blob/master/drivers/i2c/busses/i2c-pasemi.c
+# Copyright (C) 2006-2007 PA Semi, Inc
+# SMBus host driver for PA Semi PWRficient
+REG_MTXFIFO = 0x00
+REG_MRXFIFO = 0x04
+REG_SMSTA = 0x14
+REG_CTL = 0x1c
+
+MTXFIFO_READ = 0x00000400
+MTXFIFO_STOP = 0x00000200
+MTXFIFO_START = 0x00000100
+MTXFIFO_DATA_M = 0x000000ff
+
+MRXFIFO_EMPTY = 0x00000100
+MRXFIFO_DATA_M = 0x000000ff
+
+SMSTA_XEN = 0x08000000
+SMSTA_MTN = 0x00200000
+
+CTL_MRR = 0x00000400
+CTL_MTR = 0x00000200
+CTL_CLK_M = 0x000000ff
+
+CLK_100K_DIV = 84
+CLK_400K_DIV = 21
+
+
+def i2c_read_reg(addr, reg, reg_size):
+ p.set32(base + REG_CTL, CTL_MTR | CTL_MRR)
+ p.write32(base + REG_SMSTA, 0xffffffff)
+
+ p.write32(base + REG_MTXFIFO, MTXFIFO_START | (addr << 1))
+ p.write32(base + REG_MTXFIFO, MTXFIFO_STOP | reg)
+
+ while not (p.read32(base + REG_SMSTA) & SMSTA_XEN):
+ pass
+
+ p.write32(base + REG_MTXFIFO, MTXFIFO_START | (addr << 1) | 1)
+ p.write32(base + REG_MTXFIFO, MTXFIFO_READ | MTXFIFO_STOP | reg_size + 1)
+
+ res = []
+ while len(res) < reg_size+1:
+ v = p.read32(base + REG_MRXFIFO)
+ if v & 0x100:
+ continue
+ res.append(v)
+
+ if res[0] < reg_size:
+ print("only read %d instead of %d bytes" % (res[0], reg_size))
+ return res[1:]
+
+
+def i2c_write_reg(addr, reg, data):
+ p.set32(base + REG_CTL, CTL_MTR | CTL_MRR)
+ p.write32(base + REG_SMSTA, 0xffffffff)
+
+ p.write32(base + REG_MTXFIFO, MTXFIFO_START | (addr << 1))
+ p.write32(base + REG_MTXFIFO, reg)
+ for i in range(len(data)-1):
+ p.write32(base + REG_MTXFIFO, data[i])
+ p.write32(base + REG_MTXFIFO, data[-1] | MTXFIFO_STOP)
+
+ while not (p.read32(base + REG_SMSTA) & SMSTA_XEN):
+ pass
+
+
+def i2c_read16(addr, reg):
+ data = struct.pack(">2b", *i2c_read_reg(addr, reg, 2))
+ return struct.unpack(">H", data)[0]
+
+
+def i2c_read32(addr, reg):
+ data = struct.pack(">4b", *i2c_read_reg(addr, reg, 4))
+ return struct.unpack(">I", data)[0]
+
+
+def tps6598x_exec_cmd(addr, cmd, data_in, out_len):
+ if data_in:
+ data = [len(data_in)] + data_in
+
+ # TPS_REG_DATA1
+ i2c_write_reg(addr, 0x09, data)
+
+ # TPS_REG_CMD1
+ cmd = [4] + list(map(ord, cmd))
+ i2c_write_reg(addr, 0x08, cmd)
+
+ # TPS_REG_CMD1
+ v = i2c_read32(addr, 0x08)
+ while v != 0:
+ if v == 0x21434d44: # !CMD
+ raise Exception("Invalid command!")
+ v = i2c_read32(addr, 0x08)
+
+ if not out_len:
+ return
+
+ # TPS_REG_DATA1
+ return i2c_read_reg(addr, 0x09, out_len)
+
+
+print("make sure to run pmgr_adt_clocks_enable for /arm-io/i2c0 before this script.")
+
+# apple-specific command to bring the power state to zero
+# (or any other value specified as an argument)
+tps6598x_exec_cmd(0x3f, "SSPS", [0], 0)
+tps6598x_exec_cmd(0x38, "SSPS", [0], 0)
+
+tps6598x_exec_cmd(0x3f, "SWDF", None, 0)
+tps6598x_exec_cmd(0x3f, "SWSr", None, 0)
+tps6598x_exec_cmd(0x38, "SWDF", None, 0)
+tps6598x_exec_cmd(0x38, "SWSr", None, 0)
diff --git a/tools/proxyclient/experiments/jpeg.py b/tools/proxyclient/experiments/jpeg.py
new file mode 100644
index 0000000..1e53a6b
--- /dev/null
+++ b/tools/proxyclient/experiments/jpeg.py
@@ -0,0 +1,1222 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+from m1n1.setup import *
+from m1n1.hw.dart import DART, DARTRegs
+from m1n1.hw.jpeg import *
+from m1n1.utils import *
+import argparse
+import struct
+import time
+from enum import IntEnum
+from PIL import Image, ImageDraw
+
+
+def divroundup(val, div):
+ return (val + div - 1) // div
+
+
+def yuv2rgb(y, u, v):
+ y -= 16
+ u -= 128
+ v -= 128
+
+ y /= 255
+ u /= 255
+ v /= 255
+
+ r = y + 1.13983 * v
+ g = y - 0.39465 * u - 0.58060 * v
+ b = y + 2.03211 * u
+
+ r = min(255, max(0, int(r * 255)))
+ g = min(255, max(0, int(g * 255)))
+ b = min(255, max(0, int(b * 255)))
+
+ return (r, g, b)
+
+
+def rgb2yuv(r, g, b):
+ r /= 255
+ g /= 255
+ b /= 255
+
+ y = 0.299*r + 0.587*g + 0.114*b
+ u = -0.14713*r - 0.28886*g + 0.436*b
+ v = 0.615*r - 0.51499*g - 0.10001*b
+
+ y = y * 255 + 16
+ u = u * 255 + 128
+ v = v * 255 + 128
+
+ y = min(255, max(0, int(y)))
+ u = min(255, max(0, int(u)))
+ v = min(255, max(0, int(v)))
+
+ return (y, u, v)
+
+
+ap = argparse.ArgumentParser(description='JPEG block experiment')
+ap.add_argument("--jpeg", dest='which_jpeg', type=str, default='jpeg0',
+ help='which JPEG instance (jpeg0/jpeg1)')
+g = ap.add_mutually_exclusive_group(required=True)
+g.add_argument("-e", "--encode", action='store_true')
+g.add_argument("-d", "--decode", action='store_true')
+ap.add_argument("--raw-output", type=str, required=False)
+ap.add_argument("--decode-scale", type=int, required=False, default=1)
+ap.add_argument("--decode-pixelfmt", type=str, required=False, default='RGBA')
+ap.add_argument("--decode-rgba-alpha", type=int, required=False, default=255)
+ap.add_argument("--encode-subsampling", type=str, required=False, default='444')
+ap.add_argument("--encode-rst-interval", type=int, required=False)
+ap.add_argument("--encode-pixelfmt", type=str, required=False, default='RGB888')
+ap.add_argument("input", type=str)
+ap.add_argument("output", type=str)
+args = ap.parse_args()
+
+# print(args)
+
+# Perform necessary pre-parsing
+if args.decode:
+ assert args.decode_scale in [1, 2, 4, 8]
+ decode_scale = args.decode_scale
+ # FIXME: verify behavior on non-evenly-divisible sizes
+
+ assert args.decode_pixelfmt in [
+ 'RGBA',
+ 'BGRA',
+ 'RGB565',
+ 'YUV422-CbYCrY',
+ 'YUV422-YCbYCr',
+ 'YUV422-planar',
+ 'YUV420-planar',
+ 'YUV444-planar',
+ ]
+ pixfmt = args.decode_pixelfmt
+
+ with open(args.input, 'rb') as f:
+ jpeg_data = f.read()
+
+ found_sof0 = False
+
+ jpeg_work = jpeg_data
+ while jpeg_work:
+ seg_marker = struct.unpack(">H", jpeg_work[:2])[0]
+ print(f"Seg {seg_marker:04X}")
+ if seg_marker == 0xFFD8:
+ # SOI
+ jpeg_work = jpeg_work[2:]
+ elif seg_marker == 0xFFDA:
+ # SOS
+ break
+ else:
+ seg_len = struct.unpack(">H", jpeg_work[2:4])[0]
+ assert seg_len >= 2
+ seg_data = jpeg_work[4:4 + seg_len - 2]
+ jpeg_work = jpeg_work[4 + seg_len - 2:]
+
+ if seg_marker == 0xFFC0:
+ # SOF0
+ assert not found_sof0
+ found_sof0 = True
+ sof0 = struct.unpack(">BHHB", seg_data[:6])
+ (jpeg_bpp, jpeg_H, jpeg_W, jpeg_components_cnt) = sof0
+ # it is not yet verified what the requirements are for inputs
+ assert jpeg_bpp == 8
+ assert jpeg_components_cnt == 1 or jpeg_components_cnt == 3
+ if jpeg_components_cnt == 1:
+ jpeg_MODE = '400'
+ else:
+ jpeg_components = {}
+ for i in range(jpeg_components_cnt):
+ comp_id, comp_sampling, _ = seg_data[6+3*i:6+3*(i+1)]
+ jpeg_components[comp_id] = comp_sampling
+ assert 1 in jpeg_components
+ comp_Y = jpeg_components[1]
+ assert 2 in jpeg_components
+ comp_Cb = jpeg_components[2]
+ assert 3 in jpeg_components
+ comp_Cr = jpeg_components[3]
+
+ if (comp_Y, comp_Cb, comp_Cr) == (0x11, 0x11, 0x11):
+ jpeg_MODE = '444'
+ elif (comp_Y, comp_Cb, comp_Cr) == (0x21, 0x11, 0x11):
+ jpeg_MODE = '422'
+ elif (comp_Y, comp_Cb, comp_Cr) == (0x22, 0x11, 0x11):
+ jpeg_MODE = '420'
+ elif (comp_Y, comp_Cb, comp_Cr) == (0x41, 0x11, 0x11):
+ jpeg_MODE = '411'
+ else:
+ # TODO: 422-vertical, others???
+ # Is it possible to implement them?
+ print("Unsupported subsampling mode")
+ assert False
+
+ assert found_sof0
+ print(f"JPEG is {jpeg_W}x{jpeg_H} with subsampling {jpeg_MODE}")
+
+ if jpeg_MODE == '444' or jpeg_MODE == '400':
+ macroblock_W, macroblock_H = 8, 8
+ elif jpeg_MODE == '422':
+ macroblock_W, macroblock_H = 16, 8
+ elif jpeg_MODE == '420':
+ macroblock_W, macroblock_H = 16, 16
+ elif jpeg_MODE == '411':
+ macroblock_W, macroblock_H = 32, 8
+ else:
+ assert False
+
+ # FIXME: Exactly how much extra memory do we need to allocate?
+ surface_W = divroundup(jpeg_W // decode_scale, macroblock_W) * macroblock_W
+ surface_H = divroundup(jpeg_H // decode_scale, macroblock_H) * macroblock_H
+ if pixfmt in ['RGBA', 'BGRA']:
+ BYTESPP = 4
+ elif pixfmt in ['RGB565', 'YUV422-CbYCrY', 'YUV422-YCbYCr']:
+ BYTESPP = 2
+ elif pixfmt in ['YUV422-planar', 'YUV420-planar', 'YUV444-planar']:
+ BYTESPP = 1
+ else:
+ assert False
+ surface_stride = surface_W * BYTESPP
+ surface_sz = surface_stride*surface_H
+
+ if pixfmt == 'YUV422-planar':
+ P1_MULW = 1 # FIXME UGLY
+ P1_DIVW = 1
+ P1_DIVH = 1
+ elif pixfmt == 'YUV420-planar':
+ P1_MULW = 1
+ P1_DIVW = 1
+ P1_DIVH = 2
+ elif pixfmt == 'YUV444-planar':
+ P1_MULW = 2
+ P1_DIVW = 1
+ P1_DIVH = 1
+ if pixfmt in ['YUV422-planar', 'YUV420-planar', 'YUV444-planar']:
+ surface_P1_W = surface_W * P1_MULW // P1_DIVW
+ surface_P1_H = surface_H // P1_DIVH
+ surface_P1_stride = surface_P1_W
+ surface_P1_off = surface_sz
+ surface_sz += surface_P1_stride*surface_P1_H
+ else:
+ surface_P1_stride = 0
+ surface_P1_off = 0
+
+ input_mem_sz = align_up(len(jpeg_data))
+ print(f"Using size {input_mem_sz:08X} for JPEG data")
+
+ output_mem_sz = align_up(surface_sz)
+ print(f"Using size {output_mem_sz:08X} for output image")
+else:
+ assert args.encode_subsampling in ['444', '422', '420', '400']
+ if args.encode_subsampling == '444' or args.encode_subsampling == '400':
+ macroblock_W, macroblock_H = 8, 8
+ elif args.encode_subsampling == '422':
+ macroblock_W, macroblock_H = 16, 8
+ elif args.encode_subsampling == '420':
+ macroblock_W, macroblock_H = 16, 16
+ else:
+ assert False
+
+ assert args.encode_pixelfmt in [
+ 'RGB888',
+ 'RGB101010',
+ 'RGB565',
+ 'YUV10',
+ 'YUV-linear',
+ 'YUV444-planar',
+ 'YUV422-planar',
+ 'YUV420-planar',
+ ]
+ pixfmt = args.encode_pixelfmt
+
+ # Driver doesn't support this either
+ if pixfmt == 'YUV-linear' and args.encode_subsampling == '444':
+ print("WARNING: This combination does not appear to work!!!")
+ if pixfmt == 'YUV422-planar' and args.encode_subsampling == '444':
+ print("WARNING: This combination does not appear to work!!!")
+ if pixfmt == 'YUV420-planar' and args.encode_subsampling == '444':
+ print("WARNING: This combination does not appear to work!!!")
+
+ image_data = b''
+ image_data_P1 = b''
+ with Image.open(args.input) as im:
+ im_W, im_H = im.size
+
+ if pixfmt != 'YUV420-planar':
+ for y in range(im_H):
+ for x in range(im_W):
+ r, g, b = im.getpixel((x, y))
+ if pixfmt == 'RGB888':
+ image_data += struct.pack("BBBB", r, g, b, 255)
+ elif pixfmt == 'RGB101010':
+ image_data += struct.pack("<I", (r << 2) | (g << 12) | (b << 22))
+ elif pixfmt == 'RGB565':
+ image_data += struct.pack("<H", (r >> 3) | ((g >> 2) << 5) | ((b >> 3) << 11))
+ elif pixfmt == 'YUV10':
+ # absolute garbage color space conversion
+ # for demonstration purposes only
+ y_, u_, v_ = rgb2yuv(r, g, b)
+ image_data += struct.pack("<I", (y_ << 2) | (u_ << 12) | (v_ << 22))
+ elif pixfmt == 'YUV-linear':
+ # garbage color space conversion, garbage subsampling
+ # for demonstration purposes only
+ y_, u_, v_ = rgb2yuv(r, g, b)
+ if x & 1 == 0:
+ color = u_
+ else:
+ color = v_
+ image_data += struct.pack("BB", y_, color)
+ elif pixfmt == 'YUV444-planar':
+ # garbage color space conversion
+ # for demonstration purposes only
+ y_, u_, v_ = rgb2yuv(r, g, b)
+ image_data += struct.pack("B", y_)
+ image_data_P1 += struct.pack("BB", u_, v_)
+ elif pixfmt == 'YUV422-planar':
+ # garbage color space conversion, garbage subsampling
+ # for demonstration purposes only
+ y_, u_, v_ = rgb2yuv(r, g, b)
+ if x & 1 == 0:
+ color = u_
+ else:
+ color = v_
+ image_data += struct.pack("B", y_)
+ image_data_P1 += struct.pack("B", color)
+ else:
+ assert False
+ else:
+ for y in range(im_H):
+ for x in range(im_W):
+ r, g, b = im.getpixel((x, y))
+ # garbage color space conversion, garbage subsampling
+ # for demonstration purposes only
+ y_, u_, v_ = rgb2yuv(r, g, b)
+ if x & 1 == 0:
+ color = u_
+ else:
+ color = v_
+ image_data += struct.pack("B", y_)
+ if y & 1 == 0:
+ image_data_P1 += struct.pack("B", color)
+
+ if pixfmt in ['RGB888', 'RGB101010', 'YUV10']:
+ BYTESPP = 4
+ BYTESPP_P1 = 0
+ P1_DIVH = 1
+ elif pixfmt in ['RGB565', 'YUV-linear']:
+ BYTESPP = 2
+ BYTESPP_P1 = 0
+ P1_DIVH = 1
+ elif pixfmt == 'YUV444-planar':
+ BYTESPP = 1
+ BYTESPP_P1 = 2
+ P1_DIVH = 1
+ elif pixfmt == 'YUV422-planar':
+ BYTESPP = 1
+ BYTESPP_P1 = 1
+ P1_DIVH = 1
+ elif pixfmt == 'YUV420-planar':
+ BYTESPP = 1
+ BYTESPP_P1 = 1
+ P1_DIVH = 2
+ else:
+ assert False
+ surface_stride = im_W * BYTESPP
+ surface_sz = surface_stride * im_H
+ surface_P1_off = surface_sz
+ print(f"Plane 1 offset at {surface_P1_off:08X}")
+ surface_P1_stride = im_W * BYTESPP_P1
+ surface_sz += surface_P1_stride * im_H // P1_DIVH
+ input_mem_sz = align_up(surface_sz)
+
+ output_mem_sz = input_mem_sz
+
+ print(f"Using size {input_mem_sz:08X} for input image")
+ print(f"Using size {output_mem_sz:08X} for output data")
+
+# Turn on the JPEG block
+p.pmgr_adt_clocks_enable(f'/arm-io/dart-{args.which_jpeg}')
+p.pmgr_adt_clocks_enable(f'/arm-io/{args.which_jpeg}')
+
+dart = DART.from_adt(u, f'/arm-io/dart-{args.which_jpeg}')
+dart.initialize()
+
+jpeg_base, _ = u.adt[f'/arm-io/{args.which_jpeg}'].get_reg(0)
+jpeg = JPEGRegs(u, jpeg_base)
+
+
+def reset_block():
+ jpeg.MODE.val = 0x100
+ jpeg.MODE.val = 0x13e
+
+ set_default_regs()
+
+ jpeg.MODE.val = 0x17f
+ for _ in range(10000):
+ v = jpeg.REG_0x1004.val
+ if v == 0:
+ break
+ print(f"reset 1 -- {v}")
+ if (v := jpeg.REG_0x1004.val) != 0:
+ print(f"reset 1 failed! -- {v}")
+ assert False
+
+ jpeg.RST_INTERVAL.val = 1
+ for _ in range(2500):
+ v = jpeg.RST_INTERVAL.val
+ if v == 1:
+ break
+ print(f"reset 2 -- {v}")
+ if (v := jpeg.RST_INTERVAL.val) != 1:
+ print(f"reset 2 failed! -- {v}")
+ assert False
+ jpeg.RST_INTERVAL.val = 0
+
+ jpeg.ENABLE_RST_LOGGING.val = 0
+ jpeg.REG_0x1a8.val = 0
+ jpeg.REG_0x1ac.val = 0
+ jpeg.REG_0x1b0.val = 0
+ jpeg.REG_0x1b4.val = 0
+ jpeg.REG_0x1bc.val = 0
+ jpeg.REG_0x1c0.val = 0
+ jpeg.REG_0x1c4.val = 0
+ jpeg.REG_0x1c8.val = 0
+ jpeg.REG_0x1cc.val = 0
+ jpeg.REG_0x1d0.val = 0
+ jpeg.REG_0x1d4.val = 0
+
+ jpeg.MODE.val = 0x143
+
+
+def set_default_regs(param1=0):
+ jpeg.REG_0x0.val = 0
+ jpeg.REG_0x0.val = 0
+ jpeg.REG_0x4.val = 0
+ jpeg.CODEC.val = 0
+ jpeg.REG_0x2c.val = 0
+ jpeg.REG_0x30.val = 0
+ jpeg.REG_0x34.val = 1
+ jpeg.REG_0x38.val = 1
+ jpeg.CHROMA_HALVE_H_TYPE1.val = 0
+ jpeg.CHROMA_HALVE_H_TYPE2.val = 0
+ jpeg.CHROMA_HALVE_V_TYPE1.val = 0
+ jpeg.CHROMA_HALVE_V_TYPE2.val = 0
+ jpeg.CHROMA_DOUBLE_H.val = 0
+ jpeg.CHROMA_QUADRUPLE_H.val = 0
+ jpeg.CHROMA_DOUBLE_V.val = 0
+ jpeg.PLANAR_CHROMA_HALVING.val = 0
+ jpeg.PX_USE_PLANE1.val = 0
+ jpeg.PX_TILES_W.val = 1
+ jpeg.PX_TILES_H.val = 1
+ jpeg.PX_PLANE0_WIDTH.val = 1
+ jpeg.PX_PLANE0_HEIGHT.val = 1
+ jpeg.PX_PLANE0_TILING_H.val = 1
+ jpeg.PX_PLANE0_TILING_V.val = 1
+ jpeg.PX_PLANE0_STRIDE.val = 1
+ jpeg.PX_PLANE1_WIDTH.val = 1
+ jpeg.PX_PLANE1_HEIGHT.val = 1
+ jpeg.PX_PLANE1_TILING_H.val = 1
+ jpeg.PX_PLANE1_TILING_V.val = 1
+ jpeg.PX_PLANE1_STRIDE.val = 1
+ jpeg.INPUT_START1.val = 0
+ jpeg.INPUT_START2.val = 0
+ jpeg.REG_0x94.val = 1
+ jpeg.REG_0x98.val = 1
+ jpeg.INPUT_END.val = 0xffffffff
+ jpeg.OUTPUT_START1.val = 0
+ jpeg.OUTPUT_START2.val = 0
+ jpeg.OUTPUT_END.val = 0xffffffff
+ for i in range(11):
+ jpeg.MATRIX_MULT[i].val = 0
+ for i in range(10):
+ jpeg.DITHER[i].val = 0xff
+ jpeg.ENCODE_PIXEL_FORMAT.val = 0
+ jpeg.ENCODE_COMPONENT0_POS.val = 0
+ jpeg.ENCODE_COMPONENT1_POS.val = 0
+ jpeg.ENCODE_COMPONENT2_POS.val = 0
+ jpeg.ENCODE_COMPONENT3_POS.val = 0
+ jpeg.CONVERT_COLOR_SPACE.val = 0
+ jpeg.REG_0x118.val = 0
+ jpeg.REG_0x11c.val = 0
+ jpeg.REG_0x120.val = 0
+ jpeg.TILING_ENABLE.val = 0
+ jpeg.TILING_PLANE0.val = 0
+ jpeg.TILING_PLANE1.val = 0
+ jpeg.DECODE_MACROBLOCKS_W.val = 0
+ jpeg.DECODE_MACROBLOCKS_H.val = 0
+ jpeg.SCALE_FACTOR.val = 0
+ jpeg.DECODE_PIXEL_FORMAT.val = 0
+ jpeg.YUV422_ORDER.val = 0
+ jpeg.RGBA_ORDER.val = 0
+ jpeg.RGBA_ALPHA.val = 0
+ jpeg.RIGHT_EDGE_PIXELS.val = 0
+ jpeg.BOTTOM_EDGE_PIXELS.val = 0
+ jpeg.RIGHT_EDGE_SAMPLES.val = 0
+ jpeg.BOTTOM_EDGE_SAMPLES.val = 0
+
+ # this is always done on the m1 max hwrev
+ jpeg.REG_0x1fc.val = 0
+ jpeg.REG_0x200.val = 0
+ jpeg.REG_0x204.val = 0
+ jpeg.REG_0x208.val = 0
+ jpeg.REG_0x214.val = 0
+ jpeg.REG_0x218.val = 0
+ jpeg.REG_0x21c.val = 0
+ jpeg.REG_0x220.val = 0
+ jpeg.REG_0x224.val = 0
+ jpeg.REG_0x228.val = 0
+ jpeg.REG_0x22c.val = 0
+ jpeg.REG_0x230.val = 0
+ jpeg.REG_0x234.val = 0x1f40
+ jpeg.REG_0x244.val = 0
+ jpeg.REG_0x248.val = 0
+ jpeg.REG_0x258.val = 0
+ jpeg.REG_0x25c.val = 0
+ jpeg.REG_0x23c.val = 0
+ jpeg.REG_0x240.val = 0
+ jpeg.REG_0x250.val = 0
+ jpeg.REG_0x254.val = 0
+
+ jpeg.REG_0x160.val = param1
+ jpeg.TIMEOUT.val = 0
+ jpeg.REG_0x20.val = 0xff
+
+
+print(f"HW revision is {jpeg.HWREV}")
+reset_block()
+
+input_buf_phys = u.heap.memalign(0x4000, input_mem_sz)
+output_buf_phys = u.heap.memalign(0x4000, output_mem_sz)
+print(f"buffers (phys) {input_buf_phys:016X} {output_buf_phys:016X}")
+
+input_buf_iova = dart.iomap(0, input_buf_phys, input_mem_sz)
+output_buf_iova = dart.iomap(0, output_buf_phys, output_mem_sz)
+print(f"buffers (iova) {input_buf_iova:08X} {output_buf_iova:08X}")
+# dart.dump_all()
+
+iface.writemem(input_buf_phys, b'\xAA' * input_mem_sz)
+iface.writemem(output_buf_phys, b'\xAA' * output_mem_sz)
+
+
+if args.decode:
+ iface.writemem(input_buf_phys, jpeg_data)
+ print("JPEG uploaded")
+
+ jpeg.REG_0x34 = 1
+ jpeg.REG_0x2c = 0
+ jpeg.REG_0x38 = 0
+ if jpeg_MODE == '444':
+ jpeg.CODEC.set(CODEC=E_CODEC._444)
+ elif jpeg_MODE == '400':
+ jpeg.CODEC.set(CODEC=E_CODEC._400)
+ elif jpeg_MODE == '422':
+ jpeg.CODEC.set(CODEC=E_CODEC._422)
+ elif jpeg_MODE == '420':
+ jpeg.CODEC.set(CODEC=E_CODEC._420)
+ elif jpeg_MODE == '411':
+ jpeg.CODEC.set(CODEC=E_CODEC._411)
+ else:
+ assert False
+ if pixfmt == 'RGBA' or pixfmt == 'BGRA':
+ jpeg.DECODE_PIXEL_FORMAT.set(FORMAT=E_DECODE_PIXEL_FORMAT.RGBA8888)
+ elif pixfmt == 'RGB565':
+ jpeg.DECODE_PIXEL_FORMAT.set(FORMAT=E_DECODE_PIXEL_FORMAT.RGB565)
+ elif pixfmt == 'YUV422-CbYCrY' or pixfmt == 'YUV422-YCbYCr':
+ jpeg.DECODE_PIXEL_FORMAT.set(FORMAT=E_DECODE_PIXEL_FORMAT.YUV422_linear)
+ elif pixfmt == 'YUV422-planar':
+ jpeg.DECODE_PIXEL_FORMAT.set(FORMAT=E_DECODE_PIXEL_FORMAT.YUV422_planar)
+ elif pixfmt == 'YUV420-planar':
+ jpeg.DECODE_PIXEL_FORMAT.set(FORMAT=E_DECODE_PIXEL_FORMAT.YUV420_planar)
+ elif pixfmt == 'YUV444-planar':
+ jpeg.DECODE_PIXEL_FORMAT.set(FORMAT=E_DECODE_PIXEL_FORMAT.YUV444_planar)
+ else:
+ assert False
+
+ if pixfmt in ['YUV422-planar', 'YUV420-planar', 'YUV444-planar']:
+ jpeg.PX_USE_PLANE1 = 1
+ jpeg.PX_PLANE1_WIDTH = jpeg_W * P1_MULW // P1_DIVW // decode_scale - 1
+ jpeg.PX_PLANE1_HEIGHT = jpeg_H // P1_DIVH // decode_scale - 1
+ else:
+ jpeg.PX_USE_PLANE1 = 0
+ jpeg.PX_PLANE0_WIDTH = jpeg_W*BYTESPP // decode_scale - 1
+ jpeg.PX_PLANE0_HEIGHT = jpeg_H // decode_scale - 1
+ jpeg.TIMEOUT = 266000000
+
+ jpeg.REG_0x94 = 0x1f
+ jpeg.REG_0x98 = 1
+
+ jpeg.DECODE_MACROBLOCKS_W = divroundup(jpeg_W, macroblock_W)
+ jpeg.DECODE_MACROBLOCKS_H = divroundup(jpeg_H, macroblock_H)
+ right_edge_px = \
+ jpeg_W - divroundup(jpeg_W, macroblock_W)*macroblock_W + macroblock_W
+ bot_edge_px = \
+ jpeg_H - divroundup(jpeg_H, macroblock_H)*macroblock_H + macroblock_H
+ # XXX changing this does not seem to do anything.
+ # Does it possibly affect scaling down?
+ jpeg.RIGHT_EDGE_PIXELS.val = right_edge_px
+ jpeg.BOTTOM_EDGE_PIXELS.val = bot_edge_px
+ jpeg.RIGHT_EDGE_SAMPLES.val = right_edge_px // (macroblock_W // 8)
+ jpeg.BOTTOM_EDGE_SAMPLES.val = bot_edge_px // (macroblock_H // 8)
+
+ jpeg.PX_TILES_H = divroundup(jpeg_H, macroblock_H)
+ # FIXME explain this
+ if pixfmt in ['RGBA', 'BGRA', 'RGB565', 'YUV444-planar']:
+ jpeg.PX_TILES_W = divroundup(jpeg_W // decode_scale, macroblock_W)
+ else:
+ jpeg.PX_TILES_W = divroundup(jpeg_W // decode_scale, max(macroblock_W, 16))
+ if pixfmt == 'RGBA' or pixfmt == 'BGRA':
+ if jpeg_MODE == '444' or jpeg_MODE == '400':
+ jpeg.PX_PLANE0_TILING_H = 4
+ jpeg.PX_PLANE0_TILING_V = 8 // decode_scale
+ jpeg.PX_PLANE1_TILING_H = 1
+ jpeg.PX_PLANE1_TILING_V = 1
+ elif jpeg_MODE == '422':
+ jpeg.PX_PLANE0_TILING_H = 8
+ jpeg.PX_PLANE0_TILING_V = 8 // decode_scale
+ jpeg.PX_PLANE1_TILING_H = 1
+ jpeg.PX_PLANE1_TILING_V = 1
+ elif jpeg_MODE == '420':
+ jpeg.PX_PLANE0_TILING_H = 8
+ jpeg.PX_PLANE0_TILING_V = 16 // decode_scale
+ jpeg.PX_PLANE1_TILING_H = 0
+ jpeg.PX_PLANE1_TILING_V = 0
+ elif jpeg_MODE == '411':
+ jpeg.PX_PLANE0_TILING_H = 16
+ jpeg.PX_PLANE0_TILING_V = 8 // decode_scale
+ jpeg.PX_PLANE1_TILING_H = 0
+ jpeg.PX_PLANE1_TILING_V = 0
+ else:
+ assert False
+ elif pixfmt == 'RGB565':
+ if jpeg_MODE == '444' or jpeg_MODE == '400':
+ jpeg.PX_PLANE0_TILING_H = 2
+ jpeg.PX_PLANE0_TILING_V = 8 // decode_scale
+ jpeg.PX_PLANE1_TILING_H = 1
+ jpeg.PX_PLANE1_TILING_V = 1
+ elif jpeg_MODE == '422':
+ jpeg.PX_PLANE0_TILING_H = 4
+ jpeg.PX_PLANE0_TILING_V = 8 // decode_scale
+ jpeg.PX_PLANE1_TILING_H = 1
+ jpeg.PX_PLANE1_TILING_V = 1
+ elif jpeg_MODE == '420':
+ jpeg.PX_PLANE0_TILING_H = 4
+ jpeg.PX_PLANE0_TILING_V = 16 // decode_scale
+ jpeg.PX_PLANE1_TILING_H = 0
+ jpeg.PX_PLANE1_TILING_V = 0
+ elif jpeg_MODE == '411':
+ jpeg.PX_PLANE0_TILING_H = 8
+ jpeg.PX_PLANE0_TILING_V = 8 // decode_scale
+ jpeg.PX_PLANE1_TILING_H = 0
+ jpeg.PX_PLANE1_TILING_V = 0
+ else:
+ assert False
+ elif pixfmt == 'YUV422-CbYCrY' or pixfmt == 'YUV422-YCbYCr':
+ if jpeg_MODE == '444' or jpeg_MODE == '400':
+ jpeg.PX_PLANE0_TILING_H = 4
+ jpeg.PX_PLANE0_TILING_V = 8 // decode_scale
+ jpeg.PX_PLANE1_TILING_H = 1
+ jpeg.PX_PLANE1_TILING_V = 1
+ elif jpeg_MODE == '422':
+ jpeg.PX_PLANE0_TILING_H = 4
+ jpeg.PX_PLANE0_TILING_V = 8 // decode_scale
+ jpeg.PX_PLANE1_TILING_H = 1
+ jpeg.PX_PLANE1_TILING_V = 1
+ elif jpeg_MODE == '420':
+ jpeg.PX_PLANE0_TILING_H = 4
+ jpeg.PX_PLANE0_TILING_V = 16 // decode_scale
+ jpeg.PX_PLANE1_TILING_H = 0
+ jpeg.PX_PLANE1_TILING_V = 0
+ elif jpeg_MODE == '411':
+ jpeg.PX_PLANE0_TILING_H = 8
+ jpeg.PX_PLANE0_TILING_V = 8 // decode_scale
+ jpeg.PX_PLANE1_TILING_H = 0
+ jpeg.PX_PLANE1_TILING_V = 0
+ else:
+ assert False
+ elif pixfmt == 'YUV422-planar':
+ if jpeg_MODE == '444' or jpeg_MODE == '400':
+ jpeg.PX_PLANE0_TILING_H = 2
+ jpeg.PX_PLANE0_TILING_V = 8 // decode_scale
+ jpeg.PX_PLANE1_TILING_H = 2
+ jpeg.PX_PLANE1_TILING_V = 8 // decode_scale
+ elif jpeg_MODE == '422':
+ jpeg.PX_PLANE0_TILING_H = 2
+ jpeg.PX_PLANE0_TILING_V = 8 // decode_scale
+ jpeg.PX_PLANE1_TILING_H = 2
+ jpeg.PX_PLANE1_TILING_V = 8 // decode_scale
+ elif jpeg_MODE == '420':
+ jpeg.PX_PLANE0_TILING_H = 2
+ jpeg.PX_PLANE0_TILING_V = 16 // decode_scale
+ jpeg.PX_PLANE1_TILING_H = 2
+ jpeg.PX_PLANE1_TILING_V = 16 // decode_scale
+ elif jpeg_MODE == '411':
+ jpeg.PX_PLANE0_TILING_H = 4
+ jpeg.PX_PLANE0_TILING_V = 8 // decode_scale
+ jpeg.PX_PLANE1_TILING_H = 4
+ jpeg.PX_PLANE1_TILING_V = 8 // decode_scale
+ else:
+ assert False
+ elif pixfmt == 'YUV420-planar':
+ if jpeg_MODE == '444' or jpeg_MODE == '400':
+ jpeg.PX_PLANE0_TILING_H = 2
+ jpeg.PX_PLANE0_TILING_V = 8 // decode_scale
+ jpeg.PX_PLANE1_TILING_H = 2
+ jpeg.PX_PLANE1_TILING_V = 4 // decode_scale
+ elif jpeg_MODE == '422':
+ jpeg.PX_PLANE0_TILING_H = 2
+ jpeg.PX_PLANE0_TILING_V = 8 // decode_scale
+ jpeg.PX_PLANE1_TILING_H = 2
+ jpeg.PX_PLANE1_TILING_V = 4 // decode_scale
+ elif jpeg_MODE == '420':
+ jpeg.PX_PLANE0_TILING_H = 2
+ jpeg.PX_PLANE0_TILING_V = 16 // decode_scale
+ jpeg.PX_PLANE1_TILING_H = 2
+ jpeg.PX_PLANE1_TILING_V = 8 // decode_scale
+ elif jpeg_MODE == '411':
+ jpeg.PX_PLANE0_TILING_H = 4
+ jpeg.PX_PLANE0_TILING_V = 8 // decode_scale
+ jpeg.PX_PLANE1_TILING_H = 4
+ jpeg.PX_PLANE1_TILING_V = 4 // decode_scale
+ else:
+ assert False
+ elif pixfmt == 'YUV444-planar':
+ if jpeg_MODE == '444' or jpeg_MODE == '400':
+ jpeg.PX_PLANE0_TILING_H = 1
+ jpeg.PX_PLANE0_TILING_V = 8 // decode_scale
+ jpeg.PX_PLANE1_TILING_H = 2
+ jpeg.PX_PLANE1_TILING_V = 8 // decode_scale
+ elif jpeg_MODE == '422':
+ # The driver doesn't use this, but guessing seems to be fine?
+ jpeg.PX_PLANE0_TILING_H = 2
+ jpeg.PX_PLANE0_TILING_V = 8 // decode_scale
+ jpeg.PX_PLANE1_TILING_H = 4
+ jpeg.PX_PLANE1_TILING_V = 8 // decode_scale
+ elif jpeg_MODE == '420':
+ # The driver doesn't use this, but guessing seems to be fine?
+ jpeg.PX_PLANE0_TILING_H = 2
+ jpeg.PX_PLANE0_TILING_V = 16 // decode_scale
+ jpeg.PX_PLANE1_TILING_H = 4
+ jpeg.PX_PLANE1_TILING_V = 16 // decode_scale
+ elif jpeg_MODE == '411':
+ # The driver doesn't use this, but guessing seems to be fine?
+ jpeg.PX_PLANE0_TILING_H = 4
+ jpeg.PX_PLANE0_TILING_V = 8 // decode_scale
+ jpeg.PX_PLANE1_TILING_H = 8
+ jpeg.PX_PLANE1_TILING_V = 8 // decode_scale
+ else:
+ assert False
+ else:
+ assert False
+
+ if pixfmt in ['RGBA', 'BGRA', 'RGB565', 'YUV444-planar']:
+ if jpeg_MODE in ['422', '420']:
+ jpeg.CHROMA_DOUBLE_H = 1
+
+ if jpeg_MODE == '411':
+ jpeg.CHROMA_QUADRUPLE_H = 1
+
+ if jpeg_MODE == '420':
+ jpeg.CHROMA_DOUBLE_V = 1
+ elif pixfmt in ["YUV422-CbYCrY", "YUV422-YCbYCr", "YUV422-planar"]:
+ if jpeg_MODE == '444':
+ jpeg.CHROMA_HALVE_H_TYPE1 = 1
+
+ if jpeg_MODE == '411':
+ jpeg.CHROMA_DOUBLE_H = 1
+
+ if jpeg_MODE == '420':
+ jpeg.CHROMA_DOUBLE_V = 1
+ elif pixfmt in ["YUV420-planar"]:
+ if jpeg_MODE == '444':
+ jpeg.CHROMA_HALVE_H_TYPE1 = 1
+
+ if jpeg_MODE in ['444', '422', '411']:
+ jpeg.CHROMA_HALVE_V_TYPE1 = 1
+
+ if jpeg_MODE == '411':
+ jpeg.CHROMA_DOUBLE_H = 1
+ else:
+ assert False
+
+ jpeg.MATRIX_MULT[0].val = 0x100
+ jpeg.MATRIX_MULT[1].val = 0x0
+ jpeg.MATRIX_MULT[2].val = 0x167
+ jpeg.MATRIX_MULT[3].val = 0x100
+ jpeg.MATRIX_MULT[4].val = 0xffffffa8
+ jpeg.MATRIX_MULT[5].val = 0xffffff49
+ jpeg.MATRIX_MULT[6].val = 0x100
+ jpeg.MATRIX_MULT[7].val = 0x1c6
+ jpeg.MATRIX_MULT[8].val = 0x0
+ jpeg.MATRIX_MULT[9].val = 0x0
+ jpeg.MATRIX_MULT[10].val = 0xffffff80
+
+ jpeg.RGBA_ALPHA = args.decode_rgba_alpha
+ jpeg.RGBA_ORDER = pixfmt == "RGBA"
+ jpeg.YUV422_ORDER = pixfmt == "YUV422-YCbYCr"
+
+ if decode_scale == 1:
+ jpeg.SCALE_FACTOR.set(SCALE=E_SCALE.DIV1)
+ elif decode_scale == 2:
+ jpeg.SCALE_FACTOR.set(SCALE=E_SCALE.DIV2)
+ elif decode_scale == 4:
+ jpeg.SCALE_FACTOR.set(SCALE=E_SCALE.DIV4)
+ elif decode_scale == 8:
+ jpeg.SCALE_FACTOR.set(SCALE=E_SCALE.DIV8)
+ else:
+ assert False
+
+ jpeg.INPUT_START1 = input_buf_iova
+ jpeg.INPUT_START2 = 0xdeadbeef
+ jpeg.INPUT_END = input_buf_iova + input_mem_sz
+ jpeg.OUTPUT_START1 = output_buf_iova
+ jpeg.OUTPUT_START2 = output_buf_iova + surface_P1_off
+ jpeg.OUTPUT_END = output_buf_iova + output_mem_sz
+ jpeg.PX_PLANE0_STRIDE = surface_stride
+ jpeg.PX_PLANE1_STRIDE = surface_P1_stride
+
+ jpeg.REG_0x1ac = 0x0
+ jpeg.REG_0x1b0 = 0x0
+ jpeg.REG_0x1b4 = 0x0
+ jpeg.REG_0x1bc = 0x0
+ jpeg.REG_0x1c0 = 0x0
+ jpeg.REG_0x1c4 = 0x0
+
+ jpeg.REG_0x118 = 0x0
+ jpeg.REG_0x11c = 0x1
+
+ jpeg.MODE = 0x177
+ jpeg.REG_0x1028 = 0x400
+
+ jpeg.JPEG_IO_FLAGS = 0x3f
+ jpeg.REG_0x0 = 0x1
+ jpeg.REG_0x1004 = 0x1
+
+ # FIXME: we don't actually know when it's done
+ time.sleep(1)
+
+ print(jpeg.STATUS.reg)
+ print(jpeg.PERFCOUNTER.reg)
+
+ output_data = iface.readmem(output_buf_phys, output_mem_sz)
+ if args.raw_output is not None:
+ with open(args.raw_output, 'wb') as f:
+ f.write(output_data)
+
+ # Just for demonstration purposes, wrangle everything back into RGB
+ with Image.new(
+ mode='RGBA',
+ size=(jpeg_W // decode_scale, jpeg_H // decode_scale)) as im:
+ if pixfmt in ["RGBA", "BGRA", "RGB565"]:
+ for y in range(jpeg_H // decode_scale):
+ for x in range(jpeg_W // decode_scale):
+ block = output_data[
+ y*surface_stride + x*BYTESPP:
+ y*surface_stride + (x+1)*BYTESPP]
+
+ if pixfmt == "RGBA":
+ r, g, b, a = block
+ elif pixfmt == "BGRA":
+ b, g, r, a = block
+ elif pixfmt == "RGB565":
+ rgb = struct.unpack("<H", block)[0]
+ b = (rgb & 0b11111) << 3
+ g = ((rgb >> 5) & 0b111111) << 2
+ r = ((rgb >> 11) & 0b11111) << 3
+ a = 255
+ else:
+ assert False
+ im.putpixel((x, y), (r, g, b, a))
+ elif pixfmt in ["YUV422-CbYCrY", "YUV422-YCbYCr"]:
+ for y in range(jpeg_H // decode_scale):
+ for x in range(0, jpeg_W // decode_scale, 2):
+ block = output_data[
+ y*surface_stride + x*BYTESPP:
+ y*surface_stride + (x+2)*BYTESPP]
+
+ if pixfmt == "YUV422-CbYCrY":
+ cb, y0, cr, y1 = block
+ elif pixfmt == "YUV422-YCbYCr":
+ y0, cb, y1, cr = block
+
+ r0, g0, b0 = yuv2rgb(y0, cb, cr)
+ r1, g1, b1 = yuv2rgb(y1, cb, cr)
+
+ im.putpixel((x, y), (r0, g0, b0, 255))
+ # XXX this really needs some fixing
+ if x+1 < jpeg_W // decode_scale:
+ im.putpixel((x+1, y), (r1, g1, b1, 255))
+ elif pixfmt == "YUV422-planar":
+ for y in range(jpeg_H // decode_scale):
+ for x in range(jpeg_W // decode_scale):
+ y_ = output_data[y*surface_stride + x]
+ cb = output_data[surface_P1_off + y*surface_P1_stride + x&~1]
+ cr = output_data[surface_P1_off + y*surface_P1_stride + (x&~1)+1]
+
+ r, g, b = yuv2rgb(y_, cb, cr)
+
+ im.putpixel((x, y), (r, g, b, 255))
+ elif pixfmt == "YUV420-planar":
+ for y in range(jpeg_H // decode_scale):
+ for x in range(jpeg_W // decode_scale):
+ y_ = output_data[y*surface_stride + x]
+ cb = output_data[surface_P1_off + (y//2)*surface_P1_stride + x&~1]
+ cr = output_data[surface_P1_off + (y//2)*surface_P1_stride + (x&~1)+1]
+
+ r, g, b = yuv2rgb(y_, cb, cr)
+
+ im.putpixel((x, y), (r, g, b, 255))
+ elif pixfmt == "YUV444-planar":
+ for y in range(jpeg_H // decode_scale):
+ for x in range(jpeg_W // decode_scale):
+ y_ = output_data[y*surface_stride + x]
+ cb = output_data[surface_P1_off + y*surface_P1_stride + x*2]
+ cr = output_data[surface_P1_off + y*surface_P1_stride + x*2+1]
+
+ r, g, b = yuv2rgb(y_, cb, cr)
+
+ im.putpixel((x, y), (r, g, b, 255))
+ else:
+ assert False
+ im.save(args.output)
+
+if args.encode:
+ iface.writemem(input_buf_phys, image_data)
+ iface.writemem(input_buf_phys + surface_P1_off, image_data_P1)
+ print("Pixel data uploaded")
+
+ jpeg.MODE = 0x17f
+ jpeg.REG_0x38 = 0x1 # if not set nothing happens
+ jpeg.REG_0x2c = 0x1 # if not set only header is output
+ jpeg.REG_0x34 = 0x0 # if set output is a JPEG but weird with no footer
+
+ if args.encode_subsampling == '444':
+ jpeg.CODEC.set(CODEC=E_CODEC._444)
+ elif args.encode_subsampling == '422':
+ jpeg.CODEC.set(CODEC=E_CODEC._422)
+ elif args.encode_subsampling == '420':
+ jpeg.CODEC.set(CODEC=E_CODEC._420)
+ elif args.encode_subsampling == '400':
+ jpeg.CODEC.set(CODEC=E_CODEC._400)
+ else:
+ assert False
+
+ if BYTESPP_P1 != 0:
+ jpeg.PX_USE_PLANE1 = 1
+ jpeg.PX_PLANE1_WIDTH = im_W*BYTESPP_P1 - 1
+ jpeg.PX_PLANE1_HEIGHT = im_H // P1_DIVH - 1
+ else:
+ jpeg.PX_USE_PLANE1 = 0
+ jpeg.PX_PLANE1_WIDTH = 0xffffffff
+ jpeg.PX_PLANE1_HEIGHT = 0xffffffff
+ jpeg.PX_PLANE0_WIDTH = im_W*BYTESPP - 1
+ jpeg.PX_PLANE0_HEIGHT = im_H - 1
+ jpeg.TIMEOUT = 266000000
+
+ jpeg.PX_TILES_W = divroundup(im_W, macroblock_W)
+ jpeg.PX_TILES_H = divroundup(im_H, macroblock_H)
+ if pixfmt in ['RGB888', 'RGB101010', 'YUV10']:
+ if args.encode_subsampling == '444' or args.encode_subsampling == '400':
+ jpeg.PX_PLANE0_TILING_H = 4
+ jpeg.PX_PLANE0_TILING_V = 8
+ jpeg.PX_PLANE1_TILING_H = 1
+ jpeg.PX_PLANE1_TILING_V = 1
+ elif args.encode_subsampling == '422':
+ jpeg.PX_PLANE0_TILING_H = 8
+ jpeg.PX_PLANE0_TILING_V = 8
+ jpeg.PX_PLANE1_TILING_H = 1
+ jpeg.PX_PLANE1_TILING_V = 1
+ elif args.encode_subsampling == '420':
+ jpeg.PX_PLANE0_TILING_H = 8
+ jpeg.PX_PLANE0_TILING_V = 16
+ jpeg.PX_PLANE1_TILING_H = 0
+ jpeg.PX_PLANE1_TILING_V = 0
+ else:
+ assert False
+ elif pixfmt == 'RGB565':
+ if args.encode_subsampling == '444' or args.encode_subsampling == '400':
+ jpeg.PX_PLANE0_TILING_H = 2
+ jpeg.PX_PLANE0_TILING_V = 8
+ jpeg.PX_PLANE1_TILING_H = 1
+ jpeg.PX_PLANE1_TILING_V = 1
+ elif args.encode_subsampling == '422':
+ jpeg.PX_PLANE0_TILING_H = 4
+ jpeg.PX_PLANE0_TILING_V = 8
+ jpeg.PX_PLANE1_TILING_H = 1
+ jpeg.PX_PLANE1_TILING_V = 1
+ elif args.encode_subsampling == '420':
+ jpeg.PX_PLANE0_TILING_H = 4
+ jpeg.PX_PLANE0_TILING_V = 16
+ jpeg.PX_PLANE1_TILING_H = 0
+ jpeg.PX_PLANE1_TILING_V = 0
+ else:
+ assert False
+ elif pixfmt == 'YUV-linear':
+ if args.encode_subsampling == '444' or args.encode_subsampling == '400':
+ jpeg.PX_PLANE0_TILING_H = 2
+ jpeg.PX_PLANE0_TILING_V = 8
+ jpeg.PX_PLANE1_TILING_H = 1
+ jpeg.PX_PLANE1_TILING_V = 1
+ elif args.encode_subsampling == '422':
+ jpeg.PX_PLANE0_TILING_H = 4
+ jpeg.PX_PLANE0_TILING_V = 8
+ jpeg.PX_PLANE1_TILING_H = 1
+ jpeg.PX_PLANE1_TILING_V = 1
+ elif args.encode_subsampling == '420':
+ jpeg.PX_PLANE0_TILING_H = 4
+ jpeg.PX_PLANE0_TILING_V = 16
+ jpeg.PX_PLANE1_TILING_H = 0
+ jpeg.PX_PLANE1_TILING_V = 0
+ else:
+ assert False
+ elif pixfmt == 'YUV444-planar':
+ if args.encode_subsampling == '444' or args.encode_subsampling == '400':
+ jpeg.PX_PLANE0_TILING_H = 1
+ jpeg.PX_PLANE0_TILING_V = 8
+ jpeg.PX_PLANE1_TILING_H = 2
+ jpeg.PX_PLANE1_TILING_V = 8
+ elif args.encode_subsampling == '422':
+ jpeg.PX_PLANE0_TILING_H = 2
+ jpeg.PX_PLANE0_TILING_V = 8
+ jpeg.PX_PLANE1_TILING_H = 4
+ jpeg.PX_PLANE1_TILING_V = 8
+ elif args.encode_subsampling == '420':
+ jpeg.PX_PLANE0_TILING_H = 2
+ jpeg.PX_PLANE0_TILING_V = 16
+ jpeg.PX_PLANE1_TILING_H = 4
+ jpeg.PX_PLANE1_TILING_V = 16
+ else:
+ assert False
+ elif pixfmt == 'YUV422-planar':
+ if args.encode_subsampling == '444' or args.encode_subsampling == '400':
+ jpeg.PX_PLANE0_TILING_H = 1
+ jpeg.PX_PLANE0_TILING_V = 8
+ jpeg.PX_PLANE1_TILING_H = 1
+ jpeg.PX_PLANE1_TILING_V = 8
+ elif args.encode_subsampling == '422':
+ jpeg.PX_PLANE0_TILING_H = 2
+ jpeg.PX_PLANE0_TILING_V = 8
+ jpeg.PX_PLANE1_TILING_H = 2
+ jpeg.PX_PLANE1_TILING_V = 8
+ elif args.encode_subsampling == '420':
+ jpeg.PX_PLANE0_TILING_H = 2
+ jpeg.PX_PLANE0_TILING_V = 16
+ jpeg.PX_PLANE1_TILING_H = 2
+ jpeg.PX_PLANE1_TILING_V = 16
+ else:
+ assert False
+ elif pixfmt == 'YUV420-planar':
+ if args.encode_subsampling == '444' or args.encode_subsampling == '400':
+ jpeg.PX_PLANE0_TILING_H = 1
+ jpeg.PX_PLANE0_TILING_V = 8
+ jpeg.PX_PLANE1_TILING_H = 1
+ jpeg.PX_PLANE1_TILING_V = 4
+ elif args.encode_subsampling == '422':
+ jpeg.PX_PLANE0_TILING_H = 2
+ jpeg.PX_PLANE0_TILING_V = 8
+ jpeg.PX_PLANE1_TILING_H = 2
+ jpeg.PX_PLANE1_TILING_V = 4
+ elif args.encode_subsampling == '420':
+ jpeg.PX_PLANE0_TILING_H = 2
+ jpeg.PX_PLANE0_TILING_V = 16
+ jpeg.PX_PLANE1_TILING_H = 2
+ jpeg.PX_PLANE1_TILING_V = 8
+ else:
+ assert False
+ else:
+ assert False
+ jpeg.PX_PLANE0_STRIDE = surface_stride
+ jpeg.PX_PLANE1_STRIDE = surface_P1_stride
+
+ if pixfmt in ['RGB888', 'RGB101010', 'RGB565', 'YUV10', 'YUV444-planar']:
+ if args.encode_subsampling in ['422', '420']:
+ jpeg.CHROMA_HALVE_H_TYPE1 = 1
+ if args.encode_subsampling == '420':
+ jpeg.CHROMA_HALVE_V_TYPE1 = 1
+ elif pixfmt in ['YUV-linear', 'YUV422-planar']:
+ if args.encode_subsampling == '420':
+ jpeg.CHROMA_HALVE_V_TYPE1 = 1
+ elif pixfmt == 'YUV420-planar':
+ if args.encode_subsampling in ['422', '444']:
+ jpeg.CHROMA_DOUBLE_V = 1
+ else:
+ assert False
+
+ # none of this seems to affect anything????
+ jpeg.REG_0x94 = 0xc # c/2 for 444; 8/2 for 422; 3/1 for 411; b/2 for 400
+ jpeg.REG_0x98 = 0x2
+ jpeg.REG_0x20c = im_W
+ jpeg.REG_0x210 = im_H
+
+ if pixfmt in ['RGB888', 'RGB101010', 'RGB565']:
+ jpeg.CONVERT_COLOR_SPACE = 1
+ jpeg.MATRIX_MULT[0].val = 0x4d
+ jpeg.MATRIX_MULT[1].val = 0x96
+ jpeg.MATRIX_MULT[2].val = 0x1d
+ jpeg.MATRIX_MULT[3].val = 0xffffffd5
+ jpeg.MATRIX_MULT[4].val = 0xffffffab
+ jpeg.MATRIX_MULT[5].val = 0x80
+ jpeg.MATRIX_MULT[6].val = 0x80
+ jpeg.MATRIX_MULT[7].val = 0xffffff95
+ jpeg.MATRIX_MULT[8].val = 0xffffffeb
+ jpeg.MATRIX_MULT[9].val = 0x0
+ jpeg.MATRIX_MULT[10].val = 0x80
+
+ if pixfmt == 'RGB888':
+ jpeg.ENCODE_PIXEL_FORMAT.set(FORMAT=E_ENCODE_PIXEL_FORMAT.RGB888)
+ elif pixfmt == 'RGB101010':
+ jpeg.ENCODE_PIXEL_FORMAT.set(FORMAT=E_ENCODE_PIXEL_FORMAT.RGB101010)
+ elif pixfmt == 'RGB565':
+ jpeg.ENCODE_PIXEL_FORMAT.set(FORMAT=E_ENCODE_PIXEL_FORMAT.RGB565)
+ elif pixfmt == 'YUV10':
+ jpeg.ENCODE_PIXEL_FORMAT.set(FORMAT=E_ENCODE_PIXEL_FORMAT.YUV10_linear)
+ elif pixfmt == 'YUV-linear':
+ jpeg.ENCODE_PIXEL_FORMAT.set(FORMAT=E_ENCODE_PIXEL_FORMAT.YUV_linear)
+ elif pixfmt in ['YUV444-planar', 'YUV422-planar', 'YUV420-planar']:
+ jpeg.ENCODE_PIXEL_FORMAT.set(FORMAT=E_ENCODE_PIXEL_FORMAT.YUV_planar)
+ else:
+ assert False
+ if pixfmt == 'YUV-linear':
+ jpeg.ENCODE_COMPONENT0_POS = 0
+ jpeg.ENCODE_COMPONENT1_POS = 1
+ jpeg.ENCODE_COMPONENT2_POS = 3
+ jpeg.ENCODE_COMPONENT3_POS = 2
+ elif pixfmt in ['YUV422-planar', 'YUV420-planar', 'YUV444-planar']:
+ jpeg.ENCODE_COMPONENT0_POS = 0
+ jpeg.ENCODE_COMPONENT1_POS = 0
+ jpeg.ENCODE_COMPONENT2_POS = 1
+ jpeg.ENCODE_COMPONENT3_POS = 3
+ else:
+ jpeg.ENCODE_COMPONENT0_POS = 0
+ jpeg.ENCODE_COMPONENT1_POS = 1
+ jpeg.ENCODE_COMPONENT2_POS = 2
+ jpeg.ENCODE_COMPONENT3_POS = 3
+
+ jpeg.INPUT_START1 = input_buf_iova
+ jpeg.INPUT_START2 = input_buf_iova + surface_P1_off
+ jpeg.INPUT_END = input_buf_iova + input_mem_sz + 7 # NOTE +7
+ jpeg.OUTPUT_START1 = output_buf_iova
+ jpeg.OUTPUT_START2 = 0xdeadbeef
+ jpeg.OUTPUT_END = output_buf_iova + output_mem_sz
+
+ jpeg.REG_0x118 = 0x1
+ jpeg.REG_0x11c = 0x0
+
+ jpeg.ENABLE_RST_LOGGING = args.encode_rst_interval is not None
+
+ jpeg.MODE = 0x16f
+ if args.encode_subsampling == '444':
+ jpeg_subsampling = E_JPEG_IO_FLAGS_SUBSAMPLING._444
+ elif args.encode_subsampling == '422':
+ jpeg_subsampling = E_JPEG_IO_FLAGS_SUBSAMPLING._422
+ elif args.encode_subsampling == '420':
+ jpeg_subsampling = E_JPEG_IO_FLAGS_SUBSAMPLING._420
+ elif args.encode_subsampling == '400':
+ jpeg_subsampling = E_JPEG_IO_FLAGS_SUBSAMPLING._400
+ else:
+ assert False
+ jpeg.JPEG_IO_FLAGS.set(
+ OUTPUT_8BYTE_CHUNKS_CORRECTLY=1,
+ OUTPUT_MACROBLOCKS_UNFLIPPED_H=1,
+ SUBSAMPLING_MODE=jpeg_subsampling
+ )
+ jpeg.JPEG_WIDTH = im_W
+ jpeg.JPEG_HEIGHT = im_H
+ if args.encode_rst_interval is not None:
+ jpeg.RST_INTERVAL = args.encode_rst_interval
+ else:
+ jpeg.RST_INTERVAL = 0
+ jpeg.JPEG_OUTPUT_FLAGS = 0
+
+ jpeg.QTBL[0].val = 0xa06e64a0
+ jpeg.QTBL[1].val = 0xf0ffffff
+ jpeg.QTBL[2].val = 0x78788cbe
+ jpeg.QTBL[3].val = 0xffffffff
+ jpeg.QTBL[4].val = 0x8c82a0f0
+ jpeg.QTBL[5].val = 0xffffffff
+ jpeg.QTBL[6].val = 0x8caadcff
+ jpeg.QTBL[7].val = 0xffffffff
+ jpeg.QTBL[8].val = 0xb4dcffff
+ jpeg.QTBL[9].val = 0xffffffff
+ jpeg.QTBL[10].val = 0xf0ffffff
+ jpeg.QTBL[11].val = 0xffffffff
+ jpeg.QTBL[12].val = 0xffffffff
+ jpeg.QTBL[13].val = 0xffffffff
+ jpeg.QTBL[14].val = 0xffffffff
+ jpeg.QTBL[15].val = 0xffffffff
+
+ jpeg.QTBL[16].val = 0xaab4f0ff
+ jpeg.QTBL[17].val = 0xffffffff
+ jpeg.QTBL[18].val = 0xb4d2ffff
+ jpeg.QTBL[19].val = 0xffffffff
+ jpeg.QTBL[20].val = 0xf0ffffff
+ jpeg.QTBL[21].val = 0xffffffff
+ jpeg.QTBL[22].val = 0xffffffff
+ jpeg.QTBL[23].val = 0xffffffff
+ jpeg.QTBL[24].val = 0xffffffff
+ jpeg.QTBL[25].val = 0xffffffff
+ jpeg.QTBL[26].val = 0xffffffff
+ jpeg.QTBL[27].val = 0xffffffff
+ jpeg.QTBL[28].val = 0xffffffff
+ jpeg.QTBL[29].val = 0xffffffff
+ jpeg.QTBL[30].val = 0xffffffff
+ jpeg.QTBL[31].val = 0xffffffff
+
+ jpeg.QTBL[32].val = 0x01010201
+ jpeg.QTBL[33].val = 0x01020202
+ jpeg.QTBL[34].val = 0x02030202
+ jpeg.QTBL[35].val = 0x03030604
+ jpeg.QTBL[36].val = 0x03030303
+ jpeg.QTBL[37].val = 0x07050804
+ jpeg.QTBL[38].val = 0x0608080a
+ jpeg.QTBL[39].val = 0x0908070b
+ jpeg.QTBL[40].val = 0x080a0e0d
+ jpeg.QTBL[41].val = 0x0b0a0a0c
+ jpeg.QTBL[42].val = 0x0a08080b
+ jpeg.QTBL[43].val = 0x100c0c0d
+ jpeg.QTBL[44].val = 0x0f0f0f0f
+ jpeg.QTBL[45].val = 0x090b1011
+ jpeg.QTBL[46].val = 0x0f0e110d
+ jpeg.QTBL[47].val = 0x0e0e0e01
+
+ jpeg.QTBL[48].val = 0x04040405
+ jpeg.QTBL[49].val = 0x04050905
+ jpeg.QTBL[50].val = 0x05090f0a
+ jpeg.QTBL[51].val = 0x080a0f1a
+ jpeg.QTBL[52].val = 0x13090913
+ jpeg.QTBL[53].val = 0x1a1a1a1a
+ jpeg.QTBL[54].val = 0x0d1a1a1a
+ jpeg.QTBL[55].val = 0x1a1a1a1a
+ jpeg.QTBL[56].val = 0x1a1a1a1a
+ jpeg.QTBL[57].val = 0x1a1a1a1a
+ jpeg.QTBL[58].val = 0x1a1a1a1a
+ jpeg.QTBL[59].val = 0x1a1a1a1a
+ jpeg.QTBL[60].val = 0x1a1a1a1a
+ jpeg.QTBL[61].val = 0x1a1a1a1a
+ jpeg.QTBL[62].val = 0x1a1a1a1a
+ jpeg.QTBL[63].val = 0x1a1a1a1a
+
+ jpeg.HUFFMAN_TABLE.val = 0x3c
+ jpeg.QTBL_SEL.val = 0xff
+ jpeg.REG_0x0.val = 0x1
+ jpeg.REG_0x1004.val = 0x1
+
+ # FIXME: we don't actually know when it's done
+ time.sleep(1)
+
+ print(jpeg.STATUS.reg)
+ print(jpeg.PERFCOUNTER.reg)
+ jpeg_out_sz = jpeg.COMPRESSED_BYTES.val
+ print(f"JPEG output is {jpeg_out_sz} bytes")
+
+ rst_log_n = jpeg.RST_LOG_ENTRIES.val
+ for i in range(rst_log_n):
+ print(f"RST log[{i}] = 0x{jpeg.RSTLOG[i].val:X}")
+
+ output_data = iface.readmem(output_buf_phys, output_mem_sz)
+ if args.raw_output is not None:
+ with open(args.raw_output, 'wb') as f:
+ f.write(output_data)
+ with open(args.output, 'wb') as f:
+ f.write(output_data[:jpeg_out_sz])
diff --git a/tools/proxyclient/experiments/jpeg_doc.md b/tools/proxyclient/experiments/jpeg_doc.md
new file mode 100644
index 0000000..afb32f8
--- /dev/null
+++ b/tools/proxyclient/experiments/jpeg_doc.md
@@ -0,0 +1,1019 @@
+# Apple Silicon JPEG encoder/decoder reverse engineering notes
+
+## General
+
+### REG_0x0 (+0x0000)
+
+This register is not fully understood yet. It is set to 1 to kick off an operation.
+
+This register appears to be 1 bit wide. It is readable/writable.
+
+The driver resets this register to 0.
+
+
+### REG_0x4 (+0x0004)
+
+This register is not understood yet.
+
+This register appears to be 1 bit wide. It is readable/writable.
+
+The driver resets this register to 0.
+
+
+### MODE (+0x0008)
+
+This register controls the mode of operation of the hardware. The details of this register are not understood yet.
+
+This register appears to be 10 bit wide. It is readable/writable.
+
+At minimum bits 0x043 need to be set or else reading from some registers above 0x1000 will fault.
+
+This register is set to multiple different values throughout the reset process.
+
+
+### REG_0xc (+0x000C)
+
+This register is not understood yet.
+
+This register is at least 10 bit wide. It appears to be read-only. The power-up state appears to be 0x200.
+
+The driver reads this register and stores the value after an interrupt occurs.
+
+
+### REG_0x10 (+0x0010)
+### REG_0x14 (+0x0014)
+
+No access to this register has been observed.
+
+This register is at least 11 bit wide. It appears to be read-only. The power-up state appears to be 0x400.
+
+
+### REG_0x18 (+0x0018)
+
+No access to this register has been observed.
+
+This register is at least 8 bit wide. It appears to be read-only. The power-up state appears to be 0x55.
+
+
+### (+0x001C)
+
+No access to this register has been observed.
+
+It appears to be read-only. The power-up state appears to be 0.
+
+
+### REG_0x20 (+0x0020)
+
+This register is not understood yet.
+
+This register appears to be 11 bit wide. It is readable/writable.
+
+The driver resets this register to 0xff, and it is written with a 0 after an interrupt occurs.
+
+
+### STATUS (+0x0024)
+
+- bit0: Operation is completed ???
+- bit1: Timeout occurred
+- bit2: Read buffer overflow
+- bit3: Write buffer overflow
+- bit4: Codec buffer overflow
+- bit5: Some kind of error, happens if macroblock settings are messed up
+- bit6: AXI error
+- bit7: The driver checks for this after an interrupt, but the meaning is not understood
+
+
+### CODEC (+0x0028)
+
+This register controls how the JPEG data is processed wrt subsampling mode. It affects both encode and decode.
+
+- 0 = 4:4:4
+- 1 = 4:2:2
+- 2 = 4:1:1
+- 3 = 4:2:0
+- 4 = 4:0:0
+
+### REG_0x2c (+0x002C)
+
+This register is not fully understood yet.
+
+This register appears to be 1 bit wide. It is readable/writable.
+
+The driver sets this register to 0 when decoding and 1 when encoding. If it is not set to 1 when encoding, only headers will be output. The interrupt handler makes a decision based on this register.
+
+
+### REG_0x30 (+0x0030)
+
+This register is not understood yet.
+
+This register appears to be 1 bit wide. It is readable/writable.
+
+The driver resets this register to 0.
+
+
+### REG_0x34 (+0x0034)
+
+This register is not fully understood yet.
+
+This register appears to be 1 bit wide. It is readable/writable.
+
+The driver sets this register to 1 when decoding and 0 when encoding. If it is not set to 0 when encoding, the output will be corrupted in some way.
+
+
+### REG_0x38 (+0x0038)
+
+This register is not fully understood yet.
+
+This register appears to be 1 bit wide. It is readable/writable.
+
+The driver sets this register to 0 when decoding and 1 when encoding. If it is not set to 1 when encoding, nothing will be output. If it is set to 1 when decoding, the output will be a weird tiled format.
+
+
+
+## Chroma control
+
+### CHROMA_HALVE_H_TYPE1 (+0x003c)
+### CHROMA_HALVE_H_TYPE2 (+0x0040)
+
+Setting these register to 1 causes chroma to be subsampled horizontally.
+
+The second register produces a different result from the first register. It is speculated that this is related to chroma siting, but this has not been verified yet. If both the second and the first register are set, the second appears to win.
+
+
+### CHROMA_HALVE_V_TYPE1 (+0x0044)
+### CHROMA_HALVE_V_TYPE2 (+0x0048)
+
+Setting these register to 1 causes chroma to be subsampled vertically.
+
+The second register produces a different result from the first register. It is speculated that this is related to chroma siting, but this has not been verified yet. If both the second and the first register are set, the second appears to win.
+
+
+### CHROMA_DOUBLE_H (+0x004c)
+
+Setting this register to 1 causes chroma to be doubled/interpolated horizontally.
+
+
+### CHROMA_QUADRUPLE_H (+0x0050)
+
+Setting this register to 1 causes chroma to be quadrupled/interpolated horizontally. If both this and the previous register are set, double appears to win.
+
+
+### CHROMA_DOUBLE_V (+0x0054)
+
+Setting this register to 1 causes chroma to be doubled/interpolated vertically.
+
+
+## Pixel data control
+
+### PX_USE_PLANE1 (+0x0058)
+
+Setting this register to 1 enables use of the second pixel plane.
+
+
+### PX_TILES_W (+0x005c)
+
+This register specifies the width of the image in tiles/MCUs/macroblocks, where the macroblock size depends on the chroma subsampling mode, i.e. divroundup by 8 for 4:4:4, by 16 for 4:2:2 and 4:2:0, by 32 for 4:1:1 (FIXME verify this again).
+
+This register is 16 bits wide.
+
+
+### PX_TILES_H (+0x0060)
+
+This register specifies the height of the image in tiles/MCUs/macroblocks, where the macroblock size depends on the chroma subsampling mode, i.e. divroundup by 16 for 4:2:0 or else by 8 (FIXME verify this again).
+
+This register is 16 bits wide.
+
+
+### PX_PLANE0_WIDTH (+0x0064)
+
+This register specifies the width of the image data in plane 0, in bytes, minus 1. When decoding, it is important to set this correctly for the edge to be processed properly.
+
+This register is 20 bits wide, even though the driver will sometimes write 0xffffffff.
+
+
+### PX_PLANE0_HEIGHT (+0x0068)
+
+This register specifies the height of the image data in plane 0, in rows, minus 1. When decoding, it might be important to set this correctly for the edge to be processed properly.
+
+This register is 16 bits wide, even though the driver will sometimes write 0xffffffff.
+
+
+### PX_PLANE0_TILING_H (+0x006c)
+
+This register somehow controls how pixel data matches up with subsampled chroma data, but the details are not understood yet. Valid range 0-31.
+
+
+### PX_PLANE0_TILING_V (+0x0070)
+
+This register somehow controls how pixel data matches up with subsampled chroma data, but the details are not understood yet. Valid range 0-31.
+
+
+### PX_PLANE0_STRIDE (+0x0074)
+
+This is the row stride of plane 0 in bytes.
+
+This register is 24 bits wide.
+
+
+### PX_PLANE1_WIDTH (+0x0078)
+### PX_PLANE1_HEIGHT (+0x007c)
+### PX_PLANE1_TILING_H (+0x0080)
+### PX_PLANE1_TILING_V (+0x0084)
+### PX_PLANE1_STRIDE (+0x0088)
+
+These registers function similarly to the plane 0 registers.
+
+
+## Input/output pointers
+
+### INPUT_START1 (+0x008c)
+
+Input pointer 1 IOVA.
+
+
+### INPUT_START2 (+0x0090)
+
+Input pointer 2 IOVA.
+
+
+### REG_0x94 (+0x0094)
+
+This register is not understood yet.
+
+The driver sets this register to a fixed value of 0x1f when decoding and to a value that depends on the chroma subsampling mode when encoding (0xc for 4:4:4, 0x8 for 4:2:2, 0x3 for 4:2:0, 0xb for 4:0:0), but changing it does not seem to do anything.
+
+This register is 6 bits wide.
+
+
+### REG_0x98 (+0x0098)
+
+This register is not understood yet.
+
+The driver sets this register to a fixed value of 1 when decoding and to a value that depends on the chroma subsampling mode when encoding (2 for 4:4:4/4:2:2/4:0:0, 1 for 4:2:0), but changing it does not seem to do anything.
+
+This register is 6 bits wide.
+
+
+### INPUT_END (+0x009c)
+
+End of input data IOVA.
+
+For reasons that are not understood, this is ORed with 7 when encoding.
+
+
+### OUTPUT_START1 (+0x00a0)
+
+Output pointer 1 IOVA.
+
+
+### OUTPUT_START2 (+0x00a4)
+
+Output pointer 2 IOVA.
+
+
+### OUTPUT_END (+0x00a8)
+
+End of output data IOVA.
+
+
+## MATRIX_MULT (+0x00ac-0x00d7) (11 entries)
+
+Color space conversion matrix.
+
+The full details of the shifting/offset/final two values is not understood yet.
+
+
+## DITHER (+0x00d8-0x00ff) (10 entries)
+
+Dithering when decoding to RGB565.
+
+The full details of this is not understood yet.
+
+
+## Encoding pixel format
+
+### ENCODE_PIXEL_FORMAT (+0x0100)
+
+- 0 = RGB101010
+- 1 = YUV10 4:4:4 linear
+- 2 = RGB888
+- 3 = RGB565
+- 4 = YUV planar (partially tested, details not fully understood)
+- 5 = YUV8 4:2:2 linear
+- 6-9 = do something, may not be useful, maybe invalid, not used by driver
+
+
+### ENCODE_COMPONENT0_POS (+0x0104)
+### ENCODE_COMPONENT1_POS (+0x0108)
+### ENCODE_COMPONENT2_POS (+0x010c)
+### ENCODE_COMPONENT3_POS (+0x0110)
+
+These registers control the positions of each component in the parsed pixel data. It is used to allow e.g. flipping between RGBA and BGRA.
+
+
+### CONVERT_COLOR_SPACE (+0x0114)
+
+Setting this register to 1 enables color space conversion when encoding
+
+
+## Unknown
+
+### REG_0x118 (+0x0118)
+
+This register is not understood yet.
+
+This register appears to be 1 bit wide. It is readable/writable.
+
+This register is set to 0 when decoding and 1 when encoding.
+
+
+### REG_0x11c (+0x011c)
+
+This register is not understood yet.
+
+This register appears to be 1 bit wide. It is readable/writable.
+
+This register is set to 1 when decoding and 0 when encoding.
+
+
+### REG_0x120 (+0x0120)
+
+This register is not understood yet.
+
+This register appears to be 1 bit wide. It is readable/writable.
+
+The driver resets this register to 0.
+
+
+### TILING_ENABLE (+0x0124)
+
+This register enables the functionality of the following two registers.
+
+The driver sets this register to 1 when decoding if the surface "is tiled."
+
+
+### TILING_PLANE0 (+0x0128)
+
+This register is not fully understood yet. A value greater than 8 causes plane 0 to be reformatted (tiled?), but the details are not understood yet.
+
+This register appears to be 5 bit wide. It is readable/writable.
+
+
+### TILING_PLANE1 (+0x012c)
+
+This register is not fully understood yet. A value greater than 8 causes plane 1 to be reformatted (tiled?), but the details are not understood yet.
+
+This register appears to be 5 bit wide. It is readable/writable.
+
+
+## Decoding image size
+
+### DECODE_MACROBLOCKS_W (+0x0130)
+
+Sets the width of the decoded image in macroblocks, where the macroblock size depends on the chroma subsampling mode, i.e. divroundup by 8 for 4:4:4, by 16 for 4:2:2 and 4:2:0, by 32 for 4:1:1.
+
+This register is 16 bits wide.
+
+
+### DECODE_MACROBLOCKS_H (+0x0134)
+
+Sets the height of the decoded image in macroblocks, where the macroblock size depends on the chroma subsampling mode, i.e. divroundup by 16 for 4:2:0 or else by 8.
+
+This register is 16 bits wide.
+
+
+### RIGHT_EDGE_PIXELS (+0x0138)
+
+The driver sets this to the number of pixels that are valid in the rightmost macroblocks, but changing it does not seem to do anything.
+
+This register is 5 bits wide.
+
+
+### BOTTOM_EDGE_PIXELS (+0x013c)
+
+The driver sets this to the number of pixels that are valid in the bottommost macroblocks, but changing it does not seem to do anything.
+
+This register is 4 bits wide.
+
+
+### RIGHT_EDGE_SAMPLES (+0x0140)
+
+The driver sets this to the number of chroma samples that are valid in the rightmost macroblocks, but changing it does not seem to do anything.
+
+This register is 3 bits wide.
+
+
+### BOTTOM_EDGE_SAMPLES (+0x0144)
+
+The driver sets this to the number of chroma samples that are valid in the bottommost macroblocks, but changing it does not seem to do anything.
+
+This register is 3 bits wide.
+
+
+### SCALE_FACTOR (+0x0148)
+
+- 0 = /1
+- 1 = /2
+- 2 = /4
+- 3 = /8
+
+This appears to be ignored when encoding.
+
+
+## Decoding pixel format
+
+### DECODE_PIXEL_FORMAT (+0x014c)
+
+- 0 = YUV 444 (2P)
+- 1 = YUV 422 (2P)
+- 2 = YUV 420 (2P)
+- 3 = YUV 422 (1P)
+- 4 = driver mentions YUV10 444 (1P) but it does not appear to work (driver also says it doesn't work)
+- 5 = RGB888
+- 6 = RGB565
+- 7 = driver mentions RGB101010 but it does not appear to work (driver also says it doesn't work)
+
+
+### YUV422_ORDER (+0x0150)
+
+- 0 = Cb Y'0 Cr Y'1
+- 1 = Y'0 Cb Y'1 Cr
+
+
+### RGBA_ORDER (+0x0154)
+
+- 0 = BGRA
+- 1 = RGBA
+
+
+### RGBA_ALPHA (+0x0158)
+
+This value is filled in to alpha bytes when decoding into RGB888
+
+
+## Unknown/status
+
+### PLANAR_CHROMA_HALVING (+0x015c)
+
+This register is not fully understood yet. Setting it seems to halve the chroma vertically when outputting into planar modes. This is different from the CHROMA_HALVE_V_* registers because it halves the final image, not each macroblock.
+
+The driver seems to use this in some cases when scaling down an image by 8, but the details of this are not understood yet.
+
+The driver resets this register to 0.
+
+
+### REG_0x160 (+0x0160)
+
+This register is not understood yet.
+
+This register appears to be 1 bit wide. It is readable/writable.
+
+The driver resets this register to a configurable value that happens to be 0.
+
+
+### REG_0x164 (+0x0164)
+
+This register is not understood yet.
+
+It appears to be read-only. The power-up state appears to be 0.
+
+The driver reads this register and stores the value after an interrupt occurs.
+
+
+### (+0x0168)
+
+No access to this register has been observed.
+
+It appears to be read-only. The power-up state appears to be 0.
+
+
+### REG_0x16c (+0x016c)
+
+This register is not understood yet.
+
+It appears to be read-only. The power-up state appears to be 0.
+
+The driver reads this register and stores the value after an interrupt occurs.
+
+
+### REG_0x170 (+0x0170)
+
+This register is not understood yet.
+
+It appears to be read-only. The power-up state appears to be 0.
+
+The driver reads this register and stores the value after an interrupt occurs.
+
+
+### (+0x0174)
+
+No access to this register has been observed.
+
+It appears to be read-only. The power-up state appears to be 0.
+
+
+### PERFCOUNTER (+0x0178)
+
+This register appears to be a performance counter. It is not yet understood what is being measured.
+
+It appears to be read-only.
+
+The driver reads this register and accumulates it after an interrupt occurs.
+
+
+### (+0x017c)
+
+No access to this register has been observed.
+
+It appears to be read-only. The power-up state appears to be 0.
+
+
+### (+0x0180)
+
+No access to this register has been observed.
+
+It appears to be read-only. The power-up state appears to be 0.
+
+
+### TIMEOUT (+0x0184)
+
+This register configures the timeout. It is not yet understood what units this is in.
+
+This register is 32 bits wide.
+
+
+### HWREV (+0x0188)
+
+This register contains the hardware revision. On the M1 Max, it is 0xd1013.
+
+
+### REG_0x18c (+0x018c)
+
+No access to this register has been observed.
+
+This register appears to be 2 bits wide. It is readable/writable.
+
+
+### REG_0x190 (+0x0190)
+
+No access to this register has been observed.
+
+This register appears to be 2 bits wide. It is readable/writable.
+
+
+### REG_0x194 (+0x0194)
+
+No access to this register has been observed.
+
+This register appears to be 4 bits wide. It is readable/writable.
+
+
+### REG_0x198 (+0x0198)
+
+No access to this register has been observed.
+
+This register appears to be 4 bits wide. It is readable/writable.
+
+
+### REG_0x19c (+0x019c)
+
+This register is not understood yet.
+
+This register appears to be 1 bit wide. It is readable/writable.
+
+The driver under some conditions writes a 1 here.
+
+
+## RST logging / unknown
+
+### ENABLE_RST_LOGGING (+0x01a0)
+
+If this register is set to 1, some data about RST blocks will be logged when encoding.
+
+
+### RST_LOG_ENTRIES (+0x01a4)
+
+This register will contain the number of RST log entries.
+
+
+### REG_0x1a8 (+0x01a8)
+### REG_0x1ac (+0x01ac)
+### REG_0x1b0 (+0x01b0)
+
+This register is not understood yet.
+
+This register appears to be 1 bit wide. It is readable/writable.
+
+
+### REG_0x1b4 (+0x01b4)
+### REG_0x1b8 (+0x01b8)
+### REG_0x1bc (+0x01bc)
+
+This register is not understood yet.
+
+This register appears to be 32 bit wide. It is readable/writable.
+
+
+### REG_0x1c0 (+0x01c0)
+### REG_0x1c4 (+0x01c4)
+
+This register is not understood yet.
+
+This register appears to be 16 bit wide. It is readable/writable.
+
+
+### REG_0x1c8 (+0x01c8)
+
+This register is not understood yet.
+
+This register appears to be 1 bit wide. It is readable/writable.
+
+
+### REG_0x1cc (+0x01cc)
+### REG_0x1d0 (+0x01d0)
+### REG_0x1d4 (+0x01d4)
+### REG_0x1d8 (+0x01d8)
+
+This register is not understood yet.
+
+This register appears to be 32 bit wide. It is readable/writable.
+
+
+### REG_0x1dc (+0x01dc)
+
+This register is not understood yet.
+
+This register appears to be 1 bit wide. It is readable/writable.
+
+
+### REG_0x1e0 (+0x01e0)
+
+This register is not understood yet.
+
+This register appears to be 14 bit wide. It is readable/writable.
+
+
+### REG_0x1e4 (+0x01e4)
+
+This register is not understood yet.
+
+This register appears to be 13 bit wide. It is readable/writable.
+
+
+### REG_0x1e8 (+0x01e8)
+
+This register is not understood yet.
+
+This register appears to be 9 bit wide. It is readable/writable.
+
+
+### REG_0x1ec (+0x01ec)
+
+This register is not understood yet.
+
+This register appears to be 1 bit wide. It is readable/writable.
+
+
+### REG_0x1f0 (+0x01f0)
+
+This register is not understood yet.
+
+This register appears to be 14 bit wide. It is readable/writable.
+
+
+### REG_0x1f4 (+0x01f4)
+
+This register is not understood yet.
+
+This register appears to be 13 bit wide. It is readable/writable.
+
+
+### REG_0x1f8 (+0x01f8)
+
+This register is not understood yet.
+
+This register appears to be 9 bit wide. It is readable/writable.
+
+
+## Compressed pixel format / Compressed DMA / unknown
+
+### REG_0x1fc (+0x01fc)
+### REG_0x200 (+0x0200)
+
+No access to this register has been observed.
+
+This register appears to be 2 bits wide. It is readable/writable.
+
+
+### REG_0x204 (+0x0204)
+### REG_0x208 (+0x0208)
+
+This register is not understood yet.
+
+This register appears to be 32 bit wide. It is readable/writable.
+
+
+### REG_0x20c (+0x020c)
+### REG_0x210 (+0x0210)
+### REG_0x214 (+0x0214)
+### REG_0x218 (+0x0218)
+
+This register is not understood yet.
+
+This register appears to be 17 bit wide. It is readable/writable.
+
+
+### REG_0x21c (+0x021c)
+### REG_0x220 (+0x0220)
+
+This register is not understood yet.
+
+This register appears to be 16 bit wide. It is readable/writable.
+
+
+### REG_0x224 (+0x0224)
+### REG_0x228 (+0x0228)
+
+This register is not understood yet.
+
+This register appears to be 17 bit wide. It is readable/writable.
+
+
+### REG_0x22c (+0x022c)
+### REG_0x230 (+0x0230)
+### REG_0x234 (+0x0234)
+
+This register is not understood yet.
+
+This register appears to be 16 bit wide. It is readable/writable.
+
+
+### REG_0x238 (+0x0238)
+
+This register is not understood yet.
+
+This register appears to be 1 bit wide. It is readable/writable.
+
+
+### REG_0x23c (+0x023c)
+### REG_0x240 (+0x0240)
+
+This register is not understood yet.
+
+This register appears to be 4 bit wide. It is readable/writable.
+
+
+### REG_0x244 (+0x0244)
+### REG_0x248 (+0x0248)
+
+This register is not understood yet.
+
+This register appears to be 8 bit wide. It is readable/writable.
+
+
+### REG_0x24c (+0x024c)
+
+This register is not understood yet.
+
+This register appears to be 1 bit wide. It is readable/writable.
+
+
+### REG_0x250 (+0x0250)
+### REG_0x254 (+0x0254)
+
+This register is not understood yet.
+
+This register appears to be 4 bit wide. It is readable/writable.
+
+
+### REG_0x258 (+0x0258)
+### REG_0x25c (+0x025c)
+
+This register is not understood yet.
+
+This register appears to be 8 bit wide. It is readable/writable.
+
+
+## REG_0x260 (+0x0260)
+## REG_0x264 (+0x0264)
+
+No access to this register has been observed.
+
+This register appears to be 1 bit wide. It is readable/writable.
+
+
+## REG_0x268 (+0x0268)
+## REG_0x26c (+0x026c)
+
+No access to this register has been observed.
+
+This register appears to be 7 bit wide. It is readable/writable.
+
+
+## (+0x0270-0x027f)
+
+No access to this register has been observed.
+
+It appears to be read-only. The power-up state appears to be 0.
+
+
+## REG_0x280 (+0x0280)
+
+No access to this register has been observed.
+
+This register appears to be 1 bit wide. It is readable/writable.
+
+
+## (+0x0284-0x0fff)
+
+No access to this register has been observed.
+
+It appears to be read-only. The power-up state appears to be 0.
+
+
+## JPEG I/O related
+
+### JPEG_IO_FLAGS (+0x1000)
+
+- bit0-2 control subsampling mode output into the JPEG when encoding
+ - 0 = 4:4:4
+ - 1 = 4:2:2
+ - 2 = 4:2:0
+ - 3 = monochrome
+ - 4 = 4 components ??? seems to work with 422 with 444 tiling params ?????
+ - 6 = indicate 4:1:1 in file, but setting CODEC = 2 doesn't actually work (broken)
+- bit3 needs to be set when decoding. It must be unset when encoding. This is not fully understood yet
+- bit4 causes macroblocks to _not_ be flipped horizontally. It affects both encoding and decoding.
+- bit5 causes chunks of 8 bytes to _not_ be reversed. It affects both encoding and decoding.
+
+
+### REG_0x1004 (+0x1004)
+
+This register is not fully understood yet. It is set to 1 to kick off an operation.
+
+Writing to this register while MODE is set incorrectly can trigger an exception.
+
+
+### REG_0x1008 (+0x1008)
+
+No access to this register has been observed.
+
+This register is at least 1 bit wide. It appears to be read-only. The power-up state appears to be 1.
+
+
+### QTBL_SEL (+0x100c)
+
+This register selects the quantization table in use for each component.
+
+- bit0-1 = component 0
+- bit2-3 = component 1
+- bit4-5 = component 2
+- bit6-7 = component 3?
+
+
+### HUFFMAN_TABLE (+0x1010)
+
+This register controls Huffman tables used. The details of this register are not fully understood yet.
+
+This register is 8 bits wide.
+
+
+### RST_INTERVAL (+0x1014)
+
+This register controls the interval at which RST markers will be generated when encoding.
+
+This register is 16 bits wide.
+
+
+### JPEG_HEIGHT (+0x1018)
+
+This register specifies the height of the JPEG when encoding. It appears to only affect the header.
+
+This register is 16 bits wide.
+
+
+### JPEG_WIDTH (+0x101c)
+
+This register specifies the width of the JPEG when encoding.
+
+This register is 16 bits wide.
+
+
+### COMPRESSED_BYTES (+0x1020)
+
+This register will contain the final size of the JPEG when encoding
+
+
+### JPEG_OUTPUT_FLAGS (+0x1024)
+
+- bit0 doesn't seem to do anything
+- bit1 = output only SOS/EOI, no SOI/DQT/SOF0/DHT
+- bit2 = output SOF0 after DHT instead of before it
+- bit3 doesn't seem to do anything
+- bit4 not sure exactly what this does, but it makes compression worse
+
+
+### REG_0x1028 (+0x1028)
+
+This register is not understood yet.
+
+The driver sets this register to 0x400 when decoding.
+
+This register appears to be 32 bits wide, but writing 0xffffffff results in 0x8000071f.
+
+
+### REG_0x102c (+0x102c)
+
+This register is not understood yet.
+
+The driver reads this register and does something with it after an interrupt occurs.
+
+
+### BITSTREAM_CORRUPTION (+0x1030)
+
+This register is not understood yet. It supposedly contains information about bitstream corruption.
+
+
+## (+0x1034-0x107f)
+
+No access to this register has been observed.
+
+It appears to be read-only. The power-up state appears to be 0.
+
+
+## REG_0x1080 (+0x1080)
+
+No access to this register has been observed.
+
+This register appears to be 5 bit wide. It is readable/writable.
+
+
+## REG_0x1084 (+0x1084)
+
+No access to this register has been observed.
+
+This register appears to be 7 bit wide. It is readable/writable.
+
+
+## (+0x1088)
+
+No access to this register has been observed.
+
+It appears to be read-only. The power-up state appears to be 0.
+
+
+## REG_0x108c (+0x108c)
+
+No access to this register has been observed.
+
+This register appears to be 32 bit wide. It is readable/writable.
+
+
+## REG_0x1090 (+0x1090)
+
+No access to this register has been observed.
+
+This register appears to be 8 bit wide. It is readable/writable.
+
+
+## (+0x1094-0x10df)
+
+No access to this register has been observed.
+
+It appears to be read-only. The power-up state appears to be 0.
+
+
+## SHIKINO_VERSION_MAGIC0 (+0x10e0)
+## SHIKINO_VERSION_MAGIC1 (+0x10e4)
+## SHIKINO_VERSION_MAGIC2 (+0x10e8)
+## SHIKINO_VERSION_MAGIC3 (+0x10ec)
+## SHIKINO_VERSION_MAGIC4 (+0x10f0)
+
+Contains ASCII text 'SHIKINO KJN-7GI 0001'
+
+
+## (+0x10f4-0x10ff)
+
+No access to this register has been observed.
+
+It appears to be read-only. The power-up state appears to be 0.
+
+
+## QTBL (+0x1100-0x11ff)
+
+Quantization tables. The exact layout is not understood yet (zigzag or not?)
+
+
+## (+0x1200-0x1fff)
+
+No access to this register has been observed.
+
+
+## RSTLOG (+0x2000-0x2fff)
+
+RST log. The details of this are not understood yet.
+
+
+## (+0x3000-0x3fff)
+
+No access to this register has been observed.
diff --git a/tools/proxyclient/experiments/memdump.py b/tools/proxyclient/experiments/memdump.py
new file mode 100755
index 0000000..882b79f
--- /dev/null
+++ b/tools/proxyclient/experiments/memdump.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+from m1n1.setup import *
+
+p = 0x800000000
+limit = u.base
+block = 0x40000
+
+while p < limit:
+ f = "mem/0x%x.bin" % p
+ if os.path.exists(f):
+ p += block
+ continue
+
+ print("dumping 0x%x..." % p)
+
+ data = iface.readmem(p, block)
+ open(f, "wb").write(data)
+ p += block
diff --git a/tools/proxyclient/experiments/mmio_sweep.py b/tools/proxyclient/experiments/mmio_sweep.py
new file mode 100755
index 0000000..938ff29
--- /dev/null
+++ b/tools/proxyclient/experiments/mmio_sweep.py
@@ -0,0 +1,212 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib
+import time
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+from m1n1.setup import *
+from m1n1.loadobjs import *
+import argparse
+import numpy as np
+
+argparser = argparse.ArgumentParser()
+argparser.add_argument("-d", "--domain", type=str,
+ help='Look for MMIO range associated with a particular'
+ ' power domain')
+argparser.add_argument("-p", "--print", action='store_true',
+ help='Print power domain list')
+args = argparser.parse_args()
+
+if args.print:
+ for dev in u.adt["/arm-io/pmgr"].devices:
+ print(dev.name)
+ sys.exit(0)
+
+granule = 0x4000
+
+lp = LinkedProgram(u)
+lp.load_inline_c(f'''
+ #define GRANULE {granule}
+ ''' + '''
+ #include "exception.h"
+ #include "utils.h"
+ #include "soc.h"
+
+ bool is_t6000(void)
+ {
+ return chip_id == T6000;
+ }
+
+ void sweep(u64 from, u64 to, u64 target)
+ {
+ u32 *mask = (u32 *) target;
+ exc_guard = GUARD_MARK | GUARD_SILENT;
+
+ int bitp = 0;
+ for (u64 p = from; p < to; p += GRANULE) {
+ sysop("dsb sy");
+ sysop("isb");
+ bool hit = read32(p) != 0xabad1dea;
+
+ if (hit)
+ *mask |= (1 << bitp);
+ else
+ *mask &= ~(1 << bitp);
+
+ if (++bitp >= 32) {
+ bitp = 0;
+ mask++;
+ }
+ }
+
+ sysop("dsb sy");
+ sysop("isb");
+ }
+ ''')
+
+def do_sweep(maskrange):
+ masklen = (maskrange.stop - maskrange.start) // granule // 32 * 4 + 4
+ mask_base = u.heap.malloc(masklen)
+ lp.sweep(maskrange.start, maskrange.stop, mask_base)
+ mask = iface.readmem(mask_base, masklen)
+ u.heap.free(mask_base)
+ return np.frombuffer(mask, dtype=np.uint8)
+
+def describe_mask(mask, maskrange):
+ '''
+ Describe mask in terms of hot from-to ranges
+ '''
+ ranges = []
+ prev_hit = False
+ mask = np.concatenate((mask, [0]))
+ for i in range(len(mask)*8):
+ hit = mask[i//8] & (1<<(i%8)) != 0
+ if hit and not prev_hit:
+ start = maskrange.start + i*granule
+ if not hit and prev_hit:
+ end = maskrange.start + i*granule
+ ranges.append((start, end))
+ prev_hit = hit
+ return ranges
+
+if lp.is_t6000():
+ maskrange = range(0x2_9000_0000, 0x4_0000_0000)
+else:
+ maskrange = range(0x2_2000_0000, 0x3_0000_0000)
+
+pd_did_enable = set()
+pmgr = u.adt["/arm-io/pmgr"]
+ps_dev_by_id = {dev.id: dev for dev in pmgr.devices}
+ps_deps = dict()
+ps_addrs = dict()
+
+for dev in pmgr.devices:
+ ps = pmgr.ps_regs[dev.psreg]
+ addr = pmgr.get_reg(ps.reg)[0] + ps.offset + dev.psidx * 8
+
+ if lp.is_t6000() and dev.name.startswith("AOP_"):
+ addr = 0x292284000 + (dev.id - 403) * 8
+
+ ps_addrs[dev.name] = addr
+ ps_deps[dev.name] = [
+ ps_dev_by_id[idx].name for idx
+ in dev.parents if idx in ps_dev_by_id
+ ]
+
+if lp.is_t6000():
+ # on t6000, guess the AOP PD hierarchy (undocumented
+ # in ADT) by analogy with t8103
+ ps_deps["AOP_GPIO"] += ["AOP_FILTER"]
+ ps_deps["AOP_BASE"] += ["AOP_FILTER"]
+ ps_deps["AOP_FR"] += ["AOP_FILTER"]
+ ps_deps["AOP_SPMI0"] += ["AOP_FR"]
+ ps_deps["AOP_SPMI1"] += ["AOP_FR"]
+ ps_deps["AOP_LEAP_CLK"] += ["AOP_FILTER"]
+ ps_deps["AOP_SHIM"] += ["AOP_BASE"]
+ ps_deps["AOP_UART0"] += ["AOP_SHIM"]
+ ps_deps["AOP_UART1"] += ["AOP_SHIM"]
+ ps_deps["AOP_UART2"] += ["AOP_SHIM"]
+ ps_deps["AOP_SCM"] += ["AOP_BASE", "AOP_FR"]
+ ps_deps["AOP_CPU"] += ["AOP_BASE"]
+ ps_deps["AOP_I2CM0"] += ["AOP_FR"]
+ ps_deps["AOP_I2CM1"] += ["AOP_FR"]
+ ps_deps["AOP_MCA0"] += ["AOP_FR", "AOP_SHIM"]
+ ps_deps["AOP_MCA1"] += ["AOP_FR", "AOP_SHIM"]
+ ps_deps["AOP_SPI0"] += ["AOP_FR"]
+ ps_deps["AOP_LEAP"] += ["AOP_LEAP_CLK"]
+ ps_deps["AOP_AUDIO_SHIM"] += ["AOP_LEAP_CLK"]
+ ps_deps["AOP_AUDIO_ADMA0"] += ["AOP_FR"]
+ ps_deps["AOP_PDMC_LPD"] += ["AOP_SHIM"]
+ ps_deps["AOP_SRAM"] += ["AOP_SCM", "AOP_CPU"]
+
+def ps_pstate(name):
+ return p.read32(ps_addrs[name]) & 0x0f
+
+def ps_enabled(name):
+ return p.read32(ps_addrs[name]) & 0x0f == 0x0f
+
+def ps_set_pstate(name, desired):
+ p.mask32(ps_addrs[name], 0xf, desired)
+ time.sleep(0.001)
+ actual = p.read32(ps_addrs[name])
+ if actual & 0xf0 != desired << 4:
+ print("WARNING: %s stuck at pstate 0x%x (desired 0x%x)" \
+ % (name, actual >> 4, desired))
+
+def ps_enable(name):
+ print("Enabling %s..." % name)
+ ps_set_pstate(name, 0xf)
+
+def ps_disable(name):
+ p.mask32(ps_addrs[name], 0xf, 0x0)
+
+if args.domain:
+ ps_disable(args.domain)
+
+ to_enable = set([args.domain])
+ for dev in reversed(pmgr.devices):
+ if dev.name not in to_enable \
+ or ps_enabled(dev.name):
+ continue
+
+ for dep in ps_deps[dev.name]:
+ to_enable.add(dep)
+
+ save = dict()
+ for dev in pmgr.devices:
+ if dev.name in to_enable:
+ save[dev.name] = ps_pstate(dev.name)
+ if dev.name != args.domain:
+ ps_enable(dev.name)
+
+ premask = do_sweep(maskrange)
+ ps_enable(args.domain)
+ postmask = do_sweep(maskrange)
+
+ print("Reverting...")
+
+ for dev in reversed(pmgr.devices):
+ if dev.name in to_enable and dev.name:
+ ps_set_pstate(dev.name, save[dev.name])
+
+ hitmask = premask ^ postmask
+ if np.count_nonzero(hitmask & premask):
+ print("Que? Some ranges disappeared?")
+else:
+ # no --domain flag, do a plain sweep
+ hitmask = do_sweep(maskrange)
+
+al = u.adt.build_addr_lookup()
+for start, stop in describe_mask(hitmask, maskrange):
+ # bit ugly but it makes addrlookup do all the heavy lifting for us
+ al.add(range(start, stop), "hit")
+
+print("Hits:")
+for zone, value in al.items():
+ if ((zone.start - 1) // granule + 1) * granule >= zone.stop:
+ continue
+ if not any([v[0] == "hit" for v in value]):
+ continue
+
+ labels = set([v[0] for v in value if v[0] != "hit"])
+ print(f"\t{zone.start:9x} - {zone.stop:9x} | {' '.join(labels)}")
diff --git a/tools/proxyclient/experiments/mtp.py b/tools/proxyclient/experiments/mtp.py
new file mode 100644
index 0000000..56a72b1
--- /dev/null
+++ b/tools/proxyclient/experiments/mtp.py
@@ -0,0 +1,129 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib, fnmatch
+import time
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+import struct
+from m1n1.setup import *
+from m1n1.fw.asc import StandardASC
+from m1n1.hw.dart8110 import DART8110
+from m1n1.hw.dockchannel import DockChannel
+from m1n1.fw.smc import SMCClient, SMCError
+from m1n1.shell import run_shell
+from m1n1.fw.mtp import *
+
+from construct import *
+
+mon = RegMonitor(u)
+
+#mon.add(0x23b700000, 0x10000)
+mon.add(0x23d28c000, 0x4000)
+mon.poll()
+mon.poll()
+mon.poll()
+mon.add(0x24e400000, 0x4000)
+mon.add(0x24e808000, 0x14000)
+
+smc_addr = u.adt["arm-io/smc"].get_reg(0)[0]
+smc = SMCClient(u, smc_addr)
+smc.start()
+smc.start_ep(0x20)
+smc.verbose = 0
+
+p.dapf_init_all()
+
+dart = DART8110.from_adt(u, "/arm-io/dart-mtp", iova_range=(0x8000, 0x100000))
+
+dart.regs.TCR[1].set(BYPASS_DAPF=0, BYPASS_DART=0, TRANSLATE_ENABLE=1)
+
+irq_base = u.adt["/arm-io/dockchannel-mtp"].get_reg(1)[0]
+fifo_base = u.adt["/arm-io/dockchannel-mtp"].get_reg(2)[0]
+dc = DockChannel(u, irq_base, fifo_base, 1)
+
+node = u.adt["/arm-io/dockchannel-mtp/mtp-transport"]
+
+while dc.rx_count:
+ dc.read(dc.rx_count)
+
+mtp_addr = u.adt["/arm-io/mtp"].get_reg(0)[0]
+mtp = StandardASC(u, mtp_addr, dart, stream=1)
+mtp.allow_phys = True
+print("pre start")
+mon.poll()
+mtp.start()
+print("started")
+mon.poll()
+print("ok")
+
+def poll():
+ mtp.work()
+ mp.work_pending()
+
+# 0x40: device reset
+# 0x42:
+
+# 0 -> mbox?
+# 2 -> dapf?
+# 3 -> dart?
+# 3 -> dockchannel?
+# 5 -> mbox?
+def reset(i):
+ reg = 0x23d28c000 + i*8
+ p.set32(reg, 1<<10)
+ p.set32(reg, 1<<31)
+ p.clear32(reg, 1<<31)
+ p.clear32(reg, 1<<10)
+
+try:
+
+ mp = MTPProtocol(u, node, mtp, dc, smc)
+
+ mp.wait_init("keyboard")
+ #mp.wait_init("multi_touch")
+ mp.wait_init("stm")
+
+ mtp.stop()
+ mtp.start()
+ mon.poll()
+
+ for i in range(256):
+ mp.stm.get_report(i)
+ mtp.work()
+ mp.work_pending()
+
+ #for i in range(256):
+ #if i in (0x40, 0x42):
+ #continue
+ #m = UnkDeviceControlMsg()
+ #m.command = i
+ #for args in (b"", b"\x00", b"\x01", b"\x02",
+ #b"\x01\x00", b"\x01\x01", b"\x01\x02",
+ #b"\x00\x01", b"\x00\x02", b"\x00\x00",
+ #b"\x00\x00\x00",
+ #b"\x00\x00\x00\x00",
+ #b"\x00\x00\x00\x00\x00",
+ #b"\x00\x00\x00\x00\x00\x00",
+ #b"\x00\x00\x00\x00\x00\x00\x00",
+ #b"\x00\x00\x00\x00\x00\x00\x00\x00",):
+ #m.args = args
+ #print(f"{m.command:#x} {m.args.hex()}")
+ #mp.comm.device_control(m)
+
+ #mon.poll()
+ #mtp.stop()
+ #mon.poll()
+ #mtp.start()
+
+ #mon.poll()
+ #mtp.stop(1)
+ ##reset(1)
+ ##p.dapf_init_all()
+
+ #mtp.boot()
+
+ run_shell(locals(), poll_func=poll)
+
+finally:
+ #mtp.stop()
+ p.reboot()
diff --git a/tools/proxyclient/experiments/ohmmeter.py b/tools/proxyclient/experiments/ohmmeter.py
new file mode 100755
index 0000000..9ab24dd
--- /dev/null
+++ b/tools/proxyclient/experiments/ohmmeter.py
@@ -0,0 +1,88 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib
+import time
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+from m1n1.setup import *
+from m1n1.hw.i2c import I2C, I2CRegMapDev
+from m1n1.hw.codecs.cs42l84 import *
+
+class CS42L84(I2CRegMapDev):
+ REGMAP = CS42L84Regs
+ ADDRESSING = (0, 2)
+
+def read_devid():
+ pass
+
+def sense_Z():
+ r = l84.regs
+
+ r.HS_CLAMP_DISABLE.set(HS_CLAMP_DISABLE=1)
+ r.DAC_CTRL2.set(PULLDOWN_R=E_PULLDOWN_R.R_1K1OHMS)
+ r.DCID_CTRL1.set(Z_RANGE=E_DCID_Z_RANGE.UNK2)
+ r.DCID_CTRL2.set(GND_SEL=E_DCID_GND_SEL.HS3)
+ r.MSM_BLOCK_EN3.set(DCID_EN=1)
+ r.DCID_CTRL3.set(START=0)
+ r.DCID_CTRL3.set(START=1)
+
+ while not r.DCID_STATUS.reg.DONE:
+ pass
+
+ reading = r.DCID_STATUS.reg.OVERALL
+ offset_trim = r.DCID_TRIM_OFFSET.val - 128
+ slope_trim = r.DCID_TRIM_SLOPE.val - 128
+ pulldown_trim = r.DCID_PULLDOWN_TRIM.val - 128
+
+ Y_overall = ((reading + 0.5) * 0.01086 - 1.0 / (1 - slope_trim * 0.001375)) \
+ / (614.0 + offset_trim * 0.125)
+ Y_pulldown = 1.0 / (1100 - pulldown_trim*2)
+
+ if Y_overall > Y_pulldown:
+ Z_headphones = 1.0 / (Y_overall - Y_pulldown)
+ else:
+ Z_headphones = float('inf')
+
+ r.MSM_BLOCK_EN3.set(DCID_EN=0)
+ r.DAC_CTRL2.set(PULLDOWN_R=E_PULLDOWN_R.NONE)
+
+ return Z_headphones
+
+def init_ring_tip_sense():
+ l84.regs.MIC_DET_CTRL4.set(LATCH_TO_VP=1)
+ l84.regs.TIP_SENSE_CTRL2.set(CTRL=E_TIP_SENSE_CTRL.SHORT_DET)
+
+ l84.regs.RING_SENSE_CTRL.set(INV=1, UNK1=1,
+ RISETIME=E_DEBOUNCE_TIME.T_125MS, FALLTIME=E_DEBOUNCE_TIME.T_125MS)
+ l84.regs.TIP_SENSE_CTRL.set(INV=1,
+ RISETIME=E_DEBOUNCE_TIME.T_500MS, FALLTIME=E_DEBOUNCE_TIME.T_125MS)
+ l84.regs.MSM_BLOCK_EN3.set(TR_SENSE_EN=1)
+
+def wait_for_plug():
+ while not l84.regs.TR_SENSE_STATUS.reg.TIP_PLUG:
+ time.sleep(0.001)
+
+def wait_for_unplug():
+ while l84.regs.TR_SENSE_STATUS.reg.TIP_UNPLUG:
+ time.sleep(0.001)
+
+
+p.pmgr_adt_clocks_enable("/arm-io/i2c2")
+i2c2 = I2C(u, "/arm-io/i2c2")
+
+p.write32(0x2921f0010, 0x76a02) # invoke reset
+p.write32(0x2921f0010, 0x76a03) # out of reset
+
+l84 = CS42L84(i2c2, 0x4b)
+
+init_ring_tip_sense()
+
+while True:
+ print("Waiting for plug... ", end=""); sys.stdout.flush()
+ wait_for_plug()
+
+ print("measuring... ", end=""); sys.stdout.flush()
+ print(f"{sense_Z():.1f} ohms... ", end=""); sys.stdout.flush()
+
+ wait_for_unplug()
+ print("yanked")
diff --git a/tools/proxyclient/experiments/pcie_enable_devices.py b/tools/proxyclient/experiments/pcie_enable_devices.py
new file mode 100755
index 0000000..6c1efcb
--- /dev/null
+++ b/tools/proxyclient/experiments/pcie_enable_devices.py
@@ -0,0 +1,18 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+from m1n1.setup import *
+from m1n1.fw.smc import SMCClient
+
+smc_addr = u.adt["arm-io/smc"].get_reg(0)[0]
+smc = SMCClient(u, smc_addr, None)
+
+smc.start()
+smc.start_ep(0x20)
+
+smc.smcep.write32("gP0d", 0x800001)
+smc.smcep.write32("gP1a", 1)
+
+smc.stop()
diff --git a/tools/proxyclient/experiments/prores.py b/tools/proxyclient/experiments/prores.py
new file mode 100644
index 0000000..0b4308f
--- /dev/null
+++ b/tools/proxyclient/experiments/prores.py
@@ -0,0 +1,695 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+from m1n1.setup import *
+from m1n1.hw.dart8110 import DART8110, DART8110Regs
+from m1n1.hw.prores import *
+from m1n1.utils import *
+import time
+
+
+def divroundup(val, div):
+ return (val + div - 1) // div
+
+
+def bswp16(x):
+ return (x >> 8) | ((x & 0xFF) << 8)
+
+# ffmpeg -y -i prores-encode-large.png -c:v rawvideo -pix_fmt nv24 prores-encode-large.yuv
+im_W = 1920
+im_H = 1080
+with open('prores-encode-large.yuv', 'rb') as f:
+ im_data = f.read()
+assert len(im_data) == (im_W*im_H) * 3
+image_data_luma = im_data[:im_W*im_H]
+image_data_chroma = im_data[im_W*im_H:]
+
+p.pmgr_adt_clocks_enable(f'/arm-io/dart-apr0')
+p.pmgr_adt_clocks_enable(f'/arm-io/apr0')
+
+dart = DART8110.from_adt(u, f'/arm-io/dart-apr0')
+dart.initialize()
+
+apr_base, _ = u.adt[f'/arm-io/apr0'].get_reg(0)
+apr = ProResRegs(u, apr_base)
+
+print(f"Register 0 (ID?) {apr.REG_0x0}")
+
+# TUNABLES
+apr.MODE = 0x0
+apr.REG_0x118 = apr.REG_0x118.val & ~0x07FF07FF | 0x00000600
+apr.REG_0x148 = apr.REG_0x148.val & ~0x00000001 | 0x00000001
+apr.REG_0x160 = apr.REG_0x160.val & ~0x800F3FFF | 0x800A04FF
+apr.REG_0x164 = apr.REG_0x164.val & ~0x07FF07FF | 0x07800080
+apr.REG_0x170 = apr.REG_0x170.val & ~0x800F3FFF | 0x800404FF
+apr.REG_0x174 = apr.REG_0x174.val & ~0x07FF07FF | 0x06000080
+apr.REG_0x180 = apr.REG_0x180.val & ~0x800F3FFF | 0x800504FF
+apr.REG_0x184 = apr.REG_0x184.val & ~0x07FF07FF | 0x06800080
+apr.REG_0x190 = apr.REG_0x190.val & ~0x800F3FFF | 0x800004FF
+apr.REG_0x194 = apr.REG_0x194.val & ~0x000000FF | 0x00000040
+apr.REG_0x1a0 = apr.REG_0x1a0.val & ~0x800F3FFF | 0x800104FF
+apr.REG_0x1a4 = apr.REG_0x1a4.val & ~0x000000FF | 0x00000080
+apr.REG_0x1b0 = apr.REG_0x1b0.val & ~0x800F3FFF | 0x800204FF
+apr.REG_0x1b4 = apr.REG_0x1b4.val & ~0x000000FF | 0x00000040
+apr.REG_0x1c0 = apr.REG_0x1c0.val & ~0x800F3FFF | 0x800304FF
+apr.REG_0x1c4 = apr.REG_0x1c4.val & ~0x000000FF | 0x00000040
+apr.REG_0x1d0 = apr.REG_0x1d0.val & ~0xBC00FF86 | 0xA4000786
+apr.REG_0x1d4 = apr.REG_0x1d4.val & ~0x000000FF | 0x00000020
+apr.REG_0x1d8 = apr.REG_0x1d8.val & ~0x000000FF | 0x000000FF
+apr.REG_0x1dc = apr.REG_0x1dc.val & ~0x00FFFFFF | 0x00928170
+apr.REG_0x270 = apr.REG_0x270.val & ~0x800F3FFF | 0x800B08FF
+apr.REG_0x274 = apr.REG_0x274.val & ~0x07FF07FF | 0x07000080
+apr.REG_0x280 = apr.REG_0x280.val & ~0xFFFFFFC0 | 0x00180000
+apr.REG_0x290 = apr.REG_0x290.val & ~0x800F3FFF | 0x800004FF
+apr.REG_0x294 = apr.REG_0x294.val & ~0x000000FF | 0x00000080
+apr.REG_0x2a0 = apr.REG_0x2a0.val & ~0x800F3FFF | 0x800104FF
+apr.REG_0x2a4 = apr.REG_0x2a4.val & ~0x000000FF | 0x00000080
+apr.REG_0x2b0 = apr.REG_0x2b0.val & ~0x800F3FFF | 0x800204FF
+apr.REG_0x2b4 = apr.REG_0x2b4.val & ~0x000000FF | 0x00000040
+apr.REG_0x2c0 = apr.REG_0x2c0.val & ~0x800F3FFF | 0x800304FF
+apr.REG_0x2c4 = apr.REG_0x2c4.val & ~0x000000FF | 0x00000040
+apr.REG_0x2d0 = apr.REG_0x2d0.val & ~0x802FF04C | 0x80070040
+apr.REG_0x2d4 = apr.REG_0x2d4.val & ~0x00000001 | 0x00000000
+apr.REG_0x2d8 = apr.REG_0x2d8.val & ~0xFFFF0003 | 0x00FF0003
+apr.REG_0x2e0 = apr.REG_0x2e0.val & ~0x07FF07FF | 0x06000040
+apr.REG_0x2f8 = apr.REG_0x2f8.val & ~0x802FF04C | 0x80081040
+apr.REG_0x2fc = apr.REG_0x2fc.val & ~0x00000001 | 0x00000000
+apr.REG_0x300 = apr.REG_0x300.val & ~0xFFFF0003 | 0x00FF0003
+apr.REG_0x308 = apr.REG_0x308.val & ~0x07FF07FF | 0x06400040
+apr.REG_0x320 = apr.REG_0x320.val & ~0x802FF04C | 0x80092040
+apr.REG_0x324 = apr.REG_0x324.val & ~0x00000001 | 0x00000000
+apr.REG_0x328 = apr.REG_0x328.val & ~0xFFFF0003 | 0x00FF0003
+apr.REG_0x330 = apr.REG_0x330.val & ~0x07FF07FF | 0x06800040
+apr.REG_0x350 = apr.REG_0x350.val & ~0x800F3FFF | 0x800B08FF
+apr.REG_0x354 = apr.REG_0x354.val & ~0x07FF07FF | 0x076000A0
+apr.REG_0x360 = apr.REG_0x360.val & ~0xFFFFFFC0 | 0x00180000
+apr.REG_0x370 = apr.REG_0x370.val & ~0x800F3FFF | 0x800604FF
+apr.REG_0x374 = apr.REG_0x374.val & ~0x07FF07FF | 0x06C000A0
+print("Applied tunables")
+
+# XXX test wrap around behavior
+DESC_RING_SZ = 0x4000
+desc_ring_phys = u.heap.memalign(0x4000, DESC_RING_SZ)
+desc_ring_iova = dart.iomap(0, desc_ring_phys, DESC_RING_SZ)
+print(f"Descriptor ring @ phys {desc_ring_phys:016X} iova {desc_ring_iova:016X}")
+
+apr.DR_HEAD = 0
+apr.DR_TAIL = 0
+apr.DR_SIZE = DESC_RING_SZ
+apr.DR_ADDR_LO = desc_ring_iova & 0xFFFFFFFF
+apr.DR_ADDR_HI = desc_ring_iova >> 32
+
+apr.MODE = 0xd # FIXME: dunno what this means
+
+# MATRICES
+apr.QUANT_LUMA_EHQ[0].val = 0x802802
+apr.QUANT_CHROMA_EHQ[0].val = 0x804804
+apr.QUANT_LUMA_EHQ[1].val = 0x802802
+apr.QUANT_CHROMA_EHQ[1].val = 0x804804
+apr.QUANT_LUMA_EHQ[2].val = 0x802802
+apr.QUANT_CHROMA_EHQ[2].val = 0x804804
+apr.QUANT_LUMA_EHQ[3].val = 0x802802
+apr.QUANT_CHROMA_EHQ[3].val = 0x804804
+apr.QUANT_LUMA_EHQ[4].val = 0x802802
+apr.QUANT_CHROMA_EHQ[4].val = 0x804804
+apr.QUANT_LUMA_EHQ[5].val = 0x802802
+apr.QUANT_CHROMA_EHQ[5].val = 0x804804
+apr.QUANT_LUMA_EHQ[6].val = 0x802802
+apr.QUANT_CHROMA_EHQ[6].val = 0x804804
+apr.QUANT_LUMA_EHQ[7].val = 0x802802
+apr.QUANT_CHROMA_EHQ[7].val = 0x804804
+apr.QUANT_LUMA_EHQ[8].val = 0x802802
+apr.QUANT_CHROMA_EHQ[8].val = 0x804804
+apr.QUANT_LUMA_EHQ[9].val = 0x802802
+apr.QUANT_CHROMA_EHQ[9].val = 0x804804
+apr.QUANT_LUMA_EHQ[10].val = 0x802802
+apr.QUANT_CHROMA_EHQ[10].val = 0x804804
+apr.QUANT_LUMA_EHQ[11].val = 0x802802
+apr.QUANT_CHROMA_EHQ[11].val = 0x804804
+apr.QUANT_LUMA_EHQ[12].val = 0x802802
+apr.QUANT_CHROMA_EHQ[12].val = 0x804804
+apr.QUANT_LUMA_EHQ[13].val = 0x802802
+apr.QUANT_CHROMA_EHQ[13].val = 0x804804
+apr.QUANT_LUMA_EHQ[14].val = 0x802802
+apr.QUANT_CHROMA_EHQ[14].val = 0x804804
+apr.QUANT_LUMA_EHQ[15].val = 0x803802
+apr.QUANT_CHROMA_EHQ[15].val = 0x805804
+apr.QUANT_LUMA_EHQ[16].val = 0x802802
+apr.QUANT_CHROMA_EHQ[16].val = 0x804804
+apr.QUANT_LUMA_EHQ[17].val = 0x802802
+apr.QUANT_CHROMA_EHQ[17].val = 0x804804
+apr.QUANT_LUMA_EHQ[18].val = 0x802802
+apr.QUANT_CHROMA_EHQ[18].val = 0x804804
+apr.QUANT_LUMA_EHQ[19].val = 0x803803
+apr.QUANT_CHROMA_EHQ[19].val = 0x805805
+apr.QUANT_LUMA_EHQ[20].val = 0x802802
+apr.QUANT_CHROMA_EHQ[20].val = 0x804804
+apr.QUANT_LUMA_EHQ[21].val = 0x802802
+apr.QUANT_CHROMA_EHQ[21].val = 0x804804
+apr.QUANT_LUMA_EHQ[22].val = 0x803802
+apr.QUANT_CHROMA_EHQ[22].val = 0x805804
+apr.QUANT_LUMA_EHQ[23].val = 0x803803
+apr.QUANT_CHROMA_EHQ[23].val = 0x806805
+apr.QUANT_LUMA_EHQ[24].val = 0x802802
+apr.QUANT_CHROMA_EHQ[24].val = 0x804804
+apr.QUANT_LUMA_EHQ[25].val = 0x802802
+apr.QUANT_CHROMA_EHQ[25].val = 0x804804
+apr.QUANT_LUMA_EHQ[26].val = 0x803803
+apr.QUANT_CHROMA_EHQ[26].val = 0x805805
+apr.QUANT_LUMA_EHQ[27].val = 0x804803
+apr.QUANT_CHROMA_EHQ[27].val = 0x807806
+apr.QUANT_LUMA_EHQ[28].val = 0x802802
+apr.QUANT_CHROMA_EHQ[28].val = 0x804804
+apr.QUANT_LUMA_EHQ[29].val = 0x802802
+apr.QUANT_CHROMA_EHQ[29].val = 0x804804
+apr.QUANT_LUMA_EHQ[30].val = 0x803803
+apr.QUANT_CHROMA_EHQ[30].val = 0x806805
+apr.QUANT_LUMA_EHQ[31].val = 0x804804
+apr.QUANT_CHROMA_EHQ[31].val = 0x807807
+
+apr.QUANT_LUMA_HQ[0].val = 0x804804
+apr.QUANT_CHROMA_HQ[0].val = 0x804804
+apr.QUANT_LUMA_HQ[1].val = 0x804804
+apr.QUANT_CHROMA_HQ[1].val = 0x804804
+apr.QUANT_LUMA_HQ[2].val = 0x804804
+apr.QUANT_CHROMA_HQ[2].val = 0x804804
+apr.QUANT_LUMA_HQ[3].val = 0x804804
+apr.QUANT_CHROMA_HQ[3].val = 0x804804
+apr.QUANT_LUMA_HQ[4].val = 0x804804
+apr.QUANT_CHROMA_HQ[4].val = 0x804804
+apr.QUANT_LUMA_HQ[5].val = 0x804804
+apr.QUANT_CHROMA_HQ[5].val = 0x804804
+apr.QUANT_LUMA_HQ[6].val = 0x804804
+apr.QUANT_CHROMA_HQ[6].val = 0x804804
+apr.QUANT_LUMA_HQ[7].val = 0x804804
+apr.QUANT_CHROMA_HQ[7].val = 0x804804
+apr.QUANT_LUMA_HQ[8].val = 0x804804
+apr.QUANT_CHROMA_HQ[8].val = 0x804804
+apr.QUANT_LUMA_HQ[9].val = 0x804804
+apr.QUANT_CHROMA_HQ[9].val = 0x804804
+apr.QUANT_LUMA_HQ[10].val = 0x804804
+apr.QUANT_CHROMA_HQ[10].val = 0x804804
+apr.QUANT_LUMA_HQ[11].val = 0x804804
+apr.QUANT_CHROMA_HQ[11].val = 0x804804
+apr.QUANT_LUMA_HQ[12].val = 0x804804
+apr.QUANT_CHROMA_HQ[12].val = 0x804804
+apr.QUANT_LUMA_HQ[13].val = 0x804804
+apr.QUANT_CHROMA_HQ[13].val = 0x804804
+apr.QUANT_LUMA_HQ[14].val = 0x804804
+apr.QUANT_CHROMA_HQ[14].val = 0x804804
+apr.QUANT_LUMA_HQ[15].val = 0x805804
+apr.QUANT_CHROMA_HQ[15].val = 0x805804
+apr.QUANT_LUMA_HQ[16].val = 0x804804
+apr.QUANT_CHROMA_HQ[16].val = 0x804804
+apr.QUANT_LUMA_HQ[17].val = 0x804804
+apr.QUANT_CHROMA_HQ[17].val = 0x804804
+apr.QUANT_LUMA_HQ[18].val = 0x804804
+apr.QUANT_CHROMA_HQ[18].val = 0x804804
+apr.QUANT_LUMA_HQ[19].val = 0x805805
+apr.QUANT_CHROMA_HQ[19].val = 0x805805
+apr.QUANT_LUMA_HQ[20].val = 0x804804
+apr.QUANT_CHROMA_HQ[20].val = 0x804804
+apr.QUANT_LUMA_HQ[21].val = 0x804804
+apr.QUANT_CHROMA_HQ[21].val = 0x804804
+apr.QUANT_LUMA_HQ[22].val = 0x805804
+apr.QUANT_CHROMA_HQ[22].val = 0x805804
+apr.QUANT_LUMA_HQ[23].val = 0x806805
+apr.QUANT_CHROMA_HQ[23].val = 0x806805
+apr.QUANT_LUMA_HQ[24].val = 0x804804
+apr.QUANT_CHROMA_HQ[24].val = 0x804804
+apr.QUANT_LUMA_HQ[25].val = 0x804804
+apr.QUANT_CHROMA_HQ[25].val = 0x804804
+apr.QUANT_LUMA_HQ[26].val = 0x805805
+apr.QUANT_CHROMA_HQ[26].val = 0x805805
+apr.QUANT_LUMA_HQ[27].val = 0x807806
+apr.QUANT_CHROMA_HQ[27].val = 0x807806
+apr.QUANT_LUMA_HQ[28].val = 0x804804
+apr.QUANT_CHROMA_HQ[28].val = 0x804804
+apr.QUANT_LUMA_HQ[29].val = 0x804804
+apr.QUANT_CHROMA_HQ[29].val = 0x804804
+apr.QUANT_LUMA_HQ[30].val = 0x806805
+apr.QUANT_CHROMA_HQ[30].val = 0x806805
+apr.QUANT_LUMA_HQ[31].val = 0x807807
+apr.QUANT_CHROMA_HQ[31].val = 0x807807
+
+apr.QUANT_LUMA_NQ[0].val = 0x804804
+apr.QUANT_CHROMA_NQ[0].val = 0x804804
+apr.QUANT_LUMA_NQ[1].val = 0x805805
+apr.QUANT_CHROMA_NQ[1].val = 0x805805
+apr.QUANT_LUMA_NQ[2].val = 0x807806
+apr.QUANT_CHROMA_NQ[2].val = 0x807806
+apr.QUANT_LUMA_NQ[3].val = 0x809807
+apr.QUANT_CHROMA_NQ[3].val = 0x809807
+apr.QUANT_LUMA_NQ[4].val = 0x804804
+apr.QUANT_CHROMA_NQ[4].val = 0x804804
+apr.QUANT_LUMA_NQ[5].val = 0x806805
+apr.QUANT_CHROMA_NQ[5].val = 0x806805
+apr.QUANT_LUMA_NQ[6].val = 0x807807
+apr.QUANT_CHROMA_NQ[6].val = 0x807807
+apr.QUANT_LUMA_NQ[7].val = 0x809809
+apr.QUANT_CHROMA_NQ[7].val = 0x809809
+apr.QUANT_LUMA_NQ[8].val = 0x805805
+apr.QUANT_CHROMA_NQ[8].val = 0x805805
+apr.QUANT_LUMA_NQ[9].val = 0x807806
+apr.QUANT_CHROMA_NQ[9].val = 0x807806
+apr.QUANT_LUMA_NQ[10].val = 0x809807
+apr.QUANT_CHROMA_NQ[10].val = 0x809807
+apr.QUANT_LUMA_NQ[11].val = 0x80a809
+apr.QUANT_CHROMA_NQ[11].val = 0x80a809
+apr.QUANT_LUMA_NQ[12].val = 0x805805
+apr.QUANT_CHROMA_NQ[12].val = 0x805805
+apr.QUANT_LUMA_NQ[13].val = 0x807806
+apr.QUANT_CHROMA_NQ[13].val = 0x807806
+apr.QUANT_LUMA_NQ[14].val = 0x809807
+apr.QUANT_CHROMA_NQ[14].val = 0x809807
+apr.QUANT_LUMA_NQ[15].val = 0x80a809
+apr.QUANT_CHROMA_NQ[15].val = 0x80a809
+apr.QUANT_LUMA_NQ[16].val = 0x806805
+apr.QUANT_CHROMA_NQ[16].val = 0x806805
+apr.QUANT_LUMA_NQ[17].val = 0x807807
+apr.QUANT_CHROMA_NQ[17].val = 0x807807
+apr.QUANT_LUMA_NQ[18].val = 0x809808
+apr.QUANT_CHROMA_NQ[18].val = 0x809808
+apr.QUANT_LUMA_NQ[19].val = 0x80c80a
+apr.QUANT_CHROMA_NQ[19].val = 0x80c80a
+apr.QUANT_LUMA_NQ[20].val = 0x807806
+apr.QUANT_CHROMA_NQ[20].val = 0x807806
+apr.QUANT_LUMA_NQ[21].val = 0x808807
+apr.QUANT_CHROMA_NQ[21].val = 0x808807
+apr.QUANT_LUMA_NQ[22].val = 0x80a809
+apr.QUANT_CHROMA_NQ[22].val = 0x80a809
+apr.QUANT_LUMA_NQ[23].val = 0x80f80c
+apr.QUANT_CHROMA_NQ[23].val = 0x80f80c
+apr.QUANT_LUMA_NQ[24].val = 0x807806
+apr.QUANT_CHROMA_NQ[24].val = 0x807806
+apr.QUANT_LUMA_NQ[25].val = 0x809807
+apr.QUANT_CHROMA_NQ[25].val = 0x809807
+apr.QUANT_LUMA_NQ[26].val = 0x80b80a
+apr.QUANT_CHROMA_NQ[26].val = 0x80b80a
+apr.QUANT_LUMA_NQ[27].val = 0x81180e
+apr.QUANT_CHROMA_NQ[27].val = 0x81180e
+apr.QUANT_LUMA_NQ[28].val = 0x807807
+apr.QUANT_CHROMA_NQ[28].val = 0x807807
+apr.QUANT_LUMA_NQ[29].val = 0x80a809
+apr.QUANT_CHROMA_NQ[29].val = 0x80a809
+apr.QUANT_LUMA_NQ[30].val = 0x80e80b
+apr.QUANT_CHROMA_NQ[30].val = 0x80e80b
+apr.QUANT_LUMA_NQ[31].val = 0x815811
+apr.QUANT_CHROMA_NQ[31].val = 0x815811
+
+apr.QUANT_LUMA_LT[0].val = 0x805804
+apr.QUANT_CHROMA_LT[0].val = 0x805804
+apr.QUANT_LUMA_LT[1].val = 0x807806
+apr.QUANT_CHROMA_LT[1].val = 0x807806
+apr.QUANT_LUMA_LT[2].val = 0x80b809
+apr.QUANT_CHROMA_LT[2].val = 0x80b809
+apr.QUANT_LUMA_LT[3].val = 0x80f80d
+apr.QUANT_CHROMA_LT[3].val = 0x80f80d
+apr.QUANT_LUMA_LT[4].val = 0x805805
+apr.QUANT_CHROMA_LT[4].val = 0x805805
+apr.QUANT_LUMA_LT[5].val = 0x808807
+apr.QUANT_CHROMA_LT[5].val = 0x808807
+apr.QUANT_LUMA_LT[6].val = 0x80d80b
+apr.QUANT_CHROMA_LT[6].val = 0x80d80b
+apr.QUANT_LUMA_LT[7].val = 0x81180f
+apr.QUANT_CHROMA_LT[7].val = 0x81180f
+apr.QUANT_LUMA_LT[8].val = 0x807806
+apr.QUANT_CHROMA_LT[8].val = 0x807806
+apr.QUANT_LUMA_LT[9].val = 0x80b809
+apr.QUANT_CHROMA_LT[9].val = 0x80b809
+apr.QUANT_LUMA_LT[10].val = 0x80f80d
+apr.QUANT_CHROMA_LT[10].val = 0x80f80d
+apr.QUANT_LUMA_LT[11].val = 0x81180f
+apr.QUANT_CHROMA_LT[11].val = 0x81180f
+apr.QUANT_LUMA_LT[12].val = 0x807807
+apr.QUANT_CHROMA_LT[12].val = 0x807807
+apr.QUANT_LUMA_LT[13].val = 0x80b809
+apr.QUANT_CHROMA_LT[13].val = 0x80b809
+apr.QUANT_LUMA_LT[14].val = 0x80f80d
+apr.QUANT_CHROMA_LT[14].val = 0x80f80d
+apr.QUANT_LUMA_LT[15].val = 0x813811
+apr.QUANT_CHROMA_LT[15].val = 0x813811
+apr.QUANT_LUMA_LT[16].val = 0x809807
+apr.QUANT_CHROMA_LT[16].val = 0x809807
+apr.QUANT_LUMA_LT[17].val = 0x80d80b
+apr.QUANT_CHROMA_LT[17].val = 0x80d80b
+apr.QUANT_LUMA_LT[18].val = 0x81080e
+apr.QUANT_CHROMA_LT[18].val = 0x81080e
+apr.QUANT_LUMA_LT[19].val = 0x817813
+apr.QUANT_CHROMA_LT[19].val = 0x817813
+apr.QUANT_LUMA_LT[20].val = 0x80b809
+apr.QUANT_CHROMA_LT[20].val = 0x80b809
+apr.QUANT_LUMA_LT[21].val = 0x80e80d
+apr.QUANT_CHROMA_LT[21].val = 0x80e80d
+apr.QUANT_LUMA_LT[22].val = 0x813810
+apr.QUANT_CHROMA_LT[22].val = 0x813810
+apr.QUANT_LUMA_LT[23].val = 0x81d817
+apr.QUANT_CHROMA_LT[23].val = 0x81d817
+apr.QUANT_LUMA_LT[24].val = 0x80b809
+apr.QUANT_CHROMA_LT[24].val = 0x80b809
+apr.QUANT_LUMA_LT[25].val = 0x80f80d
+apr.QUANT_CHROMA_LT[25].val = 0x80f80d
+apr.QUANT_LUMA_LT[26].val = 0x815811
+apr.QUANT_CHROMA_LT[26].val = 0x815811
+apr.QUANT_LUMA_LT[27].val = 0x82381c
+apr.QUANT_CHROMA_LT[27].val = 0x82381c
+apr.QUANT_LUMA_LT[28].val = 0x80d80b
+apr.QUANT_CHROMA_LT[28].val = 0x80d80b
+apr.QUANT_LUMA_LT[29].val = 0x811810
+apr.QUANT_CHROMA_LT[29].val = 0x811810
+apr.QUANT_LUMA_LT[30].val = 0x81c815
+apr.QUANT_CHROMA_LT[30].val = 0x81c815
+apr.QUANT_LUMA_LT[31].val = 0x829823
+apr.QUANT_CHROMA_LT[31].val = 0x829823
+
+apr.QUANT_LUMA_PROXY[0].val = 0x807804
+apr.QUANT_CHROMA_PROXY[0].val = 0x807804
+apr.QUANT_LUMA_PROXY[1].val = 0x80b809
+apr.QUANT_CHROMA_PROXY[1].val = 0x80b809
+apr.QUANT_LUMA_PROXY[2].val = 0x80e80d
+apr.QUANT_CHROMA_PROXY[2].val = 0x80e80d
+apr.QUANT_LUMA_PROXY[3].val = 0xfff83f
+apr.QUANT_CHROMA_PROXY[3].val = 0xfff83f
+apr.QUANT_LUMA_PROXY[4].val = 0x807807
+apr.QUANT_CHROMA_PROXY[4].val = 0x807807
+apr.QUANT_LUMA_PROXY[5].val = 0x80c80b
+apr.QUANT_CHROMA_PROXY[5].val = 0x80c80b
+apr.QUANT_LUMA_PROXY[6].val = 0x83f80e
+apr.QUANT_CHROMA_PROXY[6].val = 0x83f80e
+apr.QUANT_LUMA_PROXY[7].val = 0xffffff
+apr.QUANT_CHROMA_PROXY[7].val = 0xffffff
+apr.QUANT_LUMA_PROXY[8].val = 0x80b809
+apr.QUANT_CHROMA_PROXY[8].val = 0x80b809
+apr.QUANT_LUMA_PROXY[9].val = 0x80e80d
+apr.QUANT_CHROMA_PROXY[9].val = 0x80e80d
+apr.QUANT_LUMA_PROXY[10].val = 0xfff83f
+apr.QUANT_CHROMA_PROXY[10].val = 0xfff83f
+apr.QUANT_LUMA_PROXY[11].val = 0xffffff
+apr.QUANT_CHROMA_PROXY[11].val = 0xffffff
+apr.QUANT_LUMA_PROXY[12].val = 0x80b80b
+apr.QUANT_CHROMA_PROXY[12].val = 0x80b80b
+apr.QUANT_LUMA_PROXY[13].val = 0x80e80d
+apr.QUANT_CHROMA_PROXY[13].val = 0x80e80d
+apr.QUANT_LUMA_PROXY[14].val = 0xffffff
+apr.QUANT_CHROMA_PROXY[14].val = 0xffffff
+apr.QUANT_LUMA_PROXY[15].val = 0xffffff
+apr.QUANT_CHROMA_PROXY[15].val = 0xffffff
+apr.QUANT_LUMA_PROXY[16].val = 0x80d80b
+apr.QUANT_CHROMA_PROXY[16].val = 0x80d80b
+apr.QUANT_LUMA_PROXY[17].val = 0xfff80e
+apr.QUANT_CHROMA_PROXY[17].val = 0xfff80e
+apr.QUANT_LUMA_PROXY[18].val = 0xffffff
+apr.QUANT_CHROMA_PROXY[18].val = 0xffffff
+apr.QUANT_LUMA_PROXY[19].val = 0xffffff
+apr.QUANT_CHROMA_PROXY[19].val = 0xffffff
+apr.QUANT_LUMA_PROXY[20].val = 0x80e80d
+apr.QUANT_CHROMA_PROXY[20].val = 0x80e80d
+apr.QUANT_LUMA_PROXY[21].val = 0xffffff
+apr.QUANT_CHROMA_PROXY[21].val = 0xffffff
+apr.QUANT_LUMA_PROXY[22].val = 0xffffff
+apr.QUANT_CHROMA_PROXY[22].val = 0xffffff
+apr.QUANT_LUMA_PROXY[23].val = 0xffffff
+apr.QUANT_CHROMA_PROXY[23].val = 0xffffff
+apr.QUANT_LUMA_PROXY[24].val = 0xfff80d
+apr.QUANT_CHROMA_PROXY[24].val = 0xfff80d
+apr.QUANT_LUMA_PROXY[25].val = 0xffffff
+apr.QUANT_CHROMA_PROXY[25].val = 0xffffff
+apr.QUANT_LUMA_PROXY[26].val = 0xffffff
+apr.QUANT_CHROMA_PROXY[26].val = 0xffffff
+apr.QUANT_LUMA_PROXY[27].val = 0xffffff
+apr.QUANT_CHROMA_PROXY[27].val = 0xffffff
+apr.QUANT_LUMA_PROXY[28].val = 0xffffff
+apr.QUANT_CHROMA_PROXY[28].val = 0xffffff
+apr.QUANT_LUMA_PROXY[29].val = 0xffffff
+apr.QUANT_CHROMA_PROXY[29].val = 0xffffff
+apr.QUANT_LUMA_PROXY[30].val = 0xffffff
+apr.QUANT_CHROMA_PROXY[30].val = 0xffffff
+apr.QUANT_LUMA_PROXY[31].val = 0xffffff
+apr.QUANT_CHROMA_PROXY[31].val = 0xffffff
+
+apr.DC_QUANT_SCALE[0].val = 0x401
+apr.DC_QUANT_SCALE[1].val = 0x803
+apr.DC_QUANT_SCALE[2].val = 0xc05
+apr.DC_QUANT_SCALE[3].val = 0x1007
+apr.DC_QUANT_SCALE[4].val = 0x1409
+apr.DC_QUANT_SCALE[5].val = 0x180b
+apr.DC_QUANT_SCALE[6].val = 0x1c0d
+apr.DC_QUANT_SCALE[7].val = 0x200f
+apr.DC_QUANT_SCALE[8].val = 0x2411
+apr.DC_QUANT_SCALE[9].val = 0x2813
+apr.DC_QUANT_SCALE[10].val = 0x2c15
+apr.DC_QUANT_SCALE[11].val = 0x3017
+apr.DC_QUANT_SCALE[12].val = 0x3419
+apr.DC_QUANT_SCALE[13].val = 0x381b
+apr.DC_QUANT_SCALE[14].val = 0x3c1d
+apr.DC_QUANT_SCALE[15].val = 0x401f
+apr.DC_QUANT_SCALE[16].val = 0x4421
+apr.DC_QUANT_SCALE[17].val = 0x4823
+apr.DC_QUANT_SCALE[18].val = 0x4c25
+apr.DC_QUANT_SCALE[19].val = 0x5027
+apr.DC_QUANT_SCALE[20].val = 0x5429
+apr.DC_QUANT_SCALE[21].val = 0x582b
+apr.DC_QUANT_SCALE[22].val = 0x5c2d
+apr.DC_QUANT_SCALE[23].val = 0x602f
+apr.DC_QUANT_SCALE[24].val = 0x6431
+apr.DC_QUANT_SCALE[25].val = 0x6833
+apr.DC_QUANT_SCALE[26].val = 0x6c35
+apr.DC_QUANT_SCALE[27].val = 0x7037
+apr.DC_QUANT_SCALE[28].val = 0x7439
+apr.DC_QUANT_SCALE[29].val = 0x783b
+apr.DC_QUANT_SCALE[30].val = 0x7c3d
+apr.DC_QUANT_SCALE[31].val = 0x803f
+apr.DC_QUANT_SCALE[32].val = 0x8441
+apr.DC_QUANT_SCALE[33].val = 0x8843
+apr.DC_QUANT_SCALE[34].val = 0x8c45
+apr.DC_QUANT_SCALE[35].val = 0x9047
+apr.DC_QUANT_SCALE[36].val = 0x9449
+apr.DC_QUANT_SCALE[37].val = 0x984b
+apr.DC_QUANT_SCALE[38].val = 0x9c4d
+apr.DC_QUANT_SCALE[39].val = 0xa04f
+apr.DC_QUANT_SCALE[40].val = 0xa451
+apr.DC_QUANT_SCALE[41].val = 0xa853
+apr.DC_QUANT_SCALE[42].val = 0xac55
+apr.DC_QUANT_SCALE[43].val = 0xb057
+apr.DC_QUANT_SCALE[44].val = 0xb459
+apr.DC_QUANT_SCALE[45].val = 0xb85b
+apr.DC_QUANT_SCALE[46].val = 0xbc5d
+apr.DC_QUANT_SCALE[47].val = 0xc05f
+apr.DC_QUANT_SCALE[48].val = 0xc461
+apr.DC_QUANT_SCALE[49].val = 0xc863
+apr.DC_QUANT_SCALE[50].val = 0xcc65
+apr.DC_QUANT_SCALE[51].val = 0xd067
+apr.DC_QUANT_SCALE[52].val = 0xd469
+apr.DC_QUANT_SCALE[53].val = 0xd86b
+apr.DC_QUANT_SCALE[54].val = 0xdc6d
+apr.DC_QUANT_SCALE[55].val = 0xe06f
+apr.DC_QUANT_SCALE[56].val = 0xe471
+apr.DC_QUANT_SCALE[57].val = 0xe873
+apr.DC_QUANT_SCALE[58].val = 0xec75
+apr.DC_QUANT_SCALE[59].val = 0xf077
+apr.DC_QUANT_SCALE[60].val = 0xf479
+apr.DC_QUANT_SCALE[61].val = 0xf87b
+apr.DC_QUANT_SCALE[62].val = 0xfc7d
+apr.DC_QUANT_SCALE[63].val = 0x1007f
+apr.DC_QUANT_SCALE[64].val = 0x11084
+apr.DC_QUANT_SCALE[65].val = 0x1208c
+apr.DC_QUANT_SCALE[66].val = 0x13094
+apr.DC_QUANT_SCALE[67].val = 0x1409c
+apr.DC_QUANT_SCALE[68].val = 0x150a4
+apr.DC_QUANT_SCALE[69].val = 0x160ac
+apr.DC_QUANT_SCALE[70].val = 0x170b4
+apr.DC_QUANT_SCALE[71].val = 0x180bc
+apr.DC_QUANT_SCALE[72].val = 0x190c4
+apr.DC_QUANT_SCALE[73].val = 0x1a0cc
+apr.DC_QUANT_SCALE[74].val = 0x1b0d4
+apr.DC_QUANT_SCALE[75].val = 0x1c0dc
+apr.DC_QUANT_SCALE[76].val = 0x1d0e4
+apr.DC_QUANT_SCALE[77].val = 0x1e0ec
+apr.DC_QUANT_SCALE[78].val = 0x1f0f4
+apr.DC_QUANT_SCALE[79].val = 0x200fc
+apr.DC_QUANT_SCALE[80].val = 0x21104
+apr.DC_QUANT_SCALE[81].val = 0x2210c
+apr.DC_QUANT_SCALE[82].val = 0x23114
+apr.DC_QUANT_SCALE[83].val = 0x2411c
+apr.DC_QUANT_SCALE[84].val = 0x25124
+apr.DC_QUANT_SCALE[85].val = 0x2612c
+apr.DC_QUANT_SCALE[86].val = 0x27134
+apr.DC_QUANT_SCALE[87].val = 0x2813c
+apr.DC_QUANT_SCALE[88].val = 0x29144
+apr.DC_QUANT_SCALE[89].val = 0x2a14c
+apr.DC_QUANT_SCALE[90].val = 0x2b154
+apr.DC_QUANT_SCALE[91].val = 0x2c15c
+apr.DC_QUANT_SCALE[92].val = 0x2d164
+apr.DC_QUANT_SCALE[93].val = 0x2e16c
+apr.DC_QUANT_SCALE[94].val = 0x2f174
+apr.DC_QUANT_SCALE[95].val = 0x3017c
+apr.DC_QUANT_SCALE[96].val = 0x31184
+apr.DC_QUANT_SCALE[97].val = 0x3218c
+apr.DC_QUANT_SCALE[98].val = 0x33194
+apr.DC_QUANT_SCALE[99].val = 0x3419c
+apr.DC_QUANT_SCALE[100].val = 0x351a4
+apr.DC_QUANT_SCALE[101].val = 0x361ac
+apr.DC_QUANT_SCALE[102].val = 0x371b4
+apr.DC_QUANT_SCALE[103].val = 0x381bc
+apr.DC_QUANT_SCALE[104].val = 0x391c4
+apr.DC_QUANT_SCALE[105].val = 0x3a1cc
+apr.DC_QUANT_SCALE[106].val = 0x3b1d4
+apr.DC_QUANT_SCALE[107].val = 0x3c1dc
+apr.DC_QUANT_SCALE[108].val = 0x3d1e4
+apr.DC_QUANT_SCALE[109].val = 0x3e1ec
+apr.DC_QUANT_SCALE[110].val = 0x3f1f4
+apr.DC_QUANT_SCALE[111].val = 0x1fc
+print("Set matrices")
+
+# dunno how this gets calculated
+OUT_SZ = 0x1000000
+out_buf_phys = u.heap.memalign(0x4000, OUT_SZ)
+iface.writemem(out_buf_phys, b'\xAA' * OUT_SZ)
+out_buf_iova = dart.iomap(0, out_buf_phys, OUT_SZ)
+print(f"Output buffer @ phys {out_buf_phys:016X} iova {out_buf_iova:016X}")
+
+IN_SZ_LUMA = align_up(im_W*im_H)
+in_buf_luma_phys = u.heap.memalign(0x4000, IN_SZ_LUMA)
+iface.writemem(in_buf_luma_phys, image_data_luma + b'\xaa' * (IN_SZ_LUMA - len(image_data_luma)))
+in_buf_luma_iova = dart.iomap(0, in_buf_luma_phys, IN_SZ_LUMA)
+print(f"Input buffer luma @ phys {in_buf_luma_phys:016X} iova {in_buf_luma_iova:016X}")
+IN_SZ_CHROMA = align_up(im_W*2*im_H)
+in_buf_chroma_phys = u.heap.memalign(0x4000, IN_SZ_CHROMA)
+iface.writemem(in_buf_chroma_phys, image_data_chroma + b'\xaa' * (IN_SZ_CHROMA - len(image_data_chroma)))
+in_buf_chroma_iova = dart.iomap(0, in_buf_chroma_phys, IN_SZ_CHROMA)
+print(f"Input buffer chroma @ phys {in_buf_chroma_phys:016X} iova {in_buf_chroma_iova:016X}")
+dart.dump_all()
+
+desc = EncodeNotRawDescriptor(
+ flags=0x373c,
+ flags2=0,
+ output_iova=out_buf_iova,
+ max_out_sz=OUT_SZ,
+ offset_x=0,
+ offset_y=0,
+ # this is the important set
+ pix_surface_w_2_=im_W,
+ pix_surface_h_2_=im_H,
+ # changing this doesn't seem to break anything
+ pix_surface_w=im_W,
+ pix_surface_h=im_H,
+ # XXX how does the div work exactly? it's different in "tiled" mode
+ luma_stride=divroundup(im_W, 64),
+ chroma_stride=divroundup(im_W*2, 64),
+ alpha_stride=divroundup(im_W, 64),
+ unk_pad_0x26_=b'\x00\x00',
+
+ luma_iova=in_buf_luma_iova,
+ pix_plane0_tileheader_thing_=0,
+ chroma_iova=in_buf_chroma_iova,
+ pix_plane1_tileheader_thing_=0,
+ alpha_iova=in_buf_luma_iova,
+ pix_plane2_tileheader_thing_=0,
+
+ # changing this does add extra 0 bytes
+ frame_header_sz=bswp16(0x94),
+ unk_pad_0x5a_=b'\x00',
+ bitstream_version=0,
+ encoder_identifier=0xcafeface,
+ # cannot change arbitrily, will break
+ pix_surface_w_byteswap_=bswp16(im_W),
+ pix_surface_h_byteswap_=bswp16(im_H),
+ # seemingly can change arbitrarily
+ chroma_format_interlace_mode=0xc0,
+ aspect_ratio_frame_rate=0,
+ color_primaries=2,
+ transfer_characteristic=2,
+ matrix_coefficients=1,
+ alpha_channel_type=1,
+ # tables will still be output even if bits not set here
+ frame_hdr_reserved14=b'\x00\x03',
+ unk_pad_0x6c_=b'\x00' * 128,
+ deprecated_number_of_slices=0,
+ # this one affects the encoding not just the header
+ log2_desired_slice_size_in_mb=0x30,
+ quantization_index=0x2,
+
+ # this impacts the quality somehow, not quite understood
+ # might be a target bitrate
+ unk_0xf0_=0xffff,
+ unk_0xf2_=0xffff,
+
+ # none of this stuff is understood, and it never seems to change
+ unk_0xf4_=0x8000402015100c0c,
+ unk_0xfc_=0x2c8080,
+ unk_0x100_0_=0x880080,
+ unk_0x100_1_=0x4e00c5,
+ unk_0x100_2_=0x9000d0,
+ unk_0x100_3_=0x200122,
+ unk_0x110_0_=0x400200, # looks like a quant table, but ??? not used ???
+ unk_0x110_1_=0x400200,
+ unk_0x110_2_=0x400200,
+ unk_0x110_3_=0x400200,
+ unk_0x110_4_=0x400200,
+ unk_0x110_5_=0x400200,
+ unk_0x110_6_=0x400200,
+ unk_0x110_7_=0x400200,
+ unk_0x110_8_=0x400200,
+ unk_0x110_9_=0x400200,
+ unk_0x110_10_=0x400200,
+ unk_0x110_11_=0x400200,
+ unk_0x110_12_=0x400200,
+ unk_0x110_13_=0x400200,
+ unk_0x110_14_=0x400200,
+ unk_0x110_15_=0x400200,
+
+ quant_table_sel=0x23,
+ unk_pad_0x154_=b'\x00' * 44,
+)
+desc_bytes = struct.pack(ENCODE_NOT_RAW_STRUCT, *desc)
+chexdump(desc_bytes)
+
+iface.writemem(desc_ring_phys, desc_bytes)
+
+# let's go
+apr.DR_HEAD = len(desc_bytes)
+
+start_time = time.time()
+while apr.IRQ_STATUS.val == 0:
+ if time.time() - start_time > 5:
+ print("TIMED OUT!!!")
+ break
+
+print(f"Done, IRQ status is {apr.IRQ_STATUS}")
+print(f"ST0 = {apr.ST0}")
+print(f"ST1 = {apr.ST1}")
+print(f"REG_0x1c = {apr.REG_0x1c}")
+print(f"REG_0x3c = {apr.REG_0x3c}")
+print(f"REG_0x44 = {apr.REG_0x44}")
+
+print(f"DR_HEAD = {apr.DR_HEAD}")
+print(f"DR_TAIL = {apr.DR_TAIL}")
+
+print(f"unk REG_0x38 = {apr.REG_0x38}")
+print(f"unk REG_0x40 = {apr.REG_0x40}")
+print(f"unk REG_0x48 = {apr.REG_0x48}")
+print(f"unk REG_0x50 = {apr.REG_0x50}")
+print(f"unk REG_0x54 = {apr.REG_0x54}")
+
+apr.IRQ_STATUS = apr.IRQ_STATUS.val
+
+dr_memory_new = iface.readmem(desc_ring_phys, DESC_RING_SZ)
+chexdump(dr_memory_new)
+
+out_buf_new = iface.readmem(out_buf_phys, OUT_SZ)
+with open('prores.bin', 'wb') as f:
+ f.write(out_buf_new)
+
+outlen = struct.unpack(">I", out_buf_new[:4])[0]
+if outlen <= len(out_buf_new):
+ with open('prores.mov', 'wb') as f:
+ f.write(b'\x00\x00\x00\x14\x66\x74\x79\x70\x71\x74\x20\x20\x00\x00\x02\x00\x71\x74\x20\x20\x00\x00\x00\x08\x77\x69\x64\x65')
+ f.write(struct.pack(">I", outlen + 8))
+ f.write(b'mdat')
+ f.write(out_buf_new[:outlen])
+ f.write(b'\x00\x00\x03\x1e\x6d\x6f\x6f\x76\x00\x00\x00\x6c\x6d\x76\x68\x64\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xe8\x00\x00\x00\x11\x00\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x02\x89\x74\x72\x61\x6b\x00\x00\x00\x5c\x74\x6b\x68\x64\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x07\x80\x00\x00\x04\x38\x00\x00\x00\x00\x00\x24\x65\x64\x74\x73\x00\x00\x00\x1c\x65\x6c\x73\x74\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x11\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x02\x01\x6d\x64\x69\x61\x00\x00\x00\x20\x6d\x64\x68\x64\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3c\x00\x00\x00\x01\x00\x7f\xff\x00\x00\x00\x00\x00\x2d\x68\x64\x6c\x72\x00\x00\x00\x00\x6d\x68\x6c\x72\x76\x69\x64\x65\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x56\x69\x64\x65\x6f\x48\x61\x6e\x64\x6c\x65\x72\x00\x00\x01\xac\x6d\x69\x6e\x66\x00\x00\x00\x14\x76\x6d\x68\x64\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x2c\x68\x64\x6c\x72\x00\x00\x00\x00\x64\x68\x6c\x72\x75\x72\x6c\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x44\x61\x74\x61\x48\x61\x6e\x64\x6c\x65\x72\x00\x00\x00\x24\x64\x69\x6e\x66\x00\x00\x00\x1c\x64\x72\x65\x66\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x0c\x75\x72\x6c\x20\x00\x00\x00\x01\x00\x00\x01\x40\x73\x74\x62\x6c\x00\x00\x00\xdc\x73\x74\x73\x64\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\xcc\x61\x70\x63\x6e\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x46\x46\x4d\x50\x00\x00\x02\x00\x00\x00\x02\x00\x07\x80\x04\x38\x00\x48\x00\x00\x00\x48\x00\x00\x00\x00\x00\x00\x00\x01\x1f\x4c\x61\x76\x63\x35\x39\x2e\x32\x35\x2e\x31\x30\x30\x20\x70\x72\x6f\x72\x65\x73\x5f\x76\x69\x64\x65\x6f\x74\x6f\x6f\x6c\x62\x00\x18\xff\xff\x00\x00\x00\x6c\x67\x6c\x62\x6c\x00\x00\x00\x64\x61\x70\x63\x6e\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xff\x07\x80\x04\x38\x00\x48\x00\x00\x00\x48\x00\x00\x00\x00\x00\x00\x00\x01\x10\x41\x70\x70\x6c\x65\x20\x50\x72\x6f\x52\x65\x73\x20\x34\x32\x32\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18\xff\xff\x00\x00\x00\x0a\x66\x69\x65\x6c\x01\x00\x00\x00\x00\x00\x00\x00\x00\x0a\x66\x69\x65\x6c\x01\x00\x00\x00\x00\x18\x73\x74\x74\x73\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x01\x00\x00\x00\x00\x1c\x73\x74\x73\x63\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x14\x73\x74\x73\x7a\x00\x00\x00\x00')
+ f.write(struct.pack(">I", outlen))
+ f.write(b'\x00\x00\x00\x01\x00\x00\x00\x14\x73\x74\x63\x6f\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x24\x00\x00\x00\x21\x75\x64\x74\x61\x00\x00\x00\x19\xa9\x73\x77\x72\x00\x0d\x55\xc4\x4c\x61\x76\x66\x35\x39\x2e\x32\x30\x2e\x31\x30\x31')
+# ffmpeg -i prores.mov prores-dec%d.png
diff --git a/tools/proxyclient/experiments/scaler.py b/tools/proxyclient/experiments/scaler.py
new file mode 100644
index 0000000..2a319ed
--- /dev/null
+++ b/tools/proxyclient/experiments/scaler.py
@@ -0,0 +1,1527 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+from m1n1.setup import *
+from m1n1.hw.dart import DART
+from m1n1.hw.scaler import *
+from m1n1.utils import *
+import struct
+import time
+from PIL import Image, ImageDraw
+
+SCALER_ADT = '/arm-io/scaler0'
+DART_ADT = '/arm-io/dart-scaler0'
+
+p.pmgr_adt_clocks_enable(DART_ADT)
+p.pmgr_adt_clocks_enable(SCALER_ADT)
+
+dart = DART.from_adt(u, DART_ADT)
+dart.initialize()
+
+scaler_base, _ = u.adt[SCALER_ADT].get_reg(0)
+apiodma_base, _ = u.adt[SCALER_ADT].get_reg(1)
+dpe_ctrl_base, _ = u.adt[SCALER_ADT].get_reg(2)
+
+scaler = ScalerMainRegs(u, scaler_base)
+
+def dpe_start():
+ p.write32(dpe_ctrl_base + 0x400, 0x1)
+ p.write32(dpe_ctrl_base + 0x404, 0x1)
+ p.write32(dpe_ctrl_base + 0x438, 0xf)
+ p.write32(dpe_ctrl_base + 0x43c, 0x5)
+ p.write32(dpe_ctrl_base + 0x408, 0x1)
+ p.write32(dpe_ctrl_base + 0x440, 0x5)
+ p.write32(dpe_ctrl_base + 0x444, 0x4)
+ p.write32(dpe_ctrl_base + 0x40c, 0x1)
+ p.write32(dpe_ctrl_base + 0x448, 0x5)
+ p.write32(dpe_ctrl_base + 0x44c, 0x5)
+ p.write32(dpe_ctrl_base + 0x410, 0x1)
+ p.write32(dpe_ctrl_base + 0x450, 0x7)
+ p.write32(dpe_ctrl_base + 0x454, 0x7)
+ p.write32(dpe_ctrl_base + 0x414, 0x1)
+ p.write32(dpe_ctrl_base + 0x458, 0xd)
+ p.write32(dpe_ctrl_base + 0x45c, 0xc)
+ p.write32(dpe_ctrl_base + 0x418, 0x1)
+ p.write32(dpe_ctrl_base + 0x460, 0x13)
+ p.write32(dpe_ctrl_base + 0x464, 0x12)
+ p.write32(dpe_ctrl_base + 0x41c, 0x1)
+ p.write32(dpe_ctrl_base + 0x468, 0x9)
+ p.write32(dpe_ctrl_base + 0x46c, 0xa)
+ p.write32(dpe_ctrl_base + 0x420, 0x1)
+ p.write32(dpe_ctrl_base + 0x470, 0x33)
+ p.write32(dpe_ctrl_base + 0x474, 0x2c)
+ p.write32(dpe_ctrl_base + 0x424, 0x1)
+ p.write32(dpe_ctrl_base + 0x478, 0x15)
+ p.write32(dpe_ctrl_base + 0x47c, 0x15)
+ p.write32(dpe_ctrl_base + 0x428, 0x1)
+ p.write32(dpe_ctrl_base + 0x480, 0xe)
+ p.write32(dpe_ctrl_base + 0x484, 0x5)
+ p.write32(dpe_ctrl_base + 0x42c, 0x1)
+ p.write32(dpe_ctrl_base + 0x488, 0x27)
+ p.write32(dpe_ctrl_base + 0x48c, 0x15)
+ p.write32(dpe_ctrl_base + 0x430, 0x1)
+ p.write32(dpe_ctrl_base + 0x490, 0x15)
+ p.write32(dpe_ctrl_base + 0x494, 0xe)
+ p.write32(dpe_ctrl_base + 0x434, 0x1)
+ p.write32(dpe_ctrl_base + 0x498, 0x0)
+ p.write32(dpe_ctrl_base + 0x49c, 0x0)
+ p.write32(dpe_ctrl_base + 0x4, 0x1000)
+ p.write32(dpe_ctrl_base + 0x0, 0x101)
+
+def dpe_stop():
+ p.write32(dpe_ctrl_base + 0x0, 0x103)
+ while p.read32(dpe_ctrl_base + 0x0) & 0xC != 4:
+ ...
+ p.write32(dpe_ctrl_base + 0x0, p.read32(dpe_ctrl_base + 0x0) & 0xfffffffc)
+
+print(f"Hardware version {scaler.HW_VERSION.val:08X}")
+
+scaler.RESET = 1
+scaler.RESET = 0
+
+print(f"Hardware version after reset {scaler.HW_VERSION.val:08X}")
+
+if len(sys.argv) < 3:
+ print(f"Usage: {sys.argv[0]} input.png output.png")
+ sys.exit(-1)
+
+input_image_fn = sys.argv[1]
+output_image_fn = sys.argv[2]
+
+in_data = b''
+with Image.open(input_image_fn) as im:
+ in_W, in_H = im.size
+ in_BYTESPP = 4
+ in_STRIDE = in_W * in_BYTESPP
+ in_SZ = in_W * in_H * in_BYTESPP
+
+ for y in range(in_H):
+ for x in range(in_W):
+ r, g, b = im.getpixel((x, y))
+ in_data += struct.pack("BBBB", r, g, b, 255)
+
+out_W = in_W * 5
+out_H = in_H * 3
+out_BYTESPP = 4
+out_STRIDE = out_W * out_BYTESPP
+out_SZ = out_W * out_H * out_BYTESPP * 2 # HACK: double size for testing purposes
+
+for i in range(in_W * in_H):
+ in_data += struct.pack("<I", i & 0xFFFFFFFF)
+# chexdump(in_data)
+
+out_buf_phys = u.heap.memalign(0x4000, out_SZ)
+iface.writemem(out_buf_phys, b'\xAA' * out_SZ)
+out_buf_iova = dart.iomap(0, out_buf_phys, out_SZ)
+print(f"Output buffer @ phys {out_buf_phys:016X} iova {out_buf_iova:016X}")
+
+in_buf_phys = u.heap.memalign(0x4000, in_SZ)
+iface.writemem(in_buf_phys, in_data)
+in_buf_iova = dart.iomap(0, in_buf_phys, in_SZ)
+print(f"Input buffer @ phys {in_buf_phys:016X} iova {in_buf_iova:016X}")
+dart.dump_all()
+
+
+
+dpe_start()
+
+# reset CM
+p.write32(scaler_base + 0x3800, 0x0)
+
+# RDMA control
+p.write32(scaler_base + 0x180, 0x1)
+p.write32(scaler_base + 0x184, 0x1e)
+p.write32(scaler_base + 0x188, 0x0)
+p.write32(scaler_base + 0x18c, 0x0)
+p.write32(scaler_base + 0x190, 0x0)
+
+# transform config (flip/rotate)
+scaler.FLIP_ROTATE.set()
+
+# cache hints
+scaler.CACHE_HINTS_THING0[0].val = 0x7d311
+scaler.CACHE_HINTS_THING0[1].val = 0x7d311
+scaler.CACHE_HINTS_THING0[2].val = 0x7d311
+scaler.CACHE_HINTS_THING0[3].val = 0x7d311
+scaler.CACHE_HINTS_THING2[0].val = 0xbd311
+scaler.CACHE_HINTS_THING2[1].val = 0xbd311
+scaler.CACHE_HINTS_THING2[2].val = 0xbd311
+# scaler.CACHE_HINTS_THING2[3].val = 0xbd311
+scaler.CACHE_HINTS_THING1[0].val = 0x707
+scaler.CACHE_HINTS_THING1[1].val = 0x707
+scaler.CACHE_HINTS_THING1[2].val = 0x707
+scaler.CACHE_HINTS_THING1[3].val = 0x707
+scaler.CACHE_HINTS_THING3[0].val = 0xc0bd307
+scaler.CACHE_HINTS_THING3[1].val = 0xc0bd307
+scaler.CACHE_HINTS_THING3[2].val = 0xc0bd307
+# scaler.CACHE_HINTS_THING3[3].val = 0xc0bd307
+
+# tunables
+scaler.TUNABLES_THING0[0].val = 0x20
+scaler.TUNABLES_THING0[1].val = 0x20
+scaler.TUNABLES_THING0[2].val = 0x20
+scaler.TUNABLES_THING0[3].val = 0x20
+scaler.TUNABLES_THING1[0].val = 0x4000720
+scaler.TUNABLES_THING1[1].val = 0x4000720
+scaler.TUNABLES_THING1[2].val = 0x4000720
+# scaler.TUNABLES_THING1[3].val = 0x4000720
+
+# dest base addresses
+scaler.DST_PLANE1_LO = 0
+scaler.DST_PLANE1_HI = 0
+scaler.DST_PLANE0_LO = out_buf_iova & 0xFFFFFFFF
+scaler.DST_PLANE0_HI = out_buf_iova >> 32
+scaler.DST_PLANE2_LO = 0
+scaler.DST_PLANE2_HI = 0
+
+# src base addresses
+scaler.SRC_PLANE1_LO = 0
+scaler.SRC_PLANE1_HI = 0
+scaler.SRC_PLANE0_LO = in_buf_iova & 0xFFFFFFFF
+scaler.SRC_PLANE0_HI = in_buf_iova >> 32
+scaler.SRC_PLANE2_LO = 0
+scaler.SRC_PLANE2_HI = 0
+
+# dest stride
+scaler.DST_PLANE1_STRIDE = 0
+scaler.DST_PLANE0_STRIDE = out_STRIDE
+scaler.DST_PLANE2_STRIDE = 0
+
+# src stride
+scaler.SRC_PLANE1_STRIDE = 0
+scaler.SRC_PLANE0_STRIDE = in_STRIDE
+scaler.SRC_PLANE2_STRIDE = 0
+
+# dest offset
+scaler.DST_PLANE1_OFFSET = 0
+scaler.DST_PLANE0_OFFSET = 0
+scaler.DST_PLANE2_OFFSET = 0
+
+# src offset
+scaler.SRC_PLANE1_OFFSET = 0
+scaler.SRC_PLANE0_OFFSET = 0
+scaler.SRC_PLANE2_OFFSET = 0
+
+# dest sizes
+scaler.DST_W = out_W
+scaler.DST_H = out_H
+
+scaler.DST_SIZE_THING3 = 0
+scaler.DST_SIZE_THING6 = 0
+scaler.DST_SIZE_THING2 = 0
+scaler.DST_SIZE_THING5 = 0
+scaler.DST_SIZE_THING4 = 0
+scaler.DST_SIZE_THING7 = 0
+
+# src sizes
+scaler.SRC_W = in_W
+scaler.SRC_H = in_H
+
+scaler.SRC_SIZE_THING3 = 0
+scaler.SRC_SIZE_THING6 = 0
+scaler.SRC_SIZE_THING2 = 0
+scaler.SRC_SIZE_THING5 = 0
+scaler.SRC_SIZE_THING4 = 0
+scaler.SRC_SIZE_THING7 = 0
+
+# swizzling
+scaler.SRC_SWIZZLE = 0x03020100
+scaler.DST_SWIZZLE = 0x03020100
+
+# WDMA control
+p.write32(scaler_base + 0x280, 0x1)
+p.write32(scaler_base + 0x284, 0x81e)
+p.write32(scaler_base + 0x288, 0x800)
+p.write32(scaler_base + 0x28c, 0x800)
+
+# pixel averaging
+scaler.PIXEL_AVERAGING = 0
+
+# ASE enhancement
+p.write32(scaler_base + 0x16800, 0x0)
+
+# ASE 3x1 transform
+p.write32(scaler_base + 0x16080, 0x0)
+p.write32(scaler_base + 0x16084, 0xb710367)
+p.write32(scaler_base + 0x16088, 0x128)
+
+# ASE interpolation
+p.write32(scaler_base + 0x16600, 0x15)
+
+# ASE angle detect
+p.write32(scaler_base + 0x16504, 0x2000500)
+p.write32(scaler_base + 0x16508, 0x3200)
+p.write32(scaler_base + 0x16534, 0x8)
+p.write32(scaler_base + 0x1651c, 0x851400)
+p.write32(scaler_base + 0x16568, 0x250500)
+p.write32(scaler_base + 0x16588, 0x496513)
+
+# ASE config
+p.write32(scaler_base + 0x16000, 0x0)
+
+# chroma upsampling
+p.write32(scaler_base + 0x800, 0xc)
+
+# chroma downsampling
+p.write32(scaler_base + 0x900, 0x0)
+
+# DDA init V???
+scaler.SCALE_H_DDA_THING0 = 0
+scaler.SCALE_H_DDA_THING2 = 0
+scaler.SCALE_V_DDA_THING1 = 0
+
+# vertical scaling
+scaler.SCALE_V_RATIO_0 = int(in_H / out_H * 0x400000)
+scaler.SCALE_V_RATIO_4 = 0 # XXX what does this do?
+scaler.SCALE_V_RATIO_1 = 0 # XXX what does this do?
+scaler.SCALE_V_RATIO_2 = 0 # XXX what does this set do?
+scaler.SCALE_V_RATIO_3 = 0 # XXX what does this set do?
+scaler.SCALE_V_RATIO_5 = 0 # XXX what does this set do?
+scaler.SCALE_V_FLAGS.set(EN=1)
+
+# XXX this is a random filter grabbed from a random trace
+scaler.SCALE_FILTER_V_BLOCK0[0].val = 0x0
+scaler.SCALE_FILTER_V_BLOCK0[1].val = 0x50005
+scaler.SCALE_FILTER_V_BLOCK1[0].val = 0x50000
+scaler.SCALE_FILTER_V_BLOCK0[2].val = 0xb000b
+scaler.SCALE_FILTER_V_BLOCK0[3].val = 0x100010
+scaler.SCALE_FILTER_V_BLOCK1[1].val = 0x10000b
+scaler.SCALE_FILTER_V_BLOCK0[4].val = 0x140014
+scaler.SCALE_FILTER_V_BLOCK0[5].val = 0x180018
+scaler.SCALE_FILTER_V_BLOCK1[2].val = 0x180014
+scaler.SCALE_FILTER_V_BLOCK0[6].val = 0x1c001c
+scaler.SCALE_FILTER_V_BLOCK0[7].val = 0x200020
+scaler.SCALE_FILTER_V_BLOCK1[3].val = 0x20001c
+scaler.SCALE_FILTER_V_BLOCK0[8].val = 0x230023
+scaler.SCALE_FILTER_V_BLOCK0[9].val = 0x260026
+scaler.SCALE_FILTER_V_BLOCK1[4].val = 0x260023
+scaler.SCALE_FILTER_V_BLOCK0[10].val = 0x290029
+scaler.SCALE_FILTER_V_BLOCK0[11].val = 0x2c002c
+scaler.SCALE_FILTER_V_BLOCK1[5].val = 0x2c0029
+scaler.SCALE_FILTER_V_BLOCK0[12].val = 0x2e002e
+scaler.SCALE_FILTER_V_BLOCK0[13].val = 0x300030
+scaler.SCALE_FILTER_V_BLOCK1[6].val = 0x30002e
+scaler.SCALE_FILTER_V_BLOCK0[14].val = 0x320032
+scaler.SCALE_FILTER_V_BLOCK0[15].val = 0x330033
+scaler.SCALE_FILTER_V_BLOCK1[7].val = 0x330032
+scaler.SCALE_FILTER_V_BLOCK0[16].val = 0xff87ff87
+scaler.SCALE_FILTER_V_BLOCK0[17].val = 0xff90ff90
+scaler.SCALE_FILTER_V_BLOCK1[8].val = 0xff90ff87
+scaler.SCALE_FILTER_V_BLOCK0[18].val = 0xff99ff99
+scaler.SCALE_FILTER_V_BLOCK0[19].val = 0xffa1ffa1
+scaler.SCALE_FILTER_V_BLOCK1[9].val = 0xffa1ff99
+scaler.SCALE_FILTER_V_BLOCK0[20].val = 0xffaaffaa
+scaler.SCALE_FILTER_V_BLOCK0[21].val = 0xffb2ffb2
+scaler.SCALE_FILTER_V_BLOCK1[10].val = 0xffb2ffaa
+scaler.SCALE_FILTER_V_BLOCK0[22].val = 0xffbaffba
+scaler.SCALE_FILTER_V_BLOCK0[23].val = 0xffc2ffc2
+scaler.SCALE_FILTER_V_BLOCK1[11].val = 0xffc2ffba
+scaler.SCALE_FILTER_V_BLOCK0[24].val = 0xffcaffca
+scaler.SCALE_FILTER_V_BLOCK0[25].val = 0xffd2ffd2
+scaler.SCALE_FILTER_V_BLOCK1[12].val = 0xffd2ffca
+scaler.SCALE_FILTER_V_BLOCK0[26].val = 0xffd9ffd9
+scaler.SCALE_FILTER_V_BLOCK0[27].val = 0xffe0ffe0
+scaler.SCALE_FILTER_V_BLOCK1[13].val = 0xffe0ffd9
+scaler.SCALE_FILTER_V_BLOCK0[28].val = 0xffe7ffe7
+scaler.SCALE_FILTER_V_BLOCK0[29].val = 0xffeeffee
+scaler.SCALE_FILTER_V_BLOCK1[14].val = 0xffeeffe7
+scaler.SCALE_FILTER_V_BLOCK0[30].val = 0xfff4fff4
+scaler.SCALE_FILTER_V_BLOCK0[31].val = 0xfffafffa
+scaler.SCALE_FILTER_V_BLOCK1[15].val = 0xfffafff4
+scaler.SCALE_FILTER_V_BLOCK0[32].val = 0xff06ff06
+scaler.SCALE_FILTER_V_BLOCK0[33].val = 0xff0cff0c
+scaler.SCALE_FILTER_V_BLOCK1[16].val = 0xff0cff06
+scaler.SCALE_FILTER_V_BLOCK0[34].val = 0xff13ff13
+scaler.SCALE_FILTER_V_BLOCK0[35].val = 0xff1aff1a
+scaler.SCALE_FILTER_V_BLOCK1[17].val = 0xff1aff13
+scaler.SCALE_FILTER_V_BLOCK0[36].val = 0xff21ff21
+scaler.SCALE_FILTER_V_BLOCK0[37].val = 0xff28ff28
+scaler.SCALE_FILTER_V_BLOCK1[18].val = 0xff28ff21
+scaler.SCALE_FILTER_V_BLOCK0[38].val = 0xff30ff30
+scaler.SCALE_FILTER_V_BLOCK0[39].val = 0xff38ff38
+scaler.SCALE_FILTER_V_BLOCK1[19].val = 0xff38ff30
+scaler.SCALE_FILTER_V_BLOCK0[40].val = 0xff41ff41
+scaler.SCALE_FILTER_V_BLOCK0[41].val = 0xff49ff49
+scaler.SCALE_FILTER_V_BLOCK1[20].val = 0xff49ff41
+scaler.SCALE_FILTER_V_BLOCK0[42].val = 0xff52ff52
+scaler.SCALE_FILTER_V_BLOCK0[43].val = 0xff5bff5b
+scaler.SCALE_FILTER_V_BLOCK1[21].val = 0xff5bff52
+scaler.SCALE_FILTER_V_BLOCK0[44].val = 0xff63ff63
+scaler.SCALE_FILTER_V_BLOCK0[45].val = 0xff6cff6c
+scaler.SCALE_FILTER_V_BLOCK1[22].val = 0xff6cff63
+scaler.SCALE_FILTER_V_BLOCK0[46].val = 0xff75ff75
+scaler.SCALE_FILTER_V_BLOCK0[47].val = 0xff7eff7e
+scaler.SCALE_FILTER_V_BLOCK1[23].val = 0xff7eff75
+scaler.SCALE_FILTER_V_BLOCK0[48].val = 0xff02ff02
+scaler.SCALE_FILTER_V_BLOCK0[49].val = 0xfefcfefc
+scaler.SCALE_FILTER_V_BLOCK1[24].val = 0xfefcff02
+scaler.SCALE_FILTER_V_BLOCK0[50].val = 0xfef7fef7
+scaler.SCALE_FILTER_V_BLOCK0[51].val = 0xfef3fef3
+scaler.SCALE_FILTER_V_BLOCK1[25].val = 0xfef3fef7
+scaler.SCALE_FILTER_V_BLOCK0[52].val = 0xfeeffeef
+scaler.SCALE_FILTER_V_BLOCK0[53].val = 0xfeedfeed
+scaler.SCALE_FILTER_V_BLOCK1[26].val = 0xfeedfeef
+scaler.SCALE_FILTER_V_BLOCK0[54].val = 0xfeecfeec
+scaler.SCALE_FILTER_V_BLOCK0[55].val = 0xfeebfeeb
+scaler.SCALE_FILTER_V_BLOCK1[27].val = 0xfeebfeec
+scaler.SCALE_FILTER_V_BLOCK0[56].val = 0xfeebfeeb
+scaler.SCALE_FILTER_V_BLOCK0[57].val = 0xfeecfeec
+scaler.SCALE_FILTER_V_BLOCK1[28].val = 0xfeecfeeb
+scaler.SCALE_FILTER_V_BLOCK0[58].val = 0xfeeefeee
+scaler.SCALE_FILTER_V_BLOCK0[59].val = 0xfef1fef1
+scaler.SCALE_FILTER_V_BLOCK1[29].val = 0xfef1feee
+scaler.SCALE_FILTER_V_BLOCK0[60].val = 0xfef4fef4
+scaler.SCALE_FILTER_V_BLOCK0[61].val = 0xfef8fef8
+scaler.SCALE_FILTER_V_BLOCK1[30].val = 0xfef8fef4
+scaler.SCALE_FILTER_V_BLOCK0[62].val = 0xfefcfefc
+scaler.SCALE_FILTER_V_BLOCK0[63].val = 0xff01ff01
+scaler.SCALE_FILTER_V_BLOCK1[31].val = 0xff01fefc
+scaler.SCALE_FILTER_V_BLOCK0[64].val = 0x0
+scaler.SCALE_FILTER_V_BLOCK0[65].val = 0xffe7ffe7
+scaler.SCALE_FILTER_V_BLOCK1[32].val = 0xffe70000
+scaler.SCALE_FILTER_V_BLOCK0[66].val = 0xffcfffcf
+scaler.SCALE_FILTER_V_BLOCK0[67].val = 0xffb9ffb9
+scaler.SCALE_FILTER_V_BLOCK1[33].val = 0xffb9ffcf
+scaler.SCALE_FILTER_V_BLOCK0[68].val = 0xffa4ffa4
+scaler.SCALE_FILTER_V_BLOCK0[69].val = 0xff90ff90
+scaler.SCALE_FILTER_V_BLOCK1[34].val = 0xff90ffa4
+scaler.SCALE_FILTER_V_BLOCK0[70].val = 0xff7dff7d
+scaler.SCALE_FILTER_V_BLOCK0[71].val = 0xff6bff6b
+scaler.SCALE_FILTER_V_BLOCK1[35].val = 0xff6bff7d
+scaler.SCALE_FILTER_V_BLOCK0[72].val = 0xff5bff5b
+scaler.SCALE_FILTER_V_BLOCK0[73].val = 0xff4cff4c
+scaler.SCALE_FILTER_V_BLOCK1[36].val = 0xff4cff5b
+scaler.SCALE_FILTER_V_BLOCK0[74].val = 0xff3eff3e
+scaler.SCALE_FILTER_V_BLOCK0[75].val = 0xff31ff31
+scaler.SCALE_FILTER_V_BLOCK1[37].val = 0xff31ff3e
+scaler.SCALE_FILTER_V_BLOCK0[76].val = 0xff26ff26
+scaler.SCALE_FILTER_V_BLOCK0[77].val = 0xff1bff1b
+scaler.SCALE_FILTER_V_BLOCK1[38].val = 0xff1bff26
+scaler.SCALE_FILTER_V_BLOCK0[78].val = 0xff12ff12
+scaler.SCALE_FILTER_V_BLOCK0[79].val = 0xff0aff0a
+scaler.SCALE_FILTER_V_BLOCK1[39].val = 0xff0aff12
+scaler.SCALE_FILTER_V_BLOCK0[80].val = 0x2210221
+scaler.SCALE_FILTER_V_BLOCK0[81].val = 0x1f901f9
+scaler.SCALE_FILTER_V_BLOCK1[40].val = 0x1f90221
+scaler.SCALE_FILTER_V_BLOCK0[82].val = 0x1d001d0
+scaler.SCALE_FILTER_V_BLOCK0[83].val = 0x1a901a9
+scaler.SCALE_FILTER_V_BLOCK1[41].val = 0x1a901d0
+scaler.SCALE_FILTER_V_BLOCK0[84].val = 0x1820182
+scaler.SCALE_FILTER_V_BLOCK0[85].val = 0x15d015d
+scaler.SCALE_FILTER_V_BLOCK1[42].val = 0x15d0182
+scaler.SCALE_FILTER_V_BLOCK0[86].val = 0x1380138
+scaler.SCALE_FILTER_V_BLOCK0[87].val = 0x1140114
+scaler.SCALE_FILTER_V_BLOCK1[43].val = 0x1140138
+scaler.SCALE_FILTER_V_BLOCK0[88].val = 0xf100f1
+scaler.SCALE_FILTER_V_BLOCK0[89].val = 0xcf00cf
+scaler.SCALE_FILTER_V_BLOCK1[44].val = 0xcf00f1
+scaler.SCALE_FILTER_V_BLOCK0[90].val = 0xae00ae
+scaler.SCALE_FILTER_V_BLOCK0[91].val = 0x8e008e
+scaler.SCALE_FILTER_V_BLOCK1[45].val = 0x8e00ae
+scaler.SCALE_FILTER_V_BLOCK0[92].val = 0x6f006f
+scaler.SCALE_FILTER_V_BLOCK0[93].val = 0x520052
+scaler.SCALE_FILTER_V_BLOCK1[46].val = 0x52006f
+scaler.SCALE_FILTER_V_BLOCK0[94].val = 0x350035
+scaler.SCALE_FILTER_V_BLOCK0[95].val = 0x1a001a
+scaler.SCALE_FILTER_V_BLOCK1[47].val = 0x1a0035
+scaler.SCALE_FILTER_V_BLOCK0[96].val = 0x4e404e4
+scaler.SCALE_FILTER_V_BLOCK0[97].val = 0x4b804b8
+scaler.SCALE_FILTER_V_BLOCK1[48].val = 0x4b804e4
+scaler.SCALE_FILTER_V_BLOCK0[98].val = 0x48b048b
+scaler.SCALE_FILTER_V_BLOCK0[99].val = 0x45f045f
+scaler.SCALE_FILTER_V_BLOCK1[49].val = 0x45f048b
+scaler.SCALE_FILTER_V_BLOCK0[100].val = 0x4320432
+scaler.SCALE_FILTER_V_BLOCK0[101].val = 0x4050405
+scaler.SCALE_FILTER_V_BLOCK1[50].val = 0x4050432
+scaler.SCALE_FILTER_V_BLOCK0[102].val = 0x3d803d8
+scaler.SCALE_FILTER_V_BLOCK0[103].val = 0x3ab03ab
+scaler.SCALE_FILTER_V_BLOCK1[51].val = 0x3ab03d8
+scaler.SCALE_FILTER_V_BLOCK0[104].val = 0x37e037e
+scaler.SCALE_FILTER_V_BLOCK0[105].val = 0x3510351
+scaler.SCALE_FILTER_V_BLOCK1[52].val = 0x351037e
+scaler.SCALE_FILTER_V_BLOCK0[106].val = 0x3240324
+scaler.SCALE_FILTER_V_BLOCK0[107].val = 0x2f802f8
+scaler.SCALE_FILTER_V_BLOCK1[53].val = 0x2f80324
+scaler.SCALE_FILTER_V_BLOCK0[108].val = 0x2cc02cc
+scaler.SCALE_FILTER_V_BLOCK0[109].val = 0x2a102a1
+scaler.SCALE_FILTER_V_BLOCK1[54].val = 0x2a102cc
+scaler.SCALE_FILTER_V_BLOCK0[110].val = 0x2760276
+scaler.SCALE_FILTER_V_BLOCK0[111].val = 0x24b024b
+scaler.SCALE_FILTER_V_BLOCK1[55].val = 0x24b0276
+scaler.SCALE_FILTER_V_BLOCK0[112].val = 0x73b073b
+scaler.SCALE_FILTER_V_BLOCK0[113].val = 0x71e071e
+scaler.SCALE_FILTER_V_BLOCK1[56].val = 0x71e073b
+scaler.SCALE_FILTER_V_BLOCK0[114].val = 0x7000700
+scaler.SCALE_FILTER_V_BLOCK0[115].val = 0x6e106e1
+scaler.SCALE_FILTER_V_BLOCK1[57].val = 0x6e10700
+scaler.SCALE_FILTER_V_BLOCK0[116].val = 0x6c006c0
+scaler.SCALE_FILTER_V_BLOCK0[117].val = 0x69e069e
+scaler.SCALE_FILTER_V_BLOCK1[58].val = 0x69e06c0
+scaler.SCALE_FILTER_V_BLOCK0[118].val = 0x67a067a
+scaler.SCALE_FILTER_V_BLOCK0[119].val = 0x6560656
+scaler.SCALE_FILTER_V_BLOCK1[59].val = 0x656067a
+scaler.SCALE_FILTER_V_BLOCK0[120].val = 0x6300630
+scaler.SCALE_FILTER_V_BLOCK0[121].val = 0x6090609
+scaler.SCALE_FILTER_V_BLOCK1[60].val = 0x6090630
+scaler.SCALE_FILTER_V_BLOCK0[122].val = 0x5e205e2
+scaler.SCALE_FILTER_V_BLOCK0[123].val = 0x5b905b9
+scaler.SCALE_FILTER_V_BLOCK1[61].val = 0x5b905e2
+scaler.SCALE_FILTER_V_BLOCK0[124].val = 0x5900590
+scaler.SCALE_FILTER_V_BLOCK0[125].val = 0x5660566
+scaler.SCALE_FILTER_V_BLOCK1[62].val = 0x5660590
+scaler.SCALE_FILTER_V_BLOCK0[126].val = 0x53b053b
+scaler.SCALE_FILTER_V_BLOCK0[127].val = 0x5100510
+scaler.SCALE_FILTER_V_BLOCK1[63].val = 0x510053b
+scaler.SCALE_FILTER_V_BLOCK0[128].val = 0x82c082c
+scaler.SCALE_FILTER_V_BLOCK0[129].val = 0x82b082b
+scaler.SCALE_FILTER_V_BLOCK1[64].val = 0x82b082c
+scaler.SCALE_FILTER_V_BLOCK0[130].val = 0x8280828
+scaler.SCALE_FILTER_V_BLOCK0[131].val = 0x8200820
+scaler.SCALE_FILTER_V_BLOCK1[65].val = 0x8200828
+scaler.SCALE_FILTER_V_BLOCK0[132].val = 0x81b081b
+scaler.SCALE_FILTER_V_BLOCK0[133].val = 0x8130813
+scaler.SCALE_FILTER_V_BLOCK1[66].val = 0x813081b
+scaler.SCALE_FILTER_V_BLOCK0[134].val = 0x8080808
+scaler.SCALE_FILTER_V_BLOCK0[135].val = 0x7fc07fc
+scaler.SCALE_FILTER_V_BLOCK1[67].val = 0x7fc0808
+scaler.SCALE_FILTER_V_BLOCK0[136].val = 0x7ed07ed
+scaler.SCALE_FILTER_V_BLOCK0[137].val = 0x7dd07dd
+scaler.SCALE_FILTER_V_BLOCK1[68].val = 0x7dd07ed
+scaler.SCALE_FILTER_V_BLOCK0[138].val = 0x7cb07cb
+scaler.SCALE_FILTER_V_BLOCK0[139].val = 0x7b607b6
+scaler.SCALE_FILTER_V_BLOCK1[69].val = 0x7b607cb
+scaler.SCALE_FILTER_V_BLOCK0[140].val = 0x7a207a2
+scaler.SCALE_FILTER_V_BLOCK0[141].val = 0x78a078a
+scaler.SCALE_FILTER_V_BLOCK1[70].val = 0x78a07a2
+scaler.SCALE_FILTER_V_BLOCK0[142].val = 0x7710771
+scaler.SCALE_FILTER_V_BLOCK0[143].val = 0x7570757
+scaler.SCALE_FILTER_V_BLOCK1[71].val = 0x7570771
+scaler.SCALE_FILTER_V_BLOCK0[144].val = 0x73d073d
+scaler.SCALE_FILTER_V_BLOCK0[145].val = 0x7570757
+scaler.SCALE_FILTER_V_BLOCK1[72].val = 0x757073d
+scaler.SCALE_FILTER_V_BLOCK0[146].val = 0x7710771
+scaler.SCALE_FILTER_V_BLOCK0[147].val = 0x78a078a
+scaler.SCALE_FILTER_V_BLOCK1[73].val = 0x78a0771
+scaler.SCALE_FILTER_V_BLOCK0[148].val = 0x7a207a2
+scaler.SCALE_FILTER_V_BLOCK0[149].val = 0x7b607b6
+scaler.SCALE_FILTER_V_BLOCK1[74].val = 0x7b607a2
+scaler.SCALE_FILTER_V_BLOCK0[150].val = 0x7cb07cb
+scaler.SCALE_FILTER_V_BLOCK0[151].val = 0x7dd07dd
+scaler.SCALE_FILTER_V_BLOCK1[75].val = 0x7dd07cb
+scaler.SCALE_FILTER_V_BLOCK0[152].val = 0x7ed07ed
+scaler.SCALE_FILTER_V_BLOCK0[153].val = 0x7fc07fc
+scaler.SCALE_FILTER_V_BLOCK1[76].val = 0x7fc07ed
+scaler.SCALE_FILTER_V_BLOCK0[154].val = 0x8080808
+scaler.SCALE_FILTER_V_BLOCK0[155].val = 0x8130813
+scaler.SCALE_FILTER_V_BLOCK1[77].val = 0x8130808
+scaler.SCALE_FILTER_V_BLOCK0[156].val = 0x81b081b
+scaler.SCALE_FILTER_V_BLOCK0[157].val = 0x8200820
+scaler.SCALE_FILTER_V_BLOCK1[78].val = 0x820081b
+scaler.SCALE_FILTER_V_BLOCK0[158].val = 0x8280828
+scaler.SCALE_FILTER_V_BLOCK0[159].val = 0x82b082b
+scaler.SCALE_FILTER_V_BLOCK1[79].val = 0x82b0828
+scaler.SCALE_FILTER_V_BLOCK0[160].val = 0x4e404e4
+scaler.SCALE_FILTER_V_BLOCK0[161].val = 0x5100510
+scaler.SCALE_FILTER_V_BLOCK1[80].val = 0x51004e4
+scaler.SCALE_FILTER_V_BLOCK0[162].val = 0x53b053b
+scaler.SCALE_FILTER_V_BLOCK0[163].val = 0x5660566
+scaler.SCALE_FILTER_V_BLOCK1[81].val = 0x566053b
+scaler.SCALE_FILTER_V_BLOCK0[164].val = 0x5900590
+scaler.SCALE_FILTER_V_BLOCK0[165].val = 0x5b905b9
+scaler.SCALE_FILTER_V_BLOCK1[82].val = 0x5b90590
+scaler.SCALE_FILTER_V_BLOCK0[166].val = 0x5e205e2
+scaler.SCALE_FILTER_V_BLOCK0[167].val = 0x6090609
+scaler.SCALE_FILTER_V_BLOCK1[83].val = 0x60905e2
+scaler.SCALE_FILTER_V_BLOCK0[168].val = 0x6300630
+scaler.SCALE_FILTER_V_BLOCK0[169].val = 0x6560656
+scaler.SCALE_FILTER_V_BLOCK1[84].val = 0x6560630
+scaler.SCALE_FILTER_V_BLOCK0[170].val = 0x67a067a
+scaler.SCALE_FILTER_V_BLOCK0[171].val = 0x69e069e
+scaler.SCALE_FILTER_V_BLOCK1[85].val = 0x69e067a
+scaler.SCALE_FILTER_V_BLOCK0[172].val = 0x6c006c0
+scaler.SCALE_FILTER_V_BLOCK0[173].val = 0x6e106e1
+scaler.SCALE_FILTER_V_BLOCK1[86].val = 0x6e106c0
+scaler.SCALE_FILTER_V_BLOCK0[174].val = 0x7000700
+scaler.SCALE_FILTER_V_BLOCK0[175].val = 0x71e071e
+scaler.SCALE_FILTER_V_BLOCK1[87].val = 0x71e0700
+scaler.SCALE_FILTER_V_BLOCK0[176].val = 0x2210221
+scaler.SCALE_FILTER_V_BLOCK0[177].val = 0x24b024b
+scaler.SCALE_FILTER_V_BLOCK1[88].val = 0x24b0221
+scaler.SCALE_FILTER_V_BLOCK0[178].val = 0x2760276
+scaler.SCALE_FILTER_V_BLOCK0[179].val = 0x2a102a1
+scaler.SCALE_FILTER_V_BLOCK1[89].val = 0x2a10276
+scaler.SCALE_FILTER_V_BLOCK0[180].val = 0x2cc02cc
+scaler.SCALE_FILTER_V_BLOCK0[181].val = 0x2f802f8
+scaler.SCALE_FILTER_V_BLOCK1[90].val = 0x2f802cc
+scaler.SCALE_FILTER_V_BLOCK0[182].val = 0x3240324
+scaler.SCALE_FILTER_V_BLOCK0[183].val = 0x3510351
+scaler.SCALE_FILTER_V_BLOCK1[91].val = 0x3510324
+scaler.SCALE_FILTER_V_BLOCK0[184].val = 0x37e037e
+scaler.SCALE_FILTER_V_BLOCK0[185].val = 0x3ab03ab
+scaler.SCALE_FILTER_V_BLOCK1[92].val = 0x3ab037e
+scaler.SCALE_FILTER_V_BLOCK0[186].val = 0x3d803d8
+scaler.SCALE_FILTER_V_BLOCK0[187].val = 0x4050405
+scaler.SCALE_FILTER_V_BLOCK1[93].val = 0x40503d8
+scaler.SCALE_FILTER_V_BLOCK0[188].val = 0x4320432
+scaler.SCALE_FILTER_V_BLOCK0[189].val = 0x45f045f
+scaler.SCALE_FILTER_V_BLOCK1[94].val = 0x45f0432
+scaler.SCALE_FILTER_V_BLOCK0[190].val = 0x48b048b
+scaler.SCALE_FILTER_V_BLOCK0[191].val = 0x4b804b8
+scaler.SCALE_FILTER_V_BLOCK1[95].val = 0x4b8048b
+scaler.SCALE_FILTER_V_BLOCK0[192].val = 0x0
+scaler.SCALE_FILTER_V_BLOCK0[193].val = 0x1a001a
+scaler.SCALE_FILTER_V_BLOCK1[96].val = 0x1a0000
+scaler.SCALE_FILTER_V_BLOCK0[194].val = 0x350035
+scaler.SCALE_FILTER_V_BLOCK0[195].val = 0x520052
+scaler.SCALE_FILTER_V_BLOCK1[97].val = 0x520035
+scaler.SCALE_FILTER_V_BLOCK0[196].val = 0x6f006f
+scaler.SCALE_FILTER_V_BLOCK0[197].val = 0x8e008e
+scaler.SCALE_FILTER_V_BLOCK1[98].val = 0x8e006f
+scaler.SCALE_FILTER_V_BLOCK0[198].val = 0xae00ae
+scaler.SCALE_FILTER_V_BLOCK0[199].val = 0xcf00cf
+scaler.SCALE_FILTER_V_BLOCK1[99].val = 0xcf00ae
+scaler.SCALE_FILTER_V_BLOCK0[200].val = 0xf100f1
+scaler.SCALE_FILTER_V_BLOCK0[201].val = 0x1140114
+scaler.SCALE_FILTER_V_BLOCK1[100].val = 0x11400f1
+scaler.SCALE_FILTER_V_BLOCK0[202].val = 0x1380138
+scaler.SCALE_FILTER_V_BLOCK0[203].val = 0x15d015d
+scaler.SCALE_FILTER_V_BLOCK1[101].val = 0x15d0138
+scaler.SCALE_FILTER_V_BLOCK0[204].val = 0x1820182
+scaler.SCALE_FILTER_V_BLOCK0[205].val = 0x1a901a9
+scaler.SCALE_FILTER_V_BLOCK1[102].val = 0x1a90182
+scaler.SCALE_FILTER_V_BLOCK0[206].val = 0x1d001d0
+scaler.SCALE_FILTER_V_BLOCK0[207].val = 0x1f901f9
+scaler.SCALE_FILTER_V_BLOCK1[103].val = 0x1f901d0
+scaler.SCALE_FILTER_V_BLOCK0[208].val = 0xff02ff02
+scaler.SCALE_FILTER_V_BLOCK0[209].val = 0xff0aff0a
+scaler.SCALE_FILTER_V_BLOCK1[104].val = 0xff0aff02
+scaler.SCALE_FILTER_V_BLOCK0[210].val = 0xff12ff12
+scaler.SCALE_FILTER_V_BLOCK0[211].val = 0xff1bff1b
+scaler.SCALE_FILTER_V_BLOCK1[105].val = 0xff1bff12
+scaler.SCALE_FILTER_V_BLOCK0[212].val = 0xff26ff26
+scaler.SCALE_FILTER_V_BLOCK0[213].val = 0xff31ff31
+scaler.SCALE_FILTER_V_BLOCK1[106].val = 0xff31ff26
+scaler.SCALE_FILTER_V_BLOCK0[214].val = 0xff3eff3e
+scaler.SCALE_FILTER_V_BLOCK0[215].val = 0xff4cff4c
+scaler.SCALE_FILTER_V_BLOCK1[107].val = 0xff4cff3e
+scaler.SCALE_FILTER_V_BLOCK0[216].val = 0xff5bff5b
+scaler.SCALE_FILTER_V_BLOCK0[218].val = 0xff6bff6b
+scaler.SCALE_FILTER_V_BLOCK1[108].val = 0xff6bff5b
+scaler.SCALE_FILTER_V_BLOCK0[218].val = 0xff7dff7d
+scaler.SCALE_FILTER_V_BLOCK0[219].val = 0xff90ff90
+scaler.SCALE_FILTER_V_BLOCK1[109].val = 0xff90ff7d
+scaler.SCALE_FILTER_V_BLOCK0[220].val = 0xffa4ffa4
+scaler.SCALE_FILTER_V_BLOCK0[221].val = 0xffb9ffb9
+scaler.SCALE_FILTER_V_BLOCK1[110].val = 0xffb9ffa4
+scaler.SCALE_FILTER_V_BLOCK0[222].val = 0xffcfffcf
+scaler.SCALE_FILTER_V_BLOCK0[223].val = 0xffe7ffe7
+scaler.SCALE_FILTER_V_BLOCK1[111].val = 0xffe7ffcf
+scaler.SCALE_FILTER_V_BLOCK0[224].val = 0xff06ff06
+scaler.SCALE_FILTER_V_BLOCK0[225].val = 0xff01ff01
+scaler.SCALE_FILTER_V_BLOCK1[112].val = 0xff01ff06
+scaler.SCALE_FILTER_V_BLOCK0[226].val = 0xfefcfefc
+scaler.SCALE_FILTER_V_BLOCK0[227].val = 0xfef8fef8
+scaler.SCALE_FILTER_V_BLOCK1[113].val = 0xfef8fefc
+scaler.SCALE_FILTER_V_BLOCK0[228].val = 0xfef4fef4
+scaler.SCALE_FILTER_V_BLOCK0[229].val = 0xfef1fef1
+scaler.SCALE_FILTER_V_BLOCK1[114].val = 0xfef1fef4
+scaler.SCALE_FILTER_V_BLOCK0[230].val = 0xfeeefeee
+scaler.SCALE_FILTER_V_BLOCK0[231].val = 0xfeecfeec
+scaler.SCALE_FILTER_V_BLOCK1[115].val = 0xfeecfeee
+scaler.SCALE_FILTER_V_BLOCK0[232].val = 0xfeebfeeb
+scaler.SCALE_FILTER_V_BLOCK0[233].val = 0xfeebfeeb
+scaler.SCALE_FILTER_V_BLOCK1[116].val = 0xfeebfeeb
+scaler.SCALE_FILTER_V_BLOCK0[234].val = 0xfeecfeec
+scaler.SCALE_FILTER_V_BLOCK0[235].val = 0xfeedfeed
+scaler.SCALE_FILTER_V_BLOCK1[117].val = 0xfeedfeec
+scaler.SCALE_FILTER_V_BLOCK0[236].val = 0xfeeffeef
+scaler.SCALE_FILTER_V_BLOCK0[237].val = 0xfef3fef3
+scaler.SCALE_FILTER_V_BLOCK1[118].val = 0xfef3feef
+scaler.SCALE_FILTER_V_BLOCK0[238].val = 0xfef7fef7
+scaler.SCALE_FILTER_V_BLOCK0[239].val = 0xfefcfefc
+scaler.SCALE_FILTER_V_BLOCK1[119].val = 0xfefcfef7
+scaler.SCALE_FILTER_V_BLOCK0[240].val = 0xff87ff87
+scaler.SCALE_FILTER_V_BLOCK0[241].val = 0xff7eff7e
+scaler.SCALE_FILTER_V_BLOCK1[120].val = 0xff7eff87
+scaler.SCALE_FILTER_V_BLOCK0[242].val = 0xff75ff75
+scaler.SCALE_FILTER_V_BLOCK0[243].val = 0xff6cff6c
+scaler.SCALE_FILTER_V_BLOCK1[121].val = 0xff6cff75
+scaler.SCALE_FILTER_V_BLOCK0[244].val = 0xff63ff63
+scaler.SCALE_FILTER_V_BLOCK0[245].val = 0xff5bff5b
+scaler.SCALE_FILTER_V_BLOCK1[122].val = 0xff5bff63
+scaler.SCALE_FILTER_V_BLOCK0[246].val = 0xff52ff52
+scaler.SCALE_FILTER_V_BLOCK0[247].val = 0xff49ff49
+scaler.SCALE_FILTER_V_BLOCK1[123].val = 0xff49ff52
+scaler.SCALE_FILTER_V_BLOCK0[248].val = 0xff41ff41
+scaler.SCALE_FILTER_V_BLOCK0[249].val = 0xff38ff38
+scaler.SCALE_FILTER_V_BLOCK1[124].val = 0xff38ff41
+scaler.SCALE_FILTER_V_BLOCK0[250].val = 0xff30ff30
+scaler.SCALE_FILTER_V_BLOCK0[251].val = 0xff28ff28
+scaler.SCALE_FILTER_V_BLOCK1[125].val = 0xff28ff30
+scaler.SCALE_FILTER_V_BLOCK0[252].val = 0xff21ff21
+scaler.SCALE_FILTER_V_BLOCK0[253].val = 0xff1aff1a
+scaler.SCALE_FILTER_V_BLOCK1[126].val = 0xff1aff21
+scaler.SCALE_FILTER_V_BLOCK0[254].val = 0xff13ff13
+scaler.SCALE_FILTER_V_BLOCK0[255].val = 0xff0cff0c
+scaler.SCALE_FILTER_V_BLOCK1[127].val = 0xff0cff13
+scaler.SCALE_FILTER_V_BLOCK0[256].val = 0x0
+scaler.SCALE_FILTER_V_BLOCK0[257].val = 0xfffafffa
+scaler.SCALE_FILTER_V_BLOCK1[128].val = 0xfffa0000
+scaler.SCALE_FILTER_V_BLOCK0[258].val = 0xfff4fff4
+scaler.SCALE_FILTER_V_BLOCK0[259].val = 0xffeeffee
+scaler.SCALE_FILTER_V_BLOCK1[129].val = 0xffeefff4
+scaler.SCALE_FILTER_V_BLOCK0[260].val = 0xffe7ffe7
+scaler.SCALE_FILTER_V_BLOCK0[261].val = 0xffe0ffe0
+scaler.SCALE_FILTER_V_BLOCK1[130].val = 0xffe0ffe7
+scaler.SCALE_FILTER_V_BLOCK0[262].val = 0xffd9ffd9
+scaler.SCALE_FILTER_V_BLOCK0[263].val = 0xffd2ffd2
+scaler.SCALE_FILTER_V_BLOCK1[131].val = 0xffd2ffd9
+scaler.SCALE_FILTER_V_BLOCK0[264].val = 0xffcaffca
+scaler.SCALE_FILTER_V_BLOCK0[265].val = 0xffc2ffc2
+scaler.SCALE_FILTER_V_BLOCK1[132].val = 0xffc2ffca
+scaler.SCALE_FILTER_V_BLOCK0[266].val = 0xffbaffba
+scaler.SCALE_FILTER_V_BLOCK0[267].val = 0xffb2ffb2
+scaler.SCALE_FILTER_V_BLOCK1[133].val = 0xffb2ffba
+scaler.SCALE_FILTER_V_BLOCK0[268].val = 0xffaaffaa
+scaler.SCALE_FILTER_V_BLOCK0[269].val = 0xffa1ffa1
+scaler.SCALE_FILTER_V_BLOCK1[134].val = 0xffa1ffaa
+scaler.SCALE_FILTER_V_BLOCK0[270].val = 0xff99ff99
+scaler.SCALE_FILTER_V_BLOCK0[271].val = 0xff90ff90
+scaler.SCALE_FILTER_V_BLOCK1[135].val = 0xff90ff99
+scaler.SCALE_FILTER_V_BLOCK0[272].val = 0x340034
+scaler.SCALE_FILTER_V_BLOCK0[273].val = 0x330033
+scaler.SCALE_FILTER_V_BLOCK1[136].val = 0x330034
+scaler.SCALE_FILTER_V_BLOCK0[274].val = 0x320032
+scaler.SCALE_FILTER_V_BLOCK0[275].val = 0x300030
+scaler.SCALE_FILTER_V_BLOCK1[137].val = 0x300032
+scaler.SCALE_FILTER_V_BLOCK0[276].val = 0x2e002e
+scaler.SCALE_FILTER_V_BLOCK0[277].val = 0x2c002c
+scaler.SCALE_FILTER_V_BLOCK1[138].val = 0x2c002e
+scaler.SCALE_FILTER_V_BLOCK0[278].val = 0x290029
+scaler.SCALE_FILTER_V_BLOCK0[279].val = 0x260026
+scaler.SCALE_FILTER_V_BLOCK1[139].val = 0x260029
+scaler.SCALE_FILTER_V_BLOCK0[280].val = 0x230023
+scaler.SCALE_FILTER_V_BLOCK0[281].val = 0x200020
+scaler.SCALE_FILTER_V_BLOCK1[140].val = 0x200023
+scaler.SCALE_FILTER_V_BLOCK0[282].val = 0x1c001c
+scaler.SCALE_FILTER_V_BLOCK0[283].val = 0x180018
+scaler.SCALE_FILTER_V_BLOCK1[141].val = 0x18001c
+scaler.SCALE_FILTER_V_BLOCK0[284].val = 0x140014
+scaler.SCALE_FILTER_V_BLOCK0[285].val = 0x100010
+scaler.SCALE_FILTER_V_BLOCK1[142].val = 0x100014
+scaler.SCALE_FILTER_V_BLOCK0[286].val = 0xb000b
+scaler.SCALE_FILTER_V_BLOCK0[287].val = 0x50005
+scaler.SCALE_FILTER_V_BLOCK1[143].val = 0x5000b
+
+# DDA init H
+scaler.SCALE_H_DDA_THING0 = 0
+scaler.SCALE_H_DDA_THING2 = 0
+scaler.SCALE_H_DDA_THING1 = 0
+
+# horizontal scaling
+scaler.SCALE_H_RATIO_0 = int(in_W / out_W * 0x400000)
+scaler.SCALE_H_RATIO_4 = 0 # XXX what does this do?
+scaler.SCALE_H_RATIO_1 = 0 # XXX what does this do?
+scaler.SCALE_H_RATIO_2 = int(out_W / in_W * 0x400000) # XXX what does this set do? zeroing this one out doesn't work
+scaler.SCALE_H_RATIO_3 = 0 # XXX what does this set do?
+scaler.SCALE_H_RATIO_5 = 0 # XXX what does this set do?
+scaler.SCALE_H_FLAGS.set(EN=1)
+
+scaler.SCALE_FILTER_H_BLOCK0[0].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[1].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[0].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[2].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[3].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[1].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[4].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[5].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[2].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[6].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[7].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[3].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[8].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[9].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[4].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[10].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[11].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[5].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[12].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[13].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[6].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[14].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[15].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[7].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[16].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[17].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[8].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[18].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[19].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[9].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[20].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[21].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[10].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[22].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[23].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[11].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[24].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[25].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[12].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[26].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[27].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[13].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[28].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[29].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[14].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[30].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[31].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[15].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[32].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[33].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[16].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[34].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[35].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[17].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[36].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[37].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[18].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[38].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[39].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[19].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[40].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[41].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[20].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[42].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[43].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[21].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[44].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[45].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[22].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[46].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[47].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[23].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[48].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[49].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[24].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[50].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[51].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[25].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[52].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[53].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[26].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[54].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[55].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[27].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[56].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[57].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[28].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[58].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[59].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[29].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[60].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[61].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[30].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[62].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[63].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[31].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[64].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[65].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[32].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[66].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[67].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[33].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[68].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[69].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[34].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[70].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[71].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[35].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[72].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[73].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[36].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[74].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[75].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[37].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[76].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[77].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[38].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[78].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[79].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[39].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[80].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[81].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[40].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[82].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[83].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[41].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[84].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[85].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[42].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[86].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[87].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[43].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[88].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[89].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[44].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[90].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[91].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[45].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[92].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[93].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[46].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[94].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[95].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[47].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[96].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[97].val = 0x50005
+scaler.SCALE_FILTER_H_BLOCK1[48].val = 0x50000
+scaler.SCALE_FILTER_H_BLOCK0[98].val = 0xb000b
+scaler.SCALE_FILTER_H_BLOCK0[99].val = 0x100010
+scaler.SCALE_FILTER_H_BLOCK1[49].val = 0x10000b
+scaler.SCALE_FILTER_H_BLOCK0[100].val = 0x140014
+scaler.SCALE_FILTER_H_BLOCK0[101].val = 0x180018
+scaler.SCALE_FILTER_H_BLOCK1[50].val = 0x180014
+scaler.SCALE_FILTER_H_BLOCK0[102].val = 0x1c001c
+scaler.SCALE_FILTER_H_BLOCK0[103].val = 0x200020
+scaler.SCALE_FILTER_H_BLOCK1[51].val = 0x20001c
+scaler.SCALE_FILTER_H_BLOCK0[104].val = 0x230023
+scaler.SCALE_FILTER_H_BLOCK0[105].val = 0x260026
+scaler.SCALE_FILTER_H_BLOCK1[52].val = 0x260023
+scaler.SCALE_FILTER_H_BLOCK0[106].val = 0x290029
+scaler.SCALE_FILTER_H_BLOCK0[107].val = 0x2c002c
+scaler.SCALE_FILTER_H_BLOCK1[53].val = 0x2c0029
+scaler.SCALE_FILTER_H_BLOCK0[108].val = 0x2e002e
+scaler.SCALE_FILTER_H_BLOCK0[109].val = 0x300030
+scaler.SCALE_FILTER_H_BLOCK1[54].val = 0x30002e
+scaler.SCALE_FILTER_H_BLOCK0[110].val = 0x320032
+scaler.SCALE_FILTER_H_BLOCK0[111].val = 0x330033
+scaler.SCALE_FILTER_H_BLOCK1[55].val = 0x330032
+scaler.SCALE_FILTER_H_BLOCK0[112].val = 0xff87ff87
+scaler.SCALE_FILTER_H_BLOCK0[113].val = 0xff90ff90
+scaler.SCALE_FILTER_H_BLOCK1[56].val = 0xff90ff87
+scaler.SCALE_FILTER_H_BLOCK0[114].val = 0xff99ff99
+scaler.SCALE_FILTER_H_BLOCK0[115].val = 0xffa1ffa1
+scaler.SCALE_FILTER_H_BLOCK1[57].val = 0xffa1ff99
+scaler.SCALE_FILTER_H_BLOCK0[116].val = 0xffaaffaa
+scaler.SCALE_FILTER_H_BLOCK0[117].val = 0xffb2ffb2
+scaler.SCALE_FILTER_H_BLOCK1[58].val = 0xffb2ffaa
+scaler.SCALE_FILTER_H_BLOCK0[118].val = 0xffbaffba
+scaler.SCALE_FILTER_H_BLOCK0[119].val = 0xffc2ffc2
+scaler.SCALE_FILTER_H_BLOCK1[59].val = 0xffc2ffba
+scaler.SCALE_FILTER_H_BLOCK0[120].val = 0xffcaffca
+scaler.SCALE_FILTER_H_BLOCK0[121].val = 0xffd2ffd2
+scaler.SCALE_FILTER_H_BLOCK1[60].val = 0xffd2ffca
+scaler.SCALE_FILTER_H_BLOCK0[122].val = 0xffd9ffd9
+scaler.SCALE_FILTER_H_BLOCK0[123].val = 0xffe0ffe0
+scaler.SCALE_FILTER_H_BLOCK1[61].val = 0xffe0ffd9
+scaler.SCALE_FILTER_H_BLOCK0[124].val = 0xffe7ffe7
+scaler.SCALE_FILTER_H_BLOCK0[125].val = 0xffeeffee
+scaler.SCALE_FILTER_H_BLOCK1[62].val = 0xffeeffe7
+scaler.SCALE_FILTER_H_BLOCK0[126].val = 0xfff4fff4
+scaler.SCALE_FILTER_H_BLOCK0[127].val = 0xfffafffa
+scaler.SCALE_FILTER_H_BLOCK1[63].val = 0xfffafff4
+scaler.SCALE_FILTER_H_BLOCK0[128].val = 0xff06ff06
+scaler.SCALE_FILTER_H_BLOCK0[129].val = 0xff0cff0c
+scaler.SCALE_FILTER_H_BLOCK1[64].val = 0xff0cff06
+scaler.SCALE_FILTER_H_BLOCK0[130].val = 0xff13ff13
+scaler.SCALE_FILTER_H_BLOCK0[131].val = 0xff1aff1a
+scaler.SCALE_FILTER_H_BLOCK1[65].val = 0xff1aff13
+scaler.SCALE_FILTER_H_BLOCK0[132].val = 0xff21ff21
+scaler.SCALE_FILTER_H_BLOCK0[133].val = 0xff28ff28
+scaler.SCALE_FILTER_H_BLOCK1[66].val = 0xff28ff21
+scaler.SCALE_FILTER_H_BLOCK0[134].val = 0xff30ff30
+scaler.SCALE_FILTER_H_BLOCK0[135].val = 0xff38ff38
+scaler.SCALE_FILTER_H_BLOCK1[67].val = 0xff38ff30
+scaler.SCALE_FILTER_H_BLOCK0[136].val = 0xff41ff41
+scaler.SCALE_FILTER_H_BLOCK0[137].val = 0xff49ff49
+scaler.SCALE_FILTER_H_BLOCK1[68].val = 0xff49ff41
+scaler.SCALE_FILTER_H_BLOCK0[138].val = 0xff52ff52
+scaler.SCALE_FILTER_H_BLOCK0[139].val = 0xff5bff5b
+scaler.SCALE_FILTER_H_BLOCK1[69].val = 0xff5bff52
+scaler.SCALE_FILTER_H_BLOCK0[140].val = 0xff63ff63
+scaler.SCALE_FILTER_H_BLOCK0[141].val = 0xff6cff6c
+scaler.SCALE_FILTER_H_BLOCK1[70].val = 0xff6cff63
+scaler.SCALE_FILTER_H_BLOCK0[142].val = 0xff75ff75
+scaler.SCALE_FILTER_H_BLOCK0[143].val = 0xff7eff7e
+scaler.SCALE_FILTER_H_BLOCK1[71].val = 0xff7eff75
+scaler.SCALE_FILTER_H_BLOCK0[144].val = 0xff02ff02
+scaler.SCALE_FILTER_H_BLOCK0[145].val = 0xfefcfefc
+scaler.SCALE_FILTER_H_BLOCK1[72].val = 0xfefcff02
+scaler.SCALE_FILTER_H_BLOCK0[146].val = 0xfef7fef7
+scaler.SCALE_FILTER_H_BLOCK0[147].val = 0xfef3fef3
+scaler.SCALE_FILTER_H_BLOCK1[73].val = 0xfef3fef7
+scaler.SCALE_FILTER_H_BLOCK0[148].val = 0xfeeffeef
+scaler.SCALE_FILTER_H_BLOCK0[149].val = 0xfeedfeed
+scaler.SCALE_FILTER_H_BLOCK1[74].val = 0xfeedfeef
+scaler.SCALE_FILTER_H_BLOCK0[150].val = 0xfeecfeec
+scaler.SCALE_FILTER_H_BLOCK0[151].val = 0xfeebfeeb
+scaler.SCALE_FILTER_H_BLOCK1[75].val = 0xfeebfeec
+scaler.SCALE_FILTER_H_BLOCK0[152].val = 0xfeebfeeb
+scaler.SCALE_FILTER_H_BLOCK0[153].val = 0xfeecfeec
+scaler.SCALE_FILTER_H_BLOCK1[76].val = 0xfeecfeeb
+scaler.SCALE_FILTER_H_BLOCK0[154].val = 0xfeeefeee
+scaler.SCALE_FILTER_H_BLOCK0[155].val = 0xfef1fef1
+scaler.SCALE_FILTER_H_BLOCK1[77].val = 0xfef1feee
+scaler.SCALE_FILTER_H_BLOCK0[156].val = 0xfef4fef4
+scaler.SCALE_FILTER_H_BLOCK0[157].val = 0xfef8fef8
+scaler.SCALE_FILTER_H_BLOCK1[78].val = 0xfef8fef4
+scaler.SCALE_FILTER_H_BLOCK0[158].val = 0xfefcfefc
+scaler.SCALE_FILTER_H_BLOCK0[159].val = 0xff01ff01
+scaler.SCALE_FILTER_H_BLOCK1[79].val = 0xff01fefc
+scaler.SCALE_FILTER_H_BLOCK0[160].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[161].val = 0xffe7ffe7
+scaler.SCALE_FILTER_H_BLOCK1[80].val = 0xffe70000
+scaler.SCALE_FILTER_H_BLOCK0[162].val = 0xffcfffcf
+scaler.SCALE_FILTER_H_BLOCK0[163].val = 0xffb9ffb9
+scaler.SCALE_FILTER_H_BLOCK1[81].val = 0xffb9ffcf
+scaler.SCALE_FILTER_H_BLOCK0[164].val = 0xffa4ffa4
+scaler.SCALE_FILTER_H_BLOCK0[165].val = 0xff90ff90
+scaler.SCALE_FILTER_H_BLOCK1[82].val = 0xff90ffa4
+scaler.SCALE_FILTER_H_BLOCK0[166].val = 0xff7dff7d
+scaler.SCALE_FILTER_H_BLOCK0[167].val = 0xff6bff6b
+scaler.SCALE_FILTER_H_BLOCK1[83].val = 0xff6bff7d
+scaler.SCALE_FILTER_H_BLOCK0[168].val = 0xff5bff5b
+scaler.SCALE_FILTER_H_BLOCK0[169].val = 0xff4cff4c
+scaler.SCALE_FILTER_H_BLOCK1[84].val = 0xff4cff5b
+scaler.SCALE_FILTER_H_BLOCK0[170].val = 0xff3eff3e
+scaler.SCALE_FILTER_H_BLOCK0[171].val = 0xff31ff31
+scaler.SCALE_FILTER_H_BLOCK1[85].val = 0xff31ff3e
+scaler.SCALE_FILTER_H_BLOCK0[172].val = 0xff26ff26
+scaler.SCALE_FILTER_H_BLOCK0[173].val = 0xff1bff1b
+scaler.SCALE_FILTER_H_BLOCK1[86].val = 0xff1bff26
+scaler.SCALE_FILTER_H_BLOCK0[174].val = 0xff12ff12
+scaler.SCALE_FILTER_H_BLOCK0[175].val = 0xff0aff0a
+scaler.SCALE_FILTER_H_BLOCK1[87].val = 0xff0aff12
+scaler.SCALE_FILTER_H_BLOCK0[176].val = 0x2210221
+scaler.SCALE_FILTER_H_BLOCK0[177].val = 0x1f901f9
+scaler.SCALE_FILTER_H_BLOCK1[88].val = 0x1f90221
+scaler.SCALE_FILTER_H_BLOCK0[178].val = 0x1d001d0
+scaler.SCALE_FILTER_H_BLOCK0[179].val = 0x1a901a9
+scaler.SCALE_FILTER_H_BLOCK1[89].val = 0x1a901d0
+scaler.SCALE_FILTER_H_BLOCK0[180].val = 0x1820182
+scaler.SCALE_FILTER_H_BLOCK0[181].val = 0x15d015d
+scaler.SCALE_FILTER_H_BLOCK1[90].val = 0x15d0182
+scaler.SCALE_FILTER_H_BLOCK0[182].val = 0x1380138
+scaler.SCALE_FILTER_H_BLOCK0[183].val = 0x1140114
+scaler.SCALE_FILTER_H_BLOCK1[91].val = 0x1140138
+scaler.SCALE_FILTER_H_BLOCK0[184].val = 0xf100f1
+scaler.SCALE_FILTER_H_BLOCK0[185].val = 0xcf00cf
+scaler.SCALE_FILTER_H_BLOCK1[92].val = 0xcf00f1
+scaler.SCALE_FILTER_H_BLOCK0[186].val = 0xae00ae
+scaler.SCALE_FILTER_H_BLOCK0[187].val = 0x8e008e
+scaler.SCALE_FILTER_H_BLOCK1[93].val = 0x8e00ae
+scaler.SCALE_FILTER_H_BLOCK0[188].val = 0x6f006f
+scaler.SCALE_FILTER_H_BLOCK0[189].val = 0x520052
+scaler.SCALE_FILTER_H_BLOCK1[94].val = 0x52006f
+scaler.SCALE_FILTER_H_BLOCK0[190].val = 0x350035
+scaler.SCALE_FILTER_H_BLOCK0[191].val = 0x1a001a
+scaler.SCALE_FILTER_H_BLOCK1[95].val = 0x1a0035
+scaler.SCALE_FILTER_H_BLOCK0[192].val = 0x4e404e4
+scaler.SCALE_FILTER_H_BLOCK0[193].val = 0x4b804b8
+scaler.SCALE_FILTER_H_BLOCK1[96].val = 0x4b804e4
+scaler.SCALE_FILTER_H_BLOCK0[194].val = 0x48b048b
+scaler.SCALE_FILTER_H_BLOCK0[195].val = 0x45f045f
+scaler.SCALE_FILTER_H_BLOCK1[97].val = 0x45f048b
+scaler.SCALE_FILTER_H_BLOCK0[196].val = 0x4320432
+scaler.SCALE_FILTER_H_BLOCK0[197].val = 0x4050405
+scaler.SCALE_FILTER_H_BLOCK1[98].val = 0x4050432
+scaler.SCALE_FILTER_H_BLOCK0[198].val = 0x3d803d8
+scaler.SCALE_FILTER_H_BLOCK0[199].val = 0x3ab03ab
+scaler.SCALE_FILTER_H_BLOCK1[99].val = 0x3ab03d8
+scaler.SCALE_FILTER_H_BLOCK0[200].val = 0x37e037e
+scaler.SCALE_FILTER_H_BLOCK0[201].val = 0x3510351
+scaler.SCALE_FILTER_H_BLOCK1[100].val = 0x351037e
+scaler.SCALE_FILTER_H_BLOCK0[202].val = 0x3240324
+scaler.SCALE_FILTER_H_BLOCK0[203].val = 0x2f802f8
+scaler.SCALE_FILTER_H_BLOCK1[101].val = 0x2f80324
+scaler.SCALE_FILTER_H_BLOCK0[204].val = 0x2cc02cc
+scaler.SCALE_FILTER_H_BLOCK0[205].val = 0x2a102a1
+scaler.SCALE_FILTER_H_BLOCK1[102].val = 0x2a102cc
+scaler.SCALE_FILTER_H_BLOCK0[206].val = 0x2760276
+scaler.SCALE_FILTER_H_BLOCK0[207].val = 0x24b024b
+scaler.SCALE_FILTER_H_BLOCK1[103].val = 0x24b0276
+scaler.SCALE_FILTER_H_BLOCK0[208].val = 0x73b073b
+scaler.SCALE_FILTER_H_BLOCK0[209].val = 0x71e071e
+scaler.SCALE_FILTER_H_BLOCK1[104].val = 0x71e073b
+scaler.SCALE_FILTER_H_BLOCK0[210].val = 0x7000700
+scaler.SCALE_FILTER_H_BLOCK0[211].val = 0x6e106e1
+scaler.SCALE_FILTER_H_BLOCK1[105].val = 0x6e10700
+scaler.SCALE_FILTER_H_BLOCK0[212].val = 0x6c006c0
+scaler.SCALE_FILTER_H_BLOCK0[213].val = 0x69e069e
+scaler.SCALE_FILTER_H_BLOCK1[106].val = 0x69e06c0
+scaler.SCALE_FILTER_H_BLOCK0[214].val = 0x67a067a
+scaler.SCALE_FILTER_H_BLOCK0[215].val = 0x6560656
+scaler.SCALE_FILTER_H_BLOCK1[107].val = 0x656067a
+scaler.SCALE_FILTER_H_BLOCK0[216].val = 0x6300630
+scaler.SCALE_FILTER_H_BLOCK0[217].val = 0x6090609
+scaler.SCALE_FILTER_H_BLOCK1[108].val = 0x6090630
+scaler.SCALE_FILTER_H_BLOCK0[218].val = 0x5e205e2
+scaler.SCALE_FILTER_H_BLOCK0[219].val = 0x5b905b9
+scaler.SCALE_FILTER_H_BLOCK1[109].val = 0x5b905e2
+scaler.SCALE_FILTER_H_BLOCK0[220].val = 0x5900590
+scaler.SCALE_FILTER_H_BLOCK0[221].val = 0x5660566
+scaler.SCALE_FILTER_H_BLOCK1[110].val = 0x5660590
+scaler.SCALE_FILTER_H_BLOCK0[222].val = 0x53b053b
+scaler.SCALE_FILTER_H_BLOCK0[223].val = 0x5100510
+scaler.SCALE_FILTER_H_BLOCK1[111].val = 0x510053b
+scaler.SCALE_FILTER_H_BLOCK0[224].val = 0x82c082c
+scaler.SCALE_FILTER_H_BLOCK0[225].val = 0x82b082b
+scaler.SCALE_FILTER_H_BLOCK1[112].val = 0x82b082c
+scaler.SCALE_FILTER_H_BLOCK0[226].val = 0x8280828
+scaler.SCALE_FILTER_H_BLOCK0[227].val = 0x8200820
+scaler.SCALE_FILTER_H_BLOCK1[113].val = 0x8200828
+scaler.SCALE_FILTER_H_BLOCK0[228].val = 0x81b081b
+scaler.SCALE_FILTER_H_BLOCK0[229].val = 0x8130813
+scaler.SCALE_FILTER_H_BLOCK1[114].val = 0x813081b
+scaler.SCALE_FILTER_H_BLOCK0[230].val = 0x8080808
+scaler.SCALE_FILTER_H_BLOCK0[231].val = 0x7fc07fc
+scaler.SCALE_FILTER_H_BLOCK1[115].val = 0x7fc0808
+scaler.SCALE_FILTER_H_BLOCK0[232].val = 0x7ed07ed
+scaler.SCALE_FILTER_H_BLOCK0[233].val = 0x7dd07dd
+scaler.SCALE_FILTER_H_BLOCK1[116].val = 0x7dd07ed
+scaler.SCALE_FILTER_H_BLOCK0[234].val = 0x7cb07cb
+scaler.SCALE_FILTER_H_BLOCK0[235].val = 0x7b607b6
+scaler.SCALE_FILTER_H_BLOCK1[117].val = 0x7b607cb
+scaler.SCALE_FILTER_H_BLOCK0[236].val = 0x7a207a2
+scaler.SCALE_FILTER_H_BLOCK0[237].val = 0x78a078a
+scaler.SCALE_FILTER_H_BLOCK1[118].val = 0x78a07a2
+scaler.SCALE_FILTER_H_BLOCK0[238].val = 0x7710771
+scaler.SCALE_FILTER_H_BLOCK0[239].val = 0x7570757
+scaler.SCALE_FILTER_H_BLOCK1[119].val = 0x7570771
+scaler.SCALE_FILTER_H_BLOCK0[240].val = 0x73d073d
+scaler.SCALE_FILTER_H_BLOCK0[241].val = 0x7570757
+scaler.SCALE_FILTER_H_BLOCK1[120].val = 0x757073d
+scaler.SCALE_FILTER_H_BLOCK0[242].val = 0x7710771
+scaler.SCALE_FILTER_H_BLOCK0[243].val = 0x78a078a
+scaler.SCALE_FILTER_H_BLOCK1[121].val = 0x78a0771
+scaler.SCALE_FILTER_H_BLOCK0[244].val = 0x7a207a2
+scaler.SCALE_FILTER_H_BLOCK0[245].val = 0x7b607b6
+scaler.SCALE_FILTER_H_BLOCK1[122].val = 0x7b607a2
+scaler.SCALE_FILTER_H_BLOCK0[246].val = 0x7cb07cb
+scaler.SCALE_FILTER_H_BLOCK0[247].val = 0x7dd07dd
+scaler.SCALE_FILTER_H_BLOCK1[123].val = 0x7dd07cb
+scaler.SCALE_FILTER_H_BLOCK0[248].val = 0x7ed07ed
+scaler.SCALE_FILTER_H_BLOCK0[249].val = 0x7fc07fc
+scaler.SCALE_FILTER_H_BLOCK1[124].val = 0x7fc07ed
+scaler.SCALE_FILTER_H_BLOCK0[250].val = 0x8080808
+scaler.SCALE_FILTER_H_BLOCK0[251].val = 0x8130813
+scaler.SCALE_FILTER_H_BLOCK1[125].val = 0x8130808
+scaler.SCALE_FILTER_H_BLOCK0[252].val = 0x81b081b
+scaler.SCALE_FILTER_H_BLOCK0[253].val = 0x8200820
+scaler.SCALE_FILTER_H_BLOCK1[126].val = 0x820081b
+scaler.SCALE_FILTER_H_BLOCK0[254].val = 0x8280828
+scaler.SCALE_FILTER_H_BLOCK0[255].val = 0x82b082b
+scaler.SCALE_FILTER_H_BLOCK1[127].val = 0x82b0828
+scaler.SCALE_FILTER_H_BLOCK0[256].val = 0x4e404e4
+scaler.SCALE_FILTER_H_BLOCK0[257].val = 0x5100510
+scaler.SCALE_FILTER_H_BLOCK1[128].val = 0x51004e4
+scaler.SCALE_FILTER_H_BLOCK0[258].val = 0x53b053b
+scaler.SCALE_FILTER_H_BLOCK0[259].val = 0x5660566
+scaler.SCALE_FILTER_H_BLOCK1[129].val = 0x566053b
+scaler.SCALE_FILTER_H_BLOCK0[260].val = 0x5900590
+scaler.SCALE_FILTER_H_BLOCK0[261].val = 0x5b905b9
+scaler.SCALE_FILTER_H_BLOCK1[130].val = 0x5b90590
+scaler.SCALE_FILTER_H_BLOCK0[262].val = 0x5e205e2
+scaler.SCALE_FILTER_H_BLOCK0[263].val = 0x6090609
+scaler.SCALE_FILTER_H_BLOCK1[131].val = 0x60905e2
+scaler.SCALE_FILTER_H_BLOCK0[264].val = 0x6300630
+scaler.SCALE_FILTER_H_BLOCK0[265].val = 0x6560656
+scaler.SCALE_FILTER_H_BLOCK1[132].val = 0x6560630
+scaler.SCALE_FILTER_H_BLOCK0[266].val = 0x67a067a
+scaler.SCALE_FILTER_H_BLOCK0[267].val = 0x69e069e
+scaler.SCALE_FILTER_H_BLOCK1[133].val = 0x69e067a
+scaler.SCALE_FILTER_H_BLOCK0[268].val = 0x6c006c0
+scaler.SCALE_FILTER_H_BLOCK0[269].val = 0x6e106e1
+scaler.SCALE_FILTER_H_BLOCK1[134].val = 0x6e106c0
+scaler.SCALE_FILTER_H_BLOCK0[270].val = 0x7000700
+scaler.SCALE_FILTER_H_BLOCK0[271].val = 0x71e071e
+scaler.SCALE_FILTER_H_BLOCK1[135].val = 0x71e0700
+scaler.SCALE_FILTER_H_BLOCK0[272].val = 0x2210221
+scaler.SCALE_FILTER_H_BLOCK0[273].val = 0x24b024b
+scaler.SCALE_FILTER_H_BLOCK1[136].val = 0x24b0221
+scaler.SCALE_FILTER_H_BLOCK0[274].val = 0x2760276
+scaler.SCALE_FILTER_H_BLOCK0[275].val = 0x2a102a1
+scaler.SCALE_FILTER_H_BLOCK1[137].val = 0x2a10276
+scaler.SCALE_FILTER_H_BLOCK0[276].val = 0x2cc02cc
+scaler.SCALE_FILTER_H_BLOCK0[277].val = 0x2f802f8
+scaler.SCALE_FILTER_H_BLOCK1[138].val = 0x2f802cc
+scaler.SCALE_FILTER_H_BLOCK0[278].val = 0x3240324
+scaler.SCALE_FILTER_H_BLOCK0[279].val = 0x3510351
+scaler.SCALE_FILTER_H_BLOCK1[139].val = 0x3510324
+scaler.SCALE_FILTER_H_BLOCK0[280].val = 0x37e037e
+scaler.SCALE_FILTER_H_BLOCK0[281].val = 0x3ab03ab
+scaler.SCALE_FILTER_H_BLOCK1[140].val = 0x3ab037e
+scaler.SCALE_FILTER_H_BLOCK0[282].val = 0x3d803d8
+scaler.SCALE_FILTER_H_BLOCK0[283].val = 0x4050405
+scaler.SCALE_FILTER_H_BLOCK1[141].val = 0x40503d8
+scaler.SCALE_FILTER_H_BLOCK0[284].val = 0x4320432
+scaler.SCALE_FILTER_H_BLOCK0[285].val = 0x45f045f
+scaler.SCALE_FILTER_H_BLOCK1[142].val = 0x45f0432
+scaler.SCALE_FILTER_H_BLOCK0[286].val = 0x48b048b
+scaler.SCALE_FILTER_H_BLOCK0[287].val = 0x4b804b8
+scaler.SCALE_FILTER_H_BLOCK1[143].val = 0x4b8048b
+scaler.SCALE_FILTER_H_BLOCK0[288].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[289].val = 0x1a001a
+scaler.SCALE_FILTER_H_BLOCK1[144].val = 0x1a0000
+scaler.SCALE_FILTER_H_BLOCK0[290].val = 0x350035
+scaler.SCALE_FILTER_H_BLOCK0[291].val = 0x520052
+scaler.SCALE_FILTER_H_BLOCK1[145].val = 0x520035
+scaler.SCALE_FILTER_H_BLOCK0[292].val = 0x6f006f
+scaler.SCALE_FILTER_H_BLOCK0[293].val = 0x8e008e
+scaler.SCALE_FILTER_H_BLOCK1[146].val = 0x8e006f
+scaler.SCALE_FILTER_H_BLOCK0[294].val = 0xae00ae
+scaler.SCALE_FILTER_H_BLOCK0[295].val = 0xcf00cf
+scaler.SCALE_FILTER_H_BLOCK1[147].val = 0xcf00ae
+scaler.SCALE_FILTER_H_BLOCK0[296].val = 0xf100f1
+scaler.SCALE_FILTER_H_BLOCK0[297].val = 0x1140114
+scaler.SCALE_FILTER_H_BLOCK1[148].val = 0x11400f1
+scaler.SCALE_FILTER_H_BLOCK0[298].val = 0x1380138
+scaler.SCALE_FILTER_H_BLOCK0[299].val = 0x15d015d
+scaler.SCALE_FILTER_H_BLOCK1[149].val = 0x15d0138
+scaler.SCALE_FILTER_H_BLOCK0[300].val = 0x1820182
+scaler.SCALE_FILTER_H_BLOCK0[301].val = 0x1a901a9
+scaler.SCALE_FILTER_H_BLOCK1[150].val = 0x1a90182
+scaler.SCALE_FILTER_H_BLOCK0[302].val = 0x1d001d0
+scaler.SCALE_FILTER_H_BLOCK0[303].val = 0x1f901f9
+scaler.SCALE_FILTER_H_BLOCK1[151].val = 0x1f901d0
+scaler.SCALE_FILTER_H_BLOCK0[304].val = 0xff02ff02
+scaler.SCALE_FILTER_H_BLOCK0[305].val = 0xff0aff0a
+scaler.SCALE_FILTER_H_BLOCK1[152].val = 0xff0aff02
+scaler.SCALE_FILTER_H_BLOCK0[306].val = 0xff12ff12
+scaler.SCALE_FILTER_H_BLOCK0[307].val = 0xff1bff1b
+scaler.SCALE_FILTER_H_BLOCK1[153].val = 0xff1bff12
+scaler.SCALE_FILTER_H_BLOCK0[308].val = 0xff26ff26
+scaler.SCALE_FILTER_H_BLOCK0[309].val = 0xff31ff31
+scaler.SCALE_FILTER_H_BLOCK1[154].val = 0xff31ff26
+scaler.SCALE_FILTER_H_BLOCK0[310].val = 0xff3eff3e
+scaler.SCALE_FILTER_H_BLOCK0[311].val = 0xff4cff4c
+scaler.SCALE_FILTER_H_BLOCK1[155].val = 0xff4cff3e
+scaler.SCALE_FILTER_H_BLOCK0[312].val = 0xff5bff5b
+scaler.SCALE_FILTER_H_BLOCK0[313].val = 0xff6bff6b
+scaler.SCALE_FILTER_H_BLOCK1[156].val = 0xff6bff5b
+scaler.SCALE_FILTER_H_BLOCK0[314].val = 0xff7dff7d
+scaler.SCALE_FILTER_H_BLOCK0[315].val = 0xff90ff90
+scaler.SCALE_FILTER_H_BLOCK1[157].val = 0xff90ff7d
+scaler.SCALE_FILTER_H_BLOCK0[316].val = 0xffa4ffa4
+scaler.SCALE_FILTER_H_BLOCK0[317].val = 0xffb9ffb9
+scaler.SCALE_FILTER_H_BLOCK1[158].val = 0xffb9ffa4
+scaler.SCALE_FILTER_H_BLOCK0[318].val = 0xffcfffcf
+scaler.SCALE_FILTER_H_BLOCK0[319].val = 0xffe7ffe7
+scaler.SCALE_FILTER_H_BLOCK1[159].val = 0xffe7ffcf
+scaler.SCALE_FILTER_H_BLOCK0[320].val = 0xff06ff06
+scaler.SCALE_FILTER_H_BLOCK0[321].val = 0xff01ff01
+scaler.SCALE_FILTER_H_BLOCK1[160].val = 0xff01ff06
+scaler.SCALE_FILTER_H_BLOCK0[322].val = 0xfefcfefc
+scaler.SCALE_FILTER_H_BLOCK0[323].val = 0xfef8fef8
+scaler.SCALE_FILTER_H_BLOCK1[161].val = 0xfef8fefc
+scaler.SCALE_FILTER_H_BLOCK0[324].val = 0xfef4fef4
+scaler.SCALE_FILTER_H_BLOCK0[325].val = 0xfef1fef1
+scaler.SCALE_FILTER_H_BLOCK1[162].val = 0xfef1fef4
+scaler.SCALE_FILTER_H_BLOCK0[326].val = 0xfeeefeee
+scaler.SCALE_FILTER_H_BLOCK0[327].val = 0xfeecfeec
+scaler.SCALE_FILTER_H_BLOCK1[163].val = 0xfeecfeee
+scaler.SCALE_FILTER_H_BLOCK0[328].val = 0xfeebfeeb
+scaler.SCALE_FILTER_H_BLOCK0[329].val = 0xfeebfeeb
+scaler.SCALE_FILTER_H_BLOCK1[164].val = 0xfeebfeeb
+scaler.SCALE_FILTER_H_BLOCK0[330].val = 0xfeecfeec
+scaler.SCALE_FILTER_H_BLOCK0[331].val = 0xfeedfeed
+scaler.SCALE_FILTER_H_BLOCK1[165].val = 0xfeedfeec
+scaler.SCALE_FILTER_H_BLOCK0[332].val = 0xfeeffeef
+scaler.SCALE_FILTER_H_BLOCK0[333].val = 0xfef3fef3
+scaler.SCALE_FILTER_H_BLOCK1[166].val = 0xfef3feef
+scaler.SCALE_FILTER_H_BLOCK0[334].val = 0xfef7fef7
+scaler.SCALE_FILTER_H_BLOCK0[335].val = 0xfefcfefc
+scaler.SCALE_FILTER_H_BLOCK1[167].val = 0xfefcfef7
+scaler.SCALE_FILTER_H_BLOCK0[336].val = 0xff87ff87
+scaler.SCALE_FILTER_H_BLOCK0[337].val = 0xff7eff7e
+scaler.SCALE_FILTER_H_BLOCK1[168].val = 0xff7eff87
+scaler.SCALE_FILTER_H_BLOCK0[338].val = 0xff75ff75
+scaler.SCALE_FILTER_H_BLOCK0[339].val = 0xff6cff6c
+scaler.SCALE_FILTER_H_BLOCK1[169].val = 0xff6cff75
+scaler.SCALE_FILTER_H_BLOCK0[340].val = 0xff63ff63
+scaler.SCALE_FILTER_H_BLOCK0[341].val = 0xff5bff5b
+scaler.SCALE_FILTER_H_BLOCK1[170].val = 0xff5bff63
+scaler.SCALE_FILTER_H_BLOCK0[342].val = 0xff52ff52
+scaler.SCALE_FILTER_H_BLOCK0[343].val = 0xff49ff49
+scaler.SCALE_FILTER_H_BLOCK1[171].val = 0xff49ff52
+scaler.SCALE_FILTER_H_BLOCK0[344].val = 0xff41ff41
+scaler.SCALE_FILTER_H_BLOCK0[345].val = 0xff38ff38
+scaler.SCALE_FILTER_H_BLOCK1[172].val = 0xff38ff41
+scaler.SCALE_FILTER_H_BLOCK0[346].val = 0xff30ff30
+scaler.SCALE_FILTER_H_BLOCK0[347].val = 0xff28ff28
+scaler.SCALE_FILTER_H_BLOCK1[173].val = 0xff28ff30
+scaler.SCALE_FILTER_H_BLOCK0[348].val = 0xff21ff21
+scaler.SCALE_FILTER_H_BLOCK0[349].val = 0xff1aff1a
+scaler.SCALE_FILTER_H_BLOCK1[174].val = 0xff1aff21
+scaler.SCALE_FILTER_H_BLOCK0[350].val = 0xff13ff13
+scaler.SCALE_FILTER_H_BLOCK0[351].val = 0xff0cff0c
+scaler.SCALE_FILTER_H_BLOCK1[175].val = 0xff0cff13
+scaler.SCALE_FILTER_H_BLOCK0[352].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[353].val = 0xfffafffa
+scaler.SCALE_FILTER_H_BLOCK1[176].val = 0xfffa0000
+scaler.SCALE_FILTER_H_BLOCK0[354].val = 0xfff4fff4
+scaler.SCALE_FILTER_H_BLOCK0[355].val = 0xffeeffee
+scaler.SCALE_FILTER_H_BLOCK1[177].val = 0xffeefff4
+scaler.SCALE_FILTER_H_BLOCK0[356].val = 0xffe7ffe7
+scaler.SCALE_FILTER_H_BLOCK0[357].val = 0xffe0ffe0
+scaler.SCALE_FILTER_H_BLOCK1[178].val = 0xffe0ffe7
+scaler.SCALE_FILTER_H_BLOCK0[358].val = 0xffd9ffd9
+scaler.SCALE_FILTER_H_BLOCK0[359].val = 0xffd2ffd2
+scaler.SCALE_FILTER_H_BLOCK1[179].val = 0xffd2ffd9
+scaler.SCALE_FILTER_H_BLOCK0[360].val = 0xffcaffca
+scaler.SCALE_FILTER_H_BLOCK0[361].val = 0xffc2ffc2
+scaler.SCALE_FILTER_H_BLOCK1[180].val = 0xffc2ffca
+scaler.SCALE_FILTER_H_BLOCK0[362].val = 0xffbaffba
+scaler.SCALE_FILTER_H_BLOCK0[363].val = 0xffb2ffb2
+scaler.SCALE_FILTER_H_BLOCK1[181].val = 0xffb2ffba
+scaler.SCALE_FILTER_H_BLOCK0[364].val = 0xffaaffaa
+scaler.SCALE_FILTER_H_BLOCK0[365].val = 0xffa1ffa1
+scaler.SCALE_FILTER_H_BLOCK1[182].val = 0xffa1ffaa
+scaler.SCALE_FILTER_H_BLOCK0[366].val = 0xff99ff99
+scaler.SCALE_FILTER_H_BLOCK0[367].val = 0xff90ff90
+scaler.SCALE_FILTER_H_BLOCK1[183].val = 0xff90ff99
+scaler.SCALE_FILTER_H_BLOCK0[368].val = 0x340034
+scaler.SCALE_FILTER_H_BLOCK0[369].val = 0x330033
+scaler.SCALE_FILTER_H_BLOCK1[184].val = 0x330034
+scaler.SCALE_FILTER_H_BLOCK0[370].val = 0x320032
+scaler.SCALE_FILTER_H_BLOCK0[371].val = 0x300030
+scaler.SCALE_FILTER_H_BLOCK1[185].val = 0x300032
+scaler.SCALE_FILTER_H_BLOCK0[372].val = 0x2e002e
+scaler.SCALE_FILTER_H_BLOCK0[373].val = 0x2c002c
+scaler.SCALE_FILTER_H_BLOCK1[186].val = 0x2c002e
+scaler.SCALE_FILTER_H_BLOCK0[374].val = 0x290029
+scaler.SCALE_FILTER_H_BLOCK0[375].val = 0x260026
+scaler.SCALE_FILTER_H_BLOCK1[187].val = 0x260029
+scaler.SCALE_FILTER_H_BLOCK0[376].val = 0x230023
+scaler.SCALE_FILTER_H_BLOCK0[377].val = 0x200020
+scaler.SCALE_FILTER_H_BLOCK1[188].val = 0x200023
+scaler.SCALE_FILTER_H_BLOCK0[378].val = 0x1c001c
+scaler.SCALE_FILTER_H_BLOCK0[379].val = 0x180018
+scaler.SCALE_FILTER_H_BLOCK1[189].val = 0x18001c
+scaler.SCALE_FILTER_H_BLOCK0[380].val = 0x140014
+scaler.SCALE_FILTER_H_BLOCK0[381].val = 0x100010
+scaler.SCALE_FILTER_H_BLOCK1[190].val = 0x100014
+scaler.SCALE_FILTER_H_BLOCK0[382].val = 0xb000b
+scaler.SCALE_FILTER_H_BLOCK0[383].val = 0x50005
+scaler.SCALE_FILTER_H_BLOCK1[191].val = 0x5000b
+scaler.SCALE_FILTER_H_BLOCK0[384].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[385].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[192].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[386].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[387].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[193].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[388].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[389].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[194].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[390].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[391].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[195].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[392].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[393].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[196].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[394].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[395].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[197].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[396].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[397].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[198].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[398].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[399].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[199].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[400].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[401].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[200].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[402].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[403].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[201].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[404].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[405].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[202].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[406].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[407].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[203].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[408].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[409].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[204].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[410].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[411].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[205].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[412].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[413].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[206].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[414].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[415].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[207].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[416].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[417].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[208].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[418].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[419].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[209].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[420].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[421].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[210].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[422].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[423].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[211].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[424].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[425].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[212].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[426].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[427].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[213].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[428].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[429].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[214].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[430].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[431].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[215].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[432].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[433].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[216].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[434].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[435].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[217].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[436].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[437].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[218].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[438].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[439].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[219].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[440].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[441].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[220].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[442].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[443].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[221].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[444].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[445].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[222].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[446].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[447].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[223].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[448].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[449].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[224].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[450].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[451].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[225].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[452].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[453].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[226].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[454].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[455].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[227].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[456].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[457].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[228].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[458].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[459].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[229].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[460].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[461].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[230].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[462].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[463].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[231].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[464].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[465].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[232].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[466].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[467].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[233].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[468].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[469].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[234].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[470].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[471].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[235].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[472].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[473].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[236].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[474].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[475].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[237].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[476].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[477].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[238].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[478].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK0[479].val = 0x0
+scaler.SCALE_FILTER_H_BLOCK1[239].val = 0x0
+
+# pseudo linear scaling
+scaler.PSEUDO_LINEAR_SCALING = 0
+
+# reshape
+p.write32(scaler_base + 0xe8, 0x0)
+
+# reset CM 3x
+p.write32(scaler_base + 0x3800, 0x0)
+p.write32(scaler_base + 0x3800, 0x0)
+p.write32(scaler_base + 0x3800, 0x0)
+
+# enable prescaler
+p.write32(scaler_base + 0x824, 0xc)
+
+# alpha override
+p.write32(scaler_base + 0x8c, 0xffff)
+
+# dither
+p.write32(scaler_base + 0xa00, 0x0)
+
+# commit convert
+p.write32(scaler_base + 0x13808, 0x0)
+p.write32(scaler_base + 0x1380c, 0x0)
+p.write32(scaler_base + 0x13800, 0x0)
+p.write32(scaler_base + 0x13804, 0x0)
+
+# convert map
+p.write32(scaler_base + 0x13810, 0x8)
+p.write32(scaler_base + 0x13814, 0x8)
+p.write32(scaler_base + 0x13818, 0x8)
+p.write32(scaler_base + 0x1381c, 0x8)
+p.write32(scaler_base + 0x13804, 0x0)
+p.write32(scaler_base + 0x13c04, 0x0)
+p.write32(scaler_base + 0x13c10, 0x8)
+p.write32(scaler_base + 0x13c14, 0x8)
+p.write32(scaler_base + 0x13c18, 0x8)
+p.write32(scaler_base + 0x13c1c, 0x8)
+
+# commit revert
+p.write32(scaler_base + 0x13c00, 0x1)
+
+# (don't) program histogram
+p.write32(scaler_base + 0x3000, 0x0)
+p.write32(scaler_base + 0x124, 0x0)
+
+# tag transform registers
+p.write32(scaler_base + 0x110, 0x1)
+
+# start
+p.write32(scaler_base + 0x98, 0xfffffffe)
+scaler.START = 1
+
+start_time = time.time()
+while scaler.MSR_GLBL_IRQSTS.reg.DONE == 0:
+ if time.time() - start_time > 5:
+ print("TIMED OUT!!!")
+ break
+
+print(f"IRQ status is now {scaler.MSR_GLBL_IRQSTS}")
+print(f"Debug status is now {scaler.MSR_CTRL_DBGSTS}")
+
+out_buf_new = iface.readmem(out_buf_phys, out_SZ)
+chexdump(out_buf_new)
+
+with Image.new(mode='RGBA', size=(out_W, out_H)) as im:
+ for y in range(out_H):
+ for x in range(out_W):
+ block = out_buf_new[
+ y*out_STRIDE + x*out_BYTESPP:
+ y*out_STRIDE + (x+1)*out_BYTESPP]
+
+ r, g, b, a = block
+ im.putpixel((x, y), (r, g, b, a))
+
+ im.save(output_image_fn)
diff --git a/tools/proxyclient/experiments/smc.py b/tools/proxyclient/experiments/smc.py
new file mode 100755
index 0000000..5e4a383
--- /dev/null
+++ b/tools/proxyclient/experiments/smc.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib
+import time
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+import struct
+from m1n1.setup import *
+from m1n1.fw.smc import SMCClient, SMCError
+
+smc_addr = u.adt["arm-io/smc"].get_reg(0)[0]
+smc = SMCClient(u, smc_addr)
+smc.start()
+smc.start_ep(0x20)
+
+smc.verbose = 0
+
+smcep = smc.epmap[0x20]
+
+def gpio_key(pin):
+ assert(pin < (1 << 16))
+
+ fourcc = 'gP' + ('00'+(hex(pin)[2:]))[-2:]
+ return fourcc
+
+## Enable wifi/bluetooth
+#RFKILL_PIN = 13
+#smcep.write(gpio_key(RFKILL_PIN), struct.pack('<I', 0x800000 | 0x0))
+#smcep.write(gpio_key(RFKILL_PIN), struct.pack('<I', 0x800000 | 0x1))
+
+count = smcep.read32b("#KEY")
+print(f"Key count: {count}")
+
+for i in range(count):
+ k = smcep.get_key_by_index(i)
+ length, type, flags = smcep.get_key_info(k)
+ if flags & 0x80:
+ try:
+ val = smcep.read_type(k, length, type)
+ print(f"#{i}: {k} = ({type}, {flags:#x}) {val}")
+ except SMCError as e:
+ print(f"#{i}: {k} = ({type}, {flags:#x}) <error {e}>")
+ else:
+ print(f"#{i}: {k} = ({type}, {flags:#x}) <not available>")
+
+
+smc.stop()
diff --git a/tools/proxyclient/experiments/smc_watcher.py b/tools/proxyclient/experiments/smc_watcher.py
new file mode 100755
index 0000000..9d07339
--- /dev/null
+++ b/tools/proxyclient/experiments/smc_watcher.py
@@ -0,0 +1,115 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib, fnmatch, signal
+import time
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+import struct
+from m1n1.setup import *
+from m1n1.shell import run_shell
+from m1n1.fw.smc import SMCClient, SMCError
+
+smc_addr = u.adt["arm-io/smc"].get_reg(0)[0]
+smc = SMCClient(u, smc_addr)
+smc.start()
+smc.start_ep(0x20)
+
+smc.verbose = 0
+
+smcep = smc.epmap[0x20]
+
+count = smcep.read32b("#KEY")
+print(f"Key count: {count}")
+
+print("Scanning keys...")
+
+pats = sys.argv[1:]
+
+vals = {}
+
+fmts = {
+ "D?CR": "#x",
+ "AC-I": "#x",
+ "D?FC": "#x",
+ "D?VM": lambda v: (v>>8) | ((v&0xff)<<8),
+ "D?VX": lambda v: (v>>8) | ((v&0xff)<<8),
+ "B0RM": lambda v: (v>>8) | ((v&0xff)<<8),
+ ##"BAAC": lambda v: ((v&0xff00)>>8) | ((v&0xff)<<8),
+}
+
+smcep.write8("NTAP", 1)
+
+for i in range(count):
+ k = smcep.get_key_by_index(i)
+ if not any(fnmatch.fnmatchcase(k, i) for i in pats):
+ continue
+ if any(fnmatch.fnmatchcase('-' + k, i) for i in pats):
+ continue
+ length, type, flags = smcep.get_key_info(k)
+ if type in ("ch8*", "{jst"):
+ continue
+ if flags & 0x80:
+ try:
+ val = smcep.read_type(k, length, type)
+ fmt = None
+ for fk, fv in fmts.items():
+ if fnmatch.fnmatchcase(k, fk):
+ fmt = fv
+ if fmt is None:
+ fmt = lambda a: ("%.02f" % a) if isinstance(a, float) else a
+ elif isinstance(fmt, str):
+ def ff(fmt):
+ return lambda a: f"{a:{fmt}}"
+ fmt = ff(fmt)
+ vals[k] = val, length, type, fmt
+ print(f"#{i}: {k} = ({type}, {flags:#x}) {fmt(val)}")
+ except SMCError as e:
+ print(f"#{i}: {k} = ({type}, {flags:#x}) <error {e}>")
+ else:
+ print(f"#{i}: {k} = ({type}, {flags:#x}) <not available>")
+
+slots = {}
+
+def poll():
+ global cnt
+ reprint = cnt % 10 == 0
+ changed = set()
+ for k, (oval, length, type, fmt) in vals.items():
+ val = smcep.read_type(k, length, type)
+ if val != oval:
+ if k not in slots:
+ reprint = True
+ slots[k] = fmt(val)
+ changed.add(k)
+ vals[k] = val, length, type, fmt
+ if reprint:
+ print("\x1b[1;4m", end="")
+ for k, v in slots.items():
+ wd = len(f"{v:>8}")
+ print(f"{k:>{wd}s}", end=" ")
+ print("\x1b[m")
+ for k, v in slots.items():
+ if k in changed:
+ print("\x1b[32m", end="")
+ print(f"{v:>8}\x1b[m", end=" ")
+ print()
+ cnt += 1
+ time.sleep(1)
+
+def handle_sigint(signal=None, stack=None):
+ global doshell
+ doshell = True
+
+signal.signal(signal.SIGINT, handle_sigint)
+
+doshell = False
+try:
+ cnt = 0
+ while True:
+ poll()
+ if doshell:
+ run_shell(globals(), msg="Interrupted")
+ doshell = False
+finally:
+ smc.stop()
+
diff --git a/tools/proxyclient/experiments/speaker_amp.py b/tools/proxyclient/experiments/speaker_amp.py
new file mode 100755
index 0000000..7a592c3
--- /dev/null
+++ b/tools/proxyclient/experiments/speaker_amp.py
@@ -0,0 +1,148 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib
+import time
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+# speaker_amp.py -- play audio through the embedded speaker on Mac mini
+#
+# sample usage with sox:
+#
+# sox INPUT_FILE -t raw -r 48000 -c 1 -e signed-int -b 32 -L - gain -63 | python3 ./speaker_amp.py
+#
+# (expects mono, 24-bit signed samples padded to 32 bits on the msb side)
+
+import argparse
+from m1n1.setup import *
+from m1n1.hw.dart import DART, DARTRegs
+from m1n1.hw.i2c import I2C
+from m1n1.hw.pmgr import PMGR
+from m1n1.hw.nco import NCO
+from m1n1.hw.admac import *
+from m1n1.hw.mca import *
+
+argparser = argparse.ArgumentParser()
+argparser.add_argument("-f", "--file", "--input", "--samples",
+ type=str, default=None,
+ help='input filename to take samples from ' \
+ '(default: standard input)')
+argparser.add_argument("-b", "--bufsize", type=int, default=1024*32,
+ help='size of buffers to keep submitting to DMA')
+args = argparser.parse_args()
+
+inputf = open(args.file, "rb") if args.file is not None \
+ else sys.stdin.buffer
+
+p.pmgr_adt_clocks_enable("/arm-io/i2c1")
+p.pmgr_adt_clocks_enable("/arm-io/admac-sio")
+p.pmgr_adt_clocks_enable("/arm-io/dart-sio")
+p.pmgr_adt_clocks_enable("/arm-io/mca-switch")
+
+# reset AUDIO_P
+PS_AUDIO_P = PMGR(u).regs[0].PS4[5]
+PS_AUDIO_P.set(DEV_DISABLE=1)
+PS_AUDIO_P.set(RESET=1)
+PS_AUDIO_P.set(RESET=0)
+PS_AUDIO_P.set(DEV_DISABLE=0)
+
+i2c1 = I2C(u, "/arm-io/i2c1")
+
+dart_base, _ = u.adt["/arm-io/dart-sio"].get_reg(0) # stream index 2
+dart = DART(iface, DARTRegs(u, dart_base), util=u)
+dart.initialize()
+
+cl_no = 0
+
+admac = ADMAC(u, "/arm-io/admac-sio", dart, debug=True)
+tx_chan = admac.chans[4*cl_no]
+
+tx_chan.disable()
+tx_chan.reset()
+tx_chan.read_reports() # read stale reports
+tx_chan.buswidth = E_BUSWIDTH.W_32BIT
+tx_chan.framesize = E_FRAME.F_1_WORD
+
+nco = NCO(u, "/arm-io/nco")
+nco[cl_no].set_rate(48000 * 256)
+nco[cl_no].enable()
+
+mca_switch1_base = u.adt["/arm-io/mca-switch"].get_reg(1)[0]
+mca_cl_base = u.adt["/arm-io/mca-switch"].get_reg(0)[0] + 0x4000*cl_no
+cl = MCACluster(u, mca_cl_base)
+
+regs, serdes = cl.regs, cl.txa
+
+regs.SYNCGEN_STATUS.set(RST=1, EN=0)
+regs.SYNCGEN_STATUS.set(RST=0)
+regs.SYNCGEN_MCLK_SEL.val =(1 + cl_no)
+regs.SYNCGEN_HI_PERIOD.val = 0
+regs.SYNCGEN_LO_PERIOD.val = 0xfe # full period minus two
+
+serdes.STATUS.set(EN=0)
+serdes.CONF.set(
+ NSLOTS=0,
+ SLOT_WIDTH=E_SLOT_WIDTH.W_32BIT,
+ BCLK_POL=1,
+ UNK1=1, UNK2=1,
+ IDLE_UNDRIVEN=1,
+ SYNC_SEL=(1 + cl_no)
+)
+serdes.BITDELAY.val = 0
+
+serdes.CHANMASK[0].val = 0xffff_fffe
+serdes.CHANMASK[1].val = 0xffff_fffe
+
+regs.PORT_ENABLES.set(CLOCK1=1, CLOCK2=1, DATA=1)
+regs.PORT_CLK_SEL.set(SEL=(cl_no + 1))
+regs.PORT_DATA_SEL.val = cl_no + 1
+regs.MCLK_STATUS.set(EN=1)
+regs.SYNCGEN_STATUS.set(EN=1)
+
+p.write32(mca_switch1_base + 0x8000*cl_no, 0x102048)
+
+# toggle the GPIO line driving the speaker-amp IC reset
+p.write32(0x23c1002d4, 0x76a02) # invoke reset
+p.write32(0x23c1002d4, 0x76a03) # take out of reset
+
+tx_chan.submit(inputf.read(args.bufsize))
+tx_chan.enable()
+while tx_chan.can_submit():
+ tx_chan.submit(inputf.read(args.bufsize))
+
+serdes.STATUS.set(EN=1)
+
+# by ADT and leaked schematic, i2c1 contains TAS5770L,
+# which is not a public part. but there's e.g. TAS2770
+# with similar registers
+#
+# https://www.ti.com/product/TAS2770
+#
+# if the speaker-amp IC loses clock on the serial sample input,
+# it automatically switches to software shutdown.
+#
+
+i2c1.write_reg(0x31, 0x08, [0x40])
+i2c1.write_reg(0x31, 0x0a, [0x06, 0x00, 0x1a])
+i2c1.write_reg(0x31, 0x1b, [0x01, 0x82, 0x06])
+i2c1.write_reg(0x31, 0x16, [0x50, 0x04])
+i2c1.write_reg(0x31, 0x0d, [0x00])
+#i2c1.write_reg(0x31, 0x03, [0x14])
+
+# amplifier gain, presumably this is the lowest setting
+i2c1.write_reg(0x31, 0x03, [0x0])
+
+# take the IC out of software shutdown
+i2c1.write_reg(0x31, 0x02, [0x0c])
+
+while (buf := inputf.read(args.bufsize)):
+ while not tx_chan.can_submit():
+ tx_chan.poll()
+ tx_chan.submit(buf)
+
+# mute
+i2c1.write_reg(0x31, 0x02, [0x0d])
+
+# software shutdown
+i2c1.write_reg(0x31, 0x02, [0x0e])
+
+tx_chan.disable()
diff --git a/tools/proxyclient/experiments/spi.py b/tools/proxyclient/experiments/spi.py
new file mode 100644
index 0000000..82d9528
--- /dev/null
+++ b/tools/proxyclient/experiments/spi.py
@@ -0,0 +1,182 @@
+import sys, pathlib
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+from m1n1.setup import *
+from m1n1 import asm
+from m1n1.shell import run_shell
+from m1n1.gpiola import GPIOLogicAnalyzer
+from m1n1.hw.spi import *
+
+p.smp_start_secondaries()
+
+p.set32(0x28e580208, 1<<31)
+p.clear32(0x28e580208, 1<<31)
+
+spi = u.adt["arm-io/spi3"].get_reg(0)[0]
+regs = SPIRegs(u, spi)
+
+mon.add(spi, 0x10)
+mon.add(spi + 0x30, 0x10)
+mon.add(spi + 0x40, 0x400)
+
+aic = u.adt["arm-io/aic"].get_reg(0)[0]
+mon.add(aic + 0x6800 + (1109 // 32) * 4, 4)
+
+gpio = u.adt["arm-io/gpio0"].get_reg(0)[0]
+
+mon.add(gpio, 0x1c8)
+mon.add(gpio+0x1e0, 0x300)
+
+mon.poll()
+
+m = GPIOLogicAnalyzer(u, "arm-io/gpio0",
+ pins={"miso": 0x34, "mosi": 0x35, "clk": 0x36, "cs": 0x37},
+ #pins={"miso": 0xa, "mosi": 0xb, "clk": 0x20, "cs": 0x21},
+ #pins={"clk": 46, "mosi": 47, "miso": 48, "cs": 49},
+ div=1, on_pin_change=False)
+
+#p.write32(spi + 0x100, 0xffffffff)
+
+regs.CTRL.val = 0xc
+regs.PIN.val = 0x2
+regs.CONFIG.val = 0x20 | (1<<15) | 6
+regs.CONFIG.val = 0x20 | (1<<15) | 4
+regs.CONFIG.val = 0x20 | (1<<15) | 2
+regs.CONFIG.val = 0x20 | (3<<15) | 0
+
+def try_all_bits():
+ for i in range(0, 0x200, 4):
+ v = p.read32(spi + i)
+ for j in range(32):
+ p.write32(spi + i, v ^ (1<<j))
+ print(f"{i:4x}:{v:8x}:{j:2d} FIFO level:", regs.FIFO_LEVEL.reg.LEVEL_TX)
+ mon.poll()
+ p.write32(spi + i, v)
+
+
+m.regs = {
+ "CTRL": (spi + 0x00, R_CTRL),
+ "STATUS": (spi + 0x08, R_STATUS),
+ "RXCNT": (spi + 0x34),
+ "TXCNT": (spi + 0x4c),
+ "FIFO_STAT": (spi + 0x10c, R_FIFO_STAT),
+ "ISTATUS1": (spi + 0x134, R_ISTATUS1),
+ "ISTATUS2": (spi + 0x13c, R_ISTATUS2),
+ "XFSTATUS": (spi + 0x1c0),
+ "SHIFTCONFIG": (spi + 0x150),
+ "PINCONFIG": (spi + 0x154),
+ "PIN": (spi + 0xc),
+ "3c": (spi + 0x3c),
+ "DIVSTATUS": (spi + 0x1e0, R_DIVSTATUS)
+}
+
+m.regs = {}
+
+m.start(300000, bufsize=0x80000)
+
+
+regs.STATUS.val = 0xffffffff
+regs.ISTATUS1.val = 0xffffffff
+regs.ISTATUS2.val = 0xffffffff
+
+regs.CLKDIV.val = 0xfff
+regs.INTER_DLY.val = 0x1000
+
+regs.SHIFTCONFIG.val = 0x20fcf7
+
+regs.PIN.val = 0x2
+print("pinconfig", hex(regs.PINCONFIG.val))
+regs.PINCONFIG.val = 0x100
+#regs.PINCONFIG.val = 0x2-7
+print("pinconfig", hex(regs.PINCONFIG.val))
+print("shiftconfig", hex(regs.SHIFTCONFIG.val))
+
+#regs.PIN.val = 0x0
+#regs.PIN.val = 0x2
+# auto_cs OR pin_cs
+
+#p.write32(spi + 0x150, 0x80c07)
+#p.write32(spi + 0x150, 0x88c07)
+print(hex(p.read32(spi + 0x150)))
+
+#p.write32(spi + 0x160, 0)
+p.write32(spi + 0x160, 0xfff0020)
+p.write32(spi + 0x168, 0xffffb20)
+#p.write32(spi + 0x164, 0x06000210)
+#p.write32(spi + 0x180, 0x02000000)
+#p.write32(spi + 0x18c, 0x500)
+#regs.INTER_DLY2 = 0x20000001
+
+p.write32(spi + 0x200, 0x0010)
+
+p.write32(spi + 0x3c, 0xffffffff)
+
+regs.PINCONFIG.val = 0x002
+regs.PINCONFIG.val = 0x200
+
+
+#p.write32(0x28e0380bc, 0x80100000)
+#p.write32(0x28e0380c4, 0x80100000)
+
+data = b"Asahi Linux"
+
+for i in range(2):
+ for j in data:
+ regs.TXDATA.val = j
+ regs.RXCNT.val = len(data)
+ regs.TXCNT.val = len(data)
+
+ regs.STATUS.val = 0xffffffff
+ regs.ISTATUS1.val = 0xffffffff
+ regs.ISTATUS2.val = 0xffffffff
+
+ regs.PIN.val = 0x0
+ regs.CTRL.val = 0x1
+ #regs.TXDATA.val = 0xff
+ #regs.TXDATA.val = 0xff
+
+ i = 0
+ while regs.TXCNT.val != 0:
+ print(f"{regs.TXCNT.val:#x} {regs.FIFO_STAT.reg} {regs.STATUS.val:#x} {regs.ISTATUS2.val:#x} {p.read32(spi + 0x134):#x}")
+ regs.STATUS.val = 0xffffffff
+ regs.ISTATUS1.val = 0xffffffff
+ regs.ISTATUS2.val = 0xffffffff
+ #regs.CTRL.val = 0x0
+ #time.sleep(0.1)
+ #regs.CTRL.val = 0x1[
+ print(hex(i))
+ #p.write32(spi + i, 0xffffffff)
+ #p.write32(spi + i, 0)
+ i += 4
+ if i > 0x100:
+ break
+ time.sleep(0.001)
+ print(f"{regs.RXCNT.val:#x} {regs.FIFO_STAT.reg} {regs.STATUS.val:#x} {regs.ISTATUS2.val:#x}")
+ regs.STATUS.val = 0xffffffff
+ regs.ISTATUS1.val = 0xffffffff
+ regs.ISTATUS2.val = 0xffffffff
+
+ mon.poll()
+
+ while regs.FIFO_STAT.reg.LEVEL_RX:
+ print("RX", hex(regs.RXDATA.val))
+
+ regs.CTRL.val = 0
+
+m.complete()
+m.show()
+
+def poll(count=1000):
+ lval = None
+ for i in range(count):
+ pins = 0x35, 0x36, 0x37
+ vals = [p.read32(gpio + 4 * pin) & 1 for pin in pins]
+ if vals != lval:
+ print(f"{i:6d}: {vals}")
+ lval = vals
+
+mon.poll()
+
+#run_shell(globals(), msg="Have fun!")
+
+
diff --git a/tools/proxyclient/experiments/sprr_test_permissions.py b/tools/proxyclient/experiments/sprr_test_permissions.py
new file mode 100755
index 0000000..9dff141
--- /dev/null
+++ b/tools/proxyclient/experiments/sprr_test_permissions.py
@@ -0,0 +1,366 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+from contextlib import contextmanager
+
+from m1n1.setup import *
+from m1n1.find_regs import *
+from m1n1 import asm
+
+p.smp_start_secondaries()
+
+class ARMPageTable:
+ PAGESIZE = 0x4000
+
+ def __init__(self, memalign, free):
+ self.memalign = memalign
+ self.free = free
+
+ self.l0 = self.memalign(self.PAGESIZE, self.PAGESIZE)
+ self.l1 = [self.memalign(self.PAGESIZE, self.PAGESIZE), self.memalign(
+ self.PAGESIZE, self.PAGESIZE)]
+ self.l2 = {}
+
+ p.write64(self.l0, self.make_table_pte(self.l1[0]))
+ p.write64(self.l0+8, self.make_table_pte(self.l1[1]))
+
+ def make_table_pte(self, addr):
+ # table mapping, access bit set
+ return addr | 0b11 | (1 << 10)
+
+ def map_page(self, vaddr, paddr, access_bits):
+ ap = (access_bits & 0b1100) >> 2
+ pxn = (access_bits & 0b0010) >> 1
+ uxn = (access_bits & 0b0001)
+
+ # block mapping, access bit set
+ pte = paddr | 0b01 | (1 << 10)
+
+ # move access bits in place
+ pte |= ap << 6
+ pte |= pxn << 54
+ pte |= uxn << 53
+
+ l0_idx = (vaddr >> (25+11+11)) & 1
+ l1_idx = (vaddr >> (25+11)) & 0x7ff
+ l2_idx = (vaddr >> 25) & 0x7ff
+
+ tbl = self.l2.get((l0_idx, l1_idx), None)
+ if not tbl:
+ tbl = self.memalign(self.PAGESIZE, self.PAGESIZE)
+ self.l2[(l0_idx, l1_idx)] = tbl
+ p.write64(self.l1[l0_idx] + 8*l1_idx, self.make_table_pte(tbl))
+
+ p.write64(tbl + 8*l2_idx, pte)
+
+ def map(self, vaddr, paddr, sz, access_bits):
+ assert sz % 0x2000000 == 0
+ assert vaddr % 0x2000000 == 0
+ assert paddr % 0x2000000 == 0
+ assert access_bits <= 0b1111
+
+ while sz > 0:
+ self.map_page(vaddr, paddr, access_bits)
+ sz -= 0x2000000
+ vaddr += 0x2000000
+ paddr += 0x2000000
+
+
+def build_and_write_code(heap, code):
+ page = heap.memalign(0x4000, 0x4000)
+ compiled = asm.ARMAsm(code, page).data
+ iface.writemem(page, compiled)
+ p.dc_cvau(page, len(compiled))
+ p.ic_ivau(page, len(compiled))
+ return page
+
+
+def setup_exception_vectors(heap, gxf=False):
+ if gxf:
+ elr = "S3_6_C15_C10_6"
+ eret = ".long 0x201400"
+ indicator = 0xf2
+ else:
+ elr = "ELR_EL1"
+ eret = "eret"
+ indicator = 0xf0
+
+ return build_and_write_code(heap, """
+ .rept 16
+ b 1f
+ .align 7
+ .endr
+
+ 1:
+ // store that we failed
+ mov x10, 0x{indicator:x}
+
+ // move PC two instruction further and clear 0xf0 0000 0000 to
+ // make sure we end up in the r-x mapping either way and don't
+ // repeat the instruction that just faulted
+ // we skip the second instruction since that one is used to
+ // indicate success
+ ldr x11, =0x0fffffffff
+ mrs x12, {elr}
+ add x12, x12, #8
+ and x12, x12, x11
+ msr {elr}, x12
+
+ isb
+ {eret}
+ """.format(eret=eret, elr=elr, indicator=indicator))
+
+
+print("Setting up..")
+pagetable = ARMPageTable(u.memalign, u.free)
+pagetable.map(0x800000000, 0x800000000, 0xc00000000, 0)
+pagetable.map(0xf800000000, 0x800000000, 0xc00000000, 1)
+
+el2_vectors = setup_exception_vectors(u.heap, gxf=False)
+gl2_vectors = setup_exception_vectors(u.heap, gxf=True)
+
+probe_page = build_and_write_code(u.heap, "mov x10, 0x80\nret\nret\nret\n")
+probe_page_vaddr = probe_page | 0xf000000000
+
+code_page = build_and_write_code(u.heap, """
+ #define SPRR_PERM_EL0 S3_6_C15_C1_5
+ #define SPRR_PERM_EL1 S3_6_C15_C1_6
+ #define SPRR_PERM_EL2 S3_6_C15_C1_7
+
+ #define GXF_CONFIG_EL2 s3_6_c15_c1_2
+ #define GXF_ENTER_EL2 s3_6_c15_c8_1
+ #define GXF_ABORT_EL2 s3_6_c15_c8_2
+
+ #define MPIDR_GL2 S3_6_C15_C10_1
+ #define VBAR_GL2 S3_6_C15_C10_2
+ #define SPSR_GL2 S3_6_C15_C10_3
+ #define ELR_GL2 S3_6_C15_C10_6
+ #define FAR_GL2 S3_6_C15_C10_7
+
+ #define genter .long 0x00201420
+ #define gexit .long 0x201400
+
+ // just store everything since i'm too lazy to think about
+ // register assignments
+ str x30, [sp, #-16]!
+ stp x28, x29, [sp, #-16]!
+ stp x26, x27, [sp, #-16]!
+ stp x24, x25, [sp, #-16]!
+ stp x22, x23, [sp, #-16]!
+ stp x20, x21, [sp, #-16]!
+ stp x18, x19, [sp, #-16]!
+ stp x16, x17, [sp, #-16]!
+ stp x14, x15, [sp, #-16]!
+ stp x12, x13, [sp, #-16]!
+ stp x10, x11, [sp, #-16]!
+ stp x8, x9, [sp, #-16]!
+ stp x6, x7, [sp, #-16]!
+ stp x4, x5, [sp, #-16]!
+ stp x2, x3, [sp, #-16]!
+ stp x0, x1, [sp, #-16]!
+
+ mov x20, x0 // store SPRR value for later
+ mov x21, 0 // clear result
+
+ // setup exception vectors
+ ldr x0, =0x{vectors:x}
+ msr VBAR_EL2, x0
+ isb
+
+ // prepare MMU
+ ldr x0, =0x0400ff
+ msr MAIR_EL1, x0
+ ldr x0, =0x27510b510
+ msr TCR_EL1, x0
+ ldr x0, =0x{ttbr:x}
+ msr TTBR0_EL2, x0
+
+ // enable SPPR
+ mov x0, 1
+ msr s3_6_c15_c1_0, x0
+ msr s3_6_c15_c1_3, xzr
+ isb
+
+ // clear all SPPR registers
+ // (note that reads from/writes to EL1 will be redirected to EL2 anyway)
+ ldr x0, =0
+ msr SPRR_PERM_EL0, x0
+ msr SPRR_PERM_EL1, x0
+ msr SPRR_PERM_EL2, x0
+ msr s3_6_c15_c1_3, x0
+
+ // setup SPPR_EL2
+ msr SPRR_PERM_EL2, x20
+ isb
+ dsb ishst
+ tlbi vmalle1is
+ dsb ish
+ isb
+
+
+ msr s3_6_c15_c1_3, xzr
+ isb
+
+ // enable MMU
+ ldr x1, =0x1005
+ mrs x0, SCTLR_EL1
+ mov x3, x0
+ orr x0, x0, x1
+ msr SCTLR_EL1, x0
+ isb
+
+ // configure and enable GXF
+ mov x0, 1
+ msr GXF_CONFIG_EL2, x0
+ isb
+ ldr x0, =gxf_entry
+ msr GXF_ENTER_EL2, x0
+ ldr x0, =gxf_abort
+ msr GXF_ABORT_EL2, x0
+ isb
+
+ // test GXF access
+ genter
+
+ // test execute access
+ ldr x1, =0x{probe_page:x}
+ mov x10, 0
+ blr x1
+ lsl x21, x21, #8
+ orr x21, x21, x10
+
+ // test read access
+ ldr x1, =0x{probe_page:x}
+ mov x10, 0
+ ldr x1, [x1]
+ mov x10, 0x80
+ lsl x21, x21, #8
+ orr x21, x21, x10
+
+ // test write access
+ ldr x1, =0x{probe_page:x}
+ add x1, x1, 0x20
+ mov x10, 0
+ str x1, [x1]
+ mov x10, 0x80
+ lsl x21, x21, #8
+ orr x21, x21, x10
+
+ // disable MMU again
+ dsb ish
+ isb
+ msr SCTLR_EL1, x3
+ isb
+
+ mov x0, 0
+ msr GXF_CONFIG_EL2, x0
+ msr s3_6_c15_c1_0, x0
+
+ mov x0, x21
+
+ // restore everything except for x0
+ add sp, sp, #8
+ ldr x1, [sp], #8
+ ldp x2, x3, [sp], #16
+ ldp x4, x5, [sp], #16
+ ldp x6, x7, [sp], #16
+ ldp x8, x9, [sp], #16
+ ldp x10, x11, [sp], #16
+ ldp x12, x13, [sp], #16
+ ldp x14, x15, [sp], #16
+ ldp x16, x17, [sp], #16
+ ldp x18, x19, [sp], #16
+ ldp x20, x21, [sp], #16
+ ldp x22, x23, [sp], #16
+ ldp x24, x25, [sp], #16
+ ldp x26, x27, [sp], #16
+ ldp x28, x29, [sp], #16
+ ldr x30, [sp], #16
+
+ ret
+
+
+ gxf_entry:
+ // setup GL exception vectors
+ ldr x0, =0x{gxf_vectors:x}
+ msr VBAR_GL2, x0
+ isb
+
+ // we might double fault -> store state here
+ mrs x14, S3_6_C15_C10_3
+ mrs x15, S3_6_C15_C10_4
+ mrs x16, S3_6_C15_C10_5
+ mrs x17, ELR_GL2
+ mrs x18, FAR_GL2
+
+ // test execute access
+ ldr x1, =0x{probe_page:x}
+ mov x10, 0
+ blr x1
+ lsl x21, x21, #8
+ orr x21, x21, x10
+
+ // test read access
+ ldr x1, =0x{probe_page:x}
+ mov x10, 0
+ ldr x1, [x1]
+ mov x10, 0x80
+ lsl x21, x21, #8
+ orr x21, x21, x10
+
+ // test write access
+ ldr x1, =0x{probe_page:x}
+ add x1, x1, #0x20
+ mov x10, 0
+ str x1, [x1]
+ mov x10, 0x80
+ lsl x21, x21, #8
+ orr x21, x21, x10
+
+ // restore state in case we faulted in here
+ msr S3_6_C15_C10_3, x14
+ msr S3_6_C15_C10_4, x15
+ msr S3_6_C15_C10_5, x16
+ msr ELR_GL2, x17
+ msr FAR_GL2, x18
+
+ isb
+ gexit
+
+ gxf_abort:
+ // store that we failed
+ mov x10, 0xf1
+
+ // move PC two instruction further and clear 0xf0 0000 0000 to
+ // make sure we end up in the r-x mapping either way and don't
+ // repeat the instruction that just faulted
+ // we skip the second instruction since that one is used to
+ // indicate success
+ ldr x11, =0x0fffffffff
+ mrs x12, ELR_GL2
+ add x12, x12, #8
+ and x12, x12, x11
+ msr ELR_GL2, x12
+
+ isb
+ gexit
+ """.format(ttbr=pagetable.l0, vectors=el2_vectors, probe_page=probe_page_vaddr, gxf_vectors=gl2_vectors))
+
+print("Running code now...")
+for i in range(0x10):
+ sprr_val = 0x5 | ((i & 0xf) << 4)
+ ret = p.smp_call_sync(1, code_page, sprr_val)
+
+ glret = ret >> 24
+ glx = 'x' if (glret >> 16) & 0xff == 0x80 else '-'
+ glr = 'r' if (glret >> 8) & 0xff == 0x80 else '-'
+ glw = 'w' if glret & 0xff == 0x80 else '-'
+
+ x = 'x' if (ret >> 16) & 0xff == 0x80 else '-'
+ r = 'r' if (ret >> 8) & 0xff == 0x80 else '-'
+ w = 'w' if ret & 0xff == 0x80 else '-'
+
+ print("SPRR: {0:04b} result: {1:x} GL: {2}{3}{4} EL: {5}{6}{7}".format(
+ i, ret, glr, glw, glx, r, w, x))
diff --git a/tools/proxyclient/experiments/timer_test.py b/tools/proxyclient/experiments/timer_test.py
new file mode 100755
index 0000000..b3422c9
--- /dev/null
+++ b/tools/proxyclient/experiments/timer_test.py
@@ -0,0 +1,109 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+from m1n1.setup import *
+
+HV_VTMR_CTL = (3, 5, 15, 1, 3)
+HV_VTMR_CTL_VMASK = (1 << 0)
+HV_VTMR_CTL_PMASK = (1 << 1)
+
+HV_VTMR_LIST = (3, 5, 15, 1, 2)
+
+TGE = (1<<27)
+
+u.msr(CNTHCTL_EL2, 3 << 10) # EL1PTEN | EL1PCTEN
+
+def run_test(ctl, tval):
+ u.inst(0xd5033fdf) # isb
+
+ u.msr(ctl, 0)
+ u.msr(tval, int(freq * 0.8))
+ u.msr(ctl, 1)
+
+ for i in range(6):
+ p.nop()
+ time.sleep(0.2)
+ #u.inst(0xd5033fdf, call=p.el1_call)
+ print(" . (ISR_EL1=%d) CTL=%x VTMR_LIST=%x" % (u.mrs(ISR_EL1), u.mrs(ctl), u.mrs(HV_VTMR_LIST)))
+
+ u.msr(ctl, 0)
+
+def test_hv_timers():
+ u.msr(DAIF, 0x3c0)
+ print("Testing HV timers...")
+ print(" TGE = 1")
+
+ u.msr(HCR_EL2, u.mrs(HCR_EL2) | TGE | (1 << 3) | (1 << 4))
+
+ print(" P:")
+ run_test(CNTP_CTL_EL0, CNTP_TVAL_EL0)
+ print(" V:")
+ run_test(CNTV_CTL_EL0, CNTV_TVAL_EL0)
+
+def test_guest_timers():
+ u.msr(DAIF, 0)
+ print("Testing guest timers...")
+
+ print(" TGE = 1, vGIC mode=0, timers unmasked")
+ u.msr(HCR_EL2, (u.mrs(HCR_EL2) | TGE) | (1 << 3) | (1 << 4))
+ u.msr(HACR_EL2, 0)
+ u.msr(HV_VTMR_CTL, 3)
+
+ print(" P:")
+ #run_test(CNTP_CTL_EL02, CNTP_TVAL_EL02)
+ print(" V:")
+ #run_test(CNTV_CTL_EL02, CNTV_TVAL_EL02)
+
+ print(" TGE = 1, vGIC mode=0, timers masked")
+ u.msr(HV_VTMR_CTL, 0)
+
+ print(" P:")
+ run_test(CNTP_CTL_EL02, CNTP_TVAL_EL02)
+ print(" V:")
+ run_test(CNTV_CTL_EL02, CNTV_TVAL_EL02)
+
+ print(" TGE = 0, vGIC mode=0, timers unmasked")
+ u.msr(HCR_EL2, (u.mrs(HCR_EL2) & ~TGE) | (1 << 3) | (1 << 4))
+ u.msr(HACR_EL2, 0)
+ u.msr(HV_VTMR_CTL, 3)
+
+ print(" P:")
+ run_test(CNTP_CTL_EL02, CNTP_TVAL_EL02)
+ print(" V:")
+ run_test(CNTV_CTL_EL02, CNTV_TVAL_EL02)
+
+ print(" TGE = 0, vGIC mode=0, timers masked")
+ u.msr(HV_VTMR_CTL, 0)
+
+ print(" P:")
+ run_test(CNTP_CTL_EL02, CNTP_TVAL_EL02)
+ print(" V:")
+ run_test(CNTV_CTL_EL02, CNTV_TVAL_EL02)
+
+ print(" TGE = 0, vGIC mode=1, timers unmasked")
+ u.msr(HCR_EL2, (u.mrs(HCR_EL2) & ~TGE) | (1 << 3) | (1 << 4))
+ u.msr(HACR_EL2, 1<<20)
+ u.msr(HV_VTMR_CTL, 3)
+
+ print(" P:")
+ run_test(CNTP_CTL_EL02, CNTP_TVAL_EL02)
+ print(" V:")
+ run_test(CNTV_CTL_EL02, CNTV_TVAL_EL02)
+
+ print(" TGE = 0, vGIC mode=1, timers masked")
+ u.msr(HV_VTMR_CTL, 0)
+
+ print(" P:")
+ run_test(CNTP_CTL_EL02, CNTP_TVAL_EL02)
+ print(" V:")
+ run_test(CNTV_CTL_EL02, CNTV_TVAL_EL02)
+
+ return
+
+freq = u.mrs(CNTFRQ_EL0)
+print("Timer freq: %d" % freq)
+
+test_hv_timers()
+test_guest_timers()
diff --git a/tools/proxyclient/hv/README.md b/tools/proxyclient/hv/README.md
new file mode 100644
index 0000000..c4fce23
--- /dev/null
+++ b/tools/proxyclient/hv/README.md
@@ -0,0 +1,4 @@
+## m1n1 hypervisor scripts
+
+This directory contains scripts that can be executed to configure the hypervisor using the `-m`
+option to `run_guest.py`.
diff --git a/tools/proxyclient/hv/trace_agx.py b/tools/proxyclient/hv/trace_agx.py
new file mode 100644
index 0000000..024130c
--- /dev/null
+++ b/tools/proxyclient/hv/trace_agx.py
@@ -0,0 +1,107 @@
+# SPDX-License-Identifier: MIT
+import datetime
+
+from m1n1.constructutils import show_struct_trace, Ver
+from m1n1.utils import *
+
+Ver.set_version(hv.u)
+
+trace_device("/arm-io/sgx", False)
+trace_device("/arm-io/pmp", False)
+trace_device("/arm-io/gfx-asc", False)
+
+from m1n1.trace.agx import AGXTracer
+AGXTracer = AGXTracer._reloadcls(True)
+
+agx_tracer = AGXTracer(hv, "/arm-io/gfx-asc", verbose=1)
+
+#agx_tracer.encoder_id_filter = lambda i: (i >> 16) == 0xc0de
+agx_tracer.start()
+
+def resume_tracing(ctx):
+ fname = f"{datetime.datetime.now().isoformat()}.log"
+ hv.set_logfile(open(f"gfxlogs/{fname}", "a"))
+ agx_tracer.resume()
+ return True
+
+def pause_tracing(ctx):
+ agx_tracer.pause()
+ hv.set_logfile(None)
+ return True
+
+hv.add_hvcall(100, resume_tracing)
+hv.add_hvcall(101, pause_tracing)
+
+mode = TraceMode.SYNC
+trace_range(irange(agx_tracer.gpu_region, agx_tracer.gpu_region_size), mode=mode, name="gpu_region")
+trace_range(irange(agx_tracer.gfx_shared_region, agx_tracer.gfx_shared_region_size), mode=mode, name="gfx_shared_region")
+
+## Trace the entire mmio range around the GPU
+node = hv.adt["/arm-io/sgx"]
+addr, size = node.get_reg(0)
+hv.trace_range(irange(addr, 0x1000000), TraceMode.SYNC, name="sgx")
+#hv.trace_range(irange(addr, 0x1000000), TraceMode.OFF, name="sgx")
+hv.trace_range(irange(0x204017030, 8), TraceMode.SYNC, name="faultcode")
+
+trace_device("/arm-io/sgx", True)
+trace_device("/arm-io/gfx-asc", False)
+
+def trace_all_gfx_io():
+ # These are all the IO ranges that get mapped into the UAT iommu pagetable
+ # Trace them so we can see if any of them are being written by the CPU
+
+ # page (8): fa010020000 ... fa010023fff -> 000000020e100000 [8000020e100447]
+ hv.trace_range(irange(0x20e100000, 0x4000), mode=TraceMode.SYNC)
+
+ # page (10): fa010028000 ... fa01002bfff -> 000000028e104000 [c000028e104447]
+ hv.trace_range(irange(0x20e100000, 0x4000), mode=TraceMode.SYNC)
+
+ # page (22): fa010058000 ... fa01005bfff -> 000000028e494000 [8000028e494447]
+ hv.trace_range(irange(0x28e494000, 0x4000), mode=TraceMode.SYNC)
+
+ # page (28): fa010070000 ... fa010073fff -> 0000000204d60000 [c0000204d60447]
+ hv.trace_range(irange(0x204d60000, 0x4000), mode=TraceMode.SYNC)
+
+ # page (30): fa010078000 ... fa01007bfff -> 0000000200000000 [c0000200000447]
+ # to
+ # page (83): fa01014c000 ... fa01014ffff -> 00000002000d4000 [c00002000d4447]
+ hv.trace_range(irange(0x200000000, 0xd5000), mode=TraceMode.SYNC)
+
+ # page (84): fa010150000 ... fa010153fff -> 0000000201000000 [c0000201000447]
+ #page (137): fa010224000 ... fa010227fff -> 00000002010d4000 [c00002010d4447]
+ hv.trace_range(irange(0x201000000, 0xd5000), mode=TraceMode.SYNC)
+
+ # page (138): fa010228000 ... fa01022bfff -> 0000000202000000 [c0000202000447]
+ # page (191): fa0102fc000 ... fa0102fffff -> 00000002020d4000 [c00002020d4447]
+ hv.trace_range(irange(0x202000000, 0xd5000), mode=TraceMode.SYNC)
+
+ # page (192): fa010300000 ... fa010303fff -> 0000000203000000 [c0000203000447]
+ hv.trace_range(irange(0x203000000, 0xd5000), mode=TraceMode.SYNC)
+ hv.trace_range(irange(0x204000000, 0xd5000), mode=TraceMode.SYNC)
+ hv.trace_range(irange(0x205000000, 0xd5000), mode=TraceMode.SYNC)
+ hv.trace_range(irange(0x206000000, 0xd5000), mode=TraceMode.SYNC)
+ hv.trace_range(irange(0x207000000, 0xd5000), mode=TraceMode.SYNC)
+
+ # page (464): fa010740000 ... fa010743fff -> 00000002643c4000 [c00002643c4447]
+ hv.trace_range(irange(0x2643c4000, 0x4000), mode=TraceMode.SYNC)
+ # page (466): fa010748000 ... fa01074bfff -> 000000028e3d0000 [c000028e3d0447]
+ hv.trace_range(irange(0x28e3d0000, 0x4000), mode=TraceMode.SYNC)
+ # page (468): fa010750000 ... fa010753fff -> 000000028e3c0000 [8000028e3c0447]
+ hv.trace_range(irange(0x28e3c0000, 0x4000), mode=TraceMode.SYNC)
+
+ # page (8): f9100020000 ... f9100023fff -> 0000000406000000 [60000406000447]
+ # page (263): f910041c000 ... f910041ffff -> 00000004063fc000 [600004063fc447]
+ hv.trace_range(irange(0x2643c4000, 0x63fc000), mode=TraceMode.SYNC)
+
+def trace_gpu_irqs():
+ # Trace sgx interrupts
+ node = hv.adt["/arm-io/sgx"]
+ for irq in getattr(node, "interrupts"):
+ hv.trace_irq(f"{node.name} {irq}", irq, 1, hv.IRQTRACE_IRQ)
+
+ ## Trace gfx-asc interrupts
+ #node = hv.adt["/arm-io/gfx-asc"]
+ #for irq in getattr(node, "interrupts"):
+ #hv.trace_irq(f"{node.name} {irq}", irq, 1, hv.IRQTRACE_IRQ)
+
+trace_gpu_irqs()
diff --git a/tools/proxyclient/hv/trace_agx_defer.py b/tools/proxyclient/hv/trace_agx_defer.py
new file mode 100644
index 0000000..257fdc5
--- /dev/null
+++ b/tools/proxyclient/hv/trace_agx_defer.py
@@ -0,0 +1,35 @@
+# SPDX-License-Identifier: MIT
+import datetime
+
+from m1n1.constructutils import show_struct_trace, Ver
+from m1n1.utils import *
+
+Ver.set_version(hv.u)
+
+from m1n1.trace.agx import AGXTracer
+AGXTracer = AGXTracer._reloadcls(True)
+
+agx_tracer = AGXTracer(hv, "/arm-io/gfx-asc", verbose=1)
+
+agx_tracer.pause_after_init = True
+agx_tracer.trace_usermap = False
+agx_tracer.trace_kernmap = False
+agx_tracer.redump = True
+
+agx_tracer.start()
+
+def resume_tracing(ctx):
+ fname = f"{datetime.datetime.now().isoformat()}.log"
+ hv.set_logfile(open(f"gfxlogs/{fname}", "a"))
+ agx_tracer.start()
+ agx_tracer.resume()
+ return True
+
+def pause_tracing(ctx):
+ agx_tracer.pause()
+ agx_tracer.stop()
+ hv.set_logfile(None)
+ return True
+
+hv.add_hvcall(100, resume_tracing)
+hv.add_hvcall(101, pause_tracing)
diff --git a/tools/proxyclient/hv/trace_agx_pwr.py b/tools/proxyclient/hv/trace_agx_pwr.py
new file mode 100644
index 0000000..dd5acf4
--- /dev/null
+++ b/tools/proxyclient/hv/trace_agx_pwr.py
@@ -0,0 +1,158 @@
+# SPDX-License-Identifier: MIT
+import datetime
+
+from m1n1.constructutils import show_struct_trace, Ver
+from m1n1.utils import *
+
+Ver.set_version(hv.u)
+
+trace_device("/arm-io/sgx", True)
+#trace_device("/arm-io/pmp", True)
+#trace_device("/arm-io/gfx-asc", False)
+
+from m1n1.trace.agx import AGXTracer
+AGXTracer = AGXTracer._reloadcls(True)
+
+agx_tracer = AGXTracer(hv, "/arm-io/gfx-asc", verbose=1)
+agx_tracer.trace_kernmap = False
+agx_tracer.trace_kernva = False
+agx_tracer.trace_usermap = False
+
+sgx = hv.adt["/arm-io/sgx"]
+
+freqs = []
+voltages = []
+
+for j in range(8):
+ for i, v in enumerate(voltages):
+ if j != 0:
+ v = 1
+ sgx.perf_states[i+j*len(voltages)].freq = freqs[i] * 1000000
+ sgx.perf_states[i+j*len(voltages)].volt = v
+ sgx.perf_states_sram[i+j*len(voltages)].freq = freqs[i] * 1000000
+ sgx.perf_states_sram[i+j*len(voltages)].volt = 1
+ if j >= 1:
+ getattr(sgx, f"perf_states{j}")[i].freq = freqs[i] * 1000000
+ getattr(sgx, f"perf_states{j}")[i].volt = v
+ getattr(sgx, f"perf_states_sram{j}")[i].freq = freqs[i] * 1000000
+ getattr(sgx, f"perf_states_sram{j}")[i].volt = 1
+
+def after_init():
+ plat = hv.adt.compatible[0].lower()
+ fname = f"initdata/{datetime.datetime.now().isoformat()}-{plat}.log"
+ idlog = open(fname, "w")
+ print(f"Platform: {plat}", file=idlog)
+ fw = hv.adt["/chosen"].firmware_version.split(b"\0")[0].decode("ascii")
+ print(f"Firmware: {fw}", file=idlog)
+ sfw = hv.adt["/chosen"].system_firmware_version
+ print(f"System firmware: {sfw}", file=idlog)
+ print(file=idlog)
+
+ print("ADT SGX:", file=idlog)
+ print(sgx, file=idlog)
+ open("adt_hv.txt","w").write(str(hv.adt))
+
+ print("InitData:", file=idlog)
+ print(agx_tracer.state.initdata, file=idlog)
+
+ power = [int(i) for i in agx_tracer.state.initdata.regionB.hwdata_b.rel_max_powers]
+ volt = [int(i[0]) for i in agx_tracer.state.initdata.regionB.hwdata_b.voltages]
+ freq = [int(i) for i in agx_tracer.state.initdata.regionB.hwdata_b.frequencies]
+
+ print("p/v", [p/max(1, v) for p,v in zip(power,volt)])
+ print("p/f", [p/max(1, f) for p,f in zip(power,freq)])
+ print("p/v2", [p/max(1, (v*v)) for p,v in zip(power,volt)])
+ hv.reboot()
+
+agx_tracer.after_init_hook = after_init
+
+#agx_tracer.encoder_id_filter = lambda i: (i >> 16) == 0xc0de
+agx_tracer.start()
+
+def resume_tracing(ctx):
+ fname = f"{datetime.datetime.now().isoformat()}.log"
+ hv.set_logfile(open(f"gfxlogs/{fname}", "a"))
+ agx_tracer.resume()
+ return True
+
+def pause_tracing(ctx):
+ agx_tracer.pause()
+ hv.set_logfile(None)
+ return True
+
+hv.add_hvcall(100, resume_tracing)
+hv.add_hvcall(101, pause_tracing)
+
+mode = TraceMode.SYNC
+trace_range(irange(agx_tracer.gpu_region, agx_tracer.gpu_region_size), mode=mode, name="gpu_region")
+trace_range(irange(agx_tracer.gfx_shared_region, agx_tracer.gfx_shared_region_size), mode=mode, name="gfx_shared_region")
+
+## Trace the entire mmio range around the GPU
+node = hv.adt["/arm-io/sgx"]
+addr, size = node.get_reg(0)
+hv.trace_range(irange(addr, 0x1000000), TraceMode.SYNC, name="sgx")
+#hv.trace_range(irange(addr, 0x1000000), TraceMode.OFF, name="sgx")
+hv.trace_range(irange(0x204017030, 8), TraceMode.SYNC, name="faultcode")
+
+trace_device("/arm-io/sgx", True)
+trace_device("/arm-io/gfx-asc", False)
+
+def trace_all_gfx_io():
+ # These are all the IO ranges that get mapped into the UAT iommu pagetable
+ # Trace them so we can see if any of them are being written by the CPU
+
+ # page (8): fa010020000 ... fa010023fff -> 000000020e100000 [8000020e100447]
+ hv.trace_range(irange(0x20e100000, 0x4000), mode=TraceMode.SYNC)
+
+ # page (10): fa010028000 ... fa01002bfff -> 000000028e104000 [c000028e104447]
+ hv.trace_range(irange(0x20e100000, 0x4000), mode=TraceMode.SYNC)
+
+ # page (22): fa010058000 ... fa01005bfff -> 000000028e494000 [8000028e494447]
+ hv.trace_range(irange(0x28e494000, 0x4000), mode=TraceMode.SYNC)
+
+ # page (28): fa010070000 ... fa010073fff -> 0000000204d60000 [c0000204d60447]
+ hv.trace_range(irange(0x204d60000, 0x4000), mode=TraceMode.SYNC)
+
+ # page (30): fa010078000 ... fa01007bfff -> 0000000200000000 [c0000200000447]
+ # to
+ # page (83): fa01014c000 ... fa01014ffff -> 00000002000d4000 [c00002000d4447]
+ hv.trace_range(irange(0x200000000, 0xd5000), mode=TraceMode.SYNC)
+
+ # page (84): fa010150000 ... fa010153fff -> 0000000201000000 [c0000201000447]
+ #page (137): fa010224000 ... fa010227fff -> 00000002010d4000 [c00002010d4447]
+ hv.trace_range(irange(0x201000000, 0xd5000), mode=TraceMode.SYNC)
+
+ # page (138): fa010228000 ... fa01022bfff -> 0000000202000000 [c0000202000447]
+ # page (191): fa0102fc000 ... fa0102fffff -> 00000002020d4000 [c00002020d4447]
+ hv.trace_range(irange(0x202000000, 0xd5000), mode=TraceMode.SYNC)
+
+ # page (192): fa010300000 ... fa010303fff -> 0000000203000000 [c0000203000447]
+ hv.trace_range(irange(0x203000000, 0xd5000), mode=TraceMode.SYNC)
+ hv.trace_range(irange(0x204000000, 0xd5000), mode=TraceMode.SYNC)
+ hv.trace_range(irange(0x205000000, 0xd5000), mode=TraceMode.SYNC)
+ hv.trace_range(irange(0x206000000, 0xd5000), mode=TraceMode.SYNC)
+ hv.trace_range(irange(0x207000000, 0xd5000), mode=TraceMode.SYNC)
+
+ # page (464): fa010740000 ... fa010743fff -> 00000002643c4000 [c00002643c4447]
+ hv.trace_range(irange(0x2643c4000, 0x4000), mode=TraceMode.SYNC)
+ # page (466): fa010748000 ... fa01074bfff -> 000000028e3d0000 [c000028e3d0447]
+ hv.trace_range(irange(0x28e3d0000, 0x4000), mode=TraceMode.SYNC)
+ # page (468): fa010750000 ... fa010753fff -> 000000028e3c0000 [8000028e3c0447]
+ hv.trace_range(irange(0x28e3c0000, 0x4000), mode=TraceMode.SYNC)
+
+ # page (8): f9100020000 ... f9100023fff -> 0000000406000000 [60000406000447]
+ # page (263): f910041c000 ... f910041ffff -> 00000004063fc000 [600004063fc447]
+ hv.trace_range(irange(0x2643c4000, 0x63fc000), mode=TraceMode.SYNC)
+
+def trace_gpu_irqs():
+ # Trace sgx interrupts
+ node = hv.adt["/arm-io/sgx"]
+ for irq in getattr(node, "interrupts"):
+ hv.trace_irq(f"{node.name} {irq}", irq, 1, hv.IRQTRACE_IRQ)
+
+ ## Trace gfx-asc interrupts
+ #node = hv.adt["/arm-io/gfx-asc"]
+ #for irq in getattr(node, "interrupts"):
+ #hv.trace_irq(f"{node.name} {irq}", irq, 1, hv.IRQTRACE_IRQ)
+
+trace_gpu_irqs()
diff --git a/tools/proxyclient/hv/trace_all.py b/tools/proxyclient/hv/trace_all.py
new file mode 100644
index 0000000..392dd21
--- /dev/null
+++ b/tools/proxyclient/hv/trace_all.py
@@ -0,0 +1,25 @@
+# SPDX-License-Identifier: MIT
+
+from m1n1.utils import irange
+
+# Map the entire MMIO range as traceable
+for r in hv.adt["/arm-io"].ranges:
+ trace_range(irange(r.parent_addr, r.size), mode=TraceMode.ASYNC)
+
+# Skip some noisy devices
+try:
+ trace_device("/arm-io/usb-drd0", False)
+except KeyError:
+ pass
+try:
+ trace_device("/arm-io/usb-drd1", False)
+except KeyError:
+ pass
+try:
+ trace_device("/arm-io/uart2", False)
+except KeyError:
+ pass
+trace_device("/arm-io/error-handler", False)
+trace_device("/arm-io/aic", False)
+trace_device("/arm-io/spi1", False)
+trace_device("/arm-io/pmgr", False)
diff --git a/tools/proxyclient/hv/trace_all_more.py b/tools/proxyclient/hv/trace_all_more.py
new file mode 100644
index 0000000..7296075
--- /dev/null
+++ b/tools/proxyclient/hv/trace_all_more.py
@@ -0,0 +1,23 @@
+# SPDX-License-Identifier: MIT
+
+from m1n1.utils import irange
+
+# Map the entire MMIO range as traceable
+for r in hv.adt["/arm-io"].ranges:
+ trace_range(irange(r.parent_addr, r.size), mode=TraceMode.ASYNC)
+
+# Skip some noisy devices
+try:
+ trace_device("/arm-io/usb-drd0", False)
+except KeyError:
+ pass
+try:
+ trace_device("/arm-io/usb-drd1", False)
+except KeyError:
+ pass
+try:
+ trace_device("/arm-io/uart2", False)
+except KeyError:
+ pass
+trace_device("/arm-io/aic", False)
+trace_device("/arm-io/spi1", False)
diff --git a/tools/proxyclient/hv/trace_aop.py b/tools/proxyclient/hv/trace_aop.py
new file mode 100644
index 0000000..489c9b3
--- /dev/null
+++ b/tools/proxyclient/hv/trace_aop.py
@@ -0,0 +1,349 @@
+# SPDX-License-Identifier: MIT
+
+from m1n1.trace import Tracer
+from m1n1.trace.dart import DARTTracer
+from m1n1.trace.asc import ASCTracer, EP, EPState, msg, msg_log, DIR, EPContainer
+from m1n1.utils import *
+from m1n1.constructutils import *
+from m1n1.fw.afk.rbep import *
+from m1n1.fw.afk.epic import *
+from m1n1.fw.aop import *
+from m1n1.fw.aop.ipc import *
+
+import sys
+
+class AFKRingBufSniffer(AFKRingBuf):
+ def __init__(self, ep, state, base, size):
+ super().__init__(ep, base, size)
+ self.state = state
+ self.rptr = getattr(state, "rptr", 0)
+
+ def update_rptr(self, rptr):
+ self.state.rptr = rptr
+
+ def update_wptr(self):
+ raise NotImplementedError()
+
+ def get_wptr(self):
+ return struct.unpack("<I", self.read_buf(2 * self.BLOCK_SIZE, 4))[0]
+
+ def read_buf(self, off, size):
+ return self.ep.dart.ioread(0, self.base + off, size)
+
+class AFKEp(EP):
+ BASE_MESSAGE = AFKEPMessage
+
+ def __init__(self, tracer, epid):
+ super().__init__(tracer, epid)
+ self.txbuf = None
+ self.rxbuf = None
+ self.state.txbuf = EPState()
+ self.state.rxbuf = EPState()
+ self.state.shmem_iova = None
+ self.state.txbuf_info = None
+ self.state.rxbuf_info = None
+ self.state.verbose = 1
+
+ def start(self):
+ self.create_bufs()
+
+ def create_bufs(self):
+ if not self.state.shmem_iova:
+ return
+ if not self.txbuf and self.state.txbuf_info:
+ off, size = self.state.txbuf_info
+ self.txbuf = AFKRingBufSniffer(self, self.state.txbuf,
+ self.state.shmem_iova + off, size)
+ if not self.rxbuf and self.state.rxbuf_info:
+ off, size = self.state.rxbuf_info
+ self.rxbuf = AFKRingBufSniffer(self, self.state.rxbuf,
+ self.state.shmem_iova + off, size)
+
+ Init = msg_log(0x80, DIR.TX)
+ Init_Ack = msg_log(0xa0, DIR.RX)
+
+ GetBuf = msg_log(0x89, DIR.RX)
+
+ Shutdown = msg_log(0xc0, DIR.TX)
+ Shutdown_Ack = msg_log(0xc1, DIR.RX)
+
+ @msg(0xa1, DIR.TX, AFKEP_GetBuf_Ack)
+ def GetBuf_Ack(self, msg):
+ self.state.shmem_iova = msg.DVA
+ self.txbuf = None
+ self.rxbuf = None
+ self.state.txbuf = EPState()
+ self.state.rxbuf = EPState()
+ self.state.txbuf_info = None
+ self.state.rxbuf_info = None
+
+ @msg(0xa2, DIR.TX, AFKEP_Send)
+ def Send(self, msg):
+ for data in self.txbuf.read():
+ #if self.state.verbose >= 3:
+ if True:
+ self.log(f"===TX DATA=== epid={self.epid} rptr={self.txbuf.state.rptr:#x}")
+ chexdump(data)
+ self.log(f"===END DATA===")
+ self.log("Backtrace on TX data:")
+ self.hv.bt()
+ self.handle_ipc(data, dir=">")
+ return True
+
+ Hello = msg_log(0xa3, DIR.TX)
+
+ @msg(0x85, DIR.RX, AFKEPMessage)
+ def Recv(self, msg):
+ for data in self.rxbuf.read():
+ #if self.state.verbose >= 3:
+ if True:
+ self.log(f"===RX DATA=== epid={self.epid} rptr={self.rxbuf.state.rptr:#x}")
+ chexdump(data)
+ self.log(f"===END DATA===")
+ self.handle_ipc(data, dir="<")
+ return True
+
+ def handle_ipc(self, data, dir=None):
+ pass
+
+ @msg(0x8a, DIR.RX, AFKEP_InitRB)
+ def InitTX(self, msg):
+ off = msg.OFFSET * AFKRingBuf.BLOCK_SIZE
+ size = msg.SIZE * AFKRingBuf.BLOCK_SIZE
+ self.state.txbuf_info = (off, size)
+ self.create_bufs()
+
+ @msg(0x8b, DIR.RX, AFKEP_InitRB)
+ def InitRX(self, msg):
+ off = msg.OFFSET * AFKRingBuf.BLOCK_SIZE
+ size = msg.SIZE * AFKRingBuf.BLOCK_SIZE
+ self.state.rxbuf_info = (off, size)
+ self.create_bufs()
+
+class DummyAFKEp(AFKEp):
+ def handle_ipc(self, data, dir=None):
+ pass
+
+class EPICEp(AFKEp):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.pending_call = None
+ self.pending_cmd = None
+
+ def handle_hello(self, hdr, sub, fd):
+ if sub.type != 0xc0:
+ return False
+
+ payload = fd.read()
+ name = payload.split(b"\0")[0].decode("ascii")
+ self.log(f"Hello! (endpoint {name})")
+ return True
+
+ def handle_notify(self, hdr, sub, fd):
+ for calltype in CALLTYPES:
+ if calltype.matches(hdr, sub):
+ call = calltype.from_stream(fd)
+ self.trace_call_early(call)
+ self.pending_call = call
+ return True
+
+ return False
+
+ def handle_reply(self, hdr, sub, fd):
+ if self.pending_call is None:
+ return False
+
+ call = self.pending_call
+ call.read_resp(fd)
+ self.trace_call(call)
+ self.pending_call = None
+ return True
+
+ def dispatch_ipc(self, dir, hdr, sub, fd):
+ if sub.category == EPICCategory.COMMAND:
+ return self.handle_notify(hdr, sub, fd)
+ if dir == "<" and sub.category == EPICCategory.REPORT:
+ return self.handle_hello(hdr, sub, fd)
+ if dir == ">" and sub.category == EPICCategory.NOTIFY:
+ return self.handle_notify(hdr, sub, fd)
+ if dir == "<" and sub.category == EPICCategory.REPLY:
+ return self.handle_reply(hdr, sub, fd)
+
+ def handle_ipc(self, data, dir=None):
+ fd = BytesIO(data)
+ hdr = EPICHeader.parse_stream(fd)
+ sub = EPICSubHeaderVer2.parse_stream(fd)
+
+ if not getattr(self, 'VERBOSE', False):
+ return
+
+ self.log(f"{dir} 0x{hdr.channel:x} Type {hdr.type} Ver {hdr.version} Tag {hdr.seq}")
+ self.log(f" Len {sub.length} Ver {sub.version} Cat {sub.category} Type {sub.type:#x} Ts {sub.timestamp:#x}")
+ self.log(f" Unk1 {sub.unk1:#x} Unk2 {sub.unk2:#x}")
+
+ if self.dispatch_ipc(dir, hdr, sub, fd):
+ return
+
+ def trace_call_early(self, call):
+ # called at TX time
+ if isinstance(call, IndirectCall):
+ call.read_txbuf(self)
+
+ def trace_call(self, call):
+ if isinstance(call, IndirectCall):
+ call.read_rxbuf(self)
+ call = call.unwrap()
+ call.dump(self.log)
+
+class SPUAppEp(EPICEp):
+ SHORT = "SPUApp"
+
+class AccelEp(EPICEp):
+ SHORT = "accel"
+
+class GyroEp(EPICEp):
+ SHORT = "gyro"
+
+class LASEp(EPICEp):
+ SHORT = "las"
+
+class WakeHintEp(EPICEp):
+ SHORT = "wakehint"
+
+class UNK26Ep(EPICEp):
+ SHORT = "unk26"
+
+class AudioEp(EPICEp):
+ SHORT = "aop-audio"
+ VERBOSE = True
+
+class VoiceTriggerEp(EPICEp):
+ SHORT = "aop-voicetrigger"
+ VERBOSE = True
+
+
+class AOPTracer(ASCTracer, AOPBase):
+ ENDPOINTS = {
+ 0x20: SPUAppEp,
+ 0x21: AccelEp,
+ 0x22: GyroEp,
+ 0x24: LASEp,
+ 0x25: WakeHintEp,
+ 0x26: UNK26Ep,
+ 0x27: AudioEp,
+ 0x28: VoiceTriggerEp,
+ }
+
+ def __init__(self, hv, devpath, verbose=False):
+ self.default_bootargs = None
+ super().__init__(hv, devpath, verbose)
+ self.u = hv.u
+ AOPBase.__init__(self, hv.u, self.dev)
+
+ def start(self, *args):
+ self.default_bootargs = self.read_bootargs()
+ super().start(*args)
+
+ def w_CPU_CONTROL(self, val):
+ if val.RUN:
+ self.bootargs = self.read_bootargs()
+ self.log("Bootargs patched by AP:")
+ self.default_bootargs.dump_diff(self.bootargs, self.log)
+ self.log("(End of list)")
+ super().w_CPU_CONTROL(val)
+
+ @classmethod
+ def replay(cls, f, passthru=False):
+ epmap = dict()
+ epcont = EPContainer()
+
+ class FakeASCTracer:
+ def __init__(self):
+ self.hv = None
+
+ def log(self, str):
+ print(str)
+ asc_tracer = FakeASCTracer()
+
+ for cls in cls.mro():
+ eps = getattr(cls, "ENDPOINTS", None)
+ if eps is None:
+ break
+ for k, v in eps.items():
+ if k in epmap:
+ continue
+ ep = v(asc_tracer, k)
+ epmap[k] = ep
+ if getattr(epcont, ep.name, None):
+ ep.name = f"{ep.name}{k:02x}"
+ setattr(epcont, ep.name, ep)
+ ep.start()
+
+ def readdump(firstline, hdr, f):
+ l = firstline
+ assert hdr in l
+ postscribe = l[l.index(hdr) + len(hdr):]
+ annotation = dict([s.split("=") for s \
+ in postscribe.strip().split(" ")])
+
+ dump = []
+ for l in f:
+ if "===END DATA===" in l:
+ break
+ dump.append(l)
+ return chexundump("".join(dump)), annotation
+
+ def read_txbuf(icall, ep):
+ hdr = "===COMMAND TX DATA==="
+ for l in f:
+ if hdr in l:
+ break
+ data, annot = readdump(l, hdr, f)
+ assert int(annot["addr"], 16) == icall.args.txbuf
+ icall.txbuf = data
+ def read_rxbuf(icall, ep):
+ hdr = "===COMMAND RX DATA==="
+ for l in f:
+ if hdr in l:
+ break
+ data, annot = readdump(l, hdr, f)
+ assert int(annot["addr"], 16) == icall.rets.rxbuf
+ icall.rxbuf = data
+ IndirectCall.read_rxbuf = read_rxbuf
+ IndirectCall.read_txbuf = read_txbuf
+
+ for l in f:
+ if (rxhdr := "===RX DATA===") in l:
+ dir = "<"
+ hdr = rxhdr
+ elif (txhdr := "===TX DATA===") in l:
+ dir = ">"
+ hdr = txhdr
+ else:
+ if passthru:
+ print(l, end="")
+ continue
+ data, annot = readdump(l, hdr, f)
+ epid = int(annot["epid"])
+ epmap[epid].handle_ipc(data, dir)
+
+
+if __name__ == "__main__":
+ # We can replay traces by saving the textual output of live tracing
+ # and then passing it to this script.
+ with open(sys.argv[1]) as f:
+ AOPTracer.replay(f)
+ sys.exit(0)
+
+dart_aop_tracer = DARTTracer(hv, "/arm-io/dart-aop", verbose=4)
+dart_aop_tracer.start()
+
+dart_aop_base = u.adt["/arm-io/dart-aop"].get_reg(0)[0]
+
+#hv.trace_range(irange(*u.adt["/arm-io/dart-aop"].get_reg(1)))
+#hv.trace_range(irange(*u.adt["/arm-io/aop"].get_reg(1)))
+#hv.trace_range(irange(*u.adt["/arm-io/aop"].get_reg(3)))
+#hv.trace_range(irange(*u.adt["/arm-io/admac-aop-audio"].get_reg(0)))
+
+aop_tracer = AOPTracer(hv, "/arm-io/aop", verbose=1)
+aop_tracer.start(dart_aop_tracer.dart)
diff --git a/tools/proxyclient/hv/trace_atc.py b/tools/proxyclient/hv/trace_atc.py
new file mode 100644
index 0000000..f301803
--- /dev/null
+++ b/tools/proxyclient/hv/trace_atc.py
@@ -0,0 +1,61 @@
+# SPDX-License-Identifier: MIT
+
+from m1n1.hv import TraceMode
+from m1n1.hw.dwc3 import XhciRegs, Dwc3CoreRegs, PipehandlerRegs
+from m1n1.hw.atc import Usb2PhyRegs, AtcPhyRegs
+from m1n1.trace import ADTDevTracer
+from m1n1.utils import *
+
+
+class PhyTracer(ADTDevTracer):
+ REGMAPS = [
+ Usb2PhyRegs,
+ None,
+ (AtcPhyRegs, 0x20000),
+ (AtcPhyRegs, 0x0),
+ (AtcPhyRegs, 0x2000),
+ (AtcPhyRegs, 0x2200),
+ (AtcPhyRegs, 0x2800),
+ (AtcPhyRegs, 0x2A00),
+ (AtcPhyRegs, 0x7000),
+ (AtcPhyRegs, 0xA00),
+ (AtcPhyRegs, 0x800),
+ (AtcPhyRegs, 0xD000),
+ (AtcPhyRegs, 0x14000),
+ (AtcPhyRegs, 0xC000),
+ (AtcPhyRegs, 0x13000),
+ (AtcPhyRegs, 0xB000),
+ (AtcPhyRegs, 0x12000),
+ (AtcPhyRegs, 0x9000),
+ (AtcPhyRegs, 0x10000),
+ (AtcPhyRegs, 0x1000),
+ (AtcPhyRegs, 0x50000),
+ (AtcPhyRegs, 0x50200),
+ (AtcPhyRegs, 0x54000),
+ None,
+ None,
+ None,
+ (AtcPhyRegs, 0xA000),
+ (AtcPhyRegs, 0x11000),
+ ]
+
+
+class Dwc3VerboseTracer(ADTDevTracer):
+ REGMAPS = [XhciRegs, None, Dwc3CoreRegs, PipehandlerRegs]
+ NAMES = ["xhci", None, "dwc-core", "pipehandler"]
+
+
+class Dwc3Tracer(ADTDevTracer):
+ REGMAPS = [None, None, Dwc3CoreRegs, PipehandlerRegs]
+ NAMES = [None, None, "dwc-core", "pipehandler"]
+
+
+PhyTracer = PhyTracer._reloadcls()
+Dwc3Tracer = Dwc3Tracer._reloadcls()
+Dwc3VerboseTracer = Dwc3VerboseTracer._reloadcls()
+
+phy_tracer = PhyTracer(hv, "/arm-io/atc-phy1", verbose=2)
+dwc3_tracer = Dwc3Tracer(hv, "/arm-io/usb-drd1", verbose=2)
+
+phy_tracer.start()
+dwc3_tracer.start()
diff --git a/tools/proxyclient/hv/trace_cd3217.py b/tools/proxyclient/hv/trace_cd3217.py
new file mode 100644
index 0000000..cea79f3
--- /dev/null
+++ b/tools/proxyclient/hv/trace_cd3217.py
@@ -0,0 +1,119 @@
+# SPDX-License-Identifier: MIT
+
+from enum import Enum
+
+from m1n1.trace.i2c import I2CTracer, I2CDevTracer
+
+class HpmTracer(I2CDevTracer):
+ class State(Enum):
+ UNKNOWN = 0
+ REQUEST = 1
+ WRITE = 2
+ READ = 3
+
+ def __init__(self, addr=128, name=None, verbose=True):
+ print(f"CD3217Tracer.__init__(addr={addr}, name={name}, verbose={verbose})")
+ super().__init__(addr, name, verbose)
+ self.reset()
+
+ def reset(self):
+ self.reg = None
+ self.state = CD3217Tracer.State.UNKNOWN
+ self.length = None
+ self.data = []
+
+ def start(self, addr, read):
+ if addr != self.addr:
+ return
+
+ if self.state == CD3217Tracer.State.UNKNOWN:
+ if read:
+ self.state = CD3217Tracer.State.READ
+ else:
+ self.state = CD3217Tracer.State.REQUEST
+ elif self.state == CD3217Tracer.State.REQUEST and read:
+ pass
+ else:
+ self.log(f"unexpected state in start(read={read}): state:{self.state} reg:{self.reg} data:{self.data}")
+
+ def stop(self):
+ if self.state == CD3217Tracer.State.REQUEST and len(self.data) == 0:
+ return
+
+ msg = f"Txn: {self.addr:02x}."
+ if self.state == CD3217Tracer.State.REQUEST:
+ msg += f"r [{self.reg:02x}]"
+ elif self.state == CD3217Tracer.State.WRITE:
+ msg += f"w [{self.reg:02x}]"
+ elif self.state == CD3217Tracer.State.READ:
+ msg += f"r [xx]"
+ else:
+ self.log(f"unexpected state in stop(): state:{self.state} reg:{self.reg} data:{self.data}")
+ self.reset()
+ return
+
+ # only for debugging as some mismatches are expected as
+ # cd3217 seems to report the register size and not the number
+ # of requested bytes (or I2CDevTracer truncates reads).
+ #if self.length is not None and self.length > len(self.data):
+ # self.log(f"length {self.length:02x} mismatch received data: {len(self.data):02x}")
+
+ for data in self.data:
+ msg += f" {data:02x}"
+
+ self.log(msg)
+ self.reset()
+
+ def read(self, data):
+ self.data.append(data)
+
+ def write(self, data):
+ if self.reg is None:
+ self.reg = data
+ elif self.length is None:
+ self.length = data
+ self.state = CD3217Tracer.State.WRITE
+ else:
+ self.data.append(data)
+
+class CD3217Tracer(HpmTracer):
+ def read(self, data):
+ if self.length is None:
+ self.length = data - 1
+ else:
+ self.data.append(data)
+
+
+i2c_tracers = {}
+
+for node in hv.adt["/arm-io"]:
+ if not node.name.startswith("i2c"):
+ continue
+
+ n = int(node.name[3:])
+ bus = I2CTracer(hv, f"/arm-io/{node.name}")
+
+ for mngr_node in node:
+ if "compatible" not in mngr_node._properties: # thanks Apple
+ continue
+
+ if mngr_node.compatible[0] != "usbc,manager":
+ continue
+
+ addr = mngr_node.reg[0] & 0xff
+ bus.add_device(addr, HpmTracer(addr=addr, name=mngr_node.name))
+
+ for devnode in mngr_node:
+
+ dcls = {
+ "usbc,cd3217": CD3217Tracer,
+ }.get(devnode.compatible[0], None)
+ if dcls:
+ addr = devnode.hpm_iic_addr & 0xff
+ bus.add_device(addr, dcls(addr=addr, name=devnode.name))
+
+ if len(bus.state.devices) > 1:
+ i2c_tracers[n] = bus
+
+for bus in i2c_tracers.values():
+ bus.start()
diff --git a/tools/proxyclient/hv/trace_codecs.py b/tools/proxyclient/hv/trace_codecs.py
new file mode 100644
index 0000000..4cd9730
--- /dev/null
+++ b/tools/proxyclient/hv/trace_codecs.py
@@ -0,0 +1,45 @@
+# SPDX-License-Identifier: MIT
+
+from m1n1.utils import RegMap
+from m1n1.trace.i2c import I2CTracer, I2CRegMapTracer
+from m1n1.hw.codecs import *
+
+hv.p.hv_set_time_stealing(0, 1)
+
+class SN012776Tracer(I2CRegMapTracer):
+ REGMAP = SN012776Regs
+ ADDRESSING = (1, 1)
+
+class TAS5770Tracer(I2CRegMapTracer):
+ REGMAP = TAS5770Regs
+ ADDRESSING = (1, 1)
+
+class CS42L84Tracer(I2CRegMapTracer):
+ REGMAP = CS42L84Regs
+ ADDRESSING = (0, 2)
+
+i2c_tracers = {}
+
+for node in hv.adt["/arm-io"]:
+ if not node.name.startswith("i2c"):
+ continue
+
+ n = int(node.name[3:])
+ i2c_tracers[n] = bus = I2CTracer(hv, f"/arm-io/{node.name}")
+
+ for devnode in node:
+ if "compatible" not in devnode._properties: # thanks Apple
+ continue
+
+ dcls = {
+ "audio-control,tas5770": TAS5770Tracer,
+ "audio-control,sn012776": SN012776Tracer,
+ "audio-control,cs42l84": CS42L84Tracer,
+ }.get(devnode.compatible[0], None)
+ if dcls:
+ bus.add_device(
+ devnode.reg[0] & 0xff,
+ dcls(name=devnode.name)
+ )
+
+ bus.start()
diff --git a/tools/proxyclient/hv/trace_dart.py b/tools/proxyclient/hv/trace_dart.py
new file mode 100644
index 0000000..d983145
--- /dev/null
+++ b/tools/proxyclient/hv/trace_dart.py
@@ -0,0 +1,27 @@
+# SPDX-License-Identifier: MIT
+
+from m1n1.trace.dart import DARTTracer
+
+DARTTracer = DARTTracer._reloadcls()
+
+DEVICES = [
+ "/arm-io/dart-pmp",
+ "/arm-io/dart-sep",
+ "/arm-io/dart-sio",
+ "/arm-io/dart-usb1",
+ "/arm-io/dart-disp0",
+ "/arm-io/dart-dcp",
+ "/arm-io/dart-dispext0",
+ "/arm-io/dart-dcpext",
+ "/arm-io/dart-scaler",
+]
+
+dart_tracers = {}
+
+for i in DEVICES:
+ tracer = DARTTracer(hv, i, verbose=3)
+ tracer.start()
+
+ dart_tracers[i.split("-")[-1]] = tracer
+
+del tracer
diff --git a/tools/proxyclient/hv/trace_dcp.py b/tools/proxyclient/hv/trace_dcp.py
new file mode 100644
index 0000000..367a990
--- /dev/null
+++ b/tools/proxyclient/hv/trace_dcp.py
@@ -0,0 +1,1113 @@
+# SPDX-License-Identifier: MIT
+
+import struct
+from io import BytesIO
+
+from enum import IntEnum
+
+from m1n1.proxyutils import RegMonitor
+from m1n1.utils import *
+from m1n1.trace.dart import DARTTracer
+from m1n1.trace.asc import ASCTracer, EP, EPState, msg, msg_log, DIR
+from m1n1.fw.afk.rbep import *
+from m1n1.fw.afk.epic import *
+
+if True:
+ dcp_adt_path = "/arm-io/dcp"
+ dcp_dart_adt_path = "/arm-io/dart-dcp"
+ dcp_dart_mapper_adt_path = "/arm-io/dart-dcp/mapper-dcp"
+ disp0_dart_adt_path = "/arm-io/dart-disp0"
+else:
+ dcp_adt_path = "/arm-io/dcpext"
+ dcp_dart_adt_path = "/arm-io/dart-dcpext"
+ dcp_dart_mapper_adt_path = "/arm-io/dart-dcpext/mapper-dcpext"
+ disp0_dart_adt_path = "/arm-io/dart-dispext0"
+
+trace_device(dcp_adt_path, True, ranges=[1])
+
+DARTTracer = DARTTracer._reloadcls()
+ASCTracer = ASCTracer._reloadcls()
+
+iomon = RegMonitor(hv.u, ascii=True)
+
+class AFKRingBufSniffer(AFKRingBuf):
+ def __init__(self, ep, state, base, size):
+ super().__init__(ep, base, size)
+ self.state = state
+ self.rptr = getattr(state, "rptr", 0)
+
+ def update_rptr(self, rptr):
+ self.state.rptr = rptr
+
+ def update_wptr(self):
+ raise NotImplementedError()
+
+ def get_wptr(self):
+ return struct.unpack("<I", self.read_buf(2 * self.BLOCK_SIZE, 4))[0]
+
+ def read_buf(self, off, size):
+ return self.ep.dart.ioread(self.ep.stream, self.base + off, size)
+
+class AFKEp(EP):
+ BASE_MESSAGE = AFKEPMessage
+
+ def __init__(self, tracer, epid):
+ super().__init__(tracer, epid)
+ self.txbuf = None
+ self.rxbuf = None
+ self.state.txbuf = EPState()
+ self.state.rxbuf = EPState()
+ self.state.shmem_iova = None
+ self.state.txbuf_info = None
+ self.state.rxbuf_info = None
+ self.state.verbose = 1
+
+ def start(self):
+ #self.add_mon()
+ self.create_bufs()
+
+ def create_bufs(self):
+ if not self.state.shmem_iova:
+ return
+ if not self.txbuf and self.state.txbuf_info:
+ off, size = self.state.txbuf_info
+ self.txbuf = AFKRingBufSniffer(self, self.state.txbuf,
+ self.state.shmem_iova + off, size)
+ if not self.rxbuf and self.state.rxbuf_info:
+ off, size = self.state.rxbuf_info
+ self.rxbuf = AFKRingBufSniffer(self, self.state.rxbuf,
+ self.state.shmem_iova + off, size)
+
+ def add_mon(self):
+ if self.state.shmem_iova:
+ iomon.add(self.state.shmem_iova, 32768,
+ name=f"{self.name}.shmem@{self.state.shmem_iova:08x}", offset=0)
+
+ Init = msg_log(0x80, DIR.TX)
+ Init_Ack = msg_log(0xa0, DIR.RX)
+
+ GetBuf = msg_log(0x89, DIR.RX)
+
+ Shutdown = msg_log(0xc0, DIR.TX)
+ Shutdown_Ack = msg_log(0xc1, DIR.RX)
+
+ @msg(0xa1, DIR.TX, AFKEP_GetBuf_Ack)
+ def GetBuf_Ack(self, msg):
+ self.state.shmem_iova = msg.DVA
+ self.txbuf = None
+ self.rxbuf = None
+ self.state.txbuf = EPState()
+ self.state.rxbuf = EPState()
+ self.state.txbuf_info = None
+ self.state.rxbuf_info = None
+ #self.add_mon()
+
+ @msg(0xa2, DIR.TX, AFKEP_Send)
+ def Send(self, msg):
+ for data in self.txbuf.read():
+ if self.state.verbose >= 3:
+ self.log(f">TX rptr={self.txbuf.state.rptr:#x}")
+ chexdump(data, print_fn=self.log)
+ self.handle_ipc(data, dir=">")
+ return True
+
+ Hello = msg_log(0xa3, DIR.TX)
+
+ @msg(0x85, DIR.RX, AFKEPMessage)
+ def Recv(self, msg):
+ for data in self.rxbuf.read():
+ if self.state.verbose >= 3:
+ self.log(f"<RX rptr={self.rxbuf.state.rptr:#x}")
+ chexdump(data, print_fn=self.log)
+ self.handle_ipc(data, dir="<")
+ return True
+
+ def handle_ipc(self, data, dir=None):
+ pass
+
+ @msg(0x8a, DIR.RX, AFKEP_InitRB)
+ def InitTX(self, msg):
+ off = msg.OFFSET * AFKRingBuf.BLOCK_SIZE
+ size = msg.SIZE * AFKRingBuf.BLOCK_SIZE
+ self.state.txbuf_info = (off, size)
+ self.create_bufs()
+
+ @msg(0x8b, DIR.RX, AFKEP_InitRB)
+ def InitRX(self, msg):
+ off = msg.OFFSET * AFKRingBuf.BLOCK_SIZE
+ size = msg.SIZE * AFKRingBuf.BLOCK_SIZE
+ self.state.rxbuf_info = (off, size)
+ self.create_bufs()
+
+class SilentEp(AFKEp):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.state.verbose = 0
+
+ def log(self, msg):
+ pass
+
+def epic_service_cmd(group, cmd):
+ def f(x):
+ x.is_cmd = True
+ x.group = group
+ x.cmd = cmd
+ return x
+ return f
+
+def epic_service_reply(group, cmd):
+ def f(x):
+ x.is_reply = True
+ x.group = group
+ x.cmd = cmd
+ return x
+ return f
+
+class EPICServiceTracer(Reloadable):
+ def __init__(self, tracer, ep, key):
+ self.tracer = tracer
+ self.ep = ep
+ self.key = key
+
+ self.cmdmap = {}
+ self.replymap = {}
+ for name in dir(self):
+ i = getattr(self, name)
+ if not callable(i):
+ continue
+ if getattr(i, "is_cmd", False):
+ self.cmdmap[i.group, i.cmd] = getattr(self, name)
+ if getattr(i, "is_reply", False):
+ self.replymap[i.group, i.cmd] = getattr(self, name)
+
+ def log(self, msg):
+ self.ep.log(f"[{self.key}] {msg}")
+
+ def init(self, props):
+ pass
+
+ def handle_cmd(self, sgroup, scmd, sdata):
+ cmdfn = self.cmdmap.get((sgroup, scmd), None)
+ if cmdfn:
+ cmdfn(sdata)
+ else:
+ self.log(f"> unknown group {sgroup}; command {scmd}")
+ if sdata:
+ chexdump(sdata, print_fn=self.log)
+
+ def handle_reply(self, sgroup, scmd, sdata):
+ replyfn = self.replymap.get((sgroup, scmd), None)
+ if replyfn:
+ replyfn(sdata)
+ else:
+ self.log(f"< unknown group {sgroup}; command {scmd}")
+ if sdata:
+ chexdump(sdata, print_fn=self.log)
+
+ @epic_service_cmd(4, 4)
+ def getLocation(self, data):
+ self.log("> getLocation")
+ @epic_service_reply(4, 4)
+ def getLocation_reply(self, data):
+ self.log("< getLocation")
+
+ @epic_service_cmd(4, 5)
+ def getUnit(self, data):
+ self.log("> getUnit")
+ @epic_service_reply(4, 5)
+ def getUnit_reply(self, data):
+ self.log("< getUnit")
+
+ @epic_service_cmd(4, 6)
+ def open(self, data):
+ self.log("> open")
+ @epic_service_reply(4, 6)
+ def open_reply(self, data):
+ self.log("< open")
+
+ @epic_service_cmd(4, 7)
+ def close(self, data):
+ self.log("> close")
+ @epic_service_reply(4, 7)
+ def close_reply(self, data):
+ self.log("< close")
+
+class EPICEp(AFKEp):
+ SERVICES = []
+
+ def __init__(self, tracer, epid):
+ super().__init__(tracer, epid)
+
+ self.serv_map = {}
+ self.chan_map = {}
+ self.serv_names = {}
+ for i in self.SERVICES:
+ self.serv_names[i.NAME] = i
+
+ def handle_ipc(self, data, dir=None):
+ fd = BytesIO(data)
+ hdr = EPICHeader.parse_stream(fd)
+ sub = EPICSubHeader.parse_stream(fd)
+
+ if sub.category == EPICCategory.REPORT:
+ self.handle_report(hdr, sub, fd)
+ if sub.category == EPICCategory.NOTIFY:
+ self.handle_notify(hdr, sub, fd)
+ elif sub.category == EPICCategory.REPLY:
+ self.handle_reply(hdr, sub, fd)
+ elif sub.category == EPICCategory.COMMAND:
+ self.handle_cmd(hdr, sub, fd)
+ else:
+ self.log(f"{dir}Ch {hdr.channel} Type {hdr.type} Ver {hdr.version} Tag {hdr.seq}")
+ self.log(f" Len {sub.length} Ver {sub.version} Cat {sub.category} Type {sub.type:#x} Seq {sub.seq}")
+ chexdump(data, print_fn=self.log)
+
+ def handle_report_init(self, hdr, sub, fd):
+ init = EPICAnnounce.parse_stream(fd)
+ self.log(f"Init: {init.name}")
+ self.log(f" Props: {init.props}")
+
+ if not init.props:
+ init.props = {}
+
+ name = init.props.get("EPICName", init.name)
+ key = name + str(init.props.get("EPICUnit", ""))
+ self.log(f"New service: {key} on channel {hdr.channel}")
+
+ srv_cls = self.serv_names.get(name, EPICServiceTracer)
+ srv = srv_cls(self.tracer, self, key)
+ srv.init(init.props)
+ srv.chan = hdr.channel
+ self.chan_map[hdr.channel] = srv
+ self.serv_map[key] = srv
+
+ def handle_report(self, hdr, sub, fd):
+ if sub.type == 0x30:
+ self.handle_report_init(hdr, sub, fd)
+ else:
+ self.log(f"Report {sub.type:#x}")
+ chexdump(fd.read(), print_fn=self.log)
+
+ def handle_notify(self, hdr, sub, fd):
+ self.log(f"Notify:")
+ chexdump(fd.read(), print_fn=self.log)
+
+ def handle_reply(self, hdr, sub, fd):
+ if sub.inline_len:
+ payload = fd.read()
+ self.log("Inline payload:")
+ chexdump(payload, print_fn=self.log)
+ else:
+ cmd = EPICCmd.parse_stream(fd)
+ if not cmd.rxbuf:
+ self.log(f"Response {sub.type:#x}: {cmd.retcode:#x}")
+ return
+
+ data = self.dart.ioread(self.stream, cmd.rxbuf, cmd.rxlen)
+ rgroup, rcmd, rlen, rmagic = struct.unpack("<2xHIII", data[:16])
+ if rmagic != 0x69706378:
+ self.log("Warning: Invalid EPICStandardService response magic")
+
+ srv = self.chan_map.get(hdr.channel, None)
+ if srv:
+ srv.handle_reply(rgroup, rcmd, data[64:64+rlen] if rlen else None)
+ else:
+ self.log(f"[???] < group {rgroup} command {rcmd}")
+ chexdump(data[64:64+rlen], print_fn=lambda msg: self.log(f"[???] {msg}"))
+
+ def handle_cmd(self, hdr, sub, fd):
+ cmd = EPICCmd.parse_stream(fd)
+ payload = fd.read()
+
+ if sub.type == 0xc0 and cmd.txbuf:
+ data = self.dart.ioread(self.stream, cmd.txbuf, cmd.txlen)
+ sgroup, scmd, slen, sfooter = struct.unpack("<2xHIII48x", data[:64])
+ sdata = data[64:64+slen] if slen else None
+
+ srv = self.chan_map.get(hdr.channel, None)
+ if srv:
+ srv.handle_cmd(sgroup, scmd, sdata)
+ else:
+ self.log(f"[???] > group {sgroup} command {scmd}")
+ chexdump(data[64:64+slen], print_fn=lambda msg: self.log(f"[???] {msg}"))
+ else:
+ self.log(f"Command {sub.type:#x}: {cmd.retcode:#x}")
+ if payload:
+ chexdump(payload, print_fn=self.log)
+ if cmd.txbuf:
+ self.log(f"TX buf @ {cmd.txbuf:#x} ({cmd.txlen:#x} bytes):")
+ chexdump(self.dart.ioread(self.stream, cmd.txbuf, cmd.txlen), print_fn=self.log)
+
+KNOWN_MSGS = {
+ "A000": "IOMFB::UPPipeAP_H13P::late_init_signal()",
+ "A001": "IOMFB::UPPipeAP_H13P::init_ipa(unsigned long long, unsigned long)",
+ "A002": "IOMFB::UPPipeAP_H13P::alss_supported()",
+ "A003": "IOMFB::UPPipeAP_H13P::reset_dsc()",
+ "A004": "IOMFB::UPPipeAP_H13P::display_edr_factor_changed(float)",
+ "A005": "IOMFB::UPPipeAP_H13P::set_contrast(float)",
+ "A006": "IOMFB::UPPipeAP_H13P::set_csc_mode(IOMFB_CSCMode)",
+ "A007": "IOMFB::UPPipeAP_H13P::set_op_mode(IOMFB_CSCMode)",
+ "A008": "IOMFB::UPPipeAP_H13P::set_op_gamma_mode(IOMFB_TFMode)",
+ "A009": "IOMFB::UPPipeAP_H13P::set_video_out_mode(IOMFB_Output_Mode)",
+ "A010": "IOMFB::UPPipeAP_H13P::set_meta_allowed(bool)",
+ "A011": "IOMFB::UPPipeAP_H13P::set_tunneled_color_mode(bool)",
+ "A012": "IOMFB::UPPipeAP_H13P::set_bwr_line_time_us(double)",
+ "A013": "IOMFB::UPPipeAP_H13P::performance_feedback(double)",
+ "A014": "IOMFB::UPPipeAP_H13P::notify_swap_complete(unsigned int)",
+ "A015": "IOMFB::UPPipeAP_H13P::is_run_mode_change_pending() const",
+ "A016": "IOMFB::UPPipeAP_H13P::ready_for_run_mode_change(IOMFB::AppleRegisterStream*)",
+ "A017": "IOMFB::UPPipeAP_H13P::set_thermal_throttle_cap(unsigned int)",
+ "A018": "IOMFB::UPPipeAP_H13P::emergency_shutdown_normal_mode(IOMFB::AppleRegisterStream*)",
+ "A019": "IOMFB::UPPipeAP_H13P::set_target_run_mode(IOMFB::AppleRegisterStream*)",
+ "A020": "IOMFB::UPPipeAP_H13P::rt_bandwidth_setup()",
+ "A021": "IOMFB::UPPipeAP_H13P::rt_bandwidth_update(IOMFB::AppleRegisterStream*, float, float, bool, bool)",
+ "A022": "IOMFB::UPPipeAP_H13P::rt_bandwidth_update_downgrade(IOMFB::AppleRegisterStream*)",
+ "A023": "IOMFB::UPPipeAP_H13P::rt_bandwidth_write_update(IOMFB::AppleRegisterStream*, RealtimeBandwithWritebackBlock, bool)",
+ "A024": "IOMFB::UPPipeAP_H13P::cif_blending_eco_present()",
+ "A025": "IOMFB::UPPipeAP_H13P::request_bic_update()",
+ "A026": "IOMFB::UPPipeAP_H13P::early_power_off_warning(IOMFB::AppleRegisterStream*)",
+ "A027": "IOMFB::UPPipeAP_H13P::get_max_frame_size(unsigned int*, unsigned int*)",
+ "A028": "IOMFB::UPPipeAP_H13P::shadow_FIFO_empty(IOMFB::AppleRegisterStream*) const",
+ "A029": "IOMFB::UPPipeAP_H13P::setup_video_limits()",
+ "A030": "IOMFB::UPPipeAP_H13P::can_program_swap() const",
+ "A031": "IOMFB::UPPipeAP_H13P::in_auto_mode() const",
+ "A032": "IOMFB::UPPipeAP_H13P::push_black_frame(IOMFB::AppleRegisterStream*)",
+ "A033": "IOMFB::UPPipeAP_H13P::read_crc(Notify_Info_Index, unsigned int)",
+ "A034": "IOMFB::UPPipeAP_H13P::update_notify_clients_dcp(unsigned int const*)",
+ "A035": "IOMFB::UPPipeAP_H13P::is_hilo() const",
+ "A036": "IOMFB::UPPipeAP_H13P::apt_supported() const",
+ "A037": "IOMFB::UPPipeAP_H13P::get_dfb_info(unsigned int*, unsigned long long*, unsigned int*)",
+ "A038": "IOMFB::UPPipeAP_H13P::get_dfb_compression_info(unsigned int*)",
+ "A039": "IOMFB::UPPipeAP_H13P::get_frame_done_time() const",
+ "A040": "IOMFB::UPPipeAP_H13P::get_performance_headroom() const",
+ "A041": "IOMFB::UPPipeAP_H13P::are_stats_active() const",
+ "A042": "IOMFB::UPPipeAP_H13P::supports_odd_h_blanking() const",
+ "A043": "IOMFB::UPPipeAP_H13P::is_first_hw_version() const",
+ "A044": "IOMFB::UPPipeAP_H13P::set_blendout_CSC_mode()",
+
+ "A100": "IOMFB::UPPipe2::get_gamma_table_gated(IOMFBGammaTable*)",
+ "A101": "IOMFB::UPPipe2::set_gamma_table_gated(IOMFBGammaTable const*)",
+ "A102": "IOMFB::UPPipe2::test_control(IOMFB_TC_Cmd, unsigned int)",
+ "A103": "IOMFB::UPPipe2::get_config_frame_size(unsigned int*, unsigned int*) const",
+ "A104": "IOMFB::UPPipe2::set_config_frame_size(unsigned int, unsigned int) const",
+ "A105": "IOMFB::UPPipe2::program_config_frame_size() const",
+ "A106": "IOMFB::UPPipe2::read_blend_crc() const",
+ "A107": "IOMFB::UPPipe2::read_config_crc() const",
+ "A108": "IOMFB::UPPipe2::disable_wpc_calibration(bool)",
+ "A109": "IOMFB::UPPipe2::vftg_is_running(IOMFB::AppleRegisterStream*) const",
+ "A110": "IOMFB::UPPipe2::vftg_debug(IOMFB::AppleRegisterStream*, unsigned int) const",
+ "A111": "IOMFB::UPPipe2::vftg_set_color_channels(unsigned int, unsigned int, unsigned int)",
+ "A112": "IOMFB::UPPipe2::set_color_filter_scale(int)",
+ "A113": "IOMFB::UPPipe2::set_corner_temps(int const*)",
+ "A114": "IOMFB::UPPipe2::reset_aot_enabled() const",
+ "A115": "IOMFB::UPPipe2::aot_enabled() const",
+ "A116": "IOMFB::UPPipe2::aot_active() const",
+ "A117": "IOMFB::UPPipe2::set_timings_enabled(IOMFB::AppleRegisterStream*, bool)",
+ "A118": "IOMFB::UPPipe2::get_frame_size(IOMFB::AppleRegisterStream*, unsigned int*, unsigned int*)",
+ "A119": "IOMFB::UPPipe2::set_block(unsigned long long, unsigned int, unsigned int, unsigned long long const*, unsigned int, unsigned char const*, unsigned long, bool)",
+ "A121": "IOMFB::UPPipe2::get_buf_block(unsigned long long, unsigned int, unsigned int, unsigned long long const*, unsigned int, unsigned char const*, unsigned long, bool)",
+ "A122": "IOMFB::UPPipe2::get_matrix(IOMFB_MatrixLocation, IOMFBColorFixedMatrix*) const",
+ "A123": "IOMFB::UPPipe2::set_matrix(IOMFB_MatrixLocation, IOMFBColorFixedMatrix const*)",
+ "A124": "IOMFB::UPPipe2::get_internal_timing_attributes_gated(IOMFB::RefreshTimingAttributes*) const",
+ "A125": "IOMFB::UPPipe2::display_edr_factor_changed(float)",
+ "A126": "IOMFB::UPPipe2::set_contrast(float)",
+ "A127": "IOMFB::UPPipe2::p3_to_disp_cs(float const*, float const (*) [2])",
+ "A128": "IOMFB::UPPipe2::max_panel_brightness() const",
+ "A129": "IOMFB::UPPipe2::swap_flush_stream_replay(IOMFB::AppleRegisterStream*)",
+ "A130": "IOMFB::UPPipe2::init_ca_pmu()",
+ "A131": "IOMFB::UPPipe2::pmu_service_matched()",
+ "A132": "IOMFB::UPPipe2::backlight_service_matched()",
+
+ "A200": "IOMFB::PropRelay::setBool(IOMFB::RuntimeProperty, bool)",
+ "A201": "IOMFB::PropRelay::setInt(IOMFB::RuntimeProperty, unsigned int)",
+ "A202": "IOMFB::PropRelay::setFx(IOMFB::RuntimeProperty, int)",
+ "A203": "IOMFB::PropRelay::setPropDynamic(IOMFB::RuntimeProperty, unsigned int)",
+ "A204": "IOMFB::PropRelay::getBool(IOMFB::RuntimeProperty)",
+ "A205": "IOMFB::PropRelay::getInt(IOMFB::RuntimeProperty)",
+ "A206": "IOMFB::PropRelay::getFx(IOMFB::RuntimeProperty)",
+
+ "A350": "UnifiedPipeline2::displayHeight()",
+ "A351": "UnifiedPipeline2::displayWidth()",
+ "A352": "UnifiedPipeline2::applyProperty(unsigned int, unsigned int)",
+ "A353": "UnifiedPipeline2::get_system_type() const",
+ "A354": "UnifiedPipeline2::headless() const",
+ "A355": "UnifiedPipeline2::export_idle_method(unsigned int)",
+ "A357": "UnifiedPipeline2::set_create_DFB()",
+ "A358": "UnifiedPipeline2::vi_set_temperature_hint()",
+
+ "A400": "IOMobileFramebufferAP::free_signal()",
+ "A401": "IOMobileFramebufferAP::start_signal()",
+ "A402": "IOMobileFramebufferAP::stop_signal()",
+ "A403": "IOMobileFramebufferAP::systemWillShutdown()",
+ "A404": "IOMobileFramebufferAP::swap_begin()",
+ "A405": "IOMobileFramebufferAP::rotate_surface(unsigned int, unsigned int, unsigned int)",
+ "A406": "IOMobileFramebufferAP::get_framebuffer_id()",
+ "A407": "IOMobileFramebufferAP::swap_start(unsigned int*, IOUserClient*)",
+ "A408": "IOMobileFramebufferAP::swap_submit_dcp(IOMFBSwapRec const*, IOSurface**, unsigned int const*, bool, double, unsigned int, bool*)",
+ "A409": "IOMobileFramebufferAP::swap_signal(unsigned int, unsigned int)",
+ "A410": "IOMobileFramebufferAP::set_display_device(unsigned int)",
+ "A411": "IOMobileFramebufferAP::is_main_display() const",
+ "A412": "IOMobileFramebufferAP::set_digital_out_mode(unsigned int, unsigned int)",
+ "A413": "IOMobileFramebufferAP::get_digital_out_state(unsigned int*)",
+ "A414": "IOMobileFramebufferAP::get_display_area(DisplayArea*)",
+ "A415": "IOMobileFramebufferAP::set_tvout_mode(unsigned int)",
+ "A416": "IOMobileFramebufferAP::set_tvout_signaltype(unsigned int)",
+ "A417": "IOMobileFramebufferAP::set_wss_info(unsigned int, unsigned int)",
+ "A418": "IOMobileFramebufferAP::set_content_flags(unsigned int)",
+ "A419": "IOMobileFramebufferAP::get_gamma_table(IOMFBGammaTable*)",
+ "A420": "IOMobileFramebufferAP::set_gamma_table(IOMFBGammaTable*)",
+ "A421": "IOMobileFramebufferAP::get_matrix(unsigned int, unsigned long long (*) [3][3]) const",
+ "A422": "IOMobileFramebufferAP::set_matrix(unsigned int, unsigned long long const (*) [3][3])",
+ "A423": "IOMobileFramebufferAP::set_contrast(float*)",
+ "A424": "IOMobileFramebufferAP::set_white_on_black_mode(unsigned int)",
+ "A425": "IOMobileFramebufferAP::set_color_remap_mode(DisplayColorRemapMode)",
+ "A426": "IOMobileFramebufferAP::get_color_remap_mode(DisplayColorRemapMode*) const",
+ "A427": "IOMobileFramebufferAP::setBrightnessCorrection(unsigned int)",
+ "A428": "IOMobileFramebufferAP::temp_queue_swap_cancel(unsigned int)",
+ "A429": "IOMobileFramebufferAP::swap_cancel(unsigned int)",
+ "A430": "IOMobileFramebufferAP::swap_cancel_all_dcp(unsigned long long)",
+ "A431": "IOMobileFramebufferAP::surface_is_replaceable(unsigned int, bool*)",
+ "A432": "IOMobileFramebufferAP::kernel_tests(IOMFBKernelTestsArguments*)",
+ "A433": "IOMobileFramebufferAP::splc_set_brightness(unsigned int)",
+ "A434": "IOMobileFramebufferAP::splc_get_brightness(unsigned int*)",
+ "A435": "IOMobileFramebufferAP::set_block_dcp(task*, unsigned int, unsigned int, unsigned long long const*, unsigned int, unsigned char const*, unsigned long)",
+ "A436": "IOMobileFramebufferAP::get_block_dcp(task*, unsigned int, unsigned int, unsigned long long const*, unsigned int, unsigned char*, unsigned long) const",
+ "A438": "IOMobileFramebufferAP::swap_set_color_matrix(IOMFBColorFixedMatrix*, IOMFBColorMatrixFunction, unsigned int)",
+ "A439": "IOMobileFramebufferAP::set_parameter_dcp(IOMFBParameterName, unsigned long long const*, unsigned int)",
+ "A440": "IOMobileFramebufferAP::display_width() const",
+ "A441": "IOMobileFramebufferAP::display_height() const",
+ "A442": "IOMobileFramebufferAP::get_display_size(unsigned int*, unsigned int*) const",
+ "A443": "IOMobileFramebufferAP::do_create_default_frame_buffer() const",
+ "A444": "IOMobileFramebufferAP::printRegs()",
+ "A445": "IOMobileFramebufferAP::enable_disable_dithering(unsigned int)",
+ "A446": "IOMobileFramebufferAP::set_underrun_color(unsigned int)",
+ "A447": "IOMobileFramebufferAP::enable_disable_video_power_savings(unsigned int)",
+ "A448": "IOMobileFramebufferAP::set_video_dac_gain(unsigned int)",
+ "A449": "IOMobileFramebufferAP::set_line21_data(unsigned int)",
+ "A450": "IOMobileFramebufferAP::enableInternalToExternalMirroring(bool)",
+ "A451": "IOMobileFramebufferAP::getExternalMirroringCapability(IOMFBMirroringCapability*)",
+ "A452": "IOMobileFramebufferAP::setRenderingAngle(float*)",
+ "A453": "IOMobileFramebufferAP::setOverscanSafeRegion(IOMFBOverscanSafeRect*)",
+ "A454": "IOMobileFramebufferAP::first_client_open()",
+ "A455": "IOMobileFramebufferAP::last_client_close_dcp(unsigned int*)",
+ "A456": "IOMobileFramebufferAP::writeDebugInfo(unsigned long)",
+ "A457": "IOMobileFramebufferAP::flush_debug_flags(unsigned int)",
+ "A458": "IOMobileFramebufferAP::io_fence_notify(unsigned int, unsigned int, unsigned long long, IOMFBStatus)",
+ "A459": "IOMobileFramebufferAP::swap_wait_dcp(bool, unsigned int, unsigned int, unsigned int)",
+ "A460": "IOMobileFramebufferAP::setDisplayRefreshProperties()",
+ "A461": "IOMobileFramebufferAP::exportProperty(unsigned int, unsigned int)",
+ "A462": "IOMobileFramebufferAP::applyProperty(unsigned int, unsigned int)",
+ "A463": "IOMobileFramebufferAP::flush_supportsPower(bool)",
+ "A464": "IOMobileFramebufferAP::abort_swaps_dcp(IOMobileFramebufferUserClient*)",
+ "A465": "IOMobileFramebufferAP::swap_signal_gated(unsigned int, unsigned int)",
+ "A466": "IOMobileFramebufferAP::update_dfb(IOSurface*, unsigned int, unsigned int, unsigned long long)",
+ "A467": "IOMobileFramebufferAP::update_dfb(IOSurface*)",
+ "A468": "IOMobileFramebufferAP::setPowerState(unsigned long, bool, unsigned int*)",
+ "A469": "IOMobileFramebufferAP::isKeepOnScreen() const",
+ "A470": "IOMobileFramebufferAP::resetStats()",
+ "A471": "IOMobileFramebufferAP::set_has_frame_swap_function(bool)",
+ "A472": "IOMobileFramebufferAP::getPerformanceStats(unsigned int*, unsigned int*)",
+
+ "D000": "bool IOMFB::UPPipeAP_H13P::did_boot_signal()",
+ "D001": "bool IOMFB::UPPipeAP_H13P::did_power_on_signal()",
+ "D002": "void IOMFB::UPPipeAP_H13P::will_power_off_signal()",
+ "D003": "void IOMFB::UPPipeAP_H13P::rt_bandwidth_setup_ap(inout rt_bw_config_t*)",
+ "D004": "void IOMFB::UPPipeAP_H13P::mcc_report_replay(bool, unsigned int)",
+ "D005": "void IOMFB::UPPipeAP_H13P::mcc_report_bics(bool, unsigned int)",
+
+ "D100": "void UnifiedPipeline2::match_pmu_service()",
+ #"D101": "", # get some uint32_t, inlined
+ "D102": "void UnifiedPipeline2::set_number_property(char const*, unsigned int)",
+ "D103": "void UnifiedPipeline2::set_boolean_property(char const*, bool)",
+ "D104": "void UnifiedPipeline2::set_string_property(char const*, char const*)",
+ "D105": "IOReturn IOService::acknowledgeSetPowerState()",
+ "D106": "void IORegistryEntry::removeProperty(char const*)",
+ "D107": "bool UnifiedPipeline2::create_provider_service",
+ "D108": "bool UnifiedPipeline2::create_product_service()",
+ "D109": "bool UnifiedPipeline2::create_PMU_service()",
+ "D110": "bool UnifiedPipeline2::create_iomfb_service()",
+ "D111": "bool UnifiedPipeline2::create_backlight_service()",
+ "D112": "void UnifiedPipeline2::set_idle_caching_state_ap(IdleCachingState, unsigned int)",
+ "D113": "bool UnifiedPipeline2::upload_trace_start(IOMFB::FrameInfoBuffer::FrameInfoConstants const*)",
+ "D114": "bool UnifiedPipeline2::upload_trace_chunk(IOMFB::FrameInfoBuffer::FrameInfoData const*, unsigned int, unsigned int)",
+ "D115": "bool UnifiedPipeline2::upload_trace_end(char const*)",
+ "D116": "bool UnifiedPipeline2::start_hardware_boot()",
+ "D117": "bool UnifiedPipeline2::is_dark_boot()",
+ "D118": "bool UnifiedPipeline2::is_waking_from_hibernate()",
+ "D119": "bool UnifiedPipeline2::detect_fastsim()",
+ "D120": "bool UnifiedPipeline2::read_edt_data(char const*, unsigned int, unsigned int*) const",
+ "D121": "bool UnifiedPipeline2::read_edt_string(char const*, char*, unsigned int*) const",
+ "D122": "bool UnifiedPipeline2::setDCPAVPropStart(unsigned int)",
+ "D123": "bool UnifiedPipeline2::setDCPAVPropChunk(unsigned char const*, unsigned int, unsigned int)",
+ "D124": "bool UnifiedPipeline2::setDCPAVPropEnd(char const*)",
+
+ "D200": "uint64_t IOMFB::UPPipe2::get_default_idle_caching_method()",
+ "D201": "IOMFBStatus IOMFB::UPPipe2::map_buf(IOMFB::BufferDescriptor*, unsigned long*, unsigned long long*, bool)",
+ "D202": "void IOMFB::UPPipe2::unmap_buf(IOMFB::BufferDescriptor*, unsigned long, unsigned long long, bool)",
+ "D203": "bool IOMFB::UPPipe2::aot_supported_peek()",
+ "D204": "uint64_t IOMFB::UPPipe2::get_ideal_screen_space()",
+ "D205": "bool IOMFB::UPPipe2::read_carveout(unsigned char*, unsigned int, unsigned int) const",
+ "D206": "bool IOMFB::UPPipe2::match_pmu_service()",
+ "D207": "bool IOMFB::UPPipe2::match_backlight_service()",
+ "D208": "uint64_ IOMFB::UPPipe2::get_calendar_time_ms()",
+ "D209": "void IOMFB::UPPipe2::plc_enable(bool)",
+ "D210": "void IOMFB::UPPipe2::plc_init()",
+ "D211": "void IOMFB::UPPipe2::update_backlight_factor_prop(int)",
+
+ "D300": "void IOMFB::PropRelay::publish(IOMFB::RuntimeProperty, unsigned int)",
+
+ "D400": "void IOMFB::ServiceRelay::get_property(unsigned int, in char const[0x40], out unsigned char[0x200], inout unsigned int*)",
+ "D401": "bool IOMFB::ServiceRelay::get_uint_prop(unsigned int, in char const[0x40], inout unsigned long long*)",
+ "D402": "void IOMFB::ServiceRelay::set_uint_prop(unsigned int, in char const[0x40], unsigned long long)",
+ "D403": "bool IOMFB::ServiceRelay::get_uint_prop(unsigned int, in char const[0x40], inout unsigned int*)",
+ "D404": "void IOMFB::ServiceRelay::set_uint_prop(unsigned int, in char const[0x40], unsigned int)",
+ "D405": "bool IOMFB::ServiceRelay::get_fx_prop(unsigned int, in char const[0x40], inout int*)",
+ "D406": "void IOMFB::ServiceRelay::set_fx_prop(unsigned int, in char const[0x40], int)",
+ "D407": "void IOMFB::ServiceRelay::set_bool_prop(unsigned int, in char const[0x40], bool)",
+ "D408": "unsigned long long IOMFB::ServiceRelay::getClockFrequency(unsigned int, unsigned int)",
+ "D409": "IOMFBStatus IOMFB::ServiceRelay::enableDeviceClock(unsigned int, unsigned int, unsigned int)",
+ "D410": "IOMFBStatus IOMFB::ServiceRelay::enableDevicePower(unsigned int, unsigned int, inout unsigned int*, unsigned int)",
+ "D411": "IOMFBStatus IOMFB::ServiceRelay::mapDeviceMemoryWithIndex(unsigned int, unsigned int, unsigned int, inout unsigned long*, inout unsigned long long*)",
+ "D412": "bool IOMFB::ServiceRelay::setProperty(unsigned int, OSString<0x40> const*, OSArray const*)",
+ "D413": "bool IOMFB::ServiceRelay::setProperty(unsigned int, OSString<0x40> const*, OSDictionary const*)",
+ "D414": "bool IOMFB::ServiceRelay::setProperty(unsigned int, OSString<0x40> const*, OSNumber const*)",
+ "D415": "bool IOMFB::ServiceRelay::setProperty(unsigned int, OSString<0x40> const*, OSBoolean const*)",
+ "D416": "bool IOMFB::ServiceRelay::setProperty(unsigned int, OSString<0x40> const*, OSString const*)",
+ "D417": "bool IOMFB::ServiceRelay::setProperty(unsigned int, char const[0x40], OSArray const*)",
+ "D418": "bool IOMFB::ServiceRelay::setProperty(unsigned int, char const[0x40], OSDictionary const*)",
+ "D419": "bool IOMFB::ServiceRelay::setProperty(unsigned int, char const[0x40], OSNumber const*)",
+ "D420": "bool IOMFB::ServiceRelay::setProperty(unsigned int, char const[0x40], OSBoolean const*)",
+ "D421": "bool IOMFB::ServiceRelay::setProperty(unsigned int, char const[0x40], OSString const*)",
+ "D422": "bool IOMFB::ServiceRelay::setProperty(unsigned int, char const[0x40], bool)",
+ "D423": "void IOMFB::ServiceRelay::removeProperty(unsigned int, char const[0x40])",
+ "D424": "void IOMFB::ServiceRelay::removeProperty(unsigned int, OSString<0x40> const*)",
+
+ "D450": "bool IOMFB::MemDescRelay::from_id(unsigned int, unsigned long*, unsigned long*, unsigned long long*)",
+ "D451": "MemDescRelay::desc_id_t IOMFB::MemDescRelay::allocate_buffer(unsigned int, unsigned long long, unsigned int, unsigned long*, unsigned long*, unsigned long long*)",
+ "D452": "MemDescRelay::desc_id_t IOMFB::MemDescRelay::map_physical(unsigned long long, unsigned long long, unsigned int, unsigned long*, unsigned long long*)",
+ "D453": "MemDescRelay::desc_id_t IOMFB::MemDescRelay::withAddressRange(unsigned long long, unsigned long long, unsigned int, task*, unsigned long*, unsigned long long*)",
+ "D454": "IOMFBStatus IOMFB::MemDescRelay::prepare(unsigned int, unsigned int)",
+ "D455": "IOMFBStatus IOMFB::MemDescRelay::complete(unsigned int, unsigned int)",
+ "D456": "bool IOMFB::MemDescRelay::release_descriptor(unsigned int)",
+
+ "D500": "IOMFBStatus IOMFB::PlatformFunctionRelay::allocate_record(unsigned int, char const*, unsigned int, bool)",
+ "D501": "IOMFBStatus IOMFB::PlatformFunctionRelay::release_record(unsigned int)",
+ "D502": "IOMFBStatus IOMFB::PlatformFunctionRelay::callFunctionLink(unsigned int, unsigned long, unsigned long, unsigned long)",
+
+ "D550": "bool IORegistryEntry::setProperty(OSString *, OSArray *)",
+ "D551": "bool IORegistryEntry::setProperty(OSString *, IOMFB::AFKArray *)",
+ "D552": "bool IORegistryEntry::setProperty(OSString *, OSDictionary *)",
+ "D553": "bool IORegistryEntry::setProperty(OSString *, IOMFB::AFKDictionary *)",
+ "D554": "bool IORegistryEntry::setProperty(OSString *, OSNumber *)",
+ "D555": "bool IORegistryEntry::setProperty(OSString *, IOMFB::AFKNumber *)",
+ "D556": "bool IORegistryEntry::setProperty(OSString *, OSBoolean *)",
+ "D557": "bool IORegistryEntry::setProperty(OSString *, OSString *)",
+ "D558": "bool IORegistryEntry::setProperty(OSString *, IOMFB::AFKString *)",
+ "D559": "bool IORegistryEntry::setProperty(char const*, OSArray *)",
+ "D560": "bool IORegistryEntry::setProperty(char const*, IOMFB::AFKArray *)",
+ "D561": "bool IORegistryEntry::setProperty(char const*, OSDictionary *)",
+ "D562": "bool IORegistryEntry::setProperty(char const*, IOMFB::AFKDictionary *)",
+ "D563": "bool IORegistryEntry::setProperty(char const*, OSNumber *)",
+ "D564": "bool IORegistryEntry::setProperty(char const*, IOMFB::AFKNumber *)",
+ "D565": "bool IORegistryEntry::setProperty(char const*, OSBoolean *)",
+ "D566": "bool IORegistryEntry::setProperty(char const*, OSString *)",
+ "D567": "bool IORegistryEntry::setProperty(char const*, IOMFB::AFKString *)",
+ "D568": "bool IORegistryEntry::setProperty(char const*, char const*)",
+ "D569": "bool IORegistryEntry::setProperty(char const*, bool)",
+ "D570": "IOMFBStatus IOMobileFramebufferAP::setProperties(OSDictionary*)",
+ "D571": "void IOMobileFramebufferAP::swapping_client_did_start(IOMobileFramebufferUserClient*)",
+ "D572": "void IOMobileFramebufferAP::swapping_client_will_stop(IOMobileFramebufferUserClient*)",
+ "D573": "IOMFBStatus IOMobileFramebufferAP::set_canvas_size(unsigned int, unsigned int)",
+ "D574": "IOMFBStatus IOMobileFramebufferAP::powerUpDART(bool)",
+ "D575": "IOMFBStatus IOMobileFramebufferAP::get_dot_pitch(unsigned int*)",
+ "D576": "void IOMobileFramebufferAP::hotPlug_notify_gated(unsigned long long)",
+ "D577": "void IOMobileFramebufferAP::powerstate_notify(bool, bool)",
+ "D578": "bool IOMobileFramebufferAP::idle_fence_create(IdleCachingState)",
+ "D579": "void IOMobileFramebufferAP::idle_fence_complete()",
+ "D580": "void IOMobileFramebufferAP::idle_surface_release_ap()",
+ "D581": "void IOMobileFramebufferAP::swap_complete_head_of_line(unsigned int, bool, unsigned int, bool)",
+ "D582": "bool IOMobileFramebufferAP::create_default_fb_surface(unsigned int, unsigned int)",
+ "D583": "bool IOMobileFramebufferAP::serializeDebugInfoCb(unsigned long, IOMFB::BufferDescriptor const*, unsigned int)",
+ "D584": "void IOMobileFramebufferAP::clear_default_surface()",
+ "D585": "void IOMobileFramebufferAP::swap_notify_gated(unsigned long long, unsigned long long, unsigned long long)",
+ "D586": "void IOMobileFramebufferAP::swap_info_notify_dispatch(SwapInfoBlob const*)",
+ "D587": "void IOMFBStatus IOMobileFramebufferAP::updateBufferMappingCount_gated(bool)",
+ "D588": "void IOMobileFramebufferAP::resize_default_fb_surface_gated()",
+ "D589": "void IOMobileFramebufferAP::swap_complete_ap_gated(unsigned int, bool, SwapCompleteData const*, SwapInfoBlob const*, unsigned int)",
+ "D590": "void IOMobileFramebufferAP::batched_swap_complete_ap_gated(unsigned int*, unsigned int, bool, bool const*, SwapCompleteData const*)",
+ "D591": "void IOMobileFramebufferAP::swap_complete_intent_gated(unsigned int, bool, IOMFBSwapRequestIntent, unsigned int, unsigned int)",
+ "D592": "void IOMobileFramebufferAP::abort_swap_ap_gated(unsigned int)",
+ "D593": "void IOMobileFramebufferAP::enable_backlight_message_ap_gated(bool)",
+ "D594": "void IOMobileFramebufferAP::setSystemConsoleMode(bool)",
+ "D595": "void IOMobileFramebufferAP::setSystemConsoleMode_gated(bool)",
+ "D596": "bool IOMobileFramebufferAP::isDFBAllocated()",
+ "D597": "bool IOMobileFramebufferAP::preserveContents()",
+ "D598": "void IOMobileFramebufferAP::find_swap_function_gated()",
+
+ "D700": "int IOMFB::DCPPowerManager::set_kernel_power_assert(bool, bool)",
+}
+
+# iboot interface
+"""
+0: setResource
+1: setSurface
+2: setPower
+3: getHpdStatus
+4: getTimingModes
+5: getColorModes
+6: setMode
+7: setBrightness
+8: rwBCONRegsRequest
+9: setParameter
+10: setMatrix
+11: setProperty
+12: getProperty
+13: setBlock
+14: getBlock
+15: swapBegin
+16: setSwapLayer
+17: setSwapTimestamp
+18: setSwapEnd
+19: setSwapWait
+20: setBrightnessCfg
+21: setNamedProperty
+22: getNamedProperty
+"""
+
+from m1n1.fw.dcp.dcpep import DCPMessage, DCPEp_SetShmem, CallContext, DCPEp_Msg
+
+class DCPCallState:
+ pass
+
+class DCPCallChannel(Reloadable):
+ def __init__(self, dcpep, name, buf, bufsize):
+ self.dcpep = dcpep
+ self.name = name
+ self.buf = buf
+ self.bufsize = bufsize
+ self.log = self.dcpep.log
+ self.state = self.dcpep.state
+
+ def call(self, msg, dir):
+ ident = f"{self.name}.{msg.OFF:x}"
+
+ if any(msg.OFF == s.off for s in self.state.ch.get(self.name, [])):
+ self.log(f"{dir}{self.name}.{msg.OFF:x} !!! Overlapping call ({msg})")
+ assert False
+
+ state = DCPCallState()
+
+ data = self.dcpep.dart.ioread(self.dcpep.stream, self.state.shmem_iova + self.buf + msg.OFF, msg.LEN)
+ tag = data[:4][::-1].decode("ascii")
+ in_len, out_len = struct.unpack("<II", data[4:12])
+ data_in = data[12:12 + in_len]
+
+ state.off = msg.OFF
+ state.tag = tag
+ state.in_len = in_len
+ state.out_addr = self.buf + msg.OFF + 12 + in_len
+ state.out_len = out_len
+
+ verb = self.dcpep.get_verbosity(tag)
+ if verb >= 1:
+ self.log(f"{dir}{self.name}.{msg.OFF:x} {tag}:{KNOWN_MSGS.get(tag, 'unk')} ({msg})")
+ if verb >= 2:
+ print(f"Message: {tag} ({KNOWN_MSGS.get(tag, 'unk')}): (in {in_len:#x}, out {out_len:#x})")
+ if data_in:
+ print(f"{dir} Input ({len(data_in):#x} bytes):")
+ chexdump(data_in[:self.state.max_len])
+
+ #if tag not in KNOWN_MSGS:
+ #hv.run_shell()
+
+ if self.state.dumpfile:
+ dump = f"CALL {dir} {msg.value:#018x} {self.name} {state.off:#x} {state.tag} {in_len:#x} {out_len:#x} {data_in.hex()}\n"
+ self.state.dumpfile.write(dump)
+ self.state.dumpfile.flush()
+
+ self.state.ch.setdefault(self.name, []).append(state)
+
+ def ack(self, msg, dir):
+ assert msg.LEN == 0
+
+ states = self.state.ch.get(self.name, None)
+ if not states:
+ self.log(f"{dir}ACK {self.name}.{msg.OFF:x} !!! ACK without call ({msg})")
+ return
+
+ state = states[-1]
+
+ if self.state.show_acks:
+ self.log(f"{dir}ACK {self.name}.{msg.OFF:x} ({msg})")
+
+ data_out = self.dcpep.dart.ioread(self.dcpep.stream, self.state.shmem_iova + state.out_addr, state.out_len)
+
+ verb = self.dcpep.get_verbosity(state.tag)
+ if verb >= 3 and state.out_len > 0:
+ print(f"{dir}{self.name}.{msg.OFF:x} Output buffer ({len(data_out):#x} bytes):")
+ chexdump(data_out[:self.state.max_len])
+
+ if self.state.dumpfile:
+ dump = f"ACK {dir} {msg.value:#018x} {self.name} {state.off:#x} {data_out.hex()}\n"
+ self.state.dumpfile.write(dump)
+ self.state.dumpfile.flush()
+
+ states.pop()
+
+class DCPEp(EP):
+ BASE_MESSAGE = DCPMessage
+
+ def __init__(self, tracer, epid):
+ super().__init__(tracer, epid)
+ self.state.shmem_iova = None
+ self.state.show_globals = True
+ self.state.show_acks = True
+ self.state.max_len = 1024 * 1024
+ self.state.verbosity = 3
+ self.state.op_verb = {}
+ self.state.ch = {}
+ self.state.dumpfile = None
+
+ self.ch_cb = DCPCallChannel(self, "CB", 0x60000, 0x8000)
+ self.ch_cmd = DCPCallChannel(self, "CMD", 0, 0x8000)
+ self.ch_async = DCPCallChannel(self, "ASYNC", 0x40000, 0x20000)
+ self.ch_oobcb = DCPCallChannel(self, "OOBCB", 0x68000, 0x8000)
+ self.ch_oobcmd = DCPCallChannel(self, "OOBCMD", 0x8000, 0x8000)
+
+ self.cmd_ch = {
+ CallContext.CB: self.ch_cmd,
+ CallContext.CMD: self.ch_cmd,
+ CallContext.ASYNC: None, # unknown
+ CallContext.OOBCB: self.ch_oobcmd,
+ CallContext.OOBCMD: self.ch_oobcmd,
+ }
+
+ self.cb_ch = {
+ CallContext.CB: self.ch_cb,
+ CallContext.CMD: None,
+ CallContext.ASYNC: self.ch_async,
+ CallContext.OOBCB: self.ch_oobcb,
+ CallContext.OOBCMD: None,
+ }
+
+ def start(self):
+ self.add_mon()
+
+ def add_mon(self):
+ if self.state.shmem_iova and self.state.show_globals:
+ addr = self.state.shmem_iova + 0x80000
+ iomon.add(addr, 128,
+ name=f"{self.name}.shmem@{addr:08x}", offset=addr)
+
+ #addr = self.state.shmem_iova
+ #iomon.add(addr, 0x80080,
+ #name=f"{self.name}.shmem@{addr:08x}", offset=addr)
+
+ InitComplete = msg_log(1, DIR.RX)
+
+ @msg(0, DIR.TX, DCPEp_SetShmem)
+ def SetShmem(self, msg):
+ self.log(f"Shared memory DVA: {msg.DVA:#x}")
+ self.state.shmem_iova = msg.DVA & 0xfffffffff
+ self.add_mon()
+
+ @msg(2, DIR.TX, DCPEp_Msg)
+ def Tx(self, msg):
+ if msg.ACK:
+ self.cb_ch[msg.CTX].ack(msg, ">")
+ else:
+ self.cmd_ch[msg.CTX].call(msg, ">")
+
+ if self.state.show_globals:
+ iomon.poll()
+
+ return True
+
+ @msg(2, DIR.RX, DCPEp_Msg)
+ def Rx(self, msg):
+ self.log(msg)
+ if msg.ACK:
+ self.cmd_ch[msg.CTX].ack(msg, "<")
+ else:
+ self.cb_ch[msg.CTX].call(msg, "<")
+
+ if self.state.show_globals:
+ iomon.poll()
+
+ return True
+
+ def get_verbosity(self, tag):
+ return self.state.op_verb.get(tag, self.state.verbosity)
+
+ def set_verb_known(self, verb):
+ for i in KNOWN_MSGS:
+ if verb is None:
+ self.state.op_verb.pop(i, None)
+ else:
+ self.state.op_verb[i] = verb
+
+class SystemService(EPICEp):
+ NAME = "system"
+
+class TestService(EPICEp):
+ NAME = "test"
+
+class DCPExpertService(EPICEp):
+ NAME = "dcpexpert"
+
+class Disp0Service(EPICEp):
+ NAME = "disp0"
+
+class DCPAVControllerEpicTracer(EPICServiceTracer):
+ NAME = "dcpav-controller-epic"
+
+ @epic_service_cmd(0, 14)
+ def getParticipatesPowerManagement(self, data):
+ self.log("> getParticipatesPowerManagement")
+ @epic_service_reply(0, 14)
+ def getParticipatesPowerManagement_reply(self, data):
+ self.log("< getParticipatesPowerManagement")
+ chexdump(data, print_fn=self.log)
+
+class DPAVController(EPICEp):
+ NAME = "dpavctrl"
+ SERVICES = [
+ DCPAVControllerEpicTracer
+ ]
+
+class DPSACService(EPICEp):
+ NAME = "dpsac"
+
+
+class DCPDPDeviceEpicTracer(EPICServiceTracer):
+ NAME = "dcpdp-device-epic"
+
+ @epic_service_cmd(0, 15)
+ def getDeviceMatchingData(self, data):
+ self.log("> getDeviceMatchingData")
+ @epic_service_reply(0, 15)
+ def getDeviceMatchingData_reply(self, data):
+ self.log("< getDeviceMatchingData")
+ chexdump(data, print_fn=self.log)
+
+class DPDevService(EPICEp):
+ NAME = "dpdev"
+ SERVICES = [
+ DCPDPDeviceEpicTracer
+ ]
+
+class DPAVService(EPICEp):
+ NAME = "dpavserv"
+
+class DCPAVAudioInterfaceEpicTracer(EPICServiceTracer):
+ NAME = "dcpav-audio-interface-epic"
+
+ # usually 4, 6 but apparently also 0, 6 here?
+ # or maybe a different open?
+ @epic_service_cmd(0, 6)
+ def open2(self, data):
+ self.log("> open")
+ @epic_service_reply(0, 6)
+ def open2_reply(self, data):
+ self.log("< open")
+ chexdump(data, print_fn=self.log)
+
+ @epic_service_cmd(0, 8)
+ def prepareLink(self, data):
+ self.log("> prepareLink")
+ @epic_service_reply(0, 8)
+ def prepareLink_reply(self, data):
+ self.log("< prepareLink")
+ chexdump(data, print_fn=self.log)
+
+ @epic_service_cmd(0, 9)
+ def startLink(self, data):
+ self.log("> startLink")
+ @epic_service_reply(0, 9)
+ def startLink_reply(self, data):
+ self.log("< startLink")
+ chexdump(data, print_fn=self.log)
+
+ @epic_service_cmd(0, 15)
+ def getLinkStatus(self, data):
+ self.log("> getLinkStatus")
+ @epic_service_reply(0, 15)
+ def getLinkStatus_reply(self, data):
+ self.log("< getLinkStatus")
+ chexdump(data, print_fn=self.log)
+
+ @epic_service_cmd(0, 16)
+ def getTransport(self, data):
+ self.log("> getTransport")
+ @epic_service_reply(0, 16)
+ def getTransport_reply(self, data):
+ self.log("< getTransport")
+ chexdump(data, print_fn=self.log)
+
+ @epic_service_cmd(0, 17)
+ def getPortID(self, data):
+ self.log("> getPortID")
+ @epic_service_reply(0, 17)
+ def getPortID_reply(self, data):
+ self.log("< getPortID")
+ chexdump(data, print_fn=self.log)
+
+ @epic_service_cmd(1, 18)
+ def getElements(self, data):
+ self.log("> getElements")
+ @epic_service_reply(1, 18)
+ def getElements_reply(self, data):
+ self.log("< getElements")
+ chexdump(data, print_fn=self.log)
+
+ @epic_service_cmd(1, 20)
+ def getProductAttributes(self, data):
+ self.log("> getProductAttributes")
+ @epic_service_reply(1, 20)
+ def getProductAttributes_reply(self, data):
+ self.log("< getProductAttributes")
+ chexdump(data, print_fn=self.log)
+
+ @epic_service_cmd(1, 21)
+ def getEDIDUUID(self, data):
+ self.log("> getEDIDUUID")
+ @epic_service_reply(1, 21)
+ def getEDIDUUID_reply(self, data):
+ self.log("< getEDIDUUID")
+ chexdump(data, print_fn=self.log)
+
+ @epic_service_cmd(0, 22)
+ def getDataLatency(self, data):
+ self.log("> getDataLatency")
+ @epic_service_reply(0, 22)
+ def getDataLatency_reply(self, data):
+ self.log("< getDataLatency")
+ chexdump(data, print_fn=self.log)
+
+
+class AVService(EPICEp):
+ NAME = "av"
+ SERVICES = [
+ DCPAVAudioInterfaceEpicTracer
+ ]
+
+class DCPDPTXHDCPAuthSessionTracer(EPICServiceTracer):
+ NAME = "dcpdptx-hdcp-auth-session"
+
+ @epic_service_cmd(4, 8)
+ def getProtocol(self, data):
+ self.log("> getProtocol")
+ @epic_service_reply(4, 8)
+ def getProtocol_reply(self, data):
+ self.log("< getProtocol")
+ chexdump(data, print_fn=self.log)
+
+class HDCPService(EPICEp):
+ NAME = "hdcp"
+ SERVICES = [
+ DCPDPTXHDCPAuthSessionTracer
+ ]
+
+class RemoteAllocService(EPICEp):
+ NAME = "remotealloc"
+
+class DCPDPTXRemotePortTarget(Register32):
+ CORE = 3, 0
+ ATC = 7, 4
+ DIE = 11, 8
+ CONNECTED = 15, 15
+
+class DCPDPTXPortEpicTracer(EPICServiceTracer):
+ NAME = "dcpdptx-port-epic"
+
+ @epic_service_cmd(0, 8)
+ def setPowerState(self, data):
+ self.log("> setPowerState")
+ @epic_service_reply(0, 8)
+ def setPowerState_reply(self, data):
+ self.log("< setPowerState")
+
+ @epic_service_cmd(0, 13)
+ def connectTo(self, data):
+ unk1, target = struct.unpack("<II24x", data)
+ target = DCPDPTXRemotePortTarget(target)
+ self.log(f"> connectTo(target={target}, unk1=0x{unk1:x})")
+ @epic_service_reply(0, 13)
+ def connectTo_reply(self, data):
+ unk1, target = struct.unpack("<II24x", data)
+ target = DCPDPTXRemotePortTarget(target)
+ self.log(f"< connectTo(target={target}, unk1=0x{unk1:x})")
+
+ @epic_service_cmd(0, 14)
+ def validateConnection(self, data):
+ unk1, target = struct.unpack("<II40x", data)
+ target = DCPDPTXRemotePortTarget(target)
+ self.log(f"> validateConnection(target={target}, unk1=0x{unk1:x})")
+ @epic_service_reply(0, 14)
+ def validateConnection_reply(self, data):
+ unk1, target = struct.unpack("<II40x", data)
+ target = DCPDPTXRemotePortTarget(target)
+ self.log(f"< validateConnection(target={target}, unk1=0x{unk1:x})")
+
+ @epic_service_cmd(8, 10)
+ def hotPlugDetectChangeOccurred(self, data):
+ unk = struct.unpack("<16x?15x", data)[0]
+ self.log(f"> hotPlugDetectChangeOccurred(unk={unk})")
+ @epic_service_reply(8, 10)
+ def hotPlugDetectChangeOccurred_reply(self, data):
+ unk = struct.unpack("<16x?15x", data)[0]
+ self.log(f"< hotPlugDetectChangeOccurred(unk={unk})")
+
+class DPTXPortService(EPICEp):
+ NAME = "dptxport"
+ SERVICES = [
+ DCPDPTXPortEpicTracer
+ ]
+
+class DCPTracer(ASCTracer):
+ ENDPOINTS = {
+ 0x20: SystemService,
+ 0x21: TestService,
+ 0x22: DCPExpertService,
+ # Disp0 / DCP iboot as used by m1n1 is incompatible with the generic
+ # EPICEp tracer, disable it for now
+ #0x23: Disp0Service,
+ 0x24: DPAVController,
+ 0x25: EPICEp, # dcpav-power-ep
+ 0x26: DPSACService,
+ 0x27: DPDevService,
+ 0x28: DPAVService,
+ 0x29: AVService,
+ 0x2a: DPTXPortService, # dcpdptx-port-ep
+ 0x2b: HDCPService,
+ 0x2c: EPICEp, # cb-ap-to-dcp-service-ep
+ 0x2d: RemoteAllocService,
+ 0x37: DCPEp, # iomfb-link
+ }
+
+ def handle_msg(self, direction, r0, r1):
+ super().handle_msg(direction, r0, r1)
+ #iomon.poll()
+
+
+dcp_sid = u.adt[dcp_dart_mapper_adt_path].reg
+
+dart_dcp_tracer = DARTTracer(hv, dcp_dart_adt_path)
+dart_dcp_tracer.start()
+
+dart_disp0_tracer = DARTTracer(hv, disp0_dart_adt_path)
+dart_disp0_tracer.start()
+
+def readmem_iova(addr, size, readfn):
+ try:
+ return dart_dcp_tracer.dart.ioread(dcp_sid, addr, size)
+ except Exception as e:
+ print(e)
+ return None
+
+iomon.readmem = readmem_iova
+
+dcp_tracer = DCPTracer(hv, dcp_adt_path, verbose=1)
+dcp_tracer.start(dart_dcp_tracer.dart, stream=dcp_sid)
+
+#dcp_tracer.ep.dcpep.state.dumpfile = open("dcp.log", "a")
diff --git a/tools/proxyclient/hv/trace_gpio.py b/tools/proxyclient/hv/trace_gpio.py
new file mode 100644
index 0000000..4560bf0
--- /dev/null
+++ b/tools/proxyclient/hv/trace_gpio.py
@@ -0,0 +1,40 @@
+# SPDX-License-Identifier: MIT
+
+from m1n1.trace.gpio import GPIOTracer
+
+#trace_device("/arm-io/gpio", True)
+
+# trace gpio interrups, useful to follow the cascaded interrupts
+aic_phandle = getattr(hv.adt["/arm-io/aic"], "AAPL,phandle")
+try:
+ node = hv.adt["/arm-io/gpio0"]
+ path = "/arm-io/gpio0"
+except:
+ node = hv.adt["/arm-io/gpio"]
+ path = "/arm-io/gpio"
+
+if getattr(node, "interrupt-parent") == aic_phandle:
+ for irq in getattr(node, "interrupts"):
+ hv.trace_irq(node.name, irq, 1, hv.IRQTRACE_IRQ)
+
+PIN_NAMES_j274 = {
+ 0xC0: "i2c0:scl",
+ 0xBC: "i2c0:sda",
+ 0xC9: "i2c1:scl",
+ 0xC7: "i2c1:sda",
+ 0xA3: "i2c2:scl",
+ 0xA2: "i2c2:sda",
+ 106: "hpm:irq",
+ 136: "bluetooth:irq",
+ 196: "wlan:irq",
+ 183: "cs42l83:irq",
+ 182: "tas5770:irq",
+ 152: "pci@0,0",
+ 153: "pci@1,0",
+ 33: "pci@2,0",
+ #0x2D: "spi_nor:CS",
+}
+
+GPIOTracer = GPIOTracer._reloadcls()
+gpio_tracer = GPIOTracer(hv, path, PIN_NAMES_j274, verbose=0)
+gpio_tracer.start()
diff --git a/tools/proxyclient/hv/trace_i2c.py b/tools/proxyclient/hv/trace_i2c.py
new file mode 100644
index 0000000..f64fd24
--- /dev/null
+++ b/tools/proxyclient/hv/trace_i2c.py
@@ -0,0 +1,41 @@
+# SPDX-License-Identifier: MIT
+
+from m1n1.trace.i2c import I2CTracer
+
+I2CTracer = I2CTracer._reloadcls()
+
+i2c_tracers = {}
+
+for node in hv.adt["/arm-io"]:
+ if node.name.startswith("i2c"):
+ n = int(node.name[3:])
+ i2c_tracers[n] = I2CTracer(hv, f"/arm-io/i2c{n}", verbose=0)
+ i2c_tracers[n].stop()
+ i2c_tracers[n].start()
+ if hv.ctx:
+ for irq in getattr(node, "interrupts"):
+ hv.trace_irq(node.name, irq, 1, hv.IRQTRACE_IRQ)
+
+from m1n1.gpiola import GPIOLogicAnalyzer
+
+if not hv.started:
+ for cpu in list(hv.adt["cpus"]):
+ if cpu.name == "cpu3":
+ print(f"Removing ADT node {cpu._path}")
+ del hv.adt["cpus"][cpu.name]
+
+if not hv.started or hv.ctx is not None:
+ m = GPIOLogicAnalyzer(u, "arm-io/gpio",
+ pins={"scl": 0xc9, "sda": 0xc7},
+ div=1, on_pin_change=True, cpu=3)
+
+ m.load_regmap(list(i2c_tracers[1].regmaps.values())[0],
+ regs={"SMSTA", "XFSTA"})
+
+def start_la():
+ m.start(1000000, bufsize=0x80000)
+ hv.cont()
+
+def stop_la():
+ m.complete()
+ m.show()
diff --git a/tools/proxyclient/hv/trace_isp.py b/tools/proxyclient/hv/trace_isp.py
new file mode 100644
index 0000000..b930095
--- /dev/null
+++ b/tools/proxyclient/hv/trace_isp.py
@@ -0,0 +1,5 @@
+from m1n1.trace.isp import ISPTracer
+
+hv.log('ISP: Registering ISP ASC tracer...')
+isp_asc_tracer = ISPTracer(hv, "/arm-io/isp", "/arm-io/dart-isp", verbose=4)
+isp_asc_tracer.start() \ No newline at end of file
diff --git a/tools/proxyclient/hv/trace_keyboard.py b/tools/proxyclient/hv/trace_keyboard.py
new file mode 100644
index 0000000..d8cae66
--- /dev/null
+++ b/tools/proxyclient/hv/trace_keyboard.py
@@ -0,0 +1,206 @@
+# SPDX-License-Identifier: MIT
+
+import struct
+from construct import *
+
+from m1n1.hv import TraceMode
+from m1n1.proxyutils import RegMonitor
+from m1n1.utils import *
+
+from m1n1.trace import ADTDevTracer
+from m1n1.trace.asc import ASCTracer, ASCRegs, EP, EPState, msg, msg_log, DIR
+from m1n1.trace.dart import DARTTracer
+from m1n1.trace.gpio import GPIOTracer
+from m1n1.trace.spi import SPITracer
+
+DARTTracer = DARTTracer._reloadcls()
+ASCTracer = ASCTracer._reloadcls()
+GPIOTracer = GPIOTracer._reloadcls()
+SPITracer = SPITracer._reloadcls()
+
+# SPI HID transport tracer for 2021 macbook models
+
+kbd_node = None
+for node in hv.adt.walk_tree():
+ try:
+ if node.compatible[0] == "spi-1,spimc":
+ for c in node:
+ try:
+ if c.compatible[0] == "hid-transport,spi":
+ kbd_node = c
+ break
+ except AttributeError:
+ continue
+ except AttributeError:
+ continue
+ if kbd_node is not None:
+ break
+
+# trace interrupts
+aic_phandle = getattr(hv.adt["/arm-io/aic"], "AAPL,phandle")
+spi_node = kbd_node._parent
+
+if getattr(spi_node, "interrupt-parent") == aic_phandle:
+ for irq in getattr(spi_node, "interrupts"):
+ hv.trace_irq(node.name, irq, 1, hv.IRQTRACE_IRQ)
+
+spi_pins = {
+ 0x37: "spi3_cs",
+ 0xc2: "ipd_en",
+}
+spi_pins_nub = {
+ 0x6: "ipd_irq",
+}
+
+SPI_HID_PKT = Struct(
+ "flags" / Int8ub,
+ "dev" / Int8ub,
+ "offset" / Int16ul,
+ "remain" / Int16ul,
+ "length" / Int16ul,
+ "data" / Bytes(246),
+ "crc16" / Int16ul
+)
+
+#gpio_tracer = GPIOTracer(hv, "/arm-io/gpio0", spi_pins, verbose=0)
+#gpio_tracer.start()
+
+#gpio_tracer_nub = GPIOTracer(hv, "/arm-io/nub-gpio0", spi_pins_nub, verbose=0)
+#gpio_tracer_nub.start()
+
+dart_sio_tracer = DARTTracer(hv, "/arm-io/dart-sio", verbose=1)
+dart_sio_tracer.start()
+
+iomon = RegMonitor(hv.u, ascii=True)
+
+def readmem_iova(addr, size):
+ try:
+ return dart_sio_tracer.dart.ioread(0, addr, size)
+ except Exception as e:
+ print(e)
+ return None
+
+iomon.readmem = readmem_iova
+
+class SIOMessage(Register64):
+ EP = 7, 0 # matches device's ADT dma-channels, j314c spi3 0x1a and 0x1b
+ TAG = 13, 8 # counts for ipd spi transfers from 0x02 to 0x20
+ TYPE = 23, 16 # OP
+ PARAM = 31, 24
+ DATA = 63, 32
+
+class SIOStart(SIOMessage):
+ TYPE = 23, 16, Constant(2)
+
+class SIOSetup(SIOMessage):
+ TYPE = 23, 16, Constant(3)
+
+class SIOConfig(SIOMessage): #???
+ TYPE = 23, 16, Constant(5)
+
+class SIOAck(SIOMessage):
+ TYPE = 23, 16, Constant(0x65)
+
+class SIOSetupIO(SIOMessage):
+ TYPE = 23, 16, Constant(6)
+
+class SIOCompleteIO(SIOMessage):
+ TYPE = 23, 16, Constant(0x68)
+
+class SIOEp(EP):
+ BASE_MESSAGE = SIOMessage
+ SHORT = "sioep"
+
+ def __init__(self, tracer, epid):
+ super().__init__(tracer, epid)
+ self.state.iova = None
+ self.state.iova_cfg = None
+ self.state.dumpfile = None
+
+ @msg(2, DIR.TX, SIOStart)
+ def Start(self, msg):
+ self.log("Start SIO")
+
+ @msg(3, DIR.TX, SIOSetup)
+ def m_Setup(self, msg):
+ iomon.poll()
+ if msg.EP == 0 and msg.PARAM == 0x1:
+ self.state.iova = msg.DATA << 12
+ elif msg.EP == 0 and msg.PARAM == 0x2:
+ # size for PARAM == 0x1?
+ if self.state.iova is not None and self.tracer.verbose > 1:
+ iomon.add(self.state.iova, msg.DATA * 8, name=f"sio.shmem@{self.state.iova:08x}",
+ offset=self.state.iova)
+ elif msg.EP == 0 and msg.PARAM == 0xb:
+ # second iova block, maybe config
+ self.state.iova_cfg = msg.DATA << 12
+ elif msg.EP == 0 and msg.PARAM == 0xc:
+ # size for PARAM == 0xb?
+ if self.state.iova is not None and self.tracer.verbose > 1:
+ iomon.add(self.state.iova_cfg, msg.DATA * 8,
+ name=f"sio.shmem@{self.state.iova_cfg:08x}", offset=self.state.iova_cfg)
+
+ @msg(0x65, DIR.RX, SIOAck)
+ def m_Ack(self, msg):
+ iomon.poll()
+
+ @msg(6, DIR.TX, SIOSetupIO)
+ def m_SetupIO(self, msg):
+ iomon.poll()
+ if self.state.iova is None:
+ return
+ if msg.EP == 0x1a:
+ buf = struct.unpack("<I", self.tracer.ioread(self.state.iova + 0xa8, 4))[0]
+ size = struct.unpack("<I", self.tracer.ioread(self.state.iova + 0xb0, 4))[0]
+ self.log_spihid_pkt("SPI3 TX", self.tracer.ioread(buf, size))
+
+ @msg(0x68, DIR.RX, SIOCompleteIO)
+ def m_CompleteIO(self, msg):
+ iomon.poll()
+ if self.state.iova is None:
+ return
+ if msg.EP == 0x1b:
+ buf = struct.unpack("<I", self.tracer.ioread(self.state.iova + 0x48, 4))[0]
+ size = struct.unpack("<I", self.tracer.ioread(self.state.iova + 0x50, 4))[0]
+ self.log_spihid_pkt("SPI3 RX", self.tracer.ioread(buf, size))
+
+ def log_spihid_pkt(self, label, data):
+ if len(data) != 256:
+ self.log(f"{label}: unexpected data length: {len(data):d}")
+ chexdump(data)
+ return
+
+ crc16 = crc16USB(0, data[:254])
+ pkt = SPI_HID_PKT.parse(data)
+ #self.log(f"pkt.crc16:{pkt.crc16:#04x} crc16:{crc16:#04x}")
+ if pkt.length == 0:
+ return
+
+ if pkt.flags == 0x80 and pkt.dev == 0x11 and pkt.length == 849 and pkt.offset == 256 and pkt.remain == 834 and crc16 == 0x1489:
+ return
+ if pkt.crc16 != crc16:
+ self.log(f"{label}: CRC mismatch: pkt.crc16:{pkt.crc16:#04x} crc16:{crc16:#04x}")
+ chexdump(data)
+ return
+ self.log(f"{label}: flags:{pkt.flags:#2x} dev:{pkt.dev:#2x} length:{pkt.length:4d} offset:{pkt.offset:3d} remain:{pkt.remain:3d}")
+ chexdump(pkt.data[:min(246, pkt.length)])
+
+ if self.state.dumpfile:
+ dump = f"{label}: flags:{pkt.flags:#2x} dev:{pkt.dev:#2x} length:{pkt.length:4d} {pkt.data[:min(246, pkt.length)].hex()}\n"
+ self.state.dumpfile.write(dump)
+ self.state.dumpfile.flush()
+
+
+class SIOTracer(ASCTracer):
+ ENDPOINTS = {
+ 0x20: SIOEp
+ }
+
+
+sio_tracer = SIOTracer(hv, "/arm-io/sio", verbose=False)
+sio_tracer.start(dart_sio_tracer.dart)
+
+sio_tracer.ep.sioep.state.dumpfile = open("spi_hid.log", "a")
+
+spi_tracer = SPITracer(hv, "/arm-io/" + spi_node.name, verbose=1)
+spi_tracer.start()
diff --git a/tools/proxyclient/hv/trace_mesa.py b/tools/proxyclient/hv/trace_mesa.py
new file mode 100644
index 0000000..37fb32f
--- /dev/null
+++ b/tools/proxyclient/hv/trace_mesa.py
@@ -0,0 +1,212 @@
+# SPDX-License-Identifier: MIT
+"""
+Things to note:
+ The command buffer is encrypted after the poweron sequence, and I
+ can't find the key in the SEP using sven's old SEP tracer.
+"""
+import struct
+from construct import *
+
+from m1n1.hv import TraceMode
+from m1n1.proxyutils import RegMonitor
+from m1n1.utils import *
+
+from m1n1.trace import ADTDevTracer
+from m1n1.trace.asc import ASCTracer, ASCRegs, EP, EPState, msg, msg_log, DIR
+from m1n1.trace.dart import DARTTracer
+from m1n1.trace.gpio import GPIOTracer
+from m1n1.trace.spi import SPITracer
+
+DARTTracer = DARTTracer._reloadcls()
+ASCTracer = ASCTracer._reloadcls()
+GPIOTracer = GPIOTracer._reloadcls()
+SPITracer = SPITracer._reloadcls()
+
+mesa_node = None
+for node in hv.adt.walk_tree():
+ try:
+ if node.compatible[0] == "spi-1,spimc":
+ for c in node:
+ try:
+ if c.compatible[0] == "biosensor,mesa":
+ mesa_node = c
+ break
+ except AttributeError:
+ continue
+ except AttributeError:
+ continue
+ if mesa_node is not None:
+ break
+
+# trace interrupts
+aic_phandle = getattr(hv.adt["/arm-io/aic"], "AAPL,phandle")
+spi_node = mesa_node._parent
+
+if getattr(spi_node, "interrupt-parent") == aic_phandle:
+ for irq in getattr(spi_node, "interrupts"):
+ hv.trace_irq("/arm-io/" + spi_node.name, irq, 1, hv.IRQTRACE_IRQ)
+
+if getattr(mesa_node, "interrupt-parent") == aic_phandle:
+ for irq in getattr(mesa_node, "interrupts"):
+ hv.trace_irq("/arm-io/" + mesa_node.name, irq, 1, hv.IRQTRACE_IRQ)
+
+mesa_pins = {
+ 0xc4: "mesa_pwr",
+
+}
+
+
+gpio_tracer = GPIOTracer(hv, "/arm-io/gpio0", mesa_pins, verbose=1)
+gpio_tracer.start()
+
+dart_sio_tracer = DARTTracer(hv, "/arm-io/dart-sio", verbose=1)
+dart_sio_tracer.start()
+
+iomon = RegMonitor(hv.u, ascii=True)
+
+def readmem_iova(addr, size, readfn=None):
+ try:
+ return dart_sio_tracer.dart.ioread(0, addr, size)
+ except Exception as e:
+ print(e)
+ return None
+
+iomon.readmem = readmem_iova
+
+
+
+class SIOMessage(Register64):
+ EP = 7, 0 # SPI2 DMA channels 0x18, 0x19
+ TAG = 13, 8 # counts up, message ID?
+ TYPE = 23, 16 # SIO message type
+ PARAM = 31, 24
+ DATA = 63, 32
+
+class SIOStart(SIOMessage):
+ TYPE = 23, 16, Constant(2)
+
+class SIOSetup(SIOMessage):
+ TYPE = 23, 16, Constant(3)
+
+class SIOConfig(SIOMessage): #???
+ TYPE = 23, 16, Constant(5)
+
+class SIOAck(SIOMessage):
+ TYPE = 23, 16, Constant(0x65)
+
+class SIOSetupIO(SIOMessage):
+ TYPE = 23, 16, Constant(6)
+
+class SIOCompleteIO(SIOMessage):
+ TYPE = 23, 16, Constant(0x68)
+
+class SIOEp(EP):
+ BASE_MESSAGE = SIOMessage
+ SHORT = "sioep"
+
+ def __init__(self, tracer, epid):
+ super().__init__(tracer, epid)
+ self.state.iova = None
+ self.state.iova_cfg = None
+ self.state.iova_unk = None
+ self.state.dumpfile = None
+
+ @msg(2, DIR.TX, SIOStart)
+ def Start(self, msg):
+ self.log("Start SIO")
+
+ @msg(3, DIR.TX, SIOSetup)
+ def m_Setup(self, msg):
+ if msg.EP == 0 and msg.PARAM == 0x1:
+ self.state.iova = msg.DATA << 12
+
+ elif msg.EP == 0 and msg.PARAM == 0x2:
+ # size for PARAM == 0x1?
+ iomon.add(self.state.iova, msg.DATA * 8,
+ name=f"SIO IOVA region at 0x{self.state.iova:08x}",
+ offset=self.state.iova)
+
+ #elif msg.EP == 0 and msg.PARAM == 0xb:
+ ## second iova block, maybe config
+ #self.state.iova_cfg = msg.DATA << 12
+
+ #elif msg.EP == 0 and msg.PARAM == 0xc:
+ ## size for PARAM == 0xb?
+ #iomon.add(self.state.iova_cfg, msg.DATA * 8,
+ #name=f"SIO IOVA CFG region at 0x{self.state.iova_cfg:08x}",
+ #offset=self.state.iova_cfg)
+
+ if msg.EP == 0 and msg.PARAM == 0xd:
+ # possible fingerprint sensor IOVA region
+ self.state.iova_unk = msg.DATA << 12
+
+ elif msg.EP == 0 and msg.PARAM == 0xe:
+ iomon.add(self.state.iova_unk, msg.DATA * 8,
+ name=f"SIO IOVA UNK region at {self.state.iova_unk:08x}",
+ offset=self.state.iova_unk)
+
+ @msg(5, DIR.TX, SIOConfig)
+ def m_Config(self, msg):
+ return
+
+ @msg(0x65, DIR.RX, SIOAck)
+ def m_Ack(self, msg):
+ return
+
+ @msg(6, DIR.TX, SIOSetupIO)
+ def m_SetupIO(self, msg):
+ if msg.EP == 0x18 or 0x19:
+ iomon.poll()
+ return
+
+ @msg(0x68, DIR.RX, SIOCompleteIO)
+ def m_CompleteIO(self, msg):
+ if msg.EP == 0x18:
+ if self.state.iova is None:
+ return
+
+ buf = struct.unpack("<I", self.tracer.ioread(self.state.iova + 0xa8, 4))[0]
+ size = struct.unpack("<I", self.tracer.ioread(self.state.iova + 0xb0, 4))[0]
+ self.log(f"SetupIO 0x18: buf {buf:#x}, size {size:#x}")
+ # XXX: Do not try to log messages going to 0x2
+ if buf == 0x2:
+ self.log("Mesa command interrupted!")
+ return
+ self.log_mesa("EP 0x18", self.tracer.ioread(buf, size))
+ return
+ if msg.EP == 0x19:
+ if self.state.iova is None:
+ return
+
+ buf = struct.unpack("<I", self.tracer.ioread(self.state.iova + 0x48, 4))[0]
+ size = struct.unpack("<I", self.tracer.ioread(self.state.iova + 0x50, 4))[0]
+ self.log(f"CompleteIO 0x19: buf {buf:#x}, size {size:#x}")
+ # XXX: Do not try to log messages going to 0x2
+ if buf == 0x2:
+ self.log("Mesa command interrupted!")
+ return
+ if size >= 0x7200:
+ with open("large_message.bin", "wb") as fd:
+ fd.write(self.tracer.ioread(buf, size))
+ print("Fingerprint record message dumped.")
+ return
+ self.log_mesa("EP 0x19", self.tracer.ioread(buf, size))
+ return
+
+ def log_mesa(self, label, data):
+ self.log(f"{label}: {len(data):d} byte message: ")
+ chexdump(data)
+ print("\n")
+
+
+class SIOTracer(ASCTracer):
+ ENDPOINTS = {
+ 0x20: SIOEp
+ }
+
+
+sio_tracer = SIOTracer(hv, "/arm-io/sio", verbose=1)
+sio_tracer.start(dart=dart_sio_tracer.dart)
+
+spi_tracer = SPITracer(hv, "/arm-io/spi2", verbose=2)
+spi_tracer.start()
diff --git a/tools/proxyclient/hv/trace_nvme.py b/tools/proxyclient/hv/trace_nvme.py
new file mode 100644
index 0000000..c10b3bc
--- /dev/null
+++ b/tools/proxyclient/hv/trace_nvme.py
@@ -0,0 +1,275 @@
+# SPDX-License-Identifier: MIT
+
+from construct import *
+from construct.core import Int16ul, Int32ul, Int64ul, Int8ul
+
+from m1n1.hv import TraceMode
+from m1n1.utils import *
+from m1n1.trace import ADTDevTracer
+from m1n1.trace.asc import ASCRegs
+from m1n1.trace.asc import ASCTracer
+
+ASCTracer = ASCTracer._reloadcls()
+
+class NVMERegs(RegMap):
+ APPLE_NVMMU_NUM = 0x28100, Register32
+ APPLE_NVMMU_BASE_ASQ = 0x28108, Register32
+ APPLE_NVMMU_BASE_ASQ1 = 0x2810C, Register32
+ APPLE_NVMMU_BASE_IOSQ = 0x28110, Register32
+ APPLE_NVMMU_BASE_IOSQ1 = 0x28114, Register32
+ APPLE_NVMMU_TCB_INVAL = 0x28118, Register32
+ APPLE_NVMMU_TCB_STAT = 0x28120, Register32
+ APPLE_ANS2_LINEAR_SQ_CTRL = 0x24908, Register32
+ APPLE_ANS2_UNKNOWN_CTRL = 0x24008, Register32
+ APPLE_ANS2_BOOT_STATUS = 0x1300, Register32
+ APPLE_ANS2_MAX_PEND_CMDS_CTRL = 0x1210, Register32
+ APPLE_ANS2_LINEAR_ASQ_DB = 0x2490C, Register32
+ APPLE_ANS2_LINEAR_IOSQ_DB = 0x24910, Register32
+
+ NVME_REG_CAP = 0x0000, Register32
+ NVME_REG_VS = 0x0008, Register32
+ NVME_REG_INTMS = 0x000C, Register32
+ NVME_REG_INTMC = 0x0010, Register32
+ NVME_REG_CC = 0x0014, Register32
+ NVME_REG_CSTS = 0x001C, Register32
+ NVME_REG_NSSR = 0x0020, Register32
+ NVME_REG_AQA = 0x0024, Register32
+ NVME_REG_ASQ = 0x0028, Register32
+ NVME_REG_ASQ1 = 0x002C, Register32
+ NVME_REG_ACQ = 0x0030, Register32
+ NVME_REG_CMBLOC = 0x0038, Register32
+ NVME_REG_CMBSZ = 0x003C, Register32
+ NVME_REG_BPINFO = 0x0040, Register32
+ NVME_REG_BPRSEL = 0x0044, Register32
+ NVME_REG_BPMBL = 0x0048, Register32
+ NVME_REG_CMBMSC = 0x0050, Register32
+ NVME_REG_PMRCAP = 0x0E00, Register32
+ NVME_REG_PMRCTL = 0x0E04, Register32
+ NVME_REG_PMRSTS = 0x0E08, Register32
+ NVME_REG_PMREBS = 0x0E0C, Register32
+ NVME_REG_PMRSWTP = 0x0E10, Register32
+ NVME_REG_DBS = 0x1000, Register32
+ NVME_REG_DBS_ASQ = 0x1004, Register32
+ NVME_REG_DBS_IOSQ = 0x100C, Register32
+
+
+AppleTunnelSetTime = Struct(
+ "unk" / Int32ul,
+ "unix_timestamp" / Int32ul,
+ "time_0" / Int64ul,
+ "time_1" / Int64ul,
+)
+
+NVMECommand = Struct(
+ "opcode" / Int8ul,
+ "flags" / Int8ul,
+ "command_id" / Int16ul,
+ "nsid" / Int32ul,
+ "cdw0" / Int32ul,
+ "cdw1" / Int32ul,
+ "metadata" / Int64ul,
+ "prp1" / Int64ul,
+ "prp2" / Int64ul,
+ "cdw10" / Int32ul,
+ "cdw11" / Int32ul,
+ "cdw12" / Int32ul,
+ "cdw13" / Int32ul,
+ "cdw14" / Int32ul,
+ "cdw16" / Int32ul,
+)
+
+NVME_IO_COMMANDS = {
+ 0x00: "nvme_cmd_flush",
+ 0x01: "nvme_cmd_write",
+ 0x02: "nvme_cmd_read",
+ 0x04: "nvme_cmd_write_uncor",
+ 0x05: "nvme_cmd_compare",
+ 0x08: "nvme_cmd_write_zeroes",
+ 0x09: "nvme_cmd_dsm",
+ 0x0C: "nvme_cmd_verify",
+ 0x0D: "nvme_cmd_resv_register",
+ 0x0E: "nvme_cmd_resv_report",
+ 0x11: "nvme_cmd_resv_acquire",
+ 0x15: "nvme_cmd_resv_release",
+ 0x79: "nvme_cmd_zone_mgmt_send",
+ 0x7A: "nvme_cmd_zone_mgmt_recv",
+ 0x7D: "nvme_cmd_zone_append",
+}
+
+NVME_ADMIN_COMMANDS = {
+ 0x00: "nvme_admin_delete_sq",
+ 0x01: "nvme_admin_create_sq",
+ 0x02: "nvme_admin_get_log_page",
+ 0x04: "nvme_admin_delete_cq",
+ 0x05: "nvme_admin_create_cq",
+ 0x06: "nvme_admin_identify",
+ 0x08: "nvme_admin_abort_cmd",
+ 0x09: "nvme_admin_set_features",
+ 0x0A: "nvme_admin_get_features",
+ 0x0C: "nvme_admin_async_event",
+ 0x0D: "nvme_admin_ns_mgmt",
+ 0x10: "nvme_admin_activate_fw",
+ 0x11: "nvme_admin_download_fw",
+ 0x14: "nvme_admin_dev_self_test",
+ 0x15: "nvme_admin_ns_attach",
+ 0x18: "nvme_admin_keep_alive",
+ 0x19: "nvme_admin_directive_send",
+ 0x1A: "nvme_admin_directive_recv",
+ 0x1C: "nvme_admin_virtual_mgmt",
+ 0x1D: "nvme_admin_nvme_mi_send",
+ 0x1E: "nvme_admin_nvme_mi_recv",
+ 0x7C: "nvme_admin_dbbuf",
+ 0x80: "nvme_admin_format_nvm",
+ 0x81: "nvme_admin_security_send",
+ 0x82: "nvme_admin_security_recv",
+ 0x84: "nvme_admin_sanitize_nvm",
+ 0x86: "nvme_admin_get_lba_status",
+ 0xC0: "nvme_admin_vendor_start",
+}
+
+APPLE_TUNNEL_CMDS = {0x06: "set_time", 0x38: "get_nand_id", 0xBA: "get_nand_geometry"}
+
+NVMMUTcb = Struct(
+ "opcode" / Int8ul,
+ "dma_flags" / Int8ul,
+ "command_id" / Int8ul,
+ "unk0" / Int8ul,
+ "length" / Int32ul,
+ "unk1a" / Int64ul,
+ "unk1b" / Int64ul,
+ "prp0" / Int64ul,
+ "prp1" / Int64ul,
+ "unk2a" / Int64ul,
+ "unk2b" / Int64ul,
+ # aes_iv, u8[8]
+ # aes_data, u8[64]
+)
+
+
+class NVMETracer(ASCTracer):
+ DEFAULT_MODE = TraceMode.SYNC
+
+ REGMAPS = [ASCRegs, None, None, NVMERegs]
+ NAMES = ["asc", None, None, "nvme"]
+
+ ENDPOINTS = {}
+
+ def init_state(self):
+ self.state.ep = {}
+ self.state.cmd_cache = {}
+ self.state.nvmmu_asq_base = None
+ self.state.nvmmu_iosq_base = None
+ self.state.asq = None
+
+ def r_APPLE_NVMMU_TCB_STAT(self, r):
+ pass
+
+ def w_APPLE_NVMMU_BASE_ASQ(self, r):
+ self.state.nvmmu_asq_base = r.value
+
+ def w_APPLE_NVMMU_BASE_ASQ1(self, r):
+ self.state.nvmmu_asq_base |= r.value << 32
+
+ def w_APPLE_NVMMU_BASE_IOSQ(self, r):
+ self.state.nvmmu_iosq_base = r.value
+
+ def w_APPLE_NVMMU_BASE_IOSQ1(self, r):
+ self.state.nvmmu_iosq_base |= r.value << 32
+
+ def w_NVME_REG_ASQ(self, r):
+ self.state.asq = r.value
+
+ def w_NVME_REG_ASQ1(self, r):
+ self.state.asq |= r.value << 32
+
+ def w_APPLE_ANS2_LINEAR_ASQ_DB(self, r):
+ tag = r.value
+ cmd = NVMECommand.parse(self.hv.iface.readmem(self.state.asq + 64 * tag, 0x40))
+ tcb = NVMMUTcb.parse(
+ self.hv.iface.readmem(self.state.nvmmu_asq_base + 0x80 * tag, 0x80)
+ )
+
+ self.state.cmd_cache[tag] = (True, cmd, tcb)
+
+ if cmd.opcode == 0xD8:
+ self.log("apple_tunnel_cmd:")
+ self.parse_apple_tunnel_cmd(cmd, False)
+ return
+
+ cmdname = NVME_ADMIN_COMMANDS.get(cmd.opcode, "unknown")
+ self.log(f"{cmdname}:")
+ self.log(f" {repr(cmd)}")
+ self.log(f" {repr(tcb)}")
+
+ if cmd.opcode == 1:
+ self.state.iosq = cmd.prp1
+
+ def w_APPLE_ANS2_LINEAR_IOSQ_DB(self, r):
+ tag = r.value
+ cmd = NVMECommand.parse(self.hv.iface.readmem(self.state.iosq + 64 * tag, 0x40))
+ tcb = NVMMUTcb.parse(
+ self.hv.iface.readmem(self.state.nvmmu_iosq_base + 0x80 * tag, 0x80)
+ )
+ cmdname = NVME_IO_COMMANDS.get(cmd.opcode, "unknown")
+ self.log(f"{cmdname}:")
+ self.log(f" {repr(cmd)}")
+ self.log(f" {repr(tcb)}")
+
+ self.state.cmd_cache[tag] = (False, cmd, tcb)
+
+ def parse_apple_tunnel_cmd(self, cmd, done):
+ ptr0 = (cmd.cdw12 << 32) | cmd.cdw11
+ ptr1 = (cmd.cdw14 << 32) | cmd.cdw13
+
+ data = self.hv.iface.readmem(ptr0, 0x4000)
+ if ptr1 > 0:
+ data1 = self.hv.iface.readmem(ptr1, 0x4000)
+
+ apple_cmd_opcode = data[12]
+ apple_cmd = APPLE_TUNNEL_CMDS.get(apple_cmd_opcode, "Unknown")
+
+ if apple_cmd_opcode == 0x06:
+ self.log(
+ f" apple_tunnel_cmd: set_time: {repr(AppleTunnelSetTime.parse(data[0x18:0x30]))}"
+ )
+ elif apple_cmd_opcode == 0x38:
+ self.log(f" apple_tunnel_cmd: get_nand_id")
+ if done:
+ self.log(f" manufacturer id: {hexdump(data1[:8])}")
+ else:
+ self.log(f" apple_tunnel_cmd: {apple_cmd} ({apple_cmd_opcode})")
+ chexdump(data, print_fn=self.log)
+ if ptr1 > 0:
+ chexdump(self.hv.iface.readmem(ptr1, 0x4000), print_fn=self.log)
+
+ def w_APPLE_NVMMU_TCB_INVAL(self, r):
+ self.log(f" NVMMU inval for {r.value}")
+ tag = r.value
+ if tag not in self.state.cmd_cache:
+ self.log(" NVMMU tag not found in cmd_cache")
+ return
+
+ is_admin, cmd, tcb = self.state.cmd_cache[tag]
+ del self.state.cmd_cache[tag]
+
+ if is_admin:
+ if cmd.opcode == 0xD8:
+ self.log(f" done apple_tunnel_cmd")
+ self.parse_apple_tunnel_cmd(cmd, True)
+ else:
+ cmdname = NVME_ADMIN_COMMANDS.get(cmd.opcode, "unknown")
+ self.log(f" done {cmdname}")
+ else:
+ cmdname = NVME_IO_COMMANDS.get(cmd.opcode, "unknown")
+ self.log(f" done {cmdname}")
+
+ def start(self):
+ self.state.cmd_cache = {}
+ super().start()
+
+
+NVMETracer = NVMETracer._reloadcls()
+nvme_tracer = NVMETracer(hv, "/arm-io/ans", verbose=1)
+nvme_tracer.start()
+
+trace_device("/arm-io/sart-ans")
diff --git a/tools/proxyclient/hv/trace_pmgr.py b/tools/proxyclient/hv/trace_pmgr.py
new file mode 100644
index 0000000..f04d8db
--- /dev/null
+++ b/tools/proxyclient/hv/trace_pmgr.py
@@ -0,0 +1,170 @@
+# SPDX-License-Identifier: MIT
+
+from m1n1 import asm
+from m1n1.trace import Tracer
+from m1n1.utils import *
+from m1n1.proxy import *
+from m1n1.sysreg import *
+from m1n1.proxyutils import RegMonitor
+from m1n1.trace.dart import DARTTracer
+from m1n1.trace.asc import ASCTracer, EP, msg, msg_log, DIR
+from m1n1.fw.pmp import *
+
+#trace_device("/arm-io/pmgr", False)
+#trace_device("/arm-io/jpeg0")
+#trace_device("/arm-io/jpeg1")
+
+#for reg in (0, 1, 2, 3, 4, 23):
+ #addr, size = hv.adt["/arm-io/pmgr"].get_reg(reg)
+ #hv.trace_range(irange(addr, 0x20000))
+
+#hv.trace_range(irange(0x210e00000, 0x80000), read=False)
+#hv.trace_range(irange(0x211e00000, 0x80000), read=False)
+
+#hv.trace_range(irange(0x23b040000, 0x1000))
+#hv.trace_range(irange(0x23b044000, 0x14000))
+
+Tracer = Tracer._reloadcls()
+ASCTracer = ASCTracer._reloadcls()
+
+iomon = RegMonitor(hv.u, ascii=True)
+
+def readmem_iova(addr, size):
+ try:
+ return dart_tracer.dart.ioread(0, addr, size)
+ except Exception as e:
+ print(e)
+ return None
+
+iomon.readmem = readmem_iova
+
+class PMPEpTracer(EP):
+ BASE_MESSAGE = PMPMessage
+
+ def __init__(self, tracer, epid):
+ super().__init__(tracer, epid)
+ self.state.shmem_iova = None
+ self.state.verbose = 1
+
+ def start(self):
+ self.add_mon()
+
+ def add_mon(self):
+ if self.state.shmem_iova:
+ iomon.add(self.state.shmem_iova, 0x10000,
+ name=f"{self.name}.shmem@{self.state.shmem_iova:08x}", offset=0)
+
+ @msg(1, DIR.TX, PMP_Configure)
+ def Configure(self, msg):
+ self.state.shmem_iova = msg.DVA
+ self.add_mon()
+
+class PMPTracer(ASCTracer):
+ ENDPOINTS = {
+ 0x20: PMPEpTracer
+ }
+
+ def handle_msg(self, direction, r0, r1):
+ super().handle_msg(direction, r0, r1)
+ iomon.poll()
+
+ def start(self, dart=None):
+ super().start()
+ # noisy doorbell
+ self.trace(0x23bc34000, 4, TraceMode.OFF)
+
+#dart_tracer = DARTTracer(hv, "/arm-io/dart-pmp", verbose=2)
+#dart_tracer.start()
+
+#pmp_tracer = PMPTracer(hv, "/arm-io/pmp", verbose=1)
+#pmp_tracer.start(dart_tracer.dart)
+
+class PMGRTracer(Tracer):
+ IGNORED = set(["SPI1", "I2C2"])
+ def __init__(self, hv):
+ super().__init__(hv)
+ self.dev = hv.adt["/arm-io/pmgr"]
+ self.ignored_ranges = [
+ (0x23b738004, 4), # ecpu state report
+ (0x23b738008, 4), # pcpu state report
+ (0x23d2b9000, 0x30),
+ (0x23d2dc100, 4),
+ ]
+ self.build_table(hv)
+ self.reg_cache = {}
+
+ def hook_w(self, addr, val, width, **kwargs):
+ self.hv.log(f"PMGR: W {addr:#x} <- {val:#x}")
+ #print("-> ignored")
+ super().hook_w(addr, val, width, **kwargs)
+
+ def hook_r(self, addr, width, **kwargs):
+ val = super().hook_r(addr, width, **kwargs)
+ self.hv.log(f"PMGR: R {addr:#x} = {val:#x}")
+ return val
+
+ def evt_rw(self, evt):
+ if not evt.flags.WRITE:
+ self.reg_cache[evt.addr] = evt.data
+ cb = self.ranges.lookup(evt.addr)
+ cb[0](evt, *cb[1:])
+ self.reg_cache[evt.addr] = evt.data
+
+ def event_default(self, evt, start, name):
+ t = "W" if evt.flags.WRITE else "R"
+ m = "+" if evt.flags.MULTI else " "
+ data = f"{evt.data:#x}"
+ if evt.flags.WRITE:
+ data = f"{evt.data:#x}"
+ old = self.reg_cache.get(evt.addr, None)
+ if old is not None:
+ data = f"{old:#x} -> {evt.data:#x}"
+ self.hv.log(f"[cpu{evt.flags.CPU}][0x{evt.pc:016x}] PMGR: {t}.{1<<evt.flags.WIDTH:<2}{m} " +
+ f"0x{evt.addr:x} ({name} + {evt.addr - start:#04x}) = {data}", show_cpu=False)
+
+ def build_table(self, hv):
+ self.ranges = ScalarRangeMap()
+ self.state_regs = {}
+
+ starts = {}
+ for reg in (0, 1):
+ addr, size = self.dev.get_reg(reg)
+ self.ranges[addr:addr + size] = self.event_default, addr, f"reg[{reg}]"
+
+ for i, ps in enumerate(self.dev.ps_regs):
+ addr = self.dev.get_reg(ps.reg)[0] + ps.offset
+ for idx in range(32):
+ ps_addr = addr + idx * 8
+ self.ranges[ps_addr:ps_addr + 8] = self.event_default, ps_addr, f"ps[{i}][{idx}]"
+
+ for i, dev in enumerate(self.dev.devices):
+ ps = self.dev.ps_regs[dev.psreg]
+ if dev.psidx or dev.psreg:
+ addr = self.dev.get_reg(ps.reg)[0] + ps.offset + dev.psidx * 8
+ self.state_regs[addr] = dev.name
+ if dev.name in self.IGNORED:
+ self.ignored_ranges.append((addr, 8))
+ self.ranges[addr:addr + 8] = self.event_default, addr, f"{dev.name}.pstate"
+
+ def start(self):
+ self.hv.clear_tracers(self.ident)
+
+ for reg in (0, 1):
+ addr, size = self.dev.get_reg(reg)
+ self.trace(addr, size, TraceMode.WSYNC, read=False)
+
+ for ps in self.dev.ps_regs:
+ addr = self.dev.get_reg(ps.reg)[0] + ps.offset
+ self.trace(addr, 0x100, TraceMode.WSYNC)
+
+ for lane in range(8):
+ addr = 0x200200000 + 0x40000 * lane
+ self.trace(addr, 0x40000, TraceMode.HOOK)
+ #for reg in (23,):
+ #addr, size = self.dev.get_reg(reg)
+ #self.trace(addr, 0x20000, TraceMode.SYNC)
+ for addr, size in self.ignored_ranges:
+ self.trace(addr, size, TraceMode.OFF)
+
+pmgr_tracer = PMGRTracer(hv)
+pmgr_tracer.start()
diff --git a/tools/proxyclient/hv/trace_prores.py b/tools/proxyclient/hv/trace_prores.py
new file mode 100644
index 0000000..05acb4a
--- /dev/null
+++ b/tools/proxyclient/hv/trace_prores.py
@@ -0,0 +1,88 @@
+from m1n1.trace import ADTDevTracer
+from m1n1.trace.dart8110 import DART8110Tracer
+from m1n1.hw.prores import *
+from m1n1.utils import *
+import struct
+
+p.pmgr_adt_clocks_enable('/arm-io/dart-apr0')
+p.pmgr_adt_clocks_enable('/arm-io/dart-apr1')
+
+dart0_tracer = DART8110Tracer(hv, "/arm-io/dart-apr0", verbose=1)
+dart0_tracer.start()
+print(dart0_tracer)
+dart1_tracer = DART8110Tracer(hv, "/arm-io/dart-apr1", verbose=1)
+dart1_tracer.start()
+print(dart1_tracer)
+
+
+class ProResTracer(ADTDevTracer):
+ DEFAULT_MODE = TraceMode.SYNC
+ REGMAPS = [ProResRegs]
+ NAMES = ['prores']
+
+ def __init__(self, hv, devpath, dart_tracer):
+ super().__init__(hv, devpath, verbose=3)
+ self.dart_tracer = dart_tracer
+
+ def w_DR_SIZE(self, val):
+ self._dr_size = val
+
+ def w_DR_ADDR_HI(self, val):
+ self._dr_addr_hi = val
+
+ def w_DR_ADDR_LO(self, val):
+ self._dr_addr_lo = val
+
+ def w_DR_TAIL(self, val):
+ self.log(f"DR_TAIL = {val}")
+ self._dr_tail = val
+
+ def w_DR_HEAD(self, val):
+ self.log(f"DR_HEAD = {val}")
+ self.dart_tracer.dart.dump_all()
+
+ dr_addr = int(self._dr_addr_hi) << 32 | int(self._dr_addr_lo)
+ dr_size = int(self._dr_size)
+ self.log(f"desc ring @ {dr_addr:016X} sz {dr_size:08X}")
+
+ dr = self.dart_tracer.dart.ioread(0, dr_addr, dr_size)
+ chexdump(dr)
+
+ # FIXME there are other descriptor types
+ # also, what if there are multiple in the ring?
+ dr_head = int(val)
+ dr_tail = int(self._dr_tail)
+
+ if dr_head - dr_tail == 0x180:
+ desc = EncodeNotRawDescriptor._make(struct.unpack(ENCODE_NOT_RAW_STRUCT, dr[dr_tail:dr_head]))
+ print(desc)
+
+ p0_iova = desc.luma_iova
+ p1_iova = desc.chroma_iova
+ p2_iova = desc.alpha_iova
+
+ if p0_iova:
+ print(f"P0 iova {p0_iova:016X}")
+ data = self.dart_tracer.dart.ioread(0, p0_iova, 0x1000)
+ chexdump(data)
+ if p1_iova:
+ print(f"P1 iova {p1_iova:016X}")
+ data = self.dart_tracer.dart.ioread(0, p1_iova, 0x1000)
+ chexdump(data)
+ if p2_iova:
+ print(f"P2 iova {p2_iova:016X}")
+ data = self.dart_tracer.dart.ioread(0, p2_iova, 0x1000)
+ chexdump(data)
+
+
+ProResTracer = ProResTracer._reloadcls()
+
+p.pmgr_adt_clocks_enable('/arm-io/apr0')
+p.pmgr_adt_clocks_enable('/arm-io/apr1')
+
+tracer0 = ProResTracer(hv, '/arm-io/apr0', dart0_tracer)
+tracer0.start()
+print(tracer0)
+tracer1 = ProResTracer(hv, '/arm-io/apr1', dart1_tracer)
+tracer1.start()
+print(tracer1)
diff --git a/tools/proxyclient/hv/trace_smc.py b/tools/proxyclient/hv/trace_smc.py
new file mode 100644
index 0000000..2a1efa6
--- /dev/null
+++ b/tools/proxyclient/hv/trace_smc.py
@@ -0,0 +1,108 @@
+# SPDX-License-Identifier: MIT
+
+import struct
+
+from enum import IntEnum
+
+from m1n1.proxyutils import RegMonitor
+from m1n1.utils import *
+from m1n1.trace.dart import DARTTracer
+from m1n1.trace.asc import ASCTracer, EP, EPState, msg, msg_log, DIR
+from m1n1.fw.smc import *
+
+ASCTracer = ASCTracer._reloadcls()
+
+class SMCEpTracer(EP):
+ BASE_MESSAGE = SMCMessage
+
+ def __init__(self, tracer, epid):
+ super().__init__(tracer, epid)
+ self.state.sram_addr = None
+ self.state.verbose = 1
+ self.state.rb = {}
+
+ Initialize = msg_log(SMC_INITIALIZE, DIR.TX, SMCInitialize)
+ Notification = msg_log(SMC_NOTIFICATION, DIR.RX)
+
+ @msg(SMC_WRITE_KEY, DIR.TX, SMCWriteKey)
+ def WriteKey(self, msg):
+ key = msg.KEY.to_bytes(4, byteorder="big").decode("ascii")
+ self.state.rb[msg.ID] = msg.TYPE, key, msg.SIZE
+ data = self.hv.iface.readmem(self.state.sram_addr, msg.SIZE)
+ self.log(f"[{msg.ID:x}] >W: <{key}> = {data.hex()} ({msg.SIZE})")
+ return True
+
+ @msg(SMC_READ_KEY, DIR.TX, SMCReadKey)
+ def ReadKey(self, msg):
+ key = msg.KEY.to_bytes(4, byteorder="big").decode("ascii")
+ self.state.rb[msg.ID] = msg.TYPE, key, msg.SIZE
+ self.log(f"[{msg.ID:x}] >R: <{key}> = ... ({msg.SIZE})")
+ return True
+
+ @msg(SMC_RW_KEY, DIR.TX, SMCReadWriteKey)
+ def ReadKeyPayload(self, msg):
+ key = msg.KEY.to_bytes(4, byteorder="big").decode("ascii")
+ self.state.rb[msg.ID] = msg.TYPE, key, msg.RSIZE
+ data = self.hv.iface.readmem(self.state.sram_addr, msg.WSIZE)
+ self.log(f"[{msg.ID:x}] >RP: <{key}> = {data.hex()} ({msg.WSIZE, msg.RSIZE})")
+ return True
+
+ @msg(SMC_GET_KEY_INFO, DIR.TX, SMCGetKeyInfo)
+ def GetInfo(self, msg):
+ key = msg.KEY.to_bytes(4, byteorder="big").decode("ascii")
+ self.state.rb[msg.ID] = msg.TYPE, key, None
+ self.log(f"[{msg.ID:x}] >KInfo: <{key}>")
+ return True
+
+ @msg(SMC_GET_KEY_BY_INDEX, DIR.TX, SMCGetKeyByIndex)
+ def GetKeyByIndex(self, msg):
+ self.state.rb[msg.ID] = msg.TYPE, msg.INDEX, None
+ self.log(f"[{msg.ID:x}] >KIdx: <{msg.INDEX}>")
+ return True
+
+ @msg(None, DIR.RX, Register64)
+ def RXMsg(self, msg):
+ if self.state.sram_addr is None:
+ self.log(f"SRAM address: {msg.value:#x}")
+ self.state.sram_addr = msg.value
+ return True
+
+ msg = SMCResult(msg.value)
+ if msg.RESULT != 0:
+ self.log(f"[{msg.ID:x}] <Err: 0x{msg.RESULT:02x}")
+ return True
+
+ if msg.ID in self.state.rb:
+ msgtype, key, size = self.state.rb.pop(msg.ID)
+ if msgtype in (SMC_READ_KEY, SMC_RW_KEY):
+ if size <= 4:
+ data = hex(msg.VALUE)
+ else:
+ data = self.hv.iface.readmem(self.state.sram_addr, msg.SIZE).hex()
+ self.log(f"[{msg.ID:x}] <R: <{key}> = {data}")
+ return True
+
+ elif msgtype == SMC_GET_KEY_INFO:
+ data = self.hv.iface.readmem(self.state.sram_addr, 6)
+ size, type, flags = struct.unpack("B4sB", data)
+ self.log(f"[{msg.ID:x}] <Info: <{key}>: size={size} type={type.decode('ascii')} flags={flags:#x}")
+ return True
+
+ elif msgtype == SMC_GET_KEY_BY_INDEX:
+ kname = msg.VALUE.to_bytes(4, byteorder="little").decode("ascii")
+ self.log(f"[{msg.ID:x}] <Key @{key}: <{kname}>")
+ return True
+
+ self.log(f"[{msg.ID:x}] <OK {msg!r}")
+ return True
+
+class SMCTracer(ASCTracer):
+ ENDPOINTS = {
+ 0x20: SMCEpTracer
+ }
+
+ def handle_msg(self, direction, r0, r1):
+ super().handle_msg(direction, r0, r1)
+
+smc_tracer = SMCTracer(hv, "/arm-io/smc", verbose=1)
+smc_tracer.start()
diff --git a/tools/proxyclient/hv/trace_wlan.py b/tools/proxyclient/hv/trace_wlan.py
new file mode 100644
index 0000000..1e4c4b1
--- /dev/null
+++ b/tools/proxyclient/hv/trace_wlan.py
@@ -0,0 +1,446 @@
+import struct
+from construct import *
+
+from m1n1.utils import irange
+from m1n1.hw.dart import DART
+from m1n1.utils import chexdump
+from m1n1.proxyutils import RegMonitor
+from m1n1.constructutils import *
+
+from m1n1.trace.pcie import *
+
+PCIeDevTracer = PCIeDevTracer._reloadcls()
+
+mon = RegMonitor(hv.u)
+
+class WLANCfgSpace(PCICfgSpace):
+ BAR0_WINDOW = 0x80, Register32
+ WRAPPERBASE = 0x70, Register32
+ INTSTATUS = 0x90, Register32
+ INTMASK = 0x94, Register32
+ SBMBX = 0x98, Register32
+ LINK_STATUS_CTRL = 0xbc, Register32
+
+class WLANBAR0(RegMap):
+ INTMASK = 0x2024, Register32
+ MAILBOXINT = 0x2048, Register32
+ MAILBOXMASK = 0x204c, Register32
+ CONFIGADDR = 0x2120, Register32
+ CONFIGDATA = 0x2124, Register32
+ H2D_MAILBOX_0 = 0x2140, Register32
+ H2D_MAILBOX_1 = 0x2144, Register32
+
+ # Linux uses these, via offset 0 instead of 0x2000
+ H2D_MAILBOX_0_ALT = 0x140, Register32
+ H2D_MAILBOX_1_ALT = 0x144, Register32
+ H2D_MAILBOX_0_64 = 0xa20, Register32
+ H2D_MAILBOX_1_64 = 0xa24, Register32
+ INTMASK_64 = 0xc14, Register32
+ MAILBOXINT_64 = 0xc30, Register32
+ MAILBOXMASK_64 = 0xc34, Register32
+
+class WLANSRAMEnd(RegMap):
+ PAD = 0x00, Register32
+ SHARED_BASE = 0x04, Register32
+
+class WLANSRAMShared(RegMap):
+ FLAGS = 0, Register32
+ CONSOLE_ADDR = 20, Register32
+ FWID = 28, Register32
+ MAX_RXBUFPOST = 34, Register16
+ RX_DATAOFFSET = 36, Register32
+ HTOD_MB_DATA_ADDR = 40, Register32
+ DTOH_MB_DATA_ADDR = 44, Register32
+ RING_INFO_ADDR = 48, Register32
+ DMA_SCRATCH_LEN = 52, Register32
+ DMA_SCRATCH_ADDR = 56, Register64
+ HOST_SCB_ADDR = 64, Register64
+ HOST_SCB_SIZE = 72, Register32
+ BUZZ_DBG_PTR = 76, Register32
+ FLAGS2 = 80, Register32
+ HOST_CAP = 84, Register32
+ HOST_TRAP_ADDR = 88, Register64
+ DEVICE_FATAL_LOGBUF_START = 96, Register32
+ HOFFLOAD_ADDR = 100, Register64
+ FLAGS3 = 108, Register32
+ HOST_CAP2 = 112, Register32
+ HOST_CAP3 = 116, Register32
+
+class WLANSRAMRingInfo(RegMap):
+ RINGMEM = 0x00, Register32
+ H2D_W_IDX_PTR = 0x04, Register32
+ H2D_R_IDX_PTR = 0x08, Register32
+ D2H_W_IDX_PTR = 0x0c, Register32
+ D2H_R_IDX_PTR = 0x10, Register32
+ H2D_W_IDX_HOSTADDR = 0x14, Register64
+ H2D_R_IDX_HOSTADDR = 0x1c, Register64
+ D2H_W_IDX_HOSTADDR = 0x24, Register64
+ D2H_R_IDX_HOSTADDR = 0x2c, Register64
+ MAX_FLOWRINGS = 0x34, Register32
+ MAX_SUBMISSIONRINGS = 0x38, Register32
+ MAX_COMPLETIONRINGS = 0x3c, Register32
+
+COMMON_RING_CNT = 5
+
+class WLANSRAMRingMem(RegMap):
+ MAX_ITEM = irange(0x04, COMMON_RING_CNT, 0x10), Register16
+ LEN_ITEMS = irange(0x06, COMMON_RING_CNT, 0x10), Register16
+ BASE_ADDR = irange(0x08, COMMON_RING_CNT, 0x10), Register32
+
+class MsgHeader(ConstructClass):
+ subcon = Struct(
+ "msg_type" / Int8ul,
+ "if_id" / Int8sl,
+ "flags" / Int8ul,
+ "epoch" / Int8ul,
+ "request_id" / Int32ul,
+ )
+
+class ComplHeader(ConstructClass):
+ subcon = Struct(
+ "status" / Int16ul,
+ "ring_id" / Int16ul,
+ )
+
+class IOCtlPtrReq(ConstructClass):
+ subcon = Struct(
+ "cmd" / Int32ul,
+ "trans_id" / Int16ul,
+ "input_buf_len" / Int16ul,
+ "output_buf_len" / Int16ul,
+ "rsvd" / Array(3, Int16ul),
+ "host_input_buf_addr" / Int64ul,
+ )
+
+class IOCtlResp(ConstructClass):
+ subcon = Struct(
+ "compl" / ComplHeader,
+ "resp_len" / Int16ul,
+ "trans_id" / Int16ul,
+ "cmd" / Int32ul,
+ )
+
+class H2DMailboxData(ConstructClass):
+ subcon = Struct(
+ "data" / Int32ul
+ )
+
+class D2HMailboxData(ConstructClass):
+ subcon = Struct(
+ "compl" / ComplHeader,
+ "data" / Int32ul,
+ )
+
+class RingMessage(ConstructClass):
+ subcon = Struct(
+ "hdr" / MsgHeader,
+ "payload" / Switch(this.hdr.msg_type, {
+ 0x09: IOCtlPtrReq,
+ 0x0c: IOCtlResp,
+ 0x23: H2DMailboxData,
+ 0x24: D2HMailboxData,
+
+ }, default=HexDump(GreedyBytes))
+ )
+
+class RingState:
+ pass
+
+class WLANRingTracer(PCIeDevTracer):
+ def __init__(self, wlan, info):
+ self.wlan = wlan
+ self.hv = wlan.hv
+ self.p = wlan.hv.p
+ self.u = wlan.hv.u
+ self.ringid = self.RX, self.PTR_IDX
+ if self.ringid in wlan.state.rings:
+ self.state = wlan.state.rings[self.ringid]
+ else:
+ self.state = wlan.state.rings[self.ringid] = RingState()
+ self.state.rptr = 0
+
+ self.info = info
+ assert info.item_size == self.ITEM_SIZE
+ self.base_addr = info.base_addr
+ self.count = info.count
+
+ if self.RX:
+ d2h_paddr = self.wlan.iotranslate(self.wlan.state.d2h_w_idx_ha + 4 * self.PTR_IDX, 4)[0][0]
+ assert d2h_paddr is not None
+ self.hv.add_tracer(irange(d2h_paddr, 4), self.wlan.ident, TraceMode.SYNC,
+ read=self.d2h_w_idx_readhook)
+
+ def d2h_w_idx_readhook(self, evt):
+ self.log("W idx read")
+ self.poll()
+
+ def poll(self):
+ if self.RX:
+ wptr = self.wlan.ioread(self.wlan.state.d2h_w_idx_ha + 4 * self.PTR_IDX, 4)
+ else:
+ wptr = self.wlan.ioread(self.wlan.state.h2d_w_idx_ha + 4 * self.PTR_IDX, 4)
+
+ wptr = struct.unpack("<I", wptr)[0]
+
+ while wptr != self.state.rptr:
+ off = self.state.rptr * self.ITEM_SIZE
+ addr = self.base_addr + off
+ data = self.wlan.ioread(addr, self.ITEM_SIZE)
+ self.pkt(data)
+ self.state.rptr = (self.state.rptr + 1) % self.count
+
+ def pkt(self, data):
+ self.log("Got packet:")
+ pkt = RingMessage.parse(data)
+ self.log(pkt)
+ if pkt.hdr.msg_type == 0x09:
+ self.wlan.ioctlptr_req(pkt)
+ if pkt.hdr.msg_type == 0x0c:
+ self.wlan.ioctlresp(pkt)
+
+ def log(self, msg):
+ self.wlan.log(f"[{self.NAME}]{msg!s}")
+
+class WLANControlSubmitRingTracer(WLANRingTracer):
+ NAME = "CTLSubmit"
+ PTR_IDX = 0
+ RX = False
+ ITEM_SIZE = 0x28
+
+class WLANControlCompleteRingTracer(WLANRingTracer):
+ NAME = "CTLCompl"
+ PTR_IDX = 0
+ RX = True
+ ITEM_SIZE = 0x18
+
+class RingInfo:
+ def __init__(self):
+ self.count = None
+ self.item_size = None
+ self.base_addr = None
+
+ def ready(self):
+ return self.count is not None and self.item_size is not None and self.base_addr is not None
+
+class WLANTracer(PCIeDevTracer):
+ DEFAULT_MODE = TraceMode.SYNC
+
+ SRAM_BASE = 0x740000
+ SRAM_SIZE = 0x1f9000
+
+ BARMAPS = [WLANBAR0, None, None]
+ CFGMAP = WLANCfgSpace
+
+ RINGS = [
+ WLANControlSubmitRingTracer,
+ None, # RXPost
+ WLANControlCompleteRingTracer,
+ None, # TX complete
+ None, # RX complete
+ ]
+
+ CMDS = {
+ 1: "GET_VERSION",
+ 2: "UP",
+ 3: "DOWN",
+ 262: "GET_VAR",
+ 263: "SET_VAR",
+ }
+
+ def __init__(self, hv, apcie, bus, dev, fn, dart_path=None, verbose=False):
+ super().__init__(hv, apcie, bus, dev, fn, verbose=verbose)
+ self.u = hv.u
+ self.p = hv.p
+ self.dart_path = dart_path
+ self.dart_dev = None
+ self.dart = None
+ self.rings = {}
+
+ def init_state(self):
+ super().init_state()
+ self.state.shared_base = None
+ self.state.ring_info_base = None
+ self.state.ring_mem_base = None
+ self.state.tcm_base = None
+ self.state.tcm_size = None
+ self.state.ring_info = None
+ self.state.ring_mem = None
+ self.state.ring_info_data = {}
+ self.state.rings = {}
+ self.state.ioctls = {}
+ self.state.h2d_w_idx_ha = None
+ self.state.h2d_r_idx_ha = None
+ self.state.d2h_w_idx_ha = None
+ self.state.d2h_r_idx_ha = None
+
+ def config_dart(self):
+ # Ugly...
+ if self.dart_dev is None:
+ for i in range (16):
+ ttbr = self.dart.regs.TTBR[i, 0].reg
+ if ttbr.VALID:
+ self.log(f"DART device: {i}")
+ self.dart_dev = i
+ break
+ else:
+ raise Exception("Failed to find DART device")
+
+ def ioread(self, addr, size):
+ self.config_dart()
+ return self.dart.ioread(self.dart_dev, addr, size)
+
+ def iotranslate(self, addr, size):
+ self.config_dart()
+ return self.dart.iotranslate(self.dart_dev, addr, size)
+
+ def r_SHARED_BASE(self, base):
+ if base.value & 0xffff == (base.value >> 16) ^ 0xffff:
+ return
+
+ self.state.shared_base = base.value
+ self.update_shared()
+
+ def w_H2D_W_IDX_HOSTADDR(self, addr):
+ self.state.h2d_w_idx_ha = addr.value
+
+ def w_H2D_R_IDX_HOSTADDR(self, addr):
+ self.state.h2d_r_idx_ha = addr.value
+
+ def w_D2H_W_IDX_HOSTADDR(self, addr):
+ self.state.d2h_w_idx_ha = addr.value
+
+ def w_D2H_R_IDX_HOSTADDR(self, addr):
+ self.state.d2h_r_idx_ha = addr.value
+
+ def w_MAX_ITEM(self, val, index):
+ info = self.state.ring_info_data.setdefault(index, RingInfo())
+ info.count = val.value
+ self.update_ring(index)
+
+ def w_LEN_ITEMS(self, val, index):
+ info = self.state.ring_info_data.setdefault(index, RingInfo())
+ info.item_size = val.value
+ self.update_ring(index)
+
+ def w_BASE_ADDR(self, val, index):
+ info = self.state.ring_info_data.setdefault(index, RingInfo())
+ info.base_addr = val.value
+ self.update_ring(index)
+
+ def update_ring(self, idx):
+ if idx not in self.state.ring_info_data:
+ return
+ info = self.state.ring_info_data[idx]
+ if not info.ready():
+ return
+
+ if idx in self.rings:
+ return
+
+ if idx > len(self.RINGS):
+ return
+
+ ringcls = self.RINGS[idx]
+
+ if ringcls is None:
+ return
+
+ self.rings[idx] = ringcls(self, info)
+
+ def w_H2D_MAILBOX_0(self, val):
+ ring = self.rings.get(2, None)
+ if ring is not None:
+ ring.poll()
+
+ ring = self.rings.get(0, None)
+ if ring is not None:
+ ring.poll()
+
+ w_H2D_MAILBOX_0_64 = w_H2D_MAILBOX_0
+ w_H2D_MAILBOX_0_ALT = w_H2D_MAILBOX_0
+
+ def ioctlptr_req(self, pkt):
+ data = self.ioread(pkt.payload.host_input_buf_addr, pkt.payload.input_buf_len)
+ cmd = self.CMDS.get(pkt.payload.cmd, "unk")
+ self.log(f"IOCTL request ({cmd}):")
+ chexdump(data, print_fn = self.log)
+ self.state.ioctls[pkt.payload.trans_id] = pkt
+
+ def ioctlresp(self, pkt):
+ req = self.state.ioctls.get(pkt.payload.trans_id, None)
+ if req is None:
+ self.log(f"ERROR: unknown transaction ID {pkt.payload.trans_id:#x}")
+ return
+
+ data = self.ioread(req.payload.host_input_buf_addr, req.payload.output_buf_len)
+ cmd = self.CMDS.get(pkt.payload.cmd, "unk")
+ self.log(f"IOCTL response ({cmd}):")
+ chexdump(data, print_fn = self.log)
+ del self.state.ioctls[pkt.payload.trans_id]
+
+ def trace_bar(self, idx, start, size):
+ if idx != 2:
+ return super().trace_bar(idx, start, size)
+
+ self.state.tcm_base = start
+ self.state.tcm_size = size
+
+ self.update_tcm_tracers()
+
+ def update_tcm_tracers(self):
+ if self.state.tcm_base is None:
+ return
+
+ if self.dart is None:
+ self.dart = DART.from_adt(self.u, self.dart_path)
+
+ self.trace_regmap(self.state.tcm_base + self.SRAM_BASE + self.SRAM_SIZE - 8, 8,
+ WLANSRAMEnd, name="sram")
+
+ def update_shared(self):
+ base = self.state.shared_base
+ if base is None:
+ return
+
+ if self.state.ring_info_base is None:
+ self.shared = WLANSRAMShared(self.hv.u, self.state.tcm_base + base)
+
+ self.log("Reading shared info")
+ self.shared.dump_regs()
+
+ self.state.ring_info_base = self.shared.RING_INFO_ADDR.val
+
+ if self.state.ring_mem_base is None:
+ self.ring_info = WLANSRAMRingInfo(self.hv.u,
+ self.state.tcm_base + self.state.ring_info_base)
+ self.log("Reading ring info")
+ self.ring_info.dump_regs()
+
+ self.state.ring_mem_base = self.ring_info.RINGMEM.val
+
+ self.trace_regmap(self.state.tcm_base + base, 0x100,
+ WLANSRAMShared, name="shared")
+
+ self.trace_regmap(self.state.tcm_base + self.state.ring_info_base, 0x40,
+ WLANSRAMRingInfo, name="ringinfo")
+
+ self.ring_mem = WLANSRAMRingMem(self.hv.u,
+ self.state.tcm_base + self.state.ring_mem_base)
+ self.log("Reading ring mem")
+ self.ring_mem.dump_regs()
+
+ self.trace_regmap(self.state.tcm_base + self.state.ring_mem_base,
+ COMMON_RING_CNT * 0x10, WLANSRAMRingMem, name="ringmem")
+
+ def start(self):
+ super().start()
+
+ self.update_tcm_tracers()
+ self.update_shared()
+ for i in range(len(self.RINGS)):
+ self.update_ring(i)
+
+wlan_tracer = WLANTracer(hv, "/arm-io/apcie",
+ 4, 0, 0, "/arm-io/dart-apcie0")
+
+wlan_tracer.start()
diff --git a/tools/proxyclient/hv/trace_z2.py b/tools/proxyclient/hv/trace_z2.py
new file mode 100644
index 0000000..c52e80e
--- /dev/null
+++ b/tools/proxyclient/hv/trace_z2.py
@@ -0,0 +1,221 @@
+# SPDX-License-Identifier: MIT
+
+import struct
+from construct import *
+
+from m1n1.hv import TraceMode
+from m1n1.proxyutils import RegMonitor
+from m1n1.utils import *
+
+from m1n1.trace import ADTDevTracer
+from m1n1.trace.asc import ASCTracer, ASCRegs, EP, EPState, msg, msg_log, DIR
+from m1n1.trace.dart import DARTTracer
+from m1n1.trace.gpio import GPIOTracer
+from m1n1.trace.spi import SPITracer
+
+DARTTracer = DARTTracer._reloadcls()
+ASCTracer = ASCTracer._reloadcls()
+SPITracer = SPITracer._reloadcls()
+
+# SPI HID transport tracer for 2021 macbook models
+
+kbd_node = None
+for node in hv.adt.walk_tree():
+ try:
+ if node.compatible[0] == "spi-1,spimc":
+ for c in node:
+ try:
+ if c.compatible[0] == "hid-transport,k1":
+ kbd_node = c
+ break
+ except AttributeError:
+ continue
+ except AttributeError:
+ continue
+ if kbd_node is not None:
+ break
+
+
+class Z2Tracer(SPITracer):
+ def start(self):
+ super().start()
+ self.txbuffer = []
+ self.rxbuffer = []
+ self.want_bytes = 0
+ self.state = Z2Tracer.preboot
+ def w_TXDATA(self, data):
+ self.txbuffer.append(data.value)
+ self.check_msg_finished()
+ def r_RXDATA(self, data):
+ self.rxbuffer.append(data.value)
+ self.check_msg_finished()
+ def check_msg_finished(self):
+ if min(len(self.txbuffer), len(self.rxbuffer)) < self.want_bytes:
+ return
+ self.state(self)
+ def bad_state(self):
+ pass
+ def error(self):
+ self.log(f"RXBUF {' '.join(hex(x) for x in self.rxbuffer)}")
+ self.log(f"TXBUF {' '.join(hex(x) for x in self.txbuffer)}")
+ self.log(f"state: {self.state}")
+ self.log("Tracer desynchronized, shutting down")
+ self.state = Z2Tracer.bad_state
+ def consume_bytes(self, n):
+ self.txbuffer = self.txbuffer[n:]
+ self.rxbuffer = self.rxbuffer[n:]
+ def preboot(self):
+ if self.txbuffer[0] == 0:
+ self.want_bytes = 4
+ self.state = Z2Tracer.init_zeros
+ elif self.txbuffer[0] == 0x1e:
+ self.want_bytes = 16
+ self.state = Z2Tracer.processing_init_data
+ else:
+ self.error()
+ def init_zeros(self):
+ self.log("sent 4 zeroes")
+ self.consume_bytes(4)
+ self.state = Z2Tracer.preboot
+ def processing_init_data(self):
+ self.log("Sent init data")
+ self.want_bytes = 2
+ self.consume_bytes(16)
+ self.state = Z2Tracer.main_hbpp
+ def main_hbpp(self):
+ if self.txbuffer[0] == 0x1a and self.txbuffer[1] == 0xa1:
+ self.log("Sent int ack")
+ self.consume_bytes(2)
+ elif self.txbuffer[0] == 0x18 and self.txbuffer[1] == 0xe1:
+ self.log("Sent nop")
+ self.consume_bytes(2)
+ elif self.txbuffer[0] == 0x1f and self.txbuffer[1] == 0x01:
+ self.log("Sent request cal")
+ self.consume_bytes(2)
+ elif self.txbuffer[0] == 0x30 and self.txbuffer[1] == 0x01:
+ self.state = Z2Tracer.send_blob_cmd
+ self.want_bytes = 10
+ elif self.txbuffer[0] == 0x1e and self.txbuffer[1] == 0x33:
+ self.state = Z2Tracer.send_rmw_cmd
+ self.want_bytes = 16
+ elif self.txbuffer[0] == 0xee and self.txbuffer[1] == 0x00:
+ self.state = Z2Tracer.main_z2
+ self.want_bytes = 16
+ else:
+ self.error()
+ def send_blob_cmd(self):
+ length = (self.txbuffer[2] << 8 | self.txbuffer[3]) * 4
+ self.consume_bytes(10)
+ self.want_bytes = length + 4
+ self.log(f"Sending blob of length {length}")
+ self.state = Z2Tracer.send_blob_tail
+ def send_blob_tail(self):
+ self.log("Finished sendind blob")
+ self.consume_bytes(self.want_bytes)
+ self.want_bytes = 2
+ self.state = Z2Tracer.main_hbpp
+ def send_rmw_cmd(self):
+ self.log('Sent RMW command')
+ self.want_bytes = 2
+ self.consume_bytes(16)
+ self.state = Z2Tracer.main_hbpp
+ def main_z2(self):
+ if self.txbuffer[0] == 0xee:
+ self.log("sent wake cmd")
+ self.consume_bytes(16)
+ elif self.txbuffer[0] == 0xe2:
+ self.log("sent get device info cmd")
+ self.consume_bytes(16)
+ self.state = Z2Tracer.read_device_info_reply
+ elif self.txbuffer[0] == 0xeb:
+ length = (self.rxbuffer[1] | (self.rxbuffer[2] << 8)) + 5
+ length = (length + 3) & (-4)
+ self.consume_bytes(16)
+ self.want_bytes = length
+ self.state = Z2Tracer.read_interrupt_data
+ elif self.txbuffer[0] == 0xe3:
+ self.log(f"got report info for {self.txbuffer[1]}, len is {self.rxbuffer[3]}")
+ self.consume_bytes(16)
+ elif self.txbuffer[0] == 0xe7:
+ self.want_bytes = self.txbuffer[3] + 5
+ self.consume_bytes(16)
+ self.state = Z2Tracer.reading_report_long
+ elif self.txbuffer[0] == 0xe6:
+ self.consume_bytes(16)
+ self.state = Z2Tracer.read_report_reply
+ else:
+ self.error()
+ def reading_report_long(self):
+ self.log(f"got report {' '.join(hex(x) for x in self.rxbuffer)}")
+ self.consume_bytes(self.want_bytes)
+ self.want_bytes = 16
+ self.state = Z2Tracer.main_z2
+ def read_interrupt_data(self):
+ data = self.rxbuffer[5:]
+ tstamp2 = data[4] | (data[5] << 8) | (data[6] << 16)
+ tx = [f"TS1 {hex(data[1])} TS2 {tstamp2} UNK1: {mxformat(data[7:16])} UNK2: {mxformat(data[17:24])}"]
+ if len(data) >= 16:
+ ntouch = data[16]
+ for i in range(ntouch):
+ ptr = 24 + 30 * i
+ finger = data[ptr]
+ state = data[ptr + 1]
+ x = data[ptr + 4] | (data[ptr + 5] << 8)
+ y = data[ptr + 6] | (data[ptr + 7] << 8)
+ wj = data[ptr + 12] | (data[ptr + 13] << 8)
+ wn = data[ptr + 14] | (data[ptr + 15] << 8)
+ dg = data[ptr + 16] | (data[ptr + 17] << 8)
+ prs = data[ptr + 18] | (data[ptr + 19] << 8)
+ tx.append(f"F: {hex(finger)} S: {hex(state)} X: {x} Y: {y} MAJ: {wj} MIN: {wn} ANG: {dg} PRS: {prs} UNK1: {mxformat(data[ptr + 2:ptr+4])} UNK2: {mxformat(data[ptr + 8:ptr+12])} UNK3: {mxformat(data[ptr + 20:ptr+30])}")
+ self.log(';'.join(tx))
+ else:
+ self.log(f"??? {mxformat(data)}")
+ self.consume_bytes(self.want_bytes)
+ self.want_bytes = 16
+ self.state = Z2Tracer.main_z2
+ def read_device_info_reply(self):
+ self.log(f"got device info {' '.join(hex(x) for x in self.rxbuffer[:16])}")
+ self.consume_bytes(16)
+ self.state = Z2Tracer.main_z2
+ def read_report_reply(self):
+ self.log(f"got report {' '.join(hex(x) for x in self.rxbuffer[:16])}")
+ self.consume_bytes(16)
+ self.state = Z2Tracer.main_z2
+
+def mxformat(ls):
+ return ''.join(xformat(x) for x in ls)
+def xformat(x):
+ x = hex(x)[2:]
+ if len(x) == 1:
+ x = '0' + x
+ return x
+
+
+
+# trace interrupts
+aic_phandle = getattr(hv.adt["/arm-io/aic"], "AAPL,phandle")
+spi_node = kbd_node._parent
+
+#if getattr(spi_node, "interrupt-parent") == aic_phandle:
+# for irq in getattr(spi_node, "interrupts"):
+# hv.trace_irq(node.name, irq, 1, hv.IRQTRACE_IRQ)
+#for irq in hv.adt['/arm-io/gpio'].interrupts:
+# hv.trace_irq('/arm-io/gpio', irq, 1, hv.IRQTRACE_IRQ)
+
+spi_tracer = Z2Tracer(hv, "/arm-io/" + spi_node.name)
+spi_tracer.start()
+
+spi_pins_nub = {
+ 0x0: "clock32khz",
+}
+
+#gpio_tracer_nub = GPIOTracer(hv, "/arm-io/nub-gpio", spi_pins_nub, verbose=0)
+#gpio_tracer_nub.start()
+
+spi_pins = {
+ 0x6d: "enable_cs",
+ 0x8b: "reset"
+}
+
+#gpio_tracer = GPIOTracer(hv, "/arm-io/gpio", spi_pins, verbose=0)
+#gpio_tracer.start()
diff --git a/tools/proxyclient/m1n1/__init__.py b/tools/proxyclient/m1n1/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tools/proxyclient/m1n1/__init__.py
diff --git a/tools/proxyclient/m1n1/adt.py b/tools/proxyclient/m1n1/adt.py
new file mode 100644
index 0000000..8cc7ebe
--- /dev/null
+++ b/tools/proxyclient/m1n1/adt.py
@@ -0,0 +1,713 @@
+# SPDX-License-Identifier: MIT
+import itertools, fnmatch, sys
+from construct import *
+import sys
+
+from .utils import AddrLookup, FourCC, SafeGreedyRange
+
+__all__ = ["load_adt"]
+
+ADTPropertyStruct = Struct(
+ "name" / PaddedString(32, "ascii"),
+ "size" / Int32ul,
+ "value" / Bytes(this.size & 0x7fffffff)
+)
+
+ADTNodeStruct = Struct(
+ "property_count" / Int32ul,
+ "child_count" / Int32ul,
+ "properties" / Array(this.property_count, Aligned(4, ADTPropertyStruct)),
+ "children" / Array(this.child_count, LazyBound(lambda: ADTNodeStruct))
+)
+
+ADTStringList = SafeGreedyRange(CString("ascii"))
+
+ADT2Tuple = Array(2, Hex(Int64ul))
+ADT3Tuple = Array(3, Hex(Int64ul))
+
+Function = Struct(
+ "phandle" / Int32ul,
+ "name" / FourCC,
+ "args" / SafeGreedyRange(Int32ul),
+)
+
+STD_PROPERTIES = {
+ "cpu-impl-reg": ADT2Tuple,
+ "name": CString("ascii"),
+ "compatible": ADTStringList,
+ "model": CString("ascii"),
+ "#size-cells": Int32ul,
+ "#address-cells": Int32ul,
+ "clock-ids": SafeGreedyRange(Int32ul),
+ "clock-gates": SafeGreedyRange(Int32ul),
+ "power-gates": SafeGreedyRange(Int32ul),
+}
+
+PMAPIORanges = SafeGreedyRange(Struct(
+ "addr" / Hex(Int64ul),
+ "size" / Hex(Int64ul),
+ "flags" / Hex(Int32ul),
+ "name" / FourCC,
+))
+
+PMGRPSRegs = SafeGreedyRange(Struct(
+ "reg" / Int32ul,
+ "offset" / Hex(Int32ul),
+ "mask" / Hex(Int32ul),
+))
+
+PMGRPerfRegs = SafeGreedyRange(Struct(
+ "reg" / Int32ul,
+ "offset" / Hex(Int32ul),
+ "size" / Hex(Int32ul),
+ "unk" / Int32ul,
+))
+
+PMGRPWRGateRegs = SafeGreedyRange(Struct(
+ "reg" / Int32ul,
+ "offset" / Hex(Int32ul),
+ "mask" / Hex(Int32ul),
+ "unk" / Hex(Int32ul),
+))
+
+PMGRDeviceFlags = BitStruct(
+ "b7" / Flag,
+ "b6" / Flag,
+ "perf" / Flag,
+ "no_ps" / Flag,
+ "critical" / Flag,
+ "b2" / Flag,
+ "notify_pmp" / Flag,
+ "on" / Flag,
+)
+
+PMGRDevices = SafeGreedyRange(Struct(
+ "flags" / PMGRDeviceFlags,
+ "unk1_0" / Int8ul,
+ "unk1_1" / Int8ul,
+ "unk1_2" / Int8ul,
+ "parents" / Array(2, Int16ul),
+ "perf_idx" / Int8ul,
+ "perf_block" / Int8ul,
+ "psidx" / Int8ul,
+ "psreg" / Int8ul,
+ "unk2_0" / Int16ul,
+ "pd" / Int8ul,
+ "ps_cfg16" / Int8ul,
+ Const(0, Int32ul),
+ Const(0, Int32ul),
+ "unk2_3" / Int16ul,
+ "id" / Int16ul,
+ "unk3" / Int32ul,
+ "name" / PaddedString(16, "ascii")
+))
+
+PMGRClocks = SafeGreedyRange(Struct(
+ "perf_idx" / Int8ul,
+ "perf_block" / Int8ul,
+ "unk" / Int8ul,
+ "id" / Int8ul,
+ Const(0, Int32ul),
+ "name" / PaddedString(16, "ascii"),
+))
+
+PMGRPowerDomains = SafeGreedyRange(Struct(
+ "unk" / Const(0, Int8ul),
+ "perf_idx" / Int8ul,
+ "perf_block" / Int8ul,
+ "id" / Int8ul,
+ Const(0, Int32ul),
+ "name" / PaddedString(16, "ascii"),
+))
+
+PMGRDeviceBridges = SafeGreedyRange(Struct(
+ "idx" / Int32ub,
+ "subdevs" / HexDump(Bytes(0x48)),
+))
+
+PMGREvents = SafeGreedyRange(Struct(
+ "unk1" / Int8ul,
+ "unk2" / Int8ul,
+ "unk3" / Int8ul,
+ "id" / Int8ul,
+ "perf2_idx" / Int8ul,
+ "perf2_block" / Int8ul,
+ "perf_idx" / Int8ul,
+ "perf_block" / Int8ul,
+ "name" / PaddedString(16, "ascii"),
+))
+
+GPUPerfState = Struct(
+ "freq" / Int32ul,
+ "volt" / Int32ul,
+)
+
+SpeakerConfig = Struct(
+ "rx_slot" / Int8ul,
+ "amp_gain" / Int8ul,
+ "vsense_slot" / Int8ul,
+ "isense_slot" / Int8ul,
+)
+
+DCBlockerConfig = Struct(
+ "dc_blk0" / Hex(Int8ul),
+ "dc_blk1" / Hex(Int8ul),
+ "pad" / Hex(Int16ul),
+)
+
+Coef = ExprAdapter(Int32ul,
+ lambda x, ctx: (x - ((x & 0x1000000) << 1)) / 65536,
+ lambda x, ctx: int(round(x * 65536)) & 0x1ffffff)
+
+MTRPolynomFuseAGX = GreedyRange(Struct(
+ "id" / Int32ul,
+ "data" / Prefixed(Int32ul, GreedyRange(Coef)),
+))
+
+DEV_PROPERTIES = {
+ "pmgr": {
+ "*": {
+ "clusters": SafeGreedyRange(Int32ul),
+ "devices": PMGRDevices,
+ "ps-regs": PMGRPSRegs,
+ "perf-regs": PMGRPerfRegs,
+ "pwrgate-regs": PMGRPWRGateRegs,
+ "power-domains": PMGRPowerDomains,
+ "clocks": PMGRClocks,
+ "device-bridges": PMGRDeviceBridges,
+ "voltage-states*": SafeGreedyRange(Int32ul),
+ "events": PMGREvents,
+ "mtr-polynom-fuse-agx": MTRPolynomFuseAGX,
+ }
+ },
+ "clpc": {
+ "*": {
+ "events": SafeGreedyRange(Int32ul),
+ "devices": SafeGreedyRange(Int32ul),
+ }
+ },
+ "soc-tuner": {
+ "*": {
+ "device-set-*": SafeGreedyRange(Int32ul),
+ "mcc-configs": SafeGreedyRange(Int32ul),
+ }
+ },
+ "mcc": {
+ "*": {
+ "dramcfg-data": SafeGreedyRange(Int32ul),
+ "config-data": SafeGreedyRange(Int32ul),
+ }
+ },
+ "*pmu*": {
+ "*": {
+ "info-*name*": CString("ascii"),
+ "info-*": SafeGreedyRange(Hex(Int32ul)),
+ },
+ },
+ "stockholm-spmi": {
+ "*": {
+ "required-functions": ADTStringList,
+ },
+ },
+ "sgx": {
+ "*": {
+ "perf-states*": SafeGreedyRange(GPUPerfState),
+ "*-kp": Float32l,
+ "*-ki": Float32l,
+ "*-ki-*": Float32l,
+ "*-gain*": Float32l,
+ "*-scale*": Float32l,
+ }
+ },
+ "arm-io": {
+ "*": {
+ "clock-frequencies": SafeGreedyRange(Int32ul),
+ "clock-frequencies-regs": SafeGreedyRange(Hex(Int64ul)),
+ "clock-frequencies-nclk": SafeGreedyRange(Int32ul),
+ },
+ },
+ "defaults": {
+ "*": {
+ "pmap-io-ranges": PMAPIORanges,
+ }
+ },
+ "audio-*": {
+ "*": {
+ "speaker-config": SafeGreedyRange(SpeakerConfig),
+ "amp-dcblocker-config": DCBlockerConfig,
+ },
+ },
+ "*aop-audio*": {
+ "*": {
+ "clockSource": FourCC,
+ "identifier": FourCC,
+ },
+ },
+ "*alc?/audio-leap-mic*": {
+ "*": {
+ "audio-stream-formatter": FourCC,
+ }
+ }
+}
+
+def parse_prop(node, path, node_name, name, v, is_template=False):
+ t = None
+
+ if is_template:
+ t = CString("ascii")
+
+ dev_props = None
+ for k, pt in DEV_PROPERTIES.items():
+ if fnmatch.fnmatch(path, k):
+ dev_props = pt
+ break
+
+ if not dev_props:
+ for k, pt in DEV_PROPERTIES.items():
+ if fnmatch.fnmatch(node_name, k):
+ dev_props = pt
+ break
+
+ possible_match = False
+ if dev_props:
+ for compat_match, cprops in dev_props.items():
+ for k, pt in cprops.items():
+ if fnmatch.fnmatch(name, k):
+ possible_match = True
+ break
+
+ if possible_match:
+ try:
+ compat = node.compatible[0]
+ except AttributeError:
+ compat = ""
+
+ for compat_match, cprops in dev_props.items():
+ if fnmatch.fnmatch(compat, compat_match):
+ for k, pt in cprops.items():
+ if fnmatch.fnmatch(name, k):
+ t = pt
+ break
+ else:
+ continue
+ break
+
+ if v == b'' or v is None:
+ return None, None
+
+ if name.startswith("function-"):
+ if len(v) == 4:
+ t = FourCC
+ else:
+ t = Function
+
+ if name == "reg" and path != "/device-tree/memory":
+ n = node._parent
+ while n is not None and n._parent is not None:
+ if "ranges" not in n._properties:
+ break
+ n = n._parent
+ else:
+ rs = node._reg_struct
+ if len(v) % rs.sizeof() == 0:
+ t = SafeGreedyRange(rs)
+
+ elif name == "ranges":
+ try:
+ ac, sc = node.address_cells, node.size_cells
+ except AttributeError:
+ return None, v
+ pac, _ = node._parent.address_cells, node._parent.size_cells
+ at = Hex(Int64ul) if ac == 2 else Array(ac, Hex(Int32ul))
+ pat = Hex(Int64ul) if pac == 2 else Array(pac, Hex(Int32ul))
+ st = Hex(Int64ul) if sc == 2 else Array(sc, Hex(Int32ul))
+ t = SafeGreedyRange(Struct("bus_addr" / at, "parent_addr" / pat, "size" / st))
+
+ elif name == "interrupts":
+ # parse "interrupts" as Array of Int32ul, wrong for nodes whose
+ # "interrupt-parent" has "interrupt-cells" = 2
+ # parsing this correctly would require a second pass
+ t = Array(len(v) // 4, Int32ul)
+
+ if t is not None:
+ v = Sequence(t, Terminated).parse(v)[0]
+ return t, v
+
+ if name in STD_PROPERTIES:
+ t = STD_PROPERTIES[name]
+ elif v and v[-1] == 0 and all(0x20 <= i <= 0x7e for i in v[:-1]):
+ t = CString("ascii")
+ elif len(v) == 4:
+ t = Int32ul
+ elif len(v) == 8:
+ t = Int64ul
+ elif len(v) == 16 and all(v[i] == 0 for i in (6, 7, 14, 15)):
+ t = ADT2Tuple
+
+ if t is not None:
+ try:
+ v = Sequence(t, Terminated).parse(v)[0]
+ except:
+ print("Failed to parse:", path, name, v.hex())
+ raise
+
+ return t, v
+
+def build_prop(path, name, v, t=None):
+ if v is None:
+ return b''
+ if t is not None:
+ return t.build(v)
+
+ if isinstance(v, bytes):
+ return v
+
+ if name in STD_PROPERTIES:
+ t = STD_PROPERTIES[name]
+ elif isinstance(v, str):
+ t = CString("ascii")
+ elif isinstance(v, int):
+ if v > 0xffffffff:
+ t = Int64ul
+ else:
+ t = Int32ul
+ elif isinstance(v, float):
+ t = Float32l
+ elif isinstance(v, tuple) and all(isinstance(i, int) for i in v):
+ t = Array(len(v), Int32ul)
+
+ return t.build(v)
+
+class ADTNode:
+ def __init__(self, val=None, path="/", parent=None):
+ self._children = []
+ self._properties = {}
+ self._types = {}
+ self._parent_path = path
+ self._parent = parent
+
+ if val is not None:
+ for p in val.properties:
+ if p.name == "name":
+ _name = p.value.decode("ascii").rstrip("\0")
+ break
+ else:
+ raise ValueError(f"Node in {path} has no name!")
+
+ path = self._parent_path + _name
+
+ for p in val.properties:
+ is_template = bool(p.size & 0x80000000)
+ try:
+ t, v = parse_prop(self, path, _name, p.name, p.value, is_template)
+ self._types[p.name] = t, is_template
+ self._properties[p.name] = v
+ except Exception as e:
+ print(f"Exception parsing {path}.{p.name} value {p.value.hex()}:", file=sys.stderr)
+ raise
+
+ # Second pass
+ for k, (t, is_template) in self._types.items():
+ if t is None:
+ t, v = parse_prop(self, path, _name, k, self._properties[k], is_template)
+ self._types[k] = t, is_template
+ self._properties[k] = v
+
+ for c in val.children:
+ node = ADTNode(c, f"{self._path}/", parent=self)
+ self._children.append(node)
+
+ @property
+ def _path(self):
+ return self._parent_path + self.name
+
+ def __getitem__(self, item):
+ if isinstance(item, str):
+ while item.startswith("/"):
+ item = item[1:]
+ if "/" in item:
+ a, b = item.split("/", 1)
+ return self[a][b]
+ for i in self._children:
+ if i.name == item:
+ return i
+ raise KeyError(f"Child node '{item}' not found")
+ return self._children[item]
+
+ def __setitem__(self, item, value):
+ if isinstance(item, str):
+ while item.startswith("/"):
+ item = item[1:]
+ if "/" in item:
+ a, b = item.split("/", 1)
+ self[a][b] = value
+ return
+ for i, c in enumerate(self._children):
+ if c.name == item:
+ self._children[i] = value
+ break
+ else:
+ self._children.append(value)
+ else:
+ self._children[item] = value
+
+ def __delitem__(self, item):
+ if isinstance(item, str):
+ while item.startswith("/"):
+ item = item[1:]
+ if "/" in item:
+ a, b = item.split("/", 1)
+ del self[a][b]
+ return
+ for i, c in enumerate(self._children):
+ if c.name == item:
+ del self._children[i]
+ return
+ raise KeyError(f"Child node '{item}' not found")
+
+ del self._children[item]
+
+ def __contains__(self, item):
+ if isinstance(item, str):
+ while item.startswith("/"):
+ item = item[1:]
+ if "/" in item:
+ a, b = item.split("/", 1)
+ return b in self[a]
+ for c in self._children:
+ if c.name == item:
+ return True
+ return False
+
+ return item in self._children
+
+ def __getattr__(self, attr):
+ attr = attr.replace("_", "-")
+ attr = attr.replace("--", "_")
+ if attr in self._properties:
+ return self._properties[attr]
+ raise AttributeError(attr)
+
+ def __setattr__(self, attr, value):
+ if attr[0] == "_":
+ self.__dict__[attr] = value
+ return
+ attr = attr.replace("_", "-")
+ attr = attr.replace("--", "_")
+ self._properties[attr] = value
+
+ def __delattr__(self, attr):
+ if attr[0] == "_":
+ del self.__dict__[attr]
+ return
+ del self._properties[attr]
+
+ def getprop(self, name, default=None):
+ return self._properties.get(name, default)
+
+ @property
+ def address_cells(self):
+ try:
+ return self._properties["#address-cells"]
+ except KeyError:
+ raise AttributeError("#address-cells")
+
+ @property
+ def size_cells(self):
+ try:
+ return self._properties["#size-cells"]
+ except KeyError:
+ raise AttributeError("#size-cells")
+
+ @property
+ def interrupt_cells(self):
+ try:
+ return self._properties["#interrupt-cells"]
+ except KeyError:
+ raise AttributeError("#interrupt-cells")
+
+ def _fmt_prop(self, k, v):
+ t, is_template = self._types.get(k, (None, False))
+ if is_template:
+ return f"<< {v} >>"
+ elif isinstance(v, ListContainer):
+ return f"[{', '.join(self._fmt_prop(k, i) for i in v)}]"
+ elif isinstance(v, bytes):
+ if all(i == 0 for i in v):
+ return f"zeroes({len(v):#x})"
+ else:
+ return v.hex()
+ elif k.startswith("function-"):
+ if isinstance(v, str):
+ return f"{v}()"
+ elif v is None:
+ return f"None"
+ else:
+ args = []
+ for arg in v.args:
+ b = arg.to_bytes(4, "big")
+ is_ascii = all(0x20 <= c <= 0x7e for c in b)
+ args.append(f"{arg:#x}" if not is_ascii else f"'{b.decode('ascii')}'")
+ return f"{v.phandle}:{v.name}({', '.join(args)})"
+ name.startswith("function-")
+ else:
+ return str(v)
+
+ def __str__(self, t=""):
+ return "\n".join([
+ t + f"{self.name} {{",
+ *(t + f" {k} = {self._fmt_prop(k, v)}" for k, v in self._properties.items() if k != "name"),
+ "",
+ *(i.__str__(t + " ") for i in self._children),
+ t + "}"
+ ])
+
+ def __repr__(self):
+ return f"<ADTNode {self.name}>"
+
+ def __iter__(self):
+ return iter(self._children)
+
+ @property
+ def _reg_struct(self):
+ ac, sc = self._parent.address_cells, self._parent.size_cells
+ return Struct(
+ "addr" / Hex(Int64ul) if ac == 2 else Array(ac, Hex(Int32ul)),
+ "size" / Hex(Int64ul) if sc == 2 else Array(sc, Hex(Int32ul))
+ )
+
+ def get_reg(self, idx):
+ reg = self.reg[idx]
+ addr = reg.addr
+ size = reg.size
+
+ return self._parent.translate(addr), size
+
+ def translate(self, addr):
+ node = self
+ while node is not None:
+ if "ranges" not in node._properties:
+ break
+ for r in node.ranges:
+ ba = r.bus_addr
+ # PCIe special case, because Apple really broke
+ # the spec here with their little endian antics
+ if isinstance(ba, list) and len(ba) == 3:
+ ba = (ba[0] << 64) | (ba[2] << 32) | ba[1]
+ if ba <= addr < (ba + r.size):
+ addr = addr - ba + r.parent_addr
+ break
+ node = node._parent
+
+ return addr
+
+ def to_bus_addr(self, addr):
+ node = self._parent
+
+ descend = []
+ while node is not None:
+ if "ranges" not in node._properties:
+ break
+ descend.append(node)
+ node = node._parent
+
+ for node in reversed(descend):
+ for r in node.ranges:
+ if r.parent_addr <= addr < (r.parent_addr + r.size):
+ addr = addr - r.parent_addr + r.bus_addr
+ break
+ return addr
+
+ def tostruct(self):
+ properties = []
+ for k,v in itertools.chain(self._properties.items()):
+ t, is_template = self._types.get(k, (None, False))
+ value = build_prop(self._path, k, v, t=t)
+ properties.append({
+ "name": k,
+ "size": len(value) | (0x80000000 if is_template else 0),
+ "value": value
+ })
+
+ data = {
+ "property_count": len(self._properties),
+ "child_count": len(self._children),
+ "properties": properties,
+ "children": [c.tostruct() for c in self._children]
+ }
+ return data
+
+ def build(self):
+ return ADTNodeStruct.build(self.tostruct())
+
+ def walk_tree(self):
+ yield self
+ for child in self:
+ yield from child
+
+ def build_addr_lookup(self):
+ lookup = AddrLookup()
+ for node in self.walk_tree():
+ reg = getattr(node, 'reg', None)
+ if not isinstance(reg, list):
+ continue
+
+ for index in range(len(reg)):
+ try:
+ addr, size = node.get_reg(index)
+ except AttributeError:
+ continue
+ if size == 0:
+ continue
+ lookup.add(range(addr, addr + size), node.name + f"[{index}]")
+
+ return lookup
+
+ def create_node(self, name):
+ while name.startswith("/"):
+ name = name[1:]
+ if "/" in name:
+ a, b = name.split("/", 1)
+ return self[a].create_node(b)
+
+ node = ADTNode(path=self._path + "/", parent=self)
+ node.name = name
+ node._types["reg"] = (SafeGreedyRange(node._reg_struct), False)
+ self[name] = node
+ return node
+
+def load_adt(data):
+ return ADTNode(ADTNodeStruct.parse(data))
+
+if __name__ == "__main__":
+ import sys, argparse, pathlib
+
+ parser = argparse.ArgumentParser(description='ADT test for m1n1')
+ parser.add_argument('input', type=pathlib.Path)
+ parser.add_argument('output', nargs='?', type=pathlib.Path)
+ parser.add_argument('-r', '--retrieve', help='retrieve and store the adt from m1n1', action='store_true')
+ parser.add_argument('-a', '--dump-addr', help='dump address lookup table', action='store_true')
+ args = parser.parse_args()
+
+ if args.retrieve:
+ if args.input.exists():
+ print('Error "{}" exists!'.format(args.input))
+ sys.exit()
+
+ from .setup import *
+ adt_data = u.get_adt()
+ args.input.write_bytes(adt_data)
+ else:
+ adt_data = args.input.read_bytes()
+
+ adt = load_adt(adt_data)
+ print(adt)
+ new_data = adt.build()
+ if args.output is not None:
+ args.output.write_bytes(new_data)
+ assert new_data == adt_data[:len(new_data)]
+ assert adt_data[len(new_data):] == bytes(len(adt_data) - len(new_data))
+
+ if args.dump_addr:
+ print("Address lookup table:")
+ print(adt.build_addr_lookup())
diff --git a/tools/proxyclient/m1n1/agx/__init__.py b/tools/proxyclient/m1n1/agx/__init__.py
new file mode 100644
index 0000000..26368ce
--- /dev/null
+++ b/tools/proxyclient/m1n1/agx/__init__.py
@@ -0,0 +1,343 @@
+# SPDX-License-Identifier: MIT
+import bisect, time
+
+from .object import GPUObject, GPUAllocator
+from .initdata import build_initdata
+from .channels import *
+from .event import GPUEventManager
+from ..proxy import IODEV
+from ..malloc import Heap
+from ..hw.uat import UAT, MemoryAttr
+from ..hw.agx import *
+from ..fw.agx import AGXASC
+from ..fw.agx.channels import ChannelInfoSet, ChannelInfo
+
+class AGXChannels:
+ pass
+
+class AGXQueue:
+ pass
+
+class AGX:
+ PAGE_SIZE = 0x4000
+ MAX_EVENTS = 128
+
+ def __init__(self, u):
+ self.start_time = time.time()
+ self.u = u
+ self.p = u.proxy
+
+ self.iface = u.iface
+ self.show_stats = False
+
+ self.asc_dev = u.adt["/arm-io/gfx-asc"]
+ self.sgx_dev = u.adt["/arm-io/sgx"]
+ self.sgx = SGXRegs(u, self.sgx_dev.get_reg(0)[0])
+
+ self.log("Initializing allocations")
+
+ self.aic_base = u.adt["/arm-io/aic"].get_reg(0)[0]
+
+ self.all_objects = {}
+ self.tracked_objects = {}
+
+ # Memory areas
+ self.fw_va_base = self.sgx_dev.rtkit_private_vm_region_base
+ self.fw_va_size = self.sgx_dev.rtkit_private_vm_region_size
+ self.kern_va_base = self.fw_va_base + self.fw_va_size
+
+ # Set up UAT
+ self.uat = UAT(self.u.iface, self.u)
+
+ # Allocator for RTKit/ASC objects
+ self.uat.allocator = Heap(self.kern_va_base + 0x80000000,
+ self.kern_va_base + 0x81000000,
+ self.PAGE_SIZE)
+
+ self.asc = AGXASC(self.u, self.asc_dev.get_reg(0)[0], self, self.uat)
+ self.asc.verbose = 0
+ self.asc.mgmt.verbose = 0
+
+ self.kobj = GPUAllocator(self, "kernel",
+ self.kern_va_base, 0x10000000,
+ AttrIndex=MemoryAttr.Shared, AP=1, guard_pages=4)
+ self.cmdbuf = GPUAllocator(self, "cmdbuf",
+ self.kern_va_base + 0x10000000, 0x10000000,
+ AttrIndex=MemoryAttr.Shared, AP=0, guard_pages=4)
+ self.kshared = GPUAllocator(self, "kshared",
+ self.kern_va_base + 0x20000000, 0x10000000,
+ AttrIndex=MemoryAttr.Shared, AP=1, guard_pages=4)
+ self.kshared2 = GPUAllocator(self, "kshared2",
+ self.kern_va_base + 0x30000000, 0x100000,
+ AttrIndex=MemoryAttr.Shared, AP=0, PXN=1, guard_pages=4)
+
+ self.io_allocator = Heap(self.kern_va_base + 0x38000000,
+ self.kern_va_base + 0x40000000,
+ block=self.PAGE_SIZE)
+
+ self.mon = None
+ self.event_mgr = GPUEventManager(self)
+
+ self.p.iodev_set_usage(IODEV.FB, 0)
+
+ self.initdata_hook = None
+
+ # Early init, needed?
+ self.poke_sgx()
+
+ def poke_sgx(self):
+ self.sgx_base = self.sgx_dev.get_reg(0)[0]
+ self.p.read32(self.sgx_base + 0xd14000)
+ self.p.write32(self.sgx_base + 0xd14000, 0x70001)
+
+ def find_object(self, addr, ctx=0):
+ all_objects = list(self.all_objects.items())
+ all_objects.sort()
+
+ idx = bisect.bisect_left(all_objects, ((ctx, addr + 1), "")) - 1
+ if idx < 0 or idx >= len(all_objects):
+ return None, None
+
+ (ctx, base), obj = all_objects[idx]
+ return base, obj
+
+ def reg_object(self, obj, track=True):
+ self.all_objects[(obj._ctx, obj._addr)] = obj
+ if track:
+ if self.mon is not None:
+ obj.add_to_mon(self.mon)
+ self.tracked_objects[(obj._ctx, obj._addr)] = obj
+
+ def unreg_object(self, obj):
+ del self.all_objects[(obj._ctx, obj._addr)]
+ if obj._addr in self.tracked_objects:
+ del self.tracked_objects[(obj._ctx, obj._addr)]
+
+ def poll_objects(self):
+ for obj in self.tracked_objects.values():
+ diff = obj.poll()
+ if diff is not None:
+ self.log(diff)
+
+ def alloc_channels(self, cls, name, channel_id, count=1, ring_size=0x100, rx=False):
+
+ # All channels have 0x100 items
+ item_count = ring_size
+ item_size = cls.item_size
+ ring_size = item_count * item_size
+
+ self.log(f"Allocating {count} channel(s) for {name} ({item_count} * {item_size:#x} bytes each)")
+
+ state_obj = self.kshared.new_buf(0x30 * count, f"Channel.{name}.state", track=False)
+ if rx:
+ ring_buf = self.kshared.new_buf(ring_size * count, f"Channel.{name}.ring", track=False)
+ else:
+ ring_buf = self.kobj.new_buf(ring_size * count, f"Channel.{name}.ring", track=False)
+
+ info = ChannelInfo()
+ info.state_addr = state_obj._addr
+ info.ringbuffer_addr = ring_buf._addr
+ if name == "FWCtl":
+ self.fwctl_chinfo = info
+ else:
+ setattr(self.ch_info, name, info)
+
+ return [cls(self, name + ("" if count == 1 else f"[{i}]"), channel_id,
+ state_obj._paddr + 0x30 * i,
+ ring_buf._paddr + ring_size * i, item_count)
+ for i in range(count)]
+
+ def init_channels(self):
+ self.log("Initializing channels...")
+ self.ch_info = ChannelInfoSet()
+ self.ch = AGXChannels()
+ self.ch.queue = []
+
+ # Command queue submission channels
+ for index in range(4):
+ queue = AGXQueue()
+ self.ch.queue.append(queue)
+ for typeid, chtype in enumerate(("TA", "3D", "CL")):
+ name = f"{chtype}_{index}"
+ chan = self.alloc_channels(GPUCmdQueueChannel, name,
+ (index << 2) | typeid)[0]
+ setattr(queue, "q_" + chtype, chan)
+
+ # Device control channel
+ self.ch.devctrl = self.alloc_channels(GPUDeviceControlChannel, "DevCtrl", 0x11)[0]
+
+ # GPU -> CPU channels
+ self.ch.event = self.alloc_channels(GPUEventChannel, "Event", None, rx=True)[0]
+ self.ch.log = self.alloc_channels(GPULogChannel, "FWLog", None, 6, rx=True)
+ self.ch.ktrace = self.alloc_channels(GPUKTraceChannel, "KTrace", None, ring_size=0x200, rx=True)[0]
+ self.ch.stats = self.alloc_channels(GPUStatsChannel, "Stats", None, rx=True)[0]
+
+ self.ch.fwctl = self.alloc_channels(GPUFWCtlChannel, "FWCtl", None, rx=False)[0]
+
+ # For some reason, the FWLog channels have their rings in a different place...
+ self.fwlog_ring = self.ch_info.FWLog.ringbuffer_addr
+ self.ch_info.FWLog.ringbuffer_addr = self.kshared.buf(0x150000, "FWLog_Dummy")
+
+ def poll_channels(self):
+ for chan in self.ch.log:
+ chan.poll()
+ self.ch.ktrace.poll()
+ if self.show_stats:
+ self.ch.stats.poll()
+ self.ch.event.poll()
+
+ def kick_firmware(self):
+ self.asc.db.doorbell(0x10)
+
+ def show_irqs(self):
+ hw_state = self.aic_base + 0x4200
+ irqs = []
+ for irq in self.sgx_dev.interrupts:
+ v = int(bool((self.p.read32(hw_state + (irq // 32) * 4) & (1 << (irq % 32)))))
+ irqs.append(v)
+ self.log(f' SGX IRQ state: {irqs}')
+
+ def timeout(self, msg):
+ if self.mon:
+ self.mon.poll()
+ self.poll_objects()
+ self.log(msg)
+ self.log(r' (\________/) ')
+ self.log(r' | | ')
+ self.log(r"'.| \ , / |.'")
+ self.log(r'--| / (( \ |--')
+ self.log(r".'| _-_- |'.")
+ self.log(r' |________| ')
+ self.log(r'')
+ self.log(r' Timeout nya~!!!!!')
+ self.log(r'')
+ self.log(f' Stamp index: {int(msg.stamp_index)}')
+ self.show_pending_stamps()
+ self.log(f' Fault info:')
+ self.log(self.initdata.regionC.fault_info)
+
+ self.show_irqs()
+ self.check_fault()
+ self.recover()
+
+ def faulted(self, msg):
+ if self.mon:
+ self.mon.poll()
+ self.poll_objects()
+ self.log(msg)
+ self.log(r' (\________/) ')
+ self.log(r' | | ')
+ self.log(r"'.| \ , / |.'")
+ self.log(r'--| / (( \ |--')
+ self.log(r".'| _-_- |'.")
+ self.log(r' |________| ')
+ self.log(r'')
+ self.log(r' Fault nya~!!!!!')
+ self.log(r'')
+ self.show_pending_stamps()
+ self.log(f' Fault info:')
+ self.log(self.initdata.regionC.fault_info)
+
+ self.show_irqs()
+ self.check_fault()
+ self.recover()
+
+ def show_pending_stamps(self):
+ self.initdata.regionC.pull()
+ self.log(f' Pending stamps:')
+ for i in self.initdata.regionC.pending_stamps:
+ if i.info or i.wait_value:
+ self.log(f" - #{i.info >> 3:3d}: {i.info & 0x7}/{i.wait_value:#x}")
+ i.info = 0
+ i.wait_value = 0
+ tmp = i.regmap()
+ tmp.info.val = 0
+ tmp.wait_value.val = 0
+
+ #self.initdata.regionC.push()
+
+ def check_fault(self):
+ fault_info = self.sgx.FAULT_INFO.reg
+ if fault_info.value == 0xacce5515abad1dea:
+ raise Exception("Got fault notification, but fault address is unreadable")
+
+ self.log(f" Fault info: {fault_info}")
+
+ if not fault_info.FAULTED:
+ return
+
+ fault_addr = fault_info.ADDR
+ if fault_addr & 0x8000000000:
+ fault_addr |= 0xffffff8000000000
+ base, obj = self.find_object(fault_addr)
+ info = ""
+ if obj is not None:
+ info = f" ({obj!s} + {fault_addr - base:#x})"
+ self.log(f" GPU fault at {fault_addr:#x}{info}")
+ self.log(f" Faulting unit: {agx_decode_unit(fault_info.UNIT)}")
+
+ def recover(self):
+ status = self.fw_status
+ self.log(f" Halt count: {status.halt_count.val}")
+ halted = bool(status.halted.val)
+ self.log(f" Halted: {halted}")
+ if halted:
+ self.log(f" Attempting recovery...")
+ status.halted.val = 0
+ status.resume.val = 1
+ else:
+ raise Exception("Cannot recover")
+ self.show_irqs()
+
+ def resume(self):
+ self.log("Starting ASC")
+ self.asc.start()
+
+ self.log("Starting endpoints")
+ self.asc.start_ep(0x20)
+ self.asc.start_ep(0x21)
+
+ def start(self):
+ self.resume()
+
+ self.init_channels()
+
+ self.log("Building initdata")
+ self.initdata = build_initdata(self)
+ if self.initdata_hook:
+ self.initdata_hook(self)
+
+ self.fw_status = self.initdata.fw_status.regmap()
+ self.uat.flush_dirty()
+
+ self.log("Sending initdata")
+ self.asc.fw.send_initdata(self.initdata._addr & 0xfff_ffffffff)
+ self.asc.work()
+
+ self.log("Sending DC_Init")
+ self.ch.devctrl.send_init()
+ self.asc.work()
+
+ self.log("Sending DC_UpdateIdleTS")
+ self.ch.devctrl.update_idle_ts()
+ self.asc.work()
+
+ def stop(self):
+ self.asc.stop()
+
+ def work(self):
+ self.asc.work()
+
+ def wait_for_events(self, timeout=1.0):
+ now = time.time()
+ deadline = now + timeout
+ cnt = self.event_mgr.event_count
+ while now < deadline and self.event_mgr.event_count == cnt:
+ self.asc.work()
+ now = time.time()
+ if self.event_mgr.event_count == cnt:
+ raise Exception("Timed out waiting for events")
+
+ def log(self, msg):
+ t = time.time() - self.start_time
+ print(f"[AGX][{t:10.03f}] " + str(msg))
diff --git a/tools/proxyclient/m1n1/agx/channels.py b/tools/proxyclient/m1n1/agx/channels.py
new file mode 100644
index 0000000..c91f347
--- /dev/null
+++ b/tools/proxyclient/m1n1/agx/channels.py
@@ -0,0 +1,178 @@
+# SPDX-License-Identifier: MIT
+
+from construct import *
+from ..fw.agx.channels import *
+from ..fw.agx.cmdqueue import *
+
+class GPUChannel:
+ STATE_FIELDS = ChannelStateFields
+
+ def __init__(self, agx, name, channel_id, state_addr, ring_addr, ring_size):
+ self.agx = agx
+ self.u = agx.u
+ self.name = name
+ self.channel_id = channel_id
+ self.iface = agx.u.iface
+ self.state_addr = state_addr
+ self.ring_addr = ring_addr
+ self.ring_size = ring_size
+ self.state = self.STATE_FIELDS(self.u, self.state_addr)
+ self.state.READ_PTR.val = 0
+ self.state.WRITE_PTR.val = 0
+
+ @classmethod
+ @property
+ def item_size(cls):
+ return cls.MSG_CLASS.sizeof()
+
+ def log(self, msg):
+ self.agx.log(f"[{self.name}] {msg}")
+
+class GPUTXChannel(GPUChannel):
+ def doorbell(self):
+ self.agx.asc.db.doorbell(self.channel_id)
+
+ def send_message(self, msg):
+ wptr = self.state.WRITE_PTR.val
+ self.iface.writemem(self.ring_addr + self.item_size * wptr,
+ msg.build())
+ self.state.WRITE_PTR.val = (wptr + 1) % self.ring_size
+ self.doorbell()
+
+class GPURXChannel(GPUChannel):
+ def poll(self):
+ wptr = self.state.WRITE_PTR.val
+ rptr = self.state.READ_PTR.val
+
+ if wptr >= self.ring_size:
+ raise Exception(f"wptr = {wptr:#x} > {self.ring_size:#x}")
+
+ while rptr != wptr:
+ msg = self.iface.readmem(self.ring_addr + self.item_size * rptr,
+ self.item_size)
+ self.handle_message(self.MSG_CLASS.parse(msg))
+ rptr = (rptr + 1) % self.ring_size
+ self.state.READ_PTR.val = rptr
+
+ def handle_message(self, msg):
+ self.log(f"Message: {msg}")
+
+class GPUCmdQueueChannel(GPUTXChannel):
+ MSG_CLASS = RunCmdQueueMsg
+
+ def run(self, queue, event):
+ msg = RunCmdQueueMsg()
+ msg.queue_type = queue.TYPE
+ msg.cmdqueue = queue.info
+ msg.cmdqueue_addr = queue.info._addr
+ msg.head = queue.wptr
+ msg.event_number = event
+ msg.new_queue = 1 if queue.first_time else 0
+ queue.first_time = False
+ #print(msg)
+ self.send_message(msg)
+
+class GPUDeviceControlChannel(GPUTXChannel):
+ MSG_CLASS = DeviceControlMsg
+
+ def send_init(self):
+ self.send_message(DC_Init())
+
+ def dc_09(self, a, ptr, b):
+ # Writes to InitData.RegionB
+ msg = DC_09()
+ msg.unk_4 = a
+ msg.unkptr_c = ptr
+ msg.unk_14 = b
+ self.send_message(msg)
+
+ def send_foo(self, t, d=None):
+ msg = DC_Any()
+ msg.msg_type = t
+ if d is not None:
+ msg.data = d
+ self.send_message(msg)
+
+ def update_idle_ts(self):
+ self.send_message(DC_UpdateIdleTS())
+
+ def destroy_context(self, ctx):
+ msg = DC_DestroyContext()
+ msg.unk_4 = 0
+ msg.unk_8 = 2
+ msg.unk_c = 0
+ msg.unk_10 = 0
+ msg.unk_14 = 0xffff
+ msg.unk_18 = 0
+ msg.context_addr = ctx.gpu_context._addr
+ print(msg)
+ self.send_message(msg)
+
+ # Maybe related to stamps?
+ def write32(self, addr, val):
+ msg = DC_Write32()
+ msg.addr = addr
+ msg.data = val
+ msg.unk_10 = 0
+ msg.unk_14 = 0
+ msg.unk_18 = 0
+ msg.unk_1c = 0
+ print(msg)
+ self.send_message(msg)
+
+ def dc_1e(self, a, b):
+ msg = DC_1e()
+ msg.unk_4 = a
+ msg.unk_c = b
+ print(msg)
+ self.send_message(msg)
+
+class GPUFWCtlChannel(GPUTXChannel):
+ STATE_FIELDS = FWControlStateFields
+ MSG_CLASS = FWCtlMsg
+
+ def doorbell(self):
+ self.agx.asc.db.fwctl_doorbell()
+
+ def send_inval(self, ctx, addr=0):
+ msg = FWCtlMsg()
+ msg.addr = addr
+ msg.unk_8 = 0
+ msg.context_id = ctx
+ msg.unk_10 = 1
+ msg.unk_12 = 2
+ print(msg)
+ self.send_message(msg)
+
+class GPUEventChannel(GPURXChannel):
+ MSG_CLASS = EventMsg
+
+ def handle_message(self, msg):
+ if isinstance(msg, FlagMsg):
+ self.agx.event_mgr.fired(msg.firing)
+ elif isinstance(msg, FaultMsg):
+ self.agx.faulted(msg)
+ elif isinstance(msg, TimeoutMsg):
+ self.agx.timeout(msg)
+ else:
+ self.log(f"Unknown event: {msg}")
+
+class GPULogChannel(GPURXChannel):
+ MSG_CLASS = FWLogMsg
+
+ def handle_message(self, msg):
+ ts = msg.timestamp / 24000000
+ self.log(f"[{msg.seq_no:<4d}{ts:14.7f}] {msg.msg}")
+
+class GPUKTraceChannel(GPURXChannel):
+ MSG_CLASS = KTraceMsg
+
+ def handle_message(self, msg):
+ self.log(f"{msg}")
+
+class GPUStatsChannel(GPURXChannel):
+ MSG_CLASS = HexDump(Bytes(0x60))
+
+ def handle_message(self, msg):
+ if self.agx.show_stats:
+ self.log(f"stat {msg}")
diff --git a/tools/proxyclient/m1n1/agx/context.py b/tools/proxyclient/m1n1/agx/context.py
new file mode 100644
index 0000000..41ebed5
--- /dev/null
+++ b/tools/proxyclient/m1n1/agx/context.py
@@ -0,0 +1,247 @@
+# SPDX-License-Identifier: MIT
+from ..utils import chexdump
+from ..malloc import Heap
+from construct.core import *
+from ..fw.agx.channels import *
+from ..fw.agx.cmdqueue import *
+from ..fw.agx.microsequence import *
+from ..hw.uat import MemoryAttr
+from .object import *
+import textwrap
+
+class GPUContext:
+ def __init__(self, agx):
+ self.agx = agx
+ self.uat = self.agx.uat
+ self.u = self.agx.u
+ self.p = self.agx.p
+ self.verbose = False
+
+ #self.job_list = agx.kshared.new(JobList)
+ #self.job_list.first_job = 0
+ #self.job_list.last_head = self.job_list._addr # Empty list has self as last_head
+ #self.job_list.unkptr_10 = 0
+ #self.job_list.push()
+
+ self.gpu_context = agx.kobj.new(GPUContextData).push()
+
+ self.ttbr0_base = self.u.memalign(self.agx.PAGE_SIZE, self.agx.PAGE_SIZE)
+ self.p.memset32(self.ttbr0_base, 0, self.agx.PAGE_SIZE)
+
+ self.objects = {}
+
+ # 32K VA pages since buffer manager needs that
+ self.uobj = GPUAllocator(agx, "Userspace", 0x1600000000, 0x100000000, ctx=None,
+ guard_pages=16,
+ va_block=32768, nG=1, AP=0, PXN=1, UXN=1)
+
+ self.gobj = GPUAllocator(agx, "GEM", 0x1500000000, 0x100000000, ctx=None,
+ guard_pages=16, nG=1, AP=0, PXN=1, UXN=1)
+
+ self.pipeline_base = 0x1100000000
+ self.pipeline_size = 1 << 32
+ self.pobj = GPUAllocator(agx, "Pipelines", self.pipeline_base + 0x10000, self.pipeline_size,
+ ctx=None, guard_pages=1, nG=1, AP=0, PXN=1, UXN=1)
+
+ def bind(self, ctx_id):
+ self.ctx = ctx_id
+ self.uobj.ctx = ctx_id
+ self.gobj.ctx = ctx_id
+ self.pobj.ctx = ctx_id
+ self.uat.bind_context(ctx_id, self.ttbr0_base)
+ self.thing = self.buf_at(0x6fffff8000, 0, 0x4000, "thing")
+
+ def make_stream(self, base):
+ return self.uat.iostream(self.ctx, base, recurse=False)
+
+ def new_at(self, addr, objtype, name=None, track=True, **flags):
+ obj = GPUObject(self, objtype)
+ obj._stream = self.make_stream
+ if name is not None:
+ obj._name = name
+
+ size_align = align_up(obj._size, self.agx.PAGE_SIZE)
+ obj._addr = addr
+
+ obj._paddr = self.agx.u.memalign(self.agx.PAGE_SIZE, size_align)
+ #if isinstance(obj.val, ConstructClassBase):
+ #obj.val._addr = obj._addr
+
+ self.agx.log(f"[Context@{self.gpu_context._addr:#x}] Map {obj._name} size {obj._size:#x} @ {obj._addr:#x} ({obj._paddr:#x})")
+
+ flags2 = {"AttrIndex": MemoryAttr.Shared}
+ flags2.update(flags)
+ obj._map_flags = flags2
+
+ obj._size_align = size_align
+ self.agx.uat.iomap_at(self.ctx, obj._addr, obj._paddr, size_align, **flags2)
+ self.objects[obj._addr] = obj
+ self.agx.reg_object(obj, track=track)
+
+ return obj
+
+ def buf_at(self, addr, is_pipeline, size, name=None, track=True):
+ return self.new_at(addr, Bytes(size), name, track=track,
+ AttrIndex=MemoryAttr.Shared, PXN=1,
+ nG=1, AP=(1 if is_pipeline else 0))
+
+ def load_blob(self, addr, is_pipeline, filename, track=True):
+ data = open(filename, "rb").read()
+ obj = self.new_at(addr, Bytes(len(data)), filename, track=track,
+ AttrIndex=MemoryAttr.Shared, PXN=1,
+ nG=1, AP=(1 if is_pipeline else 0))
+ obj.val = data
+ obj.push()
+
+ return obj
+
+ def free(self, obj):
+ obj._dead = True
+ self.agx.uat.iomap_at(self.ctx, obj._addr, 0, obj._size_align, VALID=0)
+ del self.objects[obj._addr]
+ self.agx.unreg_object(obj)
+
+ def free_at(self, addr):
+ self.free(self.objects[obj._addr])
+
+class GPUWorkQueue:
+ def __init__(self, agx, context, job_list):
+ self.agx = agx
+ self.u = agx.u
+ self.p = agx.p
+ self.context = context
+
+ self.info = agx.kobj.new(CommandQueueInfo)
+
+ self.pointers = agx.kshared.new(CommandQueuePointers).push()
+ self.pmap = CommandQueuePointerMap(self.u, self.pointers._paddr)
+
+ self.rb_size = self.pointers.rb_size
+ self.ring = agx.kobj.new_buf(8 * self.rb_size, "GPUWorkQueue.RB")
+
+ self.info.pointers = self.pointers
+ self.info.rb_addr = self.ring._addr
+ self.info.job_list = job_list
+ self.info.gpu_buf_addr = agx.kobj.buf(0x2c18, "GPUWorkQueue.gpu_buf")
+ self.info.gpu_context = context.gpu_context
+ self.info.push()
+
+ self.wptr = 0
+ self.first_time = True
+
+ self.agx.uat.flush_dirty()
+
+ def submit(self, work):
+ work.push()
+
+ self.p.write64(self.ring._paddr + 8 * self.wptr, work._addr)
+ self.wptr = (self.wptr + 1) % self.rb_size
+ self.agx.uat.flush_dirty()
+ self.pmap.CPU_WPTR.val = self.wptr
+
+ def wait_empty(self):
+ while self.wptr != self.pmap.GPU_DONEPTR.val:
+ self.agx.work()
+
+class GPU3DWorkQueue(GPUWorkQueue):
+ TYPE = 1
+
+class GPUTAWorkQueue(GPUWorkQueue):
+ TYPE = 0
+
+class GPUMicroSequence:
+ def __init__(self, agx):
+ self.agx = agx
+ self.off = 0
+ self.ops = []
+ self.obj = None
+
+ def append(self, op):
+ off = self.off
+ self.ops.append(op)
+ self.off += op.sizeof()
+ return off
+
+ def finalize(self):
+ self.ops.append(EndCmd())
+ self.size = sum(i.sizeof() for i in self.ops)
+ self.obj = self.agx.kobj.new_buf(self.size, "GPUMicroSequence", track=False)
+ self.obj.val = b"".join(i.build() for i in self.ops)
+ self.obj.push()
+ return self.obj
+
+ def dump(self):
+ chexdump(self.agx.iface.readmem(self.obj._paddr, self.size))
+ print(MicroSequence.parse_stream(self.agx.uat.iostream(0, self.obj._addr)))
+
+ def __str__(self):
+ s = f"GPUMicroSequence: {len(self.ops)} ops\n"
+ for i, op in enumerate(self.ops):
+ op_s = textwrap.indent(str(op), ' ' * 4)
+ s += f"[{i:2}:{op.sizeof():#x}] = {op!s}\n"
+ return s
+
+class GPUBufferManager:
+ def __init__(self, agx, context, blocks=8):
+ self.agx = agx
+ self.ctx = context
+
+ self.block_ctl_obj = agx.kshared.new(BufferManagerBlockControl)
+ self.block_ctl_obj.total = blocks
+ self.block_ctl_obj.wptr = 0
+ self.block_ctl_obj.unk = 0
+ self.block_ctl = self.block_ctl_obj.push().regmap()
+
+ self.counter_obj = agx.kshared.new(BufferManagerCounter)
+ self.counter_obj.count = 0
+ self.counter = self.counter_obj.push().regmap()
+
+ self.misc_obj = agx.kshared.new(BufferManagerMisc)
+ self.misc_obj.cpu_flag = 1
+ self.misc = self.misc_obj.push().regmap()
+
+ self.page_size = 0x8000
+ self.pages_per_block = 4
+ self.block_size = self.pages_per_block * self.page_size
+
+ self.page_list = context.uobj.new(Array(0x10000 // 4, Int32ul), "BM PageList", track=False)
+ self.block_list = context.uobj.new(Array(0x8000 // 4, Int32ul), "BM BlockList", track=False)
+
+ self.info = info = agx.kobj.new(BufferManagerInfo)
+ info.page_list_addr = self.page_list._addr
+ info.page_list_size = self.page_list._size
+ info.page_count = self.block_ctl_obj.total * 4
+ info.block_count = self.block_ctl_obj.total
+
+ info.block_list_addr = self.block_list._addr
+ info.block_ctl = self.block_ctl_obj
+ info.last_page = info.page_count - 1
+ info.block_size = self.block_size
+
+ info.counter = self.counter_obj
+
+ self.populate()
+ self.block_ctl_obj.pull()
+ self.block_list.push()
+ self.page_list.push()
+
+ info.push()
+
+ def increment(self):
+ self.counter_obj.count += 1
+ self.counter_obj.push()
+
+ def populate(self):
+ idx = self.block_ctl.wptr.val
+ total = self.block_ctl.total.val
+ while idx < total:
+ block = self.ctx.uobj.new_buf(self.block_size, "BM Block", track=False)
+ self.block_list[idx * 2] = block._addr // self.page_size
+
+ page_idx = idx * self.pages_per_block
+ for i in range(self.pages_per_block):
+ self.page_list[page_idx + i] = block._addr // self.page_size + i
+
+ idx += 1
+ self.block_ctl.wptr.val = idx
+
diff --git a/tools/proxyclient/m1n1/agx/event.py b/tools/proxyclient/m1n1/agx/event.py
new file mode 100644
index 0000000..693f3a5
--- /dev/null
+++ b/tools/proxyclient/m1n1/agx/event.py
@@ -0,0 +1,58 @@
+# SPDX-License-Identifier: MIT
+from ..utils import chexdump
+from ..malloc import Heap
+from construct.core import *
+from ..fw.agx.channels import *
+from ..fw.agx.cmdqueue import *
+from ..fw.agx.microsequence import *
+from ..hw.uat import MemoryAttr
+from .object import *
+import textwrap
+
+class GPUEventManager:
+ MAX_EVENTS = 128
+
+ def __init__(self, agx):
+ self.agx = agx
+
+ self.event_count = 0
+ self.free_events = set(range(self.MAX_EVENTS))
+ self.events = [None] * self.MAX_EVENTS
+
+ def allocate_event(self):
+ if not self.free_events:
+ raise Exception("No free events")
+ ev_id = self.free_events.pop()
+
+ ev = GPUEvent(ev_id)
+ self.events[ev_id] = ev
+
+ return ev
+
+ def free_event(self, ev):
+ self.events[ev.id] = None
+ self.free_events.add(ev.id)
+
+ def fired(self, flags):
+ self.agx.log("= Events fired =")
+ for i, v in enumerate(flags):
+ for j in range(64):
+ if v & (1 << j):
+ ev_id = i * 64 + j
+ ev = self.events[ev_id]
+ self.agx.log(f"Event fired: {ev_id}")
+ if ev is None:
+ raise Exception("Received spurious notification for event ID {ev}")
+ ev.fire()
+ self.event_count += 1
+
+class GPUEvent:
+ def __init__(self, ev_id):
+ self.id = ev_id
+ self.fired = False
+
+ def fire(self):
+ self.fired = True
+
+ def rearm(self):
+ self.fired = False
diff --git a/tools/proxyclient/m1n1/agx/initdata.py b/tools/proxyclient/m1n1/agx/initdata.py
new file mode 100644
index 0000000..d6fa76a
--- /dev/null
+++ b/tools/proxyclient/m1n1/agx/initdata.py
@@ -0,0 +1,387 @@
+# SPDX-License-Identifier: MIT
+from ..fw.agx.initdata import *
+from ..fw.agx.channels import ChannelInfo
+from ..hw.uat import MemoryAttr
+
+from construct import Container
+
+def build_iomappings(agx, chip_id):
+ def iomap(phys, size, range_size, rw):
+ off = phys & 0x3fff
+ virt = agx.io_allocator.malloc(size + 0x4000 + off)
+ agx.uat.iomap_at(0, virt, phys - off, size + off, AttrIndex=MemoryAttr.Device)
+ return IOMapping(phys, virt + off, size, range_size, rw)
+
+ # for t8103
+ if chip_id == 0x8103:
+ return [
+ iomap(0x204d00000, 0x1c000, 0x1c000, 1), # Fender
+ iomap(0x20e100000, 0x4000, 0x4000, 0), # AICTimer
+ iomap(0x23b104000, 0x4000, 0x4000, 1), # AICSWInt
+ iomap(0x204000000, 0x20000, 0x20000, 1), # RGX
+ IOMapping(), # UVD
+ IOMapping(), # unused
+ IOMapping(), # DisplayUnderrunWA
+ iomap(0x23b2e8000, 0x1000, 0x1000, 0), # AnalogTempSensorControllerRegs
+ iomap(0x23bc00000, 0x1000, 0x1000, 1), # PMPDoorbell
+ iomap(0x204d80000, 0x5000, 0x5000, 1), # MetrologySensorRegs
+ iomap(0x204d61000, 0x1000, 0x1000, 1), # GMGIFAFRegs
+ iomap(0x200000000, 0xd6400, 0xd6400, 1), # MCache registers
+ IOMapping(), # AICBankedRegisters
+ iomap(0x23b738000, 0x1000, 0x1000, 1), # PMGRScratch
+ IOMapping(), # NIA Special agent idle register die 0
+ IOMapping(), # NIA Special agent idle register die 1
+ IOMapping(), # CRE registers
+ IOMapping(), # Streaming codec registers
+ IOMapping(), #
+ IOMapping(), #
+ ]
+ elif chip_id == 0x8112:
+ return [
+ iomap(0x204d00000, 0x14000, 0x14000, 1), # Fender
+ iomap(0x20e100000, 0x4000, 0x4000, 0), # AICTimer
+ iomap(0x23b0c4000, 0x4000, 0x4000, 1), # AICSWInt
+ iomap(0x204000000, 0x20000, 0x20000, 1), # RGX
+ IOMapping(), # UVD
+ IOMapping(), # unused
+ IOMapping(), # DisplayUnderrunWA
+ iomap(0x23b2c0000, 0x1000, 0x1000, 0), # AnalogTempSensorControllerRegs
+ IOMapping(), # PMPDoorbell
+ iomap(0x204d80000, 0x8000, 0x8000, 1), # MetrologySensorRegs
+ iomap(0x204d61000, 0x1000, 0x1000, 1), # GMGIFAFRegs
+ iomap(0x200000000, 0xd6400, 0xd6400, 1), # MCache registers
+ IOMapping(), # AICBankedRegisters
+ IOMapping(), # PMGRScratch
+ IOMapping(), # NIA Special agent idle register die 0
+ IOMapping(), # NIA Special agent idle register die 1
+ iomap(0x204e00000, 0x10000, 0x10000, 0), # CRE registers
+ iomap(0x27d050000, 0x4000, 0x4000, 0), # Streaming codec registers
+ iomap(0x23b3d0000, 0x1000, 0x1000, 0), #
+ iomap(0x23b3c0000, 0x1000, 0x1000, 0), #
+ ]
+ elif chip_id in (0x6000, 0x6001, 0x6002):
+ mcc_cnt = {0x6002: 16, 0x6001: 8, 0x6000: 4}
+ return [
+ iomap(0x404d00000, 0x1c000, 0x1c000, 1), # Fender
+ iomap(0x20e100000, 0x4000, 0x4000, 0), # AICTimer
+ iomap(0x28e104000, 0x4000, 0x4000, 1), # AICSWInt
+ iomap(0x404000000, 0x20000, 0x20000, 1), # RGX
+ IOMapping(), # UVD
+ IOMapping(), # unused
+ IOMapping(), # DisplayUnderrunWA
+ iomap(0x28e494000, 0x1000, 0x1000, 0), # AnalogTempSensorControllerRegs
+ IOMapping(), # PMPDoorbell
+ iomap(0x404d80000, 0x8000, 0x8000, 1), # MetrologySensorRegs
+ iomap(0x204d61000, 0x1000, 0x1000, 1), # GMGIFAFRegs
+ iomap(0x200000000, mcc_cnt[chip_id] * 0xd8000, 0xd8000, 1), # MCache registers
+ IOMapping(), # AICBankedRegisters
+ IOMapping(), # PMPDoorbell
+ iomap(0x2643c4000, 0x1000, 0x1000, 1), # NIA Special agent idle register die 0
+ iomap(0x22643c4000, 0x1000, 0x1000, 1) if chip_id == 0x6002 else IOMapping(), # NIA Special agent idle register die 1
+ IOMapping(), # CRE registers
+ IOMapping(), # Streaming codec registers
+ iomap(0x28e3d0000, 0x1000, 0x1000, 1),
+ iomap(0x28e3c0000, 0x2000, 0x2000, 0),
+ ]
+
+
+CHIP_INFO = {
+ 0x8103: Container(
+ chip_id = 0x8103,
+ min_sram_volt = 850,
+ max_power = 19551,
+ max_freq_mhz = 1278,
+ unk_87c = -220,
+ unk_8cc = 9880,
+ unk_924 = [[0] * 8] * 8,
+ unk_e48 = [[0] * 8] * 8,
+ unk_e24 = 112,
+ gpu_fast_die0_sensor_mask64 = 0x12,
+ gpu_fast_die0_sensor_mask64_alt = 0x12,
+ gpu_fast_die0_sensor_present = 0x01,
+ shared1_tab = [
+ -1, 0x7282, 0x50ea, 0x370a, 0x25be, 0x1c1f, 0x16fb
+ ] + ([-1] * 10),
+ shared1_a4 = 0xffff,
+ shared2_tab = [0x800, 0x1555, -1, -1, -1, -1, -1, -1, 0, 0],
+ shared2_unk_508 = 0xc0007,
+ unk_3cf4 = [1000.0, 0, 0, 0, 0, 0, 0, 0],
+ unk_3d14 = [45.0, 0, 0, 0, 0, 0, 0, 0],
+ unk_118ec = None,
+ hwdb_4e0 = 0,
+ hwdb_534 = 0,
+ num_cores = 8,
+ gpu_core = 11,
+ gpu_rev = 4,
+ hwdb_ab8 = 0x48,
+ hwdb_abc = 0x8,
+ hwdb_b30 = 0,
+ rel_max_powers = [0, 19, 26, 38, 60, 87, 100],
+ ),
+ 0x6001: Container(
+ chip_id = 0x6001,
+ min_sram_volt = 790,
+ max_power = 81415,
+ max_freq_mhz = 1296,
+ unk_87c = 900,
+ unk_8cc = 11000,
+ unk_924 = [[i, *([0] * 7)] for i in [
+ 9.838, 9.819, 9.826, 9.799,
+ 0, 0, 0, 0,
+ ]],
+ unk_e48 = [[i, *([0] * 7)] for i in [
+ 13, 13, 13, 13, 0, 0, 0, 0,
+ ]],
+ unk_e24 = 125,
+ gpu_fast_die0_sensor_mask64 = 0x80808080,
+ gpu_fast_die0_sensor_mask64_alt = 0x90909090,
+ gpu_fast_die0_sensor_present = 0x0f,
+ shared1_tab = [0] + ([0xffff] * 16),
+ shared1_a4 = 0xffff,
+ shared2_tab = [-1, -1, -1, -1, 0x2aa, 0xaaa, -1, -1, 0, 0],
+ shared2_unk_508 = 0xcc00001,
+ unk_3cf4 = [1314.0, 1330.0, 1314.0, 1288.0, 0, 0, 0, 0],
+ unk_3d14 = [21.0, 21.0, 22.0, 21.0, 0, 0, 0, 0],
+ unk_118ec = [
+ 0, 1, 2,
+ 1, 1, 90, 75, 1, 1,
+ 1, 2, 90, 75, 1, 1,
+ 1, 1, 90, 75, 1, 1
+ ],
+ hwdb_4e0 = 4,
+ hwdb_534 = 1,
+ num_cores = 32,
+ gpu_core = 13,
+ gpu_rev = 5,
+ hwdb_ab8 = 0x2084,
+ hwdb_abc = 0x80,
+ hwdb_b30 = 0,
+ rel_max_powers = [0, 15, 20, 27, 36, 52, 100],
+ ),
+ 0x6002: Container(
+ chip_id = 0x6002,
+ min_sram_volt = 790,
+ max_power = 166743,
+ max_freq_mhz = 1296,
+ unk_87c = 900,
+ unk_8cc = 11000,
+ unk_924 = [[i, *([0] * 7)] for i in [
+ 9.838, 9.819, 9.826, 9.799,
+ 9.799, 9.826, 9.819, 9.838,
+ ]],
+ unk_c30 = 0,
+ unk_e48 = [[i, *([0] * 7)] for i in [
+ 13, 13, 13, 13, 13, 13, 13, 13,
+ ]],
+ unk_e24 = 125,
+ gpu_fast_die0_sensor_mask64 = 0x8080808080808080,
+ gpu_fast_die0_sensor_mask64_alt = 0x9090909090909090,
+ gpu_fast_die0_sensor_present = 0xff,
+ shared1_tab = [0] + ([0xffff] * 16),
+ shared1_a4 = 0xffff,
+ shared2_tab = [-1, -1, -1, -1, 0x2aa, 0xaaa, -1, -1, 0, 0],
+ shared2_unk_508 = 0xcc00001,
+ unk_3cf4 = [1244.0, 1260.0, 1242.0, 1214.0,
+ 1072.0, 1066.0, 1044.0, 1042.0],
+ unk_3d14 = [18.0, 18.0, 18.0, 17.0, 15.0, 15.0, 15.0, 14.0],
+ unk_8924 = 0,
+ unk_118ec = [
+ 0, 1, 2,
+ 1, 1, 90, 75, 1, 1,
+ 1, 2, 90, 75, 1, 1,
+ 1, 1, 90, 75, 1, 1
+ ],
+ hwdb_4e0 = 4,
+ hwdb_534 = 1,
+ num_cores = 64,
+ gpu_core = 13,
+ gpu_rev = 5,
+ hwdb_ab8 = 0x2084,
+ hwdb_abc = 0x80,
+ hwdb_b30 = 0,
+ rel_max_powers = [0, 15, 19, 25, 34, 50, 100],
+ ),
+ 0x8112: Container(
+ chip_id = 0x8112,
+ min_sram_volt = 780,
+ max_power = 22800,
+ max_freq_mhz = 1398,
+ unk_87c = 900,
+ unk_8cc = 11000,
+ unk_924 = [[
+ 0.0, 0.0, 0.0, 0.0,
+ 5.3, 0.0, 5.3, 6.6,
+ ]] + ([[0] * 8] * 7),
+ unk_e48 = [[
+ 0.0, 0.0, 0.0, 0.0,
+ 5.3, 0.0, 5.3, 6.6,
+ ]] + ([[0] * 8] * 7),
+ unk_e24 = 125,
+ gpu_fast_die0_sensor_mask64 = 0x6800,
+ gpu_fast_die0_sensor_mask64_alt = 0x6800,
+ gpu_fast_die0_sensor_present = 0x02,
+ shared1_tab = [0] + ([0xffff] * 16),
+ shared1_a4 = 0,
+ shared2_tab = [-1, -1, -1, -1, -1, -1, -1, -1, 0xaa5aa, 0],
+ shared2_unk_508 = 0xc00000,
+ unk_3cf4 = [1920.0, 0, 0, 0, 0, 0, 0, 0],
+ unk_3d14 = [74.0, 0, 0, 0, 0, 0, 0, 0],
+ unk_118ec = None,
+ hwdb_4e0 = 4,
+ hwdb_534 = 0,
+ num_cores = 10,
+ gpu_core = 15,
+ gpu_rev = 3,
+ hwdb_ab8 = 0x2048,
+ hwdb_abc = 0x4000,
+ hwdb_b30 = 1,
+ rel_max_powers = [0, 18, 27, 37, 52, 66, 82, 96, 100],
+ ),
+}
+def build_initdata(agx):
+ sgx = agx.u.adt["/arm-io/sgx"]
+ chosen = agx.u.adt["/chosen"]
+ chip_info = CHIP_INFO[chosen.chip_id]
+
+ initdata = agx.kshared.new(InitData)
+
+ initdata.ver_info = (1, 1, 16, 1)
+
+ initdata.regionA = agx.kshared.new_buf(0x4000, "InitData_RegionA").push()
+
+ regionB = agx.kobj.new(InitData_RegionB)
+
+ regionB.channels = agx.ch_info
+
+ regionB.stats_ta = agx.kobj.new(InitData_GPUGlobalStatsTA).push()
+ regionB.stats_3d = agx.kobj.new(InitData_GPUGlobalStats3D).push()
+
+ # size: 0x180, Empty
+ # 13.0: grew
+ #regionB.stats_cp = agx.kobj.new_buf(0x180, "RegionB.unkptr_180").push()
+ regionB.stats_cp = agx.kobj.new_buf(0x980, "RegionB.unkptr_180").push()
+
+ # size: 0x3b80, few floats, few ints, needed for init
+ regionB.hwdata_a = agx.kobj.new(AGXHWDataA(sgx, chip_info), track=False)
+
+ # size: 0x80, empty
+ regionB.unk_190 = agx.kobj.new_buf(0x80, "RegionB.unkptr_190").push()
+
+ # size: 0xc0, fw writes timestamps into this
+ regionB.unk_198 = agx.kobj.new_buf(0xc0, "RegionB.unkptr_198").push()
+
+ # size: 0xb80, io stuff
+ hwdata = agx.kobj.new(AGXHWDataB(sgx, chip_info), track=False)
+ hwdata.io_mappings = build_iomappings(agx, chosen.chip_id)
+
+ k = 1.02 #?
+ count = sgx.perf_state_count
+ table_count = sgx.perf_state_table_count
+ base_pstate = sgx.getprop("gpu-perf-base-pstate", 3)
+ base_freq = sgx.perf_states[base_pstate].freq
+ max_freq = sgx.perf_states[count - 1].freq
+ for i in range(count):
+ ps = sgx.perf_states[i]
+ hwdata.frequencies[i] = ps.freq // 1000000
+
+ volt = [ps.volt] * 8
+ for j in range(1, table_count):
+ volt[j] = sgx.perf_states[count * j + i].volt
+ sram_volt = [max(chip_info.min_sram_volt, i) for i in volt]
+
+ hwdata.voltages[i] = volt
+ hwdata.voltages_sram[i] = sram_volt
+
+ regionB.hwdata_a.unk_74[i] = k
+ hwdata.unk_9b4[i] = k
+ hwdata.rel_max_powers[i] = chip_info.rel_max_powers[i]
+ hwdata.rel_boost_freqs[i] = max(0, int((ps.freq - base_freq) / (max_freq - base_freq) * 100))
+
+ regionB.hwdata_a.push()
+
+ regionB.hwdata_b = hwdata.push()
+ regionB.hwdata_b_addr2 = hwdata._addr
+
+ regionB.fwlog_ring2 = agx.fwlog_ring
+
+ # Unallocated, Size 0x1000
+ regionB.unk_1b8 = agx.kobj.new_buf(0x1000, "RegionB.unkptr_1b8").push()
+
+ # Unallocated, size 0x300
+ regionB.unk_1c0 = agx.kobj.new_buf(0x300, "RegionB.unkptr_1c0").push()
+
+ # Unallocated, unknown size
+ regionB.unk_1c8 = agx.kobj.new_buf(0x1000, "RegionB.unkptr_1c8").push()
+
+ # Size: 0x4000
+ regionB.buffer_mgr_ctl = agx.kshared2.new(InitData_BufferMgrCtl).push()
+ regionB.buffer_mgr_ctl_addr2 = regionB.buffer_mgr_ctl._addr
+
+ regionB.unk_6a80 = 0
+ regionB.gpu_idle = 0
+ regionB.unk_6a9c = 0
+ regionB.unk_ctr0 = 0
+ regionB.unk_ctr1 = 0
+ regionB.unk_6aa8 = 0
+ regionB.unk_6aac = 0
+ regionB.unk_ctr2 = 0
+ regionB.unk_6ab4 = 0
+ regionB.unk_6ab8 = 0
+ regionB.unk_6abc = 0
+ regionB.unk_6ac0 = 0
+ regionB.unk_6ac4 = 0
+ regionB.unk_ctr3 = 0
+ regionB.unk_6acc = 0
+ regionB.unk_6ad0 = 0
+ regionB.unk_6ad4 = 0
+ regionB.unk_6ad8 = 0
+ regionB.unk_6adc = 0
+ regionB.unk_6ae0 = 0
+ regionB.unk_6ae4 = 0
+ regionB.unk_6ae8 = 0
+ regionB.unk_6aec = 0
+ regionB.unk_6af0 = 0
+ regionB.unk_ctr4 = 0
+ regionB.unk_ctr5 = 0
+ regionB.unk_6afc = 0
+
+ initdata.regionB = regionB.push()
+
+ initdata.regionC = agx.kshared.new(InitData_RegionC(sgx, chip_info), track=False).push()
+
+ #self.regionC_addr = agx.ksharedshared_heap.malloc(0x88000)
+
+ initdata.fw_status = agx.kobj.new(InitData_FWStatus)
+ initdata.fw_status.fwctl_channel = agx.fwctl_chinfo
+ initdata.fw_status.push()
+
+ ## This section seems to be data that would be used by firmware side page allocation
+ ## But the current firmware doesn't have this functionality enabled, so it's not used?
+ initdata.uat_num_levels = 3
+ initdata.uat_page_bits = 14
+ initdata.uat_page_size = 0x4000
+
+ if chip_info.chip_id in (0x8103, 0x8112):
+ phys_mask = 0xffffffc000
+ else:
+ phys_mask = 0x3ffffffc000
+
+ initdata.uat_level_info = [
+ UatLevelInfo(36, 8, phys_mask),
+ UatLevelInfo(25, 2048, phys_mask),
+ UatLevelInfo(14, 2048, phys_mask),
+ ]
+
+ # Host handles FW allocations for existing firmware versions
+ initdata.host_mapped_fw_allocations = 1
+
+
+ #initdata.regionC.idle_ts = agx.u.mrs("CNTPCT_EL0") + 24000000
+ #initdata.regionC.idle_unk = 0x5b2e8
+ #initdata.regionC.idle_to_off_timeout_ms = 20000
+
+ initdata.regionC.push()
+ initdata.push()
+
+ #print(InitData.parse_stream(agx.uat.iostream(0, initdata._addr)))
+ return initdata
diff --git a/tools/proxyclient/m1n1/agx/object.py b/tools/proxyclient/m1n1/agx/object.py
new file mode 100644
index 0000000..8f382f9
--- /dev/null
+++ b/tools/proxyclient/m1n1/agx/object.py
@@ -0,0 +1,263 @@
+# SPDX-License-Identifier: MIT
+import io, time
+
+from ..malloc import Heap
+from ..utils import *
+from ..constructutils import ConstructClassBase, str_value
+from construct import Bytes, Container, HexDump
+from ..hw.uat import MemoryAttr
+
+class GPUObject:
+ def __init__(self, allocator, objtype):
+ self._raw = False
+ if isinstance(objtype, int):
+ self.val = bytes(objtype)
+ self._size = objtype
+ self._name = b"Bytes({objtype})"
+ self._raw = True
+ elif isinstance(objtype, ConstructClassBase):
+ self.val = objtype
+ objtype = type(objtype)
+ self._size = objtype.sizeof()
+ self._name = objtype.__name__
+ elif isinstance(objtype, type) and issubclass(objtype, ConstructClassBase):
+ self._size = objtype.sizeof()
+ self.val = objtype()
+ self._name = objtype.__name__
+ else:
+ self._size = objtype.sizeof()
+ self.val = objtype.parse(bytes(self._size))
+ self._name = type(objtype).__name__
+
+ self._alloc = allocator
+ self._type = objtype
+ self._addr = None
+ self._data = None
+ self._dead = False
+ self._map_flags = {}
+ self._mon_val = None
+ self._skipped_pushes = 0
+ self._compress_threshold = 65536
+ self._strm = None
+ self._read_phys = False
+
+ def push(self, if_needed=False):
+ self._mon_val = self.val
+ assert self._addr is not None
+
+ if self._raw:
+ data = self.val
+ else:
+ context = Container()
+ context._parsing = False
+ context._building = True
+ context._sizing = False
+ context._params = context
+ # build locally and push as a block for efficiency
+ ios = io.BytesIO()
+ self._type._build(self.val, ios, context, "(pushing)")
+ data = ios.getvalue()
+
+ #if self._alloc.verbose:
+ #t = time.time()
+ #self._alloc.agx.log(f"[{self._name} @{self._addr:#x}] chk {self._size} bytes")
+ if if_needed and data[:] == self._data:
+ self._skipped_pushes += 1
+ #if self._alloc.verbose:
+ #t2 = time.time()
+ #mbs = self._size / (t2 - t) / 1000000
+ #self._alloc.agx.log(f"[{self._name} @{self._addr:#x}] chk done ({mbs:.02f} MB/s)")
+ return self
+
+ self._skipped_pushes = 0
+
+ t = time.time()
+ if data == bytes(self._size):
+ if self._alloc.verbose:
+ self._alloc.agx.log(f"[{self._name} @{self._addr:#x}] zeroing {self._size} bytes")
+ self._alloc.agx.p.memset8(self._paddr, 0, self._size)
+ elif self._size > self._compress_threshold:
+ if self._alloc.verbose:
+ self._alloc.agx.log(f"[{self._name} @{self._addr:#x}] pushing {self._size} bytes (compressed)")
+ self._alloc.agx.u.compressed_writemem(self._paddr, data)
+ else:
+ if self._alloc.verbose:
+ self._alloc.agx.log(f"[{self._name} @{self._addr:#x}] pushing {self._size} bytes")
+ self._alloc.agx.iface.writemem(self._paddr, data)
+ if self._alloc.verbose:
+ t2 = time.time()
+ mbs = self._size / (t2 - t) / 1000000
+ self._alloc.agx.log(f"[{self._name} @{self._addr:#x}] push done ({mbs:.02f} MB/s)")
+ #stream.write(data)
+ if isinstance(self._type, type) and issubclass(self._type, ConstructClassBase):
+ if self._strm is None:
+ self._strm = self._alloc.make_stream(self._addr)
+ self.val.set_addr(self._addr, self._strm)
+
+ self._data = bytes(data)
+ return self
+
+ def _pull(self):
+ if self._raw:
+ assert self._paddr is not None
+ return self._alloc.agx.iface.readmem(self._paddr, self._size)
+
+ assert self._addr is not None
+ context = Container()
+ context._parsing = True
+ context._building = False
+ context._sizing = False
+ context._params = context
+ if self._alloc.verbose:
+ self._alloc.agx.log(f"[{self._name} @{self._addr:#x}] pulling {self._size} bytes")
+ if self._read_phys:
+ stream = io.BytesIO()
+ stream.write(self._alloc.agx.iface.readmem(self._paddr, self._size))
+ stream.seek(0)
+ else:
+ stream = self._alloc.make_stream(self._addr)
+ return self._type._parse(stream, context, f"(pulling {self._name})")
+
+ def pull(self):
+ self._mon_val = self.val = self._pull()
+ return self
+
+ def poll(self):
+ prev_val = self._mon_val
+ self._mon_val = cur_val = self._pull()
+ if not hasattr(cur_val, "diff"):
+ return None
+ if cur_val != prev_val:
+ diff = cur_val.diff(prev_val)
+ assert diff is not None
+ return f"GPUObject {self._name} ({self._size:#x} @ {self._addr:#x}): " + diff
+ else:
+ return None
+
+ @property
+ def _ctx(self):
+ return self._alloc.ctx
+
+ def add_to_mon(self, mon):
+ mon.add(self._addr, self._size, self._name, offset=0,
+ readfn=lambda a, s: self._alloc.agx.iface.readmem(a - self._addr + self._paddr, s))
+
+ def _set_addr(self, addr, paddr=None):
+ self._addr = addr
+ self._paddr = paddr
+ if isinstance(self.val, ConstructClassBase):
+ self.val.set_addr(addr)
+
+ def __getitem__(self, item):
+ return self.val[item]
+ def __setitem__(self, item, value):
+ self.val[item] = value
+
+ def __getattr__(self, attr):
+ return getattr(self.val, attr)
+
+ def __setattr__(self, attr, val):
+ if attr.startswith("_") or attr == "val":
+ self.__dict__[attr] = val
+ return
+
+ setattr(self.val, attr, val)
+
+ def __str__(self):
+ if isinstance(self.val, bytes) and len(self.val) > 128:
+ s_val = f"<{len(self.val)} bytes>"
+ else:
+ s_val = str_value(self.val)
+ return f"GPUObject {self._name} ({self._size:#x} @ {self._addr:#x}): " + s_val
+
+ def free(self):
+ if self._dead:
+ return
+ self._dead = True
+ self._alloc.free(self)
+
+class GPUAllocator:
+ def __init__(self, agx, name, start, size,
+ ctx=0, page_size=16384, va_block=None, guard_pages=1, **kwargs):
+ self.page_size = page_size
+ if va_block is None:
+ va_block = page_size
+ self.agx = agx
+ self.ctx = ctx
+ self.name = name
+ self.va = Heap(start, start + size, block=va_block)
+ self.verbose = 0
+ self.guard_pages = guard_pages
+ self.objects = {}
+ self.flags = kwargs
+ self.align_to_end = True
+
+ def make_stream(self, base):
+ return self.agx.uat.iostream(self.ctx, base, recurse=False)
+
+ def new(self, objtype, name=None, track=True, **kwargs):
+ obj = GPUObject(self, objtype)
+ obj._stream = self.make_stream
+ if name is not None:
+ obj._name = name
+
+ guard_size = self.page_size * self.guard_pages
+
+ size_align = align_up(obj._size, self.page_size)
+ addr = self.va.malloc(size_align + guard_size)
+ paddr = self.agx.u.memalign(self.page_size, size_align)
+ off = 0
+ if self.align_to_end:
+ off = size_align - obj._size
+
+ flags = dict(self.flags)
+ flags.update(kwargs)
+
+ obj._addr_align = addr
+ obj._paddr_align = paddr
+ obj._size_align = size_align
+ self.agx.uat.iomap_at(self.ctx, addr, paddr, size_align, **flags)
+ obj._set_addr(addr + off, paddr + off)
+ obj._map_flags = flags
+
+ self.objects[obj._addr] = obj
+
+ if self.verbose:
+ self.agx.log(f"[{self.name}] Alloc {obj._name} size {obj._size:#x} @ {obj._addr:#x} ({obj._paddr:#x})")
+
+ self.agx.reg_object(obj, track=track)
+ return obj
+
+ def new_buf(self, size, name, track=True):
+ return self.new(HexDump(Bytes(size)), name=name, track=track)
+
+ def buf(self, size, name, track=True):
+ return self.new_buf(size, name, track).push()._addr
+
+ def free(self, obj):
+ obj._dead = True
+ is_private = obj._map_flags.get("AttrIndex", MemoryAttr.Normal) != MemoryAttr.Shared
+ if is_private and obj._addr_align > 0xf8000000000:
+ flags2 = dict(obj._map_flags)
+ flags2["AttrIndex"] = MemoryAttr.Shared
+ self.agx.uat.iomap_at(self.ctx, obj._addr_align, obj._paddr_align,
+ obj._size_align, **flags2)
+ self.agx.uat.flush_dirty()
+ self.agx.uat.handoff.prepare_cacheflush(obj._addr_align, obj._size_align)
+ self.agx.ch.fwctl.send_inval(0x40, obj._addr_align)
+ self.agx.uat.handoff.wait_cacheflush()
+
+ self.agx.uat.iomap_at(self.ctx, obj._addr_align, 0,
+ obj._size_align, VALID=0)
+
+ if is_private and obj._addr_align > 0xf8000000000:
+ self.agx.uat.flush_dirty()
+ self.agx.uat.handoff.complete_cacheflush()
+
+ self.agx.u.free(obj._paddr_align)
+ self.va.free(obj._addr_align)
+ del self.objects[obj._addr]
+ self.agx.unreg_object(obj)
+
+ if self.verbose:
+ self.agx.log(f"[{self.name}] Free {obj._name} size {obj._size:#x} @ {obj._addr:#x} ({obj._paddr:#x})")
diff --git a/tools/proxyclient/m1n1/agx/render.py b/tools/proxyclient/m1n1/agx/render.py
new file mode 100644
index 0000000..b29683b
--- /dev/null
+++ b/tools/proxyclient/m1n1/agx/render.py
@@ -0,0 +1,1075 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, json, zipfile
+
+json.c_make_encoder = None
+
+from m1n1.proxy import *
+from .context import *
+from .event import GPUEventManager
+from .uapi import *
+from m1n1.constructutils import ConstructClass, Ver
+
+def unswizzle(agx, addr, w, h, psize, dump=None, grid=False):
+ iface = agx.u.iface
+
+ tw = 64
+ th = 64
+ ntx = (w + tw - 1) // 64
+ nty = (h + th - 1) // 64
+ data = iface.readmem(addr, ntx * nty * psize * tw * th)
+ new_data = []
+ for y in range(h):
+ ty = y // th
+ for x in range(w):
+ tx = x // tw
+ toff = tw * th * psize * (ty * ntx + tx)
+ j = x & (tw - 1)
+ i = y & (th - 1)
+ off = (
+ ((j & 1) << 0) | ((i & 1) << 1) |
+ ((j & 2) << 1) | ((i & 2) << 2) |
+ ((j & 4) << 2) | ((i & 4) << 3) |
+ ((j & 8) << 3) | ((i & 8) << 4) |
+ ((j & 16) << 4) | ((i & 16) << 5) |
+ ((j & 32) << 5) | ((i & 32) << 6))
+ r,g,b,a = data[toff + psize*off: toff + psize*(off+1)]
+ if grid:
+ if x % 64 == 0 or y % 64 == 0:
+ r,g,b,a = 255,255,255,255
+ elif x % 32 == 0 or y % 32 == 0:
+ r,g,b,a = 128,128,128,255
+ new_data.append(bytes([b, g, r, a]))
+ data = b"".join(new_data)
+ if dump:
+ open(dump, "wb").write(data[:w*h*psize])
+ #iface.writemem(addr, data)
+
+class GPUFrame:
+ def __init__(self, context, filename=None, track=False):
+ self.ctx = context
+ self.agx = context.agx
+ self.objects = []
+ self.cmdbuf = None
+ self.track = track
+ if filename is not None:
+ self.load(filename)
+
+ def add_object(self, obj):
+ self.objects.append(obj)
+
+ def save(self, filename):
+ cmdbuf = self.cmdbuf
+ with zipfile.ZipFile(filename, "w") as zf:
+ cmdbuf_data = json.dumps(cmdbuf, indent=4).encode("utf-8")
+ zf.writestr("cmdbuf.json", cmdbuf_data)
+
+ obj_info = []
+ for obj in self.objects:
+ if obj._data == bytes(obj._size):
+ filename = None
+ else:
+ filename = f"obj_{obj._addr:x}.bin"
+ zf.writestr(filename, obj._data)
+ obj_info.append({
+ "file": filename,
+ "name": obj._name,
+ "addr": obj._addr,
+ "size": obj._size,
+ "map_flags": obj._map_flags,
+ })
+
+ obj_info_data = json.dumps(obj_info, indent=4).encode("utf-8")
+ zf.writestr("objects.json", obj_info_data)
+
+ def load(self, filename):
+ with zipfile.ZipFile(filename, "r") as zf:
+ with zf.open("cmdbuf.json", "r") as fd:
+ self.cmdbuf = drm_asahi_cmdbuf_t.from_json(fd)
+ with zf.open("objects.json", "r") as fd:
+ obj_info = json.load(fd)
+
+ self.objects = []
+ for i in obj_info:
+ filename = i["file"]
+ obj = self.ctx.new_at(i["addr"], Bytes(i["size"]), name=i["name"], track=self.track,
+ **i["map_flags"])
+ if filename is not None:
+ with zf.open(i["file"], "r") as fd:
+ data = fd.read()
+ obj.val = data
+ obj.push()
+ else:
+ obj.val = bytes(i["size"])
+ obj.push()
+ self.objects.append(obj)
+
+class GPUWork:
+ def __init__(self, renderer):
+ self.objects = []
+ self.renderer = renderer
+
+ def add(self, obj):
+ self.objects.append(obj)
+
+ def free(self):
+ for obj in self.objects:
+ obj.free()
+ self.objects = []
+
+class GPURenderer:
+ def __init__(self, ctx, buffers=16, bm_slot=0, queue=0):
+ self.agx = agx = ctx.agx
+ self.queue = queue
+
+ # 0..63
+ self.ctx = ctx
+ self.ctx_id = ctx.ctx
+
+ # 0..255
+ self.buffers = buffers
+ self.buffer_mgr_slot = bm_slot
+
+ ## These MUST go together
+ self.buffer_mgr = GPUBufferManager(agx, ctx, buffers)
+ self.buffer_mgr_initialized = False
+ self.unk_emptybuf = agx.kobj.new_buf(0x40, "unk_emptybuf")
+ self.tpc_size = 0
+
+ ##### Job group
+
+ self.job_list = agx.kshared.new(JobList)
+ self.job_list.first_job = 0
+ self.job_list.last_head = self.job_list._addr # Empty list has self as last_head
+ self.job_list.unkptr_10 = 0
+ self.job_list.push()
+
+ ##### Work Queues
+
+ self.ts3d_1 = agx.kshared.new(Int64ul, name="3D timestamp 1")
+ self.ts3d_2 = agx.kshared.new(Int64ul, name="3D timestamp 2")
+ self.tsta_1 = agx.kshared.new(Int64ul, name="TA timestamp 1")
+ self.tsta_2 = agx.kshared.new(Int64ul, name="TA timestamp 2")
+
+ self.wq_3d = GPU3DWorkQueue(agx, ctx, self.job_list)
+ self.wq_ta = GPUTAWorkQueue(agx, ctx, self.job_list)
+
+ self.wq_3d.info.uuid = 0x3D0000 | bm_slot
+ self.wq_3d.info.push()
+ self.wq_ta.info.uuid = 0x7A0000 | bm_slot
+ self.wq_ta.info.push()
+
+ self.stamp_value_3d = 0x3D000000 | (bm_slot << 16)
+ self.stamp_value_ta = 0x7A000000 | (bm_slot << 16)
+
+ ##### TA stamps
+
+ # start?
+ self.stamp_ta1 = agx.kshared.new(StampCounter, name="TA stamp 1")
+ self.stamp_ta1.value = self.stamp_value_ta
+ self.stamp_ta1.push()
+
+ # complete?
+ self.stamp_ta2 = agx.kobj.new(StampCounter, name="TA stamp 2")
+ self.stamp_ta2.value = self.stamp_value_ta
+ self.stamp_ta2.push()
+
+ ##### 3D stamps
+
+ # start?
+ self.stamp_3d1 = agx.kshared.new(StampCounter, name="3D stamp 1")
+ self.stamp_3d1.value = self.stamp_value_3d
+ self.stamp_3d1.push()
+
+ # complete?
+ self.stamp_3d2 = agx.kobj.new(StampCounter, name="3D stamp 2")
+ self.stamp_3d2.value = self.stamp_value_3d
+ self.stamp_3d2.push()
+
+
+ ##### Things userspace deals with for macOS
+
+ #self.aux_fb = ctx.uobj.new_buf(0x8000, "Aux FB thing")
+ ##self.deflake_1 = ctx.uobj.new_buf(0x20, "Deflake 1")
+ ##self.deflake_2 = ctx.uobj.new_buf(0x280, "Deflake 2")
+ ##self.deflake_3 = ctx.uobj.new_buf(0x540, "Deflake 3")
+ #self.deflake = ctx.uobj.new_buf(0x7e0, "Deflake")
+ #self.unk_buf = ctx.uobj.new(Array(0x800, Int64ul), "Unknown Buffer")
+ #self.unk_buf.val = [0, *range(1, 0x400), *(0x400 * [0])]
+ #self.unk_buf.push()
+
+ ##### Some kind of feedback/status buffer, GPU managed?
+
+ self.event_control = agx.kobj.new(EventControl)
+ self.event_control.event_count = agx.kobj.new(Int32ul, "event_count")
+ self.event_control.event_count.val = 0
+ self.event_control.event_count.push()
+
+ self.event_control.generation = 0
+ self.event_control.cur_count = 0
+ self.event_control.unk_10 = 0x50
+ self.event_control.push()
+
+ self.frames = 0
+
+ self.ev_ta = ev_ta = self.agx.event_mgr.allocate_event()
+ self.ev_3d = ev_3d = self.agx.event_mgr.allocate_event()
+
+ self.work = []
+
+ def submit(self, cmdbuf, wait_for=None):
+ nclusters = 8
+
+ work = GPUWork(self)
+ self.work.append(work)
+
+ self.buffer_mgr.increment()
+
+ aux_fb = self.ctx.uobj.new_buf(0x20000, "Aux FB thing", track=False)
+ work.add(aux_fb)
+
+ # t8103
+ deflake_1_size = 0x540
+ deflake_2_size = 0x280
+ deflake_3_size = 0x20
+
+ # t6002 - 9 times larger instead of 8? works with 8...
+ deflake_1_size *= nclusters
+ deflake_2_size *= nclusters
+ deflake_3_size *= nclusters
+
+ deflake_1 = self.ctx.uobj.new_buf(deflake_1_size, "Deflake 1", track=True)
+ deflake_2 = self.ctx.uobj.new_buf(deflake_2_size, "Deflake 2", track=True)
+ deflake_3 = self.ctx.uobj.new_buf(deflake_3_size, "Deflake 3", track=True)
+ work.add(deflake_1)
+ work.add(deflake_2)
+ work.add(deflake_3)
+
+ unk_buf = self.ctx.uobj.new(Array(0x800, Int64ul), "Unknown Buffer", track=False)
+ work.add(unk_buf)
+
+ unk_buf.val = [0, *range(2, 0x401), *(0x400 * [0])]
+ unk_buf.push()
+
+ work.cmdbuf = cmdbuf
+
+ self.frames += 1
+
+ work.ev_ta = ev_ta = self.ev_ta
+ work.ev_3d = ev_3d = self.ev_3d
+
+ self.ev_ta.rearm()
+ self.ev_3d.rearm()
+
+ self.agx.log(f"ev_ta: {ev_ta.id}")
+ self.agx.log(f"ev_3d: {ev_3d.id}")
+
+ #self.event_control.base_stamp = self.stamp_value >> 8
+ #self.event_control.push()
+
+ self.prev_stamp_value_3d = self.stamp_value_3d
+ self.prev_stamp_value_ta = self.stamp_value_ta
+ self.stamp_value_3d += 0x100
+ self.stamp_value_ta += 0x100
+ self.event_control.event_count.val += 2
+ self.event_control.event_count.push()
+
+ work.stamp_value_3d = self.stamp_value_3d
+ work.stamp_value_ta = self.stamp_value_ta
+
+ agx = self.agx
+ ctx = self.ctx
+
+ work.width = width = cmdbuf.fb_width
+ work.height = height = cmdbuf.fb_height
+
+ ##### TVB allocations / Tiler config
+
+ tile_width = 32
+ tile_height = 32
+ tiles_x = ((width + tile_width - 1) // tile_width)
+ tiles_y = ((height + tile_height - 1) // tile_height)
+ tiles = tiles_x * tiles_y
+
+ mtiles_x = 4
+ mtiles_y = 4
+
+ mtile_x1 = align(((tiles_x + mtiles_x - 1) // mtiles_x), 4)
+ mtile_x2 = 2 * mtile_x1
+ mtile_x3 = 3 * mtile_x1
+ mtile_y1 = align(((tiles_y + mtiles_y - 1) // mtiles_y), 4)
+ mtile_y2 = 2 * mtile_y1
+ mtile_y3 = 3 * mtile_y1
+
+ mtile_stride = mtile_x1 * mtile_y1
+
+ ## TODO: *samples
+ tiles_per_mtile_x = mtile_x1
+ tiles_per_mtile_y = mtile_y1
+
+ tile_blocks_x = (tiles_x + 15) // 16
+ tile_blocks_y = (tiles_y + 15) // 16
+ tile_blocks = tile_blocks_x * tile_blocks_y
+
+ tiling_params = TilingParameters()
+ # rgn_header_size
+ rgn_entry_size = 5
+ tiling_params.size1 = (rgn_entry_size * tiles_per_mtile_x * tiles_per_mtile_y + 3) // 4
+ # PPP_MULTISAMPLECTL
+ tiling_params.unk_4 = 0x88
+ # PPP_CTRL
+ tiling_params.unk_8 = 0x203 # bit 0: GL clip mode
+ # PPP_SCREEN
+ tiling_params.x_max = width - 1
+ tiling_params.y_max = height - 1
+ # TE_SCREEN
+ tiling_params.tile_count = ((tiles_y-1) << 12) | (tiles_x-1)
+ # TE_MTILE1
+ tiling_params.x_blocks = mtile_x3 | (mtile_x2 << 9) | (mtile_x1 << 18)
+ # TE_MTILE2
+ tiling_params.y_blocks = mtile_y3 | (mtile_y2 << 9) | (mtile_y1 << 18)
+ tiling_params.size2 = mtile_stride
+ tiling_params.size3 = 2 * mtile_stride
+ tiling_params.unk_24 = 0x100
+ tiling_params.unk_28 = 0x8000
+
+ tilemap_size = (4 * tiling_params.size1 * mtiles_x * mtiles_y)
+
+ tmtiles_x = tiles_per_mtile_x * mtiles_x
+ tmtiles_y = tiles_per_mtile_y * mtiles_y
+
+ tpc_entry_size = 8
+ tpc_size = tpc_entry_size * tmtiles_x * tmtiles_y * nclusters
+
+ if self.tpc_size < tpc_size:
+ self.tpc = ctx.uobj.new_buf(tpc_size, "TPC", track=True).push()
+ self.tpc_size = tpc_size
+
+ depth_aux_buffer_addr = 0
+ if cmdbuf.depth_buffer:
+ size = align_pot(max(width, tile_width)) * align_pot(max(height, tile_width)) // 32
+ depth_aux_buffer = self.ctx.uobj.new_buf(size, "Depth Aux", track=True)
+ work.add(depth_aux_buffer)
+ depth_aux_buffer_addr = depth_aux_buffer._addr
+
+ stencil_aux_buffer_addr = 0
+ if cmdbuf.stencil_buffer:
+ size = align_pot(max(width, tile_width)) * align_pot(max(height, tile_width)) // 32
+ stencil_aux_buffer = self.ctx.uobj.new_buf(size, "Stencil Aux", track=False)
+ work.add(stencil_aux_buffer)
+ stencil_aux_buffer_addr = stencil_aux_buffer._addr
+
+ #tvb_tilemap_size = 0x80 * mtile_stride
+ tvb_tilemap_size = tilemap_size
+ tvb_tilemap = ctx.uobj.new_buf(tvb_tilemap_size, "TVB Tilemap", track=True).push()
+ work.tvb_tilemap_size = tvb_tilemap_size
+ work.tvb_tilemap = tvb_tilemap
+ work.add(tvb_tilemap)
+
+ # rogue: 0x180 * 4?
+ tvb_heapmeta_size = 0x200
+ #tvb_heapmeta_size = 0x600
+ tvb_heapmeta = ctx.uobj.new_buf(tvb_heapmeta_size, "TVB Heap Meta", track=False).push()
+ work.add(tvb_heapmeta)
+
+ unk_tile_buf1 = self.ctx.uobj.new_buf(tvb_tilemap_size * nclusters, "Unk tile buf 1", track=True)
+ print("tvb_tilemap_size", hex(tvb_tilemap_size))
+ unk_tile_buf2 = self.ctx.uobj.new_buf(0x4 * nclusters, "Unk tile buf 2", track=True)
+ #size = 0xc0 * nclusters
+ size = 0xc80
+ unk_tile_buf3 = self.ctx.uobj.new_buf(size, "Unk tile buf 3", track=True)
+ unk_tile_buf4 = self.ctx.uobj.new_buf(0x280 * nclusters, "Unk tile buf 4", track=True)
+ unk_tile_buf5 = self.ctx.uobj.new_buf(0x30 * nclusters, "Unk tile buf 5", track=True)
+ work.add(unk_tile_buf1)
+ work.add(unk_tile_buf2)
+ work.add(unk_tile_buf3)
+ work.add(unk_tile_buf4)
+ work.add(unk_tile_buf5)
+
+ ##### Buffer stuff?
+
+ # buffer related?
+ bufferthing_buf = ctx.uobj.new_buf(0x80, "BufferThing.unkptr_18", track=True)
+ work.add(bufferthing_buf)
+
+ work.buf_desc = buf_desc = agx.kobj.new(BufferThing, track=False)
+ work.add(buf_desc)
+ buf_desc.unk_0 = 0x0
+ buf_desc.unk_8 = 0x0
+ buf_desc.unk_10 = 0x0
+ buf_desc.unkptr_18 = bufferthing_buf._addr
+ buf_desc.unk_20 = 0x0
+ buf_desc.bm_misc_addr = self.buffer_mgr.misc_obj._addr
+ buf_desc.unk_2c = 0x0
+ buf_desc.unk_30 = 0x0
+ buf_desc.unk_38 = 0x0
+ buf_desc.push()
+
+ uuid_3d = cmdbuf.cmd_3d_id
+ uuid_ta = cmdbuf.cmd_ta_id
+ encoder_id = cmdbuf.encoder_id
+
+ #print(barrier_cmd)
+
+ #self.wq_ta.submit(ta_barrier_cmd)
+
+ ##### 3D barrier command
+
+ barrier_cmd = agx.kobj.new(WorkCommandBarrier, track=False)
+ work.add(barrier_cmd)
+ barrier_cmd.stamp = self.stamp_ta2
+ barrier_cmd.wait_value = self.stamp_value_ta
+ barrier_cmd.stamp_self = self.stamp_value_3d
+ barrier_cmd.event = ev_ta.id
+ barrier_cmd.uuid = uuid_3d
+
+ #print(barrier_cmd)
+
+ self.wq_3d.submit(barrier_cmd)
+
+ ##### 3D execution
+
+ work.wc_3d = wc_3d = agx.kobj.new(WorkCommand3D, track=False)
+ work.add(work.wc_3d)
+ wc_3d.counter = 0
+ wc_3d.context_id = self.ctx_id
+ wc_3d.unk_8 = 0
+ wc_3d.event_control = self.event_control
+ wc_3d.buffer_mgr = self.buffer_mgr.info
+ wc_3d.buf_thing = buf_desc
+ wc_3d.unk_emptybuf_addr = self.unk_emptybuf._addr
+ wc_3d.tvb_tilemap = tvb_tilemap._addr
+ wc_3d.unk_40 = 0x88
+ wc_3d.unk_48 = 0x1
+ wc_3d.tile_blocks_y = mtile_y1
+ wc_3d.tile_blocks_x = mtile_x1
+ wc_3d.unk_50 = 0x0
+ wc_3d.unk_58 = 0x0
+
+ TAN_60 = 1.732051
+ wc_3d.merge_upper_x = TAN_60 / width
+ wc_3d.merge_upper_y = TAN_60 / height
+ wc_3d.unk_68 = 0x0
+ wc_3d.tile_count = tiles
+
+ wc_3d.unk_758 = Flag()
+ wc_3d.unk_75c = Flag()
+ wc_3d.unk_buf = WorkCommand1_UnkBuf()
+ wc_3d.busy_flag = Flag()
+ wc_3d.unk_buf2 = WorkCommand1_UnkBuf2()
+ wc_3d.unk_buf2.unk_0 = 0
+ wc_3d.unk_buf2.unk_8 = 0
+ wc_3d.unk_buf2.unk_10 = 1
+ wc_3d.ts1 = TimeStamp(0)
+ wc_3d.ts2 = TimeStamp(self.ts3d_1._addr)
+ wc_3d.ts3 = TimeStamp(self.ts3d_2._addr)
+ wc_3d.unk_914 = 0
+ wc_3d.unk_918 = 0
+ wc_3d.unk_920 = 0
+ wc_3d.unk_924 = 1
+ # Ventura
+ wc_3d.unk_928_0 = 0
+ wc_3d.unk_928_4 = 0
+ wc_3d.ts_flag = TsFlag()
+
+ # cmdbuf.ds_flags
+ # 0 - no depth
+ # 0x80000 - depth store enable
+ # 0x08000 - depth load enable
+
+ # 0x00044 - compressed depth
+
+ # 0x40000 - stencil store enable
+ # 0x04000 - stencil load enable
+ # 0x00110 - compressed stencil
+
+ # Z store format
+ # 0x4000000 - Depth16Unorm
+
+ # For Depth16Unorm: 0x40000 here also
+ # AFBI.[ 0. 4] unk1 = 0x4c000
+
+ # ASAHI_CMDBUF_SET_WHEN_RELOADING_Z_OR_S
+ # Actually set when loading *and* storing Z, OR loading *and* storing S
+
+ # Structures embedded in WorkCommand3D
+ if True:
+ wc_3d.struct_1 = Start3DStruct1()
+ wc_3d.struct_1.store_pipeline_bind = cmdbuf.store_pipeline_bind
+ wc_3d.struct_1.store_pipeline_addr = cmdbuf.store_pipeline | 4
+ wc_3d.struct_1.unk_8 = 0x0
+ wc_3d.struct_1.unk_c = 0x0
+
+ TAN_60 = 1.732051
+ wc_3d.struct_1.merge_upper_x = TAN_60 / width
+ wc_3d.struct_1.merge_upper_y = TAN_60 / height
+
+ wc_3d.struct_1.unk_18 = 0x0
+ # ISP_MTILE_SIZE
+ wc_3d.struct_1.tile_blocks_y = mtile_y1
+ wc_3d.struct_1.tile_blocks_x = mtile_x1
+ wc_3d.struct_1.unk_24 = 0x0
+ wc_3d.struct_1.tile_counts = ((tiles_y-1) << 12) | (tiles_x-1)
+ wc_3d.struct_1.unk_2c = 0x8
+ wc_3d.struct_1.depth_clear_val1 = cmdbuf.depth_clear_value
+ wc_3d.struct_1.stencil_clear_val1 = cmdbuf.stencil_clear_value
+ wc_3d.struct_1.unk_35 = 0x7 # clear flags? 2 = depth 4 = stencil?
+ wc_3d.struct_1.unk_36 = 0x0
+ wc_3d.struct_1.unk_38 = 0x0
+ wc_3d.struct_1.unk_3c = 0x1
+ wc_3d.struct_1.unk_40 = 0
+ wc_3d.struct_1.unk_44_padding = bytes(0xac)
+ wc_3d.struct_1.depth_bias_array = Start3DArrayAddr(cmdbuf.depth_bias_array)
+ wc_3d.struct_1.scissor_array = Start3DArrayAddr(cmdbuf.scissor_array)
+ wc_3d.struct_1.visibility_result_buffer = 0x0
+ wc_3d.struct_1.unk_118 = 0x0
+ wc_3d.struct_1.unk_120 = [0] * 37
+ wc_3d.struct_1.unk_reload_pipeline = Start3DClearPipelineBinding(
+ cmdbuf.partial_reload_pipeline_bind, cmdbuf.partial_reload_pipeline | 4)
+ wc_3d.struct_1.unk_258 = 0
+ wc_3d.struct_1.unk_260 = 0
+ wc_3d.struct_1.unk_268 = 0
+ wc_3d.struct_1.unk_270 = 0
+ wc_3d.struct_1.reload_pipeline = Start3DClearPipelineBinding(
+ cmdbuf.partial_reload_pipeline_bind, cmdbuf.partial_reload_pipeline | 4)
+ wc_3d.struct_1.depth_flags = cmdbuf.ds_flags | 0x44
+ wc_3d.struct_1.unk_290 = 0x0
+ wc_3d.struct_1.depth_buffer_ptr1 = cmdbuf.depth_buffer
+ wc_3d.struct_1.unk_2a0 = 0x0
+ wc_3d.struct_1.unk_2a8 = 0x0
+ wc_3d.struct_1.depth_buffer_ptr2 = cmdbuf.depth_buffer
+ wc_3d.struct_1.depth_buffer_ptr3 = cmdbuf.depth_buffer
+ wc_3d.struct_1.depth_aux_buffer_ptr = depth_aux_buffer_addr
+ wc_3d.struct_1.stencil_buffer_ptr1 = cmdbuf.stencil_buffer
+ wc_3d.struct_1.unk_2d0 = 0x0
+ wc_3d.struct_1.unk_2d8 = 0x0
+ wc_3d.struct_1.stencil_buffer_ptr2 = cmdbuf.stencil_buffer
+ wc_3d.struct_1.stencil_buffer_ptr3 = cmdbuf.stencil_buffer
+ wc_3d.struct_1.stencil_aux_buffer_ptr = stencil_aux_buffer_addr
+ wc_3d.struct_1.unk_2f8 = [0x0, 0x0]
+ wc_3d.struct_1.aux_fb_unk0 = 4 #0x8 # sometimes 4
+ wc_3d.struct_1.unk_30c = 0x0
+ wc_3d.struct_1.aux_fb = AuxFBInfo(0xc000, 0, width, height)
+ wc_3d.struct_1.unk_320_padding = bytes(0x10)
+ wc_3d.struct_1.unk_partial_store_pipeline = Start3DStorePipelineBinding(
+ cmdbuf.partial_store_pipeline_bind, cmdbuf.partial_store_pipeline | 4)
+ wc_3d.struct_1.partial_store_pipeline = Start3DStorePipelineBinding(
+ cmdbuf.partial_store_pipeline_bind, cmdbuf.partial_store_pipeline | 4)
+ wc_3d.struct_1.depth_clear_val2 = cmdbuf.depth_clear_value
+ wc_3d.struct_1.stencil_clear_val2 = cmdbuf.stencil_clear_value
+ wc_3d.struct_1.unk_375 = 3
+ wc_3d.struct_1.unk_376 = 0x0
+ wc_3d.struct_1.unk_378 = 0x10
+ wc_3d.struct_1.unk_37c = 0x0
+ wc_3d.struct_1.unk_380 = 0x0
+ wc_3d.struct_1.unk_388 = 0x0
+ wc_3d.struct_1.unk_390_0 = 0x0 # Ventura
+ wc_3d.struct_1.depth_dimensions = (width - 1) | ((height - 1) << 15)
+
+ if True:
+ wc_3d.struct_2 = Start3DStruct2()
+ wc_3d.struct_2.unk_0 = 0xa000
+ wc_3d.struct_2.clear_pipeline = Start3DClearPipelineBinding(
+ cmdbuf.load_pipeline_bind, cmdbuf.load_pipeline | 4)
+ wc_3d.struct_2.unk_18 = 0x88
+ wc_3d.struct_2.scissor_array = cmdbuf.scissor_array
+ wc_3d.struct_2.depth_bias_array = cmdbuf.depth_bias_array
+ wc_3d.struct_2.aux_fb = wc_3d.struct_1.aux_fb
+ # ISP_ZLS_PIXELS
+ wc_3d.struct_2.depth_dimensions = wc_3d.struct_1.depth_dimensions
+ wc_3d.struct_2.visibility_result_buffer = 0x0
+ # ISP_ZLSCTL
+ wc_3d.struct_2.depth_flags = cmdbuf.ds_flags
+ wc_3d.struct_2.unk_58_g14_0 = 0x4040404
+ wc_3d.struct_2.unk_58_g14_8 = 0
+ wc_3d.struct_2.depth_buffer_ptr1 = cmdbuf.depth_buffer
+ wc_3d.struct_2.depth_buffer_ptr2 = cmdbuf.depth_buffer
+ wc_3d.struct_2.unk_68_g14_0 = 0
+ wc_3d.struct_2.stencil_buffer_ptr1 = cmdbuf.stencil_buffer
+ wc_3d.struct_2.stencil_buffer_ptr2 = cmdbuf.stencil_buffer
+ wc_3d.struct_2.unk_78 = [0] * 4
+ wc_3d.struct_2.depth_aux_buffer_ptr1 = depth_aux_buffer_addr
+ wc_3d.struct_2.unk_a0 = 0
+ wc_3d.struct_2.depth_aux_buffer_ptr2 = depth_aux_buffer_addr
+ wc_3d.struct_2.unk_b0 = 0
+ wc_3d.struct_2.stencil_aux_buffer_ptr1 = stencil_aux_buffer_addr
+ wc_3d.struct_2.unk_c0 = 0
+ wc_3d.struct_2.stencil_aux_buffer_ptr2 = stencil_aux_buffer_addr
+ wc_3d.struct_2.unk_d0 = 0
+ wc_3d.struct_2.tvb_tilemap = tvb_tilemap._addr
+ wc_3d.struct_2.tvb_heapmeta_addr = tvb_heapmeta._addr
+ wc_3d.struct_2.unk_e8 = tiling_params.size1 << 24
+ wc_3d.struct_2.tvb_heapmeta_addr2 = tvb_heapmeta._addr
+ # 0x10000 - clear empty tiles
+ # ISP_CTL (but bits seem to have moved)
+ wc_3d.struct_2.unk_f8 = 0x10280 #0x10280 # TODO: varies 0, 0x280, 0x10000, 0x10280
+ wc_3d.struct_2.aux_fb_ptr = aux_fb._addr
+ wc_3d.struct_2.unk_108 = [0x0, 0x0, 0x0, 0x0, 0x0, 0x0]
+ wc_3d.struct_2.pipeline_base = self.ctx.pipeline_base
+ wc_3d.struct_2.unk_140 = 0x8c60
+ wc_3d.struct_2.unk_148 = 0x0
+ wc_3d.struct_2.unk_150 = 0x0
+ wc_3d.struct_2.unk_158 = 0x1c
+ wc_3d.struct_2.unk_160 = 0
+ wc_3d.struct_2.unk_168_padding = bytes(0x1d8)
+ wc_3d.struct_2.unk_198_padding = bytes(0x1a8)
+
+ if True:
+ wc_3d.struct_6 = Start3DStruct6()
+ wc_3d.struct_6.tvb_overflow_count = 0x0
+ wc_3d.struct_6.unk_8 = 0x0 # 1?
+ wc_3d.struct_6.unk_c = 0x0 # 1?
+ wc_3d.struct_6.unk_10 = 0x0
+ wc_3d.struct_6.encoder_id = cmdbuf.encoder_id
+ wc_3d.struct_6.unk_1c = 0xffffffff
+ wc_3d.struct_6.unknown_buffer = unk_buf._addr
+ wc_3d.struct_6.unk_28 = 0x0
+ wc_3d.struct_6.unk_30 = 0x0
+ wc_3d.struct_6.unk_34 = 0x0
+
+ if True:
+ wc_3d.struct_7 = Start3DStruct7()
+ wc_3d.struct_7.unk_0 = 0x0
+ wc_3d.struct_7.stamp1 = self.stamp_3d1
+ wc_3d.struct_7.stamp2 = self.stamp_3d2
+ wc_3d.struct_7.stamp_value = self.stamp_value_3d
+ wc_3d.struct_7.ev_3d = ev_3d.id
+ wc_3d.struct_7.evctl_index = 0x0
+ wc_3d.struct_7.unk_24 = 1
+ wc_3d.struct_7.uuid = uuid_3d
+ wc_3d.struct_7.prev_stamp_value = self.prev_stamp_value_3d >> 8
+ wc_3d.struct_7.unk_30 = 0x0
+
+ wc_3d.set_addr() # Update inner structure addresses
+ #print("WC3D", hex(wc_3d._addr))
+ #print(" s1", hex(wc_3d.struct_1._addr))
+ #print(" s2", hex(wc_3d.struct_2._addr))
+ #print(" s6", hex(wc_3d.struct_6._addr))
+ #print(" s7", hex(wc_3d.struct_7._addr))
+
+ ms = GPUMicroSequence(agx)
+
+ start_3d = Start3DCmd()
+ start_3d.struct1 = wc_3d.struct_1 # 0x44 bytes!
+ start_3d.struct2 = wc_3d.struct_2 # 0x168 bytes!
+ start_3d.buf_thing = buf_desc
+ start_3d.stats_ptr = agx.initdata.regionB.stats_3d.stats._addr
+ start_3d.busy_flag_ptr = wc_3d.busy_flag._addr
+ start_3d.struct6 = wc_3d.struct_6 # 4 bytes!
+ start_3d.struct7 = wc_3d.struct_7 # 4 bytes!
+ start_3d.cmdqueue_ptr = self.wq_3d.info._addr
+ start_3d.workitem_ptr = wc_3d._addr
+ start_3d.context_id = self.ctx_id
+ start_3d.unk_50 = 0x1
+ start_3d.event_generation = self.event_control.generation
+ start_3d.buffer_mgr_slot = self.buffer_mgr_slot
+ start_3d.unk_5c = 0x0
+ start_3d.prev_stamp_value = self.prev_stamp_value_3d >> 8
+ start_3d.unk_68 = 0x0
+ start_3d.unk_buf_ptr = wc_3d.unk_758._addr
+ start_3d.unk_buf2_ptr = wc_3d.unk_buf2._addr
+ start_3d.unk_7c = 0x0
+ start_3d.unk_80 = 0x0
+ start_3d.unk_84 = 0x0
+ start_3d.uuid = uuid_3d
+ start_3d.attachments = []
+ start_3d.unk_194 = 0
+ start_3d.unkptr_19c = self.event_control.unk_buf._addr
+
+ work.fb = None
+ work.depth = None
+
+ for i in cmdbuf.attachments[:cmdbuf.attachment_count]:
+ cache_lines = align_up(i.size, 128) // 128
+ order = 1 # FIXME
+ start_3d.attachments.append(Attachment(i.pointer, cache_lines, 0x17, order)) # FIXME check
+ if work.fb is None and i.type == ASAHI_ATTACHMENT_C:
+ work.fb = i.pointer
+ if work.depth is None and i.type == ASAHI_ATTACHMENT_Z:
+ work.depth = i.pointer
+ start_3d.attachments += [Attachment(0, 0, 0, 0)] * (16 - len(start_3d.attachments))
+ start_3d.num_attachments = cmdbuf.attachment_count
+ start_3d.unk_190 = 0x0
+
+ start_3d_offset = ms.append(start_3d)
+
+ ts1 = TimestampCmd()
+ ts1.unk_1 = 0x0
+ ts1.unk_2 = 0x0
+ ts1.unk_3 = 0x80
+ ts1.ts0_addr = wc_3d.ts1._addr
+ ts1.ts1_addr = wc_3d.ts2._addr
+ ts1.ts2_addr = wc_3d.ts2._addr
+ ts1.cmdqueue_ptr = self.wq_3d.info._addr
+ ts1.unk_24 = 0x0
+ if Ver.check("V >= V13_0B4"):
+ ts1.unkptr_2c_0 = wc_3d.ts_flag._addr
+ ts1.uuid = uuid_3d
+ ts1.unk_30_padding = 0x0
+ ms.append(ts1)
+
+ ms.append(WaitForInterruptCmd(0, 1, 0))
+
+ ts2 = TimestampCmd()
+ ts2.unk_1 = 0x0
+ ts2.unk_2 = 0x0
+ ts2.unk_3 = 0x0
+ ts2.ts0_addr = wc_3d.ts1._addr
+ ts2.ts1_addr = wc_3d.ts2._addr
+ ts2.ts2_addr = wc_3d.ts3._addr
+ ts2.cmdqueue_ptr = self.wq_3d.info._addr
+ ts2.unk_24 = 0x0
+ if Ver.check("V >= V13_0B4"):
+ ts2.unkptr_2c_0 = wc_3d.ts_flag._addr
+ ts2.uuid = uuid_3d
+ ts2.unk_30_padding = 0x0
+ ms.append(ts2)
+
+ finish_3d = Finalize3DCmd()
+ finish_3d.uuid = uuid_3d
+ finish_3d.unk_8 = 0
+ finish_3d.stamp = self.stamp_3d2
+ finish_3d.stamp_value = self.stamp_value_3d
+ finish_3d.unk_18 = 0
+ finish_3d.buf_thing = buf_desc
+ finish_3d.buffer_mgr = self.buffer_mgr.info
+ finish_3d.unk_2c = 1
+ finish_3d.stats_ptr = agx.initdata.regionB.stats_3d.stats._addr
+ finish_3d.struct7 = wc_3d.struct_7
+ finish_3d.busy_flag_ptr = wc_3d.busy_flag._addr
+ finish_3d.cmdqueue_ptr = self.wq_3d.info._addr
+ finish_3d.workitem_ptr = wc_3d._addr
+ finish_3d.unk_5c = self.ctx_id
+ finish_3d.unk_buf_ptr = wc_3d.unk_758._addr
+ finish_3d.unk_6c = 0
+ finish_3d.unk_74 = 0
+ finish_3d.unk_7c = 0
+ finish_3d.unk_84 = 0
+ finish_3d.unk_8c = 0
+ finish_3d.unk_8c_g14 = 0
+ finish_3d.restart_branch_offset = start_3d_offset - ms.off
+ finish_3d.unk_98 = 0
+ finish_3d.unk_9c = bytes(0x10)
+ ms.append(finish_3d)
+ ms.finalize()
+
+ work.add(ms.obj)
+
+ wc_3d.microsequence_ptr = ms.obj._addr
+ wc_3d.microsequence_size = ms.size
+
+ print(wc_3d)
+ self.wq_3d.submit(wc_3d)
+
+ ##### TA init
+
+ #print(ctx_info)
+ if wait_for is not None:
+ barrier_cmd = agx.kobj.new(WorkCommandBarrier, track=False)
+ work.add(barrier_cmd)
+ if not isinstance(wait_for, tuple):
+ barrier_cmd.stamp = wait_for.renderer.stamp_3d2
+ barrier_cmd.wait_value = wait_for.stamp_value_3d
+ barrier_cmd.event = wait_for.ev_3d.id
+ else:
+ barrier_cmd.stamp_addr = wait_for[0]
+ barrier_cmd.wait_value = wait_for[1]
+ barrier_cmd.event = wait_for[2]
+
+ barrier_cmd.stamp_self = self.stamp_value_ta
+ barrier_cmd.uuid = uuid_ta
+
+ self.wq_ta.submit(barrier_cmd)
+
+ if not self.buffer_mgr_initialized:
+ wc_initbm = agx.kobj.new(WorkCommandInitBM, track=False)
+ work.add(wc_initbm)
+ wc_initbm.context_id = self.ctx_id
+ wc_initbm.buffer_mgr_slot = self.buffer_mgr_slot
+ wc_initbm.unk_c = 0
+ wc_initbm.unk_10 = self.buffer_mgr.info.block_count
+ wc_initbm.buffer_mgr = self.buffer_mgr.info
+ wc_initbm.stamp_value = self.stamp_value_ta
+
+ self.wq_ta.submit(wc_initbm)
+
+ self.buffer_mgr_initialized = True
+
+ ##### TA execution
+
+ work.wc_ta = wc_ta = agx.kobj.new(WorkCommandTA, track=False)
+ work.add(work.wc_ta)
+ wc_ta.context_id = self.ctx_id
+ wc_ta.counter = 1
+ wc_ta.unk_8 = 0
+ wc_ta.event_control = self.event_control
+ wc_ta.buffer_mgr_slot = self.buffer_mgr_slot
+ wc_ta.buffer_mgr = self.buffer_mgr.info
+ wc_ta.buf_thing = buf_desc
+ wc_ta.unk_emptybuf_addr = wc_3d.unk_emptybuf_addr
+ wc_ta.unk_34 = 0x0
+
+ wc_ta.unk_154 = bytes(0x268)
+ wc_ta.unk_3e8 = bytes(0x74)
+ wc_ta.unk_594 = WorkCommand0_UnkBuf()
+
+ wc_ta.ts1 = TimeStamp(0)
+ wc_ta.ts2 = TimeStamp(self.tsta_1._addr)
+ wc_ta.ts3 = TimeStamp(self.tsta_2._addr)
+ wc_ta.unk_5c4 = 0
+ wc_ta.unk_5c8 = 0
+ wc_ta.unk_5cc = 0
+ wc_ta.unk_5d0 = 0
+ wc_ta.unk_5d4 = 1 #0x27 #1
+ # Ventura
+ wc_ta.unk_5e0 = 0
+ wc_ta.unk_5e4 = 0
+ wc_ta.ts_flag = TsFlag()
+
+ # Structures embedded in WorkCommandTA
+ if True:
+ wc_ta.tiling_params = tiling_params
+
+ if True:
+ wc_ta.struct_2 = StartTACmdStruct2()
+ wc_ta.struct_2.unk_0 = 0x200
+ wc_ta.struct_2.unk_8 = 0x1e3ce508 # fixed
+ wc_ta.struct_2.unk_c = 0x1e3ce508 # fixed
+ wc_ta.struct_2.tvb_tilemap = tvb_tilemap._addr
+ wc_ta.struct_2.tvb_cluster_tilemaps = unk_tile_buf1._addr
+ wc_ta.struct_2.tpc = self.tpc._addr
+ wc_ta.struct_2.tvb_heapmeta_addr = tvb_heapmeta._addr | 0x8000_0000_0000_0000
+ wc_ta.struct_2.iogpu_unk_54 = 0x6b0003 # fixed
+ wc_ta.struct_2.iogpu_unk_55 = 0x3a0012 # fixed
+ wc_ta.struct_2.iogpu_unk_56 = 0x1 # fixed
+ wc_ta.struct_2.tvb_cluster_meta1 = unk_tile_buf2._addr | 0x4_0000_0000_0000
+ wc_ta.struct_2.unk_48 = 0xa000
+ wc_ta.struct_2.unk_50 = 0x88 # fixed
+ wc_ta.struct_2.tvb_heapmeta_addr2 = tvb_heapmeta._addr
+ wc_ta.struct_2.unk_60 = 0x0 # fixed
+ wc_ta.struct_2.core_mask = 0xffffffffffffffff
+ #wc_ta.struct_2.unk_68 = 0xff << (8 * (self.buffer_mgr_slot % 8))
+ wc_ta.struct_2.iogpu_deflake_1 = deflake_1._addr
+ wc_ta.struct_2.iogpu_deflake_2 = deflake_2._addr
+ wc_ta.struct_2.unk_80 = 0x1 # fixed
+ wc_ta.struct_2.iogpu_deflake_3 = deflake_3._addr | 0x4_0000_0000_0000 # check
+ wc_ta.struct_2.encoder_addr = cmdbuf.encoder_ptr
+ wc_ta.struct_2.tvb_cluster_meta2 = unk_tile_buf3._addr
+ wc_ta.struct_2.tvb_cluster_meta3 = unk_tile_buf4._addr
+ wc_ta.struct_2.tiling_control = 0xa040 #0xa041 # fixed
+ wc_ta.struct_2.unk_b0 = [0x0, 0x0, 0x0, 0x0, 0x0, 0x0] # fixed
+ wc_ta.struct_2.pipeline_base = self.ctx.pipeline_base
+ wc_ta.struct_2.tvb_cluster_meta4 = unk_tile_buf5._addr | 0x3000_0000_0000_0000
+ wc_ta.struct_2.unk_f0 = 0x20 # fixed
+ wc_ta.struct_2.unk_f8 = 0x8c60 # fixed
+ wc_ta.struct_2.unk_100 = [0x0, 0x0, 0x0] # fixed
+ wc_ta.struct_2.unk_118 = 0x1c # fixed
+
+ if True:
+ wc_ta.struct_3 = StartTACmdStruct3()
+ wc_ta.struct_3.unk_480 = [0x0, 0x0, 0x0, 0x0, 0x0, 0x0] # fixed
+ wc_ta.struct_3.unk_498 = 0x0 # fixed
+ wc_ta.struct_3.unk_4a0 = 0x0 # fixed
+ wc_ta.struct_3.iogpu_deflake_1 = deflake_1._addr
+ wc_ta.struct_3.unk_4ac = 0x0 # fixed
+ wc_ta.struct_3.unk_4b0 = 0x0 # fixed
+ wc_ta.struct_3.unk_4b8 = 0x0 # fixed
+ wc_ta.struct_3.unk_4bc = 0x0 # fixed
+ wc_ta.struct_3.unk_4c4_padding = bytes(0x48)
+ wc_ta.struct_3.unk_50c = 0x0 # fixed
+ wc_ta.struct_3.unk_510 = 0x0 # fixed
+ wc_ta.struct_3.unk_518 = 0x0 # fixed
+ wc_ta.struct_3.unk_520 = 0x0 # fixed
+ wc_ta.struct_3.unk_528 = 0x0 # fixed
+ wc_ta.struct_3.unk_52c = 0x0 # fixed
+ wc_ta.struct_3.unk_530 = 0x0 # fixed
+ wc_ta.struct_3.encoder_id = cmdbuf.encoder_id
+ wc_ta.struct_3.unk_538 = 0x0 # fixed
+ wc_ta.struct_3.unk_53c = 0xffffffff
+ wc_ta.struct_3.unknown_buffer = wc_3d.struct_6.unknown_buffer
+ wc_ta.struct_3.unk_548 = 0x0 # fixed
+ wc_ta.struct_3.unk_550 = [
+ 0x0, 0x0, # fixed
+ 0x0, # 1 for boot stuff?
+ 0x0, 0x0, 0x0] # fixed
+ wc_ta.struct_3.stamp1 = self.stamp_ta1
+ wc_ta.struct_3.stamp2 = self.stamp_ta2
+ wc_ta.struct_3.stamp_value = self.stamp_value_ta
+ wc_ta.struct_3.ev_ta = ev_ta.id
+ wc_ta.struct_3.evctl_index = 0
+ wc_ta.struct_3.unk_584 = 0x0 # 1 for boot stuff?
+ wc_ta.struct_3.uuid2 = uuid_ta
+ wc_ta.struct_3.prev_stamp_value = self.prev_stamp_value_ta >> 8
+ wc_ta.struct_3.unk_590 = 0 # sometimes 1?
+
+ wc_ta.set_addr() # Update inner structure addresses
+ #print("wc_ta", wc_ta)
+
+ ms = GPUMicroSequence(agx)
+
+ start_ta = StartTACmd()
+ start_ta.tiling_params = wc_ta.tiling_params
+ start_ta.struct2 = wc_ta.struct_2 # len 0x120
+ start_ta.buffer_mgr = self.buffer_mgr.info
+ start_ta.buf_thing = buf_desc
+ start_ta.stats_ptr = agx.initdata.regionB.stats_ta.stats._addr
+ start_ta.cmdqueue_ptr = self.wq_ta.info._addr
+ start_ta.context_id = self.ctx_id
+ start_ta.unk_38 = 1
+ start_ta.event_generation = self.event_control.generation
+ start_ta.buffer_mgr_slot = self.buffer_mgr_slot
+ start_ta.unk_48 = 0#1 #0
+ start_ta.unk_50 = 0
+ start_ta.struct3 = wc_ta.struct_3
+
+ start_ta.unkptr_5c = wc_ta.unk_594._addr
+ start_ta.unk_64 = 0x0 # fixed
+ start_ta.unk_68 = 0x0 # sometimes 1?
+ start_ta.uuid = uuid_ta
+ start_ta.unk_70 = 0x0 # fixed
+ start_ta.unk_74 = [ # fixed
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ ]
+ start_ta.unk_15c = 0x0 # fixed
+ start_ta.unk_160 = 0x0 # fixed
+ start_ta.unk_168 = 0x0 # fixed
+ start_ta.unk_16c = 0x0 # fixed
+ start_ta.unk_170 = 0x0 # fixed
+ start_ta.unk_178 = 0x0 # fixed?
+ start_ta.unk_17c = 0x0
+ start_ta.unkptr_180 = self.event_control.unk_buf._addr
+ start_ta.unk_188 = 0x0
+
+ start_ta_offset = ms.append(start_ta)
+
+ ts1 = TimestampCmd()
+ ts1.unk_1 = 0x0
+ ts1.unk_2 = 0x0
+ ts1.unk_3 = 0x80
+ ts1.ts0_addr = wc_ta.ts1._addr
+ ts1.ts1_addr = wc_ta.ts2._addr
+ ts1.ts2_addr = wc_ta.ts2._addr
+ ts1.cmdqueue_ptr = self.wq_ta.info._addr
+ ts1.unk_24 = 0x0
+ if Ver.check("V >= V13_0B4"):
+ ts1.unkptr_2c_0 = wc_ta.ts_flag._addr
+ ts1.uuid = uuid_ta
+ ts1.unk_30_padding = 0x0
+ ms.append(ts1)
+
+ ms.append(WaitForInterruptCmd(1, 0, 0))
+
+ ts2 = TimestampCmd()
+ ts2.unk_1 = 0x0
+ ts2.unk_2 = 0x0
+ ts2.unk_3 = 0x0
+ ts2.ts0_addr = wc_ta.ts1._addr
+ ts2.ts1_addr = wc_ta.ts2._addr
+ ts2.ts2_addr = wc_ta.ts3._addr
+ ts2.cmdqueue_ptr = self.wq_ta.info._addr
+ ts2.unk_24 = 0x0
+ if Ver.check("V >= V13_0B4"):
+ ts2.unkptr_2c_0 = wc_ta.ts_flag._addr
+ ts2.uuid = uuid_ta
+ ts2.unk_30_padding = 0x0
+ ms.append(ts2)
+
+ finish_ta = FinalizeTACmd()
+ finish_ta.buf_thing = buf_desc
+ finish_ta.buffer_mgr = self.buffer_mgr.info
+ finish_ta.stats_ptr = agx.initdata.regionB.stats_ta.stats._addr
+ finish_ta.cmdqueue_ptr = self.wq_ta.info._addr
+ finish_ta.context_id = self.ctx_id
+ finish_ta.unk_28 = 0x0 # fixed
+ finish_ta.struct3 = wc_ta.struct_3
+ finish_ta.unk_34 = 0x0 # fixed
+ finish_ta.uuid = uuid_ta
+ finish_ta.stamp = self.stamp_ta2
+ finish_ta.stamp_value = self.stamp_value_ta
+ finish_ta.unk_48 = 0x0 # fixed
+ finish_ta.unk_50 = 0x0 # fixed
+ finish_ta.unk_54 = 0x0 # fixed
+ finish_ta.unk_58 = 0x0 # fixed
+ finish_ta.unk_60 = 0x0 # fixed
+ finish_ta.unk_64 = 0x0 # fixed
+ finish_ta.unk_68 = 0x0 # fixed
+ finish_ta.unk_6c_g14 = 0 # fixed
+ finish_ta.restart_branch_offset = start_ta_offset - ms.off
+ finish_ta.unk_70 = 0x0 # fixed
+ finish_ta.unk_74 = bytes(0x10) # Ventura
+ ms.append(finish_ta)
+
+ ms.finalize()
+
+ work.add(ms.obj)
+
+ wc_ta.unkptr_45c = self.tpc._addr
+ wc_ta.tvb_size = tpc_size
+ wc_ta.microsequence_ptr = ms.obj._addr
+ wc_ta.microsequence_size = ms.size
+ wc_ta.ev_3d = ev_3d.id
+ wc_ta.stamp_value = self.stamp_value_ta
+
+ print(wc_ta)
+ self.wq_ta.submit(wc_ta)
+
+ self.agx.log("Submit done")
+ return work
+
+ def run(self):
+ ##### Run queues
+ self.agx.log("Run queues")
+ self.agx.ch.queue[self.queue].q_3D.run(self.wq_3d, self.ev_3d.id)
+ self.agx.ch.queue[self.queue].q_TA.run(self.wq_ta, self.ev_ta.id)
+ self.agx.log("Run done")
+
+ def wait(self):
+ self.agx.log("Waiting...")
+ work = self.work[-1]
+
+ ##### Wait for work completion
+ while not self.ev_3d.fired:
+ self.agx.wait_for_events(timeout=2.0)
+
+ if not self.ev_3d.fired:
+ self.agx.log("3D event didn't fire")
+
+ self.agx.log(f"Event {self.ev_3d.id} fired")
+ #print("Stamps:")
+ #print(self.stamp_ta1.pull())
+ #print(self.stamp_ta2.pull())
+ #print(self.stamp_3d1.pull())
+ #print(self.stamp_3d2.pull())
+
+ #print("WCs:")
+ #print(work.wc_3d.pull())
+ #print(work.wc_ta.pull())
+
+ #if work.fb is not None and work.width and work.height:
+ if work.fb is not None and work.width and work.height and work.width == 1920:
+ agx = self.agx
+ self.agx.log(f"Render {work.width}x{work.height} @ {work.fb:#x}")
+ base, obj = self.agx.find_object(work.fb, self.ctx_id)
+
+ #unswizzle(agx, obj._paddr, work.width, work.height, 4, "fb.bin", grid=False)
+ #open("fb.bin", "wb").write(self.agx.u.iface.readmem(obj._paddr, work.width*work.height*4))
+ #os.system(f"convert -size {work.width}x{work.height} -depth 8 rgba:fb.bin -alpha off frame{self.frames}.png")
+ self.agx.p.fb_blit(0, 0, work.width, work.height, obj._paddr, work.width, PIX_FMT.XBGR)
+
+ if False: #work.depth is not None:
+ base, obj = self.agx.find_object(work.depth, self.ctx_id)
+
+ width = align_up(work.width, 64)
+ height = align_up(work.height, 64)
+
+ obj.pull()
+ chexdump(obj.val)
+
+ unswizzle(self.agx, obj._paddr, work.width, work.height, 4, "depth.bin", grid=False)
+ os.system(f"convert -size {work.width}x{work.height} -depth 8 rgba:depth.bin -alpha off depth.png")
+
+ for i in self.work:
+ i.free()
+
+ self.work = []
diff --git a/tools/proxyclient/m1n1/agx/shim.py b/tools/proxyclient/m1n1/agx/shim.py
new file mode 100644
index 0000000..253812a
--- /dev/null
+++ b/tools/proxyclient/m1n1/agx/shim.py
@@ -0,0 +1,244 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+
+import errno, ctypes, sys, atexit, os, os.path, mmap
+from construct import *
+
+from m1n1 import malloc
+from m1n1.utils import Register32
+from m1n1.agx import AGX
+from m1n1.agx.render import *
+from m1n1.agx.uapi import *
+from m1n1.proxyutils import *
+from m1n1.utils import *
+
+PAGE_SIZE = 32768
+SHIM_MEM_SIZE = 4 * 1024 * 1024 * 1024
+
+class IOCTL(Register32):
+ NR = 7, 0
+ TYPE = 15, 8
+ SIZE = 29, 16
+ DIR = 31, 30
+
+_IOC_NONE = 0
+_IOC_WRITE = 1
+_IOC_READ = 2
+
+_IO = lambda type, nr: IOCTL(TYPE=type, NR=nr, SIZE=0, DIR=_IOC_NONE)
+_IOR = lambda type, nr, size: IOCTL(TYPE=type, NR=nr, SIZE=size, DIR=_IOC_READ)
+_IOW = lambda type, nr, size: IOCTL(TYPE=type, NR=nr, SIZE=size, DIR=_IOC_WRITE)
+_IOWR = lambda type, nr, size: IOCTL(TYPE=type, NR=nr, SIZE=size, DIR=_IOC_READ|_IOC_WRITE)
+
+DRM_IOCTL_BASE = ord('d')
+
+def IO(nr):
+ def dec(f):
+ f._ioctl = _IO(DRM_IOCTL_BASE, nr)
+ return f
+ return dec
+
+def IOR(nr, cls):
+ def dec(f):
+ f._ioctl = _IOR(DRM_IOCTL_BASE, nr, cls.sizeof())
+ f._arg_cls = cls
+ return f
+ return dec
+
+def IOW(nr, cls):
+ def dec(f):
+ f._ioctl = _IOW(DRM_IOCTL_BASE, nr, cls.sizeof())
+ f._arg_cls = cls
+ return f
+ return dec
+
+def IOWR(nr, cls):
+ def dec(f):
+ f._ioctl = _IOWR(DRM_IOCTL_BASE, nr, cls.sizeof())
+ f._arg_cls = cls
+ return f
+ return dec
+
+class DRMAsahiShim:
+ def __init__(self, memfd):
+ self.memfd = memfd
+ self.initialized = False
+ self.ioctl_map = {}
+ for key in dir(self):
+ f = getattr(self, key)
+ ioctl = getattr(f, "_ioctl", None)
+ if ioctl is not None:
+ self.ioctl_map[ioctl.value] = ioctl, f
+ self.bos = {}
+ self.pull_buffers = bool(os.getenv("ASAHI_SHIM_PULL"))
+ self.dump_frames = bool(os.getenv("ASAHI_SHIM_DUMP"))
+ self.frame = 0
+ self.agx = None
+
+ def read_buf(self, ptr, size):
+ return ctypes.cast(ptr, ctypes.POINTER(ctypes.c_ubyte * size))[0]
+
+ def init_agx(self):
+ from m1n1.setup import p, u, iface
+
+ p.pmgr_adt_clocks_enable("/arm-io/gfx-asc")
+ p.pmgr_adt_clocks_enable("/arm-io/sgx")
+
+ self.agx = agx = AGX(u)
+
+ mon = RegMonitor(u, ascii=True, bufsize=0x8000000)
+ agx.mon = mon
+
+ sgx = agx.sgx_dev
+ #mon.add(sgx.gpu_region_base, sgx.gpu_region_size, "contexts")
+ #mon.add(sgx.gfx_shared_region_base, sgx.gfx_shared_region_size, "gfx-shared")
+ #mon.add(sgx.gfx_handoff_base, sgx.gfx_handoff_size, "gfx-handoff")
+
+ #mon.add(agx.initdasgx.gfx_handoff_base, sgx.gfx_handoff_size, "gfx-handoff")
+
+ atexit.register(p.reboot)
+ agx.start()
+
+ def init(self):
+ if self.initialized:
+ return
+
+ self.init_agx()
+ self.ctx = GPUContext(self.agx)
+ self.ctx.bind(0x17)
+ self.renderer = GPURenderer(self.ctx, 0x40, bm_slot=10, queue=1)
+
+ self.initialized = True
+
+ @IOW(DRM_COMMAND_BASE + 0x00, drm_asahi_submit_t)
+ def submit(self, fd, args):
+ sys.stdout.write(".")
+ sys.stdout.flush()
+
+ size = drm_asahi_cmdbuf_t.sizeof()
+ cmdbuf = drm_asahi_cmdbuf_t.parse(self.read_buf(args.cmdbuf, size))
+
+ self.log("Pushing objects...")
+ for obj in self.bos.values():
+ #if obj._skipped_pushes > 64:# and obj._addr > 0x1200000000 and obj._size > 131072:
+ #continue
+ obj.push(True)
+ self.log("Push done")
+
+ attachment_objs = []
+ for i in cmdbuf.attachments:
+ for obj in self.bos.values():
+ if obj._addr == i.pointer:
+ attachment_objs.append(obj)
+
+ if self.dump_frames:
+ name = f"shim_frame{self.frame:03d}.agx"
+ f = GPUFrame(self.renderer.ctx)
+ f.cmdbuf = cmdbuf
+ for obj in self.bos.values():
+ f.add_object(obj)
+ f.save(name)
+
+ self.renderer.submit(cmdbuf)
+ self.renderer.run()
+ self.renderer.wait()
+
+ if self.pull_buffers:
+ self.log("Pulling buffers...")
+ for obj in attachment_objs:
+ obj.pull()
+ obj._map[:] = obj.val
+ obj.val = obj._map
+ self.log("Pull done")
+
+ #print("HEAP STATS")
+ #self.ctx.uobj.va.check()
+ #self.ctx.gobj.va.check()
+ #self.ctx.pobj.va.check()
+ #self.agx.kobj.va.check()
+ #self.agx.cmdbuf.va.check()
+ #self.agx.kshared.va.check()
+ #self.agx.kshared2.va.check()
+
+ self.frame += 1
+ return 0
+
+ @IOW(DRM_COMMAND_BASE + 0x01, drm_asahi_wait_bo_t)
+ def wait_bo(self, fd, args):
+ self.log("Wait BO!", args)
+ return 0
+
+ @IOWR(DRM_COMMAND_BASE + 0x02, drm_asahi_create_bo_t)
+ def create_bo(self, fd, args):
+ memfd_offset = args.offset
+
+ if args.flags & ASAHI_BO_PIPELINE:
+ alloc = self.renderer.ctx.pobj
+ else:
+ alloc = self.renderer.ctx.gobj
+
+ obj = alloc.new(args.size, name=f"GBM offset {memfd_offset:#x}", track=False)
+ obj._memfd_offset = memfd_offset
+ obj._pushed = False
+ obj.val = obj._map = mmap.mmap(self.memfd, args.size, offset=memfd_offset)
+ self.bos[memfd_offset] = obj
+ args.offset = obj._addr
+
+ if args.flags & ASAHI_BO_PIPELINE:
+ args.offset -= self.renderer.ctx.pipeline_base
+
+ self.log(f"Create BO @ {memfd_offset:#x}")
+ return 0
+
+ @IOWR(DRM_COMMAND_BASE + 0x04, drm_asahi_get_param_t)
+ def get_param(self, fd, args):
+ self.log("Get Param!", args)
+ return 0
+
+ @IOWR(DRM_COMMAND_BASE + 0x05, drm_asahi_get_bo_offset_t)
+ def get_bo_offset(self, fd, args):
+ self.log("Get BO Offset!", args)
+ return 0
+
+ def bo_free(self, memfd_offset):
+ self.log(f"Free BO @ {memfd_offset:#x}")
+ self.bos[memfd_offset].free()
+ del self.bos[memfd_offset]
+ sys.stdout.flush()
+
+ def ioctl(self, fd, request, p_arg):
+ self.init()
+
+ p_arg = ctypes.c_void_p(p_arg)
+
+ if request not in self.ioctl_map:
+ self.log(f"Unknown ioctl: fd={fd} request={IOCTL(request)} arg={p_arg:#x}")
+ return -errno.ENOSYS
+
+ ioctl, f = self.ioctl_map[request]
+
+ size = ioctl.SIZE
+ if ioctl.DIR & _IOC_WRITE:
+ args = f._arg_cls.parse(self.read_buf(p_arg, size))
+ ret = f(fd, args)
+ elif ioctl.DIR & _IOC_READ:
+ args = f._arg_cls.parse(bytes(size))
+ ret = f(fd, args)
+ else:
+ ret = f(fd)
+
+ if ioctl.DIR & _IOC_READ:
+ data = args.build()
+ assert len(data) == size
+ ctypes.memmove(p_arg, data, size)
+
+ sys.stdout.flush()
+ return ret
+
+ def log(self, s):
+ if self.agx is None:
+ print("[Shim] " + s)
+ else:
+ self.agx.log("[Shim] " + s)
+
+Shim = DRMAsahiShim
diff --git a/tools/proxyclient/m1n1/agx/uapi.py b/tools/proxyclient/m1n1/agx/uapi.py
new file mode 100644
index 0000000..75850cb
--- /dev/null
+++ b/tools/proxyclient/m1n1/agx/uapi.py
@@ -0,0 +1,116 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+
+from construct import *
+from m1n1.constructutils import ConstructClass
+
+__all__ = []
+
+DRM_COMMAND_BASE = 0x40
+
+ASAHI_BO_PIPELINE = 1
+
+class drm_asahi_submit_t(ConstructClass):
+ subcon = Struct(
+ "cmdbuf" / Int64ul,
+ "in_syncs" / Int64ul,
+ "in_sync_count" / Int32ul,
+ "out_sync" / Int32ul,
+ )
+
+class drm_asahi_wait_bo_t(ConstructClass):
+ subcon = Struct(
+ "handle" / Int32ul,
+ Padding(4),
+ "timeout_ns" / Int64sl,
+ )
+
+class drm_asahi_create_bo_t(ConstructClass):
+ subcon = Struct(
+ "size" / Int32ul,
+ "flags" / Int32ul,
+ "handle" / Int32ul,
+ Padding(4),
+ "offset" / Int64ul,
+ )
+
+#class drm_asahi_mmap_bo_t(ConstructClass):
+ #subcon = Struct(
+ #"handle" / Int32ul,
+ #"flags" / Int32ul,
+ #"offset" / Int64ul,
+ #)
+
+class drm_asahi_get_param_t(ConstructClass):
+ subcon = Struct(
+ "param" / Int32ul,
+ Padding(4),
+ "value" / Int64ul,
+ )
+
+class drm_asahi_get_bo_offset_t(ConstructClass):
+ subcon = Struct(
+ "handle" / Int32ul,
+ Padding(4),
+ "offset" / Int64ul,
+ )
+
+ASAHI_MAX_ATTACHMENTS = 16
+
+ASAHI_ATTACHMENT_C = 0
+ASAHI_ATTACHMENT_Z = 1
+ASAHI_ATTACHMENT_S = 2
+
+class drm_asahi_attachment_t(ConstructClass):
+ subcon = Struct(
+ "type" / Int32ul,
+ "size" / Int32ul,
+ "pointer" / Int64ul,
+ )
+
+ASAHI_CMDBUF_LOAD_C = (1 << 0)
+ASAHI_CMDBUF_LOAD_Z = (1 << 1)
+ASAHI_CMDBUF_LOAD_S = (1 << 2)
+
+class drm_asahi_cmdbuf_t(ConstructClass):
+ subcon = Struct(
+ "flags" / Int64ul,
+
+ "encoder_ptr" / Int64ul,
+ "encoder_id" / Int32ul,
+
+ "cmd_ta_id" / Int32ul,
+ "cmd_3d_id" / Int32ul,
+
+ "ds_flags" / Int32ul,
+ "depth_buffer" / Int64ul,
+ "stencil_buffer" / Int64ul,
+
+ "scissor_array" / Int64ul,
+ "depth_bias_array" / Int64ul,
+
+ "fb_width" / Int32ul,
+ "fb_height" / Int32ul,
+
+ "load_pipeline" / Int32ul,
+ "load_pipeline_bind" / Int32ul,
+
+ "store_pipeline" / Int32ul,
+ "store_pipeline_bind" / Int32ul,
+
+ "partial_reload_pipeline" / Int32ul,
+ "partial_reload_pipeline_bind" / Int32ul,
+
+ "partial_store_pipeline" / Int32ul,
+ "partial_store_pipeline_bind" / Int32ul,
+
+ "depth_clear_value" / Float32l,
+ "stencil_clear_value" / Int8ul,
+ Padding(3),
+
+ "attachments" / Array(ASAHI_MAX_ATTACHMENTS, drm_asahi_attachment_t),
+ "attachment_count" / Int32ul,
+ )
+
+__all__.extend(k for k, v in globals().items()
+ if ((callable(v) or isinstance(v, type)) and v.__module__ == __name__) or isinstance(v, int))
diff --git a/tools/proxyclient/m1n1/asm.py b/tools/proxyclient/m1n1/asm.py
new file mode 100644
index 0000000..ef1f3af
--- /dev/null
+++ b/tools/proxyclient/m1n1/asm.py
@@ -0,0 +1,133 @@
+# SPDX-License-Identifier: MIT
+import os, tempfile, shutil, subprocess
+
+__all__ = ["AsmException", "ARMAsm"]
+
+uname = os.uname()
+
+if uname.sysname == "Darwin":
+ DEFAULT_ARCH = "aarch64-linux-gnu-"
+ if uname.machine == "arm64":
+ TOOLCHAIN = "/opt/homebrew/opt/llvm/bin/"
+ else:
+ TOOLCHAIN = "/usr/local/opt/llvm/bin/"
+ USE_CLANG = "1"
+else:
+ if uname.machine == "aarch64":
+ DEFAULT_ARCH = ""
+ else:
+ DEFAULT_ARCH = "aarch64-linux-gnu-"
+ USE_CLANG = "0"
+ TOOLCHAIN = ""
+
+use_clang = os.environ.get("USE_CLANG", USE_CLANG).strip() == "1"
+toolchain = os.environ.get("TOOLCHAIN", TOOLCHAIN)
+
+if use_clang:
+ CC = toolchain + "clang --target=%ARCH"
+ LD = toolchain + "ld.lld"
+ OBJCOPY = toolchain + "llvm-objcopy"
+ OBJDUMP = toolchain + "llvm-objdump"
+ NM = toolchain + "llvm-nm"
+else:
+ CC = toolchain + "%ARCHgcc"
+ LD = toolchain + "%ARCHld"
+ OBJCOPY = toolchain + "%ARCHobjcopy"
+ OBJDUMP = toolchain + "%ARCHobjdump"
+ NM = toolchain + "%ARCHnm"
+
+class AsmException(Exception):
+ pass
+
+class BaseAsm(object):
+ def __init__(self, source, addr = 0):
+ self.source = source
+ self._tmp = tempfile.mkdtemp() + os.sep
+ self.addr = addr
+ self.compile(source)
+
+ def _call(self, program, args):
+ subprocess.check_call(program.replace("%ARCH", self.ARCH) + " " + args, shell=True)
+
+ def _get(self, program, args):
+ return subprocess.check_output(program.replace("%ARCH", self.ARCH) + " " + args, shell=True).decode("ascii")
+
+ def compile(self, source):
+ self.sfile = self._tmp + "b.S"
+ with open(self.sfile, "w") as fd:
+ fd.write(self.HEADER + "\n")
+ fd.write(source + "\n")
+ fd.write(self.FOOTER + "\n")
+
+ self.ofile = self._tmp + "b.o"
+ self.elffile = self._tmp + "b.elf"
+ self.bfile = self._tmp + "b.b"
+ self.nfile = self._tmp + "b.n"
+
+ self._call(CC, f"{self.CFLAGS} -c -o {self.ofile} {self.sfile}")
+ self._call(LD, f"{self.LDFLAGS} --Ttext={self.addr:#x} -o {self.elffile} {self.ofile}")
+ self._call(OBJCOPY, f"-j.text -O binary {self.elffile} {self.bfile}")
+ self._call(NM, f"{self.elffile} > {self.nfile}")
+
+ with open(self.bfile, "rb") as fd:
+ self.data = fd.read()
+
+ with open(self.nfile) as fd:
+ for line in fd:
+ line = line.replace("\n", "")
+ addr, type, name = line.split()
+ addr = int(addr, 16)
+ setattr(self, name, addr)
+ self.start = self._start
+ self.len = len(self.data)
+ self.end = self.start + self.len
+
+ def objdump(self):
+ self._call(OBJDUMP, f"-rd {self.elffile}")
+
+ def disassemble(self):
+ output = self._get(OBJDUMP, f"-zd {self.elffile}")
+
+ for line in output.split("\n"):
+ if not line or line.startswith("/"):
+ continue
+ sl = line.split()
+ if not sl or sl[0][-1] != ":":
+ continue
+ yield line
+
+ def __del__(self):
+ if self._tmp:
+ shutil.rmtree(self._tmp)
+ self._tmp = None
+
+class ARMAsm(BaseAsm):
+ ARCH = os.path.join(os.environ.get("ARCH", DEFAULT_ARCH))
+ CFLAGS = "-pipe -Wall -march=armv8.4-a"
+ LDFLAGS = "-maarch64elf"
+ HEADER = """
+ .text
+ .globl _start
+_start:
+ """
+ FOOTER = """
+ .pool
+ """
+
+if __name__ == "__main__":
+ import sys
+ code = """
+ ldr x0, =0xDEADBEEF
+ b test
+ mrs x0, spsel
+ svc 1
+ %s
+test:
+ b test
+ ret
+""" % (" ".join(sys.argv[1:]))
+ c = ARMAsm(code, 0x1238)
+ c.objdump()
+ assert c.start == 0x1238
+ if not sys.argv[1:]:
+ assert c.test == 0x1248
diff --git a/tools/proxyclient/m1n1/constructutils.py b/tools/proxyclient/m1n1/constructutils.py
new file mode 100644
index 0000000..ff0c5b7
--- /dev/null
+++ b/tools/proxyclient/m1n1/constructutils.py
@@ -0,0 +1,879 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import inspect, textwrap, json, re, sys, os
+
+from construct import *
+from construct.core import evaluate
+from construct.lib import HexDisplayedInteger
+from .utils import *
+
+g_struct_trace = set()
+g_struct_addrmap = {}
+g_depth = 0
+
+def ZPadding(size):
+ return Const(bytes(size), Bytes(size))
+
+def recusive_reload(obj, token=None):
+ global g_depth
+
+ if token is None:
+ g_depth = 0
+ token = object()
+
+ cur_token = getattr(obj, "_token", None)
+ if cur_token is token:
+ return
+
+ g_depth += 1
+ #print(" " * g_depth + f"> {obj}", id(obj), id(token))
+ if isinstance(obj, Construct) and hasattr(obj, 'subcon'):
+ # Single subcon types
+ if inspect.isclass(obj.subcon):
+ #print("> isclass")
+ if hasattr(obj.subcon, "_reloadcls"):
+ #print("> Recursive (subcon)")
+ obj.subcon = obj.subcon._reloadcls(token=token)
+ else:
+ if isinstance(obj.subcon, Construct):
+ recusive_reload(obj.subcon, token)
+ if isinstance(obj, Construct) and hasattr(obj, 'subcons'):
+ # Construct types that have lists
+ new_subcons = []
+ for i, item in enumerate(obj.subcons):
+ if inspect.isclass(item):
+ if hasattr(item, "_reloadcls"):
+ #print("> Recursive (subcons)")
+ item = item._reloadcls()
+ else:
+ if isinstance(item, Construct):
+ recusive_reload(item, token)
+ new_subcons.append(item)
+ obj.subcons = new_subcons
+
+ if isinstance(obj, Construct) and hasattr(obj, 'cases'):
+ # Construct types that have lists
+ for i, item in list(obj.cases.items()):
+ if inspect.isclass(item):
+ if hasattr(item, "_reloadcls"):
+ #print("> Recursive (cases)")
+ obj.cases[i] = item._reloadcls(token=token)
+ else:
+ if isinstance(item, Construct):
+ recusive_reload(item, token)
+
+ for field in dir(obj):
+ value = getattr(obj, field)
+ if inspect.isclass(value):
+ if hasattr(value, "_reloadcls"):
+ #print("> Recursive (value)")
+ setattr(obj, field, value._reloadcls(token=token))
+ else:
+ if isinstance(value, Construct):
+ recusive_reload(value, token)
+
+ obj._token = token
+
+ g_depth -= 1
+
+def str_value(value, repr=False):
+ if isinstance(value, bytes) and value == bytes(len(value)):
+ return f"bytes({len(value):#x})"
+ if isinstance(value, bytes) and repr:
+ return f"bytes.fromhex('{value.hex()}')"
+ if isinstance(value, DecDisplayedInteger):
+ return str(value)
+ if isinstance(value, int):
+ if value in g_struct_addrmap:
+ desc = g_struct_addrmap[value]
+ return f"{value:#x} ({desc})"
+ else:
+ return f"{value:#x}"
+ if isinstance(value, ListContainer):
+ om = ""
+ while len(value) > 1 and not value[-1]:
+ value = value[:-1]
+ om = " ..."
+ if len(value) <= 16:
+ return "[" + ", ".join(map(str_value, value)) + f"{om}]"
+ else:
+ sv = ["[\n"]
+ for off in range(0, len(value), 16):
+ sv.append(" " + ", ".join(map(str_value, value[off:off+16])) + ",\n")
+ sv.append(f"{om}]\n")
+ return "".join(sv)
+
+ return str(value)
+
+class DecDisplayedInteger(int):
+ @staticmethod
+ def new(intvalue):
+ obj = DecDisplayedInteger(intvalue)
+ return obj
+
+class Dec(Adapter):
+ def _decode(self, obj, context, path):
+ try:
+ if isinstance(obj, int):
+ return DecDisplayedInteger.new(obj)
+ return obj
+ except Exception as e:
+ print(e)
+ raise
+
+ def _encode(self, obj, context, path):
+ return obj
+
+ def _emitparse(self, code):
+ return self.subcon._compileparse(code)
+
+ def _emitseq(self, ksy, bitwise):
+ return self.subcon._compileseq(ksy, bitwise)
+
+ def _emitprimitivetype(self, ksy, bitwise):
+ return self.subcon._compileprimitivetype(ksy, bitwise)
+
+ def _emitfulltype(self, ksy, bitwise):
+ return self.subcon._compilefulltype(ksy, bitwise)
+
+class ConstructClassException(Exception):
+ pass
+
+
+# We need to inherrit Construct as a metaclass so things like If and Select will work
+class ReloadableConstructMeta(ReloadableMeta, Construct):
+
+ def __new__(cls, name, bases, attrs):
+ cls = super().__new__(cls, name, bases, attrs)
+ cls.name = name
+ if cls.SHORT_NAME is not None:
+ cls.short_name = cls.SHORT_NAME
+ else:
+ cls.short_name = re.sub('[a-z]', '', cls.name)
+ if len(cls.short_name) > 5:
+ cls.short_name = cls.short_name[:3] + cls.short_name[-2:]
+
+ try:
+ cls.flagbuildnone = cls.subcon.flagbuildnone
+ except AttributeError:
+ cls.flagbuildnone = False
+
+ cls.docs = None
+
+ cls._off = {}
+ if "subcon" not in attrs:
+ return cls
+
+ subcon = attrs["subcon"]
+ if isinstance(subcon, Struct):
+ off = 0
+ for subcon in subcon.subcons:
+ try:
+ sizeof = subcon.sizeof()
+ except:
+ sizeof = None
+ if isinstance(subcon, Ver):
+ if not subcon._active():
+ cls._off[subcon.name] = -1, 0
+ continue
+ subcon = subcon.subcon
+ if isinstance(subcon, Renamed):
+ name = subcon.name
+ subcon = subcon.subcon
+ cls._off[name] = off, sizeof
+ if sizeof is None:
+ break
+ off += sizeof
+ return cls
+
+class ConstructClassBase(Reloadable, metaclass=ReloadableConstructMeta):
+ """ Offers two benifits over regular construct
+
+ 1. It's reloadable, and can recusrivly reload other refrenced ConstructClasses
+ 2. It's a class, so you can define methods
+
+ Currently only supports parsing, but could be extended to support building
+
+ Example:
+ Instead of:
+ MyStruct = Struct(
+ "field1" / Int32ul
+ )
+
+ class MyClass(ConstructClass):
+ subcon = Struct(
+ "field1" / Int32ul
+ )
+
+ """
+ SHORT_NAME = None
+
+ parsed = None
+
+ def __init__(self):
+ self._pointers = set()
+ self._addr = None
+ self._meta = {}
+
+ def regmap(self):
+ return ConstructRegMap(type(self), self._stream.to_accessor(), self._addr)
+
+ @classmethod
+ def sizeof(cls, **contextkw):
+ context = Container(**contextkw)
+ context._parsing = False
+ context._building = False
+ context._sizing = True
+ context._params = context
+ return cls._sizeof(context, "(sizeof)")
+
+ def Apply(self, dict=None, **kwargs):
+ if dict is None:
+ dict = kwargs
+
+ for key in dict:
+ if not key.startswith('_'):
+ setattr(self, key, dict[key])
+ self._keys += [key]
+
+ def set_addr(self, addr=None, stream=None):
+ #print("set_addr", type(self), addr)
+ if addr is not None:
+ self._addr = addr
+ self._set_meta(self, stream)
+
+ @classmethod
+ def _build(cls, obj, stream, context, path):
+ cls._build_prepare(obj)
+
+ addr = stream.tell()
+ try:
+ new_obj = cls.subcon._build(obj, stream, context, f"{path} -> {cls.name}")
+ except ConstructClassException:
+ raise
+ except ConstructError:
+ raise
+ except Exception as e:
+ raise ConstructClassException(f"at {path} -> {cls.name}") from e
+
+ # if obj is a raw value or Container, instance a proper object for it
+ if not isinstance(obj, ConstructClassBase):
+ obj = cls.__new__(cls)
+
+ # update the object with anything that build updated (such as defaults)
+ obj._apply(new_obj)
+
+ obj._addr = addr
+ cls._set_meta(obj, stream)
+ return obj
+
+ @classmethod
+ def _sizeof(cls, context, path):
+ return cls.subcon._sizeof(context, f"{path} -> {cls.name}")
+
+ @classmethod
+ def _reloadcls(cls, force=False, token=None):
+ #print(f"_reloadcls({cls})", id(cls))
+ newcls = Reloadable._reloadcls.__func__(cls, force)
+ if hasattr(newcls, "subcon"):
+ recusive_reload(newcls.subcon, token)
+ return newcls
+
+ def _apply(self, obj):
+ raise NotImplementedError()
+
+ @classmethod
+ def _set_meta(cls, self, stream=None):
+ if stream is not None:
+ self._pointers = set()
+ self._meta = {}
+ self._stream = stream
+
+ if isinstance(cls.subcon, Struct):
+ subaddr = int(self._addr)
+ for subcon in cls.subcon.subcons:
+ try:
+ sizeof = subcon.sizeof()
+ except:
+ break
+ if isinstance(subcon, Ver):
+ subcon = subcon.subcon
+ if isinstance(subcon, Renamed):
+ name = subcon.name
+ #print(name, subcon)
+ subcon = subcon.subcon
+ if stream is not None and getattr(stream, "meta_fn", None):
+ meta = stream.meta_fn(subaddr, sizeof)
+ if meta is not None:
+ self._meta[name] = meta
+ if isinstance(subcon, Pointer):
+ self._pointers.add(name)
+ continue
+ try:
+ #print(name, subcon)
+ val = self[name]
+ except:
+ pass
+ else:
+ if isinstance(val, ConstructClassBase):
+ val.set_addr(subaddr)
+ if isinstance(val, list):
+ for i in val:
+ if isinstance(i, ConstructClassBase):
+ i.set_addr(subaddr)
+ subaddr += i.sizeof()
+
+ subaddr += sizeof
+
+ @classmethod
+ def _parse(cls, stream, context, path):
+ #print(f"parse {cls} @ {stream.tell():#x} {path}")
+ addr = stream.tell()
+ obj = cls.subcon._parse(stream, context, path)
+ size = stream.tell() - addr
+
+ # Don't instance Selects
+ if isinstance(cls.subcon, Select):
+ return obj
+
+ # Skip calling the __init__ constructor, so that it can be used for building
+ # Use parsed instead, if you need a post-parsing constructor
+ self = cls.__new__(cls)
+ self._addr = addr
+ self._path = path
+ self._meta = {}
+ cls._set_meta(self, stream)
+
+ self._apply(obj)
+
+ if self._addr > 0x10000:
+ g_struct_trace.add((self._addr, f"{cls.name} (end: {self._addr + size:#x})"))
+ g_struct_addrmap[self._addr] = f"{cls.name}"
+ return self
+
+ @classmethod
+ def _build_prepare(cls, obj):
+ pass
+
+ def build_stream(self, obj=None, stream=None, **contextkw):
+ assert stream != None
+ if obj is None:
+ obj = self
+
+ return Construct.build_stream(self, obj, stream, **contextkw)
+
+ def build(self, obj=None, **contextkw):
+ if obj is None:
+ obj = self
+
+ return Construct.build(self, obj, **contextkw)
+
+class ROPointer(Pointer):
+ def _build(self, obj, stream, context, path):
+ return obj
+
+ def _parse(self, stream, context, path):
+ recurse = getattr(stream, "recurse", False)
+ if not recurse:
+ return None
+
+ return Pointer._parse(self, stream, context, path)
+
+class ConstructClass(ConstructClassBase, Container):
+ """ Offers two benifits over regular construct
+
+ 1. It's reloadable, and can recusrivly reload other refrenced ConstructClasses
+ 2. It's a class, so you can define methods
+
+ Currently only supports parsing, but could be extended to support building
+
+ Example:
+ Instead of:
+ MyStruct = Struct(
+ "field1" / Int32ul
+ )
+
+ class MyClass(ConstructClass):
+ subcon = Struct(
+ "field1" / Int32ul
+ )
+ """
+
+ def diff(self, other, show_all=False):
+ return self.__str__(other=other, show_all=show_all)
+
+ def __eq__(self, other):
+ return all(self[k] == other[k] for k in self
+ if (not k.startswith("_"))
+ and (k not in self._pointers)
+ and not callable(self[k]))
+
+ def __str__(self, ignore=[], other=None, show_all=False) -> str:
+
+ str = self.__class__.__name__
+ if self._addr is not None:
+ str += f" @ 0x{self._addr:x}:"
+
+ str += "\n"
+
+ keys = list(self)
+ keys.sort(key = lambda x: self._off.get(x, (-1, 0))[0])
+
+ for key in keys:
+ if key in self._off:
+ offv, sizeof = self._off[key]
+ if offv == -1:
+ print(key, offv, sizeof)
+ continue
+ if key in ignore or key.startswith('_'):
+ continue
+ value = getattr(self, key)
+ need_diff = False
+ if other is not None:
+ if key in self._pointers or callable(value):
+ continue
+ other_value = getattr(other, key)
+ if not show_all and other_value == value:
+ continue
+ offv, sizeof = self._off[key]
+ if sizeof == 0:
+ continue
+ def _valdiff(value, other_value):
+ if hasattr(value, "diff"):
+ return value.diff(other_value)
+ elif isinstance(value, bytes) and isinstance(other_value, bytes):
+ pad = bytes()
+ if len(value) & 3:
+ pad = bytes(4 - (len(value) & 3))
+ return chexdiff32(other_value+pad, value+pad, offset=offv, offset2=0)
+ else:
+ val_repr = str_value(value)
+ if other_value != value:
+ other_repr = str_value(other_value)
+ return f"\x1b[33;1;4m{val_repr}\x1b[m ← \x1b[34m{other_repr}\x1b[m"
+ return val_repr
+
+ if isinstance(value, list):
+ val_repr = "{\n"
+ for i, (a, b) in enumerate(zip(value, other_value)):
+ if a == b:
+ continue
+ val_repr += f"[{i}] = " + textwrap.indent(_valdiff(a, b), " ") + "\n"
+ offv += sizeof // len(value)
+ val_repr += "}\n"
+ else:
+ val_repr = _valdiff(value, other_value)
+
+ else:
+ val_repr = str_value(value)
+ off = ""
+ meta = ""
+ if key in self._off:
+ offv, sizeof = self._off[key]
+ if sizeof is not None:
+ sizeofs = f"{sizeof:3x}"
+ else:
+ sizeofs = " *"
+ off = f"\x1b[32m[{offv:3x}.{sizeofs}]\x1b[m "
+ if key in self._meta:
+ meta = f" \x1b[34m{self._meta[key]}\x1b[m"
+ if '\n' in val_repr:
+ val_repr = textwrap.indent(val_repr, f'\x1b[90m{self.short_name:>5s}.\x1b[m')
+ if not val_repr.endswith('\n'):
+ val_repr += '\n'
+ str += f"\x1b[90m{self.short_name:>5s}.{off}\x1b[95m{key}\x1b[m ={meta}\n{val_repr}"
+ else:
+ str += f"\x1b[90m{self.short_name:>5s}.{off}\x1b[95m{key}\x1b[m = {val_repr}{meta}\n"
+
+ return str
+
+ def _dump(self):
+ print(f"# {self.__class__.__name__}")
+ if self._addr is not None:
+ print(f"# Address: 0x{self._addr:x}")
+
+ keys = list(self)
+ keys.sort(key = lambda x: self._off.get(x, (-1, 0))[0])
+ for key in keys:
+ if key.startswith('_'):
+ continue
+ value = getattr(self, key)
+ val_repr = str_value(value, repr=True)
+ print(f"self.{key} = {val_repr}")
+
+
+ @classmethod
+ def _build_prepare(cls, obj):
+ if isinstance(cls.subcon, Struct):
+ for subcon in cls.subcon.subcons:
+ if isinstance(subcon, Ver):
+ subcon = subcon.subcon
+ if not isinstance(subcon, Renamed):
+ continue
+ name = subcon.name
+ subcon = subcon.subcon
+ if isinstance(subcon, Lazy):
+ subcon = subcon.subcon
+ if not isinstance(subcon, Pointer):
+ continue
+ addr_field = subcon.offset.__getfield__()
+ # Ugh.
+ parent = subcon.offset._Path__parent(obj)
+ if not hasattr(obj, name) and hasattr(parent, addr_field):
+ # No need for building
+ setattr(obj, name, None)
+ elif hasattr(obj, name):
+ subobj = getattr(obj, name)
+ try:
+ addr = subobj._addr
+ except (AttributeError, KeyError):
+ addr = None
+ if addr is not None:
+ setattr(parent, addr_field, addr)
+
+ @classmethod
+ def _parse(cls, stream, context, path):
+ self = ConstructClassBase._parse.__func__(cls, stream, context, path)
+
+ for key in self:
+ if key.startswith('_'):
+ continue
+ try:
+ val = int(self[key])
+ except:
+ continue
+ if (0x1000000000 <= val <= 0x1f00000000 or
+ 0xf8000000000 <= val <= 0xff000000000 or
+ 0xffffff8000000000 <= val <= 0xfffffff000000000):
+ g_struct_trace.add((val, f"{cls.name}.{key}"))
+ return self
+
+ def _apply_classful(self, obj):
+ obj2 = dict(obj)
+ if isinstance(self.__class__.subcon, Struct):
+ for subcon in self.__class__.subcon.subcons:
+ name = subcon.name
+ if name is None:
+ continue
+ subcon = subcon.subcon
+ if isinstance(subcon, Lazy):
+ continue
+ if isinstance(subcon, Pointer):
+ subcon = subcon.subcon
+ if isinstance(subcon, Ver):
+ subcon = subcon.subcon
+ if isinstance(subcon, Array):
+ subcon = subcon.subcon
+
+ if name not in obj2:
+ continue
+
+ val = obj2[name]
+ if not isinstance(subcon, type) or not issubclass(subcon, ConstructClassBase):
+ continue
+
+ def _map(v):
+ if not isinstance(v, subcon):
+ sc = subcon()
+ sc._apply(v)
+ return sc
+ return v
+
+ if isinstance(val, list):
+ obj2[name] = list(map(_map, val))
+ else:
+ obj2[name] = _map(val)
+
+ self._apply(obj2)
+
+ def _apply(self, obj):
+ self.update(obj)
+
+ def items(self):
+ for k in list(self):
+ if k.startswith("_"):
+ continue
+ yield k, self[k]
+
+ def addrof(self, name):
+ return self._addr + self._off[name][0]
+
+ @classmethod
+ def offsetof(cls, name):
+ return cls._off[name][0]
+
+ def clone(self):
+ obj = type(self)()
+ obj.update(self)
+ return obj
+
+ @classmethod
+ def from_json(cls, fd):
+ d = json.load(fd)
+ obj = cls()
+ obj._apply_classful(d)
+ return obj
+
+ @classmethod
+ def is_versioned(cls):
+ for subcon in cls.subcon.subcons:
+ if isinstance(subcon, Ver):
+ return True
+ while True:
+ try:
+ subcon = subcon.subcon
+ if isinstance(subcon, type) and issubclass(subcon, ConstructClass) and subcon.is_versioned():
+ return True
+ except:
+ break
+ return False
+
+ @classmethod
+ def to_rust(cls):
+ assert isinstance(cls.subcon, Struct)
+ s = []
+ if cls.is_versioned():
+ s.append("#[versions(AGX)]"),
+
+ s += [
+ "#[derive(Debug, Clone, Copy)]",
+ "#[repr(C, packed(4))]",
+ f"struct {cls.__name__} {{",
+ ]
+ pad = 0
+ has_ver = False
+ for subcon in cls.subcon.subcons:
+ if isinstance(subcon, Ver):
+ if not has_ver:
+ s.append("")
+ s.append(f" #[ver({subcon.cond})]")
+ subcon = subcon.subcon
+ has_ver = True
+ else:
+ has_ver = False
+
+ name = subcon.name
+ if name is None:
+ name = f"__pad{pad}"
+ pad += 1
+
+ array_len = []
+ skip = False
+ while subcon:
+ if isinstance(subcon, Lazy):
+ skip = True
+ break
+ elif isinstance(subcon, Pointer):
+ skip = True
+ break
+ elif isinstance(subcon, Array):
+ array_len.append(subcon.count)
+ elif isinstance(subcon, (HexDump, Default, Renamed, Dec, Hex, Const)):
+ pass
+ else:
+ break
+ subcon = subcon.subcon
+
+ if isinstance(subcon, Bytes):
+ array_len.append(subcon.length)
+ subcon = Int8ul
+
+ if skip:
+ #s.append(f" // {name}: {subcon}")
+ continue
+
+ TYPE_MAP = {
+ Int64ul: "u64",
+ Int32ul: "u32",
+ Int16ul: "u16",
+ Int8ul: "u8",
+ Int64sl: "i64",
+ Int32sl: "i32",
+ Int16sl: "i16",
+ Int8sl: "i8",
+ Float32l: "f32",
+ Float64l: "f64",
+ }
+
+ t = TYPE_MAP.get(subcon, repr(subcon))
+
+ if isinstance(subcon, type) and issubclass(subcon, ConstructClass):
+ t = subcon.__name__
+ if subcon.is_versioned():
+ t += "::ver"
+
+ for n in array_len[::-1]:
+ t = f"[{t}; {n:#x}]"
+
+ s.append(f" {name}: {t},")
+
+ if has_ver:
+ s.append("")
+
+ s += ["}"]
+ return "\n".join(s)
+
+class ConstructValueClass(ConstructClassBase):
+ """ Same as Construct, but for subcons that are single values, rather than containers
+
+ the value is stored as .value
+ """
+
+ def __eq__(self, other):
+ return self.value == other.value
+
+ def __str__(self) -> str:
+ str = f"{self.__class__.__name__} @ 0x{self._addr:x}:"
+ str += f"\t{str_value(self.value)}"
+ return str
+
+ def __getitem__(self, i):
+ if i == "value":
+ return self.value
+ raise Exception(f"Invalid index {i}")
+
+ @classmethod
+ def _build(cls, obj, stream, context, path):
+ return super()._build(obj.value, stream, context, path)
+
+ def _apply(self, obj):
+ self.value = obj
+ _apply_classful = _apply
+
+class ConstructRegMap(BaseRegMap):
+ TYPE_MAP = {
+ Int8ul: Register8,
+ Int16ul: Register16,
+ Int32ul: Register32,
+ Int64ul: Register64,
+ }
+
+ def __init__(self, cls, backend, base):
+ self._addrmap = {}
+ self._rngmap = SetRangeMap()
+ self._namemap = {}
+ assert isinstance(cls.subcon, Struct)
+ for subcon in cls.subcon.subcons:
+ if isinstance(subcon, Ver):
+ subcon = subcon.subcon
+ if not isinstance(subcon, Renamed):
+ continue
+ name = subcon.name
+ subcon = subcon.subcon
+ if subcon not in self.TYPE_MAP:
+ continue
+ rtype = self.TYPE_MAP[subcon]
+ if name not in cls._off:
+ continue
+ addr, size = cls._off[name]
+ self._addrmap[addr] = name, rtype
+ self._namemap[name] = addr, rtype
+ super().__init__(backend, base)
+
+ def __getattr__(self, k):
+ if k.startswith("_"):
+ return self.__dict__[k]
+ return self._accessor[k]
+
+ def __setattr__(self, k, v):
+ if k.startswith("_"):
+ self.__dict__[k] = v
+ return
+ self._accessor[k].val = v
+
+class Ver(Subconstruct):
+ # Ugly hack to make this survive across reloads...
+ try:
+ _version = sys.modules["m1n1.constructutils"].Ver._version
+ except (KeyError, AttributeError):
+ _version = {"V": os.environ.get("AGX_FWVER", "V12_3"),
+ "G": os.environ.get("AGX_GPU", "G13")}
+
+ MATRIX = {
+ "V": ["V12_1", "V12_3", "V12_4", "V13_0B4"],
+ "G": ["G13", "G14"],
+ }
+
+ def __init__(self, version, subcon):
+ self.cond = version
+ self.vcheck = self.parse_ver(version)
+ self._name = subcon.name
+ self.subcon = subcon
+ self.flagbuildnone = True
+ self.docs = ""
+ self.parsed = None
+
+ @property
+ def name(self):
+ if self._active():
+ return self._name
+ else:
+ return None
+
+ @staticmethod
+ def _split_ver(s):
+ if not s:
+ return None
+ parts = re.split(r"[-,. ]", s)
+ parts2 = []
+ for i in parts:
+ try:
+ parts2.append(int(i))
+ except ValueError:
+ parts2.append(i)
+ if len(parts2) > 3 and parts2[-2] == "beta":
+ parts2[-3] -= 1
+ parts2[-2] = 99
+ return tuple(parts2)
+
+ @classmethod
+ def parse_ver(cls, version):
+ expr = version.replace("&&", " and ").replace("||", " or ")
+
+ base_loc = {j: i for row in cls.MATRIX.values() for i, j in enumerate(row)}
+
+ def check_ver(ver):
+ loc = dict(base_loc)
+ for k, v in ver.items():
+ loc[k] = cls.MATRIX[k].index(v)
+ return eval(expr, None, loc)
+
+ return check_ver
+
+ @classmethod
+ def check(cls, version):
+ return cls.parse_ver(version)(cls._version)
+
+ def _active(self):
+ return self.vcheck(self._version)
+
+ def _parse(self, stream, context, path):
+ if not self._active():
+ return None
+ obj = self.subcon._parse(stream, context, path)
+ return obj
+
+ def _build(self, obj, stream, context, path):
+ if not self._active():
+ return None
+ return self.subcon._build(obj, stream, context, path)
+
+ def _sizeof(self, context, path):
+ if not self._active():
+ return 0
+ return self.subcon._sizeof(context, path)
+
+ @classmethod
+ def set_version_key(cls, key, version):
+ cls._version[key] = version
+
+ @classmethod
+ def set_version(cls, u):
+ cls.set_version_key("V", u.version)
+ cls.set_version_key("G", u.adt["/arm-io"].soc_generation.replace("H", "G"))
+
+def show_struct_trace(log=print):
+ for addr, desc in sorted(list(g_struct_trace)):
+ log(f"{addr:>#18x}: {desc}")
+
+__all__ = ["ConstructClass", "ConstructValueClass", "Dec", "ROPointer", "show_struct_trace", "ZPadding", "Ver"]
diff --git a/tools/proxyclient/m1n1/find_regs.py b/tools/proxyclient/m1n1/find_regs.py
new file mode 100644
index 0000000..9982661
--- /dev/null
+++ b/tools/proxyclient/m1n1/find_regs.py
@@ -0,0 +1,99 @@
+# SPDX-License-Identifier: MIT
+import struct
+
+from . import asm, sysreg
+from .proxyutils import GuardedHeap
+
+__all__ = ["dynamic_regs", "impdef_regs", "static_regs", "find_regs"]
+
+def _all():
+ for op1 in range(1 << 3):
+ for CRn in (0b1011, 0b1111):
+ for CRm in range(1 << 4):
+ for op2 in range(1 << 3):
+ yield 3, op1, CRn, CRm, op2
+
+dynamic_regs = [
+ sysreg.CNTVCT_ALIAS_EL0,
+ sysreg.CNTPCT_ALIAS_EL0,
+]
+
+impdef_regs = list(_all())
+static_regs = [i for i in _all() if i not in dynamic_regs]
+
+def find_regs(u, regs=None, block=1024, call=None, values=True):
+ if regs is None:
+ regs = impdef_regs
+
+ p = u.proxy
+ iface = u.iface
+
+ data_len = 8 * block
+
+ with GuardedHeap(u.heap) as heap:
+ data_buffer = heap.malloc(data_len)
+
+ template = asm.ARMAsm("""
+ mov x2, x0
+ mrs x2, s3_0_c0_c0_0
+ str x2, [x1], #8
+ """, 0x1000)
+
+ mov, mrs, st = struct.unpack("3I", template.data)
+
+
+ BAD = 0xacce5515abad1dea
+ OOPS = 0xdeadc0dedeadc0de
+
+ iregs = iter(regs)
+
+ while True:
+ insns = []
+ bregs = []
+ for i in iregs:
+ op0, op1, CRn, CRm, op2 = enc = sysreg.sysreg_parse(i)
+ bregs.append(enc)
+ assert op0 == 3
+
+ insns.extend((mov, mrs | (op1 << 16) | (CRn << 12) | (CRm << 8) | (op2 << 5), st))
+
+ if len(bregs) >= block:
+ break
+
+ if not bregs:
+ break
+
+ p.memset64(data_buffer, OOPS, data_len)
+ u.exec(insns, BAD, data_buffer, call=call, silent=True, ignore_exceptions=True)
+ data = iface.readmem(data_buffer, 8 * len(bregs))
+ for reg, val in zip(bregs, struct.unpack(f"<{len(bregs)}Q", data)):
+ if val == OOPS:
+ raise Exception(f"Failed to execute reg-finder code at {reg}")
+ if val != BAD:
+ if values:
+ yield reg, val
+ else:
+ yield reg
+
+if __name__ == "__main__":
+ from m1n1.setup import *
+
+ p.iodev_set_usage(IODEV.FB, 0)
+
+ for reg, val in find_regs(u):
+ print(f"{sysreg_name(reg)} ({', '.join(map(str, reg))}) = 0x{val:x}")
+
+ try:
+ u.msr(reg, val, silent=True)
+ except:
+ print(" - READONLY")
+ try:
+ u.mrs(reg, silent=True, call="el1")
+ except:
+ print(" - ### EL2 only ###")
+ try:
+ u.mrs(reg, silent=True, call="el0")
+ except:
+ pass
+ else:
+ print(" - *** EL0 accessible ***")
diff --git a/tools/proxyclient/m1n1/fw/__init__.py b/tools/proxyclient/m1n1/fw/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tools/proxyclient/m1n1/fw/__init__.py
diff --git a/tools/proxyclient/m1n1/fw/afk/__init__.py b/tools/proxyclient/m1n1/fw/afk/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tools/proxyclient/m1n1/fw/afk/__init__.py
diff --git a/tools/proxyclient/m1n1/fw/afk/epic.py b/tools/proxyclient/m1n1/fw/afk/epic.py
new file mode 100644
index 0000000..e95281d
--- /dev/null
+++ b/tools/proxyclient/m1n1/fw/afk/epic.py
@@ -0,0 +1,292 @@
+# SPDX-License-Identifier: MIT
+
+import struct
+from io import BytesIO
+from construct import *
+from ..common import *
+from ...utils import *
+from ..asc import StandardASC
+from ..asc.base import *
+from .rbep import AFKRingBufEndpoint
+
+EPICType = "EPICType" / Enum(Int32ul,
+ NOTIFY = 0,
+ COMMAND = 3,
+ REPLY = 4,
+ NOTIFY_ACK = 8,
+)
+
+EPICCategory = "EPICCategory" / Enum(Int8ul,
+ REPORT = 0x00,
+ NOTIFY = 0x10,
+ REPLY = 0x20,
+ COMMAND = 0x30,
+)
+
+EPICHeader = Struct(
+ "channel" / Int32ul,
+ "type" / EPICType,
+ "version" / Const(2, Int8ul),
+ "seq" / Int16ul,
+ "pad" / Const(0, Int8ul),
+ "unk" / Const(0, Int32ul),
+ "timestamp" / Default(Int64ul, 0),
+)
+
+EPICSubHeader = Struct(
+ "length" / Int32ul,
+ "version" / Default(Int8ul, 4),
+ "category" / EPICCategory,
+ "type" / Hex(Int16ul),
+ "timestamp" / Default(Int64ul, 0),
+ "seq" / Int16ul,
+ "unk" / Default(Hex(Int16ul), 0),
+ "inline_len" / Hex(Int32ul),
+)
+
+EPICAnnounce = Struct(
+ "name" / Padded(32, CString("utf8")),
+ "props" / Optional(OSSerialize())
+)
+
+EPICSetProp = Struct(
+ "name_len" / Int32ul,
+ "name" / Aligned(4, CString("utf8")),
+ "value" / OSSerialize()
+)
+
+EPICCmd = Struct(
+ "retcode" / Default(Hex(Int32ul), 0),
+ "rxbuf" / Hex(Int64ul),
+ "txbuf" / Hex(Int64ul),
+ "rxlen" / Hex(Int32ul),
+ "txlen" / Hex(Int32ul),
+ "rxcookie" / Optional(Default(Bool(Int8ul), False)),
+ "txcookie" / Optional(Default(Bool(Int8ul), False)),
+)
+
+
+class EPICError(Exception):
+ pass
+
+
+class EPICService:
+ RX_BUFSIZE = 0x4000
+ TX_BUFSIZE = 0x4000
+
+ def __init__(self, ep):
+ self.iface = ep.asc.iface
+ self.ep = ep
+ self.ready = False
+ self.chan = None
+ self.seq = 0
+
+ def log(self, msg):
+ print(f"[{self.ep.name}.{self.SHORT}] {msg}")
+
+ def init(self, props):
+ self.log(f"Init: {props}")
+ self.props = props
+ self.rxbuf, self.rxbuf_dva = self.ep.asc.ioalloc(self.RX_BUFSIZE)
+ self.txbuf, self.txbuf_dva = self.ep.asc.ioalloc(self.TX_BUFSIZE)
+ self.ready = True
+
+ def wait(self):
+ while not self.ready:
+ self.ep.asc.work()
+
+ def handle_report(self, category, type, seq, fd):
+ self.log(f"Report {category}/{type} #{seq}")
+ chexdump(fd.read())
+
+ def handle_notify(self, category, type, seq, fd):
+ retcode = struct.unpack("<I", fd.read(4))[0]
+ self.log(f"Notify {category}/{type} #{seq} ({retcode})")
+ data = fd.read()
+ chexdump(data)
+ print("Send ACK")
+
+ data = data[:0x50] + b"\x01\x00\x00\x00" + data[0x54:]
+
+ pkt = struct.pack("<I", 0) + data
+ self.ep.send_epic(self.chan, EPICType.NOTIFY_ACK, EPICCategory.REPLY, type, seq, pkt, len(data))
+
+ def handle_reply(self, category, type, seq, fd):
+ off = fd.tell()
+ data = fd.read()
+ if len(data) == 4:
+ retcode = struct.unpack("<I", data)[0]
+ if retcode:
+ raise EPICError(f"IOP returned errcode {retcode:#x}")
+ else:
+ self.reply = retcode
+ return
+ fd.seek(off)
+ cmd = EPICCmd.parse_stream(fd)
+ payload = fd.read()
+ self.log(f"Response {type:#x} #{seq}: {cmd.retcode:#x}")
+ if cmd.retcode != 0:
+ raise EPICError(f"IOP returned errcode {cmd.retcode:#x}")
+ if payload:
+ self.log("Inline payload:")
+ chexdump(payload)
+ assert cmd.rxbuf == self.rxbuf_dva
+ self.reply = self.iface.readmem(self.rxbuf, cmd.rxlen)
+
+ def handle_cmd(self, category, type, seq, fd):
+ cmd = EPICCmd.parse_stream(fd)
+ self.log(f"Command {type:#x} #{seq}: {cmd.retcode:#x}")
+
+ def send_cmd(self, type, data, retlen=None):
+ if retlen is None:
+ retlen = len(data)
+ cmd = Container()
+ cmd.rxbuf = self.rxbuf_dva
+ cmd.txbuf = self.txbuf_dva
+ cmd.txlen = len(data)
+ cmd.rxlen = retlen
+ self.iface.writemem(self.txbuf, data)
+ self.reply = None
+ pkt = EPICCmd.build(cmd)
+ self.ep.send_epic(self.chan, EPICType.COMMAND, EPICCategory.COMMAND, type, self.seq, pkt)
+ self.seq += 1
+ while self.reply is None:
+ self.ep.asc.work()
+ return self.reply
+
+class EPICStandardService(EPICService):
+ def call(self, group, cmd, data=b'', replen=None):
+ msg = struct.pack("<2xHIII48x", group, cmd, len(data), 0x69706378) + data
+ if replen is not None:
+ replen += 64
+ resp = self.send_cmd(0xc0, msg, replen)
+ if not resp:
+ return
+ rgroup, rcmd, rlen, rmagic = struct.unpack("<2xHIII", resp[:16])
+ assert rmagic == 0x69706378
+ assert rgroup == group
+ assert rcmd == cmd
+ return resp[64:64+rlen]
+
+ def getLocation(self, unk=0):
+ return struct.unpack("<16xI12x", self.call(4, 4, bytes(32)))
+
+ def getUnit(self, unk=0):
+ return struct.unpack("<16xI12x", self.call(4, 5, bytes(32)))
+
+ def open(self, unk=0):
+ self.call(4, 6, struct.pack("<16xI12x", unk))
+
+ def close(self):
+ self.call(4, 7, bytes(16))
+
+class AFKSystemService(EPICService):
+ NAME = "system"
+ SHORT = "system"
+
+ def getProperty(self, prop, val):
+ pass
+ #self.send_cmd(0x40, msg, 0)
+
+ def setProperty(self, prop, val):
+ msg = {
+ "name_len": (len(prop) + 3) & ~3,
+ "name": prop,
+ "value": val,
+ }
+ msg = EPICSetProp.build(msg)
+ self.send_cmd(0x43, msg, 0)
+
+class EPICEndpoint(AFKRingBufEndpoint):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.serv_map = {}
+ self.chan_map = {}
+ self.serv_names = {}
+ self.hseq = 0
+
+ for i in self.SERVICES:
+ self.serv_names[i.NAME] = i
+
+ def handle_ipc(self, data):
+ fd = BytesIO(data)
+ hdr = EPICHeader.parse_stream(fd)
+ sub = EPICSubHeader.parse_stream(fd)
+
+ if self.verbose > 2:
+ self.log(f"Ch {hdr.channel} Type {hdr.type} Ver {hdr.version} Seq {hdr.seq}")
+ self.log(f" Len {sub.length} Ver {sub.version} Cat {sub.category} Type {sub.type:#x} Seq {sub.seq}")
+
+ if sub.category == EPICCategory.REPORT:
+ self.handle_report(hdr, sub, fd)
+ if sub.category == EPICCategory.NOTIFY:
+ self.handle_notify(hdr, sub, fd)
+ elif sub.category == EPICCategory.REPLY:
+ self.handle_reply(hdr, sub, fd)
+ elif sub.category == EPICCategory.COMMAND:
+ self.handle_cmd(hdr, sub, fd)
+
+ def wait_for(self, name):
+ while True:
+ srv = getattr(self, name, None)
+ if srv is not None and srv.ready:
+ break
+ self.asc.work()
+
+ def handle_report(self, hdr, sub, fd):
+ if sub.type == 0x30:
+ init = EPICAnnounce.parse_stream(fd)
+ if init.props is None:
+ init.props = {}
+ name = init.name
+ if "EPICName" in init.props:
+ name = init.props["EPICName"]
+ key = name + str(init.props.get("EPICUnit", ""))
+ if name in self.serv_names:
+ srv = self.serv_names[name](self)
+ short = srv.SHORT + str(init.props.get("EPICUnit", ""))
+ setattr(self, short, srv)
+ srv.init(init.props)
+ srv.chan = hdr.channel
+ self.chan_map[hdr.channel] = srv
+ self.serv_map[key] = srv
+ self.log(f"New service: {key} on channel {hdr.channel} (short name: {short})")
+ else:
+ self.log(f"Unknown service {key} on channel {hdr.channel}")
+ else:
+ if hdr.channel not in self.chan_map:
+ self.log(f"Ignoring report on channel {hdr.channel}")
+ else:
+ self.chan_map[hdr.channel].handle_report(sub.category, sub.type, sub.seq, fd)
+
+ def handle_notify(self, hdr, sub, fd):
+ self.chan_map[hdr.channel].handle_notify(sub.category, sub.type, sub.seq, fd)
+
+ def handle_reply(self, hdr, sub, fd):
+ self.chan_map[hdr.channel].handle_reply(sub.category, sub.type, sub.seq, fd)
+
+ def handle_cmd(self, hdr, sub, fd):
+ self.chan_map[hdr.channel].handle_cmd(sub.category, sub.type, sub.seq, fd)
+
+ def send_epic(self, chan, ptype, category, type, seq, data, inline_len=0):
+ hdr = Container()
+ hdr.channel = chan
+ hdr.type = ptype
+ hdr.seq = self.hseq
+ self.hseq += 1
+
+ sub = Container()
+ sub.length = len(data)
+ sub.category = category
+ sub.type = type
+ sub.seq = seq
+ sub.inline_len = inline_len
+ pkt = EPICHeader.build(hdr) + EPICSubHeader.build(sub) + data
+ super().send_ipc(pkt)
+
+class AFKSystemEndpoint(EPICEndpoint):
+ SHORT = "system"
+
+ SERVICES = [
+ AFKSystemService,
+ ]
diff --git a/tools/proxyclient/m1n1/fw/afk/rbep.py b/tools/proxyclient/m1n1/fw/afk/rbep.py
new file mode 100644
index 0000000..872d75f
--- /dev/null
+++ b/tools/proxyclient/m1n1/fw/afk/rbep.py
@@ -0,0 +1,225 @@
+# SPDX-License-Identifier: MIT
+
+import struct
+
+from ..common import *
+from ...utils import *
+from ..asc.base import *
+
+
+class AFKEPMessage(Register64):
+ TYPE = 63, 48
+
+class AFKEP_GetBuf(AFKEPMessage):
+ TYPE = 63, 48, Constant(0x89)
+ SIZE = 31, 16
+ TAG = 15, 0
+
+class AFKEP_GetBuf_Ack(AFKEPMessage):
+ TYPE = 63, 48, Constant(0xa1)
+ DVA = 47, 0
+
+class AFKEP_InitRB(AFKEPMessage):
+ OFFSET = 47, 32
+ SIZE = 31, 16
+ TAG = 15, 0
+
+class AFKEP_Send(AFKEPMessage):
+ TYPE = 63, 48, Constant(0xa2)
+ WPTR = 31, 0
+
+class AFKEP_Recv(AFKEPMessage):
+ TYPE = 63, 48, Constant(0x85)
+ WPTR = 31, 0
+
+class AFKEP_Init(AFKEPMessage):
+ TYPE = 63, 48, Constant(0x80)
+
+class AFKEP_Init_Ack(AFKEPMessage):
+ TYPE = 63, 48, Constant(0xa0)
+
+class AFKEP_Start(AFKEPMessage):
+ TYPE = 63, 48, Constant(0xa3)
+
+class AFKEP_Start_Ack(AFKEPMessage):
+ TYPE = 63, 48, Constant(0x86)
+
+class AFKEP_Shutdown(AFKEPMessage):
+ TYPE = 63, 48, Constant(0xc0)
+
+class AFKEP_Shutdown_Ack(AFKEPMessage):
+ TYPE = 63, 48, Constant(0xc1)
+
+
+class AFKError(Exception):
+ pass
+
+class AFKRingBuf(Reloadable):
+ BLOCK_SIZE = 0x40
+
+ def __init__(self, ep, base, size):
+ self.ep = ep
+ self.base = base
+
+ bs, unk = struct.unpack("<II", self.read_buf(0, 8))
+ assert (bs + 3 * self.BLOCK_SIZE) == size
+ self.bufsize = bs
+ self.rptr = 0
+ self.wptr = 0
+
+ def read_buf(self, off, size):
+ return self.ep.iface.readmem(self.base + off, size)
+
+ def write_buf(self, off, data):
+ return self.ep.iface.writemem(self.base + off, data)
+
+ def get_rptr(self):
+ return self.ep.asc.p.read32(self.base + self.BLOCK_SIZE)
+
+ def get_wptr(self):
+ return self.ep.asc.p.read32(self.base + 2 * self.BLOCK_SIZE)
+
+ def update_rptr(self, rptr):
+ self.ep.asc.p.write32(self.base + self.BLOCK_SIZE, self.rptr)
+
+ def update_wptr(self, rptr):
+ self.ep.asc.p.write32(self.base + 2 * self.BLOCK_SIZE, self.wptr)
+
+ def read(self):
+ self.wptr = self.get_wptr()
+
+ while self.wptr != self.rptr:
+ hdr = self.read_buf(3 * self.BLOCK_SIZE + self.rptr, 16)
+ self.rptr += 16
+ magic, size = struct.unpack("<4sI", hdr[:8])
+ assert magic in [b"IOP ", b"AOP "]
+ if size > (self.bufsize - self.rptr):
+ hdr = self.read_buf(3 * self.BLOCK_SIZE, 16)
+ self.rptr = 16
+ magic, size = struct.unpack("<4sI", hdr[:8])
+ assert magic in [b"IOP ", b"AOP "]
+
+ payload = self.read_buf(3 * self.BLOCK_SIZE + self.rptr, size)
+ self.rptr = (align_up(self.rptr + size, self.BLOCK_SIZE)) % self.bufsize
+ self.update_rptr(self.rptr)
+ yield hdr[8:] + payload
+ self.wptr = self.get_wptr()
+
+ self.update_rptr(self.rptr)
+
+ def write(self, data):
+ hdr2, data = data[:8], data[8:]
+ self.rptr = self.get_rptr()
+
+ if self.wptr < self.rptr and self.wptr + 0x10 >= self.rptr:
+ raise AFKError("Ring buffer is full")
+
+ hdr = struct.pack("<4sI", b"IOP ", len(data)) + hdr2
+ self.write_buf(3 * self.BLOCK_SIZE + self.wptr, hdr)
+
+ if len(data) > (self.bufsize - self.wptr - 16):
+ if self.rptr < 0x10:
+ raise AFKError("Ring buffer is full")
+ self.write_buf(3 * self.BLOCK_SIZE, hdr)
+ self.wptr = 0
+
+ if self.wptr < self.rptr and self.wptr + 0x10 + len(data) >= self.rptr:
+ raise AFKError("Ring buffer is full")
+
+ self.write_buf(3 * self.BLOCK_SIZE + self.wptr + 0x10, data)
+ self.wptr = align_up(self.wptr + 0x10 + len(data), self.BLOCK_SIZE) % self.bufsize
+
+ self.update_wptr(self.wptr)
+ return self.wptr
+
+class AFKRingBufEndpoint(ASCBaseEndpoint):
+ BASE_MESSAGE = AFKEPMessage
+ SHORT = "afkep"
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.txq = None
+ self.rxq = None
+ self.iface = self.asc.iface
+ self.alive = False
+ self.started = False
+ self.iobuffer = None
+ self.verbose = 2
+ self.msgid = 0
+
+ def start(self):
+ self.send(AFKEP_Init())
+
+ @msg_handler(0xa0, AFKEP_Init_Ack)
+ def Init_Ack(self, msg):
+ self.alive = True
+ return True
+
+ @msg_handler(0x89, AFKEP_GetBuf)
+ def GetBuf(self, msg):
+ size = msg.SIZE * AFKRingBuf.BLOCK_SIZE
+
+ if self.iobuffer:
+ print("WARNING: trying to reset iobuffer!")
+
+ self.iobuffer, self.iobuffer_dva = self.asc.ioalloc(size)
+ self.asc.p.write32(self.iobuffer, 0xdeadbeef)
+ self.send(AFKEP_GetBuf_Ack(DVA=self.iobuffer_dva))
+ self.log(f"Buffer: phys={self.iobuffer:#x} dva={self.iobuffer_dva:#x} size={size:#x}")
+ return True
+
+ def stop(self):
+ self.log("Shutting down")
+ self.send(AFKEP_Shutdown())
+ while self.alive:
+ self.asc.work()
+
+ @msg_handler(0xc1, AFKEP_Shutdown_Ack)
+ def Shutdown_Ack(self, msg):
+ self.alive = False
+ self.log("Shutdown ACKed")
+ return True
+
+ @msg_handler(0x8a, AFKEP_InitRB)
+ def InitTX(self, msg):
+ self.txq = self.init_rb(msg)
+ if self.rxq and self.txq:
+ self.start_queues()
+ return True
+
+ @msg_handler(0x8b, AFKEP_InitRB)
+ def InitRX(self, msg):
+ self.rxq = self.init_rb(msg)
+ if self.rxq and self.txq:
+ self.start_queues()
+ return True
+
+ def init_rb(self, msg):
+ off = msg.OFFSET * AFKRingBuf.BLOCK_SIZE
+ size = msg.SIZE * AFKRingBuf.BLOCK_SIZE
+
+ return AFKRingBuf(self, self.iobuffer + off, size)
+
+ def start_queues(self):
+ self.send(AFKEP_Start())
+
+ @msg_handler(0x86, AFKEP_Start_Ack)
+ def Start_Ack(self, msg):
+ self.started = True
+ return True
+
+ @msg_handler(0x85, AFKEP_Recv)
+ def Recv(self, msg):
+ for data in self.rxq.read():
+ if self.verbose >= 3:
+ self.log(f"<RX rptr={self.rxq.rptr:#x}")
+ chexdump(data)
+ self.handle_ipc(data)
+ return True
+
+ def handle_ipc(self, data):
+ pass
+
+ def send_ipc(self, data):
+ wptr = self.txq.write(data)
+ self.send(AFKEP_Send(WPTR = wptr))
diff --git a/tools/proxyclient/m1n1/fw/agx/__init__.py b/tools/proxyclient/m1n1/fw/agx/__init__.py
new file mode 100644
index 0000000..e436720
--- /dev/null
+++ b/tools/proxyclient/m1n1/fw/agx/__init__.py
@@ -0,0 +1,98 @@
+# SPDX-License-Identifier: MIT
+from ...utils import *
+from ...malloc import Heap
+
+from ..asc import StandardASC
+from ..asc.base import ASCBaseEndpoint, msg_handler
+
+from .initdata import InitData, IOMapping
+
+__all__ = []
+
+class GpuMsg(Register64):
+ TYPE = 63, 48
+
+class InitMsg(GpuMsg):
+ TYPE = 63, 48, Constant(0x81)
+ UNK = 47, 44, Constant(0)
+ INITDATA = 43, 0
+
+class EventMsg(GpuMsg):
+ TYPE = 63, 48, Constant(0x42)
+ UNK = 47, 0, Constant(0)
+
+class DoorbellMsg(GpuMsg):
+ TYPE = 63, 48, Constant(0x83)
+ CHANNEL = 15, 0
+
+class FWCtlMsg(GpuMsg):
+ TYPE = 63, 48, Constant(0x84)
+
+class HaltMsg(GpuMsg):
+ TYPE = 63, 48, Constant(0x85)
+
+class FirmwareEP(ASCBaseEndpoint):
+ BASE_MESSAGE = GpuMsg
+ SHORT = "fw"
+
+ @msg_handler(0x42)
+ def event(self, msg):
+ #self.log("Received event")
+ self.asc.agx.poll_channels()
+ return True
+
+ def send_initdata(self, addr):
+ self.log(f"Sending initdata @ {addr:#x}")
+ msg = InitMsg(INITDATA=addr)
+ self.send(msg)
+
+class DoorbellEP(ASCBaseEndpoint):
+ BASE_MESSAGE = DoorbellMsg
+ SHORT = "db"
+
+ def doorbell(self, channel):
+ #self.log(f"Sending doorbell ch={channel}")
+ msg = DoorbellMsg(CHANNEL = channel)
+ self.send(msg)
+
+ def fwctl_doorbell(self):
+ msg = FWCtlMsg()
+ self.send(msg)
+
+class AGXASC(StandardASC):
+ ENDPOINTS = {
+ 0x20: FirmwareEP,
+ 0x21: DoorbellEP,
+ }
+
+ def __init__(self, u, base, agx, uat):
+ super().__init__(u, base)
+ self.agx = agx
+ self.uat = uat
+
+ def addr(self, addr):
+ base, obj = self.agx.find_object(addr)
+ if base is None:
+ return super().addr(addr)
+
+ return f"{addr:#x} ({obj._name} [{obj._size:#x}] @ {base:#x} + {addr - base:#x})"
+
+ def iomap(self, addr, size):
+ return self.uat.iomap(0, addr, size)
+
+ def ioalloc(self, size):
+ paddr = self.u.memalign(0x4000, size)
+ dva = self.iomap(paddr, size)
+ return paddr, dva
+
+ def ioread(self, dva, size, ctx=0):
+ return self.uat.ioread(ctx, dva & 0xFFFFFFFFFF, size)
+
+ def iowrite(self, dva, data, ctx=0):
+ return self.uat.iowrite(ctx, dva & 0xFFFFFFFFFF, data)
+
+ def iotranslate(self, dva, size, ctx=0):
+ return self.uat.iotranslate(ctx, dva & 0xFFFFFFFFFF, size)
+
+__all__.extend(k for k, v in globals().items()
+ if (callable(v) or isinstance(v, type)) and v.__module__ == __name__)
diff --git a/tools/proxyclient/m1n1/fw/agx/channels.py b/tools/proxyclient/m1n1/fw/agx/channels.py
new file mode 100644
index 0000000..6704d20
--- /dev/null
+++ b/tools/proxyclient/m1n1/fw/agx/channels.py
@@ -0,0 +1,481 @@
+
+import random
+
+from m1n1.utils import *
+from m1n1.constructutils import *
+from construct import *
+from .cmdqueue import *
+
+__all__ = ["channelNames", "channelRings", "DeviceControlMsg", "EventMsg", "StatsMsg"]
+
+class RunCmdQueueMsg(ConstructClass):
+ subcon = Struct (
+ "queue_type" / Default(Int32ul, 0),
+ "cmdqueue_addr" / Default(Hex(Int64ul), 0),
+ "cmdqueue" / Lazy(ROPointer(this.cmdqueue_addr, CommandQueueInfo)),
+ "head" / Default(Int32ul, 0),
+ "event_number" / Default(Int32ul, 0),
+ "new_queue" / Default(Int32ul, 0),
+ "data" / HexDump(Default(Bytes(0x18), bytes(0x18))),
+ )
+
+ TYPES = {
+ 0: "SubmitTA",
+ 1: "Submit3D",
+ 2: "SubmitCompute",
+ }
+
+ def __str__(self, *args, **kwargs):
+ s = super().__str__(*args, **kwargs) + "\n"
+
+ if self.cmdqueue_addr == 0:
+ return s + "<Empty RunCmdQueueMsg>"
+
+ r = random.randrange(2**64)
+ s += f"{self.TYPES[self.queue_type]}(0x{self.cmdqueue_addr & 0xfff_ffffffff:x}, {self.head}, ev={self.event_number}, new={self.new_queue}) //{r:x}"
+ return s
+
+class DC_DestroyContext(ConstructClass):
+ subcon = Struct (
+ "msg_type" / Const(0x17, Int32ul),
+ "unk_4" / Hex(Int32ul),
+ "unk_8" / Hex(Int32ul),
+ "unk_c" / Hex(Int32ul),
+ "unk_10" / Hex(Int32ul),
+ "unk_14" / Hex(Int32ul),
+ "unk_18" / Hex(Int32ul),
+ "context_addr" / Hex(Int64ul),
+ "rest" / HexDump(Default(Bytes(0xc), bytes(0xc)))
+ )
+
+class DC_Write32(ConstructClass):
+ subcon = Struct (
+ "msg_type" / Const(0x18, Int32ul),
+ "addr" / Hex(Int64ul),
+ "data" / Int32ul,
+ "unk_10" / Int32ul,
+ "unk_14" / Int32ul,
+ "unk_18" / Int32ul,
+ "unk_1c" / Int32ul,
+ "rest" / HexDump(Default(Bytes(0x10), bytes(0x10)))
+ )
+
+class DC_Write32B(ConstructClass):
+ subcon = Struct (
+ "msg_type" / Const(0x13, Int32ul),
+ "addr" / Hex(Int64ul),
+ "data" / Int32ul,
+ "unk_10" / Int32ul,
+ "unk_14" / Int32ul,
+ "unk_18" / Int32ul,
+ "unk_1c" / Int32ul,
+ "rest" / HexDump(Default(Bytes(0x10), bytes(0x10)))
+ )
+
+class DC_Init(ConstructClass):
+ subcon = Struct (
+ "msg_type" / Const(0x19, Int32ul),
+ "data" / HexDump(Default(Bytes(0x2c), bytes(0x2c)))
+ )
+
+class DC_09(ConstructClass):
+ subcon = Struct (
+ "msg_type" / Const(0x9, Int32ul),
+ "unk_4" / Int64ul,
+ "unkptr_c" / Int64ul,
+ "unk_14" / Int64ul,
+ "data" / HexDump(Default(Bytes(0x14), bytes(0x14)))
+ )
+
+class DC_Any(ConstructClass):
+ subcon = Struct (
+ "msg_type" / Int32ul,
+ "data" / HexDump(Default(Bytes(0x2c), bytes(0x2c)))
+ )
+
+class DC_1e(ConstructClass):
+ subcon = Struct (
+ "msg_type" / Const(0x1e, Int32ul),
+ "unk_4" / Int64ul,
+ "unk_c" / Int64ul,
+ "data" / HexDump(Default(Bytes(0x1c), bytes(0x1c)))
+ )
+
+class DC_UpdateIdleTS(ConstructClass):
+ subcon = Struct (
+ "msg_type" / Const(0x23, Int32ul),
+ "data" / HexDump(Default(Bytes(0x2c), bytes(0x2c))),
+ )
+
+class UnknownMsg(ConstructClass):
+ subcon = Struct (
+ "msg_type" / Hex(Int32ul),
+ "data" / HexDump(Bytes(0x2c)),
+ )
+
+DeviceControlMsg = FixedSized(0x30, Select(
+ DC_DestroyContext,
+ DC_Init,
+ DC_UpdateIdleTS,
+ DC_1e,
+ DC_Write32,
+ UnknownMsg,
+))
+
+class StatsMsg_Power(ConstructClass):
+ subcon = Struct (
+ "msg_type" / Hex(Const(0x00, Int32ul)),
+ ZPadding(0x18), # ??? why the hole? never written...
+ "power" / Hex(Int64ul),
+ ZPadding(0xc), # Confirmed padding
+ )
+
+ def __str__(self):
+ return f"Power: {self.power / 8192.0:.3f} mW"
+
+class StatsMsg_PowerOn(ConstructClass):
+ subcon = Struct (
+ "msg_type" / Hex(Const(0x02, Int32ul)),
+ "power_off_ticks" / Dec(Int64ul),
+ ZPadding(0x24), # Confirmed padding
+ )
+ def __str__(self):
+ t = self.power_off_ticks / 24000000
+ return f"Power ON: spent {t:.04}s powered off ({self.power_off_ticks} ticks)"
+
+class StatsMsg_PowerOff(ConstructClass):
+ subcon = Struct (
+ "msg_type" / Hex(Const(0x03, Int32ul)),
+ "power_on_ticks" / Dec(Int64ul),
+ ZPadding(0x24), # Confirmed padding
+ )
+ def __str__(self):
+ t = self.power_on_ticks / 24000000
+ return f"Power OFF: spent {t:.04}s powered on ({self.power_on_ticks} ticks)"
+
+class StatsMsg_Util(ConstructClass):
+ subcon = Struct (
+ "msg_type" / Hex(Const(0x04, Int32ul)),
+ "timestamp" / Hex(Int64ul),
+ "util1" / Dec(Int32ul),
+ "util2" / Dec(Int32ul),
+ "util3" / Dec(Int32ul),
+ "util4" / Dec(Int32ul),
+ ZPadding(0x14), # Confirmed padding
+ )
+ def __str__(self):
+ return f"Utilization: {self.util1:>3d}% {self.util2:>3d}% {self.util3:>3d}% {self.util4:>3d}%"
+
+class StatsMsg_AvgPower(ConstructClass):
+ subcon = Struct (
+ "msg_type" / Hex(Const(0x09, Int32ul)),
+ "active_cs" / Dec(Int64ul),
+ "unk2" / Hex(Int32ul),
+ "unk3" / Hex(Int32ul),
+ "unk4" / Hex(Int32ul),
+ "avg_power" / Dec(Int32ul),
+ ZPadding(0x14), # Confirmed padding
+ )
+
+ def __str__(self):
+ return f"Activity: Active {self.active_cs * 10:6d} ms Avg Pwr {self.avg_power:4d} mW ({self.unk2:d} {self.unk3:d} {self.unk4:d})"
+
+class StatsMsg_Temp(ConstructClass):
+ subcon = Struct (
+ "msg_type" / Hex(Const(0x0a, Int32ul)),
+ ZPadding(8), # Not written
+ "raw_value" / Hex(Int32ul),
+ "scale" / Hex(Int32ul),
+ "tmin" / Hex(Int32ul),
+ "tmax" / Hex(Int32ul),
+ ZPadding(0x14), # Confirmed padding
+ )
+
+ def __str__(self):
+ temp = self.raw_value / float(self.scale) / 64.0
+ return f"Temp: {temp:.2f}°C s={self.scale:d} tmin={self.tmin:d} tmax={self.tmax:d}"
+
+class StatsMsg_PowerState(ConstructClass):
+ subcon = Struct (
+ "msg_type" / Hex(Const(0x0b, Int32ul)),
+ "timestamp" / Hex(Int64ul),
+ "last_busy_ts" / Hex(Int64ul),
+ "active" / Hex(Int32ul),
+ "poweroff" / Dec(Int32ul),
+ "unk2" / Dec(Int32ul),
+ "pstate" / Dec(Int32ul),
+ "unk4" / Dec(Int32ul),
+ "unk5" / Dec(Int32ul),
+ ZPadding(4), # Confirmed padding
+ )
+
+ def __str__(self):
+ act = "ACT" if self.active else " "
+ off = "OFF" if self.poweroff else " "
+
+ return f"PowerState: {act} {off} ps={int(self.pstate)} {self.unk4} {self.unk2} {self.unk5}"
+
+class StatsMsg_FWBusy(ConstructClass):
+ subcon = Struct (
+ "msg_type" / Hex(Const(0x0c, Int32ul)),
+ "timestamp" / Hex(Int64ul),
+ "flag" / Int32ul,
+ ZPadding(0x20), # Confirmed padding
+ )
+
+ def __str__(self):
+ return f"FW active: {bool(self.flag)}"
+
+class StatsMsg_PState(ConstructClass):
+ subcon = Struct (
+ "msg_type" / Hex(Const(0x0d, Int32ul)),
+ ZPadding(8), # Not written
+ "ps_min" / Dec(Int32ul),
+ "unk1" / Dec(Int32ul),
+ "ps_max" / Dec(Int32ul),
+ "unk3" / Dec(Int32ul),
+ ZPadding(0x14), # Confirmed padding
+ )
+ def __str__(self):
+ return f"PState: {self.ps_min:d}..{self.ps_max:d} ({self.unk1:d}/{self.unk3:d})"
+
+class StatsMsg_TempSensor(ConstructClass):
+ subcon = Struct (
+ "msg_type" / Hex(Const(0x0e, Int32ul)),
+ ZPadding(4), # Not written
+ "sensor_id" / Hex(Int32ul),
+ "raw_value" / Hex(Int32ul),
+ "scale" / Dec(Int32ul),
+ "tmin" / Dec(Int32ul),
+ "tmax" / Dec(Int32ul),
+ ZPadding(0x14), # Confirmed padding
+ )
+ def __str__(self):
+ temp = self.raw_value / float(self.scale) / 64.0
+ return f"TempSensor: #{self.sensor_id:d} {temp:.2f}°C s={self.scale:d} tmin={self.tmin:d} tmax={self.tmax:d}"
+
+StatsMsg = FixedSized(0x30, Select(
+ StatsMsg_Power,
+ StatsMsg_PowerOn,
+ StatsMsg_PowerOff,
+ StatsMsg_Util,
+ StatsMsg_AvgPower,
+ StatsMsg_Temp,
+ StatsMsg_PowerState,
+ StatsMsg_FWBusy,
+ StatsMsg_PState,
+ StatsMsg_TempSensor,
+ UnknownMsg,
+))
+
+class FWLogMsg(ConstructClass):
+ subcon = Struct (
+ "msg_type" / Hex(Const(0x03, Int32ul)),
+ "seq_no" / Hex(Int32ul),
+ "timestamp" / Hex(Int64ul),
+ "msg" / PaddedString(0xc8, "ascii")
+ )
+
+class FaultMsg(ConstructClass):
+ subcon = Struct (
+ "msg_type" / Hex(Const(0, Int32ul)),
+ "unk_4" / HexDump(Bytes(0x34)),
+ )
+
+class FlagMsg(ConstructClass):
+ subcon = Struct (
+ "msg_type" / Hex(Const(1, Int32ul)),
+ "firing" / Array(2, Hex(Int64ul)),
+ "unk_14" / Hex(Int16ul),
+ "tail" / Bytes(0x38 - 0x18),
+ )
+
+class TimeoutMsg(ConstructClass):
+ subcon = Struct (
+ "msg_type" / Hex(Const(4, Int32ul)),
+ "counter" / Hex(Int32ul),
+ "unk_8" / Hex(Int32ul),
+ "stamp_index" / Hex(Int32sl),
+ "unkpad_16" / HexDump(Bytes(0x38 - 0x10)),
+ )
+
+EventMsg = FixedSized(0x38, Select(
+ FaultMsg,
+ FlagMsg,
+ TimeoutMsg,
+ HexDump(Bytes(0x38)),
+))
+
+TRACE_MSGS = {
+ (0x00, 0x00, 0): ("StartTA", "uuid", None, "unk", "cmdqueue"),
+ (0x00, 0x01, 0): ("FinishTA", "uuid", None, "unk", "cmdqueue"),
+ (0x00, 0x04, 0): ("Start3D", "uuid", "partial_render", "unk", "cmdqueue"),
+ (0x00, 0x05, 0): ("Finish3D_unk", "uuid", "unk", "flag", "buf_related"),
+ (0x00, 0x06, 0): ("Finish3D", "uuid", None, "unk", "cmdqueue"),
+ (0x00, 0x07, 0): ("StartCP", "uuid", None, "unk", "cmdqueue"),
+ (0x00, 0x08, 0): ("FinishCP", "uuid", None, "unk", "cmdqueue"),
+ (0x00, 0x0a, 0): ("StampUpdateTA", "value", "ev_id", "addr", "uuid"),
+ (0x00, 0x0c, 0): ("StampUpdate3D", "value", "ev_id", "addr", "uuid"),
+ (0x00, 0x0e, 0): ("StampUpdateCL", "value", "ev_id", "addr", "uuid"),
+ (0x00, 0x10, 1): ("TAPreproc1", "unk"),
+ (0x00, 0x10, 2): ("TAPreproc2", "unk1", "unk2"),
+ (0x00, 0x17, 0): ("Finish3D2", "uuid", None, "unk", "cmdqueue"),
+ (0x00, 0x28, 0): ("EvtNotify", "firing0", "firing1", "firing2", "firing3"),
+ (0x00, 0x2f, 0): ("Finish3D_unk2", "uuid", "unk"),
+ (0x00, 0x1e, 0): ("CleanupPB", "uuid", "unk2", "slot"),
+ (0x01, 0x0a, 0): ("Postproc", "cmdid", "event_ctl", "stamp_val", "uuid"),
+ (0x01, 0x0b, 0): ("EvtComplete", None, "event_ctl"),
+ (0x01, 0x0d, 0): ("EvtDequeued", "next", "event_ctl"),
+ (0x01, 0x16, 0): ("InitAttachment", "idx", "flags", "addr", "size"),
+ (0x01, 0x18, 0): ("ReInitAttachment", "idx", "flags", "addr", "size"),
+}
+
+class KTraceMsg(ConstructClass):
+ THREADS = [
+ "irq",
+ "bg",
+ "smpl",
+ "pwr",
+ "rec",
+ "kern",
+ ]
+ subcon = Struct (
+ "msg_type" / Hex(Const(5, Int32ul)),
+ "timestamp" / Hex(Int64ul),
+ "args" / Array(4, Int64ul),
+ "code" / Int8ul,
+ "channel" / Int8ul,
+ "pad" / Const(0, Int8ul),
+ "thread" / Int8ul,
+ "unk_flag" / Int64ul,
+ )
+ def __str__(self):
+ ts = self.timestamp / 24000000
+ code = (self.channel, self.code, self.unk_flag)
+ if code in TRACE_MSGS:
+ info = TRACE_MSGS[code]
+ args = info[0] + ": " + " ".join(f"{k}={v:#x}" for k, v in zip(info[1:], self.args) if k is not None)
+ else:
+ args = "UNK: " + ", ".join(hex(i) for i in self.args)
+ return f"TRACE: [{ts:10.06f}][{self.THREADS[self.thread]:4s}] {self.channel:2x}:{self.code:2x} ({self.unk_flag}) {args}"
+
+class FWCtlMsg(ConstructClass):
+ subcon = Struct (
+ "addr" / Int64ul,
+ "unk_8" / Int32ul,
+ "context_id" / Int32ul,
+ "unk_10" / Int16ul,
+ "unk_12" / Int16ul,
+ )
+
+channelNames = [
+ "TA_0", "3D_0", "CL_0",
+ "TA_1", "3D_1", "CL_1",
+ "TA_2", "3D_2", "CL_2",
+ "TA_3", "3D_3", "CL_3",
+ "DevCtrl",
+ "Event", "FWLog", "KTrace", "Stats",
+
+ ## Not really in normal order
+ "FWCtl"
+]
+
+# Exclude FWCtl
+CHANNEL_COUNT = len(channelNames) - 1
+
+channelRings = (
+ [[(RunCmdQueueMsg, 0x30, 0x100)]] * 12 + [
+ [(DeviceControlMsg, 0x30, 0x100)],
+ [(EventMsg, 0x38, 0x100)],
+ [
+ (FWLogMsg, 0xd8, 0x100), # unk 0
+ (FWLogMsg, 0xd8, 0x100), # init log
+ (FWLogMsg, 0xd8, 0x100), # unk 2
+ (FWLogMsg, 0xd8, 0x100), # warnings?
+ (FWLogMsg, 0xd8, 0x100), # unk 4
+ (FWLogMsg, 0xd8, 0x100), # unk 5
+ ],
+ [(KTraceMsg, 0x38, 0x200)],
+ [(HexDump(Bytes(0x60)), 0x60, 0x100)],
+ [(FWCtlMsg, 0x14, 0x100)],
+ ]
+)
+
+class ChannelStateFields(RegMap):
+ _SIZE = 0x30
+
+ READ_PTR = 0x00, Register32
+ WRITE_PTR = 0x20, Register32
+
+class FWControlStateFields(RegMap):
+ _SIZE = 0x20
+
+ READ_PTR = 0x00, Register32
+ WRITE_PTR = 0x10, Register32
+
+class Channel(Reloadable):
+ def __init__(self, u, uat, info, ring_defs, base=None, state_fields=ChannelStateFields):
+ self.uat = uat
+ self.u = u
+ self.p = u.proxy
+ self.iface = u.iface
+
+ self.ring_defs = ring_defs
+ self.info = info
+
+ self.accessor = uat.ioaccessor(0)
+ self.state_addr = info.state_addr
+ self.state = []
+ self.rb_base = []
+ self.rb_maps = []
+
+ if base is None:
+ p = info.ringbuffer_addr
+ else:
+ p = base
+ for i, (msg, size, count) in enumerate(ring_defs):
+ assert msg.sizeof() == size
+
+ self.state.append(state_fields(self.accessor, self.state_addr + 0x30 * i))
+ m = uat.iotranslate(0, p, size * count)
+ self.rb_base.append(p)
+ self.rb_maps.append(m)
+ p += size * count
+
+ def get_message(self, ring, index, meta_fn=None):
+ msgcls, size, count = self.ring_defs[ring]
+
+ assert index < count
+ addr = self.rb_base[ring] + index * size
+ stream = self.uat.iostream(0, addr)
+ stream.meta_fn = meta_fn
+ return msgcls.parse_stream(stream)
+
+ def clear_message(self, ring, index):
+ msgcls, size, count = self.ring_defs[ring]
+
+ self.put_message(ring, index, b"\xef\xbe\xad\xde" * (size // 4))
+
+ def put_message(self, ring, index, obj):
+ msgcls, size, count = self.ring_defs[ring]
+
+ assert index < count
+ if isinstance(obj, bytes):
+ data = obj
+ else:
+ data = obj.build()
+ self.uat.iowrite(0, self.rb_base[ring] + index * size, data)
+
+class ChannelInfo(ConstructClass):
+ subcon = Struct(
+ "state_addr" / Hex(Int64ul),
+ "ringbuffer_addr" / Hex(Int64ul),
+ )
+
+class ChannelInfoSet(ConstructClass):
+ CHAN_COUNT = CHANNEL_COUNT
+
+ subcon = Struct(*[ name / ChannelInfo for name in channelNames[:CHAN_COUNT]])
+
+__all__.extend(k for k, v in globals().items()
+ if (callable(v) or isinstance(v, type)) and v.__module__ == __name__)
diff --git a/tools/proxyclient/m1n1/fw/agx/cmdqueue.py b/tools/proxyclient/m1n1/fw/agx/cmdqueue.py
new file mode 100644
index 0000000..bd90a05
--- /dev/null
+++ b/tools/proxyclient/m1n1/fw/agx/cmdqueue.py
@@ -0,0 +1,516 @@
+# SPDX-License-Identifier: MIT
+from m1n1.constructutils import *
+from construct import *
+from .microsequence import *
+from ...utils import RegMap, Register32
+
+__all__ = []
+
+class WorkCommandBarrier(ConstructClass):
+ """
+ sent before WorkCommand3D on the Submit3d queue.
+ Might be for initilzing the tile buckets?
+
+ Example:
+ 00000004 0c378018 ffffffa0 00000c00 00000006 00000900 08002c9a 00000000
+ 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
+ """
+ subcon = Struct(
+ "magic" / Const(0x4, Int32ul),
+ "stamp_addr" / Int64ul,
+ "stamp" / ROPointer(this.stamp_addr, StampCounter),
+ "wait_value" / Int32ul,
+ "event" / Int32ul, # Event number that signals a stamp check
+ "stamp_self" / Int32ul,
+ "uuid" / Int32ul,
+ "unk" / Default(Int32ul, 0),
+ )
+
+class WorkCommandInitBM(ConstructClass):
+ """
+ occationally sent before WorkCommandTA on the SubmitTA queue.
+
+ Example:
+ 00000004 0c378018 ffffffa0 00000c00 00000006 00000900 08002c9a 00000000
+ 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
+ """
+ subcon = Struct(
+ "magic" / Const(0x6, Hex(Int32ul)),
+ "context_id" / Hex(Int32ul), # Might be context?
+ "buffer_mgr_slot" / Hex(Int32ul), # 0
+ "unk_c" / Hex(Int32ul), # 0
+ "unk_10" / Hex(Int32ul), # 0x30
+ "buffer_mgr_addr" / Int64ul,
+ "buffer_mgr" / ROPointer(this.buffer_mgr_addr, BufferManagerInfo),
+ "stamp_value" / Hex(Int32ul), # 0x100
+ )
+
+class LinkedListHead(ConstructClass):
+ subcon = Struct(
+ "prev" / Int64ul,
+ "next" / Int64ul,
+ )
+
+ def __init__(self):
+ super().__init__()
+ self.prev = 0
+ self.next = 0
+
+class EventControlUnkBuf(ConstructValueClass):
+ subcon = HexDump(Bytes(0x8))
+
+ def __init__(self):
+ super().__init__()
+ self.value = b"\xff" * 8
+
+class EventControl(ConstructClass):
+ subcon = Struct(
+ "event_count_addr" / Int64ul,
+ "event_count" / ROPointer(this.event_count_addr, Int32ul),
+ "generation" / Int32ul,
+ "cur_count" / Int32ul,
+ "unk_10" / Int32ul,
+ "unk_14" / Int32ul,
+ "unk_18" / Int64ul,
+ "unk_20" / Int32ul,
+ "vm_slot" / Int32ul,
+ "has_ta" / Int32ul,
+ "pstamp_ta" / Array(4, Int64ul),
+ "has_3d" / Int32ul,
+ "pstamp_3d" / Array(4, Int64ul),
+ "has_cp" / Int32ul,
+ "pstamp_cp" / Array(4, Int64ul),
+ "in_list" / Int32ul,
+ Ver("G >= G14", "unk_98_g14_0" / HexDump(Bytes(0x14))),
+ "list_head" / LinkedListHead,
+ Ver("G >= G14", "unk_a8_g14_0" / Padding(4)),
+ Ver("V >= V13_0B4", "unk_buf" / EventControlUnkBuf),
+ )
+
+ def __init__(self):
+ super().__init__()
+ self.unk_14 = 0
+ self.unk_18 = 0
+ self.unk_20 = 0
+ self.vm_slot = 0
+ self.has_ta = 0
+ self.pstamp_ta = [0]*4
+ self.has_3d = 0
+ self.pstamp_3d = [0]*4
+ self.has_cp = 0
+ self.pstamp_cp = [0]*4
+ self.in_list = 0
+ self.unk_98_g14_0 = bytes(0x14)
+ self.list_head = LinkedListHead()
+ self.unk_buf = EventControlUnkBuf()
+
+class WorkCommandCP(ConstructClass):
+ """
+ For compute
+
+ Example:
+ 00000000 00000003 00000000 00000004 0c3d80c0 ffffffa0 00000000 00000000 00000000
+ 00000020 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
+ 00000040 *
+ 00000060 00000000 00000000 00088000 00000015 00078000 00000015 000a6300 00000015
+ 00000080 000a6308 00000015 000a6310 00000015 000a6318 00000015 00000000 00000011
+ 000000a0 00008c60 00000000 00000041 00000000 000e8000 00000015 00000040 00000000
+ 000000c0 00000001 00000000 0000001c 00000000 00000000 00000000 00000000 00000000
+ 000000e0 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
+ 00000100 *
+ 000001e0 00000000 00000000 0c311cc0 ffffffa0 00000240 00000000 00000000 00000000
+ 00000200 00000000 00000000 00000000 00000000 00000000 00000000 00088000 00000015
+ 00000220 00078024 00000015 00000000 00000000 00000000 00000000 00000000 00000000
+ 00000240 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
+ 00000260 110022b3 00000000 ffffffff 00000500 00000015 00000000 00000000 00000000
+ 00000280 000c8014 ffffffa0 0c378014 ffffffa0 00003b00 00000005 00000000 00000000
+ 000002a0 120022b8 00000000 00000000 00000000 00029030 ffffffa0 00029038 ffffffa0
+ 000002c0 00000000 00000000 00000000 00000000 00000015 00000000 00000000 00000000
+ 000002e0 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
+ """
+
+ subcon = Struct(
+ "addr" / Tell,
+ "magic" / Const(0x3, Hex(Int32ul)),
+ "unk_4" / Hex(Int32ul),
+ "context_id" / Hex(Int32ul),
+ "event_control_addr" / Hex(Int64ul),
+ "event_control" / ROPointer(this.event_control_addr, EventControl),
+
+ # This struct embeeds some data that the Control List has pointers back to, but doesn't
+ # seem to be actually part of this struct
+ Padding(0x1e8 - 0x14),
+
+ # offset 000001e8
+ "microsequence_ptr" / Hex(Int64ul),
+ "microsequence_size" / Hex(Int32ul),
+ "microsequence" / ROPointer(this.microsequence_ptr, MicroSequence),
+ )
+
+class WorkCommand0_UnkBuf(ConstructValueClass):
+ subcon = HexDump(Bytes(0x18))
+
+ def __init__(self):
+ self.value = bytes(0x18)
+
+class WorkCommand1_UnkBuf(ConstructValueClass):
+ subcon = HexDump(Bytes(0x110))
+
+ def __init__(self):
+ self.value = bytes(0x110)
+
+class WorkCommand1_UnkBuf2(ConstructClass):
+ subcon = Struct(
+ "unk_0" / Int64ul,
+ "unk_8" / Int64ul,
+ "unk_10" / Int64ul,
+ )
+
+class Flag(ConstructValueClass):
+ subcon = Hex(Int32ul)
+
+ def __init__(self):
+ self.value = 0
+
+class WorkCommand3D(ConstructClass):
+ """
+ For 3D
+
+ Example: 0xfa00c095640
+ 00000000 00000001 00000004 00000000 0c2d5f00 ffffffa0 000002c0 0c3d80c0 ffffffa0
+ 00000020 0c3e0000 ffffffa0 0c3e0100 ffffffa0 0c3e09c0 ffffffa0 01cb0000 00000015
+ 00000040 00000088 00000000 00000001 0010000c 00000000 00000000 00000000 00000000
+ 00000060 3a8de3be 3abd2fa8 00000000 00000000 0000076c 00000000 0000a000 00000000
+ 00000080 ffff8002 00000000 00028044 00000000 00000088 00000000 005d0000 00000015
+ 000000a0 00758000 00000015 0000c000 00000000 00000640 000004b0 0257863f 00000000
+ 000000c0 00000000 00000000 00000154 00000000 011d0000 00000015 011d0000 00000015
+ 000000e0 0195c000 00000015 0195c000 00000015 00000000 00000000 00000000 00000000
+ 00000100 00000000 00000000 00000000 00000000 0193c000 00000015 00000000 00000000
+ 00000120 0193c000 00000015 00000000 00000000 01b64000 00000015 00000000 00000000
+ 00000140 01b64000 00000015 00000000 00000000 01cb0000 00000015 01cb4000 00000015
+ 00000160 c0000000 00000003 01cb4000 00000015 00010280 00000000 00a38000 00000015
+ 00000180 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
+ 000001a0 00000000 00000000 00000000 00000000 00000000 00000011 00008c60 00000000
+ 000001c0 00000000 00000000 00000000 00000000 0000001c 00000000 00000000 00000000
+ 000001e0 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
+ 00000200 *
+ 000003c0 00000012 00028084 00000000 00000000 3a8de3be 3abd2fa8 00000000 00000000
+ 000003e0 0010000c 00000000 00025031 00000004 3f800000 00000700 00000000 00000001
+ """
+
+ subcon = Struct(
+ "addr" / Tell,
+ "magic" / Const(0x1, Hex(Int32ul)),
+ Ver("V >= V13_0B4", "counter" / Int64ul),
+ "context_id" / Hex(Int32ul),
+ "unk_8" / Hex(Int32ul),
+ "microsequence_ptr" / Hex(Int64ul), # Command list
+ "microsequence_size" / Hex(Int32ul),
+ "microsequence" / ROPointer(this.microsequence_ptr, MicroSequence),
+ "event_control_addr" / Hex(Int64ul),
+ "event_control" / ROPointer(this.event_control_addr, EventControl),
+ "buffer_mgr_addr" / Int64ul,
+ "buffer_mgr" / ROPointer(this.buffer_mgr_addr, BufferManagerInfo),
+ "buf_thing_addr" / Int64ul,
+ "buf_thing" / ROPointer(this.buf_thing_addr, BufferThing),
+ "unk_emptybuf_addr" / Hex(Int64ul),
+ "tvb_tilemap" / Hex(Int64ul),
+ "unk_40" / Hex(Int64ul),
+ "unk_48" / Hex(Int32ul),
+ "tile_blocks_y" / Hex(Int16ul), # * 4
+ "tile_blocks_x" / Hex(Int16ul), # * 4
+ "unk_50" / Hex(Int64ul),
+ "unk_58" / Hex(Int64ul),
+ "merge_upper_x" / Hex(Float32l),
+ "merge_upper_y" / Hex(Float32l),
+ "unk_68" / Hex(Int64ul),
+ "tile_count" / Hex(Int64ul),
+
+ # Embedded structures that are also pointed to by other stuff
+ "struct_2" / Start3DStruct2,
+ "struct_1" / Start3DStruct1,
+ "unk_758" / Flag,
+ "unk_75c" / Flag,
+ "unk_buf" / WorkCommand1_UnkBuf,
+ "busy_flag" / Flag,
+ "struct_6" / Start3DStruct6,
+ "struct_7" / Start3DStruct7,
+ "unk_buf2" / WorkCommand1_UnkBuf2,
+ "ts1" / TimeStamp,
+ "ts2" / TimeStamp,
+ "ts3" / TimeStamp,
+ "unk_914" / Int32ul,
+ "unk_918" / Int64ul,
+ "unk_920" / Int32ul,
+ "unk_924" / Int32ul,
+ Ver("V >= V13_0B4", "unk_928_0" / Int32ul),
+ Ver("V >= V13_0B4", "unk_928_4" / Int8ul),
+ Ver("V >= V13_0B4", "ts_flag" / TsFlag),
+ Ver("V >= V13_0B4", "unk_5e6" / Default(Int16ul, 0)),
+ Ver("V >= V13_0B4", "unk_5e8" / Default(HexDump(Bytes(0x20)), bytes(0x20))),
+ "pad_928" / Default(HexDump(Bytes(0x18)), bytes(0x18)),
+ )
+
+class WorkCommand0_UnkBuf(ConstructValueClass):
+ subcon = HexDump(Bytes(0x18))
+
+ def __init__(self):
+ super().__init__()
+ self.value = bytes(0x18)
+
+class WorkCommandTA(ConstructClass):
+ """
+ For TA
+
+ Example:
+ 00000000 00000000 00000004 00000000 0c3d80c0 ffffffa0 00000002 00000000 0c3e0000
+ 00000020 ffffffa0 0c3e0100 ffffffa0 0c3e09c0 ffffffa0 00000000 00000200 00000000
+ 00000040 1e3ce508 1e3ce508 01cb0000 00000015 00000000 00000000 00970000 00000015
+ 00000060 01cb4000 80000015 006b0003 003a0012 00000001 00000000 00000000 00000000
+ 00000080 0000a000 00000000 00000088 00000000 01cb4000 00000015 00000000 00000000
+ 000000a0 0000ff00 00000000 007297a0 00000015 00728120 00000015 00000001 00000000
+ 000000c0 00728000 00040015 009f8000 00000015 00000000 00000000 00000000 00000000
+ 000000e0 0000a441 00000000 00000000 00000000 00000000 00000000 00000000 00000000
+ 00000100 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000011
+ 00000120 00000000 00000000 0000001c 00000000 00008c60 00000000 00000000 00000000
+ 00000140 00000000 00000000 00000000 00000000 0000001c 00000000 00000000 00000000
+ 00000160 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
+ 00000180 *
+ 000003a0 00000000 00000000 00000000 00000000 00000000 00000000 00000000 000000f0
+ 000003c0 00000088 00000202 04af063f 00025031 00404030 00303024 000000c0 00000180
+ 000003e0 00000100 00008000 00000000 00000000 00000000 00000000 00000000 00000000
+ """
+
+ subcon = Struct(
+ "addr" / Tell,
+ "magic" / Const(0x0, Hex(Int32ul)),
+ Ver("V >= V13_0B4", "counter" / Int64ul),
+ "context_id" / Hex(Int32ul),
+ "unk_8" / Hex(Int32ul),
+ "event_control_addr" / Hex(Int64ul),
+ "event_control" / ROPointer(this.event_control_addr, EventControl),
+ "buffer_mgr_slot" / Hex(Int64ul),
+ "buffer_mgr_addr" / Int64ul,
+ "buffer_mgr" / ROPointer(this.buffer_mgr_addr, BufferManagerInfo),
+ "buf_thing_addr" / Int64ul,
+ "buf_thing" / ROPointer(this.buf_thing_addr, BufferThing),
+ "unk_emptybuf_addr" / Hex(Int64ul),
+ "unk_34" / Hex(Int32ul),
+
+ # Embedded structures that are also pointed to by other stuff
+ "struct_2" / StartTACmdStruct2, # 0x11c bytes
+ "unk_154" / HexDump(Bytes(0x268)), # unknown
+ "tiling_params" / TilingParameters, # 0x2c bytes
+ "unk_3e8" / HexDump(Bytes(0x74)), # unknown
+
+ "unkptr_45c" / Int64ul,
+ "tvb_size" / Int64ul,
+ "microsequence_ptr" / Hex(Int64ul),
+ "microsequence_size" / Hex(Int32ul),
+ "microsequence" / ROPointer(this.microsequence_ptr, MicroSequence),
+ "ev_3d" / Int32ul,
+ "stamp_value" / Int32ul,
+
+ "struct_3" / StartTACmdStruct3, # 0x114 bytes
+
+ "unk_594" / WorkCommand0_UnkBuf,
+
+ "ts1" / TimeStamp,
+ "ts2" / TimeStamp,
+ "ts3" / TimeStamp,
+
+ "unk_5c4" / Int32ul,
+ "unk_5c8" / Int32ul,
+ "unk_5cc" / Int32ul,
+ "unk_5d0" / Int32ul,
+ "unk_5d4" / Int8ul,
+ "pad_5d5" / Default(HexDump(Bytes(0x3)), bytes(0x3)),
+ Ver("V >= V13_0B4", "unk_5e0" / Int32ul),
+ Ver("V >= V13_0B4", "unk_5e4" / Int8ul),
+ Ver("V >= V13_0B4", "ts_flag" / TsFlag),
+ Ver("V >= V13_0B4", "unk_5e6" / Default(Int16ul, 0)),
+ Ver("V >= V13_0B4", "unk_5e8" / Default(HexDump(Bytes(0x18)), bytes(0x18))),
+ "pad_5d8" / Default(HexDump(Bytes(0x8)), bytes(0x8)),
+ Ver("V >= V13_0B4", "pad_5e0" / Default(HexDump(Bytes(0x18)), bytes(0x18))),
+ )
+
+class UnknownWorkCommand(ConstructClass):
+ subcon = Struct(
+ "magic" / Hex(Int32ul),
+ "unk_4" / Hex(Int32ul),
+ "unk_8" / Hex(Int32ul),
+ "unk_c" / Hex(Int32ul),
+ "unk_10" / Hex(Int32ul),
+ "unk_14" / Hex(Int32ul),
+ "unk_18" / Hex(Int32ul),
+ "unk_1c" / Hex(Int32ul),
+ )
+
+class CmdBufWork(ConstructClass):
+ subcon = Struct(
+ "cmdid" / Peek(Int32ul),
+ "cmd" / Switch(this.cmdid, {
+ 0: WorkCommandTA,
+ 1: WorkCommand3D,
+ 3: WorkCommandCP,
+ 4: WorkCommandBarrier,
+ 6: WorkCommandInitBM,
+ })
+ )
+
+class JobList(ConstructClass):
+ subcon = Struct(
+ "first_job" / Default(Int64ul, 0),
+ "last_head" / Int64ul,
+ "unkptr_10" / Default(Int64ul, 0),
+ )
+
+class GPUContextData(ConstructClass):
+ subcon = Struct(
+ "unk_0" / Int8ul,
+ "unk_1" / Int8ul,
+ "unk_2" / Default(Bytes(3), bytes(3)),
+ "unk_5" / Int8ul,
+ "unk_6" / Default(Bytes(0x18), bytes(0x18)),
+ "unk_1e" / Int8ul,
+ "unk_1f" / Int8ul,
+ "unk_20" / Default(Bytes(3), bytes(3)),
+ "unk_23" / Int8ul,
+ "unk_24" / Default(Bytes(0x1c), bytes(0x1c)),
+ )
+
+ def __init__(self):
+ self.unk_0 = 0xff
+ self.unk_1 = 0xff
+ self.unk_5 = 1
+ self.unk_1e = 0xff
+ self.unk_1f = 0
+ self.unk_23 = 2
+
+class CommandQueuePointerMap(RegMap):
+ GPU_DONEPTR = 0x00, Register32
+ GPU_RPTR = 0x30, Register32
+ CPU_WPTR = 0x40, Register32
+
+class CommandQueuePointers(ConstructClass):
+ subcon = Struct(
+ "gpu_doneptr" / Int32ul,
+ ZPadding(12),
+ "unk_10" / Int32ul,
+ ZPadding(12),
+ "unk_20" / Int32ul,
+ ZPadding(12),
+ "gpu_rptr" / Int32ul,
+ ZPadding(12),
+ "cpu_wptr" / Int32ul,
+ ZPadding(12),
+ "rb_size" / Int32ul,
+ ZPadding(12),
+ )
+
+ def __init__(self):
+ super().__init__()
+ self.gpu_doneptr = 0
+ self.unk_10 = 0
+ self.unk_20 = 0
+ self.gpu_rptr = 0
+ self.cpu_wptr = 0
+ self.rb_size = 0x500
+
+class CommandQueueInfo(ConstructClass):
+ """ Structure type shared by Submit3D, SubmitTA and SubmitCompute
+ Applications have multiple of these, one of each submit type
+ TODO: Can applications have more than one of each type? One per encoder?
+ Mostly managed by GPU, only intialize by CPU
+
+ """
+ subcon = Struct(
+ "pointers_addr" / Hex(Int64ul),
+ "pointers" / ROPointer(this.pointers_addr, CommandQueuePointers),
+ "rb_addr" / Hex(Int64ul), # 0x4ff pointers
+ "job_list_addr" / Hex(Int64ul), # ffffffa000000000, size 0x18 (shared by 3D and TA)
+ "job_list" / ROPointer(this.job_list_addr, JobList),
+ "gpu_buf_addr" / Hex(Int64ul), # GPU space for this queue, 0x2c18 bytes?
+ #"gpu_buf" / ROPointer(this.gpu_buf_addr, HexDump(Bytes(0x2c18))),
+ "gpu_rptr1" / Hex(Int32ul),
+ "gpu_rptr2" / Hex(Int32ul),
+ "gpu_rptr3" / Hex(Int32ul),
+ "event_id" / Int32sl,
+ "unk_30" / Hex(Int32ul), # read by CPU
+ "unk_34" / Hex(Int32ul),
+ "unk_38" / Hex(Int64ul),
+ "unk_40" / Hex(Int32ul), # 1
+ "unk_44" / Hex(Int32ul), # 0
+ "unk_48" / Hex(Int32ul), # 1, 2
+ "unk_4c" / Int32sl, # -1
+ "uuid" / Hex(Int32ul), # Counts up for each new process or command queue
+ "unk_54" / Int32sl,
+ "unk_58" / Hex(Int64ul), # 0
+ "busy" / Hex(Int32ul), # 1 = gpu busy
+ "pad1" / ZPadding(0x20),
+ "blocked_on_barrier" / Hex(Int32ul),
+ "unk_88" / Int32ul,
+ "unk_8c" / Int32ul,
+ "unk_90" / Int32ul,
+ "unk_94" / Int32ul,
+ "pending" / Int32ul,
+ "unk_9c" / Int32ul,
+ "gpu_context_addr" / Hex(Int64ul), # GPU managed context, shared between 3D and TA. Passed to DC_DestroyContext
+ "gpu_context" / ROPointer(this.gpu_context_addr, GPUContextData),
+ "unk_a8" / Int64ul
+ # End of struct
+ )
+
+ def __init__(self):
+ super().__init__()
+ self.gpu_rptr1 = 0
+ self.gpu_rptr2 = 0
+ self.gpu_rptr3 = 0
+ self.event_id = -1
+ self.unk_4c = -1
+ self.uuid = 0xdeadbeef # some kind of ID
+ self.unk_54 = -1
+ self.unk_58 = 0x0
+ self.busy = 0x0
+ self.blocked_on_barrier = 0x0
+ self.unk_88 = 0
+ self.unk_8c = 0
+ self.unk_90 = 0
+ self.unk_94 = 0
+ self.pending = 0
+ self.unk_9c = 0
+ self.set_prio(0)
+ self.unk_a8 = 0
+
+ def set_prio(self, p):
+ if p == 0:
+ self.unk_30 = 0
+ self.unk_34 = 0 # 0-3?
+ self.unk_38 = 0xffff_ffff_ffff_0000
+ self.unk_40 = 1
+ self.unk_44 = 0
+ self.unk_48 = 1
+ elif p == 1:
+ self.unk_30 = 1
+ self.unk_34 = 1
+ self.unk_38 = 0xffff_ffff_0000_0000
+ self.unk_40 = 0
+ self.unk_44 = 0
+ self.unk_48 = 0
+ elif p == 2:
+ self.unk_30 = 2
+ self.unk_34 = 2
+ self.unk_38 = 0xffff_0000_0000_0000
+ self.unk_40 = 0
+ self.unk_44 = 0
+ self.unk_48 = 2
+ else:
+ self.unk_30 = 3
+ self.unk_34 = 3
+ self.unk_38 = 0x0000_0000_0000_0000
+ self.unk_40 = 0
+ self.unk_44 = 0
+ self.unk_48 = 3
+
+__all__.extend(k for k, v in globals().items()
+ if (callable(v) or isinstance(v, type)) and v.__module__ == __name__)
diff --git a/tools/proxyclient/m1n1/fw/agx/handoff.py b/tools/proxyclient/m1n1/fw/agx/handoff.py
new file mode 100644
index 0000000..4f9acf0
--- /dev/null
+++ b/tools/proxyclient/m1n1/fw/agx/handoff.py
@@ -0,0 +1,120 @@
+# SPDX-License-Identifier: MIT
+from ...utils import *
+from contextlib import contextmanager
+
+PPL_MAGIC = 0x4b1d000000000002
+
+class GFXHandoffStruct(RegMap):
+ MAGIC_AP = 0x0, Register64
+ MAGIC_FW = 0x8, Register64
+
+ LOCK_AP = 0x10, Register8
+ LOCK_FW = 0x11, Register8
+ TURN = 0x14, Register32
+
+ CUR_CTX = 0x18, Register32
+
+ FLUSH_STATE = irange(0x20, 0x41, 0x18), Register64
+ FLUSH_ADDR = irange(0x28, 0x41, 0x18), Register64
+ FLUSH_SIZE = irange(0x30, 0x41, 0x18), Register64
+
+ UNK2 = 0x638, Register8
+ UNK3 = 0x640, Register64
+
+class GFXHandoff:
+ def __init__(self, u):
+ self.u = u
+ self.sgx_dev = self.u.adt["/arm-io/sgx"]
+ self.base = self.sgx_dev.gfx_handoff_base
+ self.reg = GFXHandoffStruct(u, self.base)
+ self.is_locked = False
+ self.initialized = False
+
+ @contextmanager
+ def lock(self):
+ """Dekker's algorithm lock"""
+ assert not self.is_locked
+
+ # Note: This *absolutely* needs barriers everywhere.
+ # Those are implicit in proxyclient for every operation.
+
+ self.reg.LOCK_AP.val = 1
+ while self.reg.LOCK_FW.val != 0:
+ if self.reg.TURN != 0:
+ self.reg.LOCK_AP = 0
+ while self.reg.TURN != 0:
+ pass
+ self.reg.LOCK_AP = 1
+
+ self.is_locked = True
+ try:
+ yield
+ finally:
+ self.reg.TURN.val = 1
+ self.reg.LOCK_AP.val = 0
+ self.is_locked = False
+
+ def initialize(self):
+ if self.initialized:
+ return
+
+ print("[Handoff] Initializing...")
+
+ self.reg.MAGIC_AP.val = PPL_MAGIC
+ self.reg.UNK = 0xffffffff
+ self.reg.UNK3 = 0
+
+ with self.lock():
+ print("[Handoff] Waiting for FW PPL init...")
+ while self.reg.MAGIC_FW.val != PPL_MAGIC:
+ pass
+
+ for i in range(0x41):
+ self.reg.FLUSH_STATE[i].val = 0
+ self.reg.FLUSH_ADDR[i].val = 0
+ self.reg.FLUSH_SIZE[i].val = 0
+
+ self.initialized = True
+ print("[Handoff] Initialized!")
+
+ # The order here is:
+ # - Remap memory as shared
+ # - TLBI
+ # - prepare_cacheflush()
+ # - issue FWCtl request
+ # - wait for completion (ring or wait_cacheflush?)
+ # - Unmap memory
+ # - TLBI
+ # - complete_cacheflush()
+ def prepare_cacheflush(self, base, size, context=0x40):
+ assert self.reg.FLUSH_STATE[context].val == 0
+
+ self.reg.FLUSH_ADDR[context].val = base
+ self.reg.FLUSH_SIZE[context].val = size
+ self.reg.FLUSH_STATE[context].val = 1
+
+ def wait_cacheflush(self, context=0x40):
+ while self.reg.FLUSH_STATE[context].val == 1:
+ pass
+
+ def complete_cacheflush(self, context=0x40):
+ assert self.reg.FLUSH_STATE[context].val == 2
+ self.reg.FLUSH_STATE[context].val = 0
+
+ # probably not necessary?
+ # order is:
+ # - Remap memory as shared
+ # - (no TLBI?)
+ # - prepare_unmap()
+ # - unmap
+ # - TLBI
+ # - complete_unmap()
+ def prepare_unmap(self, base, size, context):
+ assert self.reg.FLUSH_STATE[context].val == 0
+ self.reg.FLUSH_ADDR[context].val = 0xdead000000000000 | (base & 0xffffffffffff)
+ self.reg.FLUSH_SIZE[context].val = size
+ self.reg.FLUSH_STATE[context].val = 2
+
+ def complete_unmap(self, context):
+ assert self.reg.FLUSH_STATE[context].val == 2
+ self.reg.FLUSH_STATE[context].val = 0
diff --git a/tools/proxyclient/m1n1/fw/agx/initdata.py b/tools/proxyclient/m1n1/fw/agx/initdata.py
new file mode 100644
index 0000000..ea8d4df
--- /dev/null
+++ b/tools/proxyclient/m1n1/fw/agx/initdata.py
@@ -0,0 +1,1931 @@
+from m1n1.utils import *
+from m1n1.constructutils import *
+from construct import *
+from construct.lib import hexundump
+
+from .channels import ChannelInfoSet, ChannelInfo
+
+__all__ = []
+
+class InitData_FWStatus(ConstructClass):
+ subcon = Struct(
+ "fwctl_channel" / ChannelInfo,
+ "halt_count" / Int32ul,
+ ZPadding(0xc),
+ "halted" / Int32ul,
+ ZPadding(0xc),
+ "resume" / Int32ul,
+ ZPadding(0xc),
+ "unk_40" / Int32ul,
+ ZPadding(0xc),
+ "unk_ctr" / Int32ul,
+ ZPadding(0xc),
+ "unk_60" / Int32ul,
+ ZPadding(0xc),
+ "unk_70" / Int32ul,
+ ZPadding(0xc),
+ )
+
+ def __init__(self):
+ super().__init__()
+ self.halt_count = 0
+ self.halted = 0
+ self.resume = 0
+ self.unk_40 = 0
+ self.unk_ctr = 0
+ self.unk_60 = 0
+ self.unk_70 = 0
+
+class AGXHWDataShared1(ConstructClass):
+ subcon = Struct(
+ "table" / Array(17, Int32sl),
+ "unk_44" / HexDump(Bytes(0x60)),
+ "unk_a4" / Int32ul,
+ "unk_a8" / Int32ul,
+ )
+
+ def __init__(self, chip_info):
+ super().__init__()
+ self.table = chip_info.shared1_tab
+ self.unk_44 = bytes(0x60)
+ self.unk_a4 = chip_info.shared1_a4
+ self.unk_a8 = 0
+
+class AGXHWDataShared2Curve(ConstructClass):
+ subcon = Struct(
+ "unk_0" / Int32ul,
+ "unk_4" / Int32ul,
+ "t1" / Array(16, Int16sl),
+ "t2" / Array(16, Int16sl),
+ "t3" / Array(8, Array(16, Int32sl)),
+ )
+
+ def __init__(self, unk_0=0, unk_4=0, t1=None, t2=None, t3=None):
+ self.unk_0 = unk_0
+ self.unk_4 = unk_4
+ if not t1:
+ self.t1 = [0] * 16
+ else:
+ self.t1 = t1 + [t1[0]] * (16 - len(t1))
+ if not t2:
+ self.t2 = [0] * 16
+ else:
+ self.t2 = t2 + [t2[0]] * (16 - len(t2))
+ if t3 is None:
+ self.t3 = [[0] * 16] * 8
+ else:
+ self.t3 = ([(i + [0x3ffffff] * (16 - len(i))) for i in t3] +
+ [[0x3ffffff] * 16] * (8 - len(t3)))
+
+
+class AGXHWDataShared2T8112(ConstructClass):
+ subcon = Struct(
+ "unk_0" / Array(5, Int32ul),
+ "unk_14" / Int32ul,
+ "unk_18" / Array(8, Int32ul),
+ "curve1" / AGXHWDataShared2Curve,
+ "curve2" / AGXHWDataShared2Curve,
+ )
+
+ def __init__(self, chip_info):
+ self.unk_0 = [0] * 5
+ self.unk_18 = [0] * 8
+
+ if chip_info.chip_id == 0x8112:
+ self.unk_14 = 0x6000000
+ self.curve1 = AGXHWDataShared2Curve(
+ 0, 0x20000000,
+ [-1], [0x0f07], [[]]
+ )
+ self.curve2 = AGXHWDataShared2Curve(
+ 7, 0x80000000,
+ [
+ -1, 25740, 17429, 12550, 9597, 7910, 6657, 5881, 5421
+ ], [
+ 0x0F07, 0x04C0, 0x06C0, 0x08C0,
+ 0x0AC0, 0x0C40, 0x0DC0, 0x0EC0,
+ 0x0F80
+ ], [
+ [0x03FFFFFF, 107, 101, 94, 87, 82, 77, 73, 71],
+ [0x03FFFFFF, 38240, 36251, 33562,
+ 31368, 29379, 27693, 26211, 25370],
+ [0x03FFFFFF, 123933, 117485, 108771,
+ 101661, 95217, 89751, 84948, 82222],
+ ]
+ )
+ else:
+ self.unk_14 = 0
+ self.curve1 = AGXHWDataShared2Curve()
+ self.curve2 = AGXHWDataShared2Curve()
+
+class AGXHWDataShared3(ConstructClass):
+ subcon = Struct(
+ "unk_0" / Int32ul,
+ "unk_4" / Int32ul,
+ "unk_8" / Int32ul,
+ "table" / Array(16, Int32ul),
+ "unk_4c" / Int32ul,
+ )
+
+ def __init__(self, chip_info):
+ if chip_info.chip_id == 0x8112:
+ self.unk_0 = 1
+ self.unk_4 = 500
+ self.unk_8 = 5
+ self.table = [
+ 10700, 10700, 10700, 10700,
+ 10700, 6000, 1000, 1000,
+ 1000, 10700, 10700, 10700,
+ 10700, 10700, 10700, 10700,
+ ]
+ self.unk_4c = 1
+ else:
+ self.unk_0 = 0
+ self.unk_4 = 0
+ self.unk_8 = 0
+ self.table = [0] * 16
+ self.unk_4c = 0
+
+class AGXHWDataShared2(ConstructClass):
+ subcon = Struct(
+ "table" / Array(10, Int32sl),
+ "unk_28" / HexDump(Bytes(0x10)),
+ "unk_38" / AGXHWDataShared2T8112,
+ "unk_500" / Int32ul,
+ "unk_504" / Int32ul,
+ "unk_508" / Int32ul,
+ "unk_50c" / Int32ul,
+ "unk_510" / Int32ul,
+ )
+
+ def __init__(self, chip_info):
+ super().__init__()
+ self.table = chip_info.shared2_tab
+ self.unk_20 = bytes(8)
+ self.unk_28 = b"\xff" * 16
+ self.unk_38 = AGXHWDataShared2T8112(chip_info)
+ self.unk_500 = 0
+ self.unk_504 = 0
+ self.unk_508 = chip_info.shared2_unk_508
+ self.unk_50c = 0
+ self.unk_510 = 0
+
+class AGXHWDataA130Extra(ConstructClass):
+ subcon = Struct(
+ "unk_0" / HexDump(Bytes(0x38)),
+ "unk_38" / Dec(Int32ul),
+ "unk_3c" / Dec(Int32ul),
+ "unk_40" / Dec(Int32ul),
+ "unk_44" / Int32ul,
+ "unk_48" / Int32ul,
+ "unk_4c" / Dec(Int32ul),
+ "unk_50" / Int32ul,
+ "unk_54" / Dec(Int32ul),
+ "unk_58" / Int32ul,
+ "unk_5c" / Int32ul,
+ "unk_60" / Float32l,
+ "unk_64" / Float32l,
+ "unk_68" / Float32l,
+ "unk_6c" / Float32l,
+ "unk_70" / Float32l,
+ "unk_74" / Float32l,
+ "unk_78" / Float32l,
+ "unk_7c" / Float32l,
+ "unk_80" / Float32l,
+ "unk_84" / Float32l,
+ "unk_88" / Int32ul,
+ "unk_8c" / Dec(Int32ul),
+ "unk_90" / Dec(Int32ul),
+ "unk_94" / Int32ul,
+ "unk_98" / Int32ul,
+ "unk_9c" / Float32l,
+ "unk_a0" / Dec(Int32ul),
+ "unk_a4" / Int32ul,
+ "unk_a8" / Dec(Int32ul),
+ "unk_ac" / Dec(Int32ul),
+ "unk_b0" / Dec(Int32ul),
+ "unk_b4" / Int32ul,
+ "unk_b8" / Dec(Int32ul),
+ "unk_bc" / Int32ul,
+ "unk_c0" / Int32ul,
+ "unk_c4" / Float32l,
+ "unk_c8" / HexDump(Bytes(0x4c)),
+ "unk_114" / Float32l,
+ "unk_118" / Int32ul,
+ "unk_11c" / Int32ul,
+ "unk_120" / Int32ul,
+ "unk_124" / Dec(Int32ul),
+ "unk_128" / Dec(Int32ul),
+ "unk_12c" / HexDump(Bytes(0x8c)),
+ )
+
+ def __init__(self):
+ super().__init__()
+ self.unk_0 = bytes(0x38)
+ self.unk_38 = 4
+ self.unk_3c = 8000
+ self.unk_40 = 2500
+ self.unk_44 = 0x0
+ self.unk_48 = 0xffffffff
+ self.unk_4c = 50
+ self.unk_50 = 0x0
+ self.unk_54 = 50
+ self.unk_58 = 0x1
+ self.unk_5c = 0x0
+ self.unk_60 = 0.88888888
+ self.unk_64 = 0.66666666
+ self.unk_68 = 0.111111111
+ self.unk_6c = 0.33333333
+ self.unk_70 = -0.4
+ self.unk_74 = -0.8
+ self.unk_78 = 0.0
+ self.unk_7c = 65536.0
+ self.unk_80 = -5.0
+ self.unk_84 = -10.0
+ self.unk_88 = 0x0
+ self.unk_8c = 40
+ self.unk_90 = 600
+ self.unk_94 = 0x0
+ self.unk_98 = 0x0
+ self.unk_9c = 8000.0
+ self.unk_a0 = 1400
+ self.unk_a4 = 0x0
+ self.unk_a8 = 72
+ self.unk_ac = 24
+ self.unk_b0 = 1728000
+ self.unk_b4 = 0x0
+ self.unk_b8 = 576000
+ self.unk_bc = 0x0
+ self.unk_c0 = 0x0
+ self.unk_c4 = 65536.0
+ self.unk_c8 = bytes(0x4c)
+ self.unk_114 = 65536.0
+ self.unk_118 = 0x0
+ self.unk_11c = 0x0
+ self.unk_120 = 0x0
+ self.unk_124 = 40
+ self.unk_128 = 600
+ self.unk_12c = bytes(0x8c)
+
+class AGXHWDataT81xx(ConstructClass):
+ subcon = Struct(
+ "unk_d8c" / Int32ul,
+ "unk_d90" / Int32ul,
+ "unk_d94" / Int32ul,
+ "unk_d98" / Int32ul,
+ "unk_d9c" / Float32l,
+ "unk_da0" / Int32ul,
+ "unk_da4" / Float32l,
+ "unk_da8" / Int32ul,
+ "unk_dac" / Float32l,
+ "unk_db0" / Int32ul,
+ "unk_db4" / Int32ul,
+ "unk_db8" / Float32l,
+ "unk_dbc" / Float32l,
+ "unk_dc0" / Int32ul,
+ "unk_dc4" / Int32ul,
+ "unk_dc8" / Int32ul,
+ "unk_dcc" / Int32ul,
+ )
+ def __init__(self, sgx, chip_info):
+ if chip_info.chip_id in (0x8103, 0x8112):
+ self.unk_d8c = 0x80000000
+ self.unk_d90 = 4
+ self.unk_d94 = 0
+ self.unk_d98 = 0
+ self.unk_d9c = 0.6
+ self.unk_da0 = 0
+ self.unk_da4 = 0.4
+ self.unk_da8 = 0
+ self.unk_dac = 0.38552
+ self.unk_db0 = 0
+ self.unk_db4 = 0
+ self.unk_db8 = 65536.0
+ self.unk_dbc = 13.56
+ self.unk_dc0 = 0
+ self.unk_dc4 = 0
+ self.unk_dc8 = 0
+ self.unk_dcc = 100 * sgx.gpu_num_perf_states
+ else:
+ self.unk_d8c = 0
+ self.unk_d90 = 0
+ self.unk_d94 = 0
+ self.unk_d98 = 0
+ self.unk_d9c = 0
+ self.unk_da0 = 0
+ self.unk_da4 = 0
+ self.unk_da8 = 0
+ self.unk_dac = 0
+ self.unk_db0 = 0
+ self.unk_db4 = 0
+ self.unk_db8 = 0
+ self.unk_dbc = 0
+ self.unk_dc0 = 0
+ self.unk_dc4 = 0
+ self.unk_dc8 = 0
+ self.unk_dcc = 0
+
+class PowerZone(ConstructClass):
+ subcon = Struct(
+ "val" / Float32l,
+ "target" / Dec(Int32ul),
+ "target_off" / Dec(Int32ul),
+ "filter_tc_x4" / Dec(Int32ul),
+ "filter_tc_xperiod" / Dec(Int32ul),
+ Ver("V >= V13_0B4", "unk_10" / Dec(Int32ul)),
+ Ver("V >= V13_0B4", "unk_14" / Dec(Int32ul)),
+ "filter_tc_neginv" / Float32l,
+ "filter_tc_inv" / Float32l,
+ "pad" / Int32ul,
+ )
+ def __init__(self, tc=None, target=None, off=None, period_ms=None):
+ self.val = 0.0
+ self.pad = 0
+ if tc is None:
+ self.target = 0
+ self.target_off = 0
+ self.filter_tc_x4 = 0
+ self.filter_tc_xperiod = 0
+ self.unk_10 = 0
+ self.unk_14 = 0
+ self.filter_tc_neginv = 0
+ self.filter_tc_inv = 0
+ else:
+ self.target = target
+ self.target_off = self.target - off
+ self.filter_tc_x4 = tc * 4
+ self.filter_tc_xperiod = tc * period_ms
+ self.unk_10 = 1320000000
+ self.unk_14 = 0
+ self.filter_tc_neginv = 1 / tc
+ self.filter_tc_inv = 1 - 1 / tc
+
+class AGXHWDataA(ConstructClass):
+ subcon = Struct(
+ "unk_0" / Int32ul,
+ "clocks_per_period" / Int32ul,
+ Ver("V >= V13_0B4", "clocks_per_period_2" / Int32ul),
+ "unk_8" / Int32ul,
+ "pwr_status" / Int32ul,
+ "unk_10" / Float32l,
+ "unk_14" / Int32ul,
+ "unk_18" / Int32ul,
+ "unk_1c" / Int32ul,
+ "unk_20" / Int32ul,
+ "unk_24" / Int32ul,
+ "actual_pstate" / Int32ul,
+ "tgt_pstate" / Int32ul,
+ "unk_30" / Int32ul,
+ "cur_pstate" / Int32ul,
+ "unk_38" / Int32ul,
+ Ver("V >= V13_0B4", "unk_3c_0" / Int32ul),
+ "base_pstate_scaled" / Int32ul,
+ "unk_40" / Int32ul,
+ "max_pstate_scaled" / Int32ul,
+ "unk_48" / Int32ul,
+ "min_pstate_scaled" / Int32ul,
+ "freq_mhz" / Float32l,
+ "unk_54" / HexDump(Bytes(0x20)),
+ Ver("V >= V13_0B4", "unk_74_0" / Int32ul),
+ "unk_74" / Array(16, Float32l),
+ "unk_b4" / HexDump(Bytes(0x100)),
+ "unk_1b4" / Int32ul,
+ "temp_c" / Int32ul,
+ "avg_power_mw" / Dec(Int32ul),
+ "update_ts" / Int64ul,
+ "unk_1c8" / Int32ul,
+ "unk_1cc" / HexDump(Bytes(0x644 - 0x1cc)),
+ "pad_644" / HexDump(Bytes(8)),
+
+ "unk_64c" / Int32ul,
+ "unk_650" / Int32ul,
+ "pad_654" / Int32ul,
+ "pwr_filter_a_neg" / Float32l,
+ "pad_65c" / Int32ul,
+ "pwr_filter_a" / Float32l,
+ "pad_664" / Int32ul,
+ "pwr_integral_gain" / Float32l,
+ "pad_66c" / Int32ul,
+ "pwr_integral_min_clamp" / Float32l,
+ "max_power_1" / Float32l,
+ "pwr_proportional_gain" / Float32l,
+ "pad_67c" / Int32ul,
+ "pwr_pstate_related_k" / Float32l,
+ "pwr_pstate_max_dc_offset" / Int32sl,
+ "unk_688" / Int32ul,
+ "max_pstate_scaled_2" / Int32ul,
+ "pad_690" / Int32ul,
+ "unk_694" / Int32ul,
+ "max_power_2" / Int32ul,
+
+ "pad_69c" / HexDump(Bytes(0x18)),
+
+ "unk_6b4" / Int32ul,
+ Ver("V >= V13_0B4", "unk_6b8_0" / HexDump(Bytes(0x10))),
+ "max_pstate_scaled_3" / Int32ul,
+ "unk_6bc" / Int32ul,
+
+ "pad_6c0" / HexDump(Bytes(0x14)),
+
+ "ppm_filter_tc_periods_x4" / Int32ul,
+ "unk_6d8" / Int32ul,
+
+ "pad_6dc" / Int32ul,
+
+ "ppm_filter_a_neg" / Float32l,
+ "pad_6e4" / Int32ul,
+ "ppm_filter_a" / Float32l,
+ "pad_6ec" / Int32ul,
+ "ppm_ki_dt" / Float32l,
+ "pad_6f4" / Int32ul,
+ "pwr_integral_min_clamp_2" / Int32ul,
+ "unk_6fc" / Float32l,
+ "ppm_kp" / Float32l,
+ "pad_704" / Int32ul,
+
+ "unk_708" / Int32ul,
+ "pwr_min_duty_cycle" / Int32ul,
+ "max_pstate_scaled_4" / Int32ul,
+ "unk_714" / Int32ul,
+
+ "pad_718" / Int32ul,
+
+ "unk_71c" / Float32l,
+ "max_power_3" / Int32ul,
+
+ "cur_power_mw_2" / Int32ul,
+
+ "ppm_filter_tc_ms" / Int32ul,
+ "unk_72c" / Int32ul,
+ Ver("V >= V13_0B4", "unk_730_0" / Int32ul),
+ Ver("V >= V13_0B4", "unk_730_4" / Int32ul),
+ Ver("V >= V13_0B4", "unk_730_8" / Int32ul),
+ Ver("V >= V13_0B4", "unk_730_c" / Int32ul),
+ "unk_730" / Float32l,
+ "unk_734" / Int32ul,
+
+ "unk_738" / Int32ul,
+ "unk_73c" / Int32ul,
+ "unk_740" / Int32ul,
+ "unk_744" / Int32ul,
+ "unk_748" / Array(4, Float32l),
+ "unk_758" / Int32ul,
+ "perf_tgt_utilization" / Int32ul,
+ "pad_760" / Int32ul,
+ "perf_boost_min_util" / Int32ul,
+ "perf_boost_ce_step" / Int32ul,
+ "perf_reset_iters" / Int32ul,
+ "pad_770" / Int32ul,
+ "unk_774" / Int32ul,
+ "unk_778" / Int32ul,
+ "perf_filter_drop_threshold" / Int32ul,
+
+ "perf_filter_a_neg" / Float32l,
+ "perf_filter_a2_neg" / Float32l,
+ "perf_filter_a" / Float32l,
+ "perf_filter_a2" / Float32l,
+ "perf_ki" / Float32l,
+ "perf_ki2" / Float32l,
+ "perf_integral_min_clamp" / Float32l,
+ "unk_79c" / Float32l,
+ "perf_kp" / Float32l,
+ "perf_kp2" / Float32l,
+ "boost_state_unk_k" / Float32l,
+
+ "base_pstate_scaled_2" / Dec(Int32ul),
+ "max_pstate_scaled_5" / Dec(Int32ul),
+ "base_pstate_scaled_3" / Dec(Int32ul),
+
+ "pad_7b8" / Int32ul,
+
+ "perf_cur_utilization" / Float32l,
+ "perf_tgt_utilization_2" / Int32ul,
+
+ "pad_7c4" / HexDump(Bytes(0x18)),
+
+ "unk_7dc" / Int32ul,
+ Ver("V >= V13_0B4", "unk_7e0_0" / HexDump(Bytes(0x10))),
+ "base_pstate_scaled_4" / Dec(Int32ul),
+ "pad_7e4" / Int32ul,
+
+ "unk_7e8" / HexDump(Bytes(0x14)),
+
+ "unk_7fc" / Float32l,
+ "pwr_min_duty_cycle_2" / Float32l,
+ "max_pstate_scaled_6" / Float32l,
+ "max_freq_mhz" / Int32ul,
+ "pad_80c" / Int32ul,
+ "unk_810" / Int32ul,
+ "pad_814" / Int32ul,
+ "pwr_min_duty_cycle_3" / Int32ul,
+ "unk_81c" / Int32ul,
+ "pad_820" / Int32ul,
+ "min_pstate_scaled_4" / Float32l,
+ "max_pstate_scaled_7" / Dec(Int32ul),
+ "unk_82c" / Int32ul,
+ "unk_alpha_neg" / Float32l,
+ "unk_alpha" / Float32l,
+ "unk_838" / Int32ul,
+ "unk_83c" / Int32ul,
+ "pad_840" / HexDump(Bytes(0x86c - 0x838 - 8)),
+
+ "unk_86c" / Int32ul,
+ "fast_die0_sensor_mask64" / Int64ul,
+ "fast_die0_release_temp_cc" / Int32ul,
+ "unk_87c" / Int32sl,
+ "unk_880" / Int32ul,
+ "unk_884" / Int32ul,
+ "pad_888" / Int32ul,
+ "unk_88c" / Int32ul,
+ "pad_890" / Int32ul,
+ "unk_894" / Float32l,
+ "pad_898" / Int32ul,
+ "fast_die0_ki_dt" / Float32l,
+ "pad_8a0" / Int32ul,
+ "unk_8a4" / Int32ul,
+ "unk_8a8" / Float32l,
+ "fast_die0_kp" / Float32l,
+ "pad_8b0" / Int32ul,
+ "unk_8b4" / Int32ul,
+ "pwr_min_duty_cycle_4" / Int32ul,
+ "max_pstate_scaled_8" / Dec(Int32ul),
+ "max_pstate_scaled_9" / Dec(Int32ul),
+ "fast_die0_prop_tgt_delta" / Int32ul,
+ "unk_8c8" / Int32ul,
+ "unk_8cc" / Int32ul,
+ "pad_8d0" / HexDump(Bytes(0x14)),
+ Ver("V >= V13_0B4", "unk_8e4_0" / HexDump(Bytes(0x10))),
+ "unk_8e4" / Int32ul,
+ "unk_8e8" / Int32ul,
+ "max_pstate_scaled_10" / Dec(Int32ul),
+ "unk_8f0" / Int32ul,
+ "unk_8f4" / Int32ul,
+ "pad_8f8" / Int32ul,
+ "pad_8fc" / Int32ul,
+ "unk_900" / HexDump(Bytes(0x24)),
+ "unk_924" / Array(8, Array(8, Float32l)),
+ "unk_a24" / Array(8, Array(8, Float32l)),
+ "unk_b24" / HexDump(Bytes(0x70)),
+ "max_pstate_scaled_11" / Dec(Int32ul),
+ "freq_with_off" / Int32ul,
+ "unk_b9c" / Int32ul,
+ "unk_ba0" / Int64ul,
+ "unk_ba8" / Int64ul,
+ "unk_bb0" / Int32ul,
+ "unk_bb4" / Int32ul,
+ "pad_bb8" / HexDump(Bytes(0xc2c - 0xbb8)),
+
+ "unk_c2c" / Int32ul,
+ "power_zone_count" / Int32ul,
+ "max_power_4" / Int32ul,
+ "max_power_5" / Int32ul,
+ "max_power_6" / Int32ul,
+ "unk_c40" / Int32ul,
+ "unk_c44" / Float32l,
+ "avg_power_target_filter_a_neg" / Float32l,
+ "avg_power_target_filter_a" / Float32l,
+ "avg_power_target_filter_tc_x4" / Dec(Int32ul),
+ "avg_power_target_filter_tc_xperiod" / Dec(Int32ul),
+ Ver("V >= V13_0B4", "base_clock_mhz" / Int32ul),
+ Ver("V >= V13_0B4", "unk_c58_4" / Int32ul),
+ "power_zones" / Array(5, PowerZone),
+ "avg_power_filter_tc_periods_x4" / Dec(Int32ul),
+ "unk_cfc" / Int32ul,
+ "unk_d00" / Int32ul,
+ "avg_power_filter_a_neg" / Float32l,
+ "unk_d08" / Int32ul,
+ "avg_power_filter_a" / Float32l,
+ "unk_d10" / Int32ul,
+ "avg_power_ki_dt" / Float32l,
+ "unk_d18" / Int32ul,
+ "unk_d1c" / Int32ul,
+ "unk_d20" / Float32l,
+ "avg_power_kp" / Float32l,
+ "unk_d28" / Int32ul,
+ "unk_d2c" / Int32ul,
+ "avg_power_min_duty_cycle" / Int32ul,
+ "max_pstate_scaled_12" / Int32ul,
+ "max_pstate_scaled_13" / Int32ul,
+ "unk_d3c" / Int32ul,
+ "max_power_7" / Float32l,
+ "max_power_8" / Int32ul,
+ "unk_d48" / Int32ul,
+ "unk_d4c" / Int32ul,
+ "unk_d50" / Int32ul,
+ Ver("V >= V13_0B4", "base_clock_mhz_2" / Int32ul),
+ Ver("V >= V13_0B4", "unk_d54_4" / HexDump(Bytes(0xc))),
+ "unk_d54" / HexDump(Bytes(0x10)),
+ "max_pstate_scaled_14" / Int32ul,
+ "unk_d68" / Bytes(0x24),
+
+ "t81xx_data" / AGXHWDataT81xx,
+
+ "unk_dd0" / HexDump(Bytes(0x40)),
+ Ver("V >= V13_0B4", "unk_e10_0" / AGXHWDataA130Extra),
+ "unk_e10" / HexDump(Bytes(0xc)),
+ "fast_die0_sensor_mask64_2" / Int64ul,
+ "unk_e24" / Int32ul,
+ "unk_e28" / Int32ul,
+ "unk_e2c" / HexDump(Bytes(0x1c)),
+ "unk_e48" / Array(8, Array(8, Float32l)),
+ "unk_f48" / Array(8, Array(8, Float32l)),
+ "pad_1048" / HexDump(Bytes(0x5e4)),
+ "fast_die0_sensor_mask64_alt" / Int64ul,
+ "fast_die0_sensor_present" / Int32ul,
+ Ver("V < V13_0B4", "unk_1638" / Array(2, Int32ul)),
+ "unk_1640" / HexDump(Bytes(0x2000)),
+ "unk_3640" / Int32ul,
+ "hws1" / AGXHWDataShared1,
+ Ver("V >= V13_0B4", "unk_pad1" / HexDump(Bytes(0x20))),
+ "hws2" / AGXHWDataShared2,
+ "unk_3c04" / Int32ul,
+ "hws3" / AGXHWDataShared3,
+ "unk_3c58" / HexDump(Bytes(0x3c)),
+ "unk_3c94" / Int32ul,
+ "unk_3c98" / Int64ul,
+ "unk_3ca0" / Int64ul,
+ "unk_3ca8" / Int64ul,
+ "unk_3cb0" / Int64ul,
+ "ts_last_idle" / Int64ul,
+ "ts_last_poweron" / Int64ul,
+ "ts_last_poweroff" / Int64ul,
+ "unk_3cd0" / Int64ul,
+ "unk_3cd8" / Int64ul,
+ Ver("V >= V13_0B4", "unk_3ce0_0" / Int32ul),
+ "unk_3ce0" / Int32ul,
+ "unk_3ce4" / Int32ul,
+ "unk_3ce8" / Int32ul,
+ "unk_3cec" / Int32ul,
+ "unk_3cf0" / Int32ul,
+ "unk_3cf4" / Array(8, Float32l),
+ "unk_3d14" / Array(8, Float32l),
+ "unk_3d34" / HexDump(Bytes(0x38)),
+ Ver("V >= V13_0B4", "unk_3d6c" / HexDump(Bytes(0x38))),
+ )
+
+ def __init__(self, sgx, chip_info):
+ super().__init__()
+
+ base_clock_khz = 24000
+ base_clock_mhz = base_clock_khz * 1000
+ period_ms = sgx.gpu_power_sample_period
+
+ self.unk_0 = 0
+ self.clocks_per_period = base_clock_khz * period_ms
+ self.clocks_per_period_2 = base_clock_khz * period_ms
+ self.unk_8 = 0
+ self.pwr_status = 4
+ self.unk_10 = 1.0
+ self.unk_14 = 0
+ self.unk_18 = 0
+ self.unk_1c = 0
+ self.unk_20 = 0
+ self.unk_24 = 0
+ self.actual_pstate = 1
+ self.tgt_pstate = 1
+ self.unk_30 = 0
+ self.cur_pstate = 0
+ self.unk_38 = 0
+ self.unk_3c_0 = 0
+ self.base_pstate_scaled = 100 * sgx.getprop("gpu-perf-base-pstate", 3)
+ self.unk_40 = 1
+ self.max_pstate_scaled = 100 * sgx.gpu_num_perf_states
+ self.unk_48 = 0
+ self.min_pstate_scaled = 100
+ self.freq_mhz = 0.0
+ self.unk_54 = bytes(0x20)
+ self.unk_74_0 = 0
+ # perf related
+ self.unk_74 = [0] * 16
+
+ self.unk_b4 = bytes(0x100)
+ self.unk_1b4 = 0
+ self.temp_c = 0
+ self.avg_power_mw = 0
+ self.update_ts = 0
+ self.unk_1c8 = 0
+ self.unk_1cc = bytes(0x644 - 0x1cc)
+
+ self.pad_644 = bytes(8)
+
+ self.unk_64c = 625
+ self.unk_650 = 0
+ self.pad_654 = 0
+ self.pwr_filter_a_neg = 1 - 1 / sgx.getprop("gpu-pwr-filter-time-constant", 313)
+ self.pad_65c = 0
+ self.pwr_filter_a = 1 - self.pwr_filter_a_neg
+ self.pad_664 = 0
+ self.pwr_integral_gain = sgx.getprop("gpu-pwr-integral-gain", 0.0202129)
+ self.pad_66c = 0
+ self.pwr_integral_min_clamp = sgx.getprop("gpu-pwr-integral-min-clamp", 0)
+ self.max_power_1 = chip_info.max_power
+ self.pwr_proportional_gain = sgx.getprop("gpu-pwr-proportional-gain", 5.2831855)
+ self.pad_67c = 0
+ self.pwr_pstate_related_k = -self.max_pstate_scaled / chip_info.max_power
+ self.pwr_pstate_max_dc_offset = sgx.gpu_pwr_min_duty_cycle - self.max_pstate_scaled
+ self.unk_688 = 0
+ self.max_pstate_scaled_2 = self.max_pstate_scaled
+ self.pad_690 = 0
+ self.unk_694 = 0
+ self.max_power_2 = chip_info.max_power
+ self.pad_69c = bytes(0x18)
+ self.unk_6b4 = 0
+ self.unk_6b8_0 = bytes(0x10)
+ self.max_pstate_scaled_3 = self.max_pstate_scaled
+ self.unk_6bc = 0
+ self.pad_6c0 = bytes(0x14)
+
+ # Note: integer rounding here
+ ppm_filter_tc_periods = sgx.gpu_ppm_filter_time_constant_ms // period_ms
+ self.ppm_filter_tc_periods_x4 = ppm_filter_tc_periods * 4
+ self.unk_6d8 = 0
+ self.pad_6dc = 0
+ self.ppm_filter_a_neg = 1 - 1 / ppm_filter_tc_periods
+ self.pad_6e4 = 0
+ self.ppm_filter_a = 1 - self.ppm_filter_a_neg
+ self.pad_6ec = 0
+ self.ppm_ki_dt = sgx.gpu_ppm_ki * (period_ms / 1000)
+ self.pad_6f4 = 0
+ self.pwr_integral_min_clamp_2 = self.pwr_integral_min_clamp
+ if Ver.check("V >= V13_0B4") or chip_info.chip_id != 0x8103:
+ self.unk_6fc = 65536.0
+ else:
+ self.unk_6fc = 0
+ self.ppm_kp = sgx.gpu_ppm_kp
+ self.pad_704 = 0
+ self.unk_708 = 0
+ self.pwr_min_duty_cycle = sgx.gpu_pwr_min_duty_cycle
+ self.max_pstate_scaled_4 = self.max_pstate_scaled
+ self.unk_714 = 0
+ self.pad_718 = 0
+ self.unk_71c = 0.0
+ self.max_power_3 = chip_info.max_power
+ self.cur_power_mw_2 = 0x0
+ self.ppm_filter_tc_ms = sgx.gpu_ppm_filter_time_constant_ms
+ self.unk_72c = 0
+ self.unk_730_0 = 0x232800
+ self.unk_730_4 = 0
+ self.unk_730_8 = 0
+ self.unk_730_c = 0
+ self.unk_730 = 0.0
+ self.unk_734 = 0
+ self.unk_738 = 0
+ self.unk_73c = 0
+ self.unk_740 = 0
+ self.unk_744 = 0
+ self.unk_748 = [0.0, 0.0, 0.0, 0.0]
+ self.unk_758 = 0
+ self.perf_tgt_utilization = sgx.gpu_perf_tgt_utilization
+ self.pad_760 = 0
+ self.perf_boost_min_util = sgx.getprop("gpu-perf-boost-min-util", 100)
+
+ self.perf_boost_ce_step = sgx.getprop("gpu-perf-boost-ce-step", 25)
+ self.perf_reset_iters = sgx.getprop("gpu-perf-reset-iters", 6)
+ self.pad_770 = 0x0
+ self.unk_774 = 6
+ self.unk_778 = 1
+ self.perf_filter_drop_threshold = sgx.gpu_perf_filter_drop_threshold
+ self.perf_filter_a_neg = 1 - 1 / sgx.gpu_perf_filter_time_constant
+ self.perf_filter_a2_neg = 1 - 1 / sgx.gpu_perf_filter_time_constant2
+ self.perf_filter_a = 1 - self.perf_filter_a_neg
+ self.perf_filter_a2 = 1 - self.perf_filter_a2_neg
+ self.perf_ki = sgx.getprop("gpu-perf-integral-gain", 7.895683288574219)
+ self.perf_ki2 = sgx.gpu_perf_integral_gain2
+ self.perf_integral_min_clamp = sgx.gpu_perf_integral_min_clamp
+ self.unk_79c = 95.0
+ self.perf_kp = sgx.getprop("gpu-perf-proportional-gain", 14.707962989807129)
+ self.perf_kp2 = sgx.gpu_perf_proportional_gain2
+ base_state = sgx.getprop("gpu-perf-base-pstate", 3)
+ max_state = sgx.gpu_num_perf_states
+ boost_states = max_state - base_state
+ self.boost_state_unk_k = boost_states / 0.95
+ self.base_pstate_scaled_2 = 100 * sgx.getprop("gpu-perf-base-pstate", 3)
+ self.max_pstate_scaled_5 = self.max_pstate_scaled
+ self.base_pstate_scaled_3 = 100 * sgx.getprop("gpu-perf-base-pstate", 3)
+ self.pad_7b8 = 0x0
+ self.perf_cur_utilization = 0.0
+ self.perf_tgt_utilization_2 = sgx.gpu_perf_tgt_utilization
+ self.pad_7c4 = bytes(0x18)
+ self.unk_7dc = 0x0
+ self.unk_7e0_0 = bytes(0x10)
+ self.base_pstate_scaled_4 = 100 * sgx.getprop("gpu-perf-base-pstate", 3)
+ self.pad_7e4 = 0x0
+ self.unk_7e8 = bytes(0x14)
+ self.unk_7fc = 65536.0
+ self.pwr_min_duty_cycle_2 = sgx.gpu_pwr_min_duty_cycle
+ self.max_pstate_scaled_6 = self.max_pstate_scaled
+ self.max_freq_mhz = sgx.perf_states[sgx.gpu_num_perf_states].freq // 1000000
+ self.pad_80c = 0x0
+ self.unk_810 = 0x0
+ self.pad_814 = 0x0
+ self.pwr_min_duty_cycle_3 = sgx.gpu_pwr_min_duty_cycle
+ self.unk_81c = 0x0
+ self.pad_820 = 0x0
+ self.min_pstate_scaled_4 = 100.0
+ self.max_pstate_scaled_7 = self.max_pstate_scaled
+ self.unk_82c = 0x0
+ self.unk_alpha_neg = 0.8
+ self.unk_alpha = 1 - self.unk_alpha_neg
+ self.unk_838 = 0x0
+ self.unk_83c = 0x0
+ self.pad_840 = bytes(0x2c)
+ self.unk_86c = 0x0
+ self.fast_die0_sensor_mask64 = chip_info.gpu_fast_die0_sensor_mask64
+ self.fast_die0_release_temp_cc = 100 * sgx.getprop("gpu-fast-die0-release-temp", 80)
+ self.unk_87c = chip_info.unk_87c
+ self.unk_880 = 0x4
+ self.unk_884 = 0x0
+ self.pad_888 = 0x0
+ self.unk_88c = 0x0
+ self.pad_890 = 0x0
+ self.unk_894 = 1.0
+ self.pad_898 = 0x0
+ self.fast_die0_ki_dt = sgx.gpu_fast_die0_integral_gain * (period_ms / 1000)
+ self.pad_8a0 = 0x0
+ self.unk_8a4 = 0x0
+ self.unk_8a8 = 65536.0
+ self.fast_die0_kp = sgx.gpu_fast_die0_proportional_gain
+ self.pad_8b0 = 0x0
+ self.unk_8b4 = 0x0
+ self.pwr_min_duty_cycle_4 = sgx.gpu_pwr_min_duty_cycle
+ self.max_pstate_scaled_8 = self.max_pstate_scaled
+ self.max_pstate_scaled_9 = self.max_pstate_scaled
+ self.fast_die0_prop_tgt_delta = 100 * sgx.getprop("gpu-fast-die0-prop-tgt-delta", 0)
+ self.unk_8c8 = 0
+ self.unk_8cc = chip_info.unk_8cc
+ self.pad_8d0 = bytes(0x14)
+ self.unk_8e4_0 = bytes(0x10)
+ self.unk_8e4 = 0
+ self.unk_8e8 = 0
+ self.max_pstate_scaled_10 = self.max_pstate_scaled
+ self.unk_8f0 = 0
+ self.unk_8f4 = 0
+ self.pad_8f8 = 0
+ self.pad_8fc = 0
+ self.unk_900 = bytes(0x24)
+ self.unk_924 = chip_info.unk_924
+ self.unk_a24 = chip_info.unk_924
+ self.unk_b24 = bytes(0x70)
+ self.max_pstate_scaled_11 = self.max_pstate_scaled
+ self.freq_with_off = 0x0
+ self.unk_b9c = 0
+ self.unk_ba0 = 0
+ self.unk_ba8 = 0
+ self.unk_bb0 = 0
+ self.unk_bb4 = 0
+ self.pad_bb8 = bytes(0x74)
+ self.unk_c2c = 1
+
+ self.power_zones = [PowerZone()] * 5
+ power_zone_count = 0
+ for i in range(5):
+ if sgx.getprop(f"gpu-power-zone-target-{i}", None) is None:
+ break
+ self.power_zones[i] = PowerZone(
+ sgx.getprop(f"gpu-power-zone-filter-tc-{i}", None),
+ sgx.getprop(f"gpu-power-zone-target-{i}", None),
+ sgx.getprop(f"gpu-power-zone-target-offset-{i}", None),
+ period_ms
+ )
+ power_zone_count += 1
+
+ self.power_zone_count = power_zone_count
+ self.max_power_4 = chip_info.max_power
+ self.max_power_5 = chip_info.max_power
+ self.max_power_6 = chip_info.max_power
+ self.unk_c40 = 0
+ self.unk_c44 = 0.0
+ self.avg_power_target_filter_a_neg = 1 - 1 / sgx.gpu_avg_power_target_filter_tc
+ self.avg_power_target_filter_a = 1 / sgx.gpu_avg_power_target_filter_tc
+ self.avg_power_target_filter_tc_x4 = 4 * sgx.gpu_avg_power_target_filter_tc
+ self.avg_power_target_filter_tc_xperiod = period_ms * sgx.gpu_avg_power_target_filter_tc
+ self.base_clock_mhz = base_clock_mhz
+ self.unk_c58_4 = 0
+
+ # Note: integer rounding
+ avg_power_filter_tc_periods = sgx.gpu_avg_power_filter_tc_ms // period_ms
+ self.avg_power_filter_tc_periods_x4 = avg_power_filter_tc_periods * 4
+ self.unk_cfc = 0
+ self.unk_d00 = 0
+ self.avg_power_filter_a_neg = 1 - 1 / avg_power_filter_tc_periods
+ self.unk_d08 = 0
+ self.avg_power_filter_a = 1 - self.avg_power_filter_a_neg
+ self.unk_d10 = 0
+ self.avg_power_ki_dt = sgx.gpu_avg_power_ki_only * (period_ms / 1000)
+ self.unk_d18 = 0
+ self.unk_d1c = 0
+ self.unk_d20 = 65536.0
+ self.avg_power_kp = sgx.gpu_avg_power_kp
+ self.unk_d28 = 0
+ self.unk_d2c = 0
+ self.avg_power_min_duty_cycle = sgx.gpu_avg_power_min_duty_cycle
+ self.max_pstate_scaled_12 = self.max_pstate_scaled
+ self.max_pstate_scaled_13 = self.max_pstate_scaled
+ self.unk_d3c = 0
+ self.max_power_7 = chip_info.max_power
+ self.max_power_8 = chip_info.max_power
+ self.unk_d48 = 0
+ self.unk_d4c = sgx.gpu_avg_power_filter_tc_ms
+ self.unk_d50 = 0
+ self.base_clock_mhz_2 = base_clock_mhz
+ self.unk_d54_4 = bytes(0xc)
+ self.unk_d54 = bytes(0x10)
+ self.max_pstate_scaled_14 = self.max_pstate_scaled
+ self.unk_d68 = bytes(0x24)
+
+ self.t81xx_data = AGXHWDataT81xx(sgx, chip_info)
+
+ self.unk_dd0 = bytes(0x40)
+
+ self.unk_e10_0 = AGXHWDataA130Extra()
+ self.unk_e10 = bytes(0xc)
+ self.fast_die0_sensor_mask64_2 = chip_info.gpu_fast_die0_sensor_mask64
+ self.unk_e24 = chip_info.unk_e24
+ self.unk_e28 = 1
+ self.unk_e2c = bytes(0x1c)
+ self.unk_e48 = chip_info.unk_e48
+ self.unk_f48 = chip_info.unk_e48
+ self.pad_1048 = bytes(0x5e4)
+ self.fast_die0_sensor_mask64_alt = chip_info.gpu_fast_die0_sensor_mask64_alt
+ self.fast_die0_sensor_present = chip_info.gpu_fast_die0_sensor_present
+ self.unk_1638 = [0, 1]
+ self.unk_1640 = bytes(0x2000)
+ self.unk_3640 = 0
+ self.hws1 = AGXHWDataShared1(chip_info)
+ self.unk_pad1 = bytes(0x20)
+ self.hws2 = AGXHWDataShared2(chip_info)
+ self.unk_3c04 = 0
+ self.hws3 = AGXHWDataShared3(chip_info)
+ self.unk_3c58 = bytes(0x3c)
+ self.unk_3c94 = 0 # flag
+ self.unk_3c98 = 0 # timestamp?
+ self.unk_3ca0 = 0 # timestamp?
+ self.unk_3ca8 = 0
+ self.unk_3cb0 = 0
+ self.ts_last_idle = 0
+ self.ts_last_poweron = 0
+ self.ts_last_poweroff = 0
+ self.unk_3cd0 = 0
+ self.unk_3cd8 = 0
+ self.unk_3ce0_0 = 0
+
+ self.unk_3ce0 = 0
+ self.unk_3ce4 = 0
+ self.unk_3ce8 = 1
+ self.unk_3cec = 0
+ self.unk_3cf0 = 0
+ self.unk_3cf4 = chip_info.unk_3cf4
+ self.unk_3d14 = chip_info.unk_3d14
+ self.unk_3d34 = bytes(0x38)
+ self.unk_3d6c = bytes(0x38)
+
+class IOMapping(ConstructClass):
+ _MAPTYPE = {
+ 0: "RO",
+ 1: "RW",
+ }
+
+ subcon = Struct(
+ "phys_addr" / Int64ul,
+ "virt_addr" / Int64ul,
+ "size" / Int32ul,
+ "range_size" / Int32ul, # Useally the same as size, but for MCC, this is the size of a single MMC register range.
+ "readwrite" / Int64ul
+ )
+
+ def __init__(self, phys=0, addr=0, size=0, range_size=0, readwrite=0):
+ self.phys_addr = phys
+ self.virt_addr = addr
+ self.size = size
+ self.range_size = range_size
+ self.readwrite = readwrite
+
+ def __str__(self):
+ if self.virt_addr == 0:
+ return "\n<IOMapping: Invalid>"
+
+ try:
+ hv = self._stream.uat.hv
+ except AttributeError:
+ hv = None
+
+ if hv:
+ dev, range = hv.device_addr_tbl.lookup(self.phys_addr)
+ offset = self.phys_addr - range.start
+ return f"\nIO Mapping: {self._MAPTYPE.get(self.readwrite, self.readwrite)} {self.virt_addr:#x} -> " \
+ f"{dev}+{offset:#x} = {self.phys_addr:#x} ({self.size:#x} / {self.range_size:#x})"
+ else:
+ return f"\nIO Mapping: {self._MAPTYPE.get(self.readwrite, self.readwrite)} {self.virt_addr:#x} -> " \
+ f"{self.phys_addr:#x} = {self.phys_addr:#x} ({self.size:#x} / {self.range_size:#x})"
+
+
+class AGXHWDataB(ConstructClass):
+ subcon = Struct(
+ Ver("V < V13_0B4", "unk_0" / Int64ul),
+ "unk_8" / Int64ul,
+ Ver("V < V13_0B4", "unk_10" / Int64ul),
+ "unk_18" / Int64ul,
+ "unk_20" / Int64ul,
+ "unk_28" / Int64ul,
+ "unk_30" / Int64ul,
+ "unkptr_38" / Int64ul,
+ "pad_40" / HexDump(Bytes(0x20)),
+ Ver("V < V13_0B4", "yuv_matrices" / Array(15, Array(3, Array(4, Int16sl)))),
+ Ver("V >= V13_0B4", "yuv_matrices" / Array(63, Array(3, Array(4, Int16sl)))),
+ "pad_1c8" / HexDump(Bytes(8)),
+ "io_mappings" / Array(0x14, IOMapping),
+ Ver("V >= V13_0B4", "unk_450_0" / HexDump(Bytes(0x68))),
+ "chip_id" / Int32ul,
+ "unk_454" / Int32ul,
+ "unk_458" / Int32ul,
+ "unk_45c" / Int32ul,
+ "unk_460" / Int32ul,
+ "unk_464" / Int32ul,
+ "unk_468" / Int32ul,
+ "unk_46c" / Int32ul,
+ "unk_470" / Int32ul,
+ "unk_474" / Int32ul,
+ "unk_478" / Int32ul,
+ "unk_47c" / Int32ul,
+ "unk_480" / Int32ul,
+ "unk_484" / Int32ul,
+ "unk_488" / Int32ul,
+ "unk_48c" / Int32ul,
+ "base_clock_khz" / Int32ul,
+ "power_sample_period" / Int32ul,
+ "pad_498" / ZPadding(4),
+
+ "unk_49c" / Int32ul,
+ "unk_4a0" / Int32ul,
+ "unk_4a4" / Int32ul,
+ "pad_4a8" / ZPadding(4),
+
+ "unk_4ac" / Int32ul,
+ "pad_4b0" / ZPadding(8),
+
+ "unk_4b8" / Int32ul,
+ "unk_4bc" / ZPadding(4),
+
+ "unk_4c0" / Int32ul,
+ "unk_4c4" / Int32ul,
+ "unk_4c8" / Int32ul,
+ "unk_4cc" / Int32ul,
+ "unk_4d0" / Int32ul,
+ "unk_4d4" / Int32ul,
+ "unk_4d8" / ZPadding(4),
+
+ "unk_4dc" / Int32ul,
+ "unk_4e0" / Int64ul,
+ "unk_4e8" / Int32ul,
+ "unk_4ec" / Int32ul,
+ "unk_4f0" / Int32ul,
+ "unk_4f4" / Int32ul,
+ "unk_4f8" / Int32ul,
+ "unk_4fc" / Int32ul,
+ "unk_500" / Int32ul,
+ Ver("V >= V13_0B4", "unk_504_0" / Int32ul),
+ "unk_504" / Int32ul,
+ "unk_508" / Int32ul,
+ "unk_50c" / Int32ul,
+ "unk_510" / Int32ul,
+ "unk_514" / Int32ul,
+ "unk_518" / Int32ul,
+ "unk_51c" / Int32ul,
+ "unk_520" / Int32ul,
+ "unk_524" / Int32ul,
+ "unk_528" / Int32ul,
+ "unk_52c" / Int32ul,
+ "unk_530" / Int32ul,
+ "unk_534" / Int32ul,
+ "unk_538" / Int32ul,
+ Ver("V >= V13_0B4", "unk_53c_0" / Int32ul),
+ "num_frags" / Int32ul,
+ "unk_540" / Int32ul,
+ "unk_544" / Int32ul,
+ "unk_548" / Int32ul,
+ "unk_54c" / Int32ul,
+ "unk_550" / Int32ul,
+ "unk_554" / Int32ul,
+ "gpu_region_base" / Int64ul,
+ "gpu_core" / Int32ul,
+ "gpu_rev" / Int32ul,
+ "num_cores" / Int32ul,
+ "max_pstate" / Int32ul,
+ Ver("V < V13_0B4", "num_pstates" / Int32ul),
+ "frequencies" / Array(16, Dec(Int32ul)),
+ "voltages" / Array(16, Array(8, Dec(Int32ul))),
+ "voltages_sram" / Array(16, Array(8, Dec(Int32ul))),
+ "unk_9b4" / Array(16, Float32l),
+ "unk_9f4" / Array(16, Int32ul),
+ "rel_max_powers" / Array(16, Dec(Int32ul)),
+ "rel_boost_freqs" / Array(16, Dec(Int32ul)),
+ Ver("V < V13_0B4", "min_sram_volt" / Dec(Int32ul)),
+ Ver("V < V13_0B4", "unk_ab8" / Int32ul),
+ Ver("V < V13_0B4", "unk_abc" / Int32ul),
+ Ver("V < V13_0B4", "unk_ac0" / Int32ul),
+
+ Ver("V >= V13_0B4", "unk_ac4_0" / HexDump(Bytes(0x1f0))),
+
+ "pad_ac4" / ZPadding(8),
+ "unk_acc" / Int32ul,
+ "unk_ad0" / Int32ul,
+ "pad_ad4" / ZPadding(16),
+ "unk_ae4" / Array(4, Int32ul),
+ "pad_af4" / ZPadding(4),
+ "unk_af8" / Int32ul,
+ "unk_afc" / Int32ul,
+ "unk_b00" / Int32ul,
+ "unk_b04" / Int32ul,
+ "unk_b08" / Int32ul,
+ "unk_b0c" / Int32ul,
+ "unk_b10" / Int32ul,
+ "pad_b14" / ZPadding(8),
+ "unk_b1c" / Int32ul,
+ "unk_b20" / Int32ul,
+ "unk_b24" / Int32ul,
+ "unk_b28" / Int32ul,
+ "unk_b2c" / Int32ul,
+ "unk_b30" / Int32ul,
+ "unk_b34" / Int32ul,
+ Ver("V >= V13_0B4", "unk_b38_0" / Int32ul),
+ Ver("V >= V13_0B4", "unk_b38_4" / Int32ul),
+ "unk_b38" / Array(6, Int64ul),
+ "unk_b68" / Int32ul,
+ Ver("V >= V13_0B4", "unk_b6c" / HexDump(Bytes(0xd0))),
+ Ver("V >= V13_0B4", "unk_c3c" / Int32ul),
+ )
+
+ def __init__(self, sgx, chip_info):
+ # Userspace VA map related
+ self.unk_0 = 0x13_00000000
+ self.unk_8 = 0x14_00000000
+ self.unk_10 = 0x1_00000000
+ self.unk_18 = 0xffc00000
+ self.unk_20 = 0x11_00000000
+ self.unk_28 = 0x11_00000000
+ # userspace address?
+ self.unk_30 = 0x6f_ffff8000
+ self.pad_40 = bytes(0x20)
+ # unmapped?
+ self.unkptr_38 = 0xffffffa0_11800000
+ self.pad_1c8 = bytes(8)
+
+ # Note: these are rounded poorly, need to recompute.
+ self.yuv_matrices = [
+ [ # BT.601 full range -> RGB
+ [ 0x2000, -0x8, 0x2cdb, -0x2cd3],
+ [ 0x2000, -0xb00, -0x16da, 0x21da],
+ [ 0x2000, 0x38b6, 0x8, -0x38be],
+ ],
+ [ # BT.709 full range -> RGB
+ [ 0x2000, -0x1, 0x3264, -0x3263],
+ [ 0x2000, -0x5fe, -0xefb, 0x14f9],
+ [ 0x2000, 0x3b61, 0x1, -0x3b62],
+ ],
+ [ # BT.2020 full range -> RGB
+ [ 0x2000, 0x0, 0x2f30, -0x2f30],
+ [ 0x2000, -0x544, -0x1248, 0x178c],
+ [ 0x2000, 0x3c34, -0x1, -0x3c33],
+ ],
+ [ # BT.601 limited range -> RGB
+ [ 0x2568, -0x9, 0x3343, -0x37e7],
+ [ 0x2568, -0xc92, -0x1a1e, 0x2203],
+ [ 0x2568, 0x40cf, 0x9, -0x4585],
+ ],
+ [ # BT.709 limited range -> RGB
+ [ 0x2568, -0x1, 0x3997, -0x3e43],
+ [ 0x2568, -0x6d9, -0x111f, 0x134b],
+ [ 0x2568, 0x43dd, 0x1, -0x488b],
+ ],
+ [ # BT.2020 limited range -> RGB
+ [ 0x2568, 0x0, 0x35ee, -0x3a9b],
+ [ 0x2568, -0x604, -0x14e5, 0x163c],
+ [ 0x2568, 0x44ce, -0x1, -0x497a],
+ ],
+ [ # Unknown YUV->RGB
+ [ 0x24cb, 0x0, 0x2cfa, -0x3676],
+ [ 0x24cb, -0xb0a, -0x16e9, 0x1877],
+ [ 0x24cb, 0x38d9, 0x0, -0x4255],
+ ],
+ [ # null
+ [ 0x0, 0x0, 0x0, 0x0],
+ [ 0x0, 0x0, 0x0, 0x0],
+ [ 0x0, 0x0, 0x0, 0x0],
+ ],
+ [ # RGB -> BT.601 full range
+ [ 0x2645, 0x4b23, 0xe98, 0x0],
+ [-0x15a1, -0x2a5e, 0x3fff, 0x4000],
+ [ 0x4000, -0x35a2, -0xa5e, 0x4000],
+ ],
+ [ # RGB -> BT.709 full range
+ [ 0x1b37, 0x5b8c, 0x93d, 0x0],
+ [ -0xeac, -0x3155, 0x4000, 0x4000],
+ [ 0x4000, -0x3a24, -0x5dd, 0x4000],
+ ],
+ [ # RGB -> BT.2020 full range
+ [ 0x21a0, 0x56c9, 0x797, 0x0],
+ [-0x11de, -0x2e22, 0x4000, 0x4000],
+ [ 0x4000, -0x3adb, -0x526, 0x4000],
+ ],
+ [ # RGB -> BT.601 limited range
+ [ 0x20bd, 0x4047, 0xc7c, 0x800],
+ [-0x12ed, -0x2513, 0x3800, 0x4000],
+ [ 0x3800, -0x2eee, -0x912, 0x4000],
+ ],
+ [ # RGB -> BT.709 limited range
+ [ 0x1748, 0x4e51, 0x7e7, 0x800],
+ [ -0xcd6, -0x2b2a, 0x3800, 0x4000],
+ [ 0x3800, -0x32df, -0x521, 0x4000],
+ ],
+ [ # RGB -> BT.2020 limited range
+ [ 0x1cc4, 0x4a3e, 0x67e, 0x800],
+ [ -0xfa3, -0x285e, 0x3800, 0x4000],
+ [ 0x3800, -0x337f, -0x481, 0x4000],
+ ],
+ [ # Unknown (identity?)
+ [-0x8000, 0x0, 0x0, 0x0],
+ [ 0x0, -0x8000, 0x0, 0x0],
+ [ 0x0, 0x0, -0x8000, 0x0],
+ ],
+ ]
+ if Ver.check("V >= V13_0B4"):
+ self.yuv_matrices = [
+ *self.yuv_matrices[:8],
+ *(24 * [[[0,0,0,0]]*3]),
+ *self.yuv_matrices[8:],
+ *(24 * [[[0,0,0,0]]*3]),
+ ]
+
+ self.unk_450_0 = bytes(0x68)
+
+ self.chip_id = chip_info.chip_id
+ self.unk_454 = 0x1
+ self.unk_458 = 0x1
+ self.unk_45c = 0x0
+ self.unk_460 = 0x1
+ self.unk_464 = 0x1
+ self.unk_468 = 0x1
+ self.unk_46c = 0x0
+ self.unk_470 = 0x0
+ self.unk_474 = 0x0
+ self.unk_478 = 0x0
+ self.unk_47c = 0x1
+ self.unk_480 = 0x0
+ self.unk_484 = 0x1
+ self.unk_488 = 0x0
+ self.unk_48c = 0x1
+ self.base_clock_khz = 24000
+ self.power_sample_period = sgx.gpu_power_sample_period
+ self.unk_49c = 0x1
+ self.unk_4a0 = 0x1
+ self.unk_4a4 = 0x1
+ self.unk_4ac = 0x0
+ self.unk_4b8 = 0x0
+ self.unk_4c0 = 0x1f
+ self.unk_4c4 = 0x0
+ self.unk_4c8 = 0x0
+ self.unk_4cc = 0x0
+ self.unk_4d0 = 0x0
+ self.unk_4d4 = 0x0
+ self.unk_4dc = 0x0
+ self.unk_4e0 = chip_info.hwdb_4e0
+ self.unk_4e8 = 0x0
+ self.unk_4ec = 0x0
+ self.unk_4f0 = 0x1
+ self.unk_4f4 = 0x1
+ self.unk_4f8 = 0x0
+ self.unk_4fc = 0x0
+ self.unk_500 = 0x0
+ self.unk_504_0 = 0
+ self.unk_504 = 0x31
+ self.unk_508 = 0x0
+ self.unk_50c = 0x0
+ self.unk_510 = 0x0
+ self.unk_514 = 0x0
+ self.unk_518 = 0x0
+ self.unk_51c = 0x0
+ self.unk_520 = 0x0
+ self.unk_524 = 0x1 # use_secure_cache_flush
+ self.unk_528 = 0x0
+ self.unk_52c = 0x0
+ self.unk_530 = 0x0
+ self.unk_534 = chip_info.hwdb_534
+ self.unk_538 = 0x0
+ self.unk_53c_0 = 0
+ self.num_frags = chip_info.num_cores
+ self.unk_540 = 0x0
+ self.unk_544 = 0x0
+ self.unk_548 = 0x0
+ self.unk_54c = 0x0
+ self.unk_550 = 0x0
+ self.unk_554 = 0x1
+ self.gpu_region_base = sgx.gpu_region_base
+ self.gpu_core = chip_info.gpu_core
+ self.gpu_rev = chip_info.gpu_rev
+ self.num_cores = chip_info.num_cores
+ self.max_pstate = sgx.gpu_num_perf_states
+ self.num_pstates = sgx.gpu_num_perf_states + 1
+
+ self.frequencies = [0] * 16
+ self.voltages = [[0] * 8 for i in range(16)]
+ self.voltages_sram = [[0] * 8 for i in range(16)]
+ self.unk_9b4 = [0.] * 16
+ self.unk_9f4 = [0] * 16
+ self.rel_max_powers = [0] * 16
+ self.rel_boost_freqs = [0] * 16
+ self.min_sram_volt = chip_info.min_sram_volt
+ self.unk_ab8 = chip_info.hwdb_ab8
+ self.unk_abc = chip_info.hwdb_abc
+ self.unk_ac0 = 0x1020
+ self.unk_ac4_0 = bytes(0x1f0)
+ self.unk_acc = 0x0
+ self.unk_ad0 = 0x0
+ if Ver.check("V >= V13_0B4"):
+ self.unk_ae4 = [0x0, 0x3, 0x7, 0x7]
+ else:
+ self.unk_ae4 = [0x0, 0xf, 0x3f, 0x3f]
+ self.unk_af8 = 0x0
+ self.unk_afc = 0x0
+ self.unk_b00 = 0x0
+ self.unk_b04 = 0x0
+ self.unk_b08 = 0x0
+ self.unk_b0c = 0x0
+ self.unk_b10 = 0x1
+ self.unk_b1c = 0x0
+ self.unk_b20 = 0x0
+ self.unk_b24 = 0x1
+ self.unk_b28 = 0x1
+ self.unk_b2c = 0x1
+ self.unk_b30 = chip_info.hwdb_b30
+ self.unk_b34 = 0x0
+ self.unk_b38_0 = 1
+ self.unk_b38_4 = 1
+ self.unk_b38 = [0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff]
+ self.unk_b68 = 0x0
+ self.unk_b6c = bytes(0xd0)
+ self.unk_c3c = 0x19
+
+class InitData_BufferMgrCtl(ConstructValueClass):
+ subcon = Array(126, Bytes(0x10))
+
+ def __init__(self):
+ self.value = [bytes(0x10)] * 126
+
+class InitData_GPUQueueStatsTA(ConstructClass):
+ subcon = Struct(
+ "busy" / Int32ul,
+ "unk_4" / Int32ul,
+ "cur_cmdqueue" / Int64ul,
+ "cur_count" / Int32ul,
+ "unk_14" / Int32ul,
+ )
+ def __init__(self):
+ self.busy = 0
+ self.unk_4 = 0
+ self.cur_cmdqueue = 0
+ self.cur_count = 0
+ self.unk_14 = 0
+
+class InitData_GPUStatsTA(ConstructClass):
+ subcon = Struct(
+ "unk_4" / Int32ul,
+ "queues" / Array(4, InitData_GPUQueueStatsTA),
+ "unk_68" / Bytes(0x8),
+ "unk_70" / Int32ul,
+ "unk_74" / Int32ul,
+ "unk_timestamp" / Int64ul,
+ "unk_80" / HexDump(Bytes(0x40)),
+ Ver("V >= V13_0B4", "unk_c0" / HexDump(Bytes(0x800))),
+ )
+
+ def __init__(self):
+ self.unk_4 = 0
+ self.queues = [InitData_GPUQueueStatsTA() for i in range(4)]
+ self.unk_68 = bytes(0x8)
+ self.unk_70 = 0
+ self.unk_74 = 0
+ self.unk_timestamp = 0
+ self.unk_80 = bytes(0x40)
+ self.unk_c0 = bytes(0x800)
+
+class InitData_GPUQueueStats3D(ConstructClass):
+ subcon = Struct(
+ "busy" / Int32ul,
+ "cur_cmdqueue" / Int64ul,
+ "unk_c" / Int32ul,
+ "unk_10" / Int32ul,
+ "unk_14" / HexDump(Bytes(0x28 - 0x14)),
+ )
+ def __init__(self):
+ self.busy = 0
+ self.cur_cmdqueue = 0
+ self.unk_c = 0
+ self.unk_10 = 0
+ self.unk_14 = bytes(0x14)
+
+class InitData_GPUStats3D(ConstructClass):
+ subcon = Struct(
+ "unk_0" / Bytes(0x18),
+ "queues" / Array(4, InitData_GPUQueueStats3D),
+ "unk_d0" / HexDump(Bytes(0x38)),
+ "tvb_overflows_1" / Int32ul,
+ "tvb_overflows_2" / Int32ul,
+ "unk_f8" / Int32ul,
+ "unk_fc" / Int32ul,
+ "cur_stamp_id" / Int32sl,
+ "unk_104" / Bytes(0x14),
+ "unk_118" / Int32sl,
+ "unk_11c" / Int32ul,
+ "unk_120" / Int32ul,
+ "unk_124" / Int32ul,
+ "unk_128" / Int32ul,
+ "unk_12c" / Int32ul,
+ "unk_timestamp" / Int64ul,
+ "unk_134" / Bytes(0x1c0 - 0x134),
+ Ver("V >= V13_0B4", "unk_1c0" / HexDump(Bytes(0x800))),
+ )
+
+ def __init__(self):
+ self.unk_0 = bytes(0x18)
+ self.queues = [InitData_GPUQueueStats3D() for i in range(4)]
+ self.unk_68 = 0
+ self.cur_cmdqueue = 0
+ self.unk_d0 = bytes(0x38)
+ self.tvb_overflows_1 = 0
+ self.tvb_overflows_2 = 0
+ self.unk_f8 = 0
+ self.unk_fc = 0
+ self.cur_stamp_id = -1
+ self.unk_104 = bytes(0x14)
+ self.unk_118 = -1
+ self.unk_11c = 0
+ self.unk_120 = 0
+ self.unk_124 = 0
+ self.unk_128 = 0
+ self.unk_12c = 0
+ self.unk_timestamp = 0
+ self.unk_134 = bytes(0x1c0 - 0x134)
+ self.unk_1c0 = bytes(0x800)
+
+class InitData_GPUGlobalStatsTA(ConstructClass):
+ subcon = Struct(
+ "total_cmds" / Int32ul,
+ "stats" / InitData_GPUStatsTA,
+ )
+
+ def __init__(self):
+ self.total_cmds = 0
+ self.stats = InitData_GPUStatsTA()
+
+class InitData_GPUGlobalStats3D(ConstructClass):
+ subcon = Struct(
+ "total_cmds" / Int32ul,
+ "unk_4" / Int32ul,
+ "stats" / InitData_GPUStats3D,
+ )
+
+ def __init__(self):
+ self.total_cmds = 0
+ self.unk_4 = 0
+ self.stats = InitData_GPUStats3D()
+
+class InitData_RegionB(ConstructClass):
+ subcon = Struct(
+ "channels" / ChannelInfoSet,
+ "pad_110" / ZPadding(0x50),
+ "unk_160" / Default(Int64ul, 0),
+ "unk_168" / Default(Int64ul, 0),
+ "stats_ta_addr" / Int64ul,
+ "stats_ta" / ROPointer(this.stats_ta_addr, InitData_GPUGlobalStatsTA),
+ "stats_3d_addr" / Int64ul,
+ "stats_3d" / ROPointer(this.stats_3d_addr, InitData_GPUGlobalStats3D),
+ "stats_cp_addr" / Int64ul,
+ "stats_cp" / ROPointer(this.stats_cp_addr, Bytes(0x140)),
+ "hwdata_a_addr" / Int64ul,
+ "hwdata_a" / ROPointer(this.hwdata_a_addr, AGXHWDataA),
+ "unkptr_190" / Int64ul, # size: 0x80, empty
+ "unk_190" / ROPointer(this.unkptr_190, Bytes(0x80)),
+ "unkptr_198" / Int64ul, # size: 0xc0, fw writes timestamps into this
+ "unk_198" / ROPointer(this.unkptr_198, Bytes(0xc0)),
+ "hwdata_b_addr" / Int64ul, # size: 0xb80, io stuff
+ "hwdata_b" / ROPointer(this.hwdata_b_addr, AGXHWDataB),
+ "hwdata_b_addr2" / Int64ul, # repeat of 1a0
+ "fwlog_ring2" / Int64ul, #
+ "unkptr_1b8" / Int64ul, # Unallocated, Size 0x1000
+ "unk_1b8" / Lazy(ROPointer(this.unkptr_1b8, Bytes(0x1000))),
+ "unkptr_1c0" / Int64ul, # Unallocated, size 0x300
+ "unk_1c0" / Lazy(ROPointer(this.unkptr_1c0, Bytes(0x300))),
+ "unkptr_1c8" / Int64ul, # Unallocated, unknown size
+ "unk_1c8" / Lazy(ROPointer(this.unkptr_1c8, Bytes(0x1000))),
+ "unk_1d0" / Int32ul,
+ "unk_1d4" / Int32ul,
+ "unk_1d8" / HexDump(Bytes(0x3c)),
+ "buffer_mgr_ctl_addr" / Int64ul, # Size: 0x4000
+ "buffer_mgr_ctl" / ROPointer(this.buffer_mgr_ctl_addr, InitData_BufferMgrCtl),
+ "buffer_mgr_ctl_addr2" / Int64ul, # Size: 0x4000
+ # Written to by DC_09
+ "unk_224" / HexDump(Bytes(0x685c)),
+ "unk_6a80" / Int32ul,
+ "gpu_idle" / Int32ul,
+ "unkpad_6a88" / HexDump(Bytes(0x14)),
+ "unk_6a9c" / Int32ul,
+ "unk_ctr0" / Int32ul,
+ "unk_ctr1" / Int32ul,
+ "unk_6aa8" / Int32ul,
+ "unk_6aac" / Int32ul,
+ "unk_ctr2" / Int32ul,
+ "unk_6ab4" / Int32ul,
+ "unk_6ab8" / Int32ul,
+ "unk_6abc" / Int32ul,
+ "unk_6ac0" / Int32ul,
+ "unk_6ac4" / Int32ul,
+ "unk_ctr3" / Int32ul,
+ "unk_6acc" / Int32ul,
+ "unk_6ad0" / Int32ul,
+ "unk_6ad4" / Int32ul,
+ "unk_6ad8" / Int32ul,
+ "unk_6adc" / Int32ul,
+ "unk_6ae0" / Int32ul,
+ "unk_6ae4" / Int32ul,
+ "unk_6ae8" / Int32ul,
+ "unk_6aec" / Int32ul,
+ "unk_6af0" / Int32ul,
+ "unk_ctr4" / Int32ul,
+ "unk_ctr5" / Int32ul,
+ "unk_6afc" / Int32ul,
+ "pad_6b00" / HexDump(Bytes(0x38)),
+ "unk_6b38" / Int32ul,
+ "pad_6b3c" / HexDump(Bytes(0x84)),
+ )
+
+ def __init__(self):
+ super().__init__()
+ self.unk_1d0 = 0
+ self.unk_1d4 = 0
+ self.unk_1d8 = bytes(0x3c)
+ self.unk_224 = bytes(0x685c)
+ self.unkpad_6a88 = bytes(0x14)
+ self.pad_6b00 = bytes(0x38)
+ self.unk_6b38 = 0xff
+ self.pad_6b3c = bytes(0x84)
+
+ def mon(self, add_fn):
+ add_fn(self.unkptr_170, 0x140, "unkptr_170")
+ add_fn(self.unkptr_178, 0x1c0, "unkptr_178")
+ add_fn(self.unkptr_180, 0x140, "unkptr_180")
+ add_fn(self.unkptr_188_addr, 0x3b80, "unkptr_188")
+ add_fn(self.unkptr_190, 0x80, "unkptr_190")
+ add_fn(self.unkptr_198_addr, 0xc0, "unkptr_198")
+ add_fn(self.unkptr_1a0_addr, 0xb80, "unkptr_1a0")
+ add_fn(self.fwlog_ring2, 0x51000, "fwlog_ring2")
+ add_fn(self.unkptr_214, 0x4000, "unkptr_214")
+
+ # Unallocated during init
+ #add_fn(self.unkptr_1b8, 0x1000, "unkptr_1b8")
+ #add_fn(self.unkptr_1c0, 0x300, "unkptr_1c0")
+ #add_fn(self.unkptr_1c8, 0x1000, "unkptr_1c8")
+
+class InitData_PendingStamp(ConstructClass):
+ subcon = Struct(
+ "info" / Int32ul,
+ "wait_value" / Int32ul,
+ )
+
+ def __init__(self):
+ super().__init__()
+ self.info = 0
+ self.wait_value = 0
+
+ def __bool__(self):
+ return bool(self.info or self.wait_value)
+
+class InitData_FaultInfo(ConstructClass):
+ subcon = Struct(
+ "unk_0" / Int32ul,
+ "unk_4" / Int32ul,
+ "queue_uuid" / Int32ul,
+ "unk_c" / Int32ul,
+ "unk_10" / Int32ul,
+ "unk_14" / Int32ul,
+ )
+
+ def __init__(self):
+ super().__init__()
+ self.unk_0 = 0
+ self.unk_4 = 0
+ self.queue_uuid = 0
+ self.unk_c = 0
+ self.unk_10 = 0
+ self.unk_14 = 0
+
+class RCPowerZone(ConstructClass):
+ subcon = Struct(
+ "target" / Dec(Int32ul),
+ "target_off" / Dec(Int32ul),
+ "tc" / Dec(Int32ul),
+ )
+ def __init__(self, tc=None, target=None, off=None):
+ if tc is None:
+ self.target = 0
+ self.target_off = 0
+ self.tc = 0
+ else:
+ self.target = target
+ self.target_off = self.target - off
+ self.tc = tc
+
+
+class InitData_RegionC(ConstructClass):
+ subcon = Struct(
+ "ktrace_enable" / Int32ul,
+ "unk_4" / HexDump(Bytes(0x24)),
+ Ver("V >= V13_0B4", "unk_28_0" / Int32ul),
+ "unk_28" / Int32ul,
+ Ver("V >= V13_0B4", "unk_2c_0" / Int32ul),
+ "unk_2c" / Int32ul,
+ "unk_30" / Int32ul,
+ "unk_34" / Int32ul,
+ "unk_38" / HexDump(Bytes(0x1c)),
+ "unk_54" / Int16ul,
+ "unk_56" / Int16ul,
+ "unk_58" / Int16ul,
+ "unk_5a" / Int32ul,
+ "unk_5e" / Int32ul,
+ "unk_62" / Int32ul,
+ Ver("V >= V13_0B4", "unk_66_0" / HexDump(Bytes(0xc))),
+ "unk_66" / Int32ul,
+ "unk_6a" / HexDump(Bytes(0x16)),
+ "unk_80" / HexDump(Bytes(0xf80)),
+ "unk_1000" / HexDump(Bytes(0x7000)),
+ "unk_8000" / HexDump(Bytes(0x900)),
+ Ver("V >= V13_0B4", "unk_8900_0" / Int32ul),
+ "unk_8900" / Int32ul,
+ "unk_atomic" / Int32ul,
+ "max_power" / Int32ul,
+ "max_pstate_scaled" / Int32ul,
+ "max_pstate_scaled_2" / Int32ul,
+ "unk_8914" / Int32ul,
+ "unk_8918" / Int32ul,
+ "max_pstate_scaled_3" / Int32ul,
+ "unk_8920" / Int32ul,
+ "power_zone_count" / Int32ul,
+ "avg_power_filter_tc_periods" / Int32ul,
+ "avg_power_ki_dt" / Float32l,
+ "avg_power_kp" / Float32l,
+ "avg_power_min_duty_cycle" / Int32ul,
+ "avg_power_target_filter_tc" / Int32ul,
+ "power_zones" / Array(5, RCPowerZone),
+ "unk_8978" / HexDump(Bytes(0x44)),
+ Ver("V >= V13_0B4", "unk_89bc_0" / HexDump(Bytes(0x3c))),
+ "unk_89bc" / Int32ul,
+ "fast_die0_release_temp" / Int32ul,
+ "unk_89c4" / Int32sl,
+ "fast_die0_prop_tgt_delta" / Int32ul,
+ "fast_die0_kp" / Float32l,
+ "fast_die0_ki_dt" / Float32l,
+ "unk_89d4" / HexDump(Bytes(0xc)),
+ "unk_89e0" / Int32ul,
+ "max_power_2" / Int32ul,
+ "ppm_kp" / Float32l,
+ "ppm_ki_dt" / Float32l,
+ "unk_89f0" / Int32ul,
+ Ver("V >= V13_0B4", "unk_89f4_0" / HexDump(Bytes(0x8))),
+ Ver("V >= V13_0B4", "unk_89f4_8" / Int32ul),
+ Ver("V >= V13_0B4", "unk_89f4_c" / HexDump(Bytes(0x50))),
+ "hws1" / AGXHWDataShared1,
+ "hws2" / AGXHWDataShared2,
+ "hws3" / AGXHWDataShared3,
+ "unk_9004" / HexDump(Bytes(8)),
+ Ver("V >= V13_0B4", "unk_900c_0" / HexDump(Bytes(0x28))),
+ "unk_900c" / Int32ul,
+ Ver("V >= V13_0B4", "unk_9010_0" / Int32ul),
+ Ver("V >= V13_0B4", "unk_9010_4" / HexDump(Bytes(0x14))),
+ "unk_9010" / HexDump(Bytes(0x2c)),
+ "unk_903c" / Int32ul,
+ "unk_9040" / HexDump(Bytes(0xc0)),
+ "unk_9100" / HexDump(Bytes(0x6f00)),
+ "unk_10000" / HexDump(Bytes(0xe50)),
+ "unk_10e50" / Int32ul,
+ "unk_10e54" / HexDump(Bytes(0x2c)),
+ "fault_control" / Int32ul,
+ "do_init" / Int32ul,
+ "unk_10e88" / HexDump(Bytes(0x188)),
+ "idle_ts" / Int64ul,
+ "idle_unk" / Int64ul,
+ "unk_11020" / Int32ul,
+ "unk_11024" / Int32ul,
+ "unk_11028" / Int32ul,
+ Ver("V >= V13_0B4", "unk_1102c_0" / Int32ul),
+ Ver("V >= V13_0B4", "unk_1102c_4" / Int32ul),
+ Ver("V >= V13_0B4", "unk_1102c_8" / Dec(Int32ul)),
+ Ver("V >= V13_0B4", "unk_1102c_c" / Int32ul),
+ Ver("V >= V13_0B4", "unk_1102c_10" / Int32ul),
+ "unk_1102c" / Int32ul,
+ "idle_to_off_delay_ms" / Int32ul,
+ "fender_idle_to_off_delay_ms" / Int32ul,
+ "fw_early_wake_timeout_ms" / Int32ul,
+ "pending_stamps" / Array(0x110, InitData_PendingStamp),
+ "unk_117bc" / Int32ul,
+ "fault_info" / InitData_FaultInfo,
+ "counter" / Int32ul,
+ "unk_118dc" / Int32ul,
+ Ver("V >= V13_0B4", "unk_118e0_0" / HexDump(Bytes(0x9c))),
+ "unk_118e0" / Dec(Int32ul),
+ Ver("V >= V13_0B4", "unk_118e4_0" / Dec(Int32ul)),
+ "unk_118e4" / Int32ul,
+ "unk_118e8" / Int32ul,
+ "unk_118ec" / Array(0x15, Int8ul),
+ "unk_11901" / HexDump(Bytes(0x43f)),
+ Ver("V >= V13_0B4", "unk_11d40" / HexDump(Bytes(0x19c))),
+ Ver("V >= V13_0B4", "unk_11edc" / Int32ul),
+ Ver("V >= V13_0B4", "unk_11ee0" / HexDump(Bytes(0x1c))),
+ Ver("V >= V13_0B4", "unk_11efc" / Int32ul),
+ )
+
+ def __init__(self, sgx, chip_info):
+ period_ms = sgx.gpu_power_sample_period
+ avg_power_filter_tc_periods = sgx.gpu_avg_power_filter_tc_ms // period_ms
+
+ self.ktrace_enable = 0# 0xffffffff
+ self.unk_4 = bytes(0x24)
+ self.unk_28_0 = 1 # debug
+ self.unk_28 = 1
+ self.unk_2c_0 = 0
+ self.unk_2c = 1
+ self.unk_30 = 0
+ self.unk_34 = 120
+ self.unk_38 = bytes(0x1c)
+ self.unk_54 = 0xffff
+ self.unk_56 = 40
+ self.unk_58 = 0xffff
+ self.unk_5a = 0
+ self.unk_5e = 1
+ self.unk_62 = 0
+ self.unk_66_0 = bytes(0xc)
+ self.unk_66 = 1
+ self.unk_6a = bytes(0x16)
+ self.unk_80 = bytes(0xf80)
+ self.unk_1000 = bytes(0x7000)
+ self.unk_8000 = bytes(0x900)
+ self.unk_8900_0 = 0
+ self.unk_8900 = 1
+ # Accessed with OSIncrementAtomic/OSDecrementAtomic
+ self.unk_atomic = 0
+ self.max_power = chip_info.max_power
+ self.max_pstate_scaled = 100 * sgx.gpu_num_perf_states
+ self.max_pstate_scaled_2 = 100 * sgx.gpu_num_perf_states
+ self.unk_8914 = 0
+ self.unk_8918 = 0
+ self.max_pstate_scaled_3 = 100 * sgx.gpu_num_perf_states
+ self.unk_8920 = 0
+
+ self.power_zones = [RCPowerZone()] * 5
+ power_zone_count = 0
+ for i in range(5):
+ if sgx.getprop(f"gpu-power-zone-target-{i}", None) is None:
+ break
+ self.power_zones[i] = RCPowerZone(
+ sgx.getprop(f"gpu-power-zone-filter-tc-{i}", None),
+ sgx.getprop(f"gpu-power-zone-target-{i}", None),
+ sgx.getprop(f"gpu-power-zone-target-offset-{i}", None),
+ )
+ power_zone_count += 1
+
+ self.power_zone_count = power_zone_count
+ self.avg_power_filter_tc_periods = avg_power_filter_tc_periods
+ self.avg_power_ki_dt = sgx.gpu_avg_power_ki_only * (period_ms / 1000)
+ self.avg_power_kp = sgx.gpu_avg_power_kp
+ self.avg_power_min_duty_cycle = sgx.gpu_avg_power_min_duty_cycle
+ self.avg_power_target_filter_tc = sgx.gpu_avg_power_target_filter_tc
+ self.unk_8978 = bytes(0x44)
+ self.unk_89bc_0 = bytes(0x3c)
+ self.unk_89bc = chip_info.unk_8cc
+ self.fast_die0_release_temp = 100 * sgx.getprop("gpu-fast-die0-release-temp", 80)
+ self.unk_89c4 = chip_info.unk_87c
+ self.fast_die0_prop_tgt_delta = 100 * sgx.getprop("gpu-fast-die0-prop-tgt-delta", 0)
+ self.fast_die0_kp = sgx.gpu_fast_die0_proportional_gain
+ self.fast_die0_ki_dt = sgx.gpu_fast_die0_integral_gain * (period_ms / 1000)
+ self.unk_89d4 = bytes(0xc)
+ self.unk_89e0 = 1
+ self.max_power_2 = chip_info.max_power
+ self.ppm_kp = sgx.gpu_ppm_kp
+ self.ppm_ki_dt = sgx.gpu_ppm_ki * (period_ms / 1000)
+ self.unk_89f0 = 0
+ self.unk_89f4_0 = bytes(8)
+ self.unk_89f4_8 = 1
+ self.unk_89f4_c = bytes(0x50)
+ self.hws1 = AGXHWDataShared1(chip_info)
+ self.hws2 = AGXHWDataShared2(chip_info)
+ self.hws3 = AGXHWDataShared3(chip_info)
+ self.unk_9004 = bytes(8)
+ self.unk_900c_0 = bytes(0x28)
+ self.unk_900c = 1
+ self.unk_9010_0 = 1
+ self.unk_9010_4 = bytes(0x14)
+ self.unk_9010 = bytes(0x2c)
+ if Ver.check("V >= V13_0B4"):
+ self.unk_903c = 1
+ else:
+ self.unk_903c = 0
+ self.unk_9040 = bytes(0xc0)
+ self.unk_9100 = bytes(0x6f00)
+ self.unk_10000 = bytes(0xe50)
+ self.unk_10e50 = 0
+ self.unk_10e54 = bytes(0x2c)
+ self.unk_10e80_0 = bytes(0xed4)
+ self.unk_10e80_ed0 = 0
+ self.unk_10e80_ed4 = bytes(0x2c)
+ #self.fault_control = 0xb
+ self.fault_control = 0
+ self.do_init = 1
+ self.unk_10e88 = bytes(0x188)
+ self.idle_ts = 0
+ self.idle_unk = 0
+ self.unk_11020 = 40
+ self.unk_11024 = 10
+ self.unk_11028 = 250
+ self.unk_1102c_0 = 1
+ self.unk_1102c_4 = 1
+ self.unk_1102c_8 = 100
+ self.unk_1102c_c = 1
+ self.unk_1102c_10 = 0
+ self.unk_1102c = 0
+ self.idle_to_off_delay_ms = sgx.getprop("gpu-idle-off-delay-ms", 2)
+ self.fender_idle_to_off_delay_ms = sgx.getprop("gpu-fender-idle-off-delay-ms", 40)
+ self.fw_early_wake_timeout_ms = sgx.getprop("gpu-fw-early-wake-timeout-ms", 5)
+ self.pending_stamps = [InitData_PendingStamp() for i in range(0x110)]
+ self.unk_117bc = 0
+ self.fault_info = InitData_FaultInfo()
+ self.counter = 0
+ self.unk_118dc = 0
+ self.unk_118e0_0 = bytes(0x9c)
+ self.unk_118e0 = 40
+ self.unk_118e4_0 = 50
+ self.unk_118e4 = 0
+ self.unk_118e8 = 0 if chip_info.unk_118ec is None else 1
+ self.unk_118ec = chip_info.unk_118ec or [0] * 0x15
+ self.unk_11901 = bytes(0x43f)
+
+ self.unk_11d40 = bytes(0x19c)
+ self.unk_11edc = 8
+ self.unk_11ee0 = bytes(0x1c)
+ self.unk_11efc = 8
+
+class UatLevelInfo(ConstructClass):
+ subcon = Struct(
+ "unk_3" / Int8ul, # always 8
+ "unk_1" / Int8ul, # always 14, page bits?
+ "unk_2" / Int8ul, # always 14, also page bits?
+ "index_shift" / Int8ul,
+ "num_entries" / Int16ul,
+ "unk_4" / Int16ul, # 0x4000, Table size?
+ "unk_8" / Int64ul, # always 1
+ "phys_mask" / Int64ul,
+ "index_mask" / Int64ul,
+ )
+
+ def __init__(self, index_shift, num_entries, phys_mask):
+ self.index_shift = index_shift
+ self.unk_1 = 14
+ self.unk_2 = 14
+ self.unk_3 = 8
+ self.unk_4 = 0x4000 # I doubt anything other than 16k pages is supported
+ self.num_entries = num_entries
+ self.unk_8 = 1
+ self.phys_mask = phys_mask
+ self.index_mask = (num_entries - 1) << index_shift
+
+class InitData(ConstructClass):
+ subcon = Struct(
+ Ver("V >= V13_0B4", "ver_info" / Array(4, Int16ul)),
+ "regionA_addr" / Int64ul, # allocation size: 0x4000
+ "regionA" / ROPointer(this.regionA_addr, HexDump(Bytes(0x4000))),
+ "unk_8" / Default(Int32ul, 0),
+ "unk_c"/ Default(Int32ul, 0),
+ "regionB_addr" / Int64ul, # 0xfa00c338000 allocation size: 0x6bc0
+ "regionB" / ROPointer(this.regionB_addr, InitData_RegionB),
+ "regionC_addr" / Int64ul, # 0xfa000200000 allocation size: 0x11d40, heap?
+ "regionC" / ROPointer(this.regionC_addr, InitData_RegionC),
+ "fw_status_addr" / Int64ul, # allocation size: 0x4000, but probably only 0x80 bytes long
+ "fw_status" / ROPointer(this.fw_status_addr, InitData_FWStatus),
+ "uat_page_size" / Int16ul,
+ "uat_page_bits" / Int8ul,
+ "uat_num_levels" / Int8ul,
+ "uat_level_info" / Array(3, UatLevelInfo),
+ "pad_8c" / HexDump(Default(Bytes(0x14), bytes(0x14))),
+ "host_mapped_fw_allocations" / Int32ul, # must be 1
+ "unk_ac" / Int32ul,
+ "unk_b0" / Int32ul,
+ "unk_b4" / Int32ul,
+ "unk_b8" / Int32ul,
+ )
+
+ def __init__(self):
+ super().__init__()
+ self.unk_ac = 0
+ self.unk_b0 = 0
+ self.unk_b4 = 0
+ self.unk_b8 = 0
+
+__all__.extend(k for k, v in globals().items()
+ if (callable(v) or isinstance(v, type)) and v.__module__ == __name__)
diff --git a/tools/proxyclient/m1n1/fw/agx/microsequence.py b/tools/proxyclient/m1n1/fw/agx/microsequence.py
new file mode 100644
index 0000000..475597f
--- /dev/null
+++ b/tools/proxyclient/m1n1/fw/agx/microsequence.py
@@ -0,0 +1,800 @@
+"""
+I think these are executed by a simple state machine on the firmware's arm core,
+and the typical result is a commandlist submitting to one of the gpu's hardware
+command processors.
+
+It seems like a common pattern is:
+ 1. Start (3D or Compute)
+ 2. Timestamp
+ 3. Wait For Interrupts
+ 4. Timestamp again
+ 5. Finish (3D or Compute)
+ 6. End
+
+Error messages call these as SKU commands
+
+"""
+from m1n1.constructutils import *
+
+from construct import *
+from construct.core import Int64ul, Int32ul, Int32sl
+import textwrap
+
+__all__ = []
+
+class TimeStamp(ConstructValueClass):
+ subcon = Int64ul
+
+ def __init__(self, value=0):
+ self.value = value
+
+class TsFlag(ConstructValueClass):
+ subcon = Int8ul
+
+ def __init__(self, value=0):
+ self.value = value
+
+class WrappedPointer(ConstructValueClass):
+ subcon = Int64ul
+
+ def __init__(self, value=0):
+ self.value = value
+
+class StampCounter(ConstructValueClass):
+ subcon = Hex(Int32ul)
+
+ def __init__(self):
+ self.value = 0
+
+class BufferManagerBlockControl(ConstructClass):
+ subcon = Struct(
+ "total" / Int32ul,
+ "wptr" / Int32ul,
+ "unk" / Int32ul,
+ "pad" / ZPadding(0x34)
+ )
+
+class BufferManagerCounter(ConstructClass):
+ subcon = Struct(
+ "count" / Int32ul,
+ "pad" / ZPadding(0x3c)
+ )
+
+class BufferManagerMisc(ConstructClass):
+ subcon = Struct(
+ "gpu_0" / Default(Int32ul, 0),
+ "gpu_4" / Default(Int32ul, 0),
+ "gpu_8" / Default(Int32ul, 0),
+ "gpu_c" / Default(Int32ul, 0),
+ "pad_10" / ZPadding(0x10),
+ "cpu_flag" / Int32ul,
+ "pad_24" / ZPadding(0x1c),
+ )
+
+class BufferManagerInfo(ConstructClass):
+ subcon = Struct(
+ "gpu_counter" / Int32ul,
+ "unk_4" / Int32ul,
+ "last_id" / Int32ul,
+ "cur_id" / Int32ul,
+ "unk_10" / Int32ul,
+ "gpu_counter2" / Int32ul,
+ "unk_18" / Int32ul,
+ Ver("V < V13_0B4", "unk_1c" / Int32ul),
+ "page_list_addr" / Int64ul,
+ "page_list_size" / Int32ul,
+ "page_count" / Int32ul,
+ "unk_30" / Int32ul,
+ "block_count" / Int32ul,
+ "unk_38" / Int32ul,
+ "block_list_addr" / Int64ul,
+ "block_ctl_addr" / Int64ul, # points to two u32s
+ "block_ctl" / ROPointer(this.block_ctl_addr, BufferManagerBlockControl),
+ "last_page" / Int32ul,
+ "gpu_page_ptr1" / Int32ul,
+ "gpu_page_ptr2" / Int32ul,
+ "unk_58" / Int32ul,
+ "block_size" / Int32ul,
+ "unk_60" / Int64ul,
+ "counter_addr" / Int64ul,
+ "counter" / ROPointer(this.counter_addr, BufferManagerCounter),
+ "unk_70" / Int32ul,
+ "unk_74" / Int32ul,
+ "unk_78" / Int32ul,
+ "unk_7c" / Int32ul,
+ "unk_80" / Int32ul,
+ "unk_84" / Int32ul,
+ "unk_88" / Int32ul,
+ "unk_8c" / Int32ul,
+ "unk_90" / HexDump(Bytes(0x30)),
+
+ )
+
+ def __init__(self):
+ super().__init__()
+ self.gpu_counter = 0x0
+ self.unk_4 = 0
+ self.last_id = 0x0
+ self.cur_id = 0xffffffff
+ self.unk_10 = 0x0
+ self.gpu_counter2 = 0x0
+ self.unk_18 = 0x0
+ self.unk_1c = 0x0
+ self.unk_30 = 0xd1a
+ self.unk_38 = 0x0
+ self.gpu_page_ptr1 = 0x0
+ self.gpu_page_ptr2 = 0x0
+ self.unk_58 = 0x0
+ self.unk_60 = 0x0
+ self.unk_70 = 0x0
+ self.unk_74 = 0x0
+ self.unk_78 = 0x0
+ self.unk_7c = 0x0
+ self.unk_80 = 0x1
+ self.unk_84 = 0x66cc
+ self.unk_88 = 0x2244
+ self.unk_8c = 0x0
+ self.unk_90 = bytes(0x30)
+
+
+class Start3DClearPipelineBinding(ConstructClass):
+ subcon = Struct(
+ "pipeline_bind" / Int64ul,
+ "address" / Int64ul,
+ )
+
+ def __init__(self, pipeline_bind=None, address=None):
+ super().__init__()
+ self.pipeline_bind = pipeline_bind
+ self.address = address
+
+class Start3DStorePipelineBinding(ConstructClass):
+ subcon = Struct(
+ "unk_0" / Int64ul,
+ "unk_8" / Int32ul,
+ "pipeline_bind" / Int32ul,
+ "unk_10" / Int32ul,
+ "address" / Int32ul,
+ "unk_18" / Int32ul,
+ "unk_1c_padding" / Int32ul,
+ )
+
+ def __init__(self, pipeline_bind=None, address=None):
+ super().__init__()
+ self.unk_0 = 0
+ self.unk_8 = 0
+ self.pipeline_bind = pipeline_bind
+ self.unk_10 = 0
+ self.address = address
+ self.unk_18 = 0
+ self.unk_1c_padding = 0
+
+class Start3DArrayAddr(ConstructClass):
+ subcon = Struct(
+ "ptr" / Int64ul,
+ "unk_padding" / Int64ul,
+ )
+
+ def __init__(self, ptr=None):
+ super().__init__()
+ self.ptr = ptr
+ self.unk_padding = 0
+
+class AuxFBInfo(ConstructClass):
+ subcon = Struct(
+ "unk1" / Int32ul,
+ "unk2" / Int32ul,
+ "width" / Dec(Int32ul),
+ "height" / Dec(Int32ul),
+ Ver("V >= V13_0B4", "unk3" / Int64ul),
+ )
+
+ def __init__(self, unk1, unk2, width, height):
+ super().__init__()
+ self.unk1 = unk1
+ self.unk2 = unk2
+ self.width = width
+ self.height = height
+ self.unk3 = 0x100000
+
+class Start3DStruct1(ConstructClass):
+ subcon = Struct(
+ "store_pipeline_bind" / Int32ul, # 0x12, 0x34 seen
+ "store_pipeline_addr" / Int32ul,
+ "unk_8" / Int32ul,
+ "unk_c" / Int32ul,
+ "merge_upper_x" / Float32l,
+ "merge_upper_y" / Float32l,
+ "unk_18" / Int64ul,
+ "tile_blocks_y" / Int16ul, # * 4
+ "tile_blocks_x" / Int16ul, # * 4
+ "unk_24" / Int32ul,
+ "tile_counts" / Int32ul,
+ "unk_2c" / Int32ul,
+ "depth_clear_val1" / Float32l,
+ "stencil_clear_val1" / Int8ul,
+ "unk_35" / Int8ul,
+ "unk_36" / Int16ul,
+ "unk_38" / Int32ul,
+ "unk_3c" / Int32ul,
+ "unk_40" / Int32ul,
+ "unk_44_padding" / HexDump(Bytes(0xac)),
+ "depth_bias_array" / Start3DArrayAddr,
+ "scissor_array" / Start3DArrayAddr,
+ "visibility_result_buffer" / Int64ul,
+ "unk_118" / Int64ul,
+ "unk_120" / Array(37, Int64ul),
+ "unk_reload_pipeline" / Start3DClearPipelineBinding,
+ "unk_258" / Int64ul,
+ "unk_260" / Int64ul,
+ "unk_268" / Int64ul,
+ "unk_270" / Int64ul,
+ "reload_pipeline" / Start3DClearPipelineBinding,
+ "depth_flags" / Int64ul, # 0x40000 - has stencil 0x80000 - has depth
+ "unk_290" / Int64ul,
+ "depth_buffer_ptr1" / Int64ul,
+ "unk_2a0" / Int64ul,
+ "unk_2a8" / Int64ul,
+ "depth_buffer_ptr2" / Int64ul,
+ "depth_buffer_ptr3" / Int64ul,
+ "depth_aux_buffer_ptr" / Int64ul,
+ "stencil_buffer_ptr1" / Int64ul,
+ "unk_2d0" / Int64ul,
+ "unk_2d8" / Int64ul,
+ "stencil_buffer_ptr2" / Int64ul,
+ "stencil_buffer_ptr3" / Int64ul,
+ "stencil_aux_buffer_ptr" / Int64ul,
+ "unk_2f8" / Array(2, Int64ul),
+ "aux_fb_unk0" / Int32ul,
+ "unk_30c" / Int32ul,
+ "aux_fb" / AuxFBInfo,
+ "unk_320_padding" / HexDump(Bytes(0x10)),
+ "unk_partial_store_pipeline" / Start3DStorePipelineBinding,
+ "partial_store_pipeline" / Start3DStorePipelineBinding,
+ "depth_clear_val2" / Float32l,
+ "stencil_clear_val2" / Int8ul,
+ "unk_375" / Int8ul,
+ "unk_376" / Int16ul,
+ "unk_378" / Int32ul,
+ "unk_37c" / Int32ul,
+ "unk_380" / Int64ul,
+ "unk_388" / Int64ul,
+ Ver("V >= V13_0B4", "unk_390_0" / Int64ul),
+ "depth_dimensions" / Int64ul,
+ )
+
+class Start3DStruct2(ConstructClass):
+ subcon = Struct(
+ "unk_0" / Int64ul,
+ "clear_pipeline" / Start3DClearPipelineBinding,
+ "unk_18" / Int64ul,
+ "scissor_array" / Int64ul,
+ "depth_bias_array" / Int64ul,
+ "aux_fb" / AuxFBInfo,
+ "depth_dimensions" / Int64ul,
+ "visibility_result_buffer" / Int64ul,
+ "depth_flags" / Int64ul, # 0x40000 - has stencil 0x80000 - has depth
+ Ver("G >= G14", "unk_58_g14_0" / Int64ul),
+ Ver("G >= G14", "unk_58_g14_8" / Int64ul),
+ "depth_buffer_ptr1" / Int64ul,
+ "depth_buffer_ptr2" / Int64ul,
+ "stencil_buffer_ptr1" / Int64ul,
+ "stencil_buffer_ptr2" / Int64ul,
+ Ver("G >= G14", "unk_68_g14_0" / HexDump(Bytes(0x20))),
+ "unk_78" / Array(4, Int64ul),
+ "depth_aux_buffer_ptr1" / Int64ul,
+ "unk_a0" / Int64ul,
+ "depth_aux_buffer_ptr2" / Int64ul,
+ "unk_b0" / Int64ul,
+ "stencil_aux_buffer_ptr1" / Int64ul,
+ "unk_c0" / Int64ul,
+ "stencil_aux_buffer_ptr2" / Int64ul,
+ "unk_d0" / Int64ul,
+ "tvb_tilemap" / Int64ul,
+ "tvb_heapmeta_addr" / Int64ul,
+ "unk_e8" / Int64ul,
+ "tvb_heapmeta_addr2" / Int64ul,
+ "unk_f8" / Int64ul,
+ "aux_fb_ptr" / Int64ul,
+ "unk_108" / Array(6, Int64ul),
+ "pipeline_base" / Int64ul,
+ "unk_140" / Int64ul,
+ "unk_148" / Int64ul,
+ "unk_150" / Int64ul,
+ "unk_158" / Int64ul,
+ "unk_160" / Int64ul,
+ Ver("G < G14", "unk_168_padding" / HexDump(Bytes(0x1d8))),
+ Ver("G >= G14", "unk_198_padding" / HexDump(Bytes(0x1a8))),
+ Ver("V < V13_0B4", ZPadding(8)),
+ )
+
+class BufferThing(ConstructClass):
+ subcon = Struct(
+ "unk_0" / Int64ul,
+ "unk_8" / Int64ul,
+ "unk_10" / Int64ul,
+ "unkptr_18" / Int64ul,
+ "unk_20" / Int32ul,
+ "bm_misc_addr" / Int64ul,
+ "bm_misc" / ROPointer(this.bm_misc_addr, BufferManagerMisc),
+ "unk_2c" / Int32ul,
+ "unk_30" / Int64ul,
+ "unk_38" / Int64ul,
+ )
+
+class Start3DStruct6(ConstructClass):
+ subcon = Struct(
+ "tvb_overflow_count" / Int64ul,
+ "unk_8" / Int32ul,
+ "unk_c" / Int32ul,
+ "unk_10" / Int32ul,
+ "encoder_id" / Int64ul,
+ "unk_1c" / Int32ul,
+ "unknown_buffer" / Int64ul,
+ "unk_28" / Int64ul,
+ "unk_30" / Int32ul,
+ "unk_34" / Int64ul,
+ )
+
+class Start3DStruct7(ConstructClass):
+ subcon = Struct(
+ "unk_0" / Int64ul,
+ "stamp1_addr" / WrappedPointer, # same contents as below
+ "stamp1" / ROPointer(this.stamp1_addr.value, StampCounter),
+ "stamp2_addr" / WrappedPointer, # same as FinalizeComputeCmd.stamp - some kind of fence/token
+ "stamp2" / ROPointer(this.stamp2_addr.value, StampCounter),
+ "stamp_value" / Int32ul,
+ "ev_3d" / Int32ul,
+ "evctl_index" / Int32ul,
+ "unk_24" / Int32ul,
+ "uuid" / Int32ul,
+ "prev_stamp_value" / Int32ul,
+ "unk_30" / Int32ul,
+ )
+
+ def __init__(self):
+ super().__init__()
+ self.stamp1_addr = StampCounter()
+ self.stamp2_addr = StampCounter()
+
+class Attachment(ConstructClass):
+ subcon = Struct(
+ "address" / Int64ul,
+ "size" / Int32ul,
+ "unk_c" / Int16ul,
+ "unk_e" / Int16ul,
+ )
+
+ def __init__(self, addr, size, unk_c, unk_e):
+ self.address = addr
+ self.size = size
+ self.unk_c = unk_c
+ self.unk_e = unk_e
+
+class Start3DCmd(ConstructClass):
+ subcon = Struct( # 0x194 bytes''''
+ "magic" / Const(0x24, Int32ul),
+ "struct1_addr" / Int64ul, # empty before run. Output? WorkCommand3D + 0x3c0
+ "struct1" / ROPointer(this.struct1_addr, Start3DStruct1),
+ "struct2_addr" / Int64ul, # ?? WorkCommand3D + 0x78
+ "struct2" / ROPointer(this.struct2_addr, Start3DStruct2),
+ "buf_thing_addr" / Int64ul,
+ "buf_thing" / ROPointer(this.buf_thing_addr, BufferThing),
+ "stats_ptr" / Int64ul,
+ "busy_flag_ptr" / Int64ul, # 4 bytes
+ "struct6_addr" / Int64ul, # 0x3c bytes
+ "struct6" / ROPointer(this.struct6_addr, Start3DStruct6),
+ "struct7_addr" / Int64ul, # 0x34 bytes
+ "struct7" / ROPointer(this.struct7_addr, Start3DStruct7),
+ "cmdqueue_ptr" / Int64ul, # points back to the CommandQueueInfo that this command came from
+ "workitem_ptr" / Int64ul, # points back at the WorkItem that this command came from
+ "context_id" / Int32ul,
+ "unk_50" / Int32ul,
+ "event_generation" / Int32ul,
+ "buffer_mgr_slot" / Int32ul,
+ "unk_5c" / Int32ul,
+ "prev_stamp_value" / Int64ul, # 0
+ "unk_68" / Int32ul, # 0
+ "unk_buf_ptr" / Int64ul,
+ "unk_buf2_ptr" / Int64ul, # 0x18 bytes
+ "unk_7c" / Int32ul,
+ "unk_80" / Int32ul,
+ "unk_84" / Int32ul,
+ "uuid" / Int32ul, # uuid for tracking
+ "attachments" / Array(16, Attachment),
+ "num_attachments" / Int32ul,
+ "unk_190" / Int32ul,
+ Ver("V >= V13_0B4", "unk_194" / Int64ul),
+ Ver("V >= V13_0B4", "unkptr_19c" / Int64ul),
+ )
+
+
+class Finalize3DCmd(ConstructClass):
+ subcon = Struct( # 0x9c bytes
+ "magic" / Const(0x25, Int32ul),
+ "uuid" / Int32ul, # uuid for tracking
+ "unk_8" / Int32ul, # 0
+ "stamp_addr" / Int64ul,
+ "stamp" / ROPointer(this.stamp_addr, StampCounter),
+ "stamp_value" / Int32ul,
+ "unk_18" / Int32ul,
+ "buf_thing_addr" / Int64ul,
+ "buf_thing" / ROPointer(this.buf_thing_addr, BufferThing),
+ "buffer_mgr_addr" / Int64ul,
+ "buffer_mgr" / ROPointer(this.buffer_mgr_addr, BufferManagerInfo),
+ "unk_2c" / Int64ul, # 1
+ "stats_ptr" / Int64ul,
+ "struct7_addr" / Int64ul,
+ "struct7" / ROPointer(this.struct7_addr, Start3DStruct7),
+ "busy_flag_ptr" / Int64ul,
+ "cmdqueue_ptr" / Int64ul,
+ "workitem_ptr" / Int64ul,
+ "unk_5c" / Int64ul,
+ "unk_buf_ptr" / Int64ul, # Same as Start3DCmd.unkptr_6c
+ "unk_6c" / Int64ul, # 0
+ "unk_74" / Int64ul, # 0
+ "unk_7c" / Int64ul, # 0
+ "unk_84" / Int64ul, # 0
+ "unk_8c" / Int64ul, # 0
+ Ver("G >= G14", "unk_8c_g14" / Int64ul),
+ "restart_branch_offset" / Int32sl,
+ "unk_98" / Int32ul, # 1
+ Ver("V >= V13_0B4", "unk_9c" / HexDump(Bytes(0x10))),
+ )
+
+class TilingParameters(ConstructClass):
+ subcon = Struct(
+ "size1" / Int32ul,
+ "unk_4" / Int32ul,
+ "unk_8" / Int32ul,
+ "x_max" / Dec(Int16ul),
+ "y_max" / Dec(Int16ul),
+ "tile_count" / Int32ul,
+ "x_blocks" / Int32ul,
+ "y_blocks" / Int32ul,
+ "size2" / Int32ul,
+ "size3" / Int32ul,
+ "unk_24" / Int32ul,
+ "unk_28" / Int32ul,
+ )
+
+class StartTACmdStruct2(ConstructClass):
+ subcon = Struct(
+ "unk_0" / Hex(Int64ul),
+ "unk_8" / Hex(Int32ul),
+ "unk_c" / Hex(Int32ul),
+ "tvb_tilemap" / Hex(Int64ul),
+ Ver("G < G14", "tvb_cluster_tilemaps" / Hex(Int64ul)),
+ "tpc" / Hex(Int64ul),
+ "tvb_heapmeta_addr" / Hex(Int64ul), # like Start3DStruct2.tvb_end_addr with bit 63 set?
+ "iogpu_unk_54" / Int32ul,
+ "iogpu_unk_55" / Int32ul,
+ "iogpu_unk_56" / Int64ul,
+ Ver("G < G14", "tvb_cluster_meta1" / Int64ul),
+ "unk_48" / Int64ul,
+ "unk_50" / Int64ul,
+ "tvb_heapmeta_addr2" / Int64ul,
+ Ver("G < G14", "unk_60" / Int64ul),
+ Ver("G < G14", "core_mask" / Int64ul),
+ "iogpu_deflake_1" / Int64ul,
+ "iogpu_deflake_2" / Int64ul,
+ "unk_80" / Int64ul,
+ "iogpu_deflake_3" / Int64ul, # bit 50 set
+ "encoder_addr" / Int64ul,
+ Ver("G < G14", "tvb_cluster_meta2" / Int64ul),
+ Ver("G < G14", "tvb_cluster_meta3" / Int64ul),
+ Ver("G < G14", "tiling_control" / Int64ul),
+ "unk_b0" / Array(6, Hex(Int64ul)),
+ "pipeline_base" / Int64ul,
+ Ver("G < G14", "tvb_cluster_meta4" / Int64ul),
+ Ver("G < G14", "unk_f0" / Int64ul),
+ "unk_f8" / Int64ul,
+ "unk_100" / Array(3, Hex(Int64ul)),
+ "unk_118" / Int32ul,
+ Ver("G >= G14", Padding(8 * 9)),
+ )
+
+class StartTACmdStruct3(ConstructClass):
+ subcon = Struct(
+ "unk_480" / Array(6, Int32ul),
+ "unk_498" / Int64ul,
+ "unk_4a0" / Int32ul,
+ "iogpu_deflake_1" / Int64ul,
+ "unk_4ac" / Int32ul,
+ "unk_4b0" / Int64ul,
+ "unk_4b8" / Int32ul,
+ "unk_4bc" / Int64ul,
+ "unk_4c4_padding" / HexDump(Bytes(0x48)),
+ "unk_50c" / Int32ul,
+ "unk_510" / Int64ul,
+ "unk_518" / Int64ul,
+ "unk_520" / Int64ul,
+ "unk_528" / Int32ul,
+ "unk_52c" / Int32ul,
+ "unk_530" / Int32ul,
+ "encoder_id" / Int32ul,
+ "unk_538" / Int32ul,
+ "unk_53c" / Int32ul,
+ "unknown_buffer" / Int64ul,
+ "unk_548" / Int64ul,
+ "unk_550" / Array(6, Int32ul),
+ "stamp1_addr" / WrappedPointer, # same contents as below
+ "stamp1" / ROPointer(this.stamp1_addr.value, StampCounter),
+ "stamp2_addr" / WrappedPointer, # same as FinalizeComputeCmd.stamp - some kind of fence/token
+ "stamp2" / ROPointer(this.stamp2_addr.value, StampCounter),
+ "stamp_value" / Int32ul,
+ "ev_ta" / Int32ul,
+ "evctl_index" / Int32ul, # 0-3
+ "unk_584" / Int32ul,
+ "uuid2" / Int32ul,
+ "prev_stamp_value" / Int32ul,
+ "unk_590" / Int32ul,
+ )
+
+ def __init__(self):
+ super().__init__()
+ self.stamp1_addr = StampCounter()
+ self.stamp2_addr = StampCounter()
+
+class StartTACmd(ConstructClass):
+ subcon = Struct(
+ "magic" / Const(0x22, Int32ul),
+ "tiling_params_addr" / Int64ul,
+ "tiling_params" / ROPointer(this.tiling_params_addr, TilingParameters),
+ "struct2_addr" / Int64ul,
+ "struct2" / ROPointer(this.struct2_addr, StartTACmdStruct2),
+ "buffer_mgr_addr" / Int64ul,
+ "buffer_mgr" / ROPointer(this.buffer_mgr_addr, BufferManagerInfo),
+ "buf_thing_addr" / Int64ul,
+ "buf_thing" / ROPointer(this.buf_thing_addr, BufferThing),
+ "stats_ptr" / Int64ul,
+ "cmdqueue_ptr" / Int64ul,
+ "context_id" / Int32ul,
+ "unk_38" / Int32ul,
+ "event_generation" / Int32ul,
+ "buffer_mgr_slot" / Int64ul,
+ "unk_48" / Int64ul,
+ "unk_50" / Int32ul,
+ "struct3_addr" / Int64ul,
+ "struct3" / ROPointer(this.struct3_addr, StartTACmdStruct3),
+ "unkptr_5c" / Int64ul,
+ "unk_5c" / ROPointer(this.unkptr_5c, HexDump(Bytes(0x18))),
+ "unk_64" / Int32ul,
+ "unk_68" / Int32ul,
+ "uuid" / Int32ul,
+ "unk_70" / Int32ul,
+ "unk_74" / Array(29, Int64ul),
+ "unk_15c" / Int32ul,
+ "unk_160" / Int64ul,
+ "unk_168" / Int32ul,
+ "unk_16c" / Int32ul,
+ "unk_170" / Int64ul,
+ "unk_178" / Int32ul,
+ Ver("V >= V13_0B4", "unk_17c" / Int32ul),
+ Ver("V >= V13_0B4", "unkptr_180" / Int64ul),
+ Ver("V >= V13_0B4", "unk_188" / Int32ul),
+ )
+
+class FinalizeTACmd(ConstructClass):
+ subcon = Struct(
+ "magic" / Const(0x23, Int32ul),
+ "buf_thing_addr" / Int64ul,
+ "buf_thing" / ROPointer(this.buf_thing_addr, BufferThing),
+ "buffer_mgr_addr" / Int64ul,
+ "buffer_mgr" / ROPointer(this.buffer_mgr_addr, BufferManagerInfo),
+ "stats_ptr" / Int64ul,
+ "cmdqueue_ptr" / Int64ul, #
+ "context_id" / Int32ul,
+ "unk_28" / Int32ul,
+ "struct3_addr" / Int64ul,
+ "struct3" / ROPointer(this.struct3_addr, StartTACmdStruct3),
+ "unk_34" / Int32ul,
+ "uuid" / Int32ul,
+ "stamp_addr" / Int64ul,
+ "stamp" / ROPointer(this.stamp_addr, StampCounter),
+ "stamp_value" / Int32ul,
+ "unk_48" / Int64ul,
+ "unk_50" / Int32ul,
+ "unk_54" / Int32ul,
+ "unk_58" / Int64ul,
+ "unk_60" / Int32ul,
+ "unk_64" / Int32ul,
+ "unk_68" / Int32ul,
+ Ver("G >= G14", "unk_6c_g14" / Int64ul),
+ "restart_branch_offset" / Int32sl,
+ "unk_70" / Int32ul,
+ Ver("V >= V13_0B4", "unk_74" / HexDump(Bytes(0x10))),
+ )
+
+class ComputeArgs(ConstructClass):
+ subcon = Struct(
+ unk = Bytes(0x7fa0),
+ arg_buffers = Array(8, Int64ul),
+ threadgroups_per_grid_addr = Int64ul,
+ threads_per_threadgroup_addr = Int64ul,
+ )
+
+class ComputeInfo(ConstructClass):
+ # Only the cmdlist and pipelinebase and cmdlist fields are strictly needed to launch a basic
+ # compute shader.
+ subcon = Struct( # 0x1c bytes
+ "args" / Int64ul, # ComputeArgs
+ "cmdlist" / Int64ul, # CommandList from userspace
+ "unkptr_10" / Int64ul, # size 8, null
+ "unkptr_18" / Int64ul, # size 8, null
+ "unkptr_20" / Int64ul, # size 8, null
+ "unkptr_28" / Int64ul, #
+ "pipeline_base" / Int64ul, # 0x11_00000000: Used for certain "short" pointers like pipelines (and shaders?)
+ "unk_38" / Int64ul, # always 0x8c60.
+ "unk_40" / Int32ul, # 0x41
+ "unk_44" / Int32ul, # 0
+ "unkptr_48" / Int64ul, # related to threadgroups / thread layout
+ "unk_50" / Int32ul, # 0x40 - Size?
+ "unk_54" / Int32ul, # 0
+ "unk_58" / Int32ul, # 1
+ "unk_5c" / Int32ul, # 0
+ "unk_60" / Int32ul, # 0x1c
+ )
+
+# Related to "IOGPU Misc"
+class ComputeInfo2(ConstructClass):
+ subcon = Struct(
+ unk_0 = HexDump(Bytes(0x24)),
+ unkptr_24 = Int64ul, # equal to args
+ unkptr_2c = Int64ul, # points at end of cmdlist?
+ unk_34 = HexDump(Bytes(0x38)),
+ encoder_id = Int32ul,
+ unk_70 = Int32ul,
+ unk_74 = Int32ul,
+ unknown_buffer = Int64ul,
+ unk_80 = Int32ul,
+ unk_84 = Int32ul,
+ unk_88 = Int32ul,
+ stamp1_addr = Int64ul, # same contents as below
+ stamp1 = ROPointer(this.stamp1_addr, Hex(Int32ul)),
+ stamp2_addr = Int64ul, # same as FinalizeComputeCmd.stamp - some kind of fence/token
+ stamp2 = ROPointer(this.stamp2_addr, Hex(Int32ul)),
+ stamp_value = Int32ul,
+ unk_a0 = Int32ul,
+ unk_a4 = Int32ul,
+ unk_a8 = Int32ul,
+ uuid = Int32ul,
+ unk_b0 = Int32ul,
+ )
+
+class StartComputeCmd(ConstructClass):
+ subcon = Struct( # 0x154 bytes''''
+ "magic" / Const(0x29, Int32ul),
+ "unkptr_4" / Int64ul, # empty: WorkCommandCP + 0x14, size: 0x54
+ "computeinfo_addr" / Int64ul, # List of userspace VAs: WorkCommandCP + 0x68
+ "computeinfo" / ROPointer(this.computeinfo_addr, ComputeInfo),
+ "unkptr_14" / Int64ul, # In gpu-asc's heap? Did this pointer come from the gfx firmware?
+ "cmdqueue_ptr" / Int64ul, # points back to the submitinfo that this command came from
+ "context_id" / Int32ul, # 4
+ "unk_28" / Int32ul, # 1
+ "unk_2c" / Int32ul, # 0
+ "unk_30" / Int32ul,
+ "unk_34" / Int32ul,
+ "unk_38" / Int32ul,
+ "computeinfo2_addr" / Int64ul, # WorkCommandCP + 0x1f4
+ "computeinfo2" / ROPointer(this.computeinfo2_addr, ComputeInfo2),
+ "unk_44" / Int32ul,
+ "uuid" / Int32ul, # uuid for tracking?
+ "padding" / Bytes(0x154 - 0x4c),
+ )
+
+class FinalizeComputeCmd(ConstructClass):
+ subcon = Struct( # 0x64 bytes''''
+ "magic" / Const(0x2a, Int32ul),
+ "unkptr_4" / Int64ul, # same as ComputeStartCmd.unkptr_14
+ "cmdqueue_ptr" / Int64ul, # points back to the submitinfo
+ "unk_14" / Int32ul, # Context ID?
+ "unk_18" / Int32ul,
+ "unkptr_1c" / Int64ul, # same as ComputeStartCmd.unkptr_3c
+ "unk_24" / Int32ul,
+ "uuid" / Int32ul, # uuid for tracking?
+ "stamp" / Int64ul,
+ "stamp_value" / Int32ul, # Gets written to unkptr_2c (after stamp?)
+ "unk_38" / Int32ul,
+ "unk_3c" / Int32ul,
+ "unk_40" / Int32ul,
+ "unk_44" / Int32ul,
+ "unk_48" / Int32ul,
+ "unk_4c" / Int32ul,
+ "unk_50" / Int32ul,
+ "unk_54" / Int32ul,
+ "unk_58" / Int32ul,
+ Ver("G >= G14", "unk_5c_g14" / Int64ul),
+ "restart_branch_offset" / Int32sl, # realative offset from start of Finalize to StartComputeCmd
+ "unk_60" / Int32ul,
+ )
+
+class EndCmd(ConstructClass):
+ subcon = Struct(
+ "magic" / Const(0x18, Int8ul),
+ "unk_1" / Int8ul,
+ "unk_2" / Int8ul,
+ "flags" / Int8ul,
+ )
+
+ def __init__(self):
+ super().__init__()
+ self.unk_1 = 0
+ self.unk_2 = 0
+ self.flags = 0x40
+
+class TimestampCmd(ConstructClass):
+ subcon = Struct( # 0x34 bytes
+ "magic" / Const(0x19, Int8ul),
+ "unk_1" / Int8ul,
+ "unk_2" / Int8ul,
+ "unk_3" / Int8ul, # Sometimes 0x80
+ # all these pointers point to 0xfa0... addresses. Might be where the timestamp should be writen?
+ "ts0_addr" / Int64ul,
+ "ts0" / ROPointer(this.ts0_addr, TimeStamp),
+ "ts1_addr" / Int64ul,
+ "ts1" / ROPointer(this.ts1_addr, TimeStamp),
+ "ts2_addr" / Int64ul,
+ "ts2" / ROPointer(this.ts2_addr, TimeStamp),
+ "cmdqueue_ptr" / Int64ul,
+ "unk_24" / Int64ul,
+ Ver("V >= V13_0B4", "unkptr_2c_0" / Int64ul),
+ "uuid" / Int32ul,
+ "unk_30_padding" / Int32ul,
+ )
+
+class WaitForInterruptCmd(ConstructClass):
+ subcon = Struct(
+ "magic" / Const(0x01, Int8ul),
+ "unk_1" / Int8ul,
+ "unk_2" / Int8ul,
+ "unk_3" / Int8ul,
+ )
+
+ def __init__(self, unk_1, unk_2, unk_3):
+ super().__init__()
+ self.unk_1 = unk_1
+ self.unk_2 = unk_2
+ self.unk_3 = unk_3
+
+class NopCmd(ConstructClass):
+ # This doesn't exist
+ subcon = Struct(
+ "magic" / Const(0x00, Int32ul),
+ )
+
+ def __str__(self) -> str:
+ return "Nop"
+
+
+class MicroSequence(ConstructValueClass):
+ subcon = RepeatUntil(lambda obj, lst, ctx: lst[-1].cmdid == 0x18,
+ Struct(
+ "cmdid" / Peek(Int8ul),
+ "cmd" / Switch(this.cmdid, {
+ 0x01: WaitForInterruptCmd,
+ 0x18: EndCmd,
+ 0x19: TimestampCmd,
+ 0x22: StartTACmd,
+ 0x23: FinalizeTACmd,
+ 0x24: Start3DCmd,
+ 0x25: Finalize3DCmd,
+ 0x29: StartComputeCmd,
+ 0x2a: FinalizeComputeCmd,
+ }, default=Error)
+ )
+ )
+
+ def __str__(self):
+ s = "{\n"
+ for cmd in self.value:
+ s += str(cmd.cmd) + '\n'
+ if isinstance(cmd.cmd, EndCmd):
+ s += "}\n"
+ break
+ else:
+ s += "?\n"
+ return s
+
+__all__.extend(k for k, v in globals().items()
+ if (callable(v) or isinstance(v, type)) and v.__module__ == __name__)
diff --git a/tools/proxyclient/m1n1/fw/aop/__init__.py b/tools/proxyclient/m1n1/fw/aop/__init__.py
new file mode 100644
index 0000000..6828232
--- /dev/null
+++ b/tools/proxyclient/m1n1/fw/aop/__init__.py
@@ -0,0 +1,31 @@
+# SPDX-License-Identifier: MIT
+from .bootargs import ASCArgumentSection
+
+class AOPBase:
+ def __init__(self, u, adtnode):
+ self.fw_base, self.fw_len = adtnode.get_reg(2)
+ if u.adt["arm-io"].compatible[0] == "arm-io,t6000":
+ # argh
+ self.fw_base -= 0x2_0000_0000
+
+ @property
+ def _bootargs_span(self):
+ base = self.fw_base + self.u.proxy.read32(self.fw_base + 0x224)
+ length = self.u.proxy.read32(self.fw_base + 0x228)
+
+ return (base, length)
+
+ def read_bootargs(self):
+ blob = self.u.proxy.iface.readmem(*self._bootargs_span)
+ return ASCArgumentSection(blob)
+
+ def write_bootargs(self, args):
+ base, _ = self._bootargs_span
+ self.u.proxy.iface.writemem(base, args.to_bytes())
+
+ def update_bootargs(self, keyvals):
+ args = self.read_bootargs()
+ args.update(keyvals)
+ self.write_bootargs(args)
+
+__all__ = ["ASCArgumentSection", "AOPBase"]
diff --git a/tools/proxyclient/m1n1/fw/aop/bootargs.py b/tools/proxyclient/m1n1/fw/aop/bootargs.py
new file mode 100644
index 0000000..100e4f3
--- /dev/null
+++ b/tools/proxyclient/m1n1/fw/aop/bootargs.py
@@ -0,0 +1,64 @@
+# SPDX-License-Identifier: MIT
+
+class ASCArgumentSection:
+ def __init__(self, bytes_):
+ self.blob = bytearray(bytes_)
+ self.index = self.build_index()
+
+ def build_index(self):
+ off = 0
+ fields = []
+ while off < len(self.blob):
+ snip = self.blob[off:]
+ key = snip[0:4]
+ length = int.from_bytes(snip[4:8], byteorder='little')
+ fields.append((key.decode('ascii'), (off + 8, length)))
+ off += 8 + length
+
+ if off > len(self.blob):
+ raise ValueError('blob overran during parsing')
+
+ return dict(fields)
+
+ def items(self):
+ for key, span in self.index.items():
+ off, length = span
+ yield key, self.blob[off:off + length]
+
+ def __getitem__(self, key):
+ off, length = self.index[key]
+ return bytes(self.blob[off:off + length])
+
+ def __setitem__(self, key, value):
+ off, length = self.index[key]
+
+ if type(value) is int:
+ value = int.to_bytes(value, length, byteorder='little')
+ elif type(value) is str:
+ value = value.encode('ascii')
+
+ if len(value) > length:
+ raise ValueError(f'field {key:s} overflown')
+
+ self.blob[off:off + length] = value
+
+ def update(self, keyvals):
+ for key, val in keyvals.items():
+ self[key] = val
+
+ def keys(self):
+ return self.index.keys()
+
+ def dump(self):
+ for key, val in self.items():
+ print(f"{key:4s} = {val}")
+
+ def dump_diff(self, other, logger):
+ assert self.index == other.index
+
+ for key in self.keys():
+ if self[key] != other[key]:
+ logger(f"\t{key:4s} = {self[key]} -> {other[key]}")
+
+ def to_bytes(self):
+ return bytes(self.blob)
diff --git a/tools/proxyclient/m1n1/fw/aop/ipc.py b/tools/proxyclient/m1n1/fw/aop/ipc.py
new file mode 100644
index 0000000..d26315d
--- /dev/null
+++ b/tools/proxyclient/m1n1/fw/aop/ipc.py
@@ -0,0 +1,365 @@
+from enum import IntEnum
+from construct import *
+from io import BytesIO
+
+from m1n1.utils import FourCC, chexdump
+from m1n1.constructutils import ZPadding
+from m1n1.fw.afk.epic import EPICCmd, EPICCategory
+
+
+EPICSubHeaderVer2 = Struct(
+ "length" / Int32ul,
+ "version" / Default(Int8ul, 2),
+ "category" / EPICCategory,
+ "type" / Hex(Int16ul),
+ "timestamp" / Default(Int64ul, 0),
+ "unk1" / Default(Hex(Int32ul), 0),
+ "unk2" / Default(Hex(Int32ul), 0),
+)
+
+class AOPAudioPropKey(IntEnum):
+ IS_READY = 0x01
+
+ UNK_11 = 0x11
+ PLACEMENT = 0x1e
+ UNK_21 = 0x21
+ ORIENTATION = 0x2e
+ LOCATION_ID = 0x30
+ SERIAL_NO = 0x3e
+ VENDOR_ID = 0x5a
+ PRODUCT_ID = 0x5b
+
+ SERVICE_CONTROLLER = 0x64
+ DEVICE_COUNT = 0x65
+
+ VERSION = 0x67
+
+class EPICCall:
+ @classmethod
+ def matches(cls, hdr, sub):
+ return int(sub.type) == cls.TYPE
+
+ def _args_fixup(self):
+ pass
+
+ def __init__(self, *args, **kwargs):
+ if args:
+ self.args = args[0]
+ else:
+ self.args = Container(**kwargs)
+ self._args_fixup()
+ self.rets = None
+
+ @classmethod
+ def from_stream(cls, f):
+ return cls(cls.ARGS.parse_stream(f))
+
+ def dump(self, logger=None):
+ if logger is None:
+ logger = print
+ args_fmt = [f"{k}={v}" for (k, v) in self.args.items() if k != "_io"]
+ rets_fmt = [f"{k}={v}" for (k, v) in self.rets.items() if k != "_io"]
+ logger(f"{type(self).__name__}({', '.join(args_fmt)}) -> ({', '.join(rets_fmt)})")
+
+ def read_resp(self, f):
+ self.rets = self.RETS.parse_stream(f)
+
+CALLTYPES = []
+def reg_calltype(calltype):
+ CALLTYPES.append(calltype)
+ return calltype
+
+@reg_calltype
+class GetHIDDescriptor(EPICCall):
+ TYPE = 0x1
+ ARGS = Struct(
+ "blank" / Const(0x0, Int32ul),
+ )
+ RETS = Struct(
+ "retcode" / Default(Hex(Int32ul), 0),
+ "descriptor" / HexDump(GreedyBytes),
+ )
+
+@reg_calltype
+class GetProperty(EPICCall):
+ TYPE = 0xa
+ ARGS = Struct(
+ "blank" / Const(0x0, Int32ul),
+ "key" / Enum(Int32ul, AOPAudioPropKey),
+ )
+ RETS = Struct(
+ #"blank" / Const(0x0, Int32ul),
+ "value" / GreedyBytes,
+ )
+
+@reg_calltype
+class WrappedCall(EPICCall):
+ SUBCLASSES = {}
+ TYPE = 0x20
+ HDR = Struct(
+ "blank" / Const(0x0, Int32ul),
+ "unk1" / Hex(Const(0xffffffff, Int32ul)),
+ "calltype" / Hex(Int32ul),
+ "blank2" / ZPadding(16),
+ "pad" / Hex(Int32ul),
+ "len" / Hex(Int64ul),
+ "residue" / HexDump(GreedyBytes),
+ )
+
+ @classmethod
+ def from_stream(cls, f):
+ payload = f.read()
+ subsub = cls.HDR.parse(payload)
+ calltype = int(subsub.calltype)
+ subcls = cls.SUBCLASSES.get(calltype, None)
+ if subcls is None:
+ raise ValueError(f"unknown calltype {calltype:#x}")
+ return subcls(subcls.ARGS.parse(payload))
+
+ @classmethod
+ def reg_subclass(cls, cls2):
+ cls.SUBCLASSES[int(cls2.CALLTYPE)] = cls2
+ return cls2
+
+ @classmethod
+ def matches(cls, hdr, sub):
+ return sub.category == EPICCategory.NOTIFY and sub.type == cls.TYPE
+
+ def check_retcode(self):
+ if self.rets.retcode:
+ self.dump()
+ raise ValueError(f"retcode {self.rets.retcode} in {str(type(self))} (call dumped, see above)")
+
+@WrappedCall.reg_subclass
+class AttachDevice(WrappedCall):
+ CALLTYPE = 0xc3_00_00_02
+ ARGS = Struct(
+ "blank" / Const(0x0, Int32ul),
+ "unk1" / Hex(Const(0xffffffff, Int32ul)),
+ "calltype" / Hex(Const(0xc3000002, Int32ul)),
+ "blank2" / ZPadding(16),
+ "pad" / Padding(4),
+ "len" / Hex(Const(0x2c, Int64ul)),
+ "devid" / FourCC,
+ "pad" / Padding(4),
+ )
+ RETS = Struct(
+ "retcode" / Default(Hex(Int32ul), 0),
+ "unk" / HexDump(GreedyBytes),
+ )
+
+@WrappedCall.reg_subclass
+class ProbeDevice(WrappedCall):
+ CALLTYPE = 0xc3_00_00_01
+ ARGS = Struct(
+ "blank" / Const(0x0, Int32ul),
+ "unk1" / Hex(Const(0xffffffff, Int32ul)),
+ "calltype" / Hex(Const(0xc3000001, Int32ul)),
+ "blank2" / ZPadding(16),
+ "pad" / Padding(4),
+ "len" / Hex(Const(0x28, Int64ul)),
+ "devno" / Int32ul,
+ )
+ RETS = Struct(
+ "retcode" / Default(Hex(Int32ul), 0),
+ "devid" / FourCC,
+ "blank2" / Const(0x0, Int32ul),
+ "unk1" / Const(8, Int32ul),
+ "blank3" / Const(0x0, Int32ul),
+ "unk2" / Hex(Const(0x01_0d_1c_20, Int32ul)),
+ "blank4" / Const(0x0, Int32ul),
+ "remainder" / HexDump(GreedyBytes),
+ )
+
+PDMConfig = Struct(
+ "unk1" / Int32ul,
+ "clockSource" / FourCC,
+ "pdmFrequency" / Int32ul,
+ "unk3_clk" / Int32ul,
+ "unk4_clk" / Int32ul,
+ "unk5_clk" / Int32ul,
+ "channelPolaritySelect" / Hex(Int32ul),
+ "unk7" / Hex(Int32ul),
+ "unk8" / Hex(Int32ul),
+ "unk9" / Hex(Int16ul),
+ "ratios" / Struct(
+ "r1" / Int8ul,
+ "r2" / Int8ul,
+ "r3" / Int8ul,
+ "pad" / Default(Int8ul, 0),
+ ),
+ "filterLengths" / Hex(Int32ul),
+ "coeff_bulk" / Int32ul,
+ #"coefficients" / Struct(
+ # "c1" / Int32sl[this._.ratios.r3 * 4 + 4],
+ # "c2" / Int32sl[this._.ratios.r2 * 4 + 4],
+ # "c3" / Int32sl[this._.ratios.r1 * 4 + 4],
+ #),
+ #"junk" / Padding(
+ # this.coeff_bulk * 4 - 48 \
+ # - (this.ratios.r1 + this.ratios.r2 + this.ratios.r3) * 16
+ #),
+ "coefficients" / Int32sl[
+ (this.ratios.r1 + this.ratios.r2 + this.ratios.r3) * 4 + 12
+ ],
+ "junk" / Padding(
+ lambda this: max(0,
+ this.coeff_bulk * 4 - 48 \
+ - (this.ratios.r1 + this.ratios.r2 + this.ratios.r3) * 16
+ )
+ ),
+ "unk10" / Int32ul, # maybe
+ "micTurnOnTimeMs" / Int32ul,
+ "blank" / ZPadding(16),
+ "unk11" / Int32ul,
+ "micSettleTimeMs" / Int32ul,
+ "blank2" / ZPadding(69),
+)
+
+DecimatorConfig = Struct(
+ "latency" / Int32ul,
+ "ratios" / Struct(
+ "r1" / Int8ul,
+ "r2" / Int8ul,
+ "r3" / Int8ul,
+ "pad" / Default(Int8ul, 0),
+ ),
+ "filterLengths" / Hex(Int32ul),
+ "coeff_bulk" / Int32ul,
+ "coefficients" / Int32sl[
+ (this.ratios.r1 + this.ratios.r2 + this.ratios.r3) * 4 + 12
+ ],
+ "junk" / Padding(
+ lambda this: max(0,
+ this.coeff_bulk * 4 - 48 \
+ - (this.ratios.r1 + this.ratios.r2 + this.ratios.r3) * 16
+ )
+ ),
+)
+
+PowerSetting = Struct(
+ "devid" / FourCC,
+ "cookie" / Int32ul,
+ "pad" / Padding(4),
+ "blank" / ZPadding(8),
+ "target_pstate" / FourCC,
+ "unk2" / Int32ul,
+ "blank2" / ZPadding(20),
+)
+
+DEVPROPS = {
+ ('hpai', 202): PowerSetting,
+ ('lpai', 202): PowerSetting,
+ ('hpai', 200): FourCC,
+ ('lpai', 200): FourCC,
+ ('pdm0', 200): PDMConfig,
+ ('pdm0', 210): DecimatorConfig,
+ ('lpai', 301): Struct(
+ "unk1" / Int32ul,
+ "unk2" / Int32ul,
+ "unk3" / Int32ul,
+ "unk4" / Int32ul,
+ ),
+}
+
+@WrappedCall.reg_subclass
+class GetDeviceProp(WrappedCall):
+ CALLTYPE = 0xc3_00_00_04
+ ARGS = Struct(
+ "blank" / Const(0x0, Int32ul),
+ "unk1" / Hex(Const(0xffffffff, Int32ul)),
+ "calltype" / Hex(Const(0xc3000004, Int32ul)),
+ "blank2" / ZPadding(16),
+ "pad" / Padding(4),
+ "len" / Hex(Const(0x30, Int64ul)),
+ "devid" / FourCC,
+ "modifier" / Int32ul,
+ "unk6" / Hex(Const(0x01, Int32ul)),
+ )
+ RETS = Struct(
+ "retcode" / Default(Hex(Int32ul), 0),
+ "len" / Optional(Int32ul),
+ "data" / Switch(lambda s: (s._params.devid, s._params.modifier),
+ DEVPROPS,
+ default=HexDump(GreedyBytes))
+ )
+
+ def read_resp(self, f):
+ self.rets = self.RETS.parse_stream(f,
+ devid=self.args.devid, modifier=self.args.modifier
+ )
+
+@WrappedCall.reg_subclass
+class SetDeviceProp(WrappedCall):
+ CALLTYPE = 0xc3_00_00_05
+ ARGS = Struct(
+ "blank" / Const(0x0, Int32ul),
+ "unk1" / Hex(Const(0xffffffff, Int32ul)),
+ "calltype" / Hex(Const(0xc3000005, Int32ul)),
+ "blank2" / ZPadding(16),
+ "pad" / Padding(4),
+ "len" / Hex(Int64ul), # len(this.data) + 0x30
+ "devid" / FourCC,
+ "modifier" / Int32ul,
+ "len2" / Hex(Int32ul), # len(this.data)
+ "data" / Switch(lambda s: (s.devid, s.modifier),
+ DEVPROPS,
+ default=HexDump(GreedyBytes))
+ )
+ RETS = Struct(
+ "retcode" / Default(Hex(Int32ul), 0),
+ "unk" / HexDump(GreedyBytes),
+ )
+
+ def _args_fixup(self):
+ data_len = len(self.ARGS.build(Container(len=0, len2=0, **self.args))) - 52
+ if 'len' not in self.args:
+ self.args.len = data_len + 0x30
+ if 'len2' not in self.args:
+ self.args.len2 = data_len
+
+@reg_calltype
+class IndirectCall(EPICCall):
+ ARGS = EPICCmd
+ RETS = EPICCmd
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.txbuf = None
+ self.rxbuf = None
+
+ @classmethod
+ def matches(cls, hdr, sub):
+ return sub.category == EPICCategory.COMMAND
+
+ def read_txbuf(self, ep):
+ cmd = self.args
+ ep.dart.invalidate_cache()
+ self.txbuf = ep.dart.ioread(0, cmd.txbuf, cmd.txlen)
+
+ # dump the command data for offline replays of traces
+ ep.log(f"===COMMAND TX DATA=== addr={cmd.txbuf:#x}")
+ chexdump(self.txbuf)
+ ep.log(f"===END DATA===")
+
+ def read_rxbuf(self, ep):
+ cmd = self.rets
+ ep.dart.invalidate_cache()
+ self.rxbuf = ep.dart.ioread(0, cmd.rxbuf, cmd.rxlen)
+
+ ep.log(f"===COMMAND RX DATA=== addr={cmd.rxbuf:#x}")
+ chexdump(self.rxbuf)
+ ep.log(f"===END DATA===")
+
+ def unwrap(self):
+ fd = BytesIO()
+ fd.write(b"\x00\x00\x00\x00")
+ fd.write(self.txbuf)
+ fd.seek(0)
+ wrapped = WrappedCall.from_stream(fd)
+ fd = BytesIO()
+ fd.write(b"\x00\x00\x00\x00")
+ fd.write(self.rxbuf)
+ fd.seek(0)
+ wrapped.read_resp(fd)
+ return wrapped
diff --git a/tools/proxyclient/m1n1/fw/asc/__init__.py b/tools/proxyclient/m1n1/fw/asc/__init__.py
new file mode 100644
index 0000000..4ffdb8b
--- /dev/null
+++ b/tools/proxyclient/m1n1/fw/asc/__init__.py
@@ -0,0 +1,125 @@
+# SPDX-License-Identifier: MIT
+from ...utils import *
+
+from .crash import ASCCrashLogEndpoint
+from .syslog import ASCSysLogEndpoint
+from .mgmt import ASCManagementEndpoint
+from .kdebug import ASCKDebugEndpoint
+from .ioreporting import ASCIOReportingEndpoint
+from .oslog import ASCOSLogEndpoint
+from .base import ASCBaseEndpoint, ASCTimeout
+from ...hw.asc import ASC
+
+__all__ = []
+
+class ASCDummyEndpoint(ASCBaseEndpoint):
+ SHORT = "dummy"
+
+class StandardASC(ASC):
+ ENDPOINTS = {
+ 0: ASCManagementEndpoint,
+ 1: ASCCrashLogEndpoint,
+ 2: ASCSysLogEndpoint,
+ 3: ASCKDebugEndpoint,
+ 4: ASCIOReportingEndpoint,
+ 8: ASCOSLogEndpoint,
+ 0xa: ASCDummyEndpoint, # tracekit
+ }
+
+ def __init__(self, u, asc_base, dart=None, stream=0):
+ super().__init__(u, asc_base)
+ self.remote_eps = set()
+ self.add_ep(0, ASCManagementEndpoint(self, 0))
+ self.dart = dart
+ self.stream = stream
+ self.eps = []
+ self.epcls = {}
+ self.dva_offset = 0
+ self.dva_size = 1 << 32
+ self.allow_phys = False
+
+ for cls in type(self).mro():
+ eps = getattr(cls, "ENDPOINTS", None)
+ if eps is None:
+ break
+ for k, v in eps.items():
+ if k not in self.epcls:
+ self.epcls[k] = v
+
+ def addr(self, addr):
+ return f"{addr:#x}"
+
+ def iomap(self, addr, size):
+ if self.dart is None:
+ return addr
+ dva = self.dva_offset | self.dart.iomap(self.stream, addr, size)
+
+ self.dart.invalidate_streams(1)
+ return dva
+
+ def ioalloc(self, size):
+ paddr = self.u.memalign(0x4000, size)
+ dva = self.iomap(paddr, size)
+ return paddr, dva
+
+ def ioread(self, dva, size):
+ if self.allow_phys and dva < self.dva_offset or dva >= (self.dva_offset + self.dva_size):
+ return self.iface.readmem(dva, size)
+
+ if self.dart:
+ return self.dart.ioread(self.stream, dva & 0xFFFFFFFFF, size)
+ else:
+ return self.iface.readmem(dva, size)
+
+ def iowrite(self, dva, data):
+ if self.allow_phys and dva < self.dva_offset or dva >= (self.dva_offset + self.dva_size):
+ return self.iface.writemem(dva, data)
+
+ if self.dart:
+ return self.dart.iowrite(self.stream, dva & 0xFFFFFFFFF, data)
+ else:
+ return self.iface.writemem(dva, data)
+
+ def iotranslate(self, dva, size):
+ if self.allow_phys and dva < self.dva_offset or dva >= (self.dva_offset + self.dva_size):
+ return [(dva, size)]
+
+ if self.dart:
+ return self.dart.iotranslate(self.stream, dva & 0xFFFFFFFFF, size)
+ else:
+ return [(dva, size)]
+
+ def start_ep(self, epno):
+ if epno not in self.epcls:
+ raise Exception(f"Unknown endpoint {epno:#x}")
+
+ epcls = self.epcls[epno]
+ ep = epcls(self, epno)
+ self.add_ep(epno, ep)
+ print(f"Starting endpoint #{epno:#x} ({ep.name})")
+ self.mgmt.start_ep(epno)
+ ep.start()
+
+ def start(self):
+ super().boot()
+ self.mgmt.start()
+ self.mgmt.wait_boot(3)
+
+ def stop(self, state=0x10):
+ for ep in list(self.epmap.values())[::-1]:
+ if ep.epnum < 0x10:
+ continue
+ ep.stop()
+ self.mgmt.stop(state=state)
+ self.epmap = {}
+ self.add_ep(0, ASCManagementEndpoint(self, 0))
+ if state < 0x10:
+ self.shutdown()
+
+ def boot(self):
+ print("Booting ASC...")
+ super().boot()
+ self.mgmt.wait_boot(1)
+
+__all__.extend(k for k, v in globals().items()
+ if (callable(v) or isinstance(v, type)) and v.__module__ == __name__)
diff --git a/tools/proxyclient/m1n1/fw/asc/base.py b/tools/proxyclient/m1n1/fw/asc/base.py
new file mode 100644
index 0000000..b10aaaf
--- /dev/null
+++ b/tools/proxyclient/m1n1/fw/asc/base.py
@@ -0,0 +1,59 @@
+# SPDX-License-Identifier: MIT
+from ...utils import *
+
+# System endpoints
+def msg_handler(message, regtype=None):
+ def f(x):
+ x.is_message = True
+ x.message = message
+ x.regtype = regtype
+ return x
+
+ return f
+
+class ASCMessage1(Register64):
+ EP = 7, 0
+
+class ASCTimeout(Exception):
+ pass
+
+class ASCBaseEndpoint:
+ BASE_MESSAGE = Register64
+ SHORT = None
+
+ def __init__(self, asc, epnum, name=None):
+ self.asc = asc
+ self.epnum = epnum
+ self.name = name or self.SHORT or f"{type(self).__name__}@{epnum:#x}"
+
+ self.msghandler = {}
+ self.msgtypes = {}
+ for name in dir(self):
+ i = getattr(self, name)
+ if not callable(i):
+ continue
+ if not getattr(i, "is_message", False):
+ continue
+ self.msghandler[i.message] = i
+ self.msgtypes[i.message] = i.regtype if i.regtype else self.BASE_MESSAGE
+
+ def handle_msg(self, msg0, msg1):
+ msg0 = self.BASE_MESSAGE(msg0)
+ handler = self.msghandler.get(msg0.TYPE, None)
+ regtype = self.msgtypes.get(msg0.TYPE, self.BASE_MESSAGE)
+
+ if handler is None:
+ return False
+ return handler(regtype(msg0.value))
+
+ def send(self, msg):
+ self.asc.send(msg, ASCMessage1(EP=self.epnum))
+
+ def start(self):
+ pass
+
+ def stop(self):
+ pass
+
+ def log(self, msg):
+ print(f"[{self.name}] {msg}")
diff --git a/tools/proxyclient/m1n1/fw/asc/crash.py b/tools/proxyclient/m1n1/fw/asc/crash.py
new file mode 100644
index 0000000..7f590c8
--- /dev/null
+++ b/tools/proxyclient/m1n1/fw/asc/crash.py
@@ -0,0 +1,248 @@
+# SPDX-License-Identifier: MIT
+from .base import *
+from ...utils import *
+from construct import *
+from ...sysreg import *
+
+class CrashLogMessage(Register64):
+ TYPE = 63, 52
+ SIZE = 51, 44
+ DVA = 43, 0
+
+CrashHeader = Struct(
+ "type" / Const("CLHE", FourCC),
+ "ver" / Int32ul,
+ "total_size" / Int32ul,
+ "flags" / Int32ul,
+ Padding(16)
+)
+
+CrashCver = Struct(
+ "uuid" / Bytes(16),
+ "version" / CString("utf8"),
+)
+
+CrashCstr = Struct(
+ "id" / Int32ul,
+ "string" / CString("utf8"),
+)
+
+CrashCtim = Struct(
+ "time" / Int64ul,
+)
+
+CrashCmbx = Struct(
+ "hdr" / Array(4, Hex(Int32ul)),
+ "type" / Int32ul,
+ "unk" / Int32ul,
+ "index" / Int32ul,
+ "messages" / GreedyRange(Struct(
+ "endpoint" / Hex(Int64ul),
+ "message" / Hex(Int64ul),
+ "timestamp" / Hex(Int32ul),
+ Padding(4),
+ )),
+)
+
+CrashCcst = Struct(
+ "task" / Int32ul,
+ "unk" / Int32ul,
+ "stack" / GreedyRange(Int64ul)
+)
+
+CrashCasC = Struct(
+ "l2c_err_sts" / Hex(Int64ul),
+ "l2c_err_adr" / Hex(Int64ul),
+ "l2c_err_inf" / Hex(Int64ul),
+ "lsu_err_sts" / Hex(Int64ul),
+ "fed_err_sts" / Hex(Int64ul),
+ "mmu_err_sts" / Hex(Int64ul)
+)
+
+CrashCrg8 = Struct(
+ "unk_0" / Int32ul,
+ "unk_4" / Int32ul,
+ "regs" / Array(31, Hex(Int64ul)),
+ "sp" / Int64ul,
+ "pc" / Int64ul,
+ "psr" / Int64ul,
+ "cpacr" / Int64ul,
+ "fpsr" / Int64ul,
+ "fpcr" / Int64ul,
+ "unk" / Array(64, Hex(Int64ul)),
+ "far" / Int64ul,
+ "unk_X" / Int64ul,
+ "esr" / Int64ul,
+ "unk_Z" / Int64ul,
+)
+
+CrashEntry = Struct(
+ "type" / FourCC,
+ Padding(4),
+ "flags" / Hex(Int32ul),
+ "len" / Int32ul,
+ "payload" / FixedSized(lambda ctx: ctx.len - 16 if ctx.type != "CLHE" else 16,
+ Switch(this.type, {
+ "Cver": CrashCver,
+ "Ctim": CrashCtim,
+ "Cmbx": CrashCmbx,
+ "Cstr": CrashCstr,
+ "Crg8": CrashCrg8,
+ "Ccst": CrashCcst,
+ "CasC": CrashCasC,
+ }, default=GreedyBytes)),
+)
+
+CrashLog = Struct(
+ "header" / CrashHeader,
+ "entries" / RepeatUntil(this.type == "CLHE", CrashEntry),
+)
+
+class CrashLogParser:
+ def __init__(self, data=None, asc=None):
+ self.asc = asc
+ if data is not None:
+ self.parse(data)
+
+ def parse(self, data):
+ self.data = CrashLog.parse(data)
+ pass
+
+ def default(self, entry):
+ print(f"# {entry.type} flags={entry.flags:#x}")
+ chexdump(entry.payload)
+ print()
+
+ def Ccst(self, entry):
+ print(f"Call stack (task {entry.payload.task}:")
+ for i in entry.payload.stack:
+ if not i:
+ break
+ print(f" - {i:#x}")
+ print()
+
+ def CasC(self, entry):
+ print(f"Async error info:")
+ print(entry.payload)
+ print()
+
+ def Cver(self, entry):
+ print(f"RTKit Version: {entry.payload.version}")
+ print()
+
+ def Crg8(self, entry):
+ print(f"Exception info:")
+
+ ctx = entry.payload
+
+ addr = self.asc.addr
+
+ spsr = SPSR(ctx.psr)
+ esr = ESR(ctx.esr)
+ elr = ctx.pc
+ far_phys = self.asc.iotranslate(ctx.far, 1)[0][0]
+ elr_phys = self.asc.iotranslate(ctx.pc, 1)[0][0]
+ sp_phys = self.asc.iotranslate(ctx.sp, 1)[0][0]
+
+ print(f" == Exception taken from {spsr.M.name} ==")
+ el = spsr.M >> 2
+ print(f" SPSR = {spsr}")
+ print(f" ELR = {addr(elr)}" + (f" (0x{elr_phys:x})" if elr_phys else ""))
+ print(f" ESR = {esr}")
+ print(f" FAR = {addr(ctx.far)}" + (f" (0x{far_phys:x})" if far_phys else ""))
+ print(f" SP = {ctx.sp:#x}" + (f" (0x{sp_phys:x})" if sp_phys else ""))
+
+ for i in range(0, 31, 4):
+ j = min(30, i + 3)
+ print(f" {f'x{i}-x{j}':>7} = {' '.join(f'{r:016x}' for r in ctx.regs[i:j + 1])}")
+
+ if elr_phys:
+ v = self.asc.p.read32(elr_phys)
+
+ print()
+ if v == 0xabad1dea:
+ print(" == Faulting code is not available ==")
+ else:
+ print(" == Faulting code ==")
+ dist = 16
+ self.asc.u.disassemble_at(elr_phys - dist * 4, (dist * 2 + 1) * 4, elr_phys)
+
+ print()
+
+ def Cstr(self, entry):
+ print(f"Message {entry.payload.id}: {entry.payload.string}")
+ print()
+
+ def Ctim(self, entry):
+ print(f"Crash time: {entry.payload.time:#x}")
+ print()
+
+ def Cmbx(self, entry):
+ print(f"Mailbox log (type {entry.payload.type}, index {entry.payload.index}):")
+ for i, msg in enumerate(entry.payload.messages):
+ print(f" #{i:3d} @{msg.timestamp:#10x} ep={msg.endpoint:#4x} {msg.message:#18x}")
+ print()
+
+ def CLHE(self, entry):
+ pass
+
+ def dump(self):
+ print("### Crash dump:")
+ print()
+ for entry in self.data.entries:
+ getattr(self, entry.type, self.default)(entry)
+
+class ASCCrashLogEndpoint(ASCBaseEndpoint):
+ SHORT = "crash"
+ BASE_MESSAGE = CrashLogMessage
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.iobuffer = None
+ self.iobuffer_dva = None
+ self.started = False
+
+ @msg_handler(0x1)
+ def Handle(self, msg):
+ if self.started:
+ return self.handle_crashed(msg)
+ else:
+ return self.handle_getbuf(msg)
+
+ def handle_getbuf(self, msg):
+ size = align(0x1000 * msg.SIZE, 0x4000)
+
+ if msg.DVA:
+ self.iobuffer_dva = msg.DVA
+ self.log(f"buf prealloc at dva {self.iobuffer_dva:#x}")
+ else:
+ self.iobuffer, self.iobuffer_dva = self.asc.ioalloc(size)
+ self.log(f"buf {self.iobuffer:#x} / {self.iobuffer_dva:#x}")
+ self.send(CrashLogMessage(TYPE=1, SIZE=size // 0x1000, DVA=self.iobuffer_dva))
+
+ self.started = True
+ return True
+
+ def crash_soft(self):
+ self.send(0x40)
+
+ def crash_hard(self):
+ self.send(0x22)
+
+ def handle_crashed(self, msg):
+ size = 0x1000 * msg.SIZE
+
+ self.log(f"Crashed!")
+ crashdata = self.asc.ioread(msg.DVA, size)
+ open("crash.bin", "wb").write(crashdata)
+ clog = CrashLogParser(crashdata, self.asc)
+ clog.dump()
+ raise Exception("ASC crashed!")
+
+ return True
+
+if __name__ == "__main__":
+ import sys
+ crashdata = open(sys.argv[1], "rb").read()
+ clog = CrashLogParser(crashdata)
+ clog.dump()
diff --git a/tools/proxyclient/m1n1/fw/asc/ioreporting.py b/tools/proxyclient/m1n1/fw/asc/ioreporting.py
new file mode 100644
index 0000000..f81b6c6
--- /dev/null
+++ b/tools/proxyclient/m1n1/fw/asc/ioreporting.py
@@ -0,0 +1,56 @@
+# SPDX-License-Identifier: MIT
+from .base import *
+from ...utils import *
+
+class IOReportingMessage(Register64):
+ TYPE = 63, 52
+
+class IOReporting_GetBuf(IOReportingMessage):
+ TYPE = 63, 52, Constant(1)
+ SIZE = 51, 44
+ DVA = 43, 0
+
+class IOReporting_Start(IOReportingMessage):
+ TYPE = 63, 52, Constant(0xc)
+
+class IOReporting_Report(IOReportingMessage):
+ TYPE = 63, 52, Constant(0x8)
+
+class ASCIOReportingEndpoint(ASCBaseEndpoint):
+ BASE_MESSAGE = IOReportingMessage
+ SHORT = "iorep"
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.iobuffer = None
+ self.iobuffer_dva = None
+
+ @msg_handler(1, IOReporting_GetBuf)
+ def GetBuf(self, msg):
+ if self.iobuffer:
+ self.log("WARNING: trying to reset iobuffer!")
+
+ self.bufsize = align(0x1000 * msg.SIZE, 0x4000)
+
+ if msg.DVA != 0:
+ self.iobuffer = self.iobuffer_dva = msg.DVA
+ self.log(f"buf {self.iobuffer:#x} / {self.iobuffer_dva:#x}")
+ else:
+ self.iobuffer, self.iobuffer_dva = self.asc.ioalloc(self.bufsize)
+ self.log(f"buf {self.iobuffer:#x} / {self.iobuffer_dva:#x}")
+ self.send(IOReporting_GetBuf(DVA=self.iobuffer_dva, SIZE=self.bufsize // 0x1000))
+
+ return True
+
+ @msg_handler(0xc, IOReporting_Start)
+ def Start(self, msg):
+ self.log("start")
+ return True
+
+ @msg_handler(8, IOReporting_Report)
+ def Init(self, msg):
+ self.log("report!")
+ buf = self.asc.iface.readmem(self.iobuffer, self.bufsize)
+ #chexdump(buf)
+ self.send(IOReporting_Report())
+ return True
diff --git a/tools/proxyclient/m1n1/fw/asc/kdebug.py b/tools/proxyclient/m1n1/fw/asc/kdebug.py
new file mode 100644
index 0000000..32a433f
--- /dev/null
+++ b/tools/proxyclient/m1n1/fw/asc/kdebug.py
@@ -0,0 +1,57 @@
+# SPDX-License-Identifier: MIT
+from .base import *
+from ...utils import *
+
+class KDebugMessage(Register64):
+ TYPE = 55, 48
+
+class KDebugGetBufMessage(KDebugMessage):
+ TYPE = 55, 48, Constant(1)
+ COUNT = 47, 0
+
+class KDebugPreallocBuf1Message(KDebugMessage):
+ TYPE = 55, 48, Constant(2)
+ DVA = 47, 12
+ FLAGS = 11, 0
+
+class KDebugPreallocBuf2Message(KDebugMessage):
+ TYPE = 55, 48, Constant(3)
+ DVA = 47, 0
+
+class KDebugSendBufMessage(KDebugMessage):
+ TYPE = 55, 48
+ DVA = 47, 0
+
+class KDebugStart(KDebugMessage):
+ TYPE = 55, 48, Constant(8)
+
+class ASCKDebugEndpoint(ASCBaseEndpoint):
+ SHORT = "kdebug"
+ BASE_MESSAGE = KDebugMessage
+
+ @msg_handler(1, KDebugGetBufMessage)
+ def GetBuf(self, msg):
+ size = align_up(msg.COUNT * 0x20, 0x4000)
+ self.iobuffer0, self.iobuffer0_iova = self.asc.ioalloc(size)
+ self.send(KDebugSendBufMessage(TYPE=1, DVA=self.iobuffer0_iova))
+
+ self.iobuffer1, self.iobuffer1_iova = self.asc.ioalloc(0x2000)
+ self.send(KDebugSendBufMessage(TYPE=2, DVA=self.iobuffer1_iova))
+ return True
+
+ @msg_handler(2, KDebugPreallocBuf1Message)
+ def SetBuf1(self, msg):
+ #self.send(KDebugSendBufMessage(TYPE=1, DVA=msg.DVA))
+ return True
+
+ @msg_handler(3, KDebugPreallocBuf2Message)
+ def SetBuf2(self, msg):
+ #self.send(KDebugSendBufMessage(TYPE=2, DVA=msg.DVA))
+ return True
+
+ def start(self):
+ self.iobuffer0 = None
+ self.iobuffer1 = None
+ self.iobuffer0_iova = None
+ self.iobuffer1_iova = None
+ self.send(KDebugStart())
diff --git a/tools/proxyclient/m1n1/fw/asc/mgmt.py b/tools/proxyclient/m1n1/fw/asc/mgmt.py
new file mode 100644
index 0000000..162fcd2
--- /dev/null
+++ b/tools/proxyclient/m1n1/fw/asc/mgmt.py
@@ -0,0 +1,144 @@
+# SPDX-License-Identifier: MIT
+import time
+
+from .base import *
+from ...utils import *
+
+## Management endpoint
+class ManagementMessage(Register64):
+ TYPE = 59, 52
+
+class Mgmt_Hello(ManagementMessage):
+ TYPE = 59, 52, Constant(1)
+ MAX_VER = 31, 16
+ MIN_VER = 15, 0
+
+class Mgmt_HelloAck(ManagementMessage):
+ TYPE = 59, 52, Constant(2)
+ MAX_VER = 31, 16
+ MIN_VER = 15, 0
+
+class Mgmt_Ping(ManagementMessage):
+ TYPE = 59, 52, Constant(3)
+
+class Mgmt_Pong(ManagementMessage):
+ TYPE = 59, 52, Constant(4)
+
+class Mgmt_StartEP(ManagementMessage):
+ TYPE = 59, 52, Constant(5)
+ EP = 39, 32
+ FLAG = 1, 0
+
+class Mgmt_SetIOPPower(ManagementMessage):
+ TYPE = 59, 52, Constant(6)
+ STATE = 15, 0
+
+class Mgmt_IOPPowerAck(ManagementMessage):
+ TYPE = 59, 52, Constant(7)
+ STATE = 15, 0
+
+class Mgmt_EPMap(ManagementMessage):
+ TYPE = 59, 52, Constant(8)
+ LAST = 51
+ BASE = 34, 32
+ BITMAP = 31, 0
+
+class Mgmt_EPMap_Ack(ManagementMessage):
+ TYPE = 59, 52, Constant(8)
+ LAST = 51
+ BASE = 34, 32
+ MORE = 0
+
+class Mgmt_SetAPPower(ManagementMessage):
+ TYPE = 59, 52, Constant(0xb)
+ STATE = 15, 0
+
+class ASCManagementEndpoint(ASCBaseEndpoint):
+ BASE_MESSAGE = ManagementMessage
+ SHORT = "mgmt"
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.syslog_started = False
+ self.iop_power_state = 0
+ self.ap_power_state = 0
+ self.verbose = 1
+
+ @msg_handler(1, Mgmt_Hello)
+ def Hello(self, msg):
+ self.log(f"Supported versions {msg.MIN_VER} .. {msg.MAX_VER}")
+ # FIXME: we pick the highest version, we should negotiate
+ self.send(Mgmt_HelloAck(MIN_VER=msg.MAX_VER, MAX_VER=msg.MAX_VER))
+ return True
+
+ @msg_handler(8, Mgmt_EPMap)
+ def EPMap(self, msg):
+ for i in range(32):
+ if msg.BITMAP & (1 << i):
+ epno = 32 * msg.BASE + i
+ self.asc.eps.append(epno)
+ if self.verbose > 0:
+ self.log(f"Adding endpoint {epno:#x}")
+
+ self.send(Mgmt_EPMap_Ack(BASE=msg.BASE, LAST=msg.LAST, MORE=0 if msg.LAST else 1))
+
+ if msg.LAST:
+ for ep in self.asc.eps:
+ if ep == 0: continue
+ if ep < 0x10:
+ self.asc.start_ep(ep)
+ self.boot_done()
+
+ return True
+
+ @msg_handler(0xb, Mgmt_SetAPPower)
+ def APPowerAck(self, msg):
+ if self.verbose > 0:
+ self.log(f"AP power state is now {msg.STATE:#x}")
+ self.ap_power_state = msg.STATE
+ return True
+
+ @msg_handler(7, Mgmt_IOPPowerAck)
+ def IOPPowerAck(self, msg):
+ if self.verbose > 0:
+ self.log(f"IOP power state is now {msg.STATE:#x}")
+ self.iop_power_state = msg.STATE
+ return True
+
+ @msg_handler(4, Mgmt_Pong)
+ def Pong(self, msg):
+ return True
+
+ def start(self):
+ self.log("Starting via message")
+ self.send(Mgmt_SetIOPPower(STATE=0x220))
+
+ def wait_boot(self, timeout=None):
+ if timeout is not None:
+ timeout += time.time()
+ while self.iop_power_state != 0x20 or self.ap_power_state != 0x20:
+ self.asc.work()
+ if timeout and time.time() > timeout:
+ raise ASCTimeout("Boot timed out")
+ self.log("Startup complete")
+
+ def start_ep(self, epno):
+ self.send(Mgmt_StartEP(EP=epno, FLAG=2))
+
+ def stop_ep(self, epno):
+ self.send(Mgmt_StartEP(EP=epno, FLAG=1))
+
+ def boot_done(self):
+ self.send(Mgmt_SetAPPower(STATE=0x20))
+
+ def ping(self):
+ self.send(Mgmt_Ping())
+
+ def stop(self, state=0x10):
+ self.log("Stopping via message")
+ self.send(Mgmt_SetAPPower(STATE=0x10))
+ while self.ap_power_state == 0x20:
+ self.asc.work()
+ self.send(Mgmt_SetIOPPower(STATE=state))
+ while self.iop_power_state != state:
+ self.asc.work()
diff --git a/tools/proxyclient/m1n1/fw/asc/oslog.py b/tools/proxyclient/m1n1/fw/asc/oslog.py
new file mode 100644
index 0000000..b1a360b
--- /dev/null
+++ b/tools/proxyclient/m1n1/fw/asc/oslog.py
@@ -0,0 +1,30 @@
+# SPDX-License-Identifier: MIT
+from .base import *
+from ...utils import *
+
+## OSLog endpoint
+
+class OSLogMessage(Register64):
+ TYPE = 63, 56
+
+class OSLog_Init(OSLogMessage):
+ TYPE = 63, 56, Constant(1)
+ UNK = 51, 0
+
+class OSLog_Ack(OSLogMessage):
+ TYPE = 63, 56, Constant(3)
+
+class ASCOSLogEndpoint(ASCBaseEndpoint):
+ BASE_MESSAGE = OSLogMessage
+ SHORT = "oslog"
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.started = False
+
+ @msg_handler(1, OSLog_Init)
+ def Init(self, msg):
+ self.log(f"oslog init: {msg.UNK:#x}")
+ self.send(OSLog_Ack())
+ self.started = True
+ return True
diff --git a/tools/proxyclient/m1n1/fw/asc/syslog.py b/tools/proxyclient/m1n1/fw/asc/syslog.py
new file mode 100644
index 0000000..3387c27
--- /dev/null
+++ b/tools/proxyclient/m1n1/fw/asc/syslog.py
@@ -0,0 +1,72 @@
+# SPDX-License-Identifier: MIT
+import struct
+
+from .base import *
+from ...utils import *
+
+## Syslog endpoint
+
+class SyslogMessage(Register64):
+ TYPE = 59, 52
+
+class Syslog_Init(SyslogMessage):
+ TYPE = 59, 52, Constant(8)
+ ENTRYSIZE = 39, 24
+ COUNT = 15, 0
+
+class Syslog_GetBuf(SyslogMessage):
+ TYPE = 59, 52, Constant(1)
+ SIZE = 51, 44
+ DVA = 43, 0
+
+class Syslog_Log(SyslogMessage):
+ TYPE = 59, 52, Constant(5)
+ INDEX = 7, 0
+
+class ASCSysLogEndpoint(ASCBaseEndpoint):
+ BASE_MESSAGE = SyslogMessage
+ SHORT = "syslog"
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.entrysize = None
+ self.count = None
+ self.iobuffer = None
+ self.iobuffer_dva = None
+ self.started = False
+
+ @msg_handler(8, Syslog_Init)
+ def Init(self, msg):
+ self.entrysize = msg.ENTRYSIZE
+ self.count = msg.COUNT
+ self.log(f"count {self.count}, entrysize {self.entrysize}")
+ return True
+
+ @msg_handler(1, Syslog_GetBuf)
+ def GetBuf(self, msg):
+ size = align(0x1000 * msg.SIZE, 0x4000)
+
+ if self.iobuffer:
+ print("WARNING: trying to reset iobuffer!")
+
+ if msg.DVA:
+ self.iobuffer_dva = msg.DVA
+ self.log(f"buf prealloc at dva {self.iobuffer_dva:#x}")
+ else:
+ self.iobuffer, self.iobuffer_dva = self.asc.ioalloc(size)
+ self.log(f"buf {self.iobuffer:#x} / {self.iobuffer_dva:#x}")
+ self.send(Syslog_GetBuf(SIZE=size // 0x1000, DVA=self.iobuffer_dva))
+
+ self.started = True
+ return True
+
+ @msg_handler(5, Syslog_Log)
+ def Log(self, msg):
+ stride = 0x20 + self.entrysize
+ log = self.asc.ioread(self.iobuffer_dva + msg.INDEX * stride, stride)
+ hdr, unk, context, logmsg = struct.unpack(f"<II24s{self.entrysize}s", log)
+ context = context.split(b"\x00")[0].decode("ascii")
+ logmsg = logmsg.split(b"\x00")[0].decode("ascii").rstrip("\n")
+ self.log(f"* [{context}]{logmsg}")
+ self.send(msg)
+ return True
diff --git a/tools/proxyclient/m1n1/fw/common.py b/tools/proxyclient/m1n1/fw/common.py
new file mode 100644
index 0000000..479e2df
--- /dev/null
+++ b/tools/proxyclient/m1n1/fw/common.py
@@ -0,0 +1,170 @@
+# SPDX-License-Identifier: MIT
+
+from dataclasses import dataclass
+from enum import IntEnum
+from m1n1.utils import *
+from construct import *
+
+uint8_t = Int8ul
+int16_t = Int16sl
+uint16_t = Int16ul
+int32_t = Int32sl
+uint32_t = Int32ul
+int64_t = Int64sl
+uint64_t = Int64ul
+
+uint = uint32_t
+int_ = int32_t
+ulong = uint64_t
+long_ = int64_t
+
+def Bool(c):
+ return ExprAdapter(c, lambda d, ctx: bool(d & 1), lambda d, ctx: int(d))
+
+def SizedArray(count, svar, subcon):
+ return Padded(subcon.sizeof() * count, Array(lambda ctx: min(count, ctx.get(svar, ctx._.get(svar))), subcon))
+
+def SizedBytes(count, svar):
+ return Lazy(Padded(count, Bytes(lambda ctx: ctx.get(svar) or ctx._.get(svar))))
+
+def UnkBytes(s):
+ return Default(HexDump(Bytes(s)), b"\x00" * s)
+
+bool_ = Bool(Int8ul)
+
+class OSObject(Construct):
+ TYPE = None
+
+ def _parse(self, stream, context, path, recurse=False):
+ tag = stream.read(1).decode("ascii")
+ if not recurse and self.TYPE is not None and self.TYPE != tag:
+ raise Exception("Object type mismatch")
+
+ if tag == "d":
+ count = Int32ul.parse_stream(stream)
+ d = {}
+ for i in range(count):
+ k = self._parse(stream, context, path, True)
+ v = self._parse(stream, context, path, True)
+ d[k] = v
+ return d
+ elif tag == "n":
+ return Int64ul.parse_stream(stream)
+ elif tag == "s":
+ length = Int32ul.parse_stream(stream)
+ s = stream.read(length).decode("utf-8")
+ assert stream.read(1) == b'\0'
+ return s
+ else:
+ raise Exception(f"Unknown object tag {tag!r}")
+
+ def _build(self, obj, stream, context, path):
+ assert False
+
+ def _sizeof(self, context, path):
+ return None
+
+class OSDictionary(OSObject):
+ TYPE = 'd'
+
+class OSSerialize(Construct):
+ def _parse(self, stream, context, path, recurse=False):
+ hdr = Int32ul.parse_stream(stream)
+ if hdr != 0xd3:
+ raise Exception("Bad header")
+
+ obj, last = self.parse_obj(stream)
+ assert last
+ return obj
+
+ def parse_obj(self, stream, level=0):
+ # align to 32 bits
+ pos = stream.tell()
+ if pos & 3:
+ stream.read(4 - (pos & 3))
+
+ tag = Int32ul.parse_stream(stream)
+
+ last = bool(tag & 0x80000000)
+ otype = (tag >> 24) & 0x1f
+ size = tag & 0xffffff
+
+ #print(f"{' '*level} @{stream.tell():#x} {otype} {last} {size}")
+
+ if otype == 1:
+ d = {}
+ for i in range(size):
+ k, l = self.parse_obj(stream, level + 1)
+ assert not l
+ v, l = self.parse_obj(stream, level + 1)
+ assert l == (i == size - 1)
+ d[k] = v
+ elif otype == 2:
+ d = []
+ for i in range(size):
+ v, l = self.parse_obj(stream, level + 1)
+ assert l == (i == size - 1)
+ d.append(v)
+ elif otype == 4:
+ d = Int64ul.parse_stream(stream)
+ elif otype == 9:
+ d = stream.read(size).decode("utf-8")
+ elif otype == 10:
+ d = stream.read(size)
+ elif otype == 11:
+ d = bool(size)
+ else:
+ raise Exception(f"Unknown tag {otype}")
+
+ #print(f"{' '*level} => {d}")
+ return d, last
+
+ def build_obj(self, obj, stream, last=True, level=0):
+ tag = 0
+ if last:
+ tag |= 0x80000000
+
+ if isinstance(obj, dict):
+ tag |= (1 << 24) | len(obj)
+ Int32ul.build_stream(tag, stream)
+ for i, (k, v) in enumerate(obj.items()):
+ self.build_obj(k, stream, False, level + 1)
+ self.build_obj(v, stream, i == len(obj) - 1, level + 1)
+ elif isinstance(obj, list):
+ tag |= (2 << 24) | len(obj)
+ Int32ul.build_stream(tag, stream)
+ for i, v in enumerate(obj):
+ self.build_obj(v, stream, i == len(obj) - 1, level + 1)
+ elif isinstance(obj, int):
+ tag |= (4 << 24) | 64
+ Int32ul.build_stream(tag, stream)
+ Int64ul.build_stream(obj, stream)
+ elif isinstance(obj, str):
+ obj = obj.encode("utf-8")
+ tag |= (9 << 24) | len(obj)
+ Int32ul.build_stream(tag, stream)
+ stream.write(obj)
+ elif isinstance(obj, bytes):
+ tag |= (10 << 24) | len(obj)
+ Int32ul.build_stream(tag, stream)
+ stream.write(obj)
+ elif isinstance(obj, bool):
+ tag |= (11 << 24) | int(obj)
+ Int32ul.build_stream(tag, stream)
+ else:
+ raise Exception(f"Cannot encode {obj!r}")
+
+ pos = stream.tell()
+ if pos & 3:
+ stream.write(bytes(4 - (pos & 3)))
+
+ def _build(self, obj, stream, context, path):
+ Int32ul.build_stream(0xd3, stream)
+ self.build_obj(obj, stream)
+
+ def _sizeof(self, context, path):
+ return None
+
+def string(size):
+ return Padded(size, CString("utf8"))
+
diff --git a/tools/proxyclient/m1n1/fw/dcp/__init__.py b/tools/proxyclient/m1n1/fw/dcp/__init__.py
new file mode 100644
index 0000000..e77f6cf
--- /dev/null
+++ b/tools/proxyclient/m1n1/fw/dcp/__init__.py
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: MIT
+
diff --git a/tools/proxyclient/m1n1/fw/dcp/client.py b/tools/proxyclient/m1n1/fw/dcp/client.py
new file mode 100644
index 0000000..941ab20
--- /dev/null
+++ b/tools/proxyclient/m1n1/fw/dcp/client.py
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: MIT
+from ...utils import *
+
+from ..asc import StandardASC
+from .dcpep import DCPEndpoint
+
+class DCPClient(StandardASC):
+ ENDPOINTS = {
+ 0x37: DCPEndpoint,
+ }
+
+ def __init__(self, u, asc_base, dart=None, disp_dart=None):
+ super().__init__(u, asc_base, dart)
+ self.disp_dart = disp_dart
diff --git a/tools/proxyclient/m1n1/fw/dcp/dcpav.py b/tools/proxyclient/m1n1/fw/dcp/dcpav.py
new file mode 100644
index 0000000..d063c6c
--- /dev/null
+++ b/tools/proxyclient/m1n1/fw/dcp/dcpav.py
@@ -0,0 +1,110 @@
+# SPDX-License-Identifier: MIT
+from construct import *
+
+from ...utils import *
+from ..asc import StandardASC
+from ..afk.epic import *
+
+class DCPAVControllerService(EPICStandardService):
+ NAME = "dcpav-controller-epic"
+ SHORT = "dcpav"
+
+ def setPower(self, power):
+ self.call(8, 0x8, struct.pack("<16xI12x", power))
+
+ def getPower(self, power):
+ return struct.unpack("<16xI12x", self.call(8, 0x9, bytes(32)))
+
+ def wakeDisplay(self):
+ self.call(8, 0xa, bytes(16))
+
+ def sleepDisplay(self):
+ self.call(8, 0xb, bytes(16))
+
+ def forceHotPlugDetect(self):
+ self.call(8, 0xc, bytes(16))
+
+ def setVirtualDeviceMode(self, mode):
+ self.call(8, 0xd, struct.pack("<16xI12x", mode))
+
+class DCPDPControllerService(EPICStandardService):
+ NAME = "dcpdp-controller-epic"
+ SHORT = "dcpdp"
+
+class DCPDPTXEndpoint(EPICEndpoint):
+ SHORT = "dptx"
+
+ SERVICES = [
+ DCPAVControllerService,
+ DCPDPControllerService,
+ ]
+
+ATC0 = 0
+ATC1 = 1
+ATC2 = 2
+ATC3 = 3
+LPDPTX = 4
+DPTX = 5
+
+DPPHY = 0
+DPIN0 = 1
+DPIN1 = 2
+
+class DCPDPTXRemotePortService(EPICStandardService):
+ NAME = "dcpdptx-port-epic"
+ SHORT = "port"
+
+ def displayRequest(self):
+ self.call(8, 8, bytes(16))
+
+ def displayRelease(self):
+ self.call(8, 9, bytes(16))
+
+ def connectTo(self, connected, unit, port, unk=0):
+ target = 0
+ if connected:
+ target |= (1 << 8)
+ target |= unit
+ target |= port << 4
+ self.call(8, 13, struct.pack("<16xII8x", unk, target))
+
+class DCPDPTXPortEndpoint(EPICEndpoint):
+ SHORT = "dpport"
+
+ SERVICES = [
+ DCPDPTXRemotePortService,
+ DCPDPControllerService,
+ ]
+
+class DCPDPDevice(EPICStandardService):
+ NAME = "dcpav-device-epic"
+ SHORT = "dpdev"
+
+class DCPAVDeviceEndpoint(EPICEndpoint):
+ SHORT = "avdev"
+
+ SERVICES = [
+ DCPDPDevice,
+ ]
+
+class DCPDPService(EPICStandardService):
+ NAME = "dcpav-service-epic"
+ SHORT = "dpserv"
+
+class DCPAVServiceEndpoint(EPICEndpoint):
+ SHORT = "avserv"
+
+ SERVICES = [
+ DCPDPService,
+ ]
+
+class DCPAVSimpleVideoInterface(EPICStandardService):
+ NAME = "dcpav-video-interface-epic"
+ SHORT = "video"
+
+class DCPAVVideoEndpoint(EPICEndpoint):
+ SHORT = "avserv"
+
+ SERVICES = [
+ DCPAVSimpleVideoInterface,
+ ]
diff --git a/tools/proxyclient/m1n1/fw/dcp/dcpep.py b/tools/proxyclient/m1n1/fw/dcp/dcpep.py
new file mode 100644
index 0000000..6e4f250
--- /dev/null
+++ b/tools/proxyclient/m1n1/fw/dcp/dcpep.py
@@ -0,0 +1,163 @@
+# SPDX-License-Identifier: MIT
+import struct
+from dataclasses import dataclass
+from enum import IntEnum
+
+from ..asc.base import *
+from ...utils import *
+
+## DCP main endpoint
+
+class DCPMessage(Register64):
+ TYPE = 3, 0
+
+class DCPEp_SetShmem(DCPMessage):
+ DVA = 63, 16
+ FLAG = 7, 4
+ TYPE = 3, 0, Constant(0)
+
+class DCPEp_InitComplete(DCPMessage):
+ TYPE = 3, 0, Constant(1)
+
+class CallContext(IntEnum):
+ CB = 0
+ CMD = 2
+ ASYNC = 3
+ OOBCB = 4
+ OOBCMD = 6
+
+class DCPEp_Msg(DCPMessage):
+ LEN = 63, 32
+ OFF = 31, 16
+ CTX = 11, 8, CallContext
+ ACK = 6
+ TYPE = 3, 0, Constant(2)
+
+@dataclass
+class DCPCallState:
+ tag: str
+ off: int
+ in_len: int
+ in_data: bytes
+ out_addr: int
+ out_len: int
+ complete: bool = False
+
+class DCPCallChannel(Reloadable):
+ def __init__(self, dcpep, name, buf, bufsize):
+ self.dcp = dcpep
+ self.name = name
+ self.buf = buf
+ self.bufsize = bufsize
+ self.off = 0
+ self.pending = []
+
+ def ack(self):
+ if not self.pending:
+ raise Exception("ACK with no calls pending")
+
+ self.pending[-1].complete = True
+
+ def call(self, ctx, tag, inbuf, out_len):
+ in_len = len(inbuf)
+ data = tag.encode("ascii")[::-1] + struct.pack("<II", in_len, out_len) + inbuf
+ data_size = len(data) + out_len
+ assert (self.off + data_size) <= self.bufsize
+
+ self.dcp.asc.iface.writemem(self.dcp.shmem + self.buf + self.off, data)
+
+ state = DCPCallState(off=self.off, tag=tag, in_len=in_len, in_data=data, out_len=out_len,
+ out_addr=self.buf + self.off + 12 + in_len)
+
+ self.off += align_up(data_size, 0x40)
+ self.pending.append(state)
+
+ print(f"len={data_size:#x} {in_len}")
+ self.dcp.send(DCPEp_Msg(LEN=data_size, OFF=state.off, CTX=ctx, ACK=0))
+
+ while not state.complete:
+ self.dcp.asc.work()
+
+ print(f"off={state.out_addr:#x} len={out_len}")
+ out_data = self.dcp.asc.iface.readmem(self.dcp.shmem + state.out_addr, out_len)
+
+ assert self.pending.pop() is state
+ self.off = state.off
+
+ return out_data
+
+class DCPCallbackChannel(Reloadable):
+ def __init__(self, dcpep, name, buf, bufsize):
+ self.dcp = dcpep
+ self.name = name
+ self.buf = buf
+ self.bufsize = bufsize
+ self.pending = []
+
+ def cb(self, msg):
+ data = self.dcp.asc.iface.readmem(self.dcp.shmem + self.buf + msg.OFF, msg.LEN)
+ tag = data[:4][::-1].decode("ascii")
+ in_len, out_len = struct.unpack("<II", data[4:12])
+ in_data = data[12:12 + in_len]
+
+ state = DCPCallState(off=msg.OFF, tag=tag, in_len=in_len, out_len=out_len,
+ in_data=in_data, out_addr=self.buf + msg.OFF + 12 + in_len)
+
+ self.pending.append(state)
+
+ out_data = self.dcp.mgr.handle_cb(state)
+ self.dcp.asc.iface.writemem(self.dcp.shmem + state.out_addr, out_data)
+ self.dcp.send(DCPEp_Msg(CTX=msg.CTX, ACK=1))
+
+ assert self.pending.pop() is state
+
+
+class DCPEndpoint(ASCBaseEndpoint):
+ BASE_MESSAGE = DCPMessage
+ SHORT = "dcpep"
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.shmem = self.shmem_dva = None
+ self.init_complete = False
+ self.mgr = None
+
+ self.ch_cb = DCPCallbackChannel(self, "CB", 0x60000, 0x8000)
+ self.ch_cmd = DCPCallChannel(self, "CMD", 0, 0x8000)
+ self.ch_async = DCPCallbackChannel(self, "ASYNC", 0x40000, 0x20000)
+ self.ch_oobcb = DCPCallbackChannel(self, "OOBCB", 0x68000, 0x8000)
+ self.ch_oobcmd = DCPCallChannel(self, "OOBCMD", 0x8000, 0x8000)
+
+ @msg_handler(2, DCPEp_Msg)
+ def Rx(self, msg):
+ if msg.ACK:
+ if msg.CTX in (CallContext.CMD, CallContext.CB):
+ self.ch_cmd.ack()
+ elif msg.CTX in (CallContext.OOBCMD, CallContext.OOBCB):
+ self.ch_oobcmd.ack()
+ else:
+ raise Exception(f"Unknown RX ack channel {msg.CTX}")
+ else:
+ if msg.CTX == CallContext.CB:
+ self.ch_cb.cb(msg)
+ elif msg.CTX == CallContext.OOBCMD:
+ self.ch_oobcb.cb(msg)
+ elif msg.CTX == CallContext.ASYNC:
+ self.ch_async.cb(msg)
+ else:
+ raise Exception(f"Unknown RX callback channel {msg.CTX}")
+ return True
+
+ @msg_handler(1, DCPEp_InitComplete)
+ def InitComplete(self, msg):
+ self.log("init complete")
+ self.init_complete = True
+ return True
+
+ def initialize(self):
+ self.shmem, self.shmem_dva = self.asc.ioalloc(0x100000)
+ self.asc.p.memset32(self.shmem, 0, 0x100000)
+ self.send(DCPEp_SetShmem(DVA=self.shmem_dva))
+ while not self.init_complete:
+ self.asc.work()
+
diff --git a/tools/proxyclient/m1n1/fw/dcp/iboot.py b/tools/proxyclient/m1n1/fw/dcp/iboot.py
new file mode 100644
index 0000000..8124ca9
--- /dev/null
+++ b/tools/proxyclient/m1n1/fw/dcp/iboot.py
@@ -0,0 +1,215 @@
+# SPDX-License-Identifier: MIT
+from construct import *
+
+from ...utils import *
+from ..asc import StandardASC
+from ..afk.epic import *
+from .dcpav import *
+
+EOTF = "EOTF" / Enum(Int32ul,
+ GAMMA_SDR = 1,
+ GAMMA_HDR = 2,
+)
+
+Encoding = "Encoding" / Enum(Int32ul,
+ RGB = 1,
+ YCBCR_444 = 3,
+ YCBCR_422 = 4,
+ YCBCR_420 = 5,
+)
+
+Colorimetry = "Colorimetry" / Enum(Int32ul,
+ BT601_709 = 1,
+ BT2020 = 2,
+ DCIP3 = 3,
+)
+
+Colorspace = "Colorspace" / Enum(Int32ul,
+ SRGB = 1,
+ Native = 2,
+ BT2020 = 3,
+)
+
+SurfaceFormat = "SurfaceFormat" / Enum(Int32ul,
+ BGRA = 1,
+ BGRA2 = 2,
+ RGBA = 3,
+ w18p = 4,
+ BGRA3 = 5,
+ _444v = 6,
+ _422v = 7,
+ _420v = 8,
+ w30r = 9,
+ w40a = 10,
+)
+
+Transform = "Transform" / Enum(Int8ul,
+ NONE = 0,
+ XFLIP = 1,
+ YFLIP = 2,
+ ROT_90 = 3,
+ ROT_180 = 4,
+ ROT_270 = 5,
+)
+
+AddrFormat = "AddrFormat" / Enum(Int32ul,
+ PLANAR = 1,
+ TILED = 2,
+ AGX = 3
+)
+
+TimingMode = Struct(
+ "valid" / Bool(Int32ul),
+ "width" / Int32ul,
+ "height" / Int32ul,
+ "fps_frac" / Int16ul,
+ "fps_int" / Int16ul,
+ Padding(8),
+)
+
+TimingModeList = Struct(
+ "count" / Int32ul,
+ "list" / GreedyRange(TimingMode),
+)
+
+ColorMode = Struct(
+ "valid" / Bool(Int32ul),
+ "colorimetry" / Colorimetry,
+ "eotf" / EOTF,
+ "encoding" / Encoding,
+ "bpp" / Int32ul,
+ "unk" / Int32ul,
+)
+
+ColorModeList = Struct(
+ "count" / Int32ul,
+ "list" / GreedyRange(ColorMode),
+)
+
+SwapInfo = Struct(
+ "unk1" / Int32ul,
+ "unk2" / Int32ul,
+ "unk3" / Int32ul,
+ "swap_id" / Int32ul,
+ "unk5" / Int32ul,
+)
+
+IBootPlaneInfo = Struct(
+ "unk1" / Default(Int32ul, 0),
+ "addr" / Default(Int64ul, 0),
+ "tile_size" / Default(Int32ul, 0),
+ "stride" / Default(Int32ul, 0),
+ "unk5" / Default(Int32ul, 0),
+ "unk6" / Default(Int32ul, 0),
+ "unk7" / Default(Int32ul, 0),
+ "unk8" / Default(Int32ul, 0),
+ "addr_format" / Default(AddrFormat, 0),
+ "unk9" / Default(Int32ul, 0),
+)
+
+IBootLayerInfo = Struct(
+ "planes" / Array(3, IBootPlaneInfo),
+ "unk" / Default(Int32ul, 0),
+ "plane_cnt" / Int32ul,
+ "width" / Int32ul,
+ "height" / Int32ul,
+ "surface_fmt" / SurfaceFormat,
+ "colorspace" / Colorspace,
+ "eotf" / EOTF,
+ "transform" / Transform,
+ Padding(3)
+)
+
+SwapSetLayer = Struct(
+ "unk" / Default(Int32ul, 0),
+ "layer_id" / Int32ul,
+ "layer_info" / IBootLayerInfo,
+ "src_w" / Int32ul,
+ "src_h" / Int32ul,
+ "src_x" / Int32ul,
+ "src_y" / Int32ul,
+ "dst_w" / Int32ul,
+ "dst_h" / Int32ul,
+ "dst_x" / Int32ul,
+ "dst_y" / Int32ul,
+ "unk2" / Default(Int32ul, 0),
+)
+
+class DCPIBootService(EPICService):
+ NAME = "disp0-service"
+ SHORT = "disp0"
+
+ def send_cmd(self, op, data=b'', replen=None):
+ msg = struct.pack("<IIII", op, 16 + len(data), 0, 0) + data
+ if replen is not None:
+ replen += 8
+ resp = super().send_cmd(0xc0, msg, replen)
+ if not resp:
+ return
+ rcmd, rlen = struct.unpack("<II", resp[:8])
+ return resp[8:rlen]
+
+ def setPower(self, power):
+ self.send_cmd(2, b"\x01" if power else b"\x00")
+
+ def getModeCount(self):
+ buf = self.send_cmd(3, b"", 12)
+ hpd, timing_cnt, color_cnt = struct.unpack("<B3xII", buf)
+ return bool(hpd), timing_cnt, color_cnt
+
+ def getTimingModes(self):
+ return TimingModeList.parse(self.send_cmd(4, replen=4096)).list
+
+ def getColorModes(self):
+ return ColorModeList.parse(self.send_cmd(5, replen=4096)).list
+
+ def setMode(self, timing_mode, color_mode):
+ data = TimingMode.build(timing_mode) + ColorMode.build(color_mode)
+ self.send_cmd(6, data)
+
+ def swapBegin(self):
+ return SwapInfo.parse(self.send_cmd(15, replen=128))
+
+ def swapSetLayer(self, layer_id, info, src_rect, dst_rect):
+ data = Container()
+ data.layer_id = layer_id
+ data.layer_info = info
+ data.src_w, data.src_h, data.src_x, data.src_y = src_rect
+ data.dst_w, data.dst_h, data.dst_x, data.dst_y = dst_rect
+ return self.send_cmd(16, SwapSetLayer.build(data), replen=128)
+
+ def swapSetTimestamp(self):
+ pass
+ # 17
+
+ def swapEnd(self):
+ return self.send_cmd(18, b"\x00" * 12, replen=128)
+
+ #def swapWait(self, swap_id):
+ #buf = struct.pack("<IIII", 1, swap_id, 0, swap_id)
+ #return self.send_cmd(19, buf, replen=128)
+
+class DCPIBootEndpoint(EPICEndpoint):
+ SHORT = "iboot"
+
+ SERVICES = [
+ DCPIBootService,
+ ]
+
+
+class DCPIBootClient(StandardASC):
+ DVA_OFFSET = 0xf00000000
+
+ ENDPOINTS = {
+ 0x20: AFKSystemEndpoint,
+ 0x23: DCPIBootEndpoint,
+ 0x24: DCPDPTXEndpoint,
+ 0x2a: DCPDPTXPortEndpoint,
+ 0x27: DCPAVDeviceEndpoint,
+ 0x28: DCPAVServiceEndpoint,
+ 0x29: DCPAVVideoEndpoint,
+ }
+
+ def __init__(self, u, asc_base, dart=None, disp_dart=None):
+ super().__init__(u, asc_base, dart)
+ self.disp_dart = disp_dart
diff --git a/tools/proxyclient/m1n1/fw/dcp/ipc.py b/tools/proxyclient/m1n1/fw/dcp/ipc.py
new file mode 100644
index 0000000..c961a8c
--- /dev/null
+++ b/tools/proxyclient/m1n1/fw/dcp/ipc.py
@@ -0,0 +1,789 @@
+# SPDX-License-Identifier: MIT
+
+from dataclasses import dataclass
+import pprint
+from enum import IntEnum
+
+from ..common import *
+from m1n1.utils import *
+from construct import *
+
+@dataclass
+class ByRef:
+ val: object
+
+class Pointer(Subconstruct):
+ pass
+
+class InPtr(Pointer):
+ pass
+
+class OutPtr(Pointer):
+ pass
+
+class InOutPtr(Pointer):
+ pass
+
+class InOut(Subconstruct):
+ pass
+
+Ptr = InOutPtr
+
+class NULL:
+ def __str__(self):
+ return "NULL"
+ def __repr__(self):
+ return "NULL"
+NULL = NULL()
+
+class Method:
+ def __init__(self, rtype, name, *args, **kwargs):
+ self.rtype = rtype
+ self.name = name
+
+ if args and kwargs:
+ raise Exception("Cannot specify args and kwargs")
+ elif args:
+ args = [(f"arg{i}", arg) for i, arg in enumerate(args)]
+ self.as_kwargs = False
+ elif kwargs:
+ args = list(kwargs.items())
+ self.as_kwargs = True
+ else:
+ args = []
+
+ self.args = args
+
+ in_size = 0
+ out_size = 0
+ self.in_fields = []
+ self.out_fields = []
+
+ if rtype is not None:
+ args.append(("ret", rtype))
+
+ self.dir = []
+ self.nullable = []
+ self.array_of_p = []
+
+ for i, (name, field) in enumerate(self.args):
+ align = 1
+
+ pfield = field
+ dir = "in"
+
+ if name == "ret":
+ dir = "out"
+
+ while isinstance(pfield, Subconstruct):
+ if isinstance(pfield, InPtr):
+ dir = "in"
+ elif isinstance(pfield, OutPtr):
+ dir = "out"
+ elif isinstance(pfield, (InOut, InOutPtr)):
+ dir = "inout"
+ pfield = pfield.subcon
+ if isinstance(pfield, FormatField):
+ align = min(4, pfield.length)
+
+ if dir in ("in", "inout"):
+ #if in_size % align:
+ #self.in_fields.append(Padding(align - (in_size % align)))
+ #in_size += align - (in_size % align)
+
+ self.in_fields.append(name / field)
+ in_size += field.sizeof()
+
+ if dir in ("out", "inout"):
+ #if out_size % align:
+ #self.out_fields.append(Padding(align - (out_size % align)))
+ #out_size += align - (out_size % align)
+
+ self.out_fields.append(name / field)
+ out_size += field.sizeof()
+
+ self.dir.append(dir)
+
+ for i, (name, field) in enumerate(self.args):
+ array_size = None
+ array_of_p = False
+ nullable = False
+ pfield = field
+
+ while isinstance(pfield, Subconstruct):
+ if isinstance(pfield, Array) and array_size is None:
+ array_size = pfield.count
+ if isinstance(pfield, Pointer):
+ nullable = True
+ array_of_p = array_size is not None
+ pfield = pfield.subcon
+
+ if nullable:
+ if array_of_p:
+ self.in_fields.append((name + "_null") / bool_[array_size])
+ in_size += array_size
+ else:
+ self.in_fields.append((name + "_null") / bool_)
+ in_size += 1
+
+ self.nullable.append(nullable)
+ self.array_of_p.append(array_of_p)
+
+ if in_size % 4:
+ self.in_fields.append(Padding(4 - (in_size % 4)))
+ if out_size % 4:
+ self.out_fields.append(Padding(4 - (out_size % 4)))
+
+ self.in_struct = Struct(*self.in_fields)
+ self.out_struct = Struct(*self.out_fields)
+
+ def get_field_val(self, i, in_vals, out_vals=None, nullobj=None):
+ name, field = self.args[i]
+
+ nullable = self.nullable[i]
+ array_of_p = self.array_of_p[i]
+
+ val = None
+
+ if out_vals:
+ val = out_vals.get(name, val)
+ if val is None and in_vals:
+ val = in_vals.get(name, val)
+
+ if nullable and val is not None:
+ null = in_vals.get(name + "_null", None)
+ if null is None:
+ return None
+ if not array_of_p:
+ val = nullobj if null else val
+ else:
+ val2 = [nullobj if n else val for val, n in zip(val, null)]
+ if isinstance(val, ListContainer):
+ val2 = ListContainer(val2)
+ val = val2
+
+ return val
+
+ def fmt_args(self, in_vals, out_vals=None):
+ s = []
+
+ for i, (name, field) in enumerate(self.args):
+ if name == "ret":
+ continue
+
+ dir = self.dir[i]
+ nullable = self.nullable[i]
+
+ val = self.get_field_val(i, in_vals, out_vals, nullobj=NULL)
+
+ if val is not None:
+ if self.is_long(val):
+ s.append(f"{name}=...")
+ elif isinstance(val, ListContainer):
+ s.append(f"{name}={list(val)!r}")
+ else:
+ s.append(f"{name}={val!r}")
+ elif dir == "out":
+ s.append(f"{name}=<out>")
+ else:
+ s.append(f"{name}=?")
+
+ return ", ".join(s)
+
+ def print_long_args(self, indent, in_vals, out_vals=None):
+ for i, (name, field) in enumerate(self.args):
+ if name == "ret":
+ continue
+
+ val = self.get_field_val(i, in_vals, out_vals, nullobj=NULL)
+
+ if name in in_vals and out_vals is not None and name not in out_vals:
+ continue
+
+ if self.is_long(val):
+ hdr = f"{indent} {name} = "
+ if isinstance(val, (ListContainer, Container)):
+ print(hdr + str(val).replace("\n", "\n" + indent))
+ elif isinstance(val, bytes):
+ print(hdr + f"({len(val):#x} bytes)")
+ chexdump(val, indent=indent + " ")
+ else:
+ dindent = " " * len(hdr)
+ if isinstance(val, dict) and "_io" in val:
+ del val["_io"]
+ print(hdr + pprint.pformat(val, sort_dicts=False).replace("\n", "\n" + dindent))
+
+ def is_long(self, arg):
+ if isinstance(arg, (list, bytes)):
+ return len(arg) > 4 or any(self.is_long(i) for i in arg)
+
+ return isinstance(arg, (dict, list, bytes))
+
+ def parse_input(self, data):
+ vals = self.in_struct.parse(data)
+
+ return Container({ k: v() if callable(v) else v for k,v in vals.items() })
+
+ def parse_output(self, data, in_vals):
+ context = dict(in_vals)
+
+ if "data" in context:
+ del context["data"]
+
+ vals = self.out_struct.parse(data, **context)
+
+ return Container({ k: v() if callable(v) else v for k,v in vals.items() })
+
+ def __str__(self):
+ if self.rtype is None:
+ rtype = "void"
+ else:
+ rtype = str(self.rtype)
+
+ args = []
+ for name, field in self.args:
+ if name == "ret":
+ continue
+ args.append(f"{field} {name}")
+
+ return f"{rtype} {self.name}({', '.join(args)})"
+
+ def callback(self, func, in_data):
+ in_vals = self.parse_input(in_data)
+
+ args = []
+ kwargs = {}
+
+ out_vals = {}
+
+ for i, (name, field) in enumerate(self.args):
+ if name == "ret":
+ continue
+
+ dir = self.dir[i]
+
+ val = self.get_field_val(i, in_vals, out_vals, nullobj=NULL)
+ is_null = val is NULL
+ if is_null:
+ val = None
+
+ if dir == "inout":
+ if val is not None and not isinstance(val, list):
+ val = ByRef(val)
+ out_vals[name] = val
+ elif dir == "out" and not is_null:
+ val = ByRef(None)
+ out_vals[name] = val
+
+ if self.as_kwargs:
+ kwargs[name] = val
+ else:
+ args.append(val)
+
+ retval = func(*args, **kwargs)
+
+ if self.rtype is None:
+ assert retval is None
+ else:
+ assert retval is not None
+ out_vals["ret"] = retval
+
+ out_vals = {k: v.val if isinstance(v, ByRef) else v for k, v in out_vals.items()}
+
+ context = dict(in_vals)
+
+ if "obj" in context:
+ del context["obj"]
+
+ out_data = self.out_struct.build(out_vals, **context)
+ return out_data
+
+
+ def call(self, call, *args, **kwargs):
+ if args and kwargs:
+ raise Exception("Cannot use both args and kwargs")
+
+ if args:
+ for arg, (name, field) in zip(args, self.args):
+ kwargs[name] = arg
+
+ in_vals = {}
+ out_refs = {}
+
+ for i, (name, field) in enumerate(self.args):
+ if name == "ret":
+ continue
+
+ val = kwargs[name]
+ dir = self.dir[i]
+ nullable = self.nullable[i]
+ array_of_p = self.array_of_p[i]
+
+ if nullable:
+ if not array_of_p:
+ in_vals[name + "_null"] = val is None
+ else:
+ defaults = field.parse(b"\x00" * field.sizeof())
+ in_vals[name + "_null"] = [i is None for i in val]
+ val = [v if v is not None else defaults[i] for i, v in enumerate(val)]
+ else:
+ assert val is not None
+
+ if val is None:
+ continue
+
+ if dir == "out":
+ assert isinstance(val, ByRef)
+ out_refs[name] = val
+ elif dir == "inout":
+ if isinstance(val, ByRef):
+ in_vals[name] = val.val
+ out_refs[name] = val
+ elif val is not None:
+ in_vals[name] = val
+ elif val is not None:
+ in_vals[name] = val
+
+ in_data = self.in_struct.build(in_vals)
+ print(f"{self.name}({self.fmt_args(in_vals)})")
+
+ out_data = call(in_data)
+ out_vals = self.parse_output(out_data, in_vals)
+
+ for k, v in out_refs.items():
+ v.val = out_vals[k]
+
+ if self.rtype is not None:
+ return out_vals["ret"]
+
+def dump_fields(fields):
+ off = 0
+ for f in fields:
+ sizeof = f.sizeof()
+ print(f"{off:#x}: {f} ({sizeof:#x})")
+ off += sizeof
+
+class Call(Method):
+ pass
+
+class Callback(Method):
+ pass
+
+int8_t = Int8sl
+uint8_t = Int8ul
+int16_t = Int16sl
+uint16_t = Int16ul
+int32_t = Int32sl
+uint32_t = Int32ul
+int64_t = Int64sl
+uint64_t = Int64ul
+
+uint = uint32_t
+int_ = int32_t
+ulong = uint64_t
+long_ = int64_t
+
+void = None
+
+class IPCObject:
+ @classmethod
+ def methods(cls):
+ ret = {}
+ for c in cls.mro():
+ ret.update({k: (cls, v) for k, v in cls.__dict__.items() if isinstance(v, Method)})
+
+ return ret
+
+rt_bw_config_t = Struct(
+ "unk1" / UnkBytes(8),
+ "reg1" / Int64ul,
+ "reg2" / Int64ul,
+ "unk2" / UnkBytes(4),
+ "bit" / Int32ul,
+ "padding" / UnkBytes(0x1c),
+)
+
+IOUserClient = Struct(
+ "addr" / Hex(Int64ul),
+ "unk" / Int32ul,
+ "flag1" / Int8ul,
+ "flag2" / Int8ul,
+ Padding(2)
+)
+
+IOMobileFramebufferUserClient = IOUserClient
+
+IOMFBStatus = Int32ul
+IOMFBParameterName = Int32ul
+
+BufferDescriptor = uint64_t
+
+SwapCompleteData = Bytes(0x12)
+SwapInfoBlob = Bytes(0x6c4)
+
+SWAP_SURFACES = 4
+
+Rect = NamedTuple("rect", "x y w h", Int32ul[4])
+
+IOMFBSwapRec = Struct(
+ "ts1" / Default(Int64ul, 0),
+ "ts2" / Default(Int64ul, 0),
+ "unk_10" / Default(Int64ul, 0),
+ "unk_18" / Default(Int64ul, 0),
+ "ts64_unk" / Default(Int64ul, 0),
+ "unk_28" / Default(Int64ul, 0),
+ "ts3" / Default(Int64ul, 0),
+ "unk_38" / Default(Int64ul, 0),
+ "flags1" / Hex(Int64ul),
+ "flags2" / Hex(Int64ul),
+ "swap_id" / Int32ul,
+ "surf_ids" / Int32ul[SWAP_SURFACES],
+ "src_rect" / Rect[SWAP_SURFACES],
+ "surf_flags" / Int32ul[SWAP_SURFACES],
+ "surf_unk" / Int32ul[SWAP_SURFACES],
+ "dst_rect" / Rect[SWAP_SURFACES],
+ "swap_enabled" / Hex(Int32ul),
+ "swap_completed" / Hex(Int32ul),
+ "unk_10c" / Hex(Default(Int32ul, 0)),
+ "unk_110" / UnkBytes(0x1b8),
+ "unk_2c8" / Hex(Default(Int32ul, 0)),
+ "unk_2cc" / UnkBytes(0x14),
+ "unk_2e0" / Hex(Default(Int32ul, 0)),
+ "unk_2e2" / UnkBytes(0x2),
+ "bl_unk" / Hex(Int64ul), # seen: 0x0, 0x1, 0x101, 0x1_0000, 0x101_010101
+ "bl_val" / Hex(Int32ul), # range 0x10000000 - approximately 0x7fe07fc0 for 4 - 510 nits
+ "bl_power" / Hex(Int8ul), # constant 0x40, 0x00: backlight off
+ "unk_2f3" / UnkBytes(0x2d),
+)
+
+assert IOMFBSwapRec.sizeof() == 0x320
+
+MAX_PLANES = 3
+
+ComponentTypes = Struct(
+ "count" / Int8ul,
+ "types" / SizedArray(7, "count", Int8ul),
+)
+
+#ComponentTypes = Bytes(8)
+
+PlaneInfo = Struct(
+ "width" / Int32ul,
+ "height" / Int32ul,
+ "base" / Hex(Int32ul),
+ "offset" / Hex(Int32ul),
+ "stride" / Hex(Int32ul),
+ "size" / Hex(Int32ul),
+ "tile_size" / Int16ul,
+ "tile_w" / Int8ul,
+ "tile_h" / Int8ul,
+ "unk1" / UnkBytes(0xd),
+ "unk2" / Hex(Int8ul),
+ "unk3" / UnkBytes(0x26),
+)
+
+assert PlaneInfo.sizeof() == 0x50
+
+IOSurface = Struct(
+ "is_tiled" / bool_,
+ "unk_1" / bool_,
+ "unk_2" / bool_,
+ "plane_cnt" / Int32ul,
+ "plane_cnt2" / Int32ul,
+ "format" / FourCC,
+ "unk_f" / Default(Hex(Int32ul), 0),
+ "xfer_func" / Int8ul,
+ "colorspace" / Int8ul,
+ "stride" / Int32ul,
+ "pix_size" / Int16ul,
+ "pel_w" / Int8ul,
+ "pel_h" / Int8ul,
+ "offset" / Default(Hex(Int32ul), 0),
+ "width" / Int32ul,
+ "height" / Int32ul,
+ "buf_size" / Hex(Int32ul),
+ "unk_2d" / Default(Int32ul, 0),
+ "unk_31" / Default(Int32ul, 0),
+ "surface_id" / Int32ul,
+ "comp_types" / Default(SizedArray(MAX_PLANES, "plane_cnt", ComponentTypes), []),
+ "has_comp" / Bool(Int64ul),
+ "planes" / Default(SizedArray(MAX_PLANES, "plane_cnt", PlaneInfo), []),
+ "has_planes" / Bool(Int64ul),
+ "compression_info" / Default(SizedArray(MAX_PLANES, "plane_cnt", UnkBytes(0x34)), []),
+ "has_compr_info" / Bool(Int64ul),
+ "unk_1f5" / Int32ul,
+ "unk_1f9" / Int32ul,
+ "padding" / UnkBytes(7),
+)
+
+assert IOSurface.sizeof() == 0x204
+
+IOMFBColorFixedMatrix = Array(5, Array(3, ulong))
+
+class PropID(IntEnum):
+ BrightnessCorrection = 14
+
+class UPPipeAP_H13P(IPCObject):
+ A000 = Call(bool_, "late_init_signal")
+ A029 = Call(void, "setup_video_limits")
+ A034 = Call(void, "update_notify_clients_dcp", Array(14, uint))
+ A035 = Call(bool_, "is_hilo")
+ A036 = Call(bool_, "apt_supported")
+ A037 = Call(uint, "get_dfb_info", InOutPtr(uint), InOutPtr(Array(4, ulong)), InOutPtr(uint))
+ A038 = Call(uint, "get_dfb_compression_info", InOutPtr(uint))
+
+ D000 = Callback(bool_, "did_boot_signal")
+ D001 = Callback(bool_, "did_power_on_signal")
+ D002 = Callback(void, "will_power_off_signal")
+ D003 = Callback(void, "rt_bandwidth_setup_ap", config=OutPtr(rt_bw_config_t))
+
+IdleCachingState = uint32_t
+
+class UnifiedPipeline2(IPCObject):
+ A352 = Call(bool_, "applyProperty", uint, uint)
+ A353 = Call(uint, "get_system_type")
+ A357 = Call(void, "set_create_DFB")
+ A358 = Call(IOMFBStatus, "vi_set_temperature_hint")
+
+ D100 = Callback(void, "match_pmu_service")
+ D101 = Callback(uint32_t, "UNK_get_some_field")
+ D102 = Callback(void, "set_number_property", key=string(0x40), value=uint)
+ D103 = Callback(void, "set_boolean_property", key=string(0x40), value=bool_)
+ D106 = Callback(void, "removeProperty", key=string(0x40))
+ D107 = Callback(bool_, "create_provider_service")
+ D108 = Callback(bool_, "create_product_service")
+ D109 = Callback(bool_, "create_PMU_service")
+ D110 = Callback(bool_, "create_iomfb_service")
+ D111 = Callback(bool_, "create_backlight_service")
+ D112 = Callback(void, "set_idle_caching_state_ap", IdleCachingState, uint)
+ D116 = Callback(bool_, "start_hardware_boot")
+ D117 = Callback(bool_, "is_dark_boot")
+ D118 = Callback(bool_, "is_waking_from_hibernate")
+ D120 = Callback(bool_, "read_edt_data", key=string(0x40), count=uint, value=InOut(Lazy(SizedArray(8, "count", uint32_t))))
+
+ D122 = Callback(bool_, "setDCPAVPropStart", length=uint)
+ D123 = Callback(bool_, "setDCPAVPropChunk", data=HexDump(SizedBytes(0x1000, "length")), offset=uint, length=uint)
+ D124 = Callback(bool_, "setDCPAVPropEnd", key=string(0x40))
+
+class UPPipe2(IPCObject):
+ A102 = Call(uint64_t, "test_control", cmd=uint64_t, arg=uint)
+ A103 = Call(void, "get_config_frame_size", width=InOutPtr(uint), height=InOutPtr(uint))
+ A104 = Call(void, "set_config_frame_size", width=uint, height=uint)
+ A105 = Call(void, "program_config_frame_size")
+ A130 = Call(bool_, "init_ca_pmu")
+ A131 = Call(bool_, "pmu_service_matched")
+ A132 = Call(bool_, "backlight_service_matched")
+
+ D201 = Callback(uint32_t, "map_buf", buf=InPtr(BufferDescriptor), vaddr=OutPtr(ulong), dva=OutPtr(ulong), unk=bool_)
+ D202 = Callback(void, "unmap_buf", buf=InPtr(BufferDescriptor), unk1=uint, unk2=ulong, unkB=uint)
+
+ D206 = Callback(bool_, "match_pmu_service_2")
+ D207 = Callback(bool_, "match_backlight_service")
+ D208 = Callback(uint64_t, "get_calendar_time_ms")
+ D211 = Callback(void, "update_backlight_factor_prop", int_)
+
+class PropRelay(IPCObject):
+ D300 = Callback(void, "pr_publish", prop_id=uint32_t, value=int_)
+
+class IOMobileFramebufferAP(IPCObject):
+ A401 = Call(uint32_t, "start_signal")
+
+ A407 = Call(uint32_t, "swap_start", swap_id=InOutPtr(uint), client=InOutPtr(IOUserClient))
+ A408 = Call(uint32_t, "swap_submit_dcp",
+ swap_rec=InPtr(IOMFBSwapRec),
+ surfaces=Array(4, InPtr(IOSurface)),
+ surfAddr=Array(4, Hex(ulong)),
+ unkBool=bool_,
+ unkFloat=Float64l,
+ unkInt=uint,
+ unkOutBool=OutPtr(bool_))
+
+ A410 = Call(uint32_t, "set_display_device", uint)
+ A411 = Call(bool_, "is_main_display")
+ A438 = Call(uint32_t, "swap_set_color_matrix", matrix=InOutPtr(IOMFBColorFixedMatrix), func=uint32_t, unk=uint)
+#"A438": "IOMobileFramebufferAP::swap_set_color_matrix(IOMFBColorFixedMatrix*, IOMFBColorMatrixFunction, unsigned int)",
+
+ A412 = Call(uint32_t, "set_digital_out_mode", uint, uint)
+ A413 = Call(uint32_t, "get_digital_out_state", InOutPtr(uint))
+ A414 = Call(uint32_t, "get_display_area", InOutPtr(ulong))
+ A419 = Call(uint32_t, "get_gamma_table", InOutPtr(Bytes(0xc0c)))
+ A422 = Call(uint32_t, "set_matrix", uint, InPtr(Array(3, Array(3, ulong))))
+ A423 = Call(uint32_t, "set_contrast", InOutPtr(Float32l))
+ A426 = Call(uint32_t, "get_color_remap_mode", InOutPtr(uint32_t))
+ A427 = Call(uint32_t, "setBrightnessCorrection", uint)
+
+ A435 = Call(uint32_t, "set_block_dcp", arg1=uint64_t, arg2=uint, arg3=uint, arg4=Array(8, ulong), arg5=uint, data=SizedBytes(0x1000, "length"), length=ulong)
+ A439 = Call(uint32_t, "set_parameter_dcp", param=IOMFBParameterName, value=Lazy(SizedArray(4, "count", ulong)), count=uint)
+
+ A440 = Call(uint, "display_width")
+ A441 = Call(uint, "display_height")
+ A442 = Call(void, "get_display_size", OutPtr(uint), OutPtr(uint))
+ A443 = Call(int_, "do_create_default_frame_buffer")
+ A444 = Call(void, "printRegs")
+ A447 = Call(int_, "enable_disable_video_power_savings", uint)
+ A454 = Call(void, "first_client_open")
+ A455 = Call(void, "last_client_close_dcp", OutPtr(uint))
+ A456 = Call(bool_, "writeDebugInfo", ulong)
+ A457 = Call(void, "flush_debug_flags", uint)
+ A458 = Call(bool_, "io_fence_notify", uint, uint, ulong, IOMFBStatus)
+ A460 = Call(bool_, "setDisplayRefreshProperties")
+ A463 = Call(void, "flush_supportsPower", bool_)
+ A464 = Call(uint, "abort_swaps_dcp", InOutPtr(IOMobileFramebufferUserClient))
+
+ A467 = Call(uint, "update_dfb", surf=InPtr(IOSurface))
+ A468 = Call(uint32_t, "setPowerState", ulong, bool_, OutPtr(uint))
+ A469 = Call(bool_, "isKeepOnScreen")
+
+ D552 = Callback(bool_, "setProperty_dict", key=string(0x40), value=InPtr(Padded(0x1000, OSDictionary())))
+ D561 = Callback(bool_, "setProperty_dict", key=string(0x40), value=InPtr(Padded(0x1000, OSDictionary())))
+ D563 = Callback(bool_, "setProperty_int", key=string(0x40), value=InPtr(uint64_t))
+ D565 = Callback(bool_, "setProperty_bool", key=string(0x40), value=InPtr(Bool(uint32_t)))
+ D567 = Callback(bool_, "setProperty_str", key=string(0x40), value=string(0x40))
+
+ D574 = Callback(IOMFBStatus, "powerUpDART", bool_)
+
+ D575 = Callback(bool_, "get_dot_pitch", OutPtr(uint))
+ D576 = Callback(void, "hotPlug_notify_gated", ulong)
+ D577 = Callback(void, "powerstate_notify", bool_, bool_)
+ D578 = Callback(bool_, "idle_fence_create", IdleCachingState)
+ D579 = Callback(void, "idle_fence_complete")
+
+ D581 = Callback(void, "swap_complete_head_of_line", uint, bool_, uint, bool_)
+ D582 = Callback(bool_, "create_default_fb_surface", uint, uint)
+ D583 = Callback(bool_, "serializeDebugInfoCb", ulong, InPtr(uint64_t), uint)
+ D584 = Callback(void, "clear_default_surface")
+
+ D588 = Callback(void, "resize_default_fb_surface_gated")
+ D589 = Callback(void, "swap_complete_ap_gated", swap_id=uint, unkBool=bool_, swap_data=InPtr(SwapCompleteData), swap_info=SwapInfoBlob, unkUint=uint)
+
+ D591 = Callback(void, "swap_complete_intent_gated", swap_id=uint, unkB=bool_, unkInt=uint32_t, width=uint, height=uint)
+ D593 = Callback(void, "enable_backlight_message_ap_gated", bool_)
+ D594 = Callback(void, "setSystemConsoleMode", bool_)
+
+ D596 = Callback(bool_, "isDFBAllocated")
+ D597 = Callback(bool_, "preserveContents")
+ D598 = Callback(void, "find_swap_function_gated")
+
+class ServiceRelay(IPCObject):
+ D400 = Callback(void, "get_property", obj=FourCC, key=string(0x40), value=OutPtr(Bytes(0x200)), lenght=InOutPtr(uint))
+ D401 = Callback(bool_, "sr_get_uint_prop", obj=FourCC, key=string(0x40), value=InOutPtr(ulong))
+ D404 = Callback(void, "sr_set_uint_prop", obj=FourCC, key=string(0x40), value=uint)
+ D406 = Callback(void, "set_fx_prop", obj=FourCC, key=string(0x40), value=uint)
+ D408 = Callback(uint64_t, "sr_getClockFrequency", obj=FourCC, arg=uint)
+ D411 = Callback(IOMFBStatus, "sr_mapDeviceMemoryWithIndex", obj=FourCC, index=uint, flags=uint, addr=OutPtr(ulong), length=OutPtr(ulong))
+ D413 = Callback(bool_, "sr_setProperty_dict", obj=FourCC, key=string(0x40), value=InPtr(Padded(0x1000, OSDictionary())))
+ D414 = Callback(bool_, "sr_setProperty_int", obj=FourCC, key=string(0x40), value=InPtr(uint64_t))
+ D415 = Callback(bool_, "sr_setProperty_bool", obj=FourCC, key=string(0x40), value=InPtr(Bool(uint32_t)))
+
+mem_desc_id = uint
+
+class MemDescRelay(IPCObject):
+ D451 = Callback(mem_desc_id, "allocate_buffer", uint, ulong, uint, OutPtr(ulong), OutPtr(ulong), OutPtr(ulong))
+ D452 = Callback(mem_desc_id, "map_physical", paddr=ulong, size=ulong, flags=uint, dva=OutPtr(ulong), dvasize=OutPtr(ulong))
+ D453 = Callback(mem_desc_id, "withAddressRange", ulong, ulong, uint, uint64_t, OutPtr(uint), OutPtr(ulong))
+ D454 = Callback(IOMFBStatus, "prepare", uint, uint)
+ D455 = Callback(IOMFBStatus, "complete", uint, uint)
+ D456 = Callback(bool_, "release_descriptor", uint)
+
+ALL_CLASSES = [
+ UPPipeAP_H13P,
+ UnifiedPipeline2,
+ IOMobileFramebufferAP,
+ ServiceRelay,
+ PropRelay,
+ UPPipe2,
+ MemDescRelay,
+]
+
+ALL_METHODS = {}
+
+for cls in ALL_CLASSES:
+ ALL_METHODS.update(cls.methods())
+
+SHORT_CHANNELS = {
+ "CB": "d",
+ "CMD": "C",
+ "ASYNC": "a",
+ "OOBCMD": "O",
+ "OOBCB": "o",
+}
+
+RDIR = { ">": "<", "<": ">" }
+
+class Call:
+ def __init__(self, dir, chan, off, msg, in_size, out_size, in_data=b''):
+ self.dir = dir
+ self.chan = chan
+ self.msg = msg
+ self.off = off
+ self.in_size = in_size
+ self.out_size = out_size
+ self.in_data = in_data
+ self.out_data = None
+ self.complete = False
+ self.ret = None
+
+ def ack(self, out_data):
+ self.out_data = out_data
+ self.complete = True
+
+ def print_req(self, indent=""):
+ log = f"{indent}{self.dir}{SHORT_CHANNELS[self.chan]}[{self.off:#x}] {self.msg} "
+
+ cls, method = ALL_METHODS.get(self.msg, (None, None))
+ if cls is None:
+ print(log + f"{self.in_size:#x}/{self.out_size:#x}")
+ return
+
+ log += f"{cls.__name__}::{method.name}("
+ in_size = method.in_struct.sizeof()
+
+ if in_size != len(self.in_data):
+ print(f"{log} !! Expected {in_size:#x} bytes, got {len(self.in_data):#x} bytes (in)")
+ dump_fields(method.in_fields)
+ chexdump(self.in_data)
+ self.in_vals = {}
+ return
+
+ self.in_vals = method.parse_input(self.in_data)
+
+ log += f"{method.fmt_args(self.in_vals)})"
+
+ print(log)
+
+ method.print_long_args(indent, self.in_vals)
+ #if method.in_fields:
+ #print(self.in_vals)
+
+ def print_reply(self, indent=""):
+ assert self.complete
+ log = f"{indent}{RDIR[self.dir]}{SHORT_CHANNELS[self.chan]}[{self.off:#x}] {self.msg} "
+
+ cls, method = ALL_METHODS.get(self.msg, (None, None))
+ if cls is None:
+ print(log + f"{self.in_size:#x}/{self.out_size:#x}")
+ return
+
+ log += f"{cls.__name__}::{method.name}("
+ out_size = method.out_struct.sizeof()
+
+ if out_size != len(self.out_data):
+ print(f"{log} !! Expected {out_size:#x} bytes, got {len(self.out_data):#x} bytes (out)")
+ dump_fields(method.out_fields)
+ chexdump(self.out_data)
+ return
+
+ self.out_vals = method.parse_output(self.out_data, self.in_vals)
+
+ log += f"{method.fmt_args(self.in_vals, self.out_vals)})"
+
+ if "ret" in self.out_vals:
+ self.ret = self.out_vals.ret
+ del self.out_vals["ret"]
+ log += f" = {self.ret!r}"
+
+ print(log)
+
+ method.print_long_args(indent, self.in_vals, self.out_vals)
+ #if len(method.out_fields) - (self.ret is not None):
+ #print(self.out_vals)
diff --git a/tools/proxyclient/m1n1/fw/dcp/manager.py b/tools/proxyclient/m1n1/fw/dcp/manager.py
new file mode 100644
index 0000000..7977c3a
--- /dev/null
+++ b/tools/proxyclient/m1n1/fw/dcp/manager.py
@@ -0,0 +1,319 @@
+# SPDX-License-Identifier: MIT
+import pprint
+import struct, functools, time
+from dataclasses import dataclass
+from enum import IntEnum
+
+from construct.lib import hexundump
+
+from ..asc.base import *
+from ...utils import *
+
+from . import ipc
+from .dcpep import CallContext
+
+## DCP API manager
+
+class DCPBaseManager:
+ def __init__(self, dcpep):
+ self.dcpep = dcpep
+ self.dcp = dcpep.asc
+ dcpep.mgr = self
+
+ self.name_map = {}
+ self.tag_map = {}
+
+ self.in_callback = 0
+
+ for k, (cls, v) in ipc.ALL_METHODS.items():
+ self.name_map[v.name] = k, v
+ self.tag_map[k] = v
+
+ def handle_cb(self, state):
+ method = self.tag_map.get(state.tag, None)
+ if method is None:
+ raise Exception(f"Unknown callback {state.tag}")
+
+ func = getattr(self, method.name, None)
+
+ if func is None:
+ raise Exception(f"Unimplemented callback {method!s} [{state.tag}]")
+
+ self.in_callback += 1
+ try:
+ retval = method.callback(func, state.in_data)
+ except Exception as e:
+ print(f"Exception in callback {method.name}")
+ raise
+ self.in_callback -= 1
+ return retval
+
+ def __getattr__(self, attr):
+ tag, method = self.name_map.get(attr, (None, None))
+ if method is None or tag.startswith("D"):
+ raise AttributeError(f"Unknown method {attr}")
+
+ out_len = method.out_struct.sizeof()
+ if self.in_callback:
+ ctx = CallContext.CB
+ else:
+ ctx = CallContext.CMD
+ rpc = functools.partial(self.dcpep.ch_cmd.call, ctx, tag, out_len=out_len)
+ return functools.partial(method.call, rpc)
+
+class DCPManager(DCPBaseManager):
+ def __init__(self, dcpep, compatible='t8103'):
+ super().__init__(dcpep)
+
+ self.iomfb_prop = {}
+ self.dcpav_prop = {}
+ self.service_prop = {}
+ self.pr_prop = {}
+
+ self.swaps = 0
+ self.frame = 0
+
+ self.mapid = 0
+ self.bufs = {}
+
+ self.compatible = compatible
+
+ ## IOMobileFramebufferAP methods
+
+ def find_swap_function_gated(self):
+ pass
+
+ def create_provider_service(self):
+ return True
+
+ def create_product_service(self):
+ return True
+
+ def create_PMU_service(self):
+ return True
+
+ def create_iomfb_service(self):
+ return True
+
+ def create_backlight_service(self):
+ return False
+
+ def setProperty(self, key, value):
+ self.iomfb_prop[key] = value
+ print(f"setProperty({key} = {value!r})")
+ return True
+
+ setProperty_dict = setProperty_int = setProperty_bool = setProperty_str = setProperty
+
+ def swap_complete_ap_gated(self, swap_id, unkBool, swap_data, swap_info, unkUint):
+ swap_data_ptr = "NULL" if swap_data is None else "..."
+ print(f"swap_complete_ap_gated({swap_id}, {unkBool}, {swap_data_ptr}, ..., {unkUint}")
+ if swap_data is not None:
+ chexdump(swap_data)
+ chexdump(swap_info)
+ self.swaps += 1
+ self.frame = swap_id
+
+ def swap_complete_intent_gated(self, swap_id, unkB, unkInt, width, height):
+ print(f"swap_complete_intent_gated({swap_id}, {unkB}, {unkInt}, {width}, {height}")
+ self.swaps += 1
+ self.frame = swap_id
+
+ def enable_backlight_message_ap_gated(self, unkB):
+ print(f"enable_backlight_message_ap_gated({unkB})")
+
+ # wrapper for set_digital_out_mode to print information on the setted modes
+ def SetDigitalOutMode(self, color_id, timing_id):
+ color_mode = [x for x in self.dcpav_prop['ColorElements'] if x['ID'] == color_id][0]
+ timing_mode = [x for x in self.dcpav_prop['TimingElements'] if x['ID'] == timing_id][0]
+ pprint.pprint(color_mode)
+ pprint.pprint(timing_mode)
+ self.set_digital_out_mode(color_id, timing_id)
+
+ ## UPPipeAP_H13P methods
+
+ def did_boot_signal(self):
+ return True
+
+ def did_power_on_signal(self):
+ return True
+
+ def will_power_off_signal(self):
+ return
+
+ def rt_bandwidth_setup_ap(self, config):
+ print("rt_bandwidth_setup_ap(...)")
+ if self.compatible == 't8103':
+ config.val = {
+ "reg1": 0x23b738014, # reg[5] in disp0/dispext0, plus 0x14 - part of pmgr
+ "reg2": 0x23bc3c000, # reg[6] in disp0/dispext0 - part of pmp/pmgr
+ "bit": 2,
+ }
+ elif self.compatible == 't600x':
+ config.val = {
+ "reg1": 0x28e3d0000 + 0x988, # reg[4] in disp0/dispext0, plus 0x988
+ "reg2": 0x0,
+ "bit": 0,
+ }
+ else:
+ raise ValueError(self.compatible)
+
+ ## UnifiedPipeline2 methods
+
+ def match_pmu_service(self):
+ pass
+
+ def set_number_property(self, key, value):
+ pass
+
+ def create_provider_service(self):
+ return True
+
+ def is_dark_boot(self):
+ return False
+
+ def read_edt_data(self, key, count, value):
+ return False
+
+ def UNK_get_some_field(self):
+ return 0
+
+ def start_hardware_boot(self):
+ self.set_create_DFB()
+ self.do_create_default_frame_buffer()
+ self.setup_video_limits()
+ self.flush_supportsPower(True)
+ self.late_init_signal()
+ self.setDisplayRefreshProperties()
+ return True
+
+ def setDCPAVPropStart(self, length):
+ print(f"setDCPAVPropStart({length:#x})")
+ self.dcpav_prop_len = length - 1 # off by one?
+ self.dcpav_prop_off = 0
+ self.dcpav_prop_data = []
+ return True
+
+ def setDCPAVPropChunk(self, data, offset, length):
+ print(f"setDCPAVPropChunk(..., {offset:#x}, {length:#x})")
+ assert offset == self.dcpav_prop_off
+ self.dcpav_prop_data.append(data)
+ self.dcpav_prop_off += len(data)
+ return True
+
+ def setDCPAVPropEnd(self, key):
+ print(f"setDCPAVPropEnd({key!r})")
+ blob = b"".join(self.dcpav_prop_data)
+ assert self.dcpav_prop_len == len(blob)
+ self.dcpav_prop[key] = ipc.OSSerialize().parse(blob)
+ self.dcpav_prop_data = self.dcpav_prop_len = self.dcpav_prop_off = None
+ #pprint.pprint(self.dcpav_prop[key])
+ return True
+
+ def set_boolean_property(self, key, value):
+ print(f"set {key!r} = {value}")
+
+ def removeProperty(self, key):
+ print(f"removeProperty({key!r})")
+
+ def powerstate_notify(self, unk1, unk2):
+ print(f"powerstate_notify({unk1}, {unk2})")
+
+ def create_default_fb_surface(self, width, height):
+ print(f"create_default_fb_surface({width}x{height})")
+ return True
+
+ def powerUpDART(self, unk):
+ print(f"powerUpDART({unk})")
+ return 0
+
+ def hotPlug_notify_gated(self, unk):
+ print(f"hotPlug_notify_gated({unk})")
+
+ def is_waking_from_hibernate(self):
+ return False
+
+ ## UPPipe2 methods
+
+ def match_pmu_service_2(self):
+ return True
+
+ def match_backlight_service(self):
+ return True
+
+ def get_calendar_time_ms(self):
+ return time.time_ns() // 1000_000
+
+ def update_backlight_factor_prop(self, value):
+ pass
+
+ def map_buf(self, buf, vaddr, dva, unk):
+ print(f"map buf {buf}, {unk}")
+ paddr, dcpdva, dvasize = self.bufs[buf]
+ vaddr.val = 0
+ dva.val = self.dcp.disp_dart.iomap(4, paddr, dvasize)
+ print(f"mapped to dva {dva}")
+ return 0
+
+ def update_backlight_factor_prop(self, unk):
+ print(f"update_backlight_factor_prop {unk}")
+
+ ## ServiceRelay methods
+
+ def sr_setProperty(self, obj, key, value):
+ self.service_prop.setdefault(obj, {})[key] = value
+ print(f"sr_setProperty({obj}/{key} = {value!r})")
+ return True
+
+ def sr_getClockFrequency(self, obj, arg):
+ print(f"sr_getClockFrequency({obj}, {arg})")
+ return 533333328
+
+ sr_setProperty_dict = sr_setProperty_int = sr_setProperty_bool = sr_setProperty_str = sr_setProperty
+
+ def sr_get_uint_prop(self, obj, key, value):
+ value.val = 0
+ return False
+
+ def sr_set_uint_prop(self, obj, key, value):
+ print(f"sr_set_uint_prop({obj}, {key} = {value})")
+
+ def set_fx_prop(self, obj, key, value):
+ print(f"set_fx_prop({obj}, {key} = {value})")
+
+ def sr_mapDeviceMemoryWithIndex(self, obj, index, flags, addr, length):
+ assert obj == "PROV"
+ addr.val, length.val = self.dcp.u.adt["/arm-io/disp0"].get_reg(index)
+ print(f"sr_mapDeviceMemoryWithIndex({obj}, {index}, {flags}, {addr.val:#x}, {length.val:#x})")
+ return 0
+
+ ## PropRelay methods
+
+ def pr_publish(self, prop_id, value):
+ self.pr_prop[prop_id] = value
+ print(f"pr_publish({prop_id}, {value!r})")
+
+ ## MemDescRelay methods:
+
+ def allocate_buffer(self, unk0, size, unk1, paddr, dva, dvasize):
+ print(f"allocate_buffer({unk0}, {size}, {unk1})")
+
+ dvasize.val = align_up(size, 4096)
+ paddr.val = self.dcp.u.memalign(0x4000, size)
+ dva.val = self.dcp.dart.iomap(0, paddr.val, size)
+
+ self.mapid += 1
+ print(f"Allocating {self.mapid} as {hex(paddr.val)} / {hex(dva.val)}")
+
+ self.bufs[self.mapid] = (paddr.val, dva.val, dvasize.val)
+
+ return self.mapid
+
+ def map_physical(self, paddr, size, flags, dva, dvasize):
+ dvasize.val = align_up(size, 4096)
+ dva.val = self.dcp.dart.iomap(0, paddr, size)
+ print(f"map_physical({paddr:#x}, {size:#x}, {flags}, {dva.val:#x}, {dvasize.val:#x})")
+
+ self.mapid += 1
+ return self.mapid
+
diff --git a/tools/proxyclient/m1n1/fw/dcp/parse_log.py b/tools/proxyclient/m1n1/fw/dcp/parse_log.py
new file mode 100644
index 0000000..e57f637
--- /dev/null
+++ b/tools/proxyclient/m1n1/fw/dcp/parse_log.py
@@ -0,0 +1,43 @@
+# SPDX-License-Identifier: MIT
+
+from m1n1.utils import *
+from m1n1.fw.dcp.ipc import *
+
+def parse_log(fd):
+ op_stack = {}
+ for line in fd:
+ optype, args = line.split(" ", 1)
+ if optype == "CALL":
+ d, msg, chan, off, msg, in_size, out_size, in_data = args.split(" ")
+ op = Call(d, chan, int(off, 0), msg, int(in_size, 0), int(out_size, 0),
+ bytes.fromhex(in_data))
+ op_stack.setdefault(chan, []).append(op)
+ elif optype == "ACK":
+ d, msg, chan, off, out_data = args.split(" ")
+ op = op_stack[chan].pop()
+ assert int(off, 0) == op.off
+ op.ack(bytes.fromhex(out_data))
+ else:
+ raise Exception(f"Unknown log cmd {optype}")
+
+ yield op
+
+def dump_log(fd):
+ nesting = {
+ "": 0,
+ "OOB": 0,
+ }
+ for op in parse_log(fd):
+ ctx = ""
+ if "OOB" in op.chan:
+ ctx = "[OOB] -----------> "
+ if not op.complete:
+ op.print_req(indent=ctx + " " * nesting.setdefault(ctx, 0))
+ nesting[ctx] += 1
+ else:
+ nesting[ctx] -= 1
+ op.print_reply(indent=ctx + " " * nesting.setdefault(ctx, 0))
+
+if __name__ == "__main__":
+ import sys
+ dump_log(open(sys.argv[1]))
diff --git a/tools/proxyclient/m1n1/fw/mtp.py b/tools/proxyclient/m1n1/fw/mtp.py
new file mode 100644
index 0000000..5b22b8a
--- /dev/null
+++ b/tools/proxyclient/m1n1/fw/mtp.py
@@ -0,0 +1,411 @@
+# SPDX-License-Identifier: MIT
+
+import struct
+from construct import *
+from ..constructutils import *
+from ..utils import *
+
+class HIDDescriptor(ConstructClass):
+ subcon = Struct(
+ "descriptor" / HexDump(GreedyBytes)
+ )
+
+class GPIOInit(ConstructClass):
+ subcon = Struct(
+ "unk1" / Int16ul,
+ "gpio_id"/ Int16ul,
+ "gpio_name" / PaddedString(32, "ascii")
+ )
+
+class InitBlock(ConstructClass):
+ subcon = Struct(
+ "type" / Int16ul,
+ "subtype" / Int16ul,
+ "length" / Int16ul,
+ "payload" / FixedSized(this.length,
+ Switch(this.type, {
+ 0: HIDDescriptor,
+ 1: GPIOInit,
+ 2: Bytes(0),
+ }, default=GreedyBytes))
+ )
+
+class InitMsg(ConstructClass):
+ subcon = Struct(
+ "msg_type" / Const(0xf0, Int8ul),
+ "msg_subtype" / Const(0x01, Int8ul),
+ "unk" / Const(0x00, Int8ul),
+ "device_id" / Int8ul,
+ "device_name" / PaddedString(16, "ascii"),
+ "msg" / RepeatUntil(lambda obj, lst, ctx: lst[-1].type == 2, InitBlock)
+ )
+
+class DeviceReadyMsg(ConstructClass):
+ subcon = Struct(
+ "msg_type" / Const(0xf1, Int8ul),
+ "device_id" / Int8ul,
+ "unk" / Int16ul
+ )
+
+class GPIORequestMsg(ConstructClass):
+ subcon = Struct(
+ "msg_type" / Const(0xa0, Int8ul),
+ "device_id" / Int8ul,
+ "gpio_num" / Int8ul,
+ "cmd" / Int16ul,
+ "args" / HexDump(GreedyBytes)
+ )
+
+NotificationMsg = Select(
+ DeviceReadyMsg,
+ InitMsg,
+ GPIORequestMsg,
+ HexDump(GreedyBytes),
+)
+
+class UnkDeviceControlMsg(ConstructClass):
+ subcon = Struct(
+ "command" / Int8ul,
+ "args" / HexDump(GreedyBytes),
+ )
+
+class DeviceEnableMsg(ConstructClass):
+ subcon = Struct(
+ "command" / Const(0xb4, Int8ul),
+ "device_id" / Int8ul,
+ )
+
+class DeviceResetMsg(ConstructClass):
+ subcon = Struct(
+ "command" / Const(0x40, Int8ul),
+ "unk1" / Int8ul,
+ "device_id" / Int8ul,
+ "state" / Int8ul,
+ )
+
+class InitBufMsg(ConstructClass):
+ subcon = Struct(
+ "command" / Const(0x91, Int8ul),
+ "unk1" / Int8ul,
+ "unk2" / Int8ul,
+ "buf_addr" / Int64ul,
+ "buf_size" / Int32ul,
+ )
+
+class InitAFEMsg(ConstructClass):
+ subcon = Struct(
+ "command" / Const(0x95, Int8ul),
+ "unk1" / Int8ul,
+ "unk2" / Int8ul,
+ "iface" / Int8ul,
+ "buf_addr" / Int64ul,
+ "buf_size" / Int32ul,
+ )
+
+class UnkMsgC1(ConstructClass):
+ subcon = Struct(
+ "command" / Const(0xc1, Int8ul),
+ "unk1" / Int8ul,
+ )
+
+class GPIOAckMsg(ConstructClass):
+ subcon = Struct(
+ "command" / Const(0xa1, Int8ul),
+ "unk" / Int32ul,
+ "msg" / GPIORequestMsg,
+ )
+
+DeviceControlMsg = Select(
+ DeviceEnableMsg,
+ DeviceResetMsg,
+ InitAFEMsg,
+ InitBufMsg,
+ UnkMsgC1,
+ UnkDeviceControlMsg
+)
+
+class DeviceControlAck(ConstructClass):
+ subcon = Struct(
+ "command" / Int8ul
+ )
+
+class MessageHeader(ConstructClass):
+ subcon = Struct(
+ "flags" / Int16ul,
+ "length" / Int16ul,
+ "retcode" / Int32ul,
+ )
+
+class TXMessage(ConstructClass):
+ subcon = Struct(
+ "hdr" / MessageHeader,
+ "msg" / FixedSized(this.hdr.length,
+ Switch(this.hdr.flags, {
+ 0x40: HexDump(GreedyBytes),
+ 0x80: DeviceControlMsg,
+ 0x81: Int8ul,
+ }))
+ )
+
+ def __init__(self):
+ self.hdr = MessageHeader()
+
+class RXMessage(ConstructClass):
+ subcon = Struct(
+ "hdr" / MessageHeader,
+ "msg" / FixedSized(this.hdr.length, HexDump(GreedyBytes)),
+ )
+
+class MTPInterface:
+ def __init__(self, proto, iface):
+ self.proto = proto
+ self.iface = iface
+ self.tx_seq = 0
+ self.initialized = False
+ self.gpios = {}
+
+ def send(self, msg):
+ self.proto.send(self.iface, self.tx_seq & 0xff, msg)
+ self.tx_seq += 1
+
+ def get_report(self, idx):
+ msg = TXMessage()
+ msg.hdr.flags = 0x81
+ msg.hdr.length = 1
+ msg.hdr.retcode = 0
+ msg.msg = idx
+ self.send(msg.build())
+
+ def packet(self, pkt):
+ self.log(f"RX: {pkt.hex()}")
+
+ def log(self, s):
+ self.proto.log(f"[{self.NAME}] " + s)
+
+ def initialize(self):
+ self.proto.comm.enable_device(self.iface)
+
+ def report(self, msg):
+ self.log(f"report: {msg.hex()}")
+
+ def ack(self, msg):
+ self.log(f"ack: {msg.hex()}")
+
+ def unk(self, msg):
+ self.log(f"unk: {msg.hex()}")
+
+ def packet(self, pkt):
+ msg = RXMessage.parse(pkt)
+ mtype = msg.hdr.flags
+ #self.log(f"FL:{msg.hdr.flag s:04x} unk:{msg.hdr.unk:08x}")
+ if mtype == 0x00:
+ self.report(msg.msg)
+ elif mtype == 0x80:
+ self.ack(msg.hdr.retcode, msg.msg)
+ elif mtype == 0x81:
+ self.log(f"REPORT")
+ chexdump(msg.msg, print_fn=self.log)
+ elif mtype == 0x40:
+ self.unk(msg.msg)
+
+ def __str__(self):
+ return f"{self.iface}/{self.NAME}"
+
+
+class MTPCommInterface(MTPInterface):
+ NAME = "comm"
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.last_cmd = None
+ self.gpios = {}
+
+
+ def device_control(self, dcmsg):
+ while self.last_cmd is not None:
+ self.proto.work()
+ msg = TXMessage()
+ msg.hdr.flags = 0x80
+ msg.hdr.length = len(dcmsg.build())
+ msg.hdr.retcode = 0
+ msg.msg = dcmsg
+ #self.log(f"Send device control {dcmsg}")
+ self.last_cmd = dcmsg.command
+ self.send(msg.build())
+ while self.last_cmd is not None:
+ self.proto.work()
+
+ def enable_device(self, iface):
+ msg = DeviceEnableMsg()
+ msg.device_id = iface
+ self.device_control(msg)
+
+ def report(self, msg):
+ msg = NotificationMsg.parse(msg)
+
+ if isinstance(msg, DeviceReadyMsg):
+ iface = self.proto.iface[msg.device_id]
+ iface.initialized = True
+ self.log(f"{iface}: init complete")
+ elif isinstance(msg, InitMsg):
+ iface = self.proto.get_interface(msg.device_id, msg.device_name)
+ for blk in msg.msg:
+ if isinstance(blk.payload, HIDDescriptor):
+ self.log(f"Got HID descriptor for {iface}:")
+ iface.descriptor = blk.payload.descriptor
+ self.log(hexdump(iface.descriptor))
+ iface.initialize()
+ elif isinstance(blk.payload, GPIOInit):
+ self.log(f"GPIO Init: {blk.payload}")
+ prop = getattr(self.proto.node[msg.device_name],
+ f"function-{blk.payload.gpio_name}".replace("-", "_"))
+ key = struct.pack(">I", prop.args[0]).decode("ascii")
+ val = prop.args[1]
+ self.log(f"GPIO key: {key}")
+ self.gpios[(msg.device_id, blk.payload.gpio_id)] = key, val
+ elif isinstance(msg, GPIORequestMsg):
+ self.log(f"GPIO request: {msg}")
+ smcep = self.proto.smc.epmap[0x20]
+ key, val = self.gpios[(msg.device_id, msg.gpio_num)]
+ if msg.cmd == 3:
+ smcep.write32(key, val | 1)
+ smcep.write32(key, val)
+
+ ackmsg = GPIOAckMsg()
+ ackmsg.unk = 0
+ ackmsg.msg = msg
+ self.device_control(ackmsg)
+
+ def ack(self, retcode, msg):
+ msg = DeviceControlAck.parse(msg)
+ self.log(f"Got ACK for {msg.command:#x}: {retcode:08x}")
+ assert msg.command == self.last_cmd
+ self.last_cmd = None
+
+ def init_afe(self, iface, data):
+ paddr, dva = self.proto.mtp.ioalloc(len(data))
+ self.proto.u.iface.writemem(paddr, data)
+
+ afemsg = InitAFEMsg()
+ afemsg.unk1 = 2
+ afemsg.unk2 = 0
+ afemsg.iface = iface
+ afemsg.buf_addr = dva
+ afemsg.buf_size = len(data)
+ self.device_control(afemsg)
+
+ def device_reset(self, iface, unk1, state):
+ self.log(f"device_reset({iface}, {unk1}, {state})")
+ rmsg = DeviceResetMsg()
+ rmsg.device_id = iface
+ rmsg.unk1 = unk1
+ rmsg.state = state
+ self.device_control(rmsg)
+
+class MTPHIDInterface(MTPInterface):
+ pass
+
+class MTPMultitouchInterface(MTPHIDInterface):
+ NAME = "multi-touch"
+
+ def initialize(self):
+ super().initialize()
+
+ #data = open("afe.bin", "rb").read()
+ #self.proto.comm.init_afe(self.iface, data)
+ #self.proto.comm.device_reset(self.iface, 1, 0)
+ #self.proto.comm.device_reset(self.iface, 1, 2)
+
+class MTPKeyboardInterface(MTPHIDInterface):
+ NAME = "keyboard"
+
+class MTPSTMInterface(MTPHIDInterface):
+ NAME = "stm"
+
+class MTPActuatorInterface(MTPHIDInterface):
+ NAME = "actuator"
+
+class MTPTPAccelInterface(MTPHIDInterface):
+ NAME = "tp_accel"
+
+class MTPProtocol:
+ INTERFACES = [
+ MTPCommInterface,
+ MTPMultitouchInterface,
+ MTPKeyboardInterface,
+ MTPSTMInterface,
+ MTPActuatorInterface,
+ MTPTPAccelInterface,
+ ]
+
+ def __init__(self, u, node, mtp, dockchannel, smc):
+ self.node = node
+ self.smc = smc
+ self.u = u
+ self.mtp = mtp
+ self.dockchannel = dockchannel
+ self.iface = {}
+
+ # Add initial comm interface
+ self.get_interface(0, "comm")
+
+ def get_interface(self, iface, name):
+ if iface in self.iface:
+ return self.iface[iface]
+
+ for cls in self.INTERFACES:
+ if cls.NAME == name:
+ break
+ else:
+ self.log(f"Unknown interface name {name}")
+ return None
+ obj = cls(self, iface)
+ self.iface[iface] = obj
+ setattr(self, name.replace("-", "_"), obj)
+ return obj
+
+ def checksum(self, d):
+ assert len(d) % 4 == 0
+ c = len(d) // 4
+ return 0xffffffff - sum(struct.unpack(f"<{c}I", d)) & 0xffffffff
+
+ def read_pkt(self):
+ self.mtp.work_pending()
+ hdr = self.dockchannel.read(8)
+ hlen, mtype, size, ctr, devid, pad = struct.unpack("<BBHBBH", hdr)
+ #self.log(f"<L:{hlen} T:{mtype:02x} S:{size:04x} D:{devid}")
+ assert hlen == 8
+ #assert mtype == 0x12
+ data = self.dockchannel.read(size)
+ checksum = struct.unpack("<I", self.dockchannel.read(4))[0]
+ expect = self.checksum(hdr + data)
+ if expect != checksum:
+ self.log(f"Checksum error: expected {expect:08x}, got {checksum:08x}")
+ return devid, data
+
+ def send(self, iface, seq, msg):
+ if len(msg) % 4:
+ msg += bytes(4 - len(msg) % 4)
+ hdr = struct.pack("<BBHBBH", 8, 0x11, len(msg), seq, iface, 0)
+ checksum = self.checksum(hdr + msg)
+ pkt = hdr + msg + struct.pack("<I", checksum)
+ self.dockchannel.write(pkt)
+ self.mtp.work_pending()
+
+ def work_pending(self):
+ self.mtp.work_pending()
+ while self.dockchannel.rx_count != 0:
+ self.work()
+ self.mtp.work_pending()
+
+ def work(self):
+ devid, pkt = self.read_pkt()
+ self.iface[devid].packet(pkt)
+
+ def wait_init(self, name):
+ self.log(f"Waiting for {name}...")
+ while not hasattr(self, name) or not getattr(self, name).initialized:
+ self.work()
+
+ def log(self, m):
+ print("[MTP]" + m)
diff --git a/tools/proxyclient/m1n1/fw/pmp.py b/tools/proxyclient/m1n1/fw/pmp.py
new file mode 100644
index 0000000..fbfef45
--- /dev/null
+++ b/tools/proxyclient/m1n1/fw/pmp.py
@@ -0,0 +1,168 @@
+# SPDX-License-Identifier: MIT
+import struct
+
+from ..utils import *
+
+from .asc import StandardASC
+from .asc.base import *
+
+class PMPMessage(Register64):
+ TYPE = 56, 44
+
+class PMP_Startup(PMPMessage):
+ TYPE = 56, 44, Constant(0x00)
+
+class PMP_Configure(PMPMessage):
+ TYPE = 56, 44, Constant(0x10)
+ DVA = 47, 0
+
+class PMP_Configure_Ack(PMPMessage):
+ TYPE = 56, 44, Constant(0x20)
+ UNK = 47, 0
+
+class PMP_Init1(PMPMessage):
+ TYPE = 56, 44, Constant(0x200)
+ UNK1 = 43, 16
+ UNK2 = 15, 0
+
+class PMP_Init1_Ack(PMPMessage):
+ TYPE = 56, 44, Constant(0x201)
+ UNK1 = 43, 16
+ UNK2 = 15, 0
+
+class PMP_Init2(PMPMessage):
+ TYPE = 56, 44, Constant(0x202)
+ UNK1 = 43, 16
+ UNK2 = 15, 0
+
+class PMP_Init2_Ack(PMPMessage):
+ TYPE = 56, 44, Constant(0x203)
+ UNK1 = 43, 16
+ UNK2 = 15, 0
+
+class PMP_Unk(PMPMessage):
+ TYPE = 56, 44, Constant(0x100)
+ UNK1 = 43, 16
+ UNK2 = 15, 0
+
+class PMP_Unk_Ack(PMPMessage):
+ TYPE = 56, 44, Constant(0x110)
+ UNK1 = 43, 16
+ UNK2 = 15, 0
+
+class PMP_DevPwr(PMPMessage):
+ TYPE = 56, 44, Constant(0x20e)
+ DEV = 31, 16
+ STATE = 15, 0
+
+class PMP_DevPwr_Sync(PMPMessage):
+ TYPE = 56, 44, Constant(0x208)
+ DEV = 31, 16
+ STATE = 15, 0
+
+class PMP_DevPwr_Ack(PMPMessage):
+ TYPE = 56, 44, Constant(0x209)
+ DEV = 31, 16
+ STATE = 15, 0
+
+class PMPEndpoint(ASCBaseEndpoint):
+ BASE_MESSAGE = PMPMessage
+ SHORT = "pmpep"
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.shmem = self.shmem_dva = None
+ self.init_complete = False
+ self.init1_acked = False
+ self.init2_acked = False
+ self.unk_acked = False
+
+ @msg_handler(0x00, PMP_Startup)
+ def Startup(self, msg):
+ self.log("Starting up")
+
+ self.shmem, self.shmem_dva = self.asc.ioalloc(0x10000)
+
+ self.send_init_config()
+ return True
+
+ def send_init_config(self):
+ self.asc.p.memset32(self.shmem, 0, 0x10000)
+ dram_config = self.asc.u.adt["arm-io/pmp/iop-pmp-nub"].energy_model_dram_configs
+ self.asc.iface.writemem(self.shmem + 0x2000, dram_config)
+
+ node = self.asc.u.adt["arm-io/pmp"]
+
+ maps = []
+ dva = 0xc0000000
+ for i in range(3, len(node.reg)):
+ addr, size = node.get_reg(i)
+ if size == 0:
+ maps.append(struct.pack("<QQ", 0, 0))
+ continue
+
+ self.asc.dart.iomap_at(0, dva, addr, size)
+ self.log(f"map {addr:#x} -> {dva:#x} [{size:#x}]")
+ maps.append(struct.pack("<QQ", dva, size))
+ dva += align(size, 0x4000)
+
+ chexdump(b"".join(maps))
+
+ self.asc.iface.writemem(self.shmem + 0xe000, b"".join(maps))
+ self.send(PMP_Configure(DVA=self.shmem_dva))
+
+ while not self.init_complete:
+ self.asc.work()
+ return True
+
+ @msg_handler(0x20, PMP_Configure_Ack)
+ def Configure_Ack(self, msg):
+ self.init_complete = True
+
+ props = self.asc.iface.readmem(self.shmem, 0x2000)
+ devinfo = self.asc.iface.readmem(self.shmem + 0x4000, 0x1000)
+ status = self.asc.iface.readmem(self.shmem + 0xc000, 0x100)
+
+ print("PMP Props:")
+ chexdump(props)
+ print("PMP Device Info:")
+ chexdump(devinfo)
+ print("PMP Status:")
+ chexdump(status)
+
+ self.send(PMP_Init1(UNK1=1, UNK2=3))
+ while not self.init1_acked:
+ self.asc.work()
+
+ self.send(PMP_Init2(UNK1=1, UNK2=0))
+ while not self.init2_acked:
+ self.asc.work()
+
+ self.send(PMP_Unk(UNK1=0x3bc, UNK2=2))
+ while not self.unk_acked:
+ self.asc.work()
+
+ return True
+
+ @msg_handler(0x201, PMP_Init1_Ack)
+ def Init1_Ack(self, msg):
+ self.init1_acked = True
+ return True
+
+ @msg_handler(0x203, PMP_Init2_Ack)
+ def Init2_Ack(self, msg):
+ self.init2_acked = True
+ return True
+
+ @msg_handler(0x110, PMP_Unk_Ack)
+ def Unk_Ack(self, msg):
+ self.unk_acked = True
+ return True
+
+
+class PMPClient(StandardASC):
+ pass
+
+ ENDPOINTS = {
+ 0x20: PMPEndpoint,
+ }
diff --git a/tools/proxyclient/m1n1/fw/smc.py b/tools/proxyclient/m1n1/fw/smc.py
new file mode 100644
index 0000000..204501c
--- /dev/null
+++ b/tools/proxyclient/m1n1/fw/smc.py
@@ -0,0 +1,202 @@
+# SPDX-License-Identifier: MIT
+import struct
+
+from ..utils import *
+
+from .asc import StandardASC
+from .asc.base import *
+
+SMC_READ_KEY = 0x10
+SMC_WRITE_KEY = 0x11
+SMC_GET_KEY_BY_INDEX = 0x12
+SMC_GET_KEY_INFO = 0x13
+SMC_INITIALIZE = 0x17
+SMC_NOTIFICATION = 0x18
+SMC_RW_KEY = 0x20
+
+class SMCMessage(Register64):
+ TYPE = 7, 0
+ UNK = 11, 8, Constant(0)
+ ID = 15, 12
+
+class SMCInitialize(SMCMessage):
+ TYPE = 7, 0, Constant(SMC_INITIALIZE)
+
+class SMCGetKeyInfo(SMCMessage):
+ TYPE = 7, 0, Constant(SMC_GET_KEY_INFO)
+ KEY = 63, 32
+
+class SMCGetKeyByIndex(SMCMessage):
+ TYPE = 7, 0, Constant(SMC_GET_KEY_BY_INDEX)
+ INDEX = 63, 32
+
+class SMCWriteKey(SMCMessage):
+ TYPE = 7, 0, Constant(SMC_WRITE_KEY)
+ SIZE = 23, 16
+ KEY = 63, 32
+
+class SMCReadKey(SMCMessage):
+ TYPE = 7, 0, Constant(SMC_READ_KEY)
+ SIZE = 23, 16
+ KEY = 63, 32
+
+class SMCReadWriteKey(SMCMessage):
+ TYPE = 7, 0, Constant(SMC_RW_KEY)
+ RSIZE = 23, 16
+ WSIZE = 31, 24
+ KEY = 63, 32
+
+class SMCResult(Register64):
+ RESULT = 7, 0
+ ID = 15, 12
+ SIZE = 31, 16
+ VALUE = 63, 32
+
+class SMCError(Exception):
+ pass
+
+class SMCEndpoint(ASCBaseEndpoint):
+ BASE_MESSAGE = SMCMessage
+ SHORT = "smcep"
+ TYPE_MAP = {
+ "ui64": ("<Q", None),
+ "ui32": ("<I", None),
+ "ui16": ("<H", None),
+ "ui8 ": ("<B", None),
+ "si64": ("<q", None),
+ "si32": ("<i", None),
+ "si16": ("<h", None),
+ "si8 ": ("<b", None),
+ "flag": ("<B", None),
+ "flt ": ("<f", None),
+ "hex_": (None, hexdump),
+ "ch8*": (None, lambda c: c.split(b"\x00")[0]),
+ "ioft": ("<Q", None),
+ }
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.shmem = None
+ self.msgid = 0
+ self.outstanding = set()
+ self.ret = {}
+
+ def start(self):
+ self.send(SMCInitialize(ID = 0))
+ self.msgid += 1 # important!
+ while self.shmem is None:
+ self.asc.work()
+
+ def new_msgid(self):
+ mid = (self.msgid & 0xF)
+ self.msgid += 1
+ assert(mid not in self.outstanding)
+ self.outstanding.add(mid)
+ return mid
+
+ def cmd(self, cmd):
+ cmd.ID = self.new_msgid()
+ self.send(cmd)
+ while cmd.ID in self.outstanding:
+ self.asc.work()
+ ret = self.ret[cmd.ID]
+ if ret.RESULT != 0:
+ raise SMCError(f"SMC error {ret}", ret)
+ return ret
+
+ def write(self, key, data):
+ key = int.from_bytes(key.encode("ascii"), byteorder="big")
+ self.asc.iface.writemem(self.shmem, data)
+ self.cmd(SMCWriteKey(KEY = key, SIZE = len(data)))
+
+ def read(self, key, size):
+ key = int.from_bytes(key.encode("ascii"), byteorder="big")
+ ret = self.cmd(SMCReadKey(KEY = key, SIZE = size))
+ if size <= 4:
+ return struct.pack("<I", ret.VALUE)[:size]
+ else:
+ return self.asc.iface.readmem(self.shmem, ret.SIZE)
+
+ def rw(self, key, data, outsize):
+ key = int.from_bytes(key.encode("ascii"), byteorder="big")
+ self.asc.iface.writemem(self.shmem, data)
+ ret = self.cmd(SMCReadWriteKey(KEY=key, RSIZE=outsize, WSIZE=len(data)))
+ if outsize <= 4:
+ return struct.pack("<I", ret.VALUE)[:outsize]
+ else:
+ return self.asc.iface.readmem(self.shmem, ret.SIZE)
+
+ def get_key_by_index(self, index):
+ ret = self.cmd(SMCGetKeyByIndex(INDEX = index))
+ key = ret.VALUE.to_bytes(4, byteorder="little").decode("ascii")
+ return key
+
+ def get_key_info(self, key):
+ key = int.from_bytes(key.encode("ascii"), byteorder="big")
+ ret = self.cmd(SMCGetKeyInfo(KEY = key))
+ info = self.asc.iface.readmem(self.shmem, 6)
+ length, type, flags = struct.unpack("B4sB", info)
+ return length, type.decode("ascii"), flags
+
+ def read64(self, key):
+ return struct.unpack("<Q", self.read(key, 8))[0]
+
+ def read32(self, key):
+ return struct.unpack("<I", self.read(key, 4))[0]
+
+ def read16(self, key):
+ return struct.unpack("<H", self.read(key, 2))[0]
+
+ def read8(self, key):
+ return struct.unpack("<B", self.read(key, 1))[0]
+
+ def read32b(self, key):
+ return struct.unpack(">I", self.read(key, 4))[0]
+
+ def write64(self, key, data):
+ self.write(key, struct.pack("<Q", data))
+
+ def write32(self, key, data):
+ self.write(key, struct.pack("<I", data))
+
+ def write16(self, key, data):
+ self.write(key, struct.pack("<H", data))
+
+ def write8(self, key, data):
+ self.write(key, struct.pack("<B", data))
+
+ def rw32(self, key, data):
+ return struct.unpack("<I", self.rw(key, struct.pack("<I", data), 4))[0]
+
+ def read_type(self, key, size, typecode):
+ fmt, func = self.TYPE_MAP.get(typecode, (None, None))
+
+ val = self.read(key, size)
+
+ if fmt:
+ val = struct.unpack(fmt, val)[0]
+ if func:
+ val = func(val)
+ return val
+
+ def handle_msg(self, msg0, msg1):
+ if self.shmem is None:
+ self.log("Starting up")
+ self.shmem = msg0
+ else:
+ msg = SMCResult(msg0)
+ ret = msg.RESULT
+ mid = msg.ID
+ if ret == SMC_NOTIFICATION:
+ self.log(f"Notification: {msg.VALUE:#x}")
+ return True
+ #print(f"msg {mid} return value {ret}")
+ self.outstanding.discard(mid)
+ self.ret[mid] = msg
+
+ return True
+
+class SMCClient(StandardASC):
+ ENDPOINTS = {
+ 0x20: SMCEndpoint,
+ }
diff --git a/tools/proxyclient/m1n1/gpiola.py b/tools/proxyclient/m1n1/gpiola.py
new file mode 100644
index 0000000..bbfd71e
--- /dev/null
+++ b/tools/proxyclient/m1n1/gpiola.py
@@ -0,0 +1,254 @@
+# SPDX-License-Identifier: MIT
+import os, sys, struct, time
+
+from .utils import *
+from . import asm
+from .proxy import REGION_RX_EL1
+from .sysreg import *
+
+class GPIOLogicAnalyzer(Reloadable):
+ def __init__(self, u, node=None, pins={}, regs={}, div=1, cpu=1, on_pin_change=True, on_reg_change=True):
+ self.u = u
+ self.p = u.proxy
+ self.iface = u.iface
+ self.cpu = cpu
+ self.base = 0
+ if node is not None:
+ self.base = u.adt[node].get_reg(0)[0]
+ else:
+ on_pin_change=False
+ self.node = node
+ self.pins = pins
+ self.regs = regs
+ assert len(pins) <= 32
+ assert div > 0
+ self.div = div
+ self.cbuf = self.u.malloc(0x1000)
+ self.dbuf = None
+ self.on_pin_change = on_pin_change
+ self.on_reg_change = on_reg_change
+ self.p.mmu_init_secondary(cpu)
+ self.tfreq = u.mrs(CNTFRQ_EL0)
+
+ def load_regmap(self, regmap, skip=set(), regs=set()):
+ base = regmap._base
+ for name, (addr, rcls) in regmap._namemap.items():
+ if name not in skip and (not regs or name in regs):
+ self.regs[name] = base + addr, rcls
+
+ def start(self, ticks, bufsize=0x10000):
+ self.bufsize = bufsize
+ if self.dbuf:
+ self.u.free(self.dbuf)
+ self.dbuf = self.u.malloc(bufsize)
+
+ text = f"""
+ trace:
+ mov x16, x2
+ add x3, x3, x2
+ add x2, x2, #4
+ mov x12, #-8
+ mov x10, x2
+ mov x6, #-1
+ mov x7, #0
+ ldr x8, ={self.base}
+ mrs x4, CNTPCT_EL0
+ isb
+ 1:
+ ldr w15, [x16]
+ cmp w15, #1
+ b.eq done
+ add x4, x4, x1
+ 2:
+ mrs x5, CNTPCT_EL0
+ isb
+ """
+ if self.div > 1:
+ text += f"""
+ cmp x5, x4
+ b.lo 2b
+ """
+
+ for idx, pin in enumerate(self.pins.values()):
+ text += f"""
+ ldr w9, [x8, #{pin * 4}]
+ bfi x7, x9, #{idx}, #1
+ """
+
+ if self.on_pin_change:
+ text += f"""
+ cmp x7, x6
+ b.eq 3f
+ mov x6, x7
+ """
+ if self.on_reg_change:
+ text += f"""
+ mov x11, x2
+ """
+
+ text += f"""
+ str w5, [x2], #4
+ str w7, [x2], #4
+ """
+ if self.on_reg_change:
+ text += f"""
+ mov x13, #0
+ add x14, x12, #8
+ """
+
+ for reg in self.regs.values():
+ if isinstance(reg, tuple):
+ reg = reg[0]
+ text += f"""
+ ldr x9, ={reg}
+ ldr w9, [x9]
+ str w9, [x2], #4
+ """
+ if self.on_reg_change:
+ text += f"""
+ eor w15, w9, #1
+ cmp x14, #0
+ b.eq 4f
+ ldr w15, [x14], #4
+ 4:
+ eor w15, w15, w9
+ orr w13, w13, w15
+ """
+
+ if self.on_reg_change:
+ text += f"""
+ cmp x13, #0
+ b.ne 4f
+ mov x2, x11
+ mov x11, x12
+ b 3f
+ 4:
+ """
+ text += f"""
+ mov x12, x11
+ cmp x2, x3
+ b.hs done
+ 3:
+ sub x0, x0, #1
+ cbnz x0, 1b
+ done:
+ sub x0, x2, x10
+ ret
+ """
+
+ code = asm.ARMAsm(text, self.cbuf)
+ self.iface.writemem(self.cbuf, code.data)
+ self.p.dc_cvau(self.cbuf, len(code.data))
+ self.p.ic_ivau(self.cbuf, len(code.data))
+
+ self.p.write32(self.dbuf, 0)
+
+ self.p.smp_call(self.cpu, code.trace | REGION_RX_EL1, ticks, self.div, self.dbuf, bufsize - (8 + 4 * len(self.regs)))
+
+ def complete(self):
+ self.p.write32(self.dbuf, 1)
+ wrote = self.p.smp_wait(self.cpu)
+ assert wrote <= self.bufsize
+ data = self.iface.readmem(self.dbuf + 4, wrote)
+ self.u.free(self.dbuf)
+ self.dbuf = None
+
+ stride = 2 + len(self.regs)
+
+ #chexdump(data)
+
+ self.data = [struct.unpack("<" + "I" * stride,
+ data[i:i + 4 * stride])
+ for i in range(0, len(data), 4 * stride)]
+
+ def vcd(self):
+ off = self.data[0][0]
+ if False: #len(self.data) > 1:
+ off2 = max(0, ((self.data[1][0] - off) & 0xffffffff) - 5000)
+ else:
+ off2 = 0
+
+ #print(off, off2)
+
+ vcd = []
+ vcd.append("""
+$timescale 1ns $end
+$scope module gpio $end
+""")
+ sym = 0
+ keys = []
+ rkeys = []
+
+ for name in self.pins:
+ keys.append(f"s{sym}")
+ vcd.append(f"$var wire 1 s{sym} {name} $end\n")
+ sym += 1
+ for name, reg in self.regs.items():
+ vcd.append(f"$var reg 32 s{sym} {name} [31:0] $end\n")
+ if isinstance(reg, tuple):
+ subkeys = {}
+ rcls = reg[1]
+ rkeys.append((f"s{sym}", rcls, subkeys))
+ sym += 1
+ for fname in rcls().fields.keys():
+ fdef = getattr(rcls, fname)
+ if isinstance(fdef, tuple):
+ width = fdef[0] - fdef[1] + 1
+ else:
+ width = 1
+ vcd.append(f"$var reg {width} s{sym} {name}.{fname} [{width-1}:0] $end\n")
+ subkeys[fname] = (width, f"s{sym}")
+ sym += 1
+ else:
+ rkeys.append((f"s{sym}", None, None))
+ sym += 1
+ vcd.append("""
+$enddefinitions $end
+$dumpvars
+""")
+
+ for v in self.data:
+ ts = v[0]
+ val = v[1]
+ regs = v[2:]
+ ts = ((ts - off) & 0xffffffff) - off2
+ ns = max(0, 1000000000 * ts // self.tfreq)
+ vcd.append(f"#{ns}\n")
+ vcd.append("\n".join(f"{(val>>i) & 1}{k}" for i, k in enumerate(keys)) + "\n")
+ for (key, rcls, subkeys), v in zip(rkeys, regs):
+ vcd.append(f"b{v:032b} {key}\n")
+ if rcls:
+ rval = rcls(v)
+ for field, (width, key) in subkeys.items():
+ v = getattr(rval, field)
+ vcd.append(f"b{v:0{width}b} {key}\n")
+
+
+ ns += ns//10
+ vcd.append(f"#{ns}\n" + "\n".join(f"{(val>>i) & 1}{k}" for i, k in enumerate(keys)) + "\n")
+
+ return "".join(vcd)
+
+ def show(self):
+ with open("/tmp/dump.vcd", "w") as fd:
+ fd.write(self.vcd())
+
+ gtkw = ("""
+[dumpfile] "/tmp/dump.vcd"
+[timestart] 0
+[size] 3063 1418
+[pos] -1 -1
+*-17.000000 2 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
+[sst_width] 288
+[signals_width] 197
+[sst_expanded] 1
+[sst_vpaned_height] 421
+@23
+""" +
+ "\n".join("gpio." + k for k in self.pins) + "\n" +
+ "\n".join("gpio." + k + "[31:0]" for k in self.regs) + "\n")
+
+ with open("/tmp/dump.gtkw", "w") as fd:
+ fd.write(gtkw)
+
+ os.system("gtkwave /tmp/dump.gtkw&")
diff --git a/tools/proxyclient/m1n1/hostutils.py b/tools/proxyclient/m1n1/hostutils.py
new file mode 100644
index 0000000..535240b
--- /dev/null
+++ b/tools/proxyclient/m1n1/hostutils.py
@@ -0,0 +1,96 @@
+# SPDX-License-Identifier: MIT
+from pathlib import Path
+import os
+
+class KernelRegmapAccessor:
+ def __init__(self, name):
+ self.path = self._find_path(name)
+ self.read_ranges()
+ self.read_linelen()
+
+ @classmethod
+ def _find_path(cls, name):
+ basedir = Path("/sys/kernel/debug/regmap")
+
+ if (path := Path(name)).exists():
+ return path
+ elif (path := basedir.joinpath(name)).exists():
+ return path
+ elif name in (available := cls._list_regmaps(basedir)):
+ return available[name]
+ else:
+ raise ValueError(f"kernel regmap not found: {name}")
+
+ @classmethod
+ def _list_regmaps(cls, basedir):
+ return {
+ p.joinpath("name").open("rb").read().strip().decode(): p
+ for p in basedir.iterdir() if p.is_dir()
+ }
+
+ def open_node(self, name, mode="rb", **kwargs):
+ return self.path.joinpath(name).open(mode, **kwargs)
+
+ def read_ranges(self):
+ with self.open_node("range") as f:
+ self.ranges = [
+ range(int(a, 16), int(b, 16) + 1)
+ for a, b in (l.strip().split(b"-") for l in f)
+ ]
+
+ def read_linelen(self):
+ with self.open_node("registers", buffering=0) as f:
+ l = f.read(64).split(b"\n")[0]
+ valstr = l.split(b":")[1].strip()
+ self.linelen = len(l) + 1
+ self.working_width = len(valstr) * 4
+
+ def _find_off(self, reg):
+ off = 0
+ for r in self.ranges:
+ if reg >= r.stop:
+ off += r.stop - r.start
+ else:
+ off += reg - r.start
+ break
+ if reg not in r:
+ raise ValueError(f"register {reg:04x} out of range")
+ return off * self.linelen
+
+ def _read(self, reg, width=None):
+ assert width == self.working_width
+ with self.open_node("registers", buffering=0) as f:
+ f.seek(self._find_off(reg))
+ l = f.read(self.linelen)
+ regstr, valstr = l.split(b":")
+ assert int(regstr, 16) == reg
+ return int(valstr, 16)
+
+ def read(self, reg, width=None):
+ assert width % self.working_width == 0
+ ret = 0
+ for off in range(0, width // 8, self.working_width // 8):
+ ret |= self._read(reg + off, self.working_width) << (8 * off)
+ return ret
+
+ def _write(self, reg, val, width=None):
+ assert width == self.working_width
+ with self.open_node("registers", mode="wb") as f:
+ f.write(f"{reg:x} {val:x}".encode())
+
+ def write(self, reg, val, width=None):
+ assert width % self.working_width == 0
+ for off in range(0, width // 8, self.working_width // 8):
+ self._write(reg + off, val >> (8 * off), self.working_width)
+
+def require_debugfs():
+ if os.path.ismount("/sys/kernel/debug"):
+ return
+ os.system("mount -t debugfs none /sys/kernel/debug")
+
+if __name__ == "__main__":
+ require_debugfs()
+ from m1n1.hw.codecs import TAS5770Regs
+ tas = TAS5770Regs(KernelRegmapAccessor("tas2770"), 0)
+ import code
+ code.interact(local=locals())
diff --git a/tools/proxyclient/m1n1/hv/__init__.py b/tools/proxyclient/m1n1/hv/__init__.py
new file mode 100644
index 0000000..407b0d1
--- /dev/null
+++ b/tools/proxyclient/m1n1/hv/__init__.py
@@ -0,0 +1,1849 @@
+# SPDX-License-Identifier: MIT
+import io, sys, traceback, struct, array, bisect, os, plistlib, signal, runpy
+from construct import *
+
+from ..asm import ARMAsm
+from ..tgtypes import *
+from ..proxy import IODEV, START, EVENT, EXC, EXC_RET, ExcInfo
+from ..utils import *
+from ..sysreg import *
+from ..macho import MachO
+from ..adt import load_adt
+from .. import xnutools, shell
+
+from .gdbserver import *
+from .types import *
+from .virtutils import *
+from .virtio import *
+
+__all__ = ["HV"]
+
+class HV(Reloadable):
+ PAC_MASK = 0xfffff00000000000
+
+ PTE_VALID = 1 << 0
+
+ PTE_MEMATTR_UNCHANGED = 0b1111 << 2
+ PTE_S2AP_RW = 0b11 << 6
+ PTE_SH_NS = 0b11 << 8
+ PTE_ACCESS = 1 << 10
+ PTE_ATTRIBUTES = PTE_ACCESS | PTE_SH_NS | PTE_S2AP_RW | PTE_MEMATTR_UNCHANGED
+
+ SPTE_TRACE_READ = 1 << 63
+ SPTE_TRACE_WRITE = 1 << 62
+ SPTE_TRACE_UNBUF = 1 << 61
+ SPTE_MAP = 0 << 50
+ SPTE_HOOK = 1 << 50
+ SPTE_PROXY_HOOK_R = 2 << 50
+ SPTE_PROXY_HOOK_W = 3 << 50
+ SPTE_PROXY_HOOK_RW = 4 << 50
+
+ MSR_REDIRECTS = {
+ SCTLR_EL1: SCTLR_EL12,
+ TTBR0_EL1: TTBR0_EL12,
+ TTBR1_EL1: TTBR1_EL12,
+ TCR_EL1: TCR_EL12,
+ ESR_EL1: ESR_EL12,
+ FAR_EL1: FAR_EL12,
+ AFSR0_EL1: AFSR0_EL12,
+ AFSR1_EL1: AFSR1_EL12,
+ MAIR_EL1: MAIR_EL12,
+ AMAIR_EL1: AMAIR_EL12,
+ CONTEXTIDR_EL1: CONTEXTIDR_EL12,
+ ACTLR_EL1: ACTLR_EL12,
+ AMX_CTL_EL1: AMX_CTL_EL12,
+ SPRR_CONFIG_EL1: SPRR_CONFIG_EL12,
+ SPRR_PERM_EL1: SPRR_PERM_EL12,
+ SPRR_PERM_EL0: SPRR_PERM_EL02,
+ SPRR_UNK1_EL1: SPRR_UNK1_EL12,
+ SPRR_UMASK0_EL1: SPRR_UMASK0_EL12,
+ APCTL_EL1: APCTL_EL12,
+ APSTS_EL1: APSTS_EL12,
+ KERNELKEYLO_EL1: KERNELKEYLO_EL12,
+ KERNELKEYHI_EL1: KERNELKEYHI_EL12,
+ GXF_CONFIG_EL1: GXF_CONFIG_EL12,
+ GXF_ABORT_EL1: GXF_ABORT_EL12,
+ GXF_ENTER_EL1: GXF_ENTER_EL12,
+ VBAR_GL1: VBAR_GL12,
+ SPSR_GL1: SPSR_GL12,
+ ASPSR_GL1: ASPSR_GL12,
+ ESR_GL1: ESR_GL12,
+ ELR_GL1: ELR_GL12,
+ }
+
+ AIC_EVT_TYPE_HW = 1
+ IRQTRACE_IRQ = 1
+
+ def __init__(self, iface, proxy, utils):
+ self.iface = iface
+ self.p = proxy
+ self.u = utils
+ self.pac_mask = self.PAC_MASK
+ self.user_pac_mask = self.PAC_MASK
+ self.vbar_el1 = None
+ self.want_vbar = None
+ self.vectors = [None]
+ self._bps = [None, None, None, None, None]
+ self._bp_hooks = dict()
+ self._wps = [None, None, None, None]
+ self._wpcs = [0, 0, 0, 0]
+ self.sym_offset = 0
+ self.symbols = []
+ self.symbol_dict = {}
+ self.sysreg = {0: {}}
+ self.novm = False
+ self._in_handler = False
+ self._sigint_pending = False
+ self._in_shell = False
+ self._gdbserver = None
+ self.vm_hooks = [None]
+ self.interrupt_map = {}
+ self.mmio_maps = DictRangeMap()
+ self.dirty_maps = BoolRangeMap()
+ self.tracer_caches = {}
+ self.shell_locals = {}
+ self.xnu_mode = False
+ self._update_shell_locals()
+ self.wdt_cpu = None
+ self.smp = True
+ self.hook_exceptions = False
+ self.started_cpus = set()
+ self.started = False
+ self.ctx = None
+ self.hvcall_handlers = {}
+ self.switching_context = False
+ self.show_timestamps = False
+ self.virtio_devs = {}
+
+ def _reloadme(self):
+ super()._reloadme()
+ self._update_shell_locals()
+
+ def _update_shell_locals(self):
+ self.shell_locals.update({
+ "hv": self,
+ "iface": self.iface,
+ "p": self.p,
+ "u": self.u,
+ "trace": trace,
+ "TraceMode": TraceMode,
+ })
+
+ for attr in dir(self):
+ a = getattr(self, attr)
+ if callable(a):
+ self.shell_locals[attr] = getattr(self, attr)
+
+ self.shell_locals["ctx"] = self.context
+
+ def log(self, s, *args, show_cpu=True, **kwargs):
+ if self.ctx is not None and show_cpu:
+ ts=""
+ if self.show_timestamps:
+ ts = f"[{self.u.mrs(CNTPCT_EL0):#x}]"
+ print(ts+f"[cpu{self.ctx.cpu_id}] " + s, *args, **kwargs)
+ if self.print_tracer.log_file:
+ print(f"# {ts}[cpu{self.ctx.cpu_id}] " + s, *args, file=self.print_tracer.log_file, **kwargs)
+ else:
+ print(s, *args, **kwargs)
+ if self.print_tracer.log_file:
+ print("# " + s, *args, file=self.print_tracer.log_file, **kwargs)
+
+ def unmap(self, ipa, size):
+ assert self.p.hv_map(ipa, 0, size, 0) >= 0
+
+ def map_hw(self, ipa, pa, size):
+ '''map IPA (Intermediate Physical Address) to actual PA'''
+ #print(f"map_hw {ipa:#x} -> {pa:#x} [{size:#x}]")
+ if (ipa & 0x3fff) != (pa & 0x3fff):
+ self.map_sw(ipa, pa, size)
+ return
+
+ ipa_p = align_up(ipa)
+ if ipa_p != ipa:
+ self.map_sw(ipa, pa, min(ipa_p - ipa, size))
+ pa += ipa_p - ipa
+ size -= ipa_p - ipa
+
+ if size <= 0:
+ return
+
+ size_p = align_down(size)
+ if size_p > 0:
+ #print(f"map_hw real {ipa_p:#x} -> {pa:#x} [{size_p:#x}]")
+ assert self.p.hv_map(ipa_p, pa | self.PTE_ATTRIBUTES | self.PTE_VALID, size_p, 1) >= 0
+
+ if size_p != size:
+ self.map_sw(ipa_p + size_p, pa + size_p, size - size_p)
+
+ def map_sw(self, ipa, pa, size):
+ #print(f"map_sw {ipa:#x} -> {pa:#x} [{size:#x}]")
+ assert self.p.hv_map(ipa, pa | self.SPTE_MAP, size, 1) >= 0
+
+ def map_hook(self, ipa, size, read=None, write=None, **kwargs):
+ index = len(self.vm_hooks)
+ self.vm_hooks.append((read, write, ipa, kwargs))
+ self.map_hook_idx(ipa, size, index, read is not None, write is not None)
+
+ def map_hook_idx(self, ipa, size, index, read=False, write=False, flags=0):
+ if read:
+ if write:
+ t = self.SPTE_PROXY_HOOK_RW
+ else:
+ t = self.SPTE_PROXY_HOOK_R
+ elif write:
+ t = self.SPTE_PROXY_HOOK_W
+ else:
+ assert False
+
+ assert self.p.hv_map(ipa, (index << 2) | flags | t, size, 0) >= 0
+
+ def readmem(self, va, size):
+ '''read from virtual memory'''
+ with io.BytesIO() as buffer:
+ while size > 0:
+ pa = self.p.hv_translate(va, False, False)
+ if pa == 0:
+ break
+
+ size_in_page = 4096 - (va % 4096)
+ if size < size_in_page:
+ buffer.write(self.iface.readmem(pa, size))
+ break
+
+ buffer.write(self.iface.readmem(pa, size_in_page))
+ va += size_in_page
+ size -= size_in_page
+
+ return buffer.getvalue()
+
+ def writemem(self, va, data):
+ '''write to virtual memory'''
+ written = 0
+ while written < len(data):
+ pa = self.p.hv_translate(va, False, True)
+ if pa == 0:
+ break
+
+ size_in_page = 4096 - (va % 4096)
+ if len(data) - written < size_in_page:
+ self.iface.writemem(pa, data[written:])
+ written = len(data)
+ break
+
+ self.iface.writemem(pa, data[written:written + size_in_page])
+ va += size_in_page
+ written += size_in_page
+
+ return written
+
+ def trace_irq(self, device, num, count, flags):
+ for n in range(num, num + count):
+ if flags & self.IRQTRACE_IRQ:
+ self.interrupt_map[n] = device
+ else:
+ self.interrupt_map.pop(n, None)
+
+ start, size = self.adt["/arm-io/aic"].get_reg(0)
+ zone = irange(start, size)
+ if len(self.interrupt_map):
+ self.add_tracer(zone, "AIC_IRQ", TraceMode.RESERVED)
+ else:
+ self.del_tracer(zone, "AIC_IRQ")
+
+ assert self.p.hv_trace_irq(self.AIC_EVT_TYPE_HW, num, count, flags) > 0
+
+ def add_tracer(self, zone, ident, mode=TraceMode.ASYNC, read=None, write=None, **kwargs):
+ assert mode in (TraceMode.RESERVED, TraceMode.OFF, TraceMode.BYPASS) or read or write
+ self.mmio_maps[zone, ident] = (mode, ident, read, write, kwargs)
+ self.dirty_maps.set(zone)
+
+ def del_tracer(self, zone, ident):
+ del self.mmio_maps[zone, ident]
+ self.dirty_maps.set(zone)
+
+ def clear_tracers(self, ident):
+ for r, v in self.mmio_maps.items():
+ if ident in v:
+ v.pop(ident)
+ self.dirty_maps.set(r)
+
+ def trace_device(self, path, mode=TraceMode.ASYNC, ranges=None):
+ node = self.adt[path]
+ for index in range(len(node.reg)):
+ if ranges is not None and index not in ranges:
+ continue
+ addr, size = node.get_reg(index)
+ self.trace_range(irange(addr, size), mode)
+
+ def trace_range(self, zone, mode=TraceMode.ASYNC, read=True, write=True, name=None):
+ if mode is True:
+ mode = TraceMode.ASYNC
+ if mode and mode != TraceMode.OFF:
+ self.add_tracer(zone, "PrintTracer", mode,
+ self.print_tracer.event_mmio if read else None,
+ self.print_tracer.event_mmio if write else None,
+ start=zone.start,
+ name=name)
+ else:
+ self.del_tracer(zone, "PrintTracer")
+
+ def pt_update(self):
+ if not self.dirty_maps:
+ return
+
+ self.dirty_maps.compact()
+ self.mmio_maps.compact()
+
+ top = 0
+
+ for zone in self.dirty_maps:
+ if zone.stop <= top:
+ continue
+ top = max(top, zone.start)
+
+ for mzone, maps in self.mmio_maps.overlaps(zone):
+ if mzone.stop <= top:
+ continue
+ if top < mzone.start:
+ self.unmap(top, mzone.start - top)
+ self.log(f"PT[{top:09x}:{mzone.start:09x}] -> *UNMAPPED*")
+
+ top = mzone.stop
+ if not maps:
+ continue
+ maps = sorted(maps.values(), reverse=True)
+ mode, ident, read, write, kwargs = maps[0]
+
+ need_read = any(m[2] for m in maps)
+ need_write = any(m[3] for m in maps)
+
+ if mode == TraceMode.RESERVED:
+ self.log(f"PT[{mzone.start:09x}:{mzone.stop:09x}] -> RESERVED {ident}")
+ continue
+ elif mode in (TraceMode.HOOK, TraceMode.SYNC):
+ self.map_hook_idx(mzone.start, mzone.stop - mzone.start, 0,
+ need_read, need_write)
+ if mode == TraceMode.HOOK:
+ for m2, i2, r2, w2, k2 in maps[1:]:
+ if m2 == TraceMode.HOOK:
+ self.log(f"!! Conflict: HOOK {i2}")
+ elif mode == TraceMode.WSYNC:
+ flags = self.SPTE_TRACE_READ if need_read else 0
+ self.map_hook_idx(mzone.start, mzone.stop - mzone.start, 0,
+ False, need_write, flags=flags)
+ elif mode in (TraceMode.UNBUF, TraceMode.ASYNC, TraceMode.BYPASS):
+ pa = mzone.start
+ if mode == TraceMode.UNBUF:
+ pa |= self.SPTE_TRACE_UNBUF
+ if need_read:
+ pa |= self.SPTE_TRACE_READ
+ if need_write:
+ pa |= self.SPTE_TRACE_WRITE
+ self.map_sw(mzone.start, pa, mzone.stop - mzone.start)
+ elif mode == TraceMode.OFF:
+ self.map_hw(mzone.start, mzone.start, mzone.stop - mzone.start)
+ self.log(f"PT[{mzone.start:09x}:{mzone.stop:09x}] -> HW:{ident}")
+ continue
+
+ rest = [m[1] for m in maps[1:] if m[0] != TraceMode.OFF]
+ if rest:
+ rest = " (+ " + ", ".join(rest) + ")"
+ else:
+ rest = ""
+
+ self.log(f"PT[{mzone.start:09x}:{mzone.stop:09x}] -> {mode.name}.{'R' if read else ''}{'W' if read else ''} {ident}{rest}")
+
+ if top < zone.stop:
+ self.unmap(top, zone.stop - top)
+ self.log(f"PT[{top:09x}:{zone.stop:09x}] -> *UNMAPPED*")
+
+ self.u.inst(0xd50c83df) # tlbi vmalls12e1is
+ self.dirty_maps.clear()
+
+ def shellwrap(self, func, description, update=None, needs_ret=False):
+
+ while True:
+ try:
+ return func()
+ except Exception:
+ print(f"Exception in {description}")
+ traceback.print_exc()
+
+ if not self.ctx:
+ print("Running in asynchronous context. Target operations are not available.")
+
+ def do_exit(i):
+ raise shell.ExitConsole(i)
+
+ self.shell_locals["skip"] = lambda: do_exit(1)
+ self.shell_locals["cont"] = lambda: do_exit(0)
+ ret = self.run_shell("Entering debug shell", "Returning to tracer")
+ self.shell_locals["skip"] = self.skip
+ self.shell_locals["cont"] = self.cont
+
+ if self.ctx:
+ self.cpu() # Return to the original CPU to avoid confusing things
+
+ if ret == 1:
+ if needs_ret:
+ print("Cannot skip, return value required.")
+ else:
+ return
+
+ if update:
+ update()
+
+ def run_shell(self, entry_msg="Entering shell", exit_msg="Continuing"):
+ def handle_sigusr1(signal, stack):
+ raise shell.ExitConsole(EXC_RET.HANDLED)
+
+ def handle_sigusr2(signal, stack):
+ raise shell.ExitConsole(EXC_RET.EXIT_GUEST)
+
+ default_sigusr1 = signal.signal(signal.SIGUSR1, handle_sigusr1)
+ try:
+ default_sigusr2 = signal.signal(signal.SIGUSR2, handle_sigusr2)
+ try:
+ self._in_shell = True
+ try:
+ if not self._gdbserver is None:
+ self._gdbserver.notify_in_shell()
+ return shell.run_shell(self.shell_locals, entry_msg, exit_msg)
+ finally:
+ self._in_shell = False
+ finally:
+ signal.signal(signal.SIGUSR2, default_sigusr2)
+ finally:
+ signal.signal(signal.SIGUSR1, default_sigusr1)
+
+ @property
+ def in_shell(self):
+ return self._in_shell
+
+ def gdbserver(self, address="/tmp/.m1n1-unix", log=None):
+ '''activate gdbserver'''
+ if not self._gdbserver is None:
+ raise Exception("gdbserver is already running")
+
+ self._gdbserver = GDBServer(self, address, log)
+ self._gdbserver.activate()
+
+ def shutdown_gdbserver(self):
+ '''shutdown gdbserver'''
+ self._gdbserver.shutdown()
+ self._gdbserver = None
+
+ def handle_mmiotrace(self, data):
+ evt = EvtMMIOTrace.parse(data)
+
+ def do_update():
+ nonlocal mode, ident, read, write, kwargs
+ read = lambda *args, **kwargs: None
+ write = lambda *args, **kwargs: None
+
+ m = self.mmio_maps[evt.addr].get(ident, None)
+ if not m:
+ return
+
+ mode, ident, read_, write_, kwargs = m
+ read = read_ or read
+ write = write_ or write
+
+ maps = sorted(self.mmio_maps[evt.addr].values(), reverse=True)
+ for mode, ident, read, write, kwargs in maps:
+ if mode > TraceMode.WSYNC or (evt.flags.WRITE and mode > TraceMode.UNBUF):
+ print(f"ERROR: mmiotrace event but expected {mode.name} mapping")
+ continue
+ if mode == TraceMode.OFF:
+ continue
+ if evt.flags.WRITE:
+ if write:
+ self.shellwrap(lambda: write(evt, **kwargs),
+ f"Tracer {ident}:write ({mode.name})", update=do_update)
+ else:
+ if read:
+ self.shellwrap(lambda: read(evt, **kwargs),
+ f"Tracer {ident}:read ({mode.name})", update=do_update)
+
+ def handle_vm_hook_mapped(self, ctx, data):
+ maps = sorted(self.mmio_maps[data.addr].values(), reverse=True)
+
+ if not maps:
+ raise Exception(f"VM hook without a mapping at {data.addr:#x}")
+
+ def do_update():
+ nonlocal mode, ident, read, write, kwargs
+ read = lambda *args, **kwargs: None
+ write = lambda *args, **kwargs: None
+
+ m = self.mmio_maps[data.addr].get(ident, None)
+ if not m:
+ return
+
+ mode, ident, read_, write_, kwargs = m
+ read = read_ or read
+ write = write_ or write
+
+ mode, ident, read, write, kwargs = maps[0]
+
+ first = 0
+
+ val = data.data
+
+ if mode not in (TraceMode.HOOK, TraceMode.SYNC, TraceMode.WSYNC):
+ raise Exception(f"VM hook with unexpected mapping at {data.addr:#x}: {maps[0][0].name}")
+
+ if not data.flags.WRITE:
+ if mode == TraceMode.HOOK:
+ val = self.shellwrap(lambda: read(data.addr, 8 << data.flags.WIDTH, **kwargs),
+ f"Tracer {ident}:read (HOOK)", update=do_update, needs_ret=True)
+
+ if not isinstance(val, list) and not isinstance(val, tuple):
+ val = [val]
+ first += 1
+ elif mode == TraceMode.SYNC:
+ try:
+ val = self.u.read(data.addr, 8 << data.flags.WIDTH)
+ except:
+ self.log(f"MMIO read failed: {data.addr:#x} (w={data.flags.WIDTH})")
+ raise
+ if not isinstance(val, list) and not isinstance(val, tuple):
+ val = [val]
+ elif mode == TraceMode.WSYNC:
+ raise Exception(f"VM hook with unexpected mapping at {data.addr:#x}: {maps[0][0].name}")
+
+ for i in range(1 << max(0, data.flags.WIDTH - 3)):
+ self.p.write64(ctx.data + 16 + 8 * i, val[i])
+
+ elif mode == TraceMode.HOOK:
+ first += 1
+
+ flags = data.flags.copy()
+ flags.CPU = self.ctx.cpu_id
+ width = data.flags.WIDTH
+
+ if width > 3:
+ flags.WIDTH = 3
+ flags.MULTI = 1
+
+ for i in range(1 << max(0, width - 3)):
+ evt = Container(
+ flags = flags,
+ reserved = 0,
+ pc = ctx.elr,
+ addr = data.addr + 8 * i,
+ data = val[i]
+ )
+
+ for mode, ident, read, write, kwargs in maps[first:]:
+ if flags.WRITE:
+ if write:
+ self.shellwrap(lambda: write(evt, **kwargs),
+ f"Tracer {ident}:write ({mode.name})", update=do_update)
+ else:
+ if read:
+ self.shellwrap(lambda: read(evt, **kwargs),
+ f"Tracer {ident}:read ({mode.name})", update=do_update)
+
+ if data.flags.WRITE:
+ mode, ident, read, write, kwargs = maps[0]
+
+ if data.flags.WIDTH <= 3:
+ wval = val[0]
+ else:
+ wval = val
+
+ if mode == TraceMode.HOOK:
+ self.shellwrap(lambda: write(data.addr, wval, 8 << data.flags.WIDTH, **kwargs),
+ f"Tracer {ident}:write (HOOK)", update=do_update)
+ elif mode in (TraceMode.SYNC, TraceMode.WSYNC):
+ try:
+ self.u.write(data.addr, wval, 8 << data.flags.WIDTH)
+ except:
+ if data.flags.WIDTH > 3:
+ wval = wval[0]
+ self.log(f"MMIO write failed: {data.addr:#x} = {wval} (w={data.flags.WIDTH})")
+ raise
+
+ return True
+
+ def handle_vm_hook(self, ctx):
+ data = self.iface.readstruct(ctx.data, VMProxyHookData)
+
+ if data.id == 0:
+ return self.handle_vm_hook_mapped(ctx, data)
+
+ rfunc, wfunc, base, kwargs = self.vm_hooks[data.id]
+
+ d = data.data
+ if data.flags.WIDTH < 3:
+ d = d[0]
+
+ if data.flags.WRITE:
+ wfunc(base, data.addr - base, d, 8 << data.flags.WIDTH, **kwargs)
+ else:
+ val = rfunc(base, data.addr - base, 8 << data.flags.WIDTH, **kwargs)
+ if not isinstance(val, list) and not isinstance(val, tuple):
+ val = [val]
+ for i in range(1 << max(0, data.flags.WIDTH - 3)):
+ self.p.write64(ctx.data + 16 + 8 * i, val[i])
+
+ return True
+
+ def handle_irqtrace(self, data):
+ evt = EvtIRQTrace.parse(data)
+
+ if evt.type == self.AIC_EVT_TYPE_HW and evt.flags & self.IRQTRACE_IRQ:
+ dev = self.interrupt_map[int(evt.num)]
+ print(f"IRQ: {dev}: {evt.num}")
+
+ def addr(self, addr):
+ unslid_addr = addr + self.sym_offset
+ if self.xnu_mode and (addr < self.tba.virt_base or unslid_addr < self.macho.vmin):
+ return f"0x{addr:x}"
+
+ saddr, name = self.sym(addr)
+
+ if name is None:
+ return f"0x{addr:x} (0x{unslid_addr:x})"
+
+ return f"0x{addr:x} ({name}+0x{unslid_addr - saddr:x})"
+
+ def resolve_symbol(self, name):
+ return self.symbol_dict[name] - self.sym_offset
+
+ def sym(self, addr):
+ unslid_addr = addr + self.sym_offset
+
+ if self.xnu_mode and (addr < self.tba.virt_base or unslid_addr < self.macho.vmin):
+ return None, None
+
+ idx = bisect.bisect_left(self.symbols, (unslid_addr + 1, "")) - 1
+ if idx < 0 or idx >= len(self.symbols):
+ return None, None
+
+ return self.symbols[idx]
+
+ def get_sym(self, addr):
+ a, name = self.sym(addr)
+ if addr == a:
+ return name
+ else:
+ return None
+
+ def handle_msr(self, ctx, iss=None):
+ if iss is None:
+ iss = ctx.esr.ISS
+ iss = ESR_ISS_MSR(iss)
+ enc = iss.Op0, iss.Op1, iss.CRn, iss.CRm, iss.Op2
+
+ name = sysreg_name(enc)
+
+ skip = set()
+ shadow = {
+ #SPRR_CONFIG_EL1,
+ #SPRR_PERM_EL0,
+ #SPRR_PERM_EL1,
+ VMSA_LOCK_EL1,
+ #SPRR_UNK1_EL1,
+ #SPRR_UNK2_EL1,
+ MDSCR_EL1,
+ }
+ ro = {
+ ACC_CFG_EL1,
+ ACC_OVRD_EL1,
+ }
+ xlate = {
+ DC_CIVAC,
+ }
+ for i in range(len(self._bps)):
+ shadow.add(DBGBCRn_EL1(i))
+ shadow.add(DBGBVRn_EL1(i))
+ for i in range(len(self._wps)):
+ shadow.add(DBGWCRn_EL1(i))
+ shadow.add(DBGWVRn_EL1(i))
+
+ value = 0
+ if enc in shadow:
+ if iss.DIR == MSR_DIR.READ:
+ value = self.sysreg[self.ctx.cpu_id].setdefault(enc, 0)
+ self.log(f"Shadow: mrs x{iss.Rt}, {name} = {value:x}")
+ if iss.Rt != 31:
+ ctx.regs[iss.Rt] = value
+ else:
+ if iss.Rt != 31:
+ value = ctx.regs[iss.Rt]
+ self.log(f"Shadow: msr {name}, x{iss.Rt} = {value:x}")
+ self.sysreg[self.ctx.cpu_id][enc] = value
+ elif enc in skip or (enc in ro and iss.DIR == MSR_DIR.WRITE):
+ if iss.DIR == MSR_DIR.READ:
+ self.log(f"Skip: mrs x{iss.Rt}, {name} = 0")
+ if iss.Rt != 31:
+ ctx.regs[iss.Rt] = 0
+ else:
+ if iss.Rt != 31:
+ value = ctx.regs[iss.Rt]
+ self.log(f"Skip: msr {name}, x{iss.Rt} = {value:x}")
+ else:
+ if iss.DIR == MSR_DIR.READ:
+ enc2 = self.MSR_REDIRECTS.get(enc, enc)
+ value = self.u.mrs(enc2)
+ self.log(f"Pass: mrs x{iss.Rt}, {name} = {value:x} ({sysreg_name(enc2)})")
+ if iss.Rt != 31:
+ ctx.regs[iss.Rt] = value
+ else:
+ if iss.Rt != 31:
+ value = ctx.regs[iss.Rt]
+ enc2 = self.MSR_REDIRECTS.get(enc, enc)
+ sys.stdout.flush()
+ if enc in xlate:
+ value = self.p.hv_translate(value, True, False)
+ self.u.msr(enc2, value, call=self.p.gl2_call)
+ self.log(f"Pass: msr {name}, x{iss.Rt} = {value:x} (OK) ({sysreg_name(enc2)})")
+
+ ctx.elr += 4
+
+ if self.hook_exceptions:
+ self.patch_exception_handling()
+
+ return True
+
+ def handle_impdef(self, ctx):
+ if ctx.esr.ISS == 0x20:
+ return self.handle_msr(ctx, ctx.afsr1)
+
+ code = struct.unpack("<I", self.iface.readmem(ctx.elr_phys, 4))
+ c = ARMAsm(".inst " + ",".join(str(i) for i in code), ctx.elr_phys)
+ insn = "; ".join(c.disassemble())
+
+ self.log(f"IMPDEF exception on: {insn}")
+
+ return False
+
+ def handle_hvc(self, ctx):
+ idx = ctx.esr.ISS
+ if idx == 0:
+ return False
+
+ vector, target = self.vectors[idx]
+ if target is None:
+ self.log(f"EL1: Exception #{vector} with no target")
+ target = 0
+ ok = False
+ else:
+ ctx.elr = target
+ ctx.elr_phys = self.p.hv_translate(target, False, False)
+ ok = True
+
+ if (vector & 3) == EXC.SYNC:
+ spsr = SPSR(self.u.mrs(SPSR_EL12))
+ esr = ESR(self.u.mrs(ESR_EL12))
+ elr = self.u.mrs(ELR_EL12)
+ elr_phys = self.p.hv_translate(elr, False, False)
+ sp_el1 = self.u.mrs(SP_EL1)
+ sp_el0 = self.u.mrs(SP_EL0)
+ far = None
+ if esr.EC == ESR_EC.DABORT or esr.EC == ESR_EC.IABORT:
+ far = self.u.mrs(FAR_EL12)
+ if self.sym(elr)[1] != "com.apple.kernel:_panic_trap_to_debugger":
+ self.log("Page fault")
+ return ok
+
+ self.log(f"EL1: Exception #{vector} ({esr.EC!s}) to {self.addr(target)} from {spsr.M.name}")
+ self.log(f" ELR={self.addr(elr)} (0x{elr_phys:x})")
+ self.log(f" SP_EL1=0x{sp_el1:x} SP_EL0=0x{sp_el0:x}")
+ if far is not None:
+ self.log(f" FAR={self.addr(far)}")
+ if elr_phys:
+ self.u.disassemble_at(elr_phys - 4 * 4, 9 * 4, elr - 4 * 4, elr, sym=self.get_sym)
+ if self.sym(elr)[1] == "com.apple.kernel:_panic_trap_to_debugger":
+ self.log("Panic! Trying to decode panic...")
+ try:
+ self.decode_panic_call()
+ except:
+ self.log("Error decoding panic.")
+ try:
+ self.bt()
+ except:
+ pass
+ return False
+ if esr.EC == ESR_EC.UNKNOWN:
+ instr = self.p.read32(elr_phys)
+ if instr == 0xe7ffdeff:
+ self.log("Debugger break! Trying to decode panic...")
+ try:
+ self.decode_dbg_panic()
+ except:
+ self.log("Error decoding panic.")
+ try:
+ self.bt()
+ except:
+ pass
+ return False
+ return False
+ else:
+ elr = self.u.mrs(ELR_EL12)
+ self.log(f"Guest: {str(EXC(vector & 3))} at {self.addr(elr)}")
+
+ return ok
+
+ def handle_step(self, ctx):
+ # not sure why MDSCR_EL1.SS needs to be disabled here but otherwise
+ # if also SPSR.SS=0 no instruction will be executed after eret
+ # and instead a debug exception is generated again
+ self.u.msr(MDSCR_EL1, MDSCR(MDE=1).value)
+
+ # enable all breakpoints again
+ for i, vaddr in enumerate(self._bps):
+ if vaddr is None:
+ continue
+ self.u.msr(DBGBCRn_EL1(i), DBGBCR(E=1, PMC=0b11, BAS=0xf).value)
+
+ # enable all watchpoints again
+ for i, wpc in enumerate(self._wpcs):
+ self.u.msr(DBGWCRn_EL1(i), wpc)
+
+ return True
+
+ def handle_break(self, ctx):
+ # disable all breakpoints so that we don't get stuck
+ for i in range(5):
+ self.u.msr(DBGBCRn_EL1(i), 0)
+
+ # we'll need to single step to enable these breakpoints again
+ self.u.msr(MDSCR_EL1, MDSCR(SS=1, MDE=1).value)
+ self.ctx.spsr.SS = 1
+
+ if ctx.elr in self._bp_hooks:
+ if self._bp_hooks[ctx.elr](ctx):
+ return True
+
+ def handle_watch(self, ctx):
+ # disable all watchpoints so that we don't get stuck
+ for i in range(len(self._wps)):
+ self.u.msr(DBGWCRn_EL1(i), 0)
+
+ # we'll need to single step to enable these watchpoints again
+ self.u.msr(MDSCR_EL1, MDSCR(SS=1, MDE=1).value)
+ self.ctx.spsr.SS = 1
+
+ def add_hvcall(self, callid, handler):
+ self.hvcall_handlers[callid] = handler
+
+ def handle_brk(self, ctx):
+ iss = ctx.esr.ISS
+ if iss != 0x4242:
+ return self._lower()
+
+ # HV call from EL0/1
+ callid = ctx.regs[0]
+ handler = self.hvcall_handlers.get(callid, None)
+ if handler is None:
+ self.log(f"Undefined HV call #{callid}")
+ return False
+
+ ok = handler(ctx)
+ if ok:
+ ctx.elr += 4
+ return ok
+
+ def handle_dabort(self, ctx):
+ insn = self.p.read32(ctx.elr_phys)
+ far_phys = self.p.hv_translate(ctx.far, True, False)
+
+ if insn & 0x3b200c00 == 0x38200000:
+ page = far_phys & ~0x3fff
+
+ before = self.p.read32(far_phys)
+ self.map_hw(page, page, 0x4000)
+ r0b = self.ctx.regs[0]
+ self.log(f"-ELR={self.ctx.elr:#x} LR={self.ctx.regs[30]:#x}")
+ self.step()
+ self.log(f"+ELR={self.ctx.elr:#x}")
+ r0a = self.ctx.regs[0]
+ self.dirty_maps.set(irange(page, 0x4000))
+ self.pt_update()
+ after = self.p.read32(far_phys)
+ self.log(f"Unhandled atomic: @{far_phys:#x} {before:#x} -> {after:#x} | r0={r0b:#x} -> {r0a:#x}")
+ return True
+
+ if insn & 0x3f000000 == 0x08000000:
+ page = far_phys & ~0x3fff
+ before = self.p.read32(far_phys)
+ self.map_hw(page, page, 0x4000)
+ r0b = self.ctx.regs[0]
+ self.log(f"-ELR={self.ctx.elr:#x} LR={self.ctx.regs[30]:#x}")
+ self.step()
+ self.log(f"+ELR={self.ctx.elr:#x}")
+ r0a = self.ctx.regs[0]
+ self.dirty_maps.set(irange(page, 0x4000))
+ self.pt_update()
+ after = self.p.read32(far_phys)
+ self.log(f"Unhandled exclusive: @{far_phys:#x} {before:#x} -> {after:#x} | r0={r0b:#x} -> {r0a:#x}")
+ return True
+
+ def handle_sync(self, ctx):
+ if ctx.esr.EC == ESR_EC.MSR:
+ return self.handle_msr(ctx)
+
+ if ctx.esr.EC == ESR_EC.IMPDEF:
+ return self.handle_impdef(ctx)
+
+ if ctx.esr.EC == ESR_EC.HVC:
+ return self.handle_hvc(ctx)
+
+ if ctx.esr.EC == ESR_EC.SSTEP_LOWER:
+ return self.handle_step(ctx)
+
+ if ctx.esr.EC == ESR_EC.BKPT_LOWER:
+ return self.handle_break(ctx)
+
+ if ctx.esr.EC == ESR_EC.WATCH_LOWER:
+ return self.handle_watch(ctx)
+
+ if ctx.esr.EC == ESR_EC.BRK:
+ return self.handle_brk(ctx)
+
+ if ctx.esr.EC == ESR_EC.DABORT_LOWER:
+ return self.handle_dabort(ctx)
+
+ def _load_context(self):
+ self._info_data = self.iface.readmem(self.exc_info, ExcInfo.sizeof())
+ self.ctx = ExcInfo.parse(self._info_data)
+ return self.ctx
+
+ def _commit_context(self):
+ new_info = ExcInfo.build(self.ctx)
+ if new_info != self._info_data:
+ self.iface.writemem(self.exc_info, new_info)
+ self._info_data = new_info
+
+ def handle_exception(self, reason, code, info):
+ self.exc_info = info
+ self.exc_reason = reason
+ if reason in (START.EXCEPTION_LOWER, START.EXCEPTION):
+ code = EXC(code)
+ elif reason == START.HV:
+ code = HV_EVENT(code)
+ self.exc_code = code
+ self.is_fault = reason == START.EXCEPTION_LOWER and code in (EXC.SYNC, EXC.SERROR)
+
+ # Nested context switch is handled by the caller
+ if self.switching_context:
+ self.switching_context = False
+ return
+
+ self._in_handler = True
+
+ ctx = self._load_context()
+ self.exc_orig_cpu = self.ctx.cpu_id
+
+ handled = False
+ user_interrupt = False
+
+ try:
+ if reason == START.EXCEPTION_LOWER:
+ if code == EXC.SYNC:
+ handled = self.handle_sync(ctx)
+ elif code == EXC.FIQ:
+ self.u.msr(CNTV_CTL_EL0, 0)
+ self.u.print_context(ctx, False, sym=self.get_sym)
+ handled = True
+ elif reason == START.HV:
+ code = HV_EVENT(code)
+ if code == HV_EVENT.HOOK_VM:
+ handled = self.handle_vm_hook(ctx)
+ elif code == HV_EVENT.USER_INTERRUPT:
+ handled = True
+ user_interrupt = True
+ except Exception as e:
+ self.log(f"Python exception while handling guest exception:")
+ traceback.print_exc()
+
+ if handled:
+ ret = EXC_RET.HANDLED
+ if self._sigint_pending:
+ self.update_pac_mask()
+ self.log("User interrupt")
+ else:
+ self.log(f"Guest exception: {reason.name}/{code.name}")
+ self.update_pac_mask()
+ self.u.print_context(ctx, self.is_fault, sym=self.get_sym)
+
+ if self._sigint_pending or not handled or user_interrupt:
+ self._sigint_pending = False
+
+ signal.signal(signal.SIGINT, self.default_sigint)
+ ret = self.run_shell("Entering hypervisor shell", "Returning from exception")
+ signal.signal(signal.SIGINT, self._handle_sigint)
+
+ if ret is None:
+ ret = EXC_RET.HANDLED
+
+ self.pt_update()
+
+ self._commit_context()
+ self.ctx = None
+ self.exc_orig_cpu = None
+ self.p.exit(ret)
+
+ self._in_handler = False
+ if self._sigint_pending:
+ self._handle_sigint()
+
+ def handle_bark(self, reason, code, info):
+ self._in_handler = True
+ self._sigint_pending = False
+
+ signal.signal(signal.SIGINT, self.default_sigint)
+ ret = self.run_shell("Entering panic shell", "Exiting")
+ signal.signal(signal.SIGINT, self._handle_sigint)
+
+ self.p.exit(0)
+
+ def attach_virtio(self, dev, base=None, irq=None, verbose=False):
+ if base is None:
+ base = alloc_mmio_base(self.adt, 0x1000)
+ if irq is None:
+ irq = alloc_aic_irq(self.adt)
+
+ data = dev.config_data
+ data_base = self.u.heap.malloc(len(data))
+ self.iface.writemem(data_base, data)
+
+ config = VirtioConfig.build({
+ "irq": irq,
+ "devid": dev.devid,
+ "feats": dev.feats,
+ "num_qus": dev.num_qus,
+ "data": data_base,
+ "data_len": len(data),
+ "verbose": verbose,
+ })
+
+ config_base = self.u.heap.malloc(len(config))
+ self.iface.writemem(config_base, config)
+
+ name = None
+ for i in range(16):
+ n = "/arm-io/virtio%d" % i
+ if n not in self.adt:
+ name = n
+ break
+ if name is None:
+ raise ValueError("Too many virtios in ADT")
+
+ print(f"Adding {n} @ 0x{base:x}, irq {irq}")
+
+ node = self.adt.create_node(name)
+ node.reg = [Container(addr=node.to_bus_addr(base), size=0x1000)]
+ node.interrupt_parent = getattr(self.adt["/arm-io/aic"], "AAPL,phandle")
+ node.interrupts = (irq,)
+ node.compatible = ["virtio,mmio"]
+
+ self.p.hv_map_virtio(base, config_base)
+ self.add_tracer(irange(base, 0x1000), "VIRTIO", TraceMode.RESERVED)
+
+ dev.base = base
+ dev.hv = self
+ self.virtio_devs[base] = dev
+
+ def handle_virtio(self, reason, code, info):
+ ctx = self.iface.readstruct(info, ExcInfo)
+ self.virtio_ctx = info = self.iface.readstruct(ctx.data, VirtioExcInfo)
+
+ try:
+ handled = self.virtio_devs[info.devbase].handle_exc(info)
+ except:
+ self.log(f"Python exception from within virtio handler")
+ traceback.print_exc()
+ handled = False
+
+ if not handled:
+ signal.signal(signal.SIGINT, self.default_sigint)
+ self.run_shell("Entering hypervisor shell", "Returning")
+ signal.signal(signal.SIGINT, self._handle_sigint)
+
+ self.p.exit(EXC_RET.HANDLED)
+
+ def skip(self):
+ self.ctx.elr += 4
+ self.cont()
+
+ def cont(self):
+ os.kill(os.getpid(), signal.SIGUSR1)
+
+ def _lower(self):
+ if not self.is_fault:
+ print("Cannot lower non-fault exception")
+ return False
+
+ self.u.msr(ELR_EL12, self.ctx.elr)
+ self.u.msr(SPSR_EL12, self.ctx.spsr.value)
+ self.u.msr(ESR_EL12, self.ctx.esr.value)
+ self.u.msr(FAR_EL12, self.ctx.far)
+
+ exc_off = 0x80 * self.exc_code
+
+ if self.ctx.spsr.M == SPSR_M.EL0t:
+ exc_off += 0x400
+ elif self.ctx.spsr.M == SPSR_M.EL1t:
+ pass
+ elif self.ctx.spsr.M == SPSR_M.EL1h:
+ exc_off += 0x200
+ else:
+ print(f"Unknown exception level {self.ctx.spsr.M}")
+ return False
+
+ self.ctx.spsr.M = SPSR_M.EL1h
+ self.ctx.spsr.D = 1
+ self.ctx.spsr.A = 1
+ self.ctx.spsr.I = 1
+ self.ctx.spsr.F = 1
+ self.ctx.elr = self.u.mrs(VBAR_EL12) + exc_off
+
+ return True
+
+ def lower(self, step=False):
+ self.cpu() # Return to exception CPU
+
+ if not self._lower():
+ return
+ elif step:
+ self.step()
+ else:
+ self.cont()
+
+ def step(self):
+ self.u.msr(MDSCR_EL1, MDSCR(SS=1, MDE=1).value)
+ self.ctx.spsr.SS = 1
+ self.p.hv_pin_cpu(self.ctx.cpu_id)
+ self._switch_context()
+ self.p.hv_pin_cpu(0xffffffffffffffff)
+
+ def _switch_context(self, exit=EXC_RET.HANDLED):
+ # Flush current CPU context out to HV
+ self._commit_context()
+ self.exc_info = None
+ self.ctx = None
+
+ self.switching_context = True
+ # Exit out of the proxy
+ self.p.exit(exit)
+ # Wait for next proxy entry
+ self.iface.wait_and_handle_boot()
+ if self.switching_context:
+ raise Exception(f"Failed to switch context")
+
+ # Fetch new context
+ self._load_context()
+
+ def cpu(self, cpu=None):
+ if cpu is None:
+ cpu = self.exc_orig_cpu
+ if cpu == self.ctx.cpu_id:
+ return
+
+ if not self.p.hv_switch_cpu(cpu):
+ raise ValueError(f"Invalid or inactive CPU #{cpu}")
+
+ self._switch_context()
+ if self.ctx.cpu_id != cpu:
+ raise Exception(f"Switching to CPU #{cpu} but ended on #{self.ctx.cpu_id}")
+
+ def add_hw_bp(self, vaddr, hook=None):
+ if None not in self._bps:
+ raise ValueError("Cannot add more HW breakpoints")
+
+ i = self._bps.index(None)
+ cpu_id = self.ctx.cpu_id
+ try:
+ for cpu in self.cpus():
+ self.u.msr(DBGBCRn_EL1(i), DBGBCR(E=1, PMC=0b11, BAS=0xf).value)
+ self.u.msr(DBGBVRn_EL1(i), vaddr)
+ finally:
+ self.cpu(cpu_id)
+ self._bps[i] = vaddr
+ if hook is not None:
+ self._bp_hooks[vaddr] = hook
+
+ def remove_hw_bp(self, vaddr):
+ idx = self._bps.index(vaddr)
+ self._bps[idx] = None
+ cpu_id = self.ctx.cpu_id
+ try:
+ for cpu in self.cpus():
+ self.u.msr(DBGBCRn_EL1(idx), 0)
+ self.u.msr(DBGBVRn_EL1(idx), 0)
+ finally:
+ self.cpu(cpu_id)
+ if vaddr in self._bp_hooks:
+ del self._bp_hooks[vaddr]
+
+ def add_sym_bp(self, name, hook=None):
+ return self.add_hw_bp(self.resolve_symbol(name), hook=hook)
+
+ def remove_sym_bp(self, name):
+ return self.remove_hw_bp(self.resolve_symbol(name))
+
+ def clear_hw_bps(self):
+ for vaddr in self._bps:
+ self.remove_hw_bp(vaddr)
+
+ def add_hw_wp(self, vaddr, bas, lsc):
+ for i, i_vaddr in enumerate(self._wps):
+ if i_vaddr is None:
+ self._wps[i] = vaddr
+ self._wpcs[i] = DBGWCR(E=1, PAC=0b11, BAS=bas, LSC=lsc).value
+ cpu_id = self.ctx.cpu_id
+ try:
+ for cpu in self.cpus():
+ self.u.msr(DBGWCRn_EL1(i), self._wpcs[i])
+ self.u.msr(DBGWVRn_EL1(i), vaddr)
+ finally:
+ self.cpu(cpu_id)
+ return
+ raise ValueError("Cannot add more HW watchpoints")
+
+ def get_wp_bas(self, vaddr):
+ for i, i_vaddr in enumerate(self._wps):
+ if i_vaddr == vaddr:
+ return self._wpcs[i].BAS
+
+ def remove_hw_wp(self, vaddr):
+ idx = self._wps.index(vaddr)
+ self._wps[idx] = None
+ self._wpcs[idx] = 0
+ cpu_id = self.ctx.cpu_id
+ try:
+ for cpu in self.cpus():
+ self.u.msr(DBGWCRn_EL1(idx), 0)
+ self.u.msr(DBGWVRn_EL1(idx), 0)
+ finally:
+ self.cpu(cpu_id)
+
+ def exit(self):
+ os.kill(os.getpid(), signal.SIGUSR2)
+
+ def reboot(self):
+ print("Hard rebooting the system")
+ self.p.reboot()
+ sys.exit(0)
+
+ def hvc(self, arg):
+ assert 0 <= arg <= 0xffff
+ return 0xd4000002 | (arg << 5)
+
+ def decode_dbg_panic(self):
+ xnutools.decode_debugger_state(self.u, self.ctx)
+
+ def decode_panic_call(self):
+ xnutools.decode_panic_call(self.u, self.ctx)
+
+ def context(self):
+ f = f" (orig: #{self.exc_orig_cpu})" if self.ctx.cpu_id != self.exc_orig_cpu else ""
+ print(f" == On CPU #{self.ctx.cpu_id}{f} ==")
+ print(f" Reason: {self.exc_reason.name}/{self.exc_code.name}")
+ self.u.print_context(self.ctx, self.is_fault, sym=self.get_sym)
+
+ def bt(self, frame=None, lr=None):
+ if frame is None:
+ frame = self.ctx.regs[29]
+ if lr is None:
+ lr = self.unpac(self.ctx.elr) + 4
+
+ print("Stack trace:")
+ frames = set()
+ while frame:
+ if frame in frames:
+ print("Stack loop detected!")
+ break
+ frames.add(frame)
+ print(f" - {self.addr(lr - 4)}")
+ lrp = self.p.hv_translate(frame + 8)
+ fpp = self.p.hv_translate(frame)
+ if not fpp:
+ break
+ lr = self.unpac(self.p.read64(lrp))
+ frame = self.p.read64(fpp)
+
+ def cpus(self):
+ for i in sorted(self.started_cpus):
+ self.cpu(i)
+ yield i
+
+ def patch_exception_handling(self):
+ if self.ctx.cpu_id != 0:
+ return
+
+ if self.want_vbar is not None:
+ vbar = self.want_vbar
+ else:
+ vbar = self.u.mrs(VBAR_EL12)
+
+ if vbar == self.vbar_el1:
+ return
+
+ if vbar == 0:
+ return
+
+ if self.u.mrs(SCTLR_EL12) & 1:
+ vbar_phys = self.p.hv_translate(vbar, False, False)
+ if vbar_phys == 0:
+ self.log(f"VBAR vaddr 0x{vbar:x} translation failed!")
+ if self.vbar_el1 is not None:
+ self.want_vbar = vbar
+ self.u.msr(VBAR_EL12, self.vbar_el1)
+ return
+ else:
+ if vbar & (1 << 63):
+ self.log(f"VBAR vaddr 0x{vbar:x} without translation enabled")
+ if self.vbar_el1 is not None:
+ self.want_vbar = vbar
+ self.u.msr(VBAR_EL12, self.vbar_el1)
+ return
+
+ vbar_phys = vbar
+
+ if self.want_vbar is not None:
+ self.want_vbar = None
+ self.u.msr(VBAR_EL12, vbar)
+
+ self.log(f"New VBAR paddr: 0x{vbar_phys:x}")
+
+ #for i in range(16):
+ for i in [0, 3, 4, 7, 8, 11, 12, 15]:
+ idx = 0
+ addr = vbar_phys + 0x80 * i
+ orig = self.p.read32(addr)
+ if (orig & 0xfc000000) != 0x14000000:
+ self.log(f"Unknown vector #{i}:\n")
+ self.u.disassemble_at(addr, 16)
+ else:
+ idx = len(self.vectors)
+ delta = orig & 0x3ffffff
+ if delta == 0:
+ target = None
+ self.log(f"Vector #{i}: Loop\n")
+ else:
+ target = (delta << 2) + vbar + 0x80 * i
+ self.log(f"Vector #{i}: 0x{target:x}\n")
+ self.vectors.append((i, target))
+ self.u.disassemble_at(addr, 16)
+ self.p.write32(addr, self.hvc(idx))
+
+ self.p.dc_cvau(vbar_phys, 0x800)
+ self.p.ic_ivau(vbar_phys, 0x800)
+
+ self.vbar_el1 = vbar
+
+ def set_logfile(self, fd):
+ self.print_tracer.log_file = fd
+
+ def init(self):
+ self.adt = load_adt(self.u.get_adt())
+ self.iodev = self.p.iodev_whoami()
+ self.tba = self.u.ba.copy()
+ self.device_addr_tbl = self.adt.build_addr_lookup()
+ self.print_tracer = trace.PrintTracer(self, self.device_addr_tbl)
+
+ # disable unused USB iodev early so interrupts can be reenabled in hv_init()
+ for iodev in IODEV:
+ if iodev >= IODEV.USB0 and iodev != self.iodev:
+ print(f"Disable iodev {iodev!s}")
+ self.p.iodev_set_usage(iodev, 0)
+
+ print("Initializing hypervisor over iodev %s" % self.iodev)
+ self.p.hv_init()
+
+ self.iface.set_handler(START.EXCEPTION_LOWER, EXC.SYNC, self.handle_exception)
+ self.iface.set_handler(START.EXCEPTION_LOWER, EXC.IRQ, self.handle_exception)
+ self.iface.set_handler(START.EXCEPTION_LOWER, EXC.FIQ, self.handle_exception)
+ self.iface.set_handler(START.EXCEPTION_LOWER, EXC.SERROR, self.handle_exception)
+ self.iface.set_handler(START.EXCEPTION, EXC.FIQ, self.handle_exception)
+ self.iface.set_handler(START.HV, HV_EVENT.USER_INTERRUPT, self.handle_exception)
+ self.iface.set_handler(START.HV, HV_EVENT.HOOK_VM, self.handle_exception)
+ self.iface.set_handler(START.HV, HV_EVENT.VTIMER, self.handle_exception)
+ self.iface.set_handler(START.HV, HV_EVENT.WDT_BARK, self.handle_bark)
+ self.iface.set_handler(START.HV, HV_EVENT.CPU_SWITCH, self.handle_exception)
+ self.iface.set_handler(START.HV, HV_EVENT.VIRTIO, self.handle_virtio)
+ self.iface.set_event_handler(EVENT.MMIOTRACE, self.handle_mmiotrace)
+ self.iface.set_event_handler(EVENT.IRQTRACE, self.handle_irqtrace)
+
+ # Map MMIO ranges as HW by default
+ for r in self.adt["/arm-io"].ranges:
+ print(f"Mapping MMIO range: {r.parent_addr:#x} .. {r.parent_addr + r.size:#x}")
+ self.add_tracer(irange(r.parent_addr, r.size), "HW", TraceMode.OFF)
+
+ hcr = HCR(self.u.mrs(HCR_EL2))
+ if self.novm:
+ hcr.VM = 0
+ hcr.AMO = 0
+ else:
+ hcr.TACR = 1
+ hcr.TIDCP = 0
+ hcr.TVM = 0
+ hcr.FMO = 1
+ hcr.IMO = 0
+ hcr.TTLBOS = 1
+ self.u.msr(HCR_EL2, hcr.value)
+
+ # Trap dangerous things
+ hacr = HACR(0)
+ if not self.novm:
+ #hacr.TRAP_CPU_EXT = 1
+ #hacr.TRAP_SPRR = 1
+ #hacr.TRAP_GXF = 1
+ hacr.TRAP_CTRR = 1
+ hacr.TRAP_EHID = 1
+ hacr.TRAP_HID = 1
+ hacr.TRAP_ACC = 1
+ hacr.TRAP_IPI = 1
+ hacr.TRAP_SERROR_INFO = 1 # M1RACLES mitigation
+ hacr.TRAP_PM = 1
+ self.u.msr(HACR_EL2, hacr.value)
+
+ # enable and route debug exceptions to EL2
+ mdcr = MDCR(0)
+ mdcr.TDE = 1
+ mdcr.TDA = 1
+ mdcr.TDOSA = 1
+ mdcr.TDRA = 1
+ self.u.msr(MDCR_EL2, mdcr.value)
+ self.u.msr(MDSCR_EL1, MDSCR(MDE=1).value)
+
+ # Enable AMX
+ amx_ctl = AMX_CTL(self.u.mrs(AMX_CTL_EL1))
+ amx_ctl.EN_EL1 = 1
+ self.u.msr(AMX_CTL_EL1, amx_ctl.value)
+
+ # Set guest AP keys
+ self.u.msr(APVMKEYLO_EL2, 0x4E7672476F6E6147)
+ self.u.msr(APVMKEYHI_EL2, 0x697665596F755570)
+ self.u.msr(APSTS_EL12, 1)
+
+ self.map_vuart()
+
+ actlr = ACTLR(self.u.mrs(ACTLR_EL12))
+ actlr.EnMDSB = 1
+ self.u.msr(ACTLR_EL12, actlr.value)
+
+ self.setup_adt()
+
+ def map_vuart(self):
+ node = base = self.adt["/arm-io/uart0"]
+ base = node.get_reg(0)[0]
+
+ zone = irange(base, 0x4000)
+ irq = node.interrupts[0]
+ self.p.hv_map_vuart(base, irq, self.iodev)
+ self.add_tracer(zone, "VUART", TraceMode.RESERVED)
+
+ def map_essential(self):
+ # Things we always map/take over, for the hypervisor to work
+ _pmgr = {}
+
+ def wh(base, off, data, width):
+ self.log(f"PMGR W {base:x}+{off:x}:{width} = 0x{data:x}: Dangerous write")
+ self.p.mask32(base + off, 0x3ff, (data | 0xf) & ~(0x80000400))
+ _pmgr[base + off] = (data & 0xfffffc0f) | ((data & 0xf) << 4)
+
+ def rh(base, off, width):
+ data = self.p.read32(base + off)
+ ret = _pmgr.setdefault(base + off, data)
+ self.log(f"PMGR R {base:x}+{off:x}:{width} = 0x{data:x} -> 0x{ret:x}")
+ return ret
+
+ atc = f"ATC{self.iodev - IODEV.USB0}_USB"
+
+ hook_devs = ["UART0", atc]
+
+ pmgr = self.adt["/arm-io/pmgr"]
+ dev_by_name = {dev.name: dev for dev in pmgr.devices}
+ dev_by_id = {dev.id: dev for dev in pmgr.devices}
+
+ pmgr_hooks = []
+
+ def hook_pmgr_dev(dev):
+ ps = pmgr.ps_regs[dev.psreg]
+ if dev.psidx or dev.psreg:
+ addr = pmgr.get_reg(ps.reg)[0] + ps.offset + dev.psidx * 8
+ pmgr_hooks.append(addr)
+ for idx in dev.parents:
+ if idx in dev_by_id:
+ hook_pmgr_dev(dev_by_id[idx])
+
+ for name in hook_devs:
+ dev = dev_by_name[name]
+ hook_pmgr_dev(dev)
+
+ pmgr0_start = pmgr.get_reg(0)[0]
+
+ for addr in pmgr_hooks:
+ self.map_hook(addr, 4, write=wh, read=rh)
+ #TODO : turn into a real tracer
+ self.add_tracer(irange(addr, 4), "PMGR HACK", TraceMode.RESERVED)
+
+ pg_overrides = {
+ 0x23d29c05c: 0xc000000,
+ 0x23d29c044: 0xc000000,
+ }
+
+ for addr in pg_overrides:
+ self.map_hook(addr, 4, read=lambda base, off, width: pg_overrides[base + off])
+ self.add_tracer(irange(addr, 4), "PMGR HACK", TraceMode.RESERVED)
+
+ def cpustart_wh(base, off, data, width):
+ self.log(f"CPUSTART W {base:x}+{off:x}:{width} = 0x{data:x}")
+ if off >= 8:
+ assert width == 32
+ die = base // 0x20_0000_0000
+ cluster = (off - 8) // 4
+ for i in range(32):
+ if data & (1 << i):
+ self.start_secondary(die, cluster, i)
+
+ die_count = self.adt["/arm-io"].die_count if hasattr(self.adt["/arm-io"], "die-count") else 1
+
+ for die in range(0, die_count):
+ if self.u.adt["/chosen"].chip_id in (0x8103, 0x6000, 0x6001, 0x6002):
+ cpu_start = 0x54000 + die * 0x20_0000_0000
+ elif self.u.adt["/chosen"].chip_id in (0x8112,):
+ cpu_start = 0x34000 + die * 0x20_0000_0000
+ else:
+ self.log("CPUSTART unknown for this SoC!")
+ break
+
+ zone = irange(pmgr0_start + cpu_start, 0x20)
+ self.map_hook(pmgr0_start + cpu_start, 0x20, write=cpustart_wh)
+ self.add_tracer(zone, "CPU_START", TraceMode.RESERVED)
+
+ def start_secondary(self, die, cluster, cpu):
+ self.log(f"Starting guest secondary {die}:{cluster}:{cpu}")
+
+ for node in list(self.adt["cpus"]):
+ if ((die << 11) | (cluster << 8) | cpu) == node.reg:
+ break
+ else:
+ self.log("CPU not found!")
+ return
+
+ entry = self.p.read64(node.cpu_impl_reg[0]) & 0xfffffffffff
+ index = node.cpu_id
+ self.log(f" CPU #{index}: RVBAR = {entry:#x}")
+
+ self.sysreg[index] = {}
+ self.started_cpus.add(index)
+ self.p.hv_start_secondary(index, entry)
+
+ def setup_adt(self):
+ self.adt["product"].product_name += " on m1n1 hypervisor"
+ self.adt["product"].product_description += " on m1n1 hypervisor"
+ soc_name = "Virtual " + self.adt["product"].product_soc_name + " on m1n1 hypervisor"
+ self.adt["product"].product_soc_name = soc_name
+
+ if self.iodev >= IODEV.USB0:
+ idx = self.iodev - IODEV.USB0
+ for prefix in ("/arm-io/dart-usb%d",
+ "/arm-io/atc-phy%d",
+ "/arm-io/usb-drd%d",
+ "/arm-io/acio%d",
+ "/arm-io/acio-cpu%d",
+ "/arm-io/dart-acio%d",
+ "/arm-io/apciec%d",
+ "/arm-io/dart-apciec%d",
+ "/arm-io/apciec%d-piodma",
+ "/arm-io/i2c0/hpmBusManager/hpm%d",
+ "/arm-io/atc%d-dpxbar",
+ "/arm-io/atc%d-dpphy",
+ "/arm-io/atc%d-dpin0",
+ "/arm-io/atc%d-dpin1",
+ "/arm-io/atc-phy%d",
+ ):
+ name = prefix % idx
+ print(f"Removing ADT node {name}")
+ try:
+ del self.adt[name]
+ except KeyError:
+ pass
+
+ if self.wdt_cpu is not None:
+ name = f"/cpus/cpu{self.wdt_cpu}"
+ print(f"Removing ADT node {name}")
+ try:
+ del self.adt[name]
+ except KeyError:
+ pass
+
+ if not self.smp:
+ for cpu in list(self.adt["cpus"]):
+ if cpu.name != "cpu0":
+ print(f"Removing ADT node {cpu._path}")
+ try:
+ del self.adt["cpus"][cpu.name]
+ except KeyError:
+ pass
+
+ def set_bootargs(self, boot_args):
+ if "-v" in boot_args.split():
+ self.tba.video.display = 0
+ else:
+ self.tba.video.display = 1
+ print(f"Setting boot arguments to {boot_args!r}")
+ self.tba.cmdline = boot_args
+
+ def unmap_carveouts(self):
+ print(f"Unmapping TZ carveouts...")
+ carveout_p = self.p.mcc_get_carveouts()
+ while True:
+ base = self.p.read64(carveout_p)
+ size = self.p.read64(carveout_p + 8)
+ if not base:
+ break
+ print(f" Unmap [{base:#x}..{base + size - 1:#x}]")
+ self.del_tracer(irange(base, size), "RAM-LOW")
+ self.del_tracer(irange(base, size), "RAM-HIGH")
+ carveout_p += 16
+
+ def enable_time_stealing(self):
+ self.p.hv_set_time_stealing(True)
+
+ def disable_time_stealing(self):
+ self.p.hv_set_time_stealing(False)
+
+
+ def load_raw(self, image, entryoffset=0x800, use_xnu_symbols=False, vmin=0):
+ sepfw_start, sepfw_length = self.u.adt["chosen"]["memory-map"].SEPFW
+ tc_start, tc_size = self.u.adt["chosen"]["memory-map"].TrustCache
+ if hasattr(self.u.adt["chosen"]["memory-map"], "preoslog"):
+ preoslog_start, preoslog_size = self.u.adt["chosen"]["memory-map"].preoslog
+ else:
+ preoslog_size = 0
+
+ image_size = align(len(image))
+ sepfw_off = image_size
+ image_size += align(sepfw_length)
+ preoslog_off = image_size
+ image_size += preoslog_size
+ self.bootargs_off = image_size
+ bootargs_size = 0x4000
+ image_size += bootargs_size
+
+ print(f"Total region size: 0x{image_size:x} bytes")
+
+ self.phys_base = phys_base = guest_base = self.u.heap_top
+ self.ram_base = self.phys_base & ~0xffffffff
+ self.ram_size = self.u.ba.mem_size_actual
+ guest_base += 16 << 20 # ensure guest starts within a 16MB aligned region of mapped RAM
+ self.adt_base = guest_base
+ guest_base += align(self.u.ba.devtree_size)
+ tc_base = guest_base
+ guest_base += align(tc_size)
+ self.guest_base = guest_base
+ mem_top = self.u.ba.phys_base + self.u.ba.mem_size
+ mem_size = mem_top - phys_base
+
+ print(f"Physical memory: 0x{phys_base:x} .. 0x{mem_top:x}")
+ print(f"Guest region start: 0x{guest_base:x}")
+
+ self.entry = guest_base + entryoffset
+
+ print(f"Mapping guest physical memory...")
+ self.add_tracer(irange(self.ram_base, self.u.ba.phys_base - self.ram_base), "RAM-LOW", TraceMode.OFF)
+ self.add_tracer(irange(phys_base, self.u.ba.mem_size_actual - phys_base + self.ram_base), "RAM-HIGH", TraceMode.OFF)
+ self.unmap_carveouts()
+
+ print(f"Loading kernel image (0x{len(image):x} bytes)...")
+ self.u.compressed_writemem(guest_base, image, True)
+ self.p.dc_cvau(guest_base, len(image))
+ self.p.ic_ivau(guest_base, len(image))
+
+ print(f"Copying SEPFW (0x{sepfw_length:x} bytes)...")
+ self.p.memcpy8(guest_base + sepfw_off, sepfw_start, sepfw_length)
+
+ print(f"Copying TrustCache (0x{tc_size:x} bytes)...")
+ self.p.memcpy8(tc_base, tc_start, tc_size)
+
+ if hasattr(self.u.adt["chosen"]["memory-map"], "preoslog"):
+ print(f"Copying preoslog (0x{preoslog_size:x} bytes)...")
+ self.p.memcpy8(guest_base + preoslog_off, preoslog_start, preoslog_size)
+
+ print(f"Adjusting addresses in ADT...")
+ self.adt["chosen"]["memory-map"].SEPFW = (guest_base + sepfw_off, sepfw_length)
+ self.adt["chosen"]["memory-map"].TrustCache = (tc_base, tc_size)
+ self.adt["chosen"]["memory-map"].DeviceTree = (self.adt_base, align(self.u.ba.devtree_size))
+ self.adt["chosen"]["memory-map"].BootArgs = (guest_base + self.bootargs_off, bootargs_size)
+ if hasattr(self.u.adt["chosen"]["memory-map"], "preoslog"):
+ self.adt["chosen"]["memory-map"].preoslog = (guest_base + preoslog_off, preoslog_size)
+
+ print(f"Setting up bootargs at 0x{guest_base + self.bootargs_off:x}...")
+
+ self.tba.mem_size = mem_size
+ self.tba.phys_base = phys_base
+ self.tba.virt_base = 0xfffffe0010000000 + (phys_base & (32 * 1024 * 1024 - 1))
+ self.tba.devtree = self.adt_base - phys_base + self.tba.virt_base
+ self.tba.top_of_kernel_data = guest_base + image_size
+
+ if use_xnu_symbols == True:
+ self.sym_offset = vmin - guest_base + self.tba.phys_base - self.tba.virt_base
+
+ self.iface.writemem(guest_base + self.bootargs_off, BootArgs.build(self.tba))
+
+ print("Setting secondary CPU RVBARs...")
+ rvbar = self.entry & ~0xfff
+ for cpu in self.adt["cpus"][1:]:
+ addr, size = cpu.cpu_impl_reg
+ print(f" {cpu.name}: [0x{addr:x}] = 0x{rvbar:x}")
+ self.p.write64(addr, rvbar)
+
+ def _load_macho_symbols(self):
+ self.symbol_dict = self.macho.symbols
+ self.symbols = [(v, k) for k, v in self.macho.symbols.items()]
+ self.symbols.sort()
+
+ def load_macho(self, data, symfile=None):
+ if isinstance(data, str):
+ data = open(data, "rb")
+
+ self.macho = macho = MachO(data)
+ if symfile is not None:
+ if isinstance(symfile, str):
+ symfile = open(symfile, "rb")
+ syms = MachO(symfile)
+ macho.add_symbols("com.apple.kernel", syms)
+ self.xnu_mode = True
+
+ self._load_macho_symbols()
+
+ def load_hook(data, segname, size, fileoff, dest):
+ if segname != "__TEXT_EXEC":
+ return data
+
+ print(f"Patching segment {segname}...")
+
+ a = array.array("I", data)
+
+ output = []
+
+ p = 0
+ while (p := data.find(b"\x20\x00", p)) != -1:
+ if (p & 3) != 2:
+ p += 1
+ continue
+
+ opcode = a[p // 4]
+ inst = self.hvc((opcode & 0xffff))
+ off = fileoff + (p & ~3)
+ if off >= 0xbfcfc0:
+ print(f" 0x{off:x}: 0x{opcode:04x} -> hvc 0x{opcode:x} (0x{inst:x})")
+ a[p // 4] = inst
+ p += 4
+
+ print("Done.")
+ return a.tobytes()
+
+ #image = macho.prepare_image(load_hook)
+ image = macho.prepare_image()
+ self.load_raw(image, entryoffset=(macho.entry - macho.vmin), use_xnu_symbols=self.xnu_mode, vmin=macho.vmin)
+
+
+ def update_pac_mask(self):
+ tcr = TCR(self.u.mrs(TCR_EL12))
+ valid_bits = (1 << (64 - tcr.T1SZ)) - 1
+ self.pac_mask = 0xffffffffffffffff & ~valid_bits
+ valid_bits = (1 << (64 - tcr.T0SZ)) - 1
+ self.user_pac_mask = 0xffffffffffffffff & ~valid_bits
+
+ def unpac(self, v):
+ if v & (1 << 55):
+ return v | self.pac_mask
+ else:
+ return v & ~self.user_pac_mask
+
+ def load_system_map(self, path):
+ # Assume Linux
+ self.sym_offset = 0
+ self.xnu_mode = False
+ self.symbols = []
+ self.symbol_dict = {}
+ with open(path) as fd:
+ for line in fd.readlines():
+ addr, t, name = line.split()
+ addr = int(addr, 16)
+ self.symbols.append((addr, name))
+ self.symbol_dict[name] = addr
+ self.symbols.sort()
+
+ def add_kext_symbols(self, kext, demangle=False):
+ info_plist = plistlib.load(open(f"{kext}/Contents/Info.plist", "rb"))
+ identifier = info_plist["CFBundleIdentifier"]
+ name = info_plist["CFBundleName"]
+ macho = MachO(open(f"{kext}/Contents/MacOS/{name}", "rb"))
+ self.macho.add_symbols(identifier, macho, demangle=demangle)
+ self._load_macho_symbols()
+
+ def _handle_sigint(self, signal=None, stack=None):
+ self._sigint_pending = True
+ self.interrupt()
+
+ def interrupt(self):
+ if self._in_handler:
+ return
+
+ # Kick the proxy to break out of the hypervisor
+ self.iface.dev.write(b"!")
+
+ def run_script(self, path):
+ new_locals = runpy.run_path(path, init_globals=self.shell_locals, run_name="<hv_script>")
+ self.shell_locals.clear()
+ self.shell_locals.update(new_locals)
+
+ def run_code(self, code):
+ exec(code, self.shell_locals)
+
+ def start(self):
+ print("Disabling other iodevs...")
+ for iodev in IODEV:
+ if iodev != self.iodev:
+ print(f" - {iodev!s}")
+ self.p.iodev_set_usage(iodev, 0)
+
+ print("Doing essential MMIO remaps...")
+ self.map_essential()
+
+ print("Updating page tables...")
+ self.pt_update()
+
+ adt_blob = self.adt.build()
+ print(f"Uploading ADT (0x{len(adt_blob):x} bytes)...")
+ self.iface.writemem(self.adt_base, adt_blob)
+
+ print("Improving logo...")
+ self.p.fb_improve_logo()
+
+ print("Shutting down framebuffer...")
+ self.p.fb_shutdown(True)
+
+ print("Enabling SPRR...")
+ self.u.msr(SPRR_CONFIG_EL1, 1)
+
+ print("Enabling GXF...")
+ self.u.msr(GXF_CONFIG_EL1, 1)
+
+ print(f"Jumping to entrypoint at 0x{self.entry:x}")
+
+ self.iface.dev.timeout = None
+ self.default_sigint = signal.signal(signal.SIGINT, self._handle_sigint)
+
+ set_sigquit_stackdump_handler()
+
+ if self.wdt_cpu is not None:
+ self.p.hv_wdt_start(self.wdt_cpu)
+ # Does not return
+
+ self.started = True
+ self.started_cpus.add(0)
+ self.p.hv_start(self.entry, self.guest_base + self.bootargs_off)
+
+from .. import trace
diff --git a/tools/proxyclient/m1n1/hv/gdbserver/__init__.py b/tools/proxyclient/m1n1/hv/gdbserver/__init__.py
new file mode 100644
index 0000000..ade807f
--- /dev/null
+++ b/tools/proxyclient/m1n1/hv/gdbserver/__init__.py
@@ -0,0 +1,480 @@
+# SPDX-License-Identifier: MIT
+import errno, io, os, pkgutil, re, selectors, socketserver, threading, traceback
+from construct import Array, BytesInteger, Container, Int32ul, Int64ul, Struct
+
+from ...proxy import *
+from ...sysreg import *
+from ...utils import *
+
+from ..types import *
+
+__all__ = ["GDBServer"]
+
+class GDBServer:
+ __g = Struct(
+ "regs" / Array(32, Int64ul),
+ "pc" / Int64ul,
+ "spsr" / Int32ul,
+ "q" / Array(32, BytesInteger(16, swapped=True)),
+ "fpsr" / Int32ul,
+ "fpcr" / Int32ul,
+ )
+ __seperator = re.compile("[,;:]")
+
+ def __init__(self, hv, address, log):
+ self.__hc = None
+ self.__hg = None
+ self.__hv = hv
+ self.__interrupt_eventfd = os.eventfd(0, flags=os.EFD_CLOEXEC | os.EFD_NONBLOCK)
+ self.__interrupt_selector = selectors.DefaultSelector()
+ self.__request = None
+ self.log = log
+
+ self.__interrupt_selector.register(self.__interrupt_eventfd, selectors.EVENT_READ)
+
+ handle = self.__handle
+
+ class Handler(socketserver.BaseRequestHandler):
+ def handle(self):
+ handle(self.request)
+
+ self.__server = socketserver.UnixStreamServer(address, Handler, False)
+ self.__thread = threading.Thread(target=self.__server.serve_forever,)
+
+ def __add_wp(self, addr, kind, lsc):
+ start = addr & 7
+ if start + kind > 8:
+ return b"E01"
+
+ self.__hv.add_hw_wp(addr & ~7, ((1 << kind) - 1) << start, lsc)
+ return b"OK"
+
+ def __remove_wp(self, addr):
+ self.__hv.remove_hw_wp(addr & ~7)
+ return b"OK"
+
+ def __cpu(self, cpu):
+ if cpu is None:
+ return
+
+ self.__hv.cpu(cpu)
+
+ def __stop_reply(self):
+ self.__hc = None
+ self.__hg = None
+
+ prefix = b"T05thread:"
+
+ if self.__hv.exc_reason == START.EXCEPTION_LOWER:
+ if self.__hv.exc_code == EXC.SYNC:
+ if self.__hv.ctx.esr.EC == ESR_EC.BKPT_LOWER:
+ prefix = b"T05hwbreak:;thread:"
+ elif self.__hv.ctx.esr.EC == ESR_EC.WATCH_LOWER:
+ bas = self.__hv.get_wp_bas(self.__hv.ctx.far)
+ if not bas is None and bas != 0:
+ offset = 0
+ while (bas & (1 << offset)) == 0:
+ offset += 1
+ addr = self.__hv.ctx.far + offset
+ formatted_addr = bytes(format(addr, "x"), "utf-8")
+ prefix = b"T05watch:" + formatted_addr + b";thread:"
+ elif self.__hv.exc_reason == START.HV:
+ if self.__hv.exc_code == HV_EVENT.USER_INTERRUPT:
+ prefix = b"T02thread:"
+
+ return prefix + bytes(format(self.__hv.ctx.cpu_id, "x"), "utf-8") + b";"
+
+ def __wait_shell(self):
+ try:
+ os.eventfd_read(self.__interrupt_eventfd)
+ except BlockingIOError:
+ pass
+
+ while not self.__interrupt_eventfd in (key.fileobj for key, mask in self.__interrupt_selector.select()):
+ recv = self.__request.recv(1)
+ if not recv:
+ break
+
+ for byte in recv:
+ if byte in b"\1\3":
+ self.__hv.interrupt()
+ break
+
+ def __eval(self, data):
+ if self.log:
+ self.log(f"eval: {data}")
+
+ if len(data) < 1:
+ return b""
+
+ if data[0] in b"?":
+ return self.__stop_reply()
+
+ if data[0] in b"c":
+ if len(data) != 1:
+ self.__cpu(self.__hc)
+ self.__hv.ctx.elr = int(data[1:].decode(), 16)
+
+ self.__hv.cont()
+ self.__wait_shell()
+ return self.__stop_reply()
+
+ if data[0] in b"g":
+ self.__cpu(self.__hg)
+ g = Container()
+ g.regs = self.__hv.ctx.regs.copy()
+ g.regs[31] = self.__hv.ctx.sp[1]
+ g.pc = self.__hv.ctx.elr
+ g.spsr = self.__hv.ctx.spsr.value
+ g.q = self.__hv.u.q
+ g.fpsr = self.__hv.u.mrs(FPSR)
+ g.fpcr = self.__hv.u.mrs(FPCR)
+
+ return bytes(GDBServer.__g.build(g).hex(), "utf-8")
+
+ if data[0] in b"G":
+ g = GDBServer.__g.parse(bytes.fromhex(data[1:].decode()))
+ self.__cpu(self.__hg)
+
+ for index in range(31):
+ self.__hv.ctx.regs[index] = g.regs[index]
+
+ self.__hv.ctx.sp[1] = g.regs[31]
+ self.__hv.ctx.elr = g.pc
+ self.__hv.ctx.spsr = g.spsr.value
+
+ q = self.__hv.u.q
+ for index, value in enumerate(g.q):
+ q[index] = value
+ self.__hv.u.push_simd()
+
+ self.__hv.u.msr(FPSR, g.fpsr, silent=True)
+ self.__hv.u.msr(FPCR, g.fpsr, silent=True)
+
+ return b"OK"
+
+ if data[0] in b"H":
+ if len(data) > 1:
+ if data[1] in b"c":
+ cpu_id = int(data[2:].decode(), 16)
+ if cpu_id in self.__hv.started_cpus:
+ self.__hc = cpu_id
+ return b"OK"
+
+ return b"E01"
+
+ if data[1] in b"g":
+ cpu_id = int(data[2:].decode(), 16)
+ if cpu_id in self.__hv.started_cpus:
+ self.__hg = cpu_id
+ return b"OK"
+
+ return b"E01"
+
+ return b""
+
+ if data[0] in b"krR":
+ self.__hv.reboot()
+
+ if data[0] in b"m":
+ split = GDBServer.__seperator.split(data[1:].decode(), maxsplit=1)
+ fields = [int(field, 16) for field in split]
+ return bytes(self.__hv.readmem(fields[0], fields[1]).hex(), "utf-8")
+
+ if data[0] in b"M":
+ split = GDBServer.__seperator.split(data[1:].decode(), maxsplit=2)
+ mem = bytes.fromhex(split[2])[:int(split[1], 16)]
+ if self.__hv.writemem(int(split[0], 16), mem) < len(mem):
+ return "E22"
+
+ return b"OK"
+
+ if data[0] in b"p":
+ number = int(data[1:].decode(), 16)
+ self.__cpu(self.__hg)
+ if number < 31:
+ reg = GDBServer.__g.regs.subcon.subcon.build(self.__hv.ctx.regs[number])
+ elif number == 31:
+ reg = GDBServer.__g.regs.subcon.subcon.build(self.__hv.ctx.sp[1])
+ elif number == 32:
+ reg = GDBServer.__g.pc.build(self.__hv.ctx.elr)
+ elif number == 33:
+ reg = GDBServer.__g.spsr.build(self.__hv.ctx.spsr.value)
+ elif number < 66:
+ reg = GDBServer.__g.q.subcon.subcon.build(self.__hv.u.q[number - 34])
+ elif number == 66:
+ reg = GDBServer.__g.fpsr.build(self.__hv.u.mrs(FPSR))
+ elif number == 67:
+ reg = GDBServer.__g.fpcr.build(self.__hv.u.mrs(FPCR))
+ else:
+ return b"E01"
+
+ return bytes(reg.hex(), "utf-8")
+
+ if data[0] in b"P":
+ partition = data[1:].partition(b"=")
+ number = int(partition[0].decode(), 16)
+ reg = bytes.fromhex(partition[2].decode())
+ self.__cpu(self.__hg)
+ if number < 31:
+ self.__hv.ctx.regs[number] = GDBServer.__g.regs.subcon.subcon.unpack(reg)
+ elif number == 31:
+ self.__hv.ctx.regs[1] = GDBServer.__g.regs.subcon.subcon.unpack(reg)
+ elif number == 32:
+ self.__hv.ctx.elr = GDBServer.__g.pc.parse(reg)
+ elif number == 33:
+ self.__hv.ctx.spsr.value = GDBServer.__g.spsr.parse(reg)
+ elif number < 66:
+ self.__hv.u.q[number - 34] = GDBServer.__g.q.subcon.subcon.parse(reg)
+ self.__hv.u.push_simd()
+ elif number == 66:
+ self.__hv.u.msr(FPSR, GDBServer.__g.fpsr.parse(reg), silent=True)
+ elif number == 67:
+ self.__hv.u.msr(FPCR, GDBServer.__g.fpcr.parse(reg), silent=True)
+ else:
+ return b"E01"
+
+ return b"OK"
+
+ if data[0] in b"q":
+ split = GDBServer.__seperator.split(data[1:].decode(), maxsplit=1)
+ if split[0] == "C":
+ cpu_id = self.__hg or self.__hv.ctx.cpu_id
+ return b"QC" + bytes(format(cpu_id, "x"), "utf-8")
+
+ if split[0] == "fThreadInfo":
+ cpu_ids = b",".join(bytes(format(cpu.cpu_id, "x"), "utf-8") for cpu in self.__hv.adt["cpus"])
+ return b"m" + cpu_ids
+
+ if split[0] == "sThreadInfo":
+ return b"l"
+
+ if split[0] == "Rcmd":
+ self.__cpu(self.__hg)
+ self.__hv.run_code(split[1])
+ return b"OK"
+
+ if split[0] == "Supported":
+ return b"PacketSize=65536;qXfer:features:read+;hwbreak+"
+
+ if split[0] == "ThreadExtraInfo":
+ thread_id = int(split[1], 16)
+ for node in self.__hv.adt["cpus"]:
+ if node.cpu_id == thread_id:
+ return bytes(bytes(str(node), "utf-8").hex(), "utf-8")
+
+ return b""
+
+ if split[0] == "Xfer":
+ xfer = GDBServer.__seperator.split(split[1], maxsplit=4)
+ if xfer[0] == "features" and xfer[1] == "read":
+ resource = os.path.join("features", xfer[2])
+ annex = pkgutil.get_data(__name__, resource)
+ if annex is None:
+ return b"E00"
+
+ request_offset = int(xfer[3], 16)
+ request_len = int(xfer[4], 16)
+ read = annex[request_offset:request_offset + request_len]
+ return (b"l" if len(read) < request_len else b"m") + read
+
+ return b""
+
+ if split[0] == "HostInfo":
+ addressing_bits = bytes(str(64 - self.__hv.pac_mask.bit_count()), "utf-8")
+ return b"cputype:16777228;cpusubtype:2;endian:little;ptrsize:64;watchpoint_exceptions_received:before;addressing_bits:" + addressing_bits + b";"
+
+ return b""
+
+ if data[0] in b"s":
+ self.__cpu(self.__hc)
+
+ if len(data) != 1:
+ self.__hv.ctx.elr = int(data[1:].decode(), 16)
+
+ self.__hv.step()
+ return self.__stop_reply()
+
+ if data[0] in b"T":
+ if int(data[1:].decode(), 16) in self.__hv.started_cpus:
+ return b"OK"
+
+ return b"E01"
+
+ if data[0] in b"X":
+ partition = data[1:].partition(b":")
+ split = GDBServer.__seperator.split(partition[0].decode(), maxsplit=1)
+ mem = partition[2][:int(split[1], 16)]
+ if self.__hv.writemem(int(split[0], 16), mem) < len(mem):
+ return b"E22"
+
+ return b"OK"
+
+ if data[0] in b"z":
+ split = GDBServer.__seperator.split(data[1:].decode(), maxsplit=2)
+ if split[0] == "1":
+ self.__hv.remove_hw_bp(int(split[1], 16))
+ return b"OK"
+
+ if split[0] == "2":
+ return self.__remove_wp(int(split[1], 16))
+
+ if split[0] == "3":
+ return self.__remove_wp(int(split[1], 16))
+
+ if split[0] == "4":
+ return self.__remove_wp(int(split[1], 16))
+
+ return b""
+
+ if data[0] in b"Z":
+ split = GDBServer.__seperator.split(data[1:].decode(), maxsplit=2)
+ if split[0] == "1":
+ self.__hv.add_hw_bp(int(split[1], 16))
+ return b"OK"
+
+ if split[0] == "2":
+ addr = int(split[1], 16)
+ kind = int(split[2], 16)
+ return self.__add_wp(addr, kind, DBGWCR_LSC.S)
+
+ if split[0] == "3":
+ addr = int(split[1], 16)
+ kind = int(split[2], 16)
+ return self.__add_wp(addr, kind, DBGWCR_LSC.L)
+
+ if split[0] == "4":
+ addr = int(split[1], 16)
+ kind = int(split[2], 16)
+ return self.__add_wp(addr, kind, DBGWCR_LSC.S | DBGWCR_LSC.L)
+
+ return b""
+
+ return b""
+
+ def __send(self, prefix, data):
+ with io.BytesIO(prefix) as buffer:
+ buffer.write(prefix)
+
+ last = 0
+ for index, byte in enumerate(data):
+ if not byte in b"#$}*":
+ continue
+
+ buffer.write(data[last:index])
+ buffer.write(b"}")
+ buffer.write(bytes([byte ^ 0x20]))
+ last = index + 1
+
+ buffer.write(data[last:])
+ checksum = (sum(buffer.getvalue()) - sum(prefix)) % 256
+
+ buffer.write(b"#")
+ buffer.write(bytes(format(checksum, "02x"), "utf-8"))
+
+ value = buffer.getvalue()
+
+ if self.log:
+ self.log(f"send: {value}")
+
+ self.__request.send(value)
+
+ def __handle(self, request):
+ self.__request = request
+ input_buffer = b""
+
+ if not self.__hv.in_shell:
+ self.__hv.interrupt()
+ self.__wait_shell()
+
+ self.__interrupt_selector.register(self.__request, selectors.EVENT_READ)
+ try:
+ while True:
+ recv = self.__request.recv(65536)
+ if not recv:
+ break
+
+ input_buffer += recv
+
+ while True:
+ dollar = input_buffer.find(b"$")
+ if dollar < 0:
+ input_buffer = b""
+ break
+
+ sharp = input_buffer.find(b"#", dollar)
+ if sharp < 0 or len(input_buffer) < sharp + 3:
+ input_buffer = input_buffer[dollar:]
+ break
+
+ input_data = input_buffer[dollar + 1:sharp]
+ input_checksum = input_buffer[sharp + 1:sharp + 3]
+ input_buffer = input_buffer[sharp + 3:]
+
+ try:
+ parsed_input_checksum = int(input_checksum.decode(), 16)
+ except ValueError as error:
+ print(error)
+ continue
+
+ if (sum(input_data) % 256) != parsed_input_checksum:
+ self.__request.send(b"-")
+ continue
+
+ self.__request.send(b"+")
+
+ with io.BytesIO() as input_decoded:
+ input_index = 0
+ input_last = 0
+ while input_index < len(input_data):
+ if input_data[input_index] == b"*":
+ input_decoded.write(input_data[input_last:input_index])
+ instance = input_decoded.getvalue()[-1]
+ input_index += 1
+ input_run_len = input_data[input_index] - 29
+ input_run = bytes([instance]) * input_run_len
+ input_decoded.write(input_run)
+ input_index += 1
+ input_last = input_index
+ elif input_data[input_index] == b"}":
+ input_decoded.write(input_data[input_last:input_index])
+ input_index += 1
+ input_decoded.write(bytes([input_data[input_index] ^ 0x20]))
+ input_index += 1
+ input_last = input_index
+ else:
+ input_index += 1
+
+ input_decoded.write(input_data[input_last:])
+
+ try:
+ output_decoded = self.__eval(input_decoded.getvalue())
+ except Exception:
+ output_decoded = b"E." + bytes(traceback.format_exc(), "utf-8")
+
+ self.__send(b"$", output_decoded)
+ finally:
+ self.__interrupt_selector.unregister(self.__request)
+
+ def notify_in_shell(self):
+ os.eventfd_write(self.__interrupt_eventfd, 1)
+
+ def activate(self):
+ try:
+ self.__server.server_bind()
+ except OSError as error:
+ if error.errno != errno.EADDRINUSE:
+ raise
+
+ os.remove(self.__server.server_address)
+ self.__server.server_bind()
+
+ self.__server.server_activate()
+ self.__thread.start()
+
+ def shutdown(self):
+ os.close(self.__interrupt_eventfd)
+ self.__interrupt_selector.close()
+ self.__server.shutdown()
+ self.__server.server_close()
+ self.__thread.join()
diff --git a/tools/proxyclient/m1n1/hv/gdbserver/features/aarch64-core.xml b/tools/proxyclient/m1n1/hv/gdbserver/features/aarch64-core.xml
new file mode 100644
index 0000000..b6d344f
--- /dev/null
+++ b/tools/proxyclient/m1n1/hv/gdbserver/features/aarch64-core.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0"?>
+<!-- Copyright (C) 2009-2022 Free Software Foundation, Inc.
+ Contributed by ARM Ltd.
+
+ Copying and distribution of this file, with or without modification,
+ are permitted in any medium without royalty provided the copyright
+ notice and this notice are preserved. -->
+
+<!DOCTYPE feature SYSTEM "gdb-target.dtd">
+<feature name="org.gnu.gdb.aarch64.core">
+ <reg name="x0" bitsize="64"/>
+ <reg name="x1" bitsize="64"/>
+ <reg name="x2" bitsize="64"/>
+ <reg name="x3" bitsize="64"/>
+ <reg name="x4" bitsize="64"/>
+ <reg name="x5" bitsize="64"/>
+ <reg name="x6" bitsize="64"/>
+ <reg name="x7" bitsize="64"/>
+ <reg name="x8" bitsize="64"/>
+ <reg name="x9" bitsize="64"/>
+ <reg name="x10" bitsize="64"/>
+ <reg name="x11" bitsize="64"/>
+ <reg name="x12" bitsize="64"/>
+ <reg name="x13" bitsize="64"/>
+ <reg name="x14" bitsize="64"/>
+ <reg name="x15" bitsize="64"/>
+ <reg name="x16" bitsize="64"/>
+ <reg name="x17" bitsize="64"/>
+ <reg name="x18" bitsize="64"/>
+ <reg name="x19" bitsize="64"/>
+ <reg name="x20" bitsize="64"/>
+ <reg name="x21" bitsize="64"/>
+ <reg name="x22" bitsize="64"/>
+ <reg name="x23" bitsize="64"/>
+ <reg name="x24" bitsize="64"/>
+ <reg name="x25" bitsize="64"/>
+ <reg name="x26" bitsize="64"/>
+ <reg name="x27" bitsize="64"/>
+ <reg name="x28" bitsize="64"/>
+ <reg name="x29" bitsize="64"/>
+ <reg name="x30" bitsize="64"/>
+ <reg name="sp" bitsize="64" type="data_ptr"/>
+
+ <reg name="pc" bitsize="64" type="code_ptr"/>
+
+ <flags id="cpsr_flags" size="4">
+ <!-- Stack Pointer. -->
+ <field name="SP" start="0" end="0"/>
+
+ <!-- Exception Level. -->
+ <field name="EL" start="2" end="3"/>
+ <!-- Execution state. -->
+ <field name="nRW" start="4" end="4"/>
+
+ <!-- FIQ interrupt mask. -->
+ <field name="F" start="6" end="6"/>
+ <!-- IRQ interrupt mask. -->
+ <field name="I" start="7" end="7"/>
+ <!-- SError interrupt mask. -->
+ <field name="A" start="8" end="8"/>
+ <!-- Debug exception mask. -->
+ <field name="D" start="9" end="9"/>
+
+ <!-- ARMv8.0-A: Speculative Store Bypass. -->
+ <field name="SSBS" start="12" end="12"/>
+
+ <!-- Illegal Execution state. -->
+ <field name="IL" start="20" end="20"/>
+ <!-- Software Step. -->
+ <field name="SS" start="21" end="21"/>
+ <!-- ARMv8.1-A: Privileged Access Never. -->
+ <field name="PAN" start="22" end="22"/>
+ <!-- ARMv8.2-A: User Access Override. -->
+ <field name="UAO" start="23" end="23"/>
+ <!-- ARMv8.4-A: Data Independent Timing. -->
+ <field name="DIT" start="24" end="24"/>
+ <!-- ARMv8.5-A: Tag Check Override. -->
+ <field name="TCO" start="25" end="25"/>
+
+ <!-- Overflow Condition flag. -->
+ <field name="V" start="28" end="28"/>
+ <!-- Carry Condition flag. -->
+ <field name="C" start="29" end="29"/>
+ <!-- Zero Condition flag. -->
+ <field name="Z" start="30" end="30"/>
+ <!-- Negative Condition flag. -->
+ <field name="N" start="31" end="31"/>
+ </flags>
+ <reg name="cpsr" bitsize="32" type="cpsr_flags"/>
+
+</feature>
diff --git a/tools/proxyclient/m1n1/hv/gdbserver/features/aarch64-fpu.xml b/tools/proxyclient/m1n1/hv/gdbserver/features/aarch64-fpu.xml
new file mode 100644
index 0000000..4db5c50
--- /dev/null
+++ b/tools/proxyclient/m1n1/hv/gdbserver/features/aarch64-fpu.xml
@@ -0,0 +1,160 @@
+<?xml version="1.0"?>
+<!-- Copyright (C) 2009-2022 Free Software Foundation, Inc.
+ Contributed by ARM Ltd.
+
+ Copying and distribution of this file, with or without modification,
+ are permitted in any medium without royalty provided the copyright
+ notice and this notice are preserved. -->
+
+<!DOCTYPE feature SYSTEM "gdb-target.dtd">
+<feature name="org.gnu.gdb.aarch64.fpu">
+ <vector id="v2d" type="ieee_double" count="2"/>
+ <vector id="v2u" type="uint64" count="2"/>
+ <vector id="v2i" type="int64" count="2"/>
+ <vector id="v4f" type="ieee_single" count="4"/>
+ <vector id="v4u" type="uint32" count="4"/>
+ <vector id="v4i" type="int32" count="4"/>
+ <vector id="v8f" type="ieee_half" count="8"/>
+ <vector id="v8u" type="uint16" count="8"/>
+ <vector id="v8i" type="int16" count="8"/>
+ <vector id="v8bf16" type="bfloat16" count="8"/>
+ <vector id="v16u" type="uint8" count="16"/>
+ <vector id="v16i" type="int8" count="16"/>
+ <vector id="v1u" type="uint128" count="1"/>
+ <vector id="v1i" type="int128" count="1"/>
+ <union id="vnd">
+ <field name="f" type="v2d"/>
+ <field name="u" type="v2u"/>
+ <field name="s" type="v2i"/>
+ </union>
+ <union id="vns">
+ <field name="f" type="v4f"/>
+ <field name="u" type="v4u"/>
+ <field name="s" type="v4i"/>
+ </union>
+ <union id="vnh">
+ <field name="bf" type="v8bf16"/>
+ <field name="f" type="v8f"/>
+ <field name="u" type="v8u"/>
+ <field name="s" type="v8i"/>
+ </union>
+ <union id="vnb">
+ <field name="u" type="v16u"/>
+ <field name="s" type="v16i"/>
+ </union>
+ <union id="vnq">
+ <field name="u" type="v1u"/>
+ <field name="s" type="v1i"/>
+ </union>
+ <union id="aarch64v">
+ <field name="d" type="vnd"/>
+ <field name="s" type="vns"/>
+ <field name="h" type="vnh"/>
+ <field name="b" type="vnb"/>
+ <field name="q" type="vnq"/>
+ </union>
+ <reg name="v0" bitsize="128" type="aarch64v" regnum="34"/>
+ <reg name="v1" bitsize="128" type="aarch64v" />
+ <reg name="v2" bitsize="128" type="aarch64v" />
+ <reg name="v3" bitsize="128" type="aarch64v" />
+ <reg name="v4" bitsize="128" type="aarch64v" />
+ <reg name="v5" bitsize="128" type="aarch64v" />
+ <reg name="v6" bitsize="128" type="aarch64v" />
+ <reg name="v7" bitsize="128" type="aarch64v" />
+ <reg name="v8" bitsize="128" type="aarch64v" />
+ <reg name="v9" bitsize="128" type="aarch64v" />
+ <reg name="v10" bitsize="128" type="aarch64v"/>
+ <reg name="v11" bitsize="128" type="aarch64v"/>
+ <reg name="v12" bitsize="128" type="aarch64v"/>
+ <reg name="v13" bitsize="128" type="aarch64v"/>
+ <reg name="v14" bitsize="128" type="aarch64v"/>
+ <reg name="v15" bitsize="128" type="aarch64v"/>
+ <reg name="v16" bitsize="128" type="aarch64v"/>
+ <reg name="v17" bitsize="128" type="aarch64v"/>
+ <reg name="v18" bitsize="128" type="aarch64v"/>
+ <reg name="v19" bitsize="128" type="aarch64v"/>
+ <reg name="v20" bitsize="128" type="aarch64v"/>
+ <reg name="v21" bitsize="128" type="aarch64v"/>
+ <reg name="v22" bitsize="128" type="aarch64v"/>
+ <reg name="v23" bitsize="128" type="aarch64v"/>
+ <reg name="v24" bitsize="128" type="aarch64v"/>
+ <reg name="v25" bitsize="128" type="aarch64v"/>
+ <reg name="v26" bitsize="128" type="aarch64v"/>
+ <reg name="v27" bitsize="128" type="aarch64v"/>
+ <reg name="v28" bitsize="128" type="aarch64v"/>
+ <reg name="v29" bitsize="128" type="aarch64v"/>
+ <reg name="v30" bitsize="128" type="aarch64v"/>
+ <reg name="v31" bitsize="128" type="aarch64v"/>
+
+ <flags id="fpsr_flags" size="4">
+ <!-- Invalid Operation cumulative floating-point exception bit. -->
+ <field name="IOC" start="0" end="0"/>
+ <!-- Divide by Zero cumulative floating-point exception bit. -->
+ <field name="DZC" start="1" end="1"/>
+ <!-- Overflow cumulative floating-point exception bit. -->
+ <field name="OFC" start="2" end="2"/>
+ <!-- Underflow cumulative floating-point exception bit. -->
+ <field name="UFC" start="3" end="3"/>
+ <!-- Inexact cumulative floating-point exception bit.. -->
+ <field name="IXC" start="4" end="4"/>
+ <!-- Input Denormal cumulative floating-point exception bit. -->
+ <field name="IDC" start="7" end="7"/>
+ <!-- Cumulative saturation bit, Advanced SIMD only. -->
+ <field name="QC" start="27" end="27"/>
+ <!-- When AArch32 is supported at any Exception level and AArch32
+ floating-point is implemented: Overflow condition flag for AArch32
+ floating-point comparison operations. -->
+ <field name="V" start="28" end="28"/>
+ <!-- When AArch32 is supported at any Exception level and AArch32
+ floating-point is implemented:
+ Carry condition flag for AArch32 floating-point comparison operations.
+ -->
+ <field name="C" start="29" end="29"/>
+ <!-- When AArch32 is supported at any Exception level and AArch32
+ floating-point is implemented:
+ Zero condition flag for AArch32 floating-point comparison operations.
+ -->
+ <field name="Z" start="30" end="30"/>
+ <!-- When AArch32 is supported at any Exception level and AArch32
+ floating-point is implemented:
+ Negative condition flag for AArch32 floating-point comparison
+ operations. -->
+ <field name="N" start="31" end="31"/>
+ </flags>
+ <reg name="fpsr" bitsize="32" type="fpsr_flags"/>
+
+ <flags id="fpcr_flags" size="4">
+ <!-- Flush Inputs to Zero (part of Armv8.7). -->
+ <field name="FIZ" start="0" end="0"/>
+ <!-- Alternate Handling (part of Armv8.7). -->
+ <field name="AH" start="1" end="1"/>
+ <!-- Controls how the output elements other than the lowest element of the
+ vector are determined for Advanced SIMD scalar instructions (part of
+ Armv8.7). -->
+ <field name="NEP" start="2" end="2"/>
+ <!-- Invalid Operation floating-point exception trap enable. -->
+ <field name="IOE" start="8" end="8"/>
+ <!-- Divide by Zero floating-point exception trap enable. -->
+ <field name="DZE" start="9" end="9"/>
+ <!-- Overflow floating-point exception trap enable. -->
+ <field name="OFE" start="10" end="10"/>
+ <!-- Underflow floating-point exception trap enable. -->
+ <field name="UFE" start="11" end="11"/>
+ <!-- Inexact floating-point exception trap enable. -->
+ <field name="IXE" start="12" end="12"/>
+ <!-- Input Denormal floating-point exception trap enable. -->
+ <field name="IDE" start="15" end="15"/>
+ <!-- Flush-to-zero mode control bit on half-precision data-processing
+ instructions. -->
+ <field name="FZ16" start="19" end="19"/>
+ <!-- Rounding Mode control field. -->
+ <field name="RMode" start="22" end="23"/>
+ <!-- Flush-to-zero mode control bit. -->
+ <field name="FZ" start="24" end="24"/>
+ <!-- Default NaN mode control bit. -->
+ <field name="DN" start="25" end="25"/>
+ <!-- Alternative half-precision control bit. -->
+ <field name="AHP" start="26" end="26"/>
+ </flags>
+ <reg name="fpcr" bitsize="32" type="fpcr_flags"/>
+</feature>
diff --git a/tools/proxyclient/m1n1/hv/gdbserver/features/target.xml b/tools/proxyclient/m1n1/hv/gdbserver/features/target.xml
new file mode 100644
index 0000000..ca0454a
--- /dev/null
+++ b/tools/proxyclient/m1n1/hv/gdbserver/features/target.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<!-- SPDX-License-Identifier: MIT -->
+<!DOCTYPE target SYSTEM "gdb-target.dtd">
+<target version="1.0">
+ <architecture>aarch64</architecture>
+ <xi:include href="aarch64-core.xml" />
+ <xi:include href="aarch64-fpu.xml" />
+</target>
diff --git a/tools/proxyclient/m1n1/hv/types.py b/tools/proxyclient/m1n1/hv/types.py
new file mode 100644
index 0000000..0c3142e
--- /dev/null
+++ b/tools/proxyclient/m1n1/hv/types.py
@@ -0,0 +1,60 @@
+# SPDX-License-Identifier: MIT
+from construct import *
+from enum import IntEnum
+
+from ..utils import *
+
+__all__ = [
+ "MMIOTraceFlags", "EvtMMIOTrace", "EvtIRQTrace", "HV_EVENT",
+ "VMProxyHookData", "TraceMode",
+]
+
+class MMIOTraceFlags(Register32):
+ ATTR = 31, 24
+ CPU = 23, 16
+ SH = 15, 14
+ WIDTH = 4, 0
+ WRITE = 5
+ MULTI = 6
+
+EvtMMIOTrace = Struct(
+ "flags" / RegAdapter(MMIOTraceFlags),
+ "reserved" / Int32ul,
+ "pc" / Hex(Int64ul),
+ "addr" / Hex(Int64ul),
+ "data" / Hex(Int64ul),
+)
+
+EvtIRQTrace = Struct(
+ "flags" / Int32ul,
+ "type" / Hex(Int16ul),
+ "num" / Int16ul,
+)
+
+class HV_EVENT(IntEnum):
+ HOOK_VM = 1
+ VTIMER = 2
+ USER_INTERRUPT = 3
+ WDT_BARK = 4
+ CPU_SWITCH = 5
+ VIRTIO = 6
+
+VMProxyHookData = Struct(
+ "flags" / RegAdapter(MMIOTraceFlags),
+ "id" / Int32ul,
+ "addr" / Hex(Int64ul),
+ "data" / Array(8, Hex(Int64ul)),
+)
+
+class TraceMode(IntEnum):
+ '''
+Different types of Tracing '''
+
+ OFF = 0
+ BYPASS = 1
+ ASYNC = 2
+ UNBUF = 3
+ WSYNC = 4
+ SYNC = 5
+ HOOK = 6
+ RESERVED = 7
diff --git a/tools/proxyclient/m1n1/hv/virtio.py b/tools/proxyclient/m1n1/hv/virtio.py
new file mode 100644
index 0000000..790bb26
--- /dev/null
+++ b/tools/proxyclient/m1n1/hv/virtio.py
@@ -0,0 +1,133 @@
+# SPDX-License-Identifier: MIT
+from construct import Struct, Int8ul, Int16ul, Int32sl, Int32ul, Int64ul
+from subprocess import Popen, PIPE
+import pathlib
+import struct
+import os
+import sys
+
+from ..utils import *
+
+VirtioConfig = Struct(
+ "irq" / Int32sl,
+ "devid" / Int32ul,
+ "feats" / Int64ul,
+ "num_qus" / Int32ul,
+ "data" / Int64ul,
+ "data_len" / Int64ul,
+ "verbose" / Int8ul,
+)
+
+class VirtioDescFlags(Register16):
+ WRITE = 1
+ NEXT = 0
+
+VirtioDesc = Struct(
+ "addr" / Int64ul,
+ "len" / Int32ul,
+ "flags" / RegAdapter(VirtioDescFlags),
+ "next" / Int16ul,
+)
+
+VirtioExcInfo = Struct(
+ "devbase" / Int64ul,
+ "qu" / Int16ul,
+ "idx" / Int16ul,
+ "pad" / Int32ul,
+ "descbase" / Int64ul,
+)
+
+class VirtioDev:
+ def __init__(self):
+ self.base, self.hv = None, None # assigned by HV object
+
+ def read_buf(self, desc):
+ return self.hv.iface.readmem(desc.addr, desc.len)
+
+ def read_desc(self, ctx, idx):
+ off = VirtioDesc.sizeof() * idx
+ return self.hv.iface.readstruct(ctx.descbase + off, VirtioDesc)
+
+ @property
+ def config_data(self):
+ return b""
+
+ @property
+ def devid(self):
+ return 0
+
+ @property
+ def num_qus(self):
+ return 1
+
+ @property
+ def feats(self):
+ return 0
+
+class Virtio9PTransport(VirtioDev):
+ def __init__(self, tag="m1n1", root=None):
+ p_stdin, self.fin = os.pipe()
+ self.fout, p_stdout = os.pipe()
+ if root is None:
+ root = str(pathlib.Path(__file__).resolve().parents[3])
+ if type(tag) is str:
+ self.tag = tag.encode("ascii")
+ else:
+ self.tag = tag
+ self.p = Popen([
+ "u9fs",
+ "-a", "none", # no auth
+ "-n", # not a network conn
+ "-u", os.getlogin(), # single user
+ root,
+ ], stdin=p_stdin, stdout=p_stdout, stderr=sys.stderr)
+
+ @property
+ def config_data(self):
+ return struct.pack("=H", len(self.tag)) + self.tag
+
+ @property
+ def devid(self):
+ return 9
+
+ @property
+ def num_qus(self):
+ return 1
+
+ @property
+ def feats(self):
+ return 1
+
+ def call(self, req):
+ os.write(self.fin, req)
+ resp = os.read(self.fout, 4)
+ length = int.from_bytes(resp, byteorder="little")
+ resp += os.read(self.fout, length - 4)
+ return resp
+
+ def handle_exc(self, ctx):
+ head = self.read_desc(ctx, ctx.idx)
+ assert not head.flags.WRITE
+
+ req = bytearray()
+
+ while not head.flags.WRITE:
+ req += self.read_buf(head)
+
+ if not head.flags.NEXT:
+ break
+ head = self.read_desc(ctx, head.next)
+
+ resp = self.call(bytes(req))
+ resplen = len(resp)
+
+ while len(resp):
+ self.hv.iface.writemem(head.addr, resp[:head.len])
+ resp = resp[head.len:]
+ if not head.flags.NEXT:
+ break
+ head = self.read_desc(ctx, head.next)
+
+ self.hv.p.virtio_put_buffer(ctx.devbase, ctx.qu, ctx.idx, resplen)
+
+ return True
diff --git a/tools/proxyclient/m1n1/hv/virtutils.py b/tools/proxyclient/m1n1/hv/virtutils.py
new file mode 100644
index 0000000..934abc0
--- /dev/null
+++ b/tools/proxyclient/m1n1/hv/virtutils.py
@@ -0,0 +1,43 @@
+# SPDX-License-Identifier: MIT
+from m1n1.utils import align_up
+
+def collect_aic_irqs_in_use(adt):
+ used = set()
+ aic_phandle = getattr(adt["/arm-io/aic"], "AAPL,phandle")
+ for node in adt.walk_tree():
+ if not hasattr(node, "interrupt_parent") or \
+ node.interrupt_parent != aic_phandle:
+ continue
+ for no in node.interrupts:
+ used.add(no)
+ return used
+
+def usable_aic_irq_range(adt):
+ # These are too optimistic but since we allocate
+ # from the bottom of the range it doesn't matter much.
+ return {
+ "aic,1": range(0, 0x400),
+ "aic,2": range(0, 0x1000),
+ }.get(adt["/arm-io/aic"].compatible[0])
+
+def alloc_aic_irq(adt):
+ used = collect_aic_irqs_in_use(adt)
+ for no in usable_aic_irq_range(adt):
+ if no not in used:
+ return no
+ return None
+
+def usable_mmio_range(adt):
+ arm_io_range = adt["arm-io"].ranges[0]
+ return range(arm_io_range.parent_addr, arm_io_range.parent_addr + arm_io_range.size)
+
+def alloc_mmio_base(adt, size, alignment=0x4000):
+ span = usable_mmio_range(adt)
+ la = adt.build_addr_lookup()
+ for zone, devs in la.populate(span):
+ if len(devs) != 0:
+ continue
+ base = align_up(zone.start, alignment)
+ if zone.stop > base + size:
+ return base
+ return None
diff --git a/tools/proxyclient/m1n1/hw/admac.py b/tools/proxyclient/m1n1/hw/admac.py
new file mode 100644
index 0000000..3fb8032
--- /dev/null
+++ b/tools/proxyclient/m1n1/hw/admac.py
@@ -0,0 +1,416 @@
+# SPDX-License-Identifier: MIT
+import sys, time
+from enum import IntEnum
+from ..utils import *
+
+__all__ = ["ADMACRegs", "ADMAC", "E_BUSWIDTH", "E_FRAME"]
+
+
+class R_RING(Register32):
+ # overflow/underflow counter
+ OF_UF = 31, 16
+
+ # goes through 0, 1, 2, 3 as the pieces of a report/descriptor
+ # are being read/written through REPORT_READ/DESC_WRITE
+ READOUT_PROGRESS = 13, 12
+
+ # when READ_SLOT==WRITE_SLOT one of the two is set
+ EMPTY = 8
+ FULL = 9
+
+ ERR = 10
+
+ # next slot to read
+ READ_SLOT = 5, 4
+
+ # next slot to be written to
+ WRITE_SLOT = 1, 0
+
+class R_CHAN_STATUS(Register32):
+ # only raised if the descriptor had NOTIFY set
+ DESC_DONE = 0
+
+ DESC_RING_EMPTY = 4
+ REPORT_RING_FULL = 5
+
+ # cleared by writing ERR=1 either to TX_DESC_RING or TX_REPORT_RING
+ RING_ERR = 6
+
+ UNK0 = 1
+ UNK3 = 8
+ UNK4 = 9
+ UNK5 = 10
+
+class R_CHAN_CONTROL(Register32):
+ RESET_RINGS = 0
+ CLEAR_OF_UF_COUNTERS = 1
+ UNK1 = 3
+
+class E_BUSWIDTH(IntEnum):
+ W_8BIT = 0
+ W_16BIT = 1
+ W_32BIT = 2
+
+class E_FRAME(IntEnum):
+ F_1_WORD = 0
+ F_2_WORDS = 1
+ F_4_WORDS = 2
+
+class R_BUSWIDTH(Register32):
+ WORD = 2, 0, E_BUSWIDTH
+ FRAME = 6, 4, E_FRAME
+
+class R_CARVEOUT(Register32):
+ SIZE = 31, 16
+ BASE = 15, 0
+
+class ADMACRegs(RegMap):
+ TX_EN = 0x0, Register32 # one bit per channel
+ TX_EN_CLR = 0x4, Register32
+
+ RX_EN = 0x8, Register32
+ RX_EN_CLR = 0xc, Register32
+
+ UNK_CTL = 0x10, Register32
+
+ # each of the four registers represents an internal interrupt line,
+ # bits represent DMA channels which at the moment raise that particular line
+ #
+ # the irq-destination-index prop in ADT maybe selects the line which
+ # is actually wired out
+ #
+ TX_INTSTATE = irange(0x30, 4, 0x4), Register32
+ RX_INTSTATE = irange(0x40, 4, 0x4), Register32
+
+ # a 24 MHz always-running counter, top bit is always set
+ COUNTER = 0x70, Register64
+
+ TX_SRAM_SIZE = 0x94, Register32
+ RX_SRAM_SIZE = 0x98, Register32
+
+ # -- per-channel registers --
+
+ CHAN_CTL = (irange(0x8000, 32, 0x200)), R_CHAN_CONTROL
+
+ CHAN_BUSWIDTH = (irange(0x8040, 32, 0x200)), R_BUSWIDTH
+ CHAN_SRAM_CARVEOUT = (irange(0x8050, 32, 0x200)), R_CARVEOUT
+ CHAN_BURSTSIZE = (irange(0x8054, 32, 0x200)), Register32
+
+ CHAN_RESIDUE = irange(0x8064, 32, 0x200), Register32
+
+ CHAN_DESC_RING = irange(0x8070, 32, 0x200), R_RING
+ CHAN_REPORT_RING = irange(0x8074, 32, 0x200), R_RING
+
+ TX_DESC_WRITE = irange(0x10000, 16, 4), Register32
+ TX_REPORT_READ = irange(0x10100, 16, 4), Register32
+
+ RX_DESC_WRITE = irange(0x14000, 16, 4), Register32
+ RX_REPORT_READ = irange(0x14100, 16, 4), Register32
+
+ # per-channel, per-internal-line
+ CHAN_STATUS = (irange(0x8010, 32, 0x200), irange(0x0, 4, 0x4)), R_CHAN_STATUS
+ CHAN_INTMASK = (irange(0x8020, 32, 0x200), irange(0x0, 4, 0x4)), R_CHAN_STATUS
+
+
+class ADMACDescriptorFlags(Register32):
+ # whether to raise DESC_DONE in CHAN_STATUS
+ NOTIFY = 16
+
+ # whether to repeat this descriptor ad infinitum
+ #
+ # once a descriptor with this flag is loaded, any descriptors loaded
+ # afterwards are also repeated and nothing short of full power domain reset
+ # seems to revoke that behaviour. this looks like a HW bug.
+ REPEAT = 17
+
+ # arbitrary ID propagated into reports
+ DESC_ID = 7, 0
+
+class ADMACDescriptor(Reloadable):
+ def __init__(self, addr, length, **flags):
+ self.addr = addr
+ self.length = length
+ self.flags = ADMACDescriptorFlags(**flags)
+
+ def __repr__(self):
+ return f"<descriptor: addr=0x{self.addr:x} len=0x{self.length:x} flags={self.flags}>"
+
+ def ser(self):
+ return [
+ self.addr & (1<<32)-1,
+ self.addr>>32 & (1<<32)-1,
+ self.length & (1<<32)-1,
+ int(self.flags)
+ ]
+
+ @classmethod
+ def deser(self, seq):
+ if not len(seq) == 4:
+ raise ValueError
+ return ADMACDescriptor(
+ seq[0] | seq[1] << 32, # addr
+ seq[2], # length (in bytes)
+ **ADMACDescriptorFlags(seq[3]).fields
+ )
+
+
+class ADMACReportFlags(Register32):
+ UNK1 = 24
+ UNK2 = 25
+ UNK4 = 26 # memory access fault?
+ UNK3 = 27
+ DESC_ID = 7, 0
+
+class ADMACReport(Reloadable):
+ def __init__(self, countval, unk1, flags):
+ self.countval, self.unk1, self.flags = countval, unk1, ADMACReportFlags(flags)
+
+ def __repr__(self):
+ return f"<report: countval=0x{self.countval:x} unk1=0x{self.unk1:x} flags={self.flags}>"
+
+ def ser(self):
+ return [
+ self.countval & (1<<32)-1,
+ self.countval>>32 & (1<<32)-1,
+ self.unk1 & (1<<32)-1,
+ int(self.flags)
+ ]
+
+ @classmethod
+ def deser(self, seq):
+ if not len(seq) == 4:
+ raise ValueError
+ return ADMACReport(
+ seq[0] | seq[1] << 32, # countval
+ seq[2], # unk1
+ seq[3] # flags
+ )
+
+
+class ADMACChannel(Reloadable):
+ def __init__(self, parent, channo):
+ self.p = parent
+ self.iface = parent.p.iface
+ self.dart = parent.dart
+ self.regs = parent.regs
+ self.tx = (channo % 2) == 0
+ self.rx = not self.tx
+ self.ch = channo
+
+ self._desc_id = 0
+ self._submitted = {}
+ self._last_report = None
+ self._est_byte_rate = None
+
+ def reset(self):
+ self.regs.CHAN_CTL[self.ch].set(RESET_RINGS=1, CLEAR_OF_UF_COUNTERS=1)
+ self.regs.CHAN_CTL[self.ch].set(RESET_RINGS=0, CLEAR_OF_UF_COUNTERS=0)
+
+ self.burstsize = 0xc0_0060
+ self.buswidth = E_BUSWIDTH.W_32BIT
+ self.framesize = E_FRAME.F_1_WORD
+
+ def enable(self):
+ self.regs.CHAN_INTMASK[self.ch, 0].reg = \
+ R_CHAN_STATUS(DESC_DONE=1, DESC_RING_EMPTY=1,
+ REPORT_RING_FULL=1, RING_ERR=1)
+
+ if self.tx:
+ self.regs.TX_EN.val = 1 << (self.ch//2)
+ else:
+ self.regs.RX_EN.val = 1 << (self.ch//2)
+
+ def disable(self):
+ if self.tx:
+ self.regs.TX_EN_CLR.val = 1 << (self.ch//2)
+ else:
+ self.regs.RX_EN_CLR.val = 1 << (self.ch//2)
+
+ @property
+ def buswidth(self):
+ self.regs.CHAN_BUSWIDTH[self.ch].reg.WORD
+
+ @buswidth.setter
+ def buswidth(self, wordsize):
+ return self.regs.CHAN_BUSWIDTH[self.ch].set(WORD=wordsize)
+
+ @property
+ def framesize(self):
+ self.regs.CHAN_BUSWIDTH[self.ch].reg.FRAME
+
+ @framesize.setter
+ def framesize(self, framesize):
+ return self.regs.CHAN_BUSWIDTH[self.ch].set(FRAME=framesize)
+
+ @property
+ def burstsize(self):
+ return self.regs.CHAN_BURSTSIZE[self.ch].val
+
+ @burstsize.setter
+ def burstsize(self, size):
+ self.regs.CHAN_BURSTSIZE[self.ch].val = size
+
+ @property
+ def sram_carveout(self):
+ reg = self.regs.CHAN_SRAM_CARVEOUT[self.ch].reg
+ return (reg.BASE, reg.SIZE)
+
+ @sram_carveout.setter
+ def sram_carveout(self, carveout):
+ base, size = carveout
+ self.regs.CHAN_SRAM_CARVEOUT[self.ch].reg = \
+ R_CARVEOUT(BASE=base, SIZE=size)
+
+ @property
+ def DESC_WRITE(self):
+ if self.tx:
+ return self.regs.TX_DESC_WRITE[self.ch//2]
+ else:
+ return self.regs.RX_DESC_WRITE[self.ch//2]
+
+ @property
+ def REPORT_READ(self):
+ if self.tx:
+ return self.regs.TX_REPORT_READ[self.ch//2]
+ else:
+ return self.regs.RX_REPORT_READ[self.ch//2]
+
+ def can_submit(self):
+ return not self.regs.CHAN_DESC_RING[self.ch].reg.FULL
+
+ def submit_desc(self, desc):
+ if self.regs.CHAN_DESC_RING[self.ch].reg.FULL:
+ raise Exception(f"ch{self.ch} descriptor ring full")
+
+ if self.p.debug:
+ print(f"admac: submitting (ch{self.ch}): {desc}", file=sys.stderr)
+
+ for piece in desc.ser():
+ self.DESC_WRITE.val = piece
+
+ self._submitted[desc.flags.DESC_ID] = desc
+
+ def submit(self, data=None, buflen=None, **kwargs):
+ if self.tx:
+ assert data is not None
+ buflen = len(data)
+ else:
+ assert buflen is not None
+
+ iova = self.p.get_buffer(buflen)
+ if self.tx:
+ self.p.iowrite(iova, data)
+ self.submit_desc(ADMACDescriptor(
+ iova, buflen, DESC_ID=self._desc_id, NOTIFY=1, **kwargs
+ ))
+ self._desc_id = (self._desc_id + 1) % 256
+
+ def read_reports(self):
+ data = bytearray()
+
+ while not self.regs.CHAN_REPORT_RING[self.ch].reg.EMPTY:
+ pieces = []
+ for _ in range(4):
+ pieces.append(self.REPORT_READ.val)
+ report = ADMACReport.deser(pieces)
+
+ if report.flags.DESC_ID in self._submitted:
+ desc = self._submitted[report.flags.DESC_ID]
+ else:
+ print(f"admac: stray report (ch{self.ch}): {report}", file=sys.stderr)
+ desc = None
+
+ if self.rx and desc and self.p.dart:
+ data.extend(self.p.ioread(desc.addr, desc.length))
+
+ if self.p.debug:
+ if self._last_report and desc:
+ countval_delta = report.countval - self._last_report.countval
+ est_rate = 24e6*desc.length/countval_delta/4
+ est = f"(estimated rate: {est_rate:.2f} dwords/s)"
+ else:
+ est = ""
+
+ print(f"admac: picked up (ch{self.ch}): {report} {est}", file=sys.stderr)
+
+ self._last_report = report
+
+ return data if self.rx else None
+
+ @property
+ def status(self):
+ return self.regs.CHAN_STATUS[self.ch, 0].reg
+
+ def poll(self, wait=True):
+ while not (self.status.DESC_DONE or self.status.RING_ERR):
+ time.sleep(0.001)
+
+ if not wait:
+ break
+
+ self.regs.CHAN_STATUS[self.ch,0].reg = R_CHAN_STATUS(DESC_DONE=1)
+
+ if self.status.RING_ERR:
+ if self.p.debug:
+ print(f"STATUS={self.regs.CHAN_STATUS[self.ch,1].reg} " + \
+ f"REPORT_RING={self.regs.CHAN_DESC_RING[self.ch]} " + \
+ f"DESC_RING={self.regs.CHAN_REPORT_RING[self.ch]}",
+ file=sys.stderr)
+ self.regs.CHAN_DESC_RING[self.ch].set(ERR=1)
+ self.regs.CHAN_REPORT_RING[self.ch].set(ERR=1)
+
+ return self.read_reports()
+
+
+class ADMAC(Reloadable):
+ def __init__(self, u, devpath, dart=None, dart_stream=2,
+ reserved_size=4*1024*1024, debug=False):
+ self.u = u
+ self.p = u.proxy
+ self.debug = debug
+
+ if type(devpath) is str:
+ adt_node = u.adt[devpath]
+ # ADT's #dma-channels counts pairs of RX/TX channel, so multiply by two
+ self.nchans = adt_node._properties["#dma-channels"] * 2
+ self.base, _ = adt_node.get_reg(0)
+ else:
+ self.base = devpath
+ self.nchans = 26
+
+ self.regs = ADMACRegs(u, self.base)
+ self.dart, self.dart_stream = dart, dart_stream
+
+ if dart is not None:
+ resmem_phys = u.heap.memalign(128*1024, reserved_size)
+ self.resmem_iova = self.dart.iomap(dart_stream, resmem_phys, reserved_size)
+ self.resmem_size = reserved_size
+ self.resmem_pos = 0
+ self.dart.invalidate_streams(1 << dart_stream)
+
+ self.chans = [ADMACChannel(self, no) for no in range(self.nchans)]
+
+ def ioread(self, base, size):
+ assert self.dart is not None
+ return self.dart.ioread(self.dart_stream, base, size)
+
+ def iowrite(self, base, data):
+ assert self.dart is not None
+ self.dart.iowrite(self.dart_stream, base, data)
+
+ def fill_canary(self):
+ ranges = self.dart.iotranslate(self.dart_stream,
+ self.resmem_iova, self.resmem_size)
+ assert len(ranges) == 1
+ start, size = ranges[0]
+ self.p.memset8(start, 0xba, size)
+
+ def get_buffer(self, size):
+ assert size < self.resmem_size
+
+ if self.resmem_pos + size > self.resmem_size:
+ self.resmem_pos = 0
+
+ bufptr = self.resmem_iova + self.resmem_pos
+ self.resmem_pos += size
+ return bufptr
diff --git a/tools/proxyclient/m1n1/hw/aes.py b/tools/proxyclient/m1n1/hw/aes.py
new file mode 100644
index 0000000..7e09335
--- /dev/null
+++ b/tools/proxyclient/m1n1/hw/aes.py
@@ -0,0 +1,110 @@
+# SPDX-License-Identifier: MIT
+from ..utils import *
+from enum import IntEnum
+from .dart import DART, DARTRegs
+import struct
+from enum import IntEnum
+
+
+class AES_OPCODE(IntEnum):
+ # 0 triggers an invalid command interrupt
+ SET_KEY = 1
+ SET_IV = 2
+ # 0x03 seems to take three additional argument, function unknown
+ # 0x04 seems to take one additional argument, function unknown
+ CRYPT = 5
+ GET_IV = 6
+ # 0x07 takes one additional argument, function unknown
+ BARRIER = 8 # can be used to trigger an IRQ but possibly also does more
+ # > 8 trigger an invalid command interrupt
+
+
+class AES_SET_KEY_LEN(IntEnum):
+ AES128 = 0
+ AES192 = 1
+ AES256 = 2
+
+
+class AES_SET_KEY_BLOCK_MODE(IntEnum):
+ ECB = 0
+ CBC = 1
+ CTR = 2
+
+
+class AESCommandBase(Register32):
+ OPCODE = 31, 28, AES_OPCODE
+
+
+class AESHwKey(IntEnum):
+ SOFTWARE = 0
+ UID = 1 # unique key for each chip
+ GID0 = 2 # (probably) globally unique key within a chip family
+ GID1 = 3 # globally unique key within a chip family
+ # 4-7 are probably empty / reserved for future use
+
+
+class AESSetKeyCommand(AESCommandBase):
+ OPCODE = 31, 28, Constant(AES_OPCODE.SET_KEY)
+ SLOT = 27, 27
+ KEY_SELECT = 26, 24
+ KEYLEN = 23, 22, AES_SET_KEY_LEN
+ # setting bit 21 breaks the engine and sets two bits in the IRQ status
+ ENCRYPT = 20, 20
+ KEYGEN = 19, 18
+ BLOCK_MODE = 17, 16, AES_SET_KEY_BLOCK_MODE
+ # 15, 0 doesn't seem to have any effect
+
+
+class AESCryptCommand(AESCommandBase):
+ OPCODE = 31, 28, Constant(AES_OPCODE.CRYPT)
+ KEY_SLOT = 27, 27
+ IV_SLOT = 26, 25
+ LEN = 24, 0
+
+
+class AESBarrierCommand(AESCommandBase):
+ OPCODE = 31, 28, Constant(AES_OPCODE.BARRIER)
+ IRQ = 27, 27
+
+
+class AESGetIVCommand(AESCommandBase):
+ OPCODE = 31, 28, Constant(AES_OPCODE.GET_IV)
+
+
+class AESSetIVCommand(AESCommandBase):
+ OPCODE = 31, 28, Constant(AES_OPCODE.SET_IV)
+ SLOT = 27, 26
+
+
+class AESIrqReg(Register32):
+ KEY1_EMPTY = 17, 17
+ KEY1_INVALID = 13, 13
+ KEY0_EMPTY = 11, 11
+ KEY0_INVALID = 7, 7
+ FLAG = 5, 5
+ UNKNOWN_COMMAND = 2, 2
+ FIFO_OVERFLOW = 1, 1
+
+
+class AESControlReg(Register32):
+ START = 0, 0
+ STOP = 1, 1
+ CLEAR_FIFO = 2, 2
+ # TOOD: not convinced about RESET anymore, I remember this un-broke the engine once but I can't reproduce that anymore
+ RESET = 3, 3
+
+
+class AESFifoStatusReg(Register32):
+ FIFO_WRITE_PTR = 31, 24
+ FIFO_READ_PTR = 23, 16
+ FIFO_LEVEL = 15, 8
+ FIFO_FULL = 2, 2
+ FIFO_EMPTY = 1, 1
+
+
+class AESRegs(RegMap):
+ R_CONTROL = 0x08, AESControlReg
+ R_IRQ_STATUS = 0x18, AESIrqReg
+ R_IRQ_ENABLE = 0x1C, AESIrqReg
+ R_FIFO_STATUS = 0x24, AESFifoStatusReg
+ R_CMD_FIFO = 0x200, Register32
diff --git a/tools/proxyclient/m1n1/hw/agx.py b/tools/proxyclient/m1n1/hw/agx.py
new file mode 100644
index 0000000..e5c2daa
--- /dev/null
+++ b/tools/proxyclient/m1n1/hw/agx.py
@@ -0,0 +1,111 @@
+# SPDX-License-Identifier: MIT
+from ..utils import *
+from enum import IntEnum
+
+__all__ = ["SGXRegs", "SGXInfoRegs", "agx_decode_unit", "R_FAULT_INFO"]
+
+class FAULT_REASON(IntEnum):
+ INVALID = 0
+ AF_FAULT = 1
+ WRITE_ONLY = 2
+ READ_ONLY = 3
+ NO_ACCESS = 4
+ UNK = 5
+
+class R_FAULT_INFO(Register64):
+ ADDR = 63, 24
+ WRITE = 23
+ CONTEXT = 22, 17
+ UNIT = 16, 9
+ UNK_8 = 8
+ REASON = 3, 1, FAULT_REASON
+ FAULTED = 0
+
+class SGXRegs(RegMap):
+ FAULT_INFO = 0x17030, R_FAULT_INFO
+
+class SGXInfoRegs(RegMap):
+ CORE_MASK_0 = 0x1500, Register32,
+ CORE_MASK_1 = 0x1514, Register32,
+
+ ID_00 = 0x4000, Register32,
+ ID_04 = 0x4004, Register32,
+ ID_08 = 0x4008, Register32,
+ ID_0c = 0x400c, Register32,
+ ID_10 = 0x4010, Register32,
+ ID_14 = 0x4014, Register32,
+ ID_18 = 0x4018, Register32,
+ ID_1c = 0x401c, Register32,
+
+ ID_8024 = 0x8024, Register32,
+
+class UNIT_00(IntEnum):
+ DCMPn = 0x00
+ UL1Cn = 0x01
+ CMPn = 0x02
+ GSL1_n = 0x03
+ IAPn = 0x04
+ VCEn = 0x05
+ TEn = 0x06
+ RASn = 0x07
+ VDMn = 0x08
+ PPPn = 0x09
+ IPFn = 0x0a
+ IPF_CPFn = 0x0b
+ VFn = 0x0c
+ VF_CPFn = 0x0d
+ ZLSn = 0x0e
+
+class UNIT_A0(IntEnum):
+ dPM = 0xa1
+ dCDM_KS0 = 0xa2
+ dCDM_KS1 = 0xa3
+ dCDM_KS2 = 0xa4
+ dIPP = 0xa5
+ dIPP_CS = 0xa6
+ dVDM_CSD = 0xa7
+ dVDM_SSD = 0xa8
+ dVDM_ILF = 0xa9
+ dVDM_ILD = 0xaa
+ dRDE0 = 0xab
+ dRDE1 = 0xac
+ FC = 0xad
+ GSL2 = 0xae
+
+ GL2CC_META0 = 0xb0
+ GL2CC_META1 = 0xb1
+ GL2CC_META2 = 0xb2
+ GL2CC_META3 = 0xb3
+ GL2CC_META4 = 0xb4
+ GL2CC_META5 = 0xb5
+ GL2CC_META6 = 0xb6
+ GL2CC_META7 = 0xb7
+ GL2CC_MB = 0xb8
+
+class UNIT_E0(IntEnum):
+ gPM_SPn = 0xe0
+ gVDM_CSD_SPn = 0xe1
+ gVDM_SSD_SPn = 0xe2
+ gVDM_ILF_SPn = 0xe3
+ gVDM_TFP_SPn = 0xe4
+ gVDM_MMB_SPn = 0xe5
+ gCDM_CS_SPn_KS0 = 0xe6
+ gCDM_CS_SPn_KS1 = 0xe7
+ gCDM_CS_SPn_KS2 = 0xe8
+ gCDM_SPn_KS0 = 0xe9
+ gCDM_SPn_KS1 = 0xea
+ gCDM_SPn_KS2 = 0xeb
+ gIPP_SPn = 0xec
+ gIPP_CS_SPn = 0xed
+ gRDE0_SPn = 0xee
+ gRDE1_SPn = 0xef
+
+def agx_decode_unit(v):
+ if v < 0xa0:
+ group = v >> 4
+ return UNIT_00(v & 0x0f).name.replace("n", str(group))
+ elif v < 0xe0:
+ return UNIT_A0(v).name
+ else:
+ group = (v >> 4) & 1
+ return UNIT_E0(v & 0xef).name.replace("n", str(group))
diff --git a/tools/proxyclient/m1n1/hw/asc.py b/tools/proxyclient/m1n1/hw/asc.py
new file mode 100644
index 0000000..f0923f8
--- /dev/null
+++ b/tools/proxyclient/m1n1/hw/asc.py
@@ -0,0 +1,121 @@
+# SPDX-License-Identifier: MIT
+from ..utils import *
+import time
+
+class R_MBOX_CTRL(Register32):
+ FIFOCNT = 23, 20
+ OVERFLOW = 18
+ EMPTY = 17
+ FULL = 16
+ RPTR = 15, 12
+ WPTR = 11, 8
+ ENABLE = 0
+
+class R_CPU_CONTROL(Register32):
+ RUN = 4
+
+class R_CPU_STATUS(Register32):
+ IDLE = 5
+ FIQ_NOT_PEND = 3 # guess
+ IRQ_NOT_PEND = 2 # guess
+ STOPPED = 1
+ RUNNING = 0
+
+class R_INBOX1(Register64):
+ EP = 7, 0
+
+class R_OUTBOX1(Register64):
+ OUTCNT = 56, 52
+ INCNT = 51, 48
+ OUTPTR = 47, 44
+ INPTR = 43, 40
+ EP = 7, 0
+
+class ASCRegs(RegMap):
+ CPU_CONTROL = 0x0044, R_CPU_CONTROL
+ CPU_STATUS = 0x0048, R_CPU_STATUS
+
+ INBOX_CTRL = 0x8110, R_MBOX_CTRL
+ OUTBOX_CTRL = 0x8114, R_MBOX_CTRL
+ INBOX0 = 0x8800, Register64
+ INBOX1 = 0x8808, R_INBOX1
+ OUTBOX0 = 0x8830, Register64
+ OUTBOX1 = 0x8838, R_OUTBOX1
+
+class ASC:
+ def __init__(self, u, asc_base):
+ self.u = u
+ self.p = u.proxy
+ self.iface = u.iface
+ self.asc = ASCRegs(u, asc_base)
+ self.verbose = 0
+ self.epmap = {}
+
+ def recv(self):
+ if self.asc.OUTBOX_CTRL.reg.EMPTY:
+ return None, None
+
+ msg0 = self.asc.OUTBOX0.val
+ msg1 = R_INBOX1(self.asc.OUTBOX1.val)
+ if self.verbose >= 3:
+ print(f"< {msg1.EP:02x}:{msg0:#x}")
+ return msg0, msg1
+
+ def send(self, msg0, msg1):
+ self.asc.INBOX0.val = msg0
+ self.asc.INBOX1.val = msg1
+
+ if self.verbose >= 3:
+ if isinstance(msg0, Register):
+ print(f"> {msg1.EP:02x}:{msg0}")
+ else:
+ print(f"> {msg1.EP:02x}:{msg0:#x}")
+
+ while self.asc.INBOX_CTRL.reg.FULL:
+ pass
+
+ def is_running(self):
+ return not self.asc.CPU_STATUS.reg.STOPPED
+
+ def boot(self):
+ self.asc.CPU_CONTROL.set(RUN=1)
+
+ def shutdown(self):
+ self.asc.CPU_CONTROL.set(RUN=0)
+
+ def add_ep(self, idx, ep):
+ self.epmap[idx] = ep
+ setattr(self, ep.SHORT, ep)
+
+ def has_messages(self):
+ return not self.asc.OUTBOX_CTRL.reg.EMPTY
+
+ def work_pending(self):
+ while self.has_messages():
+ self.work()
+
+ def work(self):
+ if self.asc.OUTBOX_CTRL.reg.EMPTY:
+ return True
+
+ msg0, msg1 = self.recv()
+
+ handled = False
+
+ ep = self.epmap.get(msg1.EP, None)
+ if ep:
+ handled = ep.handle_msg(msg0, msg1)
+
+ if not handled:
+ print(f"unknown message: {msg0:#16x} / {msg1}")
+
+ return handled
+
+ def work_forever(self):
+ while self.work():
+ pass
+
+ def work_for(self, timeout):
+ deadline = time.time() + timeout
+ while time.time() < deadline:
+ self.work()
diff --git a/tools/proxyclient/m1n1/hw/atc.py b/tools/proxyclient/m1n1/hw/atc.py
new file mode 100644
index 0000000..7ae92c0
--- /dev/null
+++ b/tools/proxyclient/m1n1/hw/atc.py
@@ -0,0 +1,455 @@
+# SPDX-License-Identifier: MIT
+
+from enum import IntEnum
+from m1n1.utils import *
+
+
+class R_USB2PHY_USBCTL(Register32):
+ MODE_HOST = 1
+ MODE_ISOLATION = 2
+
+
+class R_USB2PHY_CTL(Register32):
+ RESET = 0
+ PORT_RESET = 1
+ APB_RESETN = 2
+ SIDDQ = 3
+
+
+class R_USB2PHY_SIG(Register32):
+ VBUSDET_FORCE_VAL = 0
+ VBUSDET_FORCE_EN = 1
+ VBUSVLDEXT_FORCE_VAL = 2
+ VBUSVLDEXT_FORCE_EN = 3
+ MODE_HOST = 19, 12
+
+
+class R_USB2PHY_MISCTUNE(Register32):
+ APBCLK_GATE_OFF = 29
+ REFCLK_GATE_OFF = 30
+
+
+class Usb2PhyRegs(RegMap):
+ USB2PHY_USBCTL = 0x00, R_USB2PHY_USBCTL
+ USB2PHY_CTL = 0x04, R_USB2PHY_CTL
+ USB2PHY_SIG = 0x08, R_USB2PHY_SIG
+ USB2PHY_MISCTUNE = 0x1C, R_USB2PHY_MISCTUNE
+
+
+class R_AUSPLL_DCO_EFUSE_SPARE(Register32):
+ RODCO_ENCAP_EFUSE = 10, 9
+ RODCO_BIAS_ADJUST_EFUSE = 14, 12
+
+
+class R_AUSPLL_FRACN_CAN(Register32):
+ DLL_START_CAPCODE = 18, 17
+
+
+class R_AUSPLL_FSM_CTRL(Register32):
+ APBREQ_OVSEL = 21, 13
+
+
+class R_AUSPLL_CMD_OVERRIDE(Register32):
+ APB_OVERRIDE = 28
+
+
+class R_AUSPLL_CLKOUT_DTC_VREG(Register32):
+ DTC_VREG_ADJUST = 16, 14
+
+
+class R_AUS_COMMON_SHIM_BLK_VREG(Register32):
+ VREG_TRIM = 6, 2
+
+
+class R_CIO3PLL_CLK_CTRL(Register32):
+ PCLK_EN = 1
+ REFCLK_EN = 5
+
+
+class R_CIO3PLL_DCO_NCTRL(Register32):
+ DCO_COARSEBIN_EFUSE0 = 6, 0
+ DCO_COARSEBIN_EFUSE1 = 23, 17
+
+
+class R_CIO3PLL_FRACN_CAN(Register32):
+ DLL_CAL_START_CAPCODE = 18, 17
+
+
+class R_CIO3PLL_DTC_VREG(Register32):
+ DTC_VREG_ADJUST = 16, 14
+
+
+class E_ACIOPHY_CROSSBAR_PROTOCOL(IntEnum):
+ USB4 = 0
+ USB3 = 5
+ USB3_DP = 8
+ DP = 10
+
+
+class R_ACIOPHY_CROSSBAR(Register32):
+ PROTOCOL_SWAPPED = 0
+ PROTOCOL = 4, 1, E_ACIOPHY_CROSSBAR_PROTOCOL
+ DPMODE = 17, 5
+
+
+class E_ACIOPHY_LANE_MODE(IntEnum):
+ USB4 = 0
+ USB3 = 1
+ DP = 2
+ OFF = 3
+
+
+class R_ACIOPHY_LANE_MODE(Register32):
+ RX0 = 2, 0, E_ACIOPHY_LANE_MODE
+ TX0 = 5, 3, E_ACIOPHY_LANE_MODE
+ RX1 = 8, 6, E_ACIOPHY_LANE_MODE
+ TX1 = 11, 9, E_ACIOPHY_LANE_MODE
+
+
+class R_ATCPHY_POWER(Register32):
+ SLEEP_SMALL = 0
+ SLEEP_BIG = 1
+ CLAMP_EN = 2
+ APB_RESET_N = 3
+ PHY_RESET_N = 4
+
+
+class R_ATCPHY_MISC(Register32):
+ RESET_N = 0
+ LANE_SWAP = 2
+
+
+class R_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG0(Register32):
+ PMA_TXA_BYTECLK_RESET_SYNC_EN_OV = 2
+ PMA_TXA_BYTECLK_RESET_SYNC_EN = 3
+ PMA_TXA_BYTECLK_RESET_SYNC_CLR_OV = 4
+ PMA_TXA_BYTECLK_RESET_SYNC_CLR = 5
+ PMA_TXA_BYTECLK_RESET_SYNC_SEL_OV = 6
+
+
+class R_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG1(Register32):
+ PMA_TXA_DIV2_EN_OV = 8
+ PMA_TXA_DIV2_EN = 9
+ PMA_TXA_DIV2_RESET_OV = 10
+ PMA_TXA_DIV2_RESET = 11
+ PMA_TXA_CLK_EN_OV = 22
+ PMA_TXA_CLK_EN = 23
+
+
+class R_AUSPMA_TX_SHM_TXA_IMP_REG0(Register32):
+ PMA_TXA_CAL_CTRL_OV = 0
+ PMA_TXA_CAL_CTRL = 18, 1
+ PMA_TXA_CAL_CTRL_BASE_OV = 19
+ PMA_TXA_CAL_CTRL_BASE = 23, 20
+ PMA_TXA_HIZ_OV = 29
+ PMA_TXA_HIZ = 30
+
+
+class R_AUSPMA_TX_SHM_TXA_IMP_REG2(Register32):
+ PMA_TXA_MARGIN_OV = 0
+ PMA_TXA_MARGIN = 18, 1
+ PMA_TXA_MARGIN_2R_OV = 19
+ PMA_TXA_MARGIN_2R = 20
+
+
+class R_AUSPMA_TX_SHM_TXA_IMP_REG3(Register32):
+ PMA_TXA_MARGIN_POST_OV = 0
+ PMA_TXA_MARGIN_POST = 10, 1
+ PMA_TXA_MARGIN_POST_2R_OV = 11
+ PMA_TXA_MARGIN_POST_2R = 12
+ PMA_TXA_MARGIN_POST_4R_OV = 13
+ PMA_TXA_MARGIN_POST_4R = 14
+ PMA_TXA_MARGIN_PRE_OV = 15
+ PMA_TXA_MARGIN_PRE = 21, 16
+ PMA_TXA_MARGIN_PRE_2R_OV = 22
+ PMA_TXA_MARGIN_PRE_2R = 23
+ PMA_TXA_MARGIN_PRE_4R_OV = 24
+ PMA_TXA_MARGIN_PRE_4R = 25
+
+
+class R_AUSPMA_TX_SHM_TXA_LDOCLK(Register32):
+ PMA_TXA_LDOCLK_BYPASS_SML_OV = 8
+ PMA_TXA_LDOCLK_BYPASS_SML = 9
+ PMA_TXA_LDOCLK_BYPASS_BIG_OV = 10
+ PMA_TXA_LDOCLK_BYPASS_BIG = 11
+ PMA_TXA_LDOCLK_EN_SML_OV = 12
+ PMA_TXA_LDOCLK_EN_SML = 13
+ PMA_TXA_LDOCLK_EN_BIG_OV = 14
+ PMA_TXA_LDOCLK_EN_BIG = 15
+
+
+class R_AUSPMA_RX_SHM_TJ_RXA_CTLE_CTRL0(Register32):
+ PMA_RXA_TX_CLK_EN = 20
+ PMA_RXA_TX_CLK_EN_OV = 21
+
+
+class R_AUSPMA_RX_SHM_TJ_RXA_AFE_CTRL1(Register32):
+ CLK_LANE_RX_DIV20_SYNC_RESET_N_OV = 29
+ CLK_LANE_RX_DIV20_SYNC_RESET_N_VAL = 30
+
+
+class R_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL10(Register32):
+ PMA_RXA_DTVREG_ADJUST = 31, 27
+
+
+class R_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL11(Register32):
+ PMA_RXA_DTVREG_BIG_EN = 23
+ PMA_RXA_DTVREG_BIG_EN_OV = 24
+ PMA_RXA_DTVREG_SML_EN = 25
+ PMA_RXA_DTVREG_SML_EN_OV = 26
+
+
+class R_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL12(Register32):
+ PMA_RXA_TX_BYTECLK_RESET_SYNC_CLR = 22
+ PMA_RXA_TX_BYTECLK_RESET_SYNC_CLR_OV = 23
+ PMA_RXA_TX_BYTECLK_RESET_SYNC_EN = 24
+ PMA_RXA_TX_BYTECLK_RESET_SYNC_EN_OV = 25
+ PMA_RXA_TX_HRCLK_SEL = 28
+ PMA_RXA_TX_HRCLK_SEL_OV = 29
+ PMA_RXA_TX_PBIAS_EN = 30
+ PMA_RXA_TX_PBIAS_EN_OV = 31
+
+
+class R_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL13(Register32):
+ PMA_RXA_TX_PRE_EN = 0
+ PMA_RXA_TX_PRE_EN_OV = 1
+ PMA_RXA_TX_PST1_EN = 2
+ PMA_RXA_TX_PST1_EN_OV = 3
+ PMA_RXA_DTVREG_ADJUST_OV = 15
+
+
+class R_AUSPMA_RX_SHM_TJ_RXA_SAVOS_CTRL16(Register32):
+ PMA_RXA_RXTERM_EN = 21
+ PMA_RXA_RXTERM_EN_OV = 22
+ PMA_RXA_RXTERM_PULLUP_LEAK_EN = 23
+ PMA_RXA_RXTERM_PULLUP_LEAK_EN_OV = 24
+ PMA_RXA_TX_CAL_CODE = 29, 25
+ PMA_RXA_TX_CAL_CODE_OV = 30
+
+
+class R_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL17(Register32):
+ PMA_RXA_TX_MARGIN = 19, 15
+ PMA_RXA_TX_MARGIN_OV = 20
+ PMA_RXA_TX_MARGIN_LSB = 21
+ PMA_RXA_TX_MARGIN_LSB_OV = 22
+ PMA_RXA_TX_MARGIN_P1 = 26, 23
+ PMA_RXA_TX_MARGIN_P1_OV = 27
+ PMA_RXA_TX_MARGIN_P1_LSB = 29, 28
+ PMA_RXA_TX_MARGIN_P1_LSB_OV = 30
+
+
+class R_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18(Register32):
+ PMA_RXA_TX_P1_CODE = 3, 0
+ PMA_RXA_TX_P1_CODE_OV = 4
+ PMA_RXA_TX_P1_LSB_CODE = 6, 5
+ PMA_RXA_TX_P1_LSB_CODE_OV = 7
+ PMA_RXA_TX_MARGIN_PRE = 10, 8
+ PMA_RXA_TX_MARGIN_PRE_OV = 11
+ PMA_RXA_TX_MARGIN_PRE_LSB = 13, 12
+ PMA_RXA_TX_MARGIN_PRE_LSB_OV = 14
+ PMA_RXA_TX_PRE_LSB_CODE = 16, 15
+ PMA_RXA_TX_PRE_LSB_CODE_OV = 17
+ PMA_RXA_TX_PRE_CODE = 21, 18
+ PMA_RXA_TX_PRE_CODE_OV = 22
+
+
+class R_AUSPMA_RX_SHM_TJ_RXA_TERM_CTRL19(Register32):
+ PMA_RXA_TX_TEST_EN = 21
+ PMA_RXA_TX_TEST_EN_OV = 22
+ PMA_RXA_TX_EN = 23
+ PMA_RXA_TX_EN_OV = 24
+ PMA_RXA_TX_CLK_DLY_CTRL_TAPGEN = 27, 25
+ PMA_RXA_TX_CLK_DIV2_EN = 28
+ PMA_RXA_TX_CLK_DIV2_EN_OV = 29
+ PMA_RXA_TX_CLK_DIV2_RST = 30
+ PMA_RXA_TX_CLK_DIV2_RST_OV = 31
+
+
+class R_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22(Register32):
+ PMA_RXA_VREF_ADJUST_GRAY = 11, 7
+ PMA_RXA_VREF_ADJUST_GRAY_OV = 12
+ PMA_RXA_VREF_BIAS_SEL = 14, 13
+ PMA_RXA_VREF_BIAS_SEL_OV = 15
+ PMA_RXA_VREF_BOOST_EN = 16
+ PMA_RXA_VREF_BOOST_EN_OV = 17
+ PMA_RXA_VREF_EN = 18
+ PMA_RXA_VREF_EN_OV = 19
+ LPBKIN_RECOVERED_DATA = 29, 28
+ PMA_RXA_TEST_RXLPBKDT_EN = 30
+ PMA_RXA_TEST_RXLPBKDT_EN_OV = 31
+
+
+class R_AUSPMA_RX_TOP_TJ_CFG_RX_TXMODE(Register32):
+ RX_TXMODE = 0
+
+class R_ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0(Register32):
+ DP_PMA_BYTECLK_RESET = 0
+ DP_MAC_DIV20_CLK_SEL = 1
+ DPTXPHY_PMA_LANE_RESET_N = 2
+ DPTXPHY_PMA_LANE_RESET_N_OV = 3
+ DPTX_PCLK1_SELECT = 6, 4
+ DPTX_PCLK2_SELECT = 9, 7
+ DPRX_PCLK_SELECT = 12, 10
+ DPTX_PCLK1_ENABLE = 13
+ DPTX_PCLK2_ENABLE = 14
+ DPRX_PCLK_ENABLE = 15
+
+
+class AtcPhyRegs(RegMap):
+ ACIOPHY_CFG0 = 0x08, Register32
+ ACIOPHY_LANE_MODE = 0x48, R_ACIOPHY_LANE_MODE
+ ACIOPHY_CROSSBAR = 0x4C, R_ACIOPHY_CROSSBAR
+ ACIOPHY_BIST_EN = 0x84, Register32
+ ACIOPHY_BIST_OV = 0x8C, Register32
+ ACIOPHY_BIST_CFG0 = 0x90, Register32
+ ACIOPHY_BIST_STAT = 0x9C, Register32
+ ACIOPHY_BIST_RESET = 0xA8, Register32
+ ACIOPHY_BIST_CFG1 = 0xAC, Register32
+ ACIOPHY_SLEEP_CTRL = 0x1B0, Register32
+
+ AUS_COMMON_SHIM_BLK_VREG = 0x0A04, R_AUS_COMMON_SHIM_BLK_VREG
+
+ AUSPLL_FSM_CTRL = 0x1014, R_AUSPLL_FSM_CTRL
+ AUSPLL_CMD_OVERRIDE = 0x2000, R_AUSPLL_CMD_OVERRIDE
+ AUSPLL_CLKOUT_DTC_VREG = 0x2220, R_AUSPLL_CLKOUT_DTC_VREG
+ AUSPLL_DCO_EFUSE_SPARE = 0x222C, R_AUSPLL_DCO_EFUSE_SPARE
+ AUSPLL_FRACN_CAN = 0x22A4, R_AUSPLL_FRACN_CAN
+
+ CIO3PLL_CLK_CTRL = 0x2A00, R_CIO3PLL_CLK_CTRL
+ CIO3PLL_DTC_VREG = 0x2A20, R_CIO3PLL_DTC_VREG
+ CIO3PLL_DCO_NCTRL = 0x2A38, R_CIO3PLL_DCO_NCTRL
+ CIO3PLL_FRACN_CAN = 0x2AA4, R_CIO3PLL_FRACN_CAN
+
+ ATCPHY_POWER_CTRL = 0x20000, R_ATCPHY_POWER
+ ATCPHY_POWER_STAT = 0x20004, R_ATCPHY_POWER
+ ATCPHY_MISC = 0x20008, R_ATCPHY_MISC
+
+ ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0 = 0x7000, R_ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0
+ DPPHY_UNK_1028 = 0x1028, Register32
+ USB2PHY_AUX_CFG_BLK_AUX_POWER_DOWN_CONTROL_0 = 0x54000, Register32
+
+ FABRIC_TUNABLES = irange(0x45000, 0x1000 // 4, 4), Register32
+
+ LPDPTX_AUX_CFG_BLK = irange(0x50000, 0x1000 // 4, 4), Register32
+ LPDPTX_AUX_CFG_BLK_AUX_CTRL = 0x50000, Register32
+ LPDPTX_AUX_CFG_BLK_AUX_LDO_CTRL = 0x50008, Register32
+ LPDPTX_AUX_CFG_BLK_AUX_MARGIN = 0x5000c, Register32
+ LPDPTX_AUX_SHM_CFG_BLK_AUX_CTRL_REG0 = 0x50204, Register32
+ LPDPTX_AUX_SHM_CFG_BLK_AUX_CTRL_REG1 = 0x50208, Register32
+
+ LN0_AUSPMA_RX_TOP = irange(0x9000, 0x1000 // 4, 4), Register32
+ LN0_AUSPMA_RX_TOP_TJ_CFG_RX_TXMODE = 0x90F0, R_AUSPMA_RX_TOP_TJ_CFG_RX_TXMODE
+
+ LN0_AUSPMA_RX_EQ = irange(0xA000, 0x1000 // 4, 4), Register32
+
+ LN0_AUSPMA_RX_SHM = irange(0xB000, 0x1000 // 4, 4), Register32
+ LN0_AUSPMA_RX_SHM_TJ_RXA_CTLE_CTRL0 = 0xB000, R_AUSPMA_RX_SHM_TJ_RXA_CTLE_CTRL0
+ LN0_AUSPMA_RX_SHM_TJ_RXA_AFE_CTRL1 = 0xB004, R_AUSPMA_RX_SHM_TJ_RXA_AFE_CTRL1
+ LN0_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL2 = 0xB008, Register32
+ LN0_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL3 = 0xB00C, Register32
+ LN0_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL4 = 0xB010, Register32
+ LN0_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL5 = 0xB014, Register32
+ LN0_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL6 = 0xB018, Register32
+ LN0_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL7 = 0xB01C, Register32
+ LN0_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL8 = 0xB020, Register32
+ LN0_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL9 = 0xB024, Register32
+ LN0_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL10 = 0xB028, R_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL10
+ LN0_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL11 = 0xB02C, R_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL11
+ LN0_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL12 = 0xB030, R_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL12
+ LN0_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL13 = 0xB034, R_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL13
+ LN0_AUSPMA_RX_SHM_TJ_UNK_CTRL14A = 0xB038, Register32
+ LN0_AUSPMA_RX_SHM_TJ_UNK_CTRL14B = 0xB03C, Register32
+ LN0_AUSPMA_RX_SHM_TJ_UNK_CTRL15A = 0xB040, Register32
+ LN0_AUSPMA_RX_SHM_TJ_UNK_CTRL15B = 0xB044, Register32
+ LN0_AUSPMA_RX_SHM_TJ_RXA_SAVOS_CTRL16 = 0xB048, R_AUSPMA_RX_SHM_TJ_RXA_SAVOS_CTRL16
+ LN0_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL17 = 0xB04C, R_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL17
+ LN0_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18 = 0xB050, R_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18
+ LN0_AUSPMA_RX_SHM_TJ_RXA_TERM_CTRL19 = 0xB054, R_AUSPMA_RX_SHM_TJ_RXA_TERM_CTRL19
+ LN0_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL20 = 0xB058, Register32
+ LN0_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL21 = 0xB05C, Register32
+ LN0_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22 = 0xB060, R_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22
+
+ LN0_AUSPMA_TX_TOP = irange(0xC000, 0x1000 // 4, 4), Register32
+
+ LN0_AUSPMA_TX_SHM = irange(0xD000, 0x1000 // 4, 4), Register32
+ LN0_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG0 = 0xD000, R_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG0
+ LN0_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG1 = 0xD004, R_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG1
+ LN0_AUSPMA_TX_SHM_TXA_IMP_REG0 = 0xD008, R_AUSPMA_TX_SHM_TXA_IMP_REG0
+ LN0_AUSPMA_TX_SHM_TXA_IMP_REG1 = 0xD00C, Register32
+ LN0_AUSPMA_TX_SHM_TXA_IMP_REG2 = 0xD010, R_AUSPMA_TX_SHM_TXA_IMP_REG2
+ LN0_AUSPMA_TX_SHM_TXA_IMP_REG3 = 0xD014, R_AUSPMA_TX_SHM_TXA_IMP_REG3
+ LN0_AUSPMA_TX_SHM_TXA_UNK_REG0 = 0xD018, Register32
+ LN0_AUSPMA_TX_SHM_TXA_UNK_REG1 = 0xD01C, Register32
+ LN0_AUSPMA_TX_SHM_TXA_UNK_REG2 = 0xD020, Register32
+ LN0_AUSPMA_TX_SHM_TXA_LDOCLK = 0xD024, R_AUSPMA_TX_SHM_TXA_LDOCLK
+
+ LN1_AUSPMA_RX_TOP = irange(0x10000, 0x1000 // 4, 4), Register32
+ LN1_AUSPMA_RX_TOP_TJ_CFG_RX_TXMODE = 0x100F0, R_AUSPMA_RX_TOP_TJ_CFG_RX_TXMODE
+
+ LN1_AUSPMA_RX_EQ = irange(0x11000, 0x1000 // 4, 4), Register32
+
+ LN1_AUSPMA_RX_SHM = irange(0x12000, 0x1000 // 4, 4), Register32
+ LN1_AUSPMA_RX_SHM_TJ_RXA_CTLE_CTRL0 = 0x12000, R_AUSPMA_RX_SHM_TJ_RXA_CTLE_CTRL0
+ LN1_AUSPMA_RX_SHM_TJ_RXA_AFE_CTRL1 = 0x12004, R_AUSPMA_RX_SHM_TJ_RXA_AFE_CTRL1
+ LN1_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL2 = 0x12008, Register32
+ LN1_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL3 = 0x1200C, Register32
+ LN1_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL4 = 0x12010, Register32
+ LN1_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL5 = 0x12014, Register32
+ LN1_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL6 = 0x12018, Register32
+ LN1_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL7 = 0x1201C, Register32
+ LN1_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL8 = 0x12020, Register32
+ LN1_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL9 = 0x12024, Register32
+ LN1_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL10 = 0x12028, R_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL10
+ LN1_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL11 = 0x1202C, R_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL11
+ LN1_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL12 = 0x12030, R_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL12
+ LN1_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL13 = 0x12034, R_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL13
+ LN1_AUSPMA_RX_SHM_TJ_UNK_CTRL14A = 0x12038, Register32
+ LN1_AUSPMA_RX_SHM_TJ_UNK_CTRL14B = 0x1203C, Register32
+ LN1_AUSPMA_RX_SHM_TJ_UNK_CTRL15A = 0x12040, Register32
+ LN1_AUSPMA_RX_SHM_TJ_UNK_CTRL15B = 0x12044, Register32
+ LN1_AUSPMA_RX_SHM_TJ_RXA_SAVOS_CTRL16 = 0x12048, R_AUSPMA_RX_SHM_TJ_RXA_SAVOS_CTRL16
+ LN1_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL17 = 0x1204C, R_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL17
+ LN1_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18 = 0x12050, R_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18
+ LN1_AUSPMA_RX_SHM_TJ_RXA_TERM_CTRL19 = 0x12054, R_AUSPMA_RX_SHM_TJ_RXA_TERM_CTRL19
+ LN1_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL20 = 0x12058, Register32
+ LN1_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL21 = 0x1205C, Register32
+ LN1_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22 = 0x12060, R_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22
+
+ LN1_AUSPMA_TX_TOP = irange(0x13000, 0x1000 // 4, 4), Register32
+
+ LN1_AUSPMA_TX_SHM = irange(0x14000, 0x1000 // 4, 4), Register32
+ LN1_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG0 = 0x14000, R_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG0
+ LN1_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG1 = 0x14004, R_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG1
+ LN1_AUSPMA_TX_SHM_TXA_IMP_REG0 = 0x14008, R_AUSPMA_TX_SHM_TXA_IMP_REG0
+ LN1_AUSPMA_TX_SHM_TXA_IMP_REG1 = 0x1400C, Register32
+ LN1_AUSPMA_TX_SHM_TXA_IMP_REG2 = 0x14010, R_AUSPMA_TX_SHM_TXA_IMP_REG2
+ LN1_AUSPMA_TX_SHM_TXA_IMP_REG3 = 0x14014, R_AUSPMA_TX_SHM_TXA_IMP_REG3
+ LN1_AUSPMA_TX_SHM_TXA_UNK_REG0 = 0x14018, Register32
+ LN1_AUSPMA_TX_SHM_TXA_UNK_REG1 = 0x1401C, Register32
+ LN1_AUSPMA_TX_SHM_TXA_UNK_REG2 = 0x14020, Register32
+ LN1_AUSPMA_TX_SHM_TXA_LDOCLK = 0x14024, R_AUSPMA_TX_SHM_TXA_LDOCLK
+
+ ACIOPHY_TOP_TUNABLE_118 = 0x118, Register32
+ ACIOPHY_TOP_TUNABLE_11c = 0x11C, Register32
+ ACIOPHY_TOP_TUNABLE_124 = 0x124, Register32
+ ACIOPHY_TOP_TUNABLE_a00 = 0xA00, Register32
+ ACIOPHY_TOP_TUNABLE_808 = 0x808, Register32
+
+ AUSPLL_TOP_FREQ_DESC_0A = 0x2080, Register32
+ AUSPLL_TOP_FREQ_DESC_0B = 0x2084, Register32
+ AUSPLL_TOP_FREQ_DESC_0C = 0x2088, Register32
+
+ AUSPLL_TOP_TUNABLE_2094 = 0x2094, Register32
+ AUSPLL_TOP_TUNABLE_20a0 = 0x20A0, Register32
+ AUSPLL_TOP_TUNABLE_20ac = 0x20AC, Register32
+ AUSPLL_TOP_TUNABLE_20b8 = 0x20B8, Register32
+
+ CIO3PLL_TOP_TUNABLE_10 = 0x2810, Register32
+ CIO3PLL_TOP_TUNABLE_88 = 0x2888, Register32
+ CIO3PLL_TOP_TUNABLE_94 = 0x2894, Register32
+
+ CIO3PLL_CORE_TUNABLE_1c = 0x2A1C, Register32
+ CIO3PLL_CORE_TUNABLE_28 = 0x2A28, Register32
+ CIO3PLL_CORE_TUNABLE_9c = 0x2A9C, Register32
+
+ AUSPLL_CORE_TUNABLE_78 = 0x2278, Register32
diff --git a/tools/proxyclient/m1n1/hw/codecs/__init__.py b/tools/proxyclient/m1n1/hw/codecs/__init__.py
new file mode 100644
index 0000000..46bb5ee
--- /dev/null
+++ b/tools/proxyclient/m1n1/hw/codecs/__init__.py
@@ -0,0 +1,109 @@
+from m1n1.utils import RegMap, Register8
+from enum import IntEnum
+from .cs42l84 import *
+
+class E_PWR_MODE(IntEnum):
+ ACTIVE = 0
+ MUTE = 1
+ SHUTDOWN = 2
+
+class R_PWR_CTL(Register8):
+ ISNS_PD = 3
+ VSNS_PD = 2
+ MODE = 1, 0, E_PWR_MODE
+
+class R_PB_CFG0(Register8):
+ PDM_MAP = 7
+ PB_PDM_SRC = 6
+ PB_SRC = 5
+ AMP_LEVEL = 4, 0
+
+class R_PB_CFG2(Register8):
+ DVC_PCM = 7, 0
+
+class R_PB_CFG3(Register8):
+ DVC_PDM = 7, 0
+
+class E_RX_SCFG(IntEnum):
+ I2C_OFFSET = 0b00
+ LEFT = 0b01
+ RIGHT = 0b10
+ DOWNMIX = 0b11
+
+class E_RX_WLEN(IntEnum):
+ W_16BIT = 0b00
+ W_20BIT = 0b01
+ W_24BIT = 0b10
+ W_32BIT = 0b11
+
+class E_RX_SLEN(IntEnum):
+ W_16BIT = 0b00
+ W_24BIT = 0b01
+ W_32BIT = 0b10
+
+class R_TDM_CFG2(Register8):
+ RX_SCFG = 5, 4, E_RX_SCFG
+ RX_WLEN = 3, 2, E_RX_WLEN
+ RX_SLEN = 1, 0, E_RX_SLEN
+
+class R_TDM_CFG3(Register8):
+ RX_SLOT_R = 7, 4
+ RX_SLOT_L = 3, 0
+
+class TAS5770Regs(RegMap):
+ PWR_CTL = 0x002, R_PWR_CTL
+ PB_CFG0 = 0x003, R_PB_CFG0
+ PB_CFG2 = 0x005, R_PB_CFG2
+ PB_CFG3 = 0x006, R_PB_CFG3
+ TDM_CFG2 = 0x00c, R_TDM_CFG2
+ TDM_CFG3 = 0x00d, R_TDM_CFG3
+
+class R_MODE_CTRL(Register8):
+ BOP_SRC = 7
+ ISNS_PD = 3
+ VSNS_PD = 2
+ MODE = 1, 0, E_PWR_MODE
+
+class R_CHNL_0(Register8):
+ CDS_MODE = 7, 6
+ AMP_LEVEL = 5, 1
+
+class R_DVC(Register8):
+ DVC_LVL = 7, 0
+
+class R_INT_MASK0(Register8):
+ BOPM = 7
+ BOPIH = 6
+ LIMMA = 5
+ PBIP = 4
+ LIMA = 3
+ TDMCE = 2
+ OC = 1
+ OT = 0
+
+class R_INT_CLK_CFG(Register8):
+ CLK_ERR_PWR_EN = 7
+ DIS_CLK_HAT = 6
+ CLK_HALT_TIMER = 5, 3
+ IRQZ_CLR = 2
+ IRQZ_PIN_CFG = 1, 0
+
+class SN012776Regs(RegMap):
+ MODE_CTRL = 0x002, R_MODE_CTRL
+ CHNL_0 = 0x003, R_CHNL_0
+ DVC = 0x01a, R_DVC
+
+ INT_MASK0 = 0x03b, R_INT_MASK0
+ INT_MASK1 = 0x03c, Register8
+ INT_MASK2 = 0x040, Register8
+ INT_MASK3 = 0x041, Register8
+ INT_MASK4 = 0x03d, Register8
+
+ INT_LTCH0 = 0x049, R_INT_MASK0
+ INT_LTCH1 = 0x04a, Register8
+ INT_LTCH1_0 = 0x04b, Register8
+ INT_LTCH2 = 0x04f, Register8
+ INT_LTCH3 = 0x050, Register8
+ INT_LTCH4 = 0x051, Register8
+
+ INT_CLK_CFG = 0x05c, R_INT_CLK_CFG
diff --git a/tools/proxyclient/m1n1/hw/codecs/cs42l84.py b/tools/proxyclient/m1n1/hw/codecs/cs42l84.py
new file mode 100644
index 0000000..095b421
--- /dev/null
+++ b/tools/proxyclient/m1n1/hw/codecs/cs42l84.py
@@ -0,0 +1,365 @@
+from m1n1.utils import Register8, Register16, Register32, RegMap, irange
+from enum import IntEnum
+
+class R_IRQ_MASK1(Register8):
+ RING_PLUG = 0
+ RING_UNPLUG = 1
+ TIP_PLUG = 2
+ TIP_UNPLUG = 3
+
+class R_IRQ_MASK3(Register8):
+ HSDET_AUTO_DONE = 7
+
+class E_DCID_GND_SEL(IntEnum):
+ NONE = 0
+ HS3 = 1
+ HS4 = 2
+
+class E_DCID_Z_RANGE(IntEnum):
+ NONE = 0
+ UNK2 = 2
+ UNK3 = 3
+
+class R_DCID_CTRL1(Register8):
+ Z_RANGE = 2, 0, E_DCID_Z_RANGE
+
+class R_DCID_CTRL2(Register8):
+ GND_SEL = 6, 4, E_DCID_GND_SEL
+
+class R_DCID_CTRL3(Register8):
+ START = 0
+
+class R_DCID_STATUS(Register32):
+ OVERALL = 9, 0
+ DONE = 10
+ U = 20, 11
+ D = 30, 21
+
+class E_DEBOUNCE_TIME(IntEnum):
+ T_0MS = 0b000
+ T_125MS = 0b001
+ T_250MS = 0b010
+ T_500MS = 0b011
+ T_750MS = 0b100
+ T_1S = 0b101
+
+class R_TR_SENSE_CTRL(Register8):
+ INV = 7
+ UNK1 = 6
+ FALLTIME = 5, 3, E_DEBOUNCE_TIME
+ RISETIME = 2, 0, E_DEBOUNCE_TIME
+
+class R_TR_SENSE_STATUS(Register8):
+ RING_PLUG = 0
+ RING_UNPLUG = 1
+ TIP_PLUG = 2
+ TIP_UNPLUG = 3
+
+class R_HS_DET_STATUS2(Register8):
+ HS_TRUE = 1
+ SHORT_TRUE = 0
+
+class R_MSM_BLOCK_EN1(Register8):
+ pass
+
+class R_MSM_BLOCK_EN2(Register8):
+ ASP_EN = 6
+ BUS_EN = 5
+ DAC_EN = 4
+ ADC_EN = 3
+
+class R_MSM_BLOCK_EN3(Register8):
+ TR_SENSE_EN = 3
+ DCID_EN = 4
+
+class R_HS_CLAMP_DISABLE(Register8):
+ HS_CLAMP_DISABLE = 0
+
+class E_SAMP_RATE(IntEnum):
+ S_16KHZ = 1
+ S_24KHZ = 2
+ S_32KHZ = 3
+ S_48KHZ = 4
+ S_96KHZ = 5
+ S_192KHZ = 6
+ S_22K05HZ = 10
+ S_44K1HZ = 12
+ S_88K2HZ = 13
+ S_176K4HZ = 14
+
+class E_MCLK_SRC(IntEnum):
+ RCO = 0b00
+ MCLK_PIN = 0b01
+ BCLK = 0b10
+ PLL = 0b11
+
+class E_MCLK_FREQ(IntEnum):
+ F_12MHZ = 0b00
+ F_24MHZ = 0b01
+ F_12_288KHZ = 0b10
+ F_24_576KHZ = 0b11
+
+class R_CCM_CTRL1(Register8):
+ MCLK_SRC = 1, 0, E_MCLK_SRC
+ MCLK_FREQ = 3, 2, E_MCLK_FREQ
+
+class E_REFCLK_DIV(IntEnum):
+ DIV1 = 0b00
+ DIV2 = 0b01
+ DIV4 = 0b10
+ DIV8 = 0b11
+
+class R_CCM_CTRL3(Register8):
+ REFCLK_DIV = 2, 1, E_REFCLK_DIV
+ REFCLK_IS_MCLK = 0 # BLCK otherwise
+
+class R_CCM_CTRL4(Register8):
+ REFCLK_EN = 0
+
+class R_CCM_SAMP_RATE(Register8):
+ RATE = 7, 0, E_SAMP_RATE
+
+class E_PLL_MODE(IntEnum):
+ UNSUPP = 0b00
+ BYPASS_512 = 0b01
+ BYPASS_1024 = 0b10
+ BYPASS_BOTH = 0b11
+
+class R_PLL_CTRL(Register8):
+ MODE = 2, 1, E_PLL_MODE
+ EN = 0
+
+class E_WNF_CF(IntEnum):
+ F_UNK = 0b00
+ F_300HZ = 0b11
+
+class R_ADC_CTRL1(Register8):
+ PREAMP_GAIN = 7, 6
+ PGA_GAIN = 5, 0
+
+class R_ADC_CTRL4(Register8): # maybe
+ WNF_CF = 5, 4, E_WNF_CF
+ WNF_EN = 3
+
+class R_DAC_CTRL1(Register8):
+ UNMUTE = 0
+
+ HP_LOAD = 2 # maybe
+ UNK1 = 3
+ UNK2 = 4
+ UNK3 = 5
+ HIGH_V = 6
+
+class E_PULLDOWN_R(IntEnum):
+ NONE = 0x0
+ R_UNK8 = 0x8
+ R_1K1OHMS = 0xc
+
+class R_DAC_CTRL2(Register8):
+ PULLDOWN_R = 3, 0, E_PULLDOWN_R
+
+class R_HP_VOL_CTRL(Register8):
+ ZERO_CROSS = 1
+ SOFT = 0
+
+class E_BUS_SOURCE(IntEnum):
+ EMPTY = 0b0000
+ ADC = 0b0111
+ ASP_RX_CH1 = 0b1101
+ ASP_RX_CH2 = 0b1110
+
+class R_BUS_DAC_SRC(Register8):
+ CHB = 7, 4, E_BUS_SOURCE
+ CHA = 3, 0, E_BUS_SOURCE
+
+class R_BUS_ASP_TX_SRC(Register8):
+ CH2 = 7, 4, E_BUS_SOURCE
+ CH1 = 3, 0, E_BUS_SOURCE
+
+class E_HSBIAS_SENSE_TRIP(IntEnum):
+ C_12UA = 0b000
+ C_23UA = 0b001
+ C_41UA = 0b010
+ C_52UA = 0b011
+ C_64UA = 0b100
+ C_75UA = 0b101
+ C_93UA = 0b110
+ C_104UA = 0b111
+
+class R_HSBIAS_SC_AUTOCTL(Register8):
+ HSBIAS_SENSE_EN = 7
+ AUTO_HSBIAS_HIZ = 6
+ TIP_SENSE_EN = 5
+ SENSE_TRIP = 2, 0, E_HSBIAS_SENSE_TRIP
+
+class E_TIP_SENSE_CTRL(IntEnum):
+ DISABLED = 0b00
+ DIG_INPUT = 0b01
+ SHORT_DET = 0b11
+
+class R_TIP_SENSE_CTRL2(Register8):
+ CTRL = 7, 6, E_TIP_SENSE_CTRL
+ INV = 5
+
+class E_HSBIAS_DET_MODE(IntEnum):
+ DISABLED = 0b00
+ SHORT_DET = 0b01
+ NORMAL = 0b11
+
+class E_HSBIAS_CTRL(IntEnum):
+ HI_Z = 0b00
+ U_0V0 = 0b01
+ U_2V0 = 0b10
+ U_2V7 = 0b11
+
+class R_MISC_DET_CTRL(Register8):
+ UNK1 = 7
+ DETECT_MODE = 4, 3, E_HSBIAS_DET_MODE
+ HSBIAS_CTRL = 2, 1, E_HSBIAS_CTRL
+ PDN_MIC_LVL_DET = 0
+
+class E_S0_DEBOUNCE_TIME(IntEnum):
+ T_10MS = 0b000
+ T_20MS = 0b001
+ T_30MS = 0b010
+ T_40MS = 0b011
+ T_50MS = 0b100
+ T_60MS = 0b101
+ T_70MS = 0b110
+ T_80MS = 0b111
+
+class R_MIC_DET_CTRL2(Register8):
+ DEBOUNCE_TIME = 7, 5, E_S0_DEBOUNCE_TIME
+
+class R_MIC_DET_CTRL4(Register8):
+ LATCH_TO_VP = 1
+
+class R_HS_DET_CTRL2(Register8):
+ CTRL = 7, 6
+ SET = 5, 4
+ REF = 3
+ AUTO_TIME = 1, 0
+
+class R_HS_SWITCH_CTRL(Register8):
+ REF_HS3 = 7
+ REF_HS4 = 6
+ HSB_FILT_HS3 = 5
+ HSB_FILT_HS4 = 4
+ HSB_HS3 = 3
+ HSB_HS4 = 2
+ GNDHS_HS3 = 1
+ GNDHS_HS4 = 0
+
+class R_ASP_CTRL(Register8):
+ TDM_MODE = 2
+ BCLK_EN = 1
+
+class R_ASP_FSYNC_CTRL23(Register16):
+ BCLK_PERIOD = 12, 1
+
+class R_ASP_TX_HIZ_DLY_CTRL(Register8):
+ DRV_Z = 5, 4
+ HIZ_DELAY = 3, 2
+ FS = 1
+ UNK1 = 0
+
+class R_ASP_RX_EN(Register8):
+ CH2_EN = 1
+ CH1_EN = 0
+
+class R_ASP_CH_CTRL(Register32):
+ WIDTH = 23, 16
+ SLOT_START = 10, 1
+ EDGE = 0 # set for rising edge
+
+class CS42L84Regs(RegMap):
+ DEVID = irange(0x0, 5), Register8
+ FREEZE = 0x6, Register8
+
+ SW_RESET = 0x203, Register8
+
+ IRQ_STATUS1 = 0x400, R_IRQ_MASK1
+ IRQ_STATUS2 = 0x401, Register8
+ IRQ_STATUS3 = 0x402, R_IRQ_MASK3
+ PLL_LOCK_STATUS = 0x40e, Register8 # bit 0x10
+
+ IRQ_MASK1 = 0x418, R_IRQ_MASK1
+ IRQ_MASK2 = 0x419, Register8
+ IRQ_MASK3 = 0x41a, R_IRQ_MASK3
+
+ CCM_CTRL1 = 0x600, R_CCM_CTRL1
+ CCM_SAMP_RATE = 0x601, R_CCM_SAMP_RATE
+ CCM_CTRL3 = 0x602, R_CCM_CTRL3
+ CCM_CTRL4 = 0x603, R_CCM_CTRL4
+ CCM_ASP_CLK_CTRL = 0x608, Register8
+
+ PLL_CTRL = 0x800, R_PLL_CTRL
+ PLL_DIV_FRAC = irange(0x804, 3), Register8
+ PLL_DIV_INT = 0x807, Register8
+ PLL_DIVOUT = 0x808, Register8
+
+ DCID_CTRL1 = 0x1200, R_DCID_CTRL1
+ DCID_CTRL2 = 0x1201, R_DCID_CTRL2
+ DCID_CTRL3 = 0x1202, R_DCID_CTRL3
+ DCID_TRIM_OFFSET = 0x1207, Register8
+ DCID_TRIM_SLOPE = 0x120a, Register8
+
+ # R_pull = 1100 - (regval - 128)*2
+ DCID_PULLDOWN_TRIM = 0x120b, Register8
+ DCID_STATUS = 0x120c, R_DCID_STATUS
+
+ # tip/ring sense
+ TR_SENSE_CTRL1 = 0x1280, Register8
+ TR_SENSE_CTRL2 = 0x1281, Register8
+ RING_SENSE_CTRL = 0x1282, R_TR_SENSE_CTRL
+ TIP_SENSE_CTRL = 0x1283, R_TR_SENSE_CTRL
+ TR_SENSE_STATUS = 0x1288, R_TR_SENSE_STATUS
+
+ HSBIAS_SC_AUTOCTL = 0x1470, R_HSBIAS_SC_AUTOCTL
+ WAKE_CTRL = 0x1471, Register8
+ TIP_SENSE_CTRL2 = 0x1473, R_TIP_SENSE_CTRL2
+ MISC_DET_CTRL = 0x1474, R_MISC_DET_CTRL
+ MIC_DET_CTRL2 = 0x1478, R_MIC_DET_CTRL2
+ MIC_DET_CTRL4 = 0x1477, R_MIC_DET_CTRL4
+
+ HS_DET_STATUS1 = 0x147c, Register8
+ HS_DET_STATUS2 = 0x147d, R_HS_DET_STATUS2
+ HS_DET_IRQ_MASK = irange(0x1480, 2), Register8
+ HS_DET_IRQ_STATUS = irange(0x1484, 2), Register8
+
+ MSM_BLOCK_EN1 = 0x1800, R_MSM_BLOCK_EN1
+ MSM_BLOCK_EN2 = 0x1801, R_MSM_BLOCK_EN2
+ MSM_BLOCK_EN3 = 0x1802, R_MSM_BLOCK_EN3
+
+ HS_DET_CTRL1 = 0x1810, Register8
+ HS_DET_CTRL2 = 0x1811, R_HS_DET_CTRL2
+ HS_SWITCH_CTRL = 0x1812, R_HS_SWITCH_CTRL
+ HS_CLAMP_DISABLE = 0x1813, R_HS_CLAMP_DISABLE
+ ADC_CTRL1 = 0x2000, R_ADC_CTRL1
+ ADC_CTRL2 = 0x2001, Register8 # volume
+ ADC_CTRL3 = 0x2002, Register8
+ ADC_CTRL4 = 0x2003, R_ADC_CTRL4
+
+ DAC_CTRL1 = 0x3000, R_DAC_CTRL1
+ DAC_CTRL2 = 0x3001, R_DAC_CTRL2
+ DACA_VOL_LSB = 0x3004, Register8
+ DACA_VOL_MSB = 0x3005, Register8 # sign bit
+ DACB_VOL_LSB = 0x3006, Register8
+ DACB_VOL_MSB = 0x3007, Register8 # sign bit
+ HP_VOL_CTRL = 0x3020, R_HP_VOL_CTRL
+ HP_CLAMP_CTRL = 0x3123, Register8
+
+ BUS_ASP_TX_SRC = 0x4000, R_BUS_ASP_TX_SRC
+ BUS_DAC_SRC = 0x4001, R_BUS_DAC_SRC
+
+ ASP_CTRL = 0x5000, R_ASP_CTRL
+ ASP_FSYNC_CTRL23 = 0x5010, R_ASP_FSYNC_CTRL23
+ ASP_DATA_CTRL = 0x5018, R_ASP_TX_HIZ_DLY_CTRL
+
+ ASP_RX_EN = 0x5020, R_ASP_RX_EN
+ ASP_TX_EN = 0x5024, Register8
+
+ ASP_RX1_CTRL = 0x5028, R_ASP_CH_CTRL # 32bit
+ ASP_RX2_CTRL = 0x502c, R_ASP_CH_CTRL # 32bit
+ ASP_TX1_CTRL = 0x5068, R_ASP_CH_CTRL
+ ASP_TX2_CTRL = 0x506c, R_ASP_CH_CTRL
diff --git a/tools/proxyclient/m1n1/hw/dart.py b/tools/proxyclient/m1n1/hw/dart.py
new file mode 100644
index 0000000..cd3d8d7
--- /dev/null
+++ b/tools/proxyclient/m1n1/hw/dart.py
@@ -0,0 +1,102 @@
+# SPDX-License-Identifier: MIT
+
+import struct
+
+from enum import IntEnum
+from ..utils import *
+from ..malloc import Heap
+
+from .dart8020 import DART8020, DART8020Regs
+from .dart8110 import DART8110, DART8110Regs
+
+__all__ = ["DART"]
+
+class DART(Reloadable):
+ PAGE_BITS = 14
+ PAGE_SIZE = 1 << PAGE_BITS
+
+ def __init__(self, iface, regs, util=None, compat="dart,t8020", iova_range=(0x80000000, 0x90000000)):
+ self.iface = iface
+ self.iova_allocator = [Heap(iova_range[0], iova_range[1], self.PAGE_SIZE)
+ for i in range(16)]
+ if compat in ["dart,t8020", "dart,t6000"]:
+ self.dart = DART8020(iface, regs, util, compat)
+ elif compat in ["dart,t8110"]:
+ self.dart = DART8110(iface, regs, util)
+ else:
+ raise TypeError(compat)
+
+ @classmethod
+ def from_adt(cls, u, path, instance=0, **kwargs):
+ dart_addr = u.adt[path].get_reg(instance)[0]
+ compat = u.adt[path].compatible[0]
+ if compat in ["dart,t8020", "dart,t6000"]:
+ regs = DART8020Regs(u, dart_addr)
+ elif compat in ["dart,t8110"]:
+ regs = DART8110Regs(u, dart_addr)
+ return cls(u.iface, regs, u, compat, **kwargs)
+
+ def ioread(self, stream, base, size):
+ if size == 0:
+ return b""
+
+ ranges = self.iotranslate(stream, base, size)
+
+ iova = base
+ data = []
+ for addr, size in ranges:
+ if addr is None:
+ raise Exception(f"Unmapped page at iova {iova:#x}")
+ data.append(self.iface.readmem(addr, size))
+ iova += size
+
+ return b"".join(data)
+
+ def iowrite(self, stream, base, data):
+ if len(data) == 0:
+ return
+
+ ranges = self.iotranslate(stream, base, len(data))
+
+ iova = base
+ p = 0
+ for addr, size in ranges:
+ if addr is None:
+ raise Exception(f"Unmapped page at iova {iova:#x}")
+ self.iface.writemem(addr, data[p:p + size])
+ p += size
+ iova += size
+
+ def iomap(self, stream, addr, size):
+ iova = self.iova_allocator[stream].malloc(size)
+
+ self.iomap_at(stream, iova, addr, size)
+ return iova
+
+ def iomap_at(self, stream, iova, addr, size):
+ self.dart.iomap_at(stream, iova, addr, size)
+
+ def iotranslate(self, stream, start, size):
+ return self.dart.iotranslate(stream, start, size)
+
+ def initialize(self):
+ self.dart.initialize()
+
+ def show_error(self):
+ self.dart.show_error()
+
+ def invalidate_streams(self, streams=0xffffffff):
+ self.dart.invalidate_streams(streams)
+
+ def invalidate_cache(self):
+ self.dart.invalidate_cache()
+
+ def dump_device(self, idx):
+ self.dart.dump_device(idx)
+
+ def dump_all(self):
+ for i in range(16):
+ self.dump_device(i)
+
+ def dump_params(self):
+ self.dart.dump_params()
diff --git a/tools/proxyclient/m1n1/hw/dart8020.py b/tools/proxyclient/m1n1/hw/dart8020.py
new file mode 100644
index 0000000..9954d3c
--- /dev/null
+++ b/tools/proxyclient/m1n1/hw/dart8020.py
@@ -0,0 +1,381 @@
+# SPDX-License-Identifier: MIT
+
+import struct
+
+from enum import IntEnum
+from ..utils import *
+from ..malloc import Heap
+
+__all__ = ["DART8020Regs", "DART8020"]
+
+class R_ERROR(Register32):
+ FLAG = 31
+ STREAM = 27, 24
+ CODE = 23, 0
+ NO_DAPF_MATCH = 11
+ WRITE = 10
+ SUBPAGE_PROT = 7
+ PTE_READ_FAULT = 6
+ READ_FAULT = 4
+ WRITE_FAULT = 3
+ NO_PTE = 2
+ NO_PMD = 1
+ NO_TTBR = 0
+
+class R_STREAM_COMMAND(Register32):
+ INVALIDATE = 20
+ BUSY = 2
+
+class R_TCR(Register32):
+ BYPASS_DAPF = 12
+ BYPASS_DART = 8
+ TRANSLATE_ENABLE = 7
+
+class R_TTBR(Register32):
+ VALID = 31
+ ADDR = 30, 0
+
+class R_REMAP(Register32):
+ MAP3 = 31, 24
+ MAP2 = 23, 16
+ MAP1 = 15, 8
+ MAP0 = 7, 0
+
+class PTE_T8020(Register64):
+ SP_START = 63, 52
+ SP_END = 51, 40
+ OFFSET = 39, 14
+ SP_PROT_DIS = 1
+ VALID = 0
+
+class PTE_T6000(Register64):
+ SP_START = 63, 52
+ SP_END = 51, 40
+ OFFSET = 39, 10
+ SP_PROT_DIS = 1
+ VALID = 0
+
+class R_CONFIG(Register32):
+ LOCK = 15
+
+class R_DAPF_LOCK(Register32):
+ LOCK = 0
+
+class DART8020Regs(RegMap):
+ STREAM_COMMAND = 0x20, R_STREAM_COMMAND
+ STREAM_SELECT = 0x34, Register32
+ ERROR = 0x40, R_ERROR
+ ERROR_ADDR_LO = 0x50, Register32
+ ERROR_ADDR_HI = 0x54, Register32
+ CONFIG = 0x60, R_CONFIG
+ REMAP = irange(0x80, 4, 4), R_REMAP
+
+ DAPF_LOCK = 0xf0, R_DAPF_LOCK
+ UNK1 = 0xf8, Register32
+ ENABLED_STREAMS = 0xfc, Register32
+
+ TCR = irange(0x100, 16, 4), R_TCR
+ TTBR = (irange(0x200, 16, 16), range(0, 16, 4)), R_TTBR
+
+PTE_TYPES = {
+ "dart,t8020": PTE_T8020,
+ "dart,t6000": PTE_T6000,
+}
+
+class DART8020(Reloadable):
+ PAGE_BITS = 14
+ PAGE_SIZE = 1 << PAGE_BITS
+
+ L0_SIZE = 4 # TTBR count
+ L0_OFF = 36
+ L1_OFF = 25
+ L2_OFF = 14
+
+ IDX_BITS = 11
+ Lx_SIZE = (1 << IDX_BITS)
+ IDX_MASK = Lx_SIZE - 1
+
+ def __init__(self, iface, regs, util=None, compat="dart,t8020"):
+ self.iface = iface
+ self.regs = regs
+ self.u = util
+ self.pt_cache = {}
+ self.enabled_streams = regs.ENABLED_STREAMS.val
+ self.ptecls = PTE_TYPES[compat]
+
+ @classmethod
+ def from_adt(cls, u, path, instance=0, **kwargs):
+ dart_addr = u.adt[path].get_reg(instance)[0]
+ dart = cls(u.iface, dart_addr, u)
+ dart.ptecls = PTE_TYPES[u.adt[path].compatible[0]]
+ return dart
+
+ def iomap_at(self, stream, iova, addr, size):
+ if size == 0:
+ return
+
+ if not (self.enabled_streams & (1 << stream)):
+ self.enabled_streams |= (1 << stream)
+ self.regs.ENABLED_STREAMS.val |= self.enabled_streams
+
+ tcr = self.regs.TCR[stream].reg
+
+ if tcr.BYPASS_DART and not tcr.TRANSLATE_ENABLE:
+ raise Exception("Stream is bypassed in DART")
+
+ if tcr.BYPASS_DART or not tcr.TRANSLATE_ENABLE:
+ raise Exception(f"Unknown DART mode {tcr}")
+
+ if addr & (self.PAGE_SIZE - 1):
+ raise Exception(f"Unaligned PA {addr:#x}")
+
+ if iova & (self.PAGE_SIZE - 1):
+ raise Exception(f"Unaligned IOVA {iova:#x}")
+
+ start_page = align_down(iova, self.PAGE_SIZE)
+ end = iova + size
+ end_page = align_up(end, self.PAGE_SIZE)
+
+ dirty = set()
+
+ for page in range(start_page, end_page, self.PAGE_SIZE):
+ paddr = addr + page - start_page
+
+ l0 = page >> self.L0_OFF
+ assert l0 < self.L0_SIZE
+ ttbr = self.regs.TTBR[stream, l0].reg
+ if not ttbr.VALID:
+ l1addr = self.u.memalign(self.PAGE_SIZE, self.PAGE_SIZE)
+ self.pt_cache[l1addr] = [0] * self.Lx_SIZE
+ ttbr.VALID = 1
+ ttbr.ADDR = l1addr >> 12
+ self.regs.TTBR[stream, l0].reg = ttbr
+
+ cached, l1 = self.get_pt(ttbr.ADDR << 12)
+ l1idx = (page >> self.L1_OFF) & self.IDX_MASK
+ l1pte = self.ptecls(l1[l1idx])
+ if not l1pte.VALID:
+ l2addr = self.u.memalign(self.PAGE_SIZE, self.PAGE_SIZE)
+ self.pt_cache[l2addr] = [0] * self.Lx_SIZE
+ l1pte = self.ptecls(
+ OFFSET=l2addr >> self.PAGE_BITS, VALID=1, SP_PROT_DIS=1)
+ l1[l1idx] = l1pte.value
+ dirty.add(ttbr.ADDR << 12)
+ else:
+ l2addr = l1pte.OFFSET << self.PAGE_BITS
+
+ dirty.add(l1pte.OFFSET << self.PAGE_BITS)
+ cached, l2 = self.get_pt(l2addr)
+ l2idx = (page >> self.L2_OFF) & self.IDX_MASK
+ self.pt_cache[l2addr][l2idx] = self.ptecls(
+ SP_START=0, SP_END=0xfff,
+ OFFSET=paddr >> self.PAGE_BITS, VALID=1, SP_PROT_DIS=1).value
+
+ for page in dirty:
+ self.flush_pt(page)
+
+ def iotranslate(self, stream, start, size):
+ if size == 0:
+ return []
+
+ tcr = self.regs.TCR[stream].reg
+
+ if tcr.BYPASS_DART and not tcr.TRANSLATE_ENABLE:
+ return [(start, size)]
+
+ if tcr.BYPASS_DART or not tcr.TRANSLATE_ENABLE:
+ raise Exception(f"Unknown DART mode {tcr}")
+
+ start = start & 0xffffffff
+
+ start_page = align_down(start, self.PAGE_SIZE)
+ start_off = start - start_page
+ end = start + size
+ end_page = align_up(end, self.PAGE_SIZE)
+ end_size = end - (end_page - self.PAGE_SIZE)
+
+ pages = []
+
+ for page in range(start_page, end_page, self.PAGE_SIZE):
+ l0 = page >> self.L0_OFF
+ assert l0 < self.L0_SIZE
+ ttbr = self.regs.TTBR[stream, l0].reg
+ if not ttbr.VALID:
+ pages.append(None)
+ continue
+
+ cached, l1 = self.get_pt(ttbr.ADDR << 12)
+ l1pte = self.ptecls(l1[(page >> self.L1_OFF) & self.IDX_MASK])
+ if not l1pte.VALID and cached:
+ cached, l1 = self.get_pt(ttbr.ADDR << 12, uncached=True)
+ l1pte = self.ptecls(l1[(page >> self.L1_OFF) & self.IDX_MASK])
+ if not l1pte.VALID:
+ pages.append(None)
+ continue
+
+ cached, l2 = self.get_pt(l1pte.OFFSET << self.PAGE_BITS)
+ l2pte = self.ptecls(l2[(page >> self.L2_OFF) & self.IDX_MASK])
+ if not l2pte.VALID and cached:
+ cached, l2 = self.get_pt(l1pte.OFFSET << self.PAGE_BITS, uncached=True)
+ l2pte = self.ptecls(l2[(page >> self.L2_OFF) & self.IDX_MASK])
+ if not l2pte.VALID:
+ pages.append(None)
+ continue
+
+ pages.append(l2pte.OFFSET << self.PAGE_BITS)
+
+ ranges = []
+
+ for page in pages:
+ if not ranges:
+ ranges.append((page, self.PAGE_SIZE))
+ continue
+ laddr, lsize = ranges[-1]
+ if ((page is None and laddr is None) or
+ (page is not None and laddr == (page - lsize))):
+ ranges[-1] = laddr, lsize + self.PAGE_SIZE
+ else:
+ ranges.append((page, self.PAGE_SIZE))
+
+ ranges[-1] = (ranges[-1][0], ranges[-1][1] - self.PAGE_SIZE + end_size)
+
+ if start_off:
+ ranges[0] = (ranges[0][0] + start_off if ranges[0][0] else None,
+ ranges[0][1] - start_off)
+
+ return ranges
+
+ def get_pt(self, addr, uncached=False):
+ cached = True
+ if addr not in self.pt_cache or uncached:
+ cached = False
+ self.pt_cache[addr] = list(
+ struct.unpack(f"<{self.Lx_SIZE}Q", self.iface.readmem(addr, self.PAGE_SIZE)))
+
+ return cached, self.pt_cache[addr]
+
+ def flush_pt(self, addr):
+ assert addr in self.pt_cache
+ self.iface.writemem(addr, struct.pack(f"<{self.Lx_SIZE}Q", *self.pt_cache[addr]))
+
+ def initialize(self):
+ for i in range(15):
+ self.regs.TCR[i].reg = R_TCR(TRANSLATE_ENABLE=1)
+ self.regs.TCR[15].reg = R_TCR(BYPASS_DART=1)
+
+ for i in range(16):
+ for j in range(4):
+ self.regs.TTBR[i, j].reg = R_TTBR(VALID = 0)
+
+ self.regs.ERROR.val = 0xffffffff
+ self.regs.UNK1.val = 0
+ self.regs.ENABLED_STREAMS.val = 0
+ self.enabled_streams = 0
+
+ self.invalidate_streams()
+
+ def show_error(self):
+ if self.regs.ERROR.reg.FLAG:
+ print(f"ERROR: {self.regs.ERROR.reg!s}")
+ print(f"ADDR: {self.regs.ERROR_ADDR_HI.val:#x}:{self.regs.ERROR_ADDR_LO.val:#x}")
+ self.regs.ERROR.val = 0xffffffff
+
+ def invalidate_streams(self, streams=0xffffffff):
+ self.regs.STREAM_SELECT.val = streams
+ self.regs.STREAM_COMMAND.val = R_STREAM_COMMAND(INVALIDATE=1)
+ while self.regs.STREAM_COMMAND.reg.BUSY:
+ pass
+
+ def invalidate_cache(self):
+ self.pt_cache = {}
+
+ def dump_table2(self, base, l1_addr):
+
+ def print_block(base, pte, start, last):
+ pgcount = last - start
+ pte.OFFSET -= pgcount
+ print(" page (%4d): %08x ... %08x -> %016x [%d%d]" % (
+ start, base + start*0x4000, base + (start+1)*0x4000,
+ pte.OFFSET << self.PAGE_BITS, pte.SP_PROT_DIS, pte.VALID))
+ if start < last:
+ print(" ==> (%4d): ... %08x -> %016x size: %08x" % (
+ last, base + (last+1)*0x4000,
+ (pte.OFFSET + pgcount - 1) << self.PAGE_BITS, pgcount << self.PAGE_BITS))
+
+ cached, tbl = self.get_pt(l1_addr)
+
+ unmapped = False
+ start = 0
+ next_pte = self.ptecls(VALID=0)
+
+ for i, pte in enumerate(tbl):
+ pte = self.ptecls(pte)
+ if not pte.VALID:
+ if not unmapped:
+ if next_pte.VALID:
+ print_block(base, next_pte, start, i)
+ print(" ...")
+ unmapped = True
+ next_pte = pte
+ continue
+
+ unmapped = False
+
+ if int(pte) != int(next_pte):
+ if next_pte.VALID:
+ print_block(base, next_pte, start, i)
+ start = i
+
+ next_pte = pte
+ next_pte.OFFSET += 1
+
+ if next_pte.VALID:
+ print_block(base, next_pte, start, 2048)
+
+ def dump_table(self, base, l1_addr):
+ cached, tbl = self.get_pt(l1_addr)
+
+ unmapped = False
+ for i, pte in enumerate(tbl):
+ pte = self.ptecls(pte)
+ if not pte.VALID:
+ if not unmapped:
+ print(" ...")
+ unmapped = True
+ continue
+
+ unmapped = False
+
+ print(" table (%d): %08x ... %08x -> %016x [%d%d]" % (
+ i, base + i*0x2000000, base + (i+1)*0x2000000,
+ pte.OFFSET << self.PAGE_BITS, pte.SP_PROT_DIS, pte.VALID))
+ self.dump_table2(base + i*0x2000000, pte.OFFSET << self.PAGE_BITS)
+
+ def dump_ttbr(self, idx, ttbr):
+ if not ttbr.VALID:
+ return
+
+ l1_addr = (ttbr.ADDR) << 12
+ print(" TTBR%d: %09x" % (idx, l1_addr))
+
+ self.dump_table(0, l1_addr)
+
+ def dump_device(self, idx):
+ tcr = self.regs.TCR[idx].reg
+ ttbrs = self.regs.TTBR[idx, :]
+ print(f"dev {idx:02x}: TCR={tcr!s} TTBRs = [{', '.join(map(str, ttbrs))}]")
+
+ if tcr.TRANSLATE_ENABLE and tcr.BYPASS_DART:
+ print(" mode: INVALID")
+ elif tcr.TRANSLATE_ENABLE:
+ print(" mode: TRANSLATE")
+
+ for idx, ttbr in enumerate(ttbrs):
+ self.dump_ttbr(idx, ttbr.reg)
+ elif tcr.BYPASS_DART:
+ print(" mode: BYPASS")
+ else:
+ print(" mode: UNKNOWN")
+
+ def dump_params(self):
+ pass
diff --git a/tools/proxyclient/m1n1/hw/dart8110.py b/tools/proxyclient/m1n1/hw/dart8110.py
new file mode 100644
index 0000000..1655182
--- /dev/null
+++ b/tools/proxyclient/m1n1/hw/dart8110.py
@@ -0,0 +1,541 @@
+# SPDX-License-Identifier: MIT
+
+import struct
+
+from enum import IntEnum
+from ..utils import *
+from ..malloc import Heap
+
+__all__ = ["DART8110Regs", "DART8110"]
+
+class R_PARAMS_0(Register32):
+ CLIENT_PARTITIONS_SUPPORTED = 29
+ LOG2_PGSZ = 27, 24
+ LOG2_TE_COUNT = 22, 20
+ TLB_SET_COUNT = 11, 0
+
+class R_PARAMS_4(Register32):
+ LOG2_NUM_WAYS = 30, 28
+ NUM_ASCS = 25, 24
+ NUM_W_PORTS = 22, 20
+ NUM_R_PORTS = 18, 16
+ NUM_APFS = 15, 8
+ SUPPORT_STT_PREFETCH = 6
+ SUPPORT_TLB_PREFETCH = 5
+ SUPPORT_CTC_PREFETCH = 4
+ SUPPORT_HW_FLUSH = 3
+ SUPPORT_TZ_TAGGER = 2
+ SUPPORT_REG_LOCK = 1
+ SUPPORT_FULL_BYPASS = 0
+
+class R_PARAMS_8(Register32):
+ PA_WIDTH = 29, 24
+ VA_WIDTH = 21, 16
+ VERS_MAJ = 15, 8
+ VERS_MIN = 7, 0
+
+class R_PARAMS_C(Register32):
+ NUM_CLIENTS = 24, 16
+ NUM_SIDS = 8, 0
+
+class R_ERROR(Register32):
+ FLAG = 31
+ SMMU = 30
+ REGION_PROTECT = 29
+ WRITE_nREAD = 28
+ SID = 27, 20
+ SECONDARY = 19
+ FILL_REGION = 18
+ BPF_REJECT = 14
+ EXTERNAL = 13
+ STT_FLUSH = 12
+ STT_MISMATCH = 11
+ APF_REJECT = 10
+ DROP_PROTECT = 9
+ CTRR_WRITE_PROTECT = 8
+ AXI_ERROR = 7
+ AXI_DECODE = 6
+ READ_FAULT = 5
+ WRITE_FAULT = 4
+ NO_PTE = 3
+ NO_PMD = 2 # "STE"
+ NO_PGD = 1 # "CTE"
+ NO_TTBR = 0
+
+class R_TLB_OP(Register32):
+ BUSY = 31
+
+ # None of these bits are supported on hwrev 1
+ HARDWARE_FLUSH = 30
+ FLUSH_VA_RANGE = 14
+ ENABLE_STT_FLUSH = 13
+ DISABLE_STC_FLUSH = 12
+
+ # 0 = flush all
+ # 1 = flush SID
+ # 2 = TLB read
+ # 3 = TLB write????
+ # 4 = flush unlock, definitely not supported on hwrev 1
+ OP = 10, 8
+ STREAM = 7, 0
+
+class R_TLB_OP_IDX(Register32):
+ SET = 13, 8
+ WAY = 6, 4
+ TE = 2, 0
+
+class R_PROTECT(Register32):
+ LOCK_TZ_SELECT = 4
+ LOCK_TZ_CONFIG = 3
+ # This bit can be set, but unknown what it protects
+ _BIT2 = 2
+ LOCK_REG_4xx = 1
+ LOCK_TCR_TTBR = 0
+
+class R_DIAG_LOCK(Register32):
+ # FIXME: how does this work exactly?
+ LOCK_ON_ERR = 1
+ LOCK = 0
+
+class R_TCR(Register32):
+ REMAP = 11, 8
+ REMAP_EN = 7
+ FOUR_LEVELS = 3 # not supported on hwrev 1
+ BYPASS_DAPF = 2
+ BYPASS_DART = 1
+ TRANSLATE_ENABLE = 0
+
+class R_TTBR(Register32):
+ ADDR = 29, 2
+ VALID = 0
+
+class PTE(Register64):
+ SP_START = 63, 52
+ SP_END = 51, 40
+ OFFSET = 37, 10
+ RDPROT = 3
+ WRPROT = 2
+ UNCACHABLE = 1
+ VALID = 0
+
+class DART8110Regs(RegMap):
+ PARAMS_0 = 0x000, R_PARAMS_0
+ PARAMS_4 = 0x004, R_PARAMS_4
+ PARAMS_8 = 0x008, R_PARAMS_8
+ PARAMS_C = 0x00C, R_PARAMS_C
+ # Unknown RO
+ REG_0x10 = 0x010, Register32
+ REG_0x14 = 0x014, Register32 # hwrev 2 only
+
+ TLB_OP = 0x080, R_TLB_OP
+ TLP_OP_IDX = 0x084, R_TLB_OP_IDX
+ TLB_TAG_LO = 0x088, Register32
+ TLB_TAG_HI = 0x08c, Register32 # hwrev 2 only
+ TLB_PA_LO = 0x090, Register32
+ TLB_PA_HI = 0x094, Register32
+ TLB_START_DVA_PAGE = 0x098, Register32 # hwrev 2 only
+ TLB_END_DVA_PAGE = 0x0a0, Register32 # hwrev 2 only
+
+ ERROR = 0x100, R_ERROR
+ ERROR_DISABLE = 0x104, R_ERROR
+
+ # Found via register bruteforcing
+ STREAM_UNK_SET = irange(0x120, 8, 4), Register32
+ STREAM_UNK_CLR = irange(0x140, 8, 4), Register32
+
+ # these are all accessed by error interrupt handler
+ REG_0x160 = 0x160, Register32
+ REG_0x164 = 0x164, Register32
+ ERROR_ADDR_LO = 0x170, Register32
+ ERROR_ADDR_HI = 0x174, Register32
+ REG_0x178 = 0x178, Register32 # hwrev 2 only
+ REG_0x180 = irange(0x180, 4, 4), Register32
+ REG_0x1a0 = irange(0x1a0, 8, 4), Register32
+ ERR_SECONDARY = irange(0x1c0, 8, 4), Register32
+
+ # Write bits to _PROTECT to protect them.
+ # They can be unprotected by writing to _UNPROTECT unless _LOCK is written.
+ # If _LOCK is written, protection can be enabled but not disabled.
+ REG_PROTECT = 0x200, R_PROTECT
+ REG_UNPROTECT = 0x204, R_PROTECT
+ REG_PROTECT_LOCK = 0x208, R_PROTECT
+
+ # Tunables touch this, can set bits FF00001F, RW
+ REG_0x20c = 0x20c, Register32
+
+ DIAG_LOCK = 0x210, R_DIAG_LOCK
+
+ # All unknown, related to transaction queueing???
+
+ # can set bits 3FFFFFFC, RW
+ REG_0x218 = 0x218, Register32
+ # Tunables touch this, can set bits 000F0F0F, RW
+ REG_0x220 = 0x220, Register32
+ # Tunables touch this, can set bits 00FFFFFF, RW
+ REG_0x224 = 0x224, Register32
+ # can set bits 3F3F3F3F
+ TLIMIT = 0x228, Register32
+ # can set bits 07070707
+ TEQRESERVE = 0x22c, Register32
+ # RO, outstanding transaction count???
+ TRANS = irange(0x230, 4, 4), Register32
+
+ # hwrev 2 only for all of these
+ REG_0x300 = 0x300, Register32
+ REG_0x308 = 0x308, Register32
+ REG_0x310 = 0x310, Register32
+ REG_0x318 = 0x318, Register32
+ REG_0x320 = 0x320, Register32
+ REG_0x328 = 0x328, Register32
+ REG_0x330 = 0x330, Register32
+ REG_0x338 = 0x338, Register32
+ REG_0x340 = 0x340, Register32
+ REG_0x348 = 0x348, Register32
+ REG_0x350 = 0x350, Register32
+ REG_0x358 = 0x358, Register32
+
+ # Unknown
+ REG_0x400 = 0x400, Register32 # can set 00000003
+ REG_0x404 = 0x404, Register32 # can set 001FFFFF
+ REG_0x408 = 0x408, Register32 # can set 00FFFFFC
+ REG_0x410 = 0x410, Register32 # can set 3FFFFFFC
+
+ # These registers exist even though it's "not supported"
+ TZ_CONFIG = 0x500, Register32 # 3 bits
+ TZ_SELECT = 0x504, Register32 # 1 bit
+ TZ_REGION0_START = 0x508, Register32
+ TZ_REGION0_END = 0x510, Register32
+ TZ_REGION0_OFFSET = 0x518, Register32
+ TZ_REGION1_START = 0x520, Register32
+ TZ_REGION1_END = 0x528, Register32
+ TZ_REGION1_OFFSET = 0x530, Register32
+ TZ_REGION2_START = 0x538, Register32
+ TZ_REGION2_END = 0x540, Register32
+ TZ_REGION2_OFFSET = 0x548, Register32
+
+ # completely guessed, unverified, can set bits 0F077077
+ PERF_INTR_ENABLE = 0x700, Register32
+ PERF_INTR_STATUS = 0x704, Register32
+
+ PERF_UNK1 = irange(0x720, 8, 4), Register32
+ PERF_UNK2 = irange(0x740, 8, 4), Register32
+
+ PERF_TLB_MISS = 0x760, Register32
+ PERF_TLB_FILL = 0x764, Register32
+ PERF_TLB_HIT = 0x768, Register32
+ PERF_ST_MISS = 0x770, Register32
+ PERF_ST_FILL = 0x774, Register32
+ PERF_ST_HIT = 0x778, Register32
+ # hwrev 1 doesn't have these
+ PERF_CTC_MISS = 0x780, Register32
+ PERF_CTC_FILL = 0x784, Register32
+ PERF_CTC_HIT = 0x788, Register32
+
+ UNK_TUNABLES = irange(0x800, 256, 4), Register32
+
+ ENABLE_STREAMS = irange(0xc00, 8, 4), Register32
+ DISABLE_STREAMS = irange(0xc20, 8, 4), Register32
+
+ TCR = irange(0x1000, 256, 4), R_TCR
+ TTBR = irange(0x1400, 256, 4), R_TTBR
+
+
+class DART8110(Reloadable):
+ PAGE_BITS = 14
+ PAGE_SIZE = 1 << PAGE_BITS
+
+ L1_OFF = 25
+ L2_OFF = 14
+
+ IDX_BITS = 11
+ Lx_SIZE = (1 << IDX_BITS)
+ IDX_MASK = Lx_SIZE - 1
+
+ def __init__(self, iface, regs, util=None):
+ self.iface = iface
+ self.regs = regs
+ self.u = util
+ self.pt_cache = {}
+
+ enabled_streams = 0
+ for i in range(8):
+ enabled_streams |= regs.ENABLE_STREAMS[i].val << 32*i
+ self.enabled_streams = enabled_streams
+
+ @classmethod
+ def from_adt(cls, u, path, instance=0, **kwargs):
+ dart_addr = u.adt[path].get_reg(instance)[0]
+ regs = DART8110Regs(u, dart_addr)
+ dart = cls(u.iface, regs, u, **kwargs)
+ return dart
+
+ def iomap_at(self, stream, iova, addr, size):
+ if size == 0:
+ return
+
+ if not (self.enabled_streams & (1 << stream)):
+ self.enabled_streams |= (1 << stream)
+ self.regs.ENABLE_STREAMS[stream // 32].val |= (1 << (stream % 32))
+
+ tcr = self.regs.TCR[stream].reg
+
+ if tcr.BYPASS_DART and not tcr.TRANSLATE_ENABLE:
+ raise Exception("Stream is bypassed in DART")
+
+ if tcr.BYPASS_DART or not tcr.TRANSLATE_ENABLE:
+ raise Exception(f"Unknown DART mode {tcr}")
+
+ if addr & (self.PAGE_SIZE - 1):
+ raise Exception(f"Unaligned PA {addr:#x}")
+
+ if iova & (self.PAGE_SIZE - 1):
+ raise Exception(f"Unaligned IOVA {iova:#x}")
+
+ start_page = align_down(iova, self.PAGE_SIZE)
+ end = iova + size
+ end_page = align_up(end, self.PAGE_SIZE)
+
+ dirty = set()
+
+ for page in range(start_page, end_page, self.PAGE_SIZE):
+ paddr = addr + page - start_page
+
+ ttbr = self.regs.TTBR[stream].reg
+ if not ttbr.VALID:
+ l1addr = self.u.memalign(self.PAGE_SIZE, self.PAGE_SIZE)
+ self.pt_cache[l1addr] = [0] * self.Lx_SIZE
+ ttbr.VALID = 1
+ ttbr.ADDR = l1addr >> self.PAGE_BITS
+ self.regs.TTBR[stream].reg = ttbr
+
+ cached, l1 = self.get_pt(ttbr.ADDR << self.PAGE_BITS)
+ l1idx = (page >> self.L1_OFF) & self.IDX_MASK
+ l1pte = PTE(l1[l1idx])
+ if not l1pte.VALID:
+ l2addr = self.u.memalign(self.PAGE_SIZE, self.PAGE_SIZE)
+ self.pt_cache[l2addr] = [0] * self.Lx_SIZE
+ l1pte = PTE(
+ OFFSET=l2addr >> self.PAGE_BITS, VALID=1)
+ l1[l1idx] = l1pte.value
+ dirty.add(ttbr.ADDR << self.PAGE_BITS)
+ else:
+ l2addr = l1pte.OFFSET << self.PAGE_BITS
+
+ dirty.add(l1pte.OFFSET << self.PAGE_BITS)
+ cached, l2 = self.get_pt(l2addr)
+ l2idx = (page >> self.L2_OFF) & self.IDX_MASK
+ self.pt_cache[l2addr][l2idx] = PTE(
+ SP_START=0, SP_END=0xfff,
+ OFFSET=paddr >> self.PAGE_BITS, VALID=1).value
+
+ for page in dirty:
+ self.flush_pt(page)
+
+ def iotranslate(self, stream, start, size):
+ if size == 0:
+ return []
+
+ tcr = self.regs.TCR[stream].reg
+
+ if tcr.BYPASS_DART and not tcr.TRANSLATE_ENABLE:
+ # FIXME this may not be correct
+ return [(start, size)]
+
+ if tcr.BYPASS_DART or not tcr.TRANSLATE_ENABLE:
+ raise Exception(f"Unknown DART mode {tcr}")
+
+ start = start & 0xfffffffff
+
+ start_page = align_down(start, self.PAGE_SIZE)
+ start_off = start - start_page
+ end = start + size
+ end_page = align_up(end, self.PAGE_SIZE)
+ end_size = end - (end_page - self.PAGE_SIZE)
+
+ pages = []
+
+ for page in range(start_page, end_page, self.PAGE_SIZE):
+ ttbr = self.regs.TTBR[stream].reg
+ if not ttbr.VALID:
+ pages.append(None)
+ continue
+
+ cached, l1 = self.get_pt(ttbr.ADDR << self.PAGE_BITS)
+ l1pte = PTE(l1[(page >> self.L1_OFF) & self.IDX_MASK])
+ if not l1pte.VALID and cached:
+ cached, l1 = self.get_pt(ttbr.ADDR << self.PAGE_BITS, uncached=True)
+ l1pte = PTE(l1[(page >> self.L1_OFF) & self.IDX_MASK])
+ if not l1pte.VALID:
+ pages.append(None)
+ continue
+
+ cached, l2 = self.get_pt(l1pte.OFFSET << self.PAGE_BITS)
+ l2pte = PTE(l2[(page >> self.L2_OFF) & self.IDX_MASK])
+ if not l2pte.VALID and cached:
+ cached, l2 = self.get_pt(l1pte.OFFSET << self.PAGE_BITS, uncached=True)
+ l2pte = PTE(l2[(page >> self.L2_OFF) & self.IDX_MASK])
+ if not l2pte.VALID:
+ pages.append(None)
+ continue
+
+ pages.append(l2pte.OFFSET << self.PAGE_BITS)
+
+ ranges = []
+
+ for page in pages:
+ if not ranges:
+ ranges.append((page, self.PAGE_SIZE))
+ continue
+ laddr, lsize = ranges[-1]
+ if ((page is None and laddr is None) or
+ (page is not None and laddr == (page - lsize))):
+ ranges[-1] = laddr, lsize + self.PAGE_SIZE
+ else:
+ ranges.append((page, self.PAGE_SIZE))
+
+ ranges[-1] = (ranges[-1][0], ranges[-1][1] - self.PAGE_SIZE + end_size)
+
+ if start_off:
+ ranges[0] = (ranges[0][0] + start_off if ranges[0][0] else None,
+ ranges[0][1] - start_off)
+
+ return ranges
+
+ def get_pt(self, addr, uncached=False):
+ cached = True
+ if addr not in self.pt_cache or uncached:
+ cached = False
+ self.pt_cache[addr] = list(
+ struct.unpack(f"<{self.Lx_SIZE}Q", self.iface.readmem(addr, self.PAGE_SIZE)))
+
+ return cached, self.pt_cache[addr]
+
+ def flush_pt(self, addr):
+ assert addr in self.pt_cache
+ self.iface.writemem(addr, struct.pack(f"<{self.Lx_SIZE}Q", *self.pt_cache[addr]))
+
+ def initialize(self):
+ for i in range(15):
+ self.regs.TCR[i].reg = R_TCR(TRANSLATE_ENABLE=1)
+ self.regs.TCR[15].reg = R_TCR(BYPASS_DART=1)
+
+ for i in range(16):
+ self.regs.TTBR[i].reg = R_TTBR(VALID = 0)
+
+ # self.regs.ERROR.val = 0xffffffff
+ # self.regs.UNK1.val = 0
+ self.regs.DISABLE_STREAMS[0].val = 0xffff
+ self.enabled_streams = 0
+
+ self.invalidate_streams()
+
+ def show_error(self):
+ if self.regs.ERROR.reg.FLAG:
+ print(f"ERROR: {self.regs.ERROR.reg!s}")
+ print(f"ADDR: {self.regs.ERROR_ADDR_HI.val:#x}:{self.regs.ERROR_ADDR_LO.val:#x}")
+ self.regs.ERROR.val = 0x80000004
+
+ def invalidate_streams(self, streams=0xffff):
+ for sid in range(256):
+ if streams & (1 << sid):
+ self.regs.TLB_OP.val = R_TLB_OP(STREAM=sid, OP=1)
+ while self.regs.TLB_OP.reg.BUSY:
+ pass
+
+ def invalidate_cache(self):
+ self.pt_cache = {}
+
+ def dump_table2(self, base, l1_addr):
+
+ def print_block(base, pte, start, last):
+ pgcount = last - start
+ pte.OFFSET -= pgcount
+ print(" page (%4d): %09x ... %09x -> %016x [%d%d%d%d]" % (
+ start, base + start*0x4000, base + (start+1)*0x4000,
+ pte.OFFSET << self.PAGE_BITS,
+ pte.RDPROT, pte.WRPROT, pte.UNCACHABLE, pte.VALID))
+ if start < last:
+ print(" ==> (%4d): ... %09x -> %016x size: %08x" % (
+ last, base + (last+1)*0x4000,
+ (pte.OFFSET + pgcount - 1) << self.PAGE_BITS, pgcount << self.PAGE_BITS))
+
+ cached, tbl = self.get_pt(l1_addr)
+
+ unmapped = False
+ start = 0
+ next_pte = PTE(VALID=0)
+
+ for i, pte in enumerate(tbl):
+ pte = PTE(pte)
+ if not pte.VALID:
+ if not unmapped:
+ if next_pte.VALID:
+ print_block(base, next_pte, start, i)
+ print(" ...")
+ unmapped = True
+ next_pte = pte
+ continue
+
+ unmapped = False
+
+ if int(pte) != int(next_pte):
+ if next_pte.VALID:
+ print_block(base, next_pte, start, i)
+ start = i
+
+ next_pte = pte
+ next_pte.OFFSET += 1
+
+ if next_pte.VALID:
+ print_block(base, next_pte, start, 2048)
+
+ def dump_table(self, base, l1_addr):
+ cached, tbl = self.get_pt(l1_addr)
+
+ unmapped = False
+ for i, pte in enumerate(tbl):
+ pte = PTE(pte)
+ if not pte.VALID:
+ if not unmapped:
+ print(" ...")
+ unmapped = True
+ continue
+
+ unmapped = False
+
+ print(" table (%d): %09x ... %09x -> %016x [%d%d%d%d]" % (
+ i, base + i*0x2000000, base + (i+1)*0x2000000,
+ pte.OFFSET << self.PAGE_BITS,
+ pte.RDPROT, pte.WRPROT, pte.UNCACHABLE, pte.VALID))
+ self.dump_table2(base + i*0x2000000, pte.OFFSET << self.PAGE_BITS)
+
+ def dump_ttbr(self, ttbr):
+ if not ttbr.VALID:
+ return
+
+ l1_addr = (ttbr.ADDR) << self.PAGE_BITS
+ print(" TTBR: %011x" % (l1_addr))
+
+ self.dump_table(0, l1_addr)
+
+ def dump_device(self, idx):
+ tcr = self.regs.TCR[idx].reg
+ ttbr = self.regs.TTBR[idx]
+ print(f"dev {idx:02x}: TCR={tcr!s} TTBR = {ttbr!s}")
+
+ if tcr.TRANSLATE_ENABLE and tcr.BYPASS_DART:
+ print(" mode: INVALID")
+ elif tcr.TRANSLATE_ENABLE:
+ print(" mode: TRANSLATE")
+
+ self.dump_ttbr(ttbr.reg)
+ elif tcr.BYPASS_DART:
+ print(" mode: BYPASS")
+ else:
+ print(" mode: UNKNOWN")
+
+ def dump_params(self):
+ print(self.regs.PARAMS_0.reg)
+ print(self.regs.PARAMS_4.reg)
+ print(self.regs.PARAMS_8.reg)
+ print(self.regs.PARAMS_C.reg)
diff --git a/tools/proxyclient/m1n1/hw/dockchannel.py b/tools/proxyclient/m1n1/hw/dockchannel.py
new file mode 100644
index 0000000..1a9443e
--- /dev/null
+++ b/tools/proxyclient/m1n1/hw/dockchannel.py
@@ -0,0 +1,120 @@
+# SPDX-License-Identifier: MIT
+import struct
+
+from ..utils import *
+
+__all__ = ["DockChannel"]
+
+# DockChannel layout:
+# 00000 : Global regs
+
+# 08000 : IRQ regs (0)
+# 0c000 : IRQ regs (1)
+# 10000 : IRQ regs (2)
+# 14000 : IRQ regs (3) -> AIC #0
+# 18000 : IRQ regs (4) -> AIC #1
+# 1c000 : IRQ regs (5) (not always present)
+
+# 28000 : FIFO regs (1A)
+# 2c000 : Data regs (1A)
+# 30000 : FIFO regs (1B)
+# 34000 : Data regs (1B)
+# 38000 : FIFO regs (2A)
+# 3c000 : Data regs (2A)
+# 40000 : FIFO regs (2B)
+# 44000 : Data regs (2B)
+# (possibly more)
+
+class R_RX_DATA(Register32):
+ DATA = 31, 8
+ COUNT = 7, 0
+
+class DockChannelIRQRegs(RegMap):
+ IRQ_MASK = 0x0, Register32
+ IRQ_FLAG = 0x4, Register32
+
+class DockChannelConfigRegs(RegMap):
+ TX_THRESH = 0x0, Register32
+ RX_THRESH = 0x4, Register32
+
+class DockChannelDataRegs(RegMap):
+ TX_8 = 0x4, Register32
+ TX_16 = 0x8, Register32
+ TX_24 = 0xc, Register32
+ TX_32 = 0x10, Register32
+ TX_FREE = 0x14, Register32
+ RX_8 = 0x1c, R_RX_DATA
+ RX_16 = 0x20, R_RX_DATA
+ RX_24 = 0x24, R_RX_DATA
+ RX_32 = 0x28, Register32
+ RX_COUNT = 0x2c, Register32
+
+class DockChannel:
+ def __init__(self, u, irq_base, fifo_base, irq_idx):
+ self.u = u
+ self.p = u.proxy
+ self.iface = u.iface
+ self.config = DockChannelConfigRegs(u, fifo_base)
+ self.data = DockChannelDataRegs(u, fifo_base + 0x4000)
+ self.irq = DockChannelIRQRegs(u, irq_base)
+ self.irq_idx = irq_idx
+ self.irq.IRQ_MASK.val = 3 << (irq_idx * 2)
+
+ @property
+ def tx_irq(self):
+ self.irq.IRQ_FLAG.val = 1 << (self.irq_idx * 2)
+ return self.irq.IRQ_FLAG.val & (1 << (self.irq_idx * 2))
+
+ @property
+ def rx_irq(self):
+ self.irq.IRQ_FLAG.val = 2 << (self.irq_idx * 2)
+ return self.irq.IRQ_FLAG.val & (2 << (self.irq_idx * 2))
+
+ @property
+ def rx_count(self):
+ return self.data.RX_COUNT.val
+
+ @property
+ def tx_free(self):
+ return self.data.TX_FREE.val
+
+ def set_tx_thresh(self, v):
+ self.config.TX_THRESH.val = v
+
+ def set_rx_thresh(self, v):
+ self.config.RX_THRESH.val = v
+
+ def write(self, data):
+ p = 0
+ left = len(data)
+ while left >= 4:
+ while self.tx_free < 4:
+ pass
+ d = struct.unpack("<I", data[p:p+4])[0]
+ self.data.TX_32.val = d
+ p += 4
+ left -= 4
+ while left >= 1:
+ while self.tx_free < 1:
+ pass
+ self.data.TX_8.val = data[p]
+ p += 1
+ left -= 1
+
+ def read(self, count):
+ data = []
+ left = count
+ while left >= 4:
+ while self.rx_count < 4:
+ pass
+ data.append(struct.pack("<I", self.data.RX_32.val))
+ left -= 4
+ while left >= 1:
+ while self.rx_count < 1:
+ pass
+ data.append(bytes([self.data.RX_8.DATA]))
+ left -= 1
+ return b"".join(data)
+
+ def read_all(self):
+ return self.read(self.rx_count)
diff --git a/tools/proxyclient/m1n1/hw/dwc3.py b/tools/proxyclient/m1n1/hw/dwc3.py
new file mode 100644
index 0000000..41948f4
--- /dev/null
+++ b/tools/proxyclient/m1n1/hw/dwc3.py
@@ -0,0 +1,268 @@
+# SPDX-License-Identifier: MIT
+
+from enum import IntEnum
+from m1n1.utils import *
+
+
+class R_XHCI_USBCMD(Register32):
+ EU3S = 11
+ EWE = 10
+ CRS = 9
+ CSS = 8
+ LHCRST = 7
+ HSEE = 3
+ INTE = 2
+ HCRST = 1
+ RS = 0
+
+
+class R_XHCI_USBSTS(Register32):
+ HCE = 12
+ CNR = 11
+ SRE = 10
+ RSS = 9
+ SSS = 8
+ PCD = 4
+ EINT = 3
+ HSE = 2
+ HCH = 0
+
+
+class R_XHCI_CRCR_LO(Register32):
+ CRP = 31, 6
+ CRR = 3
+ CA = 2
+ CS = 1
+ RCS = 0
+
+
+class R_XHCI_DNCTRL(Register32):
+ N0_N15 = 15, 0
+
+
+class R_XHCI_DOORBELL(Register32):
+ TASK_ID = 31, 16
+ RSVD = 15, 8
+ TARGET = 7, 0
+
+
+class R_XHCI_PORTSC(Register32):
+ CCS = 0
+ PED = 1
+ OCA = 3
+ RESET = 4
+ PLS = 8, 5
+ PP = 9
+ SPEED = 13, 10
+ PIC = 15, 14
+ LWS = 16
+ CSC = 17
+ PEC = 18
+ WRC = 19
+ OCC = 20
+ PRC = 21
+ PLC = 22
+ CEC = 23
+ CAS = 24
+ WCE = 25
+ WDE = 26
+ WOE = 27
+ DR = 30
+ WPR = 31
+
+
+class R_XHCI_PORTLI(Register32):
+ ERROR_CNT = 15, 0
+ RLC = 19, 16
+ TLC = 23, 20
+ RSV = 31, 24
+
+
+class R_XHCI_IMAN(Register32):
+ IP = 0
+ IE = 1
+
+
+class XhciRegs(RegMap):
+ HCSPARAMS1 = 0x04, Register32
+ HCSPARAMS2 = 0x08, Register32
+ HCSPARAMS3 = 0x0C, Register32
+ HCCPARAMS1 = 0x10, Register32
+ DBOFF = 0x14, Register32
+ RTSOFF = 0x18, Register32
+ HCCPARAMS2 = 0x1C, Register32
+ USBCMD = 0x20, R_XHCI_USBCMD
+ USBSTS = 0x24, R_XHCI_USBSTS
+ DNCTRL = 0x34, R_XHCI_DNCTRL
+ CRCR_LO = 0x38, R_XHCI_CRCR_LO
+ CRCR_HI = 0x3C, Register32
+ DCBAAP_LO = 0x50, Register32
+ DCBAAP_HI = 0x54, Register32
+
+ PORTSC0 = 0x420, R_XHCI_PORTSC
+ PORTPMSC0 = 0x424, Register32
+ PORTLI0 = 0x428, R_XHCI_PORTLI
+ PORTHLPMC0 = 0x42C, Register32
+
+ PORTSC1 = 0x430, R_XHCI_PORTSC
+ PORTPMSC1 = 0x434, Register32
+ PORTLI1 = 0x438, R_XHCI_PORTLI
+ PORTHLPMC1 = 0x43C, Register32
+
+ MFINDEX = 0x440, Register32
+ IMAN0 = 0x460 + 0x00, R_XHCI_IMAN
+ IMOD0 = 0x460 + 0x04, Register32
+ ERSTSZ0 = 0x460 + 0x08, Register32
+ RSVD0 = 0x460 + 0x0C, Register32
+ ERSTBA0 = 0x460 + 0x10, Register64
+ ERDP0 = 0x460 + 0x18, Register64
+
+ IMAN1 = 0x480 + 0x00, R_XHCI_IMAN
+ IMOD1 = 0x480 + 0x04, Register32
+ ERSTSZ1 = 0x480 + 0x08, Register32
+ RSVD1 = 0x480 + 0x0C, Register32
+ ERSTBA1 = 0x480 + 0x10, Register64
+ ERDP1 = 0x480 + 0x18, Register64
+
+ IMAN2 = 0x4A0 + 0x00, R_XHCI_IMAN
+ IMOD2 = 0x4A0 + 0x04, Register32
+ ERSTSZ2 = 0x4A0 + 0x08, Register32
+ RSVD0 = 0x4A0 + 0x0C, Register32
+ ERSTBA2 = 0x4A0 + 0x10, Register64
+ ERDP2 = 0x4A0 + 0x18, Register64
+
+ IMAN3 = 0x4C0 + 0x00, R_XHCI_IMAN
+ IMOD3 = 0x4C0 + 0x04, Register32
+ ERSTSZ3 = 0x4C0 + 0x08, Register32
+ RSVD0 = 0x4C0 + 0x0C, Register32
+ ERSTBA3 = 0x4C0 + 0x10, Register64
+ ERDP3 = 0x4C0 + 0x18, Register64
+
+ DOORBELL = irange(0x4E0, 256, 4), R_XHCI_DOORBELL
+
+
+class R_GUSB3PIPECTL(Register32):
+ PHYSOFTRST = 31
+ U2SSINP3OK = 29
+ DISRXDETINP3 = 28
+ UX_EXIT_PX = 27
+ REQP1P2P3 = 24
+ DEPOCHANGE = 18
+ SUSPHY = 17
+ LFPSFILT = 9
+ RX_DETOPOLL = 8
+
+
+class R_GUSB2PHYCFG(Register32):
+ PHYSOFTRST = 31
+ U2_FREECLK_EXISTS = 30
+ SUSPHY = 6
+ ULPI_UTMI = 4
+ ENBLSLPM = 8
+
+
+class R_GCTL(Register32):
+ U2RSTECN = 16
+ PRTCAP = 14, 12
+ CORESOFTRESET = 11
+ SOFITPSYNC = 10
+ SCALEDOWN = 6, 4
+ DISSCRAMBLE = 3
+ U2EXIT_LFPS = 2
+ GBLHIBERNATIONEN = 1
+ DSBLCLKGTNG = 0
+
+
+class Dwc3CoreRegs(RegMap):
+ GSBUSCFG0 = 0x100, Register32
+ GSBUSCFG1 = 0x104, Register32
+ GTXTHRCFG = 0x108, Register32
+ GRXTHRCFG = 0x10C, Register32
+ GCTL = 0x110, R_GCTL
+ GEVTEN = 0x114, Register32
+ GSTS = 0x118, Register32
+ GUCTL1 = 0x11C, Register32
+ GSNPSID = 0x120, Register32
+ GGPIO = 0x124, Register32
+ GUID = 0x128, Register32
+ GUCTL = 0x12C, Register32
+ GBUSERRADDR0 = 0x130, Register32
+ GBUSERRADDR1 = 0x134, Register32
+ GPRTBIMAP0 = 0x138, Register32
+ GPRTBIMAP1 = 0x13C, Register32
+ GHWPARAMS0 = 0x140, Register32
+ GHWPARAMS1 = 0x144, Register32
+ GHWPARAMS2 = 0x148, Register32
+ GHWPARAMS3 = 0x14C, Register32
+ GHWPARAMS4 = 0x150, Register32
+ GHWPARAMS5 = 0x154, Register32
+ GHWPARAMS6 = 0x158, Register32
+ GHWPARAMS7 = 0x15C, Register32
+ GDBGFIFOSPACE = 0x160, Register32
+ GDBGLTSSM = 0x164, Register32
+ GDBGBMU = 0x16C, Register32
+ GDBGLSPMUX = 0x170, Register32
+ GDBGLSP = 0x174, Register32
+ GDBGEPINFO0 = 0x178, Register32
+ GDBGEPINFO1 = 0x17C, Register32
+ GPRTBIMAP_HS0 = 0x180, Register32
+ GPRTBIMAP_HS1 = 0x184, Register32
+ GPRTBIMAP_FS0 = 0x188, Register32
+ GPRTBIMAP_FS1 = 0x18C, Register32
+ GUCTL2 = 0x19C, Register32
+ GUSB2PHYCFG = 0x200, R_GUSB2PHYCFG
+ GUSB2I2CCTL = 0x240, Register32
+ GUSB2PHYACC = 0x280, Register32
+ GUSB3PIPECTL = 0x2C0, R_GUSB3PIPECTL
+ DWC3_GHWPARAMS8 = 0x600, Register32
+ DWC3_GUCTL3 = 0x60C, Register32
+ DWC3_GFLADJ = 0x630, Register32
+ DWC3_GHWPARAMS9 = 0x680, Register32
+
+
+class R_PIPEHANDLER_OVERRIDE(Register32):
+ RXVALID = 0
+ RXDETECT = 2
+
+
+class E_PIPEHANDLER_MUX_MODE(IntEnum):
+ USB3_PHY = 0
+ DUMMY_PHY = 1
+ UNK2 = 2
+
+
+class E_PIPEHANDLER_CLK_SELECT(IntEnum):
+ UNK0 = 0
+ USB3_PHY = 1
+ DUMMY_PHY = 2
+ UNK4 = 4
+
+
+class R_PIPEHANDLER_MUX_CTRL(Register32):
+ MUX_MODE = 1, 0, E_PIPEHANDLER_MUX_MODE
+ CLK_SELECT = 5, 3, E_PIPEHANDLER_CLK_SELECT
+
+
+class R_PIPEHANDLER_LOCK(Register32):
+ LOCK_EN = 0
+
+
+class R_PIPEHANDLER_AON_GEN(Register32):
+ DWC3_FORCE_CLAMP_EN = 4
+ DWC3_RESET_N = 0
+
+
+class R_PIPEHANDLER_NONSELECTED_OVERRIDE(Register32):
+ NATIVE_POWER_DOWN = 3, 0
+ NATIVE_RESET = 12
+ DUMMY_PHY_EN = 15
+
+
+class PipehandlerRegs(RegMap):
+ PIPEHANDLER_OVERRIDE = 0x00, R_PIPEHANDLER_OVERRIDE
+ PIPEHANDLER_OVERRIDE_VALUES = 0x04, R_PIPEHANDLER_OVERRIDE
+ PIPEHANDLER_MUX_CTRL = 0x0C, R_PIPEHANDLER_MUX_CTRL
+ PIPEHANDLER_LOCK_REQ = 0x10, R_PIPEHANDLER_LOCK
+ PIPEHANDLER_LOCK_ACK = 0x14, R_PIPEHANDLER_LOCK
+ PIPEHANDLER_AON_GEN = 0x1C, R_PIPEHANDLER_AON_GEN
+ PIPEHANDLER_NONSELECTED_OVERRIDE = 0x20, R_PIPEHANDLER_NONSELECTED_OVERRIDE
diff --git a/tools/proxyclient/m1n1/hw/i2c.py b/tools/proxyclient/m1n1/hw/i2c.py
new file mode 100644
index 0000000..e2bda7a
--- /dev/null
+++ b/tools/proxyclient/m1n1/hw/i2c.py
@@ -0,0 +1,251 @@
+# SPDX-License-Identifier: MIT
+from ..utils import *
+from enum import IntEnum
+
+__all__ = ["I2C", "I2CRegs"]
+
+
+class R_MTXFIFO(Register32):
+ READ = 10 # Read (DATA=count)
+ STOP = 9 # Issue START before
+ START = 8 # Issue STOP after
+ DATA = 7, 0 # Byte to send or count
+
+class R_MRXFIFO(Register32):
+ EMPTY = 8 # FIFO empty
+ DATA = 7, 0 # FIFO data
+
+class R_MCNT(Register32):
+ S_RXCNT = 31, 24 # Slave RX count
+ S_TXCNT = 23, 16 # Slave TX count
+ M_RXCNT = 15, 8 # Master RX count
+ M_TXCNT = 7, 0 # Master TX count
+
+class E_MST(IntEnum):
+ IDLE = 0
+ FRD1 = 1
+ FRD2 = 2
+ COMMAND = 3
+ START = 4
+ WRITE = 5
+ READ = 6
+ ACK = 7
+ STOP = 8
+ BAD = 15
+
+class E_SST(IntEnum):
+ IDLE = 0
+ START = 1
+ ST_ACK = 2
+ DATA = 3
+ ACK = 4
+
+class R_XFSTA(Register32):
+ MST = 27, 24, E_MST # Master controller state
+ SRD = 20 # Slave read in progress
+ SWR = 19 # Slave write in progress
+ SST = 18, 16, E_SST # Slave controller state
+ XFIFO = 9, 8 # FIFO number for error
+ XFCNT = 7, 0 # Number of bytes in current xfer
+
+class R_SADDR(Register32):
+ DEB = 31 # Enable SDA/SCL read debug
+ DIR = 30 # Direct (bitbang) mode
+ ENS = 29 # Enable slave interface
+ RST_STX = 28 # Reset slave TX FIFO
+ RST_SRX = 27 # Reset master RX fifo (if ^ both, controller too)
+ PEN = 26 # Promiscuous mode (slave)
+ AAE = 25 # SALT/ALTMASK enable
+ SAE = 24 # SADDR enable
+ ALTMASK = 23, 16 # MASK for SALT bits
+ SALT = 15, 8 # Alt slave address
+ SADDR = 7, 0 # Slave address
+
+class R_SMSTA(Register32):
+ XIP = 28 # Xaction in progress
+ XEN = 27 # Xaction ended
+ UJF = 26 # UnJam failure
+ JMD = 25 # Jam ocurred
+ JAM = 24 # Currently jammed
+ MTO = 23 # Master timeout
+ MTA = 22 # Master arb lost
+ MTN = 21 # Master received NACK
+ MRF = 20 # Master RX fifo full
+ MRNE = 19 # Master RX fifo not empty
+ MTF = 17 # Master TX fifo full
+ MTE = 16 # Master RX fifo empty
+ STO = 15 # Slave timeout
+ STA = 14 # Slave arb lost
+ STN = 13 # Slave received NACK
+ SRF = 12 # Slave RX fifo full
+ SRNE = 11 # Slave RX fifo not empty
+ STR = 10 # Slave transmit required
+ STF = 9 # Slave TX fifo full
+ STE = 8 # Slave TX fifo empty
+ TOS = 7 # Timeout due to slave FIFO
+ TOM = 6 # Timeout due to master FIFO
+ TOE = 5 # Slave timeout due to ext clock stretch
+ DCI = 4 # Direct clock in
+ DDI = 3 # Direct data in
+ DCO = 2 # Direct clock out
+ DDO = 1 # Direct data out
+ NN = 0 # NACK next (slave)
+
+class R_CTL(Register32):
+ MSW = 26, 16 # Maximum slave write size
+ ENABLE = 11 # Unknown enable bit (clock sel? Apple thing)
+ MRR = 10 # Master receive FIFO reset
+ MTR = 9 # Master transmit FIFO reset
+ UJM = 8 # Enable auto unjam machine
+ CLK = 7, 0 # Clock divider
+
+class R_STXFIFO(Register32):
+ DATA = 7, 0 # Data
+
+class R_SRXFIFO(Register32):
+ N = 12 # NACK received after this byte
+ P = 11 # Stop received, data not valid
+ S = 10 # Start received before
+ O = 9 # Overflow (promisc only)
+ E = 8 # Empty (data not valid)
+ DATA = 7, 0 # Data
+
+# Apple reg
+class R_FIFOCTL(Register32):
+ HALT = 0 # Halt machinery
+
+class I2CRegs(RegMap):
+ MTXFIFO = 0x00, R_MTXFIFO
+ MRXFIFO = 0x04, R_MRXFIFO
+ MCNT = 0x08, R_MCNT
+ XFSTA = 0x0c, R_XFSTA
+ SADDR = 0x10, R_SADDR
+ SMSTA = 0x14, R_SMSTA
+ IMASK = 0x18, R_SMSTA
+ CTL = 0x1c, R_CTL
+ STXFIFO = 0x20, R_STXFIFO
+ SRXFIFO = 0x20, R_SRXFIFO
+ FIFOCTL = 0x44, R_FIFOCTL
+
+
+class I2C:
+ def __init__(self, u, adt_path):
+ self.u = u
+ self.p = u.proxy
+ self.iface = u.iface
+ self.base = u.adt[adt_path].get_reg(0)[0]
+ self.regs = I2CRegs(u, self.base)
+ self.devs = []
+
+ def clear_fifos(self):
+ self.regs.CTL.set(MTR=1, MRR=1)
+
+ def clear_status(self):
+ self.regs.SMSTA.val = 0xffffffff
+
+ def _fifo_read(self, nbytes):
+ read = []
+ for _ in range(nbytes):
+ val = self.regs.MRXFIFO.reg
+ timeout = 10000
+ while val.EMPTY and timeout > 0:
+ val = self.regs.MRXFIFO.reg
+ timeout -= 1
+ if timeout == 0:
+ raise Exception("timeout")
+ read.append(int(val) & 0xff)
+ return bytes(read)
+
+ def _fifo_write(self, buf, stop=False):
+ for no, byte in enumerate(buf):
+ sending_stop = stop and no == len(buf) - 1
+ self.regs.MTXFIFO.set(DATA=byte, STOP=int(sending_stop))
+
+ if not stop:
+ return
+
+ timeout = 10000
+ while not self.regs.SMSTA.reg.XEN and timeout > 0:
+ timeout -= 1
+ if timeout == 0:
+ raise Exception("timeout")
+
+ def write_reg(self, addr, reg, data, regaddrlen=1):
+ self.clear_fifos()
+ self.clear_status()
+
+ self.regs.CTL.set(ENABLE=1, CLK=0x4)
+ self.regs.MTXFIFO.set(DATA=addr << 1, START=1)
+ regbytes = int.to_bytes(reg, regaddrlen, byteorder="big")
+ self._fifo_write(regbytes + bytes(data), stop=True)
+ self.regs.CTL.set(ENABLE=0, CLK=0x4)
+
+ def read_reg(self, addr, reg, nbytes, regaddrlen=1):
+ self.clear_fifos()
+ self.clear_status()
+
+ self.regs.CTL.set(ENABLE=1, CLK=0x4)
+ self.regs.MTXFIFO.set(DATA=addr << 1, START=1)
+ regbytes = int.to_bytes(reg, regaddrlen, byteorder="big")
+ self._fifo_write(regbytes, stop=False)
+ self.regs.MTXFIFO.set(DATA=(addr << 1) | 1, START=1)
+ self.regs.MTXFIFO.set(DATA=nbytes, STOP=1, READ=1)
+ data = self._fifo_read(nbytes)
+ self.regs.CTL.set(ENABLE=0, CLK=0x4)
+ return data
+
+class I2CRegMapDev:
+ REGMAP = None
+ ADDRESSING = (0, 1)
+
+ def __init__(self, bus, addr, name=None):
+ self.bus = bus
+ self.addr = addr
+ self.curr_page = None
+ self.name = name
+
+ self.paged, self.regimmbytes = self.ADDRESSING
+ if self.REGMAP is not None:
+ self.regs = self.REGMAP(self, 0)
+
+ @classmethod
+ def from_adt(cls, bus, path):
+ node = bus.u.adt[path]
+ addr = node.reg[0] & 0xff
+ return cls(bus, addr, node.name)
+
+ def _switch_page(self, page):
+ assert self.paged
+ self.bus.write_reg(self.addr, 0, bytes([page]),
+ regaddrlen=self.regimmbytes)
+ self.curr_page = page
+
+ def _snip_regaddr(self, addr):
+ pageshift = self.regimmbytes * 8
+ page = addr >> pageshift
+ immediate = addr & ~(~0 << pageshift)
+ return (page, immediate)
+
+ def write(self, reg, val, width=8):
+ page, imm = self._snip_regaddr(reg)
+
+ if self.paged and page != self.curr_page:
+ self._switch_page(page)
+
+ valbytes = val.to_bytes(width//8, byteorder="little")
+ self.bus.write_reg(self.addr, imm, valbytes,
+ regaddrlen=self.regimmbytes)
+
+ def read(self, reg, width=8):
+ page, imm = self._snip_regaddr(reg)
+
+ if self.paged and page != self.curr_page:
+ self._switch_page(page)
+
+ data = self.bus.read_reg(self.addr, imm, width//8,
+ regaddrlen=self.regimmbytes)
+ return int.from_bytes(data, byteorder='little')
+
+ def __repr__(self):
+ label = self.name or f"@ {self.addr:02x}"
+ return f"<{type(self).__name__} {label}>"
diff --git a/tools/proxyclient/m1n1/hw/isp.py b/tools/proxyclient/m1n1/hw/isp.py
new file mode 100644
index 0000000..4d8d0fd
--- /dev/null
+++ b/tools/proxyclient/m1n1/hw/isp.py
@@ -0,0 +1,507 @@
+import struct
+from enum import IntEnum
+from ..utils import *
+
+class ISPCommandDirection(IntEnum):
+ RX = 0
+ TX = 1
+
+class ISPCommand:
+ """ Represents a command in any IPC channel """
+
+ def __init__(self, channel, message, direction):
+ value, u0, u1 = struct.unpack('<3q40x', message.data)
+ self.message = message
+ self.channel = channel
+ self.direction = direction
+ self.tracer = channel.tracer
+ self.raw_value = value
+ self.value = value & 0xFFFFFFFFFFFFFFFC
+ self.arg0 = u0
+ self.arg1 = u1
+
+ def dump(self):
+ self.log(f"[CMD Value: {hex(self.value)}, U0: {hex(self.arg0)}, U1: {hex(self.arg1)}]")
+
+ def read_iova(self, address, length):
+ return self.tracer.dart.ioread(0, address, length)
+
+ def valid(self):
+ return True
+
+ def log(self, message):
+ if self.direction is ISPCommandDirection.RX:
+ self.tracer.log(f"<== [{self.channel.name}]({self.message.index}): {message}")
+ else:
+ self.tracer.log(f"==> [{self.channel.name}]({self.message.index}): {message}")
+
+class ISPTerminalCommand(ISPCommand):
+ """ Represents a command in TERMINAL channel
+
+ A command arguments include a pointer to a buffer that contains log line
+ and the length of the buffer. Buffers are 0x80 bytes wide.
+ """
+ # ISP sends buffer address at beginning
+ BUFFER_ADDRESS = None
+ # It seems messages are capped to 100 bytes
+ MAX_BUFFER_SIZE = 0x80
+
+ @staticmethod
+ def set_address(address):
+ if address != 0:
+ ISPTerminalCommand.BUFFER_ADDRESS = address
+
+ @staticmethod
+ def move_cursor():
+ if ISPTerminalCommand.BUFFER_ADDRESS:
+ ISPTerminalCommand.BUFFER_ADDRESS += ISPTerminalCommand.MAX_BUFFER_SIZE
+ else:
+ return None
+
+ def __init__(self, channel, message, direction):
+ super().__init__(channel, message, direction)
+
+ ## Set buffer address
+ ISPTerminalCommand.set_address(self.value)
+
+ ## Read contents
+ self.buffer_message = self.read_iova(ISPTerminalCommand.BUFFER_ADDRESS, self.arg0)
+
+ ## Move cursor
+ ISPTerminalCommand.move_cursor()
+
+ def dump(self):
+ self.log(f"ISPCPU: {self.buffer_message}]")
+
+ def log(self, message):
+ self.tracer.log(f"[{self.channel.name}]({str(self.message.index).ljust(3)}): {message}")
+
+class ISPIOCommand(ISPCommand):
+ """ Represents a command in IO channel
+
+ An IO command is used to request ISP to perform some operations. The command
+ contains a pointer to a command struct which contains a OPCODE. The OPCODE
+ is used to differentate commands.
+ """
+
+ def __init__(self, channel, message, direction):
+ super().__init__(channel, message, direction)
+ self.iova = self.value
+ if self.iova != 0:
+ contents = self.read_iova(self.iova, 0x8)
+ self.contents = int.from_bytes(contents, byteorder="little")
+ else:
+ self.contents = None
+
+ def dump(self):
+ if self.iova != 0:
+ self.log(f"[IO Addr: {hex(self.iova)}, Size: {hex(self.arg0)}, U1: {hex(self.arg1)} -> Opcode: {hex(self.contents >> 32)}]")
+
+class ISPT2HBufferCommand(ISPCommand):
+ """ Represents a command in BUF_T2H channel """
+ def __init__(self, channel, message, direction):
+ super().__init__(channel, message, direction)
+ self.iova = self.value
+ if self.iova != 0:
+ self.contents = self.read_iova(self.iova, 0x280)
+
+ def dump(self):
+ super().dump()
+ if self.iova != 0:
+ chexdump(self.contents)
+
+class ISPH2TBufferCommand(ISPCommand):
+ """ Represents a command in BUF_H2T channel """
+ def __init__(self, channel, message, direction):
+ super().__init__(channel, message, direction)
+ self.iova = self.value
+ if self.iova != 0:
+ # Dumping first 0x20 bytes after iova translation, but no idea how internal struct
+ self.contents = self.read_iova(self.iova, 0x20)
+
+ def dump(self):
+ super().dump()
+ if self.iova != 0:
+ chexdump(self.contents)
+
+class ISPT2HIOCommand(ISPCommand):
+ """ Represents a command in IO_T2H channel """
+ def __init__(self, channel, message, direction):
+ super().__init__(channel, message, direction)
+ self.iova = self.value
+ if self.iova != 0:
+ # Dumping first 0x20 bytes after iova translation, but no idea how internal struct
+ self.contents = self.read_iova(self.iova, 0x20)
+
+ def dump(self):
+ super().dump()
+ if self.iova != 0:
+ chexdump(self.contents)
+
+class ISPSharedMallocCommand(ISPCommand):
+ """ Represents a command in SHAREDMALLOC channel
+
+ A command of this type can either request memory allocation or memory free
+ depending the arguments. When ISP needs to allocate memory, it puts a
+ message in the SHAREDMALLOC channel, message arguments are length of buffer
+ and type of allocation.
+
+ CPU detects the new message, perform memory allocation and mutate the
+ original message to indicate the address of the allocated memory block.
+ """
+
+ def __init__(self, channel, message, direction):
+ super().__init__(channel, message, direction)
+ self.address = self.value
+ self.size = self.arg0
+ self.type = self.arg1 #.to_bytes(8, byteorder="little")
+
+ def dump(self):
+ if self.direction == ISPCommandDirection.RX:
+ if self.address is 0:
+ self.log(f"[FW Malloc, Length: {hex(self.size)}, Type: {hex(self.type)}]")
+ else:
+ self.log(f"[FW Free, Address: {hex(self.value)}, Length: {hex(self.size)}, Type: {hex(self.type)})]")
+ else:
+ if self.address is 0:
+ self.log(f"[FW Free]")
+ else:
+ self.log(f"[FW Malloc, Address: {hex(self.value)}, Type: {hex(self.type)})]")
+
+class ISPChannelTable:
+ """ A class used to present IPC table.
+
+ The Channel Table describes the IPC channels available to communicate with
+ the ISP.
+
+ In the M1 processor (tonga), the list of channels exposed by ISP are:
+ [CH - TERMINAL] (src = 0, type = 2, entries = 768, iova = 0x1804700)
+ [CH - IO] (src = 1, type = 0, entries = 8, iova = 0x1810700)
+ [CH - BUF_H2T] (src = 2, type = 0, entries = 64, iova = 0x1810b00)
+ [CH - BUF_T2H] (src = 3, type = 1, entries = 64, iova = 0x1811b00)
+ [CH - SHAREDMALLOC] (src = 3, type = 1, entries = 8, iova = 0x1812b00)
+ [CH - IO_T2H] (src = 3, type = 1, entries = 8, iova = 0x1812d00)
+
+ Each entry in the table is 256 bytes wide. Here is the layout of each entry:
+ 0x00 - 0x1F = Name (NULL terminated string)
+ 0x20 - 0x3F = Padding
+ 0x40 - 0x43 = Type (DWORD)
+ 0x44 - 0x47 = Source (DWORD)
+ 0x48 - 0x4F = Entries (QWORD)
+ 0x50 - 0x58 = Address (QWORD)
+ """
+
+ ENTRY_LENGTH = 256
+
+ def __init__(self, tracer, number_of_channels, table_address):
+ self.tracer = tracer
+ self.address = table_address
+ self.count = number_of_channels
+ self.size = number_of_channels * self.ENTRY_LENGTH
+ self.channels = []
+
+ _table = self.ioread(self.address & 0xFFFFFFFF, self.size)
+ for offset in range(0, self.size, self.ENTRY_LENGTH):
+ _entry = _table[offset: offset + self.ENTRY_LENGTH]
+ _name, _type, _source, _entries, _address = struct.unpack('<32s32x2I2q168x', _entry)
+ _channel = ISPChannel(self, _name, _type, _source, _entries, _address)
+ # We want to process terminal logs as fast as possible before they are processed by CPU
+ # So we use a special implementation for TERMINAL channel that fetches all logs
+ if _channel.name == "TERMINAL":
+ _channel = ISPTerminalChannel(self, _name, _type, _source, _entries, _address)
+ self.channels.append(_channel)
+
+ def get_last_write_command(self, doorbell_value):
+ """ Gets last written message given a Doorbell value """
+ if self.channels and len(self.channels) > 0:
+ names = []
+ channel_cmds = []
+ for channel in self.channels:
+ # We want to process terminal logs as fast as possible before they are processed by CPU
+ if (channel.doorbell == doorbell_value) or channel.name == "TERMINAL":
+ names.append(channel.name)
+ for cmd in channel.get_commands(ISPCommandDirection.TX):
+ channel_cmds.append(cmd)
+
+ self.log(f"CHs: [{(','.join(names))}]")
+ for cmd in channel_cmds:
+ cmd.dump()
+
+ def get_last_read_command(self, pending_irq):
+ """ Gets last read message given a IRQ value """
+ cmds = []
+ scanned_channels = []
+ if self.channels and len(self.channels) > 0:
+ cidx = 0
+ for channel in self.channels:
+ if (pending_irq >> channel.source & 1) != 0:
+ scanned_channels.append(channel.name)
+ for cmd in channel.get_commands(ISPCommandDirection.RX):
+ cmds.append(cmd)
+ cidx = cidx + 1
+
+ if len(scanned_channels) > 0:
+ self.log(f"CHs: [{(','.join(scanned_channels))}]")
+ for cmd in cmds:
+ cmd.dump()
+
+ def dump(self):
+ """ Dumps the content of each channel """
+ if self.channels and len(self.channels) > 0:
+ for channel in self.channels:
+ channel.dump()
+
+ def ioread(self, address, size):
+ return self.tracer.ioread(address, size)
+
+ def log(self, message):
+ self.tracer.log(message)
+
+ def __str__(self):
+ s = "======== CHANNEL TABLE ========\n"
+ for channel in self.channels:
+ s += f"\t{str(channel)}\n"
+ return s
+
+class ISPChannel:
+ """ A class used to represent IPC channel
+
+ ISP channels are ring buffers used by communication between CPU and ISP.
+ channel length is measured in number of entries, each entry is 64 bytes,
+ so channel size is '(entries * 64)' bytes.
+
+ Channel Source is used to filter out channels when processing interrupts
+ and doorbell. Each time CPU wants to notify ISP about a new message it
+ writes doorbell register. In the other hand, when ISP wants to notify CPU
+ about a new message it triggers a hardware interrupt.
+
+ Channel Type is a mistery, but it seems to have a connection with cmd bit
+ mask.
+ """
+
+ ENTRY_LENGTH = 64
+
+ def __init__(self, table, name, _type, source, number_of_entries, address):
+ self.table = table
+ self.tracer = table.tracer
+ self.name = str(name, "ascii").rstrip('\x00')
+ self.source = source
+ self.type = _type
+ self.number_of_entries = number_of_entries
+ self.entry_size = self.ENTRY_LENGTH
+ self.size = self.number_of_entries * self.entry_size
+ self.address = address
+ self.doorbell = 1 << source
+ self.last_message_sent = None
+ self.last_message_received = None
+
+ def get_commands(self, direction):
+ """ Gets a command from the channel"""
+ commands = []
+ message = self.get_message(direction)
+ if message:
+ command = self.__convert2command__(message, direction)
+ if command:
+ commands.append(command)
+ return commands
+
+ def get_message(self, direction):
+ """ Gets a message from the channel and increase the associated index """
+ last_message = self.last_message_sent if direction is ISPCommandDirection.TX else self.last_message_received
+ index = (last_message.index + 1) if last_message else 0
+ new_index, message = self.__read_message__(index)
+ if message:
+ if last_message and last_message == message:
+ return
+
+ last_message = message
+ if direction is ISPCommandDirection.TX:
+ self.last_message_sent = last_message
+ else:
+ self.last_message_received = last_message
+ return message
+
+ def dump(self):
+ """ Dumps the content of the channel """
+ s = f"[{self.name}] Channel messages: \n"
+ for index in range(self.number_of_entries):
+ _, message = self.__read_message__(index)
+ s = s + "\t" + str(message) + "\n"
+ self.table.log(s)
+
+ def __convert2command__(self, message, direction):
+ """ Converts a channel message into a command """
+ if self.name == "TERMINAL":
+ return ISPTerminalCommand(self, message, direction)
+ elif self.name == "IO" or self.name == "DEBUG":
+ return ISPIOCommand(self, message, direction)
+ elif self.name == "SHAREDMALLOC":
+ return ISPSharedMallocCommand(self, message, direction)
+ elif self.name == "BUF_T2H":
+ return ISPT2HBufferCommand(self, message, direction)
+ elif self.name == "BUF_H2T":
+ return ISPH2TBufferCommand(self, message, direction)
+ elif self.name == "IO_T2H":
+ return ISPT2HIOCommand(self, message, direction)
+ else:
+ return ISPCommand(self, message, direction)
+
+ def __read_message__(self, index):
+ message_data = self.__read_by_index__(index)
+ message = ISPChannelMessage(index, message_data)
+ if message.valid():
+ index += 1
+ if index >= self.number_of_entries:
+ index = 0
+ return index, message
+ return 0, None
+
+ def __read_by_index__(self, index):
+ return self.table.ioread(self.address + (self.entry_size * index), self.entry_size)
+
+ def __str__(self):
+ return f"[CH - {str(self.name)}] (src = {self.source!s}, type = {self.type!s}, size = {self.number_of_entries!s}, iova = {hex(self.address)!s})"
+
+class ISPTerminalChannel(ISPChannel):
+ """ Special channel implementation for TERMINAL channel
+ Addresses of log buffers are removed from memory after MacOS processes them,
+ hence we want to be a little bit ahead of MacOS and fetch all entries if
+ possible.
+ """
+
+ def __init__(self, table, name, _type, source, number_of_entries, address):
+ super().__init__(table, name, _type, source, number_of_entries, address)
+ self.last_index = 0
+
+ def get_commands(self, direction):
+ """ Gets a command from the channel"""
+ commands = []
+ for i in range(self.number_of_entries):
+ index = (self.last_index + i) % self.number_of_entries
+ _, message = self.__read_message__(index)
+ if message and message.valid():
+ command = self.__convert2command__(message, ISPCommandDirection.RX)
+ if command:
+ commands.append(command)
+ else:
+ self.last_index = index
+ break
+ return commands
+
+class ISPChannelMessage:
+ """ A class used to represent IPC channel message or entry
+
+ Each entry is 64 bytes, however only 24 bytes seems to be used. These 24
+ bytes are divided in three qwords (8-bytes).
+ """
+
+ def __init__(self, index, data):
+ self.index = index
+ self.data = data
+ idx = 0
+ for arg in struct.unpack('<8q', self.data):
+ setattr(self, f"arg{idx}", arg)
+ idx += 1
+
+ def valid(self):
+ """ Checks if a message seems to be valid
+
+ So far I have observed that invalid messages or empty slots
+ are usually marked as 0x1 (or 0x3 in case of TERMINAL msgs)
+ """
+ return (self.arg0 is not 0x1) and (self.arg0 is not 0x3)
+
+ def __str__(self):
+ s = "ISP Message: {"
+ idx = 0
+ for arg in struct.unpack('<8q', self.data):
+ s = s + f"Arg{idx}: {hex(arg)}, "
+ idx = idx + 1
+ s = s + "}"
+ return s
+
+ def __eq__(self, other):
+ return self.data == other.data
+
+class ISP_REVISION(Register32):
+ REVISION = 15, 0
+
+class ISP_PMU(Register32):
+ STATUS = 7, 0
+ OTHER = 63, 8
+
+class ISP_PMU_SPECIAL_STATUS(Register32):
+ STATUS = 7, 0
+ OTHER = 63, 8
+
+class ISPRegs(RegMap):
+ ISP_CPU_CONTROL = 0x0000, Register32
+ ISP_CPU_STATUS = 0x0004, Register32
+ ISP_REVISION = 0x1800000, ISP_REVISION
+ ISP_POWER_UNKNOWN = 0x20e0080, Register32
+ ISP_IRQ_INTERRUPT = 0x2104000, Register32
+ ISP_IRQ_INTERRUPT_2 = 0x2104004, Register32
+ ISP_SENSOR_REF_CLOCK = irange(0x2104190, 3, 4), Register32
+ ISP_GPR0 = 0x2104170, Register32
+ ISP_GPR1 = 0x2104174, Register32
+ ISP_GPR2 = 0x2104178, Register32
+ ISP_GPR3 = 0x210417c, Register32
+ ISP_GPR4 = 0x2104180, Register32
+ ISP_GPR5 = 0x2104184, Register32
+ ISP_GPR6 = 0x2104188, Register32
+ ISP_GPR7 = 0x210418c, Register32
+
+ ISP_DOORBELL_RING0 = 0x21043f0, Register32
+ ISP_IRQ_INTERRUPT_ACK = 0x21043fc, Register32
+
+ ISP_SMBUS_REG_MTXFIFO = irange(0x2110000, 4, 0x1000), Register32
+ ISP_SMBUS_REG_MRXFIFO = irange(0x2110004, 4, 0x1000), Register32
+ ISP_SMBUS_REG_UNK_1 = irange(0x2110008, 4, 0x1000), Register32
+ ISP_SMBUS_REG_UNK_2 = irange(0x211000c, 4, 0x1000), Register32
+ ISP_SMBUS_REG_UNK_3 = irange(0x2110010, 4, 0x1000), Register32
+ ISP_SMBUS_REG_SMSTA = irange(0x2110014, 4, 0x1000), Register32
+ ISP_SMBUS_REG_UNK_4 = irange(0x2110018, 4, 0x1000), Register32
+ ISP_SMBUS_REG_CTL = irange(0x211001c, 4, 0x1000), Register32
+ ISP_SMBUS_REG_UNK_5 = irange(0x2110020, 4, 0x1000), Register32
+ ISP_SMBUS_REG_UNK_6 = irange(0x2110024, 4, 0x1000), Register32
+ ISP_SMBUS_REG_REV = irange(0x2110028, 4, 0x1000), Register32
+ ISP_SMBUS_REG_UNK_7 = irange(0x211002c, 4, 0x1000), Register32
+ ISP_SMBUS_REG_UNK_8 = irange(0x2110030, 4, 0x1000), Register32
+ ISP_SMBUS_REG_UNK_9 = irange(0x2110034, 4, 0x1000), Register32
+ ISP_SMBUS_REG_UNK_A = irange(0x2110038, 4, 0x1000), Register32
+ ISP_SMBUS_REG_UNK_B = irange(0x211003c, 4, 0x1000), Register32
+
+ ISP_DPE_REG_UNK1 = 0x2504000, Register32
+ ISP_DPE_REG_UNK2 = 0x2508000, Register32
+
+ ISP_CPU_BUFFER = 0x1050000, Register32
+
+ ISP_SPMI0_REGISTER_BASE = 0x2900000, Register32
+ ISP_SPMI1_REGISTER_BASE = 0x2920000, Register32
+ ISP_SPMI2_REGISTER_BASE = 0x2940000, Register32
+
+class PSReg(RegMap):
+ PMU_UNKNOWN0 = 0x4000, ISP_PMU
+ PMU_UNKNOWN1 = 0x4008, ISP_PMU
+ PMU_UNKNOWN2 = 0x4010, ISP_PMU
+ PMU_UNKNOWN3 = 0x4018, ISP_PMU
+ PMU_UNKNOWN4 = 0x4020, ISP_PMU
+ PMU_UNKNOWN5 = 0x4028, ISP_PMU
+ PMU_UNKNOWN6 = 0x4030, ISP_PMU
+ PMU_UNKNOWN7 = 0x4038, ISP_PMU
+ PMU_UNKNOWN8 = 0x4040, ISP_PMU
+ PMU_UNKNOWN9 = 0x4048, ISP_PMU
+ PMU_UNKNOWNA = 0x4050, ISP_PMU
+ PMU_UNKNOWNB = 0x4058, ISP_PMU
+ PMU_SPECIAL_STATUS = 0x4060, ISP_PMU_SPECIAL_STATUS
+ CLOCK_TICK_LOW = 0x34004, Register32
+ CLOCK_TICK_HIGH = 0x34008, Register32
+ RT_BANDWIDTH_SCRATCH1 = 0x38014, Register32
+ RT_BANDWIDTH_SCRATCH2 = 0x38018, Register32
+
+class SPMIReg(RegMap):
+ SPMI_UNKNOWN0 = 0x28, Register32
+ SPMI_UNKNOWN1 = 0x40, Register32
+ SPMI_UNKNOWN2 = 0x90, Register32
+ SPMI_UNKNOWN3 = 0x80a0, Register32
+ SPMI_UNKNOWN4 = 0x80a4, Register32 \ No newline at end of file
diff --git a/tools/proxyclient/m1n1/hw/jpeg.py b/tools/proxyclient/m1n1/hw/jpeg.py
new file mode 100644
index 0000000..c92bfe0
--- /dev/null
+++ b/tools/proxyclient/m1n1/hw/jpeg.py
@@ -0,0 +1,334 @@
+# SPDX-License-Identifier: MIT
+from ..utils import *
+from enum import IntEnum
+
+
+class R_STATUS(Register32):
+ DONE = 0
+ TIMEOUT = 1
+ RD_BUF_OVERFLOW = 2
+ WR_BUF_OVERFLOW = 3
+ CODEC_BUF_OVERFLOW = 4
+ SOME_KIND_OF_MACROBLOCK_SIZE_ERROR = 5
+ AXI_ERROR = 6
+ UNKNOWN_FLAG = 7
+
+
+class E_CODEC(IntEnum):
+ _444 = 0
+ _422 = 1
+ _411 = 2
+ _420 = 3
+ _400 = 4
+
+
+class R_CODEC(Register32):
+ CODEC = 2, 0, E_CODEC
+
+
+class E_ENCODE_PIXEL_FORMAT(IntEnum):
+ RGB101010 = 0
+ YUV10_linear = 1
+ RGB888 = 2
+ RGB565 = 3
+ YUV_planar = 4 # partially tested, details not understood
+ YUV_linear = 5 # partially tested, details not understood
+
+
+class R_ENCODE_PIXEL_FORMAT(Register32):
+ FORMAT = 4, 0, E_ENCODE_PIXEL_FORMAT
+
+
+class E_SCALE(IntEnum):
+ DIV1 = 0
+ DIV2 = 1
+ DIV4 = 2
+ DIV8 = 3
+
+
+class R_SCALE_FACTOR(Register32):
+ SCALE = 1, 0, E_SCALE
+
+
+class E_DECODE_PIXEL_FORMAT(IntEnum):
+ YUV444_planar = 0
+ YUV422_planar = 1
+ YUV420_planar = 2
+ YUV422_linear = 3
+ _YUV10_broken_doesnt_work = 4
+ RGBA8888 = 5
+ RGB565 = 6
+ _RGB101010_broken_doesnt_work = 7
+
+
+class R_DECODE_PIXEL_FORMAT(Register32):
+ FORMAT = 3, 0, E_DECODE_PIXEL_FORMAT
+
+
+class E_JPEG_IO_FLAGS_SUBSAMPLING(IntEnum):
+ _444 = 0
+ _422 = 1
+ _420 = 2
+ _400 = 3
+ FOUR_COMPONENTS_MODE = 4
+ _411_BROKEN = 6
+
+
+class R_JPEG_IO_FLAGS(Register32):
+ SUBSAMPLING_MODE = 2, 0, E_JPEG_IO_FLAGS_SUBSAMPLING
+ # not sure what this is supposed to do
+ MAKE_DECODE_WORK_BREAK_ENCODE = 3
+ OUTPUT_MACROBLOCKS_UNFLIPPED_H = 4
+ OUTPUT_8BYTE_CHUNKS_CORRECTLY = 5
+
+
+class R_JPEG_OUTPUT_FLAGS(Register32):
+ # bit0 doesn't seem to do anything
+ SKIP_HEADERS = 1 # output only SOS/EOI, no SOI/DQT/SOF0/DHT
+ OUTPUT_SOF0_AFTER_DHT = 2 # output SOF0 after DHT instead of before it
+ # bit3 doesn't seem to do anything
+ COMPRESS_WORSE = 4 # not sure exactly what this does
+
+
+class R_QTBL_SEL(Register32):
+ COMPONENT0 = 1, 0
+ COMPONENT1 = 3, 2
+ COMPONENT2 = 5, 4
+ COMPONENT3 = 7, 6 # guessed
+
+
+class JPEGRegs(RegMap):
+ REG_0x0 = 0x0, Register32
+ REG_0x4 = 0x4, Register32
+ MODE = 0x8, Register32
+ REG_0xc = 0xc, Register32
+
+ REG_0x10 = 0x10, Register32
+ REG_0x14 = 0x14, Register32
+ REG_0x18 = 0x18, Register32
+ # REG_0x1c = 0x1c, Register32
+
+ REG_0x20 = 0x20, Register32
+ STATUS = 0x24, R_STATUS
+
+ CODEC = 0x28, R_CODEC
+
+ REG_0x2c = 0x2c, Register32
+ REG_0x30 = 0x30, Register32
+ REG_0x34 = 0x34, Register32
+ # this changes the output drastically if set to 1 for decode
+ # breaks encode if not set to 1
+ REG_0x38 = 0x38, Register32
+
+ # not sure what the difference is. siting? type2 seems to win over type1
+ CHROMA_HALVE_H_TYPE1 = 0x3c, Register32
+ CHROMA_HALVE_H_TYPE2 = 0x40, Register32
+ CHROMA_HALVE_V_TYPE1 = 0x44, Register32
+ CHROMA_HALVE_V_TYPE2 = 0x48, Register32
+
+ # if double and quadruple both set --> double
+ CHROMA_DOUBLE_H = 0x4c, Register32
+ CHROMA_QUADRUPLE_H = 0x50, Register32
+ CHROMA_DOUBLE_V = 0x54, Register32
+
+ # details not fully understood yet
+ PX_USE_PLANE1 = 0x58, Register32
+ PX_TILES_W = 0x5c, Register32
+ PX_TILES_H = 0x60, Register32
+ PX_PLANE0_WIDTH = 0x64, Register32
+ PX_PLANE0_HEIGHT = 0x68, Register32
+ PX_PLANE0_TILING_H = 0x6c, Register32
+ PX_PLANE0_TILING_V = 0x70, Register32
+ PX_PLANE0_STRIDE = 0x74, Register32
+ PX_PLANE1_WIDTH = 0x78, Register32
+ PX_PLANE1_HEIGHT = 0x7c, Register32
+ PX_PLANE1_TILING_H = 0x80, Register32
+ PX_PLANE1_TILING_V = 0x84, Register32
+ PX_PLANE1_STRIDE = 0x88, Register32
+
+ INPUT_START1 = 0x8c, Register32
+ INPUT_START2 = 0x90, Register32
+ REG_0x94 = 0x94, Register32
+ REG_0x98 = 0x98, Register32
+ INPUT_END = 0x9c, Register32
+
+ OUTPUT_START1 = 0xa0, Register32
+ OUTPUT_START2 = 0xa4, Register32
+ OUTPUT_END = 0xa8, Register32
+
+ MATRIX_MULT = irange(0xAC, 11, 4), Register32
+ DITHER = irange(0xD8, 10, 4), Register32
+
+ ENCODE_PIXEL_FORMAT = 0x100, R_ENCODE_PIXEL_FORMAT
+ # RGB888: R, G, B = byte pos
+ # RGB101010: R, G, B = 0/1/2 = low/mid/high bits
+ # RGB565: R, G, B = 0/1/2 = low/mid/high bits
+ # YUV10: Y, U, V = 0/1/2 = low/mid/high bits
+ # YUV linear: Y0 Cb Cr Y1 = byte pos
+ # YUV planar: Y U V = 0 for Y, 0/1 for U/V indicating position somehow
+ ENCODE_COMPONENT0_POS = 0x104, Register32
+ ENCODE_COMPONENT1_POS = 0x108, Register32
+ ENCODE_COMPONENT2_POS = 0x10c, Register32
+ ENCODE_COMPONENT3_POS = 0x110, Register32
+
+ CONVERT_COLOR_SPACE = 0x114, Register32
+
+ REG_0x118 = 0x118, Register32
+ REG_0x11c = 0x11c, Register32
+
+ REG_0x120 = 0x120, Register32
+
+ # details not understood yet
+ TILING_ENABLE = 0x124, Register32
+ TILING_PLANE0 = 0x128, Register32
+ TILING_PLANE1 = 0x12c, Register32
+
+ DECODE_MACROBLOCKS_W = 0x130, Register32
+ DECODE_MACROBLOCKS_H = 0x134, Register32
+ RIGHT_EDGE_PIXELS = 0x138, Register32
+ BOTTOM_EDGE_PIXELS = 0x13c, Register32
+ RIGHT_EDGE_SAMPLES = 0x140, Register32
+ BOTTOM_EDGE_SAMPLES = 0x144, Register32
+
+ SCALE_FACTOR = 0x148, R_SCALE_FACTOR
+
+ DECODE_PIXEL_FORMAT = 0x14c, R_DECODE_PIXEL_FORMAT
+ # 0 = Cb Y'0 Cr Y'1 1 = Y'0 Cb Y'1 Cr
+ YUV422_ORDER = 0x150, Register32
+ # 0 = BGRA 1 = RGBA
+ RGBA_ORDER = 0x154, Register32
+ RGBA_ALPHA = 0x158, Register32
+
+ PLANAR_CHROMA_HALVING = 0x15c, Register32
+
+ REG_0x160 = 0x160, Register32
+ REG_0x164 = 0x164, Register32
+ # REG_0x168 = 0x168, Register32
+ REG_0x16c = 0x16c, Register32
+
+ REG_0x170 = 0x170, Register32
+ # REG_0x174 = 0x174, Register32
+ PERFCOUNTER = 0x178, Register32
+ # REG_0x17c = 0x17c, Register32
+
+ # REG_0x180 = 0x180, Register32
+ TIMEOUT = 0x184, Register32
+ HWREV = 0x188, Register32
+
+ REG_0x18c = 0x18c, Register32
+ REG_0x190 = 0x190, Register32
+ REG_0x194 = 0x194, Register32
+ REG_0x198 = 0x198, Register32
+ REG_0x19c = 0x19c, Register32
+
+ ENABLE_RST_LOGGING = 0x1a0, Register32
+ RST_LOG_ENTRIES = 0x1a4, Register32
+
+ REG_0x1a8 = 0x1a8, Register32
+ REG_0x1ac = 0x1ac, Register32
+ REG_0x1b0 = 0x1b0, Register32
+
+ REG_0x1b4 = 0x1b4, Register32
+ REG_0x1b8 = 0x1b8, Register32
+ REG_0x1bc = 0x1bc, Register32
+
+ REG_0x1c0 = 0x1c0, Register32
+ REG_0x1c4 = 0x1c4, Register32
+
+ REG_0x1c8 = 0x1c8, Register32
+
+ REG_0x1cc = 0x1cc, Register32
+ REG_0x1d0 = 0x1d0, Register32
+ REG_0x1d4 = 0x1d4, Register32
+ REG_0x1d8 = 0x1d8, Register32
+
+ REG_0x1dc = 0x1dc, Register32
+ REG_0x1e0 = 0x1e0, Register32
+ REG_0x1e4 = 0x1e4, Register32
+ REG_0x1e8 = 0x1e8, Register32
+
+ REG_0x1ec = 0x1ec, Register32
+ REG_0x1f0 = 0x1f0, Register32
+ REG_0x1f4 = 0x1f4, Register32
+ REG_0x1f8 = 0x1f8, Register32
+
+ REG_0x1fc = 0x1fc, Register32
+ REG_0x200 = 0x200, Register32
+
+ REG_0x204 = 0x204, Register32
+ REG_0x208 = 0x208, Register32
+
+ REG_0x20c = 0x20c, Register32
+ REG_0x210 = 0x210, Register32
+ REG_0x214 = 0x214, Register32
+ REG_0x218 = 0x218, Register32
+
+ REG_0x21c = 0x21c, Register32
+ REG_0x220 = 0x220, Register32
+
+ REG_0x224 = 0x224, Register32
+ REG_0x228 = 0x228, Register32
+
+ REG_0x22c = 0x22c, Register32
+ REG_0x230 = 0x230, Register32
+ REG_0x234 = 0x234, Register32
+
+ REG_0x238 = 0x238, Register32
+ REG_0x23c = 0x23c, Register32
+ REG_0x240 = 0x240, Register32
+ REG_0x244 = 0x244, Register32
+ REG_0x248 = 0x248, Register32
+
+ REG_0x24c = 0x24c, Register32
+ REG_0x250 = 0x250, Register32
+ REG_0x254 = 0x254, Register32
+ REG_0x258 = 0x258, Register32
+ REG_0x25c = 0x25c, Register32
+
+ REG_0x260 = 0x260, Register32
+ REG_0x264 = 0x264, Register32
+ REG_0x268 = 0x268, Register32
+ REG_0x26c = 0x26c, Register32
+
+ REG_0x280 = 0x280, Register32
+
+ JPEG_IO_FLAGS = 0x1000, R_JPEG_IO_FLAGS
+ REG_0x1004 = 0x1004, Register32
+ REG_0x1008 = 0x1008, Register32
+ QTBL_SEL = 0x100c, R_QTBL_SEL
+
+ # fixme what _exactly_ does this control
+ HUFFMAN_TABLE = 0x1010, Register32
+ RST_INTERVAL = 0x1014, Register32 # 16 bits effective
+ JPEG_HEIGHT = 0x1018, Register32
+ JPEG_WIDTH = 0x101c, Register32
+
+ COMPRESSED_BYTES = 0x1020, Register32
+ JPEG_OUTPUT_FLAGS = 0x1024, R_JPEG_OUTPUT_FLAGS
+ REG_0x1028 = 0x1028, Register32
+ REG_0x102c = 0x102c, Register32
+
+ BITSTREAM_CORRUPTION = 0x1030, Register32
+ # REG_0x1034 = 0x1034, Register32
+ # REG_0x1038 = 0x1038, Register32
+ # REG_0x103c = 0x103c, Register32
+
+ REG_0x1080 = 0x1080, Register32
+ REG_0x1084 = 0x1084, Register32
+ # REG_0x1088 = 0x1088, Register32
+ REG_0x108c = 0x108c, Register32
+ REG_0x1090 = 0x1090, Register32
+
+ SHIKINO_VERSION_MAGIC0 = 0x10e0, Register32
+ SHIKINO_VERSION_MAGIC1 = 0x10e4, Register32
+ SHIKINO_VERSION_MAGIC2 = 0x10e8, Register32
+ SHIKINO_VERSION_MAGIC3 = 0x10ec, Register32
+ SHIKINO_VERSION_MAGIC4 = 0x10f0, Register32
+ # REG_0x10f4 = 0x10f4, Register32
+ # REG_0x10f8 = 0x10f8, Register32
+ # REG_0x10fc = 0x10fc, Register32
+
+ QTBL = irange(0x1100, 64, 4), Register32
+
+ # todo what's the format?
+ RSTLOG = irange(0x2000, 1024, 4), Register32
diff --git a/tools/proxyclient/m1n1/hw/mca.py b/tools/proxyclient/m1n1/hw/mca.py
new file mode 100644
index 0000000..4c68a39
--- /dev/null
+++ b/tools/proxyclient/m1n1/hw/mca.py
@@ -0,0 +1,110 @@
+# SPDX-License-Identifier: MIT
+from ..utils import *
+from enum import IntEnum
+
+class R_STATUS(Register32):
+ EN = 0
+ RST = 1
+
+class R_MCLK_CONF(Register32):
+ SEL = 3, 0
+
+class R_PORT_ENABLES(Register32):
+ CLOCK1 = 1
+ CLOCK2 = 2
+ DATA = 3
+
+class R_PORT_CLKSEL(Register32):
+ SEL = 11, 8
+
+class R_PORT_DATASEL(Register32):
+ TXA0 = 0
+ TXA1 = 2
+ TXA2 = 4
+ TXA3 = 6
+ TXA4 = 8
+ TXA5 = 10
+
+ TXB0 = 1
+ TXB1 = 3
+ TXB2 = 5
+ TXB3 = 7
+ TXB4 = 9
+ TXB5 = 11
+
+class E_SLOT_WIDTH(IntEnum):
+ NONE = 0
+
+ W_16BIT = 0x4
+ W_20BIT = 0x8
+ W_24BIT = 0xc
+ W_32BIT = 0x10
+
+class R_SERDES_CONF(Register32):
+ NSLOTS = 3, 0
+ SLOT_WIDTH = 8, 4, E_SLOT_WIDTH
+
+ BCLK_POL = 10
+ LSB_FIRST = 11
+
+ UNK1 = 12
+ UNK2 = 13
+ IDLE_UNDRIVEN = 14 # TX only
+ NO_DATA_FEEDBACK = 15 # RX only
+
+ SYNC_SEL = 18, 16
+
+class R_INTMASK(Register32):
+ # macOS interested in 0x823c
+ UNK1 = 2 # m
+ UNK2 = 3 # m
+ UNK3 = 4 # m
+ TX_UNDERFLOW = 5 # m
+
+ UNK4 = 9 # m
+ READ_SENSITIVE_UNK1 = 11
+ READ_SENSITIVE_UNK2 = 15 # m
+
+class MCAClusterRegs(RegMap):
+ MCLK_STATUS = 0x0, R_STATUS
+ MCLK_CONF = 0x4, R_MCLK_CONF
+
+ SYNCGEN_STATUS = 0x100, R_STATUS
+ SYNCGEN_MCLK_SEL = 0x104, Register32
+ SYNCGEN_HI_PERIOD = 0x108, Register32
+ SYNCGEN_LO_PERIOD = 0x10c, Register32
+
+ PORT_ENABLES = 0x600, R_PORT_ENABLES
+ PORT_CLK_SEL = 0x604, R_PORT_CLKSEL
+ PORT_DATA_SEL = 0x608, R_PORT_DATASEL
+
+ INTSTATE = 0x700, R_INTMASK
+ INTMASK = 0x704, R_INTMASK
+
+class MCATXSerdesRegs(RegMap):
+ STATUS = 0x0, R_STATUS
+ CONF = 0x4, R_SERDES_CONF
+ BITDELAY = 0x8, Register32
+ CHANMASK = irange(0xc, 4, 4), Register32
+
+class MCARXSerdesRegs(RegMap):
+ STATUS = 0x0, R_STATUS
+ UNK1 = 0x4, Register32
+ CONF = 0x8, R_SERDES_CONF
+ BITDELAY = 0xc, Register32
+ CHANMASK = irange(0x10, 4, 4), Register32
+
+
+class MCACluster:
+ def __init__(self, u, base):
+ self.regs = MCAClusterRegs(u, base)
+ self.txa = MCATXSerdesRegs(u, base + 0x300)
+ self.txb = MCATXSerdesRegs(u, base + 0x500)
+ self.rxa = MCARXSerdesRegs(u, base + 0x200)
+ self.rxb = MCARXSerdesRegs(u, base + 0x400)
+ self.all_regs = [
+ self.regs,
+ self.txa, self.txb,
+ self.rxa, self.rxb
+ ]
+
diff --git a/tools/proxyclient/m1n1/hw/nco.py b/tools/proxyclient/m1n1/hw/nco.py
new file mode 100644
index 0000000..3d1840d
--- /dev/null
+++ b/tools/proxyclient/m1n1/hw/nco.py
@@ -0,0 +1,89 @@
+# SPDX-License-Identifier: MIT
+
+__all__ = ["NCO"]
+
+def galois_lfsr(init, poly):
+ state = init
+ for i in range((1 << poly.bit_length() - 1) - 1):
+ if state & 1:
+ state = (state >> 1) ^ (poly >> 1)
+ else:
+ state = (state >> 1)
+ yield state
+
+def gen_lookup_tables():
+ fwd, inv = dict(), dict()
+ lfsr_states = [0] + list(reversed(list(galois_lfsr(0x7ff, 0xa01))))
+ for cycle, sr_state in enumerate(lfsr_states):
+ fwd[cycle + 2] = sr_state
+ inv[sr_state] = cycle + 2
+ return fwd, inv
+
+
+class NCOChannel:
+ def __init__(self, parent, base):
+ self.parent = parent
+ self.base = base
+ self.p = parent.u.proxy
+
+ def enabled(self):
+ return bool(self.p.read32(self.base) & (1<<31))
+
+ def enable(self):
+ self.p.set32(self.base, 1<<31)
+
+ def disable(self):
+ self.p.clear32(self.base, 1<<31)
+
+ def set_rate(self, target):
+ was_enabled = self.enabled()
+ for off, val in enumerate(NCO.calc_regvals(self.parent.fin, target)):
+ self.p.write32(self.base + off*4, val)
+ if was_enabled:
+ self.enable()
+
+ def get_rate(self):
+ return NCO.calc_rate(self.parent.fin,
+ [self.p.read32(self.base + off*4) for off in range(4)]
+ )
+
+ def __repr__(self):
+ return f"<NCO channel @ 0x{self.base:x}>"
+
+
+class NCO:
+ TBL, TBL_INV = gen_lookup_tables()
+
+ @classmethod
+ def calc_rate(self, fin, regvals):
+ try:
+ div = self.TBL_INV[regvals[1] >> 2] << 2 | regvals[1] & 3
+ except KeyError:
+ raise ValueError("bad configuration")
+ inc1 = regvals[2]
+ inc2 = regvals[3] - 0x1_0000_0000
+ return 2 * fin * (inc1 - inc2) // (div * (inc1 - inc2) + inc1)
+
+ @classmethod
+ def calc_regvals(self, fin, fout):
+ div = 2 * fin // fout
+ inc1 = (2 * fin - div * fout)
+ inc2 = inc1 - fout
+ try:
+ return [0, self.TBL[div >> 2] << 2 | div & 3, inc1, inc2 + 0x1_0000_0000]
+ except KeyError:
+ raise ValueError("target rate out of range")
+
+ def __init__(self, u, devpath, stride=0x4000):
+ self.u = u
+ node = u.adt[devpath]
+ self.fin = u.adt["/arm-io"].clock_frequencies[node.clock_ids[0] - 256]
+
+ reg = node.get_reg(0)
+ self.chans = [
+ NCOChannel(self, base)
+ for base in range(reg[0], reg[0] + reg[1], stride)
+ ]
+
+ def __getitem__(self, idx):
+ return self.chans[idx]
diff --git a/tools/proxyclient/m1n1/hw/pmgr.py b/tools/proxyclient/m1n1/hw/pmgr.py
new file mode 100644
index 0000000..85ba247
--- /dev/null
+++ b/tools/proxyclient/m1n1/hw/pmgr.py
@@ -0,0 +1,71 @@
+# SPDX-License-Identifier: MIT
+from ..utils import *
+
+class R_PSTATE(Register32):
+ RESET = 31
+ AUTO_ENABLE = 28
+ AUTO_STATE = 27, 24
+ PARENT_MISSING = 11
+ DEV_DISABLE = 10
+ WAS_CLKGATED = 9
+ WAS_PWRGATED = 8
+ ACTUAL = 7, 4
+ DESIRED = 3, 0
+
+class R_PWRGATE(Register32):
+ GATE = 31
+
+class R_CLK_CFG(Register32):
+ UNK31 = 31
+ SRC = 30, 24
+ UNK20 = 20
+ UNK8 = 8
+ UNK0 = 7, 0
+
+class PMGRRegs0(RegMap):
+ PS3 = irange(0x0000, 10, 8), R_PSTATE
+ PS4 = irange(0x0200, 32, 8), R_PSTATE
+ PS5 = irange(0x0300, 32, 8), R_PSTATE
+ PS6 = irange(0x0c00, 2, 8), R_PSTATE
+ PS7 = irange(0x4000, 13, 8), R_PSTATE
+ PS8 = irange(0x8000, 5, 8), R_PSTATE
+ PS9 = irange(0xc000, 7, 8), R_PSTATE
+ PS10 = irange(0x10000, 10, 8), R_PSTATE
+ PS11 = irange(0x100, 32, 8), R_PSTATE
+ PS12 = irange(0x400, 15, 8), R_PSTATE
+
+ PG1 = irange(0x1c010, 16, 8), R_PWRGATE
+ PG1CFG = (irange(0x1c090, 69, 24), irange(0, 6, 4)), Register32
+
+ CPUTVM0 = 0x48000, Register32
+ CPUTVM1 = 0x48c00, Register32
+ CPUTVM2 = 0x48800, Register32
+ CPUTVM3 = 0x48400, Register32
+
+class PMGRRegs1(RegMap):
+ PS0 = irange(0x58, 32, 8), R_PSTATE
+ PS1 = irange(0x4000, 32, 8), R_PSTATE
+ PS2 = irange(0x8000, 32, 8), R_PSTATE
+
+ PG0 = irange(0x1c010, 32, 8), R_PWRGATE
+
+class PMGRRegs2(RegMap):
+ CLK_CFG0 = irange(0x40000, 86, 4), R_CLK_CFG
+ CLK_CFG1 = irange(0x40200, 8, 4), R_CLK_CFG
+ CLK_CFG2 = irange(0x40280, 2, 4), R_CLK_CFG
+
+class PMGR:
+ def __init__(self, u):
+ self.u = u
+ self.p = u.proxy
+ self.iface = u.iface
+ self.node = u.adt["/arm-io/pmgr"]
+ self.regs = [
+ PMGRRegs0(u, self.node.get_reg(0)[0]),
+ PMGRRegs1(u, self.node.get_reg(1)[0]),
+ PMGRRegs2(u, self.node.get_reg(2)[0]),
+ ]
+
+ def dump_all(self):
+ for i in self.regs:
+ i.dump_regs()
diff --git a/tools/proxyclient/m1n1/hw/pmu.py b/tools/proxyclient/m1n1/hw/pmu.py
new file mode 100644
index 0000000..b545df4
--- /dev/null
+++ b/tools/proxyclient/m1n1/hw/pmu.py
@@ -0,0 +1,36 @@
+# SPDX-License-Identifier: MIT
+import struct
+
+from ..utils import *
+from .spmi import SPMI
+
+__all__ = ["PMU"]
+
+class PMU:
+
+ def __init__(self, u, adt_path=None):
+ self.u = u
+ if adt_path is None:
+ adt_path = PMU.find_primary_pmu(u.adt)
+
+ self.node = u.adt[adt_path]
+ self.spmi = SPMI(u, adt_path.rpartition('/')[0])
+ self.adt_path = adt_path
+ self.primary = u.adt[adt_path].is_primary == 1
+ self.reg = u.adt[adt_path].reg[0]
+
+ def reset_panic_counter(self):
+ if self.primary:
+ leg_scrpad = self.node.info_leg__scrpad[0]
+ self.spmi.write8(self.reg, leg_scrpad + 2, 0) # error counts
+
+ @staticmethod
+ def find_primary_pmu(adt):
+ for child in adt["/arm-io"]:
+ if child.name.startswith("nub-spmi"):
+ for pmu in child:
+ compat = getattr(pmu, "compatible")[0] if hasattr(pmu, "compatible") else "unset"
+ primary = (getattr(pmu, "is-primary") == 1) if hasattr(pmu, "is-primary") else False
+ if compat == "pmu,spmi" and primary:
+ return pmu._path.removeprefix('/device-tree')
+ raise KeyError(f"primary 'pmu,spmi' node not found")
diff --git a/tools/proxyclient/m1n1/hw/prores.py b/tools/proxyclient/m1n1/hw/prores.py
new file mode 100644
index 0000000..5985e72
--- /dev/null
+++ b/tools/proxyclient/m1n1/hw/prores.py
@@ -0,0 +1,250 @@
+# SPDX-License-Identifier: MIT
+from ..utils import *
+from collections import namedtuple
+from enum import IntEnum
+
+
+EncodeNotRawDescriptor = namedtuple('EncodeNotRawDescriptor', [
+ 'flags', # +0x000
+ # [31:16] ?????
+ # 13 ?????
+ # 12 ?????
+ # [11:9] ?????
+ # 8 - enable alpha
+ # 7 - alpha channel bpp
+ # 0 -> 8bpp
+ # 1 -> 16bpp
+ # 6 - something unknown about tiling
+ # [5:4] ?????
+ # [3:2] - chroma subsampling
+ # 00 -> broken?
+ # 01 -> broken?
+ # 10 -> 4:2:2
+ # 11 -> 4:4:4
+ # [1:0] - input bpp
+ # 00 -> 8bpp
+ # 01 -> 16bpp?
+ # 10 -> 16bpp?
+ # 11 -> 16bpp?
+ # the last three all produce slightly differnet outputs
+ # so might be 10/12/14/16?????
+ 'flags2', # +0x004
+ 'output_iova', # +0x008
+ 'max_out_sz', # +0x010
+ 'offset_x', # +0x014
+ 'offset_y', # +0x016
+ 'pix_surface_w_2_', # +0x018
+ 'pix_surface_h_2_', # +0x01a
+ 'pix_surface_w', # +0x01c
+ 'pix_surface_h', # +0x01e
+ 'luma_stride', # +0x020
+ 'chroma_stride', # +0x022
+ 'alpha_stride', # +0x024
+ 'unk_pad_0x26_', # +0x026
+ 'luma_iova', # +0x028
+ 'pix_plane0_tileheader_thing_', # +0x030
+ 'chroma_iova', # +0x038
+ 'pix_plane1_tileheader_thing_', # +0x040
+ 'alpha_iova', # +0x048
+ 'pix_plane2_tileheader_thing_', # +0x050
+ 'frame_header_sz', # +0x058
+ 'unk_pad_0x5a_', # +0x05a
+ 'bitstream_version', # +0x05b
+ 'encoder_identifier', # +0x05c
+ 'pix_surface_w_byteswap_', # +0x060
+ 'pix_surface_h_byteswap_', # +0x062
+ 'chroma_format_interlace_mode', # +0x064
+ 'aspect_ratio_frame_rate', # +0x065
+ 'color_primaries', # +0x066
+ 'transfer_characteristic', # +0x067
+ 'matrix_coefficients', # +0x068
+ 'alpha_channel_type', # +0x069
+ 'frame_hdr_reserved14', # +0x06a
+ 'unk_pad_0x6c_', # +0x06c
+ 'deprecated_number_of_slices', # +0x0ec
+ 'log2_desired_slice_size_in_mb', # +0x0ee
+ 'quantization_index', # +0x0ef
+ 'unk_0xf0_', # +0x0f0
+ 'unk_0xf2_', # +0x0f2
+ 'unk_0xf4_', # +0x0f4
+ 'unk_0xfc_', # +0x0fc
+ 'unk_0x100_0_', # +0x100
+ 'unk_0x100_1_', # +0x104
+ 'unk_0x100_2_', # +0x108
+ 'unk_0x100_3_', # +0x10c
+ 'unk_0x110_0_', # +0x110
+ 'unk_0x110_1_', # +0x114
+ 'unk_0x110_2_', # +0x118
+ 'unk_0x110_3_', # +0x11c
+ 'unk_0x110_4_', # +0x120
+ 'unk_0x110_5_', # +0x124
+ 'unk_0x110_6_', # +0x128
+ 'unk_0x110_7_', # +0x12c
+ 'unk_0x110_8_', # +0x130
+ 'unk_0x110_9_', # +0x134
+ 'unk_0x110_10_', # +0x138
+ 'unk_0x110_11_', # +0x13c
+ 'unk_0x110_12_', # +0x140
+ 'unk_0x110_13_', # +0x144
+ 'unk_0x110_14_', # +0x148
+ 'unk_0x110_15_', # +0x14c
+ 'quant_table_sel', # +0x150
+ # upper nibble: quality / table index
+ # lower nibble UNKNOWN!
+ 'unk_pad_0x154_', # +0x154
+])
+ENCODE_NOT_RAW_STRUCT = "<IIQIHHHHHHHHH2sQQQQQQH1sBIHHBBBBBB2s128sHBBHHQIIIIIIIIIIIIIIIIIIIIII44s"
+
+
+class ProResRegs(RegMap):
+ # something reads
+ REG_0x0 = 0x000, Register32
+ MODE = 0x008, Register32 # 4 bits
+ IRQ_ENABLE = 0x00c, Register32 # 2 bits
+ IRQ_STATUS = 0x010, Register32
+
+ ST0 = 0x014, Register32 # interrupt handler reads
+ ST1 = 0x018, Register32 # interrupt handler reads
+ REG_0x1c = 0x01c, Register32 # interrupt handler reads
+ REG_0x38 = 0x038, Register32 # exists, maybe RO
+ REG_0x3c = 0x03c, Register32 # interrupt handler reads
+ REG_0x40 = 0x040, Register32 # exists, maybe RO, looks like 0x44
+ REG_0x44 = 0x044, Register32 # interrupt handler reads
+ REG_0x48 = 0x048, Register32 # exists, maybe RO, looks like 0x44
+ REG_0x4c = 0x04c, Register32 # exists, maybe RO, looks like 0x44
+ REG_0x50 = 0x050, Register32 # exists, maybe RO, looks like 0x44
+ REG_0x54 = 0x054, Register32 # exists, maybe RO, looks like 0x44
+
+ DR_SIZE = 0x100, Register32
+ DR_ADDR_LO = 0x104, Register32
+ DR_ADDR_HI = 0x108, Register32
+ DR_HEAD = 0x10c, Register32 # bit24 is special, something about wrapping around?
+ DR_TAIL = 0x110, Register32
+
+ # This giant block may or may not be touched by tunables
+ # Function is all unknown
+ REG_0x114 = 0x114, Register32 # can set bits 0000FFFF
+ REG_0x118 = 0x118, Register32 # can set bits 07FF07FF
+
+ REG_0x134 = 0x134, Register32 # can set bits 00000003
+
+ REG_0x144 = 0x144, Register32 # can set bits 00000001
+ REG_0x148 = 0x148, Register32 # can set bits 00000001
+
+ REG_0x160 = 0x160, Register32 # can set bits BFFF3FFF
+ REG_0x164 = 0x164, Register32 # can set bits 07FF07FF
+
+ REG_0x170 = 0x170, Register32 # can set bits BFFF3FFF
+ REG_0x174 = 0x174, Register32 # can set bits 07FF07FF
+
+ REG_0x180 = 0x180, Register32 # can set bits BFFF3FFF
+ REG_0x184 = 0x184, Register32 # can set bits 07FF07FF
+
+ REG_0x190 = 0x190, Register32 # can set bits BFFF3FFF
+ REG_0x194 = 0x194, Register32 # can set bits 000000FF
+ REG_0x198 = 0x198, Register32 # RO? init value 07FB066F
+
+ REG_0x1a0 = 0x1a0, Register32 # can set bits BFFF3FFF
+ REG_0x1a4 = 0x1a4, Register32 # can set bits 000000FF
+ REG_0x1a8 = 0x1a8, Register32 # RO? init value 037C03EE
+
+ REG_0x1b0 = 0x1b0, Register32 # can set bits BFFF3FFF
+ REG_0x1b4 = 0x1b4, Register32 # can set bits 000000FF
+ REG_0x1b8 = 0x1b8, Register32 # RO? init value 04E00377
+
+ REG_0x1c0 = 0x1c0, Register32 # can set bits BFFF3FFF
+ REG_0x1c4 = 0x1c4, Register32 # can set bits 000000FF
+ REG_0x1c8 = 0x1c8, Register32 # RO? init value 051C00DA
+
+ REG_0x1d0 = 0x1d0, Register32 # can set bits BFFF3FFF
+ REG_0x1d4 = 0x1d4, Register32 # can set bits 000000FF
+ REG_0x1d8 = 0x1d8, Register32 # can set bits 000000FF
+ REG_0x1dc = 0x1dc, Register32 # can set bits 00FFFFFF
+
+ REG_0x1ec = 0x1ec, Register32 # can set bits FFFFFFFF
+
+ REG_0x270 = 0x270, Register32 # can set bits BFFF3FFF
+ REG_0x274 = 0x274, Register32 # can set bits 07FF07FF
+ REG_0x278 = 0x278, Register32 # can set bits FFFFFFC0
+ REG_0x27c = 0x27c, Register32 # can set bits 000003FF
+ REG_0x280 = 0x280, Register32 # can set bits FFFFFFC0
+ REG_0x284 = 0x284, Register32 # can set bits FFFFFFC0
+ REG_0x28c = 0x28c, Register32 # can set bits FFFFFFC0
+
+ REG_0x290 = 0x290, Register32 # can set bits BFFF3FFF
+ REG_0x294 = 0x294, Register32 # can set bits 000000FF
+ REG_0x298 = 0x298, Register32 # RO? init value 07FB066F
+
+ REG_0x2a0 = 0x2a0, Register32 # can set bits BFFF3FFF
+ REG_0x2a4 = 0x2a4, Register32 # can set bits 000000FF
+ REG_0x2a8 = 0x2a8, Register32 # RO? init value 037C03EE
+
+ REG_0x2b0 = 0x2b0, Register32 # can set bits BFFF3FFF
+ REG_0x2b4 = 0x2b4, Register32 # can set bits 000000FF
+ REG_0x2b8 = 0x2b8, Register32 # RO? init value 04E00377
+
+ REG_0x2c0 = 0x2c0, Register32 # can set bits BFFF3FFF
+ REG_0x2c4 = 0x2c4, Register32 # can set bits 000000FF
+ REG_0x2c8 = 0x2c8, Register32 # RO? init value 051C00DA
+
+ REG_0x2d0 = 0x2d0, Register32 # can set bits FFFFFFFD, CANNOT clear 00000011
+ REG_0x2d4 = 0x2d4, Register32 # can set bits 00000001
+ REG_0x2d8 = 0x2d8, Register32 # can set bits FFFF0007
+ REG_0x2dc = 0x2dc, Register32 # RO? init value 07FB066F
+ REG_0x2e0 = 0x2e0, Register32 # can set bits 07FF07FF
+
+ REG_0x2f0 = 0x2f0, Register32 # can set bits FFFFFFFF
+
+ REG_0x2f8 = 0x2f8, Register32 # can set bits FFFFFFFD, CANNOT clear 00000011
+ REG_0x2fc = 0x2fc, Register32 # can set bits 00000001
+ REG_0x300 = 0x300, Register32 # can set bits FFFF0007
+ REG_0x304 = 0x304, Register32 # RO? init value 037C03EE
+ REG_0x308 = 0x308, Register32 # can set bits 07FF07FF
+
+ REG_0x318 = 0x318, Register32 # can set bits FFFFFFFF
+
+ REG_0x320 = 0x320, Register32 # can set bits FFFFFFFD, CANNOT clear 00000011
+ REG_0x324 = 0x324, Register32 # can set bits 00000001
+ REG_0x328 = 0x328, Register32 # can set bits FFFF0007
+ REG_0x32c = 0x32c, Register32 # RO? init value 04E00377
+ REG_0x330 = 0x330, Register32 # can set bits 07FF07FF
+
+ REG_0x340 = 0x340, Register32 # can set bits FFFFFFFF
+
+ REG_0x350 = 0x350, Register32 # can set bits BFFF3FFF
+ REG_0x354 = 0x354, Register32 # can set bits 07FF07FF
+ REG_0x358 = 0x358, Register32 # can set bits FFFFFFC0
+ REG_0x35c = 0x35c, Register32 # can set bits 000003FF
+ REG_0x360 = 0x360, Register32 # can set bits FFFFFFC0
+ REG_0x364 = 0x364, Register32 # can set bits FFFFFFC0
+ REG_0x368 = 0x368, Register32 # can set bits FFFFFFC0
+
+ REG_0x370 = 0x370, Register32 # can set bits BFFF3FFF
+ REG_0x374 = 0x374, Register32 # can set bits 07FF07FF
+
+
+ QUANT_LUMA_EHQ = irange(0x0800, 32, 4), Register32
+ QUANT_LUMA_HQ = irange(0x0880, 32, 4), Register32
+ QUANT_LUMA_NQ = irange(0x0900, 32, 4), Register32
+ QUANT_LUMA_LT = irange(0x0980, 32, 4), Register32
+ QUANT_LUMA_PROXY = irange(0x0A00, 32, 4), Register32
+ QUANT_CHROMA_EHQ = irange(0x1000, 32, 4), Register32
+ QUANT_CHROMA_HQ = irange(0x1080, 32, 4), Register32
+ QUANT_CHROMA_NQ = irange(0x1100, 32, 4), Register32
+ QUANT_CHROMA_LT = irange(0x1180, 32, 4), Register32
+ QUANT_CHROMA_PROXY = irange(0x1200, 32, 4), Register32
+
+ # wtf, writing to this doesn't actually work? do we have to enable it?
+ DC_QUANT_SCALE = irange(0x1800, 112, 4), Register32
+
+ REG_0x19c0 = 0x19c0, Register32 # unknown, all 1s, RO?
+ REG_0x19c4 = 0x19c4, Register32 # unknown, all 1s, RO?
+ REG_0x19c8 = 0x19c8, Register32 # unknown, all 1s, RO?
+ REG_0x19cc = 0x19cc, Register32 # unknown, all 1s, RO?
+ REG_0x19d0 = 0x19d0, Register32 # unknown, all 1s, RO?
+ REG_0x19d4 = 0x19d4, Register32 # unknown, all 1s, RO?
+ REG_0x19d8 = 0x19d8, Register32 # unknown, all 1s, RO?
+ REG_0x19dc = 0x19dc, Register32 # unknown, can set bits 00000001
+
+ # Unknown, inits to 0x12345678, can R/W
+ REG_0x1A00 = 0x1a00, Register32
diff --git a/tools/proxyclient/m1n1/hw/scaler.py b/tools/proxyclient/m1n1/hw/scaler.py
new file mode 100644
index 0000000..714f366
--- /dev/null
+++ b/tools/proxyclient/m1n1/hw/scaler.py
@@ -0,0 +1,212 @@
+# SPDX-License-Identifier: MIT
+from ..utils import *
+from enum import IntEnum
+
+
+### NOTE: This is "MSR10j" (M1 Max), and there definitely *ARE* differences from M1
+
+class R_IRQS(Register32):
+ DONE = 0
+ DBGSTS_ERROR = 1
+ READ_ERROR = 3
+ # This doesn't actually trigger on bad IOVAs?
+ WRITE_ERROR_MAYBE = 4
+ DECOMPRESSION_ERROR = 9
+ CONTEXT_SWITCH = 10
+ _BIT11 = 11
+ AXI_ERROR = 12
+ _BIT13 = 13
+
+
+class E_ROTATE(IntEnum):
+ # clockwise rotations
+ ROT_0 = 0
+ ROT_90 = 1
+ ROT_180 = 2
+ ROT_270 = 3
+
+
+class R_FLIP_ROTATE(Register32):
+ ROTATE = 1, 0, E_ROTATE
+ FLIP_UPDOWN = 2
+ FLIP_LEFTRIGHT = 3
+
+
+class R_SCALE_FLAGS(Register32):
+ EN = 0
+ MAKE_THE_OUTPUT_YELLOW = 1
+ # only when bit1 is set, only on H scaling
+ MAKE_A_BLUE_LINE_APPEAR = 4
+
+
+class ScalerMainRegs(RegMap):
+ # on startup 1 will be written followed by 0
+ # but it's not clear if that actually does anything
+ HW_VERSION = 0x00000, Register32
+ # bit0 = normal, bit1 = apiodma related
+ # if things are reset here, reading all other regs will *HANG*
+ RESET = 0x00004, Register32
+ # can set 0x1f00
+ RESET_APIODMA_RELATED = 0x00008, Register32
+ IS_RUNNING = 0x0000c, Register32
+ # writable, can set to 0xfff
+ REG_0x10 = 0x00010, Register32
+ REGISTER_FIFO_AVAILABILITY = 0x00014, Register32
+ # XNU sets 0x121b, we can at most set 0x3e1b
+ IRQ_ENABLE = 0x00018, R_IRQS
+ MSR_GLBL_IRQSTS = 0x0001c, R_IRQS
+ FRAME_COUNT = 0x00020, Register32
+
+ # can set 7
+ REG_0x58 = 0x00058, Register32
+
+ # can set 0xffff
+ REG_0x74 = 0x00074, Register32
+
+ # 1, or 3 if readonly??
+ START = 0x00080, Register32
+ # can set all bits
+ REG_0x84 = 0x00084, Register32
+ # can set all bits
+ REG_0x88 = 0x00088, Register32
+ # can set 0x8000ffff
+ REG_0x8c = 0x0008c, Register32
+
+ # can set all bits
+ REG_0x98 = 0x00098, Register32
+ # 0x3f3d?
+ MSR_CTRL_DBGSTS = 0x0009c, Register32
+ # can set 3
+ REG_0xa0 = 0x000a0, Register32
+ PROFILING_RELATED = 0x000a4, Register32
+
+ # Can set bits 0/1/2
+ # Does something breaking horizontal scaling
+ # bit2 seems to affect alpha output
+ PIXEL_AVERAGING = 0x000e4, Register32
+
+ TRANSFORM_ID = 0x00110, Register32
+
+ RDMA_THING0 = 0x00180, Register32
+ RDMA_THING1 = 0x00184, Register32
+ RDMA_THING2 = 0x00188, Register32
+ RDMA_THING3 = 0x0018c, Register32
+ RDMA_THING4 = 0x00190, Register32
+
+ # there's probably another source plane existing?
+ SRC_PLANE0_LO = 0x00198, Register32
+ SRC_PLANE0_HI = 0x0019c, Register32
+ SRC_PLANE1_LO = 0x001a0, Register32
+ SRC_PLANE1_HI = 0x001a4, Register32
+ SRC_PLANE2_LO = 0x001a8, Register32
+ SRC_PLANE2_HI = 0x001ac, Register32
+
+ SRC_PLANE0_COMPRESSEDTHING_LO = 0x001b8, Register32
+ SRC_PLANE0_COMPRESSEDTHING_HI = 0x001bc, Register32
+ SRC_PLANE1_COMPRESSEDTHING_LO = 0x001c0, Register32
+ SRC_PLANE1_COMPRESSEDTHING_HI = 0x001c4, Register32
+ SRC_PLANE2_COMPRESSEDTHING_LO = 0x001c8, Register32
+ SRC_PLANE2_COMPRESSEDTHING_HI = 0x001cc, Register32
+
+ SRC_PLANE0_STRIDE = 0x001d8, Register32
+ SRC_PLANE1_STRIDE = 0x001dc, Register32
+ SRC_PLANE2_STRIDE = 0x001e0, Register32
+
+ # seems to be in "pixels"
+ SRC_PLANE0_OFFSET = 0x001e8, Register32
+ SRC_PLANE1_OFFSET = 0x001ec, Register32
+ SRC_PLANE2_OFFSET = 0x001f0, Register32
+
+ SRC_SWIZZLE = 0x001f8, Register32
+ SRC_W = 0x001fc, Register32
+ SRC_H = 0x00200, Register32
+ CACHE_HINTS_THING0 = irange(0x00204, 4, 4), Register32
+ CACHE_HINTS_THING1 = irange(0x00214, 4, 4), Register32
+ TUNABLES_THING0 = irange(0x00224, 4, 4), Register32
+ SRC_SIZE_THING2 = 0x00234, Register32
+ SRC_SIZE_THING3 = 0x00238, Register32
+ SRC_SIZE_THING4 = 0x0023c, Register32
+
+ SRC_SIZE_THING5 = 0x00244, Register32
+ SRC_SIZE_THING6 = 0x00248, Register32
+ SRC_SIZE_THING7 = 0x0024c, Register32
+
+ WDMA_THING0 = 0x00280, Register32
+ WDMA_THING1 = 0x00284, Register32
+ WDMA_THING2 = 0x00288, Register32
+ WDMA_THING3 = 0x0028c, Register32
+ DST_PLANE0_LO = 0x00290, Register32
+ DST_PLANE0_HI = 0x00294, Register32
+ DST_PLANE1_LO = 0x00298, Register32
+ DST_PLANE1_HI = 0x0029c, Register32
+ DST_PLANE2_LO = 0x002a0, Register32
+ DST_PLANE2_HI = 0x002a4, Register32
+ DST_PLANE0_COMPRESSEDTHING_LO = 0x002a8, Register32
+ DST_PLANE0_COMPRESSEDTHING_HI = 0x002ac, Register32
+ DST_PLANE1_COMPRESSEDTHING_LO = 0x002b0, Register32
+ DST_PLANE1_COMPRESSEDTHING_HI = 0x002b4, Register32
+ DST_PLANE2_COMPRESSEDTHING_LO = 0x002b8, Register32
+ DST_PLANE2_COMPRESSEDTHING_HI = 0x002bc, Register32
+ DST_PLANE0_STRIDE = 0x002c0, Register32
+ DST_PLANE1_STRIDE = 0x002c4, Register32
+ DST_PLANE2_STRIDE = 0x002c8, Register32
+ DST_PLANE0_OFFSET = 0x002cc, Register32
+ DST_PLANE1_OFFSET = 0x002d0, Register32
+ DST_PLANE2_OFFSET = 0x002d4, Register32
+ DST_SWIZZLE = 0x002d8, Register32
+ DST_W = 0x002dc, Register32
+ DST_H = 0x002e0, Register32
+ # uhh is there a macos bug with these? last val always overwritten
+ CACHE_HINTS_THING2 = irange(0x002e4, 3, 4), Register32
+ CACHE_HINTS_THING3 = irange(0x002f0, 3, 4), Register32
+ TUNABLES_THING1 = irange(0x002fc, 3, 4), Register32
+ DST_SIZE_THING2 = 0x00308, Register32
+ DST_SIZE_THING3 = 0x0030c, Register32
+ DST_SIZE_THING4 = 0x00310, Register32
+ DST_SIZE_THING5 = 0x00314, Register32
+ DST_SIZE_THING6 = 0x00318, Register32
+ DST_SIZE_THING7 = 0x0031c, Register32
+
+ FLIP_ROTATE = 0x00380, R_FLIP_ROTATE
+
+ # can set bit 0/1
+ # the output obviously changes when this is set
+ PSEUDO_LINEAR_SCALING = 0x00480, Register32
+
+ SCALE_V_FLAGS = 0x01000, R_SCALE_FLAGS
+ # No idea what a DDA is? Q1.22 or U1.22 (23 bits total)
+ # Also macOS doesn't touch a bunch of the V ones (uses H instead???)
+ SCALE_V_DDA_THING0 = 0x01004, Register32
+ SCALE_V_DDA_THING1 = 0x01008, Register32
+ # Q4.22 or U4.22 (26 bits total)
+ SCALE_V_RATIO_0 = 0x0100c, Register32
+ SCALE_V_RATIO_1 = 0x01010, Register32
+ SCALE_V_RATIO_2 = 0x01014, Register32
+ SCALE_V_RATIO_3 = 0x01018, Register32
+ SCALE_V_DDA_THING2 = 0x0101c, Register32
+ SCALE_V_RATIO_4 = 0x01020, Register32
+ SCALE_V_RATIO_5 = 0x01024, Register32
+
+ # 9 taps, 32 phases polyphase resampling filter
+ # Q4.12 (16-bit total) fixed point filter coeffs
+ # packed into 32-bit registers, 3 sets of filters total (chroma/luma/alpha??)
+ # exact ordering and arithmetic performed not yet clear
+ SCALE_FILTER_V_BLOCK0 = irange(0x01400, 9 * 32, 4), Register32
+ SCALE_FILTER_V_BLOCK1 = irange(0x01c00, 9 * 32 // 2, 4), Register32
+
+ SCALE_H_FLAGS = 0x02000, R_SCALE_FLAGS
+ # No idea what a DDA is? Q1.22 or U1.22 (23 bits total)
+ SCALE_H_DDA_THING0 = 0x02004, Register32
+ SCALE_H_DDA_THING1 = 0x02008, Register32
+ # Q4.22 or U4.22 (26 bits total)
+ SCALE_H_RATIO_0 = 0x0200c, Register32
+ SCALE_H_RATIO_1 = 0x02010, Register32
+ SCALE_H_RATIO_2 = 0x02014, Register32
+ SCALE_H_RATIO_3 = 0x02018, Register32
+ SCALE_H_DDA_THING2 = 0x0201c, Register32
+ SCALE_H_RATIO_4 = 0x02020, Register32
+ SCALE_H_RATIO_5 = 0x02024, Register32
+
+ # 15 taps, 32 phases polyphase resampling filter
+ SCALE_FILTER_H_BLOCK0 = irange(0x02400, 15 * 32, 4), Register32
+ SCALE_FILTER_H_BLOCK1 = irange(0x02c00, 15 * 32 // 2, 4), Register32
diff --git a/tools/proxyclient/m1n1/hw/sep.py b/tools/proxyclient/m1n1/hw/sep.py
new file mode 100644
index 0000000..f206a71
--- /dev/null
+++ b/tools/proxyclient/m1n1/hw/sep.py
@@ -0,0 +1,169 @@
+# SPDX-License-Identifier: MIT
+import struct
+from collections import defaultdict, deque
+from enum import IntEnum
+
+from ..trace.asc import ASCRegs
+from ..utils import *
+
+
+class BootRomMsg(IntEnum):
+ GET_STATUS = 2
+ BOOT_TZ0 = 5
+ BOOT_IMG4 = 6
+ SET_SHMEM = 0x18
+
+
+class BootRomStatus(IntEnum):
+ STATUS_OK = 0x66
+ STATUS_BOOT_TZ0_DONE = 0x69
+ STATUS_BOOT_IMG4_DONE = 0x6A
+ STATUS_BOOT_UNK_DONE = 0xD2
+
+
+class SEPMessage(Register64):
+ EP = 7, 0
+ TAG = 15, 8
+ TYPE = 23, 16
+ PARAM = 31, 24
+ DATA = 63, 32
+
+
+# TODO: make this class actually own the shared memory instead of just
+# generating a static buffer if we actually need to read/write to
+# individual items inside the shmem buffer
+class SEPShMem:
+ def __init__(self):
+ self.items = []
+ self.offset = 0x4000
+
+ def add_item(self, name, data, min_size=0):
+ sz = align_up(len(data) + 4, 0x4000)
+ sz = max(sz, min_size)
+ self.items.append((name, self.offset, sz, struct.pack("<I", len(data)) + data))
+ self.offset += sz
+
+ def finalize(self):
+ bfr = bytearray(b"\x00" * self.offset)
+ for i, (name, offset, sz, data) in enumerate(self.items):
+ bfr[i * 16 : i * 16 + 12] = struct.pack("<4sII", name, sz, offset)
+ bfr[offset : offset + len(data)] = data
+
+ cnt = len(self.items)
+ bfr[cnt * 16 : cnt * 16 + 4] = b"llun" # null
+
+ return bfr
+
+
+class SEP:
+ SHMEM_IOVA = 0xBEEF0000
+ FW_IOVA = 0xDEAD0000
+
+ def __init__(self, proxy, iface, utils):
+ self.i = iface
+ self.p = proxy
+ self.u = utils
+
+ self.sep_base = self.u.adt["/arm-io/sep"].get_reg(0)[0]
+ self.dart_base = self.u.adt["/arm-io/dart-sep"].get_reg(0)[0]
+
+ self.asc = ASCRegs(self.u, self.sep_base)
+
+ self.dart_handle = self.p.dart_init(self.dart_base, 0)
+
+ self.epnum2name = {}
+ self.epname2num = {}
+ self.msgs = defaultdict(deque)
+
+ def map_sepfw(self):
+ sepfw_addr, sepfw_size = self.u.adt["/chosen/memory-map"].SEPFW
+ self.p.dart_map(self.dart_handle, self.FW_IOVA, sepfw_addr, sepfw_size)
+
+ def unmap_sepfw(self):
+ _, sepfw_size = self.u.adt["/chosen/memory-map"].SEPFW
+ self.p.dart_unmap(self.dart_handle, self.FW_IOVA, sepfw_size)
+
+ def create_shmem(self):
+ shmem = SEPShMem()
+
+ # PNIC - panic buffer
+ shmem.add_item(b"CINP", b"\x00", 0x8000)
+
+ # ALPO / SIPS - unknown img4-like blobs from the ADT
+ addr, sz = self.u.adt["/chosen/boot-object-manifests"].lpol
+ shmem.add_item(b"OPLA", self.i.readmem(addr, sz))
+ addr, sz = self.u.adt["/chosen/boot-object-manifests"].ibot
+ shmem.add_item(b"IPIS", self.i.readmem(addr, sz))
+
+ bfr = shmem.finalize()
+ sz = align_up(len(bfr), 0x4000)
+ self.shmem = self.u.heap.memalign(0x4000, 0x30000)
+ self.i.writemem(self.shmem, bfr)
+ self.p.dart_map(self.dart_handle, self.SHMEM_IOVA, self.shmem, 0x30000)
+
+ def boot(self):
+ self.create_shmem()
+ self.map_sepfw()
+
+ self.send_msg(SEPMessage(EP=0xFF, TYPE=BootRomMsg.GET_STATUS))
+ self.expect_msg(0xFF, BootRomStatus.STATUS_OK)
+
+ self.send_msg(SEPMessage(EP=0xFF, TYPE=BootRomMsg.BOOT_TZ0))
+ self.expect_msg(0xFF, BootRomStatus.STATUS_BOOT_TZ0_DONE)
+ self.expect_msg(0xFF, BootRomStatus.STATUS_BOOT_UNK_DONE)
+
+ self.send_msg(SEPMessage(EP=0xFF, TYPE=BootRomMsg.GET_STATUS))
+ self.expect_msg(0xFF, BootRomStatus.STATUS_OK)
+
+ self.send_msg(
+ SEPMessage(EP=0xFF, TYPE=BootRomMsg.BOOT_IMG4, DATA=self.FW_IOVA >> 0xC)
+ )
+ self.send_msg(
+ SEPMessage(EP=0xFE, TYPE=BootRomMsg.SET_SHMEM, DATA=self.SHMEM_IOVA >> 0xC)
+ )
+
+ self.expect_msg(0xFF, BootRomStatus.STATUS_BOOT_IMG4_DONE)
+
+ self.unmap_sepfw()
+
+ def expect_msg(self, ep, type):
+ msg = self.recv_msg(ep, block=True)
+ if msg.TYPE != type:
+ raise ValueError(
+ f"Expected type 0x{type:x} but got message with type 0x{msg.TYPE:x}"
+ )
+
+ def send_msg(self, msg):
+ self.asc.INBOX0 = msg.value
+ self.asc.INBOX1 = 0
+
+ def _recv_single_msg(self):
+ msg = SEPMessage(self.asc.OUTBOX0.val)
+ _ = self.asc.OUTBOX1.val
+ return msg
+
+ def _try_recv_msgs(self):
+ while not self.asc.OUTBOX_CTRL.reg.EMPTY:
+ msg = self._recv_single_msg()
+ self.msgs[msg.EP].append(msg)
+ self._handle_ep_discovery()
+
+ def _handle_ep_discovery(self):
+ while len(self.msgs[0xFD]):
+ msg = self.msgs[0xFD].popleft()
+ if msg.TYPE == 0:
+ cs = "".join(
+ [chr((msg.DATA >> (i * 8)) & 0xFF) for i in range(3, -1, -1)]
+ )
+ self.epnum2name[msg.PARAM] = cs
+ self.epname2num[cs] = msg.PARAM
+
+ def recv_msg(self, ep, block=False):
+ self._try_recv_msgs()
+ while block and len(self.msgs[ep]) < 1:
+ self._try_recv_msgs()
+
+ if len(self.msgs[ep]):
+ return self.msgs[ep].popleft()
+ else:
+ return None
diff --git a/tools/proxyclient/m1n1/hw/spi.py b/tools/proxyclient/m1n1/hw/spi.py
new file mode 100644
index 0000000..1fa9be5
--- /dev/null
+++ b/tools/proxyclient/m1n1/hw/spi.py
@@ -0,0 +1,147 @@
+# SPDX-License-Identifier: MIT
+from ..utils import *
+
+__all__ = ["SPIRegs"]
+
+class R_CTRL(Register32):
+ RX_FIFO_RESET = 3
+ TX_FIFO_RESET = 2
+ RUN = 0
+
+class R_CFG(Register32):
+ # impl: 002fb1e6
+ IE_TX_COMPLETE = 21
+ b19 = 19
+ FIFO_THRESH = 18, 17
+ # 0 = 8 bytes
+ # 1 = 4 bytes
+ # 2 = 1 byte
+ # 3 = disabled
+ WORD_SIZE = 16, 15
+ # 0 = 8bit
+ # 1 = 16bit
+ # 2 = 32bit
+ LSB_FIRST = 13
+ b12 = 12
+ IE_RX_THRESH = 8
+ IE_RX_COMPLETE = 7
+ MODE = 6, 5
+ # 0 = polled
+ # 1 = irq
+ CPOL = 2
+ CPHA = 1
+
+class R_STATUS(Register32):
+ TX_COMPLETE = 22
+ TXRX_THRESH = 1 # updated if MODE == 1
+ RX_COMPLETE = 0
+
+class R_PIN(Register32):
+ CS = 1
+ KEEP_MOSI = 0
+
+class R_CLKDIV(Register32):
+ DIVIDER = 10, 0 # SPI freq = CLK / (DIVIDER + 1)
+
+class R_INTER_DELAY(Register32):
+ DELAY = 15, 0
+
+class R_FIFOSTAT(Register32):
+ LEVEL_RX = 31, 24
+ RX_EMPTY = 20
+ LEVEL_TX = 15, 8
+ TX_FULL = 4
+
+class R_IRQ_XFER(Register32):
+ TX_XFER_DONE = 1
+ RX_XFER_DONE = 0
+
+class R_IRQ_FIFO(Register32):
+ TX_OVERFLOW = 17
+ RX_UNDERRUN = 16
+ TX_EMPTY = 9
+ RX_FULL = 8
+ TX_THRESH = 5
+ RX_THRESH = 4
+
+class R_XFSTATUS(Register32):
+ SR_FULL = 26
+ SHIFTING = 20
+ STATE = 17, 16
+ UNK = 0
+
+class R_DIVSTATUS(Register32):
+ COUNT2 = 31, 16
+ COUNT1 = 15, 0
+
+class R_SHIFTCFG(Register32):
+ OVERRIDE_CS = 24
+ BITS = 21, 16
+ RX_ENABLE = 11
+ TX_ENABLE = 10
+ CS_AS_DATA = 9
+ AND_CLK_DATA = 8
+ #? = 2 # needs to be 1 for RX to not break
+ CS_ENABLE = 1
+ CLK_ENABLE = 0
+
+class R_PINCFG(Register32):
+ MOSI_INIT_VAL = 10
+ CS_INIT_VAL = 9
+ CLK_INIT_VAL = 8
+ KEEP_MOSI = 2
+ KEEP_CS = 1
+ KEEP_CLK = 0
+
+class R_DELAY(Register32):
+ DELAY = 31, 16
+ MOSI_VAL = 12
+ CS_VAL = 10
+ SCK_VAL = 8
+ SET_MOSI = 6
+ SET_CS = 5
+ SET_SCK = 4
+ NO_INTERBYTE = 1
+ ENABLE = 0
+
+class R_SCKCFG(Register32):
+ PERIOD = 31, 16
+ PHASE1 = 9
+ PHASE0 = 8
+ RESET_TO_IDLE = 4
+
+class R_SCKPHASES(Register32):
+ PHASE1_START = 31, 16
+ PHASE0_START = 15, 0
+
+class SPIRegs(RegMap):
+ CTRL = 0x00, R_CTRL
+ CFG = 0x04, R_CFG
+ STATUS = 0x08, R_STATUS
+ PIN = 0x0C, R_PIN
+ TXDATA = 0x10, Register32
+ RXDATA = 0x20, Register32
+ CLKDIV = 0x30, R_CLKDIV
+ RXCNT = 0x34, Register32
+ INTER_DELAY = 0x38, R_INTER_DELAY
+ TXCNT = 0x4C, Register32
+ FIFOSTAT = 0x10C, R_FIFOSTAT
+
+ IE_XFER = 0x130, R_IRQ_XFER
+ IF_XFER = 0x134, R_IRQ_XFER
+ IE_FIFO = 0x138, R_IRQ_FIFO
+ IF_FIFO = 0x13c, R_IRQ_FIFO
+
+ SHIFTCFG = 0x150, R_SHIFTCFG
+ PINCFG = 0x154, R_PINCFG
+
+ DELAY_PRE = 0x160, R_DELAY
+ SCKCFG = 0x164, R_SCKCFG
+ DELAY_POST = 0x168, R_DELAY
+
+ SCKPHASES = 0x180, R_SCKPHASES
+
+ UNK_PHASE = 0x18c, Register32 # probably MISO sample point
+
+ XFSTATUS = 0x1c0, R_XFSTATUS
+ DIVSTATUS = 0x1e0, R_DIVSTATUS
diff --git a/tools/proxyclient/m1n1/hw/spmi.py b/tools/proxyclient/m1n1/hw/spmi.py
new file mode 100644
index 0000000..a312f61
--- /dev/null
+++ b/tools/proxyclient/m1n1/hw/spmi.py
@@ -0,0 +1,98 @@
+# SPDX-License-Identifier: MIT
+import struct
+
+from ..utils import *
+
+__all__ = ["SPMI"]
+
+CMD_EXT_WRITE = 0x00
+CMD_EXT_READ = 0x20
+CMD_EXT_WRITEL = 0x30
+CMD_EXT_READL = 0x38
+CMD_WRITE = 0x40
+CMD_READ = 0x60
+CMD_ZERO_WRITE = 0x80
+
+class R_CMD(Register32):
+ REG = 31, 16
+ ACTIVE = 15
+ SLAVE_ID = 14, 8
+ CMD = 7, 0
+
+class R_STATUS(Register32):
+ RX_EMPTY = 24
+ RX_COUNT = 23, 16
+ TX_EMPTY = 8
+ TX_COUNT = 7, 0
+
+class SPMIRegs(RegMap):
+ STATUS = 0x00, R_STATUS
+ CMD = 0x04, R_CMD
+ REPLY = 0x08, Register32
+ IRQ_FLAG = 0x80, Register32
+
+class SPMI:
+ def __init__(self, u, adt_path):
+ self.u = u
+ self.p = u.proxy
+ self.iface = u.iface
+ self.base = u.adt[adt_path].get_reg(0)[0]
+ self.regs = SPMIRegs(u, self.base)
+
+ def read(self, slave, reg, size):
+ while not self.regs.STATUS.reg.RX_EMPTY:
+ print(">", self.regs.REPLY.val)
+
+ self.regs.CMD.reg = R_CMD(REG = reg, ACTIVE=1, SLAVE_ID = slave, CMD = CMD_EXT_READL | (size - 1))
+
+ buf = b""
+
+ left = size + 4
+ while left > 0:
+ while self.regs.STATUS.reg.RX_EMPTY:
+ pass
+ v = self.regs.REPLY.val
+ buf += struct.pack("<I", v)
+ left -= 4
+
+ return buf[4:4+size]
+
+ def write(self, slave, reg, data):
+ while not self.regs.STATUS.reg.RX_EMPTY:
+ self.regs.REPLY.val
+
+ size = len(data)
+ self.regs.CMD.reg = R_CMD(REG = reg, ACTIVE=1, SLAVE_ID = slave, CMD = CMD_EXT_WRITEL | (size - 1))
+
+ while data:
+ blk = (data[:4] + b"\0\0\0")[:4]
+ self.regs.CMD.val = struct.unpack("<I", blk)[0]
+ data = data[4:]
+
+ while self.regs.STATUS.reg.RX_EMPTY:
+ pass
+ return self.regs.REPLY.val
+
+ def read8(self, slave, reg):
+ return struct.unpack("<B", self.read(slave, reg, 1))[0]
+
+ def read16(self, slave, reg):
+ return struct.unpack("<H", self.read(slave, reg, 2))[0]
+
+ def read32(self, slave, reg):
+ return struct.unpack("<I", self.read(slave, reg, 4))[0]
+
+ def read64(self, slave, reg):
+ return struct.unpack("<Q", self.read(slave, reg, 8))[0]
+
+ def write8(self, slave, reg, val):
+ return self.write(slave, reg, struct.pack("<B", val))
+
+ def write16(self, slave, reg, val):
+ return self.write(slave, reg, struct.pack("<H", val))
+
+ def write32(self, slave, reg, val):
+ return self.write(slave, reg, struct.pack("<I", val))
+
+ def write64(self, slave, reg, val):
+ return self.write(slave, reg, struct.pack("<Q", val))
diff --git a/tools/proxyclient/m1n1/hw/uat.py b/tools/proxyclient/m1n1/hw/uat.py
new file mode 100644
index 0000000..f5dce9f
--- /dev/null
+++ b/tools/proxyclient/m1n1/hw/uat.py
@@ -0,0 +1,571 @@
+"""
+ UAT is just regular ARMv8 pagetables, shared between the gfx-asc firmware
+ and the actual AGX hardware.
+
+ The OS doesn't have direct control over it, TTBR0 and TTBR1 entries are placed at
+ gpu-region-base, one pair for each context. The firmware automatically loads TTBR0/TTBR1
+ on boot and whenever the context changes.
+"""
+
+
+import struct
+from ..fw.agx.handoff import GFXHandoff
+from ..utils import *
+from ..malloc import Heap
+from enum import IntEnum
+import traceback
+
+__all__ = []
+
+class MemoryAttr(IntEnum):
+ # ff = Normal, Outer Writeback RW, Inner Writeback RW
+ Normal = 0 # Only accessed by the gfx-asc coprocessor
+ # 00 = Device nGnRnE
+ Device = 1
+ # f4 = Normal, Outer Writeback RW, Inner NC
+ Shared = 2 # Probally Outer-shareable. Shared with either the main cpu or AGX hardware
+ # 4f = Normal, Outer NC, Inner Writeback RW
+ UNK3 = 3
+ # 00 = Device nGnRnE
+ UNK4 = 4
+ # ff = Normal, Outer Writeback RW, Inner Writeback RW
+ UNK5 = 5
+ # 00 = Device nGnRnE
+ UNK6 = 6
+ # 00 = Device nGnRnE
+ UNK7 = 7
+
+
+class TTBR(Register64):
+ ASID = 63, 48
+ BADDR = 47, 1
+ VALID = 0
+
+ def valid(self):
+ return self.VALID == 1
+
+ def offset(self):
+ return self.BADDR << 1
+
+ def set_offset(self, offset):
+ self.BADDR = offset >> 1
+
+ def describe(self):
+ return f"{self.offset():x} [ASID={self.ASID}, VALID={self.VALID}]"
+
+class PTE(Register64):
+ OFFSET = 47, 14
+ UNK0 = 10 # probally an ownership flag, seems to be 1 for FW created PTEs and 0 for OS PTEs
+ TYPE = 1
+ VALID = 0
+
+ def valid(self):
+ return self.VALID == 1 and self.TYPE == 1
+
+ def offset(self):
+ return self.OFFSET << 14
+
+ def set_offset(self, offset):
+ self.OFFSET = offset >> 14
+
+ def describe(self):
+ if not self.valid():
+ return f"<invalid> [{int(self)}:x]"
+ return f"{self.offset():x}, UNK={self.UNK0}"
+
+class Page_PTE(Register64):
+ OS = 55 # Owned by host os or firmware
+ UXN = 54
+ PXN = 53
+ OFFSET = 47, 14
+ nG = 11 # global or local TLB caching
+ AF = 10
+ SH = 9, 8
+ AP = 7, 6
+ AttrIndex = 4, 2
+ TYPE = 1
+ VALID = 0
+
+ def valid(self):
+ return self.VALID == 1 and self.TYPE == 1
+
+ def offset(self):
+ return self.OFFSET << 14
+
+ def set_offset(self, offset):
+ self.OFFSET = offset >> 14
+
+ def access_fw(self, gl=False):
+ if not self.OS:
+ return [[
+ ["--", "--", "--", "--"],
+ ["--", "RW", "--", "RW"],
+ ["--", "RX", "--", "--"],
+ ["RX", "R-", "--", "R-"],
+ ], [
+ ["--", "--", "--", "RW"],
+ ["--", "--", "--", "RW"],
+ ["RX", "--", "--", "R-"],
+ ["RX", "RW", "--", "R-"],
+ ]][gl][self.AP][(self.UXN << 1) | self.PXN]
+ else:
+ return [
+ ["--", "R-", "-?", "RW"],
+ ["R-", "--", "RW", "RW"],
+ ["--", "--", "--", "--"],
+ ["--", "--", "--", "--"],
+ ][self.AP][(self.UXN << 1) | self.PXN]
+
+ def access_gpu(self):
+ if not self.OS:
+ return "--"
+
+ return [
+ ["--", "R-", "-W", "RW"],
+ ["--", "--", "--", "R-"],
+ ["R-", "-W", "RW", "--"],
+ ["--", "--", "--", "--"],
+ ][self.AP][(self.UXN << 1) | self.PXN]
+
+ def describe(self):
+ if not self.valid():
+ return f"<invalid> [{int(self)}:x]"
+
+ return (
+ f"{self.offset():x} [GPU={self.access_gpu()}, EL1={self.access_fw(0)}, GL1={self.access_fw(1)}, " +
+ f"perm={self.OS}{self.AP:02b}{self.UXN}{self.PXN}, " +
+ f"{MemoryAttr(self.AttrIndex).name}, {['Global', 'Local'][self.nG]}, " +
+ f"Owner={['FW', 'OS'][self.OS]}, AF={self.AF}, SH={self.SH}] ({self.value:#x})"
+ )
+
+class UatAccessor(Reloadable):
+ def __init__(self, uat, ctx=0):
+ self.uat = uat
+ self.ctx = ctx
+
+ def translate(self, addr, width):
+ paddr, _ = self.uat.iotranslate(self.ctx, addr, width)[0]
+ if paddr is None:
+ raise Exception(f"UAT Failed to translate {addr:#x}")
+ return paddr
+
+ def read(self, addr, width):
+ return self.uat.u.read(self.translate(addr, width), width)
+ def read8(self, addr):
+ return self.uat.p.read8(self.translate(addr, 1))
+ def read16(self, addr):
+ return self.uat.p.read16(self.translate(addr, 2))
+ def read32(self, addr):
+ return self.uat.p.read32(self.translate(addr, 4))
+ def read64(self, addr):
+ return self.uat.p.read64(self.translate(addr, 8))
+
+ def write(self, addr, data, width):
+ self.uat.u.write(self.translate(addr, width), data, width)
+ def write8(self, addr, data):
+ self.uat.p.write8(self.translate(addr, 1), daat)
+ def write16(self, addr, data):
+ self.uat.p.write6(self.translate(addr, 2), data)
+ def write32(self, addr, data):
+ self.uat.p.write32(self.translate(addr, 4), data)
+ def write64(self, addr, data):
+ self.uat.p.write64(self.translate(addr, 8), data)
+
+class UatStream(Reloadable):
+ CACHE_SIZE = 0x1000
+
+ def __init__(self, uat, ctx, addr, recurse=True):
+ self.uat = uat
+ self.ctx = ctx
+ self.pos = addr
+ self.cache = None
+ self.meta_fn = None
+ self.recurse = recurse
+
+ def to_accessor(self):
+ return UatAccessor(self.uat, self.ctx)
+
+ def read(self, size):
+ assert size >= 0
+
+ data = b""
+ if self.cache:
+ data = self.cache[:size]
+ cached = len(self.cache)
+ self.pos += min(cached, size)
+ if cached > size:
+ self.cache = self.cache[size:]
+ return data
+ self.cache = None
+ if cached == size:
+ return data
+
+ size -= cached
+
+ # align any cache overreads to the next page boundary
+ remaining_in_page = self.uat.PAGE_SIZE - (self.pos % self.uat.PAGE_SIZE)
+ to_cache = min(remaining_in_page, self.CACHE_SIZE)
+
+ try:
+ self.cache = self.uat.ioread(self.ctx, self.pos, max(size, to_cache))
+ except:
+ traceback.print_exc()
+ raise
+ return data + self.read(size)
+
+ def readable(self):
+ return True
+
+ def write(self, bytes):
+ self.uat.iowrite(self.ctx, self.pos, bytes)
+ self.pos += len(bytes)
+ self.cache = None
+ return len(bytes)
+
+ def writable(self):
+ return True
+
+ def flush(self):
+ self.cache = None
+
+ def seek(self, n, wherenc=0):
+ self.cache = None
+ if wherenc == 0:
+ self.pos = n
+ elif wherenc == 2:
+ self.pos += n
+
+ def seekable(self):
+ return True
+
+ def tell(self):
+ return self.pos
+
+ def closed(self):
+ return False
+
+
+class UAT(Reloadable):
+ NUM_CONTEXTS = 64
+
+ PAGE_BITS = 14
+ PAGE_SIZE = 1 << PAGE_BITS
+
+ L0_SIZE = 2
+ L0_OFF = 39
+ L1_SIZE = 8
+ L1_OFF = 36
+ L2_OFF = 25
+ L3_OFF = 14
+
+ IDX_BITS = 11
+ Lx_SIZE = (1 << IDX_BITS)
+
+ LEVELS = [
+ (L0_OFF, L0_SIZE, TTBR),
+ (L1_OFF, L1_SIZE, PTE),
+ (L2_OFF, Lx_SIZE, PTE),
+ (L3_OFF, Lx_SIZE, Page_PTE),
+ ]
+
+ def __init__(self, iface, util=None, hv=None):
+ self.iface = iface
+ self.u = util
+ self.p = util.proxy
+ self.hv = hv
+ self.pt_cache = {}
+ self.dirty = set()
+ self.dirty_ranges = {}
+ self.allocator = None
+ self.ttbr = None
+ self.initialized = False
+ self.sgx_dev = self.u.adt["/arm-io/sgx"]
+ self.shared_region = self.sgx_dev.gfx_shared_region_base
+ self.gpu_region = self.sgx_dev.gpu_region_base
+ self.ttbr0_base = self.u.memalign(self.PAGE_SIZE, self.PAGE_SIZE)
+ self.ttbr1_base = self.sgx_dev.gfx_shared_region_base
+ self.handoff = GFXHandoff(self.u)
+
+ self.VA_MASK = 0
+ for (off, size, _) in self.LEVELS:
+ self.VA_MASK |= (size - 1) << off
+ self.VA_MASK |= self.PAGE_SIZE - 1
+
+
+ def set_l0(self, ctx, off, base, asid=0):
+ ttbr = TTBR(BADDR = base >> 1, ASID = asid, VALID=(base != 0))
+ print(f"[UAT] Set L0 ctx={ctx} off={off:#x} base={base:#x} asid={asid} ({ttbr})")
+ self.write_pte(self.gpu_region + ctx * 16, off, 2, ttbr)
+
+ def ioread(self, ctx, base, size):
+ if size == 0:
+ return b""
+
+ ranges = self.iotranslate(ctx, base, size)
+
+ iova = base
+ data = []
+ for addr, size in ranges:
+ if addr is None:
+ raise Exception(f"Unmapped page at iova {ctx}:{iova:#x}")
+ data.append(self.iface.readmem(addr, size))
+ iova += size
+
+ return b"".join(data)
+
+ def iowrite(self, ctx, base, data):
+ if len(data) == 0:
+ return
+
+ ranges = self.iotranslate(ctx, base, len(data))
+
+ iova = base
+ p = 0
+ for addr, size in ranges:
+ if addr is None:
+ raise Exception(f"Unmapped page at iova {ctx}:{iova:#x}")
+ self.iface.writemem(addr, data[p:p + size])
+ p += size
+ iova += size
+
+ # A stream interface that can be used for random access by Construct
+ def iostream(self, ctx, base, recurse=True):
+ return UatStream(self, ctx, base, recurse)
+
+ # A read/write register interface like proxy/utils objects that can be used by RegMap
+ def ioaccessor(self, ctx):
+ return UatAccessor(self, ctx)
+
+ def iomap(self, ctx, addr, size, **flags):
+ iova = self.allocator.malloc(size)
+
+ self.iomap_at(ctx, iova, addr, size, **flags)
+ self.flush_dirty()
+ return iova
+
+ def iomap_at(self, ctx, iova, addr, size, **flags):
+ if size == 0:
+ return
+
+ if addr & (self.PAGE_SIZE - 1):
+ raise Exception(f"Unaligned PA {addr:#x}")
+
+ if iova & (self.PAGE_SIZE - 1):
+ raise Exception(f"Unaligned IOVA {iova:#x}")
+
+ self.init()
+
+ map_flags = {'OS': 1, 'AttrIndex': MemoryAttr.Normal, 'VALID': 1, 'TYPE': 1, 'AP': 1, 'AF': 1, 'UXN': 1}
+ map_flags.update(flags)
+
+ start_page = align_down(iova, self.PAGE_SIZE)
+ end = iova + size
+ end_page = align_up(end, self.PAGE_SIZE)
+
+ for page in range(start_page, end_page, self.PAGE_SIZE):
+ table_addr = self.gpu_region + ctx * 16
+ for (offset, size, ptecls) in self.LEVELS:
+ if ptecls is Page_PTE:
+ pte = Page_PTE(**map_flags)
+ pte.set_offset(addr)
+ self.write_pte(table_addr, page >> offset, size, pte)
+ addr += self.PAGE_SIZE
+ else:
+ pte = self.fetch_pte(table_addr, page >> offset, size, ptecls)
+ if not pte.valid():
+ table = self.u.memalign(self.PAGE_SIZE, self.PAGE_SIZE)
+ self.p.memset32(table, 0, self.PAGE_SIZE)
+ pte.set_offset(table)
+ if ptecls is not TTBR:
+ pte.VALID = 1
+ pte.TYPE = 1
+ #pte.UNK0 = 1
+ self.write_pte(table_addr, page >> offset, size, pte)
+ table_addr = pte.offset()
+
+ self.dirty_ranges.setdefault(ctx, []).append((start_page, end_page - start_page))
+ #self.flush_dirty()
+
+
+ def fetch_pte(self, offset, idx, size, ptecls):
+ idx = idx & (size - 1)
+
+ cached, table = self.get_pt(offset, size=size)
+ pte = ptecls(table[idx])
+ if not pte.valid() and cached:
+ self.flush_dirty()
+ cached, table = self.get_pt(offset, size=size, uncached=True)
+ pte = ptecls(table[idx])
+
+ return pte
+
+ def write_pte(self, offset, idx, size, pte):
+ idx = idx & (size - 1)
+
+ cached, table = self.get_pt(offset, size=size)
+
+ table[idx] = pte.value
+ self.dirty.add(offset)
+
+ def iotranslate(self, ctx, start, size):
+ if size == 0:
+ return []
+
+ start = start & self.VA_MASK
+
+ start_page = align_down(start, self.PAGE_SIZE)
+ start_off = start - start_page
+ end = start + size
+ end_page = align_up(end, self.PAGE_SIZE)
+ end_size = end - (end_page - self.PAGE_SIZE)
+
+ pages = []
+
+ for page in range(start_page, end_page, self.PAGE_SIZE):
+ table_addr = self.gpu_region + ctx * 16
+ for (offset, size, ptecls) in self.LEVELS:
+ pte = self.fetch_pte(table_addr, page >> offset, size, ptecls)
+ if not pte.valid():
+ break
+ table_addr = pte.offset()
+
+ if pte.valid():
+ pages.append(pte.offset())
+ else:
+ pages.append(None)
+
+ ranges = []
+
+ for page in pages:
+ if not ranges:
+ ranges.append((page, self.PAGE_SIZE))
+ continue
+ laddr, lsize = ranges[-1]
+ if ((page is None and laddr is None) or
+ (page is not None and laddr == (page - lsize))):
+ ranges[-1] = laddr, lsize + self.PAGE_SIZE
+ else:
+ ranges.append((page, self.PAGE_SIZE))
+
+ ranges[-1] = (ranges[-1][0], ranges[-1][1] - self.PAGE_SIZE + end_size)
+
+ if start_off:
+ ranges[0] = (ranges[0][0] + start_off if ranges[0][0] else None,
+ ranges[0][1] - start_off)
+
+ return ranges
+
+ def get_pt(self, addr, size=None, uncached=False):
+ if size is None:
+ size = self.Lx_SIZE
+ cached = True
+ if addr not in self.pt_cache or uncached:
+ cached = False
+ self.pt_cache[addr] = list(
+ struct.unpack(f"<{size}Q", self.iface.readmem(addr, size * 8)))
+
+ return cached, self.pt_cache[addr]
+
+ def flush_pt(self, addr):
+ assert addr in self.pt_cache
+ table = self.pt_cache[addr]
+ self.iface.writemem(addr, struct.pack(f"<{len(table)}Q", *table))
+ #self.p.dc_civac(addr, 0x4000)
+
+ def flush_dirty(self):
+ inval = False
+
+ for page in self.dirty:
+ self.flush_pt(page)
+ inval = True
+
+ self.dirty.clear()
+
+ for ctx, ranges in self.dirty_ranges.items():
+ asid = ctx << 48
+ self.u.inst("tlbi aside1os, x0", asid)
+
+ def invalidate_cache(self):
+ self.pt_cache = {}
+
+ def recurse_level(self, level, base, table, page_fn=None, table_fn=None):
+ def extend(addr):
+ if addr >= 0x80_00000000:
+ addr |= 0xf00_00000000
+ return addr
+
+ offset, size, ptecls = self.LEVELS[level]
+
+ cached, tbl = self.get_pt(table, size)
+ sparse = False
+ for i, pte in enumerate(tbl):
+ pte = ptecls(pte)
+ if not pte.valid():
+ sparse = True
+ continue
+
+ range_size = 1 << offset
+ start = extend(base + i * range_size)
+ end = start + range_size - 1
+
+ if level + 1 == len(self.LEVELS):
+ if page_fn:
+ page_fn(start, end, i, pte, level, sparse=sparse)
+ else:
+ if table_fn:
+ table_fn(start, end, i, pte, level, sparse=sparse)
+ self.recurse_level(level + 1, start, pte.offset(), page_fn, table_fn)
+
+ sparse = False
+
+ def foreach_page(self, ctx, page_fn):
+ self.recurse_level(0, 0, self.gpu_region + ctx * 16, page_fn)
+
+ def foreach_table(self, ctx, table_fn):
+ self.recurse_level(0, 0, self.gpu_region + ctx * 16, table_fn=table_fn)
+
+ def init(self):
+ if self.initialized:
+ return
+
+ print("[UAT] Initializing...")
+
+ # Clear out any stale kernel page tables
+ self.p.memset64(self.ttbr1_base + 0x10, 0, 0x3ff0)
+ self.u.inst("tlbi vmalle1os")
+
+ self.handoff.initialize()
+
+ with self.handoff.lock():
+ print(f"[UAT] TTBR0[0] = {self.ttbr0_base:#x}")
+ print(f"[UAT] TTBR1[0] = {self.ttbr1_base:#x}")
+ self.set_l0(0, 0, self.ttbr0_base)
+ self.set_l0(0, 1, self.ttbr1_base)
+ self.flush_dirty()
+ self.invalidate_cache()
+
+ print("[UAT] Init complete")
+
+ self.initialized = True
+
+ def bind_context(self, ctx, ttbr0_base):
+ assert ctx != 0
+
+ with self.handoff.lock():
+ self.set_l0(ctx, 0, ttbr0_base, ctx)
+ self.set_l0(ctx, 1, self.ttbr1_base, ctx)
+ self.flush_dirty()
+ self.invalidate_cache()
+
+ def dump(self, ctx, log=print):
+ def print_fn(start, end, i, pte, level, sparse):
+ type = "page" if level+1 == len(self.LEVELS) else "table"
+ if sparse:
+ log(f"{' ' * level}...")
+ log(f"{' ' * level}{type}({i:03}): {start:011x} ... {end:011x}"
+ f" -> {pte.describe()}")
+
+ self.recurse_level(0, 0, self.gpu_region + ctx * 16, print_fn, print_fn)
+
+__all__.extend(k for k, v in globals().items()
+ if (callable(v) or isinstance(v, type)) and v.__module__ == __name__)
diff --git a/tools/proxyclient/m1n1/loadobjs.py b/tools/proxyclient/m1n1/loadobjs.py
new file mode 100644
index 0000000..ef567b3
--- /dev/null
+++ b/tools/proxyclient/m1n1/loadobjs.py
@@ -0,0 +1,178 @@
+# SPDX-License-Identifier: MIT
+from contextlib import contextmanager, ExitStack
+import sys, pathlib, os
+import subprocess
+import tempfile
+import bisect
+
+from .asm import NM, LD, OBJCOPY, ARMAsm
+
+__all__ = ["LinkedProgram"]
+
+
+def tool_output_lines(progname, *args):
+ with subprocess.Popen([progname.replace("%ARCH", ARMAsm.ARCH)] + list(args),
+ stdout=subprocess.PIPE) as proc:
+ for line in proc.stdout:
+ yield line.decode("ascii")
+ proc.wait()
+ if proc.returncode:
+ raise Exception(f"{progname} (args: {args}) exited with status {proc.returncode}")
+
+def run_tool(progname, *args, silent=False):
+ subprocess.check_call([progname.replace("%ARCH", ARMAsm.ARCH)] + list(args),
+ stdout=subprocess.DEVNULL if silent else None)
+
+
+class LinkedProgram:
+ SOURCE_ROOT = str(pathlib.Path(__file__).resolve().parents[2])
+
+ def __init__(self, u, base_object=None):
+ self.u = u
+ self.symbols = []
+ self.symaddrs = dict()
+ self.base_object = base_object
+ self._alloced_bases = []
+ self._attrs_to_clear = []
+ self._load_base_symbols()
+
+ def _load_base_symbols(self):
+ if self.base_object is None:
+ suffix = "-raw" if not self.m1n1_is_macho() else ""
+ self.base_object = f"build/m1n1{suffix}.elf"
+
+ addrs = self._load_elf_symbols(self.base_object, self.u.proxy.get_base())
+
+ # sanity check: compare the .rela table between ELF and m1n1 image on target
+ rela_base = addrs["_rela_start"]
+ rela_length = addrs["_rela_end"] - rela_base
+ rela_target = self.u.iface.readmem(rela_base, rela_length)
+
+ tmp = os.path.join(tempfile.mkdtemp(), "bin")
+ path = os.path.join(self.SOURCE_ROOT, self.base_object)
+ run_tool(OBJCOPY, "-O", "binary", path, tmp, "--only-section=.rela.dyn")
+ rela_objfile = open(tmp, "rb").read()
+
+ if rela_objfile[:len(rela_target)] != rela_target:
+ raise Exception(f"Mismatch between {self.base_object} and image on target")
+
+ def m1n1_is_macho(self):
+ p = self.u.proxy
+ return p.read32(p.get_base()) == 0xfeedfacf
+
+ def _load_elf_symbols(self, relpath, offset=0,
+ objname=None, ignore=""):
+ path = pathlib.Path(self.SOURCE_ROOT, relpath)
+ symaddrs = dict()
+
+ for line in tool_output_lines(NM, "-g", path):
+ addr_str, t, name = line.split()
+ addr = int(addr_str, 16) + offset
+ if t in ignore:
+ continue
+ self.symbols.append((addr, name, objname))
+ symaddrs[name] = addr
+ if t in "T" and not hasattr(self, name):
+ setattr(self, name, self._wrap_call_to(addr))
+ if relpath != self.base_object:
+ self._attrs_to_clear.append(name)
+ self.symbols.sort()
+ return symaddrs
+
+ def load_obj(self, objfile, base=None):
+ ALLOC_SIZE = 16*4096
+
+ if base is None:
+ base = self.u.heap.memalign(0x4000, ALLOC_SIZE)
+ self._alloced_bases.append(base)
+
+ objfile = os.path.join(self.SOURCE_ROOT, objfile)
+ tmp = tempfile.mkdtemp() + os.sep
+ elffile = tmp + "elf"
+ ld_script = tmp + "ld"
+ binfile = tmp + "bin"
+ with open(ld_script, "w") as f:
+ f.write("SECTIONS {\n")
+ f.write(f". = 0x{base:x};\n")
+ f.write(".text : { *(.text .text.*) }\n")
+ f.write(".data : { *(.got .data .data.* .rodata .rodata.* .bss .bss.*) }\n")
+ f.write("}\n")
+ for sym in self.symbols:
+ f.write(f"{sym[1]} = 0x{sym[0]:x};\n")
+ run_tool(LD, "-EL", "-maarch64elf", "-T", ld_script, "-o", elffile, objfile)
+ run_tool(OBJCOPY, "-O", "binary", elffile, binfile)
+ #run_tool("objdump", "-d", elffile)
+ self._load_elf_symbols(elffile, ignore="A")
+ with open(binfile, "rb") as f:
+ buf = f.read()
+ assert len(buf) <= ALLOC_SIZE
+ self.u.iface.writemem(base, buf)
+ self.u.proxy.dc_cvau(base, len(buf))
+ self.u.proxy.ic_ivau(base, len(buf))
+
+ def clear_objs(self):
+ for name in self._attrs_to_clear:
+ delattr(self, name)
+ self._attrs_to_clear = []
+
+ for base in self._alloced_bases:
+ self.u.free(base)
+ self._alloced_bases = []
+
+ self.symbols = [(a, b, objname) for (a, b, objname) \
+ in self.symbols if objname == self.base_object]
+
+ @contextmanager
+ def _copy_args_to_target(self, args):
+ heap = self.u.heap
+ with ExitStack() as stack:
+ args_copied = []
+ for arg in args:
+ if type(arg) is str:
+ arg = arg.encode("ascii")
+ # fallthrough
+ if type(arg) is bytes:
+ p = stack.enter_context(heap.guarded_malloc(len(arg) + 1))
+ self.u.iface.writemem(p, arg + b"\0")
+ args_copied.append(p)
+ elif type(arg) is int:
+ args_copied.append(arg)
+ else:
+ raise NotImplementedError(type(arg))
+ yield args_copied
+
+ def _wrap_call_to(self, addr):
+ def call_symbol(*args, call=self.u.proxy.call):
+ with self._copy_args_to_target(args) as args_copied:
+ return call(addr, *args_copied)
+ return call_symbol
+
+ def lookup(self, addr):
+ idx = bisect.bisect_left(self.symbols, (addr + 1, "", "")) - 1
+ if idx < 0 or idx >= len(self.symbols):
+ return None, None
+ return self.symbols[idx]
+
+ def load_inline_c(self, source):
+ tmp = tempfile.mkdtemp()
+ cfile = tmp + ".c"
+ objfile = tmp + ".o"
+ with open(cfile, "w") as f:
+ f.write(source)
+ run_tool("make", "-C", self.SOURCE_ROOT, "invoke_cc",
+ f"OBJFILE={objfile}", f"CFILE={cfile}", silent=True)
+ self.load_obj(objfile)
+
+
+if __name__ == "__main__":
+ from m1n1.setup import *
+ lp = LinkedProgram(u)
+ lp.debug_printf("hello from the other side! (%d)\n", 42)
+ lp.load_inline_c('''
+ #include "utils.h"
+ int add(int a, int b) {
+ debug_printf("adding %d and %d\\n", a, b);
+ return a + b;
+ }
+ ''')
+ print(f"1 + 2 = {lp.add(1, 2)}")
diff --git a/tools/proxyclient/m1n1/macho.py b/tools/proxyclient/m1n1/macho.py
new file mode 100644
index 0000000..32c7639
--- /dev/null
+++ b/tools/proxyclient/m1n1/macho.py
@@ -0,0 +1,270 @@
+# SPDX-License-Identifier: MIT
+from io import BytesIO, SEEK_END, SEEK_SET
+import bisect
+from construct import *
+import subprocess
+
+from .utils import *
+
+__all__ = ["MachO"]
+
+MachOLoadCmdType = "LoadCmdType" / Enum(Int32ul,
+ SYMTAB = 0x02,
+ UNIXTHREAD = 0x05,
+ SEGMENT_64 = 0x19,
+ UUID = 0x1b,
+ BUILD_VERSION = 0x32,
+ DYLD_CHAINED_FIXUPS = 0x80000034,
+ FILESET_ENTRY = 0x80000035,
+)
+
+MachOArmThreadStateFlavor = "ThreadStateFlavor" / Enum(Int32ul,
+ THREAD64 = 6,
+)
+
+MachOHeader = Struct(
+ "magic" / Hex(Int32ul),
+ "cputype" / Hex(Int32ul),
+ "cpusubtype" / Hex(Int32ul),
+ "filetype" / Hex(Int32ul),
+ "ncmds" / Hex(Int32ul),
+ "sizeofcmds" / Hex(Int32ul),
+ "flags" / Hex(Int32ul),
+ "reserved" / Hex(Int32ul),
+)
+
+MachOVmProt = FlagsEnum(Int32sl,
+ PROT_READ = 0x01,
+ PROT_WRITE = 0x02,
+ PROT_EXECUTE = 0x04,
+)
+
+MachOCmdSymTab = Struct(
+ "symoff" / Hex(Int32ul),
+ "nsyms" / Int32ul,
+ "stroff" / Hex(Int32ul),
+ "strsize" / Hex(Int32ul),
+)
+
+MachOCmdUnixThread = GreedyRange(Struct(
+ "flavor" / MachOArmThreadStateFlavor,
+ "data" / Prefixed(ExprAdapter(Int32ul, obj_ * 4, obj_ / 4), Switch(this.flavor, {
+ MachOArmThreadStateFlavor.THREAD64: Struct(
+ "x" / Array(29, Hex(Int64ul)),
+ "fp" / Hex(Int64ul),
+ "lr" / Hex(Int64ul),
+ "sp" / Hex(Int64ul),
+ "pc" / Hex(Int64ul),
+ "cpsr" / Hex(Int32ul),
+ "flags" / Hex(Int32ul),
+ )
+ })),
+))
+
+NList = Struct(
+ "n_strx" / Hex(Int32ul),
+ "n_type" / Hex(Int8ul),
+ "n_sect" / Hex(Int8ul),
+ "n_desc" / Hex(Int16sl),
+ "n_value" / Hex(Int64ul),
+)
+
+MachOCmdSegment64 = Struct(
+ "segname" / PaddedString(16, "ascii"),
+ "vmaddr" / Hex(Int64ul),
+ "vmsize" / Hex(Int64ul),
+ "fileoff" / Hex(Int64ul),
+ "filesize" / Hex(Int64ul),
+ "maxprot" / MachOVmProt,
+ "initprot" / MachOVmProt,
+ "nsects" / Int32ul,
+ "flags" / Hex(Int32ul),
+ "sections" / GreedyRange(Struct(
+ "sectname" / PaddedString(16, "ascii"),
+ "segname" / PaddedString(16, "ascii"),
+ "addr" / Hex(Int64ul),
+ "size" / Hex(Int64ul),
+ "offset" / Hex(Int32ul),
+ "align" / Hex(Int32ul),
+ "reloff" / Hex(Int32ul),
+ "nreloc" / Hex(Int32ul),
+ "flags" / Hex(Int32ul),
+ "reserved1" / Hex(Int32ul),
+ "reserved2" / Hex(Int32ul),
+ "reserved3" / Hex(Int32ul),
+ )),
+)
+
+MachOFilesetEntry = Struct(
+ "addr" / Hex(Int64ul),
+ "offset" / Hex(Int64ul),
+ "entryid" / Hex(Int32ul),
+ "reserved" / Hex(Int32ul),
+ "name" / CString("ascii"),
+)
+
+MachOCmd = Struct(
+ "cmd" / Hex(MachOLoadCmdType),
+ "args" / Prefixed(ExprAdapter(Int32ul, obj_ - 8, obj_ + 8), Switch(this.cmd, {
+ MachOLoadCmdType.SYMTAB: MachOCmdSymTab,
+ MachOLoadCmdType.UNIXTHREAD: MachOCmdUnixThread,
+ MachOLoadCmdType.SEGMENT_64: MachOCmdSegment64,
+ MachOLoadCmdType.UUID: Hex(Bytes(16)),
+ MachOLoadCmdType.FILESET_ENTRY: MachOFilesetEntry,
+ }, default=GreedyBytes)),
+)
+
+MachOFile = Struct(
+ "header" / MachOHeader,
+ "cmds" / Array(this.header.ncmds, MachOCmd),
+)
+
+class MachO:
+ def __init__(self, data):
+ if isinstance(data, bytes):
+ self.io = BytesIO(data)
+ else:
+ self.io = data
+
+ self.off = self.io.tell()
+ self.io.seek(0, SEEK_END)
+ self.end = self.io.tell()
+ self.size = self.end - self.off
+ self.io.seek(self.off, SEEK_SET)
+ self.obj = MachOFile.parse_stream(self.io)
+ self.symbols = {}
+ self.load_info()
+ self.load_fileset()
+
+ def load_info(self):
+ self.vmin, self.vmax = (1 << 64), 0
+ self.entry = None
+ for cmd in self.obj.cmds:
+ if cmd.cmd == MachOLoadCmdType.SEGMENT_64:
+ self.vmin = min(self.vmin, cmd.args.vmaddr)
+ self.vmax = max(self.vmax, cmd.args.vmaddr + cmd.args.vmsize)
+ elif cmd.cmd == MachOLoadCmdType.UNIXTHREAD:
+ self.entry = cmd.args[0].data.pc
+
+ def prepare_image(self, load_hook=None):
+ memory_size = self.vmax - self.vmin
+
+ image = bytearray(memory_size)
+
+ for cmd in self.get_cmds(MachOLoadCmdType.SEGMENT_64):
+ dest = cmd.args.vmaddr - self.vmin
+ end = min(self.size, cmd.args.fileoff + cmd.args.filesize)
+ size = end - cmd.args.fileoff
+ print(f"LOAD: {cmd.args.segname} {size} bytes from {cmd.args.fileoff:x} to {dest:x}")
+ self.io.seek(self.off + cmd.args.fileoff)
+ data = self.io.read(size)
+ if load_hook is not None:
+ data = load_hook(data, cmd.args.segname, size, cmd.args.fileoff, dest)
+ image[dest:dest + size] = data
+ if cmd.args.vmsize > size:
+ clearsize = cmd.args.vmsize - size
+ if cmd.args.segname == "PYLD":
+ print("SKIP: %d bytes from 0x%x to 0x%x" % (clearsize, dest + size, dest + size + clearsize))
+ memory_size -= clearsize - 4 # leave a payload end marker
+ image = image[:memory_size]
+ else:
+ print("ZERO: %d bytes from 0x%x to 0x%x" % (clearsize, dest + size, dest + size + clearsize))
+ image[dest + size:dest + cmd.args.vmsize] = bytes(clearsize)
+
+ return image
+
+ def get_cmds(self, cmdtype):
+ for cmd in self.obj.cmds:
+ if cmd.cmd == cmdtype:
+ yield cmd
+
+ def get_cmd(self, cmdtype):
+ cmds = list(self.get_cmds(cmdtype))
+ if len(cmds) == 0:
+ raise Exception(f"No commands of type {cmdtype}")
+ if len(cmds) > 1:
+ raise Exception(f"More than one commands of type {cmdtype} (found {len(cmd)})")
+ return cmds[0]
+
+ def load_fileset(self):
+ self.subfiles = {}
+
+ for fe in self.get_cmds(MachOLoadCmdType.FILESET_ENTRY):
+ self.io.seek(self.off + fe.args.offset)
+ subfile = MachO(self.io)
+ self.subfiles[fe.args.name] = subfile
+ for seg in subfile.get_cmds(MachOLoadCmdType.SEGMENT_64):
+ self.symbols[f"{fe.args.name}:{seg.args.segname}"] = seg.args.vmaddr
+
+ def add_symbols(self, filename, syms, demangle=False):
+ try:
+ subfile = self.subfiles[filename]
+ except KeyError:
+ raise Exception(f"No fileset entry for {filename}")
+
+ sym_segs = {}
+ for sym_seg in syms.get_cmds(MachOLoadCmdType.SEGMENT_64):
+ sym_segs[sym_seg.args.segname] = sym_seg
+
+ syms.load_symbols(demangle=demangle)
+ symtab = [(v, k) for (k, v) in syms.symbols.items()]
+ symtab.sort()
+
+ for seg in subfile.get_cmds(MachOLoadCmdType.SEGMENT_64):
+ if seg.args.segname not in sym_segs:
+ continue
+
+ sym_seg = sym_segs[seg.args.segname]
+
+ start = bisect.bisect_left(symtab, (sym_seg.args.vmaddr, ""))
+ end = bisect.bisect_left(symtab, (sym_seg.args.vmaddr + sym_seg.args.vmsize, ""))
+
+ for addr, sym in symtab[start:end]:
+ sname = f"{filename}:{sym}"
+ self.symbols[sname] = addr - sym_seg.args.vmaddr + seg.args.vmaddr
+
+ def load_symbols(self, demangle=False):
+ self.symbols = {}
+
+ cmd = self.get_cmd(MachOLoadCmdType.SYMTAB)
+
+ nsyms = cmd.args.nsyms
+ length = NList.sizeof() * nsyms
+ self.io.seek(self.off + cmd.args.symoff)
+ symdata = self.io.read(length)
+
+ symbols = Array(nsyms, NList).parse(symdata)
+
+ symbols_dict = {}
+ for i in symbols:
+ off = cmd.args.stroff + i.n_strx
+ self.io.seek(self.off + off)
+ name = self.io.read(1024).split(b"\x00")[0].decode("ascii")
+ symbols_dict[name] = i.n_value
+
+ if demangle:
+ names = list(symbols_dict.keys())
+ argv = ["c++filt"]
+ argv += names
+
+ with subprocess.Popen(argv, stdin=subprocess.PIPE, stdout=subprocess.PIPE) as proc:
+ demangled, _ = proc.communicate()
+
+ demangled = demangled.decode("ascii").split("\n")[:-1]
+ for name_mangled, name_demangled in zip(names, demangled):
+ self.symbols[name_demangled] = symbols_dict[name_mangled]
+ else:
+ self.symbols = symbols_dict
+
+if __name__ == "__main__":
+ import sys
+ macho = MachO(open(sys.argv[1], "rb").read())
+
+ if len(sys.argv) > 2:
+ syms = MachO(open(sys.argv[2], "rb").read())
+ macho.add_symbols("com.apple.kernel", syms)
+
+ symtab = [(v, k) for (k, v) in macho.symbols.items()]
+ symtab.sort()
+ for addr, name in symtab:
+ print(f"0x{addr:x} {name}")
diff --git a/tools/proxyclient/m1n1/malloc.py b/tools/proxyclient/m1n1/malloc.py
new file mode 100644
index 0000000..909d55b
--- /dev/null
+++ b/tools/proxyclient/m1n1/malloc.py
@@ -0,0 +1,98 @@
+# SPDX-License-Identifier: MIT
+from contextlib import contextmanager
+
+__all__ = ["Heap"]
+
+class Heap(object):
+ def __init__(self, start, end, block=64):
+ if start%block:
+ raise ValueError("heap start not aligned")
+ if end%block:
+ raise ValueError("heap end not aligned")
+ self.offset = start
+ self.count = (end - start) // block
+ self.blocks = [(self.count,False)]
+ self.block = block
+
+ def malloc(self, size):
+ size = (size + self.block - 1) // self.block
+ pos = 0
+ for i, (bsize, full) in enumerate(self.blocks):
+ if not full and bsize >= size:
+ self.blocks[i] = (size, True)
+ if bsize > size:
+ self.blocks.insert(i+1, (bsize - size, False))
+ return self.offset + self.block * pos
+ pos += bsize
+ raise Exception("Out of memory")
+
+ def memalign(self, align, size):
+ assert (align & (align - 1)) == 0
+ align = max(align, self.block) // self.block
+ size = (size + self.block - 1) // self.block
+ pos = self.offset // self.block
+ for i, (bsize, full) in enumerate(self.blocks):
+ if not full:
+ offset = 0
+ if pos % align:
+ offset = align - (pos % align)
+ if bsize >= (size + offset):
+ if offset:
+ self.blocks.insert(i, (offset, False))
+ i += 1
+ self.blocks[i] = (size, True)
+ if bsize > (size + offset):
+ self.blocks.insert(i+1, (bsize - size - offset, False))
+ return self.block * (pos + offset)
+ pos += bsize
+ raise Exception("Out of memory")
+
+ def free(self, addr):
+ if addr%self.block:
+ raise ValueError("free address not aligned")
+ if addr<self.offset:
+ raise ValueError("free address before heap")
+ addr -= self.offset
+ addr //= self.block
+ if addr>=self.count:
+ raise ValueError("free address after heap")
+ pos = 0
+ for i, (bsize, used) in enumerate(self.blocks):
+ if pos > addr:
+ raise ValueError("bad free address")
+ if pos == addr:
+ if used == False:
+ raise ValueError("block already free")
+ if i!=0 and self.blocks[i-1][1] == False:
+ bsize += self.blocks[i-1][0]
+ del self.blocks[i]
+ i -= 1
+ if i!=(len(self.blocks)-1) and self.blocks[i+1][1] == False:
+ bsize += self.blocks[i+1][0]
+ del self.blocks[i]
+ self.blocks[i] = (bsize, False)
+ return
+ pos += bsize
+ raise ValueError("bad free address")
+
+ def check(self):
+ free = 0
+ inuse = 0
+ for i, (bsize, used) in enumerate(self.blocks):
+ if used:
+ inuse += bsize
+ else:
+ free += bsize
+ if free + inuse != self.count:
+ raise Exception("Total block size is inconsistent")
+ print("Heap stats:")
+ print(" In use: %8dkB"%(inuse * self.block // 1024))
+ print(" Free: %8dkB"%(free * self.block // 1024))
+
+ @contextmanager
+ def guarded_malloc(self, size):
+ addr = self.malloc(size)
+ try:
+ yield addr
+ finally:
+ self.free(addr)
diff --git a/tools/proxyclient/m1n1/proxy.py b/tools/proxyclient/m1n1/proxy.py
new file mode 100644
index 0000000..1869f3e
--- /dev/null
+++ b/tools/proxyclient/m1n1/proxy.py
@@ -0,0 +1,1104 @@
+# SPDX-License-Identifier: MIT
+import os, sys, struct, serial, time
+from construct import *
+from enum import IntEnum, IntFlag
+from serial.tools.miniterm import Miniterm
+
+from .utils import *
+from .sysreg import *
+
+__all__ = ["REGION_RWX_EL0", "REGION_RW_EL0", "REGION_RX_EL1"]
+
+# Hack to disable input buffer flushing
+class Serial(serial.Serial):
+ def _reset_input_buffer(self):
+ return
+
+ def reset_input_buffer(self):
+ return
+
+class UartError(RuntimeError):
+ pass
+
+class UartTimeout(UartError):
+ pass
+
+class UartCMDError(UartError):
+ pass
+
+class UartChecksumError(UartError):
+ pass
+
+class UartRemoteError(UartError):
+ pass
+
+class Feature(IntFlag):
+ DISABLE_DATA_CSUMS = 0x01 # Data transfers don't use checksums
+
+ @classmethod
+ def get_all(cls):
+ return cls.DISABLE_DATA_CSUMS
+
+ def __str__(self):
+ return ", ".join(feature.name for feature in self.__class__
+ if feature & self) or "<none>"
+
+
+class START(IntEnum):
+ BOOT = 0
+ EXCEPTION = 1
+ EXCEPTION_LOWER = 2
+ HV = 3
+
+class EXC(IntEnum):
+ SYNC = 0
+ IRQ = 1
+ FIQ = 2
+ SERROR = 3
+
+class EVENT(IntEnum):
+ MMIOTRACE = 1
+ IRQTRACE = 2
+
+class EXC_RET(IntEnum):
+ UNHANDLED = 1
+ HANDLED = 2
+ EXIT_GUEST = 3
+ STEP = 4
+
+class DCP_SHUTDOWN_MODE(IntEnum):
+ QUIESCED = 0
+ SLEEP_IF_EXTERNAL = 1
+ SLEEP = 2
+
+class PIX_FMT(IntEnum):
+ XRGB = 0
+ XBGR = 1
+
+class DART(IntEnum):
+ T8020 = 0
+ T8110 = 1
+ T6000 = 2
+
+ExcInfo = Struct(
+ "regs" / Array(32, Int64ul),
+ "spsr" / RegAdapter(SPSR),
+ "elr" / Int64ul,
+ "esr" / RegAdapter(ESR),
+ "far" / Int64ul,
+ "afsr1" / Int64ul,
+ "sp" / Array(3, Int64ul),
+ "cpu_id" / Int64ul,
+ "mpidr" / Int64ul,
+ "elr_phys" / Int64ul,
+ "far_phys" / Int64ul,
+ "sp_phys" / Int64ul,
+ "data" / Int64ul,
+)
+# Sends 56+ byte Commands and Expects 36 Byte Responses
+# Commands are format <I48sI
+# 4 byte command, 48 byte null padded data + 4 byte checksum
+# Responses are of the format: struct format <Ii24sI
+# 4byte Response , 4 byte status, 24 byte string, 4 byte Checksum
+# Response must start 0xff55aaXX where XX distiguishes between them
+# In little endian mode these numbers as listed as REQ_* constants
+# defined under UartInterface
+#
+# Event Response REQ_EVENT passed to registered Event Handler
+# Boot Response REQ_BOOT passed to handle_boot() which may
+# pass to a matching registered handler based on reason, code values
+# If the status is ST_OK returns the data field to caller
+# Otherwise reports a remote Error
+
+class UartInterface(Reloadable):
+ REQ_NOP = 0x00AA55FF
+ REQ_PROXY = 0x01AA55FF
+ REQ_MEMREAD = 0x02AA55FF
+ REQ_MEMWRITE = 0x03AA55FF
+ REQ_BOOT = 0x04AA55FF
+ REQ_EVENT = 0x05AA55FF
+
+ CHECKSUM_SENTINEL = 0xD0DECADE
+ DATA_END_SENTINEL = 0xB0CACC10
+
+ ST_OK = 0
+ ST_BADCMD = -1
+ ST_INVAL = -2
+ ST_XFERERR = -3
+ ST_CSUMERR = -4
+
+ CMD_LEN = 56
+ REPLY_LEN = 36
+ EVENT_HDR_LEN = 8
+
+ def __init__(self, device=None, debug=False):
+ self.debug = debug
+ self.devpath = None
+ if device is None:
+ device = os.environ.get("M1N1DEVICE", "/dev/m1n1:115200")
+ if isinstance(device, str):
+ baud = 115200
+ if ":" in device:
+ device, baud = device.rsplit(":", 1)
+ baud = int(baud)
+ self.devpath = device
+ self.baudrate = baud
+
+ device = Serial(self.devpath, baud)
+
+ self.dev = device
+ self.dev.timeout = 0
+ self.dev.flushOutput()
+ self.dev.flushInput()
+ self.pted = False
+ #d = self.dev.read(1)
+ #while d != "":
+ #d = self.dev.read(1)
+ self.dev.timeout = int(os.environ.get("M1N1TIMEOUT", "3"))
+ self.tty_enable = True
+ self.handlers = {}
+ self.evt_handlers = {}
+ self.enabled_features = Feature(0)
+
+ def checksum(self, data):
+ sum = 0xDEADBEEF;
+ for c in data:
+ sum *= 31337
+ sum += c ^ 0x5a
+ sum &= 0xFFFFFFFF
+
+ return (sum ^ 0xADDEDBAD) & 0xFFFFFFFF
+
+ def data_checksum(self, data):
+ if self.enabled_features & Feature.DISABLE_DATA_CSUMS:
+ return self.CHECKSUM_SENTINEL
+
+ return self.checksum(data)
+
+ def readfull(self, size):
+ d = b''
+ while len(d) < size:
+ block = self.dev.read(size - len(d))
+ if not block:
+ raise UartTimeout("Expected %d bytes, got %d bytes"%(size,len(d)))
+ d += block
+ return d
+
+ def cmd(self, cmd, payload=b""):
+ if len(payload) > self.CMD_LEN:
+ raise ValueError("Incorrect payload size %d"%len(payload))
+
+ payload = payload.ljust(self.CMD_LEN, b"\x00")
+ command = struct.pack("<I", cmd) + payload
+ command += struct.pack("<I", self.checksum(command))
+ if self.debug:
+ print("<<", hexdump(command))
+ self.dev.write(command)
+
+ def unkhandler(self, s):
+ if not self.tty_enable:
+ return
+ for c in s:
+ if not self.pted:
+ sys.stdout.write("TTY> ")
+ self.pted = True
+ if c == 10:
+ self.pted = False
+ sys.stdout.write(chr(c))
+ sys.stdout.flush()
+
+ def ttymode(self, dev=None):
+ if dev is None:
+ dev = self.dev
+
+ tout = dev.timeout
+ self.tty_enable = True
+ dev.timeout = None
+
+ term = Miniterm(dev, eol='cr')
+ term.exit_character = chr(0x1d) # GS/CTRL+]
+ term.menu_character = chr(0x14) # Menu: CTRL+T
+ term.raw = True
+ term.set_rx_encoding('UTF-8')
+ term.set_tx_encoding('UTF-8')
+
+ print('--- TTY mode | Quit: CTRL+] | Menu: CTRL+T ---')
+ term.start()
+ try:
+ term.join(True)
+ except KeyboardInterrupt:
+ pass
+
+ print('--- Exit TTY mode ---')
+ term.join()
+ term.close()
+
+ dev.timeout = tout
+ self.tty_enable = False
+
+ def reply(self, cmd):
+ reply = b''
+ while True:
+ if not reply or reply[-1] != 255:
+ reply = b''
+ reply += self.readfull(1)
+ if reply != b"\xff":
+ self.unkhandler(reply)
+ continue
+ else:
+ reply = b'\xff'
+ reply += self.readfull(1)
+ if reply != b"\xff\x55":
+ self.unkhandler(reply)
+ continue
+ reply += self.readfull(1)
+ if reply != b"\xff\x55\xaa":
+ self.unkhandler(reply)
+ continue
+ reply += self.readfull(1)
+ cmdin = struct.unpack("<I", reply)[0]
+ if cmdin == self.REQ_EVENT:
+ reply += self.readfull(self.EVENT_HDR_LEN - 4)
+ data_len, event_type = struct.unpack("<HH", reply[4:])
+ reply += self.readfull(data_len + 4)
+ if self.debug:
+ print(">>", hexdump(reply))
+ checksum = struct.unpack("<I", reply[-4:])[0]
+ ccsum = self.data_checksum(reply[:-4])
+ if checksum != ccsum:
+ print("Event checksum error: Expected 0x%08x, got 0x%08x"%(checksum, ccsum))
+ raise UartChecksumError()
+ self.handle_event(EVENT(event_type), reply[self.EVENT_HDR_LEN:-4])
+ reply = b''
+ continue
+
+ reply += self.readfull(self.REPLY_LEN - 4)
+ if self.debug:
+ print(">>", hexdump(reply))
+ status, data, checksum = struct.unpack("<i24sI", reply[4:])
+ ccsum = self.checksum(reply[:-4])
+ if checksum != ccsum:
+ print("Reply checksum error: Expected 0x%08x, got 0x%08x"%(checksum, ccsum))
+ raise UartChecksumError()
+
+ if cmdin != cmd:
+ if cmdin == self.REQ_BOOT and status == self.ST_OK:
+ self.handle_boot(data)
+ reply = b''
+ continue
+ raise UartCMDError("Reply command mismatch: Expected 0x%08x, got 0x%08x"%(cmd, cmdin))
+ if status != self.ST_OK:
+ if status == self.ST_BADCMD:
+ raise UartRemoteError("Reply error: Bad Command")
+ elif status == self.ST_INVAL:
+ raise UartRemoteError("Reply error: Invalid argument")
+ elif status == self.ST_XFERERR:
+ raise UartRemoteError("Reply error: Data transfer failed")
+ elif status == self.ST_CSUMERR:
+ raise UartRemoteError("Reply error: Data checksum failed")
+ else:
+ raise UartRemoteError("Reply error: Unknown error (%d)"%status)
+ return data
+
+ def handle_boot(self, data):
+ reason, code, info = struct.unpack("<IIQ", data[:16])
+ reason = START(reason)
+ if reason in (START.EXCEPTION, START.EXCEPTION_LOWER):
+ code = EXC(code)
+ if (reason, code) in self.handlers:
+ self.handlers[(reason, code)](reason, code, info)
+ elif reason != START.BOOT:
+ print(f"Proxy callback without handler: {reason}, {code}")
+
+ def set_handler(self, reason, code, handler):
+ self.handlers[(reason, code)] = handler
+
+ def handle_event(self, event_id, data):
+ if event_id in self.evt_handlers:
+ self.evt_handlers[event_id](data)
+
+ def set_event_handler(self, event_id, handler):
+ self.evt_handlers[event_id] = handler
+
+ def wait_boot(self):
+ try:
+ return self.reply(self.REQ_BOOT)
+ except:
+ # Over USB, reboots cause a reconnect
+ self.dev.close()
+ print("Waiting for reconnection... ", end="")
+ sys.stdout.flush()
+ for i in range(100):
+ print(".", end="")
+ sys.stdout.flush()
+ try:
+ self.dev.open()
+ except serial.serialutil.SerialException:
+ time.sleep(0.1)
+ else:
+ break
+ else:
+ raise UartTimeout("Reconnection timed out")
+ print(" Connected")
+
+ def wait_and_handle_boot(self):
+ self.handle_boot(self.wait_boot())
+
+ def nop(self):
+ features = Feature.get_all()
+
+ # Send the supported feature flags in the NOP message (has no effect
+ # if the target does not support it)
+ self.cmd(self.REQ_NOP, struct.pack("<Q", features.value))
+ result = self.reply(self.REQ_NOP)
+
+ # Get the enabled feature flags from the message response (returns
+ # 0 if the target does not support it)
+ features = Feature(struct.unpack("<QQQ", result)[0])
+
+ if self.debug:
+ print(f"Enabled features: {features}")
+
+ self.enabled_features = features
+
+ def proxyreq(self, req, reboot=False, no_reply=False, pre_reply=None):
+ self.cmd(self.REQ_PROXY, req)
+ if pre_reply:
+ pre_reply()
+ if no_reply:
+ return
+ elif reboot:
+ return self.wait_boot()
+ else:
+ return self.reply(self.REQ_PROXY)
+
+ def writemem(self, addr, data, progress=False):
+ checksum = self.data_checksum(data)
+ size = len(data)
+ req = struct.pack("<QQI", addr, size, checksum)
+ self.cmd(self.REQ_MEMWRITE, req)
+ if self.debug:
+ print("<< DATA:")
+ chexdump(data)
+ for i in range(0, len(data), 8192):
+ self.dev.write(data[i:i + 8192])
+ if progress:
+ sys.stdout.write(".")
+ sys.stdout.flush()
+ if progress:
+ print()
+ if self.enabled_features & Feature.DISABLE_DATA_CSUMS:
+ # Extra sentinel after the data to make sure no data is lost
+ self.dev.write(struct.pack("<I", self.DATA_END_SENTINEL))
+
+ # should automatically report a CRC failure
+ self.reply(self.REQ_MEMWRITE)
+
+ def readmem(self, addr, size):
+ if size == 0:
+ return b""
+
+ req = struct.pack("<QQ", addr, size)
+ self.cmd(self.REQ_MEMREAD, req)
+ reply = self.reply(self.REQ_MEMREAD)
+ checksum = struct.unpack("<I",reply[:4])[0]
+ data = self.readfull(size)
+ if self.debug:
+ print(">> DATA:")
+ chexdump(data)
+ ccsum = self.data_checksum(data)
+ if checksum != ccsum:
+ raise UartChecksumError("Reply data checksum error: Expected 0x%08x, got 0x%08x"%(checksum, ccsum))
+
+ if self.enabled_features & Feature.DISABLE_DATA_CSUMS:
+ # Extra sentinel after the data to make sure no data was lost
+ sentinel = struct.unpack("<I", self.readfull(4))[0]
+ if sentinel != self.DATA_END_SENTINEL:
+ raise UartChecksumError(f"Reply data sentinel error: Expected "
+ f"{self.DATA_END_SENTINEL:#x}, got {sentinel:#x}")
+
+ return data
+
+ def readstruct(self, addr, stype):
+ return stype.parse(self.readmem(addr, stype.sizeof()))
+
+class ProxyError(RuntimeError):
+ pass
+
+class ProxyReplyError(ProxyError):
+ pass
+
+class ProxyRemoteError(ProxyError):
+ pass
+
+class ProxyCommandError(ProxyRemoteError):
+ pass
+
+class AlignmentError(Exception):
+ pass
+
+class IODEV(IntEnum):
+ UART = 0
+ FB = 1
+ USB_VUART = 2
+ USB0 = 3
+ USB1 = 4
+ USB2 = 5
+ USB3 = 6
+ USB4 = 7
+ USB5 = 8
+ USB6 = 9
+ USB7 = 10
+
+class USAGE(IntFlag):
+ CONSOLE = (1 << 0)
+ UARTPROXY = (1 << 1)
+
+class GUARD(IntFlag):
+ OFF = 0
+ SKIP = 1
+ MARK = 2
+ RETURN = 3
+ SILENT = 0x100
+
+REGION_RWX_EL0 = 0x80000000000
+REGION_RW_EL0 = 0xa0000000000
+REGION_RX_EL1 = 0xc0000000000
+
+# Uses UartInterface.proxyreq() to send requests to M1N1 and process
+# reponses sent back.
+class M1N1Proxy(Reloadable):
+ S_OK = 0
+ S_BADCMD = -1
+
+ P_NOP = 0x000
+ P_EXIT = 0x001
+ P_CALL = 0x002
+ P_GET_BOOTARGS = 0x003
+ P_GET_BASE = 0x004
+ P_SET_BAUD = 0x005
+ P_UDELAY = 0x006
+ P_SET_EXC_GUARD = 0x007
+ P_GET_EXC_COUNT = 0x008
+ P_EL0_CALL = 0x009
+ P_EL1_CALL = 0x00a
+ P_VECTOR = 0x00b
+ P_GL1_CALL = 0x00c
+ P_GL2_CALL = 0x00d
+ P_GET_SIMD_STATE = 0x00e
+ P_PUT_SIMD_STATE = 0x00f
+ P_REBOOT = 0x010
+
+ P_WRITE64 = 0x100
+ P_WRITE32 = 0x101
+ P_WRITE16 = 0x102
+ P_WRITE8 = 0x103
+ P_READ64 = 0x104
+ P_READ32 = 0x105
+ P_READ16 = 0x106
+ P_READ8 = 0x107
+ P_SET64 = 0x108
+ P_SET32 = 0x109
+ P_SET16 = 0x10a
+ P_SET8 = 0x10b
+ P_CLEAR64 = 0x10c
+ P_CLEAR32 = 0x10d
+ P_CLEAR16 = 0x10e
+ P_CLEAR8 = 0x10f
+ P_MASK64 = 0x110
+ P_MASK32 = 0x111
+ P_MASK16 = 0x112
+ P_MASK8 = 0x113
+ P_WRITEREAD64 = 0x114
+ P_WRITEREAD32 = 0x115
+ P_WRITEREAD16 = 0x116
+ P_WRITEREAD8 = 0x117
+
+ P_MEMCPY64 = 0x200
+ P_MEMCPY32 = 0x201
+ P_MEMCPY16 = 0x202
+ P_MEMCPY8 = 0x203
+ P_MEMSET64 = 0x204
+ P_MEMSET32 = 0x205
+ P_MEMSET16 = 0x206
+ P_MEMSET8 = 0x207
+
+ P_IC_IALLUIS = 0x300
+ P_IC_IALLU = 0x301
+ P_IC_IVAU = 0x302
+ P_DC_IVAC = 0x303
+ P_DC_ISW = 0x304
+ P_DC_CSW = 0x305
+ P_DC_CISW = 0x306
+ P_DC_ZVA = 0x307
+ P_DC_CVAC = 0x308
+ P_DC_CVAU = 0x309
+ P_DC_CIVAC = 0x30a
+ P_MMU_SHUTDOWN = 0x30b
+ P_MMU_INIT = 0x30c
+ P_MMU_DISABLE = 0x30d
+ P_MMU_RESTORE = 0x30e
+ P_MMU_INIT_SECONDARY = 0x30f
+
+ P_XZDEC = 0x400
+ P_GZDEC = 0x401
+
+ P_SMP_START_SECONDARIES = 0x500
+ P_SMP_CALL = 0x501
+ P_SMP_CALL_SYNC = 0x502
+ P_SMP_WAIT = 0x503
+ P_SMP_SET_WFE_MODE = 0x504
+
+ P_HEAPBLOCK_ALLOC = 0x600
+ P_MALLOC = 0x601
+ P_MEMALIGN = 0x602
+ P_FREE = 0x602
+
+ P_KBOOT_BOOT = 0x700
+ P_KBOOT_SET_CHOSEN = 0x701
+ P_KBOOT_SET_INITRD = 0x702
+ P_KBOOT_PREPARE_DT = 0x703
+
+ P_PMGR_CLOCK_ENABLE = 0x800
+ P_PMGR_CLOCK_DISABLE = 0x801
+ P_PMGR_ADT_CLOCKS_ENABLE = 0x802
+ P_PMGR_ADT_CLOCKS_DISABLE = 0x803
+ P_PMGR_RESET = 0x804
+
+ P_IODEV_SET_USAGE = 0x900
+ P_IODEV_CAN_READ = 0x901
+ P_IODEV_CAN_WRITE = 0x902
+ P_IODEV_READ = 0x903
+ P_IODEV_WRITE = 0x904
+ P_IODEV_WHOAMI = 0x905
+ P_USB_IODEV_VUART_SETUP = 0x906
+
+ P_TUNABLES_APPLY_GLOBAL = 0xa00
+ P_TUNABLES_APPLY_LOCAL = 0xa01
+
+ P_DART_INIT = 0xb00
+ P_DART_SHUTDOWN = 0xb01
+ P_DART_MAP = 0xb02
+ P_DART_UNMAP = 0xb03
+
+ P_HV_INIT = 0xc00
+ P_HV_MAP = 0xc01
+ P_HV_START = 0xc02
+ P_HV_TRANSLATE = 0xc03
+ P_HV_PT_WALK = 0xc04
+ P_HV_MAP_VUART = 0xc05
+ P_HV_TRACE_IRQ = 0xc06
+ P_HV_WDT_START = 0xc07
+ P_HV_START_SECONDARY = 0xc08
+ P_HV_SWITCH_CPU = 0xc09
+ P_HV_SET_TIME_STEALING = 0xc0a
+ P_HV_PIN_CPU = 0xc0b
+ P_HV_WRITE_HCR = 0xc0c
+ P_HV_MAP_VIRTIO = 0xc0d
+ P_VIRTIO_PUT_BUFFER = 0xc0e
+
+ P_FB_INIT = 0xd00
+ P_FB_SHUTDOWN = 0xd01
+ P_FB_BLIT = 0xd02
+ P_FB_UNBLIT = 0xd03
+ P_FB_FILL = 0xd04
+ P_FB_CLEAR = 0xd05
+ P_FB_DISPLAY_LOGO = 0xd06
+ P_FB_RESTORE_LOGO = 0xd07
+ P_FB_IMPROVE_LOGO = 0xd08
+
+ P_PCIE_INIT = 0xe00
+ P_PCIE_SHUTDOWN = 0xe01
+
+ P_NVME_INIT = 0xf00
+ P_NVME_SHUTDOWN = 0xf01
+ P_NVME_READ = 0xf02
+ P_NVME_FLUSH = 0xf03
+
+ P_MCC_GET_CARVEOUTS = 0x1000
+
+ P_DISPLAY_INIT = 0x1100
+ P_DISPLAY_CONFIGURE = 0x1101
+ P_DISPLAY_SHUTDOWN = 0x1102
+ P_DISPLAY_START_DCP = 0x1103
+ P_DISPLAY_IS_EXTERNAL = 0x1104
+
+ P_DAPF_INIT_ALL = 0x1200
+ P_DAPF_INIT = 0x1201
+
+ def __init__(self, iface, debug=False):
+ self.debug = debug
+ self.iface = iface
+ self.heap = None
+
+ def _request(self, opcode, *args, reboot=False, signed=False, no_reply=False, pre_reply=None):
+ if len(args) > 6:
+ raise ValueError("Too many arguments")
+ args = list(args) + [0] * (6 - len(args))
+ req = struct.pack("<7Q", opcode, *args)
+ if self.debug:
+ print("<<<< %08x: %08x %08x %08x %08x %08x %08x"%tuple([opcode] + args))
+ reply = self.iface.proxyreq(req, reboot=reboot, no_reply=no_reply, pre_reply=None)
+ if no_reply or reboot and reply is None:
+ return
+ ret_fmt = "q" if signed else "Q"
+ rop, status, retval = struct.unpack("<Qq" + ret_fmt, reply)
+ if self.debug:
+ print(">>>> %08x: %d %08x"%(rop, status, retval))
+ if reboot:
+ return
+ if rop != opcode:
+ raise ProxyReplyError("Reply opcode mismatch: Expected 0x%08x, got 0x%08x"%(opcode,rop))
+ if status != self.S_OK:
+ if status == self.S_BADCMD:
+ raise ProxyCommandError("Reply error: Bad Command")
+ else:
+ raise ProxyRemoteError("Reply error: Unknown error (%d)"%status)
+ return retval
+
+ def request(self, opcode, *args, **kwargs):
+ free = []
+ args = list(args)
+ args2 = []
+ for i, arg in enumerate(args):
+ if isinstance(arg, str):
+ arg = arg.encode("utf-8") + b"\0"
+ if isinstance(arg, bytes) and self.heap:
+ p = self.heap.malloc(len(arg))
+ free.append(p)
+ self.iface.writemem(p, arg)
+ if (i < (len(args) - 1)) and args[i + 1] is None:
+ args[i + 1] = len(arg)
+ arg = p
+ args2.append(arg)
+ try:
+ return self._request(opcode, *args2, **kwargs)
+ finally:
+ for i in free:
+ self.heap.free(i)
+
+ def nop(self):
+ self.request(self.P_NOP)
+ def exit(self, retval=0):
+ self.request(self.P_EXIT, retval)
+ def call(self, addr, *args, reboot=False):
+ if len(args) > 5:
+ raise ValueError("Too many arguments")
+ return self.request(self.P_CALL, addr, *args, reboot=reboot)
+ def reload(self, addr, *args, el1=False):
+ if len(args) > 4:
+ raise ValueError("Too many arguments")
+ if el1:
+ self.request(self.P_EL1_CALL, addr, *args, no_reply=True)
+ else:
+ try:
+ self.request(self.P_VECTOR, addr, *args)
+ self.iface.wait_boot()
+ except ProxyCommandError: # old m1n1 does not support P_VECTOR
+ try:
+ self.mmu_shutdown()
+ except ProxyCommandError: # older m1n1 does not support MMU
+ pass
+ self.request(self.P_CALL, addr, *args, reboot=True)
+ def get_bootargs(self):
+ return self.request(self.P_GET_BOOTARGS)
+ def get_base(self):
+ return self.request(self.P_GET_BASE)
+ def set_baud(self, baudrate):
+ self.iface.tty_enable = False
+ def change():
+ self.iface.dev.baudrate = baudrate
+ try:
+ self.request(self.P_SET_BAUD, baudrate, 16, 0x005aa5f0, pre_reply=change)
+ finally:
+ self.iface.tty_enable = True
+ def udelay(self, usec):
+ self.request(self.P_UDELAY, usec)
+ def set_exc_guard(self, mode):
+ self.request(self.P_SET_EXC_GUARD, mode)
+ def get_exc_count(self):
+ return self.request(self.P_GET_EXC_COUNT)
+ def el0_call(self, addr, *args):
+ if len(args) > 4:
+ raise ValueError("Too many arguments")
+ return self.request(self.P_EL0_CALL, addr, *args)
+ def el1_call(self, addr, *args):
+ if len(args) > 4:
+ raise ValueError("Too many arguments")
+ return self.request(self.P_EL1_CALL, addr, *args)
+ def gl1_call(self, addr, *args):
+ if len(args) > 4:
+ raise ValueError("Too many arguments")
+ return self.request(self.P_GL1_CALL, addr, *args)
+ def gl2_call(self, addr, *args):
+ if len(args) > 4:
+ raise ValueError("Too many arguments")
+ return self.request(self.P_GL2_CALL, addr, *args)
+ def get_simd_state(self, buf):
+ self.request(self.P_GET_SIMD_STATE, buf)
+ def put_simd_state(self, buf):
+ self.request(self.P_PUT_SIMD_STATE, buf)
+ def reboot(self):
+ self.request(self.P_REBOOT, no_reply=True)
+
+ def write64(self, addr, data):
+ '''write 8 byte value to given address'''
+ if addr & 7:
+ raise AlignmentError()
+ self.request(self.P_WRITE64, addr, data)
+ def write32(self, addr, data):
+ '''write 4 byte value to given address'''
+ if addr & 3:
+ raise AlignmentError()
+ self.request(self.P_WRITE32, addr, data)
+ def write16(self, addr, data):
+ '''write 2 byte value to given address'''
+ if addr & 1:
+ raise AlignmentError()
+ self.request(self.P_WRITE16, addr, data)
+ def write8(self, addr, data):
+ '''write 1 byte value to given address'''
+ self.request(self.P_WRITE8, addr, data)
+
+ def read64(self, addr):
+ '''return 8 byte value from given address'''
+ if addr & 7:
+ raise AlignmentError()
+ return self.request(self.P_READ64, addr)
+ def read32(self, addr):
+ '''return 4 byte value given address'''
+ if addr & 3:
+ raise AlignmentError()
+ return self.request(self.P_READ32, addr)
+ def read16(self, addr):
+ '''return 2 byte value from given address'''
+ if addr & 1:
+ raise AlignmentError()
+ return self.request(self.P_READ16, addr)
+ def read8(self, addr):
+ '''return 1 byte value from given address'''
+ return self.request(self.P_READ8, addr)
+
+ def set64(self, addr, data):
+ '''Or 64 bit value of data into memory at addr and return result'''
+ if addr & 7:
+ raise AlignmentError()
+ return self.request(self.P_SET64, addr, data)
+ def set32(self, addr, data):
+ '''Or 32 bit value of data into memory at addr and return result'''
+ if addr & 3:
+ raise AlignmentError()
+ return self.request(self.P_SET32, addr, data)
+ def set16(self, addr, data):
+ '''Or 16 bit value of data into memory at addr and return result'''
+ if addr & 1:
+ raise AlignmentError()
+ return self.request(self.P_SET16, addr, data)
+ def set8(self, addr, data):
+ '''Or byte value of data into memory at addr and return result'''
+ return self.request(self.P_SET8, addr, data)
+
+ def clear64(self, addr, data):
+ '''Clear bits in 64 bit memory at address addr that are set
+ in parameter data and return result'''
+ if addr & 7:
+ raise AlignmentError()
+ return self.request(self.P_CLEAR64, addr, data)
+ def clear32(self, addr, data):
+ '''Clear bits in 32 bit memory at address addr that are set
+ in parameter data and return result'''
+ if addr & 3:
+ raise AlignmentError()
+ return self.request(self.P_CLEAR32, addr, data)
+ def clear16(self, addr, data):
+ '''Clear bits in 16 bit memory at address addr that are set
+ in parameter data and return result'''
+ if addr & 1:
+ raise AlignmentError()
+ return self.request(self.P_CLEAR16, addr, data)
+ def clear8(self, addr, data):
+ '''Clear bits in 8 bit memory at addr that are set in data
+ and return result'''
+ return self.request(self.P_CLEAR8, addr, data)
+
+ def mask64(self, addr, clear, set):
+ '''Clear bits in 64 bit memory at address addr that are
+ set in clear, then set the bits in set and return result'''
+ if addr & 7:
+ raise AlignmentError()
+ return self.request(self.P_MASK64, addr, clear, set)
+ def mask32(self, addr, clear, set):
+ '''Clear bits in 32 bit memory at address addr that are
+ set in clear, then set the bits in set and return result'''
+ if addr & 3:
+ raise AlignmentError()
+ return self.request(self.P_MASK32, addr, clear, set)
+ def mask16(self, addr, clear, set):
+ '''Clear select bits in 16 bit memory addr that are set
+ in clear parameter, then set the bits in set parameter and return result'''
+ if addr & 1:
+ raise AlignmentError()
+ return self.request(self.P_MASK16, addr, clear, set)
+ def mask8(self, addr, clear, set):
+ '''Clear bits in 1 byte memory at addr that are set
+ in clear parameter, then set the bits in set parameter
+ and return the result'''
+ return self.request(self.P_MASK8, addr, clear, set)
+
+ def writeread64(self, addr, data):
+ return self.request(self.P_WRITEREAD64, addr, data)
+ def writeread32(self, addr, data):
+ return self.request(self.P_WRITEREAD32, addr, data)
+ def writeread16(self, addr, data):
+ return self.request(self.P_WRITEREAD16, addr, data)
+ def writeread8(self, addr, data):
+ return self.request(self.P_WRITEREAD8, addr, data)
+
+ def memcpy64(self, dst, src, size):
+ if src & 7 or dst & 7:
+ raise AlignmentError()
+ self.request(self.P_MEMCPY64, dst, src, size)
+ def memcpy32(self, dst, src, size):
+ if src & 3 or dst & 3:
+ raise AlignmentError()
+ self.request(self.P_MEMCPY32, dst, src, size)
+ def memcpy16(self, dst, src, size):
+ if src & 1 or dst & 1:
+ raise AlignmentError()
+ self.request(self.P_MEMCPY16, dst, src, size)
+ def memcpy8(self, dst, src, size):
+ self.request(self.P_MEMCPY8, dst, src, size)
+
+ def memset64(self, dst, src, size):
+ if dst & 7:
+ raise AlignmentError()
+ self.request(self.P_MEMSET64, dst, src, size)
+ def memset32(self, dst, src, size):
+ if dst & 3:
+ raise AlignmentError()
+ self.request(self.P_MEMSET32, dst, src, size)
+ def memset16(self, dst, src, size):
+ if dst & 1:
+ raise AlignmentError()
+ self.request(self.P_MEMSET16, dst, src, size)
+ def memset8(self, dst, src, size):
+ self.request(self.P_MEMSET8, dst, src, size)
+
+ def ic_ialluis(self):
+ self.request(self.P_IC_IALLUIS)
+ def ic_iallu(self):
+ self.request(self.P_IC_IALLU)
+ def ic_ivau(self, addr, size):
+ self.request(self.P_IC_IVAU, addr, size)
+ def dc_ivac(self, addr, size):
+ self.request(self.P_DC_IVAC, addr, size)
+ def dc_isw(self, sw):
+ self.request(self.P_DC_ISW, sw)
+ def dc_csw(self, sw):
+ self.request(self.P_DC_CSW, sw)
+ def dc_cisw(self, sw):
+ self.request(self.P_DC_CISW, sw)
+ def dc_zva(self, addr, size):
+ self.request(self.P_DC_ZVA, addr, size)
+ def dc_cvac(self, addr, size):
+ self.request(self.P_DC_CVAC, addr, size)
+ def dc_cvau(self, addr, size):
+ self.request(self.P_DC_CVAU, addr, size)
+ def dc_civac(self, addr, size):
+ self.request(self.P_DC_CIVAC, addr, size)
+ def mmu_shutdown(self):
+ self.request(self.P_MMU_SHUTDOWN)
+ def mmu_init(self):
+ self.request(self.P_MMU_INIT)
+ def mmu_disable(self):
+ return self.request(self.P_MMU_DISABLE)
+ def mmu_restore(self, flags):
+ self.request(self.P_MMU_RESTORE, flags)
+ def mmu_init_secondary(self, cpu):
+ self.request(self.P_MMU_INIT_SECONDARY, cpu)
+
+
+ def xzdec(self, inbuf, insize, outbuf=0, outsize=0):
+ return self.request(self.P_XZDEC, inbuf, insize, outbuf,
+ outsize, signed=True)
+
+ def gzdec(self, inbuf, insize, outbuf, outsize):
+ return self.request(self.P_GZDEC, inbuf, insize, outbuf,
+ outsize, signed=True)
+
+ def smp_start_secondaries(self):
+ self.request(self.P_SMP_START_SECONDARIES)
+ def smp_call(self, cpu, addr, *args):
+ if len(args) > 4:
+ raise ValueError("Too many arguments")
+ self.request(self.P_SMP_CALL, cpu, addr, *args)
+ def smp_call_sync(self, cpu, addr, *args):
+ if len(args) > 4:
+ raise ValueError("Too many arguments")
+ return self.request(self.P_SMP_CALL_SYNC, cpu, addr, *args)
+ def smp_wait(self, cpu):
+ return self.request(self.P_SMP_WAIT, cpu)
+ def smp_set_wfe_mode(self, mode):
+ return self.request(self.P_SMP_SET_WFE_MODE, mode)
+
+ def heapblock_alloc(self, size):
+ return self.request(self.P_HEAPBLOCK_ALLOC, size)
+ def malloc(self, size):
+ return self.request(self.P_MALLOC, size)
+ def memalign(self, align, size):
+ return self.request(self.P_MEMALIGN, align, size)
+ def free(self, ptr):
+ self.request(self.P_FREE, ptr)
+
+ def kboot_boot(self, kernel):
+ self.request(self.P_KBOOT_BOOT, kernel)
+ def kboot_set_chosen(self, name, value):
+ self.request(self.P_KBOOT_SET_CHOSEN, name, value)
+ def kboot_set_initrd(self, base, size):
+ self.request(self.P_KBOOT_SET_INITRD, base, size)
+ def kboot_prepare_dt(self, dt_addr):
+ return self.request(self.P_KBOOT_PREPARE_DT, dt_addr)
+
+ def pmgr_clock_enable(self, clkid):
+ return self.request(self.P_PMGR_CLOCK_ENABLE, clkid)
+ def pmgr_clock_disable(self, clkid):
+ return self.request(self.P_PMGR_CLOCK_DISABLE, clkid)
+ def pmgr_adt_clocks_enable(self, path):
+ return self.request(self.P_PMGR_ADT_CLOCKS_ENABLE, path)
+ def pmgr_adt_clocks_disable(self, path):
+ return self.request(self.P_PMGR_ADT_CLOCKS_DISABLE, path)
+ def pmgr_reset(self, die, name):
+ return self.request(self.P_PMGR_RESET, die, name)
+
+ def iodev_set_usage(self, iodev, usage):
+ return self.request(self.P_IODEV_SET_USAGE, iodev, usage)
+ def iodev_can_read(self, iodev):
+ return self.request(self.P_IODEV_CAN_READ, iodev)
+ def iodev_can_write(self, iodev):
+ return self.request(self.P_IODEV_CAN_WRITE, iodev)
+ def iodev_read(self, iodev, buf, size=None):
+ return self.request(self.P_IODEV_READ, iodev, buf, size)
+ def iodev_write(self, iodev, buf, size=None):
+ return self.request(self.P_IODEV_WRITE, iodev, buf, size)
+ def iodev_whoami(self):
+ return IODEV(self.request(self.P_IODEV_WHOAMI))
+ def usb_iodev_vuart_setup(self, iodev):
+ return self.request(self.P_USB_IODEV_VUART_SETUP, iodev)
+
+ def tunables_apply_global(self, path, prop):
+ return self.request(self.P_TUNABLES_APPLY_GLOBAL, path, prop)
+ def tunables_apply_local(self, path, prop, reg_offset):
+ return self.request(self.P_TUNABLES_APPLY_LOCAL, path, prop, reg_offset)
+ def tunables_apply_local_addr(self, path, prop, base):
+ return self.request(self.P_TUNABLES_APPLY_LOCAL, path, prop, base)
+
+ def dart_init(self, base, sid, dart_type=DART.T8020):
+ return self.request(self.P_DART_INIT, base, sid, dart_type)
+ def dart_shutdown(self, dart):
+ return self.request(self.P_DART_SHUTDOWN, dart)
+ def dart_map(self, dart, iova, bfr, len):
+ return self.request(self.P_DART_MAP, dart, iova, bfr, len)
+ def dart_unmap(self, dart, iova, len):
+ return self.request(self.P_DART_UNMAP, dart, iova, len)
+
+ def hv_init(self):
+ return self.request(self.P_HV_INIT)
+ def hv_map(self, from_, to, size, incr):
+ return self.request(self.P_HV_MAP, from_, to, size, incr)
+ def hv_start(self, entry, *args):
+ return self.request(self.P_HV_START, entry, *args)
+ def hv_translate(self, addr, s1=False, w=False):
+ '''Translate virtual address
+ stage 1 only if s1, for write if w'''
+ return self.request(self.P_HV_TRANSLATE, addr, s1, w)
+ def hv_pt_walk(self, addr):
+ return self.request(self.P_HV_PT_WALK, addr)
+ def hv_map_vuart(self, base, irq, iodev):
+ return self.request(self.P_HV_MAP_VUART, base, irq, iodev)
+ def hv_trace_irq(self, evt_type, num, count, flags):
+ return self.request(self.P_HV_TRACE_IRQ, evt_type, num, count, flags)
+ def hv_wdt_start(self, cpu):
+ return self.request(self.P_HV_WDT_START, cpu)
+ def hv_start_secondary(self, cpu, entry, *args):
+ return self.request(self.P_HV_START_SECONDARY, cpu, entry, *args)
+ def hv_switch_cpu(self, cpu):
+ return self.request(self.P_HV_SWITCH_CPU, cpu)
+ def hv_set_time_stealing(self, enabled, reset):
+ return self.request(self.P_HV_SET_TIME_STEALING, int(bool(enabled)), int(bool(reset)))
+ def hv_pin_cpu(self, cpu):
+ return self.request(self.P_HV_PIN_CPU, cpu)
+ def hv_write_hcr(self, hcr):
+ return self.request(self.P_HV_WRITE_HCR, hcr)
+ def hv_map_virtio(self, base, config):
+ return self.request(self.P_HV_MAP_VIRTIO, base, config)
+ def virtio_put_buffer(self, base, qu, idx, length):
+ return self.request(self.P_VIRTIO_PUT_BUFFER, base, qu, idx, length)
+
+ def fb_init(self):
+ return self.request(self.P_FB_INIT)
+ def fb_shutdown(self, restore_logo=True):
+ return self.request(self.P_FB_SHUTDOWN, restore_logo)
+ def fb_blit(self, x, y, w, h, ptr, stride, pix_fmt=PIX_FMT.XRGB):
+ return self.request(self.P_FB_BLIT, x, y, w, h, ptr, stride | pix_fmt << 32)
+ def fb_unblit(self, x, y, w, h, ptr, stride):
+ return self.request(self.P_FB_UNBLIT, x, y, w, h, ptr, stride)
+ def fb_fill(self, x, y, w, h, color):
+ return self.request(self.P_FB_FILL, x, y, w, h, color)
+ def fb_clear(self, color):
+ return self.request(self.P_FB_CLEAR, color)
+ def fb_display_logo(self):
+ return self.request(self.P_FB_DISPLAY_LOGO)
+ def fb_restore_logo(self):
+ return self.request(self.P_FB_RESTORE_LOGO)
+ def fb_improve_logo(self):
+ return self.request(self.P_FB_IMPROVE_LOGO)
+
+ def pcie_init(self):
+ return self.request(self.P_PCIE_INIT)
+ def pcie_shutdown(self):
+ return self.request(self.P_PCIE_SHUTDOWN)
+
+ def nvme_init(self):
+ return self.request(self.P_NVME_INIT)
+ def nvme_shutdown(self):
+ return self.request(self.P_NVME_SHUTDOWN)
+ def nvme_read(self, nsid, lba, bfr):
+ return self.request(self.P_NVME_READ, nsid, lba, bfr)
+ def nvme_flush(self, nsid):
+ return self.request(self.P_NVME_FLUSH, nsid)
+
+ def mcc_get_carveouts(self):
+ return self.request(self.P_MCC_GET_CARVEOUTS)
+
+ def display_init(self):
+ return self.request(self.P_DISPLAY_INIT)
+ def display_configure(self, cfg):
+ return self.request(self.P_DISPLAY_CONFIGURE, cfg)
+ def display_shutdown(self, mode):
+ return self.request(self.P_DISPLAY_SHUTDOWN, mode)
+ def display_start_dcp(self):
+ return self.request(self.P_DISPLAY_START_DCP)
+ def display_is_external(self):
+ return self.request(self.P_DISPLAY_IS_EXTERNAL)
+
+ def dapf_init_all(self):
+ return self.request(self.P_DAPF_INIT_ALL)
+ def dapf_init(self, path):
+ return self.request(self.P_DAPF_INIT, path)
+
+__all__.extend(k for k, v in globals().items()
+ if (callable(v) or isinstance(v, type)) and v.__module__ == __name__)
+
+if __name__ == "__main__":
+ import serial
+ uartdev = os.environ.get("M1N1DEVICE", "/dev/m1n1")
+ usbuart = serial.Serial(uartdev, 115200)
+ uartif = UartInterface(usbuart, debug=True)
+ print("Sending NOP...", end=' ')
+ uartif.nop()
+ print("OK")
+ proxy = M1N1Proxy(uartif, debug=True)
+ print("Sending Proxy NOP...", end=' ')
+ proxy.nop()
+ print("OK")
+ print("Boot args: 0x%x" % proxy.get_bootargs())
diff --git a/tools/proxyclient/m1n1/proxyutils.py b/tools/proxyclient/m1n1/proxyutils.py
new file mode 100644
index 0000000..ca75c41
--- /dev/null
+++ b/tools/proxyclient/m1n1/proxyutils.py
@@ -0,0 +1,550 @@
+# SPDX-License-Identifier: MIT
+import serial, os, struct, sys, time, json, os.path, gzip, functools
+from contextlib import contextmanager
+from construct import *
+
+from .asm import ARMAsm
+from .proxy import *
+from .utils import Reloadable, chexdiff32
+from .tgtypes import *
+from .sysreg import *
+from .malloc import Heap
+from . import adt
+
+__all__ = ["ProxyUtils", "RegMonitor", "GuardedHeap", "bootstrap_port"]
+
+SIMD_B = Array(32, Array(16, Int8ul))
+SIMD_H = Array(32, Array(8, Int16ul))
+SIMD_S = Array(32, Array(4, Int32ul))
+SIMD_D = Array(32, Array(2, Int64ul))
+SIMD_Q = Array(32, BytesInteger(16, swapped=True))
+
+# This isn't perfect, since multiple versions could have the same
+# iBoot version, but it's good enough
+VERSION_MAP = {
+ "iBoot-7429.61.2": "V12_1",
+ "iBoot-7459.101.2": "V12_3",
+ "iBoot-7459.121.3": "V12_4",
+ "iBoot-8419.0.151.0.1": "V13_0B4",
+}
+
+class ProxyUtils(Reloadable):
+ CODE_BUFFER_SIZE = 0x10000
+ def __init__(self, p, heap_size=1024 * 1024 * 1024):
+ self.iface = p.iface
+ self.proxy = p
+ self.base = p.get_base()
+ self.ba_addr = p.get_bootargs()
+
+ self.ba = self.iface.readstruct(self.ba_addr, BootArgs)
+
+ # We allocate a 128MB heap, 128MB after the m1n1 heap, without telling it about it.
+ # This frees up from having to coordinate memory management or free stuff after a Python
+ # script runs, at the expense that if m1n1 ever uses more than 128MB of heap it will
+ # clash with Python (m1n1 will normally not use *any* heap when running proxy ops though,
+ # except when running very high-level operations like booting a kernel, so this should be
+ # OK).
+ self.heap_size = heap_size
+ try:
+ self.heap_base = p.heapblock_alloc(0)
+ except ProxyRemoteError:
+ # Compat with versions that don't have heapblock yet
+ self.heap_base = (self.base + ((self.ba.top_of_kernel_data + 0xffff) & ~0xffff) -
+ self.ba.phys_base)
+
+ if os.environ.get("M1N1HEAP", ""):
+ self.heap_base = int(os.environ.get("M1N1HEAP", ""), 16)
+
+ self.heap_base += 128 * 1024 * 1024 # We leave 128MB for m1n1 heap
+ self.heap_top = self.heap_base + self.heap_size
+ self.heap = Heap(self.heap_base, self.heap_top)
+ self.proxy.heap = self.heap
+
+ self.malloc = self.heap.malloc
+ self.memalign = self.heap.memalign
+ self.free = self.heap.free
+
+ self.code_buffer = self.malloc(self.CODE_BUFFER_SIZE)
+
+ self.adt_data = None
+ self.adt = LazyADT(self)
+
+ self.simd_buf = self.malloc(32 * 16)
+ self.simd_type = None
+ self.simd = None
+
+ self.mmu_off = False
+
+ self.inst_cache = {}
+
+ self.exec_modes = {
+ None: (self.proxy.call, REGION_RX_EL1),
+ "el2": (self.proxy.call, REGION_RX_EL1),
+ "el1": (self.proxy.el1_call, 0),
+ "el0": (self.proxy.el0_call, REGION_RWX_EL0),
+ "gl2": (self.proxy.gl2_call, REGION_RX_EL1),
+ "gl1": (self.proxy.gl1_call, 0),
+ }
+ self._read = {
+ 8: lambda addr: self.proxy.read8(addr),
+ 16: lambda addr: self.proxy.read16(addr),
+ 32: lambda addr: self.proxy.read32(addr),
+ 64: lambda addr: self.uread64(addr),
+ 128: lambda addr: [self.uread64(addr),
+ self.uread64(addr + 8)],
+ 256: lambda addr: [self.uread64(addr),
+ self.uread64(addr + 8),
+ self.uread64(addr + 16),
+ self.uread64(addr + 24)],
+ 512: lambda addr: [self.uread64(addr + i) for i in range(0, 64, 8)],
+ }
+ self._write = {
+ 8: lambda addr, data: self.proxy.write8(addr, data),
+ 16: lambda addr, data: self.proxy.write16(addr, data),
+ 32: lambda addr, data: self.proxy.write32(addr, data),
+ 64: lambda addr, data: self.uwrite64(addr, data),
+ 128: lambda addr, data: (self.uwrite64(addr, data[0]),
+ self.uwrite64(addr + 8, data[1])),
+ 256: lambda addr, data: (self.uwrite64(addr, data[0]),
+ self.uwrite64(addr + 8, data[1]),
+ self.uwrite64(addr + 16, data[2]),
+ self.uwrite64(addr + 24, data[3])),
+ 512: lambda addr, data: [self.uwrite64(addr + 8 * i, data[i])
+ for i in range(8)],
+ }
+
+ def uwrite64(self, addr, data):
+ '''write 8 byte value to given address, supporting split 4-byte halves'''
+ if addr & 3:
+ raise AlignmentError()
+ if addr & 4:
+ self.proxy.write32(addr, data & 0xffffffff)
+ self.proxy.write32(addr + 4, data >> 32)
+ else:
+ self.proxy.write64(addr, data)
+
+ def uread64(self, addr):
+ '''write 8 byte value to given address, supporting split 4-byte halves'''
+ if addr & 3:
+ raise AlignmentError()
+ if addr & 4:
+ return self.proxy.read32(addr) | (self.proxy.read32(addr + 4) << 32)
+ else:
+ return self.proxy.read64(addr)
+
+ def read(self, addr, width):
+ '''do a width read from addr and return it
+ width can be 8, 16, 21, 64, 128 or 256'''
+ val = self._read[width](addr)
+ if self.proxy.get_exc_count():
+ raise ProxyError("Exception occurred")
+ return val
+
+ def write(self, addr, data, width):
+ '''do a width write of data to addr
+ width can be 8, 16, 21, 64, 128 or 256'''
+ self._write[width](addr, data)
+ if self.proxy.get_exc_count():
+ raise ProxyError("Exception occurred")
+
+ def mrs(self, reg, *, silent=False, call=None):
+ '''read system register reg'''
+ op0, op1, CRn, CRm, op2 = sysreg_parse(reg)
+
+ op = ((op0 << 19) | (op1 << 16) | (CRn << 12) |
+ (CRm << 8) | (op2 << 5) | 0xd5200000)
+
+ return self.exec(op, call=call, silent=silent)
+
+ def msr(self, reg, val, *, silent=False, call=None):
+ '''Write val to system register reg'''
+ op0, op1, CRn, CRm, op2 = sysreg_parse(reg)
+
+ op = ((op0 << 19) | (op1 << 16) | (CRn << 12) |
+ (CRm << 8) | (op2 << 5) | 0xd5000000)
+
+ self.exec(op, val, call=call, silent=silent)
+
+ sys = msr
+ sysl = mrs
+
+ def exec(self, op, r0=0, r1=0, r2=0, r3=0, *, silent=False, call=None, ignore_exceptions=False):
+ if callable(call):
+ region = REGION_RX_EL1
+ elif isinstance(call, tuple):
+ call, region = call
+ else:
+ call, region = self.exec_modes[call]
+
+ if isinstance(op, list):
+ op = tuple(op)
+
+ if op in self.inst_cache:
+ func = self.inst_cache[op]
+ elif isinstance(op, tuple) or isinstance(op, list):
+ func = struct.pack(f"<{len(op)}II", *op, 0xd65f03c0) # ret
+ elif isinstance(op, int):
+ func = struct.pack("<II", op, 0xd65f03c0) # ret
+ elif isinstance(op, str):
+ c = ARMAsm(op + "; ret", self.code_buffer)
+ func = c.data
+ elif isinstance(op, bytes):
+ func = op
+ else:
+ raise ValueError()
+
+ if self.mmu_off:
+ region = 0
+
+ self.inst_cache[op] = func
+
+ assert len(func) < self.CODE_BUFFER_SIZE
+ self.iface.writemem(self.code_buffer, func)
+ self.proxy.dc_cvau(self.code_buffer, len(func))
+ self.proxy.ic_ivau(self.code_buffer, len(func))
+
+ self.proxy.set_exc_guard(GUARD.SKIP | (GUARD.SILENT if silent else 0))
+ ret = call(self.code_buffer | region, r0, r1, r2, r3)
+ if not ignore_exceptions:
+ cnt = self.proxy.get_exc_count()
+ self.proxy.set_exc_guard(GUARD.OFF)
+ if cnt:
+ raise ProxyError("Exception occurred")
+ else:
+ self.proxy.set_exc_guard(GUARD.OFF)
+
+ return ret
+
+ inst = exec
+
+ def compressed_writemem(self, dest, data, progress=None):
+ if not len(data):
+ return
+
+ payload = gzip.compress(data, compresslevel=1)
+ compressed_size = len(payload)
+
+ with self.heap.guarded_malloc(compressed_size) as compressed_addr:
+ self.iface.writemem(compressed_addr, payload, progress)
+ timeout = self.iface.dev.timeout
+ self.iface.dev.timeout = None
+ try:
+ decompressed_size = self.proxy.gzdec(compressed_addr, compressed_size, dest, len(data))
+ finally:
+ self.iface.dev.timeout = timeout
+
+ assert decompressed_size == len(data)
+
+ def get_adt(self):
+ if self.adt_data is not None:
+ return self.adt_data
+ adt_base = (self.ba.devtree - self.ba.virt_base + self.ba.phys_base) & 0xffffffffffffffff
+ adt_size = self.ba.devtree_size
+ print(f"Fetching ADT ({adt_size} bytes)...")
+ self.adt_data = self.iface.readmem(adt_base, self.ba.devtree_size)
+ return self.adt_data
+
+ def push_adt(self):
+ self.adt_data = self.adt.build()
+ adt_base = (self.ba.devtree - self.ba.virt_base + self.ba.phys_base) & 0xffffffffffffffff
+ adt_size = len(self.adt_data)
+ print(f"Pushing ADT ({adt_size} bytes)...")
+ self.iface.writemem(adt_base, self.adt_data)
+
+ def disassemble_at(self, start, size, pc=None, vstart=None, sym=None):
+ '''disassemble len bytes of memory from start
+ optional pc address will mark that line with a '*' '''
+ code = struct.unpack(f"<{size // 4}I", self.iface.readmem(start, size))
+ if vstart is None:
+ vstart = start
+
+ c = ARMAsm(".inst " + ",".join(str(i) for i in code), vstart)
+ lines = list()
+ for line in c.disassemble():
+ sl = line.split()
+ try:
+ addr = int(sl[0].rstrip(":"), 16)
+ except:
+ addr = None
+ if pc == addr:
+ line = " *" + line
+ else:
+ line = " " + line
+ if sym:
+ if s := sym(addr):
+ print()
+ print(f"{' '*len(sl[0])} {s}:")
+ print(line)
+
+ def print_l2c_regs(self):
+ print()
+ print(" == L2C Registers ==")
+ l2c_err_sts = self.mrs(L2C_ERR_STS_EL1)
+
+ print(f" L2C_ERR_STS: {l2c_err_sts:#x}")
+ print(f" L2C_ERR_ADR: {self.mrs(L2C_ERR_ADR_EL1):#x}");
+ print(f" L2C_ERR_INF: {self.mrs(L2C_ERR_INF_EL1):#x}");
+
+ self.msr(L2C_ERR_STS_EL1, l2c_err_sts) # Clear the flag bits
+ self.msr(DAIF, self.mrs(DAIF) | 0x100) # Re-enable SError exceptions
+
+ def print_context(self, ctx, is_fault=True, addr=lambda a: f"0x{a:x}", sym=None, num_ctx=9):
+ print(f" == Exception taken from {ctx.spsr.M.name} ==")
+ el = ctx.spsr.M >> 2
+ print(f" SPSR = {ctx.spsr}")
+ print(f" ELR = {addr(ctx.elr)}" + (f" (0x{ctx.elr_phys:x})" if ctx.elr_phys else ""))
+ print(f" SP_EL{el} = 0x{ctx.sp[el]:x}" + (f" (0x{ctx.sp_phys:x})" if ctx.sp_phys else ""))
+ if is_fault:
+ print(f" ESR = {ctx.esr}")
+ print(f" FAR = {addr(ctx.far)}" + (f" (0x{ctx.far_phys:x})" if ctx.far_phys else ""))
+
+ for i in range(0, 31, 4):
+ j = min(30, i + 3)
+ print(f" {f'x{i}-x{j}':>7} = {' '.join(f'{r:016x}' for r in ctx.regs[i:j + 1])}")
+
+ if ctx.elr_phys:
+ print()
+ print(" == Code context ==")
+
+ off = -(num_ctx // 2)
+
+ self.disassemble_at(ctx.elr_phys + 4 * off, num_ctx * 4, ctx.elr, ctx.elr + 4 * off, sym=sym)
+
+ if is_fault:
+ if ctx.esr.EC == ESR_EC.MSR or ctx.esr.EC == ESR_EC.IMPDEF and ctx.esr.ISS == 0x20:
+ print()
+ print(" == MRS/MSR fault decoding ==")
+ if ctx.esr.EC == ESR_EC.MSR:
+ iss = ESR_ISS_MSR(ctx.esr.ISS)
+ else:
+ iss = ESR_ISS_MSR(self.mrs(AFSR1_EL2))
+ enc = iss.Op0, iss.Op1, iss.CRn, iss.CRm, iss.Op2
+ if enc in sysreg_rev:
+ name = sysreg_rev[enc]
+ else:
+ name = f"s{iss.Op0}_{iss.Op1}_c{iss.CRn}_c{iss.CRm}_{iss.Op2}"
+ if iss.DIR == MSR_DIR.READ:
+ print(f" Instruction: mrs x{iss.Rt}, {name}")
+ else:
+ print(f" Instruction: msr {name}, x{iss.Rt}")
+
+ if ctx.esr.EC in (ESR_EC.DABORT, ESR_EC.DABORT_LOWER):
+ print()
+ print(" == Data abort decoding ==")
+ iss = ESR_ISS_DABORT(ctx.esr.ISS)
+ if iss.ISV:
+ print(f" ISS: {iss!s}")
+ else:
+ print(" No instruction syndrome available")
+
+ if iss.DFSC == DABORT_DFSC.ECC_ERROR:
+ self.print_l2c_regs()
+
+ if ctx.esr.EC == ESR_EC.SERROR and ctx.esr.ISS == 0:
+ self.print_l2c_regs()
+
+ print()
+
+ @contextmanager
+ def mmu_disabled(self):
+ flags = self.proxy.mmu_disable()
+ try:
+ yield
+ finally:
+ self.proxy.mmu_restore(flags)
+
+ def push_simd(self):
+ if self.simd is not None:
+ data = self.simd_type.build(self.simd)
+ self.iface.writemem(self.simd_buf, data)
+ self.proxy.put_simd_state(self.simd_buf)
+ self.simd = self.simd_type = None
+
+ def get_simd(self, simd_type):
+ if self.simd is not None and self.simd_type is not simd_type:
+ data = self.simd_type.build(self.simd)
+ self.simd = simd_type.parse(data)
+ self.simd_type = simd_type
+ elif self.simd is None:
+ self.proxy.get_simd_state(self.simd_buf)
+ data = self.iface.readmem(self.simd_buf, 32 * 16)
+ self.simd = simd_type.parse(data)
+ self.simd_type = simd_type
+
+ return self.simd
+
+ @property
+ def b(self):
+ return self.get_simd(SIMD_B)
+ @property
+ def h(self):
+ return self.get_simd(SIMD_H)
+ @property
+ def s(self):
+ return self.get_simd(SIMD_S)
+ @property
+ def d(self):
+ return self.get_simd(SIMD_D)
+ @property
+ def q(self):
+ return self.get_simd(SIMD_Q)
+
+ def get_version(self, v):
+ if isinstance(v, bytes):
+ v = v.split(b"\0")[0].decode("ascii")
+ return VERSION_MAP.get(v, None)
+
+ @property
+ def version(self):
+ return self.get_version(self.adt["/chosen"].firmware_version)
+
+ @property
+ def sfr_version(self):
+ return self.get_version(self.adt["/chosen"].system_firmware_version)
+
+class LazyADT:
+ def __init__(self, utils):
+ self.__dict__["_utils"] = utils
+
+ @functools.cached_property
+ def _adt(self):
+ return adt.load_adt(self._utils.get_adt())
+ def __getitem__(self, item):
+ return self._adt[item]
+ def __setitem__(self, item, value):
+ self._adt[item] = value
+ def __delitem__(self, item):
+ del self._adt[item]
+ def __contains__(self, item):
+ return item in self._adt
+ def __getattr__(self, attr):
+ return getattr(self._adt, attr)
+ def __setattr__(self, attr, value):
+ return setattr(self._adt, attr, value)
+ def __delattr__(self, attr):
+ return delattr(self._adt, attr)
+ def __str__(self, t=""):
+ return str(self._adt)
+ def __iter__(self):
+ return iter(self._adt)
+
+class RegMonitor(Reloadable):
+ def __init__(self, utils, bufsize=0x100000, ascii=False, log=None):
+ self.utils = utils
+ self.proxy = utils.proxy
+ self.iface = self.proxy.iface
+ self.ranges = []
+ self.last = []
+ self.bufsize = bufsize
+ self.ascii = ascii
+ self.log = log or print
+
+ if bufsize:
+ self.scratch = utils.malloc(bufsize)
+ else:
+ self.scratch = None
+
+ def readmem(self, start, size, readfn):
+ if readfn:
+ return readfn(start, size)
+ if self.scratch:
+ assert size < self.bufsize
+ self.proxy.memcpy32(self.scratch, start, size)
+ start = self.scratch
+ return self.proxy.iface.readmem(start, size)
+
+ def add(self, start, size, name=None, offset=None, readfn=None):
+ if offset is None:
+ offset = start
+ self.ranges.append((start, size, name, offset, readfn))
+ self.last.append(None)
+
+ def show_regions(self, log=print):
+ for start, size, name, offset, readfn in sorted(self.ranges):
+ end = start + size - 1
+ log(f"{start:#x}..{end:#x} ({size:#x})\t{name}")
+
+ def poll(self):
+ if not self.ranges:
+ return
+ cur = []
+ for (start, size, name, offset, readfn), last in zip(self.ranges, self.last):
+ count = size // 4
+ block = self.readmem(start, size, readfn)
+ if block is None:
+ if last is not None:
+ self.log(f"# Lost: {name} ({start:#x}..{start + size - 1:#x})")
+ cur.append(None)
+ continue
+
+ words = struct.unpack("<%dI" % count, block)
+ cur.append(block)
+ if last == block:
+ continue
+ if name:
+ header = f"# {name} ({start:#x}..{start + size - 1:#x})\n"
+ else:
+ header = f"# ({start:#x}..{start + size - 1:#x})\n"
+
+ self.log(header + chexdiff32(last, block, offset=offset))
+ self.last = cur
+
+class GuardedHeap:
+ def __init__(self, malloc, memalign=None, free=None):
+ if isinstance(malloc, Heap):
+ malloc, memalign, free = malloc.malloc, malloc.memalign, malloc.free
+
+ self.ptrs = set()
+ self._malloc = malloc
+ self._memalign = memalign
+ self._free = free
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, *exc):
+ self.free_all()
+ return False
+
+ def malloc(self, sz):
+ ptr = self._malloc(sz)
+ self.ptrs.add(ptr)
+ return ptr
+
+ def memalign(self, align, sz):
+ ptr = self._memalign(align, sz)
+ self.ptrs.add(ptr)
+ return ptr
+
+ def free(self, ptr):
+ self.ptrs.remove(ptr)
+ self._free(ptr)
+
+ def free_all(self):
+ for ptr in self.ptrs:
+ self._free(ptr)
+ self.ptrs = set()
+
+def bootstrap_port(iface, proxy):
+ to = iface.dev.timeout
+ iface.dev.timeout = 0.15
+ try:
+ do_baud = proxy.iodev_whoami() == IODEV.UART
+ except ProxyCommandError:
+ # Old m1n1 version -- assume non-USB serial link, force baudrate adjust
+ do_baud = True
+ except UartTimeout:
+ # Assume the receiving end is already at 1500000
+ iface.dev.baudrate = 1500000
+ do_baud = False
+
+ if do_baud:
+ try:
+ iface.nop()
+ proxy.set_baud(1500000)
+ except UartTimeout:
+ # May fail even if the setting did get applied; checked by the .nop next
+ iface.dev.baudrate = 1500000
+
+ iface.nop()
+ iface.dev.timeout = to
diff --git a/tools/proxyclient/m1n1/setup.py b/tools/proxyclient/m1n1/setup.py
new file mode 100644
index 0000000..8a66287
--- /dev/null
+++ b/tools/proxyclient/m1n1/setup.py
@@ -0,0 +1,33 @@
+# SPDX-License-Identifier: MIT
+import os, struct, sys, time
+
+from .hv import HV
+from .proxy import *
+from .proxyutils import *
+from .sysreg import *
+from .tgtypes import *
+from .utils import *
+from .hw.pmu import PMU
+
+# Create serial connection
+iface = UartInterface()
+# Construct m1n1 proxy layer over serial connection
+p = M1N1Proxy(iface, debug=False)
+# Customise parameters of proxy and serial port
+# based on information sent over the connection
+bootstrap_port(iface, p)
+
+# Initialise the Proxy interface from values fetched from
+# the remote end
+u = ProxyUtils(p)
+# Build a Register Monitoring object on Proxy Interface
+mon = RegMonitor(u)
+hv = HV(iface, p, u)
+
+fb = u.ba.video.base
+
+PMU(u).reset_panic_counter()
+
+print(f"m1n1 base: 0x{u.base:x}")
+
+PMU(u).reset_panic_counter()
diff --git a/tools/proxyclient/m1n1/shell.py b/tools/proxyclient/m1n1/shell.py
new file mode 100644
index 0000000..06e0605
--- /dev/null
+++ b/tools/proxyclient/m1n1/shell.py
@@ -0,0 +1,213 @@
+# SPDX-License-Identifier: MIT
+import atexit, serial, os, struct, code, traceback, readline, rlcompleter, sys
+import __main__
+import builtins
+import re
+
+from .proxy import *
+from .proxyutils import *
+from .utils import *
+from . import sysreg
+from inspect import isfunction, signature
+
+__all__ = ["ExitConsole", "run_shell"]
+
+class HistoryConsole(code.InteractiveConsole):
+ def __init__(self, locals=None, filename="<console>",
+ histfile=os.path.expanduser("~/.m1n1-history")):
+ code.InteractiveConsole.__init__(self, locals, filename)
+ self.histfile = histfile
+ self.init_history(histfile)
+ self.poll_func = None
+
+ def init_history(self, histfile):
+ readline.parse_and_bind("tab: complete")
+ if hasattr(readline, "read_history_file"):
+ try:
+ readline.read_history_file(histfile)
+ except FileNotFoundError:
+ pass
+
+ def save_history(self):
+ readline.set_history_length(10000)
+ readline.write_history_file(self.histfile)
+
+ def showtraceback(self):
+ type, value, tb = sys.exc_info()
+ traceback.print_exception(type, value, tb)
+
+ def runcode(self, code):
+ super().runcode(code)
+ if self.poll_func:
+ self.poll_func()
+ if "mon" in self.locals:
+ try:
+ self.locals["mon"].poll()
+ except Exception as e:
+ print(f"mon.poll() failed: {e!r}")
+ if "u" in self.locals:
+ self.locals["u"].push_simd()
+
+
+class ExitConsole(SystemExit):
+ pass
+cmd_list = {}
+subcmd_list = {}
+# Debug levels
+DBL_NONE = 0
+DBL_INFO = 1
+DBL_TRACE = 2
+DBL_DEBUG = 3
+DBL_EDEBUG = 4
+
+db_level = DBL_NONE
+
+def debug_cmd(db=None):
+ '''Set debug level to integer %d(none)...%d(extreme debug)''' % (DBL_NONE, DBL_EDEBUG)
+ global db_level
+ if db:
+ db_level = db
+ print("debug level=%d" % db_level)
+
+def help_cmd(arg=None):
+ if db_level >= DBL_DEBUG:
+ print("arg=%s" % repr(arg))
+ if arg:
+ #cmd = arg.__qualname__
+ if callable(arg):
+ cmd = arg.__name__
+ elif isinstance(arg, str):
+ cmd = arg
+ else:
+ print("Unknown command: %s" % repr(arg))
+ return
+ if db_level >= DBL_DEBUG:
+ print("cmd=%s" % repr(cmd))
+ if cmd not in cmd_list:
+ print("Undocumented command %s" % cmd)
+ return
+ hinfo = cmd_list[cmd]
+ if isinstance(hinfo, str):
+ print("%-10s : %s" % (cmd, hinfo))
+ return
+ if cmd in subcmd_list:
+ clist = subcmd_list[cmd]
+ aname = cmd
+ if db_level >= DBL_DEBUG:
+ print("subcmd_list[%s] = %s" %
+ (repr(cmd), repr(clist)))
+ else:
+ print("command %s is not documented" % cmd)
+ return
+ else:
+ clist = cmd_list
+ aname = 'top level'
+ print("Note: To display a category's commands quote the name e.g. help('HV')")
+ print("List of %s commands:" % aname)
+ for cmd in clist.keys():
+ hinfo = clist[cmd]
+ if isinstance(hinfo, str):
+ msg = hinfo.strip().split('\n', 1)[0]
+ elif isinstance(hinfo, int):
+ msg = "%s category - %d subcommands" % (cmd, hinfo)
+ else:
+ print("%s ?" % cmd)
+ continue
+ if len(cmd) <= 10:
+ print("%-10s : %s" % (cmd, msg))
+ else:
+ print("%s:\n %s" % (cmd, msg))
+
+#locals is a dictionary for constructing the
+# InteractiveConsole with. It adds in the callables
+# in proxy utils iface and sysreg into locals
+def run_shell(locals, msg=None, exitmsg=None, poll_func=None):
+ saved_display = sys.displayhook
+ try:
+ def display(val):
+ if isinstance(val, int) and not isinstance(val, bool):
+ builtins._ = val
+ print(hex(val))
+ elif callable(val):
+ val()
+ else:
+ saved_display(val)
+
+ sys.displayhook = display
+
+ # convenience
+ locals["h"] = hex
+ locals["sysreg"] = sysreg
+
+ if "proxy" in locals and "p" not in locals:
+ locals["p"] = locals["proxy"]
+ if "utils" in locals and "u" not in locals:
+ locals["u"] = locals["utils"]
+
+ for obj_name in ("iface", "p", "u"):
+ obj = locals.get(obj_name)
+ obj_class = type(obj)
+ if obj is None:
+ continue
+
+ for attr in dir(obj_class):
+ if attr in locals or attr.startswith('_'):
+ continue
+
+ member = getattr(obj_class, attr)
+ if callable(member) and not isinstance(member, property):
+ cmd = getattr(obj, attr)
+ locals[attr] = cmd
+
+ for attr in dir(sysreg):
+ locals[attr] = getattr(sysreg, attr)
+
+ locals['help'] = help_cmd
+ locals['debug'] = debug_cmd
+ for obj_name in locals.keys():
+ obj = locals.get(obj_name)
+ if obj is None or obj_name.startswith('_'):
+ continue
+ if callable(obj) and not isinstance(obj, property):
+ try:
+ desc = obj_name + str(signature(obj))
+ except:
+ continue
+ qn = obj.__qualname__
+ if qn.find('.') > 0:
+ a = qn.split('.')
+ if a[0] not in subcmd_list:
+ subcmd_list[a[0]] = {}
+ if a[0] not in cmd_list:
+ cmd_list[a[0]] = 1
+ else:
+ cmd_list[a[0]] += 1
+ clist = subcmd_list[a[0]]
+ else:
+ clist = None
+ if locals[obj_name].__doc__:
+ desc += " - " + locals[obj_name].__doc__
+ cmd_list[obj_name] = desc
+ if isinstance(clist, dict):
+ clist[obj_name] = desc
+
+ con = HistoryConsole(locals)
+ con.poll_func = poll_func
+ try:
+ con.interact(msg, exitmsg)
+ except ExitConsole as e:
+ if len(e.args):
+ return e.args[0]
+ else:
+ return
+ finally:
+ con.save_history()
+
+ finally:
+ sys.displayhook = saved_display
+
+if __name__ == "__main__":
+ from .setup import *
+ locals = dict(__main__.__dict__)
+
+ run_shell(locals, msg="Have fun!")
diff --git a/tools/proxyclient/m1n1/sysreg.py b/tools/proxyclient/m1n1/sysreg.py
new file mode 100644
index 0000000..3c08238
--- /dev/null
+++ b/tools/proxyclient/m1n1/sysreg.py
@@ -0,0 +1,383 @@
+# SPDX-License-Identifier: MIT
+import json, os, re
+from enum import Enum, IntEnum, IntFlag
+from .utils import Register, Register64, Register32
+
+__all__ = ["sysreg_fwd", "sysreg_rev"]
+
+def _load_registers():
+ global sysreg_fwd, sysop_fwd
+
+ sysreg_fwd = {}
+ sysop_fwd = {}
+ for fname in ["arm_regs.json", "apple_regs.json"]:
+ data = json.load(open(os.path.join(os.path.dirname(__file__), "..", "..", "tools", fname)))
+ for reg in data:
+ if "accessors" in reg:
+ for acc in reg["accessors"]:
+ if acc in ("MRS", "MSR"):
+ sysreg_fwd[reg["name"]] = tuple(reg["enc"])
+ else:
+ sysop_fwd[acc + " " + reg["name"]] = tuple(reg["enc"])
+ else:
+ sysreg_fwd[reg["name"]] = tuple(reg["enc"])
+
+_load_registers()
+sysreg_rev = {v: k for k, v in sysreg_fwd.items()}
+sysop_rev = {v: k for k, v in sysop_fwd.items()}
+sysop_fwd_id = {k.replace(" ", "_"): v for k,v in sysop_fwd.items()}
+
+globals().update(sysreg_fwd)
+__all__.extend(sysreg_fwd.keys())
+globals().update(sysop_fwd_id)
+__all__.extend(sysop_fwd_id.keys())
+
+def sysreg_name(enc):
+ if enc in sysreg_rev:
+ return sysreg_rev[enc]
+ if enc in sysop_rev:
+ return sysop_rev[enc]
+ return f"s{enc[0]}_{enc[1]}_c{enc[2]}_c{enc[3]}_{enc[4]}"
+
+def sysreg_parse(s):
+ if isinstance(s, tuple) or isinstance(s, list):
+ return tuple(s)
+ s = s.strip()
+ for r in (r"s(\d+)_(\d+)_c(\d+)_c(\d+)_(\d+)", r"(\d+), *(\d+), *(\d+), *(\d+), *(\d+)"):
+ if m := re.match(r, s):
+ enc = tuple(map(int, m.groups()))
+ break
+ else:
+ for i in sysreg_fwd, sysop_fwd, sysop_fwd_id:
+ try:
+ enc = i[s]
+ except KeyError:
+ continue
+ break
+ else:
+ raise Exception(f"Unknown sysreg name {s}")
+ return enc
+
+def DBGBCRn_EL1(n):
+ return (2,0,0,n,5)
+
+def DBGBVRn_EL1(n):
+ return (2,0,0,n,4)
+
+def DBGWCRn_EL1(n):
+ return (2,0,0,n,7)
+
+def DBGWVRn_EL1(n):
+ return (2,0,0,n,6)
+
+class ESR_EC(IntEnum):
+ UNKNOWN = 0b000000
+ WFI = 0b000001
+ FP_TRAP = 0b000111
+ PAUTH_TRAP = 0b001000
+ LS64 = 0b001010
+ BTI = 0b001101
+ ILLEGAL = 0b001110
+ SVC = 0b010101
+ HVC = 0b010110
+ SMC = 0b010111
+ MSR = 0b011000
+ SVE = 0b011001
+ PAUTH_FAIL = 0b011100
+ IABORT_LOWER = 0b100000
+ IABORT = 0b100001
+ PC_ALIGN = 0b100010
+ DABORT_LOWER = 0b100100
+ DABORT = 0b100101
+ SP_ALIGN = 0b100110
+ FP_EXC = 0b101100
+ SERROR = 0b101111
+ BKPT_LOWER = 0b110000
+ BKPT = 0b110001
+ SSTEP_LOWER = 0b110010
+ SSTEP = 0b110011
+ WATCH_LOWER = 0b110100
+ WATCH = 0b110101
+ BRK = 0b111100
+ IMPDEF = 0b111111
+
+class MSR_DIR(IntEnum):
+ WRITE = 0
+ READ = 1
+
+class ESR_ISS_MSR(Register32):
+ Op0 = 21, 20
+ Op2 = 19, 17
+ Op1 = 16, 14
+ CRn = 13, 10
+ Rt = 9, 5
+ CRm = 4, 1
+ DIR = 0, 0, MSR_DIR
+
+class DABORT_DFSC(IntEnum):
+ ASIZE_L0 = 0b000000
+ ASIZE_L1 = 0b000001
+ ASIZE_L2 = 0b000010
+ ASIZE_L3 = 0b000011
+ XLAT_L0 = 0b000100
+ XLAT_L1 = 0b000101
+ XLAT_L2 = 0b000110
+ XLAT_L3 = 0b000111
+ AF_L0 = 0b001000
+ AF_L1 = 0b001001
+ AF_L2 = 0b001010
+ AF_L3 = 0b001011
+ PERM_L0 = 0b001100
+ PERM_L1 = 0b001101
+ PERM_L2 = 0b001110
+ PERM_L3 = 0b001111
+ EABORT = 0b010000
+ TAG_CHECK = 0b010001
+ PT_EABORT_Lm1 = 0b010011
+ PT_EABORT_L0 = 0b010100
+ PT_EABORT_L1 = 0b010101
+ PT_EABORT_L2 = 0b010110
+ PT_EABORT_L3 = 0b010111
+ ECC_ERROR = 0b011000
+ PT_ECC_ERROR_Lm1 = 0b011011
+ PT_ECC_ERROR_L0 = 0b011100
+ PT_ECC_ERROR_L1 = 0b011101
+ PT_ECC_ERROR_L2 = 0b011110
+ PT_ECC_ERROR_L3 = 0b011111
+ ALIGN = 0b100001
+ ASIZE_Lm1 = 0b101001
+ XLAT_Lm1 = 0b101011
+ TLB_CONFLICT = 0b110000
+ UNSUPP_ATOMIC = 0b110001
+ IMPDEF_LOCKDOWN = 0b110100
+ IMPDEF_ATOMIC = 0b110101
+
+class ESR_ISS_DABORT(Register32):
+ ISV = 24
+ SAS = 23, 22
+ SSE = 21
+ SRT = 20, 16
+ SF = 15
+ AR = 14
+ VNCR = 13
+ SET = 12, 11
+ LSR = 12, 11
+ FnV = 10
+ EA = 9
+ CM = 8
+ S1PTR = 7
+ WnR = 6
+ DFSC = 5, 0, DABORT_DFSC
+
+class ESR(Register64):
+ ISS2 = 36, 32
+ EC = 31, 26, ESR_EC
+ IL = 25
+ ISS = 24, 0
+
+class SPSR_M(IntEnum):
+ EL0t = 0
+ EL1t = 4
+ EL1h = 5
+ EL2t = 8
+ EL2h = 9
+
+class SPSR(Register64):
+ N = 31
+ Z = 30
+ C = 29
+ V = 28
+ TCO = 25
+ DIT = 24
+ UAO = 23
+ PAN = 22
+ SS = 21
+ IL = 20
+ SSBS = 12
+ BTYPE = 11, 10
+ D = 9
+ A = 8
+ I = 7
+ F = 6
+ M = 4, 0, SPSR_M
+
+class ACTLR(Register64):
+ EnMDSB = 12
+ EnPRSV = 6
+ EnAFP = 5
+ EnAPFLG = 4
+ DisHWP = 3
+ EnTSO = 1
+
+class HCR(Register64):
+ TWEDEL = 63, 60
+ TWEDEn = 59
+ TID5 = 58
+ DCT = 57
+ ATA = 56
+ TTLBOS = 55
+ TTLBIS = 54
+ EnSCXT = 53
+ TOCU = 52
+ AMVOFFEN = 51
+ TICAB = 50
+ TID4 = 49
+ FIEN = 47
+ FWB = 46
+ NV2 = 45
+ AT = 44
+ NV1 = 43
+ NV = 42
+ API = 41
+ APK = 40
+ MIOCNCE = 38
+ TEA = 37
+ TERR = 36
+ TLOR = 35
+ E2H = 34
+ ID = 33
+ CD = 32
+ RW = 31
+ TRVM = 30
+ HCD = 29
+ TDZ = 28
+ TGE = 27
+ TVM = 26
+ TTLB = 25
+ TPU = 24
+ TPCP = 23
+ TPC = 23
+ TSW = 22
+ TACR = 21
+ TIDCP = 20
+ TSC = 19
+ TID3 = 18
+ TID2 = 17
+ TID1 = 16
+ TID0 = 15
+ TWE = 14
+ TWI = 13
+ DC = 12
+ BSU = 11, 10
+ FB = 9
+ VSE = 8
+ VI = 7
+ VF = 6
+ AMO = 5
+ IMO = 4
+ FMO = 3
+ PTW = 2
+ SWIO = 1
+ VM = 0
+
+class HACR(Register64):
+ TRAP_CPU_EXT = 0
+ TRAP_AIDR = 4
+ TRAP_AMX = 10
+ TRAP_SPRR = 11
+ TRAP_GXF = 13
+ TRAP_CTRR = 14
+ TRAP_IPI = 16
+ TRAP_s3_4_c15_c5z6_x = 18
+ TRAP_s3_4_c15_c0z12_5 = 19
+ GIC_CNTV = 20
+ TRAP_s3_4_c15_c10_4 = 25
+ TRAP_SERROR_INFO = 48
+ TRAP_EHID = 49
+ TRAP_HID = 50
+ TRAP_s3_0_c15_c12_1z2 = 51
+ TRAP_ACC = 52
+ TRAP_PM = 57
+ TRAP_UPM = 58
+ TRAP_s3_1z7_c15_cx_3 = 59
+
+class AMX_CTL(Register64):
+ EN = 63
+ EN_EL1 = 62
+
+class MDCR(Register64):
+ TDE = 8
+ TDA = 9
+ TDOSA = 10
+ TDRA = 11
+
+class MDSCR(Register64):
+ SS = 0
+ MDE = 15
+
+class DBGBCR(Register32):
+ BT = 23, 20
+ LBN = 16, 16
+ SSC = 15, 14
+ HMC = 13
+ BAS = 8,5
+ PMC = 2,1
+ E = 0
+
+class DBGWCR_LSC(IntFlag):
+ L = 1
+ S = 2
+
+class DBGWCR(Register32):
+ SSCE = 29
+ MASK = 28, 24
+ WT = 20
+ LBN = 19, 16
+ SSC = 15, 14
+ HMC = 13
+ BAS = 12, 5
+ LSC = 4, 3
+ PAC = 2, 1
+ E = 0
+
+# TCR_EL1
+class TCR(Register64):
+ DS = 59
+ TCMA1 = 58
+ TCMA0 = 57
+ E0PD1 = 56
+ E0PD0 = 55
+ NFD1 = 54
+ NFD0 = 53
+ TBID1 = 52
+ TBID0 = 51
+ HWU162 = 50
+ HWU161 = 49
+ HWU160 = 48
+ HWU159 = 47
+ HWU062 = 46
+ HWU061 = 45
+ HWU060 = 44
+ HWU059 = 43
+ HPD1 = 42
+ HPD0 = 41
+ HD = 40
+ HA = 39
+ TBI1 = 38
+ TBI0 = 37
+ AS = 36
+ IPS = 34, 32
+ TG1 = 31, 30
+ SH1 = 29, 28
+ ORGN1 = 27, 26
+ IRGN1 = 25, 24
+ EPD1 = 23
+ A1 = 22
+ T1SZ = 21, 16
+ TG0 = 15, 14
+ SH0 = 13, 12
+ ORGN0 = 11, 10
+ IRGN0 = 9, 8
+ EPD0 = 7
+ T0SZ = 5, 0
+
+class TLBI_RVA(Register64):
+ ASID = 63, 48
+ TG = 47, 46
+ SCALE = 45, 44
+ NUM = 43, 39
+ TTL = 38, 37
+ BaseADDR = 36, 0
+
+__all__.extend(k for k, v in globals().items()
+ if (callable(v) or isinstance(v, type)) and v.__module__ == __name__)
diff --git a/tools/proxyclient/m1n1/tgtypes.py b/tools/proxyclient/m1n1/tgtypes.py
new file mode 100644
index 0000000..9081c8d
--- /dev/null
+++ b/tools/proxyclient/m1n1/tgtypes.py
@@ -0,0 +1,30 @@
+# SPDX-License-Identifier: MIT
+from construct import *
+
+__all__ = ["BootArgs"]
+
+BootArgs = Struct(
+ "revision" / Hex(Int16ul),
+ "version" / Hex(Int16ul),
+ Padding(4),
+ "virt_base" / Hex(Int64ul),
+ "phys_base" / Hex(Int64ul),
+ "mem_size" / Hex(Int64ul),
+ "top_of_kernel_data" / Hex(Int64ul),
+ "video" / Struct(
+ "base" / Hex(Int64ul),
+ "display" / Hex(Int64ul),
+ "stride" / Hex(Int64ul),
+ "width" / Hex(Int64ul),
+ "height" / Hex(Int64ul),
+ "depth" / Hex(Int64ul),
+ ),
+ "machine_type" / Hex(Int32ul),
+ Padding(4),
+ "devtree" / Hex(Int64ul),
+ "devtree_size" / Hex(Int32ul),
+ "cmdline" / PaddedString(608, "ascii"),
+ Padding(4),
+ "boot_flags" / Hex(Int64ul),
+ "mem_size_actual" / Hex(Int64ul),
+)
diff --git a/tools/proxyclient/m1n1/trace/__init__.py b/tools/proxyclient/m1n1/trace/__init__.py
new file mode 100644
index 0000000..73d1a10
--- /dev/null
+++ b/tools/proxyclient/m1n1/trace/__init__.py
@@ -0,0 +1,220 @@
+# SPDX-License-Identifier: MIT
+
+from ..hv import TraceMode
+from ..utils import *
+
+__all__ = []
+
+class RegCacheAlwaysCached(Reloadable):
+ def __init__(self, parent):
+ self.parent = parent
+
+ def read(self, addr, width):
+ return self.parent.read_cached(addr, width)
+
+ def write(self, addr, data, width):
+ raise Exception("Trying to write a register to the cache")
+
+class RegCache(Reloadable):
+ def __init__(self, hv):
+ self.hv = hv
+ self.u = hv.u
+ self.cache = {}
+
+ self.cached = RegCacheAlwaysCached(self)
+
+ def update(self, addr, data):
+ self.cache[addr] = data
+
+ def read(self, addr, width):
+ if self.hv.ctx or not self.hv.started:
+ data = self.u.read(addr, width)
+ self.cache[addr] = data
+ return data
+ else:
+ return self.read_cached(addr, width)
+
+ def read_cached(self, addr, width):
+ data = self.cache.get(addr, None)
+ if data is None:
+ print(f"RegCache: no cache for {addr:#x}")
+ return data
+
+ def write(self, addr, data, width):
+ if self.hv.ctx:
+ self.u.write(addr, data, width)
+ self.cache[addr] = data
+ else:
+ raise Exception("Cannot write register in asynchronous context")
+
+class TracerState:
+ pass
+
+class Tracer(Reloadable):
+ DEFAULT_MODE = TraceMode.ASYNC
+
+ def __init__(self, hv, verbose=False, ident=None):
+ self.hv = hv
+ self.ident = ident or type(self).__name__
+ self.regmaps = {}
+ self.verbose = verbose
+ self.state = TracerState()
+ self.init_state()
+ self._cache = RegCache(hv)
+ cache = hv.tracer_caches.get(self.ident, None)
+ if cache is not None:
+ self._cache.cache.update(cache.get("regcache", {}))
+ self.state.__dict__.update(cache.get("state", {}))
+ hv.tracer_caches[self.ident] = {
+ "regcache": self._cache.cache,
+ "state": self.state.__dict__
+ }
+
+ def init_state(self):
+ pass
+
+ def hook_w(self, addr, val, width, **kwargs):
+ self.hv.u.write(addr, val, width)
+
+ def hook_r(self, addr, width, **kwargs):
+ return self.hv.u.read(addr, width)
+
+ def evt_rw(self, evt, regmap=None, prefix=None):
+ self._cache.update(evt.addr, evt.data)
+ reg = rcls = None
+ value = evt.data
+
+ t = "w" if evt.flags.WRITE else "r"
+
+ if regmap is not None:
+ reg, index, rcls = regmap.lookup_addr(evt.addr)
+ if rcls is not None:
+ value = rcls(evt.data)
+
+ if self.verbose >= 3 or (reg is None and self.verbose >= 1):
+ if reg is None:
+ s = f"{evt.addr:#x} = {value:#x}"
+ else:
+ s = f"{regmap.get_name(evt.addr)} = {value!s}"
+ m = "+" if evt.flags.MULTI else " "
+ self.log(f"MMIO: {t.upper()}.{1<<evt.flags.WIDTH:<2}{m} " + s)
+
+ if reg is not None:
+ if prefix is not None:
+ attr = f"{t}_{prefix}_{reg}"
+ else:
+ attr = f"{t}_{reg}"
+ handler = getattr(self, attr, None)
+ if handler:
+ if index is not None:
+ handler(value, index)
+ else:
+ handler(value)
+ elif self.verbose == 2:
+ s = f"{regmap.get_name(evt.addr)} = {value!s}"
+ m = "+" if evt.flags.MULTI else " "
+ self.log(f"MMIO: {t.upper()}.{1<<evt.flags.WIDTH:<2}{m} " + s)
+
+ def trace(self, start, size, mode, read=True, write=True, **kwargs):
+ zone = irange(start, size)
+ if mode == TraceMode.HOOK:
+ self.hv.add_tracer(zone, self.ident, mode, self.hook_r if read else None,
+ self.hook_w if write else None, **kwargs)
+ else:
+ self.hv.add_tracer(zone, self.ident, mode, self.evt_rw if read else None,
+ self.evt_rw if write else None, **kwargs)
+
+ def trace_regmap(self, start, size, cls, mode=None, name=None, prefix=None, regmap_offset=0):
+ if mode is None:
+ mode = self.DEFAULT_MODE
+ if name is None:
+ name = cls.__name__
+
+ regmap = self.regmaps.get(start - regmap_offset, None)
+ if regmap is None:
+ regmap = cls(self._cache, start - regmap_offset)
+ regmap.cached = cls(self._cache.cached, start - regmap_offset)
+ self.regmaps[start - regmap_offset] = regmap
+ else:
+ assert isinstance(regmap, cls)
+
+ setattr(self, name, regmap)
+ self.trace(start, size, mode=mode, regmap=regmap, prefix=prefix)
+
+ def start(self):
+ pass
+
+ def stop(self):
+ self.hv.clear_tracers(self.ident)
+
+ def log(self, msg, show_cpu=True):
+ self.hv.log(f"[{self.ident}] {msg}", show_cpu=show_cpu)
+
+class PrintTracer(Tracer):
+ def __init__(self, hv, device_addr_tbl):
+ super().__init__(hv)
+ self.device_addr_tbl = device_addr_tbl
+ self.log_file = None
+
+ def event_mmio(self, evt, name=None, start=None):
+ dev, zone2 = self.device_addr_tbl.lookup(evt.addr)
+ if name is None:
+ name = dev
+ start = zone2.start
+ t = "W" if evt.flags.WRITE else "R"
+ m = "+" if evt.flags.MULTI else " "
+ logline = (f"[cpu{evt.flags.CPU}] [0x{evt.pc:016x}] MMIO: {t}.{1<<evt.flags.WIDTH:<2}{m} " +
+ f"0x{evt.addr:x} ({name}, offset {evt.addr - start:#04x}) = 0x{evt.data:x}")
+ print(logline)
+ if self.log_file:
+ self.log_file.write(f"# {logline}\n")
+ width = 8 << evt.flags.WIDTH
+ if evt.flags.WRITE:
+ stmt = f"p.write{width}({start:#x} + {evt.addr - start:#x}, {evt.data:#x})\n"
+ else:
+ stmt = f"p.read{width}({start:#x} + {evt.addr - start:#x})\n"
+ self.log_file.write(stmt)
+
+class ADTDevTracer(Tracer):
+ REGMAPS = []
+ NAMES = []
+ PREFIXES = []
+
+ def __init__(self, hv, devpath, verbose=False):
+ super().__init__(hv, verbose=verbose, ident=type(self).__name__ + "@" + devpath)
+ self.dev = hv.adt[devpath]
+
+ @classmethod
+ def _reloadcls(cls, force=False):
+ regmaps = []
+ for i in cls.REGMAPS:
+ if i is None:
+ reloaded = None
+ elif isinstance(i, tuple):
+ reloaded = (i[0]._reloadcls(force), i[1])
+ else:
+ reloaded = i._reloadcls(force)
+ regmaps.append(reloaded)
+ cls.REGMAPS = regmaps
+
+ return super()._reloadcls(force)
+
+ def start(self):
+ for i in range(len(self.dev.reg)):
+ if i >= len(self.REGMAPS) or (regmap := self.REGMAPS[i]) is None:
+ continue
+ if isinstance(regmap, tuple):
+ regmap, regmap_offset = regmap
+ else:
+ regmap_offset = 0
+ prefix = name = None
+ if i < len(self.NAMES):
+ name = self.NAMES[i]
+ if i < len(self.PREFIXES):
+ prefix = self.PREFIXES[i]
+
+ start, size = self.dev.get_reg(i)
+ self.trace_regmap(start, size, regmap, name=name, prefix=prefix, regmap_offset=regmap_offset)
+
+__all__.extend(k for k, v in globals().items()
+ if (callable(v) or isinstance(v, type)) and v.__module__.startswith(__name__))
diff --git a/tools/proxyclient/m1n1/trace/agx.py b/tools/proxyclient/m1n1/trace/agx.py
new file mode 100644
index 0000000..f46a063
--- /dev/null
+++ b/tools/proxyclient/m1n1/trace/agx.py
@@ -0,0 +1,1148 @@
+# SPDX-License-Identifier: MIT
+
+import textwrap
+from .asc import *
+from ..hw.uat import UAT, MemoryAttr, PTE, Page_PTE, TTBR
+from ..hw.agx import *
+
+from ..fw.agx.initdata import InitData
+from ..fw.agx.channels import *
+from ..fw.agx.cmdqueue import *
+from ..fw.agx.microsequence import *
+from ..fw.agx.handoff import *
+
+from m1n1.proxyutils import RegMonitor
+from m1n1.constructutils import *
+from m1n1.trace import Tracer
+
+from construct import *
+
+class ChannelTraceState(object):
+ pass
+
+class CommandQueueState(object):
+ pass
+
+class GpuMsg(Register64):
+ TYPE = 55, 48
+
+class PongMsg(GpuMsg):
+ TYPE = 59, 52
+ UNK = 47, 0
+
+class PongEp(EP):
+ # This endpoint recives pongs. The cpu code reads some status registers after receiving one
+ # Might be a "work done" message.
+ BASE_MESSAGE = GpuMsg
+
+ @msg(0x42, DIR.RX, PongMsg)
+ def pong_rx(self, msg):
+ if self.tracer.state.active:
+ self.log(f" Pong {msg!s}")
+ if msg.UNK != 0:
+ self.log(f" Pong had unexpected value{msg.UNK:x}")
+ self.hv.run_shell()
+
+ self.tracer.pong()
+ return True
+
+ @msg(0x81, DIR.TX, PongMsg)
+ def init_ep(self, msg):
+ self.log(f" Init {msg.UNK:x}")
+
+ self.tracer.pong_init(msg.UNK)
+ return True
+
+class KickMsg(GpuMsg):
+ TYPE = 59, 52
+ KICK = 7, 0 # Seen: 17, 16 (common), 9, 8, 1 (common), 0 (common)
+
+class KickEp(EP):
+ BASE_MESSAGE = GpuMsg
+
+ @msg(0x83, DIR.TX, KickMsg)
+ def kick(self, msg):
+ if self.tracer.state.active:
+ self.log(f" Kick {msg}")
+ self.tracer.kick(msg.KICK)
+
+ return True
+
+ @msg(0x84, DIR.TX, KickMsg)
+ def fwkick(self, msg):
+ if self.tracer.state.active:
+ self.log(f" FWRing Kick {msg}")
+ self.tracer.fwkick(msg.KICK)
+ return True
+
+class ChannelTracer(Reloadable):
+ STATE_FIELDS = ChannelStateFields
+ WPTR = 0x20
+ RPTR = 0x00
+
+ def __init__(self, tracer, info, index):
+ self.tracer = tracer
+ self.uat = tracer.uat
+ self.hv = tracer.hv
+ self.u = self.hv.u
+ self.ring_count = len(channelRings[index])
+ self.verbose = False
+
+ if index not in tracer.state.channels:
+ self.state = ChannelTraceState()
+ self.state.active = True
+ self.state.tail = [0] * self.ring_count
+ tracer.state.channels[index] = self.state
+ else:
+ self.state = tracer.state.channels[index]
+
+ self.index = index
+ self.name = channelNames[index]
+ self.info = info
+ base = None
+
+ if self.name == "FWLog":
+ base = self.tracer.state.fwlog_ring2
+
+ self.channel = Channel(self.u, self.uat, self.info, channelRings[index], base=base,
+ state_fields=self.STATE_FIELDS)
+ for i in range(self.ring_count):
+ for addr, size in self.channel.rb_maps[i]:
+ self.log(f"rb_map[{i}] {addr:#x} ({size:#x})")
+
+ self.set_active(self.state.active)
+
+ def state_read(self, evt, regmap=None, prefix=None, off=None):
+ ring = off // 0x30
+ off = off % 0x30
+
+ msgcls, size, count = self.channel.ring_defs[ring]
+
+ if off == self.WPTR:
+ if self.verbose:
+ self.log(f"RD [{evt.addr:#x}] WPTR[{ring}] = {evt.data:#x}")
+ self.poll_ring(ring)
+ elif off == self.RPTR:
+ if self.verbose:
+ self.log(f"RD [{evt.addr:#x}] RPTR[{ring}] = {evt.data:#x}")
+ self.poll_ring(ring)
+ else:
+ if self.verbose:
+ self.log(f"RD [{evt.addr:#x}] UNK[{ring}] {off:#x} = {evt.data:#x}")
+
+ def state_write(self, evt, regmap=None, prefix=None, off=None):
+ ring = off // 0x30
+ off = off % 0x30
+
+ msgcls, size, count = self.channel.ring_defs[ring]
+
+ if off == self.WPTR:
+ if self.verbose:
+ self.log(f"WR [{evt.addr:#x}] WPTR[{ring}] = {evt.data:#x}")
+ self.poll_ring(ring)
+ elif off == self.RPTR:
+ if self.verbose:
+ self.log(f"WR [{evt.addr:#x}] RPTR[{ring}] = {evt.data:#x}")
+ self.poll_ring(ring)
+ # Clear message with test pattern
+ idx = (evt.data - 1) % count
+ self.channel.clear_message(ring, idx)
+ else:
+ if self.verbose:
+ self.log(f"WR [{evt.addr:#x}] UNK[{ring}] {off:#x} = {evt.data:#x}")
+
+ def log(self, msg):
+ self.tracer.log(f"[{self.index}:{self.name}] {msg}")
+
+ def poll(self):
+ for i in range(self.ring_count):
+ self.poll_ring(i)
+
+ def poll_ring(self, ring):
+ msgcls, size, count = self.channel.ring_defs[ring]
+
+ cur = self.state.tail[ring]
+ tail = self.channel.state[ring].WRITE_PTR.val
+ if tail >= count:
+ raise Exception(f"Message index {tail:#x} >= {count:#x}")
+ if cur != tail:
+ #self.log(f"{cur:#x} -> {tail:#x}")
+ while cur != tail:
+ msg = self.channel.get_message(ring, cur, self.tracer.meta_gpuvm)
+ self.log(f"Message @{ring}.{cur}:\n{msg!s}")
+ self.tracer.handle_ringmsg(msg)
+ #if self.index < 12:
+ #self.hv.run_shell()
+ cur = (cur + 1) % count
+ self.state.tail[ring] = cur
+
+ def set_active(self, active=True):
+ if active:
+ if not self.state.active:
+ for ring in range(self.ring_count):
+ self.state.tail[ring] = self.channel.state[ring].WRITE_PTR.val
+
+ for base in range(0, 0x30 * self.ring_count, 0x30):
+ p = self.uat.iotranslate(0, self.channel.state_addr + base + self.RPTR, 4)[0][0]
+ self.hv.add_tracer(irange(p, 4),
+ f"ChannelTracer/{self.name}",
+ mode=TraceMode.SYNC,
+ read=self.state_read,
+ write=self.state_write,
+ off=base + self.RPTR)
+ p = self.uat.iotranslate(0, self.channel.state_addr + base + self.WPTR, 4)[0][0]
+ self.hv.add_tracer(irange(p, 4),
+ f"ChannelTracer/{self.name}",
+ mode=TraceMode.SYNC,
+ read=self.state_read,
+ write=self.state_write,
+ off=base + self.WPTR)
+ else:
+ self.hv.clear_tracers(f"ChannelTracer/{self.name}")
+ self.state.active = active
+
+ChannelTracer = ChannelTracer._reloadcls()
+CommandQueueInfo = CommandQueueInfo._reloadcls()
+
+class FWCtlChannelTracer(ChannelTracer):
+ STATE_FIELDS = FWControlStateFields
+ WPTR = 0x10
+ RPTR = 0x00
+
+class CommandQueueTracer(Reloadable):
+ def __init__(self, tracer, info_addr, new_queue):
+ self.tracer = tracer
+ self.uat = tracer.uat
+ self.hv = tracer.hv
+ self.u = self.hv.u
+ self.verbose = False
+ self.info_addr = info_addr
+
+ if info_addr not in tracer.state.queues:
+ self.state = CommandQueueState()
+ self.state.rptr = None
+ self.state.active = True
+ tracer.state.queues[info_addr] = self.state
+ else:
+ self.state = tracer.state.queues[info_addr]
+
+ if new_queue:
+ self.state.rptr = 0
+
+ self.update_info()
+
+ def update_info(self):
+ self.info = CommandQueueInfo.parse_stream(self.tracer.get_stream(0, self.info_addr))
+
+ def log(self, msg):
+ self.tracer.log(f"[CQ@{self.info_addr:#x}] {msg}")
+
+ @property
+ def rb_size(self):
+ return self.info.pointers.rb_size
+
+ def get_workitems(self, workmsg):
+ self.tracer.uat.invalidate_cache()
+ self.update_info()
+
+ if self.state.rptr is None:
+ self.state.rptr = int(self.info.pointers.gpu_doneptr)
+ self.log(f"Initializing rptr to {self.info.gpu_rptr1:#x}")
+
+ self.log(f"Got workmsg: wptr={workmsg.head:#x} rptr={self.state.rptr:#x}")
+ self.log(f"Queue info: {self.info}")
+
+
+ assert self.state.rptr < self.rb_size
+ assert workmsg.head < self.rb_size
+
+ stream = self.tracer.get_stream(0, self.info.rb_addr)
+
+ count = 0
+ orig_rptr = rptr = self.state.rptr
+ while rptr != workmsg.head:
+ count += 1
+ stream.seek(self.info.rb_addr + rptr * 8, 0)
+ pointer = Int64ul.parse_stream(stream)
+ self.log(f"WI item @{rptr:#x}: {pointer:#x}")
+ if pointer:
+ stream.seek(pointer, 0)
+ yield CmdBufWork.parse_stream(stream)
+ rptr = (rptr + 1) % self.rb_size
+
+ self.state.rptr = rptr
+
+ self.log(f"Parsed {count} items from {orig_rptr:#x} to {workmsg.head:#x}")
+
+ def set_active(self, active=True):
+ if not active:
+ self.state.rptr = None
+ self.state.active = active
+
+CmdBufWork = CmdBufWork._reloadcls()
+CommandQueueTracer = CommandQueueTracer._reloadcls()
+InitData = InitData._reloadcls(True)
+
+class HandoffTracer(Tracer):
+ DEFAULT_MODE = TraceMode.SYNC
+
+ def __init__(self, hv, agx_tracer, base, verbose=False):
+ super().__init__(hv, verbose=verbose)
+ self.agx_tracer = agx_tracer
+ self.base = base
+
+ def start(self):
+ self.trace_regmap(self.base, 0x4000, GFXHandoffStruct, name="regs")
+
+class SGXTracer(ADTDevTracer):
+ DEFAULT_MODE = TraceMode.HOOK
+
+ REGMAPS = [SGXRegs, SGXInfoRegs]
+ NAMES = ["sgx", "sgx-id"]
+
+ def __init__(self, hv, devpath, verbose=False):
+ super().__init__(hv, devpath, verbose=verbose)
+ self.hooks = {}
+
+ def hook_r(self, addr, width, **kwargs):
+ self.log(f"HOOK: {addr:#x}:{width}")
+
+ if addr in self.hooks:
+ val = self.hooks[addr]
+ self.log(f" Returning: {val:#x}")
+ else:
+ xval = val = super().hook_r(addr, width, **kwargs)
+ if isinstance(val, (list, tuple)):
+ xval = list(map(hex, val))
+ else:
+ xval = hex(val)
+ self.log(f" Read: {xval}")
+
+ return val
+
+ def hook_w(self, addr, val, width, **kwargs):
+ if isinstance(val, (list, tuple)):
+ xval = list(map(hex, val))
+ else:
+ xval = hex(val)
+
+ self.log(f"HOOK: {addr:#x}:{width} = {xval}")
+
+ super().hook_w(addr, val, width, **kwargs)
+
+class AGXTracer(ASCTracer):
+ ENDPOINTS = {
+ 0x20: PongEp,
+ 0x21: KickEp
+ }
+
+ REGMAPS = [ASCRegs]
+ NAMES = ["asc"]
+
+ PAGESIZE = 0x4000
+
+ def __init__(self, hv, devpath, verbose=False):
+ super().__init__(hv, devpath, verbose)
+ self.channels = []
+ self.uat = UAT(hv.iface, hv.u, hv)
+ self.mon = RegMonitor(hv.u, ascii=True, log=hv.log)
+ self.dev_sgx = hv.u.adt["/arm-io/sgx"]
+ self.sgx = SGXRegs(hv.u, self.dev_sgx.get_reg(0)[0])
+ self.gpu_region = getattr(self.dev_sgx, "gpu-region-base")
+ self.gpu_region_size = getattr(self.dev_sgx, "gpu-region-size")
+ self.gfx_shared_region = getattr(self.dev_sgx, "gfx-shared-region-base")
+ self.gfx_shared_region_size = getattr(self.dev_sgx, "gfx-shared-region-size")
+ self.gfx_handoff = getattr(self.dev_sgx, "gfx-handoff-base")
+ self.gfx_handoff_size = getattr(self.dev_sgx, "gfx-handoff-size")
+
+ self.handoff_tracer = HandoffTracer(hv, self, self.gfx_handoff, verbose=2)
+
+ self.ignorelist = []
+ self.last_msg = None
+
+ # self.mon.add(self.gpu_region, self.gpu_region_size, "contexts")
+ # self.mon.add(self.gfx_shared_region, self.gfx_shared_region_size, "gfx-shared")
+ # self.mon.add(self.gfx_handoff, self.gfx_handoff_size, "gfx-handoff")
+
+ self.trace_kernva = False
+ self.trace_userva = False
+ self.trace_kernmap = True
+ self.trace_usermap = True
+ self.pause_after_init = False
+ self.shell_after_init = False
+ self.after_init_hook = None
+ self.encoder_id_filter = None
+ self.redump = False
+
+ self.vmcnt = 0
+ self.readlog = {}
+ self.writelog = {}
+ self.cmdqueues = {}
+ self.va_to_pa = {}
+
+ self.last_ta = None
+ self.last_3d = None
+
+
+ def get_cmdqueue(self, info_addr, new_queue):
+ if info_addr in self.cmdqueues and not new_queue:
+ return self.cmdqueues[info_addr]
+
+ cmdqueue = CommandQueueTracer(self, info_addr, new_queue)
+ self.cmdqueues[info_addr] = cmdqueue
+
+ return cmdqueue
+
+ def clear_ttbr_tracers(self):
+ self.hv.clear_tracers(f"UATTTBRTracer")
+
+ def add_ttbr_tracers(self):
+ self.hv.add_tracer(irange(self.gpu_region, UAT.NUM_CONTEXTS * 16),
+ f"UATTTBRTracer",
+ mode=TraceMode.WSYNC,
+ write=self.uat_write,
+ iova=0,
+ base=self.gpu_region,
+ level=3)
+
+ def clear_uatmap_tracers(self, ctx=None):
+ if ctx is None:
+ for i in range(UAT.NUM_CONTEXTS):
+ self.clear_uatmap_tracers(i)
+ else:
+ self.hv.clear_tracers(f"UATMapTracer/{ctx}")
+
+ def add_uatmap_tracers(self, ctx=None):
+ self.log(f"add_uatmap_tracers({ctx})")
+ if ctx is None:
+ if self.trace_kernmap:
+ self.add_uatmap_tracers(0)
+ if self.trace_usermap:
+ for i in range(1, UAT.NUM_CONTEXTS):
+ self.add_uatmap_tracers(i)
+ return
+
+ if ctx != 0 and not self.trace_usermap:
+ return
+ if ctx == 0 and not self.trace_kernmap:
+ return
+
+ def trace_pt(start, end, idx, pte, level, sparse):
+ if start >= 0xf8000000000 and (ctx != 0 or not self.trace_kernmap):
+ return
+ if start < 0xf8000000000 and not self.trace_usermap:
+ return
+ self.log(f"Add UATMapTracer/{ctx} {start:#x}")
+ self.hv.add_tracer(irange(pte.offset(), 0x4000),
+ f"UATMapTracer/{ctx}",
+ mode=TraceMode.WSYNC,
+ write=self.uat_write,
+ iova=start,
+ base=pte.offset(),
+ level=2 - level,
+ ctx=ctx)
+
+ self.uat.foreach_table(ctx, trace_pt)
+
+ def clear_gpuvm_tracers(self, ctx=None):
+ if ctx is None:
+ for i in range(UAT.NUM_CONTEXTS):
+ self.clear_gpuvm_tracers(i)
+ else:
+ self.hv.clear_tracers(f"GPUVM/{ctx}")
+
+ def add_gpuvm_tracers(self, ctx=None):
+ self.log(f"add_gpuvm_tracers({ctx})")
+ if ctx is None:
+ self.add_gpuvm_tracers(0)
+ if self.trace_userva:
+ for i in range(1, UAT.NUM_CONTEXTS):
+ self.add_gpuvm_tracers(i)
+ return
+
+ def trace_page(start, end, idx, pte, level, sparse):
+ self.uat_page_mapped(start, pte, ctx)
+
+ self.uat.foreach_page(ctx, trace_page)
+
+ def uat_write(self, evt, level=3, base=0, iova=0, ctx=None):
+ off = (evt.addr - base) // 8
+ sh = ["NS", "??", "OS", "IS"]
+ a = f"{evt.flags.ATTR:02x}:{sh[evt.flags.SH]}"
+ self.log(f"UAT <{a}> write L{level} at {ctx}:{iova:#x} (#{off:#x}) -> {evt.data}")
+
+ if level == 3:
+ ctx = off // 2
+ is_kernel = off & 1
+ if ctx != 0 and is_kernel:
+ return
+
+ if is_kernel:
+ iova += 0xf8000000000
+ pte = TTBR(evt.data)
+ if not pte.valid():
+ self.log(f"Context {ctx} invalidated")
+ self.uat.invalidate_cache()
+ self.clear_uatmap_tracers(ctx)
+ self.clear_gpuvm_tracers(ctx)
+ return
+ self.log(f"Dumping UAT for context {ctx}")
+ self.uat.invalidate_cache()
+ _, pt = self.uat.get_pt(self.uat.gpu_region + ctx * 16, 2)
+ pt[off & 1] = evt.data
+ self.uat.dump(ctx, log=self.log)
+ self.add_uatmap_tracers(ctx)
+ self.add_gpuvm_tracers(ctx)
+ else:
+ is_kernel = iova >= 0xf8000000000
+ iova += off << (level * 11 + 14)
+ if level == 0:
+ pte = Page_PTE(evt.data)
+ self.uat_page_mapped(iova, pte, ctx)
+ return
+ else:
+ pte = PTE(evt.data)
+
+ if not pte.valid():
+ try:
+ paddr = self.va_to_pa[(ctx, level, iova)]
+ except KeyError:
+ return
+ self.hv.del_tracer(irange(paddr, 0x4000),
+ f"UATMapTracer/{ctx}")
+ del self.va_to_pa[(ctx, level, iova)]
+ return
+
+ if ctx != 0 and not self.trace_usermap:
+ return
+ if ctx == 0 and not self.trace_kernmap:
+ return
+
+ self.va_to_pa[(ctx, level, iova)] = pte.offset()
+ level -= 1
+ self.hv.add_tracer(irange(pte.offset(), 0x4000),
+ f"UATMapTracer/{ctx}",
+ mode=TraceMode.WSYNC,
+ write=self.uat_write,
+ iova=iova,
+ base=pte.offset(),
+ level=level,
+ ctx=ctx)
+
+ def uat_page_mapped(self, iova, pte, ctx=0):
+ if iova >= 0xf8000000000 and ctx != 0:
+ return
+ if not pte.valid():
+ self.log(f"UAT unmap {ctx}:{iova:#x} ({pte})")
+ try:
+ paddr = self.va_to_pa[(ctx, iova)]
+ except KeyError:
+ return
+ self.hv.del_tracer(irange(paddr, 0x4000), f"GPUVM/{ctx}")
+ del self.va_to_pa[(ctx, iova)]
+ return
+
+ paddr = pte.offset()
+ self.log(f"UAT map {ctx}:{iova:#x} -> {paddr:#x} ({pte})")
+ if paddr < 0x800000000:
+ return # MMIO, ignore
+
+ if not self.trace_userva and ctx != 0 and iova < 0x80_00000000:
+ return
+ if not self.trace_kernva and ctx == 0:
+ return
+
+ self.va_to_pa[(ctx, iova)] = paddr
+ self.hv.add_tracer(irange(paddr, 0x4000),
+ f"GPUVM/{ctx}",
+ mode=TraceMode.ASYNC,
+ read=self.event_gpuvm,
+ write=self.event_gpuvm,
+ iova=iova,
+ paddr=paddr,
+ ctx=ctx)
+
+ def event_gpuvm(self, evt, iova, paddr, name=None, base=None, ctx=None):
+ off = evt.addr - paddr
+ iova += off
+
+ if evt.flags.WRITE:
+ self.writelog[iova] = (self.vmcnt, evt)
+ else:
+ self.readlog[iova] = (self.vmcnt, evt)
+ t = "W" if evt.flags.WRITE else "R"
+ m = "+" if evt.flags.MULTI else " "
+ sh = ["NS", "??", "OS", "IS"]
+ a = f"{evt.flags.ATTR:02x}:{sh[evt.flags.SH]}"
+ dinfo = ""
+ if name is not None and base is not None:
+ dinfo = f"[{name} + {iova - base:#x}]"
+ logline = (f"[cpu{evt.flags.CPU}] GPUVM[{ctx}/{self.vmcnt:5}]: <{a}>{t}.{1<<evt.flags.WIDTH:<2}{m} " +
+ f"{iova:#x}({evt.addr:#x}){dinfo} = {evt.data:#x}")
+ self.log(logline, show_cpu=False)
+ self.vmcnt += 1
+ #self.mon.poll()
+
+ def meta_gpuvm(self, iova, size):
+ meta = ""
+ iova &= 0xfffffffffff
+ for off in range(size):
+ offva = iova + off
+ if offva in self.readlog:
+ ctr, evt = self.readlog[offva]
+ m = "+" if evt.flags.MULTI else " "
+ meta += f"[R.{1<<evt.flags.WIDTH:<2}{m} @{ctr} +{off:#x}]"
+
+ if offva in self.writelog:
+ ctr, evt = self.writelog[offva]
+ m = "+" if evt.flags.MULTI else " "
+ meta += f"[W.{1<<evt.flags.WIDTH:<2}{m} @{ctr} +{off:#x}]"
+
+ return meta or None
+
+ def get_stream(self, context, off):
+ stream = self.uat.iostream(context, off)
+ stream.meta_fn = self.meta_gpuvm
+ return stream
+
+ def start(self):
+ super().start()
+
+ self.clear_ttbr_tracers()
+ self.clear_uatmap_tracers()
+ self.add_ttbr_tracers()
+ self.add_uatmap_tracers()
+ self.clear_gpuvm_tracers()
+ self.add_mon_regions()
+
+ #self.handoff_tracer.start()
+ self.init_channels()
+ if self.state.active:
+ self.resume()
+ else:
+ self.pause()
+
+ def stop(self):
+ self.pause()
+ self.handoff_tracer.stop()
+ self.clear_ttbr_tracers()
+ self.clear_uatmap_tracers()
+ self.clear_gpuvm_tracers()
+ super().stop()
+
+ def mon_addva(self, ctx, va, size, name=""):
+ self.mon.add(va, size, name, readfn= lambda a, s: self.uat.ioread(ctx, a, s))
+
+ def handle_ringmsg(self, msg):
+ if isinstance(msg, FlagMsg):
+ self.log(f"== Event flag notification ==")
+ self.handle_event(msg)
+ return
+ elif isinstance(msg, RunCmdQueueMsg):
+ self.log(f"== Work notification (type {msg.queue_type})==")
+ queue = self.get_cmdqueue(msg.cmdqueue_addr, msg.new_queue)
+ work_items = list(queue.get_workitems(msg))
+ if self.encoder_id_filter is not None:
+ for wi in work_items:
+ if wi.cmd.magic == 0:
+ # TA
+ if not self.encoder_id_filter(wi.cmd.struct_3.encoder_id):
+ return True
+ if wi.cmd.magic == 1:
+ # 3D
+ if not self.encoder_id_filter(wi.cmd.struct_6.encoder_id):
+ return True
+ for wi in work_items:
+ self.log(str(wi))
+ if msg.queue_type == 2:
+ pass
+ #return self.handle_compute(wi)
+ elif msg.queue_type == 1:
+ self.handle_3d(wi)
+ self.queue_3d = queue
+ elif msg.queue_type == 0:
+ self.handle_ta(wi)
+ self.queue_ta = queue
+ return True
+
+ def handle_event(self, msg):
+ if self.last_ta and self.redump:
+ self.log("Redumping TA...")
+ stream = self.get_stream(0, self.last_ta._addr)
+ last_ta = CmdBufWork.parse_stream(stream)
+ self.log(str(last_ta))
+ self.handle_ta(last_ta)
+ self.queue_ta.update_info()
+ self.log(f"Queue info: {self.queue_ta.info}")
+ self.last_ta = None
+ if self.last_3d and self.redump:
+ self.log("Redumping 3D...")
+ stream = self.get_stream(0, self.last_3d._addr)
+ last_3d = CmdBufWork.parse_stream(stream)
+ self.log(str(last_3d))
+ self.handle_3d(last_3d)
+ self.queue_3d.update_info()
+ self.log(f"Queue info: {self.queue_3d.info}")
+ self.last_3d = None
+
+ def dump_buffer_manager(self, buffer_mgr, kread, read):
+ return
+
+ self.log(f" buffer_mgr @ {buffer_mgr._addr:#x}: {buffer_mgr!s}")
+ self.log(f" page_list @ {buffer_mgr.page_list_addr:#x}:")
+ chexdump(read(buffer_mgr.page_list_addr,
+ buffer_mgr.page_list_size), print_fn=self.log)
+ self.log(f" block_list @ {buffer_mgr.block_list_addr:#x}:")
+ chexdump(read(buffer_mgr.block_list_addr,
+ 0x8000), print_fn=self.log)
+ #self.log(f" unkptr_d8 @ {buffer_mgr.unkptr_d8:#x}:")
+ #chexdump(read(buffer_mgr.unkptr_d8, 0x4000), print_fn=self.log)
+
+
+ def handle_ta(self, wi):
+ self.log(f"Got TA WI{wi.cmd.magic:d}")
+ self.last_ta = wi
+
+ def kread(off, size):
+ return self.uat.ioread(0, off, size)
+
+ if wi.cmd.magic == 6:
+ wi6 = wi.cmd
+ #self.log(f" unkptr_14 @ {wi6.unkptr_14:#x}:")
+ #chexdump(kread(wi6.unkptr_14, 0x100), print_fn=self.log)
+
+ elif wi.cmd.magic == 0:
+ wi0 = wi.cmd
+ context = wi0.context_id
+
+ def read(off, size):
+ return self.uat.ioread(context, off & 0x7fff_ffff_ffff_ffff, size)
+
+ #chexdump(kread(wi0.addr, 0x600), print_fn=self.log)
+ self.log(f" context_id = {context:#x}")
+ self.dump_buffer_manager(wi0.buffer_mgr, kread, read)
+ #self.log(f" unk_emptybuf @ {wi0.unk_emptybuf_addr:#x}:")
+ #chexdump(kread(wi0.unk_emptybuf_addr, 0x1000), print_fn=self.log)
+
+ #self.log(f" unkptr_48 @ {wi0.unkptr_48:#x}:")
+ #chexdump(read(wi0.unkptr_48, 0x1000), print_fn=self.log)
+ #self.log(f" unkptr_58 @ {wi0.unkptr_58:#x}:")
+ #chexdump(read(wi0.unkptr_58, 0x4000), print_fn=self.log)
+ #self.log(f" unkptr_60 @ {wi0.unkptr_60:#x}:")
+ #chexdump(read(wi0.unkptr_60, 0x4000), print_fn=self.log)
+
+ #self.log(f" unkptr_45c @ {wi0.unkptr_45c:#x}:")
+ #chexdump(read(wi0.unkptr_45c, 0x1800), print_fn=self.log)
+
+ for i in wi0.microsequence.value:
+ i = i.cmd
+ if isinstance(i, StartTACmd):
+ self.log(f" # StartTACmd")
+ self.log(f" buf_thing @ {i.buf_thing_addr:#x}: {i.buf_thing!s}")
+ self.log(f" unkptr_18 @ {i.buf_thing.unkptr_18:#x}:")
+ chexdump(read(i.buf_thing.unkptr_18, 0x100), print_fn=self.log)
+ self.log(f" unkptr_24 @ {i.unkptr_24:#x}:")
+ chexdump(read(i.unkptr_24, 0x100), print_fn=self.log)
+ self.log(f" unk_5c @ {i.unkptr_5c:#x}:")
+ chexdump(read(i.unkptr_5c, 0x100), print_fn=self.log)
+
+ elif isinstance(i, FinalizeTACmd):
+ self.log(f" # FinalizeTACmd")
+
+
+ #self.uat.dump(context, self.log)
+
+ def handle_3d(self, wi):
+ self.log(f"Got 3D WI{wi.cmdid:d}")
+ if wi.cmdid != 1:
+ return
+
+ self.last_3d = wi
+
+ def kread(off, size):
+ return self.uat.ioread(0, off, size)
+
+ if wi.cmd.magic == 4:
+ wi4 = wi.cmd
+ #self.log(f" completion_buf @ {wi4.completion_buf_addr:#x}: {wi4.completion_buf!s} ")
+ #chexdump(kread(wi4.completion_buf_addr, 0x1000), print_fn=self.log)
+ elif wi.cmd.magic == 1:
+ wi1 = wi.cmd
+ context = wi1.context_id
+ def read(off, size):
+ return self.uat.ioread(context, off, size)
+
+ self.log(f" context_id = {context:#x}")
+ cmd3d = wi1.microsequence.value[0].cmd
+
+ self.log(f" 3D:")
+ self.log(f" struct1 @ {cmd3d.struct1_addr:#x}: {cmd3d.struct1!s}")
+ self.log(f" struct2 @ {cmd3d.struct2_addr:#x}: {cmd3d.struct2!s}")
+ #self.log(f" tvb_start_addr @ {cmd3d.struct2.tvb_start_addr:#x}:")
+ #if cmd3d.struct2.tvb_start_addr:
+ #chexdump(read(cmd3d.struct2.tvb_start_addr, 0x1000), print_fn=self.log)
+ #self.log(f" tvb_tilemap_addr @ {cmd3d.struct2.tvb_tilemap_addr:#x}:")
+ #if cmd3d.struct2.tvb_tilemap_addr:
+ #chexdump(read(cmd3d.struct2.tvb_tilemap_addr, 0x1000), print_fn=self.log)
+ #self.log(f" aux_fb_ptr @ {cmd3d.struct2.aux_fb_ptr:#x}:")
+ #chexdump(read(cmd3d.struct2.aux_fb_ptr, 0x100), print_fn=self.log)
+ #self.log(f" pipeline_base @ {cmd3d.struct2.pipeline_base:#x}:")
+ #chexdump(read(cmd3d.struct2.pipeline_base, 0x100), print_fn=self.log)
+
+ self.log(f" buf_thing @ {cmd3d.buf_thing_addr:#x}: {cmd3d.buf_thing!s}")
+ #self.log(f" unkptr_18 @ {cmd3d.buf_thing.unkptr_18:#x}:")
+ #chexdump(read(cmd3d.buf_thing.unkptr_18, 0x1000), print_fn=self.log)
+
+ #self.log(f" unk_24 @ {cmd3d.unkptr_24:#x}: {cmd3d.unk_24!s}")
+ self.log(f" struct6 @ {cmd3d.struct6_addr:#x}: {cmd3d.struct6!s}")
+ #self.log(f" unknown_buffer @ {cmd3d.struct6.unknown_buffer:#x}:")
+ #chexdump(read(cmd3d.struct6.unknown_buffer, 0x1000), print_fn=self.log)
+ self.log(f" struct7 @ {cmd3d.struct7_addr:#x}: {cmd3d.struct7!s}")
+ self.log(f" unk_buf_ptr @ {cmd3d.unk_buf_ptr:#x}:")
+ chexdump(kread(cmd3d.unk_buf_ptr, 0x11c), print_fn=self.log)
+ self.log(f" unk_buf2_ptr @ {cmd3d.unk_buf2_ptr:#x}:")
+ chexdump(kread(cmd3d.unk_buf2_ptr, 0x18), print_fn=self.log)
+
+ for i in wi1.microsequence.value:
+ i = i.cmd
+ if not isinstance(i, Finalize3DCmd):
+ continue
+ self.log(f" Finalize:")
+ cmdfin = i
+ #self.log(f" completion:")
+ #chexdump(kread(cmdfin.completion, 0x4), print_fn=self.log)
+ self.log(f" unkptr_1c @ {cmdfin.unkptr_1c:#x}:")
+ chexdump(kread(cmdfin.unkptr_1c, 0x1000), print_fn=self.log)
+ #self.log(f" unkptr_24 @ {cmdfin.unkptr_24:#x}:")
+ #chexdump(kread(cmdfin.unkptr_24, 0x100), print_fn=self.log)
+ self.log(f" unkptr_34 @ {cmdfin.unkptr_34:#x}:")
+ chexdump(kread(cmdfin.unkptr_34, 0x1000), print_fn=self.log)
+ self.log(f" unkptr_3c @ {cmdfin.unkptr_3c:#x}:")
+ chexdump(kread(cmdfin.unkptr_3c, 0x1c0), print_fn=self.log)
+ self.log(f" unkptr_44 @ {cmdfin.unkptr_44:#x}:")
+ chexdump(kread(cmdfin.unkptr_44, 0x40), print_fn=self.log)
+ self.log(f" unkptr_64 @ {cmdfin.unkptr_64:#x}:")
+ chexdump(kread(cmdfin.unkptr_64, 0x118), print_fn=self.log)
+
+ self.log(f" buf_thing @ {wi1.buf_thing_addr:#x}: {wi1.buf_thing!s}")
+ self.log(f" unkptr_18 @ {wi1.buf_thing.unkptr_18:#x}:")
+ chexdump(read(wi1.buf_thing.unkptr_18, 0x1000), print_fn=self.log)
+ self.dump_buffer_manager(wi1.buffer_mgr, kread, read)
+ #self.log(f" unk_emptybuf @ {wi1.unk_emptybuf_addr:#x}:")
+ #chexdump(kread(wi1.unk_emptybuf_addr, 0x1000), print_fn=self.log)
+ #self.log(f" tvb_addr @ {wi1.tvb_addr:#x}:")
+ #chexdump(read(wi1.tvb_addr, 0x1000), print_fn=self.log)
+
+ def handle_compute(self, msg):
+ self.log("Got Compute Work Item")
+
+ try:
+ wi = msg.workItems[0].cmd
+ except:
+ return
+
+ def kread(off, size):
+ return self.uat.ioread(0, off, size)
+
+ context = wi.context_id
+
+ def read(off, size):
+ return self.uat.ioread(context, off, size)
+
+ self.log(f" context_id = {context:#x}")
+ self.log(f" unk_c @ {wi.unkptr_c:#x}: {wi.unk_c!s} ")
+ #chexdump(kread(wi.unkptr_c, 0x100), print_fn=self.log)
+ self.log(f" unkptr_0:")
+ chexdump(kread(wi.unk_c.unkptr_0, 0x1000), print_fn=self.log)
+
+ self.log("StartComputeCmd:")
+ try:
+ ccmd = wi.microsequence.value[0].cmd
+ except:
+ self.log(" MISSING!")
+ return
+ self.log(f" unkptr_4: {ccmd.unkptr_4:#x}")
+ chexdump(kread(ccmd.unkptr_4, 0x54), print_fn=self.log)
+ self.log(f" unkptr_14: {ccmd.unkptr_14:#x}")
+ chexdump(kread(ccmd.unkptr_14, 0x1000), print_fn=self.log)
+ #self.log(f" unkptr_3c: {ccmd.unkptr_3c:#x}")
+ #chexdump(kread(ccmd.unkptr_3c, 0xb4), print_fn=self.log)
+
+ ci = ccmd.computeinfo
+ self.log(f" Compute Info: {ci!s}")
+ self.log(f" args:")
+ u0data = read(ci.args, 0x8000)
+ chexdump(u0data, print_fn=self.log)
+ args = struct.unpack("<8Q", u0data[0x7fa0:0x7fe0])
+ for i, p in enumerate(args):
+ if p:
+ self.log(f" args[{i}] @ {p:#x}")
+ chexdump(read(p, 0x1000), print_fn=self.log)
+ p0, p1 = struct.unpack("<QQ", u0data[0x7fe0:0x7ff0])
+ self.log(f" p0 @ {p0:#x}")
+ chexdump(read(p0, 0x100), print_fn=self.log)
+ self.log(f" p1 @ {p1:#x}")
+ chexdump(read(p1, 0x100), print_fn=self.log)
+ self.log(f" cmdlist:")
+ chexdump(read(ci.cmdlist, 0x8000), print_fn=self.log)
+ self.log(f" unkptr_10:")
+ chexdump(read(ci.unkptr_10, 8), print_fn=self.log)
+ self.log(f" unkptr_18:")
+ chexdump(read(ci.unkptr_18, 8), print_fn=self.log)
+ self.log(f" unkptr_20:")
+ chexdump(read(ci.unkptr_20, 8), print_fn=self.log)
+ self.log(f" unkptr_28:")
+ chexdump(read(ci.unkptr_28, 8), print_fn=self.log)
+ self.log(f" pipeline:")
+ chexdump(read(ci.pipeline_base, 0x1000), print_fn=self.log)
+ self.log(f" unkptr_48:")
+ chexdump(read(ci.unkptr_48, 0x8000), print_fn=self.log)
+
+ ci2 = ccmd.computeinfo2
+ self.log(f" Compute Info 2: {ci2!s}")
+ self.log(f" unknown_buffer:")
+ chexdump(read(ci2.unknown_buffer, 0x8000), print_fn=self.log)
+
+ def ignore(self, addr=None):
+ if addr is None:
+ addr = self.last_msg.cmdqueue_addr
+ self.ignorelist += [addr & 0xfff_ffffffff]
+
+ def kick(self, val):
+ if not self.state.active:
+ return
+
+ self.log(f"kick~! {val:#x}")
+ self.mon.poll()
+
+ if val == 0x10: # Kick Firmware
+ self.log("KickFirmware, polling")
+ self.uat.invalidate_cache()
+ for chan in self.channels:
+ chan.poll()
+ return
+
+ if val == 0x11: # Device Control
+ channel = 12
+ self.uat.invalidate_cache()
+
+ elif val < 0x10:
+ type = val & 3
+ assert type != 3
+ priority = (val >> 2) & 3
+ channel = type + priority * 3
+ self.uat.invalidate_cache()
+
+ else:
+ raise(Exception("Unknown kick type"))
+
+ self.channels[channel].poll()
+
+ ## if val not in [0x0, 0x1, 0x10, 0x11]:
+ #if self.last_msg and isinstance(self.last_msg, (RunCmdQueue, DeviceControl_17)):
+ #self.hv.run_shell()
+
+ #self.last_msg = None
+
+ # check the gfx -> cpu channels
+ for chan in self.channels[13:]:
+ chan.poll()
+
+ def fwkick(self, val):
+ if not self.state.active:
+ return
+
+ self.log(f"FW Kick~! {val:#x}")
+ self.mon.poll()
+
+ if val == 0x00: # Kick FW control
+ channel = len(self.channels) - 1
+ else:
+ raise(Exception("Unknown kick type"))
+
+ self.channels[channel].poll()
+
+ # check the gfx -> cpu channels
+ for chan in self.channels[13:]:
+ chan.poll()
+
+ def pong(self):
+ if not self.state.active:
+ return
+
+ self.log("pong~!");
+ self.mon.poll()
+
+ # check the gfx -> cpu channels
+ for chan in self.channels[13:]:
+ chan.poll()
+
+ def trace_uatrange(self, ctx, start, size, name=None, off=0):
+ start &= 0xfff_ffffffff
+ ranges = self.uat.iotranslate(ctx, start, size)
+ iova = start
+ for range in ranges:
+ pstart, psize = range
+ if pstart:
+ self.log(f"trace {name} {start:#x}/{iova:#x} [{pstart:#x}:{psize:#x}] +{off:#x}")
+ self.hv.add_tracer(irange(pstart, psize), f"GPUVM",
+ mode=TraceMode.ASYNC,
+ read=self.event_gpuvm,
+ write=self.event_gpuvm,
+ iova=iova,
+ paddr=pstart,
+ name=name,
+ base=start - off)
+ iova += psize
+
+ def untrace_uatrange(self, ctx, start, size):
+ ranges = self.uat.iotranslate(ctx, start, size)
+ for range in ranges:
+ start, size = range
+ if start:
+ self.hv.del_tracer(irange(start, size), f"GPUVM")
+
+ def dump_va(self, ctx):
+ data = b''
+ dataStart = 0
+
+ def dump_page(start, end, i, pte, level, sparse):
+ if i == 0 or sparse:
+ if len(data):
+ chexdump32(data, dataStart)
+ data = b''
+ dataStart = 0
+ if MemoryAttr(pte.AttrIndex) != MemoryAttr.Device and pte.OS:
+ if dataStart == 0:
+ dataStart = start
+ data += self.uat.ioread(0, start, 0x4000)
+
+ self.uat.foreach_page(0, dump_page)
+ if len(data):
+ chexdump32(data, dataStart)
+
+ def init_state(self):
+ super().init_state()
+ self.state.active = True
+ self.state.initdata = None
+ self.state.channel_info = []
+ self.state.channels = {}
+ self.state.queues = {}
+
+ def init_channels(self):
+ if self.channels:
+ return
+ #self.channels = []
+ for i, chan_info in enumerate(self.state.channel_info):
+ print(channelNames[i], chan_info)
+ if channelNames[i] == "Stats": # ignore stats
+ continue
+ elif channelNames[i] == "KTrace": # ignore KTrace
+ continue
+ elif channelNames[i] == "FWCtl":
+ channel_chan = FWCtlChannelTracer(self, chan_info, i)
+ else:
+ channel_chan = ChannelTracer(self, chan_info, i)
+ self.channels.append(channel_chan)
+
+ def pause(self):
+ self.clear_gpuvm_tracers()
+ if self.state.initdata is None:
+ return
+ self.clear_uatmap_tracers()
+ self.clear_ttbr_tracers()
+ self.log("Pausing tracing")
+ self.state.active = False
+ for chan in self.channels:
+ chan.set_active(False)
+ for queue in self.cmdqueues.values():
+ queue.set_active(False)
+ for info_addr in self.state.queues:
+ self.state.queues[info_addr].rptr = None
+ self.untrace_uatrange(0, self.state.initdata.regionA_addr, 0x4000)
+ self.untrace_uatrange(0, self.state.initdata.regionB_addr, 0x6bc0)
+ self.untrace_uatrange(0, self.state.initdata.regionC_addr, 0x11d40)
+
+ def resume(self):
+ self.add_gpuvm_tracers()
+ self.add_uatmap_tracers()
+ self.add_ttbr_tracers()
+ if self.state.initdata is None:
+ return
+ self.log("Resuming tracing")
+ self.state.active = True
+ for chan in self.channels:
+ if chan.name == "Stats":
+ continue
+ chan.set_active(True)
+ for queue in self.cmdqueues.values():
+ queue.set_active(True)
+ self.trace_uatrange(0, self.state.initdata.regionA_addr, 0x4000, name="regionA")
+ self.trace_uatrange(0, self.state.initdata.regionB_addr, 0x6bc0, name="regionB")
+ self.trace_uatrange(0, self.state.initdata.regionC_addr, 0x11d40, name="regionC")
+ self.trace_uatrange(0, self.state.initdata.regionB.buffer_mgr_ctl_addr, 0x4000, name="Buffer manager ctl")
+
+ def add_mon_regions(self):
+ return
+ initdata = self.state.initdata
+ if initdata is not None:
+ self.mon_addva(0, initdata.regionA_addr, 0x4000, "RegionA")
+ self.mon_addva(0, initdata.regionB_addr, 0x6bc0, "RegionB")
+ self.mon_addva(0, initdata.regionC_addr, 0x11d40, "RegionC")
+ #self.mon_addva(0, initdata.regionB.unkptr_170, 0xc0, "unkptr_170")
+ #self.mon_addva(0, initdata.regionB.unkptr_178, 0x1c0, "unkptr_178")
+ #self.mon_addva(0, initdata.regionB.unkptr_180, 0x140, "unkptr_180")
+ self.mon_addva(0, initdata.regionB.unkptr_190, 0x80, "unkptr_190")
+ self.mon_addva(0, initdata.regionB.unkptr_198, 0xc0, "unkptr_198")
+ self.mon_addva(0, initdata.regionB.buffer_mgr_ctl_addr, 0x4000, "Buffer manager ctl")
+ self.mon_addva(0, initdata.unkptr_20.unkptr_0, 0x40, "unkptr_20.unkptr_0")
+ self.mon_addva(0, initdata.unkptr_20.unkptr_8, 0x40, "unkptr_20.unkptr_8")
+
+ def pong_init(self, addr):
+ self.log("UAT at init time:")
+ self.uat.invalidate_cache()
+ self.uat.dump(0, log=self.log)
+ addr |= 0xfffff000_00000000
+ initdata = InitData.parse_stream(self.get_stream(0, addr))
+
+ self.log("Initdata:")
+ self.log(initdata)
+
+ self.add_mon_regions()
+
+ #self.initdata.regionB.mon(lambda addr, size, name: self.mon_addva(0, addr, size, name))
+
+ self.state.initdata_addr = addr
+ self.state.initdata = initdata
+ self.state.channel_info = []
+ self.state.fwlog_ring2 = initdata.regionB.fwlog_ring2
+ channels = initdata.regionB.channels
+ for i in channelNames:
+ if i == "FWCtl":
+ chan_info = initdata.fw_status.fwctl_channel
+ else:
+ chan_info = channels[i]
+ self.state.channel_info.append(chan_info)
+
+ self.init_channels()
+ self.mon.poll()
+
+ self.log("Initial commands::")
+ for chan in self.channels:
+ chan.poll()
+ self.log("Init done")
+
+ self.log("Mon regions")
+ self.mon.show_regions(log=self.log)
+
+
+ if self.pause_after_init:
+ self.log("Pausing tracing")
+ self.pause()
+ self.stop()
+ if self.shell_after_init:
+ self.hv.run_shell()
+ if self.after_init_hook:
+ self.after_init_hook()
+
+ChannelTracer = ChannelTracer._reloadcls()
diff --git a/tools/proxyclient/m1n1/trace/asc.py b/tools/proxyclient/m1n1/trace/asc.py
new file mode 100644
index 0000000..6d63a50
--- /dev/null
+++ b/tools/proxyclient/m1n1/trace/asc.py
@@ -0,0 +1,271 @@
+# SPDX-License-Identifier: MIT
+
+import struct
+from enum import IntEnum
+from ..hv import TraceMode
+from ..utils import *
+from . import ADTDevTracer
+from ..hw.asc import *
+
+class DIR(IntEnum):
+ RX = 0
+ TX = 1
+
+def msg(message, direction=None, regtype=None, name=None):
+ def f(x):
+ x.is_message = True
+ x.direction = direction
+ x.message = message
+ x.regtype = regtype
+ x.name = name
+ return x
+ return f
+
+def msg_log(*args, **kwargs):
+ def x(self, msg):
+ return False
+ return msg(*args, **kwargs)(x)
+
+def msg_ign(*args, **kwargs):
+ def x(self, msg):
+ return True
+ return msg(*args, **kwargs)(x)
+
+class EPState(object):
+ pass
+
+class EP(object):
+ NAME = None
+ BASE_MESSAGE = None
+
+ def __init__(self, tracer, epid):
+ self.tracer = tracer
+ self.epid = epid
+ self.present = False
+ self.started = False
+ self.name = self.NAME or type(self).__name__.lower()
+ self.state = EPState()
+ self.hv = self.tracer.hv
+ self.msgmap = {}
+ for name in dir(self):
+ i = getattr(self, name)
+ if not callable(i) or not getattr(i, "is_message", False):
+ continue
+ self.msgmap[i.direction, i.message] = getattr(self, name), name, i.regtype
+
+ def log(self, msg):
+ self.tracer.log(f"[{self.name}] {msg}")
+
+ def start(self):
+ pass
+
+ def handle_msg(self, direction, r0, r1):
+ msgtype = None
+ if self.BASE_MESSAGE:
+ r0 = self.BASE_MESSAGE(r0.value)
+ msgtype = r0.TYPE
+
+ handler = None
+ name = "<unknown>"
+ regtype = None
+
+ msgids = [
+ (direction, msgtype),
+ (None, msgtype),
+ (direction, None),
+ (None, None),
+ ]
+
+ for msgid in msgids:
+ handler, name, regtype = self.msgmap.get(msgid, (None, None, None))
+ if handler:
+ break
+
+ if regtype is not None:
+ r0 = regtype(r0.value)
+
+ if handler:
+ if handler.name is not None:
+ name = handler.name
+ if handler(r0):
+ return True
+
+ d = ">" if direction == DIR.TX else "<"
+ self.log(f"{d}{msgtype:#x}({name}) {r0.value:016x} ({r0.str_fields()})")
+ return True
+
+class EPContainer(object):
+ pass
+
+class BaseASCTracer(ADTDevTracer):
+ DEFAULT_MODE = TraceMode.SYNC
+
+ REGMAPS = [ASCRegs, None]
+ NAMES = ["asc", None]
+
+ ENDPOINTS = {}
+
+ def w_OUTBOX_CTRL(self, val):
+ self.log(f"OUTBOX_CTRL = {val!s}")
+
+ def w_INBOX_CTRL(self, val):
+ self.log(f"INBOX_CTRL = {val!s}")
+
+ def w_CPU_CONTROL(self, val):
+ self.log(f"CPU_CONTROL = {val!s}")
+
+ def w_INBOX1(self, inbox1):
+ inbox0 = self.asc.cached.INBOX0.reg
+ if self.verbose >= 2:
+ self.log(f"SEND: {inbox0.value:016x}:{inbox1.value:016x} " +
+ f"{inbox0.str_fields()} | {inbox1.str_fields()}")
+ self.handle_msg(DIR.TX, inbox0, inbox1)
+
+ def r_OUTBOX1(self, outbox1):
+ outbox0 = self.asc.cached.OUTBOX0.reg
+ if self.verbose >= 2:
+ self.log(f"RECV: {outbox0.value:016x}:{outbox1.value:016x} " +
+ f"{outbox0.str_fields()} | {outbox1.str_fields()}")
+ self.handle_msg(DIR.RX, outbox0, outbox1)
+
+ def init_state(self):
+ self.state.ep = {}
+
+ def handle_msg(self, direction, r0, r1):
+ if r1.EP in self.epmap:
+ if self.epmap[r1.EP].handle_msg(direction, r0, r1):
+ return
+
+ d = ">" if direction == DIR.TX else "<"
+ self.log(f"{d}ep:{r1.EP:02x} {r0.value:016x} ({r0.str_fields()})")
+
+ def ioread(self, dva, size):
+ if self.dart:
+ return self.dart.ioread(self.stream, dva & 0xFFFFFFFFF, size)
+ else:
+ return self.hv.iface.readmem(dva, size)
+
+ def iowrite(self, dva, data):
+ if self.dart:
+ return self.dart.iowrite(self.stream, dva & 0xFFFFFFFFF, data)
+ else:
+ return self.hv.iface.writemem(dva, data)
+
+ def start(self, dart=None, stream=0):
+ super().start()
+ self.dart = dart
+ self.stream = stream
+ self.msgmap = {}
+ for name in dir(self):
+ i = getattr(self, name)
+ if not callable(i) or not getattr(i, "is_message", False):
+ continue
+ self.msgmap[i.direction, i.endpoint, i.message] = getattr(self, name), name, i.regtype
+
+ self.epmap = {}
+ self.ep = EPContainer()
+ for cls in type(self).mro():
+ eps = getattr(cls, "ENDPOINTS", None)
+ if eps is None:
+ break
+ for k, v in eps.items():
+ if k in self.epmap:
+ continue
+ ep = v(self, k)
+ ep.dart = dart
+ ep.stream = stream
+ self.epmap[k] = ep
+ if k in self.state.ep:
+ ep.state.__dict__.update(self.state.ep[k])
+ self.state.ep[k] = ep.state.__dict__
+ if getattr(self.ep, ep.name, None):
+ ep.name = f"{ep.name}{k:02x}"
+ setattr(self.ep, ep.name, ep)
+ ep.start()
+
+# System endpoints
+
+## Management endpoint
+
+from ..fw.asc.mgmt import ManagementMessage, Mgmt_EPMap, Mgmt_EPMap_Ack, Mgmt_StartEP, Mgmt_SetAPPower, Mgmt_SetIOPPower, Mgmt_IOPPowerAck
+
+class Management(EP):
+ BASE_MESSAGE = ManagementMessage
+
+ HELLO = msg_log(1, DIR.RX)
+ HELLO_ACK = msg_log(2, DIR.TX)
+
+ @msg(5, DIR.TX, Mgmt_StartEP)
+ def StartEP(self, msg):
+ ep = self.tracer.epmap.get(msg.EP, None)
+ if ep:
+ ep.started = True
+ self.log(f" Starting endpoint #{msg.EP:#02x} ({ep.name})")
+ else:
+ self.log(f" Starting endpoint #{msg.EP:#02x}")
+ #return True
+
+ Init = msg_log(6, DIR.TX)
+
+ @msg(8, DIR.RX, Mgmt_EPMap)
+ def EPMap(self, msg):
+ for i in range(32):
+ if msg.BITMAP & (1 << i):
+ epno = 32 * msg.BASE + i
+ ep = self.tracer.epmap.get(epno, None)
+ if ep:
+ ep.present = True
+ self.log(f" Adding endpoint #{epno:#02x} ({ep.name})")
+ else:
+ self.log(f" Adding endpoint #{epno:#02x}")
+
+ EPMap_Ack = msg_log(8, DIR.TX, Mgmt_EPMap_Ack)
+
+ SetIOPPower = msg_log(6, DIR.TX, Mgmt_SetIOPPower)
+ SetIOPPowerAck = msg_log(7, DIR.TX, Mgmt_IOPPowerAck)
+
+ SetAPPower = msg_log(0x0b, DIR.TX, Mgmt_SetAPPower)
+ SetAPPowerAck = msg_log(0x0b, DIR.RX, Mgmt_SetAPPower)
+
+## Syslog endpoint
+
+from ..fw.asc.syslog import SyslogMessage, Syslog_Init, Syslog_GetBuf, Syslog_Log
+
+class Syslog(EP):
+ BASE_MESSAGE = SyslogMessage
+
+ @msg(8, DIR.RX, Syslog_Init)
+ def Init(self, msg):
+ self.state.count = msg.COUNT
+ self.state.entrysize = msg.ENTRYSIZE
+
+ @msg(1, DIR.RX, Syslog_GetBuf)
+ def GetBuf(self, msg):
+ if msg.DVA:
+ self.state.syslog_buf = msg.DVA
+
+ @msg(1, DIR.TX, Syslog_GetBuf)
+ def GetBuf_Ack(self, msg):
+ self.state.syslog_buf = msg.DVA
+
+ @msg(5, DIR.RX, Syslog_Log)
+ def Log(self, msg):
+ buf = self.state.syslog_buf
+ stride = 0x20 + self.state.entrysize
+ log = self.tracer.ioread(buf + msg.INDEX * stride, stride)
+ hdr, unk, context, logmsg = struct.unpack(f"<II24s{self.state.entrysize}s", log)
+ context = context.split(b"\x00")[0].decode("ascii")
+ logmsg = logmsg.split(b"\x00")[0].decode("ascii").rstrip("\n")
+ self.log(f"* [{context}]{logmsg}")
+ return True
+
+ Log_Ack = msg_ign(5, DIR.TX, Syslog_Log)
+
+class ASCTracer(BaseASCTracer):
+ ENDPOINTS = {
+ 0: Management,
+ #1: CrashLog,
+ 2: Syslog,
+ #3: KDebug,
+ #4: IOReporting,
+ }
diff --git a/tools/proxyclient/m1n1/trace/dart.py b/tools/proxyclient/m1n1/trace/dart.py
new file mode 100644
index 0000000..b1324f7
--- /dev/null
+++ b/tools/proxyclient/m1n1/trace/dart.py
@@ -0,0 +1,69 @@
+# SPDX-License-Identifier: MIT
+
+from ..hw.dart import *
+from ..hw.dart8020 import *
+from ..hw.dart8110 import *
+from ..hv import TraceMode
+from ..utils import *
+from . import ADTDevTracer
+
+class DARTTracer(ADTDevTracer):
+
+ DEFAULT_MODE = TraceMode.ASYNC
+
+ NAMES = ["regs"]
+
+ @classmethod
+ def _reloadcls(cls, force=False):
+ global DART8020
+ global DART8020Regs
+ global DART8110
+ global DART8110Regs
+ DART8020 = DART8020._reloadcls(force)
+ DART8020Regs = DART8020Regs._reloadcls(force)
+ DART8110 = DART8110._reloadcls(force)
+ DART8110Regs = DART8110Regs._reloadcls(force)
+ return super()._reloadcls(force)
+
+ def __init__(self, hv, devpath, **kwargs):
+ compat = hv.adt[devpath].compatible[0]
+ if compat in ["dart,t6000", "dart,t8020"]:
+ self.REGMAPS = [DART8020Regs]
+ elif compat in ["dart,t8110"]:
+ self.REGMAPS = [DART8110Regs]
+
+ return super().__init__(hv, devpath, **kwargs)
+
+ def start(self):
+ super().start()
+ # prime cache
+ if self.dev.compatible[0] == "dart,t8110":
+ for i in range(16):
+ self.regs.TCR[i].val
+ self.regs.TTBR[i].val
+ for _ in range(8):
+ self.regs.ENABLE_STREAMS[_].val
+ else:
+ for i in range(16):
+ self.regs.TCR[i].val
+ for j in range(4):
+ self.regs.TTBR[i, j].val
+ self.regs.ENABLED_STREAMS.val
+
+ self.dart = DART(self.hv.iface, self.regs, compat=self.dev.compatible[0])
+
+
+ def w_STREAM_COMMAND(self, stream_command):
+ if stream_command.INVALIDATE:
+ self.log(f"Invalidate Stream: {self.regs.cached.STREAM_SELECT.reg}")
+ self.dart.invalidate_cache()
+
+ def w_TLB_OP(self, tlb_op):
+ if tlb_op.OP == 0:
+ self.log(f"Invalidate all")
+ self.dart.invalidate_cache()
+ elif tlb_op.OP == 1:
+ self.log(f"Invalidate Stream: {tlb_op.STREAM}")
+ self.dart.invalidate_cache()
+ else:
+ self.log(f"Unknown TLB op {tlb_op}")
diff --git a/tools/proxyclient/m1n1/trace/dockchannel.py b/tools/proxyclient/m1n1/trace/dockchannel.py
new file mode 100644
index 0000000..10ada58
--- /dev/null
+++ b/tools/proxyclient/m1n1/trace/dockchannel.py
@@ -0,0 +1,37 @@
+# SPDX-License-Identifier: MIT
+import struct
+
+from ..hw.dockchannel import DockChannelIRQRegs, DockChannelConfigRegs, DockChannelDataRegs
+from ..hv import TraceMode
+from ..utils import *
+from . import ADTDevTracer
+
+class DockChannelTracer(ADTDevTracer):
+ DEFAULT_MODE = TraceMode.SYNC
+
+ REGMAPS = [None, DockChannelIRQRegs, DockChannelConfigRegs, DockChannelDataRegs]
+ NAMES = [None, "irq", "config", "data"]
+
+ def w_TX_8(self, d):
+ self.tx(struct.pack("<I", d.value)[0:1])
+ def w_TX_16(self, d):
+ self.tx(struct.pack("<I", d.value)[0:2])
+ def w_TX_24(self, d):
+ self.tx(struct.pack("<I", d.value)[0:3])
+ def w_TX_32(self, d):
+ self.tx(struct.pack("<I", d.value))
+
+ def r_RX_8(self, d):
+ self.rx(struct.pack("<I", d.value)[1:2])
+ def r_RX_16(self, d):
+ self.rx(struct.pack("<I", d.value)[1:3])
+ def r_RX_24(self, d):
+ self.rx(struct.pack("<I", d.value)[1:4])
+ def r_RX_32(self, d):
+ self.rx(struct.pack("<I", d.value))
+
+ def tx(self, d):
+ pass
+
+ def rx(self, d):
+ pass
diff --git a/tools/proxyclient/m1n1/trace/gpio.py b/tools/proxyclient/m1n1/trace/gpio.py
new file mode 100644
index 0000000..386f886
--- /dev/null
+++ b/tools/proxyclient/m1n1/trace/gpio.py
@@ -0,0 +1,67 @@
+# SPDX-License-Identifier: MIT
+
+from ..hv import TraceMode
+from ..utils import *
+from . import ADTDevTracer
+
+class R_PIN(Register32):
+ DRIVE_STRENGTH1 = 23, 22
+ LOCK = 21
+ GROUP = 18, 16
+ SCHMITT = 15
+ DRIVE_STRENGTH0 = 11, 10
+ INPUT_ENABLE = 9
+ PULL = 8, 7
+ PERIPH = 6, 5
+ MODE = 3, 1
+ DATA = 0
+
+class GPIORegs(RegMap):
+ PIN = irange(0x000, 212, 4), R_PIN
+
+ IRQ_GROUP = (irange(0x800, 7, 0x40), irange(0, (212 + 31) // 32, 4)), Register32
+
+def bits32(val, start):
+ return [start + i for i in range(0, 32) if int(val) & (1 << i)]
+
+class GPIOTracer(ADTDevTracer):
+ DEFAULT_MODE = TraceMode.UNBUF
+
+ REGMAPS = [GPIORegs]
+ NAMES = ["gpio"]
+
+ PIN_NAMES = {}
+
+ def __init__(self, hv, devpath, pin_names={}, verbose=False):
+ super().__init__(hv, devpath, verbose)
+ self.PIN_NAMES = pin_names
+
+ def pn(self, pin):
+ return self.PIN_NAMES.get(pin, f"Pin-{pin}")
+
+ def r_PIN(self, val, index):
+ if index not in self.PIN_NAMES and self.verbose < 2:
+ return
+ self.log(f"{self.pn(index):14} R {val!s} ")
+
+ def w_PIN(self, val, index):
+ if index not in self.PIN_NAMES and self.verbose < 2:
+ return
+ self.log(f"{self.pn(index):14} W {val!s} ")
+
+ def r_IRQ_GROUP(self, val, index):
+ (grp, index) = index
+ if int(val) == 0:
+ return
+ pins = [self.pn(x) for x in bits32(val, index * 32) if self.verbose >= 2 or x in self.PIN_NAMES]
+ if len(pins):
+ self.log(f"IRQ[{grp}] ACT {pins}")
+
+ def w_IRQ_GROUP(self, val, index):
+ (grp, index) = index
+ if int(val) == 0:
+ return
+ pins = [self.pn(x) for x in bits32(val, index * 32) if self.verbose >= 2 or x in self.PIN_NAMES]
+ if len(pins):
+ self.log(f"IRQ[{grp}] ACK {pins}")
+
diff --git a/tools/proxyclient/m1n1/trace/i2c.py b/tools/proxyclient/m1n1/trace/i2c.py
new file mode 100644
index 0000000..9c4bbb6
--- /dev/null
+++ b/tools/proxyclient/m1n1/trace/i2c.py
@@ -0,0 +1,225 @@
+# SPDX-License-Identifier: MIT
+
+from ..hv import TraceMode
+from ..utils import *
+from ..hw import i2c
+from . import ADTDevTracer
+
+class I2CTracer(ADTDevTracer):
+ DEFAULT_MODE = TraceMode.ASYNC
+ REGMAPS = [i2c.I2CRegs]
+ NAMES = ["i2c"]
+
+ def __init__(self, hv, devpath, verbose=False):
+ super().__init__(hv, devpath, verbose=verbose)
+ self.default_dev = I2CDevTracer()
+ self.default_dev.i2c_tracer = self
+
+ def init_state(self):
+ self.state.txn = []
+ self.state.devices = {}
+
+ def w_MTXFIFO(self, mtxfifo):
+ if self.state.txn is None:
+ self.state.txn = []
+
+ d = mtxfifo.DATA
+ if mtxfifo.START:
+ self.state.txn += ["S"]
+ if mtxfifo.READ:
+ self.state.txn += [None] * d
+ else:
+ self.state.txn.append(d)
+
+ if mtxfifo.STOP:
+ self.state.txn.append("P")
+ self.flush_txn()
+
+ def r_MRXFIFO(self, mrxfifo):
+ if mrxfifo.EMPTY:
+ self.log(f"Read while FIFO empty")
+ return
+
+ if not self.state.txn:
+ self.log(f"Stray read: {mrxfifo}")
+ return
+
+ try:
+ pos = self.state.txn.index(None)
+ self.state.txn[pos] = mrxfifo.DATA
+ except ValueError:
+ self.log(f"Stray read: {mrxfifo}")
+
+ self.flush_txn()
+
+ def flush_txn(self):
+ if not self.state.txn:
+ return
+
+ if self.state.txn[-1] != "P":
+ return
+
+ if not any(i is None for i in self.state.txn):
+ self.handle_txn(self.state.txn)
+ self.state.txn = None
+
+ def handle_txn(self, txn):
+ st = False
+ dev = self.default_dev
+ read = False
+ for i in txn:
+ if i == "S":
+ st = True
+ continue
+ if st:
+ addr = i >> 1
+ dev = self.state.devices.get(addr, self.default_dev)
+ read = bool(i & 1)
+ dev.start(addr, read=read)
+ elif i == "P":
+ dev.stop()
+ elif read:
+ dev.read(i)
+ else:
+ dev.write(i)
+ st = False
+
+ def add_device(self, addr, device):
+ device.hv = self.hv
+ device.i2c_tracer = self
+ self.state.devices[addr] = device
+
+class I2CDevTracer(Reloadable):
+ def __init__(self, addr=None, name=None, verbose=True):
+ self.addr = addr
+ self.name = name
+ self.verbose = verbose
+ self.txn = []
+
+ def log(self, msg, *args, **kwargs):
+ if self.name:
+ msg = f"[{self.name}] {msg}"
+ self.i2c_tracer.log(msg, *args, **kwargs)
+
+ def start(self, addr, read):
+ self.txn.append("S")
+ if read:
+ self.txn.append(f"{addr:02x}.r")
+ else:
+ self.txn.append(f"{addr:02x}.w")
+
+ def stop(self):
+ self.txn.append("P")
+ if self.verbose:
+ self.log(f"Txn: {' '.join(self.txn)}")
+ self.txn = []
+
+ def read(self, data):
+ self.txn.append(f"{data:02x}")
+
+ def write(self, data):
+ self.txn.append(f"{data:02x}")
+
+class I2CRegCache:
+ def __init__(self):
+ self.cache = {}
+
+ def update(self, addr, data):
+ self.cache[addr] = data
+
+ def read(self, addr, width):
+ data = self.cache.get(addr, None)
+ if data is None:
+ print(f"I2CRegCache: no cache for {addr:#x}")
+ return data
+
+ def write(self, addr, data, width):
+ raise NotImplementedError("No write on I2CRegCache")
+
+class I2CRegMapTracer(I2CDevTracer):
+ REGMAP = RegMap
+ ADDRESSING = (0, 1)
+
+ def __init__(self, verbose=False, **kwargs):
+ super().__init__(verbose=verbose, **kwargs)
+ self._cache = I2CRegCache()
+ self.regmap = self.REGMAP(self._cache, 0)
+ self.page = 0x0
+ self.reg = None
+ self.regbytes = []
+
+ self.npagebytes, nimmbytes = self.ADDRESSING
+ self.pageshift = 8 * nimmbytes
+ self.paged = self.npagebytes != 0
+
+ def _reloadme(self):
+ self.regmap._reloadme()
+ return super()._reloadme()
+
+ def start(self, addr, read):
+ if not read:
+ self.reg = None
+ self.regbytes = []
+ super().start(addr, read)
+
+ def stop(self):
+ super().stop()
+
+ def handle_addressing(self, data):
+ if self.reg is not None:
+ return False
+
+ self.regbytes.append(data)
+ if len(self.regbytes)*8 >= self.pageshift:
+ immediate = int.from_bytes(bytes(self.regbytes),
+ byteorder="big")
+ self.reg = self.page << self.pageshift | immediate
+ return True
+
+ @property
+ def reg_imm(self):
+ '''Returns the 'immediate' part of current register address'''
+ return self.reg & ~(~0 << self.pageshift)
+
+ def handle_page_register(self, data):
+ if not self.paged:
+ return False
+
+ if self.reg_imm >= self.npagebytes:
+ return False
+
+ shift = 8 * self.reg_imm
+ self.page &= ~(0xff << shift)
+ self.page |= data << shift
+ return True
+
+ def write(self, data):
+ if self.handle_addressing(data):
+ return
+ elif self.handle_page_register(data):
+ pass
+ else:
+ self.regwrite(self.reg, data)
+
+ self.reg += 1
+ super().write(data)
+
+ def read(self, data):
+ if self.reg_imm >= self.npagebytes:
+ self.regread(self.reg, data)
+ self.reg += 1
+ super().read(data)
+
+ def regwrite(self, reg, val):
+ self.regevent(reg, val, False)
+
+ def regread(self, reg, val):
+ self.regevent(reg, val, True)
+
+ def regevent(self, reg, val, read):
+ self._cache.update(reg, val)
+ r, index, rcls = self.regmap.lookup_addr(reg)
+ val = rcls(val) if rcls is not None else f"{val:#x}"
+ regname = self.regmap.get_name(reg) if r else f"{reg:#x}"
+ t = "R" if read else "W"
+ self.log(f"REG: {t.upper()}.8 {regname} = {val!s}")
diff --git a/tools/proxyclient/m1n1/trace/isp.py b/tools/proxyclient/m1n1/trace/isp.py
new file mode 100644
index 0000000..924bce9
--- /dev/null
+++ b/tools/proxyclient/m1n1/trace/isp.py
@@ -0,0 +1,118 @@
+from . import ADTDevTracer
+from .dart import DARTTracer
+from ..hv import TraceMode
+from ..hw.dart import DART, DARTRegs
+from ..hw.isp import *
+
+class ISPTracer(ADTDevTracer):
+
+ DEFAULT_MODE = TraceMode.SYNC
+
+ REGMAPS = [ISPRegs, PSReg, SPMIReg, SPMIReg, SPMIReg]
+ NAMES = ["isp", "ps", "spmi0", "spmi1", "spmi2"]
+
+ ALLOWLISTED_CHANNELS = ["TERMINAL", "IO", "BUF_H2T", "BUF_T2H", "SHAREDMALLOC", "IO_T2H"]
+
+ def __init__(self, hv, dev_path, dart_dev_path, verbose):
+ super().__init__(hv, dev_path, verbose)
+
+ hv.p.pmgr_adt_clocks_enable("/arm-io/dart-isp")
+
+ self.dart_tracer = DARTTracer(hv, "/arm-io/dart-isp")
+ self.dart_tracer.start()
+ self.dart = self.dart_tracer.dart
+
+ self.ignored_ranges = [
+ # -----------------------------------------------------------------
+ # ## System clock counter (24 mhz)
+ (0x23b734004, 4),
+ (0x23b734008, 4),
+ # ## Noisy memory addresses that are always zero
+ (0x23b734868, 4),
+ (0x23b73486c, 4),
+ (0x23b734b38, 4),
+ (0x23b734b3c, 4),
+ (0x23b734b58, 4),
+ (0x23b734b5c, 4),
+ (0x23b734bd8, 4),
+ (0x23b734bdc, 4),
+ (0x23b734c18, 4),
+ (0x23b734c1c, 4),
+ (0x23b778128, 4),
+ (0x23b77812c, 4),
+ (0x23b77c128, 4),
+ (0x23b77c12c, 4),
+ # # Noisy memory addresses that change value
+ (0x23b700248, 4),
+ (0x23b700258, 4),
+ (0x23b7003f8, 4),
+ (0x23b700470, 4),
+ # # ECPU/PCPU state report
+ (0x23b738004, 4), # ecpu state report
+ (0x23b738008, 4), # pcpu state report
+ # -----------------------------------------------------------------
+ ]
+
+ def r_ISP_GPR0(self, val):
+ # I have no idea how many channels may be available in other platforms
+ # but, at least for M1 I know they are seven (7), so using 64 as safe value here
+ if val.value == 0x8042006:
+ self.log(f"ISP_GPR0 = ACK")
+ elif val.value < 64:
+ self.log(f"ISP_IPC_CHANNELS = {val!s}")
+ self.number_of_channels = val.value
+ elif val.value > 0:
+ self.log(f"ISP_IPC_CHANNEL_TABLE_IOVA = {val!s}")
+ self.channel_table = ISPChannelTable(self, self.number_of_channels, val.value)
+ self.log(f"{str(self.channel_table)}")
+
+ def r_ISP_IRQ_INTERRUPT(self, val):
+ pending_irq = int(val.value)
+ self.log(f"======== BEGIN IRQ ========")
+ #self.channel_table.dump()
+ self.channel_table.get_last_read_command(pending_irq)
+ self.log(f"======== END IRQ ========")
+
+ def w_ISP_DOORBELL_RING0(self, val):
+ doorbell_value = int(val.value)
+ self.log(f"======== BEGIN DOORBELL ========")
+ #self.channel_table.dump()
+ self.channel_table.get_last_write_command(doorbell_value)
+ self.log(f"======== END DOORBELL ========")
+
+ def w_ISP_GPR0(self, val):
+ self.log(f"ISP_GPR0 = ({val!s})")
+ if val.value == 0x1812f80:
+ if self.dart:
+ self.init_struct = self.dart.ioread(0, val.value & 0xFFFFFFFF, 0x190)
+
+ def w_ISP_IRQ_INTERRUPT(self, val):
+ self.log(f"IRQ_INTERRUPT = ({val!s}).")
+ if val.value == 0xf:
+ self.log(f"ISP Interrupts enabled")
+
+ def ioread(self, dva, size):
+ if self.dart:
+ return self.dart.ioread(0, dva & 0xFFFFFFFF, size)
+ else:
+ return self.hv.iface.readmem(dva, size)
+
+ def iowrite(self, dva, data):
+ if self.dart:
+ return self.dart.iowrite(0, dva & 0xFFFFFFFF, data)
+ else:
+ return self.hv.iface.writemem(dva, data)
+
+ def start(self):
+ super().start()
+
+ self.msgmap = {}
+ for name in dir(self):
+ arg = getattr(self, name)
+ if not callable(arg) or not getattr(arg, "is_message", False):
+ continue
+ self.msgmap[arg.direction, arg.endpoint, arg.message] = getattr(self, name), name, arg.regtype
+
+ # Disable trace of memory regions
+ for addr, size in self.ignored_ranges:
+ self.trace(addr, size, TraceMode.OFF) \ No newline at end of file
diff --git a/tools/proxyclient/m1n1/trace/pcie.py b/tools/proxyclient/m1n1/trace/pcie.py
new file mode 100644
index 0000000..191da84
--- /dev/null
+++ b/tools/proxyclient/m1n1/trace/pcie.py
@@ -0,0 +1,108 @@
+from . import Tracer, TraceMode
+from ..utils import *
+
+class R_BAR(Register32):
+ BASE = 31, 4
+ PREFETCH = 3
+ ADDR64 = 2
+ BELOW_1M = 1
+ SPACE = 0
+
+class PCICfgSpace(RegMap):
+ VENDOR_ID = 0x00, Register16
+ PRODUCT_ID = 0x02, Register16
+ COMMAND = 0x04, Register16
+ STATUS = 0x06, Register16
+
+ BAR = irange(0x10, 6, 4), R_BAR
+ ROMADDR = 0x30, Register32
+
+class PCIeDevTracer(Tracer):
+ CFGMAP = PCICfgSpace
+ BARMAPS = []
+ NAMES = []
+ PREFIXES = []
+
+ def __init__(self, hv, apcie, bus, dev, fn, verbose=False):
+ super().__init__(hv, verbose=verbose, ident=f"{type(self).__name__}@{apcie}/{bus:02x}:{dev:02x}.{fn:1x}")
+ self.busn = bus
+ self.devn = dev
+ self.fn = fn
+ self.ecam_off = (bus << 20) | (dev << 15) | (fn << 12)
+ self.apcie = hv.adt[apcie]
+ self.bars = [0] * 6
+ self.bar_ranges = [None] * 6
+ self.cfg_space = self.CFGMAP(hv.u, self.apcie.get_reg(0)[0] + self.ecam_off)
+ self.verbose = 3
+
+ def init_state(self):
+ self.state.bars = [R_BAR(0) for i in range(6)]
+ self.state.barsize = [None] * 6
+
+ @classmethod
+ def _reloadcls(cls, force=False):
+ cls.BARMAPS = [i._reloadcls(force) if i else None for i in cls.BARMAPS]
+ return super()._reloadcls(force)
+
+ def r_cfg_BAR(self, val, index):
+ if self.state.bars[index].BASE == 0xfffffff:
+ size = (0x10000000 - val.BASE) << 4
+ self.log(f"BAR{index} size = {size:#x}")
+ self.state.barsize[index] = size
+
+ def w_cfg_BAR(self, val, index):
+ self.state.bars[index] = val
+ self.update_tracers(val, index)
+
+ def update_tracers(self, val = None, index = None):
+ self.hv.clear_tracers(self.ident)
+ ecam = self.apcie.get_reg(0)[0]
+ self.trace_regmap(ecam + self.ecam_off, 0x1000, self.CFGMAP, prefix="cfg",
+ mode=TraceMode.WSYNC)
+ i = 0
+ while i < 6:
+ idx = i
+ if i == index:
+ bar = val
+ else:
+ bar = self.cfg_space.BAR[i].reg
+ addr = bar.BASE << 4
+ if bar.ADDR64 and i != 5:
+ if i + 1 == index:
+ barh = val
+ else:
+ barh = self.cfg_space.BAR[i + 1].reg
+ addr |= barh.value << 32
+ i += 2
+ else:
+ i += 1
+
+ if addr in (0, 0xfffffff0, 0xffffffff00000000, 0xfffffffffffffff0):
+ continue
+
+ size = self.state.barsize[idx]
+
+ if not size:
+ self.log(f"BAR{idx} size is unknown!")
+ continue
+
+ # Add in PCIe DT addr flags to get the correct translation
+ start = self.apcie.translate(addr | (0x02 << 88))
+
+ self.log(f"Tracing BAR{idx} : {addr:#x} -> {start:#x}..{start+size-1:#x}")
+ self.bar_ranges[idx] = irange(start, size)
+ self.trace_bar(idx, start, size)
+
+ def trace_bar(self, idx, start, size):
+ if idx >= len(self.BARMAPS) or (regmap := self.BARMAPS[idx]) is None:
+ return
+ prefix = name = None
+ if idx < len(self.NAMES):
+ name = self.NAMES[i]
+ if idx < len(self.PREFIXES):
+ prefix = self.PREFIXES[i]
+
+ self.trace_regmap(start, size, regmap, name=name, prefix=prefix)
+
+ def start(self):
+ self.update_tracers()
diff --git a/tools/proxyclient/m1n1/trace/spi.py b/tools/proxyclient/m1n1/trace/spi.py
new file mode 100644
index 0000000..dd4f967
--- /dev/null
+++ b/tools/proxyclient/m1n1/trace/spi.py
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: MIT
+
+from ..hw.spi import *
+from ..hv import TraceMode
+from ..utils import *
+from . import ADTDevTracer
+
+class SPITracer(ADTDevTracer):
+ REGMAPS = [SPIRegs]
+ NAMES = ["spi"]
diff --git a/tools/proxyclient/m1n1/utils.py b/tools/proxyclient/m1n1/utils.py
new file mode 100644
index 0000000..d401575
--- /dev/null
+++ b/tools/proxyclient/m1n1/utils.py
@@ -0,0 +1,1144 @@
+# SPDX-License-Identifier: MIT
+from enum import Enum
+import threading, traceback, bisect, copy, heapq, importlib, sys, itertools, time, os, functools, struct, re, signal
+from construct import Adapter, Int64ul, Int32ul, Int16ul, Int8ul, ExprAdapter, GreedyRange, ListContainer, StopFieldError, ExplicitError, StreamError
+
+__all__ = ["FourCC"]
+
+def align_up(v, a=16384):
+ return (v + a - 1) & ~(a - 1)
+
+align = align_up
+
+def align_down(v, a=16384):
+ return v & ~(a - 1)
+
+def align_pot(v):
+ out = 1
+ while out < v:
+ out *= 2
+ return out
+
+def hexdump(s, sep=" "):
+ return sep.join(["%02x"%x for x in s])
+
+def hexdump32(s, sep=" "):
+ vals = struct.unpack("<%dI" % (len(s)//4), s)
+ return sep.join(["%08x"%x for x in vals])
+
+def _ascii(s):
+ s2 = ""
+ for c in s:
+ if c < 0x20 or c > 0x7e:
+ s2 += "."
+ else:
+ s2 += chr(c)
+ return s2
+
+def chexdump(s, st=0, abbreviate=True, stride=16, indent="", print_fn=print):
+ last = None
+ skip = False
+ for i in range(0,len(s),stride):
+ val = s[i:i+stride]
+ if val == last and abbreviate:
+ if not skip:
+ print_fn(indent+"%08x *" % (i + st))
+ skip = True
+ else:
+ print_fn(indent+"%08x %s |%s|" % (
+ i + st,
+ " ".join(hexdump(val[i:i+8], ' ').ljust(23)
+ for i in range(0, stride, 8)),
+ _ascii(val).ljust(stride)))
+ last = val
+ skip = False
+
+def chexdiff32(prev, cur, ascii=True, offset=0, offset2=None):
+ assert len(cur) % 4 == 0
+ count = len(cur) // 4
+ words = struct.unpack("<%dI" % count, cur)
+
+ if prev is None:
+ last = None
+ else:
+ assert len(prev) == len(cur)
+ last = struct.unpack("<%dI" % count, prev)
+
+ row = 8
+ skipping = False
+ out = []
+ for i in range(0, count, row):
+ off_text = f"{offset + i * 4:016x}"
+ if offset2 is not None:
+ off_text += f"/{offset2 + i * 4:08x}"
+ if not last:
+ if i != 0 and words[i:i+row] == words[i-row:i]:
+ if not skipping:
+ out.append(f"{off_text} *\n")
+ skipping = True
+ else:
+ out.append(f"{off_text} ")
+ for new in words[i:i+row]:
+ out.append("%08x " % new)
+ if ascii:
+ out.append("| " + _ascii(cur[4*i:4*(i+row)]))
+ out.append("\n")
+ skipping = False
+ elif last[i:i+row] != words[i:i+row]:
+ out.append(f"{off_text} ")
+ for old, new in zip(last[i:i+row], words[i:i+row]):
+ so = "%08x" % old
+ sn = s = "%08x" % new
+ if old != new:
+ s = "\x1b[32m"
+ ld = False
+ for a,b in zip(so, sn):
+ d = a != b
+ if ld != d:
+ s += "\x1b[31;1;4m" if d else "\x1b[32m"
+ ld = d
+ s += b
+ s += "\x1b[m"
+ out.append(s + " ")
+ if ascii:
+ out.append("| " + _ascii(cur[4*i:4*(i+row)]))
+ out.append("\n")
+ return "".join(out)
+
+def chexundump(dump, base=0):
+ if type(dump) is bytes:
+ dump = dump.decode("ascii")
+ elif type(dump) is str:
+ pass
+ else:
+ dump = dump.read()
+
+ decoded = bytearray()
+ for line in dump.splitlines():
+ if not line:
+ continue
+ try:
+ cropped = line.split("|", 2)[0]
+ mark, data = cropped.split(" ", 1)
+ if data.strip() == "*":
+ continue
+ offset = int(mark, 16)
+ data = data.replace(" ", "")
+ if len(data) % 2 != 0:
+ raise ValueError("odd sized data")
+ if offset > len(decoded):
+ decoded.extend([0] * (offset - len(decoded) - base))
+ decoded.extend([int(data[i:i+2], 16) for i \
+ in range(0, len(data), 2)])
+ except (ValueError, TypeError) as exc:
+ raise ValueError(f"can't decode line: {line:r}") from exc
+
+ return decoded
+
+_extascii_table_low = [
+ "▪", "☺", "☻", "♥", "♦", "♣", "♠", "•",
+ "◘", "○", "◙", "♂", "♀", "♪", "♫", "☼",
+ "►", "◄", "↕", "‼", "¶", "§", "▬", "↨",
+ "↑", "↓", "→", "←", "∟", "↔", "▲", "▼"]
+
+_extascii_table_high = [
+ "⌂",
+ "█", "⡀", "⢀", "⣀", "⠠", "⡠", "⢠", "⣠",
+ "⠄", "⡄", "⢄", "⣄", "⠤", "⡤", "⢤", "⣤",
+ "⠁", "⡁", "⢁", "⣁", "⠡", "⡡", "⢡", "⣡",
+ "⠅", "⡅", "⢅", "⣅", "⠥", "⡥", "⢥", "⣥",
+ "⠃", "⡃", "⢃", "⣃", "⠣", "⡣", "⢣", "⣣",
+ "⠇", "⡇", "⢇", "⣇", "⠧", "⡧", "⢧", "⣧",
+ "⠉", "⡉", "⢉", "⣉", "⠩", "⡩", "⢩", "⣩",
+ "⠍", "⡍", "⢍", "⣍", "⠭", "⡭", "⢭", "⣭",
+ "⠊", "⡊", "⢊", "⣊", "⠪", "⡪", "⢪", "⣪",
+ "⠎", "⡎", "⢎", "⣎", "⠮", "⡮", "⢮", "⣮",
+ "⠑", "⡑", "⢑", "⣑", "⠱", "⡱", "⢱", "⣱",
+ "⠕", "⡕", "⢕", "⣕", "⠵", "⡵", "⢵", "⣵",
+ "⠚", "⡚", "⢚", "⣚", "⠺", "⡺", "⢺", "⣺",
+ "⠞", "⡞", "⢞", "⣞", "⠾", "⡾", "⢾", "⣾",
+ "⠛", "⡛", "⢛", "⣛", "⠻", "⡻", "⢻", "⣻",
+ "⠟", "⡟", "⢟", "⣟", "⠿", "⡿", "⢿", "⣿"]
+
+def _extascii(s):
+ s2 = ""
+ for c in s:
+ if c < 0x20:
+ s2 += _extascii_table_low[c]
+ elif c > 0x7e:
+ s2 += _extascii_table_high[c-0x7f]
+ else:
+ s2 += chr(c)
+ return s2
+
+def ehexdump(s, st=0, abbreviate=True, indent="", print_fn=print):
+ last = None
+ skip = False
+ for i in range(0,len(s),16):
+ val = s[i:i+16]
+ if val == last and abbreviate:
+ if not skip:
+ print_fn(indent+"%08x *" % (i + st))
+ skip = True
+ else:
+ print_fn(indent+"%08x %s %s |%s|" % (
+ i + st,
+ hexdump(val[:8], ' ').ljust(23),
+ hexdump(val[8:], ' ').ljust(23),
+ _extascii(val).ljust(16)))
+ last = val
+ skip = False
+
+def chexdump32(s, st=0, abbreviate=True):
+ last = None
+ skip = False
+ for i in range(0,len(s),32):
+ val = s[i:i+32]
+ if val == last and abbreviate:
+ if not skip:
+ print("%08x *" % (i + st))
+ skip = True
+ else:
+ print("%08x %s" % (
+ i + st,
+ hexdump32(val, ' ')))
+ last = val
+ skip = False
+
+def unhex(s):
+ s = re.sub(r"/\*.*?\*/", "", s)
+ return bytes.fromhex(s.replace(" ", "").replace("\n", ""))
+
+def dumpstacks(signal, frame):
+ id2name = dict([(th.ident, th.name) for th in threading.enumerate()])
+ code = []
+ for threadId, stack in sys._current_frames().items():
+ code.append("\n# Thread: %s(%d)" % (id2name.get(threadId,""), threadId))
+ for filename, lineno, name, line in traceback.extract_stack(stack):
+ code.append('File: "%s", line %d, in %s' % (filename, lineno, name))
+ if line:
+ code.append(" %s" % (line.strip()))
+ print("\n".join(code))
+ sys.exit(1)
+
+def set_sigquit_stackdump_handler():
+ signal.signal(signal.SIGQUIT, dumpstacks)
+
+def parse_indexlist(s):
+ items = set()
+ for i in s.split(","):
+ if "-" in i:
+ a, b = map(int, i.split("-", 1))
+ for i in range(a, b + 1):
+ items.add(i)
+ else:
+ items.add(int(i))
+ return items
+
+FourCC = ExprAdapter(Int32ul,
+ lambda d, ctx: d.to_bytes(4, "big").decode("latin-1"),
+ lambda d, ctx: int.from_bytes(d.encode("latin-1"), "big"))
+
+class SafeGreedyRange(GreedyRange):
+ def __init__(self, subcon, discard=False):
+ super().__init__(subcon)
+ self.discard = discard
+
+ def _parse(self, stream, context, path):
+ discard = self.discard
+ obj = ListContainer()
+ try:
+ for i in itertools.count():
+ context._index = i
+ e = self.subcon._parsereport(stream, context, path)
+ if not discard:
+ obj.append(e)
+ except StreamError:
+ pass
+ return obj
+
+class ReloadableMeta(type):
+ def __new__(cls, name, bases, dct):
+ m = super().__new__(cls, name, bases, dct)
+ m._load_time = time.time()
+ return m
+
+class Reloadable(metaclass=ReloadableMeta):
+ @classmethod
+ def _reloadcls(cls, force=False):
+ mods = []
+ for c in cls.mro():
+ mod = sys.modules[c.__module__]
+ cur_cls = getattr(mod, c.__name__)
+ mods.append((cur_cls, mod))
+ if c.__name__ == "Reloadable":
+ break
+
+ reloaded = set()
+ newest = 0
+ for pcls, mod in mods[::-1]:
+ source = getattr(mod, "__file__", None)
+ if not source:
+ continue
+ newest = max(newest, os.stat(source).st_mtime, pcls._load_time)
+ if (force or reloaded or pcls._load_time < newest) and mod.__name__ not in reloaded:
+ print(f"Reload: {mod.__name__}")
+ mod = importlib.reload(mod)
+ reloaded.add(mod.__name__)
+
+ return getattr(mods[0][1], cls.__name__)
+
+ def _reloadme(self):
+ self.__class__ = self._reloadcls()
+
+class Constant:
+ def __init__(self, value):
+ self.value = value
+
+ def __call__(self, v):
+ assert v == self.value
+ return v
+
+class RegisterMeta(ReloadableMeta):
+ def __new__(cls, name, bases, dct):
+ m = super().__new__(cls, name, bases, dct)
+
+ f = {}
+
+ if bases and bases[0] is not Reloadable:
+ for cls in bases[0].mro():
+ if cls is Reloadable:
+ break
+ f.update({k: None for k,v in cls.__dict__.items()
+ if not k.startswith("_") and isinstance(v, (int, tuple))})
+
+ f.update({k: None for k, v in dct.items()
+ if not k.startswith("_") and isinstance(v, (int, tuple))})
+
+ m._fields_list = list(f.keys())
+ m._fields = set(f.keys())
+
+ return m
+
+class Register(Reloadable, metaclass=RegisterMeta):
+ _Constant = Constant
+ def __init__(self, v=None, **kwargs):
+ if v is not None:
+ self._value = v
+ for k in self._fields_list:
+ getattr(self, k) # validate
+ else:
+ self._value = 0
+ for k in self._fields_list:
+ field = getattr(self.__class__, k)
+ if isinstance(field, tuple) and len(field) >= 3 and isinstance(field[2], self._Constant):
+ setattr(self, k, field[2].value)
+
+ for k,v in kwargs.items():
+ setattr(self, k, v)
+
+ def __getattribute__(self, attr):
+ if attr.startswith("_") or attr not in self._fields:
+ return object.__getattribute__(self, attr)
+
+ field = getattr(self.__class__, attr)
+ value = self._value
+
+ if isinstance(field, int):
+ return (value >> field) & 1
+ elif isinstance(field, tuple):
+ if len(field) == 2:
+ msb, lsb = field
+ ftype = int
+ else:
+ msb, lsb, ftype = field
+ return ftype((value >> lsb) & ((1 << ((msb + 1) - lsb)) - 1))
+ else:
+ raise AttributeError(f"Invalid field definition {attr} = {field!r}")
+
+ def __setattr__(self, attr, fvalue):
+ if attr.startswith("_"):
+ self.__dict__[attr] = fvalue
+ return
+
+ field = getattr(self.__class__, attr)
+
+ value = self._value
+
+ if isinstance(field, int):
+ self._value = (value & ~(1 << field)) | ((fvalue & 1) << field)
+ elif isinstance(field, tuple):
+ if len(field) == 2:
+ msb, lsb = field
+ else:
+ msb, lsb, ftype = field
+ mask = ((1 << ((msb + 1) - lsb)) - 1)
+ self._value = (value & ~(mask << lsb)) | ((fvalue & mask) << lsb)
+ else:
+ raise AttributeError(f"Invalid field definition {attr} = {field!r}")
+
+ def __int__(self):
+ return self._value
+
+ def _field_val(self, field_name, as_repr=False):
+ field = getattr(self.__class__, field_name)
+ val = getattr(self, field_name)
+ if isinstance(val, Enum):
+ if as_repr:
+ return str(val)
+ else:
+ msb, lsb = field[:2]
+ if (msb - lsb + 1) > 3:
+ return f"0x{val.value:x}({val.name})"
+ else:
+ return f"{val.value}({val.name})"
+ elif not isinstance(val, int):
+ return val
+ elif isinstance(field, int):
+ return val
+ elif isinstance(field, tuple):
+ msb, lsb = field[:2]
+ if (msb - lsb + 1) > 3:
+ return f"0x{val:x}"
+
+ return val
+
+ @property
+ def fields(self):
+ return {k: getattr(self, k) for k in self._fields_list}
+
+ def str_fields(self):
+ return ', '.join(f'{k}={self._field_val(k)}' for k in self._fields_list)
+
+ def __str__(self):
+ return f"0x{self._value:x} ({self.str_fields()})"
+
+ def __repr__(self):
+ return f"{type(self).__name__}({', '.join(f'{k}={self._field_val(k, True)}' for k in self._fields_list)})"
+
+ def copy(self):
+ return type(self)(self._value)
+
+ @property
+ def value(self):
+ return self._value
+ @value.setter
+ def value(self, val):
+ self._value = val
+
+class Register8(Register):
+ __WIDTH__ = 8
+
+class Register16(Register):
+ __WIDTH__ = 16
+
+class Register32(Register):
+ __WIDTH__ = 32
+
+class Register64(Register):
+ __WIDTH__ = 64
+
+class RegAdapter(Adapter):
+ def __init__(self, register):
+ if register.__WIDTH__ == 64:
+ subcon = Int64ul
+ elif register.__WIDTH__ == 32:
+ subcon = Int32ul
+ elif register.__WIDTH__ == 16:
+ subcon = Int16ul
+ elif register.__WIDTH__ == 8:
+ subcon = Int8ul
+ else:
+ raise ValueError("Invalid reg width")
+
+ self.reg = register
+ super().__init__(subcon)
+
+ def _decode(self, obj, context, path):
+ return self.reg(obj)
+
+ def _encode(self, obj, context, path):
+ return obj.value
+
+class RangeMap(Reloadable):
+ def __init__(self):
+ self.__start = []
+ self.__end = []
+ self.__value = []
+
+ def clone(self):
+ r = type(self)()
+ r.__start = list(self.__start)
+ r.__end = list(self.__end)
+ r.__value = [copy.copy(i) for i in self.__value]
+ return r
+
+ def __len__(self):
+ return len(self.__start)
+
+ def __nonzero__(self):
+ return bool(self.__start)
+
+ def __contains(self, pos, addr):
+ if pos < 0 or pos >= len(self.__start):
+ return False
+
+ return self.__start[pos] <= addr and addr <= self.__end[pos]
+
+ def __split(self, pos, addr):
+ self.__start.insert(pos + 1, addr)
+ self.__end.insert(pos, addr - 1)
+ self.__value.insert(pos + 1, copy.copy(self.__value[pos]))
+
+ def __zone(self, zone):
+ if isinstance(zone, slice):
+ zone = range(zone.start if zone.start is not None else 0,
+ zone.stop if zone.stop is not None else 1 << 64)
+ elif isinstance(zone, int):
+ zone = range(zone, zone + 1)
+
+ return zone
+
+ def lookup(self, addr, default=None):
+ addr = int(addr)
+
+ pos = bisect.bisect_left(self.__end, addr)
+ if self.__contains(pos, addr):
+ return self.__value[pos]
+ else:
+ return default
+
+ def __iter__(self):
+ return self.ranges()
+
+ def ranges(self):
+ return (range(s, e + 1) for s, e in zip(self.__start, self.__end))
+
+ def items(self):
+ return ((range(s, e + 1), v) for s, e, v in zip(self.__start, self.__end, self.__value))
+
+ def _overlap_range(self, zone, split=False):
+ zone = self.__zone(zone)
+ if not zone:
+ return 0, 0
+
+ start = bisect.bisect_left(self.__end, zone.start)
+
+ if split:
+ # Handle left-side overlap
+ if self.__contains(start, zone.start) and self.__start[start] != zone.start:
+ self.__split(start, zone.start)
+ start += 1
+ assert self.__start[start] == zone.start
+
+ for pos in range(start, len(self.__start)):
+ if self.__start[pos] >= zone.stop:
+ return start, pos
+ if split and (self.__end[pos] + 1) > zone.stop:
+ self.__split(pos, zone.stop)
+ return start, pos + 1
+
+ return start, len(self.__start)
+
+ def populate(self, zone, default=[]):
+ zone = self.__zone(zone)
+ if len(zone) == 0:
+ return
+
+ start, stop = zone.start, zone.stop
+
+ # Starting insertion point, overlap inclusive
+ pos = bisect.bisect_left(self.__end, zone.start)
+
+ # Handle left-side overlap
+ if self.__contains(pos, zone.start) and self.__start[pos] != zone.start:
+ self.__split(pos, zone.start)
+ pos += 1
+ assert self.__start[pos] == zone.start
+
+ # Iterate through overlapping ranges
+ while start < stop:
+ if pos == len(self.__start):
+ # Append to end
+ val = copy.copy(default)
+ self.__start.append(start)
+ self.__end.append(stop - 1)
+ self.__value.append(val)
+ yield range(start, stop), val
+ break
+
+ assert self.__start[pos] >= start
+ if self.__start[pos] > start:
+ # Insert new range
+ boundary = stop
+ if pos < len(self.__start):
+ boundary = min(stop, self.__start[pos])
+ val = copy.copy(default)
+ self.__start.insert(pos, start)
+ self.__end.insert(pos, boundary - 1)
+ self.__value.insert(pos, val)
+ yield range(start, boundary), val
+ start = boundary
+ else:
+ # Handle right-side overlap
+ if self.__end[pos] > stop - 1:
+ self.__split(pos, stop)
+ # Add to existing range
+ yield range(self.__start[pos], self.__end[pos] + 1), self.__value[pos]
+ start = self.__end[pos] + 1
+
+ pos += 1
+ else:
+ assert start == stop
+
+ def overlaps(self, zone, split=False):
+ start, stop = self._overlap_range(zone, split)
+ for pos in range(start, stop):
+ yield range(self.__start[pos], self.__end[pos] + 1), self.__value[pos]
+
+ def replace(self, zone, val):
+ zone = self.__zone(zone)
+ if zone.start == zone.stop:
+ return
+ start, stop = self._overlap_range(zone, True)
+ self.__start = self.__start[:start] + [zone.start] + self.__start[stop:]
+ self.__end = self.__end[:start] + [zone.stop - 1] + self.__end[stop:]
+ self.__value = self.__value[:start] + [val] + self.__value[stop:]
+
+ def clear(self, zone=None):
+ if zone is None:
+ self.__start = []
+ self.__end = []
+ self.__value = []
+ else:
+ zone = self.__zone(zone)
+ if zone.start == zone.stop:
+ return
+ start, stop = self._overlap_range(zone, True)
+ self.__start = self.__start[:start] + self.__start[stop:]
+ self.__end = self.__end[:start] + self.__end[stop:]
+ self.__value = self.__value[:start] + self.__value[stop:]
+
+ def compact(self, equal=lambda a, b: a == b, empty=lambda a: not a):
+ if len(self) == 0:
+ return
+
+ new_s, new_e, new_v = [], [], []
+
+ for pos in range(len(self)):
+ s, e, v = self.__start[pos], self.__end[pos], self.__value[pos]
+ if empty(v):
+ continue
+ if new_v and equal(last, v) and s == new_e[-1] + 1:
+ new_e[-1] = e
+ else:
+ new_s.append(s)
+ new_e.append(e)
+ new_v.append(v)
+ last = v
+
+ self.__start, self.__end, self.__value = new_s, new_e, new_v
+
+ def _assert(self, expect, val=lambda a:a):
+ state = []
+ for i, j, v in zip(self.__start, self.__end, self.__value):
+ state.append((i, j, val(v)))
+ if state != expect:
+ print(f"Expected: {expect}")
+ print(f"Got: {state}")
+
+class AddrLookup(RangeMap):
+ def __str__(self):
+ b = [""]
+ for zone, values in self.items():
+ b.append(f"{zone.start:#11x} - {zone.stop - 1:#11x}")
+ if len(values) == 0:
+ b.append(f" (empty range)")
+ elif len(values) == 1:
+ b.append(f" : {values[0][0]}\n")
+ if len(values) > 1:
+ b.append(f" ({len(values):d} devices)\n")
+ for value, r in sorted(values, key=lambda r: r[1].start):
+ b.append(f" {r.start:#10x} - {r.stop - 1:#8x} : {value}\n")
+
+ return "".join(b)
+
+ def add(self, zone, value):
+ for r, values in self.populate(zone):
+ values.append((value, zone))
+
+ def remove(self, zone, value):
+ for r, values in self.overlaps(zone):
+ try:
+ values.remove((value, zone))
+ except:
+ pass
+
+ def lookup(self, addr, default='unknown'):
+ maps = super().lookup(addr)
+ return maps[0] if maps else (default, range(0, 1 << 64))
+
+ def lookup_all(self, addr):
+ return super().lookup(addr, [])
+
+ def _assert(self, expect, val=lambda a:a):
+ super()._assert(expect, lambda v: [i[0] for i in v])
+
+class ScalarRangeMap(RangeMap):
+ def get(self, addr, default=None):
+ return self.lookup(addr, default)
+
+ def __setitem__(self, zone, value):
+ self.replace(zone, value)
+
+ def __delitem__(self, zone):
+ self.clear(zone)
+
+ def __getitem__(self, addr):
+ value = self.lookup(addr, default=KeyError)
+ if value is KeyError:
+ raise KeyError(f"Address {addr:#x} has no value")
+ return value
+
+class BoolRangeMap(RangeMap):
+ def set(self, zone):
+ self.replace(zone, True)
+
+ def __delitem__(self, zone):
+ self.clear(zone)
+
+ def __getitem__(self, addr):
+ return self.lookup(addr, False)
+
+class DictRangeMap(RangeMap):
+ def __setitem__(self, k, value):
+ if not isinstance(k, tuple):
+ self.replace(k, dict(value))
+ else:
+ zone, key = k
+ for r, values in self.populate(zone, {}):
+ values[key] = value
+
+ def __delitem__(self, k):
+ if not isinstance(k, tuple):
+ self.clear(k)
+ else:
+ zone, key = k
+ for r, values in self.overlaps(zone, True):
+ values.pop(key, None)
+
+ def __getitem__(self, k):
+ if isinstance(k, tuple):
+ addr, k = k
+ values = self.lookup(addr)
+ return values.get(k, None) if values else None
+ else:
+ values = self.lookup(k)
+ return values or {}
+
+class SetRangeMap(RangeMap):
+ def add(self, zone, key):
+ for r, values in self.populate(zone, set()):
+ values.add(key)
+
+ def discard(self, zone, key):
+ for r, values in self.overlaps(zone, split=True):
+ if values:
+ values.discard(key)
+ remove = discard
+
+ def __setitem__(self, k, value):
+ self.replace(k, set(value))
+
+ def __delitem__(self, k):
+ self.clear(k)
+
+ def __getitem__(self, addr):
+ values = super().lookup(addr)
+ return frozenset(values) if values else frozenset()
+
+class NdRange:
+ def __init__(self, rng, min_step=1):
+ if isinstance(rng, range):
+ self.ranges = [rng]
+ else:
+ self.ranges = list(rng)
+ least_step = self.ranges[0].step
+ for i, rng in enumerate(self.ranges):
+ if rng.step == 1:
+ self.ranges[i] = range(rng.start, rng.stop, min_step)
+ least_step = min_step
+ else:
+ assert rng.step >= min_step
+ least_step = min(least_step, rng.step)
+ self.start = sum(rng[0] for rng in self.ranges)
+ self.stop = sum(rng[-1] for rng in self.ranges) + least_step
+ self.rev = {}
+ for i in itertools.product(*map(enumerate, self.ranges)):
+ index = tuple(j[0] for j in i)
+ addr = sum(j[1] for j in i)
+ if len(self.ranges) == 1:
+ index = index[0]
+ self.rev[addr] = index
+
+ def index(self, item):
+ return self.rev[item]
+
+ def __len__(self):
+ return self.stop - self.start
+
+ def __contains__(self, item):
+ return item in self.rev
+
+ def __getitem__(self, item):
+ if not isinstance(item, tuple):
+ assert len(self.ranges) == 1
+ return self.ranges[0][item]
+
+ assert len(self.ranges) == len(item)
+ if all(isinstance(i, int) for i in item):
+ return sum((i[j] for i, j in zip(self.ranges, item)))
+ else:
+ iters = (i[j] for i, j in zip(self.ranges, item))
+ return map(sum, itertools.product(*(([i] if isinstance(i, int) else i) for i in iters)))
+
+class RegMapMeta(ReloadableMeta):
+ def __new__(cls, name, bases, dct):
+ m = super().__new__(cls, name, bases, dct)
+ if getattr(m, "_addrmap", None) is None:
+ m._addrmap = {}
+ m._rngmap = SetRangeMap()
+ m._namemap = {}
+ else:
+ m._addrmap = dict(m._addrmap)
+ m._rngmap = m._rngmap.clone()
+ m._namemap = dict(m._namemap)
+
+ for k, v in dct.items():
+ if k.startswith("_") or not isinstance(v, tuple):
+ continue
+ addr, rtype = v
+
+ if isinstance(addr, int):
+ m._addrmap[addr] = k, rtype
+ else:
+ addr = NdRange(addr, rtype.__WIDTH__ // 8)
+ m._rngmap.add(addr, (addr, k, rtype))
+
+ m._namemap[k] = addr, rtype
+
+ def prop(k):
+ def getter(self):
+ return self._accessor[k]
+ def setter(self, val):
+ self._accessor[k].val = val
+ return property(getter, setter)
+
+ setattr(m, k, prop(k))
+
+ return m
+
+class RegAccessor(Reloadable):
+ def __init__(self, cls, rd, wr, addr):
+ self.cls = cls
+ self.rd = rd
+ self.wr = wr
+ self.addr = addr
+
+ def __int__(self):
+ return self.rd(self.addr)
+
+ @property
+ def val(self):
+ return self.rd(self.addr)
+
+ @val.setter
+ def val(self, value):
+ self.wr(self.addr, int(value))
+
+ @property
+ def reg(self):
+ val = self.val
+ if val is None:
+ return None
+ return self.cls(val)
+
+ @reg.setter
+ def reg(self, value):
+ self.wr(self.addr, int(value))
+
+ def set(self, **kwargs):
+ r = self.reg
+ for k, v in kwargs.items():
+ setattr(r, k, v)
+ self.wr(self.addr, int(r))
+
+ def __str__(self):
+ return str(self.reg)
+
+class RegArrayAccessor(Reloadable):
+ def __init__(self, range, cls, rd, wr, addr):
+ self.range = range
+ self.cls = cls
+ self.rd = rd
+ self.wr = wr
+ self.addr = addr
+
+ def __getitem__(self, item):
+ off = self.range[item]
+ if isinstance(off, int):
+ return RegAccessor(self.cls, self.rd, self.wr, self.addr + off)
+ else:
+ return [RegAccessor(self.cls, self.rd, self.wr, self.addr + i) for i in off]
+
+class BaseRegMap(Reloadable):
+ def __init__(self, backend, base):
+ self._base = base
+ self._backend = backend
+ self._accessor = {}
+
+ for name, (addr, rcls) in self._namemap.items():
+ width = rcls.__WIDTH__
+ rd = functools.partial(backend.read, width=width)
+ wr = functools.partial(backend.write, width=width)
+ if type(addr).__name__ == "NdRange":
+ self._accessor[name] = RegArrayAccessor(addr, rcls, rd, wr, base)
+ else:
+ self._accessor[name] = RegAccessor(rcls, rd, wr, base + addr)
+
+ def _lookup_offset(cls, offset):
+ reg = cls._addrmap.get(offset, None)
+ if reg is not None:
+ name, rcls = reg
+ return name, None, rcls
+ ret = cls._rngmap[offset]
+ if ret:
+ for rng, name, rcls in ret:
+ if offset in rng:
+ return name, rng.index(offset), rcls
+ return None, None, None
+ lookup_offset = classmethod(_lookup_offset)
+
+ def lookup_addr(self, addr):
+ return self.lookup_offset(addr - self._base)
+
+ def get_name(self, addr):
+ name, index, rcls = self.lookup_addr(addr)
+ if index is not None:
+ return f"{name}[{index}]"
+ else:
+ return name
+
+ def _lookup_name(cls, name):
+ return cls._namemap.get(name, None)
+ lookup_name = classmethod(_lookup_name)
+
+ def _scalar_regs(self):
+ for addr, (name, rtype) in self._addrmap.items():
+ yield addr, name, self._accessor[name], rtype
+
+ def _array_reg(self, zone, map):
+ addrs, name, rtype = map
+ def index(addr):
+ idx = addrs.index(addr)
+ if isinstance(idx, tuple):
+ idx = str(idx)[1:-1]
+ return idx
+ reg = ((addr, f"{name}[{index(addr)}]", self._accessor[name][addrs.index(addr)], rtype)
+ for addr in zone if addr in addrs)
+ return reg
+
+ def _array_regs(self):
+ for zone, maps in self._rngmap.items():
+ yield from heapq.merge(*(self._array_reg(zone, map) for map in maps))
+
+ def dump_regs(self):
+ for addr, name, acc, rtype in heapq.merge(sorted(self._scalar_regs()), self._array_regs()):
+ print(f"{self._base:#x}+{addr:06x} {name} = {acc.reg}")
+
+class RegMap(BaseRegMap, metaclass=RegMapMeta):
+ pass
+
+def irange(start, count, step=1):
+ return range(start, start + count * step, step)
+
+# Table generated by:
+#
+# tbl = [0] * 256
+# crc = 1
+# for i in [2**x for x in irange(7, 0, -1)]:
+# if crc & 1:
+# crc = (crc >> 1) ^ 0xA001
+# else:
+# crc = crc >> 1
+# for j in range(0, 255, 2*i):
+# tbl[i + j] = crc ^ tbl[j]
+#
+# for i in range(0, 255, 8):
+# print(f"{tbl[i]:#06x}, {tbl[i+1]:#06x}, {tbl[i+2]:#06x}, {tbl[i+3]:#06x}, {tbl[i+4]:#06x}, {tbl[i+5]:#06x}, {tbl[i+6]:#06x}, {tbl[i+7]:#06x}, ")
+
+_crc16_table = [
+ 0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241,
+ 0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440,
+ 0xcc01, 0x0cc0, 0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40,
+ 0x0a00, 0xcac1, 0xcb81, 0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841,
+ 0xd801, 0x18c0, 0x1980, 0xd941, 0x1b00, 0xdbc1, 0xda81, 0x1a40,
+ 0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, 0x1dc0, 0x1c80, 0xdc41,
+ 0x1400, 0xd4c1, 0xd581, 0x1540, 0xd701, 0x17c0, 0x1680, 0xd641,
+ 0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081, 0x1040,
+ 0xf001, 0x30c0, 0x3180, 0xf141, 0x3300, 0xf3c1, 0xf281, 0x3240,
+ 0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441,
+ 0x3c00, 0xfcc1, 0xfd81, 0x3d40, 0xff01, 0x3fc0, 0x3e80, 0xfe41,
+ 0xfa01, 0x3ac0, 0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840,
+ 0x2800, 0xe8c1, 0xe981, 0x2940, 0xeb01, 0x2bc0, 0x2a80, 0xea41,
+ 0xee01, 0x2ec0, 0x2f80, 0xef41, 0x2d00, 0xedc1, 0xec81, 0x2c40,
+ 0xe401, 0x24c0, 0x2580, 0xe541, 0x2700, 0xe7c1, 0xe681, 0x2640,
+ 0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0, 0x2080, 0xe041,
+ 0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281, 0x6240,
+ 0x6600, 0xa6c1, 0xa781, 0x6740, 0xa501, 0x65c0, 0x6480, 0xa441,
+ 0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41,
+ 0xaa01, 0x6ac0, 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840,
+ 0x7800, 0xb8c1, 0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41,
+ 0xbe01, 0x7ec0, 0x7f80, 0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40,
+ 0xb401, 0x74c0, 0x7580, 0xb541, 0x7700, 0xb7c1, 0xb681, 0x7640,
+ 0x7200, 0xb2c1, 0xb381, 0x7340, 0xb101, 0x71c0, 0x7080, 0xb041,
+ 0x5000, 0x90c1, 0x9181, 0x5140, 0x9301, 0x53c0, 0x5280, 0x9241,
+ 0x9601, 0x56c0, 0x5780, 0x9741, 0x5500, 0x95c1, 0x9481, 0x5440,
+ 0x9c01, 0x5cc0, 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40,
+ 0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901, 0x59c0, 0x5880, 0x9841,
+ 0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40,
+ 0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0, 0x4c80, 0x8c41,
+ 0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641,
+ 0x8201, 0x42c0, 0x4380, 0x8341, 0x4100, 0x81c1, 0x8081, 0x4040
+]
+
+def crc16USB(crc, data):
+ for x in data:
+ crc = (crc >> 8) ^ _crc16_table[(crc ^ x) & 0xff]
+ return crc
+
+__all__.extend(k for k, v in globals().items()
+ if (callable(v) or isinstance(v, type)) and v.__module__ == __name__)
+
+if __name__ == "__main__":
+ # AddrLookup test
+ a = AddrLookup()
+ a.add(range(0, 10), 0)
+ a._assert([
+ (0, 9, [0])
+ ])
+ a.add(range(10, 20), 1)
+ a._assert([
+ (0, 9, [0]), (10, 19, [1])
+ ])
+ a.add(range(20, 25), 2)
+ a._assert([
+ (0, 9, [0]), (10, 19, [1]), (20, 24, [2])
+ ])
+ a.add(range(30, 40), 3)
+ a._assert([
+ (0, 9, [0]), (10, 19, [1]), (20, 24, [2]), (30, 39, [3])
+ ])
+ a.add(range(0, 15), 4)
+ a._assert([
+ (0, 9, [0, 4]), (10, 14, [1, 4]), (15, 19, [1]), (20, 24, [2]), (30, 39, [3])
+ ])
+ a.add(range(0, 15), 5)
+ a._assert([
+ (0, 9, [0, 4, 5]), (10, 14, [1, 4, 5]), (15, 19, [1]), (20, 24, [2]), (30, 39, [3])
+ ])
+ a.add(range(21, 44), 6)
+ a._assert([
+ (0, 9, [0, 4, 5]), (10, 14, [1, 4, 5]), (15, 19, [1]), (20, 20, [2]), (21, 24, [2, 6]),
+ (25, 29, [6]), (30, 39, [3, 6]), (40, 43, [6])
+ ])
+ a.add(range(70, 80), 7)
+ a._assert([
+ (0, 9, [0, 4, 5]), (10, 14, [1, 4, 5]), (15, 19, [1]), (20, 20, [2]), (21, 24, [2, 6]),
+ (25, 29, [6]), (30, 39, [3, 6]), (40, 43, [6]), (70, 79, [7])
+ ])
+ a.add(range(0, 100), 8)
+ a._assert([
+ (0, 9, [0, 4, 5, 8]), (10, 14, [1, 4, 5, 8]), (15, 19, [1, 8]), (20, 20, [2, 8]),
+ (21, 24, [2, 6, 8]), (25, 29, [6, 8]), (30, 39, [3, 6, 8]), (40, 43, [6, 8]),
+ (44, 69, [8]), (70, 79, [7, 8]), (80, 99, [8])
+ ])
+ a.remove(range(21, 44), 6)
+ a._assert([
+ (0, 9, [0, 4, 5, 8]), (10, 14, [1, 4, 5, 8]), (15, 19, [1, 8]), (20, 20, [2, 8]),
+ (21, 24, [2, 8]), (25, 29, [8]), (30, 39, [3, 8]), (40, 43, [8]),
+ (44, 69, [8]), (70, 79, [7, 8]), (80, 99, [8])
+ ])
+ a.compact()
+ a._assert([
+ (0, 9, [0, 4, 5, 8]), (10, 14, [1, 4, 5, 8]), (15, 19, [1, 8]), (20, 24, [2, 8]),
+ (25, 29, [8]), (30, 39, [3, 8]), (40, 69, [8]), (70, 79, [7, 8]),
+ (80, 99, [8])
+ ])
+ a.remove(range(0, 100), 8)
+ a._assert([
+ (0, 9, [0, 4, 5]), (10, 14, [1, 4, 5]), (15, 19, [1]), (20, 24, [2]), (25, 29, []),
+ (30, 39, [3]), (40, 69, []), (70, 79, [7]), (80, 99, [])
+ ])
+ a.compact()
+ a._assert([
+ (0, 9, [0, 4, 5]), (10, 14, [1, 4, 5]), (15, 19, [1]), (20, 24, [2]), (30, 39, [3]),
+ (70, 79, [7])
+ ])
+ a.clear(range(12, 21))
+ a._assert([
+ (0, 9, [0, 4, 5]), (10, 11, [1, 4, 5]), (21, 24, [2]), (30, 39, [3]),
+ (70, 79, [7])
+ ])
+
+ # ScalarRangeMap test
+ a = ScalarRangeMap()
+ a[0:5] = 1
+ a[5:10] = 2
+ a[4:8] = 3
+ del a[2:4]
+ expect = [1, 1, None, None, 3, 3, 3, 3, 2, 2, None]
+ for i,j in enumerate(expect):
+ assert a.get(i) == j
+ if j is not None:
+ assert a[i] == j
+ try:
+ a[10]
+ except KeyError:
+ pass
+ else:
+ assert False
+
+ # DictRangeMap test
+ a = DictRangeMap()
+ a[0:5, 0] = 10
+ a[5:8, 1] = 11
+ a[4:6, 2] = 12
+ del a[2:4]
+ expect = [{0: 10}, {0: 10}, {}, {}, {0: 10, 2: 12}, {1: 11, 2: 12}, {1: 11}, {1: 11}, {}]
+ for i,j in enumerate(expect):
+ assert a[i] == j
+ for k, v in j.items():
+ assert a[i, k] == v
+
+ # SetRangeMap test
+ a = SetRangeMap()
+ a[0:2] = {1,}
+ a[2:7] = {2,}
+ a.add(range(1, 4), 3)
+ a.discard(0, -1)
+ a.discard(3, 2)
+ del a[4]
+ expect = [{1,}, {1,3}, {2,3}, {3,}, set(), {2,}, {2,}, set()]
+ for i,j in enumerate(expect):
+ assert a[i] == j
+
+ # BoolRangeMap test
+ a = BoolRangeMap()
+ a.set(range(0, 2))
+ a.set(range(4, 6))
+ a.set(range(5, 5))
+ a.clear(range(3, 5))
+ expect = [True, True, False, False, False, True, False]
+ for i,j in enumerate(expect):
+ assert a[i] == j
diff --git a/tools/proxyclient/m1n1/xnutools.py b/tools/proxyclient/m1n1/xnutools.py
new file mode 100644
index 0000000..88c1a49
--- /dev/null
+++ b/tools/proxyclient/m1n1/xnutools.py
@@ -0,0 +1,117 @@
+# SPDX-License-Identifier: MIT
+import re
+from construct import *
+
+__all__ = []
+
+DebuggerState = Struct(
+ "panic_options" / Hex(Int64ul),
+ "current_op" / Hex(Int32ul),
+ "proceed_on_sync_failre" / Int32ul,
+ "message" / Hex(Int64ul),
+ "panic_str" / Hex(Int64ul),
+ "panic_args" / Hex(Int64ul),
+ "panic_data_ptr" / Hex(Int64ul),
+ "panic_caller" / Hex(Int64ul),
+ "entry_count" / Hex(Int32ul),
+ "kern_return" / Hex(Int32sl)
+)
+
+# Darwin va_list is just a stack pointer...
+VaList = Struct(
+ "stack" / Hex(Int64ul),
+)
+
+def decode_debugger_state(u, ctx):
+ p = u.proxy
+ iface = u.iface
+
+ def hv_readmem(addr, size):
+ addr = p.hv_translate(addr, False, False)
+ assert addr != 0
+ return iface.readmem(addr, size)
+
+ p_state = p.hv_translate(ctx.regs[25], False, False)
+ assert p_state != 0
+ di = iface.readstruct(p_state, DebuggerState)
+ print(di)
+
+ message = hv_readmem(di.message, 1024).split(b"\x00")[0].decode("ascii")
+ print()
+ print(f"Message: {message}")
+
+ print("===== Panic string =====")
+ decode_panic(u, di.panic_str, di.panic_args)
+ print("========================")
+
+def decode_panic_call(u, ctx):
+ decode_panic(u, ctx.regs[0], ctx.regs[1])
+
+def decode_panic(u, p_string, p_args):
+ p = u.proxy
+ iface = u.iface
+
+ def hv_readmem(addr, size):
+ addr = p.hv_translate(addr, False, False)
+ assert addr != 0
+ return iface.readmem(addr, size)
+
+ string = hv_readmem(p_string, 1024).split(b"\x00")[0].decode("ascii")
+ p_args = p.hv_translate(p_args, False, False)
+
+ args = iface.readstruct(p_args, VaList)
+
+ stack = hv_readmem(args.stack, 504)
+
+ def va_arg(t):
+ nonlocal stack
+ d, stack = stack[:8], stack[8:]
+ return t.parse(d)
+
+ utypes = {
+ "hh": Int8ul,
+ "h": Int16ul,
+ None: Int32ul,
+ "l": Int64ul,
+ "ll": Int64ul,
+ "q": Int64ul,
+ "s": Int64ul,
+ "t": Int64ul,
+ }
+
+ stypes = {
+ "hh": Int8sl,
+ "h": Int16sl,
+ None: Int32sl,
+ "l": Int64sl,
+ "ll": Int64sl,
+ "q": Int64sl,
+ "s": Int64sl,
+ "t": Int64sl,
+ }
+
+ #print(string)
+
+ def format_arg(match):
+ pat, flags, width, mod, conv = match.group(0, 1, 2, 3, 4)
+ if conv == "%":
+ return "%"
+ elif conv == "s":
+ return hv_readmem(va_arg(Int64ul), 1024).split(b"\x00")[0].decode("ascii")
+ elif conv in "di":
+ v = va_arg(stypes[mod])
+ return f"%{flags or ''}{width or ''}{conv or ''}" % v
+ elif conv in "ouxX":
+ v = va_arg(utypes[mod])
+ return f"%{flags or ''}{width or ''}{conv or ''}" % v
+ elif conv in "p":
+ return f"0x{va_arg(Int64ul):x}"
+ else:
+ return f"[{pat!r}:{va_arg(Int64ul):x}]"
+
+ string = re.sub('%([-#0 +]*)([1-9][0-9]*)?(hh|h|l|ll|q|L|j|z|Z|t)?([diouxXeEfFgGaAcsCSpnm%])',
+ format_arg, string)
+ print(string + "\n", end="")
+
+__all__.extend(k for k, v in globals().items()
+ if (callable(v) or isinstance(v, type)) and v.__module__ == __name__)
diff --git a/tools/proxyclient/tools/admac_stream.py b/tools/proxyclient/tools/admac_stream.py
new file mode 100755
index 0000000..2c30599
--- /dev/null
+++ b/tools/proxyclient/tools/admac_stream.py
@@ -0,0 +1,87 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib
+import time
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+import argparse
+from m1n1.hw.dart import DART
+from m1n1.hw.admac import *
+
+argparser = argparse.ArgumentParser()
+argparser.add_argument("-b", "--bufsize", type=int, default=1024*32,
+ help="size of one DMA buffer (covered by one descriptor)")
+argparser.add_argument("-n", "--node", type=str, default="admac-sio",
+ help="name of ADT node")
+argparser.add_argument("-c", "--chan", "--channel", type=int, default=0,
+ help="channel no")
+argparser.add_argument("-w", "--buswidth", type=E_BUSWIDTH, default=E_BUSWIDTH.W_32BIT,
+ help="DMA device-facing bus width")
+argparser.add_argument("-v", "--verbose", action='store_true')
+args = argparser.parse_args()
+
+from m1n1.setup import p, u
+
+# find full ADT path
+path = None
+for node in u.adt["/arm-io"]:
+ if node.name == args.node:
+ path = node._path.removeprefix("/device-tree")
+ print(f"Found {path}", file=sys.stderr)
+ if "clock-gates" in node._properties:
+ print(f"Enabling {path}", file=sys.stderr)
+ p.pmgr_adt_clocks_enable(path)
+ break
+
+if path is None:
+ print(f"No instance named {args.node:r} found!", file=sys.stderr)
+ sys.exit(1)
+
+admac_node = u.adt[path]
+
+iommu_mappers = dict()
+for node in u.adt["/arm-io"].walk_tree():
+ if "compatible" in node._properties and node.compatible == ["iommu-mapper"]:
+ iommu_mappers[getattr(node, "AAPL,phandle")] = node
+
+mapper = iommu_mappers[admac_node.iommu_parent]
+dart_path = mapper._parent_path.removeprefix("/device-tree")[:-1]
+dart_idx = mapper.reg
+
+if "clock-gates" in u.adt[dart_path]._properties:
+ print(f"Enabling {dart_path}", file=sys.stderr)
+ p.pmgr_adt_clocks_enable(path)
+
+dart = DART.from_adt(u, dart_path)
+admac = ADMAC(u, admac_node.get_reg(0)[0], dart,
+ dart_stream=dart_idx, debug=args.verbose)
+
+chan = admac.chans[args.chan]
+chan.disable()
+chan.reset()
+chan.read_reports()
+chan.buswidth = args.buswidth
+chan.framesize = E_FRAME.F_1_WORD
+chan.sram_carveout = (0x0, 0x1000)
+
+if chan.tx:
+ chan.submit(bytearray(args.bufsize))
+else:
+ chan.submit(buflen=args.bufsize)
+chan.enable()
+
+try:
+ if chan.tx:
+ while (buf := sys.stdin.buffer.read(args.bufsize)):
+ while not chan.can_submit():
+ chan.poll()
+ chan.submit(buf)
+ else:
+ while True:
+ while chan.can_submit():
+ chan.submit(buflen=args.bufsize)
+ sys.stdout.buffer.write(chan.poll())
+except KeyboardInterrupt:
+ pass
+
+chan.disable()
diff --git a/tools/proxyclient/tools/chainload.py b/tools/proxyclient/tools/chainload.py
new file mode 100755
index 0000000..691a78a
--- /dev/null
+++ b/tools/proxyclient/tools/chainload.py
@@ -0,0 +1,137 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+import argparse, pathlib, time
+
+parser = argparse.ArgumentParser(description='Mach-O loader for m1n1')
+parser.add_argument('-q', '--quiet', action="store_true", help="Disable framebuffer")
+parser.add_argument('-n', '--no-sepfw', action="store_true", help="Do not preserve SEPFW")
+parser.add_argument('-c', '--call', action="store_true", help="Use call mode")
+parser.add_argument('-r', '--raw', action="store_true", help="Image is raw")
+parser.add_argument('-E', '--entry-point', action="store", type=int, help="Entry point for the raw image", default=0x800)
+parser.add_argument('-x', '--xnu', action="store_true", help="Set up for chainloading XNU")
+parser.add_argument('payload', type=pathlib.Path)
+parser.add_argument('boot_args', default=[], nargs="*")
+args = parser.parse_args()
+
+from m1n1.setup import *
+from m1n1.tgtypes import BootArgs
+from m1n1.macho import MachO
+from m1n1 import asm
+
+new_base = u.base
+
+if args.raw:
+ image = args.payload.read_bytes()
+ image += b"\x00\x00\x00\x00"
+ entry = new_base + args.entry_point
+else:
+ macho = MachO(args.payload.read_bytes())
+ image = macho.prepare_image()
+ image += b"\x00\x00\x00\x00"
+ entry = macho.entry
+ entry -= macho.vmin
+ entry += new_base
+
+if args.quiet:
+ p.iodev_set_usage(IODEV.FB, 0)
+
+if args.no_sepfw:
+ sepfw_start, sepfw_length = 0, 0
+else:
+ sepfw_start, sepfw_length = u.adt["chosen"]["memory-map"].SEPFW
+
+image_size = align(len(image))
+sepfw_off = image_size
+image_size += align(sepfw_length)
+bootargs_off = image_size
+bootargs_size = 0x4000
+image_size += bootargs_size
+
+print(f"Total region size: 0x{image_size:x} bytes")
+image_addr = u.malloc(image_size)
+
+print(f"Loading kernel image (0x{len(image):x} bytes)...")
+u.compressed_writemem(image_addr, image, True)
+p.dc_cvau(image_addr, len(image))
+
+if not args.no_sepfw:
+ print(f"Copying SEPFW (0x{sepfw_length:x} bytes)...")
+ p.memcpy8(image_addr + sepfw_off, sepfw_start, sepfw_length)
+ print(f"Adjusting addresses in ADT...")
+ u.adt["chosen"]["memory-map"].SEPFW = (new_base + sepfw_off, sepfw_length)
+ u.adt["chosen"]["memory-map"].BootArgs = (image_addr + bootargs_off, bootargs_size)
+
+ u.push_adt()
+
+ print("Setting secondary CPU RVBARs...")
+
+ rvbar = entry & ~0xfff
+ for cpu in u.adt["cpus"][1:]:
+ addr, size = cpu.cpu_impl_reg
+ print(f" {cpu.name}: [0x{addr:x}] = 0x{rvbar:x}")
+ p.write64(addr, rvbar)
+
+print("Setting up bootargs...")
+tba = u.ba.copy()
+
+tba.top_of_kernel_data = new_base + image_size
+
+if len(args.boot_args) > 0:
+ boot_args = " ".join(args.boot_args)
+ if "-v" in boot_args.split():
+ tba.video.display = 0
+ else:
+ tba.video.display = 1
+ print(f"Setting boot arguments to {boot_args!r}")
+ tba.cmdline = boot_args
+
+if args.xnu:
+ # Fix virt_base, since we often install m1n1 with it set to 0 which xnu does not like
+ tba.virt_base = 0xfffffe0010000000 + (tba.phys_base & (32 * 1024 * 1024 - 1))
+ tba.devtree = u.ba.devtree - u.ba.virt_base + tba.virt_base
+
+iface.writemem(image_addr + bootargs_off, BootArgs.build(tba))
+
+print(f"Copying stub...")
+
+stub = asm.ARMAsm(f"""
+1:
+ ldp x4, x5, [x1], #16
+ stp x4, x5, [x2]
+ dc cvau, x2
+ ic ivau, x2
+ add x2, x2, #16
+ sub x3, x3, #16
+ cbnz x3, 1b
+
+ ldr x1, ={entry}
+ br x1
+""", image_addr + image_size)
+
+iface.writemem(stub.addr, stub.data)
+p.dc_cvau(stub.addr, stub.len)
+p.ic_ivau(stub.addr, stub.len)
+
+print(f"Entry point: 0x{entry:x}")
+
+if args.xnu and p.display_is_external():
+ if p.display_start_dcp() >= 0:
+ p.display_shutdown(0)
+
+if args.call:
+ print(f"Shutting down MMU...")
+ try:
+ p.mmu_shutdown()
+ except ProxyCommandError:
+ pass
+ print(f"Jumping to stub at 0x{stub.addr:x}")
+ p.call(stub.addr, new_base + bootargs_off, image_addr, new_base, image_size, reboot=True)
+else:
+ print(f"Reloading into stub at 0x{stub.addr:x}")
+ p.reload(stub.addr, new_base + bootargs_off, image_addr, new_base, image_size)
+
+iface.nop()
+print("Proxy is alive again")
diff --git a/tools/proxyclient/tools/codecshell.py b/tools/proxyclient/tools/codecshell.py
new file mode 100755
index 0000000..b875925
--- /dev/null
+++ b/tools/proxyclient/tools/codecshell.py
@@ -0,0 +1,83 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+import argparse
+
+parser = argparse.ArgumentParser(description='Enter a shell for codec-poking')
+parser.add_argument('-n', '--no-reset', action="store_true")
+args = parser.parse_args()
+
+from m1n1.setup import *
+from m1n1.shell import run_shell
+from m1n1.hw.i2c import I2C, I2CRegMapDev
+from m1n1.hw.codecs import *
+
+i2c = {}
+
+class TAS5770(I2CRegMapDev):
+ REGMAP = TAS5770Regs
+ ADDRESSING = (1, 1)
+
+class SN012776(I2CRegMapDev):
+ REGMAP = SN012776Regs
+ ADDRESSING = (1, 1)
+
+class CS42L84(I2CRegMapDev):
+ REGMAP = CS42L84Regs
+ ADDRESSING = (0, 2)
+
+gpios = {}
+for node in u.adt["/arm-io"]:
+ if node.name.endswith("gpio") or node.name.endswith("gpio0"):
+ gpios[node._properties["AAPL,phandle"]] = node
+
+spks = []
+for node in u.adt["/arm-io"]:
+ if not node.name.startswith("i2c"):
+ continue
+
+ n = int(node.name[3:])
+ i2c[n] = bus = I2C(u, f"/arm-io/{node.name}")
+
+ for devnode in node:
+ if "compatible" not in devnode._properties:
+ continue
+
+ dcls = {
+ "audio-control,tas5770": TAS5770,
+ "audio-control,sn012776": SN012776,
+ "audio-control,cs42l84": CS42L84,
+ }.get(devnode.compatible[0], None)
+
+ if not dcls:
+ continue
+
+ dev = dcls.from_adt(bus, f"/arm-io/{node.name}/{devnode.name}")
+ dev.node = devnode
+ i2c[n].devs.append(dev)
+
+ if type(dev) in [TAS5770, SN012776]:
+ spks.append(dev)
+ else:
+ hp = dev
+
+ if "function-reset" in devnode._properties:
+ prop = devnode.function_reset
+ gpio_host = gpios[prop.phandle]
+ addr = gpio_host.get_reg(0)[0] + prop.args[0] * 4
+ if not args.no_reset:
+ print(f"Releasing #RST of {devnode.name}")
+ p.mask32(addr, 1, 0)
+ print(f"Pulling #RST of {devnode.name}")
+ p.mask32(addr, 1, 1)
+
+ if "interrupts" in devnode._properties \
+ and devnode.interrupt_parent in gpios:
+ gpio_host = gpios[devnode.interrupt_parent]
+ addr = gpio_host.get_reg(0)[0] + devnode.interrupts[0] * 4
+ print(f"Monitoring IRQ of {devnode.name}")
+ mon.add(addr, 4)
+
+run_shell(globals(), msg="Have fun!")
diff --git a/tools/proxyclient/tools/dump_pmgr.py b/tools/proxyclient/tools/dump_pmgr.py
new file mode 100755
index 0000000..7159349
--- /dev/null
+++ b/tools/proxyclient/tools/dump_pmgr.py
@@ -0,0 +1,138 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+from m1n1 import adt
+from m1n1.setup import *
+from m1n1.hw.nco import NCO
+
+dt = u.adt
+
+pmgr = dt["/arm-io/pmgr"]
+
+dev_by_id = {dev.id: dev for dev in pmgr.devices}
+pd_by_id = {pd.id: pd for pd in pmgr.power_domains}
+clk_by_id = {clk.id: clk for clk in pmgr.clocks}
+
+print("=== PS Regs ===")
+for i, r in enumerate(pmgr.ps_regs):
+ print(f" #{i:2d} reg: {r.reg} off: {r.offset:05x} mask:{r.mask:08x}")
+
+print()
+print("=== Perf Regs ===")
+for i, r in enumerate(pmgr.perf_regs):
+ print(f" #{i:2d} reg: {r.reg} off: {r.offset:05x} size:{r.size:05x} unk:{r.unk:08x}")
+
+#print()
+#print("=== PWR Gate Regs ===")
+#for i, r in enumerate(pmgr.pwrgate_regs):
+ #print(f" #{i:2d} reg: {r.reg} off: {r.offset:05x} mask:{r.mask:08x} unk:{r.unk:08x}")
+
+clock_users = {}
+dev_users = {}
+
+for dev in dt["/arm-io"]:
+ if hasattr(dev, "clock_ids") and dev.clock_ids:
+ for i, clk in enumerate(dev.clock_ids):
+ clock_users.setdefault(clk, []).append(f"{dev._path}.clk[{i}]")
+ if hasattr(dev, "clock_gates") and dev.clock_gates:
+ for i, pdev in enumerate(dev.clock_gates):
+ dev_users.setdefault(pdev, []).append(f"{dev._path}.clkgate[{i}]")
+ if hasattr(dev, "power_gates") and dev.power_gates:
+ for i, pdev in enumerate(dev.power_gates):
+ dev_users.setdefault(pdev, []).append(f"{dev._path}.pwrgate[{i}]")
+
+print()
+print("=== Devices ===")
+for i, dev in enumerate(pmgr.devices):
+ flags = ", ".join(k for k in dev.flags if k[0] != "_" and dev.flags[k])
+ s = f" #{i:3d} {dev.name:20s} id: {dev.id:3d} psreg: {dev.psreg:2d}:{dev.psidx:2d} "
+ s += f" flags: {flags:24s} unk1_0: {dev.unk1_0} unk1_1: {dev.unk1_1} unk1_2: {dev.unk1_2} "
+ s += f" perf_reg: {dev.perf_block}:{dev.perf_idx:#04x} unk3: {dev.unk3:3d} {dev.unk2_0:2d} {dev.ps_cfg16:2d} {dev.unk2_3:3d}"
+
+ if not dev.flags.no_ps:
+ ps = pmgr.ps_regs[dev.psreg]
+ addr = pmgr.get_reg(ps.reg)[0] + ps.offset + dev.psidx * 8
+ val = p.read32(addr)
+ s += f" @ {addr:#x} = {val:#010x}"
+ else:
+ s += f" @ "
+ if dev.pd:
+ pd = pd_by_id[dev.pd]
+ s += f" pd: {pd.name:20s}"
+ else:
+ s += " "
+ if any(dev.parents):
+ s += " parents: " + ", ".join(dev_by_id[idx].name if idx in dev_by_id else f"#{idx}" for idx in dev.parents if idx)
+ print(s)
+ for i in dev_users.get(dev.id, []):
+ print(f" User: {i}")
+
+print()
+print("=== Clocks ===")
+for i, clk in enumerate(pmgr.clocks):
+ perf = pmgr.perf_regs[clk.perf_block]
+ reg = pmgr.get_reg(perf.reg)[0] + 0x100 + clk.perf_idx * 0x10
+ print(f" #{i:3d} {clk.name:20s} id: {clk.id:3d} reg:{clk.perf_block}:{clk.perf_idx:#4x} ({reg:#x}) {clk.unk:#x}")
+
+print()
+print("=== Power Domains ===")
+for i, pd in enumerate(pmgr.power_domains):
+ perf = pmgr.perf_regs[pd.perf_block]
+ reg = pmgr.get_reg(perf.reg)[0] + 0x100 + pd.perf_idx * 0x10
+ print(f" #{i:3d} {pd.name:20s} id: {pd.id:3d} reg:{pd.perf_block}:{pd.perf_idx:#4x} ({reg:#x})")
+
+print()
+print("=== Events ===")
+for i, ev in enumerate(pmgr.events):
+ perf = pmgr.perf_regs[ev.perf_block]
+ reg = pmgr.get_reg(perf.reg)[0] + 0x100 + ev.perf_idx * 0x10
+ v = f" #{i:3d} {ev.name:20s} unk:{ev.unk1:#3x}/{ev.unk2}/{ev.unk3} id: {ev.id:3d} reg:{ev.perf_block}:{ev.perf_idx:#4x} ({reg:#x})"
+ if ev.perf2_idx:
+ perf2 = pmgr.perf_regs[ev.perf2_block]
+ reg2 = pmgr.get_reg(perf2.reg)[0] + 0x100 + ev.perf2_idx * 0x10
+ v += f" reg2:{ev.perf2_block}:{ev.perf2_idx:#4x} ({reg2:#x})"
+ print(v)
+
+arm_io = dt["/arm-io"]
+
+print()
+print("=== Fixed clocks ===")
+for clk in range(256):
+ users = clock_users.get(clk, [])
+ if users:
+ print(f" #{clk}")
+ for j in users:
+ print(f" User: {j}")
+
+print()
+print("=== Boot clocks ===")
+for i, (freq, reg, nclk) in enumerate(zip(arm_io.clock_frequencies,
+ arm_io.clock_frequencies_regs,
+ arm_io.clock_frequencies_nclk)):
+ v = ""
+ clk_type = reg >> 56
+ reg = reg & 0xFFFFFFFFFFFFFF
+
+ if clk_type == 0x9c:
+ v = f"fixed: {reg}"
+ elif clk_type in (0xa0, 0xa1, 0xa4, 0xa5):
+ v = f"regval: {p.read32(reg):#x}"
+ elif clk_type == 0xa8:
+ regvals = [p.read32(reg+off*4) for off in range(5)]
+ try:
+ # we are using the freq value from ADT as fin of NCO,
+ # that's not exactly correct
+ nco_freq = NCO.calc_rate(freq, regvals)
+ except ValueError as e:
+ nco_freq = 0
+
+ v = f"nco: (calculated rate: {nco_freq:d}) "
+ for val in regvals:
+ v += f"{val:#x} "
+
+
+ print(f"#{i:3}: {freq:10d} {nclk} {clk_type:#x}/{reg:#x}: {v}")
+ for j in clock_users.get(i + 256, []):
+ print(f" User: {j}")
diff --git a/tools/proxyclient/tools/freebsd.py b/tools/proxyclient/tools/freebsd.py
new file mode 100755
index 0000000..14124c6
--- /dev/null
+++ b/tools/proxyclient/tools/freebsd.py
@@ -0,0 +1,136 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib
+import serial
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+import argparse, pathlib
+
+# FreeBSD's setup differs from Linux's in the following primary ways:
+#
+# 1.) We pretend our kernel is an initramfs and pick it up as such in a modified
+# loader build. This is the simplest way to avoid having ad-hoc
+# interpretations of stuff in m1n1 for our quirky development setup.
+#
+# 2.) U-Boot and dtb are required, loader/kernel are not. The latter are
+# assumed to be discoverable by the standard U-Boot process on disk if they
+# are not specified. Otherwise, we'll load them from memory if they are
+# provided.
+#
+parser = argparse.ArgumentParser(description='(FreeBSD) kernel loader for m1n1')
+parser.add_argument('u_boot', type=pathlib.Path, help="load u-boot before linux")
+parser.add_argument('dtb', type=pathlib.Path)
+parser.add_argument('-l', '--loader', type=pathlib.Path)
+parser.add_argument('-k', '--kernel', type=pathlib.Path)
+parser.add_argument('-b', '--bootargs', type=str, metavar='"boot arguments"')
+parser.add_argument('-t', '--tty', type=str)
+args = parser.parse_args()
+
+from m1n1.setup import *
+
+if args.tty is not None:
+ tty_dev = serial.Serial(args.tty)
+ tty_dev.reset_input_buffer()
+ tty_dev.baudrate = 1500000
+else:
+ tty_dev = None
+
+if args.loader is not None:
+ loader = args.loader.read_bytes()
+ loader_size = len(loader)
+else:
+ loader = None
+ loader_size = 0
+
+dtb = args.dtb.read_bytes()
+
+if args.kernel is not None:
+ kernel = args.kernel.read_bytes()
+ kernel_size = len(kernel)
+else:
+ kernel = None
+ kernel_size = 0
+
+if args.bootargs is not None:
+ print('Setting boot args: "{}"'.format(args.bootargs))
+ p.kboot_set_chosen("bootarg", args.bootargs)
+
+dtb_addr = u.malloc(len(dtb))
+print("Loading DTB to 0x%x..." % dtb_addr)
+
+iface.writemem(dtb_addr, dtb)
+
+loader_base = u.memalign(2 * 1024 * 1024, loader_size)
+
+print("loader_base: 0x%x" % loader_base)
+
+assert not (loader_base & 0xffff)
+
+if kernel is not None:
+ kernel_base = u.memalign(65536, kernel_size)
+ print("Loading %d kernel bytes to 0x%x..." % (kernel_size, kernel_base))
+ iface.writemem(kernel_base, kernel, True)
+ p.kboot_set_initrd(kernel_base, kernel_size)
+
+uboot = bytearray(args.u_boot.read_bytes())
+uboot_size = len(uboot)
+uboot_addr = u.memalign(2*1024*1024, len(uboot))
+print("Loading u-boot to 0x%x..." % uboot_addr)
+
+bootenv_start = uboot.find(b"bootcmd=run distro_bootcmd")
+bootenv_len = uboot[bootenv_start:].find(b"\x00\x00")
+bootenv_old = uboot[bootenv_start:bootenv_start+bootenv_len]
+bootenv = str(bootenv_old, "ascii").split("\x00")
+bootenv = list(filter(lambda x: not (x.startswith("baudrate") or (x.startswith("boot_") and not x.startswith("boot_efi_")) or x.startswith("distro_bootcmd")), bootenv))
+
+if loader is not None:
+ # dtb_addr not used here, the prepared fdt's at a different location. If
+ # we use this one, we won't get any of our /chosen additions, for instance.
+ bootcmd = "distro_bootcmd=bootefi 0x%x - 0x%x" % (loader_base, loader_size)
+else:
+ bootcmd = "distro_bootcmd=devnum=0; run usb_boot"
+
+if tty_dev is not None:
+ bootenv.append("baudrate=%d" % tty_dev.baudrate)
+bootenv.append(bootcmd)
+if args.bootargs is not None:
+ bootenv.append("bootargs=" + args.bootargs)
+
+bootenv_new = b"\x00".join(map(lambda x: bytes(x, "ascii"), bootenv))
+bootenv_new = bootenv_new.ljust(len(bootenv_old), b"\x00")
+
+if len(bootenv_new) > len(bootenv_old):
+ raise Exception("New bootenv cannot be larger than original bootenv")
+uboot[bootenv_start:bootenv_start+bootenv_len] = bootenv_new
+
+u.compressed_writemem(uboot_addr, uboot, True)
+p.dc_cvau(uboot_addr, uboot_size)
+p.ic_ivau(uboot_addr, uboot_size)
+
+boot_addr = uboot_addr
+
+p.smp_start_secondaries()
+
+if p.kboot_prepare_dt(dtb_addr):
+ print("DT prepare failed")
+ sys.exit(1)
+
+iface.dev.timeout = 40
+
+if loader is not None:
+ print("Loading %d bytes to 0x%x..0x%x..." % (loader_size, loader_base, loader_base + loader_size))
+ iface.writemem(loader_base, loader, True)
+
+ p.dc_cvau(loader_base, loader_size)
+ p.ic_ivau(loader_base, loader_size)
+
+print("Ready to boot")
+
+daif = u.mrs(DAIF)
+daif = 0xc0
+u.msr(DAIF, daif)
+print("DAIF: %x" % daif)
+
+p.kboot_boot(boot_addr)
+
+iface.ttymode(tty_dev)
diff --git a/tools/proxyclient/tools/linux.py b/tools/proxyclient/tools/linux.py
new file mode 100755
index 0000000..2c1d517
--- /dev/null
+++ b/tools/proxyclient/tools/linux.py
@@ -0,0 +1,153 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib
+import serial
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+import argparse, pathlib
+
+parser = argparse.ArgumentParser(description='(Linux) kernel loader for m1n1')
+parser.add_argument('payload', type=pathlib.Path)
+parser.add_argument('dtb', type=pathlib.Path)
+parser.add_argument('initramfs', nargs='?', type=pathlib.Path)
+parser.add_argument('--compression', choices=['auto', 'none', 'gz', 'xz'], default='auto')
+parser.add_argument('-b', '--bootargs', type=str, metavar='"boot arguments"')
+parser.add_argument('-t', '--tty', type=str)
+parser.add_argument('-u', '--u-boot', type=pathlib.Path, help="load u-boot before linux")
+args = parser.parse_args()
+
+from m1n1.setup import *
+
+if args.compression == 'auto':
+ suffix = args.payload.suffix
+ if suffix == '.gz':
+ args.compression = 'gz'
+ elif suffix == '.xz':
+ args.compression = 'xz'
+ else:
+ raise ValueError('unknown compression for {}'.format(args.payload))
+
+if args.tty is not None:
+ tty_dev = serial.Serial(args.tty)
+ tty_dev.reset_input_buffer()
+ tty_dev.baudrate = 1500000
+else:
+ tty_dev = None
+
+payload = args.payload.read_bytes()
+dtb = args.dtb.read_bytes()
+if args.initramfs is not None:
+ initramfs = args.initramfs.read_bytes()
+ initramfs_size = len(initramfs)
+else:
+ initramfs = None
+ initramfs_size = 0
+
+if args.bootargs is not None:
+ print('Setting boot args: "{}"'.format(args.bootargs))
+ p.kboot_set_chosen("bootargs", args.bootargs)
+
+if args.compression != 'none':
+ compressed_size = len(payload)
+ compressed_addr = u.malloc(compressed_size)
+
+ print("Loading %d bytes to 0x%x..0x%x..." % (compressed_size, compressed_addr, compressed_addr + compressed_size))
+ iface.writemem(compressed_addr, payload, True)
+
+dtb_addr = u.malloc(len(dtb))
+print("Loading DTB to 0x%x..." % dtb_addr)
+
+iface.writemem(dtb_addr, dtb)
+
+kernel_size = 512 * 1024 * 1024
+kernel_base = u.memalign(2 * 1024 * 1024, kernel_size)
+boot_addr = kernel_base
+
+print("Kernel_base: 0x%x" % kernel_base)
+
+assert not (kernel_base & 0xffff)
+
+if initramfs is not None:
+ initramfs_base = u.memalign(65536, initramfs_size)
+ print("Loading %d initramfs bytes to 0x%x..." % (initramfs_size, initramfs_base))
+ iface.writemem(initramfs_base, initramfs, True)
+ p.kboot_set_initrd(initramfs_base, initramfs_size)
+
+
+if args.u_boot:
+ uboot = bytearray(args.u_boot.read_bytes())
+ uboot_size = len(uboot)
+ uboot_addr = u.memalign(2*1024*1024, len(uboot))
+ print("Loading u-boot to 0x%x..." % uboot_addr)
+
+ bootenv_start = uboot.find(b"bootcmd=run distro_bootcmd")
+ bootenv_len = uboot[bootenv_start:].find(b"\x00\x00")
+ bootenv_old = uboot[bootenv_start:bootenv_start+bootenv_len]
+ bootenv = str(bootenv_old, "ascii").split("\x00")
+ bootenv = list(filter(lambda x: not (x.startswith("baudrate") or x.startswith("boot_") or x.startswith("distro_bootcmd")), bootenv))
+
+ if initramfs is not None:
+ bootcmd = "distro_bootcmd=booti 0x%x 0x%x:0x%x $fdtcontroladdr" % (kernel_base, initramfs_base, initramfs_size)
+ else:
+ bootcmd = "distro_bootcmd=booti 0x%x - $fdtcontroladdr" % (kernel_base)
+
+ if tty_dev is not None:
+ bootenv.append("baudrate=%d" % tty_dev.baudrate)
+ bootenv.append(bootcmd)
+ if args.bootargs is not None:
+ bootenv.append("bootargs=" + args.bootargs)
+
+ bootenv_new = b"\x00".join(map(lambda x: bytes(x, "ascii"), bootenv))
+ bootenv_new = bootenv_new.ljust(len(bootenv_old), b"\x00")
+
+ if len(bootenv_new) > len(bootenv_old):
+ raise Exception("New bootenv cannot be larger than original bootenv")
+ uboot[bootenv_start:bootenv_start+bootenv_len] = bootenv_new
+
+ u.compressed_writemem(uboot_addr, uboot, True)
+ p.dc_cvau(uboot_addr, uboot_size)
+ p.ic_ivau(uboot_addr, uboot_size)
+
+ boot_addr = uboot_addr
+
+p.smp_start_secondaries()
+
+if p.kboot_prepare_dt(dtb_addr):
+ print("DT prepare failed")
+ sys.exit(1)
+
+iface.dev.timeout = 40
+
+if args.compression == 'none':
+ kernel_size = len(payload)
+ print("Loading %d bytes to 0x%x..0x%x..." % (kernel_size, kernel_base, kernel_base + kernel_size))
+ iface.writemem(kernel_base, payload, True)
+elif args.compression == 'gz':
+ print("Uncompressing gz ...")
+ kernel_size = p.gzdec(compressed_addr, compressed_size, kernel_base, kernel_size)
+elif args.compression == 'xz':
+ print("Uncompressing xz ...")
+ kernel_size = p.xzdec(compressed_addr, compressed_size, kernel_base, kernel_size)
+else:
+ raise ValueError('unsupported compression {}'.format(args.compression))
+
+print(kernel_size)
+
+if kernel_size < 0:
+ raise Exception("Decompression error!")
+
+print("Decompress OK...")
+
+p.dc_cvau(kernel_base, kernel_size)
+p.ic_ivau(kernel_base, kernel_size)
+
+print("Ready to boot")
+
+daif = u.mrs(DAIF)
+daif = 0xc0
+u.msr(DAIF, daif)
+print("DAIF: %x" % daif)
+
+p.kboot_boot(boot_addr)
+
+iface.ttymode(tty_dev)
diff --git a/tools/proxyclient/tools/picocom-sec.sh b/tools/proxyclient/tools/picocom-sec.sh
new file mode 100755
index 0000000..053df37
--- /dev/null
+++ b/tools/proxyclient/tools/picocom-sec.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+while true; do
+ while [ ! -e /dev/m1n1-sec ]; do sleep 1; done
+ picocom --omap crlf --imap lfcrlf -b 500000 /dev/m1n1-sec
+ sleep 1
+done
diff --git a/tools/proxyclient/tools/pmgr_adt2dt.py b/tools/proxyclient/tools/pmgr_adt2dt.py
new file mode 100755
index 0000000..bb2b3d9
--- /dev/null
+++ b/tools/proxyclient/tools/pmgr_adt2dt.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib
+import serial
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+import argparse, pathlib
+
+from m1n1 import adt
+
+parser = argparse.ArgumentParser(description='Convert ADT PMGR nodes to Device Tree format')
+parser.add_argument('input', type=pathlib.Path)
+args = parser.parse_args()
+
+adt_data = args.input.read_bytes()
+dt = adt.load_adt(adt_data)
+
+pmgr = dt["/arm-io/pmgr"]
+
+dev_by_id = {dev.id: dev for dev in pmgr.devices}
+
+blocks = {}
+maxaddr = {}
+
+for i, dev in enumerate(pmgr.devices):
+ if dev.flags.no_ps:
+ continue
+ ps = pmgr.ps_regs[dev.psreg]
+ block = pmgr.get_reg(ps.reg)
+ blocks.setdefault(block, []).append(dev)
+ offset = ps.offset + dev.psidx * 8
+ maxaddr[block[0]] = max(maxaddr.get(block[0], 0), offset)
+
+pmgr_compat = pmgr.compatible[0].split(",")[1]
+compatible = f'"apple,{pmgr_compat}-pmgr", "apple,pmgr", "syscon", "simple-mfd"'
+ps_compatible = f'"apple,{pmgr_compat}-pmgr-pwrstate", "apple,pmgr-pwrstate"'
+
+for i, ((base, size), devices) in enumerate(sorted(blocks.items())):
+
+ size = min(size, (maxaddr[base] + 0x3fff) & ~0x3fff)
+
+ print(f"pmgr{i}: power-management@{base:x} {{")
+ print(f"\tcompatible = {compatible};")
+ print( "\t#address-cells = <1>;")
+ print( "\t#size-cells = <1>;")
+ print()
+ print(f"\treg = <{base >> 32:#x} {base & 0xffffffff:#x} 0 {size:#x}>;")
+ print( "};")
+ print()
+
+for i, ((base, size), devices) in enumerate(sorted(blocks.items())):
+ print(f"&pmgr{i} {{")
+
+ for dev in sorted(devices, key=lambda d: pmgr.ps_regs[d.psreg].offset + dev.psidx * 8):
+ if dev.flags.no_ps:
+ continue
+
+ ps = pmgr.ps_regs[dev.psreg]
+ offset = ps.offset + dev.psidx * 8
+ addr = pmgr.get_reg(ps.reg)[0] + offset
+ assert base <= addr <= (base + size)
+
+ print()
+ print(f"\tps_{dev.name.lower()}: power-controller@{offset:x} {{")
+ print(f"\t\tcompatible = {ps_compatible};")
+ print(f"\t\treg = <{offset:#x} 4>;")
+ print( "\t\t#power-domain-cells = <0>;")
+ print( "\t\t#reset-cells = <0>;")
+ print(f'\t\tlabel = "{dev.name.lower()}";')
+ if dev.flags.critical:
+ print("\t\tapple,always-on;")
+
+ if any(dev.parents):
+ domains = [f"<&ps_{dev_by_id[idx].name.lower()}>" for idx in dev.parents if idx]
+ print(f"\t\tpower-domains = {', '.join(domains)};")
+
+ print( "\t};")
+ print( "};")
+ print()
diff --git a/tools/proxyclient/tools/reboot.py b/tools/proxyclient/tools/reboot.py
new file mode 100755
index 0000000..770ad1d
--- /dev/null
+++ b/tools/proxyclient/tools/reboot.py
@@ -0,0 +1,11 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+from m1n1.setup import *
+from m1n1.hw.pmu import PMU
+
+PMU(u).reset_panic_counter()
+
+p.reboot()
diff --git a/tools/proxyclient/tools/reset_panic_counter.py b/tools/proxyclient/tools/reset_panic_counter.py
new file mode 100755
index 0000000..c6351f0
--- /dev/null
+++ b/tools/proxyclient/tools/reset_panic_counter.py
@@ -0,0 +1,9 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+from m1n1.setup import *
+from m1n1.hw.pmu import PMU
+
+PMU(u).reset_panic_counter()
diff --git a/tools/proxyclient/tools/run_guest.py b/tools/proxyclient/tools/run_guest.py
new file mode 100755
index 0000000..1346a6f
--- /dev/null
+++ b/tools/proxyclient/tools/run_guest.py
@@ -0,0 +1,115 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib, traceback
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+import argparse, pathlib
+from io import BytesIO
+
+def volumespec(s):
+ return tuple(s.split(":", 2))
+
+parser = argparse.ArgumentParser(description='Run a Mach-O payload under the hypervisor')
+parser.add_argument('-s', '--symbols', type=pathlib.Path)
+parser.add_argument('-m', '--script', type=pathlib.Path, action='append', default=[])
+parser.add_argument('-c', '--command', action="append", default=[])
+parser.add_argument('-S', '--shell', action="store_true")
+parser.add_argument('-e', '--hook-exceptions', action="store_true")
+parser.add_argument('-d', '--debug-xnu', action="store_true")
+parser.add_argument('-l', '--logfile', type=pathlib.Path)
+parser.add_argument('-C', '--cpus', default=None)
+parser.add_argument('-r', '--raw', action="store_true")
+parser.add_argument('-E', '--entry-point', action="store", type=int, help="Entry point for the raw image", default=0x800)
+parser.add_argument('-a', '--append-payload', type=pathlib.Path, action="append", default=[])
+parser.add_argument('-v', '--volume', type=volumespec, action='append',
+ help='Attach a 9P virtio device for file export to the guest. The argument is a host path to the '
+ 'exported tree, joined by colon (\':\') with a tag under which the tree will be advertised '
+ 'on the guest side.')
+parser.add_argument('payload', type=pathlib.Path)
+parser.add_argument('boot_args', default=[], nargs="*")
+args = parser.parse_args()
+
+from m1n1.proxy import *
+from m1n1.proxyutils import *
+from m1n1.utils import *
+from m1n1.shell import run_shell
+from m1n1.hv import HV
+from m1n1.hv.virtio import Virtio9PTransport
+from m1n1.hw.pmu import PMU
+
+iface = UartInterface()
+p = M1N1Proxy(iface, debug=False)
+bootstrap_port(iface, p)
+u = ProxyUtils(p, heap_size = 128 * 1024 * 1024)
+
+hv = HV(iface, p, u)
+
+hv.hook_exceptions = args.hook_exceptions
+
+hv.init()
+
+if args.cpus:
+ avail = [i.name for i in hv.adt["/cpus"]]
+ want = set(f"cpu{i}" for i in args.cpus)
+ for cpu in avail:
+ if cpu in want:
+ continue
+ try:
+ del hv.adt[f"/cpus/{cpu}"]
+ print(f"Disabled {cpu}")
+ except KeyError:
+ continue
+
+if args.debug_xnu:
+ hv.adt["chosen"].debug_enabled = 1
+
+if args.volume:
+ for path, tag in args.volume:
+ hv.attach_virtio(Virtio9PTransport(root=path, tag=tag))
+
+if args.logfile:
+ hv.set_logfile(args.logfile.open("w"))
+
+if len(args.boot_args) > 0:
+ boot_args = " ".join(args.boot_args)
+ hv.set_bootargs(boot_args)
+
+symfile = None
+if args.symbols:
+ symfile = args.symbols.open("rb")
+
+payload = args.payload.open("rb")
+
+if args.append_payload:
+ concat = BytesIO()
+ concat.write(payload.read())
+ for part in args.append_payload:
+ concat.write(part.open("rb").read())
+ concat.seek(0)
+ payload = concat
+
+if args.raw:
+ hv.load_raw(payload.read(), args.entry_point)
+else:
+ hv.load_macho(payload, symfile=symfile)
+
+PMU(u).reset_panic_counter()
+
+for i in args.script:
+ try:
+ hv.run_script(i)
+ except:
+ traceback.print_exc()
+ args.shell = True
+
+for i in args.command:
+ try:
+ hv.run_code(i)
+ except:
+ traceback.print_exc()
+ args.shell = True
+
+if args.shell:
+ run_shell(hv.shell_locals, "Entering hypervisor shell. Type ^D to start the guest.")
+
+hv.start()
diff --git a/tools/proxyclient/tools/run_guest_kernel.sh b/tools/proxyclient/tools/run_guest_kernel.sh
new file mode 100755
index 0000000..849f67a
--- /dev/null
+++ b/tools/proxyclient/tools/run_guest_kernel.sh
@@ -0,0 +1,58 @@
+#!/bin/sh
+set -e
+
+: ${TMPDIR:=$XDG_RUNTIME_DIR}
+: ${TMPDIR:=/tmp}
+
+if [ "$1" == "-k" ]; then
+ kernel="$(realpath "$2")"
+ shift 2
+fi
+
+if [ ! -d "$1" ]; then
+ echo "Usage:"
+ echo " $0 <kernel build root> [kernel commandline] [initramfs]"
+ exit 1
+fi
+
+kernel_base="$(realpath "$1")"
+args="$2"
+initramfs=""
+shift 2
+
+if [ "$1" == "--" ]; then
+ shift
+elif [ -n "$1" ]; then
+ initramfs="$(realpath "$1")"
+ shift
+fi
+
+if [ -z "$kernel" ]; then
+ kernel="$kernel_base"/arch/arm64/boot/Image.gz
+fi
+
+base="$(dirname "$0")"
+
+echo "Creating m1n1+kernel image"
+cp "$base"/../../build/m1n1.bin "$TMPDIR/m1n1-linux.bin"
+if [ -n "$args" ]; then
+ echo "chosen.bootargs=$args" >>"$TMPDIR/m1n1-linux.bin"
+fi
+
+cat "$kernel_base"/arch/arm64/boot/dts/apple/*.dtb >>"$TMPDIR/m1n1-linux.bin"
+if [[ "$kernel" == *.gz ]]; then
+ cat "$kernel" >>"$TMPDIR/m1n1-linux.bin"
+else
+ gzip -c <"$kernel" >>"$TMPDIR/m1n1-linux.bin"
+fi
+
+if [ -n "$initramfs" ]; then
+ cat "$initramfs" >>"$TMPDIR/m1n1-linux.bin"
+fi
+
+echo "Chainloading to updated m1n1..."
+python "$base"/chainload.py -r "$base"/../../build/m1n1.bin
+echo "Starting guest..."
+exec python "$base"/run_guest.py \
+ -c "load_system_map('$kernel_base/System.map')" "$@" \
+ -r "$TMPDIR/m1n1-linux.bin"
diff --git a/tools/proxyclient/tools/second_proxy.py b/tools/proxyclient/tools/second_proxy.py
new file mode 100755
index 0000000..46d3c69
--- /dev/null
+++ b/tools/proxyclient/tools/second_proxy.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib
+import time
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+from m1n1.setup import *
+
+p.usb_iodev_vuart_setup(p.iodev_whoami())
+p.iodev_set_usage(IODEV.USB_VUART, USAGE.UARTPROXY)
diff --git a/tools/proxyclient/tools/shell.py b/tools/proxyclient/tools/shell.py
new file mode 100755
index 0000000..2ee07d8
--- /dev/null
+++ b/tools/proxyclient/tools/shell.py
@@ -0,0 +1,9 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+from m1n1.setup import *
+from m1n1.shell import run_shell
+
+run_shell(globals(), msg="Have fun!")
diff --git a/tools/proxyclient/tools/smccli.py b/tools/proxyclient/tools/smccli.py
new file mode 100755
index 0000000..146168d
--- /dev/null
+++ b/tools/proxyclient/tools/smccli.py
@@ -0,0 +1,19 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT
+import sys, pathlib
+sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
+
+from m1n1.setup import *
+from m1n1.shell import run_shell
+from m1n1.fw.smc import SMCClient
+
+smc_addr = u.adt["arm-io/smc"].get_reg(0)[0]
+smc = SMCClient(u, smc_addr, None)
+smc.verbose = 3
+
+smc.start()
+smc.start_ep(0x20)
+
+run_shell(globals(), msg="Have fun!")
+
+smc.stop()