Skip to main content

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}