Skip to content

Commit af3f3da

Browse files
committed
feat(arm-qemu-virt): add initial support for ARM QEMU platform
1 parent d8185f6 commit af3f3da

File tree

10 files changed

+453
-0
lines changed

10 files changed

+453
-0
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ members = [
1414
"platforms/axplat-aarch64-phytium-pi",
1515
"platforms/axplat-riscv64-qemu-virt",
1616
"platforms/axplat-loongarch64-qemu-virt",
17+
"platforms/axplat-arm-qemu-virt",
1718

1819
"examples/hello-kernel",
1920
"examples/irq-kernel",
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[package]
2+
name = "axplat-arm-qemu-virt"
3+
version = "0.1.0"
4+
edition = "2024"
5+
6+
[features]
7+
irq = ["axplat/irq"]
8+
smp = []
9+
rtc = []
10+
default = []
11+
12+
[dependencies]
13+
axplat = "0.2.0"
14+
axconfig-macros = "0.2"
15+
memory_addr = "0.4"
16+
kspin = "0.1"
17+
axcpu = "0.2"
18+
arm_pl011 = "0.1"
19+
lazy_static = { version = "1.5", features = ["spin_no_std"] }
20+
page_table_entry = {path = "../../../page_table_multiarch/page_table_entry"}
21+
aarch32-cpu = "0.1"
22+
log = "0.4"
23+
axplat-aarch64-peripherals = { version = "0.2", path = "../axplat-aarch64-peripherals" }
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Architecture identifier.
2+
arch = "arm" # str
3+
# Platform identifier.
4+
platform = "arm-qemu-virt" # str
5+
# Platform package.
6+
package = "axplat-arm-qemu-virt" # str
7+
8+
#
9+
# Platform configs
10+
#
11+
[plat]
12+
# Number of CPUs.
13+
cpu-num = 1 # uint
14+
# Base address of the whole physical memory.
15+
phys-memory-base = 0x4000_0000 # uint
16+
# Size of the whole physical memory. (128M)
17+
phys-memory-size = 0x800_0000 # uint
18+
# Base physical address of the kernel image.
19+
kernel-base-paddr = 0x4001_0000 # uint
20+
# Base virtual address of the kernel image.
21+
kernel-base-vaddr = "0x4001_0000" # uint
22+
# Linear mapping offset, for quick conversions between physical and virtual
23+
# addresses.
24+
phys-virt-offset = "0x0000" # uint
25+
# Offset of bus address and phys address. some boards, the bus address is
26+
# different from the physical address.
27+
phys-bus-offset = 0 # uint
28+
# Kernel address space base.
29+
kernel-aspace-base = "0x0000" # uint
30+
# Kernel address space size.
31+
kernel-aspace-size = "0xffff_f000" # uint
32+
# Stack size on bootstrapping. (256K)
33+
boot-stack-size = 0x40000 # uint
34+
35+
# PSCI
36+
psci-method = "hvc" # str
37+
38+
#
39+
# Device specifications
40+
#
41+
[devices]
42+
# MMIO ranges with format (`base_paddr`, `size`).
43+
mmio-ranges = [
44+
[0x0900_0000, 0x1000], # PL011 UART
45+
[0x0910_0000, 0x1000], # PL031 RTC
46+
[0x0800_0000, 0x2_0000], # GICv2
47+
[0x0a00_0000, 0x4000], # VirtIO
48+
[0x1000_0000, 0x2eff_0000], # PCI memory ranges (ranges 1: 32-bit MMIO space)
49+
] # [(uint, uint)]
50+
# VirtIO MMIO ranges with format (`base_paddr`, `size`).
51+
virtio-mmio-ranges = [
52+
53+
] # [(uint, uint)]
54+
# Base physical address of the PCIe ECAM space.
55+
pci-ecam-base = 0x40_1000_0000 # uint
56+
# End PCI bus number (`bus-range` property in device tree).
57+
pci-bus-end = 0xff # uint
58+
# PCI device memory ranges (`ranges` property in device tree).
59+
pci-ranges = [
60+
[0x3ef_f0000, 0x1_0000], # PIO space
61+
[0x1000_0000, 0x2eff_0000], # 32-bit MMIO space
62+
] # [(uint, uint)]
63+
# UART Address
64+
uart-paddr = 0x0900_0000 # uint
65+
# UART IRQ number (SPI, 1)
66+
uart-irq = 33 # uint
67+
# Timer interrupt num (PPI, physical timer).
68+
timer-irq = 30 # uint
69+
# IPI interrupt num
70+
ipi-irq = 1 # uint
71+
72+
# GIC CPU Interface base address
73+
gicc-paddr = 0x0801_0000 # uint
74+
# GIC Distributor base address
75+
gicd-paddr = 0x0800_0000 # uint
76+
77+
# pl031@9010000 {
78+
# clock-names = "apb_pclk";
79+
# clocks = <0x8000>;
80+
# interrupts = <0x00 0x02 0x04>;
81+
# reg = <0x00 0x9010000 0x00 0x1000>;
82+
# compatible = "arm,pl031\0arm,primecell";
83+
# };
84+
# RTC (PL031) Address
85+
rtc-paddr = 0x901_0000 # uint
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
fn main() {
2+
println!("cargo:rerun-if-env-changed=AX_CONFIG_PATH");
3+
if let Ok(config_path) = std::env::var("AX_CONFIG_PATH") {
4+
println!("cargo:rerun-if-changed={config_path}");
5+
}
6+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
//! Early boot initialization code for ARMv7-A.
2+
3+
use axplat::mem::pa;
4+
use page_table_entry::{GenericPTE, MappingFlags, arm::A32PTE};
5+
6+
use crate::config::plat::BOOT_STACK_SIZE;
7+
8+
/// Boot stack, 256KB
9+
#[unsafe(link_section = ".bss.stack")]
10+
pub static mut BOOT_STACK: [u8; BOOT_STACK_SIZE] = [0; BOOT_STACK_SIZE];
11+
12+
/// ARMv7-A L1 page table (16KB, contains 4096 entries)
13+
/// Must be 16KB aligned for TTBR0
14+
#[repr(align(16384))]
15+
struct Aligned16K<T>(T);
16+
17+
impl<T> Aligned16K<T> {
18+
const fn new(inner: T) -> Self {
19+
Self(inner)
20+
}
21+
}
22+
23+
#[unsafe(link_section = ".data.page_table")]
24+
static mut BOOT_PT_L1: Aligned16K<[A32PTE; 4096]> = Aligned16K::new([A32PTE::empty(); 4096]);
25+
26+
/// Initialize boot page table.
27+
/// This function is unsafe as it modifies global static variables.
28+
#[unsafe(no_mangle)]
29+
pub unsafe fn init_boot_page_table() {
30+
unsafe {
31+
// Map memory regions using 1MB sections (ARMv7-A max granularity)
32+
// Note: AArch64 can use 1GB blocks, but we're limited to 1MB here
33+
34+
// 0x0000_0000..0xc000_0000 (0-3GB): Normal memory, RWX
35+
// Equivalent to AArch64's first 3 entries, but needs 3072 entries (3 * 1024)
36+
for i in 0..0xc00 {
37+
BOOT_PT_L1.0[i] = A32PTE::new_page(
38+
pa!(i * 0x10_0000),
39+
MappingFlags::READ | MappingFlags::WRITE | MappingFlags::EXECUTE,
40+
true, // 1MB section
41+
);
42+
}
43+
44+
// 0xc000_0000..0x1_0000_0000 (3GB-4GB): Device memory
45+
// Equivalent to AArch64's BOOT_PT_L1[3], but needs 1024 entries
46+
// This includes MMIO devices (PL011 UART, GICv2, VirtIO, etc.)
47+
for i in 0xc00..0x1000 {
48+
BOOT_PT_L1.0[i] = A32PTE::new_page(
49+
pa!(i * 0x10_0000),
50+
MappingFlags::READ | MappingFlags::WRITE | MappingFlags::DEVICE,
51+
true,
52+
);
53+
}
54+
}
55+
}
56+
57+
#[unsafe(naked)]
58+
#[unsafe(no_mangle)]
59+
#[unsafe(link_section = ".text.boot")]
60+
pub unsafe extern "C" fn _start() -> ! {
61+
core::arch::naked_asm!(
62+
"
63+
// Set stack pointer to top of BOOT_STACK
64+
ldr sp, ={boot_stack}
65+
add sp, sp, #{stack_size}
66+
67+
// Initialize page table
68+
bl {init_pt}
69+
70+
// Enable MMU
71+
// bl {enable_mmu}
72+
73+
// Jump to Rust entry
74+
bl {rust_entry}
75+
1: b 1b",
76+
boot_stack = sym BOOT_STACK,
77+
stack_size = const BOOT_STACK_SIZE,
78+
init_pt = sym init_boot_page_table,
79+
enable_mmu = sym axcpu::init::init_mmu,
80+
rust_entry = sym axplat::call_main,
81+
)
82+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
use core::arch::asm;
2+
3+
const NANOS_PER_SEC: u64 = 1_000_000_000;
4+
5+
struct TimeIfImpl;
6+
7+
/// Read the CNTFRQ register (Counter-timer Frequency register)
8+
#[inline]
9+
fn timer_frequency() -> u64 {
10+
let freq: u32;
11+
unsafe {
12+
// CNTFRQ: c14, c0, 0
13+
asm!("mrc p15, 0, {}, c14, c0, 0", out(reg) freq);
14+
}
15+
freq as u64
16+
}
17+
18+
/// Read the CNTVCT register (Counter-timer Virtual Count register)
19+
#[inline]
20+
fn timer_counter() -> u64 {
21+
let low: u32;
22+
let high: u32;
23+
unsafe {
24+
// CNTVCT: c14
25+
asm!("mrrc p15, 1, {}, {}, c14", out(reg) low, out(reg) high);
26+
}
27+
((high as u64) << 32) | (low as u64)
28+
}
29+
30+
/// Write CNTP_CVAL register (Counter-timer Physical Timer CompareValue register)
31+
#[inline]
32+
fn write_timer_comparevalue(value: u64) {
33+
let low = value as u32;
34+
let high = (value >> 32) as u32;
35+
unsafe {
36+
// CNTP_CVAL: c14, c2
37+
asm!("mcrr p15, 2, {}, {}, c14", in(reg) low, in(reg) high);
38+
}
39+
}
40+
41+
/// Write Physical Timer Control register (CNTP_CTL)
42+
#[inline]
43+
fn write_timer_control(control: u32) {
44+
unsafe {
45+
// CNTP_CTL: c14, c2
46+
asm!("mcr p15, 0, {}, c14, c2, 1", in(reg) control);
47+
}
48+
}
49+
50+
/// Returns the current clock time in hardware ticks.
51+
#[impl_plat_interface]
52+
impl axplat::time::TimeIf for TimeIfImpl {
53+
fn current_ticks() -> u64 {
54+
timer_counter()
55+
}
56+
57+
/// Converts hardware ticks to nanoseconds.
58+
fn ticks_to_nanos(ticks: u64) -> u64 {
59+
let freq = timer_frequency();
60+
ticks * NANOS_PER_SEC / freq
61+
}
62+
63+
/// Converts nanoseconds to hardware ticks.
64+
fn nanos_to_ticks(nanos: u64) -> u64 {
65+
let freq = timer_frequency();
66+
nanos * freq / NANOS_PER_SEC
67+
}
68+
69+
fn epochoffset_nanos() -> u64 {
70+
0
71+
}
72+
73+
/// Set a one-shot timer.
74+
///
75+
/// A timer interrupt will be triggered at the specified monotonic time deadline (in nanoseconds).
76+
#[cfg(feature = "irq")]
77+
fn set_oneshot_timer(deadline_ns: u64) {
78+
let current_ns = Self::ticks_to_nanos(Self::current_ticks());
79+
if deadline_ns > current_ns {
80+
let ticks = Self::nanos_to_ticks(deadline_ns - current_ns);
81+
write_timer_comparevalue(Self::current_ticks() + ticks);
82+
write_timer_control(1); // Enable timer
83+
} else {
84+
// Deadline has passed, trigger immediately
85+
write_timer_comparevalue(Self::current_ticks());
86+
write_timer_control(1); // Enable timer
87+
}
88+
}
89+
}
90+
91+
/// Enable timer interrupts
92+
#[cfg(feature = "irq")]
93+
pub fn enable_irqs(_irq_num: usize) {
94+
write_timer_comparevalue(timer_counter());
95+
write_timer_control(1);
96+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
use axplat::mem::phys_to_virt;
2+
use memory_addr::pa;
3+
4+
use crate::config::plat::PSCI_METHOD;
5+
6+
#[cfg(feature = "irq")]
7+
const TIMER_IRQ: usize = crate::config::devices::TIMER_IRQ;
8+
9+
struct InitIfImpl;
10+
11+
#[impl_plat_interface]
12+
impl axplat::init::InitIf for InitIfImpl {
13+
/// Initializes the platform at the early stage for the primary core.
14+
///
15+
/// This function should be called immediately after the kernel has booted,
16+
/// and performed earliest platform configuration and initialization (e.g.,
17+
/// early console, clocking).
18+
fn init_early(_cpu_id: usize, _dtb: usize) {
19+
axcpu::init::init_trap();
20+
axplat_aarch64_peripherals::pl011::init_early(phys_to_virt(pa!(
21+
crate::config::devices::UART_PADDR
22+
)));
23+
axplat_aarch64_peripherals::psci::init(PSCI_METHOD);
24+
25+
axplat::console_println!("init_early on QEMU VIRT platform");
26+
#[cfg(feature = "rtc")]
27+
axplat_aarch64_peripherals::pl031::init_early(phys_to_virt(pa!(RTC_PADDR)));
28+
}
29+
30+
/// Initializes the platform at the early stage for secondary cores.
31+
#[cfg(feature = "smp")]
32+
fn init_early_secondary(_cpu_id: usize) {
33+
axcpu::init::init_trap();
34+
}
35+
36+
/// Initializes the platform at the later stage for the primary core.
37+
///
38+
/// This function should be called after the kernel has done part of its
39+
/// initialization (e.g, logging, memory management), and finalized the rest of
40+
/// platform configuration and initialization.
41+
fn init_later(_cpu_id: usize, _dtb: usize) {
42+
#[cfg(feature = "irq")]
43+
{
44+
axplat_aarch64_peripherals::gic::init_gic(
45+
phys_to_virt(pa!(crate::config::devices::GICD_PADDR)),
46+
phys_to_virt(pa!(crate::config::devices::GICC_PADDR)),
47+
);
48+
axplat_aarch64_peripherals::gic::init_gicc();
49+
crate::generic_timer::enable_irqs(TIMER_IRQ);
50+
}
51+
}
52+
53+
/// Initializes the platform at the later stage for secondary cores.
54+
#[cfg(feature = "smp")]
55+
fn init_later_secondary(_cpu_id: usize) {
56+
#[cfg(feature = "irq")]
57+
{
58+
crate::irq::init_current_cpu();
59+
crate::generic_timer::enable_irqs(TIMER_IRQ);
60+
}
61+
}
62+
}

0 commit comments

Comments
 (0)