kernel/object/capability/memory_mapping/
syscall.rs

1//! System calls for MemoryMappingOps capability
2//!
3//! This module implements system calls for memory mapping operations.
4//! ANONYMOUS mappings are handled directly in the syscall for efficiency,
5//! while all other mappings (including FIXED) are delegated to KernelObjects
6//! with MemoryMappingOps capability.
7
8use crate::arch::Trapframe;
9use crate::environment::PAGE_SIZE;
10use crate::mem::page::allocate_raw_pages;
11use crate::task::mytask;
12use crate::vm::vmem::{MemoryArea, VirtualMemoryMap};
13use alloc::boxed::Box;
14use alloc::vec::Vec;
15
16// Memory mapping flags (MAP_*)
17#[allow(dead_code)]
18const MAP_SHARED: usize = 0x01;
19const MAP_ANONYMOUS: usize = 0x20;
20const MAP_PRIVATE: usize = 0x02;
21const MAP_FIXED: usize = 0x10;
22
23// Protection flags (PROT_*)
24const PROT_READ: usize = 0x1;
25const PROT_WRITE: usize = 0x2;
26const PROT_EXEC: usize = 0x4;
27
28/// System call for memory mapping a KernelObject with MemoryMappingOps capability
29/// or creating anonymous mappings
30///
31/// # Arguments
32/// - handle: Handle to the KernelObject (must support MemoryMappingOps) - ignored for ANONYMOUS
33/// - vaddr: Virtual address where to map (0 means kernel chooses)
34/// - length: Length of the mapping in bytes
35/// - prot: Protection flags (PROT_READ, PROT_WRITE, PROT_EXEC)
36/// - flags: Mapping flags (MAP_SHARED, MAP_PRIVATE, MAP_FIXED, MAP_ANONYMOUS, etc.)
37/// - offset: Offset within the object to start mapping from (ignored for ANONYMOUS)
38///
39/// # Returns
40/// - On success: virtual address of the mapping
41/// - On error: usize::MAX
42///
43/// # Design
44/// - ANONYMOUS mappings are handled entirely within this syscall
45/// - All other mappings (including FIXED) are delegated to the KernelObject's MemoryMappingOps
46pub fn sys_memory_map(trapframe: &mut Trapframe) -> usize {
47    let task = match mytask() {
48        Some(task) => task,
49        None => return usize::MAX,
50    };
51
52    let handle = trapframe.get_arg(0) as u32;
53    let vaddr = trapframe.get_arg(1) as usize;
54    let length = trapframe.get_arg(2) as usize;
55    let prot = trapframe.get_arg(3) as usize;
56    let flags = trapframe.get_arg(4) as usize;
57    let offset = trapframe.get_arg(5) as usize;
58
59    // Increment PC to avoid infinite loop if mmap fails
60    trapframe.increment_pc_next(task);
61
62    // Input validation
63    if length == 0 {
64        return usize::MAX;
65    }
66
67    // Round up length to page boundary
68    let aligned_length = (length + PAGE_SIZE - 1) & !(PAGE_SIZE - 1);
69    let num_pages = aligned_length / PAGE_SIZE;
70
71    // Handle ANONYMOUS mappings specially - these are handled entirely in the syscall
72    if (flags & MAP_ANONYMOUS) != 0 {
73        return handle_anonymous_mapping(task, vaddr, aligned_length, num_pages, prot, flags);
74    }
75
76    // All other mappings are handled through the new MemoryMappingOps design
77    let kernel_obj = match task.handle_table.get(handle) {
78        Some(obj) => obj,
79        None => return usize::MAX, // Invalid handle
80    };
81
82    // Check if object supports MemoryMappingOps
83    let memory_mappable = match kernel_obj.as_memory_mappable() {
84        Some(mappable) => mappable,
85        None => return usize::MAX, // Object doesn't support memory mapping operations
86    };
87
88    // Check if the object supports mmap
89    if !memory_mappable.supports_mmap() {
90        return usize::MAX;
91    }
92
93    // Get mapping information from the object.
94    // IMPORTANT: use the page-aligned length, because the VM mapping will be created
95    // with `aligned_length` and must not exceed the object's available range.
96    // Determine is_shared from flags (MAP_SHARED controls sharing semantics)
97    let is_shared = (flags & MAP_SHARED) != 0;
98    let (paddr, obj_permissions, _obj_is_shared) =
99        match memory_mappable.get_mapping_info_with(offset, aligned_length, is_shared) {
100            Ok(info) => info,
101            Err(_) => return usize::MAX,
102        };
103    let is_map_fixed = (flags & MAP_FIXED) != 0;
104
105    // Determine final address
106    let final_vaddr = if vaddr == 0 {
107        match task
108            .vm_manager
109            .find_unmapped_area(aligned_length, PAGE_SIZE)
110        {
111            Some(addr) => addr,
112            None => return usize::MAX,
113        }
114    } else {
115        if vaddr % PAGE_SIZE != 0 {
116            return usize::MAX;
117        }
118        vaddr
119    };
120
121    // Create memory areas
122    let vmarea = MemoryArea::new(final_vaddr, final_vaddr + aligned_length - 1);
123    let pmarea = MemoryArea::new(paddr, paddr + aligned_length - 1);
124
125    // Combine object permissions with requested permissions
126    let final_permissions = obj_permissions & {
127        let mut perm = 0;
128        if (prot & PROT_READ) != 0 {
129            perm |= 0x1;
130        }
131        if (prot & PROT_WRITE) != 0 {
132            perm |= 0x2;
133        }
134        if (prot & PROT_EXEC) != 0 {
135            perm |= 0x4;
136        }
137        perm
138    } | 0x08; // Access from user space
139
140    // If this is a private file-backed mapping, allocate private pages and copy
141    let is_map_private_flag = (flags & MAP_PRIVATE) != 0;
142    if is_map_private_flag && !is_shared {
143        // Allocate private pages
144        let pages = allocate_raw_pages(num_pages);
145        let pages_ptr = pages as usize;
146        let private_pmarea = MemoryArea::new(pages_ptr, pages_ptr + aligned_length - 1);
147
148        let mut vm_map =
149            VirtualMemoryMap::new(private_pmarea, vmarea, final_permissions, false, None);
150
151        // For non-MAP_FIXED, treat vaddr as a hint: if it overlaps, pick a new area.
152        let mut chosen_vaddr = final_vaddr;
153        if !is_map_fixed {
154            if task.vm_manager.add_memory_map(vm_map.clone()).is_err() {
155                chosen_vaddr = match task
156                    .vm_manager
157                    .find_unmapped_area(aligned_length, PAGE_SIZE)
158                {
159                    Some(addr) => addr,
160                    None => {
161                        crate::mem::page::free_raw_pages(pages, num_pages);
162                        return usize::MAX;
163                    }
164                };
165                let vmarea = MemoryArea::new(chosen_vaddr, chosen_vaddr + aligned_length - 1);
166                vm_map =
167                    VirtualMemoryMap::new(private_pmarea, vmarea, final_permissions, false, None);
168                if task.vm_manager.add_memory_map(vm_map.clone()).is_err() {
169                    crate::mem::page::free_raw_pages(pages, num_pages);
170                    return usize::MAX;
171                }
172            }
173        }
174
175        let removed_mappings: Vec<VirtualMemoryMap> = if is_map_fixed {
176            match task.vm_manager.add_memory_map_fixed(vm_map.clone()) {
177                Ok(removed) => removed,
178                Err(_) => {
179                    crate::mem::page::free_raw_pages(pages, num_pages);
180                    return usize::MAX;
181                }
182            }
183        } else {
184            // Non-fixed path already inserted the map via add_memory_map above.
185            Vec::new()
186        };
187
188        {
189            // For private mappings the new mapping uses private pages and the object
190            // does not own those pages; avoid calling on_mapped for the object.
191
192            // Notify owners of removed maps (only for shared mappings)
193            for removed_map in &removed_mappings {
194                if removed_map.is_shared {
195                    if let Some(owner_weak) = &removed_map.owner {
196                        if let Some(owner) = owner_weak.upgrade() {
197                            owner.on_unmapped(removed_map.vmarea.start, removed_map.vmarea.size());
198                        }
199                    }
200                }
201            }
202
203            // Clean up removed managed pages
204            for removed_map in removed_mappings {
205                if !removed_map.is_shared {
206                    let mapping_start = removed_map.vmarea.start;
207                    let mapping_end = removed_map.vmarea.end;
208                    let num_removed_pages =
209                        (mapping_end - mapping_start + 1 + PAGE_SIZE - 1) / PAGE_SIZE;
210                    for i in 0..num_removed_pages {
211                        let page_vaddr = mapping_start + i * PAGE_SIZE;
212                        if let Some(_managed_page) = task.remove_managed_page(page_vaddr) {
213                            // freed when dropped
214                        }
215                    }
216                }
217            }
218
219            // Copy contents from object paddr to private pages
220            for i in 0..num_pages {
221                let src = (paddr + i * PAGE_SIZE) as *const u8;
222                let dst_page = unsafe { (pages as *mut crate::mem::page::Page).add(i) } as *mut u8;
223                unsafe {
224                    core::ptr::copy_nonoverlapping(src, dst_page, PAGE_SIZE);
225                }
226            }
227
228            // Add managed pages to task
229            for i in 0..num_pages {
230                let page_vaddr = chosen_vaddr + i * crate::environment::PAGE_SIZE;
231                let page_ptr = unsafe { (pages as *mut crate::mem::page::Page).add(i) };
232                task.add_managed_page(crate::task::ManagedPage {
233                    vaddr: page_vaddr,
234                    page: unsafe { Box::from_raw(page_ptr) },
235                });
236            }
237
238            return chosen_vaddr;
239        }
240    }
241
242    // Create virtual memory map with weak reference to the object
243    let owner = kernel_obj.as_memory_mappable_weak();
244    let vm_map = VirtualMemoryMap::new(pmarea, vmarea, final_permissions, is_shared, owner);
245
246    // Add the mapping to VM manager
247    if !is_map_fixed {
248        // vaddr != 0 is treated as a hint; if it overlaps, fall back to a fresh area.
249        if task.vm_manager.add_memory_map(vm_map.clone()).is_err() {
250            let chosen_vaddr = match task
251                .vm_manager
252                .find_unmapped_area(aligned_length, PAGE_SIZE)
253            {
254                Some(addr) => addr,
255                None => return usize::MAX,
256            };
257            let vmarea = MemoryArea::new(chosen_vaddr, chosen_vaddr + aligned_length - 1);
258            let pmarea = MemoryArea::new(paddr, paddr + aligned_length - 1);
259            let owner = kernel_obj.as_memory_mappable_weak();
260            let retry_map =
261                VirtualMemoryMap::new(pmarea, vmarea, final_permissions, is_shared, owner);
262            if task.vm_manager.add_memory_map(retry_map).is_err() {
263                return usize::MAX;
264            }
265
266            memory_mappable.on_mapped(chosen_vaddr, paddr, aligned_length, offset);
267            return chosen_vaddr;
268        }
269
270        memory_mappable.on_mapped(final_vaddr, paddr, aligned_length, offset);
271        return final_vaddr;
272    }
273
274    match task.vm_manager.add_memory_map_fixed(vm_map) {
275        Ok(removed_mappings) => {
276            // Notify the object that mapping was created
277            memory_mappable.on_mapped(final_vaddr, paddr, aligned_length, offset);
278
279            // First, notify object owners about removed mappings
280            for removed_map in &removed_mappings {
281                if removed_map.is_shared {
282                    if let Some(owner_weak) = &removed_map.owner {
283                        if let Some(owner) = owner_weak.upgrade() {
284                            owner.on_unmapped(removed_map.vmarea.start, removed_map.vmarea.size());
285                        }
286                    }
287                }
288            }
289
290            // Then, handle managed page cleanup (MMU cleanup is already handled by VmManager.add_memory_map_fixed)
291            for removed_map in removed_mappings {
292                // Remove managed pages only for private mappings
293                if !removed_map.is_shared {
294                    let mapping_start = removed_map.vmarea.start;
295                    let mapping_end = removed_map.vmarea.end;
296                    let num_pages = (mapping_end - mapping_start + 1 + PAGE_SIZE - 1) / PAGE_SIZE;
297
298                    for i in 0..num_pages {
299                        let page_vaddr = mapping_start + i * PAGE_SIZE;
300                        if let Some(_managed_page) = task.remove_managed_page(page_vaddr) {
301                            // The managed page is automatically freed when dropped
302                        }
303                    }
304                }
305            }
306
307            final_vaddr
308        }
309        Err(_) => usize::MAX,
310    }
311}
312
313/// Handle anonymous memory mapping
314fn handle_anonymous_mapping(
315    task: &crate::task::Task,
316    vaddr: usize,
317    aligned_length: usize,
318    num_pages: usize,
319    prot: usize,
320    flags: usize,
321) -> usize {
322    // For anonymous mappings, decide shared/private based on flags
323    let is_shared = (flags & MAP_SHARED) != 0;
324    let is_map_fixed = (flags & MAP_FIXED) != 0;
325
326    // For anonymous mappings, allocate physical memory directly
327    let pages = allocate_raw_pages(num_pages);
328    let pages_ptr = pages as usize;
329
330    // Convert protection flags to kernel permissions
331    let mut permissions = 0x08; // Access from user space
332    if (prot & PROT_READ) != 0 {
333        permissions |= 0x1; // Readable
334    }
335    if (prot & PROT_WRITE) != 0 {
336        permissions |= 0x2; // Writable
337    }
338    if (prot & PROT_EXEC) != 0 {
339        permissions |= 0x4; // Executable
340    }
341
342    // Determine final address (0 means kernel chooses)
343    let mut chosen_vaddr = vaddr;
344    if chosen_vaddr == 0 {
345        chosen_vaddr = match task
346            .vm_manager
347            .find_unmapped_area(aligned_length, PAGE_SIZE)
348        {
349            Some(addr) => addr,
350            None => {
351                crate::mem::page::free_raw_pages(pages, num_pages);
352                return usize::MAX;
353            }
354        };
355    } else {
356        if chosen_vaddr % PAGE_SIZE != 0 {
357            crate::mem::page::free_raw_pages(pages, num_pages);
358            return usize::MAX;
359        }
360    }
361
362    // Create memory areas
363    let vmarea = MemoryArea::new(chosen_vaddr, chosen_vaddr + aligned_length - 1);
364    let pmarea = MemoryArea::new(pages_ptr, pages_ptr + aligned_length - 1);
365
366    // Create virtual memory map
367    let vm_map = VirtualMemoryMap::new(pmarea, vmarea, permissions, is_shared, None); // Anonymous mappings have no owner
368
369    if !is_map_fixed {
370        // Treat chosen_vaddr as hint; if it overlaps, fall back once.
371        if task.vm_manager.add_memory_map(vm_map.clone()).is_err() {
372            chosen_vaddr = match task
373                .vm_manager
374                .find_unmapped_area(aligned_length, PAGE_SIZE)
375            {
376                Some(addr) => addr,
377                None => {
378                    crate::mem::page::free_raw_pages(pages, num_pages);
379                    return usize::MAX;
380                }
381            };
382            let vmarea = MemoryArea::new(chosen_vaddr, chosen_vaddr + aligned_length - 1);
383            let retry_map = VirtualMemoryMap::new(pmarea, vmarea, permissions, is_shared, None);
384            if task.vm_manager.add_memory_map(retry_map).is_err() {
385                crate::mem::page::free_raw_pages(pages, num_pages);
386                return usize::MAX;
387            }
388        }
389    } else {
390        match task.vm_manager.add_memory_map_fixed(vm_map) {
391            Ok(removed_mappings) => {
392                // First, process notifications for object owners
393                for removed_map in &removed_mappings {
394                    if removed_map.is_shared {
395                        if let Some(owner_weak) = &removed_map.owner {
396                            if let Some(owner) = owner_weak.upgrade() {
397                                owner.on_unmapped(
398                                    removed_map.vmarea.start,
399                                    removed_map.vmarea.size(),
400                                );
401                            }
402                        }
403                    }
404                }
405
406                // Then, handle managed page cleanup (MMU cleanup is already handled by VmManager.add_memory_map_fixed)
407                for removed_map in removed_mappings {
408                    // Remove managed pages only for private mappings
409                    if !removed_map.is_shared {
410                        let mapping_start = removed_map.vmarea.start;
411                        let mapping_end = removed_map.vmarea.end;
412                        let num_removed_pages =
413                            (mapping_end - mapping_start + 1 + PAGE_SIZE - 1) / PAGE_SIZE;
414
415                        for i in 0..num_removed_pages {
416                            let page_vaddr = mapping_start + i * PAGE_SIZE;
417                            if let Some(_managed_page) = task.remove_managed_page(page_vaddr) {
418                                // The managed page is automatically freed when dropped
419                            }
420                        }
421                    }
422                }
423            }
424            Err(_) => {
425                crate::mem::page::free_raw_pages(pages, num_pages);
426                return usize::MAX;
427            }
428        }
429    }
430
431    // Add managed pages for the new anonymous mapping
432    for i in 0..num_pages {
433        let page_vaddr = chosen_vaddr + i * crate::environment::PAGE_SIZE;
434        let page_ptr = unsafe { (pages as *mut crate::mem::page::Page).add(i) };
435        task.add_managed_page(crate::task::ManagedPage {
436            vaddr: page_vaddr,
437            page: unsafe { Box::from_raw(page_ptr) },
438        });
439    }
440
441    chosen_vaddr
442}
443
444/// System call for unmapping memory from a KernelObject or anonymous mapping
445///
446/// # Arguments
447/// - vaddr: Virtual address of the mapping to unmap
448/// - length: Length of the mapping to unmap
449///
450/// # Returns
451/// - On success: 0
452/// - On error: usize::MAX
453pub fn sys_memory_unmap(trapframe: &mut Trapframe) -> usize {
454    let task = match mytask() {
455        Some(task) => task,
456        None => return usize::MAX,
457    };
458
459    let vaddr = trapframe.get_arg(0) as usize;
460    let length = trapframe.get_arg(1) as usize;
461
462    // Increment PC to avoid infinite loop if munmap fails
463    trapframe.increment_pc_next(task);
464
465    // Input validation
466    if length == 0 || vaddr % PAGE_SIZE != 0 {
467        return usize::MAX;
468    }
469
470    // Remove the mapping regardless of whether it's anonymous or object-based
471    if let Some(removed_map) = task.vm_manager.remove_memory_map_by_addr(vaddr) {
472        // Notify the object owner if available (for object-based mappings)
473        if let Some(owner_weak) = &removed_map.owner {
474            if removed_map.is_shared {
475                if let Some(owner) = owner_weak.upgrade() {
476                    owner.on_unmapped(vaddr, length);
477                }
478            }
479        }
480
481        // Remove managed pages only for private mappings
482        // Shared mappings should not have their physical pages freed here
483        // as they might be used by other processes
484        // (MMU cleanup is already handled by VmManager.remove_memory_map_by_addr)
485        if !removed_map.is_shared {
486            let mapping_start = removed_map.vmarea.start;
487            let mapping_end = removed_map.vmarea.end;
488            let num_pages = (mapping_end - mapping_start + 1 + PAGE_SIZE - 1) / PAGE_SIZE;
489
490            for i in 0..num_pages {
491                let page_vaddr = mapping_start + i * PAGE_SIZE;
492                if let Some(_managed_page) = task.remove_managed_page(page_vaddr) {
493                    // The managed page is automatically freed when dropped
494                }
495            }
496        }
497
498        0
499    } else {
500        usize::MAX // No mapping found at this address
501    }
502}
503
504// TODO: Migrate object-backed MAP_PRIVATE mappings to delayed Copy-On-Write (COW).
505// Motivation:
506// - Currently MAP_PRIVATE file-backed mappings perform an eager (immediate) copy of
507//   the mapped region at mmap time. This can be wasteful for large mappings or when
508//   the mapping is only read by the process. Delayed COW preserves memory and CPU
509//   by copying only on the first write to a page.
510//
511// High-level plan (implementation checklist):
512// 1) Syscall layer: when a user requests MAP_PRIVATE for an object-backed mapping,
513//    set a `cow` flag on the VirtualMemoryMap and do NOT perform an immediate copy.
514//    - Ensure the mapping is installed with write permission cleared so stores trap.
515//    - Preserve the mapping owner (object) for read access until pages are copied.
516//
517// 2) VM representation: add/ensure a boolean `cow` field on VirtualMemoryMap to mark
518//    that the mapping uses copy-on-write semantics.
519//
520// 3) Exception/Trap handling: on store-page-faults, detect whether the faulting
521//    virtual address belongs to a mapping with cow == true. If so, invoke a per-page
522//    COW handler instead of the generic lazy mapping logic.
523//
524// 4) Task::handle_cow_page: implement a handler that:
525//    - Allocates a new physical page for the faulting virtual page.
526//    - Copies the contents from the original backing paddr (via the owner object
527//      or pmarea) to the newly allocated page.
528//    - Replaces only the single faulting page in the VM map by inserting a fixed
529//      one-page VirtualMemoryMap (owner = None) for that vaddr and maps it immediately
530//      (e.g., vm_manager.add_memory_map_fixed + vm_manager.lazy_map_page).
531//    - Registers the new page as a managed page of the current Task (so it will be
532//      freed on exit).
533//
534// 5) Fork/clone semantics: ensure that when a Task is cloned/forked, the child and parent
535//    share the same physical pages (do not eagerly copy) and the `cow` flag is preserved
536//    on the mapping entries so that subsequent writes by either side trigger COW.
537//    - Ensure managed_pages bookkeeping remains correct (only private copies are managed
538//      by the process that holds them).
539//
540// 6) Tests and validation:
541//    - Add unit/integration tests that map the same file in two tasks with MAP_PRIVATE,
542//      then write from one task and assert the other still sees original content.
543//    - Add tests for fork/clone + MAP_PRIVATE behavior.
544//    - Add tests for corner cases (partial-page offsets, overlapping mappings, munmap
545//      of pages that have been COW'ed).
546//
547// 7) Documentation: update rustdoc and design documentation to describe the COW
548//    semantics, the role of the `cow` flag, and the guarantees provided (ownership,
549//    notification behavior, and lifecycle of managed pages).
550//
551// Acceptance criteria:
552// - MAP_PRIVATE mappings are created without eager copying (vm_manager installs mapping
553//   with cow=true and write cleared).
554// - On first write to a page, only that page is copied and the writer gets a private
555//   writable page while others continue sharing the original page.
556// - All added tests pass in the dev environment (cargo make test) and resource leaks
557//   (pages) are not introduced.
558//
559// Notes & constraints:
560// - Some object types (e.g., device MMIO) cannot be safely COW'ed; sys_memory_map must
561//   detect such objects via supports_mmap / get_mapping_info and either fall back to
562//   eager-copy, reject the mapping, or require special flags. Document these cases.
563// - This change requires careful updates to trap handling and the Task-managed page
564//   bookkeeping; perform the work incrementally and add tests at each step.