Spaces:
Runtime error
Runtime error
// Programmable Interrupt Controller | |
// http://stanislavs.org/helppc/8259.html | |
use std::sync::{Mutex, MutexGuard}; | |
pub const PIC_LOG: bool = false; | |
pub const PIC_LOG_VERBOSE: bool = false; | |
// Note: This layout is deliberately chosen to match the old JavaScript pic state | |
// (cpu.get_state_pic depens on this layout) | |
const _: () = assert!(std::mem::offset_of!(Pic0, special_mask_mode) == 12); | |
struct Pic0 { | |
irq_mask: u8, | |
irq_map: u8, | |
// in-service register | |
// Holds interrupts that are currently being serviced | |
isr: u8, | |
// interrupt request register | |
// Holds interrupts that have been requested | |
irr: u8, | |
master: bool, | |
dummy: u8, // remove when state image is updated | |
expect_icw4: bool, | |
state: u8, | |
read_isr: bool, | |
auto_eoi: bool, | |
elcr: u8, | |
irq_value: u8, | |
special_mask_mode: bool, | |
} | |
struct Pic { | |
master: Pic0, | |
slave: Pic0, | |
} | |
static PIC: Mutex<Pic> = Mutex::new(Pic { | |
master: Pic0 { | |
// all irqs off | |
irq_mask: 0, | |
// Bogus default value (both master and slave mapped to 0). | |
// Will be initialized by the BIOS | |
irq_map: 0, | |
// in-service register | |
// Holds interrupts that are currently being serviced | |
isr: 0, | |
// interrupt request register | |
// Holds interrupts that have been requested | |
irr: 0, | |
irq_value: 0, | |
expect_icw4: false, | |
state: 0, | |
read_isr: false, | |
auto_eoi: false, | |
special_mask_mode: false, | |
elcr: 0, | |
master: true, | |
dummy: 0, | |
}, | |
slave: Pic0 { | |
// all irqs off | |
irq_mask: 0, | |
// Bogus default value (both master and slave mapped to 0). | |
// Will be initialized by the BIOS | |
irq_map: 0, | |
// in-service register | |
// Holds interrupts that are currently being serviced | |
isr: 0, | |
// interrupt request register | |
// Holds interrupts that have been requested | |
irr: 0, | |
irq_value: 0, | |
expect_icw4: false, | |
state: 0, | |
read_isr: false, | |
auto_eoi: false, | |
special_mask_mode: false, | |
elcr: 0, | |
master: false, | |
dummy: 0, | |
}, | |
}); | |
fn get_pic() -> MutexGuard<'static, Pic> { PIC.try_lock().unwrap() } | |
// called from javascript for saving/restoring state | |
pub fn get_pic_addr_master() -> u32 { &raw mut get_pic().master as u32 } | |
pub fn get_pic_addr_slave() -> u32 { &raw mut get_pic().slave as u32 } | |
impl Pic0 { | |
fn get_irq(&mut self) -> Option<u8> { | |
let enabled_irr = self.irr & self.irq_mask; | |
if enabled_irr == 0 { | |
if PIC_LOG_VERBOSE { | |
dbg_log!( | |
"[PIC] no unmasked irrs. irr={:x} mask={:x} isr={:x}", | |
self.irr, | |
self.irq_mask, | |
self.isr | |
); | |
} | |
return None; | |
} | |
let irq_mask = enabled_irr & (!enabled_irr + 1); | |
let special_mask = if self.special_mask_mode { self.irq_mask } else { 0xFF }; | |
if self.isr != 0 && (self.isr & (!self.isr + 1) & special_mask) <= irq_mask { | |
// wait for eoi of higher or same priority interrupt | |
if PIC_LOG { | |
dbg_log!( | |
"[PIC] higher prio: master={} isr={:x} mask={:x} irq={:x}", | |
self.master, | |
self.isr, | |
self.irq_mask, | |
irq_mask | |
); | |
} | |
return None; | |
} | |
dbg_assert!(irq_mask != 0); | |
let irq_number = irq_mask.ilog2() as u8; | |
dbg_assert!(irq_mask == 1 << irq_number); | |
if PIC_LOG_VERBOSE { | |
dbg_log!("[PIC] request irq {}", irq_number); | |
} | |
Some(irq_number) | |
} | |
fn port0_read(self: &Pic0) -> u32 { (if self.read_isr { self.isr } else { self.irr }) as u32 } | |
fn port1_read(self: &Pic0) -> u32 { !self.irq_mask as u32 } | |
} | |
impl Pic { | |
fn set_irq(self: &mut Pic, i: u8) { | |
let mask = 1 << (i & 7); | |
let dev = if i < 8 { &mut self.master } else { &mut self.slave }; | |
if dev.irq_value & mask == 0 || dev.elcr & mask != 0 { | |
dev.irr |= mask; | |
dev.irq_value |= mask; | |
if i >= 8 { | |
self.check_irqs_slave() | |
} | |
} | |
} | |
fn clear_irq(self: &mut Pic, i: u8) { | |
let mask = 1 << (i & 7); | |
let dev = if i < 8 { &mut self.master } else { &mut self.slave }; | |
dev.irq_value &= !mask; | |
if dev.elcr & mask != 0 { | |
dev.irr &= !mask; | |
if i >= 8 { | |
self.check_irqs_slave() | |
} | |
} | |
} | |
fn port0_write(&mut self, index: u8, v: u8) { | |
let dev = if index == 0 { &mut self.master } else { &mut self.slave }; | |
if v & 0x10 != 0 { | |
// xxxx1xxx | |
// icw1 | |
dbg_log!("icw1 = {:x}", v); | |
dev.isr = 0; | |
dev.irr = 0; | |
dev.irq_mask = 0xff; | |
dev.irq_value = 0; | |
dev.auto_eoi = true; | |
dev.expect_icw4 = v & 1 != 0; | |
dbg_assert!(v & 2 == 0, "unimplemented: single mode"); | |
dbg_assert!(v & 8 == 0, "unimplemented: level mode"); | |
dev.state = 1; | |
} | |
else if v & 8 != 0 { | |
// xxx01xxx | |
// ocw3 | |
dbg_log!("ocw3: {:x}", v); | |
if v & 2 != 0 { | |
dev.read_isr = v & 1 != 0; | |
} | |
if v & 4 != 0 { | |
dbg_assert!(false, "unimplemented: polling"); | |
} | |
if v & 0x40 != 0 { | |
dev.special_mask_mode = (v & 0x20) == 0x20; | |
dbg_log!("special mask mode: {}", dev.special_mask_mode); | |
} | |
} | |
else { | |
// xxx00xxx | |
// ocw2 | |
// end of interrupt | |
if PIC_LOG { | |
dbg_log!("eoi: {:x}", v); | |
} | |
let eoi_type = v >> 5; | |
if eoi_type == 1 { | |
// non-specific eoi | |
dev.isr &= dev.isr - 1; | |
if PIC_LOG { | |
dbg_log!("new isr: {:x}", dev.isr); | |
} | |
} | |
else if eoi_type == 3 { | |
// specific eoi | |
dev.isr &= !(1 << (v & 7)); | |
} | |
else if eoi_type == 6 { | |
// os2 v4, freebsd | |
let priority = v & 7; | |
dbg_log!("lowest priority: {:x}", priority); | |
} | |
else { | |
dbg_log!("Unknown eoi: {:x} type={:x}", v, eoi_type); | |
dbg_assert!(false); | |
dev.isr &= dev.isr - 1; | |
} | |
if index == 1 { | |
self.check_irqs_slave() | |
} | |
} | |
} | |
fn port1_write(&mut self, index: u8, v: u8) { | |
let dev = if index == 0 { &mut self.master } else { &mut self.slave }; | |
if dev.state == 0 { | |
if dev.expect_icw4 { | |
// icw4 | |
dev.expect_icw4 = false; | |
dev.auto_eoi = v & 2 != 0; | |
dbg_log!("icw4: {:x} autoeoi={}", v, dev.auto_eoi); | |
dbg_assert!(v & 0x10 == 0, "unimplemented: nested mode"); | |
dbg_assert!(v & 1 == 1, "unimplemented: 8086/88 mode"); | |
} | |
else { | |
// ocw1 | |
dev.irq_mask = !v; | |
if PIC_LOG_VERBOSE { | |
dbg_log!("interrupt mask: {:x}", dev.irq_mask); | |
} | |
if index == 1 { | |
self.check_irqs_slave() | |
} | |
} | |
} | |
else if dev.state == 1 { | |
// icw2 | |
dev.irq_map = v; | |
dbg_log!("interrupts are mapped to {:x}", dev.irq_map); | |
dev.state += 1; | |
} | |
else if dev.state == 2 { | |
// icw3 | |
dev.state = 0; | |
dbg_log!("icw3: {:x}", v); | |
} | |
} | |
fn check_irqs_slave(&mut self) { | |
let is_set = self.slave.get_irq().is_some(); | |
if is_set { | |
self.set_irq(2) | |
} | |
else { | |
self.clear_irq(2) | |
} | |
} | |
} | |
// called by the cpu | |
pub fn pic_acknowledge_irq() -> Option<u8> { | |
let mut pic = get_pic(); | |
let irq = match pic.master.get_irq() { | |
Some(i) => i, | |
None => return None, | |
}; | |
if pic.master.irr == 0 { | |
dbg_assert!(false); | |
//PIC_LOG_VERBOSE && dbg_log!("master> spurious requested=" + irq); | |
//Some(pic.irq_map | 7) | |
return None; | |
} | |
let mask = 1 << irq; | |
if pic.master.elcr & mask == 0 { | |
// not in level mode | |
pic.master.irr &= !mask; | |
} | |
if !pic.master.auto_eoi { | |
pic.master.isr |= mask; | |
} | |
if PIC_LOG_VERBOSE { | |
dbg_log!("[PIC] master> acknowledge {}", irq); | |
} | |
dbg_assert!(pic.master.get_irq().is_none()); | |
if irq == 2 { | |
acknowledge_irq_slave(&mut pic) | |
} | |
else { | |
Some(pic.master.irq_map | irq) | |
} | |
} | |
fn acknowledge_irq_slave(pic: &mut Pic) -> Option<u8> { | |
let irq = match pic.slave.get_irq() { | |
Some(i) => i, | |
None => return None, | |
}; | |
if pic.slave.irr == 0 { | |
//PIC_LOG_VERBOSE && dbg_log!("slave> spurious requested=" + irq); | |
//Some(pic.irq_map | 7) | |
dbg_assert!(false); | |
return None; | |
} | |
let mask = 1 << irq; | |
if pic.slave.elcr & mask == 0 { | |
// not in level mode | |
pic.slave.irr &= !mask; | |
} | |
if !pic.slave.auto_eoi { | |
pic.slave.isr |= mask; | |
} | |
if PIC_LOG_VERBOSE { | |
dbg_log!("[PIC] slave> acknowledge {}", irq); | |
} | |
dbg_assert!(pic.slave.get_irq().is_none()); | |
pic.clear_irq(2); | |
Some(pic.slave.irq_map | irq) | |
} | |
pub fn set_irq(i: u8) { | |
dbg_assert!(i < 16); | |
if PIC_LOG_VERBOSE { | |
dbg_log!("[PIC] set irq {}", i); | |
} | |
get_pic().set_irq(i) | |
} | |
pub fn clear_irq(i: u8) { | |
dbg_assert!(i < 16); | |
if PIC_LOG_VERBOSE { | |
dbg_log!("[PIC] clear irq {}", i); | |
} | |
get_pic().clear_irq(i) | |
} | |
pub fn port20_read() -> u32 { get_pic().master.port0_read() } | |
pub fn port21_read() -> u32 { get_pic().master.port1_read() } | |
pub fn portA0_read() -> u32 { get_pic().slave.port0_read() } | |
pub fn portA1_read() -> u32 { get_pic().slave.port1_read() } | |
pub fn port20_write(v: u8) { get_pic().port0_write(0, v) } | |
pub fn port21_write(v: u8) { get_pic().port1_write(0, v) } | |
pub fn portA0_write(v: u8) { get_pic().port0_write(1, v) } | |
pub fn portA1_write(v: u8) { get_pic().port1_write(1, v) } | |
pub fn port4D0_read() -> u32 { get_pic().master.elcr as u32 } | |
pub fn port4D1_read() -> u32 { get_pic().slave.elcr as u32 } | |
pub fn port4D0_write(v: u8) { get_pic().master.elcr = v } | |
pub fn port4D1_write(v: u8) { get_pic().slave.elcr = v } | |