From bb4fe0c2b8c34f95f9d0b0cf571414d457bac8d2 Mon Sep 17 00:00:00 2001 From: Yuekai Jia Date: Fri, 17 Jan 2025 02:02:53 +0800 Subject: [PATCH] [platforms] add axplat-x86-pc from arceos --- platforms/axplat-x86-pc/src/ap_start.S | 69 +++++++++++ platforms/axplat-x86-pc/src/apic.rs | 127 ++++++++++++++++++++ platforms/axplat-x86-pc/src/boot.rs | 52 ++++++++ platforms/axplat-x86-pc/src/dtables.rs | 37 ++++++ platforms/axplat-x86-pc/src/mem.rs | 15 +++ platforms/axplat-x86-pc/src/misc.rs | 28 +++++ platforms/axplat-x86-pc/src/mod.rs | 68 +++++++++++ platforms/axplat-x86-pc/src/mp.rs | 45 +++++++ platforms/axplat-x86-pc/src/multiboot.S | 144 +++++++++++++++++++++++ platforms/axplat-x86-pc/src/time.rs | 107 +++++++++++++++++ platforms/axplat-x86-pc/src/uart16550.rs | 127 ++++++++++++++++++++ 11 files changed, 819 insertions(+) create mode 100644 platforms/axplat-x86-pc/src/ap_start.S create mode 100644 platforms/axplat-x86-pc/src/apic.rs create mode 100644 platforms/axplat-x86-pc/src/boot.rs create mode 100644 platforms/axplat-x86-pc/src/dtables.rs create mode 100644 platforms/axplat-x86-pc/src/mem.rs create mode 100644 platforms/axplat-x86-pc/src/misc.rs create mode 100644 platforms/axplat-x86-pc/src/mod.rs create mode 100644 platforms/axplat-x86-pc/src/mp.rs create mode 100644 platforms/axplat-x86-pc/src/multiboot.S create mode 100644 platforms/axplat-x86-pc/src/time.rs create mode 100644 platforms/axplat-x86-pc/src/uart16550.rs diff --git a/platforms/axplat-x86-pc/src/ap_start.S b/platforms/axplat-x86-pc/src/ap_start.S new file mode 100644 index 0000000..9784f76 --- /dev/null +++ b/platforms/axplat-x86-pc/src/ap_start.S @@ -0,0 +1,69 @@ +# 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: + +# 0x7000 +.p2align 12 +.global ap_end +ap_end: diff --git a/platforms/axplat-x86-pc/src/apic.rs b/platforms/axplat-x86-pc/src/apic.rs new file mode 100644 index 0000000..ca2a86c --- /dev/null +++ b/platforms/axplat-x86-pc/src/apic.rs @@ -0,0 +1,127 @@ +#![allow(dead_code)] + +use core::{cell::SyncUnsafeCell, mem::MaybeUninit}; + +use kspin::SpinNoIrq; +use lazyinit::LazyInit; +use memory_addr::PhysAddr; +use x2apic::ioapic::IoApic; +use x2apic::lapic::{LocalApic, LocalApicBuilder, xapic_base}; +use x86_64::instructions::port::Port; + +use self::vectors::*; +use crate::mem::phys_to_virt; + +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; +} + +/// 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> = + SyncUnsafeCell::new(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); + } + } + } +} + +/// 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 { + // 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 { + 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(super) 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(); + LOCAL_APIC.get().as_mut().unwrap().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(super) fn init_secondary() { + unsafe { local_apic().enable() }; +} diff --git a/platforms/axplat-x86-pc/src/boot.rs b/platforms/axplat-x86-pc/src/boot.rs new file mode 100644 index 0000000..9147f23 --- /dev/null +++ b/platforms/axplat-x86-pc/src/boot.rs @@ -0,0 +1,52 @@ +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}; + +/// 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; TASK_STACK_SIZE] = [0; TASK_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, + + offset = const PHYS_VIRT_OFFSET, + boot_stack_size = const TASK_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/platforms/axplat-x86-pc/src/dtables.rs b/platforms/axplat-x86-pc/src/dtables.rs new file mode 100644 index 0000000..a6af270 --- /dev/null +++ b/platforms/axplat-x86-pc/src/dtables.rs @@ -0,0 +1,37 @@ +//! Description tables (per-CPU GDT, per-CPU ISS, IDT) + +use crate::arch::{GdtStruct, IdtStruct, TaskStateSegment}; +use lazyinit::LazyInit; + +static IDT: LazyInit = LazyInit::new(); + +#[percpu::def_percpu] +static TSS: LazyInit = LazyInit::new(); + +#[percpu::def_percpu] +static GDT: LazyInit = LazyInit::new(); + +fn init_percpu() { + unsafe { + IDT.load(); + let tss = TSS.current_ref_mut_raw(); + let gdt = GDT.current_ref_mut_raw(); + tss.init_once(TaskStateSegment::new()); + gdt.init_once(GdtStruct::new(tss)); + gdt.load(); + gdt.load_tss(); + } +} + +/// Initializes IDT, GDT on the primary CPU. +pub(super) fn init_primary() { + axlog::ax_println!("\nInitialize IDT & GDT..."); + IDT.init_once(IdtStruct::new()); + init_percpu(); +} + +/// Initializes IDT, GDT on secondary CPUs. +#[cfg(feature = "smp")] +pub(super) fn init_secondary() { + init_percpu(); +} diff --git a/platforms/axplat-x86-pc/src/mem.rs b/platforms/axplat-x86-pc/src/mem.rs new file mode 100644 index 0000000..a321c77 --- /dev/null +++ b/platforms/axplat-x86-pc/src/mem.rs @@ -0,0 +1,15 @@ +// 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()) +} diff --git a/platforms/axplat-x86-pc/src/misc.rs b/platforms/axplat-x86-pc/src/misc.rs new file mode 100644 index 0000000..788a1ac --- /dev/null +++ b/platforms/axplat-x86-pc/src/misc.rs @@ -0,0 +1,28 @@ +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 new file mode 100644 index 0000000..e39bc7e --- /dev/null +++ b/platforms/axplat-x86-pc/src/mod.rs @@ -0,0 +1,68 @@ +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 new file mode 100644 index 0000000..04b1060 --- /dev/null +++ b/platforms/axplat-x86-pc/src/mp.rs @@ -0,0 +1,45 @@ +use crate::mem::{PAGE_SIZE_4K, PhysAddr, phys_to_virt}; +use crate::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 = 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, + 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/platforms/axplat-x86-pc/src/multiboot.S b/platforms/axplat-x86-pc/src/multiboot.S new file mode 100644 index 0000000..05522ed --- /dev/null +++ b/platforms/axplat-x86-pc/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 ~ 0xffff_ffff + .quad .Ltmp_pdpt_low - {offset} + 0x3 # PRESENT | WRITABLE | paddr(tmp_pdpt) + .zero 8 * 255 + # 0xffff_8000_0000_0000 ~ 0xffff_8000_ffff_ffff + .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: + .quad 0x0000 | 0x83 # PRESENT | WRITABLE | HUGE_PAGE | paddr(0x0) + .quad 0x40000000 | 0x83 # PRESENT | WRITABLE | HUGE_PAGE | paddr(0x4000_0000) + .quad 0x80000000 | 0x83 # PRESENT | WRITABLE | HUGE_PAGE | paddr(0x8000_0000) + .quad 0xc0000000 | 0x83 # PRESENT | WRITABLE | HUGE_PAGE | paddr(0xc000_0000) + .zero 8 * 508 + +.Ltmp_pdpt_high: + .quad 0x0000 | 0x83 # PRESENT | WRITABLE | HUGE_PAGE | paddr(0x0) + .quad 0x40000000 | 0x83 # PRESENT | WRITABLE | HUGE_PAGE | paddr(0x4000_0000) + .quad 0x80000000 | 0x83 # PRESENT | WRITABLE | HUGE_PAGE | paddr(0x8000_0000) + .quad 0xc0000000 | 0x83 # PRESENT | WRITABLE | HUGE_PAGE | paddr(0xc000_0000) + .zero 8 * 508 diff --git a/platforms/axplat-x86-pc/src/time.rs b/platforms/axplat-x86-pc/src/time.rs new file mode 100644 index 0000000..8949da3 --- /dev/null +++ b/platforms/axplat-x86-pc/src/time.rs @@ -0,0 +1,107 @@ +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 = axconfig::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() { + 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 } + } + } + + 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 - ticks_to_nanos(INIT_TICK); + } + } +} + +pub(super) 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::Div256); // indeed it is Div1, the name is confusing. + lapic.enable_timer(); + + // TODO: calibrate with HPET + NANOS_TO_LAPIC_TICKS_RATIO = Ratio::new( + LAPIC_TICKS_PER_SEC as u32, + crate::time::NANOS_PER_SEC as u32, + ); + } +} + +#[cfg(feature = "smp")] +pub(super) fn init_secondary() { + #[cfg(feature = "irq")] + unsafe { + super::apic::local_apic().enable_timer(); + } +} diff --git a/platforms/axplat-x86-pc/src/uart16550.rs b/platforms/axplat-x86-pc/src/uart16550.rs new file mode 100644 index 0000000..66ee56f --- /dev/null +++ b/platforms/axplat-x86-pc/src/uart16550.rs @@ -0,0 +1,127 @@ +//! Uart 16550. + +use kspin::SpinNoIrq; +use x86_64::instructions::port::{Port, PortReadOnly, PortWriteOnly}; + +const UART_CLOCK_FACTOR: usize = 16; +const OSC_FREQ: usize = 1_843_200; + +static COM1: SpinNoIrq = SpinNoIrq::new(Uart16550::new(0x3f8)); + +bitflags::bitflags! { + /// Line status flags + struct LineStsFlags: u8 { + const INPUT_FULL = 1; + // 1 to 4 unknown + const OUTPUT_EMPTY = 1 << 5; + // 6 and 7 unknown + } +} + +struct Uart16550 { + data: Port, + int_en: PortWriteOnly, + fifo_ctrl: PortWriteOnly, + line_ctrl: PortWriteOnly, + modem_ctrl: PortWriteOnly, + line_sts: PortReadOnly, +} + +impl Uart16550 { + const fn new(port: u16) -> Self { + Self { + data: Port::new(port), + int_en: PortWriteOnly::new(port + 1), + fifo_ctrl: PortWriteOnly::new(port + 2), + line_ctrl: PortWriteOnly::new(port + 3), + modem_ctrl: PortWriteOnly::new(port + 4), + line_sts: PortReadOnly::new(port + 5), + } + } + + fn init(&mut self, baud_rate: usize) { + unsafe { + // Disable interrupts + self.int_en.write(0x00); + + // Enable DLAB + self.line_ctrl.write(0x80); + + // Set maximum speed according the input baud rate by configuring DLL and DLM + let divisor = OSC_FREQ / (baud_rate * UART_CLOCK_FACTOR); + self.data.write((divisor & 0xff) as u8); + self.int_en.write((divisor >> 8) as u8); + + // Disable DLAB and set data word length to 8 bits + self.line_ctrl.write(0x03); + + // Enable FIFO, clear TX/RX queues and + // set interrupt watermark at 14 bytes + self.fifo_ctrl.write(0xC7); + + // Mark data terminal ready, signal request to send + // and enable auxilliary output #2 (used as interrupt line for CPU) + self.modem_ctrl.write(0x0B); + } + } + + fn line_sts(&mut self) -> LineStsFlags { + unsafe { LineStsFlags::from_bits_truncate(self.line_sts.read()) } + } + + fn putchar(&mut self, c: u8) { + while !self.line_sts().contains(LineStsFlags::OUTPUT_EMPTY) {} + unsafe { self.data.write(c) }; + } + + fn getchar(&mut self) -> Option { + if self.line_sts().contains(LineStsFlags::INPUT_FULL) { + unsafe { Some(self.data.read()) } + } else { + None + } + } +} + +/// Writes a byte to the console. +fn putchar(c: u8) { + let mut uart = COM1.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 { + COM1.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 +} + +pub(super) fn init() { + COM1.lock().init(115200); +}