Skip to main content

osiris/mem/vmm/
nommu.rs

1use core::ptr::{NonNull, copy_nonoverlapping};
2
3use crate::hal::mem::{PhysAddr, VirtAddr};
4
5use crate::{
6    error::Result,
7    mem::{
8        alloc::{Allocator, bestfit},
9        pfa, vmm,
10    },
11};
12
13pub struct AddressSpace {
14    begin: PhysAddr,
15    #[allow(dead_code)]
16    end: PhysAddr,
17    allocator: bestfit::BestFitAllocator,
18}
19
20#[cfg(any(feature = "metrics", metrics))]
21impl AddressSpace {
22    pub(crate) fn metrics(&self) -> crate::mem::alloc::Metrics {
23        self.allocator.metrics()
24    }
25}
26
27impl vmm::AddressSpacelike for AddressSpace {
28    fn new(pgs: usize) -> Result<Self> {
29        let begin = pfa::alloc_page(pgs).ok_or(kerr!(ENOMEM))?;
30        let end = begin
31            .checked_add(pgs * pfa::PAGE_SIZE)
32            .ok_or(kerr!(ENOMEM))?;
33
34        let mut allocator = bestfit::BestFitAllocator::new();
35        unsafe { allocator.add_range(&(begin..end))? };
36
37        Ok(Self {
38            begin,
39            end,
40            allocator,
41        })
42    }
43
44    fn map(&mut self, region: vmm::Region) -> Result<PhysAddr> {
45        let req = region.start.and_then(|virt| self.virt_to_phys(virt));
46        // TODO: per page align
47        let align = core::mem::align_of::<u128>();
48        let start = unsafe { self.allocator.malloc::<u8>(region.len(), align, req)? };
49
50        match region.backing {
51            vmm::Backing::Anon(phys) => {
52                unsafe {
53                    copy_nonoverlapping(phys.as_mut_ptr::<u8>(), start.as_ptr(), region.len())
54                };
55            }
56            vmm::Backing::Zeroed => {
57                unsafe { core::ptr::write_bytes(start.as_ptr(), 0, region.len()) };
58            }
59            vmm::Backing::Uninit => {}
60        }
61
62        Ok(start.into())
63    }
64
65    fn unmap(&mut self, region: &vmm::Region) -> Result<()> {
66        let virt = region.start.ok_or(kerr!(EINVAL))?;
67        let phys = self.virt_to_phys(virt).ok_or(kerr!(EINVAL))?;
68        let ptr = NonNull::new(phys.as_mut_ptr::<u8>()).ok_or(kerr!(EINVAL))?;
69        unsafe { self.allocator.free(ptr, region.len()) };
70        Ok(())
71    }
72
73    fn protect(&mut self, _region: &vmm::Region, _perms: vmm::Perms) -> Result<()> {
74        Ok(())
75    }
76
77    fn phys_to_virt(&self, addr: PhysAddr) -> Option<VirtAddr> {
78        if addr < self.begin || addr >= self.end {
79            return None;
80        }
81        addr.checked_sub(self.begin.as_usize())
82            .map(|phys| VirtAddr::new(phys.as_usize()))
83    }
84
85    fn virt_to_phys(&self, addr: VirtAddr) -> Option<PhysAddr> {
86        let phys = self.begin.checked_add(addr.as_usize())?;
87        if phys >= self.end {
88            return None;
89        }
90        Some(phys)
91    }
92
93    fn end(&self) -> VirtAddr {
94        VirtAddr::new(self.end.diff(self.begin))
95    }
96
97    fn activate(&self) -> Result<()> {
98        Ok(())
99    }
100}
101
102impl Drop for AddressSpace {
103    fn drop(&mut self) {
104        // Without this the per-task page reservation returns to the PFA only on
105        // process death, which means PFA exhaustion under task churn.
106        let pgs = self.end.diff(self.begin) / pfa::PAGE_SIZE;
107        if pgs > 0 {
108            pfa::free_page(self.begin, pgs);
109        }
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116    use crate::mem::vmm::{AddressSpacelike, Backing, Perms, Region};
117
118    fn make_addr_space(size: usize) -> AddressSpace {
119        let layout =
120            std::alloc::Layout::from_size_align(size, core::mem::align_of::<u128>()).unwrap();
121        let ptr = unsafe { std::alloc::alloc(layout) };
122        let begin = PhysAddr::new(ptr as usize);
123        let end = begin + size;
124        let mut allocator = bestfit::BestFitAllocator::new();
125        unsafe { allocator.add_range(&(begin..end)).unwrap() };
126        AddressSpace {
127            begin,
128            end,
129            allocator,
130        }
131    }
132
133    #[test]
134    fn unmap_returns_space_to_allocator() {
135        let mut as_ = make_addr_space(4096);
136
137        let region = Region::new(None, 2048, Backing::Uninit, Perms::Read);
138        let phys = as_.map(region).unwrap();
139
140        let virt = as_.phys_to_virt(phys).unwrap();
141        let placed = Region::new(Some(virt), 2048, Backing::Uninit, Perms::Read);
142        as_.unmap(&placed).unwrap();
143
144        let region2 = Region::new(None, 2048, Backing::Uninit, Perms::Read);
145        as_.map(region2).expect("re-map after unmap should not OOM");
146    }
147
148    #[test]
149    fn unmap_unplaced_region_rejected() {
150        let mut as_ = make_addr_space(4096);
151        let region = Region::new(None, 128, Backing::Uninit, Perms::Read);
152        assert!(as_.unmap(&region).is_err());
153    }
154
155    #[test]
156    fn virt_to_phys_rejects_out_of_range() {
157        let as_ = make_addr_space(4096);
158        let size = as_.end.diff(as_.begin);
159        assert!(as_.virt_to_phys(VirtAddr::new(size)).is_none());
160        assert!(as_.virt_to_phys(VirtAddr::new(size + 1)).is_none());
161        assert!(as_.virt_to_phys(VirtAddr::new(usize::MAX)).is_none());
162    }
163
164    #[test]
165    fn phys_to_virt_rejects_out_of_range() {
166        let as_ = make_addr_space(4096);
167        assert!(as_.phys_to_virt(as_.end).is_none());
168        assert!(as_.phys_to_virt(as_.begin - 1).is_none());
169        assert!(as_.phys_to_virt(as_.end + 1).is_none());
170    }
171
172    #[test]
173    fn virt_phys_roundtrip() {
174        let as_ = make_addr_space(4096);
175        let v = VirtAddr::new(128);
176        let p = as_.virt_to_phys(v).unwrap();
177        assert_eq!(as_.phys_to_virt(p), Some(v));
178    }
179}