From c99660a8f9dafb8e9e39e1b52f2f8b7809634a35 Mon Sep 17 00:00:00 2001 From: Ruoqing He Date: Tue, 11 Mar 2025 08:55:07 +0800 Subject: [PATCH] vmm: Introduce riscv64 architecture support Integrate all works done previously to enable booting riscv linux on riscv platforms, example command: ```console ./target/debug/cloud-hypervisor \ --kernel path/to/kernel \ --disk path=path/to/disk \ --cmdline "console=hvc0 root=/dev/vda rw" \ --cpus boot=1 \ --memory size=1024M \ --seccomp false ``` Signed-off-by: Ruoqing He --- vmm/src/cpu.rs | 11 ++- vmm/src/device_manager.rs | 155 ++++++++++++++++++++++++++++++++++--- vmm/src/lib.rs | 4 + vmm/src/memory_manager.rs | 6 ++ vmm/src/pci_segment.rs | 2 +- vmm/src/seccomp_filters.rs | 6 ++ vmm/src/serial_manager.rs | 8 +- vmm/src/vm.rs | 139 +++++++++++++++++++++++++++++++-- 8 files changed, 306 insertions(+), 25 deletions(-) diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index 58f528c8e..20e50d8c7 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -21,6 +21,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Barrier, Mutex}; use std::{cmp, io, result, thread}; +#[cfg(not(target_arch = "riscv64"))] use acpi_tables::sdt::Sdt; use acpi_tables::{aml, Aml}; use anyhow::anyhow; @@ -390,6 +391,8 @@ impl Vcpu { self.mpidr = arch::configure_vcpu(&self.vcpu, self.id, boot_setup) .map_err(Error::VcpuConfiguration)?; } + #[cfg(target_arch = "riscv64")] + arch::configure_vcpu(&self.vcpu, self.id, boot_setup).map_err(Error::VcpuConfiguration)?; info!("Configuring vCPU: cpu_id = {}", self.id); #[cfg(target_arch = "x86_64")] arch::configure_vcpu( @@ -413,7 +416,7 @@ impl Vcpu { } /// Gets the saved vCPU state. - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] pub fn get_saved_state(&self) -> Option { self.saved_state.clone() } @@ -797,7 +800,7 @@ impl CpuManager { let topology = self.get_vcpu_topology(); #[cfg(target_arch = "x86_64")] let x2apic_id = arch::x86_64::get_x2apic_id(cpu_id as u32, topology); - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] let x2apic_id = cpu_id as u32; let mut vcpu = Vcpu::new( @@ -871,6 +874,9 @@ impl CpuManager { #[cfg(target_arch = "aarch64")] vcpu.configure(&self.vm, boot_setup)?; + #[cfg(target_arch = "riscv64")] + vcpu.configure(boot_setup)?; + Ok(()) } @@ -1396,6 +1402,7 @@ impl CpuManager { .map(|t| (t.threads_per_core, t.cores_per_die, t.packages)) } + #[cfg(not(target_arch = "riscv64"))] pub fn create_madt(&self) -> Sdt { use crate::acpi; // This is also checked in the commandline parsing. diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index aaf3e5eb0..519ab7b1c 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -18,15 +18,18 @@ use std::os::unix::io::{AsRawFd, FromRawFd}; use std::path::PathBuf; use std::result; use std::sync::{Arc, Mutex}; +#[cfg(not(target_arch = "riscv64"))] use std::time::Instant; use acpi_tables::sdt::GenericAddress; +#[cfg(not(target_arch = "riscv64"))] use acpi_tables::{aml, Aml}; +#[cfg(not(target_arch = "riscv64"))] use anyhow::anyhow; #[cfg(target_arch = "x86_64")] use arch::layout::{APIC_START, IOAPIC_SIZE, IOAPIC_START}; use arch::{layout, NumaNodes}; -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] use arch::{DeviceType, MmioDeviceInfo}; use block::async_io::DiskFile; use block::fixed_vhd_sync::FixedVhdDiskSync; @@ -39,6 +42,10 @@ use block::{ }; #[cfg(feature = "io_uring")] use block::{fixed_vhd_async::FixedVhdDiskAsync, raw_async::RawFileDisk}; +#[cfg(target_arch = "riscv64")] +use devices::aia; +#[cfg(target_arch = "x86_64")] +use devices::debug_console; #[cfg(target_arch = "x86_64")] use devices::debug_console::DebugConsole; #[cfg(target_arch = "aarch64")] @@ -48,6 +55,8 @@ use devices::interrupt_controller::InterruptController; use devices::ioapic; #[cfg(target_arch = "aarch64")] use devices::legacy::Pl011; +#[cfg(any(target_arch = "x86_64", target_arch = "riscv64"))] +use devices::legacy::Serial; #[cfg(feature = "pvmemcontrol")] use devices::pvmemcontrol::{PvmemcontrolBusDevice, PvmemcontrolPciDevice}; use devices::{interrupt_controller, AcpiNotificationFlags}; @@ -88,8 +97,6 @@ use vm_migration::{ }; use vm_virtio::{AccessPlatform, VirtioDeviceType}; use vmm_sys_util::eventfd::EventFd; -#[cfg(target_arch = "x86_64")] -use {devices::debug_console, devices::legacy::Serial}; use crate::console_devices::{ConsoleDeviceError, ConsoleInfo, ConsoleOutput}; use crate::cpu::{CpuManager, CPU_MANAGER_ACPI_SIZE}; @@ -105,7 +112,7 @@ use crate::vm_config::{ }; use crate::{device_node, GuestRegionMmap, PciDeviceInfo, DEVICE_MANAGER_SNAPSHOT_ID}; -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] const MMIO_LEN: u64 = 0x1000; // Singleton devices / devices the user cannot name @@ -823,9 +830,11 @@ pub struct DeviceManager { interrupt_controller: Option>>, #[cfg(target_arch = "aarch64")] interrupt_controller: Option>>, + #[cfg(target_arch = "riscv64")] + interrupt_controller: Option>>, - // Things to be added to the commandline (e.g. aarch64 early console) - #[cfg(target_arch = "aarch64")] + // Things to be added to the commandline (e.g. aarch64 or riscv64 early console) + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] cmdline_additions: Vec, // ACPI GED notification device @@ -888,7 +897,7 @@ pub struct DeviceManager { exit_evt: EventFd, reset_evt: EventFd, - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] id_to_dev_info: HashMap<(DeviceType, String), MmioDeviceInfo>, // seccomp action @@ -904,6 +913,7 @@ pub struct DeviceManager { // activation and thus start the threads from the VMM thread activate_evt: EventFd, + #[cfg(not(target_arch = "riscv64"))] acpi_address: GuestAddress, selected_segment: usize, @@ -936,12 +946,14 @@ pub struct DeviceManager { // List of unique identifiers provided at boot through the configuration. boot_id_list: BTreeSet, + #[cfg(not(target_arch = "riscv64"))] // Start time of the VM timestamp: Instant, // Pending activations pending_activations: Arc>>, + #[cfg(not(target_arch = "riscv64"))] // Addresses for ACPI platform devices e.g. ACPI PM timer, sleep/reset registers acpi_platform_addresses: AcpiPlatformAddresses, @@ -996,7 +1008,7 @@ impl DeviceManager { activate_evt: &EventFd, force_iommu: bool, boot_id_list: BTreeSet, - timestamp: Instant, + #[cfg(not(target_arch = "riscv64"))] timestamp: Instant, snapshot: Option, dynamic: bool, ) -> DeviceManagerResult>> { @@ -1161,7 +1173,7 @@ impl DeviceManager { address_manager: Arc::clone(&address_manager), console: Arc::new(Console::default()), interrupt_controller: None, - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] cmdline_additions: Vec::new(), ged_notification_device: None, config, @@ -1181,7 +1193,7 @@ impl DeviceManager { device_tree, exit_evt, reset_evt, - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] id_to_dev_info: HashMap::new(), seccomp_action, numa_nodes, @@ -1189,6 +1201,7 @@ impl DeviceManager { activate_evt: activate_evt .try_clone() .map_err(DeviceManagerError::EventFd)?, + #[cfg(not(target_arch = "riscv64"))] acpi_address, selected_segment: 0, serial_manager: None, @@ -1204,8 +1217,10 @@ impl DeviceManager { io_uring_supported: None, aio_supported: None, boot_id_list, + #[cfg(not(target_arch = "riscv64"))] timestamp, pending_activations: Arc::new(Mutex::new(Vec::default())), + #[cfg(not(target_arch = "riscv64"))] acpi_platform_addresses: AcpiPlatformAddresses::default(), snapshot, rate_limit_groups, @@ -1299,6 +1314,7 @@ impl DeviceManager { console_resize_pipe, )?; + #[cfg(not(target_arch = "riscv64"))] if let Some(tpm) = self.config.clone().lock().unwrap().tpm.as_ref() { let tpm_dev = self.add_tpm_device(tpm.socket.clone())?; self.bus_devices @@ -1347,11 +1363,21 @@ impl DeviceManager { vgic_config.msi_addr + vgic_config.msi_size - 1, ) } + #[cfg(target_arch = "riscv64")] + { + let vcpus = self.config.lock().unwrap().cpus.boot_vcpus; + let vaia_config = aia::Aia::create_default_config(vcpus.into()); + ( + vaia_config.imsic_addr, + vaia_config.imsic_addr + vaia_config.vcpu_count as u64 * arch::layout::IMSIC_SIZE + - 1, + ) + } #[cfg(target_arch = "x86_64")] (0xfee0_0000, 0xfeef_ffff) } - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] /// Gets the information of the devices registered up to some point in time. pub fn get_device_info(&self) -> &HashMap<(DeviceType, String), MmioDeviceInfo> { &self.id_to_dev_info @@ -1516,6 +1542,43 @@ impl DeviceManager { self.interrupt_controller.as_ref() } + #[cfg(target_arch = "riscv64")] + fn add_interrupt_controller( + &mut self, + ) -> DeviceManagerResult>> { + let interrupt_controller: Arc> = Arc::new(Mutex::new( + aia::Aia::new( + self.config.lock().unwrap().cpus.boot_vcpus, + Arc::clone(&self.msi_interrupt_manager), + self.address_manager.vm.clone(), + ) + .map_err(DeviceManagerError::CreateInterruptController)?, + )); + + self.interrupt_controller = Some(interrupt_controller.clone()); + + // Restore the vAia if this is in the process of restoration + let id = String::from(aia::_AIA_SNAPSHOT_ID); + if let Some(_vaia_snapshot) = snapshot_from_id(self.snapshot.as_ref(), &id) { + // TODO: vAia snapshotting and restoration is scheduled to next stage of riscv64 support. + // TODO: PMU support is scheduled to next stage of riscv64 support. + // PMU support is optional. Nothing should be impacted if the PMU initialization failed. + unimplemented!() + } + + self.device_tree + .lock() + .unwrap() + .insert(id.clone(), device_node!(id, interrupt_controller)); + + Ok(interrupt_controller) + } + + #[cfg(target_arch = "riscv64")] + pub fn get_interrupt_controller(&mut self) -> Option<&Arc>> { + self.interrupt_controller.as_ref() + } + #[cfg(target_arch = "x86_64")] fn add_interrupt_controller( &mut self, @@ -1995,6 +2058,69 @@ impl DeviceManager { Ok(serial) } + #[cfg(target_arch = "riscv64")] + fn add_serial_device( + &mut self, + interrupt_manager: &Arc>, + serial_writer: Option>, + ) -> DeviceManagerResult>> { + let id = String::from(SERIAL_DEVICE_NAME); + + let serial_irq = self + .address_manager + .allocator + .lock() + .unwrap() + .allocate_irq() + .unwrap(); + + let interrupt_group = interrupt_manager + .create_group(LegacyIrqGroupConfig { + irq: serial_irq as InterruptIndex, + }) + .map_err(DeviceManagerError::CreateInterruptGroup)?; + + let serial = Arc::new(Mutex::new(Serial::new( + id.clone(), + interrupt_group, + serial_writer, + state_from_id(self.snapshot.as_ref(), id.as_str()) + .map_err(DeviceManagerError::RestoreGetState)?, + ))); + + self.bus_devices + .push(Arc::clone(&serial) as Arc); + + let addr = arch::layout::LEGACY_SERIAL_MAPPED_IO_START; + + self.address_manager + .mmio_bus + .insert(serial.clone(), addr.0, MMIO_LEN) + .map_err(DeviceManagerError::BusError)?; + + self.id_to_dev_info.insert( + (DeviceType::Serial, DeviceType::Serial.to_string()), + MmioDeviceInfo { + addr: addr.0, + len: MMIO_LEN, + irq: serial_irq, + }, + ); + + self.cmdline_additions + .push(format!("earlycon=uart,mmio,0x{:08x}", addr.0)); + + // Fill the device tree with a new node. In case of restore, we + // know there is nothing to do, so we can simply override the + // existing entry. + self.device_tree + .lock() + .unwrap() + .insert(id.clone(), device_node!(id, serial)); + + Ok(serial) + } + fn add_virtio_console_device( &mut self, virtio_devices: &mut Vec, @@ -2159,6 +2285,7 @@ impl DeviceManager { Ok(Arc::new(Console { console_resizer })) } + #[cfg(not(target_arch = "riscv64"))] fn add_tpm_device( &mut self, tpm_path: PathBuf, @@ -3905,7 +4032,7 @@ impl DeviceManager { &self.pci_segments } - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] pub fn cmdline_additions(&self) -> &[String] { self.cmdline_additions.as_slice() } @@ -4491,6 +4618,7 @@ impl DeviceManager { Ok(()) } + #[cfg(not(target_arch = "riscv64"))] pub(crate) fn acpi_platform_addresses(&self) -> &AcpiPlatformAddresses { &self.acpi_platform_addresses } @@ -4516,8 +4644,10 @@ fn numa_node_id_from_pci_segment_id(numa_nodes: &NumaNodes, pci_segment_id: u16) 0 } +#[cfg(not(target_arch = "riscv64"))] struct TpmDevice {} +#[cfg(not(target_arch = "riscv64"))] impl Aml for TpmDevice { fn to_aml_bytes(&self, sink: &mut dyn acpi_tables::AmlSink) { aml::Device::new( @@ -4539,6 +4669,7 @@ impl Aml for TpmDevice { } } +#[cfg(not(target_arch = "riscv64"))] impl Aml for DeviceManager { fn to_aml_bytes(&self, sink: &mut dyn acpi_tables::AmlSink) { #[cfg(target_arch = "aarch64")] diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index ad6eadfde..a925057db 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -19,6 +19,7 @@ use std::path::PathBuf; use std::rc::Rc; use std::sync::mpsc::{Receiver, RecvError, SendError, Sender}; use std::sync::{Arc, Mutex}; +#[cfg(not(target_arch = "riscv64"))] use std::time::Instant; use std::{io, result, thread}; @@ -64,6 +65,7 @@ use crate::vm_config::{ VmConfig, VsockConfig, }; +#[cfg(not(target_arch = "riscv64"))] mod acpi; pub mod api; mod clone3; @@ -940,6 +942,7 @@ impl Vmm { MigratableError::MigrateReceive(anyhow!("Error cloning activate EventFd: {}", e)) })?; + #[cfg(not(target_arch = "riscv64"))] let timestamp = Instant::now(); let hypervisor_vm = mm.lock().unwrap().vm.clone(); let mut vm = Vm::new_from_memory_manager( @@ -953,6 +956,7 @@ impl Vmm { &self.seccomp_action, self.hypervisor.clone(), activate_evt, + #[cfg(not(target_arch = "riscv64"))] timestamp, self.console_info.clone(), self.console_resize_pipe.clone(), diff --git a/vmm/src/memory_manager.rs b/vmm/src/memory_manager.rs index a4bc741fc..1e725e51a 100644 --- a/vmm/src/memory_manager.rs +++ b/vmm/src/memory_manager.rs @@ -1246,6 +1246,11 @@ impl MemoryManager { memory_manager.add_uefi_flash()?; } + #[cfg(target_arch = "riscv64")] + { + memory_manager.allocate_address_space()?; + } + #[cfg(target_arch = "x86_64")] if let Some(sgx_epc_config) = sgx_epc_config { memory_manager.setup_sgx(sgx_epc_config)?; @@ -1607,6 +1612,7 @@ impl MemoryManager { .checked_add(1) .ok_or(Error::GuestAddressOverFlow)?; + #[cfg(not(target_arch = "riscv64"))] if mem_end < arch::layout::MEM_32BIT_RESERVED_START { return Ok(arch::layout::RAM_64BIT_START); } diff --git a/vmm/src/pci_segment.rs b/vmm/src/pci_segment.rs index 68769178f..010859e05 100644 --- a/vmm/src/pci_segment.rs +++ b/vmm/src/pci_segment.rs @@ -141,7 +141,7 @@ impl PciSegment { Ok(segment) } - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] pub(crate) fn new_default_segment( address_manager: &Arc, mem32_allocator: Arc>, diff --git a/vmm/src/seccomp_filters.rs b/vmm/src/seccomp_filters.rs index 3df3070c9..009dbdf29 100644 --- a/vmm/src/seccomp_filters.rs +++ b/vmm/src/seccomp_filters.rs @@ -447,6 +447,12 @@ fn create_vmm_ioctl_seccomp_rule_kvm() -> Result, BackendError> Ok(arch_rules) } +#[cfg(all(target_arch = "riscv64", feature = "kvm"))] +fn create_vmm_ioctl_seccomp_rule_kvm() -> Result, BackendError> { + let common_rules = create_vmm_ioctl_seccomp_rule_common(HypervisorType::Kvm)?; + Ok(common_rules) +} + #[cfg(all(target_arch = "x86_64", feature = "mshv"))] fn create_vmm_ioctl_seccomp_rule_mshv() -> Result, BackendError> { create_vmm_ioctl_seccomp_rule_common(HypervisorType::Mshv) diff --git a/vmm/src/serial_manager.rs b/vmm/src/serial_manager.rs index ac1bbc058..30aeefc0a 100644 --- a/vmm/src/serial_manager.rs +++ b/vmm/src/serial_manager.rs @@ -16,7 +16,7 @@ use std::{io, result, thread}; #[cfg(target_arch = "aarch64")] use devices::legacy::Pl011; -#[cfg(target_arch = "x86_64")] +#[cfg(any(target_arch = "x86_64", target_arch = "riscv64"))] use devices::legacy::Serial; use libc::EFD_NONBLOCK; use serial_buffer::SerialBuffer; @@ -108,7 +108,7 @@ impl From for EpollDispatch { } pub struct SerialManager { - #[cfg(target_arch = "x86_64")] + #[cfg(any(target_arch = "x86_64", target_arch = "riscv64"))] serial: Arc>, #[cfg(target_arch = "aarch64")] serial: Arc>, @@ -122,7 +122,7 @@ pub struct SerialManager { impl SerialManager { pub fn new( - #[cfg(target_arch = "x86_64")] serial: Arc>, + #[cfg(any(target_arch = "x86_64", target_arch = "riscv64"))] serial: Arc>, #[cfg(target_arch = "aarch64")] serial: Arc>, mut output: ConsoleOutput, socket: Option, @@ -226,7 +226,7 @@ impl SerialManager { // after the connection happened, and if that's the case it flushes // all output from the serial to the PTY. Otherwise, it's a no-op. fn trigger_pty_flush( - #[cfg(target_arch = "x86_64")] serial: &Arc>, + #[cfg(any(target_arch = "x86_64", target_arch = "riscv64"))] serial: &Arc>, #[cfg(target_arch = "aarch64")] serial: &Arc>, pty_write_out: Option<&Arc>, ) -> Result<()> { diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index e8a538300..66eea3390 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -20,6 +20,7 @@ use std::num::Wrapping; use std::ops::Deref; use std::os::unix::net::UnixStream; use std::sync::{Arc, Mutex, RwLock}; +#[cfg(not(target_arch = "riscv64"))] use std::time::Instant; use std::{cmp, result, str, thread}; @@ -28,7 +29,7 @@ use anyhow::anyhow; use arch::layout::{KVM_IDENTITY_MAP_START, KVM_TSS_START}; #[cfg(feature = "tdx")] use arch::x86_64::tdx::TdvfSection; -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] use arch::PciSpaceInfo; use arch::{get_host_cpu_phys_bits, EntryPoint, NumaNode, NumaNodes}; #[cfg(target_arch = "aarch64")] @@ -47,7 +48,7 @@ use linux_loader::elf; use linux_loader::loader::bzimage::BzImage; #[cfg(target_arch = "x86_64")] use linux_loader::loader::elf::PvhBootCapability::PvhEntryPresent; -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] use linux_loader::loader::pe::Error::InvalidImageMagicNumber; use linux_loader::loader::KernelLoader; use seccompiler::SeccompAction; @@ -466,8 +467,10 @@ pub struct Vm { vm: Arc, #[cfg(target_arch = "x86_64")] saved_clock: Option, + #[cfg(not(target_arch = "riscv64"))] numa_nodes: NumaNodes, #[cfg_attr(any(not(feature = "kvm"), target_arch = "aarch64"), allow(dead_code))] + #[cfg(not(target_arch = "riscv64"))] hypervisor: Arc, stop_on_boot: bool, load_payload_handle: Option>>, @@ -487,7 +490,7 @@ impl Vm { seccomp_action: &SeccompAction, hypervisor: Arc, activate_evt: EventFd, - timestamp: Instant, + #[cfg(not(target_arch = "riscv64"))] timestamp: Instant, console_info: Option, console_resize_pipe: Option>, original_termios: Arc>>, @@ -634,6 +637,7 @@ impl Vm { &activate_evt, force_iommu, boot_id_list, + #[cfg(not(target_arch = "riscv64"))] timestamp, snapshot_from_id(snapshot.as_ref(), DEVICE_MANAGER_SNAPSHOT_ID), dynamic, @@ -694,7 +698,9 @@ impl Vm { vm, #[cfg(target_arch = "x86_64")] saved_clock, + #[cfg(not(target_arch = "riscv64"))] numa_nodes, + #[cfg(not(target_arch = "riscv64"))] hypervisor, stop_on_boot, load_payload_handle, @@ -803,6 +809,7 @@ impl Vm { ) -> Result { trace_scoped!("Vm::new"); + #[cfg(not(target_arch = "riscv64"))] let timestamp = Instant::now(); #[cfg(feature = "tdx")] @@ -873,6 +880,7 @@ impl Vm { seccomp_action, hypervisor, activate_evt, + #[cfg(not(target_arch = "riscv64"))] timestamp, console_info, console_resize_pipe, @@ -943,14 +951,16 @@ impl Vm { pub fn generate_cmdline( payload: &PayloadConfig, - #[cfg(target_arch = "aarch64")] device_manager: &Arc>, + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] device_manager: &Arc< + Mutex, + >, ) -> Result { let mut cmdline = Cmdline::new(arch::CMDLINE_MAX_SIZE).map_err(Error::CmdLineCreate)?; if let Some(s) = payload.cmdline.as_ref() { cmdline.insert_str(s).map_err(Error::CmdLineInsertStr)?; } - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] for entry in device_manager.lock().unwrap().cmdline_additions() { cmdline.insert_str(entry).map_err(Error::CmdLineInsertStr)?; } @@ -1005,6 +1015,47 @@ impl Vm { Ok(EntryPoint { entry_addr }) } + #[cfg(target_arch = "riscv64")] + fn load_kernel( + firmware: Option, + kernel: Option, + memory_manager: Arc>, + ) -> Result { + let guest_memory = memory_manager.lock().as_ref().unwrap().guest_memory(); + let mem = guest_memory.memory(); + let alignment = 0x20_0000; + let aligned_kernel_addr = arch::layout::KERNEL_START.0 + (alignment - 1) & !(alignment - 1); + let entry_addr = match (firmware, kernel) { + (None, Some(mut kernel)) => { + match linux_loader::loader::pe::PE::load( + mem.deref(), + Some(GuestAddress(aligned_kernel_addr)), + &mut kernel, + None, + ) { + Ok(entry_addr) => entry_addr.kernel_load, + // Try to load the binary as kernel PE file at first. + // If failed, retry to load it as UEFI binary. + // As the UEFI binary is formatless, it must be the last option to try. + Err(linux_loader::loader::Error::Pe(InvalidImageMagicNumber)) => { + // TODO: UEFI for riscv64 is scheduled to next stage. + unimplemented!() + } + Err(e) => { + return Err(Error::KernelLoad(e)); + } + } + } + (Some(_firmware), None) => { + // TODO: UEFI for riscv64 is scheduled to next stage. + unimplemented!() + } + _ => return Err(Error::InvalidPayload), + }; + + Ok(EntryPoint { entry_addr }) + } + #[cfg(feature = "igvm")] fn load_igvm( igvm: File, @@ -1133,7 +1184,7 @@ impl Vm { } } - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] fn load_payload( payload: &PayloadConfig, memory_manager: Arc>, @@ -1351,6 +1402,72 @@ impl Vm { Ok(()) } + #[cfg(target_arch = "riscv64")] + fn configure_system(&mut self) -> Result<()> { + let cmdline = Self::generate_cmdline( + self.config.lock().unwrap().payload.as_ref().unwrap(), + &self.device_manager, + )?; + let num_vcpu = self.cpu_manager.lock().unwrap().vcpus().len(); + let mem = self.memory_manager.lock().unwrap().boot_guest_memory(); + let mut pci_space_info: Vec = Vec::new(); + let initramfs_config = match self.initramfs { + Some(_) => Some(self.load_initramfs(&mem)?), + None => None, + }; + + let device_info = &self + .device_manager + .lock() + .unwrap() + .get_device_info() + .clone(); + + for pci_segment in self.device_manager.lock().unwrap().pci_segments().iter() { + let pci_space = PciSpaceInfo { + pci_segment_id: pci_segment.id, + mmio_config_address: pci_segment.mmio_config_address, + pci_device_space_start: pci_segment.start_of_mem64_area, + pci_device_space_size: pci_segment.end_of_mem64_area + - pci_segment.start_of_mem64_area + + 1, + }; + pci_space_info.push(pci_space); + } + + // TODO: IOMMU for riscv64 is not yet support in kernel. + + let vaia = self + .device_manager + .lock() + .unwrap() + .get_interrupt_controller() + .unwrap() + .lock() + .unwrap() + .get_vaia() + .map_err(|_| { + Error::ConfigureSystem(arch::Error::PlatformSpecific( + arch::riscv64::Error::SetupAia, + )) + })?; + + // TODO: PMU support for riscv64 is scheduled to next stage. + + arch::configure_system( + &mem, + cmdline.as_cstring().unwrap().to_str().unwrap(), + num_vcpu as u32, + device_info, + &initramfs_config, + &pci_space_info, + &vaia, + ) + .map_err(Error::ConfigureSystem)?; + + Ok(()) + } + pub fn console_resize_pipe(&self) -> Option> { self.device_manager.lock().unwrap().console_resize_pipe() } @@ -2017,6 +2134,7 @@ impl Vm { // In case of TDX being used, this is a no-op since the tables will be // created and passed when populating the HOB. + #[cfg(not(target_arch = "riscv64"))] fn create_acpi_tables(&self) -> Option { #[cfg(feature = "tdx")] if self.config.lock().unwrap().is_tdx_enabled() { @@ -2118,6 +2236,7 @@ impl Vm { #[cfg(target_arch = "aarch64")] let rsdp_addr = self.create_acpi_tables(); + #[cfg(not(target_arch = "riscv64"))] // Configure shared state based on loaded kernel entry_point .map(|entry_point| { @@ -2127,6 +2246,9 @@ impl Vm { }) .transpose()?; + #[cfg(target_arch = "riscv64")] + self.configure_system().unwrap(); + #[cfg(target_arch = "x86_64")] // Note: For x86, always call this function before invoking start boot vcpus. // Otherwise guest would fail to boot because we haven't created the @@ -2327,6 +2449,11 @@ impl Vm { .map_err(Error::PowerButton) } + #[cfg(target_arch = "riscv64")] + pub fn power_button(&self) -> Result<()> { + unimplemented!() + } + pub fn memory_manager_data(&self) -> MemoryManagerSnapshotData { self.memory_manager.lock().unwrap().snapshot_data() }