use core::fmt;
use num_rational::Ratio;
use crate::{
num::{
ceil_ratio128, floor_ratio128, reduce_ratio128,
wrapping::{Wrapping, WrappingTrait},
},
utils::Init,
};
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct TickfulOptions {
pub hw_freq_num: u64,
pub hw_freq_denom: u64,
pub hw_tick_period: u32,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum CfgError {
FreqNumZero,
FreqDenomZero,
PeriodZero,
PeriodOverflowsU32,
PeriodExceedsKernelHeadroom,
}
impl CfgError {
pub const fn as_str(self) -> &'static str {
match self {
Self::FreqNumZero => "the numerator of the clock frequency must not be zero",
Self::FreqDenomZero => "the denominator of the clock frequency must not be zero",
Self::PeriodZero => "the tick period must not be zero",
Self::PeriodOverflowsU32 => {
"the tick period is too long and \
does not fit in 32 bits when measured in microseconds"
}
Self::PeriodExceedsKernelHeadroom => {
"the tick period must not be longer than `TIME_HARD_HEADROOM`"
}
}
}
pub const fn panic(self) -> ! {
core::panicking::panic(self.as_str());
}
}
impl fmt::Display for CfgError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct TickfulCfg {
tick_period_micros: u32,
tick_period_submicros: u64,
division: u64,
}
impl TickfulCfg {
pub const fn new(
TickfulOptions {
hw_freq_num: freq_num,
hw_freq_denom: freq_denom,
hw_tick_period: tick_period_cycles,
}: TickfulOptions,
) -> Result<TickfulCfg, CfgError> {
if freq_denom == 0 {
return Err(CfgError::FreqDenomZero);
} else if freq_num == 0 {
return Err(CfgError::FreqNumZero);
} else if tick_period_cycles == 0 {
return Err(CfgError::PeriodZero);
}
let tick_period_secs = Ratio::new_raw(
freq_denom as u128 * tick_period_cycles as u128,
freq_num as u128,
);
let tick_period_micros = Ratio::new_raw(
*tick_period_secs.numer() * 1_000_000,
*tick_period_secs.denom(),
);
let tick_period_micros = reduce_ratio128(tick_period_micros);
let tick_period_micros_floor = floor_ratio128(tick_period_micros);
let tick_period_micros_ceil = ceil_ratio128(tick_period_micros);
let tick_period_submicros = *tick_period_micros.numer() % *tick_period_micros.denom();
if tick_period_micros_ceil >= 0x1_0000_0000 {
return Err(CfgError::PeriodOverflowsU32);
}
if tick_period_micros_ceil > r3::kernel::TIME_HARD_HEADROOM.as_micros() as u128 {
return Err(CfgError::PeriodExceedsKernelHeadroom);
}
Ok(TickfulCfg {
tick_period_micros: tick_period_micros_floor as u32,
tick_period_submicros: tick_period_submicros as u64,
division: *tick_period_micros.denom() as u64,
})
}
pub const fn is_exact(&self) -> bool {
self.division == 1
}
pub const fn division(&self) -> u64 {
self.division
}
}
pub type TickfulState<const CFG: TickfulCfg> = TickfulStateCore<Wrapping<{ CFG.division() - 1 }>>;
#[derive(Debug, Copy, Clone)]
pub struct TickfulStateCore<Submicros> {
tick_count_micros: u32,
tick_count_submicros: Submicros,
}
pub trait TickfulStateTrait: Init {
fn tick(&mut self, cfg: &TickfulCfg);
fn tick_count(&self) -> u32;
}
impl<Submicros: Init> Init for TickfulStateCore<Submicros> {
const INIT: Self = Self {
tick_count_micros: Init::INIT,
tick_count_submicros: Init::INIT,
};
}
impl<Submicros: WrappingTrait> TickfulStateTrait for TickfulStateCore<Submicros> {
#[inline]
fn tick(&mut self, cfg: &TickfulCfg) {
self.tick_count_micros = self.tick_count_micros.wrapping_add(cfg.tick_period_micros);
if self
.tick_count_submicros
.wrapping_add_assign64(cfg.tick_period_submicros)
{
self.tick_count_micros = self.tick_count_micros.wrapping_add(1);
}
}
#[inline]
fn tick_count(&self) -> u32 {
self.tick_count_micros
}
}
#[cfg(test)]
mod tests {
extern crate std;
use super::*;
#[test]
fn tickful_known_values() {
assert_eq!(
TickfulCfg::new(TickfulOptions {
hw_freq_num: 1,
hw_freq_denom: 1,
hw_tick_period: 1
})
.unwrap(),
TickfulCfg {
tick_period_micros: 1_000_000,
tick_period_submicros: 0,
division: 1,
},
);
assert_eq!(
TickfulCfg::new(TickfulOptions {
hw_freq_num: 1,
hw_freq_denom: 1,
hw_tick_period: 1073
})
.unwrap(),
TickfulCfg {
tick_period_micros: 1073_000_000,
tick_period_submicros: 0,
division: 1,
},
);
assert_eq!(
TickfulCfg::new(TickfulOptions {
hw_freq_num: 10_000_000,
hw_freq_denom: 1,
hw_tick_period: 1
})
.unwrap(),
TickfulCfg {
tick_period_micros: 0,
tick_period_submicros: 1,
division: 10,
},
);
assert_eq!(
TickfulCfg::new(TickfulOptions {
hw_freq_num: 125_000_000,
hw_freq_denom: 1,
hw_tick_period: 125
})
.unwrap(),
TickfulCfg {
tick_period_micros: 1,
tick_period_submicros: 0,
division: 1,
},
);
assert_eq!(
TickfulCfg::new(TickfulOptions {
hw_freq_num: 125_000_000,
hw_freq_denom: 3,
hw_tick_period: 1250
})
.unwrap(),
TickfulCfg {
tick_period_micros: 30,
tick_period_submicros: 0,
division: 1,
},
);
assert_eq!(
TickfulCfg::new(TickfulOptions {
hw_freq_num: 375_000_000,
hw_freq_denom: 1,
hw_tick_period: 1250
})
.unwrap(),
TickfulCfg {
tick_period_micros: 3,
tick_period_submicros: 1,
division: 3,
},
);
}
#[test]
fn tickful_zero_freq() {
assert_eq!(
TickfulCfg::new(TickfulOptions {
hw_freq_num: 0,
hw_freq_denom: 1,
hw_tick_period: 1
}),
Err(CfgError::FreqNumZero)
);
}
#[test]
fn tickful_zero_denom() {
assert_eq!(
TickfulCfg::new(TickfulOptions {
hw_freq_num: 1,
hw_freq_denom: 0,
hw_tick_period: 1
}),
Err(CfgError::FreqDenomZero)
);
}
#[test]
fn tickful_tick_too_long1() {
assert_eq!(
TickfulCfg::new(TickfulOptions {
hw_freq_num: 1,
hw_freq_denom: 1,
hw_tick_period: 5000
}),
Err(CfgError::PeriodOverflowsU32)
);
}
#[test]
fn tickful_tick_too_long2() {
assert_eq!(
TickfulCfg::new(TickfulOptions {
hw_freq_num: 1,
hw_freq_denom: 1,
hw_tick_period: 1074
}),
Err(CfgError::PeriodExceedsKernelHeadroom)
);
}
macro_rules! tickful_simulate {
($freq_num:expr, $freq_denom:expr, $tick_period_cycles:expr) => {{
const CFG: TickfulCfg = match TickfulCfg::new(TickfulOptions {
hw_freq_num: $freq_num,
hw_freq_denom: $freq_denom,
hw_tick_period: $tick_period_cycles,
}) {
Ok(x) => x,
Err(e) => e.panic(),
};
let period =
Ratio::new($tick_period_cycles, 1u128) / Ratio::new($freq_num, $freq_denom);
let mut time = Ratio::new_raw(0, 1u128);
let mut state: TickfulState<CFG> = Init::INIT;
let mut kernel_time = 0;
let mut last_tick_count = state.tick_count();
for _ in 0..10000 {
assert_eq!((time * 1_000_000).to_integer(), kernel_time);
time += period;
state.tick(&CFG);
let new_tick_count = state.tick_count();
kernel_time += new_tick_count.wrapping_sub(last_tick_count) as u128;
last_tick_count = new_tick_count;
}
}};
}
#[test]
fn tickful_simulate1() {
tickful_simulate!(1, 1, 1);
}
#[test]
fn tickful_simulate2() {
tickful_simulate!(125_000_000, 1, 125);
}
#[test]
fn tickful_simulate3() {
tickful_simulate!(375_000_000, 1, 1250);
}
#[test]
fn tickful_simulate4() {
tickful_simulate!(125_000_000, 3, 125);
}
#[test]
fn tickful_simulate5() {
tickful_simulate!(10_000_000, 1, 1);
}
#[test]
fn tickful_simulate6() {
tickful_simulate!(375, 1, 250_000);
}
#[test]
fn tickful_simulate7() {
tickful_simulate!(0x501e_e2c2_9a0f_7b77, 0xb79a_14f3_6985, 0x64ad);
}
#[test]
fn tickful_simulate8() {
tickful_simulate!(0xffff_ffff_ffff_ffff, 0xffff_ffff_fffe, 0x41c4);
}
#[test]
fn tickful_simulate9() {
tickful_simulate!(0x501e_e2c2_9a0f_7b77, 0xb79a_14f3, 0x64ad1234);
}
}