use core::sync::atomic::Ordering;
use portable_atomic::AtomicU32;
use crate::frame_scheduler::{ErifInfo, Holdoff, Pacing, sealed};
pub trait SemaphoreOps {
fn give(&self);
fn take_blocking(&self);
fn take_with_timeout_ms(&self, ms: u32) -> bool;
}
pub trait TimerOps {
fn arm_one_pulse_us(&self, us: u32);
}
pub struct FreeRtosPacing<S: SemaphoreOps, T: TimerOps> {
erif_sem: S,
present_gate_sem: S,
buf_ready_sem: S,
render_start_sem: S,
holdoff_timer: T,
erif_count: AtomicU32,
last_erif_cyccnt: AtomicU32,
}
impl<S: SemaphoreOps, T: TimerOps> FreeRtosPacing<S, T> {
pub const fn new(
erif_sem: S,
present_gate_sem: S,
buf_ready_sem: S,
render_start_sem: S,
holdoff_timer: T,
) -> Self {
Self {
erif_sem,
present_gate_sem,
buf_ready_sem,
render_start_sem,
holdoff_timer,
erif_count: AtomicU32::new(0),
last_erif_cyccnt: AtomicU32::new(0),
}
}
pub unsafe fn isr_record_erif(&self, cyccnt: u32) {
self.last_erif_cyccnt.store(cyccnt, Ordering::Release);
self.erif_count.fetch_add(1, Ordering::Release);
}
#[inline]
pub fn erif_sem(&self) -> &S {
&self.erif_sem
}
#[inline]
pub fn render_start_sem(&self) -> &S {
&self.render_start_sem
}
#[inline]
pub fn present_gate_sem(&self) -> &S {
&self.present_gate_sem
}
}
impl<S: SemaphoreOps, T: TimerOps> sealed::Sealed for FreeRtosPacing<S, T> {}
impl<S: SemaphoreOps, T: TimerOps> Pacing for FreeRtosPacing<S, T> {
fn wait_erif(&mut self) -> ErifInfo {
self.erif_sem.take_blocking();
let erif_count = self.erif_count.load(Ordering::Acquire);
let cyccnt = self.last_erif_cyccnt.load(Ordering::Acquire);
ErifInfo { cyccnt, erif_count }
}
fn compute_holdoff_us(&self, _erif: &ErifInfo, holdoff: Holdoff) -> Option<u32> {
match holdoff {
Holdoff::None => None,
Holdoff::FixedDelay { us } => Some(us),
}
}
fn wait_holdoff(&mut self, us: u32) {
self.holdoff_timer.arm_one_pulse_us(us);
let _taken = self.present_gate_sem.take_with_timeout_ms(50);
}
fn signal_buf_ready(&self) {
self.buf_ready_sem.give();
}
fn wait_render_gate(&mut self) {
self.render_start_sem.take_blocking();
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::cell::Cell;
#[derive(Default)]
struct MockSem {
gives: Cell<u32>,
takes_blocking: Cell<u32>,
takes_timeout: Cell<u32>,
take_timeout_result: Cell<bool>,
}
impl MockSem {
fn new() -> Self {
Self {
gives: Cell::new(0),
takes_blocking: Cell::new(0),
takes_timeout: Cell::new(0),
take_timeout_result: Cell::new(true),
}
}
}
impl SemaphoreOps for MockSem {
fn give(&self) {
self.gives.set(self.gives.get() + 1);
}
fn take_blocking(&self) {
self.takes_blocking.set(self.takes_blocking.get() + 1);
}
fn take_with_timeout_ms(&self, _ms: u32) -> bool {
self.takes_timeout.set(self.takes_timeout.get() + 1);
self.take_timeout_result.get()
}
}
#[derive(Default)]
struct MockTimer {
arms: Cell<u32>,
last_arm_us: Cell<u32>,
}
impl MockTimer {
fn new() -> Self {
Self {
arms: Cell::new(0),
last_arm_us: Cell::new(0),
}
}
}
impl TimerOps for MockTimer {
fn arm_one_pulse_us(&self, us: u32) {
self.arms.set(self.arms.get() + 1);
self.last_arm_us.set(us);
}
}
fn build_pacing() -> FreeRtosPacing<MockSem, MockTimer> {
FreeRtosPacing::new(
MockSem::new(),
MockSem::new(),
MockSem::new(),
MockSem::new(),
MockTimer::new(),
)
}
#[test]
fn freertos_pacing_holdoff_dispatch() {
let pacing = build_pacing();
let erif = ErifInfo {
cyccnt: 0,
erif_count: 0,
};
assert_eq!(pacing.compute_holdoff_us(&erif, Holdoff::None), None);
assert_eq!(
pacing.compute_holdoff_us(&erif, Holdoff::FixedDelay { us: 32_000 }),
Some(32_000)
);
}
#[test]
fn wait_erif_takes_erif_sem_blocking() {
let mut pacing = build_pacing();
let info = pacing.wait_erif();
assert_eq!(pacing.erif_sem.takes_blocking.get(), 1);
assert_eq!(info.cyccnt, 0);
assert_eq!(info.erif_count, 0);
}
#[test]
fn wait_holdoff_arms_timer_then_takes_present_gate_with_timeout() {
let mut pacing = build_pacing();
pacing.wait_holdoff(1_234);
assert_eq!(pacing.holdoff_timer.arms.get(), 1);
assert_eq!(pacing.holdoff_timer.last_arm_us.get(), 1_234);
assert_eq!(pacing.present_gate_sem.takes_timeout.get(), 1);
assert_eq!(pacing.erif_sem.takes_blocking.get(), 0);
assert_eq!(pacing.buf_ready_sem.gives.get(), 0);
assert_eq!(pacing.render_start_sem.takes_blocking.get(), 0);
}
#[test]
fn signal_buf_ready_gives_buf_ready_sem() {
let pacing = build_pacing();
pacing.signal_buf_ready();
assert_eq!(pacing.buf_ready_sem.gives.get(), 1);
assert_eq!(pacing.erif_sem.gives.get(), 0);
assert_eq!(pacing.present_gate_sem.gives.get(), 0);
assert_eq!(pacing.render_start_sem.gives.get(), 0);
}
#[test]
fn wait_render_gate_takes_render_start_blocking() {
let mut pacing = build_pacing();
pacing.wait_render_gate();
assert_eq!(pacing.render_start_sem.takes_blocking.get(), 1);
assert_eq!(pacing.erif_sem.takes_blocking.get(), 0);
}
#[test]
fn isr_record_erif_round_trip() {
let mut pacing = build_pacing();
unsafe {
pacing.isr_record_erif(0xCAFE_F00D);
}
let info = pacing.wait_erif();
assert_eq!(info.cyccnt, 0xCAFE_F00D);
assert_eq!(info.erif_count, 1);
unsafe {
pacing.isr_record_erif(0xDEAD_BEEF);
}
let info2 = pacing.wait_erif();
assert_eq!(info2.cyccnt, 0xDEAD_BEEF);
assert_eq!(info2.erif_count, 2);
}
#[test]
fn accessor_helpers_return_owned_handles() {
let pacing = build_pacing();
let _: &MockSem = pacing.erif_sem();
let _: &MockSem = pacing.render_start_sem();
let _: &MockSem = pacing.present_gate_sem();
}
}