Skip to content

Commit

Permalink
asahi_firmware.multitouch: Add M2 MTFW firmware collection
Browse files Browse the repository at this point in the history
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 <marcan@marcan.st>
  • Loading branch information
marcan committed Jul 11, 2022
1 parent 7726254 commit 8f6e5ab
Show file tree
Hide file tree
Showing 3 changed files with 193 additions and 3 deletions.
178 changes: 178 additions & 0 deletions asahi_firmware/multitouch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
# SPDX-License-Identifier: MIT
import xml.etree.ElementTree as ET
import plistlib, base64, struct, os, logging
from .img4 import img4p_extract
from .core import FWFile

log = logging.getLogger("asahi_firmware.multitouch")

def load_plist_xml(d):
root = ET.fromstring(d.decode("ascii"))

idmap = {}
def unmunge(el, idmap):
if "ID" in el.attrib:
idmap[el.attrib["ID"]] = el
if "IDREF" in el.attrib:
return idmap[el.attrib["IDREF"]]
else:
el2 = ET.Element(el.tag)
el2.text = el.text
el2.tag = el.tag
el2.attrib = el.attrib
for child in el:
el2.append(unmunge(child, idmap))

return el2
pl = ET.Element("plist")
pl.append(unmunge(root, idmap))

return plistlib.loads(ET.tostring(pl))

def plist_to_bin(plist):
iface_offset = None

for i in plist:
if i["Type"] == "Config":
for j in i["Config"]["Interface Config"]:
j["bInterfaceNumber"] = None

def serialize(o):
if o is None:
yield None
elif o is True:
yield bytes([0xf5])
elif isinstance(o, dict):
l = len(o)
if l < 0x10:
yield bytes([0xa0 + l])
else:
raise Exception("Unsupported serializer case")
yield b"?"
for k, v in o.items():
yield from serialize(k)
yield from serialize(v)
elif isinstance(o, list):
l = len(o)
if l < 0x18:
yield bytes([0x80 + l])
else:
raise Exception("Unsupported serializer case")
yield b"?"
for v in o:
yield from serialize(v)
elif isinstance(o, str):
o = o.encode("utf-8") + b"\0"
l = len(o)
if l < 0x18:
yield bytes([0x60 + l])
elif l <= 0xff:
yield bytes([0x78, l])
else:
raise Exception("Unsupported serializer case")
yield b"?"
yield o
elif isinstance(o, int):
if o < 0x18:
yield bytes([o])
elif o <= 0xff:
yield bytes([0x18, o])
elif o <= 0xffff:
yield bytes([0x19])
yield struct.pack(">H", o)
else:
yield bytes([0x1a])
yield struct.pack(">I", o)
elif isinstance(o, bytes):
if len(o) <= 0xffff:
yield (4, 3)
yield struct.pack(">BH", 0x59, len(o))
else:
raise Exception("Unsupported serializer case")
yield b"?" + struct.pack(">I", len(o))
yield o
else:
raise Exception("Unsupported serializer case")
yield b"?" + str(type(o)).encode("ascii")

def add_padding(l):
nonlocal iface_offset
off = 0
for b in l:
if b is None:
assert iface_offset is None
iface_offset = off
b = b"\x00"
if isinstance(b, tuple):
align, i = b
if (off + i) % align != 0:
pad = align - ((off + i) % align)
off += pad
yield b"\xd3" * pad
else:
off += len(b)
yield b

blob = b"".join(add_padding(serialize(plist)))

assert iface_offset is not None

hdr = struct.pack("<4sIII", b"HIDF", 1, 32, len(blob))
hdr += struct.pack("<I12x", iface_offset)
assert len(hdr) == 32

return hdr + blob

class MultitouchFWCollection(object):
def __init__(self, source_path):
self.fwfiles = []
self.load(source_path)

def load(self, source_path):
if not os.path.exists(source_path):
#log.warning("fud_firmware is missing. You may need to update your stub with the Asahi Linux installer for Touch Bar functionality.")
return

