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            .open(path)
83            .map_err(UdmabufError::DriverOpenFailed)?;
84
85        Ok(UnixUdmabufDriver { driver_fd: fd })
86    }
87
88    fn create_udmabuf(
89        &self,
90        mem: &GuestMemory,
91        iovecs: &[(GuestAddress, usize)],
92    ) -> UdmabufResult<SafeDescriptor> {
93        let pgsize = pagesize();
94
95        let mut list =
96            udmabuf_create_list::<[udmabuf_create_item]>::new_box_zeroed_with_elems(iovecs.len())
97                .unwrap();
98        list.flags = UDMABUF_FLAGS_CLOEXEC;
99        list.count = iovecs.len().try_into().unwrap();
100        for (i, &(addr, len)) in iovecs.iter().enumerate() {
101            let offset = memory_offset(mem, addr, len as u64)?;
102
103            if offset as usize % pgsize != 0 || len % pgsize != 0 {
104                return Err(UdmabufError::NotPageAligned);
105            }
106
107            // `unwrap` can't panic if `memory_offset obove succeeds.
108            list.list[i].memfd = mem.shm_region(addr).unwrap().as_raw_descriptor() as u32;
109            list.list[i].__pad = 0;
110            list.list[i].offset = offset;
111            list.list[i].size = len as u64;
112        }
113
114        // SAFETY:
115        // Safe because we always allocate enough space for `udmabuf_create_list`.
116        let fd = unsafe { ioctl_with_ref(&self.driver_fd, UDMABUF_CREATE_LIST, &*list) };
117
118        if fd < 0 {
119            return Err(UdmabufError::DmabufCreationFail(IoError::last_os_error()));
120        }
121
122        // SAFETY:
123        // Safe because we validated the file exists.
124        Ok(unsafe { SafeDescriptor::from_raw_descriptor(fd) })
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131    use crate::GuestAddress;
132
133    #[test]
134    fn test_memory_offsets() {
135        let start_addr1 = GuestAddress(0x100);
136        let start_addr2 = GuestAddress(0x100 + pagesize() as u64);
137        let start_addr3 = GuestAddress(0x100 + 2 * pagesize() as u64);
138
139        let mem = GuestMemory::new(&[
140            (start_addr1, pagesize() as u64),
141            (start_addr2, pagesize() as u64),
142            (start_addr3, pagesize() as u64),
143        ])
144        .unwrap();
145
146        assert_eq!(memory_offset(&mem, GuestAddress(0x300), 1).unwrap(), 0x200);
147        assert_eq!(
148            memory_offset(&mem, GuestAddress(0x200 + pagesize() as u64), 1).unwrap(),
149            0x100 + pagesize() as u64,
150        );
151        assert_eq!(
152            memory_offset(
153                &mem,
154                GuestAddress(0x100 + pagesize() as u64),
155                pagesize() as u64
156            )
157            .unwrap(),
158            pagesize() as u64,
159        );
160        assert!(memory_offset(
161            &mem,
162            GuestAddress(0x100 + pagesize() as u64),
163            1 + pagesize() as u64
164        )
165        .is_err());
166    }
167
168    #[test]
169    fn test_udmabuf_create() {
170        let driver_result = UnixUdmabufDriver::new();
171
172        // Most kernels will not have udmabuf support.
173        if driver_result.is_err() {
174            return;
175        }
176
177        let driver = driver_result.unwrap();
178
179        let start_addr1 = GuestAddress(0x100);
180        let start_addr2 = GuestAddress(0x1100);
181        let start_addr3 = GuestAddress(0x2100);
182
183        let sg_list = [
184            (start_addr1, 0x1000),
185            (start_addr2, 0x1000),
186            (start_addr3, 0x1000),
187        ];
188
189        let mem = GuestMemory::new(&sg_list[..]).unwrap();
190
191        let mut udmabuf_create_list = vec![
192            (start_addr3, 0x1000),
193            (start_addr2, 0x1000),
194            (start_addr1, 0x1000),
195            (GuestAddress(0x4000), 0x1000),
196        ];
197
198        let result = driver.create_udmabuf(&mem, &udmabuf_create_list[..]);
199        assert_eq!(result.is_err(), true);
200
201        udmabuf_create_list.pop();
202
203        let _ = driver
204            .create_udmabuf(&mem, &udmabuf_create_list[..])
205            .unwrap();
206
207        udmabuf_create_list.pop();
208
209        // Multiple udmabufs with same memory backing is allowed.
210        let _ = driver
211            .create_udmabuf(&mem, &udmabuf_create_list[..])
212            .unwrap();
213    }
214}