1 // Copyright 2021 The Chromium OS Authors. All rights reservsize.
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, OpenOptions};
8 use std::os::raw::c_uint;
9
10 use std::path::Path;
11 use std::{fmt, io};
12
13 use base::{
14 ioctl_iow_nr, ioctl_with_ptr, pagesize, AsRawDescriptor, FromRawDescriptor, MappedRegion,
15 SafeDescriptor,
16 };
17
18 use data_model::{FlexibleArray, FlexibleArrayWrapper};
19
20 use rutabaga_gfx::{RutabagaHandle, RUTABAGA_MEM_HANDLE_TYPE_DMABUF};
21
22 use super::udmabuf_bindings::*;
23
24 use vm_memory::{GuestAddress, GuestMemory, GuestMemoryError};
25
26 const UDMABUF_IOCTL_BASE: c_uint = 0x75;
27
28 ioctl_iow_nr!(UDMABUF_CREATE, UDMABUF_IOCTL_BASE, 0x42, udmabuf_create);
29 ioctl_iow_nr!(
30 UDMABUF_CREATE_LIST,
31 UDMABUF_IOCTL_BASE,
32 0x43,
33 udmabuf_create_list
34 );
35
36 // It's possible to make the flexible array trait implementation a macro one day...
37 impl FlexibleArray<udmabuf_create_item> for udmabuf_create_list {
set_len(&mut self, len: usize)38 fn set_len(&mut self, len: usize) {
39 self.count = len as u32;
40 }
41
get_len(&self) -> usize42 fn get_len(&self) -> usize {
43 self.count as usize
44 }
45
get_slice(&self, len: usize) -> &[udmabuf_create_item]46 fn get_slice(&self, len: usize) -> &[udmabuf_create_item] {
47 unsafe { self.list.as_slice(len) }
48 }
49
get_mut_slice(&mut self, len: usize) -> &mut [udmabuf_create_item]50 fn get_mut_slice(&mut self, len: usize) -> &mut [udmabuf_create_item] {
51 unsafe { self.list.as_mut_slice(len) }
52 }
53 }
54
55 type UdmabufCreateList = FlexibleArrayWrapper<udmabuf_create_list, udmabuf_create_item>;
56
57 #[derive(Debug)]
58 pub enum UdmabufError {
59 DriverOpenFailed(io::Error),
60 NotPageAligned,
61 InvalidOffset(GuestMemoryError),
62 DmabufCreationFail(io::Error),
63 }
64
65 impl fmt::Display for UdmabufError {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result66 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
67 use self::UdmabufError::*;
68 match self {
69 DriverOpenFailed(e) => write!(f, "failed to open udmabuf driver: {:?}", e),
70 NotPageAligned => write!(f, "All guest addresses must aligned to 4KiB"),
71 InvalidOffset(e) => write!(f, "failed to get region offset: {:?}", e),
72 DmabufCreationFail(e) => write!(f, "failed to create buffer: {:?}", e),
73 }
74 }
75 }
76
77 /// The result of an operation in this file.
78 pub type UdmabufResult<T> = std::result::Result<T, UdmabufError>;
79
80 // Returns absolute offset within the memory corresponding to a particular guest address.
81 // This offset is not relative to a particular mapping.
82
83 // # Examples
84 //
85 // # fn test_memory_offsets() {
86 // # let start_addr1 = GuestAddress(0x100)
87 // # let start_addr2 = GuestAddress(0x1100);
88 // # let mem = GuestMemory::new(&vec![(start_addr1, 0x1000),(start_addr2, 0x1000)])?;
89 // # assert_eq!(memory_offset(&mem, GuestAddress(0x1100), 0x1000).unwrap(),0x1000);
90 // #}
memory_offset(mem: &GuestMemory, guest_addr: GuestAddress, len: u64) -> UdmabufResult<u64>91 fn memory_offset(mem: &GuestMemory, guest_addr: GuestAddress, len: u64) -> UdmabufResult<u64> {
92 mem.do_in_region(guest_addr, move |mapping, map_offset, memfd_offset| {
93 let map_offset = map_offset as u64;
94 if map_offset
95 .checked_add(len)
96 .map_or(true, |a| a > mapping.size() as u64)
97 {
98 return Err(GuestMemoryError::InvalidGuestAddress(guest_addr));
99 }
100
101 return Ok(memfd_offset + map_offset);
102 })
103 .map_err(UdmabufError::InvalidOffset)
104 }
105
106 /// A convenience wrapper for the Linux kernel's udmabuf driver.
107 ///
108 /// udmabuf is a kernel driver that turns memfd pages into dmabufs. It can be used for
109 /// zero-copy buffer sharing between the guest and host when guest memory is backed by
110 /// memfd pages.
111 pub struct UdmabufDriver {
112 driver_fd: File,
113 }
114
115 impl UdmabufDriver {
116 /// Opens the udmabuf device on success.
new() -> UdmabufResult<UdmabufDriver>117 pub fn new() -> UdmabufResult<UdmabufDriver> {
118 const UDMABUF_PATH: &str = "/dev/udmabuf";
119 let path = Path::new(UDMABUF_PATH);
120 let fd = OpenOptions::new()
121 .read(true)
122 .write(true)
123 .open(path)
124 .map_err(UdmabufError::DriverOpenFailed)?;
125
126 Ok(UdmabufDriver { driver_fd: fd })
127 }
128
129 /// Creates a dma-buf fd for the given scatter-gather list of guest memory pages (`iovecs`).
create_udmabuf( &self, mem: &GuestMemory, iovecs: &[(GuestAddress, usize)], ) -> UdmabufResult<RutabagaHandle>130 pub fn create_udmabuf(
131 &self,
132 mem: &GuestMemory,
133 iovecs: &[(GuestAddress, usize)],
134 ) -> UdmabufResult<RutabagaHandle> {
135 let pgsize = pagesize();
136
137 let mut list = UdmabufCreateList::new(iovecs.len() as usize);
138 let mut items = list.mut_entries_slice();
139 for (i, &(addr, len)) in iovecs.iter().enumerate() {
140 let offset = memory_offset(mem, addr, len as u64)?;
141
142 if offset as usize % pgsize != 0 || len % pgsize != 0 {
143 return Err(UdmabufError::NotPageAligned);
144 }
145
146 // `unwrap` can't panic if `memory_offset obove succeeds.
147 items[i].memfd = mem.shm_region(addr).unwrap().as_raw_descriptor() as u32;
148 items[i].__pad = 0;
149 items[i].offset = offset;
150 items[i].size = len as u64;
151 }
152
153 // Safe because we always allocate enough space for `udmabuf_create_list`.
154 let fd = unsafe {
155 let create_list = list.as_mut_ptr();
156 (*create_list).flags = UDMABUF_FLAGS_CLOEXEC;
157 ioctl_with_ptr(&self.driver_fd, UDMABUF_CREATE_LIST(), create_list)
158 };
159
160 if fd < 0 {
161 return Err(UdmabufError::DmabufCreationFail(io::Error::last_os_error()));
162 }
163
164 // Safe because we validated the file exists.
165 let os_handle = unsafe { SafeDescriptor::from_raw_descriptor(fd) };
166 Ok(RutabagaHandle {
167 os_handle,
168 handle_type: RUTABAGA_MEM_HANDLE_TYPE_DMABUF,
169 })
170 }
171 }
172
173 #[cfg(test)]
174 mod tests {
175 use super::*;
176 use base::kernel_has_memfd;
177 use vm_memory::GuestAddress;
178
179 #[test]
test_memory_offsets()180 fn test_memory_offsets() {
181 if !kernel_has_memfd() {
182 return;
183 }
184
185 let start_addr1 = GuestAddress(0x100);
186 let start_addr2 = GuestAddress(0x1100);
187 let start_addr3 = GuestAddress(0x2100);
188
189 let mem = GuestMemory::new(&vec![
190 (start_addr1, 0x1000),
191 (start_addr2, 0x1000),
192 (start_addr3, 0x1000),
193 ])
194 .unwrap();
195
196 assert_eq!(memory_offset(&mem, GuestAddress(0x300), 1).unwrap(), 0x200);
197 assert_eq!(
198 memory_offset(&mem, GuestAddress(0x1200), 1).unwrap(),
199 0x1100
200 );
201 assert_eq!(
202 memory_offset(&mem, GuestAddress(0x1100), 0x1000).unwrap(),
203 0x1000
204 );
205 assert!(memory_offset(&mem, GuestAddress(0x1100), 0x1001).is_err());
206 }
207
208 #[test]
test_udmabuf_create()209 fn test_udmabuf_create() {
210 if !kernel_has_memfd() {
211 return;
212 }
213
214 let driver_result = UdmabufDriver::new();
215
216 // Most kernels will not have udmabuf support.
217 if driver_result.is_err() {
218 return;
219 }
220
221 let driver = driver_result.unwrap();
222
223 let start_addr1 = GuestAddress(0x100);
224 let start_addr2 = GuestAddress(0x1100);
225 let start_addr3 = GuestAddress(0x2100);
226
227 let sg_list = vec![
228 (start_addr1, 0x1000),
229 (start_addr2, 0x1000),
230 (start_addr3, 0x1000),
231 ];
232
233 let mem = GuestMemory::new(&sg_list[..]).unwrap();
234
235 let mut udmabuf_create_list = vec![
236 (start_addr3, 0x1000 as usize),
237 (start_addr2, 0x1000 as usize),
238 (start_addr1, 0x1000 as usize),
239 (GuestAddress(0x4000), 0x1000 as usize),
240 ];
241
242 let result = driver.create_udmabuf(&mem, &udmabuf_create_list[..]);
243 assert_eq!(result.is_err(), true);
244
245 udmabuf_create_list.pop();
246
247 let rutabaga_handle1 = driver
248 .create_udmabuf(&mem, &udmabuf_create_list[..])
249 .unwrap();
250 assert_eq!(
251 rutabaga_handle1.handle_type,
252 RUTABAGA_MEM_HANDLE_TYPE_DMABUF
253 );
254
255 udmabuf_create_list.pop();
256
257 // Multiple udmabufs with same memory backing is allowed.
258 let rutabaga_handle2 = driver
259 .create_udmabuf(&mem, &udmabuf_create_list[..])
260 .unwrap();
261 assert_eq!(
262 rutabaga_handle2.handle_type,
263 RUTABAGA_MEM_HANDLE_TYPE_DMABUF
264 );
265 }
266 }
267