// 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. //! # MMIO //! //! This mod is used for create MMIO device. //! //! ## Design //! //! This module offers support for: //! 1. DeviceType to identify NET,BLK,SERIAL... //! 2. MMIO device structure. //! 3. MMIO device trait. //! //! ## Platform Support //! //! - `x86_64` //! - `aarch64` use kvm_ioctls::VmFd; use std::sync::{Arc, Mutex}; mod bus; mod virtio_mmio; pub use self::bus::Bus; pub use self::virtio_mmio::VirtioMmioDevice; use super::DeviceOps; use address_space::{AddressSpace, GuestAddress, Region, RegionIoEventFd, RegionOps}; use error_chain::bail; use machine_manager::config::{BootSource, ConfigCheck, Param}; pub mod errors { error_chain! { links { AddressSpace(address_space::errors::Error, address_space::errors::ErrorKind); Virtio(crate::virtio::errors::Error, crate::virtio::errors::ErrorKind); } errors { MmioRegister(offset: u64) { display("Unsupported mmio register, 0x{:x}", offset) } DeviceStatus(status: u32) { display("Invalid device status 0x{:x}", status) } } } } use self::errors::{Result, ResultExt}; /// The different type of MMIO Device. #[derive(Copy, Debug, Clone, Eq, PartialEq)] pub enum DeviceType { NET, BLK, SERIAL, BALLOON, #[cfg(target_arch = "aarch64")] RTC, OTHER, CONSOLE, } /// The requirement of address space and irq number by MMIO device. #[derive(Copy, Clone, Eq, PartialEq)] pub struct DeviceResource { /// Address space start address. pub addr: u64, /// Address space size. pub size: u64, /// Interrupt irq number. pub irq: u32, /// MMIO device type. pub dev_type: DeviceType, } /// MmioDevice structure which used to register into system address space. #[derive(Clone)] pub struct MmioDevice { /// MmioDeviceOps used to be invoked in function realize(). device: Arc<Mutex<dyn MmioDeviceOps>>, /// RegionOps used to be registered into system address space. region_ops: RegionOps, /// The DeviceResource required by this MMIO device. resource: Arc<DeviceResource>, } impl MmioDevice { pub fn new<T: 'static + MmioDeviceOps>( device: Arc<Mutex<T>>, res: DeviceResource, ) -> MmioDevice { let device_clone = device.clone(); let read_ops = move |data: &mut [u8], addr: GuestAddress, offset: u64| -> bool { let mut device_locked = device_clone.lock().unwrap(); device_locked.read(data, addr, offset) }; let device_clone = device.clone(); let write_ops = move |data: &[u8], addr: GuestAddress, offset: u64| -> bool { let mut device_locked = device_clone.lock().unwrap(); device_locked.write(data, addr, offset) }; let region_ops = RegionOps { read: Arc::new(read_ops), write: Arc::new(write_ops), }; MmioDevice { device, region_ops, resource: Arc::new(res), } } /// Realize this MMIO device for VM. /// /// # Arguments /// /// * `vm_fd` - The file descriptor of VM. /// * `bs` - The boot source of VM. /// * `sys_mem` - The guest memory to device constructs over. pub fn realize( &self, vm_fd: &VmFd, bs: &Arc<Mutex<BootSource>>, sys_mem: &Arc<AddressSpace>, #[cfg(target_arch = "x86_64")] sys_io: Arc<AddressSpace>, ) -> Result<()> { let dev_type = self.resource.dev_type; self.device .lock() .unwrap() .realize(vm_fd, *self.resource) .chain_err(|| format!("Failed to realize device {:?}", dev_type))?; let region = Region::init_io_region(self.resource.size, self.region_ops.clone()); region.set_ioeventfds(&self.device.lock().unwrap().ioeventfds()); match dev_type { DeviceType::SERIAL if cfg!(target_arch = "x86_64") => { #[cfg(target_arch = "x86_64")] sys_io .root() .add_subregion(region, self.resource.addr) .chain_err(|| { format!( "Failed to add subregion for io space, type {:?}, addr: 0x{:X}", dev_type, self.resource.addr, ) })?; } _ => { sys_mem .root() .add_subregion(region, self.resource.addr) .chain_err(|| { format!( "Failed to add subregion for mem space, type {:?}, addr: 0x{:X}", dev_type, self.resource.addr, ) })?; } } // add to kernel cmdline let cmdline = &mut bs.lock().unwrap().kernel_cmdline; if let DeviceType::SERIAL = self.resource.dev_type { #[cfg(target_arch = "aarch64")] cmdline.push(Param { param_type: "earlycon".to_string(), value: format!("uart,mmio,0x{:08x}", self.resource.addr), }); } else { #[cfg(target_arch = "x86_64")] cmdline.push(Param { param_type: "virtio_mmio.device".to_string(), value: format!( "{}@0x{:08x}:{}", self.resource.size, self.resource.addr, self.resource.irq ), }); } Ok(()) } /// Get the resource requirement of MMIO device. #[cfg(target_arch = "aarch64")] pub fn get_resource(&self) -> DeviceResource { *self.resource } /// Update the low level config of MMIO device. /// /// # Arguments /// /// * `file_path` - For Block device is image path; For Net device is tap path. pub fn update_config(&self, dev_config: Option<Arc<dyn ConfigCheck>>) -> Result<()> { self.device.lock().unwrap().update_config(dev_config) } } /// Trait for MMIO device. pub trait MmioDeviceOps: Send + DeviceOps { /// Realize this MMIO device for VM. fn realize(&mut self, vm_fd: &VmFd, resource: DeviceResource) -> Result<()>; /// Get the resource requirement of MMIO device. fn get_type(&self) -> DeviceType; /// Update the low level config of MMIO device. fn update_config(&mut self, _dev_config: Option<Arc<dyn ConfigCheck>>) -> Result<()> { bail!("Unsupported to update configuration"); } /// Get IoEventFds of MMIO device. fn ioeventfds(&self) -> Vec<RegionIoEventFd> { Vec::new() } }