#![cfg_attr(target_arch = "avr", no_std)]
#![cfg_attr(target_arch = "avr", feature(asm_experimental_arch))]
use core::{
cell::UnsafeCell,
marker::PhantomData,
sync::atomic::{Ordering::SeqCst, fence},
};
#[cfg(not(target_arch = "avr"))]
static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
#[cfg(target_arch = "avr")]
#[inline(always)]
unsafe fn read_atomic_avr(ptr: *const u8) -> u8 {
let r26 = ptr.addr() as u8;
let r27 = (ptr.addr() >> 8) as u8;
let value: u8;
unsafe {
core::arch::asm!(
"ld {value}, X",
in("r26") r26,
in("r27") r27,
value = out(reg) value,
options(nostack, preserves_flags),
);
}
value
}
#[cfg(not(target_arch = "avr"))]
#[inline(always)]
unsafe fn read_atomic_generic(ptr: *const u8) -> u8 {
let _guard = LOCK.lock();
unsafe { ptr.read() }
}
#[inline(always)]
unsafe fn read_atomic(ptr: *const u8) -> u8 {
fence(SeqCst);
#[cfg(target_arch = "avr")]
let value = unsafe { read_atomic_avr(ptr) };
#[cfg(not(target_arch = "avr"))]
let value = unsafe { read_atomic_generic(ptr) };
fence(SeqCst);
value
}
#[cfg(target_arch = "avr")]
#[inline(always)]
unsafe fn write_atomic_avr(ptr: *mut u8, value: u8) {
let r26 = ptr.addr() as u8;
let r27 = (ptr.addr() >> 8) as u8;
unsafe {
core::arch::asm!(
"st X, {value}",
in("r26") r26,
in("r27") r27,
value = in(reg) value,
options(nostack, preserves_flags),
);
}
}
#[cfg(not(target_arch = "avr"))]
#[inline(always)]
unsafe fn write_atomic_generic(ptr: *mut u8, value: u8) {
let _guard = LOCK.lock();
unsafe { ptr.write(value) };
}
#[inline(always)]
unsafe fn write_atomic(ptr: *mut u8, value: u8) {
fence(SeqCst);
#[cfg(target_arch = "avr")]
unsafe {
write_atomic_avr(ptr, value);
}
#[cfg(not(target_arch = "avr"))]
unsafe {
write_atomic_generic(ptr, value);
}
fence(SeqCst);
}
pub trait AvrAtomicConvert: Copy {
fn from_u8(value: u8) -> Self;
fn to_u8(self) -> u8;
}
impl AvrAtomicConvert for u8 {
#[inline(always)]
fn from_u8(value: u8) -> Self {
value
}
#[inline(always)]
fn to_u8(self) -> u8 {
self
}
}
impl AvrAtomicConvert for i8 {
#[inline(always)]
fn from_u8(value: u8) -> Self {
value as _
}
#[inline(always)]
fn to_u8(self) -> u8 {
self as _
}
}
impl AvrAtomicConvert for bool {
#[inline(always)]
fn from_u8(value: u8) -> Self {
value != 0
}
#[inline(always)]
fn to_u8(self) -> u8 {
self as _
}
}
#[repr(transparent)]
pub struct AvrAtomic<T> {
data: UnsafeCell<u8>,
_phantom: PhantomData<T>,
}
impl<T> AvrAtomic<T> {
#[inline(always)]
pub const fn new() -> AvrAtomic<T> {
Self {
data: UnsafeCell::new(0),
_phantom: PhantomData,
}
}
#[inline(always)]
pub fn load_raw(&self) -> u8 {
unsafe { read_atomic(self.data.get()) }
}
#[inline(always)]
pub unsafe fn store_raw(&self, value: u8) {
unsafe { write_atomic(self.data.get(), value) }
}
}
impl<T: AvrAtomicConvert> AvrAtomic<T> {
#[inline(always)]
pub fn new_value(value: T) -> Self {
let value = value.to_u8();
Self {
data: UnsafeCell::new(value),
_phantom: PhantomData,
}
}
#[inline(always)]
pub fn load(&self) -> T {
T::from_u8(self.load_raw())
}
#[inline(always)]
pub fn store(&self, value: T) {
unsafe { self.store_raw(value.to_u8()) };
}
}
impl<T> Default for AvrAtomic<T> {
#[inline(always)]
fn default() -> Self {
Self::new()
}
}
unsafe impl<T: Send> Sync for AvrAtomic<T> {}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_u8() {
let a: AvrAtomic<u8> = AvrAtomic::new();
assert_eq!(a.load(), 0);
a.store(0x5A);
assert_eq!(a.load(), 0x5A);
a.store(0);
assert_eq!(a.load(), 0);
let a: AvrAtomic<u8> = AvrAtomic::new_value(99);
assert_eq!(a.load(), 99);
}
#[test]
fn test_i8() {
let a: AvrAtomic<i8> = AvrAtomic::new();
assert_eq!(a.load(), 0);
a.store(-42);
assert_eq!(a.load(), -42);
a.store(0);
assert_eq!(a.load(), 0);
let a: AvrAtomic<i8> = AvrAtomic::new_value(-99);
assert_eq!(a.load(), -99);
}
#[test]
fn test_bool() {
let a: AvrAtomic<bool> = AvrAtomic::new();
assert!(!a.load());
a.store(true);
assert!(a.load());
a.store(false);
assert!(!a.load());
let a: AvrAtomic<bool> = AvrAtomic::new_value(true);
assert!(a.load());
}
}