From bc59077247fbd86041ab6414587f3721b1da77dc Mon Sep 17 00:00:00 2001 From: Jonathan Pallant Date: Tue, 23 Jul 2024 16:24:08 +0100 Subject: [PATCH] Timer is working on QEMU Cortex-R5 --- qemu-cortex-r5-app/Cargo.lock | 1 - qemu-cortex-r5-app/Cargo.toml | 13 +- qemu-cortex-r5-app/build.rs | 2 + qemu-cortex-r5-app/commands.gdb | 3 +- qemu-cortex-r5-app/linker.ld | 2 +- qemu-cortex-r5-app/src/critical_section.rs | 34 ----- qemu-cortex-r5-app/src/lib.rs | 23 +-- qemu-cortex-r5-app/src/main.rs | 134 ++++++++++++++---- .../src/{virt_uart.rs => pl011_uart.rs} | 2 +- qemu-cortex-r5-app/src/pl190_vic.rs | 80 +++++++++++ qemu-cortex-r5-app/src/sp804_timer.rs | 87 ++++++++++++ .../src/tx_initialize_low_level.S | 7 +- 12 files changed, 302 insertions(+), 86 deletions(-) delete mode 100644 qemu-cortex-r5-app/src/critical_section.rs rename qemu-cortex-r5-app/src/{virt_uart.rs => pl011_uart.rs} (98%) create mode 100644 qemu-cortex-r5-app/src/pl190_vic.rs create mode 100644 qemu-cortex-r5-app/src/sp804_timer.rs diff --git a/qemu-cortex-r5-app/Cargo.lock b/qemu-cortex-r5-app/Cargo.lock index 165d69d..5c776cc 100644 --- a/qemu-cortex-r5-app/Cargo.lock +++ b/qemu-cortex-r5-app/Cargo.lock @@ -253,7 +253,6 @@ version = "0.1.0" dependencies = [ "byte-strings", "cc", - "critical-section", "embedded-alloc", "static_cell", "threadx-sys", diff --git a/qemu-cortex-r5-app/Cargo.toml b/qemu-cortex-r5-app/Cargo.toml index ff5183b..0b5d601 100644 --- a/qemu-cortex-r5-app/Cargo.toml +++ b/qemu-cortex-r5-app/Cargo.toml @@ -7,14 +7,19 @@ license = "MIT OR Apache-2.0" description = "A simple ARMv7-R demo application that runs ThreadX in QEMU and compiles with Ferrocene" [dependencies] -critical-section = { version = "1.1.2", features = ["restore-state-bool"] } embedded-alloc = "0.5.1" static_cell = "2.1.0" threadx-sys = { path = "../threadx-sys" } byte-strings = "0.3.1" -[profile.release] -opt-level = "s" - [build-dependencies] cc = "1.1.6" + +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = true +incremental = false +lto = false +opt-level = 1 +overflow-checks = true diff --git a/qemu-cortex-r5-app/build.rs b/qemu-cortex-r5-app/build.rs index eeba1ed..ac2585d 100644 --- a/qemu-cortex-r5-app/build.rs +++ b/qemu-cortex-r5-app/build.rs @@ -228,6 +228,7 @@ fn main() -> Result<(), Box> { cc::Build::new() .include(&tx_common_inc) .include(&tx_port_inc) + .flag("-g") .define("TX_ENABLE_VFP_SUPPORT", "1") .files(TX_PORT_FILES.iter().map(|&s| tx_port_dir.join(s))) .files(TX_COMMON_FILES.iter().map(|&s| tx_common_dir.join(s))) @@ -236,6 +237,7 @@ fn main() -> Result<(), Box> { cc::Build::new() .include(&tx_common_inc) .include(&tx_port_inc) + .flag("-g") .file("src/tx_initialize_low_level.S") .compile("startup"); diff --git a/qemu-cortex-r5-app/commands.gdb b/qemu-cortex-r5-app/commands.gdb index bbfd2a5..9fdbd45 100644 --- a/qemu-cortex-r5-app/commands.gdb +++ b/qemu-cortex-r5-app/commands.gdb @@ -1,3 +1,4 @@ target extended-remote :1234 layout split - +break kmain +break qemu_cortex_r5_app::panic diff --git a/qemu-cortex-r5-app/linker.ld b/qemu-cortex-r5-app/linker.ld index cf182ab..bc03eaa 100644 --- a/qemu-cortex-r5-app/linker.ld +++ b/qemu-cortex-r5-app/linker.ld @@ -21,7 +21,7 @@ SECTIONS { /* Allocate room for stack. This must be big enough for the IRQ, FIQ, and SYS stack if nested interrupts are enabled. */ . = ALIGN(8) ; - . += 0x10000; + . += 0x100000; _sp = . - 16 ; _stack_top = ABSOLUTE(.) ; } > RAM diff --git a/qemu-cortex-r5-app/src/critical_section.rs b/qemu-cortex-r5-app/src/critical_section.rs deleted file mode 100644 index 2961d92..0000000 --- a/qemu-cortex-r5-app/src/critical_section.rs +++ /dev/null @@ -1,34 +0,0 @@ -//! Code that implements the `critical-section` traits on Cortex-R. - -struct SingleCoreCriticalSection; -critical_section::set_impl!(SingleCoreCriticalSection); - -/// Reads the CPU interrupt status bit from CPSR -/// -/// Returns true if interrupts enabled. -#[inline] -pub fn interrupts_enabled() -> bool { - const CPSR_I_BIT: u32 = 1 << 7; - let r: u32; - unsafe { - core::arch::asm!("mrs {}, CPSR", out(reg) r, options(nomem, nostack, preserves_flags)) - }; - r & CPSR_I_BIT != 0 -} - -unsafe impl critical_section::Impl for SingleCoreCriticalSection { - unsafe fn acquire() -> critical_section::RawRestoreState { - let was_active = interrupts_enabled(); - core::arch::asm!("cpsid i", options(nomem, nostack, preserves_flags)); - core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); - was_active - } - - unsafe fn release(was_active: critical_section::RawRestoreState) { - // Only re-enable interrupts if they were enabled before the critical section. - if was_active { - core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); - core::arch::asm!("cpsie i", options(nomem, nostack, preserves_flags)); - } - } -} diff --git a/qemu-cortex-r5-app/src/lib.rs b/qemu-cortex-r5-app/src/lib.rs index 8f48406..5298199 100644 --- a/qemu-cortex-r5-app/src/lib.rs +++ b/qemu-cortex-r5-app/src/lib.rs @@ -1,7 +1,8 @@ #![no_std] -pub mod critical_section; -pub mod virt_uart; +pub mod pl011_uart; +pub mod pl190_vic; +pub mod sp804_timer; core::arch::global_asm!( r#" @@ -14,15 +15,15 @@ core::arch::global_asm!( // Work around https://github.com/rust-lang/rust/issues/127269 .fpu vfp3-d16 -__vectors: - LDR pc, STARTUP @ Reset goes to startup function - LDR pc, UNDEFINED @ Undefined handler - LDR pc, SWI @ Software interrupt handler - LDR pc, PREFETCH @ Prefetch exception handler - LDR pc, ABORT @ Abort exception handler - LDR pc, RESERVED @ Reserved exception handler - LDR pc, IRQ @ IRQ interrupt handler - LDR pc, FIQ @ FIQ interrupt handler +_vectors: + LDR pc, STARTUP @ Reset goes to startup function 0x00 + LDR pc, UNDEFINED @ Undefined handler 0x04 + LDR pc, SWI @ Software interrupt handler 0x08 + LDR pc, PREFETCH @ Prefetch exception handler 0x0C + LDR pc, ABORT @ Abort exception handler 0x10 + LDR pc, RESERVED @ Reserved exception handler 0x14 + LDR pc, IRQ @ IRQ interrupt handler 0x18 + LDR pc, FIQ @ FIQ interrupt handler 0x1C STARTUP: .word _start @ Reset goes to C startup function diff --git a/qemu-cortex-r5-app/src/main.rs b/qemu-cortex-r5-app/src/main.rs index 7887677..416f21f 100644 --- a/qemu-cortex-r5-app/src/main.rs +++ b/qemu-cortex-r5-app/src/main.rs @@ -7,37 +7,58 @@ #![no_main] use byte_strings::c; -use core::{cell::RefCell, fmt::Write}; -use critical_section::Mutex; -use qemu_cortex_r5_app::virt_uart::Uart; +use core::{cell::UnsafeCell, fmt::Write as _, mem::MaybeUninit}; +use qemu_cortex_r5_app::{ + pl011_uart::Uart, + pl190_vic, + sp804_timer::{self, Timer0}, +}; use static_cell::StaticCell; static BUILD_SLUG: Option<&str> = option_env!("BUILD_SLUG"); -const DEMO_STACK_SIZE: usize = 1024; +const DEMO_STACK_SIZE: usize = 16384; +const DEMO_POOL_SIZE: usize = (DEMO_STACK_SIZE * 2) + 16384; static UART: GlobalUart = GlobalUart::new(); +unsafe impl Sync for GlobalUart {} + struct GlobalUart { - inner: Mutex>>>, + inner: UnsafeCell>>, + mutex: MaybeUninit>, } impl GlobalUart { /// Create a new, empty, global UART wrapper const fn new() -> GlobalUart { GlobalUart { - inner: Mutex::new(RefCell::new(None)), + inner: UnsafeCell::new(None), + mutex: MaybeUninit::uninit(), } } - /// Store a new UART at run-time + /// Store a new UART at run-time, and initialise the ThreadX mutex that + /// holds it. + /// + /// # Safety /// - /// Gives you back the old one, if any. - fn store(&self, uart: Uart<0x101f_1000>) -> Option> { - critical_section::with(|cs| { - let mut uart_ref = self.inner.borrow_ref_mut(cs); - uart_ref.replace(uart) - }) + /// Only call from init, not when threads are running, and only call it + /// once. + unsafe fn store(&self, uart: Uart<0x101f_1000>) { + // Init the ThreadX mutex + unsafe { + // init mutex + threadx_sys::_tx_mutex_create( + UnsafeCell::raw_get(self.mutex.as_ptr()), + "my_mutex\0".as_ptr() as _, + 0, + ); + // unsafely store UART object + let ptr = self.inner.get(); + let mut_ret = &mut *ptr; + *mut_ret = Some(uart); + } } } @@ -46,16 +67,40 @@ impl GlobalUart { impl core::fmt::Write for &GlobalUart { /// Write the string to the inner UART, with a lock held fn write_str(&mut self, s: &str) -> core::fmt::Result { - critical_section::with(|cs| { - let mut maybe_uart = self.inner.borrow_ref_mut(cs); - let Some(uart) = maybe_uart.as_mut() else { - return Err(core::fmt::Error); - }; - uart.write_str(s) - }) + // Grab ThreadX mutex + unsafe { + threadx_sys::_tx_mutex_get( + UnsafeCell::raw_get(self.mutex.as_ptr()), + threadx_sys::TX_WAIT_FOREVER, + ); + core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::Acquire); + } + + // # Safety + // + // We hold the ThreadX Mutex at this point + let uart_option_ref = unsafe { &mut *self.inner.get() }; + let Some(uart) = uart_option_ref else { + return Err(core::fmt::Error); + }; + + let result = uart.write_str(s); + + // Drop the UART ref, then the threadX mutex + let _ = uart; + unsafe { + core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::Release); + threadx_sys::_tx_mutex_put(UnsafeCell::raw_get(self.mutex.as_ptr())); + } + + result } } +/// Initialise our application. +/// +/// ThreadX calls this function during scheduler start-up. We use it to create +/// some threads. #[no_mangle] extern "C" fn tx_application_define(_first_unused_memory: *mut core::ffi::c_void) { _ = writeln!(&UART, "In tx_application_define()..."); @@ -66,7 +111,7 @@ extern "C" fn tx_application_define(_first_unused_memory: *mut core::ffi::c_void let byte_pool = { static BYTE_POOL: StaticCell = StaticCell::new(); - static BYTE_POOL_STORAGE: StaticCell<[u8; 32768]> = StaticCell::new(); + static BYTE_POOL_STORAGE: StaticCell<[u8; DEMO_POOL_SIZE]> = StaticCell::new(); let byte_pool = BYTE_POOL.uninit(); let byte_pool_storage = BYTE_POOL_STORAGE.uninit(); unsafe { @@ -74,7 +119,7 @@ extern "C" fn tx_application_define(_first_unused_memory: *mut core::ffi::c_void byte_pool.as_mut_ptr(), c!("byte-pool0").as_ptr() as *mut threadx_sys::CHAR, byte_pool_storage.as_mut_ptr() as *mut _, - core::mem::size_of_val(&BYTE_POOL_STORAGE) as u32, + DEMO_POOL_SIZE as u32, ); byte_pool.assume_init_mut() } @@ -139,8 +184,8 @@ extern "C" fn tx_application_define(_first_unused_memory: *mut core::ffi::c_void panic!("No space for stack"); } - static THREAD_STORAGE: StaticCell = StaticCell::new(); - let thread = THREAD_STORAGE.uninit(); + static THREAD_STORAGE2: StaticCell = StaticCell::new(); + let thread = THREAD_STORAGE2.uninit(); unsafe { let res = threadx_sys::_tx_thread_create( thread.as_mut_ptr(), @@ -190,14 +235,32 @@ extern "C" fn my_thread(value: u32) { /// It is called by the start-up code in `lib.rs`. #[no_mangle] pub extern "C" fn kmain() { - let uart0 = unsafe { Uart::new_uart0() }; - UART.store(uart0); + // Create a UART + let mut uart0 = unsafe { Uart::new_uart0() }; _ = writeln!( - &UART, + uart0, "Hello, this is version {}!", BUILD_SLUG.unwrap_or("unknown") ); - _ = writeln!(&UART, "Entering ThreadX kernel..."); + unsafe { + UART.store(uart0); + } + + let mut timer0 = unsafe { Timer0::new_timer0() }; + timer0.init( + 10_000, + sp804_timer::Mode::AutoReload, + sp804_timer::Interrupts::Enabled, + ); + + // Now we need to enable the Timer0 interrupt and connect it to IRQ on this core + // It's on PIC interrupt 4. + let mut vic = unsafe { pl190_vic::Interrupt::new() }; + vic.init(); + vic.enable_interrupt(4); + + timer0.start(); + unsafe { threadx_sys::_tx_initialize_kernel_enter(); } @@ -205,6 +268,21 @@ pub extern "C" fn kmain() { panic!("Kernel exited"); } +/// Called from the main interrupt handler +#[no_mangle] +unsafe extern "C" fn handle_interrupt() { + extern "C" { + fn _tx_timer_interrupt(); + } + + if Timer0::is_pending() { + unsafe { + _tx_timer_interrupt(); + } + Timer0::clear_interrupt(); + } +} + /// Called when the application raises an unrecoverable `panic!`. /// /// Prints the panic to the console and then exits QEMU using a semihosting diff --git a/qemu-cortex-r5-app/src/virt_uart.rs b/qemu-cortex-r5-app/src/pl011_uart.rs similarity index 98% rename from qemu-cortex-r5-app/src/virt_uart.rs rename to qemu-cortex-r5-app/src/pl011_uart.rs index fab913b..be3f1a6 100644 --- a/qemu-cortex-r5-app/src/virt_uart.rs +++ b/qemu-cortex-r5-app/src/pl011_uart.rs @@ -1,4 +1,4 @@ -//! A driver the Arm PL011 Uart +//! Code for the Arm PL011 Uart //! //! Written by Jonathan Pallant at Ferrous Systems //! diff --git a/qemu-cortex-r5-app/src/pl190_vic.rs b/qemu-cortex-r5-app/src/pl190_vic.rs new file mode 100644 index 0000000..13f60c7 --- /dev/null +++ b/qemu-cortex-r5-app/src/pl190_vic.rs @@ -0,0 +1,80 @@ +//! Code for the Arm PL190 Vector Interrupt Controller +//! +//! Written by Jonathan Pallant at Ferrous Systems +//! +//! Copyright (c) Ferrous Systems, 2024 + +/// A driver for a virtual PL190 Vector Interrupt Controller +/// +/// It might skip some important initialisation, but it works on QEMU. +pub struct Interrupt(); + +impl Interrupt<0x10140000> { + /// Create an interrupt controller driver + /// + /// # Safety + /// + /// Only construct one object per Interrupt Controller at any given time. + pub unsafe fn new() -> Interrupt<0x10140000> { + Interrupt() + } +} + +impl Interrupt { + const BASE_PTR: *mut u32 = ADDR as *mut u32; + + // These are in 32-bit word offsets (so * 4 to get byte offsets) + + const IRQ_STATUS_OFFSET: usize = 0x00 >> 2; + const INT_SELECT_OFFSET: usize = 0x0C >> 2; + const INT_EN_OFFSET: usize = 0x10 >> 2; + const DEF_VECT_ADDR_OFFSET: usize = 0x34 >> 2; + const VECT_CTRL_N_START_OFFSET: usize = 0x200 >> 2; + + const CNTL_ENABLE: u32 = 1 << 5; + + const NUM_PRIOS: u8 = 16; + const NUM_IRQS: u8 = 32; + + /// Get the address of the control register for a particular interrupt vector. + const fn prio_control_addr(prio: u8) -> *mut u32 { + if prio >= Self::NUM_PRIOS { + panic!("bad prio"); + } + unsafe { Self::BASE_PTR.add(Self::VECT_CTRL_N_START_OFFSET + (prio as usize)) } + } + + /// Initialise the interrupt controller by setting up all the vectors + pub fn init(&mut self) { + unsafe { + // Set the first 16 vectors to point to the first 16 sources + for i in 0..Self::NUM_PRIOS { + Self::prio_control_addr(i).write_volatile(Self::CNTL_ENABLE | u32::from(i)); + } + // Setup default vector - points at IRQ handler in vector table + Self::BASE_PTR + .add(Self::DEF_VECT_ADDR_OFFSET) + .write_volatile(0x18); + // Every interrupt is an IRQ not an FIQ + Self::BASE_PTR + .add(Self::INT_SELECT_OFFSET) + .write_volatile(0x0000_0000); + } + } + + pub fn enable_interrupt(&mut self, interrupt: u8) { + if interrupt > Self::NUM_IRQS { + panic!("Bad IRQ"); + } + + unsafe { + Self::BASE_PTR + .add(Self::INT_EN_OFFSET) + .write_volatile(1 << interrupt); + } + } + + pub fn read_interrupt_status() -> u32 { + unsafe { Self::BASE_PTR.add(Self::IRQ_STATUS_OFFSET).read_volatile() } + } +} diff --git a/qemu-cortex-r5-app/src/sp804_timer.rs b/qemu-cortex-r5-app/src/sp804_timer.rs new file mode 100644 index 0000000..6d08826 --- /dev/null +++ b/qemu-cortex-r5-app/src/sp804_timer.rs @@ -0,0 +1,87 @@ +//! Code for the Arm SP804 Timer +//! +//! Written by Jonathan Pallant at Ferrous Systems +//! +//! Copyright (c) Ferrous Systems, 2024 + +/// Supported timer modes +pub enum Mode { + AutoReload = 0, + SingleShot = 1, +} + +/// Supported interrupt options +pub enum Interrupts { + Disabled = 0, + Enabled = 1 << 5, +} + +pub type Timer0 = Timer<0x101e_2000>; + +/// A driver for a virtual SP804 Timer +/// +/// It probably skips some important initialisation, but it works on QEMU. +pub struct Timer(); + +impl Timer0 { + /// Create a new Timer object for Timer0 + /// + /// # Safety + /// + /// Only construct one object per Timer at any given time. + pub unsafe fn new_timer0() -> Self { + Timer() + } +} + +impl Timer { + const BASE_PTR: *mut u32 = ADDR as *mut u32; + + const LOAD_REGISTER: usize = 0x00 >> 2; + const CTRL_OFFSET: usize = 0x08 >> 2; + const ICR_OFFSET: usize = 0x0C >> 2; + const MIS_OFFSET: usize = 0x14 >> 2; + + const CTRL_TIMERSIZE_32: u32 = 1 << 1; + const CTRL_TIMERMODE: u32 = 1 << 6; + const CTRL_TIMEREN: u32 = 1 << 7; + + /// Initialise the timer + pub fn init(&mut self, load_value: u32, mode: Mode, interrupt: Interrupts) { + unsafe { + Self::BASE_PTR + .add(Self::LOAD_REGISTER) + .write_volatile(load_value); + let settings = + Self::CTRL_TIMERSIZE_32 | Self::CTRL_TIMERMODE | mode as u32 | interrupt as u32; + Self::BASE_PTR + .add(Self::CTRL_OFFSET) + .write_volatile(settings); + } + } + + /// Start the timer + pub fn start(&mut self) { + unsafe { + let time1_ctrl = Self::BASE_PTR.add(2); + let mut temp = time1_ctrl.read_volatile(); + temp |= Self::CTRL_TIMEREN; + time1_ctrl.write_volatile(temp); + } + } + + pub fn is_pending() -> bool { + let value = unsafe { Self::BASE_PTR.add(Self::MIS_OFFSET).read_volatile() }; + (value & 1) != 0 + } + + /// Clear a pending interrupt + pub fn clear_interrupt() { + // Write anything here to clear the interrupt + unsafe { + Self::BASE_PTR.add(Self::ICR_OFFSET).write_volatile(1); + } + } +} + +// End of file diff --git a/qemu-cortex-r5-app/src/tx_initialize_low_level.S b/qemu-cortex-r5-app/src/tx_initialize_low_level.S index a347127..ca36af6 100644 --- a/qemu-cortex-r5-app/src/tx_initialize_low_level.S +++ b/qemu-cortex-r5-app/src/tx_initialize_low_level.S @@ -232,11 +232,8 @@ __tx_irq_processing_return: BL _tx_thread_irq_nesting_start #endif @ -@ /* For debug purpose, execute the timer interrupt processing here. In -@ a real system, some kind of status indication would have to be checked -@ before the timer interrupt handler could be called. */ -@ - BL _tx_timer_interrupt @ Timer interrupt handler + /* Use Rust to handle the interrupt */ + BL handle_interrupt @ @ @ /* If interrupt nesting was started earlier, the end of interrupt nesting