Skip to content

Commit

Permalink
feat: implement FLC (#6)
Browse files Browse the repository at this point in the history
* wip: initial FLC code

* wip: add buggy FLC - need to investigate hard faults

* fix: remove flashprog linkage by default

* feat: add page number calculation

* chore: bump version to 0.5.0
  • Loading branch information
WhiteHoodHacker authored Jan 29, 2025
1 parent 29f412c commit cf640d2
Show file tree
Hide file tree
Showing 6 changed files with 378 additions and 3 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "max7800x-hal"
version = "0.4.0"
version = "0.5.0"
authors = ["SIGPwny <hello@sigpwny.com>", "Minh Duong <hello@whitehoodhacker.net>"]
edition = "2021"
license = "MIT OR Apache-2.0"
Expand All @@ -26,5 +26,7 @@ rand_core = { version = "0.6.4", default-features = false, optional = true }

[features]
default = ["rand", "rt"]
# Enabling this adds the `.flashprog` section header to critical flash programming functions for custom linkage
flashprog-linkage = []
rand = ["dep:rand", "dep:rand_core"]
rt = ["max78000-pac/critical-section", "max78000-pac/rt"]
365 changes: 365 additions & 0 deletions src/flc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,365 @@
//! # Flash Controller (FLC)
use crate::gcr::clocks::{Clock, SystemClock};

/// Base address of the flash memory.
pub const FLASH_BASE: u32 = 0x1000_0000;
/// Size of the flash memory.
pub const FLASH_SIZE: u32 = 0x0008_0000;
/// End address of the flash memory.
pub const FLASH_END: u32 = FLASH_BASE + FLASH_SIZE;
/// Number of flash pages.
pub const FLASH_PAGE_COUNT: u32 = 64;
/// Size of a flash page.
pub const FLASH_PAGE_SIZE: u32 = 0x2000;

/// Flash controller errors.
#[derive(Debug, PartialEq)]
pub enum FlashError {
/// The target address to write or erase is invalid.
InvalidAddress,
/// The flash controller was busy or locked when attempting to write or erase.
AccessViolation,
/// Writing over the old data with new data would cause 0 -> 1 bit transitions.
/// The target address must be erased before writing new data.
NeedsErase,
}

/// # Flash Controller (FLC) Peripheral
///
/// The flash controller manages read, write, and erase accesses to the
/// internal flash and provides the following features:
/// - Up to 512 KiB of flash memory
/// - 64 pages (8192 bytes per page)
/// - 2048 words by 128 bits per page
/// - 128-bit write granularity
/// - Page erase and mass erase
/// - Read and write protection
///
/// Example:
/// ```
/// let flc = Flc::new(p.flc, sys_clk);
///
/// // Erase page number 48
/// unsafe { flc.erase_page(0x1006_0000).unwrap(); }
/// // Read the value at address 0x1006_0004
/// let data: u32 = flc.read_32(0x1006_0004).unwrap();
/// // Should be 0xFFFFFFFF since flash defaults to all 1's
/// assert_eq!(data, 0xFFFF_FFFF);
///
/// // Write a value to address 0x1006_0004
/// flc.write_32(0x1006_0004, 0x7856_3412).unwrap();
/// // Read the data back from flash memory
/// let new_data: u32 = flc.read_32(0x1006_0004).unwrap();
/// assert_eq!(new_data, 0x7856_3412);
/// ```
pub struct Flc {
flc: crate::pac::Flc,
sys_clk: Clock<SystemClock>,
}

impl Flc {
/// Construct a new flash controller peripheral.
pub fn new(flc: crate::pac::Flc, sys_clk: Clock<SystemClock>) -> Self {
let s = Self { flc, sys_clk };
s.config();
s
}

/// Configure the flash controller.
#[inline]
fn config(&self) {
// Wait until the flash controller is not busy
while self.is_busy() {}
// Set FLC divisor
let flc_div = self.sys_clk.frequency / 1_000_000;
self.flc.clkdiv().modify(|_, w| unsafe {
w.clkdiv().bits(flc_div as u8)
});
// Clear stale interrupts
if self.flc.intr().read().af().bit_is_set() {
self.flc.intr().write(|w| w.af().clear_bit());
}
}

/// Check if the flash controller is busy.
#[inline]
pub fn is_busy(&self) -> bool {
let ctrl = self.flc.ctrl().read();
ctrl.pend().is_busy() ||
ctrl.pge().bit_is_set() ||
ctrl.me().bit_is_set() ||
ctrl.wr().bit_is_set()
}

/// Check if an address is within the valid flash memory range.
#[inline]
pub fn check_address(&self, address: u32) -> Result<(), FlashError> {
if address < FLASH_BASE || address >= FLASH_END {
return Err(FlashError::InvalidAddress);
}
Ok(())
}

/// Get the page number of a flash address.
#[inline]
pub fn get_page_number(&self, address: u32) -> Result<u32, FlashError> {
self.check_address(address)?;
let page_num = (address >> 13) & (FLASH_PAGE_COUNT - 1);
// Check for invalid page number (redundant check)
if page_num >= FLASH_PAGE_COUNT {
return Err(FlashError::InvalidAddress);
}
Ok(page_num)
}

/// Set the target address for a write or erase operation.
#[inline]
fn set_address(&self, address: u32) -> Result<(), FlashError> {
self.check_address(address)?;
// Convert to physical address
let phys_addr = address & (FLASH_SIZE - 1);
// Safety: We have validated the address already
self.flc.addr().write(|w| unsafe {
w.addr().bits(phys_addr)
});
Ok(())
}

/// Unlock the flash controller to allow write or erase operations.
#[inline]
fn unlock_flash(&self) {
self.flc.ctrl().modify(|_, w| w.unlock().unlocked());
while self.flc.ctrl().read().unlock().is_locked() {}
}

/// Lock the flash controller to prevent write or erase operations.
#[inline]
fn lock_flash(&self) {
self.flc.ctrl().modify(|_, w| w.unlock().locked());
while self.flc.ctrl().read().unlock().is_unlocked() {}
}

/// Commit a write operation.
#[cfg_attr(feature = "flashprog-linkage", link_section = ".flashprog")]
#[inline]
fn commit_write(&self) {
self.flc.ctrl().modify(|_, w| w.wr().start());
while !self.flc.ctrl().read().wr().is_complete() {}
while self.is_busy() {}
}

/// Commit a page erase operation.
#[cfg_attr(feature = "flashprog-linkage", link_section = ".flashprog")]
#[inline]
fn commit_erase(&self) {
self.flc.ctrl().modify(|_, w| w.pge().start());
while !self.flc.ctrl().read().pge().is_complete() {}
while self.is_busy() {}
}

/// Write a 128-bit word to flash memory. This is an internal function to
/// be used by all other write functions.
#[doc(hidden)]
#[cfg_attr(feature = "flashprog-linkage", link_section = ".flashprog")]
#[inline(never)]
fn _write_128(&self, address: u32, data: &[u32; 4]) -> Result<(), FlashError> {
// Target address must be 128-bit aligned
if address & 0b1111 != 0 {
return Err(FlashError::InvalidAddress);
}
self.check_address(address)?;
// Ensure that the flash controller is configured
self.config();
// Verify that only 1 -> 0 transitions are being made by reading the existing data at the target address
for i in 0..4 {
// Safety: We have checked the address already
let old_data = unsafe { core::ptr::read_volatile((address + i * 4) as *const u32) };
if (old_data & data[i as usize]) != data[i as usize] {
return Err(FlashError::NeedsErase);
}
}
self.set_address(address)?;
// Safety: Data can be written to all bits of the data registers
unsafe {
self.flc.data(0).write(|w| w.data().bits(data[0]));
self.flc.data(1).write(|w| w.data().bits(data[1]));
self.flc.data(2).write(|w| w.data().bits(data[2]));
self.flc.data(3).write(|w| w.data().bits(data[3]));
}
self.unlock_flash();
// Commit the write operation
self.commit_write();
self.lock_flash();
// Check for access violation
if self.flc.intr().read().af().bit_is_set() {
self.flc.intr().write(|w| w.af().clear_bit());
return Err(FlashError::AccessViolation);
}
Ok(())
}

/// Erases a page in flash memory.
#[doc(hidden)]
#[cfg_attr(feature = "flashprog-linkage", link_section = ".flashprog")]
#[inline(never)]
fn _erase_page(&self, address: u32) -> Result<(), FlashError> {
while self.is_busy() {}
self.set_address(address)?;
self.unlock_flash();
// Set erase page code
self.flc.ctrl().modify(|_, w| w.erase_code().erase_page());
// Commit the erase operation
self.commit_erase();
self.lock_flash();
// Check for access violation
if self.flc.intr().read().af().bit_is_set() {
self.flc.intr().write(|w| w.af().clear_bit());
return Err(FlashError::AccessViolation);
}
Ok(())
}

/// Writes four [`u32`] to flash memory. Uses little-endian byte order.
/// The lowest [`u32`] in the array is written to the lowest address in flash.
/// The target address must be 128-bit aligned.
///
/// Example:
/// ```
/// let data: [u32; 4] = [0x0403_0201, 0x0807_0605, 0x0C0B_0A09, 0x100F_0E0D];
/// flash.write_128(0x1006_0000, &data).unwrap();
/// // The bytes in flash will look like:
/// // 10060000: 0102 0304 0506 0708 090A 0B0C 0D0E 0F10
/// ```
pub fn write_128(&self, address: u32, data: &[u32; 4]) -> Result<(), FlashError> {
self._write_128(address, &data)
}

/// Write a [`u32`] to flash memory. Uses little-endian byte order.
/// The target address must be 32-bit aligned.
///
/// Note: Writes to flash memory must be done in 128-bit (16-byte) blocks.
/// This function will read the existing 128-bit word containing the target
/// address, modify the 32-bit word within the 128-bit word, and write the
/// modified 128-bit word back to flash memory.
///
/// Example:
/// ```
/// let data: u32 = 0x7856_3412;
/// flash.write_32(0x1006_0004, data).unwrap();
/// // The bytes in flash will look like:
/// // 10060000: FFFF FFFF 1234 5678 FFFF FFFF FFFF FFFF
/// ```
pub fn write_32(&self, address: u32, data: u32) -> Result<(), FlashError> {
// Target address must be 32-bit aligned
if address & 0b11 != 0 {
return Err(FlashError::InvalidAddress);
}
self.check_address(address)?;
let addr_128 = address & !0b1111;
self.check_address(addr_128)?;
let addr_128_ptr = addr_128 as *const u32;
// Read existing data at the 128-bit word containing the target address
let mut prev_data: [u32; 4] = [0xFFFF_FFFF; 4];
// Safety: We have checked the address already
unsafe {
prev_data[0] = core::ptr::read_volatile(addr_128_ptr);
prev_data[1] = core::ptr::read_volatile(addr_128_ptr.offset(1));
prev_data[2] = core::ptr::read_volatile(addr_128_ptr.offset(2));
prev_data[3] = core::ptr::read_volatile(addr_128_ptr.offset(3));
}
// Determine index of the 32-bit word within the 128-bit word
let data_idx = (address & 0b1100) >> 2;
// Modify the 32-bit word within the 128-bit word
prev_data[data_idx as usize] = data;
// Write the modified 128-bit word to flash memory
self._write_128(addr_128, &prev_data)
}

/// Reads four [`u32`] from flash memory. Uses little-endian byte order.
/// The lowest [`u32`] in the array is read from the lowest address in flash.
/// The target address must be 128-bit aligned.
pub fn read_128(&self, address: u32) -> Result<[u32; 4], FlashError> {
// Target address must be 128-bit aligned
if address & 0b1111 != 0 {
return Err(FlashError::InvalidAddress);
}
self.check_address(address)?;
let addr_128_ptr = address as *const u32;
// Safety: We have checked the address already
unsafe {
Ok([
core::ptr::read_volatile(addr_128_ptr),
core::ptr::read_volatile(addr_128_ptr.offset(1)),
core::ptr::read_volatile(addr_128_ptr.offset(2)),
core::ptr::read_volatile(addr_128_ptr.offset(3)),
])
}
}

/// Reads a [`u32`] from flash memory. Uses little-endian byte order.
/// The target address must be 32-bit aligned.
pub fn read_32(&self, address: u32) -> Result<u32, FlashError> {
// Target address must be 32-bit aligned
if address & 0b11 != 0 {
return Err(FlashError::InvalidAddress);
}
self.check_address(address)?;
let addr_32_ptr = address as *const u32;
// Safety: We have checked the address already
unsafe {
Ok(core::ptr::read_volatile(addr_32_ptr))
}
}

/// Erases a page in flash memory.
///
/// # Safety
/// Care must be taken to not erase the page containing the executing code.
pub unsafe fn erase_page(&self, address: u32) -> Result<(), FlashError> {
self._erase_page(address)
}

/// Protects a page in flash memory from write or erase operations.
/// Effective until the next external or power-on reset.
pub fn disable_page_write(&self, address: u32) -> Result<(), FlashError> {
while self.is_busy() {}
let page_num = self.get_page_number(address)?;
// Lock based on page number
if page_num < 32 {
let write_lock_bit = 1 << page_num;
self.flc.welr0().write(|w| unsafe {
w.bits(write_lock_bit)
});
while self.flc.welr0().read().bits() & write_lock_bit == write_lock_bit {}
} else {
let write_lock_bit = 1 << (page_num - 32);
self.flc.welr1().write(|w| unsafe {
w.bits(write_lock_bit)
});
while self.flc.welr1().read().bits() & write_lock_bit == write_lock_bit {}
}
Ok(())
}

/// Protects a page in flash memory from read operations.
/// Effective until the next external or power-on reset.
pub fn disable_page_read(&self, address: u32) -> Result<(), FlashError> {
while self.is_busy() {}
let page_num = self.get_page_number(address)?;
// Lock based on page number
if page_num < 32 {
let read_lock_bit = 1 << page_num;
self.flc.rlr0().write(|w| unsafe {
w.bits(read_lock_bit)
});
while self.flc.rlr0().read().bits() & read_lock_bit == read_lock_bit {}
} else {
let read_lock_bit = 1 << (page_num - 32);
self.flc.rlr1().write(|w| unsafe {
w.bits(read_lock_bit)
});
while self.flc.rlr1().read().bits() & read_lock_bit == read_lock_bit {}
}
Ok(())
}
}
Loading

0 comments on commit cf640d2

Please sign in to comment.