use core::ffi::{c_int, c_long, c_void};
use core::fmt::{Debug, Display};
use core::ops::Deref;
use core::ptr::null_mut;
use std::sync::Mutex;
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicPtr, AtomicU32, Ordering};
use alloc::boxed::Box;
use alloc::string::{String, ToString};
use alloc::sync::Arc;
use crate::os::ThreadFn;
use crate::posix::config::TICK_PERIOD_MS;
use crate::posix::ffi::{
CLOCK_MONOTONIC, SIGALRM, SIGEV_THREAD_ID, SIG_BLOCK, gettid, itimerspec, pthread_kill, sched_yield, sigaddset, sigemptyset, sigevent, sigevent_un, sigprocmask, sigset_t, sigwait,
timer_create, timer_delete, timer_settime, timer_t, timespec,
};
use crate::posix::thread::Thread;
use crate::posix::types::{StackType, TickType, TimerHandle, UBaseType};
use crate::traits::{TimerFn, TimerFnPtr, TimerParam, ToTick};
use crate::utils::{Error, OsalRsBool, Result};
const TIMER_THREAD_NAME: &str = "os_timer";
const TIMER_THREAD_STACK: StackType = 1024;
const TIMER_THREAD_PRIORITY: UBaseType = 1;
const NSECS_PER_SEC: u64 = 1_000_000_000;
struct TimerShared {
timerid: AtomicPtr<c_void>,
ready: AtomicBool,
thread_id: AtomicI32,
us: AtomicU32,
oneshot: AtomicBool,
exit: AtomicBool,
thread: Mutex<Option<Thread>>,
}
#[derive(Clone)]
pub struct Timer {
pub handle: TimerHandle,
name: String,
callback: Option<Arc<TimerFnPtr>>,
param: Option<TimerParam>,
shared: Option<Arc<TimerShared>>,
}
unsafe impl Send for Timer {}
unsafe impl Sync for Timer {}
fn ticks_to_us(ticks: TickType) -> u32 {
(ticks as u64).saturating_mul(TICK_PERIOD_MS).saturating_mul(1000).min(u32::MAX as u64) as u32
}
fn arm(shared: &TimerShared, us: u32) -> OsalRsBool {
if !shared.ready.load(Ordering::Acquire) {
return OsalRsBool::False;
}
let timerid = shared.timerid.load(Ordering::Acquire);
let nanoseconds = (us as u64) * 1000;
let it_value = timespec {
tv_sec: (nanoseconds / NSECS_PER_SEC) as c_long,
tv_nsec: (nanoseconds % NSECS_PER_SEC) as c_long,
};
let it_interval = if us == 0 || shared.oneshot.load(Ordering::Acquire) {
timespec::default()
} else {
it_value
};
let its = itimerspec { it_interval, it_value };
match unsafe { timer_settime(timerid, 0, &its, null_mut()) } {
0 => OsalRsBool::True,
_ => OsalRsBool::False,
}
}
fn run_timer_thread(shared: Arc<TimerShared>, callback: Option<Arc<TimerFnPtr>>, mut param: Option<TimerParam>, timer_self: Timer) -> Result<TimerParam> {
shared.thread_id.store(unsafe { gettid() }, Ordering::Release);
let mut sigset = sigset_t::default();
unsafe {
sigemptyset(&mut sigset);
sigaddset(&mut sigset, SIGALRM);
}
while !shared.exit.load(Ordering::Acquire) {
let mut sig: c_int = 0;
if unsafe { sigwait(&sigset, &mut sig) } != 0 || sig != SIGALRM {
continue;
}
if shared.exit.load(Ordering::Acquire) {
break;
}
if let Some(cb) = &callback {
if let Ok(new_param) = cb(Box::new(timer_self.clone()), param.clone()) {
param = Some(new_param);
}
}
if shared.oneshot.load(Ordering::Acquire) {
break;
}
}
let final_param: TimerParam = match param {
Some(p) => p,
None => Arc::new(()),
};
Ok(final_param)
}
impl Timer {
#[inline]
pub fn new_with_to_tick<F>(name: &str, timer_period_in_ticks: impl ToTick, auto_reload: bool, param: Option<TimerParam>, callback: F) -> Result<Self>
where
F: Fn(Box<dyn TimerFn>, Option<TimerParam>) -> Result<TimerParam> + Send + Sync + Clone + 'static,
{
Self::new(name, timer_period_in_ticks.to_ticks(), auto_reload, param, callback)
}
#[inline]
pub fn start_with_to_tick(&self, ticks_to_wait: impl ToTick) -> OsalRsBool {
self.start(ticks_to_wait.to_ticks())
}
#[inline]
pub fn stop_with_to_tick(&self, ticks_to_wait: impl ToTick) -> OsalRsBool {
self.stop(ticks_to_wait.to_ticks())
}
#[inline]
pub fn reset_with_to_tick(&self, ticks_to_wait: impl ToTick) -> OsalRsBool {
self.reset(ticks_to_wait.to_ticks())
}
#[inline]
pub fn change_period_with_to_tick(&self, new_period_in_ticks: impl ToTick, new_period_ticks: impl ToTick) -> OsalRsBool {
self.change_period(new_period_in_ticks.to_ticks(), new_period_ticks.to_ticks())
}
#[inline]
pub fn delete_with_to_tick(&mut self, ticks_to_wait: impl ToTick) -> OsalRsBool {
self.delete(ticks_to_wait.to_ticks())
}
pub fn new<F>(name: &str, timer_period_in_ticks: TickType, auto_reload: bool, param: Option<TimerParam>, callback: F) -> Result<Self>
where
F: Fn(Box<dyn TimerFn>, Option<TimerParam>) -> Result<TimerParam> + Send + Sync + Clone + 'static,
{
let shared = Arc::new(TimerShared {
timerid: AtomicPtr::new(null_mut()),
ready: AtomicBool::new(false),
thread_id: AtomicI32::new(0),
us: AtomicU32::new(ticks_to_us(timer_period_in_ticks)),
oneshot: AtomicBool::new(!auto_reload),
exit: AtomicBool::new(false),
thread: Mutex::new(None),
});
let mut timer = Self {
handle: null_mut(),
name: name.to_string(),
callback: Some(Arc::new(callback)),
param,
shared: Some(shared.clone()),
};
let mut sigset = sigset_t::default();
unsafe {
sigemptyset(&mut sigset);
sigaddset(&mut sigset, SIGALRM);
sigprocmask(SIG_BLOCK, &sigset, null_mut());
}
let bg_shared = shared.clone();
let bg_callback = timer.callback.clone();
let bg_param = timer.param.clone();
let bg_self = timer.clone();
let mut bg_thread = Thread::new(TIMER_THREAD_NAME, TIMER_THREAD_STACK, TIMER_THREAD_PRIORITY);
let bg_thread = bg_thread.spawn_simple(move || run_timer_thread(bg_shared.clone(), bg_callback.clone(), bg_param.clone(), bg_self.clone()))?;
while shared.thread_id.load(Ordering::Acquire) == 0 {
unsafe {
sched_yield();
}
}
let sev = sigevent {
sigev_notify: SIGEV_THREAD_ID,
sigev_signo: SIGALRM,
sigev_un: sigevent_un {
tid: shared.thread_id.load(Ordering::Acquire),
},
..Default::default()
};
let mut timerid: timer_t = null_mut();
let ret = unsafe { timer_create(CLOCK_MONOTONIC, &sev, &mut timerid) };
if ret != 0 {
shared.exit.store(true, Ordering::Release);
unsafe {
pthread_kill(*bg_thread, SIGALRM);
}
bg_thread.delete();
return Err(Error::ReturnWithCode(ret));
}
shared.timerid.store(timerid, Ordering::Release);
shared.ready.store(true, Ordering::Release);
*shared.thread.lock().unwrap() = Some(bg_thread);
timer.handle = timerid;
Ok(timer)
}
}
impl TimerFn for Timer {
fn is_null(&self) -> bool {
match &self.shared {
Some(shared) => !shared.ready.load(Ordering::Acquire),
None => true,
}
}
fn start(&self, _ticks_to_wait: TickType) -> OsalRsBool {
let Some(shared) = &self.shared else {
return OsalRsBool::False;
};
arm(shared, shared.us.load(Ordering::Acquire))
}
fn stop(&self, _ticks_to_wait: TickType) -> OsalRsBool {
let Some(shared) = &self.shared else {
return OsalRsBool::False;
};
arm(shared, 0)
}
fn reset(&self, ticks_to_wait: TickType) -> OsalRsBool {
self.start(ticks_to_wait)
}
fn change_period(&self, new_period_in_ticks: TickType, ticks_to_wait: TickType) -> OsalRsBool {
let Some(shared) = &self.shared else {
return OsalRsBool::False;
};
shared.us.store(ticks_to_us(new_period_in_ticks), Ordering::Release);
self.start(ticks_to_wait)
}
fn delete(&mut self, _ticks_to_wait: TickType) -> OsalRsBool {
let Some(shared) = self.shared.take() else {
return OsalRsBool::False;
};
if shared.ready.swap(false, Ordering::AcqRel) {
let timerid = shared.timerid.load(Ordering::Acquire);
unsafe {
timer_delete(timerid);
}
}
shared.exit.store(true, Ordering::Release);
if let Ok(mut guard) = shared.thread.lock() {
if let Some(bg_thread) = guard.take() {
unsafe {
pthread_kill(*bg_thread, SIGALRM);
}
bg_thread.delete();
}
}
self.handle = null_mut();
OsalRsBool::True
}
}
impl Drop for Timer {
fn drop(&mut self) {}
}
impl Deref for Timer {
type Target = TimerHandle;
fn deref(&self) -> &Self::Target {
&self.handle
}
}
impl Debug for Timer {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Timer")
.field("handle", &self.handle)
.field("name", &self.name)
.field("has_callback", &self.callback.is_some())
.field("has_param", &self.param.is_some())
.field("is_null", &self.is_null())
.finish()
}
}
impl Display for Timer {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Timer {{ name: {}, handle: {:?} }}", self.name, self.handle)
}
}