diff --git a/acpi/src/acpi_table.rs b/acpi/src/acpi_table.rs index 1577fd5b550a190e964072d8f33d407d67718881..72c205c755bc18d41ebf360cf35e41ab8c048158 100644 --- a/acpi/src/acpi_table.rs +++ b/acpi/src/acpi_table.rs @@ -646,3 +646,9 @@ pub mod madt_subtable { } } } + +/// This module describes ACPI MADT's sub-tables on riscv64 platform. +#[cfg(target_arch = "riscv64")] +pub mod madt_subtable { + // TODO +} diff --git a/boot_loader/src/lib.rs b/boot_loader/src/lib.rs index 46955e83f8cd040d6995d666460e3160cc02f4bb..9aa2888200948c3662bd5c109e9c2412853414f1 100644 --- a/boot_loader/src/lib.rs +++ b/boot_loader/src/lib.rs @@ -25,6 +25,7 @@ //! //! - `x86_64` //! - `aarch64` +//! - `riscv64` //! //! ## Examples //! @@ -87,6 +88,8 @@ #[cfg(target_arch = "aarch64")] mod aarch64; pub mod error; +#[cfg(target_arch = "riscv64")] +pub mod riscv64; #[cfg(target_arch = "x86_64")] mod x86_64; @@ -98,6 +101,12 @@ pub use aarch64::AArch64BootLoader as BootLoader; pub use aarch64::AArch64BootLoaderConfig as BootLoaderConfig; pub use error::BootLoaderError; +#[cfg(target_arch = "riscv64")] +pub use riscv64::load_linux; +#[cfg(target_arch = "riscv64")] +pub use riscv64::RISCVBootLoader as BootLoader; +#[cfg(target_arch = "riscv64")] +pub use riscv64::RISCVBootLoaderConfig as BootLoaderConfig; #[cfg(target_arch = "x86_64")] pub use x86_64::load_linux; #[cfg(target_arch = "x86_64")] diff --git a/boot_loader/src/riscv64/mod.rs b/boot_loader/src/riscv64/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..773ff262e2e069ac269ce1b83ee655f4a2db8b8c --- /dev/null +++ b/boot_loader/src/riscv64/mod.rs @@ -0,0 +1,202 @@ +// Copyright (c) 2024 Institute of Software, CAS. 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::fs::File; +use std::io::Read; +use std::path::{Path, PathBuf}; +use std::sync::{Arc, Mutex}; + +use crate::error::BootLoaderError; +use address_space::{AddressSpace, GuestAddress}; +use anyhow::{anyhow, Context, Result}; +use devices::legacy::{error::LegacyError as FwcfgErrorKind, FwCfgEntryType, FwCfgOps}; +use log::info; +use util::byte_code::ByteCode; + +const RISCV64_KERNEL_OFFSET: u64 = 0x20_0000; +const SZ_4M: u64 = 0x00400000; + +/// Boot loader config used for riscv. +#[derive(Default, Debug)] +pub struct RISCVBootLoaderConfig { + /// Path of kernel image. + pub kernel: Option, + /// Path of initrd image. + pub initrd: Option, + /// Start address of guest memory. + pub mem_start: u64, +} + +/// The start address for `kernel image`, `initrd image` and `dtb` in guest memory. +pub struct RISCVBootLoader { + /// PC register on riscv platform. + pub boot_pc: u64, + /// Start address for `initrd image` in guest memory. + pub initrd_start: u64, + /// Initrd file size, 0 means no initrd file. + pub initrd_size: u64, + /// Start address for `dtb` in guest memory. + pub dtb_start: u64, +} + +fn load_kernel( + fwcfg: Option<&Arc>>, + kernel_start: u64, + kernel_path: &Path, + sys_mem: &Arc, +) -> Result { + let mut kernel_image = + File::open(kernel_path).with_context(|| anyhow!(BootLoaderError::BootLoaderOpenKernel))?; + let kernel_size = kernel_image.metadata().unwrap().len(); + let kernel_end = kernel_start + kernel_size; + + if let Some(fw_cfg) = fwcfg { + let mut kernel_data = Vec::new(); + kernel_image.read_to_end(&mut kernel_data)?; + let mut lock_dev = fw_cfg.lock().unwrap(); + lock_dev + .add_data_entry( + FwCfgEntryType::KernelSize, + (kernel_size as u32).as_bytes().to_vec(), + ) + .with_context(|| anyhow!(FwcfgErrorKind::AddEntryErr("KernelSize".to_string())))?; + lock_dev + .add_data_entry(FwCfgEntryType::KernelData, kernel_data) + .with_context(|| anyhow!(FwcfgErrorKind::AddEntryErr("KernelData".to_string())))?; + } else { + if sys_mem + .memory_end_address() + .raw_value() + .checked_sub(kernel_end) + .is_none() + { + return Err(anyhow!(BootLoaderError::KernelOverflow( + kernel_start, + kernel_size + ))); + } + sys_mem + .write(&mut kernel_image, GuestAddress(kernel_start), kernel_size) + .with_context(|| "Fail to write kernel to guest memory")?; + } + Ok(kernel_end) +} + +fn load_initrd( + fwcfg: Option<&Arc>>, + initrd_path: &Path, + sys_mem: &Arc, + kernel_end: u64, +) -> Result<(u64, u64)> { + let mut initrd_image = + File::open(initrd_path).with_context(|| anyhow!(BootLoaderError::BootLoaderOpenInitrd))?; + let initrd_size = initrd_image.metadata().unwrap().len(); + + let initrd_start = if let Some(addr) = sys_mem + .memory_end_address() + .raw_value() + .checked_sub(initrd_size) + .filter(|addr| addr >= &kernel_end) + { + addr + } else { + return Err(anyhow!(BootLoaderError::InitrdOverflow( + kernel_end, + initrd_size + ))); + }; + + if let Some(fw_cfg) = fwcfg { + let mut initrd_data = Vec::new(); + initrd_image.read_to_end(&mut initrd_data)?; + let mut lock_dev = fw_cfg.lock().unwrap(); + lock_dev + .add_data_entry( + FwCfgEntryType::InitrdAddr, + (initrd_start as u32).as_bytes().to_vec(), + ) + .with_context(|| anyhow!(FwcfgErrorKind::AddEntryErr("InitrdAddr".to_string())))?; + lock_dev + .add_data_entry( + FwCfgEntryType::InitrdSize, + (initrd_size as u32).as_bytes().to_vec(), + ) + .with_context(|| anyhow!(FwcfgErrorKind::AddEntryErr("InitrdSize".to_string())))?; + lock_dev + .add_data_entry(FwCfgEntryType::InitrdData, initrd_data) + .with_context(|| anyhow!(FwcfgErrorKind::AddEntryErr("InitrdData".to_string())))?; + } else { + sys_mem + .write(&mut initrd_image, GuestAddress(initrd_start), initrd_size) + .with_context(|| "Fail to write initrd to guest memory")?; + } + + Ok((initrd_start, initrd_size)) +} + +/// Load linux kernel and other boot source to Guest Memory. +/// +/// # Steps +/// +/// 1. Prepare for linux kernel boot env, return guest memory layout. +/// 2. According guest memory layout, load linux kernel to guest memory. +/// 3. According guest memory layout, load initrd image to guest memory. +/// +/// # Arguments +/// +/// * `config` - boot source config, contains kernel, initrd. +/// * `sys_mem` - guest memory. +/// +/// # Errors +/// +/// Load kernel, initrd to guest memory failed. Boot source is broken or +/// guest memory is abnormal. +pub fn load_linux( + config: &RISCVBootLoaderConfig, + sys_mem: &Arc, + fwcfg: Option<&Arc>>, +) -> Result { + // The memory layout is as follow: + // 1. kernel address: memory start + RISCV64_KERNEL_OFFSET + // 2. dtb address: kernel end + SZ_4M + // 3. initrd address: memory end - inird_size + let kernel_start = config.mem_start + RISCV64_KERNEL_OFFSET; + let boot_pc = if fwcfg.is_some() { 0 } else { kernel_start }; + + let kernel_end = load_kernel( + fwcfg, + kernel_start, + config.kernel.as_ref().unwrap(), + sys_mem, + ) + .with_context(|| "Fail to load kernel")?; + + let dtb_addr = kernel_end + SZ_4M; + + let mut initrd_start = 0_u64; + let mut initrd_size = 0_u64; + if config.initrd.is_some() { + let initrd_tuple = load_initrd(fwcfg, config.initrd.as_ref().unwrap(), sys_mem, kernel_end) + .with_context(|| "Fail to load initrd")?; + initrd_start = initrd_tuple.0; + initrd_size = initrd_tuple.1; + } else { + info!("No initrd image file."); + } + + Ok(RISCVBootLoader { + boot_pc, + initrd_start, + initrd_size, + dtb_start: dtb_addr, + }) +} diff --git a/cpu/src/lib.rs b/cpu/src/lib.rs index 7a1162951d2e4e5141fc22555b0c79d7443aa5b3..3da91b1e97bf272f9138a7338bc45978b282f57a 100644 --- a/cpu/src/lib.rs +++ b/cpu/src/lib.rs @@ -26,12 +26,15 @@ //! //! - `x86_64` //! - `aarch64` +//! - `riscv64` pub mod error; #[allow(clippy::upper_case_acronyms)] #[cfg(target_arch = "aarch64")] mod aarch64; +#[cfg(target_arch = "riscv64")] +mod riscv64; #[cfg(target_arch = "x86_64")] mod x86_64; @@ -52,6 +55,14 @@ pub use aarch64::PMU_INTR; #[cfg(target_arch = "aarch64")] pub use aarch64::PPI_BASE; pub use error::CpuError; +#[cfg(target_arch = "riscv64")] +pub use riscv64::RISCVCPUBootConfig as CPUBootConfig; +#[cfg(target_arch = "riscv64")] +pub use riscv64::RISCVCPUState as ArchCPU; +#[cfg(target_arch = "riscv64")] +pub use riscv64::RISCVCPUTopology as CPUTopology; +#[cfg(target_arch = "riscv64")] +pub use riscv64::RISCVRegsIndex as RegsIndex; #[cfg(target_arch = "x86_64")] pub use x86_64::X86CPUBootConfig as CPUBootConfig; #[cfg(target_arch = "x86_64")] diff --git a/cpu/src/riscv64/mod.rs b/cpu/src/riscv64/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..afd3080f9d2395b502dc4ed82ee1ae7d0837ca4f --- /dev/null +++ b/cpu/src/riscv64/mod.rs @@ -0,0 +1,209 @@ +// Copyright (c) 2024 Institute of Software, CAS. 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 anyhow::{Context, Result}; +use kvm_bindings::{ + kvm_mp_state as MpState, + // AIA CSR registers for KVM_GET_ONE_REG and KVM_SET_ONE_REG + kvm_riscv_aia_csr as AiaCsrs, + // CONFIG registers for KVM_GET_ONE_REG and KVM_SET_ONE_REG + kvm_riscv_config as ConfigRegs, + // CORE registers for KVM_GET_ONE_REG and KVM_SET_ONE_REG + kvm_riscv_core as CoreRegs, + // General CSR registers for KVM_GET_ONE_REG and KVM_SET_ONE_REG + kvm_riscv_csr as Csrs, + // TIMER registers for KVM_GET_ONE_REG and KVM_SET_ONE_REG + kvm_riscv_timer as TimerRegs, + KVM_MP_STATE_RUNNABLE as MP_STATE_RUNNABLE, +}; + +use crate::CPU; +use migration::{ + DeviceStateDesc, FieldDesc, MigrationError, MigrationHook, MigrationManager, StateTransfer, +}; +use migration_derive::{ByteCode, Desc}; +use util::byte_code::ByteCode; + +/// RISCV CPU booting configure information +#[derive(Default, Copy, Clone, Debug)] +pub struct RISCVCPUBootConfig { + pub fdt_addr: u64, + pub boot_pc: u64, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum RISCVRegsIndex { + MpState, + ConfigRegs, + CoreRegs, + AiaCsrs, + Csrs, + TimerRegs, +} + +#[derive(Default, Copy, Clone, Debug)] +pub struct RISCVCPUTopology {} + +impl RISCVCPUTopology { + pub fn new() -> Self { + RISCVCPUTopology::default() + } + + pub fn set_topology(self, _topology: (u8, u8, u8)) -> Self { + self + } +} + +/// riscv64 CPU architect information +#[repr(C)] +#[derive(Copy, Clone, Desc, ByteCode)] +#[desc_version(compat_version = "0.1.0")] +pub struct RISCVCPUState { + /// The vcpu id, `0` means primary CPU. + pub apic_id: u32, + /// Vcpu mpstate register. + pub mp_state: MpState, + /// Vcpu core registers. + pub core_regs: CoreRegs, + /// AIA CSR registers for KVM_GET_ONE_REG and KVM_SET_ONE_REG + pub aia_csrs: AiaCsrs, + /// General CSR registers for KVM_GET_ONE_REG and KVM_SET_ONE_REG + pub csrs: Csrs, + /// CONFIG registers for KVM_GET_ONE_REG and KVM_SET_ONE_REG + pub config_regs: ConfigRegs, + /// TIMER registers for KVM_GET_ONE_REG and KVM_SET_ONE_REG + pub timer_regs: TimerRegs, + /// XLEN to determine RV64 or RV32 + pub xlen: u64, +} + +impl RISCVCPUState { + /// Allocates a new `RISCVCPUState`. + /// + /// # Arguments + /// + /// * `vcpu_id` - ID of this `CPU`. + pub fn new(vcpu_id: u32) -> Self { + let mp_state = MpState { + mp_state: MP_STATE_RUNNABLE, + }; + + RISCVCPUState { + apic_id: vcpu_id, + mp_state, + xlen: 64, + ..Default::default() + } + } + + pub fn set(&mut self, cpu_state: &Arc>) { + let locked_cpu_state = cpu_state.lock().unwrap(); + self.apic_id = locked_cpu_state.apic_id; + self.mp_state = locked_cpu_state.mp_state; + self.core_regs = locked_cpu_state.core_regs; + self.aia_csrs = locked_cpu_state.aia_csrs; + self.csrs = locked_cpu_state.csrs; + self.config_regs = locked_cpu_state.config_regs; + self.timer_regs = locked_cpu_state.timer_regs; + self.xlen = locked_cpu_state.xlen; + } + + /// Set cpu topology + /// + /// # Arguments + /// + /// * `topology` - RISCV CPU Topology + pub fn set_cpu_topology(&mut self, _topology: &RISCVCPUTopology) -> Result<()> { + Ok(()) + } + + /// Get config_regs value. + pub fn config_regs(&self) -> ConfigRegs { + self.config_regs + } + + /// Get core_regs value. + pub fn core_regs(&self) -> CoreRegs { + self.core_regs + } + + /// Get timer_regs value. + pub fn timer_regs(&self) -> TimerRegs { + self.timer_regs + } + + /// Get aia csrs. + pub fn aia_csrs(&self) -> AiaCsrs { + self.aia_csrs + } + + /// Get csrs. + pub fn csrs(&self) -> Csrs { + self.csrs + } + + /// Set core registers before boot + /// See https://elixir.bootlin.com/linux/v6.6/source/Documentation/riscv/boot.rst + pub fn set_core_reg(&mut self, boot_config: &RISCVCPUBootConfig) { + // Set core regs. + self.core_regs.regs.a0 = self.apic_id as u64; + + // Configure boot ip and device tree address, prepare for kernel setup + if self.apic_id == 0 { + self.core_regs.regs.a1 = boot_config.fdt_addr; + self.core_regs.regs.pc = boot_config.boot_pc; + } + } + + /// Get regs_len. + pub fn get_xlen(&self) -> u64 { + self.xlen + } +} + +impl StateTransfer for CPU { + fn get_state_vec(&self) -> Result> { + self.hypervisor_cpu + .get_regs(self.arch_cpu.clone(), RISCVRegsIndex::CoreRegs)?; + self.hypervisor_cpu + .get_regs(self.arch_cpu.clone(), RISCVRegsIndex::MpState)?; + self.hypervisor_cpu + .get_regs(self.arch_cpu.clone(), RISCVRegsIndex::TimerRegs)?; + self.hypervisor_cpu + .get_regs(self.arch_cpu.clone(), RISCVRegsIndex::ConfigRegs)?; + self.hypervisor_cpu + .get_regs(self.arch_cpu.clone(), RISCVRegsIndex::AiaCsrs)?; + self.hypervisor_cpu + .get_regs(self.arch_cpu.clone(), RISCVRegsIndex::Csrs)?; + + Ok(self.arch_cpu.lock().unwrap().as_bytes().to_vec()) + } + + fn set_state(&self, state: &[u8]) -> Result<()> { + let cpu_state = *RISCVCPUState::from_bytes(state) + .with_context(|| MigrationError::FromBytesError("CPU"))?; + + let mut cpu_state_locked = self.arch_cpu.lock().unwrap(); + *cpu_state_locked = cpu_state; + drop(cpu_state_locked); + + Ok(()) + } + + fn get_device_alias(&self) -> u64 { + MigrationManager::get_desc_alias(&RISCVCPUState::descriptor().name).unwrap_or(!0) + } +} + +impl MigrationHook for CPU {} diff --git a/devices/src/acpi/ged.rs b/devices/src/acpi/ged.rs index ea6c52dafcc57090d960bcc807029ddde3aec12e..602a999f797a6d67e0689a423cdbaba5f5cfd7c2 100644 --- a/devices/src/acpi/ged.rs +++ b/devices/src/acpi/ged.rs @@ -228,6 +228,8 @@ impl AmlBuilder for Ged { let irq_base = INTERRUPT_PPIS_COUNT + INTERRUPT_SGIS_COUNT; #[cfg(target_arch = "x86_64")] let irq_base = 0; + #[cfg(target_arch = "riscv64")] + let irq_base = 0; res.append_child(AmlExtendedInterrupt::new( AmlResourceUsage::Consumer, AmlEdgeLevel::Edge, diff --git a/devices/src/interrupt_controller/mod.rs b/devices/src/interrupt_controller/mod.rs index a1fb65d2e3a614e95f09ec5516bbb8c190dbecd2..07a63280375ad768ef4a473d7184e4ac3707e303 100644 --- a/devices/src/interrupt_controller/mod.rs +++ b/devices/src/interrupt_controller/mod.rs @@ -18,16 +18,19 @@ //! //! This module offers support for: //! 1. Create hypervisor-based interrupt controller. -//! 2. Manager lifecycle for `GIC`. +//! 2. Manager lifecycle for in-kernel interrupt chips. //! //! ## Platform Support //! //! - `aarch64` +//! - `riscv64` #[allow(clippy::upper_case_acronyms)] #[cfg(target_arch = "aarch64")] mod aarch64; mod error; +#[cfg(target_arch = "riscv64")] +mod riscv64; #[cfg(target_arch = "aarch64")] pub use aarch64::{ @@ -36,6 +39,8 @@ pub use aarch64::{ GICv3ItsState, GICv3State, GicRedistRegion, InterruptController, GIC_IRQ_INTERNAL, GIC_IRQ_MAX, }; pub use error::InterruptError; +#[cfg(target_arch = "riscv64")] +pub use riscv64::{AIAAccess, AIAConfig, AIADevice, InterruptController, AIA}; use std::sync::Arc; diff --git a/devices/src/interrupt_controller/riscv64/aia.rs b/devices/src/interrupt_controller/riscv64/aia.rs new file mode 100644 index 0000000000000000000000000000000000000000..59ccd213f61aea907bffe7d8ebb9284f83204693 --- /dev/null +++ b/devices/src/interrupt_controller/riscv64/aia.rs @@ -0,0 +1,172 @@ +// Copyright (c) 2024 Institute of Software, CAS. 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 anyhow::{Context, Result}; +use kvm_bindings::{KVM_DEV_RISCV_APLIC_SIZE, KVM_DEV_RISCV_IMSIC_SIZE}; +use log::{error, info}; + +use super::{AIAConfig, AIADevice}; +use machine_manager::machine::{MachineLifecycle, VmState}; +use util::device_tree::{self, FdtBuilder}; + +/// Access wrapper for AIA. +pub trait AIAAccess: Send + Sync { + fn init_aia(&self, nr_irqs: u32, aia_addr: u64) -> Result<()>; + + fn pause(&self) -> Result<()>; +} + +/// A wrapper around creating and managing a `AIA`. +pub struct AIA { + /// The handler for the AIA device to access the corresponding device in hypervisor. + pub hypervisor_aia: Arc, + /// Number of vCPUs, determines the number of redistributor and CPU interface. + pub(crate) vcpu_count: u32, + /// Maximum irq number. + pub(crate) nr_irqs: u32, + /// IMSIC's start addr. + pub(crate) imsic_start_addr_of: Vec, + /// APLIC's start addr. + pub(crate) aplic_start_addr: u64, + /// Base address in the guest physical address space of AIA + dist_base: u64, + /// AIA distributor region size. + dist_size: u64, + /// Lifecycle state for AIA. + state: Arc>, +} + +impl AIA { + pub fn new(hypervisor_aia: Arc, config: &AIAConfig) -> Result { + use kvm_bindings::KVM_DEV_RISCV_IMSIC_SIZE; + // Calculate AIA's IMSIC start address + let mut imsic_start_addr_of = Vec::new(); + for i in 0..config.vcpu_count { + imsic_start_addr_of.push(config.region_range.0 + (i * KVM_DEV_RISCV_IMSIC_SIZE) as u64); + } + let aplic_start_addr = + (config.vcpu_count * KVM_DEV_RISCV_IMSIC_SIZE) as u64 + config.region_range.0; + + Ok(AIA { + hypervisor_aia, + vcpu_count: config.vcpu_count, + nr_irqs: config.max_irq, + imsic_start_addr_of, + aplic_start_addr, + state: Arc::new(Mutex::new(VmState::Created)), + dist_base: config.region_range.0, + dist_size: config.region_range.1, + }) + } +} + +impl MachineLifecycle for AIA { + fn pause(&self) -> bool { + // VM change state will flush REDIST pending tables into guest RAM. + if let Err(e) = self.hypervisor_aia.pause() { + error!( + "Failed to flush REDIST pending tables into guest RAM, error: {:?}", + e + ); + return false; + } + + let mut state = self.state.lock().unwrap(); + *state = VmState::Running; + + true + } + + fn notify_lifecycle(&self, old: VmState, new: VmState) -> bool { + let state = self.state.lock().unwrap(); + if *state != old { + error!("AIA lifecycle error: state check failed."); + return false; + } + drop(state); + + match (old, new) { + (VmState::Running, VmState::Paused) => self.pause(), + _ => true, + } + } +} + +impl AIADevice for AIA { + fn realize(&self) -> Result<()> { + self.hypervisor_aia + .init_aia(self.nr_irqs, self.dist_base) + .with_context(|| "Failed to init AIA")?; + + let mut state = self.state.lock().unwrap(); + *state = VmState::Running; + + Ok(()) + } + + fn generate_fdt(&self, fdt: &mut FdtBuilder) -> Result<()> { + let node = format!("imsics@{:x}", self.imsic_start_addr_of[0] as u32); + let imsics_node_dep = fdt.begin_node(&node)?; + fdt.set_property_u32("phandle", device_tree::AIA_IMSIC_PHANDLE)?; + fdt.set_property_u32("riscv,num-ids", 255)?; + + let mut reg_cells = Vec::new(); + reg_cells.push(0); + reg_cells.push(self.imsic_start_addr_of[0] as u32); + reg_cells.push(0); + reg_cells.push(self.vcpu_count * KVM_DEV_RISCV_IMSIC_SIZE); + info!("imsic regs: {:?}", ®_cells); + fdt.set_property_array_u32("reg", ®_cells)?; + + let mut irq_cells = Vec::new(); + for i in 0..self.vcpu_count { + irq_cells.push(device_tree::INTC_PHANDLE_START + i); + irq_cells.push(9); + } + fdt.set_property_array_u32("interrupts-extended", &irq_cells)?; + + fdt.set_property("msi-controller", &Vec::new())?; + fdt.set_property("interrupt-controller", &Vec::new())?; + fdt.set_property_u32("#interrupt-cells", 0)?; + fdt.set_property_string("compatible", "riscv,imsics")?; + fdt.end_node(imsics_node_dep)?; + + // APLIC + let node = format!("aplic@{:x}", self.aplic_start_addr as u32); + let aplic_node_dep = fdt.begin_node(&node)?; + fdt.set_property_u32("phandle", device_tree::AIA_APLIC_PHANDLE)?; + fdt.set_property_u32("riscv,num-sources", 96)?; + + let mut reg_cells = Vec::new(); + reg_cells.push(0); + reg_cells.push(self.aplic_start_addr as u32); + reg_cells.push(0); + reg_cells.push(KVM_DEV_RISCV_APLIC_SIZE); + info!("aplic regs: {:?}", ®_cells); + fdt.set_property_array_u32("reg", ®_cells)?; + + fdt.set_property_u32("msi-parent", device_tree::AIA_IMSIC_PHANDLE)?; + fdt.set_property("interrupt-controller", &Vec::new())?; + fdt.set_property_u32("#interrupt-cells", 2)?; + fdt.set_property_string("compatible", "riscv,aplic")?; + + fdt.end_node(aplic_node_dep)?; + + Ok(()) + } + + fn reset(&self) -> Result<()> { + Ok(()) + } +} diff --git a/devices/src/interrupt_controller/riscv64/mod.rs b/devices/src/interrupt_controller/riscv64/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..d6da2f7f57e04a8f38cbd203c13a41acd92e89db --- /dev/null +++ b/devices/src/interrupt_controller/riscv64/mod.rs @@ -0,0 +1,94 @@ +// Copyright (c) 2024 Institute of Software, CAS. 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. + +mod aia; + +use std::sync::Arc; + +use anyhow::{Context, Result}; + +pub use aia::{AIAAccess, AIA}; +use machine_manager::machine::{MachineLifecycle, VmState}; +use util::device_tree::{self, FdtBuilder}; + +pub struct AIAConfig { + /// Config number of CPUs handled by the device + pub vcpu_count: u32, + /// Config maximum number of irqs handled by the device + pub max_irq: u32, + /// Config AIA address range + pub region_range: (u64, u64), +} + +impl AIAConfig { + pub fn check_sanity(&self) -> Result<()> { + // Yet implemented + Ok(()) + } +} + +/// A wrapper for `AIA` must perform the function. +pub trait AIADevice: MachineLifecycle { + /// Realize function for hypervisor_based `AIA` device. + fn realize(&self) -> Result<()>; + + /// Reset 'AIA' + fn reset(&self) -> Result<()> { + Ok(()) + } + + /// Constructs `fdt` node for `AIA`. + /// + /// # Arguments + /// + /// * `fdt` - Device tree presented by bytes. + fn generate_fdt(&self, fdt: &mut FdtBuilder) -> Result<()>; +} + +#[derive(Clone)] +/// A wrapper around creating and using a hypervisor-based interrupt controller. +pub struct InterruptController { + aia: Arc, +} + +impl InterruptController { + /// Constructs a new hypervisor_based `InterruptController`. + pub fn new( + aia: Arc, + ) -> InterruptController { + InterruptController { aia } + } + + pub fn realize(&self) -> Result<()> { + self.aia + .realize() + .with_context(|| "Failed to realize AIA")?; + Ok(()) + } + + /// Reset the InterruptController + pub fn reset(&self) -> Result<()> { + self.aia.reset().with_context(|| "Failed to reset AIA") + } + + /// Change `InterruptController` lifecycle state to `Stopped`. + pub fn stop(&self) { + self.aia.notify_lifecycle(VmState::Running, VmState::Paused); + } +} + +impl device_tree::CompileFDT for InterruptController { + fn generate_fdt_node(&self, fdt: &mut FdtBuilder) -> Result<()> { + self.aia.generate_fdt(fdt)?; + Ok(()) + } +} diff --git a/devices/src/legacy/fwcfg.rs b/devices/src/legacy/fwcfg.rs index 45ac6d4bc617dab49a28e6a91e2af2998f49d05a..e8fb9d16fae1b28e73bcd7fcc663ad85b20b0a8a 100644 --- a/devices/src/legacy/fwcfg.rs +++ b/devices/src/legacy/fwcfg.rs @@ -26,7 +26,7 @@ use acpi::{ }; #[cfg(target_arch = "x86_64")] use acpi::{AmlIoDecode, AmlIoResource}; -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] use acpi::{AmlMemory32Fixed, AmlReadAndWrite}; use address_space::{AddressSpace, GuestAddress}; use util::byte_code::ByteCode; @@ -817,7 +817,7 @@ fn get_io_count(data_len: usize) -> usize { } fn common_read( - #[cfg(target_arch = "aarch64")] fwcfg_arch: &mut FwCfgMem, + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] fwcfg_arch: &mut FwCfgMem, #[cfg(target_arch = "x86_64")] fwcfg_arch: &mut FwCfgIO, data: &mut [u8], base: GuestAddress, @@ -834,13 +834,13 @@ fn common_read( } /// FwCfg MMIO Device use for AArch64 platform -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] pub struct FwCfgMem { base: SysBusDevBase, fwcfg: FwCfgCommon, } -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] impl FwCfgMem { pub fn new(sys_mem: Arc) -> Self { FwCfgMem { @@ -867,7 +867,7 @@ impl FwCfgMem { } } -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] fn read_bytes( fwcfg_arch: &mut FwCfgMem, data: &mut [u8], @@ -922,14 +922,14 @@ fn read_bytes( true } -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] impl FwCfgOps for FwCfgMem { fn fw_cfg_common(&mut self) -> &mut FwCfgCommon { &mut self.fwcfg } } -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] impl Device for FwCfgMem { fn device_base(&self) -> &DeviceBase { &self.base.base @@ -940,7 +940,7 @@ impl Device for FwCfgMem { } } -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] impl SysBusDevOps for FwCfgMem { fn sysbusdev_base(&self) -> &SysBusDevBase { &self.base @@ -1248,7 +1248,7 @@ pub trait FwCfgOps: Send + Sync { } } -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] impl AmlBuilder for FwCfgMem { fn aml_bytes(&self) -> Vec { let mut acpi_dev = AmlDevice::new("FWCF"); diff --git a/devices/src/legacy/serial.rs b/devices/src/legacy/serial.rs index 520e929cdb6ee4d85abd5906a59600a0d2c745c1..7598a627a3a65185622b0b27c6eb2f1f1d70f39a 100644 --- a/devices/src/legacy/serial.rs +++ b/devices/src/legacy/serial.rs @@ -26,6 +26,8 @@ use acpi::{ }; use address_space::GuestAddress; use chardev_backend::chardev::{Chardev, InputReceiver}; +#[cfg(target_arch = "riscv64")] +use machine_manager::config::{BootSource, Param}; use machine_manager::{config::SerialConfig, event_loop::EventLoop}; use migration::{ snapshot::SERIAL_SNAPSHOT_ID, DeviceStateDesc, FieldDesc, MigrationError, MigrationHook, @@ -137,6 +139,7 @@ impl Serial { sysbus: &mut SysBus, region_base: u64, region_size: u64, + #[cfg(target_arch = "riscv64")] bs: &Arc>, ) -> Result<()> { self.chardev .lock() @@ -155,6 +158,11 @@ impl Serial { dev.clone(), SERIAL_SNAPSHOT_ID, ); + #[cfg(target_arch = "riscv64")] + bs.lock().unwrap().kernel_cmdline.push(Param { + param_type: "earlycon".to_string(), + value: format!("uart,mmio,0x{:08x}", region_base), + }); let locked_dev = dev.lock().unwrap(); locked_dev.chardev.lock().unwrap().set_receiver(&dev); EventLoop::update_event( diff --git a/devices/src/lib.rs b/devices/src/lib.rs index 0486bf1e87e126b09b7ff7c9fa8c379ccbdcb7c4..e6d43b3469667553e3cba14638e96228d13d7a20 100644 --- a/devices/src/lib.rs +++ b/devices/src/lib.rs @@ -28,6 +28,8 @@ pub mod smbios; pub mod sysbus; pub mod usb; +#[cfg(target_arch = "riscv64")] +pub use interrupt_controller::{AIAAccess, AIAConfig, AIADevice, InterruptController, AIA}; #[cfg(target_arch = "aarch64")] pub use interrupt_controller::{ GICDevice, GICVersion, GICv2, GICv2Access, GICv3, GICv3Access, GICv3ItsAccess, GICv3ItsState, diff --git a/devices/src/misc/pvpanic.rs b/devices/src/misc/pvpanic.rs index a6597954939330cbc048e4f1dfd2bdd763699812..38c2ecbd2914edab665f2f3b43a0d89bb71bf606 100644 --- a/devices/src/misc/pvpanic.rs +++ b/devices/src/misc/pvpanic.rs @@ -36,7 +36,7 @@ use machine_manager::config::{get_pci_df, valid_id}; const PVPANIC_PCI_REVISION_ID: u8 = 1; const PVPANIC_PCI_VENDOR_ID: u16 = PCI_VENDOR_ID_REDHAT_QUMRANET; -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] // param size in Region::init_io_region must greater than 4 const PVPANIC_REG_BAR_SIZE: u64 = 0x4; #[cfg(target_arch = "x86_64")] diff --git a/devices/src/pci/host.rs b/devices/src/pci/host.rs index f395715600e4664544a44d00e00cb8afcee3d608..c14472339d5c50b1d29a1e6b94397cdab3e75fe1 100644 --- a/devices/src/pci/host.rs +++ b/devices/src/pci/host.rs @@ -327,7 +327,7 @@ fn build_osc_for_aml(pci_host_bridge: &mut AmlDevice) { pci_host_bridge.append_child(method); } -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] fn build_osc_for_aml(pci_host_bridge: &mut AmlDevice) { // _OSC means Operating System Capabilities. pci_host_bridge.append_child(AmlNameDecl::new("SUPP", AmlInteger(0))); @@ -412,6 +412,8 @@ fn build_prt_for_aml(pci_bus: &mut AmlDevice, irq: i32) { let irqs = irq as u8 + i; #[cfg(target_arch = "aarch64")] let irqs = irq as u8 + PCI_INTR_BASE + i; + #[cfg(target_arch = "riscv64")] + let irqs = irq as u8 + i; let mut gsi = AmlDevice::new(format!("GSI{}", i).as_str()); gsi.append_child(AmlNameDecl::new("_HID", AmlEisaId::new("PNP0C0F"))); gsi.append_child(AmlNameDecl::new("_UID", AmlString(i.to_string()))); diff --git a/devices/src/sysbus/mod.rs b/devices/src/sysbus/mod.rs index 95c71ec8ba6fa95c52ac9629da21fcefeea9cc2f..8ba191c74568381ba7817c6d0918346f1b001e73 100644 --- a/devices/src/sysbus/mod.rs +++ b/devices/src/sysbus/mod.rs @@ -52,6 +52,11 @@ pub const IRQ_BASE: i32 = 32; #[cfg(target_arch = "aarch64")] pub const IRQ_MAX: i32 = 191; +#[cfg(target_arch = "riscv64")] +pub const IRQ_BASE: i32 = 5; +#[cfg(target_arch = "riscv64")] +pub const IRQ_MAX: i32 = 1023; + pub struct SysBus { #[cfg(target_arch = "x86_64")] pub sys_io: Arc, diff --git a/hypervisor/src/kvm/interrupt.rs b/hypervisor/src/kvm/interrupt.rs index ea9e7790c78d217585d4a5e117a03f570165670c..09e27f08828f90a4bf88fec7889b8a54fe7b3213 100644 --- a/hypervisor/src/kvm/interrupt.rs +++ b/hypervisor/src/kvm/interrupt.rs @@ -34,9 +34,9 @@ const IOAPIC_NUM_PINS: u32 = 24; const PIC_MASTER_PINS: u32 = 8; #[cfg(target_arch = "x86_64")] const PIC_SLACE_PINS: u32 = 8; -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] const IOCHIP_NUM_PINS: u32 = 192; -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] const KVM_IRQCHIP: u32 = 0; /// Return the max number kvm supports. @@ -122,7 +122,7 @@ impl IrqRouteTable { } /// Init irq route table in arch aarch64. - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] pub fn init_irq_route_table(&mut self) { for i in 0..IOCHIP_NUM_PINS { self.irq_routes diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index 3e03a2a95b6f114f797b499d375b7986aad35f03..52debe0b83ddc16ed82a142bc6bc7d8aee5240b0 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -12,6 +12,8 @@ #[cfg(target_arch = "aarch64")] pub mod aarch64; +#[cfg(target_arch = "riscv64")] +pub mod riscv64; #[cfg(target_arch = "x86_64")] pub mod x86_64; @@ -25,6 +27,8 @@ pub mod vm_state; pub use aarch64::gicv2::KvmGICv2; #[cfg(target_arch = "aarch64")] pub use aarch64::gicv3::{KvmGICv3, KvmGICv3Its}; +#[cfg(target_arch = "riscv64")] +pub use riscv64::aia::KvmAIA; use std::collections::HashMap; use std::sync::atomic::{AtomicBool, Ordering}; @@ -63,6 +67,8 @@ use cpu::{ CpuLifecycleState, RegsIndex, CPU, VCPU_TASK_SIGNAL, }; use devices::{pci::MsiVector, IrqManager, LineIrqManager, MsiIrqManager, TriggerMode}; +#[cfg(target_arch = "riscv64")] +use devices::{AIAConfig, InterruptController, AIA}; #[cfg(target_arch = "aarch64")] use devices::{ GICVersion, GICv2, GICv3, GICv3ItsState, GICv3State, ICGICConfig, InterruptController, @@ -73,6 +79,9 @@ use machine_manager::machine::HypervisorType; #[cfg(target_arch = "aarch64")] use migration::snapshot::{GICV3_ITS_SNAPSHOT_ID, GICV3_SNAPSHOT_ID}; use migration::{MigrateMemSlot, MigrateOps, MigrationManager}; +#[cfg(target_arch = "riscv64")] +use riscv64::cpu_caps::RISCVCPUCaps as CPUCaps; +use util::test_helper::is_test_enabled; #[cfg(target_arch = "x86_64")] use x86_64::cpu_caps::X86CPUCaps as CPUCaps; @@ -87,6 +96,7 @@ ioctl_iow_nr!(KVM_GET_DIRTY_LOG, KVMIO, 0x42, kvm_dirty_log); ioctl_iowr_nr!(KVM_CREATE_DEVICE, KVMIO, 0xe0, kvm_create_device); ioctl_io_nr!(KVM_GET_API_VERSION, KVMIO, 0x00); ioctl_ior_nr!(KVM_GET_MP_STATE, KVMIO, 0x98, kvm_mp_state); +#[cfg(not(target_arch = "riscv64"))] ioctl_ior_nr!(KVM_GET_VCPU_EVENTS, KVMIO, 0x9f, kvm_vcpu_events); ioctl_ior_nr!(KVM_GET_CLOCK, KVMIO, 0x7c, kvm_clock_data); ioctl_ior_nr!(KVM_GET_REGS, KVMIO, 0x81, kvm_regs); @@ -97,6 +107,7 @@ ioctl_iow_nr!(KVM_IRQFD, KVMIO, 0x76, kvm_irqfd); ioctl_iowr_nr!(KVM_GET_IRQCHIP, KVMIO, 0x62, kvm_irqchip); ioctl_iow_nr!(KVM_IRQ_LINE, KVMIO, 0x61, kvm_irq_level); ioctl_iow_nr!(KVM_SET_MP_STATE, KVMIO, 0x99, kvm_mp_state); +#[cfg(not(target_arch = "riscv64"))] ioctl_iow_nr!(KVM_SET_VCPU_EVENTS, KVMIO, 0xa0, kvm_vcpu_events); #[allow(clippy::upper_case_acronyms)] @@ -105,7 +116,7 @@ pub struct KvmHypervisor { pub fd: Option, pub vm_fd: Option>, pub mem_slots: Arc>>, - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] pub irq_chip: Option>, } @@ -124,7 +135,7 @@ impl KvmHypervisor { fd: Some(kvm_fd), vm_fd, mem_slots: Arc::new(Mutex::new(HashMap::new())), - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] irq_chip: None, }) } @@ -151,7 +162,9 @@ impl HypervisorOps for KvmHypervisor { ) -> Result<()> { self.arch_init()?; - sys_mem.set_ioevtfd_enabled(true); + if !is_test_enabled() { + sys_mem.set_ioevtfd_enabled(true); + } sys_mem .register_listener(self.create_memory_listener()) @@ -210,6 +223,24 @@ impl HypervisorOps for KvmHypervisor { } } + #[cfg(target_arch = "riscv64")] + fn create_interrupt_controller( + &mut self, + aia_conf: &AIAConfig, + ) -> Result> { + aia_conf.check_sanity()?; + + let create_aia = || { + let hypervisor_aia = KvmAIA::new(self.vm_fd.clone().unwrap(), aia_conf.vcpu_count)?; + let aia = Arc::new(AIA::new(Arc::new(hypervisor_aia), aia_conf)?); + // Need register AIA to MigrationManager here + + Ok(Arc::new(InterruptController::new(aia))) + }; + + create_aia() + } + #[cfg(target_arch = "x86_64")] fn create_interrupt_controller(&mut self) -> Result<()> { self.vm_fd @@ -233,7 +264,7 @@ impl HypervisorOps for KvmHypervisor { .with_context(|| "Create vcpu failed")?; Ok(Arc::new(KvmCpu::new( vcpu_id, - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] self.vm_fd.clone(), vcpu_fd, ))) @@ -363,8 +394,11 @@ impl MigrateOps for KvmHypervisor { pub struct KvmCpu { id: u8, - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] vm_fd: Option>, + #[cfg(target_arch = "riscv64")] + fd: Arc>, + #[cfg(not(target_arch = "riscv64"))] fd: Arc, /// The capability of VCPU. caps: CPUCaps, @@ -376,13 +410,16 @@ pub struct KvmCpu { impl KvmCpu { pub fn new( id: u8, - #[cfg(target_arch = "aarch64")] vm_fd: Option>, + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] vm_fd: Option>, vcpu_fd: VcpuFd, ) -> Self { Self { id, - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] vm_fd, + #[cfg(target_arch = "riscv64")] + fd: Arc::new(Mutex::new(vcpu_fd)), + #[cfg(not(target_arch = "riscv64"))] fd: Arc::new(vcpu_fd), caps: CPUCaps::init_capabilities(), #[cfg(target_arch = "aarch64")] @@ -413,6 +450,7 @@ impl KvmCpu { .upgrade() .with_context(|| CpuError::NoMachineInterface)?; + #[cfg(not(target_arch = "riscv64"))] match self.fd.run() { Ok(run) => { trace::kvm_vcpu_run_exit(cpu.id, &run); @@ -449,7 +487,7 @@ impl KvmCpu { return Ok(false); } - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] VcpuExit::SystemEvent(event, flags) => { if event == kvm_bindings::KVM_SYSTEM_EVENT_SHUTDOWN { info!( @@ -508,6 +546,108 @@ impl KvmCpu { }; } } + + #[cfg(target_arch = "riscv64")] + { + let mut vcpu_fd = self.fd.lock().unwrap(); + let res = vcpu_fd.run(); + match res { + Ok(run) => { + trace::kvm_vcpu_run_exit(cpu.id, &run); + match run { + #[cfg(target_arch = "x86_64")] + VcpuExit::IoIn(addr, data) => { + vm.lock().unwrap().pio_in(u64::from(addr), data); + } + #[cfg(target_arch = "x86_64")] + VcpuExit::IoOut(addr, data) => { + #[cfg(feature = "boot_time")] + capture_boot_signal(addr as u64, data); + + vm.lock().unwrap().pio_out(u64::from(addr), data); + } + VcpuExit::MmioRead(addr, data) => { + vm.lock().unwrap().mmio_read(addr, data); + } + VcpuExit::MmioWrite(addr, data) => { + #[cfg(all(target_arch = "aarch64", feature = "boot_time"))] + capture_boot_signal(addr, data); + + vm.lock().unwrap().mmio_write(addr, data); + } + #[cfg(target_arch = "x86_64")] + VcpuExit::Hlt => { + info!("Vcpu{} received KVM_EXIT_HLT signal", cpu.id); + return Err(anyhow!(CpuError::VcpuHltEvent(cpu.id))); + } + #[cfg(target_arch = "x86_64")] + VcpuExit::Shutdown => { + info!("Vcpu{} received an KVM_EXIT_SHUTDOWN signal", cpu.id); + cpu.guest_shutdown()?; + + return Ok(false); + } + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + VcpuExit::SystemEvent(event, flags) => { + if event == kvm_bindings::KVM_SYSTEM_EVENT_SHUTDOWN { + info!( + "Vcpu{} received an KVM_SYSTEM_EVENT_SHUTDOWN signal", + cpu.id() + ); + cpu.guest_shutdown() + .with_context(|| "Some error occurred in guest shutdown")?; + return Ok(true); + } else if event == kvm_bindings::KVM_SYSTEM_EVENT_RESET { + info!("Vcpu{} received an KVM_SYSTEM_EVENT_RESET signal", cpu.id()); + cpu.guest_reset() + .with_context(|| "Some error occurred in guest reset")?; + return Ok(true); + } else { + error!( + "Vcpu{} received unexpected system event with type 0x{:x}, flags 0x{:?}", + cpu.id(), + event, + flags + ); + } + return Ok(false); + } + VcpuExit::FailEntry(reason, cpuid) => { + info!( + "Vcpu{} received KVM_EXIT_FAIL_ENTRY signal. the vcpu could not be run due to unknown reasons({})", + cpuid, reason + ); + return Ok(false); + } + VcpuExit::InternalError => { + info!("Vcpu{} received KVM_EXIT_INTERNAL_ERROR signal", cpu.id()); + return Ok(false); + } + r => { + return Err(anyhow!(CpuError::VcpuExitReason( + cpu.id(), + format!("{:?}", r) + ))); + } + } + } + Err(ref e) => { + match e.errno() { + libc::EAGAIN => {} + libc::EINTR => { + self.fd.lock().unwrap().set_kvm_immediate_exit(0); + } + _ => { + return Err(anyhow!(CpuError::UnhandledHypervisorExit( + cpu.id(), + e.errno() + ))); + } + }; + } + } + } + Ok(true) } @@ -544,10 +684,17 @@ impl CPUHypervisorOps for KvmCpu { boot_config: &CPUBootConfig, #[cfg(target_arch = "aarch64")] vcpu_config: &CPUFeatures, ) -> Result<()> { + #[cfg(target_arch = "riscv64")] + { + arch_cpu.lock().unwrap().timer_regs = self.get_timer_regs()?; + arch_cpu.lock().unwrap().config_regs = self.get_config_regs()?; + } #[cfg(target_arch = "aarch64")] return self.arch_set_boot_config(arch_cpu, boot_config, vcpu_config); #[cfg(target_arch = "x86_64")] return self.arch_set_boot_config(arch_cpu, boot_config); + #[cfg(target_arch = "riscv64")] + return self.arch_set_boot_config(arch_cpu, boot_config); } fn get_one_reg(&self, reg_id: u64) -> Result { @@ -628,6 +775,13 @@ impl CPUHypervisorOps for KvmCpu { Ok(()) } + #[cfg(target_arch = "riscv64")] + fn set_hypervisor_exit(&self) -> Result<()> { + self.fd.lock().unwrap().set_kvm_immediate_exit(1); + Ok(()) + } + + #[cfg(not(target_arch = "riscv64"))] fn set_hypervisor_exit(&self) -> Result<()> { self.fd.set_kvm_immediate_exit(1); Ok(()) @@ -750,6 +904,11 @@ impl KVMInterruptManager { let irqtype = KVM_ARM_IRQ_TYPE_SPI; irqtype << KVM_ARM_IRQ_TYPE_SHIFT | irq } + + #[cfg(target_arch = "riscv64")] + pub fn arch_map_irq(&self, gsi: u32) -> u32 { + gsi + } } impl LineIrqManager for KVMInterruptManager { @@ -879,7 +1038,7 @@ impl MsiIrqManager for KVMInterruptManager { trace::kvm_trigger_irqfd(irq_fd.as_ref().unwrap()); irq_fd.unwrap().write(1)?; } else { - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] let flags: u32 = kvm_bindings::KVM_MSI_VALID_DEVID; #[cfg(target_arch = "x86_64")] let flags: u32 = 0; @@ -975,7 +1134,7 @@ mod test { } fn mmio_read(&self, addr: u64, data: &mut [u8]) -> bool { - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] { data[3] = 0x0; data[2] = 0x0; @@ -1113,7 +1272,7 @@ mod test { let vcpu_fd = kvm_hyp.vm_fd.as_ref().unwrap().create_vcpu(0).unwrap(); let hypervisor_cpu = Arc::new(KvmCpu::new( 0, - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] kvm_hyp.vm_fd.clone(), vcpu_fd, )); diff --git a/hypervisor/src/kvm/riscv64/aia.rs b/hypervisor/src/kvm/riscv64/aia.rs new file mode 100644 index 0000000000000000000000000000000000000000..19c62aa17b362dba48e09e5f7793afcc7ef5d948 --- /dev/null +++ b/hypervisor/src/kvm/riscv64/aia.rs @@ -0,0 +1,147 @@ +// Copyright (c) 2024 Institute of Software, CAS. 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; + +use anyhow::{Context, Ok, Result}; +use kvm_ioctls::{DeviceFd, VmFd}; + +use super::KvmDevice; +use devices::AIAAccess; + +pub struct KvmAIA { + fd: DeviceFd, + vcpu_count: u32, +} + +impl KvmAIA { + pub fn new(vm_fd: Arc, vcpu_count: u32) -> Result { + let mut aia_device = kvm_bindings::kvm_create_device { + type_: kvm_bindings::kvm_device_type_KVM_DEV_TYPE_RISCV_AIA, + fd: 0, + flags: 0, + }; + + let aia_fd = vm_fd.create_device(&mut aia_device).unwrap(); + + Ok(Self { + fd: aia_fd, + vcpu_count, + }) + } +} + +impl AIAAccess for KvmAIA { + fn init_aia(&self, nr_irqs: u32, aia_addr: u64) -> Result<()> { + KvmDevice::kvm_device_check(&self.fd, kvm_bindings::KVM_DEV_RISCV_AIA_GRP_CONFIG, 0)?; + + KvmDevice::kvm_device_access( + &self.fd, + kvm_bindings::KVM_DEV_RISCV_AIA_GRP_CONFIG, + u64::from(kvm_bindings::KVM_DEV_RISCV_AIA_CONFIG_SRCS), + // number of allocated irq lines + &nr_irqs as *const u32 as u64, + true, + ) + .with_context(|| "Failed to set number of irq sources of APLIC")?; + + let msis = 255u64; + KvmDevice::kvm_device_access( + &self.fd, + kvm_bindings::KVM_DEV_RISCV_AIA_GRP_CONFIG, + u64::from(kvm_bindings::KVM_DEV_RISCV_AIA_CONFIG_IDS), + &msis as *const u64 as u64, + true, + ) + .with_context(|| "Failed to set number of msi")?; + + // + let socket_bits = 0u64; + KvmDevice::kvm_device_access( + &self.fd, + kvm_bindings::KVM_DEV_RISCV_AIA_GRP_CONFIG, + u64::from(kvm_bindings::KVM_DEV_RISCV_AIA_CONFIG_GROUP_BITS), + &socket_bits as *const u64 as u64, + true, + ) + .with_context(|| "Failed to set group bits")?; + + let group_shift = 24u64; + KvmDevice::kvm_device_access( + &self.fd, + kvm_bindings::KVM_DEV_RISCV_AIA_GRP_CONFIG, + u64::from(kvm_bindings::KVM_DEV_RISCV_AIA_CONFIG_GROUP_SHIFT), + &group_shift as *const u64 as u64, + true, + ) + .with_context(|| "Failed to set group shift")?; + + let guest_bits = 0u64; + KvmDevice::kvm_device_access( + &self.fd, + kvm_bindings::KVM_DEV_RISCV_AIA_GRP_CONFIG, + u64::from(kvm_bindings::KVM_DEV_RISCV_AIA_CONFIG_GUEST_BITS), + &guest_bits as *const u64 as u64, + true, + ) + .with_context(|| "Failed to set guest bits")?; + + // Need complete logic to calculate shift + let shift = 0u32; + KvmDevice::kvm_device_access( + &self.fd, + kvm_bindings::KVM_DEV_RISCV_AIA_GRP_CONFIG, + u64::from(kvm_bindings::KVM_DEV_RISCV_AIA_CONFIG_HART_BITS), + &shift as *const u32 as u64, + true, + ) + .with_context(|| "Failed to set HART index bits for IMSIC")?; + + let aplic_addr = + aia_addr + (self.vcpu_count * kvm_bindings::KVM_DEV_RISCV_IMSIC_SIZE) as u64; + KvmDevice::kvm_device_access( + &self.fd, + kvm_bindings::KVM_DEV_RISCV_AIA_GRP_ADDR, + u64::from(kvm_bindings::KVM_DEV_RISCV_AIA_ADDR_APLIC), + &aplic_addr as *const u64 as u64, + true, + ) + .with_context(|| "Failed to set APLIC base address")?; + + for i in 0..self.vcpu_count { + let imsic = aia_addr + (i * kvm_bindings::KVM_DEV_RISCV_IMSIC_SIZE) as u64; + KvmDevice::kvm_device_access( + &self.fd, + kvm_bindings::KVM_DEV_RISCV_AIA_GRP_ADDR, + u64::from(i + 1), + &imsic as *const u64 as u64, + true, + ) + .with_context(|| "Failed to set IMSIC addr")?; + } + + // Finalize the AIA. + KvmDevice::kvm_device_access( + &self.fd, + kvm_bindings::KVM_DEV_RISCV_AIA_GRP_CTRL, + u64::from(kvm_bindings::KVM_DEV_RISCV_AIA_CTRL_INIT), + 0, + true, + ) + .with_context(|| "KVM failed to finalize AIA") + } + + fn pause(&self) -> Result<()> { + // TODO + Ok(()) + } +} diff --git a/hypervisor/src/kvm/riscv64/config_regs.rs b/hypervisor/src/kvm/riscv64/config_regs.rs new file mode 100644 index 0000000000000000000000000000000000000000..37eacd3f3ac490cee210b8d642da011445cd0c98 --- /dev/null +++ b/hypervisor/src/kvm/riscv64/config_regs.rs @@ -0,0 +1,37 @@ +// Copyright (c) 2024 Institute of Software, CAS. 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::mem::size_of; + +use kvm_bindings::{kvm_riscv_config, KVM_REG_RISCV, KVM_REG_RISCV_CONFIG, KVM_REG_SIZE_U64}; +use util::offset_of; + +/// RISCV cpu config register. +/// See: https://elixir.bootlin.com/linux/v6.0/source/arch/riscv/include/uapi/asm/kvm.h#L49 +pub enum RISCVConfigRegs { + ISA, +} + +impl Into for RISCVConfigRegs { + fn into(self) -> u64 { + let reg_offset = match self { + RISCVConfigRegs::ISA => { + offset_of!(kvm_riscv_config, isa) + } + }; + // calculate reg_id + KVM_REG_RISCV as u64 + | KVM_REG_SIZE_U64 as u64 + | u64::from(KVM_REG_RISCV_CONFIG) + | (reg_offset / size_of::()) as u64 + } +} diff --git a/hypervisor/src/kvm/riscv64/core_regs.rs b/hypervisor/src/kvm/riscv64/core_regs.rs new file mode 100644 index 0000000000000000000000000000000000000000..409c0261e8f9a74e6808bda444e8d5d45ca91169 --- /dev/null +++ b/hypervisor/src/kvm/riscv64/core_regs.rs @@ -0,0 +1,171 @@ +// Copyright (c) 2024 Institute of Software, CAS. 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::mem::size_of; + +use kvm_bindings::{ + kvm_riscv_core, user_regs_struct, KVM_REG_RISCV, KVM_REG_RISCV_CORE, KVM_REG_SIZE_U64, +}; +use util::offset_of; + +/// RISCV cpu core register. +/// See: https://elixir.bootlin.com/linux/v6.0/source/arch/riscv/include/uapi/asm/kvm.h#L54 +/// User-mode register state for core dumps, ptrace, sigcontext +/// See: https://elixir.bootlin.com/linux/v6.0/source/arch/riscv/include/uapi/asm/ptrace.h#L19 +#[allow(dead_code)] +pub enum RISCVCoreRegs { + PC, + RA, + SP, + GP, + TP, + T0, + T1, + T2, + S0, + S1, + A0, + A1, + A2, + A3, + A4, + A5, + A6, + A7, + S2, + S3, + S4, + S5, + S6, + S7, + S8, + S9, + S10, + S11, + T3, + T4, + T5, + T6, + MODE, +} + +impl Into for RISCVCoreRegs { + fn into(self) -> u64 { + let reg_offset = match self { + RISCVCoreRegs::PC => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, pc) + } + RISCVCoreRegs::RA => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, ra) + } + RISCVCoreRegs::SP => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, sp) + } + RISCVCoreRegs::GP => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, gp) + } + RISCVCoreRegs::TP => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, tp) + } + RISCVCoreRegs::T0 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, t0) + } + RISCVCoreRegs::T1 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, t1) + } + RISCVCoreRegs::T2 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, t2) + } + RISCVCoreRegs::S0 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, s0) + } + RISCVCoreRegs::S1 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, s1) + } + RISCVCoreRegs::A0 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, a0) + } + RISCVCoreRegs::A1 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, a1) + } + RISCVCoreRegs::A2 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, a2) + } + RISCVCoreRegs::A3 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, a3) + } + RISCVCoreRegs::A4 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, a4) + } + RISCVCoreRegs::A5 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, a5) + } + RISCVCoreRegs::A6 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, a6) + } + RISCVCoreRegs::A7 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, a7) + } + RISCVCoreRegs::S2 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, s2) + } + RISCVCoreRegs::S3 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, s3) + } + RISCVCoreRegs::S4 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, s4) + } + RISCVCoreRegs::S5 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, s5) + } + RISCVCoreRegs::S6 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, s6) + } + RISCVCoreRegs::S7 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, s7) + } + RISCVCoreRegs::S8 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, s8) + } + RISCVCoreRegs::S9 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, s9) + } + RISCVCoreRegs::S10 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, s10) + } + RISCVCoreRegs::S11 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, s11) + } + RISCVCoreRegs::T3 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, t3) + } + RISCVCoreRegs::T4 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, t4) + } + RISCVCoreRegs::T5 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, t5) + } + RISCVCoreRegs::T6 => { + offset_of!(kvm_riscv_core, regs, user_regs_struct, t6) + } + RISCVCoreRegs::MODE => { + offset_of!(kvm_riscv_core, mode) + } + }; + + // calculate reg_id + KVM_REG_RISCV as u64 + | KVM_REG_SIZE_U64 as u64 + | u64::from(KVM_REG_RISCV_CORE) + | (reg_offset / size_of::()) as u64 + } +} diff --git a/hypervisor/src/kvm/riscv64/cpu_caps.rs b/hypervisor/src/kvm/riscv64/cpu_caps.rs new file mode 100644 index 0000000000000000000000000000000000000000..56bad057507a1a22c894eb6f78bd97142b72dc77 --- /dev/null +++ b/hypervisor/src/kvm/riscv64/cpu_caps.rs @@ -0,0 +1,38 @@ +// Copyright (c) 2024 Institute of Software, CAS. 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 kvm_ioctls::Cap; +use kvm_ioctls::Kvm; + +// Capabilities for RISC-V cpu. +#[derive(Debug, Clone)] +pub struct RISCVCPUCaps { + pub irq_chip: bool, + pub ioevent_fd: bool, + pub irq_fd: bool, + pub user_mem: bool, + pub mp_state: bool, +} + +impl RISCVCPUCaps { + /// Initialize ArmCPUCaps instance. + pub fn init_capabilities() -> Self { + let kvm = Kvm::new().unwrap(); + RISCVCPUCaps { + irq_chip: kvm.check_extension(Cap::Irqchip), + ioevent_fd: kvm.check_extension(Cap::Ioeventfd), + irq_fd: kvm.check_extension(Cap::Irqfd), + user_mem: kvm.check_extension(Cap::UserMemory), + mp_state: kvm.check_extension(Cap::MpState), + } + } +} diff --git a/hypervisor/src/kvm/riscv64/mod.rs b/hypervisor/src/kvm/riscv64/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..1892569ec0e401c66bc57552208527a04b923fe5 --- /dev/null +++ b/hypervisor/src/kvm/riscv64/mod.rs @@ -0,0 +1,448 @@ +// Copyright (c) 2024 Institute of Software, CAS. 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. + +// mod aia_csrs; +mod config_regs; +mod core_regs; +pub mod cpu_caps; +// mod csrs; +pub mod aia; +mod timer_regs; + +use std::sync::{Arc, Mutex}; + +use anyhow::{Context, Result}; +use kvm_bindings::*; +use kvm_ioctls::DeviceFd; +use vmm_sys_util::{ioctl_ioc_nr, ioctl_iow_nr, ioctl_iowr_nr}; + +use self::config_regs::RISCVConfigRegs; +use self::core_regs::RISCVCoreRegs; +use self::timer_regs::RISCVTimerRegs; +use crate::kvm::{KvmCpu, KvmHypervisor}; +use cpu::{ArchCPU, CPUBootConfig, RegsIndex, CPU}; + +pub const KVM_MAX_CPREG_ENTRIES: usize = 500; + +ioctl_iow_nr!(KVM_GET_DEVICE_ATTR, KVMIO, 0xe2, kvm_device_attr); +ioctl_iow_nr!(KVM_GET_ONE_REG, KVMIO, 0xab, kvm_one_reg); +ioctl_iow_nr!(KVM_SET_ONE_REG, KVMIO, 0xac, kvm_one_reg); +ioctl_iowr_nr!(KVM_GET_REG_LIST, KVMIO, 0xb0, kvm_reg_list); +// ioctl_iow_nr!(KVM_ARM_VCPU_INIT, KVMIO, 0xae, kvm_vcpu_init); + +/// A wrapper for kvm_based device check and access. +pub struct KvmDevice; +impl KvmDevice { + fn kvm_device_check(fd: &DeviceFd, group: u32, attr: u64) -> Result<()> { + let attr = kvm_bindings::kvm_device_attr { + group, + attr, + addr: 0, + flags: 0, + }; + fd.has_device_attr(&attr) + .with_context(|| "Failed to check device attributes.")?; + Ok(()) + } + + fn kvm_device_access( + fd: &DeviceFd, + group: u32, + attr: u64, + addr: u64, + write: bool, + ) -> Result<()> { + let attr = kvm_bindings::kvm_device_attr { + group, + attr, + addr, + flags: 0, + }; + + if write { + fd.set_device_attr(&attr) + .with_context(|| "Failed to set device attributes.")?; + } else { + let mut attr = attr; + fd.get_device_attr(&mut attr) + .with_context(|| "Failed to get device attributes.")?; + }; + + Ok(()) + } +} + +impl KvmHypervisor { + pub fn arch_init(&self) -> Result<()> { + Ok(()) + } +} + +impl KvmCpu { + pub fn arch_init_pmu(&self) -> Result<()> { + // let pmu_attr = kvm_device_attr { + // group: KVM_ARM_VCPU_PMU_V3_CTRL, + // attr: KVM_ARM_VCPU_PMU_V3_INIT as u64, + // addr: 0, + // flags: 0, + // }; + // // SAFETY: The fd can be guaranteed to be legal during creation. + // let vcpu_device = unsafe { DeviceFd::from_raw_fd(self.fd.as_raw_fd()) }; + // vcpu_device + // .has_device_attr(&pmu_attr) + // .with_context(|| "Kernel does not support PMU for vCPU")?; + // // Set IRQ 23, PPI 7 for PMU. + // let irq = PMU_INTR + PPI_BASE; + // let pmu_irq_attr = kvm_device_attr { + // group: KVM_ARM_VCPU_PMU_V3_CTRL, + // attr: KVM_ARM_VCPU_PMU_V3_IRQ as u64, + // addr: &irq as *const u32 as u64, + // flags: 0, + // }; + + // vcpu_device + // .set_device_attr(&pmu_irq_attr) + // .with_context(|| "Failed to set IRQ for PMU")?; + // // Init PMU after setting IRQ. + // vcpu_device + // .set_device_attr(&pmu_attr) + // .with_context(|| "Failed to enable PMU for vCPU")?; + // // forget `vcpu_device` to avoid fd close on exit, as DeviceFd is backed by File. + // forget(vcpu_device); + + // TODO + + Ok(()) + } + + pub fn arch_vcpu_init(&self) -> Result<()> { + // self.fd + // .vcpu_init(&self.kvi.lock().unwrap()) + // .with_context(|| "Failed to init kvm vcpu") + Ok(()) + } + + pub fn arch_set_boot_config( + &self, + arch_cpu: Arc>, + boot_config: &CPUBootConfig, + ) -> Result<()> { + // let mut kvi = self.kvi.lock().unwrap(); + // self.vm_fd + // .as_ref() + // .unwrap() + // .get_preferred_target(&mut kvi) + // .with_context(|| "Failed to get kvm vcpu preferred target")?; + + // // support PSCI 0.2 + // // We already checked that the capability is supported. + // kvi.features[0] |= 1 << kvm_bindings::KVM_ARM_VCPU_PSCI_0_2; + // // Non-boot cpus are powered off initially. + // if arch_cpu.lock().unwrap().apic_id != 0 { + // kvi.features[0] |= 1 << kvm_bindings::KVM_ARM_VCPU_POWER_OFF; + // } + + // // Enable PMU from config. + // if vcpu_config.pmu { + // if !self.caps.pmuv3 { + // bail!("PMUv3 is not supported by KVM"); + // } + // kvi.features[0] |= 1 << kvm_bindings::KVM_ARM_VCPU_PMU_V3; + // } + // // Enable SVE from config. + // if vcpu_config.sve { + // if !self.caps.sve { + // bail!("SVE is not supported by KVM"); + // } + // kvi.features[0] |= 1 << kvm_bindings::KVM_ARM_VCPU_SVE; + // } + // drop(kvi); + + arch_cpu.lock().unwrap().set_core_reg(boot_config); + + self.arch_vcpu_init()?; + + // if vcpu_config.sve { + // self.fd + // .vcpu_finalize(&(kvm_bindings::KVM_ARM_VCPU_SVE as i32))?; + // } + + // arch_cpu.lock().unwrap().mpidr = + // self.get_one_reg(KVM_REG_ARM_MPIDR_EL1) + // .with_context(|| "Failed to get mpidr")? as u64; + + // arch_cpu.lock().unwrap().features = *vcpu_config; + + // how to set vcpu features? + + Ok(()) + } + + fn get_one_reg(&self, reg_id: u64) -> Result { + let mut val = [0_u8; 16]; + self.fd.lock().unwrap().get_one_reg(reg_id, &mut val)?; + Ok(u128::from_le_bytes(val)) + } + + fn set_one_reg(&self, reg_id: u64, val: u128) -> Result<()> { + self.fd + .lock() + .unwrap() + .set_one_reg(reg_id, &val.to_le_bytes())?; + Ok(()) + } + + pub fn arch_get_one_reg(&self, reg_id: u64) -> Result { + self.get_one_reg(reg_id) + } + + pub fn arch_get_regs( + &self, + arch_cpu: Arc>, + regs_index: RegsIndex, + ) -> Result<()> { + let mut locked_arch_cpu = arch_cpu.lock().unwrap(); + + match regs_index { + RegsIndex::CoreRegs => { + locked_arch_cpu.core_regs = self.get_core_regs()?; + } + RegsIndex::TimerRegs => { + locked_arch_cpu.timer_regs = self.get_timer_regs()?; + } + RegsIndex::ConfigRegs => { + locked_arch_cpu.config_regs = self.get_config_regs()?; + } + RegsIndex::MpState => { + if self.caps.mp_state { + let mut mp_state = self.fd.lock().unwrap().get_mp_state()?; + if mp_state.mp_state != KVM_MP_STATE_STOPPED { + mp_state.mp_state = KVM_MP_STATE_RUNNABLE; + } + locked_arch_cpu.mp_state = mp_state; + } + } + RegsIndex::AiaCsrs | RegsIndex::Csrs => {} + } + + Ok(()) + } + + pub fn arch_set_regs( + &self, + arch_cpu: Arc>, + regs_index: RegsIndex, + ) -> Result<()> { + let locked_arch_cpu = arch_cpu.lock().unwrap(); + let apic_id = locked_arch_cpu.apic_id; + match regs_index { + RegsIndex::CoreRegs => { + self.set_core_regs(locked_arch_cpu.core_regs) + .with_context(|| format!("Failed to set core register for CPU {}", apic_id))?; + } + RegsIndex::TimerRegs => { + self.set_timer_regs(locked_arch_cpu.timer_regs) + .with_context(|| format!("Failed to set timer register for CPU {}", apic_id))?; + } + RegsIndex::ConfigRegs => { + self.set_config_regs(locked_arch_cpu.config_regs) + .with_context(|| { + format!("Failed to set config register for CPU {}", apic_id) + })?; + } + RegsIndex::MpState => { + if self.caps.mp_state { + self.fd + .lock() + .unwrap() + .set_mp_state(locked_arch_cpu.mp_state) + .with_context(|| format!("Failed to set mpstate for CPU {}", apic_id))?; + } + } + RegsIndex::AiaCsrs | RegsIndex::Csrs => {} + } + + Ok(()) + } + + /// Returns the vcpu's current `core_register`. + /// + /// The register state is gotten from `KVM_GET_ONE_REG` api in KVM. + /// + /// # Arguments + /// + /// * `vcpu_fd` - the VcpuFd in KVM mod. + fn get_core_regs(&self) -> Result { + // vcpu_fd.set_one_reg(RISCVCoreRegs::PC.into(), core_regs.regs.pc as u128)?; + // vcpu_fd.set_one_reg(RISCVCoreRegs::A0.into(), core_regs.regs.a0 as u128)?; + // vcpu_fd.set_one_reg(RISCVCoreRegs::A1.into(), core_regs.regs.a1 as u128)?; + let mut core_regs = kvm_riscv_core::default(); + + core_regs.regs.pc = self.get_one_reg(RISCVCoreRegs::PC.into())? as u64; + core_regs.regs.ra = self.get_one_reg(RISCVCoreRegs::RA.into())? as u64; + core_regs.regs.sp = self.get_one_reg(RISCVCoreRegs::SP.into())? as u64; + core_regs.regs.gp = self.get_one_reg(RISCVCoreRegs::GP.into())? as u64; + core_regs.regs.tp = self.get_one_reg(RISCVCoreRegs::TP.into())? as u64; + core_regs.regs.t0 = self.get_one_reg(RISCVCoreRegs::T0.into())? as u64; + core_regs.regs.t1 = self.get_one_reg(RISCVCoreRegs::T1.into())? as u64; + core_regs.regs.t2 = self.get_one_reg(RISCVCoreRegs::T2.into())? as u64; + core_regs.regs.t3 = self.get_one_reg(RISCVCoreRegs::T3.into())? as u64; + core_regs.regs.t4 = self.get_one_reg(RISCVCoreRegs::T4.into())? as u64; + core_regs.regs.t5 = self.get_one_reg(RISCVCoreRegs::T5.into())? as u64; + core_regs.regs.t6 = self.get_one_reg(RISCVCoreRegs::T6.into())? as u64; + core_regs.regs.a0 = self.get_one_reg(RISCVCoreRegs::A0.into())? as u64; + core_regs.regs.a1 = self.get_one_reg(RISCVCoreRegs::A1.into())? as u64; + core_regs.regs.a2 = self.get_one_reg(RISCVCoreRegs::A2.into())? as u64; + core_regs.regs.a3 = self.get_one_reg(RISCVCoreRegs::A3.into())? as u64; + core_regs.regs.a4 = self.get_one_reg(RISCVCoreRegs::A4.into())? as u64; + core_regs.regs.a5 = self.get_one_reg(RISCVCoreRegs::A5.into())? as u64; + core_regs.regs.a6 = self.get_one_reg(RISCVCoreRegs::A6.into())? as u64; + core_regs.regs.a7 = self.get_one_reg(RISCVCoreRegs::A7.into())? as u64; + core_regs.regs.s0 = self.get_one_reg(RISCVCoreRegs::S0.into())? as u64; + core_regs.regs.s1 = self.get_one_reg(RISCVCoreRegs::S1.into())? as u64; + core_regs.regs.s2 = self.get_one_reg(RISCVCoreRegs::S2.into())? as u64; + core_regs.regs.s3 = self.get_one_reg(RISCVCoreRegs::S3.into())? as u64; + core_regs.regs.s4 = self.get_one_reg(RISCVCoreRegs::S4.into())? as u64; + core_regs.regs.s5 = self.get_one_reg(RISCVCoreRegs::S5.into())? as u64; + core_regs.regs.s6 = self.get_one_reg(RISCVCoreRegs::S6.into())? as u64; + core_regs.regs.s7 = self.get_one_reg(RISCVCoreRegs::S7.into())? as u64; + core_regs.regs.s8 = self.get_one_reg(RISCVCoreRegs::S8.into())? as u64; + core_regs.regs.s9 = self.get_one_reg(RISCVCoreRegs::S9.into())? as u64; + core_regs.regs.s10 = self.get_one_reg(RISCVCoreRegs::S10.into())? as u64; + core_regs.regs.s11 = self.get_one_reg(RISCVCoreRegs::S11.into())? as u64; + + Ok(core_regs) + } + + /// Sets the vcpu's current "core_register" + /// + /// The register state is gotten from `KVM_SET_ONE_REG` api in KVM. + /// + /// # Arguments + /// + /// * `vcpu_fd` - the VcpuFd in KVM mod. + /// * `core_regs` - kvm_regs state to be written. + fn set_core_regs(&self, core_regs: kvm_riscv_core) -> Result<()> { + self.set_one_reg(RISCVCoreRegs::PC.into(), core_regs.regs.pc as u128)?; + //self.set_one_reg(RISCVCoreRegs::RA.into(), core_regs.regs.ra as u128)?; + //self.set_one_reg(RISCVCoreRegs::SP.into(), core_regs.regs.sp as u128)?; + //self.set_one_reg(RISCVCoreRegs::GP.into(), core_regs.regs.gp as u128)?; + //self.set_one_reg(RISCVCoreRegs::TP.into(), core_regs.regs.tp as u128)?; + //self.set_one_reg(RISCVCoreRegs::T0.into(), core_regs.regs.t0 as u128)?; + //self.set_one_reg(RISCVCoreRegs::T1.into(), core_regs.regs.t1 as u128)?; + //self.set_one_reg(RISCVCoreRegs::T2.into(), core_regs.regs.t2 as u128)?; + //self.set_one_reg(RISCVCoreRegs::T3.into(), core_regs.regs.t3 as u128)?; + //self.set_one_reg(RISCVCoreRegs::T4.into(), core_regs.regs.t4 as u128)?; + //self.set_one_reg(RISCVCoreRegs::T5.into(), core_regs.regs.t5 as u128)?; + //self.set_one_reg(RISCVCoreRegs::T6.into(), core_regs.regs.t6 as u128)?; + self.set_one_reg(RISCVCoreRegs::A0.into(), core_regs.regs.a0 as u128)?; + self.set_one_reg(RISCVCoreRegs::A1.into(), core_regs.regs.a1 as u128)?; + //self.set_one_reg(RISCVCoreRegs::A2.into(), core_regs.regs.a2 as u128)?; + //self.set_one_reg(RISCVCoreRegs::A3.into(), core_regs.regs.a3 as u128)?; + //self.set_one_reg(RISCVCoreRegs::A4.into(), core_regs.regs.a4 as u128)?; + //self.set_one_reg(RISCVCoreRegs::A5.into(), core_regs.regs.a5 as u128)?; + //self.set_one_reg(RISCVCoreRegs::A6.into(), core_regs.regs.a6 as u128)?; + //self.set_one_reg(RISCVCoreRegs::A7.into(), core_regs.regs.a7 as u128)?; + //self.set_one_reg(RISCVCoreRegs::S0.into(), core_regs.regs.s0 as u128)?; + //self.set_one_reg(RISCVCoreRegs::S1.into(), core_regs.regs.s1 as u128)?; + //self.set_one_reg(RISCVCoreRegs::S2.into(), core_regs.regs.s2 as u128)?; + //self.set_one_reg(RISCVCoreRegs::S3.into(), core_regs.regs.s3 as u128)?; + //self.set_one_reg(RISCVCoreRegs::S4.into(), core_regs.regs.s4 as u128)?; + //self.set_one_reg(RISCVCoreRegs::S5.into(), core_regs.regs.s5 as u128)?; + //self.set_one_reg(RISCVCoreRegs::S6.into(), core_regs.regs.s6 as u128)?; + //self.set_one_reg(RISCVCoreRegs::S7.into(), core_regs.regs.s7 as u128)?; + //self.set_one_reg(RISCVCoreRegs::S8.into(), core_regs.regs.s8 as u128)?; + //self.set_one_reg(RISCVCoreRegs::S9.into(), core_regs.regs.s9 as u128)?; + //self.set_one_reg(RISCVCoreRegs::S10.into(), core_regs.regs.s10 as u128)?; + //self.set_one_reg(RISCVCoreRegs::S11.into(), core_regs.regs.s11 as u128)?; + + Ok(()) + } + + /// Returns the vcpu's current `config_register`. + /// + /// The register state is gotten from `KVM_GET_ONE_REG` api in KVM. + /// + /// # Arguments + /// + /// * `vcpu_fd` - the VcpuFd in KVM mod. + pub fn get_config_regs(&self) -> Result { + let mut config_regs = kvm_riscv_config::default(); + config_regs.isa = self.get_one_reg(RISCVConfigRegs::ISA.into())? as u64; + + Ok(config_regs) + } + + /// Sets the vcpu's current "config_register" + /// + /// The register state is gotten from `KVM_SET_ONE_REG` api in KVM. + /// + /// # Arguments + /// + /// * `vcpu_fd` - the VcpuFd in KVM mod. + /// * `config_regs` - kvm_regs state to be written. + pub fn set_config_regs(&self, config_regs: kvm_riscv_config) -> Result<()> { + self.set_one_reg(RISCVConfigRegs::ISA.into(), config_regs.isa as u128)?; + Ok(()) + } + + /// Returns the vcpu's current `timer_register`. + /// + /// The register state is gotten from `KVM_GET_ONE_REG` api in KVM. + /// + /// # Arguments + /// + /// * `vcpu_fd` - the VcpuFd in KVM mod. + pub fn get_timer_regs(&self) -> Result { + let mut timer_regs = kvm_riscv_timer::default(); + timer_regs.frequency = self.get_one_reg(RISCVTimerRegs::FREQUENCY.into())? as u64; + timer_regs.time = self.get_one_reg(RISCVTimerRegs::TIME.into())? as u64; + timer_regs.compare = self.get_one_reg(RISCVTimerRegs::COMPARE.into())? as u64; + timer_regs.state = self.get_one_reg(RISCVTimerRegs::STATE.into())? as u64; + + Ok(timer_regs) + } + + /// Sets the vcpu's current "timer_register" + /// + /// The register state is gotten from `KVM_SET_ONE_REG` api in KVM. + /// + /// # Arguments + /// + /// * `vcpu_fd` - the VcpuFd in KVM mod. + /// * `timer_regs` - kvm_regs state to be written. + pub fn set_timer_regs(&self, timer_regs: kvm_riscv_timer) -> Result<()> { + self.set_one_reg( + RISCVTimerRegs::FREQUENCY.into(), + timer_regs.frequency as u128, + )?; + self.set_one_reg(RISCVTimerRegs::TIME.into(), timer_regs.time as u128)?; + self.set_one_reg(RISCVTimerRegs::COMPARE.into(), timer_regs.compare as u128)?; + self.set_one_reg(RISCVTimerRegs::STATE.into(), timer_regs.state as u128)?; + Ok(()) + } + + pub fn arch_put_register(&self, cpu: Arc) -> Result<()> { + let arch_cpu = &cpu.arch_cpu; + self.arch_set_regs(arch_cpu.clone(), RegsIndex::CoreRegs)?; + //self.arch_set_regs(arch_cpu.clone(), RegsIndex::TimerRegs)?; + //self.arch_set_regs(arch_cpu.clone(), RegsIndex::ConfigRegs)?; + self.arch_set_regs(arch_cpu.clone(), RegsIndex::MpState)?; + // Put AIA and CSR + Ok(()) + } + + pub fn arch_reset_vcpu(&self, cpu: Arc) -> Result<()> { + cpu.arch_cpu.lock().unwrap().set(&cpu.boot_state()); + self.arch_put_register(cpu) + } +} diff --git a/hypervisor/src/kvm/riscv64/timer_regs.rs b/hypervisor/src/kvm/riscv64/timer_regs.rs new file mode 100644 index 0000000000000000000000000000000000000000..c0a29447ca82cbf673eddcea338e15de205db629 --- /dev/null +++ b/hypervisor/src/kvm/riscv64/timer_regs.rs @@ -0,0 +1,50 @@ +// Copyright (c) 2024 Institute of Software, CAS. 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::mem::size_of; + +use kvm_bindings::{kvm_riscv_timer, KVM_REG_RISCV, KVM_REG_RISCV_TIMER, KVM_REG_SIZE_U64}; +use util::offset_of; + +/// RISCV cpu time register. +/// See: https://elixir.bootlin.com/linux/v6.0/source/arch/riscv/include/uapi/asm/kvm.h#L78 +pub enum RISCVTimerRegs { + FREQUENCY, + TIME, + COMPARE, + STATE, +} + +impl Into for RISCVTimerRegs { + fn into(self) -> u64 { + let reg_offset = match self { + RISCVTimerRegs::FREQUENCY => { + offset_of!(kvm_riscv_timer, frequency) + } + RISCVTimerRegs::TIME => { + offset_of!(kvm_riscv_timer, time) + } + RISCVTimerRegs::COMPARE => { + offset_of!(kvm_riscv_timer, compare) + } + RISCVTimerRegs::STATE => { + offset_of!(kvm_riscv_timer, state) + } + }; + + // calculate reg_id + KVM_REG_RISCV as u64 + | KVM_REG_SIZE_U64 as u64 + | u64::from(KVM_REG_RISCV_TIMER) + | (reg_offset / size_of::()) as u64 + } +} diff --git a/hypervisor/src/lib.rs b/hypervisor/src/lib.rs index 25fc90ef67066a466a1ed8269babfedf8c9bd104..84e5b1880dd7a27db2e038d146a2fb8af7b331cf 100644 --- a/hypervisor/src/lib.rs +++ b/hypervisor/src/lib.rs @@ -14,6 +14,7 @@ pub mod error; pub mod kvm; +#[cfg(not(target_arch = "riscv64"))] pub mod test; pub use error::HypervisorError; @@ -27,6 +28,8 @@ use kvm_ioctls::DeviceFd; use address_space::AddressSpace; use cpu::CPUHypervisorOps; use devices::IrqManager; +#[cfg(target_arch = "riscv64")] +use devices::{AIAConfig, InterruptController}; #[cfg(target_arch = "aarch64")] use devices::{ICGICConfig, InterruptController}; use machine_manager::machine::HypervisorType; @@ -51,6 +54,12 @@ pub trait HypervisorOps: Send + Sync + Any { #[cfg(target_arch = "x86_64")] fn create_interrupt_controller(&mut self) -> Result<()>; + #[cfg(target_arch = "riscv64")] + fn create_interrupt_controller( + &mut self, + aia_conf: &AIAConfig, + ) -> Result>; + fn create_hypervisor_cpu(&self, vcpu_id: u8) -> Result>; diff --git a/machine/src/error.rs b/machine/src/error.rs index c8fb8ee67bae7eb2c5282d4b56ef91a72a00c534..f502b8f7b30ca93ee92c9d5cc45deb853580db2a 100644 --- a/machine/src/error.rs +++ b/machine/src/error.rs @@ -94,10 +94,10 @@ pub enum MachineError { #[cfg(target_arch = "x86_64")] CrtPitErr, #[error("Failed to generate FDT.")] - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] GenFdtErr, #[error("Failed to write FDT: addr={0}, size={1}")] - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] WrtFdtErr(u64, usize), #[error("Failed to register event notifier.")] RegNotifierErr, diff --git a/machine/src/lib.rs b/machine/src/lib.rs index bde75da4cb6aced0fd2d4f7f1e7fe0d54598ca9f..e3ba141e4d4e1b4e92384b2ae65cd02642b3454c 100644 --- a/machine/src/lib.rs +++ b/machine/src/lib.rs @@ -13,6 +13,9 @@ #[cfg(target_arch = "aarch64")] pub mod aarch64; pub mod error; +#[cfg(target_arch = "riscv64")] +pub mod riscv64; +#[cfg(not(target_arch = "riscv64"))] pub mod standard_common; #[cfg(target_arch = "x86_64")] pub mod x86_64; @@ -21,6 +24,7 @@ mod micro_common; pub use crate::error::MachineError; pub use micro_common::LightMachine; +#[cfg(not(target_arch = "riscv64"))] pub use standard_common::StdMachine; use std::collections::{BTreeMap, HashMap}; @@ -69,10 +73,12 @@ use devices::usb::uas::{UsbUas, UsbUasConfig}; use devices::usb::usbhost::{UsbHost, UsbHostConfig}; use devices::usb::xhci::xhci_pci::{XhciConfig, XhciPciDevice}; use devices::usb::UsbDevice; -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] use devices::InterruptController; use devices::ScsiDisk::{ScsiDevConfig, ScsiDevice}; -use hypervisor::{kvm::KvmHypervisor, test::TestHypervisor, HypervisorOps}; +#[cfg(not(target_arch = "riscv64"))] +use hypervisor::test::TestHypervisor; +use hypervisor::{kvm::KvmHypervisor, HypervisorOps}; #[cfg(feature = "usb_camera")] use machine_manager::config::get_cameradev_by_id; use machine_manager::config::{ @@ -121,7 +127,7 @@ pub struct MachineBase { /// `vCPU` devices. cpus: Vec>, /// Interrupt controller device. - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] irq_chip: Option>, /// Memory address space. sys_mem: Arc, @@ -199,6 +205,7 @@ impl MachineBase { hypervisor = kvm_hypervisor.clone(); migration_hypervisor = kvm_hypervisor; } + #[cfg(not(target_arch = "riscv64"))] HypervisorType::Test => { let test_hypervisor = Arc::new(Mutex::new(TestHypervisor::new()?)); hypervisor = test_hypervisor.clone(); @@ -209,7 +216,7 @@ impl MachineBase { Ok(MachineBase { cpu_topo, cpus: Vec::new(), - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] irq_chip: None, sys_mem, #[cfg(target_arch = "x86_64")] @@ -336,7 +343,7 @@ pub trait MachineOps { #[cfg(target_arch = "x86_64")] fn load_boot_source(&self, fwcfg: Option<&Arc>>) -> Result; - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] fn load_boot_source( &self, fwcfg: Option<&Arc>>, @@ -458,6 +465,8 @@ pub trait MachineOps { #[cfg(target_arch = "aarch64")] let arch_cpu = ArchCPU::new(u32::from(vcpu_id)); + #[cfg(target_arch = "riscv64")] + let arch_cpu = ArchCPU::new(u32::from(vcpu_id)); #[cfg(target_arch = "x86_64")] let arch_cpu = ArchCPU::new(u32::from(vcpu_id), u32::from(max_cpus)); @@ -1951,9 +1960,11 @@ pub trait MachineOps { demo_dev.realize() } + #[cfg(not(target_arch = "riscv64"))] /// Return the syscall whitelist for seccomp. fn syscall_whitelist(&self) -> Vec; + #[cfg(not(target_arch = "riscv64"))] /// Register seccomp rules in syscall whitelist to seccomp. fn register_seccomp(&self, balloon_enable: bool) -> Result<()> { let mut seccomp_filter = SyscallFilter::new(SeccompOpt::Trap); diff --git a/machine/src/micro_common/mod.rs b/machine/src/micro_common/mod.rs index 8aee4003bdaf3891f06e0468fe3995be37872490..7fb530ef952194ac5a97abe7282ef757e6af30dd 100644 --- a/machine/src/micro_common/mod.rs +++ b/machine/src/micro_common/mod.rs @@ -27,7 +27,9 @@ //! //! - `x86_64` //! - `aarch64` +//! - `riscv64` +#[cfg(not(target_arch = "riscv64"))] pub mod syscall; use std::fmt; @@ -43,6 +45,8 @@ use log::{error, info}; #[cfg(target_arch = "aarch64")] use crate::aarch64::micro::{LayoutEntryType, MEM_LAYOUT}; +#[cfg(target_arch = "riscv64")] +use crate::riscv64::micro::{LayoutEntryType, MEM_LAYOUT}; #[cfg(target_arch = "x86_64")] use crate::x86_64::micro::{LayoutEntryType, MEM_LAYOUT}; use crate::{MachineBase, MachineError, MachineOps}; @@ -646,6 +650,8 @@ impl DeviceInterface for LightMachine { let cpu_type = String::from("host-x86-cpu"); #[cfg(target_arch = "aarch64")] let cpu_type = String::from("host-aarch64-cpu"); + #[cfg(target_arch = "riscv64")] + let cpu_type = String::from("host-riscv64-cpu"); for cpu_index in 0..self.base.cpu_topo.max_cpus { if self.base.cpu_topo.get_mask(cpu_index as usize) == 0 { diff --git a/machine/src/riscv64/fdt.rs b/machine/src/riscv64/fdt.rs new file mode 100644 index 0000000000000000000000000000000000000000..286660bc859c543c7648a1178aa52cd23acce332 --- /dev/null +++ b/machine/src/riscv64/fdt.rs @@ -0,0 +1,219 @@ +// Copyright (c) 2024 Institute of Software, CAS. 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 anyhow::Result; + +use crate::MachineBase; +use devices::sysbus::{SysBusDevType, SysRes}; +use util::device_tree::{self, FdtBuilder}; + +/// Function that helps to generate arm pmu in device-tree. +/// +/// # Arguments +/// +/// * `fdt` - Flatted device-tree blob where node will be filled into. + +/// Function that helps to generate serial node in device-tree. +/// +/// # Arguments +/// +/// * `dev_info` - Device resource info of serial device. +/// * `fdt` - Flatted device-tree blob where serial node will be filled into. +fn generate_serial_device_node(fdt: &mut FdtBuilder, res: &SysRes) -> Result<()> { + let node = format!("serial@{:x}", res.region_base); + let serial_node_dep = fdt.begin_node(&node)?; + fdt.set_property_string("compatible", "ns16550a")?; + fdt.set_property_array_u64("reg", &[res.region_base, res.region_size])?; + fdt.set_property_u32("clock-frequency", 3686400)?; + fdt.set_property_u32("interrupt-parent", device_tree::AIA_APLIC_PHANDLE)?; + let mut cells: Vec = Vec::new(); + cells.push(res.irq as u32); + cells.push(0x4); + fdt.set_property_array_u32("interrupts", &cells)?; + fdt.end_node(serial_node_dep) +} + +/// Function that helps to generate RTC node in device-tree. +/// +/// # Arguments +/// +/// * `dev_info` - Device resource info of RTC device. +/// * `fdt` - Flatted device-tree blob where RTC node will be filled into. +/// Function that helps to generate Virtio-Mmio device's node in device-tree. +/// +/// # Arguments +/// +/// * `dev_info` - Device resource info of Virtio-Mmio device. +/// * `fdt` - Flatted device-tree blob where node will be filled into. +fn generate_virtio_devices_node(fdt: &mut FdtBuilder, res: &SysRes) -> Result<()> { + let node = format!("virtio_mmio@{:x}", res.region_base); + let virtio_node_dep = fdt.begin_node(&node)?; + fdt.set_property_string("compatible", "virtio,mmio")?; + fdt.set_property_u32("interrupt-parent", device_tree::AIA_APLIC_PHANDLE)?; + fdt.set_property_array_u64("reg", &[res.region_base, res.region_size])?; + let mut cells: Vec = Vec::new(); + cells.push(res.irq as u32); + cells.push(0x4); + fdt.set_property_array_u32("interrupts", &cells)?; + fdt.end_node(virtio_node_dep) +} + +/// Function that helps to generate fw-cfg node in device-tree. +/// +/// # Arguments +/// +/// * `dev_info` - Device resource info of fw-cfg device. +/// * `fdt` - Flatted device-tree blob where fw-cfg node will be filled into. +// fn generate_fwcfg_device_node(fdt: &mut FdtBuilder, res: &SysRes) -> Result<()> { +// TODO +// } + +/// Function that helps to generate flash node in device-tree. +/// +/// # Arguments +/// +/// * `dev_info` - Device resource info of fw-cfg device. +/// * `flash` - Flatted device-tree blob where fw-cfg node will be filled into. +// fn generate_flash_device_node(fdt: &mut FdtBuilder, res: &SysRes) -> Result<()> { +// TODO +// } + +/// Trait that helps to generate all nodes in device-tree. +#[allow(clippy::upper_case_acronyms)] +trait CompileFDTHelper { + /// Function that helps to generate cpu nodes. + fn generate_cpu_nodes(&self, fdt: &mut FdtBuilder) -> Result<()>; + /// Function that helps to generate Virtio-mmio devices' nodes. + fn generate_devices_node(&self, fdt: &mut FdtBuilder) -> Result<()>; + // /// Function that helps to generate numa node distances. + // fn generate_distance_node(&self, fdt: &mut FdtBuilder) -> Result<()>; +} + +impl CompileFDTHelper for MachineBase { + fn generate_cpu_nodes(&self, fdt: &mut FdtBuilder) -> Result<()> { + let node = "cpus"; + + let cpus = &self.cpus; + let cpus_node_dep = fdt.begin_node(node)?; + fdt.set_property_u32("#address-cells", 0x01)?; + fdt.set_property_u32("#size-cells", 0x0)?; + let frequency = cpus[0].arch().lock().unwrap().timer_regs().frequency; + fdt.set_property_u32("timebase-frequency", frequency as u32)?; + + let nr_vcpus = cpus.len(); + for cpu_index in 0..nr_vcpus { + let node = format!("cpu@{:x}", cpu_index); + let cpu_node_dep = fdt.begin_node(&node)?; + fdt.set_property_u32("phandle", cpu_index as u32 + device_tree::PHANDLE_CPU)?; + fdt.set_property_string("device_type", "cpu")?; + fdt.set_property_string("compatible", "riscv")?; + + let xlen = self.cpus[cpu_index] + .arch() + .lock() + .unwrap() + .get_xlen() + .to_string(); + let mut isa = format!("rv{}", xlen); + let valid_isa_order = String::from("IEMAFDQCLBJTPVNSUHKORWXYZG"); + for char in valid_isa_order.chars() { + let index = char as u32 - 'A' as u32; + let cpu_isa = self.cpus[cpu_index] + .arch() + .lock() + .unwrap() + .config_regs() + .isa; + if (cpu_isa & (1 << index) as u64) > 0 { + let tmp = char::from('a' as u8 + index as u8); + isa = format!("{}{}", isa, tmp); + } + } + isa = format!("{}_{}", isa, "ssaia"); + + fdt.set_property_string("riscv,isa", &isa)?; + + fdt.set_property_u64("reg", cpu_index as u64)?; + + let node = "interrupt-controller"; + let interrupt_controller = fdt.begin_node(node)?; + fdt.set_property_u32("#interrupt-cells", 1)?; + fdt.set_property_array_u32("interrupt-controller", &Vec::new())?; + fdt.set_property_string("compatible", "riscv,cpu-intc")?; + fdt.set_property_u32( + "phandle", + cpu_index as u32 + device_tree::INTC_PHANDLE_START, + )?; + fdt.end_node(interrupt_controller)?; + + fdt.end_node(cpu_node_dep)?; + } + + let cpu_map_node_dep = fdt.begin_node("cpu-map")?; + for cluster in 0..self.cpu_topo.clusters { + let cluster_name = format!("cluster{}", cluster); + let cluster_node_dep = fdt.begin_node(&cluster_name)?; + for core in 0..self.cpu_topo.cores { + let core_name = format!("core{}", core); + let core_node_dep = fdt.begin_node(&core_name)?; + let vcpuid = self.cpu_topo.cores * cluster + core; + fdt.set_property_u32("cpu", u32::from(vcpuid) + device_tree::PHANDLE_CPU)?; + fdt.end_node(core_node_dep)?; + } + fdt.end_node(cluster_node_dep)?; + } + fdt.end_node(cpu_map_node_dep)?; + + fdt.end_node(cpus_node_dep)?; + + Ok(()) + } + + fn generate_devices_node(&self, fdt: &mut FdtBuilder) -> Result<()> { + let node = "soc"; + let smb_node_dep = fdt.begin_node(node)?; + fdt.set_property_string("compatible", "simple-bus")?; + fdt.set_property_u32("#address-cells", 0x02)?; + fdt.set_property_u32("#size-cells", 0x2)?; + fdt.set_property("ranges", &Vec::new())?; + + for dev in self.sysbus.devices.iter() { + let locked_dev = dev.lock().unwrap(); + match locked_dev.sysbusdev_base().dev_type { + SysBusDevType::Serial => { + generate_serial_device_node(fdt, &locked_dev.sysbusdev_base().res)? + } + SysBusDevType::VirtioMmio => { + generate_virtio_devices_node(fdt, &locked_dev.sysbusdev_base().res)? + } + _ => (), + } + } + + use util::device_tree::CompileFDT; + self.irq_chip.as_ref().unwrap().generate_fdt_node(fdt)?; + + fdt.end_node(smb_node_dep)?; + Ok(()) + } +} + +impl device_tree::CompileFDT for MachineBase { + fn generate_fdt_node(&self, fdt: &mut FdtBuilder) -> Result<()> { + fdt.set_property_string("compatible", "linux,dummy-virt")?; + fdt.set_property_u32("#address-cells", 0x2)?; + fdt.set_property_u32("#size-cells", 0x2)?; + + self.generate_cpu_nodes(fdt)?; + self.generate_devices_node(fdt) + } +} diff --git a/machine/src/riscv64/micro.rs b/machine/src/riscv64/micro.rs new file mode 100644 index 0000000000000000000000000000000000000000..8462b4ed68d82129264d295909ac508fafc4d36a --- /dev/null +++ b/machine/src/riscv64/micro.rs @@ -0,0 +1,281 @@ +// Copyright (c) 2024 Institute of Software, CAS. 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 anyhow::{bail, Context, Result}; +use devices::legacy::Serial; +use log::info; + +use crate::{LightMachine, MachineOps}; +use crate::{MachineBase, MachineError}; +use address_space::{AddressSpace, GuestAddress, Region}; +use cpu::CPUTopology; +// use devices::{legacy::PL031, ICGICConfig, ICGICv2Config, ICGICv3Config, GIC_IRQ_MAX}; +use devices::AIAConfig as InterruptControllerConfig; +use hypervisor::kvm::riscv64::*; +use machine_manager::config::{SerialConfig, VmConfig}; +use migration::{MigrationManager, MigrationStatus}; +use util::{ + device_tree::{self, CompileFDT, FdtBuilder}, + seccomp::{BpfRule, SeccompCmpOpt}, +}; +use virtio::{VirtioDevice, VirtioMmioDevice}; + +/// The type of memory layout entry on riscv64 +#[repr(usize)] +pub enum LayoutEntryType { + AIA, + Uart, + Mmio, + Mem, +} +/// Layout of riscv64 +pub const MEM_LAYOUT: &[(u64, u64)] = &[ + (0x0c00_0000, 0x0400_0000), // AIA + (0x1000_0000, 0x0000_0100), // Uart + (0x1000_1000, 0x0000_1000), // Mmio + (0x8000_0000, 0x80_0000_0000), // Mem +]; + +impl MachineOps for LightMachine { + fn machine_base(&self) -> &MachineBase { + &self.base + } + + fn machine_base_mut(&mut self) -> &mut MachineBase { + &mut self.base + } + + fn init_machine_ram(&self, sys_mem: &Arc, mem_size: u64) -> Result<()> { + let vm_ram = self.get_vm_ram(); + let layout_size = MEM_LAYOUT[LayoutEntryType::Mem as usize].1; + let ram = Region::init_alias_region( + vm_ram.clone(), + 0, + std::cmp::min(layout_size, mem_size), + "pc_ram", + ); + sys_mem + .root() + .add_subregion(ram, MEM_LAYOUT[LayoutEntryType::Mem as usize].0) + } + + fn init_interrupt_controller(&mut self, vcpu_count: u64) -> Result<()> { + let aia_conf = InterruptControllerConfig { + vcpu_count: vcpu_count as u32, + max_irq: 33, + region_range: ( + MEM_LAYOUT[LayoutEntryType::AIA as usize].0, + MEM_LAYOUT[LayoutEntryType::AIA as usize].1, + ), + }; + + let hypervisor = self.get_hypervisor(); + let mut locked_hypervisor = hypervisor.lock().unwrap(); + self.base.irq_chip = Some(locked_hypervisor.create_interrupt_controller(&aia_conf)?); + self.base.irq_chip.as_ref().unwrap().realize()?; + + let irq_manager = locked_hypervisor.create_irq_manager()?; + self.base.sysbus.irq_manager = irq_manager.line_irq_manager; + Ok(()) + } + + // fn add_rtc_device(&mut self) -> Result<()> { + // TODO + // } + + fn add_serial_device(&mut self, config: &SerialConfig) -> Result<()> { + let region_base: u64 = MEM_LAYOUT[LayoutEntryType::Uart as usize].0; + let region_size: u64 = MEM_LAYOUT[LayoutEntryType::Uart as usize].1; + + let serial = Serial::new(config.clone()); + serial + .realize( + &mut self.base.sysbus, + region_base, + region_size, + &self.base.boot_source, + ) + .with_context(|| "Failed to realize Serial") + } + + fn realize(vm: &Arc>, vm_config: &mut VmConfig) -> Result<()> { + let mut locked_vm = vm.lock().unwrap(); + + trace::sysbus(&locked_vm.base.sysbus); + trace::vm_state(&locked_vm.base.vm_state); + + let topology = CPUTopology::new().set_topology(( + vm_config.machine_config.nr_threads, + vm_config.machine_config.nr_cores, + vm_config.machine_config.nr_dies, + )); + trace::cpu_topo(&topology); + locked_vm.base.numa_nodes = locked_vm.add_numa_nodes(vm_config)?; + let locked_hypervisor = locked_vm.base.hypervisor.lock().unwrap(); + locked_hypervisor.init_machine(&locked_vm.base.sys_mem)?; + drop(locked_hypervisor); + locked_vm.init_memory( + &vm_config.machine_config.mem_config, + &locked_vm.base.sys_mem, + vm_config.machine_config.nr_cpus, + )?; + + let boot_config = + locked_vm.load_boot_source(None, MEM_LAYOUT[LayoutEntryType::Mem as usize].0)?; + #[cfg(target_arch = "aarch64")] + let cpu_config = locked_vm.load_cpu_features(vm_config)?; + + let hypervisor = locked_vm.base.hypervisor.clone(); + // vCPUs init,and apply CPU features (for aarch64) + locked_vm.base.cpus.extend(::init_vcpu( + vm.clone(), + hypervisor, + vm_config.machine_config.nr_cpus, + &topology, + &boot_config, + )?); + + locked_vm.init_interrupt_controller(u64::from(vm_config.machine_config.nr_cpus))?; + + #[cfg(target_arch = "aarch64")] + locked_vm.cpu_post_init(&cpu_config)?; + + // Add mmio devices + locked_vm + .create_replaceable_devices() + .with_context(|| "Failed to create replaceable devices.")?; + locked_vm.add_devices(vm_config)?; + trace::replaceable_info(&locked_vm.replaceable_info); + + let mut fdt_helper = FdtBuilder::new(); + locked_vm + .generate_fdt_node(&mut fdt_helper) + .with_context(|| MachineError::GenFdtErr)?; + let fdt_vec = fdt_helper.finish()?; + info!("Dumping FDT to stratovirt.dtb"); + + std::fs::write("./stratovirt.dtb", &fdt_vec).unwrap(); + + locked_vm + .base + .sys_mem + .write( + &mut fdt_vec.as_slice(), + GuestAddress(boot_config.fdt_addr), + fdt_vec.len() as u64, + ) + .with_context(|| MachineError::WrtFdtErr(boot_config.fdt_addr, fdt_vec.len()))?; + + MigrationManager::register_vm_instance(vm.clone()); + MigrationManager::register_migration_instance(locked_vm.base.migration_hypervisor.clone()); + if let Err(e) = MigrationManager::set_status(MigrationStatus::Setup) { + bail!("Failed to set migration status {}", e); + } + + Ok(()) + } + + fn add_virtio_mmio_net(&mut self, vm_config: &mut VmConfig, cfg_args: &str) -> Result<()> { + self.add_virtio_mmio_net(vm_config, cfg_args) + } + + fn add_virtio_mmio_block(&mut self, vm_config: &mut VmConfig, cfg_args: &str) -> Result<()> { + self.add_virtio_mmio_block(vm_config, cfg_args) + } + + fn add_virtio_mmio_device( + &mut self, + name: String, + device: Arc>, + ) -> Result>> { + self.add_virtio_mmio_device(name, device) + } + + #[cfg(not(target_arch = "riscv64"))] + fn syscall_whitelist(&self) -> Vec { + syscall_whitelist() + } +} + +pub(crate) fn arch_ioctl_allow_list(bpf_rule: BpfRule) -> BpfRule { + bpf_rule + .add_constraint(SeccompCmpOpt::Eq, 1, KVM_GET_ONE_REG() as u32) + .add_constraint(SeccompCmpOpt::Eq, 1, KVM_GET_DEVICE_ATTR() as u32) + .add_constraint(SeccompCmpOpt::Eq, 1, KVM_GET_REG_LIST() as u32) +} + +pub(crate) fn arch_syscall_whitelist() -> Vec { + vec![ + BpfRule::new(libc::SYS_epoll_pwait), + BpfRule::new(libc::SYS_newfstatat), + BpfRule::new(libc::SYS_unlinkat), + BpfRule::new(libc::SYS_mkdirat), + ] +} + +/// Trait that helps to generate all nodes in device-tree. +#[allow(clippy::upper_case_acronyms)] +trait CompileFDTHelper { + /// Function that helps to generate memory nodes. + fn generate_memory_node(&self, fdt: &mut FdtBuilder) -> Result<()>; + /// Function that helps to generate the chosen node. + fn generate_chosen_node(&self, fdt: &mut FdtBuilder) -> Result<()>; +} + +impl CompileFDTHelper for LightMachine { + fn generate_memory_node(&self, fdt: &mut FdtBuilder) -> Result<()> { + let mem_base = MEM_LAYOUT[LayoutEntryType::Mem as usize].0; + let mem_size = self.base.sys_mem.memory_end_address().raw_value() + - MEM_LAYOUT[LayoutEntryType::Mem as usize].0; + let node = format!("memory@{:x}", mem_base); + let memory_node_dep = fdt.begin_node(&node)?; + fdt.set_property_string("device_type", "memory")?; + fdt.set_property_array_u64("reg", &[mem_base, mem_size])?; + fdt.end_node(memory_node_dep) + } + + fn generate_chosen_node(&self, fdt: &mut FdtBuilder) -> Result<()> { + let node = "chosen"; + let boot_source = self.base.boot_source.lock().unwrap(); + + let chosen_node_dep = fdt.begin_node(node)?; + let cmdline = &boot_source.kernel_cmdline.to_string(); + fdt.set_property_string("bootargs", cmdline.as_str())?; + + let serial_property_string = format!( + "/soc/serial@{:x}", + MEM_LAYOUT[LayoutEntryType::Uart as usize].0 + ); + fdt.set_property_string("stdout-path", &serial_property_string)?; + + match &boot_source.initrd { + Some(initrd) => { + fdt.set_property_u64("linux,initrd-start", initrd.initrd_addr)?; + fdt.set_property_u64("linux,initrd-end", initrd.initrd_addr + initrd.initrd_size)?; + } + None => {} + } + fdt.end_node(chosen_node_dep) + } +} + +impl device_tree::CompileFDT for LightMachine { + fn generate_fdt_node(&self, fdt: &mut FdtBuilder) -> Result<()> { + let node_dep = fdt.begin_node("")?; + self.base.generate_fdt_node(fdt)?; + self.generate_memory_node(fdt)?; + self.generate_chosen_node(fdt)?; + fdt.end_node(node_dep) + } +} diff --git a/machine/src/riscv64/mod.rs b/machine/src/riscv64/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..2f408186cc886893f5bd8207b144bd03753b15ff --- /dev/null +++ b/machine/src/riscv64/mod.rs @@ -0,0 +1,14 @@ +// Copyright (c) 2024 Institute of Software, CAS. 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. + +mod fdt; +pub mod micro; diff --git a/machine_manager/src/config/machine_config.rs b/machine_manager/src/config/machine_config.rs index 2f9cf70e3371ea7711d993e94e8dea328217edda..b50cc3a231af1ea0623ba3865acd18550d16e821 100644 --- a/machine_manager/src/config/machine_config.rs +++ b/machine_manager/src/config/machine_config.rs @@ -43,6 +43,7 @@ pub const G: u64 = 1024 * 1024 * 1024; pub enum MachineType { None, MicroVm, + #[cfg(not(target_arch = "riscv64"))] StandardVm, } diff --git a/machine_manager/src/machine.rs b/machine_manager/src/machine.rs index a040d6efa25ee06ad8e785384f1ab9bdec0c5197..4579439b8af123768d6daf6546d006df9a146e8d 100644 --- a/machine_manager/src/machine.rs +++ b/machine_manager/src/machine.rs @@ -51,6 +51,7 @@ pub enum VmState { pub enum HypervisorType { #[default] Kvm, + #[cfg(not(target_arch = "riscv64"))] Test, } @@ -61,6 +62,7 @@ impl FromStr for HypervisorType { match s { // Note: "kvm:tcg" is a configuration compatible with libvirt. "kvm" | "kvm:tcg" => Ok(HypervisorType::Kvm), + #[cfg(not(target_arch = "riscv64"))] "test" => Ok(HypervisorType::Test), _ => Err(anyhow!("Not supported or invalid hypervisor type {}.", s)), } @@ -268,6 +270,10 @@ pub trait DeviceInterface { let target = Target { arch: "aarch64".to_string(), }; + #[cfg(target_arch = "riscv64")] + let target = Target { + arch: "riscv64".to_string(), + }; Response::create_response(serde_json::to_value(target).unwrap(), None) } @@ -331,6 +337,16 @@ pub trait DeviceInterface { }; #[cfg(target_arch = "aarch64")] vec_machine.push(machine_info); + #[cfg(target_arch = "riscv64")] + let machine_info = MachineInfo { + hotplug: false, + name: "virt".to_string(), + numa_mem_support: false, + cpu_max: 255, + deprecated: false, + }; + #[cfg(target_arch = "riscv64")] + vec_machine.push(machine_info); Response::create_response(serde_json::to_value(&vec_machine).unwrap(), None) } diff --git a/machine_manager/src/qmp/qmp_schema.rs b/machine_manager/src/qmp/qmp_schema.rs index 4281624fc906da3747d275689fa78bafaf3bc464..e1f944073e0c46c45f5a811d2f6e88fcdcd1ddbc 100644 --- a/machine_manager/src/qmp/qmp_schema.rs +++ b/machine_manager/src/qmp/qmp_schema.rs @@ -843,6 +843,14 @@ pub enum CpuInfo { #[serde(rename = "Arm")] arm: CpuInfoArm, }, + #[serde(rename = "riscv")] + RISCV { + #[serde(flatten)] + common: CpuInfoCommon, + #[serde(flatten)] + #[serde(rename = "RISCV")] + riscv: CpuInfoRISCV, + }, } #[derive(Default, Debug, Clone, Serialize, Deserialize)] @@ -851,6 +859,9 @@ pub struct CpuInfoX86 {} #[derive(Default, Debug, Clone, Serialize, Deserialize)] pub struct CpuInfoArm {} +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct CpuInfoRISCV {} + /// query-status /// /// Query the run status of all VCPUs. diff --git a/migration/src/manager.rs b/migration/src/manager.rs index d381ae3125a46d0b8eb03bbca1d8d6c0c5b9a991..96d99434f4d22fcf3a8915c104b1f22f8306c58b 100644 --- a/migration/src/manager.rs +++ b/migration/src/manager.rs @@ -175,6 +175,9 @@ pub struct Vmm { #[cfg(target_arch = "aarch64")] /// Trait to represent GIC devices(GICv3, GICv3 ITS). pub gic_group: HashMap>, + #[cfg(target_arch = "riscv64")] + /// Trait to represent AIA devices(APLIC, IMSICs). + pub aia_group: HashMap>, #[cfg(target_arch = "x86_64")] /// Trait to represent kvm device. pub kvm: Option>, @@ -362,6 +365,23 @@ impl MigrationManager { locked_vmm.gic_group.insert(translate_id(id), gic); } + /// Register AIA device instance to vmm. + /// + /// # Arguments + /// + /// * `aia_desc` - The `DeviceStateDesc` of AIA instance. + /// * `aia` - The AIA device instance with MigrationHook trait. + #[cfg(target_arch = "riscv64")] + pub fn register_aia_instance(aia_desc: DeviceStateDesc, aia: Arc, id: &str) + where + T: MigrationHook + Sync + Send + 'static, + { + Self::register_device_desc(aia_desc); + + let mut locked_vmm = MIGRATION_MANAGER.vmm.write().unwrap(); + locked_vmm.aia_group.insert(translate_id(id), aia); + } + /// Register migration instance to vmm. /// /// # Arguments diff --git a/migration/src/protocol.rs b/migration/src/protocol.rs index 0d57220f00b7372687d310eaf68808b23d1c9a62..0902eb0c6f8aaefa24cc2e96fad41b4ead7735bc 100644 --- a/migration/src/protocol.rs +++ b/migration/src/protocol.rs @@ -393,6 +393,8 @@ impl Default for MigrationHeader { arch: [b'x', b'8', b'6', b'_', b'6', b'4', b'0', b'0'], #[cfg(target_arch = "aarch64")] arch: [b'a', b'a', b'r', b'c', b'h', b'6', b'4', b'0'], + #[cfg(target_arch = "riscv64")] + arch: [b'r', b'i', b's', b'c', b'v', b'6', b'4', b'0'], desc_len: 0, } } @@ -418,6 +420,8 @@ impl MigrationHeader { let current_arch = [b'x', b'8', b'6', b'_', b'6', b'4', b'0', b'0']; #[cfg(target_arch = "aarch64")] let current_arch = [b'a', b'a', b'r', b'c', b'h', b'6', b'4', b'0']; + #[cfg(target_arch = "riscv64")] + let current_arch = [b'r', b'i', b's', b'c', b'v', b'6', b'4', b'0']; if self.arch != current_arch { return Err(anyhow!(MigrationError::HeaderItemNotFit( "Arch".to_string() diff --git a/migration/src/snapshot.rs b/migration/src/snapshot.rs index 3a449bad8a7213da886363fe1ae260d5a77803aa..1126ff02a95ebd7d59b23472bf13f4b1fd7fe20e 100644 --- a/migration/src/snapshot.rs +++ b/migration/src/snapshot.rs @@ -25,6 +25,7 @@ use util::unix::host_page_size; pub const SERIAL_SNAPSHOT_ID: &str = "serial"; pub const KVM_SNAPSHOT_ID: &str = "kvm"; +pub const AIA_SNAPSHOT_ID: &str = "aia"; pub const GICV3_SNAPSHOT_ID: &str = "gicv3"; pub const GICV3_ITS_SNAPSHOT_ID: &str = "gicv3_its"; pub const PL011_SNAPSHOT_ID: &str = "pl011"; @@ -233,6 +234,16 @@ impl MigrationManager { } } + #[cfg(target_arch = "riscv64")] + { + // Save AIA device state. + let aia_id = translate_id(AIA_SNAPSHOT_ID); + if let Some(aia) = locked_vmm.aia_group.get(&aia_id) { + aia.save_device(aia_id, fd) + .with_context(|| "Failed to save aia state")?; + } + } + Ok(()) } @@ -302,6 +313,18 @@ impl MigrationManager { } } + #[cfg(target_arch = "riscv64")] + { + // Restore AIA group state. + for _ in 0..locked_vmm.aia_group.len() { + let (aia_data, id) = Self::check_vm_state(fd, &snap_desc_db)?; + if let Some(aia) = locked_vmm.aia_group.get(&id) { + aia.restore_device(&aia_data) + .with_context(|| "Failed to restore aia state")?; + } + } + } + Ok(()) } } diff --git a/src/main.rs b/src/main.rs index 4a7775646dda90ebabdad796f37905281f64df5c..0bcb5ce783a44517a58de7ef910d4b747f5b086b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,7 +18,9 @@ use anyhow::{bail, Context, Result}; use log::{error, info}; use thiserror::Error; -use machine::{type_init, LightMachine, MachineOps, StdMachine}; +#[cfg(not(target_arch = "riscv64"))] +use machine::StdMachine; +use machine::{type_init, LightMachine, MachineOps}; use machine_manager::{ cmdline::{check_api_channel, create_args_parser, create_vmconfig}, config::MachineType, @@ -169,6 +171,7 @@ fn real_main(cmd_args: &arg_parser::ArgMatches, vm_config: &mut VmConfig) -> Res } vm } + #[cfg(not(target_arch = "riscv64"))] MachineType::StandardVm => { let vm = Arc::new(Mutex::new( StdMachine::new(vm_config).with_context(|| "Failed to init StandardVM")?, @@ -195,27 +198,37 @@ fn real_main(cmd_args: &arg_parser::ArgMatches, vm_config: &mut VmConfig) -> Res vm } MachineType::None => { - if is_test_enabled() { - panic!("please specify machine type.") - } - let vm = Arc::new(Mutex::new( - StdMachine::new(vm_config).with_context(|| "Failed to init NoneVM")?, - )); - EventLoop::set_manager(vm.clone()); + #[cfg(not(target_arch = "riscv64"))] + { + if is_test_enabled() { + panic!("please specify machine type.") + } + let vm = Arc::new(Mutex::new( + StdMachine::new(vm_config).with_context(|| "Failed to init NoneVM")?, + )); + EventLoop::set_manager(vm.clone()); - for listener in listeners { - sockets.push(Socket::from_listener(listener, Some(vm.clone()))); + for listener in listeners { + sockets.push(Socket::from_listener(listener, Some(vm.clone()))); + } + vm + } + #[cfg(target_arch = "riscv64")] + { + panic!() } - vm } }; - let balloon_switch_on = vm_config.dev_name.get("balloon").is_some(); - if !cmd_args.is_present("disable-seccomp") { - vm.lock() - .unwrap() - .register_seccomp(balloon_switch_on) - .with_context(|| "Failed to register seccomp rules.")?; + #[cfg(not(target_arch = "riscv64"))] + { + let balloon_switch_on = vm_config.dev_name.get("balloon").is_some(); + if !cmd_args.is_present("disable-seccomp") { + vm.lock() + .unwrap() + .register_seccomp(balloon_switch_on) + .with_context(|| "Failed to register seccomp rules.")?; + } } for socket in sockets { diff --git a/util/src/device_tree.rs b/util/src/device_tree.rs index ebb50d0e28729c0dc879c4d4111aa38d411e5d36..9fb6783763e7dcdf6226df3e2dc655dce26acea9 100644 --- a/util/src/device_tree.rs +++ b/util/src/device_tree.rs @@ -27,6 +27,12 @@ pub const GIC_FDT_IRQ_TYPE_PPI: u32 = 1; pub const IRQ_TYPE_EDGE_RISING: u32 = 1; pub const IRQ_TYPE_LEVEL_HIGH: u32 = 4; +// PHANDEL definitions for RISC-V +pub const PHANDLE_CPU: u32 = 1; +pub const AIA_APLIC_PHANDLE: u32 = 6; +pub const AIA_IMSIC_PHANDLE: u32 = 4; +pub const INTC_PHANDLE_START: u32 = 2; + pub const FDT_MAX_SIZE: u32 = 0x1_0000; // Magic number in fdt header(big-endian). diff --git a/util/src/lib.rs b/util/src/lib.rs index f861d6f6a1cca1d01970d52d8b25957f4015df1d..37bb3facbffe1ee59ab9a8fe27e1f1dc917f1394 100644 --- a/util/src/lib.rs +++ b/util/src/lib.rs @@ -17,7 +17,7 @@ pub mod byte_code; pub mod checksum; pub mod clock; pub mod daemonize; -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] pub mod device_tree; pub mod edid; pub mod error; diff --git a/util/src/seccomp.rs b/util/src/seccomp.rs index 41206a3bce317608b84695fd1e9af87920b61a70..d3033d48ccc0c827173e3a32fca8a9cdc85a1bc3 100644 --- a/util/src/seccomp.rs +++ b/util/src/seccomp.rs @@ -24,6 +24,7 @@ //! //! - `x86_64` //! - `aarch64` +//! - `riscv64` //! //! ## Examples //! @@ -54,6 +55,8 @@ //! let nr = libc::SYS_open; //! #[cfg(target_arch = "aarch64")] //! let nr = libc::SYS_openat; +//! #[cfg(target_arch = "riscv64")] +//! let nr = libc::SYS_openat; //! nr //! }; //! @@ -127,6 +130,8 @@ const SECCOMP_FILETER_FLAG_TSYNC: u32 = 1; const EM_X86_64: u32 = 62; #[cfg(target_arch = "aarch64")] const EM_AARCH64: u32 = 183; +#[cfg(target_arch = "riscv64")] +const EM_RISCV64: u32 = 243; const __AUDIT_ATCH_64BIT: u32 = 0x8000_0000; const __AUDIT_ARCH_LE: u32 = 0x4000_0000; #[cfg(target_arch = "x86_64")] @@ -135,6 +140,9 @@ const AUDIT_ARCH_X86_64: u32 = EM_X86_64 | __AUDIT_ATCH_64BIT | __AUDIT_ARCH_LE; #[cfg(target_arch = "aarch64")] /// See: https://elixir.bootlin.com/linux/v4.19.123/source/include/uapi/linux/audit.h#L376 const AUDIT_ARCH_AARCH64: u32 = EM_AARCH64 | __AUDIT_ATCH_64BIT | __AUDIT_ARCH_LE; +#[cfg(target_arch = "riscv64")] +/// See: https://elixir.bootlin.com/linux/v5.0/source/include/uapi/linux/audit.h#L376 +const AUDIT_ARCH_RISCV64: u32 = EM_RISCV64 | __AUDIT_ATCH_64BIT | __AUDIT_ARCH_LE; /// Compared operator in bpf filter rule. #[derive(Copy, Clone, PartialEq, Eq)] @@ -270,6 +278,8 @@ fn validate_architecture() -> Vec { bpf_jump(BPF_JMP + BPF_JEQ, AUDIT_ARCH_X86_64, 1, 0), #[cfg(target_arch = "aarch64")] bpf_jump(BPF_JMP + BPF_JEQ, AUDIT_ARCH_AARCH64, 1, 0), + #[cfg(target_arch = "riscv64")] + bpf_jump(BPF_JMP + BPF_JEQ, AUDIT_ARCH_RISCV64, 1, 0), bpf_stmt(BPF_RET + BPF_K, SECCOMP_RET_KILL), ] } @@ -494,6 +504,8 @@ mod tests { k: 0xC000_003E, #[cfg(target_arch = "aarch64")] k: 0xC000_00B7, + #[cfg(target_arch = "riscv64")] + k: 0xC000_00F3, }, // Ret kill SockFilter { @@ -518,6 +530,8 @@ mod tests { k: 0, #[cfg(target_arch = "aarch64")] k: 63, + #[cfg(target_arch = "riscv64")] + k: 63, }, // Ret allow SockFilter { @@ -555,6 +569,8 @@ mod tests { k: 0xC000_003E, #[cfg(target_arch = "aarch64")] k: 0xC000_00B7, + #[cfg(target_arch = "riscv64")] + k: 0xC000_00F3, }, // Ret kill SockFilter { @@ -579,6 +595,8 @@ mod tests { k: 0, #[cfg(target_arch = "aarch64")] k: 63, + #[cfg(target_arch = "riscv64")] + k: 63, }, // Load arg SockFilter { @@ -627,6 +645,8 @@ mod tests { k: 0, #[cfg(target_arch = "aarch64")] k: 63, + #[cfg(target_arch = "riscv64")] + k: 63, }, // Load arg SockFilter {