diff --git a/Documentation/devicetree/bindings/iommu/apple,dart.yaml b/Documentation/devicetree/bindings/iommu/apple,dart.yaml index 44a0499ada2cbc..ad3f8e90338618 100644 --- a/Documentation/devicetree/bindings/iommu/apple,dart.yaml +++ b/Documentation/devicetree/bindings/iommu/apple,dart.yaml @@ -37,6 +37,7 @@ properties: - enum: - apple,t6020-dart - apple,t8122-dart + - apple,t8132-dart - apple,t8140-dart - apple,t8142-dart - const: apple,t8110-dart diff --git a/Documentation/devicetree/bindings/mailbox/apple,mailbox.yaml b/Documentation/devicetree/bindings/mailbox/apple,mailbox.yaml index 8d66dc77c0a54e..7e7644c1732e34 100644 --- a/Documentation/devicetree/bindings/mailbox/apple,mailbox.yaml +++ b/Documentation/devicetree/bindings/mailbox/apple,mailbox.yaml @@ -31,6 +31,7 @@ properties: - apple,t8103-asc-mailbox - apple,t8112-asc-mailbox - apple,t8122-asc-mailbox + - apple,t8132-asc-mailbox - apple,t8140-asc-mailbox - apple,t8142-asc-mailbox - apple,t6000-asc-mailbox diff --git a/Documentation/input/devices/j715-touchid.rst b/Documentation/input/devices/j715-touchid.rst new file mode 100644 index 00000000000000..27f3a0c4dfc15a --- /dev/null +++ b/Documentation/input/devices/j715-touchid.rst @@ -0,0 +1,54 @@ +J715 Touch ID session inputs +=========================== + +The T8132/J715 profile uses nonce authentication and source-6 calibration. +It does not change the other boards' session policies, xART guards or +persistence rules. It still requires a successful sensor power cycle before +registration. + +Root-key firmware +----------------- + +``apple/msrk-j715.bin`` must contain this machine's own FDR MSRk data: the +complete, unmodified body supplied to SBIO SET_MSRK_DATA (opcode ``0x86``). +The qualified input is 10,848 bytes, as in the successful J715 candidate. +Do not substitute J700's signed-container file, strip or add a wrapper, pad +another input to this size, or use another machine's data. + +The driver checks the length, not a private identifier or hash. It does not +parse or authenticate the FDR container; a matching length is not evidence +of validity. The enclave must accept the unmodified data. Acquisition and +container verification belong to provisioning, not the kernel driver. +No automatic extraction or alternate-file fallback is provided. + +After SENSOR_NONCE, the driver first requests SESSION_SHARE normally. +Only an explicit status ``0x16`` on J715 triggers the firmware request and +upload. A successful upload permits exactly one further SESSION_SHARE +request within that session attempt. A missing file, wrong length, failed +upload, failed transfer or refused retry stops the attempt. A successful +initial share does not read the firmware file. + +J700 retains ``apple/msrk-j700.bin``, the nonempty signed-container rule, +and its upload before SESSION_SHARE once per enclave lifetime. It does not +use J715's refusal recovery. + +Calibration and validation +-------------------------- + +The existing sensor ``firmware-name`` property, or its existing default +``apple/mesa_calibration.bin``, supplies calibration. J715 requires its own +source-6 FSC2 data from the successful setup, not the older extracted FDR3 +input. The existing calibration encoder prefixes the unchanged body with a +four-byte source word. This patch does not add a calibration fallback. + +With ``CONFIG_APPLE_SEP_KUNIT_TEST=y``, the +``apple_sep_sensor_root_key`` suite tests board selection, input-length +boundaries and refusal classification. Also run ``apple_sep_profile``, +``apple_sep_dart_window`` and ``apple_sep_registration`` to cover the +existing profile and registration behavior. + +The old candidate's successful enrollment and same-boot verification do not +qualify this upstream kernel. Hardware validation must establish sensor +readiness, enrollment, matching and persistence across a cold boot, with +the existing power-cycle and registration guards enabled. Login integration +is userspace work; disk unlock is outside this support. diff --git a/arch/arm64/boot/dts/apple/t8132.dtsi b/arch/arm64/boot/dts/apple/t8132.dtsi index afec92a0601562..e6d643950d2441 100644 --- a/arch/arm64/boot/dts/apple/t8132.dtsi +++ b/arch/arm64/boot/dts/apple/t8132.dtsi @@ -354,6 +354,50 @@ }; }; + /* + * iBoot hands over a running SEP. Keep Linux off its DART at boot; + * apple_sep enables it at module initialization. The SEP only + * accepts IOVAs in this window, which carries 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 0x4000>; + 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"; + }; + + /* + * The ADT lists these interrupts as 390 389 392 391. In that order + * the recv-not-empty slot holds the line that is asserted while the + * receive FIFO is empty, and starting the channel never returns. + */ + 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>; + }; + i2c0: i2c@3ad010000 { compatible = "apple,t8132-i2c", "apple,t8103-i2c"; reg = <0x3 0xad010000 0x0 0x4000>; diff --git a/drivers/soc/apple/profile.rs b/drivers/soc/apple/profile.rs index 6803c1464652e0..c7102c6a50fb3b 100644 --- a/drivers/soc/apple/profile.rs +++ b/drivers/soc/apple/profile.rs @@ -55,6 +55,40 @@ pub(crate) enum IdentitySource { HostPersisted, } +/// The root-key input and loading point verified for each nonce-session board. +#[derive(Clone, Copy, PartialEq, Eq)] +pub(crate) enum SensorRootKey { + None, + /// Preserve J700's signed container upload before SESSION_SHARE. + J700, + /// J715's device-specific FDR MSRk body, uploaded only after status 0x16. + J715, +} + +impl SensorRootKey { + pub(crate) const fn firmware(self) -> Option<&'static CStr> { + match self { + Self::None => None, + Self::J700 => Some(c"apple/msrk-j700.bin"), + Self::J715 => Some(c"apple/msrk-j715.bin"), + } + } + + /// Only the observed J715 FDR body size is qualified. The enclave checks + /// its authenticity; a matching length alone does not authenticate it. + pub(crate) const fn accepts_length(self, length: usize) -> bool { + match self { + Self::None => false, + Self::J700 => length != 0, + Self::J715 => length == 10_848, + } + } + + pub(crate) fn recover_share(self, status: Option) -> bool { + self == Self::J715 && status == Some(0x16) + } +} + /// SPI mode as the device tree spells it. Mode 1 is CPOL=0/CPHA=1 (`spi-cpha`); /// mode 2 is CPOL=1/CPHA=0 (`spi-cpol`). #[derive(Clone, Copy, PartialEq, Eq)] @@ -152,6 +186,17 @@ const DART_T8112: DartWindow = DartWindow { range: [0x0, 0x4000, 0x0, 0xffff_0000], }; +/// The T8132 SEP DART input aperture is the BIT(40) alias itself, +/// 0x100_0000_4000 + 0xffff0000 (J715 ADT `dart-sep` vm-base-0 and +/// vm-size-0), given as a range with no offset. Unlike [`DART_HIGH_ALIAS`] +/// it reaches the SEP whether or not the DART driver applies +/// `apple,dma-offset`. +const DART_T8132: DartWindow = DartWindow { + name: "no offset, range 0x10000004000..+0xffff0000", + offset: [0x0, 0x0], + range: [0x100, 0x4000, 0x0, 0xffff_0000], +}; + pub(crate) struct PlatformProfile { pub(crate) name: &'static str, /// Capacity of the boot shared-memory window. The layout is still checked @@ -176,6 +221,7 @@ pub(crate) struct PlatformProfile { pub(crate) keybag_create: KeybagCreate, /// Require nonce authentication and signed sensor material before capture. pub(crate) nonce_session: bool, + pub(crate) sensor_root_key: SensorRootKey, /// Key-store protocol generation (see [`KeyStore`]). pub(crate) key_store: KeyStore, /// Platforms with reboot-tested owner export and user-before-master saves. @@ -218,6 +264,7 @@ pub(crate) const T8103: PlatformProfile = PlatformProfile { }, persistent_enrol: true, nonce_session: false, + sensor_root_key: SensorRootKey::None, // macOS shares this enclave: keep Linux to its own xART records. xart_guard: 1, }; @@ -258,6 +305,7 @@ pub(crate) const T6000: PlatformProfile = PlatformProfile { }, persistent_enrol: true, nonce_session: false, + sensor_root_key: SensorRootKey::None, // As T8103. On J316s, creating the identity keybag also rewrites a // session record that predates Linux. The guard refuses that, so the // keybag can only be created there with xart_guard=0. @@ -293,6 +341,7 @@ pub(crate) const T6020: PlatformProfile = PlatformProfile { key_store: KeyStore::Variant5, persistent_enrol: true, nonce_session: false, + sensor_root_key: SensorRootKey::None, // A warm-attach enclave shared with macOS, as on T8112. xart_guard: 1, }; @@ -348,6 +397,7 @@ pub(crate) const T8140: PlatformProfile = PlatformProfile { arg: -1, }, nonce_session: true, + sensor_root_key: SensorRootKey::J700, // Linux records from before the guard's list exist here, and Linux has // left the macOS records unchanged on this platform. xart_guard: 0, @@ -394,6 +444,7 @@ pub(crate) const T8112: PlatformProfile = PlatformProfile { key_store: KeyStore::Variant5, persistent_enrol: true, nonce_session: false, + sensor_root_key: SensorRootKey::None, // Guarded from its first Linux boot (2026-10-16): no record refused. xart_guard: 1, }; @@ -425,6 +476,27 @@ pub(crate) const T8122: PlatformProfile = PlatformProfile { ..T8112 }; +/// MacBook Air 15-inch M4 (J715). The J715 ADT has `sepfw-booted`: iBoot +/// hands over a running SEP, so this is T8122's warm registration, +/// shared-memory geometry and variant-5 key store. The SEP DART window is the +/// ADT's own (see [`DART_T8132`]). The Mesa sensor sits on spi2 at +/// `0x3ad108000`; its identity and wiring are unchecked, so capture stays +/// unqualified. Like the M3, its enclave is shared with macOS. +/// The successful J715 session uses nonce authentication, FDR MSRk recovery +/// after SESSION_SHARE status 0x16, and source-6 FSC2 calibration. +pub(crate) const T8132: PlatformProfile = PlatformProfile { + name: "T8132/J715", + nonce_session: true, + sensor_root_key: SensorRootKey::J715, + sensor: SensorProfile { + controller_base: 0x3_ad10_8000, + capture_qualified: false, + ..T8122.sensor + }, + dart_window: &DART_T8132, + ..T8122 +}; + static_assert!(T8103.shmem_capacity == 0x30000); static_assert!(T6020.shmem_capacity == 0x40000); static_assert!(T8140.shmem_capacity == 0x40000); @@ -440,6 +512,8 @@ static_assert!(T8112.sensor.controller_base == T8103.sensor.controller_base); static_assert!(T8122.shmem_capacity == T8112.shmem_capacity); static_assert!(T8122.sensor.controller_base == 0x2a1108000); static_assert!(T8122.wide_dma_mask); +static_assert!(T8132.wide_dma_mask); +static_assert!(T8132.shmem_capacity == T8122.shmem_capacity); /// Whether the machine root declares `compatible`. /// @@ -586,6 +660,10 @@ pub(crate) fn detect() -> Result<&'static PlatformProfile> { if machine_has(b"apple,t8122") && (machine_has(b"apple,j613") || machine_has(b"apple,j615")) { return Ok(&T8122); } + // Only the board whose ADT was read. + if machine_has(b"apple,t8132") && machine_has(b"apple,j715") { + return Ok(&T8132); + } Err(ENODEV) } @@ -630,6 +708,82 @@ mod tests { assert!(T8122.wide_dma_mask); assert!(T8122.shmem_capacity == T8112.shmem_capacity); assert!(T8122.sensor.controller_base == 0x2_a110_8000); + assert!(T8132.nonce_session); + assert!(T8132.wide_dma_mask); + assert!(T8132.xart_guard == T8122.xart_guard); + assert!(!T8132.sensor.capture_qualified); + } +} + +#[cfg(CONFIG_APPLE_SEP_KUNIT_TEST)] +#[kernel::macros::kunit_tests(apple_sep_sensor_root_key)] +mod root_key_tests { + use super::*; + + #[test] + fn non_nonce_boards_never_load_or_recover_a_root_key() { + // Given the existing non-nonce profiles, including inherited ones. + for profile in [&T8103, &T6000, &T6020, &T6020_SEPOS13, &T8112, &T8122] { + // When selecting their root-key policy. + let policy = profile.sensor_root_key; + // Then no file, upload or recovery is selected. + assert!(!profile.nonce_session); + assert!(policy.firmware().is_none()); + assert!(!policy.accepts_length(10_848)); + assert!(!policy.recover_share(Some(0x16))); + } + } + + #[test] + fn j700_preserves_its_nonempty_container_without_share_recovery() { + // Given J700's existing signed-container policy. + let policy = T8140.sensor_root_key; + // When selecting firmware and handling the observed refusal. + let name = policy.firmware(); + // Then the existing name and nonempty input rule remain in force. + assert!(T8140.nonce_session); + assert!(policy == SensorRootKey::J700); + assert!(name == Some(c"apple/msrk-j700.bin")); + assert!(!policy.accepts_length(0)); + assert!(policy.accepts_length(1)); + assert!(policy.accepts_length(1726)); + assert!(!policy.recover_share(Some(0x16))); + } + + #[test] + fn j715_accepts_only_the_qualified_fdr_body_length() { + // Given J715's nonce-session profile. + let policy = T8132.sensor_root_key; + // When selecting firmware and checking input sizes. + let name = policy.firmware(); + // Then the public board name selects only the qualified FDR size. + assert!(T8132.nonce_session); + assert!(policy == SensorRootKey::J715); + assert!(name == Some(c"apple/msrk-j715.bin")); + assert!(policy.accepts_length(10_848)); + for length in [0, 1, 1726, 10_847, 10_849, usize::MAX] { + assert!(!policy.accepts_length(length)); + } + } + + #[test] + fn j715_recovers_only_an_explicit_share_status_16() { + // Given J715's recovery policy. + let policy = T8132.sensor_root_key; + // When classifying answered and missing statuses. + let recover = policy.recover_share(Some(0x16)); + // Then only the observed refusal selects the single retry path. + assert!(recover); + for status in [ + None, + Some(0), + Some(1), + Some(0x15), + Some(0x17), + Some(u32::MAX), + ] { + assert!(!policy.recover_share(status)); + } } } @@ -665,4 +819,16 @@ mod dart_window_tests { assert_eq!(range(profile.dart_window), (0, 1 << 32)); } } + + #[test] + fn t8132_uses_the_adt_window_without_an_offset() { + assert_eq!(offset(T8132.dart_window), 0); + assert_eq!( + range(T8132.dart_window), + (0x100_0000_4000, 0x100_0000_4000 + 0xffff_0000) + ); + // The window sits at the BIT(40) alias, inside a 42-bit DMA mask. + assert!(range(T8132.dart_window).0 >= 1 << 40); + assert!(range(T8132.dart_window).1 <= 1 << 42); + } } diff --git a/drivers/soc/apple/sbio.rs b/drivers/soc/apple/sbio.rs index 9c69ca184c6912..e96a270c4385f3 100644 --- a/drivers/soc/apple/sbio.rs +++ b/drivers/soc/apple/sbio.rs @@ -2165,7 +2165,43 @@ impl SepData { } } - /// SENSOR_NONCE, the signed root key once a boot, and the session share. + fn load_sensor_root_key(&self) -> bool { + let policy = self.profile.sensor_root_key; + let Some(name) = policy.firmware() else { + return false; + }; + let firmware = match kernel::firmware::Firmware::request(name, &self.dev) { + Ok(firmware) => firmware, + Err(error) => { + dev_err!( + self.dev, + "sensor: root-key firmware {} unavailable: {:?}\n", + name, + error + ); + return false; + } + }; + if !policy.accepts_length(firmware.data().len()) { + dev_err!( + self.dev, + "sensor: root-key firmware {} has unsupported length {}\n", + name, + firmware.data().len() + ); + self.sensor_step_failed(c"SENSOR_ROOT_KEY"); + return false; + } + if !self.sensor_session_request(0x86, c"SENSOR_ROOT_KEY", firmware.data()) { + self.sensor_step_failed(c"SENSOR_ROOT_KEY"); + return false; + } + self.sensor_key_loaded.store(true, Relaxed); + self.sensor_trace(c"SENSOR_ROOT_KEY"); + true + } + + /// SENSOR_NONCE, the board's root-key policy, and the session share. fn sensor_nonce_session(&self, nonce: &[u8; 32]) -> bool { if *module_parameters::sensor_test_fault.value() != 0 && !self.sensor_test_fault_done.xchg(true, Relaxed) @@ -2182,28 +2218,11 @@ impl SepData { } self.sensor_trace(c"SENSOR_NONCE"); - if !self.sensor_key_loaded.load(Relaxed) { - let firmware = match kernel::firmware::Firmware::request( - c"apple/msrk-j700.bin", &self.dev, - ) { - Ok(firmware) => firmware, - Err(error) => { - dev_err!( - self.dev, - "sensor: signed root-key container unavailable: {:?}\n", - error - ); - return false; - } - }; - if firmware.data().is_empty() - || !self.sensor_session_request(0x86, c"SENSOR_ROOT_KEY", firmware.data()) - { - self.sensor_step_failed(c"SENSOR_ROOT_KEY"); - return false; - } - self.sensor_key_loaded.store(true, Relaxed); - self.sensor_trace(c"SENSOR_ROOT_KEY"); + if self.profile.sensor_root_key == profile::SensorRootKey::J700 + && !self.sensor_key_loaded.load(Relaxed) + && !self.load_sensor_root_key() + { + return false; } // Each identification starts a new sensor-side exchange, even though // the signed key remains loaded for the lifetime of the enclave. @@ -2506,8 +2525,35 @@ impl SepData { } fn establish_session(&self) -> bool { - let Some(share) = self.sensor_enclave_step(&crate::sbio::sbio_request_session_share()) - else { + let op = crate::sbio::sbio_request_session_share(); + let done = match self.sbio_transfer(&op) { + Ok(done) => done, + Err(error) => { + dev_warn!(self.dev, "sensor: SESSION_SHARE transfer failed: {:?}\n", error); + self.sensor_step_failed(c"SESSION_SHARE"); + return false; + } + }; + let share = if done.status.is_ok() { + self.sensor_trace(c"SESSION_SHARE"); + done.payload + } else if self + .profile + .sensor_root_key + .recover_share(done.status.answered()) + { + dev_info!(self.dev, "sensor: SESSION_SHARE status 0x16; loading FDR MSRk\n"); + if !self.load_sensor_root_key() { + return false; + } + // One retry only: this step does not enter the recovery path again. + let Some(share) = self.sensor_enclave_step(&op) else { + return false; + }; + share + } else { + dev_warn!(self.dev, "sensor: SESSION_SHARE refused with status {}\n", done.status); + self.sensor_step_failed(c"SESSION_SHARE"); return false; };