Skip to main content

stm_32/peripherals/
sd_card.rs

1// ******************************************************************************
2// * File     : platforms/stm_32/src/peripherals/sd_card.rs
3// * Date     : July 23, 2026
4// ******************************************************************************
5// *
6// * Copyright (c) 2023, AeroVironment, Inc.
7// * All rights reserved.
8// *
9// * Redistribution and use in source and binary forms, with or without
10// * modification, are permitted provided that the following conditions are met:
11// *
12// * 1.Redistributions of source code must retain the above copyright notice, this
13// * list of conditions and the following disclaimer.
14// *
15// * 2.Redistributions in binary form must reproduce the above copyright notice,
16// * this list of conditions and the following disclaimer in the documentation
17// * and/or other materials provided with the distribution.
18// *
19// * 3.Neither the name of the copyright holder nor the names of its
20// * contributors may be used to endorse or promote products derived from
21// * this software without specific prior written permission.
22// *
23// * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24// * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25// * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26// * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
27// * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28// * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29// * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
30// * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
31// * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32// * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33// *
34// ******************************************************************************
35
36use core::cmp::min;
37
38use embassy_futures::select::{Either, select};
39use embassy_stm32::gpio::Input;
40use embassy_stm32::sdmmc::sd::{Addressable, CmdBlock, DataBlock, StorageDevice};
41use embassy_stm32::sdmmc::{Error, Sdmmc};
42use embassy_stm32::time::mhz;
43use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
44use embassy_sync::signal::Signal;
45
46use veloxity_core::errors;
47use veloxity_core::packets::{self, PARAM_PACKET_SIZE, ParamPacket};
48use veloxity_core::params::Params;
49use veloxity_core::params::storage::{
50    ROSFLIGHT_C_PARAM_STORAGE_SIZE, decode_rosflight_c_params, encode_rosflight_c_params,
51};
52
53const BLOCK_SIZE: usize = 512;
54const CHECKSUM_SIZE: usize = size_of::<u32>();
55const MAX_BLOCKS: usize = 16;
56const MAX_PAYLOAD_SIZE: usize = MAX_BLOCKS * BLOCK_SIZE - CHECKSUM_SIZE;
57const _: () = assert!(ROSFLIGHT_C_PARAM_STORAGE_SIZE == PARAM_PACKET_SIZE);
58
59pub static SD_WRITE_SIGNAL: Signal<
60    CriticalSectionRawMutex,
61    Result<packets::ParamPacket, errors::SensorError>,
62> = Signal::<CriticalSectionRawMutex, Result<packets::ParamPacket, errors::SensorError>>::new();
63
64pub static SD_READ_SIGNAL: Signal<
65    CriticalSectionRawMutex,
66    Result<packets::ParamPacket, errors::SensorError>,
67> = Signal::<CriticalSectionRawMutex, Result<packets::ParamPacket, errors::SensorError>>::new();
68
69pub static SD_READ_REQUEST_SIGNAL: Signal<CriticalSectionRawMutex, u64> =
70    Signal::<CriticalSectionRawMutex, u64>::new();
71
72/// Errors returned while accessing the ROSflight parameter image.
73#[derive(Debug)]
74pub enum SdCardError {
75    /// The STM32 SDMMC peripheral or the card reported an error.
76    Device(Error),
77    /// The requested image and its checksum exceed ROSflight's 16-block limit.
78    ImageTooLarge,
79    /// The inserted card cannot hold the requested image.
80    CardTooSmall,
81    /// The stored image did not pass ROSflight's CRC check.
82    ChecksumMismatch,
83}
84
85impl From<Error> for SdCardError {
86    fn from(error: Error) -> Self {
87        Self::Device(error)
88    }
89}
90
91/// Encodes the C-compatible subset into ROSflight 2.0's persisted `params_t`.
92pub fn encode_params(params: &Params) -> Option<ParamPacket> {
93    let mut packet = ParamPacket::default();
94    encode_rosflight_c_params(params, &mut packet.values).then_some(packet)
95}
96
97/// Decodes ROSflight 2.0's persisted `params_t` into the Rust parameter table.
98/// Rust-only parameters retain their defaults because they are absent on disk.
99pub fn decode_params(packet: &ParamPacket) -> Option<Params> {
100    if packet.header.status != 1 {
101        return None;
102    }
103    decode_rosflight_c_params(&packet.values)
104}
105
106pub struct SdCard {
107    pub sdmmc: Sdmmc<'static>,
108    pub detect: Input<'static>,
109}
110
111impl SdCard {
112    /// Reads and verifies a ROSflight parameter image from the SD card.
113    pub async fn read(&mut self, destination: &mut [u8]) -> Result<(), SdCardError> {
114        validate_image_size(destination.len())?;
115
116        let mut cmd_block = CmdBlock::new();
117        let mut storage =
118            StorageDevice::new_sd_card(&mut self.sdmmc, &mut cmd_block, mhz(4)).await?;
119        validate_card_size(&storage, destination.len())?;
120
121        let checksum = read_image(&mut storage, destination).await?;
122        if checksum != rosflight_crc32(destination) {
123            return Err(SdCardError::ChecksumMismatch);
124        }
125
126        Ok(())
127    }
128
129    /// Writes a ROSflight parameter image and its CRC to the SD card.
130    pub async fn write(&mut self, source: &[u8]) -> Result<(), SdCardError> {
131        validate_image_size(source.len())?;
132
133        let mut cmd_block = CmdBlock::new();
134        let mut storage =
135            StorageDevice::new_sd_card(&mut self.sdmmc, &mut cmd_block, mhz(4)).await?;
136        validate_card_size(&storage, source.len())?;
137
138        write_image(&mut storage, source, rosflight_crc32(source)).await
139    }
140
141    async fn run(&mut self) {
142        loop {
143            match select(SD_READ_REQUEST_SIGNAL.wait(), SD_WRITE_SIGNAL.wait()).await {
144                Either::First(request_id) => {
145                    let mut packet = packets::ParamPacket {
146                        header: packets::RosflightPacketHeader {
147                            timestamp: request_id,
148                            status: 0,
149                        },
150                        values: [0u8; packets::PARAM_PACKET_SIZE],
151                    };
152                    packet.header.status = u16::from(self.read(&mut packet.values).await.is_ok());
153                    SD_READ_SIGNAL.signal(Ok(packet));
154                }
155                Either::Second(Ok(mut packet)) => {
156                    packet.header.status = u16::from(self.write(&packet.values).await.is_ok());
157                    SD_READ_SIGNAL.signal(Ok(packet));
158                }
159                Either::Second(Err(error)) => SD_READ_SIGNAL.signal(Err(error)),
160            }
161        }
162    }
163}
164
165fn image_blocks(image_size: usize) -> usize {
166    (image_size + CHECKSUM_SIZE).div_ceil(BLOCK_SIZE)
167}
168
169fn validate_image_size(image_size: usize) -> Result<(), SdCardError> {
170    if image_size > MAX_PAYLOAD_SIZE {
171        Err(SdCardError::ImageTooLarge)
172    } else {
173        Ok(())
174    }
175}
176
177fn validate_card_size(
178    storage: &StorageDevice<'_, '_, impl Addressable>,
179    image_size: usize,
180) -> Result<(), SdCardError> {
181    let available_blocks = storage.card().size() as usize / BLOCK_SIZE;
182    if image_blocks(image_size) > available_blocks {
183        Err(SdCardError::CardTooSmall)
184    } else {
185        Ok(())
186    }
187}
188
189async fn read_image(
190    storage: &mut StorageDevice<'_, '_, impl Addressable>,
191    destination: &mut [u8],
192) -> Result<u32, SdCardError> {
193    let image_size = destination.len();
194    let mut stored_checksum = [0u8; CHECKSUM_SIZE];
195
196    for block_index in 0..image_blocks(image_size) {
197        let mut block = DataBlock::new();
198        storage.read_block(block_index as u32, &mut block).await?;
199
200        let block_start = block_index * BLOCK_SIZE;
201        let payload_end = min(image_size.saturating_sub(block_start), BLOCK_SIZE);
202        if payload_end != 0 {
203            destination[block_start..block_start + payload_end]
204                .copy_from_slice(&block[..payload_end]);
205        }
206
207        copy_checksum_overlap(&block, block_start, image_size, &mut stored_checksum);
208    }
209
210    Ok(u32::from_le_bytes(stored_checksum))
211}
212
213async fn write_image(
214    storage: &mut StorageDevice<'_, '_, impl Addressable>,
215    source: &[u8],
216    checksum: u32,
217) -> Result<(), SdCardError> {
218    let image_size = source.len();
219    let checksum = checksum.to_le_bytes();
220
221    for block_index in 0..image_blocks(image_size) {
222        let mut block = DataBlock::new();
223        let block_start = block_index * BLOCK_SIZE;
224        let payload_end = min(image_size.saturating_sub(block_start), BLOCK_SIZE);
225        if payload_end != 0 {
226            block[..payload_end].copy_from_slice(&source[block_start..block_start + payload_end]);
227        }
228
229        write_checksum_overlap(&mut block, block_start, image_size, &checksum);
230        storage.write_block(block_index as u32, &block).await?;
231    }
232
233    Ok(())
234}
235
236fn copy_checksum_overlap(
237    block: &[u8; BLOCK_SIZE],
238    block_start: usize,
239    image_size: usize,
240    checksum: &mut [u8; CHECKSUM_SIZE],
241) {
242    let checksum_start = image_size;
243    let checksum_end = image_size + CHECKSUM_SIZE;
244    let block_end = block_start + BLOCK_SIZE;
245    let overlap_start = block_start.max(checksum_start);
246    let overlap_end = block_end.min(checksum_end);
247
248    if overlap_start < overlap_end {
249        checksum[overlap_start - checksum_start..overlap_end - checksum_start]
250            .copy_from_slice(&block[overlap_start - block_start..overlap_end - block_start]);
251    }
252}
253
254fn write_checksum_overlap(
255    block: &mut [u8; BLOCK_SIZE],
256    block_start: usize,
257    image_size: usize,
258    checksum: &[u8; CHECKSUM_SIZE],
259) {
260    let checksum_start = image_size;
261    let checksum_end = image_size + CHECKSUM_SIZE;
262    let block_end = block_start + BLOCK_SIZE;
263    let overlap_start = block_start.max(checksum_start);
264    let overlap_end = block_end.min(checksum_end);
265
266    if overlap_start < overlap_end {
267        block[overlap_start - block_start..overlap_end - block_start].copy_from_slice(
268            &checksum[overlap_start - checksum_start..overlap_end - checksum_start],
269        );
270    }
271}
272
273// STM32's default CRC peripheral configuration: polynomial 0x04C11DB7,
274// initial value 0xFFFFFFFF, no input/output reflection, and no final XOR.
275fn rosflight_crc32(bytes: &[u8]) -> u32 {
276    let mut crc = u32::MAX;
277
278    for byte in bytes {
279        crc ^= u32::from(*byte) << 24;
280        for _ in 0..8 {
281            crc = if crc & 0x8000_0000 != 0 {
282                (crc << 1) ^ 0x04C1_1DB7
283            } else {
284                crc << 1
285            };
286        }
287    }
288
289    crc
290}
291
292#[embassy_executor::task]
293pub async fn task(mut sd_card: SdCard) {
294    sd_card.run().await;
295}
296
297#[cfg(test)]
298mod tests {
299    use super::*;
300
301    #[test]
302    fn crc_matches_stm32_default_crc_for_standard_vector() {
303        assert_eq!(rosflight_crc32(b"123456789"), 0x0376_E6E7);
304    }
305
306    #[test]
307    fn block_count_includes_checksum() {
308        assert_eq!(image_blocks(0), 1);
309        assert_eq!(image_blocks(508), 1);
310        assert_eq!(image_blocks(509), 2);
311        assert_eq!(image_blocks(2048), 5);
312    }
313}