// 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::{Arc, Mutex}; use address_space::AddressSpace; use kvm_ioctls::VmFd; use machine_manager::config::{BootSource, ConfigCheck}; use super::super::virtio::{Block, Net}; use super::{ errors::{Result, ResultExt}, DeviceResource, DeviceType, MmioDevice, MmioDeviceOps, VirtioMmioDevice, }; use crate::{LayoutEntryType, MEM_LAYOUT}; #[cfg(target_arch = "aarch64")] const IRQ_RANGE: (u32, u32) = (32, 191); #[cfg(target_arch = "x86_64")] const IRQ_RANGE: (u32, u32) = (5, 15); const MMIO_SERIAL_IRQ: u32 = 4; #[cfg(target_arch = "x86_64")] const MMIO_SERIAL_ADDR: u64 = 0x3f8; const MMIO_BASE: u64 = MEM_LAYOUT[LayoutEntryType::Mmio as usize].0; const MMIO_LEN: u64 = MEM_LAYOUT[LayoutEntryType::Mmio as usize].1; /// The replaceable block device maximum count. pub const MMIO_REPLACEABLE_BLK_NR: usize = 6; /// The replaceable network device maximum count. pub const MMIO_REPLACEABLE_NET_NR: usize = 2; /// The config of replaceable device. struct MmioReplaceableConfig { /// Device id. id: String, /// The dev_config of the related backend device. dev_config: Arc<dyn ConfigCheck>, } /// The device information of replaceable device. struct MmioReplaceableDevInfo { /// The related MMIO device. device: MmioDevice, /// Device id. id: String, /// Identify if this device is be used. used: bool, } /// The gather of config, info and count of all replaceable devices. struct MmioReplaceableInfo { /// The arrays of all replaceable configs. configs: Arc<Mutex<Vec<MmioReplaceableConfig>>>, /// The arrays of all replaceable device information. devices: Arc<Mutex<Vec<MmioReplaceableDevInfo>>>, /// The count of block device which is plugin. block_count: usize, /// The count of network device which is plugin. net_count: usize, } impl MmioReplaceableInfo { pub fn new() -> Self { MmioReplaceableInfo { configs: Arc::new(Mutex::new(Vec::new())), devices: Arc::new(Mutex::new(Vec::new())), block_count: 0_usize, net_count: 0_usize, } } } /// MMIO Bus. pub struct Bus { /// The devices inserted in bus. devices: Vec<MmioDevice>, /// All replaceable device information. replaceable_info: MmioReplaceableInfo, } impl Bus { /// Initial the MMIO Bus structure. /// /// # Steps /// /// 1. Initial MMIO Bus /// 2. Prepare the replaceable information of block and network devices. /// /// # Arguments /// /// * `sys_mem` - guest memory. pub fn new(sys_mem: Arc<AddressSpace>) -> Self { let mut bus = Bus { devices: Vec::new(), replaceable_info: MmioReplaceableInfo::new(), }; for _ in 0..MMIO_REPLACEABLE_BLK_NR { let block = Arc::new(Mutex::new(Block::new())); let device = Arc::new(Mutex::new(VirtioMmioDevice::new(sys_mem.clone(), block))); if let Ok(dev) = bus.attach_device(device.clone()) { bus.replaceable_info .devices .lock() .unwrap() .push(MmioReplaceableDevInfo { device: dev, id: "".to_string(), used: false, }); } } for _ in 0..MMIO_REPLACEABLE_NET_NR { let net = Arc::new(Mutex::new(Net::new())); let device = Arc::new(Mutex::new(VirtioMmioDevice::new(sys_mem.clone(), net))); if let Ok(dev) = bus.attach_device(device.clone()) { bus.replaceable_info .devices .lock() .unwrap() .push(MmioReplaceableDevInfo { device: dev, id: "".to_string(), used: false, }); } } bus } /// Attach a MMIO device to Bus. /// /// # Arguments /// /// * `device` - MMIO device. /// /// # Errors /// /// Return Error if irq number exceed the limit as Arch spec defined. pub fn attach_device<T: 'static + MmioDeviceOps>( &mut self, device: Arc<Mutex<T>>, ) -> Result<MmioDevice> { let device_type = device.lock().unwrap().get_type(); let index = self.devices.len(); let resource = match device_type { #[cfg(target_arch = "aarch64")] DeviceType::RTC => DeviceResource { addr: MEM_LAYOUT[LayoutEntryType::Rtc as usize].0, size: MEM_LAYOUT[LayoutEntryType::Rtc as usize].1, irq: IRQ_RANGE.0 + index as u32, dev_type: device_type, }, DeviceType::SERIAL => { #[cfg(target_arch = "x86_64")] { DeviceResource { addr: MMIO_SERIAL_ADDR, size: 8, irq: MMIO_SERIAL_IRQ, dev_type: device_type, } } #[cfg(target_arch = "aarch64")] { DeviceResource { addr: MEM_LAYOUT[LayoutEntryType::Uart as usize].0, size: MEM_LAYOUT[LayoutEntryType::Uart as usize].1, irq: MMIO_SERIAL_IRQ, dev_type: device_type, } } } _ => DeviceResource { addr: MMIO_BASE + index as u64 * MMIO_LEN, size: MMIO_LEN, irq: IRQ_RANGE.0 + index as u32, dev_type: device_type, }, }; if resource.irq > IRQ_RANGE.1 { bail!( "irq {} exceed max value {}, index: {} type: {:?}", resource.irq, IRQ_RANGE.1, index, device_type ); } let mmio_dev = MmioDevice::new(device, resource); self.devices.push(mmio_dev.clone()); Ok(mmio_dev) } /// Get the information of all devices inserted in bus. #[cfg(target_arch = "aarch64")] pub fn get_devices_info(&self) -> Vec<DeviceResource> { let mut infos = Vec::new(); for dev in self.devices.iter() { infos.push(dev.get_resource()) } infos } /// Get an unused entry of replaceable_info, then fill the fields and mark it as `used`. /// /// # Arguments /// /// * `id` - Device id. /// * `path` - Related backend device path. /// * `dev_type` - MMIO device type. /// /// # Errors /// /// Returns Error if the device number exceed the Max count. pub fn fill_replaceable_device( &mut self, id: &str, dev_config: Arc<dyn ConfigCheck>, dev_type: DeviceType, ) -> Result<()> { let index = match dev_type { DeviceType::BLK => { let index = self.replaceable_info.block_count; if index >= MMIO_REPLACEABLE_BLK_NR { bail!( "Index {} is out of bounds {} for block to fill replaceable device", index, MMIO_REPLACEABLE_BLK_NR, ); } self.replaceable_info.block_count += 1; index } DeviceType::NET => { let index = self.replaceable_info.net_count + MMIO_REPLACEABLE_BLK_NR; if index >= MMIO_REPLACEABLE_BLK_NR + MMIO_REPLACEABLE_NET_NR { bail!( "Index {} is out of bounds {} for net to fill replaceable device", index, MMIO_REPLACEABLE_BLK_NR + MMIO_REPLACEABLE_NET_NR, ); } self.replaceable_info.net_count += 1; index } _ => { bail!("Unsupported replaceable device type to fill replaceable device, id: {} type: {:?}", id, dev_type); } }; let mut replaceable_devices = self.replaceable_info.devices.lock().unwrap(); if let Some(device_info) = replaceable_devices.get_mut(index) { if device_info.used { return Err(format!("The index{} is used, {}", index, id).into()); } else { device_info.id = id.to_string(); device_info.used = true; device_info.device.update_config(Some(dev_config.clone()))?; } } self.add_replaceable_config(id.to_string(), dev_config)?; Ok(()) } /// Add new config into replaceable_info configs arrays. /// /// # Arguments /// /// * `id` - Device id. /// * `path` - Related backend device path. pub fn add_replaceable_config( &self, id: String, dev_config: Arc<dyn ConfigCheck>, ) -> Result<()> { let mut configs_lock = self.replaceable_info.configs.lock().unwrap(); if configs_lock.len() >= MMIO_REPLACEABLE_BLK_NR + MMIO_REPLACEABLE_NET_NR { bail!( "The size {} of replaceable configs extend the max size {}, id {}.", configs_lock.len(), MMIO_REPLACEABLE_BLK_NR + MMIO_REPLACEABLE_NET_NR, id, ); } for config in configs_lock.iter() { if config.id == id { bail!("Add the id {} repeatedly", id); } } let config = MmioReplaceableConfig { id, dev_config }; configs_lock.push(config); Ok(()) } /// Get an unused entry of replaceable_info which is indexed by `slot`, /// then update the fields and mark it as `used`. /// /// # Arguments /// /// * `id` - Device id. /// * `driver` - Driver type passed in by HotPlug. /// * `slot` - The index of replaceable_info entries. /// /// # Errors /// /// Returns Error if the entry is already used. pub fn add_replaceable_device(&self, id: &str, driver: &str, slot: usize) -> Result<()> { let index = if driver.contains("net") { if slot >= MMIO_REPLACEABLE_NET_NR { bail!( "Index {} is out of bounds {} for net to add replaceable device", slot, MMIO_REPLACEABLE_NET_NR ); } slot + MMIO_REPLACEABLE_BLK_NR } else if driver.contains("blk") { if slot >= MMIO_REPLACEABLE_BLK_NR { bail!( "Index {} is out of bounds {} for block to add replaceable device", slot, MMIO_REPLACEABLE_BLK_NR ); } slot } else { bail!( "Unsupported replaceable device type to add replaceable device, id: {} driver: {}", id, driver, ); }; let configs_lock = self.replaceable_info.configs.lock().unwrap(); // find the configuration by id let mut dev_config = None; for config in configs_lock.iter() { if config.id == id { dev_config = Some(config.dev_config.clone()); } } if dev_config.is_none() { bail!( "Failed to find the configuration to add replaceable device, id: {} driver: {}", id, driver ); } // find the replaceable device and replace it let mut replaceable_devices = self.replaceable_info.devices.lock().unwrap(); if let Some(device_info) = replaceable_devices.get_mut(index) { if device_info.used { bail!( "The slot {} is already used for adding replaceable device, {}", slot, id ); } else { device_info.id = id.to_string(); device_info.used = true; device_info.device.update_config(dev_config)?; } } Ok(()) } /// Find the entry of replaceable_info which is specified by `id`, /// then update the fields and mark it as `unused`. /// /// # Arguments /// /// * `id` - Device id. pub fn del_replaceable_device(&self, id: &str) -> Result<String> { // find the index of configuration by name and remove it let mut is_exist = false; let mut configs_lock = self.replaceable_info.configs.lock().unwrap(); for (index, config) in configs_lock.iter().enumerate() { if config.id == id { configs_lock.remove(index); is_exist = true; break; } } // set the status of the device to 'unused' let mut replaceable_devices = self.replaceable_info.devices.lock().unwrap(); for device_info in replaceable_devices.iter_mut() { if device_info.id == id { device_info.id = "".to_string(); device_info.used = false; device_info.device.update_config(None)?; } } if !is_exist { bail!("Device {} not found", id); } Ok(id.to_string()) } /// Realize all the devices inserted in this Bus. /// /// # 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_devices( &self, vm_fd: &VmFd, bs: &Arc<Mutex<BootSource>>, sys_mem: &Arc<AddressSpace>, #[cfg(target_arch = "x86_64")] sys_io: Arc<AddressSpace>, ) -> Result<()> { for device in &self.devices { device .realize( vm_fd, &bs, &sys_mem, #[cfg(target_arch = "x86_64")] sys_io.clone(), ) .chain_err(|| "Failed to realize mmio device")?; } Ok(()) } }