diff --git a/Cargo.lock b/Cargo.lock index b71a7915..0c12ac54 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -224,6 +224,22 @@ dependencies = [ "chrono", ] +[[package]] +name = "arm_vcpu" +version = "0.1.1" +dependencies = [ + "aarch64-cpu", + "axaddrspace", + "axdevice_base", + "axerrno", + "axvcpu 0.1.2 (git+https://github.com/arceos-hypervisor/axvcpu.git?branch=next)", + "axvisor_api", + "log", + "numeric-enum-macro", + "percpu", + "spin 0.10.0", +] + [[package]] name = "arm_vcpu" version = "0.1.1" @@ -233,7 +249,7 @@ dependencies = [ "axaddrspace", "axdevice_base", "axerrno", - "axvcpu", + "axvcpu 0.1.2 (git+https://github.com/arceos-hypervisor/axvcpu.git?branch=next)", "axvisor_api", "log", "numeric-enum-macro", @@ -241,6 +257,23 @@ dependencies = [ "spin 0.10.0", ] +[[package]] +name = "arm_vgic" +version = "0.1.0" +dependencies = [ + "aarch64-cpu", + "aarch64_sysreg", + "axaddrspace", + "axdevice_base", + "axerrno", + "axvisor_api", + "bitmaps", + "log", + "memory_addr", + "spin 0.9.8", + "tock-registers 0.10.1", +] + [[package]] name = "arm_vgic" version = "0.1.0" @@ -398,6 +431,22 @@ dependencies = [ "x86_64", ] +[[package]] +name = "axdevice" +version = "0.1.0" +dependencies = [ + "arm_vgic 0.1.0 (git+https://github.com/arceos-hypervisor/arm_vgic.git)", + "axaddrspace", + "axdevice_base", + "axerrno", + "axvmconfig", + "cfg-if", + "log", + "memory_addr", + "range-alloc", + "spin 0.9.8", +] + [[package]] name = "axdevice" version = "0.1.0" @@ -903,14 +952,29 @@ dependencies = [ "x2apic", "x86", "x86_64", - "x86_rtc", ] [[package]] name = "axplat-x86-qemu-q35" version = "0.1.0" dependencies = [ - "axplat-x86-pc", + "axconfig-macros", + "axcpu", + "axplat 0.2.0 (git+https://github.com/arceos-hypervisor/axplat_crates.git?tag=vmm-v0.3.0)", + "bitflags 2.10.0", + "heapless 0.9.2", + "int_ratio", + "kspin", + "lazyinit", + "log", + "multiboot", + "percpu", + "raw-cpuid 11.6.0", + "uart_16550", + "x2apic", + "x86", + "x86_64", + "x86_rtc", ] [[package]] @@ -1045,6 +1109,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "axvcpu" +version = "0.1.2" +dependencies = [ + "axaddrspace", + "axerrno", + "axvisor_api", + "memory_addr", + "percpu", +] + [[package]] name = "axvcpu" version = "0.1.2" @@ -1066,15 +1141,15 @@ dependencies = [ "arm-gic-driver", "axaddrspace", "axconfig", - "axdevice", + "axdevice 0.1.0 (git+https://github.com/arceos-hypervisor/axdevice.git)", "axdevice_base", "axerrno", "axhvc", "axruntime", "axstd", - "axvcpu", + "axvcpu 0.1.2 (git+https://github.com/arceos-hypervisor/axvcpu.git?branch=next)", "axvisor_api", - "axvm", + "axvm 0.1.0 (git+https://github.com/arceos-hypervisor/axvm.git?branch=next)", "bitflags 2.10.0", "byte-unit", "cfg-if", @@ -1126,18 +1201,42 @@ dependencies = [ "syn 2.0.110", ] +[[package]] +name = "axvm" +version = "0.1.0" +dependencies = [ + "arm_vcpu 0.1.1 (git+https://github.com/arceos-hypervisor/arm_vcpu?branch=next)", + "arm_vgic 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "axaddrspace", + "axdevice 0.1.0 (git+https://github.com/arceos-hypervisor/axdevice.git)", + "axdevice_base", + "axerrno", + "axvcpu 0.1.2 (git+https://github.com/arceos-hypervisor/axvcpu.git?branch=next)", + "axvmconfig", + "cfg-if", + "cpumask", + "log", + "memory_addr", + "page_table_entry", + "page_table_multiarch", + "percpu", + "riscv_vcpu", + "spin 0.9.8", + "x86_vcpu", +] + [[package]] name = "axvm" version = "0.1.0" source = "git+https://github.com/arceos-hypervisor/axvm.git?branch=next#0393f27dea948433e53285a400e356cdfd4c4fa3" dependencies = [ - "arm_vcpu", + "arm_vcpu 0.1.1 (git+https://github.com/arceos-hypervisor/arm_vcpu?branch=next)", "arm_vgic 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "axaddrspace", - "axdevice", + "axdevice 0.1.0 (git+https://github.com/arceos-hypervisor/axdevice.git)", "axdevice_base", "axerrno", - "axvcpu", + "axvcpu 0.1.2 (git+https://github.com/arceos-hypervisor/axvcpu.git?branch=next)", "axvmconfig", "cfg-if", "cpumask", @@ -4507,7 +4606,7 @@ checksum = "13f38f28fe6c02bb3ced43087c9667b23d18adf729becdc5adf1253f7df83904" dependencies = [ "axaddrspace", "axerrno", - "axvcpu", + "axvcpu 0.1.2 (git+https://github.com/arceos-hypervisor/axvcpu.git?branch=next)", "axvisor_api", "bit_field", "bitflags 2.10.0", @@ -6521,7 +6620,7 @@ dependencies = [ "axaddrspace", "axdevice_base", "axerrno", - "axvcpu", + "axvcpu 0.1.2 (git+https://github.com/arceos-hypervisor/axvcpu.git?branch=next)", "axvisor_api", "bit_field", "bitflags 2.10.0", diff --git a/Cargo.toml b/Cargo.toml index 6e09e36b..23ef20f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,8 +38,6 @@ axstd = {git = "https://github.com/arceos-hypervisor/arceos.git", tag = "hv-0.4. "irq", "multitask", "smp", # "page-alloc-64g", - "myplat", - "driver-dyn", ]} axalloc = {git = "https://github.com/arceos-hypervisor/arceos.git", tag = "hv-0.4.1"} diff --git a/configs/board/orangepi-5-plus.toml b/configs/board/orangepi-5-plus.toml index 9837e1c3..21559ce5 100644 --- a/configs/board/orangepi-5-plus.toml +++ b/configs/board/orangepi-5-plus.toml @@ -1,13 +1,13 @@ cargo_args = [] features = [ - # "ept-level-4", - "axstd/myplat", - "axstd/bus-mmio", - "driver/sdmmc", - "driver/rk3588-clk", - "fs", + # "ept-level-4", + "dyn-plat", + "axstd/bus-mmio", + "driver/sdmmc", + "driver/rk3588-clk", + "fs", ] log = "Info" target = "aarch64-unknown-none-softfloat" to_bin = true -vm_configs = [] \ No newline at end of file +vm_configs = [] diff --git a/configs/board/phytiumpi.toml b/configs/board/phytiumpi.toml index b46fe6a5..b6ffe3e5 100644 --- a/configs/board/phytiumpi.toml +++ b/configs/board/phytiumpi.toml @@ -1,7 +1,7 @@ cargo_args = [] features = [ # "ept-level-4", - "axstd/myplat", + "dyn-plat", "axstd/bus-mmio", ] log = "Info" diff --git a/configs/board/qemu-aarch64.toml b/configs/board/qemu-aarch64.toml index 0fa5abd3..c379cc79 100644 --- a/configs/board/qemu-aarch64.toml +++ b/configs/board/qemu-aarch64.toml @@ -1,8 +1,8 @@ cargo_args = [] features = [ "ept-level-4", - "axstd/myplat", "axstd/bus-mmio", + "dyn-plat", ] log = "Info" target = "aarch64-unknown-none-softfloat" diff --git a/configs/board/qemu-x86_64.toml b/configs/board/qemu-x86_64.toml new file mode 100644 index 00000000..2f15df4b --- /dev/null +++ b/configs/board/qemu-x86_64.toml @@ -0,0 +1,8 @@ +cargo_args = [] +features = [ + "ept-level-4", +] +log = "Info" +target = "x86_64-unknown-none" +to_bin = false +vm_configs = [] \ No newline at end of file diff --git a/configs/board/roc-rk3568-pc.toml b/configs/board/roc-rk3568-pc.toml index b46fe6a5..b6ffe3e5 100644 --- a/configs/board/roc-rk3568-pc.toml +++ b/configs/board/roc-rk3568-pc.toml @@ -1,7 +1,7 @@ cargo_args = [] features = [ # "ept-level-4", - "axstd/myplat", + "dyn-plat", "axstd/bus-mmio", ] log = "Info" diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index a6404938..6361f639 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -7,6 +7,7 @@ name = "axvisor" [features] ept-level-4 = ["axaddrspace/4-level-ept"] fs = ["axstd/fs", "axruntime/fs"] +dyn-plat = ["axstd/myplat", "axstd/driver-dyn", "axruntime/driver-dyn"] [dependencies] bitflags.workspace = true @@ -27,14 +28,12 @@ axstd = {workspace = true, features = [ "irq", "multitask", "smp", - "myplat", - "driver-dyn", ]} # System dependent modules provided by ArceOS-Hypervisor. axaddrspace.workspace = true axhvc.workspace = true -axruntime = {workspace = true, features = ["alloc", "irq", "paging", "smp", "driver-dyn", "multitask"]} +axruntime = {workspace = true, features = ["alloc", "irq", "paging", "smp", "multitask"]} axvcpu.workspace = true axvm.workspace = true diff --git a/kernel/build.rs b/kernel/build.rs index 79f1f2c2..711c372e 100644 --- a/kernel/build.rs +++ b/kernel/build.rs @@ -26,7 +26,7 @@ use std::{ env, ffi::OsString, fs, - io::{self, Write}, + io::Write, path::{Path, PathBuf}, }; @@ -239,10 +239,6 @@ fn generate_guest_img_loading_functions( fn main() -> anyhow::Result<()> { let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap(); - let mut smp = None; - if let Ok(s) = std::env::var("AXVISOR_SMP") { - smp = Some(s.parse::().unwrap_or(1)); - } // let platform = env::var("AX_PLATFORM").unwrap_or("".to_string()); @@ -256,10 +252,6 @@ fn main() -> anyhow::Result<()> { println!("cargo:rustc-cfg=platform=\"{platform}\""); - if platform != "dummy" { - gen_linker_script(&arch, platform.as_str(), smp.unwrap_or(1)).unwrap(); - } - let config_files = get_configs(); let mut output_file = open_output_file(); @@ -298,27 +290,3 @@ fn main() -> anyhow::Result<()> { } Ok(()) } - -fn gen_linker_script(arch: &str, platform: &str, smp: usize) -> io::Result<()> { - let fname = format!("linker_{platform}.lds"); - let output_arch = if arch == "x86_64" { - "i386:x86-64" - } else if arch.contains("riscv") { - "riscv" // OUTPUT_ARCH of both riscv32/riscv64 is "riscv" - } else { - arch - }; - let ld_content = std::fs::read_to_string("../scripts/lds/linker.lds.S")?; - let ld_content = ld_content.replace("%ARCH%", output_arch); - let ld_content = ld_content.replace("%KERNEL_BASE%", &format!("{:#x}", 0x800000000000usize)); - let ld_content = ld_content.replace("%SMP%", &format!("{smp}",)); - - // target///build/axvisor-xxxx/out - let out_dir = std::env::var("OUT_DIR").unwrap(); - // target///linker_xxxx.lds - let out_path = Path::new(&out_dir).join("../../../").join(fname); - - println!("writing linker script to {}", out_path.display()); - std::fs::write(out_path, ld_content)?; - Ok(()) -} diff --git a/platform/x86-qemu-q35/Cargo.toml b/platform/x86-qemu-q35/Cargo.toml index f8f2dc8a..d2860462 100644 --- a/platform/x86-qemu-q35/Cargo.toml +++ b/platform/x86-qemu-q35/Cargo.toml @@ -4,8 +4,36 @@ name = "axplat-x86-qemu-q35" version = "0.1.0" [features] -fp-simd = ["axplat-x86-pc/fp-simd"] -rtc = ["axplat-x86-pc/x86_rtc"] +default = [ + "irq", + "smp", + "reboot-on-system-off", +] +fp-simd = [] +irq = ["axplat/irq"] +reboot-on-system-off = [] +rtc = ["dep:x86_rtc"] +smp = ["axplat/smp", "kspin/smp"] [dependencies] -axplat-x86-pc = {git = "https://github.com/arceos-hypervisor/axplat_crates.git", tag = "vmm-v0.3.0", features = ["irq", "smp"]} +axconfig-macros = "0.2" +axcpu = "0.2" +axplat = {workspace = true} +bitflags = "2.6" +heapless = "0.9" +int_ratio = "0.1" +kspin = "0.1" +lazyinit = "0.2" +log = "0.4" +percpu = "0.2" + +multiboot = "0.8" +raw-cpuid = "11.5" +uart_16550 = "0.4" +x2apic = "0.5" +x86 = "0.52" +x86_64 = "0.15.2" +x86_rtc = {version = "0.1", optional = true} + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-none"] diff --git a/platform/x86-qemu-q35/axconfig.toml b/platform/x86-qemu-q35/axconfig.toml deleted file mode 100644 index 4ed16f84..00000000 --- a/platform/x86-qemu-q35/axconfig.toml +++ /dev/null @@ -1,62 +0,0 @@ -# Architecture identifier. -arch = "x86_64" # str -# Platform identifier. -platform = "x86-qemu-q35" # str -# Platform Package. -package = "axplat-x86-qemu-q35" # str - -# -# Platform configs -# -[plat] -# Number of CPUs. -cpu-num = 1 # uint -# 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 regions 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 - [0x70_0000_0000, 0x4000], # PCI devices - [0x3800_0000_0000, 0x4000] # PCI devices -] # [(uint, uint)] - -# VirtIO MMIO regions 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 frequencyin Hz. (4.0GHz) -timer-frequency = 4_000_000_000 # uint -# Timer interrupt num. -timer-irq = 0xf0 # uint diff --git a/platform/x86-qemu-q35/build.rs b/platform/x86-qemu-q35/build.rs index da0f1ed8..04702fee 100644 --- a/platform/x86-qemu-q35/build.rs +++ b/platform/x86-qemu-q35/build.rs @@ -9,12 +9,13 @@ fn main() { let ld_content = include_str!("linker.lds.S"); let ld_content = ld_content.replace("%ARCH%", "i386:x86-64"); - let ld_content = ld_content.replace("%KERNEL_BASE%", &format!("{:#x}", 0x800000000000usize)); + let ld_content = + ld_content.replace("%KERNEL_BASE%", &format!("{:#x}", 0xffff800000200000usize)); let ld_content = ld_content.replace("%SMP%", &format!("{smp}",)); // target///build/axvisor-xxxx/out let out_dir = std::env::var("OUT_DIR").unwrap(); let out_path = std::path::Path::new(&out_dir).join("link.x"); - + println!("cargo:rustc-link-search={out_dir}"); std::fs::write(out_path, ld_content).unwrap(); } diff --git a/platform/x86-qemu-q35/linker.lds.S b/platform/x86-qemu-q35/linker.lds.S index 68095208..439284c7 100644 --- a/platform/x86-qemu-q35/linker.lds.S +++ b/platform/x86-qemu-q35/linker.lds.S @@ -1,7 +1,7 @@ OUTPUT_ARCH(%ARCH%) BASE_ADDRESS = %KERNEL_BASE%; -SMP = %SMP% +SMP = %SMP%; ENTRY(_start) SECTIONS @@ -30,6 +30,10 @@ SECTIONS _sdata = .; *(.data.boot_page_table) . = ALIGN(4K); + __sdriver_register = .; + KEEP(*(.driver.register*)) + __edriver_register = .; + *(.data .data.*) *(.sdata .sdata.*) *(.got .got.*) diff --git a/platform/x86-qemu-q35/src/ap_start.S b/platform/x86-qemu-q35/src/ap_start.S new file mode 100644 index 00000000..2af322b0 --- /dev/null +++ b/platform/x86-qemu-q35/src/ap_start.S @@ -0,0 +1,70 @@ +# Boot application processors into the protected mode. + +# Each non-boot CPU ("AP") is started up in response to a STARTUP +# IPI from the boot CPU. Section B.4.2 of the Multi-Processor +# Specification says that the AP will start in real mode with CS:IP +# set to XY00:0000, where XY is an 8-bit value sent with the +# STARTUP. Thus this code must start at a 4096-byte boundary. +# +# Because this code sets DS to zero, it must sit +# at an address in the low 2^16 bytes. + +.equ pa_ap_start32, ap_start32 - ap_start + {start_page_paddr} +.equ pa_ap_gdt, .Lap_tmp_gdt - ap_start + {start_page_paddr} +.equ pa_ap_gdt_desc, .Lap_tmp_gdt_desc - ap_start + {start_page_paddr} + +.equ stack_ptr, {start_page_paddr} + 0xff0 +.equ entry_ptr, {start_page_paddr} + 0xff8 + +# 0x6000 +.section .text +.code16 +.p2align 12 +.global ap_start +ap_start: + cli + wbinvd + + xor ax, ax + mov ds, ax + mov es, ax + mov ss, ax + mov fs, ax + mov gs, ax + + # load the 64-bit GDT + lgdt [pa_ap_gdt_desc] + + # switch to protected-mode + mov eax, cr0 + or eax, (1 << 0) + mov cr0, eax + + # far jump to 32-bit code. 0x8 is code32 segment selector + ljmp 0x8, offset pa_ap_start32 + +.code32 +ap_start32: + mov esp, [stack_ptr] + mov eax, [entry_ptr] + jmp eax + +.balign 8 +# .type multiboot_header, STT_OBJECT +.Lap_tmp_gdt_desc: + .short .Lap_tmp_gdt_end - .Lap_tmp_gdt - 1 # limit + .long pa_ap_gdt # base + +.balign 16 +.Lap_tmp_gdt: + .quad 0x0000000000000000 # 0x00: null + .quad 0x00cf9b000000ffff # 0x08: code segment (base=0, limit=0xfffff, type=32bit code exec/read, DPL=0, 4k) + .quad 0x00af9b000000ffff # 0x10: code segment (base=0, limit=0xfffff, type=64bit code exec/read, DPL=0, 4k) + .quad 0x00cf93000000ffff # 0x18: data segment (base=0, limit=0xfffff, type=32bit data read/write, DPL=0, 4k) +.Lap_tmp_gdt_end: + +.code64 +# 0x7000 +.p2align 12 +.global ap_end +ap_end: diff --git a/platform/x86-qemu-q35/src/apic.rs b/platform/x86-qemu-q35/src/apic.rs new file mode 100644 index 00000000..e7b342c7 --- /dev/null +++ b/platform/x86-qemu-q35/src/apic.rs @@ -0,0 +1,184 @@ +//! Advanced Programmable Interrupt Controller (APIC) support. + +use core::mem::MaybeUninit; + +use axplat::mem::{PhysAddr, pa, phys_to_virt}; +use kspin::SpinNoIrq; +use lazyinit::LazyInit; +use x2apic::ioapic::IoApic; +use x2apic::lapic::{LocalApic, LocalApicBuilder, xapic_base}; +use x86_64::instructions::port::Port; + +use self::vectors::*; + +pub(super) mod vectors { + pub const APIC_TIMER_VECTOR: u8 = 0xf0; + pub const APIC_SPURIOUS_VECTOR: u8 = 0xf1; + pub const APIC_ERROR_VECTOR: u8 = 0xf2; +} + +const IO_APIC_BASE: PhysAddr = pa!(0xFEC0_0000); + +static mut LOCAL_APIC: MaybeUninit = MaybeUninit::uninit(); +static mut IS_X2APIC: bool = false; +static IO_APIC: LazyInit> = LazyInit::new(); + +/// Enables or disables the given IRQ. +#[cfg(feature = "irq")] +pub fn set_enable(vector: usize, enabled: bool) { + // should not affect LAPIC interrupts + if vector < APIC_TIMER_VECTOR as _ { + unsafe { + if enabled { + IO_APIC.lock().enable_irq(vector as u8); + } else { + IO_APIC.lock().disable_irq(vector as u8); + } + } + } +} + +#[cfg(any(feature = "smp", feature = "irq"))] +#[allow(static_mut_refs)] +pub fn local_apic<'a>() -> &'a mut LocalApic { + // It's safe as `LOCAL_APIC` is initialized in `init_primary`. + unsafe { LOCAL_APIC.assume_init_mut() } +} + +#[cfg(feature = "smp")] +pub fn raw_apic_id(id_u8: u8) -> u32 { + if unsafe { IS_X2APIC } { + id_u8 as u32 + } else { + (id_u8 as u32) << 24 + } +} + +fn cpu_has_x2apic() -> bool { + match raw_cpuid::CpuId::new().get_feature_info() { + Some(finfo) => finfo.has_x2apic(), + None => false, + } +} + +pub fn init_primary() { + info!("Initialize Local APIC..."); + + unsafe { + // Disable 8259A interrupt controllers + Port::::new(0x21).write(0xff); + Port::::new(0xA1).write(0xff); + } + + let mut builder = LocalApicBuilder::new(); + builder + .timer_vector(APIC_TIMER_VECTOR as _) + .error_vector(APIC_ERROR_VECTOR as _) + .spurious_vector(APIC_SPURIOUS_VECTOR as _); + + if cpu_has_x2apic() { + info!("Using x2APIC."); + unsafe { IS_X2APIC = true }; + } else { + info!("Using xAPIC."); + let base_vaddr = phys_to_virt(pa!(unsafe { xapic_base() } as usize)); + builder.set_xapic_base(base_vaddr.as_usize() as u64); + } + + let mut lapic = builder.build().unwrap(); + unsafe { + lapic.enable(); + #[allow(static_mut_refs)] + LOCAL_APIC.write(lapic); + } + + info!("Initialize IO APIC..."); + let io_apic = unsafe { IoApic::new(phys_to_virt(IO_APIC_BASE).as_usize() as u64) }; + IO_APIC.init_once(SpinNoIrq::new(io_apic)); +} + +#[cfg(feature = "smp")] +pub fn init_secondary() { + unsafe { local_apic().enable() }; +} + +#[cfg(feature = "irq")] +mod irq_impl { + use axplat::irq::{HandlerTable, IpiTarget, 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() }; + } + + /// Sends an inter-processor interrupt (IPI) to the specified target CPU or all CPUs. + fn send_ipi(irq_num: usize, target: IpiTarget) { + match target { + IpiTarget::Current { cpu_id } => { + unsafe { + super::local_apic().send_ipi_self(cpu_id as _); + }; + } + IpiTarget::Other { cpu_id } => { + unsafe { + super::local_apic().send_ipi(irq_num as _, cpu_id as _); + }; + } + IpiTarget::AllExceptCurrent { + cpu_id: _, + cpu_num: _, + } => { + use x2apic::lapic::IpiAllShorthand; + unsafe { + super::local_apic() + .send_ipi_all(irq_num as _, IpiAllShorthand::AllExcludingSelf); + }; + } + } + } + } +} diff --git a/platform/x86-qemu-q35/src/boot.rs b/platform/x86-qemu-q35/src/boot.rs new file mode 100644 index 00000000..f103065c --- /dev/null +++ b/platform/x86-qemu-q35/src/boot.rs @@ -0,0 +1,54 @@ +//! 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 crate::config::plat::{BOOT_STACK_SIZE, PHYS_VIRT_OFFSET}; + +/// Flags set in the ’flags’ member of the multiboot header. +/// +/// (bits 1, 16: memory information, address fields in header) +const MULTIBOOT_HEADER_FLAGS: usize = 0x0001_0002; + +/// The magic field should contain this. +const MULTIBOOT_HEADER_MAGIC: usize = 0x1BADB002; + +/// This should be in EAX. +pub(super) const MULTIBOOT_BOOTLOADER_MAGIC: usize = 0x2BADB002; + +const CR0: u64 = Cr0Flags::PROTECTED_MODE_ENABLE.bits() + | Cr0Flags::MONITOR_COPROCESSOR.bits() + | Cr0Flags::NUMERIC_ERROR.bits() + | Cr0Flags::WRITE_PROTECT.bits() + | Cr0Flags::PAGING.bits(); +const CR4: u64 = Cr4Flags::PHYSICAL_ADDRESS_EXTENSION.bits() + | Cr4Flags::PAGE_GLOBAL.bits() + | if cfg!(feature = "fp-simd") { + Cr4Flags::OSFXSR.bits() | Cr4Flags::OSXMMEXCPT_ENABLE.bits() + } else { + 0 + }; +const EFER: u64 = EferFlags::LONG_MODE_ENABLE.bits() | EferFlags::NO_EXECUTE_ENABLE.bits(); + +#[unsafe(link_section = ".bss.stack")] +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 crate::rust_entry, + entry_secondary = sym crate::rust_entry_secondary, + + offset = const PHYS_VIRT_OFFSET, + boot_stack_size = const BOOT_STACK_SIZE, + boot_stack = sym BOOT_STACK, + + cr0 = const CR0, + cr4 = const CR4, + efer_msr = const x86::msr::IA32_EFER, + efer = const EFER, +); diff --git a/platform/x86-qemu-q35/src/console.rs b/platform/x86-qemu-q35/src/console.rs new file mode 100644 index 00000000..92c336d4 --- /dev/null +++ b/platform/x86-qemu-q35/src/console.rs @@ -0,0 +1,49 @@ +//! Uart 16550 serial port. + +use axplat::console::ConsoleIf; +use kspin::SpinNoIrq; +use uart_16550::SerialPort; + +static COM1: SpinNoIrq = unsafe { SpinNoIrq::new(SerialPort::new(0x3f8)) }; + +/// Writes a byte to the console. +pub fn putchar(c: u8) { + COM1.lock().send(c) +} + +/// Reads a byte from the console, or returns [`None`] if no input is available. +pub fn getchar() -> Option { + COM1.lock().try_receive().ok() +} + +pub fn init() { + COM1.lock().init(); +} + +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); + } + } + + /// 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/platform/x86-qemu-q35/src/init.rs b/platform/x86-qemu-q35/src/init.rs new file mode 100644 index 00000000..15430e69 --- /dev/null +++ b/platform/x86-qemu-q35/src/init.rs @@ -0,0 +1,41 @@ +use axplat::init::InitIf; + +struct InitIfImpl; + +#[impl_plat_interface] +impl InitIf for InitIfImpl { + /// Initializes the platform at the early stage for the primary core. + /// + /// This function should be called immediately after the kernel has booted, + /// and performed earliest platform configuration and initialization (e.g., + /// early console, clocking). + fn init_early(_cpu_id: usize, mbi: usize) { + axcpu::init::init_trap(); + crate::console::init(); + crate::time::init_early(); + crate::mem::init(mbi); + } + + /// Initializes the platform at the early stage for secondary cores. + #[cfg(feature = "smp")] + fn init_early_secondary(_cpu_id: usize) { + axcpu::init::init_trap(); + } + + /// Initializes the platform at the later stage for the primary core. + /// + /// This function should be called after the kernel has done part of its + /// initialization (e.g, logging, memory management), and finalized the rest of + /// platform configuration and initialization. + fn init_later(_cpu_id: usize, _arg: usize) { + crate::apic::init_primary(); + crate::time::init_primary(); + } + + /// Initializes the platform at the later stage for secondary cores. + #[cfg(feature = "smp")] + fn init_later_secondary(_cpu_id: usize) { + crate::apic::init_secondary(); + crate::time::init_secondary(); + } +} diff --git a/platform/x86-qemu-q35/src/lib.rs b/platform/x86-qemu-q35/src/lib.rs index 9205d534..92136980 100644 --- a/platform/x86-qemu-q35/src/lib.rs +++ b/platform/x86-qemu-q35/src/lib.rs @@ -2,9 +2,54 @@ #![cfg(all(target_arch = "x86_64", target_os = "none"))] #![allow(missing_abi)] +#[macro_use] +extern crate log; +#[macro_use] +extern crate axplat; + use core::ptr::addr_of; -extern crate axplat_x86_pc; +mod apic; +mod boot; +mod console; +mod init; +mod mem; +mod power; +mod time; + +#[cfg(feature = "smp")] +mod mp; + +pub mod config { + pub mod plat { + pub const PHYS_VIRT_OFFSET: usize = 0xffff_8000_0000_0000; + pub const BOOT_STACK_SIZE: usize = 0x40000; + } + + pub mod devices { + pub const TIMER_FREQUENCY: usize = 4_000_000_000; // 100 MHz + } +} + +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 fn rust_entry(magic: usize, mbi: usize) { + if magic == self::boot::MULTIBOOT_BOOTLOADER_MAGIC { + axplat::call_main(current_cpu_id(), mbi); + } +} + +unsafe extern fn rust_entry_secondary(_magic: usize) { + #[cfg(feature = "smp")] + if _magic == self::boot::MULTIBOOT_BOOTLOADER_MAGIC { + axplat::call_secondary_main(current_cpu_id()); + } +} pub fn cpu_count() -> usize { unsafe extern { diff --git a/platform/x86-qemu-q35/src/mem.rs b/platform/x86-qemu-q35/src/mem.rs new file mode 100644 index 00000000..e6392c81 --- /dev/null +++ b/platform/x86-qemu-q35/src/mem.rs @@ -0,0 +1,86 @@ +//! Physical memory information. + +use axplat::mem::{MemIf, PhysAddr, RawRange, VirtAddr, pa, va}; +use heapless::Vec; +use lazyinit::LazyInit; +use multiboot::information::{MemoryManagement, MemoryType, Multiboot, PAddr}; + +const MMIO_RANGES: &[RawRange] = &[ + (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 + (0x70_0000_0000, 0x4000), // PCI devices + (0x3800_0000_0000, 0x4000), // PCI devices +]; +const PHYS_VIRT_OFFSET: usize = 0xffff_8000_0000_0000; + +const MAX_REGIONS: usize = 16; + +static RAM_REGIONS: LazyInit> = LazyInit::new(); + +pub fn init(multiboot_info_ptr: usize) { + let mut mm = MemIfImpl; + let info = unsafe { Multiboot::from_ptr(multiboot_info_ptr as _, &mut mm).unwrap() }; + + let mut regions = Vec::new(); + for r in info.memory_regions().unwrap() { + if r.memory_type() == MemoryType::Available { + regions + .push((r.base_address() as usize, r.length() as usize)) + .unwrap(); + } + } + RAM_REGIONS.init_once(regions); +} + +struct MemIfImpl; + +impl MemoryManagement for MemIfImpl { + unsafe fn paddr_to_slice(&self, addr: PAddr, size: usize) -> Option<&'static [u8]> { + let ptr = Self::phys_to_virt(pa!(addr as usize)).as_ptr(); + Some(unsafe { core::slice::from_raw_parts(ptr, size) }) + } + + unsafe fn allocate(&mut self, _length: usize) -> Option<(PAddr, &mut [u8])> { + None + } + + unsafe fn deallocate(&mut self, _addr: PAddr) {} +} + +#[impl_plat_interface] +impl MemIf for MemIfImpl { + /// Returns all physical memory (RAM) ranges on the platform. + fn phys_ram_ranges() -> &'static [RawRange] { + RAM_REGIONS.as_slice() + } + + /// 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, 0x200000)] + } + + /// Returns all device memory (MMIO) ranges on the platform. + fn mmio_ranges() -> &'static [RawRange] { + &MMIO_RANGES + } + + /// Translates a physical address to a virtual address. + fn phys_to_virt(paddr: PhysAddr) -> VirtAddr { + va!(paddr.as_usize() + PHYS_VIRT_OFFSET) + } + + /// Translates a virtual address to a physical address. + fn virt_to_phys(vaddr: VirtAddr) -> PhysAddr { + pa!(vaddr.as_usize() - PHYS_VIRT_OFFSET) + } + + /// Returns the kernel address space base virtual address and size. + fn kernel_aspace() -> (VirtAddr, usize) { + (va!(0xffff_8000_0000_0000), 0x0000_7fff_ffff_f000) + } +} diff --git a/platform/x86-qemu-q35/src/mp.rs b/platform/x86-qemu-q35/src/mp.rs new file mode 100644 index 00000000..30d24eb6 --- /dev/null +++ b/platform/x86-qemu-q35/src/mp.rs @@ -0,0 +1,49 @@ +//! Multi-processor booting. + +use axplat::mem::{PAGE_SIZE_4K, PhysAddr, pa}; +use axplat::time::{Duration, busy_wait}; + +const START_PAGE_IDX: u8 = 6; +const START_PAGE_PADDR: PhysAddr = pa!(START_PAGE_IDX as usize * PAGE_SIZE_4K); + +core::arch::global_asm!( + include_str!("ap_start.S"), + start_page_paddr = const START_PAGE_PADDR.as_usize(), +); + +unsafe fn setup_startup_page(stack_top: PhysAddr) { + unsafe extern "C" { + fn ap_entry32(); + fn ap_start(); + fn ap_end(); + } + const U64_PER_PAGE: usize = PAGE_SIZE_4K / 8; + + let start_page_ptr = axplat::mem::phys_to_virt(START_PAGE_PADDR).as_mut_ptr() as *mut u64; + let start_page = unsafe { core::slice::from_raw_parts_mut(start_page_ptr, U64_PER_PAGE) }; + unsafe { + core::ptr::copy_nonoverlapping( + ap_start as *const u64, + start_page_ptr, + (ap_end as usize - ap_start as usize) / 8, + ); + } + start_page[U64_PER_PAGE - 2] = stack_top.as_usize() as u64; // stack_top + start_page[U64_PER_PAGE - 1] = ap_entry32 as usize as _; // entry +} + +/// Starts the given secondary CPU with its boot stack. +pub fn start_secondary_cpu(apic_id: usize, stack_top: PhysAddr) { + unsafe { setup_startup_page(stack_top) }; + + let apic_id = super::apic::raw_apic_id(apic_id as u8); + let lapic = super::apic::local_apic(); + + // INIT-SIPI-SIPI Sequence + // Ref: Intel SDM Vol 3C, Section 8.4.4, MP Initialization Example + unsafe { lapic.send_init_ipi(apic_id) }; + busy_wait(Duration::from_millis(10)); // 10ms + unsafe { lapic.send_sipi(START_PAGE_IDX, apic_id) }; + busy_wait(Duration::from_micros(200)); // 200us + unsafe { lapic.send_sipi(START_PAGE_IDX, apic_id) }; +} diff --git a/platform/x86-qemu-q35/src/multiboot.S b/platform/x86-qemu-q35/src/multiboot.S new file mode 100644 index 00000000..c0af72a3 --- /dev/null +++ b/platform/x86-qemu-q35/src/multiboot.S @@ -0,0 +1,144 @@ +# Bootstrapping from 32-bit with the Multiboot specification. +# See https://www.gnu.org/software/grub/manual/multiboot/multiboot.html + +.section .text.boot +.code32 +.global _start +_start: + mov edi, eax # arg1: magic: 0x2BADB002 + mov esi, ebx # arg2: multiboot info + jmp bsp_entry32 + +.balign 4 +.type multiboot_header, STT_OBJECT +multiboot_header: + .int {mb_hdr_magic} # magic: 0x1BADB002 + .int {mb_hdr_flags} # flags + .int -({mb_hdr_magic} + {mb_hdr_flags}) # checksum + .int multiboot_header - {offset} # header_addr + .int _skernel - {offset} # load_addr + .int _edata - {offset} # load_end + .int _ebss - {offset} # bss_end_addr + .int _start - {offset} # entry_addr + +# Common code in 32-bit, prepare states to enter 64-bit. +.macro ENTRY32_COMMON + # set data segment selectors + mov ax, 0x18 + mov ss, ax + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + + # set PAE, PGE bit in CR4 + mov eax, {cr4} + mov cr4, eax + + # load the temporary page table + lea eax, [.Ltmp_pml4 - {offset}] + mov cr3, eax + + # set LME, NXE bit in IA32_EFER + mov ecx, {efer_msr} + mov edx, 0 + mov eax, {efer} + wrmsr + + # set protected mode, write protect, paging bit in CR0 + mov eax, {cr0} + mov cr0, eax +.endm + +# Common code in 64-bit +.macro ENTRY64_COMMON + # clear segment selectors + xor ax, ax + mov ss, ax + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax +.endm + +.code32 +bsp_entry32: + lgdt [.Ltmp_gdt_desc - {offset}] # load the temporary GDT + ENTRY32_COMMON + ljmp 0x10, offset bsp_entry64 - {offset} # 0x10 is code64 segment + +.code32 +.global ap_entry32 +ap_entry32: + ENTRY32_COMMON + ljmp 0x10, offset ap_entry64 - {offset} # 0x10 is code64 segment + +.code64 +bsp_entry64: + ENTRY64_COMMON + + # set RSP to boot stack + movabs rsp, offset {boot_stack} + add rsp, {boot_stack_size} + + # call rust_entry(magic, mbi) + movabs rax, offset {entry} + call rax + jmp .Lhlt + +.code64 +ap_entry64: + ENTRY64_COMMON + + # set RSP to high address (already set in ap_start.S) + mov rax, {offset} + add rsp, rax + + # call rust_entry_secondary(magic) + mov rdi, {mb_magic} + movabs rax, offset {entry_secondary} + call rax + jmp .Lhlt + +.Lhlt: + hlt + jmp .Lhlt + +.section .rodata +.balign 8 +.Ltmp_gdt_desc: + .short .Ltmp_gdt_end - .Ltmp_gdt - 1 # limit + .long .Ltmp_gdt - {offset} # base + +.section .data +.balign 16 +.Ltmp_gdt: + .quad 0x0000000000000000 # 0x00: null + .quad 0x00cf9b000000ffff # 0x08: code segment (base=0, limit=0xfffff, type=32bit code exec/read, DPL=0, 4k) + .quad 0x00af9b000000ffff # 0x10: code segment (base=0, limit=0xfffff, type=64bit code exec/read, DPL=0, 4k) + .quad 0x00cf93000000ffff # 0x18: data segment (base=0, limit=0xfffff, type=32bit data read/write, DPL=0, 4k) +.Ltmp_gdt_end: + +.balign 4096 +.Ltmp_pml4: + # 0x0000_0000 ~ 0x7f_ffff_ffff (512 GiB) + .quad .Ltmp_pdpt_low - {offset} + 0x3 # PRESENT | WRITABLE | paddr(tmp_pdpt) + .zero 8 * 255 + # 0xffff_8000_0000_0000 ~ 0xffff_807f_ffff_ffff (512 GiB) + .quad .Ltmp_pdpt_high - {offset} + 0x3 # PRESENT | WRITABLE | paddr(tmp_pdpt) + .zero 8 * 255 + +# FIXME: may not work on macOS using hvf as the CPU does not support 1GB page (pdpe1gb) +.Ltmp_pdpt_low: +.set i, 0 +.rept 512 + .quad 0x40000000 * i | 0x83 # PRESENT | WRITABLE | HUGE_PAGE | paddr(0x4000_0000 * i) + .set i, i + 1 +.endr + +.Ltmp_pdpt_high: +.set i, 0 +.rept 512 + .quad 0x40000000 * i | 0x83 # PRESENT | WRITABLE | HUGE_PAGE | paddr(0x4000_0000 * i) + .set i, i + 1 +.endr diff --git a/platform/x86-qemu-q35/src/power.rs b/platform/x86-qemu-q35/src/power.rs new file mode 100644 index 00000000..3a510881 --- /dev/null +++ b/platform/x86-qemu-q35/src/power.rs @@ -0,0 +1,44 @@ +//! Power management. + +use axplat::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). + #[cfg(feature = "smp")] + fn cpu_boot(cpu_id: usize, stack_top_paddr: usize) { + use axplat::mem::pa; + 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") { + axplat::console_println!("System will reboot, press any key to continue ..."); + while super::console::getchar().is_none() {} + axplat::console_println!("Rebooting ..."); + unsafe { PortWriteOnly::new(0x64).write(0xfeu8) }; + } else { + unsafe { PortWriteOnly::new(0x604).write(0x2000u16) }; + } + + axcpu::asm::halt(); + warn!("It should shutdown!"); + loop { + axcpu::asm::halt(); + } + } +} diff --git a/platform/x86-qemu-q35/src/time.rs b/platform/x86-qemu-q35/src/time.rs new file mode 100644 index 00000000..c3cf3db7 --- /dev/null +++ b/platform/x86-qemu-q35/src/time.rs @@ -0,0 +1,124 @@ +//! Time management. +//! +//! Currently, the TSC is used as the clock source. + +use axplat::time::TimeIf; +use raw_cpuid::CpuId; + +#[cfg(feature = "irq")] +use int_ratio::Ratio; + +#[cfg(feature = "irq")] +const LAPIC_TICKS_PER_SEC: u64 = 1_000_000_000; // TODO: need to calibrate + +#[cfg(feature = "irq")] +static mut NANOS_TO_LAPIC_TICKS_RATIO: Ratio = Ratio::zero(); + +static mut INIT_TICK: u64 = 0; +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; + +pub fn init_early() { + if let Some(freq) = CpuId::new() + .get_processor_frequency_info() + .map(|info| info.processor_base_frequency()) + && freq > 0 + { + unsafe { CPU_FREQ_MHZ = freq as u64 } + } + + axplat::console_println!("TSC frequency: {} MHz", unsafe { CPU_FREQ_MHZ }); + + unsafe { + INIT_TICK = core::arch::x86_64::_rdtsc(); + } + + #[cfg(feature = "rtc")] + { + use x86_rtc::Rtc; + + // Get the current time in microseconds since the epoch (1970-01-01) from the x86 RTC. + // Subtract the timer ticks to get the actual time when ArceOS was booted. + let eopch_time_nanos = Rtc::new().get_unix_timestamp() * 1_000_000_000; + unsafe { + RTC_EPOCHOFFSET_NANOS = eopch_time_nanos - axplat::time::ticks_to_nanos(INIT_TICK); + } + } +} + +pub fn init_primary() { + #[cfg(feature = "irq")] + unsafe { + use x2apic::lapic::{TimerDivide, TimerMode}; + let lapic = super::apic::local_apic(); + lapic.set_timer_mode(TimerMode::OneShot); + lapic.set_timer_divide(TimerDivide::Div1); + lapic.enable_timer(); + + // TODO: calibrate with HPET + NANOS_TO_LAPIC_TICKS_RATIO = Ratio::new( + LAPIC_TICKS_PER_SEC as u32, + axplat::time::NANOS_PER_SEC as u32, + ); + } +} + +#[cfg(feature = "smp")] +pub fn init_secondary() { + #[cfg(feature = "irq")] + unsafe { + crate::apic::local_apic().enable_timer(); + } +} + +struct TimeIfImpl; + +#[impl_plat_interface] +impl TimeIf for TimeIfImpl { + /// Returns the IRQ number for the timer interrupt. + fn irq_num() -> usize { + 0xf0 + } + + /// 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). + #[cfg(feature = "irq")] + fn set_oneshot_timer(deadline_ns: u64) { + 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); + } + } + } +} diff --git a/scripts/lds/linker.lds.S b/scripts/lds/linker.lds.S index 68095208..0806b2c4 100644 --- a/scripts/lds/linker.lds.S +++ b/scripts/lds/linker.lds.S @@ -1,7 +1,7 @@ OUTPUT_ARCH(%ARCH%) BASE_ADDRESS = %KERNEL_BASE%; -SMP = %SMP% +SMP = %SMP%; ENTRY(_start) SECTIONS @@ -30,6 +30,11 @@ SECTIONS _sdata = .; *(.data.boot_page_table) . = ALIGN(4K); + + __sdriver_register = .; + KEEP(*(.driver.register*)) + __edriver_register = .; + *(.data .data.*) *(.sdata .sdata.*) *(.got .got.*) diff --git a/scripts/ostool/qemu-x86_64.toml b/scripts/ostool/qemu-x86_64.toml new file mode 100644 index 00000000..a6d6547d --- /dev/null +++ b/scripts/ostool/qemu-x86_64.toml @@ -0,0 +1,21 @@ +args = [ + "-nographic", + "-cpu", + "host", + "-machine", + "q35", + "-smp", + "1", + "-accel", + "kvm", + "-device", + "virtio-blk-pci,drive=disk0", + "-drive", + "id=disk0,if=none,format=raw,file=${workspaceFolder}/tmp/rootfs.img", + "-m", + "128M", +] +fail_regex = [] +success_regex = [] +to_bin = true +uefi = false