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_ptr;
15use base::pagesize;
16use base::FromRawDescriptor;
17use base::MappedRegion;
18use base::SafeDescriptor;
19use data_model::flexible_array_impl;
20use data_model::FlexibleArrayWrapper;
21
22use crate::udmabuf::UdmabufDriverTrait;
23use crate::udmabuf::UdmabufError;
24use crate::udmabuf::UdmabufResult;
25use crate::udmabuf_bindings::*;
26use crate::GuestAddress;
27use crate::GuestMemory;
28use crate::GuestMemoryError;
29
30const UDMABUF_IOCTL_BASE: c_uint = 0x75;
31
32ioctl_iow_nr!(UDMABUF_CREATE, UDMABUF_IOCTL_BASE, 0x42, udmabuf_create);
33ioctl_iow_nr!(
34    UDMABUF_CREATE_LIST,
35    UDMABUF_IOCTL_BASE,
36    0x43,
37    udmabuf_create_list
38);
39
40flexible_array_impl!(udmabuf_create_list, udmabuf_create_item, count, list);
41type 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// #}
54fn 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        .is_none_or(|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.
76pub struct UnixUdmabufDriver {
77    driver_fd: File,
78}
79
80impl UdmabufDriverTrait for 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
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)]
135mod tests {
136    use super::*;
137    use crate::GuestAddress;
138
139    #[test]
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]
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}