mirror of
https://github.com/esp-rs/esp-hal.git
synced 2025-09-27 12:20:56 +00:00
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:
parent
3b9a4938cb
commit
60e4b882ef
@ -14,6 +14,7 @@ exclude = [
|
||||
"esp-println",
|
||||
"esp-riscv-rt",
|
||||
"esp-wifi",
|
||||
"esp-storage",
|
||||
"examples",
|
||||
"extras/bench-server",
|
||||
"extras/esp-wifishark",
|
||||
|
@ -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
51
esp-storage/Cargo.toml
Normal 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
34
esp-storage/README.md
Normal 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
21
esp-storage/build.rs
Normal 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
161
esp-storage/src/common.rs
Normal 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
287
esp-storage/src/esp32.rs
Normal 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
|
||||
})
|
||||
}
|
38
esp-storage/src/esp32c2.rs
Normal file
38
esp-storage/src/esp32c2.rs
Normal 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)
|
||||
})
|
||||
}
|
38
esp-storage/src/esp32c3.rs
Normal file
38
esp-storage/src/esp32c3.rs
Normal 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)
|
||||
})
|
||||
}
|
38
esp-storage/src/esp32c6.rs
Normal file
38
esp-storage/src/esp32c6.rs
Normal 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)
|
||||
})
|
||||
}
|
38
esp-storage/src/esp32h2.rs
Normal file
38
esp-storage/src/esp32h2.rs
Normal 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)
|
||||
})
|
||||
}
|
38
esp-storage/src/esp32s2.rs
Normal file
38
esp-storage/src/esp32s2.rs
Normal 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)
|
||||
})
|
||||
}
|
46
esp-storage/src/esp32s3.rs
Normal file
46
esp-storage/src/esp32s3.rs
Normal 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
60
esp-storage/src/lib.rs
Normal 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
56
esp-storage/src/ll.rs
Normal 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),
|
||||
}
|
||||
}
|
356
esp-storage/src/nor_flash.rs
Normal file
356
esp-storage/src/nor_flash.rs
Normal 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]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
74
esp-storage/src/storage.rs
Normal file
74
esp-storage/src/storage.rs
Normal 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(§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::<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, §or_data[..])?;
|
||||
|
||||
aligned_offset += Self::SECTOR_SIZE;
|
||||
data_offset = 0;
|
||||
bytes = &bytes[len..];
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
107
esp-storage/src/stub.rs
Normal file
107
esp-storage/src/stub.rs
Normal 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
|
||||
}
|
||||
}
|
@ -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"]
|
||||
|
||||
|
50
examples/src/bin/flash_read_write.rs
Normal file
50
examples/src/bin/flash_read_write.rs
Normal 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 {}
|
||||
}
|
@ -21,3 +21,6 @@ DEFMT_LOG = "info"
|
||||
|
||||
[unstable]
|
||||
build-std = ["core"]
|
||||
|
||||
[net]
|
||||
git-fetch-with-cli = true
|
||||
|
@ -29,6 +29,7 @@ pub enum Package {
|
||||
EspMetadata,
|
||||
EspPrintln,
|
||||
EspRiscvRt,
|
||||
EspStorage,
|
||||
EspWifi,
|
||||
Examples,
|
||||
HilTest,
|
||||
|
@ -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 => {}
|
||||
|
Loading…
x
Reference in New Issue
Block a user