Spaces:
Runtime error
Runtime error
// See Intel's System Programming Guide | |
use crate::cpu::{cpu::js, global_pointers::acpi_enabled, ioapic}; | |
use std::sync::{Mutex, MutexGuard}; | |
const APIC_LOG_VERBOSE: bool = false; | |
// should probably be kept in sync with TSC_RATE in cpu.rs | |
const APIC_TIMER_FREQ: f64 = 1.0 * 1000.0 * 1000.0; | |
const APIC_TIMER_MODE_MASK: u32 = 3 << 17; | |
const APIC_TIMER_MODE_ONE_SHOT: u32 = 0; | |
const APIC_TIMER_MODE_PERIODIC: u32 = 1 << 17; | |
const _APIC_TIMER_MODE_TSC: u32 = 2 << 17; | |
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"]; | |
const IOAPIC_CONFIG_MASKED: u32 = 0x10000; | |
const IOAPIC_DELIVERY_INIT: u8 = 5; | |
const IOAPIC_DELIVERY_NMI: u8 = 4; | |
const IOAPIC_DELIVERY_FIXED: u8 = 0; | |
// keep in sync with cpu.js | |
const APIC_STRUCT_SIZE: usize = 4 * 46; | |
// Note: JavaScript (cpu.get_state_apic) depens on this layout | |
const _: () = assert!(std::mem::offset_of!(Apic, icr0) == 14 * 4); | |
const _: () = assert!(std::mem::offset_of!(Apic, irr) == 16 * 4); | |
const _: () = assert!(std::mem::offset_of!(Apic, isr) == 24 * 4); | |
const _: () = assert!(std::mem::offset_of!(Apic, tmr) == 32 * 4); | |
const _: () = assert!(std::mem::offset_of!(Apic, spurious_vector) == 40 * 4); | |
const _: () = assert!(std::mem::offset_of!(Apic, lvt_thermal_sensor) == 45 * 4); | |
const _: () = assert!(std::mem::size_of::<Apic>() == APIC_STRUCT_SIZE); | |
pub struct Apic { | |
apic_id: u32, | |
timer_divider: u32, | |
timer_divider_shift: u32, | |
timer_initial_count: u32, | |
timer_current_count: u32, | |
timer_last_tick: f64, | |
lvt_timer: u32, | |
lvt_perf_counter: u32, | |
lvt_int0: u32, | |
lvt_int1: u32, | |
lvt_error: u32, | |
tpr: u32, | |
icr0: u32, | |
icr1: u32, | |
irr: [u32; 8], | |
isr: [u32; 8], | |
tmr: [u32; 8], | |
spurious_vector: u32, | |
destination_format: u32, | |
local_destination: u32, | |
error: u32, | |
read_error: u32, | |
lvt_thermal_sensor: u32, | |
} | |
static APIC: Mutex<Apic> = Mutex::new(Apic { | |
apic_id: 0, | |
timer_divider: 0, | |
timer_divider_shift: 1, | |
timer_initial_count: 0, | |
timer_current_count: 0, | |
timer_last_tick: 0.0, | |
lvt_timer: IOAPIC_CONFIG_MASKED, | |
lvt_thermal_sensor: IOAPIC_CONFIG_MASKED, | |
lvt_perf_counter: IOAPIC_CONFIG_MASKED, | |
lvt_int0: IOAPIC_CONFIG_MASKED, | |
lvt_int1: IOAPIC_CONFIG_MASKED, | |
lvt_error: IOAPIC_CONFIG_MASKED, | |
tpr: 0, | |
icr0: 0, | |
icr1: 0, | |
irr: [0; 8], | |
isr: [0; 8], | |
tmr: [0; 8], | |
spurious_vector: 0xFE, | |
destination_format: !0, | |
local_destination: 0, | |
error: 0, | |
read_error: 0, | |
}); | |
pub fn get_apic() -> MutexGuard<'static, Apic> { APIC.try_lock().unwrap() } | |
pub fn get_apic_addr() -> u32 { &raw mut *get_apic() as u32 } | |
pub fn read32(addr: u32) -> u32 { | |
if unsafe { !*acpi_enabled } { | |
return 0; | |
} | |
read32_internal(&mut get_apic(), addr) | |
} | |
fn read32_internal(apic: &mut Apic, addr: u32) -> u32 { | |
match addr { | |
0x20 => { | |
dbg_log!("APIC read id"); | |
apic.apic_id | |
}, | |
0x30 => { | |
// version | |
dbg_log!("APIC read version"); | |
0x50014 | |
}, | |
0x80 => { | |
if APIC_LOG_VERBOSE { | |
dbg_log!("APIC read tpr"); | |
} | |
apic.tpr | |
}, | |
0xB0 => { | |
// write-only (written by DSL) | |
if APIC_LOG_VERBOSE { | |
dbg_log!("APIC read eoi register"); | |
} | |
0 | |
}, | |
0xD0 => { | |
dbg_log!("Read local destination"); | |
apic.local_destination | |
}, | |
0xE0 => { | |
dbg_log!("Read destination format"); | |
apic.destination_format | |
}, | |
0xF0 => apic.spurious_vector, | |
0x100 | 0x110 | 0x120 | 0x130 | 0x140 | 0x150 | 0x160 | 0x170 => { | |
let index = ((addr - 0x100) >> 4) as usize; | |
dbg_log!("Read isr {}: {:08x}", index, apic.isr[index] as u32); | |
apic.isr[index] | |
}, | |
0x180 | 0x190 | 0x1A0 | 0x1B0 | 0x1C0 | 0x1D0 | 0x1E0 | 0x1F0 => { | |
let index = ((addr - 0x180) >> 4) as usize; | |
dbg_log!("Read tmr {}: {:08x}", index, apic.tmr[index] as u32); | |
apic.tmr[index] | |
}, | |
0x200 | 0x210 | 0x220 | 0x230 | 0x240 | 0x250 | 0x260 | 0x270 => { | |
let index = ((addr - 0x200) >> 4) as usize; | |
dbg_log!("Read irr {}: {:08x}", index, apic.irr[index] as u32); | |
apic.irr[index] | |
}, | |
0x280 => { | |
dbg_log!("Read error: {:08x}", apic.read_error); | |
apic.read_error | |
}, | |
0x300 => { | |
if APIC_LOG_VERBOSE { | |
dbg_log!("APIC read icr0"); | |
} | |
apic.icr0 | |
}, | |
0x310 => { | |
dbg_log!("APIC read icr1"); | |
apic.icr1 | |
}, | |
0x320 => { | |
dbg_log!("read timer lvt"); | |
apic.lvt_timer | |
}, | |
0x330 => { | |
dbg_log!("read lvt thermal sensor"); | |
apic.lvt_thermal_sensor | |
}, | |
0x340 => { | |
dbg_log!("read lvt perf counter"); | |
apic.lvt_perf_counter | |
}, | |
0x350 => { | |
dbg_log!("read lvt int0"); | |
apic.lvt_int0 | |
}, | |
0x360 => { | |
dbg_log!("read lvt int1"); | |
apic.lvt_int1 | |
}, | |
0x370 => { | |
dbg_log!("read lvt error"); | |
apic.lvt_error | |
}, | |
0x3E0 => { | |
// divider | |
dbg_log!("read timer divider"); | |
apic.timer_divider | |
}, | |
0x380 => { | |
dbg_log!("read timer initial count"); | |
apic.timer_initial_count | |
}, | |
0x390 => { | |
let now = unsafe { js::microtick() }; | |
if apic.timer_last_tick > now { | |
// should only happen after restore_state | |
dbg_log!("warning: APIC last_tick is in the future, resetting"); | |
apic.timer_last_tick = now; | |
} | |
let diff = now - apic.timer_last_tick; | |
let diff_in_ticks = diff * APIC_TIMER_FREQ / (1 << apic.timer_divider_shift) as f64; | |
dbg_assert!(diff_in_ticks >= 0.0); | |
let diff_in_ticks = diff_in_ticks as u64; | |
let result = if diff_in_ticks < apic.timer_initial_count as u64 { | |
apic.timer_initial_count - diff_in_ticks as u32 | |
} | |
else { | |
let mode = apic.lvt_timer & APIC_TIMER_MODE_MASK; | |
if mode == APIC_TIMER_MODE_PERIODIC { | |
apic.timer_initial_count | |
- (diff_in_ticks % (apic.timer_initial_count as u64 + 1)) as u32 | |
} | |
else if mode == APIC_TIMER_MODE_ONE_SHOT { | |
0 | |
} | |
else { | |
dbg_assert!(false, "apic unimplemented timer mode: {:x}", mode); | |
0 | |
} | |
}; | |
if APIC_LOG_VERBOSE { | |
dbg_log!("read timer current count: {}", result); | |
} | |
result | |
}, | |
_ => { | |
dbg_log!("APIC read {:x}", addr); | |
dbg_assert!(false); | |
0 | |
}, | |
} | |
} | |
pub fn write32(addr: u32, value: u32) { | |
if unsafe { !*acpi_enabled } { | |
return; | |
} | |
write32_internal(&mut get_apic(), addr, value) | |
} | |
fn write32_internal(apic: &mut Apic, addr: u32, value: u32) { | |
match addr { | |
0x20 => { | |
dbg_log!("APIC write id: {:08x}", value >> 8); | |
apic.apic_id = value; | |
}, | |
0x30 => { | |
// version | |
dbg_log!("APIC write version: {:08x}, ignored", value); | |
}, | |
0x80 => { | |
if APIC_LOG_VERBOSE { | |
dbg_log!("Set tpr: {:02x}", value & 0xFF); | |
} | |
apic.tpr = value & 0xFF; | |
}, | |
0xB0 => { | |
if let Some(highest_isr) = highest_isr(apic) { | |
if APIC_LOG_VERBOSE { | |
dbg_log!("eoi: {:08x} for vector {:x}", value, highest_isr); | |
} | |
register_clear_bit(&mut apic.isr, highest_isr); | |
if register_get_bit(&apic.tmr, highest_isr) { | |
// Send eoi to all IO APICs | |
ioapic::remote_eoi(apic, highest_isr); | |
} | |
} | |
else { | |
dbg_log!("Bad eoi: No isr set"); | |
} | |
}, | |
0xD0 => { | |
dbg_log!("Set local destination: {:08x}", value); | |
apic.local_destination = value & 0xFF000000; | |
}, | |
0xE0 => { | |
dbg_log!("Set destination format: {:08x}", value); | |
apic.destination_format = value | 0xFFFFFF; | |
}, | |
0xF0 => { | |
dbg_log!("Set spurious vector: {:08x}", value); | |
apic.spurious_vector = value; | |
}, | |
0x280 => { | |
// updated readable error register with real error | |
dbg_log!("Write error: {:08x}", value); | |
apic.read_error = apic.error; | |
apic.error = 0; | |
}, | |
0x300 => { | |
let vector = (value & 0xFF) as u8; | |
let delivery_mode = ((value >> 8) & 7) as u8; | |
let destination_mode = ((value >> 11) & 1) as u8; | |
let is_level = value & ioapic::IOAPIC_CONFIG_TRIGGER_MODE_LEVEL | |
== ioapic::IOAPIC_CONFIG_TRIGGER_MODE_LEVEL; | |
let destination_shorthand = (value >> 18) & 3; | |
let destination = (apic.icr1 >> 24) as u8; | |
dbg_log!( | |
"APIC write icr0: {:08x} vector={:02x} destination_mode={} delivery_mode={} destination_shorthand={}", | |
value, | |
vector, | |
DESTINATION_MODES[destination_mode as usize], | |
DELIVERY_MODES[delivery_mode as usize], | |
["no", "self", "all with self", "all without self"][destination_shorthand as usize] | |
); | |
let mut value = value; | |
value &= !(1 << 12); | |
apic.icr0 = value; | |
if destination_shorthand == 0 { | |
// no shorthand | |
route( | |
apic, | |
vector, | |
delivery_mode, | |
is_level, | |
destination, | |
destination_mode, | |
); | |
} | |
else if destination_shorthand == 1 { | |
// self | |
deliver(apic, vector, IOAPIC_DELIVERY_FIXED, is_level); | |
} | |
else if destination_shorthand == 2 { | |
// all including self | |
deliver(apic, vector, delivery_mode, is_level); | |
} | |
else if destination_shorthand == 3 { | |
// all but self | |
} | |
else { | |
dbg_assert!(false); | |
} | |
}, | |
0x310 => { | |
dbg_log!("APIC write icr1: {:08x}", value); | |
apic.icr1 = value; | |
}, | |
0x320 => { | |
dbg_log!("timer lvt: {:08x}", value); | |
// TODO: check if unmasking and if this should trigger an interrupt immediately | |
apic.lvt_timer = value; | |
}, | |
0x330 => { | |
dbg_log!("lvt thermal sensor: {:08x}", value); | |
apic.lvt_thermal_sensor = value; | |
}, | |
0x340 => { | |
dbg_log!("lvt perf counter: {:08x}", value); | |
apic.lvt_perf_counter = value; | |
}, | |
0x350 => { | |
dbg_log!("lvt int0: {:08x}", value); | |
apic.lvt_int0 = value; | |
}, | |
0x360 => { | |
dbg_log!("lvt int1: {:08x}", value); | |
apic.lvt_int1 = value; | |
}, | |
0x370 => { | |
dbg_log!("lvt error: {:08x}", value); | |
apic.lvt_error = value; | |
}, | |
0x3E0 => { | |
apic.timer_divider = value; | |
let divide_shift = (value & 0b11) | ((value & 0b1000) >> 1); | |
apic.timer_divider_shift = if divide_shift == 0b111 { 0 } else { divide_shift + 1 }; | |
dbg_log!( | |
"APIC timer divider: {:08x} shift={} tick={:.6}ms", | |
apic.timer_divider, | |
apic.timer_divider_shift, | |
(1 << apic.timer_divider_shift) as f64 / APIC_TIMER_FREQ | |
); | |
}, | |
0x380 => { | |
if APIC_LOG_VERBOSE { | |
dbg_log!( | |
"APIC timer initial: {} next_interrupt={:.2}ms", | |
value, | |
value as f64 * (1 << apic.timer_divider_shift) as f64 / APIC_TIMER_FREQ, | |
); | |
} | |
apic.timer_initial_count = value; | |
apic.timer_current_count = value; | |
apic.timer_last_tick = unsafe { js::microtick() }; | |
}, | |
0x390 => { | |
dbg_log!("write timer current: {:08x}", value); | |
dbg_assert!(false, "read-only register"); | |
}, | |
_ => { | |
dbg_log!("APIC write32 {:x} <- {:08x}", addr, value); | |
dbg_assert!(false); | |
}, | |
} | |
} | |
pub fn apic_timer(now: f64) -> f64 { timer(&mut get_apic(), now) } | |
fn timer(apic: &mut Apic, now: f64) -> f64 { | |
if apic.timer_initial_count == 0 || apic.timer_current_count == 0 { | |
return 100.0; | |
} | |
if apic.timer_last_tick > now { | |
// should only happen after restore_state | |
dbg_log!("warning: APIC last_tick is in the future, resetting"); | |
apic.timer_last_tick = now; | |
} | |
let diff = now - apic.timer_last_tick; | |
let diff_in_ticks = diff * APIC_TIMER_FREQ / (1 << apic.timer_divider_shift) as f64; | |
dbg_assert!(diff_in_ticks >= 0.0); | |
let diff_in_ticks = diff_in_ticks as u64; | |
let time_per_interrupt = | |
apic.timer_initial_count as f64 * (1 << apic.timer_divider_shift) as f64 / APIC_TIMER_FREQ; | |
if diff_in_ticks >= apic.timer_initial_count as u64 { | |
let mode = apic.lvt_timer & APIC_TIMER_MODE_MASK; | |
if mode == APIC_TIMER_MODE_PERIODIC { | |
if APIC_LOG_VERBOSE { | |
dbg_log!("APIC timer periodic interrupt"); | |
} | |
if diff_in_ticks >= 2 * apic.timer_initial_count as u64 { | |
dbg_log!( | |
"warning: APIC skipping {} interrupts initial={} ticks={} last_tick={:.1}ms now={:.1}ms d={:.1}ms", | |
diff_in_ticks / apic.timer_initial_count as u64 - 1, | |
apic.timer_initial_count, | |
diff_in_ticks, | |
apic.timer_last_tick, | |
now, | |
diff, | |
); | |
apic.timer_last_tick = now; | |
} | |
else { | |
apic.timer_last_tick += time_per_interrupt; | |
dbg_assert!(apic.timer_last_tick <= now); | |
} | |
} | |
else if mode == APIC_TIMER_MODE_ONE_SHOT { | |
if APIC_LOG_VERBOSE { | |
dbg_log!("APIC timer one shot end"); | |
} | |
apic.timer_current_count = 0; | |
} | |
else { | |
dbg_assert!(false, "apic unimplemented timer mode: {:x}", mode); | |
} | |
if apic.lvt_timer & IOAPIC_CONFIG_MASKED == 0 { | |
deliver( | |
apic, | |
(apic.lvt_timer & 0xFF) as u8, | |
IOAPIC_DELIVERY_FIXED, | |
false, | |
); | |
} | |
} | |
apic.timer_last_tick + time_per_interrupt - now | |
} | |
pub fn route( | |
apic: &mut Apic, | |
vector: u8, | |
mode: u8, | |
is_level: bool, | |
_destination: u8, | |
_destination_mode: u8, | |
) { | |
// TODO | |
deliver(apic, vector, mode, is_level); | |
} | |
fn deliver(apic: &mut Apic, vector: u8, mode: u8, is_level: bool) { | |
if APIC_LOG_VERBOSE { | |
dbg_log!("Deliver {:02x} mode={} level={}", vector, mode, is_level); | |
} | |
if mode == IOAPIC_DELIVERY_INIT { | |
// TODO | |
return; | |
} | |
if mode == IOAPIC_DELIVERY_NMI { | |
// TODO | |
return; | |
} | |
if vector < 0x10 || vector == 0xFF { | |
dbg_assert!(false, "TODO: Invalid vector"); | |
} | |
if register_get_bit(&apic.irr, vector) { | |
dbg_log!("Not delivered: irr already set, vector={:02x}", vector); | |
return; | |
} | |
register_set_bit(&mut apic.irr, vector); | |
if is_level { | |
register_set_bit(&mut apic.tmr, vector); | |
} | |
else { | |
register_clear_bit(&mut apic.tmr, vector); | |
} | |
} | |
fn highest_irr(apic: &mut Apic) -> Option<u8> { | |
let highest = register_get_highest_bit(&apic.irr); | |
if let Some(x) = highest { | |
dbg_assert!(x >= 0x10); | |
dbg_assert!(x != 0xFF); | |
} | |
highest | |
} | |
fn highest_isr(apic: &mut Apic) -> Option<u8> { | |
let highest = register_get_highest_bit(&apic.isr); | |
if let Some(x) = highest { | |
dbg_assert!(x >= 0x10); | |
dbg_assert!(x != 0xFF); | |
} | |
highest | |
} | |
pub fn acknowledge_irq() -> Option<u8> { acknowledge_irq_internal(&mut get_apic()) } | |
fn acknowledge_irq_internal(apic: &mut Apic) -> Option<u8> { | |
let highest_irr = match highest_irr(apic) { | |
None => return None, | |
Some(x) => x, | |
}; | |
if let Some(highest_isr) = highest_isr(apic) { | |
if highest_isr >= highest_irr { | |
if APIC_LOG_VERBOSE { | |
dbg_log!("Higher isr, isr={:x} irr={:x}", highest_isr, highest_irr); | |
} | |
return None; | |
} | |
} | |
if highest_irr & 0xF0 <= apic.tpr as u8 & 0xF0 { | |
if APIC_LOG_VERBOSE { | |
dbg_log!( | |
"Higher tpr, tpr={:x} irr={:x}", | |
apic.tpr & 0xF0, | |
highest_irr | |
); | |
} | |
return None; | |
} | |
register_clear_bit(&mut apic.irr, highest_irr); | |
register_set_bit(&mut apic.isr, highest_irr); | |
if APIC_LOG_VERBOSE { | |
dbg_log!("Calling vector {:x}", highest_irr); | |
} | |
dbg_assert!(acknowledge_irq_internal(apic).is_none()); | |
Some(highest_irr) | |
} | |
// functions operating on 256-bit registers (for irr, isr, tmr) | |
fn register_get_bit(v: &[u32; 8], bit: u8) -> bool { v[(bit >> 5) as usize] & 1 << (bit & 31) != 0 } | |
fn register_set_bit(v: &mut [u32; 8], bit: u8) { v[(bit >> 5) as usize] |= 1 << (bit & 31); } | |
fn register_clear_bit(v: &mut [u32; 8], bit: u8) { v[(bit >> 5) as usize] &= !(1 << (bit & 31)); } | |
fn register_get_highest_bit(v: &[u32; 8]) -> Option<u8> { | |
dbg_assert!(v.as_ptr().addr() & std::mem::align_of::<u64>() - 1 == 0); | |
let v: &[u64; 4] = unsafe { std::mem::transmute(v) }; | |
for i in (0..4).rev() { | |
let word = v[i]; | |
if word != 0 { | |
return Some(word.ilog2() as u8 | (i as u8) << 6); | |
} | |
} | |
None | |
} | |