Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ jobs:
- test-qemu-v6
- test-qemu-v7r
- test-qemu-v7a
- test-qemu-v7a-zynq
- test-qemu-v8r
- test-qemu-v8r-el2
steps:
Expand Down
8 changes: 4 additions & 4 deletions examples/xilinx-zynq-a9/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
[target.armv7a-none-eabihf]
runner = "qemu-system-arm -machine xilinx-zynq-a9 -cpu cortex-a9 -semihosting -nographic -audio none -kernel"
runner = "qemu-system-arm -machine xilinx-zynq-a9 -cpu cortex-a9 -semihosting -nographic -audio none -smp 2 -kernel"

[target.thumbv7a-none-eabihf]
runner = "qemu-system-arm -machine xilinx-zynq-a9 -cpu cortex-a9 -semihosting -nographic -audio none -kernel"
runner = "qemu-system-arm -machine xilinx-zynq-a9 -cpu cortex-a9 -semihosting -nographic -audio none -smp 2 -kernel"

[target.armv7a-none-eabi]
runner = "qemu-system-arm -machine xilinx-zynq-a9 -cpu cortex-a9 -semihosting -nographic -audio none -kernel"
runner = "qemu-system-arm -machine xilinx-zynq-a9 -cpu cortex-a9 -semihosting -nographic -audio none -smp 2 -kernel"

[target.thumbv7a-none-eabi]
runner = "qemu-system-arm -machine xilinx-zynq-a9 -cpu cortex-a9 -semihosting -nographic -audio none -kernel"
runner = "qemu-system-arm -machine xilinx-zynq-a9 -cpu cortex-a9 -semihosting -nographic -audio none -smp 2 -kernel"

[build]
target = "armv7a-none-eabihf"
25 changes: 23 additions & 2 deletions examples/xilinx-zynq-a9/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,25 @@ Zynq-7000, not the real thing.
[`.cargo/config.toml`]: ./.cargo/config.toml
[`rust-toolchain.toml`]: ./rust-toolchain.toml

## Examples

- `hello` - prints a message over semihosting and then panics
- `gic` - sets up the memory-mapped GIC (the GICv2 programming model), sends a
Software Generated Interrupt to this core, and handles it
- `smp` - boots the second core and checks that atomics and critical sections
work across both cores

The Zynq-7000 is a dual-core device, so QEMU is run with `-smp 2`. The
single-core examples simply leave the second core parked.

## Running

Run these examples as follows:

```console
$ cargo run --bin hello
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.80s
Running `qemu-system-arm -machine xilinx-zynq-a9 -cpu cortex-a9 -semihosting -nographic -audio none -kernel target/armv7a-none-eabihf/debug/hello`
Running `qemu-system-arm -machine xilinx-zynq-a9 -cpu cortex-a9 -semihosting -nographic -audio none -smp 2 -kernel target/armv7a-none-eabihf/debug/hello`
Hello, this is semihosting! x = 1.000, y = 2.000
PANIC: PanicInfo {
message: I am an example panic,
Expand All @@ -43,6 +54,16 @@ PANIC: PanicInfo {
}
```

The `smp` example prints the following:

```console
$ cargo run --bin smp
I am core 0 - Mpidr(80000000)
I am core 1 - Mpidr(80000001)
CAS test passed
CS Mutex test passed
```

## Debugging

You can start a GDB server by adding `-- -s -S` to the end of the `cargo run`
Expand All @@ -59,7 +80,7 @@ $ arm-none-eabi-gdb target/armv7a-none-eabihf/debug/hello
## 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
[`../../rust-toolchain.toml`] file. These examples are not version controlled and we
may change the MSRV at any time.

## Licence
Expand Down
3 changes: 3 additions & 0 deletions examples/xilinx-zynq-a9/memory.x
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,6 @@ PROVIDE(_abt_stack_size = 16K);
PROVIDE(_irq_stack_size = 64);
PROVIDE(_fiq_stack_size = 64);
PROVIDE(_sys_stack_size = 16K);

