rustubs/arch/x86_64/interrupt.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
mod idt;
pub mod pic_8259;
pub mod pit;
pub mod plugbox;
use crate::arch::x86_64::arch_regs::TrapFrame;
use crate::arch::x86_64::is_int_enabled;
use crate::arch::x86_64::paging::fault;
use crate::defs::IntNumber as INT;
use crate::io::*;
use crate::machine::interrupt::plugbox::IRQ_GATE_MAP;
use crate::proc::sched::Scheduler;
use crate::proc::sync::*;
use core::arch::asm;
#[no_mangle]
#[cfg(target_arch = "x86_64")]
extern "C" fn trap_gate(nr: u16, fp: u64) {
// cpu automatically masks interrupts so we are already in L3
if nr < 0x20 {
handle_exception(nr, fp);
} else {
unsafe { handle_irq(nr) };
}
interrupt_enable();
}
#[inline]
/// handle_irq assumes the interrupt is **disabled** when called.
/// this will also make sure interrupt is disabled when it returns
unsafe fn handle_irq(nr: u16) {
let irq_gate = match IRQ_GATE_MAP.get(&nr) {
None => {
panic!("no handler for irq {}", nr);
}
Some(g) => g,
};
// execute the prologue
irq_gate.call_prologue();
let epi = irq_gate.get_epilogue();
if epi.is_none() {
// TODO? we could also take a look into the epilogue queue here when the
// current irq doesn't have an epilogue itself. But optimistically, if
// the epilogue queue is not empty, it's very likely someone else is
// already working on it, so we just leave for now....
return;
}
let epi = epi.unwrap();
if !IS_L2_AVAILABLE() {
EPILOGUE_QUEUE.l3_get_ref_mut().queue.push_back(epi);
return;
}
// L2 is available, we run the epilogue now, also clear the queue before
// return
ENTER_L2();
interrupt_enable();
unsafe {
epi.call();
}
// we need to clear the epilogue queue on behalf of others. Modifying the
// epilogue is a level 3 critical section
let mut epi: Option<EpilogueEntrant>;
let mut done;
loop {
let r = irq_save();
let rq = EPILOGUE_QUEUE.l3_get_ref_mut();
epi = rq.queue.pop_front();
done = rq.queue.is_empty();
irq_restore(r);
if let Some(e) = epi {
debug_assert!(is_int_enabled());
e.call();
}
// This is a linearization point where we may do rescheduling unlike
// OOStuBS, we don't do rescheduling in the device epilogues. this
// decouples the scheduler from the timer interrupt driver, also has
// better "real-time" guarantee: rescheduling will not be delayed by
// more than one epilogue execution; OOStuBS doesn't have the delay
// issue because every epilogue is enqueued at most once due to the
// limitation of having no memory management.
//
// this also means that ALL rescheduling must be done in the level 2.
// otherwise 1) rescheduling may not be strictly linearized, if the CPU
// is not running fast enough there might be issues caused by spurious
// (timer) interrupts. 2) even if you can guarantee linearization, there
// is still a dead lock situation that, if the try_reschedule /
// do_reschedule was called the at a wrong place, the execution may not
// release the L2 lock when they are scheduled back. 3) this also
// requires you do explicitly release L2 lock on new task entrance (when
// they are scheduled for the first time). I'm not a big fan of this but
// there is nothing much I can do right now.
Scheduler::try_reschedule();
if done {
break;
}
}
// you need to make sure the interrupt is disabled at this point
LEAVE_L2();
}
/// handles exception/faults (nr < 32);
#[inline]
fn handle_exception(nr: u16, fp: u64) {
let frame = unsafe { &mut *(fp as *mut TrapFrame) };
match nr {
INT::PAGEFAULT => {
let fault_address = fault::get_fault_addr();
fault::page_fault_handler(frame, fault_address)
}
_ => {
sprint!("[trap {}] {:#X?}", nr, frame);
unsafe { asm!("hlt") };
}
}
}
#[inline(always)]
pub fn interrupt_enable() { unsafe { asm!("sti") }; }
#[inline(always)]
pub fn interrupt_disable() { unsafe { asm!("cli") }; }
#[inline]
/// irq_save() disables all interrupts and returns the previous state
pub fn irq_save() -> bool {
if is_int_enabled() {
interrupt_disable();
return true;
} else {
return false;
}
}
#[inline]
/// irq_restore only re-enable irq if was_enabled==true. it will not disable irq
/// regardless the was_enabled value. This function should only be called to
/// restore irq based on previous irq_save();
pub fn irq_restore(was_enabled: bool) {
if was_enabled {
interrupt_enable();
}
}
/// initialize the idt and [pic_8259]
pub fn init() {
idt::init();
pic_8259::init();
}