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

veridian_kernel/mm/
vas.rs

1//! Virtual Address Space management
2//!
3//! Manages virtual memory for processes including page tables,
4//! memory mappings, and address space operations.
5
6#![allow(clippy::manual_div_ceil)]
7
8use core::sync::atomic::{AtomicU64, Ordering};
9
10#[cfg(feature = "alloc")]
11extern crate alloc;
12
13#[cfg(feature = "alloc")]
14use alloc::{collections::BTreeMap, vec::Vec};
15
16use spin::Mutex;
17
18use super::{
19    page_table::{FrameAllocator as PageFrameAllocator, PageMapper, PageTable, PAGE_TABLE_ENTRIES},
20    FrameAllocatorError, FrameNumber, PageFlags, VirtualAddress, FRAME_ALLOCATOR, FRAME_SIZE,
21};
22use crate::error::KernelError;
23
24/// Frame allocator wrapper implementing the page_table::FrameAllocator trait.
25/// Delegates to the global FRAME_ALLOCATOR.
26struct VasFrameAllocator;
27
28impl PageFrameAllocator for VasFrameAllocator {
29    fn allocate_frames(
30        &mut self,
31        count: usize,
32        numa_node: Option<usize>,
33    ) -> Result<FrameNumber, FrameAllocatorError> {
34        FRAME_ALLOCATOR.lock().allocate_frames(count, numa_node)
35    }
36}
37
38/// Create a PageMapper from a page table root physical address.
39///
40/// # Safety
41///
42/// The `page_table_root` must be a valid physical address of a properly
43/// initialized L4 page table. The physical address must be identity-mapped
44/// or accessible via the kernel's physical memory map so that it can be
45/// dereferenced as a pointer. The caller must ensure exclusive access to
46/// the page table hierarchy for the duration of the returned PageMapper's
47/// use.
48unsafe fn create_mapper_from_root(page_table_root: u64) -> PageMapper {
49    let virt = super::phys_to_virt_addr(page_table_root);
50    let l4_ptr = virt as *mut super::page_table::PageTable;
51    // SAFETY: The caller guarantees that `page_table_root` is a valid
52    // physical address of an L4 page table. phys_to_virt_addr converts
53    // it to the corresponding virtual address in the bootloader's
54    // physical memory mapping.
55    unsafe { PageMapper::new(l4_ptr) }
56}
57
58/// Public wrapper around `create_mapper_from_root` for use by other kernel
59/// modules (e.g., process creation for writing to user stack).
60///
61/// # Safety
62///
63/// Same requirements as [`create_mapper_from_root`].
64pub unsafe fn create_mapper_from_root_pub(page_table_root: u64) -> PageMapper {
65    // SAFETY: Caller guarantees page_table_root is a valid, identity-mapped L4 page
66    // table address.
67    unsafe { create_mapper_from_root(page_table_root) }
68}
69
70/// Free all user-space page table frames in a page table hierarchy.
71///
72/// Walks the L4 table and for each **user-space** L4 entry (indices 0..256),
73/// recursively frees L3, L2, and L1 table frames. Kernel-space entries
74/// (indices 256..512) are left untouched because they are shared across all
75/// address spaces (copied from the boot page tables).
76///
77/// Finally, frees the L4 frame itself.
78///
79/// Returns the number of frames freed.
80pub fn free_user_page_table_frames(l4_phys: u64) -> usize {
81    if l4_phys == 0 {
82        return 0;
83    }
84
85    let phys_offset_val = super::PHYS_MEM_OFFSET.load(core::sync::atomic::Ordering::Acquire);
86    let mut freed = 0usize;
87
88    // SAFETY: l4_phys is a valid physical address of an L4 page table.
89    // phys_to_virt_addr maps it to the kernel's identity-mapped region.
90    let l4_table = unsafe { &*(super::phys_to_virt_addr(l4_phys) as *const PageTable) };
91
92    // Only walk user-space entries (0..256). Kernel entries (256..512) are
93    // shared references to the boot page tables and must NOT be freed.
94    for l4_idx in 0..256 {
95        let l4_entry = &l4_table[l4_idx];
96        if !l4_entry.is_present() {
97            continue;
98        }
99
100        // Also skip the physical memory mapping L4 entry (bootloader puts
101        // the identity map in a lower-half L4 slot).
102        if phys_offset_val != 0 {
103            let phys_l4_idx = ((phys_offset_val >> 39) & 0x1FF) as usize;
104            if l4_idx == phys_l4_idx {
105                continue;
106            }
107        }
108
109        let l3_phys = match l4_entry.addr() {
110            Some(a) => a.as_u64(),
111            None => continue,
112        };
113
114        // Walk L3 table
115        // SAFETY: l3_phys is from a present L4 entry; phys_to_virt_addr maps it into
116        // the kernel's identity-mapped region.
117        let l3_table = unsafe { &*(super::phys_to_virt_addr(l3_phys) as *const PageTable) };
118        for l3_idx in 0..PAGE_TABLE_ENTRIES {
119            let l3_entry = &l3_table[l3_idx];
120            if !l3_entry.is_present() {
121                continue;
122            }
123            // Skip huge pages (1GB) -- they have no L2 subtable
124            if l3_entry.flags().0 & PageFlags::HUGE.0 != 0 {
125                continue;
126            }
127
128            let l2_phys = match l3_entry.addr() {
129                Some(a) => a.as_u64(),
130                None => continue,
131            };
132
133            // Walk L2 table
134            // SAFETY: l2_phys is from a present L3 entry; phys_to_virt_addr maps it into
135            // the kernel's identity-mapped region.
136            let l2_table = unsafe { &*(super::phys_to_virt_addr(l2_phys) as *const PageTable) };
137            for l2_idx in 0..PAGE_TABLE_ENTRIES {
138                let l2_entry = &l2_table[l2_idx];
139                if !l2_entry.is_present() {
140                    continue;
141                }
142                // Skip huge pages (2MB) -- they have no L1 subtable
143                if l2_entry.flags().0 & PageFlags::HUGE.0 != 0 {
144                    continue;
145                }
146
147                let l1_phys = match l2_entry.addr() {
148                    Some(a) => a.as_u64(),
149                    None => continue,
150                };
151
152                // Free the L1 table frame
153                let l1_frame = FrameNumber::new(l1_phys / FRAME_SIZE as u64);
154                FRAME_ALLOCATOR.lock().free_frames(l1_frame, 1).ok();
155                freed += 1;
156            }
157
158            // Free the L2 table frame
159            let l2_frame = FrameNumber::new(l2_phys / FRAME_SIZE as u64);
160            FRAME_ALLOCATOR.lock().free_frames(l2_frame, 1).ok();
161            freed += 1;
162        }
163
164        // Free the L3 table frame
165        let l3_frame = FrameNumber::new(l3_phys / FRAME_SIZE as u64);
166        FRAME_ALLOCATOR.lock().free_frames(l3_frame, 1).ok();
167        freed += 1;
168    }
169
170    // Free the L4 table frame itself
171    let l4_frame = FrameNumber::new(l4_phys / FRAME_SIZE as u64);
172    FRAME_ALLOCATOR.lock().free_frames(l4_frame, 1).ok();
173    freed += 1;
174
175    freed
176}
177
178/// Free user-space page table subtrees (L3/L2/L1) but keep the L4 frame.
179///
180/// Used during exec to reclaim intermediate page table frames from the old
181/// executable's mappings while keeping the L4 root for reuse. After this
182/// call, user-space L4 entries (0..256) are cleared so fresh intermediate
183/// tables will be allocated by subsequent `map_page` calls.
184fn free_user_page_table_subtrees(l4_phys: u64) {
185    let phys_offset_val = super::PHYS_MEM_OFFSET.load(core::sync::atomic::Ordering::Acquire);
186
187    // SAFETY: l4_phys is a valid physical address of an L4 page table.
188    let l4_table = unsafe { &mut *(super::phys_to_virt_addr(l4_phys) as *mut PageTable) };
189
190    for l4_idx in 0..256 {
191        let l4_entry = &l4_table[l4_idx];
192        if !l4_entry.is_present() {
193            continue;
194        }
195
196        // Skip the physical memory mapping L4 entry
197        if phys_offset_val != 0 {
198            let phys_l4_idx = ((phys_offset_val >> 39) & 0x1FF) as usize;
199            if l4_idx == phys_l4_idx {
200                continue;
201            }
202        }
203
204        let l3_phys = match l4_entry.addr() {
205            Some(a) => a.as_u64(),
206            None => continue,
207        };
208
209        // Walk and free L3 subtree
210        // SAFETY: l3_phys is from a present L4 entry; phys_to_virt_addr maps it into
211        // the kernel's identity-mapped region.
212        let l3_table = unsafe { &*(super::phys_to_virt_addr(l3_phys) as *const PageTable) };
213        for l3_idx in 0..PAGE_TABLE_ENTRIES {
214            let l3_entry = &l3_table[l3_idx];
215            if !l3_entry.is_present() || l3_entry.flags().0 & PageFlags::HUGE.0 != 0 {
216                continue;
217            }
218
219            let l2_phys = match l3_entry.addr() {
220                Some(a) => a.as_u64(),
221                None => continue,
222            };
223
224            // SAFETY: l2_phys is from a present L3 entry; phys_to_virt_addr maps it into
225            // the kernel's identity-mapped region.
226            let l2_table = unsafe { &*(super::phys_to_virt_addr(l2_phys) as *const PageTable) };
227            for l2_idx in 0..PAGE_TABLE_ENTRIES {
228                let l2_entry = &l2_table[l2_idx];
229                if !l2_entry.is_present() || l2_entry.flags().0 & PageFlags::HUGE.0 != 0 {
230                    continue;
231                }
232
233                let l1_phys = match l2_entry.addr() {
234                    Some(a) => a.as_u64(),
235                    None => continue,
236                };
237
238                // Free L1 frame
239                let l1_frame = FrameNumber::new(l1_phys / FRAME_SIZE as u64);
240                FRAME_ALLOCATOR.lock().free_frames(l1_frame, 1).ok();
241            }
242
243            // Free L2 frame
244            let l2_frame = FrameNumber::new(l2_phys / FRAME_SIZE as u64);
245            FRAME_ALLOCATOR.lock().free_frames(l2_frame, 1).ok();
246        }
247
248        // Free L3 frame
249        let l3_frame = FrameNumber::new(l3_phys / FRAME_SIZE as u64);
250        FRAME_ALLOCATOR.lock().free_frames(l3_frame, 1).ok();
251
252        // Clear the L4 entry so new mappings get fresh page tables
253        l4_table[l4_idx].clear();
254    }
255}
256
257/// Memory mapping types
258#[derive(Debug, Clone, Copy, PartialEq, Eq)]
259pub enum MappingType {
260    /// Code segment (executable)
261    Code,
262    /// Data segment (read/write)
263    Data,
264    /// Stack segment
265    Stack,
266    /// Heap segment
267    Heap,
268    /// Memory-mapped file
269    File,
270    /// Shared memory
271    Shared,
272    /// Device memory (no caching)
273    Device,
274}
275
276/// Virtual memory mapping
277#[derive(Debug, Clone)]
278pub struct VirtualMapping {
279    /// Start address
280    pub start: VirtualAddress,
281    /// Size in bytes
282    pub size: usize,
283    /// Mapping type
284    pub mapping_type: MappingType,
285    /// Page flags
286    pub flags: PageFlags,
287    /// Backing physical frames (if mapped)
288    #[cfg(feature = "alloc")]
289    pub physical_frames: Vec<super::FrameNumber>,
290}
291
292impl VirtualMapping {
293    /// Create a new virtual mapping
294    pub fn new(start: VirtualAddress, size: usize, mapping_type: MappingType) -> Self {
295        let flags = match mapping_type {
296            MappingType::Code => PageFlags::PRESENT | PageFlags::USER,
297            MappingType::Data => PageFlags::PRESENT | PageFlags::WRITABLE | PageFlags::USER,
298            MappingType::Stack => {
299                PageFlags::PRESENT | PageFlags::WRITABLE | PageFlags::USER | PageFlags::NO_EXECUTE
300            }
301            MappingType::Heap => {
302                PageFlags::PRESENT | PageFlags::WRITABLE | PageFlags::USER | PageFlags::NO_EXECUTE
303            }
304            MappingType::File => PageFlags::PRESENT | PageFlags::USER,
305            MappingType::Shared => PageFlags::PRESENT | PageFlags::WRITABLE | PageFlags::USER,
306            MappingType::Device => PageFlags::PRESENT | PageFlags::WRITABLE | PageFlags::NO_CACHE,
307        };
308
309        Self {
310            start,
311            size,
312            mapping_type,
313            flags,
314            #[cfg(feature = "alloc")]
315            physical_frames: Vec::new(),
316        }
317    }
318
319    /// Check if address is within this mapping
320    pub fn contains(&self, addr: VirtualAddress) -> bool {
321        addr.0 >= self.start.0 && addr.0 < self.start.0 + self.size as u64
322    }
323
324    /// Get end address
325    pub fn end(&self) -> VirtualAddress {
326        VirtualAddress(self.start.0 + self.size as u64)
327    }
328}
329
330/// Virtual Address Space for a process
331pub struct VirtualAddressSpace {
332    /// Page table root (CR3 on x86_64)
333    pub page_table_root: AtomicU64,
334
335    /// Virtual memory mappings
336    #[cfg(feature = "alloc")]
337    mappings: Mutex<BTreeMap<VirtualAddress, VirtualMapping>>,
338
339    /// Next free address for mmap
340    next_mmap_addr: AtomicU64,
341
342    /// Heap start and current break
343    heap_start: AtomicU64,
344    heap_break: AtomicU64,
345
346    /// Stack top (grows down)
347    stack_top: AtomicU64,
348    /// Stack size (bytes)
349    stack_size: AtomicU64,
350
351    /// TLB generation counter. Incremented on every page table modification.
352    /// The scheduler compares this against the last-seen generation at switch
353    /// time to determine whether a TLB flush is needed.
354    pub tlb_generation: AtomicU64,
355}
356
357/// Batched TLB flush accumulator.
358///
359/// Collects up to `MAX_BATCH` virtual addresses for individual flushes.
360/// If more than `MAX_BATCH` addresses are accumulated, the entire TLB is
361/// flushed on commit. This reduces the overhead of multiple individual
362/// `invlpg` instructions in loops (e.g., munmap of many pages).
363pub struct TlbFlushBatch {
364    addresses: [u64; Self::MAX_BATCH],
365    count: usize,
366}
367
368impl Default for TlbFlushBatch {
369    fn default() -> Self {
370        Self::new()
371    }
372}
373
374impl TlbFlushBatch {
375    const MAX_BATCH: usize = 16;
376
377    /// Create a new empty batch.
378    pub const fn new() -> Self {
379        Self {
380            addresses: [0; Self::MAX_BATCH],
381            count: 0,
382        }
383    }
384
385    /// Add an address to the batch. Does not flush yet.
386    #[inline]
387    pub fn add(&mut self, vaddr: u64) {
388        if self.count < Self::MAX_BATCH {
389            self.addresses[self.count] = vaddr;
390        }
391        self.count += 1; // Allow overflow past MAX_BATCH to trigger full flush
392    }
393
394    /// Flush all accumulated addresses. If > MAX_BATCH, do a full TLB flush.
395    pub fn flush(self) {
396        if self.count == 0 {
397            return;
398        }
399        if self.count > Self::MAX_BATCH {
400            // Too many addresses -- full TLB flush is cheaper
401            crate::arch::tlb_flush_all();
402        } else {
403            // Individual flushes for small batches
404            for i in 0..self.count {
405                crate::arch::tlb_flush_address(self.addresses[i]);
406            }
407        }
408    }
409
410    /// Number of addresses accumulated
411    pub fn len(&self) -> usize {
412        self.count
413    }
414
415    /// Is the batch empty?
416    pub fn is_empty(&self) -> bool {
417        self.count == 0
418    }
419
420    /// Flush locally and broadcast TLB shootdown IPI to all other CPUs.
421    ///
422    /// On single-CPU systems the IPI is a no-op (no other CPUs to notify).
423    /// On multi-CPU systems, remote CPUs receive vector 49 and flush their
424    /// entire TLB in the handler.
425    pub fn flush_with_shootdown(self) {
426        // First, flush the local CPU's TLB.
427        self.flush();
428
429        // Then broadcast TLB shootdown IPI to all other CPUs.
430        // On x86_64 with APIC initialized, this sends vector 49 via the ICR
431        // "all excluding self" shorthand. On other architectures or if APIC is
432        // not initialized, this is a no-op.
433        #[cfg(target_arch = "x86_64")]
434        {
435            if crate::arch::x86_64::apic::is_initialized() {
436                let _ = crate::arch::x86_64::apic::send_ipi_all_excluding_self(
437                    crate::arch::x86_64::apic::TLB_SHOOTDOWN_VECTOR,
438                );
439            }
440        }
441    }
442}
443
444impl Default for VirtualAddressSpace {
445    fn default() -> Self {
446        Self {
447            page_table_root: AtomicU64::new(0),
448            #[cfg(feature = "alloc")]
449            mappings: Mutex::new(BTreeMap::new()),
450            // Start mmap region at 0x4000_0000_0000 (256GB)
451            next_mmap_addr: AtomicU64::new(0x4000_0000_0000),
452            // Heap starts at 0x2000_0000_0000 (128GB)
453            heap_start: AtomicU64::new(0x2000_0000_0000),
454            heap_break: AtomicU64::new(0x2000_0000_0000),
455            // Stack starts at 0x7FFF_FFFF_0000 and grows down
456            stack_top: AtomicU64::new(0x7FFF_FFFF_0000),
457            stack_size: AtomicU64::new(8 * 1024 * 1024),
458            tlb_generation: AtomicU64::new(0),
459        }
460    }
461}
462
463impl VirtualAddressSpace {
464    /// Create a new virtual address space
465    pub fn new() -> Self {
466        Self::default()
467    }
468
469    /// Initialize virtual address space
470    pub fn init(&mut self) -> Result<(), KernelError> {
471        use super::page_table::PageTableHierarchy;
472
473        // Allocate L4 page table
474        let page_table = PageTableHierarchy::new()?;
475        self.page_table_root
476            .store(page_table.l4_addr().as_u64(), Ordering::Release);
477
478        // Map kernel space
479        self.map_kernel_space()?;
480
481        Ok(())
482    }
483
484    /// Map kernel space into this address space.
485    ///
486    /// Copies the upper-half L4 entries (indices 256-511) from the current
487    /// (boot) page tables into this VAS's L4, plus the bootloader's physical
488    /// memory mapping entry (which may be in the lower half). This shares the
489    /// kernel's code, data, heap, MMIO, and physical memory access with the
490    /// new process, so that the kernel remains accessible during syscalls
491    /// (which run with the user's CR3).
492    pub fn map_kernel_space(&mut self) -> Result<(), KernelError> {
493        use super::page_table::{PageTable, PAGE_TABLE_ENTRIES};
494
495        let new_root = self.page_table_root.load(Ordering::Acquire);
496        if new_root == 0 {
497            return Err(KernelError::NotInitialized {
498                subsystem: "VAS page table",
499            });
500        }
501
502        // Read the current (boot) CR3 to get the kernel's L4 entries
503        let boot_cr3: u64;
504        #[cfg(target_arch = "x86_64")]
505        {
506            // SAFETY: Reading CR3 is a read-only privileged operation.
507            unsafe {
508                core::arch::asm!("mov {}, cr3", out(reg) boot_cr3);
509            }
510        }
511        #[cfg(not(target_arch = "x86_64"))]
512        {
513            boot_cr3 = 0;
514        }
515
516        let boot_l4_phys = boot_cr3 & 0x000F_FFFF_FFFF_F000;
517        if boot_l4_phys == 0 {
518            // On non-x86_64 or if CR3 is somehow 0, just record regions
519            #[cfg(feature = "alloc")]
520            {
521                self.map_region(
522                    VirtualAddress(0xFFFF_8000_0000_0000),
523                    0x200000,
524                    MappingType::Code,
525                )?;
526                self.map_region(
527                    VirtualAddress(0xFFFF_8000_0020_0000),
528                    0x200000,
529                    MappingType::Data,
530                )?;
531                self.map_region(
532                    VirtualAddress(0xFFFF_C000_0000_0000),
533                    0x1000000,
534                    MappingType::Heap,
535                )?;
536            }
537            return Ok(());
538        }
539
540        // Copy kernel-space L4 entries (indices 256-511) from boot page
541        // tables into the new process's L4. This shares the entire
542        // kernel upper-half mapping.
543        // SAFETY: Both boot_l4_phys and new_root are valid L4 page table
544        // physical addresses. We convert via phys_to_virt_addr to get
545        // kernel-accessible pointers. We copy only the upper half (kernel
546        // space), leaving the lower half (user space) zeroed.
547        unsafe {
548            let boot_l4 = &*(super::phys_to_virt_addr(boot_l4_phys) as *const PageTable);
549            let new_l4 = &mut *(super::phys_to_virt_addr(new_root) as *mut PageTable);
550
551            for i in 256..PAGE_TABLE_ENTRIES {
552                if boot_l4[i].is_present() {
553                    new_l4[i] = boot_l4[i];
554                }
555            }
556
557            // Also copy the bootloader's physical memory mapping L4 entry.
558            // On x86_64, PHYS_MEM_OFFSET is typically in the lower half
559            // (e.g. 0x180_0000_0000 = L4 index 3). Without this, syscalls
560            // running with the user's CR3 cannot access physical memory via
561            // phys_to_virt_addr(), causing page faults in kernel code.
562            let phys_offset = super::PHYS_MEM_OFFSET.load(core::sync::atomic::Ordering::Acquire);
563            if phys_offset != 0 {
564                let phys_l4_idx = ((phys_offset >> 39) & 0x1FF) as usize;
565                if phys_l4_idx < 256 && boot_l4[phys_l4_idx].is_present() {
566                    new_l4[phys_l4_idx] = boot_l4[phys_l4_idx];
567                }
568            }
569        }
570
571        Ok(())
572    }
573
574    /// Clone from another address space (deep copy for fork).
575    ///
576    /// Allocates a new L4 page table for this VAS, copies kernel-space L4
577    /// entries directly (shared kernel mapping), and for each user-space page
578    /// in the parent, allocates a new physical frame, copies the 4KB content,
579    /// and maps it into this VAS's page tables with the same flags.
580    #[cfg(feature = "alloc")]
581    pub fn clone_from(&mut self, other: &Self) -> Result<(), KernelError> {
582        use super::page_table::{PageTable, PageTableHierarchy, PAGE_TABLE_ENTRIES};
583
584        // Step 1: Allocate a new L4 page table for the child
585        let new_hierarchy = PageTableHierarchy::new()?;
586        let new_root = new_hierarchy.l4_addr().as_u64();
587        self.page_table_root.store(new_root, Ordering::Release);
588
589        let parent_root = other.page_table_root.load(Ordering::Acquire);
590
591        if parent_root != 0 {
592            // Step 2: Copy kernel-space L4 entries (indices 256-511) directly.
593            // These are shared across all address spaces.
594            // SAFETY: parent_root is a valid L4 page table physical address;
595            // phys_to_virt_addr maps it into the kernel's identity-mapped region.
596            let parent_l4 =
597                unsafe { &*(super::phys_to_virt_addr(parent_root) as *const PageTable) };
598            // SAFETY: new_root was just allocated by PageTableHierarchy::new() and is a
599            // valid, zeroed L4 page table.
600            let child_l4 = unsafe { &mut *(super::phys_to_virt_addr(new_root) as *mut PageTable) };
601
602            for i in 256..PAGE_TABLE_ENTRIES {
603                child_l4[i] = parent_l4[i];
604            }
605
606            // Also copy the bootloader's physical memory mapping L4 entry
607            // (may be in the lower half, e.g. L4 index 3 for 0x180_0000_0000).
608            let phys_offset = super::PHYS_MEM_OFFSET.load(core::sync::atomic::Ordering::Acquire);
609            if phys_offset != 0 {
610                let phys_l4_idx = ((phys_offset >> 39) & 0x1FF) as usize;
611                if phys_l4_idx < 256 {
612                    child_l4[phys_l4_idx] = parent_l4[phys_l4_idx];
613                }
614            }
615
616            // Step 3: Deep-copy user-space pages.
617            // Walk parent's mappings (which track user-space regions) and for
618            // each mapped page, allocate a new frame, copy content, and map.
619            let parent_mappings = other.mappings.lock();
620            let mut child_mappings = self.mappings.lock();
621            child_mappings.clear();
622
623            // SAFETY: parent_root is a valid identity-mapped L4 page table.
624            let parent_mapper = unsafe { create_mapper_from_root(parent_root) };
625            // SAFETY: new_root was just allocated and kernel entries copied.
626            let mut child_mapper = unsafe { create_mapper_from_root(new_root) };
627            let mut alloc = VasFrameAllocator;
628
629            const KERNEL_SPACE_START: u64 = 0xFFFF_8000_0000_0000;
630
631            for (addr, mapping) in parent_mappings.iter() {
632                // Only deep-copy user-space mappings
633                if addr.0 >= KERNEL_SPACE_START {
634                    // Kernel mappings are already shared via L4 entries above
635                    child_mappings.insert(*addr, mapping.clone());
636                    continue;
637                }
638
639                let num_pages = mapping.size / 4096;
640                let mut child_frames = Vec::with_capacity(num_pages);
641
642                for i in 0..num_pages {
643                    let vaddr = VirtualAddress(mapping.start.0 + (i as u64) * 4096);
644
645                    // Look up the parent's physical frame and flags
646                    let (parent_frame, flags) = match parent_mapper.translate_page(vaddr) {
647                        Ok(result) => result,
648                        Err(_) => continue, // Page not actually mapped in HW
649                    };
650
651                    // Allocate a new frame for the child
652                    let child_frame = {
653                        FRAME_ALLOCATOR
654                            .lock()
655                            .allocate_frames(1, None)
656                            .map_err(|_| KernelError::OutOfMemory {
657                                requested: 4096,
658                                available: 0,
659                            })?
660                    };
661
662                    // Copy 4KB of content from parent frame to child frame.
663                    // SAFETY: Both frame addresses are physical and must be
664                    // converted to virtual addresses via the bootloader's
665                    // physical memory mapping before access.
666                    unsafe {
667                        let src_phys = parent_frame.as_u64() << 12;
668                        let dst_phys = child_frame.as_u64() << 12;
669                        let src = super::phys_to_virt_addr(src_phys) as *const u8;
670                        let dst = super::phys_to_virt_addr(dst_phys) as *mut u8;
671                        core::ptr::copy_nonoverlapping(src, dst, 4096);
672                    }
673
674                    // Map the child's frame at the same virtual address
675                    child_mapper
676                        .map_page(vaddr, child_frame, flags, &mut alloc)
677                        .ok(); // Ignore errors for already-mapped pages
678
679                    child_frames.push(child_frame);
680                }
681
682                // Record the mapping with the child's physical frames
683                let mut child_mapping = mapping.clone();
684                child_mapping.physical_frames = child_frames;
685                child_mappings.insert(*addr, child_mapping);
686            }
687        }
688
689        // Copy metadata
690        self.heap_start
691            .store(other.heap_start.load(Ordering::Relaxed), Ordering::Relaxed);
692        self.heap_break
693            .store(other.heap_break.load(Ordering::Relaxed), Ordering::Relaxed);
694        self.stack_top
695            .store(other.stack_top.load(Ordering::Relaxed), Ordering::Relaxed);
696        self.next_mmap_addr.store(
697            other.next_mmap_addr.load(Ordering::Relaxed),
698            Ordering::Relaxed,
699        );
700
701        Ok(())
702    }
703
704    /// Clone from another address space (no-alloc stub).
705    #[cfg(not(feature = "alloc"))]
706    pub fn clone_from(&mut self, _other: &Self) -> Result<(), KernelError> {
707        Err(KernelError::NotImplemented {
708            feature: "clone_from (requires alloc)",
709        })
710    }
711
712    /// Destroy the address space
713    pub fn destroy(&mut self) {
714        #[cfg(feature = "alloc")]
715        {
716            let pt_root = self.page_table_root.load(Ordering::Acquire);
717
718            // First unmap all regions from page tables and free physical frames
719            let mut mappings = self.mappings.lock();
720
721            // Unmap from architecture page tables if we have a valid root
722            if pt_root != 0 {
723                // SAFETY: `pt_root` is a non-zero physical address of an L4
724                // page table set during VAS::init(). The address is identity-
725                // mapped in the kernel's physical memory window. We have
726                // `&mut self`, ensuring exclusive access.
727                let mut mapper = unsafe { create_mapper_from_root(pt_root) };
728
729                for (_, mapping) in mappings.iter() {
730                    let num_pages = mapping.size / 4096;
731                    for i in 0..num_pages {
732                        let vaddr = VirtualAddress(mapping.start.0 + (i as u64) * 4096);
733                        let _ = mapper.unmap_page(vaddr);
734                    }
735                }
736            }
737
738            // Free physical frames for each mapping
739            for (_, mapping) in mappings.iter() {
740                let allocator = FRAME_ALLOCATOR.lock();
741                for &frame in &mapping.physical_frames {
742                    let _ = allocator.free_frames(frame, 1);
743                }
744            }
745
746            // Clear all mappings
747            mappings.clear();
748
749            // NOTE: Page table frames are NOT freed here -- see clear() comment.
750            // The caller must free them after switching to a different CR3.
751
752            // Flush entire TLB since we destroyed the whole address space
753            crate::arch::tlb_flush_all();
754        }
755    }
756
757    /// Set page table root
758    pub fn set_page_table(&self, root_phys_addr: u64) {
759        self.page_table_root
760            .store(root_phys_addr, Ordering::Release);
761    }
762
763    /// Get page table root
764    pub fn get_page_table(&self) -> u64 {
765        self.page_table_root.load(Ordering::Acquire)
766    }
767
768    /// Map a region of virtual memory
769    #[cfg(feature = "alloc")]
770    pub fn map_region(
771        &self,
772        start: VirtualAddress,
773        size: usize,
774        mapping_type: MappingType,
775    ) -> Result<(), KernelError> {
776        // Align to page boundary
777        let aligned_start = VirtualAddress(start.0 & !(4096 - 1));
778        let aligned_size = ((size + 4095) / 4096) * 4096;
779
780        let mapping = VirtualMapping::new(aligned_start, aligned_size, mapping_type);
781
782        let mut mappings = self.mappings.lock();
783
784        // Check for overlaps using standard interval overlap test:
785        // [a_start, a_end) and [b_start, b_end) overlap iff
786        // a_start < b_end AND b_start < a_end.
787        // The previous check missed containment (new fully contains existing)
788        // and falsely rejected adjacent mappings (end == start).
789        let b_start = aligned_start.0;
790        let b_end = aligned_start.0 + aligned_size as u64;
791        for (_, existing) in mappings.iter() {
792            let a_start = existing.start.0;
793            let a_end = existing.start.0 + existing.size as u64;
794            if a_start < b_end && b_start < a_end {
795                return Err(KernelError::AlreadyExists {
796                    resource: "address range",
797                    id: aligned_start.0,
798                });
799            }
800        }
801
802        // Allocate physical frames for the mapping
803        let num_pages = aligned_size / 4096;
804        let mut physical_frames = Vec::with_capacity(num_pages);
805
806        // Allocate all frames first (hold FRAME_ALLOCATOR lock briefly).
807        // On partial failure, free any already-allocated frames before
808        // returning the error. Without this cleanup, OOM during a large
809        // mmap would permanently leak every frame allocated before the
810        // failing one.
811        {
812            let frame_allocator = FRAME_ALLOCATOR.lock();
813            for _ in 0..num_pages {
814                match frame_allocator.allocate_frames(1, None) {
815                    Ok(frame) => physical_frames.push(frame),
816                    Err(_) => {
817                        // Free all frames allocated so far
818                        for &f in &physical_frames {
819                            frame_allocator.free_frames(f, 1).ok();
820                        }
821                        return Err(KernelError::OutOfMemory {
822                            requested: 4096,
823                            available: 0,
824                        });
825                    }
826                }
827            }
828        } // Drop frame allocator lock before page table operations
829
830        // Zero all allocated frames through the kernel physical memory window.
831        // POSIX requires brk/mmap(MAP_ANONYMOUS) pages to be zero-filled.
832        // SAFETY: Each frame is a valid physical address returned by the frame
833        // allocator. phys_to_virt_addr maps it into the kernel's identity-mapped
834        // physical memory window, which is always accessible in kernel context.
835        for &frame in &physical_frames {
836            let phys_addr = frame.as_u64() << 12;
837            let virt = crate::mm::phys_to_virt_addr(phys_addr) as *mut u8;
838            unsafe {
839                core::ptr::write_bytes(virt, 0, 4096);
840            }
841        }
842
843        // Wire mappings into the architecture page table
844        let pt_root = self.page_table_root.load(Ordering::Acquire);
845        if pt_root != 0 {
846            // SAFETY: `pt_root` is a non-zero physical address of an L4 page
847            // table that was set during VAS::init() or inherited from a valid
848            // parent address space. The address is identity-mapped in the
849            // kernel's physical memory window. We hold the mappings lock,
850            // ensuring exclusive page table modification for this VAS.
851            let mut mapper = unsafe { create_mapper_from_root(pt_root) };
852            let mut alloc = VasFrameAllocator;
853
854            for (i, &frame) in physical_frames.iter().enumerate() {
855                let vaddr = VirtualAddress(aligned_start.0 + (i as u64) * 4096);
856                // Intermediate page tables may need frame allocation, which
857                // VasFrameAllocator provides by locking FRAME_ALLOCATOR
858                // internally. This is safe because we already dropped our
859                // earlier lock on FRAME_ALLOCATOR above.
860                mapper.map_page(vaddr, frame, mapping.flags, &mut alloc)?;
861            }
862
863            // Flush TLB for the entire mapped range using batched flushes.
864            // TlbFlushBatch accumulates up to 16 addresses and issues a full
865            // TLB flush if more are needed, reducing individual invlpg overhead.
866            let mut tlb_batch = TlbFlushBatch::new();
867            for i in 0..num_pages {
868                let vaddr = aligned_start.0 + (i as u64) * 4096;
869                tlb_batch.add(vaddr);
870            }
871            tlb_batch.flush();
872        }
873
874        // Record the mapping in our tracking structure
875        let mut mapping = mapping;
876        mapping.physical_frames = physical_frames;
877
878        mappings.insert(aligned_start, mapping);
879        Ok(())
880    }
881
882    /// Map specific physical frames into user space at a chosen virtual
883    /// address.
884    ///
885    /// Used for framebuffer mmap: the physical frames already exist (MMIO) and
886    /// must be mapped read/write into the process address space.
887    #[cfg(feature = "alloc")]
888    pub fn map_physical_region(
889        &self,
890        phys_addr: u64,
891        size: usize,
892        vaddr: VirtualAddress,
893    ) -> Result<(), KernelError> {
894        let aligned_size = ((size + 4095) / 4096) * 4096;
895        let num_pages = aligned_size / 4096;
896        let aligned_phys = phys_addr & !(4096 - 1);
897
898        let flags = PageFlags::PRESENT | PageFlags::WRITABLE | PageFlags::USER;
899
900        let pt_root = self.page_table_root.load(Ordering::Acquire);
901        if pt_root == 0 {
902            return Err(KernelError::InvalidState {
903                expected: "initialized page table",
904                actual: "null page table root",
905            });
906        }
907
908        // SAFETY: pt_root is a valid L4 page table.
909        let mut mapper = unsafe { create_mapper_from_root(pt_root) };
910        let mut alloc = VasFrameAllocator;
911
912        // Build frame list from the physical region
913        let mut physical_frames = Vec::with_capacity(num_pages);
914        for i in 0..num_pages {
915            let frame = FrameNumber::new((aligned_phys >> 12) + i as u64);
916            physical_frames.push(frame);
917            let page_vaddr = VirtualAddress(vaddr.0 + (i as u64) * 4096);
918            mapper.map_page(page_vaddr, frame, flags, &mut alloc)?;
919        }
920
921        // Flush TLB
922        let mut tlb_batch = TlbFlushBatch::new();
923        for i in 0..num_pages {
924            tlb_batch.add(vaddr.0 + (i as u64) * 4096);
925        }
926        tlb_batch.flush();
927
928        // Record mapping (no frame ownership -- these are MMIO, not allocator frames)
929        let mapping = VirtualMapping {
930            start: vaddr,
931            size: aligned_size,
932            mapping_type: MappingType::Device,
933            flags,
934            physical_frames,
935        };
936        self.mappings.lock().insert(vaddr, mapping);
937
938        Ok(())
939    }
940
941    /// Map a region of virtual memory with RAII guard
942    #[cfg(feature = "alloc")]
943    pub fn map_region_raii(
944        &self,
945        start: VirtualAddress,
946        size: usize,
947        mapping_type: MappingType,
948        process_id: crate::process::ProcessId,
949    ) -> Result<crate::raii::MappedRegion, KernelError> {
950        // First map the region normally
951        self.map_region(start, size, mapping_type)?;
952
953        // Create RAII guard for automatic unmapping
954        let aligned_start = VirtualAddress(start.0 & !(4096 - 1));
955        let aligned_size = ((size + 4095) / 4096) * 4096;
956
957        Ok(crate::raii::MappedRegion::new(
958            aligned_start.as_usize(),
959            aligned_size,
960            process_id,
961        ))
962    }
963
964    /// Unmap a region
965    #[cfg(feature = "alloc")]
966    pub fn unmap_region(&self, start: VirtualAddress) -> Result<(), KernelError> {
967        let mut mappings = self.mappings.lock();
968        let mapping = mappings.remove(&start).ok_or(KernelError::NotFound {
969            resource: "memory region",
970            id: start.0,
971        })?;
972
973        let num_pages = mapping.size / 4096;
974
975        // Unmap each page from the architecture page table
976        let pt_root = self.page_table_root.load(Ordering::Acquire);
977        if pt_root != 0 {
978            // SAFETY: `pt_root` is a non-zero physical address of an L4 page
979            // table set during VAS::init(). The address is identity-mapped in
980            // the kernel's physical memory window. We hold the mappings lock,
981            // ensuring exclusive page table modification for this VAS.
982            let mut mapper = unsafe { create_mapper_from_root(pt_root) };
983
984            for i in 0..num_pages {
985                let vaddr = VirtualAddress(mapping.start.0 + (i as u64) * 4096);
986                // Ignore errors from unmap_page -- the page may not have been
987                // installed in the hardware table (e.g., if map_region was
988                // called before the page table root was set).
989                let _ = mapper.unmap_page(vaddr);
990            }
991        }
992
993        // Flush TLB for the unmapped range using batched flushes
994        let mut tlb_batch = TlbFlushBatch::new();
995        for i in 0..num_pages {
996            let vaddr = mapping.start.0 + (i as u64) * 4096;
997            tlb_batch.add(vaddr);
998        }
999        tlb_batch.flush();
1000
1001        // Free the physical frames
1002        let frame_allocator = FRAME_ALLOCATOR.lock();
1003        for frame in mapping.physical_frames {
1004            let _ = frame_allocator.free_frames(frame, 1);
1005        }
1006
1007        Ok(())
1008    }
1009
1010    /// Unmap a region by address and size (POSIX-compliant partial munmap).
1011    ///
1012    /// Supports three cases:
1013    /// 1. **Exact match**: `addr` and `size` match a BTreeMap entry → remove
1014    ///    it.
1015    /// 2. **Front trim**: `addr` matches the start of a larger mapping → shrink
1016    ///    the mapping and free the leading pages.
1017    /// 3. **Back trim**: `addr+size` matches the end of a mapping → shrink from
1018    ///    the back.
1019    /// 4. **Hole punch**: Range is in the middle of a mapping → split into two.
1020    /// 5. **Sub-range not at start**: `addr` is inside a mapping → find the
1021    ///    containing mapping and trim/punch accordingly.
1022    ///
1023    /// GCC's ggc garbage collector relies on partial munmap to free individual
1024    /// pages within larger mmap pools. Without this, munmap(pool_start, 4KB)
1025    /// would destroy the entire multi-MB pool.
1026    #[cfg(feature = "alloc")]
1027    pub fn unmap(&self, start_addr: usize, size: usize) -> Result<(), KernelError> {
1028        let unmap_start = (start_addr & !(4096 - 1)) as u64;
1029        let unmap_size = ((size + 4095) / 4096) * 4096;
1030        let unmap_end = unmap_start + unmap_size as u64;
1031
1032        // First try exact-key match (fast path, most common for our small mmaps)
1033        let addr = VirtualAddress(unmap_start);
1034        let mut mappings = self.mappings.lock();
1035
1036        if let Some(existing) = mappings.get(&addr) {
1037            if existing.size == unmap_size {
1038                // Exact match: remove entire mapping
1039                drop(mappings);
1040                return self.unmap_region(addr);
1041            }
1042        }
1043
1044        // Find the mapping that CONTAINS the requested unmap range.
1045        // This handles partial munmap within a larger mmap.
1046        let mut containing_key = None;
1047        for (key, mapping) in mappings.iter() {
1048            let m_start = key.0;
1049            let m_end = m_start + mapping.size as u64;
1050            if m_start <= unmap_start && m_end >= unmap_end {
1051                containing_key = Some(*key);
1052                break;
1053            }
1054        }
1055
1056        let containing_key = match containing_key {
1057            Some(k) => k,
1058            None => {
1059                // No containing mapping found. If the exact key exists but with
1060                // a different size, fall back to removing the entire mapping
1061                // (original behavior, for backwards compat with code that passes
1062                // size=0 or incorrect size).
1063                if mappings.contains_key(&addr) {
1064                    drop(mappings);
1065                    return self.unmap_region(addr);
1066                }
1067                return Err(KernelError::NotFound {
1068                    resource: "memory region",
1069                    id: unmap_start,
1070                });
1071            }
1072        };
1073
1074        // Remove the containing mapping from BTreeMap
1075        let mapping = mappings
1076            .remove(&containing_key)
1077            .ok_or(KernelError::NotFound {
1078                resource: "vas_mapping",
1079                id: containing_key.0 as u64,
1080            })?;
1081        let m_start = containing_key.0;
1082
1083        // Calculate page indices within the mapping for the unmap range
1084        let unmap_page_start = ((unmap_start - m_start) / 4096) as usize;
1085        let unmap_page_count = unmap_size / 4096;
1086        let unmap_page_end = unmap_page_start + unmap_page_count;
1087
1088        // Unmap the requested pages from the page table
1089        let pt_root = self.page_table_root.load(Ordering::Acquire);
1090        if pt_root != 0 {
1091            // SAFETY: pt_root is a valid L4 page table address set during VAS::init().
1092            let mut mapper = unsafe { create_mapper_from_root(pt_root) };
1093            for i in unmap_page_start..unmap_page_end {
1094                let vaddr = VirtualAddress(m_start + (i as u64) * 4096);
1095                let _ = mapper.unmap_page(vaddr);
1096            }
1097        }
1098
1099        // Flush TLB for unmapped pages using batched flushes
1100        let mut tlb_batch = TlbFlushBatch::new();
1101        for i in unmap_page_start..unmap_page_end {
1102            let vaddr = m_start + (i as u64) * 4096;
1103            tlb_batch.add(vaddr);
1104        }
1105        tlb_batch.flush();
1106
1107        // Free the physical frames for the unmapped range
1108        {
1109            let frame_allocator = FRAME_ALLOCATOR.lock();
1110            for i in unmap_page_start..unmap_page_end.min(mapping.physical_frames.len()) {
1111                let _ = frame_allocator.free_frames(mapping.physical_frames[i], 1);
1112            }
1113        }
1114
1115        // Re-insert the remaining parts of the mapping
1116
1117        // Front portion: pages [0..unmap_page_start)
1118        if unmap_page_start > 0 {
1119            let front_size = unmap_page_start * 4096;
1120            let mut front = VirtualMapping::new(containing_key, front_size, mapping.mapping_type);
1121            front.flags = mapping.flags;
1122            if unmap_page_start <= mapping.physical_frames.len() {
1123                front.physical_frames = mapping.physical_frames[..unmap_page_start].to_vec();
1124            }
1125            mappings.insert(containing_key, front);
1126        }
1127
1128        // Back portion: pages [unmap_page_end..total_pages)
1129        let total_pages = mapping.size / 4096;
1130        if unmap_page_end < total_pages {
1131            let back_start_addr = m_start + (unmap_page_end as u64) * 4096;
1132            let back_size = (total_pages - unmap_page_end) * 4096;
1133            let mut back = VirtualMapping::new(
1134                VirtualAddress(back_start_addr),
1135                back_size,
1136                mapping.mapping_type,
1137            );
1138            back.flags = mapping.flags;
1139            if unmap_page_end < mapping.physical_frames.len() {
1140                back.physical_frames = mapping.physical_frames[unmap_page_end..].to_vec();
1141            }
1142            mappings.insert(VirtualAddress(back_start_addr), back);
1143        }
1144
1145        Ok(())
1146    }
1147
1148    /// Find mapping for address
1149    #[cfg(feature = "alloc")]
1150    pub fn find_mapping(&self, addr: VirtualAddress) -> Option<VirtualMapping> {
1151        let mappings = self.mappings.lock();
1152        for (_, mapping) in mappings.iter() {
1153            if mapping.contains(addr) {
1154                return Some(mapping.clone());
1155            }
1156        }
1157        None
1158    }
1159
1160    /// Get a reference to the underlying mappings BTreeMap.
1161    ///
1162    /// Used by COW fork to iterate user-space pages and by diagnostics.
1163    /// The caller must lock the returned Mutex before accessing entries.
1164    pub fn mappings_ref(
1165        &self,
1166    ) -> &spin::Mutex<alloc::collections::BTreeMap<VirtualAddress, VirtualMapping>> {
1167        &self.mappings
1168    }
1169
1170    /// Map a specific virtual address using a pre-allocated physical frame.
1171    ///
1172    /// Unlike `map_page` (which allocates its own frame), this takes an
1173    /// existing frame -- used by demand paging and COW fault handlers.
1174    #[cfg(feature = "alloc")]
1175    pub fn map_page_with_frame(
1176        &mut self,
1177        vaddr: usize,
1178        frame: super::FrameNumber,
1179        flags: PageFlags,
1180    ) -> Result<(), KernelError> {
1181        let vaddr_obj = VirtualAddress(vaddr as u64);
1182        let pt_root = self.page_table_root.load(Ordering::Acquire);
1183        if pt_root != 0 {
1184            // SAFETY: pt_root is a valid L4 page table address set during
1185            // VAS::init(). We have &mut self for exclusive access.
1186            let mut mapper = unsafe { create_mapper_from_root(pt_root) };
1187            let mut alloc = VasFrameAllocator;
1188            mapper.map_page(vaddr_obj, frame, flags, &mut alloc)?;
1189            crate::arch::tlb_flush_address(vaddr as u64);
1190        }
1191        Ok(())
1192    }
1193
1194    /// Re-map a virtual address to a different physical frame (for COW).
1195    ///
1196    /// Unmaps the old mapping and installs the new frame with the given flags.
1197    #[cfg(feature = "alloc")]
1198    pub fn remap_page(
1199        &mut self,
1200        vaddr: usize,
1201        new_frame: super::FrameNumber,
1202        flags: PageFlags,
1203    ) -> Result<(), KernelError> {
1204        let vaddr_obj = VirtualAddress(vaddr as u64);
1205        let pt_root = self.page_table_root.load(Ordering::Acquire);
1206        if pt_root != 0 {
1207            // SAFETY: Same as map_page_with_frame.
1208            let mut mapper = unsafe { create_mapper_from_root(pt_root) };
1209            let mut alloc = VasFrameAllocator;
1210            // Unmap old entry (ignore error if not currently mapped)
1211            let _ = mapper.unmap_page(vaddr_obj);
1212            mapper.map_page(vaddr_obj, new_frame, flags, &mut alloc)?;
1213            crate::arch::tlb_flush_address(vaddr as u64);
1214        }
1215        Ok(())
1216    }
1217
1218    /// Register a lazy (demand-paged) mapping without allocating frames.
1219    ///
1220    /// Delegates to the demand paging manager. The first access will trigger
1221    /// a page fault that the manager resolves by allocating a physical frame.
1222    #[cfg(feature = "alloc")]
1223    pub fn map_lazy(&mut self, vaddr: usize, size: usize, flags: PageFlags) {
1224        crate::mm::demand_paging::register_lazy(
1225            vaddr,
1226            size,
1227            flags,
1228            crate::mm::demand_paging::BackingType::Anonymous,
1229        );
1230    }
1231
1232    /// Allocate memory-mapped region
1233    pub fn mmap(
1234        &self,
1235        size: usize,
1236        mapping_type: MappingType,
1237    ) -> Result<VirtualAddress, KernelError> {
1238        let aligned_size = ((size + 4095) / 4096) * 4096;
1239        let addr = VirtualAddress(
1240            self.next_mmap_addr
1241                .fetch_add(aligned_size as u64, Ordering::Relaxed),
1242        );
1243
1244        // Skip physical page mapping in host tests (no frame allocator available)
1245        #[cfg(all(feature = "alloc", not(test)))]
1246        self.map_region(addr, aligned_size, mapping_type)?;
1247
1248        Ok(addr)
1249    }
1250
1251    /// Return the base address of the user heap region.
1252    pub fn heap_start_addr(&self) -> u64 {
1253        self.heap_start.load(Ordering::Relaxed)
1254    }
1255
1256    /// Extend or query heap (brk).
1257    ///
1258    /// When `new_break` is `Some`, attempts to move the program break:
1259    /// - **Grow** (new > current): allocates physical frames and maps pages for
1260    ///   the delta region.
1261    /// - **Shrink** (new < current but >= heap_start): unmaps pages and frees
1262    ///   frames for the delta region.
1263    /// - **Below heap_start** or **equal to current**: no-op.
1264    ///
1265    /// When `new_break` is `None`, returns the current break without changes.
1266    ///
1267    /// All heap pages are tracked in a SINGLE consolidated BTreeMap entry
1268    /// keyed at the heap start page. This avoids creating one entry per brk()
1269    /// call, which previously caused 50,000+ entries and O(n^2) slowdown.
1270    pub fn brk(&self, new_break: Option<VirtualAddress>) -> VirtualAddress {
1271        if let Some(addr) = new_break {
1272            let current = self.heap_break.load(Ordering::Acquire);
1273            let heap_start = self.heap_start.load(Ordering::Relaxed);
1274
1275            if addr.0 < heap_start {
1276                // Below heap start: ignore
1277            } else if addr.0 > current {
1278                // Grow: allocate pages for [current, addr) range
1279                let old_page = (current + 4095) / 4096; // First page NOT yet allocated
1280                let new_page = (addr.0 + 4095) / 4096;
1281
1282                if new_page > old_page {
1283                    // In bare-metal alloc builds, map the physical pages.
1284                    // In host test builds, skip physical mapping (no frame allocator).
1285                    #[cfg(all(feature = "alloc", not(test)))]
1286                    {
1287                        if self.brk_extend_heap(old_page, new_page).is_ok() {
1288                            self.heap_break.store(addr.0, Ordering::Release);
1289                        }
1290                        // On failure, leave break unchanged
1291                    }
1292                    #[cfg(any(not(feature = "alloc"), test))]
1293                    {
1294                        // Without alloc or in tests: just move the pointer
1295                        self.heap_break.store(addr.0, Ordering::Release);
1296                    }
1297                } else {
1298                    // Within the same page, just update the pointer
1299                    self.heap_break.store(addr.0, Ordering::Release);
1300                }
1301            } else if addr.0 < current && addr.0 >= heap_start {
1302                // Shrink attempt: brk only grows, so ignore requests to
1303                // decrease the break. Return current break
1304                // unchanged.
1305            }
1306        }
1307
1308        VirtualAddress(self.heap_break.load(Ordering::Acquire))
1309    }
1310
1311    /// Extend the heap by mapping pages [old_page..new_page).
1312    ///
1313    /// Instead of calling `map_region()` (which creates a new BTreeMap entry
1314    /// each time), this method maintains a SINGLE consolidated heap mapping.
1315    /// The first call creates the entry; subsequent calls extend it in-place.
1316    /// This reduces the mapping count from O(brk_calls) to O(1) and avoids
1317    /// the O(n) overlap check in `map_region()`.
1318    #[cfg(all(feature = "alloc", not(test)))]
1319    fn brk_extend_heap(&self, old_page: u64, new_page: u64) -> Result<(), KernelError> {
1320        let delta_pages = (new_page - old_page) as usize;
1321        let start_addr = VirtualAddress(old_page * 4096);
1322
1323        // Allocate physical frames
1324        let mut new_frames = Vec::with_capacity(delta_pages);
1325        {
1326            let frame_allocator = FRAME_ALLOCATOR.lock();
1327            for _ in 0..delta_pages {
1328                match frame_allocator.allocate_frames(1, None) {
1329                    Ok(frame) => new_frames.push(frame),
1330                    Err(_) => {
1331                        for &f in &new_frames {
1332                            frame_allocator.free_frames(f, 1).ok();
1333                        }
1334                        return Err(KernelError::OutOfMemory {
1335                            requested: 4096,
1336                            available: 0,
1337                        });
1338                    }
1339                }
1340            }
1341        }
1342
1343        // Zero the frames (POSIX requires zero-filled pages)
1344        for &frame in &new_frames {
1345            let phys_addr = frame.as_u64() << 12;
1346            let virt = crate::mm::phys_to_virt_addr(phys_addr) as *mut u8;
1347            // SAFETY: Frame is freshly allocated; phys_to_virt_addr maps it into the
1348            // kernel's identity-mapped region.
1349            unsafe {
1350                core::ptr::write_bytes(virt, 0, 4096);
1351            }
1352        }
1353
1354        // Map into page tables
1355        let pt_root = self.page_table_root.load(Ordering::Acquire);
1356        if pt_root != 0 {
1357            // SAFETY: pt_root is a valid L4 page table address set during VAS::init().
1358            let mut mapper = unsafe { create_mapper_from_root(pt_root) };
1359            let mut alloc = VasFrameAllocator;
1360            let flags = PageFlags::PRESENT | PageFlags::WRITABLE | PageFlags::USER;
1361
1362            for (i, &frame) in new_frames.iter().enumerate() {
1363                let vaddr = VirtualAddress(start_addr.0 + (i as u64) * 4096);
1364                mapper.map_page(vaddr, frame, flags, &mut alloc)?;
1365                crate::arch::tlb_flush_address(vaddr.0);
1366            }
1367        }
1368
1369        // Extend existing heap mapping or create initial one
1370        let heap_start_page = (self.heap_start.load(Ordering::Relaxed) + 4095) / 4096;
1371        let heap_key = VirtualAddress(heap_start_page * 4096);
1372
1373        let mut mappings = self.mappings.lock();
1374        if let Some(mapping) = mappings.get_mut(&heap_key) {
1375            // Extend existing consolidated heap mapping
1376            mapping.size += delta_pages * 4096;
1377            mapping.physical_frames.extend_from_slice(&new_frames);
1378        } else {
1379            // First heap allocation: create consolidated mapping
1380            let total_size = ((new_page - heap_start_page) as usize) * 4096;
1381            let mut mapping = VirtualMapping::new(heap_key, total_size, MappingType::Heap);
1382            mapping.physical_frames = new_frames;
1383            mapping.flags = PageFlags::PRESENT | PageFlags::WRITABLE | PageFlags::USER;
1384            mappings.insert(heap_key, mapping);
1385        }
1386
1387        Ok(())
1388    }
1389
1390    /// Clone address space (for fork).
1391    ///
1392    /// Creates a new VAS with its own L4 page table and deep-copies all
1393    /// user-space pages from this VAS. Kernel-space entries are shared.
1394    #[cfg(feature = "alloc")]
1395    pub fn fork(&self) -> Result<Self, KernelError> {
1396        let mut new_vas = Self::new();
1397        new_vas.clone_from(self)?;
1398        Ok(new_vas)
1399    }
1400
1401    /// Update hardware page table entry flags for a region.
1402    ///
1403    /// Walks the page table for each page in `[start, start+size)` and updates
1404    /// the PTE flags according to the POSIX `prot` bitmask. Flushes TLB for
1405    /// each modified page.
1406    #[cfg(feature = "alloc")]
1407    pub fn protect_region(
1408        &self,
1409        start: VirtualAddress,
1410        size: usize,
1411        prot: usize,
1412    ) -> Result<(), KernelError> {
1413        use super::PageFlags;
1414
1415        let pt_root = self.page_table_root.load(Ordering::Acquire);
1416        if pt_root == 0 {
1417            return Ok(()); // No page tables, nothing to update
1418        }
1419
1420        // Convert POSIX prot flags to hardware PageFlags
1421        let mut new_flags = PageFlags::PRESENT | PageFlags::USER;
1422        if prot & 0x2 != 0 {
1423            // PROT_WRITE
1424            new_flags |= PageFlags::WRITABLE;
1425        }
1426        if prot & 0x4 == 0 {
1427            // !PROT_EXEC -> NO_EXECUTE
1428            new_flags |= PageFlags::NO_EXECUTE;
1429        }
1430
1431        // SAFETY: pt_root is a valid identity-mapped L4 page table. We hold the
1432        // mappings lock implicitly via the caller's &self borrow.
1433        let mut mapper = unsafe { create_mapper_from_root(pt_root) };
1434
1435        let num_pages = (size + 4095) / 4096;
1436        for i in 0..num_pages {
1437            let vaddr = VirtualAddress(start.0 + (i as u64) * 4096);
1438            // Ignore errors for pages that aren't mapped in the hardware tables
1439            let _ = mapper.update_page_flags(vaddr, new_flags);
1440            crate::arch::tlb_flush_address(vaddr.0);
1441        }
1442
1443        // Update the mapping metadata flags too
1444        let mut mappings = self.mappings.lock();
1445        if let Some(mapping) = mappings.get_mut(&start) {
1446            mapping.flags = new_flags;
1447        }
1448
1449        Ok(())
1450    }
1451
1452    /// Handle page fault
1453    pub fn handle_page_fault(
1454        &self,
1455        fault_addr: VirtualAddress,
1456        write: bool,
1457        user: bool,
1458    ) -> Result<(), KernelError> {
1459        #[cfg(feature = "alloc")]
1460        {
1461            // Find the mapping for this address
1462            let mapping = self
1463                .find_mapping(fault_addr)
1464                .ok_or(KernelError::UnmappedMemory {
1465                    addr: fault_addr.0 as usize,
1466                })?;
1467
1468            // Check permissions
1469            if write && !mapping.flags.contains(PageFlags::WRITABLE) {
1470                return Err(KernelError::PermissionDenied {
1471                    operation: "write to read-only page",
1472                });
1473            }
1474
1475            if user && !mapping.flags.contains(PageFlags::USER) {
1476                return Err(KernelError::PermissionDenied {
1477                    operation: "user access to kernel page",
1478                });
1479            }
1480
1481            // Check if this is a valid fault (e.g., COW, demand paging)
1482            // For now, we'll just return an error as we don't support these features yet
1483            Err(KernelError::NotImplemented {
1484                feature: "page fault handling (COW/demand paging)",
1485            })
1486        }
1487
1488        #[cfg(not(feature = "alloc"))]
1489        Err(KernelError::NotImplemented {
1490            feature: "page fault handling (requires alloc)",
1491        })
1492    }
1493
1494    /// Get memory statistics
1495    #[cfg(feature = "alloc")]
1496    pub fn get_stats(&self) -> VasStats {
1497        let mappings = self.mappings.lock();
1498        let mut total_size = 0;
1499        let mut code_size = 0;
1500        let mut data_size = 0;
1501        let mut stack_size = 0;
1502        let mut heap_size = 0;
1503
1504        for (_, mapping) in mappings.iter() {
1505            total_size += mapping.size;
1506            match mapping.mapping_type {
1507                MappingType::Code => code_size += mapping.size,
1508                MappingType::Data => data_size += mapping.size,
1509                MappingType::Stack => stack_size += mapping.size,
1510                MappingType::Heap => heap_size += mapping.size,
1511                _ => {}
1512            }
1513        }
1514
1515        VasStats {
1516            total_size,
1517            code_size,
1518            data_size,
1519            stack_size,
1520            heap_size,
1521            mapping_count: mappings.len(),
1522        }
1523    }
1524
1525    /// Clear all mappings and free resources
1526    pub fn clear(&mut self) {
1527        #[cfg(feature = "alloc")]
1528        {
1529            let pt_root = self.page_table_root.load(Ordering::Acquire);
1530
1531            // Get all mappings to free their frames
1532            let mappings = self.mappings.get_mut();
1533
1534            // Unmap from architecture page tables if we have a valid root
1535            if pt_root != 0 {
1536                // SAFETY: `pt_root` is a non-zero physical address of an L4
1537                // page table set during VAS::init(). The address is identity-
1538                // mapped in the kernel's physical memory window. We have
1539                // `&mut self`, ensuring exclusive access.
1540                let mut mapper = unsafe { create_mapper_from_root(pt_root) };
1541
1542                for (_, mapping) in mappings.iter() {
1543                    let num_pages = mapping.size / 4096;
1544                    for i in 0..num_pages {
1545                        let vaddr = VirtualAddress(mapping.start.0 + (i as u64) * 4096);
1546                        let _ = mapper.unmap_page(vaddr);
1547                    }
1548                }
1549            }
1550
1551            // Free physical frames for each mapping
1552            for (_, mapping) in mappings.iter() {
1553                let frame_allocator = FRAME_ALLOCATOR.lock();
1554                for frame in &mapping.physical_frames {
1555                    frame_allocator.free_frames(*frame, 1).ok();
1556                }
1557            }
1558
1559            // Clear all mappings
1560            mappings.clear();
1561
1562            // Flush TLB for the unmapped user-space pages. This MUST happen
1563            // before freeing page table subtrees below, so that no stale TLB
1564            // entry references the about-to-be-freed L3/L2/L1 frames.
1565            crate::arch::tlb_flush_all();
1566
1567            // Free user-space page table subtree frames (L3/L2/L1) now that
1568            // all user PTEs have been cleared and the TLB flushed. The L4
1569            // frame itself is NOT freed because it may be the active CR3;
1570            // freeing it would cause a triple fault on the next TLB miss.
1571            // The L4 frame is freed later by the boot wrapper (e.g.,
1572            // run_user_process_scheduled) after the boot CR3 is restored.
1573            //
1574            // Freeing subtrees here (rather than deferring to the boot
1575            // wrapper) is critical for the exec path: exec calls clear()
1576            // then init(), which allocates a NEW L4 and overwrites
1577            // page_table_root. Without freeing the old subtrees here, they
1578            // would be leaked because the old L4 address is overwritten and
1579            // the boot wrapper only frees the pre-exec L4 (saved before
1580            // entering user mode).
1581            if pt_root != 0 {
1582                free_user_page_table_subtrees(pt_root);
1583            }
1584        }
1585
1586        // Reset metadata
1587        self.heap_break
1588            .store(self.heap_start.load(Ordering::Relaxed), Ordering::Release);
1589        self.next_mmap_addr
1590            .store(0x4000_0000_0000, Ordering::Release);
1591    }
1592
1593    /// Clear user-space mappings only (for exec)
1594    pub fn clear_user_space(&mut self) -> Result<(), KernelError> {
1595        #[cfg(feature = "alloc")]
1596        {
1597            let pt_root = self.page_table_root.load(Ordering::Acquire);
1598            let mappings = self.mappings.get_mut();
1599            let mut to_remove = Vec::new();
1600
1601            // Find all user-space mappings (below kernel space)
1602            const KERNEL_SPACE_START: u64 = 0xFFFF_8000_0000_0000;
1603
1604            for (addr, _mapping) in mappings.iter() {
1605                if addr.0 < KERNEL_SPACE_START {
1606                    to_remove.push(*addr);
1607                }
1608            }
1609
1610            // Unmap user-space pages from architecture page tables
1611            if pt_root != 0 {
1612                // SAFETY: `pt_root` is a non-zero physical address of an L4
1613                // page table set during VAS::init(). The address is identity-
1614                // mapped in the kernel's physical memory window. We have
1615                // `&mut self`, ensuring exclusive access.
1616                let mut mapper = unsafe { create_mapper_from_root(pt_root) };
1617
1618                for addr in &to_remove {
1619                    if let Some(mapping) = mappings.get(addr) {
1620                        let num_pages = mapping.size / 4096;
1621                        for i in 0..num_pages {
1622                            let vaddr = VirtualAddress(mapping.start.0 + (i as u64) * 4096);
1623                            let _ = mapper.unmap_page(vaddr);
1624                        }
1625                    }
1626                }
1627            }
1628
1629            // Free physical frames and remove mappings
1630            for addr in &to_remove {
1631                if let Some(mapping) = mappings.get(addr) {
1632                    let frame_allocator = FRAME_ALLOCATOR.lock();
1633                    for frame in &mapping.physical_frames {
1634                        frame_allocator.free_frames(*frame, 1).ok();
1635                    }
1636                }
1637            }
1638
1639            for addr in to_remove {
1640                mappings.remove(&addr);
1641            }
1642
1643            // NOTE: Page table subtree frames (L3/L2/L1) are NOT freed here
1644            // because clear_user_space() runs during exec while the process's
1645            // CR3 is still active. Freeing intermediate table frames would
1646            // corrupt the active page table hierarchy. The old page table
1647            // frames are reused by subsequent map_region calls since their L1
1648            // entries were already unmapped above (all slots are non-present).
1649
1650            // Flush TLB for user-space changes
1651            crate::arch::tlb_flush_all();
1652        }
1653
1654        // Reset user-space metadata
1655        self.heap_break
1656            .store(self.heap_start.load(Ordering::Relaxed), Ordering::Release);
1657        self.next_mmap_addr
1658            .store(0x4000_0000_0000, Ordering::Release);
1659
1660        Ok(())
1661    }
1662
1663    /// Get user stack base address
1664    pub fn user_stack_base(&self) -> usize {
1665        // User stack starts below stack_top and grows downward
1666        let size = self.stack_size.load(Ordering::Acquire);
1667        (self.stack_top.load(Ordering::Acquire) - size) as usize
1668    }
1669
1670    /// Get user stack size
1671    pub fn user_stack_size(&self) -> usize {
1672        self.stack_size.load(Ordering::Acquire) as usize
1673    }
1674
1675    /// Get stack top address
1676    pub fn stack_top(&self) -> usize {
1677        self.stack_top.load(Ordering::Acquire) as usize
1678    }
1679
1680    /// Set stack top address
1681    pub fn set_stack_top(&self, addr: usize) {
1682        self.stack_top.store(addr as u64, Ordering::Release);
1683    }
1684
1685    /// Set stack size in bytes
1686    pub fn set_stack_size(&self, size: usize) {
1687        self.stack_size.store(size as u64, Ordering::Release);
1688    }
1689
1690    /// Map a single page at a virtual address
1691    pub fn map_page(&mut self, vaddr: usize, flags: PageFlags) -> Result<(), KernelError> {
1692        use super::PAGE_SIZE;
1693
1694        // Allocate a physical frame via per-CPU cache (avoids global lock)
1695        let frame = crate::mm::frame_allocator::per_cpu_alloc_frame().map_err(|_| {
1696            KernelError::OutOfMemory {
1697                requested: 4096,
1698                available: 0,
1699            }
1700        })?;
1701
1702        // Zero the frame before mapping. POSIX requires freshly mapped pages
1703        // to be zero-filled, and the ELF loader relies on this for BSS.
1704        // SAFETY: frame is a valid physical address just allocated by the
1705        // frame allocator. phys_to_virt_addr maps it into the kernel's
1706        // identity-mapped physical memory window.
1707        let phys_addr = frame.as_u64() << 12;
1708        let virt = crate::mm::phys_to_virt_addr(phys_addr) as *mut u8;
1709        unsafe {
1710            core::ptr::write_bytes(virt, 0, 4096);
1711        }
1712
1713        let vaddr_obj = VirtualAddress(vaddr as u64);
1714
1715        // Install the mapping in the architecture page table
1716        let pt_root = self.page_table_root.load(Ordering::Acquire);
1717        if pt_root != 0 {
1718            // SAFETY: `pt_root` is a non-zero physical address of an L4 page
1719            // table set during VAS::init(). The address is identity-mapped in
1720            // the kernel's physical memory window. We have `&mut self`,
1721            // ensuring exclusive access to this VAS and its page tables.
1722            let mut mapper = unsafe { create_mapper_from_root(pt_root) };
1723            let mut alloc = VasFrameAllocator;
1724            match mapper.map_page(vaddr_obj, frame, flags, &mut alloc) {
1725                Ok(()) => {}
1726                Err(KernelError::AlreadyExists { .. }) => {
1727                    // Page already mapped by a previous segment (e.g.,
1728                    // overlapping LOAD segments sharing a boundary page).
1729                    // Update flags to the union of old and new, then free
1730                    // the unused frame we just allocated.
1731                    let _ = mapper.update_page_flags(vaddr_obj, flags);
1732                    let _ = FRAME_ALLOCATOR.lock().free_frames(frame, 1);
1733                    crate::arch::tlb_flush_address(vaddr as u64);
1734                    return Ok(());
1735                }
1736                Err(e) => return Err(e),
1737            }
1738            crate::arch::tlb_flush_address(vaddr as u64);
1739        }
1740
1741        // Record the mapping
1742        #[cfg(feature = "alloc")]
1743        {
1744            let mut mappings = self.mappings.lock();
1745
1746            if let Some(mapping) = mappings.get_mut(&vaddr_obj) {
1747                mapping.physical_frames.push(frame);
1748            } else {
1749                let mut new_mapping = VirtualMapping::new(vaddr_obj, PAGE_SIZE, MappingType::Data);
1750                new_mapping.physical_frames.push(frame);
1751                new_mapping.flags = flags;
1752                mappings.insert(vaddr_obj, new_mapping);
1753            }
1754        }
1755
1756        Ok(())
1757    }
1758
1759    /// Map a 2MB huge page at the given virtual address.
1760    ///
1761    /// Allocates 512 contiguous 4KB frames (= 2MB) and installs a single
1762    /// L2 page table entry with the HUGE flag set. This reduces TLB pressure
1763    /// for large contiguous allocations (heap, framebuffer, DMA).
1764    ///
1765    /// The virtual address must be 2MB-aligned.
1766    pub fn map_huge_page(&mut self, vaddr: usize, flags: PageFlags) -> Result<(), KernelError> {
1767        const HUGE_PAGE_SIZE: usize = 2 * 1024 * 1024; // 2MB
1768        const HUGE_PAGE_FRAMES: usize = HUGE_PAGE_SIZE / 4096; // 512
1769
1770        if vaddr & (HUGE_PAGE_SIZE - 1) != 0 {
1771            return Err(KernelError::InvalidArgument {
1772                name: "vaddr",
1773                value: "not 2MB aligned for huge page",
1774            });
1775        }
1776
1777        // Allocate 512 contiguous frames (2MB).
1778        let frame = FRAME_ALLOCATOR
1779            .lock()
1780            .allocate_frames(HUGE_PAGE_FRAMES, None)
1781            .map_err(|_| KernelError::OutOfMemory {
1782                requested: HUGE_PAGE_SIZE,
1783                available: 0,
1784            })?;
1785
1786        // Zero the huge page.
1787        let phys_addr = frame.as_u64() * 4096;
1788        let virt = crate::mm::phys_to_virt_addr(phys_addr) as *mut u8;
1789        // SAFETY: freshly allocated contiguous frames in kernel physical memory window.
1790        unsafe {
1791            core::ptr::write_bytes(virt, 0, HUGE_PAGE_SIZE);
1792        }
1793
1794        // Install the 2MB mapping with HUGE flag.
1795        let huge_flags = PageFlags(flags.0 | PageFlags::HUGE.0);
1796        let vaddr_obj = VirtualAddress(vaddr as u64);
1797
1798        let pt_root = self.page_table_root.load(Ordering::Acquire);
1799        if pt_root != 0 {
1800            // SAFETY: Same as map_page -- pt_root is a valid L4 page table.
1801            let mut mapper = unsafe { create_mapper_from_root(pt_root) };
1802            let mut alloc = VasFrameAllocator;
1803            mapper.map_page(vaddr_obj, frame, huge_flags, &mut alloc)?;
1804            crate::arch::tlb_flush_address(vaddr as u64);
1805        }
1806
1807        // Record the mapping.
1808        #[cfg(feature = "alloc")]
1809        {
1810            let mut mappings = self.mappings.lock();
1811            let mut new_mapping = VirtualMapping::new(vaddr_obj, HUGE_PAGE_SIZE, MappingType::Data);
1812            new_mapping.physical_frames.push(frame);
1813            new_mapping.flags = huge_flags;
1814            mappings.insert(vaddr_obj, new_mapping);
1815        }
1816
1817        Ok(())
1818    }
1819}
1820
1821/// Virtual address space statistics
1822#[derive(Debug, Default)]
1823pub struct VasStats {
1824    pub total_size: usize,
1825    pub code_size: usize,
1826    pub data_size: usize,
1827    pub stack_size: usize,
1828    pub heap_size: usize,
1829    pub mapping_count: usize,
1830}
1831
1832/// Map a physical memory region into the current process's user-space address
1833/// space.
1834///
1835/// Allocates a virtual address range via the process's VAS mmap region and
1836/// maps the given physical frames into it. Used for framebuffer mmap.
1837///
1838/// Returns the user-space virtual address of the mapping.
1839pub fn map_physical_region_user(
1840    phys_addr: u64,
1841    size: usize,
1842) -> Result<usize, crate::syscall::SyscallError> {
1843    let proc =
1844        crate::process::current_process().ok_or(crate::syscall::SyscallError::InvalidState)?;
1845
1846    let memory_space = proc.memory_space.lock();
1847
1848    // Allocate a virtual address range from the mmap region
1849    let aligned_size = ((size + 4095) / 4096) * 4096;
1850    let vaddr = VirtualAddress(
1851        memory_space
1852            .next_mmap_addr
1853            .fetch_add(aligned_size as u64, Ordering::Relaxed),
1854    );
1855
1856    // Map the physical frames
1857    #[cfg(feature = "alloc")]
1858    memory_space
1859        .map_physical_region(phys_addr, aligned_size, vaddr)
1860        .map_err(|_| crate::syscall::SyscallError::OutOfMemory)?;
1861
1862    Ok(vaddr.as_usize())
1863}
1864
1865#[cfg(test)]
1866mod tests {
1867    use super::*;
1868
1869    // --- MappingType tests ---
1870
1871    #[test]
1872    fn test_mapping_type_equality() {
1873        assert_eq!(MappingType::Code, MappingType::Code);
1874        assert_ne!(MappingType::Code, MappingType::Data);
1875        assert_ne!(MappingType::Stack, MappingType::Heap);
1876    }
1877
1878    // --- VirtualMapping tests ---
1879
1880    #[test]
1881    fn test_virtual_mapping_new_code() {
1882        let start = VirtualAddress(0x1000);
1883        let mapping = VirtualMapping::new(start, 0x4000, MappingType::Code);
1884
1885        assert_eq!(mapping.start, start);
1886        assert_eq!(mapping.size, 0x4000);
1887        assert_eq!(mapping.mapping_type, MappingType::Code);
1888        // Code should be PRESENT and USER, but not WRITABLE
1889        assert!(mapping.flags.contains(PageFlags::PRESENT));
1890        assert!(mapping.flags.contains(PageFlags::USER));
1891        assert!(!mapping.flags.contains(PageFlags::WRITABLE));
1892    }
1893
1894    #[test]
1895    fn test_virtual_mapping_new_data() {
1896        let mapping = VirtualMapping::new(VirtualAddress(0x2000), 0x1000, MappingType::Data);
1897
1898        assert!(mapping.flags.contains(PageFlags::PRESENT));
1899        assert!(mapping.flags.contains(PageFlags::WRITABLE));
1900        assert!(mapping.flags.contains(PageFlags::USER));
1901    }
1902
1903    #[test]
1904    fn test_virtual_mapping_new_stack() {
1905        let mapping = VirtualMapping::new(VirtualAddress(0x3000), 0x2000, MappingType::Stack);
1906
1907        assert!(mapping.flags.contains(PageFlags::PRESENT));
1908        assert!(mapping.flags.contains(PageFlags::WRITABLE));
1909        assert!(mapping.flags.contains(PageFlags::USER));
1910        assert!(mapping.flags.contains(PageFlags::NO_EXECUTE));
1911    }
1912
1913    #[test]
1914    fn test_virtual_mapping_new_heap() {
1915        let mapping = VirtualMapping::new(VirtualAddress(0x4000), 0x10000, MappingType::Heap);
1916
1917        assert!(mapping.flags.contains(PageFlags::PRESENT));
1918        assert!(mapping.flags.contains(PageFlags::WRITABLE));
1919        assert!(mapping.flags.contains(PageFlags::USER));
1920        assert!(mapping.flags.contains(PageFlags::NO_EXECUTE));
1921    }
1922
1923    #[test]
1924    fn test_virtual_mapping_new_device() {
1925        let mapping = VirtualMapping::new(VirtualAddress(0xF000), 0x1000, MappingType::Device);
1926
1927        assert!(mapping.flags.contains(PageFlags::PRESENT));
1928        assert!(mapping.flags.contains(PageFlags::WRITABLE));
1929        assert!(mapping.flags.contains(PageFlags::NO_CACHE));
1930        // Device memory should NOT have USER flag
1931        assert!(!mapping.flags.contains(PageFlags::USER));
1932    }
1933
1934    #[test]
1935    fn test_virtual_mapping_contains() {
1936        let mapping = VirtualMapping::new(VirtualAddress(0x1000), 0x3000, MappingType::Data);
1937
1938        // Start address - contained
1939        assert!(mapping.contains(VirtualAddress(0x1000)));
1940        // Middle address - contained
1941        assert!(mapping.contains(VirtualAddress(0x2000)));
1942        // Last byte before end - contained
1943        assert!(mapping.contains(VirtualAddress(0x3FFF)));
1944        // End address - NOT contained (exclusive)
1945        assert!(!mapping.contains(VirtualAddress(0x4000)));
1946        // Before start - NOT contained
1947        assert!(!mapping.contains(VirtualAddress(0x0FFF)));
1948        // Well past end - NOT contained
1949        assert!(!mapping.contains(VirtualAddress(0x5000)));
1950    }
1951
1952    #[test]
1953    fn test_virtual_mapping_end() {
1954        let mapping = VirtualMapping::new(VirtualAddress(0x1000), 0x3000, MappingType::Data);
1955        assert_eq!(mapping.end(), VirtualAddress(0x4000));
1956    }
1957
1958    #[test]
1959    fn test_virtual_mapping_zero_size() {
1960        let mapping = VirtualMapping::new(VirtualAddress(0x1000), 0, MappingType::File);
1961        assert_eq!(mapping.end(), VirtualAddress(0x1000));
1962        // A zero-sized mapping should not contain its start address
1963        assert!(!mapping.contains(VirtualAddress(0x1000)));
1964    }
1965
1966    // --- VirtualAddressSpace tests ---
1967
1968    #[test]
1969    fn test_vas_default_values() {
1970        let vas = VirtualAddressSpace::new();
1971
1972        // Check default page table root
1973        assert_eq!(vas.get_page_table(), 0);
1974
1975        // Check default heap settings
1976        let heap_break = vas.brk(None);
1977        assert_eq!(heap_break, VirtualAddress(0x2000_0000_0000));
1978
1979        // Check default stack settings
1980        assert_eq!(vas.stack_top(), 0x7FFF_FFFF_0000);
1981    }
1982
1983    #[test]
1984    fn test_vas_set_page_table() {
1985        let vas = VirtualAddressSpace::new();
1986        vas.set_page_table(0xDEAD_BEEF_0000);
1987        assert_eq!(vas.get_page_table(), 0xDEAD_BEEF_0000);
1988    }
1989
1990    #[test]
1991    fn test_vas_brk_extend_heap() {
1992        let vas = VirtualAddressSpace::new();
1993
1994        // Initial break
1995        let initial = vas.brk(None);
1996        assert_eq!(initial, VirtualAddress(0x2000_0000_0000));
1997
1998        // Extend the heap
1999        let new_addr = VirtualAddress(0x2000_0001_0000);
2000        let result = vas.brk(Some(new_addr));
2001        assert_eq!(result, new_addr);
2002
2003        // Verify it persisted
2004        let current = vas.brk(None);
2005        assert_eq!(current, new_addr);
2006    }
2007
2008    #[test]
2009    fn test_vas_brk_refuses_shrink() {
2010        let vas = VirtualAddressSpace::new();
2011
2012        // Extend the heap first
2013        let extended = VirtualAddress(0x2000_0001_0000);
2014        vas.brk(Some(extended));
2015
2016        // Try to shrink (should be ignored -- brk only grows)
2017        let shrink_addr = VirtualAddress(0x2000_0000_0000);
2018        let result = vas.brk(Some(shrink_addr));
2019        // The break should remain at the extended address
2020        assert_eq!(result, extended);
2021    }
2022
2023    #[test]
2024    fn test_vas_brk_refuses_below_heap_start() {
2025        let vas = VirtualAddressSpace::new();
2026
2027        // Try to set break below heap start
2028        let below_start = VirtualAddress(0x1000_0000_0000);
2029        let result = vas.brk(Some(below_start));
2030        // Should remain at initial break
2031        assert_eq!(result, VirtualAddress(0x2000_0000_0000));
2032    }
2033
2034    #[test]
2035    fn test_vas_stack_top_get_set() {
2036        let vas = VirtualAddressSpace::new();
2037
2038        let default_top = vas.stack_top();
2039        assert_eq!(default_top, 0x7FFF_FFFF_0000);
2040
2041        vas.set_stack_top(0x7000_0000_0000);
2042        assert_eq!(vas.stack_top(), 0x7000_0000_0000);
2043    }
2044
2045    #[test]
2046    fn test_vas_user_stack_base_and_size() {
2047        let vas = VirtualAddressSpace::new();
2048
2049        let stack_size = vas.user_stack_size();
2050        assert_eq!(stack_size, 8 * 1024 * 1024); // 8MB
2051
2052        let stack_base = vas.user_stack_base();
2053        let expected_base = 0x7FFF_FFFF_0000 - 8 * 1024 * 1024;
2054        assert_eq!(stack_base, expected_base);
2055    }
2056
2057    // Note: test_vas_clone_from removed -- clone_from() now allocates
2058    // real page tables via FRAME_ALLOCATOR, which is unavailable in the
2059    // host test environment. Verified via QEMU boot tests instead.
2060
2061    #[test]
2062    fn test_vas_mmap_advances_address() {
2063        let vas = VirtualAddressSpace::new();
2064
2065        // First mmap should return the initial mmap address
2066        let addr1 = vas.mmap(0x1000, MappingType::Data);
2067        assert!(addr1.is_ok());
2068        let addr1 = addr1.unwrap();
2069        assert_eq!(addr1, VirtualAddress(0x4000_0000_0000));
2070
2071        // Second mmap should advance past the first (page-aligned)
2072        let addr2 = vas.mmap(0x2000, MappingType::Data);
2073        assert!(addr2.is_ok());
2074        let addr2 = addr2.unwrap();
2075        assert_eq!(addr2, VirtualAddress(0x4000_0000_1000));
2076    }
2077
2078    #[test]
2079    fn test_vas_mmap_page_alignment() {
2080        let vas = VirtualAddressSpace::new();
2081
2082        // Request a non-page-aligned size
2083        let addr = vas.mmap(100, MappingType::Code);
2084        assert!(addr.is_ok());
2085
2086        // Next mmap should be at page-aligned offset
2087        let addr2 = vas.mmap(100, MappingType::Code);
2088        assert!(addr2.is_ok());
2089        let diff = addr2.unwrap().as_u64() - addr.unwrap().as_u64();
2090        assert_eq!(diff, 4096, "mmap allocations should be page-aligned");
2091    }
2092
2093    // --- VasStats tests ---
2094
2095    #[test]
2096    fn test_vas_stats_default() {
2097        let stats = VasStats::default();
2098        assert_eq!(stats.total_size, 0);
2099        assert_eq!(stats.code_size, 0);
2100        assert_eq!(stats.data_size, 0);
2101        assert_eq!(stats.stack_size, 0);
2102        assert_eq!(stats.heap_size, 0);
2103        assert_eq!(stats.mapping_count, 0);
2104    }
2105}