// Copyright (c) 2020 Huawei Technologies Co.,Ltd. All rights reserved. // // StratoVirt is licensed under Mulan PSL v2. // You can use this software according to the terms and conditions of the Mulan // PSL v2. // You may obtain a copy of Mulan PSL v2 at: // http://license.coscl.org.cn/MulanPSL2 // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. // See the Mulan PSL v2 for more details. use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::{Arc, Mutex}; use address_space::{AddressRange, AddressSpace, GuestAddress, RegionIoEventFd}; use byteorder::{ByteOrder, LittleEndian}; use kvm_ioctls::VmFd; use machine_manager::config::ConfigCheck; use vmm_sys_util::eventfd::EventFd; use super::super::virtio::{ virtio_has_feature, Queue, QueueConfig, VirtioDevice, NOTIFY_REG_OFFSET, QUEUE_TYPE_PACKED_VRING, QUEUE_TYPE_SPLIT_VRING, VIRTIO_F_RING_PACKED, VIRTIO_TYPE_BALLOON, VIRTIO_TYPE_BLOCK, VIRTIO_TYPE_CONSOLE, VIRTIO_TYPE_NET, }; use super::super::DeviceOps; use super::{ errors::{ErrorKind, Result, ResultExt}, DeviceResource, DeviceType, MmioDeviceOps, }; /// Registers of virtio-mmio device refer to Virtio Spec. /// Magic value - Read Only. const MAGIC_VALUE_REG: u64 = 0x00; /// Virtio device version - Read Only. const VERSION_REG: u64 = 0x04; /// Virtio device ID - Read Only. const DEVICE_ID_REG: u64 = 0x08; /// Virtio vendor ID - Read Only. const VENDOR_ID_REG: u64 = 0x0c; /// Bitmask of the features supported by the device(host) (32 bits per set) - Read Only. const DEVICE_FEATURES_REG: u64 = 0x10; /// Device (host) features set selector - Write Only. const DEVICE_FEATURES_SEL_REG: u64 = 0x14; /// Bitmask of features activated by the driver (guest) (32 bits per set) - Write Only. const DRIVER_FEATURES_REG: u64 = 0x20; /// Activated features set selector - Write Only. const DRIVER_FEATURES_SEL_REG: u64 = 0x24; /// Queue selector - Write Only. const QUEUE_SEL_REG: u64 = 0x30; /// Maximum size of the currently selected queue - Read Only. const QUEUE_NUM_MAX_REG: u64 = 0x34; /// Queue size for the currently selected queue - Write Only. const QUEUE_NUM_REG: u64 = 0x38; /// Ready bit for the currently selected queue - Read Write. const QUEUE_READY_REG: u64 = 0x44; /// Interrupt status - Read Only. const INTERRUPT_STATUS_REG: u64 = 0x60; /// Interrupt acknowledge - Write Only. const INTERRUPT_ACK_REG: u64 = 0x64; /// Device status register - Read Write. const STATUS_REG: u64 = 0x70; /// The low 32bit of queue's Descriptor Table address. const QUEUE_DESC_LOW_REG: u64 = 0x80; /// The high 32bit of queue's Descriptor Table address. const QUEUE_DESC_HIGH_REG: u64 = 0x84; /// The low 32 bit of queue's Available Ring address. const QUEUE_AVAIL_LOW_REG: u64 = 0x90; /// The high 32 bit of queue's Available Ring address. const QUEUE_AVAIL_HIGH_REG: u64 = 0x94; /// The low 32bit of queue's Used Ring address. const QUEUE_USED_LOW_REG: u64 = 0xa0; /// The high 32bit of queue's Used Ring address. const QUEUE_USED_HIGH_REG: u64 = 0xa4; /// Configuration atomicity value. const CONFIG_GENERATION_REG: u64 = 0xfc; const VENDOR_ID: u32 = 0; const MMIO_MAGIC_VALUE: u32 = 0x7472_6976; const MMIO_VERSION: u32 = 2; const CONFIG_STATUS_ACKNOWLEDGE: u32 = 0x01; const CONFIG_STATUS_DRIVER: u32 = 0x02; const CONFIG_STATUS_DRIVER_OK: u32 = 0x04; const CONFIG_STATUS_FEATURES_OK: u32 = 0x08; const CONFIG_STATUS_FAILED: u32 = 0x80; /// HostNotifyInfo includes the info needed for notifying backend from guest. pub struct HostNotifyInfo { /// Eventfds which notify backend to use the avail ring. events: Vec<EventFd>, } impl HostNotifyInfo { pub fn new(queue_num: usize) -> Self { let mut events = Vec::new(); for _i in 0..queue_num { events.push(EventFd::new(libc::EFD_NONBLOCK).unwrap()); } HostNotifyInfo { events } } } /// The configuration of virtio-mmio device, the fields refer to Virtio Spec. pub struct VirtioMmioCommonConfig { /// Bitmask of the features supported by the device (host)(32 bits per set). features_select: u32, /// Device (host) feature-setting selector. acked_features_select: u32, /// Interrupt status. interrupt_status: Arc<AtomicU32>, /// Device status. device_status: u32, /// Configuration atomicity value. config_generation: u32, /// Queue selector. queue_select: u32, /// The configuration of queues. queues_config: Vec<QueueConfig>, /// The type of queue, either be split ring or packed ring. queue_type: u16, } impl VirtioMmioCommonConfig { pub fn new(device: &Arc<Mutex<dyn VirtioDevice>>) -> Self { let locked_device = device.lock().unwrap(); let mut queues_config = Vec::new(); let queue_size = locked_device.queue_size(); for _ in 0..locked_device.queue_num() { queues_config.push(QueueConfig::new(queue_size)) } VirtioMmioCommonConfig { features_select: 0, acked_features_select: 0, interrupt_status: Arc::new(AtomicU32::new(0)), device_status: 0, config_generation: 0, queue_select: 0, queues_config, queue_type: QUEUE_TYPE_SPLIT_VRING, } } /// Check whether virtio device status is as expected. fn check_device_status(&self, set: u32, clr: u32) -> bool { self.device_status & (set | clr) == set } /// Get the status of virtio device fn get_device_status(&self) -> u32 { self.device_status } /// Get mutable QueueConfig structure of virtio device. fn get_mut_queue_config(&mut self) -> Result<&mut QueueConfig> { if self.check_device_status( CONFIG_STATUS_FEATURES_OK, CONFIG_STATUS_DRIVER_OK | CONFIG_STATUS_FAILED, ) { let queue_select = self.queue_select; self.queues_config .get_mut(queue_select as usize) .ok_or_else(|| { format!( "Mmio-reg queue_select {} overflows for mutable queue config", queue_select, ) .into() }) } else { Err(ErrorKind::DeviceStatus(self.device_status).into()) } } /// Get immutable QueueConfig structure of virtio device. fn get_queue_config(&self) -> Result<&QueueConfig> { let queue_select = self.queue_select; self.queues_config .get(queue_select as usize) .ok_or_else(|| { format!( "Mmio-reg queue_select overflows {} for immutable queue config", queue_select, ) .into() }) } /// Read data from the common config of virtio device. /// Return the config value in u32. /// # Arguments /// /// * `device` - Virtio device entity. /// * `offset` - The offset of common config. fn read_common_config( &self, device: &Arc<Mutex<dyn VirtioDevice>>, offset: u64, ) -> Result<u32> { let value = match offset { MAGIC_VALUE_REG => MMIO_MAGIC_VALUE, VERSION_REG => MMIO_VERSION, DEVICE_ID_REG => device.lock().unwrap().device_type() as u32, VENDOR_ID_REG => VENDOR_ID, DEVICE_FEATURES_REG => { let mut features = device .lock() .unwrap() .get_device_features(self.features_select); if self.features_select == 1 { features |= 0x1; // enable support of VirtIO Version 1 } features } QUEUE_NUM_MAX_REG => self .get_queue_config() .map(|config| u32::from(config.max_size))?, QUEUE_READY_REG => self.get_queue_config().map(|config| config.ready as u32)?, INTERRUPT_STATUS_REG => self.interrupt_status.load(Ordering::SeqCst), STATUS_REG => self.device_status, CONFIG_GENERATION_REG => self.config_generation, _ => { return Err(ErrorKind::MmioRegister(offset).into()); } }; Ok(value) } /// Write data to the common config of virtio device. /// /// # Arguments /// /// * `device` - Virtio device entity. /// * `offset` - The offset of common config. /// * `value` - The value to write. /// /// # Errors /// /// Returns Error if the offset is out of bound. fn write_common_config( &mut self, device: &Arc<Mutex<dyn VirtioDevice>>, offset: u64, value: u32, ) -> Result<()> { match offset { DEVICE_FEATURES_SEL_REG => self.features_select = value, DRIVER_FEATURES_REG => { if self.check_device_status( CONFIG_STATUS_DRIVER, CONFIG_STATUS_FEATURES_OK | CONFIG_STATUS_FAILED, ) { device .lock() .unwrap() .set_driver_features(self.acked_features_select, value); if self.acked_features_select == 1 && virtio_has_feature(u64::from(value) << 32, VIRTIO_F_RING_PACKED) { self.queue_type = QUEUE_TYPE_PACKED_VRING; } } else { return Err(ErrorKind::DeviceStatus(self.device_status).into()); } } DRIVER_FEATURES_SEL_REG => self.acked_features_select = value, QUEUE_SEL_REG => self.queue_select = value, QUEUE_NUM_REG => self .get_mut_queue_config() .map(|config| config.size = value as u16)?, QUEUE_READY_REG => self .get_mut_queue_config() .map(|config| config.ready = value == 1)?, INTERRUPT_ACK_REG => { if self.check_device_status(CONFIG_STATUS_DRIVER_OK, 0) { self.interrupt_status.fetch_and(!value, Ordering::SeqCst); } } STATUS_REG => self.device_status = value, QUEUE_DESC_LOW_REG => self.get_mut_queue_config().map(|config| { config.desc_table = GuestAddress(config.desc_table.0 | u64::from(value)); })?, QUEUE_DESC_HIGH_REG => self.get_mut_queue_config().map(|config| { config.desc_table = GuestAddress(config.desc_table.0 | (u64::from(value) << 32)); })?, QUEUE_AVAIL_LOW_REG => self.get_mut_queue_config().map(|config| { config.avail_ring = GuestAddress(config.avail_ring.0 | u64::from(value)); })?, QUEUE_AVAIL_HIGH_REG => self.get_mut_queue_config().map(|config| { config.avail_ring = GuestAddress(config.avail_ring.0 | (u64::from(value) << 32)); })?, QUEUE_USED_LOW_REG => self.get_mut_queue_config().map(|config| { config.used_ring = GuestAddress(config.used_ring.0 | u64::from(value)); })?, QUEUE_USED_HIGH_REG => self.get_mut_queue_config().map(|config| { config.used_ring = GuestAddress(config.used_ring.0 | (u64::from(value) << 32)); })?, _ => { return Err(ErrorKind::MmioRegister(offset).into()); } }; Ok(()) } } /// virtio-mmio device structure. pub struct VirtioMmioDevice { /// The entity of low level device. device: Arc<Mutex<dyn VirtioDevice>>, /// Identify if this device is activated by frontend driver. device_activated: bool, /// EventFd used to send interrupt to VM interrupt_evt: EventFd, /// HostNotifyInfo used for guest notifier host_notify_info: HostNotifyInfo, /// Virtio common config refer to Virtio Spec. common_config: VirtioMmioCommonConfig, /// System address space. mem_space: Arc<AddressSpace>, } impl VirtioMmioDevice { pub fn new(mem_space: Arc<AddressSpace>, device: Arc<Mutex<dyn VirtioDevice>>) -> Self { let device_clone = device.clone(); let queue_num = device_clone.lock().unwrap().queue_num(); VirtioMmioDevice { device, device_activated: false, interrupt_evt: EventFd::new(libc::EFD_NONBLOCK).unwrap(), host_notify_info: HostNotifyInfo::new(queue_num), common_config: VirtioMmioCommonConfig::new(&device_clone), mem_space, } } /// Activate the virtio device, this function is called by vcpu thread when frontend /// virtio driver is ready and write `DRIVER_OK` to backend. fn activate(&mut self) -> Result<()> { let queues_config = &self.common_config.queues_config; let mut queues: Vec<Arc<Mutex<Queue>>> = Vec::with_capacity(queues_config.len()); for q_config in queues_config.iter() { let queue = Queue::new(*q_config, self.common_config.queue_type)?; if !queue.is_valid(&self.mem_space) { bail!("Invalid queue"); } queues.push(Arc::new(Mutex::new(queue))) } let mut queue_evts = Vec::<EventFd>::new(); for fd in self.host_notify_info.events.iter() { let evt_fd_clone = match fd.try_clone() { Ok(fd) => fd, Err(e) => { error!("Failed to clone IoEventFd, {}", e); continue; } }; queue_evts.push(evt_fd_clone); } self.device.lock().unwrap().activate( self.mem_space.clone(), self.interrupt_evt.try_clone().unwrap(), self.common_config.interrupt_status.clone(), queues, queue_evts, )?; Ok(()) } } impl DeviceOps for VirtioMmioDevice { /// Read data by virtio driver from VM. fn read(&mut self, data: &mut [u8], _base: GuestAddress, offset: u64) -> bool { match offset { 0x00..=0xff if data.len() == 4 => { let value = match self.common_config.read_common_config(&self.device, offset) { Ok(v) => v, Err(ref e) => { error!( "Failed to read mmio register {}, type: {}, {}", offset, self.device.lock().unwrap().device_type(), error_chain::ChainedError::display_chain(e), ); return false; } }; LittleEndian::write_u32(data, value); } 0x100..=0xfff => { if let Err(ref e) = self .device .lock() .unwrap() .read_config(offset as u64 - 0x100, data) { error!( "Failed to read virtio-dev config space {} type: {} {}", offset as u64 - 0x100, self.device.lock().unwrap().device_type(), error_chain::ChainedError::display_chain(e), ); return false; } } _ => { warn!( "Failed to read mmio register: overflows, offset is 0x{:x}, type: {}", offset, self.device.lock().unwrap().device_type(), ); } }; true } /// Write data by virtio driver from VM. fn write(&mut self, data: &[u8], _base: GuestAddress, offset: u64) -> bool { match offset { 0x00..=0xff if data.len() == 4 => { let value = LittleEndian::read_u32(data); if let Err(ref e) = self.common_config .write_common_config(&self.device, offset, value) { error!( "Failed to write mmio register {}, type: {}, {}", offset, self.device.lock().unwrap().device_type(), error_chain::ChainedError::display_chain(e), ); return false; } if self.common_config.check_device_status( CONFIG_STATUS_ACKNOWLEDGE | CONFIG_STATUS_DRIVER | CONFIG_STATUS_DRIVER_OK | CONFIG_STATUS_FEATURES_OK, CONFIG_STATUS_FAILED, ) && !self.device_activated { let res = self.activate().map(|_| self.device_activated = true); if let Err(ref e) = res { error!( "Failed to activate dev, type: {}, {}", self.device.lock().unwrap().device_type(), error_chain::ChainedError::display_chain(e), ); } } } 0x100..=0xfff => { if self .common_config .check_device_status(CONFIG_STATUS_DRIVER, CONFIG_STATUS_FAILED) { if let Err(ref e) = self .device .lock() .unwrap() .write_config(offset as u64 - 0x100, data) { error!( "Failed to write virtio-dev config space {}, type: {}, {}", offset as u64 - 0x100, self.device.lock().unwrap().device_type(), error_chain::ChainedError::display_chain(e), ); return false; } } else { error!("Failed to write virtio-dev config space: driver is not ready 0x{:X}, type: {}", self.common_config.get_device_status(), self.device.lock().unwrap().device_type(), ); return false; } } _ => { warn!( "Failed to write mmio register: overflows, offset is 0x{:x} type: {}", offset, self.device.lock().unwrap().device_type(), ); return false; } } true } } impl MmioDeviceOps for VirtioMmioDevice { /// Realize this MMIO device for VM. fn realize(&mut self, vm_fd: &VmFd, resource: DeviceResource) -> Result<()> { let device_type = self.device.lock().unwrap().device_type(); vm_fd .register_irqfd(&self.interrupt_evt, resource.irq) .chain_err(|| { format!( "Failed to register irqfd for virtio mmio device, irq: {}, type: {}", resource.irq, device_type, ) })?; self.device.lock().unwrap().realize().chain_err(|| { format!( "Failed to realize device for virtio mmio device, type: {}", device_type, ) })?; Ok(()) } /// Get the resource requirement of MMIO device. fn get_type(&self) -> DeviceType { match self.device.lock().unwrap().device_type() { VIRTIO_TYPE_NET => DeviceType::NET, VIRTIO_TYPE_BLOCK => DeviceType::BLK, VIRTIO_TYPE_BALLOON => DeviceType::BALLOON, VIRTIO_TYPE_CONSOLE => DeviceType::CONSOLE, _ => DeviceType::OTHER, } } /// Update the low level config of MMIO device. fn update_config(&mut self, dev_config: Option<Arc<dyn ConfigCheck>>) -> Result<()> { let device_type = self.device.lock().unwrap().device_type(); self.device .lock() .unwrap() .update_config(dev_config) .chain_err(|| { format!( "Failed to update configuration, device type: {}", device_type ) })?; Ok(()) } fn ioeventfds(&self) -> Vec<RegionIoEventFd> { let mut ret = Vec::new(); for (index, eventfd) in self.host_notify_info.events.iter().enumerate() { let addr = u64::from(NOTIFY_REG_OFFSET); let eventfd_clone = match eventfd.try_clone() { Err(e) => { error!("Failed to clone ioeventfd, error is {}", e); continue; } Ok(fd) => fd, }; ret.push(RegionIoEventFd { fd: eventfd_clone, addr_range: AddressRange::from((addr, std::mem::size_of::<u32>() as u64)), data_match: true, data: index as u64, }) } ret } } #[cfg(test)] mod tests { use std::io::Write; use address_space::{AddressSpace, GuestAddress, HostMemMapping, Region}; use util::num_ops::{read_u32, write_u32}; use super::*; type VirtioResult<T> = std::result::Result<T, super::super::super::virtio::Error>; fn address_space_init() -> Arc<AddressSpace> { let root = Region::init_container_region(1 << 36); let sys_space = AddressSpace::new(root).unwrap(); let host_mmap = Arc::new( HostMemMapping::new(GuestAddress(0), SYSTEM_SPACE_SIZE, None, false, false).unwrap(), ); sys_space .root() .add_subregion( Region::init_ram_region(host_mmap.clone()), host_mmap.start_address().raw_value(), ) .unwrap(); sys_space } const SYSTEM_SPACE_SIZE: u64 = (1024 * 1024) as u64; const CONFIG_SPACE_SIZE: usize = 16; const QUEUE_NUM: usize = 2; const QUEUE_SIZE: u16 = 256; pub struct VirtioDeviceTest { pub device_features: u64, pub driver_features: u64, pub config_space: Vec<u8>, pub b_active: bool, pub b_realized: bool, } impl VirtioDeviceTest { pub fn new() -> Self { let mut config_space = Vec::new(); for i in 0..CONFIG_SPACE_SIZE { config_space.push(i as u8); } VirtioDeviceTest { device_features: 0, driver_features: 0, b_active: false, b_realized: false, config_space, } } } impl VirtioDevice for VirtioDeviceTest { fn realize(&mut self) -> VirtioResult<()> { self.b_realized = true; Ok(()) } fn device_type(&self) -> u32 { DeviceType::BLK as u32 } fn queue_num(&self) -> usize { QUEUE_NUM } fn queue_size(&self) -> u16 { QUEUE_SIZE } fn get_device_features(&self, features_select: u32) -> u32 { read_u32(self.device_features, features_select) } fn set_driver_features(&mut self, page: u32, value: u32) { let mut v = write_u32(value, page); let unrequested_features = v & !self.device_features; if unrequested_features != 0 { v &= !unrequested_features; } self.driver_features |= v; } fn read_config(&self, offset: u64, mut data: &mut [u8]) -> VirtioResult<()> { let config_len = self.config_space.len() as u64; if offset >= config_len { bail!( "The offset{} for reading is more than the length{} of configuration", offset, config_len ); } if let Some(end) = offset.checked_add(data.len() as u64) { data.write_all( &self.config_space[offset as usize..std::cmp::min(end, config_len) as usize], )?; } Ok(()) } fn write_config(&mut self, offset: u64, data: &[u8]) -> VirtioResult<()> { let data_len = data.len(); let config_len = self.config_space.len(); if offset as usize + data_len > config_len { bail!( "The offset{} {}for writing is more than the length{} of configuration", offset, data_len, config_len ); } self.config_space[(offset as usize)..(offset as usize + data_len)] .copy_from_slice(&data[..]); Ok(()) } fn activate( &mut self, _mem_space: Arc<AddressSpace>, _interrupt_evt: EventFd, _interrupt_status: Arc<AtomicU32>, mut _queues: Vec<Arc<Mutex<Queue>>>, mut _queue_evts: Vec<EventFd>, ) -> VirtioResult<()> { self.b_active = true; Ok(()) } } #[test] fn test_virtio_mmio_device_new() { let virtio_device = Arc::new(Mutex::new(VirtioDeviceTest::new())); let virtio_device_clone = virtio_device.clone(); let sys_space = address_space_init(); let virtio_mmio_device = VirtioMmioDevice::new(sys_space, virtio_device); assert_eq!(virtio_mmio_device.device_activated, false); assert_eq!( virtio_mmio_device.host_notify_info.events.len(), virtio_device_clone.lock().unwrap().queue_num() ); assert_eq!(virtio_mmio_device.common_config.features_select, 0); assert_eq!(virtio_mmio_device.common_config.acked_features_select, 0); assert_eq!(virtio_mmio_device.common_config.device_status, 0); assert_eq!(virtio_mmio_device.common_config.config_generation, 0); assert_eq!(virtio_mmio_device.common_config.queue_select, 0); assert_eq!( virtio_mmio_device.common_config.queues_config.len(), virtio_device_clone.lock().unwrap().queue_num() ); assert_eq!( virtio_mmio_device.common_config.queue_type, QUEUE_TYPE_SPLIT_VRING ); } #[test] fn test_virtio_mmio_device_read_01() { let virtio_device = Arc::new(Mutex::new(VirtioDeviceTest::new())); let virtio_device_clone = virtio_device.clone(); let sys_space = address_space_init(); let mut virtio_mmio_device = VirtioMmioDevice::new(sys_space, virtio_device); let addr = GuestAddress(0); // read the register of magic value let mut buf: Vec<u8> = vec![0xff, 0xff, 0xff, 0xff]; assert_eq!( virtio_mmio_device.read(&mut buf[..], addr, MAGIC_VALUE_REG), true ); assert_eq!(LittleEndian::read_u32(&buf[..]), MMIO_MAGIC_VALUE); // read the register of version let mut buf: Vec<u8> = vec![0xff, 0xff, 0xff, 0xff]; assert_eq!( virtio_mmio_device.read(&mut buf[..], addr, VERSION_REG), true ); assert_eq!(LittleEndian::read_u32(&buf[..]), MMIO_VERSION); // read the register of device id let mut buf: Vec<u8> = vec![0xff, 0xff, 0xff, 0xff]; assert_eq!( virtio_mmio_device.read(&mut buf[..], addr, DEVICE_ID_REG), true ); assert_eq!(LittleEndian::read_u32(&buf[..]), DeviceType::BLK as u32); // read the register of vendor id let mut buf: Vec<u8> = vec![0xff, 0xff, 0xff, 0xff]; assert_eq!( virtio_mmio_device.read(&mut buf[..], addr, VENDOR_ID_REG), true ); assert_eq!(LittleEndian::read_u32(&buf[..]), VENDOR_ID); // read the register of the features // get low 32bit of the features let mut buf: Vec<u8> = vec![0xff, 0xff, 0xff, 0xff]; virtio_mmio_device.common_config.features_select = 0; virtio_device_clone.lock().unwrap().device_features = 0x0000_00f8_0000_00fe; assert_eq!( virtio_mmio_device.read(&mut buf[..], addr, DEVICE_FEATURES_REG), true ); assert_eq!(LittleEndian::read_u32(&buf[..]), 0x0000_00fe); // get high 32bit of the features for device which supports VirtIO Version 1 let mut buf: Vec<u8> = vec![0xff, 0xff, 0xff, 0xff]; virtio_mmio_device.common_config.features_select = 1; assert_eq!( virtio_mmio_device.read(&mut buf[..], addr, DEVICE_FEATURES_REG), true ); assert_eq!(LittleEndian::read_u32(&buf[..]), 0x0000_00f9); } #[test] fn test_virtio_mmio_device_read_02() { let virtio_device = Arc::new(Mutex::new(VirtioDeviceTest::new())); let sys_space = address_space_init(); let mut virtio_mmio_device = VirtioMmioDevice::new(sys_space, virtio_device); let addr = GuestAddress(0); // read the register representing max size of the queue // for queue_select as 0 let mut buf: Vec<u8> = vec![0xff, 0xff, 0xff, 0xff]; virtio_mmio_device.common_config.queue_select = 0; assert_eq!( virtio_mmio_device.read(&mut buf[..], addr, QUEUE_NUM_MAX_REG), true ); assert_eq!(LittleEndian::read_u32(&buf[..]), QUEUE_SIZE as u32); // for queue_select as 1 let mut buf: Vec<u8> = vec![0xff, 0xff, 0xff, 0xff]; virtio_mmio_device.common_config.queue_select = 1; assert_eq!( virtio_mmio_device.read(&mut buf[..], addr, QUEUE_NUM_MAX_REG), true ); assert_eq!(LittleEndian::read_u32(&buf[..]), QUEUE_SIZE as u32); // read the register representing the status of queue // for queue_select as 0 let mut buf: Vec<u8> = vec![0xff, 0xff, 0xff, 0xff]; virtio_mmio_device.common_config.queue_select = 0; virtio_mmio_device.common_config.device_status = CONFIG_STATUS_FEATURES_OK; LittleEndian::write_u32(&mut buf[..], 1); assert_eq!( virtio_mmio_device.write(&buf[..], addr, QUEUE_READY_REG), true ); let mut data: Vec<u8> = vec![0xff, 0xff, 0xff, 0xff]; assert_eq!( virtio_mmio_device.read(&mut data[..], addr, QUEUE_READY_REG), true ); assert_eq!(LittleEndian::read_u32(&data[..]), 1); // for queue_select as 1 let mut buf: Vec<u8> = vec![0xff, 0xff, 0xff, 0xff]; virtio_mmio_device.common_config.queue_select = 1; virtio_mmio_device.common_config.device_status = CONFIG_STATUS_FEATURES_OK; assert_eq!( virtio_mmio_device.read(&mut buf[..], addr, QUEUE_READY_REG), true ); assert_eq!(LittleEndian::read_u32(&buf[..]), 0); // read the register representing the status of interrupt let mut buf: Vec<u8> = vec![0xff, 0xff, 0xff, 0xff]; assert_eq!( virtio_mmio_device.read(&mut buf[..], addr, INTERRUPT_STATUS_REG), true ); assert_eq!(LittleEndian::read_u32(&buf[..]), 0); let mut buf: Vec<u8> = vec![0xff, 0xff, 0xff, 0xff]; virtio_mmio_device .common_config .interrupt_status .store(0b10_1111, Ordering::Relaxed); assert_eq!( virtio_mmio_device.read(&mut buf[..], addr, INTERRUPT_STATUS_REG), true ); assert_eq!(LittleEndian::read_u32(&buf[..]), 0b10_1111); // read the register representing the status of device let mut buf: Vec<u8> = vec![0xff, 0xff, 0xff, 0xff]; virtio_mmio_device.common_config.device_status = 0; assert_eq!( virtio_mmio_device.read(&mut buf[..], addr, STATUS_REG), true ); assert_eq!(LittleEndian::read_u32(&buf[..]), 0); let mut buf: Vec<u8> = vec![0xff, 0xff, 0xff, 0xff]; virtio_mmio_device.common_config.device_status = 5; assert_eq!( virtio_mmio_device.read(&mut buf[..], addr, STATUS_REG), true ); assert_eq!(LittleEndian::read_u32(&buf[..]), 5); } #[test] fn test_virtio_mmio_device_read_03() { let virtio_device = Arc::new(Mutex::new(VirtioDeviceTest::new())); let virtio_device_clone = virtio_device.clone(); let sys_space = address_space_init(); let mut virtio_mmio_device = VirtioMmioDevice::new(sys_space, virtio_device); let addr = GuestAddress(0); // read the configuration atomic value let mut buf: Vec<u8> = vec![0xff, 0xff, 0xff, 0xff]; assert_eq!( virtio_mmio_device.read(&mut buf[..], addr, CONFIG_GENERATION_REG), true ); assert_eq!(LittleEndian::read_u32(&buf[..]), 0); let mut buf: Vec<u8> = vec![0xff, 0xff, 0xff, 0xff]; virtio_mmio_device.common_config.config_generation = 10; assert_eq!( virtio_mmio_device.read(&mut buf[..], addr, CONFIG_GENERATION_REG), true ); assert_eq!(LittleEndian::read_u32(&buf[..]), 10); // read the unknown register let mut buf: Vec<u8> = vec![0xff, 0xff, 0xff, 0xff]; assert_eq!(virtio_mmio_device.read(&mut buf[..], addr, 0xf1), false); assert_eq!(virtio_mmio_device.read(&mut buf[..], addr, 0xfff + 1), true); assert_eq!(buf, [0xff, 0xff, 0xff, 0xff]); // read the configuration space of virtio device // write something let result: Vec<u8> = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; virtio_device_clone .lock() .unwrap() .config_space .as_mut_slice() .copy_from_slice(&result[..]); let mut data: Vec<u8> = vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; assert_eq!(virtio_mmio_device.read(&mut data[..], addr, 0x100), true); assert_eq!(data, result); let mut data: Vec<u8> = vec![0, 0, 0, 0, 0, 0, 0, 0]; let result: Vec<u8> = vec![9, 10, 11, 12, 13, 14, 15, 16]; assert_eq!(virtio_mmio_device.read(&mut data[..], addr, 0x108), true); assert_eq!(data, result); } #[test] fn test_virtio_mmio_device_write_01() { let virtio_device = Arc::new(Mutex::new(VirtioDeviceTest::new())); let virtio_device_clone = virtio_device.clone(); let sys_space = address_space_init(); let mut virtio_mmio_device = VirtioMmioDevice::new(sys_space, virtio_device); let addr = GuestAddress(0); // write the selector for device features let mut buf: Vec<u8> = vec![0xff, 0xff, 0xff, 0xff]; LittleEndian::write_u32(&mut buf[..], 2); assert_eq!( virtio_mmio_device.write(&buf[..], addr, DEVICE_FEATURES_SEL_REG), true ); assert_eq!(virtio_mmio_device.common_config.features_select, 2); // write the device features // false when the device status is CONFIG_STATUS_FEATURES_OK or CONFIG_STATUS_FAILED isn't CONFIG_STATUS_DRIVER virtio_mmio_device.common_config.device_status = CONFIG_STATUS_FEATURES_OK; assert_eq!( virtio_mmio_device.write(&buf[..], addr, DRIVER_FEATURES_REG), false ); virtio_mmio_device.common_config.device_status = CONFIG_STATUS_FAILED; assert_eq!( virtio_mmio_device.write(&buf[..], addr, DRIVER_FEATURES_REG), false ); virtio_mmio_device.common_config.device_status = CONFIG_STATUS_FEATURES_OK | CONFIG_STATUS_FAILED | CONFIG_STATUS_DRIVER; assert_eq!( virtio_mmio_device.write(&buf[..], addr, DRIVER_FEATURES_REG), false ); // it is ok to write the low 32bit of device features virtio_mmio_device.common_config.device_status = CONFIG_STATUS_DRIVER; let mut buf: Vec<u8> = vec![0xff, 0xff, 0xff, 0xff]; virtio_mmio_device.common_config.acked_features_select = 0; LittleEndian::write_u32(&mut buf[..], 0x0000_00fe); virtio_device_clone.lock().unwrap().device_features = 0x0000_00fe; assert_eq!( virtio_mmio_device.write(&buf[..], addr, DRIVER_FEATURES_REG), true ); assert_eq!( virtio_device_clone.lock().unwrap().driver_features as u32, 0x0000_00fe ); // it is ok to write the high 32bit of device features let mut buf: Vec<u8> = vec![0xff, 0xff, 0xff, 0xff]; virtio_mmio_device.common_config.acked_features_select = 1; LittleEndian::write_u32(&mut buf[..], 0x0000_00ff); virtio_device_clone.lock().unwrap().device_features = 0x0000_00ff_0000_0000; assert_eq!( virtio_mmio_device.write(&buf[..], addr, DRIVER_FEATURES_REG), true ); assert_eq!( virtio_mmio_device.common_config.queue_type, QUEUE_TYPE_PACKED_VRING ); assert_eq!( virtio_device_clone.lock().unwrap().driver_features >> 32 as u32, 0x0000_00ff ); // write the selector of driver features let mut buf: Vec<u8> = vec![0xff, 0xff, 0xff, 0xff]; LittleEndian::write_u32(&mut buf[..], 0x00ff_0000); assert_eq!( virtio_mmio_device.write(&buf[..], addr, DRIVER_FEATURES_SEL_REG), true ); assert_eq!( virtio_mmio_device.common_config.acked_features_select, 0x00ff_0000 ); // write the selector of queue let mut buf: Vec<u8> = vec![0xff, 0xff, 0xff, 0xff]; LittleEndian::write_u32(&mut buf[..], 0x0000_ff00); assert_eq!( virtio_mmio_device.write(&buf[..], addr, QUEUE_SEL_REG), true ); assert_eq!(virtio_mmio_device.common_config.queue_select, 0x0000_ff00); // write the size of queue let mut buf: Vec<u8> = vec![0xff, 0xff, 0xff, 0xff]; virtio_mmio_device.common_config.queue_select = 0; virtio_mmio_device.common_config.device_status = CONFIG_STATUS_FEATURES_OK; LittleEndian::write_u32(&mut buf[..], 128); assert_eq!( virtio_mmio_device.write(&buf[..], addr, QUEUE_NUM_REG), true ); if let Ok(config) = virtio_mmio_device.common_config.get_queue_config() { assert_eq!(config.size, 128); } else { assert!(false); } } #[test] fn test_virtio_mmio_device_write_02() { let virtio_device = Arc::new(Mutex::new(VirtioDeviceTest::new())); let sys_space = address_space_init(); let mut virtio_mmio_device = VirtioMmioDevice::new(sys_space, virtio_device); let addr = GuestAddress(0); // write the ready status of queue let mut buf: Vec<u8> = vec![0xff, 0xff, 0xff, 0xff]; virtio_mmio_device.common_config.queue_select = 0; virtio_mmio_device.common_config.device_status = CONFIG_STATUS_FEATURES_OK; LittleEndian::write_u32(&mut buf[..], 1); assert_eq!( virtio_mmio_device.write(&buf[..], addr, QUEUE_READY_REG), true ); let mut data: Vec<u8> = vec![0xff, 0xff, 0xff, 0xff]; assert_eq!( virtio_mmio_device.read(&mut data[..], addr, QUEUE_READY_REG), true ); assert_eq!(LittleEndian::read_u32(&data[..]), 1); let mut buf: Vec<u8> = vec![0xff, 0xff, 0xff, 0xff]; virtio_mmio_device.common_config.queue_select = 0; virtio_mmio_device.common_config.device_status = CONFIG_STATUS_FEATURES_OK; LittleEndian::write_u32(&mut buf[..], 2); assert_eq!( virtio_mmio_device.write(&buf[..], addr, QUEUE_READY_REG), true ); let mut data: Vec<u8> = vec![0xff, 0xff, 0xff, 0xff]; assert_eq!( virtio_mmio_device.read(&mut data[..], addr, QUEUE_READY_REG), true ); assert_eq!(LittleEndian::read_u32(&data[..]), 0); // write the interrupt status let mut buf: Vec<u8> = vec![0xff, 0xff, 0xff, 0xff]; virtio_mmio_device.common_config.device_status = CONFIG_STATUS_DRIVER_OK; virtio_mmio_device .common_config .interrupt_status .store(0b10_1111, Ordering::Relaxed); LittleEndian::write_u32(&mut buf[..], 0b111); assert_eq!( virtio_mmio_device.write(&buf[..], addr, INTERRUPT_ACK_REG), true ); let mut data: Vec<u8> = vec![0xff, 0xff, 0xff, 0xff]; assert_eq!( virtio_mmio_device.read(&mut data[..], addr, INTERRUPT_STATUS_REG), true ); assert_eq!(LittleEndian::read_u32(&data[..]), 0b10_1000); } #[test] fn test_virtio_mmio_device_write_03() { let virtio_device = Arc::new(Mutex::new(VirtioDeviceTest::new())); let sys_space = address_space_init(); let mut virtio_mmio_device = VirtioMmioDevice::new(sys_space, virtio_device); let addr = GuestAddress(0); // write the low 32bit of queue's descriptor table address virtio_mmio_device.common_config.queue_select = 0; virtio_mmio_device.common_config.device_status = CONFIG_STATUS_FEATURES_OK; let mut buf: Vec<u8> = vec![0xff, 0xff, 0xff, 0xff]; LittleEndian::write_u32(&mut buf[..], 0xffff_fefe); assert_eq!( virtio_mmio_device.write(&buf[..], addr, QUEUE_DESC_LOW_REG), true ); if let Ok(config) = virtio_mmio_device.common_config.get_queue_config() { assert_eq!(config.desc_table.0 as u32, 0xffff_fefe) } else { assert!(false); } // write the high 32bit of queue's descriptor table address virtio_mmio_device.common_config.queue_select = 0; virtio_mmio_device.common_config.device_status = CONFIG_STATUS_FEATURES_OK; let mut buf: Vec<u8> = vec![0xff, 0xff, 0xff, 0xff]; LittleEndian::write_u32(&mut buf[..], 0xfcfc_ffff); assert_eq!( virtio_mmio_device.write(&buf[..], addr, QUEUE_DESC_HIGH_REG), true ); if let Ok(config) = virtio_mmio_device.common_config.get_queue_config() { assert_eq!((config.desc_table.0 >> 32) as u32, 0xfcfc_ffff) } else { assert!(false); } // write the low 32bit of queue's available ring address virtio_mmio_device.common_config.queue_select = 0; virtio_mmio_device.common_config.device_status = CONFIG_STATUS_FEATURES_OK; let mut buf: Vec<u8> = vec![0xff, 0xff, 0xff, 0xff]; LittleEndian::write_u32(&mut buf[..], 0xfcfc_fafa); assert_eq!( virtio_mmio_device.write(&buf[..], addr, QUEUE_AVAIL_LOW_REG), true ); if let Ok(config) = virtio_mmio_device.common_config.get_queue_config() { assert_eq!(config.avail_ring.0 as u32, 0xfcfc_fafa) } else { assert!(false); } // write the high 32bit of queue's available ring address virtio_mmio_device.common_config.queue_select = 0; virtio_mmio_device.common_config.device_status = CONFIG_STATUS_FEATURES_OK; let mut buf: Vec<u8> = vec![0xff, 0xff, 0xff, 0xff]; LittleEndian::write_u32(&mut buf[..], 0xecec_fafa); assert_eq!( virtio_mmio_device.write(&buf[..], addr, QUEUE_AVAIL_HIGH_REG), true ); if let Ok(config) = virtio_mmio_device.common_config.get_queue_config() { assert_eq!((config.avail_ring.0 >> 32) as u32, 0xecec_fafa) } else { assert!(false); } // write the low 32bit of queue's used ring address virtio_mmio_device.common_config.queue_select = 0; virtio_mmio_device.common_config.device_status = CONFIG_STATUS_FEATURES_OK; let mut buf: Vec<u8> = vec![0xff, 0xff, 0xff, 0xff]; LittleEndian::write_u32(&mut buf[..], 0xacac_fafa); assert_eq!( virtio_mmio_device.write(&buf[..], addr, QUEUE_USED_LOW_REG), true ); if let Ok(config) = virtio_mmio_device.common_config.get_queue_config() { assert_eq!(config.used_ring.0 as u32, 0xacac_fafa) } else { assert!(false); } // write the high 32bit of queue's used ring address virtio_mmio_device.common_config.queue_select = 0; virtio_mmio_device.common_config.device_status = CONFIG_STATUS_FEATURES_OK; let mut buf: Vec<u8> = vec![0xff, 0xff, 0xff, 0xff]; LittleEndian::write_u32(&mut buf[..], 0xcccc_fafa); assert_eq!( virtio_mmio_device.write(&buf[..], addr, QUEUE_USED_HIGH_REG), true ); if let Ok(config) = virtio_mmio_device.common_config.get_queue_config() { assert_eq!((config.used_ring.0 >> 32) as u32, 0xcccc_fafa) } else { assert!(false); } } fn align(size: u64, alignment: u64) -> u64 { let align_adjust = if size % alignment != 0 { alignment - (size % alignment) } else { 0 }; (size + align_adjust) as u64 } #[test] fn test_virtio_mmio_device_write_04() { let virtio_device = Arc::new(Mutex::new(VirtioDeviceTest::new())); let virtio_device_clone = virtio_device.clone(); let sys_space = address_space_init(); let mut virtio_mmio_device = VirtioMmioDevice::new(sys_space, virtio_device); let addr = GuestAddress(0); virtio_mmio_device.common_config.queue_select = 0; virtio_mmio_device.common_config.device_status = CONFIG_STATUS_FEATURES_OK; if let Ok(config) = virtio_mmio_device.common_config.get_mut_queue_config() { config.desc_table = GuestAddress(0); config.avail_ring = GuestAddress((QUEUE_SIZE as u64) * 16); config.used_ring = GuestAddress(align( (QUEUE_SIZE as u64) * 16 + 8 + 2 * (QUEUE_SIZE as u64), 4096, )); config.size = QUEUE_SIZE; config.ready = true; } virtio_mmio_device.common_config.queue_select = 1; if let Ok(config) = virtio_mmio_device.common_config.get_mut_queue_config() { config.desc_table = GuestAddress(0); config.avail_ring = GuestAddress((QUEUE_SIZE as u64) * 16); config.used_ring = GuestAddress(align( (QUEUE_SIZE as u64) * 16 + 8 + 2 * (QUEUE_SIZE as u64), 4096, )); config.size = QUEUE_SIZE / 2; config.ready = true; } // write the device status let mut buf: Vec<u8> = vec![0xff, 0xff, 0xff, 0xff]; LittleEndian::write_u32(&mut buf[..], CONFIG_STATUS_ACKNOWLEDGE); assert_eq!(virtio_mmio_device.write(&buf[..], addr, STATUS_REG), true); assert_eq!(virtio_mmio_device.device_activated, false); let mut data: Vec<u8> = vec![0xff, 0xff, 0xff, 0xff]; assert_eq!( virtio_mmio_device.read(&mut data[..], addr, STATUS_REG), true ); assert_eq!(LittleEndian::read_u32(&data[..]), CONFIG_STATUS_ACKNOWLEDGE); let mut buf: Vec<u8> = vec![0xff, 0xff, 0xff, 0xff]; LittleEndian::write_u32( &mut buf[..], CONFIG_STATUS_ACKNOWLEDGE | CONFIG_STATUS_DRIVER | CONFIG_STATUS_DRIVER_OK | CONFIG_STATUS_FEATURES_OK, ); assert_eq!(virtio_device_clone.lock().unwrap().b_active, false); assert_eq!(virtio_mmio_device.write(&buf[..], addr, STATUS_REG), true); assert_eq!(virtio_mmio_device.device_activated, true); assert_eq!(virtio_device_clone.lock().unwrap().b_active, true); let mut data: Vec<u8> = vec![0xff, 0xff, 0xff, 0xff]; assert_eq!( virtio_mmio_device.read(&mut data[..], addr, STATUS_REG), true ); assert_eq!( LittleEndian::read_u32(&data[..]), CONFIG_STATUS_ACKNOWLEDGE | CONFIG_STATUS_DRIVER | CONFIG_STATUS_DRIVER_OK | CONFIG_STATUS_FEATURES_OK ); } }