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/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(" (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.block_size)) % 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.block_size * 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.block_size) % self.bufsize self.update_wptr(self.wptr) return self.wptr @@ -150,14 +186,33 @@ 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 + size = msg.SIZE * AFKRingBuf.BLOCK_STEP if self.iobuffer: print("WARNING: trying to reset iobuffer!") @@ -168,18 +223,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 +237,17 @@ def InitRX(self, msg): self.start_queues() return True - def init_rb(self, msg): - off = msg.OFFSET * AFKRingBuf.BLOCK_SIZE - size = msg.SIZE * AFKRingBuf.BLOCK_SIZE + @msg_handler(0x8c, AFKEP_InitRB) + def InitUnk(self, msg): + return True # no op + def init_rb(self, msg): + off = msg.OFFSET * AFKRingBuf.BLOCK_STEP + size = msg.SIZE * AFKRingBuf.BLOCK_STEP 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 +265,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)) 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/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/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) 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 diff --git a/proxyclient/m1n1/fw/aop/ipc.py b/proxyclient/m1n1/fw/aop/ipc.py index d26315d1d..a83f04790 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 @@ -54,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)})") @@ -85,12 +73,120 @@ class GetProperty(EPICCall): TYPE = 0xa ARGS = Struct( "blank" / Const(0x0, Int32ul), - "key" / Enum(Int32ul, AOPAudioPropKey), + "key" / Enum(Int32ul, AOPPropKey), + ) + RETS = Struct( + #"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( - #"blank" / Const(0x0, Int32ul), + "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): @@ -130,6 +226,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 @@ -148,45 +268,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 +356,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, @@ -262,6 +378,96 @@ class ProbeDevice(WrappedCall): ), } +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 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