/* This is a dual-core Cortex-A9, so reserve a set of stacks per core. */
PROVIDE(_num_cores = 2);
6 changes: 6 additions & 0 deletions examples/xilinx-zynq-a9/reference/gic-armv7a-none-eabi.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
I am core Mpidr(80000000)
Creating GIC driver @ 0xf8f01000 / 0xf8f00100
Configure SGI SGI 3 as Group0...
Send SGI to self
- got interrupt SGI 3
SGI handled
6 changes: 6 additions & 0 deletions examples/xilinx-zynq-a9/reference/gic-armv7a-none-eabihf.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
I am core Mpidr(80000000)
Creating GIC driver @ 0xf8f01000 / 0xf8f00100
Configure SGI SGI 3 as Group0...
Send SGI to self
- got interrupt SGI 3
SGI handled
6 changes: 6 additions & 0 deletions examples/xilinx-zynq-a9/reference/gic-thumbv7a-none-eabi.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
I am core Mpidr(80000000)
Creating GIC driver @ 0xf8f01000 / 0xf8f00100
Configure SGI SGI 3 as Group0...
Send SGI to self
- got interrupt SGI 3
SGI handled
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
I am core Mpidr(80000000)
Creating GIC driver @ 0xf8f01000 / 0xf8f00100
Configure SGI SGI 3 as Group0...
Send SGI to self
- got interrupt SGI 3
SGI handled
4 changes: 4 additions & 0 deletions examples/xilinx-zynq-a9/reference/smp-armv7a-none-eabi.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
I am core 0 - Mpidr(80000000)
I am core 1 - Mpidr(80000001)
CAS test passed
CS Mutex test passed
4 changes: 4 additions & 0 deletions examples/xilinx-zynq-a9/reference/smp-armv7a-none-eabihf.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
I am core 0 - Mpidr(80000000)
I am core 1 - Mpidr(80000001)
CAS test passed
CS Mutex test passed
4 changes: 4 additions & 0 deletions examples/xilinx-zynq-a9/reference/smp-thumbv7a-none-eabi.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
I am core 0 - Mpidr(80000000)
I am core 1 - Mpidr(80000001)
CAS test passed
CS Mutex test passed
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
I am core 0 - Mpidr(80000000)
I am core 1 - Mpidr(80000001)
CAS test passed
CS Mutex test passed
4 changes: 0 additions & 4 deletions examples/xilinx-zynq-a9/rust-toolchain.toml

This file was deleted.

121 changes: 121 additions & 0 deletions examples/xilinx-zynq-a9/src/bin/gic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
//! # GIC example for the Arm Cortex-A9 on a Xilinx Zynq-7000
//!
//! Sets up the memory-mapped GIC (the GICv2 programming model), configures a
//! Software Generated Interrupt, sends it to this same core, and handles it in
//! the IRQ handler. This exercises the interrupt controller on a single core,
//! before we add a second core.

#![no_std]
#![no_main]

use core::cell::RefCell;
use core::sync::atomic::{AtomicBool, Ordering};

use arm_gic::{
IntId, InterruptGroup,
gicv2::{GicV2, SgiTarget, SgiTargetListFilter},
};
use critical_section::Mutex;
use semihosting::println;

use aarch32_rt::entry;
use xilinx_zynq_a9 as _;

/// Set once the IRQ handler has run.
static IRQ_FIRED: AtomicBool = AtomicBool::new(false);

/// Shared interrupt controller driver.
///
/// The GICv2 CPU interface is memory-mapped, so ack/EOI need `&mut` access to
/// the driver from inside the IRQ handler. We therefore share it behind a
/// critical-section `Mutex`.
static GLOBAL_GIC: Mutex<RefCell<Option<GicV2<'static>>>> = Mutex::new(RefCell::new(None));

/// The Software Generated Interrupt we send to ourselves.
const SGI_INTID: IntId = IntId::sgi(3);

/// The interrupt group we use.
///
/// [`GicV2::setup`] only enables Group 0 signalling on the CPU interface (it
/// writes `GICC_CTLR = 0b1`), even when the GIC implements the Security
/// Extensions and assigns every interrupt to Group 1 at the Distributor. So we
/// keep our SGI in Group 0, where it is actually delivered. With `FIQEn` clear,
/// Group 0 interrupts are signalled as IRQs.
const GROUP: InterruptGroup = InterruptGroup::Group0;

