vm_memory/udmabuf/sys/
linux.rs

1// Copyright 2021 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#![allow(dead_code)]
6
7use std::fs::File;
8use std::fs::OpenOptions;
9use std::io::Error as IoError;
10use std::os::raw::c_uint;
11use std::path::Path;
12
13use base::ioctl_iow_nr;
14use base::ioctl_with_ref;
15use base::pagesize;
16use base::FromRawDescriptor;
17use base::MappedRegion;
18use base::SafeDescriptor;
19use zerocopy::FromZeros;
20
21use crate::udmabuf::UdmabufDriverTrait;
22use crate::udmabuf::UdmabufError;
23use crate::udmabuf::UdmabufResult;
24use crate::udmabuf_bindings::*;
25use crate::GuestAddress;
26use crate::GuestMemory;
27use crate::GuestMemoryError;
28
29const UDMABUF_IOCTL_BASE: c_uint = 0x75;
30
31ioctl_iow_nr!(UDMABUF_CREATE, UDMABUF_IOCTL_BASE, 0x42, udmabuf_create);
32ioctl_iow_nr!(
33    UDMABUF_CREATE_LIST,
34    UDMABUF_IOCTL_BASE,
35    0x43,
36    udmabuf_create_list
37);
38
39// Returns absolute offset within the memory corresponding to a particular guest address.
40// This offset is not relative to a particular mapping.
41
42// # Examples
43//
44// # fn test_memory_offsets() {
45// #    let start_addr1 = GuestAddress(0x100)
46// #    let start_addr2 = GuestAddress(0x1100);
47// #    let mem = GuestMemory::new(&vec![(start_addr1, 0x1000),(start_addr2, 0x1000)])?;
48// #    assert_eq!(memory_offset(&mem, GuestAddress(0x1100), 0x1000).unwrap(),0x1000);
49// #}
50fn memory_offset(mem: &GuestMemory, guest_addr: GuestAddress, len: u64) -> UdmabufResult<u64> {
51    let (mapping, map_offset, memfd_offset) = mem
52        .find_region(guest_addr)
53        .map_err(UdmabufError::InvalidOffset)?;
54    let map_offset = map_offset as u64;
55    if map_offset
56        .checked_add(len)
57        .is_none_or(|a| a > mapping.size() as u64)
58    {
59        return Err(UdmabufError::InvalidOffset(
60            GuestMemoryError::InvalidGuestAddress(guest_addr),
61        ));
62    }
63
64    Ok(memfd_offset + map_offset)
65}
66
67/// A convenience wrapper for the Linux kernel's udmabuf driver.
68///
69/// udmabuf is a kernel driver that turns memfd pages into dmabufs. It can be used for
70/// zero-copy buffer sharing between the guest and host when guest memory is backed by
71/// memfd pages.
72pub struct UnixUdmabufDriver {
73    driver_fd: File,
74}
75
76impl UdmabufDriverTrait for UnixUdmabufDriver {
77    fn new() -> UdmabufResult<UnixUdmabufDriver> {
78        const UDMABUF_PATH: &str = "/dev/udmabuf";
79        let path = Path::new(UDMABUF_PATH);
80        let fd = OpenOptions::new()
81            .read(true)
82            .write(true)
83            .open(path)
84            .map_err(UdmabufError::DriverOpenFailed)?;
85
86        Ok(UnixUdmabufDriver { driver_fd: fd })
87    }
88
89    fn create_udmabuf(
90        &self,
91        mem: &GuestMemory,
92        iovecs: &[(GuestAddress, usize)],
93    ) -> UdmabufResult<SafeDescriptor> {
94        let pgsize = pagesize();
95
96        let mut list =
97            udmabuf_create_list::<[udmabuf_create_item]>::new_box_zeroed_with_elems(iovecs.len())
98                .unwrap();
99        list.flags = UDMABUF_FLAGS_CLOEXEC;
100        list.count = iovecs.len().try_into().unwrap();
101        for (i, &(addr, len)) in iovecs.iter().enumerate() {
102            let offset = memory_offset(mem, addr, len as u64)?;
103
104            if offset as usize % pgsize != 0 || len % pgsize != 0 {
105                return Err(UdmabufError::NotPageAligned);
106            }
107
108            // `unwrap` can't panic if `memory_offset obove succeeds.
109            list.list[i].memfd = mem.shm_region(addr).unwrap().as_raw_descriptor() as u32;
110            list.list[i].__pad = 0;
111            list.list[i].offset = offset;
112            list.list[i].size = len as u64;
113        }
114
115        // SAFETY:
116        // Safe because we always allocate enough space for `udmabuf_create_list`.
117        let fd = unsafe { ioctl_with_ref(&self.driver_fd, UDMABUF_CREATE_LIST, &*list) };
118
119        if fd < 0 {
120            return Err(UdmabufError::DmabufCreationFail(IoError::last_os_error()));
121        }
122
123        // SAFETY:
124        // Safe because we validated the file exists.
125        Ok(unsafe { SafeDescriptor::from_raw_descriptor(fd) })
126    }
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132    use crate::GuestAddress;
133
134    #[test]
135    fn test_memory_offsets() {
136        let start_addr1 = GuestAddress(0x100);
137        let start_addr2 = GuestAddress(0x100 + pagesize() as u64);
138        let start_addr3 = GuestAddress(0x100 + 2 * pagesize() as u64);
139
140        let mem = GuestMemory::new(&[
141            (start_addr1, pagesize() as u64),
142            (start_addr2, pagesize() as u64),
143            (start_addr3, pagesize() as u64),
144        ])
145        .unwrap();
146
147        assert_eq!(memory_offset(&mem, GuestAddress(0x300), 1).unwrap(), 0x200);
148        assert_eq!(
149            memory_offset(&mem, GuestAddress(0x200 + pagesize() as u64), 1).unwrap(),
150            0x100 + pagesize() as u64,
151        );
152        assert_eq!(
153            memory_offset(
154                &mem,
155                GuestAddress(0x100 + pagesize() as u64),
156                pagesize() as u64
157            )
158            .unwrap(),
159            pagesize() as u64,
160        );
161        assert!(memory_offset(
162            &mem,
163            GuestAddress(0x100 + pagesize() as u64),
164            1 + pagesize() as u64
165        )
166        .is_err());
167    }
168
169    #[test]
170    fn test_udmabuf_create() {
171        let driver_result = UnixUdmabufDriver::new();
172
173        // Most kernels will not have udmabuf support.
174        if driver_result.is_err() {
175            return;
176        }
177
178        let driver = driver_result.unwrap();
179
180        let start_addr1 = GuestAddress(0x100);
181        let start_addr2 = GuestAddress(0x1100);
182        let start_addr3 = GuestAddress(0x2100);
183
184        let sg_list = [
185            (start_addr1, 0x1000),
186            (start_addr2, 0x1000),
187            (start_addr3, 0x1000),
188        ];
189
190        let mem = GuestMemory::new(&sg_list[..]).unwrap();
191
192        let mut udmabuf_create_list = vec![
193            (start_addr3, 0x1000),
194            (start_addr2, 0x1000),
195            (start_addr1, 0x1000),
196            (GuestAddress(0x4000), 0x1000),
197        ];
198
199        let result = driver.create_udmabuf(&mem, &udmabuf_create_list[..]);
200        assert_eq!(result.is_err(), true);
201
202        udmabuf_create_list.pop();
203
204        let _ = driver
205            .create_udmabuf(&mem, &udmabuf_create_list[..])
206            .unwrap();
207
208        udmabuf_create_list.pop();
209
210        // Multiple udmabufs with same memory backing is allowed.
211        let _ = driver
212            .create_udmabuf(&mem, &udmabuf_create_list[..])
213            .unwrap();
214    }
215}