From 4891373a23583f7fc66277c510526d8df52a76a5 Mon Sep 17 00:00:00 2001 From: Dj Date: Sat, 10 Oct 2026 23:28:59 -0400 Subject: [PATCH] soc: apple: Add registry-v2 PMP and retain uncertain DMA owners Carry the matched T6050 descriptor, buffer and update protocol into the public PMP driver. Preserve the T6000 protocol and T8140 DMA-mask profile. Bound registry copies and validate the complete boot-argument table before changing it. Require an IOMMU domain before starting firmware. Own registry-v2 RTKit outside its callback context, register a drained reboot notifier, and verify the matched context-save and CPU-stop proof before releasing DMA. Probe and unbind failures stop or retain backing rather than panicking on an unconfirmed firmware transition. Port only the required auxiliary-buffer endpoint; keep newer public RTKit lifetime and inherited-buffer fixes. Expose the existing C handle and reboot declarations to the Rust owner without copying unrelated private transport features or GPU code. Native driver objects and production-parser fault tests pass. Local Rust validation uses opaque bindings for unrelated existing namespace and empty-struct bindgen layout failures, outside the published source. Hardware firmware exchange and teardown qualification remain pending. Signed-off-by: Dj --- .../bindings/soc/apple/apple,pmp.yaml | 30 +- .../driver-api/firmware/aurora-apif-pmp.rst | 35 ++ Documentation/driver-api/firmware/index.rst | 1 + drivers/soc/apple/pmp.rs | 498 ++++++++++++++++-- drivers/soc/apple/rtkit-internal.h | 4 +- drivers/soc/apple/rtkit.c | 31 +- rust/bindings/bindings_helper.h | 1 + rust/kernel/soc/apple/rtkit.rs | 6 +- tools/testing/selftests/aurora-apif/Makefile | 2 +- tools/testing/selftests/aurora-apif/pmp.py | 85 +++ 10 files changed, 641 insertions(+), 52 deletions(-) create mode 100644 Documentation/driver-api/firmware/aurora-apif-pmp.rst create mode 100755 tools/testing/selftests/aurora-apif/pmp.py diff --git a/Documentation/devicetree/bindings/soc/apple/apple,pmp.yaml b/Documentation/devicetree/bindings/soc/apple/apple,pmp.yaml index 9784c62fba89e..26b7fb66cec7f 100644 --- a/Documentation/devicetree/bindings/soc/apple/apple,pmp.yaml +++ b/Documentation/devicetree/bindings/soc/apple/apple,pmp.yaml @@ -1,4 +1,4 @@ -# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) AND GPL-2.0-only %YAML 1.2 --- $id: http://devicetree.org/schemas/soc/apple/apple,pmp.yaml# @@ -19,6 +19,7 @@ properties: compatible: oneOf: - const: apple,t6000-pmp-v2 + - const: apple,t6050-pmp-v2 - items: - const: apple,t8140-pmp-v2 - const: apple,t6000-pmp-v2 @@ -62,6 +63,22 @@ properties: minItems: 2 description: Host physical address and size pairs mapped into firmware PIO space. + apple,dram-channel-disable: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Firmware DRAM channel disable mask + apple,pmc: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Boot-producer PMC configuration + apple,pmc-msg-disabled: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Firmware PMC messaging configuration + apple,soc-chip-variant: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Firmware chip variant value + apple,pmc-pmgr: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Packed firmware PMC power-manager boot configuration + patternProperties: '^apple,tunable-': $ref: /schemas/types.yaml#/definitions/uint8-array @@ -75,6 +92,17 @@ required: - mbox-names - iommus +allOf: + - if: + properties: + compatible: + contains: + const: apple,t6050-pmp-v2 + then: + required: + - apple,board-id + - apple,dram-vendor-id + additionalProperties: false examples: diff --git a/Documentation/driver-api/firmware/aurora-apif-pmp.rst b/Documentation/driver-api/firmware/aurora-apif-pmp.rst new file mode 100644 index 0000000000000..e15e69bf07ee8 --- /dev/null +++ b/Documentation/driver-api/firmware/aurora-apif-pmp.rst @@ -0,0 +1,35 @@ +.. SPDX-License-Identifier: GPL-2.0-only + +Registry-v2 PMP guests +====================== + +The matched ``apple,t6050-pmp-v2`` firmware ABI uses 88-byte registry +descriptors, firmware-assigned 16-bit IDs, negotiated value buffers, and +update opcode ``0x36``. Linux validates descriptor sizes and names, rejects +duplicate live IDs, and bounds every shared-buffer copy. Boot arguments are +validated completely before any patch is written back. Board and calibration +values come from the boot description; no machine-name policy is embedded. + +The existing T6000 protocol and T8140 42-bit DMA mask remain unchanged. +Registry-v2 configures a 48-bit mailbox DMA address mask and requires an +attached IOMMU domain before starting the processor. Its APIF DART description +must retain the firmware streams, aperture and final-page guard reported by +the producer. A missing provider must not permit direct-DMA fallback. + +Probe accepts only the known cold context marker with the CPU stopped and no +retained DMA context. Unbind and reboot request RTKit idle, verify the matched +context-save marker/version and save counter, then clear CPU_RUN and check +its readback. Firmware allocations cannot be freed merely because RTKit +acknowledged a power state. A failed stop drains callbacks but retains both +shared RTKit backing and the PMP allocation owner until platform reset; it +also leaves the cold-reprobe check unable to admit the retained context. + +The driver owns registry-v2 RTKit outside the callback context, avoiding an +Arc ownership cycle. The notifier is removed and drained before the RTKit +handle or context is released. Probe failure after firmware start follows +the same stop-or-retain ownership path. Endpoint-7 auxiliary buffers are +session-owned; identical requests reuse them rather than replacing live DMA. + +This is support for the inspected firmware ABI, not a generic promise that +later PMP layouts use the same context registers. Hardware cold boot, +registry exchange, idle/save, reboot and unbind qualification remain pending. diff --git a/Documentation/driver-api/firmware/index.rst b/Documentation/driver-api/firmware/index.rst index 1867f10993e71..ada189185094c 100644 --- a/Documentation/driver-api/firmware/index.rst +++ b/Documentation/driver-api/firmware/index.rst @@ -7,6 +7,7 @@ Linux Firmware API aurora-apif aurora-apif-nvme aurora-apif-pcie + aurora-apif-pmp introduction core efi/index diff --git a/drivers/soc/apple/pmp.rs b/drivers/soc/apple/pmp.rs index 3356fc889b523..63b0da45806bc 100644 --- a/drivers/soc/apple/pmp.rs +++ b/drivers/soc/apple/pmp.rs @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0-only OR MIT +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) AND GPL-2.0-only #![recursion_limit = "2048"] //! Apple PMP driver @@ -16,9 +16,9 @@ use kernel::{ devres::Devres, dma::{ Coherent, - CoherentBox, + CoherentBox, // Device as _, - DmaMask, // + DmaMask, }, io::{ mem::IoMem, @@ -58,6 +58,7 @@ const OPC_FREE: u64 = 0x14; const OPC_SET_BUF: u64 = 0x30; const OPC_REGISTER_IOREG: u64 = 0x32; const OPC_SET_IOREG: u64 = 0x34; +const OPC_UPDATE_IOREG_V2: u64 = 0x36; const OPC_ACK_MASK: u64 = 0x1; const OPC_SHIFT: u32 = 48; const MALLOC_SIZE_MASK: u64 = 0xFFFFFF; @@ -70,11 +71,73 @@ const fn from_fourcc(b: &[u8; 4]) -> u32 { b[3] as u32 | (b[2] as u32) << 8 | (b[1] as u32) << 16 | (b[0] as u32) << 24 } +// Validate the complete table before changing it. The firmware table contains +// mixed-width entries, but the board configuration patched here is all u32. +fn patch_bootarg_table(bytes: &mut [u8], patches: &[(u32, u32)]) -> Result<()> { + for apply in [false, true] { + let mut rest = &mut *bytes; + while !rest.is_empty() { + if rest.len() < 8 { + return Err(EINVAL); + } + let (header, tail) = rest.split_at_mut(8); + let key = u32::from_le_bytes(header[..4].try_into().unwrap()); + let size = u32::from_le_bytes(header[4..].try_into().unwrap()) as usize; + if size > tail.len() { + return Err(EINVAL); + } + let (value, tail) = tail.split_at_mut(size); + if let Some((_, replacement)) = patches.iter().find(|(k, _)| *k == key) { + if size != mem::size_of::() { + return Err(EINVAL); + } + if apply { + value.copy_from_slice(&replacement.to_le_bytes()); + } + } + rest = tail; + } + } + Ok(()) +} + struct PmpAllocation { addr: u64, alloc: Coherent<[u8]>, } +struct RegistryDescriptor { + name: [u8; 48], + size: u32, + id: u16, +} + +impl RegistryDescriptor { + fn parse(bytes: &[u8]) -> Result { + // M5 firmware sends name[48], format[8], unit[8], then three u64s: + // value length, firmware-assigned ID, and flags. IDs are low16 on wire. + if bytes.len() < 88 { + return Err(EINVAL); + } + let size = u64::from_le_bytes(bytes[64..72].try_into().unwrap()); + let id = u64::from_le_bytes(bytes[72..80].try_into().unwrap()); + let name: [u8; 48] = bytes[..48].try_into().unwrap(); + if size > u32::MAX as u64 || id > u16::MAX as u64 || !name.contains(&0) { + return Err(EINVAL); + } + Ok(Self { + name, + size: size as u32, + id: id as u16, + }) + } +} + +struct RegistryEntry { + id: u16, + value: KVec, +} + struct IovaTableEntry { _host_addr: u64, _pio_base: u64, @@ -90,16 +153,20 @@ struct PmpState { iova_table: Option>, allocs: KVec, value_buf: Option, + host_value_buf: Option, ioreg_entries: KVec, + registry_entries: KVec, } impl PmpState { - fn new() -> Result { + fn new(registry_v2: bool) -> Result { Ok(PmpState { iova_table: None, allocs: KVec::with_capacity(10, GFP_KERNEL)?, value_buf: None, - ioreg_entries: KVec::with_capacity(340, GFP_KERNEL)?, + host_value_buf: None, + ioreg_entries: KVec::with_capacity(if registry_v2 { 0 } else { 340 }, GFP_KERNEL)?, + registry_entries: KVec::with_capacity(if registry_v2 { 340 } else { 0 }, GFP_KERNEL)?, }) } fn find_alloc(&self, addr: u64) -> Option { @@ -121,28 +188,39 @@ impl PmpState { #[pin_data] struct PmpData { dev: ARef, + registry_v2: bool, pmp_mmio: Pin>>>, asc_mmio: Pin>>>, #[pin] rtkit: Mutex>>, + // The registry-v2 RtKit is owned by PmpDriver, avoiding the context's Arc cycle. + // It drains callbacks before releasing the context and its DMA buffers. + #[pin] + registry_rtkit: Mutex>, + #[pin] + stopped: Mutex, #[pin] state: Mutex, } impl PmpData { - fn new(dev: &platform::Device) -> Result> { + fn new(dev: &platform::Device, registry_v2: bool) -> Result> { let pmp_req = dev.io_request_by_name(c"pmp").ok_or(EINVAL)?; let pmp_mmio = KBox::pin_init(pmp_req.iomap_sized::(), GFP_KERNEL)?; let asc_req = dev.io_request_by_name(c"asc").ok_or(EINVAL)?; let asc_mmio = KBox::pin_init(asc_req.iomap_sized::(), GFP_KERNEL)?; + let pmp_state = PmpState::new(registry_v2)?; Arc::pin_init( try_pin_init!( PmpData { dev: dev.as_ref().into(), + registry_v2, pmp_mmio, asc_mmio, rtkit <- new_mutex!(None), - state <- new_mutex!(PmpState::new()?) + registry_rtkit <- new_mutex!(None), + stopped <- new_mutex!(false), + state <- new_mutex!(pmp_state) } ), GFP_KERNEL, @@ -155,31 +233,94 @@ impl PmpData { Ok(()) } fn start(&self) -> Result<()> { + if self.registry_v2 { + let raw = self.context_handle()?; + // SAFETY: Probe owns RtKit and firmware has no application + // endpoint yet; C serializes system messages and power waits. + unsafe { + kernel::error::to_result(bindings::apple_rtkit_wake(raw))?; + return kernel::error::to_result(bindings::apple_rtkit_start_ep(raw, PMP_ENDPOINT)); + } + } let mut guard = self.rtkit.lock(); let mut rtk = guard.as_mut().as_pin_mut().unwrap(); rtk.as_mut().wake()?; rtk.start_endpoint(PMP_ENDPOINT) } + fn context_handle(&self) -> Result<*mut bindings::apple_rtkit> { + self.registry_rtkit + .lock() + .map(|p| p as *mut bindings::apple_rtkit) + .ok_or(ENODEV) + } + fn cold_preflight(&self, dev: &platform::Device) -> Result { + let io = self.pmp_mmio.access(dev.as_ref())?; + let asc = self.asc_mmio.access(dev.as_ref())?; + // Captured T6050 cold marker. Retained contexts carry live DMA + // addresses and cannot be cold-reprobed after their buffers retire. + if asc.read32(CPU_CONTROL) & CPU_RUN != 0 + || io.read64(0x7bcd8) != 0xfeed1b00 + || io.read64(0x7bce0) != 0 + || io.read64(0x7bcc8) != 0 + || io.read64(0x7bcd0) != 0 + || io.read32(0x7bcc4) != 0 + || io.read32(0x7bcc0) != 0 + { + return Err(EBUSY); + } + Ok(()) + } + fn stop_context(&self) -> Result { + let mut stopped = self.stopped.lock(); + if *stopped { + return Ok(()); + } + // SAFETY: Only probe, core unbind, and the registered reboot callback + // call this method. PmpDriver owns these resources through each call; + // its first dropped field unregisters and drains the callback. + let dev = unsafe { self.dev.as_bound() }; + let io = self.pmp_mmio.access(dev)?; + let saves = io.read32(0x7bcc4); + let wakes = io.read32(0x7bcc0); + let raw = self.context_handle()?; + // Do not hold the reply mutex while waiting: RX callbacks need it + // to finish outstanding registry exchanges. Ownership remains with + // the driver, and the stop mutex serializes unbind/reboot. + unsafe { kernel::error::to_result(bindings::apple_rtkit_idle(raw))? }; + let delta = io.read32(0x7bcc4).wrapping_sub(saves); + if io.read64(0x7bcd8) != 0xcafe4b0b + || io.read64(0x7bce0) != 0x10024a8 + || io.read64(0x7bcc8) == 0 + || delta == 0 + || delta >= 1u32 << 31 + || io.read32(0x7bcc0) != wakes + { + return Err(Error::from_errno(-(bindings::EPROTO as i32))); + } + let asc = self.asc_mmio.access(dev)?; + asc.write32(asc.read32(CPU_CONTROL) & !CPU_RUN, CPU_CONTROL); + if asc.read32(CPU_CONTROL) & CPU_RUN != 0 { + return Err(EIO); + } + *stopped = true; + Ok(()) + } fn patch_bootargs(&self, dev: &platform::Device, patches: &[(u32, u32)]) -> Result<()> { let io = self.pmp_mmio.access(dev.as_ref())?.relaxed(); let offset = io.read32(BOOTARGS_OFFSET) as usize; let size = io.read32(BOOTARGS_SIZE) as usize; + // Bound the allocation as well as the MMIO copy. Firmware supplies both + // fields, and an invalid table must not reach the writeback below. + if offset < BOOTARGS_SIZE + mem::size_of::() + || offset > io.maxsize() + || size == 0 + || size > io.maxsize() - offset + { + return Err(EINVAL); + } let mut arg_bytes = kvec![0u8; size]?; io.try_memcpy_fromio(&mut arg_bytes, offset)?; - let mut idx = 0; - while idx < size { - let key = u32::from_le_bytes(arg_bytes[idx..idx + 4].try_into().unwrap()); - let size = u32::from_le_bytes(arg_bytes[idx + 4..idx + 8].try_into().unwrap()) as usize; - idx += 8; - for (k, v) in patches.iter() { - if *k != key { - continue; - } - arg_bytes[idx..idx + size].copy_from_slice(&(*v as u64).to_le_bytes()[..size]); - break; - } - idx += size; - } + patch_bootarg_table(&mut arg_bytes, patches)?; io.try_memcpy_toio(offset, &arg_bytes) } fn get_iova_table(&self) -> Result { @@ -250,6 +391,12 @@ impl PmpData { fn free(&self, addr: u64) -> Result { let mut state = self.state.lock(); if let Some(idx) = state.find_alloc(addr) { + if state.value_buf == Some(addr) { + state.value_buf = None; + } + if state.host_value_buf == Some(addr) { + state.host_value_buf = None; + } state.allocs.swap_remove(idx); } else { dev_err!( @@ -274,11 +421,21 @@ impl PmpData { dev_err!(self.dev, "Unable to find buffer"); return Err(EIO); }; - if ptr_buf.size() < mem::size_of::() { + let pointers_size = if self.registry_v2 { 16 } else { 8 }; + if ptr_buf.size() < pointers_size { dev_err!(self.dev, "Buffer too small"); return Err(EIO); } - let ptr = unsafe { *(ptr_buf.as_ptr() as *const u64) }; + // Firmware relinquishes the request buffer until the reply is sent. + let pointers = unsafe { ptr_buf.as_ref() }; + let ptr = u64::from_le_bytes(pointers[..8].try_into().unwrap()); + if self.registry_v2 { + let host_ptr = u64::from_le_bytes(pointers[8..16].try_into().unwrap()); + if state.get_buf(ptr).is_none() || state.get_buf(host_ptr).is_none() { + return Err(EINVAL); + } + state.host_value_buf = Some(host_ptr); + } state.value_buf = Some(ptr); let msg = (OPC_SET_BUF | OPC_ACK_MASK) << OPC_SHIFT; Ok(msg) @@ -329,7 +486,6 @@ impl PmpData { } size = len as u32; } else { - dev_info!(self.dev, "unknown property {:?}", name_str); } } state.ioreg_entries.push(size, GFP_KERNEL)?; @@ -347,6 +503,88 @@ impl PmpData { let msg = (OPC_SET_IOREG | OPC_ACK_MASK) << OPC_SHIFT | len; Ok(msg) } + fn register_ioreg_v2(&self, addr: u64) -> Result { + let mut state = self.state.lock(); + let request = state.get_buf(addr).ok_or(EINVAL)?; + // SAFETY: The firmware has transferred this allocation for the request + // and waits for our response before reusing it. + let descriptor = RegistryDescriptor::parse(unsafe { request.as_ref() })?; + if state.registry_entries.iter().any(|e| e.id == descriptor.id) { + return Err(EINVAL); + } + let value_addr = state.value_buf.ok_or(EINVAL)?; + let value_buf = state.get_buf(value_addr).ok_or(EINVAL)?; + let value = if descriptor.size == 0 { + let name = CStr::from_bytes_until_nul(&descriptor.name).map_err(|_| EINVAL)?; + let property = CString::try_from_fmt(fmt!("apple,tunable-{name}"))?; + let node = self.dev.fwnode().ok_or(EIO)?; + if !node.property_present(&property) { + return Ok((OPC_REGISTER_IOREG | OPC_ACK_MASK) << OPC_SHIFT); + } + let len = node.property_count_elem::(&property)?; + if len > value_buf.size() || len > u32::MAX as usize { + return Err(EINVAL); + } + let value = node + .property_read_array_vec::(&property, len)? + .required_by(&self.dev)?; + // SAFETY: This is the negotiated shared value buffer; firmware + // consumes the supplied bytes only after our acknowledgement. + unsafe { value_buf.as_mut()[..len].copy_from_slice(&value) }; + value + } else { + let len = descriptor.size as usize; + if len > value_buf.size() { + return Err(EINVAL); + } + // SAFETY: As above, firmware waits for the acknowledgement before + // reusing the shared value buffer. + let mut value = KVec::with_capacity(len, GFP_KERNEL)?; + value.extend_from_slice(unsafe { &value_buf.as_ref()[..len] }, GFP_KERNEL)?; + value + }; + let size = value.len() as u64; + if size != 0 { + state.registry_entries.push( + RegistryEntry { + id: descriptor.id, + value, + }, + GFP_KERNEL, + )?; + } + // The ID came from the firmware descriptor; the reply carries length. + Ok((OPC_REGISTER_IOREG | OPC_ACK_MASK) << OPC_SHIFT | size) + } + fn change_ioreg_v2(&self, id: u16, remove: bool) -> Result { + let mut state = self.state.lock(); + let opcode = if remove { 0x35 } else { 0x37 }; + let Some(index) = state.registry_entries.iter().position(|e| e.id == id) else { + return Ok(opcode << OPC_SHIFT); + }; + let size = state.registry_entries[index].value.len(); + if remove { + state.registry_entries.swap_remove(index); + } else { + let addr = state.value_buf.ok_or(EINVAL)?; + let alloc_index = state.find_alloc(addr).ok_or(EINVAL)?; + let PmpState { + allocs, + registry_entries, + .. + } = &mut *state; + let buffer = &allocs[alloc_index].alloc; + if size > buffer.size() { + return Err(EINVAL); + } + // SAFETY: Firmware waits for this update's reply before reusing + // the shared buffer, and the stored length is bounds-checked. + registry_entries[index] + .value + .copy_from_slice(unsafe { &buffer.as_ref()[..size] }); + } + Ok(opcode << OPC_SHIFT | size as u64) + } fn recv_message(&self, msg: u64) -> Result<()> { let opc = (msg >> OPC_SHIFT) & 0xFF; let reply = match opc { @@ -354,13 +592,33 @@ impl PmpData { OPC_MALLOC => self.malloc(msg & MALLOC_SIZE_MASK)?, OPC_FREE => self.free(msg & MSG_IOVA_MASK)?, OPC_SET_BUF => self.set_buf(msg & MSG_IOVA_MASK)?, + OPC_REGISTER_IOREG if self.registry_v2 => { + self.register_ioreg_v2(msg & MSG_IOVA_MASK)? + } OPC_REGISTER_IOREG => self.register_ioreg(msg & MSG_IOVA_MASK)?, + OPC_SET_IOREG if self.registry_v2 => self.change_ioreg_v2(msg as u16, true)?, OPC_SET_IOREG => self.set_ioreg(msg & SET_IOREG_INDEX_MASK)?, + OPC_UPDATE_IOREG_V2 if self.registry_v2 => self.change_ioreg_v2(msg as u16, false)?, _ => { dev_err!(self.dev, "Got unknown message {}", msg); return Err(EIO); } }; + if self.registry_v2 { + let guard = self.registry_rtkit.lock(); + let raw = guard.ok_or(ENODEV)? as *mut bindings::apple_rtkit; + // SAFETY: The driver's owner drains C RX before releasing this + // context; C mailbox TX is internally serialized. + return unsafe { + kernel::error::to_result(bindings::apple_rtkit_send_message( + raw, + PMP_ENDPOINT, + reply, + core::ptr::null_mut(), + false, + )) + }; + } let mut rtk_guard = self.rtkit.lock(); let rtk = rtk_guard.as_mut().as_pin_mut().unwrap(); rtk.send_message(PMP_ENDPOINT, reply)?; @@ -398,21 +656,92 @@ impl rtkit::Operations for PmpData { } } +struct PmpReboot { + block: bindings::notifier_block, + data: Arc, +} + +unsafe impl Send for PmpReboot {} + +unsafe extern "C" fn pmp_reboot( + block: *mut bindings::notifier_block, + _event: usize, + _arg: *mut core::ffi::c_void, +) -> core::ffi::c_int { + // SAFETY: This exact stable KBox block is registered while its driver + // owns RtKit. Unregister drains callbacks before that owner is dropped. + let state = unsafe { &*kernel::container_of!(block, PmpReboot, block) }; + match state.data.stop_context() { + Ok(()) => bindings::NOTIFY_OK as i32, + Err(e) => { + dev_err!(state.data.dev, "PMP reboot stop failed: {:?}", e); + bindings::NOTIFY_BAD as i32 + } + } +} + #[allow(dead_code)] -struct PmpDriver(Arc); +struct PmpDriver { + reboot: Option, + // Field order drains RX before releasing the context's final Arc. + registry_rtkit: Option>, + data: Arc, +} + +impl Drop for PmpDriver { + fn drop(&mut self) { + if self.registry_rtkit.is_some() { + // Also covers failure to allocate the core's driver-data box + // after probe constructed this owner and started firmware. + if let Err(e) = self.data.stop_context() { + dev_err!( + self.data.dev, + "PMP stop failed; retaining DMA until platform reset: {:?}", + e + ); + self.registry_rtkit + .as_mut() + .unwrap() + .retain_shared_buffers_on_drop(); + core::mem::forget(self.data.clone()); + } + } + } +} + +struct PmpRebootRegistration(KBox); + +impl Drop for PmpRebootRegistration { + fn drop(&mut self) { + // Unregister holds no stop/reply mutex. The chain joins callbacks + // before the boxed block and its context reference are freed. + let ret = unsafe { bindings::unregister_reboot_notifier(&mut self.0.block) }; + assert_eq!(ret, 0, "PMP notifier could not be drained"); + } +} /// Per-SoC configuration. struct PmpHwConfig { /// DMA mask to set before the firmware allocates anything, or `None` to /// keep the default. dma_mask: Option, + registry_v2: bool, } -const HW_CFG_DEFAULT: PmpHwConfig = PmpHwConfig { dma_mask: None }; +const HW_CFG_DEFAULT: PmpHwConfig = PmpHwConfig { + dma_mask: None, + registry_v2: false, +}; /// T8140: the DART translates 42 bits and its DMA window starts at 1 TiB. const HW_CFG_T8140: PmpHwConfig = PmpHwConfig { dma_mask: Some(DmaMask::new::<42>()), + registry_v2: false, +}; + +const HW_CFG_T6050: PmpHwConfig = PmpHwConfig { + dma_mask: Some(DmaMask::new::<48>()), + registry_v2: true, }; kernel::of_device_table!( @@ -420,8 +749,9 @@ kernel::of_device_table!( MODULE_OF_TABLE, ::IdInfo, [ - (of::DeviceId::new(c"apple,t8140-pmp-v2"), &HW_CFG_T8140), (of::DeviceId::new(c"apple,t6000-pmp-v2"), &HW_CFG_DEFAULT), + (of::DeviceId::new(c"apple,t8140-pmp-v2"), &HW_CFG_T8140), + (of::DeviceId::new(c"apple,t6050-pmp-v2"), &HW_CFG_T6050), ] ); @@ -436,36 +766,112 @@ impl platform::Driver for PmpDriver { ) -> impl PinInit { let dev: ARef = pdev.as_ref().into(); let cfg = info.ok_or(ENODEV)?; + let registry_v2 = cfg.registry_v2; if let Some(mask) = cfg.dma_mask { - // SAFETY: No DMA allocations or mappings exist for this device - // yet. + // SAFETY: No allocations, mappings or firmware execution exist yet. unsafe { pdev.dma_set_mask_and_coherent(mask)? }; } - let data = PmpData::new(pdev)?; + if registry_v2 { + // Deferred-probe timeout can otherwise allow DMA to fall back to + // physical addresses when the required DART provider failed. + // SAFETY: The platform device is live throughout probe. + if unsafe { bindings::iommu_get_domain_for_dev(dev.as_raw()) }.is_null() { + dev_err!( + dev, + "PMP requires an attached IOMMU domain before CPU start" + ); + return Err(EPROBE_DEFER); + } + // PMP messages carry a 48-bit DMA address. The inherited DART + // aperture starts above 32 bits; the IOMMU constrains this mask to + // that aperture. Configure before starting any RTKit allocations. + // SAFETY: Probe has not published data or started firmware/DMA. + } + let data = PmpData::new(pdev, registry_v2)?; + if registry_v2 { + data.cold_preflight(pdev)?; + } let node = dev.fwnode().ok_or(EIO)?; let dvid = node .property_read(c"apple,dram-vendor-id") .required_by(&dev)?; let bdid = node.property_read(c"apple,board-id").required_by(&dev)?; - match node.property_read(c"apple,dram-capacity").optional() { - Some(dcap) => data.patch_bootargs( - pdev, - &[ - (from_fourcc(b"BDID"), bdid), - (from_fourcc(b"DCAP"), dcap), - (from_fourcc(b"DVID"), dvid), - ], - )?, - None => data.patch_bootargs( - pdev, - &[(from_fourcc(b"BDID"), bdid), (from_fourcc(b"DVID"), dvid)], - )?, - }; + let mut patches = KVec::with_capacity(9, GFP_KERNEL)?; + patches.push((from_fourcc(b"BDID"), bdid), GFP_KERNEL)?; + patches.push((from_fourcc(b"DVID"), dvid), GFP_KERNEL)?; + // Optional values are supplied by the bootloader's board profile. Do + // not replace firmware defaults when a property is absent. + for (property, key) in [ + (c"apple,dram-capacity", b"DCAP"), + (c"apple,dram-channel-disable", b"DCHD"), + (c"apple,pmc", b"PMC_"), + (c"apple,pmc-msg-disabled", b"PMCX"), + (c"apple,soc-chip-variant", b"CVAR"), + ] { + // Preserve the existing T6000 boot profile. The extra board/PMC + // fields belong to the registry-v2 firmware ABI. + if !registry_v2 && key != b"DCAP" { + continue; + } + if node.property_present(property) { + let value = node.property_read::(property).required_by(&dev)?; + patches.push((from_fourcc(key), value), GFP_KERNEL)?; + } + } + if registry_v2 && node.property_present(c"apple,pmc-pmgr") { + let value = node + .property_read::(c"apple,pmc-pmgr") + .required_by(&dev)?; + patches.push((from_fourcc(b"PMCV"), value & 1), GFP_KERNEL)?; + patches.push((from_fourcc(b"PMCB"), (value >> 3) & 1), GFP_KERNEL)?; + } + data.patch_bootargs(pdev, &patches)?; let rtkit = rtkit::RtKit::::new(&dev, None, 0, data.clone())?; + if registry_v2 { + *data.registry_rtkit.lock() = Some(rtkit.as_raw() as usize); + let mut owner = PmpDriver { + reboot: None, + registry_rtkit: Some(rtkit), + data, + }; + owner.data.start_cpu(pdev)?; + owner.data.start()?; + let mut reboot = KBox::new( + PmpReboot { + block: bindings::notifier_block { + notifier_call: Some(pmp_reboot), + next: core::ptr::null_mut(), + priority: -10, + }, + data: owner.data.clone(), + }, + GFP_KERNEL, + )?; + // SAFETY: The boxed notifier has a stable address until its + // registration guard unregisters and drains the callback chain. + let ret = unsafe { bindings::register_reboot_notifier(&mut reboot.block) }; + if ret != 0 { + return Err(Error::from_errno(ret)); + } + owner.reboot = Some(PmpRebootRegistration(reboot)); + return Ok(owner); + } *data.rtkit.lock() = Some(rtkit); data.start_cpu(pdev)?; data.start()?; - Ok(PmpDriver(data)) + Ok(PmpDriver { + reboot: None, + registry_rtkit: None, + data, + }) + } + + fn unbind(_dev: &platform::Device, this: Pin<&Self>) { + if this.data.registry_v2 { + if let Err(e) = this.data.stop_context() { + dev_err!(this.data.dev, "PMP unbind stop failed: {:?}", e); + } + } } } @@ -473,5 +879,5 @@ module_platform_driver! { type: PmpDriver, name: "apple_pmp", description: "Apple Power Management Processor", - license: "Dual MIT/GPL", + license: "GPL", } diff --git a/drivers/soc/apple/rtkit-internal.h b/drivers/soc/apple/rtkit-internal.h index 927f33f1642e6..fe910d13a7481 100644 --- a/drivers/soc/apple/rtkit-internal.h +++ b/drivers/soc/apple/rtkit-internal.h @@ -1,4 +1,4 @@ -/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) AND GPL-2.0-only */ /* * Apple RTKit IPC library * Copyright (C) The Asahi Linux Contributors @@ -57,6 +57,8 @@ struct apple_rtkit { struct apple_rtkit_shmem ioreport_buffer; struct apple_rtkit_shmem crashlog_buffer; struct apple_rtkit_shmem oslog_buffer; + struct apple_rtkit_shmem pmp_aux_buffer; + bool pmp_registry_v2; struct apple_rtkit_shmem syslog_buffer; char *syslog_msg_buffer; diff --git a/drivers/soc/apple/rtkit.c b/drivers/soc/apple/rtkit.c index eb0da50d092a2..736676d6c8366 100644 --- a/drivers/soc/apple/rtkit.c +++ b/drivers/soc/apple/rtkit.c @@ -1,10 +1,11 @@ -// SPDX-License-Identifier: GPL-2.0-only OR MIT +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) AND GPL-2.0-only /* * Apple RTKit IPC library * Copyright (C) The Asahi Linux Contributors */ #include "rtkit-internal.h" +#include enum { APPLE_RTKIT_PWR_STATE_OFF = 0x00, /* power off, cannot be restarted */ @@ -21,6 +22,7 @@ enum { APPLE_RTKIT_EP_SYSLOG = 2, APPLE_RTKIT_EP_DEBUG = 3, APPLE_RTKIT_EP_IOREPORT = 4, + APPLE_RTKIT_EP_PMP_AUX = 7, APPLE_RTKIT_EP_OSLOG = 8, APPLE_RTKIT_EP_TRACEKIT = 0xa, }; @@ -348,7 +350,9 @@ static int apple_rtkit_common_rx_get_buffer(struct apple_rtkit *rtk, buffer->iova >> 12); } else { reply = FIELD_PREP(APPLE_RTKIT_SYSLOG_TYPE, - APPLE_RTKIT_BUFFER_REQUEST); + ep == APPLE_RTKIT_EP_PMP_AUX ? + 5 : + APPLE_RTKIT_BUFFER_REQUEST); reply |= FIELD_PREP(APPLE_RTKIT_BUFFER_REQUEST_SIZE, buffer->size >> 12); reply |= FIELD_PREP(APPLE_RTKIT_BUFFER_REQUEST_IOVA, @@ -614,6 +618,22 @@ static void apple_rtkit_oslog_rx(struct apple_rtkit *rtk, u64 msg) } } +/* Registry-v2 firmware requests one 8 KiB auxiliary buffer at endpoint 7. + * Identical requests reuse the session buffer; changed geometry is refused. + */ +static void apple_rtkit_pmp_aux_rx(struct apple_rtkit *rtk, u64 msg) +{ + if (!rtk->pmp_registry_v2 || + msg != (FIELD_PREP(APPLE_RTKIT_SYSLOG_TYPE, + APPLE_RTKIT_BUFFER_REQUEST) | + FIELD_PREP(APPLE_RTKIT_BUFFER_REQUEST_SIZE, 2))) { + dev_err(rtk->dev, "RTKit: refused auxiliary buffer request\n"); + return; + } + apple_rtkit_common_rx_get_buffer(rtk, &rtk->pmp_aux_buffer, + APPLE_RTKIT_EP_PMP_AUX, msg); +} + static void apple_rtkit_rx_work(struct work_struct *work) { struct apple_rtkit_rx_work *rtk_work = @@ -636,6 +656,9 @@ static void apple_rtkit_rx_work(struct work_struct *work) case APPLE_RTKIT_EP_IOREPORT: apple_rtkit_ioreport_rx(rtk, rtk_work->msg); break; + case APPLE_RTKIT_EP_PMP_AUX: + apple_rtkit_pmp_aux_rx(rtk, rtk_work->msg); + break; case APPLE_RTKIT_EP_OSLOG: apple_rtkit_oslog_rx(rtk, rtk_work->msg); break; @@ -850,6 +873,8 @@ static struct apple_rtkit *__apple_rtkit_init(struct device *dev, void *cookie, rtk->dev = dev; rtk->cookie = cookie; rtk->ops = ops; + rtk->pmp_registry_v2 = + of_device_is_compatible(dev->of_node, "apple,t6050-pmp-v2"); init_completion(&rtk->epmap_completion); init_completion(&rtk->iop_pwr_ack_completion); @@ -942,6 +967,7 @@ int apple_rtkit_reinit(struct apple_rtkit *rtk) apple_rtkit_free_buffer(rtk, &rtk->ioreport_buffer); apple_rtkit_free_buffer(rtk, &rtk->crashlog_buffer); apple_rtkit_free_buffer(rtk, &rtk->oslog_buffer); + apple_rtkit_free_buffer(rtk, &rtk->pmp_aux_buffer); apple_rtkit_free_buffer(rtk, &rtk->syslog_buffer); kfree(rtk->syslog_msg_buffer); @@ -1191,6 +1217,7 @@ void apple_rtkit_free(struct apple_rtkit *rtk) apple_rtkit_free_buffer(rtk, &rtk->ioreport_buffer); apple_rtkit_free_buffer(rtk, &rtk->crashlog_buffer); apple_rtkit_free_buffer(rtk, &rtk->oslog_buffer); + apple_rtkit_free_buffer(rtk, &rtk->pmp_aux_buffer); apple_rtkit_free_buffer(rtk, &rtk->syslog_buffer); kfree(rtk->syslog_msg_buffer); diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index d819681a1e9c6..f02df218d23dc 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -94,6 +94,7 @@ #include #include #include +#include #include #include #include diff --git a/rust/kernel/soc/apple/rtkit.rs b/rust/kernel/soc/apple/rtkit.rs index ab3fc6c33fe5d..1137d458c7bed 100644 --- a/rust/kernel/soc/apple/rtkit.rs +++ b/rust/kernel/soc/apple/rtkit.rs @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0-only OR MIT +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) AND GPL-2.0-only //! Support for Apple RTKit coprocessors. //! @@ -264,6 +264,10 @@ impl RtKit { }) } + /// Borrows the C handle without transferring ownership. + /// The owner must outlive each unsafe C operation and its callbacks. + pub fn as_raw(&self) -> *mut bindings::apple_rtkit { self.rtk } + /// Requests acknowledged RTKit shutdown using the C core's bounded waits. /// /// The caller must not hold a lock needed by its receive callbacks: the diff --git a/tools/testing/selftests/aurora-apif/Makefile b/tools/testing/selftests/aurora-apif/Makefile index 49020bdc3dfee..e77c69c3de783 100644 --- a/tools/testing/selftests/aurora-apif/Makefile +++ b/tools/testing/selftests/aurora-apif/Makefile @@ -1,3 +1,3 @@ # SPDX-License-Identifier: GPL-2.0-only -TEST_PROGS := apif_mmio.py +TEST_PROGS := apif_mmio.py pmp.py include ../lib.mk diff --git a/tools/testing/selftests/aurora-apif/pmp.py b/tools/testing/selftests/aurora-apif/pmp.py new file mode 100755 index 0000000000000..dd0062d71c053 --- /dev/null +++ b/tools/testing/selftests/aurora-apif/pmp.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only +"""Exercise the production PMP parsers without firmware or kernel mappings.""" +from pathlib import Path +import re +import shutil +import subprocess +import sys +import tempfile + +ROOT = Path(__file__).resolve().parents[4] + + +def block(source, pattern): + match = re.search(pattern, source, re.M) + if not match: + raise RuntimeError(f"missing production block {pattern}") + start = match.start() + brace = source.index("{", match.end()) + depth = 0 + for pos in range(brace, len(source)): + if source[pos] == "{": + depth += 1 + elif source[pos] == "}": + depth -= 1 + if not depth: + return source[start:pos + 1] + raise RuntimeError("unterminated production block") + + +def main(): + compiler = shutil.which("rustc") + print("TAP version 13\n1..1", flush=True) + if not compiler: + print("ok 1 - PMP parsers # SKIP rustc unavailable") + return 4 + source = (ROOT / "drivers/soc/apple/pmp.rs").read_text() + program = r''' +use core::mem; +type Result = core::result::Result; +const EINVAL: i32 = -22; +''' + for pattern in [r"^fn patch_bootarg_table\(", r"^struct RegistryDescriptor\b", + r"^impl RegistryDescriptor\b"]: + program += block(source, pattern) + "\n" + program += r''' +fn main() { + let mut d = [0u8; 88]; + d[0] = b'x'; d[64..72].copy_from_slice(&16u64.to_le_bytes()); + d[72..80].copy_from_slice(&65535u64.to_le_bytes()); + let v = RegistryDescriptor::parse(&d).unwrap(); + assert_eq!(v.name[0], b'x'); assert_eq!(v.size, 16); assert_eq!(v.id, 65535); + assert!(RegistryDescriptor::parse(&d[..87]).is_err()); + d[72..80].copy_from_slice(&65536u64.to_le_bytes()); + assert!(RegistryDescriptor::parse(&d).is_err()); + d[72..80].copy_from_slice(&0u64.to_le_bytes()); + d[64..72].copy_from_slice(&(u32::MAX as u64 + 1).to_le_bytes()); + assert!(RegistryDescriptor::parse(&d).is_err()); + d[64..72].copy_from_slice(&0u64.to_le_bytes()); d[..48].fill(b'a'); + assert!(RegistryDescriptor::parse(&d).is_err()); + let mut table = [0u8; 20]; + table[..4].copy_from_slice(&7u32.to_le_bytes()); + table[4..8].copy_from_slice(&4u32.to_le_bytes()); + table[12..16].copy_from_slice(&9u32.to_le_bytes()); + table[16..20].copy_from_slice(&16u32.to_le_bytes()); + let before = table; + assert!(patch_bootarg_table(&mut table, &[(7, 42)]).is_err()); + assert_eq!(table, before); // Later malformed entries must prevent all writes. + patch_bootarg_table(&mut table[..12], &[(7, 42)]).unwrap(); + assert_eq!(&table[8..12], &42u32.to_le_bytes()); + assert!(patch_bootarg_table(&mut table[..7], &[(7, 42)]).is_err()); +} +''' + with tempfile.TemporaryDirectory(prefix="apif-pmp-test-") as directory: + path = Path(directory) / "pmp" + path.with_suffix(".rs").write_text(program) + subprocess.run([compiler, "--edition=2021", "-Dwarnings", + str(path.with_suffix(".rs")), "-o", str(path)], check=True) + subprocess.run([str(path)], check=True) + print("ok 1 - PMP production descriptors and transactional boot arguments") + return 0 + + +if __name__ == "__main__": + sys.exit(main())