for fname in os.listdir(source_path):
if fname.startswith("j"):
self.do_machine(fname, os.path.join(source_path, fname))

def do_machine(self, machine, path):
mtfw = os.path.join(path, "Multitouch.im4p")
if not os.path.exists(mtfw):
return

log.info(f"Processing {machine}")

with open(mtfw, "rb") as fd:
im4p = fd.read()

name, xml = img4p_extract(im4p)

assert name == "mtfw"

plist = load_plist_xml(xml.rstrip(b"\x00"))

collected = set()
for key, val in plist.items():
# Touchpad firmwares only for now
if not key.startswith("C1FD0"):
log.info(f" Skipping {key}")
continue

log.info(f" Collecting {key}")
filename = f"apple/tpmtfw-{machine}.bin"

if filename in collected:
raise Exception(f"Tried to collect firmware {filename} twice!")

data = plist_to_bin(val)
fw = FWFile(filename, data)
self.fwfiles.append((filename, fw))

collected.add(filename)
log.info(f" Collected {key} as {filename}")

def files(self):
return self.fwfiles

6 changes: 6 additions & 0 deletions asahi_firmware/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from .core import FWPackage
from .wifi import WiFiFWCollection
from .bluetooth import BluetoothFWCollection
from .multitouch import MultitouchFWCollection

def update_firmware(source, dest, manifest):
raw_fw = source.joinpath("all_firmware.tar.gz")
Expand All @@ -15,11 +16,16 @@ def update_firmware(source, dest, manifest):
with tempfile.TemporaryDirectory() as tmpdir:
tmpdir = pathlib.Path(tmpdir)
subprocess.run(["tar", "xf", str(raw_fw.resolve())], cwd=tmpdir, check=True)

col = WiFiFWCollection(str(tmpdir.joinpath("firmware", "wifi")))
pkg.add_files(sorted(col.files()))

col = BluetoothFWCollection(str(tmpdir.joinpath("firmware", "bluetooth")))
pkg.add_files(sorted(col.files()))

col = MultitouchFWCollection(str(tmpdir.joinpath("fud_firmware")))
pkg.add_files(sorted(col.files()))

pkg.close()

pkg.save_manifest(manifest)
Expand Down
12 changes: 9 additions & 3 deletions src/stub.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# SPDX-License-Identifier: MIT
import os, os.path, plistlib, shutil, sys, stat, subprocess, urlcache, zipfile, logging, json
import osenum, asahi_firmware.wifi, asahi_firmware.bluetooth
import osenum
from asahi_firmware.wifi import WiFiFWCollection
from asahi_firmware.bluetooth import BluetoothFWCollection
from asahi_firmware.multitouch import MultitouchFWCollection
from util import *

class StubInstaller(PackageInstaller):
Expand Down Expand Up @@ -322,10 +325,13 @@ def collect_firmware(self, pkg):
subprocess.run(["hdiutil", "attach", "-quiet", "-readonly", "-mountpoint", "recovery", img],
check=True)
logging.info("Collecting WiFi firmware")
col = asahi_firmware.wifi.WiFiFWCollection("recovery/usr/share/firmware/wifi/")
col = WiFiFWCollection("recovery/usr/share/firmware/wifi/")
pkg.add_files(sorted(col.files()))
logging.info("Collecting Bluetooth firmware")
col = asahi_firmware.bluetooth.BluetoothFWCollection("recovery/usr/share/firmware/bluetooth/")
col = BluetoothFWCollection("recovery/usr/share/firmware/bluetooth/")
pkg.add_files(sorted(col.files()))
logging.info("Collecting Multitouch firmware")
col = MultitouchFWCollection("fud_firmware/")
pkg.add_files(sorted(col.files()))
logging.info("Making fallback firmware archive")
subprocess.run(["tar", "czf", "all_firmware.tar.gz",
Expand Down

0 comments on commit 8f6e5ab

Please sign in to comment.