diff --git a/.cargo/config.toml b/.cargo/config.toml index d4d345ef..e69de29b 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,56 +0,0 @@ -[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" - -[target.armv7r-none-eabihf] -runner = "qemu-system-arm -machine versatileab -cpu cortex-r5f -semihosting -nographic -audio none -kernel" - -[target.thumbv7r-none-eabihf] -runner = "qemu-system-arm -machine versatileab -cpu cortex-r5f -semihosting -nographic -audio none -kernel" - -[target.armv7r-none-eabi] -# change '-mcpu=cortex-r5' to '-mcpu=cortex-r5f' if you use eabi-fpu feature, otherwise -# qemu-system-arm will lock up -runner = "qemu-system-arm -machine versatileab -cpu cortex-r5 -semihosting -nographic -audio none -kernel" - -[target.thumbv7r-none-eabi] -# change '-mcpu=cortex-r5' to '-mcpu=cortex-r5f' if you use eabi-fpu feature, otherwise -# qemu-system-arm will lock up -runner = "qemu-system-arm -machine versatileab -cpu cortex-r5 -semihosting -nographic -audio none -kernel" - -[target.armv7a-none-eabihf] -runner = "qemu-system-arm -machine versatileab -cpu cortex-a8 -semihosting -nographic -audio none -kernel" - -[target.thumbv7a-none-eabihf] -runner = "qemu-system-arm -machine versatileab -cpu cortex-a8 -semihosting -nographic -audio none -kernel" - -[target.armv7a-none-eabi] -runner = "qemu-system-arm -machine versatileab -cpu cortex-a8 -semihosting -nographic -audio none -kernel" - -[target.thumbv7a-none-eabi] -runner = "qemu-system-arm -machine versatileab -cpu cortex-a8 -semihosting -nographic -audio none -kernel" - -[target.armv6-none-eabihf] -runner = "qemu-system-arm -machine versatileab -cpu arm1176 -semihosting -nographic -audio none -kernel" - -[target.armv6-none-eabi] -runner = "qemu-system-arm -machine versatileab -cpu arm1176 -semihosting -nographic -audio none -kernel" - -[target.thumbv6-none-eabi] -runner = "qemu-system-arm -machine versatileab -cpu arm1176 -semihosting -nographic -audio none -kernel" - -[target.armv5te-none-eabi] -runner = "qemu-system-arm -machine versatileab -cpu arm926 -semihosting -nographic -audio none -kernel" - -[target.thumbv5te-none-eabi] -runner = "qemu-system-arm -machine versatileab -cpu arm926 -semihosting -nographic -audio none -kernel" - -[target.armv4t-none-eabi] -runner = "qemu-system-arm -machine versatileab -cpu arm926 -semihosting -nographic -audio none -kernel" - -[target.thumbv4t-none-eabi] -runner = "qemu-system-arm -machine versatileab -cpu arm926 -semihosting -nographic -audio none -kernel" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index afc8b3ff..7e0be5d6 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 a139b23f..e58f52da 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,10 @@ target 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 29d282c9..1c0f63d0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,7 +7,10 @@ "rust-analyzer.checkOnSave": true, "rust-analyzer.linkedProjects": [ "./Cargo.toml", + "./arm-targets/Cargo.toml", "examples/versatileab/Cargo.toml", - "examples/mps3-an536/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 243b915a..61edd8f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,8 @@ exclude = [ "arm-targets", "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/armv8r/hcptr.rs b/aarch32-cpu/src/register/armv8r/hcptr.rs index b4057aac..864adea7 100644 --- a/aarch32-cpu/src/register/armv8r/hcptr.rs +++ b/aarch32-cpu/src/register/armv8r/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/armv8r/hcr.rs b/aarch32-cpu/src/register/armv8r/hcr.rs index 9da41175..9424f92b 100644 --- a/aarch32-cpu/src/register/armv8r/hcr.rs +++ b/aarch32-cpu/src/register/armv8r/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/armv8r/hsr.rs b/aarch32-cpu/src/register/armv8r/hsr.rs index 40c2fe8f..b499db95 100644 --- a/aarch32-cpu/src/register/armv8r/hsr.rs +++ b/aarch32-cpu/src/register/armv8r/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 adc057fc..0069dd51 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/link.x b/aarch32-rt/link.x index bff28f54..09f77f0f 100644 --- a/aarch32-rt/link.x +++ b/aarch32-rt/link.x @@ -1,11 +1,41 @@ /* Basic AArch32 linker script. -You must supply a file called `memory.x` which defines the memory regions -'VECTORS', 'CODE', 'DATA', 'STACKS'. +You must supply a file called `memory.x` in your linker search path. It must +define Region Aliases 'VECTORS', 'CODE', 'DATA', 'STACKS'. -The stacks will be at the top of the STACKS region by default, use `_pack_stacks` -to overwrite default behaviour. +Here is an example `memory.x` file: + +------------- +MEMORY { + FLASH : ORIGIN = 0x08000000, LENGTH = 2M + SRAM : ORIGIN = 0x10000000, LENGTH = 512K +} + +REGION_ALIAS("VECTORS", FLASH); +REGION_ALIAS("CODE", FLASH); +REGION_ALIAS("DATA", SRAM); +REGION_ALIAS("STACKS", SRAM); +------------- + +The AArch32 platform uses seven separate stacks. The default sizes for each are +given at the bottom of this file. However, your `memory.x` can provide an +alternative size for any (or all) of them, provided that size is a multiple of +eight bytes. For example, your `memory.x` might include: + +------------- +PROVIDE(_und_stack_size = 3456); +PROVIDE(_svc_stack_size = 3456); +PROVIDE(_abt_stack_size = 3456); +PROVIDE(_hyp_stack_size = 3456); +PROVIDE(_irq_stack_size = 3456); +PROVIDE(_fiq_stack_size = 3456); +PROVIDE(_sys_stack_size = 3456); +------------- + +The stacks will be located at the top of the STACKS region by default. Use +`PROVIDE(_pack_stacks = 0)` to remove the padding and locate the stacks at the +bottom of that region instead. Based upon the linker script from https://github.com/rust-embedded/cortex-m */ @@ -18,26 +48,55 @@ EXTERN(_start); EXTERN(_default_handler); SECTIONS { + /* # Vector Table + * + * Our ARM interrupt vector table, consisting of branch instructions to + * each exception handler. + * + * May include FIQ handler code at the end. + */ .vector_table ORIGIN(VECTORS) : { - /* The vector table must come first */ *(.vector_table) } > VECTORS + /* # Text + * + * Our executable code. + */ .text : { - /* Now the rest of the code */ + __stext = .; + *(.text .text*) + + __etext = .; } > CODE + /* # Text + * + * Our constants. + */ .rodata : { + __srodata = .; + *(.rodata .rodata*) + + __erodata = .; } > CODE + /* # Data + * + * Our global variables that are not initialised to zero. + */ .data : ALIGN(4) { . = ALIGN(4); __sdata = .; + *(.data .data.*); + . = ALIGN(4); + /* NB: __edata defined lower down */ } > DATA AT>CODE + /* * Allow sections from user `memory.x` injected using `INSERT AFTER .data` to * use the .data loading mechanism by pushing __edata. Note: do not change @@ -49,12 +108,20 @@ SECTIONS { /* LMA of .data */ __sidata = LOADADDR(.data); + /* # Block Starting Symbol (BSS) + * + * Our global variables that *are* initialised to zero. + */ .bss (NOLOAD) : ALIGN(4) { . = ALIGN(4); __sbss = .; + *(.bss .bss* COMMON) + . = ALIGN(4); + /* NB: __ebss defined lower down */ } > DATA + /* * Allow sections from user `memory.x` injected using `INSERT AFTER .bss` to * use the .bss zeroing mechanism by pushing __ebss. Note: do not change @@ -62,15 +129,28 @@ SECTIONS { */ __ebss = .; + /* # Uninitialised Data + * + * Our global variables that have no defined initial value. + */ .uninit (NOLOAD) : ALIGN(4) { . = ALIGN(4); __suninit = .; + *(.uninit .uninit.*); + . = ALIGN(4); __euninit = .; } > DATA + + /* # Stack Padding + * + * A padding region to push the stacks to the top of the STACKS region. + * If `_pack_stacks == 0`, this is forced to be zero size, putting the + * stacks at the bottom of the STACK region. + */ .filler (NOLOAD) : { /* Move the .stacks section to the end of the STACKS memory region */ _next_region = ORIGIN(STACKS) + LENGTH(STACKS); @@ -80,61 +160,81 @@ SECTIONS { . = _start_stacks; } > STACKS + /* # Stacks + * + * Space for all seven stacks. + */ .stacks (NOLOAD) : ALIGN(8) { . = ALIGN(8); + + /* Lowest address of allocated stack */ _stacks_low_end = .; - _sys_stack_end = .; - . += _sys_stack_size; - . = ALIGN(8); - _sys_stack = .; - _fiq_stack_end = .; - . += _fiq_stack_size; - . = ALIGN(8); - _fiq_stack = .; - _irq_stack_end = .; - . += _irq_stack_size; - . = ALIGN(8); - _irq_stack = .; - _abt_stack_end = .; - . += _abt_stack_size; - . = ALIGN(8); - _abt_stack = .; - _svc_stack_end = .; - . += _svc_stack_size; - . = ALIGN(8); - _svc_stack = .; - _und_stack_end = .; - . += _und_stack_size; - . = ALIGN(8); - _und_stack = .; - _hyp_stack_end = .; - . += _hyp_stack_size; - . = ALIGN(8); - _hyp_stack = .; + + /* Stack for UND mode */ + _und_stack_low_end = .; + . += (_und_stack_size * _num_cores); + _und_stack_high_end = .; + + /* Stack for SVC mode */ + _svc_stack_low_end = .; + . += (_svc_stack_size * _num_cores); + _svc_stack_high_end = .; + + /* Stack for ABT mode */ + _abt_stack_low_end = .; + . += (_abt_stack_size * _num_cores); + _abt_stack_high_end = .; + + /* Stack for HYP mode */ + _hyp_stack_low_end = .; + . += (_hyp_stack_size * _num_cores); + _hyp_stack_high_end = .; + + /* Stack for IRQ mode */ + _irq_stack_low_end = .; + . += (_irq_stack_size * _num_cores); + _irq_stack_high_end = .; + + /* Stack for FIQ mode */ + _fiq_stack_low_end = .; + . += (_fiq_stack_size * _num_cores); + _fiq_stack_high_end = .; + + /* Stack for SYS mode */ + _sys_stack_low_end = .; + . += (_sys_stack_size * _num_cores); + _sys_stack_high_end = .; + + /* Highest address of allocated stack */ _stacks_high_end = .; } > STACKS /DISCARD/ : { + /* Discard any notes */ *(.note .note*) + /* Discard these unwinding/exception related symbols, they are not used */ *(.ARM.exidx* .gnu.linkonce.armexidx.*) + /* Discard these exception related symbols, they are not used */ *(.ARM.extab* .gnu.linkonce.armextab.*) } } -/* We provide default sizes for the stacks to be overwritten in memory.x */ -PROVIDE(_stack_top = _stacks_high_end); /* deprecated, use _xxx_stack labels as defined in .stacks section */ -PROVIDE(_hyp_stack_size = 0x400); -PROVIDE(_und_stack_size = 0x400); -PROVIDE(_svc_stack_size = 0x400); -PROVIDE(_abt_stack_size = 0x400); -PROVIDE(_irq_stack_size = 0x400); -PROVIDE(_fiq_stack_size = 0x400); -PROVIDE(_sys_stack_size = 0x2000); -PROVIDE(_pack_stacks = 0); /* set this to 1 to remove the filler section pushing the stacks to the end of STACKS. */ +/* We provide default sizes for the stacks for any not specified in memory.x (which was loaded first) */ +PROVIDE(_und_stack_size = 16K); +PROVIDE(_svc_stack_size = 16K); +PROVIDE(_abt_stack_size = 16K); +PROVIDE(_hyp_stack_size = 16K); +PROVIDE(_irq_stack_size = 64); +PROVIDE(_fiq_stack_size = 64); +PROVIDE(_sys_stack_size = 16K); +/* Default to one CPU core (i.e. one copy of each stack) */ +PROVIDE(_num_cores = 1); +/* Set this to 1 in memory.x to remove the filler section pushing the stacks to the end of STACKS. */ +PROVIDE(_pack_stacks = 0); /* Weak aliases for ASM default handlers */ PROVIDE(_start = _default_start); @@ -143,6 +243,7 @@ PROVIDE(_asm_svc_handler = _asm_default_svc_handler); PROVIDE(_asm_hvc_handler = _asm_default_hvc_handler); PROVIDE(_asm_prefetch_abort_handler = _asm_default_prefetch_abort_handler); PROVIDE(_asm_data_abort_handler = _asm_default_data_abort_handler); +/* TODO: Hyp handler goes here */ PROVIDE(_asm_irq_handler = _asm_default_irq_handler); PROVIDE(_asm_fiq_handler = _asm_default_fiq_handler); @@ -152,5 +253,26 @@ PROVIDE(_svc_handler = _default_handler); PROVIDE(_hvc_handler = _default_handler); PROVIDE(_prefetch_abort_handler = _default_handler); PROVIDE(_data_abort_handler = _default_handler); +/* TODO: Hyp handler goes here */ PROVIDE(_irq_handler = _default_handler); -/* There is no default C-language FIQ handler */ +/* NB: There is no default C-language FIQ handler */ + +/* Check the stack sizes are all a multiple of eight bytes */ +ASSERT(_und_stack_size % 8 == 0, " +ERROR(aarch32-rt): UND stack size (_und_stack_size) is not a multiple of 8 bytes"); +ASSERT(_svc_stack_size % 8 == 0, " +ERROR(aarch32-rt): SVC stack size (_svc_stack_size) is not a multiple of 8 bytes"); +ASSERT(_abt_stack_size % 8 == 0, " +ERROR(aarch32-rt): ABT stack size (_abt_stack_size) is not a multiple of 8 bytes"); +ASSERT(_hyp_stack_size % 8 == 0, " +ERROR(aarch32-rt): HYP stack size (_hyp_stack_size) is not a multiple of 8 bytes"); +ASSERT(_irq_stack_size % 8 == 0, " +ERROR(aarch32-rt): IRQ stack size (_irq_stack_size) is not a multiple of 8 bytes"); +ASSERT(_fiq_stack_size % 8 == 0, " +ERROR(aarch32-rt): FIQ stack size (_fiq_stack_size) is not a multiple of 8 bytes"); +ASSERT(_sys_stack_size % 8 == 0, " +ERROR(aarch32-rt): SYS stack size (_sys_stack_size) is not a multiple of 8 bytes"); +ASSERT(_num_cores != 0, " +ERROR(aarch32-rt): Number of cores cannot be zero"); + +/* End of file */ diff --git a/aarch32-rt/src/arch_v4/interrupt.rs b/aarch32-rt/src/arch_v4/interrupt.rs index a82296b3..db178f6f 100644 --- a/aarch32-rt/src/arch_v4/interrupt.rs +++ b/aarch32-rt/src/arch_v4/interrupt.rs @@ -20,7 +20,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 mov lr, sp // align SP down to eight byte boundary using LR and lr, lr, 7 // diff --git a/aarch32-rt/src/arch_v7/hvc.rs b/aarch32-rt/src/arch_v7/hvc.rs index 35474984..0840d698 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 dab9b3c6..3a07c659 100644 --- a/aarch32-rt/src/arch_v7/interrupt.rs +++ b/aarch32-rt/src/arch_v7/interrupt.rs @@ -16,7 +16,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 mov lr, sp // align SP down to eight byte boundary using LR and lr, lr, 7 // 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 0ca26461..8cd6940b 100644 --- a/aarch32-rt/src/lib.rs +++ b/aarch32-rt/src/lib.rs @@ -39,6 +39,8 @@ //! //! ## Constants //! +//! * `_num_cores` - the number of CPU core (and hence the number of copies of +//! each stack). Must be > 0. //! * `__sbss` - the start of zero-initialised data in RAM. Must be 4-byte //! aligned. //! * `__ebss` - the end of zero-initialised data in RAM. Must be 4-byte @@ -70,29 +72,29 @@ //! //! Stacks are located in `.stacks` section which is mapped to the `STACKS` //! memory region. Per default, the stacks are pushed to the end of the `STACKS` -//! by a filler section. +//! by a filler section. We allocate stacks for each core, based on the +//! `_num_cores` linker symbol. //! //! The stacks look like: //! //! ```text -//! +------------------+ <----_stack_top equals ORIGIN(STACKS) + LENGTH(STACKS) -//! | HYP Stack | } _hyp_stack_size bytes (Armv8-R only) +//! +------------------+ <---- ORIGIN(STACKS) + LENGTH(STACKS) +//! | SYS Stack | } _sys_stack_size * _num_cores bytes //! +------------------+ -//! | UND Stack | } _und_stack_size bytes +//! | FIQ Stack | } _fiq_stack_size * _num_cores bytes //! +------------------+ -//! | SVC Stack | } _svc_stack_size bytes +//! | IRQ Stack | } _irq_stack_size * _num_cores bytes //! +------------------+ -//! | ABT Stack | } _abt_stack_size bytes +//! | HYP Stack | } _hyp_stack_size * _num_cores bytes (only used on Armv8-R) //! +------------------+ -//! | IRQ Stack | } _irq_stack_size bytes +//! | ABT Stack | } _abt_stack_size * _num_cores bytes //! +------------------+ -//! | FIQ Stack | } _fiq_stack_size bytes +//! | SVC Stack | } _svc_stack_size * _num_cores bytes //! +------------------+ -//! | SYS Stack | } _sys_stack_size bytes +//! | UND Stack | } _und_stack_size * _num_cores bytes //! +------------------+ -//! | filler section | } calculated so that _stack_top equals end of STACKS -//! +------------------+ <----either ORIGIN(STACKS) or the end of previous -//! section located in STACKS or its alias. +//! | filler section | +//! +------------------+ <---- ORIGIN(STACKS) //! ``` //! //! Our linker script PROVIDEs a symbol `_pack_stacks`. By setting this symbol @@ -117,8 +119,9 @@ //! You can also create a 'kmain' function by using the `#[entry]` attribute on //! a normal Rust function. The function will be renamed in such a way that the //! start-up assembly code can find it, but normal Rust code cannot. Therefore -//! you can be assured that the function will only be called once (unless someone -//! resorts to `unsafe` Rust to import the `kmain` symbol as an `extern "C" fn`). +//! you can be assured that the function will only be called once (unless +//! someone resorts to `unsafe` Rust to import the `kmain` symbol as an `extern +//! "C" fn`). //! //! ```rust //! use aarch32_rt::entry; @@ -502,24 +505,19 @@ //! `_irq_handler` //! * `_asm_default_fiq_handler` - an FIQ handler that just spins //! * `_default_handler` - a C compatible function that spins forever. -//! * `_init_segments` - initialises `.bss` and `.data` +//! * `_init_segments` - initialises `.bss` and `.data` and zeroes the stacks //! * `_stack_setup_preallocated` - initialises UND, SVC, ABT, IRQ, FIQ and SYS //! stacks from the `.stacks` section defined in link.x, based on -//! _xxx_stack_size values -//! * `_xxx_stack` and `_xxx_stack_end` where the former is the top and the latter -//! the bottom of the stack for each mode (`und`, `svc`, `abt`, `irq`, `fiq`, `sys`) -//! * `_stack_top` - the address of the top of the STACKS region that contains -//! the reseved stacks, with eight-byte alignment. -//! Using this symbol is deprecated, stacks should be initialized by their -//! individual _xxx_stack symbols -//! -//! The assembly language trampolines are required because AArch32 -//! processors do 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 +//! _xxx_stack_size values, and the core number given in `r0` +//! * `_xxx_stack_high_end` and `_xxx_stack_low_end` where the former is the top +//! and the latter the bottom of the stack for each mode (`und`, `svc`, `abt`, +//! `irq`, `fiq`, `sys`) +//! +//! The assembly language trampolines are required because AArch32 processors do +//! 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. 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. //! @@ -533,18 +531,21 @@ #[cfg(target_arch = "arm")] use aarch32_cpu::register::{cpsr::ProcessorMode, Cpsr}; -#[cfg(arm_architecture = "v8-r")] +#[cfg(all(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; @@ -558,6 +559,8 @@ mod arch_v7; ))] mod arch_v4; +pub mod stacks; + /// Our default exception handler. /// /// We end up here if an exception fires and the weak 'PROVIDE' in the link.x @@ -795,38 +798,58 @@ core::arch::global_asm!( // Configure a stack for every mode. Leaves you in sys mode. // + // Pass the core number in r0 .section .text._stack_setup_preallocated .global _stack_setup_preallocated + .arm .type _stack_setup_preallocated, %function _stack_setup_preallocated: // Save LR from whatever mode we're currently in - mov r2, lr + mov r3, lr // (we might not be in the same mode when we return). // Set stack pointer and mask interrupts for UND mode (Mode 0x1B) msr cpsr_c, {und_mode} - ldr r13, =_und_stack + ldr r2, =_und_stack_high_end + ldr r1, =_und_stack_size + muls r1, r1, r0 + subs sp, r2, r1 // Set stack pointer (right after) and mask interrupts for SVC mode (Mode 0x13) msr cpsr_c, {svc_mode} - ldr r13, =_svc_stack + ldr r2, =_svc_stack_high_end + ldr r1, =_svc_stack_size + muls r1, r1, r0 + subs sp, r2, r1 // Set stack pointer (right after) and mask interrupts for ABT mode (Mode 0x17) msr cpsr_c, {abt_mode} - ldr r13, =_abt_stack + ldr r2, =_abt_stack_high_end + ldr r1, =_abt_stack_size + muls r1, r1, r0 + subs sp, r2, r1 // Set stack pointer (right after) and mask interrupts for IRQ mode (Mode 0x12) msr cpsr_c, {irq_mode} - ldr r13, =_irq_stack + ldr r2, =_irq_stack_high_end + ldr r1, =_irq_stack_size + muls r1, r1, r0 + subs sp, r2, r1 // Set stack pointer (right after) and mask interrupts for FIQ mode (Mode 0x11) msr cpsr_c, {fiq_mode} - ldr r13, =_fiq_stack + ldr r2, =_fiq_stack_high_end + ldr r1, =_fiq_stack_size + muls r1, r1, r0 + subs sp, r2, r1 // Set stack pointer (right after) and mask interrupts for System mode (Mode 0x1F) msr cpsr_c, {sys_mode} - ldr r13, =_sys_stack + ldr r2, =_sys_stack_high_end + ldr r1, =_sys_stack_size + muls r1, r1, r0 + subs sp, r2, r1 // Clear the Thumb Exception bit because all vector table is written in Arm assembly // even on Thumb targets. mrc p15, 0, r1, c1, c0, 0 bic r1, #{te_bit} mcr p15, 0, r1, c1, c0, 0 // return to caller - bx r2 + bx r3 .size _stack_setup_preallocated, . - _stack_setup_preallocated // Initialises stacks, .data and .bss @@ -835,7 +858,7 @@ core::arch::global_asm!( .global _init_segments .type _init_segments, %function _init_segments: - // Initialise .bss + // Zero .bss ldr r0, =__sbss ldr r1, =__ebss mov r2, 0 @@ -844,6 +867,16 @@ core::arch::global_asm!( beq 1f stm r0!, {{r2}} b 0b + 1: + // Zero the stacks + ldr r0, =_stacks_low_end + ldr r1, =_stacks_high_end + mov r2, 0 + 0: + cmp r1, r0 + beq 1f + stm r0!, {{r2}} + b 0b 1: // Initialise .data ldr r0, =__sdata @@ -923,11 +956,12 @@ core::arch::global_asm!( .global _default_start .type _default_start, %function _default_start: - // Set up stacks. - bl _stack_setup_preallocated // Init .data and .bss bl _init_segments - "#, + // Set up stacks. + mov r0, #0 + bl _stack_setup_preallocated + "#, fpu_enable!(), r#" // Zero all registers before calling kmain @@ -952,13 +986,13 @@ 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(arm_architecture = "v8-r")] +#[cfg(all(arm_architecture = "v8-r", not(feature = "el2-mode")))] core::arch::global_asm!( r#" // Work around https://github.com/rust-lang/rust/issues/127269 @@ -975,7 +1009,7 @@ core::arch::global_asm!( cmp r0, {cpsr_mode_hyp} bne 1f // Set stack pointer - ldr sp, =_hyp_stack + 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 @@ -993,14 +1027,15 @@ core::arch::global_asm!( isb eret 1: - // Set up stacks. - bl _stack_setup_preallocated // Set the VBAR (for EL1) to _vector_table. NB: This isn't required on // Armv7-R because that only supports 'low' (default) or 'high'. ldr r0, =_vector_table mcr p15, 0, r0, c12, c0, 0 // Init .data and .bss bl _init_segments + // Set up stacks. + mov r0, #0 + bl _stack_setup_preallocated "#, fpu_enable!(), r#" @@ -1046,3 +1081,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/aarch32-rt/src/stacks.rs b/aarch32-rt/src/stacks.rs new file mode 100644 index 00000000..6de2b2f5 --- /dev/null +++ b/aarch32-rt/src/stacks.rs @@ -0,0 +1,136 @@ +//! Code for examining linker allocated stacks + +/// Represents one of the AArch32 stacks +#[derive(Debug, Copy, Clone)] +pub enum Stack { + /// UND mode stack + Und, + /// SVC mode stack + Svc, + /// ABT mode stack, for data abort and prefetch abort + Abt, + /// HYP mode stack, for EL2 + Hyp, + /// IRQ mode stack, for interrupts + Irq, + /// FIQ mode stack, for fast interrupts + Fiq, + /// SYS mode stack, for the main thread + Sys, +} + +impl core::fmt::Display for Stack { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!( + f, + "{}", + match self { + Stack::Und => "UND", + Stack::Svc => "SVC", + Stack::Abt => "ABT", + Stack::Hyp => "HYP", + Stack::Irq => "IRQ", + Stack::Fiq => "FIQ", + Stack::Sys => "SYS", + } + ) + } +} + +impl Stack { + /// Create an iterator over all the stacks + pub fn iter() -> impl Iterator { + [ + Stack::Und, + Stack::Svc, + Stack::Abt, + Stack::Hyp, + Stack::Irq, + Stack::Fiq, + Stack::Sys, + ] + .iter() + .cloned() + } + + /// Get the highest address of this stack, for the given core + pub fn top(&self, core: usize) -> Option<*const u32> { + if core > Self::num_cores() { + return None; + } + let top = self.stack_top(); + let core = core as isize; + let per_core_size_words = self.per_core_size_words(); + Some(unsafe { top.offset(-per_core_size_words * core) }) + } + + /// Get the lowest address of this stack, for the given core + pub fn bottom(&self, core: usize) -> Option<*const u32> { + self.top(core) + .map(|p| unsafe { p.offset(-self.per_core_size_words()) }) + } + + /// Get the range of this stack, for the given core + pub fn range(&self, core: usize) -> Option> { + if let (Some(bottom), Some(top)) = (self.bottom(core), self.top(core)) { + Some(bottom..top) + } else { + None + } + } + + /// Get number of cores in this system + pub fn num_cores() -> usize { + unsafe extern "C" { + safe static _num_cores: u8; + } + core::ptr::addr_of!(_num_cores) as usize + } + + /// Get the total size of this stack across all cores + pub fn per_core_size_words(&self) -> isize { + use core::ptr::addr_of; + unsafe extern "C" { + static _und_stack_size: u8; + static _svc_stack_size: u8; + static _abt_stack_size: u8; + static _hyp_stack_size: u8; + static _irq_stack_size: u8; + static _fiq_stack_size: u8; + static _sys_stack_size: u8; + } + let size_bytes = match self { + Stack::Und => addr_of!(_und_stack_size) as isize, + Stack::Svc => addr_of!(_svc_stack_size) as isize, + Stack::Abt => addr_of!(_abt_stack_size) as isize, + Stack::Hyp => addr_of!(_hyp_stack_size) as isize, + Stack::Irq => addr_of!(_irq_stack_size) as isize, + Stack::Fiq => addr_of!(_fiq_stack_size) as isize, + Stack::Sys => addr_of!(_sys_stack_size) as isize, + }; + size_bytes / 4 + } + + /// Get the top address for this stack + fn stack_top(&self) -> *const u32 { + use core::ptr::addr_of; + unsafe extern "C" { + static _und_stack_high_end: u32; + static _svc_stack_high_end: u32; + static _abt_stack_high_end: u32; + static _hyp_stack_high_end: u32; + static _irq_stack_high_end: u32; + static _fiq_stack_high_end: u32; + static _sys_stack_high_end: u32; + } + match self { + Stack::Und => addr_of!(_und_stack_high_end), + Stack::Svc => addr_of!(_svc_stack_high_end), + Stack::Abt => addr_of!(_abt_stack_high_end), + Stack::Hyp => addr_of!(_hyp_stack_high_end), + Stack::Irq => addr_of!(_irq_stack_high_end), + Stack::Fiq => addr_of!(_fiq_stack_high_end), + Stack::Sys => addr_of!(_sys_stack_high_end), + } + } +} 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..7e4d9e6c --- /dev/null +++ b/examples/mps3-an536-el2/rust-toolchain.toml @@ -0,0 +1,6 @@ +[toolchain] +channel = "nightly-2026-01-26" +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..9bf95d4c --- /dev/null +++ b/examples/mps3-an536-el2/src/bin/abt-exception-a32.rs @@ -0,0 +1,120 @@ +//! 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"); + unsafe { + // 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); +} + +// These functions are written in assembly +unsafe extern "C" { + fn unaligned_from_a32(); +} + +core::arch::global_asm!( + r#" + // fn unaligned_from_a32(); + .arm + .global unaligned_from_a32 + .type unaligned_from_a32, %function + unaligned_from_a32: + ldr r0, =COUNTER + add r0, r0, 1 + ldr r0, [r0] + bx lr + .size unaligned_from_a32, . - unaligned_from_a32 +"# +); + +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 unsafe 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..9ac310f1 --- /dev/null +++ b/examples/mps3-an536-el2/src/bin/abt-exception-t32.rs @@ -0,0 +1,120 @@ +//! 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"); + unsafe { + // 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); +} + +// These functions are written in assembly +unsafe extern "C" { + fn unaligned_from_t32(); +} + +core::arch::global_asm!( + r#" + // fn unaligned_from_t32(); + .thumb + .global unaligned_from_t32 + .type unaligned_from_t32, %function + unaligned_from_t32: + ldr r0, =COUNTER + add r0, r0, 1 + ldr r0, [r0] + bx lr + .size unaligned_from_t32, . - unaligned_from_t32 +"# +); + +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 unsafe extern "C" fn() as usize + 5; + + 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..f660baca --- /dev/null +++ b/examples/mps3-an536-el2/src/bin/prefetch-exception-a32.rs @@ -0,0 +1,93 @@ +//! 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 + unsafe { + // trigger an prefetch abort exception, from A32 (Arm) mode + bkpt_from_a32(); + } + + println!("Recovered from fault OK!"); + + mps3_an536_el2::exit(0); +} + +// These functions are written in assembly +unsafe extern "C" { + fn bkpt_from_a32(); +} + +core::arch::global_asm!( + r#" + // fn bkpt_from_a32(); + .arm + .global bkpt_from_a32 + .type bkpt_from_a32, %function + bkpt_from_a32: + bkpt #0 + bx lr + .size bkpt_from_a32, . - bkpt_from_a32 +"# +); + +// 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 unsafe extern "C" fn() as usize { + println!("caught bkpt_from_a32"); + } else { + println!( + "Bad fault address {:08x} is not {:08x}", + addr, bkpt_from_a32 as unsafe 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..83270378 --- /dev/null +++ b/examples/mps3-an536-el2/src/bin/prefetch-exception-t32.rs @@ -0,0 +1,95 @@ +//! 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 + unsafe { + // trigger an prefetch abort exception, from T32 (Thumb) mode + bkpt_from_t32(); + } + + println!("Recovered from fault OK!"); + + mps3_an536_el2::exit(0); +} + +// These functions are written in assembly +unsafe extern "C" { + fn bkpt_from_t32(); +} + +core::arch::global_asm!( + r#" + // fn bkpt_from_t32(); + .thumb + .global bkpt_from_t32 + .type bkpt_from_t32, %function + bkpt_from_t32: + bkpt #0 + bx lr + .size bkpt_from_t32, . - bkpt_from_t32 +"# +); + +#[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 unsafe 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 unsafe 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..bc1f34ed --- /dev/null +++ b/examples/mps3-an536-el2/src/bin/undef-exception-a32.rs @@ -0,0 +1,94 @@ +//! 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"); + + unsafe { + // trigger an Undefined exception, from A32 (Arm) mode + udf_from_a32(); + } + + println!("Recovered from fault OK!"); + + mps3_an536_el2::exit(0); +} + +// These functions are written in assembly +unsafe extern "C" { + fn udf_from_a32(); +} + +core::arch::global_asm!( + r#" + // fn udf_from_a32(); + .arm + .global udf_from_a32 + .type udf_from_a32, %function + udf_from_a32: + udf #0 + bx lr + .size udf_from_a32, . - udf_from_a32 +"# +); + +#[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 unsafe extern "C" fn() as usize { + println!("caught udf_from_a32"); + } else { + println!( + "Bad fault address {:08x} is not {:08x}", + addr, udf_from_a32 as unsafe 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..01b57ea2 --- /dev/null +++ b/examples/mps3-an536-el2/src/bin/undef-exception-t32.rs @@ -0,0 +1,97 @@ +//! 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"); + + unsafe { + // trigger an Undefined exception, from T32 (Thumb) mode + udf_from_t32(); + } + + println!("Recovered from fault OK!"); + + mps3_an536_el2::exit(0); +} + +// These functions are written in assembly +unsafe extern "C" { + fn udf_from_t32(); +} + +core::arch::global_asm!( + r#" + // fn udf_from_t32(); + .thumb + .global udf_from_t32 + .type udf_from_t32, %function + udf_from_t32: + udf #0 + bx lr + .size udf_from_t32, . - udf_from_t32 +"# +); + +#[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 unsafe 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 unsafe 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..0b1669f9 --- /dev/null +++ b/examples/mps3-an536-el2/src/lib.rs @@ -0,0 +1,259 @@ +//! 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 + .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::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 physical timer instance + // as guaranteed by the atomic flag check above, ensuring exclusive access. + hyp_timer: unsafe { aarch32_cpu::generic_timer::El2HypPhysicalTimer::new() }, + }) + } else { + None + } + } +} + +/// 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/.cargo/config.toml b/examples/mps3-an536-smp/.cargo/config.toml new file mode 100644 index 00000000..b3ab14b1 --- /dev/null +++ b/examples/mps3-an536-smp/.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 -smp 2 -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 -smp 2 -kernel" + +[build] +target = "armv8r-none-eabihf" diff --git a/examples/mps3-an536-smp/Cargo.toml b/examples/mps3-an536-smp/Cargo.toml new file mode 100644 index 00000000..6bf5e110 --- /dev/null +++ b/examples/mps3-an536-smp/Cargo.toml @@ -0,0 +1,31 @@ +[package] +authors = [ + "Jonathan Pallant ", + "The Embedded Devices Working Group Arm Team " +] +default-run = "smp-test" +description = "Examples for SMP MPS3-AN536 device (2x Arm Cortex-R52)" +edition = "2024" +homepage = "https://github.com/rust-embedded/aarch32" +license = "MIT OR Apache-2.0" +name = "mps3-an536-smp" +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" } +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-smp/README.md b/examples/mps3-an536-smp/README.md new file mode 100644 index 00000000..d43a504e --- /dev/null +++ b/examples/mps3-an536-smp/README.md @@ -0,0 +1,106 @@ +# 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 + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.09s + Running `qemu-system-arm -machine mps3-an536 -cpu cortex-r52 -semihosting -nographic -audio none -smp 2 -kernel target/armv8r-none-eabihf/debug/smp-test` +I am core 0 - Mpidr(80000000) +I am core 1 - Mpidr(80000001) +CAS test passed +CS Mutex test passed +Stack usage report: +SYS0 Stack = 2680 used of 16384 bytes (016%) @ 0x1006bf80..0x1006ff80 +FIQ0 Stack = 0 used of 64 bytes (000%) @ 0x1006ff80..0x1006ffc0 +IRQ0 Stack = 0 used of 64 bytes (000%) @ 0x1006ffc0..0x10070000 +ABT0 Stack = 0 used of 16384 bytes (000%) @ 0x10070000..0x10074000 +SVC0 Stack = 0 used of 16384 bytes (000%) @ 0x10074000..0x10078000 +UND0 Stack = 0 used of 16384 bytes (000%) @ 0x10078000..0x1007c000 +HYP0 Stack = 0 used of 16384 bytes (000%) @ 0x1007c000..0x10080000 +SYS1 Stack = 680 used of 16384 bytes (004%) @ 0x10000018..0x10004018 +FIQ1 Stack = 0 used of 64 bytes (000%) @ 0x10004018..0x10004058 +IRQ1 Stack = 0 used of 64 bytes (000%) @ 0x10004058..0x10004098 +ABT1 Stack = 0 used of 16384 bytes (000%) @ 0x10004098..0x10008098 +SVC1 Stack = 0 used of 16384 bytes (000%) @ 0x10008098..0x1000c098 +UND1 Stack = 0 used of 16384 bytes (000%) @ 0x1000c098..0x10010098 +HYP1 Stack = 0 used of 16384 bytes (000%) @ 0x10010098..0x10014098 +$ cargo run --target thumbv8r-none-eabihf -Zbuild-std=core + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.08s + Running `qemu-system-arm -machine mps3-an536 -cpu cortex-r52 -semihosting -nographic -audio none -smp 2 -kernel target/thumbv8r-none-eabihf/debug/smp-test` +I am core 0 - Mpidr(80000000) +I am core 1 - Mpidr(80000001) +CAS test passed +CS Mutex test passed +Stack usage report: +SYS0 Stack = 4840 used of 16384 bytes (029%) @ 0x1006bf80..0x1006ff80 +FIQ0 Stack = 0 used of 64 bytes (000%) @ 0x1006ff80..0x1006ffc0 +IRQ0 Stack = 0 used of 64 bytes (000%) @ 0x1006ffc0..0x10070000 +ABT0 Stack = 0 used of 16384 bytes (000%) @ 0x10070000..0x10074000 +SVC0 Stack = 0 used of 16384 bytes (000%) @ 0x10074000..0x10078000 +UND0 Stack = 0 used of 16384 bytes (000%) @ 0x10078000..0x1007c000 +HYP0 Stack = 0 used of 16384 bytes (000%) @ 0x1007c000..0x10080000 +SYS1 Stack = 1568 used of 16384 bytes (009%) @ 0x10000018..0x10004018 +FIQ1 Stack = 0 used of 64 bytes (000%) @ 0x10004018..0x10004058 +IRQ1 Stack = 0 used of 64 bytes (000%) @ 0x10004058..0x10004098 +ABT1 Stack = 0 used of 16384 bytes (000%) @ 0x10004098..0x10008098 +SVC1 Stack = 0 used of 16384 bytes (000%) @ 0x10008098..0x1000c098 +UND1 Stack = 0 used of 16384 bytes (000%) @ 0x1000c098..0x10010098 +HYP1 Stack = 0 used of 16384 bytes (000%) @ 0x10010098..0x10014098 +``` + +## 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-smp/build.rs b/examples/mps3-an536-smp/build.rs new file mode 100644 index 00000000..b0b9f098 --- /dev/null +++ b/examples/mps3-an536-smp/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-smp/commands.gdb b/examples/mps3-an536-smp/commands.gdb new file mode 100644 index 00000000..0634363b --- /dev/null +++ b/examples/mps3-an536-smp/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-smp/memory.x b/examples/mps3-an536-smp/memory.x new file mode 100644 index 00000000..c4aa51bb --- /dev/null +++ b/examples/mps3-an536-smp/memory.x @@ -0,0 +1,19 @@ +/* +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); + +PROVIDE(_num_cores = 2); +PROVIDE(kmain2 = default_kmain2); diff --git a/examples/mps3-an536-smp/reference/gic-armv8r-none-eabihf.out b/examples/mps3-an536-smp/reference/gic-armv8r-none-eabihf.out new file mode 100644 index 00000000..6a49ec52 --- /dev/null +++ b/examples/mps3-an536-smp/reference/gic-armv8r-none-eabihf.out @@ -0,0 +1,16 @@ +I am core 0 - Mpidr(80000000) +Found PERIPHBASE 0xf0000000 +Creating GIC driver @ 0xf0000000 / 0xf0100000 +Calling git.setup(0) +Configure SGI on both cores... +I am core 1 - Mpidr(80000001) +Calling git.init_cpu(1) +Send SGI to other core +> IRQ on Mpidr(80000001) +- handle_interrupt_with_id(SGI 3) +- send SGI back to first core +> IRQ on Mpidr(80000000) +- handle_interrupt_with_id(SGI 3) +< IRQ on Mpidr(80000001) +< IRQ on Mpidr(80000000) +Got pong diff --git a/examples/mps3-an536-smp/reference/gic-thumbv8r-none-eabihf.out b/examples/mps3-an536-smp/reference/gic-thumbv8r-none-eabihf.out new file mode 100644 index 00000000..6a49ec52 --- /dev/null +++ b/examples/mps3-an536-smp/reference/gic-thumbv8r-none-eabihf.out @@ -0,0 +1,16 @@ +I am core 0 - Mpidr(80000000) +Found PERIPHBASE 0xf0000000 +Creating GIC driver @ 0xf0000000 / 0xf0100000 +Calling git.setup(0) +Configure SGI on both cores... +I am core 1 - Mpidr(80000001) +Calling git.init_cpu(1) +Send SGI to other core +> IRQ on Mpidr(80000001) +- handle_interrupt_with_id(SGI 3) +- send SGI back to first core +> IRQ on Mpidr(80000000) +- handle_interrupt_with_id(SGI 3) +< IRQ on Mpidr(80000001) +< IRQ on Mpidr(80000000) +Got pong diff --git a/examples/mps3-an536-smp/reference/smp-test-armv8r-none-eabihf.out b/examples/mps3-an536-smp/reference/smp-test-armv8r-none-eabihf.out new file mode 100644 index 00000000..b157d323 --- /dev/null +++ b/examples/mps3-an536-smp/reference/smp-test-armv8r-none-eabihf.out @@ -0,0 +1,4 @@ +I am core 0 - Mpidr(80000000) +I am core 1 - Mpidr(80000001) +CAS test passed +CS Mutex test passed diff --git a/examples/mps3-an536-smp/reference/smp-test-thumbv8r-none-eabihf.out b/examples/mps3-an536-smp/reference/smp-test-thumbv8r-none-eabihf.out new file mode 100644 index 00000000..b157d323 --- /dev/null +++ b/examples/mps3-an536-smp/reference/smp-test-thumbv8r-none-eabihf.out @@ -0,0 +1,4 @@ +I am core 0 - Mpidr(80000000) +I am core 1 - Mpidr(80000001) +CAS test passed +CS Mutex test passed diff --git a/examples/mps3-an536-smp/rust-toolchain.toml b/examples/mps3-an536-smp/rust-toolchain.toml new file mode 100644 index 00000000..7e4d9e6c --- /dev/null +++ b/examples/mps3-an536-smp/rust-toolchain.toml @@ -0,0 +1,6 @@ +[toolchain] +channel = "nightly-2026-01-26" +targets = [ + "armv8r-none-eabihf", +] +components = ["rust-src", "clippy", "rustfmt"] diff --git a/examples/mps3-an536-smp/src/bin/gic.rs b/examples/mps3-an536-smp/src/bin/gic.rs new file mode 100644 index 00000000..af2ad191 --- /dev/null +++ b/examples/mps3-an536-smp/src/bin/gic.rs @@ -0,0 +1,168 @@ +//! # Cross-core GIC example for Arm Cortex-R52 on an MPS2-AN536 + +#![no_std] +#![no_main] + +use core::cell::RefCell; +use core::sync::atomic::{AtomicBool, Ordering}; + +use arm_gic::{ + IntId, + gicv3::{GicCpuInterface, Group, InterruptGroup, SgiTarget, SgiTargetGroup}, +}; +use critical_section::Mutex; +use semihosting::println; + +use aarch32_rt::entry; + +static CORE1_BOOTED: AtomicBool = AtomicBool::new(false); + +static PING_PONG_COMPLETE: AtomicBool = AtomicBool::new(false); + +/// How long core 0 waits for core 1 +const CORE0_WILL_WAIT: usize = 100_000_000; + +/// Shared interrupt controller driver +static GLOBAL_GIC: Mutex>>> = + Mutex::new(RefCell::new(None)); + +const SGI_INTID: IntId = IntId::sgi(3); + +/// The entry-point to the Rust application. +/// +/// 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() + ); + + let mut gic = unsafe { mps3_an536_smp::make_gic() }; + + // Configure two Software Generated Interrupts for Core 0 + println!("Configure SGI on both cores..."); + gic.set_interrupt_priority(SGI_INTID, Some(0), 0x31) + .unwrap(); + gic.set_group(SGI_INTID, Some(0), Group::Group1NS).unwrap(); + gic.enable_interrupt(SGI_INTID, Some(0), true).unwrap(); + + gic.set_interrupt_priority(SGI_INTID, Some(1), 0x31) + .unwrap(); + gic.set_group(SGI_INTID, Some(1), Group::Group1NS).unwrap(); + gic.enable_interrupt(SGI_INTID, Some(1), true).unwrap(); + + unsafe { + aarch32_cpu::interrupt::enable(); + } + + critical_section::with(|cs| { + let mut global_gic = GLOBAL_GIC.borrow_ref_mut(cs); + global_gic.replace(gic); + }); + + mps3_an536_smp::start_core1(); + + // wait some time for core 1 to start + for counter in 0..=CORE0_WILL_WAIT { + if CORE1_BOOTED.load(Ordering::SeqCst) { + break; + } + if counter == CORE0_WILL_WAIT { + println!("CPU 1 is missing?!"); + + mps3_an536_smp::exit(0); + } + } + + // Send it + println!("Send SGI to other core"); + GicCpuInterface::send_sgi( + SGI_INTID, + SgiTarget::List { + affinity3: 0, + affinity2: 0, + affinity1: 0, + target_list: 0b10, + }, + SgiTargetGroup::CurrentGroup1, + ) + .unwrap(); + + // let the other core finish + for _ in 0..CORE0_WILL_WAIT { + aarch32_cpu::asm::wfi(); + if PING_PONG_COMPLETE.load(Ordering::Relaxed) { + println!("Got pong"); + break; + } + } + + mps3_an536_smp::exit(0); +} + +/// The entry-point to the Rust application. +/// +/// 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() + ); + + unsafe { + aarch32_cpu::interrupt::enable(); + } + + critical_section::with(|cs| { + let mut global_gic = GLOBAL_GIC.borrow_ref_mut(cs); + let global_gic = global_gic.as_mut().unwrap(); + semihosting::println!("Calling git.init_cpu(1)"); + global_gic.init_cpu(1); + }); + GicCpuInterface::enable_group1(true); + GicCpuInterface::set_priority_mask(0xFF); + + CORE1_BOOTED.store(true, Ordering::SeqCst); + + loop { + aarch32_cpu::asm::wfi(); + } +} + +/// Called when either Arm CPU gets an IRQ +/// +/// Talks to the GICv3 to find out which interrupts are pending, +/// handles the interrupt, and then tells the GICv3 it has been handled. +#[aarch32_rt::irq] +fn irq_handler() { + let id = aarch32_cpu::register::Mpidr::read(); + println!("> IRQ on {:08x?}", id); + while let Some(next_int_id) = + GicCpuInterface::get_and_acknowledge_interrupt(InterruptGroup::Group1) + { + // handle the interrupt + println!("- handle_interrupt_with_id({:?})", next_int_id); + + if id.0 == 0x8000_0001 { + println!("- send SGI back to first core"); + GicCpuInterface::send_sgi( + SGI_INTID, + SgiTarget::List { + affinity3: 0, + affinity2: 0, + affinity1: 0, + target_list: 0b01, + }, + SgiTargetGroup::CurrentGroup1, + ) + .unwrap(); + } else { + PING_PONG_COMPLETE.store(true, Ordering::Relaxed); + } + + GicCpuInterface::end_interrupt(next_int_id, InterruptGroup::Group1); + } + println!("< IRQ on {:08x?}", id); +} diff --git a/examples/mps3-an536-smp/src/bin/smp-test.rs b/examples/mps3-an536-smp/src/bin/smp-test.rs new file mode 100644 index 00000000..9fb75435 --- /dev/null +++ b/examples/mps3-an536-smp/src/bin/smp-test.rs @@ -0,0 +1,119 @@ +//! Multi-core hello-world for Arm Cortex-R +//! +//! Runs code on two cores, checking that atomic fetch_add works. + +#![no_std] +#![no_main] + +use core::cell::RefCell; +use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; + +use aarch32_rt::entry; +use semihosting::println; + +static CORE1_BOOTED: AtomicBool = AtomicBool::new(false); + +static SHARED_VARIABLE: AtomicU32 = AtomicU32::new(0); + +static SHARED_VARIABLE_2: critical_section::Mutex> = + critical_section::Mutex::new(RefCell::new(0)); + +/// How long core 0 waits for core 1 +const CORE0_WILL_WAIT: usize = 1_000_000; + +/// How many CAS loops to run? +const CAS_LOOPS: u32 = 1000; + +/// How many CS Mutex loops to run? +const CS_MUTEX_LOOPS: u32 = 1000; + +/// The entry-point to the Rust application. +/// +/// 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() + ); + + mps3_an536_smp::start_core1(); + + // wait some time for core 1 to start + for counter in 0..=CORE0_WILL_WAIT { + if CORE1_BOOTED.load(Ordering::SeqCst) { + break; + } + if counter == CORE0_WILL_WAIT { + println!("CPU 1 is missing?!"); + + mps3_an536_smp::exit(0); + } + } + + for _ in 0..CAS_LOOPS { + SHARED_VARIABLE.fetch_add(1, Ordering::Relaxed); + } + + for _ in 0..CS_MUTEX_LOOPS { + critical_section::with(|cs| { + let mut value_ref = SHARED_VARIABLE_2.borrow_ref_mut(cs); + *value_ref += 1; + }) + } + + // let the other core finish + for _ in 0..CORE0_WILL_WAIT { + aarch32_cpu::asm::nop(); + } + + let mut code = 0; + let total_a = SHARED_VARIABLE.load(Ordering::Relaxed); + if total_a == CAS_LOOPS * 2 { + println!("CAS test passed"); + } else { + println!("CAS test failed, got {} not 2000", total_a); + code = 1; + } + + let total_b = critical_section::with(|cs| { + let value_ref = SHARED_VARIABLE_2.borrow_ref(cs); + *value_ref + }); + + if total_b == CS_MUTEX_LOOPS * 2 { + println!("CS Mutex test passed"); + } else { + println!("CS Mutex test failed, got {} not 2000", total_b); + code = 1; + } + + mps3_an536_smp::exit(code); +} + +/// The entry-point to the Rust application. +/// +/// 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() + ); + CORE1_BOOTED.store(true, Ordering::SeqCst); + + for _ in 0..CAS_LOOPS { + SHARED_VARIABLE.fetch_add(1, Ordering::Relaxed); + } + + for _ in 0..CS_MUTEX_LOOPS { + critical_section::with(|cs| { + let mut value_ref = SHARED_VARIABLE_2.borrow_ref_mut(cs); + *value_ref += 1; + }) + } + + loop { + aarch32_cpu::asm::wfi(); + } +} diff --git a/examples/mps3-an536-smp/src/lib.rs b/examples/mps3-an536-smp/src/lib.rs new file mode 100644 index 00000000..a187a6ba --- /dev/null +++ b/examples/mps3-an536-smp/src/lib.rs @@ -0,0 +1,306 @@ +//! 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 aarch32_cpu::register::{Cpsr, Hactlr, cpsr::ProcessorMode}; + +use core::sync::atomic::{AtomicBool, Ordering}; + +/// The PPI for the virutal 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); + +#[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); + +/// 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) -> ! { + stack_dump(); + semihosting::process::exit(code) +} + +/// Print stack using to semihosting output for each stack +/// +/// Produces output like: +/// +/// ```text +/// Stack usage report: +/// UND1 Stack = 0 used of 16384 bytes (000%) @ 0x10057f00..0x1005bf00 +/// UND0 Stack = 0 used of 16384 bytes (000%) @ 0x1005bf00..0x1005ff00 +/// SVC1 Stack = 0 used of 16384 bytes (000%) @ 0x1005ff00..0x10063f00 +/// SVC0 Stack = 0 used of 16384 bytes (000%) @ 0x10063f00..0x10067f00 +/// ABT1 Stack = 0 used of 16384 bytes (000%) @ 0x10067f00..0x1006bf00 +/// ABT0 Stack = 0 used of 16384 bytes (000%) @ 0x1006bf00..0x1006ff00 +/// HYP1 Stack = 0 used of 16384 bytes (000%) @ 0x1006ff00..0x10073f00 +/// HYP0 Stack = 0 used of 16384 bytes (000%) @ 0x10073f00..0x10077f00 +/// IRQ1 Stack = 0 used of 64 bytes (000%) @ 0x10077f00..0x10077f40 +/// IRQ0 Stack = 0 used of 64 bytes (000%) @ 0x10077f40..0x10077f80 +/// FIQ1 Stack = 0 used of 64 bytes (000%) @ 0x10077f80..0x10077fc0 +/// FIQ0 Stack = 0 used of 64 bytes (000%) @ 0x10077fc0..0x10078000 +/// SYS1 Stack = 808 used of 16384 bytes (004%) @ 0x10078000..0x1007c000 +/// SYS0 Stack = 1432 used of 16384 bytes (008%) @ 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 + ); + } + } + } +} + +/// Create the ARM GIC driver +/// +/// # Safety +/// +/// Only call this function once, from Core 0. +pub 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, 2, false) }; + semihosting::println!("Calling git.setup(0)"); + gic.setup(0); + arm_gic::gicv3::GicCpuInterface::set_priority_mask(0xFF); + gic +} + +/// Release core1 from spin loop +pub fn start_core1() { + let fpga_led = 0xE020_2000 as *mut u32; + unsafe { + // Activate second core by writing to FPGA LEDs. + // We needed a shared register that wasn't in RAM, and this will do. + fpga_led.write_volatile(1); + } +} + +// 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. +#[cfg(arm_architecture = "v8-r")] +core::arch::global_asm!( + r#" + .section .text.startup + .align 4 + .arm + + .global _start + .global core1_released + .type _start, %function + _start: + // Read MPIDR into R0 + mrc p15, 0, r0, c0, c0, 5 + ands r0, r0, 0xFF + bne core1 + core0: + ldr pc, =_default_start + core1: + // LED GPIO register base address + ldr r0, =0xE0202000 + mov r1, #0 + core1_spin: + wfe + // spin until an LED0 is on. We use the LED because unlike RAM this register resets to a known value. + ldr r2, [r0] + cmp r1, r2 + beq core1_spin + core1_released: + // now an LED is on, we assume _core1_stack_pointer contains our stack pointer + // First we must exit EL2... + // Set the HVBAR (for EL2) to _vector_table + ldr r0, =_vector_table + mcr p15, 4, r0, c12, c0, 0 + // Configure HACTLR to let us enter EL1 + mrc p15, 4, r0, c1, c0, 1 + mov r1, {hactlr_bits} + orr r0, r0, r1 + mcr p15, 4, r0, c1, c0, 1 + // Program the SPSR - enter system mode (0x1F) in Arm mode with IRQ, FIQ masked + mov r0, {sys_mode} + msr spsr_hyp, r0 + adr r0, 1f + msr elr_hyp, r0 + dsb + isb + eret + 1: + // Allow VFP coprocessor access + mrc p15, 0, r0, c1, c0, 2 + orr r0, r0, #0xF00000 + mcr p15, 0, r0, c1, c0, 2 + // Enable VFP + mov r0, #0x40000000 + vmsr fpexc, r0 + // Set the VBAR (for EL1) to _vector_table. NB: This isn't required on + // Armv7-R because that only supports 'low' (default) or 'high'. + ldr r0, =_vector_table + mcr p15, 0, r0, c12, c0, 0 + // set up our stacks - also switches to SYS mode + movs r0, #1 + bl _stack_setup_preallocated + // Zero all registers before calling kmain2 + 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 + // call our kmain2 for core 1 + bl kmain2 + .size _start, . - _start + "#, + 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)] +pub extern "C" fn default_kmain2() { + loop { + aarch32_cpu::asm::wfe(); + } +} diff --git a/examples/mps3-an536/.cargo/config.toml b/examples/mps3-an536/.cargo/config.toml index ec698529..6960eb34 100644 --- a/examples/mps3-an536/.cargo/config.toml +++ b/examples/mps3-an536/.cargo/config.toml @@ -1,2 +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/reference/smp_test-armv8r-none-eabihf.out b/examples/mps3-an536/reference/smp_test-armv8r-none-eabihf.out deleted file mode 100644 index 7fe30b87..00000000 --- a/examples/mps3-an536/reference/smp_test-armv8r-none-eabihf.out +++ /dev/null @@ -1 +0,0 @@ -CPU 1 is missing?! diff --git a/examples/mps3-an536/reference/smp_test-armv8r-none-eabihf_smp2.out b/examples/mps3-an536/reference/smp_test-armv8r-none-eabihf_smp2.out deleted file mode 100644 index 08c8c794..00000000 --- a/examples/mps3-an536/reference/smp_test-armv8r-none-eabihf_smp2.out +++ /dev/null @@ -1,2 +0,0 @@ -CAS test passed -CS Mutex test passed diff --git a/examples/mps3-an536/reference/smp_test-thumbv8r-none-eabihf.out b/examples/mps3-an536/reference/smp_test-thumbv8r-none-eabihf.out deleted file mode 100644 index 7fe30b87..00000000 --- a/examples/mps3-an536/reference/smp_test-thumbv8r-none-eabihf.out +++ /dev/null @@ -1 +0,0 @@ -CPU 1 is missing?! diff --git a/examples/mps3-an536/reference/smp_test-thumbv8r-none-eabihf_smp2.out b/examples/mps3-an536/reference/smp_test-thumbv8r-none-eabihf_smp2.out deleted file mode 100644 index 7fe30b87..00000000 --- a/examples/mps3-an536/reference/smp_test-thumbv8r-none-eabihf_smp2.out +++ /dev/null @@ -1 +0,0 @@ -CPU 1 is missing?! diff --git a/examples/mps3-an536/src/bin/el2_hello.rs b/examples/mps3-an536/src/bin/el2_hello.rs deleted file mode 100644 index c1a52365..00000000 --- a/examples/mps3-an536/src/bin/el2_hello.rs +++ /dev/null @@ -1,98 +0,0 @@ -//! Semihosting hello-world for Arm Cortex-R52 running in EL2 (Hyp Mode) - -#![no_std] -#![no_main] - -use aarch32_cpu::register::Hactlr; -use aarch32_rt::entry; -use mps3_an536 as _; -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); - - 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::want_panic(); - panic!("I am an example panic"); -} - -// Provide a custom `_start` function that sets us up in EL2 mode, with a -// stack. -// -// Unlike the default routine, it does not initialise any other stacks, or -// switch to EL1 mode. -core::arch::global_asm!( - r#" - // Work around https://github.com/rust-lang/rust/issues/127269 - .fpu vfp3-d16 - - .section .text.start - - .global _start - .type _start, %function - _start: - // Set stack pointer - ldr sp, =_hyp_stack - // Set the HVBAR (for EL2) to _vector_table - ldr r1, =_vector_table - mcr p15, 4, r1, c12, c0, 0 - // Configure HACTLR to let us enter EL1 - mrc p15, 4, r1, c1, c0, 1 - mov r2, {hactlr_bits} - orr r1, r1, r2 - mcr p15, 4, r1, c1, c0, 1 - // Init .data and .bss - bl _init_segments - // Allow VFP coprocessor access - mrc p15, 0, r0, c1, c0, 2 - orr r0, r0, #0xF00000 - mcr p15, 0, r0, c1, c0, 2 - // Enable VFP - mov r0, #0x40000000 - vmsr fpexc, r0 - // 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 _start, . - _start - "#, - 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() - }, -); 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/bin/smp_test.rs b/examples/mps3-an536/src/bin/smp_test.rs deleted file mode 100644 index 01e1b809..00000000 --- a/examples/mps3-an536/src/bin/smp_test.rs +++ /dev/null @@ -1,354 +0,0 @@ -//! Multi-core hello-world for Arm Cortex-R -//! -//! Runs code on two cores, checking that atomic fetch_add works. -//! -//! Abuses the FPGA LED register as a place to record whether Core 0 has -//! started. -//! -//! Run with `cargo run --bin smp_test --target=armv8r-none-eabihf -- -smp 2`. - -#![no_std] -#![no_main] - -use core::cell::{RefCell, UnsafeCell}; -use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; - -use aarch32_cpu::register::{Cpsr, Hactlr, Sctlr, cpsr::ProcessorMode}; -use aarch32_rt::entry; -use semihosting::println; - -use mps3_an536 as _; - -#[repr(align(16))] -struct Stack { - contents: UnsafeCell<[u8; LEN_BYTES]>, -} - -impl Stack { - const fn new() -> Self { - Self { - contents: UnsafeCell::new([0u8; LEN_BYTES]), - } - } - - fn stack_top(&self) -> usize { - let stack_start = self.contents.get() as usize; - stack_start + LEN_BYTES - } -} - -unsafe impl Sync for Stack {} - -static CORE1_STACK: Stack<{ 256 * 1024 }> = Stack::new(); - -static CORE1_BOOTED: AtomicBool = AtomicBool::new(false); - -static SHARED_VARIABLE: AtomicU32 = AtomicU32::new(0); - -static SHARED_VARIABLE_2: critical_section::Mutex> = - critical_section::Mutex::new(RefCell::new(0)); - -/// How long core 0 waits for core 1 -const CORE0_WILL_WAIT: usize = 1_000_000; - -/// How many CAS loops to run? -const CAS_LOOPS: u32 = 1000; - -/// How many CS Mutex loops to run? -const CS_MUTEX_LOOPS: u32 = 1000; - -/// The entry-point to the Rust application. -/// -/// It is called by the start-up code in `aarch32-rt`. -#[entry] -fn main() -> ! { - let fpga_led = 0xE020_2000 as *mut u32; - unsafe extern "C" { - static mut _core1_stack_pointer: usize; - } - unsafe { - let p = &raw mut _core1_stack_pointer; - p.write(CORE1_STACK.stack_top()); - } - unsafe { - // Activate second core by writing to FPGA LEDs. - // We needed a shared register that wasn't in RAM, and this will do. - fpga_led.write_volatile(1); - } - - // wait some time for core 1 to start - for counter in 0..=CORE0_WILL_WAIT { - if CORE1_BOOTED.load(Ordering::SeqCst) { - break; - } - if counter == CORE0_WILL_WAIT { - println!("CPU 1 is missing?!"); - - mps3_an536::exit(0); - } - } - - for _ in 0..CAS_LOOPS { - SHARED_VARIABLE.fetch_add(1, Ordering::Relaxed); - } - - for _ in 0..CS_MUTEX_LOOPS { - critical_section::with(|cs| { - let mut value_ref = SHARED_VARIABLE_2.borrow_ref_mut(cs); - *value_ref += 1; - }) - } - - // let the other core finish - for _ in 0..CORE0_WILL_WAIT { - aarch32_cpu::asm::nop(); - } - - let mut code = 0; - let total_a = SHARED_VARIABLE.load(Ordering::Relaxed); - if total_a == CAS_LOOPS * 2 { - println!("CAS test passed"); - } else { - println!("CAS test failed, got {} not 2000", total_a); - code = 1; - } - - let total_b = critical_section::with(|cs| { - let value_ref = SHARED_VARIABLE_2.borrow_ref(cs); - *value_ref - }); - - if total_b == CS_MUTEX_LOOPS * 2 { - println!("CS Mutex test passed"); - } else { - println!("CS Mutex test failed, got {} not 2000", total_b); - code = 1; - } - - mps3_an536::exit(code); -} - -/// The entry-point to the Rust application. -/// -/// It is called by the start-up code below, on Core 1. -#[unsafe(no_mangle)] -pub extern "C" fn kmain2() { - CORE1_BOOTED.store(true, Ordering::SeqCst); - - for _ in 0..CAS_LOOPS { - SHARED_VARIABLE.fetch_add(1, Ordering::Relaxed); - } - - for _ in 0..CS_MUTEX_LOOPS { - critical_section::with(|cs| { - let mut value_ref = SHARED_VARIABLE_2.borrow_ref_mut(cs); - *value_ref += 1; - }) - } - - loop { - core::hint::spin_loop(); - } -} - -// 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. -#[cfg(arm_architecture = "v8-r")] -core::arch::global_asm!( - r#" - .section .bss - .align 4 - _core1_stack_pointer: - .word 0 - - .section .text.startup - .align 4 - - .global _start - .global core1_released - .type _start, %function - _start: - // Read MPIDR into R0 - mrc p15, 0, r0, c0, c0, 5 - ands r0, r0, 0xFF - bne core1 - core0: - ldr pc, =_default_start - core1: - ldr r0, =0xE0202000 - mov r1, #0 - core1_spin: - wfe - // spin until an LED0 is on - ldr r2, [r0] - cmp r1, r2 - beq core1_spin - core1_released: - // now an LED is on, we assume _core1_stack_pointer contains our stack pointer - // First we must exit EL2... - // Set the HVBAR (for EL2) to _vector_table - ldr r0, =_vector_table - mcr p15, 4, r0, c12, c0, 0 - // Configure HACTLR to let us enter EL1 - mrc p15, 4, r0, c1, c0, 1 - mov r1, {hactlr_bits} - orr r0, r0, r1 - mcr p15, 4, r0, c1, c0, 1 - // Program the SPSR - enter system mode (0x1F) in Arm mode with IRQ, FIQ masked - mov r0, {sys_mode} - msr spsr_hyp, r0 - adr r0, 1f - msr elr_hyp, r0 - dsb - isb - eret - 1: - // Set the VBAR (for EL1) to _vector_table. NB: This isn't required on - // Armv7-R because that only supports 'low' (default) or 'high'. - ldr r0, =_vector_table - mcr p15, 0, r0, c12, c0, 0 - ldr r0, =_core1_stack_pointer - ldr r0, [r0] - // set up our stacks using that stack pointer - also switches to SYS mode - bl _stack_setup - // Zero all registers before calling kmain2 - 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 - // call our kmain2 for core 1 - bl kmain2 - .size _start, . - _start - "#, - 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() - } -); - -// Initialise the stack for each mode -#[cfg(target_arch = "arm")] -core::arch::global_asm!( - r#" - // Work around https://github.com/rust-lang/rust/issues/127269 - .fpu vfp2 - - // Configure a stack for every mode. Leaves you in sys mode. - // - // Pass in stack top in r0. - .section .text._stack_setup - .arm - .global _stack_setup - .type _stack_setup, %function - _stack_setup: - // Save LR from whatever mode we're currently in - mov r2, lr - // (we might not be in the same mode when we return). - // Set stack pointer (right after) and mask interrupts for for UND mode (Mode 0x1B) - msr cpsr_c, {und_mode} - mov sp, r0 - ldr r1, =_und_stack_size - sub r0, r0, r1 - // Set stack pointer (right after) and mask interrupts for for SVC mode (Mode 0x13) - msr cpsr_c, {svc_mode} - mov sp, r0 - ldr r1, =_svc_stack_size - sub r0, r0, r1 - // Set stack pointer (right after) and mask interrupts for for ABT mode (Mode 0x17) - msr cpsr_c, {abt_mode} - mov sp, r0 - ldr r1, =_abt_stack_size - sub r0, r0, r1 - // Set stack pointer (right after) and mask interrupts for for IRQ mode (Mode 0x12) - msr cpsr_c, {irq_mode} - mov sp, r0 - ldr r1, =_irq_stack_size - sub r0, r0, r1 - // Set stack pointer (right after) and mask interrupts for for FIQ mode (Mode 0x11) - msr cpsr_c, {fiq_mode} - mov sp, r0 - ldr r1, =_fiq_stack_size - sub r0, r0, r1 - // Set stack pointer (right after) and mask interrupts for for System mode (Mode 0x1F) - msr cpsr_c, {sys_mode} - mov sp, r0 - // Clear the Thumb Exception bit because all our targets are currently - // for Arm (A32) mode - mrc p15, 0, r1, c1, c0, 0 - bic r1, #{te_bit} - mcr p15, 0, r1, c1, c0, 0 - // return to caller - bx r2 - .size _stack_setup, . - _stack_setup - "#, - und_mode = const { - Cpsr::new_with_raw_value(0) - .with_mode(ProcessorMode::Und) - .with_i(true) - .with_f(true) - .raw_value() - }, - svc_mode = const { - Cpsr::new_with_raw_value(0) - .with_mode(ProcessorMode::Svc) - .with_i(true) - .with_f(true) - .raw_value() - }, - abt_mode = const { - Cpsr::new_with_raw_value(0) - .with_mode(ProcessorMode::Abt) - .with_i(true) - .with_f(true) - .raw_value() - }, - fiq_mode = const { - Cpsr::new_with_raw_value(0) - .with_mode(ProcessorMode::Fiq) - .with_i(true) - .with_f(true) - .raw_value() - }, - irq_mode = const { - Cpsr::new_with_raw_value(0) - .with_mode(ProcessorMode::Irq) - .with_i(true) - .with_f(true) - .raw_value() - }, - sys_mode = const { - Cpsr::new_with_raw_value(0) - .with_mode(ProcessorMode::Sys) - .with_i(true) - .with_f(true) - .raw_value() - }, - te_bit = const { Sctlr::new_with_raw_value(0).with_te(true).raw_value() } -); diff --git a/examples/mps3-an536/src/lib.rs b/examples/mps3-an536/src/lib.rs index 72596eba..3f5ba82f 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"); @@ -105,61 +111,37 @@ pub fn exit(code: i32) -> ! { /// /// ```text /// Stack usage report: -/// SYS Stack = 332 used of 16384 bytes (002%) @ 0x1006bf80..0x1006ff80 -/// FIQ Stack = 0 used of 64 bytes (000%) @ 0x1006ff80..0x1006ffc0 -/// IRQ Stack = 0 used of 64 bytes (000%) @ 0x1006ffc0..0x10070000 -/// ABT Stack = 0 used of 16384 bytes (000%) @ 0x10070000..0x10074000 -/// SVC Stack = 0 used of 16384 bytes (000%) @ 0x10074000..0x10078000 -/// UND Stack = 244 used of 16384 bytes (001%) @ 0x10078000..0x1007c000 -/// HYP Stack = 0 used of 16384 bytes (000%) @ 0x1007c000..0x10080000 +/// 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 core::ptr::addr_of; - - unsafe extern "C" { - static _sys_stack_end: u32; - static _sys_stack: u32; - static _fiq_stack_end: u32; - static _fiq_stack: u32; - static _irq_stack_end: u32; - static _irq_stack: u32; - static _abt_stack_end: u32; - static _abt_stack: u32; - static _svc_stack_end: u32; - static _svc_stack: u32; - static _und_stack_end: u32; - static _und_stack: u32; - static _hyp_stack_end: u32; - static _hyp_stack: u32; - } - - // these are placed in the order they are in aarch32-rt/link.x - let stacks = [ - ("SYS", addr_of!(_sys_stack_end)..addr_of!(_sys_stack)), - ("FIQ", addr_of!(_fiq_stack_end)..addr_of!(_fiq_stack)), - ("IRQ", addr_of!(_irq_stack_end)..addr_of!(_irq_stack)), - ("ABT", addr_of!(_abt_stack_end)..addr_of!(_abt_stack)), - ("SVC", addr_of!(_svc_stack_end)..addr_of!(_svc_stack)), - ("UND", addr_of!(_und_stack_end)..addr_of!(_und_stack)), - ("HYP", addr_of!(_hyp_stack_end)..addr_of!(_hyp_stack)), - ]; + use aarch32_rt::stacks::Stack; semihosting::eprintln!("Stack usage report:"); unsafe { - for (name, range) in stacks { - let (total, used) = stack_used_bytes(range.clone()); - let percent = (used * 100).checked_div(total).unwrap_or(999); - // Send to stderr, so it doesn't mix with expected output on stdout - semihosting::eprintln!( - "{} Stack = {:6} used of {:6} bytes ({:03}%) @ {:08x?}", - name, - used, - total, - percent, - range - ); + 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 + ); + } } } } diff --git a/examples/versatileab/.cargo/config.toml b/examples/versatileab/.cargo/config.toml index cb76cb6e..41913a6c 100644 --- a/examples/versatileab/.cargo/config.toml +++ b/examples/versatileab/.cargo/config.toml @@ -1,2 +1,51 @@ +[target.armv7r-none-eabihf] +runner = "qemu-system-arm -machine versatileab -cpu cortex-r5f -semihosting -nographic -audio none -kernel" + +[target.thumbv7r-none-eabihf] +runner = "qemu-system-arm -machine versatileab -cpu cortex-r5f -semihosting -nographic -audio none -kernel" + +[target.armv7r-none-eabi] +# change '-mcpu=cortex-r5' to '-mcpu=cortex-r5f' if you use eabi-fpu feature, otherwise +# qemu-system-arm will lock up +runner = "qemu-system-arm -machine versatileab -cpu cortex-r5 -semihosting -nographic -audio none -kernel" + +[target.thumbv7r-none-eabi] +# change '-mcpu=cortex-r5' to '-mcpu=cortex-r5f' if you use eabi-fpu feature, otherwise +# qemu-system-arm will lock up +runner = "qemu-system-arm -machine versatileab -cpu cortex-r5 -semihosting -nographic -audio none -kernel" + +[target.armv7a-none-eabihf] +runner = "qemu-system-arm -machine versatileab -cpu cortex-a8 -semihosting -nographic -audio none -kernel" + +[target.thumbv7a-none-eabihf] +runner = "qemu-system-arm -machine versatileab -cpu cortex-a8 -semihosting -nographic -audio none -kernel" + +[target.armv7a-none-eabi] +runner = "qemu-system-arm -machine versatileab -cpu cortex-a8 -semihosting -nographic -audio none -kernel" + +[target.thumbv7a-none-eabi] +runner = "qemu-system-arm -machine versatileab -cpu cortex-a8 -semihosting -nographic -audio none -kernel" + +[target.armv6-none-eabihf] +runner = "qemu-system-arm -machine versatileab -cpu arm1176 -semihosting -nographic -audio none -kernel" + +[target.armv6-none-eabi] +runner = "qemu-system-arm -machine versatileab -cpu arm1176 -semihosting -nographic -audio none -kernel" + +[target.thumbv6-none-eabi] +runner = "qemu-system-arm -machine versatileab -cpu arm1176 -semihosting -nographic -audio none -kernel" + +[target.armv5te-none-eabi] +runner = "qemu-system-arm -machine versatileab -cpu arm926 -semihosting -nographic -audio none -kernel" + +[target.thumbv5te-none-eabi] +runner = "qemu-system-arm -machine versatileab -cpu arm926 -semihosting -nographic -audio none -kernel" + +[target.armv4t-none-eabi] +runner = "qemu-system-arm -machine versatileab -cpu arm926 -semihosting -nographic -audio none -kernel" + +[target.thumbv4t-none-eabi] +runner = "qemu-system-arm -machine versatileab -cpu arm926 -semihosting -nographic -audio none -kernel" + [build] target = "armv7r-none-eabihf" diff --git a/examples/versatileab/src/lib.rs b/examples/versatileab/src/lib.rs index 7b3f9969..3d6ff6e9 100644 --- a/examples/versatileab/src/lib.rs +++ b/examples/versatileab/src/lib.rs @@ -69,61 +69,37 @@ pub fn exit(code: i32) -> ! { /// /// ```text /// Stack usage report: -/// SYS Stack = 332 used of 16384 bytes (002%) @ 0x1006bf80..0x1006ff80 -/// FIQ Stack = 0 used of 64 bytes (000%) @ 0x1006ff80..0x1006ffc0 -/// IRQ Stack = 0 used of 64 bytes (000%) @ 0x1006ffc0..0x10070000 -/// ABT Stack = 0 used of 16384 bytes (000%) @ 0x10070000..0x10074000 -/// SVC Stack = 0 used of 16384 bytes (000%) @ 0x10074000..0x10078000 -/// UND Stack = 244 used of 16384 bytes (001%) @ 0x10078000..0x1007c000 -/// HYP Stack = 0 used of 16384 bytes (000%) @ 0x1007c000..0x10080000 +/// 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 core::ptr::addr_of; - - unsafe extern "C" { - static _sys_stack_end: u32; - static _sys_stack: u32; - static _fiq_stack_end: u32; - static _fiq_stack: u32; - static _irq_stack_end: u32; - static _irq_stack: u32; - static _abt_stack_end: u32; - static _abt_stack: u32; - static _svc_stack_end: u32; - static _svc_stack: u32; - static _und_stack_end: u32; - static _und_stack: u32; - static _hyp_stack_end: u32; - static _hyp_stack: u32; - } - - // these are placed in the order they are in aarch32-rt/link.x - let stacks = [ - ("SYS", addr_of!(_sys_stack_end)..addr_of!(_sys_stack)), - ("FIQ", addr_of!(_fiq_stack_end)..addr_of!(_fiq_stack)), - ("IRQ", addr_of!(_irq_stack_end)..addr_of!(_irq_stack)), - ("ABT", addr_of!(_abt_stack_end)..addr_of!(_abt_stack)), - ("SVC", addr_of!(_svc_stack_end)..addr_of!(_svc_stack)), - ("UND", addr_of!(_und_stack_end)..addr_of!(_und_stack)), - ("HYP", addr_of!(_hyp_stack_end)..addr_of!(_hyp_stack)), - ]; + use aarch32_rt::stacks::Stack; semihosting::eprintln!("Stack usage report:"); unsafe { - for (name, range) in stacks { - let (total, used) = stack_used_bytes(range.clone()); - let percent = (used * 100).checked_div(total).unwrap_or(999); - // Send to stderr, so it doesn't mix with expected output on stdout - semihosting::eprintln!( - "{} Stack = {:6} used of {:6} bytes ({:03}%) @ {:08x?}", - name, - used, - total, - percent, - range - ); + 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 + ); + } } } } diff --git a/justfile b/justfile index cf8f315f..e556bd90 100644 --- a/justfile +++ b/justfile @@ -27,6 +27,8 @@ clean: rm -rf examples/versatileab/target-d32 cd examples/mps3-an536 && cargo clean rm -rf examples/mps3-an536/target-d32 + cd examples/mps3-an536-smp && cargo clean + rm -rf examples/mps3-an536-smp/target-d32 # Builds our workspace for all targets build-all: \ @@ -101,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: @@ -115,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: @@ -125,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: \ @@ -142,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: @@ -151,7 +163,7 @@ clippy-host: cd arm-targets && cargo clippy {{verbose}} # Run all the tests -test: test-cargo test-qemu test-smp +test: test-cargo test-qemu # Run the unit tests with cargo test-cargo: @@ -161,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: test-qemu-v4t test-qemu-v5te test-qemu-v6 test-qemu-v7a test-qemu-v7r test-qemu-v8r test-qemu-v8r-smp test-qemu-v4t: #!/bin/bash @@ -214,9 +226,20 @@ test-qemu-v8r: RUSTFLAGS=-Ctarget-cpu=cortex-r52 ./tests.sh examples/mps3-an536 thumbv8r-none-eabihf -Zbuild-std=core --features=fpu-d32 --target-dir=target-d32 {{verbose}} --release || FAIL=1 if [ "${FAIL}" == "1" ]; then exit 1; fi -# Run the special SMP test -# -# You can't run the normal examples with two CPUs because nothing stops the second CPU from running :/. So we have -# a special test for SMP mode on the MPS3-AN536 -test-smp: - cd examples/mps3-an536 && cargo run --target=armv8r-none-eabihf --bin smp_test {{verbose}} -- --smp 2 +test-qemu-v8r-smp: + #!/bin/bash + FAIL=0 + ./tests.sh examples/mps3-an536-smp armv8r-none-eabihf {{verbose}} --release || FAIL=1 + ./tests.sh examples/mps3-an536-smp thumbv8r-none-eabihf -Zbuild-std=core {{verbose}} --release || FAIL=1 + 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