ax_cpu/trap.rs
1//! Trap handling.
2
3use core::sync::atomic::{AtomicUsize, Ordering};
4
5use ax_memory_addr::VirtAddr;
6
7pub use crate::{KernelTrapFrame, UserRegisters};
8
9bitflags::bitflags! {
10 /// Access information reported by a CPU page-fault trap.
11 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
12 pub struct PageFaultFlags: usize {
13 /// The faulting access was a read.
14 const READ = 1 << 0;
15 /// The faulting access was a write.
16 const WRITE = 1 << 1;
17 /// The faulting access was an instruction fetch.
18 const EXECUTE = 1 << 2;
19 /// The fault came from a less-privileged user context.
20 const USER = 1 << 3;
21 }
22}
23
24/// Privilege domain that owns a saved register image.
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum TrapOrigin {
27 /// The trap interrupted kernel execution.
28 Kernel,
29 /// The trap interrupted a less-privileged user context.
30 User,
31}
32
33/// IRQ trap hook type.
34pub type IrqHandler = fn(usize, TrapOrigin) -> bool;
35
36/// Page-fault trap hook type.
37pub type PageFaultHandler = fn(VirtAddr, PageFaultFlags) -> bool;
38
39/// Breakpoint trap hook type.
40pub type BreakpointHandler = fn(&mut KernelTrapFrame<'_>) -> bool;
41
42/// Debug trap hook type.
43pub type DebugHandler = fn(&mut KernelTrapFrame<'_>) -> bool;
44
45fn default_irq_handler(irq: usize, _origin: TrapOrigin) -> bool {
46 trace!("IRQ {} triggered", irq);
47 false
48}
49
50fn default_page_fault_handler(addr: VirtAddr, flags: PageFaultFlags) -> bool {
51 warn!("Page fault at {:#x} with flags {:?}", addr, flags);
52 false
53}
54
55fn default_breakpoint_handler(_tf: &mut KernelTrapFrame<'_>) -> bool {
56 false
57}
58
59fn default_debug_handler(_tf: &mut KernelTrapFrame<'_>) -> bool {
60 false
61}
62
63static IRQ_HANDLER: AtomicUsize = AtomicUsize::new(0);
64static PAGE_FAULT_HANDLER: AtomicUsize = AtomicUsize::new(0);
65static BREAKPOINT_HANDLER: AtomicUsize = AtomicUsize::new(0);
66static DEBUG_HANDLER: AtomicUsize = AtomicUsize::new(0);
67
68/// Installs the global IRQ trap hook and returns the previous one.
69pub fn set_irq_handler(handler: IrqHandler) -> IrqHandler {
70 let old = IRQ_HANDLER.swap(handler as usize, Ordering::AcqRel);
71 if old == 0 {
72 default_irq_handler
73 } else {
74 // SAFETY: the atomic only stores function pointers of type `IrqHandler`.
75 unsafe { core::mem::transmute::<usize, IrqHandler>(old) }
76 }
77}
78
79/// Installs the global page-fault trap hook and returns the previous one.
80pub fn set_page_fault_handler(handler: PageFaultHandler) -> PageFaultHandler {
81 let old = PAGE_FAULT_HANDLER.swap(handler as usize, Ordering::AcqRel);
82 if old == 0 {
83 default_page_fault_handler
84 } else {
85 // SAFETY: the atomic only stores function pointers of type `PageFaultHandler`.
86 unsafe { core::mem::transmute::<usize, PageFaultHandler>(old) }
87 }
88}
89
90/// Installs the global breakpoint trap hook and returns the previous one.
91pub fn set_breakpoint_handler(handler: BreakpointHandler) -> BreakpointHandler {
92 let old = BREAKPOINT_HANDLER.swap(handler as usize, Ordering::AcqRel);
93 if old == 0 {
94 default_breakpoint_handler
95 } else {
96 // SAFETY: the atomic only stores function pointers of type `BreakpointHandler`.
97 unsafe { core::mem::transmute::<usize, BreakpointHandler>(old) }
98 }
99}
100
101/// Installs the global debug trap hook and returns the previous one.
102pub fn set_debug_handler(handler: DebugHandler) -> DebugHandler {
103 let old = DEBUG_HANDLER.swap(handler as usize, Ordering::AcqRel);
104 if old == 0 {
105 default_debug_handler
106 } else {
107 // SAFETY: the atomic only stores function pointers of type `DebugHandler`.
108 unsafe { core::mem::transmute::<usize, DebugHandler>(old) }
109 }
110}
111
112/// Dispatches an IRQ through the runtime-registered handler, or the default handler.
113pub fn dispatch_irq(irq: usize, origin: TrapOrigin) -> bool {
114 let handler = IRQ_HANDLER.load(Ordering::Acquire);
115 let handler = if handler == 0 {
116 default_irq_handler
117 } else {
118 // SAFETY: the atomic only stores function pointers of type `IrqHandler`.
119 unsafe { core::mem::transmute::<usize, IrqHandler>(handler) }
120 };
121 handler(irq, origin)
122}
123
124/// Dispatches a page fault through the runtime-registered handler, or the default handler.
125pub fn dispatch_page_fault(addr: VirtAddr, flags: PageFaultFlags) -> bool {
126 let handler = PAGE_FAULT_HANDLER.load(Ordering::Acquire);
127 let handler = if handler == 0 {
128 default_page_fault_handler
129 } else {
130 // SAFETY: the atomic only stores function pointers of type `PageFaultHandler`.
131 unsafe { core::mem::transmute::<usize, PageFaultHandler>(handler) }
132 };
133 handler(addr, flags)
134}
135
136/// Dispatches an IRQ to the installed trap hook.
137pub fn irq_handler(irq: usize) -> bool {
138 dispatch_irq(irq, TrapOrigin::Kernel)
139}
140
141/// Dispatches a page fault to the installed trap hook.
142pub fn page_fault_handler(addr: VirtAddr, flags: PageFaultFlags) -> bool {
143 dispatch_page_fault(addr, flags)
144}
145
146/// Invoke the page-fault slow path with the IRQ state restored to the
147/// faulting context.
148#[inline]
149pub(crate) fn call_page_fault_handler_with_parent_irqs(
150 addr: VirtAddr,
151 flags: PageFaultFlags,
152 parent_irqs_enabled: bool,
153) -> bool {
154 if parent_irqs_enabled {
155 crate::asm::enable_irqs();
156 }
157 let handled = page_fault_handler(addr, flags);
158 if parent_irqs_enabled {
159 crate::asm::disable_irqs();
160 }
161 handled
162}
163
164/// Breakpoint handler.
165///
166/// The handler is invoked with a typed view of the trapped kernel registers
167/// and must return a boolean indicating whether it has fully handled the trap:
168///
169/// - `true` means the breakpoint has been handled and control should resume
170/// according to the state encoded in the trap frame.
171/// - `false` means the breakpoint was not handled and default processing
172/// (such as falling back to another mechanism or terminating) should occur.
173///
174/// When returning `true`, the handler is responsible for updating the saved
175/// program counter (or equivalent PC field) in the trap frame as required by
176/// the target architecture. In particular, the handler must ensure that,
177/// upon resuming from the trap, execution does not immediately re-trigger the
178/// same breakpoint instruction or condition, which could otherwise lead to an
179/// infinite trap loop. Register changes must go through
180/// [`KernelTrapFrame::apply_registers`], which preserves CPU-owned and
181/// privilege-origin state.
182pub fn breakpoint_handler(tf: &mut KernelTrapFrame<'_>) -> bool {
183 let handler = BREAKPOINT_HANDLER.load(Ordering::Acquire);
184 let handler = if handler == 0 {
185 default_breakpoint_handler
186 } else {
187 // SAFETY: the atomic only stores function pointers of type `BreakpointHandler`.
188 unsafe { core::mem::transmute::<usize, BreakpointHandler>(handler) }
189 };
190 handler(tf)
191}
192
193/// Debug handler.
194///
195/// On `x86_64`, the handler is invoked for debug-related traps (for
196/// example, hardware breakpoints, single-step traps, or other debug
197/// exceptions). The handler receives a typed kernel-register view and returns
198/// a boolean with the following meaning:
199///
200/// - `true` means the debug trap has been fully handled and execution should
201/// resume from the state stored in the trap frame.
202/// - `false` means the debug trap was not handled and default/secondary
203/// processing should take place.
204///
205/// As with [`breakpoint_handler()`], when returning `true`, the handler must adjust
206/// the saved program counter (or equivalent) in the trap frame if required by
207/// the architecture so that resuming execution does not immediately cause the
208/// same debug condition to fire again. Callers must take the architecture-
209/// specific PC semantics into account when deciding how to advance or modify
210/// the PC. Register changes must go through
211/// [`KernelTrapFrame::apply_registers`], which preserves CPU-owned and
212/// privilege-origin state.
213pub fn debug_handler(tf: &mut KernelTrapFrame<'_>) -> bool {
214 let handler = DEBUG_HANDLER.load(Ordering::Acquire);
215 let handler = if handler == 0 {
216 default_debug_handler
217 } else {
218 // SAFETY: the atomic only stores function pointers of type `DebugHandler`.
219 unsafe { core::mem::transmute::<usize, DebugHandler>(handler) }
220 };
221 handler(tf)
222}