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 _,
}
}
}