diff --git a/Cargo.toml b/Cargo.toml index 78da824..79e94a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,8 @@ rand_core = "0.6.3" sdio-host = "0.5.0" embassy-hal-internal = "0.2.0" +embedded-storage = "0.3.1" + [build-dependencies] ch32-metapac = { features = [ "metadata", diff --git a/examples/ch32v208/Cargo.toml b/examples/ch32v208/Cargo.toml index 558944b..c16b9a5 100644 --- a/examples/ch32v208/Cargo.toml +++ b/examples/ch32v208/Cargo.toml @@ -22,6 +22,7 @@ qingke-rt = "*" qingke = "*" panic-halt = "1.0" +embedded-storage = "0.3.1" [profile.release] strip = false # symbols are not flashed to the microcontroller, so don't strip them. diff --git a/examples/ch32v208/src/bin/flash.rs b/examples/ch32v208/src/bin/flash.rs new file mode 100644 index 0000000..083fdf4 --- /dev/null +++ b/examples/ch32v208/src/bin/flash.rs @@ -0,0 +1,74 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#![feature(impl_trait_in_assoc_type)] + +use ch32_hal::flash::{Flash, FLASH_BASE}; +use ch32_hal::rcc::*; +use ch32_hal::{interrupt, println}; +use embassy_executor::Spawner; +use embassy_time::Timer; +use panic_halt as _; + +#[embassy_executor::main(entry = "qingke_rt::entry")] +async fn main(_spawner: Spawner) -> ! { + // NOTE: TRM, 3.2: + // > When carrying out FLASH-related operations, it is strongly recommended that the system main + // > frequency is not greater than 120M. + // Here we configure the clock at 60MHz + let p = ch32_hal::init(ch32_hal::Config { + rcc: ch32_hal::rcc::Config { + hse: None, + sys: Sysclk::PLL, + pll_src: PllSource::HSI, + pll: Some(Pll { + prediv: PllPreDiv::DIV2, + mul: PllMul::MUL15, + }), + pllx: None, + ahb_pre: AHBPrescaler::DIV1, + apb1_pre: APBPrescaler::DIV1, + apb2_pre: APBPrescaler::DIV1, + ls: LsConfig::default(), + hspll_src: HsPllSource::HSI, + hspll: None, + }, + dma_interrupt_priority: interrupt::Priority::P0, + }); + ch32_hal::debug::SDIPrint::enable(); + + // Give a chance to catch CPU if it craps itself writing to FLASH + Timer::after_millis(1000).await; + + let mut f = Flash::new_blocking(p.FLASH); + const size: u32 = 256; + let start: u32 = 1024 * 32; + let stop = start + 256 * 3; + for offset in (start..stop).step_by(size as usize) { + println!("Testing offset: {:#X}, size: {:#X}", offset, size); + + println!("Reading..."); + let mut buf = [0u8; 32]; + println!("{:?}", f.blocking_read(offset, &mut buf)); + println!("Read: {:?}", buf); + + println!("Erasing..."); + println!("{:?}", f.blocking_erase(offset, offset + size)); + + println!("Reading..."); + let mut buf = [0u8; 32]; + println!("{:?}", f.blocking_read(offset, &mut buf)); + println!("Read after erase: {:?}", buf); + + println!("Writing..."); + println!("{:?}", f.blocking_write(offset, &[0xabu8; size as usize])); + + println!("Reading..."); + let mut buf = [0u8; 32]; + println!("{:?}", f.blocking_read(offset, &mut buf)); + println!("Read: {:?}", buf); + } + + println!("Blocking tests done."); + loop {} +} diff --git a/examples/ch32v307/src/bin/flash.rs b/examples/ch32v307/src/bin/flash.rs new file mode 100644 index 0000000..083fdf4 --- /dev/null +++ b/examples/ch32v307/src/bin/flash.rs @@ -0,0 +1,74 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#![feature(impl_trait_in_assoc_type)] + +use ch32_hal::flash::{Flash, FLASH_BASE}; +use ch32_hal::rcc::*; +use ch32_hal::{interrupt, println}; +use embassy_executor::Spawner; +use embassy_time::Timer; +use panic_halt as _; + +#[embassy_executor::main(entry = "qingke_rt::entry")] +async fn main(_spawner: Spawner) -> ! { + // NOTE: TRM, 3.2: + // > When carrying out FLASH-related operations, it is strongly recommended that the system main + // > frequency is not greater than 120M. + // Here we configure the clock at 60MHz + let p = ch32_hal::init(ch32_hal::Config { + rcc: ch32_hal::rcc::Config { + hse: None, + sys: Sysclk::PLL, + pll_src: PllSource::HSI, + pll: Some(Pll { + prediv: PllPreDiv::DIV2, + mul: PllMul::MUL15, + }), + pllx: None, + ahb_pre: AHBPrescaler::DIV1, + apb1_pre: APBPrescaler::DIV1, + apb2_pre: APBPrescaler::DIV1, + ls: LsConfig::default(), + hspll_src: HsPllSource::HSI, + hspll: None, + }, + dma_interrupt_priority: interrupt::Priority::P0, + }); + ch32_hal::debug::SDIPrint::enable(); + + // Give a chance to catch CPU if it craps itself writing to FLASH + Timer::after_millis(1000).await; + + let mut f = Flash::new_blocking(p.FLASH); + const size: u32 = 256; + let start: u32 = 1024 * 32; + let stop = start + 256 * 3; + for offset in (start..stop).step_by(size as usize) { + println!("Testing offset: {:#X}, size: {:#X}", offset, size); + + println!("Reading..."); + let mut buf = [0u8; 32]; + println!("{:?}", f.blocking_read(offset, &mut buf)); + println!("Read: {:?}", buf); + + println!("Erasing..."); + println!("{:?}", f.blocking_erase(offset, offset + size)); + + println!("Reading..."); + let mut buf = [0u8; 32]; + println!("{:?}", f.blocking_read(offset, &mut buf)); + println!("Read after erase: {:?}", buf); + + println!("Writing..."); + println!("{:?}", f.blocking_write(offset, &[0xabu8; size as usize])); + + println!("Reading..."); + let mut buf = [0u8; 32]; + println!("{:?}", f.blocking_read(offset, &mut buf)); + println!("Read: {:?}", buf); + } + + println!("Blocking tests done."); + loop {} +} diff --git a/src/flash/common.rs b/src/flash/common.rs new file mode 100644 index 0000000..486e574 --- /dev/null +++ b/src/flash/common.rs @@ -0,0 +1,226 @@ +#[allow(dead_code)] +use core::marker::PhantomData; +use core::sync::atomic::{fence, Ordering}; + +use embassy_hal_internal::drop::OnDrop; +use embassy_hal_internal::{into_ref, PeripheralRef}; +use embedded_storage::nor_flash::ReadNorFlash; + +use super::{family, Async, Blocking, Error, FlashSector, FLASH_SIZE, READ_SIZE, WRITE_SIZE}; +use crate::peripherals::FLASH; +use crate::Peripheral; + +// FIXME! the pac incorrectly defines it to 0 (wich is the mapped execute address). +pub const FLASH_BASE: usize = 0x0800_0000; +// when the pac is fixed, remove this const and use +// use crate::pac::FLASH_BASE; + +/// Internal flash memory driver. +pub struct Flash<'d, MODE = Async> { + pub(crate) inner: PeripheralRef<'d, FLASH>, + pub(crate) _mode: PhantomData, +} + +impl<'d> Flash<'d, Blocking> { + /// Create a new flash driver, usable in blocking mode. + pub fn new_blocking(p: impl Peripheral

+ 'd) -> Self { + into_ref!(p); + + Self { + inner: p, + _mode: PhantomData, + } + } +} + +impl<'d, MODE> Flash<'d, MODE> { + /// Blocking read. + /// + /// NOTE: `offset` is an offset from the flash start, NOT an absolute address. + /// For example, to read address `0x0800_1234` you have to use offset `0x1234`. + pub fn blocking_read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Error> { + blocking_read(FLASH_BASE as u32, FLASH_SIZE as u32, offset, bytes) + } + + /// Blocking write. + /// + /// NOTE: `offset` is an offset from the flash start, NOT an absolute address. + /// For example, to write address `0x0800_1234` you have to use offset `0x1234`. + pub fn blocking_write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Error> { + unsafe { + blocking_write( + FLASH_BASE as u32, + FLASH_SIZE as u32, + offset, + bytes, + write_chunk_unlocked, + ) + } + } + + /// Blocking erase. + /// + /// NOTE: `from` and `to` are offsets from the flash start, NOT an absolute address. + /// For example, to erase address `0x0801_0000` you have to use offset `0x1_0000`. + pub fn blocking_erase(&mut self, from: u32, to: u32) -> Result<(), Error> { + unsafe { blocking_erase(FLASH_BASE as u32, from, to, erase_sector_unlocked) } + } +} + +pub(super) fn blocking_read(base: u32, size: u32, offset: u32, bytes: &mut [u8]) -> Result<(), Error> { + if offset + bytes.len() as u32 > size { + return Err(Error::Size); + } + + let start_address = base + offset; + + #[cfg(flash_f4)] + family::assert_not_corrupted_read(start_address + bytes.len() as u32); + + let flash_data = unsafe { core::slice::from_raw_parts(start_address as *const u8, bytes.len()) }; + bytes.copy_from_slice(flash_data); + Ok(()) +} + +pub(super) unsafe fn blocking_write( + base: u32, + size: u32, + offset: u32, + bytes: &[u8], + write_chunk: unsafe fn(u32, &[u8]) -> Result<(), Error>, +) -> Result<(), Error> { + if offset + bytes.len() as u32 > size { + return Err(Error::Size); + } + if offset % WRITE_SIZE as u32 != 0 || bytes.len() % WRITE_SIZE != 0 { + return Err(Error::Unaligned); + } + + let mut address = base + offset; + + for chunk in bytes.chunks(WRITE_SIZE) { + write_chunk(address, chunk)?; + address += WRITE_SIZE as u32; + } + Ok(()) +} + +pub(super) unsafe fn write_chunk_unlocked(address: u32, chunk: &[u8]) -> Result<(), Error> { + family::clear_all_err(); + fence(Ordering::SeqCst); + family::unlock(); + fence(Ordering::SeqCst); + family::enable_blocking_write(); + fence(Ordering::SeqCst); + + let _on_drop = OnDrop::new(|| { + family::disable_blocking_write(); + fence(Ordering::SeqCst); + family::lock(); + }); + + family::blocking_write(address, unwrap!(chunk.try_into())) +} + +pub(super) unsafe fn write_chunk_with_critical_section(address: u32, chunk: &[u8]) -> Result<(), Error> { + critical_section::with(|_| write_chunk_unlocked(address, chunk)) +} + +pub(super) unsafe fn blocking_erase( + base: u32, + from: u32, + to: u32, + erase_sector: unsafe fn(&FlashSector) -> Result<(), Error>, +) -> Result<(), Error> { + let start_address = base + from; + let end_address = base + to; + + ensure_sector_aligned(start_address, end_address)?; + family::unlock(); + let _on_drop = OnDrop::new(|| { + family::lock(); + }); + + trace!("Erasing from 0x{:x} to 0x{:x}", start_address, end_address); + + let mut address = start_address; + while address < end_address { + let sector = get_sector(address); + trace!("Erasing sector: {:?}", sector); + erase_sector(§or)?; + address += sector.size; + } + Ok(()) +} + +pub(super) unsafe fn erase_sector_unlocked(sector: &FlashSector) -> Result<(), Error> { + family::clear_all_err(); + fence(Ordering::SeqCst); + family::unlock(); + fence(Ordering::SeqCst); + + let _on_drop = OnDrop::new(|| family::lock()); + + family::blocking_erase_sector(sector) +} + +pub(super) unsafe fn erase_sector_with_critical_section(sector: &FlashSector) -> Result<(), Error> { + critical_section::with(|_| erase_sector_unlocked(sector)) +} + +pub(super) fn get_sector(address: u32) -> FlashSector { + let mut bank_offset = 0; + let index_in_region = (address - FLASH_BASE as u32) / WRITE_SIZE as u32; + if (address as usize) < (FLASH_BASE + FLASH_SIZE) { + return FlashSector { + start: FLASH_BASE as u32 + index_in_region * WRITE_SIZE as u32, + size: WRITE_SIZE as u32, + }; + } + + panic!("Flash sector not found"); +} + +pub(super) fn ensure_sector_aligned(start_address: u32, end_address: u32) -> Result<(), Error> { + let mut address = start_address; + while address < end_address { + let sector = get_sector(address); + if sector.start != address { + return Err(Error::Unaligned); + } + address += sector.size; + } + if address != end_address { + return Err(Error::Unaligned); + } + Ok(()) +} + +impl embedded_storage::nor_flash::ErrorType for Flash<'_, MODE> { + type Error = Error; +} + +impl embedded_storage::nor_flash::ReadNorFlash for Flash<'_, MODE> { + const READ_SIZE: usize = READ_SIZE; + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Error> { + self.blocking_read(offset, bytes) + } + + fn capacity(&self) -> usize { + FLASH_SIZE + } +} + +impl embedded_storage::nor_flash::NorFlash for Flash<'_, MODE> { + const WRITE_SIZE: usize = WRITE_SIZE; + const ERASE_SIZE: usize = WRITE_SIZE; + + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Error> { + self.blocking_write(offset, bytes) + } + + fn erase(&mut self, from: u32, to: u32) -> Result<(), Error> { + self.blocking_erase(from, to) + } +} diff --git a/src/flash/mod.rs b/src/flash/mod.rs new file mode 100644 index 0000000..248b4d6 --- /dev/null +++ b/src/flash/mod.rs @@ -0,0 +1,58 @@ +//! Flash memory (FLASH) +use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind}; + +mod common; +pub use common::*; + +pub use crate::pac::{FLASH_SIZE, WRITE_SIZE}; + +/// Read size (always 1) +pub const READ_SIZE: usize = 1; + +/// Blocking flash mode typestate. +pub enum Blocking {} +/// Async flash mode typestate. +pub enum Async {} + +/// Flash sector. +#[derive(Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct FlashSector { + /// Absolute start address. + pub start: u32, + /// Size in bytes. + pub size: u32, +} + +#[cfg_attr(flash_v3, path = "v3.rs")] +#[cfg_attr(not(flash_v3), path = "other.rs")] +mod family; + +#[allow(unused_imports)] +pub use family::*; + +/// Flash error +/// +/// See STM32 Reference Manual for your chip for details. +#[allow(missing_docs)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + Prog, + Size, + Miss, + Seq, + Protected, + Unaligned, + Parallelism, +} + +impl NorFlashError for Error { + fn kind(&self) -> NorFlashErrorKind { + match self { + Self::Size => NorFlashErrorKind::OutOfBounds, + Self::Unaligned => NorFlashErrorKind::NotAligned, + _ => NorFlashErrorKind::Other, + } + } +} diff --git a/src/flash/other.rs b/src/flash/other.rs new file mode 100644 index 0000000..d63ac4b --- /dev/null +++ b/src/flash/other.rs @@ -0,0 +1,25 @@ +#![allow(unused)] + +use super::{Error, FlashSector, WRITE_SIZE}; + +pub(crate) unsafe fn lock() { + unimplemented!(); +} +pub(crate) unsafe fn unlock() { + unimplemented!(); +} +pub(crate) unsafe fn enable_blocking_write() -> Result<(), Error> { + unimplemented!(); +} +pub(crate) unsafe fn disable_blocking_write() { + unimplemented!(); +} +pub(crate) unsafe fn blocking_write(_start_address: u32, _buf: &[u8; WRITE_SIZE]) -> Result<(), Error> { + unimplemented!(); +} +pub(crate) unsafe fn blocking_erase_sector(_sector: &FlashSector) -> Result<(), Error> { + unimplemented!(); +} +pub(crate) unsafe fn clear_all_err() { + unimplemented!(); +} diff --git a/src/flash/v3.rs b/src/flash/v3.rs new file mode 100644 index 0000000..fe1be2c --- /dev/null +++ b/src/flash/v3.rs @@ -0,0 +1,108 @@ +use core::ptr::write_volatile; +use core::sync::atomic::{fence, AtomicBool, Ordering}; + +use super::{FlashSector, WRITE_SIZE}; +use crate::flash::Error; +use crate::pac; + +pub(crate) unsafe fn lock() { + pac::FLASH.ctlr().modify(|w| { + w.set_lock(true); + w.set_flock(true); + }); +} + +pub(crate) unsafe fn unlock() { + if pac::FLASH.ctlr().read().lock() { + pac::FLASH.keyr().write(|w| w.set_keyr(0x4567_0123)); + fence(Ordering::SeqCst); + pac::FLASH.keyr().write(|w| w.set_keyr(0xCDEF_89AB)); + fence(Ordering::SeqCst); + } + if pac::FLASH.ctlr().read().flock() { + pac::FLASH.modekeyr().write(|w| w.set_modekeyr(0x4567_0123)); + fence(Ordering::SeqCst); + pac::FLASH.modekeyr().write(|w| w.set_modekeyr(0xCDEF_89AB)); + fence(Ordering::SeqCst); + } + // let _ctlr = pac::FLASH.ctlr().read(); +} + +pub(crate) unsafe fn enable_blocking_write() { + assert_eq!(0, WRITE_SIZE % 4); + + pac::FLASH.ctlr().modify(|w| { + w.set_page_pg(true); + }); +} + +pub(crate) unsafe fn disable_blocking_write() { + pac::FLASH.ctlr().modify(|w| w.set_pg(false)); +} + +pub(crate) unsafe fn blocking_write(start_address: u32, buf: &[u8; WRITE_SIZE]) -> Result<(), Error> { + blocking_wait_ready()?; + blocking_wait_ready_write()?; + write_start(start_address, buf); + blocking_wait_ready() +} + +unsafe fn write_start(start_address: u32, buf: &[u8; WRITE_SIZE]) { + let mut address = start_address; + for val in buf.chunks(4) { + write_volatile(address as *mut u32, u32::from_le_bytes(unwrap!(val.try_into()))); + address += val.len() as u32; + fence(Ordering::SeqCst); + let _ = blocking_wait_ready_write(); + fence(Ordering::SeqCst); + } + pac::FLASH.ctlr().modify(|w| w.set_pgstart(true)); +} + +pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), Error> { + blocking_wait_ready()?; + pac::FLASH.ctlr().modify(|w| w.set_page_er(true)); + pac::FLASH.addr().write(|w| w.set_far(sector.start)); + fence(Ordering::SeqCst); + pac::FLASH.ctlr().modify(|w| { + // w.set_page_er(true); + w.set_strt(true); + }); + + let ret: Result<(), Error> = blocking_wait_ready(); + pac::FLASH.ctlr().modify(|w| w.set_page_er(false)); + clear_all_err(); + ret +} + +pub(crate) unsafe fn clear_all_err() { + pac::FLASH.statr().modify(|w| w.set_wrprterr(true)); +} + +unsafe fn blocking_wait_ready() -> Result<(), Error> { + loop { + let sr = pac::FLASH.statr().read(); + + if !sr.bsy() { + return get_result(); + } + } +} + +unsafe fn blocking_wait_ready_write() -> Result<(), Error> { + loop { + let sr = pac::FLASH.statr().read(); + + if !sr.wr_bsy() { + return get_result(); + } + } +} + +fn get_result() -> Result<(), Error> { + if pac::FLASH.statr().read().wrprterr() { + Err(Error::Protected) + } else { + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 997da34..281d9b7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -89,6 +89,9 @@ pub mod usbhs; #[cfg(usbpd)] pub mod usbpd; +#[cfg(flash)] +pub mod flash; + #[cfg(feature = "embassy")] pub mod embassy;