From 2839174e5f55e6588c38b075e3c90f4778a8c585 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Tue, 9 Sep 2025 13:00:23 +0200 Subject: [PATCH] fix for embedded systems, introduce portable atomic seq counters --- Cargo.toml | 1 + src/seq_count.rs | 159 +++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 142 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index dc70f5c..5c41682 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ thiserror = { version = "2", default-features = false } num_enum = { version = ">0.5, <=0.7", default-features = false } num-traits = { version = "0.2", default-features = false } serde = { version = "1", optional = true, default-features = false, features = ["derive"] } +portable-atomic = "1" time = { version = "0.3", default-features = false, optional = true } chrono = { version = "0.4", default-features = false, optional = true } diff --git a/src/seq_count.rs b/src/seq_count.rs index 7a4cab6..ad14395 100644 --- a/src/seq_count.rs +++ b/src/seq_count.rs @@ -160,6 +160,7 @@ impl SequenceCounter for core::sync::atomic::AtomicU32 { } } +#[cfg(target_has_atomic = "64")] impl SequenceCounter for core::sync::atomic::AtomicU64 { type Raw = u64; @@ -174,6 +175,62 @@ impl SequenceCounter for core::sync::atomic::AtomicU64 { } } +impl SequenceCounter for portable_atomic::AtomicU8 { + type Raw = u8; + + const MAX_BIT_WIDTH: usize = 8; + + fn get(&self) -> Self::Raw { + self.load(core::sync::atomic::Ordering::Relaxed) + } + + fn increment(&self) { + self.fetch_add(1, core::sync::atomic::Ordering::Relaxed); + } +} + +impl SequenceCounter for portable_atomic::AtomicU16 { + type Raw = u16; + + const MAX_BIT_WIDTH: usize = 16; + + fn get(&self) -> Self::Raw { + self.load(core::sync::atomic::Ordering::Relaxed) + } + + fn increment(&self) { + self.fetch_add(1, core::sync::atomic::Ordering::Relaxed); + } +} + +impl SequenceCounter for portable_atomic::AtomicU32 { + type Raw = u32; + + const MAX_BIT_WIDTH: usize = 32; + + fn get(&self) -> Self::Raw { + self.load(core::sync::atomic::Ordering::Relaxed) + } + + fn increment(&self) { + self.fetch_add(1, core::sync::atomic::Ordering::Relaxed); + } +} + +impl SequenceCounter for portable_atomic::AtomicU64 { + type Raw = u64; + + const MAX_BIT_WIDTH: usize = 64; + + fn get(&self) -> Self::Raw { + self.load(core::sync::atomic::Ordering::Relaxed) + } + + fn increment(&self) { + self.fetch_add(1, core::sync::atomic::Ordering::Relaxed); + } +} + impl SequenceCounter for &T { type Raw = T::Raw; const MAX_BIT_WIDTH: usize = T::MAX_BIT_WIDTH; @@ -188,9 +245,8 @@ impl SequenceCounter for &T { } macro_rules! sync_clonable_seq_counter_impl { - ($($ty: ident,)+) => { - $(paste! { - + ($ty: ident) => { + paste::paste! { /// This can be used if a custom wrap value is required when using a thread-safe /// atomic based sequence counter. #[derive(Debug)] @@ -232,15 +288,22 @@ macro_rules! sync_clonable_seq_counter_impl { ).unwrap() } } - })+ - } + } + }; } -sync_clonable_seq_counter_impl!(u8, u16, u32, u64,); +#[cfg(target_has_atomic = "8")] +sync_clonable_seq_counter_impl!(u8); +#[cfg(target_has_atomic = "16")] +sync_clonable_seq_counter_impl!(u16); +#[cfg(target_has_atomic = "32")] +sync_clonable_seq_counter_impl!(u32); +#[cfg(target_has_atomic = "64")] +sync_clonable_seq_counter_impl!(u64); #[cfg(test)] mod tests { - use core::sync::atomic::AtomicU8; + use core::sync::atomic::{AtomicU16, AtomicU32, AtomicU64, AtomicU8}; use crate::seq_count::{ SequenceCounter, SequenceCounterCcsdsSimple, SequenceCounterSimple, @@ -284,22 +347,82 @@ mod tests { assert_eq!(ccsds_counter.get(), 0); } - #[test] - fn test_atomic_ref_counters() { - let sync_u8_counter = AtomicU8::new(0); - assert_eq!(sync_u8_counter.get(), 0); - assert_eq!(sync_u8_counter.get_and_increment(), 0); - assert_eq!(sync_u8_counter.get_and_increment(), 1); - assert_eq!(sync_u8_counter.get(), 2); + fn common_counter_test(seq_counter: &mut impl SequenceCounter) { + assert_eq!(seq_counter.get().into(), 0); + assert_eq!(seq_counter.get_and_increment().into(), 0); + assert_eq!(seq_counter.get_and_increment().into(), 1); + assert_eq!(seq_counter.get().into(), 2); + seq_counter.increment_mut(); + assert_eq!(seq_counter.get().into(), 3); + assert_eq!(seq_counter.get_and_increment_mut().into(), 3); + assert_eq!(seq_counter.get().into(), 4); } #[test] - fn test_atomic_ref_counters_overflow() { - let sync_u8_counter = AtomicU8::new(0); + fn test_atomic_counter_u8() { + let mut sync_u8_counter = AtomicU8::new(0); + common_counter_test(&mut sync_u8_counter); + } + + #[test] + fn test_atomic_counter_u16() { + let mut sync_u16_counter = AtomicU16::new(0); + common_counter_test(&mut sync_u16_counter); + } + + #[test] + fn test_atomic_counter_u32() { + let mut sync_u32_counter = AtomicU32::new(0); + common_counter_test(&mut sync_u32_counter); + } + + #[test] + fn test_atomic_counter_u64() { + let mut sync_u64_counter = AtomicU64::new(0); + common_counter_test(&mut sync_u64_counter); + } + + #[test] + fn test_portable_atomic_counter_u8() { + let mut sync_u8_counter = portable_atomic::AtomicU8::new(0); + common_counter_test(&mut sync_u8_counter); + } + + #[test] + fn test_portable_atomic_counter_u16() { + let mut sync_u16_counter = portable_atomic::AtomicU16::new(0); + common_counter_test(&mut sync_u16_counter); + } + + #[test] + fn test_portable_atomic_counter_u32() { + let mut sync_u32_counter = portable_atomic::AtomicU32::new(0); + common_counter_test(&mut sync_u32_counter); + } + + #[test] + fn test_portable_atomic_counter_u64() { + let mut sync_u64_counter = portable_atomic::AtomicU64::new(0); + common_counter_test(&mut sync_u64_counter); + } + + fn common_overflow_test_u8(seq_counter: &impl SequenceCounter) { for _ in 0..u8::MAX as u16 + 1 { - sync_u8_counter.increment(); + seq_counter.increment(); } - assert_eq!(sync_u8_counter.get(), 0); + assert_eq!(seq_counter.get().into(), 0); + } + + #[test] + fn test_atomic_u8_counter_overflow() { + let sync_u8_counter = AtomicU8::new(0); + common_overflow_test_u8(&sync_u8_counter); + } + + #[test] + fn test_portable_atomic_u8_counter_overflow() { + let sync_u8_counter = portable_atomic::AtomicU8::new(0); + common_overflow_test_u8(&sync_u8_counter); } #[test]