feat: acpi, apic, apic timer

This commit is contained in:
Zoe
2026-08-27 13:09:11 -05:00
parent afcef918a3
commit 7dcf244ee6
22 changed files with 2394 additions and 382 deletions
+361
View File
@@ -0,0 +1,361 @@
use core::sync::atomic::{AtomicU32, AtomicUsize, Ordering};
use crate::{
arch::{
disable_interrupts,
port::write_u8,
x86_64::{
cpu::{read_msr, write_msr},
interrupts::{
apic_vectors::{
APIC_ERROR_VECTOR, APIC_SELF_IPI_VECTOR, APIC_SPURIOUS_VECTOR,
APIC_TIMER_VECTOR,
},
enable_interrupts,
},
},
},
memory::{
AddressSpace, CachePolicy, FrameAllocator, PagePermissions, PhysicalAddr, VirtualAddr,
},
println,
};
const APIC_ID: u32 = 0x20;
const APIC_VERSION: u32 = 0x30;
// End Of Interrupt
const APIC_EOI: u32 = 0xB0;
// Task Priority Register
const APIC_TPR: u32 = 0x80;
// Spurious Interrupt Vector
const APIC_SVR: u32 = 0xF0;
// Error Status Register
const APIC_ESR: u32 = 0x280;
const APIC_LVT_ERROR: u32 = 0x370;
const APIC_LVT_MASKED: u64 = 1 << 16;
const APIC_LVT_TIMER_MODE_PERIODIC: u64 = 1 << 17;
pub const LOCAL_APIC_VIRTUAL_ADDRESS: VirtualAddr = VirtualAddr::new(0xFFFF_FFFD_0000_0000);
const APIC_ICR1: u32 = 0x300;
const APIC_ICR2: u32 = 0x310;
const X2APIC_SELF_IPI: u32 = 0x3F0;
const APIC_LVT_TIMER: u32 = 0x320;
const APIC_TIMER_INITIAL_COUNT: u32 = 0x380;
const APIC_TIMER_CURRENT_COUNT: u32 = 0x390;
const APIC_TIMER_DIVIDE_CONFIG: u32 = 0x3E0;
#[derive(Debug)]
enum LocalApicAccess {
X2Apic,
XApic,
}
impl LocalApicAccess {
fn read(&self, offset: u32) -> u64 {
match self {
Self::X2Apic => {
let msr = 0x800 + offset / 16;
unsafe { read_msr(msr) }
}
Self::XApic => unsafe {
LOCAL_APIC_VIRTUAL_ADDRESS
.as_ptr::<u8>()
.add(offset as usize)
.cast::<u32>()
.read_volatile() as u64
},
}
}
fn write(&self, offset: u32, value: u64) {
match self {
Self::X2Apic => {
let msr = 0x800 + offset / 16;
unsafe { write_msr(msr, value) };
}
Self::XApic => unsafe {
LOCAL_APIC_VIRTUAL_ADDRESS
.as_mut_ptr::<u8>()
.add(offset as usize)
.cast::<u32>()
.write_volatile(value as u32);
},
}
}
fn send_self_ipi(&self, vector: u8) -> Result<(), LocalApicError> {
match self {
Self::X2Apic => {
unsafe { write_msr(0x800 + X2APIC_SELF_IPI / 16, vector as u64) };
}
Self::XApic => {
unsafe {
// bit 18 = destination type. 1 = self
let interrupt_command = vector as u32 | (1 << 18);
core::ptr::write_volatile(
LOCAL_APIC_VIRTUAL_ADDRESS
.as_mut_ptr::<u8>()
.add(APIC_ICR1 as usize)
.cast::<u32>(),
interrupt_command,
);
};
}
};
Ok(())
}
fn end_of_interrupt(&self) {
self.write(APIC_EOI, 0);
}
}
#[derive(Debug)]
pub enum LocalApicError {
AddressMismatch,
ApicDisabled,
TimerTestFailed,
FailedToMapApic,
FailedToSendSelfIpi,
NotBootSystemProcessor,
}
#[derive(Debug)]
pub struct LocalApic {
id: u32,
access: LocalApicAccess,
}
const IA32_APIC_BASE: u32 = 0x1B;
const APIC_BASE_BSP: u64 = 1 << 8;
const APIC_BASE_X2APIC_ENABLE: u64 = 1 << 10;
const APIC_BASE_GLOBAL_ENABLE: u64 = 1 << 11;
// TODO: use MAXPHYADDR
const APIC_BASE_ADDRESS_MASK: u64 = 0x000F_FFFF_FFFF_F000;
static SELF_IPI_COUNTER: AtomicUsize = AtomicUsize::new(0);
impl LocalApic {
pub fn init(
local_apic_address: PhysicalAddr,
allocator: &mut FrameAllocator,
address_space: &mut AddressSpace,
) -> Result<Self, LocalApicError> {
let apic_base = unsafe { read_msr(IA32_APIC_BASE) };
let bsp = (apic_base & APIC_BASE_BSP) != 0;
let x2apix = (apic_base & APIC_BASE_X2APIC_ENABLE) != 0;
let enabled = (apic_base & APIC_BASE_GLOBAL_ENABLE) != 0;
let xapic_physical_addr = PhysicalAddr::new((apic_base & APIC_BASE_ADDRESS_MASK) as usize);
if local_apic_address != xapic_physical_addr {
return Err(LocalApicError::AddressMismatch);
}
if !enabled {
return Err(LocalApicError::ApicDisabled);
}
if !bsp {
return Err(LocalApicError::NotBootSystemProcessor);
}
println!("BSP: {}", bsp);
println!("x2Apic: {}", x2apix);
println!("enabled: {}", enabled);
println!(
"xApic physical address: {:x}",
xapic_physical_addr.as_usize()
);
let access = match x2apix {
true => LocalApicAccess::X2Apic,
false => {
address_space
.map(
local_apic_address,
LOCAL_APIC_VIRTUAL_ADDRESS,
PagePermissions::new(true, false, false),
allocator,
CachePolicy::Uncacheable,
)
.map_err(|_| LocalApicError::FailedToMapApic)?;
LocalApicAccess::XApic
}
};
let raw_id = access.read(APIC_ID);
let id = match access {
LocalApicAccess::XApic => (raw_id >> 24) as u32,
LocalApicAccess::X2Apic => raw_id as u32,
};
let version = access.read(APIC_VERSION);
let max_lvt_entries = ((version >> 16) & 0xFF) + 1;
let version = version & 0xFF;
unsafe {
write_u8(0x21, 0xFF);
write_u8(0xA1, 0xFF);
}
access.write(APIC_LVT_ERROR, APIC_ERROR_VECTOR as u64 | APIC_LVT_MASKED);
access.write(APIC_ESR, 0);
let _ = access.read(APIC_ESR);
access.write(APIC_TPR, 0);
access.write(APIC_SVR, (1 << 8) | APIC_SPURIOUS_VECTOR as u64);
Ok(Self { id, access })
}
pub fn test_timer_interrupt(&self) -> Result<(), LocalApicError> {
self.access
.write(APIC_LVT_TIMER, APIC_TIMER_VECTOR as u64 | APIC_LVT_MASKED);
// Divide by 16
self.access.write(APIC_TIMER_DIVIDE_CONFIG, 0b11);
APIC_TIMER_COUNT.store(0, Ordering::SeqCst);
self.access.write(APIC_TIMER_INITIAL_COUNT, 123456);
self.access.write(APIC_LVT_TIMER, APIC_TIMER_VECTOR as u64);
enable_interrupts();
for _ in 0..10_000_000 {
if APIC_TIMER_COUNT.load(Ordering::SeqCst) != 0 {
break;
}
core::hint::spin_loop();
}
disable_interrupts();
self.access
.write(APIC_LVT_TIMER, APIC_TIMER_VECTOR as u64 | APIC_LVT_MASKED);
self.access.write(APIC_TIMER_INITIAL_COUNT, 0);
if APIC_TIMER_COUNT.load(Ordering::SeqCst) != 1 {
return Err(LocalApicError::TimerTestFailed);
}
Ok(())
}
pub fn start_timer(&self) {
self.access.write(APIC_LVT_TIMER, APIC_TIMER_VECTOR as u64);
}
pub fn start_calibration_counter(&self) {
self.access
.write(APIC_LVT_TIMER, APIC_TIMER_VECTOR as u64 | APIC_LVT_MASKED);
self.access.write(APIC_TIMER_DIVIDE_CONFIG, 0b11);
self.access.write(APIC_TIMER_INITIAL_COUNT, u32::MAX as u64);
}
pub fn stop_timer(&self) {
self.access
.write(APIC_LVT_TIMER, APIC_TIMER_VECTOR as u64 | APIC_LVT_MASKED);
self.access.write(APIC_TIMER_INITIAL_COUNT, 0);
}
pub fn send_self_ipi(&self) -> Result<(), LocalApicError> {
SELF_IPI_COUNTER.store(0, Ordering::SeqCst);
self.access.send_self_ipi(APIC_SELF_IPI_VECTOR)?;
enable_interrupts();
for _ in 0..10_000_000 {
if SELF_IPI_COUNTER.load(Ordering::SeqCst) != 0 {
break;
}
core::hint::spin_loop();
}
disable_interrupts();
if SELF_IPI_COUNTER.load(Ordering::SeqCst) != 1 {
return Err(LocalApicError::FailedToSendSelfIpi);
}
Ok(())
}
pub fn delay_ticks(&self, count: u32) {
if count == 0 {
return;
}
APIC_TIMER_COUNT.store(0, Ordering::SeqCst);
self.access
.write(APIC_LVT_TIMER, APIC_TIMER_VECTOR as u64 | APIC_LVT_MASKED);
self.access.write(APIC_TIMER_DIVIDE_CONFIG, 0b11);
self.access.write(APIC_TIMER_INITIAL_COUNT, count as u64);
self.access.write(APIC_LVT_TIMER, APIC_TIMER_VECTOR as u64);
while APIC_TIMER_COUNT.load(Ordering::SeqCst) == 0 {
unsafe {
core::arch::asm!("sti", "hlt", "cli", options(nomem, nostack));
}
}
self.stop_timer();
}
pub fn id(&self) -> u32 {
self.id
}
}
fn current_access() -> LocalApicAccess {
let apic_base = unsafe { read_msr(IA32_APIC_BASE) };
if apic_base & APIC_BASE_X2APIC_ENABLE != 0 {
LocalApicAccess::X2Apic
} else {
LocalApicAccess::XApic
}
}
pub fn current_timer_count() -> u32 {
current_access().read(APIC_TIMER_CURRENT_COUNT) as u32
}
pub(super) fn end_of_interrupt() {
current_access().end_of_interrupt();
}
pub(super) fn record_self_ipi() {
SELF_IPI_COUNTER.fetch_add(1, Ordering::SeqCst);
}
static APIC_ERROR_COUNT: AtomicUsize = AtomicUsize::new(0);
static LAST_APIC_ERROR: AtomicU32 = AtomicU32::new(0);
pub(super) fn record_error() {
let access = current_access();
access.write(APIC_ESR, 0);
let error = access.read(APIC_ESR) as u32;
LAST_APIC_ERROR.store(error, Ordering::SeqCst);
APIC_ERROR_COUNT.fetch_add(1, Ordering::SeqCst);
}
static APIC_TIMER_COUNT: AtomicUsize = AtomicUsize::new(0);
pub(super) fn record_timer() {
APIC_TIMER_COUNT.fetch_add(1, Ordering::SeqCst);
}
+35 -5
View File
@@ -4,6 +4,7 @@ use core::arch::asm;
pub enum CpuFeaturesError {
CpuidFeaturesNotSupported,
InvalidPhysicalAddressWidth,
InvalidVirtualAddressWidth,
}
#[derive(Clone, Copy, Debug)]
@@ -12,14 +13,19 @@ pub(crate) struct CpuFeatures {
pub nx_enabled: bool,
pub physical_address_bits: u8,
pub virtual_address_bits: u8,
pub five_level_paging_active: bool,
}
// Extended features
const IA32_EFER: u32 = 0xC0000080;
pub fn detect_features_and_enable() -> Result<CpuFeatures, CpuFeaturesError> {
let mut features = CpuFeatures {
nx_supported: false,
nx_enabled: false,
physical_address_bits: 0,
virtual_address_bits: 0,
five_level_paging_active: false,
};
let cpuid_result = core::arch::x86_64::__cpuid_count(0x80000000, 0);
@@ -40,6 +46,16 @@ pub fn detect_features_and_enable() -> Result<CpuFeatures, CpuFeaturesError> {
}
features.virtual_address_bits = (cpuid_result.eax >> 8 & 0xFF) as u8;
features.five_level_paging_active = read_cr4() & (1 << 12) != 0;
let required_virtual_address_bits = if features.five_level_paging_active {
57
} else {
48
};
if features.virtual_address_bits < required_virtual_address_bits {
return Err(CpuFeaturesError::InvalidVirtualAddressWidth);
}
if features.nx_supported {
let cpuid_result = core::arch::x86_64::__cpuid_count(0x1, 0);
@@ -50,19 +66,33 @@ pub fn detect_features_and_enable() -> Result<CpuFeatures, CpuFeaturesError> {
}
// mother efer
let efer = unsafe { read_msr(0xC0000080) };
let efer = unsafe { read_msr(IA32_EFER) };
unsafe {
write_msr(0xC0000080, efer | (1 << 11));
write_msr(IA32_EFER, efer | (1 << 11));
}
features.nx_enabled = unsafe { read_msr(0xC0000080) } & (1 << 11) != 0;
features.nx_enabled = unsafe { read_msr(IA32_EFER) } & (1 << 11) != 0;
}
Ok(features)
}
unsafe fn read_msr(msr: u32) -> u64 {
fn read_cr4() -> usize {
let value: usize;
unsafe {
asm!(
"mov {}, cr4",
out(reg) value,
options(nomem, nostack, preserves_flags),
);
}
value
}
pub(super) unsafe fn read_msr(msr: u32) -> u64 {
let low: u32;
let high: u32;
@@ -79,7 +109,7 @@ unsafe fn read_msr(msr: u32) -> u64 {
((high as u64) << 32) | low as u64
}
unsafe fn write_msr(msr: u32, value: u64) {
pub(super) unsafe fn write_msr(msr: u32, value: u64) {
unsafe {
asm!(
"wrmsr",
@@ -0,0 +1,42 @@
use crate::arch::{
apic, timer,
x86_64::interrupts::idt::{self, InterruptStackFrame},
};
pub const APIC_SELF_IPI_VECTOR: u8 = 0xF0;
pub const PIT_CALIBRATION_VECTOR: u8 = 0xF1;
pub const APIC_TIMER_VECTOR: u8 = 0xFD;
pub const APIC_ERROR_VECTOR: u8 = 0xFE;
pub const APIC_SPURIOUS_VECTOR: u8 = 0xFF;
extern "x86-interrupt" fn self_ipi_handler(_frame: InterruptStackFrame) {
apic::record_self_ipi();
apic::end_of_interrupt();
}
extern "x86-interrupt" fn error_handler(_frame: InterruptStackFrame) {
apic::record_error();
apic::end_of_interrupt();
}
extern "x86-interrupt" fn timer_handler(_frame: InterruptStackFrame) {
apic::record_timer();
apic::end_of_interrupt();
}
extern "x86-interrupt" fn pit_calibration_handler(_frame: InterruptStackFrame) {
timer::record_pit_calibration();
apic::end_of_interrupt();
}
extern "x86-interrupt" fn spurious_handler(_frame: InterruptStackFrame) {
// No EOI
}
pub(super) fn install(idt: &mut idt::Idt) {
idt.set_handler(APIC_SELF_IPI_VECTOR, self_ipi_handler, 0);
idt.set_handler(PIT_CALIBRATION_VECTOR, pit_calibration_handler, 0);
idt.set_handler(APIC_ERROR_VECTOR, error_handler, 0);
idt.set_handler(APIC_TIMER_VECTOR, timer_handler, 0);
idt.set_handler(APIC_SPURIOUS_VECTOR, spurious_handler, 0);
}
+5 -1
View File
@@ -1,5 +1,8 @@
use super::exceptions;
use crate::{arch::x86_64::gdt::KERNEL_CODE_SELECTOR, memory::VirtualAddr};
use crate::{
arch::x86_64::{gdt::KERNEL_CODE_SELECTOR, interrupts::apic_vectors},
memory::VirtualAddr,
};
#[repr(C, packed)]
#[derive(Clone, Copy)]
@@ -97,6 +100,7 @@ pub fn idt_init() {
let mut idt = Idt::new();
exceptions::install(&mut idt);
apic_vectors::install(&mut idt);
unsafe {
core::ptr::addr_of_mut!(IDT).write(idt);
+10 -1
View File
@@ -1,12 +1,21 @@
use core::arch::asm;
pub(super) mod apic_vectors;
mod exceptions;
mod idt;
pub use idt::idt_init as init;
#[inline(always)]
pub fn disable_interrupts() {
unsafe {
asm!("cli");
asm!("cli", options(nostack));
}
}
#[inline(always)]
pub fn enable_interrupts() {
unsafe {
asm!("sti", options(nostack));
}
}
+213
View File
@@ -0,0 +1,213 @@
use crate::{
memory::{
AddressSpace, CachePolicy, FrameAllocator, PagePermissions, PhysicalAddr, VirtualAddr,
},
platform::acpi::{InterruptPolarity, IoApicInfo, TriggerMode},
println,
};
const IOREGSEL: usize = 0x00;
const IOWIN: usize = 0x10;
const IOAPIC_ID: u8 = 0x00;
const IOAPIC_VERSION: u8 = 0x01;
const IOAPIC_REDIRECTION_BASE: u8 = 0x10;
const MASKED: u32 = 1 << 16;
pub const IOAPIC_VIRTUAL_ADDRESS: VirtualAddr = VirtualAddr::new(0xFFFF_FFFD_1000_0000);
#[derive(Debug)]
pub enum IoApicError {
IdMismatch { expected: u8, actual: u8 },
GsiOutsideRange,
FailedToMapIoApic,
InvalidRedirectionIndex,
}
pub struct RedirectionConfig {
pub vector: u8,
pub destination: u8,
pub polarity: InterruptPolarity,
pub trigger: TriggerMode,
}
#[derive(Debug)]
pub struct IoApic {
base: VirtualAddr,
global_interrupt_base: u32,
redirection_entry_count: u32,
}
impl IoApic {
pub fn new(
expected_id: u8,
physical_address: PhysicalAddr,
global_interrupt_base: u32,
virtual_address: VirtualAddr,
allocator: &mut FrameAllocator,
address_space: &mut AddressSpace,
) -> Result<Self, IoApicError> {
address_space
.map(
physical_address,
virtual_address,
PagePermissions::new(true, false, false),
allocator,
CachePolicy::Uncacheable,
)
.map_err(|_| IoApicError::FailedToMapIoApic)?;
let mut io_apic = Self {
base: virtual_address,
global_interrupt_base: global_interrupt_base,
redirection_entry_count: 0,
};
let version = io_apic.read(IOAPIC_VERSION);
io_apic.redirection_entry_count = ((version >> 16) & 0xFF) + 1;
let id = ((io_apic.read(IOAPIC_ID) >> 24) & 0xF) as u8;
if id != expected_id {
return Err(IoApicError::IdMismatch {
expected: expected_id,
actual: id,
});
}
println!("IOAPIC ID: {:#X}", id);
println!("IOAPIC version: {:#X}", version & 0xFF);
println!(
"IOAPIC redirection entry count: {:#X}",
io_apic.redirection_entry_count
);
Ok(io_apic)
}
fn read(&mut self, register: u8) -> u32 {
unsafe {
self.base
.as_mut_ptr::<u32>()
.write_volatile(register as u32);
self.base
.as_ptr::<u8>()
.add(IOWIN)
.cast::<u32>()
.read_volatile()
}
}
fn write(&mut self, register: u8, value: u32) {
unsafe {
self.base
.as_mut_ptr::<u32>()
.write_volatile(register as u32);
self.base
.as_mut_ptr::<u8>()
.add(IOWIN)
.cast::<u32>()
.write_volatile(value);
}
}
fn redirection_index(&self, gsi: u32) -> Result<u32, IoApicError> {
let index = gsi
.checked_sub(self.global_interrupt_base)
.ok_or(IoApicError::GsiOutsideRange)?;
if index >= self.redirection_entry_count {
return Err(IoApicError::GsiOutsideRange);
}
Ok(index)
}
pub fn handles_gsi(&mut self, gsi: u32) -> bool {
match gsi.checked_sub(self.global_interrupt_base) {
Some(index) => index < self.redirection_entry_count,
None => false,
}
}
fn read_redirection(&mut self, gsi: u32) -> Result<u64, IoApicError> {
let index = self.redirection_index(gsi)?;
let register = u8::try_from(IOAPIC_REDIRECTION_BASE as u32 + index * 2)
.map_err(|_| IoApicError::InvalidRedirectionIndex)?;
let low = self.read(register);
let high = self.read(register + 1);
Ok((high as u64) << 32 | low as u64)
}
fn redirection_registers(&mut self, gsi: u32) -> Result<(u8, u8), IoApicError> {
let index = self.redirection_index(gsi)?;
let low_register = u8::try_from(IOAPIC_REDIRECTION_BASE as u32 + index * 2)
.map_err(|_| IoApicError::InvalidRedirectionIndex)?;
let high_register = u8::try_from(IOAPIC_REDIRECTION_BASE as u32 + index * 2 + 1)
.map_err(|_| IoApicError::InvalidRedirectionIndex)?;
Ok((low_register, high_register))
}
pub fn configure_masked(
&mut self,
gsi: u32,
config: RedirectionConfig,
) -> Result<(), IoApicError> {
let mut entry = config.vector as u64;
match config.polarity {
InterruptPolarity::ActiveHigh => {
entry |= 0 << 13;
}
InterruptPolarity::ActiveLow => {
entry |= 1 << 13;
}
}
match config.trigger {
TriggerMode::Edge => {
entry |= 0 << 15;
}
TriggerMode::Level => {
entry |= 1 << 15;
}
}
entry |= 1 << 16;
entry |= (config.destination as u64) << 56;
let (low_register, high_register) = self.redirection_registers(gsi)?;
let old_low = self.read(low_register);
self.write(low_register, old_low | MASKED);
self.write(high_register, (entry >> 32) as u32);
self.write(low_register, entry as u32 | MASKED);
Ok(())
}
pub fn unmask(&mut self, gsi: u32) -> Result<(), IoApicError> {
let (low_register, _) = self.redirection_registers(gsi)?;
let low = self.read(low_register);
self.write(low_register, low & !MASKED);
Ok(())
}
pub fn mask(&mut self, gsi: u32) -> Result<(), IoApicError> {
let (low_register, _) = self.redirection_registers(gsi)?;
let low = self.read(low_register);
self.write(low_register, low | MASKED);
Ok(())
}
}
+153 -1
View File
@@ -1,8 +1,12 @@
pub mod apic;
mod cpu;
mod gdt;
mod interrupts;
pub mod io_apic;
mod paging;
mod pit;
pub mod port;
pub mod timer;
use core::arch::asm;
@@ -16,7 +20,17 @@ pub struct ArchState {
pub paging: PagingConfig,
}
use crate::println;
use crate::{
KernelHandoff,
arch::{
apic::LocalApic,
io_apic::{IOAPIC_VIRTUAL_ADDRESS, IoApic},
x86_64::interrupts::apic_vectors::PIT_CALIBRATION_VECTOR,
},
memory::{AddressSpace, FrameAllocator, VirtualAddr},
platform::acpi::Madt,
println,
};
pub fn init() -> ArchState {
disable_interrupts();
@@ -31,6 +45,144 @@ pub fn init() -> ArchState {
ArchState { paging }
}
#[derive(Debug)]
pub enum InterruptInitError {
InvalidLocalApicId,
InvalidLocalApicAddress,
FailedToGetIoApic,
IoApicError(io_apic::IoApicError),
LocalApicError(apic::LocalApicError),
TimerCalibrationError(timer::TimerCalibrationError),
MalformedMadt,
PitNotHandled,
}
#[derive(Debug)]
pub struct InterruptController {
local_apic: LocalApic,
io_apic: IoApic,
local_timer_frequency: u64,
}
pub fn init_interrupt_controller(
madt: &Madt<'_>,
allocator: &mut FrameAllocator,
address_space: &mut AddressSpace,
) -> Result<InterruptController, InterruptInitError> {
let local_apic_address = madt
.effective_local_apic_address()
.map_err(|_| InterruptInitError::InvalidLocalApicAddress)?;
let mut local_apic = apic::LocalApic::init(local_apic_address, allocator, address_space)
.map_err(|err| InterruptInitError::LocalApicError(err))?;
local_apic
.send_self_ipi()
.map_err(|err| InterruptInitError::LocalApicError(err))?;
local_apic
.test_timer_interrupt()
.map_err(|err| InterruptInitError::LocalApicError(err))?;
let io_apic_info = madt
.sole_io_apic()
.map_err(|_| InterruptInitError::FailedToGetIoApic)?;
let pit_route = madt
.isa_irq_route(0x0)
.map_err(|_| InterruptInitError::MalformedMadt)?;
let mut io_apic = io_apic::IoApic::new(
io_apic_info.id,
io_apic_info.apic_address,
io_apic_info.global_system_interrupt_base,
IOAPIC_VIRTUAL_ADDRESS,
allocator,
address_space,
)
.map_err(|err| InterruptInitError::IoApicError(err))?;
let pit_handled = io_apic.handles_gsi(pit_route.gsi);
if !pit_handled {
return Err(InterruptInitError::PitNotHandled);
}
let destination =
u8::try_from(local_apic.id()).map_err(|_| InterruptInitError::InvalidLocalApicId)?;
io_apic
.configure_masked(
pit_route.gsi,
io_apic::RedirectionConfig {
vector: PIT_CALIBRATION_VECTOR,
destination,
polarity: pit_route.polarity,
trigger: pit_route.trigger,
},
)
.map_err(|err| InterruptInitError::IoApicError(err))?;
let local_timer_frequency =
timer::calibrate_local_apic(&mut local_apic, &mut io_apic, pit_route)
.map_err(|err| InterruptInitError::TimerCalibrationError(err))?;
Ok(InterruptController {
local_apic,
io_apic,
local_timer_frequency,
})
}
#[derive(Debug)]
pub enum TimerError {
DurationOverflow,
}
impl InterruptController {
pub fn delay(&self, duration: core::time::Duration) -> Result<(), TimerError> {
let nanoseconds = duration.as_nanos();
let ticks = (nanoseconds
.checked_mul(self.local_timer_frequency as u128)
.ok_or(TimerError::DurationOverflow)?
+ 999_999_999)
/ 1_000_000_000;
if ticks == 0 {
return Ok(());
}
let mut remaining = ticks;
while remaining > 0 {
let chunk = remaining.min(u32::MAX as u128) as u32;
self.local_apic.delay_ticks(chunk);
remaining -= chunk as u128;
}
Ok(())
}
}
/// # Safety
///
/// The caller must ensure:
/// - The stack is currently mapped, writable, and 16-byte aligned
pub unsafe fn enter_kernel(stack_top: VirtualAddr, handoff: *mut KernelHandoff) -> ! {
unsafe {
asm!(
"mov rsp, {stack_top}",
"xor rbp, rbp",
"mov rdi, {handoff}",
"call {kernel_main}",
stack_top = in(reg) stack_top.as_usize(),
handoff = in(reg) handoff,
kernel_main = sym crate::kernel_main,
options(noreturn)
);
};
}
pub fn halt() {
unsafe {
asm!("hlt");
+375 -233
View File
@@ -3,7 +3,8 @@ use core::arch::asm;
use crate::{
arch::x86_64::cpu::CpuFeatures,
memory::{
DirectMap, FrameAllocator, PagePermissions, PhysicalAddr, PhysicalFrame, VirtualAddr,
CachePolicy, DirectMap, FrameAddr, FrameAllocator, OwnedFrame, PagePermissions,
PhysicalAddr, VirtualAddr,
},
};
@@ -22,7 +23,11 @@ impl PagingConfig {
Self {
physical_address_bits: features.physical_address_bits,
nx_enabled: features.nx_enabled,
mode: PagingMode::FourLevel,
mode: if features.five_level_paging_active {
PagingMode::FiveLevel
} else {
PagingMode::FourLevel
},
}
}
@@ -41,6 +46,19 @@ enum PagingMode {
FiveLevel,
}
const MAX_INTERMEDIATE_LEVELS: usize = 4;
const FOUR_LEVEL_INTERMEDIATES: [PageTableLevel; 3] = [
PageTableLevel::Pml4,
PageTableLevel::Pdpt,
PageTableLevel::PageDirectory,
];
const FIVE_LEVEL_INTERMEDIATES: [PageTableLevel; 4] = [
PageTableLevel::Pml5,
PageTableLevel::Pml4,
PageTableLevel::Pdpt,
PageTableLevel::PageDirectory,
];
impl PagingMode {
const fn virtual_address_bits(&self) -> u32 {
match self {
@@ -48,6 +66,42 @@ impl PagingMode {
PagingMode::FiveLevel => 57,
}
}
fn intermediate_levels(&self) -> &'static [PageTableLevel] {
match self {
PagingMode::FourLevel => &FOUR_LEVEL_INTERMEDIATES,
PagingMode::FiveLevel => &FIVE_LEVEL_INTERMEDIATES,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum PageTableLevel {
Pml5,
Pml4,
Pdpt,
PageDirectory,
}
impl PageTableLevel {
const fn index(self, address: usize) -> usize {
let shift = match self {
Self::Pml5 => 48,
Self::Pml4 => 39,
Self::Pdpt => 30,
Self::PageDirectory => 21,
};
address >> shift & 0x1FF
}
const fn large_page_size(self) -> Option<usize> {
match self {
Self::Pdpt => Some(1 << 30),
Self::PageDirectory => Some(1 << 21),
Self::Pml5 | Self::Pml4 => None,
}
}
}
#[derive(Debug)]
@@ -67,9 +121,14 @@ impl PageTableEntry {
const HUGE_PAGE: u64 = 1 << 7;
const NX: u64 = 1 << 63;
const WRITE_THROUGH: u64 = 1 << 3;
const CACHE_DISABLED: u64 = 1 << 4;
const PAT: u64 = 1 << 7;
const fn new(
physical_address: PhysicalAddr,
permissions: PagePermissions,
cache_policy: CachePolicy,
config: PagingConfig,
) -> Result<Self, PageTableEntryError> {
if physical_address.as_usize() >= config.physical_address_limit() {
@@ -92,11 +151,20 @@ impl PageTableEntry {
value |= Self::NX;
}
// TODO: these bit positions very by page size
// set PAT
match cache_policy {
CachePolicy::WriteBack => {}
CachePolicy::Uncacheable => {
value |= Self::CACHE_DISABLED | Self::WRITE_THROUGH;
}
};
Ok(Self(value))
}
fn new_table(
frame: PhysicalFrame,
frame: FrameAddr,
user_accessible: bool,
config: PagingConfig,
) -> Result<Self, PageTableEntryError> {
@@ -132,12 +200,20 @@ impl PageTableEntry {
self.0 & Self::HUGE_PAGE != 0
}
fn frame(&self, config: PagingConfig) -> Option<PhysicalFrame> {
fn table_frame(&self, config: PagingConfig) -> Option<FrameAddr> {
if !self.is_present() || self.is_huge() {
return None;
}
PhysicalFrame::from_start_address(self.physical_address(config))
FrameAddr::from_start_address(self.physical_address(config))
}
fn leaf_frame(&self, config: PagingConfig) -> Option<FrameAddr> {
if !self.is_present() {
return None;
}
FrameAddr::from_start_address(self.physical_address(config))
}
}
@@ -165,10 +241,9 @@ pub(crate) enum UnmapError {
}
#[derive(Clone, Copy)]
struct NewTable {
parent: PhysicalFrame,
struct EntryLocation {
table: FrameAddr,
index: usize,
child: PhysicalFrame,
}
#[derive(Debug)]
@@ -178,8 +253,8 @@ pub(crate) enum PageTableCreateError {
}
pub struct PageTable {
frame: PhysicalFrame,
direct_map: DirectMap,
pub direct_map: DirectMap,
frame: OwnedFrame,
config: PagingConfig,
}
@@ -191,7 +266,7 @@ impl PageTable {
) -> Result<Self, PageTableCreateError> {
let frame = allocator.alloc().ok_or(PageTableCreateError::OutOfFrames)?;
if frame.start_address().as_usize() >= config.physical_address_limit() {
if frame.frame_address().start_address().as_usize() >= config.physical_address_limit() {
unsafe { allocator.dealloc(frame) };
return Err(PageTableCreateError::PhysicalAddressTooLarge);
@@ -207,19 +282,16 @@ impl PageTable {
fn is_active(&self) -> bool {
let cr3 = unsafe { read_cr3(self.config) };
cr3.start_address() == self.frame.start_address()
cr3.start_address() == self.frame.frame_address().start_address()
}
fn table(&self, frame: PhysicalFrame) -> Option<&[PageTableEntry; PAGE_TABLE_ENTRIES]> {
fn table(&self, frame: FrameAddr) -> Option<&[PageTableEntry; PAGE_TABLE_ENTRIES]> {
let virtual_addr = self.direct_map.translate(frame.start_address())?;
Some(unsafe { &*(virtual_addr.as_ptr::<[PageTableEntry; PAGE_TABLE_ENTRIES]>()) })
}
fn table_mut(
&mut self,
frame: PhysicalFrame,
) -> Option<&mut [PageTableEntry; PAGE_TABLE_ENTRIES]> {
fn table_mut(&mut self, frame: FrameAddr) -> Option<&mut [PageTableEntry; PAGE_TABLE_ENTRIES]> {
let virtual_addr = self.direct_map.translate(frame.start_address())?;
Some(unsafe { &mut *(virtual_addr.as_mut_ptr::<[PageTableEntry; PAGE_TABLE_ENTRIES]>()) })
@@ -232,228 +304,263 @@ impl PageTable {
(((addr << shift) as isize >> shift) as usize) == addr
}
pub fn translate(&self, addr: VirtualAddr) -> Option<PhysicalAddr> {
let addr = addr.as_usize();
pub fn to_physical(&self, addr: VirtualAddr) -> Option<PhysicalAddr> {
let address = addr.as_usize();
if !self.is_canonical(addr) {
if !self.is_canonical(address) {
return None;
}
let p4 = self.table(self.frame)?;
let p4_entry = p4[p4_index(addr)];
let mut table_frame = self.frame.frame_address();
if !p4_entry.is_present() {
return None;
for &level in self.config.mode.intermediate_levels() {
let table = self.table(table_frame)?;
let entry = table[level.index(address)];
if !entry.is_present() {
return None;
}
if entry.is_huge() {
return translate_huge_page(entry, address, level.large_page_size()?, self.config);
}
table_frame = entry.table_frame(self.config)?;
}
let p3 = self.table(p4_entry.frame(self.config)?)?;
let p3_entry = p3[p3_index(addr)];
if !p3_entry.is_present() {
return None;
}
if p3_entry.is_huge() {
return translate_huge_page(p3_entry, addr, 1 << 30, self.config);
}
let p2 = self.table(p3_entry.frame(self.config)?)?;
let p2_entry = p2[p2_index(addr)];
if !p2_entry.is_present() {
return None;
}
if p2_entry.is_huge() {
return translate_huge_page(p2_entry, addr, 1 << 21, self.config);
}
let p1 = self.table(p2_entry.frame(self.config)?)?;
let p1_entry = p1[p1_index(addr)];
if !p1_entry.is_present() {
return None;
}
let physical_base = p1_entry.physical_address(self.config).as_usize();
let page_table = self.table(table_frame)?;
let entry = page_table[p1_index(address)];
let physical_base = entry.leaf_frame(self.config)?.start_address().as_usize();
physical_base
.checked_add(page_offset(addr))
.checked_add(page_offset(address))
.map(PhysicalAddr::new)
}
pub fn to_virtual(&self, addr: PhysicalAddr) -> Option<VirtualAddr> {
self.direct_map.translate(addr)
}
fn get_next_level(
&self,
parent: PhysicalFrame,
parent: FrameAddr,
index: usize,
) -> Result<PhysicalFrame, UnmapError> {
level: PageTableLevel,
) -> Result<FrameAddr, UnmapError> {
let parent_table = self
.table(parent)
.ok_or(UnmapError::PageTableOutsideDirectMap)?;
// TODO: encode the level so bit 7 is only interpreted where huge pages are valid.
if parent_table[index].is_huge() {
return Err(UnmapError::HugePageConflict);
}
parent_table[index]
.frame(self.config)
.ok_or(UnmapError::PageNotMapped)
}
fn get_next_level_or_allocate(
&mut self,
parent: PhysicalFrame,
index: usize,
user_accessible: bool,
allocator: &mut FrameAllocator,
) -> Result<(PhysicalFrame, bool), MapError> {
let config = self.config;
let parent_table = self
.table_mut(parent)
.ok_or(MapError::PageTableOutsideDirectMap)?;
let entry = &mut parent_table[index];
if !entry.is_present() {
let frame = allocator.alloc().ok_or(MapError::OutOfFrames)?;
let table = match PageTableEntry::new_table(frame, user_accessible, config) {
Ok(table) => table,
Err(PageTableEntryError::PhysicalAddressTooLarge) => {
unsafe { allocator.dealloc(frame) };
return Err(MapError::PhysicalAddressTooLarge);
}
Err(PageTableEntryError::NoExecuteUnsupported) => unreachable!(),
};
*entry = table;
return Ok((frame, true));
}
let entry = parent_table[index];
if entry.is_huge() {
return Err(MapError::HugePageConflict);
}
// TODO: we upgrade intermediate entries, and dont carefully rollback if we fail
if user_accessible && !entry.is_user_accessible() {
entry.0 |= PageTableEntry::USER_ACCESSIBLE;
return if level.large_page_size().is_some() {
Err(UnmapError::HugePageConflict)
} else {
Err(UnmapError::InvalidPageTableEntry)
};
}
entry
.frame(config)
.map(|frame| (frame, false))
.ok_or(MapError::InvalidPageTableEntry)
.table_frame(self.config)
.ok_or(UnmapError::PageNotMapped)
}
fn rollback_tables(
&mut self,
new_tables: &[Option<NewTable>],
fn discard_private_tables(
frames: &mut [Option<OwnedFrame>; MAX_INTERMEDIATE_LEVELS],
count: usize,
allocator: &mut FrameAllocator,
) {
for table in new_tables[..count].iter().rev().flatten() {
self.table_mut(table.parent).unwrap()[table.index] = PageTableEntry::null();
for frame in frames[..count].iter_mut().rev().filter_map(Option::take) {
unsafe { allocator.dealloc(frame) };
}
}
unsafe { allocator.dealloc(table.child) };
fn apply_user_upgrades(
&mut self,
upgrades: &[Option<EntryLocation>; MAX_INTERMEDIATE_LEVELS],
count: usize,
) {
for location in upgrades[..count].iter().flatten() {
let entry = &mut self
.table_mut(location.table)
.expect("validated page table left the direct map")[location.index];
entry.0 |= PageTableEntry::USER_ACCESSIBLE;
}
}
pub fn map(
&mut self,
mapped_addr: VirtualAddr,
frame: PhysicalFrame,
frame: FrameAddr,
permissions: PagePermissions,
allocator: &mut FrameAllocator,
cache_policy: CachePolicy,
) -> Result<(), MapError> {
if !self.is_canonical(mapped_addr.as_usize()) {
let address = mapped_addr.as_usize();
if !self.is_canonical(address) {
return Err(MapError::InvalidVirtualAddress);
}
if mapped_addr.as_usize() % PAGE_SIZE != 0 {
if address % PAGE_SIZE != 0 {
return Err(MapError::VirtualAddressUnaligned);
}
if frame.start_address().as_usize() >= self.config.physical_address_limit() {
return Err(MapError::PhysicalAddressTooLarge);
let leaf_entry = PageTableEntry::new(
frame.start_address(),
permissions,
cache_policy,
self.config,
)
.map_err(|error| match error {
PageTableEntryError::PhysicalAddressTooLarge => MapError::PhysicalAddressTooLarge,
PageTableEntryError::NoExecuteUnsupported => MapError::NoExecuteUnsupported,
})?;
let levels = self.config.mode.intermediate_levels();
let mut current_table_frame_addr = self.frame.frame_address();
let mut first_missing = None;
let mut user_upgrades: [Option<EntryLocation>; MAX_INTERMEDIATE_LEVELS] =
[None; MAX_INTERMEDIATE_LEVELS];
let mut user_upgrade_count = 0;
for (depth, &level) in levels.iter().enumerate() {
let index = level.index(address);
let table = self
.table(current_table_frame_addr)
.ok_or(MapError::PageTableOutsideDirectMap)?;
let entry = table[index];
if !entry.is_present() {
first_missing = Some((
depth,
EntryLocation {
table: current_table_frame_addr,
index,
},
));
break;
}
if entry.is_huge() {
return if level.large_page_size().is_some() {
Err(MapError::HugePageConflict)
} else {
Err(MapError::InvalidPageTableEntry)
};
}
if permissions.user_accessible && !entry.is_user_accessible() {
user_upgrades[user_upgrade_count] = Some(EntryLocation {
table: current_table_frame_addr,
index,
});
user_upgrade_count += 1;
}
current_table_frame_addr = entry
.table_frame(self.config)
.ok_or(MapError::InvalidPageTableEntry)?;
}
let mut new_tables: [Option<NewTable>; 3] = [None; 3];
let mut new_table_count = 0;
let result = (|| {
let p4_entry_index = p4_index(mapped_addr.as_usize());
let (pdpt_frame, allocated) = self.get_next_level_or_allocate(
self.frame,
p4_entry_index,
permissions.user_accessible,
allocator,
)?;
if allocated {
new_tables[new_table_count] = Some(NewTable {
parent: self.frame,
index: p4_entry_index,
child: pdpt_frame,
});
new_table_count += 1;
}
let p3_entry_index = p3_index(mapped_addr.as_usize());
let (pd_frame, allocated) = self.get_next_level_or_allocate(
pdpt_frame,
p3_entry_index,
permissions.user_accessible,
allocator,
)?;
if allocated {
new_tables[new_table_count] = Some(NewTable {
parent: pdpt_frame,
index: p3_entry_index,
child: pd_frame,
});
new_table_count += 1;
}
let p2_entry_index = p2_index(mapped_addr.as_usize());
let (pt_frame, allocated) = self.get_next_level_or_allocate(
pd_frame,
p2_entry_index,
permissions.user_accessible,
allocator,
)?;
if allocated {
new_tables[new_table_count] = Some(NewTable {
parent: pd_frame,
index: p2_entry_index,
child: pt_frame,
});
new_table_count += 1;
}
let config = self.config.clone();
let pt_table = self
.table_mut(pt_frame)
if first_missing.is_none() {
let pt = self
.table(current_table_frame_addr)
.ok_or(MapError::PageTableOutsideDirectMap)?;
let entry = &mut pt_table[p1_index(mapped_addr.as_usize())];
if entry.is_present() {
if pt[p1_index(address)].is_present() {
return Err(MapError::PageAlreadyMapped);
}
*entry =
PageTableEntry::new(frame.start_address(), permissions, config).map_err(|err| {
match err {
PageTableEntryError::PhysicalAddressTooLarge => {
MapError::PhysicalAddressTooLarge
}
PageTableEntryError::NoExecuteUnsupported => MapError::NoExecuteUnsupported,
}
})?;
Ok(())
self.apply_user_upgrades(&user_upgrades, user_upgrade_count);
self.table_mut(current_table_frame_addr)
.expect("validated page table left the direct map")[p1_index(address)] = leaf_entry;
self.flush_tlb_if_active(mapped_addr);
return Ok(());
}
let (missing_depth, publication_location) = first_missing.unwrap();
let private_table_count = levels.len() - missing_depth;
let mut private_tables: [Option<OwnedFrame>; MAX_INTERMEDIATE_LEVELS] =
core::array::from_fn(|_| None);
let mut allocated_count = 0;
while allocated_count < private_table_count {
let private_frame = match allocator.alloc() {
Some(frame) => frame,
None => {
Self::discard_private_tables(&mut private_tables, allocated_count, allocator);
return Err(MapError::OutOfFrames);
}
};
if PageTableEntry::new_table(
private_frame.frame_address(),
permissions.user_accessible,
self.config,
)
.is_err()
{
unsafe { allocator.dealloc(private_frame) };
Self::discard_private_tables(&mut private_tables, allocated_count, allocator);
return Err(MapError::PhysicalAddressTooLarge);
}
private_tables[allocated_count] = Some(private_frame);
allocated_count += 1;
}
let prepare_result = (|| {
for private_index in 0..private_table_count {
let private_frame = private_tables[private_index]
.as_ref()
.unwrap()
.frame_address();
if private_index + 1 < private_table_count {
let child = private_tables[private_index + 1]
.as_ref()
.unwrap()
.frame_address();
let child_entry =
PageTableEntry::new_table(child, permissions.user_accessible, self.config)
.map_err(|_| MapError::PhysicalAddressTooLarge)?;
let child_index = levels[missing_depth + private_index + 1].index(address);
self.table_mut(private_frame)
.ok_or(MapError::PageTableOutsideDirectMap)?[child_index] = child_entry;
} else {
self.table_mut(private_frame)
.ok_or(MapError::PageTableOutsideDirectMap)?[p1_index(address)] =
leaf_entry;
}
}
PageTableEntry::new_table(
private_tables[0].as_ref().unwrap().frame_address(),
permissions.user_accessible,
self.config,
)
.map_err(|_| MapError::PhysicalAddressTooLarge)
})();
if result.is_err() {
self.rollback_tables(&new_tables, new_table_count, allocator);
return result;
let publication_entry = match prepare_result {
Ok(entry) => entry,
Err(error) => {
Self::discard_private_tables(&mut private_tables, allocated_count, allocator);
return Err(error);
}
};
self.apply_user_upgrades(&user_upgrades, user_upgrade_count);
self.table_mut(publication_location.table)
.expect("validated publication table left the direct map")
[publication_location.index] = publication_entry;
// The published page table now owns these frames.
for frame in private_tables[..allocated_count]
.iter_mut()
.flat_map(Option::take)
{
let _ = frame.into_raw();
}
self.flush_tlb_if_active(mapped_addr);
@@ -470,7 +577,7 @@ impl PageTable {
&mut self,
mapped_addr: VirtualAddr,
allocator: &mut FrameAllocator,
) -> Result<PhysicalFrame, UnmapError> {
) -> Result<FrameAddr, UnmapError> {
if !self.is_canonical(mapped_addr.as_usize()) {
return Err(UnmapError::InvalidVirtualAddress);
}
@@ -479,48 +586,61 @@ impl PageTable {
return Err(UnmapError::VirtualAddressUnaligned);
}
let config = self.config;
let address = mapped_addr.as_usize();
let levels = self.config.mode.intermediate_levels();
let mut table_frames: [Option<FrameAddr>; MAX_INTERMEDIATE_LEVELS + 1] =
core::array::from_fn(|_| None);
table_frames[0] = Some(self.frame.frame_address());
let pdpt_frame = self.get_next_level(self.frame, p4_index(mapped_addr.as_usize()))?;
let pd_frame = self.get_next_level(pdpt_frame, p3_index(mapped_addr.as_usize()))?;
let pt_frame = self.get_next_level(pd_frame, p2_index(mapped_addr.as_usize()))?;
let pt_table = self
.table_mut(pt_frame)
.ok_or(UnmapError::PageTableOutsideDirectMap)?;
let entry = pt_table[p1_index(mapped_addr.as_usize())];
if !entry.is_present() {
return Err(UnmapError::PageNotMapped);
let mut current_table = self.frame.frame_address();
for (depth, &level) in levels.iter().enumerate() {
current_table = self.get_next_level(current_table, level.index(address), level)?;
table_frames[depth + 1] = Some(current_table);
}
let frame = entry
.frame(config)
.ok_or(UnmapError::InvalidPageTableEntry)?;
pt_table[p1_index(mapped_addr.as_usize())] = PageTableEntry::null();
let config = self.config;
let page_table = self
.table_mut(current_table)
.ok_or(UnmapError::PageTableOutsideDirectMap)?;
let entry_index = p1_index(address);
let entry = page_table[entry_index];
let frame = entry.leaf_frame(config).ok_or(UnmapError::PageNotMapped)?;
page_table[entry_index] = PageTableEntry::null();
if pt_table.is_empty() {
self.table_mut(pd_frame).unwrap()[p2_index(mapped_addr.as_usize())] =
let mut child_is_empty = page_table.iter().all(|entry| !entry.is_present());
let mut deallocatable_frames: [Option<FrameAddr>; MAX_INTERMEDIATE_LEVELS] =
core::array::from_fn(|_| None);
let mut deallocatable_count = 0;
for depth in (0..levels.len()).rev() {
if !child_is_empty {
break;
}
let parent_frame_addr = table_frames[depth].unwrap();
let child_frame = table_frames[depth + 1].unwrap();
self.table_mut(parent_frame_addr)
.expect("validated page table left the direct map")[levels[depth].index(address)] =
PageTableEntry::null();
unsafe { allocator.dealloc(pt_frame) };
if self.table(pd_frame).unwrap().is_empty() {
self.table_mut(pdpt_frame).unwrap()[p3_index(mapped_addr.as_usize())] =
PageTableEntry::null();
unsafe { allocator.dealloc(pd_frame) };
deallocatable_frames[deallocatable_count] = Some(child_frame);
deallocatable_count += 1;
if self.table(pdpt_frame).unwrap().is_empty() {
self.table_mut(self.frame).unwrap()[p4_index(mapped_addr.as_usize())] =
PageTableEntry::null();
unsafe { allocator.dealloc(pdpt_frame) };
}
if depth > 0 {
child_is_empty = self
.table(parent_frame_addr)
.expect("validated page table left the direct map")
.iter()
.all(|entry| !entry.is_present());
}
}
self.flush_tlb_if_active(mapped_addr);
for frame in deallocatable_frames[..deallocatable_count].iter().flatten() {
unsafe { allocator.dealloc(OwnedFrame::from_raw(*frame)) };
}
Ok(frame)
}
@@ -543,11 +663,45 @@ impl PageTable {
unsafe {
asm!(
"mov cr3, {}",
in(reg) self.frame.start_address().as_usize(),
in(reg) self.frame.frame_address().start_address().as_usize(),
options(nostack, preserves_flags)
);
};
}
fn destroy_children(&mut self, table: FrameAddr, depth: usize, allocator: &mut FrameAllocator) {
let levels = self.config.mode.intermediate_levels();
if depth == levels.len() {
return;
}
for idx in 0..PAGE_TABLE_ENTRIES {
let entry = self.table(table).expect("page table outside direct map")[idx];
if !entry.is_present() || entry.is_huge() {
continue;
}
let child = entry
.table_frame(self.config)
.expect("invalid page table entry");
self.destroy_children(child, depth + 1, allocator);
self.table_mut(table)
.expect("page table outside direct map")[idx] = PageTableEntry::null();
unsafe { allocator.dealloc(OwnedFrame::from_raw(child)) };
}
}
pub unsafe fn destroy(mut self, allocator: &mut FrameAllocator) {
assert!(!self.is_active(), "attempted to destroy active page table");
self.destroy_children(self.frame.frame_address(), 0, allocator);
unsafe { allocator.dealloc(self.frame) };
}
}
fn translate_huge_page(
@@ -563,18 +717,6 @@ fn translate_huge_page(
physical_base.checked_add(offset).map(PhysicalAddr::new)
}
fn p4_index(addr: usize) -> usize {
(addr >> 39) & 0x1FF
}
fn p3_index(addr: usize) -> usize {
(addr >> 30) & 0x1FF
}
fn p2_index(addr: usize) -> usize {
(addr >> 21) & 0x1FF
}
fn p1_index(addr: usize) -> usize {
(addr >> 12) & 0x1FF
}
@@ -583,7 +725,7 @@ fn page_offset(addr: usize) -> usize {
addr & 0xFFF
}
unsafe fn read_cr3(config: PagingConfig) -> PhysicalFrame {
unsafe fn read_cr3(config: PagingConfig) -> FrameAddr {
let value: usize;
unsafe {
asm!(
@@ -593,6 +735,6 @@ unsafe fn read_cr3(config: PagingConfig) -> PhysicalFrame {
);
}
PhysicalFrame::from_start_address(PhysicalAddr::new(value & config.physical_address_mask()))
FrameAddr::from_start_address(PhysicalAddr::new(value & config.physical_address_mask()))
.expect("CR3 contains an unaligned page-table address")
}
+29
View File
@@ -0,0 +1,29 @@
use core::arch::asm;
use crate::arch::port::write_u8;
const PIT_CHANNEL_0: u16 = 0x40;
const PIT_COMMAND: u16 = 0x43;
const CHANNEL_0: u8 = 0b00 << 6;
// access mode = lobyte/hibyte
const LOW_HIGH: u8 = 0b11 << 4;
// interrupt on terminal count
const MODE_0: u8 = 0b000 << 1;
const BINARY: u8 = 0;
pub const PIT_FREQUENCY: u64 = 1_193_182;
pub const PIT_CALIBRATION_COUNT: u16 = u16::MAX;
pub struct Pit;
impl Pit {
pub fn start_one_shot(count: u16) {
unsafe {
write_u8(PIT_COMMAND, CHANNEL_0 | LOW_HIGH | MODE_0 | BINARY);
write_u8(PIT_CHANNEL_0, count as u8);
write_u8(PIT_CHANNEL_0, (count >> 8) as u8);
}
}
}
+2 -2
View File
@@ -8,7 +8,7 @@ pub unsafe fn read_u8(port: u16) -> u8 {
"in al, dx",
in("dx") port,
out("al") value,
options(nomem, nostack, preserves_flags),
options(nostack, preserves_flags),
);
}
value
@@ -62,7 +62,7 @@ pub unsafe fn write_u8(port: u16, value: u8) {
"out dx, al",
in("dx") port,
in("al") value,
options(nomem, nostack, preserves_flags),
options(nostack, preserves_flags),
);
}
}
+82
View File
@@ -0,0 +1,82 @@
use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use crate::{
arch::{
apic::{self, LocalApic},
disable_interrupts,
io_apic::IoApic,
x86_64::{
interrupts::enable_interrupts,
pit::{PIT_CALIBRATION_COUNT, PIT_FREQUENCY, Pit},
},
},
platform::acpi::IsaIrqRoute,
};
static PIT_FIRED: AtomicBool = AtomicBool::new(false);
static LAPIC_COUNT_AT_PIT: AtomicU32 = AtomicU32::new(0);
#[derive(Debug)]
pub enum TimerCalibrationError {
PitTimeout,
IoApicNotHandled,
FrequencyOverflow,
InvalidTimerCount,
}
pub fn calibrate_local_apic(
local_apic: &mut LocalApic,
io_apic: &mut IoApic,
pit_route: IsaIrqRoute,
) -> Result<u64, TimerCalibrationError> {
PIT_FIRED.store(false, Ordering::SeqCst);
LAPIC_COUNT_AT_PIT.store(0, Ordering::SeqCst);
io_apic
.unmask(pit_route.gsi)
.map_err(|_| TimerCalibrationError::IoApicNotHandled)?;
local_apic.start_calibration_counter();
Pit::start_one_shot(PIT_CALIBRATION_COUNT);
enable_interrupts();
while !PIT_FIRED.load(Ordering::SeqCst) {
if apic::current_timer_count() == 0 {
disable_interrupts();
let _ = io_apic.mask(pit_route.gsi);
local_apic.stop_timer();
return Err(TimerCalibrationError::PitTimeout);
}
core::hint::spin_loop();
}
disable_interrupts();
io_apic
.mask(pit_route.gsi)
.map_err(|_| TimerCalibrationError::IoApicNotHandled)?;
local_apic.stop_timer();
let elapsed = u32::MAX - LAPIC_COUNT_AT_PIT.load(Ordering::SeqCst);
if elapsed == 0 {
return Err(TimerCalibrationError::InvalidTimerCount);
}
let ticks_per_second = (elapsed as u64)
.checked_mul(PIT_FREQUENCY)
.ok_or(TimerCalibrationError::FrequencyOverflow)?
/ PIT_CALIBRATION_COUNT as u64;
Ok(ticks_per_second)
}
pub fn record_pit_calibration() {
let current = apic::current_timer_count();
LAPIC_COUNT_AT_PIT.store(current, Ordering::SeqCst);
PIT_FIRED.store(true, Ordering::SeqCst);
}