use core::cell::UnsafeCell;
use core::mem::MaybeUninit;
use core::sync::atomic::Ordering;
use portable_atomic::{AtomicBool, AtomicU32};
pub struct IsrChannel<T: Copy, const N: usize> {
head: AtomicU32,
tail: AtomicU32,
slots: UnsafeCell<[MaybeUninit<T>; N]>,
}
unsafe impl<T: Copy + Send, const N: usize> Sync for IsrChannel<T, N> {}
impl<T: Copy, const N: usize> IsrChannel<T, N> {
pub const fn new() -> Self {
Self {
head: AtomicU32::new(0),
tail: AtomicU32::new(0),
slots: UnsafeCell::new([MaybeUninit::uninit(); N]),
}
}
#[inline]
pub const fn capacity(&self) -> usize {
N - 1
}
pub unsafe fn try_push(&self, value: T) -> Result<(), T> {
let head = self.head.load(Ordering::Relaxed) as usize;
let tail = self.tail.load(Ordering::Acquire) as usize;
let next_head = (head + 1) % N;
if next_head == tail {
return Err(value);
}
unsafe {
let slot_ptr = (self.slots.get() as *mut MaybeUninit<T>).add(head);
slot_ptr.write(MaybeUninit::new(value));
}
self.head.store(next_head as u32, Ordering::Release);
Ok(())
}
pub unsafe fn try_pop(&self) -> Option<T> {
let tail = self.tail.load(Ordering::Relaxed) as usize;
let head = self.head.load(Ordering::Acquire) as usize;
if head == tail {
return None;
}
let value = unsafe {
let slot_ptr = (self.slots.get() as *const MaybeUninit<T>).add(tail);
slot_ptr.read().assume_init()
};
let next_tail = (tail + 1) % N;
self.tail.store(next_tail as u32, Ordering::Release);
Some(value)
}
#[inline]
pub fn is_empty(&self) -> bool {
self.head.load(Ordering::Acquire) == self.tail.load(Ordering::Acquire)
}
}
impl<T: Copy, const N: usize> Default for IsrChannel<T, N> {
fn default() -> Self {
Self::new()
}
}
pub struct IsrFlag(AtomicBool);
impl IsrFlag {
pub const fn new() -> Self {
Self(AtomicBool::new(false))
}
#[inline]
pub fn set(&self) {
self.0.store(true, Ordering::Release);
}
#[inline]
pub fn take(&self) -> bool {
self.0.swap(false, Ordering::AcqRel)
}
#[inline]
pub fn is_set(&self) -> bool {
self.0.load(Ordering::Acquire)
}
#[inline]
pub fn clear(&self) {
self.0.store(false, Ordering::Release);
}
}
impl Default for IsrFlag {
fn default() -> Self {
Self::new()
}
}
pub struct IsrCounter(AtomicU32);
impl IsrCounter {
pub const fn new() -> Self {
Self(AtomicU32::new(0))
}
#[inline]
pub fn increment(&self) -> u32 {
self.0.fetch_add(1, Ordering::Release)
}
#[inline]
pub fn add(&self, delta: u32) -> u32 {
self.0.fetch_add(delta, Ordering::Release)
}
#[inline]
pub fn read(&self) -> u32 {
self.0.load(Ordering::Acquire)
}
#[inline]
pub fn reset(&self) -> u32 {
self.0.swap(0, Ordering::AcqRel)
}
#[inline]
pub fn store(&self, value: u32) -> u32 {
self.0.swap(value, Ordering::AcqRel)
}
}
impl Default for IsrCounter {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn channel_starts_empty() {
let ch: IsrChannel<u32, 4> = IsrChannel::new();
assert!(ch.is_empty());
assert_eq!(unsafe { ch.try_pop() }, None);
}
#[test]
fn channel_fifo_order() {
let ch: IsrChannel<u32, 4> = IsrChannel::new();
unsafe {
ch.try_push(10).unwrap();
ch.try_push(20).unwrap();
ch.try_push(30).unwrap();
assert_eq!(ch.try_pop(), Some(10));
assert_eq!(ch.try_pop(), Some(20));
assert_eq!(ch.try_pop(), Some(30));
assert_eq!(ch.try_pop(), None);
}
}
#[test]
fn channel_full_returns_err() {
let ch: IsrChannel<u32, 4> = IsrChannel::new();
unsafe {
ch.try_push(1).unwrap();
ch.try_push(2).unwrap();
ch.try_push(3).unwrap();
assert_eq!(ch.try_push(4), Err(4));
assert_eq!(ch.capacity(), 3);
}
}
#[test]
fn channel_wraps_around_on_repeated_push_pop() {
let ch: IsrChannel<u32, 4> = IsrChannel::new();
unsafe {
for i in 0..16 {
ch.try_push(i).unwrap();
assert_eq!(ch.try_pop(), Some(i));
}
}
assert!(ch.is_empty());
}
#[test]
fn flag_set_take_clear_round_trip() {
let f = IsrFlag::new();
assert!(!f.is_set());
f.set();
assert!(f.is_set());
assert!(f.take());
assert!(!f.is_set());
assert!(!f.take());
}
#[test]
fn counter_accumulates_then_resets() {
let c = IsrCounter::new();
c.increment();
c.increment();
c.add(3);
assert_eq!(c.read(), 5);
assert_eq!(c.reset(), 5);
assert_eq!(c.read(), 0);
}
#[test]
fn counter_store_replaces_value() {
let c = IsrCounter::new();
c.add(7);
assert_eq!(c.store(100), 7);
assert_eq!(c.read(), 100);
}
}