diff --git a/.cargo/config.toml b/.cargo/config.toml index e0e5b9f3..0156fd56 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -6,6 +6,4 @@ git-fetch-with-cli = true [target.thumbv7em-none-eabihf] runner = 'arm-none-eabi-gdb' -rustflags = [ - "-C", "link-arg=-Tlink.x", -] +rustflags = ["-C", "link-arg=-Tlink.x"] diff --git a/.github/workflows/clippy-format-rustdocs.yaml b/.github/workflows/clippy-format-rustdocs.yaml index 518c4ba1..cac0656b 100644 --- a/.github/workflows/clippy-format-rustdocs.yaml +++ b/.github/workflows/clippy-format-rustdocs.yaml @@ -14,6 +14,9 @@ jobs: run: | eval $(ssh-agent -s) ssh-add - <<< "${{ secrets.PRIVATE_SSH_KEY }}" + echo 'set man-db/auto-update false' | sudo debconf-communicate >/dev/null + sudo dpkg-reconfigure man-db + sudo apt install -y gcc-arm-none-eabi rustup target add thumbv7em-none-eabihf cargo clippy -- -D warnings @@ -25,6 +28,9 @@ jobs: run: | eval $(ssh-agent -s) ssh-add - <<< "${{ secrets.PRIVATE_SSH_KEY }}" + echo 'set man-db/auto-update false' | sudo debconf-communicate >/dev/null + sudo dpkg-reconfigure man-db + sudo apt install -y gcc-arm-none-eabi rustup target add thumbv7em-none-eabihf cargo clippy -- -W clippy::pedantic @@ -36,6 +42,9 @@ jobs: run: | eval $(ssh-agent -s) ssh-add - <<< "${{ secrets.PRIVATE_SSH_KEY }}" + echo 'set man-db/auto-update false' | sudo debconf-communicate >/dev/null + sudo dpkg-reconfigure man-db + sudo apt install -y gcc-arm-none-eabi rustup target add thumbv7em-none-eabihf cargo fmt --check @@ -48,6 +57,9 @@ jobs: eval $(ssh-agent -s) ssh-add - <<< "${{ secrets.PRIVATE_SSH_KEY }}" rustup target add thumbv7em-none-eabihf + echo 'set man-db/auto-update false' | sudo debconf-communicate >/dev/null + sudo dpkg-reconfigure man-db + sudo apt install -y gcc-arm-none-eabi RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --document-private-items - name: upload artifacts uses: actions/upload-artifact@v4 diff --git a/Cargo.toml b/Cargo.toml index f0a78872..62f7d8ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,8 @@ [workspace] members = [ "hal", - "tests" + "flc-asm", + "tests", ] resolver = "2" diff --git a/flc-asm/.cargo/config.toml b/flc-asm/.cargo/config.toml new file mode 100644 index 00000000..5d10902a --- /dev/null +++ b/flc-asm/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +target = "thumbv7em-none-eabihf" diff --git a/flc-asm/Cargo.toml b/flc-asm/Cargo.toml new file mode 100644 index 00000000..cecacb99 --- /dev/null +++ b/flc-asm/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "flc-asm" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["staticlib"] + +[dependencies] +max78000 = { git = "https://github.com/slugsecurity/max78000" } diff --git a/flc-asm/src/lib.rs b/flc-asm/src/lib.rs new file mode 100644 index 00000000..f8b4dfa8 --- /dev/null +++ b/flc-asm/src/lib.rs @@ -0,0 +1,443 @@ +//! Flash controller peripheral API, but in asm +//! +//! This generates the `flc_asm.s` assembly file in the flash controller, +//! to ensure that all function calls in the HAL primitives are located in RAM. +//! +//! Compile with `cargo rustc --release -- --emit=asm`, then find the assembly file +//! `target/thumbv7em-none-eabihf/release/deps/flc-asm-.s` file. +//! +//! You need to remove the `CORE_PERIPHERALS` and `DEVICE_PERIPHERALS` symbols to +//! avoid linker conflicts. +#![no_std] +#![deny( + clippy::missing_safety_doc, + unsafe_op_in_unsafe_fn, + clippy::undocumented_unsafe_blocks +)] + +use core::{arch::asm, panic::PanicInfo, ptr::read_volatile}; + +use max78000::{FLC, GCR, ICC0}; + +/// A macro for ensuring that code never exits, even in cases of fault-injection attacks. +macro_rules! never_exit { + () => { + // SAFETY: All branches are to a local label. + unsafe { + asm!( + "2:", + "b 2b", + "b 2b", + "b 2b", + "b 2b", + "b 2b", + "b 2b", + "b 2b", + "b 2b", + "b 2b", + "b 2b", + "b 2b", + "b 2b", + "b 2b", + "b 2b", + "b 2b", + "b 2b", + "b 2b", + "b 2b", + "b 2b", + options(noreturn), + ) + } + }; +} + +#[panic_handler] +#[link_section = ".analogsucks"] +fn panic_handler(_: &PanicInfo) -> ! { + never_exit!() +} + +/// A "panic" function that is guaranteed to be in RAM +#[link_section = ".analogsucks"] +fn panic() -> ! { + never_exit!() +} + +/// Flash memory base address. +const FLASH_MEM_BASE: u32 = 0x1000_0000; + +/// Flash memory size. +const FLASH_MEM_SIZE: u32 = 0x0008_0000; + +/// Flash page size. +const FLASH_PAGE_SIZE: u32 = 0x2000; + +struct FlashController<'gcr, 'icc> { + flc: FLC, + gcr: &'gcr GCR, + icc: &'icc ICC0, +} + +/// Checks whether the given address range (exclusive) is within flash space, returning `false` if there +/// is an error. +#[must_use] +#[link_section = ".analogsucks"] +const fn check_address_bounds(address_range: core::ops::Range) -> bool { + FLASH_MEM_BASE <= address_range.start + && address_range.start < FLASH_MEM_BASE + FLASH_MEM_SIZE + && FLASH_MEM_BASE < address_range.end + && address_range.end <= FLASH_MEM_BASE + FLASH_MEM_SIZE +} + +impl FlashController<'_, '_> { + /// Unlocks memory protection to allow flash operations. + /// + /// This MUST be called before any non-read flash controller operation. + /// + /// # Safety + /// - The FLC must be in its ready state after [`Self::wait_until_ready`] + #[link_section = ".analogsucks"] + unsafe fn unlock_write_protection(&self) { + self.flc.ctrl().modify(|_, w| w.unlock().unlocked()); + } + + /// Locks memory protection. + /// + /// This MUST be called after any non-read flash controller operation. + /// + /// # Safety + /// - The FLC must be in its ready state after [`Self::wait_until_ready`] + #[link_section = ".analogsucks"] + unsafe fn lock_write_protection(&self) { + self.flc.ctrl().modify(|_, w| w.unlock().locked()); + } + + /// Sets the `FLC_CLKDIV` register to the system clock's frequency `sys_clk_freq`, + /// calculated with `freq / div` of the current system clock. It must be a multiple + /// of 1 MHz. + /// + /// This MUST be called before any non-read flash controller operations after + /// the FLC is ready. + /// + /// # Panics + /// - If the clock is not a multiple of 1 MHz, this function panics. + /// + /// # Safety + /// - The passed argument `sys_clk_freq` must be the current system clock's + /// frequency divided by its divider. + /// - The FLC must be in its ready state after [`Self::wait_until_ready`] + #[link_section = ".analogsucks"] + unsafe fn set_clock_divisor(&self, sys_clk_freq: u32) { + if sys_clk_freq % 1_000_000 != 0 { + panic() + } + + let flc_clkdiv = sys_clk_freq / 1_000_000; + + self.flc + .clkdiv() + .modify(|_, w| w.clkdiv().variant(flc_clkdiv as u8)); + } + + /// Wait, by busy-looping, until the FLC is ready. + /// + /// This MUST be called BEFORE any FLC operation EXCEPT clearing interrupts. + #[link_section = ".analogsucks"] + fn wait_until_ready(&self) { + while !self.flc.ctrl().read().pend().bit_is_clear() {} + } + + /// Clear any stale errors in the FLC interrupt register. + /// + /// This can be called without waiting for the FLC to be ready. + #[link_section = ".analogsucks"] + fn clear_interrupts(&self) { + self.flc.intr().modify(|_, w| w.af().clear_bit()); + } + + /// Prepares the FLC for a write operation, performs the operation, and + /// cleans up after the operation. + /// + /// # Safety + /// - The argument `sys_clk_freq` must be equal to the current system clock's + /// frequency divided by its divider. + /// + /// # Panics + /// - If `sys_clk_freq` is not a multiple of 1 MHz, this function panics. + #[link_section = ".analogsucks"] + unsafe fn write_guard(&self, sys_clk_freq: u32, operation: F) { + // Pre-write + self.wait_until_ready(); + self.disable_icc0(); + self.clear_interrupts(); + + // SAFETY: we wait until the FLC is ready above, and the caller must + // guarantee that `sys_clk_freq` is valid per `[Self::set_clock_divisor]`. + unsafe { + self.set_clock_divisor(sys_clk_freq); + } + + // SAFETY: we wait until the FLC is ready above + unsafe { + self.unlock_write_protection(); + } + + operation(); + + // Post-write + self.wait_until_ready(); + + // SAFETY: we wait until the FLC is ready above + unsafe { + self.lock_write_protection(); + } + self.flush_icc(); + self.enable_icc0(); + } + + /// Flushes the flash line buffer and arm instruction cache. + /// + /// This MUST be called after any write/erase flash controller operations. + #[link_section = ".analogsucks"] + fn flush_icc(&self) { + const PAGE1: u32 = FLASH_MEM_BASE; + const PAGE2: u32 = FLASH_MEM_BASE + FLASH_PAGE_SIZE; + + self.gcr.sysctrl().modify(|_, w| w.icc0_flush().flush()); + while !self.gcr.sysctrl().read().icc0_flush().bit_is_clear() {} + + // Clear the line fill buffer by reading 2 pages from flash + const { + assert!(check_address_bounds(PAGE1..PAGE1 + 4)); + assert!(PAGE1 % 4 == 0); + assert!(check_address_bounds(PAGE2..PAGE2 + 4)); + assert!(PAGE2 % 4 == 0); + } + + // SAFETY: `FLASH_MEM_BASE` points to a valid, aligned word within flash space as asserted above. + unsafe { core::hint::black_box(read32(PAGE1 as *const u32)) }; + // SAFETY: `FLASH_MEM_BASE + FLASH_PAGE_SIZE` points to a valid, aligned word within flash space. + unsafe { core::hint::black_box(read32(PAGE2 as *const u32)) }; + } + + /// Disables instruction cache. + /// + /// This MUST be called before any non-read flash controller operations. + #[link_section = ".analogsucks"] + fn disable_icc0(&self) { + self.icc.ctrl().modify(|_, w| w.en().dis()); + } + + /// Enables instruction cache. + /// + /// This MUST be called after any non-read flash controller operations. + #[link_section = ".analogsucks"] + fn enable_icc0(&self) { + // ensure the cache is invalidated when enabled + self.disable_icc0(); + + self.icc.invalidate().modify(|_, w| w.invalid().variant(1)); + while !self.icc.ctrl().read().rdy().bit_is_set() {} + + self.icc.ctrl().modify(|_, w| w.en().en()); + while !self.icc.ctrl().read().rdy().bit_is_set() {} + } + + /// Writes 128 bits (16 bytes) of data to flash. + /// Address must be 128-bit aligned. + /// + /// # Safety + /// + /// - The argument `sys_clk_freq` must be equal to the current system clock's + /// frequency divided by its divider. + /// - Writes must not corrupt potentially executable instructions of the program. + /// - Callers must ensure that the following condition is met: + /// * If `address` points to a portion of the program's instructions, `data` must + /// contain valid instructions that does not introduce undefined behavior. + /// + /// It is very difficult to define what would cause undefined behavior when + /// modifying program instructions. This would almost certainly result + /// in unwanted and likely undefined behavior. Do so at your own risk. + /// + /// + /// # Panics + /// + /// If any of the following conditions are not met, this function panics: + /// + /// - `sys_clk_freq` must be a multiple of 1 MHz + /// - `address` must point to a word contained in flash space + /// - `address` must be aligned to 128 bits + #[link_section = ".analogsucks"] + unsafe fn write128(&self, address: u32, data: &[u32; 4], sys_clk_freq: u32) { + if !check_address_bounds(address..address + 16) { + panic(); + } + #[allow( + clippy::cast_possible_truncation, + reason = "the target pointer width is 32, so this will not truncate" + )] + if address % size_of::<[u32; 4]>() as u32 != 0 { + panic(); + } + + // SAFETY: the caller must guarantee that `sys_clk_freq` is valid per this function's + // safety comment. + unsafe { + self.write_guard(sys_clk_freq, || { + self.flc.addr().modify(|_, w| w.addr().variant(address)); + self.flc.data(0).modify(|_, w| w.data().variant(data[0])); + self.flc.data(1).modify(|_, w| w.data().variant(data[1])); + self.flc.data(2).modify(|_, w| w.data().variant(data[2])); + self.flc.data(3).modify(|_, w| w.data().variant(data[3])); + + self.flc.ctrl().modify(|_, w| w.wr().set_bit()); + + // Wait until write completes + while !self.flc.ctrl().read().wr().is_complete() {} + }); + } + } + + /// Erases a page of flash. `address[12:0]` is ignored to ensure the address + /// is page-aligned. + /// + /// # Safety + /// + /// - The argument `sys_clk_freq` must be equal to the current system clock's + /// frequency divided by its divider. + /// - Erases must not corrupt potentially executable instructions of the program. + /// - `address` must be in a valid flash page + /// + /// # Panics + /// - If `sys_clk_freq` is not a multiple of 1 MHz, this function panics. + /// - This function also panics when the `address` does not point inside of a page + /// contained in flash space. + #[link_section = ".analogsucks"] + unsafe fn page_erase(&self, address: u32, sys_clk_freq: u32) { + #[allow( + clippy::range_plus_one, + reason = "the caller takes a Range struct, not an `impl RangeBounds`" + )] + if !check_address_bounds(address..address + 1) { + panic() + } + // SAFETY: the caller must guarantee that `sys_clk_freq` is valid per this function's + // safety comment. + unsafe { + self.write_guard(sys_clk_freq, || { + self.flc.addr().modify(|_, w| w.addr().variant(address)); + + self.flc.ctrl().modify(|_, w| w.erase_code().erase_page()); + self.flc.ctrl().modify(|_, w| w.pge().set_bit()); + }); + } + } +} + +/// Reads a little-endian `u32` from flash memory. +/// +/// Panics if any of the following preconditions are not true: +/// - `address` must be 32-bit aligned. +/// - `address` must point to a valid location in flash memory (`0x1000_0000..=0x1007_ffff`). +/// +/// # Safety +/// +/// This is a pointer read to flash space, it is the caller's responsibility to ensure +/// that the pointer points to the correct value. +#[export_name = "flc_read32_primitive"] +#[link_section = ".analogsucks"] +pub unsafe extern "C" fn read32(address: *const u32) -> u32 { + if !address.is_aligned() { + panic(); + } + if !check_address_bounds(address as u32..(address as u32 + 4)) { + panic(); + } + // SAFETY: the caller must guarantee that `address` is aligned and is within + // flash memory. + unsafe { read_volatile(address) } +} + +/// Writes a little-endian 128-bit flash word into flash memory. +/// +/// # Safety +/// +/// - The caller must hold a shared reference to the [`FLC`], [`ICC0`], and [`GCR`] registers. +/// - The flash word at `address` must be in the *erased* state (with [`page_erase`]). +/// - `data` must point to an array of four `u32`s. +/// - `sys_clk_freq` must be equal to `freq / div` where `freq` is the frequency of +/// the current system clock, and `div` is the divider of the system clock. +/// - If `address` writes to an address in the currently-running program's instruction space, +/// it must be valid instructions. +/// +/// # Panics +/// +/// Panics if any of the following preconditions are not true: +/// - `address` must be 128-bit aligned. +/// - The entire flash word at address (bytes `address..address + 16`) must be within +/// the flash memory (`0x1000_0000..=0x1007_ffff`). +/// - `sys_clk_freq` must be divisible by one million (`1_000_000`). +#[export_name = "flc_write128_primitive"] +#[link_section = ".analogsucks"] +pub unsafe extern "C" fn write128(address: *mut [u32; 4], data: *const u32, sys_clk_freq: u32) { + // SAFETY: the caller must hold a valid reference to these registers during this call. + let flc = unsafe { + FlashController { + flc: FLC::steal(), + icc: &ICC0::steal(), + gcr: &GCR::steal(), + } + }; + + // SAFETY: the caller must ensure that `data` points to a valid array of four `u32`s. + let data = unsafe { &*data.cast() }; + + // SAFETY: + // - the caller must guarantee that the address is aligned and the word is within flash space + // - the caller must guarantee that the word is in the erased state + // - the caller must ensure that `sys_clk_freq` is correctly calculated per this function's + // safety comment + // - the caller must ensure that, if it is overwriting instructions, the new instructions are valid + unsafe { + flc.write128(address as u32, data, sys_clk_freq); + } +} + +/// Erases the page at the given address in flash memory. +/// +/// # Safety +/// +/// - The caller must hold a shared reference to the [`FLC`], [`ICC0`], and [`GCR`] registers. +/// - `address` must point to a valid page within flash memory (`0x1000_0000..=0x1007_ffff`). +/// - `sys_clk_freq` must be equal to `freq / div` where `freq` is the frequency of +/// the current system clock, and `div` is the divider of the system clock. +/// - `sys_clk_freq` must be divisible by one million (`1_000_000`). +/// - If `address` erases a page in the currently-running program's instruction space, +/// it must be rewritten with [`write128`] before the program reaches those instructions. +/// +/// # Panics +/// +/// Panics if any of the following preconditions are not true: +/// - `address` must point to within a valid page in flash space (`0x1000_000..=0x1007_ffff`) +/// - `sys_clk_freq` must be divisible by one million (`1_000_000`). +#[export_name = "flc_page_erase_primitive"] +#[link_section = ".analogsucks"] +pub unsafe extern "C" fn page_erase(address: *mut u8, sys_clk_freq: u32) { + // SAFETY: the caller must hold a valid reference to these registers during this call. + let flc = unsafe { + FlashController { + flc: FLC::steal(), + icc: &ICC0::steal(), + gcr: &GCR::steal(), + } + }; + + // SAFETY: + // - the caller must provide a valid address. + // - the caller must ensure that sys_clk_freq is calculated correctly per this function's + // safety comment. + // - the caller must guarantee that the program won't execute erased instructions in this page. + unsafe { + flc.page_erase(address as u32, sys_clk_freq); + } +} diff --git a/hal/Cargo.toml b/hal/Cargo.toml index f105468c..e10d5a0c 100644 --- a/hal/Cargo.toml +++ b/hal/Cargo.toml @@ -24,7 +24,12 @@ zeroize = { version = "1.7.0", default-features = false } bitvec = { version = "1.0.1", default-features = false } cortex-m-semihosting = "0.5.0" cortex-m = { version = "0.7.7" } +critical-section = "1.2.0" [features] rt = ["max78000/rt", "cortex-m-rt"] low_frequency = [] +flc-ram = ["rt"] + +[build-dependencies] +cc = "1.1" diff --git a/hal/build.rs b/hal/build.rs new file mode 100644 index 00000000..bf25bb29 --- /dev/null +++ b/hal/build.rs @@ -0,0 +1,22 @@ +#[cfg(feature = "flc-ram")] +fn compile_flc_asm() { + use std::{env, fs::File, io::Write, path::PathBuf}; + + let out = PathBuf::from(env::var_os("OUT_DIR").unwrap()); + let flc_asm_path = out.join("flc_asm.s"); + File::create(&flc_asm_path) + .unwrap() + .write_all(include_bytes!("flc_asm.s")) + .unwrap(); + + cc::Build::new().file(&flc_asm_path).compile("flc_asm"); + + println!("cargo:rerun-if-changed=flc_asm.s"); +} + +fn main() { + #[cfg(feature = "flc-ram")] + compile_flc_asm(); + + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/hal/flc_asm.s b/hal/flc_asm.s new file mode 100644 index 00000000..af8c4be1 --- /dev/null +++ b/hal/flc_asm.s @@ -0,0 +1,523 @@ + .text + .syntax unified + .eabi_attribute 67, "2.09" + .eabi_attribute 6, 13 + .eabi_attribute 7, 77 + .eabi_attribute 8, 0 + .eabi_attribute 9, 2 + .fpu fpv4-sp-d16 + .eabi_attribute 27, 1 + .eabi_attribute 36, 1 + .eabi_attribute 34, 1 + .eabi_attribute 17, 1 + .eabi_attribute 20, 1 + .eabi_attribute 21, 1 + .eabi_attribute 23, 3 + .eabi_attribute 24, 1 + .eabi_attribute 25, 1 + .eabi_attribute 28, 1 + .eabi_attribute 38, 1 + .eabi_attribute 14, 0 + .file "flc_asm.d8eec276ff8a6799-cgu.0" + .section .analogsucks,"ax",%progbits + .p2align 2 + .type _ZN7flc_asm15FlashController17set_clock_divisor17hc5cf0d05434ae31dE,%function + .code 16 + .thumb_func +_ZN7flc_asm15FlashController17set_clock_divisor17hc5cf0d05434ae31dE: +.Lfunc_begin0: + .fnstart + .cfi_sections .debug_frame + .cfi_startproc + .save {r7, lr} + push {r7, lr} + .cfi_def_cfa_offset 8 + .cfi_offset lr, -4 + .cfi_offset r7, -8 + .setfp r7, sp + mov r7, sp + .cfi_def_cfa_register r7 + ldr r1, .LCPI0_0 + ldr r2, .LCPI0_1 + udiv r1, r0, r1 + muls r2, r1, r2 + cmn r2, r0 + itttt eq + ldreq r0, .LCPI0_2 + ldreq r2, [r0] + bfieq r2, r1, #0, #8 + streq r2, [r0] + it eq + popeq {r7, pc} +.LBB0_1: + @APP +.Ltmp0: + b .Ltmp0 + b .Ltmp0 + b .Ltmp0 + b .Ltmp0 + b .Ltmp0 + b .Ltmp0 + b .Ltmp0 + b .Ltmp0 + b .Ltmp0 + b .Ltmp0 + b .Ltmp0 + b .Ltmp0 + b .Ltmp0 + b .Ltmp0 + b .Ltmp0 + b .Ltmp0 + b .Ltmp0 + b .Ltmp0 + b .Ltmp0 + @NO_APP + .inst.n 0xdefe + .p2align 2 +.LCPI0_0: + .long 1000000 +.LCPI0_1: + .long 4293967296 +.LCPI0_2: + .long 1073909764 +.Lfunc_end0: + .size _ZN7flc_asm15FlashController17set_clock_divisor17hc5cf0d05434ae31dE, .Lfunc_end0-_ZN7flc_asm15FlashController17set_clock_divisor17hc5cf0d05434ae31dE + .cfi_endproc + .cantunwind + .fnend + + .p2align 2 + .type _ZN7flc_asm15FlashController16wait_until_ready17h3156387ccf27a694E,%function + .code 16 + .thumb_func +_ZN7flc_asm15FlashController16wait_until_ready17h3156387ccf27a694E: +.Lfunc_begin1: + .fnstart + .cfi_startproc + ldr r0, .LCPI1_0 +.LBB1_1: + ldr r1, [r0] + lsls r1, r1, #7 + bmi .LBB1_1 + bx lr + .p2align 2 +.LCPI1_0: + .long 1073909768 +.Lfunc_end1: + .size _ZN7flc_asm15FlashController16wait_until_ready17h3156387ccf27a694E, .Lfunc_end1-_ZN7flc_asm15FlashController16wait_until_ready17h3156387ccf27a694E + .cfi_endproc + .cantunwind + .fnend + + .p2align 2 + .type _ZN7flc_asm15FlashController9flush_icc17hdfc1ef73516cdd37E,%function + .code 16 + .thumb_func +_ZN7flc_asm15FlashController9flush_icc17hdfc1ef73516cdd37E: +.Lfunc_begin2: + .fnstart + .cfi_startproc + .pad #8 + sub sp, #8 + .cfi_def_cfa_offset 8 + mov.w r0, #1073741824 + ldr r1, [r0] + orr r1, r1, #64 + str r1, [r0] +.LBB2_1: + ldr r1, [r0] + lsls r1, r1, #25 + bmi .LBB2_1 + mov.w r0, #268435456 + ldr r0, [r0] + str r0, [sp] + mov r0, sp + @APP + @NO_APP + ldr r0, .LCPI2_0 + ldr r0, [r0] + str r0, [sp, #4] + add r0, sp, #4 + @APP + @NO_APP + add sp, #8 + bx lr + .p2align 2 +.LCPI2_0: + .long 268443648 +.Lfunc_end2: + .size _ZN7flc_asm15FlashController9flush_icc17hdfc1ef73516cdd37E, .Lfunc_end2-_ZN7flc_asm15FlashController9flush_icc17hdfc1ef73516cdd37E + .cfi_endproc + .cantunwind + .fnend + + .p2align 2 + .type _ZN7flc_asm15FlashController11enable_icc017h626bac51309a41f0E,%function + .code 16 + .thumb_func +_ZN7flc_asm15FlashController11enable_icc017h626bac51309a41f0E: +.Lfunc_begin3: + .fnstart + .cfi_startproc + ldr r0, .LCPI3_0 + ldr r1, [r0] + bic r1, r1, #1 + str r1, [r0] + ldr.w r1, [r0, #1536] + movs r1, #1 + str.w r1, [r0, #1536] +.LBB3_1: + ldr r1, [r0] + lsls r1, r1, #15 + bpl .LBB3_1 + ldr r1, [r0] + orr r1, r1, #1 + str r1, [r0] +.LBB3_3: + ldr r1, [r0] + lsls r1, r1, #15 + bpl .LBB3_3 + bx lr + .p2align 2 +.LCPI3_0: + .long 1073914112 +.Lfunc_end3: + .size _ZN7flc_asm15FlashController11enable_icc017h626bac51309a41f0E, .Lfunc_end3-_ZN7flc_asm15FlashController11enable_icc017h626bac51309a41f0E + .cfi_endproc + .cantunwind + .fnend + + .globl flc_read32_primitive + .p2align 2 + .type flc_read32_primitive,%function + .code 16 + .thumb_func +flc_read32_primitive: +.Lfunc_begin4: + .fnstart + .cfi_startproc + .save {r7, lr} + push {r7, lr} + .cfi_def_cfa_offset 8 + .cfi_offset lr, -4 + .cfi_offset r7, -8 + .setfp r7, sp + mov r7, sp + .cfi_def_cfa_register r7 + lsls r1, r0, #30 + bne .LBB4_4 + mov r1, r0 + bfc r1, #0, #19 + cmp.w r1, #268435456 + bne .LBB4_3 + ldr r1, .LCPI4_0 + add r1, r0 + cmp.w r1, #524288 + itt lo + ldrlo r0, [r0] + poplo {r7, pc} +.LBB4_3: + @APP +.Ltmp1: + b .Ltmp1 + b .Ltmp1 + b .Ltmp1 + b .Ltmp1 + b .Ltmp1 + b .Ltmp1 + b .Ltmp1 + b .Ltmp1 + b .Ltmp1 + b .Ltmp1 + b .Ltmp1 + b .Ltmp1 + b .Ltmp1 + b .Ltmp1 + b .Ltmp1 + b .Ltmp1 + b .Ltmp1 + b .Ltmp1 + b .Ltmp1 + @NO_APP + .inst.n 0xdefe +.LBB4_4: + @APP +.Ltmp2: + b .Ltmp2 + b .Ltmp2 + b .Ltmp2 + b .Ltmp2 + b .Ltmp2 + b .Ltmp2 + b .Ltmp2 + b .Ltmp2 + b .Ltmp2 + b .Ltmp2 + b .Ltmp2 + b .Ltmp2 + b .Ltmp2 + b .Ltmp2 + b .Ltmp2 + b .Ltmp2 + b .Ltmp2 + b .Ltmp2 + b .Ltmp2 + @NO_APP + .inst.n 0xdefe + .p2align 2 +.LCPI4_0: + .long 4026531843 +.Lfunc_end4: + .size flc_read32_primitive, .Lfunc_end4-flc_read32_primitive + .cfi_endproc + .cantunwind + .fnend + + .globl flc_write128_primitive + .p2align 2 + .type flc_write128_primitive,%function + .code 16 + .thumb_func +flc_write128_primitive: +.Lfunc_begin5: + .fnstart + .cfi_startproc + .save {r4, r5, r6, r7, lr} + push {r4, r5, r6, r7, lr} + .cfi_def_cfa_offset 20 + .cfi_offset lr, -4 + .cfi_offset r7, -8 + .cfi_offset r6, -12 + .cfi_offset r5, -16 + .cfi_offset r4, -20 + .setfp r7, sp, #12 + add r7, sp, #12 + .cfi_def_cfa r7, 8 + .save {r8} + str r8, [sp, #-4]! + .cfi_offset r8, -24 + mov r5, r0 + bfc r0, #0, #19 + cmp.w r0, #268435456 + bne .LBB5_6 + ldr r0, .LCPI5_0 + add r0, r5 + cmp.w r0, #524288 + bhs .LBB5_6 + lsls r0, r5, #28 + bne .LBB5_7 + mov r8, r2 + mov r4, r1 + bl _ZN7flc_asm15FlashController16wait_until_ready17h3156387ccf27a694E + ldr r0, .LCPI5_1 + ldr r6, .LCPI5_2 + ldr r1, [r0] + bic r1, r1, #1 + str r1, [r0] + ldr r0, [r6, #28] + bic r0, r0, #2 + str r0, [r6, #28] + mov r0, r8 + bl _ZN7flc_asm15FlashController17set_clock_divisor17hc5cf0d05434ae31dE + ldr r0, [r6] + movs r1, #2 + bfi r0, r1, #28, #4 + str r0, [r6] + ldr r0, [r6, #-8] + str r5, [r6, #-8] + ldr r0, [r6, #40] + ldm r4!, {r0, r1, r2, r3} + str r0, [r6, #40] + ldr r0, [r6, #44] + str r1, [r6, #44] + ldr r0, [r6, #48] + str r2, [r6, #48] + ldr r0, [r6, #52] + str r3, [r6, #52] + ldr r0, [r6] + orr r0, r0, #1 + str r0, [r6] +.LBB5_4: + ldr r0, [r6] + lsls r0, r0, #31 + bne .LBB5_4 + bl _ZN7flc_asm15FlashController16wait_until_ready17h3156387ccf27a694E + ldr r0, [r6] + movs r1, #3 + bfi r0, r1, #28, #4 + str r0, [r6] + bl _ZN7flc_asm15FlashController9flush_icc17hdfc1ef73516cdd37E + ldr r8, [sp], #4 + pop.w {r4, r5, r6, r7, lr} + b _ZN7flc_asm15FlashController11enable_icc017h626bac51309a41f0E +.LBB5_6: + @APP +.Ltmp3: + b .Ltmp3 + b .Ltmp3 + b .Ltmp3 + b .Ltmp3 + b .Ltmp3 + b .Ltmp3 + b .Ltmp3 + b .Ltmp3 + b .Ltmp3 + b .Ltmp3 + b .Ltmp3 + b .Ltmp3 + b .Ltmp3 + b .Ltmp3 + b .Ltmp3 + b .Ltmp3 + b .Ltmp3 + b .Ltmp3 + b .Ltmp3 + @NO_APP + .inst.n 0xdefe +.LBB5_7: + @APP +.Ltmp4: + b .Ltmp4 + b .Ltmp4 + b .Ltmp4 + b .Ltmp4 + b .Ltmp4 + b .Ltmp4 + b .Ltmp4 + b .Ltmp4 + b .Ltmp4 + b .Ltmp4 + b .Ltmp4 + b .Ltmp4 + b .Ltmp4 + b .Ltmp4 + b .Ltmp4 + b .Ltmp4 + b .Ltmp4 + b .Ltmp4 + b .Ltmp4 + @NO_APP + .inst.n 0xdefe + .p2align 2 +.LCPI5_0: + .long 4026531855 +.LCPI5_1: + .long 1073914112 +.LCPI5_2: + .long 1073909768 +.Lfunc_end5: + .size flc_write128_primitive, .Lfunc_end5-flc_write128_primitive + .cfi_endproc + .cantunwind + .fnend + + .globl flc_page_erase_primitive + .p2align 2 + .type flc_page_erase_primitive,%function + .code 16 + .thumb_func +flc_page_erase_primitive: +.Lfunc_begin6: + .fnstart + .cfi_startproc + .save {r4, r5, r6, r7, lr} + push {r4, r5, r6, r7, lr} + .cfi_def_cfa_offset 20 + .cfi_offset lr, -4 + .cfi_offset r7, -8 + .cfi_offset r6, -12 + .cfi_offset r5, -16 + .cfi_offset r4, -20 + .setfp r7, sp, #12 + add r7, sp, #12 + .cfi_def_cfa r7, 8 + .save {r11} + str r11, [sp, #-4]! + .cfi_offset r11, -24 + mov r4, r0 + bfc r0, #0, #19 + cmp.w r0, #268435456 + bne .LBB6_2 + mov r5, r1 + bl _ZN7flc_asm15FlashController16wait_until_ready17h3156387ccf27a694E + ldr r0, .LCPI6_0 + ldr r6, .LCPI6_1 + ldr r1, [r0] + bic r1, r1, #1 + str r1, [r0] + ldr r0, [r6, #28] + bic r0, r0, #2 + str r0, [r6, #28] + mov r0, r5 + bl _ZN7flc_asm15FlashController17set_clock_divisor17hc5cf0d05434ae31dE + ldr r0, [r6] + movs r1, #2 + bfi r0, r1, #28, #4 + str r0, [r6] + ldr r0, [r6, #-8] + movs r1, #85 + str r4, [r6, #-8] + ldr r0, [r6] + bfi r0, r1, #8, #8 + str r0, [r6] + ldr r0, [r6] + orr r0, r0, #4 + str r0, [r6] + bl _ZN7flc_asm15FlashController16wait_until_ready17h3156387ccf27a694E + ldr r0, [r6] + movs r1, #3 + bfi r0, r1, #28, #4 + str r0, [r6] + bl _ZN7flc_asm15FlashController9flush_icc17hdfc1ef73516cdd37E + ldr r11, [sp], #4 + pop.w {r4, r5, r6, r7, lr} + b _ZN7flc_asm15FlashController11enable_icc017h626bac51309a41f0E +.LBB6_2: + @APP +.Ltmp5: + b .Ltmp5 + b .Ltmp5 + b .Ltmp5 + b .Ltmp5 + b .Ltmp5 + b .Ltmp5 + b .Ltmp5 + b .Ltmp5 + b .Ltmp5 + b .Ltmp5 + b .Ltmp5 + b .Ltmp5 + b .Ltmp5 + b .Ltmp5 + b .Ltmp5 + b .Ltmp5 + b .Ltmp5 + b .Ltmp5 + b .Ltmp5 + @NO_APP + .inst.n 0xdefe + .p2align 2 +.LCPI6_0: + .long 1073914112 +.LCPI6_1: + .long 1073909768 +.Lfunc_end6: + .size flc_page_erase_primitive, .Lfunc_end6-flc_page_erase_primitive + .cfi_endproc + .cantunwind + .fnend + + .ident "rustc version 1.84.1 (e71f9a9a9 2025-01-27)" + .ident "rustc version 1.84.1 (e71f9a9a9 2025-01-27)" + .ident "rustc version 1.84.1 (e71f9a9a9 2025-01-27)" + .ident "rustc version 1.84.1 (e71f9a9a9 2025-01-27)" + .ident "rustc version 1.84.1 (e71f9a9a9 2025-01-27)" + .ident "rustc version 1.84.1 (e71f9a9a9 2025-01-27)" + .ident "rustc version 1.84.1 (e71f9a9a9 2025-01-27)" + .ident "rustc version 1.84.1 (e71f9a9a9 2025-01-27)" + .ident "rustc version 1.84.1 (e71f9a9a9 2025-01-27)" + .ident "rustc version 1.84.1 (e71f9a9a9 2025-01-27)" + .ident "rustc version 1.84.1 (e71f9a9a9 2025-01-27)" + .ident "rustc version 1.84.1 (e71f9a9a9 2025-01-27)" + .section ".note.GNU-stack","",%progbits + .eabi_attribute 30, 4 diff --git a/hal/src/lib.rs b/hal/src/lib.rs index 9c6e9b76..f654832f 100644 --- a/hal/src/lib.rs +++ b/hal/src/lib.rs @@ -1,4 +1,36 @@ //! A HAL for the Analog Devices MAX78000. +//! +//! # Runtime +//! +//! If the `rt` feature is enabled, this crate uses the runtime of the [`cortex_m_rt`] +//! crate. Note that the HAL uses the [`pre_init`] hook internally, and it is not possible +//! for users of the hal to specify their own `pre_init` routine. +//! +//! # Flash Controller +//! +//! If the `flc-ram` feature is enabled, this crate will expose the [`FlashController`] +//! peripheral. Certain routines for flash operations need to be located in RAM (instead +//! of flash memory), so users of this feature will need to add the following section to +//! their linker script `link.x`: +//! +//! ```ld +//! .analogsucks : ALIGN(4) +//! { +//! . = ALIGN(4); +//! __sanalogsucks = .; +//! *(.analogsucks .analogsucks.*); +//! } > ANALOGSUCKS +//! . = ALIGN(4); +//! __eanalogsucks = .; +//! } > ANALOGSUCKS AT>FLASH +//! +//! __sianalogsucks = LOADADDR(.analogsucks); +//! ``` +//! +//! where the `ANALOGSUCKS` is a memory section in RAM defined in `memory.x`. +//! +//! [`pre_init`]: cortex_m_rt::pre_init +//! [`FlashController`]: peripherals::flash_controller::FlashController #![warn(missing_docs)] #![no_std] @@ -9,7 +41,39 @@ pub use max78000; pub use self::max78000::Interrupt as interrupt; #[cfg(feature = "rt")] -pub use cortex_m_rt::interrupt; +pub use cortex_m_rt::{interrupt, pre_init}; pub mod communication; pub mod peripherals; + +/// `__pre_init` symbol, ran before initializing memory in [`cortex-m-rt`]. See +/// [`pre_init`] for more. +/// +/// # Safety +/// +/// - Only assembly is allowed, because RAM has not been initialized, so any Rust +/// code that touches memory is undefined behavior. +/// +/// [`pre_init`]: cortex_m_rt::pre_init +#[cfg(feature = "rt")] +#[pre_init] +unsafe fn pre_init() { + // load the .analogsucks section into memory + #[cfg(feature = "flc-ram")] + core::arch::asm! { + "ldr {0}, =__sanalogsucks + ldr {1}, =__eanalogsucks + ldr {2}, =__sianalogsucks + 0: + cmp {1}, {0} + beq 1f + ldm {2}!, {{{3}}} + stm {0}!, {{{3}}} + b 0b + 1:", + out(reg) _, + out(reg) _, + out(reg) _, + out(reg) _, + } +} diff --git a/hal/src/peripherals.rs b/hal/src/peripherals.rs index 9737db79..f795fc4d 100644 --- a/hal/src/peripherals.rs +++ b/hal/src/peripherals.rs @@ -43,6 +43,7 @@ use core::cell::{BorrowMutError, RefCell, RefMut}; use core::ops::{Deref, DerefMut}; use embedded_hal::i2c::SevenBitAddress; +#[cfg(feature = "flc-ram")] use crate::peripherals::flash_controller::FlashController; use crate::peripherals::i2c::{BusSpeed, I2CMaster, I2CSlave}; use crate::peripherals::oscillator::SystemClock; @@ -172,6 +173,7 @@ pub struct PeripheralsToBorrow { /// The peripherals that are completely consumed and moved by the [`PeripheralManager`]. pub struct PeripheralsToConsume { + #[cfg(feature = "flc-ram")] flc: FLC, gpio0: GPIO0, gpio1: GPIO1, @@ -209,6 +211,7 @@ impl SplittablePeripheral for Peripherals { RemainingPeripherals, ) { let to_consume = PeripheralsToConsume { + #[cfg(feature = "flc-ram")] flc: self.FLC, gpio0: self.GPIO0, gpio1: self.GPIO1, @@ -446,6 +449,7 @@ impl<'a, T: Oscillator + private::Oscillator, F: FnMut(&mut [u8])> PeripheralManager { power_ctrl, + #[cfg(feature = "flc-ram")] flash_controller: RefCell::new(FlashController::new( self.consumed_periphs.flc, &self.borrowed_periphs.icc0, @@ -508,6 +512,7 @@ macro_rules! enable_rst_periph_fn { /// The methods inside here can be used to interact with the board peripherals. pub struct PeripheralManager<'a> { power_ctrl: PowerControl<'a, 'a>, + #[cfg(feature = "flc-ram")] flash_controller: RefCell>, system_clock: RefCell>, gpio0: Gpio0, @@ -524,6 +529,7 @@ pub struct PeripheralManager<'a> { } impl<'a> PeripheralManager<'a> { + #[cfg(feature = "flc-ram")] no_enable_rst_periph_fn!(flash_controller, FlashController<'a, 'a>, flash_controller); no_enable_rst_periph_fn!(system_clock, SystemClock<'a, 'a>, system_clock); diff --git a/hal/src/peripherals/flash_controller.rs b/hal/src/peripherals/flash_controller.rs index 400c4f3b..66233c0e 100644 --- a/hal/src/peripherals/flash_controller.rs +++ b/hal/src/peripherals/flash_controller.rs @@ -1,36 +1,35 @@ +#![cfg(feature = "flc-ram")] //! Flash controller peripheral API. -use core::borrow::BorrowMut; - -/// # Examples -/// ``` -/// let flash_controller = FlashController::new(flc, icc0, gcr); -/// -/// let test_addr: u32 = 0x10070FF0; -/// let test_val: u32 = 0xCAFEBABE; -/// let mut data_read: [u8; 4] = [0; 4]; -/// -/// // # Safety -/// // Non-read flash operations must not corrupt potentially instructions of the -/// // program. -/// // Callers must ensure that the following condition is met: -/// // * If `address` points to a portion of the program's instructions, `data` must -/// // contain valid instructions that does not introduce undefined behavior. -/// // -/// // It is very difficult to define what would cause undefined behavior when -/// // modifying program instructions. This would almost certainly result -/// // in unwanted and likely undefined behavior. Do so at your own risk. -/// unsafe { -/// flash_controller.page_erase(test_addr, &sys_clk).unwrap(); -/// flash_controller -/// .write(test_addr, &u32::to_le_bytes(test_val), &sys_clk) -/// .unwrap(); -/// } -/// flash_controller -/// .read_bytes(test_addr, &mut data_read) -/// .unwrap(); -/// -/// assert!(u32::from_le_bytes(data_read) == test_val); -/// ``` +//! # Examples +//! ``` +//! let flash_controller = FlashController::new(flc, icc0, gcr); +//! +//! let test_addr: u32 = 0x10070FF0; +//! let test_val: u32 = 0xCAFEBABE; +//! let mut data_read: [u8; 4] = [0; 4]; +//! +//! // # Safety +//! // Non-read flash operations must not corrupt potentially instructions of the +//! // program. +//! // Callers must ensure that the following condition is met: +//! // * If `address` points to a portion of the program's instructions, `data` must +//! // contain valid instructions that does not introduce undefined behavior. +//! // +//! // It is very difficult to define what would cause undefined behavior when +//! // modifying program instructions. This would almost certainly result +//! // in unwanted and likely undefined behavior. Do so at your own risk. +//! unsafe { +//! flash_controller.page_erase(test_addr, &sys_clk).unwrap(); +//! flash_controller +//! .write(test_addr, &u32::to_le_bytes(test_val), &sys_clk) +//! .unwrap(); +//! } +//! flash_controller +//! .read_bytes(test_addr, &mut data_read) +//! .unwrap(); +//! +//! assert!(u32::from_le_bytes(data_read) == test_val); +//! ``` use crate::peripherals::oscillator::SystemClock; use max78000::{FLC, GCR, ICC0}; @@ -56,107 +55,111 @@ pub enum FlashErr { /// Flash Controller peripheral. pub struct FlashController<'gcr, 'icc> { + #[expect( + unused, + reason = "the unsafe functions we call require us to hold references to these registers" + )] flc: FLC, + #[expect( + unused, + reason = "the unsafe functions we call require us to hold references to these registers" + )] icc: &'icc ICC0, + #[expect( + unused, + reason = "the unsafe functions we call require us to hold references to these registers" + )] gcr: &'gcr GCR, } -impl<'gcr, 'icc> FlashController<'gcr, 'icc> { - /// Creates a new flash controller peripheral. - pub(crate) fn new(flc: FLC, icc: &'icc ICC0, gcr: &'gcr GCR) -> Self { - Self { flc, icc, gcr } - } - - /// Checks the address to see if it is a valid flash memory address - fn check_address_bounds(&self, address_range: core::ops::Range) -> Result<(), FlashErr> { - if (FLASH_MEM_BASE..(FLASH_MEM_BASE + FLASH_MEM_SIZE)).contains(&address_range.start) - && (FLASH_MEM_BASE..(FLASH_MEM_BASE + FLASH_MEM_SIZE)).contains(&address_range.end) - { - Ok(()) - } else { - Err(FlashErr::PtrBoundsErr) - } - } - - /// Unlocks memory protection to allow flash operations +unsafe extern "C" { + /// Reads a little-endian `u32` from flash memory. /// - /// This MUST be called before any non-read flash controller operation. - fn unlock_write_protection(&self) { - self.flc.ctrl().modify(|_, w| w.unlock().unlocked()); - } + /// Panics if any of the following preconditions are not true: + /// - `address` must be 32-bit aligned. + /// - `address` must point to a valid location in flash memory (`0x1000_0000..=0x1007_ffff`). + pub unsafe fn flc_read32_primitive(address: *const u32) -> u32; - /// Locks memory protection. + /// Erases the page at the given address in flash memory. /// - /// This MUST be called after any non-read flash controller operation. - fn lock_write_protection(&self) { - self.flc.ctrl().modify(|_, w| w.unlock().locked()); - } - - /// Checks if the flash controller's clock divisor is correct and if not, sets it. Correct - /// clock frequency is 1 MHz. + /// Safety: + /// - The caller must hold a shared reference to the [`FLC`], [`ICC0`], and [`GCR`] registers. + /// - `address` must point to a valid page within flash memory (`0x1000_0000..=0x1007_ffff`). + /// - `sys_clk_freq` must be equal to `freq / div` where `freq` is the frequency of + /// the current system clock, and `div` is the divider of the system clock. + /// - `sys_clk_freq` must be divisible by one million (`1_000_000`). + /// - If `address` erases a page in the currently-running program's instruction space, + /// it must be rewritten with `write128` before the program reaches those instructions. /// - /// This MUST be called before any non-read flash controller operations. - fn set_clock_divisor(&self, sys_clk: &SystemClock) -> Result<(), FlashErr> { - let sys_clk_freq = sys_clk.get_freq() / sys_clk.get_div() as u32; - if sys_clk_freq % 1_000_000 != 0 { - return Err(FlashErr::FlcClkErr); - } - - let flc_clkdiv = sys_clk_freq / 1_000_000; + /// Panics if any of the following preconditions are not true: + /// - `address` must point to within a valid page in flash space (`0x1000_000..=0x1007_ffff`) + /// - `sys_clk_freq` must be divisible by one million (`1_000_000`). + pub unsafe fn flc_page_erase_primitive(address: *mut u8, sys_clk_freq: u32); - self.flc - .clkdiv() - .modify(|_, w| w.clkdiv().variant(flc_clkdiv as u8)); - - Ok(()) - } - - /// Flushes the flash line buffer and arm instruction cache. + /// Writes a little-endian 128-bit flash word into flash memory. + /// + /// Safety: + /// - The caller must hold a shared reference to the [`FLC`], [`ICC0`], and [`GCR`] registers. + /// - The flash word at `address` must be in the *erased* state (with `page_erase`). + /// - `data` must point to an array of four `u32`s. + /// - `sys_clk_freq` must be equal to `freq / div` where `freq` is the frequency of + /// the current system clock, and `div` is the divider of the system clock. + /// - If `address` writes to an address in the currently-running program's instruction space, + /// it must be valid instructions. /// - /// This MUST be called after any write/erase flash controller operations. - fn flush_icc(&self) -> Result<(), FlashErr> { - self.gcr.sysctrl().modify(|_, w| w.icc0_flush().flush()); - while !self.gcr.sysctrl().read().icc0_flush().bit_is_clear() {} - - // Clear the line fill buffer by reading 2 pages from flash - let ptr = FLASH_MEM_BASE; - let mut empty_buffer = [0; 4]; - self.read_bytes(ptr, &mut empty_buffer)?; - self.read_bytes(ptr + FLASH_PAGE_SIZE, &mut empty_buffer)?; + /// Panics if any of the following preconditions are not true: + /// - `address` must be 128-bit aligned. + /// - The entire flash word at address (bytes `address..address + 16`) must be within + /// the flash memory (`0x1000_0000..=0x1007_ffff`). + /// - `sys_clk_freq` must be divisible by one million (`1_000_000`). + pub unsafe fn flc_write128_primitive( + address: *mut [u32; 4], + data: *const u32, + sys_clk_freq: u32, + ); +} + +/// Checks whether the given address range (exclusive) is within flash space, returning an `Err` if not. +#[inline(always)] +const fn check_address_bounds(address_range: core::ops::Range) -> Result<(), FlashErr> { + if !(FLASH_MEM_BASE <= address_range.start + && address_range.start < FLASH_MEM_BASE + FLASH_MEM_SIZE + && FLASH_MEM_BASE < address_range.end + && address_range.end <= FLASH_MEM_BASE + FLASH_MEM_SIZE) + { + Err(FlashErr::PtrBoundsErr) + } else { Ok(()) } +} - /// Disables instruction cache. - /// - /// This MUST be called before any non-read flash controller operations. - fn disable_icc0(&self) { - self.icc.ctrl().modify(|_, w| w.en().dis()); +impl<'gcr, 'icc> FlashController<'gcr, 'icc> { + /// Creates a new flash controller peripheral. + pub(crate) fn new(flc: FLC, icc: &'icc ICC0, gcr: &'gcr GCR) -> Self { + Self { flc, icc, gcr } } - /// Enables instruction cache. - /// - /// This MUST be called after any non-read flash controller operations. - fn enable_icc0(&self) { - // ensure the cache is invalidated when enabled - self.disable_icc0(); - - self.icc.invalidate().modify(|_, w| w.invalid().variant(1)); - while !self.icc.ctrl().read().rdy().bit_is_set() {} + /// Calculates the correct `sys_clk_freq` from the passed [`SystemClock`] for FLC primitives. + /// Returns an `Err` if the calculated frequency is not a multiple of `1_000_000`. + fn get_clock_divisor(sys_clk: &SystemClock) -> Result { + let sys_clk_freq = sys_clk.get_freq() / sys_clk.get_div() as u32; + if sys_clk_freq % 1_000_000 != 0 { + return Err(FlashErr::FlcClkErr); + } - self.icc.ctrl().modify(|_, w| w.en().en()); - while !self.icc.ctrl().read().rdy().bit_is_set() {} + Ok(sys_clk_freq) } /// Reads data from flash. pub fn read_bytes(&self, address: u32, data: &mut [u8]) -> Result<(), FlashErr> { // change to range check - self.check_address_bounds(address..(address + data.len() as u32))?; + check_address_bounds(address..(address + data.len() as u32))?; let mut next_read_address = address; // read from flash in word chunks let mut word_chunk = data.chunks_exact_mut(4); - for word in word_chunk.borrow_mut() { + for word in &mut word_chunk { // SAFETY: // * src is valid for reads. Because read range is checked at the // beginning of function. @@ -175,7 +178,7 @@ impl<'gcr, 'icc> FlashController<'gcr, 'icc> { } for byte in word_chunk.into_remainder() { - // SAFETY: + // SAFETY:CLOSURE // * src is valid for reads. Because read range is checked at the // beginning of function. // @@ -195,7 +198,6 @@ impl<'gcr, 'icc> FlashController<'gcr, 'icc> { Ok(()) } - /// /// # Safety /// /// Writes must not corrupt potentially executable instructions of the program. @@ -212,11 +214,7 @@ impl<'gcr, 'icc> FlashController<'gcr, 'icc> { data: &[u8], sys_clk: &SystemClock, ) -> Result<(), FlashErr> { - self.check_address_bounds(address..(address + data.len() as u32))?; - - self.set_clock_divisor(sys_clk)?; - - self.disable_icc0(); + check_address_bounds(address..(address + data.len() as u32))?; // Check alignment let mut physical_addr = address; @@ -229,7 +227,7 @@ impl<'gcr, 'icc> FlashController<'gcr, 'icc> { // Write unaligned data if bytes_unaligned > 0 { let unaligned_data = &data[0..core::cmp::min(bytes_unaligned, data.len())]; - self.write_lt_128_unaligned(physical_addr, unaligned_data)?; + self.write_lt_128_unaligned(physical_addr, unaligned_data, sys_clk)?; physical_addr += unaligned_data.len() as u32; } @@ -244,25 +242,37 @@ impl<'gcr, 'icc> FlashController<'gcr, 'icc> { let mut buffer_128_bits: [u32; 4] = [0; 4]; word.enumerate() .for_each(|(idx, chunk)| buffer_128_bits[idx] = chunk); - self.write128(physical_addr, &buffer_128_bits)?; + self.write128(physical_addr, &buffer_128_bits, sys_clk)?; physical_addr += 16; } // remainder from chunks if !chunk_8.remainder().is_empty() { - self.write_lt_128_unaligned(physical_addr, chunk_8.remainder())?; + self.write_lt_128_unaligned(physical_addr, chunk_8.remainder(), sys_clk)?; } - self.flush_icc()?; - - self.enable_icc0(); - Ok(()) } - /// Writes less than 128 bits (16 bytes) of data to flash. Data needs to fit - /// within one flash word (16 bytes). - fn write_lt_128_unaligned(&self, address: u32, data: &[u8]) -> Result<(), FlashErr> { + /// Writes less than 128 bits (16 bytes) of data to flash. + /// Data needs to fit within one flash word (16 bytes). + /// + /// # Safety + /// + /// Writes must not corrupt potentially executable instructions of the program. + /// Callers must ensure that the following condition is met: + /// * If `address` points to a portion of the program's instructions, `data` must + /// contain valid instructions that does not introduce undefined behavior. + /// + /// It is very difficult to define what would cause undefined behavior when + /// modifying program instructions. This would almost certainly result + /// in unwanted and likely undefined behavior. Do so at your own risk. + unsafe fn write_lt_128_unaligned( + &self, + address: u32, + data: &[u8], + sys_clk: &SystemClock, + ) -> Result<(), FlashErr> { // Get byte idx within 128-bit word let byte_idx = (address & 0xF) as usize; @@ -284,34 +294,49 @@ impl<'gcr, 'icc> FlashController<'gcr, 'icc> { new_data[idx] = u32::from_le_bytes(word_chunk.try_into().unwrap()) }); - self.write128(aligned_addr, &new_data) + self.write128(aligned_addr, &new_data, sys_clk) } /// Writes 128 bits (16 bytes) of data to flash. - // make sure to disable ICC with ICC_Disable(); before Running this function - fn write128(&self, address: u32, data: &[u32; 4]) -> Result<(), FlashErr> { + /// Address must be 128-bit aligned. + /// + /// # Safety + /// + /// The flash word at `address` must be in the *erased* state. Otherwise, the write will not + /// occur. + /// + /// Writes must not corrupt potentially executable instructions of the program. + /// Callers must ensure that the following condition is met: + /// * If `address` points to a portion of the program's instructions, `data` must + /// contain valid instructions that does not introduce undefined behavior. + /// + /// It is very difficult to define what would cause undefined behavior when + /// modifying program instructions. This would almost certainly result + /// in unwanted and likely undefined behavior. Do so at your own risk. + unsafe fn write128( + &self, + address: u32, + data: &[u32; 4], + sys_clk: &SystemClock, + ) -> Result<(), FlashErr> { // Check if adddress is 128-bit aligned if address & 0xF > 0 { return Err(FlashErr::AddressNotAligned128); } - - self.unlock_write_protection(); - - // Clear stale errors - self.flc.intr().modify(|_, w| w.af().clear_bit()); - - while !self.flc.ctrl().read().pend().bit_is_clear() {} - - self.flc.addr().modify(|_, w| w.addr().variant(address)); - self.flc.data(0).modify(|_, w| w.data().variant(data[0])); - self.flc.data(1).modify(|_, w| w.data().variant(data[1])); - self.flc.data(2).modify(|_, w| w.data().variant(data[2])); - self.flc.data(3).modify(|_, w| w.data().variant(data[3])); - - self.flc.ctrl().modify(|_, w| w.wr().set_bit()); - while !self.flc.ctrl().read().wr().is_complete() {} - - self.lock_write_protection(); + check_address_bounds(address..address + 16)?; + + let sys_clk_freq = Self::get_clock_divisor(sys_clk)?; + + // SAFETY: per the safety contract of [`flc_write128_primitive`]: + // - we hold a reference (in `self`) to the FLC, ICC0, and GCR registers + // - the caller guarantees that the flash word at `address` in the erased state. + // - `data.as_ptr()` points to an array of 4 u32s. + // - `sys_clk_freq` is calculated as `freq / div` of the current system clock above + // - the caller must guarantee that, if the word at `address` is in instruction memory, + // it contains safe and valid instructions. + critical_section::with(|_| unsafe { + flc_write128_primitive(address as *mut [u32; 4], data.as_ptr(), sys_clk_freq); + }); Ok(()) } @@ -325,29 +350,16 @@ impl<'gcr, 'icc> FlashController<'gcr, 'icc> { /// Behavior is undefined if any of the following conditions are violated: /// * `address` must be in a valid flash page pub unsafe fn page_erase(&self, address: u32, sys_clk: &SystemClock) -> Result<(), FlashErr> { - self.check_address_bounds(address..address)?; - - if self.set_clock_divisor(sys_clk).is_err() { - return Err(FlashErr::FlcClkErr); - } - - self.unlock_write_protection(); - - // Clear stale errors - self.flc.intr().modify(|_, w| w.af().clear_bit()); - - while !self.flc.ctrl().read().pend().bit_is_clear() {} - - self.flc.addr().modify(|_, w| w.addr().variant(address)); - - self.flc.ctrl().modify(|_, w| w.erase_code().erase_page()); - self.flc.ctrl().modify(|_, w| w.pge().set_bit()); - - while !self.flc.ctrl().read().pend().bit_is_clear() {} - - self.lock_write_protection(); - - self.flush_icc()?; + check_address_bounds(address..address + 1)?; + let sys_clk_freq = Self::get_clock_divisor(sys_clk)?; + + // SAFETY: per the safety contract of [`flc_page_erase_primitive`]: + // - we hold a reference (in `self`) to the FLC, ICC0, and GCR registers. + // - `sys_clk_freq` is calculated as `freq / div` of the current system clock above. + // - the caller guarantees safety if the erased page is part of instruction memory. + critical_section::with(|_| unsafe { + flc_page_erase_primitive(address as *mut u8, sys_clk_freq); + }); Ok(()) } diff --git a/link.x.example b/link.x.example new file mode 100644 index 00000000..3c064797 --- /dev/null +++ b/link.x.example @@ -0,0 +1,293 @@ + +/* # Developer notes + +- Symbols that start with a double underscore (__) are considered "private" + +- Symbols that start with a single underscore (_) are considered "semi-public"; they can be + overridden in a user linker script, but should not be referred from user code (e.g. `extern "C" { + static mut __sbss }`). + +- `EXTERN` forces the linker to keep a symbol in the final binary. We use this to make sure a + symbol is not dropped if it appears in or near the front of the linker arguments and "it's not + needed" by any of the preceding objects (linker arguments) + +- `PROVIDE` is used to provide default values that can be overridden by a user linker script + +- On alignment: it's important for correctness that the VMA boundaries of both .bss and .data *and* + the LMA of .data are all 4-byte aligned. These alignments are assumed by the RAM initialization + routine. There's also a second benefit: 4-byte aligned boundaries means that you won't see + "Address (..) is out of bounds" in the disassembly produced by `objdump`. +*/ + +/* Provides information about the memory layout of the device */ +/* This will be provided by the user (see `memory.x`) or by a Board Support Crate */ +INCLUDE memory.x +INCLUDE device.x + +/* # Entry point = reset vector */ +EXTERN(__RESET_VECTOR); +EXTERN(Reset); +ENTRY(Reset); + +/* # Exception vectors */ +/* This is effectively weak aliasing at the linker level */ +/* The user can override any of these aliases by defining the corresponding symbol themselves (cf. + the `exception!` macro) */ +EXTERN(__EXCEPTIONS); /* depends on all the these PROVIDED symbols */ + +EXTERN(DefaultHandler); + +PROVIDE(NonMaskableInt = DefaultHandler); +EXTERN(HardFaultTrampoline); +PROVIDE(MemoryManagement = DefaultHandler); +PROVIDE(BusFault = DefaultHandler); +PROVIDE(UsageFault = DefaultHandler); +PROVIDE(SecureFault = DefaultHandler); +PROVIDE(SVCall = DefaultHandler); +PROVIDE(DebugMonitor = DefaultHandler); +PROVIDE(PendSV = DefaultHandler); +PROVIDE(SysTick = DefaultHandler); + +PROVIDE(DefaultHandler = DefaultHandler_); +PROVIDE(HardFault = HardFault_); + +/* # Interrupt vectors */ +EXTERN(__INTERRUPTS); /* `static` variable similar to `__EXCEPTIONS` */ + +/* # Pre-initialization function */ +/* If the user overrides this using the `pre_init!` macro or by creating a `__pre_init` function, + then the function this points to will be called before the RAM is initialized. */ +PROVIDE(__pre_init = DefaultPreInit); + +/* # Sections */ +SECTIONS +{ + PROVIDE(_ram_start = ORIGIN(RAM)); + PROVIDE(_ram_end = ORIGIN(RAM) + LENGTH(RAM)); + PROVIDE(_stack_start = _ram_end); + + /* ## Sections in FLASH */ + /* ### Vector table */ + .vector_table ORIGIN(FLASH) : + { + __vector_table = .; + + /* Initial Stack Pointer (SP) value. + * We mask the bottom three bits to force 8-byte alignment. + * Despite having an assert for this later, it's possible that a separate + * linker script could override _stack_start after the assert is checked. + */ + LONG(_stack_start & 0xFFFFFFF8); + + /* Reset vector */ + KEEP(*(.vector_table.reset_vector)); /* this is the `__RESET_VECTOR` symbol */ + + /* Exceptions */ + __exceptions = .; /* start of exceptions */ + KEEP(*(.vector_table.exceptions)); /* this is the `__EXCEPTIONS` symbol */ + __eexceptions = .; /* end of exceptions */ + + /* Device specific interrupts */ + KEEP(*(.vector_table.interrupts)); /* this is the `__INTERRUPTS` symbol */ + } > FLASH + + PROVIDE(_stext = ADDR(.vector_table) + SIZEOF(.vector_table)); + + /* ### .text */ + .text _stext : + { + __stext = .; + *(.Reset); + + *(.text .text.*); + + /* The HardFaultTrampoline uses the `b` instruction to enter `HardFault`, + so must be placed close to it. */ + *(.HardFaultTrampoline); + *(.HardFault.*); + + . = ALIGN(4); /* Pad .text to the alignment to workaround overlapping load section bug in old lld */ + __etext = .; + } > FLASH + + /* ### .rodata */ + .rodata : ALIGN(4) + { + . = ALIGN(4); + __srodata = .; + *(.rodata .rodata.*); + + /* 4-byte align the end (VMA) of this section. + This is required by LLD to ensure the LMA of the following .data + section will have the correct alignment. */ + . = ALIGN(4); + __erodata = .; + } > FLASH + + /* Add a section for the FLC function operations */ + .analogsucks : ALIGN(4) + { + . = ALIGN(4); + __sanalogsucks = .; + *(.analogsucks .analogsucks.*); + . = ALIGN(4); + __eanalogsucks = .; + } > ANALOGSUCKS AT>FLASH + + /* ## Sections in RAM */ + /* ### .data */ + .data : ALIGN(4) + { + . = ALIGN(4); + __sdata = .; + *(.data .data.*); + . = ALIGN(4); /* 4-byte align the end (VMA) of this section */ + } > RAM AT>FLASH + /* Allow sections from user `memory.x` injected using `INSERT AFTER .data` to + * use the .data loading mechanism by pushing __edata. Note: do not change + * output region or load region in those user sections! */ + . = ALIGN(4); + __edata = .; + + /* LMA of .data */ + __sidata = LOADADDR(.data); + + __sianalogsucks = LOADADDR(.analogsucks); + + /* ### .gnu.sgstubs + This section contains the TrustZone-M veneers put there by the Arm GNU linker. */ + /* Security Attribution Unit blocks must be 32 bytes aligned. */ + /* Note that this pads the FLASH usage to 32 byte alignment. */ + .gnu.sgstubs : ALIGN(32) + { + . = ALIGN(32); + __veneer_base = .; + *(.gnu.sgstubs*) + . = ALIGN(32); + } > FLASH + /* Place `__veneer_limit` outside the `.gnu.sgstubs` section because veneers are + * always inserted last in the section, which would otherwise be _after_ the `__veneer_limit` symbol. + */ + . = ALIGN(32); + __veneer_limit = .; + + /* ### .bss */ + .bss (NOLOAD) : ALIGN(4) + { + . = ALIGN(4); + __sbss = .; + *(.bss .bss.*); + *(COMMON); /* Uninitialized C statics */ + . = ALIGN(4); /* 4-byte align the end (VMA) of this section */ + } > RAM + /* Allow sections from user `memory.x` injected using `INSERT AFTER .bss` to + * use the .bss zeroing mechanism by pushing __ebss. Note: do not change + * output region or load region in those user sections! */ + . = ALIGN(4); + __ebss = .; + + /* ### .uninit */ + .uninit (NOLOAD) : ALIGN(4) + { + . = ALIGN(4); + __suninit = .; + *(.uninit .uninit.*); + . = ALIGN(4); + __euninit = .; + } > RAM + + /* Place the heap right after `.uninit` in RAM */ + PROVIDE(__sheap = __euninit); + + /* Place stack end at the end of allocated RAM */ + PROVIDE(_stack_end = __euninit); + + /* ## .got */ + /* Dynamic relocations are unsupported. This section is only used to detect relocatable code in + the input files and raise an error if relocatable code is found */ + .got (NOLOAD) : + { + KEEP(*(.got .got.*)); + } + + /* ## Discarded sections */ + /DISCARD/ : + { + /* Unused exception related info that only wastes space */ + *(.ARM.exidx); + *(.ARM.exidx.*); + *(.ARM.extab.*); + } +} + +/* Do not exceed this mark in the error messages below | */ +/* # Alignment checks */ +ASSERT(ORIGIN(FLASH) % 4 == 0, " +ERROR(cortex-m-rt): the start of the FLASH region must be 4-byte aligned"); + +ASSERT(ORIGIN(RAM) % 4 == 0, " +ERROR(cortex-m-rt): the start of the RAM region must be 4-byte aligned"); + +ASSERT(__sdata % 4 == 0 && __edata % 4 == 0, " +BUG(cortex-m-rt): .data is not 4-byte aligned"); + +ASSERT(__sidata % 4 == 0, " +BUG(cortex-m-rt): the LMA of .data is not 4-byte aligned"); + +ASSERT(__sbss % 4 == 0 && __ebss % 4 == 0, " +BUG(cortex-m-rt): .bss is not 4-byte aligned"); + +ASSERT(__sheap % 4 == 0, " +BUG(cortex-m-rt): start of .heap is not 4-byte aligned"); + +ASSERT(_stack_start % 8 == 0, " +ERROR(cortex-m-rt): stack start address is not 8-byte aligned. +If you have set _stack_start, check it's set to an address which is a multiple of 8 bytes. +If you haven't, stack starts at the end of RAM by default. Check that both RAM +origin and length are set to multiples of 8 in the `memory.x` file."); + +ASSERT(_stack_end % 4 == 0, " +ERROR(cortex-m-rt): end of stack is not 4-byte aligned"); + +ASSERT(_stack_start >= _stack_end, " +ERROR(cortex-m-rt): stack end address is not below stack start."); + +/* # Position checks */ + +/* ## .vector_table + * + * If the *start* of exception vectors is not 8 bytes past the start of the + * vector table, then we somehow did not place the reset vector, which should + * live 4 bytes past the start of the vector table. + */ +ASSERT(__exceptions == ADDR(.vector_table) + 0x8, " +BUG(cortex-m-rt): the reset vector is missing"); + +ASSERT(__eexceptions == ADDR(.vector_table) + 0x40, " +BUG(cortex-m-rt): the exception vectors are missing"); + +ASSERT(SIZEOF(.vector_table) > 0x40, " +ERROR(cortex-m-rt): The interrupt vectors are missing. +Possible solutions, from most likely to less likely: +- Link to a svd2rust generated device crate +- Check that you actually use the device/hal/bsp crate in your code +- Disable the 'device' feature of cortex-m-rt to build a generic application (a dependency +may be enabling it) +- Supply the interrupt handlers yourself. Check the documentation for details."); + +/* ## .text */ +ASSERT(ADDR(.vector_table) + SIZEOF(.vector_table) <= _stext, " +ERROR(cortex-m-rt): The .text section can't be placed inside the .vector_table section +Set _stext to an address greater than the end of .vector_table (See output of `nm`)"); + +ASSERT(_stext >= ORIGIN(FLASH) && _stext < ORIGIN(FLASH) + LENGTH(FLASH), " +ERROR(cortex-m-rt): The .text section must be placed inside the FLASH memory. +Set _stext to an address within the FLASH region."); + +/* # Other checks */ +ASSERT(SIZEOF(.got) == 0, " +ERROR(cortex-m-rt): .got section detected in the input object files +Dynamic relocations are not supported. If you are linking to C code compiled using +the 'cc' crate then modify your build script to compile the C code _without_ +the -fPIC flag. See the documentation of the `cc::Build.pic` method for details."); +/* Do not exceed this mark in the error messages above | */ diff --git a/memory.x.example b/memory.x.example new file mode 100644 index 00000000..a489a662 --- /dev/null +++ b/memory.x.example @@ -0,0 +1,10 @@ +MEMORY +{ + FLASH (rx) : ORIGIN = 0x10000000, LENGTH = 512K + STACK (rw) : ORIGIN = 0x20000000, LENGTH = 110K + ANALOGSUCKS (rx) : ORIGIN = ORIGIN(STACK) + LENGTH(STACK), LENGTH = 10K + RAM (rw) : ORIGIN = ORIGIN(ANALOGSUCKS) + LENGTH(ANALOGSUCKS), LENGTH = 128K - LENGTH(STACK) - LENGTH(ANALOGSUCKS) +} + +_stack_start = ORIGIN(STACK) + LENGTH(STACK); +_stack_end = ORIGIN(STACK); diff --git a/tests/Cargo.toml b/tests/Cargo.toml index c4c76731..9c62f087 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -12,9 +12,15 @@ cortex-m = { version = "0.7.7", features = ["critical-section-single-core"] } cortex-m-rt = { version = "0.7.3", features = ["set-sp", "set-vtor"] } cortex-m-semihosting = "0.5.0" libm = "0.2.8" -max78000-hal = { version = "0.1.0", path = "../hal", features = ["rt"] } +max78000-hal = { version = "0.1.0", path = "../hal", features = [ + "rt", + "flc-ram", +] } panic-semihosting = "0.6.0" -embedded-hal = { version = "1.0"} +embedded-hal = { version = "1.0" } [features] low_frequency_test = ["max78000-hal/low_frequency"] + +[build-dependencies] +cc = "1.2.16" diff --git a/tests/build.rs b/tests/build.rs index 42107c62..7170390e 100644 --- a/tests/build.rs +++ b/tests/build.rs @@ -13,7 +13,15 @@ fn main() { .write_all(include_bytes!("memory.x")) .unwrap(); + File::create(out.join("link.x")) + .unwrap() + .write_all(include_bytes!("link.x")) + .unwrap(); + + println!("cargo:rustc-link-arg=--nmagic"); + // Only re-run the build script when this file, memory.x, or link.x is changed. println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-changed=memory.x"); + println!("cargo:rerun-if-changed=link.x"); } diff --git a/tests/link.x b/tests/link.x new file mode 100644 index 00000000..3c064797 --- /dev/null +++ b/tests/link.x @@ -0,0 +1,293 @@ + +/* # Developer notes + +- Symbols that start with a double underscore (__) are considered "private" + +- Symbols that start with a single underscore (_) are considered "semi-public"; they can be + overridden in a user linker script, but should not be referred from user code (e.g. `extern "C" { + static mut __sbss }`). + +- `EXTERN` forces the linker to keep a symbol in the final binary. We use this to make sure a + symbol is not dropped if it appears in or near the front of the linker arguments and "it's not + needed" by any of the preceding objects (linker arguments) + +- `PROVIDE` is used to provide default values that can be overridden by a user linker script + +- On alignment: it's important for correctness that the VMA boundaries of both .bss and .data *and* + the LMA of .data are all 4-byte aligned. These alignments are assumed by the RAM initialization + routine. There's also a second benefit: 4-byte aligned boundaries means that you won't see + "Address (..) is out of bounds" in the disassembly produced by `objdump`. +*/ + +/* Provides information about the memory layout of the device */ +/* This will be provided by the user (see `memory.x`) or by a Board Support Crate */ +INCLUDE memory.x +INCLUDE device.x + +/* # Entry point = reset vector */ +EXTERN(__RESET_VECTOR); +EXTERN(Reset); +ENTRY(Reset); + +/* # Exception vectors */ +/* This is effectively weak aliasing at the linker level */ +/* The user can override any of these aliases by defining the corresponding symbol themselves (cf. + the `exception!` macro) */ +EXTERN(__EXCEPTIONS); /* depends on all the these PROVIDED symbols */ + +EXTERN(DefaultHandler); + +PROVIDE(NonMaskableInt = DefaultHandler); +EXTERN(HardFaultTrampoline); +PROVIDE(MemoryManagement = DefaultHandler); +PROVIDE(BusFault = DefaultHandler); +PROVIDE(UsageFault = DefaultHandler); +PROVIDE(SecureFault = DefaultHandler); +PROVIDE(SVCall = DefaultHandler); +PROVIDE(DebugMonitor = DefaultHandler); +PROVIDE(PendSV = DefaultHandler); +PROVIDE(SysTick = DefaultHandler); + +PROVIDE(DefaultHandler = DefaultHandler_); +PROVIDE(HardFault = HardFault_); + +/* # Interrupt vectors */ +EXTERN(__INTERRUPTS); /* `static` variable similar to `__EXCEPTIONS` */ + +/* # Pre-initialization function */ +/* If the user overrides this using the `pre_init!` macro or by creating a `__pre_init` function, + then the function this points to will be called before the RAM is initialized. */ +PROVIDE(__pre_init = DefaultPreInit); + +/* # Sections */ +SECTIONS +{ + PROVIDE(_ram_start = ORIGIN(RAM)); + PROVIDE(_ram_end = ORIGIN(RAM) + LENGTH(RAM)); + PROVIDE(_stack_start = _ram_end); + + /* ## Sections in FLASH */ + /* ### Vector table */ + .vector_table ORIGIN(FLASH) : + { + __vector_table = .; + + /* Initial Stack Pointer (SP) value. + * We mask the bottom three bits to force 8-byte alignment. + * Despite having an assert for this later, it's possible that a separate + * linker script could override _stack_start after the assert is checked. + */ + LONG(_stack_start & 0xFFFFFFF8); + + /* Reset vector */ + KEEP(*(.vector_table.reset_vector)); /* this is the `__RESET_VECTOR` symbol */ + + /* Exceptions */ + __exceptions = .; /* start of exceptions */ + KEEP(*(.vector_table.exceptions)); /* this is the `__EXCEPTIONS` symbol */ + __eexceptions = .; /* end of exceptions */ + + /* Device specific interrupts */ + KEEP(*(.vector_table.interrupts)); /* this is the `__INTERRUPTS` symbol */ + } > FLASH + + PROVIDE(_stext = ADDR(.vector_table) + SIZEOF(.vector_table)); + + /* ### .text */ + .text _stext : + { + __stext = .; + *(.Reset); + + *(.text .text.*); + + /* The HardFaultTrampoline uses the `b` instruction to enter `HardFault`, + so must be placed close to it. */ + *(.HardFaultTrampoline); + *(.HardFault.*); + + . = ALIGN(4); /* Pad .text to the alignment to workaround overlapping load section bug in old lld */ + __etext = .; + } > FLASH + + /* ### .rodata */ + .rodata : ALIGN(4) + { + . = ALIGN(4); + __srodata = .; + *(.rodata .rodata.*); + + /* 4-byte align the end (VMA) of this section. + This is required by LLD to ensure the LMA of the following .data + section will have the correct alignment. */ + . = ALIGN(4); + __erodata = .; + } > FLASH + + /* Add a section for the FLC function operations */ + .analogsucks : ALIGN(4) + { + . = ALIGN(4); + __sanalogsucks = .; + *(.analogsucks .analogsucks.*); + . = ALIGN(4); + __eanalogsucks = .; + } > ANALOGSUCKS AT>FLASH + + /* ## Sections in RAM */ + /* ### .data */ + .data : ALIGN(4) + { + . = ALIGN(4); + __sdata = .; + *(.data .data.*); + . = ALIGN(4); /* 4-byte align the end (VMA) of this section */ + } > RAM AT>FLASH + /* Allow sections from user `memory.x` injected using `INSERT AFTER .data` to + * use the .data loading mechanism by pushing __edata. Note: do not change + * output region or load region in those user sections! */ + . = ALIGN(4); + __edata = .; + + /* LMA of .data */ + __sidata = LOADADDR(.data); + + __sianalogsucks = LOADADDR(.analogsucks); + + /* ### .gnu.sgstubs + This section contains the TrustZone-M veneers put there by the Arm GNU linker. */ + /* Security Attribution Unit blocks must be 32 bytes aligned. */ + /* Note that this pads the FLASH usage to 32 byte alignment. */ + .gnu.sgstubs : ALIGN(32) + { + . = ALIGN(32); + __veneer_base = .; + *(.gnu.sgstubs*) + . = ALIGN(32); + } > FLASH + /* Place `__veneer_limit` outside the `.gnu.sgstubs` section because veneers are + * always inserted last in the section, which would otherwise be _after_ the `__veneer_limit` symbol. + */ + . = ALIGN(32); + __veneer_limit = .; + + /* ### .bss */ + .bss (NOLOAD) : ALIGN(4) + { + . = ALIGN(4); + __sbss = .; + *(.bss .bss.*); + *(COMMON); /* Uninitialized C statics */ + . = ALIGN(4); /* 4-byte align the end (VMA) of this section */ + } > RAM + /* Allow sections from user `memory.x` injected using `INSERT AFTER .bss` to + * use the .bss zeroing mechanism by pushing __ebss. Note: do not change + * output region or load region in those user sections! */ + . = ALIGN(4); + __ebss = .; + + /* ### .uninit */ + .uninit (NOLOAD) : ALIGN(4) + { + . = ALIGN(4); + __suninit = .; + *(.uninit .uninit.*); + . = ALIGN(4); + __euninit = .; + } > RAM + + /* Place the heap right after `.uninit` in RAM */ + PROVIDE(__sheap = __euninit); + + /* Place stack end at the end of allocated RAM */ + PROVIDE(_stack_end = __euninit); + + /* ## .got */ + /* Dynamic relocations are unsupported. This section is only used to detect relocatable code in + the input files and raise an error if relocatable code is found */ + .got (NOLOAD) : + { + KEEP(*(.got .got.*)); + } + + /* ## Discarded sections */ + /DISCARD/ : + { + /* Unused exception related info that only wastes space */ + *(.ARM.exidx); + *(.ARM.exidx.*); + *(.ARM.extab.*); + } +} + +/* Do not exceed this mark in the error messages below | */ +/* # Alignment checks */ +ASSERT(ORIGIN(FLASH) % 4 == 0, " +ERROR(cortex-m-rt): the start of the FLASH region must be 4-byte aligned"); + +ASSERT(ORIGIN(RAM) % 4 == 0, " +ERROR(cortex-m-rt): the start of the RAM region must be 4-byte aligned"); + +ASSERT(__sdata % 4 == 0 && __edata % 4 == 0, " +BUG(cortex-m-rt): .data is not 4-byte aligned"); + +ASSERT(__sidata % 4 == 0, " +BUG(cortex-m-rt): the LMA of .data is not 4-byte aligned"); + +ASSERT(__sbss % 4 == 0 && __ebss % 4 == 0, " +BUG(cortex-m-rt): .bss is not 4-byte aligned"); + +ASSERT(__sheap % 4 == 0, " +BUG(cortex-m-rt): start of .heap is not 4-byte aligned"); + +ASSERT(_stack_start % 8 == 0, " +ERROR(cortex-m-rt): stack start address is not 8-byte aligned. +If you have set _stack_start, check it's set to an address which is a multiple of 8 bytes. +If you haven't, stack starts at the end of RAM by default. Check that both RAM +origin and length are set to multiples of 8 in the `memory.x` file."); + +ASSERT(_stack_end % 4 == 0, " +ERROR(cortex-m-rt): end of stack is not 4-byte aligned"); + +ASSERT(_stack_start >= _stack_end, " +ERROR(cortex-m-rt): stack end address is not below stack start."); + +/* # Position checks */ + +/* ## .vector_table + * + * If the *start* of exception vectors is not 8 bytes past the start of the + * vector table, then we somehow did not place the reset vector, which should + * live 4 bytes past the start of the vector table. + */ +ASSERT(__exceptions == ADDR(.vector_table) + 0x8, " +BUG(cortex-m-rt): the reset vector is missing"); + +ASSERT(__eexceptions == ADDR(.vector_table) + 0x40, " +BUG(cortex-m-rt): the exception vectors are missing"); + +ASSERT(SIZEOF(.vector_table) > 0x40, " +ERROR(cortex-m-rt): The interrupt vectors are missing. +Possible solutions, from most likely to less likely: +- Link to a svd2rust generated device crate +- Check that you actually use the device/hal/bsp crate in your code +- Disable the 'device' feature of cortex-m-rt to build a generic application (a dependency +may be enabling it) +- Supply the interrupt handlers yourself. Check the documentation for details."); + +/* ## .text */ +ASSERT(ADDR(.vector_table) + SIZEOF(.vector_table) <= _stext, " +ERROR(cortex-m-rt): The .text section can't be placed inside the .vector_table section +Set _stext to an address greater than the end of .vector_table (See output of `nm`)"); + +ASSERT(_stext >= ORIGIN(FLASH) && _stext < ORIGIN(FLASH) + LENGTH(FLASH), " +ERROR(cortex-m-rt): The .text section must be placed inside the FLASH memory. +Set _stext to an address within the FLASH region."); + +/* # Other checks */ +ASSERT(SIZEOF(.got) == 0, " +ERROR(cortex-m-rt): .got section detected in the input object files +Dynamic relocations are not supported. If you are linking to C code compiled using +the 'cc' crate then modify your build script to compile the C code _without_ +the -fPIC flag. See the documentation of the `cc::Build.pic` method for details."); +/* Do not exceed this mark in the error messages above | */ diff --git a/tests/memory.x b/tests/memory.x index aabdf586..a489a662 100644 --- a/tests/memory.x +++ b/tests/memory.x @@ -1,13 +1,10 @@ MEMORY { - FLASH (rx) : ORIGIN = 0x10000000, LENGTH = 512K - STACK (rw) : ORIGIN = 0x20000000, LENGTH = 110K - RAM (rw) : ORIGIN = ORIGIN(STACK) + LENGTH(STACK), LENGTH = 128K - LENGTH(STACK) + FLASH (rx) : ORIGIN = 0x10000000, LENGTH = 512K + STACK (rw) : ORIGIN = 0x20000000, LENGTH = 110K + ANALOGSUCKS (rx) : ORIGIN = ORIGIN(STACK) + LENGTH(STACK), LENGTH = 10K + RAM (rw) : ORIGIN = ORIGIN(ANALOGSUCKS) + LENGTH(ANALOGSUCKS), LENGTH = 128K - LENGTH(STACK) - LENGTH(ANALOGSUCKS) } -/* -Add a block of memory for the stack before the RAM block, so that a stack overflow leaks into -reserved space and flash memory, instead of .data and .bss. -*/ - _stack_start = ORIGIN(STACK) + LENGTH(STACK); +_stack_end = ORIGIN(STACK); diff --git a/tests/run.sh b/tests/run.sh index a1eb710e..e9dda837 100755 --- a/tests/run.sh +++ b/tests/run.sh @@ -12,7 +12,7 @@ if [ -z "$SERIAL" ]; then fi # Open GDB server with OpenOCD using the board's config. -/opt/MaximSDK/Tools/OpenOCD/openocd -s /opt/MaximSDK/Tools/OpenOCD/scripts \ +openocd \ -f interface/cmsis-dap.cfg \ -f target/max78000.cfg \ -c "adapter serial $SERIAL; init; reset init" & # ben's board diff --git a/tests/src/main.rs b/tests/src/main.rs index 2e6b4b14..fadb25b8 100644 --- a/tests/src/main.rs +++ b/tests/src/main.rs @@ -58,6 +58,26 @@ fn main() -> ! { .configure_timer_2(Oscillator::ISO, Prescaler::_4096) .build(); + // run FLC tests with semi-hosting + flc_tests::run_flc_tests( + &mut stdout, + manager.flash_controller().unwrap(), + manager.system_clock().unwrap(), + ); + + { + let mut uart = manager.build_uart().unwrap().build(115200); + + // run FLC tests with UART + flc_tests::run_flc_tests( + &mut uart, + manager.flash_controller().unwrap(), + manager.system_clock().unwrap(), + ); + + // UART instance is tossed here + } + flc_tests::run_flc_tests( &mut stdout, manager.flash_controller().unwrap(), diff --git a/tests/src/tests/flc_tests.rs b/tests/src/tests/flc_tests.rs index f8397173..05345200 100644 --- a/tests/src/tests/flc_tests.rs +++ b/tests/src/tests/flc_tests.rs @@ -1,7 +1,6 @@ //! Flash controller tests use core::fmt::Write; -use cortex_m_semihosting::hio; use max78000_hal::peripherals::flash_controller::{FlashController, FlashErr}; use max78000_hal::peripherals::oscillator::{ Ibro, IbroDivider, IbroFrequency, Iso, IsoDivider, IsoFrequency, Oscillator, SystemClock, @@ -14,8 +13,8 @@ use max78000_hal::peripherals::PeripheralHandle; /// [`flash_write_full_outbounds`], /// [`flash_write_partially_outbound_beginning`], /// [`flash_write_full_partially_outbound_end`]. -pub fn run_flc_tests( - stdout: &mut hio::HostStream, +pub fn run_flc_tests( + stdout: &mut T, flash_controller: PeripheralHandle<'_, FlashController<'_, '_>>, mut sys_clk: PeripheralHandle<'_, SystemClock<'_, '_>>, ) {