⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/mm/
page_fault.rs

1//! Page Fault Handler Framework
2//!
3//! Provides infrastructure for handling page faults including demand paging,
4//! copy-on-write, and stack growth. Architecture-specific trap handlers
5//! construct a [`PageFaultInfo`] and delegate to [`handle_page_fault`].
6
7// Page fault handler -- demand paging, CoW, stack growth
8#![allow(dead_code)]
9
10#[cfg(feature = "alloc")]
11extern crate alloc;
12
13use crate::{
14    error::KernelError,
15    mm::{PageFlags, VirtualAddress, PAGE_SIZE},
16};
17
18/// Reason a page fault occurred.
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum PageFaultReason {
21    /// Page is not present in the page table.
22    NotPresent,
23    /// A protection violation was detected (e.g., access rights mismatch).
24    ProtectionViolation,
25    /// Write to a read-only page.
26    WriteToReadOnly,
27    /// Attempt to execute a page marked as no-execute.
28    ExecuteNoExecute,
29    /// User-mode code tried to access a kernel-only page.
30    UserModeKernelAccess,
31}
32
33/// Information about a page fault collected by the architecture trap handler.
34#[derive(Debug, Clone, Copy)]
35pub struct PageFaultInfo {
36    /// The virtual address that caused the fault.
37    pub faulting_address: u64,
38    /// Why the fault occurred.
39    pub reason: PageFaultReason,
40    /// Whether the access was a write (true) or read (false).
41    pub was_write: bool,
42    /// Whether the fault occurred while executing in user mode.
43    pub was_user_mode: bool,
44    /// Instruction pointer at the time of the fault.
45    pub instruction_pointer: u64,
46}
47
48/// Default stack guard region size (one page below the mapped stack).
49const _STACK_GUARD_SIZE: usize = PAGE_SIZE;
50
51/// Maximum stack growth beyond initial allocation (8 MiB).
52/// cc1 (GCC compiler proper) uses deep recursion for complex expressions.
53const MAX_STACK_GROWTH: usize = 8 * 1024 * 1024;
54
55/// Main page fault handler.
56///
57/// Dispatches the fault to the appropriate sub-handler:
58/// 1. **Demand paging** -- the address is within a valid VAS mapping but the
59///    physical page has not been allocated yet.
60/// 2. **Copy-on-Write** -- the page is marked read-only for CoW; allocate a
61///    private copy and remap as writable.
62/// 3. **Stack growth** -- the faulting address is just below the current stack
63///    mapping; extend the stack downward.
64/// 4. If none of the above apply, deliver SIGSEGV / return an error.
65pub fn handle_page_fault(info: PageFaultInfo) -> Result<(), KernelError> {
66    // Attempt demand paging first.
67    if let Ok(()) = try_demand_page(&info) {
68        return Ok(());
69    }
70
71    // Attempt copy-on-write handling.
72    if info.was_write {
73        if let Ok(()) = try_copy_on_write(&info) {
74            return Ok(());
75        }
76    }
77
78    // Attempt stack growth.
79    if let Ok(()) = try_stack_growth(&info) {
80        return Ok(());
81    }
82
83    // None of the handlers could resolve the fault.
84    signal_segv(&info)
85}
86
87// ---------------------------------------------------------------------------
88// Sub-handlers
89// ---------------------------------------------------------------------------
90
91/// Try to resolve the fault via demand paging.
92///
93/// If the faulting address falls within a valid VAS mapping that simply has
94/// not been backed by a physical frame yet, allocate a frame and install the
95/// mapping.
96fn try_demand_page(info: &PageFaultInfo) -> Result<(), KernelError> {
97    let process = crate::process::current_process().ok_or(KernelError::NotInitialized {
98        subsystem: "process",
99    })?;
100
101    let vaddr = VirtualAddress::new(info.faulting_address);
102
103    // Check whether the faulting address is within any existing mapping.
104    let memory_space = process.memory_space.lock();
105
106    #[cfg(feature = "alloc")]
107    {
108        let mapping = memory_space.find_mapping(vaddr);
109        match mapping {
110            Some(m) => {
111                // The address is in a valid mapping. Check whether the page
112                // has already been backed (has a physical frame).
113                let offset = info.faulting_address - m.start.as_u64();
114                let page_index = (offset / PAGE_SIZE as u64) as usize;
115
116                if page_index < m.physical_frames.len() {
117                    // Frame already exists -- this is not a demand-page fault.
118                    return Err(KernelError::InvalidAddress {
119                        addr: info.faulting_address as usize,
120                    });
121                }
122
123                // Permission check: the access type must be allowed by the mapping.
124                if info.was_write && !m.flags.contains(PageFlags::WRITABLE) {
125                    return Err(KernelError::PermissionDenied {
126                        operation: "write to read-only mapping",
127                    });
128                }
129
130                if info.was_user_mode && !m.flags.contains(PageFlags::USER) {
131                    return Err(KernelError::PermissionDenied {
132                        operation: "user access to kernel mapping",
133                    });
134                }
135
136                // Allocate a frame and map the page.
137                // We need to drop the lock before mutating the VAS.
138                drop(memory_space);
139
140                let page_addr = (info.faulting_address & !(PAGE_SIZE as u64 - 1)) as usize;
141                let mut memory_space_mut = process.memory_space.lock();
142                memory_space_mut.map_page(page_addr, m.flags)?;
143
144                Ok(())
145            }
146            None => Err(KernelError::UnmappedMemory {
147                addr: info.faulting_address as usize,
148            }),
149        }
150    }
151
152    #[cfg(not(feature = "alloc"))]
153    {
154        let _ = vaddr;
155        Err(KernelError::NotImplemented {
156            feature: "demand paging (requires alloc)",
157        })
158    }
159}
160
161/// Try to resolve the fault via copy-on-write.
162///
163/// If the page is mapped read-only and is marked for CoW, create a private
164/// copy of the page, map it as writable, and return success.
165fn try_copy_on_write(info: &PageFaultInfo) -> Result<(), KernelError> {
166    // Copy-on-write requires detecting CoW-marked pages. The current VAS
167    // implementation does not track CoW state, so we cannot resolve it yet.
168    // When CoW tracking is added (e.g., a `cow: bool` field on
169    // VirtualMapping), this handler will:
170    //   1. Allocate a new physical frame.
171    //   2. Copy the old frame's contents to the new frame.
172    //   3. Remap the page as writable with the new frame.
173    //   4. Flush the TLB entry.
174    let _ = info;
175    Err(KernelError::NotImplemented {
176        feature: "copy-on-write page handling",
177    })
178}
179
180/// Try to resolve the fault by growing the user stack.
181///
182/// The stack grows downward. If the faulting address is within one
183/// [`MAX_STACK_GROWTH`] below the current stack mapping and above the stack
184/// guard page, we extend the stack by mapping new pages.
185fn try_stack_growth(info: &PageFaultInfo) -> Result<(), KernelError> {
186    // Stack growth only applies to user-mode faults.
187    if !info.was_user_mode {
188        return Err(KernelError::PermissionDenied {
189            operation: "kernel stack growth not supported",
190        });
191    }
192
193    let process = crate::process::current_process().ok_or(KernelError::NotInitialized {
194        subsystem: "process",
195    })?;
196
197    let memory_space = process.memory_space.lock();
198    let stack_top = memory_space.stack_top() as u64;
199    let stack_size = memory_space.user_stack_size() as u64;
200    let stack_bottom = stack_top - stack_size;
201
202    // Check if the fault is just below the current stack bottom.
203    let fault = info.faulting_address;
204    if fault >= stack_bottom {
205        // Already within the stack region -- not a growth fault.
206        return Err(KernelError::InvalidAddress {
207            addr: fault as usize,
208        });
209    }
210
211    // Check the fault isn't beyond the maximum growable region.
212    let max_total_stack = stack_size as usize + MAX_STACK_GROWTH;
213    let absolute_bottom = stack_top.saturating_sub(max_total_stack as u64);
214    if fault < absolute_bottom {
215        // Too far below the stack -- real SIGSEGV.
216        return Err(KernelError::InvalidAddress {
217            addr: fault as usize,
218        });
219    }
220
221    // Calculate how many pages to grow (from current bottom down to fault).
222    let fault_page = fault & !(PAGE_SIZE as u64 - 1);
223    let pages_needed = ((stack_bottom - fault_page) / PAGE_SIZE as u64) as usize;
224
225    if pages_needed == 0 {
226        return Err(KernelError::InvalidAddress {
227            addr: fault as usize,
228        });
229    }
230
231    // Drop the lock and re-acquire as mutable to map pages.
232    drop(memory_space);
233
234    let flags = PageFlags::PRESENT | PageFlags::WRITABLE | PageFlags::USER | PageFlags::NO_EXECUTE;
235
236    let mut memory_space_mut = process.memory_space.lock();
237    for i in 0..pages_needed {
238        let page_addr = (stack_bottom - ((i + 1) as u64 * PAGE_SIZE as u64)) as usize;
239        memory_space_mut.map_page(page_addr, flags)?;
240    }
241
242    // Update stack size to reflect the growth so future faults work correctly.
243    let new_size = stack_size as usize + (pages_needed * PAGE_SIZE);
244    memory_space_mut.set_stack_size(new_size);
245
246    Ok(())
247}
248
249/// Deliver SIGSEGV to the faulting process or return an error for kernel
250/// faults.
251fn signal_segv(info: &PageFaultInfo) -> Result<(), KernelError> {
252    if info.was_user_mode {
253        // Attempt to deliver SIGSEGV to the process.
254        if let Some(process) = crate::process::current_process() {
255            use crate::process::exit::signals::SIGSEGV;
256            if let Err(_e) = crate::process::exit::kill_process(process.pid, SIGSEGV) {
257                kprintln!(
258                    "[MM] Warning: Failed to deliver SIGSEGV to pid {}: {:?}",
259                    process.pid,
260                    _e
261                );
262            }
263        }
264    }
265
266    Err(KernelError::InvalidAddress {
267        addr: info.faulting_address as usize,
268    })
269}
270
271// ---------------------------------------------------------------------------
272// Architecture-specific entry points
273// ---------------------------------------------------------------------------
274
275/// Build a [`PageFaultInfo`] from an x86_64 page fault error code and CR2.
276///
277/// Error code bits (from Intel SDM):
278/// - Bit 0 (P):    0 = not-present, 1 = protection violation
279/// - Bit 1 (W/R):  0 = read, 1 = write
280/// - Bit 2 (U/S):  0 = supervisor, 1 = user
281/// - Bit 4 (I/D):  1 = instruction fetch
282#[cfg(target_arch = "x86_64")]
283pub fn from_x86_64(error_code: u64, cr2: u64, rip: u64) -> PageFaultInfo {
284    let not_present = (error_code & 1) == 0;
285    let was_write = (error_code & 2) != 0;
286    let was_user = (error_code & 4) != 0;
287    let was_fetch = (error_code & 16) != 0;
288
289    let reason = if not_present {
290        PageFaultReason::NotPresent
291    } else if was_fetch {
292        PageFaultReason::ExecuteNoExecute
293    } else if was_write {
294        PageFaultReason::WriteToReadOnly
295    } else if was_user {
296        PageFaultReason::UserModeKernelAccess
297    } else {
298        PageFaultReason::ProtectionViolation
299    };
300
301    PageFaultInfo {
302        faulting_address: cr2,
303        reason,
304        was_write,
305        was_user_mode: was_user,
306        instruction_pointer: rip,
307    }
308}
309
310/// Build a [`PageFaultInfo`] from an AArch64 data/instruction abort.
311///
312/// `esr_el1` contains the ESR value and `far_el1` the faulting address.
313/// ISS encoding for Data Abort (EC=0b100100/0b100101):
314/// - Bit 6 (WnR): 0 = read, 1 = write
315/// - Bits [5:0] (DFSC): fault status code
316#[cfg(target_arch = "aarch64")]
317pub fn from_aarch64(esr_el1: u64, far_el1: u64, elr_el1: u64) -> PageFaultInfo {
318    let dfsc = (esr_el1 & 0x3F) as u8;
319    let was_write = (esr_el1 & (1 << 6)) != 0;
320    // EC field is bits [31:26]
321    let ec = ((esr_el1 >> 26) & 0x3F) as u8;
322    // If EC == 0b100100 the abort came from a lower EL (user mode)
323    let was_user = ec == 0b100100;
324
325    let reason = match dfsc & 0x0F {
326        // Translation faults (levels 0-3)
327        0x04..=0x07 => PageFaultReason::NotPresent,
328        // Permission faults (levels 0-3)
329        0x0C..=0x0F => {
330            if was_write {
331                PageFaultReason::WriteToReadOnly
332            } else {
333                PageFaultReason::ProtectionViolation
334            }
335        }
336        _ => PageFaultReason::ProtectionViolation,
337    };
338
339    PageFaultInfo {
340        faulting_address: far_el1,
341        reason,
342        was_write,
343        was_user_mode: was_user,
344        instruction_pointer: elr_el1,
345    }
346}
347
348/// Build a [`PageFaultInfo`] from a RISC-V page fault trap.
349///
350/// RISC-V uses different exception codes for load, store, and instruction
351/// page faults (causes 12, 13, 15 respectively).
352#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))]
353pub fn from_riscv(cause: u64, stval: u64, sepc: u64) -> PageFaultInfo {
354    let was_write = cause == 15; // Store/AMO page fault
355    let was_fetch = cause == 12; // Instruction page fault
356                                 // cause == 13 is load page fault
357
358    // RISC-V does not encode present vs. permission in the cause alone;
359    // the PTE must be inspected. Default to NotPresent and let the handler
360    // check VAS mappings.
361    let reason = if was_fetch {
362        PageFaultReason::ExecuteNoExecute
363    } else {
364        PageFaultReason::NotPresent
365    };
366
367    // User-mode faults come from U-mode; the SPP bit of sstatus indicates
368    // whether the previous privilege was S-mode. We conservatively mark all
369    // page faults as user-mode here; the caller can refine using sstatus.
370    PageFaultInfo {
371        faulting_address: stval,
372        reason,
373        was_write,
374        was_user_mode: true,
375        instruction_pointer: sepc,
376    }
377}
378
379#[cfg(test)]
380mod tests {
381    use super::*;
382
383    #[test]
384    fn test_page_fault_reason_equality() {
385        assert_eq!(PageFaultReason::NotPresent, PageFaultReason::NotPresent);
386        assert_ne!(
387            PageFaultReason::NotPresent,
388            PageFaultReason::WriteToReadOnly
389        );
390    }
391
392    #[test]
393    fn test_page_fault_info_construction() {
394        let info = PageFaultInfo {
395            faulting_address: 0xDEAD_BEEF,
396            reason: PageFaultReason::NotPresent,
397            was_write: false,
398            was_user_mode: true,
399            instruction_pointer: 0x4010_0000,
400        };
401        assert_eq!(info.faulting_address, 0xDEAD_BEEF);
402        assert!(!info.was_write);
403        assert!(info.was_user_mode);
404    }
405
406    #[test]
407    fn test_page_fault_info_write_fault() {
408        let info = PageFaultInfo {
409            faulting_address: 0x1000,
410            reason: PageFaultReason::WriteToReadOnly,
411            was_write: true,
412            was_user_mode: true,
413            instruction_pointer: 0x2000,
414        };
415        assert!(info.was_write);
416        assert_eq!(info.reason, PageFaultReason::WriteToReadOnly);
417    }
418
419    #[test]
420    fn test_page_fault_info_kernel_fault() {
421        let info = PageFaultInfo {
422            faulting_address: 0xFFFF_8000_0000_1000,
423            reason: PageFaultReason::ProtectionViolation,
424            was_write: false,
425            was_user_mode: false,
426            instruction_pointer: 0xFFFF_8000_0010_0000,
427        };
428        assert!(!info.was_user_mode);
429    }
430}