Skip to main content

esp_hal/rtc_cntl/cpu_retention/
memory.rs

1//! Shared CPU retention buffer types and the one-shot install API.
2
3use core::{
4    cell::UnsafeCell,
5    mem::MaybeUninit,
6    ptr::{self, NonNull},
7};
8
9use portable_atomic::{AtomicPtr, Ordering};
10use static_cell::ConstStaticCell;
11
12use crate::rtc_cntl::sleep::LowPower;
13
14cfg_select! {
15    cpu_retention = "software" => {
16        pub(crate) use super::software::BUFFER_SIZE;
17    }
18    _ => {
19        pub(crate) const BUFFER_SIZE: usize = property!("sleep.cpu_retention_mem_size");
20
21        /// `repr(align)` takes a literal, so the buffer takes the strictest alignment of every
22        /// chip. The alignment then needs no check at run time.
23        const _: () = ::core::assert!(property!("sleep.cpu_retention_mem_align") <= 16);
24
25        const MEM_START: usize = property!("sleep.cpu_retention_mem_start");
26        const MEM_END: usize = property!("sleep.cpu_retention_mem_end");
27    }
28}
29
30static INSTALLED: AtomicPtr<u8> = AtomicPtr::new(ptr::null_mut());
31
32/// Memory that light sleep uses to retain the CPU domain.
33///
34/// This storage must be placed to `#[ram(reclaimed, unstable(zeroed))]`, then
35/// a static mutable reference to [`CpuRetentionMemory`] can be taken out of it.
36#[instability::unstable]
37pub struct CpuRetentionStorage {
38    inner: ConstStaticCell<CpuRetentionMemory>,
39}
40
41impl CpuRetentionStorage {
42    /// Creates storage for the retention memory.
43    #[instability::unstable]
44    pub const fn new() -> Self {
45        Self {
46            inner: ConstStaticCell::new(CpuRetentionMemory {
47                storage: UnsafeCell::new(MaybeUninit::uninit()),
48            }),
49        }
50    }
51
52    /// Takes the reference to the retention memory.
53    ///
54    /// Can only be called once.
55    #[instability::unstable]
56    pub fn take(&'static self) -> &'static mut CpuRetentionMemory {
57        self.inner.take()
58    }
59}
60
61#[instability::unstable]
62impl Default for CpuRetentionStorage {
63    fn default() -> Self {
64        Self::new()
65    }
66}
67
68// SAFETY: ConstStaticCell is a `MaybeUninit` buffer, and an AtomicBool strapped together,
69// both are safe to be zero-initialized.
70unsafe impl bytemuck::Zeroable for CpuRetentionStorage {}
71
72/// Memory that light sleep uses to retain the CPU domain.
73///
74/// The sleep path writes this memory while the CPU domain has no power, so the buffer gives no way
75/// to read or write its contents through safe Rust.
76///
77/// Install the buffer with [`LowPower::install_cpu_retention_memory`]. The buffer must live in
78/// internal RAM that stays powered across light sleep. The [`#[ram(reclaimed)]`][crate::ram]
79/// attribute places a static inside the reachable range on chips that use the retention DMA.
80#[repr(align(16))]
81#[instability::unstable]
82pub struct CpuRetentionMemory {
83    storage: UnsafeCell<MaybeUninit<[u8; BUFFER_SIZE]>>,
84}
85
86impl CpuRetentionMemory {
87    pub(crate) fn as_mut_ptr(&self) -> *mut u8 {
88        self.storage.get().cast()
89    }
90}
91
92/// The reason why [`LowPower::install_cpu_retention_memory`] refused a buffer.
93#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
94#[cfg_attr(feature = "defmt", derive(defmt::Format))]
95#[instability::unstable]
96#[non_exhaustive]
97pub enum CpuRetentionMemoryError {
98    /// The buffer is not inside the range that retention needs.
99    #[cfg(not(cpu_retention = "software"))]
100    OutOfRange,
101    /// The driver holds a retention buffer already.
102    AlreadyInstalled,
103}
104
105#[instability::unstable]
106impl core::error::Error for CpuRetentionMemoryError {}
107
108#[instability::unstable]
109impl core::fmt::Display for CpuRetentionMemoryError {
110    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
111        match self {
112            #[cfg(not(cpu_retention = "software"))]
113            Self::OutOfRange => write!(
114                f,
115                "the buffer is outside the retention memory range (0x{MEM_START:X}..0x{MEM_END:X})"
116            ),
117            Self::AlreadyInstalled => write!(f, "a retention buffer is installed already"),
118        }
119    }
120}
121
122impl LowPower<'_> {
123    #[procmacros::doc_replace]
124    /// Gives light sleep the memory that it uses to retain the CPU domain.
125    ///
126    /// Light sleep powers the CPU domain down only after it has this memory. The memory stays with
127    /// the driver for the life of the program, and a second call is an error, because the sleep
128    /// path can hold one buffer only.
129    ///
130    /// # Errors
131    ///
132    /// Returns [`CpuRetentionMemoryError`] if the driver holds a buffer already, or if the buffer
133    /// is not inside the range that retention needs. The
134    /// [`#[ram(reclaimed, unstable(zeroed))]`][crate::ram] attribute places a static inside that
135    /// range on DMA chips.
136    ///
137    /// # Examples
138    ///
139    /// ```rust, no_run
140    /// # {before_snippet}
141    /// use esp_hal::rtc_cntl::{CpuRetentionStorage, sleep::LowPower};
142    ///
143    /// // #[esp_hal::ram(reclaimed, unstable(zeroed))]
144    /// static RETENTION: CpuRetentionStorage = CpuRetentionStorage::new();
145    ///
146    /// let mut lpwr = LowPower::new(peripherals.LPWR);
147    /// lpwr.install_cpu_retention_memory(RETENTION.take())?;
148    /// # {after_snippet}
149    /// ```
150    #[instability::unstable]
151    pub fn install_cpu_retention_memory(
152        &mut self,
153        memory: &'static mut CpuRetentionMemory,
154    ) -> Result<(), CpuRetentionMemoryError> {
155        let memory = memory.as_mut_ptr();
156
157        #[cfg(not(cpu_retention = "software"))]
158        {
159            let start = memory as usize;
160            if start < MEM_START || start + BUFFER_SIZE > MEM_END {
161                return Err(CpuRetentionMemoryError::OutOfRange);
162            }
163        }
164
165        INSTALLED
166            .compare_exchange(ptr::null_mut(), memory, Ordering::AcqRel, Ordering::Acquire)
167            .map(|_| ())
168            .map_err(|_| CpuRetentionMemoryError::AlreadyInstalled)
169    }
170}
171
172pub(crate) fn installed_buffer_ptr() -> Option<NonNull<u8>> {
173    let memory = INSTALLED.load(Ordering::Acquire);
174
175    NonNull::new(memory.cast::<u8>())
176}