Skip to main content

kernel/arch/x86_64/interrupts/exceptions/
_14_page_fault.rs

1// SPDX-License-Identifier: GPL-3.0-only
2//! Page-fault exception handler (vector 14, `#PF`).
3//!
4//! Authors: MarioS271
5
6use crate::arch::x86_64::mm::vmm::page_type::PageType;
7use crate::lib::addr::VirtAddr;
8use crate::lib::panic::kernel_panic;
9use crate::lib::panic_codes::PanicCode;
10use crate::lib::types::fmt_buffer::FmtBuffer;
11use crate::mm::layout::USER_MAX;
12use crate::mm::pmm::FRAME_SIZE;
13use crate::mm::vmm::{Vmm, VmmError};
14use crate::mm::vmm::traits::VmmPaging;
15use crate::mm::vmm::vma::VmaFlags;
16use crate::state::kstate::KSTATE;
17use core::fmt::Write;
18use x86_64::registers::control::Cr2;
19use x86_64::structures::idt::{InterruptStackFrame, PageFaultErrorCode};
20use crate::arch::x86_64::exception_table::search_exception_table;
21
22/// Handle Page Faults; dispatches to [`handle_kernel_address_pf`] and [`handle_user_address_pf`]
23pub extern "x86-interrupt" fn handler(
24    mut isf: InterruptStackFrame,
25    error_code: PageFaultErrorCode
26) {
27    let faulting_virt = VirtAddr::new(Cr2::read_raw());
28
29    let is_ring3 = error_code.contains(PageFaultErrorCode::USER_MODE);
30    let is_user_addr = faulting_virt.as_u64() < USER_MAX;
31
32    match (is_ring3, is_user_addr) {
33        // From Ring 3, accessing a user address
34        (true, true) => {
35            handle_user_address_pf(
36                false,
37                &mut isf,
38                error_code,
39                faulting_virt
40            );
41        }
42
43        // From Ring 3, accessing a kernel address
44        (true, false) => {
45            // TODO: actually end process when thats possible here
46            kernel_panic(
47                PanicCode::Unknown,
48                "Attempted to access kernel address from userspace, EXSEGV not implemented yet"
49            );
50        }
51
52        // From Ring 0, accessing a user address
53        (false, true) => {
54            handle_user_address_pf(
55                true,
56                &mut isf,
57                error_code,
58                faulting_virt
59            );
60        }
61
62        // From Ring 0, accessing a kernel address
63        (false, false) => {
64            handle_kernel_address_pf(
65                &isf,
66                error_code,
67                faulting_virt
68            );
69        }
70    }
71
72
73}
74
75/// Handle a kernel page fault; maps a new page if VMA is found, otherwise panics
76#[inline(always)]
77fn handle_kernel_address_pf(
78    isf: &InterruptStackFrame,
79    error_code: PageFaultErrorCode,
80    faulting_virt: VirtAddr
81) {
82    // Safety: interrupts only get enabled after full mm kernel boot, which guarantees that
83    // kernel_addr_space is already initialized
84    let addr_space = unsafe { KSTATE.mm.kernel_addr_space().lock() };
85    let vma = addr_space.find_vma(faulting_virt).unwrap_or_else(
86        || kernel_pf_panic(
87            "Could not find VMA to map a page for",
88            isf, error_code, faulting_virt
89        )
90    );
91
92    let flags = vma.flags;
93    let page_ptr = addr_space.page_ptr();
94    drop(addr_space);
95
96    // Safety: interrupts only get enabled after full mm kernel boot, which guarantees that
97    // pmm is already initialized
98    let mut pmm = unsafe { KSTATE.mm.pmm().lock() };
99    let frame = pmm.alloc_frame().unwrap_or_else(
100        || kernel_pf_panic(
101            "Could not map a new page, out of memory",
102            isf, error_code, faulting_virt
103        )
104    );
105
106    // Safety: page_ptr is the correct virtual address of the kernel PML4 and frame is a valid
107    // PMM-allocated memory frame
108    unsafe {
109        if let Err(error) = Vmm::map_page(
110            &mut pmm,
111            page_ptr,
112            faulting_virt.align_down(FRAME_SIZE),
113            frame,
114            PageType::Normal,
115            Vmm::vma_flags_to_page_flags(flags)
116        ) {
117            // TODO: check the occurred error type and react accordingly
118
119            kernel_panic(
120                PanicCode::OutOfMemory,
121                "Could not lazy-map a page for the kernel, out of memory"
122            );
123        }
124    }
125}
126
127/// Handle Page Faults which happened when accessing a user address (either from userspace or from the
128/// kernel in a syscall)
129#[inline(always)]
130fn handle_user_address_pf(
131    from_ring0: bool,
132    isf: &mut InterruptStackFrame,
133    error_code: PageFaultErrorCode,
134    faulting_virt: VirtAddr
135) {
136    let active_pid = KSTATE.sched.active_pid();
137
138    // Safety: we are already past boot stage 2 and no user memory is touched in the closure
139    let (vma_flags, page_ptr) = unsafe {
140        KSTATE.sched.with_process(active_pid, |proc| {
141            (proc.addr_space.find_vma(faulting_virt).map(|vma| vma.flags), proc.addr_space.page_ptr())
142        }).unwrap_or_else(
143            || kernel_pf_panic(
144                "Attempted to fetch process data of non-existent process in page fault handler (this is likely a kernel bug)",
145                isf, error_code, faulting_virt
146            )
147        )
148    };
149
150    let Some(vma_flags) = vma_flags else {
151        user_pf_cant_map_handler(
152            from_ring0,
153            isf,
154            error_code,
155            faulting_virt
156        );
157        return;
158    };
159
160    let mapping_allowed =
161        if error_code.contains(PageFaultErrorCode::PROTECTION_VIOLATION) { false }
162        else if error_code.contains(PageFaultErrorCode::CAUSED_BY_WRITE) && !vma_flags.contains(VmaFlags::WRITE) { false }
163        else if error_code.contains(PageFaultErrorCode::INSTRUCTION_FETCH) && !vma_flags.contains(VmaFlags::EXEC) { false }
164        else { true };
165
166    if !mapping_allowed {
167        user_pf_cant_map_handler(
168            from_ring0,
169            isf,
170            error_code,
171            faulting_virt
172        );
173        return;
174    }
175
176    // Safety: interrupts only get enabled after full mm kernel boot, which guarantees that
177    // pmm is already initialized
178    let mut pmm = unsafe { KSTATE.mm.pmm().lock() };
179    let frame = pmm.alloc_frame_zeroed().unwrap_or_else(
180        // this is a kernel bug because memory was reserved but couldn't be mapped,
181        // suggesting the VMM's bookkeeping is broken
182        || kernel_pf_panic(
183            "Could not lazy-map a page for a user process, out of memory (this is a kernel bug)",
184            isf, error_code, faulting_virt
185        )
186    );
187
188    // Safety: page_ptr is the correct virtual address of the kernel PML4 and frame is a valid
189    // PMM-allocated memory frame
190    unsafe {
191        if let Err(error) = Vmm::map_page(
192            &mut pmm,
193            page_ptr,
194            faulting_virt.align_down(FRAME_SIZE),
195            frame,
196            PageType::Normal,
197            Vmm::vma_flags_to_page_flags(vma_flags)
198        ) {
199            // TODO: check the occurred error type and react accordingly
200
201            // this is a kernel bug because memory was reserved but couldn't be mapped,
202            // suggesting the VMM's bookkeeping is broken
203            kernel_panic(
204                PanicCode::OutOfMemory,
205                "Could not lazy-map a page for a user process, out of memory (this is a kernel bug)"
206            );
207        }
208    }
209}
210
211/// Handle the user page fault handler being unable to map
212#[cold]
213fn user_pf_cant_map_handler(
214    from_ring0: bool,
215    isf: &mut InterruptStackFrame,
216    error_code: PageFaultErrorCode,
217    faulting_virt: VirtAddr
218) {
219    if from_ring0 {
220        match search_exception_table(isf.instruction_pointer.as_u64()) {
221            Some(fixup) => {
222                // Safety: fixup comes from the exception table which is compile-time valid. It
223                // points to directly after the faulting copy instruction, where the regs are
224                // already in the expected state
225                unsafe {
226                    isf.as_mut().update(
227                        |frame| frame.instruction_pointer = x86_64::VirtAddr::new(fixup)
228                    );
229                }
230            }
231            None => {
232                kernel_pf_panic(
233                    "Kernel accessed unmapped user memory outside of the exception table",
234                    isf, error_code, faulting_virt
235                )
236            }
237        }
238    } else {
239        // TODO: send EXSEGV
240        kernel_pf_panic(
241            "Process tried accessing unmapped memory (from userspace); TODO: send EXSEGV",
242            isf, error_code, faulting_virt
243        )
244    }
245}
246
247/// Wrapper for [`kernel_panic`] which appends the error code, CR2 address and interrupt stack frame
248/// onto the panic message
249#[cold]
250#[inline(never)]
251fn kernel_pf_panic(
252    panic_message: &str,
253    isf: &InterruptStackFrame,
254    error_code: PageFaultErrorCode,
255    faulting_virt: VirtAddr
256) -> ! {
257    let mut fmt_buffer = FmtBuffer::<1024>::new();
258    let _ = write!(
259        &mut fmt_buffer,
260        "{}\n\nCR2: {:x}\nError Code: {:?}\n\n{:#?}",
261        panic_message, faulting_virt, error_code, isf
262    );
263    kernel_panic(
264        PanicCode::PageFault,
265        fmt_buffer.as_str(),
266    );
267}