diff --git a/boards/feather_m4/examples/nvm_dsu.rs b/boards/feather_m4/examples/nvm_dsu.rs index e73f2f0d0913..18b21bae49a8 100644 --- a/boards/feather_m4/examples/nvm_dsu.rs +++ b/boards/feather_m4/examples/nvm_dsu.rs @@ -13,7 +13,7 @@ use panic_semihosting as _; use bsp::entry; use ehal::digital::StatefulOutputPin; -use hal::clock::GenericClockController; +use hal::clock::v2::{clock_system_at_reset, pclk::Pclk}; use hal::dsu::Dsu; use hal::nvm::{retrieve_bank_size, Bank, Nvm, WriteGranularity, BLOCKSIZE}; use hal::pac::{interrupt, CorePeripherals, Peripherals}; @@ -23,35 +23,47 @@ use usb_device::bus::UsbBusAllocator; use usb_device::prelude::*; use usbd_serial::{SerialPort, USB_CLASS_CDC}; +use core::sync::atomic; use cortex_m::asm::delay as cycle_delay; use cortex_m::peripheral::NVIC; -use core::sync::atomic; - #[entry] fn main() -> ! { let mut peripherals = Peripherals::take().unwrap(); let mut core = CorePeripherals::take().unwrap(); - let mut clocks = GenericClockController::with_external_32kosc( + + // Clocks configured as they are on reset - Gclk0 at 48Mhz + let (mut buses, clocks, tokens) = clock_system_at_reset( + peripherals.oscctrl, + peripherals.osc32kctrl, peripherals.gclk, - &mut peripherals.mclk, - &mut peripherals.osc32kctrl, - &mut peripherals.oscctrl, + peripherals.mclk, &mut peripherals.nvmctrl, ); + // This is required because the `usb` module have not yet + // been update to use `clock::v2` + let (_, _, _, mut mclk) = unsafe { clocks.pac.steal() }; + let pins = bsp::Pins::new(peripherals.port); let mut red_led = pins.d13.into_push_pull_output(); let mut nvm = Nvm::new(peripherals.nvmctrl); - let mut dsu = Dsu::new(peripherals.dsu, &peripherals.pac).unwrap(); + let ahb_dsu = clocks.ahbs.dsu; + let apb_dsu = clocks.apbs.dsu; + let mut dsu = Dsu::new(peripherals.dsu, ahb_dsu, apb_dsu, &peripherals.pac).unwrap(); + // USB Can be ran off 48Mhz clock, so we can derive the Pclk directly from Gclk0 + + let (pclk_usb, _gclk0) = Pclk::enable(tokens.pclks.usb, clocks.gclk0); let bus_allocator = unsafe { - USB_ALLOCATOR = Some(bsp::usb_allocator( + // Not using the BSP USB constructor as that has not yet + // been ported to use clock::v2 + USB_ALLOCATOR = Some(UsbBusAllocator::new(UsbBus::new( + &pclk_usb.into(), + &mut mclk, pins.usb_dm, pins.usb_dp, peripherals.usb, - &mut clocks, - &mut peripherals.mclk, - )); + ))); USB_ALLOCATOR.as_ref().unwrap() }; diff --git a/hal/src/peripherals/dsu.rs b/hal/src/peripherals/dsu.rs index 7c66f28bd211..53746500ecf6 100644 --- a/hal/src/peripherals/dsu.rs +++ b/hal/src/peripherals/dsu.rs @@ -3,14 +3,35 @@ //! This module allows users to interact with a DSU peripheral. //! //! - Run a CRC32 checksum over memory +//! - Run a memory test on RAM +//! - Check if a debugger is connected #![warn(missing_docs)] +#![allow(clippy::doc_lazy_continuation)] +use atsamd_hal_macros::{hal_cfg, hal_macro_helper}; +use core::convert::Infallible; +#[hal_cfg("dsu-d5x")] use crate::pac::{self, Pac}; +#[hal_cfg("dsu-d5x")] +type DsuAhbClk = crate::clock::v2::ahb::AhbClk; +#[hal_cfg("dsu-d5x")] +type DsuApbClk = crate::clock::v2::apb::ApbClk; + +#[hal_cfg(any("dsu-d21", "dsu-d11"))] +use crate::pac::{self, Pac1}; + /// Device Service Unit +#[hal_macro_helper] pub struct Dsu { /// PAC peripheral dsu: pac::Dsu, + /// AHB Clock + #[hal_cfg("dsu-d5x")] + ahb: DsuAhbClk, + /// APB Clock + #[hal_cfg("dsu-d5x")] + apb: DsuApbClk, } /// Errors from hardware @@ -19,6 +40,8 @@ pub struct Dsu { pub enum PeripheralError { /// Usually misaligned address of length BusError, + /// Command not allowed whilst in protected state + ProtectionError, } /// Error from within the DSU @@ -33,6 +56,15 @@ pub enum Error { CrcFailed, /// Hardware-generated errors Peripheral(PeripheralError), + /// RAM Test failed + RamTestFailed { + /// Address of failed RAM + addr: u32, + /// Phase where MBIST failed (See datasheet) + phase: u8, + /// Bit in address which failed + bit: u8, + }, } /// NVM result type @@ -41,7 +73,8 @@ pub type Result = core::result::Result; impl Dsu { /// Unlock the DSU and instantiate peripheral #[inline] - pub fn new(dsu: pac::Dsu, pac: &Pac) -> Result { + #[hal_cfg("dsu-d5x")] + pub fn new(dsu: pac::Dsu, ahb: DsuAhbClk, apb: DsuApbClk, pac: &Pac) -> Result { // Attempt to unlock DSU pac.wrctrl() .write(|w| unsafe { w.perid().bits(33).key().clr() }); @@ -49,64 +82,107 @@ impl Dsu { // Check if DSU was unlocked if pac.statusb().read().dsu_().bit_is_set() { Err(Error::PacUnlockFailed) + } else { + Ok(Self { dsu, ahb, apb }) + } + } + + /// Unlock the DSU and instantiate peripheral + #[inline] + #[hal_cfg(any("dsu-d21", "dsu-d11"))] + pub fn new(dsu: pac::Dsu, pac1: &Pac1) -> Result { + // Attempt to unlock DSU + pac1.wpclr().modify(|_, w| unsafe { w.bits(1 << 1) }); // Clear DSU protection + // Check if DSU was unlocked + if pac1.wpset().read().bits() & (1 << 1) != 0 { + Err(Error::PacUnlockFailed) } else { Ok(Self { dsu }) } } - /// Clear bus error bit - fn clear_bus_error(&mut self) { - self.dsu.statusa().write(|w| w.berr().set_bit()); + /// Releases the DSU peripheral + #[hal_cfg(any("dsu-d21", "dsu-d11"))] + pub fn free(self) -> pac::Dsu { + self.dsu + } + + /// Releases the DSU peripheral + #[hal_cfg(any("dsu-d5x"))] + pub fn free(self) -> (pac::Dsu, DsuAhbClk, DsuApbClk) { + (self.dsu, self.ahb, self.apb) + } + + /// Clear statusa bits (all errors and done flag) + #[inline] + fn clear_status_a(&mut self) { + self.dsu.statusa().write(|w| { + w.berr().set_bit(); + w.perr().set_bit(); + w.done().set_bit(); + w.fail().set_bit() + }); } /// Read bus error bit + #[inline] fn bus_error(&mut self) -> bool { self.dsu.statusa().read().berr().bit() } + /// Read protection error bit + #[inline] + fn protection_error(&mut self) -> bool { + self.dsu.statusa().read().perr().bit() + } + /// Check if operation is done + #[inline] fn is_done(&self) -> bool { self.dsu.statusa().read().done().bit_is_set() } /// Check if an operation has failed + #[inline] fn has_failed(&self) -> bool { self.dsu.statusa().read().fail().bit_is_set() } /// Set target address given as number of words offset - fn set_address(&mut self, address: u32) -> Result<()> { + #[inline] + fn set_address(&mut self, address: u32) { self.dsu.addr().write(|w| unsafe { w.addr().bits(address) }); - Ok(()) } /// Set length given as number of words - fn set_length(&mut self, length: u32) -> Result<()> { + #[inline] + fn set_length(&mut self, length: u32) { self.dsu .length() .write(|w| unsafe { w.length().bits(length) }); - Ok(()) } /// Seed CRC32 + #[inline] fn seed(&mut self, data: u32) { self.dsu.data().write(|w| unsafe { w.data().bits(data) }); } - /// Calculate CRC32 of a memory region - /// - /// - `address` is an address within a flash; must be word-aligned - /// - `length` is a length of memory region that is being processed. Must be - /// word-aligned + /// Checks if a debugger is current connected #[inline] - pub fn crc32(&mut self, address: u32, length: u32) -> Result { - // The algorithm employed is the industry standard CRC32 algorithm using the - // generator polynomial 0xEDB88320 - // (reversed representation of 0x04C11DB7). - // - // https://crccalc.com/, Hex input same as memory contents, Calc CRC-32 - // but output is reversed + pub fn is_debugger_present(&self) -> bool { + self.dsu.statusb().read().dbgpres().bit_is_set() + } + /// Abort a current DSU operation (CRC32 or MBIST) + #[inline] + fn abort_operation(&self) { + self.dsu.ctrl().write(|w| w.swrst().set_bit()); + } + + /// Setup memory length and address data, checking for word alignment + #[inline] + fn setup_mem_addr_regs(&mut self, address: u32, length: u32) -> Result<()> { if address % 4 != 0 { return Err(Error::AlignmentError); } @@ -115,43 +191,232 @@ impl Dsu { return Err(Error::AlignmentError); } - let num_words = length / 4; - - // Calculate target flash address - let flash_address = address / 4; + self.set_address(address / 4); + self.set_length(length / 4); + Ok(()) + } - // Set the ADDR of where to start calculation, as number of words - self.set_address(flash_address)?; + /// Calculate CRC32 of a memory region + /// + /// - `address` is an address within the CPUs memory space; must be + /// word-aligned + /// - `length` is a length of memory region that is being processed. + /// Must be word-aligned + #[hal_macro_helper] + pub fn crc32(&mut self, address: u32, length: u32) -> Result { + // The algorithm employed is the industry standard CRC32 algorithm using the + // generator polynomial 0xEDB88320 + // (reversed representation of 0x04C11DB7). + // + // https://crccalc.com/, Hex input same as memory contents, Calc CRC-32 + // but output is reversed - // Amount of words to check - self.set_length(num_words)?; + self.setup_mem_addr_regs(address, length)?; // Set CRC32 seed self.seed(0xffff_ffff); - // Clear the status flags indicating termination of the operation - self.dsu - .statusa() - .write(|w| w.done().set_bit().fail().set_bit()); + #[hal_cfg("dsu-d21")] + { + // Errata for D21 silicon versions A-D + if address > 0x20000000 { + // Address is in RAM + unsafe { + *(0x41007058 as *mut u32) &= !0x30000; + } + } + } + + // Clear previous done/error status + self.clear_status_a(); // Initialize CRC calculation self.dsu.ctrl().write(|w| w.crc().set_bit()); // Wait until done or failed - while !self.is_done() && !self.has_failed() {} - if self.has_failed() { - return Err(Error::CrcFailed); + while !self.is_done() { + core::hint::spin_loop(); + } + + #[hal_cfg("dsu-d21")] + { + // Errata for D21 silicon versions A-D + if address > 0x20000000 { + // Address is in RAM + unsafe { + *(0x41007058 as *mut u32) |= 0x20000; + } + } } - // CRC startup generated a bus error - // Generally misaligned length or address - if self.bus_error() { - // Return the reported bus error and clear it - self.clear_bus_error(); + if self.has_failed() { + Err(Error::CrcFailed) + } else if self.bus_error() { Err(Error::Peripheral(PeripheralError::BusError)) + } else if self.protection_error() { + Err(Error::Peripheral(PeripheralError::ProtectionError)) } else { // Return the calculated CRC32 (complement of data register) Ok(!self.dsu.data().read().data().bits()) } } + + /// Begins a DSU Memory test on a section of memory, returning + /// a handle that can be polled for the result of the test. + /// + /// ## Algorithm (Implemented by the DSU peripheral): + /// 1. Write entire memory to '0', in any order. + /// 2. Bit by bit read '0', write '1', in descending order. + /// 3. Bit by bit read '1', write '0', read '0', write '1', in ascending + /// order. + /// 4. Bit by bit read '1', write '0', in ascending order. + /// 5. Bit by bit read '0', write '1', read '1', write '0', in ascending + /// order. + /// 6. Read '0' from entire memory, in ascending order. + /// + /// + /// - `address` is an address within the CPUs RAM space; must be + /// word-aligned + /// - `length` is a length of memory region that is being tested. Must be + /// word-aligned + /// + /// # Safety + /// This function can overwrite critical data in RAM, it is up to the caller + /// to ensure that the RAM being tested is not in use by the application. + /// It is recommended to run this in a critical section so that an + /// interrupt cannot potentially access currently tested memory. + pub unsafe fn memory_test( + &mut self, + address: u32, + length: u32, + ) -> Result> { + self.setup_mem_addr_regs(address, length)?; + self.clear_status_a(); + // Initialize RAM Test + self.dsu.ctrl().write(|w| w.mbist().set_bit()); + Ok(MemoryTestHandle { dsu: self }) + } +} + +/// Handle to an ongoing background memory test +/// that can be aborted at any time +/// +/// An example usage in an RTIC idle task +/// may look like the following: +/// +/// ``` +/// // dsu = rtic_sync::arbiter::Arbiter +/// #[idle(shared=[&dsu])] +/// fn idle(ctx: idle::Context) -> ! { +/// let mut ram_offset = 0; +/// // Important that this NEVER overwrites idle task stack +/// +/// // Size of each RAM test (Per cycle) +/// const RAM_TEST_SIZE: usize = 128; +/// // Max address in RAM to test (Ensuring it doesn't +/// // overwrite the idle task stack) +/// const RAM_TEST_LEN: usize = 0xFFFF; +/// let mut ram_offset = 0; +/// let mut ram_buf = [0u8; RAM_TEST_SIZE]; +/// let ram_buf_ptr = ram_buf.as_mut_ptr(); +/// loop { +/// cortex_m::interrupt::free(|_| { +/// if let Some(mut lock) = ctx.shared.dsu.try_access() { +/// unsafe { +/// // Copy RAM to temp buffer +/// let ram_ptr = ((0x2000_0000+ram_offset) as *mut u8); +/// ram_ptr.copy_to(ram_buf_ptr, RAM_TEST_SIZE); +/// +/// // Guaranteed alignment (So unwrap) +/// let test = lock.polling_memory_test(0x2000_0000+ram_offset, RAM_TEST_LEN as u32).unwrap(); +/// // DSU does ram test whilst CPU is waiting +/// wfi(); +/// // CPU woke up, abort running test, and check our results +/// let test_res = test.finish_now(); +/// // Copy ram back +/// ram_buf_ptr.copy_to(ram_ptr, RAM_TEST_SIZE); +/// match test_res { +/// MemoryTestResult::Ok=> { +/// ram_offset += 128; +/// if (ram_offset > RAM_TEST_LEN) { +/// ram_offset = 0; +/// } +/// } +/// MemoryTestResult::Aborted => { +/// // Aborted, so don't increase counter +/// // since the test wasn't finished +/// }, +/// MemoryTestResult::Error(error) => { +/// if let atsamd_hal::dsu::Error::RamTestFailed { addr, phase, bit } = error { +/// // Handle what happens when RAM test fails (Probably panic) +/// } +/// }, +/// } +/// } +/// } else { +/// // DSU not available so we just wait +/// wfi(); +/// } +/// }) +/// } +/// } +/// ``` +pub struct MemoryTestHandle<'a> { + dsu: &'a mut Dsu, +} + +/// Tri-state result for [MemoryTestHandle] +pub enum MemoryTestResult { + /// Memory test OK + Ok, + /// Memory test aborted + Aborted, + /// Memory test error + Error(Error), +} + +impl<'a> MemoryTestHandle<'a> { + /// Gets the result of the completed memory test if it is completed, + /// or forces the memory test to abort right now. + /// + /// If the memory test is still in progress when called, + /// then it is aborted immediately, and will return [MemoryTestResult::Aborted]. + /// This functionality is used when the RAM being tested must be released + /// by the memory test and given back to the application. + /// + /// If the memory test had finished before this is called, then + /// it will either return [MemoryTestResult::Ok], or [MemoryTestResult::Error] + pub fn finish_now(self) -> MemoryTestResult { + if self.dsu.is_done() { + if self.dsu.has_failed() { + let addr = self.dsu.dsu.addr().read().addr().bits(); + let data = self.dsu.dsu.data().read().data().bits(); + let bit = (data & 0b11111) as u8; + let phase = ((data >> 8) & 0b111) as u8; + MemoryTestResult::Error(Error::RamTestFailed { addr, phase, bit }) + } else if self.dsu.bus_error() { + MemoryTestResult::Error(Error::Peripheral(PeripheralError::BusError)) + } else if self.dsu.protection_error() { + MemoryTestResult::Error(Error::Peripheral(PeripheralError::ProtectionError)) + } else { + MemoryTestResult::Ok + } + } else { + self.dsu.abort_operation(); + MemoryTestResult::Aborted + } + } + + /// Polls to see if the memory test is completed using nb's API + /// + /// Use [Self::finish_now] to actually obtain the result of the + /// test once completed + #[inline] + pub fn is_completed(&self) -> nb::Result<(), Infallible> { + if self.dsu.is_done() { + Ok(()) + } else { + Err(nb::Error::WouldBlock) + } + } } diff --git a/hal/src/peripherals/mod.rs b/hal/src/peripherals/mod.rs index 78eb4048a08c..267f7525b403 100644 --- a/hal/src/peripherals/mod.rs +++ b/hal/src/peripherals/mod.rs @@ -40,8 +40,8 @@ pub mod clock {} #[hal_module("aes")] pub mod aes {} -#[hal_module("dsu-d5x")] -pub mod dsu {} +#[cfg(feature = "device")] +pub mod dsu; #[hal_module("pukcc")] pub mod pukcc {}