Skip to main content

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}