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}