avr_atomic/
lib.rs

1// -*- coding: utf-8 -*-
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright (C) 2025 Michael Büsch <m@bues.ch>
4
5//! # AvrAtomic
6//!
7//! A fast atomic type for 8-bit values on AVR microcontrollers.
8//!
9//! # Example
10//!
11//! ```
12//! use avr_atomic::AvrAtomic;
13//!
14//! static VALUE_U8: AvrAtomic<u8> = AvrAtomic::new();
15//! static VALUE_I8: AvrAtomic<i8> = AvrAtomic::new();
16//! static VALUE_BOOL: AvrAtomic<bool> = AvrAtomic::new();
17//!
18//! assert_eq!(VALUE_U8.load(), 0);
19//! VALUE_U8.store(0x42);
20//! assert_eq!(VALUE_U8.load(), 0x42);
21//!
22//! assert_eq!(VALUE_I8.load(), 0);
23//! VALUE_I8.store(-42);
24//! assert_eq!(VALUE_I8.load(), -42);
25//!
26//! assert_eq!(VALUE_BOOL.load(), false);
27//! VALUE_BOOL.store(true);
28//! assert_eq!(VALUE_BOOL.load(), true);
29//! ```
30//!
31//! # Implement AvrAtomic for your own type
32//!
33//! ```
34//! use avr_atomic::{AvrAtomic, AvrAtomicConvert};
35//!
36//! #[derive(Copy, Clone)]
37//! struct MyFoo {
38//!     inner: u8,
39//! }
40//!
41//! impl AvrAtomicConvert for MyFoo {
42//!     fn from_u8(value: u8) -> Self {
43//!         Self { inner: value }
44//!     }
45//!
46//!     fn to_u8(self) -> u8 {
47//!         self.inner
48//!     }
49//! }
50//!
51//! static VALUE: AvrAtomic<MyFoo> = AvrAtomic::new();
52//!
53//! assert_eq!(VALUE.load().inner, 0);
54//! VALUE.store(MyFoo { inner: 2 } );
55//! assert_eq!(VALUE.load().inner, 2);
56//! ```
57
58#![cfg_attr(target_arch = "avr", no_std)]
59#![cfg_attr(target_arch = "avr", feature(asm_experimental_arch))]
60
61use core::{
62    cell::UnsafeCell,
63    marker::PhantomData,
64    sync::atomic::{Ordering::SeqCst, fence},
65};
66
67/// Lock for Non-AVR platforms.
68/// This is mainly useful for testing only.
69#[cfg(not(target_arch = "avr"))]
70static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
71
72#[cfg(target_arch = "avr")]
73#[inline(always)]
74unsafe fn read_atomic_avr(ptr: *const u8) -> u8 {
75    let r26 = ptr.addr() as u8;
76    let r27 = (ptr.addr() >> 8) as u8;
77    let value: u8;
78    // SAFETY: The LD instruction is atomic.
79    unsafe {
80        core::arch::asm!(
81            "ld {value}, X",
82            in("r26") r26,
83            in("r27") r27,
84            value = out(reg) value,
85            options(nostack, preserves_flags),
86        );
87    }
88    value
89}
90
91#[cfg(not(target_arch = "avr"))]
92#[inline(always)]
93unsafe fn read_atomic_generic(ptr: *const u8) -> u8 {
94    let _guard = LOCK.lock();
95    // SAFETY: This load is protected by the `LOCK`.
96    unsafe { ptr.read() }
97}
98
99/// Low level atomic read primitive.
100#[inline(always)]
101unsafe fn read_atomic(ptr: *const u8) -> u8 {
102    fence(SeqCst);
103
104    // SAFETY: Our caller must pass a valid pointer.
105    #[cfg(target_arch = "avr")]
106    let value = unsafe { read_atomic_avr(ptr) };
107
108    // SAFETY: Our caller must pass a valid pointer.
109    #[cfg(not(target_arch = "avr"))]
110    let value = unsafe { read_atomic_generic(ptr) };
111
112    fence(SeqCst);
113    value
114}
115
116#[cfg(target_arch = "avr")]
117#[inline(always)]
118unsafe fn write_atomic_avr(ptr: *mut u8, value: u8) {
119    let r26 = ptr.addr() as u8;
120    let r27 = (ptr.addr() >> 8) as u8;
121    // SAFETY: The ST instruction is atomic.
122    unsafe {
123        core::arch::asm!(
124            "st X, {value}",
125            in("r26") r26,
126            in("r27") r27,
127            value = in(reg) value,
128            options(nostack, preserves_flags),
129        );
130    }
131}
132
133#[cfg(not(target_arch = "avr"))]
134#[inline(always)]
135unsafe fn write_atomic_generic(ptr: *mut u8, value: u8) {
136    let _guard = LOCK.lock();
137    // SAFETY: This store is protected by the `LOCK`.
138    unsafe { ptr.write(value) };
139}
140
141/// Low level atomic write primitive.
142#[inline(always)]
143unsafe fn write_atomic(ptr: *mut u8, value: u8) {
144    fence(SeqCst);
145
146    // SAFETY: Our caller must pass a valid pointer.
147    #[cfg(target_arch = "avr")]
148    unsafe {
149        write_atomic_avr(ptr, value);
150    }
151
152    // SAFETY: Our caller must pass a valid pointer.
153    #[cfg(not(target_arch = "avr"))]
154    unsafe {
155        write_atomic_generic(ptr, value);
156    }
157
158    fence(SeqCst);
159}
160
161/// Trait convert to and from the raw `u8` value.
162pub trait AvrAtomicConvert: Copy {
163    /// Convert from `u8` to `Self`.
164    ///
165    /// # Implementation hint
166    ///
167    /// This function must create a valid `Self` value from the `value` byte.
168    /// Note that a `value` of `0_u8` must always be expected and handled correctly,
169    /// because `0_u8` is the initialization value of [AvrAtomic].
170    ///
171    /// It is guaranteed that [AvrAtomic] only ever passes values to `from_u8`
172    /// that came from `to_u8()` or are equal to `0_u8`.
173    /// Note that `0_u8` can be passed to `from_u8` even if `to_u8()` never returned `0_u8`.
174    fn from_u8(value: u8) -> Self;
175
176    /// Convert from `Self` to `u8`.
177    ///
178    /// # Implementation hint
179    ///
180    /// This function must create an `u8` byte that is possible to be converted
181    /// back into the same `Self` value by `from_u8`.
182    fn to_u8(self) -> u8;
183}
184
185impl AvrAtomicConvert for u8 {
186    #[inline(always)]
187    fn from_u8(value: u8) -> Self {
188        value
189    }
190
191    #[inline(always)]
192    fn to_u8(self) -> u8 {
193        self
194    }
195}
196
197impl AvrAtomicConvert for i8 {
198    #[inline(always)]
199    fn from_u8(value: u8) -> Self {
200        value as _
201    }
202
203    #[inline(always)]
204    fn to_u8(self) -> u8 {
205        self as _
206    }
207}
208
209impl AvrAtomicConvert for bool {
210    #[inline(always)]
211    fn from_u8(value: u8) -> Self {
212        value != 0
213    }
214
215    #[inline(always)]
216    fn to_u8(self) -> u8 {
217        self as _
218    }
219}
220
221/// A fast atomic type for 8-bit values on AVR microcontrollers.
222///
223/// This type has no IRQ-disable/restore or other locking overhead on AVR.
224///
225/// This type provides atomic load and store operations for `u8`, `i8`, and `bool`
226/// by default. But you can extend the supported types with your own types by
227/// implementing [AvrAtomicConvert].
228///
229/// # Internal implementation
230///
231/// Note that the internal representation of the data storage always is a `u8`,
232/// no matter that type `T` actually is.
233#[repr(transparent)]
234pub struct AvrAtomic<T> {
235    // Interior mutable data.
236    data: UnsafeCell<u8>,
237    _phantom: PhantomData<T>,
238}
239
240impl<T> AvrAtomic<T> {
241    /// Create a new [AvrAtomic] with the initial interior raw data being `0_u8`.
242    #[inline(always)]
243    pub const fn new() -> AvrAtomic<T> {
244        Self {
245            data: UnsafeCell::new(0),
246            _phantom: PhantomData,
247        }
248    }
249
250    /// Atomically read as raw `u8` byte.
251    ///
252    /// This atomic read is also a full SeqCst memory barrier.
253    #[inline(always)]
254    pub fn load_raw(&self) -> u8 {
255        // SAFETY: The pointer passed to `read_atomic` is a valid pointer to `u8`.
256        unsafe { read_atomic(self.data.get()) }
257    }
258
259    /// Atomically write as raw `u8` byte.
260    ///
261    /// This atomic write is also a full SeqCst memory barrier.
262    ///
263    /// # Safety
264    ///
265    /// The caller must ensure that `value` is properly encoded to represent a valid `T`.
266    #[inline(always)]
267    pub unsafe fn store_raw(&self, value: u8) {
268        // SAFETY: The pointer passed to `write_atomic` is a valid pointer to `u8`.
269        unsafe { write_atomic(self.data.get(), value) }
270    }
271}
272
273impl<T: AvrAtomicConvert> AvrAtomic<T> {
274    /// Create a new [AvrAtomic] initialized to `value`.
275    #[inline(always)]
276    pub fn new_value(value: T) -> Self {
277        let value = value.to_u8();
278        Self {
279            data: UnsafeCell::new(value),
280            _phantom: PhantomData,
281        }
282    }
283
284    /// Atomically read the current value.
285    ///
286    /// This atomic read is also a full SeqCst memory barrier.
287    #[inline(always)]
288    pub fn load(&self) -> T {
289        T::from_u8(self.load_raw())
290    }
291
292    /// Atomically write a new value.
293    ///
294    /// This atomic write is also a full SeqCst memory barrier.
295    #[inline(always)]
296    pub fn store(&self, value: T) {
297        // SAFETY: The `value` is properly encoded to represent `T`.
298        unsafe { self.store_raw(value.to_u8()) };
299    }
300}
301
302impl<T> Default for AvrAtomic<T> {
303    #[inline(always)]
304    fn default() -> Self {
305        Self::new()
306    }
307}
308
309// SAFETY: The atomic guarantees that `Sync` access is safe.
310unsafe impl<T: Send> Sync for AvrAtomic<T> {}
311
312#[cfg(test)]
313mod test {
314    use super::*;
315
316    #[test]
317    fn test_u8() {
318        let a: AvrAtomic<u8> = AvrAtomic::new();
319        assert_eq!(a.load(), 0);
320        a.store(0x5A);
321        assert_eq!(a.load(), 0x5A);
322        a.store(0);
323        assert_eq!(a.load(), 0);
324
325        let a: AvrAtomic<u8> = AvrAtomic::new_value(99);
326        assert_eq!(a.load(), 99);
327    }
328
329    #[test]
330    fn test_i8() {
331        let a: AvrAtomic<i8> = AvrAtomic::new();
332        assert_eq!(a.load(), 0);
333        a.store(-42);
334        assert_eq!(a.load(), -42);
335        a.store(0);
336        assert_eq!(a.load(), 0);
337
338        let a: AvrAtomic<i8> = AvrAtomic::new_value(-99);
339        assert_eq!(a.load(), -99);
340    }
341
342    #[test]
343    fn test_bool() {
344        let a: AvrAtomic<bool> = AvrAtomic::new();
345        assert!(!a.load());
346        a.store(true);
347        assert!(a.load());
348        a.store(false);
349        assert!(!a.load());
350
351        let a: AvrAtomic<bool> = AvrAtomic::new_value(true);
352        assert!(a.load());
353    }
354}
355
356// vim: ts=4 sw=4 expandtab