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
This commit is contained in:
Björn Quentin 2024-05-27 17:28:05 +02:00 committed by GitHub
parent 3b9a4938cb
commit 60e4b882ef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 1535 additions and 14 deletions

View File

@ -14,6 +14,7 @@ exclude = [
"esp-println",
"esp-riscv-rt",
"esp-wifi",
"esp-storage",
"examples",
"extras/bench-server",
"extras/esp-wifishark",

View File

@ -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" }

51
esp-storage/Cargo.toml Normal file
View File

@ -0,0 +1,51 @@
[package]
name = "esp-storage"
version = "0.3.0"
edition = "2021"
authors = [
"The ESP-RS team",
"Björn Quentin <bjoern.quentin@mobile-j.de>",
]
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 = []

34
esp-storage/README.md Normal file
View File

@ -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.

21
esp-storage/build.rs Normal file
View File

@ -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(())
}
}

161
esp-storage/src/common.rs Normal file
View File

@ -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<const ALIGN: u32>(
&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,
))
}
}

287
esp-storage/src/esp32.rs Normal file
View File

@ -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
})
}

View File

@ -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)
})
}

View File

@ -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)
})
}

View File

@ -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)
})
}

View File

@ -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)
})
}

View File

@ -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)
})
}

View File

@ -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)
})
}

60
esp-storage/src/lib.rs Normal file
View File

@ -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<R>(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<R>(f: impl FnOnce() -> R) -> R {
f()
}

56
esp-storage/src/ll.rs Normal file
View File

@ -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),
}
}

View File

@ -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::<FlashWordBuffer>::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::<FlashWordBuffer>::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::<FlashSectorBuffer>::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::<FlashSectorBuffer>::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<const ALIGN: u32, const MAX_OFF: u32, const MAX_LEN: u32>(
aligned: Option<bool>,
) -> impl Iterator<Item = (u32, u32)> {
(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::<WORD_SIZE, MAX_OFFSET, MAX_LENGTH>(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::<WORD_SIZE, MAX_OFFSET, MAX_LENGTH>(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::<WORD_SIZE, MAX_OFFSET, MAX_LENGTH>(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::<WORD_SIZE, MAX_OFFSET, MAX_LENGTH>(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::<WORD_SIZE, MAX_OFFSET, MAX_LENGTH>(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]
);
}
}
}

View File

@ -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::<FlashSectorBuffer>::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(&sector_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::<FlashSectorBuffer>::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, &sector_data[..])?;
aligned_offset += Self::SECTOR_SIZE;
data_offset = 0;
bytes = &bytes[len..];
}
Ok(())
}
}

107
esp-storage/src/stub.rs Normal file
View File

@ -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<const ALIGN: u32, const SIZE: u32, const MAX_LEN: u32>(
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::<WORD_SIZE, FLASH_SIZE, SECTOR_SIZE>(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::<WORD_SIZE, FLASH_SIZE, SECTOR_SIZE>(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
}
}

View File

@ -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"]

View File

@ -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 {}
}

View File

@ -21,3 +21,6 @@ DEFMT_LOG = "info"
[unstable]
build-std = ["core"]
[net]
git-fetch-with-cli = true

View File

@ -29,6 +29,7 @@ pub enum Package {
EspMetadata,
EspPrintln,
EspRiscvRt,
EspStorage,
EspWifi,
Examples,
HilTest,

View File

@ -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 => {}