• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (C) 2021 Red Hat, Inc. All rights reserved.
2 // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
3 
4 //! Kernel-based vhost-vdpa backend.
5 
6 use std::fs::{File, OpenOptions};
7 use std::io::Error as IOError;
8 use std::os::raw::{c_uchar, c_uint};
9 use std::os::unix::fs::OpenOptionsExt;
10 use std::os::unix::io::{AsRawFd, RawFd};
11 
12 use vm_memory::GuestAddressSpace;
13 use vmm_sys_util::eventfd::EventFd;
14 use vmm_sys_util::fam::*;
15 use vmm_sys_util::ioctl::{ioctl, ioctl_with_mut_ref, ioctl_with_ptr, ioctl_with_ref};
16 
17 use super::vhost_binding::*;
18 use super::{ioctl_result, Error, Result, VhostKernBackend, VhostKernFeatures};
19 use crate::vdpa::*;
20 use crate::{VhostAccess, VhostIotlbBackend, VhostIotlbMsg, VhostIotlbType, VringConfigData};
21 
22 // Implement the FamStruct trait for vhost_vdpa_config
23 generate_fam_struct_impl!(
24     vhost_vdpa_config,
25     c_uchar,
26     buf,
27     c_uint,
28     len,
29     c_uint::MAX as usize
30 );
31 
32 type VhostVdpaConfig = FamStructWrapper<vhost_vdpa_config>;
33 
34 /// Handle for running VHOST_VDPA ioctls.
35 pub struct VhostKernVdpa<AS: GuestAddressSpace> {
36     fd: File,
37     mem: AS,
38     backend_features_acked: u64,
39 }
40 
41 impl<AS: GuestAddressSpace> VhostKernVdpa<AS> {
42     /// Open a handle to a new VHOST-VDPA instance.
new(path: &str, mem: AS) -> Result<Self>43     pub fn new(path: &str, mem: AS) -> Result<Self> {
44         Ok(VhostKernVdpa {
45             fd: OpenOptions::new()
46                 .read(true)
47                 .write(true)
48                 .custom_flags(libc::O_CLOEXEC | libc::O_NONBLOCK)
49                 .open(path)
50                 .map_err(Error::VhostOpen)?,
51             mem,
52             backend_features_acked: 0,
53         })
54     }
55 
56     /// Create a `VhostKernVdpa` object with given content.
with(fd: File, mem: AS, backend_features_acked: u64) -> Self57     pub fn with(fd: File, mem: AS, backend_features_acked: u64) -> Self {
58         VhostKernVdpa {
59             fd,
60             mem,
61             backend_features_acked,
62         }
63     }
64 
65     /// Set the addresses for a given vring.
66     ///
67     /// # Arguments
68     /// * `queue_index` - Index of the queue to set addresses for.
69     /// * `config_data` - Vring config data, addresses of desc_table, avail_ring
70     ///     and used_ring are in the guest address space.
set_vring_addr(&self, queue_index: usize, config_data: &VringConfigData) -> Result<()>71     pub fn set_vring_addr(&self, queue_index: usize, config_data: &VringConfigData) -> Result<()> {
72         if !self.is_valid(config_data) {
73             return Err(Error::InvalidQueue);
74         }
75 
76         // vDPA backends expect IOVA (that can be mapped 1:1 with
77         // GPA when no IOMMU is involved).
78         let vring_addr = vhost_vring_addr {
79             index: queue_index as u32,
80             flags: config_data.flags,
81             desc_user_addr: config_data.desc_table_addr,
82             used_user_addr: config_data.used_ring_addr,
83             avail_user_addr: config_data.avail_ring_addr,
84             log_guest_addr: config_data.get_log_addr(),
85         };
86 
87         // SAFETY: This ioctl is called on a valid vhost-vdpa fd and has its
88         // return value checked.
89         let ret = unsafe { ioctl_with_ref(self, VHOST_SET_VRING_ADDR(), &vring_addr) };
90         ioctl_result(ret, ())
91     }
92 }
93 
94 impl<AS: GuestAddressSpace> VhostVdpa for VhostKernVdpa<AS> {
get_device_id(&self) -> Result<u32>95     fn get_device_id(&self) -> Result<u32> {
96         let mut device_id: u32 = 0;
97 
98         // SAFETY: This ioctl is called on a valid vhost-vdpa fd and has its
99         // return value checked.
100         let ret = unsafe { ioctl_with_mut_ref(self, VHOST_VDPA_GET_DEVICE_ID(), &mut device_id) };
101         ioctl_result(ret, device_id)
102     }
103 
get_status(&self) -> Result<u8>104     fn get_status(&self) -> Result<u8> {
105         let mut status: u8 = 0;
106 
107         // SAFETY: This ioctl is called on a valid vhost-vdpa fd and has its
108         // return value checked.
109         let ret = unsafe { ioctl_with_mut_ref(self, VHOST_VDPA_GET_STATUS(), &mut status) };
110         ioctl_result(ret, status)
111     }
112 
set_status(&self, status: u8) -> Result<()>113     fn set_status(&self, status: u8) -> Result<()> {
114         // SAFETY: This ioctl is called on a valid vhost-vdpa fd and has its
115         // return value checked.
116         let ret = unsafe { ioctl_with_ref(self, VHOST_VDPA_SET_STATUS(), &status) };
117         ioctl_result(ret, ())
118     }
119 
get_config(&self, offset: u32, buffer: &mut [u8]) -> Result<()>120     fn get_config(&self, offset: u32, buffer: &mut [u8]) -> Result<()> {
121         let mut config = VhostVdpaConfig::new(buffer.len())
122             .map_err(|_| Error::IoctlError(IOError::from_raw_os_error(libc::ENOMEM)))?;
123 
124         // SAFETY: We are not modifying the `len` field of the vhost-vdpa fam-struct
125         unsafe {
126             config.as_mut_fam_struct().off = offset;
127         }
128 
129         // SAFETY: This ioctl is called on a valid vhost-vdpa fd and has its
130         // return value checked.
131         let ret = unsafe {
132             ioctl_with_ptr(
133                 self,
134                 VHOST_VDPA_GET_CONFIG(),
135                 config.as_mut_fam_struct_ptr(),
136             )
137         };
138 
139         buffer.copy_from_slice(config.as_slice());
140 
141         ioctl_result(ret, ())
142     }
143 
set_config(&self, offset: u32, buffer: &[u8]) -> Result<()>144     fn set_config(&self, offset: u32, buffer: &[u8]) -> Result<()> {
145         let mut config = VhostVdpaConfig::new(buffer.len())
146             .map_err(|_| Error::IoctlError(IOError::from_raw_os_error(libc::ENOMEM)))?;
147 
148         // SAFETY: We are not modifying the `len` field of the vhost-vdpa fam-struct
149         unsafe {
150             config.as_mut_fam_struct().off = offset;
151         }
152         config.as_mut_slice().copy_from_slice(buffer);
153 
154         let ret =
155             // SAFETY: This ioctl is called on a valid vhost-vdpa fd and has its
156             // return value checked.
157             unsafe { ioctl_with_ptr(self, VHOST_VDPA_SET_CONFIG(), config.as_fam_struct_ptr()) };
158         ioctl_result(ret, ())
159     }
160 
set_vring_enable(&self, queue_index: usize, enabled: bool) -> Result<()>161     fn set_vring_enable(&self, queue_index: usize, enabled: bool) -> Result<()> {
162         let vring_state = vhost_vring_state {
163             index: queue_index as u32,
164             num: enabled as u32,
165         };
166 
167         // SAFETY: This ioctl is called on a valid vhost-vdpa fd and has its
168         // return value checked.
169         let ret = unsafe { ioctl_with_ref(self, VHOST_VDPA_SET_VRING_ENABLE(), &vring_state) };
170         ioctl_result(ret, ())
171     }
172 
get_vring_num(&self) -> Result<u16>173     fn get_vring_num(&self) -> Result<u16> {
174         let mut vring_num: u16 = 0;
175 
176         // SAFETY: This ioctl is called on a valid vhost-vdpa fd and has its
177         // return value checked.
178         let ret = unsafe { ioctl_with_mut_ref(self, VHOST_VDPA_GET_VRING_NUM(), &mut vring_num) };
179         ioctl_result(ret, vring_num)
180     }
181 
set_config_call(&self, fd: &EventFd) -> Result<()>182     fn set_config_call(&self, fd: &EventFd) -> Result<()> {
183         let event_fd: ::std::os::raw::c_int = fd.as_raw_fd();
184 
185         // SAFETY: This ioctl is called on a valid vhost-vdpa fd and has its
186         // return value checked.
187         let ret = unsafe { ioctl_with_ref(self, VHOST_VDPA_SET_CONFIG_CALL(), &event_fd) };
188         ioctl_result(ret, ())
189     }
190 
get_iova_range(&self) -> Result<VhostVdpaIovaRange>191     fn get_iova_range(&self) -> Result<VhostVdpaIovaRange> {
192         let mut low_iova_range = vhost_vdpa_iova_range { first: 0, last: 0 };
193 
194         let ret =
195             // SAFETY: This ioctl is called on a valid vhost-vdpa fd and has its
196             // return value checked.
197             unsafe { ioctl_with_mut_ref(self, VHOST_VDPA_GET_IOVA_RANGE(), &mut low_iova_range) };
198 
199         let iova_range = VhostVdpaIovaRange {
200             first: low_iova_range.first,
201             last: low_iova_range.last,
202         };
203 
204         ioctl_result(ret, iova_range)
205     }
206 
get_config_size(&self) -> Result<u32>207     fn get_config_size(&self) -> Result<u32> {
208         let mut config_size: u32 = 0;
209 
210         let ret =
211             // SAFETY: This ioctl is called on a valid vhost-vdpa fd and has its
212             // return value checked.
213             unsafe { ioctl_with_mut_ref(self, VHOST_VDPA_GET_CONFIG_SIZE(), &mut config_size) };
214         ioctl_result(ret, config_size)
215     }
216 
get_vqs_count(&self) -> Result<u32>217     fn get_vqs_count(&self) -> Result<u32> {
218         let mut vqs_count: u32 = 0;
219 
220         // SAFETY: This ioctl is called on a valid vhost-vdpa fd and has its
221         // return value checked.
222         let ret = unsafe { ioctl_with_mut_ref(self, VHOST_VDPA_GET_VQS_COUNT(), &mut vqs_count) };
223         ioctl_result(ret, vqs_count)
224     }
225 
get_group_num(&self) -> Result<u32>226     fn get_group_num(&self) -> Result<u32> {
227         let mut group_num: u32 = 0;
228 
229         // SAFETY: This ioctl is called on a valid vhost-vdpa fd and has its
230         // return value checked.
231         let ret = unsafe { ioctl_with_mut_ref(self, VHOST_VDPA_GET_GROUP_NUM(), &mut group_num) };
232         ioctl_result(ret, group_num)
233     }
234 
get_as_num(&self) -> Result<u32>235     fn get_as_num(&self) -> Result<u32> {
236         let mut as_num: u32 = 0;
237 
238         // SAFETY: This ioctl is called on a valid vhost-vdpa fd and has its
239         // return value checked.
240         let ret = unsafe { ioctl_with_mut_ref(self, VHOST_VDPA_GET_AS_NUM(), &mut as_num) };
241         ioctl_result(ret, as_num)
242     }
243 
get_vring_group(&self, queue_index: u32) -> Result<u32>244     fn get_vring_group(&self, queue_index: u32) -> Result<u32> {
245         let mut vring_state = vhost_vring_state {
246             index: queue_index,
247             ..Default::default()
248         };
249 
250         let ret =
251             // SAFETY: This ioctl is called on a valid vhost-vdpa fd and has its
252             // return value checked.
253             unsafe { ioctl_with_mut_ref(self, VHOST_VDPA_GET_VRING_GROUP(), &mut vring_state) };
254         ioctl_result(ret, vring_state.num)
255     }
256 
set_group_asid(&self, group_index: u32, asid: u32) -> Result<()>257     fn set_group_asid(&self, group_index: u32, asid: u32) -> Result<()> {
258         let vring_state = vhost_vring_state {
259             index: group_index,
260             num: asid,
261         };
262 
263         // SAFETY: This ioctl is called on a valid vhost-vdpa fd and has its
264         // return value checked.
265         let ret = unsafe { ioctl_with_ref(self, VHOST_VDPA_GET_VRING_GROUP(), &vring_state) };
266         ioctl_result(ret, ())
267     }
268 
suspend(&self) -> Result<()>269     fn suspend(&self) -> Result<()> {
270         // SAFETY: This ioctl is called on a valid vhost-vdpa fd and has its
271         // return value checked.
272         let ret = unsafe { ioctl(self, VHOST_VDPA_SUSPEND()) };
273         ioctl_result(ret, ())
274     }
275 
dma_map(&self, iova: u64, size: u64, vaddr: *const u8, readonly: bool) -> Result<()>276     fn dma_map(&self, iova: u64, size: u64, vaddr: *const u8, readonly: bool) -> Result<()> {
277         let iotlb = VhostIotlbMsg {
278             iova,
279             size,
280             userspace_addr: vaddr as u64,
281             perm: match readonly {
282                 true => VhostAccess::ReadOnly,
283                 false => VhostAccess::ReadWrite,
284             },
285             msg_type: VhostIotlbType::Update,
286         };
287 
288         self.send_iotlb_msg(&iotlb)
289     }
290 
dma_unmap(&self, iova: u64, size: u64) -> Result<()>291     fn dma_unmap(&self, iova: u64, size: u64) -> Result<()> {
292         let iotlb = VhostIotlbMsg {
293             iova,
294             size,
295             msg_type: VhostIotlbType::Invalidate,
296             ..Default::default()
297         };
298 
299         self.send_iotlb_msg(&iotlb)
300     }
301 }
302 
303 impl<AS: GuestAddressSpace> VhostKernBackend for VhostKernVdpa<AS> {
304     type AS = AS;
305 
mem(&self) -> &Self::AS306     fn mem(&self) -> &Self::AS {
307         &self.mem
308     }
309 
310     /// Check whether the ring configuration is valid.
is_valid(&self, config_data: &VringConfigData) -> bool311     fn is_valid(&self, config_data: &VringConfigData) -> bool {
312         let queue_size = config_data.queue_size;
313         if queue_size > config_data.queue_max_size
314             || queue_size == 0
315             || (queue_size & (queue_size - 1)) != 0
316         {
317             return false;
318         }
319 
320         // Since vDPA could be dealing with IOVAs corresponding to GVAs, it
321         // wouldn't make sense to go through the validation of the descriptor
322         // table address, available ring address and used ring address against
323         // the guest memory representation we have access to.
324 
325         config_data.is_log_addr_valid()
326     }
327 }
328 
329 impl<AS: GuestAddressSpace> AsRawFd for VhostKernVdpa<AS> {
as_raw_fd(&self) -> RawFd330     fn as_raw_fd(&self) -> RawFd {
331         self.fd.as_raw_fd()
332     }
333 }
334 
335 impl<AS: GuestAddressSpace> VhostKernFeatures for VhostKernVdpa<AS> {
get_backend_features_acked(&self) -> u64336     fn get_backend_features_acked(&self) -> u64 {
337         self.backend_features_acked
338     }
339 
set_backend_features_acked(&mut self, features: u64)340     fn set_backend_features_acked(&mut self, features: u64) {
341         self.backend_features_acked = features;
342     }
343 }
344 
345 #[cfg(test)]
346 mod tests {
347     const VHOST_VDPA_PATH: &str = "/dev/vhost-vdpa-0";
348 
349     use std::alloc::{alloc, dealloc, Layout};
350     use vm_memory::{GuestAddress, GuestMemory, GuestMemoryMmap};
351     use vmm_sys_util::eventfd::EventFd;
352 
353     use super::*;
354     use crate::{
355         VhostBackend, VhostUserDirtyLogRegion, VhostUserMemoryRegionInfo, VringConfigData,
356     };
357     use serial_test::serial;
358     use std::io::ErrorKind;
359 
360     /// macro to skip test if vhost-vdpa device path is not found.
361     ///
362     /// vDPA simulators are available since Linux 5.7, but the CI may have
363     /// an older kernel, so for now we skip the test if we don't find
364     /// the device.
365     macro_rules! unwrap_not_found {
366         ( $e:expr ) => {
367             match $e {
368                 Ok(v) => v,
369                 Err(error) => match error {
370                     Error::VhostOpen(ref e) if e.kind() == ErrorKind::NotFound => {
371                         println!("Err: {:?} SKIPPED", e);
372                         return;
373                     }
374                     e => panic!("Err: {:?}", e),
375                 },
376             }
377         };
378     }
379 
380     macro_rules! validate_ioctl {
381         ( $e:expr, $ref_value:expr ) => {
382             match $e {
383                 Ok(v) => assert_eq!(v, $ref_value),
384                 Err(error) => match error {
385                     Error::IoctlError(e) if e.raw_os_error().unwrap() == libc::ENOTTY => {
386                         println!("Err: {:?} SKIPPED", e);
387                     }
388                     e => panic!("Err: {:?}", e),
389                 },
390             }
391         };
392     }
393 
394     #[test]
395     #[serial]
test_vdpa_kern_new_device()396     fn test_vdpa_kern_new_device() {
397         let m = GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x10_0000)]).unwrap();
398         let vdpa = unwrap_not_found!(VhostKernVdpa::new(VHOST_VDPA_PATH, &m));
399 
400         assert!(vdpa.as_raw_fd() >= 0);
401         assert!(vdpa.mem().find_region(GuestAddress(0x100)).is_some());
402         assert!(vdpa.mem().find_region(GuestAddress(0x10_0000)).is_none());
403     }
404 
405     #[test]
406     #[serial]
test_vdpa_kern_is_valid()407     fn test_vdpa_kern_is_valid() {
408         let m = GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x10_0000)]).unwrap();
409         let vdpa = unwrap_not_found!(VhostKernVdpa::new(VHOST_VDPA_PATH, &m));
410 
411         let mut config = VringConfigData {
412             queue_max_size: 32,
413             queue_size: 32,
414             flags: 0,
415             desc_table_addr: 0x1000,
416             used_ring_addr: 0x2000,
417             avail_ring_addr: 0x3000,
418             log_addr: None,
419         };
420         assert!(vdpa.is_valid(&config));
421 
422         config.queue_size = 0;
423         assert!(!vdpa.is_valid(&config));
424         config.queue_size = 31;
425         assert!(!vdpa.is_valid(&config));
426         config.queue_size = 33;
427         assert!(!vdpa.is_valid(&config));
428     }
429 
430     #[test]
431     #[serial]
test_vdpa_kern_ioctls()432     fn test_vdpa_kern_ioctls() {
433         let m = GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x10_0000)]).unwrap();
434         let vdpa = unwrap_not_found!(VhostKernVdpa::new(VHOST_VDPA_PATH, &m));
435 
436         let features = vdpa.get_features().unwrap();
437         // VIRTIO_F_VERSION_1 (bit 32) should be set
438         assert_ne!(features & (1 << 32), 0);
439         vdpa.set_features(features).unwrap();
440 
441         vdpa.set_owner().unwrap();
442 
443         vdpa.set_mem_table(&[]).unwrap_err();
444 
445         let region = VhostUserMemoryRegionInfo::new(
446             0x0,
447             0x10_0000,
448             m.get_host_address(GuestAddress(0x0)).unwrap() as u64,
449             0,
450             -1,
451         );
452         vdpa.set_mem_table(&[region]).unwrap();
453 
454         let device_id = vdpa.get_device_id().unwrap();
455         assert!(device_id > 0);
456 
457         assert_eq!(vdpa.get_status().unwrap(), 0x0);
458         vdpa.set_status(0x1).unwrap();
459         assert_eq!(vdpa.get_status().unwrap(), 0x1);
460 
461         let mut vec = vec![0u8; 8];
462         vdpa.get_config(0, &mut vec).unwrap();
463         vdpa.set_config(0, &vec).unwrap();
464 
465         let eventfd = EventFd::new(0).unwrap();
466 
467         // set_log_base() and set_log_fd() are not supported by vhost-vdpa
468         vdpa.set_log_base(
469             0x4000,
470             Some(VhostUserDirtyLogRegion {
471                 mmap_size: 0x1000,
472                 mmap_offset: 0x10,
473                 mmap_handle: 1,
474             }),
475         )
476         .unwrap_err();
477         vdpa.set_log_base(0x4000, None).unwrap_err();
478         vdpa.set_log_fd(eventfd.as_raw_fd()).unwrap_err();
479 
480         let max_queues = vdpa.get_vring_num().unwrap();
481         vdpa.set_vring_num(0, max_queues + 1).unwrap_err();
482 
483         vdpa.set_vring_num(0, 32).unwrap();
484 
485         let config = VringConfigData {
486             queue_max_size: 32,
487             queue_size: 32,
488             flags: 0,
489             desc_table_addr: 0x1000,
490             used_ring_addr: 0x2000,
491             avail_ring_addr: 0x3000,
492             log_addr: None,
493         };
494         vdpa.set_vring_addr(0, &config).unwrap();
495         vdpa.set_vring_base(0, 1).unwrap();
496         vdpa.set_vring_call(0, &eventfd).unwrap();
497         vdpa.set_vring_kick(0, &eventfd).unwrap();
498         vdpa.set_vring_err(0, &eventfd).unwrap();
499 
500         vdpa.set_config_call(&eventfd).unwrap();
501 
502         let iova_range = vdpa.get_iova_range().unwrap();
503         // vDPA-block simulator returns [0, u64::MAX] range
504         assert_eq!(iova_range.first, 0);
505         assert_eq!(iova_range.last, u64::MAX);
506 
507         let (config_size, vqs_count, group_num, as_num, vring_group) = if device_id == 1 {
508             (24, 3, 2, 2, 0)
509         } else if device_id == 2 {
510             (60, 1, 1, 1, 0)
511         } else {
512             panic!("Unexpected device id {}", device_id)
513         };
514 
515         validate_ioctl!(vdpa.get_config_size(), config_size);
516         validate_ioctl!(vdpa.get_vqs_count(), vqs_count);
517         validate_ioctl!(vdpa.get_group_num(), group_num);
518         validate_ioctl!(vdpa.get_as_num(), as_num);
519         validate_ioctl!(vdpa.get_vring_group(0), vring_group);
520         validate_ioctl!(vdpa.set_group_asid(0, 12345), ());
521 
522         if vdpa.get_backend_features().unwrap() & (1 << VHOST_BACKEND_F_SUSPEND)
523             == (1 << VHOST_BACKEND_F_SUSPEND)
524         {
525             validate_ioctl!(vdpa.suspend(), ());
526         }
527 
528         assert_eq!(vdpa.get_vring_base(0).unwrap(), 1);
529 
530         vdpa.set_vring_enable(0, true).unwrap();
531         vdpa.set_vring_enable(0, false).unwrap();
532     }
533 
534     #[test]
535     #[serial]
test_vdpa_kern_dma()536     fn test_vdpa_kern_dma() {
537         let m = GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x10_0000)]).unwrap();
538         let mut vdpa = unwrap_not_found!(VhostKernVdpa::new(VHOST_VDPA_PATH, &m));
539 
540         let features = vdpa.get_features().unwrap();
541         // VIRTIO_F_VERSION_1 (bit 32) should be set
542         assert_ne!(features & (1 << 32), 0);
543         vdpa.set_features(features).unwrap();
544 
545         let backend_features = vdpa.get_backend_features().unwrap();
546         assert_ne!(backend_features & (1 << VHOST_BACKEND_F_IOTLB_MSG_V2), 0);
547         vdpa.set_backend_features(backend_features).unwrap();
548 
549         vdpa.set_owner().unwrap();
550 
551         vdpa.dma_map(0xFFFF_0000, 0xFFFF, std::ptr::null::<u8>(), false)
552             .unwrap_err();
553 
554         let layout = Layout::from_size_align(0xFFFF, 1).unwrap();
555 
556         // SAFETY: Safe because layout has non-zero size.
557         let ptr = unsafe { alloc(layout) };
558 
559         vdpa.dma_map(0xFFFF_0000, 0xFFFF, ptr, false).unwrap();
560         vdpa.dma_unmap(0xFFFF_0000, 0xFFFF).unwrap();
561 
562         // SAFETY: Safe because `ptr` is allocated with the same allocator
563         // using the same `layout`.
564         unsafe { dealloc(ptr, layout) };
565     }
566 }
567