Dedup the apic code between x86 and x86_64

This commit is contained in:
bjorn3
2024-01-24 21:46:06 +01:00
committed by Jeremy Soller
parent 86fb4208f2
commit 0d86348e41
7 changed files with 8 additions and 701 deletions
-441
View File
@@ -1,441 +0,0 @@
use core::{fmt, ptr};
use alloc::vec::Vec;
use spin::Mutex;
#[cfg(feature = "acpi")]
use crate::acpi::madt::{self, Madt, MadtEntry, MadtIntSrcOverride, MadtIoApic};
use crate::{
arch::interrupt::irq,
memory::Frame,
paging::{
entry::EntryFlags, KernelMapper, Page, PageFlags, PhysicalAddress, RmmA, RmmArch,
VirtualAddress,
},
};
use super::{super::cpuid::cpuid, pic};
pub struct IoApicRegs {
pointer: *const u32,
}
impl IoApicRegs {
fn ioregsel(&self) -> *const u32 {
self.pointer
}
fn iowin(&self) -> *const u32 {
// offset 0x10
unsafe { self.pointer.offset(4) }
}
fn write_ioregsel(&mut self, value: u32) {
unsafe { ptr::write_volatile::<u32>(self.ioregsel() as *mut u32, value) }
}
fn read_iowin(&self) -> u32 {
unsafe { ptr::read_volatile::<u32>(self.iowin()) }
}
fn write_iowin(&mut self, value: u32) {
unsafe { ptr::write_volatile::<u32>(self.iowin() as *mut u32, value) }
}
fn read_reg(&mut self, reg: u8) -> u32 {
self.write_ioregsel(reg.into());
self.read_iowin()
}
fn write_reg(&mut self, reg: u8, value: u32) {
self.write_ioregsel(reg.into());
self.write_iowin(value);
}
pub fn read_ioapicid(&mut self) -> u32 {
self.read_reg(0x00)
}
pub fn write_ioapicid(&mut self, value: u32) {
self.write_reg(0x00, value);
}
pub fn read_ioapicver(&mut self) -> u32 {
self.read_reg(0x01)
}
pub fn read_ioapicarb(&mut self) -> u32 {
self.read_reg(0x02)
}
pub fn read_ioredtbl(&mut self, idx: u8) -> u64 {
assert!(idx < 24);
let lo = self.read_reg(0x10 + idx * 2);
let hi = self.read_reg(0x10 + idx * 2 + 1);
u64::from(lo) | (u64::from(hi) << 32)
}
pub fn write_ioredtbl(&mut self, idx: u8, value: u64) {
assert!(idx < 24);
let lo = value as u32;
let hi = (value >> 32) as u32;
self.write_reg(0x10 + idx * 2, lo);
self.write_reg(0x10 + idx * 2 + 1, hi);
}
pub fn max_redirection_table_entries(&mut self) -> u8 {
let ver = self.read_ioapicver();
((ver & 0x00FF_0000) >> 16) as u8
}
pub fn id(&mut self) -> u8 {
let id_reg = self.read_ioapicid();
((id_reg & 0x0F00_0000) >> 24) as u8
}
}
pub struct IoApic {
regs: Mutex<IoApicRegs>,
gsi_start: u32,
count: u8,
}
impl IoApic {
pub fn new(regs_base: *const u32, gsi_start: u32) -> Self {
let mut regs = IoApicRegs { pointer: regs_base };
let count = regs.max_redirection_table_entries();
Self {
regs: Mutex::new(regs),
gsi_start,
count,
}
}
/// Map an interrupt vector to a physical local APIC ID of a processor (thus physical mode).
pub fn map(&self, idx: u8, info: MapInfo) {
self.regs.lock().write_ioredtbl(idx, info.as_raw())
}
pub fn set_mask(&self, gsi: u32, mask: bool) {
let idx = (gsi - self.gsi_start) as u8;
let mut guard = self.regs.lock();
let mut reg = guard.read_ioredtbl(idx);
reg &= !(1 << 16);
reg |= u64::from(mask) << 16;
guard.write_ioredtbl(idx, reg);
}
}
#[repr(u8)]
#[derive(Clone, Copy, Debug)]
pub enum ApicTriggerMode {
Edge = 0,
Level = 1,
}
#[repr(u8)]
#[derive(Clone, Copy, Debug)]
pub enum ApicPolarity {
ActiveHigh = 0,
ActiveLow = 1,
}
#[repr(u8)]
#[derive(Clone, Copy, Debug)]
pub enum DestinationMode {
Physical = 0,
Logical = 1,
}
#[repr(u8)]
#[derive(Clone, Copy, Debug)]
pub enum DeliveryMode {
Fixed = 0b000,
LowestPriority = 0b001,
Smi = 0b010,
Nmi = 0b100,
Init = 0b101,
ExtInt = 0b111,
}
#[derive(Clone, Copy, Debug)]
pub struct MapInfo {
pub dest: u8,
pub mask: bool,
pub trigger_mode: ApicTriggerMode,
pub polarity: ApicPolarity,
pub dest_mode: DestinationMode,
pub delivery_mode: DeliveryMode,
pub vector: u8,
}
impl MapInfo {
pub fn as_raw(&self) -> u64 {
assert!(self.vector >= 0x20);
assert!(self.vector <= 0xFE);
// TODO: Check for reserved fields.
(u64::from(self.dest) << 56)
| (u64::from(self.mask) << 16)
| ((self.trigger_mode as u64) << 15)
| ((self.polarity as u64) << 13)
| ((self.dest_mode as u64) << 11)
| ((self.delivery_mode as u64) << 8)
| u64::from(self.vector)
}
}
impl fmt::Debug for IoApic {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
struct RedirTable<'a>(&'a Mutex<IoApicRegs>);
impl<'a> fmt::Debug for RedirTable<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut guard = self.0.lock();
let count = guard.max_redirection_table_entries();
f.debug_list()
.entries((0..count).map(|i| guard.read_ioredtbl(i)))
.finish()
}
}
f.debug_struct("IoApic")
.field("redir_table", &RedirTable(&self.regs))
.field("gsi_start", &self.gsi_start)
.field("count", &self.count)
.finish()
}
}
#[derive(Clone, Copy, Debug)]
pub enum TriggerMode {
ConformsToSpecs,
Edge,
Level,
}
#[derive(Clone, Copy, Debug)]
pub enum Polarity {
ConformsToSpecs,
ActiveHigh,
ActiveLow,
}
#[derive(Clone, Copy, Debug)]
pub struct Override {
bus_irq: u8,
gsi: u32,
trigger_mode: TriggerMode,
polarity: Polarity,
}
// static mut because only the AP initializes the I/O Apic, and when that is done, it's solely
// accessed immutably.
static mut IOAPICS: Option<Vec<IoApic>> = None;
// static mut for the same reason as above
static mut SRC_OVERRIDES: Option<Vec<Override>> = None;
pub fn ioapics() -> &'static [IoApic] {
unsafe { IOAPICS.as_ref().map_or(&[], |vector| &vector[..]) }
}
pub fn src_overrides() -> &'static [Override] {
unsafe { SRC_OVERRIDES.as_ref().map_or(&[], |vector| &vector[..]) }
}
#[cfg(feature = "acpi")]
pub unsafe fn handle_ioapic(mapper: &mut KernelMapper, madt_ioapic: &'static MadtIoApic) {
// map the I/O APIC registers
let frame = Frame::containing_address(PhysicalAddress::new(madt_ioapic.address as usize));
let page = Page::containing_address(VirtualAddress::new(crate::IOAPIC_OFFSET));
assert!(mapper.translate(page.start_address()).is_none());
mapper
.get_mut()
.expect("expected KernelMapper not to be locked re-entrant while mapping I/O APIC memory")
.map_phys(
page.start_address(),
frame.start_address(),
PageFlags::new()
.write(true)
.custom_flag(EntryFlags::NO_CACHE.bits(), true),
)
.expect("failed to map I/O APIC")
.flush();
let ioapic_registers = page.start_address().data() as *const u32;
let ioapic = IoApic::new(ioapic_registers, madt_ioapic.gsi_base);
assert_eq!(
ioapic.regs.lock().id(),
madt_ioapic.id,
"mismatched ACPI MADT I/O APIC ID, and the ID reported by the I/O APIC"
);
IOAPICS.get_or_insert_with(Vec::new).push(ioapic);
}
#[cfg(feature = "acpi")]
pub unsafe fn handle_src_override(src_override: &'static MadtIntSrcOverride) {
let flags = src_override.flags;
let polarity_raw = (flags & 0x0003) as u8;
let trigger_mode_raw = ((flags & 0x000C) >> 2) as u8;
let polarity = match polarity_raw {
0b00 => Polarity::ConformsToSpecs,
0b01 => Polarity::ActiveHigh,
0b10 => return, // reserved
0b11 => Polarity::ActiveLow,
_ => unreachable!(),
};
let trigger_mode = match trigger_mode_raw {
0b00 => TriggerMode::ConformsToSpecs,
0b01 => TriggerMode::Edge,
0b10 => return, // reserved
0b11 => TriggerMode::Level,
_ => unreachable!(),
};
let over = Override {
bus_irq: src_override.irq_source,
gsi: src_override.gsi_base,
polarity,
trigger_mode,
};
SRC_OVERRIDES.get_or_insert_with(Vec::new).push(over);
}
pub unsafe fn init(active_table: &mut KernelMapper) {
let bsp_apic_id = cpuid()
.unwrap()
.get_feature_info()
.unwrap()
.initial_local_apic_id(); // TODO: remove unwraps
// search the madt for all IOAPICs.
#[cfg(feature = "acpi")]
{
let madt: &'static Madt = match madt::MADT.as_ref() {
Some(m) => m,
// TODO: Parse MP tables too.
None => return,
};
if madt.flags & madt::FLAG_PCAT != 0 {
pic::disable();
}
// find all I/O APICs (usually one).
for entry in madt.iter() {
match entry {
MadtEntry::IoApic(ioapic) => handle_ioapic(active_table, ioapic),
MadtEntry::IntSrcOverride(src_override) => handle_src_override(src_override),
_ => (),
}
}
}
println!(
"I/O APICs: {:?}, overrides: {:?}",
ioapics(),
src_overrides()
);
// map the legacy PC-compatible IRQs (0-15) to 32-47, just like we did with 8259 PIC (if it
// wouldn't have been disabled due to this I/O APIC)
for legacy_irq in 0..=15 {
let (gsi, trigger_mode, polarity) = match get_override(legacy_irq) {
Some(over) => (over.gsi, over.trigger_mode, over.polarity),
None => {
if src_overrides()
.iter()
.any(|over| over.gsi == u32::from(legacy_irq) && over.bus_irq != legacy_irq)
&& !src_overrides()
.iter()
.any(|over| over.bus_irq == legacy_irq)
{
// there's an IRQ conflict, making this legacy IRQ inaccessible.
continue;
}
(
legacy_irq.into(),
TriggerMode::ConformsToSpecs,
Polarity::ConformsToSpecs,
)
}
};
let apic = match find_ioapic(gsi) {
Some(ioapic) => ioapic,
None => {
println!("Unable to find a suitable APIC for legacy IRQ {} (GSI {}). It will not be mapped.", legacy_irq, gsi);
continue;
}
};
let redir_tbl_index = (gsi - apic.gsi_start) as u8;
let map_info = MapInfo {
// only send to the BSP
dest: bsp_apic_id,
dest_mode: DestinationMode::Physical,
delivery_mode: DeliveryMode::Fixed,
mask: false,
polarity: match polarity {
Polarity::ActiveHigh => ApicPolarity::ActiveHigh,
Polarity::ActiveLow => ApicPolarity::ActiveLow,
Polarity::ConformsToSpecs => ApicPolarity::ActiveHigh,
},
trigger_mode: match trigger_mode {
TriggerMode::Edge => ApicTriggerMode::Edge,
TriggerMode::Level => ApicTriggerMode::Level,
TriggerMode::ConformsToSpecs => ApicTriggerMode::Edge,
},
vector: 32 + legacy_irq,
};
apic.map(redir_tbl_index, map_info);
}
println!(
"I/O APICs: {:?}, overrides: {:?}",
ioapics(),
src_overrides()
);
irq::set_irq_method(irq::IrqMethod::Apic);
// tell the firmware that we're using APIC rather than the default 8259 PIC.
// FIXME: With ACPI moved to userspace, we should instead allow userspace to check whether the
// IOAPIC has been initialized, and then subsequently let some ACPI driver call the AML from
// userspace.
/*#[cfg(feature = "acpi")]
{
let method = {
let namespace_guard = crate::acpi::ACPI_TABLE.namespace.read();
if let Some(value) = namespace_guard.as_ref().unwrap().get("\\_PIC") {
value.get_as_method().ok()
} else {
None
}
};
if let Some(m) = method {
m.execute("\\_PIC".into(), vec!(crate::acpi::aml::AmlValue::Integer(1)));
}
}*/
}
fn get_override(irq: u8) -> Option<&'static Override> {
src_overrides().iter().find(|over| over.bus_irq == irq)
}
fn resolve(irq: u8) -> u32 {
get_override(irq).map_or(u32::from(irq), |over| over.gsi)
}
fn find_ioapic(gsi: u32) -> Option<&'static IoApic> {
ioapics()
.iter()
.find(|apic| gsi >= apic.gsi_start && gsi < apic.gsi_start + u32::from(apic.count))
}
pub unsafe fn mask(irq: u8) {
let gsi = resolve(irq);
let apic = match find_ioapic(gsi) {
Some(a) => a,
None => return,
};
apic.set_mask(gsi, true);
}
pub unsafe fn unmask(irq: u8) {
let gsi = resolve(irq);
let apic = match find_ioapic(gsi) {
Some(a) => a,
None => return,
};
apic.set_mask(gsi, false);
}
-2
View File
@@ -1,6 +1,4 @@
pub use crate::arch::x86_shared::device::*;
pub mod ioapic;
pub mod local_apic;
use crate::paging::KernelMapper;
-251
View File
@@ -1,251 +0,0 @@
use core::{
ptr::{read_volatile, write_volatile},
sync::atomic::{self, AtomicU64},
};
use x86::msr::*;
use crate::paging::{KernelMapper, PageFlags, PhysicalAddress, RmmA, RmmArch};
use super::super::cpuid::cpuid;
pub static mut LOCAL_APIC: LocalApic = LocalApic {
address: 0,
x2: false,
};
pub unsafe fn init(active_table: &mut KernelMapper) {
LOCAL_APIC.init(active_table);
}
pub unsafe fn init_ap() {
LOCAL_APIC.init_ap();
}
/// Local APIC
pub struct LocalApic {
pub address: usize,
pub x2: bool,
}
#[derive(Debug)]
struct NoFreqInfo;
static BSP_APIC_ID: AtomicU64 = AtomicU64::new(0xFFFF_FFFF_FFFF_FFFF);
#[no_mangle]
pub fn bsp_apic_id() -> Option<u32> {
let value = BSP_APIC_ID.load(atomic::Ordering::SeqCst);
if value <= u64::from(u32::max_value()) {
Some(value as u32)
} else {
None
}
}
impl LocalApic {
unsafe fn init(&mut self, mapper: &mut KernelMapper) {
let mapper = mapper
.get_mut()
.expect("expected KernelMapper not to be locked re-entrant while initializing LAPIC");
let physaddr = PhysicalAddress::new(rdmsr(IA32_APIC_BASE) as usize & 0xFFFF_0000);
let virtaddr = RmmA::phys_to_virt(physaddr);
self.address = virtaddr.data();
self.x2 = cpuid().map_or(false, |cpuid| {
cpuid
.get_feature_info()
.map_or(false, |feature_info| feature_info.has_x2apic())
});
if !self.x2 {
log::info!("Detected xAPIC at {:#x}", physaddr.data());
if let Some((_entry, _, flush)) = mapper.unmap_phys(virtaddr, true) {
// Unmap xAPIC page if already mapped
flush.flush();
}
mapper
.map_phys(virtaddr, physaddr, PageFlags::new().write(true))
.expect("failed to map local APIC memory")
.flush();
} else {
log::info!("Detected x2APIC");
}
self.init_ap();
BSP_APIC_ID.store(u64::from(self.id()), atomic::Ordering::SeqCst);
}
unsafe fn init_ap(&mut self) {
if self.x2 {
wrmsr(IA32_APIC_BASE, rdmsr(IA32_APIC_BASE) | 1 << 10);
wrmsr(IA32_X2APIC_SIVR, 0x100);
} else {
self.write(0xF0, 0x100);
}
self.setup_error_int();
//self.setup_timer();
}
unsafe fn read(&self, reg: u32) -> u32 {
read_volatile((self.address + reg as usize) as *const u32)
}
unsafe fn write(&mut self, reg: u32, value: u32) {
write_volatile((self.address + reg as usize) as *mut u32, value);
}
pub fn id(&self) -> u32 {
if self.x2 {
unsafe { rdmsr(IA32_X2APIC_APICID) as u32 }
} else {
unsafe { self.read(0x20) }
}
}
pub fn version(&self) -> u32 {
if self.x2 {
unsafe { rdmsr(IA32_X2APIC_VERSION) as u32 }
} else {
unsafe { self.read(0x30) }
}
}
pub fn icr(&self) -> u64 {
if self.x2 {
unsafe { rdmsr(IA32_X2APIC_ICR) }
} else {
unsafe { (self.read(0x310) as u64) << 32 | self.read(0x300) as u64 }
}
}
pub fn set_icr(&mut self, value: u64) {
if self.x2 {
unsafe {
wrmsr(IA32_X2APIC_ICR, value);
}
} else {
unsafe {
const PENDING: u32 = 1 << 12;
while self.read(0x300) & PENDING == PENDING {
core::hint::spin_loop();
}
self.write(0x310, (value >> 32) as u32);
self.write(0x300, value as u32);
while self.read(0x300) & PENDING == PENDING {
core::hint::spin_loop();
}
}
}
}
pub fn ipi(&mut self, apic_id: usize) {
let mut icr = 0x4040;
if self.x2 {
icr |= (apic_id as u64) << 32;
} else {
icr |= (apic_id as u64) << 56;
}
self.set_icr(icr);
}
// Not used just yet, but allows triggering an NMI to another processor.
pub fn ipi_nmi(&mut self, apic_id: u32) {
let shift = if self.x2 { 32 } else { 56 };
self.set_icr((u64::from(apic_id) << shift) | (1 << 14) | (0b100 << 8));
}
pub unsafe fn eoi(&mut self) {
if self.x2 {
wrmsr(IA32_X2APIC_EOI, 0);
} else {
self.write(0xB0, 0);
}
}
/// Reads the Error Status Register.
pub unsafe fn esr(&mut self) -> u32 {
if self.x2 {
// update the ESR to the current state of the local apic.
wrmsr(IA32_X2APIC_ESR, 0);
// read the updated value
rdmsr(IA32_X2APIC_ESR) as u32
} else {
self.write(0x280, 0);
self.read(0x280)
}
}
pub unsafe fn lvt_timer(&mut self) -> u32 {
if self.x2 {
rdmsr(IA32_X2APIC_LVT_TIMER) as u32
} else {
self.read(0x320)
}
}
pub unsafe fn set_lvt_timer(&mut self, value: u32) {
if self.x2 {
wrmsr(IA32_X2APIC_LVT_TIMER, u64::from(value));
} else {
self.write(0x320, value);
}
}
pub unsafe fn init_count(&mut self) -> u32 {
if self.x2 {
rdmsr(IA32_X2APIC_INIT_COUNT) as u32
} else {
self.read(0x380)
}
}
pub unsafe fn set_init_count(&mut self, initial_count: u32) {
if self.x2 {
wrmsr(IA32_X2APIC_INIT_COUNT, u64::from(initial_count));
} else {
self.write(0x380, initial_count);
}
}
pub unsafe fn cur_count(&mut self) -> u32 {
if self.x2 {
rdmsr(IA32_X2APIC_CUR_COUNT) as u32
} else {
self.read(0x390)
}
}
pub unsafe fn div_conf(&mut self) -> u32 {
if self.x2 {
rdmsr(IA32_X2APIC_DIV_CONF) as u32
} else {
self.read(0x3E0)
}
}
pub unsafe fn set_div_conf(&mut self, div_conf: u32) {
if self.x2 {
wrmsr(IA32_X2APIC_DIV_CONF, u64::from(div_conf));
} else {
self.write(0x3E0, div_conf);
}
}
pub unsafe fn lvt_error(&mut self) -> u32 {
if self.x2 {
rdmsr(IA32_X2APIC_LVT_ERROR) as u32
} else {
self.read(0x370)
}
}
pub unsafe fn set_lvt_error(&mut self, lvt_error: u32) {
if self.x2 {
wrmsr(IA32_X2APIC_LVT_ERROR, u64::from(lvt_error));
} else {
self.write(0x370, lvt_error);
}
}
unsafe fn setup_error_int(&mut self) {
let vector = 49u32;
self.set_lvt_error(vector);
}
}
#[repr(u8)]
pub enum LvtTimerMode {
OneShot = 0b00,
Periodic = 0b01,
TscDeadline = 0b10,
}
-2
View File
@@ -1,6 +1,4 @@
pub use crate::arch::x86_shared::device::*;
pub mod ioapic;
pub mod local_apic;
use crate::paging::KernelMapper;
@@ -12,7 +12,8 @@ use crate::{
paging::{entry::EntryFlags, KernelMapper, Page, PageFlags, PhysicalAddress, RmmA, RmmArch},
};
use super::{super::cpuid::cpuid, pic};
use crate::arch::cpuid::cpuid;
use super::pic;
pub struct IoApicRegs {
pointer: *const u32,
@@ -4,9 +4,9 @@ use core::{
};
use x86::msr::*;
use crate::paging::{KernelMapper, PageFlags, PhysicalAddress, RmmA, RmmArch, VirtualAddress};
use crate::paging::{KernelMapper, PageFlags, PhysicalAddress, RmmA, RmmArch};
use super::super::cpuid::cpuid;
use crate::arch::cpuid::cpuid;
pub static mut LOCAL_APIC: LocalApic = LocalApic {
address: 0,
@@ -36,7 +36,7 @@ static BSP_APIC_ID: AtomicU32 = AtomicU32::new(u32::max_value());
pub fn bsp_apic_id() -> Option<u32> {
let value = BSP_APIC_ID.load(atomic::Ordering::SeqCst);
if value < u32::max_value() {
Some(value as u32)
Some(value)
} else {
None
}
@@ -49,7 +49,7 @@ impl LocalApic {
.expect("expected KernelMapper not to be locked re-entrant while initializing LAPIC");
let physaddr = PhysicalAddress::new(rdmsr(IA32_APIC_BASE) as usize & 0xFFFF_0000);
let virtaddr = VirtualAddress::new(crate::LAPIC_OFFSET);
let virtaddr = RmmA::phys_to_virt(physaddr);
self.address = virtaddr.data();
self.x2 = cpuid().map_or(false, |cpuid| {
+2
View File
@@ -1,6 +1,8 @@
pub mod cpu;
#[cfg(feature = "acpi")]
pub mod hpet;
pub mod ioapic;
pub mod local_apic;
pub mod pic;
pub mod pit;
pub mod rtc;