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// ****************************************************************************