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