Files
Embedded-Hacking/drivers/0x0f_flash_rust/src/flash.rs
T
2026-04-02 11:36:06 -04:00

210 lines
6.4 KiB
Rust

//! @file flash.rs
//! @brief Pure-logic flash driver (host-testable, no HAL)
//! @author Kevin Thomas
//! @date 2025
//!
//! MIT License
//!
//! Copyright (c) 2025 Kevin Thomas
//!
//! Permission is hereby granted, free of charge, to any person obtaining a copy
//! of this software and associated documentation files (the "Software"), to deal
//! in the Software without restriction, including without limitation the rights
//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//! copies of the Software, and to permit persons to whom the Software is
//! furnished to do so, subject to the following conditions:
//!
//! The above copyright notice and this permission notice shall be included in
//! all copies or substantial portions of the Software.
//!
//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//! SOFTWARE.
/// Total on-chip flash size in bytes (4 MB).
pub const FLASH_SIZE_BYTES: u32 = 4 * 1024 * 1024;
/// Flash sector size in bytes (4096).
pub const FLASH_SECTOR_SIZE: u32 = 4096;
/// Flash page size in bytes (256).
pub const FLASH_PAGE_SIZE: u32 = 256;
/// Flash target offset: last sector of flash.
pub const FLASH_TARGET_OFFSET: u32 = FLASH_SIZE_BYTES - FLASH_SECTOR_SIZE;
/// Write buffer length matching the C demo (one page = 256 bytes).
pub const FLASH_WRITE_LEN: usize = FLASH_PAGE_SIZE as usize;
/// XIP base address where flash is memory-mapped.
pub const XIP_BASE: u32 = 0x1000_0000;
/// Demo string written to flash, matching the C demo exactly.
pub const DEMO_MSG: &[u8] = b"Embedded Hacking flash driver demo";
/// Prepare a write buffer with 0xFF fill and the demo string at the start.
///
/// Mirrors the C demo's `_prepare_write_buf()` function. The buffer is
/// filled with 0xFF (erased flash state), then the demo string including
/// its NUL terminator is copied to the beginning.
///
/// # Arguments
///
/// * `buf` - Output buffer (should be `FLASH_WRITE_LEN` bytes).
///
/// # Returns
///
/// Number of meaningful bytes (string length + NUL terminator).
pub fn prepare_write_buf(buf: &mut [u8]) -> usize {
for b in buf.iter_mut() {
*b = 0xFF;
}
let msg_with_nul = DEMO_MSG.len() + 1;
let n = msg_with_nul.min(buf.len());
let copy_len = DEMO_MSG.len().min(n);
buf[..copy_len].copy_from_slice(&DEMO_MSG[..copy_len]);
if copy_len < n {
buf[copy_len] = 0x00;
}
n
}
/// Format the "Flash readback: <string>\r\n" message into `buf`.
///
/// Reads from `read_data` up to the first NUL byte (or end of slice)
/// and formats the output message matching the C demo's printf.
///
/// # Arguments
///
/// * `buf` - Output buffer (should be >= 64 bytes).
/// * `read_data` - Data read back from flash.
///
/// # Returns
///
/// Number of bytes written to `buf`.
pub fn format_readback(buf: &mut [u8], read_data: &[u8]) -> usize {
let prefix = b"Flash readback: ";
let suffix = b"\r\n";
let mut pos = copy_slice(buf, 0, prefix);
pos += copy_c_string(&mut buf[pos..], read_data);
pos += copy_slice(buf, pos, suffix);
pos
}
/// Copy a byte slice into `buf` at the given offset, returning bytes written.
fn copy_slice(buf: &mut [u8], offset: usize, src: &[u8]) -> usize {
let n = src.len().min(buf.len().saturating_sub(offset));
buf[offset..offset + n].copy_from_slice(&src[..n]);
n
}
/// Copy bytes from `data` up to the first NUL into `buf`.
fn copy_c_string(buf: &mut [u8], data: &[u8]) -> usize {
let str_len = data.iter().position(|&b| b == 0).unwrap_or(data.len());
let n = str_len.min(buf.len());
buf[..n].copy_from_slice(&data[..n]);
n
}
#[cfg(test)]
mod tests {
// Import all parent module items
use super::*;
#[test]
fn flash_size_is_4mb() {
assert_eq!(FLASH_SIZE_BYTES, 4 * 1024 * 1024);
}
#[test]
fn sector_size_is_4096() {
assert_eq!(FLASH_SECTOR_SIZE, 4096);
}
#[test]
fn page_size_is_256() {
assert_eq!(FLASH_PAGE_SIZE, 256);
}
#[test]
fn target_offset_is_last_sector() {
assert_eq!(FLASH_TARGET_OFFSET, FLASH_SIZE_BYTES - FLASH_SECTOR_SIZE);
}
#[test]
fn write_len_matches_page_size() {
assert_eq!(FLASH_WRITE_LEN, 256);
}
#[test]
fn xip_base_address() {
assert_eq!(XIP_BASE, 0x1000_0000);
}
#[test]
fn prepare_write_buf_fills_0xff() {
let mut buf = [0u8; FLASH_WRITE_LEN];
prepare_write_buf(&mut buf);
// Bytes after the string + NUL should be 0xFF
let after = DEMO_MSG.len() + 1;
for &b in &buf[after..] {
assert_eq!(b, 0xFF);
}
}
#[test]
fn prepare_write_buf_has_demo_string() {
let mut buf = [0u8; FLASH_WRITE_LEN];
prepare_write_buf(&mut buf);
assert_eq!(&buf[..DEMO_MSG.len()], DEMO_MSG);
}
#[test]
fn prepare_write_buf_has_nul_terminator() {
let mut buf = [0u8; FLASH_WRITE_LEN];
prepare_write_buf(&mut buf);
assert_eq!(buf[DEMO_MSG.len()], 0x00);
}
#[test]
fn format_readback_matches_c_output() {
let mut read_data = [0xFFu8; FLASH_WRITE_LEN];
let msg = b"Embedded Hacking flash driver demo";
read_data[..msg.len()].copy_from_slice(msg);
read_data[msg.len()] = 0x00;
let mut buf = [0u8; 128];
let n = format_readback(&mut buf, &read_data);
assert_eq!(
&buf[..n],
b"Flash readback: Embedded Hacking flash driver demo\r\n"
);
}
#[test]
fn format_readback_empty_string() {
let read_data = [0u8; 8];
let mut buf = [0u8; 64];
let n = format_readback(&mut buf, &read_data);
assert_eq!(&buf[..n], b"Flash readback: \r\n");
}
#[test]
fn format_readback_no_nul() {
let read_data = [b'A'; 8];
let mut buf = [0u8; 64];
let n = format_readback(&mut buf, &read_data);
assert_eq!(&buf[..n], b"Flash readback: AAAAAAAA\r\n");
}
#[test]
fn demo_msg_matches_c_string() {
assert_eq!(DEMO_MSG, b"Embedded Hacking flash driver demo");
}
}
// End of file