diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 45eda03..34cfa0c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,6 +51,10 @@ jobs: platform: - name: axplat-x86-pc target: x86_64-unknown-none + - name: axplat-aarch64-common + target: aarch64-unknown-none + - name: axplat-aarch64-qemu-virt + target: aarch64-unknown-none steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly @@ -76,7 +80,9 @@ jobs: - uses: dtolnay/rust-toolchain@nightly - name: Build docs continue-on-error: ${{ github.ref != env.default-branch && github.event_name != 'pull_request' }} - run: cargo doc --no-deps --all-features --workspace --exclude "axplat-*" + run: | + cargo doc --no-deps --all-features --workspace --exclude "axplat-*" + cargo doc --no-deps --all-features -p axplat-aarch64-common - name: Deploy to Github Pages if: ${{ github.ref == env.default-branch }} uses: JamesIves/github-pages-deploy-action@v4 diff --git a/Cargo.lock b/Cargo.lock index 8ab4250..b4403f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,7 +8,7 @@ version = "10.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a21cd0131c25c438e19cd6a774adf7e3f64f7f4d723022882facc2dee0f8bc9" dependencies = [ - "tock-registers", + "tock-registers 0.9.0", ] [[package]] @@ -60,6 +60,33 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "arm_gicv2" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d25e73c949c69f75d1b9dba39c5475523403b31eb8c2fdc99da4dc33bc1aca" +dependencies = [ + "tock-registers 0.8.1", +] + +[[package]] +name = "arm_pl011" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efcf6afca4502993a737ba1e00952d1321078689da92bf7aab27d4e5756c0bec" +dependencies = [ + "tock-registers 0.8.1", +] + +[[package]] +name = "arm_pl031" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13696b1c2b59992f4223e0ae5bb173c81c63039367ca90eee845346ad2a13421" +dependencies = [ + "chrono", +] + [[package]] name = "autocfg" version = "1.4.0" @@ -100,7 +127,7 @@ dependencies = [ "page_table_entry", "riscv", "static_assertions", - "tock-registers", + "tock-registers 0.9.0", "x86", "x86_64 0.15.2", ] @@ -126,6 +153,37 @@ dependencies = [ "syn", ] +[[package]] +name = "axplat-aarch64-common" +version = "0.1.0" +dependencies = [ + "aarch64-cpu", + "arm_gicv2", + "arm_pl011", + "arm_pl031", + "axhal_cpu", + "axhal_plat", + "int_ratio", + "kspin", + "lazyinit", + "log", + "memory_addr", + "page_table_entry", + "tock-registers 0.9.0", +] + +[[package]] +name = "axplat-aarch64-qemu-virt" +version = "0.1.0" +dependencies = [ + "axconfig-gen-macros", + "axhal_cpu", + "axhal_plat", + "axplat-aarch64-common", + "memory_addr", + "page_table_entry", +] + [[package]] name = "axplat-cli" version = "0.1.0" @@ -194,6 +252,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" +dependencies = [ + "num-traits", +] + [[package]] name = "clap" version = "4.5.26" @@ -411,6 +478,15 @@ dependencies = [ "paste", ] +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "page_table_entry" version = "0.5.1" @@ -568,6 +644,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tock-registers" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "696941a0aee7e276a165a978b37918fd5d22c55c3d6bda197813070ca9c0f21c" + [[package]] name = "tock-registers" version = "0.9.0" diff --git a/Cargo.toml b/Cargo.toml index 96d0dea..8bc2bd4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,8 @@ members = [ "axhal_plat_macros", "axplat-cli", "platforms/axplat-x86-pc", + "platforms/axplat-aarch64-common", + "platforms/axplat-aarch64-qemu-virt", ] [workspace.package] diff --git a/axhal_cpu/src/aarch64/mod.rs b/axhal_cpu/src/aarch64/mod.rs index 19c0134..9f855a5 100644 --- a/axhal_cpu/src/aarch64/mod.rs +++ b/axhal_cpu/src/aarch64/mod.rs @@ -5,7 +5,7 @@ mod trap; use core::arch::asm; -use aarch64_cpu::registers::{DAIF, TPIDR_EL0, TTBR0_EL1, TTBR1_EL1, VBAR_EL1}; +use aarch64_cpu::{asm::barrier, registers::*}; use memory_addr::{PhysAddr, VirtAddr}; use tock_registers::interfaces::{Readable, Writeable}; @@ -137,3 +137,102 @@ pub fn read_thread_pointer() -> usize { pub unsafe fn write_thread_pointer(tpidr_el0: usize) { TPIDR_EL0.set(tpidr_el0 as _) } + +/// Swtich current exception level to EL1. +/// +/// It usually used in the kernel booting process, where the kernel starts at +/// EL2 or EL3. At that time, the MMU is not enable and the kernel is using +/// physical address. +/// +/// # Safety +/// +/// This function is unsafe as it changes the CPU mode. +pub unsafe fn switch_to_el1() { + SPSel.write(SPSel::SP::ELx); + SP_EL0.set(0); + let current_el = CurrentEL.read(CurrentEL::EL); + if current_el >= 2 { + if current_el == 3 { + // Set EL2 to 64bit and enable the HVC instruction. + SCR_EL3.write( + SCR_EL3::NS::NonSecure + SCR_EL3::HCE::HvcEnabled + SCR_EL3::RW::NextELIsAarch64, + ); + // Set the return address and exception level. + SPSR_EL3.write( + SPSR_EL3::M::EL1h + + SPSR_EL3::D::Masked + + SPSR_EL3::A::Masked + + SPSR_EL3::I::Masked + + SPSR_EL3::F::Masked, + ); + ELR_EL3.set(LR.get()); + } + // Disable EL1 timer traps and the timer offset. + CNTHCTL_EL2.modify(CNTHCTL_EL2::EL1PCEN::SET + CNTHCTL_EL2::EL1PCTEN::SET); + CNTVOFF_EL2.set(0); + // Set EL1 to 64bit. + HCR_EL2.write(HCR_EL2::RW::EL1IsAarch64); + // Set the return address and exception level. + SPSR_EL2.write( + SPSR_EL2::M::EL1h + + SPSR_EL2::D::Masked + + SPSR_EL2::A::Masked + + SPSR_EL2::I::Masked + + SPSR_EL2::F::Masked, + ); + SP_EL1.set(SP.get()); + ELR_EL2.set(LR.get()); + aarch64_cpu::asm::eret(); + } +} + +/// Enable EL1 MMU with the given page table root. +/// +/// It usually used in the kernel booting process. +/// +/// # Safety +/// +/// This function is unsafe as it changes the address translation configuration. +pub unsafe fn enable_mmu(root_paddr: PhysAddr) { + use page_table_entry::aarch64::MemAttr; + + MAIR_EL1.set(MemAttr::MAIR_VALUE); + + // Enable TTBR0 and TTBR1 walks, page size = 4K, vaddr size = 48 bits, paddr size = 48 bits. + let tcr_flags0 = TCR_EL1::EPD0::EnableTTBR0Walks + + TCR_EL1::TG0::KiB_4 + + TCR_EL1::SH0::Inner + + TCR_EL1::ORGN0::WriteBack_ReadAlloc_WriteAlloc_Cacheable + + TCR_EL1::IRGN0::WriteBack_ReadAlloc_WriteAlloc_Cacheable + + TCR_EL1::T0SZ.val(16); + let tcr_flags1 = TCR_EL1::EPD1::EnableTTBR1Walks + + TCR_EL1::TG1::KiB_4 + + TCR_EL1::SH1::Inner + + TCR_EL1::ORGN1::WriteBack_ReadAlloc_WriteAlloc_Cacheable + + TCR_EL1::IRGN1::WriteBack_ReadAlloc_WriteAlloc_Cacheable + + TCR_EL1::T1SZ.val(16); + TCR_EL1.write(TCR_EL1::IPS::Bits_48 + tcr_flags0 + tcr_flags1); + barrier::isb(barrier::SY); + + // Set both TTBR0 and TTBR1 + let root_paddr = root_paddr.as_usize() as u64; + TTBR0_EL1.set(root_paddr); + TTBR1_EL1.set(root_paddr); + + // Flush the entire TLB + flush_tlb(None); + + // Enable the MMU and turn on I-cache and D-cache + SCTLR_EL1.modify(SCTLR_EL1::M::Enable + SCTLR_EL1::C::Cacheable + SCTLR_EL1::I::Cacheable); + barrier::isb(barrier::SY); +} + +/// Enable FP and SIMD instructions. +/// +/// It usually used in the kernel booting process. +pub fn enable_fp() { + if cfg!(feature = "fp_simd") { + CPACR_EL1.write(CPACR_EL1::FPEN::TrapNothing); + barrier::isb(barrier::SY); + } +} diff --git a/axhal_plat/src/irq.rs b/axhal_plat/src/irq.rs index 36fb5f6..136b31b 100644 --- a/axhal_plat/src/irq.rs +++ b/axhal_plat/src/irq.rs @@ -9,24 +9,24 @@ pub type IrqHandler = handler_table::Handler; #[def_plat_interface] pub trait IrqIf { /// Enables or disables the given IRQ. - fn set_enable(vector: usize, enabled: bool); + fn set_enable(irq: usize, enabled: bool); /// Registers an IRQ handler for the given IRQ. /// /// It also enables the IRQ if the registration succeeds. It returns `false` /// if the registration failed. - fn register(vector: usize, handler: IrqHandler) -> bool; + fn register(irq: usize, handler: IrqHandler) -> bool; /// Unregisters the IRQ handler for the given IRQ. /// /// It also disables the IRQ if the unregistration succeeds. It returns the /// existing handler if it is registered, `None` otherwise. - fn unregister(vector: usize) -> Option; + fn unregister(irq: usize) -> Option; /// Handles the IRQ. /// /// It is called by the common interrupt handler. It should look up in the /// IRQ handler table and calls the corresponding handler. If necessary, it /// also acknowledges the interrupt controller after handling. - fn handle(vector: usize); + fn handle(irq: usize); } diff --git a/axhal_plat/src/lib.rs b/axhal_plat/src/lib.rs index cfa47b9..7307abe 100644 --- a/axhal_plat/src/lib.rs +++ b/axhal_plat/src/lib.rs @@ -19,12 +19,12 @@ pub mod __priv { pub use crate_interface::{call_interface, def_interface}; } -/// Call the function decorated by [`crate::main`] for the primary core. +/// Call the function decorated by [`axhal_plat::main`][main] for the primary core. pub fn call_main(cpu_id: usize, dtb: usize) -> ! { unsafe { __axhal_plat_main(cpu_id, dtb) } } -/// Call the function decorated by [`crate::secondary_main`] for secondary cores. +/// Call the function decorated by [`axhal_plat::secondary_main`][secondary_main] for secondary cores. pub fn call_secondary_main(cpu_id: usize) -> ! { unsafe { __axhal_plat_secondary_main(cpu_id) } } diff --git a/axplat-cli/template/src/irq.rs b/axplat-cli/template/src/irq.rs index 3cbf712..e121f57 100644 --- a/axplat-cli/template/src/irq.rs +++ b/axplat-cli/template/src/irq.rs @@ -5,7 +5,7 @@ struct IrqIfImpl; #[impl_plat_interface] impl IrqIf for IrqIfImpl { /// Enables or disables the given IRQ. - fn set_enable(vector: usize, enabled: bool) { + fn set_enable(irq: usize, enabled: bool) { todo!() } @@ -13,7 +13,7 @@ impl IrqIf for IrqIfImpl { /// /// It also enables the IRQ if the registration succeeds. It returns `false` /// if the registration failed. - fn register(vector: usize, handler: IrqHandler) -> bool { + fn register(irq: usize, handler: IrqHandler) -> bool { todo!() } @@ -21,7 +21,7 @@ impl IrqIf for IrqIfImpl { /// /// It also disables the IRQ if the unregistration succeeds. It returns the /// existing handler if it is registered, `None` otherwise. - fn unregister(vector: usize) -> Option { + fn unregister(irq: usize) -> Option { todo!() } @@ -30,7 +30,7 @@ impl IrqIf for IrqIfImpl { /// It is called by the common interrupt handler. It should look up in the /// IRQ handler table and calls the corresponding handler. If necessary, it /// also acknowledges the interrupt controller after handling. - fn handle(vector: usize) { + fn handle(irq: usize) { todo!() } } diff --git a/platforms/axplat-aarch64-common/Cargo.toml b/platforms/axplat-aarch64-common/Cargo.toml new file mode 100644 index 0000000..0bd9522 --- /dev/null +++ b/platforms/axplat-aarch64-common/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "axplat-aarch64-common" +version = "0.1.0" +edition.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +documentation.workspace = true +repository.workspace = true + +[dependencies] +kspin = "0.1" +log = "=0.4.21" +int_ratio = "0.1" +lazyinit = "0.2" +memory_addr = "0.3" +page_table_entry = "0.5" +aarch64-cpu = "10.0" +tock-registers = "0.9" +arm_pl011 = "0.1" +arm_gicv2 = { version = "0.1" } +arm_pl031 = { version = "0.2" } +axhal_cpu = { version = "0.1", path = "../../axhal_cpu" } +axhal_plat = { version = "0.1", path = "../../axhal_plat" } diff --git a/platforms/axplat-aarch64-common/src/generic_timer.rs b/platforms/axplat-aarch64-common/src/generic_timer.rs new file mode 100644 index 0000000..a681d4c --- /dev/null +++ b/platforms/axplat-aarch64-common/src/generic_timer.rs @@ -0,0 +1,101 @@ +//! ARM Generic Timer. + +use aarch64_cpu::registers::{CNTFRQ_EL0, CNTPCT_EL0, CNTP_CTL_EL0, CNTP_TVAL_EL0}; +use int_ratio::Ratio; +use tock_registers::interfaces::{Readable, Writeable}; + +static mut CNTPCT_TO_NANOS_RATIO: Ratio = Ratio::zero(); +static mut NANOS_TO_CNTPCT_RATIO: Ratio = Ratio::zero(); + +/// Returns the current clock time in hardware ticks. +#[inline] +pub fn current_ticks() -> u64 { + CNTPCT_EL0.get() +} + +/// Converts hardware ticks to nanoseconds. +#[inline] +pub fn ticks_to_nanos(ticks: u64) -> u64 { + unsafe { CNTPCT_TO_NANOS_RATIO.mul_trunc(ticks) } +} + +/// Converts nanoseconds to hardware ticks. +#[inline] +pub fn nanos_to_ticks(nanos: u64) -> u64 { + unsafe { NANOS_TO_CNTPCT_RATIO.mul_trunc(nanos) } +} + +/// Set a one-shot timer. +/// +/// A timer interrupt will be triggered at the specified monotonic time deadline (in nanoseconds). +pub fn set_oneshot_timer(deadline_ns: u64) { + let cnptct = CNTPCT_EL0.get(); + let cnptct_deadline = nanos_to_ticks(deadline_ns); + if cnptct < cnptct_deadline { + let interval = cnptct_deadline - cnptct; + debug_assert!(interval <= u32::MAX as u64); + CNTP_TVAL_EL0.set(interval); + } else { + CNTP_TVAL_EL0.set(0); + } +} + +/// Early stage initialization: stores the timer frequency. +pub fn init_early() { + let freq = CNTFRQ_EL0.get(); + unsafe { + CNTPCT_TO_NANOS_RATIO = Ratio::new(axhal_plat::time::NANOS_PER_SEC as u32, freq as u32); + NANOS_TO_CNTPCT_RATIO = CNTPCT_TO_NANOS_RATIO.inverse(); + } +} + +/// Enable timer interrupts. +/// +/// It should be called on all CPUs, as the timer interrupt is a PPI (Private +/// Peripheral Interrupt). +pub fn enable_irqs(timer_irq_num: usize) { + CNTP_CTL_EL0.write(CNTP_CTL_EL0::ENABLE::SET); + CNTP_TVAL_EL0.set(0); + crate::gic::set_enable(timer_irq_num, true); +} + +/// Default implementation of [`axhal_plat::time::TimeIf`] using the generic +/// timer. +#[macro_export] +macro_rules! time_if_impl { + ($name:ident) => { + struct $name; + + #[impl_plat_interface] + impl axhal_plat::time::TimeIf for $name { + /// Returns the current clock time in hardware ticks. + fn current_ticks() -> u64 { + $crate::generic_timer::current_ticks() + } + + /// Converts hardware ticks to nanoseconds. + fn ticks_to_nanos(ticks: u64) -> u64 { + $crate::generic_timer::ticks_to_nanos(ticks) + } + + /// Converts nanoseconds to hardware ticks. + fn nanos_to_ticks(nanos: u64) -> u64 { + $crate::generic_timer::nanos_to_ticks(nanos) + } + + /// Return epoch offset in nanoseconds (wall time offset to monotonic + /// clock start). + fn epochoffset_nanos() -> u64 { + $crate::pl031::epochoffset_nanos() + } + + /// Set a one-shot timer. + /// + /// A timer interrupt will be triggered at the specified monotonic time + /// deadline (in nanoseconds). + fn set_oneshot_timer(deadline_ns: u64) { + $crate::generic_timer::set_oneshot_timer(deadline_ns) + } + } + }; +} diff --git a/platforms/axplat-aarch64-common/src/gic.rs b/platforms/axplat-aarch64-common/src/gic.rs new file mode 100644 index 0000000..77a4374 --- /dev/null +++ b/platforms/axplat-aarch64-common/src/gic.rs @@ -0,0 +1,118 @@ +//! ARM Generic Interrupt Controller (GIC). + +use arm_gicv2::{GicCpuInterface, GicDistributor}; +use axhal_plat::irq::{HandlerTable, IrqHandler}; +use kspin::SpinNoIrq; +use lazyinit::LazyInit; +use memory_addr::VirtAddr; + +/// The maximum number of IRQs. +const MAX_IRQ_COUNT: usize = 1024; + +static GICD: LazyInit> = LazyInit::new(); + +// per-CPU, no lock +static GICC: LazyInit = LazyInit::new(); + +static IRQ_HANDLER_TABLE: HandlerTable = HandlerTable::new(); + +/// Enables or disables the given IRQ. +pub fn set_enable(irq_num: usize, enabled: bool) { + trace!("GICD set enable: {} {}", irq_num, enabled); + GICD.lock().set_enable(irq_num as _, enabled); +} + +/// Registers an IRQ handler for the given IRQ. +/// +/// It also enables the IRQ if the registration succeeds. It returns `false` +/// if the registration failed. +pub fn register_handler(irq_num: usize, handler: IrqHandler) -> bool { + trace!("register handler IRQ {}", irq_num); + if IRQ_HANDLER_TABLE.register_handler(irq_num, handler) { + set_enable(irq_num, true); + return true; + } + warn!("register handler for IRQ {} failed", irq_num); + false +} + +/// Unregisters the IRQ handler for the given IRQ. +/// +/// It also disables the IRQ if the unregistration succeeds. It returns the +/// existing handler if it is registered, `None` otherwise. +pub fn unregister_handler(irq_num: usize) -> Option { + trace!("unregister handler IRQ {}", irq_num); + set_enable(irq_num, false); + IRQ_HANDLER_TABLE.unregister_handler(irq_num) +} + +/// Handles the IRQ. +/// +/// It is called by the common interrupt handler. It should look up in the +/// IRQ handler table and calls the corresponding handler. If necessary, it +/// also acknowledges the interrupt controller after handling. +pub fn handle_irq(_unused: usize) { + GICC.handle_irq(|irq_num| { + trace!("IRQ {}", irq_num); + if !IRQ_HANDLER_TABLE.handle(irq_num as _) { + warn!("Unhandled IRQ {}", irq_num); + } + }); +} + +/// Initializes GICD (for the primary CPU only). +pub fn init_gicd(gicd_base: VirtAddr, gicc_base: VirtAddr) { + info!("Initialize GICv2..."); + GICD.init_once(SpinNoIrq::new(GicDistributor::new(gicd_base.as_mut_ptr()))); + GICC.init_once(GicCpuInterface::new(gicc_base.as_mut_ptr())); + GICD.lock().init(); +} + +/// Initializes GICC (for all CPUs). +/// +/// It must be called after [`init_gicd`]. +pub fn init_gicc() { + GICC.init(); +} + + +/// Default implementation of [`axhal_plat::irq::IrqIf`] using the GIC. +#[macro_export] +macro_rules! irq_if_impl { + ($name:ident) => { + struct $name; + + #[impl_plat_interface] + impl axhal_plat::irq::IrqIf for $name { + /// Enables or disables the given IRQ. + fn set_enable(irq: usize, enabled: bool) { + $crate::gic::set_enable(irq, enabled); + } + + /// Registers an IRQ handler for the given IRQ. + /// + /// It also enables the IRQ if the registration succeeds. It returns `false` + /// if the registration failed. + fn register(irq: usize, handler: axhal_plat::irq::IrqHandler) -> bool { + $crate::gic::register_handler(irq, handler) + } + + /// Unregisters the IRQ handler for the given IRQ. + /// + /// It also disables the IRQ if the unregistration succeeds. It returns the + /// existing handler if it is registered, `None` otherwise. + fn unregister(irq: usize) -> Option { + $crate::gic::unregister_handler(irq) + } + + /// Handles the IRQ. + /// + /// It is called by the common interrupt handler. It should look up in the + /// IRQ handler table and calls the corresponding handler. If necessary, it + /// also acknowledges the interrupt controller after handling. + fn handle(irq: usize) { + $crate::gic::handle_irq(irq) + } + } + }; +} diff --git a/platforms/axplat-aarch64-common/src/lib.rs b/platforms/axplat-aarch64-common/src/lib.rs new file mode 100644 index 0000000..ed68c36 --- /dev/null +++ b/platforms/axplat-aarch64-common/src/lib.rs @@ -0,0 +1,20 @@ +//! Common platform device drivers for AArch64 platforms. +//! +//! It includes: +//! +//! - PL011 UART driver. +//! - PL031 Real Time Clock (RTC) driver. +//! - GICv2 (Generic Interrupt Controller) driver. +//! - Generic Timer related functions. +//! - PSCI (Power State Coordination Interface) calls. + +#![no_std] + +#[macro_use] +extern crate log; + +pub mod generic_timer; +pub mod gic; +pub mod pl011; +pub mod pl031; +pub mod psci; diff --git a/platforms/axplat-aarch64-common/src/pl011.rs b/platforms/axplat-aarch64-common/src/pl011.rs new file mode 100644 index 0000000..296febf --- /dev/null +++ b/platforms/axplat-aarch64-common/src/pl011.rs @@ -0,0 +1,88 @@ +//! PL011 UART. + +use arm_pl011::Pl011Uart; +use kspin::SpinNoIrq; +use lazyinit::LazyInit; +use memory_addr::VirtAddr; + +static UART: LazyInit> = LazyInit::new(); + +/// Writes a byte to the console. +pub fn putchar(c: u8) { + let mut uart = UART.lock(); + match c { + b'\n' => { + uart.putchar(b'\r'); + uart.putchar(b'\n'); + } + c => uart.putchar(c), + } +} + +/// Reads a byte from the console, or returns [`None`] if no input is available. +fn getchar() -> Option { + UART.lock().getchar() +} + +/// Write a slice of bytes to the console. +pub fn write_bytes(bytes: &[u8]) { + for c in bytes { + putchar(*c); + } +} + +/// Reads bytes from the console into the given mutable slice. +/// Returns the number of bytes read. +pub fn read_bytes(bytes: &mut [u8]) -> usize { + let mut read_len = 0; + while read_len < bytes.len() { + if let Some(c) = getchar() { + bytes[read_len] = c; + } else { + break; + } + read_len += 1; + } + read_len +} + +/// Early stage initialization of the PL011 UART driver. +pub fn init_early(uart_base: VirtAddr) { + UART.init_once(SpinNoIrq::new(Pl011Uart::new(uart_base.as_mut_ptr()))); + UART.lock().init(); +} + +/// UART IRQ Handler +pub fn irq_handler() { + let is_receive_interrupt = UART.lock().is_receive_interrupt(); + UART.lock().ack_interrupts(); + if is_receive_interrupt { + while let Some(c) = getchar() { + putchar(c); + } + } +} + +/// Default implementation of [`axhal_plat::console::ConsoleIf`] using the +/// PL011 UART. +#[macro_export] +macro_rules! console_if_impl { + ($name:ident) => { + struct $name; + + #[axhal_plat::impl_plat_interface] + impl axhal_plat::console::ConsoleIf for $name { + /// Writes given bytes to the console. + fn write_bytes(bytes: &[u8]) { + $crate::pl011::write_bytes(bytes); + } + + /// Reads bytes from the console into the given mutable slice. + /// + /// Returns the number of bytes read. + fn read_bytes(bytes: &mut [u8]) -> usize { + $crate::pl011::read_bytes(bytes) + } + } + }; +} diff --git a/platforms/axplat-aarch64-common/src/pl031.rs b/platforms/axplat-aarch64-common/src/pl031.rs new file mode 100644 index 0000000..3932ffe --- /dev/null +++ b/platforms/axplat-aarch64-common/src/pl031.rs @@ -0,0 +1,35 @@ +//! PL031 Real Time Clock (RTC) driver. + +use arm_pl031::Rtc; +use memory_addr::VirtAddr; + +use crate::generic_timer::{current_ticks, ticks_to_nanos}; + +/// RTC wall time offset in nanoseconds at monotonic time base. +static mut RTC_EPOCHOFFSET_NANOS: u64 = 0; + +/// Return epoch offset in nanoseconds (wall time offset to monotonic clock start). +#[inline] +pub fn epochoffset_nanos() -> u64 { + unsafe { RTC_EPOCHOFFSET_NANOS } +} + +/// Early stage initialization of the RTC driver. +/// +/// It reads the current real time and calculates the epoch offset. +pub fn init_early(rtc_base: VirtAddr) { + // Make sure `RTC_PADDR` is valid in platform config file. + if rtc_base.as_usize() == 0 { + return; + } + + let rtc = unsafe { Rtc::new(rtc_base.as_mut_ptr() as _) }; + + // Get the current time in microseconds since the epoch (1970-01-01) from the aarch64 pl031 RTC. + // Subtract the timer ticks to get the actual time when ArceOS was booted. + let epoch_time_nanos = rtc.get_unix_timestamp() as u64 * 1_000_000_000; + + unsafe { + RTC_EPOCHOFFSET_NANOS = epoch_time_nanos - ticks_to_nanos(current_ticks()); + } +} diff --git a/platforms/axplat-aarch64-common/src/psci.rs b/platforms/axplat-aarch64-common/src/psci.rs new file mode 100644 index 0000000..c446026 --- /dev/null +++ b/platforms/axplat-aarch64-common/src/psci.rs @@ -0,0 +1,142 @@ +//! ARM Power State Coordination Interface. + +#![allow(dead_code)] + +const PSCI_0_2_FN_BASE: u32 = 0x84000000; +const PSCI_0_2_64BIT: u32 = 0x40000000; +const PSCI_0_2_FN_CPU_SUSPEND: u32 = PSCI_0_2_FN_BASE + 1; +const PSCI_0_2_FN_CPU_OFF: u32 = PSCI_0_2_FN_BASE + 2; +const PSCI_0_2_FN_CPU_ON: u32 = PSCI_0_2_FN_BASE + 3; +const PSCI_0_2_FN_MIGRATE: u32 = PSCI_0_2_FN_BASE + 5; +const PSCI_0_2_FN_SYSTEM_OFF: u32 = PSCI_0_2_FN_BASE + 8; +const PSCI_0_2_FN_SYSTEM_RESET: u32 = PSCI_0_2_FN_BASE + 9; +const PSCI_0_2_FN64_CPU_SUSPEND: u32 = PSCI_0_2_FN_BASE + PSCI_0_2_64BIT + 1; +const PSCI_0_2_FN64_CPU_ON: u32 = PSCI_0_2_FN_BASE + PSCI_0_2_64BIT + 3; +const PSCI_0_2_FN64_MIGRATE: u32 = PSCI_0_2_FN_BASE + PSCI_0_2_64BIT + 5; + +static mut PSCI_METHOD_HVC: bool = false; + +/// PSCI return values, inclusive of all PSCI versions. +#[derive(PartialEq, Debug)] +#[repr(i32)] +enum PsciError { + NotSupported = -1, + InvalidParams = -2, + Denied = -3, + AlreadyOn = -4, + OnPending = -5, + InternalFailure = -6, + NotPresent = -7, + Disabled = -8, + InvalidAddress = -9, +} + +impl From for PsciError { + fn from(code: i32) -> PsciError { + use PsciError::*; + match code { + -1 => NotSupported, + -2 => InvalidParams, + -3 => Denied, + -4 => AlreadyOn, + -5 => OnPending, + -6 => InternalFailure, + -7 => NotPresent, + -8 => Disabled, + -9 => InvalidAddress, + _ => panic!("Unknown PSCI error code: {}", code), + } + } +} + +/// arm,psci method: smc +/// when SMCCC_CONDUIT_SMC = 1 +fn arm_smccc_smc(func: u32, arg0: usize, arg1: usize, arg2: usize) -> usize { + let mut ret; + unsafe { + core::arch::asm!( + "smc #0", + inlateout("x0") func as usize => ret, + in("x1") arg0, + in("x2") arg1, + in("x3") arg2, + ) + } + ret +} + +/// psci "hvc" method call +fn psci_hvc_call(func: u32, arg0: usize, arg1: usize, arg2: usize) -> usize { + let ret; + unsafe { + core::arch::asm!( + "hvc #0", + inlateout("x0") func as usize => ret, + in("x1") arg0, + in("x2") arg1, + in("x3") arg2, + ) + } + ret +} + +fn psci_call(func: u32, arg0: usize, arg1: usize, arg2: usize) -> Result<(), PsciError> { + let ret = if unsafe { PSCI_METHOD_HVC } { + psci_hvc_call(func, arg0, arg1, arg2) + } else { + arm_smccc_smc(func, arg0, arg1, arg2) + }; + if ret == 0 { + Ok(()) + } else { + Err(PsciError::from(ret as i32)) + } +} + +/// Initialize with the given PSCI method. +/// +/// Method should be either "smc" or "hvc". +pub fn init(method: &str) { + match method { + "smc" => unsafe { PSCI_METHOD_HVC = false }, + "hvc" => unsafe { PSCI_METHOD_HVC = true }, + _ => panic!("Unknown PSCI method: {}", method), + } +} + +/// Shutdown the whole system, including all CPUs. +pub fn system_off() -> ! { + info!("Shutting down..."); + psci_call(PSCI_0_2_FN_SYSTEM_OFF, 0, 0, 0).ok(); + warn!("It should shutdown!"); + loop { + unsafe { core::arch::asm!("wfi") }; + } +} + +/// Power up a core. This call is used to power up cores that either: +/// +/// * Have not yet been booted into the calling supervisory software. +/// * Have been previously powered down with a `cpu_off` call. +/// +/// `target_cpu` contains a copy of the affinity fields of the MPIDR register. +/// `entry_point` is the physical address of the secondary CPU's entry point. +/// `arg` will be passed to the `X0` register of the secondary CPU. +pub fn cpu_on(target_cpu: usize, entry_point: usize, arg: usize) { + info!("Starting CPU {:x} ON ...", target_cpu); + let res = psci_call(PSCI_0_2_FN64_CPU_ON, target_cpu, entry_point, arg); + if let Err(e) = res { + error!("failed to boot CPU {:x} ({:?})", target_cpu, e); + } +} + +/// Power down the calling core. This call is intended for use in hotplug. A +/// core that is powered down by `cpu_off` can only be powered up again in +/// response to a `cpu_on`. +pub fn cpu_off() { + const PSCI_POWER_STATE_TYPE_STANDBY: u32 = 0; + const PSCI_POWER_STATE_TYPE_POWER_DOWN: u32 = 1; + const PSCI_0_2_POWER_STATE_TYPE_SHIFT: u32 = 16; + let state: u32 = PSCI_POWER_STATE_TYPE_POWER_DOWN << PSCI_0_2_POWER_STATE_TYPE_SHIFT; + psci_call(PSCI_0_2_FN_CPU_OFF, state as usize, 0, 0).ok(); +} diff --git a/platforms/axplat-aarch64-qemu-virt/Cargo.toml b/platforms/axplat-aarch64-qemu-virt/Cargo.toml new file mode 100644 index 0000000..ae7349b --- /dev/null +++ b/platforms/axplat-aarch64-qemu-virt/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "axplat-aarch64-qemu-virt" +version = "0.1.0" +edition.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +documentation.workspace = true +repository.workspace = true + +[features] +fp_simd = ["axhal_cpu/fp_simd"] +irq = [] +rtc = [] +smp = [] + +[dependencies] +memory_addr = "0.3" +page_table_entry = "0.5" +axconfig-gen-macros = "0.1" +axplat-aarch64-common = { version = "0.1", path = "../axplat-aarch64-common" } +axhal_cpu = { version = "0.1", path = "../../axhal_cpu" } +axhal_plat = { version = "0.1", path = "../../axhal_plat" } diff --git a/platforms/axplat-aarch64-qemu-virt/axconfig.toml b/platforms/axplat-aarch64-qemu-virt/axconfig.toml new file mode 100644 index 0000000..9711ce9 --- /dev/null +++ b/platforms/axplat-aarch64-qemu-virt/axconfig.toml @@ -0,0 +1,118 @@ +# Architecture identifier. +arch = "aarch64" # str +# Platform identifier. +platform = "aarch64-qemu-virt" # str + +[includes] +axplat-aarch64-common = "0.1.0" + +# +# Platform configs +# +[plat] +# Platform family. +family = "aarch64-qemu-virt" # str + +# Base address of the whole physical memory. +phys-memory-base = 0x4000_0000 # uint +# Size of the whole physical memory. (128M) +phys-memory-size = 0x800_0000 # uint +# Base physical address of the kernel image. +kernel-base-paddr = 0x4020_0000 # uint +# Base virtual address of the kernel image. +kernel-base-vaddr = "0xffff_0000_4020_0000" # uint +# Linear mapping offset, for quick conversions between physical and virtual +# addresses. +phys-virt-offset = "0xffff_0000_0000_0000" # uint +# Offset of bus address and phys address. some boards, the bus address is +# different from the physical address. +phys-bus-offset = 0 # uint +# Kernel address space base. +kernel-aspace-base = "0xffff_0000_0000_0000" # uint +# Kernel address space size. +kernel-aspace-size = "0x0000_ffff_ffff_f000" # uint +# Stack size on bootstrapping. (256K) +boot-stack-size = 0x40000 # uint + +# PSCI +psci-method = "hvc" # str + +# +# Device specifications +# +[devices] +# MMIO ranges with format (`base_paddr`, `size`). +mmio-ranges = [ + [0x0900_0000, 0x1000], # PL011 UART + [0x0910_0000, 0x1000], # PL031 RTC + [0x0800_0000, 0x2_0000], # GICv2 + [0x0a00_0000, 0x4000], # VirtIO + [0x1000_0000, 0x2eff_0000], # PCI memory ranges (ranges 1: 32-bit MMIO space) + [0x40_1000_0000, 0x1000_0000], # PCI config space +] # [(uint, uint)] +# VirtIO MMIO ranges with format (`base_paddr`, `size`). +virtio-mmio-ranges = [ + [0x0a00_0000, 0x200], + [0x0a00_0200, 0x200], + [0x0a00_0400, 0x200], + [0x0a00_0600, 0x200], + [0x0a00_0800, 0x200], + [0x0a00_0a00, 0x200], + [0x0a00_0c00, 0x200], + [0x0a00_0e00, 0x200], + [0x0a00_1000, 0x200], + [0x0a00_1200, 0x200], + [0x0a00_1400, 0x200], + [0x0a00_1600, 0x200], + [0x0a00_1800, 0x200], + [0x0a00_1a00, 0x200], + [0x0a00_1c00, 0x200], + [0x0a00_1e00, 0x200], + [0x0a00_3000, 0x200], + [0x0a00_2200, 0x200], + [0x0a00_2400, 0x200], + [0x0a00_2600, 0x200], + [0x0a00_2800, 0x200], + [0x0a00_2a00, 0x200], + [0x0a00_2c00, 0x200], + [0x0a00_2e00, 0x200], + [0x0a00_3000, 0x200], + [0x0a00_3200, 0x200], + [0x0a00_3400, 0x200], + [0x0a00_3600, 0x200], + [0x0a00_3800, 0x200], + [0x0a00_3a00, 0x200], + [0x0a00_3c00, 0x200], + [0x0a00_3e00, 0x200], +] # [(uint, uint)] +# Base physical address of the PCIe ECAM space. +pci-ecam-base = 0x40_1000_0000 # uint +# End PCI bus number (`bus-range` property in device tree). +pci-bus-end = 0xff # uint +# PCI device memory ranges (`ranges` property in device tree). +pci-ranges = [ + [0x3ef_f0000, 0x1_0000], # PIO space + [0x1000_0000, 0x2eff_0000], # 32-bit MMIO space + [0x80_0000_0000, 0x80_0000_0000], # 64-bit MMIO space +] # [(uint, uint)] +# UART Address +uart-paddr = 0x0900_0000 # uint +# UART IRQ number +uart-irq = 1 # uint +# Timer interrupt num (PPI, physical). +timer-irq = 30 # uint + +# GIC CPU Interface base address +gicc-paddr = 0x0801_0000 # uint +# GIC Distributor base address +gicd-paddr = 0x0800_0000 # uint + +# pl031@9010000 { +# clock-names = "apb_pclk"; +# clocks = <0x8000>; +# interrupts = <0x00 0x02 0x04>; +# reg = <0x00 0x9010000 0x00 0x1000>; +# compatible = "arm,pl031\0arm,primecell"; +# }; +# RTC (PL031) Address +rtc-paddr = 0x901_0000 # uint diff --git a/platforms/axplat-aarch64-qemu-virt/src/boot.rs b/platforms/axplat-aarch64-qemu-virt/src/boot.rs new file mode 100644 index 0000000..1b91c6c --- /dev/null +++ b/platforms/axplat-aarch64-qemu-virt/src/boot.rs @@ -0,0 +1,139 @@ +use page_table_entry::{aarch64::A64PTE, GenericPTE, MappingFlags}; + +use crate::config::plat::{BOOT_STACK_SIZE, PHYS_VIRT_OFFSET}; + +#[unsafe(link_section = ".bss.stack")] +static mut BOOT_STACK: [u8; BOOT_STACK_SIZE] = [0; BOOT_STACK_SIZE]; + +#[unsafe(link_section = ".data.boot_page_table")] +static mut BOOT_PT_L0: [A64PTE; 512] = [A64PTE::empty(); 512]; + +#[unsafe(link_section = ".data.boot_page_table")] +static mut BOOT_PT_L1: [A64PTE; 512] = [A64PTE::empty(); 512]; + +unsafe fn init_boot_page_table() { + // 0x0000_0000_0000 ~ 0x0080_0000_0000, table + BOOT_PT_L0[0] = A64PTE::new_table(pa!(&raw mut BOOT_PT_L1 as usize)); + // 0x0000_0000_0000..0x0000_4000_0000, 1G block, device memory + BOOT_PT_L1[0] = A64PTE::new_page( + pa!(0), + MappingFlags::READ | MappingFlags::WRITE | MappingFlags::DEVICE, + true, + ); + // 0x0000_4000_0000..0x0000_8000_0000, 1G block, normal memory + BOOT_PT_L1[1] = A64PTE::new_page( + pa!(0x4000_0000), + MappingFlags::READ | MappingFlags::WRITE | MappingFlags::EXECUTE, + true, + ); +} + +/// Kernel entry point with Linux image header. +/// +/// Some bootloaders require this header to be present at the beginning of the +/// kernel image. +/// +/// Documentation: +#[naked] +#[unsafe(no_mangle)] +#[unsafe(link_section = ".text.boot")] +unsafe extern "C" fn _start() -> ! { + const FLAG_LE: usize = 0b0; + const FLAG_PAGE_SIZE_4K: usize = 0b10; + const FLAG_ANY_MEM: usize = 0b1000; + unsafe { + // PC = bootloader load address + // X0 = dtb + core::arch::naked_asm!(" + add x13, x18, #0x16 // 'MZ' magic + b {entry} // Branch to kernel start, magic + + .quad 0 // Image load offset from start of RAM, little-endian + .quad _ekernel - _start // Effective size of kernel image, little-endian + .quad {flags} // Kernel flags, little-endian + .quad 0 // reserved + .quad 0 // reserved + .quad 0 // reserved + .ascii \"ARM\\x64\" // Magic number + .long 0 // reserved (used for PE COFF offset)", + flags = const FLAG_LE | FLAG_PAGE_SIZE_4K | FLAG_ANY_MEM, + entry = sym _start_primary, + ) + } +} + +/// The earliest entry point for the primary CPU. +#[naked] +#[unsafe(link_section = ".text.boot")] +unsafe extern "C" fn _start_primary() -> ! { + unsafe { + // X0 = dtb + core::arch::naked_asm!(" + mrs x19, mpidr_el1 + and x19, x19, #0xffffff // get current CPU id + mov x20, x0 // save DTB pointer + + adrp x8, {boot_stack} // setup boot stack + add x8, x8, {boot_stack_size} + mov sp, x8 + + bl {switch_to_el1} // switch to EL1 + bl {init_boot_page_table} + adr x0, {boot_pt} + bl {enable_mmu} // setup MMU + bl {enable_fp} // enable fp/neon + + mov x8, {phys_virt_offset} // set SP to the high address + add sp, sp, x8 + + mov x0, x19 // call rust_entry(cpu_id, dtb) + mov x1, x20 + ldr x8, ={entry} + blr x8 + b .", + switch_to_el1 = sym axhal_cpu::switch_to_el1, + enable_mmu = sym axhal_cpu::enable_mmu, + enable_fp = sym axhal_cpu::enable_fp, + init_boot_page_table = sym init_boot_page_table, + boot_stack = sym BOOT_STACK, + boot_stack_size = const BOOT_STACK_SIZE, + boot_pt = sym BOOT_PT_L0, + phys_virt_offset = const PHYS_VIRT_OFFSET, + entry = sym crate::rust_entry, + ) + } +} + +/// The earliest entry point for the secondary CPUs. +#[cfg(feature = "smp")] +#[naked] +#[unsafe(link_section = ".text.boot")] +pub(crate) unsafe extern "C" fn _start_secondary() -> ! { + unsafe { + // X0 = stack pointer + core::arch::naked_asm!(" + mrs x19, mpidr_el1 + and x19, x19, #0xffffff // get current CPU id + + mov sp, x0 + bl {switch_to_el1} + adr x0, {boot_pt} + bl {enable_mmu} + bl {enable_fp} + + mov x8, {phys_virt_offset} // set SP to the high address + add sp, sp, x8 + + mov x0, x19 // call rust_entry_secondary(cpu_id) + ldr x8, ={entry} + blr x8 + b .", + switch_to_el1 = sym axhal_cpu::switch_to_el1, + enable_mmu = sym axhal_cpu::enable_mmu, + enable_fp = sym axhal_cpu::enable_fp, + boot_pt = sym BOOT_PT_L0, + phys_virt_offset = const PHYS_VIRT_OFFSET, + entry = sym crate::rust_entry_secondary, + ) + } +} diff --git a/platforms/axplat-aarch64-qemu-virt/src/init.rs b/platforms/axplat-aarch64-qemu-virt/src/init.rs new file mode 100644 index 0000000..c2d9846 --- /dev/null +++ b/platforms/axplat-aarch64-qemu-virt/src/init.rs @@ -0,0 +1,32 @@ +use axhal_plat::init::InitIf; + +#[cfg(feature = "irq")] +use crate::config::devices::{GICC_PADDR, GICD_PADDR, TIMER_IRQ}; + +struct InitIfImpl; + +#[impl_plat_interface] +impl InitIf for InitIfImpl { + /// Initializes the platform devices for the primary core. + fn platform_init() { + #[cfg(feature = "irq")] + { + use crate::mem::phys_to_virt; + axplat_aarch64_common::gic::init_gicd( + phys_to_virt(pa!(GICD_PADDR)), + phys_to_virt(pa!(GICC_PADDR)), + ); + axplat_aarch64_common::gic::init_gicc(); + axplat_aarch64_common::generic_timer::enable_irqs(TIMER_IRQ); + } + } + + /// Initializes the platform devices for secondary cores. + fn platform_init_secondary() { + #[cfg(all(feature = "smp", feature = "irq"))] + { + axplat_aarch64_common::gic::init_gicc(); + axplat_aarch64_common::generic_timer::enable_irqs(TIMER_IRQ); + } + } +} diff --git a/platforms/axplat-aarch64-qemu-virt/src/lib.rs b/platforms/axplat-aarch64-qemu-virt/src/lib.rs new file mode 100644 index 0000000..24d899e --- /dev/null +++ b/platforms/axplat-aarch64-qemu-virt/src/lib.rs @@ -0,0 +1,51 @@ +#![no_std] +#![feature(naked_functions)] + +#[macro_use] +extern crate axhal_plat; + +#[macro_use] +extern crate memory_addr; + +mod boot; +mod init; +mod mem; +mod power; + +mod config { + axconfig_gen_macros::include_configs!("axconfig.toml"); +} + +axplat_aarch64_common::console_if_impl!(ConsoleIfImpl); +axplat_aarch64_common::time_if_impl!(TimeIfImpl); + +#[cfg(feature = "irq")] +axplat_aarch64_common::irq_if_impl!(IrqIfImpl); + +#[allow(unused_imports)] +use self::config::devices::{RTC_PADDR, UART_PADDR}; +use self::config::plat::PSCI_METHOD; +use self::mem::phys_to_virt; + +unsafe extern "C" { + fn exception_vector_base(); +} + +unsafe extern "C" fn rust_entry(cpu_id: usize, dtb: usize) { + axhal_plat::mem::clear_bss(); + axhal_cpu::set_exception_vector_base(exception_vector_base as usize); + axhal_cpu::write_page_table_root0(0.into()); // disable low address access + axplat_aarch64_common::psci::init(PSCI_METHOD); + axplat_aarch64_common::pl011::init_early(phys_to_virt(pa!(UART_PADDR))); + axplat_aarch64_common::generic_timer::init_early(); + #[cfg(feature = "rtc")] + axplat_aarch64_common::pl031::init_early(phys_to_virt(pa!(RTC_PADDR))); + axhal_plat::call_main(cpu_id, dtb); +} + +#[cfg(feature = "smp")] +unsafe extern "C" fn rust_entry_secondary(cpu_id: usize) { + axhal_cpu::set_exception_vector_base(exception_vector_base as usize); + axhal_cpu::write_page_table_root0(0.into()); // disable low address access + axhal_plat::call_secondary_main(cpu_id); +} diff --git a/platforms/axplat-aarch64-qemu-virt/src/mem.rs b/platforms/axplat-aarch64-qemu-virt/src/mem.rs new file mode 100644 index 0000000..0c5440b --- /dev/null +++ b/platforms/axplat-aarch64-qemu-virt/src/mem.rs @@ -0,0 +1,43 @@ +use axhal_plat::mem::{MemIf, RawRange}; +use memory_addr::{PhysAddr, VirtAddr}; + +use crate::config::devices::MMIO_RANGES; +use crate::config::plat::{PHYS_MEMORY_BASE, PHYS_MEMORY_SIZE, PHYS_VIRT_OFFSET}; + +struct MemIfImpl; + +pub const fn phys_to_virt(paddr: PhysAddr) -> VirtAddr { + va!(paddr.as_usize() + PHYS_VIRT_OFFSET) +} + +#[allow(dead_code)] +pub const fn virt_to_phys(vaddr: VirtAddr) -> PhysAddr { + pa!(vaddr.as_usize() - PHYS_VIRT_OFFSET) +} + +#[impl_plat_interface] +impl MemIf for MemIfImpl { + /// Returns all physical memory (RAM) ranges on the platform. + /// + /// All memory ranges except reserved ranges (including the kernel loaded + /// range) are free for allocation. + fn phys_ram_ranges() -> &'static [RawRange] { + &[(PHYS_MEMORY_BASE, PHYS_MEMORY_SIZE)] + } + + /// Returns all reserved physical memory ranges on the platform. + /// + /// Reserved memory can be contained in [`phys_ram_ranges`], they are not + /// allocatable but should be mapped to kernel's address space. + /// + /// Note that the ranges returned should not include the range where the + /// kernel is loaded. + fn reserved_phys_ram_ranges() -> &'static [RawRange] { + &[] + } + + /// Returns all device memory (MMIO) ranges on the platform. + fn mmio_ranges() -> &'static [RawRange] { + &MMIO_RANGES + } +} diff --git a/platforms/axplat-aarch64-qemu-virt/src/power.rs b/platforms/axplat-aarch64-qemu-virt/src/power.rs new file mode 100644 index 0000000..b188aba --- /dev/null +++ b/platforms/axplat-aarch64-qemu-virt/src/power.rs @@ -0,0 +1,24 @@ +use axhal_plat::power::PowerIf; + +struct PowerImpl; + +#[impl_plat_interface] +impl PowerIf for PowerImpl { + /// Bootstraps the given CPU core with the given initial stack (in physical + /// address). + /// + /// Where `cpu_id` is the logical CPU ID (0, 1, ..., N-1, N is the number of + /// CPU cores on the platform). + fn cpu_boot(_cpu_id: usize, _stack_top_paddr: usize) { + #[cfg(feature = "smp")] + { + let entry_paddr = crate::mem::virt_to_phys(va!(crate::boot::_start_secondary as usize)); + axplat_aarch64_common::psci::cpu_on(_cpu_id, entry_paddr.as_usize(), _stack_top_paddr); + } + } + + /// Shutdown the whole system. + fn system_off() -> ! { + axplat_aarch64_common::psci::system_off() + } +} diff --git a/platforms/axplat-x86-pc/Cargo.toml b/platforms/axplat-x86-pc/Cargo.toml index d426d4c..562b371 100644 --- a/platforms/axplat-x86-pc/Cargo.toml +++ b/platforms/axplat-x86-pc/Cargo.toml @@ -9,10 +9,10 @@ documentation.workspace = true repository.workspace = true [features] -smp = [] +fp_simd = ["axhal_cpu/fp_simd"] irq = [] rtc = ["x86_rtc"] -fp_simd = [] +smp = [] reboot-on-system-off = [] [dependencies] diff --git a/platforms/axplat-x86-pc/axconfig.toml b/platforms/axplat-x86-pc/axconfig.toml index bf46b13..bfdb9b6 100644 --- a/platforms/axplat-x86-pc/axconfig.toml +++ b/platforms/axplat-x86-pc/axconfig.toml @@ -1,5 +1,5 @@ # Architecture identifier. -arch = "x86_64" # str +arch = "x86_64" # str # Platform identifier. platform = "x86-pc" # str @@ -31,6 +31,7 @@ kernel-aspace-size = "0x0000_7fff_ffff_f000" # uint # Stack size on bootstrapping. (256K) boot-stack-size = 0x40000 # uint +# # Device specifications # [devices]