summaryrefslogtreecommitdiff
path: root/tools/proxyclient/m1n1/adt.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/proxyclient/m1n1/adt.py')
-rw-r--r--tools/proxyclient/m1n1/adt.py713
1 files changed, 713 insertions, 0 deletions
diff --git a/tools/proxyclient/m1n1/adt.py b/tools/proxyclient/m1n1/adt.py
new file mode 100644
index 0000000..8cc7ebe
--- /dev/null
+++ b/tools/proxyclient/m1n1/adt.py
@@ -0,0 +1,713 @@
+# SPDX-License-Identifier: MIT
+import itertools, fnmatch, sys
+from construct import *
+import sys
+
+from .utils import AddrLookup, FourCC, SafeGreedyRange
+
+__all__ = ["load_adt"]
+
+ADTPropertyStruct = Struct(
+ "name" / PaddedString(32, "ascii"),
+ "size" / Int32ul,
+ "value" / Bytes(this.size & 0x7fffffff)
+)
+
+ADTNodeStruct = Struct(
+ "property_count" / Int32ul,
+ "child_count" / Int32ul,
+ "properties" / Array(this.property_count, Aligned(4, ADTPropertyStruct)),
+ "children" / Array(this.child_count, LazyBound(lambda: ADTNodeStruct))
+)
+
+ADTStringList = SafeGreedyRange(CString("ascii"))
+
+ADT2Tuple = Array(2, Hex(Int64ul))
+ADT3Tuple = Array(3, Hex(Int64ul))
+
+Function = Struct(
+ "phandle" / Int32ul,
+ "name" / FourCC,
+ "args" / SafeGreedyRange(Int32ul),
+)
+
+STD_PROPERTIES = {
+ "cpu-impl-reg": ADT2Tuple,
+ "name": CString("ascii"),
+ "compatible": ADTStringList,
+ "model": CString("ascii"),
+ "#size-cells": Int32ul,
+ "#address-cells": Int32ul,
+ "clock-ids": SafeGreedyRange(Int32ul),
+ "clock-gates": SafeGreedyRange(Int32ul),
+ "power-gates": SafeGreedyRange(Int32ul),
+}
+
+PMAPIORanges = SafeGreedyRange(Struct(
+ "addr" / Hex(Int64ul),
+ "size" / Hex(Int64ul),
+ "flags" / Hex(Int32ul),
+ "name" / FourCC,
+))
+
+PMGRPSRegs = SafeGreedyRange(Struct(
+ "reg" / Int32ul,
+ "offset" / Hex(Int32ul),
+ "mask" / Hex(Int32ul),
+))
+
+PMGRPerfRegs = SafeGreedyRange(Struct(
+ "reg" / Int32ul,
+ "offset" / Hex(Int32ul),
+ "size" / Hex(Int32ul),
+ "unk" / Int32ul,
+))
+
+PMGRPWRGateRegs = SafeGreedyRange(Struct(
+ "reg" / Int32ul,
+ "offset" / Hex(Int32ul),
+ "mask" / Hex(Int32ul),
+ "unk" / Hex(Int32ul),
+))
+
+PMGRDeviceFlags = BitStruct(
+ "b7" / Flag,
+ "b6" / Flag,
+ "perf" / Flag,
+ "no_ps" / Flag,
+ "critical" / Flag,
+ "b2" / Flag,
+ "notify_pmp" / Flag,
+ "on" / Flag,
+)
+
+PMGRDevices = SafeGreedyRange(Struct(
+ "flags" / PMGRDeviceFlags,
+ "unk1_0" / Int8ul,
+ "unk1_1" / Int8ul,
+ "unk1_2" / Int8ul,
+ "parents" / Array(2, Int16ul),
+ "perf_idx" / Int8ul,
+ "perf_block" / Int8ul,
+ "psidx" / Int8ul,
+ "psreg" / Int8ul,
+ "unk2_0" / Int16ul,
+ "pd" / Int8ul,
+ "ps_cfg16" / Int8ul,
+ Const(0, Int32ul),
+ Const(0, Int32ul),
+ "unk2_3" / Int16ul,
+ "id" / Int16ul,
+ "unk3" / Int32ul,
+ "name" / PaddedString(16, "ascii")
+))
+
+PMGRClocks = SafeGreedyRange(Struct(
+ "perf_idx" / Int8ul,
+ "perf_block" / Int8ul,
+ "unk" / Int8ul,
+ "id" / Int8ul,
+ Const(0, Int32ul),
+ "name" / PaddedString(16, "ascii"),
+))
+
+PMGRPowerDomains = SafeGreedyRange(Struct(
+ "unk" / Const(0, Int8ul),
+ "perf_idx" / Int8ul,
+ "perf_block" / Int8ul,
+ "id" / Int8ul,
+ Const(0, Int32ul),
+ "name" / PaddedString(16, "ascii"),
+))
+
+PMGRDeviceBridges = SafeGreedyRange(Struct(
+ "idx" / Int32ub,
+ "subdevs" / HexDump(Bytes(0x48)),
+))
+
+PMGREvents = SafeGreedyRange(Struct(
+ "unk1" / Int8ul,
+ "unk2" / Int8ul,
+ "unk3" / Int8ul,
+ "id" / Int8ul,
+ "perf2_idx" / Int8ul,
+ "perf2_block" / Int8ul,
+ "perf_idx" / Int8ul,
+ "perf_block" / Int8ul,
+ "name" / PaddedString(16, "ascii"),
+))
+
+GPUPerfState = Struct(
+ "freq" / Int32ul,
+ "volt" / Int32ul,
+)
+
+SpeakerConfig = Struct(
+ "rx_slot" / Int8ul,
+ "amp_gain" / Int8ul,
+ "vsense_slot" / Int8ul,
+ "isense_slot" / Int8ul,
+)
+
+DCBlockerConfig = Struct(
+ "dc_blk0" / Hex(Int8ul),
+ "dc_blk1" / Hex(Int8ul),
+ "pad" / Hex(Int16ul),
+)
+
+Coef = ExprAdapter(Int32ul,
+ lambda x, ctx: (x - ((x & 0x1000000) << 1)) / 65536,
+ lambda x, ctx: int(round(x * 65536)) & 0x1ffffff)
+
+MTRPolynomFuseAGX = GreedyRange(Struct(
+ "id" / Int32ul,
+ "data" / Prefixed(Int32ul, GreedyRange(Coef)),
+))
+
+DEV_PROPERTIES = {
+ "pmgr": {
+ "*": {
+ "clusters": SafeGreedyRange(Int32ul),
+ "devices": PMGRDevices,
+ "ps-regs": PMGRPSRegs,
+ "perf-regs": PMGRPerfRegs,
+ "pwrgate-regs": PMGRPWRGateRegs,
+ "power-domains": PMGRPowerDomains,
+ "clocks": PMGRClocks,
+ "device-bridges": PMGRDeviceBridges,
+ "voltage-states*": SafeGreedyRange(Int32ul),
+ "events": PMGREvents,
+ "mtr-polynom-fuse-agx": MTRPolynomFuseAGX,
+ }
+ },
+ "clpc": {
+ "*": {
+ "events": SafeGreedyRange(Int32ul),
+ "devices": SafeGreedyRange(Int32ul),
+ }
+ },
+ "soc-tuner": {
+ "*": {
+ "device-set-*": SafeGreedyRange(Int32ul),
+ "mcc-configs": SafeGreedyRange(Int32ul),
+ }
+ },
+ "mcc": {
+ "*": {
+ "dramcfg-data": SafeGreedyRange(Int32ul),
+ "config-data": SafeGreedyRange(Int32ul),
+ }
+ },
+ "*pmu*": {
+ "*": {
+ "info-*name*": CString("ascii"),
+ "info-*": SafeGreedyRange(Hex(Int32ul)),
+ },
+ },
+ "stockholm-spmi": {
+ "*": {
+ "required-functions": ADTStringList,
+ },
+ },
+ "sgx": {
+ "*": {
+ "perf-states*": SafeGreedyRange(GPUPerfState),
+ "*-kp": Float32l,
+ "*-ki": Float32l,
+ "*-ki-*": Float32l,
+ "*-gain*": Float32l,
+ "*-scale*": Float32l,
+ }
+ },
+ "arm-io": {
+ "*": {
+ "clock-frequencies": SafeGreedyRange(Int32ul),
+ "clock-frequencies-regs": SafeGreedyRange(Hex(Int64ul)),
+ "clock-frequencies-nclk": SafeGreedyRange(Int32ul),
+ },
+ },
+ "defaults": {
+ "*": {
+ "pmap-io-ranges": PMAPIORanges,
+ }
+ },
+ "audio-*": {
+ "*": {
+ "speaker-config": SafeGreedyRange(SpeakerConfig),
+ "amp-dcblocker-config": DCBlockerConfig,
+ },
+ },
+ "*aop-audio*": {
+ "*": {
+ "clockSource": FourCC,
+ "identifier": FourCC,
+ },
+ },
+ "*alc?/audio-leap-mic*": {
+ "*": {
+ "audio-stream-formatter": FourCC,
+ }
+ }
+}
+
+def parse_prop(node, path, node_name, name, v, is_template=False):
+ t = None
+
+ if is_template:
+ t = CString("ascii")
+
+ dev_props = None
+ for k, pt in DEV_PROPERTIES.items():
+ if fnmatch.fnmatch(path, k):
+ dev_props = pt
+ break
+
+ if not dev_props:
+ for k, pt in DEV_PROPERTIES.items():
+ if fnmatch.fnmatch(node_name, k):
+ dev_props = pt
+ break
+
+ possible_match = False
+ if dev_props:
+ for compat_match, cprops in dev_props.items():
+ for k, pt in cprops.items():
+ if fnmatch.fnmatch(name, k):
+ possible_match = True
+ break
+
+ if possible_match:
+ try:
+ compat = node.compatible[0]
+ except AttributeError:
+ compat = ""
+
+ for compat_match, cprops in dev_props.items():
+ if fnmatch.fnmatch(compat, compat_match):
+ for k, pt in cprops.items():
+ if fnmatch.fnmatch(name, k):
+ t = pt
+ break
+ else:
+ continue
+ break
+
+ if v == b'' or v is None:
+ return None, None
+
+ if name.startswith("function-"):
+ if len(v) == 4:
+ t = FourCC
+ else:
+ t = Function
+
+ if name == "reg" and path != "/device-tree/memory":
+ n = node._parent
+ while n is not None and n._parent is not None:
+ if "ranges" not in n._properties:
+ break
+ n = n._parent
+ else:
+ rs = node._reg_struct
+ if len(v) % rs.sizeof() == 0:
+ t = SafeGreedyRange(rs)
+
+ elif name == "ranges":
+ try:
+ ac, sc = node.address_cells, node.size_cells
+ except AttributeError:
+ return None, v
+ pac, _ = node._parent.address_cells, node._parent.size_cells
+ at = Hex(Int64ul) if ac == 2 else Array(ac, Hex(Int32ul))
+ pat = Hex(Int64ul) if pac == 2 else Array(pac, Hex(Int32ul))
+ st = Hex(Int64ul) if sc == 2 else Array(sc, Hex(Int32ul))
+ t = SafeGreedyRange(Struct("bus_addr" / at, "parent_addr" / pat, "size" / st))
+
+ elif name == "interrupts":
+ # parse "interrupts" as Array of Int32ul, wrong for nodes whose
+ # "interrupt-parent" has "interrupt-cells" = 2
+ # parsing this correctly would require a second pass
+ t = Array(len(v) // 4, Int32ul)
+
+ if t is not None:
+ v = Sequence(t, Terminated).parse(v)[0]
+ return t, v
+
+ if name in STD_PROPERTIES:
+ t = STD_PROPERTIES[name]
+ elif v and v[-1] == 0 and all(0x20 <= i <= 0x7e for i in v[:-1]):
+ t = CString("ascii")
+ elif len(v) == 4:
+ t = Int32ul
+ elif len(v) == 8:
+ t = Int64ul
+ elif len(v) == 16 and all(v[i] == 0 for i in (6, 7, 14, 15)):
+ t = ADT2Tuple
+
+ if t is not None:
+ try:
+ v = Sequence(t, Terminated).parse(v)[0]
+ except:
+ print("Failed to parse:", path, name, v.hex())
+ raise
+
+ return t, v
+
+def build_prop(path, name, v, t=None):
+ if v is None:
+ return b''
+ if t is not None:
+ return t.build(v)
+
+ if isinstance(v, bytes):
+ return v
+
+ if name in STD_PROPERTIES:
+ t = STD_PROPERTIES[name]
+ elif isinstance(v, str):
+ t = CString("ascii")
+ elif isinstance(v, int):
+ if v > 0xffffffff:
+ t = Int64ul
+ else:
+ t = Int32ul
+ elif isinstance(v, float):
+ t = Float32l
+ elif isinstance(v, tuple) and all(isinstance(i, int) for i in v):
+ t = Array(len(v), Int32ul)
+
+ return t.build(v)
+
+class ADTNode:
+ def __init__(self, val=None, path="/", parent=None):
+ self._children = []
+ self._properties = {}
+ self._types = {}
+ self._parent_path = path
+ self._parent = parent
+
+ if val is not None:
+ for p in val.properties:
+ if p.name == "name":
+ _name = p.value.decode("ascii").rstrip("\0")
+ break
+ else:
+ raise ValueError(f"Node in {path} has no name!")
+
+ path = self._parent_path + _name
+
+ for p in val.properties:
+ is_template = bool(p.size & 0x80000000)
+ try:
+ t, v = parse_prop(self, path, _name, p.name, p.value, is_template)
+ self._types[p.name] = t, is_template
+ self._properties[p.name] = v
+ except Exception as e:
+ print(f"Exception parsing {path}.{p.name} value {p.value.hex()}:", file=sys.stderr)
+ raise
+
+ # Second pass
+ for k, (t, is_template) in self._types.items():
+ if t is None:
+ t, v = parse_prop(self, path, _name, k, self._properties[k], is_template)
+ self._types[k] = t, is_template
+ self._properties[k] = v
+
+ for c in val.children:
+ node = ADTNode(c, f"{self._path}/", parent=self)
+ self._children.append(node)
+
+ @property
+ def _path(self):
+ return self._parent_path + self.name
+
+ def __getitem__(self, item):
+ if isinstance(item, str):
+ while item.startswith("/"):
+ item = item[1:]
+ if "/" in item:
+ a, b = item.split("/", 1)
+ return self[a][b]
+ for i in self._children:
+ if i.name == item:
+ return i
+ raise KeyError(f"Child node '{item}' not found")
+ return self._children[item]
+
+ def __setitem__(self, item, value):
+ if isinstance(item, str):
+ while item.startswith("/"):
+ item = item[1:]
+ if "/" in item:
+ a, b = item.split("/", 1)
+ self[a][b] = value
+ return
+ for i, c in enumerate(self._children):
+ if c.name == item:
+ self._children[i] = value
+ break
+ else:
+ self._children.append(value)
+ else:
+ self._children[item] = value
+
+ def __delitem__(self, item):
+ if isinstance(item, str):
+ while item.startswith("/"):
+ item = item[1:]
+ if "/" in item:
+ a, b = item.split("/", 1)
+ del self[a][b]
+ return
+ for i, c in enumerate(self._children):
+ if c.name == item:
+ del self._children[i]
+ return
+ raise KeyError(f"Child node '{item}' not found")
+
+ del self._children[item]
+
+ def __contains__(self, item):
+ if isinstance(item, str):
+ while item.startswith("/"):
+ item = item[1:]
+ if "/" in item:
+ a, b = item.split("/", 1)
+ return b in self[a]
+ for c in self._children:
+ if c.name == item:
+ return True
+ return False
+
+ return item in self._children
+
+ def __getattr__(self, attr):
+ attr = attr.replace("_", "-")
+ attr = attr.replace("--", "_")
+ if attr in self._properties:
+ return self._properties[attr]
+ raise AttributeError(attr)
+
+ def __setattr__(self, attr, value):
+ if attr[0] == "_":
+ self.__dict__[attr] = value
+ return
+ attr = attr.replace("_", "-")
+ attr = attr.replace("--", "_")
+ self._properties[attr] = value
+
+ def __delattr__(self, attr):
+ if attr[0] == "_":
+ del self.__dict__[attr]
+ return
+ del self._properties[attr]
+
+ def getprop(self, name, default=None):
+ return self._properties.get(name, default)
+
+ @property
+ def address_cells(self):
+ try:
+ return self._properties["#address-cells"]
+ except KeyError:
+ raise AttributeError("#address-cells")
+
+ @property
+ def size_cells(self):
+ try:
+ return self._properties["#size-cells"]
+ except KeyError:
+ raise AttributeError("#size-cells")
+
+ @property
+ def interrupt_cells(self):
+ try:
+ return self._properties["#interrupt-cells"]
+ except KeyError:
+ raise AttributeError("#interrupt-cells")
+
+ def _fmt_prop(self, k, v):
+ t, is_template = self._types.get(k, (None, False))
+ if is_template:
+ return f"<< {v} >>"
+ elif isinstance(v, ListContainer):
+ return f"[{', '.join(self._fmt_prop(k, i) for i in v)}]"
+ elif isinstance(v, bytes):
+ if all(i == 0 for i in v):
+ return f"zeroes({len(v):#x})"
+ else:
+ return v.hex()
+ elif k.startswith("function-"):
+ if isinstance(v, str):
+ return f"{v}()"
+ elif v is None:
+ return f"None"
+ else:
+ args = []
+ for arg in v.args:
+ b = arg.to_bytes(4, "big")
+ is_ascii = all(0x20 <= c <= 0x7e for c in b)
+ args.append(f"{arg:#x}" if not is_ascii else f"'{b.decode('ascii')}'")
+ return f"{v.phandle}:{v.name}({', '.join(args)})"
+ name.startswith("function-")
+ else:
+ return str(v)
+
+ def __str__(self, t=""):
+ return "\n".join([
+ t + f"{self.name} {{",
+ *(t + f" {k} = {self._fmt_prop(k, v)}" for k, v in self._properties.items() if k != "name"),
+ "",
+ *(i.__str__(t + " ") for i in self._children),
+ t + "}"
+ ])
+
+ def __repr__(self):
+ return f"<ADTNode {self.name}>"
+
+ def __iter__(self):
+ return iter(self._children)
+
+ @property
+ def _reg_struct(self):
+ ac, sc = self._parent.address_cells, self._parent.size_cells
+ return Struct(
+ "addr" / Hex(Int64ul) if ac == 2 else Array(ac, Hex(Int32ul)),
+ "size" / Hex(Int64ul) if sc == 2 else Array(sc, Hex(Int32ul))
+ )
+
+ def get_reg(self, idx):
+ reg = self.reg[idx]
+ addr = reg.addr
+ size = reg.size
+
+ return self._parent.translate(addr), size
+
+ def translate(self, addr):
+ node = self
+ while node is not None:
+ if "ranges" not in node._properties:
+ break
+ for r in node.ranges:
+ ba = r.bus_addr
+ # PCIe special case, because Apple really broke
+ # the spec here with their little endian antics
+ if isinstance(ba, list) and len(ba) == 3:
+ ba = (ba[0] << 64) | (ba[2] << 32) | ba[1]
+ if ba <= addr < (ba + r.size):
+ addr = addr - ba + r.parent_addr
+ break
+ node = node._parent
+
+ return addr
+
+ def to_bus_addr(self, addr):
+ node = self._parent
+
+ descend = []
+ while node is not None:
+ if "ranges" not in node._properties:
+ break
+ descend.append(node)
+ node = node._parent
+
+ for node in reversed(descend):
+ for r in node.ranges:
+ if r.parent_addr <= addr < (r.parent_addr + r.size):
+ addr = addr - r.parent_addr + r.bus_addr
+ break
+ return addr
+
+ def tostruct(self):
+ properties = []
+ for k,v in itertools.chain(self._properties.items()):
+ t, is_template = self._types.get(k, (None, False))
+ value = build_prop(self._path, k, v, t=t)
+ properties.append({
+ "name": k,
+ "size": len(value) | (0x80000000 if is_template else 0),
+ "value": value
+ })
+
+ data = {
+ "property_count": len(self._properties),
+ "child_count": len(self._children),
+ "properties": properties,
+ "children": [c.tostruct() for c in self._children]
+ }
+ return data
+
+ def build(self):
+ return ADTNodeStruct.build(self.tostruct())
+
+ def walk_tree(self):
+ yield self
+ for child in self:
+ yield from child
+
+ def build_addr_lookup(self):
+ lookup = AddrLookup()
+ for node in self.walk_tree():
+ reg = getattr(node, 'reg', None)
+ if not isinstance(reg, list):
+ continue
+
+ for index in range(len(reg)):
+ try:
+ addr, size = node.get_reg(index)
+ except AttributeError:
+ continue
+ if size == 0:
+ continue
+ lookup.add(range(addr, addr + size), node.name + f"[{index}]")
+
+ return lookup
+
+ def create_node(self, name):
+ while name.startswith("/"):
+ name = name[1:]
+ if "/" in name:
+ a, b = name.split("/", 1)
+ return self[a].create_node(b)
+
+ node = ADTNode(path=self._path + "/", parent=self)
+ node.name = name
+ node._types["reg"] = (SafeGreedyRange(node._reg_struct), False)
+ self[name] = node
+ return node
+
+def load_adt(data):
+ return ADTNode(ADTNodeStruct.parse(data))
+
+if __name__ == "__main__":
+ import sys, argparse, pathlib
+
+ parser = argparse.ArgumentParser(description='ADT test for m1n1')
+ parser.add_argument('input', type=pathlib.Path)
+ parser.add_argument('output', nargs='?', type=pathlib.Path)
+ parser.add_argument('-r', '--retrieve', help='retrieve and store the adt from m1n1', action='store_true')
+ parser.add_argument('-a', '--dump-addr', help='dump address lookup table', action='store_true')
+ args = parser.parse_args()
+
+ if args.retrieve:
+ if args.input.exists():
+ print('Error "{}" exists!'.format(args.input))
+ sys.exit()
+
+ from .setup import *
+ adt_data = u.get_adt()
+ args.input.write_bytes(adt_data)
+ else:
+ adt_data = args.input.read_bytes()
+
+ adt = load_adt(adt_data)
+ print(adt)
+ new_data = adt.build()
+ if args.output is not None:
+ args.output.write_bytes(new_data)
+ assert new_data == adt_data[:len(new_data)]
+ assert adt_data[len(new_data):] == bytes(len(adt_data) - len(new_data))
+
+ if args.dump_addr:
+ print("Address lookup table:")
+ print(adt.build_addr_lookup())