From 60e4b882efce5d0d29c7283c286eb8cb6b7b1bf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Quentin?= Date: Mon, 27 May 2024 17:28:05 +0200 Subject: [PATCH] Add esp-storage to esp-hal repo (#1627) * Add esp-storage to esp-hal repo * Include needed feature to lint esp-storage * Don't lint esp-wifi for now * Remove redundant copies of license texts * Try `git-fetch-with-cli` * Fix esp-pacs URL * git-fetch-with-cli, again * desperately trying --- Cargo.toml | 1 + esp-hal/Cargo.toml | 14 +- esp-storage/Cargo.toml | 51 ++++ esp-storage/README.md | 34 +++ esp-storage/build.rs | 21 ++ esp-storage/src/common.rs | 161 ++++++++++++ esp-storage/src/esp32.rs | 287 +++++++++++++++++++++ esp-storage/src/esp32c2.rs | 38 +++ esp-storage/src/esp32c3.rs | 38 +++ esp-storage/src/esp32c6.rs | 38 +++ esp-storage/src/esp32h2.rs | 38 +++ esp-storage/src/esp32s2.rs | 38 +++ esp-storage/src/esp32s3.rs | 46 ++++ esp-storage/src/lib.rs | 60 +++++ esp-storage/src/ll.rs | 56 +++++ esp-storage/src/nor_flash.rs | 356 +++++++++++++++++++++++++++ esp-storage/src/storage.rs | 74 ++++++ esp-storage/src/stub.rs | 107 ++++++++ examples/Cargo.toml | 16 +- examples/src/bin/flash_read_write.rs | 50 ++++ hil-test/.cargo/config.toml | 3 + xtask/src/lib.rs | 1 + xtask/src/main.rs | 21 ++ 23 files changed, 1535 insertions(+), 14 deletions(-) create mode 100644 esp-storage/Cargo.toml create mode 100644 esp-storage/README.md create mode 100644 esp-storage/build.rs create mode 100644 esp-storage/src/common.rs create mode 100644 esp-storage/src/esp32.rs create mode 100644 esp-storage/src/esp32c2.rs create mode 100644 esp-storage/src/esp32c3.rs create mode 100644 esp-storage/src/esp32c6.rs create mode 100644 esp-storage/src/esp32h2.rs create mode 100644 esp-storage/src/esp32s2.rs create mode 100644 esp-storage/src/esp32s3.rs create mode 100644 esp-storage/src/lib.rs create mode 100644 esp-storage/src/ll.rs create mode 100644 esp-storage/src/nor_flash.rs create mode 100644 esp-storage/src/storage.rs create mode 100644 esp-storage/src/stub.rs create mode 100644 examples/src/bin/flash_read_write.rs diff --git a/Cargo.toml b/Cargo.toml index 4f385c4281f..557a5ec8aa5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ exclude = [ "esp-println", "esp-riscv-rt", "esp-wifi", + "esp-storage", "examples", "extras/bench-server", "extras/esp-wifishark", diff --git a/esp-hal/Cargo.toml b/esp-hal/Cargo.toml index 930bda96a5b..b6c2746ddd8 100644 --- a/esp-hal/Cargo.toml +++ b/esp-hal/Cargo.toml @@ -55,13 +55,13 @@ xtensa-lx = { version = "0.9.0", optional = true } # IMPORTANT: # Each supported device MUST have its PAC included below along with a # corresponding feature. -esp32 = { git = "https://github.com/esp-rs/esp-pacs/", rev = "9a36a93", features = ["critical-section", "rt"], optional = true } -esp32c2 = { git = "https://github.com/esp-rs/esp-pacs/", rev = "9a36a93", features = ["critical-section", "rt"], optional = true } -esp32c3 = { git = "https://github.com/esp-rs/esp-pacs/", rev = "9a36a93", features = ["critical-section", "rt"], optional = true } -esp32c6 = { git = "https://github.com/esp-rs/esp-pacs/", rev = "9a36a93", features = ["critical-section", "rt"], optional = true } -esp32h2 = { git = "https://github.com/esp-rs/esp-pacs/", rev = "9a36a93", features = ["critical-section", "rt"], optional = true } -esp32s2 = { git = "https://github.com/esp-rs/esp-pacs/", rev = "9a36a93", features = ["critical-section", "rt"], optional = true } -esp32s3 = { git = "https://github.com/esp-rs/esp-pacs/", rev = "9a36a93", features = ["critical-section", "rt"], optional = true } +esp32 = { git = "https://github.com/esp-rs/esp-pacs", rev = "9a36a9375a1dfd1b51a559ee76f144935a7e8ed8", features = ["critical-section", "rt"], optional = true } +esp32c2 = { git = "https://github.com/esp-rs/esp-pacs", rev = "9a36a9375a1dfd1b51a559ee76f144935a7e8ed8", features = ["critical-section", "rt"], optional = true } +esp32c3 = { git = "https://github.com/esp-rs/esp-pacs", rev = "9a36a9375a1dfd1b51a559ee76f144935a7e8ed8", features = ["critical-section", "rt"], optional = true } +esp32c6 = { git = "https://github.com/esp-rs/esp-pacs", rev = "9a36a9375a1dfd1b51a559ee76f144935a7e8ed8", features = ["critical-section", "rt"], optional = true } +esp32h2 = { git = "https://github.com/esp-rs/esp-pacs", rev = "9a36a9375a1dfd1b51a559ee76f144935a7e8ed8", features = ["critical-section", "rt"], optional = true } +esp32s2 = { git = "https://github.com/esp-rs/esp-pacs", rev = "9a36a9375a1dfd1b51a559ee76f144935a7e8ed8", features = ["critical-section", "rt"], optional = true } +esp32s3 = { git = "https://github.com/esp-rs/esp-pacs", rev = "9a36a9375a1dfd1b51a559ee76f144935a7e8ed8", features = ["critical-section", "rt"], optional = true } [target.'cfg(target_arch = "riscv32")'.dependencies] esp-riscv-rt = { version = "0.8.0", path = "../esp-riscv-rt" } diff --git a/esp-storage/Cargo.toml b/esp-storage/Cargo.toml new file mode 100644 index 00000000000..7d79c41f4dd --- /dev/null +++ b/esp-storage/Cargo.toml @@ -0,0 +1,51 @@ +[package] +name = "esp-storage" +version = "0.3.0" +edition = "2021" +authors = [ + "The ESP-RS team", + "Björn Quentin ", +] +description = "Implementation of embedded-storage traits to access unencrypted ESP32 flash" +repository = "https://github.com/esp-rs/esp-storage" +license = "MIT OR Apache-2.0" + +keywords = [ + "embedded-storage", + "esp", + "no-std", +] +categories = [ + "embedded", + "hardware-support", + "no-std", +] + +[dependencies] +embedded-storage = "0.3.0" +critical-section = { version = "1.1.1", optional = true } + +[build-dependencies] +esp-build = { version = "0.1.0", path = "../esp-build" } + +[features] +default = ["critical-section", "storage"] +critical-section = ["dep:critical-section"] +# ReadStorage/Storage traits +storage = [] +# ReadNorFlash/NorFlash traits +nor-flash = [] +# Bytewise read emulation +bytewise-read = [] +esp32c2 = [] +esp32c3 = [] +esp32c6 = [] +esp32h2 = [] +esp32 = [] +esp32s2 = [] +esp32s3 = [] +# Enable flash emulation to run tests +emulation = [] + +# this feature is reserved for very specific use-cases - usually you don't want to use this! +low-level = [] diff --git a/esp-storage/README.md b/esp-storage/README.md new file mode 100644 index 00000000000..952e49b75c2 --- /dev/null +++ b/esp-storage/README.md @@ -0,0 +1,34 @@ +# esp-storage + +This implements [`embedded-storage`](https://github.com/rust-embedded-community/embedded-storage) traits to access unencrypted ESP32 flash. + +## Current support + +ESP32, ESP32-C2, ESP32-C3, ESP32-C6, ESP32-H2, ESP32-S2 and ESP32-S3 are supported in `esp-storage` + +## Important + +For ESP32 it is necessary to build with [optimization level](https://doc.rust-lang.org/cargo/reference/profiles.html#opt-level) 2 or 3. + +To make it work also for `debug` builds add this to your `Cargo.toml` + +```toml +[profile.dev.package.esp-storage] +opt-level = 3 +``` + +## License + +Licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](../LICENSE-APACHE) or + http://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](../LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the +work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any +additional terms or conditions. diff --git a/esp-storage/build.rs b/esp-storage/build.rs new file mode 100644 index 00000000000..ebb63536296 --- /dev/null +++ b/esp-storage/build.rs @@ -0,0 +1,21 @@ +fn main() -> Result<(), String> { + // Ensure that only a single chip is specified + esp_build::assert_unique_used_features!( + "esp32", "esp32c2", "esp32c3", "esp32c6", "esp32h2", "esp32p4", "esp32s2", "esp32s3" + ); + + if cfg!(feature = "esp32") { + match std::env::var("OPT_LEVEL") { + Ok(level) => { + if level != "2" && level != "3" { + Err(format!("Building esp-storage for ESP32 needs optimization level 2 or 3 - yours is {}. See https://github.com/esp-rs/esp-storage", level)) + } else { + Ok(()) + } + } + Err(_err) => Ok(()), + } + } else { + Ok(()) + } +} diff --git a/esp-storage/src/common.rs b/esp-storage/src/common.rs new file mode 100644 index 00000000000..d6b0f08a9c3 --- /dev/null +++ b/esp-storage/src/common.rs @@ -0,0 +1,161 @@ +use core::ops::{Deref, DerefMut}; + +use crate::chip_specific; + +#[repr(C, align(4))] +pub struct FlashSectorBuffer { + // NOTE: Ensure that no unaligned fields are added above `data` to maintain its required + // alignment + data: [u8; FlashStorage::SECTOR_SIZE as usize], +} + +impl Deref for FlashSectorBuffer { + type Target = [u8; FlashStorage::SECTOR_SIZE as usize]; + + fn deref(&self) -> &Self::Target { + &self.data + } +} + +impl DerefMut for FlashSectorBuffer { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.data + } +} + +#[derive(Debug)] +#[non_exhaustive] +pub enum FlashStorageError { + IoError, + IoTimeout, + CantUnlock, + NotAligned, + OutOfBounds, + Other(i32), +} + +#[inline(always)] +pub fn check_rc(rc: i32) -> Result<(), FlashStorageError> { + match rc { + 0 => Ok(()), + 1 => Err(FlashStorageError::IoError), + 2 => Err(FlashStorageError::IoTimeout), + _ => Err(FlashStorageError::Other(rc)), + } +} + +#[derive(Debug)] +pub struct FlashStorage { + pub(crate) capacity: usize, + unlocked: bool, +} + +impl Default for FlashStorage { + fn default() -> Self { + Self::new() + } +} + +impl FlashStorage { + pub const WORD_SIZE: u32 = 4; + pub const SECTOR_SIZE: u32 = 4096; + + pub fn new() -> FlashStorage { + let mut storage = FlashStorage { + capacity: 0, + unlocked: false, + }; + + #[cfg(not(any(feature = "esp32", feature = "esp32s2")))] + const ADDR: u32 = 0x0000; + #[cfg(any(feature = "esp32", feature = "esp32s2"))] + const ADDR: u32 = 0x1000; + + let mut buffer = [0u8; 8]; + storage.internal_read(ADDR, &mut buffer).ok(); + let mb = match buffer[3] & 0xf0 { + 0x00 => 1, + 0x10 => 2, + 0x20 => 4, + 0x30 => 8, + 0x40 => 16, + _ => 0, + }; + storage.capacity = mb * 1024 * 1024; + + storage + } + + #[cfg(feature = "nor-flash")] + #[inline(always)] + pub(crate) fn check_alignment( + &self, + offset: u32, + length: usize, + ) -> Result<(), FlashStorageError> { + let offset = offset as usize; + if offset % ALIGN as usize != 0 || length % ALIGN as usize != 0 { + return Err(FlashStorageError::NotAligned); + } + Ok(()) + } + + #[inline(always)] + pub(crate) fn check_bounds(&self, offset: u32, length: usize) -> Result<(), FlashStorageError> { + let offset = offset as usize; + if length > self.capacity || offset > self.capacity - length { + return Err(FlashStorageError::OutOfBounds); + } + Ok(()) + } + + #[allow(clippy::all)] + #[inline(never)] + #[link_section = ".rwtext"] + pub(crate) fn internal_read( + &mut self, + offset: u32, + bytes: &mut [u8], + ) -> Result<(), FlashStorageError> { + check_rc(chip_specific::esp_rom_spiflash_read( + offset, + bytes.as_ptr() as *mut u32, + bytes.len() as u32, + )) + } + + #[inline(always)] + fn unlock_once(&mut self) -> Result<(), FlashStorageError> { + if !self.unlocked { + if chip_specific::esp_rom_spiflash_unlock() != 0 { + return Err(FlashStorageError::CantUnlock); + } + self.unlocked = true; + } + Ok(()) + } + + #[inline(never)] + #[link_section = ".rwtext"] + pub(crate) fn internal_erase(&mut self, sector: u32) -> Result<(), FlashStorageError> { + self.unlock_once()?; + + check_rc(chip_specific::esp_rom_spiflash_erase_sector(sector)) + } + + #[inline(never)] + #[link_section = ".rwtext"] + pub(crate) fn internal_write( + &mut self, + offset: u32, + bytes: &[u8], + ) -> Result<(), FlashStorageError> { + self.unlock_once()?; + + check_rc(chip_specific::esp_rom_spiflash_write( + offset, + bytes.as_ptr() as *const u32, + bytes.len() as u32, + )) + } +} diff --git a/esp-storage/src/esp32.rs b/esp-storage/src/esp32.rs new file mode 100644 index 00000000000..352a983ab6b --- /dev/null +++ b/esp-storage/src/esp32.rs @@ -0,0 +1,287 @@ +use crate::maybe_with_critical_section; + +const ESP_ROM_SPIFLASH_READ: u32 = 0x40062ed8; +const ESP_ROM_SPIFLASH_ERASE_SECTOR: u32 = 0x40062ccc; +const SPI_READ_STATUS_HIGH: u32 = 0x40062448; +const SPI_READ_STATUS: u32 = 0x4006226c; +const SPI_WRITE_STATUS: u32 = 0x400622f0; + +const CACHE_FLUSH_ROM: u32 = 0x40009a14; +const CACHE_READ_ENABLE_ROM: u32 = 0x40009a84; + +const SPI_BASE_REG: u32 = 0x3ff42000; // SPI peripheral 1, used for SPI flash +const SPI0_BASE_REG: u32 = 0x3ff43000; // SPI peripheral 0, inner state machine +const SPI_EXT2_REG: u32 = SPI_BASE_REG + 0xF8; +const SPI0_EXT2_REG: u32 = SPI0_BASE_REG + 0xF8; +const SPI_RD_STATUS_REG: u32 = SPI_BASE_REG + 0x10; +#[allow(clippy::identity_op)] +const SPI_CMD_REG: u32 = SPI_BASE_REG + 0x00; +const SPI_CTRL_REG: u32 = SPI_BASE_REG + 0x08; +const SPI_USER_REG: u32 = SPI_BASE_REG + 0x1c; +const SPI_USER1_REG: u32 = SPI_BASE_REG + 0x20; +const SPI_ADDR_REG: u32 = SPI_BASE_REG + 4; +const SPI_W0_REG: u32 = SPI_BASE_REG + 0x80; +const SPI_ST: u32 = 0x7; +const SPI_USR_DUMMY: u32 = 1 << 29; +const ESP_ROM_SPIFLASH_W_SIO_ADDR_BITSLEN: u32 = 23; +const SPI_USR_ADDR_BITLEN_M: u32 = 0x3f << 26; +const SPI_USR_ADDR_BITLEN_S: u32 = 26; +const SPI_FLASH_WREN: u32 = 1 << 30; +const STATUS_WIP_BIT: u32 = 1 << 0; +const STATUS_QIE_BIT: u32 = 1 << 9; // Quad Enable +const SPI_WRSR_2B: u32 = 1 << 22; + +const FLASH_CHIP_ADDR: u32 = 0x3ffae270; +const FLASH_DUMMY_LEN_PLUS_ADDR: u32 = 0x3ffae290; + +#[inline(always)] +#[link_section = ".rwtext"] +pub(crate) fn cache_flush_rom(cpu_num: u32) { + unsafe { + let cache_flush_rom: unsafe extern "C" fn(u32) = core::mem::transmute(CACHE_FLUSH_ROM); + cache_flush_rom(cpu_num) + } +} + +#[inline(always)] +#[link_section = ".rwtext"] +pub(crate) fn cache_read_enable_rom(cpu_num: u32) { + unsafe { + let cache_read_enable_rom: unsafe extern "C" fn(u32) = + core::mem::transmute(CACHE_READ_ENABLE_ROM); + cache_read_enable_rom(cpu_num) + } +} + +#[inline(always)] +#[link_section = ".rwtext"] +pub(crate) fn spi_read_status_high( + flash_chip: *const EspRomSpiflashChipT, + status: &mut u32, +) -> i32 { + unsafe { + let spi_read_status_high: unsafe extern "C" fn( + *const EspRomSpiflashChipT, + *mut u32, + ) -> i32 = core::mem::transmute(SPI_READ_STATUS_HIGH); + spi_read_status_high(flash_chip, status as *mut u32) + } +} + +#[inline(always)] +#[link_section = ".rwtext"] +pub(crate) fn spi_read_status(flash_chip: *const EspRomSpiflashChipT, status: &mut u32) -> i32 { + unsafe { + let spi_read_status: unsafe extern "C" fn(*const EspRomSpiflashChipT, *mut u32) -> i32 = + core::mem::transmute(SPI_READ_STATUS); + spi_read_status(flash_chip, status as *mut u32) + } +} + +#[inline(always)] +#[link_section = ".rwtext"] +pub(crate) fn spi_write_status(flash_chip: *const EspRomSpiflashChipT, status_value: u32) -> i32 { + unsafe { + let spi_write_status: unsafe extern "C" fn(*const EspRomSpiflashChipT, u32) -> i32 = + core::mem::transmute(SPI_WRITE_STATUS); + spi_write_status(flash_chip, status_value) + } +} + +#[inline(always)] +#[link_section = ".rwtext"] +fn begin() { + // on some chips disabling cache access caused issues - we don't really need + // it +} + +#[inline(always)] +#[link_section = ".rwtext"] +fn end() { + cache_flush_rom(0); + cache_flush_rom(1); + cache_read_enable_rom(0); + cache_read_enable_rom(1); +} + +#[derive(Debug)] +#[repr(C)] +pub struct EspRomSpiflashChipT { + device_id: u32, + chip_size: u32, // chip size in bytes + block_size: u32, + sector_size: u32, + page_size: u32, + status_mask: u32, +} + +#[inline(never)] +#[link_section = ".rwtext"] +pub(crate) fn esp_rom_spiflash_read(src_addr: u32, data: *const u32, len: u32) -> i32 { + maybe_with_critical_section(|| { + spiflash_wait_for_ready(); + unsafe { + let esp_rom_spiflash_read: unsafe extern "C" fn(u32, *const u32, u32) -> i32 = + core::mem::transmute(ESP_ROM_SPIFLASH_READ); + esp_rom_spiflash_read(src_addr, data, len) + } + }) +} + +#[inline(never)] +#[link_section = ".rwtext"] +pub(crate) fn esp_rom_spiflash_erase_sector(sector_number: u32) -> i32 { + maybe_with_critical_section(|| { + let res = unsafe { + let esp_rom_spiflash_erase_sector: unsafe extern "C" fn(u32) -> i32 = + core::mem::transmute(ESP_ROM_SPIFLASH_ERASE_SECTOR); + esp_rom_spiflash_erase_sector(sector_number) + }; + spiflash_wait_for_ready(); + res + }) +} + +#[inline(always)] +#[link_section = ".rwtext"] +fn spi_write_enable() { + spiflash_wait_for_ready(); + + write_register(SPI_RD_STATUS_REG, 0); + write_register(SPI_CMD_REG, SPI_FLASH_WREN); + while read_register(SPI_CMD_REG) != 0 {} +} + +#[inline(never)] +#[link_section = ".rwtext"] +pub(crate) fn esp_rom_spiflash_write(dest_addr: u32, data: *const u32, len: u32) -> i32 { + maybe_with_critical_section(|| { + begin(); + + let flashchip = FLASH_CHIP_ADDR as *const EspRomSpiflashChipT; + let mut status: u32 = 0; + + spiflash_wait_for_ready(); + if spi_read_status_high(flashchip, &mut status) != 0 { + return -1; + } + + spiflash_wait_for_ready(); + + write_register(SPI_USER_REG, read_register(SPI_USER_REG) & !SPI_USR_DUMMY); + let addrbits = ESP_ROM_SPIFLASH_W_SIO_ADDR_BITSLEN; + let mut regval = read_register(SPI_USER1_REG); + regval &= !SPI_USR_ADDR_BITLEN_M; + regval |= addrbits << SPI_USR_ADDR_BITLEN_S; + write_register(SPI_USER1_REG, regval); + + for block in (0..len).step_by(32) { + spiflash_wait_for_ready(); + spi_write_enable(); + + let block_len = if len - block < 32 { len - block } else { 32 }; + write_register( + SPI_ADDR_REG, + ((dest_addr + block) & 0xffffff) | block_len << 24, + ); + + let data_ptr = unsafe { data.offset((block / 4) as isize) }; + for i in 0..block_len / 4 { + write_register(SPI_W0_REG + (4 * i), unsafe { + data_ptr.offset(i as isize).read_volatile() + }); + } + + write_register(SPI_RD_STATUS_REG, 0); + write_register(SPI_CMD_REG, 1 << 25); // FLASH PP + while read_register(SPI_CMD_REG) != 0 { /* wait */ } + + wait_for_ready(); + } + + spiflash_wait_for_ready(); + if spi_write_status(flashchip, status) != 0 { + end(); + return -1; + } + spiflash_wait_for_ready(); + + end(); + 0 + }) +} + +#[inline(always)] +#[link_section = ".rwtext"] +pub fn read_register(address: u32) -> u32 { + unsafe { (address as *const u32).read_volatile() } +} + +#[inline(always)] +#[link_section = ".rwtext"] +pub fn write_register(address: u32, value: u32) { + unsafe { + (address as *mut u32).write_volatile(value); + } +} + +#[inline(always)] +#[link_section = ".rwtext"] +fn wait_for_ready() { + while (read_register(SPI_EXT2_REG) & SPI_ST) != 0 {} + // ESP32_OR_LATER ... we don't support anything earlier + while (read_register(SPI0_EXT2_REG) & SPI_ST) != 0 {} +} + +#[inline(always)] +#[link_section = ".rwtext"] +fn spiflash_wait_for_ready() { + let flashchip = FLASH_CHIP_ADDR as *const EspRomSpiflashChipT; + + loop { + wait_for_ready(); + let mut status = 0; + spi_read_status(flashchip, &mut status); + if status & STATUS_WIP_BIT == 0 { + break; + } + } +} + +#[inline(never)] +#[link_section = ".rwtext"] +pub(crate) fn esp_rom_spiflash_unlock() -> i32 { + let flashchip = FLASH_CHIP_ADDR as *const EspRomSpiflashChipT; + if unsafe { (*flashchip).device_id } >> 16 & 0xff == 0x9D { + panic!("ISSI flash is not supported"); + } + + let g_rom_spiflash_dummy_len_plus = FLASH_DUMMY_LEN_PLUS_ADDR as *const u8; + if unsafe { g_rom_spiflash_dummy_len_plus.add(1).read_volatile() } == 0 { + panic!("Unsupported flash chip"); + } + + maybe_with_critical_section(|| { + begin(); + spiflash_wait_for_ready(); + + let mut status: u32 = 0; + if spi_read_status_high(flashchip, &mut status) != 0 { + return -1; + } + + // Clear all bits except QE, if it is set + status &= STATUS_QIE_BIT; + + write_register(SPI_CTRL_REG, read_register(SPI_CTRL_REG) | SPI_WRSR_2B); + + spiflash_wait_for_ready(); + if spi_write_status(flashchip, status) != 0 { + end(); + return -1; + } + spiflash_wait_for_ready(); + end(); + 0 + }) +} diff --git a/esp-storage/src/esp32c2.rs b/esp-storage/src/esp32c2.rs new file mode 100644 index 00000000000..c7c071ee253 --- /dev/null +++ b/esp-storage/src/esp32c2.rs @@ -0,0 +1,38 @@ +use crate::maybe_with_critical_section; + +const ESP_ROM_SPIFLASH_READ: u32 = 0x4000013c; +const ESP_ROM_SPIFLASH_UNLOCK: u32 = 0x40000140; +const ESP_ROM_SPIFLASH_ERASE_SECTOR: u32 = 0x40000130; +const ESP_ROM_SPIFLASH_WRITE: u32 = 0x40000138; + +pub(crate) fn esp_rom_spiflash_read(src_addr: u32, data: *const u32, len: u32) -> i32 { + maybe_with_critical_section(|| unsafe { + let esp_rom_spiflash_read: unsafe extern "C" fn(u32, *const u32, u32) -> i32 = + core::mem::transmute(ESP_ROM_SPIFLASH_READ); + esp_rom_spiflash_read(src_addr, data, len) + }) +} + +pub(crate) fn esp_rom_spiflash_unlock() -> i32 { + maybe_with_critical_section(|| unsafe { + let esp_rom_spiflash_unlock: unsafe extern "C" fn() -> i32 = + core::mem::transmute(ESP_ROM_SPIFLASH_UNLOCK); + esp_rom_spiflash_unlock() + }) +} + +pub(crate) fn esp_rom_spiflash_erase_sector(sector_number: u32) -> i32 { + maybe_with_critical_section(|| unsafe { + let esp_rom_spiflash_erase_sector: unsafe extern "C" fn(u32) -> i32 = + core::mem::transmute(ESP_ROM_SPIFLASH_ERASE_SECTOR); + esp_rom_spiflash_erase_sector(sector_number) + }) +} + +pub(crate) fn esp_rom_spiflash_write(dest_addr: u32, data: *const u32, len: u32) -> i32 { + maybe_with_critical_section(|| unsafe { + let esp_rom_spiflash_write: unsafe extern "C" fn(u32, *const u32, u32) -> i32 = + core::mem::transmute(ESP_ROM_SPIFLASH_WRITE); + esp_rom_spiflash_write(dest_addr, data, len) + }) +} diff --git a/esp-storage/src/esp32c3.rs b/esp-storage/src/esp32c3.rs new file mode 100644 index 00000000000..73a2d4c8dc1 --- /dev/null +++ b/esp-storage/src/esp32c3.rs @@ -0,0 +1,38 @@ +use crate::maybe_with_critical_section; + +const ESP_ROM_SPIFLASH_READ: u32 = 0x40000130; +const ESP_ROM_SPIFLASH_UNLOCK: u32 = 0x40000140; +const ESP_ROM_SPIFLASH_ERASE_SECTOR: u32 = 0x40000128; +const ESP_ROM_SPIFLASH_WRITE: u32 = 0x4000012c; + +pub(crate) fn esp_rom_spiflash_read(src_addr: u32, data: *const u32, len: u32) -> i32 { + maybe_with_critical_section(|| unsafe { + let esp_rom_spiflash_read: unsafe extern "C" fn(u32, *const u32, u32) -> i32 = + core::mem::transmute(ESP_ROM_SPIFLASH_READ); + esp_rom_spiflash_read(src_addr, data, len) + }) +} + +pub(crate) fn esp_rom_spiflash_unlock() -> i32 { + maybe_with_critical_section(|| unsafe { + let esp_rom_spiflash_unlock: unsafe extern "C" fn() -> i32 = + core::mem::transmute(ESP_ROM_SPIFLASH_UNLOCK); + esp_rom_spiflash_unlock() + }) +} + +pub(crate) fn esp_rom_spiflash_erase_sector(sector_number: u32) -> i32 { + maybe_with_critical_section(|| unsafe { + let esp_rom_spiflash_erase_sector: unsafe extern "C" fn(u32) -> i32 = + core::mem::transmute(ESP_ROM_SPIFLASH_ERASE_SECTOR); + esp_rom_spiflash_erase_sector(sector_number) + }) +} + +pub(crate) fn esp_rom_spiflash_write(dest_addr: u32, data: *const u32, len: u32) -> i32 { + maybe_with_critical_section(|| unsafe { + let esp_rom_spiflash_write: unsafe extern "C" fn(u32, *const u32, u32) -> i32 = + core::mem::transmute(ESP_ROM_SPIFLASH_WRITE); + esp_rom_spiflash_write(dest_addr, data, len) + }) +} diff --git a/esp-storage/src/esp32c6.rs b/esp-storage/src/esp32c6.rs new file mode 100644 index 00000000000..5255a6370eb --- /dev/null +++ b/esp-storage/src/esp32c6.rs @@ -0,0 +1,38 @@ +use crate::maybe_with_critical_section; + +const ESP_ROM_SPIFLASH_READ: u32 = 0x40000150; +const ESP_ROM_SPIFLASH_UNLOCK: u32 = 0x40000154; +const ESP_ROM_SPIFLASH_ERASE_SECTOR: u32 = 0x40000144; +const ESP_ROM_SPIFLASH_WRITE: u32 = 0x4000014c; + +pub(crate) fn esp_rom_spiflash_read(src_addr: u32, data: *const u32, len: u32) -> i32 { + maybe_with_critical_section(|| unsafe { + let esp_rom_spiflash_read: unsafe extern "C" fn(u32, *const u32, u32) -> i32 = + core::mem::transmute(ESP_ROM_SPIFLASH_READ); + esp_rom_spiflash_read(src_addr, data, len) + }) +} + +pub(crate) fn esp_rom_spiflash_unlock() -> i32 { + maybe_with_critical_section(|| unsafe { + let esp_rom_spiflash_unlock: unsafe extern "C" fn() -> i32 = + core::mem::transmute(ESP_ROM_SPIFLASH_UNLOCK); + esp_rom_spiflash_unlock() + }) +} + +pub(crate) fn esp_rom_spiflash_erase_sector(sector_number: u32) -> i32 { + maybe_with_critical_section(|| unsafe { + let esp_rom_spiflash_erase_sector: unsafe extern "C" fn(u32) -> i32 = + core::mem::transmute(ESP_ROM_SPIFLASH_ERASE_SECTOR); + esp_rom_spiflash_erase_sector(sector_number) + }) +} + +pub(crate) fn esp_rom_spiflash_write(dest_addr: u32, data: *const u32, len: u32) -> i32 { + maybe_with_critical_section(|| unsafe { + let esp_rom_spiflash_write: unsafe extern "C" fn(u32, *const u32, u32) -> i32 = + core::mem::transmute(ESP_ROM_SPIFLASH_WRITE); + esp_rom_spiflash_write(dest_addr, data, len) + }) +} diff --git a/esp-storage/src/esp32h2.rs b/esp-storage/src/esp32h2.rs new file mode 100644 index 00000000000..b7a1641d2b0 --- /dev/null +++ b/esp-storage/src/esp32h2.rs @@ -0,0 +1,38 @@ +use crate::maybe_with_critical_section; + +const ESP_ROM_SPIFLASH_READ: u32 = 0x4000012c; +const ESP_ROM_SPIFLASH_UNLOCK: u32 = 0x40000130; +const ESP_ROM_SPIFLASH_ERASE_SECTOR: u32 = 0x40000120; +const ESP_ROM_SPIFLASH_WRITE: u32 = 0x40000128; + +pub(crate) fn esp_rom_spiflash_read(src_addr: u32, data: *const u32, len: u32) -> i32 { + maybe_with_critical_section(|| unsafe { + let esp_rom_spiflash_read: unsafe extern "C" fn(u32, *const u32, u32) -> i32 = + core::mem::transmute(ESP_ROM_SPIFLASH_READ); + esp_rom_spiflash_read(src_addr, data, len) + }) +} + +pub(crate) fn esp_rom_spiflash_unlock() -> i32 { + maybe_with_critical_section(|| unsafe { + let esp_rom_spiflash_unlock: unsafe extern "C" fn() -> i32 = + core::mem::transmute(ESP_ROM_SPIFLASH_UNLOCK); + esp_rom_spiflash_unlock() + }) +} + +pub(crate) fn esp_rom_spiflash_erase_sector(sector_number: u32) -> i32 { + maybe_with_critical_section(|| unsafe { + let esp_rom_spiflash_erase_sector: unsafe extern "C" fn(u32) -> i32 = + core::mem::transmute(ESP_ROM_SPIFLASH_ERASE_SECTOR); + esp_rom_spiflash_erase_sector(sector_number) + }) +} + +pub(crate) fn esp_rom_spiflash_write(dest_addr: u32, data: *const u32, len: u32) -> i32 { + maybe_with_critical_section(|| unsafe { + let esp_rom_spiflash_write: unsafe extern "C" fn(u32, *const u32, u32) -> i32 = + core::mem::transmute(ESP_ROM_SPIFLASH_WRITE); + esp_rom_spiflash_write(dest_addr, data, len) + }) +} diff --git a/esp-storage/src/esp32s2.rs b/esp-storage/src/esp32s2.rs new file mode 100644 index 00000000000..2717ae8f26d --- /dev/null +++ b/esp-storage/src/esp32s2.rs @@ -0,0 +1,38 @@ +use crate::maybe_with_critical_section; + +const ESP_ROM_SPIFLASH_READ: u32 = 0x4001728c; +const ESP_ROM_SPIFLASH_UNLOCK: u32 = 0x40016e88; +const ESP_ROM_SPIFLASH_ERASE_SECTOR: u32 = 0x4001716c; +const ESP_ROM_SPIFLASH_WRITE: u32 = 0x400171cc; + +pub(crate) fn esp_rom_spiflash_read(src_addr: u32, data: *const u32, len: u32) -> i32 { + maybe_with_critical_section(|| unsafe { + let esp_rom_spiflash_read: unsafe extern "C" fn(u32, *const u32, u32) -> i32 = + core::mem::transmute(ESP_ROM_SPIFLASH_READ); + esp_rom_spiflash_read(src_addr, data, len) + }) +} + +pub(crate) fn esp_rom_spiflash_unlock() -> i32 { + maybe_with_critical_section(|| unsafe { + let esp_rom_spiflash_unlock: unsafe extern "C" fn() -> i32 = + core::mem::transmute(ESP_ROM_SPIFLASH_UNLOCK); + esp_rom_spiflash_unlock() + }) +} + +pub(crate) fn esp_rom_spiflash_erase_sector(sector_number: u32) -> i32 { + maybe_with_critical_section(|| unsafe { + let esp_rom_spiflash_erase_sector: unsafe extern "C" fn(u32) -> i32 = + core::mem::transmute(ESP_ROM_SPIFLASH_ERASE_SECTOR); + esp_rom_spiflash_erase_sector(sector_number) + }) +} + +pub(crate) fn esp_rom_spiflash_write(dest_addr: u32, data: *const u32, len: u32) -> i32 { + maybe_with_critical_section(|| unsafe { + let esp_rom_spiflash_write: unsafe extern "C" fn(u32, *const u32, u32) -> i32 = + core::mem::transmute(ESP_ROM_SPIFLASH_WRITE); + esp_rom_spiflash_write(dest_addr, data, len) + }) +} diff --git a/esp-storage/src/esp32s3.rs b/esp-storage/src/esp32s3.rs new file mode 100644 index 00000000000..37cfbb791f4 --- /dev/null +++ b/esp-storage/src/esp32s3.rs @@ -0,0 +1,46 @@ +use crate::maybe_with_critical_section; + +const ESP_ROM_SPIFLASH_READ: u32 = 0x40000a20; +const ESP_ROM_SPIFLASH_UNLOCK: u32 = 0x40000a2c; +const ESP_ROM_SPIFLASH_ERASE_SECTOR: u32 = 0x400009fc; +const ESP_ROM_SPIFLASH_WRITE: u32 = 0x40000a14; + +#[inline(always)] +#[link_section = ".rwtext"] +pub(crate) fn esp_rom_spiflash_read(src_addr: u32, data: *const u32, len: u32) -> i32 { + maybe_with_critical_section(|| unsafe { + let esp_rom_spiflash_read: unsafe extern "C" fn(u32, *const u32, u32) -> i32 = + core::mem::transmute(ESP_ROM_SPIFLASH_READ); + esp_rom_spiflash_read(src_addr, data, len) + }) +} + +#[inline(always)] +#[link_section = ".rwtext"] +pub(crate) fn esp_rom_spiflash_unlock() -> i32 { + maybe_with_critical_section(|| unsafe { + let esp_rom_spiflash_unlock: unsafe extern "C" fn() -> i32 = + core::mem::transmute(ESP_ROM_SPIFLASH_UNLOCK); + esp_rom_spiflash_unlock() + }) +} + +#[inline(always)] +#[link_section = ".rwtext"] +pub(crate) fn esp_rom_spiflash_erase_sector(sector_number: u32) -> i32 { + maybe_with_critical_section(|| unsafe { + let esp_rom_spiflash_erase_sector: unsafe extern "C" fn(u32) -> i32 = + core::mem::transmute(ESP_ROM_SPIFLASH_ERASE_SECTOR); + esp_rom_spiflash_erase_sector(sector_number) + }) +} + +#[inline(always)] +#[link_section = ".rwtext"] +pub(crate) fn esp_rom_spiflash_write(dest_addr: u32, data: *const u32, len: u32) -> i32 { + maybe_with_critical_section(|| unsafe { + let esp_rom_spiflash_write: unsafe extern "C" fn(u32, *const u32, u32) -> i32 = + core::mem::transmute(ESP_ROM_SPIFLASH_WRITE); + esp_rom_spiflash_write(dest_addr, data, len) + }) +} diff --git a/esp-storage/src/lib.rs b/esp-storage/src/lib.rs new file mode 100644 index 00000000000..7247ab8c231 --- /dev/null +++ b/esp-storage/src/lib.rs @@ -0,0 +1,60 @@ +#![cfg_attr(not(all(test, feature = "emulation")), no_std)] + +#[cfg(not(feature = "emulation"))] +#[cfg_attr(feature = "esp32c2", path = "esp32c2.rs")] +#[cfg_attr(feature = "esp32c3", path = "esp32c3.rs")] +#[cfg_attr(feature = "esp32c6", path = "esp32c6.rs")] +#[cfg_attr(feature = "esp32h2", path = "esp32h2.rs")] +#[cfg_attr(feature = "esp32", path = "esp32.rs")] +#[cfg_attr(feature = "esp32s2", path = "esp32s2.rs")] +#[cfg_attr(feature = "esp32s3", path = "esp32s3.rs")] +#[cfg_attr( + not(any( + feature = "esp32c2", + feature = "esp32c3", + feature = "esp32c6", + feature = "esp32", + feature = "esp32s2", + feature = "esp32s3", + feature = "esp32h2" + )), + path = "stub.rs" +)] +mod chip_specific; + +#[cfg(feature = "emulation")] +#[path = "stub.rs"] +mod chip_specific; + +#[cfg(any(feature = "storage", feature = "nor-flash"))] +mod common; + +#[cfg(any(feature = "storage", feature = "nor-flash"))] +use common::FlashSectorBuffer; +#[cfg(any(feature = "storage", feature = "nor-flash"))] +pub use common::{FlashStorage, FlashStorageError}; + +#[cfg(feature = "storage")] +mod storage; + +#[cfg(feature = "nor-flash")] +mod nor_flash; + +#[cfg(feature = "low-level")] +pub mod ll; + +#[cfg(not(feature = "emulation"))] +#[inline(always)] +#[link_section = ".rwtext"] +fn maybe_with_critical_section(f: impl FnOnce() -> R) -> R { + #[cfg(feature = "critical-section")] + return critical_section::with(|_| f()); + + #[cfg(not(feature = "critical-section"))] + f() +} + +#[cfg(feature = "emulation")] +fn maybe_with_critical_section(f: impl FnOnce() -> R) -> R { + f() +} diff --git a/esp-storage/src/ll.rs b/esp-storage/src/ll.rs new file mode 100644 index 00000000000..67bac35e660 --- /dev/null +++ b/esp-storage/src/ll.rs @@ -0,0 +1,56 @@ +/// Low-level API +/// +/// This gives you access to the underlying low level functionality. +/// These operate on raw pointers and all functions here are unsafe. +/// No pre-conditions are checked by any of these functions. +use crate::chip_specific; + +/// Low-level SPI NOR Flash read +/// +/// # Safety +/// +/// The `src_addr` + `len` should not exceeds the size of flash. +/// The `data` expected to points to word-aligned pre-allocated buffer with size +/// greater or equals to `len`. +pub unsafe fn spiflash_read(src_addr: u32, data: *mut u32, len: u32) -> Result<(), i32> { + match chip_specific::esp_rom_spiflash_read(src_addr, data, len) { + 0 => Ok(()), + value => Err(value), + } +} + +/// Low-level SPI NOR Flash unlock +/// +/// # Safety +pub unsafe fn spiflash_unlock() -> Result<(), i32> { + match chip_specific::esp_rom_spiflash_unlock() { + 0 => Ok(()), + value => Err(value), + } +} + +/// Low-level SPI NOR Flash erase +/// +/// # Safety +/// +/// The `sector_number` * sector_size should not exceeds the size of flash. +pub unsafe fn spiflash_erase_sector(sector_number: u32) -> Result<(), i32> { + match chip_specific::esp_rom_spiflash_erase_sector(sector_number) { + 0 => Ok(()), + value => Err(value), + } +} + +/// Low-level SPI NOR Flash write +/// +/// # Safety +/// +/// The `dest_addr` + `len` should not exceeds the size of flash. +/// The `data` expected to points to word-aligned buffer with size greater or +/// equals to `len`. +pub unsafe fn spiflash_write(dest_addr: u32, data: *const u32, len: u32) -> Result<(), i32> { + match chip_specific::esp_rom_spiflash_write(dest_addr, data, len) { + 0 => Ok(()), + value => Err(value), + } +} diff --git a/esp-storage/src/nor_flash.rs b/esp-storage/src/nor_flash.rs new file mode 100644 index 00000000000..902dc5ea83b --- /dev/null +++ b/esp-storage/src/nor_flash.rs @@ -0,0 +1,356 @@ +use core::{ + mem::MaybeUninit, + ops::{Deref, DerefMut}, +}; + +use embedded_storage::nor_flash::{ + ErrorType, + NorFlash, + NorFlashError, + NorFlashErrorKind, + ReadNorFlash, +}; + +use crate::{FlashSectorBuffer, FlashStorage, FlashStorageError}; + +#[repr(C, align(4))] +struct FlashWordBuffer { + // NOTE: Ensure that no unaligned fields are added above `data` to maintain its required + // alignment + data: [u8; FlashStorage::WORD_SIZE as usize], +} + +impl Deref for FlashWordBuffer { + type Target = [u8; FlashStorage::WORD_SIZE as usize]; + + fn deref(&self) -> &Self::Target { + &self.data + } +} + +impl DerefMut for FlashWordBuffer { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.data + } +} + +impl FlashStorage { + #[inline(always)] + fn is_word_aligned(bytes: &[u8]) -> bool { + // TODO: Use is_aligned_to when stabilized (see `pointer_is_aligned`) + (unsafe { bytes.as_ptr().offset_from(core::ptr::null()) }) % Self::WORD_SIZE as isize == 0 + } +} + +impl NorFlashError for FlashStorageError { + fn kind(&self) -> NorFlashErrorKind { + match self { + Self::NotAligned => NorFlashErrorKind::NotAligned, + Self::OutOfBounds => NorFlashErrorKind::OutOfBounds, + _ => NorFlashErrorKind::Other, + } + } +} + +impl ErrorType for FlashStorage { + type Error = FlashStorageError; +} + +impl ReadNorFlash for FlashStorage { + #[cfg(not(feature = "bytewise-read"))] + const READ_SIZE: usize = Self::WORD_SIZE as _; + + #[cfg(feature = "bytewise-read")] + const READ_SIZE: usize = 1; + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.check_alignment::<{ Self::READ_SIZE as _ }>(offset, bytes.len())?; + self.check_bounds(offset, bytes.len())?; + + #[cfg(feature = "bytewise-read")] + let (offset, bytes) = { + let byte_offset = (offset % Self::WORD_SIZE) as usize; + if byte_offset > 0 { + let mut word_buffer = MaybeUninit::::uninit(); + let word_buffer = unsafe { word_buffer.assume_init_mut() }; + + let offset = offset - byte_offset as u32; + let length = bytes.len().min(word_buffer.len() - byte_offset); + + self.internal_read(offset, &mut word_buffer[..])?; + bytes[..length].copy_from_slice(&word_buffer[byte_offset..][..length]); + + (offset + Self::WORD_SIZE, &mut bytes[length..]) + } else { + (offset, bytes) + } + }; + + if Self::is_word_aligned(bytes) { + // Bytes buffer is word-aligned so we can read directly to it + for (offset, chunk) in (offset..) + .step_by(Self::SECTOR_SIZE as _) + .zip(bytes.chunks_mut(Self::SECTOR_SIZE as _)) + { + // Chunk already is word aligned so we can read directly to it + #[cfg(not(feature = "bytewise-read"))] + self.internal_read(offset, chunk)?; + + #[cfg(feature = "bytewise-read")] + { + let length = chunk.len(); + let byte_length = length % Self::WORD_SIZE as usize; + let length = length - byte_length; + + self.internal_read(offset, &mut chunk[..length])?; + + // Read not aligned rest of data + if byte_length > 0 { + let mut word_buffer = MaybeUninit::::uninit(); + let word_buffer = unsafe { word_buffer.assume_init_mut() }; + + self.internal_read(offset + length as u32, &mut word_buffer[..])?; + chunk[length..].copy_from_slice(&word_buffer[..byte_length]); + } + } + } + } else { + // Bytes buffer isn't word-aligned so we might read only via aligned buffer + let mut buffer = MaybeUninit::::uninit(); + let buffer = unsafe { buffer.assume_init_mut() }; + + for (offset, chunk) in (offset..) + .step_by(Self::SECTOR_SIZE as _) + .zip(bytes.chunks_mut(Self::SECTOR_SIZE as _)) + { + // Read to temporary buffer first (chunk length is aligned) + #[cfg(not(feature = "bytewise-read"))] + self.internal_read(offset, &mut buffer[..chunk.len()])?; + + // Read to temporary buffer first (chunk length is not aligned) + #[cfg(feature = "bytewise-read")] + { + let length = chunk.len(); + let byte_length = length % Self::WORD_SIZE as usize; + let length = if byte_length > 0 { + length - byte_length + Self::WORD_SIZE as usize + } else { + length + }; + + self.internal_read(offset, &mut buffer[..length])?; + } + + // Copy to bytes buffer + chunk.copy_from_slice(&buffer[..chunk.len()]); + } + } + + Ok(()) + } + + fn capacity(&self) -> usize { + self.capacity + } +} + +impl NorFlash for FlashStorage { + const WRITE_SIZE: usize = Self::WORD_SIZE as _; + const ERASE_SIZE: usize = Self::SECTOR_SIZE as _; + + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.check_alignment::<{ Self::WORD_SIZE }>(offset, bytes.len())?; + self.check_bounds(offset, bytes.len())?; + + if Self::is_word_aligned(bytes) { + // Bytes buffer is word-aligned so we can write directly from it + for (offset, chunk) in (offset..) + .step_by(Self::SECTOR_SIZE as _) + .zip(bytes.chunks(Self::SECTOR_SIZE as _)) + { + // Chunk already is word aligned so we can write directly from it + self.internal_write(offset, chunk)?; + } + } else { + // Bytes buffer isn't word-aligned so we might write only via aligned buffer + let mut buffer = MaybeUninit::::uninit(); + let buffer = unsafe { buffer.assume_init_mut() }; + + for (offset, chunk) in (offset..) + .step_by(Self::SECTOR_SIZE as _) + .zip(bytes.chunks(Self::SECTOR_SIZE as _)) + { + // Copy to temporary buffer first + buffer[..chunk.len()].copy_from_slice(chunk); + // Write from temporary buffer + self.internal_write(offset, &buffer[..chunk.len()])?; + } + } + + Ok(()) + } + + fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + let len = (to - from) as _; + self.check_alignment::<{ Self::SECTOR_SIZE }>(from, len)?; + self.check_bounds(from, len)?; + + for sector in from / Self::SECTOR_SIZE..to / Self::SECTOR_SIZE { + self.internal_erase(sector)?; + } + + Ok(()) + } +} + +#[cfg(test)] +mod test { + use core::mem::MaybeUninit; + + use super::*; + + const WORD_SIZE: u32 = 4; + const SECTOR_SIZE: u32 = 4 << 10; + const NUM_SECTORS: u32 = 3; + const FLASH_SIZE: u32 = SECTOR_SIZE * NUM_SECTORS; + const MAX_OFFSET: u32 = SECTOR_SIZE * 1; + const MAX_LENGTH: u32 = SECTOR_SIZE * 2; + + #[repr(C, align(4))] + struct TestBuffer { + // NOTE: Ensure that no unaligned fields are added above `data` to maintain its required + // alignment + data: MaybeUninit<[u8; FLASH_SIZE as _]>, + } + + impl TestBuffer { + const fn seq() -> Self { + let mut data = [0u8; FLASH_SIZE as _]; + let mut index = 0; + while index < FLASH_SIZE { + data[index as usize] = (index & 0xff) as u8; + index += 1; + } + Self { + data: MaybeUninit::new(data), + } + } + } + + impl Default for TestBuffer { + fn default() -> Self { + Self { + data: MaybeUninit::uninit(), + } + } + } + + impl Deref for TestBuffer { + type Target = [u8; FLASH_SIZE as usize]; + + fn deref(&self) -> &Self::Target { + unsafe { self.data.assume_init_ref() } + } + } + + impl DerefMut for TestBuffer { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { self.data.assume_init_mut() } + } + } + + fn range_gen( + aligned: Option, + ) -> impl Iterator { + (0..=MAX_OFF).flat_map(move |off| { + (0..=MAX_LEN - off) + .filter(move |len| { + aligned + .map(|aligned| aligned == (off % ALIGN == 0 && len % ALIGN == 0)) + .unwrap_or(true) + }) + .map(move |len| (off, len)) + }) + } + + #[test] + #[cfg(not(feature = "bytewise-read"))] + fn aligned_read() { + let mut flash = FlashStorage::new(); + let src = TestBuffer::seq(); + let mut data = TestBuffer::default(); + + flash.erase(0, FLASH_SIZE).unwrap(); + flash.write(0, &*src).unwrap(); + + for (off, len) in range_gen::(Some(true)) { + flash.read(off, &mut data[..len as usize]).unwrap(); + assert_eq!(data[..len as usize], src[off as usize..][..len as usize]); + } + } + + #[test] + #[cfg(not(feature = "bytewise-read"))] + fn not_aligned_read_aligned_buffer() { + let mut flash = FlashStorage::new(); + let mut data = TestBuffer::default(); + + for (off, len) in range_gen::(Some(false)) { + flash.read(off, &mut data[..len as usize]).unwrap_err(); + } + } + + #[test] + #[cfg(not(feature = "bytewise-read"))] + fn aligned_read_not_aligned_buffer() { + let mut flash = FlashStorage::new(); + let src = TestBuffer::seq(); + let mut data = TestBuffer::default(); + + flash.erase(0, FLASH_SIZE).unwrap(); + flash.write(0, &*src).unwrap(); + + for (off, len) in range_gen::(Some(true)) { + flash.read(off, &mut data[1..][..len as usize]).unwrap(); + assert_eq!( + data[1..][..len as usize], + src[off as usize..][..len as usize] + ); + } + } + + #[test] + #[cfg(feature = "bytewise-read")] + fn bytewise_read_aligned_buffer() { + let mut flash = FlashStorage::new(); + let src = TestBuffer::seq(); + let mut data = TestBuffer::default(); + + flash.erase(0, FLASH_SIZE).unwrap(); + flash.write(0, &*src).unwrap(); + + for (off, len) in range_gen::(None) { + flash.read(off, &mut data[..len as usize]).unwrap(); + assert_eq!(data[..len as usize], src[off as usize..][..len as usize]); + } + } + + #[test] + #[cfg(feature = "bytewise-read")] + fn bytewise_read_not_aligned_buffer() { + let mut flash = FlashStorage::new(); + let src = TestBuffer::seq(); + let mut data = TestBuffer::default(); + + flash.erase(0, FLASH_SIZE).unwrap(); + flash.write(0, &*src).unwrap(); + + for (off, len) in range_gen::(None) { + flash.read(off, &mut data[1..][..len as usize]).unwrap(); + assert_eq!( + data[1..][..len as usize], + src[off as usize..][..len as usize] + ); + } + } +} diff --git a/esp-storage/src/storage.rs b/esp-storage/src/storage.rs new file mode 100644 index 00000000000..b1b5c614a0f --- /dev/null +++ b/esp-storage/src/storage.rs @@ -0,0 +1,74 @@ +use core::mem::MaybeUninit; + +use embedded_storage::{ReadStorage, Storage}; + +use crate::{FlashSectorBuffer, FlashStorage, FlashStorageError}; + +impl ReadStorage for FlashStorage { + type Error = FlashStorageError; + + fn read(&mut self, offset: u32, mut bytes: &mut [u8]) -> Result<(), Self::Error> { + self.check_bounds(offset, bytes.len())?; + + let mut data_offset = offset % Self::WORD_SIZE; + let mut aligned_offset = offset - data_offset; + + // Bypass clearing sector buffer for performance reasons + let mut sector_data = MaybeUninit::::uninit(); + let sector_data = unsafe { sector_data.assume_init_mut() }; + + while !bytes.is_empty() { + let len = bytes.len().min((Self::SECTOR_SIZE - data_offset) as _); + + let aligned_end = (data_offset as usize + len + (Self::WORD_SIZE - 1) as usize) + & !(Self::WORD_SIZE - 1) as usize; + + // Read only needed data words + self.internal_read(aligned_offset, &mut sector_data[..aligned_end])?; + + bytes[..len].copy_from_slice(§or_data[data_offset as usize..][..len]); + + aligned_offset += Self::SECTOR_SIZE; + data_offset = 0; + bytes = &mut bytes[len..]; + } + + Ok(()) + } + + /// The SPI flash size is configured by writing a field in the software + /// bootloader image header. This is done during flashing in espflash / + /// esptool. + fn capacity(&self) -> usize { + self.capacity + } +} + +impl Storage for FlashStorage { + fn write(&mut self, offset: u32, mut bytes: &[u8]) -> Result<(), Self::Error> { + self.check_bounds(offset, bytes.len())?; + + let mut data_offset = offset % Self::SECTOR_SIZE; + let mut aligned_offset = offset - data_offset; + + // Bypass clearing sector buffer for performance reasons + let mut sector_data = MaybeUninit::::uninit(); + let sector_data = unsafe { sector_data.assume_init_mut() }; + + while !bytes.is_empty() { + self.internal_read(aligned_offset, &mut sector_data[..])?; + + let len = bytes.len().min((Self::SECTOR_SIZE - data_offset) as _); + + sector_data[data_offset as usize..][..len].copy_from_slice(&bytes[..len]); + self.internal_erase(aligned_offset / Self::SECTOR_SIZE)?; + self.internal_write(aligned_offset, §or_data[..])?; + + aligned_offset += Self::SECTOR_SIZE; + data_offset = 0; + bytes = &bytes[len..]; + } + + Ok(()) + } +} diff --git a/esp-storage/src/stub.rs b/esp-storage/src/stub.rs new file mode 100644 index 00000000000..1fa8b839219 --- /dev/null +++ b/esp-storage/src/stub.rs @@ -0,0 +1,107 @@ +use core::{ptr, slice}; + +use crate::maybe_with_critical_section; + +const SUCCESS_CODE: i32 = 0; +const ERROR_CODE: i32 = 1; +const ERASE_BYTE: u8 = 0xff; +const WORD_SIZE: u32 = 4; +const SECTOR_SIZE: u32 = 4 << 10; +const NUM_SECTORS: u32 = 4; +const FLASH_SIZE: u32 = SECTOR_SIZE * NUM_SECTORS; + +static mut FLASH_LOCK: bool = true; +static mut FLASH_DATA: [u8; FLASH_SIZE as usize] = [0u8; FLASH_SIZE as usize]; + +macro_rules! print_error { + ($($tt:tt)*) => { + #[cfg(all(test, feature = "emulation"))] + eprintln!($($tt)*) + }; +} + +fn check( + offset: u32, + length: u32, + data: *const u32, +) -> bool { + if offset % ALIGN > 0 { + print_error!("Not aligned offset: {offset}"); + return false; + } + if length % ALIGN > 0 { + print_error!("Not aligned length: {length}"); + return false; + } + if offset > SIZE { + print_error!("Offset out of range: {offset} > {SIZE}"); + return false; + } + if offset + length > SIZE { + print_error!("Length out of range: {offset} + {length} > {SIZE}"); + return false; + } + if length > MAX_LEN { + print_error!("Length out of range: {length} > {MAX_LEN}"); + return false; + } + let addr = unsafe { (data as *const u8).offset_from(ptr::null()) } as u32; + if addr % ALIGN > 0 { + print_error!("Not aligned data: {addr:#0x}"); + return false; + } + if unsafe { FLASH_LOCK } { + print_error!("Flash locked"); + return false; + } + true +} + +pub(crate) fn esp_rom_spiflash_read(src_addr: u32, data: *mut u32, len: u32) -> i32 { + if check::(src_addr, len, data) { + maybe_with_critical_section(|| { + let src = unsafe { slice::from_raw_parts_mut(data as *mut u8, len as _) }; + unsafe { src.copy_from_slice(&FLASH_DATA[src_addr as usize..][..len as usize]) }; + }); + SUCCESS_CODE + } else { + ERROR_CODE + } +} + +pub(crate) fn esp_rom_spiflash_unlock() -> i32 { + maybe_with_critical_section(|| { + unsafe { FLASH_LOCK = false }; + }); + SUCCESS_CODE +} + +pub(crate) fn esp_rom_spiflash_erase_sector(sector_number: u32) -> i32 { + if check::<1, NUM_SECTORS, 1>(sector_number, 1, ptr::null()) { + maybe_with_critical_section(|| { + let dst_addr = sector_number * SECTOR_SIZE; + let len = SECTOR_SIZE; + unsafe { FLASH_DATA[dst_addr as usize..][..len as usize].fill(ERASE_BYTE) }; + }); + SUCCESS_CODE + } else { + ERROR_CODE + } +} + +pub(crate) fn esp_rom_spiflash_write(dest_addr: u32, data: *const u32, len: u32) -> i32 { + if check::(dest_addr, len, data) { + maybe_with_critical_section(|| { + let dst = unsafe { slice::from_raw_parts(data as *const u8, len as _) }; + for (d, s) in unsafe { &mut FLASH_DATA[dest_addr as usize..][..len as usize] } + .iter_mut() + .zip(dst) + { + *d &= *s; + } + }); + SUCCESS_CODE + } else { + ERROR_CODE + } +} diff --git a/examples/Cargo.toml b/examples/Cargo.toml index e79afe10048..002732796f4 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -27,12 +27,14 @@ embedded-hal-async = "1.0.0" embedded-hal-bus = "0.1.0" embedded-io = { version = "0.6.1", default-features = false } embedded-io-async = "0.6.1" +embedded-storage = "0.3.0" esp-alloc = { version = "0.3.0", path = "../esp-alloc" } esp-backtrace = { version = "0.11.1", path = "../esp-backtrace", features = ["exception-handler", "panic-handler", "println"] } esp-hal = { version = "0.17.0", path = "../esp-hal", features = ["log"] } esp-hal-smartled = { version = "0.10.0", path = "../esp-hal-smartled", optional = true } esp-ieee802154 = { version = "0.1.0", path = "../esp-ieee802154", optional = true } esp-println = { version = "0.9.1", path = "../esp-println", features = ["log"] } +esp-storage = { version = "0.3.0", path = "../esp-storage", optional = true } esp-wifi = { version = "0.5.1", path = "../esp-wifi", optional = true } fugit = "0.3.7" heapless = "0.8.0" @@ -54,13 +56,13 @@ usb-device = "0.3.2" usbd-serial = "0.2.1" [features] -esp32 = ["esp-hal/esp32", "esp-backtrace/esp32", "esp-println/esp32", "esp-hal-smartled/esp32", "esp-wifi?/esp32"] -esp32c2 = ["esp-hal/esp32c2", "esp-backtrace/esp32c2", "esp-println/esp32c2", "esp-wifi?/esp32c2"] -esp32c3 = ["esp-hal/esp32c3", "esp-backtrace/esp32c3", "esp-println/esp32c3", "esp-hal-smartled/esp32c3", "esp-wifi?/esp32c3"] -esp32c6 = ["esp-hal/esp32c6", "esp-backtrace/esp32c6", "esp-println/esp32c6", "esp-hal-smartled/esp32c6", "esp-ieee802154/esp32c6", "esp-wifi?/esp32c6"] -esp32h2 = ["esp-hal/esp32h2", "esp-backtrace/esp32h2", "esp-println/esp32h2", "esp-hal-smartled/esp32h2", "esp-ieee802154/esp32h2", "esp-wifi?/esp32h2"] -esp32s2 = ["esp-hal/esp32s2", "esp-backtrace/esp32s2", "esp-println/esp32s2", "esp-hal-smartled/esp32s2", "esp-wifi?/esp32s2"] -esp32s3 = ["esp-hal/esp32s3", "esp-backtrace/esp32s3", "esp-println/esp32s3", "esp-hal-smartled/esp32s3", "esp-wifi?/esp32s3"] +esp32 = ["esp-hal/esp32", "esp-backtrace/esp32", "esp-println/esp32", "esp-hal-smartled/esp32", "esp-wifi?/esp32", "esp-storage?/esp32"] +esp32c2 = ["esp-hal/esp32c2", "esp-backtrace/esp32c2", "esp-println/esp32c2", "esp-wifi?/esp32c2", "esp-storage?/esp32c2"] +esp32c3 = ["esp-hal/esp32c3", "esp-backtrace/esp32c3", "esp-println/esp32c3", "esp-hal-smartled/esp32c3", "esp-wifi?/esp32c3", "esp-storage?/esp32c3"] +esp32c6 = ["esp-hal/esp32c6", "esp-backtrace/esp32c6", "esp-println/esp32c6", "esp-hal-smartled/esp32c6", "esp-ieee802154/esp32c6", "esp-wifi?/esp32c6", "esp-storage?/esp32c6"] +esp32h2 = ["esp-hal/esp32h2", "esp-backtrace/esp32h2", "esp-println/esp32h2", "esp-hal-smartled/esp32h2", "esp-ieee802154/esp32h2", "esp-wifi?/esp32h2", "esp-storage?/esp32h2"] +esp32s2 = ["esp-hal/esp32s2", "esp-backtrace/esp32s2", "esp-println/esp32s2", "esp-hal-smartled/esp32s2", "esp-wifi?/esp32s2", "esp-storage?/esp32s2"] +esp32s3 = ["esp-hal/esp32s3", "esp-backtrace/esp32s3", "esp-println/esp32s3", "esp-hal-smartled/esp32s3", "esp-wifi?/esp32s3", "esp-storage?/esp32s3"] esp-wifi = ["dep:esp-wifi"] diff --git a/examples/src/bin/flash_read_write.rs b/examples/src/bin/flash_read_write.rs new file mode 100644 index 00000000000..e330ca03366 --- /dev/null +++ b/examples/src/bin/flash_read_write.rs @@ -0,0 +1,50 @@ +//! Writes and reads flash memory. +//! +//! Uses flash address 0x9000 (default NVS) +//! See https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/partition-tables.html#built-in-partition-tables + +//% FEATURES: esp-storage +//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 + +#![no_std] +#![no_main] + +use embedded_storage::{ReadStorage, Storage}; +use esp_backtrace as _; +use esp_hal::prelude::*; +use esp_println::println; +use esp_storage::FlashStorage; + +#[entry] +fn main() -> ! { + let mut bytes = [0u8; 32]; + + let mut flash = FlashStorage::new(); + + let flash_addr = 0x9000; + println!("Flash size = {}", flash.capacity()); + println!(); + + flash.read(flash_addr, &mut bytes).unwrap(); + println!("Read from {:x}: {:02x?}", flash_addr, &bytes[..32]); + + bytes[0x00] = bytes[0x00].wrapping_add(1); + bytes[0x01] = bytes[0x01].wrapping_add(2); + bytes[0x02] = bytes[0x02].wrapping_add(3); + bytes[0x03] = bytes[0x03].wrapping_add(4); + bytes[0x04] = bytes[0x04].wrapping_add(1); + bytes[0x05] = bytes[0x05].wrapping_add(2); + bytes[0x06] = bytes[0x06].wrapping_add(3); + bytes[0x07] = bytes[0x07].wrapping_add(4); + + flash.write(flash_addr, &bytes).unwrap(); + println!("Written to {:x}: {:02x?}", flash_addr, &bytes[..32]); + + let mut reread_bytes = [0u8; 32]; + flash.read(flash_addr, &mut reread_bytes).unwrap(); + println!("Read from {:x}: {:02x?}", flash_addr, &reread_bytes[..32]); + + println!("Reset (CTRL-R in espflash) to re-read the persisted data."); + + loop {} +} diff --git a/hil-test/.cargo/config.toml b/hil-test/.cargo/config.toml index 82243731338..782ecfe704f 100644 --- a/hil-test/.cargo/config.toml +++ b/hil-test/.cargo/config.toml @@ -21,3 +21,6 @@ DEFMT_LOG = "info" [unstable] build-std = ["core"] + +[net] +git-fetch-with-cli = true diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index ab1e3b8da8c..ee73a7fc034 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -29,6 +29,7 @@ pub enum Package { EspMetadata, EspPrintln, EspRiscvRt, + EspStorage, EspWifi, Examples, HilTest, diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 99b0c99d514..6f3b8a74fb1 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -490,6 +490,27 @@ fn lint_packages(workspace: &Path, _args: LintPackagesArgs) -> Result<()> { ], )?, + Package::EspStorage => lint_package( + &path, + &[ + "-Zbuild-std=core", + "--target=riscv32imc-unknown-none-elf", + "--features=esp32c6", + ], + )?, + + Package::EspWifi => (), + // TODO lint esp-wifi! + // + // lint_package( + // &path, + // &[ + // "-Zbuild-std=core", + // "--target=riscv32imc-unknown-none-elf", + // "--features=esp32c3", + // ], + // )?, + // We will *not* check the following packages with `clippy`; this // may or may not change in the future: Package::Examples | Package::HilTest => {}