From 5f1f07da8c1300b94bf5de5a9f26ece1e22808ff Mon Sep 17 00:00:00 2001 From: Eileen Yoon Date: Tue, 9 Jan 2024 12:40:12 +0900 Subject: [PATCH 01/22] m1n1.fw.aop: Fix 13.5 bootargs ABI break + rename Move from __init__.py to base.py so it can be imported. Signed-off-by: Eileen Yoon --- proxyclient/m1n1/fw/aop/__init__.py | 30 -------- proxyclient/m1n1/fw/aop/base.py | 112 ++++++++++++++++++++++++++++ proxyclient/m1n1/fw/aop/bootargs.py | 64 ---------------- 3 files changed, 112 insertions(+), 94 deletions(-) create mode 100644 proxyclient/m1n1/fw/aop/base.py delete mode 100644 proxyclient/m1n1/fw/aop/bootargs.py diff --git a/proxyclient/m1n1/fw/aop/__init__.py b/proxyclient/m1n1/fw/aop/__init__.py index 682823252..548d2d447 100644 --- a/proxyclient/m1n1/fw/aop/__init__.py +++ b/proxyclient/m1n1/fw/aop/__init__.py @@ -1,31 +1 @@ # 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/proxyclient/m1n1/fw/aop/base.py b/proxyclient/m1n1/fw/aop/base.py new file mode 100644 index 000000000..0742073e3 --- /dev/null +++ b/proxyclient/m1n1/fw/aop/base.py @@ -0,0 +1,112 @@ +# SPDX-License-Identifier: MIT +import struct +from construct import * +from copy import deepcopy + +def round_up(x, y): return ((x + (y - 1)) & (-y)) +def round_down(x, y): return (x - (x % y)) + +AOPBootargsItem = Struct( + "key" / PaddedString(4, "utf8"), + "size" / Int32ul, +) + +class AOPBootargs: + def __init__(self, bytes_): + self.blob = bytearray(bytes_) + self.index = self.build_index(self.blob) + + def build_index(self, blob): + off = 0 + fields = [] + while off < len(blob): + item = AOPBootargsItem.parse(blob[off:off+AOPBootargsItem.sizeof()]) + off += AOPBootargsItem.sizeof() + fields.append((item.key, (off, item.size))) + off += item.size + if off > len(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, logger): + for key, val in self.items(): + logger(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) + +class AOPBase: + def __init__(self, u): + self.u = u + self.nub_base = u.adt["/arm-io/aop/iop-aop-nub"].region_base + if u.adt["arm-io"].compatible[0] == "arm-io,t6000": + # argh + self.nub_base -= 0x2_0000_0000 + + @property + def _bootargs_span(self): + """ + [cpu1] MMIO: R.4 0x24ac0022c (aop[2], offset 0x22c) = 0xaffd8 // offset + [cpu1] MMIO: R.4 0x24ac00230 (aop[2], offset 0x230) = 0x2ae // size + [cpu1] MMIO: R.4 0x24ac00234 (aop[2], offset 0x234) = 0x82000 // va? low + [cpu1] MMIO: R.4 0x24ac00238 (aop[2], offset 0x238) = 0x0 // va? high + [cpu1] MMIO: R.4 0x24ac0023c (aop[2], offset 0x23c) = 0x4ac82000 // phys low + [cpu1] MMIO: R.4 0x24ac00240 (aop[2], offset 0x240) = 0x2 // phys high + [cpu1] MMIO: W.4 0x24acaffd8 (aop[2], offset 0xaffd8) = 0x53544b47 // start of bootargs + [cpu1] MMIO: W.4 0x24acaffdc (aop[2], offset 0xaffdc) = 0x8 + [cpu1] MMIO: W.4 0x24acaffe0 (aop[2], offset 0xaffe0) = 0x73eed2a3 + ... + [cpu1] MMIO: W.4 0x24acb0280 (aop[2], offset 0xb0280) = 0x10000 + [cpu1] MMIO: W.4 0x24acb0284 (aop[2], offset 0xb0284) = 0x0 // end of bootargs + """ + offset = self.u.proxy.read32(self.nub_base + 0x22c) # 0x224 in 12.3 + size = self.u.proxy.read32(self.nub_base + 0x230) # 0x228 in 12.3 + return (self.nub_base + offset, size) + + def read_bootargs(self): + addr, size = self._bootargs_span + blob = self.u.proxy.iface.readmem(addr, size) + return AOPBootargs(blob) + + def write_bootargs(self, args): + base, _ = self._bootargs_span + self.u.proxy.iface.writemem(base, args.to_bytes()) + + def update_bootargs(self, keyval, logger=print): + args = self.read_bootargs() + old = deepcopy(args) + args.update(keyval) + self.write_bootargs(args) + old.dump_diff(args, logger) diff --git a/proxyclient/m1n1/fw/aop/bootargs.py b/proxyclient/m1n1/fw/aop/bootargs.py deleted file mode 100644 index 100e4f304..000000000 --- a/proxyclient/m1n1/fw/aop/bootargs.py +++ /dev/null @@ -1,64 +0,0 @@ -# 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) From 8ea434909e355273767985301213e978d65e382d Mon Sep 17 00:00:00 2001 From: Eileen Yoon Date: Tue, 16 Jan 2024 00:20:53 +0900 Subject: [PATCH 02/22] m1n1.fw.aop: Add aop client and ep boilerplate Signed-off-by: Eileen Yoon --- proxyclient/m1n1/fw/aop/aopep.py | 141 ++++++++++++++++++++++++++++++ proxyclient/m1n1/fw/aop/client.py | 24 +++++ 2 files changed, 165 insertions(+) create mode 100644 proxyclient/m1n1/fw/aop/aopep.py create mode 100644 proxyclient/m1n1/fw/aop/client.py diff --git a/proxyclient/m1n1/fw/aop/aopep.py b/proxyclient/m1n1/fw/aop/aopep.py new file mode 100644 index 000000000..94aeb2de1 --- /dev/null +++ b/proxyclient/m1n1/fw/aop/aopep.py @@ -0,0 +1,141 @@ +# SPDX-License-Identifier: MIT +import time +from construct import * +from ..afk.epic import * +from .ipc import * + +# spuapp +class AOPSPUAppService(EPICService): + NAME = "SPUApp" + SHORT = "spuapp" + +class AOPSPUAppI2CService(EPICService): + NAME = "i2c" + SHORT = "i2c" + +class AOPSPUAppEndpoint(EPICEndpoint): + SHORT = "spuapp" + SERVICES = [ + AOPSPUAppService, + AOPSPUAppI2CService, + ] + +# accel +class AOPAccelService(EPICService): + NAME = "accel" + SHORT = "accel" + +class AOPAccelEndpoint(EPICEndpoint): + SHORT = "accel" + SERVICES = [ + AOPAccelService, + ] + +# gyro +class AOPGyroService(EPICService): + NAME = "gyro" + SHORT = "gyro" + +class AOPGyroEndpoint(EPICEndpoint): + SHORT = "gyro" + SERVICES = [ + AOPGyroService, + ] + + def start_queues(self): + pass # don't init gyro ep (we don't have one) + +# als +class AOPALSService(EPICService): + NAME = "als" + SHORT = "als" + + @report_handler(0xc4, ALSLuxReport) + def handle_lux(self, seq, fd, rep): + self.log(rep) + return True + +class AOPALSEndpoint(EPICEndpoint): + SHORT = "als" + SERVICES = [ + AOPALSService, + ] + + def send_notify(self, call, chan="als"): + return super(AOPALSEndpoint, self).send_notify(chan, call) + + def send_roundtrip(self, call, chan="als"): + return super(AOPALSEndpoint, self).send_roundtrip(chan, call) + + def send_cmd(self, call, chan="als"): + return super(AOPALSEndpoint, self).send_cmd(chan, call) + +# wakehint +class AOPWakehintService(EPICService): + NAME = "wakehint" + SHORT = "wakehint" + +class AOPWakehintEndpoint(EPICEndpoint): + SHORT = "wakehint" + SERVICES = [ + AOPWakehintService, + ] + +# unk26 +class AOPUNK26Service(EPICService): + NAME = "unk26" + SHORT = "unk26" + +class AOPUNK26Endpoint(EPICEndpoint): + SHORT = "unk26" + SERVICES = [ + AOPUNK26Service, + ] + +# audio +class AOPAudioService(EPICService): + NAME = "aop-audio" + SHORT = "audio" + +class AOPAudioEndpoint(EPICEndpoint): + SHORT = "audio" + SERVICES = [ + AOPAudioService, + ] + + def send_ipc(self, data, chan="aop-audio", **kwargs): + return super(AOPAudioEndpoint, self).send_ipc(data, **kwargs) + + def send_notify(self, call, chan="aop-audio", **kwargs): + return super(AOPAudioEndpoint, self).send_notify(chan, call, **kwargs) + + def send_roundtrip(self, call, chan="aop-audio", **kwargs): + return super(AOPAudioEndpoint, self).send_roundtrip(chan, call, **kwargs) + + def send_cmd(self, call, chan="aop-audio", **kwargs): + return super(AOPAudioEndpoint, self).send_cmd(chan, call, **kwargs) + + def send_notifycmd(self, type, data, chan="aop-audio", **kwargs): + return super(AOPAudioEndpoint, self).send_notifycmd(chan, type, data, **kwargs) + +class AOPVoiceTriggerService(EPICService): + NAME = "aop-voicetrigger" + SHORT = "voicetrigger" + +class AOPVoiceTriggerEndpoint(EPICEndpoint): + SHORT = "voicetrigger" + SERVICES = [ + AOPVoiceTriggerService, + ] + + def send_notify(self, call, chan="aop-voicetrigger", **kwargs): + return super(AOPVoiceTriggerEndpoint, self).send_notify(chan, call, **kwargs) + + def send_roundtrip(self, call, chan="aop-voicetrigger", **kwargs): + return super(AOPVoiceTriggerEndpoint, self).send_roundtrip(chan, call, **kwargs) + + def send_cmd(self, type, data, chan="aop-voicetrigger", **kwargs): + return super(AOPVoiceTriggerEndpoint, self).send_cmd(chan, type, data, **kwargs) + + def send_notifycmd(self, type, data, chan="aop-voicetrigger", **kwargs): + return super(AOPVoiceTriggerEndpoint, self).send_notifycmd(chan, type, data, **kwargs) diff --git a/proxyclient/m1n1/fw/aop/client.py b/proxyclient/m1n1/fw/aop/client.py new file mode 100644 index 000000000..166a7c30d --- /dev/null +++ b/proxyclient/m1n1/fw/aop/client.py @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: MIT +from ...utils import * + +from ..asc import StandardASC +from .aopep import * +from .base import AOPBase + +class AOPClient(StandardASC, AOPBase): + ENDPOINTS = { + 0x20: AOPSPUAppEndpoint, + 0x21: AOPAccelEndpoint, + 0x22: AOPGyroEndpoint, + 0x24: AOPALSEndpoint, + 0x25: AOPWakehintEndpoint, + 0x26: AOPUNK26Endpoint, + 0x27: AOPAudioEndpoint, + 0x28: AOPVoiceTriggerEndpoint, + } + def __init__(self, u, dev_path, dart=None): + node = u.adt[dev_path] + asc_base = node.get_reg(0)[0] + AOPBase.__init__(self, u) + super().__init__(u, asc_base, dart) + self.dart = dart From b563b5c7615d5fd2509fe7bc3da7ecc8e53003a0 Mon Sep 17 00:00:00 2001 From: Eileen Yoon Date: Fri, 19 Jan 2024 19:42:35 +0900 Subject: [PATCH 03/22] m1n1.utils: Strip before splitting on chexundump() Signed-off-by: Eileen Yoon --- proxyclient/m1n1/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proxyclient/m1n1/utils.py b/proxyclient/m1n1/utils.py index 9e7c24531..2d220fd87 100644 --- a/proxyclient/m1n1/utils.py +++ b/proxyclient/m1n1/utils.py @@ -114,11 +114,11 @@ def chexundump(dump, base=0): dump = dump.read() decoded = bytearray() - for line in dump.splitlines(): + for line in dump.strip().splitlines(): if not line: continue try: - cropped = line.split("|", 2)[0] + cropped = line.strip().split("|", 2)[0] mark, data = cropped.split(" ", 1) if data.strip() == "*": continue From 6c893895e97e61da9f52f99ff774fd7de66332f1 Mon Sep 17 00:00:00 2001 From: Eileen Yoon Date: Sat, 20 Jan 2024 23:56:29 +0900 Subject: [PATCH 04/22] m1n1.adt: Parse aop audio pdm ratios Signed-off-by: Eileen Yoon --- proxyclient/m1n1/adt.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/proxyclient/m1n1/adt.py b/proxyclient/m1n1/adt.py index 01223a436..9bdcedcab 100644 --- a/proxyclient/m1n1/adt.py +++ b/proxyclient/m1n1/adt.py @@ -246,6 +246,13 @@ "pad" / Array(3, Hex(Int8ul)), ) +AOPRatios = Struct( + "r0" / Int8ul, + "r1" / Int8ul, + "r2" / Int8ul, + Const(0, Int8ul), +) + DEV_PROPERTIES = { "pmgr": { "*": { @@ -326,6 +333,8 @@ "*": { "clockSource": FourCC, "identifier": FourCC, + "ratios": AOPRatios, + "filterLengths": Hex(Int32ul), }, }, "*alc?/audio-leap-mic*": { From 3e51a0dd0fa45e805879d37e09737bdf3d45b6cd Mon Sep 17 00:00:00 2001 From: Eileen Yoon Date: Sun, 21 Jan 2024 02:21:27 +0900 Subject: [PATCH 05/22] experiments/dcp: Make dcp*.py scripts run again Signed-off-by: Eileen Yoon --- proxyclient/experiments/dcp.py | 6 +++--- proxyclient/experiments/dcp_iboot.py | 2 +- proxyclient/experiments/dcpext_iboot.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/proxyclient/experiments/dcp.py b/proxyclient/experiments/dcp.py index 61f3c011a..cd5a2ae99 100755 --- a/proxyclient/experiments/dcp.py +++ b/proxyclient/experiments/dcp.py @@ -9,7 +9,7 @@ from m1n1.setup import * from m1n1.shell import run_shell from m1n1 import asm -from m1n1.hw.dart import DART, DARTRegs +from m1n1.hw.dart import DART from m1n1.fw.dcp.client import DCPClient from m1n1.fw.dcp.manager import DCPManager from m1n1.fw.dcp.ipc import ByRef @@ -101,9 +101,9 @@ def get_timing_mode(mgr): disp_dart = DART.from_adt(u, "arm-io/dart-disp0") print("DCP DART:") -dart.regs.dump_regs() +dart.dart.regs.dump_regs() print("DISP DART:") -disp_dart.regs.dump_regs() +disp_dart.dart.regs.dump_regs() dcp_addr = u.adt["arm-io/dcp"].get_reg(0)[0] dcp = DCPClient(u, dcp_addr, dart, disp_dart) diff --git a/proxyclient/experiments/dcp_iboot.py b/proxyclient/experiments/dcp_iboot.py index 9aed5d869..8150149f3 100755 --- a/proxyclient/experiments/dcp_iboot.py +++ b/proxyclient/experiments/dcp_iboot.py @@ -9,7 +9,7 @@ from m1n1.setup import * from m1n1.shell import run_shell from m1n1 import asm -from m1n1.hw.dart import DART, DARTRegs +from m1n1.hw.dart import DART from m1n1.fw.dcp.iboot import DCPIBootClient, SurfaceFormat, EOTF, Transform, AddrFormat, Colorspace from m1n1.proxyutils import RegMonitor diff --git a/proxyclient/experiments/dcpext_iboot.py b/proxyclient/experiments/dcpext_iboot.py index ceb60bdd4..e5f58c93e 100644 --- a/proxyclient/experiments/dcpext_iboot.py +++ b/proxyclient/experiments/dcpext_iboot.py @@ -9,7 +9,7 @@ from m1n1.setup import * from m1n1.shell import run_shell from m1n1 import asm -from m1n1.hw.dart import DART, DARTRegs +from m1n1.hw.dart import DART from m1n1.fw.dcp.iboot import DCPIBootClient, SurfaceFormat, EOTF, Transform, AddrFormat from m1n1.fw.dcp.dcpav import * from m1n1.proxyutils import RegMonitor From 27390b82ca9f5a98992c6e6b86f0234ec3c08785 Mon Sep 17 00:00:00 2001 From: Eileen Yoon Date: Sun, 21 Jan 2024 02:30:04 +0900 Subject: [PATCH 06/22] m1n1.fw.afk: Add more hello handlers used by aop Signed-off-by: Eileen Yoon --- proxyclient/m1n1/fw/afk/rbep.py | 40 ++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/proxyclient/m1n1/fw/afk/rbep.py b/proxyclient/m1n1/fw/afk/rbep.py index 872d75fb7..236543834 100644 --- a/proxyclient/m1n1/fw/afk/rbep.py +++ b/proxyclient/m1n1/fw/afk/rbep.py @@ -150,11 +150,30 @@ def __init__(self, *args, **kwargs): def start(self): self.send(AFKEP_Init()) + def stop(self): + self.log("Shutting down") + self.send(AFKEP_Shutdown()) + while self.alive: + self.asc.work() + @msg_handler(0xa0, AFKEP_Init_Ack) def Init_Ack(self, msg): self.alive = True return True + @msg_handler(0xc1, AFKEP_Shutdown_Ack) + def Shutdown_Ack(self, msg): + self.alive = False + self.log("Shutdown ACKed") + return True + + @msg_handler(0x80, AFKEP_Init) + def Hello(self, msg): + self.rxbuf, self.rxbuf_dva = self.asc.ioalloc(0x10000) + self.txbuf, self.txbuf_dva = self.asc.ioalloc(0x10000) + self.send(AFKEP_Init_Ack()) + return True + @msg_handler(0x89, AFKEP_GetBuf) def GetBuf(self, msg): size = msg.SIZE * AFKRingBuf.BLOCK_SIZE @@ -168,18 +187,6 @@ def GetBuf(self, msg): 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) @@ -194,14 +201,17 @@ def InitRX(self, msg): self.start_queues() return True + @msg_handler(0x8c, AFKEP_InitRB) + def InitUnk(self, msg): + return True # no op + 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()) + self.send(AFKEP_Start()) @msg_handler(0x86, AFKEP_Start_Ack) def Start_Ack(self, msg): @@ -219,7 +229,7 @@ def Recv(self, msg): def handle_ipc(self, data): pass - + def send_ipc(self, data): wptr = self.txq.write(data) self.send(AFKEP_Send(WPTR = wptr)) From 406714b6a4a41c78be6a692d925cb1bd2fa3d4c5 Mon Sep 17 00:00:00 2001 From: Eileen Yoon Date: Tue, 23 Jan 2024 19:03:44 +0900 Subject: [PATCH 07/22] afk.epic: Enum for EPICSubtype & service init v2 Consistently use an enum for EPICSubtype (used later) and support the v2 EPICService intializer protocol used by AOP. Signed-off-by: Eileen Yoon --- proxyclient/m1n1/fw/afk/epic.py | 46 ++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/proxyclient/m1n1/fw/afk/epic.py b/proxyclient/m1n1/fw/afk/epic.py index e95281de6..01bd32cb3 100644 --- a/proxyclient/m1n1/fw/afk/epic.py +++ b/proxyclient/m1n1/fw/afk/epic.py @@ -23,6 +23,12 @@ COMMAND = 0x30, ) +EPICSubtype = "EPICSubtype" / Enum(Int16ul, + ANNOUNCE = 0x30, + TEARDOWN = 0x32, + STD_SERVICE = 0xc0, +) + EPICHeader = Struct( "channel" / Int32ul, "type" / EPICType, @@ -37,18 +43,30 @@ "length" / Int32ul, "version" / Default(Int8ul, 4), "category" / EPICCategory, - "type" / Hex(Int16ul), + "type" / EPICSubtype, "timestamp" / Default(Int64ul, 0), "seq" / Int16ul, "unk" / Default(Hex(Int16ul), 0), "inline_len" / Hex(Int32ul), ) +# dcp's announce EPICAnnounce = Struct( "name" / Padded(32, CString("utf8")), "props" / Optional(OSSerialize()) ) +# aop's announce +EPICServiceAnnounce = Struct( + "name" / Padded(20, CString("utf8")), + "unk1" / Hex(Int32ul), + "retcode" / Hex(Int32ul), # 0xE00002C2 + "unk3" / Hex(Int32ul), + "channel" / Hex(Int32ul), + "unk5" / Hex(Int32ul), + "unk6" / Hex(Int32ul), +) + EPICSetProp = Struct( "name_len" / Int32ul, "name" / Aligned(4, CString("utf8")), @@ -80,17 +98,17 @@ def __init__(self, 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() @@ -234,7 +252,7 @@ def wait_for(self, name): self.asc.work() def handle_report(self, hdr, sub, fd): - if sub.type == 0x30: + if sub.type == EPICSubtype.ANNOUNCE: # dcp's announce init = EPICAnnounce.parse_stream(fd) if init.props is None: init.props = {} @@ -253,6 +271,22 @@ def handle_report(self, hdr, sub, fd): self.log(f"New service: {key} on channel {hdr.channel} (short name: {short})") else: self.log(f"Unknown service {key} on channel {hdr.channel}") + elif sub.type == EPICSubtype.STD_SERVICE: # aop's announce + props = EPICServiceAnnounce.parse_stream(fd) + name = props.name + key = props.name + chan = props.channel # aop uses channel parsed from prop, not from header + if name in self.serv_names: + srv = self.serv_names[name](self) + short = srv.SHORT # aop has no EPICUnit stuff + #setattr(self, short, srv) + srv.init(props) + srv.chan = chan + self.chan_map[chan] = srv + self.serv_map[key] = srv + self.log(f"New service: {key} on channel {chan} (short name: {short})") + else: + self.log(f"Unknown service {key} on channel {chan}") else: if hdr.channel not in self.chan_map: self.log(f"Ignoring report on channel {hdr.channel}") @@ -267,7 +301,7 @@ def handle_reply(self, hdr, sub, 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 From 5b8d870119499dfe898c242b8f05d05a7bae0a86 Mon Sep 17 00:00:00 2001 From: Eileen Yoon Date: Tue, 23 Jan 2024 20:09:14 +0900 Subject: [PATCH 08/22] afk.epic: Handle EPIC replies by subtype DCP only gets two reply types: a u32 retcode or a command reply. It previously checked for retcode by checking length(data) == 4 and assuming command otherwise. AOP can get other reply types (e.g. STRING after probing device). It actually sends us the reply subtype in the EPICSubHeader, so use that information and handle replies accordingly. Signed-off-by: Eileen Yoon --- proxyclient/m1n1/fw/afk/epic.py | 59 +++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 18 deletions(-) diff --git a/proxyclient/m1n1/fw/afk/epic.py b/proxyclient/m1n1/fw/afk/epic.py index 01bd32cb3..5981060c5 100644 --- a/proxyclient/m1n1/fw/afk/epic.py +++ b/proxyclient/m1n1/fw/afk/epic.py @@ -26,7 +26,10 @@ EPICSubtype = "EPICSubtype" / Enum(Int16ul, ANNOUNCE = 0x30, TEARDOWN = 0x32, + RETCODE_WITH_PAYLOAD = 0xa0, STD_SERVICE = 0xc0, + RETCODE = 0x84, + STRING = 0x8a, ) EPICHeader = Struct( @@ -132,24 +135,44 @@ def handle_notify(self, category, type, seq, fd): def handle_reply(self, category, type, seq, fd): off = fd.tell() data = fd.read() - if len(data) == 4: - retcode = struct.unpack(" Date: Tue, 23 Jan 2024 20:12:08 +0900 Subject: [PATCH 09/22] afk.epic: Support sending direct notify messages Much simpler than the command messages. We just write directly into the TX ringbuffer. Signed-off-by: Eileen Yoon --- proxyclient/m1n1/fw/afk/epic.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/proxyclient/m1n1/fw/afk/epic.py b/proxyclient/m1n1/fw/afk/epic.py index 5981060c5..3b12eee2f 100644 --- a/proxyclient/m1n1/fw/afk/epic.py +++ b/proxyclient/m1n1/fw/afk/epic.py @@ -195,6 +195,15 @@ def send_cmd(self, type, data, retlen=None): self.ep.asc.work() return self.reply + def send_notify(self, type, data, **kwargs): + self.reply = None + self.iface.writemem(self.txbuf, data) + self.ep.send_epic(self.chan, EPICType.NOTIFY, EPICCategory.NOTIFY, type, self.seq, data, **kwargs) + 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 @@ -325,6 +334,15 @@ def handle_reply(self, hdr, sub, fd): def handle_cmd(self, hdr, sub, fd): self.chan_map[hdr.channel].handle_cmd(sub.category, sub.type, sub.seq, fd) + def send_roundtrip(self, chan, call, **kwargs): + self.serv_map[chan].last_call = call + ret = self.serv_map[chan].send_notify(call.TYPE, call.ARGS.build(call.args), **kwargs) + self.serv_map[chan].last_call = None + return ret + + def send_notify(self, chan, call, **kwargs): + return self.serv_map[chan].send_notify(call.TYPE, call.ARGS.build(call.args), **kwargs) + def send_epic(self, chan, ptype, category, type, seq, data, inline_len=0): hdr = Container() hdr.channel = chan From cb3071ef4ea0c93d952c21d58b38e625fc476c95 Mon Sep 17 00:00:00 2001 From: Eileen Yoon Date: Tue, 23 Jan 2024 20:23:52 +0900 Subject: [PATCH 10/22] afk.epic: Create API to register report handlers Because AOP gets a lot of reports ;) Signed-off-by: Eileen Yoon --- proxyclient/m1n1/fw/afk/epic.py | 40 ++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/proxyclient/m1n1/fw/afk/epic.py b/proxyclient/m1n1/fw/afk/epic.py index 3b12eee2f..061f1e166 100644 --- a/proxyclient/m1n1/fw/afk/epic.py +++ b/proxyclient/m1n1/fw/afk/epic.py @@ -86,11 +86,18 @@ "txcookie" / Optional(Default(Bool(Int8ul), False)), ) +# Register RX report handlers with @report_handler(type, ConstructClass) +def report_handler(subtype, repcls): + def f(x): + x.is_report = True + x.subtype = subtype + x.repcls = repcls + return x + return f class EPICError(Exception): pass - class EPICService: RX_BUFSIZE = 0x4000 TX_BUFSIZE = 0x4000 @@ -102,6 +109,17 @@ def __init__(self, ep): self.chan = None self.seq = 0 + self.reporthandler = {} + self.reporttypes = {} + for name in dir(self): + i = getattr(self, name) + if not callable(i): + continue + if not getattr(i, "is_report", False): + continue + self.reporthandler[i.subtype] = i + self.reporttypes[i.subtype] = i.repcls + def log(self, msg): print(f"[{self.ep.name}.{self.SHORT}] {msg}") @@ -117,8 +135,24 @@ def wait(self): self.ep.asc.work() def handle_report(self, category, type, seq, fd): - self.log(f"Report {category}/{type} #{seq}") - chexdump(fd.read()) + st = int(type) + self.log(f"Report {category}/{st:#x} #{seq}") + handler = self.reporthandler.get(st, None) + if handler is None: + self.log("unknown report: 0x%x" % (st)) + chexdump(fd.read()) + return False + + payload = fd.read() + reptype = self.reporttypes.get(st, None) + try: + rep = reptype.parse(payload) + except Exception as e: + self.log(e) + rep = None + chexdump(payload) + raise EPICError(f"failed to parse report {st:#x}") + return handler(seq, fd, rep) def handle_notify(self, category, type, seq, fd): retcode = struct.unpack(" Date: Tue, 23 Jan 2024 20:24:37 +0900 Subject: [PATCH 11/22] afk.epic: Support sending v2 EPIC subheader Default to version=4 as to not break DCP Signed-off-by: Eileen Yoon --- proxyclient/m1n1/fw/afk/epic.py | 36 ++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/proxyclient/m1n1/fw/afk/epic.py b/proxyclient/m1n1/fw/afk/epic.py index 061f1e166..26181afd4 100644 --- a/proxyclient/m1n1/fw/afk/epic.py +++ b/proxyclient/m1n1/fw/afk/epic.py @@ -53,6 +53,17 @@ "inline_len" / Hex(Int32ul), ) +EPICSubHeaderV2 = Struct( + "length" / Int32ul, + "version" / Default(Int8ul, 2), + "category" / EPICCategory, + "type" / EPICSubtype, + "seq" / Int16ul, + "unk" / Default(Hex(Int16ul), 0), + "pad" / Default(Hex(Int64ul), 0), + "inline_len" / Hex(Int32ul), +) + # dcp's announce EPICAnnounce = Struct( "name" / Padded(32, CString("utf8")), @@ -377,7 +388,7 @@ def send_roundtrip(self, chan, call, **kwargs): def send_notify(self, chan, call, **kwargs): return self.serv_map[chan].send_notify(call.TYPE, call.ARGS.build(call.args), **kwargs) - def send_epic(self, chan, ptype, category, type, seq, data, inline_len=0): + def send_epicv4(self, chan, ptype, category, type, seq, data, inline_len=0, **kwargs): hdr = Container() hdr.channel = chan hdr.type = ptype @@ -393,6 +404,29 @@ def send_epic(self, chan, ptype, category, type, seq, data, inline_len=0): pkt = EPICHeader.build(hdr) + EPICSubHeader.build(sub) + data super().send_ipc(pkt) + def send_epicv2(self, chan, ptype, category, type, seq, data, inline_len=0, **kwargs): + 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) + EPICSubHeaderV2.build(sub) + data + super().send_ipc(pkt) + + def send_epic(self, chan, ptype, category, type, seq, data, inline_len=0, version=4, **kwargs): + if (version == 2): + return self.send_epicv2(chan, ptype, category, type, seq, data, inline_len, **kwargs) + if (version == 4): + return self.send_epicv4(chan, ptype, category, type, seq, data, inline_len, **kwargs) + raise EPICError(f"unknown epic sub version: {version}") + class AFKSystemEndpoint(EPICEndpoint): SHORT = "system" From 93cbb1ba2b44c19bcbe897165f325db42cb857c9 Mon Sep 17 00:00:00 2001 From: Eileen Yoon Date: Tue, 23 Jan 2024 21:44:48 +0900 Subject: [PATCH 12/22] afk.rbep: Bootstrap ringbuf block size at runtime Make the ringbuffer class robust to various block sizes to generalize to both DCP and AOP. The first three blocks of the ringbuffer is reserved for exchanging size, rptr, wptr: ``` bufsize unk 00000000 00007e80 00070006 00000000 00000000 00000000 00000000 00000000 00000000 00000020 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000040 * rptr 00000080 00000600 00000000 00000000 00000000 00000000 00000000 00000000 00000000 000000a0 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 000000c0 * wptr 00000100 00000680 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000120 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000140 * ``` Each block is spread out by some block_size multiple of 0x40 (step). The 0th block holds the size of the ringbuffer contents, the 1st block holds the rptr, and the 2nd block holds the wptr. The actual contents of the ringbuffer starts after the first three blocks, which will be collectively called the "header". However, this block_size isn't constant. DCP seems to consistently use 0x40, but AOP can use both 0x40/0x80. Since we're not given the block_size, so wemust bootstrap it. Recall we are given the total size of the rinbuffer in the mailbox message. Since we're always given the size of the ringbuffer `bufsize` at offset +block_size * 0 (or simply 0), and we can find the header size by subtracting `bufsize` from the total size. Since we also know that the header is always 3 blocks wide, we can divide the header size by 3 to obtain the block_size. Signed-off-by: Eileen Yoon --- proxyclient/m1n1/fw/afk/rbep.py | 64 +++++++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/proxyclient/m1n1/fw/afk/rbep.py b/proxyclient/m1n1/fw/afk/rbep.py index 236543834..28d79848d 100644 --- a/proxyclient/m1n1/fw/afk/rbep.py +++ b/proxyclient/m1n1/fw/afk/rbep.py @@ -54,15 +54,45 @@ class AFKEP_Shutdown_Ack(AFKEPMessage): class AFKError(Exception): pass +""" +The first three blocks of the ringbuffer is reserved for exchanging size, +rptr, wptr: + + bufsize unk +00000000 00007e80 00070006 00000000 00000000 00000000 00000000 00000000 00000000 +00000020 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 +00000040 * rptr +00000080 00000600 00000000 00000000 00000000 00000000 00000000 00000000 00000000 +000000a0 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 +000000c0 * wptr +00000100 00000680 00000000 00000000 00000000 00000000 00000000 00000000 00000000 +00000120 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 +00000140 * + +Note how each block is spread out by some block_size multiple of 0x40 +(step). Here, the block_size is 0x80. The 0th block holds the bufsize, +the 1st block holds the rptr, and the 2nd block holds the wptr. The +actual contents of the ringbuffer starts after the first three blocks, +which will be called the "header". Since we're *always* given the total +block size at offset +0x0 or +block_size*0, we can calculate the block +size by dividing by 3. +""" + class AFKRingBuf(Reloadable): BLOCK_SIZE = 0x40 + BLOCK_COUNT = 3 def __init__(self, ep, base, size): self.ep = ep self.base = base bs, unk = struct.unpack(" (self.bufsize - self.rptr): - hdr = self.read_buf(3 * self.BLOCK_SIZE, 16) + hdr = self.read_buf(base, 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 + payload = self.read_buf(base + self.rptr, size) + self.rptr = (align_up(self.rptr + size, self.stride)) % self.bufsize self.update_rptr(self.rptr) yield hdr[8:] + payload self.wptr = self.get_wptr() @@ -108,6 +143,7 @@ def read(self): self.update_rptr(self.rptr) def write(self, data): + base = self.stride * 3 # after header (size, rptr, wptr) hdr2, data = data[:8], data[8:] self.rptr = self.get_rptr() @@ -115,19 +151,19 @@ def write(self, data): 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) + self.write_buf(base + 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.write_buf(base, 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.write_buf(base + self.wptr + 0x10, data) + self.wptr = align_up(self.wptr + 0x10 + len(data), self.stride) % self.bufsize self.update_wptr(self.wptr) return self.wptr From 4c32f8aea913b68cf67593c144c9dcee812f225d Mon Sep 17 00:00:00 2001 From: Eileen Yoon Date: Tue, 23 Jan 2024 21:58:29 +0900 Subject: [PATCH 13/22] afk.rbep: s/BLOCK_SIZE/BLOCK_STEP/ Otherwise the previous commit is way too confusing. Signed-off-by: Eileen Yoon --- proxyclient/m1n1/fw/afk/rbep.py | 42 ++++++++++++++++----------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/proxyclient/m1n1/fw/afk/rbep.py b/proxyclient/m1n1/fw/afk/rbep.py index 28d79848d..55f4400fa 100644 --- a/proxyclient/m1n1/fw/afk/rbep.py +++ b/proxyclient/m1n1/fw/afk/rbep.py @@ -79,7 +79,7 @@ class AFKError(Exception): """ class AFKRingBuf(Reloadable): - BLOCK_SIZE = 0x40 + BLOCK_STEP = 0x40 BLOCK_COUNT = 3 def __init__(self, ep, base, size): @@ -87,12 +87,12 @@ def __init__(self, ep, base, size): self.base = base bs, unk = struct.unpack(" Date: Tue, 23 Jan 2024 23:26:30 +0900 Subject: [PATCH 14/22] hv/trace_aop: Handle afk/epic API breakage Signed-off-by: Eileen Yoon --- proxyclient/hv/trace_aop.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/proxyclient/hv/trace_aop.py b/proxyclient/hv/trace_aop.py index 489c9b34e..1d0e8bef7 100644 --- a/proxyclient/hv/trace_aop.py +++ b/proxyclient/hv/trace_aop.py @@ -108,15 +108,15 @@ def handle_ipc(self, data, dir=None): @msg(0x8a, DIR.RX, AFKEP_InitRB) def InitTX(self, msg): - off = msg.OFFSET * AFKRingBuf.BLOCK_SIZE - size = msg.SIZE * AFKRingBuf.BLOCK_SIZE + off = msg.OFFSET * AFKRingBuf.BLOCK_STEP + size = msg.SIZE * AFKRingBuf.BLOCK_STEP 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 + off = msg.OFFSET * AFKRingBuf.BLOCK_STEP + size = msg.SIZE * AFKRingBuf.BLOCK_STEP self.state.rxbuf_info = (off, size) self.create_bufs() From 87e7f882750253843d27a306ac8a7ce48f4bef23 Mon Sep 17 00:00:00 2001 From: Eileen Yoon Date: Tue, 23 Jan 2024 23:33:19 +0900 Subject: [PATCH 15/22] hv/trace_dcp: Fix afk/epic API breakage Signed-off-by: Eileen Yoon --- proxyclient/hv/trace_dcp.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/proxyclient/hv/trace_dcp.py b/proxyclient/hv/trace_dcp.py index 832c597fc..21c9031e9 100644 --- a/proxyclient/hv/trace_dcp.py +++ b/proxyclient/hv/trace_dcp.py @@ -58,7 +58,7 @@ def update_wptr(self): raise NotImplementedError() def get_wptr(self): - return struct.unpack(" Date: Tue, 30 Jan 2024 09:28:04 +0900 Subject: [PATCH 16/22] aop.ipc: Fill in more known audio config fields Signed-off-by: Eileen Yoon --- proxyclient/m1n1/fw/aop/ipc.py | 44 ++++++++++++++++------------------ 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/proxyclient/m1n1/fw/aop/ipc.py b/proxyclient/m1n1/fw/aop/ipc.py index d26315d1d..0f0a2790f 100644 --- a/proxyclient/m1n1/fw/aop/ipc.py +++ b/proxyclient/m1n1/fw/aop/ipc.py @@ -148,45 +148,41 @@ class AttachDevice(WrappedCall): "unk" / HexDump(GreedyBytes), ) -@WrappedCall.reg_subclass -class ProbeDevice(WrappedCall): - CALLTYPE = 0xc3_00_00_01 +@reg_calltype +class AudioAttachDevice(EPICCall): + TYPE = 0x20 + SUBTYPE = 0xc3_00_00_02 ARGS = Struct( - "blank" / Const(0x0, Int32ul), + "pad" / 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, + "subtype" / Hex(Const(0xc3000002, Int32ul)), + "pad2" / ZPadding(16), + "cookie" / Default(Hex(Int32ul), 0), + "len" / Hex(Const(0x2c, Int64ul)), + "devid" / FourCC, + "dev2" / Default(Hex(Int32ul), 0), ) 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), + "unk" / HexDump(GreedyBytes), ) PDMConfig = Struct( - "unk1" / Int32ul, + "bytesPerSample" / Int32ul, "clockSource" / FourCC, "pdmFrequency" / Int32ul, - "unk3_clk" / Int32ul, - "unk4_clk" / Int32ul, - "unk5_clk" / Int32ul, - "channelPolaritySelect" / Hex(Int32ul), - "unk7" / Hex(Int32ul), + "pdmcFrequency" / Int32ul, + "slowClockSpeed" / Int32ul, + "fastClockSpeed" / Int32ul, + "channelPolaritySelect" / Int32ul, + "channelPhaseSelect" / Int32ul, "unk8" / Hex(Int32ul), "unk9" / Hex(Int16ul), "ratios" / Struct( "r1" / Int8ul, "r2" / Int8ul, "r3" / Int8ul, - "pad" / Default(Int8ul, 0), + "pad" / Const(0, Int8ul), ), "filterLengths" / Hex(Int32ul), "coeff_bulk" / Int32ul, @@ -240,7 +236,7 @@ class ProbeDevice(WrappedCall): PowerSetting = Struct( "devid" / FourCC, "cookie" / Int32ul, - "pad" / Padding(4), + "unk" / Default(Hex(Int32ul), 0), "blank" / ZPadding(8), "target_pstate" / FourCC, "unk2" / Int32ul, From c1495770106955e4ecc26afbe755e0e1611c0d79 Mon Sep 17 00:00:00 2001 From: Eileen Yoon Date: Tue, 30 Jan 2024 09:53:38 +0900 Subject: [PATCH 17/22] aop.ipc: Rename & fill in more property keys Signed-off-by: Eileen Yoon --- proxyclient/m1n1/fw/aop/ipc.py | 46 +++++++++++++--------------------- 1 file changed, 18 insertions(+), 28 deletions(-) diff --git a/proxyclient/m1n1/fw/aop/ipc.py b/proxyclient/m1n1/fw/aop/ipc.py index 0f0a2790f..75a714b1c 100644 --- a/proxyclient/m1n1/fw/aop/ipc.py +++ b/proxyclient/m1n1/fw/aop/ipc.py @@ -2,37 +2,27 @@ from construct import * from io import BytesIO +from ..afk.epic import * 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 - +from m1n1.constructutils import * + +class AOPPropKey(IntEnum): + IS_READY = 0x01 + MANUFACTURER = 0x0f # wtf is a firefish2? + CHIP_ID = 0x11 + PLACEMENT = 0x1e + UNK_21 = 0x21 + ORIENTATION = 0x2e + LOCATION_ID = 0x30 + PRODUCT_ID2 = 0x3f + SERIAL_NO = 0x3e + CHANNEL_NAME = 0x45 + VENDOR_ID = 0x5a + PRODUCT_ID = 0x5b SERVICE_CONTROLLER = 0x64 DEVICE_COUNT = 0x65 - - VERSION = 0x67 + VERSION = 0x67 + UNK_DUMP = 0xd7 class EPICCall: @classmethod From 235da83c722efd69603975d081a7ab0087c37cd6 Mon Sep 17 00:00:00 2001 From: Eileen Yoon Date: Tue, 30 Jan 2024 09:55:33 +0900 Subject: [PATCH 18/22] aop.ipc: Use print as default logger Signed-off-by: Eileen Yoon --- proxyclient/m1n1/fw/aop/ipc.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/proxyclient/m1n1/fw/aop/ipc.py b/proxyclient/m1n1/fw/aop/ipc.py index 75a714b1c..e7ac9c1d4 100644 --- a/proxyclient/m1n1/fw/aop/ipc.py +++ b/proxyclient/m1n1/fw/aop/ipc.py @@ -44,9 +44,7 @@ def __init__(self, *args, **kwargs): def from_stream(cls, f): return cls(cls.ARGS.parse_stream(f)) - def dump(self, logger=None): - if logger is None: - logger = print + def dump(self, 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)})") From 3a5f62bdc76b59703f4c9fdc022d7b059fb30a8b Mon Sep 17 00:00:00 2001 From: Eileen Yoon Date: Tue, 9 Jan 2024 12:40:12 +0900 Subject: [PATCH 19/22] aop: Stream high-power mic samples to ADMAC Output audio format still unknown, not sure if it's garbage (see lpai commit) or some weird packed float encoding I'm not figuring out. Signed-off-by: Eileen Yoon --- proxyclient/experiments/aop.py | 310 --------------------------- proxyclient/experiments/aop_audio.py | 170 +++++++++++++++ proxyclient/m1n1/fw/aop/ipc.py | 114 ++++++++++ 3 files changed, 284 insertions(+), 310 deletions(-) delete mode 100755 proxyclient/experiments/aop.py create mode 100755 proxyclient/experiments/aop_audio.py diff --git a/proxyclient/experiments/aop.py b/proxyclient/experiments/aop.py deleted file mode 100755 index 0dba36fb0..000000000 --- a/proxyclient/experiments/aop.py +++ /dev/null @@ -1,310 +0,0 @@ -#!/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(" pw1 -> pwrd state. it starts capturing at pwrd +# shutdown sequence must also be pwrd -> pw1 -> idle + +def aop_start(): + aop.audio.send_notify(SetDeviceProp( + devid=u'hpai', + modifier=202, + data=Container( + devid=u'hpai', + cookie=2, + target_pstate=u'pwrd', + unk2=1, + ) + )) + +def aop_stop(): + aop.audio.send_notify(SetDeviceProp( + devid='hpai', + modifier=202, + data=Container( + devid='hpai', + cookie=3, + target_pstate='pw1 ', + unk2=1, + ) + )) + + aop.audio.send_notify(SetDeviceProp( + devid='hpai', + modifier=202, + data=Container( + devid='hpai', + cookie=4, + target_pstate='idle', + unk2=0, + ) + )) + +def main(): + aop.start() + for epno in [0x20, 0x21, 0x22, 0x24, 0x25, 0x26, 0x27, 0x28]: + aop.start_ep(epno) + aop.work_for(0.3) + audep = aop.audio + + audep.send_notify(AttachDevice(devid='hpai')) # high power audio input; actual mic + audep.send_notify(AttachDevice(devid='lpai')) # low power audio input; voice trigger mic + audep.send_notify(AttachDevice(devid='pdm0')) # leap: low-energy audio processor I think + # [syslog] * [udioPDMNodeBase.cpp:554]PDMDev off ->xi0 , 0->2400000 AP state 1 + + # initChannelControl (<7, 7, 1, 7>) + audep.send_notify(SetDeviceProp(devid='lpai', modifier=301, data=Container(unk1=7, unk2=7, unk3=1, unk4=7))) + audep.send_notify(SetDeviceProp(devid='pdm0', modifier=200, data=pdm_config)) + audep.send_notify(SetDeviceProp(devid='pdm0', modifier=210, data=decimator_config)) + ret = audep.send_roundtrip(AudioPropertyState(devid="hpai")) + print("hpai state: %s" % (ret.state)) # idle + + audep.send_notify(SetDeviceProp( + devid='hpai', + modifier=202, + data=Container( + devid='hpai', + cookie=1, + target_pstate='pw1 ', + unk2=0, + ) + )) + + ret = audep.send_roundtrip(AudioPropertyState(devid="hpai")) + print("hpai state: %s" % (ret.state)) + assert(ret.state == "pw1 ") + +try: + main() + pass +except KeyboardInterrupt: + pass + +run_shell(locals(), poll_func=aop.work) diff --git a/proxyclient/m1n1/fw/aop/ipc.py b/proxyclient/m1n1/fw/aop/ipc.py index e7ac9c1d4..54dbdc486 100644 --- a/proxyclient/m1n1/fw/aop/ipc.py +++ b/proxyclient/m1n1/fw/aop/ipc.py @@ -118,6 +118,30 @@ def check_retcode(self): self.dump() raise ValueError(f"retcode {self.rets.retcode} in {str(type(self))} (call dumped, see above)") +@reg_calltype +class AudioProbeDevice(EPICCall): + TYPE = 0x20 + SUBTYPE = 0xc3_00_00_01 + ARGS = Struct( + "pad" / Const(0x0, Int32ul), + "unk1" / Hex(Const(0xffffffff, Int32ul)), + "subtype" / Hex(Const(0xc3000001, Int32ul)), + "pad2" / ZPadding(16), + "cookie" / Default(Hex(Int32ul), 0), + "len" / Hex(Const(0x28, Int64ul)), + "devno" / Int32ul, + ) + RETS = Struct( + "retcode" / Const(0x0, Int32ul), + "devid" / FourCC, + "format" / FourCC, + "sample_rate" / Int32ul, + "channels" / Int32ul, + "bytes_per_sample" / Int32ul, + "unk_1f" / Default(Hex(Int32ul), 0), + "unk" / Array(51, Default(Hex(Int32ul), 0)), + ) + @WrappedCall.reg_subclass class AttachDevice(WrappedCall): CALLTYPE = 0xc3_00_00_02 @@ -246,6 +270,96 @@ class AudioAttachDevice(EPICCall): ), } +class AudioPropertyKey(IntEnum): + STATE = 200 # 0xc8 + POWER = 202 # 0xca + MAIN = 203 # 0xcb + FORMAT = 302 # 0x12e + +@reg_calltype +class AudioProperty(EPICCall): + TYPE = 0x20 + SUBTYPE = 0xc3000004 + SUBCLASSES = {} + + @classmethod + def subclass(cls, cls2): + cls.SUBCLASSES[int(cls2.SUBTYPE)] = cls2 + return cls2 + +@AudioProperty.subclass +class AudioPropertyState(AudioProperty): + SUBSUBTYPE = AudioPropertyKey.STATE + ARGS = Struct( + "pad" / Const(0x0, Int32ul), + "unk1" / Hex(Const(0xffffffff, Int32ul)), + "calltype" / Hex(Const(0xc3000004, Int32ul)), + "blank2" / ZPadding(16), + "cookie" / Default(Hex(Int32ul), 0), + "len" / Hex(Const(0x30, Int64ul)), + "devid" / FourCC, + "modifier" / Const(AudioPropertyKey.STATE, Int32ul), + "data" / Const(0x1, Int32ul), + ) + RETS = Struct( + "retcode" / Const(0x0, Int32ul), + "size" / Const(4, Int32ul), + "state" / FourCC, + ) + +@AudioProperty.subclass +class AudioPropertyFormat(AudioProperty): + SUBSUBTYPE = AudioPropertyKey.FORMAT + ARGS = Struct( + "pad" / Const(0x0, Int32ul), + "unk1" / Hex(Const(0xffffffff, Int32ul)), + "calltype" / Hex(Const(0xc3000004, Int32ul)), + "blank2" / ZPadding(16), + "cookie" / Default(Hex(Int32ul), 0), + "len" / Hex(Const(0x30, Int64ul)), + "devid" / FourCC, + "modifier" / Const(AudioPropertyKey.FORMAT, Int32ul), + "data" / Const(0x1, Int32ul), + ) + RETS = Struct( + "retcode" / Const(0x0, Int32ul), + "format" / Int32ul, # 16 == float32? + "fourcc" / FourCC, # PCML + "sample_rate" / Int32ul, # 16000 + "channels" / Int32ul, # 3 + "bytes_per_sample" / Int32ul, # 2 + ) + +AudioPowerSetting = Struct( + "devid" / FourCC, + "unk1" / Int32ul, + "cookie" / Default(Hex(Int32ul), 0), + "blank" / ZPadding(8), + "target_pstate" / FourCC, + "unk2" / Int32ul, + "blank2" / ZPadding(20), +) + +@AudioProperty.subclass +class AudioPropertyPower(AudioProperty): + SUBSUBTYPE = AudioPropertyKey.POWER + ARGS = Struct( + "pad" / Const(0x0, Int32ul), + "unk1" / Hex(Const(0xffffffff, Int32ul)), + "calltype" / Hex(Const(0xc3000005, Int32ul)), + "blank2" / ZPadding(16), + "cookie" / Default(Hex(Int32ul), 0), + "len" / Hex(Const(0x30 + 0x30, Int64ul)), # len(this.data) + 0x30 + "devid" / FourCC, + "modifier" / Const(AudioPropertyKey.POWER, Int32ul), + "len2" / Hex(Const(0x30, Int32ul)), # len(this.data) + "data" / AudioPowerSetting, + ) + RETS = Struct( + "retcode" / Const(0x0, Int32ul), + "value" / HexDump(GreedyBytes), + ) + @WrappedCall.reg_subclass class GetDeviceProp(WrappedCall): CALLTYPE = 0xc3_00_00_04 From 42f8c3ae66db6097fcc797e640841a3ea9c9d451 Mon Sep 17 00:00:00 2001 From: Eileen Yoon Date: Mon, 22 Jan 2024 04:39:29 +0900 Subject: [PATCH 20/22] aop: Support Ambient Light Sensor (ALS) endpoint ``` [syslog] * [ALSCT720.cpp:967]updateDynamicIntegrationParameters allAbove=0, anyBelow=1, threshold hit=0, lev > 02:0x50000000000005 (TYPE=0x5, INDEX=0x5) < 24:0x85000000000000 [als.als] Report REPORT/0xc4 #0 [als.als] Container: unk0 = 0xEC sequence = 4 timestamp = 0x0000000019352268 red = 11 green = 18 blue = 11 clear = 40 lux = 7.277451038360596 unk_zero = 0 status = 3 gain = 256 unk3 = 68 unk4 = 35 unk5 = 17874 integration_time = 378080 < 02:0x50000000000006 [syslog] * [ALSCT720.cpp:454]handleInterrupt: result: 0 (status=0x3) > 02:0x50000000000006 (TYPE=0x5, INDEX=0x6) ``` Signed-off-by: Eileen Yoon --- proxyclient/experiments/aop_als.py | 84 ++++++++++++++++++++++ proxyclient/m1n1/fw/aop/ipc.py | 112 ++++++++++++++++++++++++++++- 2 files changed, 194 insertions(+), 2 deletions(-) create mode 100755 proxyclient/experiments/aop_als.py diff --git a/proxyclient/experiments/aop_als.py b/proxyclient/experiments/aop_als.py new file mode 100755 index 000000000..1230f8f8c --- /dev/null +++ b/proxyclient/experiments/aop_als.py @@ -0,0 +1,84 @@ +#!/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.hw.dart import DART +from m1n1.fw.aop.client import AOPClient +from m1n1.fw.aop.ipc import * + +# aop nodes have no clocks described in adt for j293. it does it itself +p.pmgr_adt_clocks_enable("/arm-io/aop") +p.pmgr_adt_clocks_enable("/arm-io/dart-aop") + +dart = DART.from_adt(u, "/arm-io/dart-aop", + iova_range=(u.adt["/arm-io/dart-aop"].vm_base, 0x1000000000)) +dart.initialize() + +aop = AOPClient(u, "/arm-io/aop", dart) +aop.update_bootargs({ + 'p0CE': 0x20000, + 'laCn': 0x0, + 'tPOA': 0x1, + "gila": 0x80, +}) +aop.verbose = 4 + +p.dapf_init_all() +aop.asc.OUTBOX_CTRL.val = 0x20001 # (FIFOCNT=0x0, OVERFLOW=0, EMPTY=1, FULL=0, RPTR=0x0, WPTR=0x0, ENABLE=1) + +aop.start() +for epno in [0x20, 0x21, 0x22, 0x24, 0x25, 0x26, 0x27, 0x28]: + aop.start_ep(epno) +aop.work_for(0.3) +alsep = aop.als +alsep.VERBOSE = True + +def start(): + #ret = alsep.roundtrip(GetProperty(key=AOPPropKey.MANUFACTURER)) + #print(ret.rets.value.decode("ascii")) # FireFish2 + ret = alsep.send_notify(GetProperty(key=AOPPropKey.MANUFACTURER)) + ret = alsep.send_notify(ALSSetPropertyVerbosity(level=0xffffffff)) # retcode: 0 + # [syslog] * [ALSAOPDriver.cpp:267]setProperty Setting log level = -1 + + dump = """ +00000000 09 00 00 10 00 00 00 00 02 24 00 00 00 00 00 00 |.........$......| +00000010 00 00 00 00 00 00 00 00 99 00 00 00 02 10 04 00 |................| +00000020 24 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |$...............| +00000030 00 00 00 00 0b 00 00 00 01 02 00 00 00 ad ab 0a |................| +00000040 00 01 91 00 9a 59 fa 00 64 00 64 00 08 08 08 04 |.....Y..d.d.....| +00000050 09 63 eb ff ff d4 0e 00 00 c7 ce ff ff 23 2e 00 |.c...........#..| +00000060 00 a2 08 cd 14 d6 5f 77 05 00 00 00 00 00 02 00 |......_w........| +00000070 80 aa 7e 53 7f 10 84 d9 7e 68 7f 68 7f b3 7f f1 |..~S....~h.h....| +00000080 7d 00 00 00 00 00 03 5b 7e 56 7e 00 80 ca 83 e0 |}......[~V~.....| +00000090 7e 79 7f 00 80 8d 80 f1 7f 00 00 00 00 00 02 f3 |~y..............| +000000a0 7c 00 80 38 7f 88 83 0b 7f 05 7f b9 7f b9 7f 0f ||..8............| +000000b0 7e 00 00 00 00 00 06 75 7e 38 7f 69 7f bf 83 f2 |~......u~8.i....| +000000c0 7e 16 7f 4e 7f 33 7f 1f 7d |~..N.3..} | + """ + ret = alsep.send_notify(ALSSetPropertyCalibration(value=chexundump(dump)[0x38:])) + # [syslog] * [ALSCT720.cpp:1138]loadALSCalibrationData: ALS sensor is calibrated + # retcode: 0xe00002bc + + ret = alsep.send_notify(ALSSetPropertyInterval(interval=200000)) + # [syslog] * [ALSCT720.cpp:690]configureSensor - Configure sensor with gain 8 and integration time 197380 + # [syslog] * [ALSCT720.cpp:708]setSensorEnabled - Enabling the sensor. + + if 0: # test that on/off works + aop.work_for(0.5) + ret = alsep.send_notify(ALSSetPropertyInterval(interval=0)) + # [syslog] * [ALSCT720.cpp:598]setProperty: set report interval 0, _wakeHintMode = 0 + # [syslog] * [ALSCT720.cpp:735]setSensorEnabled - Disabling the sensor. + ret = alsep.send_notify(ALSSetPropertyInterval(interval=200000)) + + while True: + aop.work() + +try: + start() +except KeyboardInterrupt: + pass diff --git a/proxyclient/m1n1/fw/aop/ipc.py b/proxyclient/m1n1/fw/aop/ipc.py index 54dbdc486..a83f04790 100644 --- a/proxyclient/m1n1/fw/aop/ipc.py +++ b/proxyclient/m1n1/fw/aop/ipc.py @@ -73,13 +73,121 @@ class GetProperty(EPICCall): TYPE = 0xa ARGS = Struct( "blank" / Const(0x0, Int32ul), - "key" / Enum(Int32ul, AOPAudioPropKey), + "key" / Enum(Int32ul, AOPPropKey), ) RETS = Struct( - #"blank" / Const(0x0, Int32ul), + #"retcode" / Const(0x0, Int32ul), "value" / GreedyBytes, ) +class GetPropertyIsReady(EPICCall): + TYPE = 0xa + ARGS = Struct( + "blank" / Const(0x0, Int32ul), + "key" / Const(AOPPropKey.IS_READY, Int32ul), + ) + RETS = Struct( + "retcode" / Const(0x0, Int32ul), + "state" / FourCC, + ) + +class ALSPropertyKey(IntEnum): + INTERVAL = 0x00 + CALIBRATION = 0x0b + MODE = 0xd7 + VERBOSITY = 0xe1 + UNKE4 = 0xe4 + +@reg_calltype +class ALSSetProperty(EPICCall): + TYPE = 0x4 + SUBCLASSES = {} + + @classmethod + def subclass(cls, cls2): + cls.SUBCLASSES[int(cls2.SUBTYPE)] = cls2 + return cls2 + +@ALSSetProperty.subclass +class ALSSetPropertyVerbosity(ALSSetProperty): + SUBTYPE = ALSPropertyKey.VERBOSITY + ARGS = Struct( + "blank" / Const(0x0, Int32ul), + "key" / Default(Hex(Int32ul), ALSPropertyKey.VERBOSITY), + "level" / Hex(Int32ul), + ) + RETS = Struct( + "retcode" / Const(0x0, Int32ul), + "value" / GreedyBytes, + ) + +@ALSSetProperty.subclass +class ALSSetPropertyMode(ALSSetProperty): + SUBTYPE = ALSPropertyKey.MODE + ARGS = Struct( + "blank" / Const(0x0, Int32ul), + "key" / Default(Hex(Int32ul), ALSPropertyKey.MODE), + "mode" / Int32ul, + ) + RETS = Struct( + "retcode" / Const(0x0, Int32ul), + "value" / GreedyBytes, + ) + +@ALSSetProperty.subclass +class ALSSetPropertyCalibration(ALSSetProperty): + SUBTYPE = ALSPropertyKey.CALIBRATION + ARGS = Struct( + "blank" / Const(0x0, Int32ul), + "key" / Default(Hex(Int32ul), ALSPropertyKey.CALIBRATION), + "value" / GreedyBytes, + ) + RETS = Struct( + "retcode" / Const(0xE00002BC, Hex(Int32ul)), + ) + +@ALSSetProperty.subclass +class ALSSetPropertyInterval(ALSSetProperty): + SUBTYPE = ALSPropertyKey.INTERVAL + ARGS = Struct( + "blank" / Const(0x0, Int32ul), + "key" / Default(Hex(Int32ul), ALSPropertyKey.INTERVAL), + "interval" / Int32ul, + ) + RETS = Struct( + "retcode" / Const(0x0, Int32ul), + ) + +@ALSSetProperty.subclass +class ALSSetPropertyUnkE4(ALSSetProperty): + SUBTYPE = ALSPropertyKey.UNKE4 + ARGS = Struct( + "blank" / Const(0x0, Int32ul), + "key" / Default(Hex(Int32ul), ALSPropertyKey.UNKE4), + "value" / Default(Hex(Int32ul), 0x1), + ) + RETS = Struct( + "retcode" / Const(0x0, Int32ul), + ) + +ALSLuxReport = Struct( + "unk0" / Const(0xec, Hex(Int8ul)), + "sequence" / Int32ul, + "timestamp" / Hex(Int64ul), + "red" / Int32ul, + "green" / Int32ul, + "blue" / Int32ul, + "clear" / Int32ul, + "lux" / Float32l, + "unk_zero" / Int32ul, # 0 + "status" / Int32ul, # 3 + "gain" / Int16ul, + "unk3" / Int8ul, + "unk4" / Int8ul, + "unk5" / Int16ul, + "integration_time" / Int32ul, +) + @reg_calltype class WrappedCall(EPICCall): SUBCLASSES = {} From 4b00d253bc40dd3681c3036152a7961c89e8b676 Mon Sep 17 00:00:00 2001 From: Eileen Yoon Date: Tue, 30 Jan 2024 10:46:40 +0900 Subject: [PATCH 21/22] afk.epic: Expose ep function to call send_cmd() Signed-off-by: Eileen Yoon --- proxyclient/m1n1/fw/afk/epic.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/proxyclient/m1n1/fw/afk/epic.py b/proxyclient/m1n1/fw/afk/epic.py index 26181afd4..090a06cfe 100644 --- a/proxyclient/m1n1/fw/afk/epic.py +++ b/proxyclient/m1n1/fw/afk/epic.py @@ -388,6 +388,9 @@ def send_roundtrip(self, chan, call, **kwargs): def send_notify(self, chan, call, **kwargs): return self.serv_map[chan].send_notify(call.TYPE, call.ARGS.build(call.args), **kwargs) + def send_cmd(self, chan, type, data, retlen=None, **kwargs): + return self.serv_map[chan].send_cmd(type, data, retlen, **kwargs) + def send_epicv4(self, chan, ptype, category, type, seq, data, inline_len=0, **kwargs): hdr = Container() hdr.channel = chan From 472796b5bdcd8b25c865b43b77551fbbe2c8908c Mon Sep 17 00:00:00 2001 From: Eileen Yoon Date: Tue, 30 Jan 2024 09:31:58 +0900 Subject: [PATCH 22/22] aop: Add lpai + voicetrigger test (doesn't work) I'm getting garbage from the low-power audio input (lpai) mic which exists solely for voicetrigger. The garbage specifically is `61 7b` repeated. Obviously no voicetrigger report because there's nothing useful in lpai output. I'm suspecting its an mca/clock issue (maybe even SEP/permissions) because there's nothing suspicious from the aop RX/TX IPC side. ``` [audio.audio] Report REPORT/0xee #0 [audio.audio] unknown report: 0xee 00000000 c5 96 20 01 00 00 00 00 9c f5 10 00 00 00 00 00 |.. .............| 00000010 b8 07 00 00 20 30 6e 69 01 00 00 00 43 02 00 00 |.... 0ni....C...| 00000020 00 00 00 00 c5 96 20 01 00 00 00 00 00 00 00 00 |...... .........| 00000030 a4 07 00 00 9a 07 00 00 61 7b 61 7b 61 7b 61 7b |........a{a{a{a{| 00000040 61 7b 61 7b 61 7b 61 7b 61 7b 61 7b 61 7b 61 7b |a{a{a{a{a{a{a{a{| 00000050 * 000007b0 61 7b 61 7b 61 7b 61 7b |a{a{a{a{ | ``` Signed-off-by: Eileen Yoon --- proxyclient/experiments/aop_vt.py | 143 ++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100755 proxyclient/experiments/aop_vt.py diff --git a/proxyclient/experiments/aop_vt.py b/proxyclient/experiments/aop_vt.py new file mode 100755 index 000000000..f2b9990c8 --- /dev/null +++ b/proxyclient/experiments/aop_vt.py @@ -0,0 +1,143 @@ +#!/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.hw.dart import DART +from m1n1.fw.aop.client import AOPClient +from m1n1.fw.aop.ipc import * + +# aop nodes have no clocks described in adt for j293. it does it itself +p.pmgr_adt_clocks_enable("/arm-io/aop") +p.pmgr_adt_clocks_enable("/arm-io/dart-aop") + +p.usb_iodev_vuart_setup(p.iodev_whoami()) +p.iodev_set_usage(IODEV.USB_VUART, USAGE.UARTPROXY) +pdm2 = u.adt["/arm-io/aop/iop-aop-nub/aop-audio/audio-pdm2"] +decm = u.adt["/arm-io/aop/iop-aop-nub/aop-audio/dc-2400000"] + +dart = DART.from_adt(u, "/arm-io/dart-aop", + iova_range=(u.adt["/arm-io/dart-aop"].vm_base, 0x1000000000)) +dart.initialize() + +aop = AOPClient(u, "/arm-io/aop", dart) +aop.update_bootargs({ + 'p0CE': 0x20000, + 'laCn': 0x0, + 'tPOA': 0x1, + "gila": 0x80, + 'gbda': 0xffffffff, +}) +aop.verbose = 4 + +p.dapf_init_all() +aop.asc.OUTBOX_CTRL.val = 0x20001 # (FIFOCNT=0x0, OVERFLOW=0, EMPTY=1, FULL=0, RPTR=0x0, WPTR=0x0, ENABLE=1) + +pdm2 = u.adt["/arm-io/aop/iop-aop-nub/aop-audio/audio-pdm2"] +decm = u.adt["/arm-io/aop/iop-aop-nub/aop-audio/dc-2400000"] + +pdm_config = Container( + bytesPerSample=pdm2.bytesPerSample, # 2 ?? + clockSource=pdm2.clockSource, # 'pll ' + pdmFrequency=pdm2.pdmFrequency, # 2400000 + pdmcFrequency=pdm2.pdmcFrequency, # 24000000 + slowClockSpeed=pdm2.slowClockSpeed, # 24000000 + fastClockSpeed=pdm2.fastClockSpeed, # 24000000 + channelPolaritySelect=pdm2.channelPolaritySelect, # 256 + channelPhaseSelect=99, # traces say 99 but device tree says 0 pdm2.channelPhaseSelect + unk8=0xf7600, + unk9=0, # this should be latency (thus 15, see below) but traces say 0 + ratios=Container( + r1=decm.ratios.r0, + r2=decm.ratios.r1, + r3=decm.ratios.r2, + ), + filterLengths=decm.filterLengths, + coeff_bulk=120, + coefficients=GreedyRange(Int32sl).parse(decm.coefficients), + unk10=1, + micTurnOnTimeMs=pdm2.micTurnOnTimeMs, # 20 + unk11=1, + micSettleTimeMs=pdm2.micSettleTimeMs, # 50 +) + +decimator_config = Container( + latency=decm.latency, # 15 + ratios=Container( + r1=decm.ratios.r0, # 15 + r2=decm.ratios.r1, # 5 + r3=decm.ratios.r2, # 2 + ), + filterLengths=decm.filterLengths, + coeff_bulk=120, + coefficients=GreedyRange(Int32sl).parse(decm.coefficients), +) + +def main(): + aop.start() + for epno in [0x20, 0x21, 0x22, 0x24, 0x25, 0x26, 0x27, 0x28]: + aop.start_ep(epno) + aop.work_for(0.3) + audep = aop.audio + vtep = aop.voicetrigger + + #ret = aop.spuapp.send_roundtrip("i2c", ALSSetPropertyUnkE4()) + #print(ret) + + audep.send_notify(AudioAttachDevice(devid='pdm0')) + audep.send_notify(AudioAttachDevice(devid='hpai')) + audep.send_notify(AudioAttachDevice(devid='lpai')) + + audep.send_notify(SetDeviceProp(devid='lpai', modifier=301, data=Container(unk1=7, unk2=7, unk3=1, unk4=7))) + audep.send_notify(SetDeviceProp(devid='pdm0', modifier=200, data=pdm_config)) + audep.send_notify(SetDeviceProp(devid='pdm0', modifier=210, data=decimator_config)) + + data = open("data/aop/dump.voicetrigger.2024-01-29T01:49:14.803544.bin", "rb").read() + ret = vtep.send_cmd(0x22, data[:0xa000]) + for n in range(3): + ret = vtep.send_roundtrip(GetPropertyIsReady()) + print("state: %s" % (ret.state)) # cnfg + assert(ret.state == "cnfg") + + #ret = vtep.send_notifycmd(0x24, struct.pack("