From a1088a99ddde01fbe50891ad456643521a6b7eed Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Sun, 25 Oct 2026 19:58:58 -0400 Subject: [PATCH 1/4] dt-bindings: spi: apple: Add the T8132 SPI controller The M4's SPI controllers are the T8103 design, as on T8140. Signed-off-by: Erik Bourget --- Documentation/devicetree/bindings/spi/apple,spi.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/spi/apple,spi.yaml b/Documentation/devicetree/bindings/spi/apple,spi.yaml index be47c5f9ff075..608e4d379f241 100644 --- a/Documentation/devicetree/bindings/spi/apple,spi.yaml +++ b/Documentation/devicetree/bindings/spi/apple,spi.yaml @@ -19,6 +19,7 @@ properties: - enum: - apple,t6020-spi - apple,t6041-spi + - apple,t8132-spi - apple,t8140-spi - const: apple,t8103-spi - items: From 0fe3023281dffbdef258a62f68e28bc93f236afa Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Sun, 25 Oct 2026 19:58:58 -0400 Subject: [PATCH 2/4] arm64: dts: apple: t8132: Add the SEP, its mailbox and DART, and SPI2 The Secure Enclave and its DART, as the ADT describes them: SEP at 0x3aa600000 with its ASC mailbox at +0x8000 (AIC 389-392), DART at 0x3a8c80000 (AIC 399) with the BIT(40) input alias the DART translates. The DART's register window is 0x20000: its stale-exception drain reads per-stream registers past 0x4000. SPI2 at 0x3ad108000 (AIC 1056) carries the Mesa fingerprint sensor. As on T8140 it runs from the 24 MHz reference clock; with a 200 MHz rate the divider for the sensor's 8 MHz gave about 1 MHz and frame reads timed out. The sensor node takes SPI mode 2, as the 0x3356 sensor does on J700. All disabled. The SEP driver enables the SEP and its DART itself; a board with a qualified sensor enables the mailbox and SPI2. Signed-off-by: Erik Bourget --- arch/arm64/boot/dts/apple/t8132.dtsi | 58 ++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8132.dtsi b/arch/arm64/boot/dts/apple/t8132.dtsi index e94528277a805..d548fb3ade7d8 100644 --- a/arch/arm64/boot/dts/apple/t8132.dtsi +++ b/arch/arm64/boot/dts/apple/t8132.dtsi @@ -728,6 +728,44 @@ }; }; + /* + * The SEP DART's input aperture is the BIT(40) alias the DART + * translates (ADT vm-base-0 0x10000004000). + */ + sep_dart: iommu@3a8c80000 { + compatible = "apple,t8132-dart", "apple,t8110-dart"; + reg = <0x3 0xa8c80000 0x0 0x20000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + apple,dma-range = /bits/ 64 <0x10000004000 0xffff0000>; + apple,dma-offset = /bits/ 64 <0x0>; + status = "disabled"; + }; + + sep: sep@3aa600000 { + compatible = "apple,sep"; + reg = <0x3 0xaa600000 0x0 0x88000>; + mboxes = <&sep_mbox>; + mbox-names = "mbox"; + iommus = <&sep_dart 0>; + status = "disabled"; + }; + + sep_mbox: mbox@3aa608000 { + compatible = "apple,t8132-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x3 0xaa608000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + status = "disabled"; + }; + i2c0: i2c@3ad010000 { compatible = "apple,t8132-i2c", "apple,t8103-i2c"; reg = <0x3 0xad010000 0x0 0x4000>; @@ -814,6 +852,26 @@ }; /* DockChannel UART: the DebugUSB console (ADT /arm-io/dockchannel-uart). */ + spi2: spi@3ad108000 { + compatible = "apple,t8132-spi", "apple,t8103-spi"; + reg = <0x3 0xad108000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + clocks = <&clkref>; + power-domains = <&ps_spi2>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + + mesa: fingerprint@0 { + compatible = "apple,mesa-fingerprint"; + reg = <0>; + spi-max-frequency = <8000000>; + spi-cpol; + status = "disabled"; + }; + }; + dockchannel_uart: serial@38812c000 { compatible = "apple,dockchannel-uart"; reg = <0x3 0x8812c000 0x0 0x4000>, From 0b2a47a9a3056d60ae643866ff90d1177bbfaf7f Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Sun, 25 Oct 2026 19:58:58 -0400 Subject: [PATCH 3/4] soc: apple: sep: Add the J713 (MacBook Air M4) profile The J713 SoC side is J613's: a warm-attach enclave shared with macOS, the variant-5 key store and the BIT(40) DART alias. Its Mesa sensor is J700's generation (0x3356, "V3" in the ADT), not J613's (0x3352): the enclave refuses the R3 session (SESSION_SHARE status 0x16), and its factory calibration is an FSC2 image. So it takes J700's SPI mode 2 and nonce session. Its device tree names its own signed root key. Profile selection becomes a pure function of the root compatible list, so the tests can check that only J713 selects T8132 and that the other boards keep their profiles. J713 on 26.6.2 (25G83) provisions its keybag, enrols, matches, and restores the enrolment after a reboot, with xart_guard 1 and no record refused. Signed-off-by: Erik Bourget --- drivers/soc/apple/profile.rs | 101 ++++++++++++++++++++++++++++++----- 1 file changed, 89 insertions(+), 12 deletions(-) diff --git a/drivers/soc/apple/profile.rs b/drivers/soc/apple/profile.rs index 0e632c65da81f..a999118522599 100644 --- a/drivers/soc/apple/profile.rs +++ b/drivers/soc/apple/profile.rs @@ -450,6 +450,30 @@ pub(crate) const T6040: PlatformProfile = PlatformProfile { ..T8140 }; +/// MacBook Air M4 (J713). The J713 ADT (26.6.2, 25G83) puts the Mesa sensor +/// on spi2 at `0x3ad108000`, 8 MHz, chip select 0, with power on the NUB GPIO +/// controller (pin 28) and data-ready on AP GPIO 199. The SEP is +/// `iop-sep,ascwrap-v6` behind a DART with the BIT(40) input alias, so the SoC +/// side is J613's. +/// +/// The sensor is J700's generation (`V3`, 0x3356), not J613's (`R3`): the +/// enclave refuses the R3 SESSION_SHARE (status 0x16) and its factory +/// calibration is an FSC2 image. So it takes J700's SPI mode 2 and nonce +/// session; its device tree names its signed root key. J713 on OS firmware +/// 26.6.2 provisions its keybag, enrols, matches and restores the enrolment +/// after a reboot. +pub(crate) const T8132: PlatformProfile = PlatformProfile { + name: "T8132/J713", + sensor: SensorProfile { + controller_base: 0x3_ad10_8000, + mode: SpiMode::Mode2, + expected_id: 0x3356, + ..T8122.sensor + }, + nonce_session: true, + ..T8122 +}; + static_assert!(T8103.shmem_capacity == 0x30000); static_assert!(T6020.shmem_capacity == 0x40000); static_assert!(T8140.shmem_capacity == 0x40000); @@ -467,6 +491,9 @@ static_assert!(T8122.sensor.controller_base == 0x2a1108000); static_assert!(T8122.wide_dma_mask); static_assert!(T6040.sensor.controller_base == 0x429108000); static_assert!(T6040.sensor.expected_id == T8140.sensor.expected_id); +static_assert!(T8132.sensor.controller_base == 0x3ad108000); +static_assert!(T8132.sensor.expected_id == T8140.sensor.expected_id); +static_assert!(T8132.nonce_session && T8132.wide_dma_mask); /// Whether the machine root declares `compatible`. /// @@ -575,21 +602,30 @@ fn t6020_profile() -> &'static PlatformProfile { /// rather than guessed at, so the driver cannot run a handshake with the wrong /// geometry. pub(crate) fn detect() -> Result<&'static PlatformProfile> { - if machine_has(b"apple,t8103") { + select(&machine_has, &t6020_profile) +} + +/// The profile for a machine whose root `compatible` list `has` answers for, +/// with `t6020` choosing the M2 Pro and Max key-store dialect. +fn select( + has: &dyn Fn(&[u8]) -> bool, + t6020: &dyn Fn() -> &'static PlatformProfile, +) -> Result<&'static PlatformProfile> { + if has(b"apple,t8103") { return Ok(&T8103); } - if machine_has(b"apple,t6020") { - return Ok(t6020_profile()); + if has(b"apple,t6020") { + return Ok(t6020()); } // t6020.dtsi is defined as a cut-down t6021: it includes t6021.dtsi and // disables the parts the smaller die lacks. Both pull in t602x-die0.dtsi, // so the SEP, its DART and the SPI controller sit at the same addresses // with the same interrupts on either part, and the T6020 constants apply // to the M2 Max unchanged. - if machine_has(b"apple,t6021") { - return Ok(t6020_profile()); + if has(b"apple,t6021") { + return Ok(t6020()); } - if machine_has(b"apple,t6000") { + if has(b"apple,t6000") { return Ok(&T6000); } // t6000.dtsi is defined as a cut-down t6001: it includes t6001.dtsi and @@ -599,25 +635,29 @@ pub(crate) fn detect() -> Result<&'static PlatformProfile> { // constants apply to the M1 Max unchanged. The Mac Studio (j375c) is also // a t6001 but leaves the SEP disabled, so nothing binds there; apple,t6002 // (M1 Ultra, no built-in sensor) stays unmapped. - if machine_has(b"apple,t6001") { + if has(b"apple,t6001") { return Ok(&T6000); } - if machine_has(b"apple,t8140") { + if has(b"apple,t8140") { return Ok(&T8140); } - if machine_has(b"apple,t8112") { + if has(b"apple,t8112") { return Ok(&T8112); } // Only the boards whose ADTs were read: the other T8122 Macs (J433, J434, // J504) share the SoC but their sensor wiring is unchecked. - if machine_has(b"apple,t8122") && (machine_has(b"apple,j613") || machine_has(b"apple,j615")) { + if has(b"apple,t8122") && (has(b"apple,j613") || has(b"apple,j615")) { return Ok(&T8122); } // Only the board whose ADT was read: J616s. The other T6040 Macs share // the SoC but their sensor wiring is unchecked. - if machine_has(b"apple,t6040") && machine_has(b"apple,j616s") { + if has(b"apple,t6040") && has(b"apple,j616s") { return Ok(&T6040); } + // Only J713: the other T8132 Macs are unread. + if has(b"apple,t8132") && has(b"apple,j713") { + return Ok(&T8132); + } Err(ENODEV) } @@ -674,6 +714,34 @@ mod tests { assert!(T6040.identity == IdentitySource::Chosen); assert!(T6040.xart_guard == 1); } + + /// The name of the profile `select` picks for a root `compatible` list. + fn picked(compatibles: &[&[u8]]) -> Option<&'static str> { + let has = |c: &[u8]| compatibles.iter().any(|x| *x == c); + select(&has, &|| &T6020).ok().map(|p| p.name) + } + + #[test] + fn t8132_selection_is_limited_to_j713() { + assert_eq!(picked(&[b"apple,j713", b"apple,t8132"]), Some(T8132.name)); + assert_eq!(picked(&[b"apple,t8132"]), None); + assert_eq!(picked(&[b"apple,j613", b"apple,t8132"]), None); + assert_eq!(picked(&[b"apple,j713", b"apple,t8122"]), None); + assert_eq!(picked(&[b"apple,j613", b"apple,t8122"]), Some(T8122.name)); + assert_eq!(picked(&[b"apple,j700", b"apple,t8140"]), Some(T8140.name)); + assert_eq!(picked(&[b"apple,j616s", b"apple,t6040"]), Some(T6040.name)); + assert_eq!(picked(&[b"apple,t6040"]), None); + } + + #[test] + fn j713_takes_the_v3_sensor_on_the_j613_soc_side() { + assert!(T8132.sensor.expected_id == T8140.sensor.expected_id); + assert!(T8132.sensor.mode == SpiMode::Mode2); + assert!(T8132.nonce_session && T8132.wide_dma_mask && T8132.persistent_enrol); + assert!(T8132.shmem_capacity == T8122.shmem_capacity); + assert!(T8132.xart_guard == T8122.xart_guard); + assert!(core::ptr::eq(T8132.dart_window, T8122.dart_window)); + } } #[cfg(CONFIG_APPLE_SEP_KUNIT_TEST)] @@ -703,7 +771,16 @@ mod dart_window_tests { #[test] fn the_other_profiles_keep_the_bit40_window() { - for profile in [&T8103, &T6000, &T6020, &T8140, &T6020_SEPOS13, &T8122, &T6040] { + for profile in [ + &T8103, + &T6000, + &T6020, + &T8140, + &T6020_SEPOS13, + &T8122, + &T6040, + &T8132, + ] { assert_eq!(offset(profile.dart_window), 1 << 40); assert_eq!(range(profile.dart_window), (0, 1 << 32)); } From dc12b11a0ffbe89d065a37f10f5399b6ff12dc97 Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Sun, 25 Oct 2026 19:58:59 -0400 Subject: [PATCH 4/4] arm64: dts: apple: t8132-j713: Enable Touch ID Enable the SEP mailbox and the Mesa sensor, which sits on spi2 with power on NUB GPIO 28 and data-ready on AP GPIO 199 (J713 ADT /arm-io/spi2/mesa). The SEP driver enables the SEP and its DART itself. The sensor node names this Mac's calibration and its signed root key. Signed-off-by: Erik Bourget --- .../arm64/boot/dts/apple/t8132-j713-pmgr.dtsi | 2 +- arch/arm64/boot/dts/apple/t8132-j713.dts | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/apple/t8132-j713-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8132-j713-pmgr.dtsi index 2e329cecdbbfe..e3a374ced44bc 100644 --- a/arch/arm64/boot/dts/apple/t8132-j713-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t8132-j713-pmgr.dtsi @@ -194,7 +194,7 @@ }; &ps_spi2 { - status = "disabled"; + status = "okay"; }; &ps_spi3 { diff --git a/arch/arm64/boot/dts/apple/t8132-j713.dts b/arch/arm64/boot/dts/apple/t8132-j713.dts index da55888e6d5e4..a6d441c6e0245 100644 --- a/arch/arm64/boot/dts/apple/t8132-j713.dts +++ b/arch/arm64/boot/dts/apple/t8132-j713.dts @@ -22,6 +22,7 @@ model = "Apple MacBook Air (13-inch, M4, 2025)"; aliases { + sep = &sep; ans = &nvme; ans-mbox = &ans_mbox; atcphy0 = &atcphy0; @@ -477,6 +478,32 @@ * own the T8132 display power sequence, and the GPIO block must remain live * for inherited firmware and input state. */ +/* + * Mesa fingerprint sensor on spi2 (J713 ADT /arm-io/spi2/mesa): power on + * NUB GPIO 28, active high; data-ready on AP GPIO 199. Native chip select only. + */ +&sep_mbox { + status = "okay"; +}; + +&spi2 { + status = "okay"; +}; + +&mesa { + status = "okay"; + enable-gpios = <&pinctrl_nub 28 GPIO_ACTIVE_HIGH>; + interrupt-parent = <&pinctrl_ap>; + interrupts = <199 IRQ_TYPE_EDGE_RISING>; + spi-cs-setup-delay-ns = <20>; + spi-cs-hold-delay-ns = <20>; + /* + * Per-device factory data from this Mac's own iBoot system container: + * the calibration, and the signed root key the nonce session loads. + */ + firmware-name = "apple/mesacal-j713.bin", "apple/msrk-j713.bin"; +}; + &framebuffer0 { /delete-property/ power-domains; panel = <&fb_panel>;