umsh_ulcp_runtime/
reply_completion.rs

1//! One outstanding response whose successful transmission permits DFU.
2//!
3//! The driver owns the transition. Transport tasks can only complete its
4//! expiring token; a stale writer can neither send nor authorize a later reset.
5
6use core::cell::RefCell;
7use embassy_sync::{
8    blocking_mutex::{Mutex, raw::CriticalSectionRawMutex},
9    signal::Signal,
10};
11use embassy_time::{Instant, with_deadline};
12
13#[derive(Default)]
14struct State {
15    next: u32,
16    active: Option<(u32, Option<bool>)>,
17}
18
19impl State {
20    fn begin(&mut self) -> u32 {
21        self.next = self.next.wrapping_add(1);
22        self.active = Some((self.next, None));
23        self.next
24    }
25
26    fn complete(&mut self, id: u32, sent: bool) -> bool {
27        if let Some((active, result)) = &mut self.active {
28            if *active == id && result.is_none() {
29                *result = Some(sent);
30                return sent;
31            }
32        }
33        false
34    }
35}
36
37static STATE: Mutex<CriticalSectionRawMutex, RefCell<State>> = Mutex::new(RefCell::new(State {
38    next: 0,
39    active: None,
40}));
41static CHANGED: Signal<CriticalSectionRawMutex, ()> = Signal::new();
42
43#[derive(Clone, Copy, Debug)]
44pub struct ReplyCompletion {
45    id: u32,
46    deadline: Instant,
47}
48
49impl ReplyCompletion {
50    pub(crate) fn begin(deadline: Instant) -> Self {
51        let id = STATE.lock(|state| state.borrow_mut().begin());
52        Self { id, deadline }
53    }
54
55    pub fn deadline(self) -> Instant {
56        self.deadline
57    }
58
59    /// Check before writing each segment, including the first one.
60    pub fn is_pending(self) -> bool {
61        Instant::now() < self.deadline
62            && STATE.lock(|state| state.borrow().active == Some((self.id, None)))
63    }
64
65    /// Returns true only if this call authorized the current handoff.
66    /// Cache owners must invalidate success if the deadline expired or the
67    /// driver retired the token while transmission was finishing.
68    pub fn complete(self, sent: bool) -> bool {
69        let accepted = STATE.lock(|state| {
70            state
71                .borrow_mut()
72                .complete(self.id, sent && Instant::now() < self.deadline)
73        });
74        CHANGED.signal(());
75        accepted
76    }
77
78    pub(crate) async fn wait(self) -> bool {
79        loop {
80            let result = STATE.lock(|state| {
81                let state = state.borrow();
82                match state.active {
83                    Some((id, result)) if id == self.id => result,
84                    _ => Some(false),
85                }
86            });
87            if let Some(sent) = result {
88                STATE.lock(|state| {
89                    let mut state = state.borrow_mut();
90                    if state.active.is_some_and(|(id, _)| id == self.id) {
91                        state.active = None;
92                    }
93                });
94                return sent;
95            }
96            if with_deadline(self.deadline, CHANGED.wait()).await.is_err() {
97                self.complete(false);
98            }
99        }
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    #[test]
108    fn only_the_current_response_can_complete_once() {
109        let mut state = State::default();
110        let first = state.begin();
111        assert_eq!(state.active, Some((first, None)));
112        state.complete(first, false);
113        state.complete(first, true);
114        assert_eq!(state.active, Some((first, Some(false))));
115        let second = state.begin();
116        state.complete(first, true);
117        assert_eq!(state.active, Some((second, None)));
118        state.complete(second, true);
119        state.complete(second, false);
120        assert_eq!(state.active, Some((second, Some(true))));
121    }
122}