// http://download.intel.com/design/chipsets/datashts/29056601.pdf use crate::cpu::{apic, global_pointers::acpi_enabled}; use std::sync::{Mutex, MutexGuard}; const IOAPIC_LOG_VERBOSE: bool = false; const IOREGSEL: u32 = 0; const IOWIN: u32 = 0x10; const IOAPIC_IRQ_COUNT: usize = 24; const IOAPIC_FIRST_IRQ_REG: u32 = 0x10; const IOAPIC_LAST_IRQ_REG: u32 = 0x10 + 2 * IOAPIC_IRQ_COUNT as u32; const IOAPIC_ID: u32 = 0; // must match value in seabios pub const IOAPIC_CONFIG_TRIGGER_MODE_LEVEL: u32 = 1 << 15; const IOAPIC_CONFIG_MASKED: u32 = 1 << 16; const IOAPIC_CONFIG_DELIVS: u32 = 1 << 12; const IOAPIC_CONFIG_REMOTE_IRR: u32 = 1 << 14; const IOAPIC_CONFIG_READONLY_MASK: u32 = IOAPIC_CONFIG_REMOTE_IRR | IOAPIC_CONFIG_DELIVS | 0xFFFE0000; const IOAPIC_DELIVERY_FIXED: u8 = 0; const IOAPIC_DELIVERY_LOWEST_PRIORITY: u8 = 1; const _IOAPIC_DELIVERY_NMI: u8 = 4; const _IOAPIC_DELIVERY_INIT: u8 = 5; const DELIVERY_MODES: [&str; 8] = [ "Fixed (0)", "Lowest Prio (1)", "SMI (2)", "Reserved (3)", "NMI (4)", "INIT (5)", "Reserved (6)", "ExtINT (7)", ]; const DESTINATION_MODES: [&str; 2] = ["physical", "logical"]; // keep in sync with cpu.js #[allow(dead_code)] const IOAPIC_STRUCT_SIZE: usize = 4 * 52; // Note: JavaScript (cpu.get_state_apic) depens on this layout const _: () = assert!(std::mem::offset_of!(Ioapic, ioredtbl_destination) == 24 * 4); const _: () = assert!(std::mem::offset_of!(Ioapic, ioregsel) == 48 * 4); const _: () = assert!(std::mem::offset_of!(Ioapic, irq_value) == 51 * 4); const _: () = assert!(std::mem::size_of::() == IOAPIC_STRUCT_SIZE); #[repr(C)] struct Ioapic { ioredtbl_config: [u32; IOAPIC_IRQ_COUNT], ioredtbl_destination: [u32; IOAPIC_IRQ_COUNT], ioregsel: u32, ioapic_id: u32, irr: u32, irq_value: u32, } static IOAPIC: Mutex = Mutex::new(Ioapic { ioredtbl_config: [IOAPIC_CONFIG_MASKED; IOAPIC_IRQ_COUNT], ioredtbl_destination: [0; IOAPIC_IRQ_COUNT], ioregsel: 0, ioapic_id: IOAPIC_ID, irr: 0, irq_value: 0, }); fn get_ioapic() -> MutexGuard<'static, Ioapic> { IOAPIC.try_lock().unwrap() } #[no_mangle] pub fn get_ioapic_addr() -> u32 { &raw mut *get_ioapic() as u32 } pub fn remote_eoi(apic: &mut apic::Apic, vector: u8) { remote_eoi_internal(&mut get_ioapic(), apic, vector); } fn remote_eoi_internal(ioapic: &mut Ioapic, apic: &mut apic::Apic, vector: u8) { for i in 0..IOAPIC_IRQ_COUNT as u8 { let config = ioapic.ioredtbl_config[i as usize]; if (config & 0xFF) as u8 == vector && config & IOAPIC_CONFIG_REMOTE_IRR != 0 { dbg_log!("Clear remote IRR for irq={:x}", i); ioapic.ioredtbl_config[i as usize] &= !IOAPIC_CONFIG_REMOTE_IRR; check_irq(ioapic, apic, i); } } } fn check_irq(ioapic: &mut Ioapic, apic: &mut apic::Apic, irq: u8) { let mask = 1 << irq; if ioapic.irr & mask == 0 { return; } let config = ioapic.ioredtbl_config[irq as usize]; if config & IOAPIC_CONFIG_MASKED == 0 { let delivery_mode = ((config >> 8) & 7) as u8; let destination_mode = ((config >> 11) & 1) as u8; let vector = (config & 0xFF) as u8; let destination = (ioapic.ioredtbl_destination[irq as usize] >> 24) as u8; let is_level = config & IOAPIC_CONFIG_TRIGGER_MODE_LEVEL == IOAPIC_CONFIG_TRIGGER_MODE_LEVEL; if config & IOAPIC_CONFIG_TRIGGER_MODE_LEVEL == 0 { ioapic.irr &= !mask; } else { ioapic.ioredtbl_config[irq as usize] |= IOAPIC_CONFIG_REMOTE_IRR; if config & IOAPIC_CONFIG_REMOTE_IRR != 0 { dbg_log!("No route: level interrupt and remote IRR still set"); return; } } if delivery_mode == IOAPIC_DELIVERY_FIXED || delivery_mode == IOAPIC_DELIVERY_LOWEST_PRIORITY { apic::route( apic, vector, delivery_mode, is_level, destination, destination_mode, ); } else { dbg_assert!(false, "TODO"); } ioapic.ioredtbl_config[irq as usize] &= !IOAPIC_CONFIG_DELIVS; } } pub fn set_irq(i: u8) { set_irq_internal(&mut get_ioapic(), &mut apic::get_apic(), i) } fn set_irq_internal(ioapic: &mut Ioapic, apic: &mut apic::Apic, i: u8) { if i as usize >= IOAPIC_IRQ_COUNT { dbg_assert!(false, "Bad irq: {}", i); return; } let mask = 1 << i; if ioapic.irq_value & mask == 0 { if IOAPIC_LOG_VERBOSE { dbg_log!("apic set irq {}", i); } ioapic.irq_value |= mask; let config = ioapic.ioredtbl_config[i as usize]; if config & (IOAPIC_CONFIG_TRIGGER_MODE_LEVEL | IOAPIC_CONFIG_MASKED) == IOAPIC_CONFIG_MASKED { // edge triggered and masked return; } ioapic.irr |= mask; check_irq(ioapic, apic, i); } } pub fn clear_irq(i: u8) { clear_irq_internal(&mut get_ioapic(), i) } fn clear_irq_internal(ioapic: &mut Ioapic, i: u8) { if i as usize >= IOAPIC_IRQ_COUNT { dbg_assert!(false, "Bad irq: {}", i); return; } let mask = 1 << i; if ioapic.irq_value & mask == mask { ioapic.irq_value &= !mask; let config = ioapic.ioredtbl_config[i as usize]; if config & IOAPIC_CONFIG_TRIGGER_MODE_LEVEL != 0 { ioapic.irr &= !mask; } } } pub fn read32(addr: u32) -> u32 { if unsafe { !*acpi_enabled } { return 0; } read32_internal(&mut get_ioapic(), addr) } fn read32_internal(ioapic: &mut Ioapic, addr: u32) -> u32 { match addr { IOREGSEL => ioapic.ioregsel, IOWIN => match ioapic.ioregsel { 0 => { dbg_log!("IOAPIC Read id"); ioapic.ioapic_id << 24 }, 1 => { dbg_log!("IOAPIC Read version"); 0x11 | (IOAPIC_IRQ_COUNT as u32 - 1) << 16 }, 2 => { dbg_log!("IOAPIC Read arbitration id"); ioapic.ioapic_id << 24 }, IOAPIC_FIRST_IRQ_REG..IOAPIC_LAST_IRQ_REG => { let irq = ((ioapic.ioregsel - IOAPIC_FIRST_IRQ_REG) >> 1) as u8; let index = ioapic.ioregsel & 1; if index != 0 { let value = ioapic.ioredtbl_destination[irq as usize]; dbg_log!("IOAPIC Read destination irq={:x} -> {:08x}", irq, value); value } else { let value = ioapic.ioredtbl_config[irq as usize]; dbg_log!("IOAPIC Read config irq={:x} -> {:08x}", irq, value); value } }, reg => { dbg_assert!(false, "IOAPIC register read outside of range {:x}", reg); 0 }, }, _ => { dbg_assert!(false, "Unaligned or oob IOAPIC memory read: {:x}", addr); 0 }, } } pub fn write32(addr: u32, value: u32) { if unsafe { !*acpi_enabled } { return; } write32_internal(&mut get_ioapic(), &mut apic::get_apic(), addr, value) } fn write32_internal(ioapic: &mut Ioapic, apic: &mut apic::Apic, addr: u32, value: u32) { //dbg_log!("IOAPIC write {:x} <- {:08x}", reg, value); match addr { IOREGSEL => ioapic.ioregsel = value, IOWIN => match ioapic.ioregsel { 0 => ioapic.ioapic_id = (value >> 24) & 0x0F, 1 | 2 => { dbg_log!("IOAPIC Invalid write: {}", ioapic.ioregsel); }, IOAPIC_FIRST_IRQ_REG..IOAPIC_LAST_IRQ_REG => { let irq = ((ioapic.ioregsel - IOAPIC_FIRST_IRQ_REG) >> 1) as u8; let index = ioapic.ioregsel & 1; if index != 0 { dbg_log!( "Write destination {:08x} irq={:x} dest={:02x}", value, irq, value >> 24 ); ioapic.ioredtbl_destination[irq as usize] = value & 0xFF000000; } else { let old_value = ioapic.ioredtbl_config[irq as usize] as u32; ioapic.ioredtbl_config[irq as usize] = (value & !IOAPIC_CONFIG_READONLY_MASK) | (old_value & IOAPIC_CONFIG_READONLY_MASK); let vector = value & 0xFF; let delivery_mode = (value >> 8) & 7; let destination_mode = (value >> 11) & 1; let is_level = (value >> 15) & 1; let disabled = (value >> 16) & 1; dbg_log!( "Write config {:08x} irq={:x} vector={:02x} deliverymode={} destmode={} is_level={} disabled={}", value, irq, vector, DELIVERY_MODES[delivery_mode as usize], DESTINATION_MODES[destination_mode as usize], is_level, disabled ); check_irq(ioapic, apic, irq); } }, reg => { dbg_assert!( false, "IOAPIC register write outside of range {:x} <- {:x}", reg, value ) }, }, _ => { dbg_assert!( false, "Unaligned or oob IOAPIC memory write: {:x} <- {:x}", addr, value ) }, } }