Skip to main content

esp_idf_svc/fs/
fatfs.rs

1use core::borrow::BorrowMut;
2
3use alloc::boxed::Box;
4
5use config::{FatFsType, FormatConfiguration};
6
7use ::log::warn;
8
9use crate::hal::sd::SdCardDriver;
10use crate::sys::*;
11
12extern crate alloc;
13
14pub mod config {
15    /// Type of FAT filesystem to create when formatting the partition.
16    #[derive(Copy, Clone, Eq, PartialEq)]
17    pub enum FatFsType {
18        /// Automatically choose the best FAT type depending on volume and cluster size.
19        Auto,
20        /// FAT12 filesystem.
21        Fat,
22        /// FAT32 filesystem.
23        Fat32,
24        /// ExFAT filesystem.
25        ExFat,
26    }
27
28    /// Configuration for formatting a FAT partition.
29    pub struct FormatConfiguration {
30        /// Type of FAT filesystem to create.
31        pub fs_type: FatFsType,
32        /// Whether to create a backup copy of the FAT table.
33        #[cfg(not(esp_idf_version_major = "4"))]
34        pub fat_backup_copy: bool,
35        /// Volume data alignment in number of sectors.
36        #[cfg(not(esp_idf_version_major = "4"))]
37        pub volume_data_alignment: core::num::NonZeroU16,
38        /// Number of root directory entries.
39        #[cfg(not(esp_idf_version_major = "4"))]
40        pub root_dir_entries: core::num::NonZeroU16,
41        /// Cluster size in bytes.
42        pub cluster_size: u32,
43    }
44
45    impl FormatConfiguration {
46        /// Create a new default configuration
47        pub const fn new() -> Self {
48            Self {
49                fs_type: FatFsType::Auto,
50                #[cfg(not(esp_idf_version_major = "4"))]
51                fat_backup_copy: false,
52                #[cfg(not(esp_idf_version_major = "4"))]
53                volume_data_alignment: unsafe { core::num::NonZeroU16::new_unchecked(1) },
54                #[cfg(not(esp_idf_version_major = "4"))]
55                root_dir_entries: unsafe { core::num::NonZeroU16::new_unchecked(512) },
56                cluster_size: 4096,
57            }
58        }
59    }
60
61    impl Default for FormatConfiguration {
62        fn default() -> Self {
63            Self::new()
64        }
65    }
66}
67
68enum Partition<T> {
69    SdCard(T),
70    RawPartition,
71}
72
73/// Represents a mounted FAT filesystem instance that can be used to interact with the filesystem.
74/// The filesystem is automatically unmounted when the instance is dropped.
75///
76/// The interaction happens via the native, unsafe FATFS library API (i.e. `crate::sys::f_open`, `crate::sys::f_read` and so on).
77/// An alternative way to mount the filesystem is to use the VFS API, which is more high-level and abstracts the underlying filesystem.
78pub struct MountedFatfs<'a, T> {
79    fs: &'a mut Fatfs<T>,
80    fatfs: Box<FATFS>,
81}
82
83impl<T> MountedFatfs<'_, T> {
84    /// Get the underlying FATFS instance.
85    pub fn fatfs(&self) -> &FATFS {
86        &self.fatfs
87    }
88
89    // TODO: Add safe methods to interact with the filesystem
90}
91
92impl<T> Drop for MountedFatfs<'_, T> {
93    fn drop(&mut self) {
94        let drive_path = self.fs.drive_path();
95
96        let res = unsafe { f_mount(core::ptr::null_mut(), drive_path.as_ptr(), 0) };
97
98        if res != FRESULT_FR_OK {
99            panic!("Unmount failed: {res}");
100        }
101    }
102}
103
104/// Represents a FAT filesystem.
105pub struct Fatfs<T> {
106    drive: u8,
107    _partition: Partition<T>,
108}
109
110impl<T> Fatfs<T> {
111    /// Create a new FAT filesystem instance for a given SD card driver.
112    ///
113    /// # Arguments
114    /// - Drive number to assign to the filesystem.
115    /// - SD card driver instance.
116    pub fn new_sdcard<H>(drive: u8, mut sd_card_driver: T) -> Result<Self, EspError>
117    where
118        T: BorrowMut<SdCardDriver<H>>,
119    {
120        unsafe {
121            ff_diskio_register_sdmmc(
122                drive,
123                sd_card_driver.borrow_mut().card() as *const _ as *mut _,
124            );
125        }
126
127        Ok(Self {
128            drive,
129            _partition: Partition::SdCard(sd_card_driver),
130        })
131    }
132
133    /// Get the drive number of the filesystem.
134    pub fn drive(&self) -> u8 {
135        self.drive
136    }
137
138    /// Format the partition with the given configuration.
139    ///
140    /// # Arguments
141    /// - Formatting configuration.
142    /// - Buffer to use when formatting.
143    pub fn format(
144        &mut self,
145        configuration: &FormatConfiguration,
146        buf: &mut [u8],
147    ) -> Result<(), EspError> {
148        let drive_path = self.drive_path();
149
150        #[cfg(not(esp_idf_version_major = "4"))]
151        {
152            let opt = MKFS_PARM {
153                fmt: match configuration.fs_type {
154                    FatFsType::Auto => FM_ANY,
155                    FatFsType::Fat => FM_FAT,
156                    FatFsType::Fat32 => FM_FAT32,
157                    FatFsType::ExFat => FM_EXFAT,
158                } as _,
159                au_size: configuration.cluster_size,
160                n_fat: if configuration.fat_backup_copy { 2 } else { 1 },
161                n_root: configuration.root_dir_entries.get() as _,
162                align: configuration.volume_data_alignment.get() as _,
163            };
164
165            unsafe {
166                f_mkfs(
167                    drive_path.as_ptr(),
168                    &opt,
169                    buf.as_mut_ptr() as *mut _,
170                    buf.len() as _,
171                );
172            }
173        }
174
175        #[cfg(esp_idf_version_major = "4")]
176        {
177            unsafe {
178                f_mkfs(
179                    drive_path.as_ptr(),
180                    match configuration.fs_type {
181                        FatFsType::Auto => FM_ANY,
182                        FatFsType::Fat => FM_FAT,
183                        FatFsType::Fat32 => FM_FAT32,
184                        FatFsType::ExFat => FM_EXFAT,
185                    } as _,
186                    configuration.cluster_size,
187                    buf.as_mut_ptr() as *mut _,
188                    buf.len() as _,
189                );
190            }
191        }
192
193        Ok(())
194    }
195
196    /// Mount the filesystem and return a handle to it.
197    pub fn mount(&mut self) -> Result<MountedFatfs<'_, T>, EspError> {
198        let mut fatfs: Box<FATFS> = Box::default(); // TODO: Large stack size
199
200        let drive_path = self.drive_path();
201
202        let res = unsafe { f_mount(&mut *fatfs, drive_path.as_ptr(), 0) };
203
204        if res != FRESULT_FR_OK {
205            warn!("Mount failed: {res}");
206            Err(EspError::from_infallible::<ESP_FAIL>())?
207        }
208
209        Ok(MountedFatfs { fs: self, fatfs })
210    }
211
212    pub(crate) fn drive_path_from(drive: u8) -> [core::ffi::c_char; 2] {
213        [drive as _, 0]
214    }
215
216    pub(crate) fn drive_path(&self) -> [core::ffi::c_char; 2] {
217        Self::drive_path_from(self.drive)
218    }
219}
220
221impl Fatfs<()> {
222    /// Create a new - readonly - FAT filesystem instance for a given raw partition in the internal flash.
223    /// This API is unsafe because currently `esp-idf-hal` does not have a safe way to
224    /// represent a flash partition - neither a raw one, nor a wear-leveling one.
225    ///
226    /// # Arguments
227    /// - Drive number to assign to the filesystem.
228    /// - Raw partition pointer.
229    ///
230    /// # Safety
231    ///
232    /// While the filesystem object is alive, the partition should not be modified elsewhere
233    pub unsafe fn new_raw_part(
234        drive: u8,
235        partition: *const esp_partition_t,
236    ) -> Result<Self, EspError> {
237        unsafe {
238            ff_diskio_register_raw_partition(drive, partition);
239        }
240
241        Ok(Self {
242            drive,
243            _partition: Partition::RawPartition,
244        })
245    }
246
247    /// Create a new FAT filesystem instance for a given raw partition in the internal flash.
248    /// This API is unsafe because currently `esp-idf-hal` does not have a safe way to
249    /// represent a flash partition - neither a raw one, nor a wear-leveling one.
250    ///
251    /// # Arguments
252    /// - Drive number to assign to the filesystem.
253    /// - A handle to a wear-leveling partition.
254    ///
255    /// # Safety
256    ///
257    /// While the filesystem object is alive, the partition should not be modified elsewhere
258    pub unsafe fn new_wl_part(drive: u8, partition: wl_handle_t) -> Result<Self, EspError> {
259        unsafe {
260            ff_diskio_register_wl_partition(drive, partition);
261        }
262
263        Ok(Self {
264            drive,
265            _partition: Partition::RawPartition,
266        })
267    }
268}
269
270impl<T> Drop for Fatfs<T> {
271    fn drop(&mut self) {
272        unsafe {
273            ff_diskio_register(self.drive, core::ptr::null_mut());
274        }
275    }
276}