Skip to content

Commit 93011fc

Browse files
committed
asahi_firmware.multitouch: Add M2 MTFW firmware collection
While this code technically works for the Touch Bar firmware too, it seems quite likely that the kernel will need a different representation than this serialized plist nonsense for that, so for now this is limited to M2 trackpad multitouch firmware. Signed-off-by: Hector Martin <[email protected]>
1 parent 7726254 commit 93011fc

3 files changed

Lines changed: 187 additions & 0 deletions

File tree

asahi_firmware/multitouch.py

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
# SPDX-License-Identifier: MIT
2+
import xml.etree.ElementTree as ET
3+
import plistlib, base64, struct, os, logging
4+
from .img4 import img4p_extract
5+
from .core import FWFile
6+
7+
log = logging.getLogger("asahi_firmware.multitouch")
8+
9+
def load_plist_xml(d):
10+
root = ET.fromstring(d.decode("ascii"))
11+
12+
idmap = {}
13+
def unmunge(el, idmap):
14+
if "ID" in el.attrib:
15+
idmap[el.attrib["ID"]] = el
16+
if "IDREF" in el.attrib:
17+
return idmap[el.attrib["IDREF"]]
18+
else:
19+
el2 = ET.Element(el.tag)
20+
el2.text = el.text
21+
el2.tag = el.tag
22+
el2.attrib = el.attrib
23+
for child in el:
24+
el2.append(unmunge(child, idmap))
25+
26+
return el2
27+
pl = ET.Element("plist")
28+
pl.append(unmunge(root, idmap))
29+
30+
return plistlib.loads(ET.tostring(pl))
31+
32+
def plist_to_bin(plist):
33+
iface_offset = None
34+
35+
for i in plist:
36+
if i["Type"] == "Config":
37+
for j in i["Config"]["Interface Config"]:
38+
j["bInterfaceNumber"] = None
39+
40+
def serialize(o):
41+
if o is None:
42+
yield None
43+
elif o is True:
44+
yield bytes([0xf5])
45+
elif isinstance(o, dict):
46+
l = len(o)
47+
if l < 0x10:
48+
yield bytes([0xa0 + l])
49+
else:
50+
raise Exception("Unsupported serializer case")
51+
yield b"?"
52+
for k, v in o.items():
53+
yield from serialize(k)
54+
yield from serialize(v)
55+
elif isinstance(o, list):
56+
l = len(o)
57+
if l < 0x18:
58+
yield bytes([0x80 + l])
59+
else:
60+
raise Exception("Unsupported serializer case")
61+
yield b"?"
62+
for v in o:
63+
yield from serialize(v)
64+
elif isinstance(o, str):
65+
o = o.encode("utf-8") + b"\0"
66+
l = len(o)
67+
if l < 0x18:
68+
yield bytes([0x60 + l])
69+
elif l <= 0xff:
70+
yield bytes([0x78, l])
71+
else:
72+
raise Exception("Unsupported serializer case")
73+
yield b"?"
74+
yield o
75+
elif isinstance(o, int):
76+
if o < 0x18:
77+
yield bytes([o])
78+
elif o <= 0xff:
79+
yield bytes([0x18, o])
80+
elif o <= 0xffff:
81+
yield bytes([0x19])
82+
yield struct.pack(">H", o)
83+
else:
84+
yield bytes([0x1a])
85+
yield struct.pack(">I", o)
86+
elif isinstance(o, bytes):
87+
if len(o) <= 0xffff:
88+
yield (4, 3)
89+
yield struct.pack(">BH", 0x59, len(o))
90+
else:
91+
raise Exception("Unsupported serializer case")
92+
yield b"?" + struct.pack(">I", len(o))
93+
yield o
94+
else:
95+
raise Exception("Unsupported serializer case")
96+
yield b"?" + str(type(o)).encode("ascii")
97+
98+
def add_padding(l):
99+
nonlocal iface_offset
100+
off = 0
101+
for b in l:
102+
if b is None:
103+
assert iface_offset is None
104+
iface_offset = off
105+
b = b"\x00"
106+
if isinstance(b, tuple):
107+
align, i = b
108+
if (off + i) % align != 0:
109+
pad = align - ((off + i) % align)
110+
off += pad
111+
yield b"\xd3" * pad
112+
else:
113+
off += len(b)
114+
yield b
115+
116+
blob = b"".join(add_padding(serialize(plist)))
117+
118+
assert iface_offset is not None
119+
120+
hdr = struct.pack("<4sIII", b"HIDF", 1, 32, len(blob))
121+
hdr += struct.pack("<I12x", iface_offset)
122+
assert len(hdr) == 32
123+
124+
return hdr + blob
125+
126+
class MultitouchFWCollection(object):
127+
def __init__(self, source_path):
128+
self.fwfiles = []
129+
self.load(source_path)
130+
131+
def load(self, source_path):
132+
if not os.path.exists(source_path):
133+
#log.warning("fud_firmware is missing. You may need to update your stub with the Asahi Linux installer for Touch Bar functionality.")
134+
return
135+
136+
for fname in os.listdir(source_path):
137+
if fname.startswith("j"):
138+
self.do_machine(fname, os.path.join(source_path, fname))
139+
140+
def do_machine(self, machine, path):
141+
mtfw = os.path.join(path, "Multitouch.im4p")
142+
if not os.path.exists(mtfw):
143+
return
144+
145+
log.info(f"Processing {machine}")
146+
147+
with open(mtfw, "rb") as fd:
148+
im4p = fd.read()
149+
150+
name, xml = img4p_extract(im4p)
151+
152+
assert name == "mtfw"
153+
154+
plist = load_plist_xml(xml.rstrip(b"\x00"))
155+
156+
collected = set()
157+
for key, val in plist.items():
158+
# Touchpad firmwares only for now
159+
if not key.startswith("C1FD0"):
160+
log.info(f" Skipping {key}")
161+
continue
162+
163+
log.info(f" Collecting {key}")
164+
filename = f"apple/tpmtfw-{machine}.bin"
165+
166+
if filename in collected:
167+
raise Exception(f"Tried to collect firmware {filename} twice!")
168+
169+
data = plist_to_bin(val)
170+
fw = FWFile(filename, data)
171+
self.fwfiles.append((filename, fw))
172+
173+
collected.add(filename)
174+
log.info(f" Collected {key} as {filename}")
175+
176+
def files(self):
177+
return self.fwfiles
178+

