364 lines
10 KiB
Rust
364 lines
10 KiB
Rust
//! x86-64 Local APIC driver implementation
|
|
use core::sync::atomic::Ordering;
|
|
|
|
use abi::error::Error;
|
|
use alloc::vec::Vec;
|
|
use device_api::{
|
|
interrupt::{
|
|
IpiDeliveryTarget, LocalInterruptController, MessageInterruptController, MsiHandler,
|
|
MsiInfo,
|
|
},
|
|
Device,
|
|
};
|
|
use kernel_util::{sync::IrqSafeSpinlock, util::OneTimeInit};
|
|
use tock_registers::{
|
|
interfaces::{ReadWriteable, Readable, Writeable},
|
|
register_bitfields, register_structs,
|
|
registers::{ReadOnly, ReadWrite, WriteOnly},
|
|
};
|
|
|
|
use crate::{
|
|
arch::{
|
|
x86_64::{
|
|
apic::APIC_MSI_OFFSET, mem::table::L3, registers::MSR_IA32_APIC_BASE, smp::CPU_COUNT,
|
|
},
|
|
CpuMessage,
|
|
},
|
|
mem::{
|
|
address::{FromRaw, IntoRaw},
|
|
device::DeviceMemoryIo,
|
|
PhysicalAddress,
|
|
},
|
|
task::Cpu,
|
|
};
|
|
|
|
use super::{
|
|
APIC_IPI_VECTOR, APIC_LINT0_VECTOR, APIC_LINT1_VECTOR, APIC_SPURIOUS_VECTOR, APIC_TIMER_VECTOR,
|
|
};
|
|
|
|
const TIMER_INTERVAL: u32 = 150000;
|
|
|
|
/// When initialized, contains the Local APIC ID of the bootstrap processor
|
|
pub static BSP_APIC_ID: OneTimeInit<u32> = OneTimeInit::new();
|
|
|
|
register_bitfields! {
|
|
u32,
|
|
Id [
|
|
ApicId OFFSET(24) NUMBITS(8) []
|
|
],
|
|
SpuriousVector [
|
|
Vector OFFSET(0) NUMBITS(8) [],
|
|
SoftwareEnable OFFSET(8) NUMBITS(1) [],
|
|
],
|
|
TimerLocalVectorEntry [
|
|
Vector OFFSET(0) NUMBITS(8) [],
|
|
Mask OFFSET(16) NUMBITS(1) [
|
|
Masked = 1,
|
|
Unmasked = 0
|
|
],
|
|
TimerMode OFFSET(17) NUMBITS(1) [
|
|
Periodic = 1,
|
|
OneShot = 0
|
|
]
|
|
],
|
|
LocalVectorEntry [
|
|
Vector OFFSET(0) NUMBITS(8) [],
|
|
Mask OFFSET(16) NUMBITS(1) [
|
|
Masked = 1,
|
|
Unmasked = 0,
|
|
],
|
|
DeliveryMode OFFSET(8) NUMBITS(3) [
|
|
Nmi = 4,
|
|
ExtINT = 7
|
|
],
|
|
],
|
|
ICR0 [
|
|
Vector OFFSET(0) NUMBITS(8) [],
|
|
Destination OFFSET(8) NUMBITS(3) [
|
|
Normal = 1,
|
|
Lowest = 2,
|
|
SMI = 3,
|
|
NMI = 4,
|
|
INIT = 5,
|
|
SIPI = 6
|
|
],
|
|
DeliveryStatus OFFSET(12) NUMBITS(1) [],
|
|
INIT0 OFFSET(14) NUMBITS(1) [
|
|
Deassert = 0,
|
|
Assert = 1,
|
|
],
|
|
INIT1 OFFSET(15) NUMBITS(1) [
|
|
Deassert = 1,
|
|
Assert = 0,
|
|
],
|
|
DestinationType OFFSET(18) NUMBITS(3) [
|
|
Physical = 0,
|
|
This = 1,
|
|
All = 2,
|
|
AllExceptThis = 3,
|
|
]
|
|
],
|
|
ICR1 [
|
|
PhysicalDestination OFFSET(24) NUMBITS(4) []
|
|
],
|
|
}
|
|
|
|
register_structs! {
|
|
#[allow(non_snake_case, missing_docs)]
|
|
Regs {
|
|
(0x00 => _0),
|
|
(0x20 => Id: ReadOnly<u32, Id::Register>),
|
|
(0x24 => _1),
|
|
(0x80 => TaskPriorityRegister: ReadWrite<u32>),
|
|
(0x84 => _13),
|
|
(0xB0 => EndOfInterrupt: WriteOnly<u32>),
|
|
(0xB4 => _2),
|
|
(0xF0 => SpuriousVector: ReadWrite<u32, SpuriousVector::Register>),
|
|
(0xF4 => _3),
|
|
(0x100 => ISR0: ReadOnly<u32>),
|
|
(0x104 => _14),
|
|
(0x280 => ErrorStatus: ReadOnly<u32>),
|
|
(0x284 => _4),
|
|
(0x300 => ICR0: ReadWrite<u32, ICR0::Register>),
|
|
(0x304 => _5),
|
|
(0x310 => ICR1: ReadWrite<u32, ICR1::Register>),
|
|
(0x314 => _6),
|
|
(0x320 => TimerLocalVectorEntry: ReadWrite<u32, TimerLocalVectorEntry::Register>),
|
|
(0x324 => _7),
|
|
(0x350 => LInt0: ReadWrite<u32, LocalVectorEntry::Register>),
|
|
(0x354 => _8),
|
|
(0x360 => LInt1: ReadWrite<u32, LocalVectorEntry::Register>),
|
|
(0x364 => _9),
|
|
(0x380 => TimerInitCount: ReadWrite<u32>),
|
|
(0x384 => _10),
|
|
(0x390 => TimerCurrentCount: ReadOnly<u32>),
|
|
(0x394 => _11),
|
|
(0x3E0 => TimerDivideConfig: ReadWrite<u32>),
|
|
(0x3E4 => _12),
|
|
(0x530 => @END),
|
|
}
|
|
}
|
|
|
|
/// Per-processor local APIC interface
|
|
pub struct LocalApic {
|
|
regs: DeviceMemoryIo<'static, Regs>,
|
|
msi_vectors: IrqSafeSpinlock<Vec<&'static dyn MsiHandler>>,
|
|
}
|
|
|
|
unsafe impl Send for LocalApic {}
|
|
unsafe impl Sync for LocalApic {}
|
|
|
|
impl Device for LocalApic {
|
|
fn display_name(&self) -> &'static str {
|
|
"Local APIC"
|
|
}
|
|
}
|
|
|
|
impl MessageInterruptController for LocalApic {
|
|
fn handle_msi(&self, vector: usize) {
|
|
// TODO this is ugly
|
|
let mut i = 0;
|
|
|
|
loop {
|
|
let table = self.msi_vectors.lock();
|
|
let Some(&handler) = table.get(i) else {
|
|
break;
|
|
};
|
|
drop(table);
|
|
|
|
if handler.handle_msi(vector) {
|
|
break;
|
|
}
|
|
|
|
i += 1;
|
|
}
|
|
}
|
|
|
|
fn register_msi(&self, handler: &'static dyn MsiHandler) -> Result<MsiInfo, Error> {
|
|
// TODO only 1 ISR vector allocated for MSIs
|
|
let vector = 0;
|
|
let mut table = self.msi_vectors.lock();
|
|
|
|
table.push(handler);
|
|
|
|
// TODO magic numbers
|
|
let apic_vector = 32 + APIC_MSI_OFFSET + vector;
|
|
|
|
let value = apic_vector;
|
|
let address = Self::base();
|
|
|
|
Ok(MsiInfo {
|
|
address: address.into_raw(),
|
|
value,
|
|
vector: vector as _,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl LocalInterruptController for LocalApic {
|
|
type IpiMessage = CpuMessage;
|
|
|
|
fn send_ipi(&self, target: IpiDeliveryTarget, msg: Self::IpiMessage) -> Result<(), Error> {
|
|
while self.regs.ICR0.matches_all(ICR0::DeliveryStatus::SET) {
|
|
core::hint::spin_loop();
|
|
}
|
|
|
|
// TODO use NMI or regular interrupt depending on severity of the message
|
|
match target {
|
|
IpiDeliveryTarget::OtherCpus => {
|
|
let local = Cpu::local_id();
|
|
for i in 0..CPU_COUNT.load(Ordering::Acquire) {
|
|
if i != local as usize {
|
|
Cpu::push_ipi_queue(i as u32, msg);
|
|
}
|
|
}
|
|
|
|
self.regs.ICR1.write(ICR1::PhysicalDestination.val(0));
|
|
self.regs.ICR0.write(
|
|
ICR0::Vector.val(APIC_IPI_VECTOR + 32)
|
|
+ ICR0::Destination::NMI
|
|
+ ICR0::DestinationType::AllExceptThis,
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
IpiDeliveryTarget::ThisCpu => todo!(),
|
|
IpiDeliveryTarget::Specific(_) => todo!(),
|
|
}
|
|
}
|
|
|
|
unsafe fn init_ap(&self) -> Result<(), Error> {
|
|
todo!()
|
|
}
|
|
}
|
|
|
|
impl LocalApic {
|
|
/// Constructs a new instance of Local APIC.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// Only meant to be called once per processor during their init.
|
|
pub unsafe fn new() -> Self {
|
|
let regs = DeviceMemoryIo::<Regs>::map(Self::base()).unwrap();
|
|
|
|
let id = regs.Id.read(Id::ApicId);
|
|
|
|
if Self::is_bootstrap_cpu() {
|
|
BSP_APIC_ID.init(id);
|
|
}
|
|
|
|
Self::enable();
|
|
// Configure spurious interrupt handler
|
|
regs.SpuriousVector.write(
|
|
SpuriousVector::SoftwareEnable::SET
|
|
+ SpuriousVector::Vector.val(APIC_SPURIOUS_VECTOR + 32),
|
|
);
|
|
|
|
// Configure task priority register
|
|
regs.TaskPriorityRegister.set(0);
|
|
|
|
// Enable timer
|
|
regs.TimerDivideConfig.set(0x3);
|
|
regs.TimerInitCount.set(TIMER_INTERVAL);
|
|
|
|
// Configure local interrupt vectors
|
|
regs.TimerLocalVectorEntry.write(
|
|
TimerLocalVectorEntry::Vector.val(APIC_TIMER_VECTOR + 32)
|
|
+ TimerLocalVectorEntry::Mask::Unmasked
|
|
+ TimerLocalVectorEntry::TimerMode::Periodic,
|
|
);
|
|
// LINT0 unmasked, leave LINT1 masked
|
|
regs.LInt0.write(
|
|
LocalVectorEntry::Mask::Unmasked
|
|
+ LocalVectorEntry::Vector.val(APIC_LINT0_VECTOR + 32)
|
|
+ LocalVectorEntry::DeliveryMode::ExtINT,
|
|
);
|
|
regs.LInt1.write(
|
|
LocalVectorEntry::Mask::Masked + LocalVectorEntry::Vector.val(APIC_LINT1_VECTOR + 32),
|
|
);
|
|
|
|
Self {
|
|
regs,
|
|
msi_vectors: IrqSafeSpinlock::new(Vec::new()),
|
|
}
|
|
}
|
|
|
|
/// Signals local APIC that we've handled the IRQ
|
|
pub fn clear_interrupt(&self) {
|
|
self.regs.EndOfInterrupt.set(0);
|
|
}
|
|
|
|
/// Performs an application processor startup sequence.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// Unsafe: only meant to be called by the BSP during SMP init.
|
|
pub unsafe fn wakeup_cpu(&self, apic_id: u32, entry_vector: PhysicalAddress) {
|
|
infoln!("Waking up apic{}, entry = {:#x}", apic_id, entry_vector);
|
|
|
|
while self.regs.ICR0.matches_all(ICR0::DeliveryStatus::SET) {
|
|
core::hint::spin_loop();
|
|
}
|
|
|
|
let entry_vector = entry_vector.page_index::<L3>();
|
|
|
|
// INIT assert
|
|
self.regs.ICR1.write(ICR1::PhysicalDestination.val(apic_id));
|
|
|
|
self.regs.ICR0.write(
|
|
ICR0::Destination::INIT
|
|
+ ICR0::DestinationType::Physical
|
|
+ ICR0::INIT0::Assert
|
|
+ ICR0::INIT1::Assert,
|
|
);
|
|
|
|
while self.regs.ICR0.matches_all(ICR0::DeliveryStatus::SET) {
|
|
core::hint::spin_loop();
|
|
}
|
|
|
|
// INIT deassert
|
|
self.regs.ICR1.write(ICR1::PhysicalDestination.val(apic_id));
|
|
|
|
self.regs.ICR0.write(
|
|
ICR0::Destination::INIT
|
|
+ ICR0::DestinationType::Physical
|
|
+ ICR0::INIT0::Deassert
|
|
+ ICR0::INIT1::Deassert,
|
|
);
|
|
|
|
while self.regs.ICR0.matches_all(ICR0::DeliveryStatus::SET) {
|
|
core::hint::spin_loop();
|
|
}
|
|
|
|
// Send another SIPI type IPI because the spec says so
|
|
self.regs.ICR1.write(ICR1::PhysicalDestination.val(apic_id));
|
|
|
|
self.regs.ICR0.write(
|
|
ICR0::Vector.val(entry_vector as u32)
|
|
+ ICR0::Destination::SIPI
|
|
+ ICR0::DestinationType::Physical,
|
|
);
|
|
|
|
while self.regs.ICR0.matches_all(ICR0::DeliveryStatus::SET) {
|
|
core::hint::spin_loop();
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn base() -> PhysicalAddress {
|
|
PhysicalAddress::from_raw(MSR_IA32_APIC_BASE.read_base())
|
|
}
|
|
|
|
#[inline]
|
|
fn is_bootstrap_cpu() -> bool {
|
|
MSR_IA32_APIC_BASE.read(MSR_IA32_APIC_BASE::BootstrapCpuCore) != 0
|
|
}
|
|
|
|
#[inline]
|
|
fn enable() {
|
|
MSR_IA32_APIC_BASE.modify(
|
|
MSR_IA32_APIC_BASE::ApicEnable::SET + MSR_IA32_APIC_BASE::ExtendedEnable::CLEAR,
|
|
);
|
|
}
|
|
}
|