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