blob: 2d2b68fae81cd7cf9e4b09dfe130789ef32e5d3a [file]
// Copyright 2026 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#[cfg(ktest)]
#[unittest::suite]
/// Tests for Rust ksync bindings
mod ksync_tests {
use pin_init::{pin_data, pin_init, stack_pin_init};
use unittest::{assert_true, expect_true};
#[ksync::guarded]
#[fbl::ref_counted]
#[derive(fbl::Recyclable)]
#[pin_data]
#[repr(C)]
struct GuardedMutexObj {
#[mutex]
mu: ksync::KMutex,
#[guarded_by(mu)]
value: u32,
}
#[ksync::guarded]
struct GuardedSpinlockObj {
#[mutex]
mu: ksync::KMutex<ksync::RawSpinlock>,
#[guarded_by(mu)]
value: u32,
}
#[ksync::guarded]
struct GuardedSpinlockObj2 {
#[mutex]
mu: ksync::KMutex<ksync::RawSpinlock>,
#[guarded_by(mu)]
value: u32,
}
#[ksync::guarded]
struct GuardedMonitoredSpinlockObj {
#[mutex]
mu: ksync::KMutex<ksync::RawMonitoredSpinlock>,
#[guarded_by(mu)]
value: u32,
}
#[ksync::guarded]
struct GuardedCriticalMutexObj {
#[mutex]
mu: ksync::KMutex<ksync::RawCriticalMutex>,
#[guarded_by(mu)]
value: u32,
}
#[ksync::guarded]
struct GuardedBrwLockObj {
#[brwlock]
lock: ksync::BrwLockPi,
#[guarded_by(lock)]
value: u32,
}
#[ksync::guarded]
struct GuardedOnceObj {
#[mutex]
mu: ksync::KMutex,
#[guarded_by(mu)]
once_val: ksync::KOnceCell<u32>,
}
#[ksync::guarded]
struct GuardedPhantomObj {
#[mutex(GuardedMutexObjMuClass)]
mu: ksync::KMutex<ksync::PhantomMutex>,
#[guarded_by(mu)]
target: fbl::RefPtr<GuardedMutexObj>,
}
#[ksync::guarded]
#[fbl::ref_counted]
#[derive(fbl::Recyclable)]
#[pin_data]
#[repr(C)]
struct GuardedGenericObj<T> {
#[mutex]
mu: ksync::KMutex,
#[guarded_by(mu)]
value: T,
}
#[ksync::guarded]
struct GuardedGenericPhantomObj<T> {
#[mutex(GuardedGenericObjMuClass<T>)]
mu: ksync::KMutex<ksync::PhantomMutex>,
#[guarded_by(mu)]
target: fbl::RefPtr<GuardedGenericObj<T>>,
}
unsafe extern "C" {
fn cpp_verify_mutex_id(
lock: *const core::ffi::c_void,
expected_id: *const core::ffi::c_void,
) -> bool;
fn cpp_verify_critical_mutex_id(
lock: *const core::ffi::c_void,
expected_id: *const core::ffi::c_void,
) -> bool;
fn cpp_verify_spinlock_id(
lock: *const core::ffi::c_void,
expected_id: *const core::ffi::c_void,
) -> bool;
fn cpp_verify_monitored_spinlock_id(
lock: *const core::ffi::c_void,
expected_id: *const core::ffi::c_void,
) -> bool;
fn cpp_verify_brwlock_id(
lock: *const core::ffi::c_void,
expected_id: *const core::ffi::c_void,
) -> bool;
fn cpp_get_test_singleton_mutex_ptr() -> *const core::ffi::c_void;
fn cpp_get_test_singleton_mutex_size() -> usize;
fn cpp_get_test_singleton_mutex_raw_lock_ptr() -> *const core::ffi::c_void;
fn cpp_get_test_singleton_mutex_raw_lock_size() -> usize;
fn cpp_get_test_singleton_critical_mutex_ptr() -> *const core::ffi::c_void;
fn cpp_get_test_singleton_critical_mutex_size() -> usize;
fn cpp_get_test_singleton_critical_mutex_raw_lock_ptr() -> *const core::ffi::c_void;
fn cpp_get_test_singleton_critical_mutex_raw_lock_size() -> usize;
}
/// test Rust KMutex ID
#[test]
fn mutex_id() {
let obj = fbl::pin_make_ref_counted!(GuardedMutexObj {
mu <- ksync::KMutex::init(),
value: 0.into(),
})
.unwrap();
unsafe {
assert_true!(cpp_verify_mutex_id(
&obj.mu as *const _ as *const core::ffi::c_void,
<GuardedMutexObjMuClass as ksync::LockClass>::ID,
));
}
}
/// test Rust KCriticalMutex ID
#[test]
fn critical_mutex_id() {
stack_pin_init!(let obj = pin_init!(GuardedCriticalMutexObj {
mu <- ksync::KMutex::init(),
value: 0.into(),
}));
unsafe {
assert_true!(cpp_verify_critical_mutex_id(
&obj.mu as *const _ as *const core::ffi::c_void,
<GuardedCriticalMutexObjMuClass as ksync::LockClass>::ID,
));
}
}
/// test Rust KSpinlock ID
#[test]
fn spinlock_id() {
stack_pin_init!(let obj = pin_init!(GuardedSpinlockObj {
mu <- ksync::KMutex::init(),
value: 0.into(),
}));
unsafe {
assert_true!(cpp_verify_spinlock_id(
&obj.mu as *const _ as *const core::ffi::c_void,
<GuardedSpinlockObjMuClass as ksync::LockClass>::ID,
));
}
}
/// test Rust KMonitoredSpinlock ID and acquire/release with source_tag!
#[test]
fn monitored_spinlock_id_and_ops() {
stack_pin_init!(let obj = pin_init!(GuardedMonitoredSpinlockObj {
mu <- ksync::KMutex::init(),
value: 10.into(),
}));
unsafe {
assert_true!(cpp_verify_monitored_spinlock_id(
&obj.mu as *const _ as *const core::ffi::c_void,
<GuardedMonitoredSpinlockObjMuClass as ksync::LockClass>::ID,
));
}
// Acquire with default IrqSavePolicy and source_tag!
{
ksync::lock!(let mut guard = obj.lock_mu(ksync::source_tag!()));
expect_true!(*guard.value() == 10);
*guard.as_mut().value_mut() = 20;
expect_true!(*guard.value() == 20);
}
// Verify persistence and acquire with explicit policy
{
ksync::lock!(
let guard = obj.lock_mu_policy::<ksync::IrqSavePolicy>(ksync::source_tag!())
);
expect_true!(*guard.value() == 20);
}
}
/// test Rust BrwLockPi ID
#[test]
fn brwlock_id() {
stack_pin_init!(let obj = pin_init!(GuardedBrwLockObj {
lock <- ksync::BrwLockPi::init(),
value: 0.into(),
}));
unsafe {
assert_true!(cpp_verify_brwlock_id(
&obj.lock as *const _ as *const core::ffi::c_void,
<GuardedBrwLockObjLockClass as ksync::LockClass>::ID,
));
}
}
/// test Rust KSpinlock
#[test]
fn spinlock() {
stack_pin_init!(let obj = pin_init!(GuardedSpinlockObj {
mu <- ksync::KMutex::init(),
value: 100.into(),
}));
stack_pin_init!(let obj2 = pin_init!(GuardedSpinlockObj2 {
mu <- ksync::KMutex::init(),
value: 100.into(),
}));
{
ksync::lock!(let mut guard = obj.lock_mu());
expect_true!(*guard.value() == 100);
*guard.as_mut().value_mut() = 101;
}
{
ksync::lock!(let guard = obj.lock_mu());
expect_true!(*guard.value() == 101);
}
{
ksync::lock!(let guard = obj.mu.lock());
ksync::lock!(let guard2 = obj2.mu.lock_policy::<ksync::NoIrqSavePolicy>());
// Expect a no irq save policy guard to be strictly smaller than one that had to
// remember the irq state.
expect_true!(core::mem::size_of_val(&*guard2) < core::mem::size_of_val(&*guard));
}
}
/// test Rust KMutex
#[test]
fn mutex() {
let obj = fbl::pin_make_ref_counted!(GuardedMutexObj {
mu <- ksync::KMutex::init(),
value: 42.into(),
})
.unwrap();
{
ksync::lock!(let mut guard = obj.lock_mu());
expect_true!(*guard.value() == 42);
*guard.as_mut().value_mut() = 43;
}
{
ksync::lock!(let guard = obj.lock_mu());
expect_true!(*guard.value() == 43);
}
}
/// test PhantomMutex and #[mutex(LockClass)]
#[test]
fn phantom_lock() {
// This test demonstrates how an object (`phantom_obj`) can use a zero-sized `PhantomMutex`
// to participate in `#[guarded]` type checking while physical mutual exclusion is
// provided by an external physical lock (`real_obj.mu`).
// Create an object with a real, physical `KMutex`, wrapped in an `fbl::RefPtr`.
let real_obj = fbl::pin_make_ref_counted!(GuardedMutexObj {
mu <- ksync::KMutex::init(),
value: 1.into(),
})
.unwrap();
// Create an object protected by a zero-sized `PhantomMutex` (no physical lock storage).
// Its synchronization is provided externally by `real_obj.mu`.
let target = real_obj.clone();
stack_pin_init!(let phantom_obj = pin_init!(GuardedPhantomObj {
mu: ksync::KMutex::new(ksync::PhantomMutex),
target: target.into(),
}));
{
// Acquire a guard for `phantom_obj` by passing the physical `real_obj.mu` to `lock_mu`.
ksync::lock!(let mut guard = phantom_obj.lock_mu(&real_obj.mu));
// Read the reference to `real_obj` from `phantom_obj.target`.
let target = guard.target().clone();
// Using the proof token from `guard`, read `target.value`.
expect_true!(*target.guard_mu(guard.token()).value() == 1);
// Modify `target.value` using the mutable token from `guard`.
*target.guard_mu_mut(guard.as_mut().token_mut()).value_mut() = 200;
}
{
// Lock `real_obj` directly using `GuardedMutexObj`'s own guard to verify
// that we read back the same value (200) that was written via `phantom_obj`.
ksync::lock!(let guard = real_obj.lock_mu());
expect_true!(*guard.value() == 200);
}
}
/// test PhantomMutex with generic LockClass
#[test]
fn generic_phantom_mutex() {
let real_obj = fbl::pin_make_ref_counted!(GuardedGenericObj {
mu <- ksync::KMutex::init(),
value: 42.into(),
})
.unwrap();
let real_obj_clone = real_obj.clone();
stack_pin_init!(let phantom_obj = pin_init!(GuardedGenericPhantomObj {
mu: ksync::KMutex::new(ksync::PhantomMutex),
target: ksync::KCell::new(real_obj_clone),
}));
{
ksync::lock!(let mut guard = phantom_obj.lock_mu(&real_obj.mu));
let target = guard.target().clone();
expect_true!(*target.guard_mu(guard.token()).value() == 42);
*target.guard_mu_mut(guard.as_mut().token_mut()).value_mut() = 99;
}
{
ksync::lock!(let guard = real_obj.lock_mu());
expect_true!(*guard.value() == 99);
}
}
/// test Rust BrwLockPi
#[test]
fn brwlock() {
stack_pin_init!(let obj = pin_init!(GuardedBrwLockObj {
lock <- ksync::BrwLockPi::init(),
value: 10.into(),
}));
{
ksync::lock!(let guard = obj.read_lock());
expect_true!(*guard.value() == 10);
}
{
ksync::lock!(let mut guard = obj.write_lock());
expect_true!(*guard.value() == 10);
*guard.as_mut().value_mut() = 20;
}
{
ksync::lock!(let guard = obj.read_lock());
expect_true!(*guard.value() == 20);
}
}
/// test call_unlocked on KMutex and macro-generated guards
#[test]
fn call_unlocked() {
let obj = fbl::pin_make_ref_counted!(GuardedMutexObj {
mu <- ksync::KMutex::init(),
value: 10.into(),
})
.unwrap();
ksync::lock!(let mut guard = obj.lock_mu());
expect_true!(*guard.value() == 10);
let res = guard.as_mut().call_unlocked(|| 42);
expect_true!(res == 42);
*guard.as_mut().value_mut() = 25;
expect_true!(*guard.value() == 25);
}
/// test AliasedLock in kernel
#[test]
fn aliased_lock() {
let real_obj = fbl::pin_make_ref_counted!(GuardedMutexObj {
mu <- ksync::KMutex::init(),
value: 10.into(),
})
.unwrap();
let target = real_obj.clone();
stack_pin_init!(let phantom_obj = pin_init!(GuardedPhantomObj {
mu: ksync::KMutex::new(ksync::PhantomMutex),
target: target.into(),
}));
{
ksync::lock!(let mut guard = ksync::aliased_lock(&real_obj.mu, &phantom_obj.mu));
let (t1, _t2) = guard.tokens();
expect_true!(*real_obj.guard_mu(t1).value() == 10);
let (t1_mut, _t2_mut) = guard.as_mut().tokens_mut();
*real_obj.guard_mu_mut(t1_mut).value_mut() = 55;
let res = guard.as_mut().call_unlocked(|| 99);
expect_true!(res == 99);
let (t1, _t2) = guard.tokens();
expect_true!(*real_obj.guard_mu(t1).value() == 55);
}
ksync::lock!(let guard = real_obj.lock_mu());
expect_true!(*guard.value() == 55);
}
#[ksync::guarded]
struct GuardedFlaggedMutexObj {
#[mutex(flags = lockdep::LOCK_FLAGS_ACTIVE_LIST_DISABLED)]
seek_lock: ksync::KMutex,
#[guarded_by(seek_lock)]
seek: u64,
}
/// test Mutex with custom flags (e.g. LOCK_FLAGS_ACTIVE_LIST_DISABLED)
#[test]
fn flagged_mutex() {
stack_pin_init!(let obj = pin_init!(GuardedFlaggedMutexObj {
seek_lock <- ksync::KMutex::init(),
seek: 0.into(),
}));
ksync::lock!(let mut guard = obj.lock_seek_lock());
expect_true!(*guard.seek() == 0);
*guard.as_mut().seek_mut() = 4096;
expect_true!(*guard.seek() == 4096);
}
ksync::declare_singleton_mutex!(TestKernelSingletonMutex);
ksync::declare_singleton_critical_mutex!(TestKernelSingletonCriticalMutex);
ksync::declare_singleton_spinlock!(TestKernelSingletonSpinlock);
/// test Rust declare_singleton_mutex and declare_singleton_critical_mutex
#[test]
fn singleton_mutex() {
let lock1 = TestKernelSingletonMutex::Get();
let lock2 = TestKernelSingletonMutex::get();
expect_true!(core::ptr::eq(lock1, lock2));
// Verify that the C++ lockdep helper reads the lock ID from the Rust singleton correctly.
unsafe {
expect_true!(cpp_verify_mutex_id(
lock1 as *const _ as *const core::ffi::c_void,
<TestKernelSingletonMutex as ksync::LockClass>::ID,
));
expect_true!(cpp_verify_critical_mutex_id(
TestKernelSingletonCriticalMutex::Get() as *const _ as *const core::ffi::c_void,
<TestKernelSingletonCriticalMutex as ksync::LockClass>::ID,
));
expect_true!(cpp_verify_spinlock_id(
TestKernelSingletonSpinlock::Get() as *const _ as *const core::ffi::c_void,
<TestKernelSingletonSpinlock as ksync::LockClass>::ID,
));
}
// Compare total sizes of Rust-declared singletons vs C++-declared singletons.
let rust_mutex_size = core::mem::size_of_val(lock1);
let cpp_mutex_size = unsafe { cpp_get_test_singleton_mutex_size() };
expect_true!(rust_mutex_size == cpp_mutex_size);
let rust_critical_size = core::mem::size_of_val(TestKernelSingletonCriticalMutex::Get());
let cpp_critical_size = unsafe { cpp_get_test_singleton_critical_mutex_size() };
expect_true!(rust_critical_size == cpp_critical_size);
// Compare raw mutex storage byte-by-byte between Rust and C++.
let cpp_raw_ptr = unsafe { cpp_get_test_singleton_mutex_raw_lock_ptr() };
let cpp_raw_size = unsafe { cpp_get_test_singleton_mutex_raw_lock_size() };
let cpp_raw_bytes =
unsafe { core::slice::from_raw_parts(cpp_raw_ptr as *const u8, cpp_raw_size) };
let rust_raw_bytes = lock1.raw_mutex().raw_storage_slice();
expect_true!(rust_raw_bytes == cpp_raw_bytes);
// Compare CriticalMutex raw storage byte-by-byte between Rust and C++.
let cpp_critical_raw_ptr = unsafe { cpp_get_test_singleton_critical_mutex_raw_lock_ptr() };
let cpp_critical_raw_size =
unsafe { cpp_get_test_singleton_critical_mutex_raw_lock_size() };
let cpp_critical_raw_bytes = unsafe {
core::slice::from_raw_parts(cpp_critical_raw_ptr as *const u8, cpp_critical_raw_size)
};
let rust_critical_raw_bytes =
TestKernelSingletonCriticalMutex::Get().raw_mutex().raw_storage_slice();
expect_true!(rust_critical_raw_bytes == cpp_critical_raw_bytes);
{
ksync::lock!(let _guard = TestKernelSingletonMutex::Get().lock());
}
{
ksync::lock!(TestKernelSingletonMutex::lock());
}
{
ksync::lock!(let _guard = TestKernelSingletonCriticalMutex::Get().lock());
}
// Verify that a singleton spinlock registers LOCK_FLAGS_IRQ_SAFE so it can be acquired
// while holding another IRQ-safe spinlock without triggering a lockdep violation.
stack_pin_init!(let outer = pin_init!(GuardedSpinlockObj {
mu <- ksync::KMutex::init(),
value: 0.into(),
}));
{
ksync::lock!(let _outer_guard = outer.lock_mu());
ksync::lock!(let _inner_guard = TestKernelSingletonSpinlock::lock());
}
}
/// test KOnceCell lazy initialization and access
#[test]
fn konce_cell_test() {
static TEST_CELL: ksync::KOnceCell<u64, TestKernelSingletonMutex> = ksync::KOnceCell::new();
ksync::lock!(let mut guard = TestKernelSingletonMutex::lock());
unsafe {
let token = guard.token();
expect_true!(!TEST_CELL.is_initialized(token));
expect_true!(TEST_CELL.get(token).is_none());
let token_mut = guard.as_mut().token_mut();
let val = TEST_CELL.get_or_init(|| 12345u64, token_mut);
expect_true!(*val == 12345);
let token = guard.token();
expect_true!(TEST_CELL.is_initialized(token));
expect_true!(TEST_CELL.get(token) == Some(&12345));
let unchk = TEST_CELL.get_unchecked();
expect_true!(unchk == Some(&12345));
}
}
/// test KOnceCell integration with #[ksync::guarded]
#[test]
fn guarded_konce_cell_test() {
stack_pin_init!(let obj = pin_init!(GuardedOnceObj {
mu <- ksync::KMutex::init(),
once_val: ksync::KOnceCell::new(),
}));
// Before init: read accessor returns None
{
ksync::lock!(let guard = obj.lock_mu());
expect_true!(guard.once_val().is_none());
}
// Initialize through mutable guard
{
ksync::lock!(let mut guard = obj.lock_mu());
let mut once_cell = guard.as_mut().once_val_cell();
let val = once_cell.get_or_init(|| 42);
expect_true!(*val == 42);
// Read after init
expect_true!(guard.once_val() == Some(&42));
expect_true!(guard.as_mut().once_val_mut() == Some(&mut 42));
// Attempting to set fails because it's already initialized
expect_true!(guard.as_mut().once_val_cell().set(99) == Err(99));
// Test proxy cell access
expect_true!(guard.as_mut().once_val_cell().get() == Some(&42));
// Disjoint fields access
let fields = guard.fields();
expect_true!(fields.once_val == Some(&42));
let fields_mut = guard.as_mut().fields_mut();
expect_true!(fields_mut.once_val == Some(&mut 42));
}
}
#[ksync::guarded]
struct GuardedMultiSpinlockObj {
#[mutex(flags = lockdep::LOCK_FLAGS_MULTI_ACQUIRE)]
mu: ksync::KMutex<ksync::RawSpinlock>,
#[guarded_by(mu)]
value: u32,
}
/// test explicit release() and KMutexGuardMultiple in kernel
#[test]
fn guard_multiple_and_release_test() {
stack_pin_init!(let s1 = pin_init!(GuardedMultiSpinlockObj {
mu <- ksync::KMutex::init(),
value: 10.into(),
}));
stack_pin_init!(let s2 = pin_init!(GuardedMultiSpinlockObj {
mu <- ksync::KMutex::init(),
value: 20.into(),
}));
// Test single guard early release
{
ksync::lock!(let mut g = s1.lock_mu());
*g.as_mut().value_mut() = 11;
g.as_mut().release();
ksync::lock!(let g2 = s1.lock_mu());
expect_true!(*g2.value() == 11);
}
// Test multi-lock in [s1, s2] and [s2, s1] order with early release
{
ksync::lock!(
let mut guards = GuardedMultiSpinlockObj::lock_mu_multiple([&*s1, &*s2])
);
let [t1, t2] = guards.as_mut().tokens_mut();
*s1.guard_mu_mut(t1).value_mut() = 100;
*s2.guard_mu_mut(t2).value_mut() = 200;
}
{
ksync::lock!(
let mut guards = GuardedMultiSpinlockObj::lock_mu_multiple([&*s2, &*s1])
);
let [t1, t2] = guards.tokens();
expect_true!(*s1.guard_mu(t1).value() == 100);
expect_true!(*s2.guard_mu(t2).value() == 200);
guards.as_mut().release();
ksync::lock!(let g1 = s1.lock_mu());
ksync::lock!(let g2 = s2.lock_mu());
expect_true!(*g1.value() == 100);
expect_true!(*g2.value() == 200);
}
}
}