280 lines
7.6 KiB
Rust
280 lines
7.6 KiB
Rust
//! x86-64 I/O APIC driver implementation
|
|
use abi::error::Error;
|
|
use acpi::platform::interrupt::{Apic as AcpiApic, Polarity, TriggerMode};
|
|
use device_api::{
|
|
interrupt::{
|
|
ExternalInterruptController, FixedInterruptTable, InterruptHandler, InterruptTable,
|
|
IrqLevel, IrqOptions, IrqTrigger,
|
|
},
|
|
Device,
|
|
};
|
|
|
|
use crate::{
|
|
arch::x86_64::{apic::local::BSP_APIC_ID, IrqNumber},
|
|
mem::ConvertAddress,
|
|
sync::IrqSafeSpinlock,
|
|
};
|
|
|
|
use super::{APIC_EXTERNAL_OFFSET, MAX_EXTERNAL_VECTORS};
|
|
|
|
// IRQ 0 is timer, IRQ 1 reserved (for now?), +32 offset for exception entries
|
|
const IO_APIC_VECTOR_OFFSET: u32 = 32 + APIC_EXTERNAL_OFFSET;
|
|
|
|
const REG_IOAPIC_VERSION: u32 = 0x01;
|
|
const REG_REDIRECTION_BASE: u32 = 0x10;
|
|
|
|
const ENTRY_LOW_MASK: u32 = 1 << 16;
|
|
const ENTRY_LOW_TRIGGER_LEVEL: u32 = 1 << 15;
|
|
const ENTRY_LOW_POLARITY_LOW: u32 = 1 << 13;
|
|
const ENTRY_LOW_DESTINATION_LOGICAL: u32 = 1 << 11;
|
|
|
|
const ENTRY_HIGH_APIC_ID_SHIFT: u32 = 24;
|
|
|
|
#[derive(Clone, Copy, PartialEq)]
|
|
enum ExternalTriggerMode {
|
|
Edge,
|
|
Level,
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
#[derive(Clone, Copy)]
|
|
struct IsaRedirection {
|
|
gsi_index: u32,
|
|
polarity: bool,
|
|
trigger: ExternalTriggerMode,
|
|
}
|
|
|
|
struct Regs {
|
|
base: usize,
|
|
}
|
|
|
|
struct Inner {
|
|
regs: Regs,
|
|
max_gsi: u32,
|
|
}
|
|
|
|
/// I/O APIC interface. Provides a way to route and control how interrupts from external devices
|
|
/// are handled.
|
|
pub struct IoApic {
|
|
inner: IrqSafeSpinlock<Inner>,
|
|
isa_redirections: [Option<IsaRedirection>; 16],
|
|
|
|
table: IrqSafeSpinlock<FixedInterruptTable<{ MAX_EXTERNAL_VECTORS as usize }>>,
|
|
}
|
|
|
|
impl Regs {
|
|
#[inline]
|
|
fn read(&self, reg: u32) -> u32 {
|
|
let ptr = self.base as *mut u32;
|
|
|
|
unsafe {
|
|
ptr.write_volatile(reg & 0xFF);
|
|
ptr.add(4).read_volatile()
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn write(&self, reg: u32, value: u32) {
|
|
let ptr = self.base as *mut u32;
|
|
|
|
unsafe {
|
|
ptr.write_volatile(reg & 0xFF);
|
|
ptr.add(4).write_volatile(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Inner {
|
|
fn map_gsi(&mut self, gsi: u32, vector: u32, apic_id: u32) -> Result<(), Error> {
|
|
assert!(gsi < self.max_gsi);
|
|
assert!(vector < 0x100);
|
|
|
|
infoln!("map_irq gsi{}, vec{}, apic{}", gsi, vector, apic_id);
|
|
|
|
let mut low = self.regs.read(REG_REDIRECTION_BASE + gsi * 2);
|
|
let mut high = self.regs.read(REG_REDIRECTION_BASE + gsi * 2 + 1);
|
|
|
|
// Vector
|
|
low &= !0xFF;
|
|
low |= vector;
|
|
// Destination - physical
|
|
low &= !ENTRY_LOW_DESTINATION_LOGICAL;
|
|
|
|
// Destination APIC ID
|
|
high &= !(0xFF << ENTRY_HIGH_APIC_ID_SHIFT);
|
|
high |= apic_id << ENTRY_HIGH_APIC_ID_SHIFT;
|
|
|
|
self.regs.write(REG_REDIRECTION_BASE + gsi * 2, low);
|
|
self.regs.write(REG_REDIRECTION_BASE + gsi * 2 + 1, high);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn configure_gsi(&mut self, gsi: u32, options: IrqOptions) {
|
|
assert!(gsi < self.max_gsi);
|
|
|
|
let mut low = self.regs.read(REG_REDIRECTION_BASE + gsi * 2);
|
|
|
|
match options.level {
|
|
IrqLevel::Default => (),
|
|
IrqLevel::ActiveLow => {
|
|
low |= ENTRY_LOW_POLARITY_LOW;
|
|
}
|
|
IrqLevel::ActiveHigh => {
|
|
low &= !ENTRY_LOW_POLARITY_LOW;
|
|
}
|
|
}
|
|
|
|
match options.trigger {
|
|
IrqTrigger::Default => (),
|
|
IrqTrigger::Level => {
|
|
low |= ENTRY_LOW_TRIGGER_LEVEL;
|
|
}
|
|
IrqTrigger::Edge => {
|
|
low &= !ENTRY_LOW_TRIGGER_LEVEL;
|
|
}
|
|
}
|
|
|
|
self.regs.write(REG_REDIRECTION_BASE + gsi * 2, low);
|
|
}
|
|
|
|
fn set_gsi_enabled(&mut self, gsi: u32, enabled: bool) {
|
|
assert!(gsi < self.max_gsi);
|
|
|
|
let low = self.regs.read(REG_REDIRECTION_BASE + gsi * 2);
|
|
if enabled {
|
|
debugln!("Unmask GSI #{}", gsi);
|
|
self.regs
|
|
.write(REG_REDIRECTION_BASE + gsi * 2, low & !ENTRY_LOW_MASK)
|
|
} else {
|
|
self.regs
|
|
.write(REG_REDIRECTION_BASE + gsi * 2, low | ENTRY_LOW_MASK);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Device for IoApic {
|
|
fn display_name(&self) -> &'static str {
|
|
"I/O APIC"
|
|
}
|
|
|
|
unsafe fn init(&'static self) -> Result<(), Error> {
|
|
todo!()
|
|
}
|
|
}
|
|
|
|
impl ExternalInterruptController for IoApic {
|
|
type IrqNumber = IrqNumber;
|
|
|
|
fn register_irq(
|
|
&self,
|
|
irq: Self::IrqNumber,
|
|
options: IrqOptions,
|
|
handler: &'static dyn InterruptHandler,
|
|
) -> Result<(), Error> {
|
|
let mut inner = self.inner.lock();
|
|
let table_vector = self.table.lock().insert_least_loaded(handler)?;
|
|
|
|
let gsi_target_vector = (table_vector as u32) + IO_APIC_VECTOR_OFFSET;
|
|
let bsp_apic = *BSP_APIC_ID.get();
|
|
|
|
infoln!(
|
|
"Binding {:?} ({}) to {}:{}",
|
|
irq,
|
|
handler.display_name(),
|
|
bsp_apic,
|
|
table_vector
|
|
);
|
|
|
|
let gsi = self.translate_irq(irq);
|
|
inner.configure_gsi(gsi, options);
|
|
inner.map_gsi(gsi, gsi_target_vector, bsp_apic)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn enable_irq(&self, irq: Self::IrqNumber) -> Result<(), Error> {
|
|
let mut inner = self.inner.lock();
|
|
let gsi = self.translate_irq(irq);
|
|
inner.set_gsi_enabled(gsi, true);
|
|
Ok(())
|
|
}
|
|
|
|
fn handle_specific_irq(&self, gsi: usize) {
|
|
let table = self.table.lock();
|
|
|
|
if let Some(handler) = table.handler(gsi) {
|
|
handler.handle_irq();
|
|
} else {
|
|
warnln!("No handler set for GSI #{}", gsi);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl IoApic {
|
|
/// Creates an I/O APIC instance from its ACPI definition
|
|
pub fn from_acpi(info: &AcpiApic) -> Result<Self, Error> {
|
|
let ioapic = info.io_apics.first().unwrap();
|
|
|
|
infoln!("I/O APIC at {:#x}", ioapic.address);
|
|
|
|
let mut isa_redirections = [None; 16];
|
|
|
|
for redir in info.interrupt_source_overrides.iter() {
|
|
let index = redir.isa_source as usize;
|
|
let polarity = match redir.polarity {
|
|
Polarity::ActiveLow => false,
|
|
// TODO this may not be correct
|
|
Polarity::ActiveHigh | Polarity::SameAsBus => true,
|
|
};
|
|
let trigger = match redir.trigger_mode {
|
|
TriggerMode::Edge | TriggerMode::SameAsBus => ExternalTriggerMode::Edge,
|
|
TriggerMode::Level => ExternalTriggerMode::Level,
|
|
};
|
|
|
|
debugln!(
|
|
"ISA IRQ #{} -> GSI #{}",
|
|
index,
|
|
redir.global_system_interrupt
|
|
);
|
|
isa_redirections[index].replace(IsaRedirection {
|
|
gsi_index: redir.global_system_interrupt,
|
|
polarity,
|
|
trigger,
|
|
});
|
|
}
|
|
|
|
// TODO properly map this using DeviceMemory
|
|
let regs = Regs {
|
|
base: unsafe { (ioapic.address as usize).virtualize() },
|
|
};
|
|
|
|
let max_gsi = (regs.read(REG_IOAPIC_VERSION) >> 16) & 0xFF;
|
|
|
|
infoln!("Maximum GSI number: {}", max_gsi);
|
|
|
|
let mut inner = Inner { regs, max_gsi };
|
|
|
|
// Mask all GSIs
|
|
for gsi in 0..max_gsi {
|
|
inner.set_gsi_enabled(gsi, false);
|
|
}
|
|
|
|
Ok(Self {
|
|
isa_redirections,
|
|
inner: IrqSafeSpinlock::new(inner),
|
|
table: IrqSafeSpinlock::new(FixedInterruptTable::new()),
|
|
})
|
|
}
|
|
|
|
fn translate_irq(&self, irq: IrqNumber) -> u32 {
|
|
let redir = &self.isa_redirections;
|
|
match irq {
|
|
IrqNumber::Isa(isa) => redir[isa as usize]
|
|
.map(|t| t.gsi_index)
|
|
.unwrap_or(isa as u32),
|
|
IrqNumber::Gsi(gsi) => gsi as _,
|
|
}
|
|
}
|
|
}
|