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}