• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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