diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8505e52..5ab806c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,7 +3,7 @@ name: CI on: [push, pull_request] jobs: - ci: + crates: runs-on: ubuntu-latest strategy: fail-fast: false @@ -22,12 +22,13 @@ jobs: - name: Check code format run: cargo fmt --all -- --check - name: Clippy - run: cargo clippy --target ${{ matrix.targets }} --all-features --workspace --exclude axplat-cli + run: | + cargo clippy --target ${{ matrix.targets }} --all-features -p axhal_cpu -p axhal_plat - name: Build - run: cargo build --target ${{ matrix.targets }} --all-features --workspace --exclude axplat-cli + run: cargo build --target ${{ matrix.targets }} --all-features -p axhal_cpu -p axhal_plat - name: Unit test if: ${{ matrix.targets == 'x86_64-unknown-linux-gnu' }} - run: cargo test --target ${{ matrix.targets }} -- --nocapture + run: cargo test --target ${{ matrix.targets }} --all-features -p axhal_cpu -p axhal_plat -- --nocapture cli: runs-on: ubuntu-latest @@ -43,6 +44,21 @@ jobs: - name: Build run: cargo build -p axplat-cli + platforms: + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly + with: + components: rust-src, clippy + targets: x86_64-unknown-none + - name: axplat-x86-pc + run: | + cargo clippy --target x86_64-unknown-none --all-features -p axplat-x86-pc + cargo build --target x86_64-unknown-none --all-features -p axplat-x86-pc + doc: runs-on: ubuntu-latest strategy: @@ -57,7 +73,7 @@ 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-cli + run: cargo doc --no-deps --all-features --workspace --exclude axplat-* - 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 4b17060..22d4f60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "aarch64-cpu" -version = "9.4.0" +version = "10.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac42a04a61c19fc8196dd728022a784baecc5d63d7e256c01ad1b3fbfab26287" +checksum = "6a21cd0131c25c438e19cd6a774adf7e3f64f7f4d723022882facc2dee0f8bc9" dependencies = [ "tock-registers", ] @@ -60,6 +60,34 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "axconfig-gen" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ffa518605969ff8f4ebce2cdc3b6090345152c14987ec540601335effbf36d5" +dependencies = [ + "clap", + "toml_edit", +] + +[[package]] +name = "axconfig-gen-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92110c7e7a5633d7fb8a402393c91c326ad6d19710bb9cfa5ab4095e63c25948" +dependencies = [ + "axconfig-gen", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "axhal_cpu" version = "0.1.0" @@ -74,7 +102,7 @@ dependencies = [ "static_assertions", "tock-registers", "x86", - "x86_64", + "x86_64 0.15.2", ] [[package]] @@ -106,6 +134,34 @@ dependencies = [ "toml_edit", ] +[[package]] +name = "axplat-x86-pc" +version = "0.1.0" +dependencies = [ + "axconfig-gen-macros", + "axhal_cpu", + "axhal_plat", + "bitflags 2.6.0", + "int_ratio", + "kspin", + "lazyinit", + "log", + "memory_addr", + "percpu", + "raw-cpuid 11.3.0", + "uart_16550", + "x2apic", + "x86", + "x86_64 0.15.2", + "x86_rtc", +] + +[[package]] +name = "bit" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b645c5c09a7d4035949cfce1a915785aaad6f17800c35fda8a8c311c491f284" + [[package]] name = "bit_field" version = "0.10.2" @@ -233,12 +289,44 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "int_ratio" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd361c344145620f0c02e56200ca3e3a45203121447376519a9070e546b2916f" + [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "kernel_guard" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "307e6be468f3d6b6d895e191f63c11602e4e76575ecca68325d8c8dbebe2870e" +dependencies = [ + "cfg-if", + "crate_interface", +] + +[[package]] +name = "kspin" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51954c939251c5899b6e953aa0ed8903c5c0d1140fc7ce3a8fd60c931d694f6e" +dependencies = [ + "cfg-if", + "kernel_guard", +] + +[[package]] +name = "lazyinit" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3861aac8febbb038673bf945ee47ac67940ca741b94d1bb3ff6066af2a181338" + [[package]] name = "linkme" version = "0.3.31" @@ -259,6 +347,16 @@ dependencies = [ "syn", ] +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.21" @@ -279,14 +377,14 @@ checksum = "6f769efcf10b9dfb4c913bebb409cda77b1a3f072b249bf5465e250bcb30eb49" [[package]] name = "page_table_entry" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069d6f06c815a18754ddf47c44a3b70ece3ce62ac092e078024cfcc0c12185f4" +checksum = "937b855b31ff3fa1274a5a4dfc1e57f124d26321adfa80ba03cdcc65921e8718" dependencies = [ "aarch64-cpu", "bitflags 2.6.0", "memory_addr", - "x86_64", + "x86_64 0.15.2", ] [[package]] @@ -295,6 +393,29 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "percpu" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01e56c0c558952222967b592899f98765b48590e7bd7403bfd7075f73afc6ed6" +dependencies = [ + "cfg-if", + "percpu_macros", + "spin", + "x86", +] + +[[package]] +name = "percpu_macros" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9f4cc54a2e471ff72f1499461ba381ad4eae9cbd60d29c258545b995e406e0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.92" @@ -322,6 +443,15 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "raw-cpuid" +version = "11.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6928fa44c097620b706542d428957635951bade7143269085389d42c8a4927e" +dependencies = [ + "bitflags 2.6.0", +] + [[package]] name = "riscv" version = "0.12.1" @@ -358,6 +488,21 @@ version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -383,9 +528,9 @@ dependencies = [ [[package]] name = "tock-registers" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "696941a0aee7e276a165a978b37918fd5d22c55c3d6bda197813070ca9c0f21c" +checksum = "2b9e2fdb3a1e862c0661768b7ed25390811df1947a8acbfbefe09b47078d93c4" [[package]] name = "toml_datetime" @@ -404,6 +549,17 @@ dependencies = [ "winnow", ] +[[package]] +name = "uart_16550" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e492212ac378a5e00da953718dafb1340d9fbaf4f27d6f3c5cab03d931d1c049" +dependencies = [ + "bitflags 2.6.0", + "rustversion", + "x86", +] + [[package]] name = "unicode-ident" version = "1.0.14" @@ -504,6 +660,19 @@ dependencies = [ "memchr", ] +[[package]] +name = "x2apic" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbcd582541cbb8ef1dfc24a3c849a64ff074b1b512af723ad90056558d424602" +dependencies = [ + "bit", + "bitflags 1.3.2", + "paste", + "raw-cpuid 10.7.0", + "x86_64 0.14.13", +] + [[package]] name = "x86" version = "0.52.0" @@ -512,7 +681,19 @@ checksum = "2781db97787217ad2a2845c396a5efe286f87467a5810836db6d74926e94a385" dependencies = [ "bit_field", "bitflags 1.3.2", - "raw-cpuid", + "raw-cpuid 10.7.0", +] + +[[package]] +name = "x86_64" +version = "0.14.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c101112411baafbb4bf8d33e4c4a80ab5b02d74d2612331c61e8192fc9710491" +dependencies = [ + "bit_field", + "bitflags 2.6.0", + "rustversion", + "volatile", ] [[package]] @@ -526,3 +707,13 @@ dependencies = [ "rustversion", "volatile", ] + +[[package]] +name = "x86_rtc" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1a42420da20c01d82e5d42231570efa3b9e16a5515eaaf9ee4e964f49cc1313" +dependencies = [ + "cfg-if", + "x86_64 0.15.2", +] diff --git a/Cargo.toml b/Cargo.toml index 7d71d18..96d0dea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ members = [ "axhal_plat", "axhal_plat_macros", "axplat-cli", + "platforms/axplat-x86-pc", ] [workspace.package] diff --git a/axhal_cpu/Cargo.toml b/axhal_cpu/Cargo.toml index e5b4115..3774350 100644 --- a/axhal_cpu/Cargo.toml +++ b/axhal_cpu/Cargo.toml @@ -29,8 +29,8 @@ x86 = "0.52" x86_64 = "0.15.2" [target.'cfg(target_arch = "aarch64")'.dependencies] -aarch64-cpu = "9.4" -tock-registers = "0.8" +aarch64-cpu = "10.0" +tock-registers = "0.9" [target.'cfg(any(target_arch = "riscv32", target_arch = "riscv64"))'.dependencies] riscv = "0.12" diff --git a/platforms/axplat-x86-pc/Cargo.toml b/platforms/axplat-x86-pc/Cargo.toml new file mode 100644 index 0000000..e6e7290 --- /dev/null +++ b/platforms/axplat-x86-pc/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "axplat-x86-pc" +version = "0.1.0" +edition.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +documentation.workspace = true +repository.workspace = true + +[features] +smp = [] +irq = [] +rtc = ["x86_rtc"] +fp_simd = [] +reboot-on-system-off = [] + +[dependencies] +kspin = "0.1" +log = "=0.4.21" +bitflags = "2.6" +lazyinit = "0.2" +int_ratio = "0.1" +percpu = "0.2" +memory_addr = "0.3" +axconfig-gen-macros = { version = "0.1", features = ["nightly"] } +axhal_cpu = { version = "0.1.0", path = "../../axhal_cpu" } +axhal_plat = { version = "0.1.0", path = "../../axhal_plat" } + +x86 = "0.52" +x86_64 = "0.15.2" +x2apic = "0.4" +raw-cpuid = "11.1" +uart_16550 = "0.3" +x86_rtc = { version = "0.1", optional = true } diff --git a/platforms/axplat-x86-pc/axconfig.toml b/platforms/axplat-x86-pc/axconfig.toml new file mode 100644 index 0000000..bf46b13 --- /dev/null +++ b/platforms/axplat-x86-pc/axconfig.toml @@ -0,0 +1,57 @@ +# Architecture identifier. +arch = "x86_64" # str +# Platform identifier. +platform = "x86-pc" # str + +# +# Platform configs +# +[plat] +# Platform family (deprecated). +family = "" # str + +# Base address of the whole physical memory. +phys-memory-base = 0 # uint +# Size of the whole physical memory. (128M) +phys-memory-size = 0x800_0000 # uint +# Base physical address of the kernel image. +kernel-base-paddr = 0x20_0000 # uint +# Base virtual address of the kernel image. +kernel-base-vaddr = "0xffff_8000_0020_0000" # uint +# Linear mapping offset, for quick conversions between physical and virtual +# addresses. +phys-virt-offset = "0xffff_8000_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_8000_0000_0000" # uint +# Kernel address space size. +kernel-aspace-size = "0x0000_7fff_ffff_f000" # uint +# Stack size on bootstrapping. (256K) +boot-stack-size = 0x40000 # uint + +# Device specifications +# +[devices] +# MMIO ranges with format (`base_paddr`, `size`). +mmio-ranges = [ + [0xb000_0000, 0x1000_0000], # PCI config space + [0xfe00_0000, 0xc0_0000], # PCI devices + [0xfec0_0000, 0x1000], # IO APIC + [0xfed0_0000, 0x1000], # HPET + [0xfee0_0000, 0x1000], # Local APIC +] # [(uint, uint)] +# VirtIO MMIO ranges with format (`base_paddr`, `size`). +virtio-mmio-ranges = [] # [(uint, uint)] +# Base physical address of the PCIe ECAM space (should read from ACPI 'MCFG' table). +pci-ecam-base = 0xb000_0000 # uint +# End PCI bus number. +pci-bus-end = 0xff # uint +# PCI device memory ranges (not used on x86). +pci-ranges = [] # [(uint, uint)] + +# Timer interrupt frequency in Hz. (4.0GHz) +timer-frequency = 4_000_000_000 # uint +# Timer interrupt num. +timer-irq = 0xf0 # uint diff --git a/platforms/axplat-x86-pc/src/apic.rs b/platforms/axplat-x86-pc/src/apic.rs index ca2a86c..fea8c98 100644 --- a/platforms/axplat-x86-pc/src/apic.rs +++ b/platforms/axplat-x86-pc/src/apic.rs @@ -1,4 +1,4 @@ -#![allow(dead_code)] +//! Advanced Programmable Interrupt Controller (APIC) support. use core::{cell::SyncUnsafeCell, mem::MaybeUninit}; @@ -6,7 +6,7 @@ use kspin::SpinNoIrq; use lazyinit::LazyInit; use memory_addr::PhysAddr; use x2apic::ioapic::IoApic; -use x2apic::lapic::{LocalApic, LocalApicBuilder, xapic_base}; +use x2apic::lapic::{xapic_base, LocalApic, LocalApicBuilder}; use x86_64::instructions::port::Port; use self::vectors::*; @@ -18,12 +18,6 @@ pub(super) mod vectors { pub const APIC_ERROR_VECTOR: u8 = 0xf2; } -/// The maximum number of IRQs. -pub const MAX_IRQ_COUNT: usize = 256; - -/// The timer IRQ number. -pub const TIMER_IRQ_NUM: usize = APIC_TIMER_VECTOR as usize; - const IO_APIC_BASE: PhysAddr = pa!(0xFEC0_0000); static LOCAL_APIC: SyncUnsafeCell> = @@ -46,32 +40,12 @@ pub fn set_enable(vector: 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. -#[cfg(feature = "irq")] -pub fn register_handler(vector: usize, handler: crate::irq::IrqHandler) -> bool { - crate::irq::register_handler_common(vector, handler) -} - -/// Dispatches the IRQ. -/// -/// This function is called by the common interrupt handler. It looks -/// up in the IRQ handler table and calls the corresponding handler. If -/// necessary, it also acknowledges the interrupt controller after handling. -#[cfg(feature = "irq")] -pub fn dispatch_irq(vector: usize) { - crate::irq::dispatch_irq_common(vector); - unsafe { local_apic().end_of_interrupt() }; -} - -pub(super) fn local_apic<'a>() -> &'a mut LocalApic { +pub fn local_apic<'a>() -> &'a mut LocalApic { // It's safe as `LOCAL_APIC` is initialized in `init_primary`. unsafe { LOCAL_APIC.get().as_mut().unwrap().assume_init_mut() } } -pub(super) fn raw_apic_id(id_u8: u8) -> u32 { +pub fn raw_apic_id(id_u8: u8) -> u32 { if unsafe { IS_X2APIC } { id_u8 as u32 } else { @@ -86,7 +60,7 @@ fn cpu_has_x2apic() -> bool { } } -pub(super) fn init_primary() { +pub fn init_primary() { info!("Initialize Local APIC..."); unsafe { @@ -122,6 +96,61 @@ pub(super) fn init_primary() { } #[cfg(feature = "smp")] -pub(super) fn init_secondary() { +pub fn init_secondary() { unsafe { local_apic().enable() }; } + +#[cfg(feature = "irq")] +mod irq_impl { + use axhal_plat::irq::{HandlerTable, IrqHandler, IrqIf}; + + /// The maximum number of IRQs. + const MAX_IRQ_COUNT: usize = 256; + + static IRQ_HANDLER_TABLE: HandlerTable = HandlerTable::new(); + + struct IrqIfImpl; + + #[impl_plat_interface] + impl IrqIf for IrqIfImpl { + /// Enables or disables the given IRQ. + fn set_enable(vector: usize, enabled: bool) { + super::set_enable(vector, 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(vector: usize, handler: IrqHandler) -> bool { + if IRQ_HANDLER_TABLE.register_handler(vector, handler) { + Self::set_enable(vector, true); + return true; + } + warn!("register handler for IRQ {} failed", vector); + 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. + fn unregister(vector: usize) -> Option { + Self::set_enable(vector, false); + IRQ_HANDLER_TABLE.unregister_handler(vector) + } + + /// 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) { + trace!("IRQ {}", vector); + if !IRQ_HANDLER_TABLE.handle(vector) { + warn!("Unhandled IRQ {}", vector); + } + unsafe { super::local_apic().end_of_interrupt() }; + } + } +} diff --git a/platforms/axplat-x86-pc/src/boot.rs b/platforms/axplat-x86-pc/src/boot.rs index 9147f23..5a39ebb 100644 --- a/platforms/axplat-x86-pc/src/boot.rs +++ b/platforms/axplat-x86-pc/src/boot.rs @@ -1,9 +1,11 @@ +//! Kernel booting using multiboot header. + use core::arch::global_asm; use x86_64::registers::control::{Cr0Flags, Cr4Flags}; use x86_64::registers::model_specific::EferFlags; -use axconfig::{TASK_STACK_SIZE, plat::PHYS_VIRT_OFFSET}; +use crate::config::plat::{BOOT_STACK_SIZE, PHYS_VIRT_OFFSET}; /// Flags set in the ’flags’ member of the multiboot header. /// @@ -31,18 +33,18 @@ const CR4: u64 = Cr4Flags::PHYSICAL_ADDRESS_EXTENSION.bits() const EFER: u64 = EferFlags::LONG_MODE_ENABLE.bits() | EferFlags::NO_EXECUTE_ENABLE.bits(); #[unsafe(link_section = ".bss.stack")] -static mut BOOT_STACK: [u8; TASK_STACK_SIZE] = [0; TASK_STACK_SIZE]; +static mut BOOT_STACK: [u8; BOOT_STACK_SIZE] = [0; BOOT_STACK_SIZE]; global_asm!( include_str!("multiboot.S"), mb_magic = const MULTIBOOT_BOOTLOADER_MAGIC, mb_hdr_magic = const MULTIBOOT_HEADER_MAGIC, mb_hdr_flags = const MULTIBOOT_HEADER_FLAGS, - entry = sym super::rust_entry, - entry_secondary = sym super::rust_entry_secondary, + entry = sym crate::rust_entry, + entry_secondary = sym crate::rust_entry_secondary, offset = const PHYS_VIRT_OFFSET, - boot_stack_size = const TASK_STACK_SIZE, + boot_stack_size = const BOOT_STACK_SIZE, boot_stack = sym BOOT_STACK, cr0 = const CR0, diff --git a/platforms/axplat-x86-pc/src/uart16550.rs b/platforms/axplat-x86-pc/src/console.rs similarity index 78% rename from platforms/axplat-x86-pc/src/uart16550.rs rename to platforms/axplat-x86-pc/src/console.rs index 66ee56f..617c168 100644 --- a/platforms/axplat-x86-pc/src/uart16550.rs +++ b/platforms/axplat-x86-pc/src/console.rs @@ -1,5 +1,6 @@ -//! Uart 16550. +//! Uart 16550 serial port. +use axhal_plat::console::ConsoleIf; use kspin::SpinNoIrq; use x86_64::instructions::port::{Port, PortReadOnly, PortWriteOnly}; @@ -84,7 +85,7 @@ impl Uart16550 { } /// Writes a byte to the console. -fn putchar(c: u8) { +pub fn putchar(c: u8) { let mut uart = COM1.lock(); match c { b'\n' => { @@ -96,32 +97,38 @@ fn putchar(c: u8) { } /// Reads a byte from the console, or returns [`None`] if no input is available. -fn getchar() -> Option { +pub fn getchar() -> Option { COM1.lock().getchar() } -/// Write a slice of bytes to the console. -pub fn write_bytes(bytes: &[u8]) { - for c in bytes { - putchar(*c); - } +pub fn init() { + COM1.lock().init(115200); } -/// 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; +struct ConsoleIfImpl; + +#[impl_plat_interface] +impl ConsoleIf for ConsoleIfImpl { + /// Writes given bytes to the console. + fn write_bytes(bytes: &[u8]) { + for c in bytes { + putchar(*c); } - read_len += 1; } - read_len -} -pub(super) fn init() { - COM1.lock().init(115200); + /// Reads bytes from the console into the given mutable slice. + /// + /// Returns the number of bytes read. + 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 + } } diff --git a/platforms/axplat-x86-pc/src/dtables.rs b/platforms/axplat-x86-pc/src/dtables.rs index a6af270..715a10f 100644 --- a/platforms/axplat-x86-pc/src/dtables.rs +++ b/platforms/axplat-x86-pc/src/dtables.rs @@ -1,6 +1,6 @@ //! Description tables (per-CPU GDT, per-CPU ISS, IDT) -use crate::arch::{GdtStruct, IdtStruct, TaskStateSegment}; +use axhal_cpu::{GdtStruct, IdtStruct, TaskStateSegment}; use lazyinit::LazyInit; static IDT: LazyInit = LazyInit::new(); @@ -12,6 +12,7 @@ static TSS: LazyInit = LazyInit::new(); static GDT: LazyInit = LazyInit::new(); fn init_percpu() { + percpu::init_percpu_reg(super::current_cpu_id()); unsafe { IDT.load(); let tss = TSS.current_ref_mut_raw(); @@ -24,14 +25,15 @@ fn init_percpu() { } /// Initializes IDT, GDT on the primary CPU. -pub(super) fn init_primary() { - axlog::ax_println!("\nInitialize IDT & GDT..."); +pub fn init_primary() { + axhal_plat::console_println!("\nInitialize IDT & GDT..."); + percpu::init(); IDT.init_once(IdtStruct::new()); init_percpu(); } /// Initializes IDT, GDT on secondary CPUs. #[cfg(feature = "smp")] -pub(super) fn init_secondary() { +pub fn init_secondary() { init_percpu(); } diff --git a/platforms/axplat-x86-pc/src/init.rs b/platforms/axplat-x86-pc/src/init.rs new file mode 100644 index 0000000..2795d6f --- /dev/null +++ b/platforms/axplat-x86-pc/src/init.rs @@ -0,0 +1,21 @@ +use axhal_plat::init::InitIf; + +struct InitIfImpl; + +#[impl_plat_interface] +impl InitIf for InitIfImpl { + /// Initializes the platform devices for the primary core. + fn platform_init() { + crate::apic::init_primary(); + crate::time::init_primary(); + } + + /// Initializes the platform devices for secondary cores. + fn platform_init_secondary() { + #[cfg(feature = "smp")] + { + crate::apic::init_secondary(); + crate::time::init_secondary(); + } + } +} diff --git a/platforms/axplat-x86-pc/src/lib.rs b/platforms/axplat-x86-pc/src/lib.rs new file mode 100644 index 0000000..3f01c34 --- /dev/null +++ b/platforms/axplat-x86-pc/src/lib.rs @@ -0,0 +1,51 @@ +#![no_std] +#![feature(sync_unsafe_cell)] + +#[macro_use] +extern crate log; +#[macro_use] +extern crate memory_addr; +#[macro_use] +extern crate axhal_plat; + +mod apic; +mod boot; +mod console; +mod dtables; +mod init; +mod mem; +mod power; +mod time; + +#[cfg(feature = "smp")] +mod mp; + +mod config { + axconfig_gen_macros::include_configs!("axconfig.toml"); +} + +fn current_cpu_id() -> usize { + match raw_cpuid::CpuId::new().get_feature_info() { + Some(finfo) => finfo.initial_local_apic_id() as usize, + None => 0, + } +} + +unsafe extern "C" fn rust_entry(magic: usize, _mbi: usize) { + // TODO: handle multiboot info + if magic == self::boot::MULTIBOOT_BOOTLOADER_MAGIC { + axhal_plat::mem::clear_bss(); + self::console::init(); + self::dtables::init_primary(); + self::time::init_early(); + axhal_plat::call_main(current_cpu_id(), 0); + } +} + +unsafe extern "C" fn rust_entry_secondary(magic: usize) { + #[cfg(feature = "smp")] + if magic == self::boot::MULTIBOOT_BOOTLOADER_MAGIC { + self::dtables::init_secondary(); + axhal_plat::call_secondary_main(current_cpu_id()); + } +} diff --git a/platforms/axplat-x86-pc/src/mem.rs b/platforms/axplat-x86-pc/src/mem.rs index a321c77..ac0543c 100644 --- a/platforms/axplat-x86-pc/src/mem.rs +++ b/platforms/axplat-x86-pc/src/mem.rs @@ -1,15 +1,33 @@ -// TODO: get memory regions from multiboot info. - -use crate::mem::{MemRegion, MemRegionFlags}; - -/// Returns platform-specific memory regions. -pub(crate) fn platform_regions() -> impl Iterator { - core::iter::once(MemRegion { - paddr: pa!(0x1000), - size: 0x9e000, - flags: MemRegionFlags::RESERVED | MemRegionFlags::READ | MemRegionFlags::WRITE, - name: "low memory", - }) - .chain(crate::mem::default_free_regions()) - .chain(crate::mem::default_mmio_regions()) +//! Physical memory information. + +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}; + +pub const fn phys_to_virt(paddr: PhysAddr) -> VirtAddr { + va!(paddr.as_usize() + PHYS_VIRT_OFFSET) +} + +struct MemIfImpl; + +#[impl_plat_interface] +impl MemIf for MemIfImpl { + /// Returns all physical memory (RAM) ranges on the platform. + fn phys_ram_ranges() -> &'static [RawRange] { + &[(PHYS_MEMORY_BASE, PHYS_MEMORY_SIZE)] + } + + /// Returns all reserved physical memory ranges on the platform. + /// + /// Lower 1MiB memory is reserved and not allocatable. + fn reserved_phys_ram_ranges() -> &'static [RawRange] { + &[(0, 0x100000)] + } + + /// Returns all device memory (MMIO) ranges on the platform. + fn mmio_ranges() -> &'static [RawRange] { + &MMIO_RANGES + } } diff --git a/platforms/axplat-x86-pc/src/misc.rs b/platforms/axplat-x86-pc/src/misc.rs deleted file mode 100644 index 788a1ac..0000000 --- a/platforms/axplat-x86-pc/src/misc.rs +++ /dev/null @@ -1,28 +0,0 @@ -use x86_64::instructions::port::PortWriteOnly; - -/// Shutdown the whole system (in QEMU), including all CPUs. -/// -/// See for more information. -pub fn terminate() -> ! { - info!("Shutting down..."); - - #[cfg(platform = "x86_64-pc-oslab")] - { - axlog::ax_println!("System will reboot, press any key to continue ..."); - let mut buffer = [0u8; 1]; - while super::console::read_bytes(&mut buffer) == 0 {} - axlog::ax_println!("Rebooting ..."); - unsafe { PortWriteOnly::new(0x64).write(0xfeu8) }; - } - - #[cfg(platform = "x86_64-qemu-q35")] - unsafe { - PortWriteOnly::new(0x604).write(0x2000u16) - }; - - crate::arch::halt(); - warn!("It should shutdown!"); - loop { - crate::arch::halt(); - } -} diff --git a/platforms/axplat-x86-pc/src/mod.rs b/platforms/axplat-x86-pc/src/mod.rs deleted file mode 100644 index e39bc7e..0000000 --- a/platforms/axplat-x86-pc/src/mod.rs +++ /dev/null @@ -1,68 +0,0 @@ -mod apic; -mod boot; -mod dtables; -mod uart16550; - -pub mod mem; -pub mod misc; -pub mod time; - -#[cfg(feature = "smp")] -pub mod mp; - -#[cfg(feature = "irq")] -pub mod irq { - pub use super::apic::*; -} - -pub mod console { - pub use super::uart16550::*; -} - -unsafe extern "C" { - fn rust_main(cpu_id: usize, dtb: usize) -> !; - #[cfg(feature = "smp")] - fn rust_main_secondary(cpu_id: usize) -> !; -} - -fn current_cpu_id() -> usize { - match raw_cpuid::CpuId::new().get_feature_info() { - Some(finfo) => finfo.initial_local_apic_id() as usize, - None => 0, - } -} - -unsafe extern "C" fn rust_entry(magic: usize, _mbi: usize) { - // TODO: handle multiboot info - if magic == self::boot::MULTIBOOT_BOOTLOADER_MAGIC { - crate::mem::clear_bss(); - crate::cpu::init_primary(current_cpu_id()); - self::uart16550::init(); - self::dtables::init_primary(); - self::time::init_early(); - rust_main(current_cpu_id(), 0); - } -} - -#[allow(unused_variables)] -unsafe extern "C" fn rust_entry_secondary(magic: usize) { - #[cfg(feature = "smp")] - if magic == self::boot::MULTIBOOT_BOOTLOADER_MAGIC { - crate::cpu::init_secondary(current_cpu_id()); - self::dtables::init_secondary(); - rust_main_secondary(current_cpu_id()); - } -} - -/// Initializes the platform devices for the primary CPU. -pub fn platform_init() { - self::apic::init_primary(); - self::time::init_primary(); -} - -/// Initializes the platform devices for secondary CPUs. -#[cfg(feature = "smp")] -pub fn platform_init_secondary() { - self::apic::init_secondary(); - self::time::init_secondary(); -} diff --git a/platforms/axplat-x86-pc/src/mp.rs b/platforms/axplat-x86-pc/src/mp.rs index 04b1060..2de60b3 100644 --- a/platforms/axplat-x86-pc/src/mp.rs +++ b/platforms/axplat-x86-pc/src/mp.rs @@ -1,5 +1,7 @@ -use crate::mem::{PAGE_SIZE_4K, PhysAddr, phys_to_virt}; -use crate::time::{Duration, busy_wait}; +//! Multi-processor booting. + +use axhal_plat::time::{busy_wait, Duration}; +use memory_addr::{PhysAddr, PAGE_SIZE_4K}; const START_PAGE_IDX: u8 = 6; const START_PAGE_PADDR: PhysAddr = pa!(START_PAGE_IDX as usize * PAGE_SIZE_4K); @@ -17,7 +19,7 @@ unsafe fn setup_startup_page(stack_top: PhysAddr) { } const U64_PER_PAGE: usize = PAGE_SIZE_4K / 8; - let start_page_ptr = phys_to_virt(START_PAGE_PADDR).as_mut_ptr() as *mut u64; + let start_page_ptr = crate::mem::phys_to_virt(START_PAGE_PADDR).as_mut_ptr() as *mut u64; let start_page = core::slice::from_raw_parts_mut(start_page_ptr, U64_PER_PAGE); core::ptr::copy_nonoverlapping( ap_start as *const u64, diff --git a/platforms/axplat-x86-pc/src/power.rs b/platforms/axplat-x86-pc/src/power.rs new file mode 100644 index 0000000..c47f543 --- /dev/null +++ b/platforms/axplat-x86-pc/src/power.rs @@ -0,0 +1,43 @@ +//! Power management. + +use axhal_plat::power::PowerIf; +use x86_64::instructions::port::PortWriteOnly; + +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")] + crate::mp::start_secondary_cpu(cpu_id, pa!(stack_top_paddr)); + } + + /// Shutdown the whole system (in QEMU). + /// + /// See for more information. + fn system_off() -> ! { + info!("Shutting down..."); + + // For real hardware platforms, using port `0x604` to shutdown does not + // work. Therefore we use port `0x64` to reboot the system instead. + if cfg!(feature = "reboot-on-system-off") { + axhal_plat::console_println!("System will reboot, press any key to continue ..."); + while super::console::getchar().is_none() {} + axhal_plat::console_println!("Rebooting ..."); + unsafe { PortWriteOnly::new(0x64).write(0xfeu8) }; + } else { + unsafe { PortWriteOnly::new(0x604).write(0x2000u16) }; + } + + axhal_cpu::halt(); + warn!("It should shutdown!"); + loop { + axhal_cpu::halt(); + } + } +} diff --git a/platforms/axplat-x86-pc/src/time.rs b/platforms/axplat-x86-pc/src/time.rs index 8949da3..1b373bc 100644 --- a/platforms/axplat-x86-pc/src/time.rs +++ b/platforms/axplat-x86-pc/src/time.rs @@ -1,3 +1,8 @@ +//! Time management. +//! +//! Currently, the TSC is used as the clock source. + +use axhal_plat::time::TimeIf; use raw_cpuid::CpuId; #[cfg(feature = "irq")] @@ -10,60 +15,23 @@ const LAPIC_TICKS_PER_SEC: u64 = 1_000_000_000; // TODO: need to calibrate static mut NANOS_TO_LAPIC_TICKS_RATIO: Ratio = Ratio::zero(); static mut INIT_TICK: u64 = 0; -static mut CPU_FREQ_MHZ: u64 = axconfig::devices::TIMER_FREQUENCY as u64 / 1_000_000; +static mut CPU_FREQ_MHZ: u64 = crate::config::devices::TIMER_FREQUENCY as u64 / 1_000_000; /// RTC wall time offset in nanoseconds at monotonic time base. static mut RTC_EPOCHOFFSET_NANOS: u64 = 0; -/// Returns the current clock time in hardware ticks. -pub fn current_ticks() -> u64 { - unsafe { core::arch::x86_64::_rdtsc() - INIT_TICK } -} - -/// Converts hardware ticks to nanoseconds. -pub fn ticks_to_nanos(ticks: u64) -> u64 { - ticks * 1_000 / unsafe { CPU_FREQ_MHZ } -} - -/// Converts nanoseconds to hardware ticks. -pub fn nanos_to_ticks(nanos: u64) -> u64 { - nanos * unsafe { CPU_FREQ_MHZ } / 1_000 -} - -/// Return epoch offset in nanoseconds (wall time offset to monotonic clock start). -pub fn epochoffset_nanos() -> u64 { - unsafe { RTC_EPOCHOFFSET_NANOS } -} - -/// Set a one-shot timer. -/// -/// A timer interrupt will be triggered at the specified monotonic time deadline (in nanoseconds). -#[cfg(feature = "irq")] -pub fn set_oneshot_timer(deadline_ns: u64) { - let lapic = super::apic::local_apic(); - let now_ns = crate::time::monotonic_time_nanos(); - unsafe { - if now_ns < deadline_ns { - let apic_ticks = NANOS_TO_LAPIC_TICKS_RATIO.mul_trunc(deadline_ns - now_ns); - assert!(apic_ticks <= u32::MAX as u64); - lapic.set_timer_initial(apic_ticks.max(1) as u32); - } else { - lapic.set_timer_initial(1); - } - } -} - -pub(super) fn init_early() { +pub fn init_early() { if let Some(freq) = CpuId::new() .get_processor_frequency_info() .map(|info| info.processor_base_frequency()) { if freq > 0 { - axlog::ax_println!("Got TSC frequency by CPUID: {} MHz", freq); unsafe { CPU_FREQ_MHZ = freq as u64 } } } + axhal_plat::console_println!("TSC frequency: {} MHz", unsafe { CPU_FREQ_MHZ }); + unsafe { INIT_TICK = core::arch::x86_64::_rdtsc(); } @@ -81,7 +49,7 @@ pub(super) fn init_early() { } } -pub(super) fn init_primary() { +pub fn init_primary() { #[cfg(feature = "irq")] unsafe { use x2apic::lapic::{TimerDivide, TimerMode}; @@ -93,15 +61,62 @@ pub(super) fn init_primary() { // TODO: calibrate with HPET NANOS_TO_LAPIC_TICKS_RATIO = Ratio::new( LAPIC_TICKS_PER_SEC as u32, - crate::time::NANOS_PER_SEC as u32, + axhal_plat::time::NANOS_PER_SEC as u32, ); } } #[cfg(feature = "smp")] -pub(super) fn init_secondary() { +pub fn init_secondary() { #[cfg(feature = "irq")] unsafe { - super::apic::local_apic().enable_timer(); + crate::apic::local_apic().enable_timer(); + } +} + +struct TimeIfImpl; + +#[impl_plat_interface] +impl TimeIf for TimeIfImpl { + /// Returns the current clock time in hardware ticks. + fn current_ticks() -> u64 { + unsafe { core::arch::x86_64::_rdtsc() - INIT_TICK } + } + + /// Converts hardware ticks to nanoseconds. + fn ticks_to_nanos(ticks: u64) -> u64 { + ticks * 1_000 / unsafe { CPU_FREQ_MHZ } + } + + /// Converts nanoseconds to hardware ticks. + fn nanos_to_ticks(nanos: u64) -> u64 { + nanos * unsafe { CPU_FREQ_MHZ } / 1_000 + } + + /// Return epoch offset in nanoseconds (wall time offset to monotonic + /// clock start). + fn epochoffset_nanos() -> u64 { + unsafe { RTC_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) { + #[cfg(feature = "irq")] + { + let lapic = super::apic::local_apic(); + let now_ns = Self::ticks_to_nanos(Self::current_ticks()); + unsafe { + if now_ns < deadline_ns { + let apic_ticks = NANOS_TO_LAPIC_TICKS_RATIO.mul_trunc(deadline_ns - now_ns); + assert!(apic_ticks <= u32::MAX as u64); + lapic.set_timer_initial(apic_ticks.max(1) as u32); + } else { + lapic.set_timer_initial(1); + } + } + } } }