user_driver/
lockmem.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4#![cfg(target_os = "linux")]
5
6use crate::memory::MappedDmaTarget;
7use anyhow::Context;
8use inspect::Inspect;
9use std::ffi::c_void;
10use std::fs::File;
11use std::io::Read;
12use std::io::Seek;
13use std::io::SeekFrom;
14use zerocopy::IntoBytes;
15
16const PAGE_SIZE: usize = 4096;
17
18pub struct LockedMemory {
19    mapping: Mapping,
20    pfns: Vec<u64>,
21}
22
23// SAFETY: The result of an mmap is safe to share amongst threads.
24unsafe impl Send for Mapping {}
25// SAFETY: The result of an mmap is safe to share amongst threads.
26unsafe impl Sync for Mapping {}
27
28struct Mapping {
29    addr: *mut c_void,
30    len: usize,
31}
32
33impl Mapping {
34    fn new(len: usize) -> std::io::Result<Self> {
35        // SAFETY: No file descriptor or address is being passed.
36        // The result is being validated.
37        let addr = unsafe {
38            libc::mmap(
39                std::ptr::null_mut(),
40                len,
41                libc::PROT_READ | libc::PROT_WRITE,
42                libc::MAP_PRIVATE | libc::MAP_ANONYMOUS | libc::MAP_LOCKED,
43                -1,
44                0,
45            )
46        };
47        if addr == libc::MAP_FAILED {
48            return Err(std::io::Error::last_os_error());
49        }
50
51        Ok(Self { addr, len })
52    }
53
54    fn lock(&self) -> std::io::Result<()> {
55        // SAFETY: self contains a valid mmap result.
56        if unsafe { libc::mlock(self.addr, self.len) } < 0 {
57            return Err(std::io::Error::last_os_error());
58        }
59        Ok(())
60    }
61
62    fn pages(&self) -> anyhow::Result<Vec<u64>> {
63        let mut pagemap = File::open("/proc/self/pagemap").context("failed to open pagemap")?;
64        pagemap
65            .seek(SeekFrom::Start((8 * self.addr as usize / PAGE_SIZE) as u64))
66            .context("failed to seek")?;
67        let n = self.len / PAGE_SIZE;
68        let mut pfns = vec![0u64; n];
69        pagemap
70            .read(pfns.as_mut_bytes())
71            .context("failed to read from pagemap")?;
72        for pfn in &mut pfns {
73            if *pfn & (1 << 63) == 0 {
74                anyhow::bail!("page not present in RAM");
75            }
76            *pfn &= 0x3f_ffff_ffff_ffff;
77        }
78        Ok(pfns)
79    }
80}
81
82impl Drop for Mapping {
83    fn drop(&mut self) {
84        // SAFETY: self contains a valid mmap result.
85        if unsafe { libc::munmap(self.addr, self.len) } < 0 {
86            panic!("{:?}", std::io::Error::last_os_error());
87        }
88    }
89}
90
91impl LockedMemory {
92    pub fn new(len: usize) -> anyhow::Result<Self> {
93        if len % PAGE_SIZE != 0 {
94            anyhow::bail!("not a page-size multiple");
95        }
96        let mapping = Mapping::new(len).context("failed to create mapping")?;
97        mapping.lock().context("failed to lock mapping")?;
98        let pages = mapping.pages()?;
99        Ok(Self {
100            mapping,
101            pfns: pages,
102        })
103    }
104}
105
106// SAFETY: The stored mapping is valid for the lifetime of the LockedMemory.
107// It is only unmapped on drop.
108unsafe impl MappedDmaTarget for LockedMemory {
109    fn base(&self) -> *const u8 {
110        self.mapping.addr.cast()
111    }
112
113    fn len(&self) -> usize {
114        self.mapping.len
115    }
116
117    fn pfns(&self) -> &[u64] {
118        &self.pfns
119    }
120
121    fn pfn_bias(&self) -> u64 {
122        0
123    }
124}
125
126#[derive(Clone, Inspect)]
127pub struct LockedMemorySpawner;
128
129impl crate::DmaClient for LockedMemorySpawner {
130    fn allocate_dma_buffer(&self, len: usize) -> anyhow::Result<crate::memory::MemoryBlock> {
131        Ok(crate::memory::MemoryBlock::new(LockedMemory::new(len)?))
132    }
133
134    fn attach_pending_buffers(&self) -> anyhow::Result<Vec<crate::memory::MemoryBlock>> {
135        anyhow::bail!("restore not supported for lockmem")
136    }
137}