nrf_mpsl/
flash.rs

1//! Flash operations using the timeslot API.
2use core::cell::RefCell;
3use core::future::poll_fn;
4use core::mem::MaybeUninit;
5use core::ops::ControlFlow;
6use core::slice;
7use core::sync::atomic::{compiler_fence, Ordering};
8use core::task::Poll;
9
10use cortex_m::peripheral::NVIC;
11use embassy_nrf::interrupt::Interrupt;
12use embassy_nrf::nvmc::{FLASH_SIZE, PAGE_SIZE};
13use embassy_nrf::pac::nvmc::vals::Wen;
14use embassy_nrf::peripherals::NVMC;
15use embassy_nrf::{pac, Peri};
16use embassy_sync::blocking_mutex::raw::RawMutex;
17use embassy_sync::blocking_mutex::Mutex;
18use embassy_sync::waitqueue::WakerRegistration;
19use embedded_storage::nor_flash::{ErrorType, NorFlashError, NorFlashErrorKind};
20
21use crate::{raw, MultiprotocolServiceLayer, RetVal};
22
23// A custom RawMutex implementation that also masks the timer0 interrupt
24// which invokes the timeslot callback.
25struct Timer0RawMutex;
26unsafe impl RawMutex for Timer0RawMutex {
27    const INIT: Self = Timer0RawMutex;
28    fn lock<R>(&self, f: impl FnOnce() -> R) -> R {
29        unsafe {
30            let nvic = &*NVIC::PTR;
31            nvic.icer[0].write(1 << Interrupt::TIMER0 as u8);
32            compiler_fence(Ordering::SeqCst);
33            let r = f();
34            compiler_fence(Ordering::SeqCst);
35            nvic.iser[0].write(1 << Interrupt::TIMER0 as u8);
36            r
37        }
38    }
39}
40
41/// Error type for flash operations.
42#[derive(Debug, Copy, Clone, PartialEq, Eq)]
43#[cfg_attr(feature = "defmt", derive(defmt::Format))]
44pub enum FlashError {
45    /// An error from the MPSL API.
46    Mpsl(crate::Error),
47    /// The operation tried to access an address outside of the flash memory.
48    OutOfBounds,
49    /// The address or buffer is not aligned to a word boundary.
50    Unaligned,
51}
52
53/// Represents a flash that can be used with the MPSL timeslot API to
54/// ensure it does not affect radio transmissions.
55///
56/// # Requirements
57///
58/// The MPSL must be initialized with timeslots support before creating a `Flash` instance.
59/// Use `MultiprotocolServiceLayer::with_timeslots()` to create an MPSL instance with timeslot
60/// support.
61///
62/// # Example
63///
64/// ```rust,no_run
65/// // Initialize MPSL with timeslots
66/// let mpsl = MultiprotocolServiceLayer::with_timeslots(
67///     mpsl_p,
68///     Irqs,
69///     lfclk_cfg,
70///     session_mem
71/// ).unwrap();
72///
73/// // Create a Flash instance
74/// let flash = Flash::take(&mpsl, p.NVMC);
75/// ```
76pub struct Flash<'d> {
77    _mpsl: &'d MultiprotocolServiceLayer<'d>,
78    _p: Peri<'d, NVMC>,
79}
80
81/// Global state of the timeslot flash operation
82struct State {
83    inner: Mutex<Timer0RawMutex, RefCell<InnerState>>,
84}
85
86/// Inner state.
87struct InnerState {
88    taken: bool,
89    operation: FlashOp,
90    slot_duration_us: u32,
91    waker: WakerRegistration,
92    result: Option<Result<(), FlashError>>,
93    timeslot_request: raw::mpsl_timeslot_request_t,
94    return_param: raw::mpsl_timeslot_signal_return_param_t,
95}
96
97/// A flash operation being performed.
98enum FlashOp {
99    None,
100    Erase {
101        /// Next address to erase.
102        address: u32,
103        /// How many partial erase cycles performed on the current address.
104        elapsed: u32,
105        /// Destination address, exclusive
106        to: u32,
107    },
108    Write {
109        /// Destination address
110        dest: *mut u32,
111        /// Source address
112        src: *const u32,
113        words: u32,
114    },
115}
116
117// Safety:
118// Timeslot request and return parameters are only modified before and after timeslot starts,
119// or within the callback.
120unsafe impl Send for InnerState {}
121unsafe impl Sync for InnerState {}
122
123const TIMESLOT_FLASH_HFCLK_CFG: u8 = raw::MPSL_TIMESLOT_HFCLK_CFG_NO_GUARANTEE as u8;
124const TIMESLOT_FLASH_PRIORITY: u8 = raw::MPSL_TIMESLOT_PRIORITY_NORMAL as u8;
125
126// Values derived from nRF SDK
127const TIMESLOT_TIMEOUT_PRIORITY_NORMAL_US: u32 = 30000;
128// Extra slack for non-flash activity
129const TIMESLOT_SLACK_US: u32 = 1000;
130
131// Values according to nRF52 product specification
132const ERASE_PAGE_DURATION_US: u32 = 85_000;
133const WRITE_WORD_DURATION_US: u32 = 41;
134
135#[cfg(not(feature = "nrf52832"))]
136const ERASE_PARTIAL_PAGE_DURATION_MS: u32 = 10;
137#[cfg(not(feature = "nrf52832"))]
138const ERASE_PARTIAL_PAGE_DURATION_US: u32 = ERASE_PARTIAL_PAGE_DURATION_MS * 1000;
139
140const WORD_SIZE: u32 = 4;
141
142// Values derived from Zephyr
143#[cfg(not(feature = "nrf52832"))]
144const TIMESLOT_LENGTH_ERASE_US: u32 = ERASE_PARTIAL_PAGE_DURATION_US;
145
146#[cfg(feature = "nrf52832")]
147const TIMESLOT_LENGTH_ERASE_US: u32 = ERASE_PAGE_DURATION_US;
148
149const TIMESLOT_LENGTH_WRITE_US: u32 = 7500;
150
151static STATE: State = State::new();
152
153impl<'d> Flash<'d> {
154    /// Creates a new `Flash` instance, taking ownership of the NVMC peripheral.
155    ///
156    /// This method should only be called once.
157    ///
158    /// # Panics
159    ///
160    /// This method will panic if it is called more than once.
161    pub fn take(_mpsl: &'d MultiprotocolServiceLayer<'d>, _p: Peri<'d, NVMC>) -> Flash<'d> {
162        STATE.with_inner(|state| {
163            if state.taken {
164                panic!("nrf_mpsl::Flash::take() called multiple times.")
165            }
166            state.taken = true;
167        });
168
169        Self { _mpsl, _p }
170    }
171
172    /// Reads data from flash.
173    ///
174    /// This operation does not require a timeslot.
175    pub fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), FlashError> {
176        if offset as usize >= FLASH_SIZE || offset as usize + bytes.len() > FLASH_SIZE {
177            return Err(FlashError::OutOfBounds);
178        }
179
180        let flash_data = unsafe { slice::from_raw_parts(offset as *const u8, bytes.len()) };
181        bytes.copy_from_slice(flash_data);
182        Ok(())
183    }
184
185    // Perform a flash operation using the timeslot API
186    //
187    // This method creates a timeslot session, reserves a timeslot to run the flash operation,
188    // and waits until the operation is done. Any result associated with the operation is propagated
189    // to the caller.
190    //
191    // If the future is cancelled, or the operation completes, the session is closed.
192    async fn do_op(&mut self, slot_duration_us: u32, op: FlashOp) -> Result<(), FlashError> {
193        // Wait until no flash operation is running
194        poll_fn(|cx| {
195            STATE.with_inner(|state| {
196                state.waker.register(cx.waker());
197                if let FlashOp::None = state.operation {
198                    Poll::Ready(())
199                } else {
200                    Poll::Pending
201                }
202            })
203        })
204        .await;
205
206        let mut session_id: u8 = 0;
207        let ret =
208            unsafe { raw::mpsl_timeslot_session_open(Some(timeslot_session_callback), (&mut session_id) as *mut _) };
209        RetVal::from(ret).to_result()?;
210
211        // Make sure that session is closed if the future is dropped from here on.
212        let _drop = OnDrop::new(|| {
213            let _ = unsafe { raw::mpsl_timeslot_session_close(session_id) };
214        });
215
216        // Prepare the operation and start the timeslot
217        let request = STATE.with_inner(|state| {
218            state.result = None;
219            state.operation = op;
220            state.slot_duration_us = slot_duration_us;
221            state.timeslot_request.request_type = raw::MPSL_TIMESLOT_REQ_TYPE_EARLIEST as u8;
222            state.timeslot_request.params.earliest = raw::mpsl_timeslot_request_earliest_t {
223                hfclk: TIMESLOT_FLASH_HFCLK_CFG,
224                priority: TIMESLOT_FLASH_PRIORITY,
225                length_us: slot_duration_us + TIMESLOT_SLACK_US,
226                timeout_us: TIMESLOT_TIMEOUT_PRIORITY_NORMAL_US,
227            };
228            core::ptr::from_ref(&state.timeslot_request)
229        });
230
231        let ret = unsafe { raw::mpsl_timeslot_request(session_id, request) };
232        RetVal::from(ret).to_result()?;
233
234        // Wait until the operation has produced a result.
235        poll_fn(|cx| {
236            STATE.with_inner(|state| {
237                state.waker.register(cx.waker());
238                match state.result.take() {
239                    Some(result) => Poll::Ready(result),
240                    None => Poll::Pending,
241                }
242            })
243        })
244        .await?;
245
246        // No need to run the drop handler now
247        _drop.defuse();
248
249        unsafe {
250            let ret = raw::mpsl_timeslot_session_close(session_id);
251            RetVal::from(ret).to_result()?;
252        }
253
254        Ok(())
255    }
256
257    /// Erases a region of flash.
258    ///
259    /// This operation is aligned to page boundaries. The `from` and `to` addresses must
260    /// be page-aligned.
261    pub async fn erase(&mut self, from: u32, to: u32) -> Result<(), FlashError> {
262        if to < from || to as usize > FLASH_SIZE {
263            return Err(FlashError::OutOfBounds);
264        }
265        if from as usize % PAGE_SIZE != 0 || to as usize % PAGE_SIZE != 0 {
266            return Err(FlashError::Unaligned);
267        }
268
269        self.do_op(
270            TIMESLOT_LENGTH_ERASE_US,
271            FlashOp::Erase {
272                elapsed: 0,
273                address: from,
274                to,
275            },
276        )
277        .await?;
278        Ok(())
279    }
280
281    /// Writes data to flash.
282    ///
283    /// This operation is aligned to word boundaries. The `offset` and `data.len()` must
284    /// be word-aligned.
285    pub async fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), FlashError> {
286        if offset as usize + data.len() > FLASH_SIZE {
287            return Err(FlashError::OutOfBounds);
288        }
289        if offset as usize % 4 != 0 || data.len() % 4 != 0 {
290            return Err(FlashError::Unaligned);
291        }
292
293        let src = data.as_ptr() as *const u32;
294        let dest = offset as *mut u32;
295        let words = data.len() as u32 / WORD_SIZE;
296        self.do_op(TIMESLOT_LENGTH_WRITE_US, FlashOp::Write { dest, words, src })
297            .await?;
298        Ok(())
299    }
300}
301
302unsafe extern "C" fn timeslot_session_callback(
303    session_id: u8,
304    signal: u32,
305) -> *mut raw::mpsl_timeslot_signal_return_param_t {
306    // Read current time spent inside a slot
307    //
308    // Safety: guaranteed by MPSL to provide values when called inside slot callback.
309    unsafe fn get_timeslot_time_us() -> u32 {
310        let p = pac::TIMER0;
311        p.tasks_capture(0).write_value(1);
312        p.cc(0).read()
313    }
314
315    match signal {
316        raw::MPSL_TIMESLOT_SIGNAL_START => STATE.with_inner(|state| {
317            match state
318                .operation
319                .perform(|| get_timeslot_time_us(), state.slot_duration_us)
320            {
321                ControlFlow::Continue(_) => {
322                    state.return_param.callback_action = raw::MPSL_TIMESLOT_SIGNAL_ACTION_REQUEST as u8;
323                    state.timeslot_request.params.earliest.priority = raw::MPSL_TIMESLOT_PRIORITY_NORMAL as u8;
324                    state.timeslot_request.params.earliest.timeout_us = TIMESLOT_TIMEOUT_PRIORITY_NORMAL_US;
325                    state.return_param.params.request.p_next = &mut state.timeslot_request;
326                }
327                ControlFlow::Break(_) => {
328                    let p = pac::NVMC;
329                    p.config().write(|w| w.set_wen(Wen::REN));
330                    while !p.ready().read().ready() {}
331                    state.result.replace(Ok(()));
332                    state.return_param.callback_action = raw::MPSL_TIMESLOT_SIGNAL_ACTION_END as u8;
333                    state.waker.wake();
334                }
335            }
336            &mut state.return_param as *mut _
337        }),
338        raw::MPSL_TIMESLOT_SIGNAL_SESSION_IDLE => core::ptr::null_mut(),
339
340        raw::MPSL_TIMESLOT_SIGNAL_SESSION_CLOSED => {
341            STATE.with_inner(|state| {
342                state.operation = FlashOp::None;
343                state.waker.wake();
344            });
345            core::ptr::null_mut()
346        }
347        raw::MPSL_TIMESLOT_SIGNAL_CANCELLED | raw::MPSL_TIMESLOT_SIGNAL_BLOCKED => {
348            STATE.with_inner(|state| {
349                state.timeslot_request.params.earliest.priority = raw::MPSL_TIMESLOT_PRIORITY_HIGH as u8;
350                state.timeslot_request.params.earliest.timeout_us = raw::MPSL_TIMESLOT_EARLIEST_TIMEOUT_MAX_US;
351                let ret = unsafe { raw::mpsl_timeslot_request(session_id, &state.timeslot_request) };
352                assert!(ret == 0);
353            });
354            core::ptr::null_mut()
355        }
356        raw::MPSL_TIMESLOT_SIGNAL_OVERSTAYED => {
357            panic!("Used too much of our timeslot");
358        }
359        _ => core::ptr::null_mut(),
360    }
361}
362
363impl State {
364    pub const fn new() -> Self {
365        Self {
366            inner: Mutex::new(RefCell::new(InnerState {
367                taken: false,
368                operation: FlashOp::None,
369                result: None,
370                slot_duration_us: 0,
371                waker: WakerRegistration::new(),
372                timeslot_request: raw::mpsl_timeslot_request_t {
373                    request_type: raw::MPSL_TIMESLOT_REQ_TYPE_EARLIEST as u8,
374                    params: raw::mpsl_timeslot_request_t__bindgen_ty_1 {
375                        earliest: raw::mpsl_timeslot_request_earliest_t {
376                            hfclk: TIMESLOT_FLASH_HFCLK_CFG,
377                            priority: TIMESLOT_FLASH_PRIORITY,
378                            length_us: 0,
379                            timeout_us: TIMESLOT_TIMEOUT_PRIORITY_NORMAL_US,
380                        },
381                    },
382                },
383                return_param: raw::mpsl_timeslot_signal_return_param_t {
384                    callback_action: 0,
385                    params: raw::mpsl_timeslot_signal_return_param_t__bindgen_ty_1 {
386                        request: raw::mpsl_timeslot_signal_return_param_t__bindgen_ty_1__bindgen_ty_1 {
387                            p_next: core::ptr::null_mut(),
388                        },
389                    },
390                },
391            })),
392        }
393    }
394
395    fn with_inner<F: FnOnce(&mut InnerState) -> R, R>(&self, f: F) -> R {
396        self.inner.lock(|inner| {
397            let mut inner = inner.borrow_mut();
398            f(&mut inner)
399        })
400    }
401}
402
403impl FlashOp {
404    #[cfg(not(feature = "nrf52832"))]
405    fn erase<F: Fn() -> u32>(
406        get_time: F,
407        slot_duration_us: u32,
408        elapsed: &mut u32,
409        address: &mut u32,
410        to: u32,
411    ) -> core::ops::ControlFlow<()> {
412        let p = pac::NVMC;
413        loop {
414            // Enable erase and erase next page
415            p.config().write(|w| w.set_wen(Wen::EEN));
416            p.erasepagepartialcfg().write(|w| w.0 = ERASE_PARTIAL_PAGE_DURATION_MS);
417            while !p.ready().read().ready() {}
418
419            p.erasepagepartial().write_value(*address);
420            while !p.ready().read().ready() {}
421            p.config().write(|w| w.set_wen(Wen::REN));
422
423            *elapsed += ERASE_PARTIAL_PAGE_DURATION_US;
424            if *elapsed > ERASE_PAGE_DURATION_US {
425                *address += PAGE_SIZE as u32;
426                if *address >= to {
427                    return ControlFlow::Break(());
428                }
429            }
430            if get_time() + ERASE_PARTIAL_PAGE_DURATION_US >= slot_duration_us {
431                return ControlFlow::Continue(());
432            }
433        }
434    }
435
436    // No partial erase for this chip, just do one page at a time
437    #[cfg(feature = "nrf52832")]
438    fn erase<F: Fn() -> u32>(
439        _get_time: F,
440        _slot_duration_us: u32,
441        _elapsed: &mut u32,
442        address: &mut u32,
443        to: u32,
444    ) -> core::ops::ControlFlow<()> {
445        let p = pac::NVMC;
446        p.config().write(|w| w.set_wen(Wen::EEN));
447        while !p.ready().read().ready() {}
448        p.erasepage().write_value(*address);
449        while !p.ready().read().ready() {}
450        p.config().write(|w| w.set_wen(Wen::REN));
451        *address += PAGE_SIZE as u32;
452        if *address >= to {
453            ControlFlow::Break(())
454        } else {
455            ControlFlow::Continue(())
456        }
457    }
458
459    fn perform<F: Fn() -> u32>(&mut self, get_time: F, slot_duration_us: u32) -> core::ops::ControlFlow<()> {
460        match self {
461            Self::Erase { elapsed, address, to } => {
462                // Do at least one erase to avoid getting stuck. The timeslot parameters guarantees we should be able to at least one operation.
463                if *address >= *to {
464                    return ControlFlow::Break(());
465                }
466                Self::erase(get_time, slot_duration_us, elapsed, address, *to)
467            }
468            Self::Write { dest, src, words } => {
469                let p = pac::NVMC;
470                let mut i = 0;
471                // Do at least one write to avoid getting stuck. The timeslot parameters guarantees we should be able to at least one operation.
472                if *words > 0 {
473                    loop {
474                        p.config().write(|w| w.set_wen(Wen::WEN));
475                        while !p.ready().read().ready() {}
476                        unsafe {
477                            let w = core::ptr::read_unaligned(src.add(i));
478                            core::ptr::write_volatile(dest.add(i), w);
479                        }
480                        while !p.ready().read().ready() {}
481                        i += 1;
482                        if get_time() + WRITE_WORD_DURATION_US >= slot_duration_us || ((i as u32) >= *words) {
483                            break;
484                        }
485                    }
486                }
487
488                unsafe {
489                    *src = src.add(i);
490                    *dest = dest.add(i);
491                    *words -= i as u32;
492                }
493
494                if *words == 0 {
495                    ControlFlow::Break(())
496                } else {
497                    ControlFlow::Continue(())
498                }
499            }
500            FlashOp::None => ControlFlow::Break(()),
501        }
502    }
503}
504
505impl ErrorType for Flash<'_> {
506    type Error = FlashError;
507}
508
509impl embedded_storage_async::nor_flash::MultiwriteNorFlash for Flash<'_> {}
510
511impl NorFlashError for FlashError {
512    fn kind(&self) -> NorFlashErrorKind {
513        match self {
514            Self::Mpsl(_) => NorFlashErrorKind::Other,
515            Self::OutOfBounds => NorFlashErrorKind::OutOfBounds,
516            Self::Unaligned => NorFlashErrorKind::NotAligned,
517        }
518    }
519}
520
521impl embedded_storage::nor_flash::ReadNorFlash for Flash<'_> {
522    const READ_SIZE: usize = 1;
523    fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
524        Self::read(self, offset, bytes)
525    }
526
527    fn capacity(&self) -> usize {
528        FLASH_SIZE
529    }
530}
531
532impl embedded_storage_async::nor_flash::ReadNorFlash for Flash<'_> {
533    const READ_SIZE: usize = 1;
534    async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
535        Self::read(self, offset, bytes)
536    }
537
538    fn capacity(&self) -> usize {
539        FLASH_SIZE
540    }
541}
542
543impl embedded_storage_async::nor_flash::NorFlash for Flash<'_> {
544    const WRITE_SIZE: usize = 4;
545    const ERASE_SIZE: usize = PAGE_SIZE;
546
547    async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
548        Self::erase(self, from, to).await
549    }
550
551    async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
552        Self::write(self, offset, bytes).await
553    }
554}
555
556impl From<crate::Error> for FlashError {
557    fn from(e: crate::Error) -> Self {
558        Self::Mpsl(e)
559    }
560}
561
562/// A guard that runs a closure when it is dropped.
563#[must_use = "to delay the drop handler invocation to the end of the scope"]
564pub(crate) struct OnDrop<F: FnOnce()> {
565    f: MaybeUninit<F>,
566}
567
568impl<F: FnOnce()> OnDrop<F> {
569    /// Creates a new `OnDrop` guard.
570    ///
571    /// The closure `f` will be called when the guard is dropped.
572    pub fn new(f: F) -> Self {
573        Self { f: MaybeUninit::new(f) }
574    }
575
576    /// Prevents the closure from being called when the guard is dropped.
577    pub fn defuse(self) {
578        core::mem::forget(self)
579    }
580}
581
582impl<F: FnOnce()> Drop for OnDrop<F> {
583    fn drop(&mut self) {
584        unsafe { self.f.as_ptr().read()() }
585    }
586}