use alloc::vec::Vec;
use core::ops::DivAssign;
use crate::{
bsp::{
__clock_get_set_next_event, __clock_get_set_state_stoped, __clock_max_delta,
__clock_min_delta, __clock_rate,
},
irq::irqflags::{local_irq_restore, local_irq_save},
processor::{nr_cpus, this_processor_id},
time::{
NSEC_PER_SEC, clocksource::clocks_calc_mult_shift, hrtimer::hrtimer_interrupt,
timekeeping::ktime_get,
},
};
#[derive(Clone, Copy)]
#[repr(align(64))]
struct ClockEvent {
pub(crate) event_handler: fn(),
set_next_event: fn(u64) -> bool,
set_state_stoped: fn(),
min_delta_ns: u64,
max_delta_ns: u64,
min_delta_ticks: u64,
max_delta_ticks: u64,
mult: u32,
shift: u32,
cpu: usize,
}
#[inline(always)]
fn dummy_event_handler() {}
#[inline(always)]
fn dummy_set_next_event(_: u64) -> bool {
false
}
#[inline(always)]
fn dummy_set_state_stoped() {}
impl ClockEvent {
const fn new() -> Self {
Self {
event_handler: dummy_event_handler,
set_next_event: dummy_set_next_event,
set_state_stoped: dummy_set_state_stoped,
min_delta_ns: 1,
max_delta_ns: 1,
min_delta_ticks: 1,
max_delta_ticks: 1,
mult: 1,
shift: 1,
cpu: 0,
}
}
fn program_min_delta(&mut self) -> bool {
for _ in 0..10 {
let delta = self.min_delta_ns;
let tick = (delta * u64::from(self.mult)) >> self.shift;
if (self.set_next_event)(tick) {
return true;
}
}
false
}
fn program_event(&mut self, expires: u64, force: bool) -> bool {
let now = ktime_get();
if expires <= now {
if force {
return self.program_min_delta();
}
return false;
}
let mut delta = expires - now;
delta = delta.min(self.max_delta_ns);
delta = delta.max(self.min_delta_ns);
let tick = (delta * u64::from(self.mult)) >> self.shift;
let rc = (self.set_next_event)(tick);
if !rc && force { self.program_min_delta() } else { rc }
}
fn cev_delta2ns(&mut self, latch: u64, ismax: bool) -> u64 {
let mut clc = latch << self.shift;
assert!(self.mult != 0);
let rnd = self.mult - 1;
if (clc >> self.shift) != latch {
clc = u64::MAX;
}
if ((u64::MAX - clc) > u64::from(rnd))
&& (!ismax || u64::from(self.mult) <= (1 << u64::from(self.shift)))
{
clc += u64::from(rnd);
}
clc.div_assign(u64::from(self.mult));
if clc > 1000 { clc } else { 1000 }
}
fn config(&mut self, freq: u64) {
let mut sec = self.max_delta_ticks;
sec.div_assign(freq);
if sec == 0 {
sec = 1;
} else if sec > 600 && self.max_delta_ticks > (u64::from(u32::MAX)) {
sec = 600;
}
clocks_calc_mult_shift(
&mut (self.mult),
&mut (self.shift),
NSEC_PER_SEC as u32,
freq as u32,
sec as u32,
);
self.min_delta_ns = self.cev_delta2ns(self.min_delta_ticks, false);
self.max_delta_ns = self.cev_delta2ns(self.max_delta_ticks, true);
}
fn config_and_register(&mut self, freq: u64, min_tick: u64, max_tick: u64) {
self.min_delta_ticks = min_tick;
self.max_delta_ticks = max_tick;
self.config(freq);
}
#[inline(always)]
fn clockevent_handler(&self) {
debug_assert_eq!(self.cpu, this_processor_id());
(self.event_handler)();
}
}
static mut CLOCKEVENT_DEV: Vec<ClockEvent> = Vec::new();
#[allow(static_mut_refs)]
pub(super) fn clockevent_init() {
unsafe {
for _ in 0..nr_cpus() {
CLOCKEVENT_DEV.push(ClockEvent::new());
}
}
clockevent_init_cpu();
}
pub(super) fn clockevent_init_cpu() {
let cd = unsafe { &mut CLOCKEVENT_DEV[this_processor_id()] };
cd.event_handler = hrtimer_interrupt;
cd.set_next_event = unsafe { __clock_get_set_next_event() };
cd.set_state_stoped = unsafe { __clock_get_set_state_stoped() };
cd.cpu = this_processor_id();
clockevent_shutdown();
cd.config_and_register(
unsafe { __clock_rate() as u64 },
unsafe { __clock_min_delta() },
unsafe { __clock_max_delta() },
);
}
#[inline(always)]
pub(super) fn nsec_program_event(expires: u64, force: bool) -> bool {
let flags = local_irq_save();
let cd = unsafe { &mut CLOCKEVENT_DEV[this_processor_id()] };
let res = cd.program_event(expires, force);
local_irq_restore(flags);
res
}
#[allow(unused)]
#[inline(always)]
pub(crate) fn clockevent_handler() {
let cd = unsafe { &CLOCKEVENT_DEV[this_processor_id()] };
cd.clockevent_handler();
}
pub fn clockevent_shutdown() {
let flags = local_irq_save();
let cd = unsafe { &CLOCKEVENT_DEV[this_processor_id()] };
(cd.set_state_stoped)();
local_irq_restore(flags);
}