Skip to main content

kernel/mm/vmm/
vmm.rs

1// SPDX-License-Identifier: GPL-3.0-only
2//! VMM definitions (VMM struct, common helpers)
3//!
4//! Authors: MarioS271
5
6use crate::lib::addr::VirtAddr;
7use crate::mm::pmm::{Pmm, FRAME_SIZE};
8use crate::mm::vmm::address_space::AddressSpace;
9use crate::mm::vmm::traits::VmmPaging;
10use crate::mm::vmm::vma::{Vma, VmaFlags};
11
12#[cfg(feature = "debug-checks")] use crate::lib::panic::kernel_panic;
13#[cfg(feature = "debug-checks")] use crate::lib::panic_codes::PanicCode;
14
15// TODO: huge page stuff idk
16
17/// Namespace for all VMM methods
18pub struct Vmm;
19
20/// Error type for VMM operations
21pub type VmmResult = Result<(), VmmError>;
22
23/// VMM Error Enum with VMM error lib
24#[derive(Debug)]
25pub enum VmmError {
26    OutOfMemory,
27    InvalidUnmap,
28    InvalidRemap,
29    VmaNotFound,
30    VmaOverlap
31}
32
33impl Vmm {
34    /// Lazily map a virtual memory region by creating a VMA in the given [`AddressSpace`]
35    ///
36    /// # Panics
37    /// Panics in debug builds if either `virt` is not aligned to [`FRAME_SIZE`] or `size`
38    /// is not a multiple of [`FRAME_SIZE`]
39    ///
40    /// # Returns
41    /// Returns [`VmmError::VmaOverlap`] if the given region overlaps with an existing VMA
42    pub fn lazy_map_region(
43        address_space: &mut AddressSpace,
44        virt: VirtAddr,
45        size: u64,
46        vma_flags: VmaFlags
47    ) -> VmmResult {
48        #[cfg(feature = "debug-checks")]
49        {
50            if !virt.is_aligned(FRAME_SIZE) || size % FRAME_SIZE != 0 {
51                kernel_panic(
52                    PanicCode::MisalignedAddress,
53                    "Vmm::map_region recieved an improperly aligned virtual address or size"
54                );
55            }
56        }
57
58        address_space.insert_vma(
59            Vma {
60                start_addr: virt,
61                end_addr: virt + size,
62                flags: vma_flags
63            }
64        )
65    }
66
67    /// Map a virtual memory region by creating a VMA in the given [`AddressSpace`] and then mapping matching pages
68    ///
69    /// This method first calls [`Vmm::lazy_map_region`] to create the VMA and do the debug align check,
70    /// and then eagerly maps the necessary pages
71    ///
72    /// # Panics
73    /// Panics in debug builds if either `virt` is not aligned to [`FRAME_SIZE`] or `size`
74    /// is not a multiple of [`FRAME_SIZE`]
75    ///
76    /// # Returns
77    /// Returns [`VmmError::VmaOverlap`] if the given region overlaps with an existing VMA
78    pub fn map_region(
79        pmm: &mut Pmm,
80        address_space: &mut AddressSpace,
81        virt: VirtAddr,
82        size: u64,
83        vma_flags: VmaFlags
84    ) -> VmmResult {
85        Self::lazy_map_region(address_space, virt, size, vma_flags)?;
86
87        let page_ptr = address_space.page_ptr();
88        let page_flags = Self::vma_flags_to_page_flags(vma_flags);
89
90        let mut offset = 0u64;
91        while offset < size {
92            let frame = match pmm.alloc_frame_zeroed() {
93                Some(frame) => frame,
94                None => {
95                    // Safety: nothing except this method could hold refs/ptrs into this, and
96                    // this method doesn't
97                    unsafe { Self::unmap_region(pmm, address_space, virt)? };
98                    return Err(VmmError::OutOfMemory);
99                }
100            };
101
102            if let Err(error) = unsafe {
103                Self::map_page(
104                    pmm,
105                    page_ptr,
106                    virt + offset,
107                    frame,
108                    crate::arch::x86_64::mm::vmm::page_type::PageType::Normal,   // TODO: abstract somehow cause arch specific enums cant be used here
109                    page_flags
110                )
111            } {
112                pmm.free_frame(frame);
113
114                // Safety: nothing except this function could hold refs/ptrs into this region yet,
115                // and this function doesn't
116                unsafe { Self::unmap_region(pmm, address_space, virt)?; }
117
118                return Err(error);
119            }
120
121            offset += FRAME_SIZE;
122        }
123
124        Ok(())
125    }
126
127    /// Unmap a virtual memory region by removing its VMA from the given [`AddressSpace`] and
128    /// unmapping all currently mapped pages
129    ///
130    /// # Safety
131    /// The caller must ensure that no live code or data holds references into any address mapped
132    /// within `[virt, vma.end_addr)` after this method call returns
133    ///
134    /// # Panics
135    /// Panics in debug builds if `virt` is not aligned to [`FRAME_SIZE`]
136    ///
137    /// # Returns
138    /// Returns [`VmmError::VmaNotFound`] if no VMA starting at `virt` exists
139    pub unsafe fn unmap_region(
140        pmm: &mut Pmm,
141        address_space: &mut AddressSpace,
142        virt: VirtAddr
143    ) -> VmmResult
144    where
145        Self: VmmPaging
146    {
147        #[cfg(feature = "debug-checks")]
148        if !virt.is_aligned(FRAME_SIZE) {
149            kernel_panic(
150                PanicCode::MisalignedAddress,
151                "Vmm::unmap_region recieved an improperly aligned virtual address or size"
152            );
153        }
154
155        let vma = address_space.remove_vma(virt).ok_or(VmmError::VmaNotFound)?;
156        let size = vma.end_addr.as_u64() - vma.start_addr.as_u64();
157
158        let mut offset = 0;
159        while offset < size {
160            if let Some(phys) = Self::translate(address_space.page_ptr(), virt + offset) {
161                unsafe { Self::unmap_page(address_space.page_ptr(), virt + offset)?; }
162                pmm.free_frame(phys);
163            }
164
165            offset += FRAME_SIZE;
166        }
167
168        Ok(())
169    }
170
171    /// Remap a virtual memory region by changing its VMA's flags from the given [`AddressSpace`] and
172    /// remapping all currently mapped pages to new matching page flags.
173    ///
174    /// # Safety
175    /// The caller must ensure that for example when removing the `WRITE` flag,
176    /// no mutable references to the to be modified memory is held.
177    ///
178    /// # Panics
179    /// Panics in debug builds if `virt` is not aligned to [`FRAME_SIZE`]
180    ///
181    /// # Returns
182    /// Returns [`VmmError::VmaNotFound`] if no VMA starting at `virt` exists
183    pub unsafe fn remap_region(
184        address_space: &mut AddressSpace,
185        virt: VirtAddr,
186        new_vma_flags: VmaFlags
187    ) -> VmmResult
188    where
189        Self: VmmPaging
190    {
191        #[cfg(feature = "debug-checks")]
192        if !virt.is_aligned(FRAME_SIZE) {
193            kernel_panic(
194                PanicCode::MisalignedAddress,
195                "Vmm::remap_region recieved an improperly aligned virtual address or size"
196            );
197        }
198
199        let mut vma = address_space.remove_vma(virt).ok_or(VmmError::VmaNotFound)?;
200        let size = vma.end_addr.as_u64() - vma.start_addr.as_u64();
201
202        vma.flags = new_vma_flags;
203        address_space.insert_vma(vma)?;
204
205        let new_page_flags = Self::vma_flags_to_page_flags(new_vma_flags);
206
207        let mut offset = 0;
208        while offset < size {
209            if let Some((_, page_size)) = Self::translate_with_size(address_space.page_ptr(), virt + offset) {
210                unsafe { Self::remap_page(address_space.page_ptr(), virt + offset, new_page_flags)?; }
211                offset += page_size;
212            } else {
213                offset += FRAME_SIZE;
214            }
215        }
216
217        Ok(())
218    }
219}