soc: apple: add Apple SEP driver - #7
Open
DjDeveloperr wants to merge 26 commits into
Open
DjDeveloperr wants to merge 26 commits into
DjDeveloperr wants to merge 26 commits into
Conversation
Add a Rust driver for the Apple SEP (Secure Enclave Processor) on Apple silicon Macs, built as the CONFIG_APPLE_SEP module apple-sep. It attaches to a running SEP over the AP mailbox, drives the Touch ID fingerprint sensor, and exposes the enclave's key services to Linux. The driver is organised by SEP endpoint: - sep.rs transport, endpoint bring-up, mailbox and probe (crate root) - sbio.rs Touch ID: enrol, verify and match over /dev/sep-bio - sks.rs the key store: key-bag recovery and lock state - refkey.rs machine ref-key sealing, signing and attestation - fv.rs the device-bound FileVault key hierarchy - proto.rs the SEP wire protocol Touch ID enrol and verify are exposed through a /dev/sep-bio character device; the enclave matches and no biometric image ever crosses to userspace. A SEP-backed trusted key source lets keyctl seal keys to the enclave, which needs the trusted-keys core change included here to register a runtime trusted-key source. The C shims bridge kernel interfaces whose ABI is awkward to restate in Rust (struct hwrng, the file and crypto APIs, the trusted-key framework); no protocol logic lives in them. Assisted-by: GPT-5.6 Sol Assisted-by: Opus 5 Signed-off-by: Diljit Singh <dj@aurorasilicon.org>
Serve SEP anti-replay records from the existing device-wide xART extent instead of a Linux-private seeded store. Validate the entire slot grid and both root records before SEP registration, preserve the store's copy-on-write ordering, and require both an explicit module parameter and a writable block mapping before any write. Keep Linux-only biometric metadata in a separate namespaced host store. Assisted-by: GPT-5.6 Sol Assisted-by: Opus 5 Signed-off-by: Diljit Singh <dj@aurorasilicon.org>
Unwrap a FileVault volume's key through the enclave and load the volume's class keys into the enclave's volatile state, then drive the storage controller's inline AES-XTS with the wrapped key so an encrypted APFS volume is read and written with no plaintext key material on the application processor. The volume and key-bag records come from plaintext APFS metadata; only wrapped keys cross to the enclave. Assisted-by: GPT-5.6 Sol Assisted-by: Opus 5 Signed-off-by: Diljit Singh <dj@aurorasilicon.org>
The match check was stricter than the enclave requires and rejected valid results. Accept a match when the stored identity's user id matches. Assisted-by: GPT-5.6 Sol Assisted-by: Opus 5 Signed-off-by: Diljit Singh <dj@aurorasilicon.org>
The driver opened its backing-store files with the credentials of the task that triggered the access. A keyctl(2) call from an unprivileged process reaches key unsealing in that process's context, which then could not read the driver's own root-owned 0600 key-bag file, so unsealing failed with -74. Open the stores under kernel credentials from the initial namespace so access never depends on the calling task. Assisted-by: GPT-5.6 Sol Assisted-by: Opus 5 Signed-off-by: Diljit Singh <dj@aurorasilicon.org>
The biometric character device was exposed before the shared anti-replay store and the key store had come up, so an early open could race enclave setup. Publish /dev/sep-bio only once shared-xART and the key store are ready. Assisted-by: GPT-5.6 Sol Assisted-by: Opus 5 Signed-off-by: Diljit Singh <dj@aurorasilicon.org>
Reference-count the enclave class keys a FileVault volume loads. Repeated mounts of volumes that share a key bag now load the class keys once, and the volatile keys are unloaded from the enclave only when the last volume using them is released. Key material stays resident no longer than a mounted volume needs it, and keys another mount still depends on are not unloaded early. Assisted-by: GPT-5.6 Sol Assisted-by: Opus 5 Signed-off-by: Diljit Singh <dj@aurorasilicon.org>
Remove counters and fields that nothing reads. No functional change. Assisted-by: GPT-5.6 Sol Assisted-by: Opus 5 Signed-off-by: Diljit Singh <dj@aurorasilicon.org>
Concurrent key-store (SKS) out-of-line exchanges could interleave their mailbox traffic and out-of-line buffer registrations and wedge the endpoint when fingerprint matching and a FileVault mount ran at once. Hold a mutex across each complete SKS exchange so only one is in flight at a time. Assisted-by: GPT-5.6 Sol Assisted-by: Opus 5 Signed-off-by: Diljit Singh <dj@aurorasilicon.org>
When the sensor node carries no power GPIO the shim fell back to a fixed pinctrl node and line number specific to j414s. That fallback is machine-blind: on a sibling board whose sensor sits on a different line it would drive the wrong line. Take the fallback only on j414s; every other machine must describe the power GPIO in its device-tree node. Assisted-by: GPT-5.6 Sol Assisted-by: Opus 5 Signed-off-by: Diljit Singh <dj@aurorasilicon.org>
Sensor bring-up located its SPI controller by a fixed address and so only found the bus on j414s. Look up the apple,mesa-fingerprint node wherever the device tree describes it and enable that node and its parent controller directly, taking the power and interrupt GPIOs from the node. Any machine that describes the sensor in its device tree can then bring it up. When no such node exists the driver still creates one at the caller's fixed controller address, so j414s is unchanged. Assisted-by: GPT-5.6 Sol Assisted-by: Opus 5 Signed-off-by: Diljit Singh <dj@aurorasilicon.org>
The match path rejected any sensor whose reported identifier was not 0x3352, the revision fitted up to M4. Newer machines ship a 0x335e part that is identical over the wire. Accept both revisions so matching is not refused on those machines. Assisted-by: GPT-5.6 Sol Assisted-by: Opus 5 Signed-off-by: Diljit Singh <dj@aurorasilicon.org>
The MacBook Air (M2) and MacBook Pro 13" (M2) carry the Mesa fingerprint sensor on spi2, chip-select 0, like every other Apple laptop. t8112 only described spi1, spi3 and spi4, so the Apple SEP driver had no controller to bring the sensor up on these machines. Add the spi2 controller node, mirroring the sibling spi1/spi3 controllers (reg base + 0x8000, AIC IRQ 750, ps_spi2 power domain), and the spi2_pins pinmux group. The pad numbers (64-67: SDI, SDO, SCK and the hardware chip select) were read back from the live AP pinctrl on a J415 and cross-checked against the T8112 ADT, where spi2 sits between spi1's CS pad 49 and spi3's CS pad 96. The node stays disabled; the SEP driver enables it when it brings up the sensor. Signed-off-by: Diljit Singh <dj@aurorasilicon.org> Assisted-by: GPT-5.6 Sol Assisted-by: Opus 5
Add a binding for the Mesa fingerprint sensor on the SPI2 bus of Apple laptops, driven by the Secure Enclave through a /dev/sep-bio character device. The sensor uses the SPI controller's native chip select; a board describes its power line (enable-gpios), an optional data-ready line (interrupts), and the per-device calibration firmware name. Also document the apple,dma-range DART property, used to place the SEP firmware mapping on the M1 boot path. Signed-off-by: Chromatischer <dominik@hildania.de> [dj: adapted for aurora feat/sep, clean-room] Signed-off-by: Diljit Singh <dj@aurorasilicon.org> Assisted-by: GPT-5.6 Sol Assisted-by: Opus 5
Describe the SPI2 controller and the Mesa fingerprint sensor on T8103, and enable them on the MacBook Air (M1, J313). The controller sits at the SPI cluster base + 0x8000 (reg 0x235108000, AIC IRQ 616, ps_spi2 power domain), mirroring the sibling SPI nodes. Its pin group is the three signal pads (128 CLK, 129 MOSI, 130 MISO), read back from live hardware as peripheral function 1; SPI2 has no chip-select pad and uses the controller's native chip select. The sensor's power line is GPIO 108 (active high); the data-ready line is left out because the driver polls. Signed-off-by: Chromatischer <dominik@hildania.de> [dj: adapted for aurora feat/sep, clean-room] Signed-off-by: Diljit Singh <dj@aurorasilicon.org> Assisted-by: GPT-5.6 Sol Assisted-by: Opus 5
Name every SoC-specific fact the driver needs — the shared-memory capacity and first-item fourcc, the boot handshake, the OS-identity source and the sensor transport — in one typed profile, and refuse an unsupported SoC rather than run a handshake with the wrong geometry. Two targets are modelled: T8103/J313 (M1) boots the SEP with the boot endpoint handshake over a 0x30000 window whose first item is CNIP; T6020/J414s (M2 Pro) does the warm registration over a 0x40000 window whose first item is CINP. The shared-memory build takes the capacity and first-item spelling from the profile; T6020's values are unchanged. Signed-off-by: Chromatischer <dominik@hildania.de> [dj: adapted for aurora feat/sep - dropped the storage-backend enum (single in-kernel owner), identity is /chosen only, kept the driver modular; clean-room] Signed-off-by: Diljit Singh <dj@aurorasilicon.org> Assisted-by: GPT-5.6 Sol Assisted-by: Opus 5
The T8103 SEP is not brought up by the warm registration message. On that target, send TZ0 on the boot endpoint and let its acknowledgements drive the handoff: map the firmware reserved region, submit the firmware address (IMG4) and the shared-memory table (SET_SHMEM), and mark the registration complete on the IMG4 acknowledgement. The warm target keeps the single registration message; the profile selects which path runs, and only the warm target enables the SEP and its DART with a runtime changeset - the cold-boot target's DART is already enabled before probe and is described statically. The firmware mapping is created once and released exactly once in remove(), gated on the teardown flag so a boot message racing removal cannot map firmware or hand memory to the peer after teardown. Signed-off-by: Chromatischer <dominik@hildania.de> [dj: ported onto aurora feat/sep - reused the existing teardown flag and warm attach path, profile-gated the runtime DT enable, clean-room] Signed-off-by: Diljit Singh <dj@aurorasilicon.org> Assisted-by: GPT-5.6 Sol Assisted-by: Opus 5
The Mesa calibration blob is per-device factory data, so its name is board data, not a build constant. Read it from the sensor node's firmware-name property, falling back to the built-in default when the property is absent so the runtime-fabricated node keeps working. Signed-off-by: Chromatischer <dominik@hildania.de> [dj: kept the default fallback so the fabricated j414s node still loads; clean-room] Signed-off-by: Diljit Singh <dj@aurorasilicon.org> Assisted-by: GPT-5.6 Sol Assisted-by: Opus 5
Log once when the key-store endpoint (0x12) comes up and its out-of-line buffers are registered, and enrich the "not advertised" diagnostic to show how far the endpoint ladder reached: the live endpoint count and which of control/xarm/sbio/scrd are present. An incomplete xART bring-up is then diagnosable from dmesg alone, without instrumenting the driver. Signed-off-by: Diljit Singh <dj@aurorasilicon.org> Assisted-by: GPT-5.6 Sol Assisted-by: Opus 5 Assisted-by: Claude Opus 4.8
The identity keybag CREATE_KEYBAG request carries the bag type in a word the strict T8103 enclave reads as the type and the lenient T6020 enclave does not. T6020 takes the variant in the first word (the proven encoding, hardware-verified for enrol, match and reboot); T8103 rejects that and wants the type in the third word, as macOS sends it. Carry the three field values in the platform profile so each SoC sends what its enclave requires, leaving the T6020 wire encoding byte-for-byte unchanged. Drop the now-unused variant constant, create-flags helper and first-identity handle. Verified on j414s (T6020): the identity keybag still provisions. Signed-off-by: Diljit Singh <dj@aurorasilicon.org> Assisted-by: GPT-5.6 Sol Assisted-by: Opus 5 Assisted-by: Claude Opus 4.8
Add an in-kernel path that opens the iBoot system container directly and serves the STORE_SIZE gigalocker window at a configured sector, so the driver reaches the shared anti-replay store on its own. It is additive and fail-closed: the xart_start_sector module parameter selects the raw extent, the logical store is always exactly STORE_SIZE so a larger backing device serves only its extent, and a wrong window still fails the existing root-record check rather than feeding SEP an unrelated store. Verified on j414s: with xart_start_sector at the gigalocker's first sector the store opens with the expected slot count, record count and revision, and the key store comes up. Signed-off-by: Diljit Singh <dj@aurorasilicon.org> Assisted-by: GPT-5.6 Sol Assisted-by: Opus 5 Assisted-by: Claude Opus 4.8
Drop the last manual step from the in-kernel raw-extent owner: instead of a hand-supplied start sector, the driver finds the gigalocker itself. It reads the iBoot system container sequentially and searches, at block-aligned offsets, for a root record's signature (a 1 or 2 key kind with an all-zero UUID, the shape of Key::root). A root signature alone does not pin the base. A window shifted a few slots off the true origin still keeps both root records inside its 6 MiB extent, so it passes the two-root test yet silently drops the live records that fall outside the shift -- and, with writes on, would repair (write) at that wrong origin. The driver therefore probes every candidate base a hit implies read-only: through a writable handle, because the block layer only grants a read-only handle to a read-only device and the container is writable, but with repair disarmed so nothing is written at an unconfirmed origin. It then pins the base that recovers the most live records. Only the true origin captures the whole record set, and container noise never forges a CRC-valid record, so max live records -- ties broken to fewer malformed, then the higher base -- is exact. Only the pinned base is re-opened writable, so repair runs at the confirmed origin alone. open_based gains open_based_ext, which decouples the block-handle writability from whether repair is armed, so discovery of the writable container never mutates it. With automatic location in place, xart_start_sector becomes an explicit override (default 0 = automatic) and the driver has no external dependency for reaching the store. Boot-verified on j414s (T6020): the scan pins the true base 0x4d4000, reads all 10 live records with 0 malformed and 0 repaired (no errant write), and the key store (EP 0x12) reaches 12 endpoints. Signed-off-by: Diljit Singh <dj@aurorasilicon.org> Assisted-by: GPT-5.6 Sol Assisted-by: Opus 5 Assisted-by: Claude Opus 4.8
The SEP endpoint ladder climbs 7 -> 11 (sbio, scrd) -> 12 (the key store, EP 0x12). tick_exchange declared the persistent-state exchange done as soon as the endpoint count passed a hardcoded pre-exchange baseline (7), but the SEP advertises the key store a beat after it falls briefly quiet following scrd. On some boots the driver therefore reached run_bringup at 11 endpoints, found EP 0x12 absent, and stranded the boot with no key store -- enable_sks fails with no retry, so keybag and ref-key operations (and thus Touch ID) are unavailable for the whole boot. Wait through quiescence until the key store itself is advertised, bounded by the same EXCHANGE_TIMEOUT_MS. A boot that already has it exits immediately, so healthy boots are unaffected; the hardcoded endpoint count, which is not portable across SoCs, is dropped in favour of the endpoint that actually matters. Boot-verified on j414s (T6020): the key store (EP 0x12) came up at 12 endpoints on 5 of 5 reboots, up from roughly 2 in 3 before the change. Signed-off-by: Diljit Singh <dj@aurorasilicon.org> Assisted-by: GPT-5.6 Sol Assisted-by: Opus 5 Assisted-by: Claude Opus 4.8
The capture loop polled the sensor's status over SPI every 2 ms to spot a ready frame. Add an opt-in data-ready interrupt (capture_irq=1; default 0 keeps polling) that wakes the loop the instant the sensor asserts its data-ready line. The SPI status read still gates every frame, so a missed or spurious interrupt costs at most one poll interval and the poll path is untouched when the option is off. The interrupt is configured once, while the sensor is patched and idle -- the only safe moment, since reconfiguring the line mid-capture is what drops the sensor's firmware patch -- and then left alone for the driver's life. On j414s it is the pin adjacent to the power line (GPIO 121 -> IRQ 144); other boards describe it as the SPI node's interrupt. It is edge-triggered on both edges: the line idles low, so a level trigger would storm. Boot-verified on j414s (T6020): with capture_irq=1 the data-ready IRQ 144 is configured, fingerprint verify captures and matches through the interrupt, the IRQ fires a handful of times per touch (not a level storm), and the sensor keeps its firmware patch (no STATE_NEEDS_PATCH). Signed-off-by: Diljit Singh <dj@aurorasilicon.org> Assisted-by: GPT-5.6 Sol Assisted-by: Opus 5 Assisted-by: Claude Opus 4.8
An enrol capture the enclave rejects (partial or unusable contact) silently looped for another frame with no feedback, so the person had no idea their touch had not counted. Set the hold-still guidance on that path so the enrol poll reports it and userspace can prompt for a firmer press, matching the guidance already surfaced from the armed and reading sensor states. Signed-off-by: Diljit Singh <dj@aurorasilicon.org> Assisted-by: GPT-5.6 Sol Assisted-by: Opus 5 Assisted-by: Claude Opus 4.8
Land the interrupt path as the default from hardware validation: it is no longer behind the capture_irq parameter (removed), and it is set up whenever the sensor exposes a data-ready line, with SPI polling kept only as the fallback for a machine that does not describe one. Two fixes the enrolment stress test surfaced: - Trigger on the rising edge only. The line idles low, so a level trigger storms and both-edges also fires on the deassert -- a spurious wake that, mid-enrolment (the finger lifts and repositions between the eight stages), spun the capture loop. - Bound the capture wait by wall-clock time, not a fixed attempt count. A stray edge returns the wait early, so an attempt budget would be spent in milliseconds and time the capture out before a frame lands; the time bound degrades, at worst, to the poll path's duration. The wait now blocks on the data-ready line for up to ENROL_IRQ_WAIT_MS (250 ms) rather than re-reading SPI status every 2 ms, waking the instant a frame is ready. Boot-verified on j414s (T6020): fprintd verify and a full eight-stage enrol both capture and match through the interrupt, with no STATE_NEEDS_PATCH and no level storm. Signed-off-by: Diljit Singh <dj@aurorasilicon.org> Assisted-by: GPT-5.6 Sol Assisted-by: Opus 5 Assisted-by: Claude Opus 4.8
DjDeveloperr
force-pushed
the
feat/sep
branch
from
September 18, 2026 07:11
48807cc to
06f1c56
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds support for Touch ID and Secure Enclave backed keyctl & hwrng.
TODO: Newer devices like M4 / later and Neo seem to use a new power sequence for Touch ID sensor, not GPIO but SMC functions.
Before you start
This touches the Secure Enclave. Use a machine you can DFU-restore ideally.
It's verified to be safe on M2 Pro MBP and M1 Air so far and harmless to SEP anti-replay state and works nicely with macOS, but being experimental, I recommend to be careful.
With writes enabled, xART advances a device-wide anti-replay epoch, rapid reboot / delete / re-provision loops drift it and risk needing a DFU restore. Enrol through fprintd only (mixing tools splits host and enclave state); the keybag and ref-key are always recovered together.
1. Copy the calibration blob from macOS
Update:
You can just extract it from the NVMe using this script (verified on M2 Pro):
extract-mesa-calibration.py
Or, use the old way:
The sensor won't match a finger without its per-device factory calibration, and we can't extract it at runtime so copy it out of macOS once.
In macOS, enrol a finger in Touch ID once (to populate the cache), then:
Finally, to install:
In Asahi:
2. Device-tree pins — skip on j414s
The sensor is always on spi2, chip-select 0, with identical electrical constants on every Mac. Only the SPI2 pin group and the power/IRQ GPIOs are per-board and can't be probed.
On any other machine, read them once and add an
spi2controller +mesa@0node to that machine's.dts, modelled on the committedt8103/t8112nodes:mesa-spi2-report.py
3. Build the kernel
Start from your running config so the rest of the machine keeps working, then enable the driver and build the DTBs alongside the image:
4. Update m1n1 (stage 2) + DTB
The bootloader hands the SEP off to Linux by a warm attach. A stock m1n1 asks the SEP for randomness during boot, which burns its one-shot session before Linux ever attaches. Boot through the Aurora-patched m1n1 and point it at the DTB you just built:
sudo update-m1n1 /path/to/aurora-m1n1.bin # or your distro's m1n1 update flow5. Install the loader + fprintd
The driver finds the xART gigalocker itself inside the iBoot system container. Just install the loader service and the fingerprint stack (both in
tools/aurora-sep/):The service runs
modprobe apple_sep xart_writes=1 provision_keybag=1; the driver auto-locates the gigalocker and provisions the identity keybag on the first boot.6. Boot the new kernel once
Boot it as a one-shot GRUB entry, so a bad build falls back on the next reboot instead of stranding you:
7. Check it came up, then enrol
Each line is the signal that the layer beneath it worked:
xART: ... auto-located raw-extent owner (base 0x...); N slots, M live recordssks: endpoint 0x12 (key store) up; 12 EPssks: identity keybag provisionedls /dev/sep-bio·systemctl is-active aurora-sep.serviceWhen it doesn't come up
sks: endpoint 0x12 (key store) not advertised; N EPs up: stuck below 12 EPs. First confirmxART: ... auto-located ... M live recordsappears (the driver found the store). A clean attach climbs 7 → 11 → 12; the driver waits for the key-store endpoint, so a healthy machine reaches 12.sensor: calibration blob ... could not be loaded (ENOENT): the per-device blob is missing. Redo step 1; it must be exactly at/lib/firmware/apple/mesa_calibration.bin.identity keybag state is ambiguous: EEXIST/provisioning failed: the keybag / ref-key pair is broken. They are a bound pair; delete both and reboot:enrol "unknown error" /
verify: ... enclave holds no template: stale enclave state from a previous enrol. Wipe the per-enrol state (keeping the keybag) and enrol fresh:sudo tools/aurora-sep/sep-fresh-test.sh # DELETE_ALL + catacombs + fprintd DB; keeps the keybagSensor never binds /
no power line: non-j414s only: wrong power GPIO in your DTS (step 2). The sensor answers sixteen zero bytes when it isn't powered.No endpoints / warm attach fails: a stock m1n1 poked the SEP first; install the Aurora-patched build (step 4). Also seen if the SEP was already attached this boot — reboot for a fresh attempt.
Nothing changes after
modprobe -r/modprobe apple_sep:SET_SHMEMis one-shot per reset, so a reload can't re-attach. Reboot to test a change.