From 4c870c5213b7a7312b95d7351aae3b23fbab438a Mon Sep 17 00:00:00 2001 From: 9names <60134748+9names@users.noreply.github.com> Date: Sat, 6 Aug 2022 14:58:43 +1000 Subject: [PATCH] RAM-based interrupt vector tables (#321) * Add struct VectorTable to represent an interrupt vector table * Add member function to VectorTable to initialise based on the current Interrupt Vector Table from VTOR * Add member function to VectorTable to register an extern "C" function to call on interrupt * Add example using VectorTable to demonstrate initialisation and interrupt function registration --- rp2040-hal/examples/vector_table.rs | 190 ++++++++++++++++++++++++++++ rp2040-hal/src/lib.rs | 1 + rp2040-hal/src/vector_table.rs | 84 ++++++++++++ 3 files changed, 275 insertions(+) create mode 100644 rp2040-hal/examples/vector_table.rs create mode 100644 rp2040-hal/src/vector_table.rs diff --git a/rp2040-hal/examples/vector_table.rs b/rp2040-hal/examples/vector_table.rs new file mode 100644 index 000000000..b8eee08f8 --- /dev/null +++ b/rp2040-hal/examples/vector_table.rs @@ -0,0 +1,190 @@ +//! # RAM Vector Table example +//! +//! This application demonstrates how to create a new Interrupt Vector Table in RAM. +//! To demonstrate the extra utility of this, we also replace an entry in the Vector Table +//! with a new one. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// The macro for our start-up function +use cortex_m_rt::entry; + +// Ensure we halt the program on panic +use panic_halt as _; + +// Alias for our HAL crate +use rp2040_hal as hal; + +// A shorter alias for the Peripheral Access Crate +use hal::pac; + +// Some traits we need +use core::cell::RefCell; +use cortex_m::interrupt::Mutex; +use embedded_hal::digital::v2::ToggleableOutputPin; +use embedded_time::duration::Microseconds; +use embedded_time::fixed_point::FixedPoint; +use pac::interrupt; +use rp2040_hal::clocks::Clock; +use rp2040_hal::timer::Alarm; +use rp2040_hal::vector_table::VectorTable; + +// Memory that will hold our vector table in RAM +static mut RAM_VTABLE: VectorTable = VectorTable::new(); + +// Give our LED and Alarm a type alias to make it easier to refer to them +type LedAndAlarm = ( + hal::gpio::Pin, + hal::timer::Alarm0, +); + +// Place our LED and Alarm type in a static variable, so we can access it from interrupts +static mut LED_AND_ALARM: Mutex>> = Mutex::new(RefCell::new(None)); + +// Period that each of the alarms will be set for - 1 second and 300ms respectively +const SLOW_BLINK_INTERVAL_US: u32 = 1_000_000; +const FAST_BLINK_INTERVAL_US: u32 = 300_000; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables are initialised. +/// +/// The function configures the RP2040 peripherals, then toggles a GPIO pin in +/// an infinite loop. If there is an LED connected to that pin, it will blink. +#[entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + let core = pac::CorePeripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Need to make a reference to the Peripheral Base at this scope to avoid confusing the borrow checker + let ppb = &mut pac.PPB; + unsafe { + // Copy the vector table that cortex_m_rt produced into the RAM vector table + RAM_VTABLE.init(ppb); + // Replace the function that is called on Alarm0 interrupts with a new one + RAM_VTABLE.register_handler(pac::Interrupt::TIMER_IRQ_0 as usize, timer_irq0_replacement); + } + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + // Create simple delay + let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().integer()); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure GPIO25 as an output + let led_pin = pins.gpio25.into_push_pull_output(); + + let mut timer = hal::Timer::new(pac.TIMER, &mut pac.RESETS); + cortex_m::interrupt::free(|cs| { + let mut alarm = timer.alarm_0().unwrap(); + // Schedule an alarm in 1 second + let _ = alarm.schedule(Microseconds(SLOW_BLINK_INTERVAL_US)); + // Enable generating an interrupt on alarm + alarm.enable_interrupt(); + // Move alarm into ALARM, so that it can be accessed from interrupts + unsafe { + LED_AND_ALARM.borrow(cs).replace(Some((led_pin, alarm))); + } + }); + // Unmask the timer0 IRQ so that it will generate an interrupt + unsafe { + pac::NVIC::unmask(pac::Interrupt::TIMER_IRQ_0); + } + + // After 5 seconds, switch to our modified vector rable + delay.delay_ms(5000); + unsafe { + cortex_m::interrupt::free(|_| { + RAM_VTABLE.activate(ppb); + }); + } + + loop { + // Wait for an interrupt to fire before doing any more work + cortex_m::asm::wfi(); + } +} + +// Regular interrupt handler for Alarm0. The `interrupt` macro will perform some transformations to ensure +// that this interrupt entry ends up in the vector table. +#[interrupt] +fn TIMER_IRQ_0() { + cortex_m::interrupt::free(|cs| { + // Temporarily take our LED_AND_ALARM + let ledalarm = unsafe { LED_AND_ALARM.borrow(cs).take() }; + if let Some((mut led, mut alarm)) = ledalarm { + // Clear the alarm interrupt or this interrupt service routine will keep firing + alarm.clear_interrupt(); + // Schedule a new alarm after SLOW_BLINK_INTERVAL_US have passed (1 second) + let _ = alarm.schedule(Microseconds(SLOW_BLINK_INTERVAL_US)); + // Blink the LED so we know we hit this interrupt + led.toggle().unwrap(); + // Return LED_AND_ALARM into our static variable + unsafe { + LED_AND_ALARM + .borrow(cs) + .replace_with(|_| Some((led, alarm))); + } + } + }); +} + +// This is the function we will use to replace TIMER_IRQ_0 in our RAM Vector Table +extern "C" fn timer_irq0_replacement() { + cortex_m::interrupt::free(|cs| { + let ledalarm = unsafe { LED_AND_ALARM.borrow(cs).take() }; + if let Some((mut led, mut alarm)) = ledalarm { + // Clear the alarm interrupt or this interrupt service routine will keep firing + alarm.clear_interrupt(); + // Schedule a new alarm after FAST_BLINK_INTERVAL_US have passed (300 milliseconds) + let _ = alarm.schedule(Microseconds(FAST_BLINK_INTERVAL_US)); + led.toggle().unwrap(); + // Return LED_AND_ALARM into our static variable + unsafe { + LED_AND_ALARM + .borrow(cs) + .replace_with(|_| Some((led, alarm))); + } + } + }); +} + +// End of file diff --git a/rp2040-hal/src/lib.rs b/rp2040-hal/src/lib.rs index 6e2aa8f37..f143b9008 100644 --- a/rp2040-hal/src/lib.rs +++ b/rp2040-hal/src/lib.rs @@ -40,6 +40,7 @@ pub mod timer; pub mod typelevel; pub mod uart; pub mod usb; +pub mod vector_table; pub mod watchdog; pub mod xosc; diff --git a/rp2040-hal/src/vector_table.rs b/rp2040-hal/src/vector_table.rs new file mode 100644 index 000000000..57ea35a81 --- /dev/null +++ b/rp2040-hal/src/vector_table.rs @@ -0,0 +1,84 @@ +//! Interrupt vector table utilities +//! +//! Provide functionality to switch to another vector table using the +//! Vector Table Offset Register (VTOR) of the Cortex-M0+ +//! Also provides types and utilities for copying a vector table into RAM + +/// Entry for a Vector in the Interrupt Vector Table. +/// +/// Each entry in the Vector table is a union with usize to allow it to be 0 initialized via const initializer +/// +/// Implementation borrowed from https://docs.rs/cortex-m-rt/0.7.1/cortex_m_rt/index.html#__interrupts +#[derive(Clone, Copy)] +union Vector { + handler: extern "C" fn(), + reserved: usize, +} + +/// Data type for a properly aligned interrupt vector table +/// +/// The VTOR register can only point to a 256 byte offsets - see +/// [Cortex-M0+ Devices Generic User Guide](https://developer.arm.com/documentation/dui0662/b/The-Cortex-M0--Processor/Exception-model/Vector-table) - +/// so that is our required alignment. +/// The vector table length depends on the number of interrupts the system supports. +/// The first 16 words are defined in the ARM Cortex-M spec. +/// The M0+ cores on RP2040 have 32 interrupts, of which only 26 are wired to external interrupt +/// signals - but the last 6 can be used for software interrupts so leave room for them +#[repr(C, align(256))] +pub struct VectorTable { + /// SP + Reset vector + 14 exceptions + 32 interrupts = 48 entries (192 bytes) in an RP2040 core's VectorTable + table: [Vector; 48], +} + +impl VectorTable { + /// Create a new vector table. All entries will point to 0 - you must call init() + /// on this to copy the current vector table before setting it as active + pub const fn new() -> VectorTable { + VectorTable { + table: [Vector { reserved: 0 }; 48], + } + } + + /// Initialise our vector table by copying the current table on top of it + pub fn init(&mut self, ppb: &mut pac::PPB) { + let vector_table = ppb.vtor.read().bits(); + unsafe { + crate::rom_data::memcpy44( + &mut self.table as *mut _ as *mut u32, + vector_table as *const u32, + 192, + ) + }; + } + + /// Dynamically register a function as being an interrupt handler + pub fn register_handler(&mut self, interrupt_idx: usize, interrupt_fn: extern "C" fn()) { + self.table[16 + interrupt_idx].handler = interrupt_fn; + } + + /// Set the stack pointer address in a VectorTable. This will be used on Reset + /// + /// # Safety + /// There is no checking whether this is a valid stack pointer address + pub unsafe fn set_sp(&mut self, stack_pointer_address: usize) { + self.table[0].reserved = stack_pointer_address; + } + + /// Set the entry-point address in a VectorTable. This will be used on Reset + /// + /// # Safety + /// There is no checking whether this is a valid entry point + pub unsafe fn set_entry(&mut self, entry_address: usize) { + self.table[1].reserved = entry_address; + } + + /// Switch the current core to use this Interrupt Vector Table + /// + /// # Safety + /// Until the vector table has valid entries, activating it will cause an unhandled hardfault! + /// You must call init() first. + pub unsafe fn activate(&mut self, ppb: &mut pac::PPB) { + ppb.vtor + .write(|w| w.bits(&mut self.table as *mut _ as *mut u32 as u32)); + } +}