asahi_firmware/update.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from .core import FWPackage
55
from .wifi import WiFiFWCollection
66
from .bluetooth import BluetoothFWCollection
7+
from .multitouch import MultitouchFWCollection
78

89
def update_firmware(source, dest, manifest):
910
raw_fw = source.joinpath("all_firmware.tar.gz")
@@ -15,11 +16,16 @@ def update_firmware(source, dest, manifest):
1516
with tempfile.TemporaryDirectory() as tmpdir:
1617
tmpdir = pathlib.Path(tmpdir)
1718
subprocess.run(["tar", "xf", str(raw_fw.resolve())], cwd=tmpdir, check=True)
19+
1820
col = WiFiFWCollection(str(tmpdir.joinpath("firmware", "wifi")))
1921
pkg.add_files(sorted(col.files()))
22+
2023
col = BluetoothFWCollection(str(tmpdir.joinpath("firmware", "bluetooth")))
2124
pkg.add_files(sorted(col.files()))
2225

26+
col = MultitouchFWCollection(str(tmpdir.joinpath("fud_firmware")))
27+
pkg.add_files(sorted(col.files()))
28+
2329
pkg.close()
2430

2531
pkg.save_manifest(manifest)

src/stub.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,9 @@ def collect_firmware(self, pkg):
327327
logging.info("Collecting Bluetooth firmware")
328328
col = asahi_firmware.bluetooth.BluetoothFWCollection("recovery/usr/share/firmware/bluetooth/")
329329
pkg.add_files(sorted(col.files()))
330+
logging.info("Collecting Multitouch firmware")
331+
col = asahi_firmware.multitouch.MultitouchFWCollection("fud_firmware/")
332+
pkg.add_files(sorted(col.files()))
330333
logging.info("Making fallback firmware archive")
331334
subprocess.run(["tar", "czf", "all_firmware.tar.gz",
332335
"fud_firmware",

0 commit comments

Comments
 (0)