/// The entry-point to the Rust application.
///
/// It is called by the start-up code in `aarch32-rt`.
#[entry]
fn main() -> ! {
xilinx_zynq_a9::init();

println!("I am core {:08x?}", aarch32_cpu::register::Mpidr::read());

// SAFETY: this is the only call to `make_gic()`.
let mut gic = unsafe { xilinx_zynq_a9::make_gic() };

// `setup()` only enabled Group 1 in the Distributor (this GIC reports the
// Security Extensions), so enable Group 0 too - that is the group our SGI
// and the CPU interface use.
gic.enable_group0(true);

println!("Configure SGI {:?} as {:?}...", SGI_INTID, GROUP);
gic.set_interrupt_priority(SGI_INTID, 0x31);
gic.set_group(SGI_INTID, GROUP);
gic.enable_interrupt(SGI_INTID, true).unwrap();

critical_section::with(|cs| {
GLOBAL_GIC.borrow_ref_mut(cs).replace(gic);
});

unsafe {
aarch32_cpu::interrupt::enable();
}

println!("Send SGI to self");
critical_section::with(|cs| {
let mut gic = GLOBAL_GIC.borrow_ref_mut(cs);
let gic = gic.as_mut().unwrap();
gic.send_sgi(
SGI_INTID,
SgiTarget::List {
target_list_filter: SgiTargetListFilter::ForwardSelfOnly,
target_list: 0,
},
);
});

// Wait for the IRQ handler to run.
for _ in 0..1_000_000 {
if IRQ_FIRED.load(Ordering::SeqCst) {
break;
}
aarch32_cpu::asm::wfi();
}

if IRQ_FIRED.load(Ordering::SeqCst) {
println!("SGI handled");
} else {
println!("SGI missing?!");
}

xilinx_zynq_a9::exit(0);
}

/// Called when the Arm CPU gets an IRQ.
///
/// Talks to the GIC to find out which interrupt is pending, handles it, and
/// then tells the GIC it has been handled.
#[aarch32_rt::irq]
fn irq_handler() {
critical_section::with(|cs| {
let mut gic = GLOBAL_GIC.borrow_ref_mut(cs);
let gic = gic.as_mut().unwrap();
while let Some(intid) = gic.get_and_acknowledge_interrupt(GROUP) {
println!("- got interrupt {:?}", intid);
IRQ_FIRED.store(true, Ordering::SeqCst);
gic.end_interrupt(intid, GROUP);
}
});
}
130 changes: 130 additions & 0 deletions examples/xilinx-zynq-a9/src/bin/smp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
//! # Multi-core hello-world for the Arm Cortex-A9 on a Xilinx Zynq-7000
//!
//! Boots the second core and checks that atomics and critical sections work
//! across both cores.

#![no_std]
#![no_main]

use core::cell::RefCell;
use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};

use aarch32_rt::entry;
use semihosting::println;
use xilinx_zynq_a9 as _;

/// Set by Core 1 once it has booted.
static CORE1_BOOTED: AtomicBool = AtomicBool::new(false);

/// Incremented by both cores using an atomic read-modify-write.
static SHARED_VARIABLE: AtomicU32 = AtomicU32::new(0);

/// Incremented by both cores from inside a critical section.
static SHARED_VARIABLE_2: critical_section::Mutex<RefCell<u32>> =
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 atomic-add loops each core runs.
const CAS_LOOPS: u32 = 1000;

/// How many critical-section loops each core runs.
const CS_MUTEX_LOOPS: u32 = 1000;

/// The entry-point to the Rust application.
///
/// It is called by the start-up code in `aarch32-rt` on Core 0.
#[entry]
fn main() -> ! {
xilinx_zynq_a9::init();

println!(
"I am core 0 - {:08x?}",
aarch32_cpu::register::Mpidr::read()
);

xilinx_zynq_a9::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?!");
xilinx_zynq_a9::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;
}

xilinx_zynq_a9::exit(code);
}

/// The entry-point to the Rust application on Core 1.
///
/// Called by the start-up code once Core 0 has released this core.
#[unsafe(no_mangle)]
pub extern "C" fn kmain_secondary() {
// Each core enables its own MMU before it does any atomics: the exclusive
// monitor used by `fetch_add` and the critical section needs normal,
// shareable memory, which the MMU provides.
xilinx_zynq_a9::init();

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();
}
}
Loading