Skip to content

Commit

Permalink
[platforms] add axplat-x86-pc from arceos
Browse files Browse the repository at this point in the history
  • Loading branch information
equation314 committed Jan 17, 2025
1 parent 513fde4 commit bb4fe0c
Show file tree
Hide file tree
Showing 11 changed files with 819 additions and 0 deletions.
69 changes: 69 additions & 0 deletions platforms/axplat-x86-pc/src/ap_start.S
Original file line number Diff line number Diff line change
@@ -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:
127 changes: 127 additions & 0 deletions platforms/axplat-x86-pc/src/apic.rs
Original file line number Diff line number Diff line change
@@ -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<MaybeUninit<LocalApic>> =
SyncUnsafeCell::new(MaybeUninit::uninit());
static mut IS_X2APIC: bool = false;
static IO_APIC: LazyInit<SpinNoIrq<IoApic>> = 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::<u8>::new(0x21).write(0xff);
Port::<u8>::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() };
}
52 changes: 52 additions & 0 deletions platforms/axplat-x86-pc/src/boot.rs
Original file line number Diff line number Diff line change
@@ -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,
);
37 changes: 37 additions & 0 deletions platforms/axplat-x86-pc/src/dtables.rs
Original file line number Diff line number Diff line change
@@ -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<IdtStruct> = LazyInit::new();

#[percpu::def_percpu]
static TSS: LazyInit<TaskStateSegment> = LazyInit::new();

#[percpu::def_percpu]
static GDT: LazyInit<GdtStruct> = 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();
}
15 changes: 15 additions & 0 deletions platforms/axplat-x86-pc/src/mem.rs
Original file line number Diff line number Diff line change
@@ -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<Item = MemRegion> {
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())
}
28 changes: 28 additions & 0 deletions platforms/axplat-x86-pc/src/misc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use x86_64::instructions::port::PortWriteOnly;

/// Shutdown the whole system (in QEMU), including all CPUs.
///
/// See <https://wiki.osdev.org/Shutdown> 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();
}
}
Loading

0 comments on commit bb4fe0c

Please sign in to comment.