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