Skip to content

Commit

Permalink
Timer is working on QEMU Cortex-R5
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathanpallant committed Jul 23, 2024
1 parent bf57197 commit bc59077
Show file tree
Hide file tree
Showing 12 changed files with 302 additions and 86 deletions.
1 change: 0 additions & 1 deletion qemu-cortex-r5-app/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 9 additions & 4 deletions qemu-cortex-r5-app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 2 additions & 0 deletions qemu-cortex-r5-app/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ fn main() -> Result<(), Box<dyn Error>> {
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)))
Expand All @@ -236,6 +237,7 @@ fn main() -> Result<(), Box<dyn Error>> {
cc::Build::new()
.include(&tx_common_inc)
.include(&tx_port_inc)
.flag("-g")
.file("src/tx_initialize_low_level.S")
.compile("startup");

Expand Down
3 changes: 2 additions & 1 deletion qemu-cortex-r5-app/commands.gdb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
target extended-remote :1234
layout split

break kmain
break qemu_cortex_r5_app::panic
2 changes: 1 addition & 1 deletion qemu-cortex-r5-app/linker.ld
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
34 changes: 0 additions & 34 deletions qemu-cortex-r5-app/src/critical_section.rs

This file was deleted.

23 changes: 12 additions & 11 deletions qemu-cortex-r5-app/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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#"
Expand All @@ -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
Expand Down
134 changes: 106 additions & 28 deletions qemu-cortex-r5-app/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<RefCell<Option<Uart<0x101f_1000>>>>,
inner: UnsafeCell<Option<Uart<0x101f_1000>>>,
mutex: MaybeUninit<UnsafeCell<threadx_sys::TX_MUTEX_STRUCT>>,
}

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<Uart<0x101f_1000>> {
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);
}
}
}

Expand All @@ -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()...");
Expand All @@ -66,15 +111,15 @@ extern "C" fn tx_application_define(_first_unused_memory: *mut core::ffi::c_void

let byte_pool = {
static BYTE_POOL: StaticCell<threadx_sys::TX_BYTE_POOL> = 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 {
threadx_sys::_tx_byte_pool_create(
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()
}
Expand Down Expand Up @@ -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<threadx_sys::TX_THREAD> = StaticCell::new();
let thread = THREAD_STORAGE.uninit();
static THREAD_STORAGE2: StaticCell<threadx_sys::TX_THREAD> = StaticCell::new();
let thread = THREAD_STORAGE2.uninit();
unsafe {
let res = threadx_sys::_tx_thread_create(
thread.as_mut_ptr(),
Expand Down Expand Up @@ -190,21 +235,54 @@ 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();
}

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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! A driver the Arm PL011 Uart
//! Code for the Arm PL011 Uart
//!
//! Written by Jonathan Pallant at Ferrous Systems
//!
Expand Down
Loading

0 comments on commit bc59077

Please sign in to comment.