diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0c4007ab..9e600605 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -406,6 +406,50 @@ jobs: export PATH=/opt/qemu/bin:$PATH just test-qemu-v8r + # Run some SMP programs in QEMU 9 for Armv8-R + # These tests build with nightly as pinned by the rust-toolchain.toml file, because they include Tier 3 targets + test-qemu-v8r-smp: + runs-on: ubuntu-24.04 + needs: [build-all] + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install Just + uses: taiki-e/install-action@just + - name: Install Dependencies + run: | + sudo apt-get -y update + sudo apt-get -y install libpixman-1-0 libfdt1 libglib2.0-0t64 + - name: Install custom QEMU into /opt + run: | + curl -sSL https://github.com/jonathanpallant/qemu9-for-ubuntu-2404/releases/download/qemu-9.2.3%2Bbuild0/qemu-9.2.3-ubuntu-24.04.tar.gz | sudo tar xvzf - -C / + - name: Run tests in QEMU + run: | + export PATH=/opt/qemu/bin:$PATH + just test-qemu-v8r-smp + + # Run some EL2 programs in QEMU 9 for Armv8-R + # These tests build with nightly as pinned by the rust-toolchain.toml file, because they include Tier 3 targets + test-qemu-v8r-el2: + runs-on: ubuntu-24.04 + needs: [build-all] + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install Just + uses: taiki-e/install-action@just + - name: Install Dependencies + run: | + sudo apt-get -y update + sudo apt-get -y install libpixman-1-0 libfdt1 libglib2.0-0t64 + - name: Install custom QEMU into /opt + run: | + curl -sSL https://github.com/jonathanpallant/qemu9-for-ubuntu-2404/releases/download/qemu-9.2.3%2Bbuild0/qemu-9.2.3-ubuntu-24.04.tar.gz | sudo tar xvzf - -C / + - name: Run tests in QEMU + run: | + export PATH=/opt/qemu/bin:$PATH + just test-qemu-v8r-el2 + # Gather all the above QEMU jobs together for the purposes of getting an overall pass-fail test-qemu-all: runs-on: ubuntu-24.04 @@ -417,6 +461,8 @@ jobs: test-qemu-v7a, test-qemu-v7r, test-qemu-v8r, + test-qemu-v8r-smp, + test-qemu-v8r-el2, ] steps: - run: /bin/true diff --git a/.gitignore b/.gitignore index 666f29a1..e58f52da 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ examples/mps3-an536/target examples/mps3-an536/target-d32 examples/mps3-an536-smp/target examples/mps3-an536-smp/target-d32 +examples/mps3-an536-el2/target +examples/mps3-an536-el2/target-d32 examples/versatileab/target examples/versatileab/target-d32 Cargo.lock diff --git a/.vscode/settings.json b/.vscode/settings.json index fbd3ad9b..1c0f63d0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,5 +11,6 @@ "examples/versatileab/Cargo.toml", "examples/mps3-an536/Cargo.toml", "examples/mps3-an536-smp/Cargo.toml", + "examples/mps3-an536-el2/Cargo.toml" ] } diff --git a/Cargo.toml b/Cargo.toml index 2209a19b..61edd8f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ exclude = [ "examples/versatileab", "examples/mps3-an536", "examples/mps3-an536-smp", + "examples/mps3-an536-el2", ] members = [ "aarch32-cpu", diff --git a/aarch32-cpu/src/generic_timer/el2.rs b/aarch32-cpu/src/generic_timer/el2.rs index 89a56e95..fd34c915 100644 --- a/aarch32-cpu/src/generic_timer/el2.rs +++ b/aarch32-cpu/src/generic_timer/el2.rs @@ -8,7 +8,7 @@ use super::{El1PhysicalTimer, El1VirtualTimer, GenericTimer}; pub struct El2PhysicalTimer(El1PhysicalTimer); impl El2PhysicalTimer { - /// Create an EL1 Generic Timer handle + /// Create an EL2 Physical Timer handle /// /// # Safety /// @@ -79,7 +79,7 @@ impl GenericTimer for El2PhysicalTimer { pub struct El2VirtualTimer(El1VirtualTimer); impl El2VirtualTimer { - /// Create an EL1 Generic Timer handle + /// Create an EL2 Generic Timer handle /// /// # Safety /// diff --git a/aarch32-cpu/src/register/hyp/hcptr.rs b/aarch32-cpu/src/register/hyp/hcptr.rs index b4057aac..864adea7 100644 --- a/aarch32-cpu/src/register/hyp/hcptr.rs +++ b/aarch32-cpu/src/register/hyp/hcptr.rs @@ -3,10 +3,22 @@ use crate::register::{SysReg, SysRegRead, SysRegWrite}; /// HCPTR (*Hyp Architectural Feature Trap Register*) -#[derive(Debug, Copy, Clone)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[bitbybit::bitfield(u32, debug, defmt_fields(feature = "defmt"))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct Hcptr(pub u32); +pub struct Hcptr { + /// TCPAC - Traps EL1 accesses to the CPACR to Hyp mode + #[bit(31, rw)] + tcpac: bool, + /// TTA - Traps System register accesses to all implemented trace registers to Hyp mode + #[bit(20, rw)] + tta: bool, + /// TASE - Traps execution of Advanced SIMD instructions to Hyp mode when the value of HCPTR.TCP10 is 0. + #[bit(15, rw)] + tase: bool, + /// TCP - Trap accesses to Advanced SIMD and floating-point functionality to Hyp mode + #[bit(10, rw)] + tcp: bool, +} impl SysReg for Hcptr { const CP: u32 = 15; @@ -22,7 +34,18 @@ impl Hcptr { #[inline] /// Reads HCPTR (*Hyp Architectural Feature Trap Register*) pub fn read() -> Hcptr { - unsafe { Self(::read_raw()) } + unsafe { Self::new_with_raw_value(::read_raw()) } + } + + /// Modify HCPTR (*Hyp Architectural Feature Trap Register*) + #[inline] + pub fn modify(f: F) + where + F: FnOnce(&mut Self), + { + let mut value = Self::read(); + f(&mut value); + Self::write(value); } } @@ -31,13 +54,9 @@ impl crate::register::SysRegWrite for Hcptr {} impl Hcptr { #[inline] /// Writes HCPTR (*Hyp Architectural Feature Trap Register*) - /// - /// # Safety - /// - /// Ensure that this value is appropriate for this register - pub unsafe fn write(value: Self) { + pub fn write(value: Self) { unsafe { - ::write_raw(value.0); + ::write_raw(value.raw_value()); } } } diff --git a/aarch32-cpu/src/register/hyp/hcr.rs b/aarch32-cpu/src/register/hyp/hcr.rs index 9da41175..9424f92b 100644 --- a/aarch32-cpu/src/register/hyp/hcr.rs +++ b/aarch32-cpu/src/register/hyp/hcr.rs @@ -3,10 +3,106 @@ use crate::register::{SysReg, SysRegRead, SysRegWrite}; /// HCR (*Hyp Configuration Register*) -#[derive(Debug, Copy, Clone)] +#[bitbybit::bitfield(u32, debug, defmt_fields(feature = "defmt"))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Hcr { + /// TCPAC - Traps EL1 accesses to the CPACR to Hyp mode + #[bit(31, rw)] + tcpac: bool, + /// TRVM - Trap Reads of Memory controls + #[bit(30, rw)] + trvm: bool, + /// HCD - HVC instruction disable + #[bit(29, rw)] + hcd: bool, + /// TGE - Trap General Exceptions from EL0 + #[bit(27, rw)] + tge: bool, + /// TVM - Trap Memory controls + #[bit(26, rw)] + tvm: bool, + /// TPU - Trap cache maintenance instructions that operate to the Point of Unification + #[bit(24, rw)] + tpu: bool, + /// TPC - Trap data or unified cache maintenance instructions that operate to the Point of Coherency + #[bit(23, rw)] + tpc: bool, + /// TSW - Trap data or unified cache maintenance instructions that operate by Set/Way + #[bit(22, rw)] + tsw: bool, + /// TAC - Trap Auxiliary Control Registers + #[bit(21, rw)] + tac: bool, + /// TIDCP - Trap IMPLEMENTATION DEFINED functionality + #[bit(20, rw)] + tidcp: bool, + /// TID3 - Trap ID group 3 + #[bit(18, rw)] + tid3: bool, + /// TID2 - Trap ID group 2 + #[bit(17, rw)] + tid2: bool, + /// TID1 - Trap ID group 1 + #[bit(16, rw)] + tid1: bool, + /// TID0 - Trap ID group 0 + #[bit(15, rw)] + tid0: bool, + /// TWE - Traps EL0 and EL1 execution of WFE instructions to Hyp mode + #[bit(14, rw)] + twe: bool, + /// TWI - Traps EL0 and EL1 execution of WFI instructions to Hyp mode + #[bit(13, rw)] + twi: bool, + /// DC - Default Cacheability + #[bit(12, rw)] + dc: bool, + /// BSU - Barrier Shareability upgrade. + #[bits(10..=11, rw)] + bsu: Bsu, + /// FB - Force broadcast + #[bit(9, rw)] + fb: bool, + /// VA - Virtual SError interrupt exception + #[bit(8, rw)] + va: bool, + /// VI - Virtual IRQ exception + #[bit(7, rw)] + vi: bool, + /// VF - Virtual FIQ exception + #[bit(6, rw)] + vf: bool, + /// AMO - SError interrupt Mask Override + #[bit(5, rw)] + amo: bool, + /// IMO - IRQ Mask Override + #[bit(4, rw)] + imo: bool, + /// FMO - FIQ Mask Override + #[bit(3, rw)] + fmo: bool, + /// SWIO - Set/Way Invalidation Override + #[bit(1, rw)] + swio: bool, + /// VM - Virtualization enable + #[bit(0, rw)] + vm: bool, +} + +/// Barrier Shareability upgrade +/// +/// This field determines the minimum Shareability domain that is applied to any +/// barrier instruction executed from EL1 or EL0 +#[bitbybit::bitenum(u2, exhaustive = true)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct Hcr(pub u32); +#[derive(Debug, PartialEq, Eq)] +pub enum Bsu { + NoEffect = 0b00, + InnerShareable = 0b01, + OuterShareable = 0b10, + FullSystem = 0b11, +} impl SysReg for Hcr { const CP: u32 = 15; @@ -22,7 +118,7 @@ impl Hcr { #[inline] /// Reads HCR (*Hyp Configuration Register*) pub fn read() -> Hcr { - unsafe { Self(::read_raw()) } + unsafe { Self::new_with_raw_value(::read_raw()) } } } @@ -31,13 +127,20 @@ impl crate::register::SysRegWrite for Hcr {} impl Hcr { #[inline] /// Writes HCR (*Hyp Configuration Register*) - /// - /// # Safety - /// - /// Ensure that this value is appropriate for this register - pub unsafe fn write(value: Self) { + pub fn write(value: Self) { unsafe { - ::write_raw(value.0); + ::write_raw(value.raw_value()); } } + + #[inline] + /// Modify HCR (*Hyp Configuration Register*) + pub fn modify(f: F) + where + F: FnOnce(&mut Self), + { + let mut value = Self::read(); + f(&mut value); + Self::write(value); + } } diff --git a/aarch32-cpu/src/register/hyp/hsr.rs b/aarch32-cpu/src/register/hyp/hsr.rs index 40c2fe8f..b499db95 100644 --- a/aarch32-cpu/src/register/hyp/hsr.rs +++ b/aarch32-cpu/src/register/hyp/hsr.rs @@ -2,7 +2,7 @@ use crate::register::{SysReg, SysRegRead, SysRegWrite}; -use arbitrary_int::u25; +use arbitrary_int::{u2, u25, u3, u4, u6}; /// HSR (*Hyp Syndrome Register*) #[bitbybit::bitfield(u32, debug, defmt_bitfields(feature = "defmt"))] @@ -27,6 +27,17 @@ pub struct Hsr { iss: u25, } +impl Hsr { + /// Get the ISS value from the HSR + pub fn get_iss(&self) -> Option { + if let Ok(ec) = self.ec() { + Some(ec.decode_iss(self.iss())) + } else { + None + } + } +} + #[bitbybit::bitenum(u6, exhaustive = false)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -52,6 +63,263 @@ pub enum ExceptionClass { DataAbortFromCurrent = 0b10_0101, } +/// A decoded ISS +/// +/// ISS is a 25 bit field whose meaning varies depending on the value of the EC field. +#[derive(Debug, Clone)] +pub enum Iss { + Unknown(IssUnknown), + TrappedWfiWfe(IssTrappedWfiWfe), + TrappedCp15McrMrc(IssTrappedMcrMrc), + TrappedCp15McrrMrrc(IssTrappedMcrrMrrc), + TrappedCp14McrMrc(IssTrappedMcrMrc), + TrappedLdcStc(IssTrappedLdcStc), + TrappedFpu(IssTrappedFpu), + TrappedVmrs(IssTrappedVmrs), + TrappedCp14McrrMrrc(IssTrappedMcrrMrrc), + IllegalAArch32Eret, + Svc(IssCall), + Hvc(IssCall), + Smc(IssSmc), + PrefetchAbortFromLower(IssPrefetchAbort), + PrefetchAbortFromCurrent(IssPrefetchAbort), + PcAlignment, + DataAbortFromLower(IssDataAbort), + DataAbortFromCurrent(IssDataAbort), +} + +impl ExceptionClass { + pub fn decode_iss(&self, iss: u25) -> Iss { + match self { + ExceptionClass::Unknown => Iss::Unknown(IssUnknown(iss.value())), + ExceptionClass::TrappedWfiWfe => { + Iss::TrappedWfiWfe(IssTrappedWfiWfe::new_with_raw_value(iss)) + } + ExceptionClass::TrappedCp15McrMrc => { + Iss::TrappedCp15McrMrc(IssTrappedMcrMrc::new_with_raw_value(iss)) + } + ExceptionClass::TrappedCp15McrrMrrc => { + Iss::TrappedCp15McrrMrrc(IssTrappedMcrrMrrc::new_with_raw_value(iss)) + } + ExceptionClass::TrappedCp14McrMrc => { + Iss::TrappedCp14McrMrc(IssTrappedMcrMrc::new_with_raw_value(iss)) + } + ExceptionClass::TrappedLdcStc => { + Iss::TrappedLdcStc(IssTrappedLdcStc::new_with_raw_value(iss)) + } + ExceptionClass::TrappedFpu => Iss::TrappedFpu(IssTrappedFpu::new_with_raw_value(iss)), + ExceptionClass::TrappedVmrs => Iss::TrappedVmrs(IssTrappedVmrs(iss.value())), + ExceptionClass::TrappedCp14McrrMrrc => { + Iss::TrappedCp14McrrMrrc(IssTrappedMcrrMrrc::new_with_raw_value(iss)) + } + ExceptionClass::IllegalAArch32Eret => Iss::IllegalAArch32Eret, + ExceptionClass::Svc => Iss::Svc(IssCall::new_with_raw_value(iss)), + ExceptionClass::Hvc => Iss::Hvc(IssCall::new_with_raw_value(iss)), + ExceptionClass::Smc => Iss::Smc(IssSmc(iss.value())), + ExceptionClass::PrefetchAbortFromLower => { + Iss::PrefetchAbortFromLower(IssPrefetchAbort::new_with_raw_value(iss)) + } + ExceptionClass::PrefetchAbortFromCurrent => { + Iss::PrefetchAbortFromCurrent(IssPrefetchAbort::new_with_raw_value(iss)) + } + ExceptionClass::PcAlignment => Iss::PcAlignment, + ExceptionClass::DataAbortFromLower => { + Iss::DataAbortFromLower(IssDataAbort::new_with_raw_value(iss)) + } + ExceptionClass::DataAbortFromCurrent => { + Iss::DataAbortFromCurrent(IssDataAbort::new_with_raw_value(iss)) + } + } + } +} + +/// The ISS field when EC = ExceptionClass::Unknown +/// +/// All bits are reserved +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct IssUnknown(pub u32); + +/// The ISS field when EC = ExceptionClass::TrappedWfiWfe +#[bitbybit::bitfield(u25, debug, defmt_bitfields(feature = "defmt"))] +pub struct IssTrappedWfiWfe { + /// Condition code valid + #[bit(24, r)] + cv: bool, + /// Condition code + #[bits(20..=23, r)] + cc: u4, + /// Trapped Instruction is WFE + #[bit(0, r)] + ti: bool, +} + +/// The ISS field when EC = ExceptionClass::TrappedCp15McrMrc or ExceptionClass::TrappedCp14McrMrc +#[bitbybit::bitfield(u25, debug, defmt_bitfields(feature = "defmt"))] +pub struct IssTrappedMcrMrc { + /// Condition code valid + #[bit(24, r)] + cv: bool, + /// Condition code + #[bits(20..=23, r)] + cc: u4, + /// OPC2 value from instruction + #[bits(17..=19, r)] + opc2: u3, + /// OPC1 value from instruction + #[bits(14..=16, r)] + opc1: u3, + /// CRn value from instruction + #[bits(10..=13, r)] + crn: u4, + /// Rt value from instruction + #[bits(5..=8, r)] + rt: u4, + /// CRm value from instruction + #[bits(1..=4, r)] + crm: u4, + /// Direction (true = read, false = write) + #[bit(0, r)] + is_read: bool, +} + +/// The ISS field when EC = ExceptionClass::TrappedCp15McrrMrrc or ExceptionClass::TrappedCp14McrrMrrc +#[bitbybit::bitfield(u25, debug, defmt_bitfields(feature = "defmt"))] +pub struct IssTrappedMcrrMrrc { + /// Condition code valid + #[bit(24, r)] + cv: bool, + /// Condition code + #[bits(20..=23, r)] + cc: u4, + /// OPC2 value from instruction + #[bits(16..=19, r)] + opc2: u4, + /// Rt2 value from instruction + #[bits(10..=13, r)] + rt2: u4, + /// Rt value from instruction + #[bits(5..=8, r)] + rt: u4, + /// CRm value from instruction + #[bits(1..=4, r)] + crm: u4, + /// Direction (true = read, false = write) + #[bit(0, r)] + is_read: bool, +} + +/// The ISS field when EC = ExceptionClass::TrappedLdcStc +#[bitbybit::bitfield(u25, debug, defmt_bitfields(feature = "defmt"))] +pub struct IssTrappedLdcStc { + /// Condition code valid + #[bit(24, r)] + cv: bool, + /// Condition code + #[bits(20..=23, r)] + cc: u4, + /// The immediate value from the instruction + #[bits(12..=19, r)] + imm8: u8, + /// Rn value from instruction + #[bits(5..=8, r)] + rn: u4, + /// Whether offset is added (true) or subtracted (false) + #[bit(4, r)] + offset: bool, + /// Addressing Mode + #[bits(1..=3, r)] + am: u3, + /// Direction (true = read, false = write) + #[bit(0, r)] + is_read: bool, +} + +/// The ISS field when EC = ExceptionClass::TrappedFpu +#[bitbybit::bitfield(u25, debug, defmt_bitfields(feature = "defmt"))] +pub struct IssTrappedFpu { + /// Condition code valid + #[bit(24, r)] + cv: bool, + /// Condition code + #[bits(20..=23, r)] + cc: u4, + /// Trapped Advanced SIMD + #[bit(5, r)] + ta: bool, + /// CoProc Bits + #[bits(0..=3, r)] + coproc: u4, +} + +/// The ISS field when EC = ExceptionClass::TrappedVmrs +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct IssTrappedVmrs(pub u32); + +/// The ISS field when EC = ExceptionClass::Svc +#[bitbybit::bitfield(u25, debug, defmt_bitfields(feature = "defmt"))] +pub struct IssCall { + /// Immediate value from instruction + #[bits(0..=15, r)] + imm16: u16, +} + +/// The ISS field when EC = ExceptionClass::Smc +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct IssSmc(pub u32); + +/// The ISS field when EC = ExceptionClass::PrefetchAbortFromLower or ExceptionClass::PrefetchAbortFromCurrent +#[bitbybit::bitfield(u25, debug, defmt_bitfields(feature = "defmt"))] +pub struct IssPrefetchAbort { + /// FAR not Valid, for a Synchronous External abort. + #[bit(10, r)] + fnv: bool, + /// External Abort Type. + /// + /// External = true, anything else = false + #[bit(9, r)] + ea: bool, + /// Instruction Fault Status Code + #[bits(0..=5, r)] + ifsc: u6, +} + +/// The ISS field when EC = ExceptionClass::DataAbortFromLower or ExceptionClass::DataAbortFromCurrent +#[bitbybit::bitfield(u25, debug, defmt_bitfields(feature = "defmt"))] +pub struct IssDataAbort { + /// Instruction Syndrome Valid + #[bit(24, r)] + isv: bool, + /// Syndrome Access Size + #[bits(22..=23, r)] + sas: u2, + /// Syndrome Sign Extend + #[bit(21, r)] + sae: bool, + /// Syndrome Register transfer + #[bits(16..=19, r)] + srt: u4, + /// Acquire/Release + #[bit(14, r)] + ar: bool, + /// FAR not Valid + #[bit(10, r)] + fnv: bool, + /// External Abort Type. + /// + /// External = true, anything else = false + #[bit(9, r)] + ea: bool, + /// Cache maintenance + #[bit(8, r)] + cm: bool, + /// Write not Read + #[bit(6, r)] + wnr: bool, + /// Data Fault Status Code + #[bits(0..=5, r)] + dfsc: u6, +} + #[bitbybit::bitenum(u1, exhaustive = true)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] diff --git a/aarch32-rt/Cargo.toml b/aarch32-rt/Cargo.toml index 8e9f25ac..f796ebe7 100644 --- a/aarch32-rt/Cargo.toml +++ b/aarch32-rt/Cargo.toml @@ -35,6 +35,8 @@ eabi-fpu = [] # if you have set the `+d32` target feature) then you need to enable this # option otherwise important FPU state may be lost when an exception occurs. fpu-d32 = [] +# Leave the CPU in HYP mode (EL2), and handle exceptions in HYP mode +el2-mode = [] [build-dependencies] arm-targets = { version = "0.4.0", path = "../arm-targets" } diff --git a/aarch32-rt/src/arch_v4/interrupt.rs b/aarch32-rt/src/arch_v4/interrupt.rs index b6509a43..588cd87a 100644 --- a/aarch32-rt/src/arch_v4/interrupt.rs +++ b/aarch32-rt/src/arch_v4/interrupt.rs @@ -23,7 +23,7 @@ core::arch::global_asm!( push {{ lr }} // save adjusted LR to IRQ stack mrs lr, spsr // The hardware has copied the interrupted task's CPSR to SPSR_irq - grab it and push {{ lr }} // save it to IRQ stack using LR - msr cpsr_c, {sys_mode} // switch to system mode so we can handle another interrupt (because if we interrupt irq mode we trash our own shadow registers) + msr cpsr_c, {sys_mode} // switch to system mode (because if we interrupt in IRQ mode we trash IRQ mode's LR) push {{ lr }} // Save LR of system mode before using it for stack alignment and lr, sp, 7 // align SP down to eight byte boundary using LR sub sp, lr // SP now aligned - only push 64-bit values from here diff --git a/aarch32-rt/src/arch_v7/hvc.rs b/aarch32-rt/src/arch_v7/hvc.rs index 3320b42a..718c7c01 100644 --- a/aarch32-rt/src/arch_v7/hvc.rs +++ b/aarch32-rt/src/arch_v7/hvc.rs @@ -15,7 +15,7 @@ core::arch::global_asm!( .global _asm_default_hvc_handler .type _asm_default_hvc_handler, %function _asm_default_hvc_handler: - push {{ r12, lr }} // push state to stack + push {{ r12, lr }} // give us R12 and LR to work with push {{ r0-r5 }} // push frame to stack mov r12, sp // r12 = pointer to Frame "#, diff --git a/aarch32-rt/src/arch_v7/interrupt.rs b/aarch32-rt/src/arch_v7/interrupt.rs index d9499c6b..63727440 100644 --- a/aarch32-rt/src/arch_v7/interrupt.rs +++ b/aarch32-rt/src/arch_v7/interrupt.rs @@ -19,7 +19,7 @@ core::arch::global_asm!( _asm_default_irq_handler: sub lr, lr, 4 // make sure we jump back to the right place srsfd sp!, #{sys_mode} // store return state to SYS stack - cps #{sys_mode} // switch to system mode so we can handle another interrupt (because if we interrupt irq mode we trash our own shadow registers) + cps #{sys_mode} // switch to system mode (because if we interrupt in IRQ mode we trash IRQ mode's LR) push {{ lr }} // save adjusted LR to SYS stack and lr, sp, 7 // align SP down to eight byte boundary using LR sub sp, lr // SP now aligned - only push 64-bit values from here diff --git a/aarch32-rt/src/arch_v8_hyp/abort.rs b/aarch32-rt/src/arch_v8_hyp/abort.rs new file mode 100644 index 00000000..8a281228 --- /dev/null +++ b/aarch32-rt/src/arch_v8_hyp/abort.rs @@ -0,0 +1,77 @@ +//! Data and Prefetch Abort handlers for Armv8-R at EL2 + +core::arch::global_asm!( + r#" + // Work around https://github.com/rust-lang/rust/issues/127269 + .fpu vfp3 + + .section .text._asm_default_data_abort_handler + + // Called from the vector table when we have an undefined exception. + // Saves state and calls a C-compatible handler like + // `extern "C" fn _data_abort_handler(addr: usize);` + .global _asm_default_data_abort_handler + .type _asm_default_data_abort_handler, %function + _asm_default_data_abort_handler: + push {{ r0-r3, r12, lr }} // preserve state that C function won't save + mrs r0, elr_hyp // grab ELR_hyp + mrs r1, spsr_hyp // grab SPSR_hyp + mov r12, sp // align SP down to eight byte boundary using R12 + and r12, r12, 7 // + sub sp, r12 // SP now aligned - only push 64-bit values from here + push {{ r0-r2, r12 }} // save ELR, SPSR, padding and alignment amount + "#, + crate::save_fpu_context!(), + r#" + mrs r0, elr_hyp // Pass the faulting instruction address to the handler. + bl _data_abort_handler // call C handler + msr elr_hyp, r0 // if we get back here, assume they returned a new LR in r0 + "#, + crate::restore_fpu_context!(), + r#" + pop {{ r0-r2, r12 }} // restore ELR, SPSR, padding and alignment amount + add sp, r12 // restore SP alignment + msr spsr_hyp, r1 // restore SPSR + pop {{ r0-r3, r12, lr }} // restore state that C function didn't save + eret // Return from the asm handler + .size _asm_default_data_abort_handler, . - _asm_default_data_abort_handler + "#, +); + +core::arch::global_asm!( + r#" + // Work around https://github.com/rust-lang/rust/issues/127269 + .fpu vfp3 + + .section .text._asm_default_prefetch_abort_handler + + // Called from the vector table when we have an undefined exception. + // Saves state and calls a C-compatible handler like + // `extern "C" fn _prefetch_abort_handler(addr: usize);` + .global _asm_default_prefetch_abort_handler + .type _asm_default_prefetch_abort_handler, %function + _asm_default_prefetch_abort_handler: + push {{ r0-r3, r12, lr }} // preserve state that C function won't save + mrs r0, elr_hyp // grab ELR_hyp + mrs r1, spsr_hyp // grab SPSR_hyp + mov r12, sp // align SP down to eight byte boundary using R12 + and r12, r12, 7 // + sub sp, r12 // SP now aligned - only push 64-bit values from here + push {{ r0-r2, r12 }} // save ELR, SPSR, padding and alignment amount + "#, + crate::save_fpu_context!(), + r#" + mrs r0, elr_hyp // Pass the faulting instruction address to the handler. + bl _prefetch_abort_handler // call C handler + msr elr_hyp, r0 // if we get back here, assume they returned a new LR in r0 + "#, + crate::restore_fpu_context!(), + r#" + pop {{ r0-r2, r12 }} // restore ELR, SPSR, padding and alignment amount + add sp, r12 // restore SP alignment + msr spsr_hyp, r1 // restore SPSR + pop {{ r0-r3, r12, lr }} // restore state that C function didn't save + eret // Return from the asm handler + .size _asm_default_prefetch_abort_handler, . - _asm_default_prefetch_abort_handler + "#, +); diff --git a/aarch32-rt/src/arch_v8_hyp/hvc.rs b/aarch32-rt/src/arch_v8_hyp/hvc.rs new file mode 100644 index 00000000..171f165f --- /dev/null +++ b/aarch32-rt/src/arch_v8_hyp/hvc.rs @@ -0,0 +1,46 @@ +//! HVC handler for Armv8-R at EL2 + +#[cfg(target_arch = "arm")] +core::arch::global_asm!( + r#" + // Work around https://github.com/rust-lang/rust/issues/127269 + .fpu vfp3 + + .section .text._asm_default_hvc_handler + + // Called from the vector table when we have an hypervisor call. + // Saves state and calls a C-compatible handler like + // `extern "C" fn _hvc_handler(hsr: u32, frame: &Frame) -> u32;` + .global _asm_default_hvc_handler + .type _asm_default_hvc_handler, %function + _asm_default_hvc_handler: + push {{ r12, lr }} // give us R12 and LR to work with + mrs lr, elr_hyp // grab elr + mrs r12, spsr_hyp // grab spsr + push {{ r12, lr }} // push them to stack + mov r12, sp // align SP down to eight byte boundary using R12 + and r12, r12, 7 // + sub sp, r12 // SP now aligned - only push 64-bit values from here + push {{ r0-r6, r12 }} // push frame and alignment amount to stack + mov r12, sp // r12 = pointer to Frame + "#, + crate::save_fpu_context!(), + r#" + mrc p15, 4, r0, c5, c2, 0 // r0 = HSR value + mov r1, r12 // r1 = frame pointer + bl _hvc_handler + mov lr, r0 // copy return value into LR, because we're about to use r0 in the FPU restore + "#, + crate::restore_fpu_context!(), + r#" + pop {{ r0-r6, r12 }} // restore frame and alignment + mov r0, lr // copy return value from lr back to r0, overwriting saved r0 + add sp, r12 // restore SP alignment using R12 + pop {{ r12, lr }} // pop elr and spsr from stack + msr elr_hyp, lr // restore elr + msr spsr_hyp, r12 // restore spsr + pop {{ r12, lr }} // pop R12 and LR from stack + eret // Return from the asm handler + .size _asm_default_hvc_handler, . - _asm_default_hvc_handler + "#, +); diff --git a/aarch32-rt/src/arch_v8_hyp/interrupt.rs b/aarch32-rt/src/arch_v8_hyp/interrupt.rs new file mode 100644 index 00000000..8295aaf1 --- /dev/null +++ b/aarch32-rt/src/arch_v8_hyp/interrupt.rs @@ -0,0 +1,38 @@ +//! IRQ handler for Armv8-R at EL2 + +#[cfg(target_arch = "arm")] +core::arch::global_asm!( + r#" + // Work around https://github.com/rust-lang/rust/issues/127269 + .fpu vfp3 + + .section .text._asm_default_irq_handler + + // Called from the vector table when we have an interrupt. + // Saves state and calls a C-compatible handler like + // `extern "C" fn _irq_handler();` + .global _asm_default_irq_handler + .type _asm_default_irq_handler, %function + _asm_default_irq_handler: + push {{ r0-r3, r12, lr }} // preserve state that C function won't save + mrs r0, elr_hyp // grab ELR_hyp + mrs r1, spsr_hyp // grab SPSR_hyp + mov r12, sp // align SP down to eight byte boundary using R12 + and r12, r12, 7 // + sub sp, r12 // SP now aligned - only push 64-bit values from here + push {{ r0-r2, r12 }} // save ELR, SPSR, padding and alignment amount + "#, + crate::save_fpu_context!(), + r#" + bl _irq_handler // call C handler (they may choose to re-enable interrupts) + "#, + crate::restore_fpu_context!(), + r#" + pop {{ r0-r2, r12 }} // restore ELR, SPSR, padding and alignment amount + add sp, r12 // restore SP alignment + msr spsr_hyp, r1 // restore SPSR + pop {{ r0-r3, r12, lr }} // restore state that C function didn't save + eret // Return from the asm handler + .size _asm_default_irq_handler, . - _asm_default_irq_handler + "#, +); diff --git a/aarch32-rt/src/arch_v8_hyp/mod.rs b/aarch32-rt/src/arch_v8_hyp/mod.rs new file mode 100644 index 00000000..144f7494 --- /dev/null +++ b/aarch32-rt/src/arch_v8_hyp/mod.rs @@ -0,0 +1,7 @@ +//! ASM routines for Armv8-R at EL2 + +mod abort; +mod hvc; +mod interrupt; +mod svc; +mod undefined; diff --git a/aarch32-rt/src/arch_v8_hyp/svc.rs b/aarch32-rt/src/arch_v8_hyp/svc.rs new file mode 100644 index 00000000..65e9a337 --- /dev/null +++ b/aarch32-rt/src/arch_v8_hyp/svc.rs @@ -0,0 +1,52 @@ +//! SVC handler for Armv8-R at EL2 + +#[cfg(target_arch = "arm")] +core::arch::global_asm!( + r#" + // Work around https://github.com/rust-lang/rust/issues/127269 + .fpu vfp3 + + // Called from the vector table when we have an hypervisor call from Hyp + // mode (which seems to end up in this SVC handler). + // + // Saves state and calls a C-compatible handler like `extern "C" fn + // _hvc_handler(hsr: u32, frame: &Frame) -> u32;` + // + // NOTE: We call '_hvc_handler' rather than '_svc_handler', because we are + // passing the Hypervisor Syndrome Register contents, rather trying to parse + // the HVC instruction. + .section .text._asm_default_svc_handler + .arm + .global _asm_default_svc_handler + .type _asm_default_svc_handler, %function + _asm_default_svc_handler: + push {{ r12, lr }} // give us R12 and LR to work with + mrs lr, elr_hyp // grab elr + mrs r12, spsr_hyp // grab spsr + push {{ r12, lr }} // push them to stack + mov r12, sp // align SP down to eight byte boundary using R12 + and r12, r12, 7 // + sub sp, r12 // SP now aligned - only push 64-bit values from here + push {{ r0-r6, r12 }} // push frame and alignment amount to stack + mov r12, sp // r12 = pointer to Frame + "#, + crate::save_fpu_context!(), + r#" + mrc p15, 4, r0, c5, c2, 0 // r0 = HSR value + mov r1, r12 // r1 = frame pointer + bl _hvc_handler + mov lr, r0 // copy return value into LR, because we're about to use r0 in the FPU restore + "#, + crate::restore_fpu_context!(), + r#" + pop {{ r0-r6, r12 }} // restore frame and alignment + mov r0, lr // copy return value from lr back to r0, overwriting saved r0 + add sp, r12 // restore SP alignment using R12 + pop {{ r12, lr }} // pop elr and spsr from stack + msr elr_hyp, lr // restore elr + msr spsr_hyp, r12 // restore spsr + pop {{ r12, lr }} // pop R12 and LR from stack + eret // Return from the asm handler + .size _asm_default_svc_handler, . - _asm_default_svc_handler + "#, +); diff --git a/aarch32-rt/src/arch_v8_hyp/undefined.rs b/aarch32-rt/src/arch_v8_hyp/undefined.rs new file mode 100644 index 00000000..8c4b0d5e --- /dev/null +++ b/aarch32-rt/src/arch_v8_hyp/undefined.rs @@ -0,0 +1,41 @@ +//! Undefined handler for Armv8-R at EL2 + +#[cfg(target_arch = "arm")] +core::arch::global_asm!( + r#" + // Work around https://github.com/rust-lang/rust/issues/127269 + .fpu vfp3 + + // Called from the vector table when we have an undefined exception. + // Saves state and calls a C-compatible handler like + // `extern "C" fn _undefined_handler(addr: usize) -> usize;` + // or + // `extern "C" fn _undefined_handler(addr: usize) -> !;` + .section .text._asm_default_undefined_handler + .global _asm_default_undefined_handler + .type _asm_default_undefined_handler, %function + _asm_default_undefined_handler: + push {{ r0-r3, r12, lr }} // preserve state that C function won't save + mrs r0, elr_hyp // grab ELR_hyp + mrs r1, spsr_hyp // grab SPSR_hyp + mov r12, sp // align SP down to eight byte boundary using R12 + and r12, r12, 7 // + sub sp, r12 // SP now aligned - only push 64-bit values from here + push {{ r0-r2, r12 }} // save ELR, SPSR, padding and alignment amount + "#, + crate::save_fpu_context!(), + r#" + mrs r0, elr_hyp // Pass the faulting instruction address to the handler. + bl _undefined_handler // call C handler + msr elr_hyp, r0 // if we get back here, assume they returned a new LR in r0 + "#, + crate::restore_fpu_context!(), + r#" + pop {{ r0-r2, r12 }} // restore ELR, SPSR, padding and alignment amount + add sp, r12 // restore SP alignment + msr spsr_hyp, r1 // restore SPSR + pop {{ r0-r3, r12, lr }} // restore state that C function didn't save + eret // Return from the asm handler + .size _asm_default_undefined_handler, . - _asm_default_undefined_handler + "#, +); diff --git a/aarch32-rt/src/lib.rs b/aarch32-rt/src/lib.rs index c585c2ff..04bb2824 100644 --- a/aarch32-rt/src/lib.rs +++ b/aarch32-rt/src/lib.rs @@ -517,11 +517,9 @@ //! not save a great deal of state on entry to an exception handler, unlike //! Armv7-M (and other M-Profile) processors. We must therefore save this state //! to the stack using assembly language, before transferring to an `extern "C"` -//! function. We do not change modes before entering that `extern "C"` function - -//! that's for the handler to deal with as it wishes. Because FIQ is often -//! performance-sensitive, we don't supply an FIQ trampoline; if you want to use -//! FIQ, you have to write your own assembly routine, allowing you to preserve -//! only whatever state is important to you. +//! function. Because FIQ is often performance-sensitive, we don't supply an FIQ +//! trampoline; if you want to use FIQ, you have to write your own assembly +//! routine, allowing you to preserve only whatever state is important to you. //! //! ## Examples //! @@ -533,18 +531,24 @@ #[cfg(target_arch = "arm")] use aarch32_cpu::register::{cpsr::ProcessorMode, Cpsr}; -#[cfg(any(arm_architecture = "v7-a", arm_architecture = "v8-r"))] +#[cfg(all( + any(arm_architecture = "v7-a", arm_architecture = "v8-r"), + not(feature = "el2-mode") +))] use aarch32_cpu::register::Hactlr; pub use aarch32_rt_macros::{entry, exception, irq}; +#[cfg(all(target_arch = "arm", arm_architecture = "v8-r", feature = "el2-mode"))] +mod arch_v8_hyp; + #[cfg(all( target_arch = "arm", any( arm_architecture = "v7-a", arm_architecture = "v7-r", - arm_architecture = "v8-r" - ) + all(arm_architecture = "v8-r", not(feature = "el2-mode")) + ), ))] mod arch_v7; @@ -968,7 +972,7 @@ core::arch::global_asm!( // Set up stacks. mov r0, #0 bl _stack_setup_preallocated - "#, + "#, fpu_enable!(), r#" // Zero all registers before calling kmain @@ -994,13 +998,16 @@ core::arch::global_asm!( "# ); -// Start-up code for Armv8-R. +// Start-up code for Armv8-R to switch to EL1. // // There's only one Armv8-R CPU (the Cortex-R52) and the FPU is mandatory, so we // always enable it. // // We boot into EL2, set up a stack pointer, and run `kmain` in EL1. -#[cfg(any(arm_architecture = "v7-a", arm_architecture = "v8-r"))] +#[cfg(all( + any(arm_architecture = "v7-a", arm_architecture = "v8-r"), + not(feature = "el2-mode") +))] core::arch::global_asm!( r#" // Work around https://github.com/rust-lang/rust/issues/127269 @@ -1091,3 +1098,56 @@ core::arch::global_asm!( .raw_value() } ); + +// Start-up code for Armv8-R to stay in EL2. +// +// There's only one Armv8-R CPU (the Cortex-R52) and the FPU is mandatory, so we +// always enable it. +// +// We boot into EL2, set up a HYP stack pointer, and run `kmain` in EL2. +#[cfg(all(arm_architecture = "v8-r", feature = "el2-mode"))] +core::arch::global_asm!( + r#" + // Work around https://github.com/rust-lang/rust/issues/127269 + .fpu vfp3 + + .section .text.default_start + .global _default_start + .type _default_start, %function + _default_start: + // Init .data and .bss + bl _init_segments + // Set stack pointer + ldr sp, =_hyp_stack_high_end + // Set the HVBAR (for EL2) to _vector_table + ldr r1, =_vector_table + mcr p15, 4, r1, c12, c0, 0 + // Mask IRQ and FIQ + mrs r0, CPSR + orr r0, {irq_fiq} + msr CPSR, r0 + "#, + fpu_enable!(), + r#" + // Zero all registers before calling kmain + mov r0, 0 + mov r1, 0 + mov r2, 0 + mov r3, 0 + mov r4, 0 + mov r5, 0 + mov r6, 0 + mov r7, 0 + mov r8, 0 + mov r9, 0 + mov r10, 0 + mov r11, 0 + mov r12, 0 + // Jump to application + bl kmain + // In case the application returns, loop forever + b . + .size _default_start, . - _default_start + "#, + irq_fiq = const aarch32_cpu::register::Cpsr::new_with_raw_value(0).with_i(true).with_f(true).raw_value() +); diff --git a/examples/mps3-an536-el2/.cargo/config.toml b/examples/mps3-an536-el2/.cargo/config.toml new file mode 100644 index 00000000..6960eb34 --- /dev/null +++ b/examples/mps3-an536-el2/.cargo/config.toml @@ -0,0 +1,10 @@ +[target.armv8r-none-eabihf] +# Note, this requires QEMU 9 or higher +runner = "qemu-system-arm -machine mps3-an536 -cpu cortex-r52 -semihosting -nographic -audio none -kernel" + +[target.thumbv8r-none-eabihf] +# Note, this requires QEMU 9 or higher +runner = "qemu-system-arm -machine mps3-an536 -cpu cortex-r52 -semihosting -nographic -audio none -kernel" + +[build] +target = "armv8r-none-eabihf" \ No newline at end of file diff --git a/examples/mps3-an536-el2/Cargo.toml b/examples/mps3-an536-el2/Cargo.toml new file mode 100644 index 00000000..5b30c467 --- /dev/null +++ b/examples/mps3-an536-el2/Cargo.toml @@ -0,0 +1,31 @@ +[package] +authors = [ + "Jonathan Pallant ", + "The Embedded Devices Working Group Arm Team " +] +default-run = "hello" +description = "Examples for MPS3-AN536 device (Arm Cortex-R52)" +edition = "2024" +homepage = "https://github.com/rust-embedded/aarch32" +license = "MIT OR Apache-2.0" +name = "mps3-an536-el2" +publish = false +readme = "README.md" +repository = "https://github.com/rust-embedded/aarch32.git" +version = "0.0.0" + +[dependencies] +aarch32-cpu = { path = "../../aarch32-cpu", features = ["critical-section-multi-core"] } +aarch32-rt = { path = "../../aarch32-rt", features = ["el2-mode"] } +arm-gic = { version = "0.7.1" } +critical-section = "1.2.0" +heapless = "0.9.1" +libm = "0.2.15" +semihosting = { version = "0.1.18", features = ["stdio"] } + +[build-dependencies] +arm-targets = {version = "0.4.0", path = "../../arm-targets"} + +[features] +eabi-fpu = ["aarch32-rt/eabi-fpu"] +fpu-d32 = ["aarch32-rt/fpu-d32"] diff --git a/examples/mps3-an536-el2/README.md b/examples/mps3-an536-el2/README.md new file mode 100644 index 00000000..1f71befe --- /dev/null +++ b/examples/mps3-an536-el2/README.md @@ -0,0 +1,90 @@ +# Examples for Arm MPS3-AN536 + +This package contains example binaries for the Arm MPS3-AN536 evaluation system, +featuring one or two Arm Cortex-R52 processor cores. This crate is tested on the +following targets: + +- `armv8r-none-eabihf` - ARMv8-R AArch32, hard-float, Arm mode +- `thumbv8r-none-eabihf` - ARMv8-R AArch32, hard-float, Thumb mode + +The repo-level [`.cargo/config.toml`] will ensure the code runs on the +appropriate QEMU configuration. + +As of Rust 1.92, `armv8r-none-eabihf` is a Tier 2 target and so any stable +release from 1.92 or newer should work for that target. However, +`thumbv8r-none-eabihf` is still a Tier 3 target, which means Nightly Rust is +required. This folder contains a [`rust-toolchain.toml`] which pins us to a +specific release of nightly that is known to work. + +We have only tested this crate on `qemu-system-arm` emulating the Arm +MPS3-AN536, not the real thing. + +[`.cargo/config.toml`]: ../../.cargo/config.toml +[`rust-toolchain.toml`]: ./rust-toolchain.toml + +## Running + +Run these examples as follows: + +```console +$ cargo run --bin hello + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.03s + Running `qemu-system-arm -machine mps3-an536 -cpu cortex-r52 -semihosting -nographic -audio none -kernel target/armv8r-none-eabihf/debug/hello` +Hello, this is semihosting! x = 1.000, y = 2.000 +PANIC: PanicInfo { + message: I am an example panic, + location: Location { + file: "src/bin/hello.rs", + line: 20, + column: 5, + }, + can_unwind: true, + force_no_backtrace: false, +} +$ cargo run --bin hello --target thumbv8r-none-eabihf -Zbuild-std=core + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.92s + Running `qemu-system-arm -machine mps3-an536 -cpu cortex-r52 -semihosting -nographic -audio none -kernel target/thumbv8r-none-eabihf/debug/hello` +Hello, this is semihosting! x = 1.000, y = 2.000 +PANIC: PanicInfo { + message: I am an example panic, + location: Location { + file: "src/bin/hello.rs", + line: 20, + column: 5, + }, + can_unwind: true, + force_no_backtrace: false, +} +``` + +## Debugging + +You can start a GDB server by adding `-- -s -S` to the end of the `cargo run` +command, and the connect with GDB as follows: + +```console +$ cargo run --bin hello -- -s -S +# QEMU runs and hangs waiting for a connection. In another terminal run: +$ arm-none-eabi-gdb -x commands.gdb target/armv8r-none-eabihf/debug/hello +# GDB will start and connect to QEMU's GDB server. The commands.gdb file sets up some useful defaults. +``` + +## Minimum Supported Rust Version (MSRV) + +These examples are guaranteed to compile on the version of Rust given in the +[`rust-toolchain.toml`] file. These examples are not version controlled and we +may change the MSRV at any time. + +## Licence + +- Copyright (c) Ferrous Systems +- Copyright (c) The Rust Embedded Devices Working Group developers + +Licensed under either [MIT](../LICENSE-MIT) or [Apache-2.0](../LICENSE-APACHE) at +your option. + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you shall be licensed as above, without any +additional terms or conditions. diff --git a/examples/mps3-an536-el2/build.rs b/examples/mps3-an536-el2/build.rs new file mode 100644 index 00000000..b0b9f098 --- /dev/null +++ b/examples/mps3-an536-el2/build.rs @@ -0,0 +1,26 @@ +//! # Build script for the MPS3-AN536 Examples +//! +//! This script only executes when using `cargo` to build the project. +//! +//! Copyright (c) Ferrous Systems, 2025 + +use std::io::Write; + +fn main() { + arm_targets::process(); + write("memory.x", include_bytes!("memory.x")); + // Use the cortex-m-rt linker script + println!("cargo:rustc-link-arg=-Tlink.x"); +} + +fn write(file: &str, contents: &[u8]) { + // Put linker file in our output directory and ensure it's on the + // linker search path. + let out = &std::path::PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); + std::fs::File::create(out.join(file)) + .unwrap() + .write_all(contents) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + println!("cargo:rerun-if-changed={}", file); +} diff --git a/examples/mps3-an536-el2/commands.gdb b/examples/mps3-an536-el2/commands.gdb new file mode 100644 index 00000000..0634363b --- /dev/null +++ b/examples/mps3-an536-el2/commands.gdb @@ -0,0 +1,13 @@ +target extended-remote :1234 +break kmain +break _asm_undefined_handler +break _asm_svc_handler +break _asm_prefetch_abort_handler +break _asm_data_abort_handler +break _asm_irq_handler +break _asm_fiq_handler +layout asm +layout regs +set logging file ./target/debug.log +set logging enabled on +stepi diff --git a/examples/mps3-an536-el2/memory.x b/examples/mps3-an536-el2/memory.x new file mode 100644 index 00000000..00158077 --- /dev/null +++ b/examples/mps3-an536-el2/memory.x @@ -0,0 +1,44 @@ +/* +Memory configuration for the MPS3-AN536 machine. + +See https://github.com/qemu/qemu/blob/master/hw/arm/mps3r.c +*/ + +MEMORY { + QSPI : ORIGIN = 0x08000000, LENGTH = 8M + BRAM : ORIGIN = 0x10000000, LENGTH = 512K + DDR : ORIGIN = 0x20000000, LENGTH = 1536M +} + +REGION_ALIAS("VECTORS", QSPI); +REGION_ALIAS("CODE", QSPI); +REGION_ALIAS("DATA", BRAM); +REGION_ALIAS("STACKS", BRAM); + +SECTIONS { + /* ### Interrupt Handler Entries + * + * The IRQ handler walks this section to find registered + * interrupt handlers + */ + .irq_entries : ALIGN(4) + { + /* We put this in the header */ + __irq_entries_start = .; + /* Here are the entries */ + KEEP(*(.irq_entries)); + /* Keep this block a nice round size */ + . = ALIGN(4); + /* We put this in the header */ + __irq_entries_end = .; + } > CODE +} INSERT AFTER .text; + + +PROVIDE(_hyp_stack_size = 16K); +PROVIDE(_und_stack_size = 16K); +PROVIDE(_svc_stack_size = 16K); +PROVIDE(_abt_stack_size = 16K); +PROVIDE(_irq_stack_size = 64); +PROVIDE(_fiq_stack_size = 64); +PROVIDE(_sys_stack_size = 16K); diff --git a/examples/mps3-an536-el2/reference/abt-exception-a32-armv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/abt-exception-a32-armv8r-none-eabihf.out new file mode 100644 index 00000000..1d3d93c4 --- /dev/null +++ b/examples/mps3-an536-el2/reference/abt-exception-a32-armv8r-none-eabihf.out @@ -0,0 +1,8 @@ +Hello, this is an data abort exception example +data abort occurred Hsr { ec: Ok(DataAbortFromCurrent), il: ThirtyTwoBit, iss: 33 } Some(DataAbortFromCurrent(IssDataAbort { isv: false, sas: 0, sae: false, srt: 0, ar: false, fnv: false, ea: false, cm: false, wnr: false, dfsc: 21 })) +caught unaligned_from_a32 +Doing it again +data abort occurred Hsr { ec: Ok(DataAbortFromCurrent), il: ThirtyTwoBit, iss: 33 } Some(DataAbortFromCurrent(IssDataAbort { isv: false, sas: 0, sae: false, srt: 0, ar: false, fnv: false, ea: false, cm: false, wnr: false, dfsc: 21 })) +caught unaligned_from_a32 +Skipping instruction +Recovered from fault OK! diff --git a/examples/mps3-an536-el2/reference/abt-exception-a32-thumbv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/abt-exception-a32-thumbv8r-none-eabihf.out new file mode 100644 index 00000000..1d3d93c4 --- /dev/null +++ b/examples/mps3-an536-el2/reference/abt-exception-a32-thumbv8r-none-eabihf.out @@ -0,0 +1,8 @@ +Hello, this is an data abort exception example +data abort occurred Hsr { ec: Ok(DataAbortFromCurrent), il: ThirtyTwoBit, iss: 33 } Some(DataAbortFromCurrent(IssDataAbort { isv: false, sas: 0, sae: false, srt: 0, ar: false, fnv: false, ea: false, cm: false, wnr: false, dfsc: 21 })) +caught unaligned_from_a32 +Doing it again +data abort occurred Hsr { ec: Ok(DataAbortFromCurrent), il: ThirtyTwoBit, iss: 33 } Some(DataAbortFromCurrent(IssDataAbort { isv: false, sas: 0, sae: false, srt: 0, ar: false, fnv: false, ea: false, cm: false, wnr: false, dfsc: 21 })) +caught unaligned_from_a32 +Skipping instruction +Recovered from fault OK! diff --git a/examples/mps3-an536-el2/reference/abt-exception-t32-armv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/abt-exception-t32-armv8r-none-eabihf.out new file mode 100644 index 00000000..f53b10f6 --- /dev/null +++ b/examples/mps3-an536-el2/reference/abt-exception-t32-armv8r-none-eabihf.out @@ -0,0 +1,8 @@ +Hello, this is an data abort exception example +data abort occurred Hsr { ec: Ok(DataAbortFromCurrent), il: ThirtyTwoBit, iss: 33 } Some(DataAbortFromCurrent(IssDataAbort { isv: false, sas: 0, sae: false, srt: 0, ar: false, fnv: false, ea: false, cm: false, wnr: false, dfsc: 21 })) +caught unaligned_from_t32 +Doing it again +data abort occurred Hsr { ec: Ok(DataAbortFromCurrent), il: ThirtyTwoBit, iss: 33 } Some(DataAbortFromCurrent(IssDataAbort { isv: false, sas: 0, sae: false, srt: 0, ar: false, fnv: false, ea: false, cm: false, wnr: false, dfsc: 21 })) +caught unaligned_from_t32 +Skipping instruction +Recovered from fault OK! diff --git a/examples/mps3-an536-el2/reference/abt-exception-t32-thumbv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/abt-exception-t32-thumbv8r-none-eabihf.out new file mode 100644 index 00000000..f53b10f6 --- /dev/null +++ b/examples/mps3-an536-el2/reference/abt-exception-t32-thumbv8r-none-eabihf.out @@ -0,0 +1,8 @@ +Hello, this is an data abort exception example +data abort occurred Hsr { ec: Ok(DataAbortFromCurrent), il: ThirtyTwoBit, iss: 33 } Some(DataAbortFromCurrent(IssDataAbort { isv: false, sas: 0, sae: false, srt: 0, ar: false, fnv: false, ea: false, cm: false, wnr: false, dfsc: 21 })) +caught unaligned_from_t32 +Doing it again +data abort occurred Hsr { ec: Ok(DataAbortFromCurrent), il: ThirtyTwoBit, iss: 33 } Some(DataAbortFromCurrent(IssDataAbort { isv: false, sas: 0, sae: false, srt: 0, ar: false, fnv: false, ea: false, cm: false, wnr: false, dfsc: 21 })) +caught unaligned_from_t32 +Skipping instruction +Recovered from fault OK! diff --git a/examples/mps3-an536-el2/reference/generic-timer-armv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/generic-timer-armv8r-none-eabihf.out new file mode 100644 index 00000000..9f58641c --- /dev/null +++ b/examples/mps3-an536-el2/reference/generic-timer-armv8r-none-eabihf.out @@ -0,0 +1,40 @@ +Found PERIPHBASE 0xf0000000 +Creating GIC driver @ 0xf0000000 / 0xf0100000 +Calling git.setup(0) +Hcr { tcpac: false, trvm: false, hcd: false, tge: false, tvm: false, tpu: false, tpc: false, tsw: false, tac: false, tidcp: false, tid3: false, tid2: false, tid1: false, tid0: false, twe: false, twi: false, dc: false, bsu: NoEffect, fb: false, va: false, vi: false, vf: false, amo: false, imo: false, fmo: false, swio: false, vm: false } +Configure Timer Interrupt... +Timer Hz = 62500000 +Enabling interrupts... +SCTLR { IE=0 TE=0 NMFI=0 EE=0 U=1 FI=0 DZ=1 BR=0 RR=0 V=0 I=0 Z=1 SW=0 C=0 A=0 M=0 } +SCTLR { IE=0 TE=0 NMFI=0 EE=0 U=1 FI=0 DZ=1 BR=0 RR=0 V=0 I=0 Z=1 SW=0 C=0 A=0 M=0 } +> IRQ +Hyp timer tick! +< IRQ +> IRQ +Hyp timer tick! +< IRQ +> IRQ +Hyp timer tick! +< IRQ +> IRQ +Hyp timer tick! +< IRQ +> IRQ +Hyp timer tick! +< IRQ +> IRQ +Hyp timer tick! +< IRQ +> IRQ +Hyp timer tick! +< IRQ +> IRQ +Hyp timer tick! +< IRQ +> IRQ +Hyp timer tick! +< IRQ +> IRQ +Hyp timer tick! +< IRQ +EL2 timer test completed OK diff --git a/examples/mps3-an536-el2/reference/generic-timer-thumbv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/generic-timer-thumbv8r-none-eabihf.out new file mode 100644 index 00000000..9f58641c --- /dev/null +++ b/examples/mps3-an536-el2/reference/generic-timer-thumbv8r-none-eabihf.out @@ -0,0 +1,40 @@ +Found PERIPHBASE 0xf0000000 +Creating GIC driver @ 0xf0000000 / 0xf0100000 +Calling git.setup(0) +Hcr { tcpac: false, trvm: false, hcd: false, tge: false, tvm: false, tpu: false, tpc: false, tsw: false, tac: false, tidcp: false, tid3: false, tid2: false, tid1: false, tid0: false, twe: false, twi: false, dc: false, bsu: NoEffect, fb: false, va: false, vi: false, vf: false, amo: false, imo: false, fmo: false, swio: false, vm: false } +Configure Timer Interrupt... +Timer Hz = 62500000 +Enabling interrupts... +SCTLR { IE=0 TE=0 NMFI=0 EE=0 U=1 FI=0 DZ=1 BR=0 RR=0 V=0 I=0 Z=1 SW=0 C=0 A=0 M=0 } +SCTLR { IE=0 TE=0 NMFI=0 EE=0 U=1 FI=0 DZ=1 BR=0 RR=0 V=0 I=0 Z=1 SW=0 C=0 A=0 M=0 } +> IRQ +Hyp timer tick! +< IRQ +> IRQ +Hyp timer tick! +< IRQ +> IRQ +Hyp timer tick! +< IRQ +> IRQ +Hyp timer tick! +< IRQ +> IRQ +Hyp timer tick! +< IRQ +> IRQ +Hyp timer tick! +< IRQ +> IRQ +Hyp timer tick! +< IRQ +> IRQ +Hyp timer tick! +< IRQ +> IRQ +Hyp timer tick! +< IRQ +> IRQ +Hyp timer tick! +< IRQ +EL2 timer test completed OK diff --git a/examples/mps3-an536-el2/reference/hello-armv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/hello-armv8r-none-eabihf.out new file mode 100644 index 00000000..3238d13c --- /dev/null +++ b/examples/mps3-an536-el2/reference/hello-armv8r-none-eabihf.out @@ -0,0 +1,19 @@ +Hello, this is semihosting! x = 1.000, y = 2.000 +SCTLR { IE=0 TE=0 NMFI=0 EE=0 U=1 FI=0 DZ=1 BR=0 RR=0 V=0 I=0 Z=1 SW=0 C=0 A=0 M=0 } +CPSR { N=0 Z=1 C=1 V=0 Q=0 J=0 E=0 A=1 I=1 F=1 T=0 MODE=Ok(Hyp) } +Region 0: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 1: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 2: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 3: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 4: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 5: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 6: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 7: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 8: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 9: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 10: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 11: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 12: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 13: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 14: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 15: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } diff --git a/examples/mps3-an536-el2/reference/hello-thumbv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/hello-thumbv8r-none-eabihf.out new file mode 100644 index 00000000..3238d13c --- /dev/null +++ b/examples/mps3-an536-el2/reference/hello-thumbv8r-none-eabihf.out @@ -0,0 +1,19 @@ +Hello, this is semihosting! x = 1.000, y = 2.000 +SCTLR { IE=0 TE=0 NMFI=0 EE=0 U=1 FI=0 DZ=1 BR=0 RR=0 V=0 I=0 Z=1 SW=0 C=0 A=0 M=0 } +CPSR { N=0 Z=1 C=1 V=0 Q=0 J=0 E=0 A=1 I=1 F=1 T=0 MODE=Ok(Hyp) } +Region 0: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 1: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 2: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 3: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 4: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 5: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 6: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 7: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 8: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 9: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 10: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 11: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 12: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 13: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 14: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 15: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } diff --git a/examples/mps3-an536-el2/reference/hvc-a32-armv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/hvc-a32-armv8r-none-eabihf.out new file mode 100644 index 00000000..0800ffcf --- /dev/null +++ b/examples/mps3-an536-el2/reference/hvc-a32-armv8r-none-eabihf.out @@ -0,0 +1,5 @@ +x = 1, y = 2, z = 3.000 +In hvc_handler, with Hsr { ec: Ok(Hvc), il: ThirtyTwoBit, iss: 0000abcd }, Some(Hvc(IssCall { imm16: abcd })), Frame { r0: 10000000, r1: 10000001, r2: 10000002, r3: 10000003, r4: 10000004, r5: 10000005 } +In hvc_handler, with Hsr { ec: Ok(Hvc), il: ThirtyTwoBit, iss: 00009876 }, Some(Hvc(IssCall { imm16: 9876 })), Frame { r0: 20000000, r1: 20000001, r2: 20000002, r3: 20000003, r4: 20000004, r5: 20000005 } +Got 12345678 +x = 1, y = 2, z = 3.000 diff --git a/examples/mps3-an536-el2/reference/hvc-a32-thumbv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/hvc-a32-thumbv8r-none-eabihf.out new file mode 100644 index 00000000..0800ffcf --- /dev/null +++ b/examples/mps3-an536-el2/reference/hvc-a32-thumbv8r-none-eabihf.out @@ -0,0 +1,5 @@ +x = 1, y = 2, z = 3.000 +In hvc_handler, with Hsr { ec: Ok(Hvc), il: ThirtyTwoBit, iss: 0000abcd }, Some(Hvc(IssCall { imm16: abcd })), Frame { r0: 10000000, r1: 10000001, r2: 10000002, r3: 10000003, r4: 10000004, r5: 10000005 } +In hvc_handler, with Hsr { ec: Ok(Hvc), il: ThirtyTwoBit, iss: 00009876 }, Some(Hvc(IssCall { imm16: 9876 })), Frame { r0: 20000000, r1: 20000001, r2: 20000002, r3: 20000003, r4: 20000004, r5: 20000005 } +Got 12345678 +x = 1, y = 2, z = 3.000 diff --git a/examples/mps3-an536-el2/reference/hvc-t32-armv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/hvc-t32-armv8r-none-eabihf.out new file mode 100644 index 00000000..0800ffcf --- /dev/null +++ b/examples/mps3-an536-el2/reference/hvc-t32-armv8r-none-eabihf.out @@ -0,0 +1,5 @@ +x = 1, y = 2, z = 3.000 +In hvc_handler, with Hsr { ec: Ok(Hvc), il: ThirtyTwoBit, iss: 0000abcd }, Some(Hvc(IssCall { imm16: abcd })), Frame { r0: 10000000, r1: 10000001, r2: 10000002, r3: 10000003, r4: 10000004, r5: 10000005 } +In hvc_handler, with Hsr { ec: Ok(Hvc), il: ThirtyTwoBit, iss: 00009876 }, Some(Hvc(IssCall { imm16: 9876 })), Frame { r0: 20000000, r1: 20000001, r2: 20000002, r3: 20000003, r4: 20000004, r5: 20000005 } +Got 12345678 +x = 1, y = 2, z = 3.000 diff --git a/examples/mps3-an536-el2/reference/hvc-t32-thumbv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/hvc-t32-thumbv8r-none-eabihf.out new file mode 100644 index 00000000..0800ffcf --- /dev/null +++ b/examples/mps3-an536-el2/reference/hvc-t32-thumbv8r-none-eabihf.out @@ -0,0 +1,5 @@ +x = 1, y = 2, z = 3.000 +In hvc_handler, with Hsr { ec: Ok(Hvc), il: ThirtyTwoBit, iss: 0000abcd }, Some(Hvc(IssCall { imm16: abcd })), Frame { r0: 10000000, r1: 10000001, r2: 10000002, r3: 10000003, r4: 10000004, r5: 10000005 } +In hvc_handler, with Hsr { ec: Ok(Hvc), il: ThirtyTwoBit, iss: 00009876 }, Some(Hvc(IssCall { imm16: 9876 })), Frame { r0: 20000000, r1: 20000001, r2: 20000002, r3: 20000003, r4: 20000004, r5: 20000005 } +Got 12345678 +x = 1, y = 2, z = 3.000 diff --git a/examples/mps3-an536-el2/reference/prefetch-exception-a32-armv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/prefetch-exception-a32-armv8r-none-eabihf.out new file mode 100644 index 00000000..50cd4e78 --- /dev/null +++ b/examples/mps3-an536-el2/reference/prefetch-exception-a32-armv8r-none-eabihf.out @@ -0,0 +1,8 @@ +Hello, this is a prefetch abort exception example +prefetch abort occurred Hsr { ec: Ok(PrefetchAbortFromCurrent), il: ThirtyTwoBit, iss: 00000022 }, Some(PrefetchAbortFromCurrent(IssPrefetchAbort { fnv: false, ea: false, ifsc: 22 })) +caught bkpt_from_a32 +Doing it again +prefetch abort occurred Hsr { ec: Ok(PrefetchAbortFromCurrent), il: ThirtyTwoBit, iss: 00000022 }, Some(PrefetchAbortFromCurrent(IssPrefetchAbort { fnv: false, ea: false, ifsc: 22 })) +caught bkpt_from_a32 +Skipping instruction +Recovered from fault OK! diff --git a/examples/mps3-an536-el2/reference/prefetch-exception-a32-thumbv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/prefetch-exception-a32-thumbv8r-none-eabihf.out new file mode 100644 index 00000000..50cd4e78 --- /dev/null +++ b/examples/mps3-an536-el2/reference/prefetch-exception-a32-thumbv8r-none-eabihf.out @@ -0,0 +1,8 @@ +Hello, this is a prefetch abort exception example +prefetch abort occurred Hsr { ec: Ok(PrefetchAbortFromCurrent), il: ThirtyTwoBit, iss: 00000022 }, Some(PrefetchAbortFromCurrent(IssPrefetchAbort { fnv: false, ea: false, ifsc: 22 })) +caught bkpt_from_a32 +Doing it again +prefetch abort occurred Hsr { ec: Ok(PrefetchAbortFromCurrent), il: ThirtyTwoBit, iss: 00000022 }, Some(PrefetchAbortFromCurrent(IssPrefetchAbort { fnv: false, ea: false, ifsc: 22 })) +caught bkpt_from_a32 +Skipping instruction +Recovered from fault OK! diff --git a/examples/mps3-an536-el2/reference/prefetch-exception-t32-armv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/prefetch-exception-t32-armv8r-none-eabihf.out new file mode 100644 index 00000000..fa18811d --- /dev/null +++ b/examples/mps3-an536-el2/reference/prefetch-exception-t32-armv8r-none-eabihf.out @@ -0,0 +1,8 @@ +Hello, this is a prefetch abort exception example +prefetch abort occurred Hsr { ec: Ok(PrefetchAbortFromCurrent), il: ThirtyTwoBit, iss: 00000022 }, Some(PrefetchAbortFromCurrent(IssPrefetchAbort { fnv: false, ea: false, ifsc: 22 })) +caught bkpt_from_t32 +Doing it again +prefetch abort occurred Hsr { ec: Ok(PrefetchAbortFromCurrent), il: ThirtyTwoBit, iss: 00000022 }, Some(PrefetchAbortFromCurrent(IssPrefetchAbort { fnv: false, ea: false, ifsc: 22 })) +caught bkpt_from_t32 +Skipping instruction +Recovered from fault OK! diff --git a/examples/mps3-an536-el2/reference/prefetch-exception-t32-thumbv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/prefetch-exception-t32-thumbv8r-none-eabihf.out new file mode 100644 index 00000000..fa18811d --- /dev/null +++ b/examples/mps3-an536-el2/reference/prefetch-exception-t32-thumbv8r-none-eabihf.out @@ -0,0 +1,8 @@ +Hello, this is a prefetch abort exception example +prefetch abort occurred Hsr { ec: Ok(PrefetchAbortFromCurrent), il: ThirtyTwoBit, iss: 00000022 }, Some(PrefetchAbortFromCurrent(IssPrefetchAbort { fnv: false, ea: false, ifsc: 22 })) +caught bkpt_from_t32 +Doing it again +prefetch abort occurred Hsr { ec: Ok(PrefetchAbortFromCurrent), il: ThirtyTwoBit, iss: 00000022 }, Some(PrefetchAbortFromCurrent(IssPrefetchAbort { fnv: false, ea: false, ifsc: 22 })) +caught bkpt_from_t32 +Skipping instruction +Recovered from fault OK! diff --git a/examples/mps3-an536-el2/reference/svc-a32-armv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/svc-a32-armv8r-none-eabihf.out new file mode 100644 index 00000000..f5d7e128 --- /dev/null +++ b/examples/mps3-an536-el2/reference/svc-a32-armv8r-none-eabihf.out @@ -0,0 +1,5 @@ +x = 1, y = 2, z = 3.000 +In hvc_handler, with Hsr { ec: Ok(Svc), il: ThirtyTwoBit, iss: 0000abcd }, Some(Svc(IssCall { imm16: abcd })), Frame { r0: 10000000, r1: 10000001, r2: 10000002, r3: 10000003, r4: 10000004, r5: 10000005 } +In hvc_handler, with Hsr { ec: Ok(Svc), il: ThirtyTwoBit, iss: 00009876 }, Some(Svc(IssCall { imm16: 9876 })), Frame { r0: 20000000, r1: 20000001, r2: 20000002, r3: 20000003, r4: 20000004, r5: 20000005 } +Got 12345678 +x = 1, y = 2, z = 3.000 diff --git a/examples/mps3-an536-el2/reference/svc-a32-thumbv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/svc-a32-thumbv8r-none-eabihf.out new file mode 100644 index 00000000..f5d7e128 --- /dev/null +++ b/examples/mps3-an536-el2/reference/svc-a32-thumbv8r-none-eabihf.out @@ -0,0 +1,5 @@ +x = 1, y = 2, z = 3.000 +In hvc_handler, with Hsr { ec: Ok(Svc), il: ThirtyTwoBit, iss: 0000abcd }, Some(Svc(IssCall { imm16: abcd })), Frame { r0: 10000000, r1: 10000001, r2: 10000002, r3: 10000003, r4: 10000004, r5: 10000005 } +In hvc_handler, with Hsr { ec: Ok(Svc), il: ThirtyTwoBit, iss: 00009876 }, Some(Svc(IssCall { imm16: 9876 })), Frame { r0: 20000000, r1: 20000001, r2: 20000002, r3: 20000003, r4: 20000004, r5: 20000005 } +Got 12345678 +x = 1, y = 2, z = 3.000 diff --git a/examples/mps3-an536-el2/reference/svc-t32-armv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/svc-t32-armv8r-none-eabihf.out new file mode 100644 index 00000000..92fda701 --- /dev/null +++ b/examples/mps3-an536-el2/reference/svc-t32-armv8r-none-eabihf.out @@ -0,0 +1,5 @@ +x = 1, y = 2, z = 3.000 +In hvc_handler, with Hsr { ec: Ok(Svc), il: SixteenBit, iss: 00000012 }, Some(Svc(IssCall { imm16: 12 })), Frame { r0: 10000000, r1: 10000001, r2: 10000002, r3: 10000003, r4: 10000004, r5: 10000005 } +In hvc_handler, with Hsr { ec: Ok(Svc), il: SixteenBit, iss: 00000032 }, Some(Svc(IssCall { imm16: 32 })), Frame { r0: 20000000, r1: 20000001, r2: 20000002, r3: 20000003, r4: 20000004, r5: 20000005 } +Got 12345678 +x = 1, y = 2, z = 3.000 diff --git a/examples/mps3-an536-el2/reference/svc-t32-thumbv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/svc-t32-thumbv8r-none-eabihf.out new file mode 100644 index 00000000..92fda701 --- /dev/null +++ b/examples/mps3-an536-el2/reference/svc-t32-thumbv8r-none-eabihf.out @@ -0,0 +1,5 @@ +x = 1, y = 2, z = 3.000 +In hvc_handler, with Hsr { ec: Ok(Svc), il: SixteenBit, iss: 00000012 }, Some(Svc(IssCall { imm16: 12 })), Frame { r0: 10000000, r1: 10000001, r2: 10000002, r3: 10000003, r4: 10000004, r5: 10000005 } +In hvc_handler, with Hsr { ec: Ok(Svc), il: SixteenBit, iss: 00000032 }, Some(Svc(IssCall { imm16: 32 })), Frame { r0: 20000000, r1: 20000001, r2: 20000002, r3: 20000003, r4: 20000004, r5: 20000005 } +Got 12345678 +x = 1, y = 2, z = 3.000 diff --git a/examples/mps3-an536-el2/reference/undef-exception-a32-armv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/undef-exception-a32-armv8r-none-eabihf.out new file mode 100644 index 00000000..7258e7b2 --- /dev/null +++ b/examples/mps3-an536-el2/reference/undef-exception-a32-armv8r-none-eabihf.out @@ -0,0 +1,8 @@ +Hello, this is a undef exception example +undefined abort occurred Hsr { ec: Ok(Unknown), il: ThirtyTwoBit, iss: 00000000 }, Some(Unknown(IssUnknown(0))) +caught udf_from_a32 +Doing it again +undefined abort occurred Hsr { ec: Ok(Unknown), il: ThirtyTwoBit, iss: 00000000 }, Some(Unknown(IssUnknown(0))) +caught udf_from_a32 +Skipping instruction +Recovered from fault OK! diff --git a/examples/mps3-an536-el2/reference/undef-exception-a32-thumbv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/undef-exception-a32-thumbv8r-none-eabihf.out new file mode 100644 index 00000000..7258e7b2 --- /dev/null +++ b/examples/mps3-an536-el2/reference/undef-exception-a32-thumbv8r-none-eabihf.out @@ -0,0 +1,8 @@ +Hello, this is a undef exception example +undefined abort occurred Hsr { ec: Ok(Unknown), il: ThirtyTwoBit, iss: 00000000 }, Some(Unknown(IssUnknown(0))) +caught udf_from_a32 +Doing it again +undefined abort occurred Hsr { ec: Ok(Unknown), il: ThirtyTwoBit, iss: 00000000 }, Some(Unknown(IssUnknown(0))) +caught udf_from_a32 +Skipping instruction +Recovered from fault OK! diff --git a/examples/mps3-an536-el2/reference/undef-exception-t32-armv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/undef-exception-t32-armv8r-none-eabihf.out new file mode 100644 index 00000000..c0f85065 --- /dev/null +++ b/examples/mps3-an536-el2/reference/undef-exception-t32-armv8r-none-eabihf.out @@ -0,0 +1,8 @@ +Hello, this is a undef exception example +undefined abort occurred Hsr { ec: Ok(Unknown), il: ThirtyTwoBit, iss: 00000000 }, Some(Unknown(IssUnknown(0))) +caught udf_from_t32 +Doing it again +undefined abort occurred Hsr { ec: Ok(Unknown), il: ThirtyTwoBit, iss: 00000000 }, Some(Unknown(IssUnknown(0))) +caught udf_from_t32 +Skipping instruction +Recovered from fault OK! diff --git a/examples/mps3-an536-el2/reference/undef-exception-t32-thumbv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/undef-exception-t32-thumbv8r-none-eabihf.out new file mode 100644 index 00000000..c0f85065 --- /dev/null +++ b/examples/mps3-an536-el2/reference/undef-exception-t32-thumbv8r-none-eabihf.out @@ -0,0 +1,8 @@ +Hello, this is a undef exception example +undefined abort occurred Hsr { ec: Ok(Unknown), il: ThirtyTwoBit, iss: 00000000 }, Some(Unknown(IssUnknown(0))) +caught udf_from_t32 +Doing it again +undefined abort occurred Hsr { ec: Ok(Unknown), il: ThirtyTwoBit, iss: 00000000 }, Some(Unknown(IssUnknown(0))) +caught udf_from_t32 +Skipping instruction +Recovered from fault OK! diff --git a/examples/mps3-an536-el2/rust-toolchain.toml b/examples/mps3-an536-el2/rust-toolchain.toml new file mode 100644 index 00000000..331a4f09 --- /dev/null +++ b/examples/mps3-an536-el2/rust-toolchain.toml @@ -0,0 +1,6 @@ +[toolchain] +channel = "stable" +targets = [ + "armv8r-none-eabihf", +] +components = ["rust-src", "clippy", "rustfmt"] diff --git a/examples/mps3-an536-el2/src/bin/abt-exception-a32.rs b/examples/mps3-an536-el2/src/bin/abt-exception-a32.rs new file mode 100644 index 00000000..ef9aa90e --- /dev/null +++ b/examples/mps3-an536-el2/src/bin/abt-exception-a32.rs @@ -0,0 +1,109 @@ +//! Example triggering an data abort exception. + +#![no_std] +#![no_main] + +use core::sync::atomic::{AtomicU32, Ordering}; + +use aarch32_cpu::register::Hsctlr; +use aarch32_rt::{entry, exception}; +use semihosting::println; + +#[unsafe(no_mangle)] +static COUNTER: AtomicU32 = AtomicU32::new(0); + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up. +#[entry] +fn main() -> ! { + // Enable alignment check for Armv7-R. Was not required + // on Cortex-A for some reason, even though the bit was not set. + enable_alignment_check(); + + println!("Hello, this is an data abort exception example"); + // Unaligned read + unaligned_from_a32(); + + // turn it off before we do the stack dump on exit, because println! has been + // observed to do unaligned reads. + disable_alignment_check(); + + println!("Recovered from fault OK!"); + + mps3_an536_el2::exit(0); +} + +#[unsafe(naked)] +#[instruction_set(arm::a32)] +extern "C" fn unaligned_from_a32() { + core::arch::naked_asm!( + "ldr r0, =COUNTER", + "adds r0, r0, 1", + "ldr r0, [r0]", + "bx lr", + ); +} + +fn enable_alignment_check() { + let mut hsctrl = Hsctlr::read(); + hsctrl.set_a(true); + Hsctlr::write(hsctrl); +} + +fn disable_alignment_check() { + let mut hsctrl = Hsctlr::read(); + hsctrl.set_a(false); + Hsctlr::write(hsctrl); +} + +#[exception(Undefined)] +fn undefined_handler(_addr: usize) -> ! { + panic!("unexpected undefined exception"); +} + +#[exception(PrefetchAbort)] +fn prefetch_abort_handler(_addr: usize) -> ! { + panic!("unexpected prefetch abort"); +} + +#[exception(DataAbort)] +unsafe fn data_abort_handler(addr: usize) -> usize { + let hsr = aarch32_cpu::register::Hsr::read(); + disable_alignment_check(); + println!("data abort occurred {:?} {:x?}", hsr, hsr.get_iss()); + enable_alignment_check(); + + // note the fault isn't at the start of the function + let expect_fault_at = unaligned_from_a32 as extern "C" fn() as usize + 8; + + if addr == expect_fault_at { + println!("caught unaligned_from_a32"); + } else { + println!( + "Bad fault address {:08x} is not {:08x}", + addr, expect_fault_at + ); + semihosting::process::abort(); + } + + match COUNTER.fetch_add(1, Ordering::Relaxed) { + 0 => { + // first time, huh? + // go back and do it again + println!("Doing it again"); + addr + } + 1 => { + // second time, huh? + // go back but skip the instruction + println!("Skipping instruction"); + addr + 4 + } + _ => { + // we've faulted thrice - time to quit + println!("We triple faulted"); + semihosting::process::abort(); + } + } +} diff --git a/examples/mps3-an536-el2/src/bin/abt-exception-t32.rs b/examples/mps3-an536-el2/src/bin/abt-exception-t32.rs new file mode 100644 index 00000000..4470a924 --- /dev/null +++ b/examples/mps3-an536-el2/src/bin/abt-exception-t32.rs @@ -0,0 +1,109 @@ +//! Example triggering an data abort exception. + +#![no_std] +#![no_main] + +use core::sync::atomic::{AtomicU32, Ordering}; + +use aarch32_cpu::register::Hsctlr; +use aarch32_rt::{entry, exception}; +use semihosting::println; + +#[unsafe(no_mangle)] +static COUNTER: AtomicU32 = AtomicU32::new(0); + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up. +#[entry] +fn main() -> ! { + // Enable alignment check for Armv7-R. Was not required + // on Cortex-A for some reason, even though the bit was not set. + enable_alignment_check(); + + println!("Hello, this is an data abort exception example"); + // Unaligned read + unaligned_from_t32(); + + // turn it off before we do the stack dump on exit, because println! has been + // observed to do unaligned reads. + disable_alignment_check(); + + println!("Recovered from fault OK!"); + + mps3_an536_el2::exit(0); +} + +#[unsafe(naked)] +#[instruction_set(arm::t32)] +extern "C" fn unaligned_from_t32() { + core::arch::naked_asm!( + "ldr r0, =COUNTER", + "adds r0, r0, 1", + "ldr r0, [r0]", + "bx lr", + ); +} + +fn enable_alignment_check() { + let mut hsctrl = Hsctlr::read(); + hsctrl.set_a(true); + Hsctlr::write(hsctrl); +} + +fn disable_alignment_check() { + let mut hsctrl = Hsctlr::read(); + hsctrl.set_a(false); + Hsctlr::write(hsctrl); +} + +#[exception(Undefined)] +fn undefined_handler(_addr: usize) -> ! { + panic!("unexpected undefined exception"); +} + +#[exception(PrefetchAbort)] +fn prefetch_abort_handler(_addr: usize) -> ! { + panic!("unexpected prefetch abort"); +} + +#[exception(DataAbort)] +unsafe fn data_abort_handler(addr: usize) -> usize { + let hsr = aarch32_cpu::register::Hsr::read(); + disable_alignment_check(); + println!("data abort occurred {:?} {:x?}", hsr, hsr.get_iss()); + enable_alignment_check(); + + // note the fault isn't at the start of the function + let expect_fault_at = unaligned_from_t32 as extern "C" fn() as usize + 3; + + if addr == expect_fault_at { + println!("caught unaligned_from_t32"); + } else { + println!( + "Bad fault address {:08x} is not {:08x}", + addr, expect_fault_at + ); + semihosting::process::abort(); + } + + match COUNTER.fetch_add(1, Ordering::Relaxed) { + 0 => { + // first time, huh? + // go back and do it again + println!("Doing it again"); + addr + } + 1 => { + // second time, huh? + // go back but skip the instruction + println!("Skipping instruction"); + addr + 2 + } + _ => { + // we've faulted thrice - time to quit + println!("We triple faulted"); + semihosting::process::abort(); + } + } +} diff --git a/examples/mps3-an536-el2/src/bin/generic-timer.rs b/examples/mps3-an536-el2/src/bin/generic-timer.rs new file mode 100644 index 00000000..102a3220 --- /dev/null +++ b/examples/mps3-an536-el2/src/bin/generic-timer.rs @@ -0,0 +1,115 @@ +//! Hyp Timer Test Arm Cortex-R52 running in EL2 (Hyp Mode) + +#![no_std] +#![no_main] + +use core::sync::atomic::{AtomicU32, Ordering::Relaxed}; + +use aarch32_cpu::generic_timer::GenericTimer; +use aarch32_rt::{entry, exception, irq}; +use arm_gic::gicv3::{GicCpuInterface, Group, InterruptGroup}; +use semihosting::println; + +use mps3_an536_el2::HYP_TIMER_PPI; + +static TICK_COUNT: AtomicU32 = AtomicU32::new(0); + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up code at the bottom of this file. +#[entry] +fn main() -> ! { + let mut board = mps3_an536_el2::Board::new().unwrap(); + + println!("{:?}", aarch32_cpu::register::Hcr::read()); + + println!("Configure Timer Interrupt..."); + board + .gic + .set_interrupt_priority(HYP_TIMER_PPI, Some(0), 0x31) + .unwrap(); + board + .gic + .set_group(HYP_TIMER_PPI, Some(0), Group::Group1NS) + .unwrap(); + board + .gic + .enable_interrupt(HYP_TIMER_PPI, Some(0), true) + .unwrap(); + + let mut hyp_timer = board.hyp_timer; + + println!("Timer Hz = {}", hyp_timer.frequency_hz()); + hyp_timer.interrupt_mask(false); + hyp_timer.countdown_set(hyp_timer.frequency_hz() / 5); + hyp_timer.enable(true); + // used in interrupt handler + drop(hyp_timer); + + println!("Enabling interrupts..."); + dump_sctlr(); + unsafe { + aarch32_cpu::interrupt::enable(); + } + dump_sctlr(); + + loop { + aarch32_cpu::asm::wfi(); + let tick_count = TICK_COUNT.load(Relaxed); + // println!("Main loop wake up {}", tick_count); + if tick_count >= 10 { + break; + } + } + + println!("EL2 timer test completed OK"); + + mps3_an536_el2::exit(0); +} + +fn dump_sctlr() { + let sctlr = aarch32_cpu::register::Sctlr::read(); + println!("{:?}", sctlr); +} + +#[irq] +fn irq_handler() { + println!("> IRQ"); + while let Some(int_id) = GicCpuInterface::get_and_acknowledge_interrupt(InterruptGroup::Group1) + { + match int_id { + HYP_TIMER_PPI => { + println!("Hyp timer tick!"); + handle_timer_irq(); + } + _ => { + println!("Interrupt {:?}?", int_id); + } + } + GicCpuInterface::end_interrupt(int_id, InterruptGroup::Group1); + } + println!("< IRQ"); +} + +/// Run when the timer IRQ fires +fn handle_timer_irq() { + // SAFETY: We drop en other time handle in main, this is the only active handle. + let mut el2_timer = unsafe { aarch32_cpu::generic_timer::El2HypPhysicalTimer::new() }; + // trigger a timer in 0.2 seconds + el2_timer.countdown_set(el2_timer.frequency_hz() / 5); + // tell the main loop the timer went tick + TICK_COUNT.fetch_add(1, Relaxed); +} + +/// This is our HVC exception handler +#[exception(HypervisorCall)] +fn hvc_handler(hsr: u32, frame: &aarch32_rt::Frame) -> u32 { + let hsr = aarch32_cpu::register::Hsr::new_with_raw_value(hsr); + println!( + "In hvc_handler, with {:08x?}, {:x?}, {:08x?}", + hsr, + hsr.get_iss(), + frame + ); + return frame.r0; +} diff --git a/examples/mps3-an536-el2/src/bin/hello.rs b/examples/mps3-an536-el2/src/bin/hello.rs new file mode 100644 index 00000000..7f1c01df --- /dev/null +++ b/examples/mps3-an536-el2/src/bin/hello.rs @@ -0,0 +1,28 @@ +//! Semihosting hello-world for Arm Cortex-R52 running in EL2 (Hyp Mode) + +#![no_std] +#![no_main] + +use aarch32_rt::entry; +use semihosting::println; + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up code at the bottom of this file. +#[entry] +fn main() -> ! { + let x = 1.0f64; + let y = x * 2.0; + println!("Hello, this is semihosting! x = {:0.3}, y = {:0.3}", x, y); + println!("{:?}", aarch32_cpu::register::Sctlr::read()); + println!("{:?}", aarch32_cpu::register::Cpsr::read()); + + let mut mpu = unsafe { aarch32_cpu::pmsav8::El2Mpu::new() }; + for idx in 0..mpu.num_regions() { + if let Some(region) = mpu.get_region(idx) { + println!("Region {}: {:?}", idx, region); + } + } + + mps3_an536_el2::exit(0); +} diff --git a/examples/mps3-an536-el2/src/bin/hvc-a32.rs b/examples/mps3-an536-el2/src/bin/hvc-a32.rs new file mode 100644 index 00000000..8d12237b --- /dev/null +++ b/examples/mps3-an536-el2/src/bin/hvc-a32.rs @@ -0,0 +1,64 @@ +//! HVC (hypervisor call) example + +#![no_std] +#![no_main] + +use aarch32_rt::{entry, exception}; +use semihosting::println; + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up. +#[entry] +fn main() -> ! { + let x = 1; + let y = x + 1; + let z = (y as f64) * 1.5; + println!("x = {}, y = {}, z = {:0.3}", x, y, z); + let value = do_hvc1(); + println!("Got {:08x}", value); + println!("x = {}, y = {}, z = {:0.3}", x, y, z); + mps3_an536_el2::exit(0); +} + +/// This is our HVC exception handler +#[exception(HypervisorCall)] +fn hvc_handler(hsr: u32, frame: &aarch32_rt::Frame) -> u32 { + let hsr = aarch32_cpu::register::Hsr::new_with_raw_value(hsr); + println!( + "In hvc_handler, with {:08x?}, {:x?}, {:08x?}", + hsr, + hsr.get_iss(), + frame + ); + if hsr.iss().value() == 0xABCD { + do_hvc2(); + } + return 0x12345678; +} + +#[instruction_set(arm::a32)] +fn do_hvc1() -> u32 { + aarch32_cpu::hvc6!( + 0xABCD, + 0x1000_0000, + 0x1000_0001, + 0x1000_0002, + 0x1000_0003, + 0x1000_0004, + 0x1000_0005 + ) +} + +#[instruction_set(arm::a32)] +fn do_hvc2() -> u32 { + aarch32_cpu::hvc6!( + 0x9876, + 0x2000_0000, + 0x2000_0001, + 0x2000_0002, + 0x2000_0003, + 0x2000_0004, + 0x2000_0005 + ) +} diff --git a/examples/mps3-an536-el2/src/bin/hvc-t32.rs b/examples/mps3-an536-el2/src/bin/hvc-t32.rs new file mode 100644 index 00000000..98ad5808 --- /dev/null +++ b/examples/mps3-an536-el2/src/bin/hvc-t32.rs @@ -0,0 +1,64 @@ +//! HVC (hypervisor call) example + +#![no_std] +#![no_main] + +use aarch32_rt::{entry, exception}; +use semihosting::println; + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up. +#[entry] +fn main() -> ! { + let x = 1; + let y = x + 1; + let z = (y as f64) * 1.5; + println!("x = {}, y = {}, z = {:0.3}", x, y, z); + let value = do_hvc1(); + println!("Got {:08x}", value); + println!("x = {}, y = {}, z = {:0.3}", x, y, z); + mps3_an536_el2::exit(0); +} + +/// This is our HVC exception handler +#[exception(HypervisorCall)] +fn hvc_handler(hsr: u32, frame: &aarch32_rt::Frame) -> u32 { + let hsr = aarch32_cpu::register::Hsr::new_with_raw_value(hsr); + println!( + "In hvc_handler, with {:08x?}, {:x?}, {:08x?}", + hsr, + hsr.get_iss(), + frame + ); + if hsr.iss().value() == 0xABCD { + do_hvc2(); + } + return 0x12345678; +} + +#[instruction_set(arm::t32)] +fn do_hvc1() -> u32 { + aarch32_cpu::hvc6!( + 0xABCD, + 0x1000_0000, + 0x1000_0001, + 0x1000_0002, + 0x1000_0003, + 0x1000_0004, + 0x1000_0005 + ) +} + +#[instruction_set(arm::t32)] +fn do_hvc2() -> u32 { + aarch32_cpu::hvc6!( + 0x9876, + 0x2000_0000, + 0x2000_0001, + 0x2000_0002, + 0x2000_0003, + 0x2000_0004, + 0x2000_0005 + ) +} diff --git a/examples/mps3-an536-el2/src/bin/prefetch-exception-a32.rs b/examples/mps3-an536-el2/src/bin/prefetch-exception-a32.rs new file mode 100644 index 00000000..8df1a094 --- /dev/null +++ b/examples/mps3-an536-el2/src/bin/prefetch-exception-a32.rs @@ -0,0 +1,85 @@ +//! Example triggering a prefetch abort exception. + +#![no_std] +#![no_main] + +use core::sync::atomic::{AtomicU32, Ordering}; + +use aarch32_rt::{entry, exception}; +use semihosting::println; + +static COUNTER: AtomicU32 = AtomicU32::new(0); + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up. +#[entry] +fn main() -> ! { + println!("Hello, this is a prefetch abort exception example"); + + // A BKPT instruction triggers a Prefetch Abort except when Halting debug-mode is enabled. + // See p. 2038 of ARMv7-M Architecture Reference Manual + + // trigger an prefetch abort exception, from A32 (Arm) mode + bkpt_from_a32(); + + println!("Recovered from fault OK!"); + + mps3_an536_el2::exit(0); +} + +#[unsafe(naked)] +#[instruction_set(arm::a32)] +extern "C" fn bkpt_from_a32() { + core::arch::naked_asm!( + r#" + bkpt #0 + bx lr + "# + ); +} + +// Custom link sections are allowed as well. +#[exception(Undefined)] +fn undefined_handler(_addr: usize) -> ! { + panic!("unexpected undefined exception"); +} + +#[exception(PrefetchAbort)] +unsafe fn prefetch_abort_handler(addr: usize) -> usize { + let hsr = aarch32_cpu::register::Hsr::read(); + println!("prefetch abort occurred {:08x?}, {:x?}", hsr, hsr.get_iss()); + + if addr == bkpt_from_a32 as extern "C" fn() as usize { + println!("caught bkpt_from_a32"); + } else { + println!( + "Bad fault address {:08x} is not {:08x}", + addr, bkpt_from_a32 as extern "C" fn() as usize + ); + } + + match COUNTER.fetch_add(1, Ordering::Relaxed) { + 0 => { + // first time, huh? + // go back and do it again + println!("Doing it again"); + addr + } + 1 => { + // second time, huh? + // go back but skip the instruction + println!("Skipping instruction"); + addr + 4 + } + _ => { + // we've faulted thrice - time to quit + panic!("prefetch_handler called too often"); + } + } +} + +#[exception(DataAbort)] +fn data_abort_handler(_addr: usize) -> ! { + panic!("unexpected data abort exception"); +} diff --git a/examples/mps3-an536-el2/src/bin/prefetch-exception-t32.rs b/examples/mps3-an536-el2/src/bin/prefetch-exception-t32.rs new file mode 100644 index 00000000..9d409483 --- /dev/null +++ b/examples/mps3-an536-el2/src/bin/prefetch-exception-t32.rs @@ -0,0 +1,87 @@ +//! Example triggering a prefetch abort exception. + +#![no_std] +#![no_main] + +use core::sync::atomic::{AtomicU32, Ordering}; + +use aarch32_rt::{entry, exception}; +use semihosting::println; + +static COUNTER: AtomicU32 = AtomicU32::new(0); + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up. +#[entry] +fn main() -> ! { + println!("Hello, this is a prefetch abort exception example"); + + // A BKPT instruction triggers a Prefetch Abort except when Halting debug-mode is enabled. + // See p. 2038 of ARMv7-M Architecture Reference Manual + + // trigger an prefetch abort exception, from T32 (Thumb) mode + bkpt_from_t32(); + + println!("Recovered from fault OK!"); + + mps3_an536_el2::exit(0); +} + +#[unsafe(naked)] +#[instruction_set(arm::t32)] +extern "C" fn bkpt_from_t32() { + core::arch::naked_asm!( + r#" + bkpt #0 + bx lr + "# + ); +} + +#[exception(Undefined)] +fn undefined_handler(_addr: usize) -> ! { + panic!("unexpected undefined exception"); +} + +#[exception(PrefetchAbort)] +unsafe fn prefetch_abort_handler(addr: usize) -> usize { + let hsr = aarch32_cpu::register::Hsr::read(); + println!("prefetch abort occurred {:08x?}, {:x?}", hsr, hsr.get_iss()); + + if (addr + 1) == bkpt_from_t32 as extern "C" fn() as usize { + // note that thumb functions have their LSB set, despite always being a + // multiple of two - that's how the CPU knows they are written in T32 + // machine code. + println!("caught bkpt_from_t32"); + } else { + println!( + "Bad fault address {:08x} is not {:08x}", + addr, bkpt_from_t32 as extern "C" fn() as usize + ); + } + + match COUNTER.fetch_add(1, Ordering::Relaxed) { + 0 => { + // first time, huh? + // go back and do it again + println!("Doing it again"); + addr + } + 1 => { + // second time, huh? + // go back but skip the instruction + println!("Skipping instruction"); + addr + 2 + } + _ => { + // we've faulted thrice - time to quit + panic!("prefetch_handler called too often"); + } + } +} + +#[exception(DataAbort)] +fn data_abort_handler(_addr: usize) -> ! { + panic!("unexpected data abort exception"); +} diff --git a/examples/mps3-an536-el2/src/bin/svc-a32.rs b/examples/mps3-an536-el2/src/bin/svc-a32.rs new file mode 100644 index 00000000..e131b768 --- /dev/null +++ b/examples/mps3-an536-el2/src/bin/svc-a32.rs @@ -0,0 +1,64 @@ +//! SVC (supervisor call) at EL2 example + +#![no_std] +#![no_main] + +use aarch32_rt::{entry, exception}; +use semihosting::println; + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up. +#[entry] +fn main() -> ! { + let x = 1; + let y = x + 1; + let z = (y as f64) * 1.5; + println!("x = {}, y = {}, z = {:0.3}", x, y, z); + let value = do_svc1(); + println!("Got {:08x}", value); + println!("x = {}, y = {}, z = {:0.3}", x, y, z); + mps3_an536_el2::exit(0); +} + +/// This is our HVC exception handler +#[exception(HypervisorCall)] +fn hvc_handler(hsr: u32, frame: &aarch32_rt::Frame) -> u32 { + let hsr = aarch32_cpu::register::Hsr::new_with_raw_value(hsr); + println!( + "In hvc_handler, with {:08x?}, {:x?}, {:08x?}", + hsr, + hsr.get_iss(), + frame + ); + if hsr.iss().value() == 0xABCD { + do_svc2(); + } + return 0x12345678; +} + +#[instruction_set(arm::a32)] +fn do_svc1() -> u32 { + aarch32_cpu::svc6!( + 0xABCD, + 0x1000_0000, + 0x1000_0001, + 0x1000_0002, + 0x1000_0003, + 0x1000_0004, + 0x1000_0005 + ) +} + +#[instruction_set(arm::a32)] +fn do_svc2() -> u32 { + aarch32_cpu::svc6!( + 0x9876, + 0x2000_0000, + 0x2000_0001, + 0x2000_0002, + 0x2000_0003, + 0x2000_0004, + 0x2000_0005 + ) +} diff --git a/examples/mps3-an536-el2/src/bin/svc-t32.rs b/examples/mps3-an536-el2/src/bin/svc-t32.rs new file mode 100644 index 00000000..6957aea0 --- /dev/null +++ b/examples/mps3-an536-el2/src/bin/svc-t32.rs @@ -0,0 +1,64 @@ +//! SVC (supervisor call) at EL2 example + +#![no_std] +#![no_main] + +use aarch32_rt::{entry, exception}; +use semihosting::println; + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up. +#[entry] +fn main() -> ! { + let x = 1; + let y = x + 1; + let z = (y as f64) * 1.5; + println!("x = {}, y = {}, z = {:0.3}", x, y, z); + let value = do_svc1(); + println!("Got {:08x}", value); + println!("x = {}, y = {}, z = {:0.3}", x, y, z); + mps3_an536_el2::exit(0); +} + +/// This is our HVC exception handler +#[exception(HypervisorCall)] +fn hvc_handler(hsr: u32, frame: &aarch32_rt::Frame) -> u32 { + let hsr = aarch32_cpu::register::Hsr::new_with_raw_value(hsr); + println!( + "In hvc_handler, with {:08x?}, {:x?}, {:08x?}", + hsr, + hsr.get_iss(), + frame + ); + if hsr.iss().value() == 0x12 { + do_svc2(); + } + return 0x12345678; +} + +#[instruction_set(arm::t32)] +fn do_svc1() -> u32 { + aarch32_cpu::svc6!( + 0x12, + 0x1000_0000, + 0x1000_0001, + 0x1000_0002, + 0x1000_0003, + 0x1000_0004, + 0x1000_0005 + ) +} + +#[instruction_set(arm::t32)] +fn do_svc2() -> u32 { + aarch32_cpu::svc6!( + 0x32, + 0x2000_0000, + 0x2000_0001, + 0x2000_0002, + 0x2000_0003, + 0x2000_0004, + 0x2000_0005 + ) +} diff --git a/examples/mps3-an536-el2/src/bin/undef-exception-a32.rs b/examples/mps3-an536-el2/src/bin/undef-exception-a32.rs new file mode 100644 index 00000000..9d9d44d0 --- /dev/null +++ b/examples/mps3-an536-el2/src/bin/undef-exception-a32.rs @@ -0,0 +1,85 @@ +//! Example triggering a undef exception. + +#![no_std] +#![no_main] + +use core::sync::atomic::{AtomicU32, Ordering}; + +use aarch32_rt::{entry, exception}; +use semihosting::println; + +static COUNTER: AtomicU32 = AtomicU32::new(0); + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up. +#[entry] +fn main() -> ! { + println!("Hello, this is a undef exception example"); + + // trigger an Undefined exception, from A32 (Arm) mode + udf_from_a32(); + + println!("Recovered from fault OK!"); + + mps3_an536_el2::exit(0); +} + +#[unsafe(naked)] +#[instruction_set(arm::a32)] +extern "C" fn udf_from_a32() { + core::arch::naked_asm!( + // Do a UDF + "udf #0", + // Return + "bx lr", + ); +} + +#[exception(PrefetchAbort)] +fn prefetch_abort_handler(_addr: usize) -> ! { + panic!("unexpected undefined exception"); +} + +#[exception(Undefined)] +unsafe fn undefined_handler(addr: usize) -> usize { + let hsr = aarch32_cpu::register::Hsr::read(); + println!( + "undefined abort occurred {:08x?}, {:x?}", + hsr, + hsr.get_iss() + ); + + if addr == udf_from_a32 as extern "C" fn() as usize { + println!("caught udf_from_a32"); + } else { + println!( + "Bad fault address {:08x} is not {:08x}", + addr, udf_from_a32 as extern "C" fn() as usize + ); + } + + match COUNTER.fetch_add(1, Ordering::Relaxed) { + 0 => { + // first time, huh? + // go back and do it again + println!("Doing it again"); + addr + } + 1 => { + // second time, huh? + // go back but skip the instruction + println!("Skipping instruction"); + addr + 4 + } + _ => { + // we've faulted thrice - time to quit + panic!("_undefined_handler called too often"); + } + } +} + +#[exception(DataAbort)] +fn data_abort_handler(_addr: usize) -> ! { + panic!("unexpected data abort exception"); +} diff --git a/examples/mps3-an536-el2/src/bin/undef-exception-t32.rs b/examples/mps3-an536-el2/src/bin/undef-exception-t32.rs new file mode 100644 index 00000000..cf7bc611 --- /dev/null +++ b/examples/mps3-an536-el2/src/bin/undef-exception-t32.rs @@ -0,0 +1,88 @@ +//! Example triggering a undef exception. + +#![no_std] +#![no_main] + +use core::sync::atomic::{AtomicU32, Ordering}; + +use aarch32_rt::{entry, exception}; +use semihosting::println; + +static COUNTER: AtomicU32 = AtomicU32::new(0); + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up. +#[entry] +fn main() -> ! { + println!("Hello, this is a undef exception example"); + + // trigger an Undefined exception, from T32 (Thumb) mode + udf_from_t32(); + + println!("Recovered from fault OK!"); + + mps3_an536_el2::exit(0); +} + +#[unsafe(naked)] +#[instruction_set(arm::t32)] +extern "C" fn udf_from_t32() { + core::arch::naked_asm!( + // Do a UDF + "udf #0", + // Return + "bx lr", + ); +} + +#[exception(PrefetchAbort)] +fn prefetch_abort_handler(_addr: usize) -> ! { + panic!("unexpected undefined exception"); +} + +#[exception(Undefined)] +unsafe fn undefined_handler(addr: usize) -> usize { + let hsr = aarch32_cpu::register::Hsr::read(); + println!( + "undefined abort occurred {:08x?}, {:x?}", + hsr, + hsr.get_iss() + ); + + if (addr + 1) == udf_from_t32 as extern "C" fn() as usize { + // note that thumb functions have their LSB set, despite always being a + // multiple of two - that's how the CPU knows they are written in T32 + // machine code. + println!("caught udf_from_t32"); + } else { + println!( + "Bad fault address {:08x} is not {:08x}", + addr, udf_from_t32 as extern "C" fn() as usize + ); + } + + match COUNTER.fetch_add(1, Ordering::Relaxed) { + 0 => { + // first time, huh? + // go back and do it again + println!("Doing it again"); + addr + } + 1 => { + // second time, huh? + // go back but skip the instruction + println!("Skipping instruction"); + addr + 2 + } + _ => { + // we've faulted thrice - time to quit + panic!("_undefined_handler called too often"); + } + } +} + +#[exception(DataAbort)] +fn data_abort_handler(_addr: usize) -> ! { + panic!("unexpected data abort exception"); +} diff --git a/examples/mps3-an536-el2/src/lib.rs b/examples/mps3-an536-el2/src/lib.rs new file mode 100644 index 00000000..604ca126 --- /dev/null +++ b/examples/mps3-an536-el2/src/lib.rs @@ -0,0 +1,256 @@ +//! Common code for all examples +//! +//! ## Interrupt Map +//! +//! | Interrupt ID | Description | +//! |--------------|------------------------------| +//! | `EXTPPI0[0]` | UART 0 Receive Interrupt | +//! | `EXTPPI0[1]` | UART 0 Transmit Interrupt | +//! | `EXTPPI0[2]` | UART 0 Combined Interrupt | +//! | `EXTPPI0[3]` | UART 0 Overflow | +//! | `EXTPPI1[0]` | UART 1 Receive Interrupt | +//! | `EXTPPI1[1]` | UART 1 Transmit Interrupt | +//! | `EXTPPI1[2]` | UART 1 Combined Interrupt | +//! | `EXTPPI1[3]` | UART 1 Overflow | +//! | `SP[0]` | WDG | +//! | `SP[1]` | DualTimer 1 | +//! | `SP[2]` | DualTimer 2 | +//! | `SP[3]` | DualTimer Combined | +//! | `SP[4]` | RTC | +//! | `SP[5]` | UART 2 Receive Interrupt | +//! | `SP[6]` | UART 2 Transmit Interrupt | +//! | `SP[7]` | UART 3 Receive Interrupt | +//! | `SP[8]` | UART 3 Transmit Interrupt | +//! | `SP[9]` | UART 4 Receive Interrupt | +//! | `SP[10]` | UART 4 Transmit Interrupt | +//! | `SP[11]` | UART 5 Receive Interrupt | +//! | `SP[12]` | UART 5 Transmit Interrupt | +//! | `SP[13]` | UART 2 Combined Interrupt | +//! | `SP[14]` | UART 3 Combined Interrupt | +//! | `SP[15]` | UART 4 Combined Interrupt | +//! | `SP[16]` | UART 5 Combined Interrupt | +//! | `SP[17]` | UART Overflow (2, 3, 4 & 5) | +//! | `SP[18]` | Ethernet | +//! | `SP[19]` | USB | +//! | `SP[20]` | FPGA Audio I2S | +//! | `SP[21]` | Touch Screen | +//! | `SP[22]` | SPI ADC | +//! | `SP[23]` | SPI Shield 0 | +//! | `SP[24]` | SPI Shield 1 | +//! | `SP[25]` | HDCLCD Interrupt | +//! | `SP[26]` | GPIO 0 Combined Interrupt | +//! | `SP[27]` | GPIO 1 Combined Interrupt | +//! | `SP[28]` | GPIO 2 Combined Interrupt | +//! | `SP[29]` | GPIO 3 Combined Interrupt | +//! | `SP[30..=45]`| GPIO 0.x Interrupt | +//! | `SP[46..=61]`| GPIO 1.x Interrupt | +//! | `SP[62..=77]`| GPIO 2.x Interrupt | +//! | `SP[78..=93]`| GPIO 3.x Interrupt | +//! +//! * Interrupt ID `SP[x]` are shared across cores +//! * Interrupt ID `EXTPPI0[x]` is only available on Core 0 +//! * Interrupt ID `EXTPPI1[x]` is only available on Core 1 + +#![no_std] + +use core::sync::atomic::{AtomicBool, Ordering}; + +/// The PPI for the virtual timer, according to the Cortex-R52 Technical Reference Manual, +/// Table 10-3: PPI assignments. +/// +/// This corresponds to Interrupt ID 27. +pub const VIRTUAL_TIMER_PPI: arm_gic::IntId = arm_gic::IntId::ppi(11); + +/// The PPI for the EL2 timer, according to the Cortex-R52 Technical Reference Manual, +/// Table 10-3: PPI assignments. +/// +/// This corresponds to Interrupt ID 26. +pub const HYP_TIMER_PPI: arm_gic::IntId = arm_gic::IntId::ppi(10); + +#[cfg(not(arm_architecture = "v8-r"))] +compile_error!("This example is only compatible to the ARMv8-R architecture"); + +static WANT_PANIC: AtomicBool = AtomicBool::new(false); + +/// Track if we're already in the exit routine. +/// +/// Stops us doing infinite recursion if we panic whilst doing the stack reporting. +static IN_EXIT: AtomicBool = AtomicBool::new(false); + +/// Called when the application raises an unrecoverable `panic!`. +/// +/// Prints the panic to the console and then exits QEMU using a semihosting +/// breakpoint. +#[panic_handler] +#[cfg(target_os = "none")] +fn panic(info: &core::panic::PanicInfo) -> ! { + semihosting::println!("PANIC: {:#?}", info); + if WANT_PANIC.load(Ordering::Relaxed) { + exit(0); + } else { + exit(1); + } +} + +/// Set the panic function as no longer returning a failure code via semihosting +pub fn want_panic() { + WANT_PANIC.store(true, Ordering::Relaxed); +} + +/// Exit from QEMU with code +pub fn exit(code: i32) -> ! { + if !IN_EXIT.swap(true, Ordering::Relaxed) { + stack_dump(); + } + semihosting::process::exit(code) +} + +/// Print stack using to semihosting output for each stack +/// +/// Produces output like: +/// +/// ```text +/// Stack usage report: +/// UND0 Stack = 0 used of 16384 bytes (000%) @ 0x1006bf80..0x1006ff80 +/// SVC0 Stack = 0 used of 16384 bytes (000%) @ 0x1006ff80..0x10073f80 +/// ABT0 Stack = 0 used of 16384 bytes (000%) @ 0x10073f80..0x10077f80 +/// HYP0 Stack = 0 used of 16384 bytes (000%) @ 0x10077f80..0x1007bf80 +/// IRQ0 Stack = 0 used of 64 bytes (000%) @ 0x1007bf80..0x1007bfc0 +/// FIQ0 Stack = 0 used of 64 bytes (000%) @ 0x1007bfc0..0x1007c000 +/// SYS0 Stack = 2416 used of 16384 bytes (014%) @ 0x1007c000..0x10080000 +/// ``` +fn stack_dump() { + use aarch32_cpu::stacks::stack_used_bytes; + use aarch32_rt::stacks::Stack; + + semihosting::eprintln!("Stack usage report:"); + + unsafe { + for stack in Stack::iter() { + for core in (0..Stack::num_cores()).rev() { + let core_range = stack.range(core).unwrap(); + let (total, used) = stack_used_bytes(core_range.clone()); + let percent = used * 100 / total; + // Send to stderr, so it doesn't mix with expected output on stdout + semihosting::eprintln!( + "{}{} Stack = {:6} used of {:6} bytes ({:03}%) @ {:08x?}", + stack, + core, + used, + total, + percent, + core_range + ); + } + } + } +} + +#[derive(Clone, Debug)] +/// Represents a handler for an interrupt +pub struct InterruptHandler { + int_id: arm_gic::IntId, + function: fn(arm_gic::IntId), +} + +impl InterruptHandler { + /// Create a new `InterruptHandler`, associating an `IntId` with a function to call + pub const fn new(int_id: arm_gic::IntId, function: fn(arm_gic::IntId)) -> InterruptHandler { + InterruptHandler { int_id, function } + } + + /// Get the [`arm_gic::IntId`] this handler is for + pub const fn int_id(&self) -> arm_gic::IntId { + self.int_id + } + + /// Is this handler for this [`arm_gic::IntId`]? + pub fn matches(&self, int_id: arm_gic::IntId) -> bool { + self.int_id == int_id + } + + /// Execute the handler + pub fn execute(&self) { + (self.function)(self.int_id); + } +} + +/// Represents all the hardware we support in our MPS3-AN536 system +pub struct Board { + /// The Arm Generic Interrupt Controller (v3) + pub gic: arm_gic::gicv3::GicV3<'static>, + /// The Arm Virtual Generic Timer + pub virtual_timer: aarch32_cpu::generic_timer::El2VirtualTimer, + /// The Arm Physical Generic Timer + pub physical_timer: aarch32_cpu::generic_timer::El2PhysicalTimer, + /// The Arm EL2-specific Physical Generic Timer + pub hyp_timer: aarch32_cpu::generic_timer::El2HypPhysicalTimer, +} + +impl Board { + /// Create a new board structure. + /// + /// Returns `Some(board)` the first time you call it, and None thereafter, + /// so you cannot have two copies of the [`Board`] structure. + pub fn new() -> Option { + static TAKEN: AtomicBool = AtomicBool::new(false); + if TAKEN.swap(true, Ordering::SeqCst) { + // they already took the peripherals + return None; + } + Some(Board { + // SAFETY: This is the first and only call to `make_gic()` as guaranteed by + // the atomic flag check above, ensuring no aliasing of GIC register access. + gic: unsafe { make_gic() }, + // SAFETY: This is the first and only time we create the virtual timer instance + // as guaranteed by the atomic flag check above, ensuring exclusive access. + virtual_timer: unsafe { aarch32_cpu::generic_timer::El2VirtualTimer::new() }, + // SAFETY: This is the first and only time we create the physical timer instance + // as guaranteed by the atomic flag check above, ensuring exclusive access. + physical_timer: unsafe { aarch32_cpu::generic_timer::El2PhysicalTimer::new() }, + // SAFETY: This is the first and only time we create the hyp physical timer instance + // as guaranteed by the atomic flag check above, ensuring exclusive access. + hyp_timer: unsafe { aarch32_cpu::generic_timer::El2HypPhysicalTimer::new() }, + }) + } +} + +/// Create the ARM GIC driver +/// +/// # Safety +/// +/// Only call this function once. +unsafe fn make_gic() -> arm_gic::gicv3::GicV3<'static> { + /// Offset from PERIPHBASE for GIC Distributor + const GICD_BASE_OFFSET: usize = 0x0000_0000usize; + + /// Offset from PERIPHBASE for the first GIC Redistributor + const GICR_BASE_OFFSET: usize = 0x0010_0000usize; + + // Get the GIC address by reading CBAR + let periphbase = aarch32_cpu::register::ImpCbar::read().periphbase(); + semihosting::println!("Found PERIPHBASE {:010p}", periphbase); + let gicd_base = periphbase.wrapping_byte_add(GICD_BASE_OFFSET); + let gicr_base = periphbase.wrapping_byte_add(GICR_BASE_OFFSET); + + // Initialise the GIC. + semihosting::println!( + "Creating GIC driver @ {:010p} / {:010p}", + gicd_base, + gicr_base + ); + // SAFETY: `gicd_base` points to the valid GICD MMIO region as obtained from the + // hardware CBAR register. This pointer is used exclusively by this GIC instance. + let gicd = unsafe { + arm_gic::UniqueMmioPointer::new(core::ptr::NonNull::new(gicd_base.cast()).unwrap()) + }; + let gicr_base = core::ptr::NonNull::new(gicr_base.cast()).unwrap(); + // SAFETY: The GICD and GICR base addresses point to valid GICv3 MMIO regions as + // obtained from the hardware CBAR register. This function is only called once + // (via Board::new()'s atomic guard), ensuring exclusive ownership of the GIC. + let mut gic = unsafe { arm_gic::gicv3::GicV3::new(gicd, gicr_base, 1, false) }; + semihosting::println!("Calling git.setup(0)"); + gic.setup(0); + arm_gic::gicv3::GicCpuInterface::set_priority_mask(0x80); + gic +} diff --git a/examples/mps3-an536-smp/rust-toolchain.toml b/examples/mps3-an536-smp/rust-toolchain.toml index 7e4d9e6c..331a4f09 100644 --- a/examples/mps3-an536-smp/rust-toolchain.toml +++ b/examples/mps3-an536-smp/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "nightly-2026-01-26" +channel = "stable" targets = [ "armv8r-none-eabihf", ] diff --git a/examples/mps3-an536-smp/src/bin/smp-test.rs b/examples/mps3-an536-smp/src/bin/smp-test.rs index 4364fbbe..9fb75435 100644 --- a/examples/mps3-an536-smp/src/bin/smp-test.rs +++ b/examples/mps3-an536-smp/src/bin/smp-test.rs @@ -32,7 +32,10 @@ const CS_MUTEX_LOOPS: u32 = 1000; /// It is called by the start-up code in `aarch32-rt`. #[entry] fn main() -> ! { - println!("I am core 0 - {:08x?}", aarch32_cpu::register::Mpidr::read()); + println!( + "I am core 0 - {:08x?}", + aarch32_cpu::register::Mpidr::read() + ); mps3_an536_smp::start_core1(); @@ -93,7 +96,10 @@ fn main() -> ! { /// It is called by the start-up code below, on Core 1. #[unsafe(no_mangle)] pub extern "C" fn kmain2() { - println!("I am core 1 - {:08x?}", aarch32_cpu::register::Mpidr::read()); + println!( + "I am core 1 - {:08x?}", + aarch32_cpu::register::Mpidr::read() + ); CORE1_BOOTED.store(true, Ordering::SeqCst); for _ in 0..CAS_LOOPS { diff --git a/examples/mps3-an536-smp/src/lib.rs b/examples/mps3-an536-smp/src/lib.rs index 3fe7087a..d89d09ea 100644 --- a/examples/mps3-an536-smp/src/lib.rs +++ b/examples/mps3-an536-smp/src/lib.rs @@ -53,7 +53,7 @@ #![no_std] -use aarch32_cpu::register::{Hactlr, Cpsr, cpsr::ProcessorMode}; +use aarch32_cpu::register::{Cpsr, Hactlr, cpsr::ProcessorMode}; use core::sync::atomic::{AtomicBool, Ordering}; @@ -192,21 +192,23 @@ pub fn start_core1() { } } -// Start-up code for multi-core Armv8-R, as implemented on the MPS3-AN536. -// -// We boot into EL2, set up a stack pointer, init .data on .bss on core0, and -// run `kmain` in EL1 on all cores. +/// Start-up code for multi-core Armv8-R, as implemented on the MPS3-AN536. +/// +/// We boot into EL2, set up a stack pointer, init .data on .bss on core0, and +/// run `kmain` in EL1 on all cores. +/// +/// # Safety +/// +/// This function should not be called manually. It should only be called on reset +/// from the reset vector. +#[unsafe(naked)] +#[unsafe(no_mangle)] +#[unsafe(link_section = ".text.startup")] +#[instruction_set(arm::a32)] #[cfg(arm_architecture = "v8-r")] -core::arch::global_asm!( - r#" - .pushsection .text.startup - .align 4 - .arm - - .global _start - .global core1_released - .type _start, %function - _start: +pub unsafe extern "C" fn _start() { + core::arch::naked_asm!( + r#" // Read MPIDR into R0 mrc p15, 0, r0, c0, c0, 5 ands r0, r0, 0xFF @@ -273,30 +275,29 @@ core::arch::global_asm!( mov r12, 0 // call our kmain2 for core 1 bl kmain2 - .size _start, . - _start - .popsection "#, - hactlr_bits = const { - Hactlr::new_with_raw_value(0) - .with_cpuactlr(true) - .with_cdbgdci(true) - .with_flashifregionr(true) - .with_periphpregionr(true) - .with_qosr(true) - .with_bustimeoutr(true) - .with_intmonr(true) - .with_err(true) - .with_testr1(true) - .raw_value() - }, - sys_mode = const { - Cpsr::new_with_raw_value(0) - .with_mode(ProcessorMode::Sys) - .with_i(true) - .with_f(true) - .raw_value() - }, -); + hactlr_bits = const { + Hactlr::new_with_raw_value(0) + .with_cpuactlr(true) + .with_cdbgdci(true) + .with_flashifregionr(true) + .with_periphpregionr(true) + .with_qosr(true) + .with_bustimeoutr(true) + .with_intmonr(true) + .with_err(true) + .with_testr1(true) + .raw_value() + }, + sys_mode = const { + Cpsr::new_with_raw_value(0) + .with_mode(ProcessorMode::Sys) + .with_i(true) + .with_f(true) + .raw_value() + }, + ) +} /// What a second core does when no `kmain2` is supplied. #[unsafe(no_mangle)] diff --git a/examples/mps3-an536/rust-toolchain.toml b/examples/mps3-an536/rust-toolchain.toml index 34345179..331a4f09 100644 --- a/examples/mps3-an536/rust-toolchain.toml +++ b/examples/mps3-an536/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "nightly-2026-02-26" +channel = "stable" targets = [ "armv8r-none-eabihf", ] diff --git a/examples/mps3-an536/src/bin/generic_timer_irq.rs b/examples/mps3-an536/src/bin/generic_timer_irq.rs index f870ddea..2f2f4566 100644 --- a/examples/mps3-an536/src/bin/generic_timer_irq.rs +++ b/examples/mps3-an536/src/bin/generic_timer_irq.rs @@ -16,9 +16,6 @@ use semihosting::println; fn main() -> ! { let mut board = mps3_an536::Board::new().unwrap(); - // Only interrupts with a higher priority (numerically lower) will be signalled. - GicCpuInterface::set_priority_mask(0x80); - println!("Configure Timer Interrupt..."); board .gic diff --git a/examples/mps3-an536/src/bin/gic-map.rs b/examples/mps3-an536/src/bin/gic-map.rs index e45db841..f10edf92 100644 --- a/examples/mps3-an536/src/bin/gic-map.rs +++ b/examples/mps3-an536/src/bin/gic-map.rs @@ -29,9 +29,6 @@ static INTERRUPT_HANDLERS: critical_section::Mutex ! { let mut board = mps3_an536::Board::new().unwrap(); - // Only interrupts with a higher priority (numerically lower) will be signalled. - GicCpuInterface::set_priority_mask(0x80); - // Configure two Software Generated Interrupts for Core 0 println!("Configure low-prio SGI..."); board diff --git a/examples/mps3-an536/src/bin/gic-priority-ceiling.rs b/examples/mps3-an536/src/bin/gic-priority-ceiling.rs index 30e14cc3..c3064254 100644 --- a/examples/mps3-an536/src/bin/gic-priority-ceiling.rs +++ b/examples/mps3-an536/src/bin/gic-priority-ceiling.rs @@ -26,9 +26,6 @@ const HIGH_PRIORITY: u8 = 0x10; fn main() -> ! { let mut board = mps3_an536::Board::new().unwrap(); - // Only interrupts with a higher priority (numerically lower) will be signalled. - GicCpuInterface::set_priority_mask(0x80); - // Configure a Software Generated Interrupt for Core 0 println!("Configure low-prio SGI..."); board diff --git a/examples/mps3-an536/src/bin/gic-static-section-irq.rs b/examples/mps3-an536/src/bin/gic-static-section-irq.rs index 340bfc24..b3869885 100644 --- a/examples/mps3-an536/src/bin/gic-static-section-irq.rs +++ b/examples/mps3-an536/src/bin/gic-static-section-irq.rs @@ -23,9 +23,6 @@ const SGI_INTID_HI: IntId = IntId::sgi(4); fn main() -> ! { let mut board = mps3_an536::Board::new().unwrap(); - // Only interrupts with a higher priority (numerically lower) will be signalled. - GicCpuInterface::set_priority_mask(0x80); - // Configure two Software Generated Interrupts for Core 0 println!("Configure low-prio SGI..."); board diff --git a/examples/mps3-an536/src/lib.rs b/examples/mps3-an536/src/lib.rs index e3eb26b5..a5e5396e 100644 --- a/examples/mps3-an536/src/lib.rs +++ b/examples/mps3-an536/src/lib.rs @@ -55,12 +55,18 @@ use core::sync::atomic::{AtomicBool, Ordering}; -/// The PPI for the virutal timer, according to the Cortex-R52 Technical Reference Manual, +/// The PPI for the virtual timer, according to the Cortex-R52 Technical Reference Manual, /// Table 10-3: PPI assignments. /// /// This corresponds to Interrupt ID 27. pub const VIRTUAL_TIMER_PPI: arm_gic::IntId = arm_gic::IntId::ppi(11); +/// The PPI for the EL2 timer, according to the Cortex-R52 Technical Reference Manual, +/// Table 10-3: PPI assignments. +/// +/// This corresponds to Interrupt ID 26. +pub const HYP_TIMER_PPI: arm_gic::IntId = arm_gic::IntId::ppi(10); + #[cfg(not(arm_architecture = "v8-r"))] compile_error!("This example is only compatible to the ARMv8-R architecture"); @@ -186,24 +192,21 @@ impl Board { /// so you cannot have two copies of the [`Board`] structure. pub fn new() -> Option { static TAKEN: AtomicBool = AtomicBool::new(false); - if TAKEN - .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) - .is_ok() - { - Some(Board { - // SAFETY: This is the first and only call to `make_gic()` as guaranteed by - // the atomic flag check above, ensuring no aliasing of GIC register access. - gic: unsafe { make_gic() }, - // SAFETY: This is the first and only time we create the virtual timer instance - // as guaranteed by the atomic flag check above, ensuring exclusive access. - virtual_timer: unsafe { aarch32_cpu::generic_timer::El1VirtualTimer::new() }, - // SAFETY: This is the first and only time we create the physical timer instance - // as guaranteed by the atomic flag check above, ensuring exclusive access. - physical_timer: unsafe { aarch32_cpu::generic_timer::El1PhysicalTimer::new() }, - }) - } else { - None + if TAKEN.swap(true, Ordering::SeqCst) { + // they already took the peripherals + return None; } + Some(Board { + // SAFETY: This is the first and only call to `make_gic()` as guaranteed by + // the atomic flag check above, ensuring no aliasing of GIC register access. + gic: unsafe { make_gic() }, + // SAFETY: This is the first and only time we create the virtual timer instance + // as guaranteed by the atomic flag check above, ensuring exclusive access. + virtual_timer: unsafe { aarch32_cpu::generic_timer::El1VirtualTimer::new() }, + // SAFETY: This is the first and only time we create the physical timer instance + // as guaranteed by the atomic flag check above, ensuring exclusive access. + physical_timer: unsafe { aarch32_cpu::generic_timer::El1PhysicalTimer::new() }, + }) } } diff --git a/examples/versatileab/rust-toolchain.toml b/examples/versatileab/rust-toolchain.toml index c939921d..3f96260f 100644 --- a/examples/versatileab/rust-toolchain.toml +++ b/examples/versatileab/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "nightly-2026-02-26" +channel = "stable" targets = [ "armv7r-none-eabi", "armv7r-none-eabihf", diff --git a/justfile b/justfile index d6501bcf..07e66086 100644 --- a/justfile +++ b/justfile @@ -103,10 +103,14 @@ build-versatileab-tier2 target: # Builds the MPS3-AN536 examples, building core from source build-mps3-tier3 target: cd examples/mps3-an536 && cargo build --target={{target}} -Zbuild-std=core {{verbose}} + cd examples/mps3-an536-smp && cargo build --target={{target}} -Zbuild-std=core {{verbose}} + cd examples/mps3-an536-el2 && cargo build --target={{target}} -Zbuild-std=core {{verbose}} # Builds the MPS3-AN536 examples, assuming core has been prebuilt build-mps3-tier2 target: cd examples/mps3-an536 && cargo build --target={{target}} {{verbose}} + cd examples/mps3-an536-smp && cargo build --target={{target}} {{verbose}} + cd examples/mps3-an536-el2 && cargo build --target={{target}} {{verbose}} # Formats all the code fmt: @@ -117,6 +121,8 @@ fmt: # The cross-compiled examples cargo fmt cd examples/versatileab && cargo fmt {{verbose}} cd examples/mps3-an536 && cargo fmt {{verbose}} + cd examples/mps3-an536-smp && cargo fmt {{verbose}} + cd examples/mps3-an536-el2 && cargo fmt {{verbose}} # Checks all the code is formatted fmt-check: @@ -127,6 +133,8 @@ fmt-check: # The cross-compiled examples cargo fmt cd examples/versatileab && cargo fmt --check {{verbose}} cd examples/mps3-an536 && cargo fmt --check {{verbose}} + cd examples/mps3-an536-smp && cargo fmt --check {{verbose}} + cd examples/mps3-an536-el2 && cargo fmt --check {{verbose}} # Checks all the cross-compiled workspace passes the clippy lints clippy-targets: \ @@ -144,6 +152,8 @@ clippy-target target: clippy-examples: cd examples/versatileab && cargo clippy --target=armv7r-none-eabi {{verbose}} cd examples/mps3-an536 && cargo clippy --target=armv8r-none-eabihf {{verbose}} + cd examples/mps3-an536-smp && cargo clippy --target=armv8r-none-eabihf {{verbose}} + cd examples/mps3-an536-el2 && cargo clippy --target=armv8r-none-eabihf {{verbose}} # Checks the host code passes the clippy lints clippy-host: @@ -163,7 +173,7 @@ test-cargo: cd arm-targets && cargo test {{verbose}} # Run the integration tests in QEMU -test-qemu: test-qemu-v4t test-qemu-v5te test-qemu-v6 test-qemu-v7a test-qemu-v7r test-qemu-v8r test-qemu-v8r-smp +test-qemu: test-qemu-v4t test-qemu-v5te test-qemu-v6 test-qemu-v7a test-qemu-v7r test-qemu-v8r test-qemu-v8r-smp test-qemu-v8r-el2 test-qemu-v4t: #!/bin/bash @@ -224,3 +234,12 @@ test-qemu-v8r-smp: RUSTFLAGS=-Ctarget-cpu=cortex-r52 ./tests.sh examples/mps3-an536-smp armv8r-none-eabihf --features=fpu-d32 --target-dir=target-d32 {{verbose}} --release || FAIL=1 RUSTFLAGS=-Ctarget-cpu=cortex-r52 ./tests.sh examples/mps3-an536-smp thumbv8r-none-eabihf -Zbuild-std=core --features=fpu-d32 --target-dir=target-d32 {{verbose}} --release || FAIL=1 if [ "${FAIL}" == "1" ]; then exit 1; fi + +test-qemu-v8r-el2: + #!/bin/bash + FAIL=0 + ./tests.sh examples/mps3-an536-el2 armv8r-none-eabihf {{verbose}} --release || FAIL=1 + ./tests.sh examples/mps3-an536-el2 thumbv8r-none-eabihf -Zbuild-std=core {{verbose}} --release || FAIL=1 + RUSTFLAGS=-Ctarget-cpu=cortex-r52 ./tests.sh examples/mps3-an536-el2 armv8r-none-eabihf --features=fpu-d32 --target-dir=target-d32 {{verbose}} --release || FAIL=1 + RUSTFLAGS=-Ctarget-cpu=cortex-r52 ./tests.sh examples/mps3-an536-el2 thumbv8r-none-eabihf -Zbuild-std=core --features=fpu-d32 --target-dir=target-d32 {{verbose}} --release || FAIL=1 + if [ "${FAIL}" == "1" ]; then exit 1; fi