From d943cf8609d3db3c0bf43050c7ff7152e2704694 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Thu, 10 Oct 2024 13:09:46 +0000 Subject: [PATCH] Support for LittleFS --- .cargo/config.toml | 4 +- .github/configs/sdkconfig.defaults | 2 + Cargo.toml | 5 +- components_esp32c3.lock | 20 ---- examples/sd_spi_littlefs.rs | 141 +++++++++++++++++++++++++++++ src/fs.rs | 2 + src/fs/littlefs.rs | 120 ++++++++++++++++++++++++ src/io.rs | 115 +++++++++++++++++++++++ 8 files changed, 386 insertions(+), 23 deletions(-) delete mode 100644 components_esp32c3.lock create mode 100755 examples/sd_spi_littlefs.rs create mode 100644 src/fs/littlefs.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 0656ef2bf6a..068afd5b97c 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,6 +1,6 @@ [build] -target = "riscv32imc-esp-espidf" -#target = "xtensa-esp32-espidf" +#target = "riscv32imc-esp-espidf" +target = "xtensa-esp32-espidf" [target.xtensa-esp32-espidf] linker = "ldproxy" diff --git a/.github/configs/sdkconfig.defaults b/.github/configs/sdkconfig.defaults index bb040473505..882ec3e33d6 100644 --- a/.github/configs/sdkconfig.defaults +++ b/.github/configs/sdkconfig.defaults @@ -66,3 +66,5 @@ CONFIG_OPENTHREAD_ENABLED=y # Border Router again, mDNS #CONFIG_MDNS_MULTIPLE_INSTANCE=y + +CONFIG_LITTLEFS_SDMMC_SUPPORT=y diff --git a/Cargo.toml b/Cargo.toml index 6873bb2650b..592a7528ceb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,12 +18,15 @@ rust-version = "1.77" [lib] harness = false +[[package.metadata.esp-idf-sys.extra_components]] +remote_component = { name = "joltwallet/littlefs", version = "1.14" } + [patch.crates-io] esp-idf-hal = { git = "https://github.com/esp-rs/esp-idf-hal" } esp-idf-sys = { git = "https://github.com/esp-rs/esp-idf-sys" } [features] -default = ["std", "binstart"] +default = ["std", "binstart", "experimental"] std = ["alloc", "log/std", "esp-idf-hal/std", "embedded-svc/std", "futures-io"] alloc = ["esp-idf-hal/alloc", "embedded-svc/alloc", "uncased/alloc"] diff --git a/components_esp32c3.lock b/components_esp32c3.lock deleted file mode 100644 index 418d9c5b21f..00000000000 --- a/components_esp32c3.lock +++ /dev/null @@ -1,20 +0,0 @@ -dependencies: - espressif/mdns: - component_hash: d8ae7aa491ff97aea1d00d514a1de42cdbaaf1bce10df183968fa5594b904de7 - dependencies: - - name: idf - require: private - version: '>=5.0' - source: - registry_url: https://components.espressif.com/ - type: service - version: 1.2.5 - idf: - source: - type: idf - version: 5.3.1 -direct_dependencies: -- espressif/mdns -manifest_hash: c3b94c87f4e7f6400185dd295864ac2a11d6dea38982a89ee366397f983b68f6 -target: esp32c3 -version: 2.0.0 diff --git a/examples/sd_spi_littlefs.rs b/examples/sd_spi_littlefs.rs new file mode 100755 index 00000000000..8c933d79c10 --- /dev/null +++ b/examples/sd_spi_littlefs.rs @@ -0,0 +1,141 @@ +//! An example of mounting, formatting, and using an SD card with Littlefs +//! +//! To use, put this in your `Cargo.toml`: +//! ``` +//! [[package.metadata.esp-idf-sys.extra_components]] +//! remote_component = { name = "joltwallet/littlefs", version = "1.14" } +//! ``` +//! +//! To use with an SD card, put this in your `sdkconfig.defaults`: +//! ``` +//! CONFIG_LITTLEFS_SDMMC_SUPPORT=y +//! ``` + +#![allow(unexpected_cfgs)] + +fn main() -> anyhow::Result<()> { + esp_idf_svc::sys::link_patches(); + esp_idf_svc::log::EspLogger::initialize_default(); + + #[cfg(not(esp32))] + { + log::error!("This example is configured for esp32, please adjust pins to your module."); + } + + #[cfg(esp32)] + { + #[cfg(i_have_done_all_configs_from_the_top_comment)] + // Remove this `cfg` when you have done all of the above for the example to compile + example_main()?; + + // Remove this whole code block when you have done all of the above for the example to compile + #[cfg(not(i_have_done_all_configs_from_the_top_comment))] + { + log::error!("Please follow the instructions in the source code."); + } + } + + Ok(()) +} + +#[cfg(i_have_done_all_configs_from_the_top_comment)] // Remove this `cfg` when you have done all of the above for the example to compile +#[cfg(esp32)] +fn example_main() -> anyhow::Result<()> { + use std::fs::{read_dir, File}; + use std::io::{Read, Seek, Write}; + + use esp_idf_svc::fs::littlefs::Littlefs; + use esp_idf_svc::hal::gpio::AnyIOPin; + use esp_idf_svc::hal::prelude::*; + use esp_idf_svc::hal::sd::{spi::SdSpiHostDriver, SdCardConfiguration, SdCardDriver}; + use esp_idf_svc::hal::spi::{config::DriverConfig, Dma, SpiDriver}; + use esp_idf_svc::io::vfs::MountedLittlefs; + use esp_idf_svc::log::EspLogger; + + use log::info; + + esp_idf_svc::sys::link_patches(); + + EspLogger::initialize_default(); + + let peripherals = Peripherals::take()?; + let pins = peripherals.pins; + + let spi_driver = SpiDriver::new( + peripherals.spi3, + pins.gpio18, + pins.gpio23, + Some(pins.gpio19), + &DriverConfig::default().dma(Dma::Auto(4096)), + )?; + + let sd_card_driver = SdCardDriver::new_spi( + SdSpiHostDriver::new( + spi_driver, + Some(pins.gpio5), + AnyIOPin::none(), + AnyIOPin::none(), + AnyIOPin::none(), + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + )))] // For ESP-IDF v5.2 and later + None, + )?, + &SdCardConfiguration::new(), + )?; + + let littlefs = Littlefs::new_sdcard(sd_card_driver)?; + + // Format it first, as chances are, the user won't have easy access to the Littlefs filesystem from her PC + // Commented out for safety + // log::info!("Formatting the SD card"); + // littlefs.format()?; + // log::info!("SD card formatted"); + + // Keep it around or else it will be dropped and unmounted + let mut mounted_littlefs = MountedLittlefs::mount(littlefs, "/sdcard")?; + + info!("Filesystem usage: {:?}", mounted_littlefs.info()?); + + let content = b"Hello, world!"; + + { + let mut file = File::create("/sdcard/test.txt")?; + + info!("File {file:?} created"); + + file.write_all(content).expect("Write failed"); + + info!("File {file:?} written with {content:?}"); + + file.seek(std::io::SeekFrom::Start(0)).expect("Seek failed"); + + info!("File {file:?} seeked"); + } + + { + let mut file = File::open("/sdcard/test.txt")?; + + info!("File {file:?} opened"); + + let mut file_content = String::new(); + + file.read_to_string(&mut file_content).expect("Read failed"); + + info!("File {file:?} read: {file_content}"); + + assert_eq!(file_content.as_bytes(), content); + } + + { + let directory = read_dir("/sdcard")?; + + for entry in directory { + log::info!("Entry: {:?}", entry?.file_name()); + } + } + + Ok(()) +} diff --git a/src/fs.rs b/src/fs.rs index 7a1d90075c5..2f3ba9d03f7 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -1,4 +1,6 @@ #[cfg(all(feature = "alloc", esp_idf_comp_fatfs_enabled))] pub mod fatfs; +#[cfg(all(feature = "alloc", esp_idf_comp_joltwallet__littlefs_enabled))] +pub mod littlefs; #[cfg(all(feature = "alloc", esp_idf_comp_spiffs_enabled))] pub mod spiffs; diff --git a/src/fs/littlefs.rs b/src/fs/littlefs.rs new file mode 100644 index 00000000000..8cf67c34ec9 --- /dev/null +++ b/src/fs/littlefs.rs @@ -0,0 +1,120 @@ +//! Littlefs filesystem. +//! +//! To use, put this in your `Cargo.toml`: +//! ``` +//! [[package.metadata.esp-idf-sys.extra_components]] +//! remote_component = { name = "joltwallet/littlefs", version = "1.14" } +//! ``` +//! +//! To use with an SD card, put this in your `sdkconfig.defaults`: +//! ``` +//! CONFIG_LITTLEFS_SDMMC_SUPPORT=y +//! ``` + +use alloc::ffi::CString; + +use crate::{private::cstr::to_cstring_arg, sys::*}; + +extern crate alloc; + +#[allow(dead_code)] +enum Partition { + SdCard(T), + PartitionLabel(CString), + RawPartition(*mut esp_partition_t), +} + +#[derive(Clone)] +pub(crate) enum PartitionRawData { + #[cfg(esp_idf_littlefs_sdmmc_support)] + SdCard(*mut sdmmc_card_t), + PartitionLabel(*const i8), + RawPartition(*mut esp_partition_t), +} + +/// Information about the filesystem. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct LittleFsInfo { + pub total_bytes: usize, + pub used_bytes: usize, +} + +/// Represents a Littlefs filesystem. +pub struct Littlefs { + _partition: Partition, + partition_raw_data: PartitionRawData, +} + +impl Littlefs { + /// Create a new Littlefs filesystem instance for a given SD card driver. + /// + /// # Arguments + /// - SD card driver. + #[cfg(esp_idf_littlefs_sdmmc_support)] + pub fn new_sdcard(sd_card_driver: T) -> Result + where + T: core::borrow::BorrowMut>, + { + let card_raw_ptr = sd_card_driver.borrow().card() as *const _ as *mut _; + + Ok(Self { + _partition: Partition::SdCard(sd_card_driver), + partition_raw_data: PartitionRawData::SdCard(card_raw_ptr), + }) + } + + /// Create a new Littlefs filesystem instance for a given partition label. + /// + /// # Safety + /// - This method should be used with a valid partition label. + /// - While the partition is in use by the filesystem, it should not be modified or used elsewhere. + /// + /// # Arguments + /// - `partition_label`: Partition label. + pub unsafe fn new_partition(partition_label: &str) -> Result { + let partition_label = to_cstring_arg(partition_label)?; + let partition_raw_data = PartitionRawData::PartitionLabel(partition_label.as_ptr()); + + Ok(Self { + _partition: Partition::PartitionLabel(partition_label), + partition_raw_data, + }) + } + + /// Create a new Littlefs filesystem instance for a given partition. + /// + /// # Safety + /// - This method should be used with a valid partition. + /// - While the partition is in use by the filesystem, it should not be modified or used elsewhere. + /// + /// # Arguments + /// - `partition`: the raw ESP-IDF partition. + pub unsafe fn new_raw_partition(partition: *mut esp_partition_t) -> Result { + Ok(Self { + _partition: Partition::RawPartition(partition), + partition_raw_data: PartitionRawData::RawPartition(partition), + }) + } + + pub(crate) fn partition_raw_data(&self) -> PartitionRawData { + self.partition_raw_data.clone() + } + + /// Format the Littlefs partition. + pub fn format(&mut self) -> Result<(), EspError> { + match self.partition_raw_data { + #[cfg(esp_idf_littlefs_sdmmc_support)] + PartitionRawData::SdCard(sd_card_ptr) => { + esp!(unsafe { esp_littlefs_format_sdmmc(sd_card_ptr) })?; + } + PartitionRawData::PartitionLabel(label) => { + esp!(unsafe { esp_littlefs_format(label) })?; + } + PartitionRawData::RawPartition(partition) => { + esp!(unsafe { esp_littlefs_format_partition(partition) })?; + } + } + + Ok(()) + } +} diff --git a/src/io.rs b/src/io.rs index 45cadc767bf..1a0b3a4613a 100644 --- a/src/io.rs +++ b/src/io.rs @@ -143,4 +143,119 @@ pub mod vfs { sys::esp!(unsafe { sys::esp_vfs_fat_unregister_path(self.path.as_ptr()) }).unwrap(); } } + + /// Represents a mounted Littlefs filesystem. + #[cfg(all(feature = "alloc", esp_idf_comp_joltwallet__littlefs_enabled))] + pub struct MountedLittlefs { + _littlefs: T, + partition_raw_data: crate::fs::littlefs::PartitionRawData, + } + + #[cfg(all(feature = "alloc", esp_idf_comp_joltwallet__littlefs_enabled))] + impl MountedLittlefs { + /// Mount a Littlefs filesystem. + /// + /// # Arguments + /// - `littlefs`: The Littlefs filesystem instance to mount. + /// - `path`: The path to mount the filesystem at. + pub fn mount(mut littlefs: T, path: &str) -> Result + where + T: core::borrow::BorrowMut>, + { + use crate::fs::littlefs::PartitionRawData; + use crate::private::cstr::to_cstring_arg; + + let path = to_cstring_arg(path)?; + + let partition_raw_data = littlefs.borrow_mut().partition_raw_data(); + + let conf = sys::esp_vfs_littlefs_conf_t { + base_path: path.as_ptr(), + partition_label: if let PartitionRawData::PartitionLabel(label) = partition_raw_data + { + label + } else { + core::ptr::null() + }, + partition: if let PartitionRawData::RawPartition(partition) = partition_raw_data { + partition + } else { + core::ptr::null_mut() + }, + #[cfg(esp_idf_littlefs_sdmmc_support)] + sdcard: if let PartitionRawData::SdCard(sdcard) = partition_raw_data { + sdcard + } else { + core::ptr::null_mut() + }, + ..Default::default() + }; + + sys::esp!(unsafe { sys::esp_vfs_littlefs_register(&conf) })?; + + Ok(Self { + _littlefs: littlefs, + partition_raw_data, + }) + } + + pub fn info(&mut self) -> Result { + use crate::fs::littlefs::PartitionRawData; + + let mut info = crate::fs::littlefs::LittleFsInfo { + total_bytes: 0, + used_bytes: 0, + }; + + match self.partition_raw_data { + #[cfg(esp_idf_littlefs_sdmmc_support)] + PartitionRawData::SdCard(sd_card_ptr) => { + sys::esp!(unsafe { + sys::esp_littlefs_sdmmc_info( + sd_card_ptr, + &mut info.total_bytes, + &mut info.used_bytes, + ) + })?; + } + PartitionRawData::PartitionLabel(label) => { + sys::esp!(unsafe { + sys::esp_littlefs_info(label, &mut info.total_bytes, &mut info.used_bytes) + })?; + } + PartitionRawData::RawPartition(partition) => { + sys::esp!(unsafe { + sys::esp_littlefs_partition_info( + partition, + &mut info.total_bytes, + &mut info.used_bytes, + ) + })?; + } + } + + Ok(info) + } + } + + #[cfg(all(feature = "alloc", esp_idf_comp_joltwallet__littlefs_enabled))] + impl Drop for MountedLittlefs { + fn drop(&mut self) { + use crate::fs::littlefs::PartitionRawData; + + match self.partition_raw_data { + PartitionRawData::PartitionLabel(label) => { + sys::esp!(unsafe { sys::esp_vfs_littlefs_unregister(label) }).unwrap(); + } + PartitionRawData::RawPartition(partition) => { + sys::esp!(unsafe { sys::esp_vfs_littlefs_unregister_partition(partition) }) + .unwrap(); + } + #[cfg(esp_idf_littlefs_sdmmc_support)] + PartitionRawData::SdCard(sdcard) => { + sys::esp!(unsafe { sys::esp_vfs_littlefs_unregister_sdmmc(sdcard) }).unwrap(); + } + } + } + } }