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