blob: 353fb56c405a6091fb0f00bc8e360c283dd5443a [file]
// Copyright 2026 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
//! MP (multiprocessor) subsystem interface.
use super::deadline::Deadline;
pub use super::types::{cpu_mask_t, cpu_num_t};
use zx_status::Status;
use zx_types::zx_instant_mono_t;
/// Maximum number of CPUs the kernel is built to support; the Rust view of the
/// C++ `SMP_MAX_CPUS` build parameter.
pub const SMP_MAX_CPUS: usize =
zr::parse_usize(env!("SMP_MAX_CPUS")).expect("SMP_MAX_CPUS invalid");
pub const INVALID_CPU: cpu_num_t = super::types::INVALID_CPU;
pub const CPU_MASK_ALL: cpu_mask_t = super::types::CPU_MASK_ALL;
/// C++ `MpUnplugEvent`, opaque here. An unplugging CPU is handed one and signals
/// it once its state is flushed.
#[repr(C)]
pub struct MpUnplugEvent {
_opaque: [u8; 0],
}
/// Signals `event`, releasing the CPU that requested this CPU's unplug.
///
/// # Safety
///
/// `event` must be the pointer handed to `arch_flush_state_and_halt()`.
pub unsafe fn unplug_event_signal(event: *mut MpUnplugEvent) {
// SAFETY: forwarded to `MpUnplugEvent::Signal()` with a caller-verified pointer.
unsafe { cpp_mp_unplug_event_signal(event) };
}
unsafe extern "C" {
fn cpp_mp_set_cpu_online(cpu: cpu_num_t, online: bool);
fn cpp_mp_set_curr_cpu_online(online: bool);
fn cpp_mp_get_online_mask() -> cpu_mask_t;
fn cpp_mp_is_cpu_online(cpu: cpu_num_t) -> bool;
fn cpp_mp_signal_curr_cpu_ready();
fn cpp_mp_wait_for_all_cpus_ready(deadline: *const Deadline) -> zx_types::zx_status_t;
fn cpp_mp_reschedule(mask: cpu_mask_t, flags: u32);
fn cpp_mp_reschedule_self();
fn cpp_mp_interrupt(target: MpIpiTarget, mask: cpu_mask_t);
fn cpp_mp_unplug_event_signal(event: *mut MpUnplugEvent);
fn cpp_mp_mbx_generic_irq();
fn cpp_mp_mbx_reschedule_irq();
fn cpp_mp_mbx_interrupt_irq();
fn cpp_mp_sync_exec(
target: MpIpiTarget,
mask: cpu_mask_t,
task: SyncTask,
context: *mut core::ffi::c_void,
);
fn cpp_mp_hotplug_cpu_mask(mask: cpu_mask_t) -> zx_types::zx_status_t;
fn cpp_mp_hotplug_cpu(cpu: cpu_num_t) -> zx_types::zx_status_t;
fn cpp_mp_unplug_current_cpu();
fn cpp_mp_unplug_cpu_mask(
mask: cpu_mask_t,
deadline: zx_instant_mono_t,
) -> zx_types::zx_status_t;
fn cpp_mp_unplug_cpu(cpu: cpu_num_t) -> zx_types::zx_status_t;
}
/// Target CPUs for inter-processor interrupts (IPIs).
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MpIpiTarget {
/// Target a specific set of CPUs given by bitmask.
Mask = 0,
/// Target all online CPUs.
All = 1,
/// Target all online CPUs except the calling CPU.
///
/// Note: Calling with `AllButLocal` requires interrupts to be disabled.
AllButLocal = 2,
}
/// IPI types dispatched across processors.
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MpIpi {
/// Generic task execution IPI.
Generic = 0,
/// Reschedule IPI.
Reschedule = 1,
/// Interrupt-only IPI.
Interrupt = 2,
/// Halt CPU IPI.
Halt = 3,
}
zr::static_assert!(core::mem::size_of::<MpIpiTarget>() == 1);
zr::static_assert!(core::mem::size_of::<MpIpi>() == 1);
/// Initializes the MP subsystem.
pub fn init() {}
/// Converts a CPU ID to a CPU bitmask.
pub const fn cpu_num_to_mask(cpu: cpu_num_t) -> cpu_mask_t {
if cpu < cpu_mask_t::BITS { 1 << cpu } else { 0 }
}
/// Sets whether a given CPU is online and initialized.
pub fn set_cpu_online(cpu: cpu_num_t, online: bool) {
// SAFETY: FFI call with value arguments has no safety preconditions.
unsafe { cpp_mp_set_cpu_online(cpu, online) };
}
/// Sets whether the calling CPU is online and initialized.
pub fn set_curr_cpu_online(online: bool) {
// SAFETY: FFI call has no safety preconditions.
unsafe { cpp_mp_set_curr_cpu_online(online) };
}
/// Returns the bitmask of currently online CPUs.
pub fn get_online_mask() -> cpu_mask_t {
// SAFETY: FFI call reads the kernel atomic online mask.
unsafe { cpp_mp_get_online_mask() }
}
/// Checks whether a specific CPU is currently online.
pub fn is_cpu_online(cpu: cpu_num_t) -> bool {
// SAFETY: FFI call with value argument has no safety preconditions.
unsafe { cpp_mp_is_cpu_online(cpu) }
}
/// Signals that the current CPU has reached the ready state during startup.
pub fn signal_curr_cpu_ready() {
// SAFETY: FFI call has no safety preconditions.
unsafe { cpp_mp_signal_curr_cpu_ready() };
}
/// Waits until all CPUs in the system have signalled that they are ready, up to `deadline`.
pub fn wait_for_all_cpus_ready(deadline: Deadline) -> Result<(), Status> {
// SAFETY: `&deadline` is a valid pointer to a `Deadline` struct for the call duration.
let status = unsafe { cpp_mp_wait_for_all_cpus_ready(&deadline) };
Status::ok(status)
}
/// Triggers reschedules on CPUs specified by `mask`.
pub fn reschedule(mask: cpu_mask_t, flags: u32) {
// SAFETY: FFI call with value arguments has no safety preconditions.
unsafe { cpp_mp_reschedule(mask, flags) };
}
/// Triggers a reschedule on the calling CPU using a self-IPI.
pub fn reschedule_self() {
// SAFETY: FFI call has no safety preconditions.
unsafe { cpp_mp_reschedule_self() };
}
/// Triggers an interrupt on target CPUs without a corresponding reschedule.
pub fn interrupt(target: MpIpiTarget, mask: cpu_mask_t) {
// SAFETY: FFI call with value arguments has no safety preconditions.
unsafe { cpp_mp_interrupt(target, mask) };
}
/// Generic mailbox interrupt handler.
pub fn mbx_generic_irq() {
// SAFETY: FFI call has no safety preconditions.
unsafe { cpp_mp_mbx_generic_irq() };
}
/// Reschedule mailbox interrupt handler.
pub fn mbx_reschedule_irq() {
// SAFETY: FFI call has no safety preconditions.
unsafe { cpp_mp_mbx_reschedule_irq() };
}
/// Interrupt-only mailbox interrupt handler.
pub fn mbx_interrupt_irq() {
// SAFETY: FFI call has no safety preconditions.
unsafe { cpp_mp_mbx_interrupt_irq() };
}
/// Raw function pointer callback for synchronous cross-CPU task execution.
pub type SyncTask = unsafe extern "C" fn(context: *mut core::ffi::c_void);
/// Executes a raw task function synchronously on the specified CPUs and blocks
/// until all target CPUs have completed execution.
///
/// # Safety
///
/// The caller must ensure that:
/// - `task` is safe to call concurrently from multiple CPUs with `context`.
/// - `context` remains valid for the duration of the execution.
/// - If `target == MpIpiTarget::AllButLocal`, interrupts are disabled on the calling CPU.
pub unsafe fn sync_exec_raw(
target: MpIpiTarget,
mask: cpu_mask_t,
task: SyncTask,
context: *mut core::ffi::c_void,
) {
// SAFETY: The caller guarantees the validity of `task` and `context`.
unsafe { cpp_mp_sync_exec(target, mask, task, context) };
}
/// Executes a closure synchronously across target CPUs and blocks until all
/// target CPUs have completed execution.
///
/// If `target == MpIpiTarget::AllButLocal`, interrupts must be disabled on the calling CPU.
pub fn sync_exec<F: Fn() + Sync>(target: MpIpiTarget, mask: cpu_mask_t, f: F) {
unsafe extern "C" fn sync_exec_trampoline<F: Fn() + Sync>(context: *mut core::ffi::c_void) {
// SAFETY: `context` points to a valid `F` on the caller's stack. Because `F: Sync`,
// multiple CPUs can safely execute `closure()` concurrently via shared reference `&F`.
let closure = unsafe { &*(context as *const F) };
closure();
}
// SAFETY: `&f` remains valid on the stack for the entire duration of `cpp_mp_sync_exec`,
// which blocks synchronously until all target CPUs have finished executing the trampoline.
// TODO(https://fxbug.dev/517305410): This is not safe if CPUs are offlined and then brought
// back online with this IPI still pending.
unsafe {
cpp_mp_sync_exec(
target,
mask,
sync_exec_trampoline::<F>,
&f as *const F as *mut core::ffi::c_void,
);
}
}
/// Hotplugs CPUs specified in `mask`, blocking until all CPUs are online or an error occurs.
pub fn hotplug_cpu_mask(mask: cpu_mask_t) -> Result<(), Status> {
// SAFETY: FFI call with value argument has no safety preconditions.
let status = unsafe { cpp_mp_hotplug_cpu_mask(mask) };
Status::ok(status)
}
/// Hotplugs a single CPU by ID, blocking until it is online or an error occurs.
pub fn hotplug_cpu(cpu: cpu_num_t) -> Result<(), Status> {
// SAFETY: FFI call with value argument has no safety preconditions.
let status = unsafe { cpp_mp_hotplug_cpu(cpu) };
Status::ok(status)
}
/// Unplugs the calling CPU, halting it and taking it offline.
pub fn unplug_current_cpu() {
// SAFETY: FFI call terminates the current CPU.
unsafe { cpp_mp_unplug_current_cpu() };
}
/// Unplugs CPUs specified by `mask`, waiting up to `deadline` for them to go offline.
pub fn unplug_cpu_mask(mask: cpu_mask_t, deadline: zx_instant_mono_t) -> Result<(), Status> {
// SAFETY: FFI call with value arguments has no safety preconditions.
let status = unsafe { cpp_mp_unplug_cpu_mask(mask, deadline) };
Status::ok(status)
}
/// Unplugs a single CPU by ID, waiting indefinitely for it to go offline.
pub fn unplug_cpu(cpu: cpu_num_t) -> Result<(), Status> {
// SAFETY: FFI call with value argument has no safety preconditions.
let status = unsafe { cpp_mp_unplug_cpu(cpu) };
Status::ok(status)
}
/// Kernel MP unit tests.
#[cfg(ktest)]
#[unittest::suite(name = "mp_rust")]
mod tests {
use super::{
Deadline, MpIpiTarget, get_online_mask, is_cpu_online, sync_exec, wait_for_all_cpus_ready,
};
use crate::platform_rs::timer::InstantUnknown;
use core::sync::atomic::{AtomicU32, Ordering};
/// Verifies querying online CPU mask and per-CPU online status.
#[test]
fn test_mp_online_mask() {
let mask = get_online_mask();
unittest::expect_ne!(mask, 0);
for cpu in 0..32 {
let is_set = (mask & (1 << cpu)) != 0;
unittest::expect_eq!(is_cpu_online(cpu), is_set);
}
}
/// Verifies synchronous cross-CPU closure execution via `sync_exec` across various targets.
#[test]
fn test_mp_sync_exec() {
let online = get_online_mask();
let num_online = online.count_ones();
// 1. Target with explicit bitmask.
let count_mask = AtomicU32::new(0);
sync_exec(MpIpiTarget::Mask, online, || {
count_mask.fetch_add(1, Ordering::SeqCst);
});
unittest::expect_eq!(count_mask.load(Ordering::SeqCst), num_online);
// 2. Target all online CPUs.
let count_all = AtomicU32::new(0);
sync_exec(MpIpiTarget::All, 0, || {
count_all.fetch_add(1, Ordering::SeqCst);
});
unittest::expect_eq!(count_all.load(Ordering::SeqCst), num_online);
}
/// Verifies waiting for all CPUs to be ready during kernel test execution.
#[test]
fn test_mp_ready() {
let status = wait_for_all_cpus_ready(Deadline::no_slack(InstantUnknown(0)));
unittest::expect_ok!(status);
}
}