Skip to main content

autd3_cpu_wire/
update.rs

1use zerocopy::little_endian::U32;
2use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned};
3
4pub const FLASH_BYTES: u32 = 0x40_0000;
5pub const FLASH_SECTOR_BYTES: u32 = 0x1000;
6pub const FLASH_PAGE_BYTES: u32 = 0x100;
7pub const LOADER_REGION_END: u32 = 0x1_0000;
8pub const SLOT_BYTES: u32 = 0x8_0000;
9pub const SLOT_A_BASE: u32 = LOADER_REGION_END;
10pub const SLOT_B_BASE: u32 = SLOT_A_BASE + SLOT_BYTES;
11pub const SLOT_HEADER_BYTES: u32 = FLASH_PAGE_BYTES;
12pub const SLOT_IMAGE_CAPACITY: u32 = SLOT_BYTES - SLOT_HEADER_BYTES;
13pub const IMAGE_MAGIC: u32 = u32::from_le_bytes(*b"AUT2");
14pub const IMAGE_VECTOR_BYTES: u32 = 0x40;
15pub const IMAGE_VECTOR_LOAD_ADDR: u32 = 0x0000_0000;
16pub const IMAGE_RESET_VECTOR_OFFSET: u32 = 0x20;
17pub const IMAGE_APP_LOAD_ADDR: u32 = 0x0004_0000;
18pub const ATCM_APP_REGION_END: u32 = 0x0007_0000;
19pub const IMAGE_APP_CAPACITY: u32 = ATCM_APP_REGION_END - IMAGE_APP_LOAD_ADDR;
20
21#[must_use]
22pub const fn is_plausible_length(length: u32) -> bool {
23    length > IMAGE_VECTOR_BYTES
24        && length <= SLOT_IMAGE_CAPACITY
25        && length - IMAGE_VECTOR_BYTES <= IMAGE_APP_CAPACITY
26}
27
28const _: () = assert!(IMAGE_VECTOR_BYTES + IMAGE_APP_CAPACITY <= SLOT_IMAGE_CAPACITY);
29const _: () = assert!(IMAGE_RESET_VECTOR_OFFSET < IMAGE_VECTOR_BYTES);
30const _: () = assert!(SLOT_A_BASE.is_multiple_of(FLASH_SECTOR_BYTES));
31const _: () = assert!(SLOT_B_BASE.is_multiple_of(FLASH_SECTOR_BYTES));
32const _: () = assert!(SLOT_B_BASE + SLOT_BYTES <= FLASH_BYTES);
33
34#[derive(Clone, Copy, PartialEq, Eq, Debug)]
35pub enum Slot {
36    A,
37    B,
38}
39
40impl Slot {
41    #[must_use]
42    pub const fn base(self) -> u32 {
43        match self {
44            Self::A => SLOT_A_BASE,
45            Self::B => SLOT_B_BASE,
46        }
47    }
48
49    #[must_use]
50    pub const fn image_base(self) -> u32 {
51        self.base() + SLOT_HEADER_BYTES
52    }
53
54    #[must_use]
55    pub const fn other(self) -> Self {
56        match self {
57            Self::A => Self::B,
58            Self::B => Self::A,
59        }
60    }
61}
62
63pub const IMAGE_STATUS_NORMAL: u32 = 0xFFFF_FFFF;
64pub const IMAGE_STATUS_TRIAL: u32 = 0x5A5A_5A5A;
65pub const IMAGE_STATUS_CONFIRMED: u32 = 0x0000_0000;
66pub const IMAGE_ATTEMPTS_UNTRIED: u32 = 0xFFFF_FFFF;
67pub const IMAGE_MAX_ATTEMPTS: u32 = 1;
68
69pub const IMAGE_HEADER_STATUS_OFFSET: u32 = 16;
70pub const IMAGE_HEADER_ATTEMPTS_OFFSET: u32 = 20;
71
72const _: () = assert!(IMAGE_STATUS_CONFIRMED & !IMAGE_STATUS_TRIAL == 0);
73const _: () = assert!(IMAGE_MAX_ATTEMPTS >= 1 && IMAGE_MAX_ATTEMPTS <= u32::BITS);
74
75#[must_use]
76pub const fn next_attempts(attempts: u32) -> u32 {
77    attempts << 1
78}
79
80#[derive(
81    FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned, Clone, Copy, PartialEq, Eq, Debug,
82)]
83#[repr(C)]
84pub struct ImageHeader {
85    pub magic: U32,
86    pub generation: U32,
87    pub length: U32,
88    pub crc32: U32,
89    pub status: U32,
90    pub attempts: U32,
91}
92
93const _: () = assert!(core::mem::size_of::<ImageHeader>() == 24);
94const _: () = assert!(core::mem::size_of::<ImageHeader>() <= SLOT_HEADER_BYTES as usize);
95const _: () =
96    assert!(core::mem::offset_of!(ImageHeader, status) == IMAGE_HEADER_STATUS_OFFSET as usize);
97const _: () =
98    assert!(core::mem::offset_of!(ImageHeader, attempts) == IMAGE_HEADER_ATTEMPTS_OFFSET as usize);
99
100impl ImageHeader {
101    #[must_use]
102    pub fn new(generation: u32, length: u32, crc32: u32) -> Self {
103        Self::with_status(generation, length, crc32, IMAGE_STATUS_NORMAL)
104    }
105
106    #[must_use]
107    pub fn new_trial(generation: u32, length: u32, crc32: u32) -> Self {
108        Self::with_status(generation, length, crc32, IMAGE_STATUS_TRIAL)
109    }
110
111    fn with_status(generation: u32, length: u32, crc32: u32, status: u32) -> Self {
112        Self {
113            magic: U32::new(IMAGE_MAGIC),
114            generation: U32::new(generation),
115            length: U32::new(length),
116            crc32: U32::new(crc32),
117            status: U32::new(status),
118            attempts: U32::new(IMAGE_ATTEMPTS_UNTRIED),
119        }
120    }
121
122    #[must_use]
123    pub fn is_plausible(&self) -> bool {
124        self.magic.get() == IMAGE_MAGIC && is_plausible_length(self.length.get())
125    }
126
127    #[must_use]
128    pub fn is_trial(&self) -> bool {
129        self.status.get() == IMAGE_STATUS_TRIAL
130    }
131
132    #[must_use]
133    pub fn needs_confirmation(&self) -> bool {
134        !matches!(
135            self.status.get(),
136            IMAGE_STATUS_NORMAL | IMAGE_STATUS_CONFIRMED
137        )
138    }
139
140    #[must_use]
141    pub fn attempts_used(&self) -> u32 {
142        self.attempts.get().count_zeros()
143    }
144
145    #[must_use]
146    pub fn is_boot_eligible(&self) -> bool {
147        match self.status.get() {
148            IMAGE_STATUS_NORMAL | IMAGE_STATUS_CONFIRMED => true,
149            IMAGE_STATUS_TRIAL => self.attempts_used() < IMAGE_MAX_ATTEMPTS,
150            _ => false,
151        }
152    }
153}
154
155#[derive(Clone, Copy, PartialEq, Eq, Debug)]
156pub struct SlotCandidate {
157    pub generation: u32,
158    pub eligible: bool,
159}
160
161#[must_use]
162pub fn select_slot(a: Option<u32>, b: Option<u32>) -> Option<(Slot, u32)> {
163    match (a, b) {
164        (Some(ga), Some(gb)) if gb > ga => Some((Slot::B, gb)),
165        (Some(ga), _) => Some((Slot::A, ga)),
166        (None, Some(gb)) => Some((Slot::B, gb)),
167        (None, None) => None,
168    }
169}
170
171#[must_use]
172pub fn select_boot_slot(a: Option<SlotCandidate>, b: Option<SlotCandidate>) -> Option<(Slot, u32)> {
173    let eligible = |c: Option<SlotCandidate>| c.filter(|c| c.eligible).map(|c| c.generation);
174    let any = |c: Option<SlotCandidate>| c.map(|c| c.generation);
175    select_slot(eligible(a), eligible(b)).or_else(|| select_slot(any(a), any(b)))
176}
177
178pub const CRC32_INIT: u32 = 0xFFFF_FFFF;
179
180#[must_use]
181pub const fn crc32_update(mut crc: u32, bytes: &[u8]) -> u32 {
182    let mut i = 0;
183    while i < bytes.len() {
184        crc ^= bytes[i] as u32;
185        let mut bit = 0;
186        while bit < 8 {
187            let mask = 0u32.wrapping_sub(crc & 1);
188            crc = (crc >> 1) ^ (0xEDB8_8320 & mask);
189            bit += 1;
190        }
191        i += 1;
192    }
193    crc
194}
195
196#[must_use]
197pub const fn crc32_finish(crc: u32) -> u32 {
198    !crc
199}
200
201#[must_use]
202pub const fn crc32(bytes: &[u8]) -> u32 {
203    crc32_finish(crc32_update(CRC32_INIT, bytes))
204}
205
206#[cfg(test)]
207mod tests {
208    use super::*;
209
210    #[test]
211    fn crc32_matches_the_reference_vector() {
212        assert_eq!(crc32(b"123456789"), 0xCBF4_3926);
213        assert_eq!(crc32(b""), 0);
214    }
215
216    #[test]
217    fn crc32_streams_in_pieces() {
218        let whole = crc32(b"hello world");
219        let mut crc = crc32_update(CRC32_INIT, b"hello ");
220        crc = crc32_update(crc, b"world");
221        assert_eq!(crc32_finish(crc), whole);
222    }
223
224    #[test]
225    fn slot_selection_prefers_the_newest_generation_and_a_on_ties() {
226        assert_eq!(select_slot(None, None), None);
227        assert_eq!(select_slot(Some(3), None), Some((Slot::A, 3)));
228        assert_eq!(select_slot(None, Some(7)), Some((Slot::B, 7)));
229        assert_eq!(select_slot(Some(3), Some(7)), Some((Slot::B, 7)));
230        assert_eq!(select_slot(Some(9), Some(7)), Some((Slot::A, 9)));
231        assert_eq!(select_slot(Some(5), Some(5)), Some((Slot::A, 5)));
232    }
233
234    #[test]
235    fn header_plausibility() {
236        let max = IMAGE_VECTOR_BYTES + IMAGE_APP_CAPACITY;
237        assert!(ImageHeader::new(0, IMAGE_VECTOR_BYTES + 1, 0).is_plausible());
238        assert!(ImageHeader::new(0, max, 0).is_plausible());
239        assert!(!ImageHeader::new(0, 0, 0).is_plausible());
240        assert!(!ImageHeader::new(0, IMAGE_VECTOR_BYTES, 0).is_plausible());
241        assert!(!ImageHeader::new(0, max + 1, 0).is_plausible());
242        assert!(!ImageHeader::new(0, SLOT_IMAGE_CAPACITY, 0).is_plausible());
243        assert!(!ImageHeader::new(0, SLOT_IMAGE_CAPACITY + 1, 0).is_plausible());
244        let mut blank = ImageHeader::new(0, IMAGE_VECTOR_BYTES + 1, 0);
245        blank.magic = U32::new(0xFFFF_FFFF);
246        assert!(!blank.is_plausible());
247    }
248
249    #[test]
250    fn the_image_magic_spells_the_format() {
251        assert_eq!(IMAGE_MAGIC.to_le_bytes(), *b"AUT2");
252    }
253
254    #[test]
255    fn trial_state_machine_only_clears_bits() {
256        let normal = ImageHeader::new(1, 100, 0);
257        assert!(!normal.needs_confirmation());
258        assert!(normal.is_boot_eligible());
259
260        let mut trial = ImageHeader::new_trial(2, 100, 0);
261        assert!(trial.is_trial());
262        assert!(trial.needs_confirmation());
263        assert_eq!(trial.attempts_used(), 0);
264        assert!(trial.is_boot_eligible());
265        assert_eq!(IMAGE_STATUS_TRIAL & normal.status.get(), IMAGE_STATUS_TRIAL);
266
267        let before = trial.attempts.get();
268        trial.attempts = U32::new(next_attempts(before));
269        assert_eq!(trial.attempts.get() & !before, 0);
270        assert_eq!(trial.attempts_used(), 1);
271        assert!(!trial.is_boot_eligible());
272
273        trial.status = U32::new(IMAGE_STATUS_CONFIRMED);
274        assert!(!trial.needs_confirmation());
275        assert!(trial.is_boot_eligible());
276    }
277
278    #[test]
279    fn an_interrupted_status_write_is_not_eligible() {
280        let mut h = ImageHeader::new_trial(2, 100, 0);
281        h.status = U32::new(IMAGE_STATUS_TRIAL & 0x00FF_FFFF);
282        assert!(h.needs_confirmation());
283        assert!(!h.is_trial());
284        assert!(!h.is_boot_eligible());
285    }
286
287    #[test]
288    fn untried_trials_boot_and_tried_ones_fall_back() {
289        let ok = |generation| {
290            Some(SlotCandidate {
291                generation,
292                eligible: true,
293            })
294        };
295        let spent = |generation| {
296            Some(SlotCandidate {
297                generation,
298                eligible: false,
299            })
300        };
301        assert_eq!(select_boot_slot(ok(1), ok(2)), Some((Slot::B, 2)));
302        assert_eq!(select_boot_slot(ok(1), spent(2)), Some((Slot::A, 1)));
303        assert_eq!(select_boot_slot(spent(3), ok(2)), Some((Slot::B, 2)));
304        assert_eq!(select_boot_slot(spent(1), spent(2)), Some((Slot::B, 2)));
305        assert_eq!(select_boot_slot(None, spent(2)), Some((Slot::B, 2)));
306        assert_eq!(select_boot_slot(None, None), None);
307    }
308
309    #[test]
310    fn slots_do_not_overlap_the_loader() {
311        assert!(Slot::A.base() >= LOADER_REGION_END);
312        assert_eq!(Slot::A.other(), Slot::B);
313        assert_eq!(Slot::B.image_base(), SLOT_B_BASE + SLOT_HEADER_BYTES);
314    }
315}