embedded_sdmmc/
lib.rs

1//! # embedded-sdmmc
2//!
3//! > An SD/MMC Library written in Embedded Rust
4//!
5//! This crate is intended to allow you to read/write files on a FAT formatted
6//! SD card on your Rust Embedded device, as easily as using the `SdFat` Arduino
7//! library. It is written in pure-Rust, is `#![no_std]` and does not use
8//! `alloc` or `collections` to keep the memory footprint low. In the first
9//! instance it is designed for readability and simplicity over performance.
10//!
11//! ## Using the crate
12//!
13//! You will need something that implements the `BlockDevice` trait, which can
14//! read and write the 512-byte blocks (or sectors) from your card. If you were
15//! to implement this over USB Mass Storage, there's no reason this crate
16//! couldn't work with a USB Thumb Drive, but we only supply a `BlockDevice`
17//! suitable for reading SD and SDHC cards over SPI.
18//!
19//! ```rust
20//! use embedded_sdmmc::{Error, Mode, SdCard, SdCardError, TimeSource, VolumeIdx, VolumeManager};
21//!
22//! fn example<S, D, T>(spi: S, delay: D, ts: T) -> Result<(), Error<SdCardError>>
23//! where
24//!     S: embedded_hal::spi::SpiDevice,
25//!     D: embedded_hal::delay::DelayNs,
26//!     T: TimeSource,
27//! {
28//!     let sdcard = SdCard::new(spi, delay);
29//!     println!("Card size is {} bytes", sdcard.num_bytes()?);
30//!     let mut volume_mgr = VolumeManager::new(sdcard, ts);
31//!     let mut volume0 = volume_mgr.open_volume(VolumeIdx(0))?;
32//!     println!("Volume 0: {:?}", volume0);
33//!     let mut root_dir = volume0.open_root_dir()?;
34//!     let mut my_file = root_dir.open_file_in_dir("MY_FILE.TXT", Mode::ReadOnly)?;
35//!     while !my_file.is_eof() {
36//!         let mut buffer = [0u8; 32];
37//!         let num_read = my_file.read(&mut buffer)?;
38//!         for b in &buffer[0..num_read] {
39//!             print!("{}", *b as char);
40//!         }
41//!     }
42//!     Ok(())
43//! }
44//! ```
45//!
46//! ## Features
47//!
48//! * `log`: Enabled by default. Generates log messages using the `log` crate.
49//! * `defmt-log`: By turning off the default features and enabling the
50//! `defmt-log` feature you can configure this crate to log messages over defmt
51//! instead.
52//!
53//! You cannot enable both the `log` feature and the `defmt-log` feature.
54
55#![cfg_attr(not(test), no_std)]
56#![deny(missing_docs)]
57
58// ****************************************************************************
59//
60// Imports
61//
62// ****************************************************************************
63
64#[cfg(test)]
65#[macro_use]
66extern crate hex_literal;
67
68#[macro_use]
69mod structure;
70
71pub mod blockdevice;
72pub mod fat;
73pub mod filesystem;
74pub mod sdcard;
75
76use filesystem::SearchId;
77
78#[doc(inline)]
79pub use crate::blockdevice::{Block, BlockCount, BlockDevice, BlockIdx};
80
81#[doc(inline)]
82pub use crate::fat::FatVolume;
83
84#[doc(inline)]
85pub use crate::filesystem::{
86    Attributes, ClusterId, DirEntry, Directory, File, FilenameError, Mode, RawDirectory, RawFile,
87    ShortFileName, TimeSource, Timestamp, MAX_FILE_SIZE,
88};
89
90use filesystem::DirectoryInfo;
91
92#[doc(inline)]
93pub use crate::sdcard::Error as SdCardError;
94
95#[doc(inline)]
96pub use crate::sdcard::SdCard;
97
98mod volume_mgr;
99#[doc(inline)]
100pub use volume_mgr::VolumeManager;
101
102#[cfg(all(feature = "defmt-log", feature = "log"))]
103compile_error!("Cannot enable both log and defmt-log");
104
105#[cfg(feature = "log")]
106use log::{debug, trace, warn};
107
108#[cfg(feature = "defmt-log")]
109use defmt::{debug, trace, warn};
110
111#[cfg(all(not(feature = "defmt-log"), not(feature = "log")))]
112#[macro_export]
113/// Like log::debug! but does nothing at all
114macro_rules! debug {
115    ($($arg:tt)+) => {};
116}
117
118#[cfg(all(not(feature = "defmt-log"), not(feature = "log")))]
119#[macro_export]
120/// Like log::trace! but does nothing at all
121macro_rules! trace {
122    ($($arg:tt)+) => {};
123}
124
125#[cfg(all(not(feature = "defmt-log"), not(feature = "log")))]
126#[macro_export]
127/// Like log::warn! but does nothing at all
128macro_rules! warn {
129    ($($arg:tt)+) => {};
130}
131
132// ****************************************************************************
133//
134// Public Types
135//
136// ****************************************************************************
137
138/// All the ways the functions in this crate can fail.
139#[cfg_attr(feature = "defmt-log", derive(defmt::Format))]
140#[derive(Debug, Clone)]
141pub enum Error<E>
142where
143    E: core::fmt::Debug,
144{
145    /// The underlying block device threw an error.
146    DeviceError(E),
147    /// The filesystem is badly formatted (or this code is buggy).
148    FormatError(&'static str),
149    /// The given `VolumeIdx` was bad,
150    NoSuchVolume,
151    /// The given filename was bad
152    FilenameError(FilenameError),
153    /// Out of memory opening volumes
154    TooManyOpenVolumes,
155    /// Out of memory opening directories
156    TooManyOpenDirs,
157    /// Out of memory opening files
158    TooManyOpenFiles,
159    /// Bad handle given
160    BadHandle,
161    /// That file or directory doesn't exist
162    NotFound,
163    /// You can't open a file twice or delete an open file
164    FileAlreadyOpen,
165    /// You can't open a directory twice
166    DirAlreadyOpen,
167    /// You can't open a directory as a file
168    OpenedDirAsFile,
169    /// You can't open a file as a directory
170    OpenedFileAsDir,
171    /// You can't delete a directory as a file
172    DeleteDirAsFile,
173    /// You can't close a volume with open files or directories
174    VolumeStillInUse,
175    /// You can't open a volume twice
176    VolumeAlreadyOpen,
177    /// We can't do that yet
178    Unsupported,
179    /// Tried to read beyond end of file
180    EndOfFile,
181    /// Found a bad cluster
182    BadCluster,
183    /// Error while converting types
184    ConversionError,
185    /// The device does not have enough space for the operation
186    NotEnoughSpace,
187    /// Cluster was not properly allocated by the library
188    AllocationError,
189    /// Jumped to free space during FAT traversing
190    UnterminatedFatChain,
191    /// Tried to open Read-Only file with write mode
192    ReadOnly,
193    /// Tried to create an existing file
194    FileAlreadyExists,
195    /// Bad block size - only 512 byte blocks supported
196    BadBlockSize(u16),
197    /// Bad offset given when seeking
198    InvalidOffset,
199    /// Disk is full
200    DiskFull,
201    /// A directory with that name already exists
202    DirAlreadyExists,
203}
204
205impl<E> From<E> for Error<E>
206where
207    E: core::fmt::Debug,
208{
209    fn from(value: E) -> Error<E> {
210        Error::DeviceError(value)
211    }
212}
213
214/// A partition with a filesystem within it.
215#[cfg_attr(feature = "defmt-log", derive(defmt::Format))]
216#[derive(Debug, Copy, Clone, PartialEq, Eq)]
217pub struct RawVolume(SearchId);
218
219impl RawVolume {
220    /// Convert a raw volume into a droppable [`Volume`]
221    pub fn to_volume<
222        D,
223        T,
224        const MAX_DIRS: usize,
225        const MAX_FILES: usize,
226        const MAX_VOLUMES: usize,
227    >(
228        self,
229        volume_mgr: &mut VolumeManager<D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>,
230    ) -> Volume<D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>
231    where
232        D: crate::BlockDevice,
233        T: crate::TimeSource,
234    {
235        Volume::new(self, volume_mgr)
236    }
237}
238
239/// An open volume on disk, which closes on drop.
240///
241/// In contrast to a `RawVolume`, a `Volume` holds a mutable reference to its
242/// parent `VolumeManager`, which restricts which operations you can perform.
243///
244/// If you drop a value of this type, it closes the volume automatically, but
245/// any error that may occur will be ignored. To handle potential errors, use
246/// the [`Volume::close`] method.
247pub struct Volume<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize>
248where
249    D: crate::BlockDevice,
250    T: crate::TimeSource,
251{
252    raw_volume: RawVolume,
253    volume_mgr: &'a mut VolumeManager<D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>,
254}
255
256impl<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize>
257    Volume<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>
258where
259    D: crate::BlockDevice,
260    T: crate::TimeSource,
261{
262    /// Create a new `Volume` from a `RawVolume`
263    pub fn new(
264        raw_volume: RawVolume,
265        volume_mgr: &'a mut VolumeManager<D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>,
266    ) -> Volume<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES> {
267        Volume {
268            raw_volume,
269            volume_mgr,
270        }
271    }
272
273    /// Open the volume's root directory.
274    ///
275    /// You can then read the directory entries with `iterate_dir`, or you can
276    /// use `open_file_in_dir`.
277    pub fn open_root_dir(
278        &mut self,
279    ) -> Result<crate::Directory<D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>, Error<D::Error>> {
280        let d = self.volume_mgr.open_root_dir(self.raw_volume)?;
281        Ok(d.to_directory(self.volume_mgr))
282    }
283
284    /// Convert back to a raw volume
285    pub fn to_raw_volume(self) -> RawVolume {
286        let v = self.raw_volume;
287        core::mem::forget(self);
288        v
289    }
290
291    /// Consume the `Volume` handle and close it. The behavior of this is similar
292    /// to using [`core::mem::drop`] or letting the `Volume` go out of scope,
293    /// except this lets the user handle any errors that may occur in the process,
294    /// whereas when using drop, any errors will be discarded silently.
295    pub fn close(self) -> Result<(), Error<D::Error>> {
296        let result = self.volume_mgr.close_volume(self.raw_volume);
297        core::mem::forget(self);
298        result
299    }
300}
301
302impl<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize> Drop
303    for Volume<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>
304where
305    D: crate::BlockDevice,
306    T: crate::TimeSource,
307{
308    fn drop(&mut self) {
309        _ = self.volume_mgr.close_volume(self.raw_volume)
310    }
311}
312
313impl<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize>
314    core::fmt::Debug for Volume<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>
315where
316    D: crate::BlockDevice,
317    T: crate::TimeSource,
318{
319    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
320        write!(f, "Volume({})", self.raw_volume.0 .0)
321    }
322}
323
324#[cfg(feature = "defmt-log")]
325impl<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize>
326    defmt::Format for Volume<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES>
327where
328    D: crate::BlockDevice,
329    T: crate::TimeSource,
330{
331    fn format(&self, fmt: defmt::Formatter) {
332        defmt::write!(fmt, "Volume({})", self.raw_volume.0 .0)
333    }
334}
335
336/// Internal information about a Volume
337#[cfg_attr(feature = "defmt-log", derive(defmt::Format))]
338#[derive(Debug, PartialEq, Eq)]
339pub(crate) struct VolumeInfo {
340    /// Search ID for this volume.
341    volume_id: RawVolume,
342    /// TODO: some kind of index
343    idx: VolumeIdx,
344    /// What kind of volume this is
345    volume_type: VolumeType,
346}
347
348/// This enum holds the data for the various different types of filesystems we
349/// support.
350#[cfg_attr(feature = "defmt-log", derive(defmt::Format))]
351#[derive(Debug, PartialEq, Eq)]
352pub enum VolumeType {
353    /// FAT16/FAT32 formatted volumes.
354    Fat(FatVolume),
355}
356
357/// A number which identifies a volume (or partition) on a disk.
358///
359/// `VolumeIdx(0)` is the first primary partition on an MBR partitioned disk.
360#[cfg_attr(feature = "defmt-log", derive(defmt::Format))]
361#[derive(Debug, PartialEq, Eq, Copy, Clone)]
362pub struct VolumeIdx(pub usize);
363
364/// Marker for a FAT32 partition. Sometimes also use for FAT16 formatted
365/// partitions.
366const PARTITION_ID_FAT32_LBA: u8 = 0x0C;
367/// Marker for a FAT16 partition with LBA. Seen on a Raspberry Pi SD card.
368const PARTITION_ID_FAT16_LBA: u8 = 0x0E;
369/// Marker for a FAT16 partition. Seen on a card formatted with the official
370/// SD-Card formatter.
371const PARTITION_ID_FAT16: u8 = 0x06;
372/// Marker for a FAT32 partition. What Macosx disk utility (and also SD-Card formatter?)
373/// use.
374const PARTITION_ID_FAT32_CHS_LBA: u8 = 0x0B;
375
376// ****************************************************************************
377//
378// Unit Tests
379//
380// ****************************************************************************
381
382// None
383
384// ****************************************************************************
385//
386// End Of File
387//
388// ****************************************************************************