use core::ffi::{c_void, CStr};
use crate::kernel::tasks::{
taskENTER_CRITICAL, taskEXIT_CRITICAL, taskSCHEDULER_RUNNING, xTaskGetSchedulerState,
};
#[cfg(any(feature = "alloc", feature = "heap-4", feature = "heap-5"))]
use crate::kernel::timers::xTimerCreate;
use crate::kernel::timers::{
pvTimerGetTimerID, vTimerSetTimerID, xTimerChangePeriod, xTimerChangePeriodFromISR,
xTimerCreateStatic, xTimerDelete, xTimerGetExpiryTime, xTimerGetPeriod,
xTimerGetTimerDaemonTaskHandle, xTimerIsTimerActive, xTimerReset, xTimerResetFromISR,
xTimerStart, xTimerStartFromISR, xTimerStop, xTimerStopFromISR, StaticTimer_t,
TimerCallbackFunction_t,
};
use crate::sync::task::TaskContext;
use crate::sync::{assert_can_block, assert_task_context, is_in_isr};
use crate::types::*;
pub type TimerCallback = extern "C" fn(TimerHandle_t);
pub struct Timer {
handle: TimerHandle_t,
owns_handle: bool,
}
unsafe impl Sync for Timer {}
unsafe impl Send for Timer {}
impl Timer {
#[cfg(all(
feature = "timers",
any(feature = "alloc", feature = "heap-4", feature = "heap-5")
))]
pub fn new_periodic(
context: &TaskContext,
name: &'static CStr,
period_ticks: TickType_t,
callback: TimerCallback,
) -> Option<Self> {
Self::new_internal(context, name, period_ticks, true, callback)
}
#[cfg(all(
feature = "timers",
any(feature = "alloc", feature = "heap-4", feature = "heap-5")
))]
pub fn new_oneshot(
context: &TaskContext,
name: &'static CStr,
period_ticks: TickType_t,
callback: TimerCallback,
) -> Option<Self> {
Self::new_internal(context, name, period_ticks, false, callback)
}
#[cfg(all(
feature = "timers",
any(feature = "alloc", feature = "heap-4", feature = "heap-5")
))]
fn new_internal(
_context: &TaskContext,
name: &'static CStr,
period_ticks: TickType_t,
auto_reload: bool,
callback: TimerCallback,
) -> Option<Self> {
assert_task_context("Timer::new");
Self::assert_nonzero_period("Timer::new", period_ticks);
let handle = unsafe {
xTimerCreate(
name.as_ptr().cast::<u8>(),
period_ticks,
if auto_reload { pdTRUE } else { pdFALSE },
core::ptr::null_mut(), callback as TimerCallbackFunction_t,
)
};
if handle.is_null() {
None
} else {
Some(Self {
handle,
owns_handle: true,
})
}
}
#[cfg(feature = "timers")]
pub fn new_periodic_static(
context: &TaskContext,
name: &'static CStr,
period_ticks: TickType_t,
callback: TimerCallback,
timer_buffer: &'static mut StaticTimer_t,
) -> Option<Self> {
Self::new_static_internal(context, name, period_ticks, true, callback, timer_buffer)
}
#[cfg(feature = "timers")]
pub fn new_oneshot_static(
context: &TaskContext,
name: &'static CStr,
period_ticks: TickType_t,
callback: TimerCallback,
timer_buffer: &'static mut StaticTimer_t,
) -> Option<Self> {
Self::new_static_internal(context, name, period_ticks, false, callback, timer_buffer)
}
#[cfg(feature = "timers")]
fn new_static_internal(
_context: &TaskContext,
name: &'static CStr,
period_ticks: TickType_t,
auto_reload: bool,
callback: TimerCallback,
timer_buffer: &'static mut StaticTimer_t,
) -> Option<Self> {
assert_task_context("Timer::new_static");
Self::assert_nonzero_period("Timer::new_static", period_ticks);
let handle = unsafe {
xTimerCreateStatic(
name.as_ptr().cast::<u8>(),
period_ticks,
if auto_reload { pdTRUE } else { pdFALSE },
core::ptr::null_mut(),
callback as TimerCallbackFunction_t,
timer_buffer as *mut StaticTimer_t,
)
};
if handle.is_null() {
None
} else {
Some(Self {
handle,
owns_handle: true,
})
}
}
fn assert_nonzero_period(operation: &str, period_ticks: TickType_t) {
assert!(
period_ticks != 0,
"{operation} requires a nonzero timer period"
);
}
fn assert_command_context(_context: &TaskContext, operation: &str, ticks_to_wait: TickType_t) {
assert_can_block(operation, ticks_to_wait);
}
#[cfg(feature = "timers")]
pub fn start(&self, context: &TaskContext) -> bool {
self.start_timeout(context, 0)
}
#[cfg(feature = "timers")]
pub fn start_timeout(&self, context: &TaskContext, ticks: TickType_t) -> bool {
Self::assert_command_context(context, "Timer::start_timeout", ticks);
unsafe { xTimerStart(self.handle, ticks) == pdPASS }
}
#[cfg(feature = "timers")]
pub fn stop(&self, context: &TaskContext) -> bool {
self.stop_timeout(context, 0)
}
#[cfg(feature = "timers")]
pub fn stop_timeout(&self, context: &TaskContext, ticks: TickType_t) -> bool {
Self::assert_command_context(context, "Timer::stop_timeout", ticks);
unsafe { xTimerStop(self.handle, ticks) == pdPASS }
}
#[cfg(feature = "timers")]
pub fn reset(&self, context: &TaskContext) -> bool {
self.reset_timeout(context, 0)
}
#[cfg(feature = "timers")]
pub fn reset_timeout(&self, context: &TaskContext, ticks: TickType_t) -> bool {
Self::assert_command_context(context, "Timer::reset_timeout", ticks);
unsafe { xTimerReset(self.handle, ticks) == pdPASS }
}
#[cfg(feature = "timers")]
pub fn set_period(&self, context: &TaskContext, new_period_ticks: TickType_t) -> bool {
self.set_period_timeout(context, new_period_ticks, 0)
}
#[cfg(feature = "timers")]
pub fn set_period_timeout(
&self,
context: &TaskContext,
new_period_ticks: TickType_t,
ticks: TickType_t,
) -> bool {
Self::assert_nonzero_period("Timer::set_period_timeout", new_period_ticks);
Self::assert_command_context(context, "Timer::set_period_timeout", ticks);
unsafe { xTimerChangePeriod(self.handle, new_period_ticks, ticks) == pdPASS }
}
pub unsafe fn start_from_isr(&self, higher_priority_task_woken: &mut BaseType_t) -> bool {
unsafe { xTimerStartFromISR(self.handle, higher_priority_task_woken) == pdPASS }
}
pub unsafe fn stop_from_isr(&self, higher_priority_task_woken: &mut BaseType_t) -> bool {
unsafe { xTimerStopFromISR(self.handle, higher_priority_task_woken) == pdPASS }
}
pub unsafe fn reset_from_isr(&self, higher_priority_task_woken: &mut BaseType_t) -> bool {
unsafe { xTimerResetFromISR(self.handle, higher_priority_task_woken) == pdPASS }
}
pub unsafe fn set_period_from_isr(
&self,
new_period_ticks: TickType_t,
higher_priority_task_woken: &mut BaseType_t,
) -> bool {
Self::assert_nonzero_period("Timer::set_period_from_isr", new_period_ticks);
unsafe {
xTimerChangePeriodFromISR(self.handle, new_period_ticks, higher_priority_task_woken)
== pdPASS
}
}
#[cfg(feature = "timers")]
pub fn is_active(&self, _context: &TaskContext) -> bool {
assert_task_context("Timer::is_active");
unsafe { xTimerIsTimerActive(self.handle) != pdFALSE }
}
#[cfg(feature = "timers")]
pub fn period(&self, _context: &TaskContext) -> TickType_t {
assert_task_context("Timer::period");
taskENTER_CRITICAL();
let period = unsafe { xTimerGetPeriod(self.handle) };
taskEXIT_CRITICAL();
period
}
#[cfg(feature = "timers")]
pub fn expiry_time(&self, _context: &TaskContext) -> TickType_t {
assert_task_context("Timer::expiry_time");
taskENTER_CRITICAL();
let expiry_time = unsafe { xTimerGetExpiryTime(self.handle) };
taskEXIT_CRITICAL();
expiry_time
}
#[cfg(feature = "timers")]
pub fn set_id<T: Sync + 'static>(&self, _context: &TaskContext, id: &'static T) {
assert_task_context("Timer::set_id");
unsafe { vTimerSetTimerID(self.handle, (id as *const T).cast_mut().cast::<c_void>()) };
}
#[cfg(feature = "timers")]
pub unsafe fn set_id_raw(&self, _context: &TaskContext, id: *mut c_void) {
assert_task_context("Timer::set_id_raw");
unsafe { vTimerSetTimerID(self.handle, id) };
}
#[cfg(feature = "timers")]
pub unsafe fn get_id<T>(&self, context: &TaskContext) -> Option<*mut T> {
let id = self.get_id_raw(context);
if id.is_null() {
None
} else {
Some(id as *mut T)
}
}
#[cfg(feature = "timers")]
pub fn get_id_raw(&self, _context: &TaskContext) -> *mut c_void {
assert_task_context("Timer::get_id_raw");
unsafe { pvTimerGetTimerID(self.handle) }
}
pub unsafe fn raw_handle(&self) -> TimerHandle_t {
self.handle
}
pub fn into_raw(mut self) -> TimerHandle_t {
self.owns_handle = false;
self.handle
}
pub unsafe fn from_raw(handle: TimerHandle_t) -> Self {
assert!(
!handle.is_null(),
"Timer::from_raw requires a non-null handle"
);
Self {
handle,
owns_handle: false,
}
}
pub fn delete(mut self, context: &TaskContext, ticks: TickType_t) -> Result<(), Self> {
Self::assert_command_context(context, "Timer::delete", ticks);
if unsafe { xTimerDelete(self.handle, ticks) } == pdPASS {
self.owns_handle = false;
Ok(())
} else {
Err(self)
}
}
#[cfg(feature = "timers")]
pub fn daemon_task_handle(_context: &TaskContext) -> Option<TaskHandle_t> {
assert_task_context("Timer::daemon_task_handle");
if xTaskGetSchedulerState() == taskSCHEDULER_RUNNING {
Some(xTimerGetTimerDaemonTaskHandle())
} else {
None
}
}
}
impl Drop for Timer {
fn drop(&mut self) {
if self.owns_handle && !is_in_isr() {
let _ = unsafe { xTimerDelete(self.handle, 0) };
self.owns_handle = false;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic(expected = "requires a nonzero timer period")]
fn zero_timer_period_is_rejected_before_kernel_call() {
Timer::assert_nonzero_period("test", 0);
}
#[test]
fn timer_handle_is_send_and_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<Timer>();
}
#[cfg(feature = "port-test")]
#[test]
fn direct_timer_queries_are_preemption_safe() {
extern "C" fn callback(_timer: TimerHandle_t) {}
crate::port::test_port_reset();
let context = unsafe { TaskContext::assume() };
let control = std::boxed::Box::leak(std::boxed::Box::new(StaticTimer_t::new()));
let timer = Timer::new_periodic_static(&context, c"query", 7, callback, control)
.expect("static timer creation");
let before = crate::port::test_port_snapshot();
assert_eq!(timer.period(&context), 7);
assert_eq!(timer.expiry_time(&context), 0);
let after = crate::port::test_port_snapshot();
assert_eq!(after.critical_nesting, 0);
assert!(!after.interrupts_masked);
assert_eq!(
after.interrupt_disable_count,
before.interrupt_disable_count + 2
);
assert_eq!(
after.interrupt_enable_count,
before.interrupt_enable_count + 2
);
}
}