swage_core/memory/
memblock.rs

1use std::{cell::RefCell, ops::Range, ptr::null_mut};
2
3use super::{BytePointer, PfnOffset, PhysAddr, pfn_offset::CachedPfnOffset};
4use crate::memory::virt_to_phys::LinuxPageMapError;
5use crate::memory::{LinuxPageMap, VirtToPhysResolver};
6use crate::util::PAGE_SIZE;
7use libc::{MAP_ANONYMOUS, MAP_POPULATE, MAP_SHARED};
8use log::{log, trace, warn};
9use pagemap2::VirtualMemoryArea;
10
11/// A managed memory region.
12///
13/// Represents an allocated memory block with pointer, length, and physical
14/// frame number (PFN) offset information for address translation.
15#[derive(Clone, Debug)]
16pub struct Memory {
17    /// Block pointer
18    pub ptr: *mut u8,
19    /// Block length in bytes
20    pub len: usize,
21    pfn_offset: PfnOffset,
22}
23
24unsafe impl Send for Memory {}
25
26impl Memory {
27    /// Creates a new memory block with the given pointer and length.
28    pub fn new(ptr: *mut u8, len: usize) -> Self {
29        Memory {
30            ptr,
31            len,
32            pfn_offset: PfnOffset::Dynamic(Box::new(RefCell::new(None))),
33        }
34    }
35
36    /// Creates a new memory block with specified PFN offset.
37    ///
38    /// # Arguments
39    ///
40    /// * `ptr` - Pointer to the memory block
41    /// * `len` - Length in bytes
42    /// * `pfn_offset` - Physical frame number offset configuration
43    pub fn new_with_parts(ptr: *mut u8, len: usize, pfn_offset: PfnOffset) -> Self {
44        Memory {
45            ptr,
46            len,
47            pfn_offset,
48        }
49    }
50
51    /// Allocates memory using mmap.
52    ///
53    /// Creates a memory-mapped region of the specified size with
54    /// read/write permissions.
55    ///
56    /// # Errors
57    ///
58    /// Returns an I/O error if mmap fails.
59    pub fn mmap(size: usize) -> std::result::Result<Self, std::io::Error> {
60        let p = unsafe {
61            libc::mmap(
62                null_mut(),
63                size,
64                libc::PROT_READ | libc::PROT_WRITE,
65                MAP_SHARED | MAP_ANONYMOUS | MAP_POPULATE,
66                -1,
67                0,
68            )
69        };
70        if p == libc::MAP_FAILED {
71            return Err(std::io::Error::last_os_error());
72        }
73        unsafe { libc::memset(p, 0x00, size) };
74        Ok(Memory::new(p as *mut u8, size))
75    }
76
77    /// Deallocates the memory block.
78    ///
79    /// Unmaps the memory region using munmap. Consumes self.
80    pub fn dealloc(self) {
81        unsafe { libc::munmap(self.ptr as *mut libc::c_void, self.len) };
82    }
83}
84
85impl BytePointer for Memory {
86    fn addr(&self, offset: usize) -> *mut u8 {
87        assert!(
88            offset < self.len,
89            "Memory::byte_add failed. Offset {} >= {}",
90            offset,
91            self.len
92        );
93        unsafe { self.ptr.byte_add(offset) }
94    }
95    fn ptr(&self) -> *mut u8 {
96        self.ptr
97    }
98    fn len(&self) -> usize {
99        self.len
100    }
101}
102
103impl CachedPfnOffset for Memory {
104    fn cached_offset(&self) -> &PfnOffset {
105        &self.pfn_offset
106    }
107}
108
109/// Errors that can occur during physical frame number operations.
110#[derive(Debug, thiserror::Error)]
111pub enum Error {
112    /// Error resolving virtual to physical address
113    #[error(transparent)]
114    LinuxPageMapError(#[from] LinuxPageMapError),
115    /// Memory region has no physical pages mapped
116    #[error("Empty PFN range")]
117    EmptyPfnRange,
118}
119
120/// Result type for memblock operations.
121pub type Result<T> = std::result::Result<T, Error>;
122
123/// Trait for types that can provide consecutive physical frame numbers.
124///
125/// Allows querying which physical address ranges a memory region occupies.
126pub trait GetConsecPfns {
127    /// Returns the consecutive PFN ranges for this memory region.
128    ///
129    /// # Errors
130    ///
131    /// Returns an error if PFN resolution fails.
132    fn consec_pfns(&self) -> Result<ConsecPfns>;
133
134    /// Logs the PFN ranges at the specified log level.
135    fn log_pfns(&self, level: log::Level) {
136        let pfns = match self.consec_pfns() {
137            Ok(pfns) => pfns,
138            Err(e) => {
139                warn!("Failed to get PFNs: {:?}", e);
140                return;
141            }
142        };
143        let pfns = pfns.format_pfns();
144        log!(level, "PFNs:\n{}", pfns);
145    }
146}
147
148impl GetConsecPfns for Memory {
149    fn consec_pfns(&self) -> Result<ConsecPfns> {
150        (self.ptr, self.len).consec_pfns()
151    }
152}
153
154impl<T> GetConsecPfns for (*mut T, usize) {
155    fn consec_pfns(&self) -> Result<ConsecPfns> {
156        trace!("Get consecutive PFNs for vaddr 0x{:x}", self.0 as u64);
157        let mut consecs = vec![];
158        // optimization: get PFN range
159        let mut resolver = LinuxPageMap::new()?;
160        let pfns = resolver.get_phys_range(VirtualMemoryArea::from((self.0 as u64, unsafe {
161            self.0.byte_add(self.1) as u64
162        })))?;
163        if pfns.is_empty() {
164            return Err(Error::EmptyPfnRange);
165        }
166        let mut phys_prev = pfns[0];
167        let mut range_start = phys_prev;
168        for phys in pfns.into_iter().skip(1) {
169            if phys != phys_prev + PAGE_SIZE {
170                consecs.push(range_start..phys_prev + PAGE_SIZE);
171                range_start = phys;
172            }
173            phys_prev = phys;
174        }
175        consecs.push(range_start..phys_prev + PAGE_SIZE);
176        trace!("PFN check done");
177        Ok(consecs)
178    }
179}
180
181/// Formats physical frame number ranges for display.
182pub trait FormatPfns {
183    /// Formats PFN ranges as a human-readable string.
184    fn format_pfns(&self) -> String;
185}
186
187/// Type alias for consecutive physical frame number ranges.
188type ConsecPfns = Vec<Range<PhysAddr>>;
189
190impl FormatPfns for ConsecPfns {
191    fn format_pfns(&self) -> String {
192        let mut pfns = String::from("");
193        for range in self {
194            pfns += &format!(
195                "{:p}..[{:04} KB]..{:p}\n",
196                range.start,
197                (range.end - range.start).as_usize() / 1024,
198                range.end
199            );
200        }
201        pfns
202    }
203}
204
205// TODO: we can move this alongside consec_alloc/mmap.rs, but we'll need some more refactoring before (self.pfn_offset is private).
206impl Memory {
207    #[cfg(false)]
208    pub fn pfn_align(mut self) -> Result<Vec<Memory>> {
209        let mut blocks = vec![];
210        let offset = match self.pfn_offset {
211            PfnOffset::Fixed(offset) => offset,
212            PfnOffset::Dynamic(ref offset) => {
213                let offset = offset.borrow();
214                match offset.into() {
215                    Some(offset) => offset
216                        .expect("PFN offset not determined yet. Call MemBlock::pfn_offset() before MemBlock::pfn_align()")
217                        .0
218                        .expect("Block is not consecutive"),
219                    None => bail!("PFN offset not determined yet. Call MemBlock::pfn_offset() before MemBlock::pfn_align()"),
220                }
221            }
222        };
223        if offset == 0 {
224            return Ok(vec![self]);
225        }
226        assert_eq!(self.len, MB(4).bytes());
227        let offset = self.len - offset * ROW_SIZE;
228        assert!(offset < MB(4).bytes(), "Offset {} >= 4MB", offset);
229        let ptr = self.addr(offset);
230        let len = self.len - offset;
231        let block = Memory::new(ptr, len); // TODO: add new trait for offsetting into MemBlock (byte_add returns *mut u8 now, but we need MemBlock here)
232        blocks.push(block);
233        self.len = offset;
234        blocks.push(self);
235
236        Ok(blocks)
237    }
238}