|
| 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::os::raw::c_int; |
| 8 | +use std::os::unix::fs::OpenOptionsExt; |
| 9 | +use std::os::unix::io::{AsRawFd, RawFd}; |
| 10 | + |
| 11 | +use vm_memory::GuestAddressSpace; |
| 12 | +use vmm_sys_util::eventfd::EventFd; |
| 13 | +use vmm_sys_util::ioctl::{ioctl_with_mut_ref, ioctl_with_ptr, ioctl_with_ref}; |
| 14 | + |
| 15 | +use std::alloc::{alloc, dealloc, Layout}; |
| 16 | +use std::mem; |
| 17 | + |
| 18 | +use super::vhost_binding::*; |
| 19 | +use super::{ioctl_result, Error, Result, VhostKernBackend}; |
| 20 | +use crate::vdpa::*; |
| 21 | + |
| 22 | +/// Handle for running VHOST_VDPA ioctls. |
| 23 | +pub struct VhostKernVdpa<AS: GuestAddressSpace> { |
| 24 | + fd: File, |
| 25 | + mem: AS, |
| 26 | +} |
| 27 | + |
| 28 | +impl<AS: GuestAddressSpace> VhostKernVdpa<AS> { |
| 29 | + /// Open a handle to a new VHOST-VDPA instance. |
| 30 | + pub fn new(path: &str, mem: AS) -> Result<Self> { |
| 31 | + Ok(VhostKernVdpa { |
| 32 | + fd: OpenOptions::new() |
| 33 | + .read(true) |
| 34 | + .write(true) |
| 35 | + .custom_flags(libc::O_CLOEXEC | libc::O_NONBLOCK) |
| 36 | + .open(path) |
| 37 | + .map_err(Error::VhostOpen)?, |
| 38 | + mem, |
| 39 | + }) |
| 40 | + } |
| 41 | +} |
| 42 | + |
| 43 | +impl<AS: GuestAddressSpace> VhostVdpa for VhostKernVdpa<AS> { |
| 44 | + fn get_device_id(&self) -> Result<u32> { |
| 45 | + let mut device_id: u32 = 0; |
| 46 | + let ret = unsafe { ioctl_with_mut_ref(self, VHOST_VDPA_GET_DEVICE_ID(), &mut device_id) }; |
| 47 | + ioctl_result(ret, device_id) |
| 48 | + } |
| 49 | + |
| 50 | + fn get_status(&self) -> Result<u8> { |
| 51 | + let mut status: u8 = 0; |
| 52 | + let ret = unsafe { ioctl_with_mut_ref(self, VHOST_VDPA_GET_STATUS(), &mut status) }; |
| 53 | + ioctl_result(ret, status) |
| 54 | + } |
| 55 | + |
| 56 | + fn set_status(&self, status: u8) -> Result<()> { |
| 57 | + let ret = unsafe { ioctl_with_ref(self, VHOST_VDPA_SET_STATUS(), &status) }; |
| 58 | + ioctl_result(ret, ()) |
| 59 | + } |
| 60 | + |
| 61 | + fn get_config(&self, offset: u32, buffer: &mut [u8]) -> Result<()> { |
| 62 | + let buffer_len = buffer.len(); |
| 63 | + let layout = |
| 64 | + Layout::from_size_align(mem::size_of::<vhost_vdpa_config>() + buffer_len, 1).unwrap(); |
| 65 | + let ret: c_int; |
| 66 | + |
| 67 | + unsafe { |
| 68 | + let ptr = alloc(layout); |
| 69 | + let config = ptr as *mut vhost_vdpa_config; |
| 70 | + (*config).off = offset; |
| 71 | + (*config).len = buffer_len as u32; |
| 72 | + |
| 73 | + ret = ioctl_with_ptr(self, VHOST_VDPA_GET_CONFIG(), ptr); |
| 74 | + |
| 75 | + buffer.copy_from_slice((*config).buf.as_slice(buffer_len)); |
| 76 | + |
| 77 | + dealloc(ptr, layout); |
| 78 | + }; |
| 79 | + |
| 80 | + ioctl_result(ret, ()) |
| 81 | + } |
| 82 | + |
| 83 | + fn set_config(&self, offset: u32, buffer: &[u8]) -> Result<()> { |
| 84 | + let buffer_len = buffer.len(); |
| 85 | + let layout = |
| 86 | + Layout::from_size_align(mem::size_of::<vhost_vdpa_config>() + buffer_len, 1).unwrap(); |
| 87 | + let ret: c_int; |
| 88 | + |
| 89 | + unsafe { |
| 90 | + let ptr = alloc(layout); |
| 91 | + let config = ptr as *mut vhost_vdpa_config; |
| 92 | + (*config).off = offset; |
| 93 | + (*config).len = buffer_len as u32; |
| 94 | + |
| 95 | + (*config) |
| 96 | + .buf |
| 97 | + .as_mut_slice(buffer_len) |
| 98 | + .copy_from_slice(&buffer); |
| 99 | + |
| 100 | + ret = ioctl_with_ptr(self, VHOST_VDPA_SET_CONFIG(), ptr); |
| 101 | + |
| 102 | + dealloc(ptr, layout); |
| 103 | + }; |
| 104 | + |
| 105 | + ioctl_result(ret, ()) |
| 106 | + } |
| 107 | + |
| 108 | + fn set_vring_enable(&self, queue_index: usize, enabled: bool) -> Result<()> { |
| 109 | + let vring_state = vhost_vring_state { |
| 110 | + index: queue_index as u32, |
| 111 | + num: enabled as u32, |
| 112 | + }; |
| 113 | + |
| 114 | + let ret = unsafe { ioctl_with_ref(self, VHOST_VDPA_SET_VRING_ENABLE(), &vring_state) }; |
| 115 | + ioctl_result(ret, ()) |
| 116 | + } |
| 117 | + |
| 118 | + fn get_vring_num(&self) -> Result<u16> { |
| 119 | + let mut vring_num: u16 = 0; |
| 120 | + let ret = unsafe { ioctl_with_mut_ref(self, VHOST_VDPA_GET_VRING_NUM(), &mut vring_num) }; |
| 121 | + ioctl_result(ret, vring_num) |
| 122 | + } |
| 123 | + |
| 124 | + fn set_config_call(&self, fd: &EventFd) -> Result<()> { |
| 125 | + let event_fd: ::std::os::raw::c_int = fd.as_raw_fd(); |
| 126 | + let ret = unsafe { ioctl_with_ref(self, VHOST_VDPA_SET_CONFIG_CALL(), &event_fd) }; |
| 127 | + ioctl_result(ret, ()) |
| 128 | + } |
| 129 | + |
| 130 | + fn get_iova_range(&self) -> Result<VhostVdpaIovaRange> { |
| 131 | + let mut low_iova_range = vhost_vdpa_iova_range { first: 0, last: 0 }; |
| 132 | + |
| 133 | + let ret = |
| 134 | + unsafe { ioctl_with_mut_ref(self, VHOST_VDPA_GET_VRING_NUM(), &mut low_iova_range) }; |
| 135 | + |
| 136 | + let iova_range = VhostVdpaIovaRange { |
| 137 | + first: low_iova_range.first, |
| 138 | + last: low_iova_range.last, |
| 139 | + }; |
| 140 | + |
| 141 | + ioctl_result(ret, iova_range) |
| 142 | + } |
| 143 | +} |
| 144 | + |
| 145 | +impl<AS: GuestAddressSpace> VhostKernBackend for VhostKernVdpa<AS> { |
| 146 | + type AS = AS; |
| 147 | + |
| 148 | + fn mem(&self) -> &Self::AS { |
| 149 | + &self.mem |
| 150 | + } |
| 151 | +} |
| 152 | + |
| 153 | +impl<AS: GuestAddressSpace> AsRawFd for VhostKernVdpa<AS> { |
| 154 | + fn as_raw_fd(&self) -> RawFd { |
| 155 | + self.fd.as_raw_fd() |
| 156 | + } |
| 157 | +} |
| 158 | + |
| 159 | +#[cfg(test)] |
| 160 | +mod tests { |
| 161 | + const VHOST_VDPA_PATH: &str = "/dev/vhost-vdpa-0"; |
| 162 | + |
| 163 | + use vm_memory::{GuestAddress, GuestMemory, GuestMemoryMmap}; |
| 164 | + use vmm_sys_util::eventfd::EventFd; |
| 165 | + |
| 166 | + use super::*; |
| 167 | + use crate::{ |
| 168 | + VhostBackend, VhostUserDirtyLogRegion, VhostUserMemoryRegionInfo, VringConfigData, |
| 169 | + }; |
| 170 | + use serial_test::serial; |
| 171 | + |
| 172 | + #[test] |
| 173 | + #[serial] |
| 174 | + fn test_vdpa_kern_new_device() { |
| 175 | + let m = GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x10_0000)]).unwrap(); |
| 176 | + let vdpa = VhostKernVdpa::new(VHOST_VDPA_PATH, &m).unwrap(); |
| 177 | + |
| 178 | + assert!(vdpa.as_raw_fd() >= 0); |
| 179 | + assert!(vdpa.mem().find_region(GuestAddress(0x100)).is_some()); |
| 180 | + assert!(vdpa.mem().find_region(GuestAddress(0x10_0000)).is_none()); |
| 181 | + } |
| 182 | + |
| 183 | + #[test] |
| 184 | + #[serial] |
| 185 | + fn test_vdpa_kern_is_valid() { |
| 186 | + let m = GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x10_0000)]).unwrap(); |
| 187 | + let vdpa = VhostKernVdpa::new(VHOST_VDPA_PATH, &m).unwrap(); |
| 188 | + |
| 189 | + let mut config = VringConfigData { |
| 190 | + queue_max_size: 32, |
| 191 | + queue_size: 32, |
| 192 | + flags: 0, |
| 193 | + desc_table_addr: 0x1000, |
| 194 | + used_ring_addr: 0x2000, |
| 195 | + avail_ring_addr: 0x3000, |
| 196 | + log_addr: None, |
| 197 | + }; |
| 198 | + assert_eq!(vdpa.is_valid(&config), true); |
| 199 | + |
| 200 | + config.queue_size = 0; |
| 201 | + assert_eq!(vdpa.is_valid(&config), false); |
| 202 | + config.queue_size = 31; |
| 203 | + assert_eq!(vdpa.is_valid(&config), false); |
| 204 | + config.queue_size = 33; |
| 205 | + assert_eq!(vdpa.is_valid(&config), false); |
| 206 | + } |
| 207 | + |
| 208 | + #[test] |
| 209 | + #[serial] |
| 210 | + fn test_vdpa_kern_ioctls() { |
| 211 | + let m = GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x10_0000)]).unwrap(); |
| 212 | + let vdpa = VhostKernVdpa::new(VHOST_VDPA_PATH, &m).unwrap(); |
| 213 | + |
| 214 | + let features = vdpa.get_features().unwrap(); |
| 215 | + // VIRTIO_F_VERSION_1 (bit 32) should be set |
| 216 | + assert_ne!(features & (1 << 32), 0); |
| 217 | + vdpa.set_features(features).unwrap(); |
| 218 | + |
| 219 | + vdpa.set_owner().unwrap(); |
| 220 | + |
| 221 | + vdpa.set_mem_table(&[]).unwrap_err(); |
| 222 | + |
| 223 | + let region = VhostUserMemoryRegionInfo { |
| 224 | + guest_phys_addr: 0x0, |
| 225 | + memory_size: 0x10_0000, |
| 226 | + userspace_addr: m.get_host_address(GuestAddress(0x0)).unwrap() as u64, |
| 227 | + mmap_offset: 0, |
| 228 | + mmap_handle: -1, |
| 229 | + }; |
| 230 | + vdpa.set_mem_table(&[region]).unwrap(); |
| 231 | + |
| 232 | + assert!(vdpa.get_device_id().unwrap() > 0); |
| 233 | + |
| 234 | + assert_eq!(vdpa.get_status().unwrap(), 0x0); |
| 235 | + vdpa.set_status(0x1).unwrap(); |
| 236 | + assert_eq!(vdpa.get_status().unwrap(), 0x1); |
| 237 | + |
| 238 | + let mut vec = vec![0u8; 8]; |
| 239 | + vdpa.get_config(0, &mut vec).unwrap(); |
| 240 | + vdpa.set_config(0, &vec).unwrap(); |
| 241 | + |
| 242 | + let eventfd = EventFd::new(0).unwrap(); |
| 243 | + |
| 244 | + // set_log_base() and set_log_fd() are not supported by vhost-vdpa |
| 245 | + vdpa.set_log_base( |
| 246 | + 0x4000, |
| 247 | + Some(VhostUserDirtyLogRegion { |
| 248 | + mmap_size: 0x1000, |
| 249 | + mmap_offset: 0x10, |
| 250 | + mmap_handle: 1, |
| 251 | + }), |
| 252 | + ) |
| 253 | + .unwrap_err(); |
| 254 | + vdpa.set_log_base(0x4000, None).unwrap_err(); |
| 255 | + vdpa.set_log_fd(eventfd.as_raw_fd()).unwrap_err(); |
| 256 | + |
| 257 | + let max_queues = vdpa.get_vring_num().unwrap(); |
| 258 | + vdpa.set_vring_num(0, max_queues + 1).unwrap_err(); |
| 259 | + |
| 260 | + vdpa.set_vring_num(0, 32).unwrap(); |
| 261 | + |
| 262 | + let config = VringConfigData { |
| 263 | + queue_max_size: 32, |
| 264 | + queue_size: 32, |
| 265 | + flags: 0, |
| 266 | + desc_table_addr: 0x1000, |
| 267 | + used_ring_addr: 0x2000, |
| 268 | + avail_ring_addr: 0x3000, |
| 269 | + log_addr: None, |
| 270 | + }; |
| 271 | + vdpa.set_vring_addr(0, &config).unwrap(); |
| 272 | + vdpa.set_vring_base(0, 1).unwrap(); |
| 273 | + vdpa.set_vring_call(0, &eventfd).unwrap(); |
| 274 | + vdpa.set_vring_kick(0, &eventfd).unwrap(); |
| 275 | + vdpa.set_vring_err(0, &eventfd).unwrap(); |
| 276 | + |
| 277 | + vdpa.set_config_call(&eventfd).unwrap(); |
| 278 | + |
| 279 | + assert_eq!(vdpa.get_vring_base(0).unwrap(), 1); |
| 280 | + |
| 281 | + vdpa.set_vring_enable(0, true).unwrap(); |
| 282 | + vdpa.set_vring_enable(0, false).unwrap(); |
| 283 | + } |
| 284 | +} |
0 commit comments