blob: 585d168372b24472dd2a4eeb75a107ead9616df5 [file]
// Copyright 2025 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#![no_std]
use kernel::scheduler::{self, Priority, StackStorage, StackStorageExt as _, Thread};
use kernel::sync::event::{Event, EventConfig, EventSignaler};
use kernel::sync::mutex::Mutex;
use kernel::{Duration, Kernel};
use kernel_config::{KernelConfig, KernelConfigInterface};
use pw_status::Result;
pub struct AppState<K: Kernel> {
thread: Thread<K>,
stack: StackStorage<{ KernelConfig::KERNEL_STACK_SIZE_BYTES }>,
test_counter: Mutex<K, u64>,
thread_a_done_event: Event<K>,
}
impl<K: Kernel> AppState<K> {
pub const fn new(kernel: K) -> AppState<K> {
AppState {
thread: Thread::new(
"",
Priority::DEFAULT_PRIORITY,
kernel::scheduler::Stack::new(),
),
stack: StackStorage::ZEROED,
test_counter: Mutex::new(kernel, 0),
thread_a_done_event: Event::new(kernel, EventConfig::ManualReset),
}
}
}
struct ThreadAArgs<'a, K: Kernel> {
test_counter: &'a Mutex<K, u64>,
done_signaler: EventSignaler<K>,
}
pub fn main<K: Kernel>(kernel: K, state: &'static mut AppState<K>) -> Result<()> {
test_logger::start("Kernel Threads Test");
let thread_b_args = ThreadAArgs {
test_counter: &state.test_counter,
done_signaler: state.thread_a_done_event.get_signaler(),
};
let thread_b = scheduler::init_thread_in(
kernel,
&mut state.thread,
&mut state.stack,
"B",
Priority::DEFAULT_PRIORITY,
test_thread_entry_b,
&thread_b_args,
);
kernel::start_thread(kernel, thread_b);
test_logger::info!("Thread A re-using bootstrap thread");
thread_a(kernel, &state.test_counter);
let wait_result = state
.thread_a_done_event
.wait_until(kernel.now() + Duration::from_secs(1));
match wait_result {
Ok(()) => test_logger::passed("Kernel Threads Test"),
Err(_err) => test_logger::failed("Kernel Threads Test"),
}
wait_result
}
fn test_thread_entry_b<K: Kernel>(kernel: K, args: &ThreadAArgs<K>) {
test_logger::info!("Thread B starting");
thread_b(kernel, args);
}
fn thread_a<K: Kernel>(kernel: K, test_counter: &Mutex<K, u64>) {
// Verify that lock_until can be called from the same thread when already locked,
// provided the deadline has already passed (instant/expired timeout).
{
let _guard = test_counter.lock();
let res = test_counter.lock_until(kernel.now());
pw_assert::assert!(res.is_err());
}
for _ in 0..3 {
let mut counter = test_counter.lock();
let _ = kernel::sleep_until(kernel, kernel.now() + Duration::from_secs(1));
test_logger::info!("Thread A: Incrementing counter");
*counter = (*counter).saturating_add(1);
}
test_logger::info!("Thread A: Done");
}
fn thread_b<K: Kernel>(kernel: K, args: &ThreadAArgs<K>) {
for _ in 0..4 {
let deadline = kernel.now() + Duration::from_millis(600);
let Ok(counter) = args.test_counter.lock_until(deadline) else {
test_logger::info!("Thread B: Timeout");
continue;
};
test_logger::info!("Thread B: Counter value {}", *counter as u64);
pw_assert::assert!(*counter < 4);
drop(counter);
// Give Thread A a chance to acquire the mutex.
kernel::yield_timeslice(kernel);
}
test_logger::info!("Thread B: Done");
args.done_signaler.signal();
}