mirror of
https://github.com/AsahiLinux/m1n1
synced 2024-11-10 09:44:13 +00:00
hv/trace_mesa.py: add mesa tracer
Mesa is Apple's codename for the TouchID sensor. On M1-based systems, it is connected to the SPI bus and communicates via SIO on DMA channels 0x18 and 0x19. The application processors seem to have very little to do with its operation. After power on, the command buffer is encrypted by the SEP and very little useful data can be gleaned from snooping the SIO messages. While the commands are garbled by the SEP, we can see that it has a few recurring themes: * A power on routine involving some sort of calibration, perhaps to get a noise image to subtract from each fingerprint * A polling mode where it is kicked by the kernel and acks if there's no finger on the sensor (runs while macOS waits for a print) * A data transfer mode, where a SIO message is sent to an unmapped EP and the fingerprint scanned into memory. Likely triggered by an interrupt coming off the finger detection ring, but I haven't been able to verify this. Signed-off-by: James Calligeros <jcalligeros99@gmail.com>
This commit is contained in:
parent
66a85593db
commit
e2d671d597
1 changed files with 218 additions and 0 deletions
218
proxyclient/hv/trace_mesa.py
Normal file
218
proxyclient/hv/trace_mesa.py
Normal file
|
@ -0,0 +1,218 @@
|
|||
# SPDX-License-Identifier: MIT
|
||||
"""
|
||||
Things to note:
|
||||
The command buffer is encrypted after the poweron sequence, and I
|
||||
can't find the key in the SEP using sven's old SEP tracer.
|
||||
"""
|
||||
import struct
|
||||
from construct import *
|
||||
|
||||
from m1n1.hv import TraceMode
|
||||
from m1n1.proxyutils import RegMonitor
|
||||
from m1n1.utils import *
|
||||
|
||||
from m1n1.trace import ADTDevTracer
|
||||
from m1n1.trace.asc import ASCTracer, ASCRegs, EP, EPState, msg, msg_log, DIR
|
||||
from m1n1.trace.dart import DARTTracer
|
||||
from m1n1.trace.gpio import GPIOTracer
|
||||
from m1n1.trace.spi import SPITracer
|
||||
|
||||
DARTTracer = DARTTracer._reloadcls()
|
||||
ASCTracer = ASCTracer._reloadcls()
|
||||
GPIOTracer = GPIOTracer._reloadcls()
|
||||
SPITracer = SPITracer._reloadcls()
|
||||
|
||||
mesa_node = None
|
||||
for node in hv.adt.walk_tree():
|
||||
try:
|
||||
if node.compatible[0] == "spi-1,spimc":
|
||||
for c in node:
|
||||
try:
|
||||
if c.compatible[0] == "biosensor,mesa":
|
||||
mesa_node = c
|
||||
break
|
||||
except AttributeError:
|
||||
continue
|
||||
except AttributeError:
|
||||
continue
|
||||
if mesa_node is not None:
|
||||
break
|
||||
|
||||
# trace interrupts
|
||||
aic_phandle = getattr(hv.adt["/arm-io/aic"], "AAPL,phandle")
|
||||
spi_node = mesa_node._parent
|
||||
|
||||
if getattr(spi_node, "interrupt-parent") == aic_phandle:
|
||||
for irq in getattr(spi_node, "interrupts"):
|
||||
hv.trace_irq("/arm-io/" + spi_node.name, irq, 1, hv.IRQTRACE_IRQ)
|
||||
|
||||
if getattr(mesa_node, "interrupt-parent") == aic_phandle:
|
||||
for irq in getattr(mesa_node, "interrupts"):
|
||||
hv.trace_irq("/arm-io/" + mesa_node.name, irq, 1, hv.IRQTRACE_IRQ)
|
||||
|
||||
mesa_pins = {
|
||||
0xc4: "mesa_pwr",
|
||||
|
||||
}
|
||||
|
||||
# Trace entire SPI MMIO range, can probably disable
|
||||
#trace_range(irange(0x000000019B108000, 0x4000), mode=TraceMode.SYNC)
|
||||
|
||||
gpio_tracer = GPIOTracer(hv, "/arm-io/gpio0", mesa_pins, verbose=1)
|
||||
gpio_tracer.start()
|
||||
|
||||
dart_sio_tracer = DARTTracer(hv, "/arm-io/dart-sio", verbose=1)
|
||||
dart_sio_tracer.start()
|
||||
|
||||
iomon = RegMonitor(hv.u, ascii=True)
|
||||
|
||||
def readmem_iova(addr, size):
|
||||
try:
|
||||
return dart_sio_tracer.dart.ioread(0, addr, size)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return None
|
||||
|
||||
iomon.readmem = readmem_iova
|
||||
|
||||
|
||||
|
||||
class SIOMessage(Register64):
|
||||
EP = 7, 0 # SPI2 DMA channels 0x18, 0x19
|
||||
TAG = 13, 8 # counts up, message ID?
|
||||
TYPE = 23, 16 # SIO message type
|
||||
PARAM = 31, 24
|
||||
DATA = 63, 32
|
||||
|
||||
class SIOStart(SIOMessage):
|
||||
TYPE = 23, 16, Constant(2)
|
||||
|
||||
class SIOSetup(SIOMessage):
|
||||
TYPE = 23, 16, Constant(3)
|
||||
|
||||
class SIOConfig(SIOMessage): #???
|
||||
TYPE = 23, 16, Constant(5)
|
||||
|
||||
class SIOAck(SIOMessage):
|
||||
TYPE = 23, 16, Constant(0x65)
|
||||
|
||||
class SIOSetupIO(SIOMessage):
|
||||
TYPE = 23, 16, Constant(6)
|
||||
|
||||
class SIOCompleteIO(SIOMessage):
|
||||
TYPE = 23, 16, Constant(0x68)
|
||||
|
||||
class SIOEp(EP):
|
||||
BASE_MESSAGE = SIOMessage
|
||||
SHORT = "sioep"
|
||||
|
||||
def __init__(self, tracer, epid):
|
||||
super().__init__(tracer, epid)
|
||||
self.state.iova = None
|
||||
self.state.iova_cfg = None
|
||||
self.state.iova_unk = None
|
||||
self.state.dumpfile = None
|
||||
|
||||
@msg(2, DIR.TX, SIOStart)
|
||||
def Start(self, msg):
|
||||
self.log("Start SIO")
|
||||
|
||||
@msg(3, DIR.TX, SIOSetup)
|
||||
def m_Setup(self, msg):
|
||||
#iomon.poll()
|
||||
if msg.EP == 0 and msg.PARAM == 0x1:
|
||||
self.state.iova = msg.DATA << 12
|
||||
|
||||
elif msg.EP == 0 and msg.PARAM == 0x2:
|
||||
# size for PARAM == 0x1?
|
||||
iomon.add(self.state.iova, msg.DATA * 8,
|
||||
name=f"SIO IOVA region at 0x{self.state.iova:08x}",
|
||||
offset=self.state.iova)
|
||||
|
||||
elif msg.EP == 0 and msg.PARAM == 0xb:
|
||||
# second iova block, maybe config
|
||||
self.state.iova_cfg = msg.DATA << 12
|
||||
|
||||
elif msg.EP == 0 and msg.PARAM == 0xc:
|
||||
# size for PARAM == 0xb?
|
||||
iomon.add(self.state.iova_cfg, msg.DATA * 8,
|
||||
name=f"SIO IOVA CFG region at 0x{self.state.iova_cfg:08x}",
|
||||
offset=self.state.iova_cfg)
|
||||
|
||||
#if msg.EP == 0 and msg.PARAM == 0xd:
|
||||
## possible fingerprint sensor IOVA region
|
||||
#self.state.iova_unk = msg.DATA << 12
|
||||
|
||||
#elif msg.EP == 0 and msg.PARAM == 0xe:
|
||||
#iomon.add(self.state.iova_unk, msg.DATA * 8,
|
||||
#name=f"SIO IOVA UNK region at {self.state.iova_unk:08x}",
|
||||
#offset=self.state.iova_unk)
|
||||
|
||||
@msg(5, DIR.TX, SIOConfig)
|
||||
def m_Config(self, msg):
|
||||
return
|
||||
|
||||
@msg(0x65, DIR.RX, SIOAck)
|
||||
def m_Ack(self, msg):
|
||||
return
|
||||
|
||||
@msg(6, DIR.TX, SIOSetupIO)
|
||||
def m_SetupIO(self, msg):
|
||||
if msg.EP == 0x18:
|
||||
if self.state.iova is None:
|
||||
return
|
||||
|
||||
buf = struct.unpack("<I", self.tracer.ioread(self.state.iova + 0xa8, 4))[0]
|
||||
size = struct.unpack("<I", self.tracer.ioread(self.state.iova + 0xb0, 4))[0]
|
||||
self.log(f"SetupIO 0x18: buf {buf:#x}, size {size:#x}")
|
||||
# XXX: Do not try to log messages going to 0x2
|
||||
if buf == 0x2:
|
||||
self.log("Mesa command interrupted!")
|
||||
return
|
||||
self.log_mesa("TO Mesa? (0x18)", self.tracer.ioread(buf, size))
|
||||
return
|
||||
return
|
||||
|
||||
@msg(0x68, DIR.RX, SIOCompleteIO)
|
||||
def m_CompleteIO(self, msg):
|
||||
if msg.EP == 0x19:
|
||||
iomon.poll()
|
||||
if self.state.iova is None:
|
||||
return
|
||||
|
||||
buf = struct.unpack("<I", self.tracer.ioread(self.state.iova + 0x48, 4))[0]
|
||||
size = struct.unpack("<I", self.tracer.ioread(self.state.iova + 0x50, 4))[0]
|
||||
self.log(f"CompleteIO 0x19: buf {buf:#x}, size {size:#x}")
|
||||
# XXX: Do not try to log messages going to 0x2
|
||||
if buf == 0x2:
|
||||
self.log("Mesa command interrupted!")
|
||||
return
|
||||
self.log_mesa("FROM Mesa? (0x19)", self.tracer.ioread(buf, size))
|
||||
return
|
||||
|
||||
def log_mesa(self, label, data):
|
||||
self.log(f"{label}: {len(data):d} byte message: ")
|
||||
chexdump(data)
|
||||
print("\n")
|
||||
|
||||
|
||||
class SIOTracer(ASCTracer):
|
||||
ENDPOINTS = {
|
||||
0x20: SIOEp
|
||||
}
|
||||
|
||||
|
||||
# RegMonitor for fingerprint endpoints?
|
||||
# Read pages pointed to by SIO
|
||||
#iomon.add((0x3d0 << 12), 0x4000, name="MESA 0x19?", offset=(0x3d0 << 12))
|
||||
#trace_device("/arm-io/spi2", True)
|
||||
#iomon.add((0x3db << 12), 0x4000, name="MESA 0x18?", offset=(0x3db << 12))
|
||||
|
||||
|
||||
|
||||
sio_tracer = SIOTracer(hv, "/arm-io/sio", verbose=1)
|
||||
sio_tracer.start(dart=dart_sio_tracer.dart)
|
||||
|
||||
# This doesn't seem to do anything
|
||||
spi_tracer = SPITracer(hv, "/arm-io/" + spi_node.name, verbose=1)
|
||||
spi_tracer.start()
|
Loading…
Reference in a new issue