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::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}