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 if nr == INT::SYSCALL {
22 handle_syscall(fp);
23 } else {
24 unsafe { handle_irq(nr) };
25 }
26
27 // yield CPU if this process has been marked as killed
28 use crate::proc::task::{Task, TaskState};
29 let t = Task::current().unwrap();
30 // TODO mark the true branch as "unlikely"
31 // TODO this could be buggy. Are we sure L2 must be free here?
32 if t.state == TaskState::Dead {
33 interrupt_enable();
34 Scheduler::yield_cpu();
35 } else {
36 interrupt_enable();
37 }
38}
39
40#[inline]
41/// handle_irq assumes the interrupt is **disabled** when called.
42/// this will also make sure interrupt is disabled when it returns
43unsafe fn handle_irq(nr: u16) {
44 let irq_gate = match IRQ_GATE_MAP.get(&nr) {
45 None => {
46 panic!("no handler for irq {}", nr);
47 }
48 Some(g) => g,
49 };
50 // execute the prologue
51 irq_gate.call_prologue();
52 let epi = irq_gate.get_epilogue();
53 if epi.is_none() {
54 // TODO? we could also take a look into the epilogue queue here when the
55 // current irq doesn't have an epilogue itself. But optimistically, if
56 // the epilogue queue is not empty, it's very likely someone else is
57 // already working on it, so we just leave for now....
58 return;
59 }
60 let epi = epi.unwrap();
61 if !IS_L2_AVAILABLE() {
62 EPILOGUE_QUEUE.l3_get_ref_mut().queue.push_back(epi);
63 return;
64 }
65 // L2 is available, we run the epilogue now, also clear the queue before
66 // return
67 ENTER_L2();
68 interrupt_enable();
69 unsafe {
70 epi.call();
71 }
72 // we need to clear the epilogue queue on behalf of others. Modifying the
73 // epilogue is a level 3 critical section
74 let mut epi: Option<EpilogueEntrant>;
75 let mut done;
76 loop {
77 let r = irq_save();
78 let rq = EPILOGUE_QUEUE.l3_get_ref_mut();
79 epi = rq.queue.pop_front();
80 done = rq.queue.is_empty();
81 irq_restore(r);
82
83 if let Some(e) = epi {
84 debug_assert!(is_int_enabled());
85 e.call();
86 }
87 // This is a linearization point where we may do rescheduling. Unlike
88 // OOStuBS, we don't do rescheduling in the device epilogues. this
89 // decouples the scheduler from the timer interrupt driver, also has
90 // better "real-time" guarantee: rescheduling will not be delayed by
91 // more than one epilogue execution; OOStuBS doesn't have the delay
92 // issue because every epilogue is enqueued at most once due to the
93 // limitation of having no memory management.
94 //
95 // this also means that ALL rescheduling must be done in the level 2.
96 // otherwise 1) rescheduling may not be strictly linearized, if the CPU
97 // is not running fast enough there might be issues caused by spurious
98 // (timer) interrupts. 2) even if you can guarantee linearization, there
99 // is still a dead lock situation that, if the try_reschedule /
100 // do_reschedule was called the at a wrong place, the execution may not
101 // release the L2 lock when they are scheduled back. 3) this also
102 // requires you do explicitly release L2 lock on new task entrance (when
103 // they are scheduled for the first time). I'm not a big fan of this but
104 // there is nothing much I can do right now.
105 Scheduler::try_reschedule();
106 if done {
107 break;
108 }
109 }
110 // you need to make sure the interrupt is disabled at this point
111 LEAVE_L2();
112}
113
114/// handles exception/faults (nr < 32);
115#[inline]
116fn handle_exception(nr: u16, fp: u64) {
117 let frame = unsafe { &mut *(fp as *mut TrapFrame) };
118 match nr {
119 INT::PAGEFAULT => {
120 let fault_address = fault::get_fault_addr();
121 fault::page_fault_handler(frame, fault_address)
122 }
123 _ => {
124 sprint!("[trap {}] {:#X?}", nr, frame);
125 unsafe { asm!("hlt") };
126 }
127 }
128}
129
130/// dispatch a syscall (int 0x80). The syscall number is in rax; args in
131/// rdi, rsi, rdx. The return value is written back into the trap frame's rax
132/// so iretq returns it to user space.
133#[inline]
134fn handle_syscall(fp: u64) {
135 let frame = unsafe { &mut *(fp as *mut TrapFrame) };
136 let nr = frame.rax;
137 let a0 = frame.rdi;
138 let a1 = frame.rsi;
139 let a2 = frame.rdx;
140 // syscalls are voluntary traps, not hardware IRQs. Enable interrupts so
141 // that sys_exit -> yield_cpu and sys_write (which may block on the console
142 // lock) work correctly.
143 interrupt_enable();
144 let ret = crate::syscalls::dispatch(nr, a0, a1, a2);
145 frame.rax = ret;
146}
147
148#[inline(always)]
149pub fn interrupt_enable() { unsafe { asm!("sti") }; }
150
151#[inline(always)]
152pub fn interrupt_disable() { unsafe { asm!("cli") }; }
153
154#[inline]
155/// irq_save() disables all interrupts and returns the previous state
156pub fn irq_save() -> bool {
157 if is_int_enabled() {
158 interrupt_disable();
159 return true;
160 } else {
161 return false;
162 }
163}
164
165#[inline]
166/// irq_restore only re-enable irq if was_enabled==true. it will not disable irq
167/// regardless the was_enabled value. This function should only be called to
168/// restore irq based on previous irq_save();
169pub fn irq_restore(was_enabled: bool) {
170 if was_enabled {
171 interrupt_enable();
172 }
173}
174
175/// initialize the idt and [pic_8259]
176pub fn init() {
177 idt::init();
178 pic_8259::init();
179}